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 CHANGED
@@ -448,7 +448,7 @@ var WikiBotService = class _WikiBotService extends import_koishi2.Service {
448
448
  });
449
449
  })(WikiBotService || (WikiBotService = {}));
450
450
 
451
- // src/plugins/consoleLogProvider.ts
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
- MODLE_NAMESPACE: 828,
1058
- // 模块命名空间 (注意:这里原代码拼写为 MODLE,保留原样)
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.MODLE_NAMESPACE})`
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.MODLE_NAMESPACE,
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 (!ctx.wikiBot.isGGBotReady() || !ctx.wikiBot.isBWikiBotReady()) {
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 (!ctx.wikiBot.isGGBotReady() || !ctx.wikiBot.isBWikiBotReady()) {
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 (!ctx.wikiBot.isGGBotReady() || !ctx.wikiBot.isBWikiBotReady()) {
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
- checkBotsReady(ctx) {
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 bwReady = ctx.wikiBot.isBWikiBotReady();
1277
- return ggReady && bwReady;
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.checkBotsReady(ctx)) {
1282
- return session.send("❌ Wiki 机器人未就绪,请检查登录配置或查看日志");
1294
+ if (!await this.ensureBotsReady(ctx, "同步页面")) {
1295
+ return "❌ Wiki 机器人未就绪,请检查登录配置或查看日志";
1283
1296
  }
1284
- await syncSinglePage(
1285
- ctx.wikiBot.getGGBot(),
1286
- ctx.wikiBot.getBWikiBot(),
1287
- pageTitle,
1288
- "sync-bot"
1289
- ).then(() => {
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
- }).catch((err) => {
1294
- session.send(`❌ 同步页面失败:${pageTitle}`);
1295
- this.log.error(`,错误信息:${err}`);
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.checkBotsReady(ctx)) {
1302
- return session.send("❌ Wiki 机器人未就绪,请检查登录配置或查看日志");
1313
+ if (!await this.ensureBotsReady(ctx, "增量更新")) {
1314
+ return "❌ Wiki 机器人未就绪,请检查登录配置或查看日志";
1303
1315
  }
1304
- session.send(
1305
- `🚀 获取3h内的编辑并尝试更新,任务耗时可能较长,请前往控制台查看日志:${this.config.logsUrl}`
1306
- );
1307
- await incrementalUpdate(
1308
- ctx.wikiBot.getGGBot(),
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.log.error(`同步所有页面失败,错误信息:${err}`);
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.checkBotsReady(ctx)) {
1324
- return session.send("❌ Wiki 机器人未就绪,请检查登录配置或查看日志");
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.checkBotsReady(ctx)) {
1344
- return session.send("❌ Wiki 机器人未就绪,请检查登录配置或查看日志");
1343
+ if (!await this.ensureBotsReady(ctx, "同步模块")) {
1344
+ return "❌ Wiki 机器人未就绪,请检查登录配置或查看日志";
1345
1345
  }
1346
- await session.send(
1347
- `✅ 同步中,请前往控制台查看:${this.config.logsUrl}`
1348
- );
1349
- await syncSingleModule(
1350
- ctx.wikiBot.getGGBot(),
1351
- ctx.wikiBot.getBWikiBot(),
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
- }).catch((err) => {
1359
- session.send(`❌ 同步模块失败:${moduleTitle}`);
1360
- this.log.error(`错误信息:${err}`);
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.checkBotsReady(ctx)) {
1365
- return session.send("❌ Wiki 机器人未就绪,请检查登录配置或查看日志");
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.checkBotsReady(ctx)) {
1383
- return session.send("❌ Wiki 机器人未就绪,请检查登录配置或查看日志");
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.checkBotsReady(ctx)) {
1402
- return session.send("❌ Wiki 机器人未就绪,请检查登录配置或查看日志");
1389
+ if (!await this.ensureBotsReady(ctx, "同步所有图片")) {
1390
+ return "❌ Wiki 机器人未就绪,请检查登录配置或查看日志";
1403
1391
  }
1404
- session.send(
1405
- `🚀 开始同步所有图片,任务耗时较长,请前往控制台查看:${this.config.logsUrl}`
1406
- );
1407
- await syncAllImages(
1408
- ctx.wikiBot.getGGBot(),
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.log.error(`同步所有图片失败,错误信息:${err}`);
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.getUsageMessage();
1443
+ return this.renderUsageMessage(session);
1460
1444
  }
1461
1445
  if (SPECIAL_CASES.has(queryKey)) {
1462
- return SPECIAL_CASES.get(queryKey);
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.formatResultMessage(
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 `❌ 本地缓存为空,请联系管理员执行【update】指令更新缓存!`;
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 `❌ 未找到【${queryKey}】相关内容,请按游戏内标准名称重新查询!`;
1477
+ return this.sendResponse(
1478
+ session,
1479
+ `❌ 未找到【${queryKey}】相关内容,请按游戏内标准名称重新查询!`
1480
+ );
1489
1481
  }
1490
1482
  return await this.handleSelection(session, fuzzyMatches);
1491
1483
  });
1492
1484
  }
1493
- getUsageMessage() {
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
- formatResultMessage(match, prefix) {
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 += `原站点: https://${this.config.domain}/gg/${match.id}
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 = `🔍 未找到精准匹配,为你找到【 ${resultCount} 】个相似结果,请输入序号选择(10秒内有效):
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 session.send(replyMsg);
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 `❌ 输入无效!请输入 1-${resultCount} 之间的数字序号`;
1702
+ return this.sendResponse(
1703
+ session,
1704
+ `❌ 输入无效!请输入 1-${resultCount} 之间的数字序号`
1705
+ );
1585
1706
  }
1586
1707
  const selected = matches[selectNum - 1];
1587
- return this.formatResultMessage(
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 getUsageMessage;
11
- private formatResultMessage;
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, Logger, Schema } from "koishi";
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
- private checkBotsReady;
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-oni-sync-bot",
3
3
  "description": "缺氧Wiki站镜像点同步-测试",
4
- "version": "0.8.3",
4
+ "version": "0.8.6",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [
package/readme.md CHANGED
@@ -2,235 +2,311 @@
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/koishi-plugin-oni-sync-bot?style=flat-square)](https://www.npmjs.com/package/koishi-plugin-oni-sync-bot)
4
4
  [![Koishi](https://img.shields.io/badge/koishi-plugin-42b983?style=flat-square)](https://koishi.chat)
5
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
5
6
 
6
- 缺氧 Wiki 站镜像点同步插件 - 自动同步两个缺氧 Wiki 站点的内容,同时提供 TodoList 管理功能。
7
+ **缺氧 Wiki 镜像站同步机器人插件** 实现 [oxygennotincluded.wiki.gg](https://oxygennotincluded.wiki.gg) B站 Wiki 镜像的双向内容同步、拼音匹配页面查询、模块同步管理,以及页面短链接重定向服务。
7
8
 
8
- ## 功能特性
9
+ ## ✨ 核心功能
9
10
 
10
- ### 核心功能
11
+ ### 📄 Wiki 同步
11
12
 
12
- - 🚀 **自动登录** - 支持 WIKIGG bwiki 双站点登录
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
- ### TodoList 功能
21
+ ### 🔍 页面查询
21
22
 
22
- - **待办事项管理** - 增删改查待办事项
23
- - 📋 **控制台页面** - 可视化管理 TodoList
24
- - 🔄 **模块同步** - 自动同步 wiki.gg 模块到 TodoList
25
- - 📊 **同步状态检查** - 自动检查模块同步状态
26
- - 🕐 **定时任务** - 每周一同步模块,每周二检查状态
27
- - 🎨 **可爱风格** - 萌系风格的 UI 设计
28
- - 🔍 **搜索筛选** - 支持关键词搜索和状态筛选
29
- - 📄 **分页显示** - 支持分页和自定义每页条数
30
- - 📱 **移动端适配** - 响应式设计,支持手机访问
23
+ - **精准匹配** 标题/全拼/首字母精确匹配
24
+ - **拼音模糊匹配** 支持中文名称的拼音搜索
25
+ - **QQ Markdown 按钮** QQ群查询结果以可点击按钮形式呈现(点击即跳转页面)
26
+ - **命令别名** `x` / `查wiki` 快捷命令
31
27
 
32
- ## 安装
28
+ ### 📋 模块同步 TodoList
33
29
 
34
- ```bash
35
- npm install koishi-plugin-oni-sync-bot
36
- #
37
- yarn add koishi-plugin-oni-sync-bot
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
- ### TodoList 命令
48
+ ## 📦 项目结构
64
49
 
65
- | 命令 | 说明 | 权限要求 |
66
- | ---------------------- | ------------------------------------- | -------- |
67
- | `todo.list` | 查看 TodoList(重定向到网页) | - |
68
- | `todo.add <标题>` | 添加待办事项 | - |
69
- | `todo.edit <id>` | 编辑待办事项 | - |
70
- | `todo.complete <id>` | 标记待办事项为完成 | - |
71
- | `todo.uncomplete <id>` | 标记待办事项为未完成 | - |
72
- | `todo.delete <id>` | 删除待办事项 | 2 |
73
- | `todo.clear` | 清空 TodoList | 2 |
74
- | `todo.syncmodules` | 同步 wiki.gg 模块到 TodoList | - |
75
- | `todo.checksync` | 检查模块同步状态 | - |
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
- ## TodoList 命令示例
89
+ ### 子插件模式
84
90
 
85
- ### 添加待办
91
+ 主入口 `src/index.ts` 采用依赖顺序注册子插件:
86
92
 
87
93
  ```
88
- /todo.add 同步模块 -c "同步 Module:Data" -u "管理员"
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
- /todo.edit 1 -t "新标题" -c "新内容" -u "编辑者"
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
- /todo.syncmodules
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
- /todo.checksync
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
- ### TodoList 控制台
237
+ ### 🔍 查询页面
121
238
 
122
- - ✨ **可爱风格** - 萌系设计的 UI 界面
123
- - **新增待办** - 弹窗表单添加待办
124
- - ✏️ **编辑待办** - 修改待办内容和状态
125
- - ✅ **完成标记** - 一键标记完成/未完成
126
- - 🔍 **搜索筛选** - 支持按标题、内容搜索,按状态筛选
127
- - 📄 **分页显示** - 超过 8 条自动分页,支持自定义每页条数
128
- - 📊 **统计信息** - 显示总数、已完成、未完成数量
129
- - 📱 **移动端适配** - 响应式布局,完美支持手机访问
239
+ ```
240
+ > x 精炼器
130
241
 
131
- **注意:控制台界面无法删除待办事项,删除操作仅可通过命令完成(需要权限 2)。**
242
+ 🔍 为你找到【3】个相似结果,请点击下方按钮查看详情:
132
243
 
133
- ## 定时任务
244
+ 1. [精炼器]
245
+ 2. [蒸汽精炼器]
246
+ 3. [岩浆精炼器]
134
247
 
135
- 插件使用 `koishi-plugin-cron` 提供定时任务功能:
248
+ (QQ 平台显示可点击按钮,点击即跳转 Wiki 页面;非 QQ 平台返回纯文本)
249
+ ```
136
250
 
137
- - **每周一 00:00** - 自动同步 wiki.gg 所有模块到 TodoList
138
- - **每周二 00:00** - 自动检查模块同步状态,更新完成标记
251
+ ### 🔄 同步页面
139
252
 
140
- ## 技术特性
253
+ ```
254
+ > sync 精炼器
141
255
 
142
- - **TypeScript** - 完整的类型支持
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
- koishi-plugin-oni-sync-bot/
157
- ├── src/
158
- │ ├── services/ # 服务层
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
- ## TodoList 模块同步逻辑
267
+ ## 🎨 控制台界面
185
268
 
186
- ### 模块同步流程
269
+ 插件在 Koishi 控制台提供以下管理页面:
187
270
 
188
- 1. wiki.gg 获取命名空间 828(Module)的所有页面
189
- 2. 逐个获取每个模块的最新修订信息(用户、时间、注释)
190
- 3. 将模块信息存入 TodoList:
191
- - `title` = 模块名称
192
- - `content` = 修订注释
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
- 1. 获取所有 TodoList 中的待办事项
200
- 2. 对每个待办:
201
- - wiki.gg 获取最新修订时间
202
- - bwikia 获取最新修订时间
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
- ```bash
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
- yarn dev
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>。