DIYgod/RSSHub · error · Error

Cannot find n-token

Error message

Cannot find n-token

What it means

Thrown after the route fetches newrank.cn's bundled main.js and runs the regex /"N-Token":"([^"]+)/ against it. The N-Token is an anti-crawler header required by gw.newrank.cn's article-list API; if the regex finds no match, the script cannot proceed. This is a fragile scraping step that breaks whenever newrank renames, restructures, or removes the N-Token literal from its bundle.

Source

Thrown at lib/routes/newrank/wechat.ts:58

    const { data: summaryHTML } = await got({
        method: 'get',
        url: `https://www.newrank.cn/new/readDetial?account=${uid}`,
        headers: {
            Connection: 'keep-alive',
            Cookie: config.newrank.cookie,
        },
    });
    const summary$ = load(summaryHTML);
    const mainsrc = summary$('script')
        .toArray()
        .find((item) => (item.attribs.src || '').startsWith('/new/static/js/main.'))!.attribs.src;
    const { data: mainScript } = await got({
        method: 'get',
        url: `https://www.newrank.cn${mainsrc}`,
    });
    const N_TOKEN_match = mainScript.match(/"N-Token":"([^"]+)/);
    if (!N_TOKEN_match) {
        throw new Error('Cannot find n-token');
    }
    const N_TOKEN = N_TOKEN_match[1];
    const response = await got({
        method: 'post',
        url: 'https://gw.newrank.cn/api/wechat/xdnphb/detail/v1/rank/article/lists',
        headers: {
            Connection: 'keep-alive',
            Cookie: config.newrank.cookie,
            'n-token': N_TOKEN,
        },
        form: {
            account: uid,
            nonce,
            xyz: utils.decrypt_wechat_detail_xyz(uid, nonce),
        },
    });

    const name = response.data.value.user.name;

View on GitHub (pinned to bed535e087)

Solutions

  1. Open the resolved mainsrc URL in a browser and grep for N-Token / n-token / token literals to find the new key, then update the regex.
  2. Check that config.newrank.cookie is valid — an expired cookie often makes newrank return a login page where the token never appears.
  3. If newrank moved the token into another chunk, follow the webpack chunk graph or load the page in Puppeteer and read the request header to capture N-Token at runtime.
  4. File an issue on the RSSHub route so the regex can be updated upstream.

Example fix

// before
const N_TOKEN_match = mainScript.match(/"N-Token":"([^"]+)/);
if (!N_TOKEN_match) {
    throw new Error('Cannot find n-token');
}

// after — try multiple known shapes before failing
const N_TOKEN_match =
    mainScript.match(/"N-Token":"([^"]+)/) ||
    mainScript.match(/N-Token['"]?\s*[:=]\s*['"]([^"]+)/);
if (!N_TOKEN_match) {
    throw new Error('Cannot find n-token (newrank bundle changed)');
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-flight: confirm the bundle still contains an N-Token literal before relying on it.
async function bundleHasNToken(mainsrc) {
  const { data } = await got(`https://www.newrank.cn${mainsrc}`);
  return /"N-Token":"[^"]+"/.test(data);
}

Type guard

const hasNToken = (src: string): boolean => /"N-Token":"[^"]+"/.test(src);

Try / catch

// Try several known shapes, then fail with actionable context.
let N_TOKEN;
try {
  const m = mainScript.match(/"N-Token":"([^"]+)/) || mainScript.match(/N-Token['"]?\s*[:=]\s*['"]([^"]+)/);
  if (!m) throw new Error('no match');
  N_TOKEN = m[1];
} catch (e) {
  throw new Error('Cannot find n-token — newrank bundle likely changed; cookie may be expired');
}

Prevention

When it happens

Trigger: newrank ships a new JS bundle where the N-Token literal is split, minified under a different key, loaded dynamically, or removed entirely. The mainsrc selector (/new/static/js/main.) may still resolve, but the token string is no longer present in that file. Also possible if the got() request for the script is intercepted by a CDN/error page.

Common situations: newrank frontend deploy renames the token key; bundle chunking moves the token into a different chunk; the cookie is expired so newrank serves a login page instead of the app bundle; an anti-bot wall replaces the JS body.

Related errors


AI-assisted analysis of DIYgod/RSSHub@bed535e087 (2026-08-12). Data as JSON: /api/errors/0781acd2d67651ec. Report an issue: GitHub.