@zhin.js/plugin-guess-number 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,17 +1,18 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { replyGameOperation, gamePlayerFromInput, normalizeGuessAction } from '@zhin.js/game-kit';
2
3
  import { defineCommand } from 'zhin.js/command';
3
- import { messageFromCommandInput, normalizeGuessAction } from '@zhin.js/game-kit';
4
4
  import { getGuessHelp, runGuessCommand } from "../../../lib/guess-command.js";
5
5
  import { resolveGameServices } from "../../../lib/runtime-store.js";
6
6
  export default defineCommand({
7
7
  description: 'Guess Number',
8
8
  params: { action: { type: 'string', default: '' } },
9
- async execute({ params, input, use, locale, formatCommand }) {
9
+ async execute({ params, input, use, locale, formatCommand, session }) {
10
10
  const action = normalizeGuessAction(String(params.action ?? ''));
11
11
  if (!action || action === 'help')
12
12
  return getGuessHelp({ locale, formatCommand });
13
13
  const services = resolveGameServices({ use });
14
- const message = messageFromCommandInput(input, { locale, formatCommand });
15
- return runGuessCommand(services, message, action);
14
+ const message = gamePlayerFromInput(input, { locale, formatCommand });
15
+ await replyGameOperation({ session: services, player: message, reply: session?.reply,
16
+ run: () => runGuessCommand(services, message, action) });
16
17
  },
17
18
  });
@@ -1,16 +1,17 @@
1
+ import { replyGameOperation, gamePlayerFromInput, normalizeGuessAction } from '@zhin.js/game-kit';
1
2
  import { defineCommand } from 'zhin.js/command';
2
- import { messageFromCommandInput, normalizeGuessAction } from '@zhin.js/game-kit';
3
3
  import { getGuessHelp, runGuessCommand } from '../../../src/guess-command.js';
4
4
  import { resolveGameServices } from '../../../src/runtime-store.js';
5
5
 
6
6
  export default defineCommand({
7
7
  description: 'Guess Number',
8
8
  params: { action: { type: 'string', default: '' } },
9
- async execute({ params, input, use, locale, formatCommand }) {
9
+ async execute({ params, input, use, locale, formatCommand, session }) {
10
10
  const action = normalizeGuessAction(String(params.action ?? ''));
11
11
  if (!action || action === 'help') return getGuessHelp({ locale, formatCommand });
12
12
  const services = resolveGameServices({ use });
13
- const message = messageFromCommandInput(input, { locale, formatCommand });
14
- return runGuessCommand(services, message, action);
13
+ const message = gamePlayerFromInput(input, { locale, formatCommand });
14
+ await replyGameOperation({ session: services, player: message, reply: session?.reply,
15
+ run: () => runGuessCommand(services, message, action) });
15
16
  },
16
17
  });
@@ -1,5 +1,5 @@
1
- import type { GameMessageLike, GameReply } from '@zhin.js/game-kit';
1
+ import { type GamePlayer, type GameReply } from '@zhin.js/game-kit';
2
2
  import type { SessionService } from './session-service.js';
3
- export declare function startGame(services: SessionService, message: GameMessageLike): Promise<GameReply>;
4
- export declare function processGuess(services: SessionService, message: GameMessageLike, value: number): Promise<GameReply | null>;
5
- export declare function handleGuessChoice(services: SessionService, message: GameMessageLike, sessionId: string, choiceId: string): Promise<GameReply>;
3
+ export declare function startGame(services: SessionService, message: GamePlayer): Promise<GameReply>;
4
+ export declare function processGuess(services: SessionService, message: GamePlayer, value: number): Promise<GameReply | null>;
5
+ export declare function handleGuessChoice(services: SessionService, message: GamePlayer, sessionId: string, choiceId: string): Promise<GameReply>;
package/lib/game-flow.js CHANGED
@@ -1,69 +1,76 @@
1
1
  import { createMessages, resolveLocale } from 'zhin.js/i18n';
2
+ import { channelKey } from '@zhin.js/game-kit';
2
3
  import { evaluateGuess, hintText, MAX, MIN } from './engine.js';
3
4
  import { buildGuessView } from './view.js';
4
5
  export async function startGame(services, message) {
5
- const ch = `${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`;
6
- const mine = await services.getActiveForUser(ch, message.$sender.id);
7
- const locale = resolveLocale(mine ? mine.locale : message.locale);
8
- if (mine) {
9
- return buildGuessView(mine, [messages(locale, 'text1')], message.$channel.type);
10
- }
11
- const session = await services.createSession(message);
12
- return buildGuessView(session, [], message.$channel.type);
6
+ return services.runInScope({ scope: channelKey(message), run: async () => {
7
+ const ch = channelKey(message);
8
+ const mine = await services.getActiveForUser(ch, message.sender.id);
9
+ const locale = resolveLocale(mine ? mine.locale : message.locale);
10
+ if (mine) {
11
+ return buildGuessView(mine, [messages(locale, 'text1')], message.target.kind);
12
+ }
13
+ const session = await services.createSession(message);
14
+ return buildGuessView(session, [], message.target.kind);
15
+ } });
13
16
  }
14
17
  export async function processGuess(services, message, value) {
15
- const ch = `${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`;
16
- const session = await services.getActiveForUser(ch, message.$sender.id);
17
- const locale = resolveLocale(session ? session.locale : message.locale);
18
- if (!session)
19
- return null;
20
- const result = evaluateGuess(session.secret, value);
21
- if (result === 'invalid') {
22
- return messages(locale, 'text2', { p0: MIN, p1: MAX });
23
- }
24
- const attempts = session.attempts + 1;
25
- if (result === 'win') {
26
- await services.updateSession(session.id, { attempts, status: 'won' });
27
- const updated = (await services.getById(session.id));
28
- return buildGuessView(updated, [messages(locale, 'text3', { p0: attempts })], message.$channel.type);
29
- }
30
- if (attempts >= session.max_attempts) {
31
- await services.updateSession(session.id, { attempts, status: 'lost' });
32
- const updated = (await services.getById(session.id));
33
- return buildGuessView(updated, [], message.$channel.type);
34
- }
35
- let rangeMin = session.range_min;
36
- let rangeMax = session.range_max;
37
- if (result === 'low')
38
- rangeMin = Math.max(rangeMin, value + 1);
39
- else
40
- rangeMax = Math.min(rangeMax, value - 1);
41
- await services.updateSession(session.id, { attempts, range_min: rangeMin, range_max: rangeMax });
42
- const updated = (await services.getById(session.id));
43
- return buildGuessView(updated, [hintText(result, rangeMin, rangeMax, locale)], message.$channel.type);
18
+ return services.runInScope({ scope: channelKey(message), run: async () => {
19
+ const ch = channelKey(message);
20
+ const session = await services.getActiveForUser(ch, message.sender.id);
21
+ const locale = resolveLocale(session ? session.locale : message.locale);
22
+ if (!session)
23
+ return null;
24
+ const result = evaluateGuess(session.secret, value);
25
+ if (result === 'invalid') {
26
+ return messages(locale, 'text2', { p0: MIN, p1: MAX });
27
+ }
28
+ const attempts = session.attempts + 1;
29
+ if (result === 'win') {
30
+ await services.updateSession(session.id, { attempts, status: 'won' });
31
+ const updated = (await services.getById(session.id));
32
+ return buildGuessView(updated, [messages(locale, 'text3', { p0: attempts })], message.target.kind);
33
+ }
34
+ if (attempts >= session.max_attempts) {
35
+ await services.updateSession(session.id, { attempts, status: 'lost' });
36
+ const updated = (await services.getById(session.id));
37
+ return buildGuessView(updated, [], message.target.kind);
38
+ }
39
+ let rangeMin = session.range_min;
40
+ let rangeMax = session.range_max;
41
+ if (result === 'low')
42
+ rangeMin = Math.max(rangeMin, value + 1);
43
+ else
44
+ rangeMax = Math.min(rangeMax, value - 1);
45
+ await services.updateSession(session.id, { attempts, range_min: rangeMin, range_max: rangeMax });
46
+ const updated = (await services.getById(session.id));
47
+ return buildGuessView(updated, [hintText(result, rangeMin, rangeMax, locale)], message.target.kind);
48
+ } });
44
49
  }
45
50
  export async function handleGuessChoice(services, message, sessionId, choiceId) {
46
- const session = await services.getById(sessionId);
47
- const locale = resolveLocale(session ? session.locale : message.locale);
48
- if (!session)
49
- return messages(locale, 'text4');
50
- if (session.channel_key
51
- !== `${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`) {
52
- return messages(locale, 'text5');
53
- }
54
- if (session.player_id !== message.$sender.id)
55
- return messages(locale, 'text6');
56
- if (choiceId === 'restart' && session.status !== 'active') {
57
- return startGame(services, message);
58
- }
59
- if (choiceId === 'quit' && session.status === 'active') {
60
- await services.updateSession(session.id, { status: 'aborted' });
61
- const updated = (await services.getById(session.id));
62
- return buildGuessView(updated, [messages(locale, 'text7')], message.$channel.type);
63
- }
64
- return session.status === 'active'
65
- ? messages(locale, 'text8')
66
- : buildGuessView(session, [], message.$channel.type);
51
+ return services.runInScope({ scope: channelKey(message), run: async () => {
52
+ const session = await services.getById(sessionId);
53
+ const locale = resolveLocale(session ? session.locale : message.locale);
54
+ if (!session)
55
+ return messages(locale, 'text4');
56
+ if (session.channel_key
57
+ !== channelKey(message)) {
58
+ return messages(locale, 'text5');
59
+ }
60
+ if (session.player_id !== message.sender.id)
61
+ return messages(locale, 'text6');
62
+ if (choiceId === 'restart' && session.status !== 'active') {
63
+ return startGame(services, message);
64
+ }
65
+ if (choiceId === 'quit' && session.status === 'active') {
66
+ await services.updateSession(session.id, { status: 'aborted' });
67
+ const updated = (await services.getById(session.id));
68
+ return buildGuessView(updated, [messages(locale, 'text7')], message.target.kind);
69
+ }
70
+ return session.status === 'active'
71
+ ? messages(locale, 'text8')
72
+ : buildGuessView(session, [], message.target.kind);
73
+ } });
67
74
  }
68
75
  const messages = createMessages({
69
76
  text1: { 'zh-CN': "你已有进行中的一局,请继续猜数字。", en: "You already have an active game. Keep guessing." },
@@ -1 +1 @@
1
- {"version":3,"file":"game-flow.js","sourceRoot":"","sources":["../src/game-flow.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"game-flow.js","sourceRoot":"","sources":["../src/game-flow.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import { type GameMessageLike, type GameReply } from '@zhin.js/game-kit';
1
+ import { type GamePlayer, type GameReply } from '@zhin.js/game-kit';
2
2
  import type { SessionService } from './session-service.js';
3
- export declare function getGuessHelp(message?: Pick<GameMessageLike, 'locale' | 'formatCommand'>): string;
3
+ export declare function getGuessHelp(message?: Pick<GamePlayer, 'locale' | 'formatCommand'>): string;
4
4
  export declare const GUESS_HELP: string;
5
- export declare function runGuessCommand(services: SessionService, message: GameMessageLike, action: string): Promise<GameReply>;
5
+ export declare function runGuessCommand(services: SessionService, message: GamePlayer, action: string): Promise<GameReply>;
@@ -3,34 +3,36 @@ import { createMessages } from 'zhin.js/i18n';
3
3
  import { startGame } from './game-flow.js';
4
4
  import { buildGuessView } from './view.js';
5
5
  export function getGuessHelp(message = {}) {
6
- return formatGameHelp(message, { 'zh-CN': "猜数字(1~100,7 次机会)", en: "Guess the Number (1–100, seven guesses)" }, 'guess', ['start', 'quit'], { 'zh-CN': "进行中直接回复范围内的整数。", en: "During a game, reply with an integer in the range." });
6
+ return formatGameHelp({ presentation: message, title: { 'zh-CN': "猜数字(1~100,7 次机会)", en: "Guess the Number (1–100, seven guesses)" }, commandPath: 'guess', actions: ['start', 'quit'], footer: { 'zh-CN': "进行中直接回复范围内的整数。", en: "During a game, reply with an integer in the range." } });
7
7
  }
8
8
  export const GUESS_HELP = getGuessHelp();
9
9
  export async function runGuessCommand(services, message, action) {
10
- const ch = channelKey(message);
11
- const userId = message.$sender.id;
12
- if (!action || action === 'help') {
13
- const active = await services.getActiveForUser(ch, userId);
14
- const lines = [getGuessHelp(message), ''];
15
- if (active) {
16
- return buildGuessView(active, [getGuessHelp(message)], message.$channel.type);
17
- }
18
- else {
19
- lines.push(gameCommandFeedback(message, 'noGame'), gameCommandHint(message, 'guess'));
20
- }
21
- return lines.join('\n');
22
- }
23
- if (action === 'start')
24
- return startGame(services, message);
25
- if (action === 'quit') {
26
- const row = await services.getActiveForUser(ch, userId);
27
- if (!row)
28
- return gameCommandFeedback(message, 'noGame');
29
- await services.updateSession(row.id, { status: 'aborted' });
30
- const updated = (await services.getById(row.id));
31
- return buildGuessView(updated, [messages(row.locale ?? undefined, 'text1')], message.$channel.type);
32
- }
33
- return gameCommandFeedback(message, 'unknown', action) + '\n\n' + getGuessHelp(message);
10
+ return services.runInScope({ scope: channelKey(message), run: async () => {
11
+ const ch = channelKey(message);
12
+ const userId = message.sender.id;
13
+ if (!action || action === 'help') {
14
+ const active = await services.getActiveForUser(ch, userId);
15
+ const lines = [getGuessHelp(message), ''];
16
+ if (active) {
17
+ return buildGuessView(active, [getGuessHelp(message)], message.target.kind);
18
+ }
19
+ else {
20
+ lines.push(gameCommandFeedback(message, 'noGame'), gameCommandHint(message, 'guess'));
21
+ }
22
+ return lines.join('\n');
23
+ }
24
+ if (action === 'start')
25
+ return startGame(services, message);
26
+ if (action === 'quit') {
27
+ const row = await services.getActiveForUser(ch, userId);
28
+ if (!row)
29
+ return gameCommandFeedback(message, 'noGame');
30
+ await services.updateSession(row.id, { status: 'aborted' });
31
+ const updated = (await services.getById(row.id));
32
+ return buildGuessView(updated, [messages(row.locale ?? undefined, 'text1')], message.target.kind);
33
+ }
34
+ return gameCommandFeedback(message, 'unknown', action) + '\n\n' + getGuessHelp(message);
35
+ } });
34
36
  }
35
37
  const messages = createMessages({
36
38
  text1: { 'zh-CN': "已放弃本局。", en: "You quit this game." },
@@ -1 +1 @@
1
- {"version":3,"file":"guess-command.js","sourceRoot":"","sources":["../src/guess-command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,eAAe,EAAE,UAAU,EAAwC,MAAM,mBAAmB,CAAC;AAC3I,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3C,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAE3C,MAAM,UAAU,YAAY,CAAC,UAA6D,EAAE;IAC1F,OAAO,cAAc,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,kBAAkB,EAAE,EAAE,EAAE,yCAAyC,EAAE,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,EACvI,EAAE,OAAO,EAAE,gBAAgB,EAAE,EAAE,EAAE,oDAAoD,EAAE,CAAC,CAAC;AAC7F,CAAC;AAED,MAAM,CAAC,MAAM,UAAU,GAAG,YAAY,EAAE,CAAC;AAEzC,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,QAAwB,EACxB,OAAwB,EACxB,MAAc;IAEd,MAAM,EAAE,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC;IAC/B,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;IAElC,IAAI,CAAC,MAAM,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QACjC,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,gBAAgB,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;QAC3D,MAAM,KAAK,GAAG,CAAC,YAAY,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC;QAC1C,IAAI,MAAM,EAAE,CAAC;YACX,OAAO,cAAc,CAAC,MAAM,EAAE,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAChF,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,IAAI,CAAC,mBAAmB,CAAC,OAAO,EAAE,QAAQ,CAAC,EAAE,eAAe,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;QACxF,CAAC;QACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAED,IAAI,MAAM,KAAK,OAAO;QAAE,OAAO,SAAS,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAE5D,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QACtB,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,gBAAgB,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;QACxD,IAAI,CAAC,GAAG;YAAE,OAAO,mBAAmB,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QACxD,MAAM,QAAQ,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;QAC5D,MAAM,OAAO,GAAG,CAAC,MAAM,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAE,CAAC;QAClD,OAAO,cAAc,CAAC,OAAO,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,IAAI,SAAS,EAAE,OAAO,CAAC,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACtG,CAAC;IAED,OAAO,mBAAmB,CAAC,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC,GAAG,MAAM,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;AAC1F,CAAC;AAED,MAAM,QAAQ,GAAG,cAAc,CAAC;IAC9B,KAAK,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,qBAAqB,EAAE;CACxD,CAAC,CAAC"}
1
+ {"version":3,"file":"guess-command.js","sourceRoot":"","sources":["../src/guess-command.ts"],"names":[],"mappings":"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"}
@@ -1,9 +1,9 @@
1
1
  import type { Database, Models } from '@zhin.js/core';
2
- import { BaseSessionService, type GameMessageLike } from '@zhin.js/game-kit';
2
+ import { BaseSessionService, type GamePlayer } from '@zhin.js/game-kit';
3
3
  import type { GuessSessionRow } from './models.js';
4
4
  export type GuessDatabase = Database<unknown, Models, string>;
5
5
  export declare class SessionService extends BaseSessionService<GuessSessionRow> {
6
6
  constructor(db: GuessDatabase);
7
- createSession(message: GameMessageLike): Promise<GuessSessionRow>;
7
+ createSession(message: GamePlayer): Promise<GuessSessionRow>;
8
8
  }
9
9
  export declare function createServices(db: GuessDatabase): SessionService;
@@ -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
  import { MAX, MAX_ATTEMPTS, MIN, newSecret } from './engine.js';
3
3
  export class SessionService extends BaseSessionService {
4
4
  constructor(db) {
@@ -27,13 +27,8 @@ export class SessionService extends BaseSessionService {
27
27
  const row = {
28
28
  id: generateSessionId(),
29
29
  locale: message.locale ?? 'zh-CN',
30
- adapter: String(message.$adapter),
31
- endpoint: message.$endpoint,
32
- channel_type: message.$channel.type,
33
- channel_id: message.$channel.id,
34
- channel_key: channelKey(message),
35
- player_id: message.$sender.id,
36
- player_name: message.$sender.name?.trim() || message.$sender.id,
30
+ player_id: message.sender.id,
31
+ player_name: message.sender.name?.trim() || message.sender.id,
37
32
  secret: newSecret(),
38
33
  range_min: MIN,
39
34
  range_max: MAX,
@@ -43,7 +38,7 @@ export class SessionService extends BaseSessionService {
43
38
  updated_at: now,
44
39
  created_at: now,
45
40
  };
46
- return this.createRow(row);
41
+ return this.createRow({ target: message.target, fields: row });
47
42
  }
48
43
  }
49
44
  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;AAC3B,OAAO,EAAE,GAAG,EAAE,YAAY,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAKhE,MAAM,OAAO,cAAe,SAAQ,kBAAmC;IACrE,YAAY,EAAiB;QAC3B,KAAK,CAAC,EAAqD,EAAE;YAC3D,MAAM,EAAE,OAAO;YACf,KAAK,EAAE,gBAAgB;YACvB,UAAU,EAAE,CAAC,WAAW,CAAC;YACzB,eAAe,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,KAAK,KAAK;gBACpD,CAAC,CAAC,CAAC;wBACC,MAAM,EAAE,OAAO,CAAC,SAAS;wBACzB,QAAQ,EAAE,OAAO,CAAC,WAAW;wBAC7B,MAAM,EAAE,KAAK;wBACb,KAAK,EAAE,IAAI,CAAC,GAAG,CACb,CAAC,EACD,OAAO,CAAC,YAAY,GAAG,OAAO,CAAC,QAAQ,GAAG,CAAC,CAC5C,GAAG,EAAE;qBACP,CAAC;gBACJ,CAAC,CAAC,OAAO,CAAC,MAAM,KAAK,MAAM;oBACzB,CAAC,CAAC,CAAC;4BACC,MAAM,EAAE,OAAO,CAAC,SAAS;4BACzB,QAAQ,EAAE,OAAO,CAAC,WAAW;4BAC7B,MAAM,EAAE,MAAM;yBACf,CAAC;oBACJ,CAAC,CAAC,EAAE;SACT,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,OAAwB;QAC1C,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,MAAM,EAAE,SAAS,EAAE;YACnB,SAAS,EAAE,GAAG;YACd,SAAS,EAAE,GAAG;YACd,QAAQ,EAAE,CAAC;YACX,YAAY,EAAE,YAAY;YAC1B,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;AAC3B,OAAO,EAAE,GAAG,EAAE,YAAY,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAKhE,MAAM,OAAO,cAAe,SAAQ,kBAAmC;IACrE,YAAY,EAAiB;QAC3B,KAAK,CAAC,EAAqD,EAAE;YAC3D,MAAM,EAAE,OAAO;YACf,KAAK,EAAE,gBAAgB;YACvB,UAAU,EAAE,CAAC,WAAW,CAAC;YACzB,eAAe,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,KAAK,KAAK;gBACpD,CAAC,CAAC,CAAC;wBACC,MAAM,EAAE,OAAO,CAAC,SAAS;wBACzB,QAAQ,EAAE,OAAO,CAAC,WAAW;wBAC7B,MAAM,EAAE,KAAK;wBACb,KAAK,EAAE,IAAI,CAAC,GAAG,CACb,CAAC,EACD,OAAO,CAAC,YAAY,GAAG,OAAO,CAAC,QAAQ,GAAG,CAAC,CAC5C,GAAG,EAAE;qBACP,CAAC;gBACJ,CAAC,CAAC,OAAO,CAAC,MAAM,KAAK,MAAM;oBACzB,CAAC,CAAC,CAAC;4BACC,MAAM,EAAE,OAAO,CAAC,SAAS;4BACzB,QAAQ,EAAE,OAAO,CAAC,WAAW;4BAC7B,MAAM,EAAE,MAAM;yBACf,CAAC;oBACJ,CAAC,CAAC,EAAE;SACT,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,OAAmB;QACrC,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,MAAM,EAAE,SAAS,EAAE;YACnB,SAAS,EAAE,GAAG;YACd,SAAS,EAAE,GAAG;YACd,QAAQ,EAAE,CAAC;YACX,YAAY,EAAE,YAAY;YAC1B,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, isGuessAction, messageFromCommandInput, normalizeGuessAction, } from '@zhin.js/game-kit';
2
+ import { replyGameOperation, defineGameCommandAliasMiddleware, isGuessAction, gamePlayerFromInput, normalizeGuessAction, } from '@zhin.js/game-kit';
3
3
  import { getGuessHelp, runGuessCommand } from "../../lib/guess-command.js";
4
4
  import { resolveGameServices } from "../../lib/runtime-store.js";
5
5
  export default defineGameCommandAliasMiddleware({
6
6
  aliases: ['猜数', 'guess'],
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 = normalizeGuessAction(String(action ?? ''));
12
- if (normalized === 'help')
13
- return getGuessHelp(message);
11
+ if (normalized === 'help') {
12
+ await context.input.$reply(getGuessHelp(presentation));
13
+ return true;
14
+ }
14
15
  // action 无法识别:放行给后续中间件,避免劫持普通聊天
15
16
  if (!isGuessAction(normalized))
16
- return null;
17
+ return false;
18
+ const message = gamePlayerFromInput(input, presentation);
17
19
  const services = resolveGameServices(context);
18
- return runGuessCommand(services, message, normalized);
20
+ await replyGameOperation({ session: services, player: message, reply: content => context.input.$reply(content),
21
+ run: () => runGuessCommand(services, message, normalized) });
22
+ return true;
19
23
  },
20
24
  });
@@ -1,7 +1,8 @@
1
1
  import {
2
+ replyGameOperation,
2
3
  defineGameCommandAliasMiddleware,
3
4
  isGuessAction,
4
- messageFromCommandInput,
5
+ gamePlayerFromInput,
5
6
  normalizeGuessAction,
6
7
  } from '@zhin.js/game-kit';
7
8
  import { getGuessHelp, runGuessCommand } from '../../src/guess-command.js';
@@ -10,14 +11,17 @@ import { resolveGameServices } from '../../src/runtime-store.js';
10
11
  export default defineGameCommandAliasMiddleware({
11
12
  aliases: ['猜数', 'guess'],
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 = normalizeGuessAction(String(action ?? ''));
17
- if (normalized === 'help') return getGuessHelp(message);
18
+ if (normalized === 'help') { await context.input.$reply(getGuessHelp(presentation)); return true; }
18
19
  // action 无法识别:放行给后续中间件,避免劫持普通聊天
19
- if (!isGuessAction(normalized)) return null;
20
+ if (!isGuessAction(normalized)) return false;
21
+ const message = gamePlayerFromInput(input, presentation);
20
22
  const services = resolveGameServices(context);
21
- return runGuessCommand(services, message, normalized);
23
+ await replyGameOperation({ session: services, player: message, reply: content => context.input.$reply(content),
24
+ run: () => runGuessCommand(services, message, normalized) });
25
+ return true;
22
26
  },
23
27
  });
@@ -1,12 +1,16 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { replyGameOperation, gamePlayerFromInput, parseChoicePayload, resolveGameTextPayload } from '@zhin.js/game-kit';
2
3
  import { defineMiddleware } from 'zhin.js/middleware';
3
- import { messageFromCommandInput, parseChoicePayload, resolveGameTextPayload, } from '@zhin.js/game-kit';
4
4
  import { handleGuessChoice } from "../../lib/game-flow.js";
5
5
  import { resolveGameServices } from "../../lib/runtime-store.js";
6
6
  import { GUESS_PREFIX } from "../../lib/view.js";
7
7
  export default defineMiddleware({
8
8
  target: 'inbound',
9
9
  async handle(context, next) {
10
+ if (!context.input.sender?.id) {
11
+ await next();
12
+ return;
13
+ }
10
14
  const raw = context.input.content?.trim() ?? '';
11
15
  const payload = resolveGameTextPayload(raw);
12
16
  if (!payload?.startsWith(`${GUESS_PREFIX}:`)) {
@@ -19,7 +23,8 @@ export default defineMiddleware({
19
23
  return;
20
24
  }
21
25
  const services = resolveGameServices(context);
22
- const reply = await handleGuessChoice(services, messageFromCommandInput(context.input), parsed.sessionId, parsed.choiceId);
23
- await context.input.$reply(reply);
26
+ const message = gamePlayerFromInput(context.input);
27
+ await replyGameOperation({ session: services, player: message, reply: content => context.input.$reply(content),
28
+ run: () => handleGuessChoice(services, message, parsed.sessionId, parsed.choiceId) });
24
29
  },
25
30
  });
@@ -1,10 +1,6 @@
1
+ import { replyGameOperation, gamePlayerFromInput, parseChoicePayload, resolveGameTextPayload } from '@zhin.js/game-kit';
1
2
  import type { Message } from '@zhin.js/core/runtime';
2
3
  import { defineMiddleware } from 'zhin.js/middleware';
3
- import {
4
- messageFromCommandInput,
5
- parseChoicePayload,
6
- resolveGameTextPayload,
7
- } from '@zhin.js/game-kit';
8
4
  import { handleGuessChoice } from '../../src/game-flow.js';
9
5
  import { resolveGameServices } from '../../src/runtime-store.js';
10
6
  import { GUESS_PREFIX } from '../../src/view.js';
@@ -12,6 +8,7 @@ import { GUESS_PREFIX } from '../../src/view.js';
12
8
  export default defineMiddleware<Message>({
13
9
  target: 'inbound',
14
10
  async handle(context, next) {
11
+ if (!context.input.sender?.id) { await next(); return; }
15
12
  const raw = context.input.content?.trim() ?? '';
16
13
  const payload = resolveGameTextPayload(raw);
17
14
  if (!payload?.startsWith(`${GUESS_PREFIX}:`)) {
@@ -24,12 +21,13 @@ export default defineMiddleware<Message>({
24
21
  return;
25
22
  }
26
23
  const services = resolveGameServices(context);
27
- const reply = await handleGuessChoice(
24
+ const message = gamePlayerFromInput(context.input);
25
+ await replyGameOperation({ session: services, player: message, reply: content => context.input.$reply(content),
26
+ run: () => handleGuessChoice(
28
27
  services,
29
- messageFromCommandInput(context.input),
28
+ message,
30
29
  parsed.sessionId,
31
30
  parsed.choiceId,
32
- );
33
- await context.input.$reply(reply);
31
+ ) });
34
32
  },
35
33
  });
@@ -1,23 +1,24 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { replyGameOperation, gamePlayerFromInput } from '@zhin.js/game-kit';
2
3
  import { defineMiddleware } from 'zhin.js/middleware';
3
- import { messageFromCommandInput } from '@zhin.js/game-kit';
4
4
  import { resolveGameServices } from "../../lib/runtime-store.js";
5
5
  import { processGuess } from "../../lib/game-flow.js";
6
6
  export default defineMiddleware({
7
7
  target: 'inbound',
8
8
  async handle(context, next) {
9
+ if (!context.input.sender?.id) {
10
+ await next();
11
+ return;
12
+ }
9
13
  const raw = context.input.content?.trim() ?? '';
10
14
  if (!/^(\d+)$/.test(raw)) {
11
15
  await next();
12
16
  return;
13
17
  }
14
18
  const services = resolveGameServices(context);
15
- const message = messageFromCommandInput(context.input);
16
- const reply = await processGuess(services, message, Number(raw));
17
- if (reply) {
18
- await context.input.$reply(reply);
19
- return;
20
- }
19
+ const message = gamePlayerFromInput(context.input);
20
+ await replyGameOperation({ session: services, player: message, reply: content => context.input.$reply(content),
21
+ run: () => processGuess(services, message, Number(raw)) });
21
22
  await next();
22
23
  },
23
24
  });
@@ -1,24 +1,22 @@
1
+ import { replyGameOperation, gamePlayerFromInput } from '@zhin.js/game-kit';
1
2
  import { defineMiddleware } from 'zhin.js/middleware';
2
3
  import type { Message } from '@zhin.js/core/runtime';
3
- import { messageFromCommandInput } from '@zhin.js/game-kit';
4
4
  import { resolveGameServices } from '../../src/runtime-store.js';
5
5
  import { processGuess } from '../../src/game-flow.js';
6
6
 
7
7
  export default defineMiddleware<Message>({
8
8
  target: 'inbound',
9
9
  async handle(context, next) {
10
+ if (!context.input.sender?.id) { await next(); return; }
10
11
  const raw = context.input.content?.trim() ?? '';
11
12
  if (!/^(\d+)$/.test(raw)) {
12
13
  await next();
13
14
  return;
14
15
  }
15
16
  const services = resolveGameServices(context);
16
- const message = messageFromCommandInput(context.input);
17
- const reply = await processGuess(services, message, Number(raw));
18
- if (reply) {
19
- await context.input.$reply(reply);
20
- return;
21
- }
17
+ const message = gamePlayerFromInput(context.input);
18
+ await replyGameOperation({ session: services, player: message, reply: content => context.input.$reply(content),
19
+ run: () => processGuess(services, message, Number(raw)) });
22
20
  await next();
23
21
  },
24
22
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/plugin-guess-number",
3
- "version": "1.1.18",
3
+ "version": "1.1.19",
4
4
  "description": "Zhin.js 猜数字 — 1~100 七步之内猜中 (Plugin Runtime)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -23,13 +23,13 @@
23
23
  ],
24
24
  "license": "MIT",
25
25
  "dependencies": {
26
- "@zhin.js/core": "1.1.53",
27
- "@zhin.js/game-kit": "1.1.18"
26
+ "@zhin.js/core": "1.1.54",
27
+ "@zhin.js/game-kit": "1.1.19"
28
28
  },
29
29
  "devDependencies": {
30
30
  "typescript": "^6.0.3",
31
31
  "vitest": "^4.1.10",
32
- "zhin.js": "1.1.18"
32
+ "zhin.js": "1.1.19"
33
33
  },
34
34
  "repository": {
35
35
  "type": "git",
@@ -66,9 +66,9 @@
66
66
  "plugins": []
67
67
  },
68
68
  "peerDependencies": {
69
- "@zhin.js/command": "^1.1.11",
70
- "@zhin.js/middleware": "^1.1.10",
71
- "zhin.js": "^1.1.18"
69
+ "@zhin.js/command": "^1.1.12",
70
+ "@zhin.js/middleware": "^1.1.11",
71
+ "zhin.js": "^1.1.19"
72
72
  },
73
73
  "peerDependenciesMeta": {
74
74
  "@zhin.js/command": {
package/src/game-flow.ts CHANGED
@@ -1,101 +1,107 @@
1
1
  import { createMessages, resolveLocale } from 'zhin.js/i18n';
2
- import type { GameMessageLike, GameReply } from '@zhin.js/game-kit';
2
+ import { channelKey, type GamePlayer, type GameReply } from '@zhin.js/game-kit';
3
3
  import { evaluateGuess, hintText, MAX, MIN } from './engine.js';
4
4
  import type { SessionService } from './session-service.js';
5
5
  import { buildGuessView } from './view.js';
6
6
 
7
7
  export async function startGame(
8
8
  services: SessionService,
9
- message: GameMessageLike,
9
+ message: GamePlayer,
10
10
  ): Promise<GameReply> {
11
- const ch = `${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`;
12
- const mine = await services.getActiveForUser(ch, message.$sender.id);
13
- const locale = resolveLocale(mine ? mine.locale : message.locale);
14
- if (mine) {
15
- return buildGuessView(
16
- mine,
17
- [messages(locale, 'text1')],
18
- message.$channel.type,
19
- );
20
- }
21
- const session = await services.createSession(message);
22
- return buildGuessView(session, [], message.$channel.type);
11
+ return services.runInScope({ scope: channelKey(message), run: async () => {
12
+ const ch = channelKey(message);
13
+ const mine = await services.getActiveForUser(ch, message.sender.id);
14
+ const locale = resolveLocale(mine ? mine.locale : message.locale);
15
+ if (mine) {
16
+ return buildGuessView(
17
+ mine,
18
+ [messages(locale, 'text1')],
19
+ message.target.kind,
20
+ );
21
+ }
22
+ const session = await services.createSession(message);
23
+ return buildGuessView(session, [], message.target.kind);
24
+ } });
23
25
  }
24
26
 
25
27
  export async function processGuess(
26
28
  services: SessionService,
27
- message: GameMessageLike,
29
+ message: GamePlayer,
28
30
  value: number,
29
31
  ): Promise<GameReply | null> {
30
- const ch = `${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`;
31
- const session = await services.getActiveForUser(ch, message.$sender.id);
32
- const locale = resolveLocale(session ? session.locale : message.locale);
33
- if (!session) return null;
32
+ return services.runInScope({ scope: channelKey(message), run: async () => {
33
+ const ch = channelKey(message);
34
+ const session = await services.getActiveForUser(ch, message.sender.id);
35
+ const locale = resolveLocale(session ? session.locale : message.locale);
36
+ if (!session) return null;
34
37
 
35
- const result = evaluateGuess(session.secret, value);
36
- if (result === 'invalid') {
37
- return messages(locale, 'text2', { p0: MIN, p1: MAX });
38
- }
38
+ const result = evaluateGuess(session.secret, value);
39
+ if (result === 'invalid') {
40
+ return messages(locale, 'text2', { p0: MIN, p1: MAX });
41
+ }
39
42
 
40
- const attempts = session.attempts + 1;
43
+ const attempts = session.attempts + 1;
41
44
 
42
- if (result === 'win') {
43
- await services.updateSession(session.id, { attempts, status: 'won' });
44
- const updated = (await services.getById(session.id))!;
45
- return buildGuessView(
46
- updated,
47
- [messages(locale, 'text3', { p0: attempts })],
48
- message.$channel.type,
49
- );
50
- }
45
+ if (result === 'win') {
46
+ await services.updateSession(session.id, { attempts, status: 'won' });
47
+ const updated = (await services.getById(session.id))!;
48
+ return buildGuessView(
49
+ updated,
50
+ [messages(locale, 'text3', { p0: attempts })],
51
+ message.target.kind,
52
+ );
53
+ }
51
54
 
52
- if (attempts >= session.max_attempts) {
53
- await services.updateSession(session.id, { attempts, status: 'lost' });
54
- const updated = (await services.getById(session.id))!;
55
- return buildGuessView(updated, [], message.$channel.type);
56
- }
55
+ if (attempts >= session.max_attempts) {
56
+ await services.updateSession(session.id, { attempts, status: 'lost' });
57
+ const updated = (await services.getById(session.id))!;
58
+ return buildGuessView(updated, [], message.target.kind);
59
+ }
57
60
 
58
- let rangeMin = session.range_min;
59
- let rangeMax = session.range_max;
60
- if (result === 'low') rangeMin = Math.max(rangeMin, value + 1);
61
- else rangeMax = Math.min(rangeMax, value - 1);
61
+ let rangeMin = session.range_min;
62
+ let rangeMax = session.range_max;
63
+ if (result === 'low') rangeMin = Math.max(rangeMin, value + 1);
64
+ else rangeMax = Math.min(rangeMax, value - 1);
62
65
 
63
- await services.updateSession(session.id, { attempts, range_min: rangeMin, range_max: rangeMax });
66
+ await services.updateSession(session.id, { attempts, range_min: rangeMin, range_max: rangeMax });
64
67
 
65
- const updated = (await services.getById(session.id))!;
66
- return buildGuessView(
67
- updated,
68
- [hintText(result, rangeMin, rangeMax, locale)],
69
- message.$channel.type,
70
- );
68
+ const updated = (await services.getById(session.id))!;
69
+ return buildGuessView(
70
+ updated,
71
+ [hintText(result, rangeMin, rangeMax, locale)],
72
+ message.target.kind,
73
+ );
74
+ } });
71
75
  }
72
76
 
73
77
  export async function handleGuessChoice(
74
78
  services: SessionService,
75
- message: GameMessageLike,
79
+ message: GamePlayer,
76
80
  sessionId: string,
77
81
  choiceId: string,
78
82
  ): Promise<GameReply> {
79
- const session = await services.getById(sessionId);
80
- const locale = resolveLocale(session ? session.locale : message.locale);
81
- if (!session) return messages(locale, 'text4');
82
- if (session.channel_key
83
- !== `${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`) {
84
- return messages(locale, 'text5');
85
- }
86
- if (session.player_id !== message.$sender.id) return messages(locale, 'text6');
83
+ return services.runInScope({ scope: channelKey(message), run: async () => {
84
+ const session = await services.getById(sessionId);
85
+ const locale = resolveLocale(session ? session.locale : message.locale);
86
+ if (!session) return messages(locale, 'text4');
87
+ if (session.channel_key
88
+ !== channelKey(message)) {
89
+ return messages(locale, 'text5');
90
+ }
91
+ if (session.player_id !== message.sender.id) return messages(locale, 'text6');
87
92
 
88
- if (choiceId === 'restart' && session.status !== 'active') {
89
- return startGame(services, message);
90
- }
91
- if (choiceId === 'quit' && session.status === 'active') {
92
- await services.updateSession(session.id, { status: 'aborted' });
93
- const updated = (await services.getById(session.id))!;
94
- return buildGuessView(updated, [messages(locale, 'text7')], message.$channel.type);
95
- }
96
- return session.status === 'active'
97
- ? messages(locale, 'text8')
98
- : buildGuessView(session, [], message.$channel.type);
93
+ if (choiceId === 'restart' && session.status !== 'active') {
94
+ return startGame(services, message);
95
+ }
96
+ if (choiceId === 'quit' && session.status === 'active') {
97
+ await services.updateSession(session.id, { status: 'aborted' });
98
+ const updated = (await services.getById(session.id))!;
99
+ return buildGuessView(updated, [messages(locale, 'text7')], message.target.kind);
100
+ }
101
+ return session.status === 'active'
102
+ ? messages(locale, 'text8')
103
+ : buildGuessView(session, [], message.target.kind);
104
+ } });
99
105
  }
100
106
 
101
107
  const messages = createMessages({
@@ -1,46 +1,47 @@
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 { startGame } from './game-flow.js';
4
4
  import type { SessionService } from './session-service.js';
5
5
  import { buildGuessView } from './view.js';
6
6
 
7
- export function getGuessHelp(message: Pick<GameMessageLike, 'locale' | 'formatCommand'> = {}): string {
8
- return formatGameHelp(message, { 'zh-CN': "猜数字(1~100,7 次机会)", en: "Guess the Number (1–100, seven guesses)" }, 'guess', ['start', 'quit'],
9
- { 'zh-CN': "进行中直接回复范围内的整数。", en: "During a game, reply with an integer in the range." });
7
+ export function getGuessHelp(message: Pick<GamePlayer, 'locale' | 'formatCommand'> = {}): string {
8
+ return formatGameHelp({ presentation: message, title: { 'zh-CN': "猜数字(1~100,7 次机会)", en: "Guess the Number (1–100, seven guesses)" }, commandPath: 'guess', actions: ['start', 'quit'], footer: { 'zh-CN': "进行中直接回复范围内的整数。", en: "During a game, reply with an integer in the range." } });
10
9
  }
11
10
 
12
11
  export const GUESS_HELP = getGuessHelp();
13
12
 
14
13
  export async function runGuessCommand(
15
14
  services: SessionService,
16
- message: GameMessageLike,
15
+ message: GamePlayer,
17
16
  action: string,
18
17
  ): Promise<GameReply> {
19
- const ch = channelKey(message);
20
- const userId = message.$sender.id;
21
-
22
- if (!action || action === 'help') {
23
- const active = await services.getActiveForUser(ch, userId);
24
- const lines = [getGuessHelp(message), ''];
25
- if (active) {
26
- return buildGuessView(active, [getGuessHelp(message)], message.$channel.type);
27
- } else {
28
- lines.push(gameCommandFeedback(message, 'noGame'), gameCommandHint(message, 'guess'));
18
+ return services.runInScope({ scope: channelKey(message), run: async () => {
19
+ const ch = channelKey(message);
20
+ const userId = message.sender.id;
21
+
22
+ if (!action || action === 'help') {
23
+ const active = await services.getActiveForUser(ch, userId);
24
+ const lines = [getGuessHelp(message), ''];
25
+ if (active) {
26
+ return buildGuessView(active, [getGuessHelp(message)], message.target.kind);
27
+ } else {
28
+ lines.push(gameCommandFeedback(message, 'noGame'), gameCommandHint(message, 'guess'));
29
+ }
30
+ return lines.join('\n');
29
31
  }
30
- return lines.join('\n');
31
- }
32
32
 
33
- if (action === 'start') return startGame(services, message);
33
+ if (action === 'start') return startGame(services, message);
34
34
 
35
- if (action === 'quit') {
36
- const row = await services.getActiveForUser(ch, userId);
37
- if (!row) return gameCommandFeedback(message, 'noGame');
38
- await services.updateSession(row.id, { status: 'aborted' });
39
- const updated = (await services.getById(row.id))!;
40
- return buildGuessView(updated, [messages(row.locale ?? undefined, 'text1')], message.$channel.type);
41
- }
35
+ if (action === 'quit') {
36
+ const row = await services.getActiveForUser(ch, userId);
37
+ if (!row) return gameCommandFeedback(message, 'noGame');
38
+ await services.updateSession(row.id, { status: 'aborted' });
39
+ const updated = (await services.getById(row.id))!;
40
+ return buildGuessView(updated, [messages(row.locale ?? undefined, 'text1')], message.target.kind);
41
+ }
42
42
 
43
- return gameCommandFeedback(message, 'unknown', action) + '\n\n' + getGuessHelp(message);
43
+ return gameCommandFeedback(message, 'unknown', action) + '\n\n' + getGuessHelp(message);
44
+ } });
44
45
  }
45
46
 
46
47
  const messages = createMessages({
@@ -1,9 +1,8 @@
1
1
  import type { Database, Models } from '@zhin.js/core';
2
2
  import {
3
3
  BaseSessionService,
4
- channelKey,
5
4
  generateSessionId,
6
- type GameMessageLike,
5
+ type GamePlayer,
7
6
  type GameSessionDatabase,
8
7
  } from '@zhin.js/game-kit';
9
8
  import { MAX, MAX_ATTEMPTS, MIN, newSecret } from './engine.js';
@@ -37,18 +36,13 @@ export class SessionService extends BaseSessionService<GuessSessionRow> {
37
36
  });
38
37
  }
39
38
 
40
- async createSession(message: GameMessageLike): Promise<GuessSessionRow> {
39
+ async createSession(message: GamePlayer): Promise<GuessSessionRow> {
41
40
  const now = Date.now();
42
- const row: GuessSessionRow = {
41
+ const row: Omit<GuessSessionRow, 'channel_key' | 'adapter' | 'endpoint' | 'channel_type' | 'channel_id'> = {
43
42
  id: generateSessionId(),
44
43
  locale: message.locale ?? 'zh-CN',
45
- adapter: String(message.$adapter),
46
- endpoint: message.$endpoint,
47
- channel_type: message.$channel.type,
48
- channel_id: message.$channel.id,
49
- channel_key: channelKey(message),
50
- player_id: message.$sender.id,
51
- player_name: message.$sender.name?.trim() || message.$sender.id,
44
+ player_id: message.sender.id,
45
+ player_name: message.sender.name?.trim() || message.sender.id,
52
46
  secret: newSecret(),
53
47
  range_min: MIN,
54
48
  range_max: MAX,
@@ -58,7 +52,7 @@ export class SessionService extends BaseSessionService<GuessSessionRow> {
58
52
  updated_at: now,
59
53
  created_at: now,
60
54
  };
61
- return this.createRow(row);
55
+ return this.createRow({ target: message.target, fields: row });
62
56
  }
63
57
  }
64
58