yz-yuki-plugin 2.0.7-1 → 2.0.7-10
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 +2 -0
- package/README.md +3 -1
- package/defaultConfig/bilibili/config.yaml +9 -0
- package/defaultConfig/weibo/config.yaml +9 -0
- package/lib/apps/bilibili.js +3 -2
- package/lib/models/bilibili/bilibili.main.get.web.data.js +1 -5
- package/lib/models/bilibili/bilibili.main.query.js +2 -2
- package/lib/models/bilibili/bilibili.main.task.js +74 -43
- package/lib/models/bilibili/bilibili.risk.dm.img.js +4 -4
- package/lib/models/weibo/weibo.main.task.js +69 -38
- package/package.json +1 -1
- package/resources/css/dynamic/MainPage.css +3 -1
- package/lib/models/bilibili/bilibili.risk.w_webid.js +0 -46
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# YUKI-PLUGIN
|
|
4
4
|
|
|
5
|
-
- 一个适用于 `Yunzai 系列机器人框架` 的B
|
|
5
|
+
- 一个适用于 `Yunzai 系列机器人框架` 的B站动态、B站视频链接解析和微博动态订阅推送的插件
|
|
6
6
|
|
|
7
7
|
- 支持 群聊/私聊 订阅B站动态和微博动态,支持定时推送,支持手动触发推送,支持简单查询B站/微博用户信息。
|
|
8
8
|
|
|
@@ -160,6 +160,7 @@ https://m.weibo.cn/u/7643376782 # 7643376782 为崩坏星穹铁道博主uid
|
|
|
160
160
|
请使用 `#优纪帮助`或 `/yuki帮助` 获取完整帮助
|
|
161
161
|
|
|
162
162
|
- [x] B站动态
|
|
163
|
+
- [x] B站视频链接解析
|
|
163
164
|
- [x] 微博动态
|
|
164
165
|
|
|
165
166
|
|
|
@@ -190,6 +191,7 @@ https://m.weibo.cn/u/7643376782 # 7643376782 为崩坏星穹铁道博主uid
|
|
|
190
191
|
| 删除B站ck | 删除手动获取的B站cookie,权限:Master | `#删除B站本地ck` |
|
|
191
192
|
| 查看B站ck | 查看当前启用的B站ck,仅限私聊 | `#我的B站ck` |
|
|
192
193
|
| 刷新B站临时ck | 重新获取并刷新redis缓存的未绑定自己的B站ck而自动获取的 临时B站cookie | `#刷新B站临时ck` |
|
|
194
|
+
| B站视频链接解析 | 解析B站视频链接,支持av号、BV号、app分享链接,官方短链 | `链接xxxx` |
|
|
193
195
|
||||
|
|
194
196
|
| **微博功能** | ------------------------- | ---------- |
|
|
195
197
|
| 添加微博推送 | 检测博主的微博动态进行推送,权限:Master。可选分类:视频、图文、文章、转发,不加分类则默认全部 | `#订阅微博推送uid` `#订阅微博推送 图文 uid` |
|
|
@@ -43,6 +43,15 @@ pushContentLineLimit: 5
|
|
|
43
43
|
# 是否展示定时任务的日志,0 不显示 1 显示
|
|
44
44
|
pushTaskLog: 1
|
|
45
45
|
|
|
46
|
+
# 白名单关键词,命中即推送,不在白名单则不推送。
|
|
47
|
+
# 白名单与黑名单共同起作用,即命中白名单但不命中黑名单即推送,不在白名单或命中黑名单则不推送。
|
|
48
|
+
# 白名单为空则不启用白名单功能。
|
|
49
|
+
# 配置示例:
|
|
50
|
+
# whiteWordslist:
|
|
51
|
+
# - 白名单关键词1
|
|
52
|
+
# - 白名单关键词2
|
|
53
|
+
whiteWordslist:
|
|
54
|
+
|
|
46
55
|
# 包含关键词不推送
|
|
47
56
|
banWords:
|
|
48
57
|
- 关键词1
|
|
@@ -38,6 +38,15 @@ pushContentLineLimit: 5
|
|
|
38
38
|
# 是否展示定时任务的日志,0 不显示 1 显示
|
|
39
39
|
pushTaskLog: 1
|
|
40
40
|
|
|
41
|
+
# 白名单关键词,命中即推送,不在白名单则不推送。
|
|
42
|
+
# 白名单与黑名单共同起作用,即命中白名单但不命中黑名单即推送,不在白名单或命中黑名单则不推送。
|
|
43
|
+
# 白名单为空则不启用白名单功能。
|
|
44
|
+
# 配置示例:
|
|
45
|
+
# whiteWordslist:
|
|
46
|
+
# - 白名单关键词1
|
|
47
|
+
# - 白名单关键词2
|
|
48
|
+
whiteWordslist:
|
|
49
|
+
|
|
41
50
|
# 包含关键词不推送
|
|
42
51
|
banWords:
|
|
43
52
|
- 关键词1
|
package/lib/apps/bilibili.js
CHANGED
|
@@ -499,9 +499,10 @@ message.use(async (e) => {
|
|
|
499
499
|
}, [/^(#|\/)(yuki|优纪)?搜索(b站|B站|bili|bilibili|哔哩|哔哩哔哩)(up|UP)主.*$/]);
|
|
500
500
|
/** 根据名称搜索up的uid*/
|
|
501
501
|
message.use(async (e) => {
|
|
502
|
-
|
|
502
|
+
let parseVideoLink = !!biliConfigData?.parseVideoLink === false ? false : true;
|
|
503
|
+
if (parseVideoLink === false) {
|
|
503
504
|
logger?.info(`优纪B站视频链接解析配置文件已设置关闭,解析终止。`);
|
|
504
|
-
return;
|
|
505
|
+
return false;
|
|
505
506
|
}
|
|
506
507
|
const videoIDMatch = e.msg.match(/(b23\.tv\/([a-zA-Z0-9]+))|(www\.bilibili\.com\/video\/)?(av\d+|BV[a-zA-Z0-9]+)/);
|
|
507
508
|
let videoID;
|
|
@@ -4,7 +4,6 @@ import BiliApi from './bilibili.main.api.js';
|
|
|
4
4
|
import { readSyncCookie, cookieWithBiliTicket, readSavedCookieItems, readSavedCookieOtherItems } from './bilibili.main.models.js';
|
|
5
5
|
import { getWbiSign } from './bilibili.risk.wbi.js';
|
|
6
6
|
import { getDmImg } from './bilibili.risk.dm.img.js';
|
|
7
|
-
import { getWebId } from './bilibili.risk.w_webid.js';
|
|
8
7
|
|
|
9
8
|
class BilibiliWebDataFetcher {
|
|
10
9
|
e;
|
|
@@ -52,20 +51,17 @@ class BilibiliWebDataFetcher {
|
|
|
52
51
|
let { cookie } = await readSyncCookie();
|
|
53
52
|
cookie = await cookieWithBiliTicket(cookie);
|
|
54
53
|
const dmImg = await getDmImg();
|
|
55
|
-
const w_webid = await getWebId(uid);
|
|
56
54
|
const data = {
|
|
57
55
|
mid: uid,
|
|
58
56
|
token: '',
|
|
59
57
|
platform: 'web',
|
|
60
58
|
web_location: 1550101,
|
|
61
|
-
...dmImg
|
|
62
|
-
w_webid: w_webid
|
|
59
|
+
...dmImg
|
|
63
60
|
};
|
|
64
61
|
let signCookie = (await readSavedCookieItems(cookie, ['SESSDATA'], false)) || (await readSavedCookieOtherItems(cookie, ['SESSDATA']));
|
|
65
62
|
const { w_rid, time_stamp } = await getWbiSign(data, BiliApi.BILIBILI_HEADERS, signCookie);
|
|
66
63
|
const params = {
|
|
67
64
|
...data,
|
|
68
|
-
w_webid: w_webid,
|
|
69
65
|
w_rid: w_rid,
|
|
70
66
|
wts: time_stamp
|
|
71
67
|
};
|
|
@@ -227,7 +227,7 @@ class BiliQuery {
|
|
|
227
227
|
case 'RICH_TEXT_NODE_TYPE_EMOJI':
|
|
228
228
|
// 处理表情类型,使用 img 标签显示表情
|
|
229
229
|
const emoji = node.emoji;
|
|
230
|
-
return `<img src="${emoji?.icon_url}" alt="${emoji?.text}" title="${emoji?.text}" style="vertical-align: middle; width: ${emoji?.size ? Number(emoji?.size) *
|
|
230
|
+
return `<img src="${emoji?.icon_url}" alt="${emoji?.text}" title="${emoji?.text}" style="vertical-align: middle; width: ${emoji?.size ? Number(emoji?.size) * 1.5 : 1.5}em; height: ${emoji?.size ? Number(emoji?.size) * 1.5 : 1.5}em;">`;
|
|
231
231
|
case 'RICH_TEXT_NODE_TYPE_GOODS':
|
|
232
232
|
// 处理商品推广类型,使用官方的HTML标签写法
|
|
233
233
|
const goods_url = node?.jump_url;
|
|
@@ -346,7 +346,7 @@ class BiliQuery {
|
|
|
346
346
|
case 'RICH_TEXT_NODE_TYPE_EMOJI':
|
|
347
347
|
// 处理表情类型,使用 img 标签显示表情
|
|
348
348
|
const emoji = node?.rich?.emoji;
|
|
349
|
-
return `<img src="${emoji?.icon_url}" alt="${emoji?.text}" title="${emoji?.text}" style="vertical-align: middle; width: ${emoji?.size ? Number(emoji?.size) *
|
|
349
|
+
return `<img src="${emoji?.icon_url}" alt="${emoji?.text}" title="${emoji?.text}" style="vertical-align: middle; width: ${emoji?.size ? Number(emoji?.size) * 1.5 : 1.5}em; height: ${emoji?.size ? Number(emoji?.size) * 1.5 : 1.5}em;">`;
|
|
350
350
|
case 'RICH_TEXT_NODE_TYPE_GOODS':
|
|
351
351
|
// 处理商品推广类型,使用官方的HTML标签写法
|
|
352
352
|
const goods_url = node?.rich?.jump_url;
|
|
@@ -71,39 +71,45 @@ class BiliTask {
|
|
|
71
71
|
for (let chatId in biliPushData[chatType]) {
|
|
72
72
|
const subUpsOfChat = Array.prototype.slice.call(biliPushData[chatType][chatId] || []);
|
|
73
73
|
for (let subInfoOfup of subUpsOfChat) {
|
|
74
|
+
const { uid, bot_id, name, type } = subInfoOfup;
|
|
74
75
|
let resp;
|
|
75
76
|
// 检查是否已经请求过该 uid
|
|
76
|
-
if (requestedDataOfUids.has(
|
|
77
|
-
resp = requestedDataOfUids.get(
|
|
77
|
+
if (requestedDataOfUids.has(uid)) {
|
|
78
|
+
resp = requestedDataOfUids.get(uid); // 从已请求的映射中获取响应数据
|
|
78
79
|
const dynamicData = resp.data?.items || [];
|
|
79
|
-
dynamicList[
|
|
80
|
+
dynamicList[uid] = dynamicData;
|
|
80
81
|
}
|
|
81
82
|
else {
|
|
82
|
-
resp = await this.hendleEventDynamicData(
|
|
83
|
+
resp = await this.hendleEventDynamicData(uid);
|
|
83
84
|
if (resp) {
|
|
84
85
|
if (resp.code === 0) {
|
|
85
|
-
requestedDataOfUids.set(
|
|
86
|
+
requestedDataOfUids.set(uid, resp); // 将响应数据存储到映射中
|
|
86
87
|
const dynamicData = resp.data?.items || [];
|
|
87
|
-
dynamicList[
|
|
88
|
+
dynamicList[uid] = dynamicData;
|
|
88
89
|
}
|
|
89
90
|
else if (resp.code === -352) {
|
|
90
|
-
logger.error(`获取 ${
|
|
91
|
+
logger.error(`获取 ${uid} 动态失败,resCode:-352,请待下次任务自动重试`);
|
|
91
92
|
return;
|
|
92
93
|
}
|
|
93
94
|
else if (resp.code !== 0) {
|
|
94
|
-
logger.error(`获取 ${
|
|
95
|
+
logger.error(`获取 ${uid} 动态失败,resCode:${resp.code},请待下次任务自动重试`);
|
|
95
96
|
return;
|
|
96
97
|
}
|
|
97
98
|
}
|
|
98
99
|
else {
|
|
99
|
-
logger.error(`获取 ${
|
|
100
|
+
logger.error(`获取 ${uid} 动态失败,无响应数据,请待下次任务自动重试`);
|
|
100
101
|
return;
|
|
101
102
|
}
|
|
102
103
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
chatTypeMap.
|
|
104
|
+
if (!chatTypeMap.has(uid)) {
|
|
105
|
+
chatTypeMap.set(uid, new Map());
|
|
106
|
+
}
|
|
107
|
+
const botChatMap = chatTypeMap.get(uid);
|
|
108
|
+
if (!botChatMap?.has(bot_id)) {
|
|
109
|
+
botChatMap?.set(bot_id, new Map());
|
|
110
|
+
}
|
|
111
|
+
const chatMap = botChatMap?.get(bot_id);
|
|
112
|
+
chatMap?.set(chatId, { upName: name, types: type });
|
|
107
113
|
await this.randomDelay(2000, getDataRandomDelay); // 随机延时
|
|
108
114
|
}
|
|
109
115
|
}
|
|
@@ -119,11 +125,11 @@ class BiliTask {
|
|
|
119
125
|
* @param biliConfigData Bilibili配置数据
|
|
120
126
|
*/
|
|
121
127
|
async makeUidDynamicDataMap(uidMap, dynamicList, now, dynamicTimeRange, biliConfigData, messageMap) {
|
|
128
|
+
const printedList = new Set(); // 已打印的动态列表
|
|
122
129
|
for (let [chatType, chatTypeMap] of uidMap) {
|
|
123
|
-
for (let [
|
|
124
|
-
const tempDynamicList = dynamicList[
|
|
130
|
+
for (let [upUid, bot_idMap] of chatTypeMap) {
|
|
131
|
+
const tempDynamicList = dynamicList[upUid] || [];
|
|
125
132
|
const willPushDynamicList = [];
|
|
126
|
-
const printedList = new Set(); // 已打印的动态列表
|
|
127
133
|
for (let dynamicItem of tempDynamicList) {
|
|
128
134
|
let author = dynamicItem?.modules?.module_author || {};
|
|
129
135
|
if (!printedList.has(author?.mid)) {
|
|
@@ -140,14 +146,12 @@ class BiliTask {
|
|
|
140
146
|
continue; // 如果关闭了转发动态的推送,跳过当前循环
|
|
141
147
|
willPushDynamicList.push(dynamicItem);
|
|
142
148
|
}
|
|
143
|
-
printedList.clear();
|
|
144
|
-
const pushMapInfo = value || {}; // 获取当前 uid 对应的推送信息
|
|
145
|
-
const { chatIds, bot_id, upName, type } = pushMapInfo;
|
|
146
149
|
// 遍历待推送的动态数组,发送动态消息
|
|
147
|
-
for (let
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
150
|
+
for (let [bot_id, chatIdMap] of bot_idMap) {
|
|
151
|
+
for (let [chatId, subUpInfo] of chatIdMap) {
|
|
152
|
+
const { upName, types } = subUpInfo;
|
|
153
|
+
for (let pushDynamicData of willPushDynamicList) {
|
|
154
|
+
if (types && types.length && !types.includes(pushDynamicData.type))
|
|
151
155
|
continue; // 如果禁用了某类型的动态推送,跳过当前循环
|
|
152
156
|
await this.makeDynamicMessageMap(chatId, bot_id, upName, pushDynamicData, biliConfigData, chatType, messageMap); // 发送动态消息
|
|
153
157
|
await this.randomDelay(1000, 2000); // 随机延时1-2秒
|
|
@@ -156,6 +160,7 @@ class BiliTask {
|
|
|
156
160
|
}
|
|
157
161
|
}
|
|
158
162
|
}
|
|
163
|
+
printedList.clear(); // 清空已打印的动态列表
|
|
159
164
|
}
|
|
160
165
|
/**
|
|
161
166
|
* 渲染构建待发送的动态消息数据的映射数组
|
|
@@ -184,12 +189,23 @@ class BiliTask {
|
|
|
184
189
|
if (pushMsgMode === 'PIC') {
|
|
185
190
|
const { data, uid } = await BiliQuery.formatDynamicData(pushDynamicData); // 处理动态数据
|
|
186
191
|
const extentData = { ...data };
|
|
192
|
+
const getWhiteWords = biliConfigData?.whiteWordslist;
|
|
187
193
|
const getBanWords = biliConfigData?.banWords;
|
|
194
|
+
if (getWhiteWords && Array.isArray(getWhiteWords) && getWhiteWords.length > 0) {
|
|
195
|
+
// 构建白名单关键字正则表达式,转义特殊字符
|
|
196
|
+
const whiteWords = new RegExp(getWhiteWords.map(word => word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|'), 'g');
|
|
197
|
+
if (!whiteWords.test(`${extentData?.title}${extentData?.content}`)) {
|
|
198
|
+
return; // 如果动态消息不在白名单中,则直接返回
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
else if (getWhiteWords && !Array.isArray(getWhiteWords)) {
|
|
202
|
+
logger.error(`B站动态:Yaml配置文件中,whiteWordslist 字段格式不是数组格式,请检查!`);
|
|
203
|
+
}
|
|
188
204
|
if (getBanWords && Array.isArray(getBanWords) && getBanWords.length > 0) {
|
|
189
205
|
// 构建屏蔽关键字正则表达式,转义特殊字符
|
|
190
206
|
const banWords = new RegExp(getBanWords.map(word => word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|'), 'g');
|
|
191
207
|
if (banWords.test(`${extentData?.title}${extentData?.content}`)) {
|
|
192
|
-
return
|
|
208
|
+
return; // 如果动态消息包含屏蔽关键字,则直接返回
|
|
193
209
|
}
|
|
194
210
|
}
|
|
195
211
|
else if (getBanWords && !Array.isArray(getBanWords)) {
|
|
@@ -223,7 +239,18 @@ class BiliTask {
|
|
|
223
239
|
if (dynamicMsg === undefined || dynamicMsg === 'continue') {
|
|
224
240
|
return 'return'; // 如果动态消息构建失败,则直接返回
|
|
225
241
|
}
|
|
242
|
+
const getWhiteWords = biliConfigData?.whiteWordslist;
|
|
226
243
|
const getBanWords = biliConfigData?.banWords;
|
|
244
|
+
if (getWhiteWords && Array.isArray(getWhiteWords) && getWhiteWords.length > 0) {
|
|
245
|
+
// 构建白名单关键字正则表达式,转义特殊字符
|
|
246
|
+
const whiteWords = new RegExp(getWhiteWords.map(word => word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|'), 'g');
|
|
247
|
+
if (!whiteWords.test(dynamicMsg.msg.join(''))) {
|
|
248
|
+
return; // 如果动态消息不在白名单中,则直接返回
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
else if (getWhiteWords && !Array.isArray(getWhiteWords)) {
|
|
252
|
+
logger.error(`B站动态:Yaml配置文件中,whiteWordslist 字段格式不是数组格式,请检查!`);
|
|
253
|
+
}
|
|
227
254
|
if (getBanWords && Array.isArray(getBanWords) && getBanWords.length > 0) {
|
|
228
255
|
// 构建屏蔽关键字正则表达式,转义特殊字符
|
|
229
256
|
const banWords = new RegExp(getBanWords.map(word => word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|'), 'g');
|
|
@@ -399,15 +426,22 @@ class BiliTask {
|
|
|
399
426
|
}
|
|
400
427
|
}
|
|
401
428
|
if (sendMode === 'SINGLE') {
|
|
429
|
+
let allSent = true;
|
|
402
430
|
for (let i = 0; i < messages.length; i++) {
|
|
403
|
-
await this.sendMessageApi(chatId, bot_id, chatType, messages[i])
|
|
431
|
+
if (!(await this.sendMessageApi(chatId, bot_id, chatType, messages[i]))) {
|
|
432
|
+
allSent = false;
|
|
433
|
+
break; // 如果有任何一条消息发送失败,停止发送后续消息
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
if (allSent) {
|
|
437
|
+
await Redis.set(sendMarkKey, '1', { EX: 3600 * 72 }); // 发送成功后设置标记
|
|
438
|
+
await this.randomDelay(1000, 2000); // 随机延时1-2秒
|
|
404
439
|
}
|
|
405
|
-
await Redis.set(sendMarkKey, '1', { EX: 3600 * 72 }); // 发送成功后设置标记
|
|
406
|
-
await this.randomDelay(1000, 2000); // 随机延时1-2秒
|
|
407
440
|
}
|
|
408
441
|
else if (sendMode === 'MERGE') {
|
|
409
|
-
await this.sendMessageApi(chatId, bot_id, chatType, messages)
|
|
410
|
-
|
|
442
|
+
if (await this.sendMessageApi(chatId, bot_id, chatType, messages)) {
|
|
443
|
+
await Redis.set(sendMarkKey, '1', { EX: 3600 * 72 }); // 发送成功后设置标记
|
|
444
|
+
}
|
|
411
445
|
}
|
|
412
446
|
}
|
|
413
447
|
}
|
|
@@ -423,21 +457,18 @@ class BiliTask {
|
|
|
423
457
|
* @param message 消息内容
|
|
424
458
|
*/
|
|
425
459
|
async sendMessageApi(chatId, bot_id, chatType, message) {
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
?.pickGroup(String(chatId))
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
}
|
|
460
|
+
try {
|
|
461
|
+
if (chatType === 'group') {
|
|
462
|
+
await (Bot[bot_id] ?? Bot)?.pickGroup(String(chatId)).sendMsg(message); // 发送群聊
|
|
463
|
+
}
|
|
464
|
+
else if (chatType === 'private') {
|
|
465
|
+
await (Bot[bot_id] ?? Bot)?.pickFriend(String(chatId)).sendMsg(message); // 发送好友私聊
|
|
466
|
+
}
|
|
467
|
+
return true; // 发送成功
|
|
433
468
|
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
.sendMsg(message)
|
|
438
|
-
.catch((error) => {
|
|
439
|
-
global?.logger?.error(`用户[${chatId}]推送失败:${JSON.stringify(error)}`);
|
|
440
|
-
}); // 发送好友私聊
|
|
469
|
+
catch (error) {
|
|
470
|
+
global?.logger?.error(`${chatType === 'group' ? '群聊' : '私聊'} ${chatId} 消息发送失败:${JSON.stringify(error)}`);
|
|
471
|
+
return false; // 发送失败
|
|
441
472
|
}
|
|
442
473
|
}
|
|
443
474
|
/**
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/**获取dm参数 */
|
|
2
2
|
async function getDmImg() {
|
|
3
|
-
const dm_img_list = []
|
|
3
|
+
const dm_img_list = `[]`;
|
|
4
4
|
//Buffer.from("WebGL 1", 'utf-8').toString("base64") //webgl version的值 WebGL 1 的base64 编码
|
|
5
5
|
const dm_img_str = 'V2ViR0wgMS';
|
|
6
6
|
//webgl unmasked renderer的值拼接webgl unmasked vendor的值的base64编码
|
|
7
7
|
const dm_cover_img_str = 'QU5HTEUgKEludGVsLCBJbnRlbChSKSBIRCBHcmFwaGljcyBEaXJlY3QzRDExIHZzXzVfMCBwc181XzApLCBvciBzaW1pbGFyR29vZ2xlIEluYy4gKEludGVsKQ';
|
|
8
|
-
const dm_img_inter = {
|
|
8
|
+
const dm_img_inter = `{ds:[],wh:[0,0,0],of:[0,0,0]}`;
|
|
9
9
|
return {
|
|
10
|
-
dm_img_list:
|
|
10
|
+
dm_img_list: dm_img_list,
|
|
11
11
|
dm_img_str: dm_img_str,
|
|
12
12
|
dm_cover_img_str: dm_cover_img_str,
|
|
13
|
-
dm_img_inter:
|
|
13
|
+
dm_img_inter: dm_img_inter
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
16
|
|
|
@@ -51,25 +51,31 @@ class WeiboTask {
|
|
|
51
51
|
for (let chatId in weiboPushData[chatType]) {
|
|
52
52
|
const subUpsOfChat = Array.prototype.slice.call(weiboPushData[chatType][chatId] || []);
|
|
53
53
|
for (let subInfoOfup of subUpsOfChat) {
|
|
54
|
+
const { uid, bot_id, name, type } = subInfoOfup;
|
|
54
55
|
let resp;
|
|
55
56
|
// 检查是否已经请求过该 uid
|
|
56
|
-
if (requestedDataOfUids.has(
|
|
57
|
-
resp = requestedDataOfUids.get(
|
|
57
|
+
if (requestedDataOfUids.has(uid)) {
|
|
58
|
+
resp = requestedDataOfUids.get(uid); // 从已请求的映射中获取响应数据
|
|
58
59
|
const dynamicData = resp || [];
|
|
59
|
-
dynamicList[
|
|
60
|
+
dynamicList[uid] = dynamicData;
|
|
60
61
|
}
|
|
61
62
|
else {
|
|
62
|
-
resp = await new WeiboWebDataFetcher().getBloggerDynamicList(
|
|
63
|
+
resp = await new WeiboWebDataFetcher().getBloggerDynamicList(uid); // 获取指定 uid 的动态列表
|
|
63
64
|
if (resp) {
|
|
64
|
-
requestedDataOfUids.set(
|
|
65
|
+
requestedDataOfUids.set(uid, resp); // 将响应数据存储到映射中
|
|
65
66
|
const dynamicData = resp || [];
|
|
66
67
|
dynamicList[subInfoOfup.uid] = dynamicData;
|
|
67
68
|
}
|
|
68
69
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
chatTypeMap.
|
|
70
|
+
if (!chatTypeMap.has(uid)) {
|
|
71
|
+
chatTypeMap.set(uid, new Map());
|
|
72
|
+
}
|
|
73
|
+
const botChatMap = chatTypeMap.get(uid);
|
|
74
|
+
if (!botChatMap?.has(bot_id)) {
|
|
75
|
+
botChatMap?.set(bot_id, new Map());
|
|
76
|
+
}
|
|
77
|
+
const chatMap = botChatMap?.get(bot_id);
|
|
78
|
+
chatMap?.set(chatId, { upName: name, types: type });
|
|
73
79
|
await this.randomDelay(1000, 4000); // 随机延时1-4秒
|
|
74
80
|
}
|
|
75
81
|
}
|
|
@@ -85,11 +91,11 @@ class WeiboTask {
|
|
|
85
91
|
* @param weiboConfigData 微博配置数据
|
|
86
92
|
*/
|
|
87
93
|
async makeUidDynamicDataMap(uidMap, dynamicList, now, dynamicTimeRange, weiboConfigData, messageMap) {
|
|
94
|
+
const printedList = new Set(); // 已打印的动态列表
|
|
88
95
|
for (let [chatType, chatTypeMap] of uidMap) {
|
|
89
|
-
for (let [
|
|
90
|
-
const tempDynamicList = dynamicList[
|
|
96
|
+
for (let [upUid, bot_idMap] of chatTypeMap) {
|
|
97
|
+
const tempDynamicList = dynamicList[upUid] || [];
|
|
91
98
|
const willPushDynamicList = [];
|
|
92
|
-
const printedList = new Set(); // 已打印的动态列表
|
|
93
99
|
for (let dynamicItem of tempDynamicList) {
|
|
94
100
|
let raw_post = dynamicItem || {};
|
|
95
101
|
let user = raw_post?.mblog?.user || {};
|
|
@@ -107,14 +113,12 @@ class WeiboTask {
|
|
|
107
113
|
continue; // 如果关闭了转发动态的推送,跳过当前循环
|
|
108
114
|
willPushDynamicList.push(dynamicItem);
|
|
109
115
|
}
|
|
110
|
-
printedList.clear();
|
|
111
|
-
const pushMapInfo = value || {}; // 获取当前 uid 对应的推送信息
|
|
112
|
-
const { chatIds, bot_id, upName, type } = pushMapInfo;
|
|
113
116
|
// 遍历待推送的动态数组,发送动态消息
|
|
114
|
-
for (let
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
117
|
+
for (let [bot_id, chatIdMap] of bot_idMap) {
|
|
118
|
+
for (let [chatId, subUpInfo] of chatIdMap) {
|
|
119
|
+
const { upName, types } = subUpInfo;
|
|
120
|
+
for (let pushDynamicData of willPushDynamicList) {
|
|
121
|
+
if (types && types.length && !types.includes(pushDynamicData.type))
|
|
118
122
|
continue; // 如果禁用了某类型的动态推送,跳过当前循环
|
|
119
123
|
await this.makeDynamicMessageMap(chatId, bot_id, upName, pushDynamicData, weiboConfigData, chatType, messageMap); // 发送动态消息
|
|
120
124
|
await this.randomDelay(1000, 2000); // 随机延时1-2秒
|
|
@@ -123,6 +127,7 @@ class WeiboTask {
|
|
|
123
127
|
}
|
|
124
128
|
}
|
|
125
129
|
}
|
|
130
|
+
printedList.clear(); // 清空已打印的动态列表
|
|
126
131
|
}
|
|
127
132
|
/**
|
|
128
133
|
* 渲染构建待发送的动态消息数据的映射数组
|
|
@@ -149,7 +154,18 @@ class WeiboTask {
|
|
|
149
154
|
return; // 如果已经发送过,则直接返回
|
|
150
155
|
if (!!weiboConfigData.pushMsgMode) {
|
|
151
156
|
const { data, uid } = await WeiboQuery.formatDynamicData(pushDynamicData); // 处理动态数据
|
|
157
|
+
const getWhiteWords = weiboConfigData?.whiteWordslist;
|
|
152
158
|
const getBanWords = weiboConfigData?.banWords;
|
|
159
|
+
if (getWhiteWords && Array.isArray(getWhiteWords) && getWhiteWords.length > 0) {
|
|
160
|
+
// 构建白名单关键字正则表达式,转义特殊字符
|
|
161
|
+
const whiteWords = new RegExp(getWhiteWords.map(word => word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|'), 'g');
|
|
162
|
+
if (!whiteWords.test(`${data?.title}${data?.content}`)) {
|
|
163
|
+
return; // 如果动态消息不在白名单中,则直接返回
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
else if (getWhiteWords && !Array.isArray(getWhiteWords)) {
|
|
167
|
+
logger.error(`微博动态:Yaml配置文件中,whiteWordslist 字段格式不是数组格式,请检查!`);
|
|
168
|
+
}
|
|
153
169
|
if (getBanWords && Array.isArray(getBanWords) && getBanWords.length > 0) {
|
|
154
170
|
// 构建屏蔽关键字正则表达式,转义特殊字符
|
|
155
171
|
const banWords = new RegExp(getBanWords.map(word => word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|'), 'g');
|
|
@@ -189,7 +205,18 @@ class WeiboTask {
|
|
|
189
205
|
if (dynamicMsg === undefined || dynamicMsg === 'continue') {
|
|
190
206
|
return 'return'; // 如果动态消息构建失败或内部资源获取失败,则直接返回
|
|
191
207
|
}
|
|
208
|
+
const getWhiteWords = weiboConfigData?.whiteWordslist;
|
|
192
209
|
const getBanWords = weiboConfigData?.banWords;
|
|
210
|
+
if (getWhiteWords && Array.isArray(getWhiteWords) && getWhiteWords.length > 0) {
|
|
211
|
+
// 构建白名单关键字正则表达式,转义特殊字符
|
|
212
|
+
const whiteWords = new RegExp(getWhiteWords.map(word => word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|'), 'g');
|
|
213
|
+
if (!whiteWords.test(dynamicMsg.msg.join(''))) {
|
|
214
|
+
return; // 如果动态消息不在白名单中,则直接返回
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
else if (getWhiteWords && !Array.isArray(getWhiteWords)) {
|
|
218
|
+
logger.error(`微博动态:Yaml配置文件中,whiteWordslist 字段格式不是数组格式,请检查!`);
|
|
219
|
+
}
|
|
193
220
|
if (getBanWords && Array.isArray(getBanWords) && getBanWords.length > 0) {
|
|
194
221
|
// 构建屏蔽关键字正则表达式,转义特殊字符
|
|
195
222
|
const banWords = new RegExp(getBanWords.map(word => word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|'), 'g');
|
|
@@ -346,15 +373,22 @@ class WeiboTask {
|
|
|
346
373
|
LogMark.add('1');
|
|
347
374
|
}
|
|
348
375
|
if (sendMode === 'SINGLE') {
|
|
376
|
+
let allSent = true;
|
|
349
377
|
for (let i = 0; i < messages.length; i++) {
|
|
350
|
-
await this.sendMessageApi(chatId, bot_id, chatType, messages[i])
|
|
378
|
+
if (!(await this.sendMessageApi(chatId, bot_id, chatType, messages[i]))) {
|
|
379
|
+
allSent = false;
|
|
380
|
+
break; // 如果有任何一条消息发送失败,停止发送后续消息
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
if (allSent) {
|
|
384
|
+
await Redis.set(sendMarkKey, '1', { EX: 3600 * 72 }); // 发送成功后设置标记
|
|
385
|
+
await this.randomDelay(1000, 2000); // 随机延时1-2秒
|
|
351
386
|
}
|
|
352
|
-
await Redis.set(sendMarkKey, '1', { EX: 3600 * 72 }); // 发送成功后设置标记
|
|
353
|
-
await this.randomDelay(1000, 2000); // 随机延时1-2秒
|
|
354
387
|
}
|
|
355
388
|
else if (sendMode === 'MERGE') {
|
|
356
|
-
await this.sendMessageApi(chatId, bot_id, chatType, messages)
|
|
357
|
-
|
|
389
|
+
if (await this.sendMessageApi(chatId, bot_id, chatType, messages)) {
|
|
390
|
+
await Redis.set(sendMarkKey, '1', { EX: 3600 * 72 }); // 发送成功后设置标记
|
|
391
|
+
}
|
|
358
392
|
}
|
|
359
393
|
}
|
|
360
394
|
}
|
|
@@ -370,21 +404,18 @@ class WeiboTask {
|
|
|
370
404
|
* @param message 消息内容
|
|
371
405
|
*/
|
|
372
406
|
async sendMessageApi(chatId, bot_id, chatType, message) {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
?.pickGroup(String(chatId))
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
}
|
|
407
|
+
try {
|
|
408
|
+
if (chatType === 'group') {
|
|
409
|
+
await (Bot[bot_id] ?? Bot)?.pickGroup(String(chatId)).sendMsg(message); // 发送群聊
|
|
410
|
+
}
|
|
411
|
+
else if (chatType === 'private') {
|
|
412
|
+
await (Bot[bot_id] ?? Bot)?.pickFriend(String(chatId)).sendMsg(message); // 发送好友私聊
|
|
413
|
+
}
|
|
414
|
+
return true; // 发送成功
|
|
380
415
|
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
.sendMsg(message)
|
|
385
|
-
.catch(error => {
|
|
386
|
-
global?.logger?.error(`用户[${chatId}]推送失败:${JSON.stringify(error)}`);
|
|
387
|
-
}); // 发送好友私聊
|
|
416
|
+
catch (error) {
|
|
417
|
+
global?.logger?.error(`${chatType === 'group' ? '群聊' : '私聊'} ${chatId} 消息发送失败:${JSON.stringify(error)}`);
|
|
418
|
+
return false; // 发送失败
|
|
388
419
|
}
|
|
389
420
|
}
|
|
390
421
|
/**
|
package/package.json
CHANGED
|
@@ -25,8 +25,10 @@ body::-webkit-scrollbar {
|
|
|
25
25
|
font-style: normal;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
@import url('https://s1.hdslb.com/bfs/static/jinkela/long/font/regular.css');
|
|
29
|
+
|
|
28
30
|
body {
|
|
29
|
-
font-family: 'OPSans', Arial, sans-serif;
|
|
31
|
+
font-family: 'OPSans', 'HarmonyOS_Regular', 'PingFang SC', 'Microsoft YaHei', Arial, sans-serif;
|
|
30
32
|
background-color: #f9f9f9;
|
|
31
33
|
margin: 0;
|
|
32
34
|
padding: 0;
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import axios from 'axios';
|
|
2
|
-
import lodash from 'lodash';
|
|
3
|
-
import { Redis } from 'yunzaijs';
|
|
4
|
-
import BiliApi from './bilibili.main.api.js';
|
|
5
|
-
import { readSyncCookie, cookieWithBiliTicket } from './bilibili.main.models.js';
|
|
6
|
-
|
|
7
|
-
async function getWebId(uid) {
|
|
8
|
-
const w_webid_key = 'Yz:yuki:bili:w_webid';
|
|
9
|
-
const w_webid = await Redis.get(w_webid_key);
|
|
10
|
-
const keyTTL = await Redis.ttl(w_webid_key);
|
|
11
|
-
if (w_webid && keyTTL < 259200) {
|
|
12
|
-
return String(w_webid);
|
|
13
|
-
}
|
|
14
|
-
else {
|
|
15
|
-
const url = `https://space.bilibili.com/${uid ? uid : 401742377}/dynamic`;
|
|
16
|
-
let { cookie } = await readSyncCookie();
|
|
17
|
-
cookie = await cookieWithBiliTicket(cookie);
|
|
18
|
-
const res = await axios.get(url, {
|
|
19
|
-
timeout: 8000,
|
|
20
|
-
headers: lodash.merge(BiliApi.BILIBILI_DYNAMIC_SPACE_HEADERS, {
|
|
21
|
-
Cookie: `${cookie}`,
|
|
22
|
-
Host: `space.bilibili.com`
|
|
23
|
-
})
|
|
24
|
-
});
|
|
25
|
-
const htmlContent = await res.data;
|
|
26
|
-
const htmlContentRegex = /="__RENDER_DATA__"\s*type="application\/json">(.*?)<\/script>/;
|
|
27
|
-
const __RENDER_DATA__ = htmlContent.match(htmlContentRegex);
|
|
28
|
-
if (__RENDER_DATA__ && __RENDER_DATA__[1]) {
|
|
29
|
-
const decoded__RENDER_DATA__JsonString = decodeURIComponent(__RENDER_DATA__[1]);
|
|
30
|
-
const accessIdRegex = /"access_id":"(.*?)"/;
|
|
31
|
-
const access_id = decoded__RENDER_DATA__JsonString.match(accessIdRegex);
|
|
32
|
-
const ExpirationTimeRegex = /document.getElementById\("__RENDER_DATA__"\).*?setTimeout\(function\(\)\s*{window.location.reload\(true\);},\s*(\d+)\s*\*\s*(\d+)\);<\/script>/;
|
|
33
|
-
const ExpirationTime = htmlContent.match(ExpirationTimeRegex);
|
|
34
|
-
if (access_id && access_id[1] && ExpirationTime && ExpirationTime[1]) {
|
|
35
|
-
await Redis.set(w_webid_key, access_id[1], { EX: Number(ExpirationTime[1]) });
|
|
36
|
-
return String(access_id[1]);
|
|
37
|
-
}
|
|
38
|
-
else {
|
|
39
|
-
console.error('Failed to get access_id from __RENDER_DATA__');
|
|
40
|
-
return null;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export { getWebId };
|