koishi-plugin-oni-sync-bot 0.0.1 → 0.0.2

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/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ import{defineComponent as p,resolveComponent as e,openBlock as r,createBlock as u,withCtx as n,createVNode as c,createElementVNode as d}from"vue";const m=p({__name:"page",setup(t){return(s,o)=>{const a=e("k-card-content"),_=e("k-card"),l=e("k-layout");return r(),u(l,null,{default:n(()=>[c(_,null,{default:n(()=>[c(a,null,{default:n(()=>[...o[0]||(o[0]=[d("p",null," 这是想写却不知道咋写的页面 ",-1)])]),_:1})]),_:1})]),_:1})}}}),f=t=>{t.page({name:"同步机器人",path:"/syncbot",component:m})};export{f as default};
package/dist/style.css ADDED
@@ -0,0 +1 @@
1
+
package/lib/index.js ADDED
@@ -0,0 +1,1061 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
8
+ var __export = (target, all) => {
9
+ for (var name2 in all)
10
+ __defProp(target, name2, { get: all[name2], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var src_exports = {};
32
+ __export(src_exports, {
33
+ Config: () => Config,
34
+ apply: () => apply,
35
+ inject: () => inject,
36
+ name: () => name
37
+ });
38
+ module.exports = __toCommonJS(src_exports);
39
+ var import_koishi4 = require("koishi");
40
+ var import_path = require("path");
41
+
42
+ // src/utils/login.ts
43
+ var import_mwn = require("mwn");
44
+ async function login(siteConfig) {
45
+ const bot = new import_mwn.Mwn({
46
+ apiUrl: siteConfig.api,
47
+ username: siteConfig.username,
48
+ password: siteConfig.password,
49
+ userAgent: siteConfig.userAgent,
50
+ defaultParams: {
51
+ assert: "user"
52
+ }
53
+ });
54
+ const customRequestOptions = {
55
+ headers: {
56
+ "User-Agent": siteConfig.userAgent
57
+ }
58
+ };
59
+ if (siteConfig.uakey) {
60
+ customRequestOptions.headers["X-authkey"] = siteConfig.uakey;
61
+ }
62
+ bot.setRequestOptions(customRequestOptions);
63
+ await bot.login();
64
+ console.log(`✅ 成功登录 ${siteConfig.name}`);
65
+ return bot;
66
+ }
67
+ __name(login, "login");
68
+
69
+ // src/config/index.ts
70
+ var userAgent = `OniSyncBot/1.0 (https://klei.vip; Charles@klei.vip)`;
71
+ function getSitesConfig(config) {
72
+ return {
73
+ gg: {
74
+ name: "WIKIGG",
75
+ api: "https://oxygennotincluded.wiki.gg/zh/api.php",
76
+ username: config.ggUsername,
77
+ password: config.ggPassword,
78
+ userAgent
79
+ },
80
+ huiji: {
81
+ name: "灰机wiki",
82
+ api: "https://oni.huijiwiki.com/api.php",
83
+ username: config.huijiUsername,
84
+ password: config.huijiPassword,
85
+ userAgent,
86
+ uakey: config.huijiUAKey
87
+ }
88
+ };
89
+ }
90
+ __name(getSitesConfig, "getSitesConfig");
91
+
92
+ // src/utils/tools.ts
93
+ var import_pinyin_pro = require("pinyin-pro");
94
+ var CROSS_SITE_LINK_REGEX = /\[\[(en|ru|pt-br):[^\]]*\]\]/g;
95
+ var DEV_TEXT_REGEX = /Dev:/g;
96
+ var MODULE_NAMESPACE_PREFIX = "Module:Dev/";
97
+ function clean_page_text(text) {
98
+ const textWithoutCrossLink = text.replace(CROSS_SITE_LINK_REGEX, "");
99
+ const textWithReplacedDev = textWithoutCrossLink.replace(
100
+ DEV_TEXT_REGEX,
101
+ MODULE_NAMESPACE_PREFIX
102
+ );
103
+ return textWithReplacedDev;
104
+ }
105
+ __name(clean_page_text, "clean_page_text");
106
+ async function getAndProcessPageContent(site, pageTitle) {
107
+ try {
108
+ const res = await site.read(pageTitle);
109
+ const rawText = res.revisions[0]?.content || "";
110
+ const processedText = clean_page_text(rawText);
111
+ return processedText.trimEnd();
112
+ } catch (err) {
113
+ throw new Error(`[${pageTitle}] 内容获取失败: ${err}`);
114
+ }
115
+ }
116
+ __name(getAndProcessPageContent, "getAndProcessPageContent");
117
+ function generatePinyinInfo(text) {
118
+ if (!text) return { pinyin_full: "", pinyin_first: "" };
119
+ const cleanText = text.replace(/[^\u4e00-\u9fa5a-zA-Z0-9]/g, "");
120
+ if (!cleanText) return { pinyin_full: "", pinyin_first: "" };
121
+ const fullPinyin = (0, import_pinyin_pro.pinyin)(cleanText, {
122
+ toneType: "none",
123
+ type: "string",
124
+ separator: ""
125
+ }).toLowerCase();
126
+ const firstLetter = (0, import_pinyin_pro.pinyin)(cleanText, {
127
+ pattern: "initial",
128
+ separator: ""
129
+ }).toLowerCase();
130
+ return {
131
+ pinyin_full: fullPinyin,
132
+ pinyin_first: firstLetter
133
+ };
134
+ }
135
+ __name(generatePinyinInfo, "generatePinyinInfo");
136
+
137
+ // src/sync/pageSync.ts
138
+ var import_koishi2 = require("koishi");
139
+
140
+ // src/sync/imgSync.ts
141
+ var import_koishi = require("koishi");
142
+ var import_node_fetch = __toESM(require("node-fetch"));
143
+ var import_form_data = __toESM(require("form-data"));
144
+ var CONFIG = {
145
+ IGNORED_IMAGES: [],
146
+ SYNC_INTERVAL_SUCCESS: 3e3,
147
+ SYNC_INTERVAL_FAILED: 5e3,
148
+ UPLOAD_COMMENT: "从 WikiGG 自动同步转存",
149
+ UPLOAD_TEXT: "== 授权说明 ==\n本文件从 WikiGG 转存,遵循原站点授权协议。"
150
+ };
151
+ async function getImageInfo(site, fileName) {
152
+ try {
153
+ const response = await site.query({
154
+ action: "query",
155
+ titles: fileName,
156
+ prop: "imageinfo",
157
+ iiprop: "url|sha1|size|mime"
158
+ });
159
+ const pages = response.query?.pages || {};
160
+ const page = Object.values(pages)[0];
161
+ if (!page || page.missing || !page.imageinfo || page.imageinfo.length === 0) {
162
+ return null;
163
+ }
164
+ const imageInfo = page.imageinfo[0];
165
+ return {
166
+ url: imageInfo.url,
167
+ sha1: imageInfo.sha1,
168
+ size: imageInfo.size
169
+ };
170
+ } catch (error) {
171
+ console.error(`[GetImageInfo] 获取 ${fileName} 信息失败:`, error);
172
+ return null;
173
+ }
174
+ }
175
+ __name(getImageInfo, "getImageInfo");
176
+ async function syncSingleImage(sourceBot, targetBot, fileName, config) {
177
+ if (CONFIG.IGNORED_IMAGES.includes(fileName)) {
178
+ console.log(`[SyncImg] 🚫 图片 ${fileName} 在忽略列表,跳过`);
179
+ return { success: true, reason: "ignored" };
180
+ }
181
+ try {
182
+ console.log(`[SyncImg] 🚀 开始处理: ${fileName}`);
183
+ const sourceImageInfo = await getImageInfo(sourceBot, fileName);
184
+ if (!sourceImageInfo) {
185
+ console.log(`[SyncImg] ❌ 源站未找到图片: ${fileName}`);
186
+ return { success: false, reason: "source_missing" };
187
+ }
188
+ const targetImageInfo = await getImageInfo(targetBot, fileName);
189
+ if (targetImageInfo && targetImageInfo.sha1 === sourceImageInfo.sha1) {
190
+ console.log(`[SyncImg] 🟡 图片 ${fileName} 已存在且内容一致,跳过`);
191
+ return { success: true, reason: "no_change" };
192
+ }
193
+ console.log(`[SyncImg] 📥 下载图片: ${sourceImageInfo.url}`);
194
+ const imageResponse = await (0, import_node_fetch.default)(sourceImageInfo.url, {
195
+ headers: {
196
+ "User-Agent": "OniSyncBot/1.0 (https://klei.vip; Charles@klei.vip)"
197
+ }
198
+ });
199
+ if (!imageResponse.ok) {
200
+ throw new Error(`图片下载失败,HTTP状态码: ${imageResponse.status}`);
201
+ }
202
+ const imageBuffer = Buffer.from(await imageResponse.arrayBuffer());
203
+ console.log(
204
+ `[SyncImg] 📤 上传图片: ${fileName} (大小: ${(imageBuffer.length / 1024).toFixed(1)} KB)`
205
+ );
206
+ const token = await targetBot.getCsrfToken();
207
+ const form = new import_form_data.default();
208
+ form.append("action", "upload");
209
+ form.append("filename", fileName);
210
+ form.append("text", CONFIG.UPLOAD_TEXT);
211
+ form.append("comment", CONFIG.UPLOAD_COMMENT);
212
+ form.append("token", token);
213
+ form.append("ignorewarnings", "1");
214
+ form.append("format", "json");
215
+ form.append("file", imageBuffer, {
216
+ filename: fileName.split(":").pop() || fileName,
217
+ contentType: imageResponse.headers.get("content-type") || "application/octet-stream"
218
+ });
219
+ const rawResponse = await targetBot.rawRequest({
220
+ method: "POST",
221
+ url: targetBot.options.apiUrl,
222
+ data: form,
223
+ headers: {
224
+ ...form.getHeaders(),
225
+ "X-authkey": `${getSitesConfig(config).huiji.uakey}`
226
+ }
227
+ });
228
+ const responseData = rawResponse.data;
229
+ if (responseData.upload && responseData.upload.result === "Success") {
230
+ console.log(`[SyncImg] ✅ 图片 ${fileName} 同步成功`);
231
+ return { success: true, reason: "synced" };
232
+ } else if (responseData.error) {
233
+ throw new Error(`${responseData.error.code}: ${responseData.error.info}`);
234
+ } else {
235
+ throw new Error(`未知响应: ${JSON.stringify(responseData)}`);
236
+ }
237
+ } catch (error) {
238
+ const errMsg = error.message || String(error);
239
+ console.error(`[SyncImg] ❌ 图片 ${fileName} 同步失败:`, errMsg);
240
+ return { success: false, reason: errMsg };
241
+ }
242
+ }
243
+ __name(syncSingleImage, "syncSingleImage");
244
+ async function getAllImages(site) {
245
+ console.log(`[SyncAllImg] 📥 开始获取WikiGG所有图片`);
246
+ const allImages = [];
247
+ const queryGen = site.continuedQueryGen({
248
+ action: "query",
249
+ list: "allimages",
250
+ ailimit: "max",
251
+ aidir: "ascending",
252
+ ainamespace: 6
253
+ });
254
+ for await (const res of queryGen) {
255
+ const imageItems = res.query?.allimages || [];
256
+ const imageTitles = imageItems.map((img) => img.title);
257
+ allImages.push(...imageTitles);
258
+ console.log(`[SyncAllImg] 📄 已获取 ${allImages.length} 个图片`);
259
+ }
260
+ console.log(`[SyncAllImg] 📊 总计获取到 ${allImages.length} 个图片`);
261
+ return allImages;
262
+ }
263
+ __name(getAllImages, "getAllImages");
264
+ async function syncAllImages(sourceBot, targetBot, config) {
265
+ try {
266
+ const imageList = await getAllImages(sourceBot);
267
+ if (imageList.length === 0) {
268
+ console.log(`[SyncAllImg] 📭 源站无图片可同步,结束`);
269
+ return;
270
+ }
271
+ let successCount = 0;
272
+ let failCount = 0;
273
+ let skipCount = 0;
274
+ const failedImages = [];
275
+ console.log(
276
+ `[SyncAllImg] 🚦 开始批量同步,总计 ${imageList.length} 个图片`
277
+ );
278
+ for (let i = 0; i < imageList.length; i++) {
279
+ const fileName = imageList[i];
280
+ const progress = (i + 1) / imageList.length * 100;
281
+ console.log(
282
+ `
283
+ [SyncAllImg] 📈 进度 ${i + 1}/${imageList.length} (${progress.toFixed(1)}%)`
284
+ );
285
+ const result = await syncSingleImage(
286
+ sourceBot,
287
+ targetBot,
288
+ fileName,
289
+ config
290
+ );
291
+ if (!result.success) {
292
+ failCount++;
293
+ failedImages.push(fileName);
294
+ await (0, import_koishi.sleep)(CONFIG.SYNC_INTERVAL_FAILED);
295
+ } else {
296
+ successCount++;
297
+ if (result.reason === "ignored" || result.reason === "no_change") {
298
+ skipCount++;
299
+ }
300
+ await (0, import_koishi.sleep)(CONFIG.SYNC_INTERVAL_SUCCESS);
301
+ }
302
+ }
303
+ if (failedImages.length > 0) {
304
+ console.log(
305
+ `
306
+ [SyncAllImg] 🔄 开始重试 ${failedImages.length} 个失败图片`
307
+ );
308
+ const stillFailed = [];
309
+ for (const fileName of failedImages) {
310
+ console.log(`
311
+ [SyncAllImg] 🔁 重试: ${fileName}`);
312
+ const result = await syncSingleImage(
313
+ sourceBot,
314
+ targetBot,
315
+ fileName,
316
+ config
317
+ );
318
+ if (result.success) {
319
+ successCount++;
320
+ failCount--;
321
+ console.log(`[SyncAllImg] ✅ 重试成功: ${fileName}`);
322
+ } else {
323
+ stillFailed.push(fileName);
324
+ console.log(`[SyncAllImg] ❌ 重试失败: ${fileName}`);
325
+ }
326
+ await (0, import_koishi.sleep)(CONFIG.SYNC_INTERVAL_SUCCESS);
327
+ }
328
+ if (stillFailed.length > 0) {
329
+ console.log(`
330
+ [SyncAllImg] ❌ 最终失败列表(需手动处理):`);
331
+ stillFailed.forEach(
332
+ (title, idx) => console.log(` ${idx + 1}. ${title}`)
333
+ );
334
+ } else {
335
+ console.log(`
336
+ [SyncAllImg] 🎉 所有失败图片重试成功!`);
337
+ }
338
+ }
339
+ console.log(`
340
+ [SyncAllImg] 📊 同步完成!`);
341
+ console.log(`├─ 总计:${imageList.length} 个图片`);
342
+ console.log(`├─ 成功:${successCount} 个(含跳过 ${skipCount} 个)`);
343
+ console.log(`└─ 失败:${failCount} 个`);
344
+ } catch (globalError) {
345
+ console.error(`[SyncAllImg] 💥 同步流程异常终止:`, globalError);
346
+ throw globalError;
347
+ }
348
+ }
349
+ __name(syncAllImages, "syncAllImages");
350
+
351
+ // src/sync/pageSync.ts
352
+ var CONFIG2 = {
353
+ IGNORED_PAGES: /* @__PURE__ */ new Set(["教程", "MediaWiki:Common.css"]),
354
+ SYNC_INTERVAL_SUCCESS: 500,
355
+ SYNC_INTERVAL_FAILED: 1e3,
356
+ NAMESPACE: 0,
357
+ BATCH_LIMIT: "max",
358
+ FILE_NAMESPACE_PREFIX: "File:",
359
+ DEFAULT_USER: "同步坤器人",
360
+ INCREMENTAL_USER: "定时同步"
361
+ };
362
+ async function syncSinglePage(oldSite, newSite, pageTitle, user) {
363
+ if (CONFIG2.IGNORED_PAGES.has(pageTitle)) {
364
+ console.log(`[syncSinglePage] 🚫 页面 ${pageTitle} 在忽略列表中,跳过`);
365
+ return { success: true, reason: "ignored" };
366
+ }
367
+ try {
368
+ console.log(`[syncSinglePage] 🚀 开始同步页面: ${pageTitle}`);
369
+ const [oldContent, newContent] = await Promise.all([
370
+ getAndProcessPageContent(oldSite, pageTitle),
371
+ getAndProcessPageContent(newSite, pageTitle)
372
+ ]);
373
+ if (oldContent === newContent) {
374
+ console.log(`[syncSinglePage] 🟡 页面 ${pageTitle} 内容未改变,跳过`);
375
+ return { success: true, reason: "no_change" };
376
+ }
377
+ await newSite.save(pageTitle, oldContent, `由:${user} 触发更改,此时同步`);
378
+ console.log(`[syncSinglePage] ✅ 页面 ${pageTitle} 同步成功`);
379
+ return { success: true, reason: "synced" };
380
+ } catch (error) {
381
+ const errMsg = error instanceof Error ? error.message : String(error);
382
+ console.error(`[syncSinglePage] ❌ 页面 ${pageTitle} 同步失败:`, errMsg);
383
+ return { success: false, reason: errMsg };
384
+ }
385
+ }
386
+ __name(syncSinglePage, "syncSinglePage");
387
+ async function getAllPages(site) {
388
+ console.log(
389
+ `[SyncAllPages] 📥 开始获取原站点所有页面(命名空间${CONFIG2.NAMESPACE})`
390
+ );
391
+ const allPages = [];
392
+ const queryGen = site.continuedQueryGen({
393
+ action: "query",
394
+ list: "allpages",
395
+ apnamespace: CONFIG2.NAMESPACE,
396
+ aplimit: CONFIG2.BATCH_LIMIT,
397
+ apdir: "ascending"
398
+ });
399
+ for await (const res of queryGen) {
400
+ const pageTitles = res.query?.allpages?.map((page) => page.title) || [];
401
+ allPages.push(...pageTitles);
402
+ console.log(`[SyncAllPages] 📄 已获取 ${allPages.length} 个页面`);
403
+ }
404
+ console.log(`[SyncAllPages] 📊 原站点总计获取到 ${allPages.length} 个页面`);
405
+ return allPages;
406
+ }
407
+ __name(getAllPages, "getAllPages");
408
+ async function processPageWithStats(oldSite, newSite, pageTitle, user, stats, failedPages) {
409
+ const syncResult = await syncSinglePage(oldSite, newSite, pageTitle, user);
410
+ if (!syncResult.success) {
411
+ stats.failCount++;
412
+ failedPages.push(pageTitle);
413
+ await (0, import_koishi2.sleep)(CONFIG2.SYNC_INTERVAL_FAILED);
414
+ } else {
415
+ stats.successCount++;
416
+ if (syncResult.reason === "ignored" || syncResult.reason === "no_change") {
417
+ stats.skipCount++;
418
+ }
419
+ await (0, import_koishi2.sleep)(CONFIG2.SYNC_INTERVAL_SUCCESS);
420
+ }
421
+ }
422
+ __name(processPageWithStats, "processPageWithStats");
423
+ function printProgress(current, total, pageTitle) {
424
+ const progress = (current / total * 100).toFixed(1);
425
+ const remaining = total - current;
426
+ console.log(
427
+ `
428
+ [SyncAllPages] 📈 进度 [${current}/${total}] (${progress}%) - 处理 ${pageTitle} | 剩余 ${remaining} 个`
429
+ );
430
+ }
431
+ __name(printProgress, "printProgress");
432
+ function printFinalReport(total, successCount, failCount, skipCount, stillFailed) {
433
+ console.log(`
434
+ [SyncAllPages] 📋 ===== 最终同步报告 =====`);
435
+ if (stillFailed.length > 0) {
436
+ console.log(`❌ 以下页面经过重试仍然失败,请手动检查:`);
437
+ stillFailed.forEach((title, idx) => {
438
+ console.log(` ${idx + 1}. ${title}`);
439
+ });
440
+ } else {
441
+ console.log(`🎉 所有页面同步成功(含重试)!`);
442
+ }
443
+ console.log(`
444
+ [SyncAllPages] 🎯 同步流程结束!`);
445
+ console.log(`├─ 总计:${total} 个页面`);
446
+ console.log(`├─ 成功:${successCount} 个(含跳过 ${skipCount} 个)`);
447
+ console.log(`└─ 失败:${failCount} 个`);
448
+ }
449
+ __name(printFinalReport, "printFinalReport");
450
+ async function syncPages(oldSite, newSite) {
451
+ try {
452
+ const oldPageList = await getAllPages(oldSite);
453
+ const total = oldPageList.length;
454
+ if (total === 0) {
455
+ console.log(`[SyncAllPages] 📭 原站点无页面可同步,结束`);
456
+ return;
457
+ }
458
+ const stats = { successCount: 0, failCount: 0, skipCount: 0 };
459
+ const failedPages = [];
460
+ console.log(`[SyncAllPages] 🚦 开始批量同步,总计 ${total} 个页面`);
461
+ for (let index = 0; index < total; index++) {
462
+ const pageTitle = oldPageList[index];
463
+ printProgress(index + 1, total, pageTitle);
464
+ await processPageWithStats(
465
+ oldSite,
466
+ newSite,
467
+ pageTitle,
468
+ CONFIG2.DEFAULT_USER,
469
+ stats,
470
+ failedPages
471
+ );
472
+ }
473
+ let stillFailed = [];
474
+ if (failedPages.length > 0) {
475
+ console.log(
476
+ `
477
+ [SyncAllPages] 🔄 ===== 开始重试 ${failedPages.length} 个失败页面 =====`
478
+ );
479
+ for (const pageTitle of failedPages) {
480
+ console.log(`
481
+ [SyncAllPages] 🔁 重试中: ${pageTitle}`);
482
+ const syncResult = await syncSinglePage(
483
+ oldSite,
484
+ newSite,
485
+ pageTitle,
486
+ CONFIG2.DEFAULT_USER
487
+ );
488
+ if (syncResult.success) {
489
+ stats.successCount++;
490
+ stats.failCount--;
491
+ if (syncResult.reason === "ignored" || syncResult.reason === "no_change") {
492
+ stats.skipCount++;
493
+ }
494
+ console.log(`[SyncAllPages] ✅ 页面 ${pageTitle} 重试成功`);
495
+ await (0, import_koishi2.sleep)(CONFIG2.SYNC_INTERVAL_SUCCESS);
496
+ } else {
497
+ stillFailed.push(pageTitle);
498
+ console.log(`[SyncAllPages] ❌ 页面 ${pageTitle} 再次失败`);
499
+ await (0, import_koishi2.sleep)(CONFIG2.SYNC_INTERVAL_FAILED);
500
+ }
501
+ }
502
+ }
503
+ printFinalReport(
504
+ total,
505
+ stats.successCount,
506
+ stats.failCount,
507
+ stats.skipCount,
508
+ stillFailed
509
+ );
510
+ } catch (globalError) {
511
+ console.error(`[SyncAllPages] 💥 批量同步流程异常终止:`, globalError);
512
+ throw globalError;
513
+ }
514
+ }
515
+ __name(syncPages, "syncPages");
516
+ async function incrementalUpdate(oldSite, newSite, config) {
517
+ try {
518
+ const now = /* @__PURE__ */ new Date();
519
+ const threeHoursAgo = new Date(now.getTime() - 3 * 60 * 60 * 1e3);
520
+ console.log(
521
+ `[增量更新流程] ⏰ 开始处理 ${threeHoursAgo.toISOString()} 到 ${now.toISOString()} 的更新...`
522
+ );
523
+ const queryGen = oldSite.continuedQueryGen({
524
+ action: "query",
525
+ list: "recentchanges",
526
+ rcstart: now.toISOString(),
527
+ rcend: threeHoursAgo.toISOString(),
528
+ rcdir: "older",
529
+ rcprop: "user|comment|title|timestamp"
530
+ });
531
+ const processedTitles = /* @__PURE__ */ new Set();
532
+ let totalProcessed = 0;
533
+ let totalSkipped = 0;
534
+ for await (const res of queryGen) {
535
+ const pages = res.query?.recentchanges || [];
536
+ for (const page of pages) {
537
+ const title = page.title;
538
+ if (processedTitles.has(title)) {
539
+ console.log(`[增量更新流程] ⏭️ 已经处理过 ${title}, 跳过`);
540
+ totalSkipped++;
541
+ continue;
542
+ }
543
+ if (CONFIG2.IGNORED_PAGES.has(title)) {
544
+ console.log(
545
+ `[增量更新流程] 🚫 ${title} 在无需处理的页面列表中, 跳过`
546
+ );
547
+ processedTitles.add(title);
548
+ totalSkipped++;
549
+ continue;
550
+ }
551
+ processedTitles.add(title);
552
+ totalProcessed++;
553
+ try {
554
+ if (title.startsWith(CONFIG2.FILE_NAMESPACE_PREFIX)) {
555
+ const fileName = title.replace(CONFIG2.FILE_NAMESPACE_PREFIX, "");
556
+ console.log(
557
+ `[增量更新流程] 🖼️ 检查到图片: ${title},正在尝试转存`
558
+ );
559
+ await syncSingleImage(oldSite, newSite, fileName, config);
560
+ } else {
561
+ await syncSinglePage(
562
+ oldSite,
563
+ newSite,
564
+ title,
565
+ CONFIG2.INCREMENTAL_USER
566
+ );
567
+ }
568
+ await (0, import_koishi2.sleep)(CONFIG2.SYNC_INTERVAL_SUCCESS);
569
+ } catch (error) {
570
+ const errMsg = error instanceof Error ? error.message : String(error);
571
+ console.error(`[增量更新流程] ❌ 处理 ${title} 时出错:`, errMsg);
572
+ await (0, import_koishi2.sleep)(CONFIG2.SYNC_INTERVAL_FAILED);
573
+ }
574
+ }
575
+ }
576
+ console.log(
577
+ `[增量更新流程] ✅ 增量更新完成!处理: ${totalProcessed}, 跳过: ${totalSkipped}`
578
+ );
579
+ } catch (globalError) {
580
+ console.error(`[增量更新流程] 💥 增量更新流程异常终止:`, globalError);
581
+ throw globalError;
582
+ }
583
+ }
584
+ __name(incrementalUpdate, "incrementalUpdate");
585
+
586
+ // src/sync/moduleSync.ts
587
+ var import_koishi3 = require("koishi");
588
+ var CONFIG3 = {
589
+ MODLE_NAMESPACE: 828,
590
+ // 模块命名空间 (注意:这里原代码拼写为 MODLE,保留原样)
591
+ IGNORED_MODULES: [],
592
+ // 忽略的模块列表
593
+ SYNC_INTERVAL_SUCCESS: 500,
594
+ // 同步成功后等待时间(毫秒)
595
+ SYNC_INTERVAL_FAILED: 1e3
596
+ // 同步失败后等待时间(毫秒)
597
+ };
598
+ async function syncSingleModule(oldSite, newSite, moduleTitle, user) {
599
+ if (CONFIG3.IGNORED_MODULES.includes(moduleTitle)) {
600
+ console.log(`[SyncModule] 🚫 模块 ${moduleTitle} 在忽略列表中,跳过`);
601
+ return { success: true, reason: "ignored" };
602
+ }
603
+ try {
604
+ console.log(`[SyncModule] 🔍 开始获取模块 ${moduleTitle} 的内容`);
605
+ const [oldContent, newContent] = await Promise.all([
606
+ getAndProcessPageContent(oldSite, moduleTitle),
607
+ getAndProcessPageContent(newSite, moduleTitle)
608
+ ]);
609
+ if (oldContent === newContent) {
610
+ console.log(`[SyncModule] 🟡 模块 ${moduleTitle} 内容未改变,跳过`);
611
+ return { success: true, reason: "no_change" };
612
+ }
613
+ await newSite.save(
614
+ moduleTitle,
615
+ oldContent,
616
+ `由:${user || "同步坤器人手动"} 触发更改,此时同步`
617
+ );
618
+ console.log(`[SyncModule] ✅ 模块 ${moduleTitle} 同步成功`);
619
+ return { success: true, reason: "synced" };
620
+ } catch (error) {
621
+ const errMsg = error.message || String(error);
622
+ console.error(`[SyncModule] ❌ 模块 ${moduleTitle} 同步失败:`, errMsg);
623
+ return { success: false, reason: errMsg };
624
+ }
625
+ }
626
+ __name(syncSingleModule, "syncSingleModule");
627
+ async function getAllModules(site) {
628
+ console.log(
629
+ `[SyncAllModules] 📥 开始获取原站点所有模块(命名空间${CONFIG3.MODLE_NAMESPACE})`
630
+ );
631
+ const allModules = [];
632
+ const queryGen = site.continuedQueryGen({
633
+ action: "query",
634
+ list: "allpages",
635
+ apnamespace: CONFIG3.MODLE_NAMESPACE,
636
+ // 模块命名空间
637
+ aplimit: "max",
638
+ apdir: "ascending"
639
+ });
640
+ for await (const res of queryGen) {
641
+ const moduleTitles = res.query?.allpages?.map((page) => page.title) || [];
642
+ allModules.push(...moduleTitles);
643
+ console.log(`[SyncAllModules] 📄 已获取 ${allModules.length} 个模块`);
644
+ }
645
+ console.log(
646
+ `[SyncAllModules] 📊 原站点总计获取到 ${allModules.length} 个模块`
647
+ );
648
+ return allModules;
649
+ }
650
+ __name(getAllModules, "getAllModules");
651
+ async function syncModules(oldSite, newSite) {
652
+ try {
653
+ const oldModuleList = await getAllModules(oldSite);
654
+ const total = oldModuleList.length;
655
+ if (total === 0) {
656
+ console.log(`[SyncAllModules] 📭 原站点无模块可同步,结束`);
657
+ return;
658
+ }
659
+ let successCount = 0;
660
+ let failCount = 0;
661
+ let skipCount = 0;
662
+ const failedModules = [];
663
+ console.log(`[SyncAllModules] 🚦 开始批量同步,总计 ${total} 个模块`);
664
+ for (let index = 0; index < total; index++) {
665
+ const moduleTitle = oldModuleList[index];
666
+ const current = index + 1;
667
+ const remaining = total - current;
668
+ const progress = (current / total * 100).toFixed(1);
669
+ console.log(
670
+ `
671
+ [SyncAllModules] 📈 进度 [${current}/${total}] (${progress}%) - 处理 ${moduleTitle} | 剩余 ${remaining} 个`
672
+ );
673
+ const syncResult = await syncSingleModule(
674
+ oldSite,
675
+ newSite,
676
+ moduleTitle,
677
+ "同步坤器人"
678
+ );
679
+ if (!syncResult.success) {
680
+ failCount++;
681
+ failedModules.push(moduleTitle);
682
+ await (0, import_koishi3.sleep)(CONFIG3.SYNC_INTERVAL_FAILED);
683
+ } else {
684
+ successCount++;
685
+ if (syncResult.reason === "ignored" || syncResult.reason === "no_change") {
686
+ skipCount++;
687
+ }
688
+ await (0, import_koishi3.sleep)(CONFIG3.SYNC_INTERVAL_SUCCESS);
689
+ }
690
+ }
691
+ if (failedModules.length > 0) {
692
+ console.log(
693
+ `
694
+ [SyncAllModules] 🔄 ===== 开始重试 ${failedModules.length} 个失败模块 =====`
695
+ );
696
+ const stillFailed = [];
697
+ for (const moduleTitle of failedModules) {
698
+ console.log(`
699
+ [SyncAllModules] 🔁 重试中: ${moduleTitle}`);
700
+ const syncResult = await syncSingleModule(
701
+ oldSite,
702
+ newSite,
703
+ moduleTitle,
704
+ "同步坤器人"
705
+ );
706
+ if (syncResult.success) {
707
+ successCount++;
708
+ failCount--;
709
+ if (syncResult.reason === "ignored" || syncResult.reason === "no_change") {
710
+ skipCount++;
711
+ }
712
+ console.log(`[SyncAllModules] ✅ 模块 ${moduleTitle} 重试成功`);
713
+ await (0, import_koishi3.sleep)(CONFIG3.SYNC_INTERVAL_SUCCESS);
714
+ } else {
715
+ stillFailed.push(moduleTitle);
716
+ console.log(`[SyncAllModules] ❌ 模块 ${moduleTitle} 再次失败`);
717
+ await (0, import_koishi3.sleep)(CONFIG3.SYNC_INTERVAL_FAILED);
718
+ }
719
+ }
720
+ console.log(`
721
+ [SyncAllModules] 📋 ===== 最终同步报告 =====`);
722
+ if (stillFailed.length > 0) {
723
+ console.log(`❌ 以下模块经过重试仍然失败,请手动检查:`);
724
+ stillFailed.forEach((title, idx) => {
725
+ console.log(` ${idx + 1}. ${title}`);
726
+ });
727
+ } else {
728
+ console.log(`🎉 所有模块同步成功(含重试)!`);
729
+ }
730
+ }
731
+ console.log(`
732
+ [SyncAllModules] 🎯 同步完成!`);
733
+ console.log(`├─ 总计:${total} 个模块`);
734
+ console.log(`├─ 成功:${successCount} 个(含跳过 ${skipCount} 个)`);
735
+ console.log(`└─ 失败:${failCount} 个`);
736
+ } catch (error) {
737
+ console.error(`[SyncAllModules] 💥 批量同步流程异常终止:`, error);
738
+ throw error;
739
+ }
740
+ }
741
+ __name(syncModules, "syncModules");
742
+
743
+ // src/index.ts
744
+ var name = "oni-sync-bot";
745
+ var inject = ["console", "database", "server", "cron"];
746
+ var Config = import_koishi4.Schema.object({
747
+ ggUsername: import_koishi4.Schema.string().description("WIKIGG 用户名").default("${{ env.ggUsername }}"),
748
+ ggPassword: import_koishi4.Schema.string().description("WIKIGG 密码").default("${{ env.ggPassword }}"),
749
+ huijiUsername: import_koishi4.Schema.string().description("灰机wiki 用户名").default("${{ env.huijiUsername }}"),
750
+ huijiPassword: import_koishi4.Schema.string().description("灰机wiki 密码").default("${{ env.huijiPassword }}"),
751
+ huijiUAKey: import_koishi4.Schema.string().description("灰机wiki UAKey").default("${{ env.huijiUAKey }}"),
752
+ domain: import_koishi4.Schema.string().description("你的短链域名(必填,如:klei.vip)").default("klei.vip"),
753
+ main_site: import_koishi4.Schema.string().description("主站域名(必填,如:oxygennotincluded.wiki.gg)").default("oxygennotincluded.wiki.gg/zh"),
754
+ mirror_site: import_koishi4.Schema.string().description("镜像站域名(必填,如:wiki.biligame.com)").default("wiki.biligame.com/oni")
755
+ });
756
+ function apply(ctx, config) {
757
+ const logger = ctx.logger("oni-sync-bot");
758
+ let ggbot;
759
+ let huijibot;
760
+ ctx.inject(["console"], (ctx2) => {
761
+ ctx2.console.addEntry({
762
+ dev: (0, import_path.resolve)(__dirname, "../client/index.ts"),
763
+ prod: (0, import_path.resolve)(__dirname, "../dist")
764
+ });
765
+ });
766
+ ctx.model.extend("wikipages", {
767
+ id: "integer",
768
+ title: "string",
769
+ pinyin_full: "string",
770
+ // 全拼
771
+ pinyin_first: "string"
772
+ // 首字母
773
+ });
774
+ ctx.server.get("/gg/:id", async (router) => {
775
+ const pageId = Number(router.params.id);
776
+ if (isNaN(pageId)) return router.body = "❌ 无效的页面ID,必须为数字!";
777
+ const [page] = await ctx.database.get("wikipages", { id: pageId });
778
+ if (!page)
779
+ return router.body = `❌ 未找到ID为【${pageId}】的页面,请联系管理员更新缓存!`;
780
+ const targetUrl = `https://${config.main_site}/${encodeURIComponent(
781
+ page.title
782
+ )}?variant=zh`;
783
+ router.redirect(targetUrl);
784
+ });
785
+ ctx.server.get("/bw/:id", async (router) => {
786
+ const pageId = Number(router.params.id);
787
+ if (isNaN(pageId)) return router.body = "❌ 无效的页面ID,必须为数字!";
788
+ const [page] = await ctx.database.get("wikipages", { id: pageId });
789
+ if (!page)
790
+ return router.body = `❌ 未找到ID为【${pageId}】的页面,请联系管理员更新缓存!`;
791
+ const targetUrl = `https://${config.mirror_site}/${encodeURIComponent(
792
+ page.title
793
+ )}`;
794
+ router.redirect(targetUrl);
795
+ });
796
+ ctx.on("ready", async () => {
797
+ logger.info("初始化中...");
798
+ const sitesConfig = getSitesConfig(config);
799
+ ggbot = await login(sitesConfig.gg);
800
+ huijibot = await login(sitesConfig.huiji);
801
+ if (ggbot.login && huijibot.login) {
802
+ logger.info("登录成功,插件已准备就绪");
803
+ } else {
804
+ logger.error("登录失败,请检查配置");
805
+ }
806
+ ctx.cron("15 * * * *", async () => {
807
+ await incrementalUpdate(ggbot, huijibot, config);
808
+ });
809
+ ctx.cron("30 8 * * 4", async () => {
810
+ await syncPages(ggbot, huijibot).then(() => {
811
+ logger.info("自动任务:尝试同步所有页面,从 WIKIGG 到 灰机wiki");
812
+ }).catch((err) => {
813
+ logger.error(`同步所有页面失败,错误信息:${err}`);
814
+ });
815
+ });
816
+ ctx.cron("30 8 * * 3", async () => {
817
+ await syncAllImages(ggbot, huijibot, config).then(() => {
818
+ logger.info("自动任务:尝试同步所有图片,从 WIKIGG 到 灰机wiki");
819
+ }).catch((err) => {
820
+ logger.error(`同步所有图片失败,错误信息:${err}`);
821
+ });
822
+ });
823
+ });
824
+ ctx.command("sync <pageTitle:string>", "同步指定页面", { authority: 2 }).action(async ({ session }, pageTitle) => {
825
+ await syncSinglePage(ggbot, huijibot, pageTitle, "sync-bot").then(() => {
826
+ session.send(
827
+ `✅ 已尝试同步页面:${pageTitle},从 WIKIGG 到 灰机wiki`
828
+ );
829
+ }).catch((err) => {
830
+ session.send(`❌ 同步页面失败:${pageTitle},错误信息:${err}`);
831
+ });
832
+ });
833
+ ctx.command("sync.incrementalUpdate", "同步所有页面", { authority: 2 }).alias("增量更新").action(async ({ session }) => {
834
+ session.send(`🚀 开始同步所有页面,任务耗时可能较长,请耐心等待...`);
835
+ await incrementalUpdate(ggbot, huijibot, config).then(() => {
836
+ session.send(
837
+ `✅ 已尝试获取三小时前的编辑并同步,从 WIKIGG 到 灰机wiki`
838
+ );
839
+ }).catch((err) => {
840
+ session.send(`❌ 同步所有页面失败,错误信息:${err}`);
841
+ });
842
+ });
843
+ ctx.command("sync.allpages", "同步所有页面", { authority: 2 }).action(async ({ session }) => {
844
+ session.send(`🚀 开始同步所有页面,任务耗时较长,请耐心等待...`);
845
+ await syncPages(ggbot, huijibot).then(() => {
846
+ session.send(`✅ 已尝试同步所有页面,从 WIKIGG 到 灰机wiki`);
847
+ }).catch((err) => {
848
+ session.send(`❌ 同步所有页面失败,错误信息:${err}`);
849
+ });
850
+ });
851
+ ctx.command("sync.module <moduleTitle:string>", "同步指定模块", {
852
+ authority: 2
853
+ }).action(async ({ session }, moduleTitle) => {
854
+ await syncSingleModule(ggbot, huijibot, moduleTitle, "sync-bot").then(() => {
855
+ session.send(
856
+ `✅ 已尝试同步模块:${moduleTitle},从 WIKIGG 到 灰机wiki`
857
+ );
858
+ }).catch((err) => {
859
+ session.send(`❌ 同步模块失败:${moduleTitle},错误信息:${err}`);
860
+ });
861
+ });
862
+ ctx.command("sync.allmodules", "同步所有模块", { authority: 2 }).action(async ({ session }) => {
863
+ session.send(`🚀 开始同步所有模块,任务耗时较长,请耐心等待...`);
864
+ await syncModules(ggbot, huijibot).then(() => {
865
+ session.send(`✅ 已尝试同步所有模块,从 WIKIGG 到 灰机wiki`);
866
+ }).catch((err) => {
867
+ session.send(`❌ 同步所有模块失败,错误信息:${err}`);
868
+ });
869
+ });
870
+ ctx.command("sync.img <imgTitle:string>", "同步指定图片", { authority: 2 }).action(async ({ session }, imgTitle) => {
871
+ await syncSingleImage(
872
+ ggbot,
873
+ huijibot,
874
+ `${imgTitle.startsWith("File:") ? "" : "File:"}${imgTitle}`,
875
+ config
876
+ ).then(() => {
877
+ session.send(`✅ 已尝试同步图片:${imgTitle}`);
878
+ }).catch((err) => {
879
+ session.send(`❌ 同步图片失败:${imgTitle},错误信息:${err}`);
880
+ });
881
+ });
882
+ ctx.command("sync.allimgs", "同步所有图片", { authority: 2 }).action(async ({ session }) => {
883
+ session.send(`🚀 开始同步所有图片,任务耗时较长,请耐心等待...`);
884
+ await syncAllImages(ggbot, huijibot, config).then(() => {
885
+ session.send(`✅ 已尝试同步所有图片,从 WIKIGG 到 灰机wiki`);
886
+ }).catch((err) => {
887
+ session.send(`❌ 同步所有图片失败,错误信息:${err}`);
888
+ });
889
+ });
890
+ ctx.command("x <itemName>", "查询缺氧中文wiki,精准匹配+拼音模糊匹配").alias("/查wiki").action(async ({ session }, itemName = "") => {
891
+ const queryKey = itemName.trim().toLowerCase();
892
+ if (queryKey === "")
893
+ return `以下是使用说明:
894
+ 原站点: https://${config.domain}/gg/88888888
895
+
896
+ 镜像站: https://${config.domain}/bw/88888888`;
897
+ const { pinyin_full: queryPinyinFull, pinyin_first: queryPinyinFirst } = generatePinyinInfo(queryKey);
898
+ const preciseTitleRes = await ctx.database.get("wikipages", {
899
+ title: queryKey
900
+ });
901
+ if (preciseTitleRes.length > 0) {
902
+ const { id: id2 } = preciseTitleRes[0];
903
+ return `✅ 精准匹配成功
904
+ 原站点: https://${config.domain}/gg/${id2}
905
+
906
+ 镜像站: https://${config.domain}/bw/${id2}`;
907
+ }
908
+ const preciseFullPinyinRes = await ctx.database.get("wikipages", {
909
+ pinyin_full: queryKey
910
+ });
911
+ if (preciseFullPinyinRes.length > 0) {
912
+ const { id: id2, title } = preciseFullPinyinRes[0];
913
+ return `✅ 拼音精准匹配成功(${queryKey} → ${title})
914
+ 原站点: https://${config.domain}/gg/${id2}
915
+
916
+ 镜像站: https://${config.domain}/bw/${id2}`;
917
+ }
918
+ const preciseFirstPinyinRes = await ctx.database.get("wikipages", {
919
+ pinyin_first: queryKey
920
+ });
921
+ if (preciseFirstPinyinRes.length > 0) {
922
+ const { id: id2, title } = preciseFirstPinyinRes[0];
923
+ return `✅ 首字母精准匹配成功(${queryKey} → ${title})
924
+ 原站点: https://${config.domain}/gg/${id2}
925
+
926
+ 镜像站: https://${config.domain}/bw/${id2}`;
927
+ }
928
+ const allPages = await ctx.database.get("wikipages", {});
929
+ if (allPages.length === 0) {
930
+ return `❌ 本地缓存为空,请联系管理员执行【update】指令更新缓存!`;
931
+ }
932
+ const matchResult = [];
933
+ allPages.forEach((page) => {
934
+ const { title, pinyin_full, pinyin_first } = page;
935
+ let score = 0;
936
+ if (title.includes(queryKey)) score += 10;
937
+ if (pinyin_full.startsWith(queryPinyinFull)) score += 9;
938
+ if (pinyin_full.includes(queryPinyinFull)) score += 8;
939
+ if (pinyin_first.includes(queryPinyinFirst)) score += 6;
940
+ if (queryPinyinFull.includes(
941
+ pinyin_full.substring(
942
+ 0,
943
+ Math.min(pinyin_full.length, queryPinyinFull.length)
944
+ )
945
+ ))
946
+ score += 5;
947
+ if (score > 0) {
948
+ matchResult.push({ id: page.id, title, score });
949
+ }
950
+ });
951
+ if (matchResult.length === 0) {
952
+ return `❌ 未找到【${queryKey}】相关内容,请按游戏内标准名称重新查询!`;
953
+ }
954
+ const sortedResult = matchResult.sort((a, b) => {
955
+ if (b.score !== a.score) return b.score - a.score;
956
+ return a.title.length - b.title.length;
957
+ });
958
+ const uniqueResult = Array.from(
959
+ new Map(sortedResult.map((item) => [item.title, item])).values()
960
+ ).slice(0, 5);
961
+ const resultCount = uniqueResult.length;
962
+ let replyMsg = `🔍 未找到精准匹配,为你找到【 ${resultCount} 】个相似结果,请输入序号选择(10秒内有效):
963
+ `;
964
+ uniqueResult.forEach((item, index) => {
965
+ replyMsg += `${index + 1}. ${item.title}
966
+ `;
967
+ });
968
+ replyMsg += `
969
+ ❗️ 提示:超时将静默结束,无任何回应`;
970
+ await session.send(replyMsg);
971
+ const userInput = await session.prompt(15e3);
972
+ if (!userInput) return;
973
+ const selectNum = parseInt(userInput.trim());
974
+ if (isNaN(selectNum) || selectNum < 1 || selectNum > resultCount) {
975
+ return `❌ 输入无效!请输入 1-${resultCount} 之间的数字序号`;
976
+ }
977
+ const { id } = uniqueResult[selectNum - 1];
978
+ return `✅ 选择成功
979
+ 原站点: https://${config.domain}/gg/${id}
980
+
981
+ 镜像站: https://${config.domain}/bw/${id}`;
982
+ });
983
+ ctx.command("update", "更新本地页面缓存(主站)", { authority: 2 }).action(async ({ session }) => {
984
+ await session.execute("update.status");
985
+ try {
986
+ const res = await ggbot.request({
987
+ action: "query",
988
+ list: "allpages",
989
+ format: "json",
990
+ aplimit: "max"
991
+ });
992
+ logger.info("主站页面查询成功");
993
+ const pages = res.query.allpages || [];
994
+ const pageData = pages.map((page) => {
995
+ const { pinyin_full, pinyin_first } = generatePinyinInfo(page.title);
996
+ return {
997
+ id: page.pageid,
998
+ title: page.title,
999
+ pinyin_full,
1000
+ pinyin_first
1001
+ };
1002
+ });
1003
+ if (pageData.length > 0) {
1004
+ await ctx.database.upsert("wikipages", pageData);
1005
+ }
1006
+ session.send(`✅ 检索到 ${pages.length} 个页面,已更新至数据库`);
1007
+ logger.info(`检索到 ${pages.length} 个页面,已更新至数据库`);
1008
+ } catch (err) {
1009
+ logger.error("主站缓存更新失败", err);
1010
+ session.send("❌ 主站缓存更新失败,请联系管理员查看日志");
1011
+ }
1012
+ });
1013
+ ctx.command("update.delete", "删除本地页面缓存", { authority: 4 }).action(async ({ session }) => {
1014
+ try {
1015
+ const count = await ctx.database.remove("wikipages", {});
1016
+ session.send(`✅ 已删除 ${count.removed} 条本地缓存`);
1017
+ logger.info(`已删除 ${count.removed} 条本地缓存`);
1018
+ } catch (err) {
1019
+ logger.error("删除缓存失败", err);
1020
+ session.send("❌ 删除缓存失败,请联系管理员查看日志");
1021
+ }
1022
+ });
1023
+ ctx.command("update.status", "查询本地缓存数量", { authority: 1 }).action(async ({ session }) => {
1024
+ try {
1025
+ const pages = await ctx.database.get("wikipages", {});
1026
+ session.send(`📊 数据库中缓存了 ${pages.length} 条页面`);
1027
+ logger.info(`数据库中缓存了 ${pages.length} 条页面`);
1028
+ } catch (err) {
1029
+ logger.error("查询缓存状态失败", err);
1030
+ session.send("❌ 查询缓存状态失败,请联系管理员查看日志");
1031
+ }
1032
+ });
1033
+ ctx.command("redirect <pageName> <targetPageName>", "添加原站点重定向", {
1034
+ authority: 2
1035
+ }).alias("重定向").action(async ({ session }, pageName, targetPageName) => {
1036
+ if (!pageName || !targetPageName) {
1037
+ return "❌ 参数错误!用法:redirect <原页面名> <目标页面名>";
1038
+ }
1039
+ try {
1040
+ await ggbot.create(
1041
+ pageName,
1042
+ `#REDIRECT [[${targetPageName}]]`,
1043
+ "来自qq机器人的添加重定向页面请求"
1044
+ );
1045
+ logger.info(`已为 ${pageName} 添加重定向至 ${targetPageName}`);
1046
+ session.send(`✅ 已尝试添加重定向 ${pageName} -> ${targetPageName}`);
1047
+ await session.execute(`update`);
1048
+ } catch (err) {
1049
+ logger.error(`添加重定向 ${pageName} -> ${targetPageName} 失败`, err);
1050
+ session.send(`❌ 添加重定向失败,请联系管理员查看日志`);
1051
+ }
1052
+ });
1053
+ }
1054
+ __name(apply, "apply");
1055
+ // Annotate the CommonJS export names for ESM import in node:
1056
+ 0 && (module.exports = {
1057
+ Config,
1058
+ apply,
1059
+ inject,
1060
+ name
1061
+ });
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.0.1",
4
+ "version": "0.0.2",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [
@@ -23,6 +23,7 @@
23
23
  },
24
24
  "dependencies": {
25
25
  "@types/node-fetch": "^2.6.13",
26
+ "form-data": "^4.0.5",
26
27
  "koishi-plugin-cron": "^3.1.0",
27
28
  "mwn": "^3.0.1",
28
29
  "node-fetch": "^3.3.2",