jackwener/OpenCLI · error · CommandExecutionError

Bilibili view API returned a malformed payload during paid-c

Error message

Bilibili view API returned a malformed payload during paid-content pre-check

What it means

During the pre-download paid-content check, the /x/web-interface/view response either wasn't an object or lacked a `code` field, so its payment rights cannot be evaluated. The library throws this structured error instead of assuming the video is free (unlike network errors, which are swallowed and treated as pass-through).

Source

Thrown at clis/bilibili/download.js:39

function isObject(value) {
    return value && typeof value === 'object' && !Array.isArray(value);
}

/**
 * 下载前付费预检:付费/会员视频 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)

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Re-run the command; risk-control interstitials are often transient.
  2. Refresh the stored Bilibili cookies / re-login so requests aren't challenged.
  3. Check network path (proxy/VPN) for HTML injection or blocked domains.
  4. Clear cookies entirely to bypass pre-check failures (pre-check failures other than this structured error are non-fatal by design).

Example fix

// caller-side guard mirroring the check
const payload = await apiGet(page, '/x/web-interface/view', { params: { bvid } });
if (!payload || typeof payload !== 'object' || Array.isArray(payload) || !Object.hasOwn(payload, 'code')) {
    // treat as malformed: skip pre-check or retry
}
Defensive patterns

Strategy: try-catch

Validate before calling

const res = await fetch('https://api.bilibili.com/x/web-interface/view?bvid=' + bvid, { headers: { cookie } });
const body = await res.text();
let payload; try { payload = JSON.parse(body); } catch { /* HTML/risk-control page */ }
if (!payload || typeof payload !== 'object' || Array.isArray(payload) || !('code' in payload)) { /* retry or refresh cookies */ }

Type guard

function isViewEnvelope(v) {
    return Boolean(v) && typeof v === 'object' && !Array.isArray(v) && Object.hasOwn(v, 'code');
}

Try / catch

try {
    await download(bvid);
} catch (e) {
    if (/malformed payload during paid-content pre-check/.test(e.message)) {
        // refresh cookies / retry; risk-control interstitials are often transient
    } else throw e;
}

Prevention

When it happens

Trigger: apiGet to /x/web-interface/view succeeds at transport level but returns a non-object body, HTML instead of JSON, or JSON without `code` — e.g. Bilibili risk-control interstitial, gateway HTML error page, or API format change.

Common situations: Bilibili anti-bot risk control serving HTML challenges; region-locked or CDN edge returning error pages; expired/invalid cookies producing an unexpected body shape; corporate proxy injecting content.

Understand the failure class

Related errors


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