koishi-plugin-cchess 2.4.0 → 2.5.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 CHANGED
@@ -1,3 +1,7 @@
1
+ # 2.5.0 · 2026-09-26
2
+
3
+ 主指令在 help 列表里补回描述;去掉「.显示」显示模式指令,有图只发图、出图失败才退回文字;多轮输入不再追加计时说明。
4
+
1
5
  # 2.4.0 · 2026-09-26
2
6
 
3
7
  每次棋盘输出附带完整棋子坐标与轮次;个人文字模式;裸输入冲突保护。
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. 多轮输入允许 5 分钟等待,发送「延长」重新计时,发送「取消」退出。斗牛、21 点提供延长等待入口。文字模式不主动撤回已发出的说明。
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 一致性、文字输出与身份隔离、输入冲突、可延长输入、入账异常、清空确认和 Wordle 并发/渲染失败。工作区截图脚本为 `scripts/verify-rendering.ts`,需 Puppeteer Core 和可用 Chromium。
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
@@ -31,6 +31,6 @@ yarn add koishi-plugin-cchess
31
31
 
32
32
  ## 显示与交互
33
33
 
34
- 发送 `cchess.显示 文字` 或 `cchess.显示 图文` 切换个人显示偏好。同一机器人中的配套插件共享选择,重启后恢复图文。图文模式中的信息图片附带文字说明;作品素材与感官测试的适用边界见 [设计系统](./DESIGN_SYSTEM.md)。
34
+ 有渲染图时只发图片,不再附带同内容的文字;图片生成失败时才退回文字。作品素材与感官测试的适用边界见 [设计系统](./DESIGN_SYSTEM.md)。
35
35
 
36
- 本次更新:每次棋盘输出附带完整棋子坐标与轮次;个人文字模式;裸输入冲突保护。
36
+ 本次更新:主指令在 help 列表里补回描述;去掉「.显示」显示模式指令,有图只发图、出图失败才退回文字;多轮输入不再追加计时说明。
package/lib/index.js CHANGED
@@ -43,27 +43,9 @@ var import_koishi = require("koishi");
43
43
  var KEY = Symbol.for("araea.koishi.ux.v1");
44
44
  function state(ctx) {
45
45
  const root = ctx.root;
46
- return root[KEY] ??= { modes: /* @__PURE__ */ new Map(), owners: /* @__PURE__ */ new Set() };
46
+ return root[KEY] ??= { owners: /* @__PURE__ */ new Set() };
47
47
  }
48
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
49
  function registerDirectInput(ctx, name2, matches) {
68
50
  const owner = { name: name2, matches };
69
51
  state(ctx).owners.add(owner);
@@ -86,15 +68,6 @@ async function directInputConflict(ctx, session) {
86
68
  return true;
87
69
  }
88
70
  __name(directInputConflict, "directInputConflict");
89
- function withoutImages(content) {
90
- const walk = /* @__PURE__ */ __name((elements) => elements.flatMap((element) => {
91
- if (element.type === "img" || element.type === "image") return [];
92
- if (element.children.length) return [(0, import_koishi.h)(element.type, element.attrs, walk(element.children))];
93
- return [element];
94
- }), "walk");
95
- return walk(import_koishi.h.normalize(content));
96
- }
97
- __name(withoutImages, "withoutImages");
98
71
 
99
72
  // src/index.ts
100
73
  var import_koishi2 = require("koishi");
@@ -5106,9 +5079,13 @@ function harmonize(hex, tone, chroma = 48, fallbackHue = 0) {
5106
5079
  }
5107
5080
  __name(harmonize, "harmonize");
5108
5081
  var SPACING = { xs: 4, sm: 8, md: 12, lg: 16, xl: 24, xxl: 32 };
5082
+ var NEUTRAL_CHROMA = 6;
5083
+ var NEUTRAL_VARIANT_CHROMA = 12;
5109
5084
  function scheme(hue, dark = false, options = {}) {
5110
5085
  const source = Hct.from(hue, options.chroma ?? 56, 50);
5111
5086
  const dynamic = new SchemeTonalSpot(source, dark, 0, "2025");
5087
+ const neutral = /* @__PURE__ */ __name((tone) => hexFromArgb(Hct.from(hue, NEUTRAL_CHROMA, tone).toInt()), "neutral");
5088
+ const variant = /* @__PURE__ */ __name((tone) => hexFromArgb(Hct.from(hue, NEUTRAL_VARIANT_CHROMA, tone).toInt()), "variant");
5112
5089
  return {
5113
5090
  primary: hexFromArgb(dynamic.primary),
5114
5091
  onPrimary: hexFromArgb(dynamic.onPrimary),
@@ -5126,23 +5103,23 @@ function scheme(hue, dark = false, options = {}) {
5126
5103
  onError: hexFromArgb(dynamic.onError),
5127
5104
  errorContainer: hexFromArgb(dynamic.errorContainer),
5128
5105
  onErrorContainer: hexFromArgb(dynamic.onErrorContainer),
5129
- background: hexFromArgb(dynamic.background),
5130
- onBackground: hexFromArgb(dynamic.onBackground),
5131
- surface: hexFromArgb(dynamic.surface),
5132
- onSurface: hexFromArgb(dynamic.onSurface),
5133
- surfaceVariant: hexFromArgb(dynamic.surfaceVariant),
5134
- onSurfaceVariant: hexFromArgb(dynamic.onSurfaceVariant),
5135
- surfaceDim: hexFromArgb(dynamic.surfaceDim),
5136
- surfaceBright: hexFromArgb(dynamic.surfaceBright),
5137
- surfaceContainerLowest: hexFromArgb(dynamic.surfaceContainerLowest),
5138
- surfaceContainerLow: hexFromArgb(dynamic.surfaceContainerLow),
5139
- surfaceContainer: hexFromArgb(dynamic.surfaceContainer),
5140
- surfaceContainerHigh: hexFromArgb(dynamic.surfaceContainerHigh),
5141
- surfaceContainerHighest: hexFromArgb(dynamic.surfaceContainerHighest),
5142
- outline: hexFromArgb(dynamic.outline),
5143
- outlineVariant: hexFromArgb(dynamic.outlineVariant),
5144
- inverseSurface: hexFromArgb(dynamic.inverseSurface),
5145
- inverseOnSurface: hexFromArgb(dynamic.inverseOnSurface),
5106
+ background: dark ? neutral(6) : neutral(97),
5107
+ onBackground: dark ? neutral(90) : neutral(10),
5108
+ surface: dark ? neutral(6) : neutral(97),
5109
+ onSurface: dark ? neutral(90) : neutral(10),
5110
+ surfaceVariant: dark ? variant(30) : variant(90),
5111
+ onSurfaceVariant: dark ? variant(80) : variant(30),
5112
+ surfaceDim: dark ? neutral(6) : neutral(88),
5113
+ surfaceBright: dark ? neutral(24) : neutral(98),
5114
+ surfaceContainerLowest: dark ? neutral(4) : neutral(100),
5115
+ surfaceContainerLow: dark ? neutral(10) : neutral(96),
5116
+ surfaceContainer: dark ? neutral(12) : neutral(94),
5117
+ surfaceContainerHigh: dark ? neutral(17) : neutral(92),
5118
+ surfaceContainerHighest: dark ? neutral(22) : neutral(90),
5119
+ outline: dark ? variant(60) : variant(50),
5120
+ outlineVariant: dark ? variant(30) : variant(80),
5121
+ inverseSurface: dark ? neutral(90) : neutral(20),
5122
+ inverseOnSurface: dark ? neutral(20) : neutral(95),
5146
5123
  inversePrimary: hexFromArgb(dynamic.inversePrimary),
5147
5124
  scrim: hexFromArgb(dynamic.scrim),
5148
5125
  shadow: hexFromArgb(dynamic.shadow)
@@ -5252,7 +5229,6 @@ function panel(options) {
5252
5229
  }
5253
5230
  __name(panel, "panel");
5254
5231
  function apply(ctx, config) {
5255
- const presentation = usePresentation(ctx, "cchess");
5256
5232
  const logger = ctx.logger("cchess");
5257
5233
  const engines = {};
5258
5234
  const enginePromises = {};
@@ -5970,13 +5946,12 @@ function apply(ctx, config) {
5970
5946
  }));
5971
5947
  const text = `棋盘:轮到${convertTurnToString(game.turn)}。坐标 a–i 自左向右,0–9 自红方底线向黑方。
5972
5948
  ${pieces.join(";")}`;
5973
- const description = (0, import_koishi2.h)("p", {}, import_koishi2.h.text(text)).toString();
5974
- if (config.disableImages) return description;
5949
+ if (config.disableImages) return text;
5975
5950
  try {
5976
- return import_koishi2.h.image(await drawChessBoard(channelId), imageMimeType).toString() + description;
5951
+ return import_koishi2.h.image(await drawChessBoard(channelId), imageMimeType).toString();
5977
5952
  } catch (error) {
5978
5953
  logger.warn("棋盘图片生成失败:%s", error.message);
5979
- return description;
5954
+ return text;
5980
5955
  }
5981
5956
  }
5982
5957
  __name(renderBoard, "renderBoard");
@@ -7261,8 +7236,8 @@ ${pieces.join(";")}`;
7261
7236
  const sentMessages = {};
7262
7237
  async function sendMessage(session, message) {
7263
7238
  const { bot, channelId } = session;
7264
- const [messageId] = await session.send(presentation.textOnly(session) ? withoutImages(message) : message);
7265
- if (presentation.textOnly(session) || config.retractDelay === 0 || !messageId) return;
7239
+ const [messageId] = await session.send(message);
7240
+ if (config.retractDelay === 0 || !messageId) return;
7266
7241
  const previousMessageId = sentMessages[channelId];
7267
7242
  sentMessages[channelId] = messageId;
7268
7243
  if (!previousMessageId) return;
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
- /** Default output is an image plus its usable text equivalent. Preferences affect this user only. */
7
- export declare function usePresentation(ctx: Context, command: string): {
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
- /** No hidden time limit: users can extend the non-game input step without losing context. */
15
- export declare function promptInput(session: Session, instruction: string): Promise<string | undefined>;
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 {};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-cchess",
3
3
  "description": "Koishi 中国象棋:支持玩家对战、皮卡鱼引擎和 FEN 局面导入。",
4
- "version": "2.4.0",
4
+ "version": "2.5.0",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [