{"record":{"id":"9fb64875c25f20bb","repo":"jackwener/OpenCLI","slug":"pixiv-novel-id-returned-malformed-detail-payloa","errorCode":null,"errorMessage":"Pixiv novel ${id} returned malformed detail payload","messagePattern":"Pixiv novel (.+?) returned malformed detail payload","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/pixiv/novel-download-utils.js","lineNumber":17,"sourceCode":"import * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { ArgumentError, CommandExecutionError } from '@jackwener/opencli/errors';\nimport { pixivFetch } from './utils.js';\nimport { dateOnly, tagsToString } from './bookmark-utils.js';\n\nfunction optionalDownloadCount(value, label) {\n  if (value == null || value === '') return null;\n  if (!Number.isSafeInteger(value) || value < 0) {\n    throw new CommandExecutionError(`Pixiv novel ${label} returned malformed data`);\n  }\n  return value;\n}\n\nfunction requireNovelDownloadBody(body, id) {\n  if (!body || Array.isArray(body) || typeof body !== 'object') {\n    throw new CommandExecutionError(`Pixiv novel ${id} returned malformed detail payload`);\n  }\n  const novelId = String(body.id ?? '').trim();\n  const title = typeof body.title === 'string' ? body.title.trim() : '';\n  const author = typeof body.userName === 'string' ? body.userName.trim() : '';\n  const userId = String(body.userId ?? '').trim();\n  if (typeof body.content !== 'string') {\n    throw new CommandExecutionError(`Pixiv novel ${id} returned malformed content payload`);\n  }\n  if (!/^\\d+$/.test(novelId) || novelId !== id || !title || !author || !/^\\d+$/.test(userId)) {\n    throw new CommandExecutionError(`Pixiv novel ${id} returned malformed detail payload`);\n  }\n  // Validate metadata before any file is planned or created.\n  tagsToString(body.tags);\n  const createdDate = dateOnly(body.createDate);\n  const wordCount = optionalDownloadCount(body.wordCount, 'word count');\n  const bookmarkCount = optionalDownloadCount(body.bookmarkCount, 'bookmark count');\n  return {\n    ...body,","sourceCodeStart":1,"sourceCodeEnd":35,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/pixiv/novel-download-utils.js#L1-L35","documentation":"requireNovelDownloadBody checks the novel detail response before any files are written. If the body is null, an array, or not a plain object, it throws this error meaning the /ajax/novel/{id} endpoint did not return the expected detail object.","triggerScenarios":"pixivFetch returns null/falsey body, an array, or a non-object (error JSON, HTML parsed unexpectedly, or rate-limit page) for the novel detail endpoint.","commonSituations":"Novel deleted or ID wrong in a way that bypasses notFoundMsg; session expired causing a redirect payload; pixiv rate limiting during bulk novel downloads.","solutions":["Verify the novel ID exists by opening it on pixiv in a browser","Refresh pixiv cookies / re-authenticate the session","Slow down bulk downloads to avoid throttling","Log the raw response to identify whether pixiv changed the payload shape"],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":"const body = await pixivFetch(page, `/ajax/novel/${id}`);\nif (!body || Array.isArray(body) || typeof body !== 'object') {\n  throw new Error(`Novel ${id}: non-object detail payload`);\n}","typeGuard":"const isNovelDetail = (v) => !!v && !Array.isArray(v) && typeof v === 'object';","tryCatchPattern":"try {\n  await novelDownload(id);\n} catch (e) {\n  if (/malformed detail payload/.test(e.message)) {\n    // refresh session, back off, or skip this ID\n  } else throw e;\n}","preventionTips":["Keep session cookies valid","Throttle bulk downloads","Log raw responses to detect rate-limit/redirect payloads","Verify the novel exists before scripting the download"],"tags":["api","malformed-response","pixiv"],"backgroundTag":"malformed-api-response","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}