{"record":{"id":"0ea876de8b516223","repo":"jackwener/OpenCLI","slug":"failed-to-parse-12306-station-name-js-source-stri","errorCode":null,"errorMessage":"Failed to parse 12306 station_name.js: source string not found","messagePattern":"Failed to parse 12306 station_name\\.js: source string not found","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/12306/utils.js","lineNumber":44,"sourceCode":" *\n * Bundle format (single line, `@`-delimited records, each `|`-delimited):\n *   `var station_names ='@bjb|北京北|VAP|beijingbei|bjb|0|0357|北京|||...';`\n *\n * Per-record fields (positional):\n *   [0] short pinyin alias  (e.g. `bjb`)\n *   [1] Chinese station name (e.g. `北京北`)\n *   [2] telecode (3-4 uppercase letters, e.g. `VAP`) - this is the\n *       wire format 12306 uses for `from_station` / `to_station`.\n *   [3] full pinyin           (e.g. `beijingbei`)\n *   [4] short alias           (duplicate of [0] usually)\n *   [5] index/rank\n *   [6] city code\n *   [7] city name             (e.g. `北京`)\n */\nexport function parseStationBundle(text) {\n    const match = text.match(/'([^']+)'/);\n    if (!match) {\n        throw new CommandExecutionError('Failed to parse 12306 station_name.js: source string not found');\n    }\n    const raw = match[1];\n    const records = raw.split('@').filter(Boolean);\n    const stations = [];\n    for (const r of records) {\n        const parts = r.split('|');\n        if (parts.length < 8 || !parts[2]) continue;\n        stations.push({\n            short: parts[0] || '',\n            name: parts[1] || '',\n            code: parts[2] || '',\n            pinyin: parts[3] || '',\n            abbr: parts[4] || '',\n            city: parts[7] || '',\n        });\n    }\n    if (stations.length === 0) {\n        throw new CommandExecutionError('Failed to parse 12306 station_name.js: no station records found');","sourceCodeStart":26,"sourceCodeEnd":62,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/12306/utils.js#L26-L62","documentation":"CommandExecutionError from parseStationBundle when the downloaded station_name.js text contains no single-quoted string literal. The parser expects the bundle in the form `var station_names ='@...|...@...';` and regex-extracts the first quoted string; if none is found the fetched resource is not the expected script, so parsing aborts.","triggerScenarios":"fetchStationBundle got HTTP 200 but the body is HTML (anti-bot/WAF page, error page, maintenance notice) or some other non-JS content with no `'...'` literal — e.g. a login redirect page or a JSON error document from kyfw.12306.cn.","commonSituations":"12306 WAF serving an HTML challenge to datacenter IPs; 12306 moved or renamed station_name.js so the URL now serves a 200 error page; offline dev proxy returning a captive-portal page; 12306 down for maintenance returning HTML with status 200.","solutions":["Re-run after a delay and from a non-datacenter network — the anti-bot HTML usually disappears.","Log/inspect the beginning of the fetched text to see what was actually returned (HTML? JSON? empty?).","Verify the station bundle URL (https://kyfw.12306.cn/otn/resources/js/framework/station_name.js) still serves the JS — if 12306 moved it, update STATION_BUNDLE_URL in clis/12306/utils.js.","Cache a copy of the station bundle locally and fall back to it when the live fetch is unusable."],"exampleFix":"// before\nreturn parseStationBundle(await resp.text());\n// after\nconst text = await resp.text();\nif (/<html/i.test(text)) {\n    throw new CommandExecutionError('station_name.js fetch got an HTML page (anti-bot or moved resource); check STATION_BUNDLE_URL');\n}\nreturn parseStationBundle(text);","handlingStrategy":"fallback","validationCode":"const text = await (await fetch(STATION_BUNDLE_URL, { headers: { 'User-Agent': UA } })).text();\nif (!text.includes(\"var station_names\")) {\n  console.warn('station_name.js body is not the expected JS — WAF page or moved resource');\n}","typeGuard":"function isStationBundleScript(text) {\n  return typeof text === 'string' && /var\\s+station_names\\s*=\\s*'/.test(text);\n}","tryCatchPattern":"try {\n  const stations = await fetchStationBundle();\n} catch (e) {\n  if (/source string not found/.test(e.message)) {\n    // serve a cached copy while the live fetch is blocked/changed\n    const stations = parseStationBundle(await fs.readFile('./station_name.cache.js', 'utf8'));\n  } else throw e;\n}","preventionTips":["Cache the station bundle locally and refresh on success.","Detect HTML responses (anti-bot/WAF) before parsing and back off.","Verify the STATION_BUNDLE_URL still resolves to the JS resource after 12306 site updates.","Run from residential networks; datacenter IPs frequently get HTML challenge pages."],"tags":["parsing","html-response","anti-bot","resource-changed"],"backgroundTag":"unexpected-response-format","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}