koishi-plugin-forward-hime 1.4.0 → 1.4.1-alpha.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/config.d.ts CHANGED
@@ -17,6 +17,7 @@ export interface ConfigSet {
17
17
  RequestTimeoutSec: number;
18
18
  MaxFileSizeMB: number;
19
19
  };
20
+ ForwardTimeoutSec: number;
20
21
  CacheTimeout: number;
21
22
  ForwardGroups: {
22
23
  Nodes: ForwardNode[];
@@ -9,6 +9,7 @@ export declare function defaultDecorator({ head, content }: ForwardMsg): Element
9
9
  export declare function defaultMiddleware(session: Session): ForwardMsg;
10
10
  export declare function MsgMiddlewareCache(session: Session): Promise<void>;
11
11
  export declare function MsgDecorator(session: Session, node: ForwardNode): Promise<any>;
12
+ export declare function MsgDecoratorNoRelay(session: Session, node: ForwardNode): Promise<any>;
12
13
  export declare function MsgDecoratorFallback(session: Session, node: ForwardNode): Promise<Element[]>;
13
14
  export declare function MsgDecoratorFallbackReason(session: Session, node: ForwardNode, reason: string): Promise<Element[]>;
14
15
  export {};
package/lib/index.js CHANGED
@@ -70,8 +70,8 @@ async function msgCacheFindByUUID(uuid) {
70
70
  if (cacheNotEnabled()) {
71
71
  return;
72
72
  }
73
- let caches = await ctx.cache.entries("msgCache");
74
- let result = [];
73
+ const caches = await ctx.cache.entries("msgCache");
74
+ const result = [];
75
75
  for await (const item of caches) {
76
76
  if (item[1].uuid === uuid) {
77
77
  result.push(item);
@@ -83,7 +83,7 @@ async function msgCacheGetLocalIDByUUID(node, uuid) {
83
83
  if (cacheNotEnabled()) {
84
84
  return;
85
85
  }
86
- let caches = await ctx.cache.entries("msgCache");
86
+ const caches = await ctx.cache.entries("msgCache");
87
87
  for await (const item of caches) {
88
88
  if (item[1].uuid === uuid && item[1].platform === node.Platform && item[1].guild === node.Guild && item[1].bot === node.BotID) {
89
89
  return item[1].msgid;
@@ -103,10 +103,11 @@ var createConfig = () => import_koishi2.Schema.intersect([
103
103
  Newline: import_koishi2.Schema.boolean().description("\u662F\u5426\u6362\u884C").default(true)
104
104
  }).description("\u9ED8\u8BA4\u88C5\u9970\u5668"),
105
105
  DefaultFallbackMsgPrefix: import_koishi2.Schema.string().description("\u9ED8\u8BA4Fallback\u6D88\u606F\u524D\u7F00").default("[\u6D88\u606F\u964D\u7EA7] "),
106
+ ForwardTimeoutSec: import_koishi2.Schema.number().description("\u8F6C\u53D1\u6162\u901F\u544A\u8B66\u9608\u503C\uFF08\u79D2\uFF09- \u8D85\u8FC7\u8BE5\u65F6\u95F4\u4ECD\u7B49\u5F85\u53D1\u9001\u7ED3\u679C\uFF0C\u907F\u514D\u91CD\u590D\u6D88\u606F").default(60).min(5).max(120),
106
107
  MediaRelay: import_koishi2.Schema.object({
107
108
  Enabled: import_koishi2.Schema.boolean().description("\u542F\u7528\u5A92\u4F53\u4E2D\u8F6C\uFF08\u4E0B\u8F7D\u540E\u518D\u8F6C\u53D1\uFF09").default(true),
108
109
  CacheMinutes: import_koishi2.Schema.number().description("\u5A92\u4F53\u6682\u5B58\u65F6\u95F4\uFF08\u5206\u949F\uFF09").default(10).min(1).max(180),
109
- RequestTimeoutSec: import_koishi2.Schema.number().description("\u5A92\u4F53\u4E0B\u8F7D\u8D85\u65F6\u65F6\u95F4\uFF08\u79D2\uFF09").default(15).min(3).max(120),
110
+ RequestTimeoutSec: import_koishi2.Schema.number().description("\u5A92\u4F53\u4E0B\u8F7D\u8D85\u65F6\u65F6\u95F4\uFF08\u79D2\uFF09").default(30).min(3).max(120),
110
111
  MaxFileSizeMB: import_koishi2.Schema.number().description("\u5141\u8BB8\u4E2D\u8F6C\u7684\u5A92\u4F53\u6700\u5927\u5927\u5C0F\uFF08MB\uFF09").default(20).min(1).max(200)
111
112
  }).description("\u5A92\u4F53\u4E2D\u8F6C")
112
113
  }).description("\u8F6C\u53D1\u683C\u5F0F"),
@@ -164,7 +165,7 @@ var kook_default = {
164
165
  // src/decorators/onebot.ts
165
166
  var import_koishi4 = require("koishi");
166
167
  function cqJsonTranslator(content, elem) {
167
- let json = JSON.parse(elem.attrs.data);
168
+ const json = JSON.parse(elem.attrs.data);
168
169
  let hit = 0;
169
170
  if (json) {
170
171
  if (json.prompt) {
@@ -201,10 +202,10 @@ function getNameFromQQat(elem) {
201
202
  }
202
203
  function Middleware(session) {
203
204
  const platform = guessPlatform(session);
204
- let sessionTemp = session;
205
+ const sessionTemp = session;
205
206
  sessionTemp.platform = platform;
206
- let head = defaultMiddleware(sessionTemp).head;
207
- let newContent = [];
207
+ const head = defaultMiddleware(sessionTemp).head;
208
+ const newContent = [];
208
209
  if (platform === "QQ") {
209
210
  IdNameCache.set(session.userId, session.username);
210
211
  for (const key in session.elements) {
@@ -460,7 +461,7 @@ async function MessageSendWithDecorator(ctx2, node, session, Deco) {
460
461
  const uuid = session.channelId + ":" + session.messageId;
461
462
  const content = await Deco(session, node);
462
463
  await ctx2.bots[`${node.Platform}:${node.BotID}`].sendMessage(node.Guild, content).then((res) => {
463
- let mc = {
464
+ const mc = {
464
465
  platform: node.Platform,
465
466
  bot: node.BotID,
466
467
  guild: node.Guild,
@@ -487,26 +488,56 @@ function sessionTypeArray(session) {
487
488
  });
488
489
  return contentTypes;
489
490
  }
490
- async function MessageForward(ctx2, node, session) {
491
+ async function MessageForward(ctx2, node, session, timeoutSec, relayEnabled) {
491
492
  if (!botExistsCheck(ctx2, node)) {
492
493
  return;
493
494
  }
494
- MessageSendWithDecorator(ctx2, node, session, MsgDecorator).catch((error) => {
495
- logger.error(
496
- `ERROR:<MessageSend ${node.Platform}> ctx=${ctx2} ${sessionTypeArray(session)} ${error}`
497
- );
498
- const readableReason = error instanceof MediaRelayError ? `[\u8F6C\u53D1\u5931\u8D25] ${error.userMessage} ` : void 0;
499
- const fallbackDeco = readableReason ? (fallbackSession, fallbackNode) => MsgDecoratorFallbackReason(
500
- fallbackSession,
501
- fallbackNode,
502
- readableReason
503
- ) : MsgDecoratorFallback;
504
- MessageSendWithDecorator(ctx2, node, session, fallbackDeco).catch((error2) => {
495
+ const timeoutMs = (timeoutSec ?? 30) * 1e3;
496
+ const deco = relayEnabled !== false ? MsgDecorator : MsgDecoratorNoRelay;
497
+ function warnIfSlow(promise) {
498
+ const timer = setTimeout(() => {
499
+ logger.warn(
500
+ `[MessageForward] still pending after ${timeoutSec ?? 30}s; awaiting result to prevent duplicate delivery: ${node.Platform}`
501
+ );
502
+ }, timeoutMs);
503
+ return promise.finally(() => clearTimeout(timer));
504
+ }
505
+ function sendDegraded(reason) {
506
+ const fallbackDeco = reason ? (fallbackSession, fallbackNode) => MsgDecoratorFallbackReason(fallbackSession, fallbackNode, reason) : MsgDecoratorFallback;
507
+ MessageSendWithDecorator(ctx2, node, session, fallbackDeco).catch((error) => {
505
508
  logger.error(
506
- `ERROR:<MessageSendFallback ${node.Platform}> ctx=${ctx2} ${sessionTypeArray(session)} ${error2}`
509
+ `ERROR:<MessageSendFallback ${node.Platform}> ctx=${ctx2} ${sessionTypeArray(session)} ${error}`
507
510
  );
508
511
  });
509
- });
512
+ }
513
+ function reasonFromError(error) {
514
+ if (error instanceof MediaRelayError) {
515
+ return `[\u8F6C\u53D1\u5931\u8D25] ${error.userMessage} `;
516
+ }
517
+ return void 0;
518
+ }
519
+ warnIfSlow(MessageSendWithDecorator(ctx2, node, session, deco)).catch(
520
+ (firstError) => {
521
+ logger.error(
522
+ `ERROR:<MessageSend ${node.Platform}> ctx=${ctx2} ${sessionTypeArray(session)} ${firstError}`
523
+ );
524
+ if (relayEnabled !== false && firstError instanceof MediaRelayError) {
525
+ logger.info(
526
+ `[MessageForward] relay failed, retrying without relay: ${node.Platform}`
527
+ );
528
+ warnIfSlow(
529
+ MessageSendWithDecorator(ctx2, node, session, MsgDecoratorNoRelay)
530
+ ).catch((secondError) => {
531
+ logger.error(
532
+ `ERROR:<MessageSendDirect ${node.Platform}> ctx=${ctx2} ${sessionTypeArray(session)} ${secondError}`
533
+ );
534
+ sendDegraded(reasonFromError(secondError));
535
+ });
536
+ } else {
537
+ sendDegraded(reasonFromError(firstError));
538
+ }
539
+ }
540
+ );
510
541
  }
511
542
  async function MessageDelete(ctx2, msg) {
512
543
  if (!botExistsCheck(ctx2, { Platform: msg.platform, BotID: msg.bot, Guild: msg.guild })) {
@@ -548,7 +579,7 @@ function defaultDecorator({ head, content }) {
548
579
  return msg;
549
580
  }
550
581
  function defaultMiddleware(session) {
551
- let head = [(0, import_koishi6.h)("span", renderTemplate(defaultPrefix, session))];
582
+ const head = [(0, import_koishi6.h)("span", renderTemplate(defaultPrefix, session))];
552
583
  if (defaultPrefixNewline) {
553
584
  head.push((0, import_koishi6.h)("br"));
554
585
  }
@@ -572,24 +603,21 @@ function msgMiddleCacheFind(uuid) {
572
603
  return null;
573
604
  }
574
605
  function MsgToMiddleware(session) {
575
- let elems = { head: [], content: [] };
576
- let _platform_in = decorators_exports[session.platform];
606
+ const _platform_in = decorators_exports[session.platform];
577
607
  if (_platform_in && typeof _platform_in.Middleware === "function") {
578
- elems = _platform_in.Middleware(session);
579
- } else {
580
- elems = defaultMiddleware(session);
608
+ return _platform_in.Middleware(session);
581
609
  }
582
- return elems;
610
+ return defaultMiddleware(session);
583
611
  }
584
612
  async function MsgMiddlewareCache(session) {
585
- let elems = MsgToMiddleware(session);
613
+ const elems = MsgToMiddleware(session);
586
614
  msgMiddleCacheAppend({ UUID: MsgUUIDFromSession(session), msg: elems });
587
615
  logger.debug(`[msgMiddleCache] CACHED`);
588
616
  }
589
617
  async function MsgDecorator(session, node) {
590
618
  let elems;
591
- let _platform_out = decorators_exports[node.Platform];
592
- let elemCache = msgMiddleCacheFind(MsgUUIDFromSession(session));
619
+ const _platform_out = decorators_exports[node.Platform];
620
+ const elemCache = msgMiddleCacheFind(MsgUUIDFromSession(session));
593
621
  if (elemCache) {
594
622
  logger.debug(`[msgMiddleCache] HIT`);
595
623
  elems = elemCache;
@@ -610,9 +638,28 @@ async function MsgDecorator(session, node) {
610
638
  return defaultDecorator(elems);
611
639
  }
612
640
  }
641
+ async function MsgDecoratorNoRelay(session, node) {
642
+ let elems;
643
+ const _platform_out = decorators_exports[node.Platform];
644
+ const elemCache = msgMiddleCacheFind(MsgUUIDFromSession(session));
645
+ if (elemCache) {
646
+ elems = elemCache;
647
+ } else {
648
+ elems = MsgToMiddleware(session);
649
+ }
650
+ for (const fn of localDecorators) {
651
+ elems = await fn(session, node, elems);
652
+ }
653
+ logger.debug(`[MsgDecoratorNoRelay] fallback to direct forward`);
654
+ if (_platform_out && typeof _platform_out.Decorator === "function") {
655
+ return _platform_out.Decorator(elems);
656
+ } else {
657
+ return defaultDecorator(elems);
658
+ }
659
+ }
613
660
  function defaultDecoratorFallback({ head, content }, reason) {
614
661
  let msg = [];
615
- let newContent = [];
662
+ const newContent = [];
616
663
  newContent.push((0, import_koishi6.h)("span", reason || defaultFallback));
617
664
  for (const key in content) {
618
665
  if (["img", "audio", "video", "file"].includes(content[key].type)) {
@@ -625,16 +672,14 @@ function defaultDecoratorFallback({ head, content }, reason) {
625
672
  return msg;
626
673
  }
627
674
  async function MsgDecoratorFallback(session, node) {
628
- let elems = { head: [], content: [] };
629
- elems = defaultMiddleware(session);
675
+ let elems = defaultMiddleware(session);
630
676
  for (const fn of localDecorators) {
631
677
  elems = await fn(session, node, elems);
632
678
  }
633
679
  return defaultDecoratorFallback(elems);
634
680
  }
635
681
  async function MsgDecoratorFallbackReason(session, node, reason) {
636
- let elems = { head: [], content: [] };
637
- elems = defaultMiddleware(session);
682
+ let elems = defaultMiddleware(session);
638
683
  for (const fn of localDecorators) {
639
684
  elems = await fn(session, node, elems);
640
685
  }
@@ -642,7 +687,7 @@ async function MsgDecoratorFallbackReason(session, node, reason) {
642
687
  }
643
688
  async function atTranslator(session, _, { head, content }) {
644
689
  const newMsg = await new Promise((resolve) => {
645
- let newContent = [];
690
+ const newContent = [];
646
691
  for (const key in content) {
647
692
  const element = content[key];
648
693
  if (element.type === "at") {
@@ -666,9 +711,9 @@ async function quoteTranslator(session, node, { head, content }) {
666
711
  const key = session.channelId + ":" + session.quote.id;
667
712
  const cache = await msgCacheFindByKey(key);
668
713
  if (cache) {
669
- let msgid = await msgCacheGetLocalIDByUUID(node, cache.uuid);
714
+ const msgid = await msgCacheGetLocalIDByUUID(node, cache.uuid);
670
715
  if (msgid) {
671
- let newHead = [(0, import_koishi6.h)("quote", { id: msgid })].concat(head);
716
+ const newHead = [(0, import_koishi6.h)("quote", { id: msgid })].concat(head);
672
717
  return { head: newHead, content };
673
718
  } else {
674
719
  logger.error(`[msgCacheGetLocalIDByUUID] ${cache.uuid} not found`);
@@ -712,7 +757,7 @@ function apply(ctx2, cfg) {
712
757
  session.platform,
713
758
  session.channelId + ":" + session.messageId
714
759
  );
715
- let uuid = MsgUUIDFromSession(session);
760
+ const uuid = MsgUUIDFromSession(session);
716
761
  msgCache({
717
762
  platform: session.platform,
718
763
  bot: group.Nodes[k].BotID,
@@ -732,7 +777,13 @@ function apply(ctx2, cfg) {
732
777
  const group = cfg.ForwardGroups[hitGroup[g]];
733
778
  for (const k in group.Nodes) {
734
779
  if (group.Nodes[k].Guild !== session.channelId) {
735
- MessageForward(ctx2, group.Nodes[k], session);
780
+ MessageForward(
781
+ ctx2,
782
+ group.Nodes[k],
783
+ session,
784
+ cfg.ForwardTimeoutSec,
785
+ cfg.MediaRelay.Enabled
786
+ );
736
787
  }
737
788
  }
738
789
  }
package/lib/message.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Context, Session } from "koishi";
2
2
  import { ForwardNode } from "./config";
3
3
  import { MsgCache } from "./cache";
4
- export declare function MessageForward(ctx: Context, node: ForwardNode, session: Session): Promise<void>;
4
+ export declare function MessageForward(ctx: Context, node: ForwardNode, session: Session, timeoutSec?: number, relayEnabled?: boolean): Promise<void>;
5
5
  export declare function MessageDelete(ctx: Context, msg: MsgCache): Promise<void>;
6
6
  export declare function MessageEdit(ctx: Context, node: ForwardNode, _session: Session): Promise<void>;
7
7
  export declare function MsgUUIDFromSession(session: Session): string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-forward-hime",
3
3
  "description": "多群组消息互通",
4
- "version": "1.4.0",
4
+ "version": "1.4.1-alpha.1",
5
5
  "author": {
6
6
  "name": "xianii",
7
7
  "email": "jiyucheng007@gmail.com"
@@ -35,22 +35,25 @@
35
35
  "koishi": "^4.18.7"
36
36
  },
37
37
  "devDependencies": {
38
+ "@eslint/compat": "^2.1.0",
39
+ "@eslint/js": "^10.0.1",
38
40
  "@koishijs/cache": "^2.0.0",
39
41
  "@types/node": "^22.15.0",
40
42
  "@typescript-eslint/eslint-plugin": "^7.13.0",
41
43
  "@typescript-eslint/parser": "^7.13.0",
44
+ "esbuild": "^0.25.9",
42
45
  "eslint": "^9.4.0",
46
+ "eslint-config-prettier": "^9.1.0",
43
47
  "eslint-plugin-import": "^2.31.0",
44
48
  "eslint-plugin-jsx-a11y": "^6.10.2",
45
- "eslint-config-prettier": "^9.1.0",
46
49
  "eslint-plugin-prettier": "^5.1.3",
47
50
  "eslint-plugin-unused-imports": "^4.0.0",
48
- "esbuild": "^0.25.9",
49
51
  "husky": "^9.0.11",
50
52
  "koishi": "^4.18.7",
51
53
  "lint-staged": "^15.2.7",
52
54
  "prettier": "^3.3.2",
53
- "typescript": "~5.4.5"
55
+ "typescript": "~5.4.5",
56
+ "typescript-eslint": "^8.62.0"
54
57
  },
55
58
  "scripts": {
56
59
  "prepare": "husky",