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 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 = require('jsxp/.puppeteerrc');
44
+ module.exports = { skipDownload, executablePath };
package/CHANGELOG.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # 2.0.6
2
2
  * 优化文字动态图片资源的发送
3
3
  * 依赖升级
4
+ * 新增哔哩直播动态@全体成员功能,开启前请检查机器人管理员权限和所在聊天类型是否支持
4
5
 
5
6
  # 2.0.5
6
7
  * 优化ck
package/README.md CHANGED
@@ -8,6 +8,29 @@
8
8
 
9
9
  [![访问量](https://profile-counter.glitch.me/yuki-plugin/count.svg)](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站动态的周期,Cron表达式,作用域共6位,具体方法浏览器搜索 “node-schedule cron表达式”,示例:
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
- # 检测微博动态的周期,Cron表达式,作用域共6位,具体方法浏览器搜索 “node-schedule cron表达式”,示例:
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
 
@@ -1,6 +1,6 @@
1
1
  import JSON from 'json5';
2
2
  import lodash from 'lodash';
3
- import { Messages, Bot, Redis } from 'yunzaijs';
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
- let biliLoginCk = await pollLoginQRCode(e, token);
174
- if (lodash.trim(biliLoginCk).length != 0) {
175
- await saveLoginCookie(e, biliLoginCk);
176
- e.reply(`get bilibili LoginCk:成功!`);
177
- const result = await postGateway(biliLoginCk); //激活ck
178
- const { code, data } = await result.data; // 解析校验结果
179
- switch (code) {
180
- case 0:
181
- (logger ?? Bot.logger)?.mark(`优纪插件:获取biliLoginCK,Gateway校验成功:${JSON.stringify(data)}`);
182
- break;
183
- default:
184
- (logger ?? Bot.logger)?.mark(`优纪插件:获取biliLoginCK,Gateway校验失败:${JSON.stringify(data)}`);
185
- break;
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
- (logger ?? Bot.logger)?.info(`yuki-plugin Login bilibili Failed:${Error}`);
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
- (logger ?? Bot.logger)?.mark(`优纪插件:绑定localCK,Gateway校验成功:${JSON.stringify(data)}`);
270
+ global?.logger?.mark(`优纪插件:绑定localCK,Gateway校验成功:${JSON.stringify(data)}`);
267
271
  break;
268
272
  default:
269
- (logger ?? Bot.logger)?.mark(`优纪插件:绑定localCK,Gateway校验失败:${JSON.stringify(data)}`);
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
- (logger ?? Bot.logger)?.mark(`优纪插件:B站临时ck刷新error:${error}`);
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 && userInfo.length !== 0) {
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, Bot, Redis } from 'yunzaijs';
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.data.qrcode_key;
35
- const qrcodeUrl = res.data.url;
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.data.code === 0) {
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.data.code === 86101) {
96
+ else if (data?.data?.code === 86101) {
97
97
  // 未扫码
98
98
  // 继续轮询
99
99
  await new Promise(resolve => setTimeout(resolve, 2000));
100
- (logger ?? Bot.logger)?.mark(`优纪插件:扫码B站登录:未扫码,轮询中...`);
100
+ global?.logger?.mark(`优纪插件:扫码B站登录:未扫码,轮询中...`);
101
101
  return pollLoginQRCode(e, qrcodeKey);
102
102
  }
103
- else if (data.data.code === 86090) {
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.data.code === 86038) {
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
- Bot?.logger?.debug(`B站验证登录状态:${JSON.stringify(resData)}`);
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
- await Redis.set(BiliJctKey, ticket, { EX: ttl });
459
- return cookie + `;bili_ticket=${ticket};`;
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 { Bot, Segment } from 'yunzaijs';
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 { readInfo, articleType } = await this.getFullArticleContent(this.formatUrl(desc?.jump_url));
104
- // 文章链接类型为 cv(旧类型) 或者 opus(新类型)
105
- if (articleType === 'cv') {
106
- formatData.data.content = this.praseFullOldTypeArticleContent(readInfo?.content);
107
- if (String(formatData.data.content).length < 100) {
108
- formatData.data.content = this.parseRichTextNodes(desc?.summary?.rich_text_nodes || desc?.summary?.text) || '';
109
- formatData.data.pics = pics;
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
- formatData.data.pics = [];
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
- else if (articleType === 'opus') {
116
- const { content, img } = this.praseFullNewTypeArticleContent(readInfo?.paragraphs);
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.text.replace(/\n/g, '<br>');
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
- * @param paragraphs - MODULE_TYPE_CONTENT 类型文章的段落数组
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
- content += item.text.nodes
304
- .map((node) => {
305
- let nodeType = node.type;
306
- if (nodeType === 'TEXT_NODE_TYPE_RICH') {
307
- let richType = node?.rich?.type;
308
- switch (richType) {
309
- case 'RICH_TEXT_NODE_TYPE_TOPIC':
310
- // 确保链接以 https:// 开头
311
- let jumpUrl = node?.rich?.jump_url;
312
- if (jumpUrl && !jumpUrl.startsWith('http://') && !jumpUrl.startsWith('https://')) {
313
- jumpUrl = `https://${jumpUrl}`;
314
- }
315
- return `<span class="bili-rich-text-module topic" href="${jumpUrl}">${node?.rich?.text}</span>`;
316
- case 'RICH_TEXT_NODE_TYPE_TEXT':
317
- // 正文将 \n 替换为 <br> 以实现换行
318
- return node?.rich?.text.replace(/\n/g, '<br>');
319
- case 'RICH_TEXT_NODE_TYPE_AT':
320
- // 处理 @ 类型,使用官方的HTML标签写法
321
- return `<span data-module="desc" data-type="at" data-oid="${node?.rich?.rid}" class="bili-rich-text-module at">${node?.rich?.text}</span>`;
322
- case 'RICH_TEXT_NODE_TYPE_LOTTERY':
323
- // 处理互动抽奖类型,使用官方的HTML标签写法
324
- return `<span data-module="desc" data-type="lottery" data-oid="${node?.rich?.rid}" class="bili-rich-text-module lottery">${node?.rich?.text}</span>`;
325
- case 'RICH_TEXT_NODE_TYPE_WEB':
326
- // 处理 RICH_TEXT_NODE_TYPE_WEB 类型,直接拼接 text 属性
327
- return node?.rich?.text;
328
- case 'RICH_TEXT_NODE_TYPE_EMOJI':
329
- // 处理表情类型,使用 img 标签显示表情
330
- const emoji = node?.rich?.emoji;
331
- return `<img src="${emoji?.icon_url}" alt="${emoji?.text}" title="${emoji?.text}" style="vertical-align: middle; width: ${emoji?.size}em; height: ${emoji?.size}em;">`;
332
- case 'RICH_TEXT_NODE_TYPE_GOODS':
333
- // 处理商品推广类型,使用官方的HTML标签写法
334
- const goods_url = node?.rich?.jump_url;
335
- 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}">&ZeroWidthSpace;${node?.rich?.text}</span>`;
336
- default:
337
- return node;
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}">&ZeroWidthSpace;${node?.rich?.text}</span>`;
354
+ default:
355
+ return node;
356
+ }
338
357
  }
339
- }
340
- else if (nodeType === 'TEXT_NODE_TYPE_WORD') {
341
- return `${node?.word?.words}<br>`;
342
- }
343
- })
344
- .join('');
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
- img = img.concat(item?.pic?.pics.map((item) => {
349
- return { url: item?.url, width: item?.width, height: item?.height };
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
- (Bot.logger ?? logger)?.mark(`未处理的B站推送【${upName}】:${data.type}`);
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, Bot, Segment } from 'yunzaijs';
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
- (logger ?? Bot.logger)?.mark('优纪插件:B站动态执行推送');
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 == 'continue') {
232
+ if (dynamicMsg === undefined || dynamicMsg === 'continue') {
207
233
  return 'return'; // 如果动态消息构建失败,则直接返回
208
234
  }
209
- if (biliConfigData.banWords.length > 0) {
210
- const banWords = new RegExp(biliConfigData.banWords.join('|'), 'g'); // 构建屏蔽关键字正则表达式
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
- await this.sendMessage(chatId, bot_id, chatType, dynamicMsg.msg);
216
- const pics = dynamicMsg.pics;
217
- if (pics && pics.length > 0) {
218
- for (let i = 0; i < pics.length; i++) {
219
- await this.sendMessage(chatId, bot_id, chatType, pics[i]);
220
- await this.randomDelay(1000, 2000); // 随机延时1-2秒
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
- (logger ?? Bot.logger)?.error(`群组[${chatId}]推送失败:${JSON.stringify(error)}`);
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
- (logger ?? Bot.logger)?.error(`用户[${chatId}]推送失败:${JSON.stringify(error)}`);
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
- if (w_webid) {
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
- if (access_id && access_id[1]) {
32
- await Redis.set(w_webid_key, access_id[1], { EX: 43197 * 1000 });
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 { Bot } from 'yunzaijs';
3
- import { WeiboApi } from './weibo.api.js';
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
- (logger ?? Bot.logger)?.mark('微博推送:Error fetching sub list:', error);
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 { Bot, Segment } from 'yunzaijs';
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
- (logger ?? Bot.logger)?.mark(`优纪插件:获取微博动态全文出错:https://m.weibo.cn/detail/${info?.mid}`);
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, Bot, Segment } from 'yunzaijs';
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
- (logger ?? Bot.logger)?.mark('优纪插件:微博动态执行推送');
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 == 'continue') {
184
+ if (dynamicMsg === undefined || dynamicMsg === 'continue') {
182
185
  return 'return'; // 如果动态消息构建失败或内部资源获取失败,则直接返回
183
186
  }
184
- if (weiboConfigData.banWords.length > 0) {
185
- const banWords = new RegExp(weiboConfigData.banWords.join('|'), 'g'); // 构建屏蔽关键字正则表达式
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
- await this.sendMessage(chatId, bot_id, chatType, dynamicMsg.msg);
191
- const pics = dynamicMsg.pics;
192
- if (pics && pics.length > 0) {
193
- for (let i = 0; i < pics.length; i++) {
194
- await this.sendMessage(chatId, bot_id, chatType, pics[i]);
195
- await this.randomDelay(1000, 2000); // 随机延时1-2秒
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
- (logger ?? Bot.logger)?.error(`群组[${chatId}]推送失败:${JSON.stringify(error)}`);
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
- (logger ?? Bot.logger)?.error(`用户[${chatId}]推送失败:${JSON.stringify(error)}`);
309
+ global?.logger?.error(`用户[${chatId}]推送失败:${JSON.stringify(error)}`);
299
310
  }); // 发送好友私聊
300
311
  }
301
312
  }
@@ -1,6 +1,6 @@
1
1
  import * as fs from 'fs';
2
2
  import YAML from 'yaml';
3
- import chokidar from 'chokidar';
3
+ import * as chokidar from 'chokidar';
4
4
  import lodash from 'lodash';
5
5
  import path from 'path';
6
6
  import { _paths } from './paths.js';
@@ -15,11 +15,11 @@ class Image extends Picture {
15
15
  // 继承父类实例
16
16
  super();
17
17
  // 父类已经实例化组件渲染对象
18
- //this.Com;
18
+ //this.component;
19
19
  // 父类已经实例化启动 Puppeteer
20
- //this.Pup.start();
20
+ //this.puppeteer.start();
21
21
  // 初始化 YukiPuppeteerRender 实例
22
- this.yukiPuppeteerRender = new YukiPuppeteerRender(this.Pup);
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.Com.compile({
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 = null;
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-0",
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.0.4",
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": "^23.10.1",
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.0.4",
66
+ "jsxp": "^1.1.2",
65
67
  "lodash": "^4.17.21",
66
- "lvyjs": "^0.1.4",
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": "^23.10.1",
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.0",
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": ">=16.14.0"
110
+ "node": ">=20"
109
111
  }
110
112
  }