yz-yuki-plugin 2.0.8-3 → 2.0.8-5
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 +1 -0
- package/README.md +7 -1
- package/defaultConfig/help/help.yaml +21 -0
- package/lib/apps/bilibili.js +2 -2
- package/lib/apps/weibo.js +143 -24
- package/lib/components/loginQrcode/Page.js +3 -1
- package/lib/models/bilibili/bilibili.main.models.js +7 -7
- package/lib/models/weibo/weibo.main.api.js +115 -8
- package/lib/models/weibo/weibo.main.get.web.data.js +61 -4
- package/lib/models/weibo/weibo.main.models.js +347 -0
- package/lib/models/weibo/weibo.risk.bd.rid.js +309 -0
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -186,7 +186,7 @@ https://m.weibo.cn/u/7643376782 # 7643376782 为崩坏星穹铁道博主uid
|
|
|
186
186
|
| 扫码B站登录 | app扫码获取登录ck | `#扫码B站登录` |
|
|
187
187
|
| 取消B站登录 | 删除扫码获取的B站CK | `#取消B站登陆` |
|
|
188
188
|
| 查看B站登录信息 | 查看app扫码登录的信息和状态 | `#我的B站登录` |
|
|
189
|
-
| 绑定B站ck | 配置手动本地获取的B站CK,仅限私聊/私信,权限:Master | `#绑定B
|
|
189
|
+
| 绑定B站ck | 配置手动本地获取的B站CK,仅限私聊/私信,权限:Master | `#绑定B站本地ck: xxx` |
|
|
190
190
|
| 删除B站ck | 删除手动获取的B站cookie,权限:Master | `#删除B站本地ck` |
|
|
191
191
|
| 查看B站ck | 查看当前启用的B站ck,仅限私聊 | `#我的B站ck` |
|
|
192
192
|
| 刷新B站临时ck | 重新获取并刷新redis缓存的未绑定自己的B站ck而自动获取的 临时B站cookie | `#刷新B站临时ck` |
|
|
@@ -200,6 +200,12 @@ https://m.weibo.cn/u/7643376782 # 7643376782 为崩坏星穹铁道博主uid
|
|
|
200
200
|
| 手动推送微博订阅 | 手动触发定时推送任务,权限:Bot的Master | `#执行微博任务` |
|
|
201
201
|
| 查看博主信息 | 通过uid查看博主信息 | `#微博博主 uid` |
|
|
202
202
|
| 搜索微博博主 | 根据关键词在微博搜索大V博主的信息 | `#搜索微博博主 xxx` |
|
|
203
|
+
| 扫码微博登录(待完成) | app扫码获取登录ck | `#扫码微博登录` |
|
|
204
|
+
| 取消微博登录 | 删除扫码获取的微博CK | `#取消微博登陆` |
|
|
205
|
+
| 查看微博登录信息 | 查看app扫码登录的信息和状态 | `#我的微博登录` |
|
|
206
|
+
| 绑定微博ck | 配置手动本地获取的微博CK,仅限私聊/私信,权限:Master | `#绑定微博本地ck: xxx` |
|
|
207
|
+
| 删除微博ck | 删除手动获取的微博cookie,权限:Master | `#删除微博本地ck` |
|
|
208
|
+
| 查看微博ck | 查看当前启用的微博ck,仅限私聊 | `#我的微博ck` |
|
|
203
209
|
||||
|
|
204
210
|
| **其他指令** | | |
|
|
205
211
|
| 查看版本信息 | 查看版本信息 | `#优纪版本` |
|
|
@@ -50,6 +50,9 @@
|
|
|
50
50
|
- icon: wondrous_lovely_flower
|
|
51
51
|
title: '#刷新B站临时ck'
|
|
52
52
|
desc: '刷新自动获取的临时B站cookie'
|
|
53
|
+
- icon: romaritime_flower
|
|
54
|
+
title: 'B站视频链接解析'
|
|
55
|
+
desc: '直接发送B站视频链接即可'
|
|
53
56
|
- group: 微博功能
|
|
54
57
|
list:
|
|
55
58
|
- icon: diagram
|
|
@@ -73,6 +76,24 @@
|
|
|
73
76
|
- icon: shell
|
|
74
77
|
title: '#搜索微博博主原神'
|
|
75
78
|
desc: '根据关键词在微博搜索大V博主的信息'
|
|
79
|
+
- icon: 雷神瞳共鸣石
|
|
80
|
+
title: '#扫码微博登录'
|
|
81
|
+
desc: 'app扫码获取登录ck(待完成)'
|
|
82
|
+
- icon: everamber
|
|
83
|
+
title: '#取消微博登陆'
|
|
84
|
+
desc: '删除扫码获取的微博CK'
|
|
85
|
+
- icon: condessence_crystal
|
|
86
|
+
title: '#我的微博登录'
|
|
87
|
+
desc: '查看app扫码登录的信息和状态'
|
|
88
|
+
- icon: romaritime_flower
|
|
89
|
+
title: '#绑定微博本地ck: ***'
|
|
90
|
+
desc: '配置本地获取的微博CK'
|
|
91
|
+
- icon: unfading_silky_grace
|
|
92
|
+
title: '#删除微博本地ck'
|
|
93
|
+
desc: '删除手动获取的微博ck'
|
|
94
|
+
- icon: delightful_encounter
|
|
95
|
+
title: '#我的微博ck'
|
|
96
|
+
desc: '查看当前启用的微博ck,仅限私聊'
|
|
76
97
|
- group: 其他指令
|
|
77
98
|
list:
|
|
78
99
|
- icon: 风车
|
package/lib/apps/bilibili.js
CHANGED
|
@@ -297,7 +297,7 @@ message.use(async (e) => {
|
|
|
297
297
|
e.reply('未取得bot主人身份,无权限删除B站登录ck');
|
|
298
298
|
}
|
|
299
299
|
}, [/^(#|\/)(yuki|优纪)?(取消|删除|del|DEL)(b站|B站|bili|bilibili|哔哩|哔哩哔哩)本地(ck|CK|cookie|COOKIE)$/]);
|
|
300
|
-
/**
|
|
300
|
+
/** 当前正在使用的本地B站ck */
|
|
301
301
|
message.use(async (e) => {
|
|
302
302
|
if (e.isGroup) {
|
|
303
303
|
await e.reply('注意账号安全,请私聊查看叭');
|
|
@@ -325,7 +325,7 @@ message.use(async (e) => {
|
|
|
325
325
|
e.reply('未取得bot主人身份,无权限查看当前使用的B站cookie');
|
|
326
326
|
}
|
|
327
327
|
}
|
|
328
|
-
}, [/^(#|\/)(yuki|优纪)
|
|
328
|
+
}, [/^(#|\/)(yuki|优纪)?我的(b站|B站|bili|bilibili|哔哩|哔哩哔哩)(ck|CK|cookie|COOKIE)$/]);
|
|
329
329
|
/** 删除并刷新redis缓存的临时B站ck */
|
|
330
330
|
message.use(async (e) => {
|
|
331
331
|
try {
|
package/lib/apps/weibo.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { WeiboWebDataFetcher } from '../models/weibo/weibo.main.get.web.data.js';
|
|
2
|
+
import { WeiboMainModels } from '../models/weibo/weibo.main.models.js';
|
|
2
3
|
import { WeiboQuery } from '../models/weibo/weibo.main.query.js';
|
|
3
4
|
import { WeiboTask } from '../models/weibo/weibo.main.task.js';
|
|
4
5
|
import Config from '../utils/config.js';
|
|
5
|
-
import
|
|
6
|
+
import lodash from 'lodash';
|
|
7
|
+
import { Messages, Redis } from 'yunzaijs';
|
|
6
8
|
|
|
7
9
|
const message = new Messages('message');
|
|
8
10
|
let weiboPushData = Config.getConfigData('config', 'weibo', 'push');
|
|
@@ -124,6 +126,119 @@ message.use(async (e) => {
|
|
|
124
126
|
e.reply(`${isDel ? '删除' : '修改'}微博推送成功~\n${uid}`);
|
|
125
127
|
}
|
|
126
128
|
}, [/^(#|\/)(yuki|优纪)?(取消|删除|del|DEL)(微博|weibo|WEIBO)推送\s*(视频\s*|图文\s*|文章\s*|转发\s*)*.*$/]);
|
|
129
|
+
/** 扫码登录微博 */
|
|
130
|
+
message.use(async (e) => {
|
|
131
|
+
if (!e.isMaster) {
|
|
132
|
+
e.reply('未取得bot主人身份,无权限配置微博登录ck');
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
const LoginCk = await WeiboMainModels.readLoginCookie();
|
|
136
|
+
if (LoginCk) {
|
|
137
|
+
e.reply(`当前已有微博登录ck,请勿重复扫码!\n如需更换,请先删除当前登录再扫码:\n#yuki删除微博登录`);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
try {
|
|
141
|
+
const tokenKey = await WeiboMainModels.applyLoginQRCode(e);
|
|
142
|
+
if (tokenKey && tokenKey.rid) {
|
|
143
|
+
let weiboLoginCk = await WeiboMainModels.pollLoginQRCode(e, tokenKey.qrid, tokenKey.rid, tokenKey.X_CSRF_TOKEN);
|
|
144
|
+
if (weiboLoginCk) {
|
|
145
|
+
if (lodash.trim(weiboLoginCk).length != 0) {
|
|
146
|
+
await WeiboMainModels.saveLoginCookie(e, weiboLoginCk);
|
|
147
|
+
e.reply(`get weibo LoginCk:成功!`);
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
e.reply(`get weibo LoginCk:失败X﹏X`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
catch (Error) {
|
|
156
|
+
global?.logger?.info(`yuki-plugin Login weibo Failed:${Error}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}, [/^(#|\/)(yuki|优纪)?(扫码|添加|ADD|add)(微博|weibo|WEIBO)登录$/]);
|
|
161
|
+
/** 删除登陆的微博ck */
|
|
162
|
+
message.use(async (e) => {
|
|
163
|
+
if (e.isMaster) {
|
|
164
|
+
await Redis.set('Yz:yuki:weibo:loginCookie', '', { EX: 3600 * 24 * 180 });
|
|
165
|
+
e.reply(`扫码登陆的微博cookie已删除~`);
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
e.reply('未取得bot主人身份,无权限删除微博登录ck');
|
|
169
|
+
}
|
|
170
|
+
}, [/^(#|\/)(yuki|优纪)?(取消|删除|del|DEL)(微博|weibo|WEIBO)登录$/]);
|
|
171
|
+
/** 显示我的微博登录信息 */
|
|
172
|
+
message.use(async (e) => {
|
|
173
|
+
if (e.isMaster) {
|
|
174
|
+
await WeiboMainModels.checkWeiboLogin(e);
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
e.reply('未取得bot主人身份,无权限查看微博登录状态');
|
|
178
|
+
}
|
|
179
|
+
}, [/^(#|\/)(yuki|优纪)?我的(微博|weibo|WEIBO)登录$/]);
|
|
180
|
+
/** 手动绑定本地获取的微博cookie */
|
|
181
|
+
message.use(async (e) => {
|
|
182
|
+
if (e.isMaster) {
|
|
183
|
+
if (e.isPrivate) {
|
|
184
|
+
await e.reply('请注意账号安全,请手动撤回发送的cookie,并私聊进行添加绑定!');
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
let localBiliCookie = e.msg.replace(/^(#|\/)(yuki|优纪)?(绑定|添加|ADD|add)(微博|weibo|WEIBO)(ck|CK|cookie|COOKIE)(:|:)?/g, '').trim();
|
|
188
|
+
const XSRF_TOKEN = await WeiboMainModels.readSavedCookieItems(localBiliCookie, ['XSRF-TOKEN'], false);
|
|
189
|
+
if (XSRF_TOKEN) {
|
|
190
|
+
//筛选ck
|
|
191
|
+
localBiliCookie = await WeiboMainModels.readSavedCookieItems(localBiliCookie, ['XSRF-TOKEN', 'SUB', 'SUBP', 'SRF', 'SCF', 'SRT', ' _T_WM', 'M_WEIBOCN_PARAMS', 'SSOLoginState', 'ALF'], false);
|
|
192
|
+
await WeiboMainModels.saveLocalBiliCk(localBiliCookie);
|
|
193
|
+
logger.mark(`${e.logFnc} 保存微博cookie成功 [XSRF_TOKEN: ${XSRF_TOKEN}]`);
|
|
194
|
+
let uidMsg = [`好耶~绑定微博cookie成功:\nXSRF_TOKEN: ${XSRF_TOKEN}`];
|
|
195
|
+
await e.reply(uidMsg);
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
e.reply('绑定的微博cookie无效,请检查后重新添加!');
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
e.reply('未取得bot主人身份,无权限配置B站登录ck');
|
|
205
|
+
}
|
|
206
|
+
}, [/^^(#|\/)(yuki|优纪)?(绑定|添加|ADD|add)(微博|weibo|WEIBO)本地(ck|CK|cookie|COOKIE)(:|:)?.*$/]);
|
|
207
|
+
/** 删除绑定的本地微博ck */
|
|
208
|
+
message.use(async (e) => {
|
|
209
|
+
if (e.isMaster) {
|
|
210
|
+
await WeiboMainModels.saveLocalBiliCk('');
|
|
211
|
+
await e.reply(`手动绑定的微博ck已删除~`);
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
e.reply('未取得bot主人身份,无权限删除B站登录ck');
|
|
215
|
+
}
|
|
216
|
+
}, [/^(#|\/)(yuki|优纪)?(取消|删除|del|DEL)(微博|weibo|WEIBO)本地(ck|CK|cookie|COOKIE)$/]);
|
|
217
|
+
/** 查看当前正在使用的本地微博ck */
|
|
218
|
+
message.use(async (e) => {
|
|
219
|
+
if (e.isGroup) {
|
|
220
|
+
await e.reply('注意账号安全,请私聊查看叭');
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
if (e.isMaster) {
|
|
224
|
+
let { cookie, mark } = await WeiboMainModels.readSyncCookie();
|
|
225
|
+
if (mark === 'localCk') {
|
|
226
|
+
e.reply(`当前使用本地获取的微博cookie:`);
|
|
227
|
+
e.reply(`${cookie}`);
|
|
228
|
+
}
|
|
229
|
+
else if (mark === 'loginCk') {
|
|
230
|
+
e.reply(`当前使用扫码登录的微博cookie:`);
|
|
231
|
+
e.reply(`${cookie}`);
|
|
232
|
+
}
|
|
233
|
+
else if (mark == 'ckIsEmpty') {
|
|
234
|
+
e.reply(`当前无可使用的微博cookie。`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
e.reply('未取得bot主人身份,无权限查看当前使用的B站cookie');
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}, [/^(#|\/)(yuki|优纪)?我的(微博|weibo|WEIBO)(ck|CK|cookie|COOKIE)$/]);
|
|
127
242
|
/** 订阅的全部微博推送列表 */
|
|
128
243
|
message.use(async (e) => {
|
|
129
244
|
if (!e.isMaster) {
|
|
@@ -155,17 +270,19 @@ message.use(async (e) => {
|
|
|
155
270
|
messages.push('\n>>>>>>群组微博订阅<<<<<<');
|
|
156
271
|
Object.keys(subData.group).forEach(groupId => {
|
|
157
272
|
messages.push(`\n<群组${groupId}>:`);
|
|
158
|
-
subData.group
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
item.type.
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
273
|
+
if (subData.group) {
|
|
274
|
+
subData.group[groupId].forEach((item) => {
|
|
275
|
+
const types = new Set();
|
|
276
|
+
if (item.type && item.type.length) {
|
|
277
|
+
item.type.forEach((typeItem) => {
|
|
278
|
+
if (typeMap[typeItem]) {
|
|
279
|
+
types.add(typeMap[typeItem]);
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
messages.push(`${item.uid}:${item.name} ${types.size ? `[${Array.from(types).join('、')}]` : ' [全部动态]'}`);
|
|
284
|
+
});
|
|
285
|
+
}
|
|
169
286
|
});
|
|
170
287
|
}
|
|
171
288
|
else {
|
|
@@ -176,17 +293,19 @@ message.use(async (e) => {
|
|
|
176
293
|
messages.push('\n>>>>>>私聊微博订阅<<<<<<');
|
|
177
294
|
Object.keys(subData.private).forEach(userId => {
|
|
178
295
|
messages.push(`\n<用户${userId}>:`);
|
|
179
|
-
subData.private
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
item.type.
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
296
|
+
if (subData.private) {
|
|
297
|
+
subData.private[userId].forEach((item) => {
|
|
298
|
+
const types = new Set();
|
|
299
|
+
if (item.type && item.type.length) {
|
|
300
|
+
item.type.forEach((typeItem) => {
|
|
301
|
+
if (typeMap[typeItem]) {
|
|
302
|
+
types.add(typeMap[typeItem]);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
messages.push(`${item.uid}:${item.name} ${types.size ? `[${Array.from(types).join('、')}]` : ' [全部动态]'}`);
|
|
307
|
+
});
|
|
308
|
+
}
|
|
190
309
|
});
|
|
191
310
|
}
|
|
192
311
|
else {
|
|
@@ -11,7 +11,9 @@ function App({ data }) {
|
|
|
11
11
|
React.createElement("div", { className: "txt-0 text-center mt-3 mb-3 p-1 text-blue-500" },
|
|
12
12
|
"Created By yuki-plugin",
|
|
13
13
|
React.createElement("br", null),
|
|
14
|
-
"\u626B\u7801\u767B\
|
|
14
|
+
"\u626B\u7801\u767B\u5F55",
|
|
15
|
+
data.name,
|
|
16
|
+
"\u83B7\u53D6CK"),
|
|
15
17
|
React.createElement("div", { className: "QrCode m-auto" },
|
|
16
18
|
React.createElement("img", { className: "qr-code w-72 h-72 ml-7", src: data.url, alt: "\u4E8C\u7EF4\u7801" })),
|
|
17
19
|
React.createElement("div", { className: "txt-1 text-center mt-3 mb-3 p-1 text-red-600" },
|
|
@@ -1,3 +1,8 @@
|
|
|
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';
|
|
1
6
|
import axios from 'axios';
|
|
2
7
|
import fs__default from 'fs';
|
|
3
8
|
import lodash from 'lodash';
|
|
@@ -7,11 +12,6 @@ import path from 'path';
|
|
|
7
12
|
import QRCode from 'qrcode';
|
|
8
13
|
import YAML from 'yaml';
|
|
9
14
|
import { Redis, Segment } from 'yunzaijs';
|
|
10
|
-
import { renderPage } from '../../utils/image.js';
|
|
11
|
-
import { _paths } from '../../utils/paths.js';
|
|
12
|
-
import BiliApi from './bilibili.main.api.js';
|
|
13
|
-
import { gen_buvid_fp } from './bilibili.risk.buid.fp.js';
|
|
14
|
-
import { getBiliTicket } from './bilibili.risk.ticket.js';
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* *******************************************************************
|
|
@@ -35,7 +35,7 @@ async function applyLoginQRCode(e) {
|
|
|
35
35
|
const qrcodeUrl = res?.data?.url;
|
|
36
36
|
let loginUrlQrcodeData = await QRCode.toDataURL(`${qrcodeUrl}`);
|
|
37
37
|
const LoginPropsData = {
|
|
38
|
-
data: { url: loginUrlQrcodeData }
|
|
38
|
+
data: { name: 'B站', url: loginUrlQrcodeData }
|
|
39
39
|
};
|
|
40
40
|
const ScreenshotOptionsData = {
|
|
41
41
|
saveHtmlfile: false,
|
|
@@ -251,7 +251,7 @@ async function readLocalBiliCk() {
|
|
|
251
251
|
if (!fs__default.existsSync(dir)) {
|
|
252
252
|
fs__default.mkdirSync(dir, { recursive: true }); // 创建目录,包括父目录
|
|
253
253
|
}
|
|
254
|
-
const files = fs__default.readdirSync(dir).filter((file) => file.endsWith('.yaml'));
|
|
254
|
+
const files = fs__default.readdirSync(dir).filter((file) => file.endsWith('biliCookie.yaml'));
|
|
255
255
|
const readFile = promisify(fs__default.readFile);
|
|
256
256
|
const promises = files.map((file) => readFile(path.join(dir, file), 'utf8'));
|
|
257
257
|
const contents = await Promise.all(promises);
|
|
@@ -30,14 +30,121 @@ class WeiboApi {
|
|
|
30
30
|
/**统一设置header */
|
|
31
31
|
get WEIBO_HEADERS() {
|
|
32
32
|
return {
|
|
33
|
-
'Accept': '
|
|
34
|
-
'Accept-
|
|
35
|
-
'
|
|
36
|
-
'
|
|
37
|
-
'
|
|
38
|
-
'
|
|
39
|
-
'Sec-
|
|
40
|
-
'
|
|
33
|
+
'Accept': 'application/json, text/plain, */*',
|
|
34
|
+
'Accept-Language': 'zh-CN,en-US;q=0.5',
|
|
35
|
+
'Accept-Encoding': 'gzip, deflate, br, zstd',
|
|
36
|
+
'X-Requested-With': 'XMLHttpRequest',
|
|
37
|
+
'MWeibo-Pwa': '1',
|
|
38
|
+
'X-XSRF-TOKEN': '',
|
|
39
|
+
'Sec-Fetch-Dest': 'empty',
|
|
40
|
+
'Sec-Fetch-Mode': 'cors',
|
|
41
|
+
'Sec-Fetch-Site': 'same-origin',
|
|
42
|
+
'Connection': 'keep-alive',
|
|
43
|
+
'User-agent': this.USER_AGENT
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
get WEIBO_GET_X_CSRF_TOKEN_HEADERS() {
|
|
47
|
+
return {
|
|
48
|
+
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
|
49
|
+
'Accept-Language': 'zh-CN,en-US;q=0.5',
|
|
50
|
+
'Accept-Encoding': 'gzip, deflate, br, zstd',
|
|
51
|
+
'Sec-GPC': '1',
|
|
52
|
+
'Connection': 'keep-alive',
|
|
53
|
+
'Upgrade-Insecure-Requests': '1',
|
|
54
|
+
'Sec-Fetch-Dest': 'document',
|
|
55
|
+
'Sec-Fetch-Mode': 'navigate',
|
|
56
|
+
'Sec-Fetch-Site': 'none',
|
|
57
|
+
'Sec-Fetch-User': '?1',
|
|
58
|
+
'Priority': 'u=0, i',
|
|
59
|
+
'User-agent': this.USER_AGENT
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
get WEIBO_LOGIN_QR_CODE_HEADERS() {
|
|
63
|
+
return {
|
|
64
|
+
'Accept': `application/json, text/plain, */*`,
|
|
65
|
+
'Accept-Language': 'zh-CN,en-US;q=0.5',
|
|
66
|
+
'Accept-Encoding': 'gzip, deflate, br, zstd',
|
|
67
|
+
'X-Requested-With': 'XMLHttpRequest',
|
|
68
|
+
'X-CSRF-TOKEN': '',
|
|
69
|
+
'Sec-GPC': '1',
|
|
70
|
+
'Connection': 'keep-alive',
|
|
71
|
+
'Referer': 'https://passport.weibo.com/sso/signin?entry=wapsso&source=wapssowb&url=https%3A%2F%2Fm.weibo.cn%2F',
|
|
72
|
+
'Cookie': '',
|
|
73
|
+
'Sec-Fetch-Dest': 'empty',
|
|
74
|
+
'Sec-Fetch-Mode': 'cors',
|
|
75
|
+
'Sec-Fetch-Site': 'same-origin',
|
|
76
|
+
'Priority': 'u=0',
|
|
77
|
+
'User-agent': this.USER_AGENT
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
get WEIBO_LOGIN_QR_CODE_IMAGE_HEADERS() {
|
|
81
|
+
return {
|
|
82
|
+
'Accept': `image/avif,image/webp,image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5`,
|
|
83
|
+
'Accept-Language': 'zh-CN,en-US;q=0.5',
|
|
84
|
+
'Accept-Encoding': 'gzip, deflate, br, zstd',
|
|
85
|
+
'Sec-GPC': '1',
|
|
86
|
+
'Connection': 'keep-alive',
|
|
87
|
+
'Referer': 'https://passport.weibo.com/',
|
|
88
|
+
'Cookie': '',
|
|
89
|
+
'Sec-Fetch-Dest': 'Image',
|
|
90
|
+
'Sec-Fetch-Mode': 'no-cors',
|
|
91
|
+
'Sec-Fetch-Site': 'cross-site',
|
|
92
|
+
'Priority': 'u=4, i',
|
|
93
|
+
'User-agent': this.USER_AGENT
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
get WEIBO_GET_BD_TOKEN_HEADERS() {
|
|
97
|
+
return {
|
|
98
|
+
'Accept': '*/*',
|
|
99
|
+
'Accept-Language': 'zh-CN,en-US;q=0.5',
|
|
100
|
+
'Accept-Encoding': 'gzip, deflate, br, zstd',
|
|
101
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
102
|
+
'Host': 'passport.weibo.com',
|
|
103
|
+
'Origin': 'https://passport.weibo.com',
|
|
104
|
+
'Priority': 'u=4',
|
|
105
|
+
'Referer': 'https://passport.weibo.com/sso/signin?entry=wapsso&source=wapsso&url=https%3A%2F%2Fm.weibo.cn%2F',
|
|
106
|
+
'Connection': 'keep-alive',
|
|
107
|
+
'Cookie': '',
|
|
108
|
+
'Sec-Fetch-Dest': 'empty',
|
|
109
|
+
'Sec-Fetch-Mode': 'no-cors',
|
|
110
|
+
'Sec-Fetch-Site': 'same-origin',
|
|
111
|
+
'Sec-GPC': '1',
|
|
112
|
+
'TE': 'trailers',
|
|
113
|
+
'User-Agent': this.USER_AGENT
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
get WEIBO_POLL_LOGIN_STATUS_HEADERS() {
|
|
117
|
+
return {
|
|
118
|
+
'Accept': 'application/json, text/plain, */*',
|
|
119
|
+
'Accept-Language': 'zh-CN,en-US;q=0.5',
|
|
120
|
+
'Accept-Encoding': 'gzip, deflate, br, zstd',
|
|
121
|
+
'X-Requested-With': 'XMLHttpRequest',
|
|
122
|
+
'X-CSRF-TOKEN': '',
|
|
123
|
+
'Sec-GPC': '1',
|
|
124
|
+
'Connection': 'keep-alive',
|
|
125
|
+
'Referer': 'https://passport.weibo.com/sso/signin?entry=wapsso&source=wapssowb&url=https%3A%2F%2Fm.weibo.cn%2F',
|
|
126
|
+
'Cookie': '',
|
|
127
|
+
'Sec-Fetch-Dest': 'empty',
|
|
128
|
+
'Sec-Fetch-Mode': 'cors',
|
|
129
|
+
'Sec-Fetch-Site': 'same-origin',
|
|
130
|
+
'TE': 'trailers',
|
|
131
|
+
'User-agent': this.USER_AGENT
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
get WEIBO_COOKIE_HEADERS() {
|
|
135
|
+
return {
|
|
136
|
+
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
|
137
|
+
'Accept-Language': 'zh-CN,en-US;q=0.5',
|
|
138
|
+
'Accept-Encoding': 'gzip, deflate, br, zstd',
|
|
139
|
+
'Connection': 'keep-alive',
|
|
140
|
+
'Referer': 'https://passport.weibo.com/sso/signin?entry=wapsso&source=wapsso&url=https%3A%2F%2Fm.weibo.cn%2F%3Fjumpfrom%3Dweibocom',
|
|
141
|
+
'Cookie': '',
|
|
142
|
+
'Upgrade-Insecure-Requests': '1',
|
|
143
|
+
'Sec-Fetch-Dest': 'document',
|
|
144
|
+
'Sec-Fetch-Mode': 'navigate',
|
|
145
|
+
'Sec-Fetch-Site': 'same-origin',
|
|
146
|
+
'Priority': 'u=0, i',
|
|
147
|
+
'TE': 'trailers',
|
|
41
148
|
'User-agent': this.USER_AGENT
|
|
42
149
|
};
|
|
43
150
|
}
|
|
@@ -1,7 +1,19 @@
|
|
|
1
|
-
import axios from 'axios';
|
|
2
1
|
import WeiboApi from './weibo.main.api.js';
|
|
2
|
+
import { WeiboMainModels } from './weibo.main.models.js';
|
|
3
3
|
import { WeiboQuery } from './weibo.main.query.js';
|
|
4
|
+
import axios from 'axios';
|
|
5
|
+
import { randomInt } from 'crypto';
|
|
6
|
+
import lodash from 'lodash';
|
|
4
7
|
|
|
8
|
+
function generateRandomString(length = 6) {
|
|
9
|
+
const characters = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
|
10
|
+
let result = '';
|
|
11
|
+
for (let i = 0; i < length; i++) {
|
|
12
|
+
const randomIndex = randomInt(0, characters.length);
|
|
13
|
+
result += characters[randomIndex];
|
|
14
|
+
}
|
|
15
|
+
return result;
|
|
16
|
+
}
|
|
5
17
|
class WeiboWebDataFetcher {
|
|
6
18
|
e;
|
|
7
19
|
constructor(e) { }
|
|
@@ -10,25 +22,55 @@ class WeiboWebDataFetcher {
|
|
|
10
22
|
const param = { containerid: '100505' + target };
|
|
11
23
|
const url = new URL(WeiboApi.WEIBO_API.weiboGetIndex);
|
|
12
24
|
url.search = new URLSearchParams(param).toString();
|
|
25
|
+
const { cookie, mark } = await WeiboMainModels.readSyncCookie();
|
|
26
|
+
let COOKIE = '', XSRF_TOKEN = '';
|
|
27
|
+
if (String(cookie).includes('XSRF-TOKEN')) {
|
|
28
|
+
COOKIE = cookie;
|
|
29
|
+
XSRF_TOKEN = await WeiboMainModels.readSavedCookieItems(cookie, ['XSRF-TOKEN'], false);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
logger.warn(`优纪插件:获取博主信息,未登录,如获取报错或失败请先 #扫码微博登录`);
|
|
33
|
+
XSRF_TOKEN = generateRandomString();
|
|
34
|
+
COOKIE = `XSRF_TOKEN=${XSRF_TOKEN}`;
|
|
35
|
+
}
|
|
13
36
|
const resp = await axios(url.toString(), {
|
|
14
37
|
method: 'GET',
|
|
15
38
|
timeout: 10000,
|
|
16
|
-
headers: { '
|
|
39
|
+
headers: lodash.merge(WeiboApi.WEIBO_HEADERS, { 'X-XSRF-TOKEN': `${XSRF_TOKEN}`, 'Cookie': `${COOKIE}`, 'referer': 'https://m.weibo.cn' })
|
|
17
40
|
});
|
|
41
|
+
// 处理 Set-Cookie 响应头
|
|
42
|
+
if (resp.headers['set-cookie']) {
|
|
43
|
+
await WeiboMainModels.updateCookieWithSetCookie(resp.headers['set-cookie'], mark);
|
|
44
|
+
}
|
|
18
45
|
return resp;
|
|
19
46
|
}
|
|
20
47
|
/**通过关键词搜索微博大v */
|
|
21
48
|
async searchBloggerInfo(keyword) {
|
|
22
49
|
const url = WeiboApi.WEIBO_API.weiboAjaxSearch;
|
|
50
|
+
const { cookie, mark } = await WeiboMainModels.readSyncCookie();
|
|
51
|
+
let COOKIE = '', XSRF_TOKEN = '';
|
|
23
52
|
const params = {
|
|
24
53
|
q: keyword
|
|
25
54
|
};
|
|
55
|
+
if (String(cookie).includes('XSRF-TOKEN')) {
|
|
56
|
+
COOKIE = cookie;
|
|
57
|
+
XSRF_TOKEN = await WeiboMainModels.readSavedCookieItems(cookie, ['XSRF-TOKEN'], false);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
logger.warn(`优纪插件:搜索微博大V,未登录,如获取报错或失败请先 #扫码微博登录`);
|
|
61
|
+
XSRF_TOKEN = generateRandomString();
|
|
62
|
+
COOKIE = `XSRF_TOKEN=${XSRF_TOKEN}`;
|
|
63
|
+
}
|
|
26
64
|
const resp = await axios(url, {
|
|
27
65
|
method: 'GET',
|
|
28
66
|
params,
|
|
29
67
|
timeout: 10000,
|
|
30
|
-
headers: { '
|
|
68
|
+
headers: lodash.merge(WeiboApi.WEIBO_HEADERS, { 'X-XSRF-TOKEN': `${XSRF_TOKEN}`, 'Cookie': `${COOKIE}`, 'referer': 'https://m.weibo.cn' })
|
|
31
69
|
});
|
|
70
|
+
// 处理 Set-Cookie 响应头
|
|
71
|
+
if (resp.headers['set-cookie']) {
|
|
72
|
+
await WeiboMainModels.updateCookieWithSetCookie(resp.headers['set-cookie'], mark);
|
|
73
|
+
}
|
|
32
74
|
return resp;
|
|
33
75
|
}
|
|
34
76
|
/**获取主页动态资源相关数组 */
|
|
@@ -37,12 +79,27 @@ class WeiboWebDataFetcher {
|
|
|
37
79
|
const url = new URL(WeiboApi.WEIBO_API.weiboGetIndex);
|
|
38
80
|
url.search = new URLSearchParams(params).toString();
|
|
39
81
|
await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * (6500 - 1000 + 1) + 1000)));
|
|
82
|
+
const { cookie, mark } = await WeiboMainModels.readSyncCookie();
|
|
83
|
+
let COOKIE = '', XSRF_TOKEN = '';
|
|
84
|
+
if (String(cookie).includes('XSRF-TOKEN')) {
|
|
85
|
+
COOKIE = cookie;
|
|
86
|
+
XSRF_TOKEN = await WeiboMainModels.readSavedCookieItems(cookie, ['XSRF-TOKEN'], false);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
logger.warn(`优纪插件:获取主页动态资源,未登录,如获取报错或失败请先 #扫码微博登录`);
|
|
90
|
+
XSRF_TOKEN = generateRandomString();
|
|
91
|
+
COOKIE = `XSRF_TOKEN=${XSRF_TOKEN}`;
|
|
92
|
+
}
|
|
40
93
|
try {
|
|
41
94
|
const response = await axios(url.toString(), {
|
|
42
95
|
method: 'GET',
|
|
43
96
|
timeout: 10000,
|
|
44
|
-
headers: { '
|
|
97
|
+
headers: lodash.merge(WeiboApi.WEIBO_HEADERS, { 'X-XSRF-TOKEN': `${XSRF_TOKEN}`, 'Cookie': `${COOKIE}`, 'referer': 'https://m.weibo.cn' })
|
|
45
98
|
});
|
|
99
|
+
// 处理 Set-Cookie 响应头
|
|
100
|
+
if (response.headers['set-cookie']) {
|
|
101
|
+
await WeiboMainModels.updateCookieWithSetCookie(response.headers['set-cookie'], mark);
|
|
102
|
+
}
|
|
46
103
|
const { ok, data, msg } = response?.data;
|
|
47
104
|
if (!ok && msg !== '这里还没有内容') {
|
|
48
105
|
throw new Error(response?.config.url);
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import WeiboApi from './weibo.main.api.js';
|
|
2
|
+
import { getRidFromBd } from './weibo.risk.bd.rid.js';
|
|
3
|
+
import { parse, serialize } from 'cookie';
|
|
4
|
+
import lodash from 'lodash';
|
|
5
|
+
import moment from 'moment';
|
|
6
|
+
import fetch from 'node-fetch';
|
|
7
|
+
import { Segment, Redis } from 'yunzaijs';
|
|
8
|
+
|
|
9
|
+
class WeiboMainModels {
|
|
10
|
+
/**
|
|
11
|
+
* *******************************************************************
|
|
12
|
+
* Login 相关
|
|
13
|
+
* *******************************************************************
|
|
14
|
+
*/
|
|
15
|
+
/**申请登陆二维码(web端) */
|
|
16
|
+
static async applyLoginQRCode(e) {
|
|
17
|
+
const response = await fetch('https://passport.weibo.com/sso/signin?entry=wapsso&source=wapssowb&url=https://m.weibo.cn/', {
|
|
18
|
+
method: 'GET',
|
|
19
|
+
headers: lodash.merge(WeiboApi.WEIBO_GET_X_CSRF_TOKEN_HEADERS, { Host: 'passport.weibo.com' }),
|
|
20
|
+
redirect: 'follow'
|
|
21
|
+
});
|
|
22
|
+
const setCookie = response.headers.get('set-cookie');
|
|
23
|
+
const tokenMatch = setCookie?.match(/X-CSRF-TOKEN=([^;]+)/);
|
|
24
|
+
const X_CSRF_TOKEN = tokenMatch ? tokenMatch[1].replace(/X-CSRF-TOKEN=/g, '') : null;
|
|
25
|
+
if (X_CSRF_TOKEN) {
|
|
26
|
+
const resData = (await fetch('https://passport.weibo.com/sso/v2/qrcode/image?entry=wapsso&size=180', {
|
|
27
|
+
method: 'GET',
|
|
28
|
+
headers: lodash.merge(WeiboApi.WEIBO_LOGIN_QR_CODE_HEADERS, {
|
|
29
|
+
'Host': 'passport.weibo.com',
|
|
30
|
+
'X-CSRF-TOKEN': X_CSRF_TOKEN,
|
|
31
|
+
'Cookie': `X-CSRF-TOKEN=${X_CSRF_TOKEN}`
|
|
32
|
+
}),
|
|
33
|
+
redirect: 'follow'
|
|
34
|
+
}).then(res => res.json()));
|
|
35
|
+
if (resData?.retcode === 20000000) {
|
|
36
|
+
const qrid = resData?.data?.qrid;
|
|
37
|
+
const qrcodeUrl = resData?.data?.image;
|
|
38
|
+
let msg = [];
|
|
39
|
+
if (qrid && qrcodeUrl) {
|
|
40
|
+
const imgResponse = await fetch(qrcodeUrl, {
|
|
41
|
+
method: 'GET',
|
|
42
|
+
headers: WeiboApi.WEIBO_LOGIN_QR_CODE_IMAGE_HEADERS,
|
|
43
|
+
redirect: 'follow'
|
|
44
|
+
});
|
|
45
|
+
if (!imgResponse.ok) {
|
|
46
|
+
logger.error(`获取微博登录二维码失败: ${imgResponse.status}`);
|
|
47
|
+
throw new Error(`获取微博登录图片失败,状态码: ${imgResponse.status}`);
|
|
48
|
+
}
|
|
49
|
+
// 等待3秒再获取rid
|
|
50
|
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
51
|
+
const ridData = await getRidFromBd(X_CSRF_TOKEN);
|
|
52
|
+
if (ridData?.retcode === 20000000) {
|
|
53
|
+
const rid = ridData?.data?.rid;
|
|
54
|
+
const arrayBuffer = await imgResponse.arrayBuffer();
|
|
55
|
+
msg.push(Segment.image(Buffer.from(arrayBuffer)));
|
|
56
|
+
e.reply('请在3分钟内扫码以完成微博登陆绑定');
|
|
57
|
+
e.reply(msg);
|
|
58
|
+
logger.info(`优纪插件: 如果发送二维码图片消息失败可复制如下URL, 浏览器访问此URL查看二维码并扫码`);
|
|
59
|
+
logger.info(`优纪插件: 微博登陆二维码URL: ${qrcodeUrl}`);
|
|
60
|
+
return { qrid, rid, X_CSRF_TOKEN };
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
logger.error('微博登录:获取rid失败');
|
|
64
|
+
e.reply(`获取微博登录rid密钥失败: ${JSON.stringify(ridData)},\n接口逆向进度受阻,未完成,无法登录。\n无妨,已切换启用临时ck`);
|
|
65
|
+
throw new Error(`获取微博登录rid密钥失败: ${JSON.stringify(ridData)},\n接口逆向进度受阻,未完成,无法登录。\n无妨,已切换启用临时ck`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
e.reply(`获取微博登录二维码失败: ${JSON.stringify(resData)}`);
|
|
71
|
+
throw new Error(`获取微博登录二维码失败: ${JSON.stringify(resData)}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
logger.error('微博登录:获取X_CSRF_TOKEN失败');
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**处理扫码结果 */
|
|
80
|
+
static async pollLoginQRCode(e, qrid, rid, X_CSRF_TOKEN) {
|
|
81
|
+
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`;
|
|
82
|
+
const response = await fetch(url, {
|
|
83
|
+
method: 'GET',
|
|
84
|
+
headers: lodash.merge(WeiboApi.WEIBO_POLL_LOGIN_STATUS_HEADERS, {
|
|
85
|
+
'X-CSRF-TOKEN': X_CSRF_TOKEN,
|
|
86
|
+
'Cookie': `X-CSRF-TOKEN=${X_CSRF_TOKEN}`
|
|
87
|
+
}),
|
|
88
|
+
redirect: 'follow'
|
|
89
|
+
});
|
|
90
|
+
if (!response.ok) {
|
|
91
|
+
throw new Error(`处理B站登录token网络请求失败,状态码: ${response.status}`);
|
|
92
|
+
}
|
|
93
|
+
const data = (await response.json());
|
|
94
|
+
if (data?.retcode === 20000000) {
|
|
95
|
+
// 获取 cookie url
|
|
96
|
+
const getCookieUrl = data?.data?.url;
|
|
97
|
+
if (getCookieUrl) {
|
|
98
|
+
const CookieResp = await fetch(getCookieUrl, {
|
|
99
|
+
method: 'GET',
|
|
100
|
+
headers: lodash.merge(WeiboApi.WEIBO_COOKIE_HEADERS, { Cookie: `X-CSRF-TOKEN=${X_CSRF_TOKEN}` }),
|
|
101
|
+
redirect: 'follow'
|
|
102
|
+
});
|
|
103
|
+
const setCookie = CookieResp.headers.get('set-cookie');
|
|
104
|
+
if (setCookie) {
|
|
105
|
+
await this.saveLoginCookie(e, setCookie);
|
|
106
|
+
e.reply(`~微博登陆成功~`);
|
|
107
|
+
return setCookie;
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
e.reply(`获取微博登录Cookie失败: ${JSON.stringify(CookieResp.headers)}`);
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
else if (data?.retcode === 50114001) {
|
|
116
|
+
// 未扫码
|
|
117
|
+
// 继续轮询
|
|
118
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
119
|
+
global?.logger?.mark(`优纪插件:扫码微博登录:未扫码,轮询中...`);
|
|
120
|
+
return this.pollLoginQRCode(e, qrid, rid, X_CSRF_TOKEN);
|
|
121
|
+
}
|
|
122
|
+
else if (data?.retcode === 50114002) {
|
|
123
|
+
// 已扫码未确认
|
|
124
|
+
// 继续轮询
|
|
125
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
126
|
+
return this.pollLoginQRCode(e, qrid, rid, X_CSRF_TOKEN);
|
|
127
|
+
}
|
|
128
|
+
else if (data?.retcode === 50114003) {
|
|
129
|
+
// 二维码已失效
|
|
130
|
+
e.reply('微博登陆二维码已失效');
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
e.reply('处理微博登录扫码结果出错');
|
|
135
|
+
throw new Error(`处理微博登录扫码结果出错: ${JSON.stringify(data)}`);
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
/**查看app扫码登陆获取的ck的有效状态*/
|
|
140
|
+
static async checkWeiboLogin(e) {
|
|
141
|
+
const LoginCookie = await this.readLoginCookie();
|
|
142
|
+
if (String(LoginCookie).trim().length < 10) {
|
|
143
|
+
e.reply('啊咧?微博登录CK呢?哦,没 #扫码微博登录# 或缓存失效了啊,那没事了。');
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
const { cookie, mark } = await WeiboMainModels.readSyncCookie();
|
|
148
|
+
let COOKIE = '', XSRF_TOKEN = '';
|
|
149
|
+
if (String(cookie).includes('XSRF-TOKEN')) {
|
|
150
|
+
COOKIE = cookie;
|
|
151
|
+
XSRF_TOKEN = await WeiboMainModels.readSavedCookieItems(cookie, ['XSRF-TOKEN'], false);
|
|
152
|
+
}
|
|
153
|
+
const resData = (await fetch('https://m.weibo.cn/api/config', {
|
|
154
|
+
method: 'GET',
|
|
155
|
+
headers: lodash.merge(WeiboApi.WEIBO_HEADERS, {
|
|
156
|
+
'X-XSRF-TOKEN': `${XSRF_TOKEN}`,
|
|
157
|
+
'Cookie': `${COOKIE}`,
|
|
158
|
+
'Host': 'm.weibo.cn',
|
|
159
|
+
'Referer': 'https://m.weibo.cn'
|
|
160
|
+
}),
|
|
161
|
+
redirect: 'follow'
|
|
162
|
+
}).then(res => res.json()));
|
|
163
|
+
global?.logger?.debug(`微博验证登录状态:${JSON.stringify(resData)}`);
|
|
164
|
+
if (resData.data?.login === true) {
|
|
165
|
+
const uid = Number(resData.data.uid);
|
|
166
|
+
const user_token = resData.data.user_token;
|
|
167
|
+
const infoRes = (await fetch(`https://m.weibo.cn/profile/info?uid=${uid}`, {
|
|
168
|
+
method: 'GET',
|
|
169
|
+
headers: lodash.merge(WeiboApi.WEIBO_HEADERS, {
|
|
170
|
+
'X-XSRF-TOKEN': `${XSRF_TOKEN}`,
|
|
171
|
+
'Cookie': `${COOKIE}`,
|
|
172
|
+
'Host': 'm.weibo.cn',
|
|
173
|
+
'x-h5-user-token': `${user_token}`,
|
|
174
|
+
'Referer': `https://m.weibo.cn/profile/${uid}?user_token=${user_token}`
|
|
175
|
+
}),
|
|
176
|
+
redirect: 'follow'
|
|
177
|
+
}).then(res => res.json()));
|
|
178
|
+
let uname = infoRes.data?.user?.screen_name;
|
|
179
|
+
let mid = infoRes.data?.user?.id;
|
|
180
|
+
let follow_count = infoRes.data?.user?.follow_count;
|
|
181
|
+
let svip = infoRes.data?.user?.svip;
|
|
182
|
+
const cookie = await this.readLoginCookie();
|
|
183
|
+
const ALF = this.extractALFValue(cookie);
|
|
184
|
+
let TLL = 0;
|
|
185
|
+
if (ALF) {
|
|
186
|
+
TLL = Number(ALF) * 1000;
|
|
187
|
+
}
|
|
188
|
+
const EXP_TIME = moment(TLL * 1000).format('YYYY年MM月DD日 HH:mm:ss');
|
|
189
|
+
e.reply(`~微博账号已登陆~\n昵称:${uname}\nuid:${mid}\nsvip等级:${svip}\n关注:${follow_count}\n登录失效时间:${EXP_TIME}`);
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
// 处理其他情况
|
|
193
|
+
e.reply('意外情况,未能获取微博登录ck的有效状态');
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/** 获取ALF值*/
|
|
199
|
+
static extractALFValue(cookieString) {
|
|
200
|
+
const match = cookieString.match(/ALF=(\d+)/);
|
|
201
|
+
if (match && match[1]) {
|
|
202
|
+
return parseInt(match[1], 10);
|
|
203
|
+
}
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* *******************************************************************
|
|
208
|
+
* cookie相关
|
|
209
|
+
* *******************************************************************
|
|
210
|
+
*/
|
|
211
|
+
/**保存扫码登录的weiboLoginCK*/
|
|
212
|
+
static async saveLoginCookie(e, weiboLoginCk) {
|
|
213
|
+
if (weiboLoginCk && weiboLoginCk.length > 0) {
|
|
214
|
+
const weiboLoginCkKey = 'Yz:yuki:weibo:loginCookie';
|
|
215
|
+
Redis.set(weiboLoginCkKey, weiboLoginCk, { EX: 3600 * 24 * 300 });
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
e.reply('扫码超时');
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/** 读取扫码登陆后缓存的weiboCookie */
|
|
222
|
+
static async readLoginCookie() {
|
|
223
|
+
const CK_KEY = 'Yz:yuki:weibo:loginCookie';
|
|
224
|
+
const tempCk = await Redis.get(CK_KEY);
|
|
225
|
+
return tempCk ? tempCk : '';
|
|
226
|
+
}
|
|
227
|
+
/** 读取手动绑定的weibo CK */
|
|
228
|
+
static async readLocalBiliCk() {
|
|
229
|
+
const CK_KEY = 'Yz:yuki:weibo:localCookie';
|
|
230
|
+
const tempCk = await Redis.get(CK_KEY);
|
|
231
|
+
return tempCk ? tempCk : '';
|
|
232
|
+
}
|
|
233
|
+
/** 覆盖保存手动获取绑定的weibo ck */
|
|
234
|
+
static async saveLocalBiliCk(data) {
|
|
235
|
+
const weiboLoginCkKey = 'Yz:yuki:weibo:localCookie';
|
|
236
|
+
Redis.set(weiboLoginCkKey, data, { EX: 3600 * 24 * 300 });
|
|
237
|
+
}
|
|
238
|
+
/** 读取扫码登陆后缓存的weibo cookie的有效时间 */
|
|
239
|
+
static async readLoginCookieTTL() {
|
|
240
|
+
const CK_KEY = 'Yz:yuki:weibo:loginCookie';
|
|
241
|
+
const tempCk = await Redis.get(CK_KEY);
|
|
242
|
+
if (tempCk) {
|
|
243
|
+
const LoginCookieTTL = await Redis.ttl(CK_KEY);
|
|
244
|
+
return LoginCookieTTL;
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
return -2;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/** 综合获取ck,返回优先级:localCK > loginCK */
|
|
251
|
+
static async readSyncCookie() {
|
|
252
|
+
const localCk = await this.readLocalBiliCk();
|
|
253
|
+
const loginCk = await this.readLoginCookie();
|
|
254
|
+
const validCk = (ck) => ck?.includes('SUB=') && ck?.includes('SUBP=');
|
|
255
|
+
if (validCk(localCk)) {
|
|
256
|
+
return { cookie: localCk, mark: 'localCk' };
|
|
257
|
+
}
|
|
258
|
+
else if (validCk(loginCk)) {
|
|
259
|
+
return { cookie: loginCk + ';', mark: 'loginCk' };
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
return { cookie: '', mark: 'ckIsEmpty' };
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* 综合读取、筛选 传入的或本地或redis存储的cookie的item
|
|
267
|
+
* @param {string} mark 读取存储的CK类型,'localCK' 'loginCK' 或传入值 'xxx'并进行筛选
|
|
268
|
+
* @param {Array} items 选取获取CK的项 选全部值:items[0] = 'all' ,或选取其中的值 ['XSRF-TOKEN', 'SUB', 'SUBP', 'SRF', 'SCF', 'SRT', ' _T_WM', 'M_WEIBOCN_PARAMS', 'SSOLoginState','ALF']
|
|
269
|
+
* @param {boolean} isInverted 控制正取和反取,true为反取,默认为false正取
|
|
270
|
+
* @returns {string}
|
|
271
|
+
**/
|
|
272
|
+
static async readSavedCookieItems(mark, items, isInverted = false) {
|
|
273
|
+
let ckString;
|
|
274
|
+
switch (mark) {
|
|
275
|
+
case 'localCK':
|
|
276
|
+
ckString = await this.readLocalBiliCk();
|
|
277
|
+
break;
|
|
278
|
+
case 'loginCK':
|
|
279
|
+
ckString = await this.readLoginCookie();
|
|
280
|
+
break;
|
|
281
|
+
default:
|
|
282
|
+
ckString = mark;
|
|
283
|
+
}
|
|
284
|
+
const Wck = lodash.trim(ckString);
|
|
285
|
+
if (!Wck) {
|
|
286
|
+
return '';
|
|
287
|
+
}
|
|
288
|
+
if (items[0] === 'all') {
|
|
289
|
+
return Wck;
|
|
290
|
+
}
|
|
291
|
+
const cookiePairs = String(Wck)
|
|
292
|
+
.trim()
|
|
293
|
+
.match(/(\w+)=([^;|,]+)/g) //正则 /(\w+)=([^;]+);/g 匹配 a=b 的内容,并分组为 [^;|,]+ 来匹配值,其中 [^;|,] 表示除了分号和,以外的任意字符
|
|
294
|
+
?.map(match => match.split('='))
|
|
295
|
+
.filter(([key, value]) => (isInverted ? !items.includes(key) : items.includes(key)) && value !== '')
|
|
296
|
+
.map(([key, value]) => `${key}=${value}`)
|
|
297
|
+
.join(';') || '';
|
|
298
|
+
return cookiePairs;
|
|
299
|
+
}
|
|
300
|
+
// 取反读取ck、筛选 传入的或本地或redis存储的cookie的item
|
|
301
|
+
static async readSavedCookieOtherItems(mark, items) {
|
|
302
|
+
return await this.readSavedCookieItems(mark, items, true);
|
|
303
|
+
}
|
|
304
|
+
/**更新cookie */
|
|
305
|
+
static async updateCookieWithSetCookie(setCookieHeaders, mark) {
|
|
306
|
+
// 1. 读取当前保存的 Cookie
|
|
307
|
+
const currentCookie = await this.readLoginCookie();
|
|
308
|
+
// 使用 Record<string, string | undefined> 类型接收解析结果
|
|
309
|
+
const cookieObj = currentCookie ? parse(currentCookie) : {};
|
|
310
|
+
// 2. 解析 Set-Cookie 响应头并更新 Cookie 对象
|
|
311
|
+
setCookieHeaders.forEach(header => {
|
|
312
|
+
const parsedCookie = parse(header.split(';')[0]);
|
|
313
|
+
Object.entries(parsedCookie).forEach(([key, value]) => {
|
|
314
|
+
if (value === 'deleted') {
|
|
315
|
+
delete cookieObj[key];
|
|
316
|
+
}
|
|
317
|
+
else if (value !== undefined) {
|
|
318
|
+
// 添加类型守卫
|
|
319
|
+
cookieObj[key] = value;
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
// 3. 过滤掉 undefined 值并重新组合为字符串
|
|
324
|
+
const validCookies = {};
|
|
325
|
+
Object.entries(cookieObj).forEach(([key, value]) => {
|
|
326
|
+
if (value !== undefined) {
|
|
327
|
+
validCookies[key] = value;
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
const updatedCookie = Object.entries(validCookies)
|
|
331
|
+
.map(([key, value]) => serialize(key, value))
|
|
332
|
+
.join('; ');
|
|
333
|
+
// 4. 保存更新后的 Cookie
|
|
334
|
+
if (updatedCookie) {
|
|
335
|
+
let key = '';
|
|
336
|
+
if (mark === 'loginCK') {
|
|
337
|
+
key = 'Yz:yuki:weibo:loginCookie';
|
|
338
|
+
}
|
|
339
|
+
else if (mark === 'localCK') {
|
|
340
|
+
key = 'Yz:yuki:weibo:localCookie';
|
|
341
|
+
}
|
|
342
|
+
Redis.set(key, updatedCookie, { EX: 3600 * 24 * 300 });
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export { WeiboMainModels };
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import WeiboApi from './weibo.main.api.js';
|
|
2
|
+
import lodash from 'lodash';
|
|
3
|
+
import crypto from 'crypto';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 生成更真实的鼠标轨迹数据(贝塞尔曲线模拟)
|
|
7
|
+
* @returns {Array<Array<number>>} 鼠标轨迹数据 [[x偏移, y偏移, 时间戳], ...]
|
|
8
|
+
*/
|
|
9
|
+
function generateRealisticMouseTrack() {
|
|
10
|
+
const tracks = [];
|
|
11
|
+
const now = Date.now();
|
|
12
|
+
// 生成最近5分钟内的时间点
|
|
13
|
+
const startTime = now - 5 * 60 * 1000;
|
|
14
|
+
const endTime = now;
|
|
15
|
+
// 起始点和结束点
|
|
16
|
+
const startX = Math.floor(Math.random() * 200);
|
|
17
|
+
const startY = Math.floor(Math.random() * 200);
|
|
18
|
+
const endX = 200 + Math.floor(Math.random() * 800); // 页面常见宽度范围内
|
|
19
|
+
const endY = 100 + Math.floor(Math.random() * 600); // 页面常见高度范围内
|
|
20
|
+
// 控制点(用于贝塞尔曲线)
|
|
21
|
+
const controlX = startX + (endX - startX) * 0.3 + Math.random() * 200 - 100;
|
|
22
|
+
const controlY = startY + (endY - startY) * 0.7 + Math.random() * 200 - 100;
|
|
23
|
+
// 生成轨迹点数量
|
|
24
|
+
const pointCount = 30 + Math.floor(Math.random() * 40);
|
|
25
|
+
// 时间间隔
|
|
26
|
+
let currentTime = startTime;
|
|
27
|
+
const timeStep = (endTime - startTime) / pointCount;
|
|
28
|
+
for (let i = 0; i <= pointCount; i++) {
|
|
29
|
+
const t = i / pointCount;
|
|
30
|
+
// 二次贝塞尔曲线计算当前位置
|
|
31
|
+
const x = (1 - t) * (1 - t) * startX + 2 * (1 - t) * t * controlX + t * t * endX;
|
|
32
|
+
const y = (1 - t) * (1 - t) * startY + 2 * (1 - t) * t * controlY + t * t * endY;
|
|
33
|
+
// 计算与上一个点的偏移量
|
|
34
|
+
if (i === 0) {
|
|
35
|
+
// 第一个点,偏移量为0
|
|
36
|
+
tracks.push([0, 0, Math.floor(currentTime)]);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
const prevPoint = tracks[tracks.length - 1];
|
|
40
|
+
const deltaX = Math.round(x - (prevPoint[0] + prevPoint[0])); // 简化计算
|
|
41
|
+
const deltaY = Math.round(y - (prevPoint[1] + prevPoint[1]));
|
|
42
|
+
tracks.push([deltaX, deltaY, Math.floor(currentTime)]);
|
|
43
|
+
}
|
|
44
|
+
// 更新时间戳
|
|
45
|
+
currentTime += timeStep + (Math.random() * 200 - 100); // 添加随机波动
|
|
46
|
+
}
|
|
47
|
+
return tracks;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* 生成浏览器指纹数据
|
|
51
|
+
* @returns {Object} 包含浏览器指纹信息的对象
|
|
52
|
+
*/
|
|
53
|
+
function generateFingerprint() {
|
|
54
|
+
const fingerprintData = { fp: {}, bh: {}, r: {} };
|
|
55
|
+
// 浏览器指纹信息
|
|
56
|
+
fingerprintData.fp = {
|
|
57
|
+
// 版本信息
|
|
58
|
+
0: '1.2.1',
|
|
59
|
+
// 是否支持某特性(布尔值)
|
|
60
|
+
1: {
|
|
61
|
+
s: 1,
|
|
62
|
+
v: false
|
|
63
|
+
},
|
|
64
|
+
// 语言信息
|
|
65
|
+
2: {
|
|
66
|
+
s: 1,
|
|
67
|
+
v: ['lang']
|
|
68
|
+
},
|
|
69
|
+
// User Agent 字符串
|
|
70
|
+
3: {
|
|
71
|
+
s: 1,
|
|
72
|
+
v: WeiboApi.USER_AGENT.replace(/Mozilla\//g, '')
|
|
73
|
+
},
|
|
74
|
+
// 错误信息
|
|
75
|
+
4: {
|
|
76
|
+
s: 1,
|
|
77
|
+
v: "TypeError: Cannot read properties of null (reading '0')\n at W (https://passport.sinaimg.cn/js/fp/1.2.1.umd.js:1:14066)\n at Object.NiOqR (https://passport.sinaimg.cn/js/fp/1.2.1.umd.js:1:3108)\n at https://passport.sinaimg.cn/js/fp/1.2.1.umd.js:1:29253\n at Array.map (<anonymous>)\n at je (https://passport.sinaimg.cn/js/fp/1.2.1.umd.js:1:29193)\n at Object.Qhlex (https://passport.sinaimg.cn/js/fp/1.2.1.umd.js:1:5250)\n at Ie (https://passport.sinaimg.cn/js/fp/1.2.1.umd.js:1:25296)\n at Object.NsuAP (https://passport.sinaimg.cn/js/fp/1.2.1.umd.js:1:4977)\n at Pe (https://passport.sinaimg.cn/js/fp/1.2.1.umd.js:1:24928)\n at Module.Ve [as get] (https://passport.sinaimg.cn/js/fp/1.2.1.umd.js:1:24667)"
|
|
78
|
+
},
|
|
79
|
+
// 数值型数据
|
|
80
|
+
5: {
|
|
81
|
+
s: 1,
|
|
82
|
+
v: 33
|
|
83
|
+
},
|
|
84
|
+
// 函数字符串表示
|
|
85
|
+
6: {
|
|
86
|
+
s: 1,
|
|
87
|
+
v: 'function bind() { [native code] }'
|
|
88
|
+
},
|
|
89
|
+
// 语言环境
|
|
90
|
+
7: {
|
|
91
|
+
s: 1,
|
|
92
|
+
v: [['zh-CN']]
|
|
93
|
+
},
|
|
94
|
+
// 布尔值
|
|
95
|
+
8: {
|
|
96
|
+
s: 1,
|
|
97
|
+
v: true
|
|
98
|
+
},
|
|
99
|
+
// 布尔值
|
|
100
|
+
9: {
|
|
101
|
+
s: 1,
|
|
102
|
+
v: false
|
|
103
|
+
},
|
|
104
|
+
// 布尔值
|
|
105
|
+
10: {
|
|
106
|
+
s: 1,
|
|
107
|
+
v: true
|
|
108
|
+
},
|
|
109
|
+
// 数值
|
|
110
|
+
11: {
|
|
111
|
+
s: 1,
|
|
112
|
+
v: 5
|
|
113
|
+
},
|
|
114
|
+
// 错误信息
|
|
115
|
+
12: {
|
|
116
|
+
s: -1,
|
|
117
|
+
e: ''
|
|
118
|
+
},
|
|
119
|
+
// 日期字符串
|
|
120
|
+
13: {
|
|
121
|
+
s: 1,
|
|
122
|
+
v: '20030107'
|
|
123
|
+
},
|
|
124
|
+
// 数值
|
|
125
|
+
14: {
|
|
126
|
+
s: 1,
|
|
127
|
+
v: 50
|
|
128
|
+
},
|
|
129
|
+
// 完整的 User Agent
|
|
130
|
+
15: {
|
|
131
|
+
s: 1,
|
|
132
|
+
v: WeiboApi.USER_AGENT
|
|
133
|
+
},
|
|
134
|
+
// WebGL 信息
|
|
135
|
+
16: {
|
|
136
|
+
s: 1,
|
|
137
|
+
v: {
|
|
138
|
+
vendor: 'WebKit',
|
|
139
|
+
renderer: 'WebKit WebGL'
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
// 外部对象字符串表示
|
|
143
|
+
17: {
|
|
144
|
+
s: 1,
|
|
145
|
+
v: '[object External]'
|
|
146
|
+
},
|
|
147
|
+
// 屏幕尺寸信息
|
|
148
|
+
18: {
|
|
149
|
+
s: 1,
|
|
150
|
+
v: {
|
|
151
|
+
ow: 1920,
|
|
152
|
+
oh: 1152,
|
|
153
|
+
iw: 257,
|
|
154
|
+
ih: 1031
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
// 浏览器名称
|
|
158
|
+
19: {
|
|
159
|
+
s: 1,
|
|
160
|
+
v: 'chrome'
|
|
161
|
+
},
|
|
162
|
+
// 浏览器内核
|
|
163
|
+
20: {
|
|
164
|
+
s: 1,
|
|
165
|
+
v: 'chromium'
|
|
166
|
+
},
|
|
167
|
+
// 布尔值
|
|
168
|
+
21: {
|
|
169
|
+
s: 1,
|
|
170
|
+
v: false
|
|
171
|
+
},
|
|
172
|
+
// 布尔值
|
|
173
|
+
22: {
|
|
174
|
+
s: 1,
|
|
175
|
+
v: true
|
|
176
|
+
},
|
|
177
|
+
// 触摸支持信息
|
|
178
|
+
23: {
|
|
179
|
+
s: 1,
|
|
180
|
+
v: {
|
|
181
|
+
ots: false,
|
|
182
|
+
mtp: 0,
|
|
183
|
+
mmtp: -1
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
// 行为数据(鼠标轨迹和键盘操作)
|
|
188
|
+
fingerprintData.bh = {
|
|
189
|
+
// 鼠标移动轨迹 [x偏移, y偏移, 时间戳毫秒]
|
|
190
|
+
mt: generateRealisticMouseTrack(),
|
|
191
|
+
// 键盘操作统计
|
|
192
|
+
kt: {
|
|
193
|
+
down: 0,
|
|
194
|
+
up: 0
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
// 追踪设置
|
|
198
|
+
fingerprintData.r = {
|
|
199
|
+
isTraceKeyboard: true,
|
|
200
|
+
isTraceMouse: true
|
|
201
|
+
};
|
|
202
|
+
return fingerprintData;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* 生成 AES 加密密钥和初始化向量
|
|
206
|
+
* @returns {Promise<Object>} 包含 key 和 iv 的对象
|
|
207
|
+
*/
|
|
208
|
+
function generateAESKey() {
|
|
209
|
+
// 生成 AES 密钥 (16位)
|
|
210
|
+
const key = crypto.randomBytes(16);
|
|
211
|
+
// 生成初始化向量
|
|
212
|
+
const iv = crypto.randomBytes(16);
|
|
213
|
+
return {
|
|
214
|
+
key: Buffer.from(key).toString('base64'),
|
|
215
|
+
iv: Buffer.from(iv).toString('base64')
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* RSA 加密函数 (模拟浏览器 Web Crypto API)
|
|
220
|
+
* @param {string} data - 待加密数据
|
|
221
|
+
* @param {Uint8Array} publicKeyDer - DER 格式的公钥
|
|
222
|
+
* @returns {Buffer} 加密后的数据
|
|
223
|
+
*/
|
|
224
|
+
function rsaEncrypt(data, publicKeyDer) {
|
|
225
|
+
// 将 DER 格式的公钥转换为 PEM 格式
|
|
226
|
+
const base64Key = Buffer.from(publicKeyDer).toString('base64');
|
|
227
|
+
const keyLines = base64Key.match(/.{1,64}/g) ?? [];
|
|
228
|
+
const publicKeyPem = `-----BEGIN PUBLIC KEY-----\n${keyLines.join('\n')}\n-----END PUBLIC KEY-----`;
|
|
229
|
+
// 使用 RSA-OAEP-SHA256 算法加密
|
|
230
|
+
return crypto.publicEncrypt({
|
|
231
|
+
key: publicKeyPem,
|
|
232
|
+
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
|
|
233
|
+
oaepHash: 'sha256'
|
|
234
|
+
}, Buffer.from(data, 'binary'));
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* AES CBC 加密函数 (模拟浏览器 Web Crypto API)
|
|
238
|
+
* @param {string} data - 待加密数据
|
|
239
|
+
* @param {string} keyBase64 - Base64 编码的密钥
|
|
240
|
+
* @param {string} ivBase64 - Base64 编码的初始化向量
|
|
241
|
+
* @returns {string} Base64 编码的加密结果
|
|
242
|
+
*/
|
|
243
|
+
function aesEncrypt(data, keyBase64, ivBase64) {
|
|
244
|
+
const key = Buffer.from(keyBase64, 'base64');
|
|
245
|
+
const iv = Buffer.from(ivBase64, 'base64');
|
|
246
|
+
const cipher = crypto.createCipheriv('aes-128-cbc', key, iv);
|
|
247
|
+
cipher.setAutoPadding(true);
|
|
248
|
+
let encrypted = cipher.update(data, 'utf8', 'binary');
|
|
249
|
+
encrypted += cipher.final('binary');
|
|
250
|
+
return Buffer.from(encrypted, 'binary').toString('base64');
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* 数据加密主函数
|
|
254
|
+
* @param {string} jsonData - JSON 格式的指纹数据
|
|
255
|
+
* @returns {Promise<string>} 加密后的数据
|
|
256
|
+
*/
|
|
257
|
+
async function encryptData(jsonData) {
|
|
258
|
+
// 原始的 RSA 公钥(DER 格式),与源代码完全一致
|
|
259
|
+
const publicKeyDer = new Uint8Array([
|
|
260
|
+
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,
|
|
261
|
+
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,
|
|
262
|
+
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,
|
|
263
|
+
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, 97,
|
|
264
|
+
89, 137, 170, 141, 39, 19, 85, 4, 153, 238, 75, 93, 243, 96, 206, 72, 135, 91, 2, 3, 1, 0, 1
|
|
265
|
+
]);
|
|
266
|
+
// 生成 AES 密钥和 IV
|
|
267
|
+
const { key, iv } = generateAESKey();
|
|
268
|
+
// RSA 加密 AES 密钥(将密钥重复两次后加密)
|
|
269
|
+
const keyBinary = Buffer.from(key, 'base64').toString('binary');
|
|
270
|
+
const encryptedKey = rsaEncrypt(keyBinary + keyBinary, publicKeyDer);
|
|
271
|
+
// AES 加密数据
|
|
272
|
+
const encryptedData = aesEncrypt(jsonData, key, iv);
|
|
273
|
+
// 组合加密数据(与原始代码保持一致的格式)
|
|
274
|
+
const result = '01' + Buffer.from('01' + encryptedKey.toString('binary') + '02' + Buffer.from(encryptedData, 'base64').toString('binary')).toString('base64');
|
|
275
|
+
return result;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* 生成请求载荷
|
|
279
|
+
* @returns {Promise<string>} 加密后的载荷数据
|
|
280
|
+
*/
|
|
281
|
+
async function genBdPayload() {
|
|
282
|
+
const fingerprint = generateFingerprint();
|
|
283
|
+
const jsonString = JSON.stringify(fingerprint);
|
|
284
|
+
const encryptedData = await encryptData(jsonString);
|
|
285
|
+
return encryptedData;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* 访问bd接口获取rid
|
|
289
|
+
* @param {string} X_CSRF_TOKEN - X_CSRF_TOKEN
|
|
290
|
+
* @returns {Promise<JSON>} 服务器响应结果
|
|
291
|
+
*/
|
|
292
|
+
async function getRidFromBd(X_CSRF_TOKEN) {
|
|
293
|
+
const params = new URLSearchParams();
|
|
294
|
+
const payload = await genBdPayload();
|
|
295
|
+
payload && params.append('data', payload);
|
|
296
|
+
params.append('from', 'weibo');
|
|
297
|
+
const ridData = (await fetch('https://passport.weibo.com/sso/bd', {
|
|
298
|
+
method: 'POST',
|
|
299
|
+
headers: lodash.merge(WeiboApi.WEIBO_GET_BD_TOKEN_HEADERS, {
|
|
300
|
+
Origin: 'https://passport.weibo.com',
|
|
301
|
+
Cookie: `X-CSRF-TOKEN=${X_CSRF_TOKEN}`
|
|
302
|
+
}),
|
|
303
|
+
body: params,
|
|
304
|
+
redirect: 'follow'
|
|
305
|
+
}).then(res => res.json()));
|
|
306
|
+
return ridData;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export { genBdPayload, getRidFromBd };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yz-yuki-plugin",
|
|
3
|
-
"version": "2.0.8-
|
|
3
|
+
"version": "2.0.8-5",
|
|
4
4
|
"description": "优纪插件,yunzaijs 关于 微博推送、B站推送 等功能的拓展插件",
|
|
5
5
|
"author": "snowtafir",
|
|
6
6
|
"type": "module",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"axios": "^1.7.9",
|
|
29
29
|
"chalk": "^5.3.0",
|
|
30
30
|
"chokidar": "4.0.1",
|
|
31
|
+
"cookie": "^1.0.2",
|
|
31
32
|
"debug": "^4.3.6",
|
|
32
33
|
"jsdom": "^25.0.1",
|
|
33
34
|
"json5": "^2.2.3",
|