jackwener/OpenCLI · error · CommandExecutionError

Failed to parse 12306 station_name.js: source string not fou

Error message

Failed to parse 12306 station_name.js: source string not found

What it means

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.

Source

Thrown at clis/12306/utils.js:44

 *
 * Bundle format (single line, `@`-delimited records, each `|`-delimited):
 *   `var station_names ='@bjb|北京北|VAP|beijingbei|bjb|0|0357|北京|||...';`
 *
 * Per-record fields (positional):
 *   [0] short pinyin alias  (e.g. `bjb`)
 *   [1] Chinese station name (e.g. `北京北`)
 *   [2] telecode (3-4 uppercase letters, e.g. `VAP`) - this is the
 *       wire format 12306 uses for `from_station` / `to_station`.
 *   [3] full pinyin           (e.g. `beijingbei`)
 *   [4] short alias           (duplicate of [0] usually)
 *   [5] index/rank
 *   [6] city code
 *   [7] city name             (e.g. `北京`)
 */
export function parseStationBundle(text) {
    const match = text.match(/'([^']+)'/);
    if (!match) {
        throw new CommandExecutionError('Failed to parse 12306 station_name.js: source string not found');
    }
    const raw = match[1];
    const records = raw.split('@').filter(Boolean);
    const stations = [];
    for (const r of records) {
        const parts = r.split('|');
        if (parts.length < 8 || !parts[2]) continue;
        stations.push({
            short: parts[0] || '',
            name: parts[1] || '',
            code: parts[2] || '',
            pinyin: parts[3] || '',
            abbr: parts[4] || '',
            city: parts[7] || '',
        });
    }
    if (stations.length === 0) {
        throw new CommandExecutionError('Failed to parse 12306 station_name.js: no station records found');

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Re-run after a delay and from a non-datacenter network — the anti-bot HTML usually disappears.
  2. Log/inspect the beginning of the fetched text to see what was actually returned (HTML? JSON? empty?).
  3. 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.
  4. Cache a copy of the station bundle locally and fall back to it when the live fetch is unusable.

Example fix

// before
return parseStationBundle(await resp.text());
// after
const text = await resp.text();
if (/<html/i.test(text)) {
    throw new CommandExecutionError('station_name.js fetch got an HTML page (anti-bot or moved resource); check STATION_BUNDLE_URL');
}
return parseStationBundle(text);
Defensive patterns

Strategy: fallback

Validate before calling

const text = await (await fetch(STATION_BUNDLE_URL, { headers: { 'User-Agent': UA } })).text();
if (!text.includes("var station_names")) {
  console.warn('station_name.js body is not the expected JS — WAF page or moved resource');
}

Type guard

function isStationBundleScript(text) {
  return typeof text === 'string' && /var\s+station_names\s*=\s*'/.test(text);
}

Try / catch

try {
  const stations = await fetchStationBundle();
} catch (e) {
  if (/source string not found/.test(e.message)) {
    // serve a cached copy while the live fetch is blocked/changed
    const stations = parseStationBundle(await fs.readFile('./station_name.cache.js', 'utf8'));
  } else throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/0ea876de8b516223. Report an issue: GitHub.