yz-yuki-plugin 2.0.6-0 → 2.0.6-10
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/.puppeteerrc.cjs +41 -1
- package/CHANGELOG.md +1 -0
- package/README.md +23 -0
- package/defaultConfig/bilibili/config.yaml +14 -1
- package/defaultConfig/weibo/config.yaml +4 -1
- package/lib/apps/bilibili.js +27 -23
- package/lib/apps/weibo.js +4 -4
- package/lib/index.js +1 -1
- package/lib/models/bilibili/bilibili.main.models.js +18 -13
- package/lib/models/bilibili/bilibili.main.query.js +97 -75
- package/lib/models/bilibili/bilibili.main.task.js +70 -16
- package/lib/models/bilibili/bilibili.risk.w_webid.js +6 -3
- package/lib/models/weibo/{weibo.get.web.data.js → weibo.main.get.web.data.js} +3 -4
- package/lib/models/weibo/{weibo.query.js → weibo.main.query.js} +8 -4
- package/lib/models/weibo/{weibo.task.js → weibo.main.task.js} +28 -17
- package/lib/utils/config.js +1 -1
- package/lib/utils/image.js +5 -5
- package/lib/utils/puppeteer.render.js +2 -0
- package/package.json +10 -8
- /package/lib/models/weibo/{weibo.api.js → weibo.main.api.js} +0 -0
package/.puppeteerrc.cjs
CHANGED
|
@@ -1,4 +1,44 @@
|
|
|
1
|
+
const os = require('os');
|
|
2
|
+
const { existsSync } = require('fs');
|
|
3
|
+
const { execSync } = require('child_process');
|
|
4
|
+
const arch = os.arch();
|
|
5
|
+
|
|
6
|
+
let skipDownload = false;
|
|
7
|
+
let executablePath;
|
|
8
|
+
|
|
9
|
+
if (['linux', 'android'].includes(process.platform))
|
|
10
|
+
for (const item of ['chromium', 'chromium-browser', 'chrome', 'google-chrome'])
|
|
11
|
+
try {
|
|
12
|
+
const chromiumPath = execSync(`command -v ${item}`).toString().trim();
|
|
13
|
+
if (chromiumPath && existsSync(chromiumPath)) {
|
|
14
|
+
executablePath = chromiumPath;
|
|
15
|
+
break;
|
|
16
|
+
}
|
|
17
|
+
} catch (err) {}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @type {string} 浏览器 "可执行文件路径" 列表,可根据需要自行修改或添加
|
|
21
|
+
*/
|
|
22
|
+
if (!executablePath)
|
|
23
|
+
for (const item of [
|
|
24
|
+
// Windows
|
|
25
|
+
'C:/Program Files/Google/Chrome/Application/chrome.exe',
|
|
26
|
+
'C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe',
|
|
27
|
+
// macOS
|
|
28
|
+
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
29
|
+
'/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge'
|
|
30
|
+
])
|
|
31
|
+
if (existsSync(item)) {
|
|
32
|
+
executablePath = item;
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (executablePath || arch == 'arm64' || arch == 'aarch64') {
|
|
37
|
+
(typeof logger != 'undefined' ? logger : console).info(`[Chromium] ${executablePath}`);
|
|
38
|
+
skipDownload = true;
|
|
39
|
+
}
|
|
40
|
+
|
|
1
41
|
/**
|
|
2
42
|
* @type {import("puppeteer").Configuration}
|
|
3
43
|
*/
|
|
4
|
-
module.exports =
|
|
44
|
+
module.exports = { skipDownload, executablePath };
|
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -8,6 +8,29 @@
|
|
|
8
8
|
|
|
9
9
|
[](https://github.com/snowtafir/yuki-plugin)
|
|
10
10
|
|
|
11
|
+
|
|
12
|
+
# 🚩运行环境:
|
|
13
|
+
1. 系统:
|
|
14
|
+
* windows 10/11+,
|
|
15
|
+
* Linux推荐:CentOS Stream 8 +, Debian 12+, Fedora 35+
|
|
16
|
+
|
|
17
|
+
2. node v22+ 下载地址:https://nodejs.org/zh-cn/download/
|
|
18
|
+
|
|
19
|
+
3. 推荐使用chrome或chromium浏览器,其他浏览器可能存在兼容性问题。
|
|
20
|
+
* chrome 浏览器 v131+ win_x64下载地址:https://www.google.cn/chrome/
|
|
21
|
+
* chromium 浏览器 v128+ Linux/win手动下载安装:https://download-chromium.appspot.com
|
|
22
|
+
|
|
23
|
+
> linux命令行安装chromiun浏览器:
|
|
24
|
+
```sh
|
|
25
|
+
sudo apt-get install chromium-browser # Ubuntu/Debian
|
|
26
|
+
sudo dnf install chromium # Fedora
|
|
27
|
+
sudo yum install chromium # CentOS Stream 8
|
|
28
|
+
|
|
29
|
+
#查看版本
|
|
30
|
+
chromium-browser --version
|
|
31
|
+
```
|
|
32
|
+
> 注意,如果windows下手动安装chromium浏览器,或安装了其他修改版chrome浏览器,出现找不到浏览器,需要手动将`可执行文件路径`添加到 [./.puppeteerrc.cjs](./.puppeteerrc.cjs) 的路径列表中。
|
|
33
|
+
|
|
11
34
|
# 🌰一、安装插件
|
|
12
35
|
|
|
13
36
|
## 选择安装方式
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# b站推送,1 开启 0 关闭,保留添加的相关数据,但是不再推送
|
|
2
2
|
pushStatus: 1
|
|
3
3
|
|
|
4
|
-
# 检测b
|
|
4
|
+
# 检测b站动态的冷却时间 CD,Cron表达式,作用域共6位,具体方法浏览器搜索 “node-schedule cron表达式”,示例:
|
|
5
5
|
# "*/15 * * * *" #每15min检测一次
|
|
6
6
|
# "*/31 * * * *" #每31min检测一次
|
|
7
7
|
# "0 5,35 * * * *" #每小时固定第5分0秒、第35分0秒检测一次,共2次/h
|
|
@@ -44,6 +44,9 @@ banWords:
|
|
|
44
44
|
# 设置B站动态消息模式 0 文字模式 1 图片模式
|
|
45
45
|
pushMsgMode: 1
|
|
46
46
|
|
|
47
|
+
# 文字模式时,文字消息与图片附件是否合并在一起发送,默认 1 合并,0 不合并。如果合并时图片过多导致发送失败,可设置为 0 单独发送图片。
|
|
48
|
+
mergeTextPic: 1
|
|
49
|
+
|
|
47
50
|
# 是否启用九宫格样式:默认 1 启用,0 不启用。此为最高优先级,九宫格为动态模式,特定大小/长宽比的图片资源将会动态启用九宫格/四宫格/无宫格样式。
|
|
48
51
|
boxGrid: 1
|
|
49
52
|
|
|
@@ -55,3 +58,13 @@ noSplitHeight: 7500
|
|
|
55
58
|
|
|
56
59
|
# 动态卡片分页截图高度,默认8000px(仅填数字,无需填入单位),请勿设置过大或过小。启用分片截图时生效。
|
|
57
60
|
splitHeight: 8000
|
|
61
|
+
|
|
62
|
+
# 直播动态是否@全体成员,默认 0 关闭,1 开启。开启前请检查 <机器人> 是否有 [管理员权限] 或 [聊天平台是否支持],某些聊天平台或类型不支持@全体成员,如qq官方机器人等。
|
|
63
|
+
liveAtAll: 0
|
|
64
|
+
|
|
65
|
+
# 直播动态@全体成员的群组/聊天/私聊列表,默认为空即不在任何群于推送直播动态中执行@全体成员。开启liveAtAll后才会生效。
|
|
66
|
+
liveAtAllGroupList:
|
|
67
|
+
- 1234567890 # 示例群号
|
|
68
|
+
|
|
69
|
+
# 直播动态@全体成员的共享冷却时间CD,单位秒,默认 1800 秒(30分钟),即每个群聊30分钟内不论多少条直播动态,只会@一次。注意,qq群有 @全体成员 10次/日 的限制,所以请合理设置。
|
|
70
|
+
liveAtAllCD: 1800
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# 微博推送,1 开启 0 关闭,保留添加的相关数据,但是不再推送
|
|
2
2
|
pushStatus: 1
|
|
3
3
|
|
|
4
|
-
#
|
|
4
|
+
# 检测微博动态的冷却时间 CD,Cron表达式,作用域共6位,具体方法浏览器搜索 “node-schedule cron表达式”,示例:
|
|
5
5
|
# "*/15 * * * *" #每15min检测一次
|
|
6
6
|
# "*/31 * * * *" #每31min检测一次
|
|
7
7
|
# "0 5,35 * * * *" #每小时固定第5分0秒、第35分0秒检测一次,共2次/h
|
|
@@ -34,6 +34,9 @@ banWords:
|
|
|
34
34
|
# 设置微博动态消息模式 0 文字模式 1 图片模式
|
|
35
35
|
pushMsgMode: 1
|
|
36
36
|
|
|
37
|
+
# 文字模式时,文字消息与图片附件是否合并在一起发送,默认 1 合并,0 不合并。如果合并时图片过多导致发送失败,可设置为 0 单独发送图片。
|
|
38
|
+
mergeTextPic: 1
|
|
39
|
+
|
|
37
40
|
# 是否启用九宫格样式:默认 1 启用,0 不启用。此为最高优先级,九宫格为动态模式,特定大小/长宽比的图片资源将会动态启用九宫格/四宫格/无宫格样式。
|
|
38
41
|
boxGrid: 1
|
|
39
42
|
|
package/lib/apps/bilibili.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import JSON from 'json5';
|
|
2
2
|
import lodash from 'lodash';
|
|
3
|
-
import { Messages,
|
|
3
|
+
import { Messages, Redis } from 'yunzaijs';
|
|
4
4
|
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';
|
|
@@ -63,7 +63,7 @@ message.use(async (e) => {
|
|
|
63
63
|
logger.mark(`yuki-plugin addDynamicSub Failed:${JSON.stringify(res.data)}`);
|
|
64
64
|
}
|
|
65
65
|
const { has_more, items } = data || {};
|
|
66
|
-
let infoName;
|
|
66
|
+
let infoName = '';
|
|
67
67
|
if (code === 0 && has_more === false) {
|
|
68
68
|
e.reply(`检测到该uid的主页空间动态内容为空,\n执行uid:${uid} 校验...`);
|
|
69
69
|
const resp = await new BiliGetWebData(e).getBilibiUserInfoByUid(uid);
|
|
@@ -87,7 +87,7 @@ message.use(async (e) => {
|
|
|
87
87
|
e.reply(`昵称:${infoName} \nuid:${uid} 校验成功!`);
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
|
-
let name;
|
|
90
|
+
let name = '';
|
|
91
91
|
if (Array.isArray(items)) {
|
|
92
92
|
if (items.length > 0) {
|
|
93
93
|
name = items[0].modules?.module_author?.name || uid;
|
|
@@ -170,27 +170,31 @@ message.use(async (e) => {
|
|
|
170
170
|
if (e.isMaster) {
|
|
171
171
|
try {
|
|
172
172
|
const token = await applyLoginQRCode(e);
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
(
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
e.reply(`get bilibili LoginCk:失败X﹏X`);
|
|
192
|
+
}
|
|
186
193
|
}
|
|
187
194
|
}
|
|
188
|
-
else {
|
|
189
|
-
e.reply(`get bilibili LoginCk:失败X﹏X`);
|
|
190
|
-
}
|
|
191
195
|
}
|
|
192
196
|
catch (Error) {
|
|
193
|
-
|
|
197
|
+
global?.logger?.info(`yuki-plugin Login bilibili Failed:${Error}`);
|
|
194
198
|
}
|
|
195
199
|
}
|
|
196
200
|
else {
|
|
@@ -263,10 +267,10 @@ message.use(async (e) => {
|
|
|
263
267
|
const { code, data } = await result.data; // 解析校验结果
|
|
264
268
|
switch (code) {
|
|
265
269
|
case 0:
|
|
266
|
-
|
|
270
|
+
global?.logger?.mark(`优纪插件:绑定localCK,Gateway校验成功:${JSON.stringify(data)}`);
|
|
267
271
|
break;
|
|
268
272
|
default:
|
|
269
|
-
|
|
273
|
+
global?.logger?.mark(`优纪插件:绑定localCK,Gateway校验失败:${JSON.stringify(data)}`);
|
|
270
274
|
break;
|
|
271
275
|
}
|
|
272
276
|
}
|
|
@@ -336,7 +340,7 @@ message.use(async (e) => {
|
|
|
336
340
|
}
|
|
337
341
|
catch (error) {
|
|
338
342
|
e.reply(`~yuki-plugin:\n临时b站ck刷新失败X﹏X\n请重启bot(手动或发送指令 #重启)后重试`);
|
|
339
|
-
|
|
343
|
+
global?.logger?.mark(`优纪插件:B站临时ck刷新error:${error}`);
|
|
340
344
|
}
|
|
341
345
|
}, [/^(#|\/)(yuki|优纪)?刷新(b站|B站|bili|bilibili|哔哩|哔哩哔哩)临时(ck|CK|cookie|COOKIE)$/]);
|
|
342
346
|
/** 订阅的全部b站推送列表 */
|
package/lib/apps/weibo.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Messages } from 'yunzaijs';
|
|
2
|
-
import { WeiboQuery } from '../models/weibo/weibo.query.js';
|
|
3
|
-
import { WeiboTask } from '../models/weibo/weibo.task.js';
|
|
2
|
+
import { WeiboQuery } from '../models/weibo/weibo.main.query.js';
|
|
3
|
+
import { WeiboTask } from '../models/weibo/weibo.main.task.js';
|
|
4
4
|
import Config from '../utils/config.js';
|
|
5
|
-
import { WeiboGetWebData } from '../models/weibo/weibo.get.web.data.js';
|
|
5
|
+
import { WeiboGetWebData } from '../models/weibo/weibo.main.get.web.data.js';
|
|
6
6
|
|
|
7
7
|
const message = new Messages('message');
|
|
8
8
|
let weiboPushData = Config.getConfigData('config', 'weibo', 'push');
|
|
@@ -58,7 +58,7 @@ message.use(async (e) => {
|
|
|
58
58
|
}
|
|
59
59
|
const userInfo = data.userInfo || {};
|
|
60
60
|
let name = uid;
|
|
61
|
-
if (userInfo
|
|
61
|
+
if (userInfo.length !== 0) {
|
|
62
62
|
name = userInfo.screen_name || uid;
|
|
63
63
|
}
|
|
64
64
|
// 添加新的推送数据
|
package/lib/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import path from 'path';
|
|
|
5
5
|
import { _paths } from './utils/paths.js';
|
|
6
6
|
import * as index$1 from './apps/index.js';
|
|
7
7
|
import { BiliTask } from './models/bilibili/bilibili.main.task.js';
|
|
8
|
-
import { WeiboTask } from './models/weibo/weibo.task.js';
|
|
8
|
+
import { WeiboTask } from './models/weibo/weibo.main.task.js';
|
|
9
9
|
|
|
10
10
|
const yukiPluginVersion = Config.getPackageJsonKey('version', path.join(_paths.pluginPath, 'package.json'));
|
|
11
11
|
let biliConfigData = Config.getConfigData('config', 'bilibili', 'config');
|
|
@@ -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,
|
|
9
|
+
import { Segment, Redis } 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';
|
|
@@ -29,10 +29,10 @@ async function applyLoginQRCode(e) {
|
|
|
29
29
|
if (!response.ok) {
|
|
30
30
|
throw new Error(`获取B站登录二维码URL网络请求失败,状态码: ${response.status}`);
|
|
31
31
|
}
|
|
32
|
-
const res = await response.json();
|
|
32
|
+
const res = (await response.json());
|
|
33
33
|
if (res?.code === 0) {
|
|
34
|
-
const qrcodeKey = res
|
|
35
|
-
const qrcodeUrl = res
|
|
34
|
+
const qrcodeKey = res?.data?.qrcode_key;
|
|
35
|
+
const qrcodeUrl = res?.data?.url;
|
|
36
36
|
let loginUrlQrcodeData = await QRCode.toDataURL(`${qrcodeUrl}`);
|
|
37
37
|
const LoginPropsData = {
|
|
38
38
|
data: { url: loginUrlQrcodeData }
|
|
@@ -76,9 +76,9 @@ async function pollLoginQRCode(e, qrcodeKey) {
|
|
|
76
76
|
if (!response.ok) {
|
|
77
77
|
throw new Error(`处理B站登录token网络请求失败,状态码: ${response.status}`);
|
|
78
78
|
}
|
|
79
|
-
const data = await response.json();
|
|
79
|
+
const data = (await response.json());
|
|
80
80
|
if (data.code === 0) {
|
|
81
|
-
if (data
|
|
81
|
+
if (data?.data?.code === 0) {
|
|
82
82
|
// 登录成功,获取 cookie
|
|
83
83
|
const LoginCookie = response.headers.get('set-cookie');
|
|
84
84
|
let loginCk = '';
|
|
@@ -93,20 +93,20 @@ async function pollLoginQRCode(e, qrcodeKey) {
|
|
|
93
93
|
e.reply(`~B站登陆成功~`);
|
|
94
94
|
return loginCk;
|
|
95
95
|
}
|
|
96
|
-
else if (data
|
|
96
|
+
else if (data?.data?.code === 86101) {
|
|
97
97
|
// 未扫码
|
|
98
98
|
// 继续轮询
|
|
99
99
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
100
|
-
|
|
100
|
+
global?.logger?.mark(`优纪插件:扫码B站登录:未扫码,轮询中...`);
|
|
101
101
|
return pollLoginQRCode(e, qrcodeKey);
|
|
102
102
|
}
|
|
103
|
-
else if (data
|
|
103
|
+
else if (data?.data?.code === 86090) {
|
|
104
104
|
// 已扫码未确认
|
|
105
105
|
// 继续轮询
|
|
106
106
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
107
107
|
return pollLoginQRCode(e, qrcodeKey);
|
|
108
108
|
}
|
|
109
|
-
else if (data
|
|
109
|
+
else if (data?.data?.code === 86038) {
|
|
110
110
|
// 二维码已失效
|
|
111
111
|
e.reply('B站登陆二维码已失效');
|
|
112
112
|
return null;
|
|
@@ -135,7 +135,7 @@ async function checkBiliLogin(e) {
|
|
|
135
135
|
redirect: 'follow'
|
|
136
136
|
});
|
|
137
137
|
const resData = await res.json();
|
|
138
|
-
|
|
138
|
+
global?.logger?.debug(`B站验证登录状态:${JSON.stringify(resData)}`);
|
|
139
139
|
if (resData.code === 0) {
|
|
140
140
|
let uname = resData.data?.uname;
|
|
141
141
|
let mid = resData.data?.mid;
|
|
@@ -455,8 +455,13 @@ async function cookieWithBiliTicket(cookie) {
|
|
|
455
455
|
try {
|
|
456
456
|
const csrf = await readSavedCookieItems(cookie, ['bili_jct'], false);
|
|
457
457
|
const { ticket, ttl } = await getBiliTicket(csrf);
|
|
458
|
-
|
|
459
|
-
|
|
458
|
+
if (ticket && ttl) {
|
|
459
|
+
await Redis.set(BiliJctKey, ticket, { EX: ttl });
|
|
460
|
+
return cookie + `;bili_ticket=${ticket};`;
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
return cookie;
|
|
464
|
+
}
|
|
460
465
|
}
|
|
461
466
|
catch (error) {
|
|
462
467
|
logger?.error(`${error}`);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import moment from 'moment';
|
|
2
|
-
import {
|
|
2
|
+
import { Segment } from 'yunzaijs';
|
|
3
3
|
import { readSyncCookie, cookieWithBiliTicket } from './bilibili.main.models.js';
|
|
4
4
|
import BiliApi from './bilibili.main.api.js';
|
|
5
5
|
import axios from 'axios';
|
|
@@ -100,30 +100,36 @@ class BiliQuery {
|
|
|
100
100
|
formatData.data.title = desc?.title;
|
|
101
101
|
// 文章内容过长,则尝试获取全文
|
|
102
102
|
if (desc?.summary?.text?.length >= 480) {
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
if (
|
|
108
|
-
formatData.data.content = this.
|
|
109
|
-
formatData.data.
|
|
103
|
+
const fullArticleContent = await this.getFullArticleContent(this.formatUrl(desc?.jump_url));
|
|
104
|
+
if (fullArticleContent) {
|
|
105
|
+
const { readInfo, articleType } = fullArticleContent;
|
|
106
|
+
// 文章链接类型为 cv(旧类型) 或者 opus(新类型)
|
|
107
|
+
if (articleType === 'cv') {
|
|
108
|
+
formatData.data.content = this.praseFullOldTypeArticleContent(readInfo?.content);
|
|
109
|
+
if (String(formatData.data.content).length < 100) {
|
|
110
|
+
formatData.data.content = this.parseRichTextNodes(desc?.summary?.rich_text_nodes || desc?.summary?.text) || '';
|
|
111
|
+
formatData.data.pics = pics;
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
formatData.data.pics = [];
|
|
115
|
+
}
|
|
110
116
|
}
|
|
111
|
-
else {
|
|
112
|
-
|
|
117
|
+
else if (articleType === 'opus') {
|
|
118
|
+
const FullNewTypeArticleContent = this.praseFullNewTypeArticleContent(readInfo?.paragraphs);
|
|
119
|
+
if (FullNewTypeArticleContent) {
|
|
120
|
+
const { content, img } = FullNewTypeArticleContent;
|
|
121
|
+
formatData.data.content = content;
|
|
122
|
+
formatData.data.pics = img && img.length > 0 ? img : pics;
|
|
123
|
+
if (content && content.length < 100) {
|
|
124
|
+
formatData.data.content = this.parseRichTextNodes(desc?.summary?.rich_text_nodes || desc?.summary?.text);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
113
127
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
formatData.data.content = content;
|
|
118
|
-
formatData.data.pics = img && img.length > 0 ? img : pics;
|
|
119
|
-
if (content && content.length < 100) {
|
|
120
|
-
formatData.data.content = this.parseRichTextNodes(desc?.summary?.rich_text_nodes || desc?.summary?.text);
|
|
128
|
+
else {
|
|
129
|
+
formatData.data.content = this.parseRichTextNodes(desc?.summary?.rich_text_nodes || desc?.summary?.text) || '';
|
|
130
|
+
formatData.data.pics = pics;
|
|
121
131
|
}
|
|
122
132
|
}
|
|
123
|
-
else {
|
|
124
|
-
formatData.data.content = this.parseRichTextNodes(desc?.summary?.rich_text_nodes || desc?.summary?.text) || '';
|
|
125
|
-
formatData.data.pics = pics;
|
|
126
|
-
}
|
|
127
133
|
}
|
|
128
134
|
else {
|
|
129
135
|
formatData.data.content = this.parseRichTextNodes(desc?.summary?.rich_text_nodes || desc?.summary?.text) || '';
|
|
@@ -208,7 +214,7 @@ class BiliQuery {
|
|
|
208
214
|
return `<span class="bili-rich-text-module topic" href="${jumpUrl}">${node?.text}</span>`;
|
|
209
215
|
case 'RICH_TEXT_NODE_TYPE_TEXT':
|
|
210
216
|
// 正文将 \n 替换为 <br> 以实现换行
|
|
211
|
-
return node
|
|
217
|
+
return node?.text?.replace(/\n/g, '<br>') || '';
|
|
212
218
|
case 'RICH_TEXT_NODE_TYPE_AT':
|
|
213
219
|
// 处理 @ 类型,使用官方的HTML标签写法
|
|
214
220
|
return `<span data-module="desc" data-type="at" data-oid="${node?.rid}" class="bili-rich-text-module at">${node?.text}</span>`;
|
|
@@ -287,8 +293,19 @@ class BiliQuery {
|
|
|
287
293
|
});
|
|
288
294
|
return content;
|
|
289
295
|
}
|
|
290
|
-
|
|
291
|
-
*
|
|
296
|
+
/**
|
|
297
|
+
* 解析新版完整文章内容,将其转换为HTML格式的正文和图片数组。
|
|
298
|
+
* 该方法处理的是 MODULE_TYPE_CONTENT 类型的文章,文章内容由多个段落组成。
|
|
299
|
+
* 每个段落可能包含不同类型的内容,如正文、图片、链接、表情等。
|
|
300
|
+
*
|
|
301
|
+
* @param paragraphs - MODULE_TYPE_CONTENT 类型文章的段落数组,每个段落是一个对象。
|
|
302
|
+
* 每个段落对象包含一个 para_type 属性,用于标识段落类型(1表示正文,2表示图片)。
|
|
303
|
+
* 正文段落中可能包含多个 nodes,每个 node 表示一段文本或一个富文本元素。
|
|
304
|
+
* 图片段落中包含一个 pic 对象,其中 pics 是图片信息的数组。
|
|
305
|
+
* @returns 返回一个对象,包含两个属性:
|
|
306
|
+
* - content: string 类型,解析后的HTML格式的正文字符串。
|
|
307
|
+
* - img: array 类型,包含图片信息的对象数组,每个对象有 url、width 和 height 属性。
|
|
308
|
+
* 如果输入的 paragraphs 不是数组,则返回 null。
|
|
292
309
|
*/
|
|
293
310
|
static praseFullNewTypeArticleContent = (paragraphs) => {
|
|
294
311
|
if (Array.isArray(paragraphs)) {
|
|
@@ -300,54 +317,58 @@ class BiliQuery {
|
|
|
300
317
|
switch (item.para_type) {
|
|
301
318
|
case 1:
|
|
302
319
|
// 处理正文
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
jumpUrl
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
320
|
+
if (item.text?.nodes) {
|
|
321
|
+
content += item.text.nodes
|
|
322
|
+
.map((node) => {
|
|
323
|
+
let nodeType = node.type;
|
|
324
|
+
if (nodeType === 'TEXT_NODE_TYPE_RICH') {
|
|
325
|
+
let richType = node?.rich?.type;
|
|
326
|
+
switch (richType) {
|
|
327
|
+
case 'RICH_TEXT_NODE_TYPE_TOPIC':
|
|
328
|
+
// 确保链接以 https:// 开头
|
|
329
|
+
let jumpUrl = node?.rich?.jump_url;
|
|
330
|
+
if (jumpUrl && !jumpUrl.startsWith('http://') && !jumpUrl.startsWith('https://')) {
|
|
331
|
+
jumpUrl = `https://${jumpUrl}`;
|
|
332
|
+
}
|
|
333
|
+
return `<span class="bili-rich-text-module topic" href="${jumpUrl}">${node?.rich?.text}</span>`;
|
|
334
|
+
case 'RICH_TEXT_NODE_TYPE_TEXT':
|
|
335
|
+
// 正文将 \n 替换为 <br> 以实现换行
|
|
336
|
+
return node?.rich?.text.replace(/\n/g, '<br>');
|
|
337
|
+
case 'RICH_TEXT_NODE_TYPE_AT':
|
|
338
|
+
// 处理 @ 类型,使用官方的HTML标签写法
|
|
339
|
+
return `<span data-module="desc" data-type="at" data-oid="${node?.rich?.rid}" class="bili-rich-text-module at">${node?.rich?.text}</span>`;
|
|
340
|
+
case 'RICH_TEXT_NODE_TYPE_LOTTERY':
|
|
341
|
+
// 处理互动抽奖类型,使用官方的HTML标签写法
|
|
342
|
+
return `<span data-module="desc" data-type="lottery" data-oid="${node?.rich?.rid}" class="bili-rich-text-module lottery">${node?.rich?.text}</span>`;
|
|
343
|
+
case 'RICH_TEXT_NODE_TYPE_WEB':
|
|
344
|
+
// 处理 RICH_TEXT_NODE_TYPE_WEB 类型,直接拼接 text 属性
|
|
345
|
+
return node?.rich?.text;
|
|
346
|
+
case 'RICH_TEXT_NODE_TYPE_EMOJI':
|
|
347
|
+
// 处理表情类型,使用 img 标签显示表情
|
|
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;">`;
|
|
350
|
+
case 'RICH_TEXT_NODE_TYPE_GOODS':
|
|
351
|
+
// 处理商品推广类型,使用官方的HTML标签写法
|
|
352
|
+
const goods_url = node?.rich?.jump_url;
|
|
353
|
+
return `<span data-module="desc" data-type="goods" data-url="${goods_url}" data-oid="${node?.rich?.rid}" class="bili-rich-text-module goods ${node?.rich?.icon_name}">​${node?.rich?.text}</span>`;
|
|
354
|
+
default:
|
|
355
|
+
return node;
|
|
356
|
+
}
|
|
338
357
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
|
|
358
|
+
else if (nodeType === 'TEXT_NODE_TYPE_WORD') {
|
|
359
|
+
return `${node?.word?.words}<br>`;
|
|
360
|
+
}
|
|
361
|
+
})
|
|
362
|
+
.join('');
|
|
363
|
+
}
|
|
345
364
|
break;
|
|
346
365
|
case 2:
|
|
347
366
|
// 处理图片
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
367
|
+
if (item?.pic?.pics) {
|
|
368
|
+
img = img.concat(item?.pic?.pics.map((item) => {
|
|
369
|
+
return { url: item?.url, width: item?.width, height: item?.height };
|
|
370
|
+
}) || []);
|
|
371
|
+
}
|
|
351
372
|
break;
|
|
352
373
|
}
|
|
353
374
|
});
|
|
@@ -374,6 +395,7 @@ class BiliQuery {
|
|
|
374
395
|
const BiliDrawDynamicLinkUrl = 'https://m.bilibili.com/dynamic/';
|
|
375
396
|
let desc, msg = [], pics = [], author, majorType, content, dynamicTitle;
|
|
376
397
|
let title = `B站【${upName}】动态推送:\n`;
|
|
398
|
+
let dynamicType = data.type;
|
|
377
399
|
switch (data.type) {
|
|
378
400
|
case 'DYNAMIC_TYPE_AV':
|
|
379
401
|
// 处理视频动态
|
|
@@ -391,7 +413,7 @@ class BiliQuery {
|
|
|
391
413
|
`时间:${author ? moment(author.pub_ts * 1000).format('YYYY年MM月DD日 HH:mm:ss') : ''}`
|
|
392
414
|
];
|
|
393
415
|
pics = [Segment.image(desc?.cover)];
|
|
394
|
-
return { msg, pics };
|
|
416
|
+
return { msg, pics, dynamicType };
|
|
395
417
|
case 'DYNAMIC_TYPE_WORD':
|
|
396
418
|
// 处理文字动态
|
|
397
419
|
author = data?.modules?.module_author;
|
|
@@ -421,7 +443,7 @@ class BiliQuery {
|
|
|
421
443
|
`链接:${BiliDrawDynamicLinkUrl}${data.id_str}\n`,
|
|
422
444
|
`时间:${author ? moment(author.pub_ts * 1000).format('YYYY年MM月DD日 HH:mm:ss') : ''}`
|
|
423
445
|
];
|
|
424
|
-
return { msg, pics };
|
|
446
|
+
return { msg, pics, dynamicType };
|
|
425
447
|
case 'DYNAMIC_TYPE_DRAW':
|
|
426
448
|
// 处理图文动态
|
|
427
449
|
author = data?.modules?.module_author;
|
|
@@ -467,7 +489,7 @@ class BiliQuery {
|
|
|
467
489
|
`链接:${BiliDrawDynamicLinkUrl}${data.id_str}\n`,
|
|
468
490
|
`时间:${author ? moment(author.pub_ts * 1000).format('YYYY年MM月DD日 HH:mm:ss') : ''}`
|
|
469
491
|
];
|
|
470
|
-
return { msg, pics };
|
|
492
|
+
return { msg, pics, dynamicType };
|
|
471
493
|
case 'DYNAMIC_TYPE_ARTICLE':
|
|
472
494
|
// 处理文章动态
|
|
473
495
|
author = data?.modules?.module_author;
|
|
@@ -510,7 +532,7 @@ class BiliQuery {
|
|
|
510
532
|
`链接:${this.formatUrl(desc.jump_url)}\n`,
|
|
511
533
|
`时间:${author ? moment(author.pub_ts * 1000).format('YYYY年MM月DD日 HH:mm:ss') : ''}`
|
|
512
534
|
];
|
|
513
|
-
return { msg, pics };
|
|
535
|
+
return { msg, pics, dynamicType };
|
|
514
536
|
case 'DYNAMIC_TYPE_FORWARD':
|
|
515
537
|
// 处理转发动态
|
|
516
538
|
author = data?.modules?.module_author;
|
|
@@ -540,7 +562,7 @@ class BiliQuery {
|
|
|
540
562
|
'\n---以下为转发内容---\n',
|
|
541
563
|
...origContent
|
|
542
564
|
];
|
|
543
|
-
return { msg, pics };
|
|
565
|
+
return { msg, pics, dynamicType };
|
|
544
566
|
case 'DYNAMIC_TYPE_LIVE_RCMD':
|
|
545
567
|
// 处理直播动态
|
|
546
568
|
desc = data?.modules?.module_dynamic?.major?.live_rcmd?.content;
|
|
@@ -553,10 +575,10 @@ class BiliQuery {
|
|
|
553
575
|
title = `B站【${upName}】直播动态推送:\n`;
|
|
554
576
|
msg = [title, `-----------------------------\n`, `标题:${desc.title}\n`, `链接:https:${desc.link}`];
|
|
555
577
|
pics = [Segment.image(desc.cover)];
|
|
556
|
-
return { msg, pics };
|
|
578
|
+
return { msg, pics, dynamicType };
|
|
557
579
|
default:
|
|
558
580
|
// 处理未定义的动态类型
|
|
559
|
-
|
|
581
|
+
global?.logger?.mark(`未处理的B站推送【${upName}】:${data.type}`);
|
|
560
582
|
return 'continue';
|
|
561
583
|
}
|
|
562
584
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import QRCode from 'qrcode';
|
|
2
|
-
import { Redis,
|
|
2
|
+
import { Redis, Segment, Bot } from 'yunzaijs';
|
|
3
3
|
import Config from '../../utils/config.js';
|
|
4
4
|
import { renderPage } from '../../utils/image.js';
|
|
5
5
|
import { BiliGetWebData } from './bilibili.main.get.web.data.js';
|
|
@@ -37,6 +37,7 @@ class BiliTask {
|
|
|
37
37
|
let biliConfigData = await Config.getUserConfig('bilibili', 'config');
|
|
38
38
|
let biliPushData = await Config.getUserConfig('bilibili', 'push');
|
|
39
39
|
let interval = biliConfigData?.interval || 7200;
|
|
40
|
+
logger.debug(`当前B站功能配置:${JSON.stringify(biliConfigData)}`);
|
|
40
41
|
const uidMap = new Map(); // 存放group 和 private 对应所属 uid 与推送信息的映射
|
|
41
42
|
const dynamicList = {}; // 存放获取的所有动态,键为 uid,值为动态数组
|
|
42
43
|
await this.processBiliData(biliPushData, biliConfigData, uidMap, dynamicList);
|
|
@@ -59,6 +60,8 @@ class BiliTask {
|
|
|
59
60
|
uidMap.set(chatType, new Map());
|
|
60
61
|
}
|
|
61
62
|
const chatTypeMap = uidMap.get(chatType); // 建立当前 chatType (group 或 private) 的 uid 映射
|
|
63
|
+
if (chatTypeMap === undefined)
|
|
64
|
+
continue; // 如果 chatTypeMap 未定义,跳过此次循环
|
|
62
65
|
for (let chatId in biliPushData[chatType]) {
|
|
63
66
|
const subUpsOfChat = Array.prototype.slice.call(biliPushData[chatType][chatId] || []);
|
|
64
67
|
for (let subInfoOfup of subUpsOfChat) {
|
|
@@ -148,10 +151,18 @@ class BiliTask {
|
|
|
148
151
|
}
|
|
149
152
|
}
|
|
150
153
|
}
|
|
151
|
-
|
|
154
|
+
/**
|
|
155
|
+
* 发送动态消息
|
|
156
|
+
* @param chatId 聊天 ID
|
|
157
|
+
* @param bot_id 机器人 ID
|
|
158
|
+
* @param upName 用户名
|
|
159
|
+
* @param pushDynamicData 推送动态数据
|
|
160
|
+
* @param biliConfigData 哔哩配置数据
|
|
161
|
+
* @param chatType 聊天类型
|
|
162
|
+
*/
|
|
152
163
|
async sendDynamic(chatId, bot_id, upName, pushDynamicData, biliConfigData, chatType) {
|
|
153
164
|
const id_str = pushDynamicData.id_str;
|
|
154
|
-
let sended, markKey;
|
|
165
|
+
let sended = null, markKey = '';
|
|
155
166
|
if (chatType === 'group') {
|
|
156
167
|
markKey = this.groupKey;
|
|
157
168
|
sended = await Redis.get(`${markKey}${chatId}:${id_str}`);
|
|
@@ -162,6 +173,11 @@ class BiliTask {
|
|
|
162
173
|
}
|
|
163
174
|
if (sended)
|
|
164
175
|
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)) : []);
|
|
165
181
|
if (!!biliConfigData.pushMsgMode) {
|
|
166
182
|
const { data, uid } = await BiliQuery.formatDynamicData(pushDynamicData); // 处理动态数据
|
|
167
183
|
const extentData = { ...data };
|
|
@@ -192,7 +208,17 @@ class BiliTask {
|
|
|
192
208
|
if (!imgs)
|
|
193
209
|
return;
|
|
194
210
|
Redis.set(`${markKey}${chatId}:${id_str}`, '1', { EX: 3600 * 72 }); // 设置已发送标记
|
|
195
|
-
|
|
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
|
+
}
|
|
196
222
|
for (let i = 0; i < imgs.length; i++) {
|
|
197
223
|
const image = imgs[i];
|
|
198
224
|
await this.sendMessage(chatId, bot_id, chatType, Segment.image(image));
|
|
@@ -203,24 +229,52 @@ class BiliTask {
|
|
|
203
229
|
else {
|
|
204
230
|
const dynamicMsg = await BiliQuery.formatTextDynamicData(upName, pushDynamicData, false, biliConfigData); // 构建图文动态消息
|
|
205
231
|
Redis.set(`${markKey}${chatId}:${id_str}`, '1', { EX: 3600 * 72 }); // 设置已发送标记
|
|
206
|
-
if (dynamicMsg
|
|
232
|
+
if (dynamicMsg === undefined || dynamicMsg === 'continue') {
|
|
207
233
|
return 'return'; // 如果动态消息构建失败,则直接返回
|
|
208
234
|
}
|
|
209
|
-
|
|
210
|
-
|
|
235
|
+
const getBanWords = biliConfigData?.banWords;
|
|
236
|
+
if (getBanWords && Array.isArray(getBanWords) && getBanWords.length > 0) {
|
|
237
|
+
const banWords = new RegExp(getBanWords.join('|'), 'g'); // 构建屏蔽关键字正则表达式
|
|
211
238
|
if (banWords.test(dynamicMsg.msg.join(''))) {
|
|
212
239
|
return 'return'; // 如果动态消息包含屏蔽关键字,则直接返回
|
|
213
240
|
}
|
|
214
241
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
242
|
+
let mergeTextPic = !!biliConfigData.mergeTextPic === false ? false : true; // 是否合并文本和图片,默认为 true
|
|
243
|
+
if (mergeTextPic === true) {
|
|
244
|
+
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);
|
|
256
|
+
}
|
|
257
|
+
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
|
+
}
|
|
221
275
|
}
|
|
276
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
222
277
|
}
|
|
223
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
224
278
|
}
|
|
225
279
|
}
|
|
226
280
|
/**
|
|
@@ -312,7 +366,7 @@ class BiliTask {
|
|
|
312
366
|
?.pickGroup(String(chatId))
|
|
313
367
|
.sendMsg(message) // 发送群聊
|
|
314
368
|
.catch((error) => {
|
|
315
|
-
|
|
369
|
+
global?.logger?.error(`群组[${chatId}]推送失败:${JSON.stringify(error)}`);
|
|
316
370
|
});
|
|
317
371
|
}
|
|
318
372
|
else if (chatType === 'private') {
|
|
@@ -320,7 +374,7 @@ class BiliTask {
|
|
|
320
374
|
?.pickFriend(String(chatId))
|
|
321
375
|
.sendMsg(message)
|
|
322
376
|
.catch((error) => {
|
|
323
|
-
|
|
377
|
+
global?.logger?.error(`用户[${chatId}]推送失败:${JSON.stringify(error)}`);
|
|
324
378
|
}); // 发送好友私聊
|
|
325
379
|
}
|
|
326
380
|
}
|
|
@@ -7,7 +7,8 @@ import { readSyncCookie, cookieWithBiliTicket } from './bilibili.main.models.js'
|
|
|
7
7
|
async function getWebId(uid) {
|
|
8
8
|
const w_webid_key = 'Yz:yuki:bili:w_webid';
|
|
9
9
|
const w_webid = await Redis.get(w_webid_key);
|
|
10
|
-
|
|
10
|
+
const keyTTL = await Redis.ttl(w_webid_key);
|
|
11
|
+
if (w_webid && keyTTL < 259200) {
|
|
11
12
|
return String(w_webid);
|
|
12
13
|
}
|
|
13
14
|
else {
|
|
@@ -28,8 +29,10 @@ async function getWebId(uid) {
|
|
|
28
29
|
const decoded__RENDER_DATA__JsonString = decodeURIComponent(__RENDER_DATA__[1]);
|
|
29
30
|
const accessIdRegex = /"access_id":"(.*?)"/;
|
|
30
31
|
const access_id = decoded__RENDER_DATA__JsonString.match(accessIdRegex);
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
const ExpirationTimeRegex = /document.getElementById\("__RENDER_DATA__"\).*?setTimeout\(function\(\)\s*{window.location.reload\(true\);},\s*(\d+)\s*\*\s*(\d+)\);<\/script>/;
|
|
33
|
+
const ExpirationTime = htmlContent.match(ExpirationTimeRegex);
|
|
34
|
+
if (access_id && access_id[1] && ExpirationTime && ExpirationTime[1]) {
|
|
35
|
+
await Redis.set(w_webid_key, access_id[1], { EX: Number(ExpirationTime[1]) });
|
|
33
36
|
return String(access_id[1]);
|
|
34
37
|
}
|
|
35
38
|
else {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { WeiboQuery } from './weibo.query.js';
|
|
2
|
+
import { WeiboApi } from './weibo.main.api.js';
|
|
3
|
+
import { WeiboQuery } from './weibo.main.query.js';
|
|
5
4
|
|
|
6
5
|
class WeiboGetWebData {
|
|
7
6
|
e;
|
|
@@ -48,7 +47,7 @@ class WeiboGetWebData {
|
|
|
48
47
|
return data.cards.filter(WeiboQuery.filterCardTypeCustom);
|
|
49
48
|
}
|
|
50
49
|
catch (error) {
|
|
51
|
-
|
|
50
|
+
global?.logger?.mark('微博推送:Error fetching sub list:', error);
|
|
52
51
|
return [];
|
|
53
52
|
}
|
|
54
53
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import moment from 'moment';
|
|
2
2
|
import fetch from 'node-fetch';
|
|
3
|
-
import { WeiboApi } from './weibo.api.js';
|
|
4
|
-
import {
|
|
3
|
+
import { WeiboApi } from './weibo.main.api.js';
|
|
4
|
+
import { Segment } from 'yunzaijs';
|
|
5
5
|
import { JSDOM } from 'jsdom';
|
|
6
6
|
|
|
7
7
|
class WeiboQuery {
|
|
@@ -29,9 +29,12 @@ class WeiboQuery {
|
|
|
29
29
|
else if (raw_post?.mblog?.pics) {
|
|
30
30
|
return 'DYNAMIC_TYPE_DRAW';
|
|
31
31
|
}
|
|
32
|
-
else {
|
|
32
|
+
else if (!raw_post?.mblog?.pics && String(raw_post?.mblog?.text).trim().length > 0) {
|
|
33
33
|
return 'DYNAMIC_TYPE_ARTICLE';
|
|
34
34
|
}
|
|
35
|
+
else {
|
|
36
|
+
return 'DYNAMIC_TYPE_UNKNOWN';
|
|
37
|
+
}
|
|
35
38
|
}
|
|
36
39
|
/**筛选正文 */
|
|
37
40
|
static filterText(raw_text) {
|
|
@@ -77,6 +80,7 @@ class WeiboQuery {
|
|
|
77
80
|
formatData.data.name = nick_name;
|
|
78
81
|
/**头像框 */
|
|
79
82
|
formatData.data.pendant = '';
|
|
83
|
+
/**生成日期 */
|
|
80
84
|
formatData.data.created = moment().format('YYYY年MM月DD日 HH:mm:ss');
|
|
81
85
|
formatData.data.type = type;
|
|
82
86
|
switch (type) {
|
|
@@ -195,7 +199,7 @@ class WeiboQuery {
|
|
|
195
199
|
}
|
|
196
200
|
}
|
|
197
201
|
catch (err) {
|
|
198
|
-
|
|
202
|
+
global?.logger?.mark(`优纪插件:获取微博动态全文出错:https://m.weibo.cn/detail/${info?.mid}`);
|
|
199
203
|
}
|
|
200
204
|
}
|
|
201
205
|
/**动态发布时间 */
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import QRCode from 'qrcode';
|
|
2
|
-
import { Redis,
|
|
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 { WeiboGetWebData } from './weibo.get.web.data.js';
|
|
6
|
-
import { WeiboQuery } from './weibo.query.js';
|
|
5
|
+
import { WeiboGetWebData } from './weibo.main.get.web.data.js';
|
|
6
|
+
import { WeiboQuery } from './weibo.main.query.js';
|
|
7
7
|
|
|
8
8
|
class WeiboTask {
|
|
9
9
|
taskName;
|
|
@@ -19,6 +19,7 @@ class WeiboTask {
|
|
|
19
19
|
let weiboConfigData = await Config.getUserConfig('weibo', 'config');
|
|
20
20
|
let weiboPushData = await Config.getUserConfig('weibo', 'push');
|
|
21
21
|
let interval = weiboConfigData.interval || 7200; // 推送间隔时间,单位为秒,默认2小时
|
|
22
|
+
logger.debug(`当前微博功能配置:${JSON.stringify(weiboConfigData)}`);
|
|
22
23
|
const uidMap = new Map(); // 存放group 和 private 对应所属 uid 与推送信息的映射
|
|
23
24
|
const dynamicList = {}; // 存放获取的所有动态,键为 uid,值为动态数组
|
|
24
25
|
await this.processWeiboData(weiboPushData, uidMap, dynamicList);
|
|
@@ -39,6 +40,8 @@ class WeiboTask {
|
|
|
39
40
|
uidMap.set(chatType, new Map());
|
|
40
41
|
}
|
|
41
42
|
const chatTypeMap = uidMap.get(chatType); // 建立当前 chatType (group 或 private) 的 uid 映射
|
|
43
|
+
if (chatTypeMap === undefined)
|
|
44
|
+
continue; // 如果 chatTypeMap 未定义,跳过此次循环
|
|
42
45
|
for (let chatId in weiboPushData[chatType]) {
|
|
43
46
|
const subUpsOfChat = Array.prototype.slice.call(weiboPushData[chatType][chatId] || []);
|
|
44
47
|
for (let subInfoOfup of subUpsOfChat) {
|
|
@@ -126,7 +129,7 @@ class WeiboTask {
|
|
|
126
129
|
*/
|
|
127
130
|
async sendDynamic(chatId, bot_id, upName, pushDynamicData, weiboConfigData, chatType) {
|
|
128
131
|
const id_str = WeiboQuery.getDynamicId(pushDynamicData); // 获取动态 ID
|
|
129
|
-
let sended, markKey;
|
|
132
|
+
let sended = null, markKey = '';
|
|
130
133
|
if (chatType === 'group') {
|
|
131
134
|
markKey = this.groupKey;
|
|
132
135
|
sended = await Redis.get(`${markKey}${chatId}:${id_str}`);
|
|
@@ -167,7 +170,7 @@ class WeiboTask {
|
|
|
167
170
|
if (!imgs)
|
|
168
171
|
return;
|
|
169
172
|
Redis.set(`${markKey}${chatId}:${id_str}`, '1', { EX: 3600 * 72 }); // 设置已发送标记
|
|
170
|
-
|
|
173
|
+
global?.logger?.mark('优纪插件:微博动态执行推送');
|
|
171
174
|
for (let i = 0; i < imgs.length; i++) {
|
|
172
175
|
const image = imgs[i];
|
|
173
176
|
await this.sendMessage(chatId, bot_id, chatType, Segment.image(image));
|
|
@@ -178,24 +181,32 @@ class WeiboTask {
|
|
|
178
181
|
else {
|
|
179
182
|
const dynamicMsg = await WeiboQuery.formatTextDynamicData(upName, pushDynamicData, false, weiboConfigData); //构建文字动态消息
|
|
180
183
|
Redis.set(`${markKey}${chatId}:${id_str}`, '1', { EX: 3600 * 72 }); // 设置已发送标记
|
|
181
|
-
if (dynamicMsg
|
|
184
|
+
if (dynamicMsg === undefined || dynamicMsg === 'continue') {
|
|
182
185
|
return 'return'; // 如果动态消息构建失败或内部资源获取失败,则直接返回
|
|
183
186
|
}
|
|
184
|
-
|
|
185
|
-
|
|
187
|
+
const getBanWords = weiboConfigData?.banWords;
|
|
188
|
+
if (getBanWords && Array.isArray(getBanWords) && getBanWords.length > 0) {
|
|
189
|
+
const banWords = new RegExp(getBanWords.join('|'), 'g'); // 构建屏蔽关键字正则表达式
|
|
186
190
|
if (banWords.test(dynamicMsg.msg.join(''))) {
|
|
187
191
|
return 'return'; // 如果动态消息包含屏蔽关键字,则直接返回
|
|
188
192
|
}
|
|
189
193
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
194
|
+
let mergeTextPic = !!weiboConfigData.mergeTextPic === false ? false : true; // 是否合并文字和图片,默认为 true
|
|
195
|
+
if (mergeTextPic) {
|
|
196
|
+
const mergeMsg = [...dynamicMsg.msg, ...dynamicMsg.pics];
|
|
197
|
+
await this.sendMessage(chatId, bot_id, chatType, mergeMsg);
|
|
198
|
+
}
|
|
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
|
+
}
|
|
196
207
|
}
|
|
208
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
197
209
|
}
|
|
198
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
199
210
|
}
|
|
200
211
|
}
|
|
201
212
|
/**
|
|
@@ -287,7 +298,7 @@ class WeiboTask {
|
|
|
287
298
|
?.pickGroup(String(chatId))
|
|
288
299
|
.sendMsg(message) // 发送群聊
|
|
289
300
|
.catch(error => {
|
|
290
|
-
|
|
301
|
+
global?.logger?.error(`群组[${chatId}]推送失败:${JSON.stringify(error)}`);
|
|
291
302
|
});
|
|
292
303
|
}
|
|
293
304
|
else if (chatType === 'private') {
|
|
@@ -295,7 +306,7 @@ class WeiboTask {
|
|
|
295
306
|
?.pickFriend(String(chatId))
|
|
296
307
|
.sendMsg(message)
|
|
297
308
|
.catch(error => {
|
|
298
|
-
|
|
309
|
+
global?.logger?.error(`用户[${chatId}]推送失败:${JSON.stringify(error)}`);
|
|
299
310
|
}); // 发送好友私聊
|
|
300
311
|
}
|
|
301
312
|
}
|
package/lib/utils/config.js
CHANGED
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 }),
|
|
@@ -43,7 +43,7 @@ class Image extends Picture {
|
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
// 存储单例实例
|
|
46
|
-
let instance
|
|
46
|
+
let instance;
|
|
47
47
|
// 存储任务队列
|
|
48
48
|
const queue = [];
|
|
49
49
|
// 标记当前是否有任务正在处理
|
|
@@ -47,6 +47,8 @@ class YukiPuppeteerRender {
|
|
|
47
47
|
await page.addStyleTag({ content: Options.addStyle });
|
|
48
48
|
}
|
|
49
49
|
const boundingBox = await element.boundingBox(); // 获取内容区域的边界框信息
|
|
50
|
+
if (!boundingBox)
|
|
51
|
+
return false;
|
|
50
52
|
const num = Options?.isSplit ? Math.ceil(boundingBox.height / pageHeight) : 1; // 根据是否需要分片,计算分片数量,默认为 1
|
|
51
53
|
pageHeight = Math.round(boundingBox.height / num); //动态调整分片高度,防止过短影响观感。
|
|
52
54
|
await page.setViewport({ width: boundingBox.width + 50, height: pageHeight + 100 });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yz-yuki-plugin",
|
|
3
|
-
"version": "2.0.6-
|
|
3
|
+
"version": "2.0.6-10",
|
|
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": "^
|
|
39
|
+
"puppeteer": "^24.1.0",
|
|
40
40
|
"qrcode": "^1.5.4",
|
|
41
41
|
"react": "^18.3.1",
|
|
42
42
|
"react-dom": "^18.3.1",
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/chalk": "2.2.0",
|
|
49
|
+
"@types/chokidar": "2.1.7",
|
|
49
50
|
"@types/jsdom": "^21.1.7",
|
|
50
51
|
"@types/lodash": "^4.17.7",
|
|
51
52
|
"@types/md5": "^2.3.5",
|
|
@@ -59,23 +60,24 @@
|
|
|
59
60
|
"axios": "^1.7.9",
|
|
60
61
|
"chokidar": "^4.0.1",
|
|
61
62
|
"husky": "^9.1.6",
|
|
63
|
+
"icqq": "^0.6.10",
|
|
62
64
|
"jsdom": "^24.1.1",
|
|
63
65
|
"json5": "^2.2.3",
|
|
64
|
-
"jsxp": "^1.
|
|
66
|
+
"jsxp": "^1.1.2",
|
|
65
67
|
"lodash": "^4.17.21",
|
|
66
|
-
"lvyjs": "^0.
|
|
68
|
+
"lvyjs": "^0.2.14",
|
|
67
69
|
"md5": "^2.3.0",
|
|
68
70
|
"node-fetch": "^3.3.2",
|
|
69
71
|
"postcss": "^8.4.47",
|
|
70
72
|
"prettier": "^3.4.2",
|
|
71
|
-
"puppeteer": "^
|
|
73
|
+
"puppeteer": "^24.1.0",
|
|
72
74
|
"qrcode": "^1.5.4",
|
|
73
75
|
"react": "^18.3.1",
|
|
74
76
|
"react-dom": "^18.3.1",
|
|
75
77
|
"redis": "^4.7.0",
|
|
76
78
|
"tailwindcss": "^3.4.14",
|
|
77
79
|
"ts-node": "^10.9.2",
|
|
78
|
-
"tsx": "^4.19.
|
|
80
|
+
"tsx": "^4.19.2",
|
|
79
81
|
"typescript": "^5.5.4",
|
|
80
82
|
"yaml": "^2.6.1",
|
|
81
83
|
"yunzaijs": "^1.0.0-rc.5"
|
|
@@ -105,6 +107,6 @@
|
|
|
105
107
|
"registry": "https://registry.npmjs.org"
|
|
106
108
|
},
|
|
107
109
|
"engines": {
|
|
108
|
-
"node": ">=
|
|
110
|
+
"node": ">=20"
|
|
109
111
|
}
|
|
110
112
|
}
|
|
File without changes
|