koishi-plugin-ciyi 1.4.1 → 2.1.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
@@ -15,11 +15,11 @@ yarn add koishi-plugin-ciyi
15
15
  | 指令 | 说明 |
16
16
  | --- | --- |
17
17
  | `ciyi` | 玩法 |
18
- | `ciyi.猜 <词>` | 开始今日游戏并提交猜测 |
19
- | `ciyi.裸词 [开/关]` | 切换本群无前缀续猜 |
20
- | `ciyi.排行榜` | 猜中次数排行 |
18
+ | `ciyi.猜 <词>` | 开始今日对局并报一个两字词 |
19
+ | `ciyi.裸词 [开/关]` | 切换本频道无前缀续猜 |
20
+ | `ciyi.排行榜` | 累计猜中排行榜 |
21
21
 
22
- 首次用 `ciyi.猜 <词>` 开题,之后可直接发送两字词继续猜。裸词设置只对当前群生效,重启后恢复默认。
22
+ 首次用 `ciyi.猜 <词>` 开题,之后可直接发送两字词继续猜。裸词设置只对当前频道生效,重启后恢复默认。
23
23
 
24
24
  ## 许可证
25
25
 
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
@@ -203,6 +203,25 @@ var SHAPE = {
203
203
  full: 9999
204
204
  };
205
205
  var FONT_STACK = '"Roboto Flex", "Roboto", "Noto Sans SC", "PingFang SC", "Microsoft YaHei", "Source Han Sans SC", system-ui, sans-serif';
206
+ var MONO_STACK = '"Roboto Mono", "JetBrains Mono", "SF Mono", "Cascadia Mono", Consolas, "Noto Sans Mono", monospace';
207
+ var TYPE = {
208
+ displayLarge: { size: 57, line: 64, weight: 400, tracking: -0.25 },
209
+ displayMedium: { size: 45, line: 52, weight: 400, tracking: 0 },
210
+ displaySmall: { size: 36, line: 44, weight: 400, tracking: 0 },
211
+ headlineLarge: { size: 32, line: 40, weight: 400, tracking: 0 },
212
+ headlineMedium: { size: 28, line: 36, weight: 400, tracking: 0 },
213
+ headlineSmall: { size: 24, line: 32, weight: 400, tracking: 0 },
214
+ titleLarge: { size: 22, line: 28, weight: 400, tracking: 0 },
215
+ titleMedium: { size: 16, line: 24, weight: 500, tracking: 0.15 },
216
+ titleSmall: { size: 14, line: 20, weight: 500, tracking: 0.1 },
217
+ bodyLarge: { size: 16, line: 24, weight: 400, tracking: 0.5 },
218
+ bodyMedium: { size: 14, line: 20, weight: 400, tracking: 0.25 },
219
+ bodySmall: { size: 12, line: 16, weight: 400, tracking: 0.4 },
220
+ labelLarge: { size: 14, line: 20, weight: 500, tracking: 0.1 },
221
+ labelMedium: { size: 12, line: 16, weight: 500, tracking: 0.5 },
222
+ labelSmall: { size: 11, line: 16, weight: 500, tracking: 0.5 }
223
+ };
224
+ var EMPHASIZED_WEIGHT = { display: 600, headline: 600, title: 600, label: 600 };
206
225
  var MEDAL = {
207
226
  gold: lch(58, 52, 85),
208
227
  silver: lch(66, 5, 260),
@@ -247,11 +266,13 @@ var C = {
247
266
  primary: SCHEME.primary,
248
267
  onPrimary: SCHEME.onPrimary,
249
268
  primaryContainer: SCHEME.primaryContainer,
250
- onPrimaryContainer: SCHEME.onPrimaryContainer
269
+ onPrimaryContainer: SCHEME.onPrimaryContainer,
270
+ secondaryContainer: SCHEME.secondaryContainer,
271
+ onSecondaryContainer: SCHEME.onSecondaryContainer
251
272
  };
252
273
  var FRESH = lch(94, 14, HUE);
253
274
  var FONT_SANS = FONT_STACK;
254
- var FONT_NUM = FONT_STACK;
275
+ var FONT_NUM = `${MONO_STACK},${FONT_STACK}`;
255
276
  function s(n) {
256
277
  return Math.round(n * SCALE);
257
278
  }
@@ -317,7 +338,7 @@ function textRight(ctx, text, x, y, font, color, offsetY = 0) {
317
338
  }
318
339
  __name(textRight, "textRight");
319
340
  function fontSizeOf(font) {
320
- return Number(font.match(/([\d.]+)px/)?.[1] ?? s(12));
341
+ return Number(font.match(/([\d.]+)px/)?.[1] ?? s(TYPE.bodySmall.size));
321
342
  }
322
343
  __name(fontSizeOf, "fontSizeOf");
323
344
  function textWidth(text, fontSize) {
@@ -378,9 +399,30 @@ async function toPng(service, innerW, innerH, draw) {
378
399
  }
379
400
  __name(toPng, "toPng");
380
401
  var GRID = {
381
- sm: { cell: 26, gap: 4, font: 15, border: 1 },
382
- md: { cell: 34, gap: 4, font: 20, border: 1 },
383
- lg: { cell: 78, gap: 9, font: 46, border: 1.5 }
402
+ sm: {
403
+ cell: 26,
404
+ gap: 4,
405
+ border: 1,
406
+ font: TYPE.labelLarge.size,
407
+ weight: EMPHASIZED_WEIGHT.label,
408
+ radius: SHAPE.extraSmall
409
+ },
410
+ md: {
411
+ cell: 34,
412
+ gap: 4,
413
+ border: 1,
414
+ font: TYPE.titleLarge.size,
415
+ weight: EMPHASIZED_WEIGHT.title,
416
+ radius: SHAPE.small
417
+ },
418
+ lg: {
419
+ cell: 78,
420
+ gap: 9,
421
+ border: 1.5,
422
+ font: TYPE.displayMedium.size,
423
+ weight: EMPHASIZED_WEIGHT.display,
424
+ radius: SHAPE.large
425
+ }
384
426
  };
385
427
  function gridWidth(word, size) {
386
428
  const g = GRID[size];
@@ -389,9 +431,9 @@ function gridWidth(word, size) {
389
431
  return s(g.cell * n + g.gap * (n - 1));
390
432
  }
391
433
  __name(gridWidth, "gridWidth");
392
- function drawGridCell(ctx, x, y, size, ch, mode, fontSize, borderW) {
434
+ function drawGridCell(ctx, x, y, size, ch, mode, g) {
393
435
  const half = size / 2;
394
- const radius = Math.min(s(SHAPE.large), Math.max(s(SHAPE.extraSmall), size * 0.2));
436
+ const radius = s(g.radius);
395
437
  if (mode === "mk") {
396
438
  fillRound(ctx, x, y, size, size, radius, C.surfaceContainerHighest);
397
439
  ctx.save();
@@ -404,20 +446,20 @@ function drawGridCell(ctx, x, y, size, ch, mode, fontSize, borderW) {
404
446
  size - s(1),
405
447
  radius,
406
448
  C.outline,
407
- s(borderW)
449
+ s(g.border)
408
450
  );
409
451
  ctx.restore();
410
452
  return;
411
453
  }
412
454
  fillRound(ctx, x, y, size, size, radius, C.surfaceContainerHigh);
413
455
  if (ch) {
414
- const fs = s(fontSize);
456
+ const fs = s(g.font);
415
457
  textCenter(
416
458
  ctx,
417
459
  ch,
418
460
  x + half,
419
461
  y + half,
420
- `600 ${fs}px ${FONT_SANS}`,
462
+ `${g.weight} ${fs}px ${FONT_SANS}`,
421
463
  C.onSurface,
422
464
  cjkOffset(fs)
423
465
  );
@@ -430,15 +472,15 @@ function drawWordGrid(ctx, x, y, word, size, mask) {
430
472
  const gap = s(g.gap);
431
473
  const chars = Array.from(word || "");
432
474
  if (chars.length === 0) {
433
- textCenter(ctx, "—", x + s(14), y + cell / 2, `${s(13)}px ${FONT_SANS}`, C.onSurfaceVariant);
475
+ textCenter(ctx, "—", x + s(14), y + cell / 2, `${s(TYPE.bodySmall.size)}px ${FONT_SANS}`, C.onSurfaceVariant);
434
476
  return s(28);
435
477
  }
436
478
  let cx = x;
437
479
  for (let i = 0; i < chars.length; i++) {
438
480
  if (i === mask) {
439
- drawGridCell(ctx, cx, y, cell, null, "mk", g.font, g.border);
481
+ drawGridCell(ctx, cx, y, cell, null, "mk", g);
440
482
  } else {
441
- drawGridCell(ctx, cx, y, cell, chars[i], "on", g.font, g.border);
483
+ drawGridCell(ctx, cx, y, cell, chars[i], "on", g);
442
484
  }
443
485
  cx += cell + gap;
444
486
  }
@@ -447,12 +489,12 @@ function drawWordGrid(ctx, x, y, word, size, mask) {
447
489
  __name(drawWordGrid, "drawWordGrid");
448
490
  function drawSeal(ctx, x, y, size, lines) {
449
491
  fillRound(ctx, x, y, size, size, s(SHAPE.extraLarge), C.primary);
450
- const fs = s(19);
492
+ const fs = s(TYPE.titleLarge.size);
451
493
  const lh = fs * 1.06;
452
494
  const total = lh * lines.length;
453
495
  let cy = y + size / 2 - total / 2 + lh / 2;
454
496
  for (const line of lines) {
455
- textCenter(ctx, line, x + size / 2, cy, `600 ${fs}px ${FONT_SANS}`, C.onPrimary);
497
+ textCenter(ctx, line, x + size / 2, cy, `${EMPHASIZED_WEIGHT.title} ${fs}px ${FONT_SANS}`, C.onPrimary);
456
498
  cy += lh;
457
499
  }
458
500
  }
@@ -465,13 +507,20 @@ function drawHeader(ctx, innerX, innerY, innerW, sub, right) {
465
507
  const h2 = padTop + sealSize + padBottom;
466
508
  drawSeal(ctx, innerX + padX, innerY + padTop, sealSize, ["词", "意"]);
467
509
  const brandX = innerX + padX + sealSize + s(15);
468
- textLeft(ctx, "词意", brandX, innerY + padTop + s(14), `700 ${s(26)}px ${FONT_SANS}`, C.onSurface);
510
+ textLeft(
511
+ ctx,
512
+ "词意",
513
+ brandX,
514
+ innerY + padTop + s(14),
515
+ `${EMPHASIZED_WEIGHT.headline} ${s(TYPE.headlineMedium.size)}px ${FONT_SANS}`,
516
+ C.onSurface
517
+ );
469
518
  textLeft(
470
519
  ctx,
471
520
  "ci yi",
472
521
  brandX,
473
522
  innerY + padTop + s(36),
474
- `italic ${s(10)}px ${FONT_NUM}`,
523
+ `italic ${s(TYPE.labelSmall.size)}px ${FONT_SANS}`,
475
524
  C.onSurfaceVariant
476
525
  );
477
526
  const subRight = right ? innerX + innerW - padX - s(150) : innerX + innerW - padX;
@@ -481,13 +530,27 @@ function drawHeader(ctx, innerX, innerY, innerW, sub, right) {
481
530
  brandX,
482
531
  innerY + padTop + s(58),
483
532
  subRight - brandX,
484
- `${s(12.5)}px ${FONT_SANS}`,
533
+ `${s(TYPE.bodySmall.size)}px ${FONT_SANS}`,
485
534
  C.onSurfaceVariant
486
535
  );
487
536
  if (right) {
488
537
  const rx = innerX + innerW - padX;
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.onSurfaceVariant);
538
+ textRight(
539
+ ctx,
540
+ right.big,
541
+ rx,
542
+ innerY + padTop + s(16),
543
+ `${EMPHASIZED_WEIGHT.headline} ${s(TYPE.headlineLarge.size)}px ${FONT_NUM}`,
544
+ C.primary
545
+ );
546
+ textRight(
547
+ ctx,
548
+ right.cap,
549
+ rx,
550
+ innerY + padTop + s(44),
551
+ `${s(TYPE.labelSmall.size)}px ${FONT_SANS}`,
552
+ C.onSurfaceVariant
553
+ );
491
554
  }
492
555
  const lineY = innerY + h2;
493
556
  ctx.strokeStyle = C.outlineVariant;
@@ -525,16 +588,16 @@ function drawFooter(ctx, innerX, y, innerW, legendItems, tip) {
525
588
  for (const { tier, range } of legendItems) {
526
589
  fillRound(ctx, lx, cy - s(4.5), s(9), s(9), s(SHAPE.full), tier.color);
527
590
  lx += s(9) + s(6);
528
- textLeft(ctx, tier.name, lx, cy, `${s(11.5)}px ${FONT_SANS}`, C.onSurfaceVariant);
529
- lx += textWidth(tier.name, s(11.5)) + s(4);
530
- textLeft(ctx, range, lx, cy, `${s(11)}px ${FONT_NUM}`, C.onSurfaceVariant);
531
- lx += textWidth(range, s(11)) + s(13);
591
+ textLeft(ctx, tier.name, lx, cy, `${s(TYPE.labelSmall.size)}px ${FONT_SANS}`, C.onSurfaceVariant);
592
+ lx += textWidth(tier.name, s(TYPE.labelSmall.size)) + s(4);
593
+ textLeft(ctx, range, lx, cy, `${s(TYPE.labelSmall.size)}px ${FONT_NUM}`, C.onSurfaceVariant);
594
+ lx += textWidth(range, s(TYPE.labelSmall.size)) + s(13);
532
595
  }
533
- const tipFont = `${s(11.5)}px ${FONT_SANS}`;
596
+ const tipFont = `${s(TYPE.labelSmall.size)}px ${FONT_SANS}`;
534
597
  const maxTipWidth = legendItems.length ? s(145) : innerW - padX * 2;
535
598
  textRight(
536
599
  ctx,
537
- ellipsize(tip, maxTipWidth, s(11.5)),
600
+ ellipsize(tip, maxTipWidth, s(TYPE.labelSmall.size)),
538
601
  innerX + innerW - padX,
539
602
  cy,
540
603
  tipFont,
@@ -551,17 +614,24 @@ function drawLegendFull(ctx, x, y, w) {
551
614
  const cx = x + i % 2 * colW;
552
615
  const cy = y + Math.floor(i / 2) * rowH;
553
616
  fillRound(ctx, cx, cy - s(4.5), s(9), s(9), s(SHAPE.full), t.color);
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.onSurfaceVariant);
556
- textLeft(ctx, t.note, cx + s(100), cy, `${s(11.5)}px ${FONT_SANS}`, C.onSurfaceVariant);
617
+ textLeft(ctx, t.name, cx + s(15), cy, `${s(TYPE.labelSmall.size)}px ${FONT_SANS}`, C.onSurfaceVariant);
618
+ textLeft(ctx, tierRange(i), cx + s(52), cy, `${s(TYPE.labelSmall.size)}px ${FONT_NUM}`, C.onSurfaceVariant);
619
+ textLeft(ctx, t.note, cx + s(100), cy, `${s(TYPE.labelSmall.size)}px ${FONT_SANS}`, C.onSurfaceVariant);
557
620
  }
558
621
  return rowH * Math.ceil(TIERS.length / 2) + s(8);
559
622
  }
560
623
  __name(drawLegendFull, "drawLegendFull");
561
624
  function drawSectionTitle(ctx, x, y, w, title) {
562
625
  fillRound(ctx, x, y - s(1), s(3), s(12), s(SHAPE.full), C.primary);
563
- textLeft(ctx, title, x + s(15), y + s(5), `600 ${s(11.5)}px ${FONT_SANS}`, C.onSurfaceVariant);
564
- const tw = textWidth(title, s(11.5));
626
+ textLeft(
627
+ ctx,
628
+ title,
629
+ x + s(15),
630
+ y + s(5),
631
+ `${EMPHASIZED_WEIGHT.label} ${s(TYPE.labelMedium.size)}px ${FONT_SANS}`,
632
+ C.onSurfaceVariant
633
+ );
634
+ const tw = textWidth(title, s(TYPE.labelMedium.size));
565
635
  ctx.strokeStyle = C.outlineVariant;
566
636
  ctx.lineWidth = s(1);
567
637
  ctx.beginPath();
@@ -581,7 +651,7 @@ function drawNearBar(ctx, x, y, pct, tier) {
581
651
  const gap = s(3);
582
652
  const fillW = Math.max(barH, barW * pct / 100);
583
653
  const top = y - barH / 2;
584
- fillRound(ctx, x, top, fillW, barH, barH / 2, tier.color);
654
+ fillRound(ctx, x, top, fillW, barH, s(SHAPE.full), tier.color);
585
655
  if (fillW + gap < barW) {
586
656
  fillRound(
587
657
  ctx,
@@ -589,11 +659,18 @@ function drawNearBar(ctx, x, y, pct, tier) {
589
659
  top,
590
660
  barW - fillW - gap,
591
661
  barH,
592
- barH / 2,
662
+ s(SHAPE.full),
593
663
  C.surfaceContainerHighest
594
664
  );
595
665
  }
596
- textLeft(ctx, tier.name, x + barW + s(9), y, `600 ${s(12.5)}px ${FONT_SANS}`, tier.color);
666
+ textLeft(
667
+ ctx,
668
+ tier.name,
669
+ x + barW + s(9),
670
+ y,
671
+ `${EMPHASIZED_WEIGHT.label} ${s(TYPE.labelMedium.size)}px ${FONT_SANS}`,
672
+ tier.color
673
+ );
597
674
  }
598
675
  __name(drawNearBar, "drawNearBar");
599
676
  function measureBoard(opts) {
@@ -633,10 +710,10 @@ function drawBoardTable(ctx, x, y, w, opts, rowH) {
633
710
  const rk0 = nb1 + cols.nb;
634
711
  const mt0 = rk0 + cols.rk;
635
712
  const noX = bx + s(2);
636
- const meterBlockW = s(88) + s(9) + textWidth("咫尺", s(12.5));
713
+ const meterBlockW = s(88) + s(9) + textWidth("咫尺", s(TYPE.labelMedium.size));
637
714
  const mtX = mt0 + Math.max(0, (cols.mt - meterBlockW) / 2);
638
715
  const headY = y;
639
- const fs = s(10.5);
716
+ const fs = s(TYPE.labelSmall.size);
640
717
  textLeft(ctx, "序", noX, headY, `${fs}px ${FONT_SANS}`, C.onSurfaceVariant);
641
718
  textCenter(ctx, "更近 ◀", nb0 + cols.nb / 2, headY, `${fs}px ${FONT_SANS}`, C.onSurfaceVariant);
642
719
  textCenter(ctx, "猜测", gw0 + cols.gw / 2, headY, `${fs}px ${FONT_SANS}`, C.onSurfaceVariant);
@@ -650,10 +727,10 @@ function drawBoardTable(ctx, x, y, w, opts, rowH) {
650
727
  ry += s(8);
651
728
  textCenter(
652
729
  ctx,
653
- `⋯ 另有 ${row.gapBefore} 词未列 ⋯`,
730
+ `…… 另有 ${row.gapBefore} 词未列`,
654
731
  cx + tableW / 2,
655
732
  ry + s(10),
656
- `${s(11.5)}px ${FONT_SANS}`,
733
+ `${s(TYPE.labelSmall.size)}px ${FONT_SANS}`,
657
734
  C.onSurfaceVariant
658
735
  );
659
736
  ctx.strokeStyle = C.outlineVariant;
@@ -683,7 +760,7 @@ function drawBoardTable(ctx, x, y, w, opts, rowH) {
683
760
  String(ordinal).padStart(2, "0"),
684
761
  noX,
685
762
  midY,
686
- `${s(13)}px ${FONT_NUM}`,
763
+ `${s(TYPE.labelMedium.size)}px ${FONT_NUM}`,
687
764
  C.onSurfaceVariant
688
765
  );
689
766
  const nbX = nb0 + (cols.nb - gridWidth(history.leftHint, "sm")) / 2;
@@ -693,12 +770,12 @@ function drawBoardTable(ctx, x, y, w, opts, rowH) {
693
770
  const rbX = nb1 + (cols.nb - gridWidth(history.rightHint, "sm")) / 2;
694
771
  drawWordGrid(ctx, rbX, midY - s(13), history.rightHint, "sm", 1);
695
772
  const rankText = String(history.rank);
696
- const rankFont = `700 ${s(21)}px ${FONT_NUM}`;
697
- const rankWidth = textWidth(rankText, s(21));
698
- const hashW = textWidth("#", s(13));
773
+ const rankFont = `${EMPHASIZED_WEIGHT.title} ${s(TYPE.titleLarge.size)}px ${FONT_NUM}`;
774
+ const rankWidth = textWidth(rankText, s(TYPE.titleLarge.size));
775
+ const hashW = textWidth("#", s(TYPE.labelMedium.size));
699
776
  const rankBlock = hashW + s(3) + rankWidth;
700
777
  const rankLeft = rk0 + (cols.rk - rankBlock) / 2;
701
- textLeft(ctx, "#", rankLeft, midY, `${s(13)}px ${FONT_NUM}`, C.onSurfaceVariant);
778
+ textLeft(ctx, "#", rankLeft, midY, `${s(TYPE.labelMedium.size)}px ${FONT_NUM}`, C.onSurfaceVariant);
702
779
  textLeft(ctx, rankText, rankLeft + hashW + s(3), midY, rankFont, tier.color);
703
780
  drawNearBar(ctx, mtX, midY, pct, tier);
704
781
  ry += rowH;
@@ -740,31 +817,59 @@ function renderIntroCard(service, opts) {
740
817
  y += s(11);
741
818
  const p1h = s(104);
742
819
  drawPanel(ctx, ix + s(22), y, iw - s(44), p1h);
743
- textLeft(ctx, "开始", ix + s(38), y + s(24), `${s(14)}px ${FONT_SANS}`, C.primary);
744
- textLeft(ctx, "ciyi.猜 山水", ix + s(96), y + s(24), `${s(13.5)}px ${FONT_NUM}`, C.onSurface);
820
+ textLeft(ctx, "开始", ix + s(38), y + s(24), `${s(TYPE.labelLarge.size)}px ${FONT_SANS}`, C.primary);
821
+ textLeft(
822
+ ctx,
823
+ "ciyi.猜 山水",
824
+ ix + s(96),
825
+ y + s(24),
826
+ `${s(TYPE.labelLarge.size)}px ${FONT_SANS}`,
827
+ C.onSurface
828
+ );
745
829
  textLeftFit(
746
830
  ctx,
747
831
  `开题并报一个两字词${opts.middleware ? ";开题后可直接发词" : ""}`,
748
832
  ix + s(230),
749
833
  y + s(24),
750
834
  iw - s(268),
751
- `${s(14)}px ${FONT_SANS}`,
835
+ `${s(TYPE.bodyMedium.size)}px ${FONT_SANS}`,
836
+ C.onSurface
837
+ );
838
+ textLeft(ctx, "切换", ix + s(38), y + s(52), `${s(TYPE.labelLarge.size)}px ${FONT_SANS}`, C.primary);
839
+ textLeft(
840
+ ctx,
841
+ "ciyi.裸词 开/关",
842
+ ix + s(96),
843
+ y + s(52),
844
+ `${s(TYPE.labelLarge.size)}px ${FONT_SANS}`,
752
845
  C.onSurface
753
846
  );
754
- textLeft(ctx, "切换", ix + s(38), y + s(52), `${s(14)}px ${FONT_SANS}`, C.primary);
755
- textLeft(ctx, "ciyi.裸词 开/关", ix + s(96), y + s(52), `${s(13.5)}px ${FONT_NUM}`, C.onSurface);
756
847
  textLeftFit(
757
848
  ctx,
758
- "临时改本群的续猜方式",
849
+ "临时改本频道的续猜方式",
759
850
  ix + s(230),
760
851
  y + s(52),
761
852
  iw - s(268),
762
- `${s(14)}px ${FONT_SANS}`,
853
+ `${s(TYPE.bodyMedium.size)}px ${FONT_SANS}`,
854
+ C.onSurface
855
+ );
856
+ textLeft(ctx, "排行", ix + s(38), y + s(80), `${s(TYPE.labelLarge.size)}px ${FONT_SANS}`, C.primary);
857
+ textLeft(
858
+ ctx,
859
+ "ciyi.排行榜",
860
+ ix + s(96),
861
+ y + s(80),
862
+ `${s(TYPE.labelLarge.size)}px ${FONT_SANS}`,
863
+ C.onSurface
864
+ );
865
+ textLeft(
866
+ ctx,
867
+ "看谁猜中得最多",
868
+ ix + s(230),
869
+ y + s(80),
870
+ `${s(TYPE.bodyMedium.size)}px ${FONT_SANS}`,
763
871
  C.onSurface
764
872
  );
765
- textLeft(ctx, "排行", ix + s(38), y + s(80), `${s(14)}px ${FONT_SANS}`, C.primary);
766
- textLeft(ctx, "ciyi.排行榜", ix + s(96), y + s(80), `${s(13.5)}px ${FONT_NUM}`, C.onSurface);
767
- textLeft(ctx, "看谁猜中得最多", ix + s(230), y + s(80), `${s(14)}px ${FONT_SANS}`, C.onSurface);
768
873
  y += p1h + s(19);
769
874
  y += drawSectionTitle(ctx, ix + s(22), y, iw - s(44), "读板");
770
875
  y += s(11);
@@ -788,7 +893,7 @@ function renderIntroCard(service, opts) {
788
893
  "这一行是说:「企业」与今日答案的意思相近程度,排在第 467 位。",
789
894
  ix + s(38),
790
895
  y + s(104),
791
- `${s(12)}px ${FONT_SANS}`,
896
+ `${s(TYPE.bodySmall.size)}px ${FONT_SANS}`,
792
897
  C.onSurfaceVariant
793
898
  );
794
899
  textLeft(
@@ -796,7 +901,7 @@ function renderIntroCard(service, opts) {
796
901
  "两侧空格各藏一字:左邻更近,右邻更远。",
797
902
  ix + s(38),
798
903
  y + s(126),
799
- `${s(12)}px ${FONT_SANS}`,
904
+ `${s(TYPE.bodySmall.size)}px ${FONT_SANS}`,
800
905
  C.onSurfaceVariant
801
906
  );
802
907
  textLeft(
@@ -804,7 +909,7 @@ function renderIntroCard(service, opts) {
804
909
  "名次越小,离答案越近;#1 就是答案本身。",
805
910
  ix + s(38),
806
911
  y + s(148),
807
- `${s(12)}px ${FONT_SANS}`,
912
+ `${s(TYPE.bodySmall.size)}px ${FONT_SANS}`,
808
913
  C.onSurfaceVariant
809
914
  );
810
915
  y += sampleH + s(19);
@@ -827,7 +932,7 @@ function renderIntroCard(service, opts) {
827
932
  "一日一词,猜中即止,次日零点换题",
828
933
  ix + s(22),
829
934
  iy + ih - s(30),
830
- `${s(11.5)}px ${FONT_SANS}`,
935
+ `${s(TYPE.labelSmall.size)}px ${FONT_SANS}`,
831
936
  C.onSurfaceVariant
832
937
  );
833
938
  });
@@ -842,37 +947,44 @@ function renderWinCard(service, opts) {
842
947
  const revealH = s(130);
843
948
  drawPanel(ctx, ix + s(22), y, iw - s(44), revealH);
844
949
  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.onSurfaceVariant);
950
+ textLeft(ctx, "今日答案", ix + s(38), y + s(24), `${s(TYPE.labelSmall.size)}px ${FONT_SANS}`, C.onSurfaceVariant);
846
951
  drawWordGrid(ctx, ix + s(38), y + s(40), opts.answer, "lg");
847
952
  const ax = ix + iw - s(310);
848
953
  if (opts.neighbors.length) {
849
- textLeft(ctx, "意思最近的几个词", ax, y + s(24), `${s(11.5)}px ${FONT_SANS}`, C.onSurfaceVariant);
954
+ textLeft(ctx, "意思最近的几个词", ax, y + s(24), `${s(TYPE.labelSmall.size)}px ${FONT_SANS}`, C.onSurfaceVariant);
850
955
  let nx = ax;
851
956
  let ny = y + s(48);
852
- const chipFont = `${s(14)}px ${FONT_SANS}`;
957
+ const chipFont = `${s(TYPE.labelLarge.size)}px ${FONT_SANS}`;
853
958
  const chipRight = ix + iw - s(38);
854
959
  for (const word of opts.neighbors) {
855
- const fittedWord = ellipsize(word, s(116), s(14));
856
- const pw = textWidth(fittedWord, s(14)) + s(20);
960
+ const fittedWord = ellipsize(word, s(116), s(TYPE.labelLarge.size));
961
+ const pw = textWidth(fittedWord, s(TYPE.labelLarge.size)) + s(20);
857
962
  if (nx !== ax && nx + pw > chipRight) {
858
963
  nx = ax;
859
964
  ny += s(36);
860
965
  }
861
966
  if (ny + s(14) > y + revealH - s(12)) break;
862
- fillRound(ctx, nx, ny - s(14), pw, s(28), s(SHAPE.full), SCHEME.secondaryContainer);
967
+ fillRound(ctx, nx, ny - s(14), pw, s(28), s(SHAPE.full), C.secondaryContainer);
863
968
  textCenter(
864
969
  ctx,
865
970
  fittedWord,
866
971
  nx + pw / 2,
867
972
  ny,
868
973
  chipFont,
869
- SCHEME.onSecondaryContainer,
870
- cjkOffset(s(14))
974
+ C.onSecondaryContainer,
975
+ cjkOffset(s(TYPE.labelLarge.size))
871
976
  );
872
977
  nx += pw + s(7);
873
978
  }
874
979
  } else {
875
- textLeft(ctx, "今日一词,就此收笔。", ax, y + s(50), `${s(13.5)}px ${FONT_SANS}`, C.onSurfaceVariant);
980
+ textLeft(
981
+ ctx,
982
+ "今日一词,就此收笔。",
983
+ ax,
984
+ y + s(50),
985
+ `${s(TYPE.bodyMedium.size)}px ${FONT_SANS}`,
986
+ C.onSurfaceVariant
987
+ );
876
988
  }
877
989
  y += revealH + s(14);
878
990
  const statW = (iw - s(44) - s(20)) / 3;
@@ -884,11 +996,32 @@ function renderWinCard(service, opts) {
884
996
  for (let i = 0; i < 3; i++) {
885
997
  const sx = ix + s(22) + i * (statW + s(10));
886
998
  drawPanel(ctx, sx, y, statW, s(56));
887
- textLeft(ctx, stats[i].n, sx + s(14), y + s(22), `700 ${s(23)}px ${FONT_NUM}`, C.primary);
999
+ textLeft(
1000
+ ctx,
1001
+ stats[i].n,
1002
+ sx + s(14),
1003
+ y + s(22),
1004
+ `${EMPHASIZED_WEIGHT.headline} ${s(TYPE.headlineSmall.size)}px ${FONT_NUM}`,
1005
+ C.primary
1006
+ );
888
1007
  if (stats[i].u) {
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);
1008
+ textLeft(
1009
+ ctx,
1010
+ stats[i].u,
1011
+ sx + s(14) + textWidth(stats[i].n, s(TYPE.headlineSmall.size)) + s(3),
1012
+ y + s(26),
1013
+ `${s(TYPE.labelMedium.size)}px ${FONT_SANS}`,
1014
+ C.onSurfaceVariant
1015
+ );
890
1016
  }
891
- textLeft(ctx, stats[i].c, sx + s(14), y + s(44), `${s(10.5)}px ${FONT_SANS}`, C.onSurfaceVariant);
1017
+ textLeft(
1018
+ ctx,
1019
+ stats[i].c,
1020
+ sx + s(14),
1021
+ y + s(44),
1022
+ `${s(TYPE.labelSmall.size)}px ${FONT_SANS}`,
1023
+ C.onSurfaceVariant
1024
+ );
892
1025
  }
893
1026
  const quip = opts.closest === null ? "一击即中,今日无需第二次落笔" : `从 #${opts.closest} 一步跨到 #1`;
894
1027
  const footerTip = opts.canStartToday ? "ciyi.猜 山水 · 开启今日新题" : "明日零点换新题 · ciyi.排行榜";
@@ -900,8 +1033,8 @@ function renderWinCard(service, opts) {
900
1033
  [],
901
1034
  footerTip
902
1035
  );
903
- const footerFont = `${s(11.5)}px ${FONT_SANS}`;
904
- const footerTipWidth = textWidth(footerTip, s(11.5));
1036
+ const footerFont = `${s(TYPE.labelSmall.size)}px ${FONT_SANS}`;
1037
+ const footerTipWidth = textWidth(footerTip, s(TYPE.labelSmall.size));
905
1038
  textLeftFit(
906
1039
  ctx,
907
1040
  `${opts.username} 拿下今日一词 · ${quip}`,
@@ -941,10 +1074,10 @@ function renderRankCard(service, opts) {
941
1074
  if (!opts.entries.length) {
942
1075
  textCenter(
943
1076
  ctx,
944
- "榜上无名。",
1077
+ "排行榜还空着",
945
1078
  panelX + panelW / 2,
946
1079
  y + s(31),
947
- `${s(13.5)}px ${FONT_SANS}`,
1080
+ `${s(TYPE.bodyMedium.size)}px ${FONT_SANS}`,
948
1081
  C.onSurfaceVariant
949
1082
  );
950
1083
  textCenter(
@@ -952,7 +1085,7 @@ function renderRankCard(service, opts) {
952
1085
  "今日第一个猜中的人,会写在这里。",
953
1086
  panelX + panelW / 2,
954
1087
  y + s(61),
955
- `${s(13.5)}px ${FONT_SANS}`,
1088
+ `${s(TYPE.bodyMedium.size)}px ${FONT_SANS}`,
956
1089
  C.onSurfaceVariant
957
1090
  );
958
1091
  } else {
@@ -974,35 +1107,49 @@ function renderRankCard(service, opts) {
974
1107
  const px = contentLeft;
975
1108
  const py = midY - posSize / 2;
976
1109
  const podium = [MEDAL.gold, MEDAL.silver, MEDAL.bronze];
977
- const pc = i < 3 ? { bg: podium[i], fg: "#ffffff" } : { bg: C.surfaceContainerHighest, fg: C.onSurfaceVariant };
1110
+ const pc = i < 3 ? { bg: podium[i], fg: C.onPrimary } : { bg: C.surfaceContainerHighest, fg: C.onSurfaceVariant };
978
1111
  fillRound(ctx, px, py, posSize, posSize, s(SHAPE.full), pc.bg);
979
- textCenter(ctx, String(i + 1), px + posSize / 2, midY, `600 ${s(14)}px ${FONT_NUM}`, pc.fg);
1112
+ textCenter(
1113
+ ctx,
1114
+ String(i + 1),
1115
+ px + posSize / 2,
1116
+ midY,
1117
+ `${EMPHASIZED_WEIGHT.label} ${s(TYPE.labelLarge.size)}px ${FONT_NUM}`,
1118
+ pc.fg
1119
+ );
980
1120
  const name2 = e.username || "无名氏";
981
1121
  const nameX = px + posSize + s(14);
982
- const nameFont = `${s(15)}px ${FONT_SANS}`;
1122
+ const nameFont = `${s(TYPE.titleMedium.size)}px ${FONT_SANS}`;
983
1123
  const score = String(e.score);
984
- const unitFont = s(11.5);
1124
+ const unitFont = s(TYPE.labelSmall.size);
985
1125
  const unitW = textWidth("次", unitFont);
986
- const scoreFont = s(19);
1126
+ const scoreFont = s(TYPE.titleLarge.size);
987
1127
  const scoreRight = contentRight - unitW - s(8);
988
1128
  const scoreLeft = scoreRight - textWidth(score, scoreFont);
989
- const meWidth = e.me ? textWidth("我", s(10.5)) + s(18) : 0;
1129
+ const meWidth = e.me ? textWidth("我", s(TYPE.labelSmall.size)) + s(18) : 0;
990
1130
  const fittedName = ellipsize(
991
1131
  name2,
992
1132
  Math.max(0, scoreLeft - s(16) - nameX - meWidth),
993
- s(15)
1133
+ s(TYPE.titleMedium.size)
994
1134
  );
995
1135
  textLeft(ctx, fittedName, nameX, midY, nameFont, C.onSurface);
996
1136
  if (e.me) {
997
- const nw = textWidth(fittedName, s(15));
998
- textLeft(ctx, "我", nameX + nw + s(8), midY, `${s(10.5)}px ${FONT_SANS}`, C.primary);
1137
+ const nw = textWidth(fittedName, s(TYPE.titleMedium.size));
1138
+ textLeft(
1139
+ ctx,
1140
+ "我",
1141
+ nameX + nw + s(8),
1142
+ midY,
1143
+ `${s(TYPE.labelSmall.size)}px ${FONT_SANS}`,
1144
+ C.primary
1145
+ );
999
1146
  }
1000
1147
  textRight(
1001
1148
  ctx,
1002
1149
  score,
1003
1150
  scoreRight,
1004
1151
  midY,
1005
- `700 ${scoreFont}px ${FONT_NUM}`,
1152
+ `${EMPHASIZED_WEIGHT.title} ${scoreFont}px ${FONT_NUM}`,
1006
1153
  C.onSurface
1007
1154
  );
1008
1155
  textRight(ctx, "次", contentRight, midY, `${unitFont}px ${FONT_SANS}`, C.onSurfaceVariant);
@@ -1011,10 +1158,10 @@ function renderRankCard(service, opts) {
1011
1158
  if (opts.hidden > 0) {
1012
1159
  textLeft(
1013
1160
  ctx,
1014
- `⋯ 另有 ${opts.hidden} 人在榜`,
1161
+ `…… 另有 ${opts.hidden} 人在榜`,
1015
1162
  contentLeft,
1016
1163
  ry + s(16),
1017
- `${s(11.5)}px ${FONT_SANS}`,
1164
+ `${s(TYPE.labelSmall.size)}px ${FONT_SANS}`,
1018
1165
  C.onSurfaceVariant
1019
1166
  );
1020
1167
  }
@@ -1032,7 +1179,7 @@ function renderRankCard(service, opts) {
1032
1179
  "每猜中一日之词,记一次",
1033
1180
  panelX,
1034
1181
  iy + ih - s(30),
1035
- `${s(11.5)}px ${FONT_SANS}`,
1182
+ `${s(TYPE.labelSmall.size)}px ${FONT_SANS}`,
1036
1183
  C.onSurfaceVariant
1037
1184
  );
1038
1185
  });
@@ -1050,19 +1197,20 @@ var usage = `## 使用
1050
1197
  | 指令 | 说明 |
1051
1198
  | --- | --- |
1052
1199
  | \`ciyi\` | 玩法 |
1053
- | \`ciyi.猜 <词>\` | 开始今日游戏并提交猜测 |
1200
+ | \`ciyi.猜 <词>\` | 开始今日对局并报一个两字词 |
1054
1201
  | \`ciyi.裸词 [开/关]\` | 开关本频道的无前缀续猜 |
1055
1202
  | \`ciyi.排行榜\` | 累计猜中排行榜 |`;
1056
1203
  var inject = { required: ["database"], optional: ["canvas"] };
1057
1204
  var Config = import_koishi.Schema.object({
1058
1205
  atReply: import_koishi.Schema.boolean().default(false).description("回复时 @ 用户。"),
1059
1206
  quoteReply: import_koishi.Schema.boolean().default(false).description("回复时引用消息。"),
1060
- isEnableMiddleware: import_koishi.Schema.boolean().default(true).description(
1061
- "已开题时,直接发送两字词即可续猜,无需指令前缀。各频道可用「ciyi.裸词」临时切换。"
1207
+ enableDirectInput: import_koishi.Schema.boolean().default(true).description(
1208
+ "对局中直接发送两字词即可续猜,无需指令前缀。各频道可用「ciyi.裸词」临时切换。"
1062
1209
  ),
1063
1210
  renderImage: import_koishi.Schema.boolean().default(true).description("把猜测板渲染成图片,复用 Koishi Canvas 服务。服务不可用时自动回退为等价文本。"),
1064
1211
  maxHistory: import_koishi.Schema.number().default(10).min(0).description("猜测板最多列出的历史条数。最新一次猜测始终会列出。"),
1065
- maxRank: import_koishi.Schema.number().default(10).min(0).description("排行榜最多显示的人数。")
1212
+ maxRank: import_koishi.Schema.number().default(10).min(0).description("排行榜最多显示的人数。"),
1213
+ retractDelay: import_koishi.Schema.number().min(0).default(0).description("自动撤回延迟(秒),0 表示不撤回。")
1066
1214
  });
1067
1215
  function getMiddlewareGuess(session) {
1068
1216
  const message = session.event.message;
@@ -1128,9 +1276,9 @@ function apply(ctx, cfg) {
1128
1276
  );
1129
1277
  const logger = ctx.logger("ciyi");
1130
1278
  const random = new import_koishi.Random(() => Math.random());
1131
- const RULE = "────────────";
1279
+ const busy = /* @__PURE__ */ new Set();
1132
1280
  const middlewareOverrides = /* @__PURE__ */ new Map();
1133
- const middlewareOn = /* @__PURE__ */ __name((channelId) => channelId === void 0 ? cfg.isEnableMiddleware : middlewareOverrides.get(channelId) ?? cfg.isEnableMiddleware, "middlewareOn");
1281
+ const middlewareOn = /* @__PURE__ */ __name((channelId) => channelId === void 0 ? cfg.enableDirectInput : middlewareOverrides.get(channelId) ?? cfg.enableDirectInput, "middlewareOn");
1134
1282
  ctx.middleware(async (session, next) => {
1135
1283
  if (!session.channelId || !middlewareOn(session.channelId)) return await next();
1136
1284
  const guess = getMiddlewareGuess(session);
@@ -1152,7 +1300,7 @@ function apply(ctx, cfg) {
1152
1300
  ctx.command("ciyi", "词意 · 按意思远近找词").action(async ({ session }) => {
1153
1301
  return await wf(session);
1154
1302
  });
1155
- ctx.command("ciyi.猜 <guess:string>", "报一个两字词").usage("例:ciyi.猜 山水").action(async ({ session }, guess) => {
1303
+ ctx.command("ciyi.猜 <guess:string>", "开题并报一个两字词").usage("例:ciyi.猜 山水").action(async ({ session }, guess) => {
1156
1304
  return await c(session, guess?.trim());
1157
1305
  });
1158
1306
  ctx.command("ciyi.排行榜", "查看累计猜中排行榜").action(async ({ session }) => {
@@ -1160,7 +1308,7 @@ function apply(ctx, cfg) {
1160
1308
  });
1161
1309
  ctx.command("ciyi.裸词 [state:string]", "开关本频道的无前缀续猜").usage("例:ciyi.裸词(切换)· ciyi.裸词 开 · ciyi.裸词 关 · ciyi.裸词 状态").action(async ({ session }, state) => {
1162
1310
  const result = resolveMiddlewareSwitch(
1163
- cfg.isEnableMiddleware,
1311
+ cfg.enableDirectInput,
1164
1312
  session.channelId === void 0 ? void 0 : middlewareOverrides.get(session.channelId),
1165
1313
  state
1166
1314
  );
@@ -1235,7 +1383,7 @@ function apply(ctx, cfg) {
1235
1383
  __name(bestRank, "bestRank");
1236
1384
  function textCard(title, ...sections) {
1237
1385
  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");
1386
+ return [`▍${title}`, body].filter(Boolean).join("\n");
1239
1387
  }
1240
1388
  __name(textCard, "textCard");
1241
1389
  function neighborText(hint, side) {
@@ -1246,7 +1394,7 @@ function apply(ctx, cfg) {
1246
1394
  function boardText(rows, hidden, attempts) {
1247
1395
  const lines = [];
1248
1396
  rows.forEach((row, i) => {
1249
- if (row.gapBefore) lines.push(`  另有 ${row.gapBefore} 词未列 ⋯`);
1397
+ if (row.gapBefore) lines.push(` …… 另有 ${row.gapBefore} 词未列`);
1250
1398
  const { guess, rank, leftHint, rightHint } = row.history;
1251
1399
  const mark = row.fresh ? "▸" : " ";
1252
1400
  lines.push(
@@ -1259,7 +1407,7 @@ function apply(ctx, cfg) {
1259
1407
  `词意 · 第 ${attempts} 次猜测`,
1260
1408
  best === null ? null : `当前最佳 #${best} · ${tierOf(best).name}(${tierOf(best).note})`,
1261
1409
  lines,
1262
- hidden > 0 && !tail ? `  另有 ${hidden} 词未列 ⋯` : null,
1410
+ hidden > 0 && !tail ? ` …… 另有 ${hidden} 词未列` : null,
1263
1411
  "左邻更近答案,右邻更远;? 为隐去的字"
1264
1412
  );
1265
1413
  }
@@ -1267,12 +1415,16 @@ function apply(ctx, cfg) {
1267
1415
  function introText(channelOn) {
1268
1416
  return textCard(
1269
1417
  "词意 · 按意思远近找词",
1270
- `每天藏起一个两字词。你报词,我回它与答案的语义排名 —— 名次越小越近,#1 就是答案本身。`,
1418
+ [
1419
+ `每天藏起一个两字词。`,
1420
+ `报一个两字词,回它与答案的语义排名。`,
1421
+ `名次越小越近,#1 就是答案本身。`
1422
+ ],
1271
1423
  [
1272
1424
  `开始 ciyi.猜 山水  开题并报一个两字词`,
1273
1425
  channelOn ? `续猜 直接发送两字词 仅限已开题且未结束` : null,
1274
- `排行 ciyi.排行榜  看谁猜中得最多`,
1275
- `切换 ciyi.裸词 开/关  临时改本群续猜方式`
1426
+ `排行榜 ciyi.排行榜  看谁猜中得最多`,
1427
+ `切换 ciyi.裸词 开/关  临时改本频道续猜方式`
1276
1428
  ].filter(Boolean),
1277
1429
  [
1278
1430
  `读板 ?好)企业(地? #467 · 沾边`,
@@ -1284,27 +1436,28 @@ function apply(ctx, cfg) {
1284
1436
  `亲疏 咫尺 ≤10 毗邻 ≤50 相近 ≤200`,
1285
1437
  `   沾边 ≤1000 疏远 ≤5000 天涯 5000+`
1286
1438
  ],
1287
- `词库 ${allWords_default.length.toLocaleString()} 词,题库 ${questionList_default.length.toLocaleString()} 题,一日一词。`
1439
+ `词库 ${allWords_default.length.toLocaleString()} 词,一日一词。`
1288
1440
  );
1289
1441
  }
1290
1442
  __name(introText, "introText");
1291
1443
  function middlewareSwitchText(o) {
1292
1444
  const mark = "✅";
1293
1445
  const state = o.on ? "开启" : "停用";
1294
- const config = cfg.isEnableMiddleware ? "开启" : "停用";
1446
+ const config = cfg.enableDirectInput ? "开启" : "停用";
1295
1447
  if (!o.temporary) {
1296
- return `${mark} 裸词续猜 · ${o.reverted ? "已复原," : ""}跟随插件配置(${state})`;
1448
+ return `${mark} 裸词续猜 · 跟随插件配置
1449
+ 当前${state}。${o.reverted ? "本次已复原。" : ""}`;
1297
1450
  }
1298
1451
  const hint = o.on ? "开题后直接发送两字词即可续猜" : "续猜改用「ciyi.猜 山水」";
1299
1452
  return [
1300
- `${mark} 裸词续猜 · 本频道临时${state}(插件配置:${config})`,
1301
- `${hint};再次发送「ciyi.裸词」复原`
1453
+ `${mark} 裸词续猜 · 本频道临时${state}`,
1454
+ `${hint};插件配置为${config};再次发送「ciyi.裸词」复原`
1302
1455
  ].join("\n");
1303
1456
  }
1304
1457
  __name(middlewareSwitchText, "middlewareSwitchText");
1305
1458
  function winText(o) {
1306
1459
  return textCard(
1307
- "猜中了 · 今日已封题",
1460
+ "🏆 猜中了 · 今日已封题",
1308
1461
  `答案 ${o.answer}`,
1309
1462
  [
1310
1463
  `猜测 ${o.attempts} 次`,
@@ -1312,23 +1465,24 @@ function apply(ctx, cfg) {
1312
1465
  `累计 ${o.username} 已猜中 ${o.score} 次`,
1313
1466
  o.neighbors.length ? `近旁 ${o.neighbors.join(" · ")}` : null
1314
1467
  ].filter(Boolean),
1315
- o.canStartToday ? "继续 ciyi.猜 山水 · 开启今日新题" : "明日零点换新题 · ciyi.排行榜 看战绩"
1468
+ o.canStartToday ? "继续发送「ciyi.猜 山水」开启今日新题" : "明日零点换新题 · 发送「ciyi.排行榜」看战绩"
1316
1469
  );
1317
1470
  }
1318
1471
  __name(winText, "winText");
1319
1472
  function rankText(entries, hidden) {
1320
1473
  if (!entries.length) {
1321
1474
  return textCard(
1322
- "词意每日挑战排行榜",
1323
- "榜上无名。今日第一个猜中的人,名字会写在这里。"
1475
+ "📋 排行榜还空着",
1476
+ "第一个猜中的人,名字会写在这里。",
1477
+ "发送「ciyi.猜 山水」开一局。"
1324
1478
  );
1325
1479
  }
1326
1480
  return textCard(
1327
- "词意每日挑战排行榜",
1481
+ "📋 词意每日挑战排行榜",
1328
1482
  entries.map(
1329
1483
  (e, i) => `${String(i + 1).padStart(2, "0")}. ${e.username || "无名氏"} ${e.score} 次`
1330
1484
  ),
1331
- hidden > 0 ? `⋯ 另有 ${hidden} 人在榜 ⋯` : null
1485
+ hidden > 0 ? `…… 另有 ${hidden} 人在榜` : null
1332
1486
  );
1333
1487
  }
1334
1488
  __name(rankText, "rankText");
@@ -1343,16 +1497,33 @@ function apply(ctx, cfg) {
1343
1497
  }
1344
1498
  }
1345
1499
  __name(renderCard, "renderCard");
1500
+ const lastMessage = /* @__PURE__ */ new Map();
1501
+ async function send(session, content) {
1502
+ const ids = await session.send(content);
1503
+ const messageId = ids?.[0];
1504
+ if (!cfg.retractDelay || !messageId) return;
1505
+ const previous = lastMessage.get(session.channelId);
1506
+ if (previous) {
1507
+ const passed = Date.now() - previous.timestamp;
1508
+ if (passed < 118e3) {
1509
+ ctx.setTimeout(() => {
1510
+ session.bot.deleteMessage(session.channelId, previous.id).catch((error) => logger.debug("撤回消息 %s 失败:%s", previous.id, error.message));
1511
+ }, Math.max(0, cfg.retractDelay * 1e3 - passed));
1512
+ }
1513
+ }
1514
+ lastMessage.set(session.channelId, { id: messageId, timestamp: Date.now() });
1515
+ }
1516
+ __name(send, "send");
1346
1517
  async function sendCard(session, render, fallback) {
1347
1518
  const image = await renderCard(render);
1348
1519
  const prefix = [];
1349
1520
  if (cfg.quoteReply && session.messageId) prefix.push(import_koishi.h.quote(session.messageId));
1350
1521
  if (cfg.atReply) prefix.push(import_koishi.h.at(session.userId), (0, import_koishi.h)("p"));
1351
1522
  if (image) {
1352
- await session.send([...prefix, image]);
1523
+ await send(session, [...prefix, image]);
1353
1524
  return;
1354
1525
  }
1355
- await session.send([...prefix, ...import_koishi.h.normalize(fallback)]);
1526
+ await send(session, [...prefix, ...import_koishi.h.normalize(fallback)]);
1356
1527
  }
1357
1528
  __name(sendCard, "sendCard");
1358
1529
  async function wf(session) {
@@ -1429,6 +1600,18 @@ function apply(ctx, cfg) {
1429
1600
  return await sendMsg(session, `⚠️ 「${guess}」不在词库里
1430
1601
  换个常见些的词试试。`);
1431
1602
  }
1603
+ if (session.channelId && busy.has(session.channelId)) {
1604
+ return await sendMsg(session, "⏳ 上一手还在处理\n稍等再发一次。");
1605
+ }
1606
+ if (session.channelId) busy.add(session.channelId);
1607
+ try {
1608
+ return await guessOnce(session, guess);
1609
+ } finally {
1610
+ if (session.channelId) busy.delete(session.channelId);
1611
+ }
1612
+ }
1613
+ __name(c, "c");
1614
+ async function guessOnce(session, guess) {
1432
1615
  const gameInfo = await getTodayGame(session);
1433
1616
  if (!gameInfo) {
1434
1617
  return await sendMsg(
@@ -1439,7 +1622,7 @@ function apply(ctx, cfg) {
1439
1622
  if (gameInfo.isOver) {
1440
1623
  return await sendMsg(
1441
1624
  session,
1442
- `💡 今日挑战已结束,答案是「${gameInfo.answer}」。
1625
+ `💡 今日挑战已结束,答案是「${gameInfo.answer}
1443
1626
  明日零点换新题,发送「ciyi.排行榜」看战绩。`
1444
1627
  );
1445
1628
  }
@@ -1499,7 +1682,7 @@ function apply(ctx, cfg) {
1499
1682
  const entry = getHistory(guess, rankList);
1500
1683
  if (!entry) {
1501
1684
  logger.warn(`「${guess}」不在 ${gameInfo.answer} 的榜单中`);
1502
- return await sendMsg(session, `⚠️ 「${guess}」不在今日的榜单里
1685
+ return await sendMsg(session, `⚠️ 「${guess}」不在今日的榜里
1503
1686
  换个词试试。`);
1504
1687
  }
1505
1688
  const history = [...gameInfo.history, entry];
@@ -1524,7 +1707,7 @@ function apply(ctx, cfg) {
1524
1707
  boardText(rows, hidden, history.length)
1525
1708
  );
1526
1709
  }
1527
- __name(c, "c");
1710
+ __name(guessOnce, "guessOnce");
1528
1711
  async function fetchCiYi(word) {
1529
1712
  const url = `https://ci-ying.oss-cn-zhangjiakou.aliyuncs.com/v1/ci-yi-list/${word}.txt`;
1530
1713
  try {
@@ -1547,7 +1730,7 @@ function apply(ctx, cfg) {
1547
1730
  const prefix = [];
1548
1731
  if (cfg.quoteReply && session.messageId) prefix.push(import_koishi.h.quote(session.messageId));
1549
1732
  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)]);
1733
+ await send(session, [...prefix, ...import_koishi.h.normalize(msg)]);
1551
1734
  }
1552
1735
  __name(sendMsg, "sendMsg");
1553
1736
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-ciyi",
3
3
  "description": "Koishi 的词意猜词插件。",
4
- "version": "1.4.1",
4
+ "version": "2.1.0",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [