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.
- package/CHANGELOG.md +4 -0
- package/README.md +0 -3
- package/defaultConfig/bilibili/config.yaml +1 -0
- package/defaultConfig/help/help.yaml +0 -9
- package/defaultConfig/weibo/config.yaml +1 -0
- package/lib/apps/bilibili.js +36 -50
- package/lib/apps/weibo.js +19 -105
- package/lib/components/dynamic/Account.js +4 -4
- package/lib/components/dynamic/Content.js +4 -4
- package/lib/components/dynamic/Footer.js +4 -4
- package/lib/components/dynamic/ForwardContent.js +2 -2
- package/lib/components/dynamic/LogoText.js +2 -2
- package/lib/components/dynamic/MainPage.js +2 -2
- package/lib/components/help/Help.js +3 -3
- package/lib/components/loginQrcode/Page.js +2 -2
- package/lib/components/version/Version.js +2 -2
- package/lib/models/bilibili/bilibili.main.get.web.data.js +130 -59
- package/lib/models/bilibili/bilibili.main.query.js +16 -8
- package/lib/models/bilibili/bilibili.main.task.js +37 -11
- package/lib/models/bilibili/bilibili.risk.cookie.js +799 -0
- package/lib/models/weibo/weibo.main.api.js +7 -10
- package/lib/models/weibo/weibo.main.get.web.data.js +127 -69
- package/lib/models/weibo/weibo.main.task.js +14 -5
- package/lib/models/weibo/weibo.risk.cookie.js +979 -0
- package/package.json +5 -2
- package/resources/css/dynamic/MainPage.css +1 -1
- package/lib/models/bilibili/bilibili.main.models.js +0 -493
- package/lib/models/weibo/weibo.main.models.js +0 -347
|
@@ -0,0 +1,799 @@
|
|
|
1
|
+
import BiliApi from './bilibili.main.api.js';
|
|
2
|
+
import { gen_buvid_fp } from './bilibili.risk.buid.fp.js';
|
|
3
|
+
import { getBiliTicket } from './bilibili.risk.ticket.js';
|
|
4
|
+
import { renderPage } from '../../utils/image.js';
|
|
5
|
+
import { _paths } from '../../utils/paths.js';
|
|
6
|
+
import axioss from 'axios';
|
|
7
|
+
import { wrapper } from 'axios-cookiejar-support';
|
|
8
|
+
import fs__default from 'fs';
|
|
9
|
+
import lodash from 'lodash';
|
|
10
|
+
import { promisify } from 'node:util';
|
|
11
|
+
import path from 'path';
|
|
12
|
+
import QRCode from 'qrcode';
|
|
13
|
+
import * as tough from 'tough-cookie';
|
|
14
|
+
import YAML from 'yaml';
|
|
15
|
+
import { Segment, Redis } from 'yunzaijs';
|
|
16
|
+
|
|
17
|
+
const axios = wrapper(axioss);
|
|
18
|
+
const REDIS_PREFIX = 'Yz:yuki:bili:cookie';
|
|
19
|
+
class BiliRiskCookie {
|
|
20
|
+
cookieJar;
|
|
21
|
+
prefix;
|
|
22
|
+
constructor() {
|
|
23
|
+
this.prefix = REDIS_PREFIX;
|
|
24
|
+
this.cookieJar = new tough.CookieJar();
|
|
25
|
+
this.initialize().catch(err => logger?.error('BiliRiskCookie init error', err));
|
|
26
|
+
}
|
|
27
|
+
async initialize() {
|
|
28
|
+
await this.ensureLoginCookies(this.cookieJar);
|
|
29
|
+
}
|
|
30
|
+
async getSessionCookieJar() {
|
|
31
|
+
if (!this.cookieJar)
|
|
32
|
+
this.cookieJar = new tough.CookieJar();
|
|
33
|
+
return this.cookieJar;
|
|
34
|
+
}
|
|
35
|
+
/** 生成 _uuid */
|
|
36
|
+
async genUUID() {
|
|
37
|
+
const generatePart = (length) => Array.from({ length }, () => Math.floor(16 * Math.random()))
|
|
38
|
+
.map(num => num.toString(16).toUpperCase())
|
|
39
|
+
.join('');
|
|
40
|
+
const padLeft = (str, length) => str.padStart(length, '0');
|
|
41
|
+
const e = generatePart(8);
|
|
42
|
+
const t = generatePart(4);
|
|
43
|
+
const r = generatePart(4);
|
|
44
|
+
const n = generatePart(4);
|
|
45
|
+
const o = generatePart(12);
|
|
46
|
+
const i = Date.now();
|
|
47
|
+
const uuid = `_uuid=${e}-${t}-${r}-${n}-${o}${padLeft((i % 1e5).toString(), 5)}infoc;`;
|
|
48
|
+
return uuid;
|
|
49
|
+
}
|
|
50
|
+
/**生成 b_lsid */
|
|
51
|
+
async gen_b_lsid() {
|
|
52
|
+
function get_random_str(length) {
|
|
53
|
+
return Array.from({ length }, () => Math.floor(Math.random() * 16)
|
|
54
|
+
.toString(16)
|
|
55
|
+
.toUpperCase()).join('');
|
|
56
|
+
}
|
|
57
|
+
const timestamp = Date.now();
|
|
58
|
+
const randomPart = get_random_str(8);
|
|
59
|
+
const timestampHex = timestamp.toString(16).toUpperCase();
|
|
60
|
+
return `b_lsid=${randomPart}_${timestampHex};`;
|
|
61
|
+
}
|
|
62
|
+
/** 获取 buvid3 和 buvid4 */
|
|
63
|
+
async getBuvid3_4(uuid) {
|
|
64
|
+
const url = 'https://api.bilibili.com/x/frontend/finger/spi/';
|
|
65
|
+
const headers = lodash.merge({}, BiliApi.BILIBILI_HEADERS, {
|
|
66
|
+
Cookie: uuid,
|
|
67
|
+
Host: 'api.bilibili.com',
|
|
68
|
+
Origin: 'https://www.bilibili.com',
|
|
69
|
+
Referer: 'https://www.bilibili.com/'
|
|
70
|
+
});
|
|
71
|
+
const response = await axios.get(url, { headers });
|
|
72
|
+
const { code, data } = response.data;
|
|
73
|
+
if (code === 0) {
|
|
74
|
+
const { b_3: buvid3, b_4: buvid4 } = data;
|
|
75
|
+
return `buvid3=${buvid3};buvid4=${buvid4};`;
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
return '';
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**生成buvid_fp */
|
|
82
|
+
async get_buvid_fp(uuid) {
|
|
83
|
+
const fingerprintData = BiliApi.BILIBILI_FINGERPRINT_DATA(uuid);
|
|
84
|
+
const buvidFp = gen_buvid_fp(fingerprintData);
|
|
85
|
+
return `buvid_fp=${buvidFp};`;
|
|
86
|
+
}
|
|
87
|
+
/**获取新的tempCK(并写入 Jar)*/
|
|
88
|
+
async getNewTempCk() {
|
|
89
|
+
const uuid = await this.genUUID();
|
|
90
|
+
const b_nut = `b_nut=${Math.floor(Date.now() / 1000)};`; //秒 时间戳,有效时间1年
|
|
91
|
+
const buvid3_buvid4 = await this.getBuvid3_4(uuid); //有效时间1年
|
|
92
|
+
const b_lsid = await this.gen_b_lsid();
|
|
93
|
+
const buvid_fp = await this.get_buvid_fp(uuid);
|
|
94
|
+
const newTemp = `${uuid}${buvid3_buvid4}${b_lsid}${buvid_fp}${b_nut}`;
|
|
95
|
+
// 写入 Jar 持久化
|
|
96
|
+
await this.setCookieString(newTemp, 'https://api.bilibili.com');
|
|
97
|
+
return newTemp;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* B站扫码登录流程
|
|
101
|
+
*/
|
|
102
|
+
async biliLogin(e) {
|
|
103
|
+
const isLogin = await this.checkBiliLogin(e);
|
|
104
|
+
if (!isLogin) {
|
|
105
|
+
const tokenKey = await this.applyLoginQRCode(e);
|
|
106
|
+
if (tokenKey) {
|
|
107
|
+
await this.pollLoginQRCode(e, tokenKey);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
e.reply(`当前已有B站登录ck,请勿重复扫码!\n如需更换,请先删除当前登录ck:\n#yuki删除B站登录`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**申请登陆二维码(web端) */
|
|
115
|
+
async applyLoginQRCode(e) {
|
|
116
|
+
const url = 'https://passport.bilibili.com/x/passport-login/web/qrcode/generate?source=main-fe-header';
|
|
117
|
+
const response = await fetch(url, {
|
|
118
|
+
method: 'GET',
|
|
119
|
+
headers: lodash.merge(BiliApi.BIlIBILI_LOGIN_HEADERS, { 'User-Agent': BiliApi.BILIBILI_HEADERS['User-Agent'] }, { Host: 'passport.bilibili.com' }),
|
|
120
|
+
redirect: 'follow'
|
|
121
|
+
});
|
|
122
|
+
if (!response.ok) {
|
|
123
|
+
throw new Error(`获取B站登录二维码URL网络请求失败,状态码: ${response.status}`);
|
|
124
|
+
}
|
|
125
|
+
const res = (await response.json());
|
|
126
|
+
if (res?.code === 0) {
|
|
127
|
+
const qrcodeKey = res?.data?.qrcode_key;
|
|
128
|
+
const qrcodeUrl = res?.data?.url;
|
|
129
|
+
let loginUrlQrcodeData = await QRCode.toDataURL(`${qrcodeUrl}`);
|
|
130
|
+
const LoginPropsData = {
|
|
131
|
+
data: { name: 'B站', url: loginUrlQrcodeData }
|
|
132
|
+
};
|
|
133
|
+
const ScreenshotOptionsData = {
|
|
134
|
+
saveHtmlfile: false,
|
|
135
|
+
modelName: 'bili-login'
|
|
136
|
+
};
|
|
137
|
+
const qrCodeImage = await renderPage('bili-login', 'LoginQrcodePage', LoginPropsData, ScreenshotOptionsData);
|
|
138
|
+
let qrCodeBufferArray = [];
|
|
139
|
+
if (qrCodeImage !== false) {
|
|
140
|
+
const { img } = qrCodeImage;
|
|
141
|
+
qrCodeBufferArray = img;
|
|
142
|
+
}
|
|
143
|
+
let msg = [];
|
|
144
|
+
if (qrCodeBufferArray.length === 0) {
|
|
145
|
+
msg.push('渲染二维码图片失败,请查看终端输出的实时日志,\n复制哔哩登陆二维码URL,使用在线或本地二维码生成工具生成二维码并扫码。');
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
msg.push(Segment.image(qrCodeBufferArray[0]));
|
|
149
|
+
}
|
|
150
|
+
e.reply('请在3分钟内扫码以完成B站登陆绑定');
|
|
151
|
+
e.reply(msg);
|
|
152
|
+
logger.info(`优纪插件: 如果发送二维码图片消息失败可复制如下URL, 使用在线或本地二维码生成工具生成二维码并扫码`);
|
|
153
|
+
logger.info(`优纪插件: 哔哩登陆二维码URL: ${qrcodeUrl}`);
|
|
154
|
+
return qrcodeKey;
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
e.reply(`获取B站登录二维码失败: ${JSON.stringify(res.data)}`);
|
|
158
|
+
throw new Error(`获取B站登录二维码失败: ${JSON.stringify(res.data)}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/**处理扫码结果(扫码成功后将 set-cookie 写入 Jar 并持久化)*/
|
|
162
|
+
async pollLoginQRCode(e, qrcodeKey) {
|
|
163
|
+
const url = `https://passport.bilibili.com/x/passport-login/web/qrcode/poll?qrcode_key=${qrcodeKey}&source=main-fe-header`;
|
|
164
|
+
const jar = await this.getSessionCookieJar();
|
|
165
|
+
const response = await axios.get(url, {
|
|
166
|
+
jar,
|
|
167
|
+
withCredentials: true,
|
|
168
|
+
headers: lodash.merge(BiliApi.BIlIBILI_LOGIN_HEADERS, { 'User-agent': BiliApi.BILIBILI_HEADERS['User-Agent'] }, { Host: 'passport.bilibili.com' })
|
|
169
|
+
});
|
|
170
|
+
if (!response.status || response.status !== 200) {
|
|
171
|
+
throw new Error(`处理B站登录token网络请求失败,状态码: ${response.status}`);
|
|
172
|
+
}
|
|
173
|
+
let data = (await response.data);
|
|
174
|
+
if (data?.code === 0) {
|
|
175
|
+
if (data?.data?.code === 0) {
|
|
176
|
+
// 登录成功,获取 set-cookie header 并写入 Jar
|
|
177
|
+
const SESSDATA_expires = await this.getCookieExpiration(jar, 'SESSDATA', 'https://api.bilibili.com');
|
|
178
|
+
try {
|
|
179
|
+
if (SESSDATA_expires != null) {
|
|
180
|
+
const tempCookie = await this.getNewTempCk();
|
|
181
|
+
// 写入 CookieJar 并持久化
|
|
182
|
+
await this.setCookieString(tempCookie, 'https://api.bilibili.com');
|
|
183
|
+
await this.saveCookiesToRedis(jar);
|
|
184
|
+
e.reply(`~B站登陆成功~`);
|
|
185
|
+
const cookieString = await new Promise((resolve, reject) => {
|
|
186
|
+
jar.getCookieString('https://api.bilibili.com', (err, cookieString) => {
|
|
187
|
+
if (err)
|
|
188
|
+
reject(err);
|
|
189
|
+
else
|
|
190
|
+
resolve(cookieString || '');
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
const result = await this.postGateway(cookieString); //激活ck
|
|
194
|
+
const { code, data } = await result.data; // 解析校验结果
|
|
195
|
+
switch (code) {
|
|
196
|
+
case 0:
|
|
197
|
+
global?.logger?.mark(`优纪插件:获取biliLoginCK,Gateway校验成功:${JSON.stringify(data)}`);
|
|
198
|
+
break;
|
|
199
|
+
default:
|
|
200
|
+
global?.logger?.mark(`优纪插件:获取biliLoginCK,Gateway校验失败:${JSON.stringify(data)}`);
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
e.reply(`获取B站登录CK失败: ${data?.data?.message}`);
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
catch (err) {
|
|
211
|
+
logger.error(`获取B站登录CK失败: ${data?.data?.message}`);
|
|
212
|
+
logger.error(err);
|
|
213
|
+
e.reply(`获取B站登录CK失败: ${data?.data?.message?.slice(0, 100)}...`);
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
else if (data?.data?.code === 86101) {
|
|
218
|
+
// 继续轮询
|
|
219
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
220
|
+
(logger ?? Bot.logger)?.mark(`优纪插件:扫码B站登录:未扫码,轮询中...`);
|
|
221
|
+
return this.pollLoginQRCode(e, qrcodeKey);
|
|
222
|
+
}
|
|
223
|
+
else if (data?.data?.code === 86090) {
|
|
224
|
+
// 已扫码未确认
|
|
225
|
+
// 继续轮询
|
|
226
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
227
|
+
return this.pollLoginQRCode(e, qrcodeKey);
|
|
228
|
+
}
|
|
229
|
+
else if (data?.data?.code === 86038) {
|
|
230
|
+
// 二维码已失效
|
|
231
|
+
e.reply('B站登陆二维码已失效');
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
e.reply('处理扫码结果出错');
|
|
236
|
+
throw new Error(`处理扫码结果出错: ${data?.data?.message}`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
function safeStringify(obj, indent = 2) {
|
|
241
|
+
const seen = new WeakSet();
|
|
242
|
+
return JSON.stringify(obj, (key, value) => {
|
|
243
|
+
if (typeof value === 'object' && value !== null) {
|
|
244
|
+
if (seen.has(value)) {
|
|
245
|
+
return '[Circular]'; // 标记循环引用
|
|
246
|
+
}
|
|
247
|
+
seen.add(value); // 记录已访问的对象
|
|
248
|
+
}
|
|
249
|
+
return value;
|
|
250
|
+
}, indent);
|
|
251
|
+
}
|
|
252
|
+
e.reply('处理扫码结果出错');
|
|
253
|
+
throw new Error(`处理扫码结果出错: ${safeStringify(data)}`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
/**查看当前 Jar 中的登录 cookie 有效状态并返回信息(改为基于 jar)*/
|
|
257
|
+
async checkBiliLogin(e) {
|
|
258
|
+
// 读取 jar 中 api.bilibili.com 的 cookie 字符串
|
|
259
|
+
const jar = await this.getSessionCookieJar();
|
|
260
|
+
const SESSDATA_expires = await this.getCookieExpiration(jar, 'SESSDATA', 'https://api.bilibili.com');
|
|
261
|
+
const SESSDATA_str = await this.getCookieValueByKeyFromString(jar, 'SESSDATA', 'https://api.bilibili.com');
|
|
262
|
+
if (SESSDATA_expires === null) {
|
|
263
|
+
e.reply('当前未登录B站,接下来请扫码登录。');
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
const res = await fetch('https://api.bilibili.com/x/web-interface/nav', {
|
|
268
|
+
method: 'GET',
|
|
269
|
+
headers: lodash.merge(BiliApi.BIlIBILI_LOGIN_HEADERS, {
|
|
270
|
+
'User-agent': BiliApi.BILIBILI_HEADERS['User-Agent'],
|
|
271
|
+
'Cookie': SESSDATA_str ? SESSDATA_str : ''
|
|
272
|
+
}),
|
|
273
|
+
redirect: 'follow'
|
|
274
|
+
});
|
|
275
|
+
const resData = await res.json();
|
|
276
|
+
if (resData.code === 0) {
|
|
277
|
+
const uname = resData.data?.uname;
|
|
278
|
+
const mid = resData.data?.mid;
|
|
279
|
+
const money = resData.data?.money;
|
|
280
|
+
const level_info = resData.data?.level_info;
|
|
281
|
+
const current_level = level_info?.current_level;
|
|
282
|
+
const current_exp = level_info?.current_exp;
|
|
283
|
+
const next_exp = level_info?.next_exp;
|
|
284
|
+
const ttl = SESSDATA_expires;
|
|
285
|
+
const LoginCookieTTLStr = ttl === -1 ? '永久' : ttl === -2 ? '-' : `${new Date(Date.now() + ttl).toLocaleString()}`;
|
|
286
|
+
e.reply(`~B站账号已登陆~\n有效期至:${LoginCookieTTLStr}。\n昵称:${uname}\nuid:${mid}\n硬币:${money}\n经验等级:${current_level}\n当前经验值exp:${current_exp}\n下一等级所需exp:${next_exp}`);
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
else if (resData.code === -101) {
|
|
290
|
+
e.reply('B站登录CK已失效,请重新扫码登录');
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
e.reply('意外情况,未能成功获取登录ck的有效状态');
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* 请求参数POST接口(ExClimbWuzhi)过校验
|
|
299
|
+
*/
|
|
300
|
+
async postGateway(cookie) {
|
|
301
|
+
const _uuid = await this.readSavedCookieItems(cookie, ['_uuid'], false);
|
|
302
|
+
const payloadJsonData = await BiliApi.BILIBILI_BROWSER_DATA('_uuid=' + _uuid);
|
|
303
|
+
const data = { payload: JSON.stringify(payloadJsonData) };
|
|
304
|
+
const requestUrl = 'https://api.bilibili.com/x/internal/gaia-gateway/ExClimbWuzhi';
|
|
305
|
+
const config = {
|
|
306
|
+
headers: lodash.merge({}, BiliApi.BILIBILI_HEADERS, {
|
|
307
|
+
'Content-type': 'application/json;charset=UTF-8'
|
|
308
|
+
}, {
|
|
309
|
+
Host: 'api.bilibili.com',
|
|
310
|
+
Origin: 'https://www.bilibili.com',
|
|
311
|
+
Referer: 'https://www.bilibili.com/'
|
|
312
|
+
})
|
|
313
|
+
};
|
|
314
|
+
try {
|
|
315
|
+
const res = await axios.post(requestUrl, data, config);
|
|
316
|
+
return res;
|
|
317
|
+
}
|
|
318
|
+
catch (error) {
|
|
319
|
+
logger.error('Error making POST request:', error);
|
|
320
|
+
throw error;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
/**退出B站账号登录,将会删除redis缓存的LoginCK,并在服务器注销该登录 Token (SESSDATA)*/
|
|
324
|
+
async exitBiliLogin(e) {
|
|
325
|
+
const url = 'https://passport.bilibili.com/login/exit/v2';
|
|
326
|
+
const jar = await this.getSessionCookieJar();
|
|
327
|
+
const [SESSDATA, biliCSRF, DedeUserID] = await Promise.all([
|
|
328
|
+
await this.getCookieValueByKeyFromString(jar, 'SESSDATA', 'https://api.bilibili.com'),
|
|
329
|
+
await this.getCookieValueByKeyFromString(jar, 'bili_jct', 'https://api.bilibili.com'),
|
|
330
|
+
await this.getCookieValueByKeyFromString(jar, 'DedeUserID', 'https://api.bilibili.com')
|
|
331
|
+
]);
|
|
332
|
+
if (lodash.trim(SESSDATA).length === 0 || lodash.trim(biliCSRF).length === 0 || lodash.trim(DedeUserID).length === 0) {
|
|
333
|
+
e.reply('当前无可用的B站登录CK可退出登录');
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
const postData = String(biliCSRF)
|
|
337
|
+
.trim()
|
|
338
|
+
.replace(/^bili_jct=/g, '')
|
|
339
|
+
.replace(/;*$/g, '');
|
|
340
|
+
try {
|
|
341
|
+
const resp = await axios.post(url, { biliCSRF: postData }, {
|
|
342
|
+
headers: {
|
|
343
|
+
'Host': 'passport.bilibili.com',
|
|
344
|
+
'User-Agent': BiliApi.BILIBILI_HEADERS['User-Agent'],
|
|
345
|
+
'Cookie': `DedeUserID]=${DedeUserID};bili_jct=${biliCSRF};SESSDATA=${SESSDATA}`,
|
|
346
|
+
'Content-Type': 'application/x-www-form-urlencoded'
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
const contentType = resp.headers['Content-Type'];
|
|
350
|
+
if (typeof contentType === 'string' && contentType.includes('text/html')) {
|
|
351
|
+
e.reply('当前缓存的B站登录CK早已失效!');
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
const res = resp.data;
|
|
355
|
+
logger?.debug(`exitBiliLogin: ${JSON.stringify(res)}`);
|
|
356
|
+
const { code } = res;
|
|
357
|
+
switch (code) {
|
|
358
|
+
case 0:
|
|
359
|
+
e.reply('当前缓存的B站登录CK已在B站服务器退出登录~');
|
|
360
|
+
break;
|
|
361
|
+
case 2202:
|
|
362
|
+
e.reply('csrf 请求非法,退出登录请求出错');
|
|
363
|
+
break;
|
|
364
|
+
case -101:
|
|
365
|
+
e.reply('当前缓存的扫码B站ck未登录!');
|
|
366
|
+
break;
|
|
367
|
+
default:
|
|
368
|
+
e.reply('未知情况!无妨');
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
catch (error) {
|
|
373
|
+
e.reply('退出登录请求出错');
|
|
374
|
+
console.error('Error during Bili login exit:', error);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* 获取有效bili_ticket并添加到cookie(bili_ticket 仍缓存于 Redis 单独 key)
|
|
379
|
+
*/
|
|
380
|
+
async checkCookieBiliTicket() {
|
|
381
|
+
const bili_jct_expires = await this.getCookieExpiration(this.cookieJar, 'bili_jct', 'https://api.bilibili.com');
|
|
382
|
+
if (bili_jct_expires === null) {
|
|
383
|
+
try {
|
|
384
|
+
const { ticket, ttl } = await getBiliTicket('');
|
|
385
|
+
if (ticket && ttl) {
|
|
386
|
+
await this.setCookieString([
|
|
387
|
+
new tough.Cookie({
|
|
388
|
+
key: 'bili_jct',
|
|
389
|
+
value: ticket,
|
|
390
|
+
domain: '.bilibili.com',
|
|
391
|
+
path: '/',
|
|
392
|
+
expires: new Date(Date.now() + ttl) // 3天后过期
|
|
393
|
+
})
|
|
394
|
+
]);
|
|
395
|
+
return `bili_ticket=${ticket};`;
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
throw new Error('获取bili_ticket失败');
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
catch (error) {
|
|
402
|
+
logger?.error(`${error}`);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
else {
|
|
406
|
+
return `bili_jct=${await this.getCookieValueByKeyFromString(this.cookieJar, 'bili_jct', 'https://api.bilibili.com')};`;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* 将 非标准化 bilibili 的 cookie 写入 jar 并持久化到 Redis
|
|
411
|
+
* 支持两种输入形式:
|
|
412
|
+
* 1. Cookie 数组:[{ key: 'name', value: 'value', 'domain': 'domain', 'expires': new Date() ,'path': 'path', 'httpOnly': true, 'secure': true }, ...]
|
|
413
|
+
* 2. 字符串形式:'a=1; b=2;'
|
|
414
|
+
*/
|
|
415
|
+
async setCookieString(input, url = 'https://api.bilibili.com') {
|
|
416
|
+
const jar = await this.getSessionCookieJar();
|
|
417
|
+
// 默认过期时间为一年
|
|
418
|
+
const DEFAULT_EXPIRATION = new Date(Date.now() + 365 * 24 * 60 * 60 * 1000);
|
|
419
|
+
try {
|
|
420
|
+
if (Array.isArray(input)) {
|
|
421
|
+
// 处理数组形式的 Cookie 输入
|
|
422
|
+
input.forEach(cookie => {
|
|
423
|
+
if (!(cookie instanceof tough.Cookie)) {
|
|
424
|
+
throw new Error('数组中的元素必须是 tough.Cookie 类型');
|
|
425
|
+
}
|
|
426
|
+
// 如果未设置 expires 或 maxAge,默认设置为一年
|
|
427
|
+
if (!cookie.expires && typeof cookie.maxAge !== 'number') {
|
|
428
|
+
cookie.setExpires(DEFAULT_EXPIRATION);
|
|
429
|
+
}
|
|
430
|
+
jar.setCookieSync(cookie, url);
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
else if (typeof input === 'string') {
|
|
434
|
+
// 处理字符串形式的 Cookie 输入
|
|
435
|
+
if (!input || input.trim().length === 0)
|
|
436
|
+
return;
|
|
437
|
+
const parts = input
|
|
438
|
+
.split(';')
|
|
439
|
+
.map(s => s.trim())
|
|
440
|
+
.filter(Boolean);
|
|
441
|
+
for (const part of parts) {
|
|
442
|
+
const [k, ...rest] = part.split('=');
|
|
443
|
+
const v = rest.join('=');
|
|
444
|
+
if (!k)
|
|
445
|
+
continue;
|
|
446
|
+
// 构造 Cookie 字符串,并默认设置 Domain 和 Path
|
|
447
|
+
const cookieStr = `${k}=${v}; Domain=.bilibili.com; Path=/; Expires=${DEFAULT_EXPIRATION.toUTCString()}`;
|
|
448
|
+
try {
|
|
449
|
+
await jar.setCookie(cookieStr, url);
|
|
450
|
+
}
|
|
451
|
+
catch (e) {
|
|
452
|
+
logger?.warn('setCookieString setCookie failed', cookieStr, e);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
else {
|
|
457
|
+
throw new Error('输入类型无效,仅支持字符串或 tough.Cookie 数组');
|
|
458
|
+
}
|
|
459
|
+
// 持久化到 Redis
|
|
460
|
+
await this.saveCookiesToRedis(jar);
|
|
461
|
+
}
|
|
462
|
+
catch (err) {
|
|
463
|
+
logger?.error('setCookieString error', err);
|
|
464
|
+
throw err;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* 简单策略:优先从 Redis 读取长期 cookie(如 SUP/SUBP),若缺失则触发完整获取流程
|
|
469
|
+
* */
|
|
470
|
+
async ensureLoginCookies(jar) {
|
|
471
|
+
// 常见域名为 .weibo.cn 或 m.weibo.cn,根据实际情况检查
|
|
472
|
+
const SESSDATA_Key1 = `${this.prefix}:.bilibili.com:SESSDATA`;
|
|
473
|
+
const SESSDATA_Key2 = `${this.prefix}:bilibili.com:SESSDATA`;
|
|
474
|
+
const isLogin = (await Redis.get(SESSDATA_Key1)) || (await Redis.get(SESSDATA_Key2));
|
|
475
|
+
if (isLogin) {
|
|
476
|
+
// 直接把 Redis 的所有 cookie 恢复到 jar(包含 SUP/SUBP)
|
|
477
|
+
await this.loadCookiesFromRedis(jar);
|
|
478
|
+
return true;
|
|
479
|
+
}
|
|
480
|
+
else {
|
|
481
|
+
// 缺少长期 cookie:返回 false,调用方需执行完整流程生成并持久化
|
|
482
|
+
await this.getNewTempCk();
|
|
483
|
+
return false;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
/** 从 Redis 恢复到 cookieJar */
|
|
487
|
+
async loadCookiesFromRedis(jar) {
|
|
488
|
+
try {
|
|
489
|
+
const pattern = `${this.prefix}:*:*`;
|
|
490
|
+
const keys = (await Redis.keys(pattern)) || [];
|
|
491
|
+
if (!keys.length)
|
|
492
|
+
return;
|
|
493
|
+
for (const key of keys) {
|
|
494
|
+
if (key.endsWith(':meta'))
|
|
495
|
+
continue; // 跳过 meta 键
|
|
496
|
+
const raw = await Redis.get(key);
|
|
497
|
+
if (raw == null)
|
|
498
|
+
continue;
|
|
499
|
+
// 解析 key 获取 name 和 domain
|
|
500
|
+
const parts = key.split(':');
|
|
501
|
+
const name = parts.pop();
|
|
502
|
+
const domain = parts.pop();
|
|
503
|
+
// 获取元数据
|
|
504
|
+
const metaRaw = await Redis.get(`${key}:meta`);
|
|
505
|
+
let meta = {};
|
|
506
|
+
try {
|
|
507
|
+
meta = metaRaw ? JSON.parse(metaRaw) : {};
|
|
508
|
+
}
|
|
509
|
+
catch (e) {
|
|
510
|
+
meta = {};
|
|
511
|
+
}
|
|
512
|
+
// 构造 Cookie 字符串
|
|
513
|
+
const ttl = await Redis.ttl(key);
|
|
514
|
+
const path = meta.path || '/'; // 默认路径为 '/'
|
|
515
|
+
let cookieStr = `${name}=${raw}; Domain=${domain}; Path=${path};`;
|
|
516
|
+
if (ttl && ttl > 0) {
|
|
517
|
+
const exp = new Date(Date.now() + ttl * 1000).toUTCString();
|
|
518
|
+
cookieStr += ` Expires=${exp};`;
|
|
519
|
+
}
|
|
520
|
+
if (meta.httpOnly)
|
|
521
|
+
cookieStr += ' HttpOnly;';
|
|
522
|
+
if (meta.secure)
|
|
523
|
+
cookieStr += ' Secure;';
|
|
524
|
+
try {
|
|
525
|
+
// 动态生成 URL
|
|
526
|
+
if (domain) {
|
|
527
|
+
const host = domain.startsWith('.') ? domain.slice(1) : domain; // 去掉前导的 '.'
|
|
528
|
+
const protocol = meta.secure ? 'https' : 'https';
|
|
529
|
+
const url = `${protocol}://${host}${path}`;
|
|
530
|
+
// 设置 Cookie 到 jar
|
|
531
|
+
await jar.setCookie(cookieStr, url);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
catch (e) {
|
|
535
|
+
logger?.warn('Failed to restore cookie to jar', key, e);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
catch (err) {
|
|
540
|
+
logger?.error('loadCookiesFromRedis failed', err);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
/** 从 CookieJar 中读取所有 cookie 并保存到 Redis(按 name/domain 存) */
|
|
544
|
+
/**
|
|
545
|
+
* 将 CookieJar 中的所有 Cookie 同步到 Redis
|
|
546
|
+
*/
|
|
547
|
+
async saveCookiesToRedis(jar) {
|
|
548
|
+
// 获取所有 Cookie
|
|
549
|
+
this.cookieJar = jar;
|
|
550
|
+
const allCookies = await new Promise((resolve, reject) => {
|
|
551
|
+
jar.store.getAllCookies((err, cookies) => {
|
|
552
|
+
if (err) {
|
|
553
|
+
reject(err);
|
|
554
|
+
}
|
|
555
|
+
else {
|
|
556
|
+
resolve(cookies || []);
|
|
557
|
+
}
|
|
558
|
+
});
|
|
559
|
+
});
|
|
560
|
+
for (const cookie of allCookies) {
|
|
561
|
+
if (!cookie)
|
|
562
|
+
continue; // 跳过无效的 Cookie
|
|
563
|
+
if (typeof cookie.value === 'string' && cookie.value.toLowerCase() === 'deleted')
|
|
564
|
+
continue; // 跳过被删除的 Cookie
|
|
565
|
+
// 构造 Redis 键
|
|
566
|
+
const redisKey = `${this.prefix}:${cookie.domain}:${cookie.key}`;
|
|
567
|
+
let ttl = 0;
|
|
568
|
+
// 使用 cookie.TTL() 方法计算 TTL
|
|
569
|
+
const ttlInMs = cookie.TTL(); // TTL 返回的是毫秒值
|
|
570
|
+
if (ttlInMs > 0) {
|
|
571
|
+
ttl = Math.floor(ttlInMs / 1000); // 转换为秒
|
|
572
|
+
}
|
|
573
|
+
else if (ttlInMs === 0) {
|
|
574
|
+
// 如果 TTL 为 0,表示 Cookie 已过期,跳过存储
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
// 构造元数据
|
|
578
|
+
const meta = JSON.stringify({
|
|
579
|
+
path: cookie.path,
|
|
580
|
+
httpOnly: !!cookie.httpOnly,
|
|
581
|
+
secure: !!cookie.secure
|
|
582
|
+
});
|
|
583
|
+
// 写入 Redis
|
|
584
|
+
if (ttl && ttl > 0) {
|
|
585
|
+
await Redis.set(redisKey, cookie.value, { EX: ttl }); // 设置键值对,并指定 TTL
|
|
586
|
+
await Redis.set(`${redisKey}:meta`, meta, { EX: ttl }); // 存储元数据,TTL 与主键一致
|
|
587
|
+
}
|
|
588
|
+
else {
|
|
589
|
+
// 如果没有 TTL(例如会话 Cookie),直接存储
|
|
590
|
+
await Redis.set(redisKey, cookie.value);
|
|
591
|
+
await Redis.set(`${redisKey}:meta`, meta);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
/** 清空 jar 与 Redis 中该前缀的 cookie */
|
|
596
|
+
async resetCookiesAndRedis() {
|
|
597
|
+
try {
|
|
598
|
+
const jar = await this.getSessionCookieJar();
|
|
599
|
+
// 1. 清空 CookieJar 中的所有 Cookie
|
|
600
|
+
const allCookies = await new Promise((resolve, reject) => {
|
|
601
|
+
jar.store.getAllCookies((err, cookies) => {
|
|
602
|
+
if (err)
|
|
603
|
+
reject(err);
|
|
604
|
+
else
|
|
605
|
+
resolve(cookies || []);
|
|
606
|
+
});
|
|
607
|
+
});
|
|
608
|
+
for (const cookie of allCookies) {
|
|
609
|
+
try {
|
|
610
|
+
await jar.setCookie(`${cookie.key}=; Expires=Thu, 01 Jan 1970 00:00:00 GMT`, `https://${cookie.domain}${cookie.path}`);
|
|
611
|
+
}
|
|
612
|
+
catch (e) { }
|
|
613
|
+
}
|
|
614
|
+
// 2. 清除 Redis 中的 Cookie 缓存
|
|
615
|
+
const pattern = `${this.prefix}:*:*`;
|
|
616
|
+
const keys = (await Redis.keys(pattern)) || [];
|
|
617
|
+
if (keys.length) {
|
|
618
|
+
const delKeys = [];
|
|
619
|
+
for (const k of keys) {
|
|
620
|
+
delKeys.push(k);
|
|
621
|
+
delKeys.push(`${k}:meta`);
|
|
622
|
+
}
|
|
623
|
+
if (delKeys.length)
|
|
624
|
+
await Promise.all(delKeys.map(key => Redis.del(key)));
|
|
625
|
+
}
|
|
626
|
+
this.cookieJar = new tough.CookieJar();
|
|
627
|
+
}
|
|
628
|
+
catch (err) {
|
|
629
|
+
logger?.error('resetCookiesAndRedis failed', err);
|
|
630
|
+
throw err;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
/** 从 jar 中获取指定 URL 的 cookie 字符串 */
|
|
634
|
+
async getCookieStringForUrl(url) {
|
|
635
|
+
try {
|
|
636
|
+
const jar = await this.getSessionCookieJar();
|
|
637
|
+
const cookieString = await new Promise((resolve, reject) => {
|
|
638
|
+
jar.getCookieString(url, (err, cs) => {
|
|
639
|
+
if (err)
|
|
640
|
+
reject(err);
|
|
641
|
+
else
|
|
642
|
+
resolve(cs || '');
|
|
643
|
+
});
|
|
644
|
+
});
|
|
645
|
+
return cookieString;
|
|
646
|
+
}
|
|
647
|
+
catch (err) {
|
|
648
|
+
logger?.error('getCookieStringForUrl error', err);
|
|
649
|
+
return '';
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
/** 从 jar 中获取指定 key 的值(基于 url) */
|
|
653
|
+
async getCookieValueByKeyFromString(jar, key, url) {
|
|
654
|
+
try {
|
|
655
|
+
const cookieString = await new Promise((resolve, reject) => {
|
|
656
|
+
jar.getCookieString(url, (err, cookieString) => {
|
|
657
|
+
if (err)
|
|
658
|
+
reject(err);
|
|
659
|
+
else
|
|
660
|
+
resolve(cookieString || '');
|
|
661
|
+
});
|
|
662
|
+
});
|
|
663
|
+
const match = cookieString.match(new RegExp(`${key}=([^;]+)`));
|
|
664
|
+
return match ? match[1] : undefined;
|
|
665
|
+
}
|
|
666
|
+
catch (err) {
|
|
667
|
+
logger?.error('getCookieValueByKeyFromString error', err);
|
|
668
|
+
return undefined;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* 获取指定 key 的 Cookie 的过期时间(时间戳,毫秒级)
|
|
673
|
+
* @param jar CookieJar 实例
|
|
674
|
+
* @param key 要查询的 Cookie 键名
|
|
675
|
+
* @param url 与 Cookie 关联的 URL
|
|
676
|
+
* @returns 过期时间的时间戳(毫秒级)或 null(如果未设置)
|
|
677
|
+
*/
|
|
678
|
+
async getCookieExpiration(jar, key, url) {
|
|
679
|
+
try {
|
|
680
|
+
// 获取所有与 URL 相关的 Cookie
|
|
681
|
+
const cookies = await new Promise((resolve, reject) => {
|
|
682
|
+
jar.getCookies(url, (err, cookies) => {
|
|
683
|
+
if (err)
|
|
684
|
+
reject(err);
|
|
685
|
+
else
|
|
686
|
+
resolve(cookies || []);
|
|
687
|
+
});
|
|
688
|
+
});
|
|
689
|
+
// 查找目标 key 的 Cookie
|
|
690
|
+
const targetCookie = cookies.find(cookie => cookie.key === key);
|
|
691
|
+
if (!targetCookie)
|
|
692
|
+
return null;
|
|
693
|
+
// 如果存在 expires,判断其类型
|
|
694
|
+
if (targetCookie.expires instanceof Date) {
|
|
695
|
+
return targetCookie.expires.getTime(); // 返回时间戳
|
|
696
|
+
}
|
|
697
|
+
else if (targetCookie.expires === 'Infinity') {
|
|
698
|
+
return -1; // 返回 -1 Infinity 表示永不过期
|
|
699
|
+
}
|
|
700
|
+
// 如果存在 maxAge,计算过期时间戳
|
|
701
|
+
if (typeof targetCookie.maxAge === 'number' && !isNaN(targetCookie.maxAge)) {
|
|
702
|
+
return Date.now() + targetCookie.maxAge * 1000;
|
|
703
|
+
}
|
|
704
|
+
// 如果既没有 expires 也没有 maxAge,返回 null(会话级别 Cookie)
|
|
705
|
+
return null;
|
|
706
|
+
}
|
|
707
|
+
catch (err) {
|
|
708
|
+
console.error('获取 Cookie 过期时间失败', err);
|
|
709
|
+
throw err;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
/** 综合获取ck,返回优先级:localCK > loginCK > tempCK */
|
|
713
|
+
async readSyncCookie() {
|
|
714
|
+
const localCk = await this.readLocalBiliCk();
|
|
715
|
+
const SESSDATA_expires = await this.getCookieExpiration(this.cookieJar, 'SESSDATA', 'https://api.bilibili.com');
|
|
716
|
+
const validCk = (ck) => ck?.trim().length > 10;
|
|
717
|
+
if (validCk(localCk)) {
|
|
718
|
+
return { cookie: localCk, mark: 'localCk' };
|
|
719
|
+
}
|
|
720
|
+
else if (!validCk(localCk) && SESSDATA_expires !== null) {
|
|
721
|
+
return { cookie: await this.getSessionCookieJar(), mark: 'loginCk' };
|
|
722
|
+
}
|
|
723
|
+
else if (!validCk(localCk) && SESSDATA_expires === null) {
|
|
724
|
+
return { cookie: await this.getSessionCookieJar(), mark: 'tempCk' };
|
|
725
|
+
}
|
|
726
|
+
return { cookie: '', mark: 'ckIsEmpty' };
|
|
727
|
+
}
|
|
728
|
+
/** 读取手动绑定的B站ck */
|
|
729
|
+
async readLocalBiliCk() {
|
|
730
|
+
const dir = path.join(_paths.root, 'data/yuki-plugin/');
|
|
731
|
+
if (!fs__default.existsSync(dir)) {
|
|
732
|
+
fs__default.mkdirSync(dir, { recursive: true }); // 创建目录,包括父目录
|
|
733
|
+
}
|
|
734
|
+
const files = fs__default.readdirSync(dir).filter((file) => file.endsWith('biliCookie.yaml'));
|
|
735
|
+
const readFile = promisify(fs__default.readFile);
|
|
736
|
+
const promises = files.map((file) => readFile(path.join(dir, file), 'utf8'));
|
|
737
|
+
const contents = await Promise.all(promises);
|
|
738
|
+
const Bck = contents.map((content) => YAML.parse(content));
|
|
739
|
+
return Bck[0] || '';
|
|
740
|
+
}
|
|
741
|
+
/** 覆盖保存手动获取绑定的B站ck */
|
|
742
|
+
async saveLocalBiliCk(data) {
|
|
743
|
+
const dirPath = path.join(_paths.root, 'data/yuki-plugin/');
|
|
744
|
+
const filePath = path.join(dirPath, 'biliCookie.yaml');
|
|
745
|
+
const cleanedData = String(data).replace(/\s/g, '').trim();
|
|
746
|
+
if (lodash.isEmpty(cleanedData)) {
|
|
747
|
+
fs__default.existsSync(filePath) && fs__default.unlinkSync(filePath);
|
|
748
|
+
}
|
|
749
|
+
else {
|
|
750
|
+
if (!fs__default.existsSync(dirPath)) {
|
|
751
|
+
fs__default.mkdirSync(dirPath, { recursive: true });
|
|
752
|
+
}
|
|
753
|
+
const yamlContent = YAML.stringify(cleanedData);
|
|
754
|
+
fs__default.writeFileSync(filePath, yamlContent, 'utf8');
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
/**
|
|
758
|
+
* 综合读取、筛选 传入的或本地或redis存储的cookie的item
|
|
759
|
+
* @param {string} mark 读取存储的CK类型,'localCK' 'loginCK' 或传入值 'xxx'并进行筛选
|
|
760
|
+
* @param {Array} items 选取获取CK的项 选全部值:items[0] = 'all' ,或选取其中的值 ['XSRF-TOKEN', 'SUB', 'SUBP', 'SRF', 'SCF', 'SRT', ' _T_WM', 'M_WEIBOCN_PARAMS', 'SSOLoginState','ALF']
|
|
761
|
+
* @param {boolean} isInverted 控制正取和反取,true为反取,默认为false正取
|
|
762
|
+
* @returns {string}
|
|
763
|
+
**/
|
|
764
|
+
async readSavedCookieItems(mark, items, isInverted = false) {
|
|
765
|
+
let ckString;
|
|
766
|
+
switch (mark) {
|
|
767
|
+
case 'localCK':
|
|
768
|
+
ckString = await this.readLocalBiliCk();
|
|
769
|
+
break;
|
|
770
|
+
case 'loginCK':
|
|
771
|
+
ckString = await this.getCookieStringForUrl('https://api.bilibili.com');
|
|
772
|
+
break;
|
|
773
|
+
default:
|
|
774
|
+
ckString = mark;
|
|
775
|
+
}
|
|
776
|
+
const Wck = lodash.trim(ckString);
|
|
777
|
+
if (!Wck) {
|
|
778
|
+
return '';
|
|
779
|
+
}
|
|
780
|
+
if (items[0] === 'all') {
|
|
781
|
+
return Wck;
|
|
782
|
+
}
|
|
783
|
+
const cookiePairs = String(Wck)
|
|
784
|
+
.trim()
|
|
785
|
+
.match(/([a-zA-Z0-9_-]+)=([^;|,]+)/g) //正则 /(\w+)=([^;]+);/g 匹配 a=b 的内容,并分组为 [^;|,]+ 来匹配值,其中 [^;|,] 表示除了分号和,以外的任意字符
|
|
786
|
+
?.map(match => match.split('='))
|
|
787
|
+
.filter(([key, value]) => (isInverted ? !items.includes(key) : items.includes(key)) && value !== '')
|
|
788
|
+
.map(([key, value]) => `${key}=${value}`)
|
|
789
|
+
.join(';') || '';
|
|
790
|
+
return cookiePairs;
|
|
791
|
+
}
|
|
792
|
+
// 取反读取ck、筛选 传入的或本地或redis存储的cookie的item
|
|
793
|
+
async readSavedCookieOtherItems(mark, items) {
|
|
794
|
+
return await this.readSavedCookieItems(mark, items, true);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
const BiliCookieManager = new BiliRiskCookie();
|
|
798
|
+
|
|
799
|
+
export { BiliCookieManager as default };
|