@zhin.js/plugin-idiom-chain 1.1.18 → 1.1.19

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.
@@ -1,4 +1,4 @@
1
- import { BaseSessionService, channelKey, generateSessionId, } from '@zhin.js/game-kit';
1
+ import { BaseSessionService, generateSessionId, } from '@zhin.js/game-kit';
2
2
  export function parseUsed(value) {
3
3
  if (Array.isArray(value))
4
4
  return new Set(value.filter((x) => typeof x === 'string'));
@@ -28,13 +28,8 @@ export class SessionService extends BaseSessionService {
28
28
  const row = {
29
29
  id: generateSessionId(),
30
30
  locale: message.locale ?? 'zh-CN',
31
- adapter: String(message.$adapter),
32
- endpoint: message.$endpoint,
33
- channel_type: message.$channel.type,
34
- channel_id: message.$channel.id,
35
- channel_key: channelKey(message),
36
- player_id: message.$sender.id,
37
- player_name: message.$sender.name?.trim() || message.$sender.id,
31
+ player_id: message.sender.id,
32
+ player_name: message.sender.name?.trim() || message.sender.id,
38
33
  last_idiom: starter.text,
39
34
  next_char: starter.nextChar,
40
35
  match_mode: starter.matchMode,
@@ -50,7 +45,7 @@ export class SessionService extends BaseSessionService {
50
45
  updated_at: now,
51
46
  created_at: now,
52
47
  };
53
- return this.createRow(row);
48
+ return this.createRow({ target: message.target, fields: row });
54
49
  }
55
50
  }
56
51
  export function createServices(db) {
@@ -1 +1 @@
1
- {"version":3,"file":"session-service.js","sourceRoot":"","sources":["../src/session-service.ts"],"names":[],"mappings":"AACA,OAAO,EACL,kBAAkB,EAClB,UAAU,EACV,iBAAiB,GAGlB,MAAM,mBAAmB,CAAC;AAM3B,MAAM,UAAU,SAAS,CAAC,KAAkC;IAC1D,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC;IAClG,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,GAAG,EAAE,CAAC;IAC1D,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC5B,OAAO,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,EAAE,CAAC;IACrG,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,GAAG,EAAE,CAAC;IACnB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,GAAgB;IAC5C,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;AAClC,CAAC;AAED,MAAM,OAAO,cAAe,SAAQ,kBAAmC;IACrE,YAAY,EAAiB;QAC3B,KAAK,CAAC,EAAqD,EAAE;YAC3D,MAAM,EAAE,OAAO;YACf,KAAK,EAAE,sBAAsB;YAC7B,UAAU,EAAE,CAAC,WAAW,CAAC;SAC1B,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,aAAa,CACjB,OAAwB,EACxB,OAAiF;QAEjF,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,GAAG,GAAoB;YAC3B,EAAE,EAAE,iBAAiB,EAAE;YACvB,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,OAAO;YACjC,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC;YACjC,QAAQ,EAAE,OAAO,CAAC,SAAS;YAC3B,YAAY,EAAE,OAAO,CAAC,QAAQ,CAAC,IAAI;YACnC,UAAU,EAAE,OAAO,CAAC,QAAQ,CAAC,EAAE;YAC/B,WAAW,EAAE,UAAU,CAAC,OAAO,CAAC;YAChC,SAAS,EAAE,OAAO,CAAC,OAAO,CAAC,EAAE;YAC7B,WAAW,EAAE,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE;YAC/D,UAAU,EAAE,OAAO,CAAC,IAAI;YACxB,SAAS,EAAE,OAAO,CAAC,QAAQ;YAC3B,UAAU,EAAE,OAAO,CAAC,SAAS;YAC7B,WAAW,EAAE,aAAa,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YACjD,YAAY,EAAE,CAAC;YACf,SAAS,EAAE,CAAC;YACZ,MAAM,EAAE,CAAC;YACT,WAAW,EAAE,CAAC;YACd,WAAW,EAAE,CAAC;YACd,UAAU,EAAE,CAAC;YACb,IAAI,EAAE,QAAQ;YACd,MAAM,EAAE,QAAQ;YAChB,UAAU,EAAE,GAAG;YACf,UAAU,EAAE,GAAG;SAChB,CAAC;QACF,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IAC7B,CAAC;CACF;AAED,MAAM,UAAU,cAAc,CAAC,EAAiB;IAC9C,OAAO,IAAI,cAAc,CAAC,EAAE,CAAC,CAAC;AAChC,CAAC"}
1
+ {"version":3,"file":"session-service.js","sourceRoot":"","sources":["../src/session-service.ts"],"names":[],"mappings":"AACA,OAAO,EACL,kBAAkB,EAClB,iBAAiB,GAGlB,MAAM,mBAAmB,CAAC;AAM3B,MAAM,UAAU,SAAS,CAAC,KAAkC;IAC1D,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC;IAClG,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,GAAG,EAAE,CAAC;IAC1D,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC5B,OAAO,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,EAAE,CAAC;IACrG,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,GAAG,EAAE,CAAC;IACnB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,GAAgB;IAC5C,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;AAClC,CAAC;AAED,MAAM,OAAO,cAAe,SAAQ,kBAAmC;IACrE,YAAY,EAAiB;QAC3B,KAAK,CAAC,EAAqD,EAAE;YAC3D,MAAM,EAAE,OAAO;YACf,KAAK,EAAE,sBAAsB;YAC7B,UAAU,EAAE,CAAC,WAAW,CAAC;SAC1B,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,aAAa,CACjB,OAAmB,EACnB,OAAiF;QAEjF,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,GAAG,GAAkG;YACzG,EAAE,EAAE,iBAAiB,EAAE;YACvB,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,OAAO;YACjC,SAAS,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE;YAC5B,WAAW,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,OAAO,CAAC,MAAM,CAAC,EAAE;YAC7D,UAAU,EAAE,OAAO,CAAC,IAAI;YACxB,SAAS,EAAE,OAAO,CAAC,QAAQ;YAC3B,UAAU,EAAE,OAAO,CAAC,SAAS;YAC7B,WAAW,EAAE,aAAa,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YACjD,YAAY,EAAE,CAAC;YACf,SAAS,EAAE,CAAC;YACZ,MAAM,EAAE,CAAC;YACT,WAAW,EAAE,CAAC;YACd,WAAW,EAAE,CAAC;YACd,UAAU,EAAE,CAAC;YACb,IAAI,EAAE,QAAQ;YACd,MAAM,EAAE,QAAQ;YAChB,UAAU,EAAE,GAAG;YACf,UAAU,EAAE,GAAG;SAChB,CAAC;QACF,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;IACjE,CAAC;CACF;AAED,MAAM,UAAU,cAAc,CAAC,EAAiB;IAC9C,OAAO,IAAI,cAAc,CAAC,EAAE,CAAC,CAAC;AAChC,CAAC"}
@@ -1,20 +1,24 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
- import { defineGameCommandAliasMiddleware, isChainAction, messageFromCommandInput, normalizeChainAction, } from '@zhin.js/game-kit';
2
+ import { replyGameOperation, defineGameCommandAliasMiddleware, isChainAction, gamePlayerFromInput, normalizeChainAction, } from '@zhin.js/game-kit';
3
3
  import { getChainHelp, runChainCommand } from "../../lib/chain-command.js";
4
4
  import { resolveGameServices } from "../../lib/runtime-store.js";
5
5
  export default defineGameCommandAliasMiddleware({
6
6
  aliases: ['接龙', 'chain'],
7
7
  async run(action, input, context) {
8
- const message = messageFromCommandInput(input, {
9
- formatCommand: (path) => context.input.formatCommand?.(path, context.owner.id),
10
- });
8
+ const presentation = { locale: context.input.locale,
9
+ formatCommand: (path) => context.input.formatCommand?.(path, context.owner.id) };
11
10
  const normalized = normalizeChainAction(String(action ?? ''));
12
- if (normalized === 'help')
13
- return getChainHelp(message);
11
+ if (normalized === 'help') {
12
+ await context.input.$reply(getChainHelp(presentation));
13
+ return true;
14
+ }
14
15
  // action 无法识别:放行给后续中间件,避免劫持普通聊天
15
16
  if (!isChainAction(normalized))
16
- return null;
17
+ return false;
18
+ const message = gamePlayerFromInput(input, presentation);
17
19
  const services = resolveGameServices(context);
18
- return runChainCommand(services, message, normalized);
20
+ await replyGameOperation({ session: services, player: message, reply: content => context.input.$reply(content),
21
+ run: () => runChainCommand(services, message, normalized) });
22
+ return true;
19
23
  },
20
24
  });
@@ -1,7 +1,8 @@
1
1
  import {
2
+ replyGameOperation,
2
3
  defineGameCommandAliasMiddleware,
3
4
  isChainAction,
4
- messageFromCommandInput,
5
+ gamePlayerFromInput,
5
6
  normalizeChainAction,
6
7
  } from '@zhin.js/game-kit';
7
8
  import { getChainHelp, runChainCommand } from '../../src/chain-command.js';
@@ -10,14 +11,17 @@ import { resolveGameServices } from '../../src/runtime-store.js';
10
11
  export default defineGameCommandAliasMiddleware({
11
12
  aliases: ['接龙', 'chain'],
12
13
  async run(action, input, context) {
13
- const message = messageFromCommandInput(input, {
14
- formatCommand: (path) => context.input.formatCommand?.(path, context.owner.id),
15
- });
14
+ const presentation = { locale: context.input.locale,
15
+ formatCommand: (path: string) => context.input.formatCommand?.(path, context.owner.id) };
16
+
16
17
  const normalized = normalizeChainAction(String(action ?? ''));
17
- if (normalized === 'help') return getChainHelp(message);
18
+ if (normalized === 'help') { await context.input.$reply(getChainHelp(presentation)); return true; }
18
19
  // action 无法识别:放行给后续中间件,避免劫持普通聊天
19
- if (!isChainAction(normalized)) return null;
20
+ if (!isChainAction(normalized)) return false;
21
+ const message = gamePlayerFromInput(input, presentation);
20
22
  const services = resolveGameServices(context);
21
- return runChainCommand(services, message, normalized);
23
+ await replyGameOperation({ session: services, player: message, reply: content => context.input.$reply(content),
24
+ run: () => runChainCommand(services, message, normalized) });
25
+ return true;
22
26
  },
23
27
  });
@@ -1,6 +1,6 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { replyGameOperation, buildChoiceFallbackMap, channelKey, gamePlayerFromInput, parseChoicePayload, resolveGameTextPayload, } from '@zhin.js/game-kit';
2
3
  import { defineMiddleware } from 'zhin.js/middleware';
3
- import { buildChoiceFallbackMap, channelKey, messageFromCommandInput, parseChoicePayload, resolveGameTextPayload, } from '@zhin.js/game-kit';
4
4
  import { resolveGameServices } from "../../lib/runtime-store.js";
5
5
  import { CHAIN_PREFIX, handleChoice, processIdiomText } from "../../lib/game-flow.js";
6
6
  /**
@@ -10,13 +10,17 @@ import { CHAIN_PREFIX, handleChoice, processIdiomText } from "../../lib/game-flo
10
10
  export default defineMiddleware({
11
11
  target: 'inbound',
12
12
  async handle(context, next) {
13
+ if (!context.input.sender?.id) {
14
+ await next();
15
+ return;
16
+ }
13
17
  const raw = context.input.content?.trim() ?? '';
14
18
  if (!raw) {
15
19
  await next();
16
20
  return;
17
21
  }
18
22
  const services = resolveGameServices(context);
19
- const message = messageFromCommandInput(context.input);
23
+ const message = gamePlayerFromInput(context.input);
20
24
  const ch = channelKey(message);
21
25
  // 直接 payload(QQ 指令预填 / Sandbox action→text)
22
26
  const payloadFromText = resolveGameTextPayload(raw);
@@ -25,16 +29,15 @@ export default defineMiddleware({
25
29
  if (parsed) {
26
30
  const session = await services.getById(parsed.sessionId);
27
31
  if (session?.channel_key === ch) {
28
- const reply = await handleChoice(services, message, parsed.sessionId, parsed.choiceId);
29
- if (reply)
30
- await context.input.$reply(reply);
32
+ await replyGameOperation({ session: services, player: message, reply: content => context.input.$reply(content),
33
+ run: () => handleChoice(services, message, parsed.sessionId, parsed.choiceId) });
31
34
  return;
32
35
  }
33
36
  }
34
37
  await next();
35
38
  return;
36
39
  }
37
- const session = await services.getActiveForUser(ch, message.$sender.id);
40
+ const session = await services.getActiveForUser(ch, message.sender.id);
38
41
  if (!session) {
39
42
  await next();
40
43
  return;
@@ -48,17 +51,15 @@ export default defineMiddleware({
48
51
  const payload = resolveGameTextPayload(raw, map);
49
52
  const parsed = payload ? parseChoicePayload(payload, CHAIN_PREFIX) : null;
50
53
  if (parsed?.sessionId === session.id) {
51
- const reply = await handleChoice(services, message, session.id, parsed.choiceId);
52
- if (reply)
53
- await context.input.$reply(reply);
54
+ await replyGameOperation({ session: services, player: message, reply: content => context.input.$reply(content),
55
+ run: () => handleChoice(services, message, session.id, parsed.choiceId) });
54
56
  return;
55
57
  }
56
58
  }
57
59
  // 仅把 2-8 个汉字的纯文本当成语作答,其余放行
58
60
  if (/^[\u4e00-\u9fff]{2,8}$/.test(raw)) {
59
- const reply = await processIdiomText(services, message, raw);
60
- if (reply)
61
- await context.input.$reply(reply);
61
+ await replyGameOperation({ session: services, player: message, reply: content => context.input.$reply(content),
62
+ run: () => processIdiomText(services, message, raw) });
62
63
  return;
63
64
  }
64
65
  await next();
@@ -1,12 +1,13 @@
1
- import { defineMiddleware } from 'zhin.js/middleware';
2
- import type { Message } from '@zhin.js/core/runtime';
3
1
  import {
2
+ replyGameOperation,
4
3
  buildChoiceFallbackMap,
5
4
  channelKey,
6
- messageFromCommandInput,
5
+ gamePlayerFromInput,
7
6
  parseChoicePayload,
8
7
  resolveGameTextPayload,
9
8
  } from '@zhin.js/game-kit';
9
+ import { defineMiddleware } from 'zhin.js/middleware';
10
+ import type { Message } from '@zhin.js/core/runtime';
10
11
  import { resolveGameServices } from '../../src/runtime-store.js';
11
12
  import { CHAIN_PREFIX, handleChoice, processIdiomText } from '../../src/game-flow.js';
12
13
 
@@ -17,13 +18,14 @@ import { CHAIN_PREFIX, handleChoice, processIdiomText } from '../../src/game-flo
17
18
  export default defineMiddleware<Message>({
18
19
  target: 'inbound',
19
20
  async handle(context, next) {
21
+ if (!context.input.sender?.id) { await next(); return; }
20
22
  const raw = context.input.content?.trim() ?? '';
21
23
  if (!raw) {
22
24
  await next();
23
25
  return;
24
26
  }
25
27
  const services = resolveGameServices(context);
26
- const message = messageFromCommandInput(context.input);
28
+ const message = gamePlayerFromInput(context.input);
27
29
  const ch = channelKey(message);
28
30
 
29
31
  // 直接 payload(QQ 指令预填 / Sandbox action→text)
@@ -33,8 +35,8 @@ export default defineMiddleware<Message>({
33
35
  if (parsed) {
34
36
  const session = await services.getById(parsed.sessionId);
35
37
  if (session?.channel_key === ch) {
36
- const reply = await handleChoice(services, message, parsed.sessionId, parsed.choiceId);
37
- if (reply) await context.input.$reply(reply);
38
+ await replyGameOperation({ session: services, player: message, reply: content => context.input.$reply(content),
39
+ run: () => handleChoice(services, message, parsed.sessionId, parsed.choiceId) });
38
40
  return;
39
41
  }
40
42
  }
@@ -42,7 +44,7 @@ export default defineMiddleware<Message>({
42
44
  return;
43
45
  }
44
46
 
45
- const session = await services.getActiveForUser(ch, message.$sender.id);
47
+ const session = await services.getActiveForUser(ch, message.sender.id);
46
48
  if (!session) {
47
49
  await next();
48
50
  return;
@@ -57,16 +59,16 @@ export default defineMiddleware<Message>({
57
59
  const payload = resolveGameTextPayload(raw, map);
58
60
  const parsed = payload ? parseChoicePayload(payload, CHAIN_PREFIX) : null;
59
61
  if (parsed?.sessionId === session.id) {
60
- const reply = await handleChoice(services, message, session.id, parsed.choiceId);
61
- if (reply) await context.input.$reply(reply);
62
+ await replyGameOperation({ session: services, player: message, reply: content => context.input.$reply(content),
63
+ run: () => handleChoice(services, message, session.id, parsed.choiceId) });
62
64
  return;
63
65
  }
64
66
  }
65
67
 
66
68
  // 仅把 2-8 个汉字的纯文本当成语作答,其余放行
67
69
  if (/^[\u4e00-\u9fff]{2,8}$/.test(raw)) {
68
- const reply = await processIdiomText(services, message, raw);
69
- if (reply) await context.input.$reply(reply);
70
+ await replyGameOperation({ session: services, player: message, reply: content => context.input.$reply(content),
71
+ run: () => processIdiomText(services, message, raw) });
70
72
  return;
71
73
  }
72
74
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/plugin-idiom-chain",
3
- "version": "1.1.18",
3
+ "version": "1.1.19",
4
4
  "description": "Zhin.js 成语接龙 — 四字成语文字接龙 (Plugin Runtime)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -24,13 +24,13 @@
24
24
  "license": "MIT",
25
25
  "dependencies": {
26
26
  "chinese-idiom-chengyu": "^1.1.0",
27
- "@zhin.js/core": "1.1.53",
28
- "@zhin.js/game-kit": "1.1.18"
27
+ "@zhin.js/core": "1.1.54",
28
+ "@zhin.js/game-kit": "1.1.19"
29
29
  },
30
30
  "devDependencies": {
31
31
  "typescript": "^6.0.3",
32
32
  "vitest": "^4.1.10",
33
- "zhin.js": "1.1.18"
33
+ "zhin.js": "1.1.19"
34
34
  },
35
35
  "repository": {
36
36
  "type": "git",
@@ -67,9 +67,9 @@
67
67
  "plugins": []
68
68
  },
69
69
  "peerDependencies": {
70
- "@zhin.js/command": "^1.1.11",
71
- "@zhin.js/middleware": "^1.1.10",
72
- "zhin.js": "^1.1.18"
70
+ "@zhin.js/command": "^1.1.12",
71
+ "@zhin.js/middleware": "^1.1.11",
72
+ "zhin.js": "^1.1.19"
73
73
  },
74
74
  "peerDependenciesMeta": {
75
75
  "@zhin.js/command": {
@@ -1,4 +1,4 @@
1
- import { formatGameHelp, gameCommandFeedback, gameCommandHint, channelKey, type GameMessageLike, type GameReply } from '@zhin.js/game-kit';
1
+ import { formatGameHelp, gameCommandFeedback, gameCommandHint, channelKey, type GamePlayer, type GameReply } from '@zhin.js/game-kit';
2
2
  import { createMessages } from 'zhin.js/i18n';
3
3
  import { continueGame, startGame } from './game-flow.js';
4
4
  import { idiomCount, modeLabel, promptLine, type MatchMode } from './engine.js';
@@ -15,10 +15,10 @@ const messages = createMessages({
15
15
  quit: { 'zh-CN': '已放弃成语接龙。', en: 'You quit the idiom chain.' },
16
16
  });
17
17
 
18
- export function getChainHelp(message: Pick<GameMessageLike, 'locale' | 'formatCommand'> = {}): string {
18
+ export function getChainHelp(message: Pick<GamePlayer, 'locale' | 'formatCommand'> = {}): string {
19
19
  const locale = message.locale ?? 'zh-CN';
20
20
  const command = message.formatCommand?.('chain');
21
- return [formatGameHelp(message, { 'zh-CN': '📜 成语接龙(文字接龙 · 四字成语)', en: '📜 Chinese Idiom Chain' }, 'chain', ['continue', 'quit']),
21
+ return [formatGameHelp({ presentation: message, title: { 'zh-CN': '📜 成语接龙(文字接龙 · 四字成语)', en: '📜 Chinese Idiom Chain' }, commandPath: 'chain', actions: ['continue', 'quit'] }),
22
22
  messages(locale, 'dictionary', { count: idiomCount() }),
23
23
  ...(command ? [messages(locale, 'pinyin', { command: command + ' start_pinyin' }), messages(locale, 'char', { command: command + ' start_char' })] : []),
24
24
  messages(locale, 'instructions'),
@@ -28,37 +28,39 @@ export const CHAIN_HELP = getChainHelp();
28
28
 
29
29
  export async function runChainCommand(
30
30
  services: SessionService,
31
- message: GameMessageLike,
31
+ message: GamePlayer,
32
32
  action: string,
33
33
  ): Promise<GameReply> {
34
- const ch = channelKey(message);
35
- const userId = message.$sender.id;
34
+ return services.runInScope({ scope: channelKey(message), run: async () => {
35
+ const ch = channelKey(message);
36
+ const userId = message.sender.id;
36
37
 
37
- if (!action || action === 'help') {
38
- const active = await services.getActiveByChannel(ch);
39
- const lines = [getChainHelp(message), ''];
40
- if (active) {
41
- const mode: MatchMode = active.match_mode === 'char' ? 'char' : 'pinyin';
42
- lines.push(
43
- messages(active.locale ?? undefined, 'active', { player: active.player_name, mode: modeLabel(mode, active.locale ?? undefined), playerScore: active.player_score, botScore: active.bot_score, streak: active.streak }),
44
- );
45
- if (active.last_idiom) lines.push(promptLine(active.last_idiom, mode, active.locale ?? undefined));
46
- } else {
47
- lines.push(gameCommandFeedback(message, 'noGame'), gameCommandHint(message, 'chain', 'start_pinyin'));
38
+ if (!action || action === 'help') {
39
+ const active = await services.getActiveByChannel(ch);
40
+ const lines = [getChainHelp(message), ''];
41
+ if (active) {
42
+ const mode: MatchMode = active.match_mode === 'char' ? 'char' : 'pinyin';
43
+ lines.push(
44
+ messages(active.locale ?? undefined, 'active', { player: active.player_name, mode: modeLabel(mode, active.locale ?? undefined), playerScore: active.player_score, botScore: active.bot_score, streak: active.streak }),
45
+ );
46
+ if (active.last_idiom) lines.push(promptLine(active.last_idiom, mode, active.locale ?? undefined));
47
+ } else {
48
+ lines.push(gameCommandFeedback(message, 'noGame'), gameCommandHint(message, 'chain', 'start_pinyin'));
49
+ }
50
+ return lines.join('\n');
48
51
  }
49
- return lines.join('\n');
50
- }
51
52
 
52
- if (action === 'start_pinyin') return (await startGame(services, message, 'pinyin')) ?? '';
53
- if (action === 'start_char') return (await startGame(services, message, 'char')) ?? '';
54
- if (action === 'continue') return continueGame(services, message);
53
+ if (action === 'start_pinyin') return (await startGame(services, message, 'pinyin')) ?? '';
54
+ if (action === 'start_char') return (await startGame(services, message, 'char')) ?? '';
55
+ if (action === 'continue') return continueGame(services, message);
55
56
 
56
- if (action === 'quit') {
57
- const row = await services.getActiveForUser(ch, userId);
58
- if (!row) return gameCommandFeedback(message, 'noGame');
59
- await services.updateSession(row.id, { status: 'aborted' });
60
- return messages(row.locale ?? undefined, 'quit');
61
- }
57
+ if (action === 'quit') {
58
+ const row = await services.getActiveForUser(ch, userId);
59
+ if (!row) return gameCommandFeedback(message, 'noGame');
60
+ await services.updateSession(row.id, { status: 'aborted' });
61
+ return messages(row.locale ?? undefined, 'quit');
62
+ }
62
63
 
63
- return gameCommandFeedback(message, 'unknown', action) + '\n\n' + getChainHelp(message);
64
+ return gameCommandFeedback(message, 'unknown', action) + '\n\n' + getChainHelp(message);
65
+ } });
64
66
  }