koishi-plugin-ciyi 1.4.0 → 2.0.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/README.md CHANGED
@@ -16,7 +16,7 @@ yarn add koishi-plugin-ciyi
16
16
  | --- | --- |
17
17
  | `ciyi` | 玩法 |
18
18
  | `ciyi.猜 <词>` | 开始今日游戏并提交猜测 |
19
- | `ciyi.裸词 [开/关]` | 切换本群无前缀续猜 |
19
+ | `ciyi.裸词 [开/关]` | 切换本频道无前缀续猜 |
20
20
  | `ciyi.排行榜` | 猜中次数排行 |
21
21
 
22
22
  首次用 `ciyi.猜 <词>` 开题,之后可直接发送两字词继续猜。裸词设置只对当前群生效,重启后恢复默认。
package/lib/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Context, Schema, Session } from "koishi";
2
2
  import { History } from "./view";
3
3
  export declare const name = "ciyi";
4
- export declare const usage = "## \u4F7F\u7528\n\n\u53D1\u9001 `ciyi` \u67E5\u770B\u73A9\u6CD5\u3002\u6BCF\u65E5\u85CF\u4E00\u4E2A\u4E24\u5B57\u8BCD\uFF0C\u7528 `ciyi.\u731C <\u8BCD>` \u5F00\u9898\uFF0C\u4E4B\u540E\u53EF\u4EE5\u76F4\u63A5\u53D1\u9001\u4E24\u5B57\u8BCD\u7EE7\u7EED\u731C\u3002\u88F8\u8BCD\u8BBE\u7F6E\u53EA\u5BF9\u5F53\u524D\u9891\u9053\u751F\u6548\uFF0C\u91CD\u542F\u540E\u6062\u590D\u9ED8\u8BA4\u3002\n\n## \u6307\u4EE4\n\n| \u6307\u4EE4 | \u8BF4\u660E |\n| --- | --- |\n| `ciyi` | \u73A9\u6CD5 |\n| `ciyi.\u731C <\u8BCD>` | \u5F00\u59CB\u4ECA\u65E5\u6E38\u620F\u5E76\u63D0\u4EA4\u731C\u6D4B |\n| `ciyi.\u88F8\u8BCD [\u5F00/\u5173]` | \u5F00\u5173\u672C\u9891\u9053\u7684\u65E0\u524D\u7F00\u7EED\u731C |\n| `ciyi.\u6392\u884C\u699C` | \u7D2F\u8BA1\u731C\u4E2D\u6392\u884C\u699C |";
4
+ export declare const usage = "## \u4F7F\u7528\n\n\u53D1\u9001 `ciyi` \u67E5\u770B\u73A9\u6CD5\u3002\u6BCF\u65E5\u85CF\u4E00\u4E2A\u4E24\u5B57\u8BCD\uFF0C\u7528 `ciyi.\u731C <\u8BCD>` \u5F00\u9898\uFF0C\u4E4B\u540E\u53EF\u4EE5\u76F4\u63A5\u53D1\u9001\u4E24\u5B57\u8BCD\u7EE7\u7EED\u731C\u3002\u88F8\u8BCD\u8BBE\u7F6E\u53EA\u5BF9\u5F53\u524D\u9891\u9053\u751F\u6548\uFF0C\u91CD\u542F\u540E\u6062\u590D\u9ED8\u8BA4\u3002\n\n## \u6307\u4EE4\n\n| \u6307\u4EE4 | \u8BF4\u660E |\n| --- | --- |\n| `ciyi` | \u73A9\u6CD5 |\n| `ciyi.\u731C <\u8BCD>` | \u5F00\u59CB\u4ECA\u65E5\u5BF9\u5C40\u5E76\u62A5\u4E00\u4E2A\u4E24\u5B57\u8BCD |\n| `ciyi.\u88F8\u8BCD [\u5F00/\u5173]` | \u5F00\u5173\u672C\u9891\u9053\u7684\u65E0\u524D\u7F00\u7EED\u731C |\n| `ciyi.\u6392\u884C\u699C` | \u7D2F\u8BA1\u731C\u4E2D\u6392\u884C\u699C |";
5
5
  export declare const inject: {
6
6
  required: string[];
7
7
  optional: string[];
@@ -9,10 +9,11 @@ export declare const inject: {
9
9
  export interface Config {
10
10
  atReply: boolean;
11
11
  quoteReply: boolean;
12
- isEnableMiddleware: boolean;
12
+ enableDirectInput: boolean;
13
13
  renderImage: boolean;
14
14
  maxHistory: number;
15
15
  maxRank: number;
16
+ retractDelay: number;
16
17
  }
17
18
  export declare const Config: Schema<Config>;
18
19
  declare module "koishi" {
package/lib/index.js CHANGED
@@ -430,7 +430,7 @@ function drawWordGrid(ctx, x, y, word, size, mask) {
430
430
  const gap = s(g.gap);
431
431
  const chars = Array.from(word || "");
432
432
  if (chars.length === 0) {
433
- textCenter(ctx, "—", x + s(14), y + cell / 2, `${s(13)}px ${FONT_SANS}`, C.outline);
433
+ textCenter(ctx, "—", x + s(14), y + cell / 2, `${s(13)}px ${FONT_SANS}`, C.onSurfaceVariant);
434
434
  return s(28);
435
435
  }
436
436
  let cx = x;
@@ -472,7 +472,7 @@ function drawHeader(ctx, innerX, innerY, innerW, sub, right) {
472
472
  brandX,
473
473
  innerY + padTop + s(36),
474
474
  `italic ${s(10)}px ${FONT_NUM}`,
475
- C.outline
475
+ C.onSurfaceVariant
476
476
  );
477
477
  const subRight = right ? innerX + innerW - padX - s(150) : innerX + innerW - padX;
478
478
  textLeftFit(
@@ -487,7 +487,7 @@ function drawHeader(ctx, innerX, innerY, innerW, sub, right) {
487
487
  if (right) {
488
488
  const rx = innerX + innerW - padX;
489
489
  textRight(ctx, right.big, rx, innerY + padTop + s(16), `700 ${s(31)}px ${FONT_NUM}`, C.primary);
490
- textRight(ctx, right.cap, rx, innerY + padTop + s(44), `${s(10.5)}px ${FONT_SANS}`, C.outline);
490
+ textRight(ctx, right.cap, rx, innerY + padTop + s(44), `${s(10.5)}px ${FONT_SANS}`, C.onSurfaceVariant);
491
491
  }
492
492
  const lineY = innerY + h2;
493
493
  ctx.strokeStyle = C.outlineVariant;
@@ -527,7 +527,7 @@ function drawFooter(ctx, innerX, y, innerW, legendItems, tip) {
527
527
  lx += s(9) + s(6);
528
528
  textLeft(ctx, tier.name, lx, cy, `${s(11.5)}px ${FONT_SANS}`, C.onSurfaceVariant);
529
529
  lx += textWidth(tier.name, s(11.5)) + s(4);
530
- textLeft(ctx, range, lx, cy, `${s(11)}px ${FONT_NUM}`, C.outline);
530
+ textLeft(ctx, range, lx, cy, `${s(11)}px ${FONT_NUM}`, C.onSurfaceVariant);
531
531
  lx += textWidth(range, s(11)) + s(13);
532
532
  }
533
533
  const tipFont = `${s(11.5)}px ${FONT_SANS}`;
@@ -538,7 +538,7 @@ function drawFooter(ctx, innerX, y, innerW, legendItems, tip) {
538
538
  innerX + innerW - padX,
539
539
  cy,
540
540
  tipFont,
541
- C.outline
541
+ C.onSurfaceVariant
542
542
  );
543
543
  return h2 + padY * 2;
544
544
  }
@@ -552,8 +552,8 @@ function drawLegendFull(ctx, x, y, w) {
552
552
  const cy = y + Math.floor(i / 2) * rowH;
553
553
  fillRound(ctx, cx, cy - s(4.5), s(9), s(9), s(SHAPE.full), t.color);
554
554
  textLeft(ctx, t.name, cx + s(15), cy, `${s(11.5)}px ${FONT_SANS}`, C.onSurfaceVariant);
555
- textLeft(ctx, tierRange(i), cx + s(52), cy, `${s(11)}px ${FONT_NUM}`, C.outline);
556
- textLeft(ctx, t.note, cx + s(100), cy, `${s(11.5)}px ${FONT_SANS}`, C.outline);
555
+ textLeft(ctx, tierRange(i), cx + s(52), cy, `${s(11)}px ${FONT_NUM}`, C.onSurfaceVariant);
556
+ textLeft(ctx, t.note, cx + s(100), cy, `${s(11.5)}px ${FONT_SANS}`, C.onSurfaceVariant);
557
557
  }
558
558
  return rowH * Math.ceil(TIERS.length / 2) + s(8);
559
559
  }
@@ -637,12 +637,12 @@ function drawBoardTable(ctx, x, y, w, opts, rowH) {
637
637
  const mtX = mt0 + Math.max(0, (cols.mt - meterBlockW) / 2);
638
638
  const headY = y;
639
639
  const fs = s(10.5);
640
- textLeft(ctx, "序", noX, headY, `${fs}px ${FONT_SANS}`, C.outline);
641
- textCenter(ctx, "更近 ◀", nb0 + cols.nb / 2, headY, `${fs}px ${FONT_SANS}`, C.outline);
642
- textCenter(ctx, "猜测", gw0 + cols.gw / 2, headY, `${fs}px ${FONT_SANS}`, C.outline);
643
- textCenter(ctx, "▶ 更远", nb1 + cols.nb / 2, headY, `${fs}px ${FONT_SANS}`, C.outline);
644
- textCenter(ctx, "排名", rk0 + cols.rk / 2, headY, `${fs}px ${FONT_SANS}`, C.outline);
645
- textCenter(ctx, "亲疏", mt0 + cols.mt / 2, headY, `${fs}px ${FONT_SANS}`, C.outline);
640
+ textLeft(ctx, "序", noX, headY, `${fs}px ${FONT_SANS}`, C.onSurfaceVariant);
641
+ textCenter(ctx, "更近 ◀", nb0 + cols.nb / 2, headY, `${fs}px ${FONT_SANS}`, C.onSurfaceVariant);
642
+ textCenter(ctx, "猜测", gw0 + cols.gw / 2, headY, `${fs}px ${FONT_SANS}`, C.onSurfaceVariant);
643
+ textCenter(ctx, "▶ 更远", nb1 + cols.nb / 2, headY, `${fs}px ${FONT_SANS}`, C.onSurfaceVariant);
644
+ textCenter(ctx, "排名", rk0 + cols.rk / 2, headY, `${fs}px ${FONT_SANS}`, C.onSurfaceVariant);
645
+ textCenter(ctx, "亲疏", mt0 + cols.mt / 2, headY, `${fs}px ${FONT_SANS}`, C.onSurfaceVariant);
646
646
  let ry = y + s(22);
647
647
  let ordinal = 0;
648
648
  for (const row of opts.rows) {
@@ -650,11 +650,11 @@ function drawBoardTable(ctx, x, y, w, opts, rowH) {
650
650
  ry += s(8);
651
651
  textCenter(
652
652
  ctx,
653
- `⋯ 另有 ${row.gapBefore} 词未列 ⋯`,
653
+ `…… 另有 ${row.gapBefore} 词未列`,
654
654
  cx + tableW / 2,
655
655
  ry + s(10),
656
656
  `${s(11.5)}px ${FONT_SANS}`,
657
- C.outline
657
+ C.onSurfaceVariant
658
658
  );
659
659
  ctx.strokeStyle = C.outlineVariant;
660
660
  ctx.beginPath();
@@ -684,7 +684,7 @@ function drawBoardTable(ctx, x, y, w, opts, rowH) {
684
684
  noX,
685
685
  midY,
686
686
  `${s(13)}px ${FONT_NUM}`,
687
- C.outline
687
+ C.onSurfaceVariant
688
688
  );
689
689
  const nbX = nb0 + (cols.nb - gridWidth(history.leftHint, "sm")) / 2;
690
690
  drawWordGrid(ctx, nbX, midY - s(13), history.leftHint, "sm", 0);
@@ -698,7 +698,7 @@ function drawBoardTable(ctx, x, y, w, opts, rowH) {
698
698
  const hashW = textWidth("#", s(13));
699
699
  const rankBlock = hashW + s(3) + rankWidth;
700
700
  const rankLeft = rk0 + (cols.rk - rankBlock) / 2;
701
- textLeft(ctx, "#", rankLeft, midY, `${s(13)}px ${FONT_NUM}`, C.outline);
701
+ textLeft(ctx, "#", rankLeft, midY, `${s(13)}px ${FONT_NUM}`, C.onSurfaceVariant);
702
702
  textLeft(ctx, rankText, rankLeft + hashW + s(3), midY, rankFont, tier.color);
703
703
  drawNearBar(ctx, mtX, midY, pct, tier);
704
704
  ry += rowH;
@@ -755,7 +755,7 @@ function renderIntroCard(service, opts) {
755
755
  textLeft(ctx, "ciyi.裸词 开/关", ix + s(96), y + s(52), `${s(13.5)}px ${FONT_NUM}`, C.onSurface);
756
756
  textLeftFit(
757
757
  ctx,
758
- "临时改本群的续猜方式",
758
+ "临时改本频道的续猜方式",
759
759
  ix + s(230),
760
760
  y + s(52),
761
761
  iw - s(268),
@@ -789,7 +789,7 @@ function renderIntroCard(service, opts) {
789
789
  ix + s(38),
790
790
  y + s(104),
791
791
  `${s(12)}px ${FONT_SANS}`,
792
- C.outline
792
+ C.onSurfaceVariant
793
793
  );
794
794
  textLeft(
795
795
  ctx,
@@ -797,7 +797,7 @@ function renderIntroCard(service, opts) {
797
797
  ix + s(38),
798
798
  y + s(126),
799
799
  `${s(12)}px ${FONT_SANS}`,
800
- C.outline
800
+ C.onSurfaceVariant
801
801
  );
802
802
  textLeft(
803
803
  ctx,
@@ -828,7 +828,7 @@ function renderIntroCard(service, opts) {
828
828
  ix + s(22),
829
829
  iy + ih - s(30),
830
830
  `${s(11.5)}px ${FONT_SANS}`,
831
- C.outline
831
+ C.onSurfaceVariant
832
832
  );
833
833
  });
834
834
  }
@@ -842,11 +842,11 @@ function renderWinCard(service, opts) {
842
842
  const revealH = s(130);
843
843
  drawPanel(ctx, ix + s(22), y, iw - s(44), revealH);
844
844
  drawSeal(ctx, ix + iw - s(80), y - s(13), s(42), ["中"]);
845
- textLeft(ctx, "今日答案", ix + s(38), y + s(24), `${s(11.5)}px ${FONT_SANS}`, C.outline);
845
+ textLeft(ctx, "今日答案", ix + s(38), y + s(24), `${s(11.5)}px ${FONT_SANS}`, C.onSurfaceVariant);
846
846
  drawWordGrid(ctx, ix + s(38), y + s(40), opts.answer, "lg");
847
847
  const ax = ix + iw - s(310);
848
848
  if (opts.neighbors.length) {
849
- textLeft(ctx, "意思最近的几个词", ax, y + s(24), `${s(11.5)}px ${FONT_SANS}`, C.outline);
849
+ textLeft(ctx, "意思最近的几个词", ax, y + s(24), `${s(11.5)}px ${FONT_SANS}`, C.onSurfaceVariant);
850
850
  let nx = ax;
851
851
  let ny = y + s(48);
852
852
  const chipFont = `${s(14)}px ${FONT_SANS}`;
@@ -888,7 +888,7 @@ function renderWinCard(service, opts) {
888
888
  if (stats[i].u) {
889
889
  textLeft(ctx, stats[i].u, sx + s(14) + textWidth(stats[i].n, s(23)) + s(3), y + s(26), `${s(12)}px ${FONT_SANS}`, C.onSurfaceVariant);
890
890
  }
891
- textLeft(ctx, stats[i].c, sx + s(14), y + s(44), `${s(10.5)}px ${FONT_SANS}`, C.outline);
891
+ textLeft(ctx, stats[i].c, sx + s(14), y + s(44), `${s(10.5)}px ${FONT_SANS}`, C.onSurfaceVariant);
892
892
  }
893
893
  const quip = opts.closest === null ? "一击即中,今日无需第二次落笔" : `从 #${opts.closest} 一步跨到 #1`;
894
894
  const footerTip = opts.canStartToday ? "ciyi.猜 山水 · 开启今日新题" : "明日零点换新题 · ciyi.排行榜";
@@ -945,7 +945,7 @@ function renderRankCard(service, opts) {
945
945
  panelX + panelW / 2,
946
946
  y + s(31),
947
947
  `${s(13.5)}px ${FONT_SANS}`,
948
- C.outline
948
+ C.onSurfaceVariant
949
949
  );
950
950
  textCenter(
951
951
  ctx,
@@ -953,7 +953,7 @@ function renderRankCard(service, opts) {
953
953
  panelX + panelW / 2,
954
954
  y + s(61),
955
955
  `${s(13.5)}px ${FONT_SANS}`,
956
- C.outline
956
+ C.onSurfaceVariant
957
957
  );
958
958
  } else {
959
959
  let ry = y;
@@ -1005,17 +1005,17 @@ function renderRankCard(service, opts) {
1005
1005
  `700 ${scoreFont}px ${FONT_NUM}`,
1006
1006
  C.onSurface
1007
1007
  );
1008
- textRight(ctx, "次", contentRight, midY, `${unitFont}px ${FONT_SANS}`, C.outline);
1008
+ textRight(ctx, "次", contentRight, midY, `${unitFont}px ${FONT_SANS}`, C.onSurfaceVariant);
1009
1009
  ry += rowH;
1010
1010
  }
1011
1011
  if (opts.hidden > 0) {
1012
1012
  textLeft(
1013
1013
  ctx,
1014
- `⋯ 另有 ${opts.hidden} 人在榜`,
1014
+ `…… 另有 ${opts.hidden} 人在榜`,
1015
1015
  contentLeft,
1016
1016
  ry + s(16),
1017
1017
  `${s(11.5)}px ${FONT_SANS}`,
1018
- C.outline
1018
+ C.onSurfaceVariant
1019
1019
  );
1020
1020
  }
1021
1021
  }
@@ -1033,7 +1033,7 @@ function renderRankCard(service, opts) {
1033
1033
  panelX,
1034
1034
  iy + ih - s(30),
1035
1035
  `${s(11.5)}px ${FONT_SANS}`,
1036
- C.outline
1036
+ C.onSurfaceVariant
1037
1037
  );
1038
1038
  });
1039
1039
  }
@@ -1050,19 +1050,20 @@ var usage = `## 使用
1050
1050
  | 指令 | 说明 |
1051
1051
  | --- | --- |
1052
1052
  | \`ciyi\` | 玩法 |
1053
- | \`ciyi.猜 <词>\` | 开始今日游戏并提交猜测 |
1053
+ | \`ciyi.猜 <词>\` | 开始今日对局并报一个两字词 |
1054
1054
  | \`ciyi.裸词 [开/关]\` | 开关本频道的无前缀续猜 |
1055
1055
  | \`ciyi.排行榜\` | 累计猜中排行榜 |`;
1056
1056
  var inject = { required: ["database"], optional: ["canvas"] };
1057
1057
  var Config = import_koishi.Schema.object({
1058
1058
  atReply: import_koishi.Schema.boolean().default(false).description("回复时 @ 用户。"),
1059
1059
  quoteReply: import_koishi.Schema.boolean().default(false).description("回复时引用消息。"),
1060
- isEnableMiddleware: import_koishi.Schema.boolean().default(true).description(
1061
- "已开题时,直接发送两字词即可续猜,无需指令前缀。各频道可用「ciyi.裸词」临时切换。"
1060
+ enableDirectInput: import_koishi.Schema.boolean().default(true).description(
1061
+ "对局中直接发送两字词即可续猜,无需指令前缀。各频道可用「ciyi.裸词」临时切换。"
1062
1062
  ),
1063
1063
  renderImage: import_koishi.Schema.boolean().default(true).description("把猜测板渲染成图片,复用 Koishi Canvas 服务。服务不可用时自动回退为等价文本。"),
1064
1064
  maxHistory: import_koishi.Schema.number().default(10).min(0).description("猜测板最多列出的历史条数。最新一次猜测始终会列出。"),
1065
- maxRank: import_koishi.Schema.number().default(10).min(0).description("排行榜最多显示的人数。")
1065
+ maxRank: import_koishi.Schema.number().default(10).min(0).description("排行榜最多显示的人数。"),
1066
+ retractDelay: import_koishi.Schema.number().min(0).default(0).description("自动撤回延迟(秒),0 表示不撤回。")
1066
1067
  });
1067
1068
  function getMiddlewareGuess(session) {
1068
1069
  const message = session.event.message;
@@ -1128,9 +1129,9 @@ function apply(ctx, cfg) {
1128
1129
  );
1129
1130
  const logger = ctx.logger("ciyi");
1130
1131
  const random = new import_koishi.Random(() => Math.random());
1131
- const RULE = "────────────";
1132
+ const busy = /* @__PURE__ */ new Set();
1132
1133
  const middlewareOverrides = /* @__PURE__ */ new Map();
1133
- const middlewareOn = /* @__PURE__ */ __name((channelId) => channelId === void 0 ? cfg.isEnableMiddleware : middlewareOverrides.get(channelId) ?? cfg.isEnableMiddleware, "middlewareOn");
1134
+ const middlewareOn = /* @__PURE__ */ __name((channelId) => channelId === void 0 ? cfg.enableDirectInput : middlewareOverrides.get(channelId) ?? cfg.enableDirectInput, "middlewareOn");
1134
1135
  ctx.middleware(async (session, next) => {
1135
1136
  if (!session.channelId || !middlewareOn(session.channelId)) return await next();
1136
1137
  const guess = getMiddlewareGuess(session);
@@ -1152,7 +1153,7 @@ function apply(ctx, cfg) {
1152
1153
  ctx.command("ciyi", "词意 · 按意思远近找词").action(async ({ session }) => {
1153
1154
  return await wf(session);
1154
1155
  });
1155
- ctx.command("ciyi.猜 <guess:string>", "报一个两字词").usage("例:ciyi.猜 山水").action(async ({ session }, guess) => {
1156
+ ctx.command("ciyi.猜 <guess:string>", "开题并报一个两字词").usage("例:ciyi.猜 山水").action(async ({ session }, guess) => {
1156
1157
  return await c(session, guess?.trim());
1157
1158
  });
1158
1159
  ctx.command("ciyi.排行榜", "查看累计猜中排行榜").action(async ({ session }) => {
@@ -1160,7 +1161,7 @@ function apply(ctx, cfg) {
1160
1161
  });
1161
1162
  ctx.command("ciyi.裸词 [state:string]", "开关本频道的无前缀续猜").usage("例:ciyi.裸词(切换)· ciyi.裸词 开 · ciyi.裸词 关 · ciyi.裸词 状态").action(async ({ session }, state) => {
1162
1163
  const result = resolveMiddlewareSwitch(
1163
- cfg.isEnableMiddleware,
1164
+ cfg.enableDirectInput,
1164
1165
  session.channelId === void 0 ? void 0 : middlewareOverrides.get(session.channelId),
1165
1166
  state
1166
1167
  );
@@ -1235,7 +1236,7 @@ function apply(ctx, cfg) {
1235
1236
  __name(bestRank, "bestRank");
1236
1237
  function textCard(title, ...sections) {
1237
1238
  const body = sections.map((s2) => Array.isArray(s2) ? s2.filter(Boolean).join("\n") : s2).filter((s2) => !!s2 && !!s2.trim()).join("\n\n");
1238
- return [`▍${title}`, RULE, body].filter(Boolean).join("\n");
1239
+ return [`▍${title}`, body].filter(Boolean).join("\n");
1239
1240
  }
1240
1241
  __name(textCard, "textCard");
1241
1242
  function neighborText(hint, side) {
@@ -1246,7 +1247,7 @@ function apply(ctx, cfg) {
1246
1247
  function boardText(rows, hidden, attempts) {
1247
1248
  const lines = [];
1248
1249
  rows.forEach((row, i) => {
1249
- if (row.gapBefore) lines.push(`  另有 ${row.gapBefore} 词未列 ⋯`);
1250
+ if (row.gapBefore) lines.push(` …… 另有 ${row.gapBefore} 词未列`);
1250
1251
  const { guess, rank, leftHint, rightHint } = row.history;
1251
1252
  const mark = row.fresh ? "▸" : " ";
1252
1253
  lines.push(
@@ -1259,7 +1260,7 @@ function apply(ctx, cfg) {
1259
1260
  `词意 · 第 ${attempts} 次猜测`,
1260
1261
  best === null ? null : `当前最佳 #${best} · ${tierOf(best).name}(${tierOf(best).note})`,
1261
1262
  lines,
1262
- hidden > 0 && !tail ? `  另有 ${hidden} 词未列 ⋯` : null,
1263
+ hidden > 0 && !tail ? ` …… 另有 ${hidden} 词未列` : null,
1263
1264
  "左邻更近答案,右邻更远;? 为隐去的字"
1264
1265
  );
1265
1266
  }
@@ -1267,12 +1268,16 @@ function apply(ctx, cfg) {
1267
1268
  function introText(channelOn) {
1268
1269
  return textCard(
1269
1270
  "词意 · 按意思远近找词",
1270
- `每天藏起一个两字词。你报词,我回它与答案的语义排名 —— 名次越小越近,#1 就是答案本身。`,
1271
+ [
1272
+ `每天藏起一个两字词。`,
1273
+ `报一个两字词,回它与答案的语义排名。`,
1274
+ `名次越小越近,#1 就是答案本身。`
1275
+ ],
1271
1276
  [
1272
1277
  `开始 ciyi.猜 山水  开题并报一个两字词`,
1273
1278
  channelOn ? `续猜 直接发送两字词 仅限已开题且未结束` : null,
1274
- `排行 ciyi.排行榜  看谁猜中得最多`,
1275
- `切换 ciyi.裸词 开/关  临时改本群续猜方式`
1279
+ `排行榜 ciyi.排行榜  看谁猜中得最多`,
1280
+ `切换 ciyi.裸词 开/关  临时改本频道续猜方式`
1276
1281
  ].filter(Boolean),
1277
1282
  [
1278
1283
  `读板 ?好)企业(地? #467 · 沾边`,
@@ -1284,21 +1289,22 @@ function apply(ctx, cfg) {
1284
1289
  `亲疏 咫尺 ≤10 毗邻 ≤50 相近 ≤200`,
1285
1290
  `   沾边 ≤1000 疏远 ≤5000 天涯 5000+`
1286
1291
  ],
1287
- `词库 ${allWords_default.length.toLocaleString()} 词,题库 ${questionList_default.length.toLocaleString()} 题,一日一词。`
1292
+ `词库 ${allWords_default.length.toLocaleString()} 词,一日一词。`
1288
1293
  );
1289
1294
  }
1290
1295
  __name(introText, "introText");
1291
1296
  function middlewareSwitchText(o) {
1292
1297
  const mark = "✅";
1293
1298
  const state = o.on ? "开启" : "停用";
1294
- const config = cfg.isEnableMiddleware ? "开启" : "停用";
1299
+ const config = cfg.enableDirectInput ? "开启" : "停用";
1295
1300
  if (!o.temporary) {
1296
- return `${mark} 裸词续猜 · ${o.reverted ? "已复原," : ""}跟随插件配置(${state})`;
1301
+ return `${mark} 裸词续猜 · 跟随插件配置
1302
+ 当前${state}。${o.reverted ? "本次已复原。" : ""}`;
1297
1303
  }
1298
1304
  const hint = o.on ? "开题后直接发送两字词即可续猜" : "续猜改用「ciyi.猜 山水」";
1299
1305
  return [
1300
- `${mark} 裸词续猜 · 本频道临时${state}(插件配置:${config})`,
1301
- `${hint};再次发送「ciyi.裸词」复原`
1306
+ `${mark} 裸词续猜 · 本频道临时${state}`,
1307
+ `${hint};插件配置为${config};再次发送「ciyi.裸词」复原`
1302
1308
  ].join("\n");
1303
1309
  }
1304
1310
  __name(middlewareSwitchText, "middlewareSwitchText");
@@ -1312,23 +1318,24 @@ function apply(ctx, cfg) {
1312
1318
  `累计 ${o.username} 已猜中 ${o.score} 次`,
1313
1319
  o.neighbors.length ? `近旁 ${o.neighbors.join(" · ")}` : null
1314
1320
  ].filter(Boolean),
1315
- o.canStartToday ? "继续 ciyi.猜 山水 · 开启今日新题" : "明日零点换新题 · ciyi.排行榜 看战绩"
1321
+ o.canStartToday ? "继续发送「ciyi.猜 山水」开启今日新题" : "明日零点换新题 · 发送「ciyi.排行榜」看战绩"
1316
1322
  );
1317
1323
  }
1318
1324
  __name(winText, "winText");
1319
1325
  function rankText(entries, hidden) {
1320
1326
  if (!entries.length) {
1321
1327
  return textCard(
1322
- "词意每日挑战排行榜",
1323
- "榜上无名。今日第一个猜中的人,名字会写在这里。"
1328
+ "📋 排行榜还空着",
1329
+ "第一个猜中的人,名字会写在这里。",
1330
+ "发送「ciyi.猜 山水」开一局。"
1324
1331
  );
1325
1332
  }
1326
1333
  return textCard(
1327
- "词意每日挑战排行榜",
1334
+ "📋 词意每日挑战排行榜",
1328
1335
  entries.map(
1329
1336
  (e, i) => `${String(i + 1).padStart(2, "0")}. ${e.username || "无名氏"} ${e.score} 次`
1330
1337
  ),
1331
- hidden > 0 ? `⋯ 另有 ${hidden} 人在榜 ⋯` : null
1338
+ hidden > 0 ? `…… 另有 ${hidden} 人在榜` : null
1332
1339
  );
1333
1340
  }
1334
1341
  __name(rankText, "rankText");
@@ -1343,16 +1350,33 @@ function apply(ctx, cfg) {
1343
1350
  }
1344
1351
  }
1345
1352
  __name(renderCard, "renderCard");
1353
+ const lastMessage = /* @__PURE__ */ new Map();
1354
+ async function send(session, content) {
1355
+ const ids = await session.send(content);
1356
+ const messageId = ids?.[0];
1357
+ if (!cfg.retractDelay || !messageId) return;
1358
+ const previous = lastMessage.get(session.channelId);
1359
+ if (previous) {
1360
+ const passed = Date.now() - previous.timestamp;
1361
+ if (passed < 118e3) {
1362
+ ctx.setTimeout(() => {
1363
+ session.bot.deleteMessage(session.channelId, previous.id).catch((error) => logger.debug("撤回消息 %s 失败:%s", previous.id, error.message));
1364
+ }, Math.max(0, cfg.retractDelay * 1e3 - passed));
1365
+ }
1366
+ }
1367
+ lastMessage.set(session.channelId, { id: messageId, timestamp: Date.now() });
1368
+ }
1369
+ __name(send, "send");
1346
1370
  async function sendCard(session, render, fallback) {
1347
1371
  const image = await renderCard(render);
1348
1372
  const prefix = [];
1349
1373
  if (cfg.quoteReply && session.messageId) prefix.push(import_koishi.h.quote(session.messageId));
1350
1374
  if (cfg.atReply) prefix.push(import_koishi.h.at(session.userId), (0, import_koishi.h)("p"));
1351
1375
  if (image) {
1352
- await session.send([...prefix, image]);
1376
+ await send(session, [...prefix, image]);
1353
1377
  return;
1354
1378
  }
1355
- await session.send([...prefix, ...import_koishi.h.normalize(fallback)]);
1379
+ await send(session, [...prefix, ...import_koishi.h.normalize(fallback)]);
1356
1380
  }
1357
1381
  __name(sendCard, "sendCard");
1358
1382
  async function wf(session) {
@@ -1429,6 +1453,18 @@ function apply(ctx, cfg) {
1429
1453
  return await sendMsg(session, `⚠️ 「${guess}」不在词库里
1430
1454
  换个常见些的词试试。`);
1431
1455
  }
1456
+ if (session.channelId && busy.has(session.channelId)) {
1457
+ return await sendMsg(session, "⏳ 上一手还在处理\n稍等再发一次。");
1458
+ }
1459
+ if (session.channelId) busy.add(session.channelId);
1460
+ try {
1461
+ return await guessOnce(session, guess);
1462
+ } finally {
1463
+ if (session.channelId) busy.delete(session.channelId);
1464
+ }
1465
+ }
1466
+ __name(c, "c");
1467
+ async function guessOnce(session, guess) {
1432
1468
  const gameInfo = await getTodayGame(session);
1433
1469
  if (!gameInfo) {
1434
1470
  return await sendMsg(
@@ -1439,7 +1475,7 @@ function apply(ctx, cfg) {
1439
1475
  if (gameInfo.isOver) {
1440
1476
  return await sendMsg(
1441
1477
  session,
1442
- `💡 今日挑战已结束,答案是「${gameInfo.answer}」。
1478
+ `💡 今日挑战已结束,答案是「${gameInfo.answer}
1443
1479
  明日零点换新题,发送「ciyi.排行榜」看战绩。`
1444
1480
  );
1445
1481
  }
@@ -1499,7 +1535,7 @@ function apply(ctx, cfg) {
1499
1535
  const entry = getHistory(guess, rankList);
1500
1536
  if (!entry) {
1501
1537
  logger.warn(`「${guess}」不在 ${gameInfo.answer} 的榜单中`);
1502
- return await sendMsg(session, `⚠️ 「${guess}」不在今日的榜单里
1538
+ return await sendMsg(session, `⚠️ 「${guess}」不在今日的榜里
1503
1539
  换个词试试。`);
1504
1540
  }
1505
1541
  const history = [...gameInfo.history, entry];
@@ -1524,7 +1560,7 @@ function apply(ctx, cfg) {
1524
1560
  boardText(rows, hidden, history.length)
1525
1561
  );
1526
1562
  }
1527
- __name(c, "c");
1563
+ __name(guessOnce, "guessOnce");
1528
1564
  async function fetchCiYi(word) {
1529
1565
  const url = `https://ci-ying.oss-cn-zhangjiakou.aliyuncs.com/v1/ci-yi-list/${word}.txt`;
1530
1566
  try {
@@ -1547,7 +1583,7 @@ function apply(ctx, cfg) {
1547
1583
  const prefix = [];
1548
1584
  if (cfg.quoteReply && session.messageId) prefix.push(import_koishi.h.quote(session.messageId));
1549
1585
  if (cfg.atReply) prefix.push(import_koishi.h.at(session.userId), (0, import_koishi.h)("p"));
1550
- await session.send([...prefix, ...import_koishi.h.normalize(msg)]);
1586
+ await send(session, [...prefix, ...import_koishi.h.normalize(msg)]);
1551
1587
  }
1552
1588
  __name(sendMsg, "sendMsg");
1553
1589
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-ciyi",
3
3
  "description": "Koishi 的词意猜词插件。",
4
- "version": "1.4.0",
4
+ "version": "2.0.0",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [