koishi-plugin-wordpress-notifier 2.8.5 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.d.ts +7 -7
- package/lib/index.js +371 -96
- package/package.json +1 -1
package/lib/index.d.ts
CHANGED
|
@@ -10,12 +10,12 @@ declare module 'koishi' {
|
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
export interface WordPressVersionRecord {
|
|
13
|
-
id:
|
|
13
|
+
id: string;
|
|
14
14
|
version: string;
|
|
15
15
|
updatedAt: Date;
|
|
16
16
|
}
|
|
17
17
|
export interface WordPressConfigRecord {
|
|
18
|
-
id:
|
|
18
|
+
id: string;
|
|
19
19
|
key: string;
|
|
20
20
|
value: string;
|
|
21
21
|
updatedAt: Date;
|
|
@@ -50,7 +50,7 @@ export interface Config {
|
|
|
50
50
|
};
|
|
51
51
|
}
|
|
52
52
|
export interface WordPressPost {
|
|
53
|
-
id:
|
|
53
|
+
id: string;
|
|
54
54
|
title: {
|
|
55
55
|
rendered: string;
|
|
56
56
|
};
|
|
@@ -60,12 +60,12 @@ export interface WordPressPost {
|
|
|
60
60
|
excerpt: {
|
|
61
61
|
rendered: string;
|
|
62
62
|
};
|
|
63
|
-
author:
|
|
64
|
-
categories:
|
|
65
|
-
tags:
|
|
63
|
+
author: string;
|
|
64
|
+
categories: string[];
|
|
65
|
+
tags: string[];
|
|
66
66
|
}
|
|
67
67
|
export interface WordPressUser {
|
|
68
|
-
id:
|
|
68
|
+
id: string;
|
|
69
69
|
name: string;
|
|
70
70
|
slug: string;
|
|
71
71
|
date?: string;
|
package/lib/index.js
CHANGED
|
@@ -129,7 +129,7 @@ function apply(ctx, config) {
|
|
|
129
129
|
ctx.logger.info('WordPress 推送插件已加载');
|
|
130
130
|
// 修复 MySQL 自增主键问题,使用正确的模型配置
|
|
131
131
|
// 确保 id 字段被正确设置为自增主键,并且在插入时不会被设置为 NULL
|
|
132
|
-
// 显式指定
|
|
132
|
+
// 显式指定 nullable: false 约束以适配 MySQL 特有要求
|
|
133
133
|
ctx.model.extend('wordpress_post_updates', {
|
|
134
134
|
id: { type: 'integer', nullable: false },
|
|
135
135
|
siteId: { type: 'string', nullable: false },
|
|
@@ -153,43 +153,108 @@ function apply(ctx, config) {
|
|
|
153
153
|
});
|
|
154
154
|
// 配置存储表
|
|
155
155
|
ctx.model.extend('wordpress_config', {
|
|
156
|
-
id: { type: '
|
|
156
|
+
id: { type: 'string', nullable: false },
|
|
157
157
|
key: { type: 'string', nullable: false },
|
|
158
158
|
value: { type: 'string', nullable: false },
|
|
159
159
|
updatedAt: { type: 'timestamp', nullable: false }
|
|
160
160
|
}, {
|
|
161
161
|
primary: 'id',
|
|
162
|
-
autoInc: true,
|
|
163
162
|
unique: ['key']
|
|
164
163
|
});
|
|
165
164
|
// 版本记录表
|
|
166
165
|
ctx.model.extend('wordpress_version', {
|
|
167
|
-
id: { type: '
|
|
166
|
+
id: { type: 'string', nullable: false },
|
|
168
167
|
version: { type: 'string', nullable: false },
|
|
169
168
|
updatedAt: { type: 'timestamp', nullable: false }
|
|
170
169
|
}, {
|
|
171
170
|
primary: 'id',
|
|
172
|
-
autoInc: true,
|
|
173
171
|
unique: ['version']
|
|
174
172
|
});
|
|
175
173
|
ctx.logger.info('数据库表配置完成,autoInc: true 已启用,确保插入操作不手动指定 id 字段');
|
|
174
|
+
// 时间处理工具函数
|
|
175
|
+
function parseWPDate(dateStr) {
|
|
176
|
+
if (!dateStr) {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
try {
|
|
180
|
+
const date = new Date(dateStr);
|
|
181
|
+
if (isNaN(date.getTime())) {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
return date;
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
ctx.logger.warn(`解析日期失败: ${dateStr}, 错误: ${error}`);
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
176
191
|
// 为所有数据库操作添加详细日志,便于诊断自增主键问题
|
|
177
192
|
ctx.on('ready', async () => {
|
|
178
193
|
ctx.logger.info('WordPress 推送插件已就绪,开始初始化推送任务');
|
|
179
194
|
ctx.logger.info('数据库表配置:');
|
|
180
|
-
ctx.logger.info('wordpress_post_updates: id 字段设置为
|
|
181
|
-
ctx.logger.info('wordpress_user_registrations: id 字段设置为
|
|
195
|
+
ctx.logger.info('wordpress_post_updates: id 字段设置为 autoIncrement: true');
|
|
196
|
+
ctx.logger.info('wordpress_user_registrations: id 字段设置为 autoIncrement: true');
|
|
182
197
|
ctx.logger.info('wordpress_config: 配置持久化存储表');
|
|
183
198
|
ctx.logger.info('wordpress_version: 数据库版本记录表');
|
|
184
199
|
ctx.logger.info('所有群聊共用一个文章标记,不再区分群聊');
|
|
185
200
|
// 检查并修复数据库表结构问题
|
|
186
201
|
await checkAndFixTableStructure();
|
|
202
|
+
// 检查和更新数据库版本
|
|
203
|
+
await checkAndUpdateDatabaseVersion();
|
|
187
204
|
// 加载持久化配置
|
|
188
205
|
await loadPersistentConfig();
|
|
189
206
|
// 执行初始推送
|
|
190
207
|
await pushNewPosts();
|
|
191
208
|
});
|
|
192
|
-
//
|
|
209
|
+
// 检查和更新数据库版本
|
|
210
|
+
async function checkAndUpdateDatabaseVersion() {
|
|
211
|
+
try {
|
|
212
|
+
ctx.logger.info('开始检查数据库版本...');
|
|
213
|
+
// 确保数据库连接正常
|
|
214
|
+
const connected = await ensureDatabaseConnection();
|
|
215
|
+
if (!connected) {
|
|
216
|
+
ctx.logger.warn('数据库连接异常,跳过版本检查');
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
// 检查现有版本记录
|
|
220
|
+
const versionRecords = await ctx.database.get('wordpress_version', {}, { limit: 1 });
|
|
221
|
+
if (versionRecords.length === 0) {
|
|
222
|
+
// 首次安装,创建版本记录
|
|
223
|
+
await ctx.database.create('wordpress_version', {
|
|
224
|
+
id: '1',
|
|
225
|
+
version: DATABASE_VERSION,
|
|
226
|
+
updatedAt: new Date()
|
|
227
|
+
});
|
|
228
|
+
ctx.logger.info(`数据库版本初始化完成,当前版本: ${DATABASE_VERSION}`);
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
const currentVersion = versionRecords[0].version;
|
|
232
|
+
if (currentVersion !== DATABASE_VERSION) {
|
|
233
|
+
// 版本不一致,执行升级逻辑
|
|
234
|
+
ctx.logger.info(`数据库版本更新,从 ${currentVersion} 升级到 ${DATABASE_VERSION}`);
|
|
235
|
+
// 这里可以添加具体的升级逻辑,例如:
|
|
236
|
+
// 1. 表结构变更
|
|
237
|
+
// 2. 数据迁移
|
|
238
|
+
// 3. 配置更新
|
|
239
|
+
// 更新版本记录
|
|
240
|
+
await ctx.database.remove('wordpress_version', { id: versionRecords[0].id });
|
|
241
|
+
await ctx.database.create('wordpress_version', {
|
|
242
|
+
id: versionRecords[0].id,
|
|
243
|
+
version: DATABASE_VERSION,
|
|
244
|
+
updatedAt: new Date()
|
|
245
|
+
});
|
|
246
|
+
ctx.logger.info(`数据库版本升级完成,当前版本: ${DATABASE_VERSION}`);
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
ctx.logger.info(`数据库版本检查完成,当前版本: ${DATABASE_VERSION} (最新)`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
ctx.logger.error(`检查数据库版本失败: ${error}`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
// 加载持久化配置,使用分页查询避免内存溢出
|
|
193
258
|
async function loadPersistentConfig() {
|
|
194
259
|
try {
|
|
195
260
|
ctx.logger.info('开始加载持久化配置...');
|
|
@@ -199,7 +264,10 @@ function apply(ctx, config) {
|
|
|
199
264
|
ctx.logger.warn('数据库连接异常,跳过加载持久化配置');
|
|
200
265
|
return;
|
|
201
266
|
}
|
|
202
|
-
|
|
267
|
+
// 使用分页查询,每次最多获取 100 条记录
|
|
268
|
+
const configRecords = await ctx.database.get('wordpress_config', {}, {
|
|
269
|
+
limit: 100
|
|
270
|
+
});
|
|
203
271
|
ctx.logger.info(`找到 ${configRecords.length} 条持久化配置记录`);
|
|
204
272
|
for (const record of configRecords) {
|
|
205
273
|
try {
|
|
@@ -244,7 +312,7 @@ function apply(ctx, config) {
|
|
|
244
312
|
// 更新现有配置
|
|
245
313
|
await ctx.database.remove('wordpress_config', { key });
|
|
246
314
|
}
|
|
247
|
-
//
|
|
315
|
+
// 创建新配置记录,只保存指定的键值对
|
|
248
316
|
await ctx.database.create('wordpress_config', {
|
|
249
317
|
key,
|
|
250
318
|
value: JSON.stringify(value),
|
|
@@ -256,6 +324,47 @@ function apply(ctx, config) {
|
|
|
256
324
|
ctx.logger.error(`保存配置失败,键: ${key},错误: ${error}`);
|
|
257
325
|
}
|
|
258
326
|
}
|
|
327
|
+
// 保存单个站点配置
|
|
328
|
+
async function saveSiteConfig(siteId, siteConfig) {
|
|
329
|
+
try {
|
|
330
|
+
ctx.logger.info(`保存单个站点配置: ${siteId} = ${JSON.stringify(siteConfig)}`);
|
|
331
|
+
// 确保数据库连接正常
|
|
332
|
+
const connected = await ensureDatabaseConnection();
|
|
333
|
+
if (!connected) {
|
|
334
|
+
ctx.logger.warn('数据库连接异常,跳过保存站点配置');
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
// 获取当前所有站点配置
|
|
338
|
+
const sitesConfigKey = 'sites';
|
|
339
|
+
const existingSitesConfig = await ctx.database.get('wordpress_config', { key: sitesConfigKey });
|
|
340
|
+
let currentSites = [];
|
|
341
|
+
if (existingSitesConfig.length > 0) {
|
|
342
|
+
try {
|
|
343
|
+
currentSites = JSON.parse(existingSitesConfig[0].value);
|
|
344
|
+
}
|
|
345
|
+
catch (error) {
|
|
346
|
+
ctx.logger.error(`解析现有站点配置失败: ${error}`);
|
|
347
|
+
currentSites = [];
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
// 查找站点是否已存在
|
|
351
|
+
const siteIndex = currentSites.findIndex(site => site.id === siteId);
|
|
352
|
+
if (siteIndex >= 0) {
|
|
353
|
+
// 更新现有站点
|
|
354
|
+
currentSites[siteIndex] = siteConfig;
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
// 添加新站点
|
|
358
|
+
currentSites.push(siteConfig);
|
|
359
|
+
}
|
|
360
|
+
// 保存更新后的站点配置
|
|
361
|
+
await saveConfig(sitesConfigKey, currentSites);
|
|
362
|
+
ctx.logger.info(`站点配置保存成功: ${siteId}`);
|
|
363
|
+
}
|
|
364
|
+
catch (error) {
|
|
365
|
+
ctx.logger.error(`保存站点配置失败,站点 ID: ${siteId},错误: ${error}`);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
259
368
|
// 缓存对象,用于存储高频格式化结果,有效期1小时
|
|
260
369
|
const formatCache = {
|
|
261
370
|
post: new Map(),
|
|
@@ -282,8 +391,8 @@ function apply(ctx, config) {
|
|
|
282
391
|
}
|
|
283
392
|
}
|
|
284
393
|
}
|
|
285
|
-
//
|
|
286
|
-
setInterval(cleanExpiredCache, CONSTANTS.CACHE_EXPIRY);
|
|
394
|
+
// 定期清理过期缓存,绑定到 ctx 生命周期
|
|
395
|
+
const cacheCleanupTimer = ctx.setInterval(cleanExpiredCache, CONSTANTS.CACHE_EXPIRY);
|
|
287
396
|
// 健壮获取 QQ Bot 实例,兼容多种适配器,优先选择 QQ 官方 bot
|
|
288
397
|
function getValidBot() {
|
|
289
398
|
// 支持的 QQ 相关适配器列表,'qq' 为 QQ 官方 bot
|
|
@@ -294,8 +403,9 @@ function apply(ctx, config) {
|
|
|
294
403
|
const connectedBots = botList.filter(bot => {
|
|
295
404
|
// 检查 Bot 是否已连接
|
|
296
405
|
// 不同适配器可能有不同的状态属性
|
|
297
|
-
//
|
|
298
|
-
|
|
406
|
+
// 尝试检查常见的状态属性
|
|
407
|
+
const status = String(bot.status || '');
|
|
408
|
+
return status === 'online' || status === 'connected' || status === '';
|
|
299
409
|
});
|
|
300
410
|
ctx.logger.info(`找到 ${connectedBots.length} 个已连接的 Bot,总 Bot 数: ${botList.length}`);
|
|
301
411
|
if (connectedBots.length === 0) {
|
|
@@ -320,18 +430,53 @@ function apply(ctx, config) {
|
|
|
320
430
|
ctx.logger.info(`选择可用的已连接 Bot: ${connectedBots[0].platform} - ${connectedBots[0].selfId || 'unknown'}`);
|
|
321
431
|
return connectedBots[0];
|
|
322
432
|
}
|
|
433
|
+
// 检查 Bot 是否有发送消息的权限
|
|
434
|
+
async function checkBotPermission(bot, target) {
|
|
435
|
+
try {
|
|
436
|
+
// 不同适配器可能有不同的权限检查方法
|
|
437
|
+
// 这里使用通用的检查方法,尝试发送一条空消息或检查权限
|
|
438
|
+
// 注意:这种方法可能会在某些适配器上失败,所以需要捕获异常
|
|
439
|
+
// 检查 Bot 是否有 sendMessage 方法
|
|
440
|
+
if (!bot.sendMessage) {
|
|
441
|
+
ctx.logger.warn(`Bot 实例没有 sendMessage 方法: ${bot.platform}:${bot.selfId || 'unknown'}`);
|
|
442
|
+
return false;
|
|
443
|
+
}
|
|
444
|
+
// 检查 Bot 是否在线
|
|
445
|
+
if (bot.status && bot.status !== 'online' && bot.status !== 'connected') {
|
|
446
|
+
ctx.logger.warn(`Bot 不在线: ${bot.platform}:${bot.selfId || 'unknown'},状态: ${bot.status}`);
|
|
447
|
+
return false;
|
|
448
|
+
}
|
|
449
|
+
// 对于 QQ 官方 bot,检查是否有权限发送消息
|
|
450
|
+
if (bot.platform === 'qq') {
|
|
451
|
+
// QQ 官方 bot 可能有专门的权限检查方法
|
|
452
|
+
// 这里暂时返回 true,实际项目中需要根据具体适配器实现权限检查
|
|
453
|
+
return true;
|
|
454
|
+
}
|
|
455
|
+
// 对于其他适配器,尝试发送一条测试消息或检查权限
|
|
456
|
+
// 这里暂时返回 true,实际项目中需要根据具体适配器实现权限检查
|
|
457
|
+
return true;
|
|
458
|
+
}
|
|
459
|
+
catch (error) {
|
|
460
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
461
|
+
ctx.logger.error(`检查 Bot 权限失败: ${errorMessage}`);
|
|
462
|
+
return false;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
323
465
|
const failedPushQueue = [];
|
|
324
466
|
const MAX_RETRIES = CONSTANTS.MAX_PUSH_RETRIES;
|
|
325
467
|
const RETRY_INTERVAL = CONSTANTS.PUSH_RETRY_INTERVAL; // 5分钟
|
|
326
468
|
// 添加到失败队列
|
|
327
469
|
function addToFailedQueue(type, data, targets, siteConfig) {
|
|
470
|
+
const now = new Date();
|
|
471
|
+
const expireAt = new Date(now.getTime() + 24 * 60 * 60 * 1000); // 24小时过期
|
|
328
472
|
const item = {
|
|
329
473
|
id: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
330
474
|
type,
|
|
331
475
|
data,
|
|
332
476
|
targets,
|
|
333
477
|
retries: 0,
|
|
334
|
-
createdAt:
|
|
478
|
+
createdAt: now,
|
|
479
|
+
expireAt,
|
|
335
480
|
siteConfig
|
|
336
481
|
};
|
|
337
482
|
failedPushQueue.push(item);
|
|
@@ -348,9 +493,16 @@ function apply(ctx, config) {
|
|
|
348
493
|
ctx.logger.error('没有可用的 Bot 实例,无法处理失败队列');
|
|
349
494
|
return;
|
|
350
495
|
}
|
|
496
|
+
const now = new Date();
|
|
351
497
|
const itemsToRemove = [];
|
|
352
498
|
for (let i = 0; i < failedPushQueue.length; i++) {
|
|
353
499
|
const item = failedPushQueue[i];
|
|
500
|
+
// 检查是否过期
|
|
501
|
+
if (now > item.expireAt) {
|
|
502
|
+
ctx.logger.warn(`任务已过期,放弃推送,类型: ${item.type},创建时间: ${item.createdAt}`);
|
|
503
|
+
itemsToRemove.push(i);
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
354
506
|
if (item.retries >= MAX_RETRIES) {
|
|
355
507
|
ctx.logger.warn(`达到最大重试次数,放弃推送,类型: ${item.type},创建时间: ${item.createdAt}`);
|
|
356
508
|
itemsToRemove.push(i);
|
|
@@ -391,8 +543,8 @@ function apply(ctx, config) {
|
|
|
391
543
|
}
|
|
392
544
|
ctx.logger.info(`失败队列处理完成,剩余队列长度: ${failedPushQueue.length}`);
|
|
393
545
|
}
|
|
394
|
-
//
|
|
395
|
-
setInterval(processFailedQueue, RETRY_INTERVAL);
|
|
546
|
+
// 定期处理失败队列,绑定到 ctx 生命周期
|
|
547
|
+
const failedQueueTimer = ctx.setInterval(processFailedQueue, RETRY_INTERVAL);
|
|
396
548
|
// 通用权限检查函数,检查用户是否为超级管理员
|
|
397
549
|
function checkSuperAdmin(session) {
|
|
398
550
|
// 检查是否为超级管理员
|
|
@@ -596,13 +748,12 @@ function apply(ctx, config) {
|
|
|
596
748
|
ctx.logger.info('旧表删除成功,重新初始化表结构...');
|
|
597
749
|
// 重新扩展模型
|
|
598
750
|
ctx.model.extend('wordpress_config', {
|
|
599
|
-
id: { type: '
|
|
751
|
+
id: { type: 'string', nullable: false },
|
|
600
752
|
key: { type: 'string', nullable: false },
|
|
601
753
|
value: { type: 'string', nullable: false },
|
|
602
754
|
updatedAt: { type: 'timestamp', nullable: false }
|
|
603
755
|
}, {
|
|
604
756
|
primary: 'id',
|
|
605
|
-
autoInc: true,
|
|
606
757
|
unique: ['key']
|
|
607
758
|
});
|
|
608
759
|
ctx.logger.info('wordpress_config 表重新初始化成功');
|
|
@@ -627,12 +778,11 @@ function apply(ctx, config) {
|
|
|
627
778
|
ctx.logger.info('旧表删除成功,重新初始化表结构...');
|
|
628
779
|
// 重新扩展模型
|
|
629
780
|
ctx.model.extend('wordpress_version', {
|
|
630
|
-
id: { type: '
|
|
781
|
+
id: { type: 'string', nullable: false },
|
|
631
782
|
version: { type: 'string', nullable: false },
|
|
632
783
|
updatedAt: { type: 'timestamp', nullable: false }
|
|
633
784
|
}, {
|
|
634
785
|
primary: 'id',
|
|
635
|
-
autoInc: true,
|
|
636
786
|
unique: ['version']
|
|
637
787
|
});
|
|
638
788
|
ctx.logger.info('wordpress_version 表重新初始化成功');
|
|
@@ -706,18 +856,22 @@ function apply(ctx, config) {
|
|
|
706
856
|
}
|
|
707
857
|
const response = await httpRequest(url, requestConfig);
|
|
708
858
|
if (!response) {
|
|
709
|
-
|
|
710
|
-
|
|
859
|
+
const errorMessage = `获取站点 ${site.id} (${site.name}) 的 WordPress 文章失败,已达到最大重试次数`;
|
|
860
|
+
ctx.logger.error(errorMessage);
|
|
861
|
+
// 记录失败
|
|
862
|
+
recordPushFailure(`获取站点 ${site.id} (${site.name}) 的文章失败: ${errorMessage}`);
|
|
863
|
+
return { success: false, data: [], error: errorMessage };
|
|
711
864
|
}
|
|
712
865
|
ctx.logger.info(`成功获取站点 ${site.id} (${site.name}) 的 ${response.length} 篇文章`);
|
|
713
|
-
return response;
|
|
866
|
+
return { success: true, data: response, error: '' };
|
|
714
867
|
}
|
|
715
868
|
catch (error) {
|
|
716
869
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
717
|
-
|
|
870
|
+
const fullErrorMessage = `获取站点 ${site.id} (${site.name}) 的 WordPress 文章失败: ${errorMessage}`;
|
|
871
|
+
ctx.logger.error(fullErrorMessage);
|
|
718
872
|
// 记录失败
|
|
719
873
|
recordPushFailure(`获取站点 ${site.id} (${site.name}) 的文章失败: ${errorMessage}`);
|
|
720
|
-
return [];
|
|
874
|
+
return { success: false, data: [], error: fullErrorMessage };
|
|
721
875
|
}
|
|
722
876
|
}
|
|
723
877
|
async function fetchLatestUsers(site) {
|
|
@@ -740,12 +894,13 @@ function apply(ctx, config) {
|
|
|
740
894
|
}
|
|
741
895
|
const response = await httpRequest(url, requestConfig);
|
|
742
896
|
if (!response) {
|
|
743
|
-
|
|
897
|
+
const errorMessage = `获取站点 ${site.id} (${site.name}) 的 WordPress 用户失败,已达到最大重试次数`;
|
|
898
|
+
ctx.logger.error(errorMessage);
|
|
744
899
|
ctx.logger.error(`WordPress REST API 的 users 端点需要认证才能访问,请在站点配置中添加 WordPress 用户名和应用程序密码`);
|
|
745
900
|
// 记录失败
|
|
746
901
|
recordPushFailure(`获取站点 ${site.id} (${site.name}) 的用户失败: API 认证失败`);
|
|
747
902
|
// 返回空数组,确保插件继续运行
|
|
748
|
-
return [];
|
|
903
|
+
return { success: false, data: [], error: errorMessage };
|
|
749
904
|
}
|
|
750
905
|
ctx.logger.info(`成功获取站点 ${site.id} (${site.name}) 的 ${response.length} 位用户`);
|
|
751
906
|
// 添加调试日志,查看API返回的实际数据结构
|
|
@@ -755,16 +910,17 @@ function apply(ctx, config) {
|
|
|
755
910
|
const user = response[0];
|
|
756
911
|
ctx.logger.info(`用户日期字段: date=${user.date}, date_registered=${user.date_registered}, registered_date=${user.registered_date}, created_at=${user.created_at}`);
|
|
757
912
|
}
|
|
758
|
-
return response;
|
|
913
|
+
return { success: true, data: response, error: '' };
|
|
759
914
|
}
|
|
760
915
|
catch (error) {
|
|
761
916
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
762
|
-
|
|
917
|
+
const fullErrorMessage = `获取站点 ${site.id} (${site.name}) 的 WordPress 用户失败: ${errorMessage}`;
|
|
918
|
+
ctx.logger.error(fullErrorMessage);
|
|
763
919
|
ctx.logger.error(`WordPress REST API 的 users 端点需要认证才能访问,请在站点配置中添加 WordPress 用户名和应用程序密码`);
|
|
764
920
|
// 记录失败
|
|
765
921
|
recordPushFailure(`获取站点 ${site.id} (${site.name}) 的用户失败: ${errorMessage}`);
|
|
766
922
|
// 返回空数组,确保插件继续运行
|
|
767
|
-
return [];
|
|
923
|
+
return { success: false, data: [], error: fullErrorMessage };
|
|
768
924
|
}
|
|
769
925
|
}
|
|
770
926
|
async function fetchUpdatedPosts(site) {
|
|
@@ -784,20 +940,22 @@ function apply(ctx, config) {
|
|
|
784
940
|
}
|
|
785
941
|
const response = await httpRequest(url, requestConfig);
|
|
786
942
|
if (!response) {
|
|
787
|
-
|
|
943
|
+
const errorMessage = `获取站点 ${site.id} (${site.name}) 的 WordPress 更新文章失败,已达到最大重试次数`;
|
|
944
|
+
ctx.logger.error(errorMessage);
|
|
788
945
|
// 记录失败
|
|
789
946
|
recordPushFailure(`获取站点 ${site.id} (${site.name}) 的更新文章失败`);
|
|
790
|
-
return [];
|
|
947
|
+
return { success: false, data: [], error: errorMessage };
|
|
791
948
|
}
|
|
792
949
|
ctx.logger.info(`成功获取站点 ${site.id} (${site.name}) 的 ${response.length} 篇更新文章`);
|
|
793
|
-
return response;
|
|
950
|
+
return { success: true, data: response, error: '' };
|
|
794
951
|
}
|
|
795
952
|
catch (error) {
|
|
796
953
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
797
|
-
|
|
954
|
+
const fullErrorMessage = `获取站点 ${site.id} (${site.name}) 的 WordPress 更新文章失败: ${errorMessage}`;
|
|
955
|
+
ctx.logger.error(fullErrorMessage);
|
|
798
956
|
// 记录失败
|
|
799
957
|
recordPushFailure(`获取站点 ${site.id} (${site.name}) 的更新文章失败: ${errorMessage}`);
|
|
800
|
-
return [];
|
|
958
|
+
return { success: false, data: [], error: fullErrorMessage };
|
|
801
959
|
}
|
|
802
960
|
}
|
|
803
961
|
async function isUserPushed(siteId, userId) {
|
|
@@ -809,7 +967,7 @@ function apply(ctx, config) {
|
|
|
809
967
|
return false;
|
|
810
968
|
}
|
|
811
969
|
ctx.logger.info(`检查用户是否已推送,站点 ID: ${siteId},用户 ID: ${userId}`);
|
|
812
|
-
const record = await ctx.database.get('wordpress_user_registrations', { siteId, userId });
|
|
970
|
+
const record = await ctx.database.get('wordpress_user_registrations', { siteId, userId: parseInt(userId) });
|
|
813
971
|
const result = record.length > 0;
|
|
814
972
|
ctx.logger.info(`检查结果:站点 ${siteId} 用户 ${userId} 已推送:${result ? '是' : '否'}`);
|
|
815
973
|
return result;
|
|
@@ -831,7 +989,7 @@ function apply(ctx, config) {
|
|
|
831
989
|
return null;
|
|
832
990
|
}
|
|
833
991
|
ctx.logger.info(`获取文章更新记录,站点 ID: ${siteId},文章 ID: ${postId}`);
|
|
834
|
-
const records = await ctx.database.get('wordpress_post_updates', { siteId, postId });
|
|
992
|
+
const records = await ctx.database.get('wordpress_post_updates', { siteId, postId: parseInt(postId) });
|
|
835
993
|
const result = records.length > 0 ? records[0] : null;
|
|
836
994
|
ctx.logger.info(`获取结果:站点 ${siteId} 文章 ${postId} 更新记录:${result ? '找到' : '未找到'}`);
|
|
837
995
|
return result;
|
|
@@ -856,7 +1014,7 @@ function apply(ctx, config) {
|
|
|
856
1014
|
// 创建新记录,不手动指定id,让数据库自动生成
|
|
857
1015
|
const newRecord = {
|
|
858
1016
|
siteId,
|
|
859
|
-
userId,
|
|
1017
|
+
userId: parseInt(userId),
|
|
860
1018
|
pushedAt: new Date()
|
|
861
1019
|
};
|
|
862
1020
|
ctx.logger.info(`准备创建用户推送记录:${JSON.stringify(newRecord)}`);
|
|
@@ -890,13 +1048,13 @@ function apply(ctx, config) {
|
|
|
890
1048
|
if (record) {
|
|
891
1049
|
ctx.logger.info(`发现现有记录,站点 ID: ${siteId},文章 ID: ${postId},上次修改时间: ${record.lastModified}`);
|
|
892
1050
|
// Koishi database API 不支持 update 方法,使用 remove + create 代替
|
|
893
|
-
await ctx.database.remove('wordpress_post_updates', { siteId, postId });
|
|
1051
|
+
await ctx.database.remove('wordpress_post_updates', { siteId, postId: parseInt(postId) });
|
|
894
1052
|
ctx.logger.info(`已删除旧记录,站点 ID: ${siteId},文章 ID: ${postId}`);
|
|
895
1053
|
}
|
|
896
1054
|
// 创建新记录,不指定 id 字段,让数据库自动生成
|
|
897
1055
|
const newRecord = {
|
|
898
1056
|
siteId,
|
|
899
|
-
postId,
|
|
1057
|
+
postId: parseInt(postId),
|
|
900
1058
|
lastModified: modifiedDate,
|
|
901
1059
|
pushedAt: new Date()
|
|
902
1060
|
};
|
|
@@ -968,7 +1126,11 @@ function apply(ctx, config) {
|
|
|
968
1126
|
}
|
|
969
1127
|
// 根据配置格式化日期
|
|
970
1128
|
const formatDate = (dateString) => {
|
|
971
|
-
|
|
1129
|
+
// 使用统一的时间处理工具函数
|
|
1130
|
+
const date = parseWPDate(dateString);
|
|
1131
|
+
if (!date) {
|
|
1132
|
+
return '未知时间';
|
|
1133
|
+
}
|
|
972
1134
|
const year = date.getFullYear();
|
|
973
1135
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
974
1136
|
const day = String(date.getDate()).padStart(2, '0');
|
|
@@ -1080,10 +1242,9 @@ function apply(ctx, config) {
|
|
|
1080
1242
|
ctx.logger.info(`用户 ${username} 的原始数据: ${JSON.stringify(user)}`);
|
|
1081
1243
|
}
|
|
1082
1244
|
if (dateStr) {
|
|
1083
|
-
//
|
|
1084
|
-
const date =
|
|
1085
|
-
|
|
1086
|
-
if (!isNaN(date.getTime())) {
|
|
1245
|
+
// 使用统一的时间处理工具函数
|
|
1246
|
+
const date = parseWPDate(dateStr);
|
|
1247
|
+
if (date) {
|
|
1087
1248
|
const year = date.getFullYear();
|
|
1088
1249
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
1089
1250
|
const day = String(date.getDate()).padStart(2, '0');
|
|
@@ -1134,14 +1295,21 @@ function apply(ctx, config) {
|
|
|
1134
1295
|
ctx.logger.info(`开始处理站点: ${site.id} (${site.name})`);
|
|
1135
1296
|
// 推送新文章
|
|
1136
1297
|
if (site.enableAutoPush) {
|
|
1137
|
-
const
|
|
1298
|
+
const postsResult = await fetchLatestPosts(site);
|
|
1299
|
+
if (!postsResult.success) {
|
|
1300
|
+
ctx.logger.error(`获取站点 ${site.id} (${site.name}) 的文章失败: ${postsResult.error}`);
|
|
1301
|
+
// 记录失败
|
|
1302
|
+
recordPushFailure(`获取站点 ${site.id} (${site.name}) 的文章失败: ${postsResult.error}`);
|
|
1303
|
+
continue;
|
|
1304
|
+
}
|
|
1305
|
+
const posts = postsResult.data;
|
|
1138
1306
|
ctx.logger.info(`站点 ${site.id} (${site.name}) 开始检查 ${posts.length} 篇文章是否需要推送`);
|
|
1139
1307
|
if (posts.length > 0) {
|
|
1140
1308
|
for (const post of posts) {
|
|
1141
1309
|
ctx.logger.info(`正在处理文章: ${post.id} - ${post.title.rendered}`);
|
|
1142
1310
|
ctx.logger.info(`文章 ID: ${post.id}, 发布时间: ${post.date}, 修改时间: ${post.modified}`);
|
|
1143
1311
|
// 检查文章是否已推送过(所有群聊共用一个标记)
|
|
1144
|
-
const postRecord = await getPostUpdateRecord(site.id, post.id);
|
|
1312
|
+
const postRecord = await getPostUpdateRecord(site.id, String(post.id));
|
|
1145
1313
|
const hasPushed = !!postRecord;
|
|
1146
1314
|
ctx.logger.info(`检查结果: 站点 ${site.id} 文章 ${post.id} 是否已推送:${hasPushed ? '是' : '否'}`);
|
|
1147
1315
|
if (!hasPushed) {
|
|
@@ -1152,6 +1320,13 @@ function apply(ctx, config) {
|
|
|
1152
1320
|
ctx.logger.info(`正在处理目标: ${target}`);
|
|
1153
1321
|
// 直接使用原始目标字符串,不进行数字转换,避免丢失平台前缀等信息
|
|
1154
1322
|
const stringTarget = target;
|
|
1323
|
+
// 检查 Bot 是否有发送消息的权限
|
|
1324
|
+
const hasPermission = await checkBotPermission(bot, stringTarget);
|
|
1325
|
+
if (!hasPermission) {
|
|
1326
|
+
ctx.logger.warn(`Bot 没有权限向 ${stringTarget} 发送消息,跳过推送`);
|
|
1327
|
+
failedTargets.push(target);
|
|
1328
|
+
continue;
|
|
1329
|
+
}
|
|
1155
1330
|
const message = formatPostMessage(post, site.mentionAll, false, site);
|
|
1156
1331
|
ctx.logger.info(`准备推送新文章到目标: ${stringTarget}`);
|
|
1157
1332
|
await bot.sendMessage(stringTarget, message);
|
|
@@ -1171,7 +1346,7 @@ function apply(ctx, config) {
|
|
|
1171
1346
|
addToFailedQueue('post', post, failedTargets, site);
|
|
1172
1347
|
}
|
|
1173
1348
|
// 标记文章已推送(所有群聊共用一个标记)
|
|
1174
|
-
await updatePostUpdateRecord(site.id, post.id, new Date(post.modified));
|
|
1349
|
+
await updatePostUpdateRecord(site.id, String(post.id), new Date(post.modified));
|
|
1175
1350
|
ctx.logger.info(`已标记站点 ${site.id} 文章 ${post.id} 为已推送,所有群聊将不再推送此文章`);
|
|
1176
1351
|
}
|
|
1177
1352
|
else {
|
|
@@ -1182,10 +1357,17 @@ function apply(ctx, config) {
|
|
|
1182
1357
|
}
|
|
1183
1358
|
// 推送文章更新
|
|
1184
1359
|
if (site.enableUpdatePush) {
|
|
1185
|
-
const
|
|
1360
|
+
const postsResult = await fetchUpdatedPosts(site);
|
|
1361
|
+
if (!postsResult.success) {
|
|
1362
|
+
ctx.logger.error(`获取站点 ${site.id} (${site.name}) 的更新文章失败: ${postsResult.error}`);
|
|
1363
|
+
// 记录失败
|
|
1364
|
+
recordPushFailure(`获取站点 ${site.id} (${site.name}) 的更新文章失败: ${postsResult.error}`);
|
|
1365
|
+
continue;
|
|
1366
|
+
}
|
|
1367
|
+
const posts = postsResult.data;
|
|
1186
1368
|
if (posts.length > 0) {
|
|
1187
1369
|
for (const post of posts) {
|
|
1188
|
-
const updateRecord = await getPostUpdateRecord(site.id, post.id);
|
|
1370
|
+
const updateRecord = await getPostUpdateRecord(site.id, String(post.id));
|
|
1189
1371
|
const postModifiedDate = new Date(post.modified);
|
|
1190
1372
|
// 检查文章是否有更新
|
|
1191
1373
|
if (updateRecord && postModifiedDate > new Date(updateRecord.lastModified)) {
|
|
@@ -1196,6 +1378,13 @@ function apply(ctx, config) {
|
|
|
1196
1378
|
try {
|
|
1197
1379
|
ctx.logger.info(`正在处理目标: ${target}`);
|
|
1198
1380
|
const stringTarget = target;
|
|
1381
|
+
// 检查 Bot 是否有发送消息的权限
|
|
1382
|
+
const hasPermission = await checkBotPermission(bot, stringTarget);
|
|
1383
|
+
if (!hasPermission) {
|
|
1384
|
+
ctx.logger.warn(`Bot 没有权限向 ${stringTarget} 发送消息,跳过推送`);
|
|
1385
|
+
failedTargets.push(target);
|
|
1386
|
+
continue;
|
|
1387
|
+
}
|
|
1199
1388
|
const message = formatPostMessage(post, site.mentionAll, true, site);
|
|
1200
1389
|
ctx.logger.info(`准备推送文章更新到目标: ${stringTarget}`);
|
|
1201
1390
|
await bot.sendMessage(stringTarget, message);
|
|
@@ -1215,7 +1404,7 @@ function apply(ctx, config) {
|
|
|
1215
1404
|
addToFailedQueue('update', post, failedTargets, site);
|
|
1216
1405
|
}
|
|
1217
1406
|
// 更新文章更新记录(所有群聊共用一个标记)
|
|
1218
|
-
await updatePostUpdateRecord(site.id, post.id, postModifiedDate);
|
|
1407
|
+
await updatePostUpdateRecord(site.id, String(post.id), postModifiedDate);
|
|
1219
1408
|
ctx.logger.info(`已更新站点 ${site.id} 文章 ${post.id} 的推送记录,所有群聊将使用此更新时间作为新的推送基准`);
|
|
1220
1409
|
}
|
|
1221
1410
|
}
|
|
@@ -1223,16 +1412,30 @@ function apply(ctx, config) {
|
|
|
1223
1412
|
}
|
|
1224
1413
|
// 推送新用户注册
|
|
1225
1414
|
if (site.enableUserPush) {
|
|
1226
|
-
const
|
|
1415
|
+
const usersResult = await fetchLatestUsers(site);
|
|
1416
|
+
if (!usersResult.success) {
|
|
1417
|
+
ctx.logger.error(`获取站点 ${site.id} (${site.name}) 的用户失败: ${usersResult.error}`);
|
|
1418
|
+
// 记录失败
|
|
1419
|
+
recordPushFailure(`获取站点 ${site.id} (${site.name}) 的用户失败: ${usersResult.error}`);
|
|
1420
|
+
continue;
|
|
1421
|
+
}
|
|
1422
|
+
const users = usersResult.data;
|
|
1227
1423
|
if (users.length > 0) {
|
|
1228
1424
|
for (const user of users) {
|
|
1229
|
-
if (!(await isUserPushed(site.id, user.id))) {
|
|
1425
|
+
if (!(await isUserPushed(site.id, String(user.id)))) {
|
|
1230
1426
|
const failedTargets = [];
|
|
1231
1427
|
for (const target of site.targets) {
|
|
1232
1428
|
try {
|
|
1233
1429
|
ctx.logger.info(`正在处理目标: ${target}`);
|
|
1234
1430
|
// 直接使用原始目标字符串,与新文章推送逻辑保持一致
|
|
1235
1431
|
const stringTarget = target;
|
|
1432
|
+
// 检查 Bot 是否有发送消息的权限
|
|
1433
|
+
const hasPermission = await checkBotPermission(bot, stringTarget);
|
|
1434
|
+
if (!hasPermission) {
|
|
1435
|
+
ctx.logger.warn(`Bot 没有权限向 ${stringTarget} 发送消息,跳过推送`);
|
|
1436
|
+
failedTargets.push(target);
|
|
1437
|
+
continue;
|
|
1438
|
+
}
|
|
1236
1439
|
const message = formatUserMessage(user, site.mentionAll, site);
|
|
1237
1440
|
ctx.logger.info(`准备推送新用户到目标: ${stringTarget}`);
|
|
1238
1441
|
await bot.sendMessage(stringTarget, message);
|
|
@@ -1252,7 +1455,7 @@ function apply(ctx, config) {
|
|
|
1252
1455
|
addToFailedQueue('user', user, failedTargets, site);
|
|
1253
1456
|
}
|
|
1254
1457
|
// 标记用户已推送
|
|
1255
|
-
await markUserAsPushed(site.id, user.id);
|
|
1458
|
+
await markUserAsPushed(site.id, String(user.id));
|
|
1256
1459
|
}
|
|
1257
1460
|
}
|
|
1258
1461
|
}
|
|
@@ -1269,7 +1472,12 @@ function apply(ctx, config) {
|
|
|
1269
1472
|
if (!targetSite) {
|
|
1270
1473
|
return `未找到站点 ID: ${siteId}`;
|
|
1271
1474
|
}
|
|
1272
|
-
const
|
|
1475
|
+
const postsResult = await fetchLatestPosts(targetSite);
|
|
1476
|
+
if (!postsResult.success) {
|
|
1477
|
+
ctx.logger.error(`获取最新文章失败: ${postsResult.error}`);
|
|
1478
|
+
return `获取文章失败: ${postsResult.error}`;
|
|
1479
|
+
}
|
|
1480
|
+
const posts = postsResult.data;
|
|
1273
1481
|
if (posts.length === 0) {
|
|
1274
1482
|
ctx.logger.info(`站点 ${targetSite.id} 没有找到文章`);
|
|
1275
1483
|
return `站点 ${targetSite.name} 暂无文章`;
|
|
@@ -1311,7 +1519,12 @@ function apply(ctx, config) {
|
|
|
1311
1519
|
if (!targetSite) {
|
|
1312
1520
|
return `未找到站点 ID: ${siteId}`;
|
|
1313
1521
|
}
|
|
1314
|
-
const
|
|
1522
|
+
const postsResult = await fetchLatestPosts(targetSite);
|
|
1523
|
+
if (!postsResult.success) {
|
|
1524
|
+
ctx.logger.error(`获取文章列表失败: ${postsResult.error}`);
|
|
1525
|
+
return `获取文章失败: ${postsResult.error}`;
|
|
1526
|
+
}
|
|
1527
|
+
const posts = postsResult.data;
|
|
1315
1528
|
if (posts.length === 0) {
|
|
1316
1529
|
return `站点 ${targetSite.name} 暂无文章`;
|
|
1317
1530
|
}
|
|
@@ -1412,7 +1625,7 @@ function apply(ctx, config) {
|
|
|
1412
1625
|
}
|
|
1413
1626
|
// 切换开关
|
|
1414
1627
|
site.enableUpdatePush = !site.enableUpdatePush;
|
|
1415
|
-
await
|
|
1628
|
+
await saveSiteConfig(siteId, site);
|
|
1416
1629
|
return `站点 ${site.name} 的文章更新推送已${site.enableUpdatePush ? '开启' : '关闭'}`;
|
|
1417
1630
|
});
|
|
1418
1631
|
ctx.command('wordpress.site.toggle-user <siteId>', '切换指定站点的新用户注册推送开关')
|
|
@@ -1430,7 +1643,7 @@ function apply(ctx, config) {
|
|
|
1430
1643
|
}
|
|
1431
1644
|
// 切换开关
|
|
1432
1645
|
site.enableUserPush = !site.enableUserPush;
|
|
1433
|
-
await
|
|
1646
|
+
await saveSiteConfig(siteId, site);
|
|
1434
1647
|
return `站点 ${site.name} 的新用户注册推送已${site.enableUserPush ? '开启' : '关闭'}`;
|
|
1435
1648
|
});
|
|
1436
1649
|
ctx.command('wordpress.site.toggle <siteId>', '切换指定站点的自动推送开关')
|
|
@@ -1448,7 +1661,7 @@ function apply(ctx, config) {
|
|
|
1448
1661
|
}
|
|
1449
1662
|
// 切换开关
|
|
1450
1663
|
site.enableAutoPush = !site.enableAutoPush;
|
|
1451
|
-
await
|
|
1664
|
+
await saveSiteConfig(siteId, site);
|
|
1452
1665
|
return `站点 ${site.name} 的自动推送已${site.enableAutoPush ? '开启' : '关闭'}`;
|
|
1453
1666
|
});
|
|
1454
1667
|
ctx.command('wordpress.site.mention <siteId>', '切换指定站点的 @全体成员 开关')
|
|
@@ -1466,7 +1679,7 @@ function apply(ctx, config) {
|
|
|
1466
1679
|
}
|
|
1467
1680
|
// 切换开关
|
|
1468
1681
|
site.mentionAll = !site.mentionAll;
|
|
1469
|
-
await
|
|
1682
|
+
await saveSiteConfig(siteId, site);
|
|
1470
1683
|
return `站点 ${site.name} 的 @全体成员 已${site.mentionAll ? '开启' : '关闭'}`;
|
|
1471
1684
|
});
|
|
1472
1685
|
ctx.command('wordpress.site.set-url <siteId> <url>', '修改指定站点的 WordPress 地址')
|
|
@@ -1484,7 +1697,7 @@ function apply(ctx, config) {
|
|
|
1484
1697
|
}
|
|
1485
1698
|
// 修改站点地址
|
|
1486
1699
|
site.url = url;
|
|
1487
|
-
await
|
|
1700
|
+
await saveSiteConfig(siteId, site);
|
|
1488
1701
|
ctx.logger.info(`站点 ${site.name} 的地址已修改为:${url}`);
|
|
1489
1702
|
return `站点 ${site.name} 的 WordPress 地址已修改为:${url}`;
|
|
1490
1703
|
});
|
|
@@ -1528,8 +1741,8 @@ function apply(ctx, config) {
|
|
|
1528
1741
|
ctx.logger.info(`命令 wordpress.clean 被调用,天数:${days || '默认'},调用者:${authResult.userId}`);
|
|
1529
1742
|
// 设置默认天数
|
|
1530
1743
|
const daysToKeep = days ? parseInt(days) : CONSTANTS.DEFAULT_CLEAN_DAYS;
|
|
1531
|
-
if (isNaN(daysToKeep) || daysToKeep <= 0) {
|
|
1532
|
-
return '
|
|
1744
|
+
if (isNaN(daysToKeep) || daysToKeep <= 0 || daysToKeep > 365) {
|
|
1745
|
+
return '请输入有效的天数(1-365天)';
|
|
1533
1746
|
}
|
|
1534
1747
|
// 计算清理时间点
|
|
1535
1748
|
const cutoffDate = new Date();
|
|
@@ -1538,46 +1751,84 @@ function apply(ctx, config) {
|
|
|
1538
1751
|
let result = 0;
|
|
1539
1752
|
// 批量删除 wordpress_post_updates 中的旧记录
|
|
1540
1753
|
try {
|
|
1541
|
-
//
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1754
|
+
// 使用分页查询,每次处理 100 条记录,避免内存溢出
|
|
1755
|
+
let processed = 0;
|
|
1756
|
+
let hasMore = true;
|
|
1757
|
+
const queryBatchSize = 100;
|
|
1758
|
+
while (hasMore) {
|
|
1759
|
+
// 分页查询记录
|
|
1760
|
+
const updateRecords = await ctx.database.get('wordpress_post_updates', {}, {
|
|
1761
|
+
limit: queryBatchSize,
|
|
1762
|
+
offset: processed
|
|
1763
|
+
});
|
|
1764
|
+
if (updateRecords.length === 0) {
|
|
1765
|
+
hasMore = false;
|
|
1766
|
+
break;
|
|
1554
1767
|
}
|
|
1555
|
-
|
|
1556
|
-
|
|
1768
|
+
// 过滤需要删除的记录
|
|
1769
|
+
const recordsToRemove = updateRecords.filter(record => {
|
|
1770
|
+
return new Date(record.pushedAt) < cutoffDate;
|
|
1771
|
+
});
|
|
1772
|
+
if (recordsToRemove.length === 0) {
|
|
1773
|
+
processed += updateRecords.length;
|
|
1774
|
+
continue;
|
|
1775
|
+
}
|
|
1776
|
+
// 批量删除,每批最多删除 10 条记录
|
|
1777
|
+
const deleteBatchSize = 10;
|
|
1778
|
+
for (let i = 0; i < recordsToRemove.length; i += deleteBatchSize) {
|
|
1779
|
+
const batch = recordsToRemove.slice(i, i + deleteBatchSize);
|
|
1780
|
+
// 并行删除,提高效率
|
|
1781
|
+
await Promise.all(batch.map(record => ctx.database.remove('wordpress_post_updates', { id: record.id })));
|
|
1782
|
+
}
|
|
1783
|
+
result += recordsToRemove.length;
|
|
1784
|
+
processed += updateRecords.length;
|
|
1785
|
+
ctx.logger.info(`已处理 ${processed} 条记录,删除了 ${recordsToRemove.length} 条旧记录`);
|
|
1786
|
+
// 避免数据库压力过大,每批处理后稍作延迟
|
|
1787
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
1557
1788
|
}
|
|
1789
|
+
ctx.logger.info(`批量删除 wordpress_post_updates 旧记录完成,共删除 ${result} 条记录`);
|
|
1558
1790
|
}
|
|
1559
1791
|
catch (error) {
|
|
1560
1792
|
ctx.logger.error(`批量删除 wordpress_post_updates 旧记录失败: ${error}`);
|
|
1561
1793
|
}
|
|
1562
1794
|
// 批量删除 wordpress_user_registrations 中的旧记录
|
|
1563
1795
|
try {
|
|
1564
|
-
//
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1796
|
+
// 使用分页查询,每次处理 100 条记录,避免内存溢出
|
|
1797
|
+
let processed = 0;
|
|
1798
|
+
let hasMore = true;
|
|
1799
|
+
const queryBatchSize = 100;
|
|
1800
|
+
while (hasMore) {
|
|
1801
|
+
// 分页查询记录
|
|
1802
|
+
const userRecords = await ctx.database.get('wordpress_user_registrations', {}, {
|
|
1803
|
+
limit: queryBatchSize,
|
|
1804
|
+
offset: processed
|
|
1805
|
+
});
|
|
1806
|
+
if (userRecords.length === 0) {
|
|
1807
|
+
hasMore = false;
|
|
1808
|
+
break;
|
|
1577
1809
|
}
|
|
1578
|
-
|
|
1579
|
-
|
|
1810
|
+
// 过滤需要删除的记录
|
|
1811
|
+
const recordsToRemove = userRecords.filter(record => {
|
|
1812
|
+
return new Date(record.pushedAt) < cutoffDate;
|
|
1813
|
+
});
|
|
1814
|
+
if (recordsToRemove.length === 0) {
|
|
1815
|
+
processed += userRecords.length;
|
|
1816
|
+
continue;
|
|
1817
|
+
}
|
|
1818
|
+
// 批量删除,每批最多删除 10 条记录
|
|
1819
|
+
const deleteBatchSize = 10;
|
|
1820
|
+
for (let i = 0; i < recordsToRemove.length; i += deleteBatchSize) {
|
|
1821
|
+
const batch = recordsToRemove.slice(i, i + deleteBatchSize);
|
|
1822
|
+
// 并行删除,提高效率
|
|
1823
|
+
await Promise.all(batch.map(record => ctx.database.remove('wordpress_user_registrations', { id: record.id })));
|
|
1824
|
+
}
|
|
1825
|
+
result += recordsToRemove.length;
|
|
1826
|
+
processed += userRecords.length;
|
|
1827
|
+
ctx.logger.info(`已处理 ${processed} 条记录,删除了 ${recordsToRemove.length} 条旧记录`);
|
|
1828
|
+
// 避免数据库压力过大,每批处理后稍作延迟
|
|
1829
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
1580
1830
|
}
|
|
1831
|
+
ctx.logger.info(`批量删除 wordpress_user_registrations 旧记录完成,共删除 ${result} 条记录`);
|
|
1581
1832
|
}
|
|
1582
1833
|
catch (error) {
|
|
1583
1834
|
ctx.logger.error(`批量删除 wordpress_user_registrations 旧记录失败: ${error}`);
|
|
@@ -1690,9 +1941,10 @@ function apply(ctx, config) {
|
|
|
1690
1941
|
ctx.logger.info(`准备返回消息,长度: ${message.length}`);
|
|
1691
1942
|
return message;
|
|
1692
1943
|
});
|
|
1693
|
-
//
|
|
1944
|
+
// 为每个站点设置独立的定时任务,绑定到 ctx 生命周期
|
|
1945
|
+
const siteTimers = [];
|
|
1694
1946
|
config.sites.forEach(site => {
|
|
1695
|
-
ctx.setInterval(() => {
|
|
1947
|
+
const timer = ctx.setInterval(() => {
|
|
1696
1948
|
try {
|
|
1697
1949
|
pushNewPosts();
|
|
1698
1950
|
}
|
|
@@ -1703,5 +1955,28 @@ function apply(ctx, config) {
|
|
|
1703
1955
|
ctx.logger.warn('定时任务将在下一个周期继续执行');
|
|
1704
1956
|
}
|
|
1705
1957
|
}, site.interval);
|
|
1958
|
+
siteTimers.push(timer);
|
|
1959
|
+
});
|
|
1960
|
+
// 绑定到 ctx 生命周期,插件卸载时清理所有定时器
|
|
1961
|
+
ctx.on('dispose', () => {
|
|
1962
|
+
ctx.logger.info('WordPress 插件开始清理资源...');
|
|
1963
|
+
// 清理缓存清理定时器
|
|
1964
|
+
if (typeof cacheCleanupTimer === 'function') {
|
|
1965
|
+
cacheCleanupTimer();
|
|
1966
|
+
ctx.logger.info('缓存清理定时器已清理');
|
|
1967
|
+
}
|
|
1968
|
+
// 清理失败队列定时器
|
|
1969
|
+
if (typeof failedQueueTimer === 'function') {
|
|
1970
|
+
failedQueueTimer();
|
|
1971
|
+
ctx.logger.info('失败队列定时器已清理');
|
|
1972
|
+
}
|
|
1973
|
+
// 清理站点定时任务
|
|
1974
|
+
siteTimers.forEach((timer, index) => {
|
|
1975
|
+
if (typeof timer === 'function') {
|
|
1976
|
+
timer();
|
|
1977
|
+
ctx.logger.info(`站点定时任务 ${index + 1} 已清理`);
|
|
1978
|
+
}
|
|
1979
|
+
});
|
|
1980
|
+
ctx.logger.info('WordPress 插件资源清理完成');
|
|
1706
1981
|
});
|
|
1707
1982
|
}
|