koishi-plugin-ciyi 1.2.6 → 1.2.8

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/lib/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { Context, Schema } from "koishi";
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\u8BBE\u7F6E\u6307\u4EE4\u522B\u540D\u540E\uFF0C\u53D1\u9001 `ciyi` \u67E5\u770B\u73A9\u6CD5\u3002\u6BCF\u65E5\u85CF\u4E00\u4E2A\u4E24\u5B57\u8BCD\uFF0C\u7B2C\u4E00\u6B21\u731C\u6D4B\u4F1A\u81EA\u52A8\u5F00\u9898\u3002\n\n## \u6307\u4EE4\n\n| \u6307\u4EE4 | \u8BF4\u660E |\n| --- | --- |\n| `ciyi` | \u73A9\u6CD5\u8BF4\u660E |\n| `ciyi.\u731C <\u8BCD>` | \u5F00\u59CB\u4ECA\u65E5\u6E38\u620F\u5E76\u63D0\u4EA4\u731C\u6D4B |\n| `ciyi.\u6392\u884C\u699C` | \u731C\u4E2D\u6B21\u6570\u699C |";
4
+ export declare const usage = "## \u4F7F\u7528\n\n\u8BBE\u7F6E\u6307\u4EE4\u522B\u540D\u540E\uFF0C\u53D1\u9001 `ciyi` \u67E5\u770B\u73A9\u6CD5\u3002\u6BCF\u65E5\u85CF\u4E00\u4E2A\u4E24\u5B57\u8BCD\uFF0C\u4F7F\u7528 `ciyi.\u731C <\u8BCD>` \u63D0\u4EA4\u7B2C\u4E00\u6B21\u731C\u6D4B\u5E76\u5F00\u9898\uFF1B\u5F00\u9898\u540E\u53EF\u76F4\u63A5\u53D1\u9001\u4E24\u5B57\u8BCD\u7EE7\u7EED\u731C\u6D4B\u3002\n\n## \u6307\u4EE4\n\n| \u6307\u4EE4 | \u8BF4\u660E |\n| --- | --- |\n| `ciyi` | \u73A9\u6CD5\u8BF4\u660E |\n| `ciyi.\u731C <\u8BCD>` | \u5F00\u59CB\u4ECA\u65E5\u6E38\u620F\u5E76\u63D0\u4EA4\u731C\u6D4B |\n| `ciyi.\u6392\u884C\u699C` | \u731C\u4E2D\u6B21\u6570\u699C |";
5
5
  export declare const inject: {
6
6
  required: string[];
7
7
  optional: string[];
@@ -39,4 +39,11 @@ export interface CiyiRank {
39
39
  score: number;
40
40
  }
41
41
  export type { History };
42
+ /**
43
+ * 裸词中间件只接受一条完整、无修饰的两字中文纯文本消息。
44
+ * 引用、@、图片、富文本以及机器人消息都可能只是普通聊天的一部分,不能据此猜词。
45
+ */
46
+ export declare function getMiddlewareGuess(session: Session): string | null;
47
+ /** 裸词只能加入已开始且未封题的游戏,不能开题,也不重复提交旧猜测。 */
48
+ export declare function canHandleMiddlewareGuess(game: Ciyi | null | undefined, guess: string): boolean;
42
49
  export declare function apply(ctx: Context, cfg: Config): void;
package/lib/index.js CHANGED
@@ -22,6 +22,8 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  Config: () => Config,
24
24
  apply: () => apply,
25
+ canHandleMiddlewareGuess: () => canHandleMiddlewareGuess,
26
+ getMiddlewareGuess: () => getMiddlewareGuess,
25
27
  inject: () => inject,
26
28
  name: () => name,
27
29
  usage: () => usage
@@ -578,7 +580,7 @@ function renderIntroCard(service, opts) {
578
580
  textLeft(ctx, "ciyi.猜 山水", ix + s(96), y + s(24), `${s(13.5)}px ${FONT_NUM}`, C.ink);
579
581
  textLeftFit(
580
582
  ctx,
581
- `开题并报一个两字词${opts.middleware ? ",也可以直接把词发出来" : ""}`,
583
+ `开题并报一个两字词${opts.middleware ? ";开题后可直接发词" : ""}`,
582
584
  ix + s(230),
583
585
  y + s(24),
584
586
  iw - s(268),
@@ -733,7 +735,9 @@ function renderWinCard(service, opts) {
733
735
  }
734
736
  __name(renderWinCard, "renderWinCard");
735
737
  function renderRankCard(service, opts) {
736
- const innerW = s(620);
738
+ const innerW = s(640);
739
+ const rankPadX = s(32);
740
+ const rankInnerPad = s(20);
737
741
  const headerH = s(87);
738
742
  const contentGap = s(20);
739
743
  const rowH = s(52);
@@ -744,17 +748,21 @@ function renderRankCard(service, opts) {
744
748
  const contentH = opts.entries.length ? opts.entries.length * rowH + hiddenH : emptyH;
745
749
  const innerH = headerH + contentGap + contentH + footerGap + footerH;
746
750
  return toPng(service, innerW, innerH, (ctx, ix, iy, iw, ih) => {
751
+ const panelX = ix + rankPadX;
752
+ const panelW = iw - rankPadX * 2;
753
+ const contentLeft = panelX + rankInnerPad;
754
+ const contentRight = panelX + panelW - rankInnerPad;
747
755
  let y = iy + drawHeader(ctx, ix, iy, iw, "每日挑战 · 累计猜中", {
748
756
  big: String(opts.players),
749
757
  cap: "上榜人数"
750
758
  });
751
759
  y += s(20);
760
+ drawPanel(ctx, panelX, y, panelW, contentH);
752
761
  if (!opts.entries.length) {
753
- drawPanel(ctx, ix + s(22), y, iw - s(44), emptyH);
754
762
  textCenter(
755
763
  ctx,
756
764
  "榜上无名。",
757
- ix + iw / 2,
765
+ panelX + panelW / 2,
758
766
  y + s(31),
759
767
  `${s(13.5)}px ${FONT_SERIF}`,
760
768
  C.ink3
@@ -762,7 +770,7 @@ function renderRankCard(service, opts) {
762
770
  textCenter(
763
771
  ctx,
764
772
  "今日第一个猜中的人,会写在这里。",
765
- ix + iw / 2,
773
+ panelX + panelW / 2,
766
774
  y + s(61),
767
775
  `${s(13.5)}px ${FONT_SERIF}`,
768
776
  C.ink3
@@ -774,17 +782,17 @@ function renderRankCard(service, opts) {
774
782
  const midY = ry + rowH / 2;
775
783
  if (e.me) {
776
784
  ctx.fillStyle = "rgba(178,58,46,0.055)";
777
- ctx.fillRect(ix + s(22), ry, iw - s(44), rowH);
785
+ ctx.fillRect(panelX, ry, panelW, rowH);
778
786
  }
779
787
  if (i > 0) {
780
788
  ctx.strokeStyle = C.rule;
781
789
  ctx.beginPath();
782
- ctx.moveTo(ix + s(22), ry);
783
- ctx.lineTo(ix + iw - s(22), ry);
790
+ ctx.moveTo(contentLeft, ry);
791
+ ctx.lineTo(contentRight, ry);
784
792
  ctx.stroke();
785
793
  }
786
794
  const posSize = s(30);
787
- const px = ix + s(26);
795
+ const px = contentLeft;
788
796
  const py = midY - posSize / 2;
789
797
  const posColors = [
790
798
  { bg: C.seal, fg: "#F7F1E5", border: C.seal },
@@ -801,12 +809,15 @@ function renderRankCard(service, opts) {
801
809
  const nameX = px + posSize + s(14);
802
810
  const nameFont = `${s(15)}px ${FONT_SERIF}`;
803
811
  const score = String(e.score);
804
- const scoreRight = ix + iw - s(38);
805
- const scoreLeft = scoreRight - textWidth(score, s(19));
812
+ const unitFont = s(11.5);
813
+ const unitW = textWidth("次", unitFont);
814
+ const scoreFont = s(19);
815
+ const scoreRight = contentRight - unitW - s(8);
816
+ const scoreLeft = scoreRight - textWidth(score, scoreFont);
806
817
  const meWidth = e.me ? textWidth("我", s(10.5)) + s(18) : 0;
807
818
  const fittedName = ellipsize(
808
819
  name2,
809
- Math.max(0, scoreLeft - s(18) - nameX - meWidth),
820
+ Math.max(0, scoreLeft - s(16) - nameX - meWidth),
810
821
  s(15)
811
822
  );
812
823
  textLeft(ctx, fittedName, nameX, midY, nameFont, C.ink);
@@ -819,17 +830,17 @@ function renderRankCard(service, opts) {
819
830
  score,
820
831
  scoreRight,
821
832
  midY,
822
- `700 ${s(19)}px ${FONT_NUM}`,
833
+ `700 ${scoreFont}px ${FONT_NUM}`,
823
834
  C.ink
824
835
  );
825
- textRight(ctx, "次", ix + iw - s(22), midY, `${s(11.5)}px ${FONT_SERIF}`, C.ink3);
836
+ textRight(ctx, "次", contentRight, midY, `${unitFont}px ${FONT_SERIF}`, C.ink3);
826
837
  ry += rowH;
827
838
  }
828
839
  if (opts.hidden > 0) {
829
840
  textLeft(
830
841
  ctx,
831
842
  `⋯ 另有 ${opts.hidden} 人在榜`,
832
- ix + s(26),
843
+ contentLeft,
833
844
  ry + s(16),
834
845
  `${s(11.5)}px ${FONT_SERIF}`,
835
846
  C.ink3
@@ -847,7 +858,7 @@ function renderRankCard(service, opts) {
847
858
  textLeft(
848
859
  ctx,
849
860
  "每猜中一日之词,记一次",
850
- ix + s(22),
861
+ panelX,
851
862
  iy + ih - s(30),
852
863
  `${s(11.5)}px ${FONT_SERIF}`,
853
864
  C.ink3
@@ -860,7 +871,7 @@ __name(renderRankCard, "renderRankCard");
860
871
  var name = "ciyi";
861
872
  var usage = `## 使用
862
873
 
863
- 设置指令别名后,发送 \`ciyi\` 查看玩法。每日藏一个两字词,第一次猜测会自动开题。
874
+ 设置指令别名后,发送 \`ciyi\` 查看玩法。每日藏一个两字词,使用 \`ciyi.猜 <词>\` 提交第一次猜测并开题;开题后可直接发送两字词继续猜测。
864
875
 
865
876
  ## 指令
866
877
 
@@ -873,11 +884,33 @@ var inject = { required: ["database"], optional: ["canvas"] };
873
884
  var Config = import_koishi.Schema.object({
874
885
  atReply: import_koishi.Schema.boolean().default(false).description("响应时 @"),
875
886
  quoteReply: import_koishi.Schema.boolean().default(false).description("响应时引用"),
876
- isEnableMiddleware: import_koishi.Schema.boolean().default(true).description("是否启用中间件(若启用,猜测词语时可以不使用指令直接猜测)"),
887
+ isEnableMiddleware: import_koishi.Schema.boolean().default(true).description("是否启用中间件(若启用,已开题时可以不使用指令直接猜测)"),
877
888
  renderImage: import_koishi.Schema.boolean().default(true).description("渲染图片(复用 Koishi Canvas 服务;不可用时使用等价文本)"),
878
889
  maxHistory: import_koishi.Schema.number().default(10).min(0).description("猜测板最多列出的历史条数(最新一次猜测始终会列出)"),
879
890
  maxRank: import_koishi.Schema.number().default(10).min(0).description("最大排行榜人数")
880
891
  });
892
+ function getMiddlewareGuess(session) {
893
+ const message = session.event.message;
894
+ if (!message || message.quote || session.event.user?.isBot || message.user?.isBot) {
895
+ return null;
896
+ }
897
+ const elements = message.elements ?? [];
898
+ if (elements.length !== 1 || elements[0].type !== "text") {
899
+ return null;
900
+ }
901
+ const text = elements[0].attrs.content;
902
+ if (typeof text !== "string" || text !== text.trim() || Array.from(text).length !== 2 || !/^\p{Script=Han}{2}$/u.test(text) || !allWords_default.includes(text)) {
903
+ return null;
904
+ }
905
+ return text;
906
+ }
907
+ __name(getMiddlewareGuess, "getMiddlewareGuess");
908
+ function canHandleMiddlewareGuess(game, guess) {
909
+ if (!game || game.isOver) return false;
910
+ if (game.guessedHistoryInOneGame?.includes(guess)) return false;
911
+ return guess === game.answer || game.rankList?.includes(guess) === true;
912
+ }
913
+ __name(canHandleMiddlewareGuess, "canHandleMiddlewareGuess");
881
914
  function apply(ctx, cfg) {
882
915
  ctx.model.extend(
883
916
  "ciyi",
@@ -909,14 +942,21 @@ function apply(ctx, cfg) {
909
942
  const RULE = "────────────";
910
943
  if (cfg.isEnableMiddleware) {
911
944
  ctx.middleware(async (session, next) => {
912
- const text = `${import_koishi.h.select(session.event.message.elements, "text")}`;
913
- if (text.length !== 2) {
914
- return await next();
915
- }
916
- if (!allWords_default.includes(text)) {
945
+ const guess = getMiddlewareGuess(session);
946
+ if (!guess || !session.channelId) return await next();
947
+ try {
948
+ const [game] = await ctx.database.get("ciyi", {
949
+ channelId: session.channelId
950
+ });
951
+ if (!canHandleMiddlewareGuess(game, guess)) return await next();
952
+ } catch (error) {
953
+ logger.warn(
954
+ "中间件读取游戏状态失败,本次消息不作猜测:%s",
955
+ error?.message ?? error
956
+ );
917
957
  return await next();
918
958
  }
919
- return await session.execute(`ciyi.猜 ${text}`);
959
+ return await session.execute(`ciyi.猜 ${guess}`);
920
960
  });
921
961
  }
922
962
  ctx.command("ciyi", "词意(猜词游戏)").action(async ({ session }) => {
@@ -1018,9 +1058,10 @@ function apply(ctx, cfg) {
1018
1058
  "词意 · 按意思远近找词",
1019
1059
  `每天藏起一个两字词。你报词,我回它与答案的语义排名 —— 名次越小越近,#1 就是答案本身。`,
1020
1060
  [
1021
- `开始 ciyi.猜 山水  开题并报一个两字词${cfg.isEnableMiddleware ? "(也可直接发词)" : ""}`,
1061
+ `开始 ciyi.猜 山水  开题并报一个两字词`,
1062
+ cfg.isEnableMiddleware ? `续猜 直接发送两字词 仅限已开题且未结束` : null,
1022
1063
  `排行 ciyi.排行榜  看谁猜中得最多`
1023
- ],
1064
+ ].filter(Boolean),
1024
1065
  [
1025
1066
  `读板 ?好)企业(地? #467 · 沾边`,
1026
1067
  `   #467 是「企业」与答案的相近名次`,
@@ -1286,6 +1327,8 @@ __name(apply, "apply");
1286
1327
  0 && (module.exports = {
1287
1328
  Config,
1288
1329
  apply,
1330
+ canHandleMiddlewareGuess,
1331
+ getMiddlewareGuess,
1289
1332
  inject,
1290
1333
  name,
1291
1334
  usage
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-ciyi",
3
- "description": "Koishi 的词意(猜词游戏)插件。根据词语的含义相似程度,猜测正确的词语。",
4
- "version": "1.2.6",
3
+ "description": "Koishi 的词意猜词插件。",
4
+ "version": "1.2.8",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [
@@ -41,7 +41,12 @@
41
41
  "词意"
42
42
  ],
43
43
  "devDependencies": {
44
- "@koishijs/canvas": "^0.2.0"
44
+ "@koishijs/canvas": "^0.2.0",
45
+ "koishi": "^4.18.11",
46
+ "tsx": "^4.23.13"
47
+ },
48
+ "scripts": {
49
+ "test": "tsx --test test/*.test.ts"
45
50
  },
46
51
  "peerDependencies": {
47
52
  "koishi": "^4.18.11"
package/readme.md CHANGED
@@ -8,7 +8,7 @@ Koishi 的词意猜词插件。
8
8
 
9
9
  ## 使用
10
10
 
11
- 设置指令别名后,发送 `ciyi` 查看玩法。每日藏一个两字词,第一次猜测会自动开题。
11
+ 设置指令别名后,发送 `ciyi` 查看玩法。每日藏一个两字词,使用 `ciyi.猜 <词>` 提交第一次猜测并开题;开题后可直接发送两字词继续猜测。
12
12
 
13
13
  ## 指令
14
14