{"record":{"id":"830667e60b2d72fe","repo":"DIYgod/RSSHub","slug":"user-id-is-required-for-non-posts-sources","errorCode":null,"errorMessage":"User ID is required for non-posts sources","messagePattern":"User ID is required for non-posts sources","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"lib/routes/kemono/index.tsx","lineNumber":101,"sourceCode":"            parsedData = JSON.parse(parsedData);\n        }\n        return parsedData;\n    } catch {\n        return field;\n    }\n}\n\nfunction buildApiUrl(source: string, userId?: string, contentType?: string): string {\n    if (source === 'posts') {\n        return `${KEMONO_API_URL}/posts`;\n    }\n\n    if (source === 'discord' && userId) {\n        return `${KEMONO_API_URL}/discord/channel/lookup/${userId}`;\n    }\n\n    if (!userId) {\n        throw new Error('User ID is required for non-posts sources');\n    }\n\n    const basePath = `${KEMONO_API_URL}/${source}/user/${userId}`;\n    return contentType ? `${basePath}/${contentType}` : `${basePath}/posts`;\n}\n\nfunction buildFrontendUrl(source: string, userId?: string, contentType?: string): string {\n    if (source === 'posts') {\n        return `${KEMONO_ROOT_URL}/posts`;\n    }\n\n    if (source === 'discord' && userId) {\n        return `${KEMONO_ROOT_URL}/${source}/server/${userId}`;\n    }\n\n    if (!userId) {\n        throw new Error('User ID is required for non-posts sources');\n    }","sourceCodeStart":83,"sourceCodeEnd":119,"githubUrl":"https://github.com/DIYgod/RSSHub/blob/bed535e0879dc71c5aff6f1e7bd1ac21ede40115/lib/routes/kemono/index.tsx#L83-L119","documentation":"Thrown inside buildApiUrl() when the source is not 'posts' (and not the discord-with-userId case) and no userId was provided. The Kemono API requires a user identifier for all per-creator endpoints (e.g. /patreon/user/{userId}/posts), so a missing userId makes the URL unbuildable. This is a programming/contract error in how the route is invoked, not a network condition.","triggerScenarios":"The route's path/capturing logic failed to extract the userId path parameter, or the route was called with a source like 'patreon' or 'fanbox' but the userId segment was empty. Also fires if upstream route matching regex changed and the userId capture group no longer binds.","commonSituations":"A refactor of the route path regex drops the userId capture. A user visits a URL like /kemono/patreon (missing the /user/123 segment) due to a broken link or typo. The parameter is present but empty string (falsy), which also triggers the guard.","solutions":["Ensure the route path includes and correctly captures the userId parameter (e.g. /:source/user/:userId).","Validate userId is a non-empty string before calling buildApiUrl and return a clear InvalidParameterError if missing.","If the route legitimately allows a no-userId mode for a new source, add that source to the early-return branches alongside 'posts'.","Unit-test buildApiUrl with all source values and undefined userId to confirm only intended sources bypass the check."],"exampleFix":"// before\nfunction buildApiUrl(source: string, userId?: string, contentType?: string): string {\n    if (source === 'posts') return `${KEMONO_API_URL}/posts`;\n    if (source === 'discord' && userId) return `${KEMONO_API_URL}/discord/channel/lookup/${userId}`;\n    if (!userId) throw new Error('User ID is required for non-posts sources');\n    ...\n}\n\n// after — validate at the route boundary with a typed error\nif (source !== 'posts' && source !== 'discord' && !userId) {\n    throw new InvalidParameterError(`Source '${source}' requires a user ID. Usage: /kemono/${source}/user/:userId`);\n}","handlingStrategy":"validation","validationCode":"function assertUserIdForSource(source: string, userId?: string): asserts userId is string {\n    if (source !== 'posts' && source !== 'discord' && !userId) {\n        throw new InvalidParameterError(`Source '${source}' requires a userId. Route: /kemono/${source}/user/:userId`);\n    }\n}\n// call before buildApiUrl:\nassertUserIdForSource(source, userId);","typeGuard":"function isUserScopedSource(source: string): boolean {\n    return source !== 'posts';\n}\nfunction hasUserId(source: string, userId?: string): userId is string {\n    return source === 'posts' || typeof userId === 'string' && userId.length > 0;\n}","tryCatchPattern":null,"preventionTips":["Validate userId presence at the route boundary, not deep in URL-builder helpers.","Use TypeScript assertion functions to narrow userId to string after validation.","Unit-test buildApiUrl with every supported source value, both with and without userId.","Keep the route path regex explicit about the userId segment to prevent empty captures."],"tags":["validation","parameter-required","kemono","api-url-construction"],"backgroundTag":null,"analyzedSha":"bed535e0879dc71c5aff6f1e7bd1ac21ede40115","analyzedAt":"2026-08-12T19:29:35.364Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}