{"record":{"id":"2fc6777bbf1a5985","repo":"jackwener/OpenCLI","slug":"bilibili-view-api-returned-a-malformed-label","errorCode":null,"errorMessage":"Bilibili view API returned a malformed ${label}","messagePattern":"Bilibili view API returned a malformed (.+?)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/bilibili/utils.js","lineNumber":108,"sourceCode":"    if (typeof value !== 'string' || !/^[1-9]\\d*$/.test(value)) {\n        throw new ArgumentError(`--page must be a positive decimal integer, got: ${String(value)}`);\n    }\n    const n = Number(value);\n    if (!Number.isSafeInteger(n)) {\n        throw new ArgumentError(`--page is too large: ${value}`);\n    }\n    return n;\n}\n\nfunction readApiPositiveInteger(value, label) {\n    if (typeof value === 'number' && Number.isSafeInteger(value) && value >= 1) {\n        return value;\n    }\n    if (typeof value === 'string' && /^[1-9]\\d*$/.test(value)) {\n        const n = Number(value);\n        if (Number.isSafeInteger(n)) return n;\n    }\n    throw new CommandExecutionError(`Bilibili view API returned a malformed ${label}`);\n}\n\n/**\n * 从 view API 的 data.pages 数组取第 N 集（1-based）。\n * page/cid 都以 view API 的 pages[] 为 source-of-truth；缺失、重复或畸形都 fail closed。\n * 返回该集 raw 对象（含 cid / part(分集标题) / page / duration）。\n */\nexport function selectVideoPart(viewData, pageNum) {\n    const pages = Array.isArray(viewData?.pages) ? viewData.pages : null;\n    if (!pages || pages.length === 0) {\n        throw new CommandExecutionError('Bilibili view API did not return pages[] for --page selection');\n    }\n    const matches = [];\n    for (const entry of pages) {\n        if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {\n            throw new CommandExecutionError('Bilibili view API returned a malformed pages[] entry');\n        }\n        const apiPage = readApiPositiveInteger(entry.page, 'page number');","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/bilibili/utils.js#L90-L126","documentation":"readApiPositiveInteger validates that a numeric field from the Bilibili view API (e.g. page, cid) is a positive integer — either a number or a numeric string matching /^[1-9]\\d*$/. If the value is missing, zero, negative, fractional, non-numeric, or an unsafe integer, the library fails closed because page/cid are the source-of-truth for selecting a video part and using a malformed one would target the wrong resource.","triggerScenarios":"Bilibili's /x/web-interface/view endpoint returns a pages[] entry (or another validated field) whose `page` or `cid` is undefined, null, 0, a float, a negative number, a non-numeric string, or a number beyond Number.MAX_SAFE_INTEGER.","commonSituations":"Bilibili API schema changes or partial responses during incidents; caching proxies/CDNs returning truncated JSON; mocked or replayed responses missing fields; an API regression returning null cid for newly uploaded videos.","solutions":["Re-fetch the view API response — the malformed value is often transient (CDN/cache glitch).","Verify the video actually has multi-page data (check data.pages exists and each entry has page/cid).","Log the full raw response body to see which field and value failed validation.","If Bilibili changed the schema, update the validation/field extraction in clis/bilibili/utils.js accordingly."],"exampleFix":"// before\nconst part = viewData.pages[0];\nconst cid = part.cid; // may be undefined -> CommandExecutionError\n// after\nif (typeof part?.cid === 'number' && Number.isSafeInteger(part.cid) && part.cid > 0) {\n  const cid = part.cid;\n}","handlingStrategy":"validation","validationCode":"function isValidPart(p){ return p && typeof p==='object' && Number.isSafeInteger(p.page) && p.page>0 && Number.isSafeInteger(p.cid) && p.cid>0; }\nconst usable = (viewData?.pages ?? []).filter(isValidPart);","typeGuard":"function isPositiveInteger(v){ return (typeof v==='number' || (typeof v==='string' && /^[1-9]\\d*$/.test(v))) && Number.isSafeInteger(Number(v)) && Number(v)>0; }","tryCatchPattern":"try { const part = selectVideoPart(viewData, pageNum); } catch (e) { if (/malformed/.test(e.message)) { viewData = await refetchViewData(url); /* retry once */ } else throw e; }","preventionTips":["Always validate pages[] entries (page/cid present, positive safe integers) before using them.","Re-fetch the view API once on malformed responses — many are transient CDN glitches.","Pin/monitor the Bilibili view API schema in tests with recorded fixtures.","Fail fast: log the raw response body when validation fails to speed diagnosis."],"tags":["api","validation","bilibili","fail-closed"],"backgroundTag":"malformed-api-response","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}