koishi-plugin-dew-bot 1.0.246 → 1.0.249
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 +16 -53
- package/lib/index.d.ts +1 -0
- package/lib/index.js +68 -77
- package/lib/plugin/serverHandel/handel_base.d.ts +1 -1
- package/lib/plugin/serverHandel/qqPageMessage.d.ts +3 -4
- package/lib/shared/face/index.d.ts +2 -1
- package/package.json +16 -4
- package/src/index.ts +17 -1
- package/src/plugin/equipmentCardTemplate.ts +3 -2
- package/src/plugin/serverHandel/handel_base.ts +1 -1
- package/src/plugin/serverHandel/qqPageMessage.ts +47 -70
- package/src/plugin/serverHandel/text.ts +1 -13
- package/src/shared/bot/serviceProto.ts +10 -2
- package/src/shared/face/index.ts +2 -1
package/README.md
CHANGED
|
@@ -1,53 +1,16 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
## 配置
|
|
18
|
-
|
|
19
|
-
| 项 | 说明 | 默认 |
|
|
20
|
-
| --- | --- | --- |
|
|
21
|
-
| 服务器地址 | 游戏服 WebSocket 地址 | `ws://dew-v7-test:3021` |
|
|
22
|
-
|
|
23
|
-
插件会忽略机器人自己发出的消息,避免回显再次上传形成循环。
|
|
24
|
-
|
|
25
|
-
## 发布到 npm
|
|
26
|
-
|
|
27
|
-
在 `bot/external/dew-bot` 目录执行:
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
npm run release
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
脚本会先从 `bot` 工作区执行 `npm run build -- dew-bot`,构建成功后运行 `npm version patch --no-git-tag-version --workspaces=false`,再以 `public` 权限和 `latest` 标签发布到 `https://registry.npmjs.org`。例如 `1.0.245` 会提升为 `1.0.246`,不会创建 Git 提交或标签。发布前需已安装工作区依赖,并登录有该包发布权限的 npm 账号:
|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
|
-
npm login --registry=https://registry.npmjs.org
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
仅构建并检查包内容,执行:
|
|
40
|
-
|
|
41
|
-
```bash
|
|
42
|
-
npm run release:check
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
这相当于 `npm run release -- --dry-run`,会真实构建并执行 `npm pack --dry-run`,不会提升版本号、生成压缩包或发布到 npm。
|
|
46
|
-
|
|
47
|
-
构建失败时脚本会停止,版本不递增。若递增后发布失败,本地保留新版本,脚本会以非零退出码结束并打印当前版本及重试命令;在插件目录确认该版本的发布状态后,可重试同一版本:
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
npm publish --workspaces=false --access=public --tag=latest --registry=https://registry.npmjs.org
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
重试发布时不要再次运行 `npm run release`,否则会再次递增版本。
|
|
1
|
+
# 🌀 DEW · 无限轮回 RPG
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/koishi-plugin-dew-bot)
|
|
4
|
+

|
|
5
|
+
|
|
6
|
+
> **下一场轮回,主角是你。**
|
|
7
|
+
|
|
8
|
+
**DEW Bot** 是一款基于《无限恐怖》小说改编的 **RPG 机器人**。
|
|
9
|
+
|
|
10
|
+
踏入主神空间,成为轮回者。与伙伴迎接未知的试炼,在生死之间积蓄力量,书写属于你的无限冒险。
|
|
11
|
+
|
|
12
|
+
无论你熟悉原作,还是初次踏入无限流世界,这场轮回,都有你的位置。
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
连接 DEW 游戏服后,在聊天中发送 **帮助**,开启第一场冒险。
|
package/lib/index.d.ts
CHANGED
package/lib/index.js
CHANGED
|
@@ -33,7 +33,8 @@ __export(src_exports, {
|
|
|
33
33
|
Config: () => Config,
|
|
34
34
|
apply: () => apply2,
|
|
35
35
|
inject: () => inject,
|
|
36
|
-
name: () => name
|
|
36
|
+
name: () => name,
|
|
37
|
+
usage: () => usage
|
|
37
38
|
});
|
|
38
39
|
module.exports = __toCommonJS(src_exports);
|
|
39
40
|
var import_koishi2 = require("koishi");
|
|
@@ -309,6 +310,7 @@ function equipmentCardHtml(card, artwork, unknownFallback = false) {
|
|
|
309
310
|
const notes = card.notes?.filter(Boolean).length ? `<div class="notes">${card.notes.filter(Boolean).map((note) => `<p>${escape6(note)}</p>`).join("")}</div>` : "";
|
|
310
311
|
const enhancement = Number(card.enhancement) > 0 ? `<span class="enhance"><small>强化</small>+${escape6(card.enhancement)}</span>` : "";
|
|
311
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>`;
|
|
312
314
|
return `<!doctype html><html><head><meta charset="utf-8"><style>
|
|
313
315
|
*{box-sizing:border-box}
|
|
314
316
|
html,body{margin:0;width:760px;background:#f1eee5;font-family:'Microsoft YaHei','Noto Sans CJK SC',sans-serif;color:#343b35}
|
|
@@ -317,14 +319,14 @@ html,body{margin:0;width:760px;background:#f1eee5;font-family:'Microsoft YaHei',
|
|
|
317
319
|
#capture:after{content:'';position:absolute;inset:17px;border:1px solid #8c8b6f22;pointer-events:none}
|
|
318
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}
|
|
319
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}
|
|
320
|
-
.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}
|
|
321
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}
|
|
322
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}
|
|
323
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)}
|
|
324
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}
|
|
325
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}
|
|
326
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}
|
|
327
|
-
</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>`;
|
|
328
330
|
}
|
|
329
331
|
__name(equipmentCardHtml, "equipmentCardHtml");
|
|
330
332
|
|
|
@@ -717,23 +719,28 @@ function escapeMarkdown(value) {
|
|
|
717
719
|
return value.replace(/[\\`*_{}\[\]()#+\-.!|>~<&]/g, "\\$&");
|
|
718
720
|
}
|
|
719
721
|
__name(escapeMarkdown, "escapeMarkdown");
|
|
720
|
-
function
|
|
721
|
-
return
|
|
722
|
+
function compactDisplayText(value) {
|
|
723
|
+
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, "");
|
|
722
724
|
}
|
|
723
|
-
__name(
|
|
725
|
+
__name(compactDisplayText, "compactDisplayText");
|
|
726
|
+
function compactListLabel(value) {
|
|
727
|
+
const label = compactDisplayText(value);
|
|
728
|
+
return /[📖📚]/u.test(label) ? label.replace(/技能书(?=$|×\d+(?:\.\d+)?(?:||$)||)/g, "") : label;
|
|
729
|
+
}
|
|
730
|
+
__name(compactListLabel, "compactListLabel");
|
|
724
731
|
function inlineCommandUrl(command) {
|
|
725
|
-
const encoded = encodeURIComponent(
|
|
732
|
+
const encoded = encodeURIComponent(command).replace(/[!'()*]/g, (character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`);
|
|
726
733
|
return `mqqapi://aio/inlinecmd?command=${encoded}&enter=true&reply=false`;
|
|
727
734
|
}
|
|
728
735
|
__name(inlineCommandUrl, "inlineCommandUrl");
|
|
729
736
|
function pageCommandLines(nav) {
|
|
730
737
|
return [
|
|
731
|
-
...nav.previousCommand ? [
|
|
732
|
-
...nav.nextCommand ? [
|
|
738
|
+
...nav.previousCommand ? [nav.previousCommand] : [],
|
|
739
|
+
...nav.nextCommand ? [nav.nextCommand] : []
|
|
733
740
|
];
|
|
734
741
|
}
|
|
735
742
|
__name(pageCommandLines, "pageCommandLines");
|
|
736
|
-
function renderQQPageMarkdown(data, showGreeting
|
|
743
|
+
function renderQQPageMarkdown(data, showGreeting) {
|
|
737
744
|
const nav = data.pageNav;
|
|
738
745
|
const entryCommands = new Map(nav?.entries?.map((entry) => [entry.lineIndex, entry.viewCommand]) ?? []);
|
|
739
746
|
const listPattern = /^\[(\d+)\]\s*([^\r\n]+)$/;
|
|
@@ -744,7 +751,7 @@ function renderQQPageMarkdown(data, showGreeting, showCommands) {
|
|
|
744
751
|
const listCount = data.line.filter((item) => item.type === 1 /* line */ && listPattern.test(item.content)).length;
|
|
745
752
|
const count = summary?.[1] ?? (nav?.total === 1 ? String(listCount) : void 0);
|
|
746
753
|
const countUnit = summary?.[2] ?? "项";
|
|
747
|
-
const isLeaderboardDirectory = !!nav?.entries?.length &&
|
|
754
|
+
const isLeaderboardDirectory = !!nav?.entries?.length && /^(?:🏆)?排行榜$/u.test(compactDisplayText(title?.content ?? ""));
|
|
748
755
|
const sections = [];
|
|
749
756
|
const bodyLines = [];
|
|
750
757
|
const listLines = [];
|
|
@@ -752,7 +759,7 @@ function renderQQPageMarkdown(data, showGreeting, showCommands) {
|
|
|
752
759
|
if (bodyLines.length) sections.push(bodyLines.splice(0).join(" \n"));
|
|
753
760
|
}, "flushBody");
|
|
754
761
|
const flushList = /* @__PURE__ */ __name(() => {
|
|
755
|
-
if (listLines.length) sections.push(listLines.splice(0).join("
|
|
762
|
+
if (listLines.length) sections.push(listLines.splice(0).join("\n"));
|
|
756
763
|
}, "flushList");
|
|
757
764
|
const flush = /* @__PURE__ */ __name(() => {
|
|
758
765
|
flushList();
|
|
@@ -767,23 +774,27 @@ function renderQQPageMarkdown(data, showGreeting, showCommands) {
|
|
|
767
774
|
case 0 /* title */:
|
|
768
775
|
flush();
|
|
769
776
|
if (isLeaderboardDirectory) {
|
|
770
|
-
sections.push(`## ${item.content.includes("🏆") ? "" : "🏆
|
|
771
|
-
sections.push(`>
|
|
777
|
+
sections.push(`## ${item.content.includes("🏆") ? "" : "🏆"}${escapeMarkdown(compactDisplayText(item.content))}`);
|
|
778
|
+
sections.push(`> 共${nav.entries.length}个榜单·点击榜名查看`);
|
|
772
779
|
} else {
|
|
773
|
-
sections.push(`**${escapeMarkdown(item.content)}${count !== void 0 ?
|
|
780
|
+
sections.push(`**${escapeMarkdown(compactDisplayText(item.content))}${count !== void 0 ? `·${count}${countUnit}` : ""}**`);
|
|
774
781
|
}
|
|
775
782
|
break;
|
|
776
783
|
case 1 /* line */: {
|
|
777
784
|
const listEntry = listPattern.exec(item.content);
|
|
778
785
|
if (listEntry) {
|
|
779
786
|
flushBody();
|
|
780
|
-
const
|
|
781
|
-
const label = escapeMarkdown(listEntry[2]);
|
|
782
|
-
const name2 =
|
|
783
|
-
listLines.push(`${
|
|
787
|
+
const command2 = entryCommands.get(lineIndex);
|
|
788
|
+
const label = escapeMarkdown(compactListLabel(listEntry[2]));
|
|
789
|
+
const name2 = command2 ? `[${label}](${inlineCommandUrl(command2)})` : label;
|
|
790
|
+
listLines.push(`${listEntry[1]}. ${name2}`);
|
|
784
791
|
break;
|
|
785
792
|
}
|
|
786
793
|
flushList();
|
|
794
|
+
if (nav && item.content.trim() === "[上一页] [下一页]") {
|
|
795
|
+
flushBody();
|
|
796
|
+
break;
|
|
797
|
+
}
|
|
787
798
|
if (/^\[指令\]相关指令$/.test(item.content)) {
|
|
788
799
|
flushBody();
|
|
789
800
|
hasHelp = true;
|
|
@@ -792,11 +803,12 @@ function renderQQPageMarkdown(data, showGreeting, showCommands) {
|
|
|
792
803
|
const page = pagePattern.exec(item.content);
|
|
793
804
|
if (page && nav) {
|
|
794
805
|
flushBody();
|
|
795
|
-
pageFooter =
|
|
806
|
+
pageFooter = `**第${nav.current}/${nav.total}页${!title && page[1] ? `·共${page[1]}${page[2]}` : ""}**`;
|
|
796
807
|
hasPageSummary = true;
|
|
797
808
|
break;
|
|
798
809
|
}
|
|
799
|
-
|
|
810
|
+
const command = entryCommands.get(lineIndex);
|
|
811
|
+
bodyLines.push(command ? `[${escapeMarkdown(compactDisplayText(item.content))}](${inlineCommandUrl(command)})` : escapeMarkdown(item.content));
|
|
800
812
|
break;
|
|
801
813
|
}
|
|
802
814
|
case 2 /* error */:
|
|
@@ -807,42 +819,18 @@ function renderQQPageMarkdown(data, showGreeting, showCommands) {
|
|
|
807
819
|
}
|
|
808
820
|
flush();
|
|
809
821
|
if (nav && !hasPageSummary && (nav.total > 1 || nav.previousCommand || nav.nextCommand))
|
|
810
|
-
pageFooter =
|
|
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;
|
|
811
825
|
const helpLink = hasHelp ? `[指令](${inlineCommandUrl("指令")})` : void 0;
|
|
812
|
-
const footer = pageFooter ? [pageFooter, helpLink].filter(Boolean).join("
|
|
826
|
+
const footer = pageFooter ? [pageFooter, previousLink, nextLink, helpLink].filter(Boolean).join("·") : helpLink ? `更多操作:${helpLink}` : void 0;
|
|
813
827
|
if (footer) {
|
|
814
828
|
if (isLeaderboardDirectory) sections.push("***");
|
|
815
829
|
sections.push(footer);
|
|
816
830
|
}
|
|
817
|
-
if (showCommands && nav) {
|
|
818
|
-
const commands = pageCommandLines(nav);
|
|
819
|
-
if (commands.length) sections.push(`**翻页指令**
|
|
820
|
-
${commands.map(escapeMarkdown).join(" \n")}`);
|
|
821
|
-
}
|
|
822
831
|
return sections.join("\n\n") || " ";
|
|
823
832
|
}
|
|
824
833
|
__name(renderQQPageMarkdown, "renderQQPageMarkdown");
|
|
825
|
-
function makeButton(id, label, command, userId, autoEnter) {
|
|
826
|
-
return {
|
|
827
|
-
id,
|
|
828
|
-
render_data: { label, visited_label: label, style: 1 },
|
|
829
|
-
action: {
|
|
830
|
-
type: 2,
|
|
831
|
-
permission: { type: 0, specify_user_ids: [userId] },
|
|
832
|
-
data: pageCommand(command),
|
|
833
|
-
...autoEnter ? { enter: true } : {},
|
|
834
|
-
unsupport_tips: "当前客户端暂不支持该按钮,请发送上一页或下一页。"
|
|
835
|
-
}
|
|
836
|
-
};
|
|
837
|
-
}
|
|
838
|
-
__name(makeButton, "makeButton");
|
|
839
|
-
function buttonsFor(nav, userId, autoEnter) {
|
|
840
|
-
return [
|
|
841
|
-
...nav.previousCommand ? [makeButton("previous", "上一页", nav.previousCommand, userId, autoEnter)] : [],
|
|
842
|
-
...nav.nextCommand ? [makeButton("next", "下一页", nav.nextCommand, userId, autoEnter)] : []
|
|
843
|
-
];
|
|
844
|
-
}
|
|
845
|
-
__name(buttonsFor, "buttonsFor");
|
|
846
834
|
function canSendQQPage(session, data) {
|
|
847
835
|
if (!session || session.bot.platform !== "qq" || session.bot.userId !== data.botId) return false;
|
|
848
836
|
if (session.userId !== data.userId || (session.channelId ?? session.userId) !== data.channelId) return false;
|
|
@@ -850,23 +838,14 @@ function canSendQQPage(session, data) {
|
|
|
850
838
|
return !!internal?.sendMessage && !!internal.sendPrivateMessage;
|
|
851
839
|
}
|
|
852
840
|
__name(canSendQQPage, "canSendQQPage");
|
|
853
|
-
async function sendQQPage(session, data
|
|
841
|
+
async function sendQQPage(session, data) {
|
|
854
842
|
const internal = session.bot.internal;
|
|
855
|
-
const
|
|
856
|
-
const buttons = withKeyboard ? buttonsFor(nav, data.userId, session.isDirect) : [];
|
|
857
|
-
const markdown = renderQQPageMarkdown(data, session.event?.channel?.type === 0, buttons.length === 0);
|
|
858
|
-
const hasEntries = !!nav.entries?.length;
|
|
859
|
-
const hasInlineHelp = data.line.some((item) => item.type === 1 /* line */ && item.content === "[指令]相关指令");
|
|
843
|
+
const markdown = renderQQPageMarkdown(data, session.event?.channel?.type === 0);
|
|
860
844
|
const request = {
|
|
861
845
|
// With msg_type=2, QQ requires the ordinary content field to be empty.
|
|
862
846
|
content: "",
|
|
863
847
|
msg_type: 2,
|
|
864
|
-
markdown: {
|
|
865
|
-
content: !session.isDirect && (buttons.length || hasEntries || hasInlineHelp) ? `${markdown}
|
|
866
|
-
|
|
867
|
-
点击后如未自动发送,请点发送。` : markdown
|
|
868
|
-
},
|
|
869
|
-
...buttons.length ? { keyboard: { content: { rows: [{ buttons }] } } } : {}
|
|
848
|
+
markdown: { content: markdown }
|
|
870
849
|
};
|
|
871
850
|
const age = Date.now() - session.timestamp;
|
|
872
851
|
if (session.messageId && Number.isFinite(age) && age >= 0 && age < 5 * 60 * 1e3 - 2e3) {
|
|
@@ -891,20 +870,8 @@ var handelr_text = class extends handel_base {
|
|
|
891
870
|
const session = sessions_default.get(sessionKey(data.botId, data.userId, data.channelId));
|
|
892
871
|
if (data.pageNav && canSendQQPage(session, data)) {
|
|
893
872
|
const sendStartedAt = this.timing ? performance.now() : 0;
|
|
894
|
-
if (data.pageNav.previousCommand || data.pageNav.nextCommand) {
|
|
895
|
-
try {
|
|
896
|
-
await sendQQPage(session, data, true);
|
|
897
|
-
if (this.timing) {
|
|
898
|
-
this.timing.delivery = "qq-markdown-keyboard";
|
|
899
|
-
this.timing.sendMs = performance.now() - sendStartedAt;
|
|
900
|
-
}
|
|
901
|
-
return;
|
|
902
|
-
} catch (error) {
|
|
903
|
-
this.ctx.logger.warn("QQ 翻页按钮发送失败,改用 Markdown 文字指令", error instanceof Error ? error.message : error);
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
873
|
try {
|
|
907
|
-
await sendQQPage(session, data
|
|
874
|
+
await sendQQPage(session, data);
|
|
908
875
|
if (this.timing) {
|
|
909
876
|
this.timing.delivery = "qq-markdown";
|
|
910
877
|
this.timing.sendMs = performance.now() - sendStartedAt;
|
|
@@ -986,7 +953,7 @@ var import_tsrpc = require("tsrpc");
|
|
|
986
953
|
|
|
987
954
|
// src/shared/bot/serviceProto.ts
|
|
988
955
|
var serviceProto = {
|
|
989
|
-
"version":
|
|
956
|
+
"version": 11,
|
|
990
957
|
"services": [
|
|
991
958
|
{
|
|
992
959
|
"id": 0,
|
|
@@ -1578,6 +1545,14 @@ var serviceProto = {
|
|
|
1578
1545
|
}
|
|
1579
1546
|
},
|
|
1580
1547
|
"optional": true
|
|
1548
|
+
},
|
|
1549
|
+
{
|
|
1550
|
+
"id": 12,
|
|
1551
|
+
"name": "authorName",
|
|
1552
|
+
"type": {
|
|
1553
|
+
"type": "String"
|
|
1554
|
+
},
|
|
1555
|
+
"optional": true
|
|
1581
1556
|
}
|
|
1582
1557
|
]
|
|
1583
1558
|
},
|
|
@@ -2170,8 +2145,23 @@ __name(apply, "apply");
|
|
|
2170
2145
|
// src/index.ts
|
|
2171
2146
|
var inject = { optional: ["puppeteer"] };
|
|
2172
2147
|
var name = "dew-bot";
|
|
2148
|
+
var usage = `
|
|
2149
|
+
## 🌀 DEW · 无限轮回 RPG
|
|
2150
|
+
|
|
2151
|
+
> **下一场轮回,主角是你。**
|
|
2152
|
+
|
|
2153
|
+
**DEW Bot** 是一款基于《无限恐怖》小说改编的 **RPG 机器人**。
|
|
2154
|
+
|
|
2155
|
+
踏入主神空间,成为轮回者。与伙伴迎接未知的试炼,在生死之间积蓄力量,书写属于你的无限冒险。
|
|
2156
|
+
|
|
2157
|
+
无论你熟悉原作,还是初次踏入无限流世界,这场轮回,都有你的位置。
|
|
2158
|
+
|
|
2159
|
+
---
|
|
2160
|
+
|
|
2161
|
+
连接 DEW 游戏服后,在聊天中发送 **帮助**,开启第一场冒险。
|
|
2162
|
+
`;
|
|
2173
2163
|
var Config = import_koishi2.Schema.object({
|
|
2174
|
-
服务器地址: import_koishi2.Schema.string().default("ws://dew-v7-test:3021").description("
|
|
2164
|
+
服务器地址: import_koishi2.Schema.string().default("ws://dew-v7-test:3021").description("DEW 游戏服的 WebSocket 地址;同机部署可填写 ws://127.0.0.1:3021")
|
|
2175
2165
|
});
|
|
2176
2166
|
function apply2(ctx, config) {
|
|
2177
2167
|
ctx.plugin(apply, config);
|
|
@@ -2182,5 +2172,6 @@ __name(apply2, "apply");
|
|
|
2182
2172
|
Config,
|
|
2183
2173
|
apply,
|
|
2184
2174
|
inject,
|
|
2185
|
-
name
|
|
2175
|
+
name,
|
|
2176
|
+
usage
|
|
2186
2177
|
});
|
|
@@ -7,7 +7,7 @@ export interface MessageTiming {
|
|
|
7
7
|
renderMs?: number;
|
|
8
8
|
imageBytes?: number;
|
|
9
9
|
sendMs?: number;
|
|
10
|
-
delivery?: 'image' | 'text' | 'qq-markdown
|
|
10
|
+
delivery?: 'image' | 'text' | 'qq-markdown' | 'missing-session';
|
|
11
11
|
}
|
|
12
12
|
export default class handel_base {
|
|
13
13
|
ctx: Context;
|
|
@@ -1,10 +1,9 @@
|
|
|
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
|
-
export declare function pageCommand(command: string): string;
|
|
5
4
|
export declare function pageCommandLines(nav: PageNavData): string[];
|
|
6
|
-
export declare function renderQQPageMarkdown(data: MsgMessage, showGreeting: boolean
|
|
5
|
+
export declare function renderQQPageMarkdown(data: MsgMessage, showGreeting: boolean): string;
|
|
7
6
|
/** Returns false if this is not the matching QQ session or the adapter has no native API. */
|
|
8
7
|
export declare function canSendQQPage(session: Session | undefined, data: MsgMessage): session is Session;
|
|
9
|
-
/** Send a native QQ Markdown page
|
|
10
|
-
export declare function sendQQPage(session: Session, data: MsgMessage
|
|
8
|
+
/** Send a native QQ Markdown page with inline command links. */
|
|
9
|
+
export declare function sendQQPage(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;
|
|
@@ -108,7 +109,7 @@ export interface IErrorMessage {
|
|
|
108
109
|
}
|
|
109
110
|
/** 第一阶段支持的消息项 **/
|
|
110
111
|
export type IMessageItem = ITitleMessage | ILineMessage | IErrorMessage;
|
|
111
|
-
/**
|
|
112
|
+
/** 平台无关的翻页动作;客户端决定如何呈现可点击文字,点击后发送普通指令。 */
|
|
112
113
|
export interface PageNavData {
|
|
113
114
|
current: number;
|
|
114
115
|
total: number;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koishi-plugin-dew-bot",
|
|
3
|
-
"description": "
|
|
4
|
-
"version": "1.0.
|
|
3
|
+
"description": "基于《无限恐怖》小说改编的 RPG 机器人。踏入主神空间,与伙伴迎接轮回试炼,书写属于你的无限冒险。",
|
|
4
|
+
"version": "1.0.249",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"typings": "lib/index.d.ts",
|
|
7
7
|
"files": [
|
|
@@ -14,10 +14,22 @@
|
|
|
14
14
|
"release:check": "node scripts/release.cjs --dry-run"
|
|
15
15
|
},
|
|
16
16
|
"license": "MIT",
|
|
17
|
+
"homepage": "https://gitee.com/ChuXuanD30/koishi-plugin-dew-bot",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://gitee.com/ChuXuanD30/koishi-plugin-dew-bot.git"
|
|
21
|
+
},
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://gitee.com/ChuXuanD30/koishi-plugin-dew-bot/issues"
|
|
24
|
+
},
|
|
17
25
|
"keywords": [
|
|
18
26
|
"chatbot",
|
|
19
27
|
"koishi",
|
|
20
|
-
"plugin"
|
|
28
|
+
"plugin",
|
|
29
|
+
"rpg",
|
|
30
|
+
"game",
|
|
31
|
+
"qq",
|
|
32
|
+
"markdown"
|
|
21
33
|
],
|
|
22
34
|
"peerDependencies": {
|
|
23
35
|
"koishi": "^4.18.0",
|
|
@@ -29,7 +41,7 @@
|
|
|
29
41
|
},
|
|
30
42
|
"koishi": {
|
|
31
43
|
"description": {
|
|
32
|
-
"zh": "
|
|
44
|
+
"zh": "基于《无限恐怖》小说改编的 RPG 机器人。踏入主神空间,与伙伴迎接轮回试炼,书写属于你的无限冒险。"
|
|
33
45
|
}
|
|
34
46
|
}
|
|
35
47
|
}
|
package/src/index.ts
CHANGED
|
@@ -5,12 +5,28 @@ export const inject = { optional: ['puppeteer'] }
|
|
|
5
5
|
|
|
6
6
|
export const name = 'dew-bot'
|
|
7
7
|
|
|
8
|
+
export const usage: string = `
|
|
9
|
+
## 🌀 DEW · 无限轮回 RPG
|
|
10
|
+
|
|
11
|
+
> **下一场轮回,主角是你。**
|
|
12
|
+
|
|
13
|
+
**DEW Bot** 是一款基于《无限恐怖》小说改编的 **RPG 机器人**。
|
|
14
|
+
|
|
15
|
+
踏入主神空间,成为轮回者。与伙伴迎接未知的试炼,在生死之间积蓄力量,书写属于你的无限冒险。
|
|
16
|
+
|
|
17
|
+
无论你熟悉原作,还是初次踏入无限流世界,这场轮回,都有你的位置。
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
连接 DEW 游戏服后,在聊天中发送 **帮助**,开启第一场冒险。
|
|
22
|
+
`
|
|
23
|
+
|
|
8
24
|
export interface Config {
|
|
9
25
|
服务器地址: string
|
|
10
26
|
}
|
|
11
27
|
|
|
12
28
|
export const Config: Schema<Config> = Schema.object({
|
|
13
|
-
服务器地址: Schema.string().default('ws://dew-v7-test:3021').description('
|
|
29
|
+
服务器地址: Schema.string().default('ws://dew-v7-test:3021').description('DEW 游戏服的 WebSocket 地址;同机部署可填写 ws://127.0.0.1:3021'),
|
|
14
30
|
})
|
|
15
31
|
|
|
16
32
|
export function apply(ctx: Context, config: Config) {
|
|
@@ -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
|
}
|
|
@@ -9,7 +9,7 @@ export interface MessageTiming {
|
|
|
9
9
|
renderMs?: number
|
|
10
10
|
imageBytes?: number
|
|
11
11
|
sendMs?: number
|
|
12
|
-
delivery?: 'image' | 'text' | 'qq-markdown
|
|
12
|
+
delivery?: 'image' | 'text' | 'qq-markdown' | 'missing-session'
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export default class handel_base {
|
|
@@ -2,23 +2,10 @@ import type { Session } from 'koishi'
|
|
|
2
2
|
import { MessageType, type PageNavData } from '../../shared/face'
|
|
3
3
|
import type { MsgMessage } from '../../shared/bot/MsgMessage'
|
|
4
4
|
|
|
5
|
-
type Button = {
|
|
6
|
-
id: string
|
|
7
|
-
render_data: { label: string; visited_label: string; style: number }
|
|
8
|
-
action: {
|
|
9
|
-
type: 2
|
|
10
|
-
permission: { type: 0; specify_user_ids: string[] }
|
|
11
|
-
data: string
|
|
12
|
-
enter?: true
|
|
13
|
-
unsupport_tips: string
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
5
|
type QQRequest = {
|
|
18
6
|
content: string
|
|
19
7
|
msg_type: 2
|
|
20
8
|
markdown: { content: string }
|
|
21
|
-
keyboard?: { content: { rows: Array<{ buttons: Button[] }> } }
|
|
22
9
|
msg_id?: string
|
|
23
10
|
msg_seq?: number
|
|
24
11
|
}
|
|
@@ -40,26 +27,39 @@ function escapeMarkdown(value: string): string {
|
|
|
40
27
|
return value.replace(/[\\`*_{}\[\]()#+\-.!|>~<&]/g, '\\$&')
|
|
41
28
|
}
|
|
42
29
|
|
|
43
|
-
|
|
44
|
-
|
|
30
|
+
/** Compact display padding while retaining spaces between English words. */
|
|
31
|
+
function compactDisplayText(value: string): string {
|
|
32
|
+
return value.trim()
|
|
33
|
+
.replace(/[ \t\u3000]*([·×+|])[ \t\u3000]*/g, '$1')
|
|
34
|
+
.replace(/([\p{Script=Han}\p{Extended_Pictographic}\p{Emoji_Modifier}\p{Regional_Indicator}\uFE0F])[ \t\u3000]+/gu, '$1')
|
|
35
|
+
.replace(/[ \t\u3000]+(?=[\p{Script=Han}\p{Extended_Pictographic}\p{Regional_Indicator}])/gu, '')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function compactListLabel(value: string): string {
|
|
39
|
+
const label = compactDisplayText(value)
|
|
40
|
+
// The book icon already identifies skill books. Only shorten the displayed
|
|
41
|
+
// name suffix, including before inventory quantities or market columns.
|
|
42
|
+
return /[📖📚]/u.test(label)
|
|
43
|
+
? label.replace(/技能书(?=$|×\d+(?:\.\d+)?(?:||$)||)/g, '')
|
|
44
|
+
: label
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
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
|
-
const encoded = encodeURIComponent(
|
|
50
|
+
const encoded = encodeURIComponent(command).replace(/[!'()*]/g, character =>
|
|
51
51
|
`%${character.charCodeAt(0).toString(16).toUpperCase()}`)
|
|
52
52
|
return `mqqapi://aio/inlinecmd?command=${encoded}&enter=true&reply=false`
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
export function pageCommandLines(nav: PageNavData): string[] {
|
|
56
56
|
return [
|
|
57
|
-
...(nav.previousCommand ? [
|
|
58
|
-
...(nav.nextCommand ? [
|
|
57
|
+
...(nav.previousCommand ? [nav.previousCommand] : []),
|
|
58
|
+
...(nav.nextCommand ? [nav.nextCommand] : []),
|
|
59
59
|
]
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
export function renderQQPageMarkdown(data: MsgMessage, showGreeting: boolean
|
|
62
|
+
export function renderQQPageMarkdown(data: MsgMessage, showGreeting: boolean): string {
|
|
63
63
|
const nav = data.pageNav
|
|
64
64
|
const entryCommands = new Map(nav?.entries?.map(entry => [entry.lineIndex, entry.viewCommand]) ?? [])
|
|
65
65
|
const listPattern = /^\[(\d+)\]\s*([^\r\n]+)$/
|
|
@@ -71,7 +71,7 @@ export function renderQQPageMarkdown(data: MsgMessage, showGreeting: boolean, sh
|
|
|
71
71
|
const count = summary?.[1] ?? (nav?.total === 1 ? String(listCount) : undefined)
|
|
72
72
|
const countUnit = summary?.[2] ?? '项'
|
|
73
73
|
const isLeaderboardDirectory = !!nav?.entries?.length
|
|
74
|
-
&&
|
|
74
|
+
&& /^(?:🏆)?排行榜$/u.test(compactDisplayText(title?.content ?? ''))
|
|
75
75
|
const sections: string[] = []
|
|
76
76
|
const bodyLines: string[] = []
|
|
77
77
|
const listLines: string[] = []
|
|
@@ -79,7 +79,7 @@ export function renderQQPageMarkdown(data: MsgMessage, showGreeting: boolean, sh
|
|
|
79
79
|
if (bodyLines.length) sections.push(bodyLines.splice(0).join(' \n'))
|
|
80
80
|
}
|
|
81
81
|
const flushList = () => {
|
|
82
|
-
if (listLines.length) sections.push(listLines.splice(0).join('
|
|
82
|
+
if (listLines.length) sections.push(listLines.splice(0).join('\n'))
|
|
83
83
|
}
|
|
84
84
|
const flush = () => { flushList(); flushBody() }
|
|
85
85
|
let hasPageSummary = false
|
|
@@ -91,10 +91,10 @@ export function renderQQPageMarkdown(data: MsgMessage, showGreeting: boolean, sh
|
|
|
91
91
|
case MessageType.title:
|
|
92
92
|
flush()
|
|
93
93
|
if (isLeaderboardDirectory) {
|
|
94
|
-
sections.push(`## ${item.content.includes('🏆') ? '' : '🏆
|
|
95
|
-
sections.push(`>
|
|
94
|
+
sections.push(`## ${item.content.includes('🏆') ? '' : '🏆'}${escapeMarkdown(compactDisplayText(item.content))}`)
|
|
95
|
+
sections.push(`> 共${nav!.entries!.length}个榜单·点击榜名查看`)
|
|
96
96
|
} else {
|
|
97
|
-
sections.push(`**${escapeMarkdown(item.content)}${count !== undefined ?
|
|
97
|
+
sections.push(`**${escapeMarkdown(compactDisplayText(item.content))}${count !== undefined ? `·${count}${countUnit}` : ''}**`)
|
|
98
98
|
}
|
|
99
99
|
break
|
|
100
100
|
case MessageType.line: {
|
|
@@ -102,14 +102,21 @@ export function renderQQPageMarkdown(data: MsgMessage, showGreeting: boolean, sh
|
|
|
102
102
|
if (listEntry) {
|
|
103
103
|
flushBody()
|
|
104
104
|
const command = entryCommands.get(lineIndex)
|
|
105
|
-
const label = escapeMarkdown(listEntry[2])
|
|
105
|
+
const label = escapeMarkdown(compactListLabel(listEntry[2]))
|
|
106
106
|
const name = command
|
|
107
107
|
? `[${label}](${inlineCommandUrl(command)})`
|
|
108
108
|
: label
|
|
109
|
-
|
|
109
|
+
// Keep the numeric marker as Markdown syntax; escape only the item text.
|
|
110
|
+
listLines.push(`${listEntry[1]}. ${name}`)
|
|
110
111
|
break
|
|
111
112
|
}
|
|
112
113
|
flushList()
|
|
114
|
+
// Recruitment pages include this legacy plain-text navigation hint.
|
|
115
|
+
// QQ navigation is already rendered as clickable links in the footer.
|
|
116
|
+
if (nav && item.content.trim() === '[上一页] [下一页]') {
|
|
117
|
+
flushBody()
|
|
118
|
+
break
|
|
119
|
+
}
|
|
113
120
|
if (/^\[指令\]相关指令$/.test(item.content)) {
|
|
114
121
|
flushBody()
|
|
115
122
|
hasHelp = true
|
|
@@ -118,11 +125,14 @@ export function renderQQPageMarkdown(data: MsgMessage, showGreeting: boolean, sh
|
|
|
118
125
|
const page = pagePattern.exec(item.content)
|
|
119
126
|
if (page && nav) {
|
|
120
127
|
flushBody()
|
|
121
|
-
pageFooter =
|
|
128
|
+
pageFooter = `**第${nav.current}/${nav.total}页${!title && page[1] ? `·共${page[1]}${page[2]}` : ''}**`
|
|
122
129
|
hasPageSummary = true
|
|
123
130
|
break
|
|
124
131
|
}
|
|
125
|
-
|
|
132
|
+
const command = entryCommands.get(lineIndex)
|
|
133
|
+
bodyLines.push(command
|
|
134
|
+
? `[${escapeMarkdown(compactDisplayText(item.content))}](${inlineCommandUrl(command)})`
|
|
135
|
+
: escapeMarkdown(item.content))
|
|
126
136
|
break
|
|
127
137
|
}
|
|
128
138
|
case MessageType.error:
|
|
@@ -133,43 +143,20 @@ export function renderQQPageMarkdown(data: MsgMessage, showGreeting: boolean, sh
|
|
|
133
143
|
}
|
|
134
144
|
flush()
|
|
135
145
|
if (nav && !hasPageSummary && (nav.total > 1 || nav.previousCommand || nav.nextCommand))
|
|
136
|
-
pageFooter =
|
|
146
|
+
pageFooter = `**第${nav.current}/${nav.total}页**`
|
|
147
|
+
const previousLink = nav?.previousCommand ? `[上一页](${inlineCommandUrl(nav.previousCommand)})` : undefined
|
|
148
|
+
const nextLink = nav?.nextCommand ? `[下一页](${inlineCommandUrl(nav.nextCommand)})` : undefined
|
|
137
149
|
const helpLink = hasHelp ? `[指令](${inlineCommandUrl('指令')})` : undefined
|
|
138
150
|
const footer = pageFooter
|
|
139
|
-
? [pageFooter, helpLink].filter(Boolean).join('
|
|
151
|
+
? [pageFooter, previousLink, nextLink, helpLink].filter(Boolean).join('·')
|
|
140
152
|
: helpLink ? `更多操作:${helpLink}` : undefined
|
|
141
153
|
if (footer) {
|
|
142
154
|
if (isLeaderboardDirectory) sections.push('***')
|
|
143
155
|
sections.push(footer)
|
|
144
156
|
}
|
|
145
|
-
if (showCommands && nav) {
|
|
146
|
-
const commands = pageCommandLines(nav)
|
|
147
|
-
if (commands.length) sections.push(`**翻页指令** \n${commands.map(escapeMarkdown).join(' \n')}`)
|
|
148
|
-
}
|
|
149
157
|
return sections.join('\n\n') || ' '
|
|
150
158
|
}
|
|
151
159
|
|
|
152
|
-
function makeButton(id: string, label: string, command: string, userId: string, autoEnter: boolean): Button {
|
|
153
|
-
return {
|
|
154
|
-
id,
|
|
155
|
-
render_data: { label, visited_label: label, style: 1 },
|
|
156
|
-
action: {
|
|
157
|
-
type: 2,
|
|
158
|
-
permission: { type: 0, specify_user_ids: [userId] },
|
|
159
|
-
data: pageCommand(command),
|
|
160
|
-
...(autoEnter ? { enter: true as const } : {}),
|
|
161
|
-
unsupport_tips: '当前客户端暂不支持该按钮,请发送上一页或下一页。',
|
|
162
|
-
},
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function buttonsFor(nav: PageNavData, userId: string, autoEnter: boolean): Button[] {
|
|
167
|
-
return [
|
|
168
|
-
...(nav.previousCommand ? [makeButton('previous', '上一页', nav.previousCommand, userId, autoEnter)] : []),
|
|
169
|
-
...(nav.nextCommand ? [makeButton('next', '下一页', nav.nextCommand, userId, autoEnter)] : []),
|
|
170
|
-
]
|
|
171
|
-
}
|
|
172
|
-
|
|
173
160
|
/** Returns false if this is not the matching QQ session or the adapter has no native API. */
|
|
174
161
|
export function canSendQQPage(session: Session | undefined, data: MsgMessage): session is Session {
|
|
175
162
|
if (!session || session.bot.platform !== 'qq' || session.bot.userId !== data.botId) return false
|
|
@@ -178,27 +165,17 @@ export function canSendQQPage(session: Session | undefined, data: MsgMessage): s
|
|
|
178
165
|
return !!internal?.sendMessage && !!internal.sendPrivateMessage
|
|
179
166
|
}
|
|
180
167
|
|
|
181
|
-
/** Send a native QQ Markdown page
|
|
182
|
-
export async function sendQQPage(session: Session, data: MsgMessage
|
|
168
|
+
/** Send a native QQ Markdown page with inline command links. */
|
|
169
|
+
export async function sendQQPage(session: Session, data: MsgMessage): Promise<void> {
|
|
183
170
|
const internal = (session.bot as typeof session.bot & { internal: QQInternal }).internal
|
|
184
|
-
const
|
|
185
|
-
const buttons = withKeyboard ? buttonsFor(nav, data.userId, session.isDirect) : []
|
|
186
|
-
const markdown = renderQQPageMarkdown(data, session.event?.channel?.type === 0, buttons.length === 0)
|
|
187
|
-
const hasEntries = !!nav.entries?.length
|
|
188
|
-
const hasInlineHelp = data.line.some(item => item.type === MessageType.line && item.content === '[指令]相关指令')
|
|
171
|
+
const markdown = renderQQPageMarkdown(data, session.event?.channel?.type === 0)
|
|
189
172
|
const request: QQRequest = {
|
|
190
173
|
// With msg_type=2, QQ requires the ordinary content field to be empty.
|
|
191
174
|
content: '',
|
|
192
175
|
msg_type: 2,
|
|
193
|
-
markdown: {
|
|
194
|
-
content: !session.isDirect && (buttons.length || hasEntries || hasInlineHelp)
|
|
195
|
-
? `${markdown}\n\n点击后如未自动发送,请点发送。`
|
|
196
|
-
: markdown,
|
|
197
|
-
},
|
|
198
|
-
...(buttons.length ? { keyboard: { content: { rows: [{ buttons }] } } } : {}),
|
|
176
|
+
markdown: { content: markdown },
|
|
199
177
|
}
|
|
200
|
-
// Match the QQ adapter's passive-reply sequence
|
|
201
|
-
// keyboard attempt failed and this call retries without the keyboard.
|
|
178
|
+
// Match the QQ adapter's passive-reply sequence.
|
|
202
179
|
const age = Date.now() - session.timestamp
|
|
203
180
|
if (session.messageId && Number.isFinite(age) && age >= 0 && age < 5 * 60 * 1000 - 2000) {
|
|
204
181
|
const sequenced = session as Session & { seq?: number }
|
|
@@ -10,20 +10,8 @@ export default class handelr_text extends handel_base {
|
|
|
10
10
|
const session = sessions.get(sessionKey(data.botId, data.userId, data.channelId))
|
|
11
11
|
if (data.pageNav && canSendQQPage(session, data)) {
|
|
12
12
|
const sendStartedAt = this.timing ? performance.now() : 0
|
|
13
|
-
if (data.pageNav.previousCommand || data.pageNav.nextCommand) {
|
|
14
|
-
try {
|
|
15
|
-
await sendQQPage(session, data, true)
|
|
16
|
-
if (this.timing) {
|
|
17
|
-
this.timing.delivery = 'qq-markdown-keyboard'
|
|
18
|
-
this.timing.sendMs = performance.now() - sendStartedAt
|
|
19
|
-
}
|
|
20
|
-
return
|
|
21
|
-
} catch (error) {
|
|
22
|
-
this.ctx.logger.warn('QQ 翻页按钮发送失败,改用 Markdown 文字指令', error instanceof Error ? error.message : error)
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
13
|
try {
|
|
26
|
-
await sendQQPage(session, data
|
|
14
|
+
await sendQQPage(session, data)
|
|
27
15
|
if (this.timing) {
|
|
28
16
|
this.timing.delivery = 'qq-markdown'
|
|
29
17
|
this.timing.sendMs = performance.now() - sendStartedAt
|
|
@@ -20,7 +20,7 @@ export interface ServiceType {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export const serviceProto: ServiceProto<ServiceType> = {
|
|
23
|
-
"version":
|
|
23
|
+
"version": 11,
|
|
24
24
|
"services": [
|
|
25
25
|
{
|
|
26
26
|
"id": 0,
|
|
@@ -612,6 +612,14 @@ export const serviceProto: ServiceProto<ServiceType> = {
|
|
|
612
612
|
}
|
|
613
613
|
},
|
|
614
614
|
"optional": true
|
|
615
|
+
},
|
|
616
|
+
{
|
|
617
|
+
"id": 12,
|
|
618
|
+
"name": "authorName",
|
|
619
|
+
"type": {
|
|
620
|
+
"type": "String"
|
|
621
|
+
},
|
|
622
|
+
"optional": true
|
|
615
623
|
}
|
|
616
624
|
]
|
|
617
625
|
},
|
|
@@ -1042,4 +1050,4 @@ export const serviceProto: ServiceProto<ServiceType> = {
|
|
|
1042
1050
|
]
|
|
1043
1051
|
}
|
|
1044
1052
|
}
|
|
1045
|
-
};
|
|
1053
|
+
};
|
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;
|
|
@@ -83,7 +84,7 @@ export interface IErrorMessage {
|
|
|
83
84
|
/** 第一阶段支持的消息项 **/
|
|
84
85
|
export type IMessageItem = ITitleMessage | ILineMessage | IErrorMessage;
|
|
85
86
|
|
|
86
|
-
/**
|
|
87
|
+
/** 平台无关的翻页动作;客户端决定如何呈现可点击文字,点击后发送普通指令。 */
|
|
87
88
|
export interface PageNavData {
|
|
88
89
|
current: number;
|
|
89
90
|
total: number;
|