{"record":{"id":"d0a9956d96ce5038","repo":"jackwener/OpenCLI","slug":"bilibili-view-api-did-not-return-pages-for-pag","errorCode":null,"errorMessage":"Bilibili view API did not return pages[] for --page selection","messagePattern":"Bilibili view API did not return pages\\[\\] for --page selection","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/bilibili/utils.js","lineNumber":119,"sourceCode":"    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');\n        if (apiPage === pageNum) {\n            matches.push(entry);\n        }\n    }\n    if (matches.length > 1) {\n        throw new CommandExecutionError(`Bilibili view API returned duplicate page entries for p=${pageNum}`);\n    }\n    const part = matches[0];\n    if (!part) {\n        const total = pages.length || viewData?.videos || 1;\n        throw new CommandExecutionError(`分P 序号超出范围：p=${pageNum}（该视频共 ${total} 集）`);","sourceCodeStart":101,"sourceCodeEnd":137,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/bilibili/utils.js#L101-L137","documentation":"selectVideoPart requires the view API's data.pages[] array as the source-of-truth for --page (分P) selection. If the payload has no pages array or it is empty, the library throws rather than guessing a cid, because single-page videos can omit pages and selecting a part without one would be wrong.","triggerScenarios":"Calling selectVideoPart(viewData, n) where viewData is null/undefined, viewData.pages is not an array, or pages is an empty array — typically because --page was passed for a video whose view response lacks pages[].","commonSituations":"Passing --page 1 to a single-part (单P) video whose API response omits pages[]; Bilibili returning an error-shaped body that slipped past earlier checks; stale cached view data.","solutions":["Drop the --page flag for single-part videos — pages[] is absent when there is only one part.","Re-fetch the view API and confirm data.pages exists before selecting a part.","Check that you parsed the right response level: pages lives under data.pages of the view payload.","Handle the error by falling back to the default (first/only) part if your use case allows."],"exampleFix":"// before\nawait cmd(['bili', 'download', url, '--page', '2']); // single-P video, no pages[]\n// after\nconst view = await getViewData(url);\nif (Array.isArray(view.pages) && view.pages.length > 0) {\n  await cmd(['bili', 'download', url, '--page', '2']);\n} else {\n  await cmd(['bili', 'download', url]); // default single part\n}","handlingStrategy":"validation","validationCode":"const pages = Array.isArray(viewData?.pages) ? viewData.pages : null;\nif (!pages || pages.length === 0) { /* fall back to single-part download: omit --page */ }","typeGuard":"function hasPages(v){ return !!v && typeof v==='object' && Array.isArray(v.pages) && v.pages.length>0; }","tryCatchPattern":"try { const part = selectVideoPart(viewData, pageNum); } catch (e) { if (/did not return pages/.test(e.message)) return downloadDefaultPart(viewData); throw e; }","preventionTips":["Only pass --page when the video actually has multiple parts (pages[] non-empty).","Treat absent pages[] as a single-part video and use the default part.","Cache view responses briefly but re-validate shape before reuse.","Check data.pages (not top-level pages) when inspecting the view payload."],"tags":["api","validation","bilibili","pagination"],"backgroundTag":"malformed-api-response","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}