koishi-plugin-dew-bot 1.0.250 → 1.0.252

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/README.md CHANGED
@@ -10,7 +10,3 @@
10
10
  踏入主神空间,成为轮回者。与伙伴迎接未知的试炼,在生死之间积蓄力量,书写属于你的无限冒险。
11
11
 
12
12
  无论你熟悉原作,还是初次踏入无限流世界,这场轮回,都有你的位置。
13
-
14
- ---
15
-
16
- 连接 DEW 游戏服后,在聊天中发送 **帮助**,开启第一场冒险。
package/lib/index.js CHANGED
@@ -42,40 +42,6 @@ var import_koishi2 = require("koishi");
42
42
  // src/plugin/serverHandel/image.ts
43
43
  var import_koishi = require("koishi");
44
44
 
45
- // src/shared/bot/analysisMsg.ts
46
- var AnalysisMsg = class {
47
- static {
48
- __name(this, "AnalysisMsg");
49
- }
50
- parse(data) {
51
- let msg = "";
52
- let haveTitle = false;
53
- for (const item of data.line) {
54
- switch (item.type) {
55
- case 0 /* title */:
56
- haveTitle = true;
57
- msg += `┄━═${item.content}═━┄
58
- `;
59
- break;
60
- case 1 /* line */:
61
- msg += `${item.content}
62
- `;
63
- break;
64
- case 2 /* error */:
65
- msg += `❌出错了!错误原因:${item.content},请反馈至联系开发者
66
- `;
67
- break;
68
- default:
69
- break;
70
- }
71
- }
72
- if (haveTitle) {
73
- msg += "┄━════════━┄";
74
- }
75
- return msg;
76
- }
77
- };
78
-
79
45
  // src/plugin/appearanceCardTemplate.ts
80
46
  var escape = /* @__PURE__ */ __name((value) => String(value ?? "").replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]), "escape");
81
47
  function appearanceCardHtml(card, artwork, paper) {
@@ -689,28 +655,37 @@ var handel_base = class {
689
655
  }
690
656
  };
691
657
 
692
- // src/plugin/serverHandel/image.ts
693
- var ImageHandler = class extends handel_base {
658
+ // src/shared/bot/analysisMsg.ts
659
+ var AnalysisMsg = class {
694
660
  static {
695
- __name(this, "ImageHandler");
661
+ __name(this, "AnalysisMsg");
696
662
  }
697
- async start(data) {
698
- let content;
699
- let rendered = false;
700
- const renderStartedAt = this.timing ? performance.now() : 0;
701
- try {
702
- if (!data.characterCard && !data.itemCard && !data.card && !data.leaderboard && !data.equipmentCard) throw new Error("缺少卡片数据");
703
- const image = await renderSkillCard(this.ctx, data.card, data.leaderboard, data.itemCard, data.characterCard, data.equipmentCard);
704
- if (this.timing) this.timing.imageBytes = image.length;
705
- content = import_koishi.h.image(image, "image/jpeg").toString();
706
- rendered = true;
707
- } catch (error) {
708
- this.ctx.logger.warn("卡片绘制失败,使用文字详情", error instanceof Error ? error.message : "unknown");
709
- content = new AnalysisMsg().parse(data);
710
- } finally {
711
- if (this.timing) this.timing.renderMs = performance.now() - renderStartedAt;
663
+ parse(data) {
664
+ let msg = "";
665
+ let haveTitle = false;
666
+ for (const item of data.line) {
667
+ switch (item.type) {
668
+ case 0 /* title */:
669
+ haveTitle = true;
670
+ msg += `┄━═${item.content}═━┄
671
+ `;
672
+ break;
673
+ case 1 /* line */:
674
+ msg += `${item.content}
675
+ `;
676
+ break;
677
+ case 2 /* error */:
678
+ msg += `❌出错了!错误原因:${item.content},请反馈至联系开发者
679
+ `;
680
+ break;
681
+ default:
682
+ break;
683
+ }
684
+ }
685
+ if (haveTitle) {
686
+ msg += "┄━════════━┄";
712
687
  }
713
- await this.send(data, content, rendered ? "image" : "text");
688
+ return msg;
714
689
  }
715
690
  };
716
691
 
@@ -723,6 +698,12 @@ function compactDisplayText(value) {
723
698
  return value.trim().replace(/[ \t\u3000]*([·×+|])[ \t\u3000]*/g, "$1").replace(/([\p{Script=Han}\p{Extended_Pictographic}\p{Emoji_Modifier}\p{Regional_Indicator}\uFE0F])[ \t\u3000]+/gu, "$1").replace(/[ \t\u3000]+(?=[\p{Script=Han}\p{Extended_Pictographic}\p{Regional_Indicator}])/gu, "");
724
699
  }
725
700
  __name(compactDisplayText, "compactDisplayText");
701
+ function renderQQTitle(content) {
702
+ return `**${escapeMarkdown(compactDisplayText(content))}**
703
+
704
+ ***`;
705
+ }
706
+ __name(renderQQTitle, "renderQQTitle");
726
707
  function compactListLabel(value) {
727
708
  const label = compactDisplayText(value);
728
709
  return /[📖📚]/u.test(label) ? label.replace(/技能书(?=$|×\d+(?:\.\d+)?(?:||$)||)/g, "") : label;
@@ -733,6 +714,42 @@ function inlineCommandUrl(command) {
733
714
  return `mqqapi://aio/inlinecmd?command=${encoded}&enter=true&reply=false`;
734
715
  }
735
716
  __name(inlineCommandUrl, "inlineCommandUrl");
717
+ function inlineCommandSpans(content) {
718
+ const spans = [];
719
+ let depth = 0, start = 0, labelStart = 0;
720
+ for (let index = 0; index < content.length; index++) {
721
+ if (content[index] === "[") {
722
+ if (depth === 0) {
723
+ start = index > 0 && content[index - 1] === "\\" ? index - 1 : index;
724
+ labelStart = index + 1;
725
+ }
726
+ depth++;
727
+ } else if (content[index] === "]" && depth > 0 && --depth === 0) {
728
+ const label = content.slice(labelStart, index).replace(/\\$/, "");
729
+ if (!label.trim() || /[\[\]\r\n]/.test(label) || /^\d+$/.test(label.trim())) continue;
730
+ const command = label.split(/[++]/, 1)[0].trim();
731
+ if (command) spans.push({ start, end: index + 1, label, command });
732
+ }
733
+ }
734
+ return spans;
735
+ }
736
+ __name(inlineCommandSpans, "inlineCommandSpans");
737
+ function hasInlineCommands(content) {
738
+ return inlineCommandSpans(content).length > 0;
739
+ }
740
+ __name(hasInlineCommands, "hasInlineCommands");
741
+ function renderInlineCommands(content) {
742
+ const parts = [];
743
+ let offset = 0;
744
+ for (const span of inlineCommandSpans(content)) {
745
+ parts.push(escapeMarkdown(content.slice(offset, span.start)));
746
+ parts.push("[" + escapeMarkdown(span.label) + "](" + inlineCommandUrl(span.command) + ")");
747
+ offset = span.end;
748
+ }
749
+ parts.push(escapeMarkdown(content.slice(offset)));
750
+ return parts.join("");
751
+ }
752
+ __name(renderInlineCommands, "renderInlineCommands");
736
753
  function pageCommandLines(nav) {
737
754
  return [
738
755
  ...nav.previousCommand ? [nav.previousCommand] : [],
@@ -746,11 +763,6 @@ function renderQQPageMarkdown(data, showGreeting) {
746
763
  const listPattern = /^\[(\d+)\]\s*([^\r\n]+)$/;
747
764
  const pagePattern = /^第\s*\d+\s*\/\s*\d+\s*页(?:\s*[||]\s*共\s*(\d+)\s*([项条]))?$/;
748
765
  const title = data.line.find((item) => item.type === 0 /* title */);
749
- const summaryLine = data.line.find((item) => item.type === 1 /* line */ && pagePattern.test(item.content));
750
- const summary = summaryLine && pagePattern.exec(summaryLine.content);
751
- const listCount = data.line.filter((item) => item.type === 1 /* line */ && listPattern.test(item.content)).length;
752
- const count = summary?.[1] ?? (nav?.total === 1 ? String(listCount) : void 0);
753
- const countUnit = summary?.[2] ?? "项";
754
766
  const isLeaderboardDirectory = !!nav?.entries?.length && /^(?:🏆)?排行榜$/u.test(compactDisplayText(title?.content ?? ""));
755
767
  const sections = [];
756
768
  const bodyLines = [];
@@ -773,20 +785,16 @@ function renderQQPageMarkdown(data, showGreeting) {
773
785
  switch (item.type) {
774
786
  case 0 /* title */:
775
787
  flush();
776
- if (isLeaderboardDirectory) {
777
- sections.push(`## ${item.content.includes("🏆") ? "" : "🏆"}${escapeMarkdown(compactDisplayText(item.content))}`);
778
- sections.push(`> 共${nav.entries.length}个榜单·点击榜名查看`);
779
- } else {
780
- sections.push(`**${escapeMarkdown(compactDisplayText(item.content))}${count !== void 0 ? `·${count}${countUnit}` : ""}**`);
781
- }
788
+ sections.push(renderQQTitle(`${isLeaderboardDirectory && !item.content.includes("🏆") ? "🏆" : ""}${item.content}`));
782
789
  break;
783
790
  case 1 /* line */: {
784
791
  const listEntry = listPattern.exec(item.content);
785
792
  if (listEntry) {
786
793
  flushBody();
787
794
  const command2 = entryCommands.get(lineIndex);
788
- const label = escapeMarkdown(compactListLabel(listEntry[2]));
789
- const name2 = command2 ? `[${label}](${inlineCommandUrl(command2)})` : label;
795
+ const content = compactListLabel(listEntry[2]);
796
+ const label = escapeMarkdown(content);
797
+ const name2 = command2 ? `[${label}](${inlineCommandUrl(command2)})` : renderInlineCommands(content);
790
798
  listLines.push(`${listEntry[1]}. ${name2}`);
791
799
  break;
792
800
  }
@@ -803,31 +811,28 @@ function renderQQPageMarkdown(data, showGreeting) {
803
811
  const page = pagePattern.exec(item.content);
804
812
  if (page && nav) {
805
813
  flushBody();
806
- pageFooter = `**第${nav.current}/${nav.total}页${!title && page[1] ? `·共${page[1]}${page[2]}` : ""}**`;
814
+ pageFooter = `**第${nav.current}/${nav.total}页**`;
807
815
  hasPageSummary = true;
808
816
  break;
809
817
  }
810
818
  const command = entryCommands.get(lineIndex);
811
- bodyLines.push(command ? `[${escapeMarkdown(compactDisplayText(item.content))}](${inlineCommandUrl(command)})` : escapeMarkdown(item.content));
819
+ bodyLines.push(command ? `[${escapeMarkdown(compactListLabel(item.content))}](${inlineCommandUrl(command)})` : renderInlineCommands(item.content));
812
820
  break;
813
821
  }
814
822
  case 2 /* error */:
815
823
  flush();
816
- sections.push(`❌ 出错了!错误原因:${escapeMarkdown(item.content)},请反馈至联系开发者`);
824
+ sections.push(`❌ 出错了!错误原因:${renderInlineCommands(item.content)},请反馈至联系开发者`);
817
825
  break;
818
826
  }
819
827
  }
820
828
  flush();
821
829
  if (nav && !hasPageSummary && (nav.total > 1 || nav.previousCommand || nav.nextCommand))
822
830
  pageFooter = `**第${nav.current}/${nav.total}页**`;
823
- const previousLink = nav?.previousCommand ? `[上一页](${inlineCommandUrl(nav.previousCommand)})` : void 0;
824
- const nextLink = nav?.nextCommand ? `[下一页](${inlineCommandUrl(nav.nextCommand)})` : void 0;
831
+ const previousLink = nav?.previousCommand ? `[${nav.previousCommand === "上一个" ? "上一个" : "上一页"}](${inlineCommandUrl(nav.previousCommand)})` : void 0;
832
+ const nextLink = nav?.nextCommand ? `[${nav.nextCommand === "下一个" ? "下一个" : "下一页"}](${inlineCommandUrl(nav.nextCommand)})` : void 0;
825
833
  const helpLink = hasHelp ? `[指令](${inlineCommandUrl("指令")})` : void 0;
826
834
  const footer = pageFooter ? [pageFooter, previousLink, nextLink, helpLink].filter(Boolean).join("·") : helpLink ? `更多操作:${helpLink}` : void 0;
827
- if (footer) {
828
- if (isLeaderboardDirectory) sections.push("***");
829
- sections.push(footer);
830
- }
835
+ if (footer) sections.push(footer);
831
836
  return sections.join("\n\n") || " ";
832
837
  }
833
838
  __name(renderQQPageMarkdown, "renderQQPageMarkdown");
@@ -883,19 +888,19 @@ function renderQQTransactionMarkdown(data, showGreeting) {
883
888
  for (const item of data.line) {
884
889
  if (item.type === 0 /* title */) {
885
890
  flush();
886
- sections.push(`**${escapeMarkdown(item.content)}**`);
891
+ sections.push(renderQQTitle(item.content));
887
892
  continue;
888
893
  }
889
894
  if (item.type === 2 /* error */) {
890
895
  flush();
891
- sections.push(`❌ 出错了!错误原因:${escapeMarkdown(item.content)},请反馈至联系开发者`);
896
+ sections.push(`❌ 出错了!错误原因:${renderInlineCommands(item.content)},请反馈至联系开发者`);
892
897
  continue;
893
898
  }
894
899
  if (item.type !== 1 /* line */) continue;
895
900
  const action = data.transaction && (item.content === "[是]确认交易" ? { command: "是", label: "确认交易" } : item.content === "[否]取消交易" ? { command: "否", label: "取消交易" } : void 0);
896
901
  if (action) {
897
902
  flushBody();
898
- actions.push(`[${action.command}](${inlineCommandUrl(action.command)}) ${action.label}`);
903
+ actions.push(`[${action.label}](${inlineCommandUrl(action.command)})`);
899
904
  continue;
900
905
  }
901
906
  flushActions();
@@ -908,7 +913,7 @@ function renderQQTransactionMarkdown(data, showGreeting) {
908
913
  flushBody();
909
914
  sections.push(`**${escapeMarkdown(section?.[1] ?? "还缺")}**`);
910
915
  } else {
911
- bodyLines.push(escapeMarkdown(item.content));
916
+ bodyLines.push(renderInlineCommands(item.content));
912
917
  }
913
918
  }
914
919
  flush();
@@ -920,6 +925,90 @@ async function sendQQTransaction(session, data) {
920
925
  }
921
926
  __name(sendQQTransaction, "sendQQTransaction");
922
927
 
928
+ // src/plugin/serverHandel/qqCommandMessage.ts
929
+ function renderQQCommandMarkdown(data, showGreeting) {
930
+ const sections = [];
931
+ const bodyLines = [];
932
+ const flushBody = /* @__PURE__ */ __name(() => {
933
+ if (bodyLines.length) sections.push(bodyLines.splice(0).join(" \n"));
934
+ }, "flushBody");
935
+ if (showGreeting) sections.push(escapeMarkdown("Ciallo~(∠・ω< )"));
936
+ for (const item of data.line) {
937
+ switch (item.type) {
938
+ case 0 /* title */:
939
+ flushBody();
940
+ sections.push(renderQQTitle(item.content));
941
+ break;
942
+ case 1 /* line */:
943
+ if (!item.content.trim()) flushBody();
944
+ else bodyLines.push(renderInlineCommands(item.content));
945
+ break;
946
+ case 2 /* error */:
947
+ flushBody();
948
+ sections.push(`❌ 出错了!错误原因:${renderInlineCommands(item.content)},请反馈至联系开发者`);
949
+ break;
950
+ }
951
+ }
952
+ flushBody();
953
+ return sections.join("\n\n") || " ";
954
+ }
955
+ __name(renderQQCommandMarkdown, "renderQQCommandMarkdown");
956
+ async function sendQQCommand(session, data) {
957
+ await sendQQMarkdown(session, renderQQCommandMarkdown(data, session.event?.channel?.type === 0));
958
+ }
959
+ __name(sendQQCommand, "sendQQCommand");
960
+
961
+ // src/plugin/serverHandel/qqStoryMessage.ts
962
+ function hasStoryCodeBlock(data) {
963
+ return data.line.some((item) => item.type === 1 /* line */ && item.codeBlock === true);
964
+ }
965
+ __name(hasStoryCodeBlock, "hasStoryCodeBlock");
966
+ function fencedStory(content) {
967
+ const story = content.replace(/\r\n?/g, "\n");
968
+ const longestRun = Math.max(0, ...Array.from(story.matchAll(/`+/g), (match) => match[0].length));
969
+ const fence = "`".repeat(Math.max(3, longestRun + 1));
970
+ return fence + "\n" + story + (story.endsWith("\n") ? "" : "\n") + fence;
971
+ }
972
+ __name(fencedStory, "fencedStory");
973
+ function renderQQStoryMarkdown(data, showGreeting) {
974
+ const sections = [];
975
+ const bodyLines = [];
976
+ const flushBody = /* @__PURE__ */ __name(() => {
977
+ if (bodyLines.length) sections.push(bodyLines.splice(0).join(" \n"));
978
+ }, "flushBody");
979
+ if (showGreeting) sections.push(escapeMarkdown("Ciallo~(∠・ω< )"));
980
+ for (const item of data.line) {
981
+ switch (item.type) {
982
+ case 0 /* title */:
983
+ flushBody();
984
+ sections.push(renderQQTitle(item.content));
985
+ break;
986
+ case 1 /* line */:
987
+ if (item.codeBlock) {
988
+ flushBody();
989
+ sections.push(fencedStory(item.content));
990
+ } else {
991
+ for (const line of item.content.replace(/\r\n?/g, "\n").split("\n")) {
992
+ if (!line.trim()) flushBody();
993
+ else bodyLines.push(renderInlineCommands(line));
994
+ }
995
+ }
996
+ break;
997
+ case 2 /* error */:
998
+ flushBody();
999
+ sections.push("❌ 出错了!错误原因:" + renderInlineCommands(item.content) + ",请反馈至联系开发者");
1000
+ break;
1001
+ }
1002
+ }
1003
+ flushBody();
1004
+ return sections.join("\n\n") || " ";
1005
+ }
1006
+ __name(renderQQStoryMarkdown, "renderQQStoryMarkdown");
1007
+ async function sendQQStory(session, data) {
1008
+ await sendQQMarkdown(session, renderQQStoryMarkdown(data, session.event?.channel?.type === 0));
1009
+ }
1010
+ __name(sendQQStory, "sendQQStory");
1011
+
923
1012
  // src/plugin/serverHandel/text.ts
924
1013
  var handelr_text = class extends handel_base {
925
1014
  static {
@@ -928,11 +1017,16 @@ var handelr_text = class extends handel_base {
928
1017
  async start(data) {
929
1018
  const content = new AnalysisMsg().parse(data);
930
1019
  const session = sessions_default.get(sessionKey(data.botId, data.userId, data.channelId));
931
- if ((data.transaction || data.pageNav) && canSendQQPage(session, data)) {
1020
+ const inlineCommands = data.line.some((item) => (item.type === 1 /* line */ || item.type === 2 /* error */) && hasInlineCommands(item.content));
1021
+ const story = hasStoryCodeBlock(data);
1022
+ if ((story || data.commandHelp || data.transaction || data.pageNav || inlineCommands) && canSendQQPage(session, data)) {
932
1023
  const sendStartedAt = this.timing ? performance.now() : 0;
933
1024
  try {
934
- if (data.transaction) await sendQQTransaction(session, data);
935
- else await sendQQPage(session, data);
1025
+ if (story) await sendQQStory(session, data);
1026
+ else if (data.commandHelp) await sendQQCommand(session, data);
1027
+ else if (data.transaction) await sendQQTransaction(session, data);
1028
+ else if (data.pageNav) await sendQQPage(session, data);
1029
+ else await sendQQCommand(session, data);
936
1030
  if (this.timing) {
937
1031
  this.timing.delivery = "qq-markdown";
938
1032
  this.timing.sendMs = performance.now() - sendStartedAt;
@@ -941,7 +1035,7 @@ var handelr_text = class extends handel_base {
941
1035
  } catch (error) {
942
1036
  this.ctx.logger.warn("QQ Markdown 发送失败,改用普通文字", error instanceof Error ? error.message : error);
943
1037
  }
944
- const commands = !data.transaction && data.pageNav ? pageCommandLines(data.pageNav) : [];
1038
+ const commands = !story && !data.commandHelp && !data.transaction && data.pageNav ? pageCommandLines(data.pageNav) : [];
945
1039
  await this.send(data, commands.length ? `${content.trimEnd()}
946
1040
  ${commands.join("\n")}` : content);
947
1041
  return;
@@ -950,6 +1044,38 @@ ${commands.join("\n")}` : content);
950
1044
  }
951
1045
  };
952
1046
 
1047
+ // src/plugin/serverHandel/image.ts
1048
+ var ImageHandler = class extends handel_base {
1049
+ static {
1050
+ __name(this, "ImageHandler");
1051
+ }
1052
+ async start(data) {
1053
+ let content = "";
1054
+ let rendered = false;
1055
+ const renderStartedAt = this.timing ? performance.now() : 0;
1056
+ try {
1057
+ if (!data.characterCard && !data.itemCard && !data.card && !data.leaderboard && !data.equipmentCard) throw new Error("缺少卡片数据");
1058
+ const image = await renderSkillCard(this.ctx, data.card, data.leaderboard, data.itemCard, data.characterCard, data.equipmentCard);
1059
+ if (this.timing) this.timing.imageBytes = image.length;
1060
+ content = import_koishi.h.image(image, "image/jpeg").toString();
1061
+ rendered = true;
1062
+ } catch (error) {
1063
+ this.ctx.logger.warn("卡片绘制失败,使用文字详情", error instanceof Error ? error.message : "unknown");
1064
+ } finally {
1065
+ if (this.timing) this.timing.renderMs = performance.now() - renderStartedAt;
1066
+ }
1067
+ if (!rendered) {
1068
+ const fallback = new handelr_text(data);
1069
+ fallback.set(this.ctx);
1070
+ if (this.timing) fallback.setTiming(this.timing);
1071
+ fallback.send = (message, text, type = "text") => this.send(message, text, type);
1072
+ await fallback.start(data);
1073
+ return;
1074
+ }
1075
+ await this.send(data, content, "image");
1076
+ }
1077
+ };
1078
+
953
1079
  // src/plugin/messageQueue.ts
954
1080
  var handlers = {
955
1081
  ["text" /* text */]: handelr_text,
@@ -1014,7 +1140,7 @@ var import_tsrpc = require("tsrpc");
1014
1140
 
1015
1141
  // src/shared/bot/serviceProto.ts
1016
1142
  var serviceProto = {
1017
- "version": 12,
1143
+ "version": 14,
1018
1144
  "services": [
1019
1145
  {
1020
1146
  "id": 0,
@@ -1157,6 +1283,14 @@ var serviceProto = {
1157
1283
  },
1158
1284
  "optional": true
1159
1285
  },
1286
+ {
1287
+ "id": 9,
1288
+ "name": "commandHelp",
1289
+ "type": {
1290
+ "type": "Boolean"
1291
+ },
1292
+ "optional": true
1293
+ },
1160
1294
  {
1161
1295
  "id": 1,
1162
1296
  "name": "line",
@@ -1893,6 +2027,14 @@ var serviceProto = {
1893
2027
  "type": {
1894
2028
  "type": "String"
1895
2029
  }
2030
+ },
2031
+ {
2032
+ "id": 2,
2033
+ "name": "codeBlock",
2034
+ "type": {
2035
+ "type": "Boolean"
2036
+ },
2037
+ "optional": true
1896
2038
  }
1897
2039
  ]
1898
2040
  },
@@ -2224,10 +2366,6 @@ var usage = `
2224
2366
  踏入主神空间,成为轮回者。与伙伴迎接未知的试炼,在生死之间积蓄力量,书写属于你的无限冒险。
2225
2367
 
2226
2368
  无论你熟悉原作,还是初次踏入无限流世界,这场轮回,都有你的位置。
2227
-
2228
- ---
2229
-
2230
- 连接 DEW 游戏服后,在聊天中发送 **帮助**,开启第一场冒险。
2231
2369
  `;
2232
2370
  var Config = import_koishi2.Schema.object({
2233
2371
  服务器地址: import_koishi2.Schema.string().default("ws://dew-v7-test:3021").description("DEW 游戏服的 WebSocket 地址;同机部署可填写 ws://127.0.0.1:3021")
@@ -0,0 +1,5 @@
1
+ import type { Session } from 'koishi';
2
+ import type { MsgMessage } from '../../shared/bot/MsgMessage';
3
+ /** Render command help and ordinary prose with clickable bracketed commands. */
4
+ export declare function renderQQCommandMarkdown(data: MsgMessage, showGreeting: boolean): string;
5
+ export declare function sendQQCommand(session: Session, data: MsgMessage): Promise<void>;
@@ -3,7 +3,12 @@ import { type PageNavData } from '../../shared/face';
3
3
  import type { MsgMessage } from '../../shared/bot/MsgMessage';
4
4
  /** Escape server-provided lines before embedding them in native QQ Markdown. */
5
5
  export declare function escapeMarkdown(value: string): string;
6
+ /** Keep native QQ Markdown titles consistent across message types. */
7
+ export declare function renderQQTitle(content: string): string;
6
8
  export declare function inlineCommandUrl(command: string): string;
9
+ export declare function hasInlineCommands(content: string): boolean;
10
+ /** Escape surrounding prose while rendering boxed commands as inline links. */
11
+ export declare function renderInlineCommands(content: string): string;
7
12
  export declare function pageCommandLines(nav: PageNavData): string[];
8
13
  export declare function renderQQPageMarkdown(data: MsgMessage, showGreeting: boolean): string;
9
14
  /** Returns false if this is not the matching QQ session or the adapter has no native API. */
@@ -0,0 +1,6 @@
1
+ import type { Session } from 'koishi';
2
+ import type { MsgMessage } from '../../shared/bot/MsgMessage';
3
+ export declare function hasStoryCodeBlock(data: Pick<MsgMessage, 'line'>): boolean;
4
+ /** Only marked story lines become code blocks; result summaries remain prose. */
5
+ export declare function renderQQStoryMarkdown(data: MsgMessage, showGreeting: boolean): string;
6
+ export declare function sendQQStory(session: Session, data: MsgMessage): Promise<void>;
@@ -101,6 +101,8 @@ export interface ITitleMessage {
101
101
  export interface ILineMessage {
102
102
  type: MessageType.line;
103
103
  content: string;
104
+ /** 原文长故事:支持的平台使用 Markdown 代码块,其余平台按普通文字显示。 */
105
+ codeBlock?: boolean;
104
106
  }
105
107
  /** 错误消息项 **/
106
108
  export interface IErrorMessage {
@@ -132,6 +134,8 @@ export interface IMessage {
132
134
  pageNav?: PageNavData;
133
135
  /** 通用交易确认、结果与失败消息;客户端可使用交易 Markdown 展示。 */
134
136
  transaction?: boolean;
137
+ /** 指令系统与指令提示;客户端可用 Markdown 展示可点击的方框指令。 */
138
+ commandHelp?: boolean;
135
139
  /** 消息项列表 **/
136
140
  line: IMessageItem[];
137
141
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-dew-bot",
3
3
  "description": "基于《无限恐怖》小说改编的 RPG 机器人。踏入主神空间,与伙伴迎接轮回试炼,书写属于你的无限冒险。",
4
- "version": "1.0.250",
4
+ "version": "1.0.252",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [
package/src/index.ts CHANGED
@@ -15,10 +15,6 @@ export const usage: string = `
15
15
  踏入主神空间,成为轮回者。与伙伴迎接未知的试炼,在生死之间积蓄力量,书写属于你的无限冒险。
16
16
 
17
17
  无论你熟悉原作,还是初次踏入无限流世界,这场轮回,都有你的位置。
18
-
19
- ---
20
-
21
- 连接 DEW 游戏服后,在聊天中发送 **帮助**,开启第一场冒险。
22
18
  `
23
19
 
24
20
  export interface Config {
@@ -1,11 +1,11 @@
1
1
  import { h } from 'koishi'
2
2
  import { MsgMessage } from '../../shared/bot/MsgMessage'
3
- import { AnalysisMsg } from '../../shared/bot/analysisMsg'
4
3
  import { renderSkillCard } from '../skillCard'
5
4
  import handel_base from './handel_base'
5
+ import TextHandler from './text'
6
6
  export default class ImageHandler extends handel_base {
7
7
  async start(data: MsgMessage) {
8
- let content: string
8
+ let content = ''
9
9
  let rendered = false
10
10
  const renderStartedAt = this.timing ? performance.now() : 0
11
11
  try {
@@ -16,10 +16,17 @@ export default class ImageHandler extends handel_base {
16
16
  rendered = true
17
17
  } catch (error) {
18
18
  this.ctx.logger.warn('卡片绘制失败,使用文字详情', error instanceof Error ? error.message : 'unknown')
19
- content = new AnalysisMsg().parse(data)
20
19
  } finally {
21
20
  if (this.timing) this.timing.renderMs = performance.now() - renderStartedAt
22
21
  }
23
- await this.send(data, content, rendered ? 'image' : 'text')
22
+ if (!rendered) {
23
+ const fallback = new TextHandler(data)
24
+ fallback.set(this.ctx)
25
+ if (this.timing) fallback.setTiming(this.timing)
26
+ fallback.send = (message, text, type = 'text') => this.send(message, text, type)
27
+ await fallback.start(data)
28
+ return
29
+ }
30
+ await this.send(data, content, 'image')
24
31
  }
25
32
  }
@@ -0,0 +1,36 @@
1
+ import type { Session } from 'koishi'
2
+ import { MessageType } from '../../shared/face'
3
+ import type { MsgMessage } from '../../shared/bot/MsgMessage'
4
+ import { escapeMarkdown, renderInlineCommands, renderQQTitle, sendQQMarkdown } from './qqPageMessage'
5
+
6
+ /** Render command help and ordinary prose with clickable bracketed commands. */
7
+ export function renderQQCommandMarkdown(data: MsgMessage, showGreeting: boolean): string {
8
+ const sections: string[] = []
9
+ const bodyLines: string[] = []
10
+ const flushBody = () => {
11
+ if (bodyLines.length) sections.push(bodyLines.splice(0).join(' \n'))
12
+ }
13
+ if (showGreeting) sections.push(escapeMarkdown('Ciallo~(∠・ω< )'))
14
+ for (const item of data.line) {
15
+ switch (item.type) {
16
+ case MessageType.title:
17
+ flushBody()
18
+ sections.push(renderQQTitle(item.content))
19
+ break
20
+ case MessageType.line:
21
+ if (!item.content.trim()) flushBody()
22
+ else bodyLines.push(renderInlineCommands(item.content))
23
+ break
24
+ case MessageType.error:
25
+ flushBody()
26
+ sections.push(`❌ 出错了!错误原因:${renderInlineCommands(item.content)},请反馈至联系开发者`)
27
+ break
28
+ }
29
+ }
30
+ flushBody()
31
+ return sections.join('\n\n') || ' '
32
+ }
33
+
34
+ export async function sendQQCommand(session: Session, data: MsgMessage): Promise<void> {
35
+ await sendQQMarkdown(session, renderQQCommandMarkdown(data, session.event?.channel?.type === 0))
36
+ }
@@ -35,6 +35,11 @@ function compactDisplayText(value: string): string {
35
35
  .replace(/[ \t\u3000]+(?=[\p{Script=Han}\p{Extended_Pictographic}\p{Regional_Indicator}])/gu, '')
36
36
  }
37
37
 
38
+ /** Keep native QQ Markdown titles consistent across message types. */
39
+ export function renderQQTitle(content: string): string {
40
+ return `**${escapeMarkdown(compactDisplayText(content))}**\n\n***`
41
+ }
42
+
38
43
  function compactListLabel(value: string): string {
39
44
  const label = compactDisplayText(value)
40
45
  // The book icon already identifies skill books. Only shorten the displayed
@@ -52,6 +57,47 @@ export function inlineCommandUrl(command: string): string {
52
57
  return `mqqapi://aio/inlinecmd?command=${encoded}&enter=true&reply=false`
53
58
  }
54
59
 
60
+
61
+ type InlineCommandSpan = { start: number; end: number; label: string; command: string }
62
+
63
+ /** Bracketed commands may appear anywhere in prose, including escaped brackets. */
64
+ function inlineCommandSpans(content: string): InlineCommandSpan[] {
65
+ const spans: InlineCommandSpan[] = []
66
+ let depth = 0, start = 0, labelStart = 0
67
+ for (let index = 0; index < content.length; index++) {
68
+ if (content[index] === '[') {
69
+ if (depth === 0) {
70
+ start = index > 0 && content[index - 1] === '\\' ? index - 1 : index
71
+ labelStart = index + 1
72
+ }
73
+ depth++
74
+ } else if (content[index] === ']' && depth > 0 && --depth === 0) {
75
+ const label = content.slice(labelStart, index).replace(/\\$/, '')
76
+ if (!label.trim() || /[\[\]\r\n]/.test(label) || /^\d+$/.test(label.trim())) continue
77
+ const command = label.split(/[++]/, 1)[0].trim()
78
+ if (command) spans.push({ start, end: index + 1, label, command })
79
+ }
80
+ }
81
+ return spans
82
+ }
83
+
84
+ export function hasInlineCommands(content: string): boolean {
85
+ return inlineCommandSpans(content).length > 0
86
+ }
87
+
88
+ /** Escape surrounding prose while rendering boxed commands as inline links. */
89
+ export function renderInlineCommands(content: string): string {
90
+ const parts: string[] = []
91
+ let offset = 0
92
+ for (const span of inlineCommandSpans(content)) {
93
+ parts.push(escapeMarkdown(content.slice(offset, span.start)))
94
+ parts.push('[' + escapeMarkdown(span.label) + '](' + inlineCommandUrl(span.command) + ')')
95
+ offset = span.end
96
+ }
97
+ parts.push(escapeMarkdown(content.slice(offset)))
98
+ return parts.join('')
99
+ }
100
+
55
101
  export function pageCommandLines(nav: PageNavData): string[] {
56
102
  return [
57
103
  ...(nav.previousCommand ? [nav.previousCommand] : []),
@@ -65,11 +111,6 @@ export function renderQQPageMarkdown(data: MsgMessage, showGreeting: boolean): s
65
111
  const listPattern = /^\[(\d+)\]\s*([^\r\n]+)$/
66
112
  const pagePattern = /^第\s*\d+\s*\/\s*\d+\s*页(?:\s*[||]\s*共\s*(\d+)\s*([项条]))?$/
67
113
  const title = data.line.find(item => item.type === MessageType.title)
68
- const summaryLine = data.line.find(item => item.type === MessageType.line && pagePattern.test(item.content))
69
- const summary = summaryLine && pagePattern.exec(summaryLine.content)
70
- const listCount = data.line.filter(item => item.type === MessageType.line && listPattern.test(item.content)).length
71
- const count = summary?.[1] ?? (nav?.total === 1 ? String(listCount) : undefined)
72
- const countUnit = summary?.[2] ?? '项'
73
114
  const isLeaderboardDirectory = !!nav?.entries?.length
74
115
  && /^(?:🏆)?排行榜$/u.test(compactDisplayText(title?.content ?? ''))
75
116
  const sections: string[] = []
@@ -90,22 +131,18 @@ export function renderQQPageMarkdown(data: MsgMessage, showGreeting: boolean): s
90
131
  switch (item.type) {
91
132
  case MessageType.title:
92
133
  flush()
93
- if (isLeaderboardDirectory) {
94
- sections.push(`## ${item.content.includes('🏆') ? '' : '🏆'}${escapeMarkdown(compactDisplayText(item.content))}`)
95
- sections.push(`> 共${nav!.entries!.length}个榜单·点击榜名查看`)
96
- } else {
97
- sections.push(`**${escapeMarkdown(compactDisplayText(item.content))}${count !== undefined ? `·${count}${countUnit}` : ''}**`)
98
- }
134
+ sections.push(renderQQTitle(`${isLeaderboardDirectory && !item.content.includes('🏆') ? '🏆' : ''}${item.content}`))
99
135
  break
100
136
  case MessageType.line: {
101
137
  const listEntry = listPattern.exec(item.content)
102
138
  if (listEntry) {
103
139
  flushBody()
104
140
  const command = entryCommands.get(lineIndex)
105
- const label = escapeMarkdown(compactListLabel(listEntry[2]))
141
+ const content = compactListLabel(listEntry[2])
142
+ const label = escapeMarkdown(content)
106
143
  const name = command
107
144
  ? `[${label}](${inlineCommandUrl(command)})`
108
- : label
145
+ : renderInlineCommands(content)
109
146
  // Keep the numeric marker as Markdown syntax; escape only the item text.
110
147
  listLines.push(`${listEntry[1]}. ${name}`)
111
148
  break
@@ -125,35 +162,36 @@ export function renderQQPageMarkdown(data: MsgMessage, showGreeting: boolean): s
125
162
  const page = pagePattern.exec(item.content)
126
163
  if (page && nav) {
127
164
  flushBody()
128
- pageFooter = `**第${nav.current}/${nav.total}页${!title && page[1] ? `·共${page[1]}${page[2]}` : ''}**`
165
+ pageFooter = `**第${nav.current}/${nav.total}页**`
129
166
  hasPageSummary = true
130
167
  break
131
168
  }
132
169
  const command = entryCommands.get(lineIndex)
133
170
  bodyLines.push(command
134
- ? `[${escapeMarkdown(compactDisplayText(item.content))}](${inlineCommandUrl(command)})`
135
- : escapeMarkdown(item.content))
171
+ ? `[${escapeMarkdown(compactListLabel(item.content))}](${inlineCommandUrl(command)})`
172
+ : renderInlineCommands(item.content))
136
173
  break
137
174
  }
138
175
  case MessageType.error:
139
176
  flush()
140
- sections.push(`❌ 出错了!错误原因:${escapeMarkdown(item.content)},请反馈至联系开发者`)
177
+ sections.push(`❌ 出错了!错误原因:${renderInlineCommands(item.content)},请反馈至联系开发者`)
141
178
  break
142
179
  }
143
180
  }
144
181
  flush()
145
182
  if (nav && !hasPageSummary && (nav.total > 1 || nav.previousCommand || nav.nextCommand))
146
183
  pageFooter = `**第${nav.current}/${nav.total}页**`
147
- const previousLink = nav?.previousCommand ? `[上一页](${inlineCommandUrl(nav.previousCommand)})` : undefined
148
- const nextLink = nav?.nextCommand ? `[下一页](${inlineCommandUrl(nav.nextCommand)})` : undefined
184
+ const previousLink = nav?.previousCommand
185
+ ? `[${nav.previousCommand === '上一个' ? '上一个' : '上一页'}](${inlineCommandUrl(nav.previousCommand)})`
186
+ : undefined
187
+ const nextLink = nav?.nextCommand
188
+ ? `[${nav.nextCommand === '下一个' ? '下一个' : '下一页'}](${inlineCommandUrl(nav.nextCommand)})`
189
+ : undefined
149
190
  const helpLink = hasHelp ? `[指令](${inlineCommandUrl('指令')})` : undefined
150
191
  const footer = pageFooter
151
192
  ? [pageFooter, previousLink, nextLink, helpLink].filter(Boolean).join('·')
152
193
  : helpLink ? `更多操作:${helpLink}` : undefined
153
- if (footer) {
154
- if (isLeaderboardDirectory) sections.push('***')
155
- sections.push(footer)
156
- }
194
+ if (footer) sections.push(footer)
157
195
  return sections.join('\n\n') || ' '
158
196
  }
159
197
 
@@ -0,0 +1,56 @@
1
+ import type { Session } from 'koishi'
2
+ import { MessageType } from '../../shared/face'
3
+ import type { MsgMessage } from '../../shared/bot/MsgMessage'
4
+ import { escapeMarkdown, renderInlineCommands, renderQQTitle, sendQQMarkdown } from './qqPageMessage'
5
+
6
+ export function hasStoryCodeBlock(data: Pick<MsgMessage, 'line'>): boolean {
7
+ return data.line.some(item => item.type === MessageType.line && item.codeBlock === true)
8
+ }
9
+
10
+ function fencedStory(content: string): string {
11
+ const story = content.replace(/\r\n?/g, '\n')
12
+ // A story may itself contain backticks. A longer fence keeps its entire text
13
+ // inside the same block without altering the source used by plain fallback.
14
+ const longestRun = Math.max(0, ...Array.from(story.matchAll(/`+/g), match => match[0].length))
15
+ const fence = '`'.repeat(Math.max(3, longestRun + 1))
16
+ return fence + '\n' + story + (story.endsWith('\n') ? '' : '\n') + fence
17
+ }
18
+
19
+ /** Only marked story lines become code blocks; result summaries remain prose. */
20
+ export function renderQQStoryMarkdown(data: MsgMessage, showGreeting: boolean): string {
21
+ const sections: string[] = []
22
+ const bodyLines: string[] = []
23
+ const flushBody = () => {
24
+ if (bodyLines.length) sections.push(bodyLines.splice(0).join(' \n'))
25
+ }
26
+ if (showGreeting) sections.push(escapeMarkdown('Ciallo~(∠・ω< )'))
27
+ for (const item of data.line) {
28
+ switch (item.type) {
29
+ case MessageType.title:
30
+ flushBody()
31
+ sections.push(renderQQTitle(item.content))
32
+ break
33
+ case MessageType.line:
34
+ if (item.codeBlock) {
35
+ flushBody()
36
+ sections.push(fencedStory(item.content))
37
+ } else {
38
+ for (const line of item.content.replace(/\r\n?/g, '\n').split('\n')) {
39
+ if (!line.trim()) flushBody()
40
+ else bodyLines.push(renderInlineCommands(line))
41
+ }
42
+ }
43
+ break
44
+ case MessageType.error:
45
+ flushBody()
46
+ sections.push('❌ 出错了!错误原因:' + renderInlineCommands(item.content) + ',请反馈至联系开发者')
47
+ break
48
+ }
49
+ }
50
+ flushBody()
51
+ return sections.join('\n\n') || ' '
52
+ }
53
+
54
+ export async function sendQQStory(session: Session, data: MsgMessage): Promise<void> {
55
+ await sendQQMarkdown(session, renderQQStoryMarkdown(data, session.event?.channel?.type === 0))
56
+ }
@@ -1,7 +1,7 @@
1
1
  import type { Session } from 'koishi'
2
2
  import { MessageType } from '../../shared/face'
3
3
  import type { MsgMessage } from '../../shared/bot/MsgMessage'
4
- import { escapeMarkdown, inlineCommandUrl, sendQQMarkdown } from './qqPageMessage'
4
+ import { escapeMarkdown, inlineCommandUrl, renderInlineCommands, renderQQTitle, sendQQMarkdown } from './qqPageMessage'
5
5
 
6
6
  /** Render only explicitly marked transactions; line content never enables this channel. */
7
7
  export function renderQQTransactionMarkdown(data: MsgMessage, showGreeting: boolean): string {
@@ -19,23 +19,22 @@ export function renderQQTransactionMarkdown(data: MsgMessage, showGreeting: bool
19
19
  for (const item of data.line) {
20
20
  if (item.type === MessageType.title) {
21
21
  flush()
22
- sections.push(`**${escapeMarkdown(item.content)}**`)
22
+ sections.push(renderQQTitle(item.content))
23
23
  continue
24
24
  }
25
25
  if (item.type === MessageType.error) {
26
26
  flush()
27
- sections.push(`❌ 出错了!错误原因:${escapeMarkdown(item.content)},请反馈至联系开发者`)
27
+ sections.push(`❌ 出错了!错误原因:${renderInlineCommands(item.content)},请反馈至联系开发者`)
28
28
  continue
29
29
  }
30
30
  if (item.type !== MessageType.line) continue
31
- // These are the common trade actions. Names, reasons and arbitrary bracketed
32
- // material text remain escaped display text rather than executable commands.
31
+ // Keep the common trade actions grouped; other bracketed commands render inline.
33
32
  const action = data.transaction && (item.content === '[是]确认交易'
34
33
  ? { command: '是', label: '确认交易' }
35
34
  : item.content === '[否]取消交易' ? { command: '否', label: '取消交易' } : undefined)
36
35
  if (action) {
37
36
  flushBody()
38
- actions.push(`[${action.command}](${inlineCommandUrl(action.command)}) ${action.label}`)
37
+ actions.push(`[${action.label}](${inlineCommandUrl(action.command)})`)
39
38
  continue
40
39
  }
41
40
  flushActions()
@@ -50,7 +49,7 @@ export function renderQQTransactionMarkdown(data: MsgMessage, showGreeting: bool
50
49
  } else {
51
50
  // Preserve the server's quoted amounts, names and descriptions verbatim
52
51
  // apart from Markdown escaping; rendering never recalculates payments.
53
- bodyLines.push(escapeMarkdown(item.content))
52
+ bodyLines.push(renderInlineCommands(item.content))
54
53
  }
55
54
  }
56
55
  flush()
@@ -1,19 +1,27 @@
1
+ import { MessageType } from '../../shared/face'
1
2
  import { AnalysisMsg } from '../../shared/bot/analysisMsg'
2
3
  import { MsgMessage } from '../../shared/bot/MsgMessage'
3
4
  import sessions, { sessionKey } from '../sessions'
4
5
  import handel_base from './handel_base'
5
- import { canSendQQPage, pageCommandLines, sendQQPage } from './qqPageMessage'
6
+ import { canSendQQPage, hasInlineCommands, pageCommandLines, sendQQPage } from './qqPageMessage'
6
7
  import { sendQQTransaction } from './qqTransactionMessage'
8
+ import { sendQQCommand } from './qqCommandMessage'
9
+ import { hasStoryCodeBlock, sendQQStory } from './qqStoryMessage'
7
10
 
8
11
  export default class handelr_text extends handel_base {
9
12
  async start(data: MsgMessage) {
10
13
  const content = new AnalysisMsg().parse(data)
11
14
  const session = sessions.get(sessionKey(data.botId, data.userId, data.channelId))
12
- if ((data.transaction || data.pageNav) && canSendQQPage(session, data)) {
15
+ const inlineCommands = data.line.some(item => (item.type === MessageType.line || item.type === MessageType.error) && hasInlineCommands(item.content))
16
+ const story = hasStoryCodeBlock(data)
17
+ if ((story || data.commandHelp || data.transaction || data.pageNav || inlineCommands) && canSendQQPage(session, data)) {
13
18
  const sendStartedAt = this.timing ? performance.now() : 0
14
19
  try {
15
- if (data.transaction) await sendQQTransaction(session, data)
16
- else await sendQQPage(session, data)
20
+ if (story) await sendQQStory(session, data)
21
+ else if (data.commandHelp) await sendQQCommand(session, data)
22
+ else if (data.transaction) await sendQQTransaction(session, data)
23
+ else if (data.pageNav) await sendQQPage(session, data)
24
+ else await sendQQCommand(session, data)
17
25
  if (this.timing) {
18
26
  this.timing.delivery = 'qq-markdown'
19
27
  this.timing.sendMs = performance.now() - sendStartedAt
@@ -22,7 +30,7 @@ export default class handelr_text extends handel_base {
22
30
  } catch (error) {
23
31
  this.ctx.logger.warn('QQ Markdown 发送失败,改用普通文字', error instanceof Error ? error.message : error)
24
32
  }
25
- const commands = !data.transaction && data.pageNav ? pageCommandLines(data.pageNav) : []
33
+ const commands = !story && !data.commandHelp && !data.transaction && data.pageNav ? pageCommandLines(data.pageNav) : []
26
34
  await this.send(data, commands.length ? `${content.trimEnd()}\n${commands.join('\n')}` : content)
27
35
  return
28
36
  }
@@ -20,7 +20,7 @@ export interface ServiceType {
20
20
  }
21
21
 
22
22
  export const serviceProto: ServiceProto<ServiceType> = {
23
- "version": 12,
23
+ "version": 14,
24
24
  "services": [
25
25
  {
26
26
  "id": 0,
@@ -163,6 +163,14 @@ export const serviceProto: ServiceProto<ServiceType> = {
163
163
  },
164
164
  "optional": true
165
165
  },
166
+ {
167
+ "id": 9,
168
+ "name": "commandHelp",
169
+ "type": {
170
+ "type": "Boolean"
171
+ },
172
+ "optional": true
173
+ },
166
174
  {
167
175
  "id": 1,
168
176
  "name": "line",
@@ -899,6 +907,14 @@ export const serviceProto: ServiceProto<ServiceType> = {
899
907
  "type": {
900
908
  "type": "String"
901
909
  }
910
+ },
911
+ {
912
+ "id": 2,
913
+ "name": "codeBlock",
914
+ "type": {
915
+ "type": "Boolean"
916
+ },
917
+ "optional": true
902
918
  }
903
919
  ]
904
920
  },
@@ -1058,4 +1074,4 @@ export const serviceProto: ServiceProto<ServiceType> = {
1058
1074
  ]
1059
1075
  }
1060
1076
  }
1061
- };
1077
+ };
@@ -73,6 +73,8 @@ export interface ITitleMessage {
73
73
  export interface ILineMessage {
74
74
  type: MessageType.line;
75
75
  content: string;
76
+ /** 原文长故事:支持的平台使用 Markdown 代码块,其余平台按普通文字显示。 */
77
+ codeBlock?: boolean;
76
78
  }
77
79
 
78
80
  /** 错误消息项 **/
@@ -105,6 +107,8 @@ export interface IMessage {
105
107
  pageNav?: PageNavData;
106
108
  /** 通用交易确认、结果与失败消息;客户端可使用交易 Markdown 展示。 */
107
109
  transaction?: boolean;
110
+ /** 指令系统与指令提示;客户端可用 Markdown 展示可点击的方框指令。 */
111
+ commandHelp?: boolean;
108
112
  /** 消息项列表 **/
109
113
  line: IMessageItem[];
110
114
  }