@zhin.js/plugin-idiom-chain 1.0.1 → 1.0.2

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.
Files changed (62) hide show
  1. package/commands/chain/[action:string=].ts +15 -0
  2. package/lib/chain-command.d.ts +4 -3
  3. package/lib/chain-command.js +5 -1
  4. package/lib/chain-command.js.map +1 -1
  5. package/lib/engine.d.ts +0 -1
  6. package/lib/game-flow.d.ts +6 -7
  7. package/lib/game-flow.js +26 -21
  8. package/lib/game-flow.js.map +1 -1
  9. package/lib/idiom-provider.d.ts +0 -1
  10. package/lib/idiom-provider.js +1 -1
  11. package/lib/idiom-provider.js.map +1 -1
  12. package/lib/index.d.ts +8 -2
  13. package/lib/index.js +8 -25
  14. package/lib/index.js.map +1 -1
  15. package/lib/memory-db.d.ts +6 -0
  16. package/lib/memory-db.js +18 -0
  17. package/lib/memory-db.js.map +1 -0
  18. package/lib/models.d.ts +5 -4
  19. package/lib/models.js +2 -2
  20. package/lib/models.js.map +1 -1
  21. package/lib/runtime-store.d.ts +6 -0
  22. package/lib/runtime-store.js +7 -0
  23. package/lib/runtime-store.js.map +1 -0
  24. package/lib/session-service.d.ts +1 -3
  25. package/lib/session-service.js +1 -4
  26. package/lib/session-service.js.map +1 -1
  27. package/lib/view.d.ts +1 -2
  28. package/lib/view.js +1 -1
  29. package/lib/view.js.map +1 -1
  30. package/middlewares/chain-alias.ts +18 -0
  31. package/middlewares/chain-text.ts +75 -0
  32. package/package.json +36 -10
  33. package/plugin.ts +64 -0
  34. package/schema.json +6 -0
  35. package/src/chain-command.ts +12 -3
  36. package/src/game-flow.ts +32 -28
  37. package/src/idiom-provider.ts +1 -1
  38. package/src/index.ts +9 -30
  39. package/src/memory-db.ts +22 -0
  40. package/src/models.ts +7 -4
  41. package/src/runtime-store.ts +9 -0
  42. package/src/session-service.ts +2 -5
  43. package/src/view.ts +2 -2
  44. package/lib/chain-command.d.ts.map +0 -1
  45. package/lib/commands.d.ts +0 -6
  46. package/lib/commands.d.ts.map +0 -1
  47. package/lib/commands.js +0 -95
  48. package/lib/commands.js.map +0 -1
  49. package/lib/engine.d.ts.map +0 -1
  50. package/lib/game-flow.d.ts.map +0 -1
  51. package/lib/hub-register.d.ts +0 -5
  52. package/lib/hub-register.d.ts.map +0 -1
  53. package/lib/hub-register.js +0 -31
  54. package/lib/hub-register.js.map +0 -1
  55. package/lib/idiom-provider.d.ts.map +0 -1
  56. package/lib/index.d.ts.map +0 -1
  57. package/lib/models.d.ts.map +0 -1
  58. package/lib/session-service.d.ts.map +0 -1
  59. package/lib/view.d.ts.map +0 -1
  60. package/plugin.yml +0 -2
  61. package/src/commands.ts +0 -116
  62. package/src/hub-register.ts +0 -32
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@zhin.js/plugin-idiom-chain",
3
- "version": "1.0.1",
4
- "description": "Zhin.js 成语接龙 — 四字成语文字接龙",
3
+ "version": "1.0.2",
4
+ "description": "Zhin.js 成语接龙 — 四字成语文字接龙 (Plugin Runtime)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
7
7
  "types": "./lib/index.d.ts",
@@ -14,22 +14,26 @@
14
14
  "./package.json": "./package.json"
15
15
  },
16
16
  "files": [
17
+ "plugin.ts",
18
+ "schema.json",
19
+ "commands",
20
+ "middlewares",
17
21
  "src",
18
22
  "lib",
19
- "plugin.yml",
20
23
  "README.md"
21
24
  ],
22
25
  "license": "MIT",
23
- "peerDependencies": {
24
- "zhin.js": "4.1.2"
25
- },
26
26
  "dependencies": {
27
27
  "chinese-idiom-chengyu": "^1.1.0",
28
- "@zhin.js/game-shared": "1.0.1"
28
+ "@zhin.js/command": "1.0.1",
29
+ "@zhin.js/core": "1.3.5",
30
+ "@zhin.js/game-kit": "1.0.2",
31
+ "@zhin.js/middleware": "1.0.1",
32
+ "@zhin.js/plugin-runtime": "1.0.1"
29
33
  },
30
34
  "devDependencies": {
31
35
  "typescript": "^6.0.3",
32
- "zhin.js": "4.1.2"
36
+ "vitest": "^4.1.10"
33
37
  },
34
38
  "repository": {
35
39
  "type": "git",
@@ -40,8 +44,30 @@
40
44
  "access": "public",
41
45
  "registry": "https://registry.npmjs.org"
42
46
  },
47
+ "engines": {
48
+ "node": "^20.19.0 || >=22.12.0"
49
+ },
50
+ "zhin": {
51
+ "protocol": 1,
52
+ "type": "plugin",
53
+ "entry": "./plugin.ts",
54
+ "engine": "^1.0.0",
55
+ "runtime": "trusted",
56
+ "features": [
57
+ {
58
+ "package": "@zhin.js/command",
59
+ "api": "^1.0.0"
60
+ },
61
+ {
62
+ "package": "@zhin.js/middleware",
63
+ "api": "^1.0.0"
64
+ }
65
+ ],
66
+ "plugins": []
67
+ },
43
68
  "scripts": {
44
- "build": "tsc --build",
45
- "clean": "rimraf lib"
69
+ "build": "tsc",
70
+ "clean": "rimraf lib",
71
+ "test": "NODE_OPTIONS=--experimental-strip-types vitest run --root ../../.. plugins/games/idiom-chain/tests"
46
72
  }
47
73
  }
package/plugin.ts ADDED
@@ -0,0 +1,64 @@
1
+ import { definePlugin, databaseHostToken, scheduleHostToken } from '@zhin.js/plugin-runtime';
2
+ import {
3
+ registerRuntimeGame,
4
+ initGameRecordHost,
5
+ DEFAULT_GAME_STALE_CRON,
6
+ DEFAULT_GAME_STALE_IDLE_MS,
7
+ } from '@zhin.js/game-kit';
8
+ import { mountChainHostServices, mountChainMemoryServices } from './src/memory-db.js';
9
+ import { gameServicesToken } from './src/runtime-store.js';
10
+ import type { SessionService } from './src/session-service.js';
11
+
12
+ /**
13
+ * Plugin Runtime:
14
+ * - Commands under `commands/` are authoritative (help + start/continue/quit via in-memory DB).
15
+ * - DB: prefer databaseHostToken; else in-memory SessionService.
16
+ * - Text middleware under `middlewares/` handles idiom answer payloads.
17
+ */
18
+ export default definePlugin({
19
+ name: 'idiom-chain',
20
+ metadata: {
21
+ displayName: 'Idiom Chain',
22
+ },
23
+ setup(context) {
24
+ let services: SessionService;
25
+ if (context.resources.has(databaseHostToken)) {
26
+ const host = context.resources.use(databaseHostToken);
27
+ services = mountChainHostServices(host);
28
+ context.lifecycle.add(initGameRecordHost(host));
29
+ } else {
30
+ services = mountChainMemoryServices();
31
+ }
32
+ context.resources.provide(gameServicesToken, services);
33
+ const disposeHub = registerRuntimeGame({
34
+ id: 'chain',
35
+ title: '成语接龙',
36
+ icon: '📜',
37
+ description: '四字成语接龙(同音/同字)',
38
+ commandPrefix: '/接龙',
39
+ quickStart: 'start_pinyin',
40
+ aliases: ['chain'],
41
+ menus: [
42
+ { id: 'start_pinyin', label: '🎮 同音接龙' },
43
+ { id: 'start_char', label: '📝 同字接龙' },
44
+ { id: 'continue', label: '🔄 继续' },
45
+ { id: 'help', label: '📖 玩法说明' },
46
+ ],
47
+ });
48
+ context.lifecycle.add(disposeHub);
49
+
50
+ if (context.resources.has(scheduleHostToken)) {
51
+ const schedule = context.resources.use(scheduleHostToken);
52
+ const disposeCron = schedule.register({
53
+ id: 'chain/abort-stale',
54
+ cron: DEFAULT_GAME_STALE_CRON,
55
+ description: 'Abort stale idiom-chain sessions',
56
+ async execute() {
57
+ if (!services.abortStale) return;
58
+ await services.abortStale(DEFAULT_GAME_STALE_IDLE_MS);
59
+ },
60
+ });
61
+ context.lifecycle.add(disposeCron);
62
+ }
63
+ },
64
+ });
package/schema.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "type": "object",
4
+ "additionalProperties": false,
5
+ "properties": {}
6
+ }
@@ -1,5 +1,5 @@
1
- import type { Message, Plugin } from 'zhin.js';
2
- import { channelKey } from '@zhin.js/game-shared';
1
+ import type { Message, Plugin } from '@zhin.js/core';
2
+ import { channelKey } from '@zhin.js/game-kit';
3
3
  import { continueGame, startGame } from './game-flow.js';
4
4
  import { idiomCount, modeLabel, promptLine, type MatchMode } from './engine.js';
5
5
 
@@ -19,7 +19,7 @@ export const CHAIN_HELP = [
19
19
  ].join('\n');
20
20
 
21
21
  export async function runChainCommand(
22
- plugin: Plugin,
22
+ plugin: Plugin | null,
23
23
  services: SessionService,
24
24
  message: Message<any>,
25
25
  action: string,
@@ -55,3 +55,12 @@ export async function runChainCommand(
55
55
 
56
56
  return `未知子命令:${action}\n\n${CHAIN_HELP}`;
57
57
  }
58
+
59
+ /** Plugin Runtime / smoke: text-only, no Adapter.editMessage. */
60
+ export async function runChainCommandText(
61
+ services: SessionService,
62
+ message: Message<any>,
63
+ action: string,
64
+ ): Promise<string> {
65
+ return (await runChainCommand(null, services, message, action)) ?? '';
66
+ }
package/src/game-flow.ts CHANGED
@@ -1,4 +1,5 @@
1
- import type { Adapter, Message, Plugin } from 'zhin.js';
1
+ import type { Adapter, Message, Plugin } from '@zhin.js/core';
2
+ import { plainTextFromSendContent } from '@zhin.js/game-kit';
2
3
  import {
3
4
  CHAIN_PREFIX,
4
5
  getGloss,
@@ -21,13 +22,15 @@ function sessionMode(session: ChainSessionRow): MatchMode {
21
22
  }
22
23
 
23
24
  export async function sendOrEditView(
24
- plugin: Plugin,
25
+ plugin: Plugin | null,
25
26
  services: SessionService,
26
27
  message: Message<any>,
27
28
  session: ChainSessionRow,
28
29
  eventLines: string[] = [],
29
- ): Promise<void> {
30
+ ): Promise<string | void> {
30
31
  const content = buildChainView(session, eventLines, message.$channel.type);
32
+ if (!plugin) return plainTextFromSendContent(content);
33
+
31
34
  const adapter = plugin.root.inject(message.$adapter) as Adapter;
32
35
 
33
36
  if (session.board_message_id) {
@@ -83,7 +86,7 @@ async function botTurn(
83
86
  }
84
87
 
85
88
  export async function startGame(
86
- plugin: Plugin,
89
+ plugin: Plugin | null,
87
90
  services: SessionService,
88
91
  message: Message<any>,
89
92
  matchMode: MatchMode = 'pinyin',
@@ -108,15 +111,15 @@ export async function startGame(
108
111
  });
109
112
 
110
113
  const gloss = starter.gloss ?? getGloss(starter.text);
111
- await sendOrEditView(plugin, services, message, session, [
114
+ const text = await sendOrEditView(plugin, services, message, session, [
112
115
  `🎬 ${modeLabel(matchMode)}开局!我先出:**${starter.text}**${gloss ? `(${gloss})` : ''}`,
113
116
  promptLine(starter.text, matchMode),
114
117
  ]);
115
- return undefined;
118
+ return typeof text === 'string' ? text : undefined;
116
119
  }
117
120
 
118
121
  export async function continueGame(
119
- plugin: Plugin,
122
+ plugin: Plugin | null,
120
123
  services: SessionService,
121
124
  message: Message<any>,
122
125
  ): Promise<string> {
@@ -125,12 +128,13 @@ export async function continueGame(
125
128
  message.$sender.id,
126
129
  );
127
130
  if (!session) return '你没有进行中的接龙,发送「接龙 开始」。';
128
- await sendOrEditView(plugin, services, message, session);
131
+ const text = await sendOrEditView(plugin, services, message, session);
132
+ if (typeof text === 'string') return text;
129
133
  return '已刷新接龙界面。';
130
134
  }
131
135
 
132
136
  export async function processIdiomText(
133
- plugin: Plugin,
137
+ plugin: Plugin | null,
134
138
  services: SessionService,
135
139
  message: Message<any>,
136
140
  raw: string,
@@ -153,13 +157,14 @@ export async function processIdiomText(
153
157
  streak: 0,
154
158
  });
155
159
  const updated = (await services.getById(session.id))!;
156
- await sendOrEditView(plugin, services, message, updated, [check.reason!, '失误过多,本局结束。']);
157
- return null;
160
+ return (await sendOrEditView(plugin, services, message, updated, [
161
+ check.reason!,
162
+ '失误过多,本局结束。',
163
+ ])) ?? null;
158
164
  }
159
165
  await services.updateSession(session.id, { wrong_count: wrong, streak: 0 });
160
166
  const updated = (await services.getById(session.id))!;
161
- await sendOrEditView(plugin, services, message, updated, [check.reason!]);
162
- return null;
167
+ return (await sendOrEditView(plugin, services, message, updated, [check.reason!])) ?? null;
163
168
  }
164
169
 
165
170
  used.add(idiom);
@@ -181,12 +186,14 @@ export async function processIdiomText(
181
186
  const userLine = `✅ 你:${idiom}${gloss ? `(${gloss})` : ''}`;
182
187
 
183
188
  const botResult = await botTurn(services, current, used);
184
- await sendOrEditView(plugin, services, message, botResult.session, [userLine, ...botResult.lines]);
185
- return null;
189
+ return (await sendOrEditView(plugin, services, message, botResult.session, [
190
+ userLine,
191
+ ...botResult.lines,
192
+ ])) ?? null;
186
193
  }
187
194
 
188
195
  export async function handleChoice(
189
- plugin: Plugin,
196
+ plugin: Plugin | null,
190
197
  services: SessionService,
191
198
  message: Message<any>,
192
199
  sessionId: string,
@@ -214,11 +221,10 @@ export async function handleChoice(
214
221
  });
215
222
  const updated = (await services.getById(session.id))!;
216
223
  const gloss = starter.gloss ?? getGloss(starter.text);
217
- await sendOrEditView(plugin, services, message, updated, [
224
+ return (await sendOrEditView(plugin, services, message, updated, [
218
225
  `🎬 新一局!我先出:**${starter.text}**${gloss ? `(${gloss})` : ''}`,
219
226
  promptLine(starter.text, mode),
220
- ]);
221
- return null;
227
+ ])) ?? null;
222
228
  }
223
229
 
224
230
  if (session.status !== 'active') {
@@ -233,17 +239,15 @@ export async function handleChoice(
233
239
  await sendOrEditView(plugin, services, message, session, ['💡 词库中暂无可用提示,你赢了!']);
234
240
  await services.updateSession(session.id, { status: 'won', player_score: session.player_score + 1 });
235
241
  const updated = (await services.getById(session.id))!;
236
- await sendOrEditView(plugin, services, message, updated);
237
- return null;
242
+ return (await sendOrEditView(plugin, services, message, updated)) ?? null;
238
243
  }
239
244
  const gloss = getGloss(hint);
240
245
  await services.updateSession(session.id, { hints_used: session.hints_used + 1, streak: 0 });
241
246
  const updated = (await services.getById(session.id))!;
242
- await sendOrEditView(plugin, services, message, updated, [
247
+ return (await sendOrEditView(plugin, services, message, updated, [
243
248
  `💡 提示:可试 **${hint}**${gloss ? `(${gloss})` : ''}`,
244
249
  '(使用提示会清零连击)',
245
- ]);
246
- return null;
250
+ ])) ?? null;
247
251
  }
248
252
 
249
253
  if (choiceId === 'skip') {
@@ -253,15 +257,15 @@ export async function handleChoice(
253
257
  streak: 0,
254
258
  });
255
259
  const updated = (await services.getById(session.id))!;
256
- await sendOrEditView(plugin, services, message, updated, ['⏭️ 你选择跳过,机器人得一分。']);
257
- return null;
260
+ return (await sendOrEditView(plugin, services, message, updated, [
261
+ '⏭️ 你选择跳过,机器人得一分。',
262
+ ])) ?? null;
258
263
  }
259
264
 
260
265
  if (choiceId === 'quit') {
261
266
  await services.updateSession(session.id, { status: 'lost', bot_score: session.bot_score + 1 });
262
267
  const updated = (await services.getById(session.id))!;
263
- await sendOrEditView(plugin, services, message, updated, ['🏳️ 你认输了。']);
264
- return null;
268
+ return (await sendOrEditView(plugin, services, message, updated, ['🏳️ 你认输了。'])) ?? null;
265
269
  }
266
270
 
267
271
  return '未知操作。';
@@ -3,7 +3,7 @@
3
3
  * @see https://www.npmjs.com/package/chinese-idiom-chengyu
4
4
  */
5
5
  import { createRequire } from 'node:module';
6
- import { secureRandomItem } from '@zhin.js/game-shared';
6
+ import { secureRandomItem } from '@zhin.js/game-kit';
7
7
 
8
8
  const require = createRequire(import.meta.url);
9
9
 
package/src/index.ts CHANGED
@@ -1,30 +1,9 @@
1
- import { formatCompact, usePlugin, type DatabaseFeature } from 'zhin.js';
2
- import { registerModels } from './models.js';
3
- import { createServices, resolveGameDatabase, type SessionService } from './session-service.js';
4
- import { registerCommands, registerInteractive, registerTextMiddleware } from './commands.js';
5
- import { registerChainHub } from './hub-register.js';
6
-
7
- const plugin = usePlugin();
8
- const { logger, useContext, addSchedule } = plugin;
9
-
10
- registerModels(plugin);
11
-
12
- let services: SessionService | null = null;
13
-
14
- useContext('database', (dbFeature: DatabaseFeature) => {
15
- services = createServices(resolveGameDatabase(dbFeature));
16
- logger.debug(formatCompact({ 模块: '成语接龙', 数据模型: '已就绪' }));
17
- });
18
-
19
- registerChainHub(() => services);
20
- registerCommands(plugin, () => services);
21
- registerInteractive(plugin, () => services);
22
- registerTextMiddleware(plugin, () => services);
23
-
24
- addSchedule({ kind: 'solar', cron: '0 */15 * * * *' }, async () => {
25
- if (!services) return;
26
- const n = await services.abortStale(45 * 60 * 1000);
27
- if (n > 0) logger.debug(formatCompact({ 成语接龙: '清理超时局', count: n }));
28
- });
29
-
30
- logger.debug(formatCompact({ 模块: '成语接龙', 状态: '已加载' }));
1
+ export { CHAIN_HELP } from './chain-command.js';
2
+ export { gameServicesToken, resolveGameServices } from './runtime-store.js';
3
+ export { createInMemoryChainDb, mountChainMemoryServices } from './memory-db.js';
4
+
5
+ /**
6
+ * 已由 plugin.ts 接线:commands/ 命令、middlewares/ 文本与选项中间件、
7
+ * 游戏大厅注册(registerRuntimeGame)、过期会话 cron(scheduleHostToken)、
8
+ * host DatabaseHost(databaseHostToken)。仍未接:interactive 按钮回调。
9
+ */
@@ -0,0 +1,22 @@
1
+ import { createHostGameDb, createInMemoryGameDb } from '@zhin.js/game-kit';
2
+ import type { DatabaseHost } from '@zhin.js/plugin-runtime';
3
+ import { defineHostTables } from './models.js';
4
+ import { createServices, type ChainDatabase, type SessionService } from './session-service.js';
5
+
6
+ const CHAIN_TABLES = ['idiom_chain_sessions'] as const;
7
+
8
+ /** In-memory idiom_chain_sessions for Plugin Runtime slice-2 (no DatabaseFeature). */
9
+ export function createInMemoryChainDb(): ChainDatabase {
10
+ return createInMemoryGameDb(CHAIN_TABLES) as unknown as ChainDatabase;
11
+ }
12
+
13
+ export function mountChainMemoryServices(): SessionService {
14
+ const services = createServices(createInMemoryChainDb());
15
+ return services;
16
+ }
17
+
18
+ export function mountChainHostServices(host: DatabaseHost): SessionService {
19
+ defineHostTables(host);
20
+ const services = createServices(createHostGameDb(host, CHAIN_TABLES) as unknown as ChainDatabase);
21
+ return services;
22
+ }
package/src/models.ts CHANGED
@@ -1,9 +1,9 @@
1
- import type { Models, Plugin } from 'zhin.js';
1
+ import type { Models } from '@zhin.js/core';
2
2
  import type { MatchMode } from './engine.js';
3
3
 
4
4
  export type ChainSessionStatus = 'active' | 'won' | 'lost' | 'aborted';
5
5
 
6
- declare module 'zhin.js' {
6
+ declare module '@zhin.js/core' {
7
7
  interface Models {
8
8
  idiom_chain_sessions: {
9
9
  id: string;
@@ -35,8 +35,11 @@ declare module 'zhin.js' {
35
35
 
36
36
  export type ChainSessionRow = Models['idiom_chain_sessions'];
37
37
 
38
- export function registerModels(plugin: Plugin): void {
39
- plugin.defineModel('idiom_chain_sessions', {
38
+
39
+ export function defineHostTables(
40
+ db: { define: (name: string, definition: Record<string, unknown>) => void },
41
+ ): void {
42
+ db.define('idiom_chain_sessions', {
40
43
  id: { type: 'text', primary: true },
41
44
  adapter: { type: 'text', nullable: false },
42
45
  endpoint: { type: 'text', nullable: false },
@@ -0,0 +1,9 @@
1
+ import { createToken } from '@zhin.js/plugin-runtime';
2
+ import type { SessionService } from './session-service.js';
3
+
4
+ /** Owner-scoped service consumed by discovered commands and middleware. */
5
+ export const gameServicesToken = createToken<SessionService>('zhin.game.idiom-chain.services');
6
+
7
+ export function resolveGameServices(context: { use<T>(token: typeof gameServicesToken): T }): SessionService {
8
+ return context.use(gameServicesToken) as SessionService;
9
+ }
@@ -1,5 +1,5 @@
1
- import type { Database, DatabaseFeature, Message, Models, RelatedModel } from 'zhin.js';
2
- import { channelKey, generateSessionId, boardMessageMatches } from '@zhin.js/game-shared';
1
+ import type { Database, Message, Models, RelatedModel } from '@zhin.js/core';
2
+ import { channelKey, generateSessionId, boardMessageMatches } from '@zhin.js/game-kit';
3
3
  import type { MatchMode } from './engine.js';
4
4
  import type { ChainSessionRow } from './models.js';
5
5
 
@@ -110,6 +110,3 @@ export function createServices(db: ChainDatabase): SessionService {
110
110
  return new SessionService(db);
111
111
  }
112
112
 
113
- export function resolveGameDatabase(feature: DatabaseFeature): ChainDatabase {
114
- return feature.db;
115
- }
package/src/view.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { buildChoiceKeyboard } from '@zhin.js/game-shared';
2
- import type { SendContent } from 'zhin.js';
1
+ import { buildChoiceKeyboard } from '@zhin.js/game-kit';
2
+ import type { SendContent } from '@zhin.js/core';
3
3
  import type { ChainSessionRow } from './models.js';
4
4
  import { CHAIN_PREFIX, getGloss, idiomCount, modeLabel, promptLine } from './engine.js';
5
5
 
@@ -1 +0,0 @@
1
- {"version":3,"file":"chain-command.d.ts","sourceRoot":"","sources":["../src/chain-command.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAK/C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,eAAO,MAAM,UAAU,QAWX,CAAC;AAEb,wBAAsB,eAAe,CACnC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,cAAc,EACxB,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,EACrB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CA+B7B"}
package/lib/commands.d.ts DELETED
@@ -1,6 +0,0 @@
1
- import { type Plugin } from 'zhin.js';
2
- import type { SessionService } from './session-service.js';
3
- export declare function registerCommands(plugin: Plugin, getServices: () => SessionService | null): void;
4
- export declare function registerInteractive(plugin: Plugin, getServices: () => SessionService | null): void;
5
- export declare function registerTextMiddleware(plugin: Plugin, getServices: () => SessionService | null): void;
6
- //# sourceMappingURL=commands.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiD,KAAK,MAAM,EAAE,MAAM,SAAS,CAAC;AAYrF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAsB3D,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,cAAc,GAAG,IAAI,GAAG,IAAI,CAG/F;AAgBD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,cAAc,GAAG,IAAI,GAAG,IAAI,CAUlG;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,cAAc,GAAG,IAAI,GAAG,IAAI,CAkDrG"}
package/lib/commands.js DELETED
@@ -1,95 +0,0 @@
1
- import { MessageCommand, getActionFromMessage } from 'zhin.js';
2
- import { buildChoiceFallbackMap, channelKey, normalizeChainAction, parseChoicePayload, registerGameTextMiddleware, resolveGameChoice, resolveGameTextPayload, } from '@zhin.js/game-shared';
3
- import { CHAIN_PREFIX, handleChoice, processIdiomText } from './game-flow.js';
4
- import { runChainCommand } from './chain-command.js';
5
- const VALID_CHOICES = ['hint', 'skip', 'quit', 'restart'];
6
- function registerPattern(plugin, pattern, desc, getServices) {
7
- plugin.addCommand(new MessageCommand(pattern)
8
- .desc(desc)
9
- .action(async (message, result) => {
10
- const services = getServices();
11
- if (!services)
12
- return '成语接龙需要启用 database 配置。';
13
- const raw = result.params.action ?? '';
14
- return runChainCommand(plugin, services, message, normalizeChainAction(raw));
15
- }));
16
- }
17
- export function registerCommands(plugin, getServices) {
18
- registerPattern(plugin, '/chain [action:word]', '成语接龙(chain)', getServices);
19
- registerPattern(plugin, '/接龙 [action:word]', '成语接龙(中文)', getServices);
20
- }
21
- async function resolveChoice(message, services) {
22
- return resolveGameChoice({
23
- message,
24
- gamePrefix: CHAIN_PREFIX,
25
- validChoiceIds: VALID_CHOICES,
26
- getById: (id) => services.getById(id),
27
- getActiveForUser: (ch, uid) => services.getActiveForUser(ch, uid),
28
- getByBoardMessageId: (mid) => services.getActiveByBoardMessageId(mid),
29
- });
30
- }
31
- export function registerInteractive(plugin, getServices) {
32
- plugin.registerInteractiveHandler(`${CHAIN_PREFIX}:`, async (message) => {
33
- const services = getServices();
34
- if (!services)
35
- return false;
36
- const choice = await resolveChoice(message, services);
37
- if (!choice)
38
- return false;
39
- const err = await handleChoice(plugin, services, message, choice.sessionId, choice.choiceId);
40
- if (err)
41
- await message.$reply?.(err);
42
- return true;
43
- });
44
- }
45
- export function registerTextMiddleware(plugin, getServices) {
46
- registerGameTextMiddleware(plugin, async (message, next) => {
47
- const services = getServices();
48
- if (!services)
49
- return next();
50
- const action = getActionFromMessage(message);
51
- if (action?.payload.startsWith(`${CHAIN_PREFIX}:`))
52
- return next();
53
- const raw = message.$raw?.trim() ?? '';
54
- if (!raw)
55
- return next();
56
- const ch = channelKey(message);
57
- const payloadFromText = resolveGameTextPayload(raw);
58
- if (payloadFromText?.startsWith(`${CHAIN_PREFIX}:`)) {
59
- const parsed = parseChoicePayload(payloadFromText, CHAIN_PREFIX);
60
- if (parsed) {
61
- const session = await services.getById(parsed.sessionId);
62
- if (session?.channel_key === ch) {
63
- const err = await handleChoice(plugin, services, message, parsed.sessionId, parsed.choiceId);
64
- if (err)
65
- await message.$reply?.(err);
66
- return;
67
- }
68
- }
69
- }
70
- const session = await services.getActiveForUser(ch, message.$sender.id);
71
- if (!session)
72
- return next();
73
- if (session.status === 'active') {
74
- const map = buildChoiceFallbackMap(CHAIN_PREFIX, session.id, [
75
- { id: 'hint', label: '提示' },
76
- { id: 'skip', label: '跳过' },
77
- { id: 'quit', label: '认输' },
78
- ]);
79
- const payload = resolveGameTextPayload(raw, map);
80
- const parsed = payload ? parseChoicePayload(payload, CHAIN_PREFIX) : null;
81
- if (parsed?.sessionId === session.id) {
82
- const err = await handleChoice(plugin, services, message, session.id, parsed.choiceId);
83
- if (err)
84
- await message.$reply?.(err);
85
- return;
86
- }
87
- }
88
- if (/^[\u4e00-\u9fff]{2,8}$/.test(raw)) {
89
- await processIdiomText(plugin, services, message, raw);
90
- return;
91
- }
92
- return next();
93
- }, 'idiom-chain:text');
94
- }
95
- //# sourceMappingURL=commands.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"commands.js","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,YAAY,UAAU,CAAC;AAEpC,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACjF,OAAO,EACL,UAAU,EACV,cAAc,EACd,YAAY,EACZ,QAAQ,EACR,QAAQ,EACR,kBAAkB,EAClB,SAAS,EACT,YAAY,EACZ,YAAY,EACZ,gBAAgB,EAChB,aAAa,EACb,mBAAmB,EACnB,UAAU,GACX,MAAM,qBAAqB,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"game-flow.d.ts","sourceRoot":"","sources":["../src/game-flow.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACxD,OAAO,EACL,YAAY,EAUZ,KAAK,SAAS,EACf,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAa,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAOtE,wBAAsB,cAAc,CAClC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,cAAc,EACxB,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,EACrB,OAAO,EAAE,eAAe,EACxB,UAAU,GAAE,MAAM,EAAO,GACxB,OAAO,CAAC,IAAI,CAAC,CAqBf;AAmCD,wBAAsB,SAAS,CAC7B,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,cAAc,EACxB,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,EACrB,SAAS,GAAE,SAAoB,GAC9B,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CA0B7B;AAED,wBAAsB,YAAY,CAChC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,cAAc,EACxB,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,GACpB,OAAO,CAAC,MAAM,CAAC,CAQjB;AAED,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,cAAc,EACxB,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,EACrB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAiDxB;AAED,wBAAsB,YAAY,CAChC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,cAAc,EACxB,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,EACrB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA0ExB;AAED,OAAO,EAAE,YAAY,EAAE,CAAC"}
@@ -1,5 +0,0 @@
1
- import { CHAIN_HELP } from './chain-command.js';
2
- import type { SessionService } from './session-service.js';
3
- export declare function registerChainHub(getServices: () => SessionService | null): () => void;
4
- export { CHAIN_HELP };
5
- //# sourceMappingURL=hub-register.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"hub-register.d.ts","sourceRoot":"","sources":["../src/hub-register.ts"],"names":[],"mappings":"AAGA,OAAO,EAAmB,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,cAAc,GAAG,IAAI,GAAG,MAAM,IAAI,CAuBrF;AAED,OAAO,EAAE,UAAU,EAAE,CAAC"}
@@ -1,31 +0,0 @@
1
- import { getPlugin } from 'zhin.js';
2
- import { ensureGameHubService } from '@zhin.js/game-shared';
3
- import { idiomCount } from './engine.js';
4
- import { runChainCommand, CHAIN_HELP } from './chain-command.js';
5
- export function registerChainHub(getServices) {
6
- const plugin = getPlugin();
7
- ensureGameHubService(plugin);
8
- return plugin.registerGame({
9
- id: 'chain',
10
- title: '成语接龙',
11
- icon: '📜',
12
- description: `四字成语接龙(同音/同字),词库 ${idiomCount()} 条`,
13
- commandPrefix: '/接龙',
14
- quickStart: 'start_pinyin',
15
- aliases: ['chain'],
16
- menus: [
17
- { id: 'start_pinyin', label: '🎮 同音接龙', style: 'primary' },
18
- { id: 'start_char', label: '📝 同字接龙' },
19
- { id: 'continue', label: '🔄 继续' },
20
- { id: 'help', label: '📖 玩法说明' },
21
- ],
22
- runAction: async (actionId, ctx) => {
23
- const services = getServices();
24
- if (!services)
25
- return '成语接龙需要启用 database 配置。';
26
- return runChainCommand(ctx.plugin, services, ctx.message, actionId);
27
- },
28
- });
29
- }
30
- export { CHAIN_HELP };
31
- //# sourceMappingURL=hub-register.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"hub-register.js","sourceRoot":"","sources":["../src/hub-register.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAGjE,MAAM,UAAU,gBAAgB,CAAC,WAAwC;IACvE,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAC7B,OAAO,MAAM,CAAC,YAAY,CAAC;QACzB,EAAE,EAAE,OAAO;QACX,KAAK,EAAE,MAAM;QACb,IAAI,EAAE,IAAI;QACV,WAAW,EAAE,oBAAoB,UAAU,EAAE,IAAI;QACjD,aAAa,EAAE,KAAK;QACpB,UAAU,EAAE,cAAc;QAC1B,OAAO,EAAE,CAAC,OAAO,CAAC;QAClB,KAAK,EAAE;YACL,EAAE,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE;YAC1D,EAAE,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,SAAS,EAAE;YACtC,EAAE,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE;YAClC,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE;SACjC;QACD,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,EAAE;YACjC,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAC;YAC/B,IAAI,CAAC,QAAQ;gBAAE,OAAO,uBAAuB,CAAC;YAC9C,OAAO,eAAe,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,GAAG,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QACtE,CAAC;KACF,CAAC,CAAC;AACL,CAAC;AAED,OAAO,EAAE,UAAU,EAAE,CAAC"}