{"record":{"id":"0781acd2d67651ec","repo":"DIYgod/RSSHub","slug":"cannot-find-n-token","errorCode":null,"errorMessage":"Cannot find n-token","messagePattern":"Cannot find n-token","errorType":"exception","errorClass":"Error","httpStatus":503,"severity":"error","filePath":"lib/routes/newrank/wechat.ts","lineNumber":58,"sourceCode":"    const { data: summaryHTML } = await got({\n        method: 'get',\n        url: `https://www.newrank.cn/new/readDetial?account=${uid}`,\n        headers: {\n            Connection: 'keep-alive',\n            Cookie: config.newrank.cookie,\n        },\n    });\n    const summary$ = load(summaryHTML);\n    const mainsrc = summary$('script')\n        .toArray()\n        .find((item) => (item.attribs.src || '').startsWith('/new/static/js/main.'))!.attribs.src;\n    const { data: mainScript } = await got({\n        method: 'get',\n        url: `https://www.newrank.cn${mainsrc}`,\n    });\n    const N_TOKEN_match = mainScript.match(/\"N-Token\":\"([^\"]+)/);\n    if (!N_TOKEN_match) {\n        throw new Error('Cannot find n-token');\n    }\n    const N_TOKEN = N_TOKEN_match[1];\n    const response = await got({\n        method: 'post',\n        url: 'https://gw.newrank.cn/api/wechat/xdnphb/detail/v1/rank/article/lists',\n        headers: {\n            Connection: 'keep-alive',\n            Cookie: config.newrank.cookie,\n            'n-token': N_TOKEN,\n        },\n        form: {\n            account: uid,\n            nonce,\n            xyz: utils.decrypt_wechat_detail_xyz(uid, nonce),\n        },\n    });\n\n    const name = response.data.value.user.name;","sourceCodeStart":40,"sourceCodeEnd":76,"githubUrl":"https://github.com/DIYgod/RSSHub/blob/bed535e0879dc71c5aff6f1e7bd1ac21ede40115/lib/routes/newrank/wechat.ts#L40-L76","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","Check that config.newrank.cookie is valid — an expired cookie often makes newrank return a login page where the token never appears.","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.","File an issue on the RSSHub route so the regex can be updated upstream."],"exampleFix":"// before\nconst N_TOKEN_match = mainScript.match(/\"N-Token\":\"([^\"]+)/);\nif (!N_TOKEN_match) {\n    throw new Error('Cannot find n-token');\n}\n\n// after — try multiple known shapes before failing\nconst N_TOKEN_match =\n    mainScript.match(/\"N-Token\":\"([^\"]+)/) ||\n    mainScript.match(/N-Token['\"]?\\s*[:=]\\s*['\"]([^\"]+)/);\nif (!N_TOKEN_match) {\n    throw new Error('Cannot find n-token (newrank bundle changed)');\n}","handlingStrategy":"try-catch","validationCode":"// Pre-flight: confirm the bundle still contains an N-Token literal before relying on it.\nasync function bundleHasNToken(mainsrc) {\n  const { data } = await got(`https://www.newrank.cn${mainsrc}`);\n  return /\"N-Token\":\"[^\"]+\"/.test(data);\n}","typeGuard":"const hasNToken = (src: string): boolean => /\"N-Token\":\"[^\"]+\"/.test(src);","tryCatchPattern":"// Try several known shapes, then fail with actionable context.\nlet N_TOKEN;\ntry {\n  const m = mainScript.match(/\"N-Token\":\"([^\"]+)/) || mainScript.match(/N-Token['\"]?\\s*[:=]\\s*['\"]([^\"]+)/);\n  if (!m) throw new Error('no match');\n  N_TOKEN = m[1];\n} catch (e) {\n  throw new Error('Cannot find n-token — newrank bundle likely changed; cookie may be expired');\n}","preventionTips":["Treat the N-Token regex as fragile — wrap it and log the bundle hash so drift is detectable.","Verify the cookie is fresh before debugging the regex; expired cookies often cause the bundle to omit the token.","Consider capturing N-Token from a real request header via Puppeteer when static scraping breaks."],"tags":["scraping","regex","anti-crawler","fragile","newrank"],"backgroundTag":null,"analyzedSha":"bed535e0879dc71c5aff6f1e7bd1ac21ede40115","analyzedAt":"2026-08-12T19:29:35.364Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}