koishi-plugin-dew-bot 1.0.248 → 1.0.250
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 +91 -19
- 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 +3 -0
- package/package.json +1 -1
- package/src/plugin/equipmentCardTemplate.ts +3 -2
- package/src/plugin/serverHandel/qqPageMessage.ts +13 -12
- package/src/plugin/serverHandel/qqTransactionMessage.ts +62 -0
- package/src/plugin/serverHandel/text.ts +5 -3
- package/src/shared/bot/serviceProto.ts +18 -2
- package/src/shared/face/index.ts +3 -0
package/lib/index.js
CHANGED
|
@@ -310,6 +310,7 @@ function equipmentCardHtml(card, artwork, unknownFallback = false) {
|
|
|
310
310
|
const notes = card.notes?.filter(Boolean).length ? `<div class="notes">${card.notes.filter(Boolean).map((note) => `<p>${escape6(note)}</p>`).join("")}</div>` : "";
|
|
311
311
|
const enhancement = Number(card.enhancement) > 0 ? `<span class="enhance"><small>强化</small>+${escape6(card.enhancement)}</span>` : "";
|
|
312
312
|
const price = card.referencePrice ? `<div class="price"><span>参考价格</span><strong>${escape6(card.referencePrice)}</strong></div>` : "";
|
|
313
|
+
const author = `<span class="author"><small>打造者</small>${escape6(card.authorName?.trim() || "未知")}</span>`;
|
|
313
314
|
return `<!doctype html><html><head><meta charset="utf-8"><style>
|
|
314
315
|
*{box-sizing:border-box}
|
|
315
316
|
html,body{margin:0;width:760px;background:#f1eee5;font-family:'Microsoft YaHei','Noto Sans CJK SC',sans-serif;color:#343b35}
|
|
@@ -318,14 +319,14 @@ html,body{margin:0;width:760px;background:#f1eee5;font-family:'Microsoft YaHei',
|
|
|
318
319
|
#capture:after{content:'';position:absolute;inset:17px;border:1px solid #8c8b6f22;pointer-events:none}
|
|
319
320
|
.heading{display:flex;align-items:center;gap:24px}.heading-copy{flex:1;min-width:0}.overline{font-size:14px;letter-spacing:6px;color:#868a76;margin-bottom:14px}.name{font:700 43px/1.35 'KaiTi','STKaiti','Noto Serif CJK SC','SimSun',serif;margin:0;color:#303a31;overflow-wrap:anywhere}
|
|
320
321
|
.seal{flex-shrink:0;width:66px;min-height:74px;transform:rotate(3deg);padding:8px 3px;color:#a04b40;border:2px solid #a04b40;outline:1px solid #a04b40;outline-offset:-6px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;border-radius:3px 1px 4px 2px}.seal strong{font:700 24px Georgia,'KaiTi',serif;letter-spacing:-1px}.seal span{font:16px 'KaiTi','SimSun',serif;letter-spacing:4px;padding-left:4px}
|
|
321
|
-
.meta{display:flex;flex-wrap:wrap;align-items:baseline;gap:8px 26px;margin:24px 0 0;padding:15px 0;border-top:1px solid #8a8a7144;border-bottom:1px solid #8a8a7128;font-size:19px;color:#535d50;line-height:1.5}.meta .slot{color:#a04b40;font:21px 'KaiTi','SimSun',serif;letter-spacing:2px}.meta small{font-size:16px;color:#92917f;margin-right:9px}
|
|
322
|
+
.meta{display:flex;flex-wrap:wrap;align-items:baseline;gap:8px 26px;margin:24px 0 0;padding:15px 0;border-top:1px solid #8a8a7144;border-bottom:1px solid #8a8a7128;font-size:19px;color:#535d50;line-height:1.5}.meta .slot{color:#a04b40;font:21px 'KaiTi','SimSun',serif;letter-spacing:2px}.meta .author{min-width:0;overflow-wrap:anywhere}.meta small{font-size:16px;color:#92917f;margin-right:9px}
|
|
322
323
|
.art{position:relative;width:610px;height:610px;max-width:100%;margin:18px auto 11px;display:grid;place-items:center;isolation:isolate;overflow:hidden}.art:before{content:'';position:absolute;inset:4%;border-radius:48% 52% 56% 44%;background:radial-gradient(ellipse,#aab4a443,transparent 69%);transform:rotate(-12deg)}.equipment-artwork{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:center;filter:saturate(.72) contrast(.97);mix-blend-mode:multiply;mask-image:linear-gradient(to bottom,transparent 0,#000 8%,#000 84%,transparent 100%),linear-gradient(to right,transparent 0,#000 7%,#000 93%,transparent 100%);mask-composite:intersect}.art-fallback{position:relative;width:360px;height:360px;max-width:90%;display:flex;flex-direction:column;align-items:center;justify-content:center;color:#7d8b7d;border:1px solid #7c887d3b;border-radius:50%;background:radial-gradient(circle,#b1b9a435,transparent 70%);font-family:'KaiTi','SimSun',serif}.art-fallback:before,.art-fallback:after{content:'';position:absolute;inset:21px;border:1px solid #7c887d22;border-radius:50%}.art-fallback:after{inset:48px;border-color:#9a9e8533}.art-fallback .glyph{font-size:126px;line-height:1.2;opacity:.46}.art-fallback .kind{font-size:20px;letter-spacing:4px;opacity:.55}.art:has(.equipment-artwork) .art-fallback{visibility:hidden}.placeholder-note{position:absolute;right:18px;bottom:16px;z-index:2;padding:5px 9px;background:#f4f1e8ce;border:1px solid #9d9b8755;color:#536055;font:18px 'KaiTi','SimSun',serif;letter-spacing:2px}
|
|
323
324
|
.power{display:flex;align-items:center;justify-content:space-between;gap:20px;padding:18px 0 22px;border-top:1px solid #8a8a7128;border-bottom:1px solid #8a8a7144;color:#31463e}.power span{font:24px 'KaiTi','SimSun',serif;letter-spacing:12px}.power strong{font:700 45px/1.1 Georgia,'Times New Roman',serif;letter-spacing:2px;font-variant-numeric:lining-nums}
|
|
324
325
|
.section{margin-top:29px}.section h2{display:flex;align-items:center;gap:16px;margin:0 0 17px;color:#626e5e;font:21px 'KaiTi','SimSun',serif;letter-spacing:5px}.section h2:before{content:'';width:5px;height:17px;background:#a04b40;transform:rotate(8deg);opacity:.75}.section h2:after{content:'';height:1px;flex:1;background:linear-gradient(to right,#7f897c55,transparent)}
|
|
325
326
|
.affixes{display:grid;grid-template-columns:1fr 1fr;column-gap:26px}.affix{display:flex;align-items:baseline;justify-content:space-between;gap:10px;min-width:0;padding:9px 0 12px;border-bottom:1px solid #8a8a7124;font-size:18px;color:#697064}.affix span{overflow-wrap:anywhere}.affix strong{flex-shrink:0;font:700 22px Georgia,'KaiTi',serif;color:#344b42}.empty-line{font-size:18px;color:#92917f;margin:4px 0 12px}
|
|
326
327
|
.skill{display:flex;align-items:baseline;justify-content:space-between;gap:18px;padding:5px 0 10px;font-size:20px;color:#304b40}.skill-name{font-weight:700;overflow-wrap:anywhere}.skill-name i{display:inline-block;width:13px;height:13px;margin:0 17px 1px 4px;background:#a04b40;transform:rotate(45deg)}.rank{font:18px Georgia,'KaiTi',serif;color:#7f8d7b;white-space:nowrap}
|
|
327
328
|
.description p{margin:0;font:22px/1.75 'KaiTi','STKaiti','Noto Serif CJK SC','SimSun',serif;white-space:pre-wrap;overflow-wrap:anywhere;color:#313f38}.price{display:flex;align-items:baseline;gap:16px;margin-top:24px;padding-top:17px;border-top:1px solid #8a8a7128;font-size:18px;color:#8b907d}.price strong{font-size:18px;color:#354c42}.notes{margin-top:17px;padding:10px 15px;border-left:3px solid #a04b4080;background:#e8e8db70;color:#3e5549}.notes p{margin:5px 0;font-size:17px;line-height:1.6;overflow-wrap:anywhere}.edition{display:flex;align-items:center;gap:12px;justify-content:center;margin-top:34px;color:#a6a38e;font:14px 'KaiTi','SimSun',serif;letter-spacing:5px}.edition:before,.edition:after{content:'';height:1px;width:32px;background:#9b9c833d}
|
|
328
|
-
</style></head><body><div id="capture" class="equipment-sheet ink-equipment-sheet"><header><div class="heading"><div class="heading-copy"><div class="overline">装 备 图 录</div><h1 class="name">${escape6(card.name)}</h1></div><div class="seal"><strong>${escape6(card.quality)}</strong><span>品阶</span></div></div><div class="meta"><span class="slot">${escape6(card.slot)}</span>${enhancement}</div></header><div class="art"><div class="art-fallback"><span class="glyph">?</span><span class="kind">未知外观</span></div>${artwork ? `<img class="skill-artwork equipment-artwork" alt="" src="${escape6(artwork)}">` : ""}${unknownFallback || !artwork ? '<span class="placeholder-note">外观暂未显现</span>' : ""}</div><div class="power"><span>装 备 战 力</span><strong>${escape6(power)}</strong></div><section class="section attributes"><h2>附加属性</h2><div class="affixes">${affixes}</div></section>${skills}${description}${price}${notes}<div class="edition">露 · 装备录</div></div></body></html>`;
|
|
329
|
+
</style></head><body><div id="capture" class="equipment-sheet ink-equipment-sheet"><header><div class="heading"><div class="heading-copy"><div class="overline">装 备 图 录</div><h1 class="name">${escape6(card.name)}</h1></div><div class="seal"><strong>${escape6(card.quality)}</strong><span>品阶</span></div></div><div class="meta"><span class="slot">${escape6(card.slot)}</span>${enhancement}${author}</div></header><div class="art"><div class="art-fallback"><span class="glyph">?</span><span class="kind">未知外观</span></div>${artwork ? `<img class="skill-artwork equipment-artwork" alt="" src="${escape6(artwork)}">` : ""}${unknownFallback || !artwork ? '<span class="placeholder-note">外观暂未显现</span>' : ""}</div><div class="power"><span>装 备 战 力</span><strong>${escape6(power)}</strong></div><section class="section attributes"><h2>附加属性</h2><div class="affixes">${affixes}</div></section>${skills}${description}${price}${notes}<div class="edition">露 · 装备录</div></div></body></html>`;
|
|
329
330
|
}
|
|
330
331
|
__name(equipmentCardHtml, "equipmentCardHtml");
|
|
331
332
|
|
|
@@ -783,9 +784,9 @@ function renderQQPageMarkdown(data, showGreeting) {
|
|
|
783
784
|
const listEntry = listPattern.exec(item.content);
|
|
784
785
|
if (listEntry) {
|
|
785
786
|
flushBody();
|
|
786
|
-
const
|
|
787
|
+
const command2 = entryCommands.get(lineIndex);
|
|
787
788
|
const label = escapeMarkdown(compactListLabel(listEntry[2]));
|
|
788
|
-
const name2 =
|
|
789
|
+
const name2 = command2 ? `[${label}](${inlineCommandUrl(command2)})` : label;
|
|
789
790
|
listLines.push(`${listEntry[1]}. ${name2}`);
|
|
790
791
|
break;
|
|
791
792
|
}
|
|
@@ -806,7 +807,8 @@ function renderQQPageMarkdown(data, showGreeting) {
|
|
|
806
807
|
hasPageSummary = true;
|
|
807
808
|
break;
|
|
808
809
|
}
|
|
809
|
-
|
|
810
|
+
const command = entryCommands.get(lineIndex);
|
|
811
|
+
bodyLines.push(command ? `[${escapeMarkdown(compactDisplayText(item.content))}](${inlineCommandUrl(command)})` : escapeMarkdown(item.content));
|
|
810
812
|
break;
|
|
811
813
|
}
|
|
812
814
|
case 2 /* error */:
|
|
@@ -837,20 +839,17 @@ function canSendQQPage(session, data) {
|
|
|
837
839
|
}
|
|
838
840
|
__name(canSendQQPage, "canSendQQPage");
|
|
839
841
|
async function sendQQPage(session, data) {
|
|
840
|
-
const internal = session.bot.internal;
|
|
841
|
-
const nav = data.pageNav;
|
|
842
842
|
const markdown = renderQQPageMarkdown(data, session.event?.channel?.type === 0);
|
|
843
|
-
|
|
844
|
-
|
|
843
|
+
await sendQQMarkdown(session, markdown);
|
|
844
|
+
}
|
|
845
|
+
__name(sendQQPage, "sendQQPage");
|
|
846
|
+
async function sendQQMarkdown(session, markdown) {
|
|
847
|
+
const internal = session.bot.internal;
|
|
845
848
|
const request = {
|
|
846
849
|
// With msg_type=2, QQ requires the ordinary content field to be empty.
|
|
847
850
|
content: "",
|
|
848
851
|
msg_type: 2,
|
|
849
|
-
markdown: {
|
|
850
|
-
content: !session.isDirect && (hasInlineNavigation || hasInlineHelp) ? `${markdown}
|
|
851
|
-
|
|
852
|
-
点击后如未自动发送,请点发送。` : markdown
|
|
853
|
-
}
|
|
852
|
+
markdown: { content: markdown }
|
|
854
853
|
};
|
|
855
854
|
const age = Date.now() - session.timestamp;
|
|
856
855
|
if (session.messageId && Number.isFinite(age) && age >= 0 && age < 5 * 60 * 1e3 - 2e3) {
|
|
@@ -863,7 +862,63 @@ async function sendQQPage(session, data) {
|
|
|
863
862
|
const pendingAudit = response?.code === 304023 && !!response.data?.message_audit?.audit_id;
|
|
864
863
|
if (!response?.id && !response?.audit_id && !pendingAudit) throw new Error("QQ 未确认消息已接收");
|
|
865
864
|
}
|
|
866
|
-
__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.command}](${inlineCommandUrl(action.command)}) ${action.label}`);
|
|
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");
|
|
867
922
|
|
|
868
923
|
// src/plugin/serverHandel/text.ts
|
|
869
924
|
var handelr_text = class extends handel_base {
|
|
@@ -873,10 +928,11 @@ var handelr_text = class extends handel_base {
|
|
|
873
928
|
async start(data) {
|
|
874
929
|
const content = new AnalysisMsg().parse(data);
|
|
875
930
|
const session = sessions_default.get(sessionKey(data.botId, data.userId, data.channelId));
|
|
876
|
-
if (data.pageNav && canSendQQPage(session, data)) {
|
|
931
|
+
if ((data.transaction || data.pageNav) && canSendQQPage(session, data)) {
|
|
877
932
|
const sendStartedAt = this.timing ? performance.now() : 0;
|
|
878
933
|
try {
|
|
879
|
-
await
|
|
934
|
+
if (data.transaction) await sendQQTransaction(session, data);
|
|
935
|
+
else await sendQQPage(session, data);
|
|
880
936
|
if (this.timing) {
|
|
881
937
|
this.timing.delivery = "qq-markdown";
|
|
882
938
|
this.timing.sendMs = performance.now() - sendStartedAt;
|
|
@@ -885,7 +941,7 @@ var handelr_text = class extends handel_base {
|
|
|
885
941
|
} catch (error) {
|
|
886
942
|
this.ctx.logger.warn("QQ Markdown 发送失败,改用普通文字", error instanceof Error ? error.message : error);
|
|
887
943
|
}
|
|
888
|
-
const commands = pageCommandLines(data.pageNav);
|
|
944
|
+
const commands = !data.transaction && data.pageNav ? pageCommandLines(data.pageNav) : [];
|
|
889
945
|
await this.send(data, commands.length ? `${content.trimEnd()}
|
|
890
946
|
${commands.join("\n")}` : content);
|
|
891
947
|
return;
|
|
@@ -958,7 +1014,7 @@ var import_tsrpc = require("tsrpc");
|
|
|
958
1014
|
|
|
959
1015
|
// src/shared/bot/serviceProto.ts
|
|
960
1016
|
var serviceProto = {
|
|
961
|
-
"version":
|
|
1017
|
+
"version": 12,
|
|
962
1018
|
"services": [
|
|
963
1019
|
{
|
|
964
1020
|
"id": 0,
|
|
@@ -1093,6 +1149,14 @@ var serviceProto = {
|
|
|
1093
1149
|
},
|
|
1094
1150
|
"optional": true
|
|
1095
1151
|
},
|
|
1152
|
+
{
|
|
1153
|
+
"id": 8,
|
|
1154
|
+
"name": "transaction",
|
|
1155
|
+
"type": {
|
|
1156
|
+
"type": "Boolean"
|
|
1157
|
+
},
|
|
1158
|
+
"optional": true
|
|
1159
|
+
},
|
|
1096
1160
|
{
|
|
1097
1161
|
"id": 1,
|
|
1098
1162
|
"name": "line",
|
|
@@ -1550,6 +1614,14 @@ var serviceProto = {
|
|
|
1550
1614
|
}
|
|
1551
1615
|
},
|
|
1552
1616
|
"optional": true
|
|
1617
|
+
},
|
|
1618
|
+
{
|
|
1619
|
+
"id": 12,
|
|
1620
|
+
"name": "authorName",
|
|
1621
|
+
"type": {
|
|
1622
|
+
"type": "String"
|
|
1623
|
+
},
|
|
1624
|
+
"optional": true
|
|
1553
1625
|
}
|
|
1554
1626
|
]
|
|
1555
1627
|
},
|
|
@@ -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>;
|
|
@@ -44,6 +44,7 @@ export interface EquipmentCardData {
|
|
|
44
44
|
rank: string;
|
|
45
45
|
}>;
|
|
46
46
|
notes?: string[];
|
|
47
|
+
authorName?: string;
|
|
47
48
|
}
|
|
48
49
|
export interface LeaderboardImageData {
|
|
49
50
|
backgroundUrl?: string;
|
|
@@ -129,6 +130,8 @@ export interface IMessage {
|
|
|
129
130
|
equipmentCard?: EquipmentCardData;
|
|
130
131
|
leaderboard?: LeaderboardImageData;
|
|
131
132
|
pageNav?: PageNavData;
|
|
133
|
+
/** 通用交易确认、结果与失败消息;客户端可使用交易 Markdown 展示。 */
|
|
134
|
+
transaction?: boolean;
|
|
132
135
|
/** 消息项列表 **/
|
|
133
136
|
line: IMessageItem[];
|
|
134
137
|
}
|
package/package.json
CHANGED
|
@@ -20,6 +20,7 @@ export function equipmentCardHtml(card: EquipmentCardData, artwork?: string, unk
|
|
|
20
20
|
: ''
|
|
21
21
|
const enhancement = Number(card.enhancement) > 0 ? `<span class="enhance"><small>强化</small>+${escape(card.enhancement)}</span>` : ''
|
|
22
22
|
const price = card.referencePrice ? `<div class="price"><span>参考价格</span><strong>${escape(card.referencePrice)}</strong></div>` : ''
|
|
23
|
+
const author = `<span class="author"><small>打造者</small>${escape(card.authorName?.trim() || '未知')}</span>`
|
|
23
24
|
|
|
24
25
|
return `<!doctype html><html><head><meta charset="utf-8"><style>
|
|
25
26
|
*{box-sizing:border-box}
|
|
@@ -29,12 +30,12 @@ html,body{margin:0;width:760px;background:#f1eee5;font-family:'Microsoft YaHei',
|
|
|
29
30
|
#capture:after{content:'';position:absolute;inset:17px;border:1px solid #8c8b6f22;pointer-events:none}
|
|
30
31
|
.heading{display:flex;align-items:center;gap:24px}.heading-copy{flex:1;min-width:0}.overline{font-size:14px;letter-spacing:6px;color:#868a76;margin-bottom:14px}.name{font:700 43px/1.35 'KaiTi','STKaiti','Noto Serif CJK SC','SimSun',serif;margin:0;color:#303a31;overflow-wrap:anywhere}
|
|
31
32
|
.seal{flex-shrink:0;width:66px;min-height:74px;transform:rotate(3deg);padding:8px 3px;color:#a04b40;border:2px solid #a04b40;outline:1px solid #a04b40;outline-offset:-6px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;border-radius:3px 1px 4px 2px}.seal strong{font:700 24px Georgia,'KaiTi',serif;letter-spacing:-1px}.seal span{font:16px 'KaiTi','SimSun',serif;letter-spacing:4px;padding-left:4px}
|
|
32
|
-
.meta{display:flex;flex-wrap:wrap;align-items:baseline;gap:8px 26px;margin:24px 0 0;padding:15px 0;border-top:1px solid #8a8a7144;border-bottom:1px solid #8a8a7128;font-size:19px;color:#535d50;line-height:1.5}.meta .slot{color:#a04b40;font:21px 'KaiTi','SimSun',serif;letter-spacing:2px}.meta small{font-size:16px;color:#92917f;margin-right:9px}
|
|
33
|
+
.meta{display:flex;flex-wrap:wrap;align-items:baseline;gap:8px 26px;margin:24px 0 0;padding:15px 0;border-top:1px solid #8a8a7144;border-bottom:1px solid #8a8a7128;font-size:19px;color:#535d50;line-height:1.5}.meta .slot{color:#a04b40;font:21px 'KaiTi','SimSun',serif;letter-spacing:2px}.meta .author{min-width:0;overflow-wrap:anywhere}.meta small{font-size:16px;color:#92917f;margin-right:9px}
|
|
33
34
|
.art{position:relative;width:610px;height:610px;max-width:100%;margin:18px auto 11px;display:grid;place-items:center;isolation:isolate;overflow:hidden}.art:before{content:'';position:absolute;inset:4%;border-radius:48% 52% 56% 44%;background:radial-gradient(ellipse,#aab4a443,transparent 69%);transform:rotate(-12deg)}.equipment-artwork{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:center;filter:saturate(.72) contrast(.97);mix-blend-mode:multiply;mask-image:linear-gradient(to bottom,transparent 0,#000 8%,#000 84%,transparent 100%),linear-gradient(to right,transparent 0,#000 7%,#000 93%,transparent 100%);mask-composite:intersect}.art-fallback{position:relative;width:360px;height:360px;max-width:90%;display:flex;flex-direction:column;align-items:center;justify-content:center;color:#7d8b7d;border:1px solid #7c887d3b;border-radius:50%;background:radial-gradient(circle,#b1b9a435,transparent 70%);font-family:'KaiTi','SimSun',serif}.art-fallback:before,.art-fallback:after{content:'';position:absolute;inset:21px;border:1px solid #7c887d22;border-radius:50%}.art-fallback:after{inset:48px;border-color:#9a9e8533}.art-fallback .glyph{font-size:126px;line-height:1.2;opacity:.46}.art-fallback .kind{font-size:20px;letter-spacing:4px;opacity:.55}.art:has(.equipment-artwork) .art-fallback{visibility:hidden}.placeholder-note{position:absolute;right:18px;bottom:16px;z-index:2;padding:5px 9px;background:#f4f1e8ce;border:1px solid #9d9b8755;color:#536055;font:18px 'KaiTi','SimSun',serif;letter-spacing:2px}
|
|
34
35
|
.power{display:flex;align-items:center;justify-content:space-between;gap:20px;padding:18px 0 22px;border-top:1px solid #8a8a7128;border-bottom:1px solid #8a8a7144;color:#31463e}.power span{font:24px 'KaiTi','SimSun',serif;letter-spacing:12px}.power strong{font:700 45px/1.1 Georgia,'Times New Roman',serif;letter-spacing:2px;font-variant-numeric:lining-nums}
|
|
35
36
|
.section{margin-top:29px}.section h2{display:flex;align-items:center;gap:16px;margin:0 0 17px;color:#626e5e;font:21px 'KaiTi','SimSun',serif;letter-spacing:5px}.section h2:before{content:'';width:5px;height:17px;background:#a04b40;transform:rotate(8deg);opacity:.75}.section h2:after{content:'';height:1px;flex:1;background:linear-gradient(to right,#7f897c55,transparent)}
|
|
36
37
|
.affixes{display:grid;grid-template-columns:1fr 1fr;column-gap:26px}.affix{display:flex;align-items:baseline;justify-content:space-between;gap:10px;min-width:0;padding:9px 0 12px;border-bottom:1px solid #8a8a7124;font-size:18px;color:#697064}.affix span{overflow-wrap:anywhere}.affix strong{flex-shrink:0;font:700 22px Georgia,'KaiTi',serif;color:#344b42}.empty-line{font-size:18px;color:#92917f;margin:4px 0 12px}
|
|
37
38
|
.skill{display:flex;align-items:baseline;justify-content:space-between;gap:18px;padding:5px 0 10px;font-size:20px;color:#304b40}.skill-name{font-weight:700;overflow-wrap:anywhere}.skill-name i{display:inline-block;width:13px;height:13px;margin:0 17px 1px 4px;background:#a04b40;transform:rotate(45deg)}.rank{font:18px Georgia,'KaiTi',serif;color:#7f8d7b;white-space:nowrap}
|
|
38
39
|
.description p{margin:0;font:22px/1.75 'KaiTi','STKaiti','Noto Serif CJK SC','SimSun',serif;white-space:pre-wrap;overflow-wrap:anywhere;color:#313f38}.price{display:flex;align-items:baseline;gap:16px;margin-top:24px;padding-top:17px;border-top:1px solid #8a8a7128;font-size:18px;color:#8b907d}.price strong{font-size:18px;color:#354c42}.notes{margin-top:17px;padding:10px 15px;border-left:3px solid #a04b4080;background:#e8e8db70;color:#3e5549}.notes p{margin:5px 0;font-size:17px;line-height:1.6;overflow-wrap:anywhere}.edition{display:flex;align-items:center;gap:12px;justify-content:center;margin-top:34px;color:#a6a38e;font:14px 'KaiTi','SimSun',serif;letter-spacing:5px}.edition:before,.edition:after{content:'';height:1px;width:32px;background:#9b9c833d}
|
|
39
|
-
</style></head><body><div id="capture" class="equipment-sheet ink-equipment-sheet"><header><div class="heading"><div class="heading-copy"><div class="overline">装 备 图 录</div><h1 class="name">${escape(card.name)}</h1></div><div class="seal"><strong>${escape(card.quality)}</strong><span>品阶</span></div></div><div class="meta"><span class="slot">${escape(card.slot)}</span>${enhancement}</div></header><div class="art"><div class="art-fallback"><span class="glyph">?</span><span class="kind">未知外观</span></div>${artwork ? `<img class="skill-artwork equipment-artwork" alt="" src="${escape(artwork)}">` : ''}${unknownFallback || !artwork ? '<span class="placeholder-note">外观暂未显现</span>' : ''}</div><div class="power"><span>装 备 战 力</span><strong>${escape(power)}</strong></div><section class="section attributes"><h2>附加属性</h2><div class="affixes">${affixes}</div></section>${skills}${description}${price}${notes}<div class="edition">露 · 装备录</div></div></body></html>`
|
|
40
|
+
</style></head><body><div id="capture" class="equipment-sheet ink-equipment-sheet"><header><div class="heading"><div class="heading-copy"><div class="overline">装 备 图 录</div><h1 class="name">${escape(card.name)}</h1></div><div class="seal"><strong>${escape(card.quality)}</strong><span>品阶</span></div></div><div class="meta"><span class="slot">${escape(card.slot)}</span>${enhancement}${author}</div></header><div class="art"><div class="art-fallback"><span class="glyph">?</span><span class="kind">未知外观</span></div>${artwork ? `<img class="skill-artwork equipment-artwork" alt="" src="${escape(artwork)}">` : ''}${unknownFallback || !artwork ? '<span class="placeholder-note">外观暂未显现</span>' : ''}</div><div class="power"><span>装 备 战 力</span><strong>${escape(power)}</strong></div><section class="section attributes"><h2>附加属性</h2><div class="affixes">${affixes}</div></section>${skills}${description}${price}${notes}<div class="edition">露 · 装备录</div></div></body></html>`
|
|
40
41
|
}
|
|
@@ -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 =>
|
|
@@ -129,7 +129,10 @@ export function renderQQPageMarkdown(data: MsgMessage, showGreeting: boolean): s
|
|
|
129
129
|
hasPageSummary = true
|
|
130
130
|
break
|
|
131
131
|
}
|
|
132
|
-
|
|
132
|
+
const command = entryCommands.get(lineIndex)
|
|
133
|
+
bodyLines.push(command
|
|
134
|
+
? `[${escapeMarkdown(compactDisplayText(item.content))}](${inlineCommandUrl(command)})`
|
|
135
|
+
: escapeMarkdown(item.content))
|
|
133
136
|
break
|
|
134
137
|
}
|
|
135
138
|
case MessageType.error:
|
|
@@ -164,20 +167,18 @@ export function canSendQQPage(session: Session | undefined, data: MsgMessage): s
|
|
|
164
167
|
|
|
165
168
|
/** Send a native QQ Markdown page with inline command links. */
|
|
166
169
|
export async function sendQQPage(session: Session, data: MsgMessage): Promise<void> {
|
|
167
|
-
const internal = (session.bot as typeof session.bot & { internal: QQInternal }).internal
|
|
168
|
-
const nav = data.pageNav!
|
|
169
170
|
const markdown = renderQQPageMarkdown(data, session.event?.channel?.type === 0)
|
|
170
|
-
|
|
171
|
-
|
|
171
|
+
await sendQQMarkdown(session, markdown)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Reuse the adapter's native reply API for structured Markdown messages. */
|
|
175
|
+
export async function sendQQMarkdown(session: Session, markdown: string): Promise<void> {
|
|
176
|
+
const internal = (session.bot as typeof session.bot & { internal: QQInternal }).internal
|
|
172
177
|
const request: QQRequest = {
|
|
173
178
|
// With msg_type=2, QQ requires the ordinary content field to be empty.
|
|
174
179
|
content: '',
|
|
175
180
|
msg_type: 2,
|
|
176
|
-
markdown: {
|
|
177
|
-
content: !session.isDirect && (hasInlineNavigation || hasInlineHelp)
|
|
178
|
-
? `${markdown}\n\n点击后如未自动发送,请点发送。`
|
|
179
|
-
: markdown,
|
|
180
|
-
},
|
|
181
|
+
markdown: { content: markdown },
|
|
181
182
|
}
|
|
182
183
|
// Match the QQ adapter's passive-reply sequence.
|
|
183
184
|
const age = Date.now() - session.timestamp
|
|
@@ -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.command}](${inlineCommandUrl(action.command)}) ${action.label}`)
|
|
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,17 @@ 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'
|
|
6
7
|
|
|
7
8
|
export default class handelr_text extends handel_base {
|
|
8
9
|
async start(data: MsgMessage) {
|
|
9
10
|
const content = new AnalysisMsg().parse(data)
|
|
10
11
|
const session = sessions.get(sessionKey(data.botId, data.userId, data.channelId))
|
|
11
|
-
if (data.pageNav && canSendQQPage(session, data)) {
|
|
12
|
+
if ((data.transaction || data.pageNav) && canSendQQPage(session, data)) {
|
|
12
13
|
const sendStartedAt = this.timing ? performance.now() : 0
|
|
13
14
|
try {
|
|
14
|
-
await
|
|
15
|
+
if (data.transaction) await sendQQTransaction(session, data)
|
|
16
|
+
else await sendQQPage(session, data)
|
|
15
17
|
if (this.timing) {
|
|
16
18
|
this.timing.delivery = 'qq-markdown'
|
|
17
19
|
this.timing.sendMs = performance.now() - sendStartedAt
|
|
@@ -20,7 +22,7 @@ export default class handelr_text extends handel_base {
|
|
|
20
22
|
} catch (error) {
|
|
21
23
|
this.ctx.logger.warn('QQ Markdown 发送失败,改用普通文字', error instanceof Error ? error.message : error)
|
|
22
24
|
}
|
|
23
|
-
const commands = pageCommandLines(data.pageNav)
|
|
25
|
+
const commands = !data.transaction && data.pageNav ? pageCommandLines(data.pageNav) : []
|
|
24
26
|
await this.send(data, commands.length ? `${content.trimEnd()}\n${commands.join('\n')}` : content)
|
|
25
27
|
return
|
|
26
28
|
}
|
|
@@ -20,7 +20,7 @@ export interface ServiceType {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export const serviceProto: ServiceProto<ServiceType> = {
|
|
23
|
-
"version":
|
|
23
|
+
"version": 12,
|
|
24
24
|
"services": [
|
|
25
25
|
{
|
|
26
26
|
"id": 0,
|
|
@@ -155,6 +155,14 @@ 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
|
+
},
|
|
158
166
|
{
|
|
159
167
|
"id": 1,
|
|
160
168
|
"name": "line",
|
|
@@ -612,6 +620,14 @@ export const serviceProto: ServiceProto<ServiceType> = {
|
|
|
612
620
|
}
|
|
613
621
|
},
|
|
614
622
|
"optional": true
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
"id": 12,
|
|
626
|
+
"name": "authorName",
|
|
627
|
+
"type": {
|
|
628
|
+
"type": "String"
|
|
629
|
+
},
|
|
630
|
+
"optional": true
|
|
615
631
|
}
|
|
616
632
|
]
|
|
617
633
|
},
|
|
@@ -1042,4 +1058,4 @@ export const serviceProto: ServiceProto<ServiceType> = {
|
|
|
1042
1058
|
]
|
|
1043
1059
|
}
|
|
1044
1060
|
}
|
|
1045
|
-
};
|
|
1061
|
+
};
|
package/src/shared/face/index.ts
CHANGED
|
@@ -23,6 +23,7 @@ export interface EquipmentCardData {
|
|
|
23
23
|
affixes: Array<{ name: string; value: string }>;
|
|
24
24
|
skills: Array<{ name: string; rank: string }>;
|
|
25
25
|
notes?: string[];
|
|
26
|
+
authorName?: string;
|
|
26
27
|
}
|
|
27
28
|
export interface LeaderboardImageData {
|
|
28
29
|
backgroundUrl?: string;
|
|
@@ -102,6 +103,8 @@ export interface IMessage {
|
|
|
102
103
|
equipmentCard?: EquipmentCardData;
|
|
103
104
|
leaderboard?: LeaderboardImageData;
|
|
104
105
|
pageNav?: PageNavData;
|
|
106
|
+
/** 通用交易确认、结果与失败消息;客户端可使用交易 Markdown 展示。 */
|
|
107
|
+
transaction?: boolean;
|
|
105
108
|
/** 消息项列表 **/
|
|
106
109
|
line: IMessageItem[];
|
|
107
110
|
}
|