{"record":{"id":"4b29034f58430828","repo":"DIYgod/RSSHub","slug":"novel-not-found-in-both-apis","errorCode":null,"errorMessage":"Novel not found in both APIs","messagePattern":"Novel not found in both APIs","errorType":"validation","errorClass":"InvalidParameterError","httpStatus":503,"severity":"error","filePath":"lib/routes/syosetu/utils.ts","lineNumber":20,"sourceCode":"import type { NarouSearchResult } from 'narou';\nimport { NarouNovelFetch, SearchBuilder, SearchBuilderR18 } from 'narou';\n\nimport { config } from '@/config';\nimport InvalidParameterError from '@/errors/types/invalid-parameter';\nimport type { DataItem } from '@/types';\nimport cache from '@/utils/cache';\nimport ofetch from '@/utils/ofetch';\n\nexport async function fetchNovelInfo(ncode: string): Promise<{ baseUrl: string; novel: NarouSearchResult }> {\n    const api = new NarouNovelFetch();\n    const [generalRes, r18Res] = await Promise.all([new SearchBuilder({ gzip: 5, of: 't-s-k-ga-nt-nu' }, api).ncode(ncode).execute(), new SearchBuilderR18({ gzip: 5, of: 't-s-k-ga-nt-nu' }, api).ncode(ncode).execute()]);\n\n    const isGeneral = generalRes.allcount !== 0;\n    const novelData = isGeneral ? generalRes : r18Res;\n    const baseUrl = isGeneral ? 'https://ncode.syosetu.com' : 'https://novel18.syosetu.com';\n\n    if (novelData.allcount === 0) {\n        throw new InvalidParameterError('Novel not found in both APIs');\n    }\n\n    return {\n        baseUrl,\n        novel: novelData.values[0] as NarouSearchResult,\n    };\n}\n\nexport async function fetchChapterContent(chapterUrl: string, chapter?: number): Promise<DataItem> {\n    return (await cache.tryGet(chapterUrl, async () => {\n        const response = await ofetch(chapterUrl, {\n            headers: {\n                Cookie: 'over18=yes',\n                'User-Agent': config.ua,\n            },\n        });\n\n        const $ = load(response);","sourceCodeStart":2,"sourceCodeEnd":38,"githubUrl":"https://github.com/DIYgod/RSSHub/blob/bed535e0879dc71c5aff6f1e7bd1ac21ede40115/lib/routes/syosetu/utils.ts#L2-L38","documentation":"Thrown by fetchNovelInfo (syosetu/utils.ts:20) as an InvalidParameterError when BOTH the general (ncode.syosetu.com) and R18 (novel18.syosetu.com) narou search APIs return allcount === 0 for the given ncode. The two APIs are queried in parallel; the code picks the general result if its allcount is non-zero, else the R18 result. If neither has the novel, it does not exist (or the ncode is wrong). Used by the dev/chapter routes.","triggerScenarios":"Passing a malformed or non-existent ncode; the novel was deleted by its author; the ncode belongs to a private/draft novel; confusing the ncode with the numeric novel id.","commonSituations":"Typo in the ncode; following a stale link to a deleted novel; mixing up ncode (e.g. n1234ab) with the numeric id.","solutions":["Verify the ncode by opening https://ncode.syosetu.com/<ncode>/ (general) or https://novel18.syosetu.com/<ncode>/ (R18) in a browser.","Re-copy the ncode from the novel's URL — it is the path segment, lowercase, starting with a letter."],"exampleFix":"// before (typo / wrong id)\nGET /syosetu/dev/n1234zz\n// after (verified ncode)\nGET /syosetu/dev/n1234ab","handlingStrategy":"validation","validationCode":"// Pre-check the ncode shape before hitting the API.\n// Narou ncodes are lowercase, start with a letter, then digits then letters.\nfunction isPlausibleNcode(ncode: string): boolean {\n  return typeof ncode === 'string'\n    && ncode === ncode.toLowerCase()\n    && /^[a-z]\\d+[a-z]+$/.test(ncode);\n}","typeGuard":"function isNcode(ncode: string): ncode is string {\n  return typeof ncode === 'string'\n    && ncode === ncode.toLowerCase()\n    && /^[a-z]\\d+[a-z]+$/.test(ncode);\n}","tryCatchPattern":"try {\n  await fetchNovelInfo(ncode);\n} catch (e) {\n  if (e instanceof InvalidParameterError && /not found in both APIs/i.test(e.message)) {\n    // ncode does not exist on either general or R18 site\n  }\n  throw e;\n}","preventionTips":["Verify the ncode by opening https://ncode.syosetu.com/<ncode>/ or https://novel18.syosetu.com/<ncode>/ first.","Take the ncode from the URL path segment; never confuse it with the numeric novel id.","Lowercase the ncode before use."],"tags":["invalid-parameter","not-found","api-validation","syosetu"],"backgroundTag":null,"analyzedSha":"bed535e0879dc71c5aff6f1e7bd1ac21ede40115","analyzedAt":"2026-08-12T19:29:35.364Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}