koishi-plugin-wordpress-notifier 2.8.4 → 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/README.md +13 -0
- package/lib/index.d.ts +7 -7
- package/lib/index.js +425 -119
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -389,6 +389,19 @@ npm install
|
|
|
389
389
|
|
|
390
390
|
## 版本历史
|
|
391
391
|
|
|
392
|
+
### 2.8.5 (2026-02-19)
|
|
393
|
+
|
|
394
|
+
- 🐛 修复 MySQL 主键约束错误 (ER_PRIMARY_CANT_HAVE_NULL)
|
|
395
|
+
|
|
396
|
+
**核心修复**
|
|
397
|
+
- ✅ 增强 `checkAndFixTableStructure` 函数,添加对 `wordpress_version` 表的验证和修复逻辑
|
|
398
|
+
- ✅ 确保所有数据库表的主键约束正确设置,避免 NULL 值问题
|
|
399
|
+
- ✅ 优化表结构初始化过程,确保所有表都能正确创建和初始化
|
|
400
|
+
|
|
401
|
+
**稳定性提升**
|
|
402
|
+
- ✅ 提高插件在 MySQL 数据库环境下的稳定性
|
|
403
|
+
- ✅ 增强数据库表结构异常情况下的恢复能力
|
|
404
|
+
|
|
392
405
|
### 2.8.4 (2026-02-19)
|
|
393
406
|
|
|
394
407
|
- 🔧 版本更新和文档完善
|
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,22 +129,23 @@ function apply(ctx, config) {
|
|
|
129
129
|
ctx.logger.info('WordPress 推送插件已加载');
|
|
130
130
|
// 修复 MySQL 自增主键问题,使用正确的模型配置
|
|
131
131
|
// 确保 id 字段被正确设置为自增主键,并且在插入时不会被设置为 NULL
|
|
132
|
+
// 显式指定 nullable: false 约束以适配 MySQL 特有要求
|
|
132
133
|
ctx.model.extend('wordpress_post_updates', {
|
|
133
|
-
id: 'integer',
|
|
134
|
-
siteId: 'string',
|
|
135
|
-
postId: 'integer',
|
|
136
|
-
lastModified: 'timestamp',
|
|
137
|
-
pushedAt: 'timestamp'
|
|
134
|
+
id: { type: 'integer', nullable: false },
|
|
135
|
+
siteId: { type: 'string', nullable: false },
|
|
136
|
+
postId: { type: 'integer', nullable: false },
|
|
137
|
+
lastModified: { type: 'timestamp', nullable: false },
|
|
138
|
+
pushedAt: { type: 'timestamp', nullable: false }
|
|
138
139
|
}, {
|
|
139
140
|
primary: 'id',
|
|
140
141
|
autoInc: true,
|
|
141
142
|
unique: ['siteId', 'postId'] // 每个站点的文章 ID 唯一
|
|
142
143
|
});
|
|
143
144
|
ctx.model.extend('wordpress_user_registrations', {
|
|
144
|
-
id: 'integer',
|
|
145
|
-
siteId: 'string',
|
|
146
|
-
userId: 'integer',
|
|
147
|
-
pushedAt: 'timestamp'
|
|
145
|
+
id: { type: 'integer', nullable: false },
|
|
146
|
+
siteId: { type: 'string', nullable: false },
|
|
147
|
+
userId: { type: 'integer', nullable: false },
|
|
148
|
+
pushedAt: { type: 'timestamp', nullable: false }
|
|
148
149
|
}, {
|
|
149
150
|
primary: 'id',
|
|
150
151
|
autoInc: true,
|
|
@@ -152,43 +153,108 @@ function apply(ctx, config) {
|
|
|
152
153
|
});
|
|
153
154
|
// 配置存储表
|
|
154
155
|
ctx.model.extend('wordpress_config', {
|
|
155
|
-
id: '
|
|
156
|
-
key: 'string',
|
|
157
|
-
value: 'string',
|
|
158
|
-
updatedAt: 'timestamp'
|
|
156
|
+
id: { type: 'string', nullable: false },
|
|
157
|
+
key: { type: 'string', nullable: false },
|
|
158
|
+
value: { type: 'string', nullable: false },
|
|
159
|
+
updatedAt: { type: 'timestamp', nullable: false }
|
|
159
160
|
}, {
|
|
160
161
|
primary: 'id',
|
|
161
|
-
autoInc: true,
|
|
162
162
|
unique: ['key']
|
|
163
163
|
});
|
|
164
164
|
// 版本记录表
|
|
165
165
|
ctx.model.extend('wordpress_version', {
|
|
166
|
-
id: '
|
|
167
|
-
version: 'string',
|
|
168
|
-
updatedAt: 'timestamp'
|
|
166
|
+
id: { type: 'string', nullable: false },
|
|
167
|
+
version: { type: 'string', nullable: false },
|
|
168
|
+
updatedAt: { type: 'timestamp', nullable: false }
|
|
169
169
|
}, {
|
|
170
170
|
primary: 'id',
|
|
171
|
-
autoInc: true,
|
|
172
171
|
unique: ['version']
|
|
173
172
|
});
|
|
174
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
|
+
}
|
|
175
191
|
// 为所有数据库操作添加详细日志,便于诊断自增主键问题
|
|
176
192
|
ctx.on('ready', async () => {
|
|
177
193
|
ctx.logger.info('WordPress 推送插件已就绪,开始初始化推送任务');
|
|
178
194
|
ctx.logger.info('数据库表配置:');
|
|
179
|
-
ctx.logger.info('wordpress_post_updates: id 字段设置为
|
|
180
|
-
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');
|
|
181
197
|
ctx.logger.info('wordpress_config: 配置持久化存储表');
|
|
182
198
|
ctx.logger.info('wordpress_version: 数据库版本记录表');
|
|
183
199
|
ctx.logger.info('所有群聊共用一个文章标记,不再区分群聊');
|
|
184
200
|
// 检查并修复数据库表结构问题
|
|
185
201
|
await checkAndFixTableStructure();
|
|
202
|
+
// 检查和更新数据库版本
|
|
203
|
+
await checkAndUpdateDatabaseVersion();
|
|
186
204
|
// 加载持久化配置
|
|
187
205
|
await loadPersistentConfig();
|
|
188
206
|
// 执行初始推送
|
|
189
207
|
await pushNewPosts();
|
|
190
208
|
});
|
|
191
|
-
//
|
|
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
|
+
// 加载持久化配置,使用分页查询避免内存溢出
|
|
192
258
|
async function loadPersistentConfig() {
|
|
193
259
|
try {
|
|
194
260
|
ctx.logger.info('开始加载持久化配置...');
|
|
@@ -198,7 +264,10 @@ function apply(ctx, config) {
|
|
|
198
264
|
ctx.logger.warn('数据库连接异常,跳过加载持久化配置');
|
|
199
265
|
return;
|
|
200
266
|
}
|
|
201
|
-
|
|
267
|
+
// 使用分页查询,每次最多获取 100 条记录
|
|
268
|
+
const configRecords = await ctx.database.get('wordpress_config', {}, {
|
|
269
|
+
limit: 100
|
|
270
|
+
});
|
|
202
271
|
ctx.logger.info(`找到 ${configRecords.length} 条持久化配置记录`);
|
|
203
272
|
for (const record of configRecords) {
|
|
204
273
|
try {
|
|
@@ -243,7 +312,7 @@ function apply(ctx, config) {
|
|
|
243
312
|
// 更新现有配置
|
|
244
313
|
await ctx.database.remove('wordpress_config', { key });
|
|
245
314
|
}
|
|
246
|
-
//
|
|
315
|
+
// 创建新配置记录,只保存指定的键值对
|
|
247
316
|
await ctx.database.create('wordpress_config', {
|
|
248
317
|
key,
|
|
249
318
|
value: JSON.stringify(value),
|
|
@@ -255,6 +324,47 @@ function apply(ctx, config) {
|
|
|
255
324
|
ctx.logger.error(`保存配置失败,键: ${key},错误: ${error}`);
|
|
256
325
|
}
|
|
257
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
|
+
}
|
|
258
368
|
// 缓存对象,用于存储高频格式化结果,有效期1小时
|
|
259
369
|
const formatCache = {
|
|
260
370
|
post: new Map(),
|
|
@@ -281,8 +391,8 @@ function apply(ctx, config) {
|
|
|
281
391
|
}
|
|
282
392
|
}
|
|
283
393
|
}
|
|
284
|
-
//
|
|
285
|
-
setInterval(cleanExpiredCache, CONSTANTS.CACHE_EXPIRY);
|
|
394
|
+
// 定期清理过期缓存,绑定到 ctx 生命周期
|
|
395
|
+
const cacheCleanupTimer = ctx.setInterval(cleanExpiredCache, CONSTANTS.CACHE_EXPIRY);
|
|
286
396
|
// 健壮获取 QQ Bot 实例,兼容多种适配器,优先选择 QQ 官方 bot
|
|
287
397
|
function getValidBot() {
|
|
288
398
|
// 支持的 QQ 相关适配器列表,'qq' 为 QQ 官方 bot
|
|
@@ -293,8 +403,9 @@ function apply(ctx, config) {
|
|
|
293
403
|
const connectedBots = botList.filter(bot => {
|
|
294
404
|
// 检查 Bot 是否已连接
|
|
295
405
|
// 不同适配器可能有不同的状态属性
|
|
296
|
-
//
|
|
297
|
-
|
|
406
|
+
// 尝试检查常见的状态属性
|
|
407
|
+
const status = String(bot.status || '');
|
|
408
|
+
return status === 'online' || status === 'connected' || status === '';
|
|
298
409
|
});
|
|
299
410
|
ctx.logger.info(`找到 ${connectedBots.length} 个已连接的 Bot,总 Bot 数: ${botList.length}`);
|
|
300
411
|
if (connectedBots.length === 0) {
|
|
@@ -319,18 +430,53 @@ function apply(ctx, config) {
|
|
|
319
430
|
ctx.logger.info(`选择可用的已连接 Bot: ${connectedBots[0].platform} - ${connectedBots[0].selfId || 'unknown'}`);
|
|
320
431
|
return connectedBots[0];
|
|
321
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
|
+
}
|
|
322
465
|
const failedPushQueue = [];
|
|
323
466
|
const MAX_RETRIES = CONSTANTS.MAX_PUSH_RETRIES;
|
|
324
467
|
const RETRY_INTERVAL = CONSTANTS.PUSH_RETRY_INTERVAL; // 5分钟
|
|
325
468
|
// 添加到失败队列
|
|
326
469
|
function addToFailedQueue(type, data, targets, siteConfig) {
|
|
470
|
+
const now = new Date();
|
|
471
|
+
const expireAt = new Date(now.getTime() + 24 * 60 * 60 * 1000); // 24小时过期
|
|
327
472
|
const item = {
|
|
328
473
|
id: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
329
474
|
type,
|
|
330
475
|
data,
|
|
331
476
|
targets,
|
|
332
477
|
retries: 0,
|
|
333
|
-
createdAt:
|
|
478
|
+
createdAt: now,
|
|
479
|
+
expireAt,
|
|
334
480
|
siteConfig
|
|
335
481
|
};
|
|
336
482
|
failedPushQueue.push(item);
|
|
@@ -347,9 +493,16 @@ function apply(ctx, config) {
|
|
|
347
493
|
ctx.logger.error('没有可用的 Bot 实例,无法处理失败队列');
|
|
348
494
|
return;
|
|
349
495
|
}
|
|
496
|
+
const now = new Date();
|
|
350
497
|
const itemsToRemove = [];
|
|
351
498
|
for (let i = 0; i < failedPushQueue.length; i++) {
|
|
352
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
|
+
}
|
|
353
506
|
if (item.retries >= MAX_RETRIES) {
|
|
354
507
|
ctx.logger.warn(`达到最大重试次数,放弃推送,类型: ${item.type},创建时间: ${item.createdAt}`);
|
|
355
508
|
itemsToRemove.push(i);
|
|
@@ -390,8 +543,8 @@ function apply(ctx, config) {
|
|
|
390
543
|
}
|
|
391
544
|
ctx.logger.info(`失败队列处理完成,剩余队列长度: ${failedPushQueue.length}`);
|
|
392
545
|
}
|
|
393
|
-
//
|
|
394
|
-
setInterval(processFailedQueue, RETRY_INTERVAL);
|
|
546
|
+
// 定期处理失败队列,绑定到 ctx 生命周期
|
|
547
|
+
const failedQueueTimer = ctx.setInterval(processFailedQueue, RETRY_INTERVAL);
|
|
395
548
|
// 通用权限检查函数,检查用户是否为超级管理员
|
|
396
549
|
function checkSuperAdmin(session) {
|
|
397
550
|
// 检查是否为超级管理员
|
|
@@ -532,11 +685,11 @@ function apply(ctx, config) {
|
|
|
532
685
|
ctx.logger.info('旧表删除成功,重新初始化表结构...');
|
|
533
686
|
// 重新扩展模型
|
|
534
687
|
ctx.model.extend('wordpress_post_updates', {
|
|
535
|
-
id: 'integer',
|
|
536
|
-
siteId: 'string',
|
|
537
|
-
postId: 'integer',
|
|
538
|
-
lastModified: 'timestamp',
|
|
539
|
-
pushedAt: 'timestamp'
|
|
688
|
+
id: { type: 'integer', nullable: false },
|
|
689
|
+
siteId: { type: 'string', nullable: false },
|
|
690
|
+
postId: { type: 'integer', nullable: false },
|
|
691
|
+
lastModified: { type: 'timestamp', nullable: false },
|
|
692
|
+
pushedAt: { type: 'timestamp', nullable: false }
|
|
540
693
|
}, {
|
|
541
694
|
primary: 'id',
|
|
542
695
|
autoInc: true,
|
|
@@ -564,10 +717,10 @@ function apply(ctx, config) {
|
|
|
564
717
|
ctx.logger.info('旧表删除成功,重新初始化表结构...');
|
|
565
718
|
// 重新扩展模型
|
|
566
719
|
ctx.model.extend('wordpress_user_registrations', {
|
|
567
|
-
id: 'integer',
|
|
568
|
-
siteId: 'string',
|
|
569
|
-
userId: 'integer',
|
|
570
|
-
pushedAt: 'timestamp'
|
|
720
|
+
id: { type: 'integer', nullable: false },
|
|
721
|
+
siteId: { type: 'string', nullable: false },
|
|
722
|
+
userId: { type: 'integer', nullable: false },
|
|
723
|
+
pushedAt: { type: 'timestamp', nullable: false }
|
|
571
724
|
}, {
|
|
572
725
|
primary: 'id',
|
|
573
726
|
autoInc: true,
|
|
@@ -595,13 +748,12 @@ function apply(ctx, config) {
|
|
|
595
748
|
ctx.logger.info('旧表删除成功,重新初始化表结构...');
|
|
596
749
|
// 重新扩展模型
|
|
597
750
|
ctx.model.extend('wordpress_config', {
|
|
598
|
-
id: '
|
|
599
|
-
key: 'string',
|
|
600
|
-
value: 'string',
|
|
601
|
-
updatedAt: 'timestamp'
|
|
751
|
+
id: { type: 'string', nullable: false },
|
|
752
|
+
key: { type: 'string', nullable: false },
|
|
753
|
+
value: { type: 'string', nullable: false },
|
|
754
|
+
updatedAt: { type: 'timestamp', nullable: false }
|
|
602
755
|
}, {
|
|
603
756
|
primary: 'id',
|
|
604
|
-
autoInc: true,
|
|
605
757
|
unique: ['key']
|
|
606
758
|
});
|
|
607
759
|
ctx.logger.info('wordpress_config 表重新初始化成功');
|
|
@@ -610,6 +762,35 @@ function apply(ctx, config) {
|
|
|
610
762
|
ctx.logger.warn(`删除旧表失败,可能表不存在:${dropError}`);
|
|
611
763
|
}
|
|
612
764
|
}
|
|
765
|
+
try {
|
|
766
|
+
ctx.logger.info('验证 wordpress_version 表结构...');
|
|
767
|
+
const testVersion = await ctx.database.get('wordpress_version', {}, {
|
|
768
|
+
limit: 1
|
|
769
|
+
});
|
|
770
|
+
ctx.logger.info(`wordpress_version 表验证成功,现有记录数:${testVersion.length}`);
|
|
771
|
+
}
|
|
772
|
+
catch (error) {
|
|
773
|
+
ctx.logger.warn(`wordpress_version 表可能结构不正确,尝试重新初始化...`);
|
|
774
|
+
// 尝试删除旧表并重新初始化
|
|
775
|
+
try {
|
|
776
|
+
ctx.logger.info('尝试删除旧的 wordpress_version 表...');
|
|
777
|
+
await ctx.database.drop('wordpress_version');
|
|
778
|
+
ctx.logger.info('旧表删除成功,重新初始化表结构...');
|
|
779
|
+
// 重新扩展模型
|
|
780
|
+
ctx.model.extend('wordpress_version', {
|
|
781
|
+
id: { type: 'string', nullable: false },
|
|
782
|
+
version: { type: 'string', nullable: false },
|
|
783
|
+
updatedAt: { type: 'timestamp', nullable: false }
|
|
784
|
+
}, {
|
|
785
|
+
primary: 'id',
|
|
786
|
+
unique: ['version']
|
|
787
|
+
});
|
|
788
|
+
ctx.logger.info('wordpress_version 表重新初始化成功');
|
|
789
|
+
}
|
|
790
|
+
catch (dropError) {
|
|
791
|
+
ctx.logger.warn(`删除旧表失败,可能表不存在:${dropError}`);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
613
794
|
ctx.logger.info('表结构检查和修复完成');
|
|
614
795
|
}
|
|
615
796
|
catch (error) {
|
|
@@ -675,18 +856,22 @@ function apply(ctx, config) {
|
|
|
675
856
|
}
|
|
676
857
|
const response = await httpRequest(url, requestConfig);
|
|
677
858
|
if (!response) {
|
|
678
|
-
|
|
679
|
-
|
|
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 };
|
|
680
864
|
}
|
|
681
865
|
ctx.logger.info(`成功获取站点 ${site.id} (${site.name}) 的 ${response.length} 篇文章`);
|
|
682
|
-
return response;
|
|
866
|
+
return { success: true, data: response, error: '' };
|
|
683
867
|
}
|
|
684
868
|
catch (error) {
|
|
685
869
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
686
|
-
|
|
870
|
+
const fullErrorMessage = `获取站点 ${site.id} (${site.name}) 的 WordPress 文章失败: ${errorMessage}`;
|
|
871
|
+
ctx.logger.error(fullErrorMessage);
|
|
687
872
|
// 记录失败
|
|
688
873
|
recordPushFailure(`获取站点 ${site.id} (${site.name}) 的文章失败: ${errorMessage}`);
|
|
689
|
-
return [];
|
|
874
|
+
return { success: false, data: [], error: fullErrorMessage };
|
|
690
875
|
}
|
|
691
876
|
}
|
|
692
877
|
async function fetchLatestUsers(site) {
|
|
@@ -709,12 +894,13 @@ function apply(ctx, config) {
|
|
|
709
894
|
}
|
|
710
895
|
const response = await httpRequest(url, requestConfig);
|
|
711
896
|
if (!response) {
|
|
712
|
-
|
|
897
|
+
const errorMessage = `获取站点 ${site.id} (${site.name}) 的 WordPress 用户失败,已达到最大重试次数`;
|
|
898
|
+
ctx.logger.error(errorMessage);
|
|
713
899
|
ctx.logger.error(`WordPress REST API 的 users 端点需要认证才能访问,请在站点配置中添加 WordPress 用户名和应用程序密码`);
|
|
714
900
|
// 记录失败
|
|
715
901
|
recordPushFailure(`获取站点 ${site.id} (${site.name}) 的用户失败: API 认证失败`);
|
|
716
902
|
// 返回空数组,确保插件继续运行
|
|
717
|
-
return [];
|
|
903
|
+
return { success: false, data: [], error: errorMessage };
|
|
718
904
|
}
|
|
719
905
|
ctx.logger.info(`成功获取站点 ${site.id} (${site.name}) 的 ${response.length} 位用户`);
|
|
720
906
|
// 添加调试日志,查看API返回的实际数据结构
|
|
@@ -724,16 +910,17 @@ function apply(ctx, config) {
|
|
|
724
910
|
const user = response[0];
|
|
725
911
|
ctx.logger.info(`用户日期字段: date=${user.date}, date_registered=${user.date_registered}, registered_date=${user.registered_date}, created_at=${user.created_at}`);
|
|
726
912
|
}
|
|
727
|
-
return response;
|
|
913
|
+
return { success: true, data: response, error: '' };
|
|
728
914
|
}
|
|
729
915
|
catch (error) {
|
|
730
916
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
731
|
-
|
|
917
|
+
const fullErrorMessage = `获取站点 ${site.id} (${site.name}) 的 WordPress 用户失败: ${errorMessage}`;
|
|
918
|
+
ctx.logger.error(fullErrorMessage);
|
|
732
919
|
ctx.logger.error(`WordPress REST API 的 users 端点需要认证才能访问,请在站点配置中添加 WordPress 用户名和应用程序密码`);
|
|
733
920
|
// 记录失败
|
|
734
921
|
recordPushFailure(`获取站点 ${site.id} (${site.name}) 的用户失败: ${errorMessage}`);
|
|
735
922
|
// 返回空数组,确保插件继续运行
|
|
736
|
-
return [];
|
|
923
|
+
return { success: false, data: [], error: fullErrorMessage };
|
|
737
924
|
}
|
|
738
925
|
}
|
|
739
926
|
async function fetchUpdatedPosts(site) {
|
|
@@ -753,20 +940,22 @@ function apply(ctx, config) {
|
|
|
753
940
|
}
|
|
754
941
|
const response = await httpRequest(url, requestConfig);
|
|
755
942
|
if (!response) {
|
|
756
|
-
|
|
943
|
+
const errorMessage = `获取站点 ${site.id} (${site.name}) 的 WordPress 更新文章失败,已达到最大重试次数`;
|
|
944
|
+
ctx.logger.error(errorMessage);
|
|
757
945
|
// 记录失败
|
|
758
946
|
recordPushFailure(`获取站点 ${site.id} (${site.name}) 的更新文章失败`);
|
|
759
|
-
return [];
|
|
947
|
+
return { success: false, data: [], error: errorMessage };
|
|
760
948
|
}
|
|
761
949
|
ctx.logger.info(`成功获取站点 ${site.id} (${site.name}) 的 ${response.length} 篇更新文章`);
|
|
762
|
-
return response;
|
|
950
|
+
return { success: true, data: response, error: '' };
|
|
763
951
|
}
|
|
764
952
|
catch (error) {
|
|
765
953
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
766
|
-
|
|
954
|
+
const fullErrorMessage = `获取站点 ${site.id} (${site.name}) 的 WordPress 更新文章失败: ${errorMessage}`;
|
|
955
|
+
ctx.logger.error(fullErrorMessage);
|
|
767
956
|
// 记录失败
|
|
768
957
|
recordPushFailure(`获取站点 ${site.id} (${site.name}) 的更新文章失败: ${errorMessage}`);
|
|
769
|
-
return [];
|
|
958
|
+
return { success: false, data: [], error: fullErrorMessage };
|
|
770
959
|
}
|
|
771
960
|
}
|
|
772
961
|
async function isUserPushed(siteId, userId) {
|
|
@@ -778,7 +967,7 @@ function apply(ctx, config) {
|
|
|
778
967
|
return false;
|
|
779
968
|
}
|
|
780
969
|
ctx.logger.info(`检查用户是否已推送,站点 ID: ${siteId},用户 ID: ${userId}`);
|
|
781
|
-
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) });
|
|
782
971
|
const result = record.length > 0;
|
|
783
972
|
ctx.logger.info(`检查结果:站点 ${siteId} 用户 ${userId} 已推送:${result ? '是' : '否'}`);
|
|
784
973
|
return result;
|
|
@@ -800,7 +989,7 @@ function apply(ctx, config) {
|
|
|
800
989
|
return null;
|
|
801
990
|
}
|
|
802
991
|
ctx.logger.info(`获取文章更新记录,站点 ID: ${siteId},文章 ID: ${postId}`);
|
|
803
|
-
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) });
|
|
804
993
|
const result = records.length > 0 ? records[0] : null;
|
|
805
994
|
ctx.logger.info(`获取结果:站点 ${siteId} 文章 ${postId} 更新记录:${result ? '找到' : '未找到'}`);
|
|
806
995
|
return result;
|
|
@@ -825,7 +1014,7 @@ function apply(ctx, config) {
|
|
|
825
1014
|
// 创建新记录,不手动指定id,让数据库自动生成
|
|
826
1015
|
const newRecord = {
|
|
827
1016
|
siteId,
|
|
828
|
-
userId,
|
|
1017
|
+
userId: parseInt(userId),
|
|
829
1018
|
pushedAt: new Date()
|
|
830
1019
|
};
|
|
831
1020
|
ctx.logger.info(`准备创建用户推送记录:${JSON.stringify(newRecord)}`);
|
|
@@ -859,13 +1048,13 @@ function apply(ctx, config) {
|
|
|
859
1048
|
if (record) {
|
|
860
1049
|
ctx.logger.info(`发现现有记录,站点 ID: ${siteId},文章 ID: ${postId},上次修改时间: ${record.lastModified}`);
|
|
861
1050
|
// Koishi database API 不支持 update 方法,使用 remove + create 代替
|
|
862
|
-
await ctx.database.remove('wordpress_post_updates', { siteId, postId });
|
|
1051
|
+
await ctx.database.remove('wordpress_post_updates', { siteId, postId: parseInt(postId) });
|
|
863
1052
|
ctx.logger.info(`已删除旧记录,站点 ID: ${siteId},文章 ID: ${postId}`);
|
|
864
1053
|
}
|
|
865
1054
|
// 创建新记录,不指定 id 字段,让数据库自动生成
|
|
866
1055
|
const newRecord = {
|
|
867
1056
|
siteId,
|
|
868
|
-
postId,
|
|
1057
|
+
postId: parseInt(postId),
|
|
869
1058
|
lastModified: modifiedDate,
|
|
870
1059
|
pushedAt: new Date()
|
|
871
1060
|
};
|
|
@@ -937,7 +1126,11 @@ function apply(ctx, config) {
|
|
|
937
1126
|
}
|
|
938
1127
|
// 根据配置格式化日期
|
|
939
1128
|
const formatDate = (dateString) => {
|
|
940
|
-
|
|
1129
|
+
// 使用统一的时间处理工具函数
|
|
1130
|
+
const date = parseWPDate(dateString);
|
|
1131
|
+
if (!date) {
|
|
1132
|
+
return '未知时间';
|
|
1133
|
+
}
|
|
941
1134
|
const year = date.getFullYear();
|
|
942
1135
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
943
1136
|
const day = String(date.getDate()).padStart(2, '0');
|
|
@@ -1049,10 +1242,9 @@ function apply(ctx, config) {
|
|
|
1049
1242
|
ctx.logger.info(`用户 ${username} 的原始数据: ${JSON.stringify(user)}`);
|
|
1050
1243
|
}
|
|
1051
1244
|
if (dateStr) {
|
|
1052
|
-
//
|
|
1053
|
-
const date =
|
|
1054
|
-
|
|
1055
|
-
if (!isNaN(date.getTime())) {
|
|
1245
|
+
// 使用统一的时间处理工具函数
|
|
1246
|
+
const date = parseWPDate(dateStr);
|
|
1247
|
+
if (date) {
|
|
1056
1248
|
const year = date.getFullYear();
|
|
1057
1249
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
1058
1250
|
const day = String(date.getDate()).padStart(2, '0');
|
|
@@ -1103,14 +1295,21 @@ function apply(ctx, config) {
|
|
|
1103
1295
|
ctx.logger.info(`开始处理站点: ${site.id} (${site.name})`);
|
|
1104
1296
|
// 推送新文章
|
|
1105
1297
|
if (site.enableAutoPush) {
|
|
1106
|
-
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;
|
|
1107
1306
|
ctx.logger.info(`站点 ${site.id} (${site.name}) 开始检查 ${posts.length} 篇文章是否需要推送`);
|
|
1108
1307
|
if (posts.length > 0) {
|
|
1109
1308
|
for (const post of posts) {
|
|
1110
1309
|
ctx.logger.info(`正在处理文章: ${post.id} - ${post.title.rendered}`);
|
|
1111
1310
|
ctx.logger.info(`文章 ID: ${post.id}, 发布时间: ${post.date}, 修改时间: ${post.modified}`);
|
|
1112
1311
|
// 检查文章是否已推送过(所有群聊共用一个标记)
|
|
1113
|
-
const postRecord = await getPostUpdateRecord(site.id, post.id);
|
|
1312
|
+
const postRecord = await getPostUpdateRecord(site.id, String(post.id));
|
|
1114
1313
|
const hasPushed = !!postRecord;
|
|
1115
1314
|
ctx.logger.info(`检查结果: 站点 ${site.id} 文章 ${post.id} 是否已推送:${hasPushed ? '是' : '否'}`);
|
|
1116
1315
|
if (!hasPushed) {
|
|
@@ -1121,6 +1320,13 @@ function apply(ctx, config) {
|
|
|
1121
1320
|
ctx.logger.info(`正在处理目标: ${target}`);
|
|
1122
1321
|
// 直接使用原始目标字符串,不进行数字转换,避免丢失平台前缀等信息
|
|
1123
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
|
+
}
|
|
1124
1330
|
const message = formatPostMessage(post, site.mentionAll, false, site);
|
|
1125
1331
|
ctx.logger.info(`准备推送新文章到目标: ${stringTarget}`);
|
|
1126
1332
|
await bot.sendMessage(stringTarget, message);
|
|
@@ -1140,7 +1346,7 @@ function apply(ctx, config) {
|
|
|
1140
1346
|
addToFailedQueue('post', post, failedTargets, site);
|
|
1141
1347
|
}
|
|
1142
1348
|
// 标记文章已推送(所有群聊共用一个标记)
|
|
1143
|
-
await updatePostUpdateRecord(site.id, post.id, new Date(post.modified));
|
|
1349
|
+
await updatePostUpdateRecord(site.id, String(post.id), new Date(post.modified));
|
|
1144
1350
|
ctx.logger.info(`已标记站点 ${site.id} 文章 ${post.id} 为已推送,所有群聊将不再推送此文章`);
|
|
1145
1351
|
}
|
|
1146
1352
|
else {
|
|
@@ -1151,10 +1357,17 @@ function apply(ctx, config) {
|
|
|
1151
1357
|
}
|
|
1152
1358
|
// 推送文章更新
|
|
1153
1359
|
if (site.enableUpdatePush) {
|
|
1154
|
-
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;
|
|
1155
1368
|
if (posts.length > 0) {
|
|
1156
1369
|
for (const post of posts) {
|
|
1157
|
-
const updateRecord = await getPostUpdateRecord(site.id, post.id);
|
|
1370
|
+
const updateRecord = await getPostUpdateRecord(site.id, String(post.id));
|
|
1158
1371
|
const postModifiedDate = new Date(post.modified);
|
|
1159
1372
|
// 检查文章是否有更新
|
|
1160
1373
|
if (updateRecord && postModifiedDate > new Date(updateRecord.lastModified)) {
|
|
@@ -1165,6 +1378,13 @@ function apply(ctx, config) {
|
|
|
1165
1378
|
try {
|
|
1166
1379
|
ctx.logger.info(`正在处理目标: ${target}`);
|
|
1167
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
|
+
}
|
|
1168
1388
|
const message = formatPostMessage(post, site.mentionAll, true, site);
|
|
1169
1389
|
ctx.logger.info(`准备推送文章更新到目标: ${stringTarget}`);
|
|
1170
1390
|
await bot.sendMessage(stringTarget, message);
|
|
@@ -1184,7 +1404,7 @@ function apply(ctx, config) {
|
|
|
1184
1404
|
addToFailedQueue('update', post, failedTargets, site);
|
|
1185
1405
|
}
|
|
1186
1406
|
// 更新文章更新记录(所有群聊共用一个标记)
|
|
1187
|
-
await updatePostUpdateRecord(site.id, post.id, postModifiedDate);
|
|
1407
|
+
await updatePostUpdateRecord(site.id, String(post.id), postModifiedDate);
|
|
1188
1408
|
ctx.logger.info(`已更新站点 ${site.id} 文章 ${post.id} 的推送记录,所有群聊将使用此更新时间作为新的推送基准`);
|
|
1189
1409
|
}
|
|
1190
1410
|
}
|
|
@@ -1192,16 +1412,30 @@ function apply(ctx, config) {
|
|
|
1192
1412
|
}
|
|
1193
1413
|
// 推送新用户注册
|
|
1194
1414
|
if (site.enableUserPush) {
|
|
1195
|
-
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;
|
|
1196
1423
|
if (users.length > 0) {
|
|
1197
1424
|
for (const user of users) {
|
|
1198
|
-
if (!(await isUserPushed(site.id, user.id))) {
|
|
1425
|
+
if (!(await isUserPushed(site.id, String(user.id)))) {
|
|
1199
1426
|
const failedTargets = [];
|
|
1200
1427
|
for (const target of site.targets) {
|
|
1201
1428
|
try {
|
|
1202
1429
|
ctx.logger.info(`正在处理目标: ${target}`);
|
|
1203
1430
|
// 直接使用原始目标字符串,与新文章推送逻辑保持一致
|
|
1204
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
|
+
}
|
|
1205
1439
|
const message = formatUserMessage(user, site.mentionAll, site);
|
|
1206
1440
|
ctx.logger.info(`准备推送新用户到目标: ${stringTarget}`);
|
|
1207
1441
|
await bot.sendMessage(stringTarget, message);
|
|
@@ -1221,7 +1455,7 @@ function apply(ctx, config) {
|
|
|
1221
1455
|
addToFailedQueue('user', user, failedTargets, site);
|
|
1222
1456
|
}
|
|
1223
1457
|
// 标记用户已推送
|
|
1224
|
-
await markUserAsPushed(site.id, user.id);
|
|
1458
|
+
await markUserAsPushed(site.id, String(user.id));
|
|
1225
1459
|
}
|
|
1226
1460
|
}
|
|
1227
1461
|
}
|
|
@@ -1238,7 +1472,12 @@ function apply(ctx, config) {
|
|
|
1238
1472
|
if (!targetSite) {
|
|
1239
1473
|
return `未找到站点 ID: ${siteId}`;
|
|
1240
1474
|
}
|
|
1241
|
-
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;
|
|
1242
1481
|
if (posts.length === 0) {
|
|
1243
1482
|
ctx.logger.info(`站点 ${targetSite.id} 没有找到文章`);
|
|
1244
1483
|
return `站点 ${targetSite.name} 暂无文章`;
|
|
@@ -1280,7 +1519,12 @@ function apply(ctx, config) {
|
|
|
1280
1519
|
if (!targetSite) {
|
|
1281
1520
|
return `未找到站点 ID: ${siteId}`;
|
|
1282
1521
|
}
|
|
1283
|
-
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;
|
|
1284
1528
|
if (posts.length === 0) {
|
|
1285
1529
|
return `站点 ${targetSite.name} 暂无文章`;
|
|
1286
1530
|
}
|
|
@@ -1381,7 +1625,7 @@ function apply(ctx, config) {
|
|
|
1381
1625
|
}
|
|
1382
1626
|
// 切换开关
|
|
1383
1627
|
site.enableUpdatePush = !site.enableUpdatePush;
|
|
1384
|
-
await
|
|
1628
|
+
await saveSiteConfig(siteId, site);
|
|
1385
1629
|
return `站点 ${site.name} 的文章更新推送已${site.enableUpdatePush ? '开启' : '关闭'}`;
|
|
1386
1630
|
});
|
|
1387
1631
|
ctx.command('wordpress.site.toggle-user <siteId>', '切换指定站点的新用户注册推送开关')
|
|
@@ -1399,7 +1643,7 @@ function apply(ctx, config) {
|
|
|
1399
1643
|
}
|
|
1400
1644
|
// 切换开关
|
|
1401
1645
|
site.enableUserPush = !site.enableUserPush;
|
|
1402
|
-
await
|
|
1646
|
+
await saveSiteConfig(siteId, site);
|
|
1403
1647
|
return `站点 ${site.name} 的新用户注册推送已${site.enableUserPush ? '开启' : '关闭'}`;
|
|
1404
1648
|
});
|
|
1405
1649
|
ctx.command('wordpress.site.toggle <siteId>', '切换指定站点的自动推送开关')
|
|
@@ -1417,7 +1661,7 @@ function apply(ctx, config) {
|
|
|
1417
1661
|
}
|
|
1418
1662
|
// 切换开关
|
|
1419
1663
|
site.enableAutoPush = !site.enableAutoPush;
|
|
1420
|
-
await
|
|
1664
|
+
await saveSiteConfig(siteId, site);
|
|
1421
1665
|
return `站点 ${site.name} 的自动推送已${site.enableAutoPush ? '开启' : '关闭'}`;
|
|
1422
1666
|
});
|
|
1423
1667
|
ctx.command('wordpress.site.mention <siteId>', '切换指定站点的 @全体成员 开关')
|
|
@@ -1435,7 +1679,7 @@ function apply(ctx, config) {
|
|
|
1435
1679
|
}
|
|
1436
1680
|
// 切换开关
|
|
1437
1681
|
site.mentionAll = !site.mentionAll;
|
|
1438
|
-
await
|
|
1682
|
+
await saveSiteConfig(siteId, site);
|
|
1439
1683
|
return `站点 ${site.name} 的 @全体成员 已${site.mentionAll ? '开启' : '关闭'}`;
|
|
1440
1684
|
});
|
|
1441
1685
|
ctx.command('wordpress.site.set-url <siteId> <url>', '修改指定站点的 WordPress 地址')
|
|
@@ -1453,7 +1697,7 @@ function apply(ctx, config) {
|
|
|
1453
1697
|
}
|
|
1454
1698
|
// 修改站点地址
|
|
1455
1699
|
site.url = url;
|
|
1456
|
-
await
|
|
1700
|
+
await saveSiteConfig(siteId, site);
|
|
1457
1701
|
ctx.logger.info(`站点 ${site.name} 的地址已修改为:${url}`);
|
|
1458
1702
|
return `站点 ${site.name} 的 WordPress 地址已修改为:${url}`;
|
|
1459
1703
|
});
|
|
@@ -1497,8 +1741,8 @@ function apply(ctx, config) {
|
|
|
1497
1741
|
ctx.logger.info(`命令 wordpress.clean 被调用,天数:${days || '默认'},调用者:${authResult.userId}`);
|
|
1498
1742
|
// 设置默认天数
|
|
1499
1743
|
const daysToKeep = days ? parseInt(days) : CONSTANTS.DEFAULT_CLEAN_DAYS;
|
|
1500
|
-
if (isNaN(daysToKeep) || daysToKeep <= 0) {
|
|
1501
|
-
return '
|
|
1744
|
+
if (isNaN(daysToKeep) || daysToKeep <= 0 || daysToKeep > 365) {
|
|
1745
|
+
return '请输入有效的天数(1-365天)';
|
|
1502
1746
|
}
|
|
1503
1747
|
// 计算清理时间点
|
|
1504
1748
|
const cutoffDate = new Date();
|
|
@@ -1507,46 +1751,84 @@ function apply(ctx, config) {
|
|
|
1507
1751
|
let result = 0;
|
|
1508
1752
|
// 批量删除 wordpress_post_updates 中的旧记录
|
|
1509
1753
|
try {
|
|
1510
|
-
//
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
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;
|
|
1767
|
+
}
|
|
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 })));
|
|
1523
1782
|
}
|
|
1524
|
-
result +=
|
|
1525
|
-
|
|
1783
|
+
result += recordsToRemove.length;
|
|
1784
|
+
processed += updateRecords.length;
|
|
1785
|
+
ctx.logger.info(`已处理 ${processed} 条记录,删除了 ${recordsToRemove.length} 条旧记录`);
|
|
1786
|
+
// 避免数据库压力过大,每批处理后稍作延迟
|
|
1787
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
1526
1788
|
}
|
|
1789
|
+
ctx.logger.info(`批量删除 wordpress_post_updates 旧记录完成,共删除 ${result} 条记录`);
|
|
1527
1790
|
}
|
|
1528
1791
|
catch (error) {
|
|
1529
1792
|
ctx.logger.error(`批量删除 wordpress_post_updates 旧记录失败: ${error}`);
|
|
1530
1793
|
}
|
|
1531
1794
|
// 批量删除 wordpress_user_registrations 中的旧记录
|
|
1532
1795
|
try {
|
|
1533
|
-
//
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
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;
|
|
1546
1809
|
}
|
|
1547
|
-
|
|
1548
|
-
|
|
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));
|
|
1549
1830
|
}
|
|
1831
|
+
ctx.logger.info(`批量删除 wordpress_user_registrations 旧记录完成,共删除 ${result} 条记录`);
|
|
1550
1832
|
}
|
|
1551
1833
|
catch (error) {
|
|
1552
1834
|
ctx.logger.error(`批量删除 wordpress_user_registrations 旧记录失败: ${error}`);
|
|
@@ -1659,9 +1941,10 @@ function apply(ctx, config) {
|
|
|
1659
1941
|
ctx.logger.info(`准备返回消息,长度: ${message.length}`);
|
|
1660
1942
|
return message;
|
|
1661
1943
|
});
|
|
1662
|
-
//
|
|
1944
|
+
// 为每个站点设置独立的定时任务,绑定到 ctx 生命周期
|
|
1945
|
+
const siteTimers = [];
|
|
1663
1946
|
config.sites.forEach(site => {
|
|
1664
|
-
ctx.setInterval(() => {
|
|
1947
|
+
const timer = ctx.setInterval(() => {
|
|
1665
1948
|
try {
|
|
1666
1949
|
pushNewPosts();
|
|
1667
1950
|
}
|
|
@@ -1672,5 +1955,28 @@ function apply(ctx, config) {
|
|
|
1672
1955
|
ctx.logger.warn('定时任务将在下一个周期继续执行');
|
|
1673
1956
|
}
|
|
1674
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 插件资源清理完成');
|
|
1675
1981
|
});
|
|
1676
1982
|
}
|