koishi-plugin-dew-bot 1.0.249 → 1.0.251
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.js +134 -9
- package/lib/plugin/serverHandel/qqCommandMessage.d.ts +5 -0
- package/lib/plugin/serverHandel/qqPageMessage.d.ts +5 -0
- package/lib/plugin/serverHandel/qqTransactionMessage.d.ts +5 -0
- package/lib/shared/face/index.d.ts +4 -0
- package/package.json +1 -1
- package/src/plugin/serverHandel/qqCommandMessage.ts +54 -0
- package/src/plugin/serverHandel/qqPageMessage.ts +15 -6
- package/src/plugin/serverHandel/qqTransactionMessage.ts +62 -0
- package/src/plugin/serverHandel/text.ts +7 -3
- package/src/shared/bot/serviceProto.ts +18 -2
- package/src/shared/face/index.ts +4 -0
package/lib/index.js
CHANGED
|
@@ -749,7 +749,7 @@ function renderQQPageMarkdown(data, showGreeting) {
|
|
|
749
749
|
const summaryLine = data.line.find((item) => item.type === 1 /* line */ && pagePattern.test(item.content));
|
|
750
750
|
const summary = summaryLine && pagePattern.exec(summaryLine.content);
|
|
751
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);
|
|
752
|
+
const count = summary?.[1] ?? (nav?.total === 1 && listCount > 0 ? String(listCount) : void 0);
|
|
753
753
|
const countUnit = summary?.[2] ?? "项";
|
|
754
754
|
const isLeaderboardDirectory = !!nav?.entries?.length && /^(?:🏆)?排行榜$/u.test(compactDisplayText(title?.content ?? ""));
|
|
755
755
|
const sections = [];
|
|
@@ -820,8 +820,8 @@ function renderQQPageMarkdown(data, showGreeting) {
|
|
|
820
820
|
flush();
|
|
821
821
|
if (nav && !hasPageSummary && (nav.total > 1 || nav.previousCommand || nav.nextCommand))
|
|
822
822
|
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;
|
|
823
|
+
const previousLink = nav?.previousCommand ? `[${nav.previousCommand === "上一个" ? "上一个" : "上一页"}](${inlineCommandUrl(nav.previousCommand)})` : void 0;
|
|
824
|
+
const nextLink = nav?.nextCommand ? `[${nav.nextCommand === "下一个" ? "下一个" : "下一页"}](${inlineCommandUrl(nav.nextCommand)})` : void 0;
|
|
825
825
|
const helpLink = hasHelp ? `[指令](${inlineCommandUrl("指令")})` : void 0;
|
|
826
826
|
const footer = pageFooter ? [pageFooter, previousLink, nextLink, helpLink].filter(Boolean).join("·") : helpLink ? `更多操作:${helpLink}` : void 0;
|
|
827
827
|
if (footer) {
|
|
@@ -839,8 +839,12 @@ function canSendQQPage(session, data) {
|
|
|
839
839
|
}
|
|
840
840
|
__name(canSendQQPage, "canSendQQPage");
|
|
841
841
|
async function sendQQPage(session, data) {
|
|
842
|
-
const internal = session.bot.internal;
|
|
843
842
|
const markdown = renderQQPageMarkdown(data, session.event?.channel?.type === 0);
|
|
843
|
+
await sendQQMarkdown(session, markdown);
|
|
844
|
+
}
|
|
845
|
+
__name(sendQQPage, "sendQQPage");
|
|
846
|
+
async function sendQQMarkdown(session, markdown) {
|
|
847
|
+
const internal = session.bot.internal;
|
|
844
848
|
const request = {
|
|
845
849
|
// With msg_type=2, QQ requires the ordinary content field to be empty.
|
|
846
850
|
content: "",
|
|
@@ -858,7 +862,110 @@ async function sendQQPage(session, data) {
|
|
|
858
862
|
const pendingAudit = response?.code === 304023 && !!response.data?.message_audit?.audit_id;
|
|
859
863
|
if (!response?.id && !response?.audit_id && !pendingAudit) throw new Error("QQ 未确认消息已接收");
|
|
860
864
|
}
|
|
861
|
-
__name(
|
|
865
|
+
__name(sendQQMarkdown, "sendQQMarkdown");
|
|
866
|
+
|
|
867
|
+
// src/plugin/serverHandel/qqTransactionMessage.ts
|
|
868
|
+
function renderQQTransactionMarkdown(data, showGreeting) {
|
|
869
|
+
const sections = [];
|
|
870
|
+
const bodyLines = [];
|
|
871
|
+
const actions = [];
|
|
872
|
+
const flushBody = /* @__PURE__ */ __name(() => {
|
|
873
|
+
if (bodyLines.length) sections.push(bodyLines.splice(0).join(" \n"));
|
|
874
|
+
}, "flushBody");
|
|
875
|
+
const flushActions = /* @__PURE__ */ __name(() => {
|
|
876
|
+
if (actions.length) sections.push(actions.splice(0).join(" · "));
|
|
877
|
+
}, "flushActions");
|
|
878
|
+
const flush = /* @__PURE__ */ __name(() => {
|
|
879
|
+
flushBody();
|
|
880
|
+
flushActions();
|
|
881
|
+
}, "flush");
|
|
882
|
+
if (showGreeting) sections.push(escapeMarkdown("Ciallo~(∠・ω< )"));
|
|
883
|
+
for (const item of data.line) {
|
|
884
|
+
if (item.type === 0 /* title */) {
|
|
885
|
+
flush();
|
|
886
|
+
sections.push(`**${escapeMarkdown(item.content)}**`);
|
|
887
|
+
continue;
|
|
888
|
+
}
|
|
889
|
+
if (item.type === 2 /* error */) {
|
|
890
|
+
flush();
|
|
891
|
+
sections.push(`❌ 出错了!错误原因:${escapeMarkdown(item.content)},请反馈至联系开发者`);
|
|
892
|
+
continue;
|
|
893
|
+
}
|
|
894
|
+
if (item.type !== 1 /* line */) continue;
|
|
895
|
+
const action = data.transaction && (item.content === "[是]确认交易" ? { command: "是", label: "确认交易" } : item.content === "[否]取消交易" ? { command: "否", label: "取消交易" } : void 0);
|
|
896
|
+
if (action) {
|
|
897
|
+
flushBody();
|
|
898
|
+
actions.push(`[${action.label}](${inlineCommandUrl(action.command)})`);
|
|
899
|
+
continue;
|
|
900
|
+
}
|
|
901
|
+
flushActions();
|
|
902
|
+
if (!item.content.trim()) {
|
|
903
|
+
flushBody();
|
|
904
|
+
continue;
|
|
905
|
+
}
|
|
906
|
+
const section = /^┄━═(.+)═━┄$/.exec(item.content);
|
|
907
|
+
if (section || item.content === "【还缺】") {
|
|
908
|
+
flushBody();
|
|
909
|
+
sections.push(`**${escapeMarkdown(section?.[1] ?? "还缺")}**`);
|
|
910
|
+
} else {
|
|
911
|
+
bodyLines.push(escapeMarkdown(item.content));
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
flush();
|
|
915
|
+
return sections.join("\n\n") || " ";
|
|
916
|
+
}
|
|
917
|
+
__name(renderQQTransactionMarkdown, "renderQQTransactionMarkdown");
|
|
918
|
+
async function sendQQTransaction(session, data) {
|
|
919
|
+
await sendQQMarkdown(session, renderQQTransactionMarkdown(data, session.event?.channel?.type === 0));
|
|
920
|
+
}
|
|
921
|
+
__name(sendQQTransaction, "sendQQTransaction");
|
|
922
|
+
|
|
923
|
+
// src/plugin/serverHandel/qqCommandMessage.ts
|
|
924
|
+
function renderCommandLine(content, commandHelp) {
|
|
925
|
+
if (!commandHelp || !/^\s*\[[^\[\]\r\n]+\]/.test(content)) return escapeMarkdown(content);
|
|
926
|
+
const parts = [];
|
|
927
|
+
let offset = 0;
|
|
928
|
+
for (const match of content.matchAll(/\[([^\[\]\r\n]+)\]/g)) {
|
|
929
|
+
parts.push(escapeMarkdown(content.slice(offset, match.index)));
|
|
930
|
+
const command = match[1].split(/[++]/, 1)[0].trim();
|
|
931
|
+
parts.push(command ? `[${escapeMarkdown(match[1])}](${inlineCommandUrl(command)})` : escapeMarkdown(match[0]));
|
|
932
|
+
offset = match.index + match[0].length;
|
|
933
|
+
}
|
|
934
|
+
parts.push(escapeMarkdown(content.slice(offset)));
|
|
935
|
+
return parts.join("");
|
|
936
|
+
}
|
|
937
|
+
__name(renderCommandLine, "renderCommandLine");
|
|
938
|
+
function renderQQCommandMarkdown(data, showGreeting) {
|
|
939
|
+
const sections = [];
|
|
940
|
+
const bodyLines = [];
|
|
941
|
+
const flushBody = /* @__PURE__ */ __name(() => {
|
|
942
|
+
if (bodyLines.length) sections.push(bodyLines.splice(0).join(" \n"));
|
|
943
|
+
}, "flushBody");
|
|
944
|
+
if (showGreeting) sections.push(escapeMarkdown("Ciallo~(∠・ω< )"));
|
|
945
|
+
for (const item of data.line) {
|
|
946
|
+
switch (item.type) {
|
|
947
|
+
case 0 /* title */:
|
|
948
|
+
flushBody();
|
|
949
|
+
sections.push(`**${escapeMarkdown(item.content)}**`);
|
|
950
|
+
break;
|
|
951
|
+
case 1 /* line */:
|
|
952
|
+
if (!item.content.trim()) flushBody();
|
|
953
|
+
else bodyLines.push(renderCommandLine(item.content, !!data.commandHelp));
|
|
954
|
+
break;
|
|
955
|
+
case 2 /* error */:
|
|
956
|
+
flushBody();
|
|
957
|
+
sections.push(`❌ 出错了!错误原因:${escapeMarkdown(item.content)},请反馈至联系开发者`);
|
|
958
|
+
break;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
flushBody();
|
|
962
|
+
return sections.join("\n\n") || " ";
|
|
963
|
+
}
|
|
964
|
+
__name(renderQQCommandMarkdown, "renderQQCommandMarkdown");
|
|
965
|
+
async function sendQQCommand(session, data) {
|
|
966
|
+
await sendQQMarkdown(session, renderQQCommandMarkdown(data, session.event?.channel?.type === 0));
|
|
967
|
+
}
|
|
968
|
+
__name(sendQQCommand, "sendQQCommand");
|
|
862
969
|
|
|
863
970
|
// src/plugin/serverHandel/text.ts
|
|
864
971
|
var handelr_text = class extends handel_base {
|
|
@@ -868,10 +975,12 @@ var handelr_text = class extends handel_base {
|
|
|
868
975
|
async start(data) {
|
|
869
976
|
const content = new AnalysisMsg().parse(data);
|
|
870
977
|
const session = sessions_default.get(sessionKey(data.botId, data.userId, data.channelId));
|
|
871
|
-
if (data.pageNav && canSendQQPage(session, data)) {
|
|
978
|
+
if ((data.commandHelp || data.transaction || data.pageNav) && canSendQQPage(session, data)) {
|
|
872
979
|
const sendStartedAt = this.timing ? performance.now() : 0;
|
|
873
980
|
try {
|
|
874
|
-
await
|
|
981
|
+
if (data.commandHelp) await sendQQCommand(session, data);
|
|
982
|
+
else if (data.transaction) await sendQQTransaction(session, data);
|
|
983
|
+
else await sendQQPage(session, data);
|
|
875
984
|
if (this.timing) {
|
|
876
985
|
this.timing.delivery = "qq-markdown";
|
|
877
986
|
this.timing.sendMs = performance.now() - sendStartedAt;
|
|
@@ -880,7 +989,7 @@ var handelr_text = class extends handel_base {
|
|
|
880
989
|
} catch (error) {
|
|
881
990
|
this.ctx.logger.warn("QQ Markdown 发送失败,改用普通文字", error instanceof Error ? error.message : error);
|
|
882
991
|
}
|
|
883
|
-
const commands = pageCommandLines(data.pageNav);
|
|
992
|
+
const commands = !data.commandHelp && !data.transaction && data.pageNav ? pageCommandLines(data.pageNav) : [];
|
|
884
993
|
await this.send(data, commands.length ? `${content.trimEnd()}
|
|
885
994
|
${commands.join("\n")}` : content);
|
|
886
995
|
return;
|
|
@@ -953,7 +1062,7 @@ var import_tsrpc = require("tsrpc");
|
|
|
953
1062
|
|
|
954
1063
|
// src/shared/bot/serviceProto.ts
|
|
955
1064
|
var serviceProto = {
|
|
956
|
-
"version":
|
|
1065
|
+
"version": 13,
|
|
957
1066
|
"services": [
|
|
958
1067
|
{
|
|
959
1068
|
"id": 0,
|
|
@@ -1088,6 +1197,22 @@ var serviceProto = {
|
|
|
1088
1197
|
},
|
|
1089
1198
|
"optional": true
|
|
1090
1199
|
},
|
|
1200
|
+
{
|
|
1201
|
+
"id": 8,
|
|
1202
|
+
"name": "transaction",
|
|
1203
|
+
"type": {
|
|
1204
|
+
"type": "Boolean"
|
|
1205
|
+
},
|
|
1206
|
+
"optional": true
|
|
1207
|
+
},
|
|
1208
|
+
{
|
|
1209
|
+
"id": 9,
|
|
1210
|
+
"name": "commandHelp",
|
|
1211
|
+
"type": {
|
|
1212
|
+
"type": "Boolean"
|
|
1213
|
+
},
|
|
1214
|
+
"optional": true
|
|
1215
|
+
},
|
|
1091
1216
|
{
|
|
1092
1217
|
"id": 1,
|
|
1093
1218
|
"name": "line",
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Session } from 'koishi';
|
|
2
|
+
import type { MsgMessage } from '../../shared/bot/MsgMessage';
|
|
3
|
+
/** Keep help text intact while making its explicitly boxed commands clickable. */
|
|
4
|
+
export declare function renderQQCommandMarkdown(data: MsgMessage, showGreeting: boolean): string;
|
|
5
|
+
export declare function sendQQCommand(session: Session, data: MsgMessage): Promise<void>;
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import type { Session } from 'koishi';
|
|
2
2
|
import { type PageNavData } from '../../shared/face';
|
|
3
3
|
import type { MsgMessage } from '../../shared/bot/MsgMessage';
|
|
4
|
+
/** Escape server-provided lines before embedding them in native QQ Markdown. */
|
|
5
|
+
export declare function escapeMarkdown(value: string): string;
|
|
6
|
+
export declare function inlineCommandUrl(command: string): string;
|
|
4
7
|
export declare function pageCommandLines(nav: PageNavData): string[];
|
|
5
8
|
export declare function renderQQPageMarkdown(data: MsgMessage, showGreeting: boolean): string;
|
|
6
9
|
/** Returns false if this is not the matching QQ session or the adapter has no native API. */
|
|
7
10
|
export declare function canSendQQPage(session: Session | undefined, data: MsgMessage): session is Session;
|
|
8
11
|
/** Send a native QQ Markdown page with inline command links. */
|
|
9
12
|
export declare function sendQQPage(session: Session, data: MsgMessage): Promise<void>;
|
|
13
|
+
/** Reuse the adapter's native reply API for structured Markdown messages. */
|
|
14
|
+
export declare function sendQQMarkdown(session: Session, markdown: string): Promise<void>;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Session } from 'koishi';
|
|
2
|
+
import type { MsgMessage } from '../../shared/bot/MsgMessage';
|
|
3
|
+
/** Render only explicitly marked transactions; line content never enables this channel. */
|
|
4
|
+
export declare function renderQQTransactionMarkdown(data: MsgMessage, showGreeting: boolean): string;
|
|
5
|
+
export declare function sendQQTransaction(session: Session, data: MsgMessage): Promise<void>;
|
|
@@ -130,6 +130,10 @@ export interface IMessage {
|
|
|
130
130
|
equipmentCard?: EquipmentCardData;
|
|
131
131
|
leaderboard?: LeaderboardImageData;
|
|
132
132
|
pageNav?: PageNavData;
|
|
133
|
+
/** 通用交易确认、结果与失败消息;客户端可使用交易 Markdown 展示。 */
|
|
134
|
+
transaction?: boolean;
|
|
135
|
+
/** 指令系统与指令提示;客户端可用 Markdown 展示可点击的方框指令。 */
|
|
136
|
+
commandHelp?: boolean;
|
|
133
137
|
/** 消息项列表 **/
|
|
134
138
|
line: IMessageItem[];
|
|
135
139
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { Session } from 'koishi'
|
|
2
|
+
import { MessageType } from '../../shared/face'
|
|
3
|
+
import type { MsgMessage } from '../../shared/bot/MsgMessage'
|
|
4
|
+
import { escapeMarkdown, inlineCommandUrl, sendQQMarkdown } from './qqPageMessage'
|
|
5
|
+
|
|
6
|
+
function renderCommandLine(content: string, commandHelp: boolean): string {
|
|
7
|
+
// Only explicit command-description rows enable links. Unknown input and
|
|
8
|
+
// other prose remain display text, even when they contain square brackets.
|
|
9
|
+
if (!commandHelp || !/^\s*\[[^\[\]\r\n]+\]/.test(content)) return escapeMarkdown(content)
|
|
10
|
+
const parts: string[] = []
|
|
11
|
+
let offset = 0
|
|
12
|
+
for (const match of content.matchAll(/\[([^\[\]\r\n]+)\]/g)) {
|
|
13
|
+
parts.push(escapeMarkdown(content.slice(offset, match.index)))
|
|
14
|
+
const command = match[1].split(/[++]/, 1)[0].trim()
|
|
15
|
+
parts.push(command
|
|
16
|
+
? `[${escapeMarkdown(match[1])}](${inlineCommandUrl(command)})`
|
|
17
|
+
: escapeMarkdown(match[0]))
|
|
18
|
+
offset = match.index! + match[0].length
|
|
19
|
+
}
|
|
20
|
+
parts.push(escapeMarkdown(content.slice(offset)))
|
|
21
|
+
return parts.join('')
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Keep help text intact while making its explicitly boxed commands clickable. */
|
|
25
|
+
export function renderQQCommandMarkdown(data: MsgMessage, showGreeting: boolean): string {
|
|
26
|
+
const sections: string[] = []
|
|
27
|
+
const bodyLines: string[] = []
|
|
28
|
+
const flushBody = () => {
|
|
29
|
+
if (bodyLines.length) sections.push(bodyLines.splice(0).join(' \n'))
|
|
30
|
+
}
|
|
31
|
+
if (showGreeting) sections.push(escapeMarkdown('Ciallo~(∠・ω< )'))
|
|
32
|
+
for (const item of data.line) {
|
|
33
|
+
switch (item.type) {
|
|
34
|
+
case MessageType.title:
|
|
35
|
+
flushBody()
|
|
36
|
+
sections.push(`**${escapeMarkdown(item.content)}**`)
|
|
37
|
+
break
|
|
38
|
+
case MessageType.line:
|
|
39
|
+
if (!item.content.trim()) flushBody()
|
|
40
|
+
else bodyLines.push(renderCommandLine(item.content, !!data.commandHelp))
|
|
41
|
+
break
|
|
42
|
+
case MessageType.error:
|
|
43
|
+
flushBody()
|
|
44
|
+
sections.push(`❌ 出错了!错误原因:${escapeMarkdown(item.content)},请反馈至联系开发者`)
|
|
45
|
+
break
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
flushBody()
|
|
49
|
+
return sections.join('\n\n') || ' '
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function sendQQCommand(session: Session, data: MsgMessage): Promise<void> {
|
|
53
|
+
await sendQQMarkdown(session, renderQQCommandMarkdown(data, session.event?.channel?.type === 0))
|
|
54
|
+
}
|
|
@@ -23,7 +23,7 @@ type QQResponse = {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
/** Escape server-provided lines before embedding them in native QQ Markdown. */
|
|
26
|
-
function escapeMarkdown(value: string): string {
|
|
26
|
+
export function escapeMarkdown(value: string): string {
|
|
27
27
|
return value.replace(/[\\`*_{}\[\]()#+\-.!|>~<&]/g, '\\$&')
|
|
28
28
|
}
|
|
29
29
|
|
|
@@ -44,7 +44,7 @@ function compactListLabel(value: string): string {
|
|
|
44
44
|
: label
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
function inlineCommandUrl(command: string): string {
|
|
47
|
+
export function inlineCommandUrl(command: string): string {
|
|
48
48
|
// encodeURIComponent leaves parentheses unescaped, which would close the
|
|
49
49
|
// surrounding Markdown link if an item ID ever contained one.
|
|
50
50
|
const encoded = encodeURIComponent(command).replace(/[!'()*]/g, character =>
|
|
@@ -68,7 +68,7 @@ export function renderQQPageMarkdown(data: MsgMessage, showGreeting: boolean): s
|
|
|
68
68
|
const summaryLine = data.line.find(item => item.type === MessageType.line && pagePattern.test(item.content))
|
|
69
69
|
const summary = summaryLine && pagePattern.exec(summaryLine.content)
|
|
70
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)
|
|
71
|
+
const count = summary?.[1] ?? (nav?.total === 1 && listCount > 0 ? String(listCount) : undefined)
|
|
72
72
|
const countUnit = summary?.[2] ?? '项'
|
|
73
73
|
const isLeaderboardDirectory = !!nav?.entries?.length
|
|
74
74
|
&& /^(?:🏆)?排行榜$/u.test(compactDisplayText(title?.content ?? ''))
|
|
@@ -144,8 +144,12 @@ export function renderQQPageMarkdown(data: MsgMessage, showGreeting: boolean): s
|
|
|
144
144
|
flush()
|
|
145
145
|
if (nav && !hasPageSummary && (nav.total > 1 || nav.previousCommand || nav.nextCommand))
|
|
146
146
|
pageFooter = `**第${nav.current}/${nav.total}页**`
|
|
147
|
-
const previousLink = nav?.previousCommand
|
|
148
|
-
|
|
147
|
+
const previousLink = nav?.previousCommand
|
|
148
|
+
? `[${nav.previousCommand === '上一个' ? '上一个' : '上一页'}](${inlineCommandUrl(nav.previousCommand)})`
|
|
149
|
+
: undefined
|
|
150
|
+
const nextLink = nav?.nextCommand
|
|
151
|
+
? `[${nav.nextCommand === '下一个' ? '下一个' : '下一页'}](${inlineCommandUrl(nav.nextCommand)})`
|
|
152
|
+
: undefined
|
|
149
153
|
const helpLink = hasHelp ? `[指令](${inlineCommandUrl('指令')})` : undefined
|
|
150
154
|
const footer = pageFooter
|
|
151
155
|
? [pageFooter, previousLink, nextLink, helpLink].filter(Boolean).join('·')
|
|
@@ -167,8 +171,13 @@ export function canSendQQPage(session: Session | undefined, data: MsgMessage): s
|
|
|
167
171
|
|
|
168
172
|
/** Send a native QQ Markdown page with inline command links. */
|
|
169
173
|
export async function sendQQPage(session: Session, data: MsgMessage): Promise<void> {
|
|
170
|
-
const internal = (session.bot as typeof session.bot & { internal: QQInternal }).internal
|
|
171
174
|
const markdown = renderQQPageMarkdown(data, session.event?.channel?.type === 0)
|
|
175
|
+
await sendQQMarkdown(session, markdown)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Reuse the adapter's native reply API for structured Markdown messages. */
|
|
179
|
+
export async function sendQQMarkdown(session: Session, markdown: string): Promise<void> {
|
|
180
|
+
const internal = (session.bot as typeof session.bot & { internal: QQInternal }).internal
|
|
172
181
|
const request: QQRequest = {
|
|
173
182
|
// With msg_type=2, QQ requires the ordinary content field to be empty.
|
|
174
183
|
content: '',
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { Session } from 'koishi'
|
|
2
|
+
import { MessageType } from '../../shared/face'
|
|
3
|
+
import type { MsgMessage } from '../../shared/bot/MsgMessage'
|
|
4
|
+
import { escapeMarkdown, inlineCommandUrl, sendQQMarkdown } from './qqPageMessage'
|
|
5
|
+
|
|
6
|
+
/** Render only explicitly marked transactions; line content never enables this channel. */
|
|
7
|
+
export function renderQQTransactionMarkdown(data: MsgMessage, showGreeting: boolean): string {
|
|
8
|
+
const sections: string[] = []
|
|
9
|
+
const bodyLines: string[] = []
|
|
10
|
+
const actions: string[] = []
|
|
11
|
+
const flushBody = () => {
|
|
12
|
+
if (bodyLines.length) sections.push(bodyLines.splice(0).join(' \n'))
|
|
13
|
+
}
|
|
14
|
+
const flushActions = () => {
|
|
15
|
+
if (actions.length) sections.push(actions.splice(0).join(' · '))
|
|
16
|
+
}
|
|
17
|
+
const flush = () => { flushBody(); flushActions() }
|
|
18
|
+
if (showGreeting) sections.push(escapeMarkdown('Ciallo~(∠・ω< )'))
|
|
19
|
+
for (const item of data.line) {
|
|
20
|
+
if (item.type === MessageType.title) {
|
|
21
|
+
flush()
|
|
22
|
+
sections.push(`**${escapeMarkdown(item.content)}**`)
|
|
23
|
+
continue
|
|
24
|
+
}
|
|
25
|
+
if (item.type === MessageType.error) {
|
|
26
|
+
flush()
|
|
27
|
+
sections.push(`❌ 出错了!错误原因:${escapeMarkdown(item.content)},请反馈至联系开发者`)
|
|
28
|
+
continue
|
|
29
|
+
}
|
|
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.
|
|
33
|
+
const action = data.transaction && (item.content === '[是]确认交易'
|
|
34
|
+
? { command: '是', label: '确认交易' }
|
|
35
|
+
: item.content === '[否]取消交易' ? { command: '否', label: '取消交易' } : undefined)
|
|
36
|
+
if (action) {
|
|
37
|
+
flushBody()
|
|
38
|
+
actions.push(`[${action.label}](${inlineCommandUrl(action.command)})`)
|
|
39
|
+
continue
|
|
40
|
+
}
|
|
41
|
+
flushActions()
|
|
42
|
+
if (!item.content.trim()) {
|
|
43
|
+
flushBody()
|
|
44
|
+
continue
|
|
45
|
+
}
|
|
46
|
+
const section = /^┄━═(.+)═━┄$/.exec(item.content)
|
|
47
|
+
if (section || item.content === '【还缺】') {
|
|
48
|
+
flushBody()
|
|
49
|
+
sections.push(`**${escapeMarkdown(section?.[1] ?? '还缺')}**`)
|
|
50
|
+
} else {
|
|
51
|
+
// Preserve the server's quoted amounts, names and descriptions verbatim
|
|
52
|
+
// apart from Markdown escaping; rendering never recalculates payments.
|
|
53
|
+
bodyLines.push(escapeMarkdown(item.content))
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
flush()
|
|
57
|
+
return sections.join('\n\n') || ' '
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function sendQQTransaction(session: Session, data: MsgMessage): Promise<void> {
|
|
61
|
+
await sendQQMarkdown(session, renderQQTransactionMarkdown(data, session.event?.channel?.type === 0))
|
|
62
|
+
}
|
|
@@ -3,15 +3,19 @@ import { MsgMessage } from '../../shared/bot/MsgMessage'
|
|
|
3
3
|
import sessions, { sessionKey } from '../sessions'
|
|
4
4
|
import handel_base from './handel_base'
|
|
5
5
|
import { canSendQQPage, pageCommandLines, sendQQPage } from './qqPageMessage'
|
|
6
|
+
import { sendQQTransaction } from './qqTransactionMessage'
|
|
7
|
+
import { sendQQCommand } from './qqCommandMessage'
|
|
6
8
|
|
|
7
9
|
export default class handelr_text extends handel_base {
|
|
8
10
|
async start(data: MsgMessage) {
|
|
9
11
|
const content = new AnalysisMsg().parse(data)
|
|
10
12
|
const session = sessions.get(sessionKey(data.botId, data.userId, data.channelId))
|
|
11
|
-
if (data.pageNav && canSendQQPage(session, data)) {
|
|
13
|
+
if ((data.commandHelp || data.transaction || data.pageNav) && canSendQQPage(session, data)) {
|
|
12
14
|
const sendStartedAt = this.timing ? performance.now() : 0
|
|
13
15
|
try {
|
|
14
|
-
await
|
|
16
|
+
if (data.commandHelp) await sendQQCommand(session, data)
|
|
17
|
+
else if (data.transaction) await sendQQTransaction(session, data)
|
|
18
|
+
else await sendQQPage(session, data)
|
|
15
19
|
if (this.timing) {
|
|
16
20
|
this.timing.delivery = 'qq-markdown'
|
|
17
21
|
this.timing.sendMs = performance.now() - sendStartedAt
|
|
@@ -20,7 +24,7 @@ export default class handelr_text extends handel_base {
|
|
|
20
24
|
} catch (error) {
|
|
21
25
|
this.ctx.logger.warn('QQ Markdown 发送失败,改用普通文字', error instanceof Error ? error.message : error)
|
|
22
26
|
}
|
|
23
|
-
const commands = pageCommandLines(data.pageNav)
|
|
27
|
+
const commands = !data.commandHelp && !data.transaction && data.pageNav ? pageCommandLines(data.pageNav) : []
|
|
24
28
|
await this.send(data, commands.length ? `${content.trimEnd()}\n${commands.join('\n')}` : content)
|
|
25
29
|
return
|
|
26
30
|
}
|
|
@@ -20,7 +20,7 @@ export interface ServiceType {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export const serviceProto: ServiceProto<ServiceType> = {
|
|
23
|
-
"version":
|
|
23
|
+
"version": 13,
|
|
24
24
|
"services": [
|
|
25
25
|
{
|
|
26
26
|
"id": 0,
|
|
@@ -155,6 +155,22 @@ export const serviceProto: ServiceProto<ServiceType> = {
|
|
|
155
155
|
},
|
|
156
156
|
"optional": true
|
|
157
157
|
},
|
|
158
|
+
{
|
|
159
|
+
"id": 8,
|
|
160
|
+
"name": "transaction",
|
|
161
|
+
"type": {
|
|
162
|
+
"type": "Boolean"
|
|
163
|
+
},
|
|
164
|
+
"optional": true
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
"id": 9,
|
|
168
|
+
"name": "commandHelp",
|
|
169
|
+
"type": {
|
|
170
|
+
"type": "Boolean"
|
|
171
|
+
},
|
|
172
|
+
"optional": true
|
|
173
|
+
},
|
|
158
174
|
{
|
|
159
175
|
"id": 1,
|
|
160
176
|
"name": "line",
|
|
@@ -1050,4 +1066,4 @@ export const serviceProto: ServiceProto<ServiceType> = {
|
|
|
1050
1066
|
]
|
|
1051
1067
|
}
|
|
1052
1068
|
}
|
|
1053
|
-
};
|
|
1069
|
+
};
|
package/src/shared/face/index.ts
CHANGED
|
@@ -103,6 +103,10 @@ export interface IMessage {
|
|
|
103
103
|
equipmentCard?: EquipmentCardData;
|
|
104
104
|
leaderboard?: LeaderboardImageData;
|
|
105
105
|
pageNav?: PageNavData;
|
|
106
|
+
/** 通用交易确认、结果与失败消息;客户端可使用交易 Markdown 展示。 */
|
|
107
|
+
transaction?: boolean;
|
|
108
|
+
/** 指令系统与指令提示;客户端可用 Markdown 展示可点击的方框指令。 */
|
|
109
|
+
commandHelp?: boolean;
|
|
106
110
|
/** 消息项列表 **/
|
|
107
111
|
line: IMessageItem[];
|
|
108
112
|
}
|