@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.
- package/README.md +73 -16
- package/lib/base-session-service.d.ts +54 -0
- package/lib/base-session-service.d.ts.map +1 -0
- package/lib/base-session-service.js +139 -0
- package/lib/base-session-service.js.map +1 -0
- package/lib/command-alias.d.ts +3 -1
- package/lib/command-alias.d.ts.map +1 -1
- package/lib/command-alias.js.map +1 -1
- package/lib/command-message.d.ts +3 -0
- package/lib/command-message.d.ts.map +1 -1
- package/lib/command-message.js +0 -6
- package/lib/command-message.js.map +1 -1
- package/lib/define-game-plugin.d.ts +32 -0
- package/lib/define-game-plugin.d.ts.map +1 -0
- package/lib/define-game-plugin.js +87 -0
- package/lib/define-game-plugin.js.map +1 -0
- package/lib/deterministic-random.d.ts +16 -0
- package/lib/deterministic-random.d.ts.map +1 -0
- package/lib/deterministic-random.js +68 -0
- package/lib/deterministic-random.js.map +1 -0
- package/lib/game-events.d.ts +53 -0
- package/lib/game-events.d.ts.map +1 -0
- package/lib/game-events.js +26 -0
- package/lib/game-events.js.map +1 -0
- package/lib/game-interactive.d.ts +0 -22
- package/lib/game-interactive.d.ts.map +1 -1
- package/lib/game-interactive.js +1 -57
- package/lib/game-interactive.js.map +1 -1
- package/lib/game-records.d.ts +0 -1
- package/lib/game-records.d.ts.map +1 -1
- package/lib/game-records.js +0 -60
- package/lib/game-records.js.map +1 -1
- package/lib/game-session.d.ts +0 -59
- package/lib/game-session.d.ts.map +1 -1
- package/lib/game-session.js +0 -29
- package/lib/game-session.js.map +1 -1
- package/lib/index.d.ts +6 -9
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +6 -9
- package/lib/index.js.map +1 -1
- package/lib/memory-db.d.ts +6 -0
- package/lib/memory-db.d.ts.map +1 -1
- package/lib/memory-db.js +8 -0
- package/lib/memory-db.js.map +1 -1
- package/lib/session-coordinator.d.ts +27 -0
- package/lib/session-coordinator.d.ts.map +1 -0
- package/lib/session-coordinator.js +57 -0
- package/lib/session-coordinator.js.map +1 -0
- package/lib/versioned-session-service.d.ts +48 -0
- package/lib/versioned-session-service.d.ts.map +1 -0
- package/lib/versioned-session-service.js +116 -0
- package/lib/versioned-session-service.js.map +1 -0
- package/package.json +7 -6
- package/src/base-session-service.ts +210 -0
- package/src/command-alias.ts +3 -1
- package/src/command-message.ts +4 -0
- package/src/define-game-plugin.ts +162 -0
- package/src/deterministic-random.ts +81 -0
- package/src/game-events.ts +75 -0
- package/src/game-interactive.ts +1 -80
- package/src/game-records.ts +0 -73
- package/src/game-session.ts +0 -83
- package/src/index.ts +6 -9
- package/src/memory-db.ts +12 -0
- package/src/session-coordinator.ts +74 -0
- package/src/versioned-session-service.ts +162 -0
- package/lib/command-help.d.ts +0 -19
- package/lib/command-help.d.ts.map +0 -1
- package/lib/command-help.js +0 -91
- package/lib/command-help.js.map +0 -1
- package/lib/game-commands.d.ts +0 -3
- package/lib/game-commands.d.ts.map +0 -1
- package/lib/game-commands.js +0 -5
- package/lib/game-commands.js.map +0 -1
- package/lib/game-hub-feature.d.ts +0 -58
- package/lib/game-hub-feature.d.ts.map +0 -1
- package/lib/game-hub-feature.js +0 -102
- package/lib/game-hub-feature.js.map +0 -1
- package/lib/game-hub-flow.d.ts +0 -9
- package/lib/game-hub-flow.d.ts.map +0 -1
- package/lib/game-hub-flow.js +0 -90
- package/lib/game-hub-flow.js.map +0 -1
- package/lib/game-hub-menu-context.d.ts +0 -25
- package/lib/game-hub-menu-context.d.ts.map +0 -1
- package/lib/game-hub-menu-context.js +0 -64
- package/lib/game-hub-menu-context.js.map +0 -1
- package/lib/game-hub-menu.d.ts +0 -37
- package/lib/game-hub-menu.d.ts.map +0 -1
- package/lib/game-hub-menu.js +0 -162
- package/lib/game-hub-menu.js.map +0 -1
- package/lib/game-hub-mount.d.ts +0 -13
- package/lib/game-hub-mount.d.ts.map +0 -1
- package/lib/game-hub-mount.js +0 -89
- package/lib/game-hub-mount.js.map +0 -1
- package/lib/game-middleware.d.ts +0 -6
- package/lib/game-middleware.d.ts.map +0 -1
- package/lib/game-middleware.js +0 -9
- package/lib/game-middleware.js.map +0 -1
- package/lib/game-onboarding.d.ts +0 -8
- package/lib/game-onboarding.d.ts.map +0 -1
- package/lib/game-onboarding.js +0 -67
- package/lib/game-onboarding.js.map +0 -1
- package/lib/help-card.d.ts +0 -12
- package/lib/help-card.d.ts.map +0 -1
- package/lib/help-card.js +0 -129
- package/lib/help-card.js.map +0 -1
- package/src/command-help.ts +0 -117
- package/src/game-commands.ts +0 -4
- package/src/game-hub-feature.ts +0 -154
- package/src/game-hub-flow.ts +0 -114
- package/src/game-hub-menu-context.ts +0 -94
- package/src/game-hub-menu.ts +0 -223
- package/src/game-hub-mount.ts +0 -147
- package/src/game-middleware.ts +0 -14
- package/src/game-onboarding.ts +0 -71
- package/src/help-card.ts +0 -188
package/lib/help-card.js
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import { Card, CardCanvas, CardHeader, DEFAULT_CARD_THEME, h, KvRow, Section, TopicItem, } from '@zhin.js/satori';
|
|
2
|
-
import { slashCommandPrefix } from './game-commands.js';
|
|
3
|
-
import { collectSupplementaryCommandHelp, formatHubOpenCommandLines, } from './command-help.js';
|
|
4
|
-
export const HELP_CARD_CANVAS = DEFAULT_CARD_THEME.canvas;
|
|
5
|
-
export const HELP_CARD_WIDTH = 560;
|
|
6
|
-
function cardShell(inner) {
|
|
7
|
-
return h(CardCanvas, {
|
|
8
|
-
width: HELP_CARD_WIDTH,
|
|
9
|
-
backgroundColor: HELP_CARD_CANVAS,
|
|
10
|
-
children: h(Card, { children: inner }),
|
|
11
|
-
});
|
|
12
|
-
}
|
|
13
|
-
function gameTopicSummary(game) {
|
|
14
|
-
const quick = game.quickStart ?? '开始';
|
|
15
|
-
const cmd = `${slashCommandPrefix(game.commandPrefix.replace(/^\//, ''))} ${quick}`;
|
|
16
|
-
const alias = game.aliases?.length ? ` · ${game.aliases.join(' / ')}` : '';
|
|
17
|
-
return `${cmd}${alias} — ${game.description}`;
|
|
18
|
-
}
|
|
19
|
-
function commandTopicSummary(cmd) {
|
|
20
|
-
const [label = '', detail = ''] = cmd.helpInfo.desc;
|
|
21
|
-
return detail || label || cmd.pattern;
|
|
22
|
-
}
|
|
23
|
-
export function buildBotHelpHtml(input) {
|
|
24
|
-
const { games, commands, channelType } = input;
|
|
25
|
-
const hubLines = formatHubOpenCommandLines(commands);
|
|
26
|
-
const other = commands?.length
|
|
27
|
-
? collectSupplementaryCommandHelp(commands, games)
|
|
28
|
-
: [];
|
|
29
|
-
const parts = [];
|
|
30
|
-
parts.push(h(CardHeader, {
|
|
31
|
-
title: '游戏大厅',
|
|
32
|
-
subtitle: games.length
|
|
33
|
-
? `已注册 ${games.length} 款游戏 · 发送 /游戏 打开按钮大厅`
|
|
34
|
-
: '暂无已注册游戏',
|
|
35
|
-
badge: '🎮',
|
|
36
|
-
}));
|
|
37
|
-
if (hubLines.length) {
|
|
38
|
-
parts.push(h(Section, {
|
|
39
|
-
title: '入口',
|
|
40
|
-
children: hubLines.map((line) => {
|
|
41
|
-
const sep = line.indexOf(' — ');
|
|
42
|
-
const pattern = sep >= 0 ? line.slice(0, sep) : line;
|
|
43
|
-
const desc = sep >= 0 ? line.slice(sep + 3) : '';
|
|
44
|
-
return h(KvRow, { label: pattern, value: desc, labelWidth: 72 });
|
|
45
|
-
}),
|
|
46
|
-
}));
|
|
47
|
-
}
|
|
48
|
-
if (games.length) {
|
|
49
|
-
parts.push(h(Section, {
|
|
50
|
-
title: '游戏列表',
|
|
51
|
-
children: games.map((g, i) => h(TopicItem, {
|
|
52
|
-
index: i + 1,
|
|
53
|
-
title: `${g.icon} ${g.title}`,
|
|
54
|
-
summary: gameTopicSummary(g),
|
|
55
|
-
})),
|
|
56
|
-
}));
|
|
57
|
-
}
|
|
58
|
-
else {
|
|
59
|
-
parts.push(h(Section, {
|
|
60
|
-
title: '提示',
|
|
61
|
-
children: h(TopicItem, {
|
|
62
|
-
index: 1,
|
|
63
|
-
title: '安装任意游戏插件',
|
|
64
|
-
summary: 'registerGame 后会自动挂载大厅命令',
|
|
65
|
-
}),
|
|
66
|
-
}));
|
|
67
|
-
}
|
|
68
|
-
if (other.length) {
|
|
69
|
-
parts.push(h(Section, {
|
|
70
|
-
title: '其他命令',
|
|
71
|
-
children: other.map((cmd, i) => h(TopicItem, {
|
|
72
|
-
index: i + 1,
|
|
73
|
-
title: cmd.pattern,
|
|
74
|
-
summary: commandTopicSummary(cmd),
|
|
75
|
-
})),
|
|
76
|
-
}));
|
|
77
|
-
}
|
|
78
|
-
if (channelType === 'group') {
|
|
79
|
-
parts.push(h(Section, {
|
|
80
|
-
title: 'QQ 群用法',
|
|
81
|
-
children: [
|
|
82
|
-
h(TopicItem, {
|
|
83
|
-
index: 1,
|
|
84
|
-
title: '公域群需 @ 机器人',
|
|
85
|
-
summary: '发送命令或直接点击消息按钮',
|
|
86
|
-
}),
|
|
87
|
-
h(TopicItem, {
|
|
88
|
-
index: 2,
|
|
89
|
-
title: '/游戏 打开大厅',
|
|
90
|
-
summary: '同群成员均可点击按钮参与',
|
|
91
|
-
}),
|
|
92
|
-
],
|
|
93
|
-
}));
|
|
94
|
-
}
|
|
95
|
-
return cardShell(parts.join(''));
|
|
96
|
-
}
|
|
97
|
-
export function buildHubHelpHtml(games, commands) {
|
|
98
|
-
const hubLines = formatHubOpenCommandLines(commands);
|
|
99
|
-
const parts = [
|
|
100
|
-
h(CardHeader, {
|
|
101
|
-
title: '游戏大厅帮助',
|
|
102
|
-
subtitle: games.length ? `${games.length} 款游戏可选` : '暂无游戏',
|
|
103
|
-
badge: '🎮',
|
|
104
|
-
}),
|
|
105
|
-
];
|
|
106
|
-
if (hubLines.length) {
|
|
107
|
-
parts.push(h(Section, {
|
|
108
|
-
title: '入口',
|
|
109
|
-
children: hubLines.map((line) => {
|
|
110
|
-
const sep = line.indexOf(' — ');
|
|
111
|
-
const pattern = sep >= 0 ? line.slice(0, sep) : line;
|
|
112
|
-
const desc = sep >= 0 ? line.slice(sep + 3) : '';
|
|
113
|
-
return h(KvRow, { label: pattern, value: desc, labelWidth: 72 });
|
|
114
|
-
}),
|
|
115
|
-
}));
|
|
116
|
-
}
|
|
117
|
-
if (games.length) {
|
|
118
|
-
parts.push(h(Section, {
|
|
119
|
-
title: '游戏',
|
|
120
|
-
children: games.map((g, i) => h(TopicItem, {
|
|
121
|
-
index: i + 1,
|
|
122
|
-
title: `${g.icon} ${g.title}`,
|
|
123
|
-
summary: gameTopicSummary(g),
|
|
124
|
-
})),
|
|
125
|
-
}));
|
|
126
|
-
}
|
|
127
|
-
return cardShell(parts.join(''));
|
|
128
|
-
}
|
|
129
|
-
//# sourceMappingURL=help-card.js.map
|
package/lib/help-card.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"help-card.js","sourceRoot":"","sources":["../src/help-card.ts"],"names":[],"mappings":"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"}
|
package/src/command-help.ts
DELETED
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import type { Message, MessageCommand, Plugin } from '@zhin.js/core';
|
|
2
|
-
import type { RegisteredGame } from './game-hub-feature.js';
|
|
3
|
-
|
|
4
|
-
export interface CommandHelpSource {
|
|
5
|
-
pattern: string;
|
|
6
|
-
helpInfo: {
|
|
7
|
-
desc: string[];
|
|
8
|
-
usage?: string[];
|
|
9
|
-
examples?: string[];
|
|
10
|
-
};
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function commandToHelpSource(cmd: Pick<MessageCommand, 'pattern' | 'helpInfo'>): CommandHelpSource {
|
|
14
|
-
return { pattern: cmd.pattern, helpInfo: cmd.helpInfo };
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/** 按 MessageCommand.permit 与当前消息上下文过滤(与 handle 一致) */
|
|
18
|
-
export async function filterHelpCommands(
|
|
19
|
-
commands: readonly MessageCommand[],
|
|
20
|
-
message: Message,
|
|
21
|
-
plugin: Plugin,
|
|
22
|
-
): Promise<CommandHelpSource[]> {
|
|
23
|
-
const out: CommandHelpSource[] = [];
|
|
24
|
-
for (const cmd of commands) {
|
|
25
|
-
if (await cmd.checkPermits(message, plugin)) {
|
|
26
|
-
out.push(commandToHelpSource(cmd));
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
return out;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function commandLeadingToken(pattern: string): string {
|
|
33
|
-
return pattern.split(/\s/)[0] ?? '';
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function normalizeToken(token: string): string {
|
|
37
|
-
return token.replace(/^\//, '').toLowerCase();
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/** 不在「其他命令」中重复展示的 pattern(大厅入口 / 帮助自身) */
|
|
41
|
-
const HELP_EXCLUDE_EXACT = new Set([
|
|
42
|
-
'/帮助',
|
|
43
|
-
'/help',
|
|
44
|
-
'帮助',
|
|
45
|
-
'/游戏 帮助',
|
|
46
|
-
'/游戏',
|
|
47
|
-
'/game',
|
|
48
|
-
'游戏',
|
|
49
|
-
'game',
|
|
50
|
-
]);
|
|
51
|
-
|
|
52
|
-
function isHubListedGameCommand(pattern: string, games: readonly RegisteredGame[]): boolean {
|
|
53
|
-
const lead = normalizeToken(commandLeadingToken(pattern));
|
|
54
|
-
return games.some((g) => normalizeToken(g.commandPrefix) === lead);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function comparePatterns(a: string, b: string): number {
|
|
58
|
-
const leadDiff = commandLeadingToken(b).length - commandLeadingToken(a).length;
|
|
59
|
-
if (leadDiff !== 0) return leadDiff;
|
|
60
|
-
return b.length - a.length;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function formatPatternDisplay(pattern: string): string {
|
|
64
|
-
return `\`${pattern}\``;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function formatCommandHelpLine(cmd: CommandHelpSource): string {
|
|
68
|
-
const [label = '', detail = ''] = cmd.helpInfo.desc;
|
|
69
|
-
const display = formatPatternDisplay(cmd.pattern);
|
|
70
|
-
const summary = detail || label || cmd.pattern;
|
|
71
|
-
if (label && label !== commandLeadingToken(cmd.pattern) && !cmd.pattern.startsWith(label)) {
|
|
72
|
-
return `• ${display}(${label})— ${summary}`;
|
|
73
|
-
}
|
|
74
|
-
return `• ${display} — ${summary}`;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/** 从 command 服务收集应在 /帮助 中展示的命令(去重、排除已在游戏大厅列出的项) */
|
|
78
|
-
export function collectSupplementaryCommandHelp(
|
|
79
|
-
commands: readonly CommandHelpSource[],
|
|
80
|
-
games: readonly RegisteredGame[],
|
|
81
|
-
): CommandHelpSource[] {
|
|
82
|
-
const seen = new Set<string>();
|
|
83
|
-
const out: CommandHelpSource[] = [];
|
|
84
|
-
for (const cmd of [...commands].sort((a, b) => comparePatterns(a.pattern, b.pattern))) {
|
|
85
|
-
if (HELP_EXCLUDE_EXACT.has(cmd.pattern)) continue;
|
|
86
|
-
if (isHubListedGameCommand(cmd.pattern, games)) continue;
|
|
87
|
-
if (seen.has(cmd.pattern)) continue;
|
|
88
|
-
seen.add(cmd.pattern);
|
|
89
|
-
out.push(cmd);
|
|
90
|
-
}
|
|
91
|
-
return out;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export function formatSupplementaryCommandsHelp(
|
|
95
|
-
commands: readonly CommandHelpSource[],
|
|
96
|
-
games: readonly RegisteredGame[],
|
|
97
|
-
): string {
|
|
98
|
-
const items = collectSupplementaryCommandHelp(commands, games);
|
|
99
|
-
if (!items.length) return '';
|
|
100
|
-
const lines = items.map(formatCommandHelpLine);
|
|
101
|
-
return ['**其他命令**', '', ...lines].join('\n');
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/** 大厅入口行:从已注册命令的 .desc() 生成 */
|
|
105
|
-
export function formatHubOpenCommandLines(
|
|
106
|
-
commands: readonly CommandHelpSource[] | undefined,
|
|
107
|
-
): string[] {
|
|
108
|
-
if (!commands?.length) return [];
|
|
109
|
-
const lines: string[] = [];
|
|
110
|
-
for (const pattern of ['/游戏', '/game'] as const) {
|
|
111
|
-
const cmd = commands.find((c) => c.pattern === pattern);
|
|
112
|
-
if (!cmd) continue;
|
|
113
|
-
const desc = cmd.helpInfo.desc[0] ?? cmd.helpInfo.desc[1] ?? '';
|
|
114
|
-
if (desc) lines.push(`${pattern} — ${desc}`);
|
|
115
|
-
}
|
|
116
|
-
return lines;
|
|
117
|
-
}
|
package/src/game-commands.ts
DELETED
package/src/game-hub-feature.ts
DELETED
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
import { Feature, getPlugin, type FeatureJSON, type Message, type Plugin, type PluginLike, type DatabaseFeature } from '@zhin.js/core';
|
|
2
|
-
import { mountGameHubUi, markGameHubUiMounted } from './game-hub-mount.js';
|
|
3
|
-
import { registerGameRecordModels, initGameRecordDatabase } from './game-records.js';
|
|
4
|
-
|
|
5
|
-
export interface GameHubContext {
|
|
6
|
-
plugin: Plugin;
|
|
7
|
-
message: Message;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export interface GameMenuAction {
|
|
11
|
-
id: string;
|
|
12
|
-
label: string;
|
|
13
|
-
style?: 'primary' | 'danger' | 'secondary';
|
|
14
|
-
groupOnly?: boolean;
|
|
15
|
-
privateOnly?: boolean;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface RegisteredGame {
|
|
19
|
-
id: string;
|
|
20
|
-
title: string;
|
|
21
|
-
icon: string;
|
|
22
|
-
description: string;
|
|
23
|
-
commandPrefix: string;
|
|
24
|
-
quickStart?: string;
|
|
25
|
-
aliases?: string[];
|
|
26
|
-
menus: GameMenuAction[];
|
|
27
|
-
runAction: (actionId: string, ctx: GameHubContext) => Promise<string | undefined | void>;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface GameHubContextExtensions {
|
|
31
|
-
registerGame(game: RegisteredGame): () => void;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
declare module '@zhin.js/core' {
|
|
35
|
-
namespace Plugin {
|
|
36
|
-
interface Extensions extends GameHubContextExtensions {}
|
|
37
|
-
interface Contexts {
|
|
38
|
-
game: GameHubFeature;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
let activeFeature: GameHubFeature | null = null;
|
|
44
|
-
|
|
45
|
-
function setActiveFeature(feature: GameHubFeature | null): void {
|
|
46
|
-
activeFeature = feature;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export class GameHubFeature extends Feature<RegisteredGame> {
|
|
50
|
-
readonly name = 'game' as const;
|
|
51
|
-
readonly icon = 'Gamepad2';
|
|
52
|
-
readonly desc = '游戏';
|
|
53
|
-
|
|
54
|
-
private readonly byId = new Map<string, RegisteredGame>();
|
|
55
|
-
private hubDisposers: (() => void)[] = [];
|
|
56
|
-
|
|
57
|
-
register(game: RegisteredGame, pluginName: string): () => void {
|
|
58
|
-
const prev = this.byId.get(game.id);
|
|
59
|
-
if (prev) {
|
|
60
|
-
this.remove(prev);
|
|
61
|
-
}
|
|
62
|
-
this.byId.set(game.id, game);
|
|
63
|
-
return this.add(game, pluginName);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
getGame(id: string): RegisteredGame | undefined {
|
|
67
|
-
return this.byId.get(id);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
override remove(item: RegisteredGame, pluginName?: string): boolean {
|
|
71
|
-
this.byId.delete(item.id);
|
|
72
|
-
return super.remove(item, pluginName);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
toJSON(pluginName?: string): FeatureJSON {
|
|
76
|
-
const list = pluginName ? this.getByPlugin(pluginName) : [...this.items];
|
|
77
|
-
return {
|
|
78
|
-
name: this.name,
|
|
79
|
-
icon: this.icon,
|
|
80
|
-
desc: this.desc,
|
|
81
|
-
count: list.length,
|
|
82
|
-
items: list.map((g) => ({
|
|
83
|
-
name: g.id,
|
|
84
|
-
desc: g.title,
|
|
85
|
-
usage: `${g.commandPrefix} ${g.quickStart ?? '开始'}`,
|
|
86
|
-
})),
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
get extensions() {
|
|
91
|
-
return {
|
|
92
|
-
registerGame: (game: RegisteredGame) => {
|
|
93
|
-
const plugin = getPlugin();
|
|
94
|
-
ensureGameHubService(plugin);
|
|
95
|
-
const dispose = this.register(game, plugin.name);
|
|
96
|
-
plugin.recordFeatureContribution(this.name, game.id);
|
|
97
|
-
plugin.onDispose(dispose);
|
|
98
|
-
return dispose;
|
|
99
|
-
},
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
mounted(plugin: PluginLike): void {
|
|
104
|
-
setActiveFeature(this);
|
|
105
|
-
const root = plugin as Plugin;
|
|
106
|
-
const host = root.root ?? root;
|
|
107
|
-
registerGameRecordModels(host);
|
|
108
|
-
if (host.contextIsReady('database')) {
|
|
109
|
-
initGameRecordDatabase(host.inject('database') as DatabaseFeature);
|
|
110
|
-
} else {
|
|
111
|
-
host.useContext('database', (dbFeature: DatabaseFeature) => {
|
|
112
|
-
initGameRecordDatabase(dbFeature);
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
this.hubDisposers = mountGameHubUi(host);
|
|
116
|
-
markGameHubUiMounted();
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
dispose(): void {
|
|
120
|
-
for (const d of this.hubDisposers) d();
|
|
121
|
-
this.hubDisposers = [];
|
|
122
|
-
if (activeFeature === this) {
|
|
123
|
-
setActiveFeature(null);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* 在 root 上注册 `game` 服务(幂等)。游戏插件初始化时调用一次即可。
|
|
130
|
-
*/
|
|
131
|
-
export function ensureGameHubService(plugin: Plugin): GameHubFeature {
|
|
132
|
-
const root = plugin.root;
|
|
133
|
-
if (root.contextIsReady('game')) {
|
|
134
|
-
return root.inject('game') as GameHubFeature;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const feature = new GameHubFeature();
|
|
138
|
-
root.provide(feature);
|
|
139
|
-
setActiveFeature(feature);
|
|
140
|
-
|
|
141
|
-
if (root.started && feature.mounted) {
|
|
142
|
-
feature.mounted(root);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return root.inject('game') as GameHubFeature;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
export function getRegisteredGames(): readonly RegisteredGame[] {
|
|
149
|
-
return activeFeature?.items ?? [];
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
export function getRegisteredGame(id: string): RegisteredGame | undefined {
|
|
153
|
-
return activeFeature?.getGame(id);
|
|
154
|
-
}
|
package/src/game-hub-flow.ts
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import type { Message, Plugin } from '@zhin.js/core';
|
|
2
|
-
import { parseChoicePayload } from './choice-keyboard.js';
|
|
3
|
-
import {
|
|
4
|
-
buildGameHubMenu,
|
|
5
|
-
buildMainHubMenu,
|
|
6
|
-
formatHubEmptyMessage,
|
|
7
|
-
HUB_PREFIX,
|
|
8
|
-
hubActionChoiceId,
|
|
9
|
-
hubGameChoiceId,
|
|
10
|
-
parseHubChoiceId,
|
|
11
|
-
} from './game-hub-menu.js';
|
|
12
|
-
import { getRegisteredGame, getRegisteredGames } from './game-hub-feature.js';
|
|
13
|
-
import {
|
|
14
|
-
createHubScope,
|
|
15
|
-
getHubContext,
|
|
16
|
-
rememberHubMenu,
|
|
17
|
-
type HubMenuChoice,
|
|
18
|
-
} from './game-hub-menu-context.js';
|
|
19
|
-
|
|
20
|
-
function mainMenuChoices(): HubMenuChoice[] {
|
|
21
|
-
return getRegisteredGames().map((g) => ({
|
|
22
|
-
id: hubGameChoiceId(g.id),
|
|
23
|
-
label: `${g.icon} ${g.title}`,
|
|
24
|
-
}));
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function gameMenuChoices(gameId: string, channelType: string): HubMenuChoice[] {
|
|
28
|
-
const game = getRegisteredGame(gameId);
|
|
29
|
-
if (!game) return [];
|
|
30
|
-
const isGroup = channelType !== 'private';
|
|
31
|
-
const menus = game.menus.filter((m) => {
|
|
32
|
-
if (m.groupOnly && !isGroup) return false;
|
|
33
|
-
if (m.privateOnly && isGroup) return false;
|
|
34
|
-
return true;
|
|
35
|
-
});
|
|
36
|
-
return [
|
|
37
|
-
...menus.map((m) => ({
|
|
38
|
-
id: hubActionChoiceId(game.id, m.id),
|
|
39
|
-
label: m.label,
|
|
40
|
-
})),
|
|
41
|
-
{ id: 'back', label: '↩️ 返回大厅' },
|
|
42
|
-
];
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function openMainMenu(message: Message): ReturnType<typeof buildMainHubMenu> | string {
|
|
46
|
-
const games = getRegisteredGames();
|
|
47
|
-
if (!games.length) {
|
|
48
|
-
return formatHubEmptyMessage();
|
|
49
|
-
}
|
|
50
|
-
const scopeId = createHubScope(message);
|
|
51
|
-
const choices = mainMenuChoices();
|
|
52
|
-
rememberHubMenu(message, scopeId, choices);
|
|
53
|
-
return buildMainHubMenu(scopeId, games, message.$channel.type);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export async function handleHubChoice(
|
|
57
|
-
plugin: Plugin,
|
|
58
|
-
message: Message,
|
|
59
|
-
scopeId: string,
|
|
60
|
-
choiceId: string,
|
|
61
|
-
): Promise<boolean> {
|
|
62
|
-
const ctx = getHubContext(scopeId);
|
|
63
|
-
if (!ctx) {
|
|
64
|
-
await message.$reply?.('菜单已过期,请重新发送「游戏」。');
|
|
65
|
-
return true;
|
|
66
|
-
}
|
|
67
|
-
const ch = `${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`;
|
|
68
|
-
if (ctx.channelKey !== ch) {
|
|
69
|
-
await message.$reply?.('请在本频道使用游戏大厅。');
|
|
70
|
-
return true;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const parsed = parseHubChoiceId(choiceId);
|
|
74
|
-
if (!parsed) return false;
|
|
75
|
-
|
|
76
|
-
// 大厅导航(选游戏 / 返回):同频道任何人都可点;具体开局由 runAction 用当前点击者 message
|
|
77
|
-
if (parsed.kind === 'back') {
|
|
78
|
-
const games = getRegisteredGames();
|
|
79
|
-
const choices = mainMenuChoices();
|
|
80
|
-
rememberHubMenu(message, scopeId, choices);
|
|
81
|
-
await message.$reply?.(buildMainHubMenu(scopeId, games, message.$channel.type));
|
|
82
|
-
return true;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (parsed.kind === 'game') {
|
|
86
|
-
const game = getRegisteredGame(parsed.gameId);
|
|
87
|
-
if (!game) {
|
|
88
|
-
await message.$reply?.('该游戏未安装。');
|
|
89
|
-
return true;
|
|
90
|
-
}
|
|
91
|
-
const choices = gameMenuChoices(parsed.gameId, message.$channel.type);
|
|
92
|
-
rememberHubMenu(message, scopeId, choices);
|
|
93
|
-
await message.$reply?.(
|
|
94
|
-
buildGameHubMenu(scopeId, game, message.$channel.type),
|
|
95
|
-
);
|
|
96
|
-
return true;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const game = getRegisteredGame(parsed.gameId);
|
|
100
|
-
if (!game) {
|
|
101
|
-
await message.$reply?.('该游戏未安装。');
|
|
102
|
-
return true;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const result = await game.runAction(parsed.actionId, { plugin, message });
|
|
106
|
-
if (typeof result === 'string') await message.$reply?.(result);
|
|
107
|
-
return true;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
export function parseHubPayload(payload: string): { scopeId: string; choiceId: string } | null {
|
|
111
|
-
const parsed = parseChoicePayload(payload, HUB_PREFIX);
|
|
112
|
-
if (!parsed) return null;
|
|
113
|
-
return { scopeId: parsed.sessionId, choiceId: parsed.choiceId };
|
|
114
|
-
}
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import type { Message } from '@zhin.js/core';
|
|
2
|
-
import { channelKey } from './board-sender.js';
|
|
3
|
-
import { generateCompactId } from './random.js';
|
|
4
|
-
|
|
5
|
-
export interface HubMenuContext {
|
|
6
|
-
channelKey: string;
|
|
7
|
-
/** 打开菜单的用户(仅记录,大厅导航不限制点击者) */
|
|
8
|
-
openerId: string;
|
|
9
|
-
message: Message;
|
|
10
|
-
expiresAt: number;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface HubMenuChoice {
|
|
14
|
-
id: string;
|
|
15
|
-
label: string;
|
|
16
|
-
disabled?: boolean;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface LastHubMenu {
|
|
20
|
-
scopeId: string;
|
|
21
|
-
choices: HubMenuChoice[];
|
|
22
|
-
expiresAt: number;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const TTL_MS = 60 * 60 * 1000;
|
|
26
|
-
const contexts = new Map<string, HubMenuContext>();
|
|
27
|
-
const lastMenus = new Map<string, LastHubMenu>();
|
|
28
|
-
|
|
29
|
-
/** 大厅 fallback 按频道共享,任意成员可回复数字或点按钮 */
|
|
30
|
-
function hubMenuKey(message: Message): string {
|
|
31
|
-
return channelKey(message);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function createHubScope(message: Message): string {
|
|
35
|
-
const scopeId = generateCompactId('h');
|
|
36
|
-
contexts.set(scopeId, {
|
|
37
|
-
channelKey: channelKey(message),
|
|
38
|
-
openerId: message.$sender.id,
|
|
39
|
-
message,
|
|
40
|
-
expiresAt: Date.now() + TTL_MS,
|
|
41
|
-
});
|
|
42
|
-
pruneExpired();
|
|
43
|
-
return scopeId;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export function rememberHubMenu(
|
|
47
|
-
message: Message,
|
|
48
|
-
scopeId: string,
|
|
49
|
-
choices: HubMenuChoice[],
|
|
50
|
-
): void {
|
|
51
|
-
lastMenus.set(hubMenuKey(message), {
|
|
52
|
-
scopeId,
|
|
53
|
-
choices,
|
|
54
|
-
expiresAt: Date.now() + TTL_MS,
|
|
55
|
-
});
|
|
56
|
-
pruneExpired();
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export function getLastHubMenu(message: Message): LastHubMenu | null {
|
|
60
|
-
pruneExpired();
|
|
61
|
-
const key = hubMenuKey(message);
|
|
62
|
-
const last = lastMenus.get(key);
|
|
63
|
-
if (!last || last.expiresAt < Date.now()) {
|
|
64
|
-
lastMenus.delete(key);
|
|
65
|
-
return null;
|
|
66
|
-
}
|
|
67
|
-
return last;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export function getHubContext(scopeId: string): HubMenuContext | null {
|
|
71
|
-
pruneExpired();
|
|
72
|
-
const ctx = contexts.get(scopeId);
|
|
73
|
-
if (!ctx || ctx.expiresAt < Date.now()) {
|
|
74
|
-
contexts.delete(scopeId);
|
|
75
|
-
return null;
|
|
76
|
-
}
|
|
77
|
-
return ctx;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function pruneExpired(): void {
|
|
81
|
-
const now = Date.now();
|
|
82
|
-
for (const [id, ctx] of contexts) {
|
|
83
|
-
if (ctx.expiresAt < now) contexts.delete(id);
|
|
84
|
-
}
|
|
85
|
-
for (const [key, menu] of lastMenus) {
|
|
86
|
-
if (menu.expiresAt < now) lastMenus.delete(key);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/** 测试专用:清空菜单上下文 */
|
|
91
|
-
export function resetHubMenuContextForTests(): void {
|
|
92
|
-
contexts.clear();
|
|
93
|
-
lastMenus.clear();
|
|
94
|
-
}
|