yz-yuki-plugin 2.0.6-8 → 2.0.7-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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站账号已登陆~\n有效期至:${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 };
@@ -99,7 +99,7 @@ class BiliQuery {
99
99
  }) || [];
100
100
  formatData.data.title = desc?.title;
101
101
  // 文章内容过长,则尝试获取全文
102
- if (desc?.summary?.text?.length >= 480) {
102
+ if (String(desc?.summary?.text).length >= 480) {
103
103
  const fullArticleContent = await this.getFullArticleContent(this.formatUrl(desc?.jump_url));
104
104
  if (fullArticleContent) {
105
105
  const { readInfo, articleType } = fullArticleContent;
@@ -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}em; height: ${emoji?.size}em;">`;
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;">`;
231
231
  case 'RICH_TEXT_NODE_TYPE_GOODS':
232
232
  // 处理商品推广类型,使用官方的HTML标签写法
233
233
  const goods_url = node?.jump_url;
@@ -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();
@@ -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}em; height: ${emoji?.size}em;">`;
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;">`;
350
350
  case 'RICH_TEXT_NODE_TYPE_GOODS':
351
351
  // 处理商品推广类型,使用官方的HTML标签写法
352
352
  const goods_url = node?.rich?.jump_url;
@@ -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;
@@ -2,7 +2,7 @@ import QRCode from 'qrcode';
2
2
  import { Redis, Segment, Bot } from 'yunzaijs';
3
3
  import Config from '../../utils/config.js';
4
4
  import { renderPage } from '../../utils/image.js';
5
- import { BiliGetWebData } from './bilibili.main.get.web.data.js';
5
+ import { BilibiliWebDataFetcher } from './bilibili.main.get.web.data.js';
6
6
  import { readSyncCookie, postGateway } from './bilibili.main.models.js';
7
7
  import { BiliQuery } from './bilibili.main.query.js';
8
8
 
@@ -18,8 +18,8 @@ class BiliTask {
18
18
  }
19
19
  async hendleEventDynamicData(uid, count = 0) {
20
20
  let { cookie } = await readSyncCookie();
21
- const resp = await new BiliGetWebData().getBiliDynamicListDataByUid(uid);
22
- const resjson = await resp.data;
21
+ const resp = await new BilibiliWebDataFetcher().getBiliDynamicListDataByUid(uid);
22
+ const resjson = await resp?.data;
23
23
  if (!resjson || resjson.code !== 0 || resjson.code === -352) {
24
24
  await postGateway(cookie);
25
25
  if (count < 2) {
@@ -33,16 +33,22 @@ 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');
39
- let interval = biliConfigData?.interval || 7200;
42
+ let dynamicTimeRange = biliConfigData?.dynamicTimeRange || 7200; // 筛选何时发布的动态,单位为秒,默认2小时内发布的动态
40
43
  logger.debug(`当前B站功能配置:${JSON.stringify(biliConfigData)}`);
41
44
  const uidMap = new Map(); // 存放group 和 private 对应所属 uid 与推送信息的映射
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, dynamicTimeRange, 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
- * @param interval 推送间隔时间
118
+ * @param dynamicTimeRange 筛选何时发布的动态
113
119
  * @param biliConfigData Bilibili配置数据
114
120
  */
115
- async pushDynamicMessages(uidMap, dynamicList, now, interval, biliConfigData) {
121
+ async makeUidDynamicDataMap(uidMap, dynamicList, now, dynamicTimeRange, biliConfigData, messageMap) {
116
122
  for (let [chatType, chatTypeMap] of uidMap) {
117
123
  for (let [key, value] of chatTypeMap) {
118
124
  const tempDynamicList = dynamicList[key] || [];
@@ -126,7 +132,7 @@ class BiliTask {
126
132
  }
127
133
  if (!author?.pub_ts)
128
134
  continue; // 如果动态没有发布时间,跳过当前循环
129
- if (Number(now - author.pub_ts) > interval) {
135
+ if (Number(now - author.pub_ts) > dynamicTimeRange) {
130
136
  logger.debug(`超过间隔,跳过 [ ${author?.name} : ${author?.mid} ] ${author?.pub_time} 的动态`);
131
137
  continue;
132
138
  } // 如果超过推送时间间隔,跳过当前循环
@@ -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,18 +179,21 @@ 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
- const eval2 = eval;
185
- let banWords = eval2(`/${biliConfigData.banWords.join('|')}/g`); // 构建屏蔽关键字正则表达式
186
- if (new RegExp(banWords).test(`${extentData?.title}${extentData?.content}`)) {
187
- return 'return'; // 如果动态包含屏蔽关键字,则直接返回
187
+ const getBanWords = biliConfigData?.banWords;
188
+ if (getBanWords && Array.isArray(getBanWords) && getBanWords.length > 0) {
189
+ // 构建屏蔽关键字正则表达式,转义特殊字符
190
+ const banWords = new RegExp(getBanWords.map(word => word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|'), 'g');
191
+ if (banWords.test(`${extentData?.title}${extentData?.content}`)) {
192
+ return 'return'; // 如果动态消息包含屏蔽关键字,则直接返回
193
+ }
194
+ }
195
+ else if (getBanWords && !Array.isArray(getBanWords)) {
196
+ logger.error(`B站动态:Yaml配置文件中,banWords 字段格式不是数组格式,请检查!`);
188
197
  }
189
198
  let boxGrid = !!biliConfigData.boxGrid === false ? false : true; // 是否启用九宫格样式,默认为 true
190
199
  let isSplit = !!biliConfigData.isSplit === false ? false : true; // 是否启用分片截图,默认为 true
@@ -206,74 +215,35 @@ class BiliTask {
206
215
  };
207
216
  let imgs = await this.renderDynamicCard(uid, renderData, ScreenshotOptionsData);
208
217
  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秒
218
+ return; // 如果渲染失败,则直接返回
219
+ await this.addMessageToMap(messageMap, chatType, bot_id, chatId, 'SINGLE', id_str, extentData?.type, imgs.map(img => Segment.image(img)));
228
220
  }
229
221
  else {
230
222
  const dynamicMsg = await BiliQuery.formatTextDynamicData(upName, pushDynamicData, false, biliConfigData); // 构建图文动态消息
231
- Redis.set(`${markKey}${chatId}:${id_str}`, '1', { EX: 3600 * 72 }); // 设置已发送标记
232
223
  if (dynamicMsg === undefined || dynamicMsg === 'continue') {
233
224
  return 'return'; // 如果动态消息构建失败,则直接返回
234
225
  }
235
226
  const getBanWords = biliConfigData?.banWords;
236
227
  if (getBanWords && Array.isArray(getBanWords) && getBanWords.length > 0) {
237
- const banWords = new RegExp(getBanWords.join('|'), 'g'); // 构建屏蔽关键字正则表达式
228
+ // 构建屏蔽关键字正则表达式,转义特殊字符
229
+ const banWords = new RegExp(getBanWords.map(word => word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|'), 'g');
238
230
  if (banWords.test(dynamicMsg.msg.join(''))) {
239
231
  return 'return'; // 如果动态消息包含屏蔽关键字,则直接返回
240
232
  }
241
233
  }
234
+ else if (getBanWords && !Array.isArray(getBanWords)) {
235
+ logger.error(`B站动态:Yaml配置文件中,banWords 字段格式不是数组格式,请检查!`);
236
+ }
242
237
  let mergeTextPic = !!biliConfigData.mergeTextPic === false ? false : true; // 是否合并文本和图片,默认为 true
238
+ //开启了合并文本和图片
243
239
  if (mergeTextPic === true) {
244
240
  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);
241
+ await this.addMessageToMap(messageMap, chatType, bot_id, chatId, 'MERGE', id_str, dynamicMsg.dynamicType, mergeMsg);
256
242
  }
257
243
  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));
244
+ //不合并文本和图片
245
+ await this.addMessageToMap(messageMap, chatType, bot_id, chatId, 'MERGE', id_str, dynamicMsg.dynamicType, dynamicMsg.msg);
246
+ await this.addMessageToMap(messageMap, chatType, bot_id, chatId, 'SINGLE', id_str, dynamicMsg.dynamicType, dynamicMsg.pics);
277
247
  }
278
248
  }
279
249
  }
@@ -354,13 +324,105 @@ class BiliTask {
354
324
  }
355
325
  }
356
326
  /**
357
- * 发送消息
327
+ * 收集消息映射
328
+ * @param messageMap 消息映射
329
+ * @param chatType 聊天类型
330
+ * @param bot_id 机器人 ID
331
+ * @param chatId 聊天 ID
332
+ * @param sendMode 发送模式: SINGLE 逐条发送,MERGE 合并发送
333
+ * @param dynamicUUid_str 动态 UUID
334
+ * @param dynamicType 动态类型
335
+ * @param message 消息内容
336
+ */
337
+ async addMessageToMap(messageMap, chatType, bot_id, chatId, sendMode, dynamicUUid_str, dynamicType, messages) {
338
+ if (!messageMap.has(chatType)) {
339
+ messageMap.set(chatType, new Map());
340
+ }
341
+ const botMap = messageMap.get(chatType);
342
+ if (!botMap?.has(bot_id)) {
343
+ botMap?.set(bot_id, new Map());
344
+ }
345
+ const chatMap = botMap?.get(bot_id);
346
+ if (!chatMap?.has(chatId)) {
347
+ chatMap?.set(chatId, []);
348
+ }
349
+ chatMap?.get(chatId)?.push({ sendMode, dynamicUUid_str, dynamicType, messages });
350
+ }
351
+ /**
352
+ * 推送动态消息
353
+ * @param messageMap 消息映射
354
+ * @param biliConfigData 哔哩配置数据
355
+ */
356
+ async sendDynamicMessage(messageMap, biliConfigData) {
357
+ let liveAtAll = !!biliConfigData.liveAtAll === true ? true : false; // 直播动态是否@全体成员,默认false
358
+ let liveAtAllCD = biliConfigData.liveAtAllCD || 1800; // 直播动态@全体成员 冷却时间CD,默认 30 分钟
359
+ // 直播动态@全体成员的群组/好友列表,默认空数组,为空则不进行@全体成员操作
360
+ let liveAtAllGroupList = new Set(Array.isArray(biliConfigData?.liveAtAllGroupList) ? Array.from(biliConfigData.liveAtAllGroupList).map(item => String(item)) : []);
361
+ const LogMark = new Set(); // 日志mark
362
+ for (const [chatType, botMap] of messageMap) {
363
+ for (const [bot_id, chatMap] of botMap) {
364
+ for (const [chatId, messageCombinationList] of chatMap) {
365
+ // 遍历组合消息
366
+ for (const messageCombination of messageCombinationList) {
367
+ const { sendMode, dynamicUUid_str, dynamicType, messages } = messageCombination;
368
+ let sended = null;
369
+ let markKey = '';
370
+ if (chatType === 'group') {
371
+ markKey = this.groupKey;
372
+ sended = await Redis.get(`${markKey}${chatId}:${dynamicUUid_str}`);
373
+ }
374
+ else if (chatType === 'private') {
375
+ markKey = this.privateKey;
376
+ sended = await Redis.get(`${markKey}${chatId}:${dynamicUUid_str}`);
377
+ }
378
+ const sendMarkKey = `${markKey}${chatId}:${dynamicUUid_str}`;
379
+ if (sended) {
380
+ continue; // 如果已经发送过,则直接跳过
381
+ }
382
+ if (!LogMark.has('1')) {
383
+ global?.logger?.mark('优纪插件: B站动态执行推送');
384
+ LogMark.add('1');
385
+ }
386
+ let liveAtAllMark = await Redis.get(`${markKey}${chatId}:liveAtAllMark`); // 直播动态@全体成员标记,默认 0
387
+ // 如果开启了直播动态@全体成员
388
+ if (liveAtAll && !liveAtAllMark && dynamicType === 'DYNAMIC_TYPE_LIVE_RCMD' && liveAtAllGroupList.has(String(chatId))) {
389
+ try {
390
+ await this.sendMessageApi(chatId, bot_id, chatType, [Segment.at('all')]);
391
+ await Redis.set(`${markKey}${chatId}:liveAtAllMark`, 1, { EX: liveAtAllCD }); // 设置直播动态@全体成员标记为 1
392
+ }
393
+ catch (error) {
394
+ logger.error(`直播动态发送@全体成员失败,请检查 <机器人> 是否有 [管理员权限] 或 [聊天平台是否支持] :${error}`);
395
+ let liveAtAllErrMsg = !!biliConfigData.liveAtAllErrMsg === false ? false : true; // 直播动态@全体成员失败是否发送错误提示消息,默认 false
396
+ if (liveAtAllErrMsg) {
397
+ await this.sendMessageApi(chatId, bot_id, chatType, ['直播动态发送@全体成员失败,请检查权限或平台是否支持']);
398
+ }
399
+ }
400
+ }
401
+ if (sendMode === 'SINGLE') {
402
+ for (let i = 0; i < messages.length; i++) {
403
+ await this.sendMessageApi(chatId, bot_id, chatType, messages[i]);
404
+ }
405
+ await Redis.set(sendMarkKey, '1', { EX: 3600 * 72 }); // 发送成功后设置标记
406
+ await this.randomDelay(1000, 2000); // 随机延时1-2秒
407
+ }
408
+ else if (sendMode === 'MERGE') {
409
+ await this.sendMessageApi(chatId, bot_id, chatType, messages);
410
+ await Redis.set(sendMarkKey, '1', { EX: 3600 * 72 }); // 发送成功后设置标记
411
+ }
412
+ }
413
+ }
414
+ }
415
+ }
416
+ LogMark.clear(); // 清空日志mark
417
+ }
418
+ /**
419
+ * 发送消息api
358
420
  * @param chatId 聊天 ID
359
421
  * @param bot_id 机器人 ID
360
422
  * @param chatType 聊天类型
361
423
  * @param message 消息内容
362
424
  */
363
- async sendMessage(chatId, bot_id, chatType, message) {
425
+ async sendMessageApi(chatId, bot_id, chatType, message) {
364
426
  if (chatType === 'group') {
365
427
  await (Bot[bot_id] ?? Bot)
366
428
  ?.pickGroup(String(chatId))
@@ -1,21 +1,47 @@
1
+ import Config from '../../utils/config.js';
2
+
1
3
  class WeiboApi {
2
- static WEIBO_API = {
3
- weiboGetIndex: 'https://m.weibo.cn/api/container/getIndex',
4
- //通过关键词${upKeyword}搜索博主 parama = { q: 'Keyword'},
5
- weiboAjaxSearch: 'https://weibo.com/ajax/side/search'
6
- };
4
+ weiboConfigData;
5
+ USER_AGENT;
6
+ constructor() {
7
+ this.weiboConfigData = Config.getUserConfig('weibo', 'config');
8
+ this.USER_AGENT = WeiboApi.WEIBO_USER_AGENT;
9
+ this.initialize();
10
+ }
11
+ static WEIBO_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
+ //初始化User-Agent
13
+ async initialize() {
14
+ await this.initUserAgent();
15
+ }
16
+ async initUserAgent() {
17
+ const userAgentList = await this.weiboConfigData.userAgentList;
18
+ if (userAgentList && userAgentList.length > 0) {
19
+ const randomIndex = Math.floor(Math.random() * userAgentList.length);
20
+ this.USER_AGENT = String(userAgentList[randomIndex]);
21
+ }
22
+ }
23
+ get WEIBO_API() {
24
+ return {
25
+ weiboGetIndex: 'https://m.weibo.cn/api/container/getIndex',
26
+ //通过关键词${upKeyword}搜索博主 parama = { q: 'Keyword'},
27
+ weiboAjaxSearch: 'https://weibo.com/ajax/side/search'
28
+ };
29
+ }
7
30
  /**统一设置header */
8
- static WEIBO_HEADERS = {
9
- 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
10
- 'Accept-language': 'zh-CN,zh;q=0.9',
11
- 'Authority': 'm.weibo.cn',
12
- 'Cache-control': 'max-age=0',
13
- 'Sec-fetch-dest': 'empty',
14
- 'Sec-fetch-mode': 'same-origin',
15
- 'Sec-fetch-site': 'same-origin',
16
- 'Upgrade-insecure-requests': '1',
17
- 'User-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:127.0) Gecko/20100101 Firefox/127.0'
18
- };
31
+ get WEIBO_HEADERS() {
32
+ return {
33
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
34
+ 'Accept-language': 'zh-CN,zh;q=0.9',
35
+ 'Authority': 'm.weibo.cn',
36
+ 'Cache-control': 'max-age=0',
37
+ 'Sec-fetch-dest': 'empty',
38
+ 'Sec-fetch-mode': 'same-origin',
39
+ 'Sec-fetch-site': 'same-origin',
40
+ 'Upgrade-insecure-requests': '1',
41
+ 'User-agent': this.USER_AGENT
42
+ };
43
+ }
19
44
  }
45
+ var WeiboApi$1 = new WeiboApi();
20
46
 
21
- export { WeiboApi };
47
+ export { WeiboApi$1 as default };
@@ -1,8 +1,8 @@
1
1
  import axios from 'axios';
2
- import { WeiboApi } from './weibo.main.api.js';
2
+ import WeiboApi from './weibo.main.api.js';
3
3
  import { WeiboQuery } from './weibo.main.query.js';
4
4
 
5
- class WeiboGetWebData {
5
+ class WeiboWebDataFetcher {
6
6
  e;
7
7
  constructor(e) { }
8
8
  /**通过uid获取博主信息 */
@@ -10,7 +10,8 @@ class WeiboGetWebData {
10
10
  const param = { containerid: '100505' + target };
11
11
  const url = new URL(WeiboApi.WEIBO_API.weiboGetIndex);
12
12
  url.search = new URLSearchParams(param).toString();
13
- const resp = await axios.get(url.toString(), {
13
+ const resp = await axios(url.toString(), {
14
+ method: 'GET',
14
15
  timeout: 10000,
15
16
  headers: { 'accept': '*/*', 'Content-Type': 'application/json', 'referer': 'https://m.weibo.cn' }
16
17
  });
@@ -22,7 +23,8 @@ class WeiboGetWebData {
22
23
  const params = {
23
24
  q: keyword
24
25
  };
25
- const resp = await axios.get(url, {
26
+ const resp = await axios(url, {
27
+ method: 'GET',
26
28
  params,
27
29
  timeout: 10000,
28
30
  headers: { 'accept': '*/*', 'Content-Type': 'application/json', 'referer': 'https://s.weibo.com' }
@@ -36,13 +38,14 @@ class WeiboGetWebData {
36
38
  url.search = new URLSearchParams(params).toString();
37
39
  await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * (6500 - 1000 + 1) + 1000)));
38
40
  try {
39
- const response = await axios.get(url.toString(), {
40
- timeout: 15000,
41
+ const response = await axios(url.toString(), {
42
+ method: 'GET',
43
+ timeout: 10000,
41
44
  headers: { 'accept': '*/*', 'Content-Type': 'application/json', 'referer': 'https://m.weibo.cn' }
42
45
  });
43
- const { ok, data, msg } = response.data;
46
+ const { ok, data, msg } = response?.data;
44
47
  if (!ok && msg !== '这里还没有内容') {
45
- throw new Error(response.config.url);
48
+ throw new Error(response?.config.url);
46
49
  }
47
50
  return data.cards.filter(WeiboQuery.filterCardTypeCustom);
48
51
  }
@@ -53,4 +56,4 @@ class WeiboGetWebData {
53
56
  }
54
57
  }
55
58
 
56
- export { WeiboGetWebData };
59
+ export { WeiboWebDataFetcher };
@@ -1,6 +1,6 @@
1
1
  import moment from 'moment';
2
2
  import fetch from 'node-fetch';
3
- import { WeiboApi } from './weibo.main.api.js';
3
+ import WeiboApi from './weibo.main.api.js';
4
4
  import { Segment } from 'yunzaijs';
5
5
  import { JSDOM } from 'jsdom';
6
6
 
@@ -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
  }