jackwener/OpenCLI · error · CommandExecutionError

Pixiv novel returned malformed series metadata

Error message

Pixiv novel returned malformed series metadata

What it means

novelRowFromBody validates the Pixiv /ajax/novel response before building a row. If seriesNavData is present but is an array or a non-object primitive (string, number, boolean), the payload's series metadata is structurally invalid, so the CLI throws CommandExecutionError instead of emitting bad data. This guards against upstream Pixiv API changes or proxy-injected garbage.

Source

Thrown at clis/pixiv/novel.js:33

  if (!body || Array.isArray(body) || typeof body !== 'object') {
    throw new CommandExecutionError(`Pixiv novel ${id} returned malformed detail payload`);
  }
  const novelId = String(body.id ?? '').trim();
  const title = String(body.title ?? '').trim();
  const userName = String(body.userName ?? '').trim();
  const userId = String(body.userId ?? '').trim();
  if (!/^\d+$/.test(novelId) || novelId !== id || !title || !userName || !/^\d+$/.test(userId)) {
    throw new CommandExecutionError(`Pixiv novel ${id} returned malformed detail payload`);
  }
  return { payload: body, identity: { novelId, title, userName, userId } };
}

export function novelRowFromBody(body, id) {
  const normalized = requireNovelBody(body, id);
  const b = normalized.payload;
  const identity = normalized.identity;
  if (b.seriesNavData != null && (Array.isArray(b.seriesNavData) || typeof b.seriesNavData !== 'object')) {
    throw new CommandExecutionError('Pixiv novel returned malformed series metadata');
  }
  const series = b.seriesNavData || {};
  const seriesId = b.seriesId ?? series.seriesId ?? '';
  const seriesTitle = b.seriesTitle ?? series.title ?? '';
  if (seriesId !== '' && !/^\d+$/.test(String(seriesId))) {
    throw new CommandExecutionError('Pixiv novel returned malformed series ID');
  }
  if (seriesTitle !== '' && typeof seriesTitle !== 'string') {
    throw new CommandExecutionError('Pixiv novel returned malformed series title');
  }
  const seriesOrder = series.order ?? b.seriesContentOrder ?? '';
  if (seriesOrder !== '' && (!Number.isSafeInteger(seriesOrder) || seriesOrder < 1)) {
    throw new CommandExecutionError('Pixiv novel returned malformed series order');
  }
  return {
    novel_id: identity.novelId,
    title: identity.title,
    author: identity.userName,

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Log the raw response body from pixivFetch to confirm what shape seriesNavData actually has
  2. Update the CLI to the latest version so the parser matches the current Pixiv API schema
  3. If a proxy/VPN is involved, retry the request with an authenticated session or different egress so Pixiv returns real JSON
  4. As a caller, strip or null-out seriesNavData before invoking novelRowFromBody when you do not need series info

Example fix

// before (caller passing raw/legacy body)
const row = novelRowFromBody(legacyBody, id);
// after
if (Array.isArray(legacyBody.seriesNavData)) legacyBody.seriesNavData = null;
const row = novelRowFromBody(legacyBody, id);
Defensive patterns

Strategy: type-guard

Validate before calling

function hasValidSeriesMeta(body) {
  const s = body?.seriesNavData;
  return s == null || (typeof s === 'object' && !Array.isArray(s));
}
if (!hasValidSeriesMeta(body)) throw new Error('unexpected seriesNavData shape');

Type guard

function isSeriesNavData(v) {
  return v == null || (typeof v === 'object' && !Array.isArray(v));
}

Try / catch

try {
  const row = novelRowFromBody(body, id);
} catch (e) {
  if (e.message.includes('malformed series metadata')) {
    console.warn('seriesNavData not an object; proceeding without series info');
  } else throw e;
}

Prevention

When it happens

Trigger: The Pixiv AJAX novel endpoint returns seriesNavData as an array, a string, a number, or a boolean instead of a null/object (e.g. API schema change, HTML-interstitial or error JSON from a proxy/blocked request, cached/legacy payload shape).

Common situations: Pixiv changes the /ajax/novel/{id} response schema; a scraping proxy or auth wall returns an unexpected body; unit fixtures built from an old API version carry a seriesNavData array; callers pass a hand-crafted body into novelRowFromBody.

Understand the failure class

Related errors


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