{"record":{"id":"d890b8631831b108","repo":"DIYgod/RSSHub","slug":"failed-to-fetch-followings-data","errorCode":null,"errorMessage":"Failed to fetch followings data","messagePattern":"Failed to fetch followings data","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"lib/routes/skeb/utils.tsx","lineNumber":136,"sourceCode":"export async function getFollowingsItems(username: string, path: 'friend_works' | 'following_works' | 'following_creators'): Promise<DataItem[]> {\n    const url = `${baseUrl}/api/users/${username.replace('@', '')}/followings`;\n\n    const followings_data = await cache.tryGet(\n        `skeb:followings_data:${username}`,\n        async () => {\n            const data = await ofetch(url, {\n                headers: {\n                    Authorization: `Bearer ${config.skeb.bearerToken}`,\n                },\n            });\n            return data;\n        },\n        config.cache.routeExpire,\n        false\n    );\n\n    if (!followings_data || typeof followings_data !== 'object') {\n        throw new Error('Failed to fetch followings data');\n    }\n\n    if (path === 'following_creators') {\n        return followings_data[path].map((item) => processCreator(item)).filter(Boolean) as DataItem[];\n    }\n    return followings_data[path].map((item) => processWork(item)).filter(Boolean) as DataItem[];\n}\n\nconst SkebWorkDescription = ({ imageUrl, body, audioUrl }: { imageUrl?: string; body: string; audioUrl?: string | null }) => (\n    <>\n        {imageUrl ? (\n            <>\n                <img src={imageUrl} />\n                <br />\n            </>\n        ) : null}\n        {audioUrl ? (\n            <>","sourceCodeStart":118,"sourceCodeEnd":154,"githubUrl":"https://github.com/DIYgod/RSSHub/blob/bed535e0879dc71c5aff6f1e7bd1ac21ede40115/lib/routes/skeb/utils.tsx#L118-L154","documentation":"Thrown at lib/routes/skeb/utils.tsx:136 after fetching `https://skeb.jp/api/users/{username}/followings` with a bearer-token Authorization header. The guard checks that `followings_data` is truthy and an object before calling `.map()` on it. If Skeb's API returns null, an empty string, a 401/403 error body, or a redirect HTML page (ofetch follows redirects and parses whatever comes back), the check fails and the error fires.","triggerScenarios":"Calling getFollowingsItems with a username that does not exist; the SKEB_BEARER_TOKEN is expired or revoked so Skeb returns a JSON error object instead of the followings payload; Cloudflare or Skeb rate-limiting returns an interstitial HTML page; the username still has a leading '@' that wasn't stripped correctly (line 119 should strip it but edge cases exist).","commonSituations":"SKEB_BEARER_TOKEN environment variable not set or left empty; token harvested from a logged-out session; Skeb changed their API response shape; the user passed a screen_name that contains special characters.","solutions":["Set a fresh SKEB_BEARER_TOKEN in config by logging into skeb.jp and copying the Authorization Bearer value from a real API request in DevTools.","Verify the username exists by opening `https://skeb.jp/@{username}` in a browser.","Temporarily log the raw `followings_data` value to see what Skeb actually returned (it may be an auth-error JSON).","Clear the cache key `skeb:followings_data:{username}` if stale bad data was cached."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"if (!config.skeb.bearerToken) {\n    throw new ConfigNotFoundError('SKEB_BEARER_TOKEN is not set');\n}\nconst cleanUsername = username.replace('@', '');\nif (!cleanUsername) {\n    throw new InvalidParameterError('username is required');\n}","typeGuard":"const isFollowingsResponse = (d: unknown): d is Record<string, unknown[]> =>\n    !!d && typeof d === 'object' && !Array.isArray(d);","tryCatchPattern":"try {\n    const items = await getFollowingsItems(username, path);\n} catch (e) {\n    if (e instanceof Error && e.message === 'Failed to fetch followings data') {\n        // likely auth or upstream issue; check bearerToken freshness\n        throw new Error('Skeb followings unavailable — verify SKEB_BEARER_TOKEN and username');\n    }\n    throw e;\n}","preventionTips":["Always set SKEB_BEARER_TOKEN from a currently-logged-in skeb.jp browser session.","Strip the leading '@' from usernames before calling getFollowingsItems.","Monitor token expiry — Skeb bearer tokens have a limited lifetime and silent failure manifests as this error."],"tags":["api","authentication","network","config","skeb"],"backgroundTag":null,"analyzedSha":"bed535e0879dc71c5aff6f1e7bd1ac21ede40115","analyzedAt":"2026-08-12T19:29:35.364Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}