@zhin.js/game-kit 2.0.1 → 2.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 (116) hide show
  1. package/README.md +73 -16
  2. package/lib/base-session-service.d.ts +54 -0
  3. package/lib/base-session-service.d.ts.map +1 -0
  4. package/lib/base-session-service.js +139 -0
  5. package/lib/base-session-service.js.map +1 -0
  6. package/lib/command-alias.d.ts +3 -1
  7. package/lib/command-alias.d.ts.map +1 -1
  8. package/lib/command-alias.js.map +1 -1
  9. package/lib/command-message.d.ts +3 -0
  10. package/lib/command-message.d.ts.map +1 -1
  11. package/lib/command-message.js +0 -6
  12. package/lib/command-message.js.map +1 -1
  13. package/lib/define-game-plugin.d.ts +32 -0
  14. package/lib/define-game-plugin.d.ts.map +1 -0
  15. package/lib/define-game-plugin.js +87 -0
  16. package/lib/define-game-plugin.js.map +1 -0
  17. package/lib/deterministic-random.d.ts +16 -0
  18. package/lib/deterministic-random.d.ts.map +1 -0
  19. package/lib/deterministic-random.js +68 -0
  20. package/lib/deterministic-random.js.map +1 -0
  21. package/lib/game-events.d.ts +53 -0
  22. package/lib/game-events.d.ts.map +1 -0
  23. package/lib/game-events.js +26 -0
  24. package/lib/game-events.js.map +1 -0
  25. package/lib/game-interactive.d.ts +0 -22
  26. package/lib/game-interactive.d.ts.map +1 -1
  27. package/lib/game-interactive.js +1 -57
  28. package/lib/game-interactive.js.map +1 -1
  29. package/lib/game-records.d.ts +0 -1
  30. package/lib/game-records.d.ts.map +1 -1
  31. package/lib/game-records.js +0 -60
  32. package/lib/game-records.js.map +1 -1
  33. package/lib/game-session.d.ts +0 -59
  34. package/lib/game-session.d.ts.map +1 -1
  35. package/lib/game-session.js +0 -29
  36. package/lib/game-session.js.map +1 -1
  37. package/lib/index.d.ts +6 -9
  38. package/lib/index.d.ts.map +1 -1
  39. package/lib/index.js +6 -9
  40. package/lib/index.js.map +1 -1
  41. package/lib/memory-db.d.ts +6 -0
  42. package/lib/memory-db.d.ts.map +1 -1
  43. package/lib/memory-db.js +8 -0
  44. package/lib/memory-db.js.map +1 -1
  45. package/lib/session-coordinator.d.ts +27 -0
  46. package/lib/session-coordinator.d.ts.map +1 -0
  47. package/lib/session-coordinator.js +57 -0
  48. package/lib/session-coordinator.js.map +1 -0
  49. package/lib/versioned-session-service.d.ts +48 -0
  50. package/lib/versioned-session-service.d.ts.map +1 -0
  51. package/lib/versioned-session-service.js +116 -0
  52. package/lib/versioned-session-service.js.map +1 -0
  53. package/package.json +7 -6
  54. package/src/base-session-service.ts +210 -0
  55. package/src/command-alias.ts +3 -1
  56. package/src/command-message.ts +4 -0
  57. package/src/define-game-plugin.ts +162 -0
  58. package/src/deterministic-random.ts +81 -0
  59. package/src/game-events.ts +75 -0
  60. package/src/game-interactive.ts +1 -80
  61. package/src/game-records.ts +0 -73
  62. package/src/game-session.ts +0 -83
  63. package/src/index.ts +6 -9
  64. package/src/memory-db.ts +12 -0
  65. package/src/session-coordinator.ts +74 -0
  66. package/src/versioned-session-service.ts +162 -0
  67. package/lib/command-help.d.ts +0 -19
  68. package/lib/command-help.d.ts.map +0 -1
  69. package/lib/command-help.js +0 -91
  70. package/lib/command-help.js.map +0 -1
  71. package/lib/game-commands.d.ts +0 -3
  72. package/lib/game-commands.d.ts.map +0 -1
  73. package/lib/game-commands.js +0 -5
  74. package/lib/game-commands.js.map +0 -1
  75. package/lib/game-hub-feature.d.ts +0 -58
  76. package/lib/game-hub-feature.d.ts.map +0 -1
  77. package/lib/game-hub-feature.js +0 -102
  78. package/lib/game-hub-feature.js.map +0 -1
  79. package/lib/game-hub-flow.d.ts +0 -9
  80. package/lib/game-hub-flow.d.ts.map +0 -1
  81. package/lib/game-hub-flow.js +0 -90
  82. package/lib/game-hub-flow.js.map +0 -1
  83. package/lib/game-hub-menu-context.d.ts +0 -25
  84. package/lib/game-hub-menu-context.d.ts.map +0 -1
  85. package/lib/game-hub-menu-context.js +0 -64
  86. package/lib/game-hub-menu-context.js.map +0 -1
  87. package/lib/game-hub-menu.d.ts +0 -37
  88. package/lib/game-hub-menu.d.ts.map +0 -1
  89. package/lib/game-hub-menu.js +0 -162
  90. package/lib/game-hub-menu.js.map +0 -1
  91. package/lib/game-hub-mount.d.ts +0 -13
  92. package/lib/game-hub-mount.d.ts.map +0 -1
  93. package/lib/game-hub-mount.js +0 -89
  94. package/lib/game-hub-mount.js.map +0 -1
  95. package/lib/game-middleware.d.ts +0 -6
  96. package/lib/game-middleware.d.ts.map +0 -1
  97. package/lib/game-middleware.js +0 -9
  98. package/lib/game-middleware.js.map +0 -1
  99. package/lib/game-onboarding.d.ts +0 -8
  100. package/lib/game-onboarding.d.ts.map +0 -1
  101. package/lib/game-onboarding.js +0 -67
  102. package/lib/game-onboarding.js.map +0 -1
  103. package/lib/help-card.d.ts +0 -12
  104. package/lib/help-card.d.ts.map +0 -1
  105. package/lib/help-card.js +0 -129
  106. package/lib/help-card.js.map +0 -1
  107. package/src/command-help.ts +0 -117
  108. package/src/game-commands.ts +0 -4
  109. package/src/game-hub-feature.ts +0 -154
  110. package/src/game-hub-flow.ts +0 -114
  111. package/src/game-hub-menu-context.ts +0 -94
  112. package/src/game-hub-menu.ts +0 -223
  113. package/src/game-hub-mount.ts +0 -147
  114. package/src/game-middleware.ts +0 -14
  115. package/src/game-onboarding.ts +0 -71
  116. package/src/help-card.ts +0 -188
@@ -0,0 +1,162 @@
1
+ import {
2
+ BaseSessionService,
3
+ type BaseGameSessionRow,
4
+ type BaseSessionServiceOptions,
5
+ type GameSessionDatabase,
6
+ } from './base-session-service.js';
7
+
8
+ const DEFAULT_ACTION_HISTORY_LIMIT = 128;
9
+
10
+ export interface VersionedGameSessionRow extends BaseGameSessionRow {
11
+ revision: number;
12
+ /** JSON string containing the bounded action-id history. */
13
+ processed_actions: string;
14
+ }
15
+
16
+ export interface SessionMutationOptions<TRow extends VersionedGameSessionRow> {
17
+ readonly actionId: string;
18
+ readonly expectedRevision?: number;
19
+ readonly apply:
20
+ | Partial<TRow>
21
+ | ((session: Readonly<TRow>) =>
22
+ Partial<TRow> | Promise<Partial<TRow>>);
23
+ }
24
+
25
+ export type SessionMutationResult<TRow extends VersionedGameSessionRow> =
26
+ | { readonly kind: 'applied'; readonly session: TRow }
27
+ | { readonly kind: 'duplicate'; readonly session: TRow };
28
+
29
+ export class SessionNotFoundError extends Error {
30
+ constructor(readonly sessionId: string) {
31
+ super(`Game session "${sessionId}" does not exist.`);
32
+ this.name = 'SessionNotFoundError';
33
+ }
34
+ }
35
+
36
+ export class SessionRevisionConflictError extends Error {
37
+ constructor(
38
+ readonly sessionId: string,
39
+ readonly expectedRevision: number,
40
+ readonly actualRevision: number,
41
+ ) {
42
+ super(
43
+ `Game session "${sessionId}" changed: expected revision `
44
+ + `${expectedRevision}, received ${actualRevision}.`,
45
+ );
46
+ this.name = 'SessionRevisionConflictError';
47
+ }
48
+ }
49
+
50
+ /**
51
+ * A keyed promise queue. It prevents lost updates inside one runtime while the
52
+ * persisted revision check protects deployments that share a database.
53
+ */
54
+ export class SessionActionGate {
55
+ readonly #tails = new Map<string, Promise<void>>();
56
+
57
+ async run<TResult>(
58
+ sessionId: string,
59
+ action: () => TResult | Promise<TResult>,
60
+ ): Promise<TResult> {
61
+ const previous = this.#tails.get(sessionId) ?? Promise.resolve();
62
+ let release = (): void => undefined;
63
+ const pending = new Promise<void>((resolve) => {
64
+ release = resolve;
65
+ });
66
+ const tail = previous.catch(() => undefined).then(() => pending);
67
+ this.#tails.set(sessionId, tail);
68
+ await previous.catch(() => undefined);
69
+ try {
70
+ return await action();
71
+ } finally {
72
+ release();
73
+ if (this.#tails.get(sessionId) === tail) this.#tails.delete(sessionId);
74
+ }
75
+ }
76
+
77
+ get pendingSessions(): number {
78
+ return this.#tails.size;
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Adds serialized, optimistic and idempotent mutations to BaseSessionService.
84
+ * Action ids are intentionally persisted so adapter retries remain harmless
85
+ * after a process restart.
86
+ */
87
+ export abstract class VersionedSessionService<
88
+ TRow extends VersionedGameSessionRow,
89
+ > extends BaseSessionService<TRow> {
90
+ readonly #gate = new SessionActionGate();
91
+ readonly #actionHistoryLimit: number;
92
+
93
+ protected constructor(
94
+ database: GameSessionDatabase<TRow>,
95
+ options: BaseSessionServiceOptions<TRow>,
96
+ ) {
97
+ super(database, options);
98
+ this.#actionHistoryLimit = DEFAULT_ACTION_HISTORY_LIMIT;
99
+ }
100
+
101
+ mutateSession(
102
+ id: string,
103
+ options: SessionMutationOptions<TRow>,
104
+ ): Promise<SessionMutationResult<TRow>> {
105
+ if (!options.actionId.trim()) {
106
+ return Promise.reject(new TypeError('actionId must not be empty'));
107
+ }
108
+ return this.#gate.run(id, async () => {
109
+ const previous = await this.getById(id);
110
+ if (!previous) throw new SessionNotFoundError(id);
111
+ const processedActions = parseActionHistory(previous.processed_actions);
112
+ if (processedActions.includes(options.actionId)) {
113
+ return { kind: 'duplicate', session: previous };
114
+ }
115
+ if (options.expectedRevision !== undefined
116
+ && options.expectedRevision !== previous.revision) {
117
+ throw new SessionRevisionConflictError(
118
+ id,
119
+ options.expectedRevision,
120
+ previous.revision,
121
+ );
122
+ }
123
+
124
+ const patch = typeof options.apply === 'function'
125
+ ? await options.apply(previous)
126
+ : options.apply;
127
+ const revision = previous.revision + 1;
128
+ const nextActions = [
129
+ ...processedActions,
130
+ options.actionId,
131
+ ].slice(-this.#actionHistoryLimit);
132
+ const session = await this.commitSession(
133
+ previous,
134
+ {
135
+ ...patch,
136
+ revision,
137
+ processed_actions: JSON.stringify(nextActions),
138
+ } as Partial<TRow>,
139
+ { id, revision: previous.revision },
140
+ );
141
+ if (!session
142
+ || session.revision !== revision
143
+ || !parseActionHistory(session.processed_actions).includes(options.actionId)) {
144
+ const actual = session?.revision
145
+ ?? (await this.getById(id))?.revision
146
+ ?? previous.revision;
147
+ throw new SessionRevisionConflictError(id, previous.revision, actual);
148
+ }
149
+ return { kind: 'applied', session };
150
+ });
151
+ }
152
+ }
153
+
154
+ function parseActionHistory(value: string): string[] {
155
+ try {
156
+ const parsed: unknown = JSON.parse(value);
157
+ if (!Array.isArray(parsed)) return [];
158
+ return parsed.filter((item): item is string => typeof item === 'string');
159
+ } catch {
160
+ return [];
161
+ }
162
+ }
@@ -1,19 +0,0 @@
1
- import type { Message, MessageCommand, Plugin } from '@zhin.js/core';
2
- import type { RegisteredGame } from './game-hub-feature.js';
3
- export interface CommandHelpSource {
4
- pattern: string;
5
- helpInfo: {
6
- desc: string[];
7
- usage?: string[];
8
- examples?: string[];
9
- };
10
- }
11
- export declare function commandToHelpSource(cmd: Pick<MessageCommand, 'pattern' | 'helpInfo'>): CommandHelpSource;
12
- /** 按 MessageCommand.permit 与当前消息上下文过滤(与 handle 一致) */
13
- export declare function filterHelpCommands(commands: readonly MessageCommand[], message: Message, plugin: Plugin): Promise<CommandHelpSource[]>;
14
- /** 从 command 服务收集应在 /帮助 中展示的命令(去重、排除已在游戏大厅列出的项) */
15
- export declare function collectSupplementaryCommandHelp(commands: readonly CommandHelpSource[], games: readonly RegisteredGame[]): CommandHelpSource[];
16
- export declare function formatSupplementaryCommandsHelp(commands: readonly CommandHelpSource[], games: readonly RegisteredGame[]): string;
17
- /** 大厅入口行:从已注册命令的 .desc() 生成 */
18
- export declare function formatHubOpenCommandLines(commands: readonly CommandHelpSource[] | undefined): string[];
19
- //# sourceMappingURL=command-help.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"command-help.d.ts","sourceRoot":"","sources":["../src/command-help.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACrE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE;QACR,IAAI,EAAE,MAAM,EAAE,CAAC;QACf,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;QACjB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;KACrB,CAAC;CACH;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,IAAI,CAAC,cAAc,EAAE,SAAS,GAAG,UAAU,CAAC,GAAG,iBAAiB,CAExG;AAED,sDAAsD;AACtD,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE,SAAS,cAAc,EAAE,EACnC,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAQ9B;AA+CD,mDAAmD;AACnD,wBAAgB,+BAA+B,CAC7C,QAAQ,EAAE,SAAS,iBAAiB,EAAE,EACtC,KAAK,EAAE,SAAS,cAAc,EAAE,GAC/B,iBAAiB,EAAE,CAWrB;AAED,wBAAgB,+BAA+B,CAC7C,QAAQ,EAAE,SAAS,iBAAiB,EAAE,EACtC,KAAK,EAAE,SAAS,cAAc,EAAE,GAC/B,MAAM,CAKR;AAED,+BAA+B;AAC/B,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,SAAS,iBAAiB,EAAE,GAAG,SAAS,GACjD,MAAM,EAAE,CAUV"}
@@ -1,91 +0,0 @@
1
- export function commandToHelpSource(cmd) {
2
- return { pattern: cmd.pattern, helpInfo: cmd.helpInfo };
3
- }
4
- /** 按 MessageCommand.permit 与当前消息上下文过滤(与 handle 一致) */
5
- export async function filterHelpCommands(commands, message, plugin) {
6
- const out = [];
7
- for (const cmd of commands) {
8
- if (await cmd.checkPermits(message, plugin)) {
9
- out.push(commandToHelpSource(cmd));
10
- }
11
- }
12
- return out;
13
- }
14
- function commandLeadingToken(pattern) {
15
- return pattern.split(/\s/)[0] ?? '';
16
- }
17
- function normalizeToken(token) {
18
- return token.replace(/^\//, '').toLowerCase();
19
- }
20
- /** 不在「其他命令」中重复展示的 pattern(大厅入口 / 帮助自身) */
21
- const HELP_EXCLUDE_EXACT = new Set([
22
- '/帮助',
23
- '/help',
24
- '帮助',
25
- '/游戏 帮助',
26
- '/游戏',
27
- '/game',
28
- '游戏',
29
- 'game',
30
- ]);
31
- function isHubListedGameCommand(pattern, games) {
32
- const lead = normalizeToken(commandLeadingToken(pattern));
33
- return games.some((g) => normalizeToken(g.commandPrefix) === lead);
34
- }
35
- function comparePatterns(a, b) {
36
- const leadDiff = commandLeadingToken(b).length - commandLeadingToken(a).length;
37
- if (leadDiff !== 0)
38
- return leadDiff;
39
- return b.length - a.length;
40
- }
41
- function formatPatternDisplay(pattern) {
42
- return `\`${pattern}\``;
43
- }
44
- function formatCommandHelpLine(cmd) {
45
- const [label = '', detail = ''] = cmd.helpInfo.desc;
46
- const display = formatPatternDisplay(cmd.pattern);
47
- const summary = detail || label || cmd.pattern;
48
- if (label && label !== commandLeadingToken(cmd.pattern) && !cmd.pattern.startsWith(label)) {
49
- return `• ${display}(${label})— ${summary}`;
50
- }
51
- return `• ${display} — ${summary}`;
52
- }
53
- /** 从 command 服务收集应在 /帮助 中展示的命令(去重、排除已在游戏大厅列出的项) */
54
- export function collectSupplementaryCommandHelp(commands, games) {
55
- const seen = new Set();
56
- const out = [];
57
- for (const cmd of [...commands].sort((a, b) => comparePatterns(a.pattern, b.pattern))) {
58
- if (HELP_EXCLUDE_EXACT.has(cmd.pattern))
59
- continue;
60
- if (isHubListedGameCommand(cmd.pattern, games))
61
- continue;
62
- if (seen.has(cmd.pattern))
63
- continue;
64
- seen.add(cmd.pattern);
65
- out.push(cmd);
66
- }
67
- return out;
68
- }
69
- export function formatSupplementaryCommandsHelp(commands, games) {
70
- const items = collectSupplementaryCommandHelp(commands, games);
71
- if (!items.length)
72
- return '';
73
- const lines = items.map(formatCommandHelpLine);
74
- return ['**其他命令**', '', ...lines].join('\n');
75
- }
76
- /** 大厅入口行:从已注册命令的 .desc() 生成 */
77
- export function formatHubOpenCommandLines(commands) {
78
- if (!commands?.length)
79
- return [];
80
- const lines = [];
81
- for (const pattern of ['/游戏', '/game']) {
82
- const cmd = commands.find((c) => c.pattern === pattern);
83
- if (!cmd)
84
- continue;
85
- const desc = cmd.helpInfo.desc[0] ?? cmd.helpInfo.desc[1] ?? '';
86
- if (desc)
87
- lines.push(`${pattern} — ${desc}`);
88
- }
89
- return lines;
90
- }
91
- //# sourceMappingURL=command-help.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"command-help.js","sourceRoot":"","sources":["../src/command-help.ts"],"names":[],"mappings":"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"}
@@ -1,3 +0,0 @@
1
- /** 展示用:`/猜拳` */
2
- export declare function slashCommandPrefix(name: string): string;
3
- //# sourceMappingURL=game-commands.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"game-commands.d.ts","sourceRoot":"","sources":["../src/game-commands.ts"],"names":[],"mappings":"AAAA,gBAAgB;AAChB,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEvD"}
@@ -1,5 +0,0 @@
1
- /** 展示用:`/猜拳` */
2
- export function slashCommandPrefix(name) {
3
- return name.startsWith('/') ? name : `/${name}`;
4
- }
5
- //# sourceMappingURL=game-commands.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"game-commands.js","sourceRoot":"","sources":["../src/game-commands.ts"],"names":[],"mappings":"AAAA,gBAAgB;AAChB,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;AAClD,CAAC"}
@@ -1,58 +0,0 @@
1
- import { Feature, type FeatureJSON, type Message, type Plugin, type PluginLike } from '@zhin.js/core';
2
- export interface GameHubContext {
3
- plugin: Plugin;
4
- message: Message;
5
- }
6
- export interface GameMenuAction {
7
- id: string;
8
- label: string;
9
- style?: 'primary' | 'danger' | 'secondary';
10
- groupOnly?: boolean;
11
- privateOnly?: boolean;
12
- }
13
- export interface RegisteredGame {
14
- id: string;
15
- title: string;
16
- icon: string;
17
- description: string;
18
- commandPrefix: string;
19
- quickStart?: string;
20
- aliases?: string[];
21
- menus: GameMenuAction[];
22
- runAction: (actionId: string, ctx: GameHubContext) => Promise<string | undefined | void>;
23
- }
24
- export interface GameHubContextExtensions {
25
- registerGame(game: RegisteredGame): () => void;
26
- }
27
- declare module '@zhin.js/core' {
28
- namespace Plugin {
29
- interface Extensions extends GameHubContextExtensions {
30
- }
31
- interface Contexts {
32
- game: GameHubFeature;
33
- }
34
- }
35
- }
36
- export declare class GameHubFeature extends Feature<RegisteredGame> {
37
- readonly name: "game";
38
- readonly icon = "Gamepad2";
39
- readonly desc = "\u6E38\u620F";
40
- private readonly byId;
41
- private hubDisposers;
42
- register(game: RegisteredGame, pluginName: string): () => void;
43
- getGame(id: string): RegisteredGame | undefined;
44
- remove(item: RegisteredGame, pluginName?: string): boolean;
45
- toJSON(pluginName?: string): FeatureJSON;
46
- get extensions(): {
47
- registerGame: (game: RegisteredGame) => () => void;
48
- };
49
- mounted(plugin: PluginLike): void;
50
- dispose(): void;
51
- }
52
- /**
53
- * 在 root 上注册 `game` 服务(幂等)。游戏插件初始化时调用一次即可。
54
- */
55
- export declare function ensureGameHubService(plugin: Plugin): GameHubFeature;
56
- export declare function getRegisteredGames(): readonly RegisteredGame[];
57
- export declare function getRegisteredGame(id: string): RegisteredGame | undefined;
58
- //# sourceMappingURL=game-hub-feature.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"game-hub-feature.d.ts","sourceRoot":"","sources":["../src/game-hub-feature.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAa,KAAK,WAAW,EAAE,KAAK,OAAO,EAAE,KAAK,MAAM,EAAE,KAAK,UAAU,EAAwB,MAAM,eAAe,CAAC;AAIvI,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,WAAW,CAAC;IAC3C,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,cAAc,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,GAAG,IAAI,CAAC,CAAC;CAC1F;AAED,MAAM,WAAW,wBAAwB;IACvC,YAAY,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,IAAI,CAAC;CAChD;AAED,OAAO,QAAQ,eAAe,CAAC;IAC7B,UAAU,MAAM,CAAC;QACf,UAAU,UAAW,SAAQ,wBAAwB;SAAG;QACxD,UAAU,QAAQ;YAChB,IAAI,EAAE,cAAc,CAAC;SACtB;KACF;CACF;AAQD,qBAAa,cAAe,SAAQ,OAAO,CAAC,cAAc,CAAC;IACzD,QAAQ,CAAC,IAAI,EAAG,MAAM,CAAU;IAChC,QAAQ,CAAC,IAAI,cAAc;IAC3B,QAAQ,CAAC,IAAI,kBAAQ;IAErB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAqC;IAC1D,OAAO,CAAC,YAAY,CAAsB;IAE1C,QAAQ,CAAC,IAAI,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,IAAI;IAS9D,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS;IAItC,MAAM,CAAC,IAAI,EAAE,cAAc,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO;IAKnE,MAAM,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,WAAW;IAexC,IAAI,UAAU;6BAEW,cAAc,WAnCmB,IAAI;MA4C7D;IAED,OAAO,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAgBjC,OAAO,IAAI,IAAI;CAOhB;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,cAAc,CAenE;AAED,wBAAgB,kBAAkB,IAAI,SAAS,cAAc,EAAE,CAE9D;AAED,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAExE"}
@@ -1,102 +0,0 @@
1
- import { Feature, getPlugin } from '@zhin.js/core';
2
- import { mountGameHubUi, markGameHubUiMounted } from './game-hub-mount.js';
3
- import { registerGameRecordModels, initGameRecordDatabase } from './game-records.js';
4
- let activeFeature = null;
5
- function setActiveFeature(feature) {
6
- activeFeature = feature;
7
- }
8
- export class GameHubFeature extends Feature {
9
- name = 'game';
10
- icon = 'Gamepad2';
11
- desc = '游戏';
12
- byId = new Map();
13
- hubDisposers = [];
14
- register(game, pluginName) {
15
- const prev = this.byId.get(game.id);
16
- if (prev) {
17
- this.remove(prev);
18
- }
19
- this.byId.set(game.id, game);
20
- return this.add(game, pluginName);
21
- }
22
- getGame(id) {
23
- return this.byId.get(id);
24
- }
25
- remove(item, pluginName) {
26
- this.byId.delete(item.id);
27
- return super.remove(item, pluginName);
28
- }
29
- toJSON(pluginName) {
30
- const list = pluginName ? this.getByPlugin(pluginName) : [...this.items];
31
- return {
32
- name: this.name,
33
- icon: this.icon,
34
- desc: this.desc,
35
- count: list.length,
36
- items: list.map((g) => ({
37
- name: g.id,
38
- desc: g.title,
39
- usage: `${g.commandPrefix} ${g.quickStart ?? '开始'}`,
40
- })),
41
- };
42
- }
43
- get extensions() {
44
- return {
45
- registerGame: (game) => {
46
- const plugin = getPlugin();
47
- ensureGameHubService(plugin);
48
- const dispose = this.register(game, plugin.name);
49
- plugin.recordFeatureContribution(this.name, game.id);
50
- plugin.onDispose(dispose);
51
- return dispose;
52
- },
53
- };
54
- }
55
- mounted(plugin) {
56
- setActiveFeature(this);
57
- const root = plugin;
58
- const host = root.root ?? root;
59
- registerGameRecordModels(host);
60
- if (host.contextIsReady('database')) {
61
- initGameRecordDatabase(host.inject('database'));
62
- }
63
- else {
64
- host.useContext('database', (dbFeature) => {
65
- initGameRecordDatabase(dbFeature);
66
- });
67
- }
68
- this.hubDisposers = mountGameHubUi(host);
69
- markGameHubUiMounted();
70
- }
71
- dispose() {
72
- for (const d of this.hubDisposers)
73
- d();
74
- this.hubDisposers = [];
75
- if (activeFeature === this) {
76
- setActiveFeature(null);
77
- }
78
- }
79
- }
80
- /**
81
- * 在 root 上注册 `game` 服务(幂等)。游戏插件初始化时调用一次即可。
82
- */
83
- export function ensureGameHubService(plugin) {
84
- const root = plugin.root;
85
- if (root.contextIsReady('game')) {
86
- return root.inject('game');
87
- }
88
- const feature = new GameHubFeature();
89
- root.provide(feature);
90
- setActiveFeature(feature);
91
- if (root.started && feature.mounted) {
92
- feature.mounted(root);
93
- }
94
- return root.inject('game');
95
- }
96
- export function getRegisteredGames() {
97
- return activeFeature?.items ?? [];
98
- }
99
- export function getRegisteredGame(id) {
100
- return activeFeature?.getGame(id);
101
- }
102
- //# sourceMappingURL=game-hub-feature.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"game-hub-feature.js","sourceRoot":"","sources":["../src/game-hub-feature.ts"],"names":[],"mappings":"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"}
@@ -1,9 +0,0 @@
1
- import type { Message, Plugin } from '@zhin.js/core';
2
- import { buildMainHubMenu } from './game-hub-menu.js';
3
- export declare function openMainMenu(message: Message): ReturnType<typeof buildMainHubMenu> | string;
4
- export declare function handleHubChoice(plugin: Plugin, message: Message, scopeId: string, choiceId: string): Promise<boolean>;
5
- export declare function parseHubPayload(payload: string): {
6
- scopeId: string;
7
- choiceId: string;
8
- } | null;
9
- //# sourceMappingURL=game-hub-flow.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"game-hub-flow.d.ts","sourceRoot":"","sources":["../src/game-hub-flow.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAErD,OAAO,EAEL,gBAAgB,EAMjB,MAAM,oBAAoB,CAAC;AAkC5B,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,GAAG,MAAM,CAS3F;AAED,wBAAsB,eAAe,CACnC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,OAAO,CAAC,CA+ClB;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAI7F"}
@@ -1,90 +0,0 @@
1
- import { parseChoicePayload } from './choice-keyboard.js';
2
- import { buildGameHubMenu, buildMainHubMenu, formatHubEmptyMessage, HUB_PREFIX, hubActionChoiceId, hubGameChoiceId, parseHubChoiceId, } from './game-hub-menu.js';
3
- import { getRegisteredGame, getRegisteredGames } from './game-hub-feature.js';
4
- import { createHubScope, getHubContext, rememberHubMenu, } from './game-hub-menu-context.js';
5
- function mainMenuChoices() {
6
- return getRegisteredGames().map((g) => ({
7
- id: hubGameChoiceId(g.id),
8
- label: `${g.icon} ${g.title}`,
9
- }));
10
- }
11
- function gameMenuChoices(gameId, channelType) {
12
- const game = getRegisteredGame(gameId);
13
- if (!game)
14
- return [];
15
- const isGroup = channelType !== 'private';
16
- const menus = game.menus.filter((m) => {
17
- if (m.groupOnly && !isGroup)
18
- return false;
19
- if (m.privateOnly && isGroup)
20
- return false;
21
- return true;
22
- });
23
- return [
24
- ...menus.map((m) => ({
25
- id: hubActionChoiceId(game.id, m.id),
26
- label: m.label,
27
- })),
28
- { id: 'back', label: '↩️ 返回大厅' },
29
- ];
30
- }
31
- export function openMainMenu(message) {
32
- const games = getRegisteredGames();
33
- if (!games.length) {
34
- return formatHubEmptyMessage();
35
- }
36
- const scopeId = createHubScope(message);
37
- const choices = mainMenuChoices();
38
- rememberHubMenu(message, scopeId, choices);
39
- return buildMainHubMenu(scopeId, games, message.$channel.type);
40
- }
41
- export async function handleHubChoice(plugin, message, scopeId, choiceId) {
42
- const ctx = getHubContext(scopeId);
43
- if (!ctx) {
44
- await message.$reply?.('菜单已过期,请重新发送「游戏」。');
45
- return true;
46
- }
47
- const ch = `${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`;
48
- if (ctx.channelKey !== ch) {
49
- await message.$reply?.('请在本频道使用游戏大厅。');
50
- return true;
51
- }
52
- const parsed = parseHubChoiceId(choiceId);
53
- if (!parsed)
54
- return false;
55
- // 大厅导航(选游戏 / 返回):同频道任何人都可点;具体开局由 runAction 用当前点击者 message
56
- if (parsed.kind === 'back') {
57
- const games = getRegisteredGames();
58
- const choices = mainMenuChoices();
59
- rememberHubMenu(message, scopeId, choices);
60
- await message.$reply?.(buildMainHubMenu(scopeId, games, message.$channel.type));
61
- return true;
62
- }
63
- if (parsed.kind === 'game') {
64
- const game = getRegisteredGame(parsed.gameId);
65
- if (!game) {
66
- await message.$reply?.('该游戏未安装。');
67
- return true;
68
- }
69
- const choices = gameMenuChoices(parsed.gameId, message.$channel.type);
70
- rememberHubMenu(message, scopeId, choices);
71
- await message.$reply?.(buildGameHubMenu(scopeId, game, message.$channel.type));
72
- return true;
73
- }
74
- const game = getRegisteredGame(parsed.gameId);
75
- if (!game) {
76
- await message.$reply?.('该游戏未安装。');
77
- return true;
78
- }
79
- const result = await game.runAction(parsed.actionId, { plugin, message });
80
- if (typeof result === 'string')
81
- await message.$reply?.(result);
82
- return true;
83
- }
84
- export function parseHubPayload(payload) {
85
- const parsed = parseChoicePayload(payload, HUB_PREFIX);
86
- if (!parsed)
87
- return null;
88
- return { scopeId: parsed.sessionId, choiceId: parsed.choiceId };
89
- }
90
- //# sourceMappingURL=game-hub-flow.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"game-hub-flow.js","sourceRoot":"","sources":["../src/game-hub-flow.ts"],"names":[],"mappings":"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"}
@@ -1,25 +0,0 @@
1
- import type { Message } from '@zhin.js/core';
2
- export interface HubMenuContext {
3
- channelKey: string;
4
- /** 打开菜单的用户(仅记录,大厅导航不限制点击者) */
5
- openerId: string;
6
- message: Message;
7
- expiresAt: number;
8
- }
9
- export interface HubMenuChoice {
10
- id: string;
11
- label: string;
12
- disabled?: boolean;
13
- }
14
- export interface LastHubMenu {
15
- scopeId: string;
16
- choices: HubMenuChoice[];
17
- expiresAt: number;
18
- }
19
- export declare function createHubScope(message: Message): string;
20
- export declare function rememberHubMenu(message: Message, scopeId: string, choices: HubMenuChoice[]): void;
21
- export declare function getLastHubMenu(message: Message): LastHubMenu | null;
22
- export declare function getHubContext(scopeId: string): HubMenuContext | null;
23
- /** 测试专用:清空菜单上下文 */
24
- export declare function resetHubMenuContextForTests(): void;
25
- //# sourceMappingURL=game-hub-menu-context.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"game-hub-menu-context.d.ts","sourceRoot":"","sources":["../src/game-hub-menu-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAI7C,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,8BAA8B;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;CACnB;AAWD,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAUvD;AAED,wBAAgB,eAAe,CAC7B,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,aAAa,EAAE,GACvB,IAAI,CAON;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,WAAW,GAAG,IAAI,CASnE;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAQpE;AAYD,mBAAmB;AACnB,wBAAgB,2BAA2B,IAAI,IAAI,CAGlD"}