koishi-plugin-oni-sync-bot 0.8.3 → 0.8.6
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.js +276 -154
- package/lib/plugins/queryCommands.d.ts +8 -2
- package/lib/plugins/syncCommands.d.ts +8 -3
- package/lib/services/consoleLogProvider.d.ts +13 -0
- package/package.json +1 -1
- package/readme.md +245 -169
package/lib/index.js
CHANGED
|
@@ -448,7 +448,7 @@ var WikiBotService = class _WikiBotService extends import_koishi2.Service {
|
|
|
448
448
|
});
|
|
449
449
|
})(WikiBotService || (WikiBotService = {}));
|
|
450
450
|
|
|
451
|
-
// src/
|
|
451
|
+
// src/services/consoleLogProvider.ts
|
|
452
452
|
var import_koishi3 = require("koishi");
|
|
453
453
|
var import_plugin_console = require("@koishijs/plugin-console");
|
|
454
454
|
var logBuffer = [];
|
|
@@ -537,6 +537,12 @@ var RouteRedirect = class {
|
|
|
537
537
|
)}`;
|
|
538
538
|
router.redirect(targetUrl);
|
|
539
539
|
});
|
|
540
|
+
ctx.server.get("/ggwiki/*", async (router) => {
|
|
541
|
+
const suffix = router.params[""] || "";
|
|
542
|
+
const queryString = router.querystring ? `?${router.querystring}` : "";
|
|
543
|
+
const targetUrl = `https://${this.config.main_site}/${suffix}${queryString}`;
|
|
544
|
+
router.redirect(targetUrl);
|
|
545
|
+
});
|
|
540
546
|
}
|
|
541
547
|
};
|
|
542
548
|
((RouteRedirect2) => {
|
|
@@ -1054,8 +1060,8 @@ __name(incrementalUpdate, "incrementalUpdate");
|
|
|
1054
1060
|
// src/sync/moduleSync.ts
|
|
1055
1061
|
var import_koishi7 = require("koishi");
|
|
1056
1062
|
var CONFIG3 = {
|
|
1057
|
-
|
|
1058
|
-
// 模块命名空间
|
|
1063
|
+
MODULE_NAMESPACE: 828,
|
|
1064
|
+
// 模块命名空间
|
|
1059
1065
|
IGNORED_MODULES: [],
|
|
1060
1066
|
// 忽略的模块列表
|
|
1061
1067
|
SYNC_INTERVAL_SUCCESS: 500,
|
|
@@ -1102,13 +1108,13 @@ async function syncSingleModule(oldSite, newSite, moduleTitle, user) {
|
|
|
1102
1108
|
__name(syncSingleModule, "syncSingleModule");
|
|
1103
1109
|
async function getAllModules(site) {
|
|
1104
1110
|
logger.info(
|
|
1105
|
-
`[SyncAllModules] 📥 开始获取原站点所有模块(命名空间${CONFIG3.
|
|
1111
|
+
`[SyncAllModules] 📥 开始获取原站点所有模块(命名空间${CONFIG3.MODULE_NAMESPACE})`
|
|
1106
1112
|
);
|
|
1107
1113
|
const allModules = [];
|
|
1108
1114
|
const queryGen = site.continuedQueryGen({
|
|
1109
1115
|
action: "query",
|
|
1110
1116
|
list: "allpages",
|
|
1111
|
-
apnamespace: CONFIG3.
|
|
1117
|
+
apnamespace: CONFIG3.MODULE_NAMESPACE,
|
|
1112
1118
|
// 模块命名空间
|
|
1113
1119
|
aplimit: "max",
|
|
1114
1120
|
apdir: "ascending"
|
|
@@ -1128,7 +1134,6 @@ async function syncModules(oldSite, newSite) {
|
|
|
1128
1134
|
try {
|
|
1129
1135
|
const oldModuleList = await getAllModules(oldSite);
|
|
1130
1136
|
const total = oldModuleList.length;
|
|
1131
|
-
console.log(oldModuleList);
|
|
1132
1137
|
if (total === 0) {
|
|
1133
1138
|
logger.info(`[SyncAllModules] 📭 原站点无模块可同步,结束`);
|
|
1134
1139
|
return;
|
|
@@ -1225,16 +1230,11 @@ var SyncCommands = class {
|
|
|
1225
1230
|
}
|
|
1226
1231
|
static inject = ["wikiBot", "cron"];
|
|
1227
1232
|
config;
|
|
1228
|
-
log;
|
|
1229
1233
|
constructor(ctx, config) {
|
|
1230
1234
|
this.config = config;
|
|
1231
|
-
this.log = ctx.logger("oni-sync");
|
|
1232
1235
|
logger.info("WikiBot 服务已就绪,初始化定时任务和指令");
|
|
1233
1236
|
ctx.cron("15 * * * *", async () => {
|
|
1234
|
-
if (!
|
|
1235
|
-
logger.warn("增量更新跳过:Wiki 机器人未就绪");
|
|
1236
|
-
return;
|
|
1237
|
-
}
|
|
1237
|
+
if (!await this.ensureBotsReady(ctx, "增量更新")) return;
|
|
1238
1238
|
await incrementalUpdate(
|
|
1239
1239
|
ctx.wikiBot.getGGBot(),
|
|
1240
1240
|
ctx.wikiBot.getBWikiBot(),
|
|
@@ -1242,22 +1242,15 @@ var SyncCommands = class {
|
|
|
1242
1242
|
);
|
|
1243
1243
|
});
|
|
1244
1244
|
ctx.cron("30 8 * * 4", async () => {
|
|
1245
|
-
if (!
|
|
1246
|
-
logger.warn("同步所有页面跳过:Wiki 机器人未就绪");
|
|
1247
|
-
return;
|
|
1248
|
-
}
|
|
1245
|
+
if (!await this.ensureBotsReady(ctx, "同步所有页面")) return;
|
|
1249
1246
|
await syncPages(ctx.wikiBot.getGGBot(), ctx.wikiBot.getBWikiBot()).then(() => {
|
|
1250
1247
|
logger.info("自动任务:尝试同步所有页面,从 WIKIGG 到 bwiki");
|
|
1251
1248
|
}).catch((err) => {
|
|
1252
|
-
logger.error(
|
|
1253
|
-
this.log.error(`,错误信息:${err}`);
|
|
1249
|
+
logger.error(`同步所有页面失败,错误信息:${err}`);
|
|
1254
1250
|
});
|
|
1255
1251
|
});
|
|
1256
1252
|
ctx.cron("30 8 * * 3", async () => {
|
|
1257
|
-
if (!
|
|
1258
|
-
logger.warn("同步所有图片跳过:Wiki 机器人未就绪");
|
|
1259
|
-
return;
|
|
1260
|
-
}
|
|
1253
|
+
if (!await this.ensureBotsReady(ctx, "同步所有图片")) return;
|
|
1261
1254
|
await syncAllImages(
|
|
1262
1255
|
ctx.wikiBot.getGGBot(),
|
|
1263
1256
|
ctx.wikiBot.getBWikiBot(),
|
|
@@ -1265,159 +1258,148 @@ var SyncCommands = class {
|
|
|
1265
1258
|
).then(() => {
|
|
1266
1259
|
logger.info("自动任务:尝试同步所有图片,从 WIKIGG 到 bwiki");
|
|
1267
1260
|
}).catch((err) => {
|
|
1268
|
-
logger.error(
|
|
1269
|
-
this.log.error(`,错误信息:${err}`);
|
|
1261
|
+
logger.error(`同步所有图片失败,错误信息:${err}`);
|
|
1270
1262
|
});
|
|
1271
1263
|
});
|
|
1272
1264
|
this.registerCommands(ctx);
|
|
1273
1265
|
}
|
|
1274
|
-
|
|
1266
|
+
/**
|
|
1267
|
+
* 确保机器人就绪,如果未就绪则尝试重新登录
|
|
1268
|
+
* @param ctx Koishi 上下文
|
|
1269
|
+
* @param taskName 任务名称(用于日志)
|
|
1270
|
+
* @returns 机器人是否已就绪
|
|
1271
|
+
*/
|
|
1272
|
+
async ensureBotsReady(ctx, taskName) {
|
|
1273
|
+
if (!ctx.wikiBot.isGGBotReady() || !ctx.wikiBot.isBWikiBotReady()) {
|
|
1274
|
+
logger.warn(`检测到部分机器人未就绪,尝试重新登录...`);
|
|
1275
|
+
try {
|
|
1276
|
+
await ctx.wikiBot.relogin();
|
|
1277
|
+
} catch (error) {
|
|
1278
|
+
logger.error(`重新登录失败: ${error}`);
|
|
1279
|
+
return false;
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1275
1282
|
const ggReady = ctx.wikiBot.isGGBotReady();
|
|
1276
|
-
const
|
|
1277
|
-
|
|
1283
|
+
const bwikiReady = ctx.wikiBot.isBWikiBotReady();
|
|
1284
|
+
if (!ggReady || !bwikiReady) {
|
|
1285
|
+
logger.warn(
|
|
1286
|
+
`${taskName} 跳过:Wiki 机器人仍未就绪 - WIKIGG: ${ggReady ? "✅" : "❌"}, bwiki: ${bwikiReady ? "✅" : "❌"}`
|
|
1287
|
+
);
|
|
1288
|
+
return false;
|
|
1289
|
+
}
|
|
1290
|
+
return true;
|
|
1278
1291
|
}
|
|
1279
1292
|
registerCommands(ctx) {
|
|
1280
1293
|
ctx.command("sync <pageTitle:string>", "同步指定页面", { authority: 2 }).action(async ({ session }, pageTitle) => {
|
|
1281
|
-
if (!this.
|
|
1282
|
-
return
|
|
1294
|
+
if (!await this.ensureBotsReady(ctx, "同步页面")) {
|
|
1295
|
+
return "❌ Wiki 机器人未就绪,请检查登录配置或查看日志";
|
|
1283
1296
|
}
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
session.send(
|
|
1291
|
-
`✅ 已尝试同步页面:${pageTitle},请前往控制台查看:${this.config.logsUrl}`
|
|
1297
|
+
try {
|
|
1298
|
+
await syncSinglePage(
|
|
1299
|
+
ctx.wikiBot.getGGBot(),
|
|
1300
|
+
ctx.wikiBot.getBWikiBot(),
|
|
1301
|
+
pageTitle,
|
|
1302
|
+
"sync-bot"
|
|
1292
1303
|
);
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1304
|
+
return `✅ 已尝试同步页面:${pageTitle},请前往控制台查看:${this.config.logsUrl}`;
|
|
1305
|
+
} catch (err) {
|
|
1306
|
+
logger.error(`同步页面 ${pageTitle} 失败,错误信息:${err}`);
|
|
1307
|
+
return `❌ 同步页面失败:${pageTitle}`;
|
|
1308
|
+
}
|
|
1297
1309
|
});
|
|
1298
1310
|
ctx.command("sync.incrementalUpdate", "获取3h内的编辑并尝试更新", {
|
|
1299
1311
|
authority: 2
|
|
1300
1312
|
}).alias("增量更新").action(async ({ session }) => {
|
|
1301
|
-
if (!this.
|
|
1302
|
-
return
|
|
1313
|
+
if (!await this.ensureBotsReady(ctx, "增量更新")) {
|
|
1314
|
+
return "❌ Wiki 机器人未就绪,请检查登录配置或查看日志";
|
|
1303
1315
|
}
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
ctx.wikiBot.getBWikiBot(),
|
|
1310
|
-
this.config
|
|
1311
|
-
).then(() => {
|
|
1312
|
-
session.send(
|
|
1313
|
-
`✅ 已尝试获取三小时前的编辑并同步,请前往控制台查看:${this.config.logsUrl}`
|
|
1314
|
-
);
|
|
1315
|
-
}).catch((err) => {
|
|
1316
|
-
session.send(
|
|
1317
|
-
`❌ 同步所有页面失败,请前往控制台查看日志:${this.config.logsUrl}`
|
|
1316
|
+
try {
|
|
1317
|
+
await incrementalUpdate(
|
|
1318
|
+
ctx.wikiBot.getGGBot(),
|
|
1319
|
+
ctx.wikiBot.getBWikiBot(),
|
|
1320
|
+
this.config
|
|
1318
1321
|
);
|
|
1319
|
-
this.
|
|
1320
|
-
})
|
|
1322
|
+
return `✅ 已尝试获取三小时前的编辑并同步,请前往控制台查看:${this.config.logsUrl}`;
|
|
1323
|
+
} catch (err) {
|
|
1324
|
+
logger.error(`增量更新失败,错误信息:${err}`);
|
|
1325
|
+
return `❌ 增量更新失败,请前往控制台查看日志:${this.config.logsUrl}`;
|
|
1326
|
+
}
|
|
1321
1327
|
});
|
|
1322
1328
|
ctx.command("sync.allpages", "同步所有页面", { authority: 2 }).action(async ({ session }) => {
|
|
1323
|
-
if (!this.
|
|
1324
|
-
return
|
|
1329
|
+
if (!await this.ensureBotsReady(ctx, "同步所有页面")) {
|
|
1330
|
+
return "❌ Wiki 机器人未就绪,请检查登录配置或查看日志";
|
|
1331
|
+
}
|
|
1332
|
+
try {
|
|
1333
|
+
await syncPages(ctx.wikiBot.getGGBot(), ctx.wikiBot.getBWikiBot());
|
|
1334
|
+
return `✅ 已尝试同步所有页面,请前往控制台查看:${this.config.logsUrl}`;
|
|
1335
|
+
} catch (err) {
|
|
1336
|
+
logger.error(`同步所有页面失败,错误信息:${err}`);
|
|
1337
|
+
return `❌ 同步所有页面失败,请前往控制台查看日志:${this.config.logsUrl}`;
|
|
1325
1338
|
}
|
|
1326
|
-
session.send(
|
|
1327
|
-
`🚀 开始同步所有页面,任务耗时较长,请前往控制台查看日志:${this.config.logsUrl}`
|
|
1328
|
-
);
|
|
1329
|
-
await syncPages(ctx.wikiBot.getGGBot(), ctx.wikiBot.getBWikiBot()).then(() => {
|
|
1330
|
-
session.send(
|
|
1331
|
-
`✅ 已尝试同步所有页面,请前往控制台查看:${this.config.logsUrl}`
|
|
1332
|
-
);
|
|
1333
|
-
}).catch((err) => {
|
|
1334
|
-
session.send(
|
|
1335
|
-
`❌ 同步所有页面失败,请前往控制台查看日志:${this.config.logsUrl}`
|
|
1336
|
-
);
|
|
1337
|
-
this.log.error(`同步所有页面失败,错误信息:${err}`);
|
|
1338
|
-
});
|
|
1339
1339
|
});
|
|
1340
1340
|
ctx.command("sync.module <moduleTitle:string>", "同步指定模块", {
|
|
1341
1341
|
authority: 2
|
|
1342
1342
|
}).action(async ({ session }, moduleTitle) => {
|
|
1343
|
-
if (!this.
|
|
1344
|
-
return
|
|
1343
|
+
if (!await this.ensureBotsReady(ctx, "同步模块")) {
|
|
1344
|
+
return "❌ Wiki 机器人未就绪,请检查登录配置或查看日志";
|
|
1345
1345
|
}
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
moduleTitle,
|
|
1353
|
-
"sync-bot"
|
|
1354
|
-
).then(() => {
|
|
1355
|
-
session.send(
|
|
1356
|
-
`✅ 已尝试同步模块:${moduleTitle},请前往控制台查看:${this.config.logsUrl}`
|
|
1346
|
+
try {
|
|
1347
|
+
await syncSingleModule(
|
|
1348
|
+
ctx.wikiBot.getGGBot(),
|
|
1349
|
+
ctx.wikiBot.getBWikiBot(),
|
|
1350
|
+
moduleTitle,
|
|
1351
|
+
"sync-bot"
|
|
1357
1352
|
);
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1353
|
+
return `✅ 已尝试同步模块:${moduleTitle},请前往控制台查看:${this.config.logsUrl}`;
|
|
1354
|
+
} catch (err) {
|
|
1355
|
+
logger.error(`同步模块 ${moduleTitle} 失败,错误信息:${err}`);
|
|
1356
|
+
return `❌ 同步模块失败:${moduleTitle}`;
|
|
1357
|
+
}
|
|
1362
1358
|
});
|
|
1363
1359
|
ctx.command("sync.allmodules", "同步所有模块", { authority: 2 }).action(async ({ session }) => {
|
|
1364
|
-
if (!this.
|
|
1365
|
-
return
|
|
1360
|
+
if (!await this.ensureBotsReady(ctx, "同步所有模块")) {
|
|
1361
|
+
return "❌ Wiki 机器人未就绪,请检查登录配置或查看日志";
|
|
1362
|
+
}
|
|
1363
|
+
try {
|
|
1364
|
+
await syncModules(ctx.wikiBot.getGGBot(), ctx.wikiBot.getBWikiBot());
|
|
1365
|
+
return `✅ 已尝试同步所有模块,请前往控制台查看:${this.config.logsUrl}`;
|
|
1366
|
+
} catch (err) {
|
|
1367
|
+
logger.error(`同步所有模块失败,错误信息:${err}`);
|
|
1368
|
+
return `❌ 同步所有模块失败,请前往控制台查看日志:${this.config.logsUrl}`;
|
|
1366
1369
|
}
|
|
1367
|
-
await session.send(
|
|
1368
|
-
`🚀 开始同步所有模块,任务耗时较长,请前往控制台查看:${this.config.logsUrl}`
|
|
1369
|
-
);
|
|
1370
|
-
await syncModules(ctx.wikiBot.getGGBot(), ctx.wikiBot.getBWikiBot()).then(() => {
|
|
1371
|
-
session.send(
|
|
1372
|
-
`✅ 已尝试同步所有模块,请前往控制台查看:${this.config.logsUrl}`
|
|
1373
|
-
);
|
|
1374
|
-
}).catch((err) => {
|
|
1375
|
-
session.send(
|
|
1376
|
-
`❌ 同步所有模块失败,请前往控制台查看日志:${this.config.logsUrl}`
|
|
1377
|
-
);
|
|
1378
|
-
this.log.error(`同步所有模块失败,错误信息:${err}`);
|
|
1379
|
-
});
|
|
1380
1370
|
});
|
|
1381
1371
|
ctx.command("sync.img <imgTitle:string>", "同步指定图片", { authority: 2 }).action(async ({ session }, imgTitle) => {
|
|
1382
|
-
if (!this.
|
|
1383
|
-
return
|
|
1372
|
+
if (!await this.ensureBotsReady(ctx, "同步图片")) {
|
|
1373
|
+
return "❌ Wiki 机器人未就绪,请检查登录配置或查看日志";
|
|
1374
|
+
}
|
|
1375
|
+
try {
|
|
1376
|
+
await syncSingleImage(
|
|
1377
|
+
ctx.wikiBot.getGGBot(),
|
|
1378
|
+
ctx.wikiBot.getBWikiBot(),
|
|
1379
|
+
`${imgTitle.startsWith("File:") ? "" : "File:"}${imgTitle}`,
|
|
1380
|
+
this.config
|
|
1381
|
+
);
|
|
1382
|
+
return `✅ 已尝试同步图片:${imgTitle}`;
|
|
1383
|
+
} catch (err) {
|
|
1384
|
+
logger.error(`同步图片 ${imgTitle} 失败,错误信息:${err}`);
|
|
1385
|
+
return `❌ 同步图片失败:${imgTitle}`;
|
|
1384
1386
|
}
|
|
1385
|
-
await session.send(
|
|
1386
|
-
`🚀 开始同步,任务可能耗时较长,请前往控制台查看:${this.config.logsUrl}`
|
|
1387
|
-
);
|
|
1388
|
-
await syncSingleImage(
|
|
1389
|
-
ctx.wikiBot.getGGBot(),
|
|
1390
|
-
ctx.wikiBot.getBWikiBot(),
|
|
1391
|
-
`${imgTitle.startsWith("File:") ? "" : "File:"}${imgTitle}`,
|
|
1392
|
-
this.config
|
|
1393
|
-
).then(() => {
|
|
1394
|
-
session.send(`✅ 已尝试同步图片:${imgTitle}`);
|
|
1395
|
-
}).catch((err) => {
|
|
1396
|
-
session.send(`❌ 同步图片失败:${imgTitle}`);
|
|
1397
|
-
this.log.error(`同步图片失败:${imgTitle},错误信息:${err}`);
|
|
1398
|
-
});
|
|
1399
1387
|
});
|
|
1400
1388
|
ctx.command("sync.allimgs", "同步所有图片", { authority: 2 }).action(async ({ session }) => {
|
|
1401
|
-
if (!this.
|
|
1402
|
-
return
|
|
1389
|
+
if (!await this.ensureBotsReady(ctx, "同步所有图片")) {
|
|
1390
|
+
return "❌ Wiki 机器人未就绪,请检查登录配置或查看日志";
|
|
1403
1391
|
}
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
ctx.wikiBot.getBWikiBot(),
|
|
1410
|
-
this.config
|
|
1411
|
-
).then(() => {
|
|
1412
|
-
session.send(
|
|
1413
|
-
`✅ 已尝试同步所有图片,请前往控制台查看:${this.config.logsUrl}`
|
|
1414
|
-
);
|
|
1415
|
-
}).catch((err) => {
|
|
1416
|
-
session.send(
|
|
1417
|
-
`❌ 同步所有图片失败,请前往控制台查看日志:${this.config.logsUrl}`
|
|
1392
|
+
try {
|
|
1393
|
+
await syncAllImages(
|
|
1394
|
+
ctx.wikiBot.getGGBot(),
|
|
1395
|
+
ctx.wikiBot.getBWikiBot(),
|
|
1396
|
+
this.config
|
|
1418
1397
|
);
|
|
1419
|
-
this.
|
|
1420
|
-
})
|
|
1398
|
+
return `✅ 已尝试同步所有图片,请前往控制台查看:${this.config.logsUrl}`;
|
|
1399
|
+
} catch (err) {
|
|
1400
|
+
logger.error(`同步所有图片失败,错误信息:${err}`);
|
|
1401
|
+
return `❌ 同步所有图片失败,请前往控制台查看日志:${this.config.logsUrl}`;
|
|
1402
|
+
}
|
|
1421
1403
|
});
|
|
1422
1404
|
}
|
|
1423
1405
|
};
|
|
@@ -1448,18 +1430,21 @@ var QueryCommands = class {
|
|
|
1448
1430
|
}
|
|
1449
1431
|
static inject = ["database"];
|
|
1450
1432
|
config;
|
|
1433
|
+
ctx;
|
|
1451
1434
|
constructor(ctx, config) {
|
|
1435
|
+
this.ctx = ctx;
|
|
1452
1436
|
this.config = config;
|
|
1453
1437
|
this.registerCommands(ctx);
|
|
1454
1438
|
}
|
|
1455
1439
|
registerCommands(ctx) {
|
|
1456
|
-
ctx.command("x <itemName>", "查询缺氧中文wiki,精准匹配+拼音模糊匹配").alias("/查wiki").action(async ({ session }, itemName = "") => {
|
|
1440
|
+
ctx.command("x <itemName:text>", "查询缺氧中文wiki,精准匹配+拼音模糊匹配").alias("/查wiki").action(async ({ session }, itemName = "") => {
|
|
1457
1441
|
const queryKey = itemName.trim().toLowerCase();
|
|
1458
1442
|
if (!queryKey) {
|
|
1459
|
-
return this.
|
|
1443
|
+
return this.renderUsageMessage(session);
|
|
1460
1444
|
}
|
|
1461
1445
|
if (SPECIAL_CASES.has(queryKey)) {
|
|
1462
|
-
|
|
1446
|
+
const content = SPECIAL_CASES.get(queryKey);
|
|
1447
|
+
return this.sendResponse(session, content);
|
|
1463
1448
|
}
|
|
1464
1449
|
const { pinyin_full: queryPinyinFull, pinyin_first: queryPinyinFirst } = generatePinyinInfo(queryKey);
|
|
1465
1450
|
const preciseMatch = await this.findPreciseMatch(
|
|
@@ -1469,14 +1454,18 @@ var QueryCommands = class {
|
|
|
1469
1454
|
queryPinyinFirst
|
|
1470
1455
|
);
|
|
1471
1456
|
if (preciseMatch) {
|
|
1472
|
-
return this.
|
|
1457
|
+
return this.renderResultMessage(
|
|
1458
|
+
session,
|
|
1473
1459
|
preciseMatch,
|
|
1474
1460
|
preciseMatch.prefix
|
|
1475
1461
|
);
|
|
1476
1462
|
}
|
|
1477
1463
|
const allPages = await ctx.database.get("wikipages", {});
|
|
1478
1464
|
if (allPages.length === 0) {
|
|
1479
|
-
return
|
|
1465
|
+
return this.sendResponse(
|
|
1466
|
+
session,
|
|
1467
|
+
"❌ 本地缓存为空,请联系管理员执行【update】指令更新缓存!"
|
|
1468
|
+
);
|
|
1480
1469
|
}
|
|
1481
1470
|
const fuzzyMatches = this.findFuzzyMatches(
|
|
1482
1471
|
allPages,
|
|
@@ -1485,23 +1474,139 @@ var QueryCommands = class {
|
|
|
1485
1474
|
queryPinyinFirst
|
|
1486
1475
|
);
|
|
1487
1476
|
if (fuzzyMatches.length === 0) {
|
|
1488
|
-
return
|
|
1477
|
+
return this.sendResponse(
|
|
1478
|
+
session,
|
|
1479
|
+
`❌ 未找到【${queryKey}】相关内容,请按游戏内标准名称重新查询!`
|
|
1480
|
+
);
|
|
1489
1481
|
}
|
|
1490
1482
|
return await this.handleSelection(session, fuzzyMatches);
|
|
1491
1483
|
});
|
|
1492
1484
|
}
|
|
1493
|
-
|
|
1485
|
+
isQQPlatform(session) {
|
|
1486
|
+
return session?.platform === "qq";
|
|
1487
|
+
}
|
|
1488
|
+
buildLinkButton(idSuffix, label, url) {
|
|
1489
|
+
return {
|
|
1490
|
+
id: `btn-${idSuffix}`,
|
|
1491
|
+
render_data: {
|
|
1492
|
+
label,
|
|
1493
|
+
visited_label: "已打开",
|
|
1494
|
+
style: 1
|
|
1495
|
+
},
|
|
1496
|
+
action: {
|
|
1497
|
+
type: 0,
|
|
1498
|
+
permission: {
|
|
1499
|
+
type: 2
|
|
1500
|
+
},
|
|
1501
|
+
data: url
|
|
1502
|
+
}
|
|
1503
|
+
};
|
|
1504
|
+
}
|
|
1505
|
+
buildQQKeyboard(matches) {
|
|
1506
|
+
const rows = [];
|
|
1507
|
+
for (let i = 0; i < matches.length; i += 2) {
|
|
1508
|
+
const buttonsInRow = matches.slice(i, i + 2).map((item) => {
|
|
1509
|
+
const label = item.title.length > 10 ? item.title.slice(0, 10) : item.title;
|
|
1510
|
+
return this.buildLinkButton(
|
|
1511
|
+
`${item.id}`,
|
|
1512
|
+
label,
|
|
1513
|
+
`https://${this.config.domain}/bw/${item.id}`
|
|
1514
|
+
);
|
|
1515
|
+
});
|
|
1516
|
+
rows.push({ buttons: buttonsInRow });
|
|
1517
|
+
}
|
|
1518
|
+
return {
|
|
1519
|
+
content: { rows }
|
|
1520
|
+
};
|
|
1521
|
+
}
|
|
1522
|
+
async sendResponse(session, content, keyboard) {
|
|
1523
|
+
if (this.isQQPlatform(session)) {
|
|
1524
|
+
try {
|
|
1525
|
+
const payload = {
|
|
1526
|
+
msg_id: session.messageId,
|
|
1527
|
+
msg_type: 2,
|
|
1528
|
+
markdown: {
|
|
1529
|
+
content
|
|
1530
|
+
}
|
|
1531
|
+
};
|
|
1532
|
+
if (keyboard) {
|
|
1533
|
+
payload.keyboard = keyboard;
|
|
1534
|
+
}
|
|
1535
|
+
await session.bot.internal.sendMessage(session.channelId, payload);
|
|
1536
|
+
return;
|
|
1537
|
+
} catch (e) {
|
|
1538
|
+
this.ctx.logger("oni-sync-bot").warn("QQ Markdown 发送失败,降级为纯文本:", e);
|
|
1539
|
+
return content;
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
return content;
|
|
1543
|
+
}
|
|
1544
|
+
renderUsageMessage(session) {
|
|
1545
|
+
if (this.isQQPlatform(session)) {
|
|
1546
|
+
const md = `**以下是使用说明:**
|
|
1547
|
+
> GG原站点: [https://${this.config.domain}/gg/88888888](https://${this.config.domain}/gg/88888888)
|
|
1548
|
+
|
|
1549
|
+
> bwiki: [https://${this.config.domain}/bw/88888888](https://${this.config.domain}/bw/88888888)
|
|
1550
|
+
`;
|
|
1551
|
+
return this.sendResponse(session, md);
|
|
1552
|
+
}
|
|
1494
1553
|
return `以下是使用说明:
|
|
1495
|
-
原站点: https://${this.config.domain}/gg/88888888
|
|
1554
|
+
GG原站点: https://${this.config.domain}/gg/88888888
|
|
1496
1555
|
|
|
1497
1556
|
bwiki: https://${this.config.domain}/bw/88888888`;
|
|
1498
1557
|
}
|
|
1499
|
-
|
|
1558
|
+
buildResultKeyboard(match) {
|
|
1559
|
+
return {
|
|
1560
|
+
content: {
|
|
1561
|
+
rows: [
|
|
1562
|
+
{
|
|
1563
|
+
buttons: [
|
|
1564
|
+
this.buildLinkButton(
|
|
1565
|
+
`gg-${match.id}`,
|
|
1566
|
+
"GG 原站",
|
|
1567
|
+
`https://${this.config.domain}/gg/${match.id}`
|
|
1568
|
+
)
|
|
1569
|
+
]
|
|
1570
|
+
},
|
|
1571
|
+
{
|
|
1572
|
+
buttons: [
|
|
1573
|
+
this.buildLinkButton(
|
|
1574
|
+
`bw-${match.id}`,
|
|
1575
|
+
"Bwiki 镜像",
|
|
1576
|
+
`https://${this.config.domain}/bw/${match.id}`
|
|
1577
|
+
)
|
|
1578
|
+
]
|
|
1579
|
+
},
|
|
1580
|
+
{
|
|
1581
|
+
buttons: [
|
|
1582
|
+
this.buildLinkButton(
|
|
1583
|
+
`wiki-${match.id}`,
|
|
1584
|
+
"GG直链",
|
|
1585
|
+
`https://oxygennotincluded.wiki.gg/zh/${encodeURIComponent(match.title)}`
|
|
1586
|
+
)
|
|
1587
|
+
]
|
|
1588
|
+
}
|
|
1589
|
+
]
|
|
1590
|
+
}
|
|
1591
|
+
};
|
|
1592
|
+
}
|
|
1593
|
+
renderResultMessage(session, match, prefix) {
|
|
1594
|
+
if (this.isQQPlatform(session)) {
|
|
1595
|
+
let md = "";
|
|
1596
|
+
if (prefix) {
|
|
1597
|
+
md += `**${prefix}**
|
|
1598
|
+
`;
|
|
1599
|
+
}
|
|
1600
|
+
md += `**📖 ${match.title}**
|
|
1601
|
+
`;
|
|
1602
|
+
const keyboard = this.buildResultKeyboard(match);
|
|
1603
|
+
return this.sendResponse(session, md, keyboard);
|
|
1604
|
+
}
|
|
1500
1605
|
let message = "";
|
|
1501
1606
|
if (prefix) {
|
|
1502
1607
|
message += prefix + "\n";
|
|
1503
1608
|
}
|
|
1504
|
-
message +=
|
|
1609
|
+
message += `GG原站点: https://${this.config.domain}/gg/${match.id}
|
|
1505
1610
|
|
|
1506
1611
|
bwiki: https://${this.config.domain}/bw/${match.id}`;
|
|
1507
1612
|
return message;
|
|
@@ -1568,7 +1673,20 @@ bwiki: https://${this.config.domain}/bw/${match.id}`;
|
|
|
1568
1673
|
}
|
|
1569
1674
|
async handleSelection(session, matches) {
|
|
1570
1675
|
const resultCount = matches.length;
|
|
1571
|
-
let replyMsg
|
|
1676
|
+
let replyMsg;
|
|
1677
|
+
if (this.isQQPlatform(session)) {
|
|
1678
|
+
let md = `**🔍 为你找到【 ${resultCount} 】个相似结果,请点击下方按钮查看详情:**
|
|
1679
|
+
`;
|
|
1680
|
+
matches.forEach((item, index) => {
|
|
1681
|
+
md += `
|
|
1682
|
+
> \`${index + 1}\`. **${item.title}**
|
|
1683
|
+
`;
|
|
1684
|
+
});
|
|
1685
|
+
const keyboard = this.buildQQKeyboard(matches);
|
|
1686
|
+
await this.sendResponse(session, md, keyboard);
|
|
1687
|
+
return;
|
|
1688
|
+
}
|
|
1689
|
+
replyMsg = `🔍 未找到精准匹配,为你找到【 ${resultCount} 】个相似结果,请输入序号选择(10秒内有效):
|
|
1572
1690
|
`;
|
|
1573
1691
|
matches.forEach((item, index) => {
|
|
1574
1692
|
replyMsg += `${index + 1}. ${item.title}
|
|
@@ -1576,15 +1694,19 @@ bwiki: https://${this.config.domain}/bw/${match.id}`;
|
|
|
1576
1694
|
});
|
|
1577
1695
|
replyMsg += `
|
|
1578
1696
|
❗️ 提示:超时将静默结束,无任何回应`;
|
|
1579
|
-
await
|
|
1697
|
+
await this.sendResponse(session, replyMsg);
|
|
1580
1698
|
const userInput = await session.prompt(15e3);
|
|
1581
1699
|
if (!userInput) return;
|
|
1582
1700
|
const selectNum = parseInt(userInput.trim(), 10);
|
|
1583
1701
|
if (isNaN(selectNum) || selectNum < 1 || selectNum > resultCount) {
|
|
1584
|
-
return
|
|
1702
|
+
return this.sendResponse(
|
|
1703
|
+
session,
|
|
1704
|
+
`❌ 输入无效!请输入 1-${resultCount} 之间的数字序号`
|
|
1705
|
+
);
|
|
1585
1706
|
}
|
|
1586
1707
|
const selected = matches[selectNum - 1];
|
|
1587
|
-
return this.
|
|
1708
|
+
return this.renderResultMessage(
|
|
1709
|
+
session,
|
|
1588
1710
|
selected,
|
|
1589
1711
|
"✅ 选择成功"
|
|
1590
1712
|
);
|
|
@@ -5,10 +5,16 @@ export interface QueryCommandsConfig {
|
|
|
5
5
|
export declare class QueryCommands {
|
|
6
6
|
static readonly inject: string[];
|
|
7
7
|
config: QueryCommandsConfig;
|
|
8
|
+
private ctx;
|
|
8
9
|
constructor(ctx: Context, config: QueryCommandsConfig);
|
|
9
10
|
private registerCommands;
|
|
10
|
-
private
|
|
11
|
-
private
|
|
11
|
+
private isQQPlatform;
|
|
12
|
+
private buildLinkButton;
|
|
13
|
+
private buildQQKeyboard;
|
|
14
|
+
private sendResponse;
|
|
15
|
+
private renderUsageMessage;
|
|
16
|
+
private buildResultKeyboard;
|
|
17
|
+
private renderResultMessage;
|
|
12
18
|
private findPreciseMatch;
|
|
13
19
|
private calculateScore;
|
|
14
20
|
private findFuzzyMatches;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Context,
|
|
1
|
+
import { Context, Schema } from "koishi";
|
|
2
2
|
export interface SyncCommandsConfig {
|
|
3
3
|
logsUrl: string;
|
|
4
4
|
ggUsername: string;
|
|
@@ -12,9 +12,14 @@ export interface SyncCommandsConfig {
|
|
|
12
12
|
export declare class SyncCommands {
|
|
13
13
|
static readonly inject: string[];
|
|
14
14
|
config: SyncCommandsConfig;
|
|
15
|
-
log: Logger;
|
|
16
15
|
constructor(ctx: Context, config: SyncCommandsConfig);
|
|
17
|
-
|
|
16
|
+
/**
|
|
17
|
+
* 确保机器人就绪,如果未就绪则尝试重新登录
|
|
18
|
+
* @param ctx Koishi 上下文
|
|
19
|
+
* @param taskName 任务名称(用于日志)
|
|
20
|
+
* @returns 机器人是否已就绪
|
|
21
|
+
*/
|
|
22
|
+
private ensureBotsReady;
|
|
18
23
|
private registerCommands;
|
|
19
24
|
}
|
|
20
25
|
export declare namespace SyncCommands {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Context, Logger } from "koishi";
|
|
2
|
+
declare module "@koishijs/console" {
|
|
3
|
+
namespace Console {
|
|
4
|
+
interface Services {
|
|
5
|
+
onilogs: DataService<Logger.Record[]>;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export declare class ConsoleLogProvider {
|
|
10
|
+
static readonly inject: string[];
|
|
11
|
+
constructor(ctx: Context);
|
|
12
|
+
}
|
|
13
|
+
export declare function apply(ctx: Context): void;
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -2,235 +2,311 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/koishi-plugin-oni-sync-bot)
|
|
4
4
|
[](https://koishi.chat)
|
|
5
|
+
[](LICENSE)
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
**缺氧 Wiki 镜像站同步机器人插件** — 实现 [oxygennotincluded.wiki.gg](https://oxygennotincluded.wiki.gg) 与 B站 Wiki 镜像的双向内容同步、拼音匹配页面查询、模块同步管理,以及页面短链接重定向服务。
|
|
7
8
|
|
|
8
|
-
##
|
|
9
|
+
## ✨ 核心功能
|
|
9
10
|
|
|
10
|
-
###
|
|
11
|
+
### 📄 Wiki 同步
|
|
11
12
|
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
13
|
+
- **双向登录** — 自动登录 wiki.gg 与 B站 Wiki(bwiki)两个站点
|
|
14
|
+
- **页面同步** — 单个/批量页面从 wiki.gg 同步到 bwiki
|
|
15
|
+
- **模块同步** — 批量同步 Module 命名空间页面
|
|
16
|
+
- **图片同步** — 同步页面引用的图片文件(支持单个/批量)
|
|
17
|
+
- **增量更新** — 每小时自动检测最近编辑并同步到镜像站
|
|
18
|
+
- **自动重连** — 登录过期自动检测并重新登录(3次重试机制)
|
|
19
|
+
- **登录重试** — 单次登录失败自动重试(最多3次,间隔5秒)
|
|
19
20
|
|
|
20
|
-
###
|
|
21
|
+
### 🔍 页面查询
|
|
21
22
|
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
- 🕐 **定时任务** - 每周一同步模块,每周二检查状态
|
|
27
|
-
- 🎨 **可爱风格** - 萌系风格的 UI 设计
|
|
28
|
-
- 🔍 **搜索筛选** - 支持关键词搜索和状态筛选
|
|
29
|
-
- 📄 **分页显示** - 支持分页和自定义每页条数
|
|
30
|
-
- 📱 **移动端适配** - 响应式设计,支持手机访问
|
|
23
|
+
- **精准匹配** — 标题/全拼/首字母精确匹配
|
|
24
|
+
- **拼音模糊匹配** — 支持中文名称的拼音搜索
|
|
25
|
+
- **QQ Markdown 按钮** — QQ群查询结果以可点击按钮形式呈现(点击即跳转页面)
|
|
26
|
+
- **命令别名** — `x` / `查wiki` 快捷命令
|
|
31
27
|
|
|
32
|
-
|
|
28
|
+
### 📋 模块同步 TodoList
|
|
33
29
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
## 配置
|
|
41
|
-
|
|
42
|
-
在 Koishi 控制台中配置以下参数:
|
|
30
|
+
- **自动同步** — 每周一 00:00 自动同步 wiki.gg 所有 Module 到待办
|
|
31
|
+
- **状态检查** — 每周二 00:00 自动检查模块同步状态(对比修订时间)
|
|
32
|
+
- **控制台管理** — 萌系风格可视化管理界面,支持搜索/筛选/分页
|
|
33
|
+
- **增删改查** — 完整的待办管理命令和控制台操作
|
|
34
|
+
- **权限控制** — 删除/清空操作需权限 2,删除缓存需权限 4
|
|
43
35
|
|
|
44
|
-
|
|
45
|
-
| --------------- | ------------- | ------ |
|
|
46
|
-
| `ggUsername` | WIKIGG 用户名 | 1 |
|
|
47
|
-
| `ggPassword` | WIKIGG 密码 | 1 |
|
|
48
|
-
| `bwikiusername` | bwiki 用户名 | 1 |
|
|
49
|
-
| `bwikipassword` | bwiki 密码 | 1 |
|
|
36
|
+
### � 路由重定向
|
|
50
37
|
|
|
51
|
-
|
|
38
|
+
- **短链接服务** — `/:id` → 跳转至完整 Wiki 页面 URL
|
|
39
|
+
- **GG 原站跳转** — `/gg/:id` → 跳转至 oxygennotincluded.wiki.gg 对应页面
|
|
40
|
+
- **Bwiki 镜像跳转** — `/bw/:id` → 跳转至 wiki.biligame.com/oni 对应页面
|
|
41
|
+
- **通配路径转发** — `/ggwiki/*` → 转发任意路径和查询参数到原站
|
|
42
|
+
- **持久化缓存** — 页面标题和页面ID映射保存在本地数据库,首次启动需执行 `update`
|
|
52
43
|
|
|
53
|
-
###
|
|
44
|
+
### 🎮 特殊指令
|
|
54
45
|
|
|
55
|
-
|
|
56
|
-
| -------------------- | ---------------------- |
|
|
57
|
-
| `sync.page <标题>` | 同步单个页面到 bwiki |
|
|
58
|
-
| `sync.gg <标题>` | 同步单个页面到 WIKIGG |
|
|
59
|
-
| `sync.module <前缀>` | 同步指定前缀的所有页面 |
|
|
60
|
-
| `sync.img <标题>` | 同步页面引用的图片 |
|
|
61
|
-
| `sync.update` | 执行增量更新 |
|
|
46
|
+
- **火箭计算器** — 查询 "火箭计算器" 返回计算器工具链接提示
|
|
62
47
|
|
|
63
|
-
|
|
48
|
+
## 📦 项目结构
|
|
64
49
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
50
|
+
```
|
|
51
|
+
oni-sync-bot/
|
|
52
|
+
├── src/
|
|
53
|
+
│ ├── index.ts # 插件入口(子插件注册表)
|
|
54
|
+
│ ├── services/
|
|
55
|
+
│ │ ├── wikiBotService.ts # Wiki 机器人核心服务(登录、重连、代理)
|
|
56
|
+
│ │ └── consoleLogProvider.ts # 控制台日志服务
|
|
57
|
+
│ ├── plugins/
|
|
58
|
+
│ │ ├── databaseExtension.ts # 数据库模型扩展(wikipages 表)
|
|
59
|
+
│ │ ├── queryCommands.ts # 页面查询命令(x / 查wiki)
|
|
60
|
+
│ │ ├── syncCommands.ts # Wiki 同步命令(sync.*)+ 定时任务
|
|
61
|
+
│ │ ├── updateCommands.ts # 缓存管理命令(update.* / redirect / relogin)
|
|
62
|
+
│ │ ├── routeRedirect.ts # 路由重定向服务(/gg / /bw / /ggwiki)
|
|
63
|
+
│ │ └── todoList.ts # TodoList 模块同步管理(命令 + 控制台 + 定时任务)
|
|
64
|
+
│ ├── sync/
|
|
65
|
+
│ │ ├── pageSync.ts # 页面同步逻辑(单页、全量、增量)
|
|
66
|
+
│ │ ├── moduleSync.ts # 模块同步逻辑
|
|
67
|
+
│ │ └── imgSync.ts # 图片同步逻辑
|
|
68
|
+
│ └── utils/
|
|
69
|
+
│ └── tools.ts # 工具函数(拼音生成、日志、错误处理)
|
|
70
|
+
├── client/
|
|
71
|
+
│ ├── Rocketcalculator/ # 火箭计算器前端页面
|
|
72
|
+
│ │ ├── calculator.ts
|
|
73
|
+
│ │ ├── calculator.vue
|
|
74
|
+
│ │ ├── config.ts
|
|
75
|
+
│ │ ├── index.vue
|
|
76
|
+
│ ├── onitodolist/ # TodoList 管理前端页面
|
|
77
|
+
│ │ ├── index.vue
|
|
78
|
+
│ │ └── todolist.vue
|
|
79
|
+
│ ├── index.ts
|
|
80
|
+
│ ├── page.vue # 日志查看页面
|
|
81
|
+
│ └── tsconfig.json
|
|
82
|
+
├── package.json
|
|
83
|
+
├── tsconfig.json
|
|
84
|
+
└── README.md
|
|
85
|
+
```
|
|
78
86
|
|
|
79
|
-
|
|
80
|
-
| --------- | ---------------------- |
|
|
81
|
-
| `relogin` | 手动重新登录两个机器人 |
|
|
87
|
+
## 🔧 架构设计
|
|
82
88
|
|
|
83
|
-
|
|
89
|
+
### 子插件模式
|
|
84
90
|
|
|
85
|
-
|
|
91
|
+
主入口 `src/index.ts` 采用依赖顺序注册子插件:
|
|
86
92
|
|
|
87
93
|
```
|
|
88
|
-
|
|
94
|
+
Service 层(数据库、WikiBot 服务)
|
|
95
|
+
↓
|
|
96
|
+
Core 层(同步逻辑、路由重定向)
|
|
97
|
+
↓
|
|
98
|
+
Command 层(查询命令、同步命令、更新命令、TodoList)
|
|
99
|
+
↓
|
|
100
|
+
UI 层(控制台日志、TodoList 可视化面板、火箭计算器)
|
|
89
101
|
```
|
|
90
102
|
|
|
91
|
-
###
|
|
103
|
+
### 数据库模型
|
|
104
|
+
|
|
105
|
+
| 表名 | 字段 | 说明 |
|
|
106
|
+
| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- |
|
|
107
|
+
| `wikipages` | `id` (integer), `title` (string), `pinyin_full` (string), `pinyin_first` (string) | Wiki 页面索引缓存,支持拼音检索 |
|
|
108
|
+
| `onitodos` | `id` (unsigned, auto), `title` (string), `content` (text), `completed` (boolean), `createdBy` (string), `createdAt` (timestamp), `updatedAt` (timestamp) | 模块同步待办事项 |
|
|
109
|
+
|
|
110
|
+
### 核心技术
|
|
111
|
+
|
|
112
|
+
| 技术 | 用途 |
|
|
113
|
+
| ---------------------------- | -------------------------------------------- |
|
|
114
|
+
| **TypeScript** | 完整类型支持,严格模式编译 |
|
|
115
|
+
| **Koishi 4** | 机器人框架,基于 Service 和 Plugin 架构 |
|
|
116
|
+
| **Mwn** | MediaWiki API 客户端(wiki.gg / bwiki 交互) |
|
|
117
|
+
| **pinyin-pro** | 中文转拼音,支持页面名称模糊匹配 |
|
|
118
|
+
| **koishi-plugin-cron** | 定时任务调度(每小时增量更新、每周同步等) |
|
|
119
|
+
| **@koishijs/plugin-console** | Koishi 控制台(提供可视化管理界面) |
|
|
120
|
+
| **@koishijs/plugin-server** | HTTP 服务器(提供短链接重定向路由) |
|
|
121
|
+
|
|
122
|
+
## ⚙️ 配置项
|
|
123
|
+
|
|
124
|
+
| 参数 | 说明 | 默认值 | 必需 |
|
|
125
|
+
| --------------- | ------------------- | ------------------------------ | ---- |
|
|
126
|
+
| `ggUsername` | WIKIGG 用户名 | `1` | ✅ |
|
|
127
|
+
| `ggPassword` | WIKIGG 密码 | `1` | ✅ |
|
|
128
|
+
| `bwikiusername` | Bwiki 用户名 | `1` | ✅ |
|
|
129
|
+
| `bwikipassword` | Bwiki 密码 | `1` | ✅ |
|
|
130
|
+
| `domain` | 你的短链域名 | `klei.vip` | ✅ |
|
|
131
|
+
| `main_site` | 主站域名(wiki.gg) | `oxygennotincluded.wiki.gg/zh` | ✅ |
|
|
132
|
+
| `bwiki_site` | 镜像站域名(bwiki) | `wiki.biligame.com/oni` | ✅ |
|
|
133
|
+
| `logsUrl` | 日志查看地址 | `https://klei.vip/onilogs` | - |
|
|
134
|
+
|
|
135
|
+
## 📚 命令列表
|
|
136
|
+
|
|
137
|
+
### 🔍 查询命令
|
|
138
|
+
|
|
139
|
+
| 命令 | 别名 | 说明 | 权限 |
|
|
140
|
+
| --------------- | -------- | ------------------------------ | ---- |
|
|
141
|
+
| `x <itemName>` | `查wiki` | 查询页面(标题/拼音/模糊匹配) | - |
|
|
142
|
+
| `查询 <关键词>` | - | 同上(备用命令名) | - |
|
|
143
|
+
|
|
144
|
+
### 🔄 同步命令
|
|
145
|
+
|
|
146
|
+
| 命令 | 别名 | 说明 | 权限 |
|
|
147
|
+
| ------------------------ | ---------- | -------------------- | ---- |
|
|
148
|
+
| `sync <标题>` | - | 同步单个页面到 bwiki | 2 |
|
|
149
|
+
| `sync.allpages` | - | 同步所有页面 | 2 |
|
|
150
|
+
| `sync.module <模块名>` | - | 同步单个模块 | 2 |
|
|
151
|
+
| `sync.allmodules` | - | 同步所有模块 | 2 |
|
|
152
|
+
| `sync.img <图片名>` | - | 同步单张图片 | 2 |
|
|
153
|
+
| `sync.allimgs` | - | 同步所有图片 | 2 |
|
|
154
|
+
| `sync.incrementalUpdate` | `增量更新` | 执行增量更新 | 2 |
|
|
155
|
+
|
|
156
|
+
### 📝 缓存管理
|
|
157
|
+
|
|
158
|
+
| 命令 | 别名 | 说明 | 权限 |
|
|
159
|
+
| ------------------------------ | ---------- | ----------------------------------- | ---- |
|
|
160
|
+
| `update` | - | 更新本地页面缓存(从 wiki.gg 获取) | 2 |
|
|
161
|
+
| `update.status` | - | 查询缓存数量和状态 | 1 |
|
|
162
|
+
| `update.delete` | - | 清空页面缓存 | 4 |
|
|
163
|
+
| `redirect <原页面> <目标页面>` | `重定向` | 在 wiki.gg 创建重定向页面 | 2 |
|
|
164
|
+
| `relogin` | `重新登录` | 手动重新登录两个 Wiki 机器人 | 2 |
|
|
165
|
+
|
|
166
|
+
### ✅ TodoList 命令
|
|
167
|
+
|
|
168
|
+
| 命令 | 别名 | 说明 | 权限 |
|
|
169
|
+
| ---------------------- | -------- | ---------------------------------------- | ---- |
|
|
170
|
+
| `todolist` | `todo` | 查看 TodoList(跳转网页) | - |
|
|
171
|
+
| `todo.list` | `todo.l` | 查看 TodoList(跳转网页) | - |
|
|
172
|
+
| `todo.add <标题>` | `todo.a` | 添加待办事项(支持 `-c` `-u` 选项) | - |
|
|
173
|
+
| `todo.edit <id>` | `todo.e` | 编辑待办事项(支持 `-t` `-c` `-u` 选项) | - |
|
|
174
|
+
| `todo.complete <id>` | `todo.c` | 标记待办为完成 | - |
|
|
175
|
+
| `todo.uncomplete <id>` | `todo.u` | 标记待办为未完成 | - |
|
|
176
|
+
| `todo.delete <id>` | `todo.d` | 删除待办事项 | 2 |
|
|
177
|
+
| `todo.clear` | - | 清空 TodoList | 2 |
|
|
178
|
+
| `todo.syncmodules` | - | 手动同步 wiki.gg 模块到 TodoList | - |
|
|
179
|
+
| `todo.checksync` | - | 手动检查模块同步状态 | - |
|
|
180
|
+
|
|
181
|
+
## ⏰ 定时任务
|
|
182
|
+
|
|
183
|
+
| 时间 | 任务 | 说明 |
|
|
184
|
+
| ---------------- | ------------------- | -------------------------------------- |
|
|
185
|
+
| **每小时 15 分** | 增量更新 | 获取最近编辑并同步到 bwiki |
|
|
186
|
+
| **每周四 08:30** | 同步所有页面 | 从 wiki.gg 全量同步页面到 bwiki |
|
|
187
|
+
| **每周三 08:30** | 同步所有图片 | 从 wiki.gg 全量同步图片资源 |
|
|
188
|
+
| **每周一 00:00** | 同步模块到 TodoList | 获取所有 Module 命名空间页面并写入待办 |
|
|
189
|
+
| **每周二 00:00** | 检查模块同步状态 | 对比修订时间,自动标记完成/未完成 |
|
|
190
|
+
|
|
191
|
+
## 🔌 路由接口
|
|
192
|
+
|
|
193
|
+
| 路径 | 方法 | 说明 |
|
|
194
|
+
| ----------- | ---- | --------------------------------------------------------- |
|
|
195
|
+
| `/gg/:id` | GET | 跳转至 oxygennotincluded.wiki.gg 对应页面(自动编码标题) |
|
|
196
|
+
| `/bw/:id` | GET | 跳转至 wiki.biligame.com/oni 对应页面 |
|
|
197
|
+
| `/ggwiki/*` | GET | 通配路径转发至 oxygennotincluded.wiki.gg,保留查询参数 |
|
|
198
|
+
|
|
199
|
+
> � **提示**:首次部署请先执行 `update` 命令填充 `wikipages` 表,否则路由跳转将返回"未找到页面"提示。
|
|
200
|
+
|
|
201
|
+
## 🚀 安装
|
|
92
202
|
|
|
93
|
-
```
|
|
94
|
-
|
|
203
|
+
```bash
|
|
204
|
+
npm install koishi-plugin-oni-sync-bot
|
|
205
|
+
# 或
|
|
206
|
+
yarn add koishi-plugin-oni-sync-bot
|
|
95
207
|
```
|
|
96
208
|
|
|
97
|
-
|
|
209
|
+
## 🔨 开发
|
|
98
210
|
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
|
|
211
|
+
```bash
|
|
212
|
+
# 克隆项目
|
|
213
|
+
git clone https://github.com/Charles-LF/oni-sync-bot.git
|
|
102
214
|
|
|
103
|
-
|
|
215
|
+
# 安装依赖
|
|
216
|
+
yarn install
|
|
104
217
|
|
|
105
|
-
|
|
106
|
-
|
|
218
|
+
# 构建
|
|
219
|
+
yarn build
|
|
220
|
+
|
|
221
|
+
# 开发模式
|
|
222
|
+
yarn dev
|
|
107
223
|
```
|
|
108
224
|
|
|
109
|
-
|
|
225
|
+
### 依赖服务
|
|
110
226
|
|
|
111
|
-
|
|
227
|
+
插件正常运行需以下 Koishi 核心插件(Koishi 默认已提供):
|
|
112
228
|
|
|
113
|
-
|
|
229
|
+
- **@koishijs/plugin-database** — 数据持久化(wikipages / onitodos)
|
|
230
|
+
- **@koishijs/plugin-server** — HTTP 服务器(短链接路由)
|
|
231
|
+
- **@koishijs/plugin-console** — 控制台 UI(TodoList 管理界面)
|
|
232
|
+
- **koishi-plugin-cron** — 定时任务调度
|
|
233
|
+
- **@koishijs/plugin-adapter-\*(可选)** — 如 QQ 适配器,以获得 Markdown 按钮效果
|
|
114
234
|
|
|
115
|
-
|
|
116
|
-
- 🔍 **搜索功能** - 快速搜索日志内容
|
|
117
|
-
- ⏬ **自动滚动** - 新日志自动滚动到底部
|
|
118
|
-
- 🎯 **一键滚动** - 手动跳转到顶部或底部
|
|
235
|
+
## 📊 使用示例
|
|
119
236
|
|
|
120
|
-
###
|
|
237
|
+
### 🔍 查询页面
|
|
121
238
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
- ✏️ **编辑待办** - 修改待办内容和状态
|
|
125
|
-
- ✅ **完成标记** - 一键标记完成/未完成
|
|
126
|
-
- 🔍 **搜索筛选** - 支持按标题、内容搜索,按状态筛选
|
|
127
|
-
- 📄 **分页显示** - 超过 8 条自动分页,支持自定义每页条数
|
|
128
|
-
- 📊 **统计信息** - 显示总数、已完成、未完成数量
|
|
129
|
-
- 📱 **移动端适配** - 响应式布局,完美支持手机访问
|
|
239
|
+
```
|
|
240
|
+
> x 精炼器
|
|
130
241
|
|
|
131
|
-
|
|
242
|
+
🔍 为你找到【3】个相似结果,请点击下方按钮查看详情:
|
|
132
243
|
|
|
133
|
-
|
|
244
|
+
1. [精炼器]
|
|
245
|
+
2. [蒸汽精炼器]
|
|
246
|
+
3. [岩浆精炼器]
|
|
134
247
|
|
|
135
|
-
|
|
248
|
+
(QQ 平台显示可点击按钮,点击即跳转 Wiki 页面;非 QQ 平台返回纯文本)
|
|
249
|
+
```
|
|
136
250
|
|
|
137
|
-
|
|
138
|
-
- **每周二 00:00** - 自动检查模块同步状态,更新完成标记
|
|
251
|
+
### 🔄 同步页面
|
|
139
252
|
|
|
140
|
-
|
|
253
|
+
```
|
|
254
|
+
> sync 精炼器
|
|
141
255
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
- 🔌 **服务化设计** - 核心功能封装为独立服务
|
|
145
|
-
- 🧩 **模块化** - 功能拆分为独立子插件
|
|
146
|
-
- 🔄 **自动重试** - 登录失败自动重试(最多 3 次)
|
|
147
|
-
- 🔐 **自动重连** - 登录过期自动重新登录
|
|
148
|
-
- 🛡️ **错误处理** - 完善的错误处理和日志记录
|
|
149
|
-
- ⏱️ **API 限流** - 模块同步时请求间隔 1 秒
|
|
150
|
-
- 🔄 **API 重试** - API 调用失败自动重试(最多 3 次)
|
|
151
|
-
- 📦 **数据库持久化** - TodoList 数据存储在数据库中
|
|
256
|
+
✅ 已尝试同步页面:精炼器,请前往控制台查看
|
|
257
|
+
```
|
|
152
258
|
|
|
153
|
-
|
|
259
|
+
### ✅ 标记待办完成
|
|
154
260
|
|
|
155
261
|
```
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
│ │ └── wikiBotService.ts # Wiki 机器人服务
|
|
160
|
-
│ ├── plugins/ # 插件层
|
|
161
|
-
│ │ ├── queryCommands.ts # 查询命令插件
|
|
162
|
-
│ │ ├── syncCommands.ts # 同步命令插件
|
|
163
|
-
│ │ ├── updateCommands.ts # 更新命令插件
|
|
164
|
-
│ │ ├── consoleLogProvider.ts # 日志控制台插件
|
|
165
|
-
│ │ ├── todoList.ts # TodoList 插件
|
|
166
|
-
│ │ ├── databaseExtension.ts # 数据库扩展
|
|
167
|
-
│ │ └── routeRedirect.ts # 路由重定向
|
|
168
|
-
│ ├── sync/ # 同步逻辑
|
|
169
|
-
│ │ ├── pageSync.ts # 页面同步
|
|
170
|
-
│ │ ├── moduleSync.ts # 模块同步
|
|
171
|
-
│ │ └── imgSync.ts # 图片同步
|
|
172
|
-
│ ├── utils/ # 工具函数
|
|
173
|
-
│ │ └── tools.ts # 通用工具
|
|
174
|
-
│ ├── client/ # 前端
|
|
175
|
-
│ │ ├── page.vue # 日志页面
|
|
176
|
-
│ │ ├── onitodolist/ # TodoList 页面
|
|
177
|
-
│ │ │ └── todolist.vue # TodoList 主组件
|
|
178
|
-
│ │ └── index.ts # 入口
|
|
179
|
-
│ └── index.ts # 主入口
|
|
180
|
-
├── package.json
|
|
181
|
-
└── tsconfig.json
|
|
262
|
+
> todo.complete 5
|
|
263
|
+
|
|
264
|
+
✅ 已标记完成,请访问 https://klei.vip/onitodos 查看
|
|
182
265
|
```
|
|
183
266
|
|
|
184
|
-
##
|
|
267
|
+
## 🎨 控制台界面
|
|
185
268
|
|
|
186
|
-
|
|
269
|
+
插件在 Koishi 控制台提供以下管理页面:
|
|
187
270
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
- `createdBy` = 修订用户
|
|
194
|
-
- `createdAt` / `updatedAt` = 修订时间
|
|
195
|
-
- `completed` = false(默认未完成)
|
|
271
|
+
| 页面 | 功能 |
|
|
272
|
+
| ------------------------------------ | ---------------------------------------------------- |
|
|
273
|
+
| **日志控制台** (`page.vue`) | 按级别筛选日志,搜索内容,自动滚动,快速跳转 |
|
|
274
|
+
| **TodoList 管理** (`onitodolist/`) | 萌系 UI 设计,新增/编辑/标记完成,搜索筛选,分页显示 |
|
|
275
|
+
| **火箭计算器** (`Rocketcalculator/`) | ONI 火箭设计计算器 |
|
|
196
276
|
|
|
197
|
-
|
|
277
|
+
## 🔒 安全与权限
|
|
198
278
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
- 对比时间:
|
|
204
|
-
- 如果 bwikia 时间 ≥ wiki.gg 时间 → 标记为已完成
|
|
205
|
-
- 如果 bwikia 时间 < wiki.gg 时间 → 标记为未完成
|
|
279
|
+
- 所有 Wiki 编辑操作(同步、创建重定向)需权限 **2**
|
|
280
|
+
- 删除页面缓存需权限 **4**
|
|
281
|
+
- 登录凭证(用户名/密码)仅保存在 Koishi 配置中,不会写入日志
|
|
282
|
+
- WikiBotService 通过 **Proxy** 模式透明处理登录过期,自动重新登录后继续执行原请求
|
|
206
283
|
|
|
207
|
-
##
|
|
284
|
+
## 🐛 故障排查
|
|
208
285
|
|
|
209
|
-
|
|
210
|
-
# 创建 KOISHI 项目
|
|
211
|
-
yarn create koishi
|
|
286
|
+
### "❌ 本地缓存为空"
|
|
212
287
|
|
|
213
|
-
|
|
214
|
-
git clone https://github.com/Charles-LF/oni-sync-bot.git
|
|
288
|
+
请管理员执行 `update` 命令从 wiki.gg 获取页面列表。
|
|
215
289
|
|
|
216
|
-
|
|
217
|
-
yarn install
|
|
290
|
+
### "❌ Wiki 机器人未就绪"
|
|
218
291
|
|
|
219
|
-
|
|
220
|
-
yarn build
|
|
292
|
+
检查配置中的账号密码是否正确;可执行 `relogin` 手动重新登录。
|
|
221
293
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
294
|
+
### "❌ 未找到ID为【...】的页面"
|
|
295
|
+
|
|
296
|
+
页面ID不在本地缓存中,可能是最近新增的页面。执行 `update` 更新缓存后再试。
|
|
297
|
+
|
|
298
|
+
### QQ 机器人只显示纯文本
|
|
299
|
+
|
|
300
|
+
QQ 平台的 Markdown 与按钮效果需要 `session.platform === "qq"` 检测生效。请确认适配器正确上报平台信息。
|
|
225
301
|
|
|
226
|
-
## License
|
|
302
|
+
## 📜 License
|
|
227
303
|
|
|
228
304
|
MIT License
|
|
229
305
|
|
|
230
|
-
## 贡献
|
|
306
|
+
## 🤝 贡献
|
|
231
307
|
|
|
232
308
|
欢迎提交 Issue 和 Pull Request!
|
|
233
309
|
|
|
234
|
-
## 支持
|
|
310
|
+
## 📧 支持
|
|
235
311
|
|
|
236
|
-
如有问题,请在 GitHub 上提交 Issue
|
|
312
|
+
如有问题,请在 GitHub 上提交 Issue,或访问 <https://klei.vip>。
|