{"record":{"id":"41322bc13941c49d","repo":"DIYgod/RSSHub","slug":"collection-not-found","errorCode":null,"errorMessage":"Collection Not Found","messagePattern":"Collection Not Found","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"lib/routes/lofter/collection.ts","lineNumber":39,"sourceCode":"    maintainers: ['SrakhiuMeow'],\n    handler,\n};\n\nasync function fetchCollection(collectionID, limit, offset = 0) {\n    const response = await got({\n        method: 'post',\n        url: 'https://api.lofter.com/v1.1/postCollection.api?product=lofter-android-7.6.12',\n        body: new URLSearchParams({\n            collectionid: collectionID,\n            limit: limit.toString(),\n            method: 'getCollectionDetail',\n            offset: offset.toString(),\n            order: '0',\n        }),\n    });\n\n    if (!response.data.response) {\n        throw new Error('Collection Not Found');\n    }\n\n    const data = response.data.response;\n\n    return {\n        title: data.collection.name || 'Lofter Collection',\n        link: data.blogInfo.homePageUrl || 'https://www.lofter.com/',\n        description: data.collection.description || 'No description provided.',\n        items: data.items,\n    };\n}\n\nasync function handler(ctx) {\n    const collectionID = ctx.req.param('collectionID');\n    const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : '50';\n\n    const response = await cache.tryGet(collectionID, () => fetchCollection(collectionID, Number(limit)), config.cache.routeExpire, false);\n","sourceCodeStart":21,"sourceCodeEnd":57,"githubUrl":"https://github.com/DIYgod/RSSHub/blob/bed535e0879dc71c5aff6f1e7bd1ac21ede40115/lib/routes/lofter/collection.ts#L21-L57","documentation":"Thrown by the Lofter collection handler (lib/routes/lofter/collection.ts:39) when the Lofter Android API response has no `response` field — i.e. the collection id does not exist, was deleted, or the API returned an error envelope. The check is `if (!response.data.response)`.","triggerScenarios":"Requesting `/lofter/collection/<id>/...` with a non-existent or deleted collection id; the Lofter API changed its envelope and moved data elsewhere; rate limiting/error payload lacks the `response` key.","commonSituations":"Stale collection id from an old link; collection made private; Lofter API version bump (`postCollection.api` signature change).","solutions":["Verify the collection id by opening `https://www.lofter.com/front/blog/collection?collectionId=<id>` in a browser.","Inspect the raw `response.data` to see whether the API returned an error code/message under a different key.","If the envelope changed, update the guard and the downstream `data.collection`/`data.blogInfo` accesses."],"exampleFix":"// before\nif (!response.data.response) {\n    throw new Error('Collection Not Found');\n}\n\n// after: surface the API error meta when present\nif (!response.data.response) {\n    const meta = response.data.meta || response.data;\n    throw new Error(`Collection Not Found: ${JSON.stringify(meta).slice(0, 200)}`);\n}","handlingStrategy":"validation","validationCode":"function collectionExists(data: { response?: unknown }): boolean {\n    return Boolean(data && data.response);\n}\nconst data = (await got(...)).data;\nif (!collectionExists(data)) throw new TypeError(`Lofter collection '${collectionID}' not found`);","typeGuard":"interface LofterCollectionResponse { response: { collection: { name: string }; blogInfo: { homePageUrl: string }; items: unknown[] } }\nfunction isLofterCollectionResponse(d: unknown): d is LofterCollectionResponse {\n    return typeof d === 'object' && d !== null && 'response' in d && !!((d as any).response);\n}","tryCatchPattern":"try {\n    return await fetchLofterCollection(collectionID);\n} catch (e) {\n    if (e instanceof Error && /Collection Not Found/.test(e.message)) {\n        return { notFound: true, collectionID };\n    }\n    throw e;\n}","preventionTips":["Validate the collection id format (numeric) before the API call.","Inspect the full API envelope when debugging, not just `.response`.","Cache negative results briefly to avoid hammering a dead collection."],"tags":["upstream-change","api-envelope","lofter","not-found"],"backgroundTag":null,"analyzedSha":"bed535e0879dc71c5aff6f1e7bd1ac21ede40115","analyzedAt":"2026-08-12T19:29:35.364Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}