koishi-plugin-mcdle 0.1.4 → 0.1.5

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 { MobData, BlockData, ItemData } from './data';
3
3
  export declare const name = "mcdle";
4
- export declare const usage = "## \u4F7F\u7528\n\n`mcdle.\u731C [\u540D\u79F0]` \u5F00\u5C40\u6216\u731C\u6D4B\u3002\u7CFB\u7EDF\u968F\u673A\u9009\u62E9\u751F\u7269\u3001\u7269\u54C1\u6216\u65B9\u5757\uFF0C\u6309\u5C5E\u6027\u989C\u8272\u63D0\u793A\u63A5\u8FD1\u7A0B\u5EA6\u3002\n\n## \u63D0\u793A\n\n| \u7B26\u53F7 | \u542B\u4E49 |\n| --- | --- |\n| \uD83D\uDFE9 | \u5B8C\u5168\u5339\u914D |\n| \uD83D\uDFE8 | \u90E8\u5206\u5339\u914D |\n| \uD83D\uDFE5 | \u4E0D\u5339\u914D |\n| \uD83D\uDFE5\u2B06\uFE0F / \uD83D\uDFE5\u2B07\uFE0F | \u7B54\u6848\u66F4\u5927 / \u66F4\u5C0F |\n\n## \u6307\u4EE4\n\n| \u6307\u4EE4 | \u8BF4\u660E |\n| --- | --- |\n| `mcdle` | \u67E5\u770B\u5E2E\u52A9 |\n| `mcdle.\u731C [\u540D\u79F0]` | \u5F00\u5C40\u6216\u731C\u6D4B |\n| `mcdle.\u5E2E\u52A9` | \u5B8C\u6574\u8BF4\u660E |\n| `mcdle.\u6392\u884C\u699C` | \u7FA4\u5185\u6218\u7EE9 |\n| `mcdle.\u8BCD\u5E93` | \u5168\u90E8\u8BCD\u6761 |";
4
+ export declare const usage = "## \u4F7F\u7528\n\n`mcdle.\u731C [\u540D\u79F0]` \u4F7F\u7528\u8BCD\u5E93\u4E2D\u7684\u6709\u6548\u540D\u79F0\u5F00\u5C40\u6216\u731C\u6D4B\u3002\u9996\u4E2A\u8BCD\u6761\u51B3\u5B9A\u751F\u7269\u3001\u7269\u54C1\u6216\u65B9\u5757\u6A21\u5F0F\uFF0C\u7CFB\u7EDF\u518D\u4ECE\u540C\u7C7B\u8BCD\u5E93\u4E2D\u62BD\u53D6\u7B54\u6848\u3002\n\n## \u63D0\u793A\n\n| \u7B26\u53F7 | \u542B\u4E49 |\n| --- | --- |\n| \uD83D\uDFE9 | \u5B8C\u5168\u5339\u914D |\n| \uD83D\uDFE8 | \u90E8\u5206\u5339\u914D |\n| \uD83D\uDFE5 | \u4E0D\u5339\u914D |\n| \uD83D\uDFE5\u2B06\uFE0F / \uD83D\uDFE5\u2B07\uFE0F | \u7B54\u6848\u66F4\u5927 / \u66F4\u5C0F |\n\n## \u6307\u4EE4\n\n| \u6307\u4EE4 | \u8BF4\u660E |\n| --- | --- |\n| `mcdle` | \u67E5\u770B\u5E2E\u52A9 |\n| `mcdle.\u731C [\u540D\u79F0]` | \u7528\u6709\u6548\u8BCD\u6761\u5F00\u5C40\u6216\u731C\u6D4B |\n| `mcdle.\u88F8\u731C [\u5F00/\u5173]` | \u4E34\u65F6\u5207\u6362\u672C\u7FA4\u65E0\u524D\u7F00\u7EED\u731C |\n| `mcdle.\u5E2E\u52A9` | \u5B8C\u6574\u8BF4\u660E |\n| `mcdle.\u6392\u884C\u699C` | \u7FA4\u5185\u6218\u7EE9 |\n| `mcdle.\u8BCD\u5E93` | \u5168\u90E8\u8BCD\u6761 |";
5
5
  export declare const inject: {
6
6
  required: string[];
7
7
  optional: string[];
@@ -24,7 +24,7 @@ declare module "koishi" {
24
24
  mcdle_rank: McdleRank;
25
25
  }
26
26
  }
27
- interface Mcdle {
27
+ export interface Mcdle {
28
28
  id: number;
29
29
  channelId: string;
30
30
  answer: MobData | ItemData | BlockData | null;
@@ -46,5 +46,25 @@ interface McdleRank {
46
46
  blockSuccessCount: number;
47
47
  totalSuccessCount: number;
48
48
  }
49
+ /**
50
+ * 裸猜只接受一条完整、无修饰、且与 MCDLE 词库精确匹配的纯文本消息。
51
+ * 引用、@、图片、富文本及机器人消息都可能只是普通聊天的一部分,不能据此猜测。
52
+ */
53
+ export declare function getMiddlewareGuess(session: Session): string | null;
54
+ /** 裸猜只能加入当前模式下已开始的有效局面,绝不能代替完整指令开新局。 */
55
+ export declare function canHandleMiddlewareGuess(game: Mcdle | null | undefined, guess: string, allowRepeatedGuesses?: boolean): boolean;
56
+ export type MiddlewareSwitch = {
57
+ error: string;
58
+ } | {
59
+ /** 写回本群覆盖表的新值;undefined 表示清除覆盖,重新跟随插件配置。 */
60
+ override: boolean | undefined;
61
+ on: boolean;
62
+ reverted: boolean;
63
+ };
64
+ /**
65
+ * 无参调用在「配置默认」与「本群临时相反」间往返;显式开/关若与配置相同,
66
+ * 就清除冗余覆盖。临时状态只保存在内存中,插件重载后自然回到配置默认。
67
+ */
68
+ export declare function resolveMiddlewareSwitch(config: boolean, override: boolean | undefined, action: string | null | undefined): MiddlewareSwitch;
49
69
  export declare function apply(ctx: Context, cfg: Config): void;
50
70
  export {};
package/lib/index.js CHANGED
@@ -22,8 +22,11 @@ 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,
29
+ resolveMiddlewareSwitch: () => resolveMiddlewareSwitch,
27
30
  usage: () => usage
28
31
  });
29
32
  module.exports = __toCommonJS(index_exports);
@@ -15395,7 +15398,7 @@ function boardCard(opts) {
15395
15398
  return shell(body, { accent: meta.accent });
15396
15399
  }
15397
15400
  __name(boardCard, "boardCard");
15398
- function startCard(mode, played, limit) {
15401
+ function startCard(mode, played, limit, middleware) {
15399
15402
  const meta = MODES[mode];
15400
15403
  const fields = FIELDS[mode];
15401
15404
  const attrs = fields.map((k) => `<div class="attr">${px(fieldIcon(k), 13)}<span>${esc(keyMap[k] || k)}</span></div>`).join("");
@@ -15415,7 +15418,7 @@ function startCard(mode, played, limit) {
15415
15418
  <div class="sec">
15416
15419
  <div class="sec-t"><span>下 一 步</span></div>
15417
15420
  <div class="panel"><div class="rows">
15418
- <div class="row"><span class="cmd">mcdle.猜 [名称]</span><span class="m">提交一次猜测</span></div>
15421
+ <div class="row"><span class="cmd">${middleware ? `直接发送${esc(meta.name)}名称` : "mcdle.猜 [名称]"}</span><span class="m">提交后续猜测</span></div>
15419
15422
  <div class="row"><span class="cmd">mcdle.词库</span><span class="m">忘了有哪些词条时翻一翻</span></div>
15420
15423
  </div></div>
15421
15424
  </div>
@@ -15466,7 +15469,7 @@ function winCard(o) {
15466
15469
  return shell(body, { accent: meta.accent, width: 720 });
15467
15470
  }
15468
15471
  __name(winCard, "winCard");
15469
- function helpCard(dailyLimit, allowRepeat) {
15472
+ function helpCard(dailyLimit, allowRepeat, middleware) {
15470
15473
  const modeCards = ["mob", "item", "block"].map((m) => {
15471
15474
  const meta = MODES[m];
15472
15475
  return `<div class="mode-card" style="--accent:${meta.accent}">
@@ -15496,6 +15499,7 @@ function helpCard(dailyLimit, allowRepeat) {
15496
15499
  <div class="panel"><div class="rows">
15497
15500
  <div class="row"><span class="cmd">mcdle.猜 [名称]</span><span class="m">开始一局,或提交猜测</span></div>
15498
15501
  <div class="row"><span class="cmd">mcdle.猜</span><span class="m">局中直接使用可回看当前棋盘</span></div>
15502
+ <div class="row"><span class="cmd">mcdle.裸猜 [开/关]</span><span class="m">临时切换本群无前缀续猜</span></div>
15499
15503
  <div class="row"><span class="cmd">mcdle.排行榜</span><span class="m">查看群内战绩</span></div>
15500
15504
  <div class="row"><span class="cmd">mcdle.词库</span><span class="m">查阅全部候选词条</span></div>
15501
15505
  </div></div>
@@ -15505,6 +15509,8 @@ function helpCard(dailyLimit, allowRepeat) {
15505
15509
  <div class="panel"><div class="rows">
15506
15510
  <div class="row"><span class="k">·</span><span>每个群每日可开始 <b>${dailyLimit}</b> 局,跨零点重置。</span></div>
15507
15511
  <div class="row"><span class="k">·</span><span>${allowRepeat ? "允许重复提交已经猜过的词条。" : "同一局内不能重复提交已猜过的词条。"}</span></div>
15512
+ <div class="row"><span class="k">·</span><span>${middleware ? "本群裸猜已开启:只接受当前模式词库中的完整纯文本词条。" : "本群裸猜已停用:续猜请使用完整指令。"}</span></div>
15513
+ <div class="row"><span class="k">·</span><span>空指令与词库外名称不会开启新局或消耗额度。</span></div>
15508
15514
  <div class="row"><span class="k">·</span><span>词条与数据来自 Minecraft Wiki,版本号按发布先后比较。</span></div>
15509
15515
  </div></div>
15510
15516
  </div>
@@ -15512,7 +15518,7 @@ function helpCard(dailyLimit, allowRepeat) {
15512
15518
  return shell(body, { accent: "#8ec96a", width: 760 });
15513
15519
  }
15514
15520
  __name(helpCard, "helpCard");
15515
- function introCard(dailyLimit) {
15521
+ function introCard(dailyLimit, middleware) {
15516
15522
  const modeCards = ["mob", "item", "block"].map((m) => {
15517
15523
  const meta = MODES[m];
15518
15524
  return `<div class="mode-card" style="--accent:${meta.accent}">
@@ -15528,8 +15534,8 @@ function introCard(dailyLimit) {
15528
15534
  <div class="sec">
15529
15535
  <div class="sec-t"><span>三 步 上 手</span></div>
15530
15536
  <div class="panel"><div class="rows">
15531
- <div class="row"><span class="k">壹</span><span class="cmd">mcdle.猜 苦力怕</span><span class="m">随便报一个名字即可开局</span></div>
15532
- <div class="row"><span class="k">贰</span><span>对照颜色与箭头缩小范围,每一次猜测都会留在棋盘上。</span></div>
15537
+ <div class="row"><span class="k">壹</span><span class="cmd">mcdle.猜 苦力怕</span><span class="m">用一个有效词条开局</span></div>
15538
+ <div class="row"><span class="k">贰</span><span>${middleware ? "开局后可直接发送当前模式的完整词条名称继续猜测。" : "对照颜色与箭头缩小范围,每一次猜测都会留在棋盘上。"}</span></div>
15533
15539
  <div class="row"><span class="k">叁</span><span>锁定答案,战绩自动记入 <span class="cmd">mcdle.排行榜</span>。</span></div>
15534
15540
  </div></div>
15535
15541
  </div>
@@ -15537,6 +15543,7 @@ function introCard(dailyLimit) {
15537
15543
  <div class="sec-t"><span>今 日 额 度</span></div>
15538
15544
  <div class="panel"><div class="rows">
15539
15545
  <div class="row"><span class="k">·</span><span>每个群每日 <b>${dailyLimit}</b> 局,跨零点重置;详细规则见 <span class="cmd">mcdle.帮助</span>。</span></div>
15546
+ <div class="row"><span class="k">·</span><span><span class="cmd">mcdle.裸猜 开/关</span> 可临时调整本群续猜方式。</span></div>
15540
15547
  </div></div>
15541
15548
  </div>
15542
15549
  </div>` + noteRow("灵感来自 Wordle 与 mcdle.net");
@@ -15546,7 +15553,7 @@ __name(introCard, "introCard");
15546
15553
  function rankCard(entries, shown) {
15547
15554
  if (!entries.length) {
15548
15555
  const body2 = header(null, "排行榜", { big: "0", cap: "上榜人数" }) + `<div class="pad"><div class="empty">${px("trophy", 44)}
15549
- 榜单还空着<br><span style="font-size:12px">用 <span class="cmd">mcdle.猜</span> 开出第一局,成为榜首</span></div></div>`;
15556
+ 榜单还空着<br><span style="font-size:12px">用 <span class="cmd">mcdle.猜 苦力怕</span> 开出第一局,成为榜首</span></div></div>`;
15550
15557
  return shell(body2, { accent: "#e8b53c", width: 560 });
15551
15558
  }
15552
15559
  const max = Math.max(...entries.map((e) => e.total), 1);
@@ -15574,7 +15581,7 @@ __name(rankCard, "rankCard");
15574
15581
  var name = "mcdle";
15575
15582
  var usage = `## 使用
15576
15583
 
15577
- \`mcdle.猜 [名称]\` 开局或猜测。系统随机选择生物、物品或方块,按属性颜色提示接近程度。
15584
+ \`mcdle.猜 [名称]\` 使用词库中的有效名称开局或猜测。首个词条决定生物、物品或方块模式,系统再从同类词库中抽取答案。
15578
15585
 
15579
15586
  ## 提示
15580
15587
 
@@ -15590,15 +15597,16 @@ var usage = `## 使用
15590
15597
  | 指令 | 说明 |
15591
15598
  | --- | --- |
15592
15599
  | \`mcdle\` | 查看帮助 |
15593
- | \`mcdle.猜 [名称]\` | 开局或猜测 |
15600
+ | \`mcdle.猜 [名称]\` | 用有效词条开局或猜测 |
15601
+ | \`mcdle.裸猜 [开/关]\` | 临时切换本群无前缀续猜 |
15594
15602
  | \`mcdle.帮助\` | 完整说明 |
15595
15603
  | \`mcdle.排行榜\` | 群内战绩 |
15596
15604
  | \`mcdle.词库\` | 全部词条 |`;
15597
15605
  var inject = { required: ["database"], optional: ["puppeteer"] };
15598
15606
  var Config = import_koishi.Schema.object({
15599
15607
  atReply: import_koishi.Schema.boolean().default(false).description("响应时@用户"),
15600
- quoteReply: import_koishi.Schema.boolean().default(true).description("响应时引用消息"),
15601
- isEnableMiddleware: import_koishi.Schema.boolean().default(false).description("启用中间件(无需指令直接猜测)"),
15608
+ quoteReply: import_koishi.Schema.boolean().default(false).description("响应时引用消息"),
15609
+ isEnableMiddleware: import_koishi.Schema.boolean().default(false).description("是否默认启用无前缀续猜(仅在游戏进行中生效;mcdle.裸猜 可在单个群内临时切换)"),
15602
15610
  addStatusTextAfterEmoji: import_koishi.Schema.boolean().default(true).description("在状态表情后添加文字说明"),
15603
15611
  maxRank: import_koishi.Schema.number().default(10).min(0).description("排行榜最大显示人数"),
15604
15612
  dailyPlayLimit: import_koishi.Schema.number().default(1).min(1).description("每日游玩次数上限"),
@@ -15626,6 +15634,56 @@ var guessesField = {
15626
15634
  }
15627
15635
  }
15628
15636
  };
15637
+ var DATA_BY_MODE = {
15638
+ mob: mobData,
15639
+ item: itemData,
15640
+ block: blockData
15641
+ };
15642
+ var allChineseTitles = new Set(
15643
+ Object.values(DATA_BY_MODE).flatMap(
15644
+ (entries) => entries.map((entry) => entry.chinese_title)
15645
+ )
15646
+ );
15647
+ function getMiddlewareGuess(session) {
15648
+ const message = session.event.message;
15649
+ if (!message || message.quote || session.event.user?.isBot || message.user?.isBot) {
15650
+ return null;
15651
+ }
15652
+ const elements = message.elements ?? [];
15653
+ if (elements.length !== 1 || elements[0].type !== "text") return null;
15654
+ const text = elements[0].attrs.content;
15655
+ if (typeof text !== "string" || text !== text.trim() || !text || !allChineseTitles.has(text)) {
15656
+ return null;
15657
+ }
15658
+ return text;
15659
+ }
15660
+ __name(getMiddlewareGuess, "getMiddlewareGuess");
15661
+ function canHandleMiddlewareGuess(game, guess, allowRepeatedGuesses = false) {
15662
+ if (!game || game.gameEnded || !game.answer) return false;
15663
+ const mode = game.gameMode;
15664
+ if (!mode || !DATA_BY_MODE[mode]) return false;
15665
+ if (!allowRepeatedGuesses && game.guessedChineseTitles?.includes(guess)) return false;
15666
+ return DATA_BY_MODE[mode].some((entry) => entry.chinese_title === guess);
15667
+ }
15668
+ __name(canHandleMiddlewareGuess, "canHandleMiddlewareGuess");
15669
+ function resolveMiddlewareSwitch(config, override, action) {
15670
+ const arg = action?.trim();
15671
+ if (!arg) {
15672
+ return override === void 0 ? { override: !config, on: !config, reverted: false } : { override: void 0, on: config, reverted: true };
15673
+ }
15674
+ if (arg === "状态") {
15675
+ return { override, on: override ?? config, reverted: false };
15676
+ }
15677
+ const on = arg === "开" || arg === "开启";
15678
+ if (!on && arg !== "关" && arg !== "关闭") {
15679
+ return { error: "裸猜开关只认 开 / 关 / 状态" };
15680
+ }
15681
+ if (on === config) {
15682
+ return { override: void 0, on: config, reverted: override !== void 0 };
15683
+ }
15684
+ return { override: on, on, reverted: false };
15685
+ }
15686
+ __name(resolveMiddlewareSwitch, "resolveMiddlewareSwitch");
15629
15687
  function apply(ctx, cfg) {
15630
15688
  ctx.model.extend(
15631
15689
  "mcdle",
@@ -15661,41 +15719,49 @@ function apply(ctx, cfg) {
15661
15719
  }
15662
15720
  );
15663
15721
  const logger = ctx.logger("mcdle");
15664
- if (cfg.isEnableMiddleware) {
15665
- ctx.middleware(async (session, next) => {
15666
- let gameRecords = await ctx.database.get("mcdle", { channelId: session.channelId });
15667
- if (!gameRecords || gameRecords.length === 0) {
15668
- return next();
15669
- }
15670
- const game = gameRecords[0];
15671
- if (game.gameEnded) {
15672
- return next();
15673
- }
15674
- const content = session.content.trim();
15675
- if (content.length > 10 || /[,。!?;:'"“”‘’【】()《》.,!?;:'"\[\]()<>]/.test(content)) return next();
15676
- if (!cfg.allowRepeatedGuesses && game.guessedChineseTitles.includes(content)) {
15677
- return next();
15678
- }
15679
- let dataSource = [];
15680
- if (game.gameMode === "mob") dataSource = mobData;
15681
- else if (game.gameMode === "item") dataSource = itemData;
15682
- else if (game.gameMode === "block") dataSource = blockData;
15683
- else return next();
15684
- const matched = dataSource.find(
15685
- (d) => d.chinese_title === content
15722
+ const middlewareOverrides = /* @__PURE__ */ new Map();
15723
+ const middlewareOn = /* @__PURE__ */ __name((channelId) => channelId === void 0 ? cfg.isEnableMiddleware : middlewareOverrides.get(channelId) ?? cfg.isEnableMiddleware, "middlewareOn");
15724
+ ctx.middleware(async (session, next) => {
15725
+ if (!session.channelId || !middlewareOn(session.channelId)) return await next();
15726
+ const guess = getMiddlewareGuess(session);
15727
+ if (!guess) return await next();
15728
+ try {
15729
+ const [game] = await ctx.database.get("mcdle", { channelId: session.channelId });
15730
+ if (!canHandleMiddlewareGuess(game, guess, cfg.allowRepeatedGuesses)) return await next();
15731
+ const handled = await c(session, guess, false);
15732
+ return handled ? void 0 : await next();
15733
+ } catch (error) {
15734
+ logger.warn(
15735
+ "中间件读取或处理游戏状态失败,本次消息不作猜测:%s",
15736
+ error instanceof Error ? error.message : String(error)
15686
15737
  );
15687
- if (!matched) {
15688
- return next();
15689
- }
15690
- await session.execute(`mcdle.猜 ${content}`);
15691
- return;
15692
- });
15693
- }
15738
+ return await next();
15739
+ }
15740
+ });
15694
15741
  ctx.command("mcdle", "我的世界猜谜游戏").action(async ({ session }) => mcdle(session));
15695
- ctx.command("mcdle.猜 [guess:string]").action(async ({ session }, guess) => c(session, guess?.trim()));
15742
+ ctx.command("mcdle.猜 [guess:string]").action(async ({ session }, guess) => {
15743
+ await c(session, guess?.trim());
15744
+ });
15696
15745
  ctx.command("mcdle.帮助").action(async ({ session }) => bz(session));
15697
15746
  ctx.command("mcdle.排行榜").action(async ({ session }) => phb(session));
15698
15747
  ctx.command("mcdle.词库").action(async ({ session }) => ck(session));
15748
+ ctx.command("mcdle.裸猜 [state:string]", "临时开关本群的无前缀续猜").usage("例:mcdle.裸猜(切换)· mcdle.裸猜 开 · mcdle.裸猜 关 · mcdle.裸猜 状态").action(async ({ session }, state) => {
15749
+ const result = resolveMiddlewareSwitch(
15750
+ cfg.isEnableMiddleware,
15751
+ session.channelId === void 0 ? void 0 : middlewareOverrides.get(session.channelId),
15752
+ state
15753
+ );
15754
+ if ("error" in result) {
15755
+ await sendMsg(session, `⚠️ ${result.error}。例:mcdle.裸猜 开`);
15756
+ return;
15757
+ }
15758
+ if (result.override === void 0) middlewareOverrides.delete(session.channelId);
15759
+ else middlewareOverrides.set(session.channelId, result.override);
15760
+ await sendMsg(
15761
+ session,
15762
+ middlewareSwitchText(result.on, result.override !== void 0, result.reverted)
15763
+ );
15764
+ });
15699
15765
  const RULE = "────────────";
15700
15766
  function textCard(title, ...sections) {
15701
15767
  const body = sections.map((s) => Array.isArray(s) ? s.filter(Boolean).join("\n") : s).filter((s) => !!s && !!s.trim()).join("\n\n");
@@ -15755,13 +15821,16 @@ function apply(ctx, cfg) {
15755
15821
  );
15756
15822
  }
15757
15823
  __name(boardText, "boardText");
15758
- function startText(mode, played) {
15824
+ function startText(mode, played, channelOn) {
15759
15825
  const meta = MODES[mode];
15760
15826
  return textCard(
15761
15827
  `新的一局 · ${meta.name}模式`,
15762
15828
  `${meta.tagline},从 ${meta.total} 个候选中锁定唯一答案。`,
15763
15829
  `可推理属性:${FIELDS[mode].map((k) => keyMap[k] || k).join(" / ")}`,
15764
- [`mcdle.猜 [名称] 提交一次猜测`, `今日进度 ${played}/${cfg.dailyPlayLimit}`]
15830
+ [
15831
+ channelOn ? `直接发送词库中的${meta.name}名称,或使用 mcdle.猜 [名称]` : `mcdle.猜 [名称] 提交一次猜测`,
15832
+ `今日进度 ${played}/${cfg.dailyPlayLimit}`
15833
+ ]
15765
15834
  );
15766
15835
  }
15767
15836
  __name(startText, "startText");
@@ -15818,27 +15887,47 @@ function apply(ctx, cfg) {
15818
15887
  }
15819
15888
  __name(sendCard, "sendCard");
15820
15889
  async function mcdle(session) {
15821
- await sendCard(session, introCard(cfg.dailyPlayLimit), introText());
15890
+ const channelOn = middlewareOn(session.channelId);
15891
+ await sendCard(
15892
+ session,
15893
+ introCard(cfg.dailyPlayLimit, channelOn),
15894
+ introText(channelOn)
15895
+ );
15822
15896
  }
15823
15897
  __name(mcdle, "mcdle");
15824
- function introText() {
15898
+ function introText(channelOn) {
15825
15899
  const total = mobData.length + itemData.length + blockData.length;
15826
15900
  return textCard(
15827
15901
  "MCDLE · 我的世界猜谜",
15828
15902
  `从 ${total} 个词条里,只凭属性提示锁定唯一答案。`,
15829
15903
  [
15830
- `壹 mcdle.猜 苦力怕 随便报个名字即可开局`,
15831
- `贰 对照颜色与箭头缩小范围,每次猜测都会留在棋盘上`,
15904
+ `壹 mcdle.猜 苦力怕 用一个有效词条开局`,
15905
+ channelOn ? `贰 开局后可直接发送当前模式的词条名称继续猜测` : `贰 对照颜色与箭头缩小范围,每次猜测都会留在棋盘上`,
15832
15906
  `叁 猜中后自动记入 mcdle.排行榜`
15833
15907
  ],
15834
15908
  [
15835
15909
  `生物 ${mobData.length} · 物品 ${itemData.length} · 方块 ${blockData.length}`,
15836
15910
  `每日 ${cfg.dailyPlayLimit} 局,跨零点重置`,
15911
+ `裸猜 mcdle.裸猜 开/关 临时调整本群续猜方式`,
15837
15912
  `完整规则:mcdle.帮助`
15838
15913
  ]
15839
15914
  );
15840
15915
  }
15841
15916
  __name(introText, "introText");
15917
+ function middlewareSwitchText(on, temporary, reverted) {
15918
+ const mark = on ? "✅" : "⛔";
15919
+ const state = on ? "开启" : "停用";
15920
+ const config = cfg.isEnableMiddleware ? "开启" : "停用";
15921
+ if (!temporary) {
15922
+ return `${mark} 裸猜续局 · ${reverted ? "已复原," : ""}跟随插件配置(${state})`;
15923
+ }
15924
+ const hint = on ? "仅在已有对局中,直接发送当前模式的完整词条名称才会响应" : "续猜请使用 mcdle.猜 [名称]";
15925
+ return [
15926
+ `${mark} 裸猜续局 · 本群临时${state}(插件配置:${config})`,
15927
+ `${hint};再次发送 mcdle.裸猜 可复原`
15928
+ ].join("\n");
15929
+ }
15930
+ __name(middlewareSwitchText, "middlewareSwitchText");
15842
15931
  async function ck(session) {
15843
15932
  const sections = [
15844
15933
  [`生物词库 · ${mobChineseTitle.length} 条`, mobChineseTitle],
@@ -15866,11 +15955,7 @@ ${words.join(" ")}`).join("\n\n")
15866
15955
  );
15867
15956
  }
15868
15957
  __name(ck, "ck");
15869
- const POOLS = {
15870
- mob: mobData,
15871
- item: itemData,
15872
- block: blockData
15873
- };
15958
+ const POOLS = DATA_BY_MODE;
15874
15959
  function modeOf(entry) {
15875
15960
  if (mobData.includes(entry)) return "mob";
15876
15961
  if (itemData.includes(entry)) return "item";
@@ -15927,7 +16012,7 @@ ${words.join(" ")}`).join("\n\n")
15927
16012
  return candidates[Math.floor(Math.random() * candidates.length)];
15928
16013
  }
15929
16014
  __name(pickAnswer, "pickAnswer");
15930
- async function c(session, guess) {
16015
+ async function c(session, guess, allowStart = true) {
15931
16016
  const record = await ensureRecord(session);
15932
16017
  const now = /* @__PURE__ */ new Date();
15933
16018
  await resetDailyIfNeeded(session, record, now);
@@ -15943,12 +16028,12 @@ ${words.join(" ")}`).join("\n\n")
15943
16028
  record.guesses = restored;
15944
16029
  record.formatVersion = FORMAT_VERSION;
15945
16030
  } else {
15946
- await rollbackLegacyGame(session, record, guess, now);
15947
- return;
16031
+ await rollbackLegacyGame(session, record, guess, now, allowStart);
16032
+ return true;
15948
16033
  }
15949
16034
  } else if (record.guesses.length) {
15950
- await rollbackLegacyGame(session, record, guess, now);
15951
- return;
16035
+ await rollbackLegacyGame(session, record, guess, now, allowStart);
16036
+ return true;
15952
16037
  } else {
15953
16038
  await ctx.database.set(
15954
16039
  "mcdle",
@@ -15959,10 +16044,12 @@ ${words.join(" ")}`).join("\n\n")
15959
16044
  }
15960
16045
  }
15961
16046
  if (record.gameEnded) {
16047
+ if (!allowStart) return false;
15962
16048
  await startNewGame(session, record, guess, now);
15963
16049
  } else {
15964
16050
  await continueGame(session, record, guess);
15965
16051
  }
16052
+ return true;
15966
16053
  }
15967
16054
  __name(c, "c");
15968
16055
  function restoreGuessHistory(record) {
@@ -15975,7 +16062,7 @@ ${words.join(" ")}`).join("\n\n")
15975
16062
  return entries.map((entry) => compareData(entry, record.answer));
15976
16063
  }
15977
16064
  __name(restoreGuessHistory, "restoreGuessHistory");
15978
- async function rollbackLegacyGame(session, record, guess, now) {
16065
+ async function rollbackLegacyGame(session, record, guess, now, allowStart) {
15979
16066
  const answerTitle = record.answer?.chinese_title;
15980
16067
  const history = answerTitle ? record.historyChineseTitles.filter((title) => title !== answerTitle) : record.historyChineseTitles;
15981
16068
  const refunded = Math.max(0, record.dailyPlayedToday - 1);
@@ -15999,24 +16086,50 @@ ${words.join(" ")}`).join("\n\n")
15999
16086
  "检测到旧版本遗留的一局",
16000
16087
  "这局是在旧版本下开始的,已有的猜测记录没法用新版图片牌面渲染。",
16001
16088
  answerTitle ? `「${answerTitle}」已退回词库,之后仍有机会被抽到。` : null,
16002
- "这一局已作废并退还今日额度,马上为你重新开一局。"
16089
+ allowStart ? "这一局已作废并退还今日额度,马上按这次的完整指令重新开局。" : "这一局已作废并退还今日额度,请用 mcdle.猜 [名称] 重新开局。"
16003
16090
  )
16004
16091
  );
16005
- await startNewGame(
16006
- session,
16007
- {
16008
- ...record,
16009
- historyChineseTitles: history,
16010
- dailyPlayedToday: refunded,
16011
- gameEnded: true,
16012
- formatVersion: FORMAT_VERSION
16013
- },
16014
- guess,
16015
- now
16016
- );
16092
+ if (allowStart) {
16093
+ await startNewGame(
16094
+ session,
16095
+ {
16096
+ ...record,
16097
+ historyChineseTitles: history,
16098
+ dailyPlayedToday: refunded,
16099
+ gameEnded: true,
16100
+ formatVersion: FORMAT_VERSION
16101
+ },
16102
+ guess,
16103
+ now
16104
+ );
16105
+ }
16017
16106
  }
16018
16107
  __name(rollbackLegacyGame, "rollbackLegacyGame");
16019
16108
  async function startNewGame(session, record, guess, now) {
16109
+ if (!guess) {
16110
+ await sendMsg(
16111
+ session,
16112
+ textCard(
16113
+ "还没有进行中的游戏",
16114
+ "请发送完整指令 mcdle.猜 [名称] 开局,例如 mcdle.猜 苦力怕。",
16115
+ "名称必须来自 mcdle.词库。"
16116
+ )
16117
+ );
16118
+ return;
16119
+ }
16120
+ const opener = findAnywhere(guess);
16121
+ if (!opener) {
16122
+ const near = [...allChineseTitles].filter((title) => title.includes(guess) || guess.includes(title)).slice(0, 3);
16123
+ await sendMsg(
16124
+ session,
16125
+ textCard(
16126
+ `⚠️ 「${guess}」不在 MCDLE 词库里`,
16127
+ near.length ? `是不是想猜:${near.join("、")}?` : null,
16128
+ "没有开启新局,也没有消耗今日额度。请用 mcdle.猜 [词库名称] 开局。"
16129
+ )
16130
+ );
16131
+ return;
16132
+ }
16020
16133
  if (record.dailyPlayedToday >= cfg.dailyPlayLimit) {
16021
16134
  await sendMsg(
16022
16135
  session,
@@ -16028,8 +16141,7 @@ ${words.join(" ")}`).join("\n\n")
16028
16141
  );
16029
16142
  return;
16030
16143
  }
16031
- const opener = guess ? findAnywhere(guess) : void 0;
16032
- const mode = opener ? modeOf(opener) : ["mob", "item", "block"][Math.floor(Math.random() * 3)];
16144
+ const mode = modeOf(opener);
16033
16145
  const answer = await pickAnswer(session, record, mode);
16034
16146
  const played = record.dailyPlayedToday + 1;
16035
16147
  await ctx.database.set(
@@ -16047,14 +16159,17 @@ ${words.join(" ")}`).join("\n\n")
16047
16159
  formatVersion: FORMAT_VERSION
16048
16160
  }
16049
16161
  );
16050
- await sendCard(session, startCard(mode, played, cfg.dailyPlayLimit), startText(mode, played));
16051
- if (opener) {
16052
- await applyGuess(
16053
- session,
16054
- { mode, answer, guesses: [], titles: [], startedAt: now, played },
16055
- opener
16056
- );
16057
- }
16162
+ const channelOn = middlewareOn(session.channelId);
16163
+ await sendCard(
16164
+ session,
16165
+ startCard(mode, played, cfg.dailyPlayLimit, channelOn),
16166
+ startText(mode, played, channelOn)
16167
+ );
16168
+ await applyGuess(
16169
+ session,
16170
+ { mode, answer, guesses: [], titles: [], startedAt: now, played },
16171
+ opener
16172
+ );
16058
16173
  }
16059
16174
  __name(startNewGame, "startNewGame");
16060
16175
  async function continueGame(session, record, guess) {
@@ -16172,10 +16287,15 @@ ${words.join(" ")}`).join("\n\n")
16172
16287
  }
16173
16288
  __name(applyGuess, "applyGuess");
16174
16289
  function bz(session) {
16175
- return sendCard(session, helpCard(cfg.dailyPlayLimit, cfg.allowRepeatedGuesses), helpText());
16290
+ const channelOn = middlewareOn(session.channelId);
16291
+ return sendCard(
16292
+ session,
16293
+ helpCard(cfg.dailyPlayLimit, cfg.allowRepeatedGuesses, channelOn),
16294
+ helpText(channelOn)
16295
+ );
16176
16296
  }
16177
16297
  __name(bz, "bz");
16178
- function helpText() {
16298
+ function helpText(channelOn) {
16179
16299
  const legend = ["true", "mixed", "false", "false_up", "false_down"].map(
16180
16300
  (s) => `${STATUS_META[s].emoji} ${STATUS_META[s].label}`
16181
16301
  );
@@ -16195,12 +16315,15 @@ ${words.join(" ")}`).join("\n\n")
16195
16315
  [
16196
16316
  "mcdle.猜 [名称] 开始一局,或提交猜测",
16197
16317
  "mcdle.猜 局中直接使用可回看当前棋盘",
16318
+ "mcdle.裸猜 [开/关] 临时切换本群无前缀续猜",
16198
16319
  "mcdle.排行榜 查看群内战绩",
16199
16320
  "mcdle.词库 查阅全部候选词条"
16200
16321
  ],
16201
16322
  [
16202
16323
  `每个群每日 ${cfg.dailyPlayLimit} 局,跨零点重置。`,
16203
16324
  cfg.allowRepeatedGuesses ? "允许重复提交已经猜过的词条。" : "同一局内不能重复提交已猜过的词条。",
16325
+ channelOn ? "本群裸猜已开启:仅已有对局、纯文本完整词条、且符合当前模式时响应。" : "本群裸猜已停用:请使用完整猜测指令。",
16326
+ "空指令和词库外名称不会开启新局或消耗额度。",
16204
16327
  "词条与数据来自 zh.minecraft.wiki,版本号按发布先后比较。"
16205
16328
  ]
16206
16329
  );
@@ -16381,7 +16504,10 @@ __name(apply, "apply");
16381
16504
  0 && (module.exports = {
16382
16505
  Config,
16383
16506
  apply,
16507
+ canHandleMiddlewareGuess,
16508
+ getMiddlewareGuess,
16384
16509
  inject,
16385
16510
  name,
16511
+ resolveMiddlewareSwitch,
16386
16512
  usage
16387
16513
  });
package/lib/view.d.ts CHANGED
@@ -42,7 +42,7 @@ export interface BoardOptions {
42
42
  tip?: string;
43
43
  }
44
44
  export declare function boardCard(opts: BoardOptions): string;
45
- export declare function startCard(mode: Mode, played: number, limit: number): string;
45
+ export declare function startCard(mode: Mode, played: number, limit: number, middleware: boolean): string;
46
46
  export interface WinOptions {
47
47
  mode: Mode;
48
48
  answer: any;
@@ -56,8 +56,8 @@ export interface WinOptions {
56
56
  /** 把秒数说成人话;秒级以内也要有话说,异常才落到破折号。 */
57
57
  export declare function formatDuration(seconds: number): string;
58
58
  export declare function winCard(o: WinOptions): string;
59
- export declare function helpCard(dailyLimit: number, allowRepeat: boolean): string;
60
- export declare function introCard(dailyLimit: number): string;
59
+ export declare function helpCard(dailyLimit: number, allowRepeat: boolean, middleware: boolean): string;
60
+ export declare function introCard(dailyLimit: number, middleware: boolean): string;
61
61
  export interface RankEntry {
62
62
  username: string;
63
63
  userId: string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-mcdle",
3
3
  "description": "Koishi 的 Minecraft 猜词游戏插件。",
4
- "version": "0.1.4",
4
+ "version": "0.1.5",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [
@@ -59,6 +59,10 @@
59
59
  }
60
60
  },
61
61
  "devDependencies": {
62
- "koishi-plugin-puppeteer": "^3.9.0"
62
+ "koishi-plugin-puppeteer": "^3.9.0",
63
+ "tsx": "^4.23.13"
64
+ },
65
+ "scripts": {
66
+ "test": "tsx --test test/*.test.ts"
63
67
  }
64
68
  }
package/readme.md CHANGED
@@ -8,7 +8,7 @@ Koishi 的 Minecraft 猜词游戏插件。
8
8
 
9
9
  ## 使用
10
10
 
11
- `mcdle.猜 [名称]` 开局或猜测。系统随机选择生物、物品或方块,按属性颜色提示接近程度。
11
+ `mcdle.猜 [名称]` 使用词库中的有效名称开局或猜测。首个词条决定生物、物品或方块模式,系统再从同类词库中抽取答案。开局后可用 `mcdle.裸猜` 临时切换本群的无前缀续猜;这一状态不改插件配置,插件重载后自动复原。
12
12
 
13
13
  ## 提示
14
14
 
@@ -24,7 +24,8 @@ Koishi 的 Minecraft 猜词游戏插件。
24
24
  | 指令 | 说明 |
25
25
  | --- | --- |
26
26
  | `mcdle` | 查看帮助 |
27
- | `mcdle.猜 [名称]` | 开局或猜测 |
27
+ | `mcdle.猜 [名称]` | 用有效词条开局或猜测 |
28
+ | `mcdle.裸猜 [开/关]` | 临时切换本群无前缀续猜 |
28
29
  | `mcdle.帮助` | 完整说明 |
29
30
  | `mcdle.排行榜` | 群内战绩 |
30
31
  | `mcdle.词库` | 全部词条 |