yz-yuki-plugin 2.0.6-9 → 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.
- package/CHANGELOG.md +8 -1
- package/defaultConfig/bilibili/config.yaml +33 -15
- package/defaultConfig/weibo/config.yaml +23 -8
- package/lib/apps/bilibili.js +139 -38
- package/lib/apps/weibo.js +8 -8
- package/lib/components/dynamic/Footer.js +2 -2
- package/lib/components/help/Help.js +1 -1
- package/lib/components/version/Version.js +1 -1
- package/lib/index.js +3 -3
- package/lib/models/bilibili/bilibili.main.api.js +7 -1
- package/lib/models/bilibili/bilibili.main.get.web.data.js +57 -8
- package/lib/models/bilibili/bilibili.main.models.js +17 -3
- package/lib/models/bilibili/bilibili.main.query.js +5 -5
- package/lib/models/bilibili/bilibili.main.task.js +139 -77
- package/lib/models/weibo/weibo.main.api.js +43 -17
- package/lib/models/weibo/weibo.main.get.web.data.js +12 -9
- package/lib/models/weibo/weibo.main.query.js +8 -8
- package/lib/models/weibo/weibo.main.task.js +117 -40
- package/lib/utils/image.js +4 -4
- package/package.json +5 -5
|
@@ -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 {
|
|
5
|
+
import { WeiboWebDataFetcher } from './weibo.main.get.web.data.js';
|
|
6
6
|
import { WeiboQuery } from './weibo.main.query.js';
|
|
7
7
|
|
|
8
8
|
class WeiboTask {
|
|
@@ -15,16 +15,22 @@ 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');
|
|
21
|
-
let interval = weiboConfigData.interval || 7200; //
|
|
24
|
+
let interval = weiboConfigData.interval || 7200; // 筛选何时发布的动态,单位为秒,默认2小时内发布的动态
|
|
22
25
|
logger.debug(`当前微博功能配置:${JSON.stringify(weiboConfigData)}`);
|
|
23
26
|
const uidMap = new Map(); // 存放group 和 private 对应所属 uid 与推送信息的映射
|
|
24
27
|
const dynamicList = {}; // 存放获取的所有动态,键为 uid,值为动态数组
|
|
25
28
|
await this.processWeiboData(weiboPushData, uidMap, dynamicList);
|
|
26
29
|
let now = Date.now() / 1000; // 当前时间戳(秒)
|
|
27
|
-
|
|
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 映射
|
|
@@ -53,7 +59,7 @@ class WeiboTask {
|
|
|
53
59
|
dynamicList[subInfoOfup.uid] = dynamicData;
|
|
54
60
|
}
|
|
55
61
|
else {
|
|
56
|
-
resp = await
|
|
62
|
+
resp = await new WeiboWebDataFetcher().getBloggerDynamicList(subInfoOfup.uid); // 获取指定 uid 的动态列表
|
|
57
63
|
if (resp) {
|
|
58
64
|
requestedDataOfUids.set(subInfoOfup.uid, resp); // 将响应数据存储到映射中
|
|
59
65
|
const dynamicData = resp || [];
|
|
@@ -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
|
-
* @param
|
|
84
|
+
* @param dynamicTimeRange 筛选何时发布的动态
|
|
79
85
|
* @param weiboConfigData 微博配置数据
|
|
80
86
|
*/
|
|
81
|
-
async
|
|
87
|
+
async makeUidDynamicDataMap(uidMap, dynamicList, now, dynamicTimeRange, weiboConfigData, messageMap) {
|
|
82
88
|
for (let [chatType, chatTypeMap] of uidMap) {
|
|
83
89
|
for (let [key, value] of chatTypeMap) {
|
|
84
90
|
const tempDynamicList = dynamicList[key] || [];
|
|
@@ -93,7 +99,7 @@ class WeiboTask {
|
|
|
93
99
|
}
|
|
94
100
|
if (!raw_post?.mblog?.created_at)
|
|
95
101
|
continue;
|
|
96
|
-
if (Number(now - WeiboQuery.getDynamicCreatetDate(raw_post) / 1000) >
|
|
102
|
+
if (Number(now - WeiboQuery.getDynamicCreatetDate(raw_post) / 1000) > dynamicTimeRange) {
|
|
97
103
|
logger.debug(`超过间隔,跳过 [ ${user?.screen_name} : ${user?.id} ] ${raw_post?.mblog?.created_at} 的动态`);
|
|
98
104
|
continue;
|
|
99
105
|
} // 如果超过推送时间间隔,跳过当前循环
|
|
@@ -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.
|
|
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
|
|
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') {
|
|
@@ -142,10 +149,16 @@ class WeiboTask {
|
|
|
142
149
|
return; // 如果已经发送过,则直接返回
|
|
143
150
|
if (!!weiboConfigData.pushMsgMode) {
|
|
144
151
|
const { data, uid } = await WeiboQuery.formatDynamicData(pushDynamicData); // 处理动态数据
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
152
|
+
const getBanWords = weiboConfigData?.banWords;
|
|
153
|
+
if (getBanWords && Array.isArray(getBanWords) && getBanWords.length > 0) {
|
|
154
|
+
// 构建屏蔽关键字正则表达式,转义特殊字符
|
|
155
|
+
const banWords = new RegExp(getBanWords.map(word => word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|'), 'g');
|
|
156
|
+
if (banWords.test(`${data?.title}${data?.content}`)) {
|
|
157
|
+
return 'return'; // 如果动态消息包含屏蔽关键字,则直接返回
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
else if (getBanWords && !Array.isArray(getBanWords)) {
|
|
161
|
+
logger.error(`微博动态:Yaml配置文件中,banWords 字段格式不是数组格式,请检查!`);
|
|
149
162
|
}
|
|
150
163
|
let boxGrid = !!weiboConfigData.boxGrid === false ? false : true; // 是否启用九宫格样式,默认为 true
|
|
151
164
|
let isSplit = !!weiboConfigData.isSplit === false ? false : true; // 是否启用分片截图,默认为 true
|
|
@@ -168,44 +181,35 @@ class WeiboTask {
|
|
|
168
181
|
};
|
|
169
182
|
let imgs = await this.renderDynamicCard(uid, renderData, ScreenshotOptionsData);
|
|
170
183
|
if (!imgs)
|
|
171
|
-
return;
|
|
172
|
-
|
|
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));
|
|
184
|
+
return; // 如果渲染失败,则直接返回
|
|
185
|
+
await this.addMessageToMap(messageMap, chatType, bot_id, chatId, 'SINGLE', id_str, extentData?.type, imgs.map(img => Segment.image(img)));
|
|
180
186
|
}
|
|
181
187
|
else {
|
|
182
188
|
const dynamicMsg = await WeiboQuery.formatTextDynamicData(upName, pushDynamicData, false, weiboConfigData); //构建文字动态消息
|
|
183
|
-
Redis.set(`${markKey}${chatId}:${id_str}`, '1', { EX: 3600 * 72 }); // 设置已发送标记
|
|
184
189
|
if (dynamicMsg === undefined || dynamicMsg === 'continue') {
|
|
185
190
|
return 'return'; // 如果动态消息构建失败或内部资源获取失败,则直接返回
|
|
186
191
|
}
|
|
187
192
|
const getBanWords = weiboConfigData?.banWords;
|
|
188
193
|
if (getBanWords && Array.isArray(getBanWords) && getBanWords.length > 0) {
|
|
189
|
-
|
|
194
|
+
// 构建屏蔽关键字正则表达式,转义特殊字符
|
|
195
|
+
const banWords = new RegExp(getBanWords.map(word => word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|'), 'g');
|
|
190
196
|
if (banWords.test(dynamicMsg.msg.join(''))) {
|
|
191
197
|
return 'return'; // 如果动态消息包含屏蔽关键字,则直接返回
|
|
192
198
|
}
|
|
193
199
|
}
|
|
200
|
+
else if (getBanWords && !Array.isArray(getBanWords)) {
|
|
201
|
+
logger.error(`微博动态:Yaml配置文件中,banWords 字段格式不是数组格式,请检查!`);
|
|
202
|
+
}
|
|
194
203
|
let mergeTextPic = !!weiboConfigData.mergeTextPic === false ? false : true; // 是否合并文字和图片,默认为 true
|
|
195
|
-
|
|
204
|
+
//开启了合并文本和图片
|
|
205
|
+
if (mergeTextPic === true) {
|
|
196
206
|
const mergeMsg = [...dynamicMsg.msg, ...dynamicMsg.pics];
|
|
197
|
-
await this.
|
|
207
|
+
await this.addMessageToMap(messageMap, chatType, bot_id, chatId, 'MERGE', id_str, dynamicMsg.dynamicType, mergeMsg);
|
|
198
208
|
}
|
|
199
209
|
else {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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));
|
|
210
|
+
//不合并文本和图片
|
|
211
|
+
await this.addMessageToMap(messageMap, chatType, bot_id, chatId, 'MERGE', id_str, dynamicMsg.dynamicType, dynamicMsg.msg);
|
|
212
|
+
await this.addMessageToMap(messageMap, chatType, bot_id, chatId, 'SINGLE', id_str, dynamicMsg.dynamicType, dynamicMsg.pics);
|
|
209
213
|
}
|
|
210
214
|
}
|
|
211
215
|
}
|
|
@@ -286,13 +290,86 @@ class WeiboTask {
|
|
|
286
290
|
}
|
|
287
291
|
}
|
|
288
292
|
/**
|
|
289
|
-
*
|
|
293
|
+
* 收集消息映射
|
|
294
|
+
* @param messageMap 消息映射
|
|
295
|
+
* @param chatType 聊天类型
|
|
296
|
+
* @param bot_id 机器人 ID
|
|
297
|
+
* @param chatId 聊天 ID
|
|
298
|
+
* @param sendMode 发送模式: SINGLE 逐条发送,MERGE 合并发送
|
|
299
|
+
* @param dynamicUUid_str 动态 UUID
|
|
300
|
+
* @param dynamicType 动态类型
|
|
301
|
+
* @param message 消息内容
|
|
302
|
+
*/
|
|
303
|
+
async addMessageToMap(messageMap, chatType, bot_id, chatId, sendMode, dynamicUUid_str, dynamicType, messages) {
|
|
304
|
+
if (!messageMap.has(chatType)) {
|
|
305
|
+
messageMap.set(chatType, new Map());
|
|
306
|
+
}
|
|
307
|
+
const botMap = messageMap.get(chatType);
|
|
308
|
+
if (!botMap?.has(bot_id)) {
|
|
309
|
+
botMap?.set(bot_id, new Map());
|
|
310
|
+
}
|
|
311
|
+
const chatMap = botMap?.get(bot_id);
|
|
312
|
+
if (!chatMap?.has(chatId)) {
|
|
313
|
+
chatMap?.set(chatId, []);
|
|
314
|
+
}
|
|
315
|
+
chatMap?.get(chatId)?.push({ sendMode, dynamicUUid_str, dynamicType, messages });
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* 推送动态消息
|
|
319
|
+
* @param messageMap 消息映射
|
|
320
|
+
* @param biliConfigData 微博配置数据
|
|
321
|
+
*/
|
|
322
|
+
async sendDynamicMessage(messageMap, weiboConfigData) {
|
|
323
|
+
const LogMark = new Set(); // 日志mark
|
|
324
|
+
for (const [chatType, botMap] of messageMap) {
|
|
325
|
+
for (const [bot_id, chatMap] of botMap) {
|
|
326
|
+
for (const [chatId, messageCombinationList] of chatMap) {
|
|
327
|
+
// 遍历组合消息
|
|
328
|
+
for (const messageCombination of messageCombinationList) {
|
|
329
|
+
const { sendMode, dynamicUUid_str, dynamicType, messages } = messageCombination;
|
|
330
|
+
let sended = null;
|
|
331
|
+
let markKey = '';
|
|
332
|
+
if (chatType === 'group') {
|
|
333
|
+
markKey = this.groupKey;
|
|
334
|
+
sended = await Redis.get(`${markKey}${chatId}:${dynamicUUid_str}`);
|
|
335
|
+
}
|
|
336
|
+
else if (chatType === 'private') {
|
|
337
|
+
markKey = this.privateKey;
|
|
338
|
+
sended = await Redis.get(`${markKey}${chatId}:${dynamicUUid_str}`);
|
|
339
|
+
}
|
|
340
|
+
const sendMarkKey = `${markKey}${chatId}:${dynamicUUid_str}`;
|
|
341
|
+
if (sended) {
|
|
342
|
+
continue; // 如果已经发送过,则直接跳过
|
|
343
|
+
}
|
|
344
|
+
if (!LogMark.has('1')) {
|
|
345
|
+
global?.logger?.mark('优纪插件: B站动态执行推送');
|
|
346
|
+
LogMark.add('1');
|
|
347
|
+
}
|
|
348
|
+
if (sendMode === 'SINGLE') {
|
|
349
|
+
for (let i = 0; i < messages.length; i++) {
|
|
350
|
+
await this.sendMessageApi(chatId, bot_id, chatType, messages[i]);
|
|
351
|
+
}
|
|
352
|
+
await Redis.set(sendMarkKey, '1', { EX: 3600 * 72 }); // 发送成功后设置标记
|
|
353
|
+
await this.randomDelay(1000, 2000); // 随机延时1-2秒
|
|
354
|
+
}
|
|
355
|
+
else if (sendMode === 'MERGE') {
|
|
356
|
+
await this.sendMessageApi(chatId, bot_id, chatType, messages);
|
|
357
|
+
await Redis.set(sendMarkKey, '1', { EX: 3600 * 72 }); // 发送成功后设置标记
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
LogMark.clear(); // 清空日志mark
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* 发送消息api
|
|
290
367
|
* @param chatId 聊天 ID
|
|
291
368
|
* @param bot_id 机器人 ID
|
|
292
369
|
* @param chatType 聊天类型
|
|
293
370
|
* @param message 消息内容
|
|
294
371
|
*/
|
|
295
|
-
async
|
|
372
|
+
async sendMessageApi(chatId, bot_id, chatType, message) {
|
|
296
373
|
if (chatType === 'group') {
|
|
297
374
|
await (Bot[bot_id] ?? Bot)
|
|
298
375
|
?.pickGroup(String(chatId))
|
package/lib/utils/image.js
CHANGED
|
@@ -15,11 +15,11 @@ class Image extends Picture {
|
|
|
15
15
|
// 继承父类实例
|
|
16
16
|
super();
|
|
17
17
|
// 父类已经实例化组件渲染对象
|
|
18
|
-
//this.
|
|
18
|
+
//this.component;
|
|
19
19
|
// 父类已经实例化启动 Puppeteer
|
|
20
|
-
//this.
|
|
20
|
+
//this.puppeteer.start();
|
|
21
21
|
// 初始化 YukiPuppeteerRender 实例
|
|
22
|
-
this.yukiPuppeteerRender = new YukiPuppeteerRender(this.
|
|
22
|
+
this.yukiPuppeteerRender = new YukiPuppeteerRender(this.puppeteer);
|
|
23
23
|
}
|
|
24
24
|
/**
|
|
25
25
|
* 实例方法,用于执行实际的渲染和截图操作
|
|
@@ -34,7 +34,7 @@ class Image extends Picture {
|
|
|
34
34
|
// 根据组件名称获取对应的 React 组件
|
|
35
35
|
const Page = index[page];
|
|
36
36
|
// 调用 yukiPuppeteerRender 进行截图操作
|
|
37
|
-
return this.yukiPuppeteerRender.yukiScreenshot(this.
|
|
37
|
+
return this.yukiPuppeteerRender.yukiScreenshot(this.component.compile({
|
|
38
38
|
path: page,
|
|
39
39
|
name: `${uid}.html`,
|
|
40
40
|
component: React.createElement(Page, { ...props }),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yz-yuki-plugin",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.7-0",
|
|
4
4
|
"description": "优纪插件,yunzaijs 关于 微博推送、B站推送 等功能的拓展插件",
|
|
5
5
|
"author": "snowtafir",
|
|
6
6
|
"type": "module",
|
|
@@ -31,12 +31,12 @@
|
|
|
31
31
|
"debug": "^4.3.6",
|
|
32
32
|
"jsdom": "^25.0.1",
|
|
33
33
|
"json5": "^2.2.3",
|
|
34
|
-
"jsxp": "^1.
|
|
34
|
+
"jsxp": "^1.1.2",
|
|
35
35
|
"lodash": "^4.17.21",
|
|
36
36
|
"md5": "^2.3.0",
|
|
37
37
|
"moment": "^2.30.1",
|
|
38
38
|
"node-fetch": "^3.3.2",
|
|
39
|
-
"puppeteer": "^24.
|
|
39
|
+
"puppeteer": "^24.4.0",
|
|
40
40
|
"qrcode": "^1.5.4",
|
|
41
41
|
"react": "^18.3.1",
|
|
42
42
|
"react-dom": "^18.3.1",
|
|
@@ -63,14 +63,14 @@
|
|
|
63
63
|
"icqq": "^0.6.10",
|
|
64
64
|
"jsdom": "^24.1.1",
|
|
65
65
|
"json5": "^2.2.3",
|
|
66
|
-
"jsxp": "^1.
|
|
66
|
+
"jsxp": "^1.1.2",
|
|
67
67
|
"lodash": "^4.17.21",
|
|
68
68
|
"lvyjs": "^0.2.14",
|
|
69
69
|
"md5": "^2.3.0",
|
|
70
70
|
"node-fetch": "^3.3.2",
|
|
71
71
|
"postcss": "^8.4.47",
|
|
72
72
|
"prettier": "^3.4.2",
|
|
73
|
-
"puppeteer": "^24.
|
|
73
|
+
"puppeteer": "^24.4.0",
|
|
74
74
|
"qrcode": "^1.5.4",
|
|
75
75
|
"react": "^18.3.1",
|
|
76
76
|
"react-dom": "^18.3.1",
|