{"record":{"id":"eb927bb17baa6115","repo":"jackwener/OpenCLI","slug":"bilibili-view-api-returned-malformed-paid-content","errorCode":null,"errorMessage":"Bilibili view API returned malformed paid-content metadata","messagePattern":"Bilibili view API returned malformed paid-content metadata","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/bilibili/download.js","lineNumber":44,"sourceCode":"/**\n * 下载前付费预检：付费/会员视频 yt-dlp 只能拿到试看流或直接失败，\n * 与其跑一半吐一坨 yt-dlp stderr，不如提前抛结构化 PAID_CONTENT（exit 77）。\n *\n * 大会员专享（vip）会再查一次 nav API：当前账号大会员有效就放行（cookie 喂给\n * yt-dlp 能下完整流）。ugc_pay / upower 的购买/充电状态没有廉价查询端点，保守\n * 拦截，已购用户用 --force 跳过。预检自身的 API 失败不阻塞下载（保持旧行为）。\n */\nasync function assertNotPaidContent(page, bvid) {\n    let d;\n    try {\n        const payload = await apiGet(page, '/x/web-interface/view', { params: { bvid } });\n        if (!isObject(payload) || !Object.hasOwn(payload, 'code')) {\n            throw new CommandExecutionError('Bilibili view API returned a malformed payload during paid-content pre-check');\n        }\n        if (payload.code !== 0)\n            return;\n        if (!isObject(payload.data) || !isObject(payload.data.rights)) {\n            throw new CommandExecutionError('Bilibili view API returned malformed paid-content metadata');\n        }\n        d = payload.data;\n    }\n    catch (error) {\n        if (error instanceof CommandExecutionError) {\n            throw error;\n        }\n        return;\n    }\n    const rights = d.rights;\n    const paymentType = rights.pay\n        ? 'vip'\n        : (rights.ugc_pay || rights.arc_pay)\n            ? 'ugc_pay'\n            : d.is_upower_exclusive\n                ? 'upower'\n                : '';\n    if (!paymentType)","sourceCodeStart":26,"sourceCodeEnd":62,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/bilibili/download.js#L26-L62","documentation":"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.","triggerScenarios":"/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.","commonSituations":"Downloading charging-exclusive (充电专属) or course content whose view payload lacks standard rights; Bilibili API drift; intermittent partial responses under load.","solutions":["Retry; if intermittent it's a partial response, not a schema change.","If the video is known-paid and you have entitlement, use --force to skip the pre-check entirely.","Fetch the raw view API response to confirm whether data.rights exists; if Bilibili moved it, the CLI needs an update.","Try a different bvid to determine if it is content-type-specific."],"exampleFix":"// caller-side guard\nconst payload = await apiGet(page, '/x/web-interface/view', { params: { bvid } });\nconst rights = payload?.data?.rights;\nif (payload?.code === 0 && (!rights || typeof rights !== 'object' || Array.isArray(rights))) {\n    // fall back to --force download or retry\n}","handlingStrategy":"type-guard","validationCode":"const payload = JSON.parse(await (await fetch(viewApiUrl, { headers: { cookie } })).text());\nif (payload?.code === 0 && (!(payload.data?.rights) || typeof payload.data.rights !== 'object')) {\n    // rights unavailable: skip pre-check with --force or retry\n}","typeGuard":"function hasRights(payload) {\n    return Boolean(payload?.data) && typeof payload.data === 'object' && !Array.isArray(payload.data)\n        && Boolean(payload.data.rights) && typeof payload.data.rights === 'object';\n}","tryCatchPattern":"try {\n    await download(bvid);\n} catch (e) {\n    if (/malformed paid-content metadata/.test(e.message)) {\n        // fall back to --force if you hold entitlement, or retry\n    } else throw e;\n}","preventionTips":["Use --force for content types known to omit rights (courses, charging-exclusive)","Retry intermittent partial responses before concluding schema drift","Verify data.rights in the raw view API when debugging","Update the CLI when Bilibili relocates the rights object"],"tags":["bilibili","api-schema","paid-content","validation"],"backgroundTag":"malformed-api-response","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}