jackwener/OpenCLI · error · CommandExecutionError

Bilibili view API returned malformed paid-content metadata

Error message

Bilibili view API returned malformed paid-content metadata

What it means

The view API returned code 0 (success) but payload.data or payload.data.rights is not an object, so payment rights (pay, ugc_pay, arc_pay, is_upower_exclusive) cannot be read. The library throws rather than silently treating the video as free, since rights info is the whole point of the pre-check.

Source

Thrown at clis/bilibili/download.js:44

/**
 * 下载前付费预检:付费/会员视频 yt-dlp 只能拿到试看流或直接失败,
 * 与其跑一半吐一坨 yt-dlp stderr,不如提前抛结构化 PAID_CONTENT(exit 77)。
 *
 * 大会员专享(vip)会再查一次 nav API:当前账号大会员有效就放行(cookie 喂给
 * yt-dlp 能下完整流)。ugc_pay / upower 的购买/充电状态没有廉价查询端点,保守
 * 拦截,已购用户用 --force 跳过。预检自身的 API 失败不阻塞下载(保持旧行为)。
 */
async function assertNotPaidContent(page, bvid) {
    let d;
    try {
        const payload = await apiGet(page, '/x/web-interface/view', { params: { bvid } });
        if (!isObject(payload) || !Object.hasOwn(payload, 'code')) {
            throw new CommandExecutionError('Bilibili view API returned a malformed payload during paid-content pre-check');
        }
        if (payload.code !== 0)
            return;
        if (!isObject(payload.data) || !isObject(payload.data.rights)) {
            throw new CommandExecutionError('Bilibili view API returned malformed paid-content metadata');
        }
        d = payload.data;
    }
    catch (error) {
        if (error instanceof CommandExecutionError) {
            throw error;
        }
        return;
    }
    const rights = d.rights;
    const paymentType = rights.pay
        ? 'vip'
        : (rights.ugc_pay || rights.arc_pay)
            ? 'ugc_pay'
            : d.is_upower_exclusive
                ? 'upower'
                : '';
    if (!paymentType)

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Retry; if intermittent it's a partial response, not a schema change.
  2. If the video is known-paid and you have entitlement, use --force to skip the pre-check entirely.
  3. Fetch the raw view API response to confirm whether data.rights exists; if Bilibili moved it, the CLI needs an update.
  4. Try a different bvid to determine if it is content-type-specific.

Example fix

// caller-side guard
const payload = await apiGet(page, '/x/web-interface/view', { params: { bvid } });
const rights = payload?.data?.rights;
if (payload?.code === 0 && (!rights || typeof rights !== 'object' || Array.isArray(rights))) {
    // fall back to --force download or retry
}
Defensive patterns

Strategy: type-guard

Validate before calling

const payload = JSON.parse(await (await fetch(viewApiUrl, { headers: { cookie } })).text());
if (payload?.code === 0 && (!(payload.data?.rights) || typeof payload.data.rights !== 'object')) {
    // rights unavailable: skip pre-check with --force or retry
}

Type guard

function hasRights(payload) {
    return Boolean(payload?.data) && typeof payload.data === 'object' && !Array.isArray(payload.data)
        && Boolean(payload.data.rights) && typeof payload.data.rights === 'object';
}

Try / catch

try {
    await download(bvid);
} catch (e) {
    if (/malformed paid-content metadata/.test(e.message)) {
        // fall back to --force if you hold entitlement, or retry
    } else throw e;
}

Prevention

When it happens

Trigger: /x/web-interface/view responds with code:0 but data or data.rights is missing/null/non-object — seen with partial responses from Bilibili, A/B schema rollouts, or special archive types (courses, charging-exclusive) that omit the rights object.

Common situations: Downloading charging-exclusive (充电专属) or course content whose view payload lacks standard rights; Bilibili API drift; intermittent partial responses under load.

Understand the failure class

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/eb927bb17baa6115. Report an issue: GitHub.