{"record":{"id":"1de70aeae84b9fff","repo":"jackwener/OpenCLI","slug":"pixiv-novel-label-returned-malformed-data","errorCode":null,"errorMessage":"Pixiv novel ${label} returned malformed data","messagePattern":"Pixiv novel (.+?) returned malformed data","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/pixiv/novel-download-utils.js","lineNumber":10,"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  }","sourceCodeStart":1,"sourceCodeEnd":28,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/pixiv/novel-download-utils.js#L1-L28","documentation":"optionalDownloadCount validates pixiv's wordCount/bookmarkCount fields for a novel download. If the field is present but not a safe non-negative integer, it throws this CommandExecutionError, indicating the API returned data in an unexpected shape for that counter.","triggerScenarios":"The novel detail JSON has wordCount or bookmarkCount as a string, float, negative number, or other non-integer value (e.g. '' handled earlier, but '123' as a string fails).","commonSituations":"Pixiv A/B-testing or changing their internal AJAX novel payload types; proxied/cached responses with altered types; scraping through a translation layer that stringifies numbers.","solutions":["Inspect the raw /ajax/novel/{id} response to see the actual type of wordCount/bookmarkCount","Re-fetch after a short delay in case of a transient bad response","Update the parsing code to coerce numeric strings with Number() when pixiv changes types","Pin/report the pixiv API behavior change in the tool's issue tracker"],"exampleFix":"// before\nif (!Number.isSafeInteger(value) || value < 0) { throw ... }\n// after\nconst n = typeof value === 'string' && /^\\d+$/.test(value) ? Number(value) : value;\nif (!Number.isSafeInteger(n) || n < 0) { throw ... }\nreturn n;","handlingStrategy":"type-guard","validationCode":"const wc = body.wordCount;\nif (wc != null && wc !== '' && !(Number.isSafeInteger(wc) && wc >= 0)) {\n  console.warn('Unexpected wordCount type from pixiv:', typeof wc, wc);\n}","typeGuard":"const isCount = (v) => v == null || v === '' || (Number.isSafeInteger(v) && v >= 0);","tryCatchPattern":"try {\n  await novelDownload(id);\n} catch (e) {\n  if (/returned malformed data/.test(e.message)) {\n    console.warn('pixiv payload type changed; inspect raw /ajax/novel response');\n  } else throw e;\n}","preventionTips":["Log raw novel payloads when counters look wrong","Retry transiently before assuming an API change","Coerce numeric strings defensively in your own pre-fetch checks","Track pixiv AJAX schema changes"],"tags":["validation","api","malformed-response"],"backgroundTag":"malformed-api-response","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}