{"record":{"id":"54d28476fbb7d6a1","repo":"DIYgod/RSSHub","slug":"no-articles-found-54d284","errorCode":null,"errorMessage":"No articles found","messagePattern":"No articles found","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"lib/routes/newslaundry/utils.tsx","lineNumber":17,"sourceCode":"import { raw } from 'hono/html';\nimport { renderToString } from 'hono/jsx/dom/server';\n\nimport type { Data, DataItem } from '@/types';\nimport ofetch from '@/utils/ofetch';\nimport { parseDate } from '@/utils/parse-date';\n\nexport const rootUrl = 'https://www.newslaundry.com';\n\nexport async function fetchCollection(collectionSlug: string, customUrl?: string, skipFirstItem: boolean = false) {\n    const apiUrl = `${rootUrl}/api/v1/collections/${collectionSlug}`;\n    const currentUrl = customUrl || `${rootUrl}/${collectionSlug}`;\n\n    const response = await ofetch(apiUrl);\n\n    if (!response.items || !response.items.length) {\n        throw new Error('No articles found');\n    }\n\n    // Skip first item if requested\n    const itemsToProcess = skipFirstItem ? response.items.slice(1) : response.items;\n    const items = itemsToProcess.map((item) => processStory(item.story));\n\n    return {\n        title: `${response.name} - Newslaundry`,\n        description: response.summary || `${response.name} articles from Newslaundry`,\n        link: currentUrl,\n        item: items,\n        language: 'en',\n        logo: `${rootUrl}/favicon.ico`,\n        icon: `${rootUrl}/favicon.ico`,\n    } as Data;\n}\n\nfunction processStory(story: any): DataItem {","sourceCodeStart":1,"sourceCodeEnd":35,"githubUrl":"https://github.com/DIYgod/RSSHub/blob/bed535e0879dc71c5aff6f1e7bd1ac21ede40115/lib/routes/newslaundry/utils.tsx#L1-L35","documentation":"Thrown by fetchCollection when the Newslaundry collections API responds with no 'items' array (or an empty one). The route treats a populated items list as a hard precondition before mapping stories into feed entries, so an empty payload is treated as a broken feed rather than a legitimately-empty one. It surfaces as a plain Error, so RSSHub reports it as a route-level failure to the subscriber.","triggerScenarios":"A GET to https://www.newslaundry.com/api/v1/collections/{collectionSlug} where the JSON body omits 'items' or returns items: []. This happens when collectionSlug is misspelled/decommissioned, when Newslaundry returns an error object that lacks items, or when anti-bot/Cloudflare middleware returns a 200 with an interstitial/empty body.","commonSituations":"A collection slug renamed or retired on the Newslaundry site; the upstream CMS (Quintype assettype) changed its API response envelope; the instance's egress IP is rate-limited and gets a stripped-down 200 response; a transient outage returns an error JSON that ofetch parsed successfully but without items.","solutions":["Confirm the collectionSlug still exists by opening https://www.newslaundry.com/api/v1/collections/{slug} in a browser and checking that items[] is populated.","If the slug changed, update the route path/example and any callers passing collectionSlug to the current value.","Reproduce the raw response with curl/config.trueUA to detect anti-bot interstitials; if blocked, the instance may need a different egress IP or proxy.","Consider widening the guard so a missing-items response carries diagnostic detail (e.g. log response.name or the HTTP status) instead of a bare 'No articles found'."],"exampleFix":"// before\nconst response = await ofetch(apiUrl);\nif (!response.items || !response.items.length) {\n    throw new Error('No articles found');\n}\n\n// after\nconst response = await ofetch(apiUrl);\nif (!response.items || !response.items.length) {\n    throw new Error(`No articles found for collection \"${collectionSlug}\" (api: ${apiUrl})`);\n}","handlingStrategy":"validation","validationCode":"// Before calling fetchCollection, sanity-check the slug shape and probe the API surface.\nasync function collectionExists(slug: string): Promise<boolean> {\n    try {\n        const res = await ofetch(`https://www.newslaundry.com/api/v1/collections/${slug}`, { timeout: 10000 });\n        return Array.isArray(res?.items) && res.items.length > 0;\n    } catch {\n        return false;\n    }\n}\nif (!(await collectionExists(slug))) {\n    // surface a friendly 'feed unavailable' instead of letting the throw propagate\n}","typeGuard":"const hasItems = (r: unknown): r is { items: unknown[] } =>\n    typeof r === 'object' && r !== null && Array.isArray((r as any).items) && (r as any).items.length > 0;","tryCatchPattern":"// In the route handler, downgrade a 'No articles found' into an empty-but-valid feed when appropriate.\ntry {\n    return await fetchCollection(slug, customUrl, skipFirst);\n} catch (e) {\n    if (e instanceof Error && e.message === 'No articles found') {\n        return { title: `${slug} - Newslaundry`, description: 'No articles available.', link: customUrl ?? `${rootUrl}/${slug}`, item: [], allowEmpty: true };\n    }\n    throw e;\n}","preventionTips":["Treat an empty items array as a possible transient outage and retry once before failing.","Log the raw API status/body when items is empty so root cause (block vs rename) is distinguishable.","Cache only non-empty results so a transient empty response does not poison the feed."],"tags":["api","upstream-change","data-validation","rss-feed"],"backgroundTag":null,"analyzedSha":"bed535e0879dc71c5aff6f1e7bd1ac21ede40115","analyzedAt":"2026-08-12T19:29:35.364Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}