{"record":{"id":"61631c3e8183e6e8","repo":"DIYgod/RSSHub","slug":"javascript-file-not-found","errorCode":null,"errorMessage":"JavaScript file not found.","messagePattern":"JavaScript file not found\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"lib/routes/scoop/apps.tsx","lineNumber":52,"sourceCode":"\nexport const handler = async (ctx: Context): Promise<Data> => {\n    const { query = 's=2&d=1&n=true&dm=true&o=true' } = ctx.req.param();\n    const limit = Number(ctx.req.query('limit') ?? '50');\n\n    const baseUrl = 'https://scoop.sh';\n    const apiBaseUrl = 'https://scoopsearch.search.windows.net';\n    const targetUrl: string = new URL(`/#/apps?${query}`, baseUrl).href;\n    const apiUrl: string = new URL('indexes/apps/docs/search', apiBaseUrl).href;\n\n    const targetResponse = await ofetch(targetUrl);\n    const $: CheerioAPI = load(targetResponse);\n    const language = $('html').attr('lang') ?? 'en';\n\n    const scriptRegExp = /<script type=\"module\" crossorigin src=\"(.*?)\"><\\/script>/;\n    const scriptUrl: string = scriptRegExp.test(targetResponse) ? new URL(targetResponse.match(scriptRegExp)?.[1], baseUrl).href : '';\n\n    if (!scriptUrl) {\n        throw new Error('JavaScript file not found.');\n    }\n\n    const scriptResponse = await ofetch(scriptUrl, {\n        parseResponse: (txt) => txt,\n    });\n\n    const key: string = scriptResponse.match(/VITE_APP_AZURESEARCH_KEY:\"(.*?)\"/)?.[1];\n\n    if (!key) {\n        throw new Error('Key not found.');\n    }\n\n    const isOffcial = !query.includes('o=false');\n    const isDistinct = !query.includes('dm=false');\n    const sort: string = query.match(/s=(\\d+)/)?.[1] ?? '2';\n    const desc: string = query.match(/d=(\\d+)/)?.[1] ?? '1';\n\n    const response = await ofetch(apiUrl, {","sourceCodeStart":34,"sourceCodeEnd":70,"githubUrl":"https://github.com/DIYgod/RSSHub/blob/bed535e0879dc71c5aff6f1e7bd1ac21ede40115/lib/routes/scoop/apps.tsx#L34-L70","documentation":"Thrown when the scoop.sh index HTML contains no <script type=\"module\" crossorigin src=\"...\"> tag from which the route extracts the JS bundle URL. The route scrapes the JS bundle to recover the embedded Azure Search API key, so a missing module script aborts the whole flow.","triggerScenarios":"ofetch(targetUrl) returns HTML where scriptRegExp.test(targetResponse) is false — i.e. the Vite module script tag is absent (renamed attribute, different bundler, or an anti-bot page was returned). scriptUrl stays '' and the guard throws.","commonSituations":"scoop.sh rebuilt its frontend and changed the script tag attributes (no longer 'module crossorigin'); a CDN/anti-bot layer returned a challenge page instead of the app shell; the response was gzip/binary and the regex ran against a non-string (mitigated by ofetch default text handling).","solutions":["View-source https://scoop.sh and confirm the module script tag's current attributes.","Update scriptRegExp to match the new tag shape (e.g. dropped 'crossorigin', changed type).","If an anti-bot page is served, add browser-like headers via config.trueUA.","Detect a non-HTML response early and throw a clearer 'unexpected response type' error."],"exampleFix":"// before\nconst scriptRegExp = /<script type=\"module\" crossorigin src=\"(.*?)\"><\\/script>/;\nconst scriptUrl: string = scriptRegExp.test(targetResponse) ? new URL(targetResponse.match(scriptRegExp)?.[1], baseUrl).href : '';\nif (!scriptUrl) {\n    throw new Error('JavaScript file not found.');\n}\n\n// after — tolerate the optional crossorigin attribute\nconst scriptRegExp = /<script[^>]*type=\"module\"[^>]*src=\"([^\"]+)\"[^>]*><\\/script>/;\nconst scriptUrl = new URL(targetResponse.match(scriptRegExp)?.[1] ?? '', baseUrl).href;\nif (!scriptUrl) {\n    throw new Error('JavaScript module bundle not found in scoop.sh index HTML');\n}","handlingStrategy":"fallback","validationCode":"// Tolerate attribute changes by matching the module script more loosely.\nfunction extractScriptUrl(html: string, baseUrl: string): string {\n    const m = html.match(/<script[^>]*\\btype=\"module\"[^>]*\\bsrc=\"([^\"]+)\"/);\n    return m ? new URL(m[1], baseUrl).href : '';\n}","typeGuard":"const isHtmlWithModuleScript = (s: string): boolean =>\n    /<script[^>]*type=\"module\"[^>]*src=/.test(s);","tryCatchPattern":"let scriptUrl = extractScriptUrl(targetResponse, baseUrl);\nif (!scriptUrl) {\n    // retry once with a browser UA in case an anti-bot page was served\n    const retry = await ofetch(targetUrl, { headers: { 'user-agent': config.trueUA }, parseResponse: (t) => t });\n    scriptUrl = extractScriptUrl(retry, baseUrl);\n}\nif (!scriptUrl) throw new Error('JavaScript module bundle not found in scoop.sh index HTML');","preventionTips":["Match the module script by the stable 'type=\"module\"' attribute rather than the volatile 'crossorigin'.","Send config.trueUA to avoid lightweight anti-bot interstitials.","Snapshot the index HTML in a fixture to catch structural changes."],"tags":["scraping","upstream-change","regex","dom-parsing","scoop"],"backgroundTag":null,"analyzedSha":"bed535e0879dc71c5aff6f1e7bd1ac21ede40115","analyzedAt":"2026-08-12T19:29:35.364Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}