yz-yuki-plugin 2.0.8-8 → 2.0.9-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,979 @@
1
+ import WeiboApi from './weibo.main.api.js';
2
+ import axioss from 'axios';
3
+ import { wrapper } from 'axios-cookiejar-support';
4
+ import crypto from 'crypto';
5
+ import lodash from 'lodash';
6
+ import fetch from 'node-fetch';
7
+ import querystring from 'querystring';
8
+ import * as tough from 'tough-cookie';
9
+ import { Redis, Segment } from 'yunzaijs';
10
+
11
+ const axios = wrapper(axioss);
12
+ const START_URL = 'https://m.weibo.cn/u/7643376782';
13
+ class WeiboRiskCookie {
14
+ cookieJar;
15
+ prefix;
16
+ constructor() {
17
+ this.prefix = 'Yz:yuki:weibo:cookie';
18
+ this.cookieJar = new tough.CookieJar(null, { looseMode: true });
19
+ this.initialize();
20
+ }
21
+ async initialize() {
22
+ await this.ensureLongLivedCookies(this.cookieJar);
23
+ }
24
+ // 新增:根据步骤返回严格的请求头
25
+ static getHeadersForStep(step, referer) {
26
+ switch (step) {
27
+ case 1: // GET m.weibo 页面
28
+ return {
29
+ 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
30
+ 'accept-encoding': 'gzip, deflate, br, zstd',
31
+ 'accept-language': 'zh-CN,zh;q=0.9',
32
+ 'priority': 'u=0, i',
33
+ /* 'sec-ch-ua': '"Chromium";v="142", "Google Chrome";v="142", "Not_A Brand";v="99"',
34
+ 'sec-ch-ua-mobile': '?0',
35
+ 'sec-ch-ua-platform': '"Windows"', */
36
+ 'sec-fetch-dest': 'document',
37
+ 'sec-fetch-mode': 'navigate',
38
+ 'sec-fetch-site': 'none',
39
+ 'sec-fetch-user': '?1',
40
+ 'upgrade-insecure-requests': '1',
41
+ 'user-agent': `${WeiboApi.USER_AGENT}`
42
+ };
43
+ case 2: // POST enter
44
+ return {
45
+ 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
46
+ 'accept-encoding': 'gzip, deflate, br, zstd',
47
+ 'accept-language': 'zh-CN,zh;q=0.9',
48
+ 'priority': 'u=0, i',
49
+ /* 'sec-ch-ua': '"Chromium";v="142", "Google Chrome";v="142", "Not_A Brand";v="99"',
50
+ 'sec-ch-ua-mobile': '?0',
51
+ 'sec-ch-ua-platform': '"Windows"', */
52
+ 'sec-fetch-dest': 'document',
53
+ 'sec-fetch-mode': 'navigate',
54
+ 'sec-fetch-site': 'none',
55
+ 'sec-fetch-user': '?1',
56
+ 'upgrade-insecure-requests': '1',
57
+ 'user-agent': `${WeiboApi.USER_AGENT}`
58
+ };
59
+ case 3: // GET mini_original.js
60
+ return Object.assign({
61
+ 'accept': '*/*',
62
+ 'accept-encoding': 'gzip, deflate, br, zstd',
63
+ 'accept-language': 'zh-CN,zh;q=0.9',
64
+ 'priority': 'u=1',
65
+ /* 'sec-ch-ua': '"Chromium";v="142", "Google Chrome";v="142", "Not_A Brand";v="99"',
66
+ 'sec-ch-ua-mobile': '?0',
67
+ 'sec-ch-ua-platform': '"Windows"', */
68
+ 'sec-fetch-dest': 'script',
69
+ 'sec-fetch-mode': 'no-cors',
70
+ 'sec-fetch-site': 'same-origin',
71
+ 'user-agent': `${WeiboApi.USER_AGENT}`
72
+ }, referer ? { referer: referer } : {});
73
+ case 4: // GET 1.2.1.umd.js
74
+ return Object.assign({
75
+ 'accept': '*/*',
76
+ 'accept-encoding': 'gzip, deflate, br, zstd',
77
+ 'accept-language': 'zh-CN,zh;q=0.9',
78
+ 'priority': 'u=1',
79
+ /* 'sec-ch-ua': '"Chromium";v="142", "Google Chrome";v="142", "Not_A Brand";v="99"',
80
+ 'sec-ch-ua-mobile': '?0',
81
+ 'sec-ch-ua-platform': '"Windows"', */
82
+ 'sec-fetch-dest': 'script',
83
+ 'sec-fetch-mode': 'no-cors',
84
+ 'sec-fetch-site': 'cross-site',
85
+ 'sec-fetch-storage-access': 'none',
86
+ 'user-agent': `${WeiboApi.USER_AGENT}`
87
+ }, referer ? { referer: referer } : {});
88
+ case 5: // POST bd
89
+ return {
90
+ 'accept': '*/*',
91
+ 'accept-encoding': 'gzip, deflate, br, zstd',
92
+ 'accept-language': 'zh-CN,zh;q=0.9',
93
+ 'content-type': 'application/x-www-form-urlencoded',
94
+ 'origin': 'https://visitor.passport.weibo.cn',
95
+ 'priority': 'u=1, i',
96
+ 'referer': 'https://visitor.passport.weibo.cn/',
97
+ /* 'sec-ch-ua': '"Chromium";v="142", "Google Chrome";v="142", "Not_A Brand";v="99"',
98
+ 'sec-ch-ua-mobile': '?0',
99
+ 'sec-ch-ua-platform': '"Windows"', */
100
+ 'sec-fetch-dest': 'empty',
101
+ 'sec-fetch-mode': 'cors',
102
+ 'sec-fetch-site': 'cross-site',
103
+ 'sec-fetch-storage-access': 'none',
104
+ 'user-agent': `${WeiboApi.USER_AGENT}`
105
+ };
106
+ case 6: // POST genvisitor2
107
+ return {
108
+ 'accept': '*/*',
109
+ 'accept-encoding': 'gzip, deflate, br, zstd',
110
+ 'accept-language': 'zh-CN,zh;q=0.9',
111
+ 'cache-control': 'max-age=0',
112
+ 'content-type': 'application/x-www-form-urlencoded',
113
+ 'if-modified-since': '0',
114
+ 'origin': 'https://visitor.passport.weibo.cn',
115
+ 'priority': 'u=1, i',
116
+ 'referer': referer || 'https://visitor.passport.weibo.cn/visitor/visitor',
117
+ /* 'sec-ch-ua': '"Chromium";v="142", "Google Chrome";v="142", "Not_A Brand";v="99"',
118
+ 'sec-ch-ua-mobile': '?0',
119
+ 'sec-ch-ua-platform': '"Windows"', */
120
+ 'sec-fetch-dest': 'empty',
121
+ 'sec-fetch-mode': 'cors',
122
+ 'sec-fetch-site': 'same-origin',
123
+ 'user-agent': `${WeiboApi.USER_AGENT}`
124
+ };
125
+ default:
126
+ return {};
127
+ }
128
+ }
129
+ async fetchInitialHtml(jar) {
130
+ const res = await axios.get(START_URL, {
131
+ jar,
132
+ withCredentials: true,
133
+ headers: WeiboRiskCookie.getHeadersForStep(1),
134
+ responseType: 'text'
135
+ });
136
+ return res.data;
137
+ }
138
+ static extractParamsFromHtml(html) {
139
+ const getString = (re, fallback = '') => {
140
+ const m = html.match(re);
141
+ return m ? m[1] : fallback;
142
+ };
143
+ const request_id = getString(/request_id\s*=\s*["']([\w\d]+)["']/i, '');
144
+ const return_url = getString(/var\s+return_url\s*=\s*["']([^"']+)["']/i, START_URL);
145
+ const ver = getString(/ver=(\d+)/i, '20250916');
146
+ const from = getString(/var\s+from\s*=\s*["']([^"']+)["']/i, 'weibo');
147
+ return { request_id, return_url, ver, from };
148
+ }
149
+ static extractTidFromJarSync(jar) {
150
+ const cookies = jar.getCookiesSync(START_URL);
151
+ const tidCookie = cookies.find(c => c.key === 'tid' || c.key === 'TID');
152
+ return tidCookie ? tidCookie.value : '';
153
+ }
154
+ // 简单 fingerprint(可按需增强)
155
+ static buildSimpleFingerprint() {
156
+ const randFracInt = (base, min = 0.6, max = 1) => {
157
+ const v = min + Math.random() * (max - min); // 0.6-1
158
+ return Math.floor(base * v); // 换成 Math.round(...) 或 Math.ceil(...) 根据需要
159
+ };
160
+ return JSON.stringify({
161
+ fp: {
162
+ '0': { s: 1, v: '1.2.1' },
163
+ '1': { s: 1, v: false },
164
+ '2': { s: 1, v: ['lang'] },
165
+ '3': { s: 1, v: `${WeiboApi.USER_AGENT}` },
166
+ '4': { s: -1, v: '' },
167
+ '5': { s: 1, v: 33 },
168
+ '6': { s: -1, v: '' },
169
+ '7': { s: 1, v: [['zh-CN'], ['zh-CN']] },
170
+ '8': { s: 1, v: true },
171
+ '9': { s: 1, v: 'default' },
172
+ '10': { s: 1, v: true },
173
+ '11': { s: 1, v: 5 },
174
+ '12': { s: -1, e: '' },
175
+ '13': { s: 1, v: '20030107' },
176
+ '14': { s: -1, e: '' },
177
+ '15': { s: 1, v: `${WeiboApi.USER_AGENT}` },
178
+ '16': { s: 1, v: { vendor: 'WebKit', renderer: 'WebKit WebGL' } },
179
+ '17': { s: -1, v: '' },
180
+ '18': {
181
+ s: 1,
182
+ v: { ow: randFracInt(2235, 0.6, 1), oh: randFracInt(1210, 0.6, 1), iw: randFracInt(589, 0.6, 1), ih: randFracInt(1089, 0.6, 1) }
183
+ },
184
+ '19': { s: 1, v: 'chrome' },
185
+ '20': { s: 1, v: 'webkit' },
186
+ '21': { s: 1, v: false },
187
+ '22': { s: 1, v: true },
188
+ '23': {
189
+ s: 1,
190
+ v: { ots: false, mtp: 0, mmtp: -1 }
191
+ }
192
+ },
193
+ bh: {
194
+ mt: [],
195
+ kt: {
196
+ down: 0,
197
+ up: 0
198
+ }
199
+ },
200
+ meta: {
201
+ isTraceKeyboard: false,
202
+ isTraceMouse: false
203
+ }
204
+ });
205
+ }
206
+ // UMD 公钥 DER bytes(来自 1.2.1.umd.js 的 Uint8Array)
207
+ static getUmdPublicKeyDer() {
208
+ const arr = [
209
+ 48, 129, 159, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0, 3, 129, 141, 0, 48, 129, 137, 2, 129, 129, 0, 180, 249, 101, 74, 227, 247, 222, 230,
210
+ 24, 220, 10, 149, 183, 131, 164, 185, 20, 166, 164, 114, 158, 71, 46, 151, 77, 71, 226, 23, 78, 67, 177, 246, 197, 249, 213, 39, 243, 55, 38, 112, 17, 64,
211
+ 135, 155, 109, 50, 185, 61, 21, 105, 106, 245, 148, 212, 127, 7, 18, 227, 255, 40, 199, 241, 65, 211, 167, 185, 232, 5, 186, 189, 245, 59, 161, 214, 48,
212
+ 160, 251, 21, 92, 187, 172, 83, 152, 11, 85, 72, 37, 137, 87, 104, 63, 39, 86, 6, 150, 84, 6, 178, 229, 220, 144, 133, 131, 212, 47, 139, 232, 185, 192,
213
+ 97, 89, 137, 170, 141, 39, 19, 85, 4, 153, 238, 75, 93, 243, 96, 206, 72, 135, 91, 2, 3, 1, 0, 1
214
+ ];
215
+ return Buffer.from(arr);
216
+ }
217
+ static derToPem(derBuf) {
218
+ const b64 = derBuf.toString('base64');
219
+ const lines = (b64.match(/.{1,64}/g) || []).join('\n');
220
+ return `-----BEGIN PUBLIC KEY-----\n${lines}\n-----END PUBLIC KEY-----\n`;
221
+ }
222
+ // 根据 UMD::Aa 逻辑加密并拼装 bd 的 payload(返回最终的字符串)
223
+ async makeBdPayload(fpStr) {
224
+ const key = crypto.randomBytes(16);
225
+ const iv = crypto.randomBytes(16);
226
+ // AES-CBC 加密 fingerprint
227
+ const cipher = crypto.createCipheriv('aes-128-cbc', key, iv);
228
+ let enc = cipher.update(Buffer.from(fpStr, 'utf8'));
229
+ enc = Buffer.concat([enc, cipher.final()]);
230
+ const aesBase64 = enc.toString('base64');
231
+ // RSA-OAEP(SHA-256) 加密 key+iv
232
+ const pubDer = WeiboRiskCookie.getUmdPublicKeyDer();
233
+ const pubPem = WeiboRiskCookie.derToPem(pubDer);
234
+ const rsaPlain = Buffer.concat([key, iv]);
235
+ const rsaCipher = crypto.publicEncrypt({
236
+ key: pubPem,
237
+ padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
238
+ oaepHash: 'sha256'
239
+ }, rsaPlain);
240
+ // 拼装: inner = '01' + rsaCipher + '02' + atob(aesBase64) ; final = '01' + base64(inner)
241
+ const prefix = Buffer.from('01', 'utf8');
242
+ const mid = Buffer.from('02', 'utf8');
243
+ const aesDecoded = Buffer.from(aesBase64, 'base64');
244
+ const inner = Buffer.concat([prefix, rsaCipher, mid, aesDecoded]);
245
+ const innerB64 = inner.toString('base64');
246
+ const final = '01' + innerB64;
247
+ return final;
248
+ }
249
+ async postFormWithJar(url, jar, dataObj, extraHeaders = {}) {
250
+ const body = querystring.stringify(dataObj);
251
+ const res = await axios.post(url, body, {
252
+ jar,
253
+ withCredentials: true,
254
+ headers: Object.assign({
255
+ 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
256
+ 'User-Agent': `${WeiboApi.USER_AGENT}`,
257
+ 'Referer': START_URL
258
+ }, extraHeaders),
259
+ responseType: 'text'
260
+ });
261
+ return res;
262
+ }
263
+ static parseCallbackJs(jsText, cbName = 'visitor_gray_callback') {
264
+ let m = jsText.match(new RegExp(cbName + '\\s*\\(\\s*([\\s\\S]*?)\\s*\\)\\s*;?'));
265
+ if (m) {
266
+ try {
267
+ return JSON.parse(m[1]);
268
+ }
269
+ catch (e) {
270
+ /* fallthrough */
271
+ }
272
+ }
273
+ m = jsText.match(/\{[\s\S]*\}/);
274
+ if (m) {
275
+ try {
276
+ return JSON.parse(m[0]);
277
+ }
278
+ catch (e) { }
279
+ }
280
+ return null;
281
+ }
282
+ async getNewSessionCookie() {
283
+ try {
284
+ const jar = new tough.CookieJar();
285
+ // 1) GET 初始页面
286
+ const html = await this.fetchInitialHtml(jar);
287
+ // 2) 解析页面内参数
288
+ const { request_id, return_url, ver, from } = WeiboRiskCookie.extractParamsFromHtml(html);
289
+ logger.info('parsed params:', { request_id, return_url, ver, from });
290
+ // 3) POST 到 visitor enter(按真实流程)
291
+ const enterUrl = 'https://visitor.passport.weibo.cn/visitor/visitor';
292
+ const rand = Math.random().toString().slice(2, 12);
293
+ const enterParams = {
294
+ entry: 'sinawap',
295
+ a: 'enter',
296
+ url: START_URL,
297
+ domain: '.weibo.cn',
298
+ sudaref: '',
299
+ ua: 'php-sso_sdk_client-0.6.36',
300
+ _rand: rand
301
+ };
302
+ logger.info('POST enter ->', enterUrl, enterParams);
303
+ await axios.post(enterUrl, querystring.stringify(enterParams), {
304
+ jar,
305
+ withCredentials: true,
306
+ headers: WeiboRiskCookie.getHeadersForStep(2),
307
+ responseType: 'text'
308
+ });
309
+ // 3.1 GET mini_original.js(模拟脚本加载)
310
+ const miniJsUrl = 'https://visitor.passport.weibo.cn/js/visitor/mini_original.js?v=20161116';
311
+ logger.info('GET mini_original.js ->', miniJsUrl);
312
+ await axios.get(miniJsUrl, {
313
+ jar,
314
+ withCredentials: true,
315
+ headers: WeiboRiskCookie.getHeadersForStep(3, `${enterUrl}?_rand=${rand}`),
316
+ responseType: 'text'
317
+ });
318
+ // 3.2 GET 1.2.1.umd.js(模拟脚本加载)
319
+ const umdJsUrl = 'https://passport.sinaimg.cn/js/fp/1.2.1.umd.js';
320
+ logger.info('GET 1.2.1.umd.js ->', umdJsUrl);
321
+ await axios.get(umdJsUrl, {
322
+ jar,
323
+ withCredentials: true,
324
+ headers: WeiboRiskCookie.getHeadersForStep(4, 'https://visitor.passport.weibo.cn/'),
325
+ responseType: 'text'
326
+ });
327
+ // 4) 构造指纹并向 bd 发送加密数据以获取 rid
328
+ const fp = WeiboRiskCookie.buildSimpleFingerprint();
329
+ const bdPayload = await this.makeBdPayload(fp);
330
+ const bdUrl = 'https://passport.weibo.com/sso/bd';
331
+ logger.info('POST bd ->', bdUrl);
332
+ const bdRes = await axios.post(bdUrl, querystring.stringify({ data: bdPayload, from: 'android-visitor' }), {
333
+ jar,
334
+ withCredentials: true,
335
+ headers: WeiboRiskCookie.getHeadersForStep(5),
336
+ responseType: 'text'
337
+ });
338
+ let bdJson = null;
339
+ try {
340
+ bdJson = typeof bdRes.data === 'string' ? JSON.parse(bdRes.data) : bdRes.data;
341
+ }
342
+ catch (e) {
343
+ console.warn('无法直接解析 bd 返回,尝试宽松解析:', bdRes.data.slice ? bdRes.data.slice(0, 200) : bdRes.data);
344
+ const m = String(bdRes.data).match(/\{[\s\S]*\}/);
345
+ if (m) {
346
+ try {
347
+ bdJson = JSON.parse(m[0]);
348
+ }
349
+ catch (e2) { }
350
+ }
351
+ }
352
+ if (!bdJson || !bdJson.data || !bdJson.data.rid) {
353
+ console.warn('bd 未返回 rid,返回内容:', bdRes.data);
354
+ }
355
+ const rid = bdJson && bdJson.data ? bdJson.data.rid : Date.now().toString();
356
+ logger.info('got rid:', rid);
357
+ // 5) 查询是否已有 tid cookie
358
+ let tid = WeiboRiskCookie.extractTidFromJarSync(jar);
359
+ logger.info('existing tid cookie:', tid || '(none)');
360
+ // 6) POST 到 genvisitor2(最终获取 cookie)
361
+ const genUrl = 'https://visitor.passport.weibo.cn/visitor/genvisitor2';
362
+ const genParams = {
363
+ cb: 'visitor_gray_callback',
364
+ ver: ver || '20250916',
365
+ request_id: request_id || '',
366
+ tid: tid || '',
367
+ from: from || 'weibo',
368
+ webdriver: 'false',
369
+ rid: rid,
370
+ return_url: return_url || START_URL
371
+ };
372
+ logger.info('POST genvisitor2 ->', genUrl, genParams);
373
+ const genRes = await axios.post(genUrl, querystring.stringify(genParams), {
374
+ jar,
375
+ withCredentials: true,
376
+ headers: WeiboRiskCookie.getHeadersForStep(6, `${enterUrl}?_rand=${rand}`),
377
+ responseType: 'text'
378
+ });
379
+ const parsed = WeiboRiskCookie.parseCallbackJs(genRes.data);
380
+ if (!parsed) {
381
+ console.warn('无法解析 genvisitor2 返回(非标准 JSONP),原文片段:', String(genRes.data).slice(0, 500));
382
+ }
383
+ else {
384
+ logger.info('genvisitor2 parsed:', parsed);
385
+ if (parsed.retcode === 20000000 && parsed.data && parsed.data.tid) {
386
+ const tidVal = parsed.data.tid;
387
+ await jar.setCookie(`tid=${tidVal}; Domain=.weibo.cn; Path=/`, START_URL);
388
+ logger.info('got tid from genvisitor2:', tidVal);
389
+ }
390
+ }
391
+ // 读取所有 cookie(跨域)
392
+ const allCookies = await new Promise((resolve, reject) => {
393
+ jar.store.getAllCookies((err, cookies) => {
394
+ if (err)
395
+ reject(err);
396
+ else
397
+ resolve(cookies || []);
398
+ });
399
+ });
400
+ // 遍历并复制到 m.weibo.cn
401
+ allCookies.forEach(async (cookie) => {
402
+ if (cookie.domain === 'weibo.cn') {
403
+ const newCookieStr = `${cookie.key}=${cookie.value}; Domain=.weibo.cn; Path=${cookie.path}; Expires=${cookie.expires}`;
404
+ await jar.setCookie(newCookieStr, 'https://m.weibo.cn');
405
+ }
406
+ });
407
+ logger.info('cookies in jar (all domains):');
408
+ allCookies.forEach(c => {
409
+ logger.info(` - ${c.key} = ${c.value}; domain=${c.domain}; path=${c.path}; hostOnly=${!!c.hostOnly}; secure=${!!c.secure}; httpOnly=${!!c.httpOnly}`);
410
+ });
411
+ // 7) 输出并持久化当前 cookie(包含所有域名)
412
+ await this.saveCookiesToRedis(jar);
413
+ logger.info('完成。注意:真实成功依赖服务器对指纹/行为数据的校验,可能需增强 fingerprint 与行为模拟。');
414
+ }
415
+ catch (err) {
416
+ console.error('error:', err);
417
+ }
418
+ }
419
+ async getSessionCookieJar() {
420
+ return this.cookieJar;
421
+ }
422
+ /**
423
+ * 简单策略:优先从 Redis 读取长期 cookie(如 SUP/SUBP),若缺失则触发完整获取流程
424
+ * */
425
+ async ensureLongLivedCookies(jar) {
426
+ // 常见域名为 .weibo.cn 或 m.weibo.cn,根据实际情况检查
427
+ const supKey1 = `${this.prefix}:weibo.com:SUP`;
428
+ const subpKey1 = `${this.prefix}:weibo.com:SUBP`;
429
+ const supKey2 = `${this.prefix}:weibo.cn:SUP`;
430
+ const subpKey2 = `${this.prefix}:weibo.cn:SUBP`;
431
+ const sup = (await Redis.get(supKey1)) || (await Redis.get(subpKey1));
432
+ const subp = (await Redis.get(supKey2)) || (await Redis.get(subpKey2));
433
+ if (sup || subp) {
434
+ // 直接把 Redis 的所有 cookie 恢复到 jar(包含 SUP/SUBP)
435
+ await this.loadCookiesFromRedis(jar);
436
+ return true;
437
+ }
438
+ else if (!sup || !subp) {
439
+ // 缺少长期 cookie:返回 false,调用方需执行完整流程生成并持久化
440
+ await this.getNewSessionCookie();
441
+ return false;
442
+ }
443
+ }
444
+ /** 从 Redis 恢复 Cookie 到 CookieJar */
445
+ async loadCookiesFromRedis(jar) {
446
+ const pattern = `${this.prefix}:*:*`;
447
+ // 第一步:扫描并迁移没有前导点的 domain 键(如 weibo.cn -> .weibo.cn)
448
+ let keys = await Redis.keys(pattern);
449
+ for (const key of keys) {
450
+ if (key.endsWith(':meta'))
451
+ continue;
452
+ const parts = key.split(':');
453
+ const name = parts.pop();
454
+ const domainRaw = parts.pop() || '';
455
+ if (!domainRaw.startsWith('.')) {
456
+ const newDomain = `.${domainRaw}`;
457
+ const newKey = `${this.prefix}:${newDomain}:${name}`;
458
+ try {
459
+ const value = await Redis.get(key);
460
+ if (value == null) {
461
+ // nothing to migrate
462
+ continue;
463
+ }
464
+ const metaKey = `${key}:meta`;
465
+ const metaRaw = await Redis.get(metaKey);
466
+ const ttl = await Redis.ttl(key);
467
+ if (ttl > 0) {
468
+ await Redis.set(newKey, value, { EX: ttl });
469
+ if (metaRaw)
470
+ await Redis.set(`${newKey}:meta`, metaRaw, { EX: ttl });
471
+ }
472
+ else {
473
+ await Redis.set(newKey, value);
474
+ if (metaRaw)
475
+ await Redis.set(`${newKey}:meta`, metaRaw);
476
+ }
477
+ // 删除旧键和旧元数据
478
+ await Redis.del(key);
479
+ await Redis.del(metaKey);
480
+ logger.info(`Migrated cookie key "${key}" -> "${newKey}".`);
481
+ }
482
+ catch (e) {
483
+ logger.warn('Failed to migrate redis cookie key:', key, e);
484
+ }
485
+ }
486
+ }
487
+ // 重新获取 keys,确保后续都是规范域名
488
+ keys = await Redis.keys(pattern);
489
+ for (const key of keys) {
490
+ if (key.endsWith(':meta'))
491
+ continue; // 跳过 meta 键
492
+ const value = await Redis.get(key);
493
+ if (value == null)
494
+ continue;
495
+ // 解析 key 获取 name 和 domain
496
+ const parts = key.split(':');
497
+ const name = parts.pop(); // Cookie 的 key
498
+ const domainRaw = parts.pop() || ''; // 可能已经是 .weibo.cn 或 .m.weibo.cn 等
499
+ const domain = domainRaw.startsWith('.') ? domainRaw : `.${domainRaw}`; // 统一为带前导点
500
+ // 获取元数据
501
+ const metaKey = `${key}:meta`;
502
+ const metaRaw = await Redis.get(metaKey);
503
+ let meta = {};
504
+ try {
505
+ meta = metaRaw ? JSON.parse(metaRaw) : {};
506
+ }
507
+ catch (e) {
508
+ meta = {};
509
+ }
510
+ // 构造 Cookie 字符串
511
+ const ttl = await Redis.ttl(key);
512
+ const path = meta.path || '/'; // 默认路径为 '/'
513
+ let cookieStr = `${name}=${value}; Domain=${domain}; Path=${path};`;
514
+ if (ttl > 0) {
515
+ const exp = new Date(Date.now() + ttl * 1000).toUTCString();
516
+ cookieStr += ` Expires=${exp};`;
517
+ }
518
+ if (meta.httpOnly)
519
+ cookieStr += ' HttpOnly;';
520
+ if (meta.secure)
521
+ cookieStr += ' Secure;';
522
+ try {
523
+ // 动态生成 URL,host 去掉前导点
524
+ const protocol = meta.secure ? 'https' : 'http';
525
+ const host = domain.startsWith('.') ? domain.slice(1) : domain;
526
+ const url = `${protocol}://${host}${path}`;
527
+ // 设置 Cookie 到 jar
528
+ await jar.setCookie(cookieStr, url);
529
+ logger.info(`Restored cookie "${name}" for domain "${domain}" and path "${path}".`);
530
+ }
531
+ catch (e) {
532
+ console.warn('Failed to restore cookie:', cookieStr, e);
533
+ }
534
+ }
535
+ }
536
+ /**
537
+ * 将 CookieJar 中的所有 Cookie 同步到 Redis
538
+ */
539
+ async saveCookiesToRedis(jar) {
540
+ // 获取所有 Cookie
541
+ this.cookieJar = jar;
542
+ const allCookies = await new Promise((resolve, reject) => {
543
+ jar.store.getAllCookies((err, cookies) => {
544
+ if (err) {
545
+ reject(err);
546
+ }
547
+ else {
548
+ resolve(cookies || []);
549
+ }
550
+ });
551
+ });
552
+ for (const cookie of allCookies) {
553
+ if (!cookie)
554
+ continue; // 跳过无效的 Cookie
555
+ if (typeof cookie.value === 'string' && cookie.value.toLowerCase() === 'deleted')
556
+ continue; // 跳过被删除的 Cookie
557
+ // 规范 domain 为带前导点,避免子域不可见的问题
558
+ const domainKey = cookie.domain ? (cookie.domain.startsWith('.') ? cookie.domain : `.${cookie.domain}`) : '.weibo.cn';
559
+ // 构造 Redis 键
560
+ const redisKey = `${this.prefix}:${domainKey}:${cookie.key}`;
561
+ // 计算 TTL(秒)
562
+ let ttl = 0;
563
+ const ttlInMs = cookie.TTL ? cookie.TTL() : 0; // TTL 返回毫秒,可能不存在
564
+ if (ttlInMs > 0) {
565
+ ttl = Math.floor(ttlInMs / 1000);
566
+ }
567
+ else if (ttlInMs === 0) {
568
+ // 已过期,跳过
569
+ continue;
570
+ }
571
+ // 构造元数据
572
+ const meta = JSON.stringify({
573
+ path: cookie.path,
574
+ httpOnly: !!cookie.httpOnly,
575
+ secure: !!cookie.secure
576
+ });
577
+ // 写入 Redis(使用 TTL 时一并写 meta)
578
+ try {
579
+ if (ttl && ttl > 0) {
580
+ await Redis.set(redisKey, cookie.value, { EX: ttl });
581
+ await Redis.set(`${redisKey}:meta`, meta, { EX: ttl });
582
+ }
583
+ else {
584
+ await Redis.set(redisKey, cookie.value);
585
+ await Redis.set(`${redisKey}:meta`, meta);
586
+ }
587
+ }
588
+ catch (error) {
589
+ logger.warn('Failed to save cookie to Redis:', redisKey, error);
590
+ }
591
+ }
592
+ }
593
+ /**
594
+ * 删除所有 Cookie 并同步清除 Redis 缓存
595
+ */
596
+ async resetCookiesAndRedis() {
597
+ try {
598
+ // 1. 清空 CookieJar 中的所有 Cookie
599
+ const allCookies = await new Promise((resolve, reject) => {
600
+ this.cookieJar.store.getAllCookies((err, cookies) => {
601
+ if (err)
602
+ reject(err);
603
+ else
604
+ resolve(cookies || []);
605
+ });
606
+ });
607
+ if (allCookies.length > 0) {
608
+ for (const cookie of allCookies) {
609
+ await this.cookieJar.setCookie(`${cookie.key}=; Expires=Thu, 01 Jan 1970 00:00:00 GMT`, `https://${cookie.domain}${cookie.path}`);
610
+ }
611
+ console.log('All cookies in CookieJar have been cleared.');
612
+ }
613
+ else {
614
+ console.log('No cookies found in CookieJar to clear.');
615
+ }
616
+ // 2. 清除 Redis 中的 Cookie 缓存
617
+ const pattern = `${this.prefix}:*:*`; // 匹配所有 Cookie 键
618
+ const keys = await Redis.keys(pattern);
619
+ if (keys.length > 0) {
620
+ // 删除匹配的键及其元数据
621
+ const deleteOperations = keys.map(key => ['del', key]);
622
+ const metaKeys = keys.map(key => `${key}:meta`);
623
+ deleteOperations.push(...metaKeys.map(metaKey => ['del', metaKey]));
624
+ if (deleteOperations.length > 0) {
625
+ const multi = Redis.multi(); // 创建 multi 实例
626
+ deleteOperations.forEach(operation => {
627
+ multi[operation[0]](operation[1]); // 动态调用命令
628
+ });
629
+ await multi.exec(); // 执行批量操作
630
+ console.log(`Deleted ${keys.length} cookies and their metadata from Redis.`);
631
+ }
632
+ }
633
+ else {
634
+ console.log('No cookies found in Redis to clear.');
635
+ }
636
+ this.cookieJar = new tough.CookieJar(); // 清空 CookieJar
637
+ await this.initialize(); // 重新初始化 CookieJar
638
+ console.log('Cookie reset completed successfully.');
639
+ }
640
+ catch (error) {
641
+ console.error('Error resetting cookies and Redis cache:', error);
642
+ throw error;
643
+ }
644
+ }
645
+ //** 从 CookieJar 中获取指定 key 的值 */
646
+ async getCookieValueByKeyFromString(jar, key, url) {
647
+ try {
648
+ // 获取与 URL 匹配的所有 Cookie 字符串
649
+ const cookieString = await new Promise((resolve, reject) => {
650
+ jar.getCookieString(url, (err, cookieString) => {
651
+ if (err)
652
+ reject(err);
653
+ else
654
+ resolve(cookieString || '');
655
+ });
656
+ });
657
+ // 解析目标 key 的值
658
+ const match = cookieString.match(new RegExp(`${key}=([^;]+)`));
659
+ return match ? match[1] : null;
660
+ }
661
+ catch (err) {
662
+ console.error(`Error fetching cookie with key "${key}":`, err);
663
+ return null;
664
+ }
665
+ }
666
+ /**
667
+ * *******************************************************************
668
+ * 微博登录
669
+ * *******************************************************************
670
+ */
671
+ /**查看当前ck是否登录*/
672
+ async checkWeiboLogin(e) {
673
+ const url = 'https://m.weibo.cn/api/config';
674
+ const jar = await WeiboCookieManager.getSessionCookieJar();
675
+ const X_XSRF_TOKEN = await WeiboCookieManager.getCookieValueByKeyFromString(jar, 'X-XSRF-TOKEN', url);
676
+ const resData = (await axios(url, {
677
+ method: 'GET',
678
+ jar,
679
+ withCredentials: true,
680
+ headers: lodash.merge(WeiboApi.WEIBO_HEADERS, {
681
+ 'X-XSRF-TOKEN': `${X_XSRF_TOKEN}`,
682
+ 'Host': 'm.weibo.cn',
683
+ 'Referer': 'https://m.weibo.cn'
684
+ })
685
+ })).data;
686
+ global?.logger?.debug(`微博验证登录状态:${JSON.stringify(resData)}`);
687
+ if (resData.data?.login === false) {
688
+ e.reply('微博账号未登录。\n注意:登录后如果检测动态频率设置过高可能会被微博风控/封号,请谨慎使用!');
689
+ return false;
690
+ }
691
+ else if (resData.data?.login === true) {
692
+ const uid = Number(resData.data.uid);
693
+ const user_token = resData.data.user_token;
694
+ const url_1 = `https://m.weibo.cn/profile/info?uid=${uid}`;
695
+ const X_XSRF_TOKEN_1 = await WeiboCookieManager.getCookieValueByKeyFromString(jar, 'X-XSRF-TOKEN', url_1);
696
+ const infoRes = (await axios(url_1, {
697
+ method: 'GET',
698
+ jar,
699
+ withCredentials: true,
700
+ headers: lodash.merge(WeiboApi.WEIBO_HEADERS, {
701
+ 'X-XSRF-TOKEN': `${X_XSRF_TOKEN_1}`,
702
+ 'Host': 'm.weibo.cn',
703
+ 'x-h5-user-token': `${user_token}`,
704
+ 'Referer': `https://m.weibo.cn/profile/${uid}?user_token=${user_token}`
705
+ })
706
+ }));
707
+ let uname = infoRes.data?.user?.screen_name;
708
+ let mid = infoRes.data?.user?.id;
709
+ let follow_count = infoRes.data?.user?.follow_count;
710
+ let svip = infoRes.data?.user?.svip;
711
+ e.reply(`~微博账号已登陆~\n昵称:${uname}\nuid:${mid}\nsvip等级:${svip}\n关注:${follow_count}`);
712
+ return true;
713
+ }
714
+ }
715
+ /**
716
+ * 扫码登录流程
717
+ */
718
+ async weiboLogin(e) {
719
+ const isLogin = await this.checkWeiboLogin(e);
720
+ if (!isLogin) {
721
+ const tokenKey = await this.applyLoginQRCode(e);
722
+ if (tokenKey && tokenKey.rid) {
723
+ const isSuccessLogin = await this.pollLoginQRCode(e, tokenKey.qrid, tokenKey.rid, tokenKey.X_CSRF_TOKEN);
724
+ return isSuccessLogin;
725
+ }
726
+ }
727
+ }
728
+ /**
729
+ * 登录前访问bd接口获取rid
730
+ * @param {string} X_CSRF_TOKEN - X_CSRF_TOKEN
731
+ * @returns {Promise<JSON>} 服务器响应结果
732
+ */
733
+ async getRidFromBd(X_CSRF_TOKEN) {
734
+ try {
735
+ // 构造指纹并向 bd 发送加密数据以获取 rid
736
+ const fp = WeiboRiskCookie.buildSimpleFingerprint();
737
+ const bdPayload = await this.makeBdPayload(fp);
738
+ const bdUrl = 'https://passport.weibo.com/sso/bd';
739
+ logger.info('POST bd ->', bdUrl);
740
+ const bdRes = await axios.post(bdUrl, querystring.stringify({ data: bdPayload, from: 'weibo' }), {
741
+ withCredentials: true,
742
+ headers: lodash.merge(WeiboApi.WEIBO_GET_BD_TOKEN_HEADERS, {
743
+ Origin: 'https://passport.weibo.com',
744
+ Cookie: `X-CSRF-TOKEN=${X_CSRF_TOKEN}`
745
+ }),
746
+ responseType: 'text'
747
+ });
748
+ // 尝试直接解析为 JSON
749
+ let parsedData = {};
750
+ if (typeof bdRes.data === 'string') {
751
+ try {
752
+ parsedData = JSON.parse(bdRes.data);
753
+ }
754
+ catch (e) {
755
+ console.warn('微博登录前访问bd接口:无法直接解析 bd 返回,尝试宽松解析:', bdRes.data.slice ? bdRes.data.slice(0, 200) : bdRes.data);
756
+ // 宽松解析逻辑
757
+ const match = String(bdRes.data).match(/\{[\s\S]*\}/);
758
+ if (match) {
759
+ try {
760
+ parsedData = JSON.parse(match[0]);
761
+ }
762
+ catch (e2) {
763
+ console.warn('宽松解析失败,返回内容:', bdRes.data);
764
+ }
765
+ }
766
+ }
767
+ }
768
+ else if (typeof bdRes.data === 'object') {
769
+ parsedData = bdRes.data;
770
+ }
771
+ // 检查是否包含有效的 rid
772
+ if (parsedData.retcode === 20000000 && parsedData.data?.rid) {
773
+ return parsedData; // 返回完整的解析结果
774
+ }
775
+ else {
776
+ console.warn(`微博登录前访问bd接口:返回异常,retcode=${parsedData.retcode}, msg=${parsedData.msg}`);
777
+ return { retcode: parsedData.retcode || -1, msg: parsedData.msg || '无效响应', data: {} };
778
+ }
779
+ }
780
+ catch (error) {
781
+ console.error('微博登录前访问bd接口:发生异常', error);
782
+ return { retcode: -1, msg: '网络请求或解析失败', error: String(error), data: {} };
783
+ }
784
+ }
785
+ /**申请登陆二维码(web端) */
786
+ async applyLoginQRCode(e) {
787
+ const response = await fetch('https://passport.weibo.com/sso/signin?entry=wapsso&source=wapssowb&url=https://m.weibo.cn/', {
788
+ method: 'GET',
789
+ headers: lodash.merge(WeiboApi.WEIBO_GET_X_CSRF_TOKEN_HEADERS, { Host: 'passport.weibo.com' }),
790
+ redirect: 'follow'
791
+ });
792
+ const setCookie = response.headers.get('set-cookie');
793
+ const tokenMatch = setCookie?.match(/X-CSRF-TOKEN=([^;]+)/);
794
+ const X_CSRF_TOKEN = tokenMatch ? tokenMatch[1].replace(/X-CSRF-TOKEN=/g, '') : null;
795
+ if (X_CSRF_TOKEN) {
796
+ const resData = (await fetch('https://passport.weibo.com/sso/v2/qrcode/image?entry=wapsso&size=180', {
797
+ method: 'GET',
798
+ headers: lodash.merge(WeiboApi.WEIBO_LOGIN_QR_CODE_HEADERS, {
799
+ 'Host': 'passport.weibo.com',
800
+ 'X-CSRF-TOKEN': X_CSRF_TOKEN,
801
+ 'Cookie': `X-CSRF-TOKEN=${X_CSRF_TOKEN}`
802
+ }),
803
+ redirect: 'follow'
804
+ }).then(res => res.json()));
805
+ if (resData?.retcode === 20000000) {
806
+ const qrid = resData?.data?.qrid;
807
+ const qrcodeUrl = resData?.data?.image;
808
+ let msg = [];
809
+ if (qrid && qrcodeUrl) {
810
+ const imgResponse = await fetch(qrcodeUrl, {
811
+ method: 'GET',
812
+ headers: WeiboApi.WEIBO_LOGIN_QR_CODE_IMAGE_HEADERS,
813
+ redirect: 'follow'
814
+ });
815
+ if (!imgResponse.ok) {
816
+ logger.error(`获取微博登录二维码失败: ${imgResponse.status}`);
817
+ throw new Error(`获取微博登录图片失败,状态码: ${imgResponse.status}`);
818
+ }
819
+ // 等待3秒再获取rid
820
+ await new Promise(resolve => setTimeout(resolve, 3000));
821
+ const ridData = (await this.getRidFromBd(X_CSRF_TOKEN));
822
+ if (ridData.retcode === 20000000) {
823
+ const rid = ridData.data?.rid;
824
+ const arrayBuffer = await imgResponse.arrayBuffer();
825
+ msg.push(Segment.image(Buffer.from(arrayBuffer)));
826
+ e.reply('请在3分钟内扫码以完成微博登陆绑定');
827
+ e.reply(msg);
828
+ logger.info(`优纪插件: 如果发送二维码图片消息失败可复制如下URL, 浏览器访问此URL查看二维码并扫码`);
829
+ logger.info(`优纪插件: 微博登陆二维码URL: ${qrcodeUrl}`);
830
+ return { qrid, rid, X_CSRF_TOKEN };
831
+ }
832
+ else {
833
+ logger.error('微博登录:获取rid失败', ridData);
834
+ e.reply(`获取微博登录rid密钥失败: ${JSON.stringify(ridData)},\n接口逆向进度受阻,未完成,无法登录。\n已自动切换启用访客ck`);
835
+ throw new Error(`获取微博登录rid密钥失败: ${JSON.stringify(ridData)},\n接口逆向进度受阻,未完成,无法登录。\n已自动切换启用访客ck`);
836
+ }
837
+ }
838
+ }
839
+ else {
840
+ e.reply(`获取微博登录二维码失败: ${JSON.stringify(resData)}`);
841
+ throw new Error(`获取微博登录二维码失败: ${JSON.stringify(resData)}`);
842
+ }
843
+ }
844
+ else {
845
+ logger.error('微博登录:获取X_CSRF_TOKEN失败');
846
+ return false;
847
+ }
848
+ }
849
+ /**处理扫码结果 */
850
+ async pollLoginQRCode(e, qrid, rid, X_CSRF_TOKEN) {
851
+ const url = `https://passport.weibo.com/sso/v2/qrcode/check?entry=wapsso&source=wapssowb&url=https://m.weibo.cn/&qrid=${qrid}&rid=${rid}&ver=20250520`;
852
+ const response = await axios(url, {
853
+ method: 'GET',
854
+ headers: lodash.merge(WeiboApi.WEIBO_POLL_LOGIN_STATUS_HEADERS, {
855
+ 'X-CSRF-TOKEN': X_CSRF_TOKEN,
856
+ 'Cookie': `X-CSRF-TOKEN=${X_CSRF_TOKEN}`
857
+ }),
858
+ maxRedirects: 0
859
+ });
860
+ if (!response) {
861
+ throw new Error(`处理B站登录token网络请求失败,状态码: ${response.status}`);
862
+ }
863
+ const data = (await response.data);
864
+ if (data?.retcode === 20000000) {
865
+ // 获取 cookie url
866
+ // 获取 cookie url
867
+ let currentUrl = data?.data?.url;
868
+ const jar = new tough.CookieJar();
869
+ // 保存初始状态 Cookie
870
+ const initialCookies = await new Promise((resolve, reject) => {
871
+ jar.store.getAllCookies((err, cookies) => {
872
+ if (err)
873
+ reject(err);
874
+ else
875
+ resolve(cookies || []);
876
+ });
877
+ });
878
+ if (currentUrl) {
879
+ // 处理 5 次重定向
880
+ for (let i = 0; i < 4; i++) {
881
+ try {
882
+ // 确保 currentUrl 不为 undefined
883
+ if (!currentUrl) {
884
+ throw new Error('currentUrl 不能为空');
885
+ }
886
+ const resp = await axios.get(currentUrl, {
887
+ method: 'GET',
888
+ jar,
889
+ withCredentials: true,
890
+ headers: lodash.merge(WeiboApi.WEIBO_COOKIE_HEADERS, {
891
+ Referer: 'https://passport.weibo.com/'
892
+ }),
893
+ maxRedirects: 0
894
+ });
895
+ // 如果响应状态码为 302,提取 location 并继续
896
+ if (resp.status === 302) {
897
+ currentUrl = resp.headers['location'];
898
+ continue;
899
+ }
900
+ }
901
+ catch (error) {
902
+ // 类型守卫:确认 error 是 AxiosError 类型
903
+ if (axios.isAxiosError(error)) {
904
+ const axiosError = error; // 类型断言
905
+ if (axiosError.response && axiosError.response.status === 302) {
906
+ console.log('捕获到 302 重定向:', axiosError.response.data);
907
+ }
908
+ }
909
+ else {
910
+ console.error('非 Axios 错误:', error);
911
+ }
912
+ }
913
+ }
914
+ // 获取请求完成后的 Cookie 状态
915
+ const updatedCookies = await new Promise((resolve, reject) => {
916
+ jar.store.getAllCookies((err, cookies) => {
917
+ if (err)
918
+ reject(err);
919
+ else
920
+ resolve(cookies || []);
921
+ });
922
+ });
923
+ // 构建初始状态的 Map
924
+ const initialCookieMap = new Map(initialCookies.map(c => [`${c.key}:${c.domain}`, c]));
925
+ // 构建更新后的 Map
926
+ const updatedCookieMap = new Map(updatedCookies.map(c => [`${c.key}:${c.domain}`, c]));
927
+ // 比较两个 Map
928
+ let hasUpdates = false; // 标记是否有 Cookie 更新
929
+ for (const [key, updatedCookie] of updatedCookieMap.entries()) {
930
+ const initialCookie = initialCookieMap.get(key);
931
+ if (!initialCookie || initialCookie.value !== updatedCookie.value) {
932
+ logger.info(`Weibo Cookie ${updatedCookie.key} was updated.`);
933
+ hasUpdates = true; // 标记有更新
934
+ }
935
+ }
936
+ // 如果有更新,统一保存到 Redis
937
+ if (hasUpdates) {
938
+ logger.info('weibo: Detected updates in cookies, saving all cookies to Redis...');
939
+ logger.info('weibo: cookies in jar (all domains):');
940
+ updatedCookies.forEach(c => {
941
+ logger.info(` - ${c.key} = ${c.value}; domain=${c.domain}; path=${c.path}; hostOnly=${!!c.hostOnly}; secure=${!!c.secure}; httpOnly=${!!c.httpOnly}`);
942
+ });
943
+ await WeiboCookieManager.saveCookiesToRedis(jar); // 统一保存
944
+ await e.reply(`~微博登陆成功~`);
945
+ return true;
946
+ }
947
+ else {
948
+ await e.reply(`获取微博登录Cookie失败`);
949
+ return false;
950
+ }
951
+ }
952
+ }
953
+ else if (data?.retcode === 50114001) {
954
+ // 未扫码
955
+ // 继续轮询
956
+ await new Promise(resolve => setTimeout(resolve, 2000));
957
+ global?.logger?.mark(`优纪插件:扫码微博登录:未扫码,轮询中...`);
958
+ return this.pollLoginQRCode(e, qrid, rid, X_CSRF_TOKEN);
959
+ }
960
+ else if (data?.retcode === 50114002) {
961
+ // 已扫码未确认
962
+ // 继续轮询
963
+ await new Promise(resolve => setTimeout(resolve, 2000));
964
+ return this.pollLoginQRCode(e, qrid, rid, X_CSRF_TOKEN);
965
+ }
966
+ else if (data?.retcode === 50114003) {
967
+ // 二维码已失效
968
+ e.reply('微博登陆二维码已失效');
969
+ return false;
970
+ }
971
+ else {
972
+ e.reply('处理微博登录扫码结果出错');
973
+ throw new Error(`处理微博登录扫码结果出错: ${JSON.stringify(data)}`);
974
+ }
975
+ }
976
+ }
977
+ const WeiboCookieManager = new WeiboRiskCookie();
978
+
979
+ export { WeiboCookieManager as default };