yz-yuki-plugin 2.0.6-10 → 2.0.6-12

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,5 @@
1
1
  # 2.0.6
2
+ * 优化消息发送
2
3
  * 优化文字动态图片资源的发送
3
4
  * 依赖升级
4
5
  * 新增哔哩直播动态@全体成员功能,开启前请检查机器人管理员权限和所在聊天类型是否支持
@@ -10,9 +10,9 @@ pushTime: '*/23 * * * *'
10
10
 
11
11
  # 请求头 User-Agent 列表。如出现 -352 风控,可尝试更换请求头,请根据需要自行添加或修改。可设置多个请求头,每次重启后会随机选择一个。获取方法请浏览器自行搜索。
12
12
  userAgentList:
13
- - Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36
14
- #- Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:131.0) Gecko/20100101 Firefox/131.0
15
- #- Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 Edg/130.0.0.0
13
+ - Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36
14
+ #- Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:134.0) Gecko/20100101 Firefox/134.0
15
+ #- Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 Edg/132.0.0.0
16
16
 
17
17
  # 推送监测间隔,单位为秒,默认7200秒即2小时,即以当前时间为基准,监测过去2小时内的动态,并推送。取值范围:3600-36000秒,即过去的1-10h。应大于pushTime的周期。
18
18
  interval: 7200
@@ -68,3 +68,6 @@ liveAtAllGroupList:
68
68
 
69
69
  # 直播动态@全体成员的共享冷却时间CD,单位秒,默认 1800 秒(30分钟),即每个群聊30分钟内不论多少条直播动态,只会@一次。注意,qq群有 @全体成员 10次/日 的限制,所以请合理设置。
70
70
  liveAtAllCD: 1800
71
+
72
+ # 直播动态@全体成员失败时是否发送错误消息,默认 1 发送,0 不发送。开启liveAtAll后才会生效。
73
+ liveAtAllErrMsg: 1
@@ -5,7 +5,7 @@ import { BiliQuery } from '../models/bilibili/bilibili.main.query.js';
5
5
  import { BiliTask } from '../models/bilibili/bilibili.main.task.js';
6
6
  import Config from '../utils/config.js';
7
7
  import { BiliGetWebData } from '../models/bilibili/bilibili.main.get.web.data.js';
8
- import { applyLoginQRCode, pollLoginQRCode, saveLoginCookie, postGateway, exitBiliLogin, checkBiliLogin, readSavedCookieItems, saveLocalBiliCk, readSyncCookie, getNewTempCk, saveTempCk } from '../models/bilibili/bilibili.main.models.js';
8
+ import { readLoginCookie, applyLoginQRCode, pollLoginQRCode, saveLoginCookie, postGateway, exitBiliLogin, checkBiliLogin, readSavedCookieItems, saveLocalBiliCk, readSyncCookie, getNewTempCk, saveTempCk } from '../models/bilibili/bilibili.main.models.js';
9
9
 
10
10
  const message = new Messages('message');
11
11
  let biliPushData = Config.getConfigData('config', 'bilibili', 'push');
@@ -167,38 +167,44 @@ message.use(async (e) => {
167
167
  }, [/^(#|\/)(yuki|优纪)?(取消|删除|del|DEL)(b站|B站|bili|bilibili|哔哩|哔哩哔哩)推送\s*(视频\s*|图文\s*|文章\s*|转发\s*|直播\s*)*.*$/]);
168
168
  /** 扫码登录B站 */
169
169
  message.use(async (e) => {
170
- if (e.isMaster) {
171
- try {
172
- const token = await applyLoginQRCode(e);
173
- if (token) {
174
- let biliLoginCk = await pollLoginQRCode(e, token);
175
- if (biliLoginCk) {
176
- if (lodash.trim(biliLoginCk).length != 0) {
177
- await saveLoginCookie(e, biliLoginCk);
178
- e.reply(`get bilibili LoginCk:成功!`);
179
- const result = await postGateway(biliLoginCk); //激活ck
180
- const { code, data } = await result.data; // 解析校验结果
181
- switch (code) {
182
- case 0:
183
- global?.logger?.mark(`优纪插件:获取biliLoginCK,Gateway校验成功:${JSON.stringify(data)}`);
184
- break;
185
- default:
186
- global?.logger?.mark(`优纪插件:获取biliLoginCK,Gateway校验失败:${JSON.stringify(data)}`);
187
- break;
170
+ if (!e.isMaster) {
171
+ e.reply('未取得bot主人身份,无权限配置B站登录ck');
172
+ }
173
+ else {
174
+ const LoginCk = await readLoginCookie();
175
+ if (LoginCk) {
176
+ e.reply(`当前已有B站登录ck,请勿重复扫码!\n如需更换,请先删除当前登录再扫码:\n#yuki删除B站登录`);
177
+ }
178
+ else {
179
+ try {
180
+ const token = await applyLoginQRCode(e);
181
+ if (token) {
182
+ let biliLoginCk = await pollLoginQRCode(e, token);
183
+ if (biliLoginCk) {
184
+ if (lodash.trim(biliLoginCk).length != 0) {
185
+ await saveLoginCookie(e, biliLoginCk);
186
+ e.reply(`get bilibili LoginCk:成功!`);
187
+ const result = await postGateway(biliLoginCk); //激活ck
188
+ const { code, data } = await result.data; // 解析校验结果
189
+ switch (code) {
190
+ case 0:
191
+ global?.logger?.mark(`优纪插件:获取biliLoginCK,Gateway校验成功:${JSON.stringify(data)}`);
192
+ break;
193
+ default:
194
+ global?.logger?.mark(`优纪插件:获取biliLoginCK,Gateway校验失败:${JSON.stringify(data)}`);
195
+ break;
196
+ }
197
+ }
198
+ else {
199
+ e.reply(`get bilibili LoginCk:失败X﹏X`);
188
200
  }
189
- }
190
- else {
191
- e.reply(`get bilibili LoginCk:失败X﹏X`);
192
201
  }
193
202
  }
194
203
  }
204
+ catch (Error) {
205
+ global?.logger?.info(`yuki-plugin Login bilibili Failed:${Error}`);
206
+ }
195
207
  }
196
- catch (Error) {
197
- global?.logger?.info(`yuki-plugin Login bilibili Failed:${Error}`);
198
- }
199
- }
200
- else {
201
- e.reply('未取得bot主人身份,无权限配置B站登录ck');
202
208
  }
203
209
  }, [/^(#|\/)(yuki|优纪)?(扫码|添加|ADD|add)(b站|B站|bili|bilibili|哔哩|哔哩哔哩)登录$/]);
204
210
  /** 删除登陆的B站ck */
package/lib/apps/weibo.js CHANGED
@@ -58,7 +58,7 @@ message.use(async (e) => {
58
58
  }
59
59
  const userInfo = data.userInfo || {};
60
60
  let name = uid;
61
- if (userInfo.length !== 0) {
61
+ if (userInfo && userInfo.length !== 0) {
62
62
  name = userInfo.screen_name || uid;
63
63
  }
64
64
  // 添加新的推送数据
@@ -1,8 +1,8 @@
1
1
  import React from 'react';
2
- import { ConfigController, BOT_NAME } from 'yunzaijs';
2
+ import { BOT_NAME, ConfigController } from 'yunzaijs';
3
3
  import Config from '../../utils/config.js';
4
4
  import path from 'path';
5
- import { _paths, createRequire } from '../../utils/paths.js';
5
+ import { createRequire, _paths } from '../../utils/paths.js';
6
6
 
7
7
  // Footer.tsx
8
8
  const require = createRequire(import.meta.url);
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import { BOT_NAME, ConfigController } from 'yunzaijs';
3
3
  import Config from '../../utils/config.js';
4
4
  import path from 'path';
5
- import { _paths, createRequire } from '../../utils/paths.js';
5
+ import { createRequire, _paths } from '../../utils/paths.js';
6
6
 
7
7
  //help.tsx
8
8
  const require = createRequire(import.meta.url);
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import { BOT_NAME, ConfigController } from 'yunzaijs';
3
3
  import Config from '../../utils/config.js';
4
4
  import path from 'path';
5
- import { _paths, createRequire } from '../../utils/paths.js';
5
+ import { createRequire, _paths } from '../../utils/paths.js';
6
6
 
7
7
  const require = createRequire(import.meta.url);
8
8
  const botVersion = ConfigController.package?.version;
package/lib/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import chalk from 'chalk';
2
- import { applicationOptions, setBotTask, useEvent } from 'yunzaijs';
2
+ import { applicationOptions, useEvent, setBotTask } from 'yunzaijs';
3
3
  import Config from './utils/config.js';
4
4
  import path from 'path';
5
5
  import { _paths } from './utils/paths.js';
@@ -8,7 +8,7 @@ class BiliApi {
8
8
  this.USER_AGENT = BiliApi.BILIBILI_USER_AGENT;
9
9
  this.initialize();
10
10
  }
11
- static BILIBILI_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36';
11
+ static BILIBILI_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36';
12
12
  //初始化User-Agent
13
13
  async initialize() {
14
14
  await this.initUserAgent();
@@ -6,7 +6,7 @@ import { promisify } from 'node:util';
6
6
  import path from 'path';
7
7
  import QRCode from 'qrcode';
8
8
  import YAML from 'yaml';
9
- import { Segment, Redis } from 'yunzaijs';
9
+ import { Redis, Segment } from 'yunzaijs';
10
10
  import { renderPage } from '../../utils/image.js';
11
11
  import { _paths } from '../../utils/paths.js';
12
12
  import BiliApi from './bilibili.main.api.js';
@@ -144,7 +144,9 @@ async function checkBiliLogin(e) {
144
144
  let current_level = level_info?.current_level;
145
145
  let current_exp = level_info?.current_exp;
146
146
  let next_exp = level_info?.next_exp;
147
- e.reply(`~B站账号已登陆~\n昵称:${uname}\nuid:${mid}\n硬币:${money}\n经验等级:${current_level}\n当前经验值exp:${current_exp}\n下一等级所需exp:${next_exp}`);
147
+ const LoginCookieTTL = await readLoginCookieTTL();
148
+ const LoginCookieTTLStr = LoginCookieTTL === -1 ? '永久' : LoginCookieTTL === -2 ? '未登录或登录状态无效' : `${new Date(Date.now() + LoginCookieTTL * 1000).toLocaleString()}`;
149
+ e.reply(`~B站账号已登陆~,有效期至:${LoginCookieTTLStr}。\n昵称:${uname}\nuid:${mid}\n硬币:${money}\n经验等级:${current_level}\n当前经验值exp:${current_exp}\n下一等级所需exp:${next_exp}`);
148
150
  }
149
151
  else {
150
152
  // 处理其他情况
@@ -228,6 +230,18 @@ async function readLoginCookie() {
228
230
  const tempCk = await Redis.get(CK_KEY);
229
231
  return tempCk ? tempCk : '';
230
232
  }
233
+ /** 读取扫码登陆后缓存的cookie的有效时间 */
234
+ async function readLoginCookieTTL() {
235
+ const CK_KEY = 'Yz:yuki:bili:loginCookie';
236
+ const tempCk = await Redis.get(CK_KEY);
237
+ if (tempCk) {
238
+ const LoginCookieTTL = await Redis.ttl(CK_KEY);
239
+ return LoginCookieTTL;
240
+ }
241
+ else {
242
+ return -2;
243
+ }
244
+ }
231
245
  /** 读取手动绑定的B站ck */
232
246
  async function readLocalBiliCk() {
233
247
  const dir = path.join(_paths.root, 'data/yuki-plugin/');
@@ -473,4 +487,4 @@ async function cookieWithBiliTicket(cookie) {
473
487
  }
474
488
  }
475
489
 
476
- export { applyLoginQRCode, checkBiliLogin, cookieWithBiliTicket, exitBiliLogin, genUUID, gen_b_lsid, getNewTempCk, get_buvid_fp, pollLoginQRCode, postGateway, readSavedCookieItems, readSavedCookieOtherItems, readSyncCookie, readTempCk, saveLocalBiliCk, saveLoginCookie, saveTempCk };
490
+ export { applyLoginQRCode, checkBiliLogin, cookieWithBiliTicket, exitBiliLogin, genUUID, gen_b_lsid, getNewTempCk, get_buvid_fp, pollLoginQRCode, postGateway, readLoginCookie, readSavedCookieItems, readSavedCookieOtherItems, readSyncCookie, readTempCk, saveLocalBiliCk, saveLoginCookie, saveTempCk };
@@ -245,7 +245,7 @@ class BiliQuery {
245
245
  };
246
246
  /**获取完整B站文章内容
247
247
  * @param postUrl - 文章链接: https://www.bilibili.com/read/cvxxxx 或者 https://www.bilibili.com/opus/xxxx
248
- * @returns {Json} 完整的B站文章内容json数据
248
+ * @returns {JSON} 完整的B站文章内容json数据
249
249
  */
250
250
  static async getFullArticleContent(postUrl) {
251
251
  let { cookie } = await readSyncCookie();
@@ -396,7 +396,7 @@ class BiliQuery {
396
396
  let desc, msg = [], pics = [], author, majorType, content, dynamicTitle;
397
397
  let title = `B站【${upName}】动态推送:\n`;
398
398
  let dynamicType = data.type;
399
- switch (data.type) {
399
+ switch (dynamicType) {
400
400
  case 'DYNAMIC_TYPE_AV':
401
401
  // 处理视频动态
402
402
  desc = data?.modules?.module_dynamic?.major?.archive;
@@ -33,6 +33,9 @@ class BiliTask {
33
33
  }
34
34
  return resjson;
35
35
  }
36
+ /**
37
+ * 执行动态推送任务
38
+ */
36
39
  async runTask() {
37
40
  let biliConfigData = await Config.getUserConfig('bilibili', 'config');
38
41
  let biliPushData = await Config.getUserConfig('bilibili', 'push');
@@ -42,7 +45,10 @@ class BiliTask {
42
45
  const dynamicList = {}; // 存放获取的所有动态,键为 uid,值为动态数组
43
46
  await this.processBiliData(biliPushData, biliConfigData, uidMap, dynamicList);
44
47
  let now = Date.now() / 1000; // 时间戳(秒)
45
- await this.pushDynamicMessages(uidMap, dynamicList, now, interval, biliConfigData);
48
+ // 定义待推送动态消息映射
49
+ const messageMap = new Map();
50
+ await this.makeUidDynamicDataMap(uidMap, dynamicList, now, interval, biliConfigData, messageMap);
51
+ await this.sendDynamicMessage(messageMap, biliConfigData);
46
52
  }
47
53
  /**
48
54
  * 处理Bilibili数据,获取动态列表并构建 uid 映射
@@ -102,17 +108,17 @@ class BiliTask {
102
108
  }
103
109
  }
104
110
  }
105
- requestedDataOfUids.clear(); // 清空已请求的映射
111
+ requestedDataOfUids.clear(); // 清空已请求的 uid 映射
106
112
  }
107
113
  /**
108
- * 推送动态消息
114
+ * 构建uid对应动态数据映射
109
115
  * @param uidMap uid 映射
110
116
  * @param dynamicList 动态列表
111
117
  * @param now 当前时间戳
112
118
  * @param interval 推送间隔时间
113
119
  * @param biliConfigData Bilibili配置数据
114
120
  */
115
- async pushDynamicMessages(uidMap, dynamicList, now, interval, biliConfigData) {
121
+ async makeUidDynamicDataMap(uidMap, dynamicList, now, interval, biliConfigData, messageMap) {
116
122
  for (let [chatType, chatTypeMap] of uidMap) {
117
123
  for (let [key, value] of chatTypeMap) {
118
124
  const tempDynamicList = dynamicList[key] || [];
@@ -143,7 +149,7 @@ class BiliTask {
143
149
  for (let chatId of chatIds) {
144
150
  if (type && type.length && !type.includes(pushDynamicData.type))
145
151
  continue; // 如果禁用了某类型的动态推送,跳过当前循环
146
- await this.sendDynamic(chatId, bot_id, upName, pushDynamicData, biliConfigData, chatType); // 发送动态消息
152
+ await this.makeDynamicMessageMap(chatId, bot_id, upName, pushDynamicData, biliConfigData, chatType, messageMap); // 发送动态消息
147
153
  await this.randomDelay(1000, 2000); // 随机延时1-2秒
148
154
  }
149
155
  }
@@ -152,15 +158,15 @@ class BiliTask {
152
158
  }
153
159
  }
154
160
  /**
155
- * 发送动态消息
161
+ * 渲染构建待发送的动态消息数据的映射数组
156
162
  * @param chatId 聊天 ID
157
163
  * @param bot_id 机器人 ID
158
- * @param upName 用户名
164
+ * @param upName up主用户名
159
165
  * @param pushDynamicData 推送动态数据
160
166
  * @param biliConfigData 哔哩配置数据
161
167
  * @param chatType 聊天类型
162
168
  */
163
- async sendDynamic(chatId, bot_id, upName, pushDynamicData, biliConfigData, chatType) {
169
+ async makeDynamicMessageMap(chatId, bot_id, upName, pushDynamicData, biliConfigData, chatType, messageMap) {
164
170
  const id_str = pushDynamicData.id_str;
165
171
  let sended = null, markKey = '';
166
172
  if (chatType === 'group') {
@@ -173,12 +179,9 @@ class BiliTask {
173
179
  }
174
180
  if (sended)
175
181
  return; // 如果已经发送过,则直接返回
176
- let liveAtAll = !!biliConfigData.liveAtAll === true ? true : false; // 直播动态是否@全体成员,默认false
177
- let liveAtAllCD = biliConfigData.liveAtAllCD || 1800; // 直播动态@全体成员 冷却时间CD,默认 30 分钟
178
- let liveAtAllMark = await Redis.get(`${markKey}${chatId}:liveAtAllMark`); // 直播动态@全体成员标记,默认 0
179
- // 直播动态@全体成员的群组列表,默认空数组,为空则不进行@全体成员操作
180
- let liveAtAllGroupList = new Set(Array.isArray(biliConfigData?.liveAtAllGroupList) ? Array.from(biliConfigData.liveAtAllGroupList).map(item => String(item)) : []);
181
- if (!!biliConfigData.pushMsgMode) {
182
+ // 判断推送内容模式
183
+ let pushMsgMode = !!biliConfigData.pushMsgMode === false ? 'TEXT' : 'PIC'; // 是否启用图文模式,默认为 PIC 截图模式
184
+ if (pushMsgMode === 'PIC') {
182
185
  const { data, uid } = await BiliQuery.formatDynamicData(pushDynamicData); // 处理动态数据
183
186
  const extentData = { ...data };
184
187
  const eval2 = eval;
@@ -206,29 +209,11 @@ class BiliTask {
206
209
  };
207
210
  let imgs = await this.renderDynamicCard(uid, renderData, ScreenshotOptionsData);
208
211
  if (!imgs)
209
- return;
210
- Redis.set(`${markKey}${chatId}:${id_str}`, '1', { EX: 3600 * 72 }); // 设置已发送标记
211
- global?.logger?.mark('优纪插件:B站动态执行推送');
212
- if (liveAtAll && !liveAtAllMark && extentData?.type === 'DYNAMIC_TYPE_LIVE_RCMD' && liveAtAllGroupList.has(String(chatId))) {
213
- try {
214
- await this.sendMessage(chatId, bot_id, chatType, Segment.at('all'));
215
- await Redis.set(`${markKey}${chatId}:liveAtAllMark`, 1, { EX: liveAtAllCD }); // 设置直播动态@全体成员标记为 1
216
- }
217
- catch (error) {
218
- logger.error(`直播动态发送@全体成员失败,请检查 <机器人> 是否有 [管理员权限] 或 [聊天平台是否支持] :${error}`);
219
- await this.sendMessage(chatId, bot_id, chatType, ['直播动态发送@全体成员失败,请检查权限或平台是否支持']);
220
- }
221
- }
222
- for (let i = 0; i < imgs.length; i++) {
223
- const image = imgs[i];
224
- await this.sendMessage(chatId, bot_id, chatType, Segment.image(image));
225
- await this.randomDelay(1000, 2000); // 随机延时1-2秒
226
- }
227
- await new Promise(resolve => setTimeout(resolve, 1000)); // 休眠1秒
212
+ return; // 如果渲染失败,则直接返回
213
+ await this.addMessageToMap(messageMap, chatType, bot_id, chatId, 'SINGLE', id_str, extentData?.type, imgs.map(img => Segment.image(img)));
228
214
  }
229
215
  else {
230
216
  const dynamicMsg = await BiliQuery.formatTextDynamicData(upName, pushDynamicData, false, biliConfigData); // 构建图文动态消息
231
- Redis.set(`${markKey}${chatId}:${id_str}`, '1', { EX: 3600 * 72 }); // 设置已发送标记
232
217
  if (dynamicMsg === undefined || dynamicMsg === 'continue') {
233
218
  return 'return'; // 如果动态消息构建失败,则直接返回
234
219
  }
@@ -240,40 +225,15 @@ class BiliTask {
240
225
  }
241
226
  }
242
227
  let mergeTextPic = !!biliConfigData.mergeTextPic === false ? false : true; // 是否合并文本和图片,默认为 true
228
+ //开启了合并文本和图片
243
229
  if (mergeTextPic === true) {
244
230
  const mergeMsg = [...dynamicMsg.msg, ...dynamicMsg.pics];
245
- if (liveAtAll && !liveAtAllMark && dynamicMsg.dynamicType === 'DYNAMIC_TYPE_LIVE_RCMD' && liveAtAllGroupList.has(String(chatId))) {
246
- try {
247
- await this.sendMessage(chatId, bot_id, chatType, Segment.at('all'));
248
- await Redis.set(`${markKey}${chatId}:liveAtAllMark`, 1, { EX: liveAtAllCD }); // 设置直播动态@全体成员标记为 1
249
- }
250
- catch (error) {
251
- global?.logger.error(`直播动态发送@全体成员失败,请检查 <机器人> 是否有 [管理员权限] 或 [聊天平台是否支持] :${error}`);
252
- await this.sendMessage(chatId, bot_id, chatType, ['直播动态发送@全体成员失败,请检查权限或平台是否支持']);
253
- }
254
- }
255
- await this.sendMessage(chatId, bot_id, chatType, mergeMsg);
231
+ await this.addMessageToMap(messageMap, chatType, bot_id, chatId, 'MERGE', id_str, dynamicMsg.dynamicType, mergeMsg);
256
232
  }
257
233
  else {
258
- if (liveAtAll && !liveAtAllMark && dynamicMsg.dynamicType === 'DYNAMIC_TYPE_LIVE_RCMD' && liveAtAllGroupList.has(String(chatId))) {
259
- try {
260
- await this.sendMessage(chatId, bot_id, chatType, Segment.at('all'));
261
- await Redis.set(`${markKey}${chatId}:liveAtAllMark`, 1, { EX: liveAtAllCD }); // 设置直播动态@全体成员标记为 1
262
- }
263
- catch (error) {
264
- global?.logger.error(`直播动态发送@全体成员失败,请检查 <机器人> 是否有 [管理员权限] 或 [聊天平台是否支持] :${error}`);
265
- await this.sendMessage(chatId, bot_id, chatType, ['直播动态发送@全体成员失败,请检查权限或平台是否支持']);
266
- }
267
- }
268
- await this.sendMessage(chatId, bot_id, chatType, dynamicMsg.msg);
269
- const pics = dynamicMsg.pics;
270
- if (pics && pics.length > 0) {
271
- for (let i = 0; i < pics.length; i++) {
272
- await this.sendMessage(chatId, bot_id, chatType, pics[i]);
273
- await this.randomDelay(1000, 2000); // 随机延时1-2秒
274
- }
275
- }
276
- await new Promise(resolve => setTimeout(resolve, 1000));
234
+ //不合并文本和图片
235
+ await this.addMessageToMap(messageMap, chatType, bot_id, chatId, 'MERGE', id_str, dynamicMsg.dynamicType, dynamicMsg.msg);
236
+ await this.addMessageToMap(messageMap, chatType, bot_id, chatId, 'SINGLE', id_str, dynamicMsg.dynamicType, dynamicMsg.pics);
277
237
  }
278
238
  }
279
239
  }
@@ -354,13 +314,105 @@ class BiliTask {
354
314
  }
355
315
  }
356
316
  /**
357
- * 发送消息
317
+ * 收集消息映射
318
+ * @param messageMap 消息映射
319
+ * @param chatType 聊天类型
320
+ * @param bot_id 机器人 ID
321
+ * @param chatId 聊天 ID
322
+ * @param sendMode 发送模式: SINGLE 逐条发送,MERGE 合并发送
323
+ * @param dynamicUUid_str 动态 UUID
324
+ * @param dynamicType 动态类型
325
+ * @param message 消息内容
326
+ */
327
+ async addMessageToMap(messageMap, chatType, bot_id, chatId, sendMode, dynamicUUid_str, dynamicType, messages) {
328
+ if (!messageMap.has(chatType)) {
329
+ messageMap.set(chatType, new Map());
330
+ }
331
+ const botMap = messageMap.get(chatType);
332
+ if (!botMap?.has(bot_id)) {
333
+ botMap?.set(bot_id, new Map());
334
+ }
335
+ const chatMap = botMap?.get(bot_id);
336
+ if (!chatMap?.has(chatId)) {
337
+ chatMap?.set(chatId, []);
338
+ }
339
+ chatMap?.get(chatId)?.push({ sendMode, dynamicUUid_str, dynamicType, messages });
340
+ }
341
+ /**
342
+ * 推送动态消息
343
+ * @param messageMap 消息映射
344
+ * @param biliConfigData 哔哩配置数据
345
+ */
346
+ async sendDynamicMessage(messageMap, biliConfigData) {
347
+ let liveAtAll = !!biliConfigData.liveAtAll === true ? true : false; // 直播动态是否@全体成员,默认false
348
+ let liveAtAllCD = biliConfigData.liveAtAllCD || 1800; // 直播动态@全体成员 冷却时间CD,默认 30 分钟
349
+ // 直播动态@全体成员的群组/好友列表,默认空数组,为空则不进行@全体成员操作
350
+ let liveAtAllGroupList = new Set(Array.isArray(biliConfigData?.liveAtAllGroupList) ? Array.from(biliConfigData.liveAtAllGroupList).map(item => String(item)) : []);
351
+ const LogMark = new Set(); // 日志mark
352
+ for (const [chatType, botMap] of messageMap) {
353
+ for (const [bot_id, chatMap] of botMap) {
354
+ for (const [chatId, messageCombinationList] of chatMap) {
355
+ // 遍历组合消息
356
+ for (const messageCombination of messageCombinationList) {
357
+ const { sendMode, dynamicUUid_str, dynamicType, messages } = messageCombination;
358
+ let sended = null;
359
+ let markKey = '';
360
+ if (chatType === 'group') {
361
+ markKey = this.groupKey;
362
+ sended = await Redis.get(`${markKey}${chatId}:${dynamicUUid_str}`);
363
+ }
364
+ else if (chatType === 'private') {
365
+ markKey = this.privateKey;
366
+ sended = await Redis.get(`${markKey}${chatId}:${dynamicUUid_str}`);
367
+ }
368
+ const sendMarkKey = `${markKey}${chatId}:${dynamicUUid_str}`;
369
+ if (sended) {
370
+ continue; // 如果已经发送过,则直接跳过
371
+ }
372
+ if (!LogMark.has('1')) {
373
+ global?.logger?.mark('优纪插件: B站动态执行推送');
374
+ LogMark.add('1');
375
+ }
376
+ let liveAtAllMark = await Redis.get(`${markKey}${chatId}:liveAtAllMark`); // 直播动态@全体成员标记,默认 0
377
+ // 如果开启了直播动态@全体成员
378
+ if (liveAtAll && !liveAtAllMark && dynamicType === 'DYNAMIC_TYPE_LIVE_RCMD' && liveAtAllGroupList.has(String(chatId))) {
379
+ try {
380
+ await this.sendMessageApi(chatId, bot_id, chatType, [Segment.at('all')]);
381
+ await Redis.set(`${markKey}${chatId}:liveAtAllMark`, 1, { EX: liveAtAllCD }); // 设置直播动态@全体成员标记为 1
382
+ }
383
+ catch (error) {
384
+ logger.error(`直播动态发送@全体成员失败,请检查 <机器人> 是否有 [管理员权限] 或 [聊天平台是否支持] :${error}`);
385
+ let liveAtAllErrMsg = !!biliConfigData.liveAtAllErrMsg === false ? false : true; // 直播动态@全体成员失败是否发送错误提示消息,默认 false
386
+ if (liveAtAllErrMsg) {
387
+ await this.sendMessageApi(chatId, bot_id, chatType, ['直播动态发送@全体成员失败,请检查权限或平台是否支持']);
388
+ }
389
+ }
390
+ }
391
+ if (sendMode === 'SINGLE') {
392
+ for (let i = 0; i < messages.length; i++) {
393
+ await this.sendMessageApi(chatId, bot_id, chatType, messages[i]);
394
+ }
395
+ await Redis.set(sendMarkKey, '1', { EX: 3600 * 72 }); // 发送成功后设置标记
396
+ await this.randomDelay(1000, 2000); // 随机延时1-2秒
397
+ }
398
+ else if (sendMode === 'MERGE') {
399
+ await this.sendMessageApi(chatId, bot_id, chatType, messages);
400
+ await Redis.set(sendMarkKey, '1', { EX: 3600 * 72 }); // 发送成功后设置标记
401
+ }
402
+ }
403
+ }
404
+ }
405
+ }
406
+ LogMark.clear(); // 清空日志mark
407
+ }
408
+ /**
409
+ * 发送消息api
358
410
  * @param chatId 聊天 ID
359
411
  * @param bot_id 机器人 ID
360
412
  * @param chatType 聊天类型
361
413
  * @param message 消息内容
362
414
  */
363
- async sendMessage(chatId, bot_id, chatType, message) {
415
+ async sendMessageApi(chatId, bot_id, chatType, message) {
364
416
  if (chatType === 'group') {
365
417
  await (Bot[bot_id] ?? Bot)
366
418
  ?.pickGroup(String(chatId))
@@ -186,7 +186,7 @@ class WeiboQuery {
186
186
  let info = raw_post?.mblog || raw_post;
187
187
  let retweeted = info && info.retweeted_status ? true : false; //是否为转发动态
188
188
  let pic_num = retweeted ? info?.retweeted_status?.pic_num : info?.pic_num;
189
- let type = this.MakeCategory(raw_post);
189
+ let dynamicType = this.MakeCategory(raw_post);
190
190
  /**获取动态全文 */
191
191
  if (info?.isLongText || pic_num > 9) {
192
192
  const res = await fetch(`https://m.weibo.cn/detail/${info.mid}`, { headers: WeiboApi.WEIBO_HEADERS });
@@ -207,7 +207,7 @@ class WeiboQuery {
207
207
  let detail_url = `https://weibo.com/${info?.user?.id}/${info?.bid}`;
208
208
  let title = `微博【${upName}】动态推送:\n`;
209
209
  const dynamicPicCountLimit = setData.pushPicCountLimit || 3;
210
- switch (type) {
210
+ switch (dynamicType) {
211
211
  case 'DYNAMIC_TYPE_AV':
212
212
  if (!info)
213
213
  return;
@@ -223,7 +223,7 @@ class WeiboQuery {
223
223
  `时间:${created_time ? moment(created_time).format('YYYY年MM月DD日 HH:mm:ss') : ''}`
224
224
  ];
225
225
  pics = [cover_img];
226
- return { msg, pics };
226
+ return { msg, pics, dynamicType };
227
227
  case 'DYNAMIC_TYPE_DRAW':
228
228
  raw_pics_list = retweeted ? info?.retweeted_status?.pics || [] : info?.pics || [];
229
229
  if (!info && !raw_pics_list)
@@ -243,7 +243,7 @@ class WeiboQuery {
243
243
  `链接:${detail_url}\n`,
244
244
  `时间:${created_time ? moment(created_time).format('YYYY年MM月DD日 HH:mm:ss') : ''}`
245
245
  ];
246
- return { msg, pics };
246
+ return { msg, pics, dynamicType };
247
247
  case 'DYNAMIC_TYPE_ARTICLE':
248
248
  if (!info)
249
249
  return;
@@ -263,7 +263,7 @@ class WeiboQuery {
263
263
  `链接:${detail_url}\n`,
264
264
  `时间:${created_time ? moment(created_time).format('YYYY年MM月DD日 HH:mm:ss') : ''}`
265
265
  ];
266
- return { msg, pics };
266
+ return { msg, pics, dynamicType };
267
267
  case 'DYNAMIC_TYPE_FORWARD':
268
268
  if (!info)
269
269
  return;
@@ -290,9 +290,9 @@ class WeiboQuery {
290
290
  '\n---以下为转发内容---\n',
291
291
  ...origContent
292
292
  ];
293
- return { msg, pics };
293
+ return { msg, pics, dynamicType };
294
294
  default:
295
- logger?.mark(`未处理的微博推送【${upName}】:${type}`);
295
+ logger?.mark(`未处理的微博推送【${upName}】:${dynamicType}`);
296
296
  return 'continue';
297
297
  }
298
298
  }
@@ -15,6 +15,9 @@ class WeiboTask {
15
15
  this.groupKey = 'Yz:yuki:weibo:upPush:group:';
16
16
  this.privateKey = 'Yz:yuki:weibo:upPush:private:';
17
17
  }
18
+ /**
19
+ * 执行动态推送任务
20
+ */
18
21
  async runTask() {
19
22
  let weiboConfigData = await Config.getUserConfig('weibo', 'config');
20
23
  let weiboPushData = await Config.getUserConfig('weibo', 'push');
@@ -24,7 +27,10 @@ class WeiboTask {
24
27
  const dynamicList = {}; // 存放获取的所有动态,键为 uid,值为动态数组
25
28
  await this.processWeiboData(weiboPushData, uidMap, dynamicList);
26
29
  let now = Date.now() / 1000; // 当前时间戳(秒)
27
- await this.pushDynamicMessages(uidMap, dynamicList, now, interval, weiboConfigData);
30
+ // 定义待推送动态消息映射
31
+ const messageMap = new Map();
32
+ await this.makeUidDynamicDataMap(uidMap, dynamicList, now, interval, weiboConfigData, messageMap);
33
+ await this.sendDynamicMessage(messageMap, weiboConfigData);
28
34
  }
29
35
  /**
30
36
  * 处理微博数据,获取动态列表并构建 uid 映射
@@ -71,14 +77,14 @@ class WeiboTask {
71
77
  requestedDataOfUids.clear(); // 清空已请求的映射
72
78
  }
73
79
  /**
74
- * 推送动态消息
80
+ * 构建uid对应动态数据映射
75
81
  * @param uidMap uid 映射
76
82
  * @param dynamicList 动态列表
77
83
  * @param now 当前时间戳
78
84
  * @param interval 推送间隔时间
79
85
  * @param weiboConfigData 微博配置数据
80
86
  */
81
- async pushDynamicMessages(uidMap, dynamicList, now, interval, weiboConfigData) {
87
+ async makeUidDynamicDataMap(uidMap, dynamicList, now, interval, weiboConfigData, messageMap) {
82
88
  for (let [chatType, chatTypeMap] of uidMap) {
83
89
  for (let [key, value] of chatTypeMap) {
84
90
  const tempDynamicList = dynamicList[key] || [];
@@ -110,7 +116,7 @@ class WeiboTask {
110
116
  for (let chatId of chatIds) {
111
117
  if (type && type.length && !type.includes(pushDynamicData.type))
112
118
  continue; // 如果禁用了某类型的动态推送,跳过当前循环
113
- await this.sendDynamic(chatId, bot_id, upName, pushDynamicData, weiboConfigData, chatType); // 发送动态消息
119
+ await this.makeDynamicMessageMap(chatId, bot_id, upName, pushDynamicData, weiboConfigData, chatType, messageMap); // 发送动态消息
114
120
  await this.randomDelay(1000, 2000); // 随机延时1-2秒
115
121
  }
116
122
  }
@@ -119,15 +125,16 @@ class WeiboTask {
119
125
  }
120
126
  }
121
127
  /**
122
- * 发送动态消息
128
+ * 渲染构建待发送的动态消息数据的映射数组
123
129
  * @param chatId 聊天 ID
124
130
  * @param bot_id 机器人 ID
125
- * @param upName 用户名
131
+ * @param upName 博主用户名
126
132
  * @param pushDynamicData 推送动态数据
127
133
  * @param weiboConfigData 微博配置数据
128
134
  * @param chatType 聊天类型
135
+ * @param messageMap 待发送的动态消息映射
129
136
  */
130
- async sendDynamic(chatId, bot_id, upName, pushDynamicData, weiboConfigData, chatType) {
137
+ async makeDynamicMessageMap(chatId, bot_id, upName, pushDynamicData, weiboConfigData, chatType, messageMap) {
131
138
  const id_str = WeiboQuery.getDynamicId(pushDynamicData); // 获取动态 ID
132
139
  let sended = null, markKey = '';
133
140
  if (chatType === 'group') {
@@ -168,19 +175,11 @@ class WeiboTask {
168
175
  };
169
176
  let imgs = await this.renderDynamicCard(uid, renderData, ScreenshotOptionsData);
170
177
  if (!imgs)
171
- return;
172
- Redis.set(`${markKey}${chatId}:${id_str}`, '1', { EX: 3600 * 72 }); // 设置已发送标记
173
- global?.logger?.mark('优纪插件:微博动态执行推送');
174
- for (let i = 0; i < imgs.length; i++) {
175
- const image = imgs[i];
176
- await this.sendMessage(chatId, bot_id, chatType, Segment.image(image));
177
- await this.randomDelay(1000, 2000); // 随机延时1-2秒
178
- }
179
- await new Promise(resolve => setTimeout(resolve, 1000));
178
+ return; // 如果渲染失败,则直接返回
179
+ await this.addMessageToMap(messageMap, chatType, bot_id, chatId, 'SINGLE', id_str, extentData?.type, imgs.map(img => Segment.image(img)));
180
180
  }
181
181
  else {
182
182
  const dynamicMsg = await WeiboQuery.formatTextDynamicData(upName, pushDynamicData, false, weiboConfigData); //构建文字动态消息
183
- Redis.set(`${markKey}${chatId}:${id_str}`, '1', { EX: 3600 * 72 }); // 设置已发送标记
184
183
  if (dynamicMsg === undefined || dynamicMsg === 'continue') {
185
184
  return 'return'; // 如果动态消息构建失败或内部资源获取失败,则直接返回
186
185
  }
@@ -192,20 +191,15 @@ class WeiboTask {
192
191
  }
193
192
  }
194
193
  let mergeTextPic = !!weiboConfigData.mergeTextPic === false ? false : true; // 是否合并文字和图片,默认为 true
195
- if (mergeTextPic) {
194
+ //开启了合并文本和图片
195
+ if (mergeTextPic === true) {
196
196
  const mergeMsg = [...dynamicMsg.msg, ...dynamicMsg.pics];
197
- await this.sendMessage(chatId, bot_id, chatType, mergeMsg);
197
+ await this.addMessageToMap(messageMap, chatType, bot_id, chatId, 'MERGE', id_str, dynamicMsg.dynamicType, mergeMsg);
198
198
  }
199
199
  else {
200
- await this.sendMessage(chatId, bot_id, chatType, dynamicMsg.msg);
201
- const pics = dynamicMsg.pics;
202
- if (pics && pics.length > 0) {
203
- for (let i = 0; i < pics.length; i++) {
204
- await this.sendMessage(chatId, bot_id, chatType, pics[i]);
205
- await this.randomDelay(1000, 2000); // 随机延时1-2秒
206
- }
207
- }
208
- await new Promise(resolve => setTimeout(resolve, 1000));
200
+ //不合并文本和图片
201
+ await this.addMessageToMap(messageMap, chatType, bot_id, chatId, 'MERGE', id_str, dynamicMsg.dynamicType, dynamicMsg.msg);
202
+ await this.addMessageToMap(messageMap, chatType, bot_id, chatId, 'SINGLE', id_str, dynamicMsg.dynamicType, dynamicMsg.pics);
209
203
  }
210
204
  }
211
205
  }
@@ -286,13 +280,86 @@ class WeiboTask {
286
280
  }
287
281
  }
288
282
  /**
289
- * 发送消息
283
+ * 收集消息映射
284
+ * @param messageMap 消息映射
285
+ * @param chatType 聊天类型
286
+ * @param bot_id 机器人 ID
287
+ * @param chatId 聊天 ID
288
+ * @param sendMode 发送模式: SINGLE 逐条发送,MERGE 合并发送
289
+ * @param dynamicUUid_str 动态 UUID
290
+ * @param dynamicType 动态类型
291
+ * @param message 消息内容
292
+ */
293
+ async addMessageToMap(messageMap, chatType, bot_id, chatId, sendMode, dynamicUUid_str, dynamicType, messages) {
294
+ if (!messageMap.has(chatType)) {
295
+ messageMap.set(chatType, new Map());
296
+ }
297
+ const botMap = messageMap.get(chatType);
298
+ if (!botMap?.has(bot_id)) {
299
+ botMap?.set(bot_id, new Map());
300
+ }
301
+ const chatMap = botMap?.get(bot_id);
302
+ if (!chatMap?.has(chatId)) {
303
+ chatMap?.set(chatId, []);
304
+ }
305
+ chatMap?.get(chatId)?.push({ sendMode, dynamicUUid_str, dynamicType, messages });
306
+ }
307
+ /**
308
+ * 推送动态消息
309
+ * @param messageMap 消息映射
310
+ * @param biliConfigData 微博配置数据
311
+ */
312
+ async sendDynamicMessage(messageMap, weiboConfigData) {
313
+ const LogMark = new Set(); // 日志mark
314
+ for (const [chatType, botMap] of messageMap) {
315
+ for (const [bot_id, chatMap] of botMap) {
316
+ for (const [chatId, messageCombinationList] of chatMap) {
317
+ // 遍历组合消息
318
+ for (const messageCombination of messageCombinationList) {
319
+ const { sendMode, dynamicUUid_str, dynamicType, messages } = messageCombination;
320
+ let sended = null;
321
+ let markKey = '';
322
+ if (chatType === 'group') {
323
+ markKey = this.groupKey;
324
+ sended = await Redis.get(`${markKey}${chatId}:${dynamicUUid_str}`);
325
+ }
326
+ else if (chatType === 'private') {
327
+ markKey = this.privateKey;
328
+ sended = await Redis.get(`${markKey}${chatId}:${dynamicUUid_str}`);
329
+ }
330
+ const sendMarkKey = `${markKey}${chatId}:${dynamicUUid_str}`;
331
+ if (sended) {
332
+ continue; // 如果已经发送过,则直接跳过
333
+ }
334
+ if (!LogMark.has('1')) {
335
+ global?.logger?.mark('优纪插件: B站动态执行推送');
336
+ LogMark.add('1');
337
+ }
338
+ if (sendMode === 'SINGLE') {
339
+ for (let i = 0; i < messages.length; i++) {
340
+ await this.sendMessageApi(chatId, bot_id, chatType, messages[i]);
341
+ }
342
+ await Redis.set(sendMarkKey, '1', { EX: 3600 * 72 }); // 发送成功后设置标记
343
+ await this.randomDelay(1000, 2000); // 随机延时1-2秒
344
+ }
345
+ else if (sendMode === 'MERGE') {
346
+ await this.sendMessageApi(chatId, bot_id, chatType, messages);
347
+ await Redis.set(sendMarkKey, '1', { EX: 3600 * 72 }); // 发送成功后设置标记
348
+ }
349
+ }
350
+ }
351
+ }
352
+ }
353
+ LogMark.clear(); // 清空日志mark
354
+ }
355
+ /**
356
+ * 发送消息api
290
357
  * @param chatId 聊天 ID
291
358
  * @param bot_id 机器人 ID
292
359
  * @param chatType 聊天类型
293
360
  * @param message 消息内容
294
361
  */
295
- async sendMessage(chatId, bot_id, chatType, message) {
362
+ async sendMessageApi(chatId, bot_id, chatType, message) {
296
363
  if (chatType === 'group') {
297
364
  await (Bot[bot_id] ?? Bot)
298
365
  ?.pickGroup(String(chatId))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yz-yuki-plugin",
3
- "version": "2.0.6-10",
3
+ "version": "2.0.6-12",
4
4
  "description": "优纪插件,yunzaijs 关于 微博推送、B站推送 等功能的拓展插件",
5
5
  "author": "snowtafir",
6
6
  "type": "module",