koishi-plugin-oni-sync-bot 0.6.2 → 0.7.1

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
@@ -32,15 +32,14 @@ var src_exports = {};
32
32
  __export(src_exports, {
33
33
  Config: () => Config,
34
34
  apply: () => apply,
35
- inject: () => inject,
36
35
  name: () => name
37
36
  });
38
37
  module.exports = __toCommonJS(src_exports);
39
- var import_koishi5 = require("koishi");
38
+ var import_koishi11 = require("koishi");
40
39
  var import_path = require("path");
41
- var import_plugin_console = require("@koishijs/plugin-console");
42
40
 
43
- // src/utils/login.ts
41
+ // src/services/wikiBotService.ts
42
+ var import_koishi2 = require("koishi");
44
43
  var import_mwn = require("mwn");
45
44
 
46
45
  // src/utils/tools.ts
@@ -88,63 +87,383 @@ function generatePinyinInfo(text) {
88
87
  };
89
88
  }
90
89
  __name(generatePinyinInfo, "generatePinyinInfo");
90
+ function getErrorMessage(error) {
91
+ if (error instanceof Error) {
92
+ return error.message;
93
+ }
94
+ return String(error);
95
+ }
96
+ __name(getErrorMessage, "getErrorMessage");
91
97
  var logger = new import_koishi.Logger("oni-sync");
92
98
 
93
- // src/utils/login.ts
94
- async function login(siteConfig) {
95
- const bot = new import_mwn.Mwn({
96
- apiUrl: siteConfig.api,
97
- username: siteConfig.username,
98
- password: siteConfig.password,
99
- userAgent: siteConfig.userAgent,
100
- defaultParams: {
101
- assert: "user"
99
+ // src/services/wikiBotService.ts
100
+ var WikiBotService = class _WikiBotService extends import_koishi2.Service {
101
+ static {
102
+ __name(this, "WikiBotService");
103
+ }
104
+ static inject = [];
105
+ ggbot = null;
106
+ bwikibot = null;
107
+ isReady = false;
108
+ botConfig;
109
+ static USER_AGENT = `OniSyncBot/1.0 (https://klei.vip; Charles@klei.vip)`;
110
+ static MAX_RETRIES = 3;
111
+ static RETRY_DELAY = 5e3;
112
+ constructor(ctx, config) {
113
+ super(ctx, "wikiBot", true);
114
+ this.botConfig = config;
115
+ }
116
+ getSitesConfig() {
117
+ return {
118
+ gg: {
119
+ name: "WIKIGG",
120
+ api: "https://oxygennotincluded.wiki.gg/zh/api.php",
121
+ username: this.botConfig.ggUsername,
122
+ password: this.botConfig.ggPassword,
123
+ userAgent: _WikiBotService.USER_AGENT
124
+ },
125
+ bwiki: {
126
+ name: "bwiki",
127
+ api: "https://wiki.biligame.com/oni/api.php",
128
+ username: this.botConfig.bwikiusername,
129
+ password: this.botConfig.bwikipassword,
130
+ userAgent: _WikiBotService.USER_AGENT
131
+ }
132
+ };
133
+ }
134
+ delay(ms) {
135
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
136
+ }
137
+ async loginWithRetry(siteConfig, attempt = 1) {
138
+ try {
139
+ logger.info(
140
+ `正在登录 ${siteConfig.name}... (尝试 ${attempt}/${_WikiBotService.MAX_RETRIES})`
141
+ );
142
+ const bot = await this.login(siteConfig);
143
+ return bot;
144
+ } catch (error) {
145
+ const errorMsg = getErrorMessage(error);
146
+ if (attempt < _WikiBotService.MAX_RETRIES) {
147
+ logger.warn(
148
+ `登录 ${siteConfig.name} 失败,${_WikiBotService.RETRY_DELAY / 1e3}秒后重试...`,
149
+ errorMsg
150
+ );
151
+ await this.delay(_WikiBotService.RETRY_DELAY);
152
+ return this.loginWithRetry(siteConfig, attempt + 1);
153
+ }
154
+ logger.error(
155
+ `登录 ${siteConfig.name} 失败,已达到最大重试次数`,
156
+ errorMsg
157
+ );
158
+ throw error;
102
159
  }
103
- });
104
- if (siteConfig.name === "bwiki") {
105
- const cookieString = "SESSDATA=666; Domain=wiki.biligame.com; Path=/oni; HttpOnly; Secure;";
106
- bot.cookieJar.setCookie(cookieString, bot.options.apiUrl, (err) => {
107
- if (err) console.error("Cookie 注入失败:", err);
160
+ }
161
+ async login(siteConfig) {
162
+ logger.info(`正在连接 ${siteConfig.name} API: ${siteConfig.api}`);
163
+ const bot = new import_mwn.Mwn({
164
+ apiUrl: siteConfig.api,
165
+ username: siteConfig.username,
166
+ password: siteConfig.password,
167
+ userAgent: siteConfig.userAgent,
168
+ defaultParams: {
169
+ assert: "user"
170
+ },
171
+ maxRetries: 0,
172
+ retryPause: 0
108
173
  });
109
- bot.setRequestOptions({
110
- headers: {
111
- referer: "https://wiki.biligame.com/oni/"
174
+ if (siteConfig.name === "bwiki") {
175
+ const cookieString = "SESSDATA=666; Domain=wiki.biligame.com; Path=/oni; HttpOnly; Secure;";
176
+ bot.cookieJar.setCookie(cookieString, bot.options.apiUrl, (err) => {
177
+ if (err) {
178
+ logger.warn("Cookie 注入失败:", err);
179
+ } else {
180
+ logger.info("Cookie 注入成功");
181
+ }
182
+ });
183
+ bot.setRequestOptions({
184
+ headers: {
185
+ referer: "https://wiki.biligame.com/oni/",
186
+ "User-Agent": siteConfig.userAgent
187
+ }
188
+ });
189
+ }
190
+ logger.info(`正在执行 ${siteConfig.name} 登录...`);
191
+ await bot.login();
192
+ logger.info(`✅ 成功登录 ${siteConfig.name}`);
193
+ return bot;
194
+ }
195
+ async start() {
196
+ logger.info("WikiBotService 正在初始化...");
197
+ try {
198
+ const sitesConfig = this.getSitesConfig();
199
+ logger.info("开始登录 WIKIGG...");
200
+ try {
201
+ this.ggbot = await this.loginWithRetry(sitesConfig.gg);
202
+ } catch (error) {
203
+ const errorMsg = getErrorMessage(error);
204
+ logger.error(
205
+ "WIKIGG 登录失败,服务将继续运行,但 WIKIGG 相关功能不可用",
206
+ errorMsg
207
+ );
208
+ }
209
+ logger.info("开始登录 bwiki...");
210
+ try {
211
+ this.bwikibot = await this.loginWithRetry(sitesConfig.bwiki);
212
+ } catch (error) {
213
+ const errorMsg = getErrorMessage(error);
214
+ logger.error(
215
+ "bwiki 登录失败,服务将继续运行,但 bwiki 相关功能不可用",
216
+ errorMsg
217
+ );
218
+ }
219
+ if (this.ggbot && this.bwikibot) {
220
+ this.isReady = true;
221
+ logger.info("WikiBotService 初始化成功,两个 Wiki 已登录");
222
+ } else if (this.ggbot || this.bwikibot) {
223
+ this.isReady = true;
224
+ logger.warn(
225
+ `WikiBotService 部分初始化成功,已登录: ${this.ggbot ? "WIKIGG" : ""} ${this.bwikibot ? "bwiki" : ""}`
226
+ );
227
+ } else {
228
+ logger.error("WikiBotService 初始化失败,所有登录都失败");
229
+ }
230
+ } catch (error) {
231
+ const errorMsg = getErrorMessage(error);
232
+ logger.error("WikiBotService 初始化出错:", errorMsg);
233
+ }
234
+ }
235
+ stop() {
236
+ this.isReady = false;
237
+ this.ggbot = null;
238
+ this.bwikibot = null;
239
+ logger.info("WikiBotService 已停止");
240
+ }
241
+ async relogin() {
242
+ const sitesConfig = this.getSitesConfig();
243
+ let ggSuccess = false;
244
+ let bwikiSuccess = false;
245
+ logger.info("开始重新登录 WIKIGG...");
246
+ try {
247
+ this.ggbot = await this.loginWithRetry(sitesConfig.gg);
248
+ ggSuccess = true;
249
+ logger.info("✅ 成功重新登录 WIKIGG");
250
+ } catch (error) {
251
+ this.ggbot = null;
252
+ const errorMsg = getErrorMessage(error);
253
+ logger.error("❌ 重新登录 WIKIGG 失败", errorMsg);
254
+ }
255
+ logger.info("开始重新登录 bwiki...");
256
+ try {
257
+ this.bwikibot = await this.loginWithRetry(sitesConfig.bwiki);
258
+ bwikiSuccess = true;
259
+ logger.info("✅ 成功重新登录 bwiki");
260
+ } catch (error) {
261
+ this.bwikibot = null;
262
+ const errorMsg = getErrorMessage(error);
263
+ logger.error("❌ 重新登录 bwiki 失败", errorMsg);
264
+ }
265
+ if (this.ggbot && this.bwikibot) {
266
+ this.isReady = true;
267
+ logger.info("WikiBotService 重新登录成功,两个 Wiki 已登录");
268
+ } else if (this.ggbot || this.bwikibot) {
269
+ this.isReady = true;
270
+ logger.warn(
271
+ `WikiBotService 部分重新登录成功,已登录: ${this.ggbot ? "WIKIGG" : ""} ${this.bwikibot ? "bwiki" : ""}`
272
+ );
273
+ } else {
274
+ this.isReady = false;
275
+ logger.error("WikiBotService 重新登录失败,所有登录都失败");
276
+ }
277
+ return { gg: ggSuccess, bwiki: bwikiSuccess };
278
+ }
279
+ async reloginSite(site) {
280
+ const sitesConfig = this.getSitesConfig();
281
+ const siteConfig = site === "gg" ? sitesConfig.gg : sitesConfig.bwiki;
282
+ try {
283
+ const bot = await this.loginWithRetry(siteConfig);
284
+ if (site === "gg") {
285
+ this.ggbot = bot;
286
+ } else {
287
+ this.bwikibot = bot;
288
+ }
289
+ logger.info(`✅ ${siteConfig.name} 自动重新登录成功`);
290
+ } catch (error) {
291
+ const errorMsg = getErrorMessage(error);
292
+ if (site === "gg") {
293
+ this.ggbot = null;
294
+ } else {
295
+ this.bwikibot = null;
296
+ }
297
+ logger.error(`❌ ${siteConfig.name} 自动重新登录失败`, errorMsg);
298
+ throw error;
299
+ }
300
+ }
301
+ isGGBotReady() {
302
+ return this.ggbot !== null;
303
+ }
304
+ isBWikiBotReady() {
305
+ return this.bwikibot !== null;
306
+ }
307
+ createBotProxy(bot, site) {
308
+ const self = this;
309
+ return new Proxy(bot, {
310
+ get(target, prop) {
311
+ const originalMethod = target[prop];
312
+ if (typeof originalMethod !== "function") {
313
+ return originalMethod;
314
+ }
315
+ return async function(...args) {
316
+ try {
317
+ return await originalMethod.apply(target, args);
318
+ } catch (error) {
319
+ if (error.code === "assertuserfailed") {
320
+ logger.warn(
321
+ `检测到 ${site === "gg" ? "WIKIGG" : "bwiki"} 登录过期,正在自动重新登录...`
322
+ );
323
+ await self.reloginSite(site);
324
+ const newBot = site === "gg" ? self.ggbot : self.bwikibot;
325
+ if (!newBot) {
326
+ throw new Error(
327
+ `${site === "gg" ? "WIKIGG" : "bwiki"} 自动重新登录失败`
328
+ );
329
+ }
330
+ return await newBot[prop].apply(newBot, args);
331
+ }
332
+ throw error;
333
+ }
334
+ };
112
335
  }
113
336
  });
114
337
  }
115
- await bot.login();
116
- logger.info(`✅ 成功登录 ${siteConfig.name}`);
117
- return bot;
118
- }
119
- __name(login, "login");
120
-
121
- // src/config/index.ts
122
- var userAgent = `OniSyncBot/1.0 (https://klei.vip; Charles@klei.vip)`;
123
- function getSitesConfig(config) {
124
- return {
125
- gg: {
126
- name: "WIKIGG",
127
- api: "https://oxygennotincluded.wiki.gg/zh/api.php",
128
- username: config.ggUsername,
129
- password: config.ggPassword,
130
- userAgent
131
- },
132
- bwiki: {
133
- name: "bwiki",
134
- api: "https://wiki.biligame.com/oni/api.php",
135
- username: config.bwikiusername,
136
- password: config.bwikipassword,
137
- userAgent
338
+ getGGBot() {
339
+ if (!this.ggbot) {
340
+ throw new Error("WIKIGG bot 尚未就绪,请检查登录配置或查看日志");
138
341
  }
139
- };
140
- }
141
- __name(getSitesConfig, "getSitesConfig");
342
+ return this.createBotProxy(this.ggbot, "gg");
343
+ }
344
+ getBWikiBot() {
345
+ if (!this.bwikibot) {
346
+ throw new Error("bwiki bot 尚未就绪,请检查登录配置或查看日志");
347
+ }
348
+ return this.createBotProxy(this.bwikibot, "bwiki");
349
+ }
350
+ };
351
+ ((WikiBotService2) => {
352
+ WikiBotService2.Config = import_koishi2.Schema.object({
353
+ ggUsername: import_koishi2.Schema.string().description("WIKIGG 用户名").default("1"),
354
+ ggPassword: import_koishi2.Schema.string().description("WIKIGG 密码").default("1"),
355
+ bwikiusername: import_koishi2.Schema.string().description("bwiki用户名").default("1"),
356
+ bwikipassword: import_koishi2.Schema.string().description("bwiki密码").default("1")
357
+ });
358
+ })(WikiBotService || (WikiBotService = {}));
142
359
 
143
- // src/sync/pageSync.ts
360
+ // src/plugins/consoleLogProvider.ts
144
361
  var import_koishi3 = require("koishi");
362
+ var import_plugin_console = require("@koishijs/plugin-console");
363
+ var logBuffer = [];
364
+ var PublicLogProvider = class extends import_plugin_console.DataService {
365
+ static {
366
+ __name(this, "PublicLogProvider");
367
+ }
368
+ constructor(ctx) {
369
+ super(ctx, "onilogs", { authority: 0 });
370
+ }
371
+ async get() {
372
+ return logBuffer;
373
+ }
374
+ };
375
+ var ConsoleLogProvider = class {
376
+ static {
377
+ __name(this, "ConsoleLogProvider");
378
+ }
379
+ static inject = ["console"];
380
+ constructor(ctx) {
381
+ ctx.plugin(PublicLogProvider);
382
+ const target = {
383
+ colors: 0,
384
+ record: /* @__PURE__ */ __name((record) => {
385
+ if (record.name !== "oni-sync") return;
386
+ logBuffer.push(record);
387
+ if (logBuffer.length > 1e3) {
388
+ logBuffer = logBuffer.slice(-1e3);
389
+ }
390
+ ctx.get("console")?.patch("onilogs", logBuffer);
391
+ }, "record")
392
+ };
393
+ import_koishi3.Logger.targets.push(target);
394
+ ctx.on("dispose", () => {
395
+ const index = import_koishi3.Logger.targets.indexOf(target);
396
+ if (index > -1) import_koishi3.Logger.targets.splice(index, 1);
397
+ });
398
+ }
399
+ };
400
+
401
+ // src/plugins/databaseExtension.ts
402
+ var DatabaseExtension = class {
403
+ static {
404
+ __name(this, "DatabaseExtension");
405
+ }
406
+ static inject = ["database"];
407
+ constructor(ctx) {
408
+ ctx.model.extend("wikipages", {
409
+ id: "integer",
410
+ title: "string",
411
+ pinyin_full: "string",
412
+ pinyin_first: "string"
413
+ });
414
+ }
415
+ };
416
+
417
+ // src/plugins/routeRedirect.ts
418
+ var import_koishi4 = require("koishi");
419
+ var RouteRedirect = class {
420
+ static {
421
+ __name(this, "RouteRedirect");
422
+ }
423
+ static inject = ["database", "server"];
424
+ config;
425
+ constructor(ctx, config) {
426
+ this.config = config;
427
+ ctx.server.get("/gg/:id", async (router) => {
428
+ const pageId = Number(router.params.id);
429
+ if (isNaN(pageId)) return router.body = "❌ 无效的页面ID,必须为数字!";
430
+ const [page] = await ctx.database.get("wikipages", { id: pageId });
431
+ if (!page)
432
+ return router.body = `❌ 未找到ID为【${pageId}】的页面,请联系管理员更新缓存!`;
433
+ const targetUrl = `https://${this.config.main_site}/${encodeURIComponent(
434
+ page.title
435
+ )}?variant=zh`;
436
+ router.redirect(targetUrl);
437
+ });
438
+ ctx.server.get("/bw/:id", async (router) => {
439
+ const pageId = Number(router.params.id);
440
+ if (isNaN(pageId)) return router.body = "❌ 无效的页面ID,必须为数字!";
441
+ const [page] = await ctx.database.get("wikipages", { id: pageId });
442
+ if (!page)
443
+ return router.body = `❌ 未找到ID为【${pageId}】的页面,请联系管理员更新缓存!`;
444
+ const targetUrl = `https://${this.config.bwiki_site}/${encodeURIComponent(
445
+ page.title
446
+ )}`;
447
+ router.redirect(targetUrl);
448
+ });
449
+ }
450
+ };
451
+ ((RouteRedirect2) => {
452
+ RouteRedirect2.Config = import_koishi4.Schema.object({
453
+ domain: import_koishi4.Schema.string().description("你的短链域名(必填,如:klei.vip)").default("klei.vip"),
454
+ main_site: import_koishi4.Schema.string().description("主站域名(必填,如:oxygennotincluded.wiki.gg)").default("oxygennotincluded.wiki.gg/zh"),
455
+ bwiki_site: import_koishi4.Schema.string().description("镜像站域名(必填,如:wiki.biligame.com)").default("wiki.biligame.com/oni")
456
+ });
457
+ })(RouteRedirect || (RouteRedirect = {}));
458
+
459
+ // src/plugins/syncCommands.ts
460
+ var import_koishi8 = require("koishi");
461
+
462
+ // src/sync/pageSync.ts
463
+ var import_koishi6 = require("koishi");
145
464
 
146
465
  // src/sync/imgSync.ts
147
- var import_koishi2 = require("koishi");
466
+ var import_koishi5 = require("koishi");
148
467
  var import_node_fetch = __toESM(require("node-fetch"));
149
468
  var import_form_data = __toESM(require("form-data"));
150
469
  var CONFIG = {
@@ -174,11 +493,56 @@ async function getImageInfo(site, fileName) {
174
493
  size: imageInfo.size
175
494
  };
176
495
  } catch (error) {
177
- logger.error(`[GetImageInfo] 获取 ${fileName} 信息失败:`, error);
496
+ const errorMsg = getErrorMessage(error);
497
+ logger.error(`[GetImageInfo] 获取 ${fileName} 信息失败:`, errorMsg);
178
498
  return null;
179
499
  }
180
500
  }
181
501
  __name(getImageInfo, "getImageInfo");
502
+ async function deleteOldVersions(bot, fileName) {
503
+ try {
504
+ const response = await bot.query({
505
+ action: "query",
506
+ titles: fileName,
507
+ prop: "revisions",
508
+ rvprop: "ids|timestamp",
509
+ rvlimit: "max",
510
+ rvdir: "newer"
511
+ // 按时间正序排列,最新的在最后
512
+ });
513
+ const pages = response.query?.pages || {};
514
+ const page = Object.values(pages)[0];
515
+ if (!page || !page.revisions || page.revisions.length <= 1) {
516
+ return;
517
+ }
518
+ const revisionIds = page.revisions.slice(0, -1).map((rev) => rev.revid);
519
+ const token = await bot.getCsrfToken();
520
+ for (const revid of revisionIds) {
521
+ try {
522
+ await bot.rawRequest({
523
+ method: "POST",
524
+ url: bot.options.apiUrl,
525
+ data: {
526
+ action: "delete",
527
+ title: fileName,
528
+ revid,
529
+ token,
530
+ reason: "自动清理旧版本,只保留最新同步版本",
531
+ format: "json"
532
+ }
533
+ });
534
+ logger.info(`[SyncImg] 🗑️ 删除旧版本 ${revid} 成功`);
535
+ } catch (error) {
536
+ const errorMsg = getErrorMessage(error);
537
+ logger.error(`[SyncImg] ❌ 删除旧版本 ${revid} 失败:`, errorMsg);
538
+ }
539
+ }
540
+ } catch (error) {
541
+ const errorMsg = getErrorMessage(error);
542
+ logger.error(`[SyncImg] ❌ 获取版本信息失败:`, errorMsg);
543
+ }
544
+ }
545
+ __name(deleteOldVersions, "deleteOldVersions");
182
546
  async function syncSingleImage(sourceBot, targetBot, fileName, config) {
183
547
  if (CONFIG.IGNORED_IMAGES.includes(fileName)) {
184
548
  logger.info(`[SyncImg] 🚫 图片 ${fileName} 在忽略列表,跳过`);
@@ -193,7 +557,7 @@ async function syncSingleImage(sourceBot, targetBot, fileName, config) {
193
557
  }
194
558
  const targetImageInfo = await getImageInfo(targetBot, fileName);
195
559
  logger.info(`原图片sha1: ${sourceImageInfo.sha1}`);
196
- logger.info(`目标图片sha1: ${targetImageInfo.sha1}`);
560
+ logger.info(`目标图片sha1: ${targetImageInfo?.sha1}`);
197
561
  if (targetImageInfo && targetImageInfo.sha1 === sourceImageInfo.sha1) {
198
562
  logger.info(`[SyncImg] 🟡 图片 ${fileName} 已存在且内容一致,跳过`);
199
563
  return { success: true, reason: "no_change" };
@@ -236,6 +600,7 @@ async function syncSingleImage(sourceBot, targetBot, fileName, config) {
236
600
  const responseData = rawResponse.data;
237
601
  if (responseData.upload && responseData.upload.result === "Success") {
238
602
  logger.info(`[SyncImg] ✅ 图片 ${fileName} 同步成功`);
603
+ await deleteOldVersions(targetBot, fileName);
239
604
  return { success: true, reason: "synced" };
240
605
  } else if (responseData.error) {
241
606
  throw new Error(`${responseData.error.code}: ${responseData.error.info}`);
@@ -303,13 +668,13 @@ async function syncAllImages(sourceBot, targetBot, config) {
303
668
  if (!result.success) {
304
669
  failCount++;
305
670
  failedImages.push(fileName);
306
- await (0, import_koishi2.sleep)(CONFIG.SYNC_INTERVAL_FAILED);
671
+ await (0, import_koishi5.sleep)(CONFIG.SYNC_INTERVAL_FAILED);
307
672
  } else {
308
673
  successCount++;
309
674
  if (result.reason === "ignored" || result.reason === "no_change") {
310
675
  skipCount++;
311
676
  }
312
- await (0, import_koishi2.sleep)(CONFIG.SYNC_INTERVAL_SUCCESS);
677
+ await (0, import_koishi5.sleep)(CONFIG.SYNC_INTERVAL_SUCCESS);
313
678
  }
314
679
  }
315
680
  if (failedImages.length > 0) {
@@ -335,7 +700,7 @@ async function syncAllImages(sourceBot, targetBot, config) {
335
700
  stillFailed.push(fileName);
336
701
  logger.info(`[SyncAllImg] ❌ 重试失败: ${fileName}`);
337
702
  }
338
- await (0, import_koishi2.sleep)(CONFIG.SYNC_INTERVAL_SUCCESS);
703
+ await (0, import_koishi5.sleep)(CONFIG.SYNC_INTERVAL_SUCCESS);
339
704
  }
340
705
  if (stillFailed.length > 0) {
341
706
  logger.info(`
@@ -422,13 +787,13 @@ async function processPageWithStats(oldSite, newSite, pageTitle, user, stats, fa
422
787
  if (!syncResult.success) {
423
788
  stats.failCount++;
424
789
  failedPages.push(pageTitle);
425
- await (0, import_koishi3.sleep)(CONFIG2.SYNC_INTERVAL_FAILED);
790
+ await (0, import_koishi6.sleep)(CONFIG2.SYNC_INTERVAL_FAILED);
426
791
  } else {
427
792
  stats.successCount++;
428
793
  if (syncResult.reason === "ignored" || syncResult.reason === "no_change") {
429
794
  stats.skipCount++;
430
795
  }
431
- await (0, import_koishi3.sleep)(CONFIG2.SYNC_INTERVAL_SUCCESS);
796
+ await (0, import_koishi6.sleep)(CONFIG2.SYNC_INTERVAL_SUCCESS);
432
797
  }
433
798
  }
434
799
  __name(processPageWithStats, "processPageWithStats");
@@ -504,11 +869,11 @@ async function syncPages(oldSite, newSite) {
504
869
  stats.skipCount++;
505
870
  }
506
871
  logger.info(`[SyncAllPages] ✅ 页面 ${pageTitle} 重试成功`);
507
- await (0, import_koishi3.sleep)(CONFIG2.SYNC_INTERVAL_SUCCESS);
872
+ await (0, import_koishi6.sleep)(CONFIG2.SYNC_INTERVAL_SUCCESS);
508
873
  } else {
509
874
  stillFailed.push(pageTitle);
510
875
  logger.info(`[SyncAllPages] ❌ 页面 ${pageTitle} 再次失败`);
511
- await (0, import_koishi3.sleep)(CONFIG2.SYNC_INTERVAL_FAILED);
876
+ await (0, import_koishi6.sleep)(CONFIG2.SYNC_INTERVAL_FAILED);
512
877
  }
513
878
  }
514
879
  }
@@ -577,11 +942,11 @@ async function incrementalUpdate(oldSite, newSite, config) {
577
942
  CONFIG2.INCREMENTAL_USER
578
943
  );
579
944
  }
580
- await (0, import_koishi3.sleep)(CONFIG2.SYNC_INTERVAL_SUCCESS);
945
+ await (0, import_koishi6.sleep)(CONFIG2.SYNC_INTERVAL_SUCCESS);
581
946
  } catch (error) {
582
947
  const errMsg = error instanceof Error ? error.message : String(error);
583
948
  logger.error(`[增量更新流程] ❌ 处理 ${title} 时出错:`, errMsg);
584
- await (0, import_koishi3.sleep)(CONFIG2.SYNC_INTERVAL_FAILED);
949
+ await (0, import_koishi6.sleep)(CONFIG2.SYNC_INTERVAL_FAILED);
585
950
  }
586
951
  }
587
952
  }
@@ -596,7 +961,7 @@ async function incrementalUpdate(oldSite, newSite, config) {
596
961
  __name(incrementalUpdate, "incrementalUpdate");
597
962
 
598
963
  // src/sync/moduleSync.ts
599
- var import_koishi4 = require("koishi");
964
+ var import_koishi7 = require("koishi");
600
965
  var CONFIG3 = {
601
966
  MODLE_NAMESPACE: 828,
602
967
  // 模块命名空间 (注意:这里原代码拼写为 MODLE,保留原样)
@@ -700,13 +1065,13 @@ async function syncModules(oldSite, newSite) {
700
1065
  if (!syncResult.success) {
701
1066
  failCount++;
702
1067
  failedModules.push(moduleTitle);
703
- await (0, import_koishi4.sleep)(CONFIG3.SYNC_INTERVAL_FAILED);
1068
+ await (0, import_koishi7.sleep)(CONFIG3.SYNC_INTERVAL_FAILED);
704
1069
  } else {
705
1070
  successCount++;
706
1071
  if (syncResult.reason === "ignored" || syncResult.reason === "no_change") {
707
1072
  skipCount++;
708
1073
  }
709
- await (0, import_koishi4.sleep)(CONFIG3.SYNC_INTERVAL_SUCCESS);
1074
+ await (0, import_koishi7.sleep)(CONFIG3.SYNC_INTERVAL_SUCCESS);
710
1075
  }
711
1076
  }
712
1077
  if (failedModules.length > 0) {
@@ -731,11 +1096,11 @@ async function syncModules(oldSite, newSite) {
731
1096
  skipCount++;
732
1097
  }
733
1098
  logger.info(`[SyncAllModules] ✅ 模块 ${moduleTitle} 重试成功`);
734
- await (0, import_koishi4.sleep)(CONFIG3.SYNC_INTERVAL_SUCCESS);
1099
+ await (0, import_koishi7.sleep)(CONFIG3.SYNC_INTERVAL_SUCCESS);
735
1100
  } else {
736
1101
  stillFailed.push(moduleTitle);
737
1102
  logger.info(`[SyncAllModules] ❌ 模块 ${moduleTitle} 再次失败`);
738
- await (0, import_koishi4.sleep)(CONFIG3.SYNC_INTERVAL_FAILED);
1103
+ await (0, import_koishi7.sleep)(CONFIG3.SYNC_INTERVAL_FAILED);
739
1104
  }
740
1105
  }
741
1106
  logger.info(`
@@ -755,303 +1120,366 @@ async function syncModules(oldSite, newSite) {
755
1120
  logger.info(`├─ 成功:${successCount} 个(含跳过 ${skipCount} 个)`);
756
1121
  logger.info(`└─ 失败:${failCount} 个`);
757
1122
  } catch (error) {
758
- logger.error(`[SyncAllModules] 💥 批量同步流程异常终止:`, error);
1123
+ const errorMsg = getErrorMessage(error);
1124
+ logger.error(`[SyncAllModules] 💥 批量同步流程异常终止:`, errorMsg);
759
1125
  throw error;
760
1126
  }
761
1127
  }
762
1128
  __name(syncModules, "syncModules");
763
1129
 
764
- // src/index.ts
765
- var name = "oni-sync-bot";
766
- var inject = ["console", "database", "server", "cron"];
767
- var logBuffer = [];
768
- var Config = import_koishi5.Schema.object({
769
- ggUsername: import_koishi5.Schema.string().description("WIKIGG 用户名").default("1"),
770
- ggPassword: import_koishi5.Schema.string().description("WIKIGG 密码").default("1"),
771
- bwikiusername: import_koishi5.Schema.string().description("bwiki用户名").default("1"),
772
- bwikipassword: import_koishi5.Schema.string().description("bwiki密码").default("1"),
773
- domain: import_koishi5.Schema.string().description("你的短链域名(必填,如:klei.vip)").default("klei.vip"),
774
- main_site: import_koishi5.Schema.string().description("主站域名(必填,如:oxygennotincluded.wiki.gg)").default("oxygennotincluded.wiki.gg/zh"),
775
- bwiki_site: import_koishi5.Schema.string().description("镜像站域名(必填,如:wiki.biligame.com)").default("wiki.biligame.com/oni"),
776
- logsUrl: import_koishi5.Schema.string().description("日志查看地址").default("https://klei.vip/onilogs")
777
- });
778
- var PublicLogProvider = class extends import_plugin_console.DataService {
1130
+ // src/plugins/syncCommands.ts
1131
+ var SyncCommands = class {
779
1132
  static {
780
- __name(this, "PublicLogProvider");
781
- }
782
- constructor(ctx) {
783
- super(ctx, "onilogs", { authority: 0 });
784
- }
785
- async get() {
786
- return logBuffer;
1133
+ __name(this, "SyncCommands");
787
1134
  }
788
- };
789
- function apply(ctx, config) {
790
- const log = ctx.logger("oni-sync");
791
- let ggbot;
792
- let bwikibot;
793
- ctx.inject(["console"], (ctx2) => {
794
- ctx2.console.addEntry({
795
- dev: (0, import_path.resolve)(__dirname, "../client/index.ts"),
796
- prod: (0, import_path.resolve)(__dirname, "../dist")
797
- });
798
- });
799
- ctx.plugin(PublicLogProvider);
800
- const target = {
801
- colors: 0,
802
- record: /* @__PURE__ */ __name((record) => {
803
- if (record.name !== "oni-sync") return;
804
- logBuffer.push(record);
805
- if (logBuffer.length > 100) {
806
- logBuffer = logBuffer.slice(-100);
807
- }
808
- ctx.get("console")?.patch("onilogs", logBuffer);
809
- }, "record")
810
- };
811
- import_koishi5.Logger.targets.push(target);
812
- ctx.on("dispose", () => {
813
- const index = import_koishi5.Logger.targets.indexOf(target);
814
- if (index > -1) import_koishi5.Logger.targets.splice(index, 1);
815
- });
816
- ctx.model.extend("wikipages", {
817
- id: "integer",
818
- title: "string",
819
- pinyin_full: "string",
820
- // 全拼
821
- pinyin_first: "string"
822
- // 首字母
823
- });
824
- ctx.server.get("/gg/:id", async (router) => {
825
- const pageId = Number(router.params.id);
826
- if (isNaN(pageId)) return router.body = "❌ 无效的页面ID,必须为数字!";
827
- const [page] = await ctx.database.get("wikipages", { id: pageId });
828
- if (!page)
829
- return router.body = `❌ 未找到ID为【${pageId}】的页面,请联系管理员更新缓存!`;
830
- const targetUrl = `https://${config.main_site}/${encodeURIComponent(
831
- page.title
832
- )}?variant=zh`;
833
- router.redirect(targetUrl);
834
- });
835
- ctx.server.get("/bw/:id", async (router) => {
836
- const pageId = Number(router.params.id);
837
- if (isNaN(pageId)) return router.body = "❌ 无效的页面ID,必须为数字!";
838
- const [page] = await ctx.database.get("wikipages", { id: pageId });
839
- if (!page)
840
- return router.body = `❌ 未找到ID为【${pageId}】的页面,请联系管理员更新缓存!`;
841
- const targetUrl = `https://${config.bwiki_site}/${encodeURIComponent(
842
- page.title
843
- )}`;
844
- router.redirect(targetUrl);
845
- });
846
- ctx.on("ready", async () => {
847
- logger.info("初始化中...");
848
- const sitesConfig = getSitesConfig(config);
849
- ggbot = await login(sitesConfig.gg);
850
- bwikibot = await login(sitesConfig.bwiki);
851
- if (ggbot.login && bwikibot.login) {
852
- logger.info("登录成功,插件已准备就绪");
853
- } else {
854
- logger.error("登录失败,请检查配置");
855
- }
1135
+ static inject = ["wikiBot", "cron"];
1136
+ config;
1137
+ log;
1138
+ constructor(ctx, config) {
1139
+ this.config = config;
1140
+ this.log = ctx.logger("oni-sync");
1141
+ logger.info("WikiBot 服务已就绪,初始化定时任务和指令");
856
1142
  ctx.cron("15 * * * *", async () => {
857
- await incrementalUpdate(ggbot, bwikibot, config);
1143
+ if (!ctx.wikiBot.isGGBotReady() || !ctx.wikiBot.isBWikiBotReady()) {
1144
+ logger.warn("增量更新跳过:Wiki 机器人未就绪");
1145
+ return;
1146
+ }
1147
+ await incrementalUpdate(
1148
+ ctx.wikiBot.getGGBot(),
1149
+ ctx.wikiBot.getBWikiBot(),
1150
+ config
1151
+ );
858
1152
  });
859
1153
  ctx.cron("30 8 * * 4", async () => {
860
- await syncPages(ggbot, bwikibot).then(() => {
1154
+ if (!ctx.wikiBot.isGGBotReady() || !ctx.wikiBot.isBWikiBotReady()) {
1155
+ logger.warn("同步所有页面跳过:Wiki 机器人未就绪");
1156
+ return;
1157
+ }
1158
+ await syncPages(ctx.wikiBot.getGGBot(), ctx.wikiBot.getBWikiBot()).then(() => {
861
1159
  logger.info("自动任务:尝试同步所有页面,从 WIKIGG 到 bwiki");
862
1160
  }).catch((err) => {
863
1161
  logger.error(`同步所有页面失败`);
864
- log.error(`,错误信息:${err}`);
1162
+ this.log.error(`,错误信息:${err}`);
865
1163
  });
866
1164
  });
867
1165
  ctx.cron("30 8 * * 3", async () => {
868
- await syncAllImages(ggbot, bwikibot, config).then(() => {
1166
+ if (!ctx.wikiBot.isGGBotReady() || !ctx.wikiBot.isBWikiBotReady()) {
1167
+ logger.warn("同步所有图片跳过:Wiki 机器人未就绪");
1168
+ return;
1169
+ }
1170
+ await syncAllImages(
1171
+ ctx.wikiBot.getGGBot(),
1172
+ ctx.wikiBot.getBWikiBot(),
1173
+ config
1174
+ ).then(() => {
869
1175
  logger.info("自动任务:尝试同步所有图片,从 WIKIGG 到 bwiki");
870
1176
  }).catch((err) => {
871
1177
  logger.error(`同步所有图片失败`);
872
- log.error(`,错误信息:${err}`);
1178
+ this.log.error(`,错误信息:${err}`);
873
1179
  });
874
1180
  });
875
- });
876
- ctx.command("sync <pageTitle:string>", "同步指定页面", { authority: 2 }).action(async ({ session }, pageTitle) => {
877
- await syncSinglePage(ggbot, bwikibot, pageTitle, "sync-bot").then(() => {
878
- session.send(
879
- `✅ 已尝试同步页面:${pageTitle},请前往控制台查看:${config.logsUrl}`
880
- );
881
- }).catch((err) => {
882
- session.send(`❌ 同步页面失败:${pageTitle}`);
883
- log.error(`,错误信息:${err}`);
1181
+ this.registerCommands(ctx);
1182
+ }
1183
+ checkBotsReady(ctx) {
1184
+ const ggReady = ctx.wikiBot.isGGBotReady();
1185
+ const bwReady = ctx.wikiBot.isBWikiBotReady();
1186
+ return ggReady && bwReady;
1187
+ }
1188
+ registerCommands(ctx) {
1189
+ ctx.command("sync <pageTitle:string>", "同步指定页面", { authority: 2 }).action(async ({ session }, pageTitle) => {
1190
+ if (!this.checkBotsReady(ctx)) {
1191
+ return session.send("❌ Wiki 机器人未就绪,请检查登录配置或查看日志");
1192
+ }
1193
+ await syncSinglePage(
1194
+ ctx.wikiBot.getGGBot(),
1195
+ ctx.wikiBot.getBWikiBot(),
1196
+ pageTitle,
1197
+ "sync-bot"
1198
+ ).then(() => {
1199
+ session.send(
1200
+ `✅ 已尝试同步页面:${pageTitle},请前往控制台查看:${this.config.logsUrl}`
1201
+ );
1202
+ }).catch((err) => {
1203
+ session.send(`❌ 同步页面失败:${pageTitle}`);
1204
+ this.log.error(`,错误信息:${err}`);
1205
+ });
884
1206
  });
885
- });
886
- ctx.command("sync.incrementalUpdate", "获取3h内的编辑并尝试更新", {
887
- authority: 2
888
- }).alias("增量更新").action(async ({ session }) => {
889
- session.send(
890
- `🚀 获取3h内的编辑并尝试更新,任务耗时可能较长,请前往控制台查看日志:${config.logsUrl}`
891
- );
892
- await incrementalUpdate(ggbot, bwikibot, config).then(() => {
893
- session.send(
894
- `✅ 已尝试获取三小时前的编辑并同步,请前往控制台查看:${config.logsUrl}`
895
- );
896
- }).catch((err) => {
1207
+ ctx.command("sync.incrementalUpdate", "获取3h内的编辑并尝试更新", {
1208
+ authority: 2
1209
+ }).alias("增量更新").action(async ({ session }) => {
1210
+ if (!this.checkBotsReady(ctx)) {
1211
+ return session.send("❌ Wiki 机器人未就绪,请检查登录配置或查看日志");
1212
+ }
897
1213
  session.send(
898
- `❌ 同步所有页面失败,请前往控制台查看日志:${config.logsUrl}`
1214
+ `🚀 获取3h内的编辑并尝试更新,任务耗时可能较长,请前往控制台查看日志:${this.config.logsUrl}`
899
1215
  );
900
- log.error(`同步所有页面失败,错误信息:${err}`);
1216
+ await incrementalUpdate(
1217
+ ctx.wikiBot.getGGBot(),
1218
+ ctx.wikiBot.getBWikiBot(),
1219
+ this.config
1220
+ ).then(() => {
1221
+ session.send(
1222
+ `✅ 已尝试获取三小时前的编辑并同步,请前往控制台查看:${this.config.logsUrl}`
1223
+ );
1224
+ }).catch((err) => {
1225
+ session.send(
1226
+ `❌ 同步所有页面失败,请前往控制台查看日志:${this.config.logsUrl}`
1227
+ );
1228
+ this.log.error(`同步所有页面失败,错误信息:${err}`);
1229
+ });
901
1230
  });
902
- });
903
- ctx.command("sync.allpages", "同步所有页面", { authority: 2 }).action(async ({ session }) => {
904
- session.send(
905
- `🚀 开始同步所有页面,任务耗时较长,请前往控制台查看日志:${config.logsUrl}`
906
- );
907
- await syncPages(ggbot, bwikibot).then(() => {
908
- session.send(
909
- `✅ 已尝试同步所有页面,请前往控制台查看:${config.logsUrl}`
910
- );
911
- }).catch((err) => {
1231
+ ctx.command("sync.allpages", "同步所有页面", { authority: 2 }).action(async ({ session }) => {
1232
+ if (!this.checkBotsReady(ctx)) {
1233
+ return session.send("❌ Wiki 机器人未就绪,请检查登录配置或查看日志");
1234
+ }
912
1235
  session.send(
913
- `❌ 同步所有页面失败,请前往控制台查看日志:${config.logsUrl}`
1236
+ `🚀 开始同步所有页面,任务耗时较长,请前往控制台查看日志:${this.config.logsUrl}`
914
1237
  );
915
- log.error(`同步所有页面失败,错误信息:${err}`);
1238
+ await syncPages(ctx.wikiBot.getGGBot(), ctx.wikiBot.getBWikiBot()).then(() => {
1239
+ session.send(
1240
+ `✅ 已尝试同步所有页面,请前往控制台查看:${this.config.logsUrl}`
1241
+ );
1242
+ }).catch((err) => {
1243
+ session.send(
1244
+ `❌ 同步所有页面失败,请前往控制台查看日志:${this.config.logsUrl}`
1245
+ );
1246
+ this.log.error(`同步所有页面失败,错误信息:${err}`);
1247
+ });
916
1248
  });
917
- });
918
- ctx.command("sync.module <moduleTitle:string>", "同步指定模块", {
919
- authority: 2
920
- }).action(async ({ session }, moduleTitle) => {
921
- await session.send(`✅ 同步中,请前往控制台查看:${config.logsUrl}`);
922
- await syncSingleModule(ggbot, bwikibot, moduleTitle, "sync-bot").then(() => {
923
- session.send(
924
- `✅ 已尝试同步模块:${moduleTitle},请前往控制台查看:${config.logsUrl}`
1249
+ ctx.command("sync.module <moduleTitle:string>", "同步指定模块", {
1250
+ authority: 2
1251
+ }).action(async ({ session }, moduleTitle) => {
1252
+ if (!this.checkBotsReady(ctx)) {
1253
+ return session.send("❌ Wiki 机器人未就绪,请检查登录配置或查看日志");
1254
+ }
1255
+ await session.send(
1256
+ `✅ 同步中,请前往控制台查看:${this.config.logsUrl}`
925
1257
  );
926
- }).catch((err) => {
927
- session.send(`❌ 同步模块失败:${moduleTitle}`);
928
- log.error(`错误信息:${err}`);
1258
+ await syncSingleModule(
1259
+ ctx.wikiBot.getGGBot(),
1260
+ ctx.wikiBot.getBWikiBot(),
1261
+ moduleTitle,
1262
+ "sync-bot"
1263
+ ).then(() => {
1264
+ session.send(
1265
+ `✅ 已尝试同步模块:${moduleTitle},请前往控制台查看:${this.config.logsUrl}`
1266
+ );
1267
+ }).catch((err) => {
1268
+ session.send(`❌ 同步模块失败:${moduleTitle}`);
1269
+ this.log.error(`错误信息:${err}`);
1270
+ });
929
1271
  });
930
- });
931
- ctx.command("sync.allmodules", "同步所有模块", { authority: 2 }).action(async ({ session }) => {
932
- await session.send(
933
- `🚀 开始同步所有模块,任务耗时较长,请前往控制台查看:${config.logsUrl}`
934
- );
935
- await syncModules(ggbot, bwikibot).then(() => {
936
- session.send(
937
- `✅ 已尝试同步所有模块,请前往控制台查看:${config.logsUrl}`
938
- );
939
- }).catch((err) => {
940
- session.send(
941
- `❌ 同步所有模块失败,请前往控制台查看日志:${config.logsUrl}`
1272
+ ctx.command("sync.allmodules", "同步所有模块", { authority: 2 }).action(async ({ session }) => {
1273
+ if (!this.checkBotsReady(ctx)) {
1274
+ return session.send("❌ Wiki 机器人未就绪,请检查登录配置或查看日志");
1275
+ }
1276
+ await session.send(
1277
+ `🚀 开始同步所有模块,任务耗时较长,请前往控制台查看:${this.config.logsUrl}`
942
1278
  );
943
- log.error(`同步所有模块失败,错误信息:${err}`);
944
- });
945
- });
946
- ctx.command("sync.img <imgTitle:string>", "同步指定图片", { authority: 2 }).action(async ({ session }, imgTitle) => {
947
- await session.send(
948
- `🚀 开始同步,任务可能耗时较长,请前往控制台查看:${config.logsUrl}`
949
- );
950
- await syncSingleImage(
951
- ggbot,
952
- bwikibot,
953
- `${imgTitle.startsWith("File:") ? "" : "File:"}${imgTitle}`,
954
- config
955
- ).then(() => {
956
- session.send(`✅ 已尝试同步图片:${imgTitle}`);
957
- }).catch((err) => {
958
- session.send(`❌ 同步图片失败:${imgTitle}`);
959
- log.error(`同步图片失败:${imgTitle},错误信息:${err}`);
1279
+ await syncModules(ctx.wikiBot.getGGBot(), ctx.wikiBot.getBWikiBot()).then(() => {
1280
+ session.send(
1281
+ `✅ 已尝试同步所有模块,请前往控制台查看:${this.config.logsUrl}`
1282
+ );
1283
+ }).catch((err) => {
1284
+ session.send(
1285
+ `❌ 同步所有模块失败,请前往控制台查看日志:${this.config.logsUrl}`
1286
+ );
1287
+ this.log.error(`同步所有模块失败,错误信息:${err}`);
1288
+ });
960
1289
  });
961
- });
962
- ctx.command("sync.allimgs", "同步所有图片", { authority: 2 }).action(async ({ session }) => {
963
- session.send(
964
- `🚀 开始同步所有图片,任务耗时较长,请前往控制台查看:${config.logsUrl}`
965
- );
966
- await syncAllImages(ggbot, bwikibot, config).then(() => {
967
- session.send(
968
- `✅ 已尝试同步所有图片,请前往控制台查看:${config.logsUrl}`
1290
+ ctx.command("sync.img <imgTitle:string>", "同步指定图片", { authority: 2 }).action(async ({ session }, imgTitle) => {
1291
+ if (!this.checkBotsReady(ctx)) {
1292
+ return session.send("❌ Wiki 机器人未就绪,请检查登录配置或查看日志");
1293
+ }
1294
+ await session.send(
1295
+ `🚀 开始同步,任务可能耗时较长,请前往控制台查看:${this.config.logsUrl}`
969
1296
  );
970
- }).catch((err) => {
1297
+ await syncSingleImage(
1298
+ ctx.wikiBot.getGGBot(),
1299
+ ctx.wikiBot.getBWikiBot(),
1300
+ `${imgTitle.startsWith("File:") ? "" : "File:"}${imgTitle}`,
1301
+ this.config
1302
+ ).then(() => {
1303
+ session.send(`✅ 已尝试同步图片:${imgTitle}`);
1304
+ }).catch((err) => {
1305
+ session.send(`❌ 同步图片失败:${imgTitle}`);
1306
+ this.log.error(`同步图片失败:${imgTitle},错误信息:${err}`);
1307
+ });
1308
+ });
1309
+ ctx.command("sync.allimgs", "同步所有图片", { authority: 2 }).action(async ({ session }) => {
1310
+ if (!this.checkBotsReady(ctx)) {
1311
+ return session.send("❌ Wiki 机器人未就绪,请检查登录配置或查看日志");
1312
+ }
971
1313
  session.send(
972
- `❌ 同步所有图片失败,请前往控制台查看日志:${config.logsUrl}`
1314
+ `🚀 开始同步所有图片,任务耗时较长,请前往控制台查看:${this.config.logsUrl}`
973
1315
  );
974
- log.error(`同步所有图片失败,错误信息:${err}`);
1316
+ await syncAllImages(
1317
+ ctx.wikiBot.getGGBot(),
1318
+ ctx.wikiBot.getBWikiBot(),
1319
+ this.config
1320
+ ).then(() => {
1321
+ session.send(
1322
+ `✅ 已尝试同步所有图片,请前往控制台查看:${this.config.logsUrl}`
1323
+ );
1324
+ }).catch((err) => {
1325
+ session.send(
1326
+ `❌ 同步所有图片失败,请前往控制台查看日志:${this.config.logsUrl}`
1327
+ );
1328
+ this.log.error(`同步所有图片失败,错误信息:${err}`);
1329
+ });
975
1330
  });
1331
+ }
1332
+ };
1333
+ ((SyncCommands2) => {
1334
+ SyncCommands2.Config = import_koishi8.Schema.object({
1335
+ ggUsername: import_koishi8.Schema.string().description("WIKIGG 用户名").default("1"),
1336
+ ggPassword: import_koishi8.Schema.string().description("WIKIGG 密码").default("1"),
1337
+ bwikiusername: import_koishi8.Schema.string().description("bwiki用户名").default("1"),
1338
+ bwikipassword: import_koishi8.Schema.string().description("bwiki密码").default("1"),
1339
+ domain: import_koishi8.Schema.string().description("你的短链域名(必填,如:klei.vip)").default("klei.vip"),
1340
+ main_site: import_koishi8.Schema.string().description("主站域名(必填,如:oxygennotincluded.wiki.gg)").default("oxygennotincluded.wiki.gg/zh"),
1341
+ bwiki_site: import_koishi8.Schema.string().description("镜像站域名(必填,如:wiki.biligame.com)").default("wiki.biligame.com/oni"),
1342
+ logsUrl: import_koishi8.Schema.string().description("日志查看地址").default("https://klei.vip/onilogs")
976
1343
  });
977
- ctx.command("x <itemName>", "查询缺氧中文wiki,精准匹配+拼音模糊匹配").alias("/查wiki").action(async ({ session }, itemName = "") => {
978
- const queryKey = itemName.trim().toLowerCase();
979
- if (queryKey === "")
980
- return `以下是使用说明:
981
- 原站点: https://${config.domain}/gg/88888888
1344
+ })(SyncCommands || (SyncCommands = {}));
982
1345
 
983
- bwiki: https://${config.domain}/bw/88888888`;
984
- if (queryKey === "火箭计算器") {
985
- return "请前往以下站点使用火箭计算器工具:\n\nhttps://klei.vip/calculator\n\n(注:该工具正在测试阶段,数据可能不够准确,仅供参考)";
986
- }
987
- const { pinyin_full: queryPinyinFull, pinyin_first: queryPinyinFirst } = generatePinyinInfo(queryKey);
988
- const preciseTitleRes = await ctx.database.get("wikipages", {
989
- title: queryKey
990
- });
991
- if (preciseTitleRes.length > 0) {
992
- const { id: id2 } = preciseTitleRes[0];
993
- return `✅ 精准匹配成功
994
- 原站点: https://${config.domain}/gg/${id2}
995
-
996
- bwiki: https://${config.domain}/bw/${id2}`;
997
- }
998
- const preciseFullPinyinRes = await ctx.database.get("wikipages", {
999
- pinyin_full: queryKey
1346
+ // src/plugins/queryCommands.ts
1347
+ var import_koishi9 = require("koishi");
1348
+ var SPECIAL_CASES = /* @__PURE__ */ new Map([
1349
+ [
1350
+ "火箭计算器",
1351
+ "请前往以下站点使用火箭计算器工具:\n\nhttps://klei.vip/calculator\n\n(注:该工具正在测试阶段,数据可能不够准确,仅供参考)"
1352
+ ]
1353
+ ]);
1354
+ var QueryCommands = class {
1355
+ static {
1356
+ __name(this, "QueryCommands");
1357
+ }
1358
+ static inject = ["database"];
1359
+ config;
1360
+ constructor(ctx, config) {
1361
+ this.config = config;
1362
+ this.registerCommands(ctx);
1363
+ }
1364
+ registerCommands(ctx) {
1365
+ ctx.command("x <itemName>", "查询缺氧中文wiki,精准匹配+拼音模糊匹配").alias("/查wiki").action(async ({ session }, itemName = "") => {
1366
+ const queryKey = itemName.trim().toLowerCase();
1367
+ if (!queryKey) {
1368
+ return this.getUsageMessage();
1369
+ }
1370
+ if (SPECIAL_CASES.has(queryKey)) {
1371
+ return SPECIAL_CASES.get(queryKey);
1372
+ }
1373
+ const { pinyin_full: queryPinyinFull, pinyin_first: queryPinyinFirst } = generatePinyinInfo(queryKey);
1374
+ const preciseMatch = await this.findPreciseMatch(
1375
+ ctx,
1376
+ queryKey,
1377
+ queryPinyinFull,
1378
+ queryPinyinFirst
1379
+ );
1380
+ if (preciseMatch) {
1381
+ return this.formatResultMessage(
1382
+ preciseMatch,
1383
+ preciseMatch.prefix
1384
+ );
1385
+ }
1386
+ const allPages = await ctx.database.get("wikipages", {});
1387
+ if (allPages.length === 0) {
1388
+ return `❌ 本地缓存为空,请联系管理员执行【update】指令更新缓存!`;
1389
+ }
1390
+ const fuzzyMatches = this.findFuzzyMatches(
1391
+ allPages,
1392
+ queryKey,
1393
+ queryPinyinFull,
1394
+ queryPinyinFirst
1395
+ );
1396
+ if (fuzzyMatches.length === 0) {
1397
+ return `❌ 未找到【${queryKey}】相关内容,请按游戏内标准名称重新查询!`;
1398
+ }
1399
+ return await this.handleSelection(session, fuzzyMatches);
1000
1400
  });
1001
- if (preciseFullPinyinRes.length > 0) {
1002
- const { id: id2, title } = preciseFullPinyinRes[0];
1003
- return `✅ 拼音精准匹配成功(${queryKey} → ${title})
1004
- 原站点: https://${config.domain}/gg/${id2}
1401
+ }
1402
+ getUsageMessage() {
1403
+ return `以下是使用说明:
1404
+ 原站点: https://${this.config.domain}/gg/88888888
1005
1405
 
1006
- bwiki: https://${config.domain}/bw/${id2}`;
1406
+ bwiki: https://${this.config.domain}/bw/88888888`;
1407
+ }
1408
+ formatResultMessage(match, prefix) {
1409
+ let message = "";
1410
+ if (prefix) {
1411
+ message += prefix + "\n";
1007
1412
  }
1008
- const preciseFirstPinyinRes = await ctx.database.get("wikipages", {
1009
- pinyin_first: queryKey
1010
- });
1011
- if (preciseFirstPinyinRes.length > 0) {
1012
- const { id: id2, title } = preciseFirstPinyinRes[0];
1013
- return `✅ 首字母精准匹配成功(${queryKey} → ${title})
1014
- 原站点: https://${config.domain}/gg/${id2}
1413
+ message += `原站点: https://${this.config.domain}/gg/${match.id}
1015
1414
 
1016
- bwiki: https://${config.domain}/bw/${id2} `;
1415
+ bwiki: https://${this.config.domain}/bw/${match.id}`;
1416
+ return message;
1417
+ }
1418
+ async findPreciseMatch(ctx, queryKey, queryPinyinFull, queryPinyinFirst) {
1419
+ const checks = [
1420
+ { query: { title: queryKey }, prefix: "✅ 精准匹配成功" },
1421
+ {
1422
+ query: { pinyin_full: queryKey },
1423
+ prefix: `✅ 拼音精准匹配成功(${queryKey} → `
1424
+ },
1425
+ {
1426
+ query: { pinyin_first: queryKey },
1427
+ prefix: `✅ 首字母精准匹配成功(${queryKey} → `
1428
+ }
1429
+ ];
1430
+ for (const check of checks) {
1431
+ const results = await ctx.database.get("wikipages", check.query);
1432
+ if (results.length > 0) {
1433
+ const match = results[0];
1434
+ let prefix = check.prefix;
1435
+ if (prefix && prefix.includes("→")) {
1436
+ prefix += `${match.title})`;
1437
+ }
1438
+ match.prefix = prefix;
1439
+ return match;
1440
+ }
1017
1441
  }
1018
- const allPages = await ctx.database.get("wikipages", {});
1019
- if (allPages.length === 0) {
1020
- return `❌ 本地缓存为空,请联系管理员执行【update】指令更新缓存!`;
1442
+ return null;
1443
+ }
1444
+ calculateScore(page, queryKey, queryPinyinFull, queryPinyinFirst) {
1445
+ let score = 0;
1446
+ const { title, pinyin_full, pinyin_first } = page;
1447
+ if (title.includes(queryKey)) score += 10;
1448
+ if (pinyin_full.startsWith(queryPinyinFull)) score += 9;
1449
+ if (pinyin_full.includes(queryPinyinFull)) score += 8;
1450
+ if (pinyin_first.includes(queryPinyinFirst)) score += 6;
1451
+ if (queryPinyinFull.includes(
1452
+ pinyin_full.substring(
1453
+ 0,
1454
+ Math.min(pinyin_full.length, queryPinyinFull.length)
1455
+ )
1456
+ )) {
1457
+ score += 5;
1021
1458
  }
1022
- const matchResult = [];
1023
- allPages.forEach((page) => {
1024
- const { title, pinyin_full, pinyin_first } = page;
1025
- let score = 0;
1026
- if (title.includes(queryKey)) score += 10;
1027
- if (pinyin_full.startsWith(queryPinyinFull)) score += 9;
1028
- if (pinyin_full.includes(queryPinyinFull)) score += 8;
1029
- if (pinyin_first.includes(queryPinyinFirst)) score += 6;
1030
- if (queryPinyinFull.includes(
1031
- pinyin_full.substring(
1032
- 0,
1033
- Math.min(pinyin_full.length, queryPinyinFull.length)
1034
- )
1035
- ))
1036
- score += 5;
1459
+ return score;
1460
+ }
1461
+ findFuzzyMatches(allPages, queryKey, queryPinyinFull, queryPinyinFirst) {
1462
+ const matchResults = [];
1463
+ for (const page of allPages) {
1464
+ const score = this.calculateScore(
1465
+ page,
1466
+ queryKey,
1467
+ queryPinyinFull,
1468
+ queryPinyinFirst
1469
+ );
1037
1470
  if (score > 0) {
1038
- matchResult.push({ id: page.id, title, score });
1471
+ matchResults.push({ id: page.id, title: page.title, score });
1039
1472
  }
1040
- });
1041
- if (matchResult.length === 0) {
1042
- return `❌ 未找到【${queryKey}】相关内容,请按游戏内标准名称重新查询!`;
1043
1473
  }
1044
- const sortedResult = matchResult.sort((a, b) => {
1045
- if (b.score !== a.score) return b.score - a.score;
1046
- return a.title.length - b.title.length;
1047
- });
1048
- const uniqueResult = Array.from(
1049
- new Map(sortedResult.map((item) => [item.title, item])).values()
1474
+ return matchResults.sort((a, b) => b.score - a.score || a.title.length - b.title.length).filter(
1475
+ (item, index, array) => array.findIndex((t) => t.title === item.title) === index
1050
1476
  ).slice(0, 5);
1051
- const resultCount = uniqueResult.length;
1477
+ }
1478
+ async handleSelection(session, matches) {
1479
+ const resultCount = matches.length;
1052
1480
  let replyMsg = `🔍 未找到精准匹配,为你找到【 ${resultCount} 】个相似结果,请输入序号选择(10秒内有效):
1053
1481
  `;
1054
- uniqueResult.forEach((item, index) => {
1482
+ matches.forEach((item, index) => {
1055
1483
  replyMsg += `${index + 1}. ${item.title}
1056
1484
  `;
1057
1485
  });
@@ -1060,85 +1488,163 @@ bwiki: https://${config.domain}/bw/${id2} `;
1060
1488
  await session.send(replyMsg);
1061
1489
  const userInput = await session.prompt(15e3);
1062
1490
  if (!userInput) return;
1063
- const selectNum = parseInt(userInput.trim());
1491
+ const selectNum = parseInt(userInput.trim(), 10);
1064
1492
  if (isNaN(selectNum) || selectNum < 1 || selectNum > resultCount) {
1065
1493
  return `❌ 输入无效!请输入 1-${resultCount} 之间的数字序号`;
1066
1494
  }
1067
- const { id } = uniqueResult[selectNum - 1];
1068
- return `✅ 选择成功
1069
- 原站点: https://${config.domain}/gg/${id}
1070
-
1071
- bwiki: https://${config.domain}/bw/${id}`;
1495
+ const selected = matches[selectNum - 1];
1496
+ return this.formatResultMessage(
1497
+ selected,
1498
+ "✅ 选择成功"
1499
+ );
1500
+ }
1501
+ };
1502
+ ((QueryCommands2) => {
1503
+ QueryCommands2.Config = import_koishi9.Schema.object({
1504
+ domain: import_koishi9.Schema.string().description("你的短链域名(必填,如:klei.vip)").default("klei.vip")
1072
1505
  });
1073
- ctx.command("update", "更新本地页面缓存(主站)", { authority: 2 }).action(async ({ session }) => {
1074
- await session.execute("update.status");
1075
- try {
1076
- const res = await ggbot.request({
1077
- action: "query",
1078
- list: "allpages",
1079
- format: "json",
1080
- aplimit: "max"
1081
- });
1082
- logger.info("主站页面查询成功");
1083
- const pages = res.query.allpages || [];
1084
- const pageData = pages.map((page) => {
1085
- const { pinyin_full, pinyin_first } = generatePinyinInfo(page.title);
1086
- return {
1087
- id: page.pageid,
1088
- title: page.title,
1089
- pinyin_full,
1090
- pinyin_first
1091
- };
1092
- });
1093
- if (pageData.length > 0) {
1094
- await ctx.database.upsert("wikipages", pageData);
1506
+ })(QueryCommands || (QueryCommands = {}));
1507
+
1508
+ // src/plugins/updateCommands.ts
1509
+ var import_koishi10 = require("koishi");
1510
+ var UpdateCommands = class {
1511
+ static {
1512
+ __name(this, "UpdateCommands");
1513
+ }
1514
+ static inject = ["database", "wikiBot"];
1515
+ config;
1516
+ log;
1517
+ constructor(ctx, config) {
1518
+ this.config = config;
1519
+ this.log = ctx.logger("oni-sync");
1520
+ this.registerCommands(ctx);
1521
+ }
1522
+ registerCommands(ctx) {
1523
+ ctx.command("update", "更新本地页面缓存(主站)", { authority: 2 }).action(async ({ session }) => {
1524
+ await session.execute("update.status");
1525
+ try {
1526
+ const res = await ctx.wikiBot.getGGBot().request({
1527
+ action: "query",
1528
+ list: "allpages",
1529
+ format: "json",
1530
+ aplimit: "max"
1531
+ });
1532
+ logger.info("主站页面查询成功");
1533
+ const pages = res.query.allpages || [];
1534
+ const pageData = pages.map((page) => {
1535
+ const { pinyin_full, pinyin_first } = generatePinyinInfo(
1536
+ page.title
1537
+ );
1538
+ return {
1539
+ id: page.pageid,
1540
+ title: page.title,
1541
+ pinyin_full,
1542
+ pinyin_first
1543
+ };
1544
+ });
1545
+ if (pageData.length > 0) {
1546
+ await ctx.database.upsert("wikipages", pageData);
1547
+ }
1548
+ session.send(`✅ 检索到 ${pages.length} 个页面,已更新至数据库`);
1549
+ logger.info(`检索到 ${pages.length} 个页面,已更新至数据库`);
1550
+ } catch (err) {
1551
+ this.log.error("主站缓存更新失败", err);
1552
+ session.send("❌ 主站缓存更新失败,请联系管理员查看日志");
1095
1553
  }
1096
- session.send(`✅ 检索到 ${pages.length} 个页面,已更新至数据库`);
1097
- logger.info(`检索到 ${pages.length} 个页面,已更新至数据库`);
1098
- } catch (err) {
1099
- log.error("主站缓存更新失败", err);
1100
- session.send("❌ 主站缓存更新失败,请联系管理员查看日志");
1101
- }
1102
- });
1103
- ctx.command("update.delete", "删除本地页面缓存", { authority: 4 }).action(async ({ session }) => {
1104
- try {
1105
- const count = await ctx.database.remove("wikipages", {});
1106
- session.send(`✅ 已删除 ${count.removed} 条本地缓存`);
1107
- logger.info(`已删除 ${count.removed} 条本地缓存`);
1108
- } catch (err) {
1109
- log.error("删除缓存失败", err);
1110
- session.send("❌ 删除缓存失败,请联系管理员查看日志");
1111
- }
1112
- });
1113
- ctx.command("update.status", "查询本地缓存数量", { authority: 1 }).action(async ({ session }) => {
1114
- try {
1115
- const pages = await ctx.database.get("wikipages", {});
1116
- session.send(`📊 数据库中缓存了 ${pages.length} 条页面`);
1117
- logger.info(`数据库中缓存了 ${pages.length} 条页面`);
1118
- } catch (err) {
1119
- log.error("查询缓存状态失败", err);
1120
- session.send("❌ 查询缓存状态失败,请联系管理员查看日志");
1121
- }
1554
+ });
1555
+ ctx.command("update.delete", "删除本地页面缓存", { authority: 4 }).action(async ({ session }) => {
1556
+ try {
1557
+ const count = await ctx.database.remove("wikipages", {});
1558
+ session.send(`✅ 已删除 ${count.removed} 条本地缓存`);
1559
+ logger.info(`已删除 ${count.removed} 条本地缓存`);
1560
+ } catch (err) {
1561
+ this.log.error("删除缓存失败", err);
1562
+ session.send("❌ 删除缓存失败,请联系管理员查看日志");
1563
+ }
1564
+ });
1565
+ ctx.command("update.status", "查询本地缓存数量", { authority: 1 }).action(async ({ session }) => {
1566
+ try {
1567
+ const pages = await ctx.database.get("wikipages", {});
1568
+ session.send(`📊 数据库中缓存了 ${pages.length} 条页面`);
1569
+ logger.info(`数据库中缓存了 ${pages.length} 条页面`);
1570
+ } catch (err) {
1571
+ this.log.error("查询缓存状态失败", err);
1572
+ session.send("❌ 查询缓存状态失败,请联系管理员查看日志");
1573
+ }
1574
+ });
1575
+ ctx.command("redirect <pageName> <targetPageName>", "添加原站点重定向", {
1576
+ authority: 2
1577
+ }).alias("重定向").action(async ({ session }, pageName, targetPageName) => {
1578
+ if (!pageName || !targetPageName) {
1579
+ return "❌ 参数错误!用法:redirect <原页面名> <目标页面名>";
1580
+ }
1581
+ try {
1582
+ await ctx.wikiBot.getGGBot().create(
1583
+ pageName,
1584
+ `#REDIRECT [[${targetPageName}]]`,
1585
+ "来自qq机器人的添加重定向页面请求"
1586
+ );
1587
+ logger.info(`已为 ${pageName} 添加重定向至 ${targetPageName}`);
1588
+ session.send(`✅ 已尝试添加重定向 ${pageName} -> ${targetPageName}`);
1589
+ await session.execute(`update`);
1590
+ } catch (err) {
1591
+ this.log.error(
1592
+ `添加重定向 ${pageName} -> ${targetPageName} 失败`,
1593
+ err
1594
+ );
1595
+ session.send(`❌ 添加重定向失败,请联系管理员查看日志`);
1596
+ }
1597
+ });
1598
+ ctx.command("relogin", "手动重新登录 Wiki 机器人", { authority: 2 }).alias("重新登录").action(async ({ session }) => {
1599
+ session.send("🚀 开始重新登录 Wiki 机器人...");
1600
+ try {
1601
+ const result = await ctx.wikiBot.relogin();
1602
+ let message = "📋 重新登录结果:\n";
1603
+ message += result.gg ? "✅ WIKIGG 登录成功\n" : "❌ WIKIGG 登录失败\n";
1604
+ message += result.bwiki ? "✅ bwiki 登录成功\n" : "❌ bwiki 登录失败\n";
1605
+ if (result.gg && result.bwiki) {
1606
+ message += "\n🎉 两个 Wiki 机器人都已成功登录!";
1607
+ } else if (result.gg || result.bwiki) {
1608
+ message += "\n⚠️ 部分 Wiki 机器人已登录";
1609
+ } else {
1610
+ message += "\n💥 所有 Wiki 机器人登录都失败了,请检查配置";
1611
+ }
1612
+ session.send(message);
1613
+ } catch (err) {
1614
+ this.log.error("重新登录失败", err);
1615
+ session.send("❌ 重新登录过程中发生错误,请查看日志");
1616
+ }
1617
+ });
1618
+ }
1619
+ };
1620
+ ((UpdateCommands2) => {
1621
+ UpdateCommands2.Config = import_koishi10.Schema.object({
1622
+ logsUrl: import_koishi10.Schema.string().description("日志查看地址").default("https://klei.vip/onilogs")
1122
1623
  });
1123
- ctx.command("redirect <pageName> <targetPageName>", "添加原站点重定向", {
1124
- authority: 2
1125
- }).alias("重定向").action(async ({ session }, pageName, targetPageName) => {
1126
- if (!pageName || !targetPageName) {
1127
- return "❌ 参数错误!用法:redirect <原页面名> <目标页面名>";
1128
- }
1129
- try {
1130
- await ggbot.create(
1131
- pageName,
1132
- `#REDIRECT [[${targetPageName}]]`,
1133
- "来自qq机器人的添加重定向页面请求"
1134
- );
1135
- logger.info(`已为 ${pageName} 添加重定向至 ${targetPageName}`);
1136
- session.send(`✅ 已尝试添加重定向 ${pageName} -> ${targetPageName}`);
1137
- await session.execute(`update`);
1138
- } catch (err) {
1139
- log.error(`添加重定向 ${pageName} -> ${targetPageName} 失败`, err);
1140
- session.send(`❌ 添加重定向失败,请联系管理员查看日志`);
1141
- }
1624
+ })(UpdateCommands || (UpdateCommands = {}));
1625
+
1626
+ // src/index.ts
1627
+ var name = "oni-sync-bot";
1628
+ var Config = import_koishi11.Schema.intersect([
1629
+ WikiBotService.Config,
1630
+ RouteRedirect.Config,
1631
+ SyncCommands.Config,
1632
+ QueryCommands.Config,
1633
+ UpdateCommands.Config
1634
+ ]);
1635
+ function apply(ctx, config) {
1636
+ ctx.plugin(WikiBotService, config);
1637
+ ctx.plugin(ConsoleLogProvider);
1638
+ ctx.plugin(DatabaseExtension);
1639
+ ctx.plugin(RouteRedirect, config);
1640
+ ctx.plugin(SyncCommands, config);
1641
+ ctx.plugin(QueryCommands, config);
1642
+ ctx.plugin(UpdateCommands, config);
1643
+ ctx.inject(["console"], (ctx2) => {
1644
+ ctx2.console.addEntry({
1645
+ dev: (0, import_path.resolve)(__dirname, "../client/index.ts"),
1646
+ prod: (0, import_path.resolve)(__dirname, "../dist")
1647
+ });
1142
1648
  });
1143
1649
  }
1144
1650
  __name(apply, "apply");
@@ -1146,6 +1652,5 @@ __name(apply, "apply");
1146
1652
  0 && (module.exports = {
1147
1653
  Config,
1148
1654
  apply,
1149
- inject,
1150
1655
  name
1151
1656
  });