koishi-plugin-wordle-game 4.2.0 → 4.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/DESIGN_SYSTEM.md +9 -9
- package/README.md +2 -2
- package/lib/index.js +139 -203
- package/lib/ux.d.ts +4 -11
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/DESIGN_SYSTEM.md
CHANGED
|
@@ -7,18 +7,18 @@
|
|
|
7
7
|
- 视觉、组件、形状、排版与语义颜色以 [Material 3 / Expressive](https://m3.material.io/) 为准。
|
|
8
8
|
- [Apple HIG](https://developer.apple.com/design/human-interface-guidelines/accessibility) 用于操作反馈、平台惯例、可恢复性与可访问性,不引入 Apple 视觉组件。
|
|
9
9
|
- [WCAG 2.2 AA](https://www.w3.org/TR/WCAG22/) 用于非文本内容、颜色与对比度、时间限制、输入错误和破坏性操作审查。
|
|
10
|
-
- 色彩使用 Google Material Color Utilities 0.4.0 的 HCT 与 Tonal Spot 动态角色,明确使用 2025 色彩规范。历史 `lch` API 名仅用于源码兼容,计算已经统一为 HCT
|
|
10
|
+
- 色彩使用 Google Material Color Utilities 0.4.0 的 HCT 与 Tonal Spot 动态角色,明确使用 2025 色彩规范。历史 `lch` API 名仅用于源码兼容,计算已经统一为 HCT。彩色角色(primary / secondary / tertiary / error 及其容器)走官方 Tonal Spot;中性面(surface 系列、on-surface 系列、outline 系列)由 HCT 按本设计系统的色调重算——表面留住一点源色味道、文字取深色档,避免成片近白、文字偏浅的发晃感。
|
|
11
11
|
- `m3.ts` 是角色颜色、字阶、间距、圆角、层级和基础组件的唯一源文件。`material-color*.ts` 是固定版本的官方算法生成物,授权见 `THIRD_PARTY_NOTICES.md`。
|
|
12
12
|
- 不额外添加静态截图无法表达的动画;基础 CSS 禁用减少动态效果偏好下的动画和过渡。
|
|
13
13
|
|
|
14
14
|
## 输出、交互与内容
|
|
15
15
|
|
|
16
|
-
1.
|
|
17
|
-
2.
|
|
16
|
+
1. 有渲染图时只发图片,不附同内容的文字,避免在群里重复刷屏;图片生成失败时才退回文字。插件不提供个人显示模式开关。
|
|
17
|
+
2. 每个插件的主指令都带描述,`help` 列表右侧不留空;主指令先于任何子指令注册(Koishi 会忽略已存在指令的描述)。文本回复不能把后续条目无提示地压掉;较长管理列表分页。
|
|
18
18
|
3. 裸输入必须符合词法、对局状态及参与条件。多个插件匹配时不执行,要求明确完整指令;完整指令继续可用。
|
|
19
19
|
4. 回复说明当前结果、未完成原因和可执行的下一步,不用管理员日志替代用户可用的反馈。退款、赔付与渲染失败不能伪装成功。
|
|
20
20
|
5. 破坏性清空需展示范围、不可撤销性和限时确认码。确认码限制到同一机器人、平台、用户与频道,使用后立即失效。
|
|
21
|
-
6.
|
|
21
|
+
6. 多轮输入等待 5 分钟,提问里写明可回复的内容与「取消」,不再追加计时说明。对局内的短窗口(如 21 点保险、投降)提供显式跳过,全员表态后立即进入下一阶段,而不是提供「延长」。
|
|
22
22
|
7. 货币发放先写入核对记录,入账结果不明确时保留待核对状态,不自动重发。管理员核对实际流水并按需人工补发后标记确认;确认操作自身不转账。
|
|
23
23
|
|
|
24
24
|
## 内容边界
|
|
@@ -44,7 +44,7 @@ node markdown-to-image-service/scripts/sync-design-system.mjs --check
|
|
|
44
44
|
|
|
45
45
|
官方色彩算法可在源仓库安装开发依赖后通过 `node scripts/build-material-colors.mjs` 重建,再同步消费者。
|
|
46
46
|
|
|
47
|
-
回归检查覆盖:24 个源色相的明暗角色对比度、奖牌前景、图表填充/轨道差异、浏览器/服务端 HCT
|
|
47
|
+
回归检查覆盖:24 个源色相的明暗角色对比度、奖牌前景、图表填充/轨道差异、浏览器/服务端 HCT 一致性、主指令描述、输入冲突、入账异常、清空确认和 Wordle 并发/渲染失败。工作区截图脚本为 `scripts/verify-rendering.ts`,需 Puppeteer Core 和可用 Chromium。
|
|
48
48
|
|
|
49
49
|
## 本轮改动对应关系
|
|
50
50
|
|
|
@@ -53,9 +53,9 @@ node markdown-to-image-service/scripts/sync-design-system.mjs --check
|
|
|
53
53
|
| 共享设计规范 | 官方 HCT、2025 动态颜色角色、共享字阶与间距、同步与哈希校验 |
|
|
54
54
|
| Markdown | M3 明暗主题;旧主题名按明暗迁移;代码高亮与 Mermaid 使用 M3 角色;长代码可换行 |
|
|
55
55
|
| message-counter / monetary-rank | M3 角色与共享图表字阶、HCT 头像配色、完整文字榜单;保留昵称截断 |
|
|
56
|
-
| Wordle | 替换复制的 NYT
|
|
57
|
-
| MCDLE |
|
|
56
|
+
| Wordle | 替换复制的 NYT 外观;状态符号与图例;明暗角色;异常释放并发锁;安全解析模式编号;裸词只在确定是猜测时回应 |
|
|
57
|
+
| MCDLE | 猜测按行排成一张表,方便逐项对照;单句页脚居中 |
|
|
58
58
|
| 2048 / ciyi | 数字与奖牌前景按实际对比度选色 |
|
|
59
|
-
| 斗牛 / 21 点 |
|
|
59
|
+
| 斗牛 / 21 点 | 付款核对记录、如实反馈退款失败、明确下注金额、可显式跳过的短窗口 |
|
|
60
60
|
| sentinel | 完整封印列表按每页 10 位展示 |
|
|
61
|
-
| 其余插件 |
|
|
61
|
+
| 其余插件 | 渲染失败退回文字、可取消输入与上下文反馈 |
|
package/README.md
CHANGED
|
@@ -34,6 +34,6 @@ yarn add koishi-plugin-wordle-game
|
|
|
34
34
|
|
|
35
35
|
## 显示与交互
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
有渲染图时只发图片,不再附带同内容的文字;图片生成失败时才退回文字。作品素材与感官测试的适用边界见 [设计系统](./DESIGN_SYSTEM.md)。
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
本次更新:无前缀猜测只接手确定有效的猜测(在词库中、未猜过),其余静默放行;汉兜提示色改为清晰的绿 / 琥珀,下划线对齐拼音;模式清单上下留白对称。 主指令在 help 列表里补回描述;去掉「.显示」显示模式指令,有图只发图、出图失败才退回文字;多轮输入不再追加计时说明。
|
package/lib/index.js
CHANGED
|
@@ -37,85 +37,11 @@ __export(src_exports, {
|
|
|
37
37
|
usage: () => usage
|
|
38
38
|
});
|
|
39
39
|
module.exports = __toCommonJS(src_exports);
|
|
40
|
-
|
|
41
|
-
// src/ux.ts
|
|
42
|
-
var import_koishi = require("koishi");
|
|
43
|
-
var KEY = Symbol.for("araea.koishi.ux.v1");
|
|
44
|
-
function state(ctx) {
|
|
45
|
-
const root2 = ctx.root;
|
|
46
|
-
return root2[KEY] ??= { modes: /* @__PURE__ */ new Map(), owners: /* @__PURE__ */ new Set() };
|
|
47
|
-
}
|
|
48
|
-
__name(state, "state");
|
|
49
|
-
var userKey = /* @__PURE__ */ __name((session) => `${session.platform}:${session.selfId}:${session.userId}`, "userKey");
|
|
50
|
-
function usePresentation(ctx, command) {
|
|
51
|
-
const shared = state(ctx);
|
|
52
|
-
ctx.command(`${command}.显示 [mode:string]`, "选择图文或文字输出").action(({ session }, mode) => {
|
|
53
|
-
if (!mode) return `当前:${shared.modes.get(userKey(session)) ? "文字" : "图文"}。发送「${command}.显示 文字」或「${command}.显示 图文」。设置对本机器人各插件生效,重启后恢复图文。`;
|
|
54
|
-
if (!["文字", "图文"].includes(mode)) return "请选择「文字」或「图文」。";
|
|
55
|
-
shared.modes.set(userKey(session), mode === "文字");
|
|
56
|
-
return `已切换为${mode}输出。`;
|
|
57
|
-
});
|
|
58
|
-
return {
|
|
59
|
-
textOnly: /* @__PURE__ */ __name((session) => shared.modes.get(userKey(session)) === true, "textOnly"),
|
|
60
|
-
present(session, image, text) {
|
|
61
|
-
if (!image || shared.modes.get(userKey(session))) return text;
|
|
62
|
-
return [...import_koishi.h.normalize(image), (0, import_koishi.h)("p", {}, import_koishi.h.normalize(text))];
|
|
63
|
-
}
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
__name(usePresentation, "usePresentation");
|
|
67
|
-
function registerDirectInput(ctx, name2, matches) {
|
|
68
|
-
const owner = { name: name2, matches };
|
|
69
|
-
state(ctx).owners.add(owner);
|
|
70
|
-
ctx.on("dispose", () => {
|
|
71
|
-
state(ctx).owners.delete(owner);
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
__name(registerDirectInput, "registerDirectInput");
|
|
75
|
-
async function directInputConflict(ctx, session) {
|
|
76
|
-
if (session.event.user?.isBot) return true;
|
|
77
|
-
const owners = [];
|
|
78
|
-
for (const owner of state(ctx).owners) {
|
|
79
|
-
try {
|
|
80
|
-
if (await owner.matches(session)) owners.push(owner.name);
|
|
81
|
-
} catch {
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
if (owners.length < 2) return false;
|
|
85
|
-
await session.send(`这条消息同时适用于 ${owners.join("、")},尚未执行。请使用对应游戏的完整指令。`);
|
|
86
|
-
return true;
|
|
87
|
-
}
|
|
88
|
-
__name(directInputConflict, "directInputConflict");
|
|
89
|
-
async function promptInput(session, instruction) {
|
|
90
|
-
for (; ; ) {
|
|
91
|
-
await session.send(`${instruction}
|
|
92
|
-
请在 5 分钟内回复;发送「延长」重新计时,发送「取消」退出。`);
|
|
93
|
-
const input = await session.prompt(3e5);
|
|
94
|
-
if (input?.trim() === "延长") continue;
|
|
95
|
-
return input;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
__name(promptInput, "promptInput");
|
|
99
|
-
function withoutImages(content) {
|
|
100
|
-
const walk = /* @__PURE__ */ __name((elements) => elements.flatMap((element) => {
|
|
101
|
-
if (element.type === "img" || element.type === "image") return [];
|
|
102
|
-
if (element.children.length) return [(0, import_koishi.h)(element.type, element.attrs, walk(element.children))];
|
|
103
|
-
return [element];
|
|
104
|
-
}), "walk");
|
|
105
|
-
return walk(import_koishi.h.normalize(content));
|
|
106
|
-
}
|
|
107
|
-
__name(withoutImages, "withoutImages");
|
|
108
|
-
function isTextOnly(ctx, session) {
|
|
109
|
-
return state(ctx).modes.get(userKey(session)) === true;
|
|
110
|
-
}
|
|
111
|
-
__name(isTextOnly, "isTextOnly");
|
|
112
|
-
|
|
113
|
-
// src/index.ts
|
|
114
40
|
var fs6 = __toESM(require("fs"));
|
|
115
41
|
var path3 = __toESM(require("path"));
|
|
116
42
|
|
|
117
43
|
// src/config.ts
|
|
118
|
-
var
|
|
44
|
+
var import_koishi = require("koishi");
|
|
119
45
|
var usage = `## 使用
|
|
120
46
|
|
|
121
47
|
发送 \`wordle.开始\` 开局,之后直接发送猜测词即可。
|
|
@@ -139,44 +65,44 @@ var usage = `## 使用
|
|
|
139
65
|
模式包括经典、汉兜、词影、Numberle、Math、Lewdle 及其他词库。
|
|
140
66
|
|
|
141
67
|
可用选项:\`--hard\`、\`--uhard\`、\`--absurd\`、\`--challenge\`、\`--wordles\`、\`--free\`、\`--all\`。`;
|
|
142
|
-
var Config =
|
|
143
|
-
|
|
144
|
-
isDarkThemeEnabled:
|
|
145
|
-
isHighContrastThemeEnabled:
|
|
68
|
+
var Config = import_koishi.Schema.intersect([
|
|
69
|
+
import_koishi.Schema.object({
|
|
70
|
+
isDarkThemeEnabled: import_koishi.Schema.boolean().default(false).description(`使用黑暗主题。`),
|
|
71
|
+
isHighContrastThemeEnabled: import_koishi.Schema.boolean().default(false).description(`使用高对比度主题,为色觉障碍准备。`)
|
|
146
72
|
}).description("主题设置"),
|
|
147
|
-
|
|
148
|
-
compositeImagePageWidth:
|
|
149
|
-
compositeImagePageHeight:
|
|
150
|
-
maxSimultaneousGuesses:
|
|
151
|
-
defaultMaxLeaderboardEntries:
|
|
152
|
-
defaultWordLengthForGuessing:
|
|
73
|
+
import_koishi.Schema.object({
|
|
74
|
+
compositeImagePageWidth: import_koishi.Schema.number().min(1).default(800).description(`多词合成图的页面宽度(像素)。`),
|
|
75
|
+
compositeImagePageHeight: import_koishi.Schema.number().min(1).default(100).description(`多词合成图的页面高度(像素)。`),
|
|
76
|
+
maxSimultaneousGuesses: import_koishi.Schema.number().min(1).default(4).description(`同时猜测的单词数量上限。`),
|
|
77
|
+
defaultMaxLeaderboardEntries: import_koishi.Schema.number().min(0).default(10).description(`排行榜默认显示的人数。`),
|
|
78
|
+
defaultWordLengthForGuessing: import_koishi.Schema.number().min(1).default(5).description(`非经典模式下默认的猜测长度。`)
|
|
153
79
|
}).description("游戏设置"),
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
enableDirectInput:
|
|
157
|
-
isPreventUserDuplicateGuessInput:
|
|
158
|
-
shouldPromptWordLengthInput:
|
|
80
|
+
import_koishi.Schema.intersect([
|
|
81
|
+
import_koishi.Schema.object({
|
|
82
|
+
enableDirectInput: import_koishi.Schema.boolean().default(true).description(`对局中直接发送猜测词即可续猜,无需指令前缀。`),
|
|
83
|
+
isPreventUserDuplicateGuessInput: import_koishi.Schema.boolean().default(true).description(`拦截重复提交的猜测词。`),
|
|
84
|
+
shouldPromptWordLengthInput: import_koishi.Schema.boolean().default(true).description(
|
|
159
85
|
`引导式开局时,追问一次单词长度。关闭则直接用默认长度。`
|
|
160
86
|
),
|
|
161
|
-
shouldPromptForWordLengthOnNonClassicStart:
|
|
87
|
+
shouldPromptForWordLengthOnNonClassicStart: import_koishi.Schema.boolean().default(true).description(
|
|
162
88
|
`以非经典模式开局时,追问一次单词长度。关闭则直接用默认长度。`
|
|
163
89
|
)
|
|
164
90
|
}).description("游戏行为设置"),
|
|
165
|
-
|
|
166
|
-
enableWordGuessTimeLimit:
|
|
91
|
+
import_koishi.Schema.object({
|
|
92
|
+
enableWordGuessTimeLimit: import_koishi.Schema.boolean().default(false).description(`给每次作答加上时间限制。`)
|
|
167
93
|
}),
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
enableWordGuessTimeLimit:
|
|
171
|
-
wordGuessTimeLimitInSeconds:
|
|
94
|
+
import_koishi.Schema.union([
|
|
95
|
+
import_koishi.Schema.object({
|
|
96
|
+
enableWordGuessTimeLimit: import_koishi.Schema.const(true).required(),
|
|
97
|
+
wordGuessTimeLimitInSeconds: import_koishi.Schema.number().min(0).default(120).description(`每次作答的时间限制(秒)。`)
|
|
172
98
|
}),
|
|
173
|
-
|
|
99
|
+
import_koishi.Schema.object({})
|
|
174
100
|
]),
|
|
175
|
-
|
|
176
|
-
retractDelay:
|
|
101
|
+
import_koishi.Schema.object({
|
|
102
|
+
retractDelay: import_koishi.Schema.number().min(0).default(0).description(
|
|
177
103
|
`自动撤回延迟(秒),0 表示不撤回。`
|
|
178
104
|
),
|
|
179
|
-
imageType:
|
|
105
|
+
imageType: import_koishi.Schema.union(["png", "jpeg", "webp"]).default("png").description(`发送的图片格式。`)
|
|
180
106
|
})
|
|
181
107
|
])
|
|
182
108
|
]);
|
|
@@ -709,8 +635,8 @@ __name(processExtraGameRecords, "processExtraGameRecords");
|
|
|
709
635
|
async function sendMessage(g, session, message) {
|
|
710
636
|
const config = g.config;
|
|
711
637
|
const { bot, channelId } = session;
|
|
712
|
-
const [messageId] = await session.send(
|
|
713
|
-
if (
|
|
638
|
+
const [messageId] = await session.send(message);
|
|
639
|
+
if (config.retractDelay > 0 && messageId) {
|
|
714
640
|
const prevMessage = g.lastMessageInfo.get(channelId);
|
|
715
641
|
if (prevMessage) {
|
|
716
642
|
const timePassed = Date.now() - prevMessage.timestamp;
|
|
@@ -779,12 +705,47 @@ function register(g) {
|
|
|
779
705
|
return sendMessage(
|
|
780
706
|
g,
|
|
781
707
|
session,
|
|
782
|
-
"玩法:发送「wordle.开始」选择词库,再发送「wordle.猜 单词」。\n每次猜测会标记:✓ 字符与位置均正确;↔ 答案含该字符但位置不符;× 不包含(重复字符按数量判断)。\n「wordle.查询进度」查看已知线索;「wordle.拼音速查表」查看汉兜拼音;「wordle
|
|
708
|
+
"玩法:发送「wordle.开始」选择词库,再发送「wordle.猜 单词」。\n每次猜测会标记:✓ 字符与位置均正确;↔ 答案含该字符但位置不符;× 不包含(重复字符按数量判断)。\n「wordle.查询进度」查看已知线索;「wordle.拼音速查表」查看汉兜拼音;「wordle.结束」结束本局。"
|
|
783
709
|
);
|
|
784
710
|
});
|
|
785
711
|
}
|
|
786
712
|
__name(register, "register");
|
|
787
713
|
|
|
714
|
+
// src/ux.ts
|
|
715
|
+
var import_koishi2 = require("koishi");
|
|
716
|
+
var KEY = Symbol.for("araea.koishi.ux.v1");
|
|
717
|
+
function state(ctx) {
|
|
718
|
+
const root2 = ctx.root;
|
|
719
|
+
return root2[KEY] ??= { owners: /* @__PURE__ */ new Set() };
|
|
720
|
+
}
|
|
721
|
+
__name(state, "state");
|
|
722
|
+
function registerDirectInput(ctx, name2, matches) {
|
|
723
|
+
const owner = { name: name2, matches };
|
|
724
|
+
state(ctx).owners.add(owner);
|
|
725
|
+
ctx.on("dispose", () => {
|
|
726
|
+
state(ctx).owners.delete(owner);
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
__name(registerDirectInput, "registerDirectInput");
|
|
730
|
+
async function directInputConflict(ctx, session) {
|
|
731
|
+
if (session.event.user?.isBot) return true;
|
|
732
|
+
const owners = [];
|
|
733
|
+
for (const owner of state(ctx).owners) {
|
|
734
|
+
try {
|
|
735
|
+
if (await owner.matches(session)) owners.push(owner.name);
|
|
736
|
+
} catch {
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
if (owners.length < 2) return false;
|
|
740
|
+
await session.send(`这条消息同时适用于 ${owners.join("、")},尚未执行。请使用对应游戏的完整指令。`);
|
|
741
|
+
return true;
|
|
742
|
+
}
|
|
743
|
+
__name(directInputConflict, "directInputConflict");
|
|
744
|
+
function promptInput(session) {
|
|
745
|
+
return session.prompt(3e5);
|
|
746
|
+
}
|
|
747
|
+
__name(promptInput, "promptInput");
|
|
748
|
+
|
|
788
749
|
// src/services/renderer.ts
|
|
789
750
|
var import_cheerio = require("cheerio");
|
|
790
751
|
var import_url = require("url");
|
|
@@ -5804,9 +5765,13 @@ function onColor(background) {
|
|
|
5804
5765
|
}
|
|
5805
5766
|
__name(onColor, "onColor");
|
|
5806
5767
|
var SPACING = { xs: 4, sm: 8, md: 12, lg: 16, xl: 24, xxl: 32 };
|
|
5768
|
+
var NEUTRAL_CHROMA = 6;
|
|
5769
|
+
var NEUTRAL_VARIANT_CHROMA = 12;
|
|
5807
5770
|
function scheme(hue, dark = false, options = {}) {
|
|
5808
5771
|
const source = Hct.from(hue, options.chroma ?? 56, 50);
|
|
5809
5772
|
const dynamic = new SchemeTonalSpot(source, dark, 0, "2025");
|
|
5773
|
+
const neutral = /* @__PURE__ */ __name((tone) => hexFromArgb(Hct.from(hue, NEUTRAL_CHROMA, tone).toInt()), "neutral");
|
|
5774
|
+
const variant = /* @__PURE__ */ __name((tone) => hexFromArgb(Hct.from(hue, NEUTRAL_VARIANT_CHROMA, tone).toInt()), "variant");
|
|
5810
5775
|
return {
|
|
5811
5776
|
primary: hexFromArgb(dynamic.primary),
|
|
5812
5777
|
onPrimary: hexFromArgb(dynamic.onPrimary),
|
|
@@ -5824,23 +5789,23 @@ function scheme(hue, dark = false, options = {}) {
|
|
|
5824
5789
|
onError: hexFromArgb(dynamic.onError),
|
|
5825
5790
|
errorContainer: hexFromArgb(dynamic.errorContainer),
|
|
5826
5791
|
onErrorContainer: hexFromArgb(dynamic.onErrorContainer),
|
|
5827
|
-
background:
|
|
5828
|
-
onBackground:
|
|
5829
|
-
surface:
|
|
5830
|
-
onSurface:
|
|
5831
|
-
surfaceVariant:
|
|
5832
|
-
onSurfaceVariant:
|
|
5833
|
-
surfaceDim:
|
|
5834
|
-
surfaceBright:
|
|
5835
|
-
surfaceContainerLowest:
|
|
5836
|
-
surfaceContainerLow:
|
|
5837
|
-
surfaceContainer:
|
|
5838
|
-
surfaceContainerHigh:
|
|
5839
|
-
surfaceContainerHighest:
|
|
5840
|
-
outline:
|
|
5841
|
-
outlineVariant:
|
|
5842
|
-
inverseSurface:
|
|
5843
|
-
inverseOnSurface:
|
|
5792
|
+
background: dark ? neutral(6) : neutral(97),
|
|
5793
|
+
onBackground: dark ? neutral(90) : neutral(10),
|
|
5794
|
+
surface: dark ? neutral(6) : neutral(97),
|
|
5795
|
+
onSurface: dark ? neutral(90) : neutral(10),
|
|
5796
|
+
surfaceVariant: dark ? variant(30) : variant(90),
|
|
5797
|
+
onSurfaceVariant: dark ? variant(80) : variant(30),
|
|
5798
|
+
surfaceDim: dark ? neutral(6) : neutral(88),
|
|
5799
|
+
surfaceBright: dark ? neutral(24) : neutral(98),
|
|
5800
|
+
surfaceContainerLowest: dark ? neutral(4) : neutral(100),
|
|
5801
|
+
surfaceContainerLow: dark ? neutral(10) : neutral(96),
|
|
5802
|
+
surfaceContainer: dark ? neutral(12) : neutral(94),
|
|
5803
|
+
surfaceContainerHigh: dark ? neutral(17) : neutral(92),
|
|
5804
|
+
surfaceContainerHighest: dark ? neutral(22) : neutral(90),
|
|
5805
|
+
outline: dark ? variant(60) : variant(50),
|
|
5806
|
+
outlineVariant: dark ? variant(30) : variant(80),
|
|
5807
|
+
inverseSurface: dark ? neutral(90) : neutral(20),
|
|
5808
|
+
inverseOnSurface: dark ? neutral(20) : neutral(95),
|
|
5844
5809
|
inversePrimary: hexFromArgb(dynamic.inversePrimary),
|
|
5845
5810
|
scrim: hexFromArgb(dynamic.scrim),
|
|
5846
5811
|
shadow: hexFromArgb(dynamic.shadow)
|
|
@@ -6142,9 +6107,14 @@ function imageText(buffer) {
|
|
|
6142
6107
|
__name(imageText, "imageText");
|
|
6143
6108
|
function imageMessage(buffer, type) {
|
|
6144
6109
|
const text = imageText(buffer) ?? "图片内容请使用「wordle.查询进度」查询。";
|
|
6145
|
-
|
|
6110
|
+
if (!buffer.length) return (0, import_koishi3.h)("p", {}, import_koishi3.h.text(text));
|
|
6111
|
+
return import_koishi3.h.image(buffer, type);
|
|
6146
6112
|
}
|
|
6147
6113
|
__name(imageMessage, "imageMessage");
|
|
6114
|
+
var HINT = {
|
|
6115
|
+
light: { ok: lch(40, 48, 142), mis: lch(46, 60, 62), okAlt: lch(46, 70, 50), misAlt: lch(42, 52, 260) },
|
|
6116
|
+
dark: { ok: lch(80, 56, 142), mis: lch(82, 60, 85), okAlt: lch(74, 62, 55), misAlt: lch(76, 40, 255) }
|
|
6117
|
+
};
|
|
6148
6118
|
var PANEL_WIDTH = 611;
|
|
6149
6119
|
async function capture(g, html, viewport, fileOrigin = false) {
|
|
6150
6120
|
const $ = (0, import_cheerio.load)(html);
|
|
@@ -6170,9 +6140,14 @@ async function capture(g, html, viewport, fileOrigin = false) {
|
|
|
6170
6140
|
await page.setViewport({ ...viewport, deviceScaleFactor: 1 });
|
|
6171
6141
|
if (fileOrigin) await page.goto((0, import_url.pathToFileURL)(resource("emptyHtml.html")).href);
|
|
6172
6142
|
await page.setContent(html, { waitUntil: "load" });
|
|
6173
|
-
const
|
|
6143
|
+
const dark = g.config.isDarkThemeEnabled;
|
|
6144
|
+
const c = scheme(HUE, dark);
|
|
6145
|
+
const hint = HINT[dark ? "dark" : "light"];
|
|
6174
6146
|
await page.addStyleTag({ content: `${baseline(c)} body{background:${c.surface}!important;color:${c.onSurface}!important} input{color:${c.onSurface};border-radius:12px}
|
|
6175
|
-
.text-ok{
|
|
6147
|
+
.text-ok,.text-mis{text-decoration-line:underline;text-decoration-thickness:1.5px;text-underline-offset:4px;text-decoration-skip-ink:none}
|
|
6148
|
+
.text-ok{color:${hint.ok}!important;text-decoration-style:double}.text-mis{color:${hint.mis}!important;text-decoration-style:dashed}
|
|
6149
|
+
.colorblind .text-ok{color:${hint.okAlt}!important}.colorblind .text-mis{color:${hint.misAlt}!important}
|
|
6150
|
+
.op35,.op80{opacity:1!important;color:${c.onSurfaceVariant}!important}
|
|
6176
6151
|
.bg-correct{background:${c.primary}!important;border-radius:12px}.fill-correct{fill:${c.primary}!important}.fill-white{fill:${c.onPrimary}!important}
|
|
6177
6152
|
.bg-base{background:${c.surface}!important} *{animation:none!important;transition:none!important}` });
|
|
6178
6153
|
buffer = await page.screenshot({ fullPage: true, type: g.config.imageType });
|
|
@@ -6345,11 +6320,12 @@ async function generatePanelImage(g, rows, isRanked = false) {
|
|
|
6345
6320
|
<style>
|
|
6346
6321
|
${baseline(s)}
|
|
6347
6322
|
${components()}
|
|
6348
|
-
body
|
|
6349
|
-
|
|
6350
|
-
|
|
6351
|
-
|
|
6352
|
-
}
|
|
6323
|
+
/* 留白放在外层容器上:截图时注入的基线样式会把 body 的内边距清零,上下就不对称了 */
|
|
6324
|
+
body { width: ${PANEL_WIDTH}px; background: var(--md-sys-color-surface); }
|
|
6325
|
+
.panel { padding: 24px; }
|
|
6326
|
+
/* 序号一位数、两位数时徽标同宽,名字才对得齐 */
|
|
6327
|
+
.m3-list-item .m3-badge { width: 36px; padding: 0; }
|
|
6328
|
+
.m3-list { margin: 0; }
|
|
6353
6329
|
/* 一屏里要放下十几行,行高比默认的列表项紧一档 */
|
|
6354
6330
|
.m3-list-item {
|
|
6355
6331
|
min-height: 48px;
|
|
@@ -6375,12 +6351,12 @@ async function generatePanelImage(g, rows, isRanked = false) {
|
|
|
6375
6351
|
</style>
|
|
6376
6352
|
</head>
|
|
6377
6353
|
<body>
|
|
6378
|
-
<ul class="m3-list">
|
|
6354
|
+
<main class="panel"><ul class="m3-list">
|
|
6379
6355
|
${items}
|
|
6380
|
-
</ul>
|
|
6356
|
+
</ul></main>
|
|
6381
6357
|
</body>
|
|
6382
6358
|
</html>`;
|
|
6383
|
-
return capture(g, html, { width: PANEL_WIDTH, height:
|
|
6359
|
+
return capture(g, html, { width: PANEL_WIDTH, height: 1 });
|
|
6384
6360
|
}
|
|
6385
6361
|
__name(generatePanelImage, "generatePanelImage");
|
|
6386
6362
|
async function renderPanel(g, rows, isRanked = false) {
|
|
@@ -18396,75 +18372,36 @@ function register2(g) {
|
|
|
18396
18372
|
const idiomsList = g.data.idiomsList;
|
|
18397
18373
|
const pinyinData = g.data.pinyinData;
|
|
18398
18374
|
const equations = g.data.equations;
|
|
18375
|
+
async function directGuess(session) {
|
|
18376
|
+
if (!config.enableDirectInput || !session.channelId) return;
|
|
18377
|
+
const content = `${import_koishi5.h.select(session.content ?? "", "text")}`.trim();
|
|
18378
|
+
if (!content || !/^[\p{L}\d+*/=().-]+$/u.test(content)) return;
|
|
18379
|
+
const [gameInfo] = await ctx.database.get("wordle_game_records", { channelId: session.channelId });
|
|
18380
|
+
if (!gameInfo?.isStarted) return;
|
|
18381
|
+
const { gameMode } = gameInfo;
|
|
18382
|
+
const word = gameMode === "汉兜" || gameMode === "词影" ? content : content.toLowerCase();
|
|
18383
|
+
if (gameInfo.guessHistory?.includes(word)) return;
|
|
18384
|
+
if (gameMode === "汉兜" || gameMode === "词影") {
|
|
18385
|
+
if (!isFourCharacterIdiom(content)) return;
|
|
18386
|
+
const known = isIdiomInList(content, idiomsList) || gameMode === "汉兜" && pinyinData.some((item) => item.term === content);
|
|
18387
|
+
if (!known) return;
|
|
18388
|
+
if (gameMode === "词影" && !checkStrokesData(content, g.data.strokesData)) return;
|
|
18389
|
+
return content;
|
|
18390
|
+
}
|
|
18391
|
+
if (content.length !== gameInfo.guessWordLength) return;
|
|
18392
|
+
if (gameMode === "Numberle") return isNumericString(content) ? content : void 0;
|
|
18393
|
+
if (gameMode === "Math") return isMathEquationValid(content) ? content : void 0;
|
|
18394
|
+
if (!/^[a-zA-Z]+$/.test(content) || !findWord(word)) return;
|
|
18395
|
+
return content;
|
|
18396
|
+
}
|
|
18397
|
+
__name(directGuess, "directGuess");
|
|
18399
18398
|
registerDirectInput(ctx, "wordle-game", async (session) => {
|
|
18400
18399
|
if (!ctx.filter(session)) return false;
|
|
18401
|
-
|
|
18402
|
-
if (!config.enableDirectInput) {
|
|
18403
|
-
return false;
|
|
18404
|
-
}
|
|
18405
|
-
if (content) {
|
|
18406
|
-
content = `${import_koishi5.h.select(content, "text")}`.trim();
|
|
18407
|
-
}
|
|
18408
|
-
if (!channelId || !content || !/^[\p{L}\d+*/=().-]+$/u.test(content)) return false;
|
|
18409
|
-
const [gameInfo] = await ctx.database.get("wordle_game_records", { channelId });
|
|
18410
|
-
if (!gameInfo?.isStarted) {
|
|
18411
|
-
return false;
|
|
18412
|
-
}
|
|
18413
|
-
if (gameInfo.gameMode === "汉兜" || gameInfo.gameMode === "词影") {
|
|
18414
|
-
if (!isFourCharacterIdiom(content)) {
|
|
18415
|
-
return false;
|
|
18416
|
-
}
|
|
18417
|
-
} else if (gameInfo.gameMode === "Numberle") {
|
|
18418
|
-
if (!isNumericString(content)) {
|
|
18419
|
-
return false;
|
|
18420
|
-
}
|
|
18421
|
-
} else if (gameInfo.gameMode === "Math") {
|
|
18422
|
-
if (!isMathEquationValid(content)) {
|
|
18423
|
-
return false;
|
|
18424
|
-
}
|
|
18425
|
-
} else {
|
|
18426
|
-
if (!/^[a-zA-Z]+$/.test(content)) {
|
|
18427
|
-
return false;
|
|
18428
|
-
}
|
|
18429
|
-
}
|
|
18430
|
-
if (content.length !== gameInfo.guessWordLength) {
|
|
18431
|
-
return false;
|
|
18432
|
-
}
|
|
18433
|
-
return true;
|
|
18400
|
+
return Boolean(await directGuess(session));
|
|
18434
18401
|
});
|
|
18435
18402
|
ctx.middleware(async (session, next) => {
|
|
18436
|
-
|
|
18437
|
-
if (!
|
|
18438
|
-
return await next();
|
|
18439
|
-
}
|
|
18440
|
-
if (content) {
|
|
18441
|
-
content = `${import_koishi5.h.select(content, "text")}`.trim();
|
|
18442
|
-
}
|
|
18443
|
-
if (!channelId || !content || !/^[\p{L}\d+*/=().-]+$/u.test(content)) return next();
|
|
18444
|
-
const [gameInfo] = await ctx.database.get("wordle_game_records", { channelId });
|
|
18445
|
-
if (!gameInfo?.isStarted) {
|
|
18446
|
-
return await next();
|
|
18447
|
-
}
|
|
18448
|
-
if (gameInfo.gameMode === "汉兜" || gameInfo.gameMode === "词影") {
|
|
18449
|
-
if (!isFourCharacterIdiom(content)) {
|
|
18450
|
-
return await next();
|
|
18451
|
-
}
|
|
18452
|
-
} else if (gameInfo.gameMode === "Numberle") {
|
|
18453
|
-
if (!isNumericString(content)) {
|
|
18454
|
-
return await next();
|
|
18455
|
-
}
|
|
18456
|
-
} else if (gameInfo.gameMode === "Math") {
|
|
18457
|
-
if (!isMathEquationValid(content)) {
|
|
18458
|
-
return await next();
|
|
18459
|
-
}
|
|
18460
|
-
} else {
|
|
18461
|
-
if (!/^[a-zA-Z]+$/.test(content)) {
|
|
18462
|
-
return await next();
|
|
18463
|
-
}
|
|
18464
|
-
}
|
|
18465
|
-
if (content.length !== gameInfo.guessWordLength) {
|
|
18466
|
-
return await next();
|
|
18467
|
-
}
|
|
18403
|
+
const content = await directGuess(session);
|
|
18404
|
+
if (!content) return next();
|
|
18468
18405
|
if (await directInputConflict(ctx, session)) return;
|
|
18469
18406
|
await session.execute(`wordle.猜 ${content}`);
|
|
18470
18407
|
return;
|
|
@@ -18543,7 +18480,7 @@ ${generateGameEndMessage(gameInfo)}`}${processedResult}
|
|
|
18543
18480
|
${examPanel ?? examList}
|
|
18544
18481
|
发送序号或模式名即可开局,或发送「取消」。`
|
|
18545
18482
|
);
|
|
18546
|
-
const userInput = await promptInput(session
|
|
18483
|
+
const userInput = await promptInput(session);
|
|
18547
18484
|
if (!userInput)
|
|
18548
18485
|
return await sendMessage(
|
|
18549
18486
|
g,
|
|
@@ -18566,7 +18503,7 @@ ${examPanel ?? examList}
|
|
|
18566
18503
|
)}
|
|
18567
18504
|
发送一个长度即可开局,或发送「取消」。`
|
|
18568
18505
|
);
|
|
18569
|
-
const userInput2 = await promptInput(session
|
|
18506
|
+
const userInput2 = await promptInput(session);
|
|
18570
18507
|
if (!userInput2)
|
|
18571
18508
|
return await sendMessage(
|
|
18572
18509
|
g,
|
|
@@ -18746,7 +18683,7 @@ ${image}
|
|
|
18746
18683
|
)}
|
|
18747
18684
|
发送一个长度即可开局,或发送「取消」。`
|
|
18748
18685
|
);
|
|
18749
|
-
const userInput = await promptInput(session
|
|
18686
|
+
const userInput = await promptInput(session);
|
|
18750
18687
|
if (!userInput)
|
|
18751
18688
|
return await sendMessage(
|
|
18752
18689
|
g,
|
|
@@ -19026,7 +18963,7 @@ ${image}${tail}`
|
|
|
19026
18963
|
session,
|
|
19027
18964
|
`💡 发送一个猜测词,或发送「取消」。`
|
|
19028
18965
|
);
|
|
19029
|
-
const userInput = await promptInput(session
|
|
18966
|
+
const userInput = await promptInput(session);
|
|
19030
18967
|
if (!userInput) {
|
|
19031
18968
|
await setGuessRunningStatus(g, channelId, false);
|
|
19032
18969
|
return await sendMessage(
|
|
@@ -19367,7 +19304,7 @@ ${imageMessage(
|
|
|
19367
19304
|
发送「撤销」回到上一步,或发送「结束」收掉这一局。
|
|
19368
19305
|
没有等到有效输入时,按「撤销」处理。`
|
|
19369
19306
|
);
|
|
19370
|
-
let userInput = await promptInput(session
|
|
19307
|
+
let userInput = await promptInput(session);
|
|
19371
19308
|
const imageBuffer22 = await generateImage(
|
|
19372
19309
|
g,
|
|
19373
19310
|
styledHtml,
|
|
@@ -20427,7 +20364,6 @@ var inject = {
|
|
|
20427
20364
|
};
|
|
20428
20365
|
var name = "wordle-game";
|
|
20429
20366
|
async function apply(ctx, config) {
|
|
20430
|
-
const presentation = usePresentation(ctx, "wordle");
|
|
20431
20367
|
const logger = ctx.logger(`wordleGame`);
|
|
20432
20368
|
const wordleGameDirPath = path3.join(ctx.baseDir, "data", "wordleGame");
|
|
20433
20369
|
const idiomsFilePath = path3.join(__dirname, "assets", "汉兜", "idioms.json");
|
package/lib/ux.d.ts
CHANGED
|
@@ -3,18 +3,11 @@ type Owner = {
|
|
|
3
3
|
name: string;
|
|
4
4
|
matches: (session: Session) => boolean | Promise<boolean>;
|
|
5
5
|
};
|
|
6
|
-
/**
|
|
7
|
-
export declare function
|
|
8
|
-
textOnly: (session: Session) => boolean;
|
|
9
|
-
present(session: Session, image: h.Fragment | null | undefined, text: h.Fragment): h.Fragment;
|
|
10
|
-
};
|
|
6
|
+
/** 渲染图优先,只发图片;图片缺失时才退回等价文字。 */
|
|
7
|
+
export declare function present(image: h.Fragment | null | undefined, text: h.Fragment): h.Fragment;
|
|
11
8
|
/** Plugins register their active input grammar; overlapping messages are never executed. */
|
|
12
9
|
export declare function registerDirectInput(ctx: Context, name: string, matches: Owner['matches']): void;
|
|
13
10
|
export declare function directInputConflict(ctx: Context, session: Session): Promise<boolean>;
|
|
14
|
-
/**
|
|
15
|
-
export declare function promptInput(session: Session
|
|
16
|
-
export declare const IMAGE_FAILURE = "\u56FE\u7247\u6682\u65F6\u65E0\u6CD5\u751F\u6210\uFF0C\u5DF2\u4FDD\u7559\u6587\u5B57\u5185\u5BB9\u3002\u53EF\u4EE5\u7A0D\u540E\u91CD\u8BD5\uFF1B\u82E5\u6301\u7EED\u5931\u8D25\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u3002";
|
|
17
|
-
/** Preserve companion text when image messages are switched to text mode. */
|
|
18
|
-
export declare function withoutImages(content: h.Fragment): h[];
|
|
19
|
-
export declare function isTextOnly(ctx: Context, session: Session): boolean;
|
|
11
|
+
/** 等待下一句回复。提问本身已经写明了可回复的内容与「取消」,这里不再追加说明。 */
|
|
12
|
+
export declare function promptInput(session: Session): Promise<string | undefined>;
|
|
20
13
|
export {};
|