{"record":{"id":"78b0d1b2c035d45f","repo":"jackwener/OpenCLI","slug":"pixiv-user-profile-returned-malformed-novels-paylo","errorCode":null,"errorMessage":"Pixiv user profile returned malformed novels payload","messagePattern":"Pixiv user profile returned malformed novels payload","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/pixiv/novels.js","lineNumber":44,"sourceCode":"  }\n  const title = requirePixivString(item.title, 'Pixiv user novel item');\n  return {\n    rank,\n    title,\n    novel_id: id,\n    words: optionalCount(item.wordCount, 'word count'),\n    characters: optionalCount(item.textCount ?? item.characterCount, 'character count'),\n    bookmarks: optionalCount(item.bookmarkCount, 'bookmark count', 0),\n    tags: tagsToString(item.tags),\n    created: dateOnly(item.createDate),\n    url: `https://www.pixiv.net/novel/show.php?id=${id}`,\n  };\n}\n\nfunction requireProfileNovelIds(body) {\n  const payload = requirePixivPayloadObject(body, 'Pixiv user profile');\n  if (!payload.novels || Array.isArray(payload.novels) || typeof payload.novels !== 'object') {\n    throw new CommandExecutionError('Pixiv user profile returned malformed novels payload');\n  }\n  const ids = Object.keys(payload.novels);\n  const invalid = ids.find(id => !/^\\d+$/.test(id));\n  if (invalid) {\n    throw new CommandExecutionError(`Pixiv user profile returned malformed novel ID: ${invalid}`);\n  }\n  return ids;\n}\n\nfunction requireDetailWorks(body) {\n  const payload = requirePixivPayloadObject(body, 'Pixiv user novel details');\n  if (!payload.works || Array.isArray(payload.works) || typeof payload.works !== 'object') {\n    throw new CommandExecutionError('Pixiv user novel details returned malformed works payload');\n  }\n  return payload.works;\n}\n\ncli({","sourceCodeStart":26,"sourceCodeEnd":62,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/pixiv/novels.js#L26-L62","documentation":"requireProfileNovelIds validates the Pixiv user profile response's novels field: it must be a non-null, non-array object (a map of novelId -> data). If novels is missing/null, is an array, or is a non-object primitive, the library throws because the profile payload shape is unusable for enumerating novel IDs. This guards against Pixiv schema changes or error responses being parsed as a valid profile.","triggerScenarios":"Pixiv profile endpoint returns {novels: null}, {novels: []}, {novels: '123'}, or omits novels entirely — often when the user has no novels, the user does not exist/is private, or the API returns an error/HTML page parsed loosely.","commonSituations":"Querying a user ID that has zero novels (API may return empty array instead of object); deleted or restricted accounts; rate-limit or auth-failure bodies being fed to the parser; Pixiv API version drift changing novels from map to list.","solutions":["Log the full response body/status to see what actually came back (auth failure vs shape change)","Handle the empty case before parsing: if the user has no novels, treat empty array/missing as zero results rather than an error","Verify the target user ID is valid, public, and not deleted","Check for rate-limit/login requirements (Pixiv may need cookies/PHPSESSID) and retry with valid auth","Update the parser to accept both object-map and array forms of novels"],"exampleFix":"// before\nconst ids = requireProfileNovelIds(body);\n// after\nif (body && Array.isArray(body.novels)) {\n  body.novels = Object.fromEntries(body.novels.map(n => [String(n.id), n]));\n}\nconst ids = requireProfileNovelIds(body);","handlingStrategy":"validation","validationCode":"function hasValidNovelsMap(body) {\n  return body != null &&\n    typeof body.novels === 'object' && body.novels !== null &&\n    !Array.isArray(body.novels);\n}\nif (!hasValidNovelsMap(body)) throw new Error('Profile response unusable: novels is not an object map');","typeGuard":"function hasNovelsMap(p) {\n  return typeof p === 'object' && p !== null &&\n    typeof p.novels === 'object' && p.novels !== null && !Array.isArray(p.novels);\n}\n// usage: if (hasNovelsMap(profile)) { Object.keys(profile.novels) ... }","tryCatchPattern":"try {\n  const ids = requireProfileNovelIds(body);\n} catch (err) {\n  if (String(err.message).includes('malformed novels payload')) {\n    console.warn('No usable novels map (empty account, private user, or auth/rate-limit body); returning []');\n    return [];\n  }\n  throw err;\n}","preventionTips":["Check HTTP status and auth state before parsing profile JSON","Treat 'user has no novels' as an empty result, not an error","Record Pixiv API fixtures and test the parser against them to catch schema changes","Validate the user ID exists and is public before querying"],"tags":["pixiv","cli","schema-validation","api-payload"],"backgroundTag":"schema-validation-failed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}