koishi-plugin-forward-hime 1.4.0 → 1.4.1-alpha.0

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,6 +103,7 @@ 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\u603B\u8D85\u65F6\u65F6\u95F4\uFF08\u79D2\uFF09- \u542B\u5A92\u4F53\u4E0B\u8F7D\u4E0E\u53D1\u9001\uFF0C\u8D85\u65F6\u5219\u964D\u7EA7").default(30).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),
@@ -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,57 @@ 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}`
495
+ const timeoutMs = (timeoutSec ?? 30) * 1e3;
496
+ const deco = relayEnabled !== false ? MsgDecorator : MsgDecoratorNoRelay;
497
+ function withTimeout(promise) {
498
+ const timeoutPromise = new Promise(
499
+ (_, reject) => setTimeout(() => reject(new Error("forward total timeout")), timeoutMs)
497
500
  );
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) => {
501
+ return Promise.race([promise, timeoutPromise]);
502
+ }
503
+ function sendDegraded(reason) {
504
+ const fallbackDeco = reason ? (fallbackSession, fallbackNode) => MsgDecoratorFallbackReason(fallbackSession, fallbackNode, reason) : MsgDecoratorFallback;
505
+ MessageSendWithDecorator(ctx2, node, session, fallbackDeco).catch((error) => {
505
506
  logger.error(
506
- `ERROR:<MessageSendFallback ${node.Platform}> ctx=${ctx2} ${sessionTypeArray(session)} ${error2}`
507
+ `ERROR:<MessageSendFallback ${node.Platform}> ctx=${ctx2} ${sessionTypeArray(session)} ${error}`
507
508
  );
508
509
  });
509
- });
510
+ }
511
+ function reasonFromError(error) {
512
+ if (error instanceof MediaRelayError) {
513
+ return `[\u8F6C\u53D1\u5931\u8D25] ${error.userMessage} `;
514
+ }
515
+ if (error instanceof Error && error.message === "forward total timeout") {
516
+ return "[\u8F6C\u53D1\u8D85\u65F6] ";
517
+ }
518
+ return void 0;
519
+ }
520
+ withTimeout(MessageSendWithDecorator(ctx2, node, session, deco)).catch(
521
+ (firstError) => {
522
+ logger.error(
523
+ `ERROR:<MessageSend ${node.Platform}> ctx=${ctx2} ${sessionTypeArray(session)} ${firstError}`
524
+ );
525
+ if (relayEnabled !== false && firstError instanceof MediaRelayError) {
526
+ logger.info(
527
+ `[MessageForward] relay failed, retrying without relay: ${node.Platform}`
528
+ );
529
+ withTimeout(
530
+ MessageSendWithDecorator(ctx2, node, session, MsgDecoratorNoRelay)
531
+ ).catch((secondError) => {
532
+ logger.error(
533
+ `ERROR:<MessageSendDirect ${node.Platform}> ctx=${ctx2} ${sessionTypeArray(session)} ${secondError}`
534
+ );
535
+ sendDegraded(reasonFromError(secondError));
536
+ });
537
+ } else {
538
+ sendDegraded(reasonFromError(firstError));
539
+ }
540
+ }
541
+ );
510
542
  }
511
543
  async function MessageDelete(ctx2, msg) {
512
544
  if (!botExistsCheck(ctx2, { Platform: msg.platform, BotID: msg.bot, Guild: msg.guild })) {
@@ -548,7 +580,7 @@ function defaultDecorator({ head, content }) {
548
580
  return msg;
549
581
  }
550
582
  function defaultMiddleware(session) {
551
- let head = [(0, import_koishi6.h)("span", renderTemplate(defaultPrefix, session))];
583
+ const head = [(0, import_koishi6.h)("span", renderTemplate(defaultPrefix, session))];
552
584
  if (defaultPrefixNewline) {
553
585
  head.push((0, import_koishi6.h)("br"));
554
586
  }
@@ -572,24 +604,21 @@ function msgMiddleCacheFind(uuid) {
572
604
  return null;
573
605
  }
574
606
  function MsgToMiddleware(session) {
575
- let elems = { head: [], content: [] };
576
- let _platform_in = decorators_exports[session.platform];
607
+ const _platform_in = decorators_exports[session.platform];
577
608
  if (_platform_in && typeof _platform_in.Middleware === "function") {
578
- elems = _platform_in.Middleware(session);
579
- } else {
580
- elems = defaultMiddleware(session);
609
+ return _platform_in.Middleware(session);
581
610
  }
582
- return elems;
611
+ return defaultMiddleware(session);
583
612
  }
584
613
  async function MsgMiddlewareCache(session) {
585
- let elems = MsgToMiddleware(session);
614
+ const elems = MsgToMiddleware(session);
586
615
  msgMiddleCacheAppend({ UUID: MsgUUIDFromSession(session), msg: elems });
587
616
  logger.debug(`[msgMiddleCache] CACHED`);
588
617
  }
589
618
  async function MsgDecorator(session, node) {
590
619
  let elems;
591
- let _platform_out = decorators_exports[node.Platform];
592
- let elemCache = msgMiddleCacheFind(MsgUUIDFromSession(session));
620
+ const _platform_out = decorators_exports[node.Platform];
621
+ const elemCache = msgMiddleCacheFind(MsgUUIDFromSession(session));
593
622
  if (elemCache) {
594
623
  logger.debug(`[msgMiddleCache] HIT`);
595
624
  elems = elemCache;
@@ -610,9 +639,28 @@ async function MsgDecorator(session, node) {
610
639
  return defaultDecorator(elems);
611
640
  }
612
641
  }
642
+ async function MsgDecoratorNoRelay(session, node) {
643
+ let elems;
644
+ const _platform_out = decorators_exports[node.Platform];
645
+ const elemCache = msgMiddleCacheFind(MsgUUIDFromSession(session));
646
+ if (elemCache) {
647
+ elems = elemCache;
648
+ } else {
649
+ elems = MsgToMiddleware(session);
650
+ }
651
+ for (const fn of localDecorators) {
652
+ elems = await fn(session, node, elems);
653
+ }
654
+ logger.debug(`[MsgDecoratorNoRelay] fallback to direct forward`);
655
+ if (_platform_out && typeof _platform_out.Decorator === "function") {
656
+ return _platform_out.Decorator(elems);
657
+ } else {
658
+ return defaultDecorator(elems);
659
+ }
660
+ }
613
661
  function defaultDecoratorFallback({ head, content }, reason) {
614
662
  let msg = [];
615
- let newContent = [];
663
+ const newContent = [];
616
664
  newContent.push((0, import_koishi6.h)("span", reason || defaultFallback));
617
665
  for (const key in content) {
618
666
  if (["img", "audio", "video", "file"].includes(content[key].type)) {
@@ -625,16 +673,14 @@ function defaultDecoratorFallback({ head, content }, reason) {
625
673
  return msg;
626
674
  }
627
675
  async function MsgDecoratorFallback(session, node) {
628
- let elems = { head: [], content: [] };
629
- elems = defaultMiddleware(session);
676
+ let elems = defaultMiddleware(session);
630
677
  for (const fn of localDecorators) {
631
678
  elems = await fn(session, node, elems);
632
679
  }
633
680
  return defaultDecoratorFallback(elems);
634
681
  }
635
682
  async function MsgDecoratorFallbackReason(session, node, reason) {
636
- let elems = { head: [], content: [] };
637
- elems = defaultMiddleware(session);
683
+ let elems = defaultMiddleware(session);
638
684
  for (const fn of localDecorators) {
639
685
  elems = await fn(session, node, elems);
640
686
  }
@@ -642,7 +688,7 @@ async function MsgDecoratorFallbackReason(session, node, reason) {
642
688
  }
643
689
  async function atTranslator(session, _, { head, content }) {
644
690
  const newMsg = await new Promise((resolve) => {
645
- let newContent = [];
691
+ const newContent = [];
646
692
  for (const key in content) {
647
693
  const element = content[key];
648
694
  if (element.type === "at") {
@@ -666,9 +712,9 @@ async function quoteTranslator(session, node, { head, content }) {
666
712
  const key = session.channelId + ":" + session.quote.id;
667
713
  const cache = await msgCacheFindByKey(key);
668
714
  if (cache) {
669
- let msgid = await msgCacheGetLocalIDByUUID(node, cache.uuid);
715
+ const msgid = await msgCacheGetLocalIDByUUID(node, cache.uuid);
670
716
  if (msgid) {
671
- let newHead = [(0, import_koishi6.h)("quote", { id: msgid })].concat(head);
717
+ const newHead = [(0, import_koishi6.h)("quote", { id: msgid })].concat(head);
672
718
  return { head: newHead, content };
673
719
  } else {
674
720
  logger.error(`[msgCacheGetLocalIDByUUID] ${cache.uuid} not found`);
@@ -712,7 +758,7 @@ function apply(ctx2, cfg) {
712
758
  session.platform,
713
759
  session.channelId + ":" + session.messageId
714
760
  );
715
- let uuid = MsgUUIDFromSession(session);
761
+ const uuid = MsgUUIDFromSession(session);
716
762
  msgCache({
717
763
  platform: session.platform,
718
764
  bot: group.Nodes[k].BotID,
@@ -732,7 +778,13 @@ function apply(ctx2, cfg) {
732
778
  const group = cfg.ForwardGroups[hitGroup[g]];
733
779
  for (const k in group.Nodes) {
734
780
  if (group.Nodes[k].Guild !== session.channelId) {
735
- MessageForward(ctx2, group.Nodes[k], session);
781
+ MessageForward(
782
+ ctx2,
783
+ group.Nodes[k],
784
+ session,
785
+ cfg.ForwardTimeoutSec,
786
+ cfg.MediaRelay.Enabled
787
+ );
736
788
  }
737
789
  }
738
790
  }
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.0",
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",