{"record":{"id":"b54a48ada80e33f5","repo":"jackwener/OpenCLI","slug":"url-finalurl","errorCode":null,"errorMessage":"字幕 URL 非法: ${finalUrl}","messagePattern":"字幕 URL 非法: (.+?)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/bilibili/subtitle.js","lineNumber":82,"sourceCode":"            if (needLoginSubtitle) {\n                throw new AuthRequiredError('bilibili.com', 'Bilibili subtitles are hidden behind login for this video. Please log in to bilibili.com in Chrome and retry.');\n            }\n            throw new EmptyResultError('bilibili subtitle', '此视频没有发现外挂或智能字幕。');\n        }\n        // 3. 选择目标字幕语言\n        const target = kwargs.lang\n            ? subtitles.find((s) => s.lan === kwargs.lang) || subtitles[0]\n            : subtitles[0];\n        if (!target || typeof target !== 'object' || !Object.hasOwn(target, 'subtitle_url')) {\n            throw new CommandExecutionError('字幕条目缺少 subtitle_url 字段');\n        }\n        const targetSubUrl = typeof target.subtitle_url === 'string' ? target.subtitle_url.trim() : '';\n        if (!targetSubUrl) {\n            throw new AuthRequiredError('bilibili.com', '[风控拦截/未登录] 获取到的 subtitle_url 为空！请确保 CLI 已成功登录且风控未封锁此账号。');\n        }\n        const finalUrl = targetSubUrl.startsWith('//') ? 'https:' + targetSubUrl : targetSubUrl;\n        if (!/^https?:\\/\\//i.test(finalUrl)) {\n            throw new CommandExecutionError(`字幕 URL 非法: ${finalUrl}`);\n        }\n        // 4. 解析并拉取 CDN 的 JSON 文件\n        const fetchJs = `\n      (async () => {\n         const url = ${JSON.stringify(finalUrl)};\n         const res = await fetch(url);\n         const text = await res.text();\n\n         if (text.startsWith('<!DOCTYPE') || text.startsWith('<html')) {\n            return { error: 'HTML', text: text.substring(0, 100), url };\n         }\n\n         try {\n             const subJson = JSON.parse(text);\n             // B站真实返回格式是 { font_size: 0.4, font_color: \"#FFFFFF\", background_alpha: 0.5, background_color: \"#9C27B0\", Stroke: \"none\", type: \"json\" , body: [{from: 0, to: 0, content: \"\"}] }\n             if (Array.isArray(subJson?.body)) return { success: true, data: subJson.body };\n             if (Array.isArray(subJson)) return { success: true, data: subJson };\n             return { error: 'UNKNOWN_JSON', data: subJson };","sourceCodeStart":64,"sourceCodeEnd":100,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/bilibili/subtitle.js#L64-L100","documentation":"A CommandExecutionError thrown when the resolved subtitle URL does not match /^https?:\\/\\// after protocol normalization. The library already prepends 'https:' to protocol-relative '//...' URLs, so a non-http(s) value means the API returned something unrecognizable as a fetchable URL.","triggerScenarios":"target.subtitle_url contains a non-URL string (e.g. a relative path, placeholder text, or corrupted value) that doesn't start with '//' or 'http(s)://'.","commonSituations":"Bilibili API schema changes; proxies or response interceptors mangling the URL field; bogus data from cached/intercepted responses.","solutions":["Inspect the raw player API response to see what subtitle_url actually contains.","Re-run the request — if it's transient data corruption it may resolve.","Update the library if Bilibili changed the URL format (e.g. new scheme).","As a workaround, patch/normalize the URL yourself before fetch if you control the pipeline."],"exampleFix":"// before\nconst finalUrl = targetSubUrl.startsWith('//') ? 'https:' + targetSubUrl : targetSubUrl;\n// after\nconst finalUrl = targetSubUrl.startsWith('//')\n  ? 'https:' + targetSubUrl\n  : targetSubUrl.startsWith('/') ? 'https://api.bilibili.com' + targetSubUrl : targetSubUrl;","handlingStrategy":"validation","validationCode":"const isHttpUrl = (u) => typeof u === 'string' && /^https?:\\/\\//i.test(u.startsWith('//') ? 'https:' + u : u);\nif (!isHttpUrl(track.subtitle_url)) throw new Error('bad subtitle url');","typeGuard":"const isHttpUrl = (u) => typeof u === 'string' && /^https?:\\/\\//i.test(u);","tryCatchPattern":"try {\n  await run('bilibili subtitle', { url });\n} catch (e) {\n  if (/字幕 URL 非法/.test(e.message)) throw new Error(`Upstream returned invalid subtitle URL; raw response should be inspected: ${e.message}`);\n  throw e;\n}","preventionTips":["Keep the library updated for URL format changes in the API.","Log raw player API responses when debugging URL issues.","Disable response-rewriting proxies/interceptors for bilibili domains."],"tags":["bilibili","subtitle","url","validation"],"backgroundTag":"invalid-url-format","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}