{"record":{"id":"524fa0db6f1a4bda","repo":"jackwener/OpenCLI","slug":"label-returned-a-missing-image-url","errorCode":null,"errorMessage":"${label} returned a missing image URL","messagePattern":"(.+?) returned a missing image URL","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/pixiv/bookmark-download.js","lineNumber":34,"sourceCode":"} from './novel-download-utils.js';\n\nconst IMAGE_CONTENT_TYPES = new Map([\n  ['.jpg', 'image/jpeg'],\n  ['.jpeg', 'image/jpeg'],\n  ['.png', 'image/png'],\n  ['.gif', 'image/gif'],\n  ['.webp', 'image/webp'],\n]);\n\nfunction requireExecute(value) {\n  if (value !== true) {\n    throw new ArgumentError('Refusing to write local Pixiv downloads: pass --execute');\n  }\n}\n\nfunction parsePixivImageUrl(value, label) {\n  if (typeof value !== 'string' || !value) {\n    throw new CommandExecutionError(`${label} returned a missing image URL`);\n  }\n  let url;\n  try {\n    url = new URL(value);\n  } catch {\n    throw new CommandExecutionError(`${label} returned a malformed image URL`);\n  }\n  const extension = path.extname(url.pathname).toLowerCase();\n  const contentType = IMAGE_CONTENT_TYPES.get(extension);\n  if (url.protocol !== 'https:' || url.hostname !== 'i.pximg.net' || url.username || url.password || url.port || !contentType) {\n    throw new CommandExecutionError(`${label} returned an untrusted Pixiv image URL`);\n  }\n  return { url: url.href, extension, contentType };\n}\n\nasync function prepareIllustPlan(page, row, outputRoot) {\n  const pages = await pixivFetch(page, `/ajax/illust/${row.illust_id}/pages`, {\n    notFoundMsg: `Illustration not found: ${row.illust_id}`,","sourceCodeStart":16,"sourceCodeEnd":52,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/pixiv/bookmark-download.js#L16-L52","documentation":"This CommandExecutionError is thrown by parsePixivImageUrl in clis/pixiv/bookmark-download.js:32-35 when a Pixiv API payload yields no usable image URL. The value passed in (entry.urls.original or entry.urls.regular, per prepareIllustPlan) is not a non-empty string, meaning the API response lacks the expected URL field.","triggerScenarios":"The /ajax/illust/<id>/pages response contains a page entry whose urls object has neither original nor regular, or both are empty/non-string, so parsePixivImageUrl receives undefined/null/'' at clis/pixiv/bookmark-download.js:33.","commonSituations":"Pixiv changed the /ajax/illust pages response schema; the illustration is restricted (R-18, my-pixiv-only, or deleted) so Pixiv returns a payload without image URLs; an unauthenticated/expired session causes degraded API responses; region-restricted works return empty url fields.","solutions":["Re-authenticate the pixiv session (run the pixiv auth flow) — restricted or expired sessions often yield incomplete payloads","Check the illustration is publicly accessible (not deleted, R-18-gated, or limited to my-pixiv watchers)","Log the raw /ajax/illust/<id>/pages response to confirm the urls field shape and compare against the current Pixiv API schema","Skip the offending illustration or update the CLI's URL extraction to match a changed API response format"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"function hasImageUrl(entry) {\n  const u = entry && entry.urls;\n  return u && typeof u === 'object' &&\n    ((typeof u.original === 'string' && u.original) ||\n     (typeof u.regular === 'string' && u.regular));\n}\nif (!hasImageUrl(pageEntry)) {\n  console.warn('Skipping page with no image URL:', pageEntry);\n}","typeGuard":"function isNonEmptyString(v) {\n  return typeof v === 'string' && v.length > 0;\n}","tryCatchPattern":"try {\n  const parsed = parsePixivImageUrl(entry.urls.original || entry.urls.regular, label);\n} catch (e) {\n  if (e instanceof CommandExecutionError && e.message.includes('missing image URL')) {\n    console.warn(`${label}: no URL in API response — check auth/permissions for this illust`);\n  } else {\n    throw e;\n  }\n}","preventionTips":["Re-authenticate the pixiv session before large batch downloads","Skip or log illustrations that are deleted, R-18-gated, or my-pixiv-only rather than failing the whole batch","Spot-check the /ajax/illust/<id>/pages payload for a changed schema after Pixiv updates","Filter restricted works out of the bookmark list before downloading"],"tags":["pixiv","api-response","url-parsing","data-shape"],"backgroundTag":"api-response-schema-mismatch","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}