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
|
@@ -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 {
|
|
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
|
-
|
|
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
|
|
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 {
|
|
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 (
|
|
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 {
|
|
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
|
|
22
|
-
const resjson = await resp
|
|
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
|
|
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
|
-
|
|
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
|
|
118
|
+
* @param dynamicTimeRange 筛选何时发布的动态
|
|
113
119
|
* @param biliConfigData Bilibili配置数据
|
|
114
120
|
*/
|
|
115
|
-
async
|
|
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) >
|
|
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.
|
|
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
|
|
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
|
-
|
|
177
|
-
let
|
|
178
|
-
|
|
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
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
|
2
|
+
import WeiboApi from './weibo.main.api.js';
|
|
3
3
|
import { WeiboQuery } from './weibo.main.query.js';
|
|
4
4
|
|
|
5
|
-
class
|
|
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
|
|
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
|
|
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
|
|
40
|
-
|
|
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
|
|
46
|
+
const { ok, data, msg } = response?.data;
|
|
44
47
|
if (!ok && msg !== '这里还没有内容') {
|
|
45
|
-
throw new Error(response
|
|
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 {
|
|
59
|
+
export { WeiboWebDataFetcher };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import moment from 'moment';
|
|
2
2
|
import fetch from 'node-fetch';
|
|
3
|
-
import
|
|
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
|
|
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 (
|
|
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}】:${
|
|
295
|
+
logger?.mark(`未处理的微博推送【${upName}】:${dynamicType}`);
|
|
296
296
|
return 'continue';
|
|
297
297
|
}
|
|
298
298
|
}
|