yz-yuki-plugin 2.0.7-1 → 2.0.7-11

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 CHANGED
@@ -1,4 +1,6 @@
1
1
  # 2.0.7
2
+ * 修复同一up订阅多个群聊订阅,推送类型合并的问题
3
+ * 添加白名单关键词过滤功能
2
4
  * 新增B站视频解析
3
5
 
4
6
  # 2.0.6
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
@@ -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
- if (biliConfigData?.parseVideoLink && !!biliConfigData.parseVideoLink === false) {
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) * 2 : 2}em; height: ${emoji?.size ? Number(emoji?.size) * 2 : 2}em;">`;
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) * 2 : 2}em; height: ${emoji?.size ? Number(emoji?.size) * 2 : 2}em;">`;
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,40 @@ 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(subInfoOfup.uid)) {
77
- resp = requestedDataOfUids.get(subInfoOfup.uid); // 从已请求的映射中获取响应数据
78
- const dynamicData = resp.data?.items || [];
79
- dynamicList[subInfoOfup.uid] = dynamicData;
80
- }
81
- else {
82
- resp = await this.hendleEventDynamicData(subInfoOfup.uid);
77
+ if (!requestedDataOfUids.has(uid)) {
78
+ resp = await this.hendleEventDynamicData(uid);
83
79
  if (resp) {
84
80
  if (resp.code === 0) {
85
- requestedDataOfUids.set(subInfoOfup.uid, resp); // 将响应数据存储到映射中
81
+ requestedDataOfUids.set(uid, resp); // 将响应数据存储到映射中
86
82
  const dynamicData = resp.data?.items || [];
87
- dynamicList[subInfoOfup.uid] = dynamicData;
83
+ dynamicList[uid] = dynamicData;
88
84
  }
89
85
  else if (resp.code === -352) {
90
- logger.error(`获取 ${subInfoOfup.uid} 动态失败,resCode:-352,请待下次任务自动重试`);
86
+ logger.error(`获取 ${uid} 动态失败,resCode:-352,请待下次任务自动重试`);
91
87
  return;
92
88
  }
93
89
  else if (resp.code !== 0) {
94
- logger.error(`获取 ${subInfoOfup.uid} 动态失败,resCode:${resp.code},请待下次任务自动重试`);
90
+ logger.error(`获取 ${uid} 动态失败,resCode:${resp.code},请待下次任务自动重试`);
95
91
  return;
96
92
  }
97
93
  }
98
94
  else {
99
- logger.error(`获取 ${subInfoOfup.uid} 动态失败,无响应数据,请待下次任务自动重试`);
95
+ logger.error(`获取 ${uid} 动态失败,无响应数据,请待下次任务自动重试`);
100
96
  return;
101
97
  }
102
98
  }
103
- const chatIds = Array.from(new Set([...Object((chatTypeMap.get(subInfoOfup.uid) && chatTypeMap.get(subInfoOfup.uid).chatIds) || []), chatId]));
104
- const bot_id = subInfoOfup.bot_id || [];
105
- const { name, type } = subInfoOfup;
106
- chatTypeMap.set(subInfoOfup.uid, { chatIds, bot_id, upName: name, type });
99
+ if (!chatTypeMap.has(uid)) {
100
+ chatTypeMap.set(uid, new Map());
101
+ }
102
+ const botChatMap = chatTypeMap.get(uid);
103
+ if (!botChatMap?.has(bot_id)) {
104
+ botChatMap?.set(bot_id, new Map());
105
+ }
106
+ const chatMap = botChatMap?.get(bot_id);
107
+ chatMap?.set(chatId, { upName: name, types: type });
107
108
  await this.randomDelay(2000, getDataRandomDelay); // 随机延时
108
109
  }
109
110
  }
@@ -119,11 +120,11 @@ class BiliTask {
119
120
  * @param biliConfigData Bilibili配置数据
120
121
  */
121
122
  async makeUidDynamicDataMap(uidMap, dynamicList, now, dynamicTimeRange, biliConfigData, messageMap) {
123
+ const printedList = new Set(); // 已打印的动态列表
122
124
  for (let [chatType, chatTypeMap] of uidMap) {
123
- for (let [key, value] of chatTypeMap) {
124
- const tempDynamicList = dynamicList[key] || [];
125
+ for (let [upUid, bot_idMap] of chatTypeMap) {
126
+ const tempDynamicList = dynamicList[upUid] || [];
125
127
  const willPushDynamicList = [];
126
- const printedList = new Set(); // 已打印的动态列表
127
128
  for (let dynamicItem of tempDynamicList) {
128
129
  let author = dynamicItem?.modules?.module_author || {};
129
130
  if (!printedList.has(author?.mid)) {
@@ -136,19 +137,19 @@ class BiliTask {
136
137
  logger.debug(`超过间隔,跳过 [ ${author?.name} : ${author?.mid} ] ${author?.pub_time} 的动态`);
137
138
  continue;
138
139
  } // 如果超过推送时间间隔,跳过当前循环
139
- if (dynamicItem.type === 'DYNAMIC_TYPE_FORWARD' && !biliConfigData.pushTransmit)
140
- continue; // 如果关闭了转发动态的推送,跳过当前循环
140
+ if (dynamicItem?.type === 'DYNAMIC_TYPE_FORWARD' && !biliConfigData.pushTransmit) {
141
+ continue;
142
+ } // 如果关闭了转发动态的推送,跳过当前循环
141
143
  willPushDynamicList.push(dynamicItem);
142
144
  }
143
- printedList.clear();
144
- const pushMapInfo = value || {}; // 获取当前 uid 对应的推送信息
145
- const { chatIds, bot_id, upName, type } = pushMapInfo;
146
145
  // 遍历待推送的动态数组,发送动态消息
147
- for (let pushDynamicData of willPushDynamicList) {
148
- if (chatIds && chatIds.length) {
149
- for (let chatId of chatIds) {
150
- if (type && type.length && !type.includes(pushDynamicData.type))
151
- continue; // 如果禁用了某类型的动态推送,跳过当前循环
146
+ for (let [bot_id, chatIdMap] of bot_idMap) {
147
+ for (let [chatId, subUpInfo] of chatIdMap) {
148
+ const { upName, types } = subUpInfo;
149
+ for (let pushDynamicData of willPushDynamicList) {
150
+ if (types && types.length > 0 && !types.includes(pushDynamicData.type)) {
151
+ continue;
152
+ } // 如果禁用了某类型的动态推送,跳过当前循环
152
153
  await this.makeDynamicMessageMap(chatId, bot_id, upName, pushDynamicData, biliConfigData, chatType, messageMap); // 发送动态消息
153
154
  await this.randomDelay(1000, 2000); // 随机延时1-2秒
154
155
  }
@@ -156,6 +157,7 @@ class BiliTask {
156
157
  }
157
158
  }
158
159
  }
160
+ printedList.clear(); // 清空已打印的动态列表
159
161
  }
160
162
  /**
161
163
  * 渲染构建待发送的动态消息数据的映射数组
@@ -184,12 +186,23 @@ class BiliTask {
184
186
  if (pushMsgMode === 'PIC') {
185
187
  const { data, uid } = await BiliQuery.formatDynamicData(pushDynamicData); // 处理动态数据
186
188
  const extentData = { ...data };
189
+ const getWhiteWords = biliConfigData?.whiteWordslist;
187
190
  const getBanWords = biliConfigData?.banWords;
191
+ if (getWhiteWords && Array.isArray(getWhiteWords) && getWhiteWords.length > 0) {
192
+ // 构建白名单关键字正则表达式,转义特殊字符
193
+ const whiteWords = new RegExp(getWhiteWords.map(word => word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|'), 'g');
194
+ if (!whiteWords.test(`${extentData?.title}${extentData?.content}`)) {
195
+ return; // 如果动态消息不在白名单中,则直接返回
196
+ }
197
+ }
198
+ else if (getWhiteWords && !Array.isArray(getWhiteWords)) {
199
+ logger.error(`B站动态:Yaml配置文件中,whiteWordslist 字段格式不是数组格式,请检查!`);
200
+ }
188
201
  if (getBanWords && Array.isArray(getBanWords) && getBanWords.length > 0) {
189
202
  // 构建屏蔽关键字正则表达式,转义特殊字符
190
203
  const banWords = new RegExp(getBanWords.map(word => word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|'), 'g');
191
204
  if (banWords.test(`${extentData?.title}${extentData?.content}`)) {
192
- return 'return'; // 如果动态消息包含屏蔽关键字,则直接返回
205
+ return; // 如果动态消息包含屏蔽关键字,则直接返回
193
206
  }
194
207
  }
195
208
  else if (getBanWords && !Array.isArray(getBanWords)) {
@@ -223,7 +236,18 @@ class BiliTask {
223
236
  if (dynamicMsg === undefined || dynamicMsg === 'continue') {
224
237
  return 'return'; // 如果动态消息构建失败,则直接返回
225
238
  }
239
+ const getWhiteWords = biliConfigData?.whiteWordslist;
226
240
  const getBanWords = biliConfigData?.banWords;
241
+ if (getWhiteWords && Array.isArray(getWhiteWords) && getWhiteWords.length > 0) {
242
+ // 构建白名单关键字正则表达式,转义特殊字符
243
+ const whiteWords = new RegExp(getWhiteWords.map(word => word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|'), 'g');
244
+ if (!whiteWords.test(dynamicMsg.msg.join(''))) {
245
+ return; // 如果动态消息不在白名单中,则直接返回
246
+ }
247
+ }
248
+ else if (getWhiteWords && !Array.isArray(getWhiteWords)) {
249
+ logger.error(`B站动态:Yaml配置文件中,whiteWordslist 字段格式不是数组格式,请检查!`);
250
+ }
227
251
  if (getBanWords && Array.isArray(getBanWords) && getBanWords.length > 0) {
228
252
  // 构建屏蔽关键字正则表达式,转义特殊字符
229
253
  const banWords = new RegExp(getBanWords.map(word => word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|'), 'g');
@@ -399,15 +423,22 @@ class BiliTask {
399
423
  }
400
424
  }
401
425
  if (sendMode === 'SINGLE') {
426
+ let allSent = true;
402
427
  for (let i = 0; i < messages.length; i++) {
403
- await this.sendMessageApi(chatId, bot_id, chatType, messages[i]);
428
+ if (!(await this.sendMessageApi(chatId, bot_id, chatType, messages[i]))) {
429
+ allSent = false;
430
+ break; // 如果有任何一条消息发送失败,停止发送后续消息
431
+ }
432
+ }
433
+ if (allSent) {
434
+ await Redis.set(sendMarkKey, '1', { EX: 3600 * 72 }); // 发送成功后设置标记
435
+ await this.randomDelay(1000, 2000); // 随机延时1-2秒
404
436
  }
405
- await Redis.set(sendMarkKey, '1', { EX: 3600 * 72 }); // 发送成功后设置标记
406
- await this.randomDelay(1000, 2000); // 随机延时1-2秒
407
437
  }
408
438
  else if (sendMode === 'MERGE') {
409
- await this.sendMessageApi(chatId, bot_id, chatType, messages);
410
- await Redis.set(sendMarkKey, '1', { EX: 3600 * 72 }); // 发送成功后设置标记
439
+ if (await this.sendMessageApi(chatId, bot_id, chatType, messages)) {
440
+ await Redis.set(sendMarkKey, '1', { EX: 3600 * 72 }); // 发送成功后设置标记
441
+ }
411
442
  }
412
443
  }
413
444
  }
@@ -423,21 +454,18 @@ class BiliTask {
423
454
  * @param message 消息内容
424
455
  */
425
456
  async sendMessageApi(chatId, bot_id, chatType, message) {
426
- if (chatType === 'group') {
427
- await (Bot[bot_id] ?? Bot)
428
- ?.pickGroup(String(chatId))
429
- .sendMsg(message) // 发送群聊
430
- .catch((error) => {
431
- global?.logger?.error(`群组[${chatId}]推送失败:${JSON.stringify(error)}`);
432
- });
457
+ try {
458
+ if (chatType === 'group') {
459
+ await (Bot[bot_id] ?? Bot)?.pickGroup(String(chatId)).sendMsg(message); // 发送群聊
460
+ }
461
+ else if (chatType === 'private') {
462
+ await (Bot[bot_id] ?? Bot)?.pickFriend(String(chatId)).sendMsg(message); // 发送好友私聊
463
+ }
464
+ return true; // 发送成功
433
465
  }
434
- else if (chatType === 'private') {
435
- await (Bot[bot_id] ?? Bot)
436
- ?.pickFriend(String(chatId))
437
- .sendMsg(message)
438
- .catch((error) => {
439
- global?.logger?.error(`用户[${chatId}]推送失败:${JSON.stringify(error)}`);
440
- }); // 发送好友私聊
466
+ catch (error) {
467
+ global?.logger?.error(`${chatType === 'group' ? '群聊' : '私聊'} ${chatId} 消息发送失败:${JSON.stringify(error)}`);
468
+ return false; // 发送失败
441
469
  }
442
470
  }
443
471
  /**
@@ -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 = { ds: [], wh: [0, 0, 0], of: [0, 0, 0] };
8
+ const dm_img_inter = `{ds:[],wh:[0,0,0],of:[0,0,0]}`;
9
9
  return {
10
- dm_img_list: String(dm_img_list).replace(/\s+/g, ''),
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: String(dm_img_inter).replace(/\s+/g, '')
13
+ dm_img_inter: dm_img_inter
14
14
  };
15
15
  }
16
16
 
@@ -51,25 +51,26 @@ 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(subInfoOfup.uid)) {
57
- resp = requestedDataOfUids.get(subInfoOfup.uid); // 从已请求的映射中获取响应数据
58
- const dynamicData = resp || [];
59
- dynamicList[subInfoOfup.uid] = dynamicData;
60
- }
61
- else {
62
- resp = await new WeiboWebDataFetcher().getBloggerDynamicList(subInfoOfup.uid); // 获取指定 uid 的动态列表
57
+ if (!requestedDataOfUids.has(uid)) {
58
+ resp = await new WeiboWebDataFetcher().getBloggerDynamicList(uid); // 获取指定 uid 的动态列表
63
59
  if (resp) {
64
- requestedDataOfUids.set(subInfoOfup.uid, resp); // 将响应数据存储到映射中
60
+ requestedDataOfUids.set(uid, resp); // 将响应数据存储到映射中
65
61
  const dynamicData = resp || [];
66
62
  dynamicList[subInfoOfup.uid] = dynamicData;
67
63
  }
68
64
  }
69
- const chatIds = Array.from(new Set([...Object((chatTypeMap.get(subInfoOfup.uid) && chatTypeMap.get(subInfoOfup.uid).chatIds) || []), chatId]));
70
- const bot_id = subInfoOfup.bot_id || [];
71
- const { name, type } = subInfoOfup;
72
- chatTypeMap.set(subInfoOfup.uid, { chatIds, bot_id, upName: name, type });
65
+ if (!chatTypeMap.has(uid)) {
66
+ chatTypeMap.set(uid, new Map());
67
+ }
68
+ const botChatMap = chatTypeMap.get(uid);
69
+ if (!botChatMap?.has(bot_id)) {
70
+ botChatMap?.set(bot_id, new Map());
71
+ }
72
+ const chatMap = botChatMap?.get(bot_id);
73
+ chatMap?.set(chatId, { upName: name, types: type });
73
74
  await this.randomDelay(1000, 4000); // 随机延时1-4秒
74
75
  }
75
76
  }
@@ -85,11 +86,11 @@ class WeiboTask {
85
86
  * @param weiboConfigData 微博配置数据
86
87
  */
87
88
  async makeUidDynamicDataMap(uidMap, dynamicList, now, dynamicTimeRange, weiboConfigData, messageMap) {
89
+ const printedList = new Set(); // 已打印的动态列表
88
90
  for (let [chatType, chatTypeMap] of uidMap) {
89
- for (let [key, value] of chatTypeMap) {
90
- const tempDynamicList = dynamicList[key] || [];
91
+ for (let [upUid, bot_idMap] of chatTypeMap) {
92
+ const tempDynamicList = dynamicList[upUid] || [];
91
93
  const willPushDynamicList = [];
92
- const printedList = new Set(); // 已打印的动态列表
93
94
  for (let dynamicItem of tempDynamicList) {
94
95
  let raw_post = dynamicItem || {};
95
96
  let user = raw_post?.mblog?.user || {};
@@ -103,19 +104,19 @@ class WeiboTask {
103
104
  logger.debug(`超过间隔,跳过 [ ${user?.screen_name} : ${user?.id} ] ${raw_post?.mblog?.created_at} 的动态`);
104
105
  continue;
105
106
  } // 如果超过推送时间间隔,跳过当前循环
106
- if (dynamicItem.type === 'DYNAMIC_TYPE_FORWARD' && !weiboConfigData.pushTransmit)
107
- continue; // 如果关闭了转发动态的推送,跳过当前循环
107
+ if (dynamicItem?.type === 'DYNAMIC_TYPE_FORWARD' && !weiboConfigData.pushTransmit) {
108
+ continue;
109
+ } // 如果关闭了转发动态的推送,跳过当前循环
108
110
  willPushDynamicList.push(dynamicItem);
109
111
  }
110
- printedList.clear();
111
- const pushMapInfo = value || {}; // 获取当前 uid 对应的推送信息
112
- const { chatIds, bot_id, upName, type } = pushMapInfo;
113
112
  // 遍历待推送的动态数组,发送动态消息
114
- for (let pushDynamicData of willPushDynamicList) {
115
- if (chatIds && chatIds.length) {
116
- for (let chatId of chatIds) {
117
- if (type && type.length && !type.includes(pushDynamicData.type))
118
- continue; // 如果禁用了某类型的动态推送,跳过当前循环
113
+ for (let [bot_id, chatIdMap] of bot_idMap) {
114
+ for (let [chatId, subUpInfo] of chatIdMap) {
115
+ const { upName, types } = subUpInfo;
116
+ for (let pushDynamicData of willPushDynamicList) {
117
+ if (types && types.length > 0 && !types.includes(pushDynamicData.type)) {
118
+ continue;
119
+ } // 如果禁用了某类型的动态推送,跳过当前循环
119
120
  await this.makeDynamicMessageMap(chatId, bot_id, upName, pushDynamicData, weiboConfigData, chatType, messageMap); // 发送动态消息
120
121
  await this.randomDelay(1000, 2000); // 随机延时1-2秒
121
122
  }
@@ -123,6 +124,7 @@ class WeiboTask {
123
124
  }
124
125
  }
125
126
  }
127
+ printedList.clear(); // 清空已打印的动态列表
126
128
  }
127
129
  /**
128
130
  * 渲染构建待发送的动态消息数据的映射数组
@@ -149,7 +151,18 @@ class WeiboTask {
149
151
  return; // 如果已经发送过,则直接返回
150
152
  if (!!weiboConfigData.pushMsgMode) {
151
153
  const { data, uid } = await WeiboQuery.formatDynamicData(pushDynamicData); // 处理动态数据
154
+ const getWhiteWords = weiboConfigData?.whiteWordslist;
152
155
  const getBanWords = weiboConfigData?.banWords;
156
+ if (getWhiteWords && Array.isArray(getWhiteWords) && getWhiteWords.length > 0) {
157
+ // 构建白名单关键字正则表达式,转义特殊字符
158
+ const whiteWords = new RegExp(getWhiteWords.map(word => word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|'), 'g');
159
+ if (!whiteWords.test(`${data?.title}${data?.content}`)) {
160
+ return; // 如果动态消息不在白名单中,则直接返回
161
+ }
162
+ }
163
+ else if (getWhiteWords && !Array.isArray(getWhiteWords)) {
164
+ logger.error(`微博动态:Yaml配置文件中,whiteWordslist 字段格式不是数组格式,请检查!`);
165
+ }
153
166
  if (getBanWords && Array.isArray(getBanWords) && getBanWords.length > 0) {
154
167
  // 构建屏蔽关键字正则表达式,转义特殊字符
155
168
  const banWords = new RegExp(getBanWords.map(word => word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|'), 'g');
@@ -189,7 +202,18 @@ class WeiboTask {
189
202
  if (dynamicMsg === undefined || dynamicMsg === 'continue') {
190
203
  return 'return'; // 如果动态消息构建失败或内部资源获取失败,则直接返回
191
204
  }
205
+ const getWhiteWords = weiboConfigData?.whiteWordslist;
192
206
  const getBanWords = weiboConfigData?.banWords;
207
+ if (getWhiteWords && Array.isArray(getWhiteWords) && getWhiteWords.length > 0) {
208
+ // 构建白名单关键字正则表达式,转义特殊字符
209
+ const whiteWords = new RegExp(getWhiteWords.map(word => word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|'), 'g');
210
+ if (!whiteWords.test(dynamicMsg.msg.join(''))) {
211
+ return; // 如果动态消息不在白名单中,则直接返回
212
+ }
213
+ }
214
+ else if (getWhiteWords && !Array.isArray(getWhiteWords)) {
215
+ logger.error(`微博动态:Yaml配置文件中,whiteWordslist 字段格式不是数组格式,请检查!`);
216
+ }
193
217
  if (getBanWords && Array.isArray(getBanWords) && getBanWords.length > 0) {
194
218
  // 构建屏蔽关键字正则表达式,转义特殊字符
195
219
  const banWords = new RegExp(getBanWords.map(word => word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|'), 'g');
@@ -346,15 +370,22 @@ class WeiboTask {
346
370
  LogMark.add('1');
347
371
  }
348
372
  if (sendMode === 'SINGLE') {
373
+ let allSent = true;
349
374
  for (let i = 0; i < messages.length; i++) {
350
- await this.sendMessageApi(chatId, bot_id, chatType, messages[i]);
375
+ if (!(await this.sendMessageApi(chatId, bot_id, chatType, messages[i]))) {
376
+ allSent = false;
377
+ break; // 如果有任何一条消息发送失败,停止发送后续消息
378
+ }
379
+ }
380
+ if (allSent) {
381
+ await Redis.set(sendMarkKey, '1', { EX: 3600 * 72 }); // 发送成功后设置标记
382
+ await this.randomDelay(1000, 2000); // 随机延时1-2秒
351
383
  }
352
- await Redis.set(sendMarkKey, '1', { EX: 3600 * 72 }); // 发送成功后设置标记
353
- await this.randomDelay(1000, 2000); // 随机延时1-2秒
354
384
  }
355
385
  else if (sendMode === 'MERGE') {
356
- await this.sendMessageApi(chatId, bot_id, chatType, messages);
357
- await Redis.set(sendMarkKey, '1', { EX: 3600 * 72 }); // 发送成功后设置标记
386
+ if (await this.sendMessageApi(chatId, bot_id, chatType, messages)) {
387
+ await Redis.set(sendMarkKey, '1', { EX: 3600 * 72 }); // 发送成功后设置标记
388
+ }
358
389
  }
359
390
  }
360
391
  }
@@ -370,21 +401,18 @@ class WeiboTask {
370
401
  * @param message 消息内容
371
402
  */
372
403
  async sendMessageApi(chatId, bot_id, chatType, message) {
373
- if (chatType === 'group') {
374
- await (Bot[bot_id] ?? Bot)
375
- ?.pickGroup(String(chatId))
376
- .sendMsg(message) // 发送群聊
377
- .catch(error => {
378
- global?.logger?.error(`群组[${chatId}]推送失败:${JSON.stringify(error)}`);
379
- });
404
+ try {
405
+ if (chatType === 'group') {
406
+ await (Bot[bot_id] ?? Bot)?.pickGroup(String(chatId)).sendMsg(message); // 发送群聊
407
+ }
408
+ else if (chatType === 'private') {
409
+ await (Bot[bot_id] ?? Bot)?.pickFriend(String(chatId)).sendMsg(message); // 发送好友私聊
410
+ }
411
+ return true; // 发送成功
380
412
  }
381
- else if (chatType === 'private') {
382
- await (Bot[bot_id] ?? Bot)
383
- ?.pickFriend(String(chatId))
384
- .sendMsg(message)
385
- .catch(error => {
386
- global?.logger?.error(`用户[${chatId}]推送失败:${JSON.stringify(error)}`);
387
- }); // 发送好友私聊
413
+ catch (error) {
414
+ global?.logger?.error(`${chatType === 'group' ? '群聊' : '私聊'} ${chatId} 消息发送失败:${JSON.stringify(error)}`);
415
+ return false; // 发送失败
388
416
  }
389
417
  }
390
418
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yz-yuki-plugin",
3
- "version": "2.0.7-1",
3
+ "version": "2.0.7-11",
4
4
  "description": "优纪插件,yunzaijs 关于 微博推送、B站推送 等功能的拓展插件",
5
5
  "author": "snowtafir",
6
6
  "type": "module",
@@ -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 };