Skip to content

Commit 55ca8bb

Browse files
authored
feat(router/36kr): add WAF token handling (#21051)
1 parent 8ea0cd7 commit 55ca8bb

File tree

3 files changed

+49
-12
lines changed

3 files changed

+49
-12
lines changed

lib/routes/36kr/hot-list.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import cache from '@/utils/cache';
44
import got from '@/utils/got';
55
import { parseDate } from '@/utils/parse-date';
66

7-
import { ProcessItem, rootUrl } from './utils';
7+
import { getWafTokenId, ProcessItem, rootUrl } from './utils';
88

99
const categories = {
1010
24: {
@@ -70,9 +70,13 @@ async function handler(ctx) {
7070

7171
const currentUrl = category === '24' ? rootUrl : `${rootUrl}/hot-list/catalog`;
7272

73+
const wafTokenId = await getWafTokenId();
7374
const response = await got({
7475
method: 'get',
7576
url: currentUrl,
77+
headers: {
78+
Cookie: `_waftokenid=${wafTokenId}`,
79+
},
7680
});
7781

7882
const data = getProperty(JSON.parse(response.data.match(/window.initialState=({.*})/)[1]), categories[category].key);

lib/routes/36kr/utils.ts

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,20 @@
11
import { load } from 'cheerio';
22
import CryptoJS from 'crypto-js';
33

4-
import got from '@/utils/got';
4+
import { solveWafChallenge } from '@/routes/juejin/utils';
5+
import cache from '@/utils/cache';
6+
import ofetch from '@/utils/ofetch';
57

6-
const rootUrl = 'https://www.36kr.com';
8+
export const rootUrl = 'https://www.36kr.com';
79

8-
const ProcessItem = (item, tryGet) =>
10+
export const ProcessItem = (item, tryGet) =>
911
tryGet(item.link, async () => {
10-
const detailResponse = await got({
11-
method: 'get',
12-
url: item.link,
13-
});
12+
const detailResponse = await ofetch(item.link);
1413

15-
const cipherTextList = detailResponse.data.match(/{"state":"(.*)","isEncrypt":true}/) ?? [];
14+
const cipherTextList = detailResponse.match(/{"state":"(.*)","isEncrypt":true}/) ?? [];
1615

1716
if (cipherTextList.length === 0) {
18-
const $ = load(detailResponse.body);
17+
const $ = load(detailResponse);
1918
item.description = $('div.articleDetailContent').html();
2019
} else {
2120
const key = CryptoJS.enc.Utf8.parse('efabccee-b754-4c');
@@ -33,4 +32,33 @@ const ProcessItem = (item, tryGet) =>
3332
return item;
3433
});
3534

36-
export { ProcessItem, rootUrl };
35+
export const getWafTokenId = () =>
36+
cache.tryGet(
37+
'36kr:_waftokenid',
38+
async () => {
39+
const captchaResponse = await ofetch(rootUrl);
40+
41+
const $ = load(captchaResponse);
42+
const payload = $('script')
43+
.text()
44+
.match(/atob\('(.*?)'\)\),/)?.[1];
45+
const response = solveWafChallenge(payload);
46+
47+
const tokenIdResponse = await ofetch.raw(rootUrl, {
48+
headers: {
49+
Cookie: `_wafchallengeid=${response};`,
50+
},
51+
redirect: 'manual',
52+
});
53+
54+
const _wafTokenId = tokenIdResponse.headers
55+
.getSetCookie()
56+
.find((cookie) => cookie.startsWith('_waftokenid='))
57+
?.split(';')[0]
58+
.split('=')[1];
59+
60+
return _wafTokenId as string;
61+
},
62+
300, // server-provided value
63+
false
64+
);

lib/routes/juejin/utils.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@ const s256 = (s1: Uint8Array, s2: string) => {
1717
return sha.digest('hex');
1818
};
1919

20-
const solveWafChallenge = (cs) => {
20+
/**
21+
* Solve _wafchallengeid
22+
* @param cs - base64 encoded challenge string {"v":{"a":"...", "b":"timestamp", "c":"..."}, "s":"..."}
23+
* @returns base64 encoded solved challenge string {"v":{"a":"...", "b":"timestamp", "c":"..."}, "s":"...", "d":"solution"}
24+
*/
25+
export const solveWafChallenge = (cs: string) => {
2126
const c = JSON.parse(Buffer.from(cs, 'base64').toString());
2227
const prefix = b64tou8a(c.v.a);
2328
const expect = b64tohex(c.v.c);

0 commit comments

Comments
 (0)