jackwener/OpenCLI · error · CommandExecutionError

Bilibili view API did not return pages[] for --page selectio

Error message

Bilibili view API did not return pages[] for --page selection

What it means

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.

Source

Thrown at clis/bilibili/utils.js:119

    if (typeof value === 'number' && Number.isSafeInteger(value) && value >= 1) {
        return value;
    }
    if (typeof value === 'string' && /^[1-9]\d*$/.test(value)) {
        const n = Number(value);
        if (Number.isSafeInteger(n)) return n;
    }
    throw new CommandExecutionError(`Bilibili view API returned a malformed ${label}`);
}

/**
 * 从 view API 的 data.pages 数组取第 N 集(1-based)。
 * page/cid 都以 view API 的 pages[] 为 source-of-truth;缺失、重复或畸形都 fail closed。
 * 返回该集 raw 对象(含 cid / part(分集标题) / page / duration)。
 */
export function selectVideoPart(viewData, pageNum) {
    const pages = Array.isArray(viewData?.pages) ? viewData.pages : null;
    if (!pages || pages.length === 0) {
        throw new CommandExecutionError('Bilibili view API did not return pages[] for --page selection');
    }
    const matches = [];
    for (const entry of pages) {
        if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
            throw new CommandExecutionError('Bilibili view API returned a malformed pages[] entry');
        }
        const apiPage = readApiPositiveInteger(entry.page, 'page number');
        if (apiPage === pageNum) {
            matches.push(entry);
        }
    }
    if (matches.length > 1) {
        throw new CommandExecutionError(`Bilibili view API returned duplicate page entries for p=${pageNum}`);
    }
    const part = matches[0];
    if (!part) {
        const total = pages.length || viewData?.videos || 1;
        throw new CommandExecutionError(`分P 序号超出范围:p=${pageNum}(该视频共 ${total} 集)`);

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Drop the --page flag for single-part videos — pages[] is absent when there is only one part.
  2. Re-fetch the view API and confirm data.pages exists before selecting a part.
  3. Check that you parsed the right response level: pages lives under data.pages of the view payload.
  4. Handle the error by falling back to the default (first/only) part if your use case allows.

Example fix

// before
await cmd(['bili', 'download', url, '--page', '2']); // single-P video, no pages[]
// after
const view = await getViewData(url);
if (Array.isArray(view.pages) && view.pages.length > 0) {
  await cmd(['bili', 'download', url, '--page', '2']);
} else {
  await cmd(['bili', 'download', url]); // default single part
}
Defensive patterns

Strategy: validation

Validate before calling

const pages = Array.isArray(viewData?.pages) ? viewData.pages : null;
if (!pages || pages.length === 0) { /* fall back to single-part download: omit --page */ }

Type guard

function hasPages(v){ return !!v && typeof v==='object' && Array.isArray(v.pages) && v.pages.length>0; }

Try / catch

try { const part = selectVideoPart(viewData, pageNum); } catch (e) { if (/did not return pages/.test(e.message)) return downloadDefaultPart(viewData); throw e; }

Prevention

When it happens

Trigger: 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[].

Common situations: 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.

Related errors


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