koishi-plugin-wordpress-notifier 2.9.1 → 2.9.3
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/README.md +76 -14
- package/lib/commands.d.ts +5 -1
- package/lib/commands.js +214 -280
- package/lib/index.d.ts +4 -0
- package/lib/index.js +120 -40
- package/lib/push.d.ts +4 -5
- package/lib/push.js +86 -242
- package/lib/schedule.d.ts +6 -0
- package/lib/schedule.js +168 -39
- package/lib/storage.d.ts +4 -0
- package/lib/storage.js +93 -54
- package/lib/wordpress.d.ts +77 -54
- package/lib/wordpress.js +349 -252
- package/package.json +40 -39
package/lib/push.js
CHANGED
|
@@ -7,6 +7,7 @@ const crypto_1 = require("crypto");
|
|
|
7
7
|
class PushService {
|
|
8
8
|
constructor(ctx, config, storageService) {
|
|
9
9
|
this.ctx = ctx;
|
|
10
|
+
this.atAllFrequencyMap = new Map(); // 记录每个群组的@全体成员时间
|
|
10
11
|
this.config = config;
|
|
11
12
|
this.storageService = storageService;
|
|
12
13
|
}
|
|
@@ -19,7 +20,6 @@ class PushService {
|
|
|
19
20
|
let retries = 0;
|
|
20
21
|
while (retries <= maxRetries) {
|
|
21
22
|
try {
|
|
22
|
-
// 发送前添加基础延迟,避免触发频率限制
|
|
23
23
|
if (retries > 0) {
|
|
24
24
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
25
25
|
}
|
|
@@ -33,8 +33,7 @@ class PushService {
|
|
|
33
33
|
this.ctx.logger.error('Failed to send message after all retries:', error);
|
|
34
34
|
return false;
|
|
35
35
|
}
|
|
36
|
-
|
|
37
|
-
const delay = Math.pow(2, retries) * 1500; // 增加到 1.5 秒基础延迟
|
|
36
|
+
const delay = Math.pow(2, retries) * 1500;
|
|
38
37
|
this.ctx.logger.info(`Waiting ${delay}ms before next retry`);
|
|
39
38
|
await new Promise(resolve => setTimeout(resolve, delay));
|
|
40
39
|
}
|
|
@@ -44,47 +43,29 @@ class PushService {
|
|
|
44
43
|
// 分段发送消息
|
|
45
44
|
async sendMessageInParts(bot, targetId, message) {
|
|
46
45
|
try {
|
|
47
|
-
// 消息分段阈值(根据实际适配器调整)
|
|
48
46
|
const MAX_MESSAGE_LENGTH = 2000;
|
|
49
47
|
if (message.length <= MAX_MESSAGE_LENGTH) {
|
|
50
|
-
// 消息长度在限制内,直接发送(带重试)
|
|
51
48
|
return await this.sendMessageWithRetry(bot, targetId, message);
|
|
52
49
|
}
|
|
53
|
-
// 消息过长,需要分段
|
|
54
50
|
const parts = [];
|
|
55
|
-
let
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
splitIndex--;
|
|
68
|
-
}
|
|
69
|
-
if (splitIndex > 0) {
|
|
70
|
-
// 从分割点断开
|
|
71
|
-
parts.push(currentPart.substring(0, splitIndex + 1));
|
|
72
|
-
currentPart = currentPart.substring(splitIndex + 1);
|
|
73
|
-
currentLength = currentPart.length;
|
|
74
|
-
}
|
|
75
|
-
else {
|
|
76
|
-
// 没有找到合适的分割点,强制分割
|
|
77
|
-
parts.push(currentPart);
|
|
78
|
-
currentPart = '';
|
|
79
|
-
currentLength = 0;
|
|
51
|
+
let currentPosition = 0;
|
|
52
|
+
while (currentPosition < message.length) {
|
|
53
|
+
let endPosition = currentPosition + MAX_MESSAGE_LENGTH;
|
|
54
|
+
if (endPosition < message.length) {
|
|
55
|
+
const searchStart = Math.max(currentPosition, endPosition - 200);
|
|
56
|
+
let i = endPosition;
|
|
57
|
+
while (i >= searchStart) {
|
|
58
|
+
if (/[\s,。!?;:,.!?;:]/.test(message[i])) {
|
|
59
|
+
endPosition = i + 1;
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
i--;
|
|
80
63
|
}
|
|
81
64
|
}
|
|
65
|
+
const part = message.substring(currentPosition, endPosition);
|
|
66
|
+
parts.push(part);
|
|
67
|
+
currentPosition = endPosition;
|
|
82
68
|
}
|
|
83
|
-
// 添加最后一部分
|
|
84
|
-
if (currentPart) {
|
|
85
|
-
parts.push(currentPart);
|
|
86
|
-
}
|
|
87
|
-
// 发送所有部分
|
|
88
69
|
let allSuccess = true;
|
|
89
70
|
for (let i = 0; i < parts.length; i++) {
|
|
90
71
|
const partMessage = parts.length > 1
|
|
@@ -94,7 +75,6 @@ class PushService {
|
|
|
94
75
|
if (!success) {
|
|
95
76
|
allSuccess = false;
|
|
96
77
|
}
|
|
97
|
-
// 避免发送过快导致被限制
|
|
98
78
|
if (i < parts.length - 1) {
|
|
99
79
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
100
80
|
}
|
|
@@ -107,67 +87,52 @@ class PushService {
|
|
|
107
87
|
}
|
|
108
88
|
}
|
|
109
89
|
// 获取可用的机器人实例
|
|
110
|
-
getAvailableBot() {
|
|
90
|
+
getAvailableBot(targetId) {
|
|
111
91
|
return this.ctx.bots[0] || null;
|
|
112
92
|
}
|
|
113
|
-
//
|
|
114
|
-
async
|
|
115
|
-
if (!
|
|
116
|
-
this.ctx.logger.error('Invalid group ID format');
|
|
93
|
+
// 检查是否允许@全体成员
|
|
94
|
+
async isAtAllAllowed(groupId) {
|
|
95
|
+
if (!this.config.allowAtAll) {
|
|
117
96
|
return false;
|
|
118
97
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
// 其他平台: 允许字母、数字、-、_ 组合
|
|
124
|
-
if (!/^[a-zA-Z0-9_-]+$/.test(groupId)) {
|
|
125
|
-
this.ctx.logger.error('Invalid group ID format, only letters, numbers, hyphens and underscores are allowed');
|
|
98
|
+
const now = Date.now();
|
|
99
|
+
const lastAtTime = this.atAllFrequencyMap.get(groupId);
|
|
100
|
+
if (lastAtTime && (now - lastAtTime) < 60 * 60 * 1000) {
|
|
101
|
+
this.ctx.logger.warn(`At-all mention frequency limit reached for group ${groupId}`);
|
|
126
102
|
return false;
|
|
127
103
|
}
|
|
128
|
-
|
|
129
|
-
// ...
|
|
104
|
+
this.atAllFrequencyMap.set(groupId, now);
|
|
130
105
|
return true;
|
|
131
106
|
}
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
this.ctx.logger.error('Invalid user ID format');
|
|
136
|
-
return false;
|
|
137
|
-
}
|
|
138
|
-
// 支持多平台 ID 格式
|
|
139
|
-
// QQ: 纯数字字符串
|
|
140
|
-
// Telegram: 带-前缀的数字字符串
|
|
141
|
-
// Discord: 数字字符串
|
|
142
|
-
// 其他平台: 允许字母、数字、-、_ 组合
|
|
143
|
-
if (!/^[a-zA-Z0-9_-]+$/.test(userId)) {
|
|
144
|
-
this.ctx.logger.error('Invalid user ID format, only letters, numbers, hyphens and underscores are allowed');
|
|
145
|
-
return false;
|
|
146
|
-
}
|
|
147
|
-
return true;
|
|
107
|
+
// 获取@全体成员消息段
|
|
108
|
+
getAtAllMessage(bot) {
|
|
109
|
+
return (0, koishi_1.h)('at', { type: 'all' });
|
|
148
110
|
}
|
|
149
111
|
// 推送新文章
|
|
150
112
|
async pushNewArticle(article, groupId, enableAtAll = false) {
|
|
151
113
|
try {
|
|
152
|
-
|
|
153
|
-
|
|
114
|
+
if (!this.isPushTimeAllowed()) {
|
|
115
|
+
this.ctx.logger.info('Current time is not in allowed push time range');
|
|
154
116
|
return false;
|
|
155
117
|
}
|
|
156
|
-
// 检查群聊推送配置
|
|
157
118
|
const pushConfig = await this.storageService.getPushConfig(groupId);
|
|
158
119
|
if (!pushConfig?.enabled) {
|
|
159
120
|
this.ctx.logger.info(`Push is disabled for group ${groupId}, skipping`);
|
|
160
121
|
return false;
|
|
161
122
|
}
|
|
162
|
-
const bot = this.getAvailableBot();
|
|
123
|
+
const bot = this.getAvailableBot(groupId);
|
|
163
124
|
if (!bot) {
|
|
164
125
|
this.ctx.logger.error('No available bot for pushing messages');
|
|
165
126
|
return false;
|
|
166
127
|
}
|
|
167
|
-
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
128
|
+
const canAtAll = enableAtAll && await this.isAtAllAllowed(groupId);
|
|
129
|
+
const message = this.getPlatformCompatibleMessage(article, bot, false, canAtAll);
|
|
130
|
+
if (typeof message === 'string') {
|
|
131
|
+
return await this.sendMessageInParts(bot, groupId, message);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
return await this.sendMessageWithRetry(bot, groupId, message);
|
|
135
|
+
}
|
|
171
136
|
}
|
|
172
137
|
catch (error) {
|
|
173
138
|
this.ctx.logger.error(`Failed to push new article to group ${groupId}:`, error);
|
|
@@ -177,24 +142,27 @@ class PushService {
|
|
|
177
142
|
// 推送文章更新
|
|
178
143
|
async pushArticleUpdate(article, groupId) {
|
|
179
144
|
try {
|
|
180
|
-
|
|
181
|
-
|
|
145
|
+
if (!this.isPushTimeAllowed()) {
|
|
146
|
+
this.ctx.logger.info('Current time is not in allowed push time range');
|
|
182
147
|
return false;
|
|
183
148
|
}
|
|
184
|
-
// 检查群聊推送配置
|
|
185
149
|
const pushConfig = await this.storageService.getPushConfig(groupId);
|
|
186
150
|
if (!pushConfig?.enabled || !pushConfig?.enableUpdatePush) {
|
|
187
151
|
this.ctx.logger.info(`Update push is disabled for group ${groupId}, skipping`);
|
|
188
152
|
return false;
|
|
189
153
|
}
|
|
190
|
-
const bot = this.getAvailableBot();
|
|
154
|
+
const bot = this.getAvailableBot(groupId);
|
|
191
155
|
if (!bot) {
|
|
192
156
|
this.ctx.logger.error('No available bot for pushing messages');
|
|
193
157
|
return false;
|
|
194
158
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
159
|
+
const message = this.getPlatformCompatibleMessage(article, bot, true, false);
|
|
160
|
+
if (typeof message === 'string') {
|
|
161
|
+
return await this.sendMessageInParts(bot, groupId, message);
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
return await this.sendMessageWithRetry(bot, groupId, message);
|
|
165
|
+
}
|
|
198
166
|
}
|
|
199
167
|
catch (error) {
|
|
200
168
|
this.ctx.logger.error(`Failed to push article update to group ${groupId}:`, error);
|
|
@@ -204,24 +172,27 @@ class PushService {
|
|
|
204
172
|
// 推送新用户注册
|
|
205
173
|
async pushUserRegister(user, groupId) {
|
|
206
174
|
try {
|
|
207
|
-
|
|
208
|
-
|
|
175
|
+
if (!this.isPushTimeAllowed()) {
|
|
176
|
+
this.ctx.logger.info('Current time is not in allowed push time range');
|
|
209
177
|
return false;
|
|
210
178
|
}
|
|
211
|
-
// 检查群聊推送配置
|
|
212
179
|
const pushConfig = await this.storageService.getPushConfig(groupId);
|
|
213
180
|
if (!pushConfig?.enabled) {
|
|
214
181
|
this.ctx.logger.info(`Push is disabled for group ${groupId}, skipping`);
|
|
215
182
|
return false;
|
|
216
183
|
}
|
|
217
|
-
const bot = this.getAvailableBot();
|
|
184
|
+
const bot = this.getAvailableBot(groupId);
|
|
218
185
|
if (!bot) {
|
|
219
186
|
this.ctx.logger.error('No available bot for pushing messages');
|
|
220
187
|
return false;
|
|
221
188
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
189
|
+
const message = this.getPlatformCompatibleUserRegisterMessage(user, bot);
|
|
190
|
+
if (typeof message === 'string') {
|
|
191
|
+
return await this.sendMessageInParts(bot, groupId, message);
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
return await this.sendMessageWithRetry(bot, groupId, message);
|
|
195
|
+
}
|
|
225
196
|
}
|
|
226
197
|
catch (error) {
|
|
227
198
|
this.ctx.logger.error(`Failed to push user register to group ${groupId}:`, error);
|
|
@@ -231,22 +202,20 @@ class PushService {
|
|
|
231
202
|
// 私聊推送
|
|
232
203
|
async pushToUser(article, userId) {
|
|
233
204
|
try {
|
|
234
|
-
|
|
235
|
-
|
|
205
|
+
if (!this.isPushTimeAllowed()) {
|
|
206
|
+
this.ctx.logger.info('Current time is not in allowed push time range');
|
|
236
207
|
return false;
|
|
237
208
|
}
|
|
238
|
-
// 检查用户订阅状态
|
|
239
209
|
const isSubscribed = await this.storageService.isUserSubscribed(userId, 'private');
|
|
240
210
|
if (!isSubscribed) {
|
|
241
211
|
this.ctx.logger.info(`User ${userId} is not subscribed, skipping`);
|
|
242
212
|
return false;
|
|
243
213
|
}
|
|
244
|
-
const bot = this.getAvailableBot();
|
|
214
|
+
const bot = this.getAvailableBot(userId);
|
|
245
215
|
if (!bot) {
|
|
246
216
|
this.ctx.logger.error('No available bot for pushing messages');
|
|
247
217
|
return false;
|
|
248
218
|
}
|
|
249
|
-
// 发送文本消息
|
|
250
219
|
const textMessage = this.getNewArticleTemplate(article);
|
|
251
220
|
return await this.sendMessageInParts(bot, userId, textMessage);
|
|
252
221
|
}
|
|
@@ -255,145 +224,29 @@ class PushService {
|
|
|
255
224
|
return false;
|
|
256
225
|
}
|
|
257
226
|
}
|
|
258
|
-
//
|
|
259
|
-
createArticleCard(article, isUpdate = false) {
|
|
260
|
-
const title = article.title?.rendered || '无标题';
|
|
261
|
-
const author = article.author_name || '未知作者';
|
|
262
|
-
const date = isUpdate ? new Date(article.modified).toLocaleString('zh-CN') : new Date(article.date).toLocaleString('zh-CN');
|
|
263
|
-
const excerpt = this.getExcerpt(article);
|
|
264
|
-
const link = article.link || this.config.wordpressUrl;
|
|
265
|
-
const type = isUpdate ? '更新' : '发布';
|
|
266
|
-
// 使用腾讯官方 ARK 卡片格式
|
|
267
|
-
try {
|
|
268
|
-
// 官方 ARK 卡片格式(使用模板 23:链接+文本列表模板)
|
|
269
|
-
return (0, koishi_1.h)('ark', {
|
|
270
|
-
template_id: 23,
|
|
271
|
-
kv: [
|
|
272
|
-
{
|
|
273
|
-
key: '#DESC#',
|
|
274
|
-
value: isUpdate ? '文章更新通知' : '新文章推送'
|
|
275
|
-
},
|
|
276
|
-
{
|
|
277
|
-
key: '#PROMPT#',
|
|
278
|
-
value: 'WordPress 推送'
|
|
279
|
-
},
|
|
280
|
-
{
|
|
281
|
-
key: '#TITLE#',
|
|
282
|
-
value: title
|
|
283
|
-
},
|
|
284
|
-
{
|
|
285
|
-
key: '#META_URL#',
|
|
286
|
-
value: link
|
|
287
|
-
},
|
|
288
|
-
{
|
|
289
|
-
key: '#META_LIST#',
|
|
290
|
-
obj: [
|
|
291
|
-
{
|
|
292
|
-
obj_kv: [
|
|
293
|
-
{
|
|
294
|
-
key: '作者',
|
|
295
|
-
value: author
|
|
296
|
-
},
|
|
297
|
-
{
|
|
298
|
-
key: '时间',
|
|
299
|
-
value: date
|
|
300
|
-
}
|
|
301
|
-
]
|
|
302
|
-
},
|
|
303
|
-
{
|
|
304
|
-
obj_kv: [
|
|
305
|
-
{
|
|
306
|
-
key: '摘要',
|
|
307
|
-
value: excerpt
|
|
308
|
-
}
|
|
309
|
-
]
|
|
310
|
-
}
|
|
311
|
-
]
|
|
312
|
-
}
|
|
313
|
-
]
|
|
314
|
-
});
|
|
315
|
-
}
|
|
316
|
-
catch (arkError) {
|
|
317
|
-
this.ctx.logger.warn('ARK card failed, falling back to text format:', arkError);
|
|
318
|
-
// 降级到文本消息
|
|
319
|
-
return isUpdate ? this.getArticleUpdateTemplate(article) : this.getNewArticleTemplate(article);
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
// 检查平台是否支持卡片消息
|
|
323
|
-
isCardMessageSupported(bot) {
|
|
324
|
-
// 检查机器人平台是否为 QQ(支持 ARK 卡片)
|
|
325
|
-
return bot.platform === 'qq';
|
|
326
|
-
}
|
|
327
|
-
// 获取平台兼容的消息
|
|
227
|
+
// 获取平台兼容的消息(默认使用文本消息)
|
|
328
228
|
getPlatformCompatibleMessage(article, bot, isUpdate = false, enableAtAll = false) {
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
229
|
+
try {
|
|
230
|
+
const textMessage = isUpdate ? this.getArticleUpdateTemplate(article) : this.getNewArticleTemplate(article);
|
|
231
|
+
if (enableAtAll) {
|
|
232
|
+
const atAllMessage = this.getAtAllMessage(bot);
|
|
233
|
+
return [atAllMessage, textMessage];
|
|
234
|
+
}
|
|
235
|
+
return textMessage;
|
|
333
236
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
const
|
|
337
|
-
return
|
|
237
|
+
catch (error) {
|
|
238
|
+
this.ctx.logger.warn('Message creation failed:', error);
|
|
239
|
+
const textMessage = isUpdate ? this.getArticleUpdateTemplate(article) : this.getNewArticleTemplate(article);
|
|
240
|
+
return textMessage;
|
|
338
241
|
}
|
|
339
242
|
}
|
|
340
|
-
//
|
|
341
|
-
|
|
342
|
-
const username = user.name || '未知用户';
|
|
343
|
-
const registerDate = new Date(user.registered_date || new Date()).toLocaleString('zh-CN');
|
|
344
|
-
// 使用腾讯官方 ARK 卡片格式
|
|
243
|
+
// 获取平台兼容的用户注册消息(默认使用文本消息)
|
|
244
|
+
getPlatformCompatibleUserRegisterMessage(user, bot) {
|
|
345
245
|
try {
|
|
346
|
-
|
|
347
|
-
return (0, koishi_1.h)('ark', {
|
|
348
|
-
template_id: 23,
|
|
349
|
-
kv: [
|
|
350
|
-
{
|
|
351
|
-
key: '#DESC#',
|
|
352
|
-
value: '新用户注册通知'
|
|
353
|
-
},
|
|
354
|
-
{
|
|
355
|
-
key: '#PROMPT#',
|
|
356
|
-
value: 'WordPress 推送'
|
|
357
|
-
},
|
|
358
|
-
{
|
|
359
|
-
key: '#TITLE#',
|
|
360
|
-
value: `新用户注册:${username}`
|
|
361
|
-
},
|
|
362
|
-
{
|
|
363
|
-
key: '#META_URL#',
|
|
364
|
-
value: this.config.wordpressUrl
|
|
365
|
-
},
|
|
366
|
-
{
|
|
367
|
-
key: '#META_LIST#',
|
|
368
|
-
obj: [
|
|
369
|
-
{
|
|
370
|
-
obj_kv: [
|
|
371
|
-
{
|
|
372
|
-
key: '用户名',
|
|
373
|
-
value: username
|
|
374
|
-
},
|
|
375
|
-
{
|
|
376
|
-
key: '注册时间',
|
|
377
|
-
value: registerDate
|
|
378
|
-
}
|
|
379
|
-
]
|
|
380
|
-
},
|
|
381
|
-
{
|
|
382
|
-
obj_kv: [
|
|
383
|
-
{
|
|
384
|
-
key: '通知',
|
|
385
|
-
value: '欢迎加入我们的社区!'
|
|
386
|
-
}
|
|
387
|
-
]
|
|
388
|
-
}
|
|
389
|
-
]
|
|
390
|
-
}
|
|
391
|
-
]
|
|
392
|
-
});
|
|
246
|
+
return this.getUserRegisterTemplate(user);
|
|
393
247
|
}
|
|
394
|
-
catch (
|
|
395
|
-
this.ctx.logger.warn('
|
|
396
|
-
// 降级到文本消息
|
|
248
|
+
catch (error) {
|
|
249
|
+
this.ctx.logger.warn('User register message creation failed:', error);
|
|
397
250
|
return this.getUserRegisterTemplate(user);
|
|
398
251
|
}
|
|
399
252
|
}
|
|
@@ -401,7 +254,7 @@ class PushService {
|
|
|
401
254
|
getNewArticleTemplate(article) {
|
|
402
255
|
const title = article.title?.rendered || '无标题';
|
|
403
256
|
const author = article.author_name || '未知作者';
|
|
404
|
-
const publishDate = new Date(article.date).toLocaleString('zh-CN');
|
|
257
|
+
const publishDate = new Date(article.date || new Date()).toLocaleString('zh-CN');
|
|
405
258
|
const excerpt = this.getExcerpt(article);
|
|
406
259
|
const link = article.link || this.config.wordpressUrl;
|
|
407
260
|
return `📰 新文章推送
|
|
@@ -420,7 +273,7 @@ class PushService {
|
|
|
420
273
|
// 文章更新推送模板
|
|
421
274
|
getArticleUpdateTemplate(article) {
|
|
422
275
|
const title = article.title?.rendered || '无标题';
|
|
423
|
-
const modifiedDate = new Date(article.modified).toLocaleString('zh-CN');
|
|
276
|
+
const modifiedDate = new Date(article.modified || article.date || new Date()).toLocaleString('zh-CN');
|
|
424
277
|
const excerpt = this.getExcerpt(article);
|
|
425
278
|
const link = article.link || this.config.wordpressUrl;
|
|
426
279
|
return `🔄 文章更新通知
|
|
@@ -446,11 +299,9 @@ class PushService {
|
|
|
446
299
|
// 获取文章摘要
|
|
447
300
|
getExcerpt(article) {
|
|
448
301
|
if (article.excerpt?.rendered) {
|
|
449
|
-
// 移除 HTML 标签
|
|
450
302
|
return article.excerpt.rendered.replace(/<[^>]+>/g, '').trim();
|
|
451
303
|
}
|
|
452
304
|
else if (article.content?.rendered) {
|
|
453
|
-
// 从内容中提取摘要
|
|
454
305
|
const content = article.content.rendered.replace(/<[^>]+>/g, '').trim();
|
|
455
306
|
return content.length > 200 ? content.substring(0, 200) + '...' : content;
|
|
456
307
|
}
|
|
@@ -460,23 +311,17 @@ class PushService {
|
|
|
460
311
|
async batchPush(articles, groupIds, enableAtAll = false, messageType = 'text') {
|
|
461
312
|
let success = 0;
|
|
462
313
|
let failed = 0;
|
|
463
|
-
|
|
464
|
-
const pushPromises = [];
|
|
465
|
-
const MAX_CONCURRENT = 5; // 最大并发数,避免 API 限流
|
|
466
|
-
// 生成所有推送任务
|
|
314
|
+
const MAX_CONCURRENT = 3;
|
|
467
315
|
const allTasks = [];
|
|
468
316
|
for (const article of articles) {
|
|
469
317
|
for (const groupId of groupIds) {
|
|
470
|
-
allTasks.push({ article, groupId, enableAtAll
|
|
318
|
+
allTasks.push({ article, groupId, enableAtAll });
|
|
471
319
|
}
|
|
472
320
|
}
|
|
473
|
-
// 分批执行推送任务
|
|
474
321
|
for (let i = 0; i < allTasks.length; i += MAX_CONCURRENT) {
|
|
475
322
|
const batchTasks = allTasks.slice(i, i + MAX_CONCURRENT);
|
|
476
323
|
const batchPromises = batchTasks.map(task => this.pushNewArticle(task.article, task.groupId, task.enableAtAll));
|
|
477
|
-
// 等待当前批次完成
|
|
478
324
|
const batchResults = await Promise.allSettled(batchPromises);
|
|
479
|
-
// 统计当前批次结果
|
|
480
325
|
batchResults.forEach(result => {
|
|
481
326
|
if (result.status === 'fulfilled' && result.value) {
|
|
482
327
|
success++;
|
|
@@ -485,9 +330,9 @@ class PushService {
|
|
|
485
330
|
failed++;
|
|
486
331
|
}
|
|
487
332
|
});
|
|
488
|
-
// 批次之间添加延迟,避免 API 限流
|
|
489
333
|
if (i + MAX_CONCURRENT < allTasks.length) {
|
|
490
|
-
|
|
334
|
+
const delay = Math.max(1500, batchTasks.length * 500);
|
|
335
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
491
336
|
}
|
|
492
337
|
}
|
|
493
338
|
return { success, failed };
|
|
@@ -495,7 +340,6 @@ class PushService {
|
|
|
495
340
|
// 检查推送时间是否在允许范围内
|
|
496
341
|
isPushTimeAllowed() {
|
|
497
342
|
const hour = new Date().getHours();
|
|
498
|
-
// 默认允许推送时间:9:00-22:00
|
|
499
343
|
return hour >= 9 && hour <= 22;
|
|
500
344
|
}
|
|
501
345
|
}
|
package/lib/schedule.d.ts
CHANGED
|
@@ -13,11 +13,17 @@ export declare class ScheduleService {
|
|
|
13
13
|
private failureCount;
|
|
14
14
|
private readonly FAILURE_THRESHOLD;
|
|
15
15
|
private adminUserId;
|
|
16
|
+
private isRunning;
|
|
17
|
+
private isDisposed;
|
|
18
|
+
private readonly MAX_CONCURRENT_PUSHES;
|
|
16
19
|
constructor(ctx: Context, config: Config, wordpressService: WordPressService, storageService: StorageService, pushService: PushService);
|
|
20
|
+
private validateAdminUserId;
|
|
17
21
|
private initSchedule;
|
|
18
22
|
startSchedule(): void;
|
|
19
23
|
stopSchedule(): void;
|
|
20
24
|
private sendFailureAlert;
|
|
25
|
+
private isPushTimeAllowed;
|
|
26
|
+
private executeWithConcurrencyLimit;
|
|
21
27
|
checkAndPushArticles(): Promise<void>;
|
|
22
28
|
checkNewUsers(): Promise<void>;
|
|
23
29
|
triggerCheck(): Promise<void>;
|