@sfmc-bds/qq-bridge 0.2.0-beta.11 → 0.2.0-beta.13

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 (70) hide show
  1. package/README.md +68 -1
  2. package/deploy/install-nginx-webhook.sh +34 -0
  3. package/deploy/nginx-webhook-location.conf +10 -0
  4. package/deploy/qq-bridge.service +23 -0
  5. package/deploy/qq-config-sync.ps1 +106 -0
  6. package/deploy/qq-tunnel.ps1 +15 -0
  7. package/deploy/sync-qq-config.mjs +229 -0
  8. package/dist/commands/account-format.js +41 -0
  9. package/dist/commands/account-format.js.map +7 -0
  10. package/dist/commands/content-format.js +114 -0
  11. package/dist/commands/content-format.js.map +7 -0
  12. package/dist/commands/db-api.js +21 -6
  13. package/dist/commands/db-api.js.map +2 -2
  14. package/dist/commands/handlers.js +392 -524
  15. package/dist/commands/handlers.js.map +2 -2
  16. package/dist/commands/index.js +5 -5
  17. package/dist/commands/index.js.map +3 -3
  18. package/dist/commands/interaction-router.js +38 -133
  19. package/dist/commands/interaction-router.js.map +2 -2
  20. package/dist/commands/join-settings-ui.js +1 -24
  21. package/dist/commands/join-settings-ui.js.map +2 -2
  22. package/dist/commands/menu-format.js +36 -33
  23. package/dist/commands/menu-format.js.map +2 -2
  24. package/dist/commands/menu-panel-sync.js +39 -13
  25. package/dist/commands/menu-panel-sync.js.map +2 -2
  26. package/dist/commands/message-text.js +91 -0
  27. package/dist/commands/message-text.js.map +7 -0
  28. package/dist/commands/pending.js +23 -16
  29. package/dist/commands/pending.js.map +2 -2
  30. package/dist/commands/permissions.js +17 -0
  31. package/dist/commands/permissions.js.map +7 -0
  32. package/dist/commands/registry.js +1 -1
  33. package/dist/commands/registry.js.map +2 -2
  34. package/dist/commands/render.js +7 -16
  35. package/dist/commands/render.js.map +2 -2
  36. package/dist/commands/reply-llbot.js +18 -7
  37. package/dist/commands/reply-llbot.js.map +2 -2
  38. package/dist/commands/reply-official.js +55 -39
  39. package/dist/commands/reply-official.js.map +3 -3
  40. package/dist/commands/router.js +116 -23
  41. package/dist/commands/router.js.map +2 -2
  42. package/dist/config.js +35 -27
  43. package/dist/config.js.map +2 -2
  44. package/dist/console.js +3 -3
  45. package/dist/console.js.map +2 -2
  46. package/dist/index.js +30 -16
  47. package/dist/index.js.map +2 -2
  48. package/dist/llbot-ws-api.js +2 -2
  49. package/dist/llbot-ws-api.js.map +2 -2
  50. package/dist/official/events.js +32 -7
  51. package/dist/official/events.js.map +2 -2
  52. package/dist/official/gateway.js +10 -1
  53. package/dist/official/gateway.js.map +2 -2
  54. package/dist/official/webhook.js +165 -0
  55. package/dist/official/webhook.js.map +7 -0
  56. package/dist/onebot.js +0 -4
  57. package/dist/onebot.js.map +2 -2
  58. package/package.json +4 -4
  59. package/dist/commands/join-settings-ui.test.js +0 -43
  60. package/dist/commands/join-settings-ui.test.js.map +0 -7
  61. package/dist/commands/menu-panel-sync.test.js +0 -27
  62. package/dist/commands/menu-panel-sync.test.js.map +0 -7
  63. package/dist/commands/render.test.js +0 -44
  64. package/dist/commands/render.test.js.map +0 -7
  65. package/dist/commands/router.test.js +0 -92
  66. package/dist/commands/router.test.js.map +0 -7
  67. package/dist/llbot-ws-api.test.js +0 -32
  68. package/dist/llbot-ws-api.test.js.map +0 -7
  69. package/dist/official/events.test.js +0 -123
  70. package/dist/official/events.test.js.map +0 -7
@@ -1,22 +1,17 @@
1
- import {
2
- getGroupBotState,
3
- getGroupInfo,
4
- sendGroupMessage
5
- } from "@sfmc-bds/sdk/node/qq-official";
1
+ import { getGroupBotState, getGroupInfo } from "@sfmc-bds/sdk/node/qq-official";
2
+ import { log } from "../log.js";
6
3
  import {
7
4
  fetchBindMe,
8
- fetchJoinPending,
9
- fetchJoinSettings,
5
+ fetchContent,
6
+ fetchQqEventSettings,
10
7
  fetchSfmcStatus,
11
- postAdminKick,
12
8
  postBindRequest,
13
9
  postBindUnbind,
14
- postJoinDecide,
15
- postJoinRequest,
16
- postJoinSettings
10
+ postQqEventSettings
17
11
  } from "./db-api.js";
18
- import { buildJoinSettingsPanel, settingsFromResponse } from "./join-settings-ui.js";
19
- import { formatCommandMenu } from "./menu-format.js";
12
+ import { formatAccountCard } from "./account-format.js";
13
+ import { formatModuleLines, formatPackLines, formatPackMarkdown } from "./content-format.js";
14
+ import { formatCard, formatCommandMenu } from "./menu-format.js";
20
15
  function formatUptime(startedAt) {
21
16
  const ms = Math.max(0, Date.now() - startedAt);
22
17
  const s = Math.floor(ms / 1e3);
@@ -33,26 +28,25 @@ function dbEp(ctx) {
33
28
  return { host, port };
34
29
  }
35
30
  function isAdmin(ctx) {
36
- const admins = ctx.runtimeInfo.adminOpenids ?? [];
37
- if (admins.length > 0 && admins.includes(ctx.inbound.userId)) return true;
38
- return ctx.inbound.isGroupAdmin === true;
31
+ return ctx.adminAuthorized === true;
32
+ }
33
+ function failure(error) {
34
+ log.warn(`QQ \u64CD\u4F5C\u5931\u8D25: ${String(error)}`);
35
+ return { text: "\u64CD\u4F5C\u6682\u65F6\u672A\u5B8C\u6210\uFF0C\u8BF7\u7A0D\u540E\u67E5\u8BE2\u72B6\u6001\u6216\u8054\u7CFB\u7BA1\u7406\u5458\uFF1B\u8BF7\u52FF\u8FDE\u7EED\u91CD\u590D\u63D0\u4EA4\u4FEE\u6539\u3002" };
39
36
  }
40
37
  function asGroupAdminField(ctx) {
41
38
  return ctx.inbound.isGroupAdmin === true;
42
39
  }
43
40
  async function fetchQqGroupLines(ctx) {
44
41
  const creds = ctx.runtimeInfo.officialCreds;
45
- const gid = ctx.runtimeInfo.groupOpenid || ctx.inbound.groupId;
42
+ const gid = ctx.runtimeInfo.groupOpenid || (ctx.inbound.scene !== "c2c" ? ctx.inbound.groupId : "");
46
43
  if (!creds || !gid || ctx.inbound.backend !== "official") return [];
47
44
  const lines = [];
48
45
  try {
49
- const [infoRes, stateRes] = await Promise.all([
50
- getGroupInfo(creds, gid),
51
- getGroupBotState(creds, gid)
52
- ]);
46
+ const [infoRes, stateRes] = await Promise.all([getGroupInfo(creds, gid), getGroupBotState(creds, gid)]);
53
47
  if (infoRes.ok && infoRes.json && typeof infoRes.json === "object") {
54
48
  const j = infoRes.json;
55
- lines.push(`QQ\u7FA4\uFF1A${j.group_name ?? "\u2014"} \xB7 \u6210\u5458 ${j.group_member_num ?? "\u2014"}`);
49
+ lines.push(`QQ\u7FA4\uFF1A${j["group_name"] ?? "\u2014"} \xB7 \u6210\u5458 ${j["group_member_num"] ?? "\u2014"}`);
56
50
  } else if (!infoRes.ok) {
57
51
  const hint = /11253/.test(infoRes.error || infoRes.body || "") ? "\u7FA4\u4FE1\u606F\u63A5\u53E3\u672A\u5F00\u901A\u767D\u540D\u5355(11253)" : `\u7FA4\u4FE1\u606F\u5931\u8D25\uFF1A${(infoRes.error || "").slice(0, 60)}`;
58
52
  lines.push(hint);
@@ -60,7 +54,7 @@ async function fetchQqGroupLines(ctx) {
60
54
  if (stateRes.ok && stateRes.json && typeof stateRes.json === "object") {
61
55
  const j = stateRes.json;
62
56
  lines.push(
63
- `\u673A\u5668\u4EBA\uFF1A\u89D2\u8272=${j.member_role ?? "\u2014"} \xB7 \u4E3B\u52A8\u63A8\u9001=${j.allow_proactive_msg === true ? "\u5F00" : "\u5173"} \xB7 \u6536\u6D88\u606F=${j.recv_msg_setting ?? "\u2014"}`
57
+ `\u673A\u5668\u4EBA\uFF1A\u89D2\u8272=${j["member_role"] ?? "\u2014"} \xB7 \u4E3B\u52A8\u63A8\u9001=${j["allow_proactive_msg"] === true ? "\u5F00" : "\u5173"} \xB7 \u6536\u6D88\u606F=${j["recv_msg_setting"] ?? "\u2014"}`
64
58
  );
65
59
  }
66
60
  } catch (e) {
@@ -69,143 +63,158 @@ async function fetchQqGroupLines(ctx) {
69
63
  return lines;
70
64
  }
71
65
  function createMenuHandler(registry) {
72
- return () => {
73
- const cmds = registry.userMenu().filter((c) => c.name !== "menu");
66
+ return (ctx) => formatCommandMenu({
67
+ title: "\u4E1C\u65B9\u72AC\u660E\u6E56",
68
+ home: true,
69
+ subtitle: "\u6B22\u8FCE\u56DE\u6765\uFF0C\u4ECA\u5929\u4E5F\u4E00\u8D77\u73A9\u5427 (\u2267\u2207\u2266)\uFF89\n\n\u67E5\u8BE2\u670D\u52A1\u5668\u4FE1\u606F\uFF0C\u6216\u7BA1\u7406\u4F60\u7684\u6E38\u620F\u8D26\u53F7\u3002",
70
+ cmds: registry.all().filter((c) => c.group === "home" && c.name !== "menu" && (c.permission !== "admin" || isAdmin(ctx))),
71
+ idPrefix: "home",
72
+ footerMd: "",
73
+ footerText: ""
74
+ });
75
+ }
76
+ function createAdminMenuHandler(registry) {
77
+ return (ctx) => {
78
+ if (!isAdmin(ctx)) return { text: "\u5F53\u524D\u6CA1\u6709\u7BA1\u7406\u6743\u9650\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u3002" };
74
79
  return formatCommandMenu({
75
- title: "SFMC \u6307\u4EE4",
76
- subtitle: "\u70B9\u6309\u94AE\u6216\u53D1\u540D\u79F0/\u7F16\u53F7\uFF1B\u7BA1\u7406\u5458\u53D1\u300C\u7BA1\u7406\u300D\u6253\u5F00\u7BA1\u7406\u83DC\u5355\u3002",
77
- cmds,
78
- idPrefix: "cmd",
79
- footerMd: "_\u5B98\u65B9\u4EC5 @\u673A\u5668\u4EBA \u89E6\u53D1\uFF1B\u6E38\u620F\u804A\u5929\u9700\u914D\u7F6E `bridge_channel_id`\u3002_",
80
- footerText: "\u5B98\u65B9\u4EC5 @\u673A\u5668\u4EBA \u89E6\u53D1\uFF1B\u6E38\u620F\u804A\u5929\u9700\u914D\u7F6E bridge_channel_id\u3002"
80
+ title: "\u670D\u52A1\u5668\u7BA1\u7406",
81
+ subtitle: "\u7BA1\u7406\u670D\u52A1\u5668\u4E0E\u7FA4\u63A8\u9001\u3002",
82
+ cmds: registry.adminMenu(),
83
+ idPrefix: "admin",
84
+ footerMd: "",
85
+ footerText: ""
81
86
  });
82
87
  };
83
88
  }
84
- function createAdminMenuHandler(registry) {
89
+ function sectionHandler(registry, group, title) {
90
+ return () => formatCommandMenu({
91
+ title,
92
+ subtitle: "\u67E5\u770B\u8FD0\u884C\u72B6\u6001\u3001\u5728\u7EBF\u73A9\u5BB6\u548C\u8FDE\u63A5\u65B9\u5F0F\u3002",
93
+ cmds: registry.all().filter((c) => c.group === group),
94
+ idPrefix: group,
95
+ footerMd: "",
96
+ footerText: ""
97
+ });
98
+ }
99
+ function helpHandler(registry) {
85
100
  return (ctx) => {
86
- if (!isAdmin(ctx)) {
87
- return { text: "\u4EC5\u7BA1\u7406\u5458\u53EF\u6253\u5F00\u7BA1\u7406\u83DC\u5355\uFF08\u9700\u5728 qq_admin_openids\uFF0C\u6216\u5F00\u542F\u7FA4\u7BA1\u89C6\u4F5C\u7BA1\u7406\u5458\uFF09" };
88
- }
89
- const cmds = registry.adminMenu();
90
- if (cmds.length === 0) {
91
- return { text: "\u6682\u65E0\u7BA1\u7406\u6307\u4EE4" };
101
+ const trigger = ctx.inbound.backend === "official" ? "\u7FA4\u5185\u5F00\u542F\u300C\u63A5\u6536\u6240\u6709\u6D88\u606F\u300D\u540E\u53EF\u76F4\u63A5\u53D1\u9001\u6307\u4EE4\uFF1B\u5426\u5219\u8BF7 @\u673A\u5668\u4EBA\u3002\u5355\u804A\u76F4\u63A5\u53D1\u9001\u3002" : "\u76F4\u63A5\u53D1\u9001\u547D\u4EE4\uFF0C\u6216\u5728\u83DC\u5355\u6253\u5F00\u540E 60 \u79D2\u5185\u56DE\u590D\u7F16\u53F7\u3002";
102
+ const sections = [
103
+ ["server", "\u670D\u52A1\u5668"],
104
+ ["account", "\u6211\u7684\u8D26\u53F7"],
105
+ ["admin", "\u7BA1\u7406"]
106
+ ];
107
+ const lines = [trigger];
108
+ for (const [group, title] of sections) {
109
+ const commands = registry.all().filter((c) => c.group === group && (c.permission !== "admin" || isAdmin(ctx)));
110
+ if (!commands.length) continue;
111
+ lines.push(
112
+ "",
113
+ `\u3010${title}\u3011`,
114
+ ...commands.map((c) => `${c.name === "ip" ? "ip" : c.aliases[0] || c.name}\uFF1A${c.description}`)
115
+ );
92
116
  }
93
- return formatCommandMenu({
94
- title: "SFMC \u7BA1\u7406",
95
- subtitle: "\u4EE5\u4E0B\u4E3A\u7BA1\u7406\u6307\u4EE4\uFF1B\u4E5F\u53EF\u76F4\u63A5\u53D1\u300C\u8E22\u4EBA\u300D\u300C\u5F85\u5BA1\u300D\u7B49\u89E6\u53D1\u8BCD\u3002",
96
- cmds,
97
- idPrefix: "adm",
98
- footerMd: "_\u654F\u611F\u64CD\u4F5C\u8BF7\u786E\u8BA4\u5BF9\u8C61\u6B63\u786E\u3002_",
99
- footerText: "\u654F\u611F\u64CD\u4F5C\u8BF7\u786E\u8BA4\u5BF9\u8C61\u6B63\u786E\u3002"
100
- });
117
+ lines.push("", "\u53D1\u9001 \u83DC\u5355 \u8FD4\u56DE\u9996\u9875\uFF0C\u53D1\u9001 \u53D6\u6D88 \u53D6\u6D88\u5F85\u786E\u8BA4\u64CD\u4F5C\u3002", "\u539F\u6709\u82F1\u6587\u547D\u4EE4\u53CA /\u547D\u4EE4 \u65B9\u5F0F\u4ECD\u53EF\u4F7F\u7528\u3002");
118
+ return formatCard("\u4F7F\u7528\u5E2E\u52A9", lines);
101
119
  };
102
120
  }
103
- const pingHandler = (ctx) => {
104
- const { runtimeInfo, startedAt, inbound } = ctx;
105
- const parts = ["pong", `backend=${inbound.backend}`, `uptime=${formatUptime(startedAt)}`];
106
- if (inbound.backend === "official") {
107
- parts.push(`sandbox=${runtimeInfo.sandbox ? "true" : "false"}`);
108
- if (runtimeInfo.appIdHint) parts.push(`app=${runtimeInfo.appIdHint}`);
109
- }
110
- return { text: parts.join(" \xB7 ") };
111
- };
112
- const whoamiHandler = async (ctx) => {
113
- const { inbound } = ctx;
114
- const lines = [`\u4F60\u662F ${inbound.userName}`, `id=${inbound.userId}`, `group=${inbound.groupId}`];
121
+ const serverVersionHandler = async (ctx) => {
115
122
  const ep = dbEp(ctx);
116
123
  if (ep) {
117
124
  try {
118
- const me = await fetchBindMe(ep, inbound.userId);
119
- if (me.bound && me.binding) {
120
- lines.push(`\u5DF2\u7ED1\u5B9A MC\uFF1A${me.binding.player_name || "?"} (${me.binding.player_xuid || "?"})`);
121
- } else {
122
- lines.push("\u672A\u7ED1\u5B9A MC\uFF08\u53D1\u9001\u300C\u7ED1\u5B9A\u300D\u83B7\u53D6\u9A8C\u8BC1\u7801\uFF09");
123
- }
124
- } catch {
125
- lines.push("\u7ED1\u5B9A\u67E5\u8BE2\u5931\u8D25\uFF08db-server \u4E0D\u53EF\u8FBE\uFF09");
125
+ const status = await fetchSfmcStatus(ep);
126
+ const bds = status.processes?.bds;
127
+ if (bds?.state === "running" && bds.version)
128
+ return formatCard("\u7248\u672C", ["\u6E38\u620F\u7C7B\u578B\uFF1A\u57FA\u5CA9\u7248", `\u5F53\u524D BDS \u7248\u672C\uFF1A${bds.version}`]);
129
+ } catch (error) {
130
+ log.warn(`BDS \u7248\u672C\u67E5\u8BE2\u5931\u8D25: ${String(error)}`);
126
131
  }
127
132
  }
128
- return { text: lines.join("\n") };
133
+ return {
134
+ text: ctx.runtimeInfo.publicServer?.version ? `\u6682\u65F6\u65E0\u6CD5\u786E\u8BA4\u5B9E\u9645 BDS \u7248\u672C\u3002
135
+ \u7BA1\u7406\u5458\u63D0\u4F9B\u7684\u5165\u670D\u8BF4\u660E\uFF1A${ctx.runtimeInfo.publicServer.version}` : "\u6682\u65F6\u65E0\u6CD5\u786E\u8BA4\u5B9E\u9645 BDS \u7248\u672C\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u786E\u8BA4\u5165\u670D\u7248\u672C\u3002"
136
+ };
137
+ };
138
+ const serverAddressHandler = (ctx) => {
139
+ const info = ctx.runtimeInfo.publicServer;
140
+ return formatCard(
141
+ "\u8FDE\u63A5\u5730\u5740",
142
+ info?.address ? [
143
+ "\u6E38\u620F\u7C7B\u578B\uFF1A\u57FA\u5CA9\u7248",
144
+ `\u670D\u52A1\u5668\u5730\u5740\uFF1A${info.address}`,
145
+ `\u7AEF\u53E3\uFF1A${info.port ?? 19132}`,
146
+ "",
147
+ "\u5728\u6E38\u620F\u5185\u9009\u62E9\u300C\u6DFB\u52A0\u670D\u52A1\u5668\u300D\uFF0C\u586B\u5199\u4EE5\u4E0A\u4FE1\u606F\u3002"
148
+ ] : ["\u6682\u672A\u516C\u5E03\u670D\u52A1\u5668\u5730\u5740\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u83B7\u53D6\u3002"]
149
+ );
150
+ };
151
+ const pingHandler = (ctx) => ({
152
+ text: `\u673A\u5668\u4EBA\u8FDE\u63A5\u6B63\u5E38\uFF0C\u5DF2\u8FD0\u884C ${formatUptime(ctx.startedAt)}`
153
+ });
154
+ const whoamiHandler = async (ctx) => {
155
+ const ep = dbEp(ctx);
156
+ if (!ep) return failure("db \u672A\u914D\u7F6E");
157
+ const me = await fetchBindMe(ep, ctx.inbound.userId);
158
+ if (me.success === false) return failure("\u7ED1\u5B9A\u67E5\u8BE2\u5931\u8D25");
159
+ return {
160
+ ...formatAccountCard({
161
+ bound: !!me.bound,
162
+ ...me.binding?.player_name ? { playerName: me.binding.player_name } : {},
163
+ ...me.profile ? { profile: me.profile } : {}
164
+ }),
165
+ buttons: me.bound ? [{ id: "unbind", label: "\u89E3\u7ED1", command: "/unbind" }] : [{ id: "bind", label: "\u7ED1\u5B9A", command: "/bind" }]
166
+ };
129
167
  };
130
168
  const statusHandler = async (ctx) => {
131
169
  const ep = dbEp(ctx);
132
- if (!ep) return { text: "status\uFF1A\u672A\u914D\u7F6E db_host/db_port" };
170
+ if (!ep) return failure("db \u672A\u914D\u7F6E");
171
+ let st;
133
172
  try {
134
- const st = await fetchSfmcStatus(ep);
135
- const n = Array.isArray(st.online) ? st.online.length : 0;
136
- const day = st.world?.day != null ? String(st.world.day) : "\u2014";
137
- const diff = st.world?.difficulty ? String(st.world.difficulty) : "\u2014";
138
- const age = typeof st.updatedAt === "number" ? `${Math.max(0, Math.floor((Date.now() - st.updatedAt) / 1e3))}s\u524D` : "\u2014";
139
- const host = st.host;
140
- const bds = st.processes?.bds;
141
- const db = st.processes?.db;
142
- const hostUp = host?.uptimeText || "\u2014";
143
- const bdsLine = bds?.state === "running" ? `\u8FD0\u884C\u4E2D \xB7 ${bds.uptimeText || "\u2014"}${bds.pid ? ` (PID ${bds.pid})` : ""}` : "\u672A\u8FD0\u884C";
144
- const mem = host?.memory != null ? `${host.memory.usedMb ?? "\u2014"}/${host.memory.totalMb ?? "\u2014"} MB (${host.memory.usedPercent ?? "\u2014"}%)` : "\u2014";
145
- const cpu = host?.cpu != null ? `${host.cpu.cores ?? "\u2014"} \u6838 \xB7 ${host.cpu.model || "\u2014"}` : "\u2014";
146
- const machine = host ? `${host.hostname || "\u2014"} \xB7 ${host.platform || "\u2014"}/${host.arch || "\u2014"} ${host.release || ""}`.trim() : "\u2014";
147
- const note = st.note ? `
148
- ${st.note}` : n === 0 ? "\n\u6682\u65E0\u5728\u7EBF\u73A9\u5BB6\u6570\u636E" : "";
149
- const qqLines = await fetchQqGroupLines(ctx);
150
- const text = [
151
- "\u670D\u52A1\u5668\u72B6\u6001",
152
- `\u5728\u7EBF\uFF1A${n} \u4EBA`,
153
- `\u4E16\u754C\u65E5\uFF1A${day}`,
154
- `\u96BE\u5EA6\uFF1A${diff}`,
155
- `\u4E3B\u673A\uFF1A${machine}`,
156
- `\u4E3B\u673A\u8FD0\u884C\uFF1A${hostUp}`,
157
- `BDS\uFF1A${bdsLine}`,
158
- `\u5185\u5B58\uFF1A${mem}`,
159
- `CPU\uFF1A${cpu}`,
160
- `db-server\uFF1A${db?.uptimeText || "\u2014"}${db?.pid ? ` (PID ${db.pid})` : ""}`,
161
- ...qqLines,
162
- `\u6570\u636E\uFF1A${age}${note}`
163
- ].join("\n");
164
- const markdown = [
165
- "## \u670D\u52A1\u5668\u72B6\u6001",
166
- "",
167
- `- **\u5728\u7EBF**\uFF1A${n} \u4EBA`,
168
- `- **\u4E16\u754C\u65E5**\uFF1A${day}`,
169
- `- **\u96BE\u5EA6**\uFF1A${diff}`,
170
- `- **\u4E3B\u673A**\uFF1A${machine}`,
171
- `- **\u4E3B\u673A\u8FD0\u884C**\uFF1A${hostUp}`,
172
- `- **BDS**\uFF1A${bdsLine}`,
173
- `- **\u5185\u5B58**\uFF1A${mem}`,
174
- `- **CPU**\uFF1A${cpu}`,
175
- `- **db-server**\uFF1A${db?.uptimeText || "\u2014"}${db?.pid ? ` (PID ${db.pid})` : ""}`,
176
- ...qqLines.map((l) => `- ${l}`),
177
- `- **\u6570\u636E**\uFF1A${age}`,
178
- note ? `
179
- _${(st.note || "\u6682\u65E0\u5728\u7EBF\u73A9\u5BB6\u6570\u636E").trim()}_` : ""
180
- ].filter((line) => line !== "").join("\n");
181
- return { text, markdown };
182
- } catch (e) {
183
- return { text: `status \u5931\u8D25\uFF1A${e.message}` };
173
+ st = await fetchSfmcStatus(ep);
174
+ } catch (error) {
175
+ log.warn(`\u67E5\u670D\u72B6\u6001\u63A5\u53E3\u4E0D\u53EF\u7528: ${String(error)}`);
176
+ return formatCard("\u670D\u52A1\u5668\u72B6\u6001", ["\u6682\u65F6\u65E0\u6CD5\u8FDE\u63A5\u72B6\u6001\u670D\u52A1\uFF0C\u65E0\u6CD5\u786E\u8BA4\u6E38\u620F\u670D\u52A1\u5668\u662F\u5426\u8FD0\u884C\u3002", "\u8BF7\u7A0D\u540E\u91CD\u8BD5\u6216\u8054\u7CFB\u7BA1\u7406\u5458\u3002"]);
184
177
  }
178
+ const state = st.processes?.bds;
179
+ const running = state?.state === "running" || state?.running === true;
180
+ const stopped = state?.state === "stopped" || state?.running === false;
181
+ const onlineText = stopped ? "0 \u4EBA" : st.note || !Array.isArray(st.online) ? "\u6570\u636E\u6682\u4E0D\u53EF\u786E\u8BA4" : `${st.online.length} \u4EBA`;
182
+ const age = typeof st.updatedAt === "number" ? `${Math.max(0, Math.floor((Date.now() - st.updatedAt) / 1e3))} \u79D2\u524D` : "\u672A\u77E5";
183
+ return {
184
+ ...formatCard("\u670D\u52A1\u5668\u72B6\u6001", [
185
+ `\u8FD0\u884C\u72B6\u6001\uFF1A${running ? "\u8FD0\u884C\u4E2D" : stopped ? "\u672A\u8FD0\u884C" : "\u6682\u65F6\u65E0\u6CD5\u786E\u8BA4"}`,
186
+ `\u5728\u7EBF\u73A9\u5BB6\uFF1A${onlineText}`,
187
+ `\u4E16\u754C\u65E5\uFF1A${st.world?.day ?? "\u672A\u77E5"}`,
188
+ `\u96BE\u5EA6\uFF1A${st.world?.difficulty ?? "\u672A\u77E5"}`,
189
+ `\u6570\u636E\u66F4\u65B0\uFF1A${age}`,
190
+ ...st.note && running ? ["\u5728\u7EBF\u6570\u636E\u6682\u4E0D\u53EF\u786E\u8BA4\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002"] : []
191
+ ]),
192
+ buttons: [
193
+ { id: "online", label: "\u5728\u7EBF\u73A9\u5BB6", command: "/online" },
194
+ { id: "version", label: "\u7248\u672C", command: "/version" },
195
+ { id: "ip", label: "\u8FDE\u63A5\u5730\u5740", command: "/ip" },
196
+ { id: "refresh_status", label: "\u5237\u65B0\u72B6\u6001", command: "/status" }
197
+ ]
198
+ };
185
199
  };
186
- const ONLINE_CAP = 30;
187
200
  const onlineHandler = async (ctx) => {
188
201
  const ep = dbEp(ctx);
189
- if (!ep) return { text: "online\uFF1A\u672A\u914D\u7F6E db_host/db_port" };
190
- try {
191
- const st = await fetchSfmcStatus(ep);
192
- const list = Array.isArray(st.online) ? st.online : [];
193
- if (list.length === 0) {
194
- return { text: st.note || "\u6682\u65E0\u5728\u7EBF\u73A9\u5BB6" };
195
- }
196
- const shown = list.slice(0, ONLINE_CAP);
197
- const names = shown.map((p, i) => `${i + 1}. ${p.name || p.id || "?"}`).join("\n");
198
- const more = list.length > ONLINE_CAP ? `
199
- \u2026\u53E6\u6709 ${list.length - ONLINE_CAP} \u4EBA\u672A\u5217\u51FA` : "";
200
- return { text: `\u5728\u7EBF ${list.length} \u4EBA
201
- ${names}${more}` };
202
- } catch (e) {
203
- return { text: `online \u5931\u8D25\uFF1A${e.message}` };
204
- }
202
+ if (!ep) return failure("db \u672A\u914D\u7F6E");
203
+ const st = await fetchSfmcStatus(ep);
204
+ if (st.processes?.bds?.state === "stopped") return formatCard("\u5728\u7EBF\u73A9\u5BB6", ["\u670D\u52A1\u5668\u672A\u8FD0\u884C\uFF0C\u5F53\u524D\u6CA1\u6709\u5728\u7EBF\u73A9\u5BB6\u3002"]);
205
+ if (!Array.isArray(st.online) || st.note) return { text: "\u5728\u7EBF\u6570\u636E\u6682\u4E0D\u53EF\u786E\u8BA4\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002" };
206
+ if (!st.online.length) return formatCard("\u5728\u7EBF\u73A9\u5BB6", ["\u5F53\u524D\u6CA1\u6709\u5728\u7EBF\u73A9\u5BB6\u3002"]);
207
+ return {
208
+ ...formatCard("\u5728\u7EBF\u73A9\u5BB6", [
209
+ `\u5F53\u524D\u5728\u7EBF ${st.online.length} \u4EBA`,
210
+ "",
211
+ ...st.online.map((p, i) => `${i + 1}. ${p.name || "\u672A\u547D\u540D\u73A9\u5BB6"}`)
212
+ ])
213
+ };
205
214
  };
206
215
  const bindHandler = async (ctx) => {
207
216
  const ep = dbEp(ctx);
208
- if (!ep) return { text: "\u7ED1\u5B9A\uFF1A\u672A\u914D\u7F6E db_host/db_port" };
217
+ if (!ep) return failure("db \u672A\u914D\u7F6E");
209
218
  try {
210
219
  const { status, data } = await postBindRequest(ep, {
211
220
  openid: ctx.inbound.userId,
@@ -217,442 +226,301 @@ const bindHandler = async (ctx) => {
217
226
  };
218
227
  }
219
228
  if (!data.success || !data.code) {
220
- return { text: `\u7533\u8BF7\u7ED1\u5B9A\u5931\u8D25\uFF1A${data.error || `HTTP ${status}`}` };
229
+ return failure(data.error);
221
230
  }
222
- return {
223
- text: `\u7ED1\u5B9A\u7801\uFF1A${data.code}
224
- \u8BF7\u5728\u6E38\u620F\u5185\u8F93\u5165 !bind\uFF0C\u7136\u540E\u5728 60 \u79D2\u5185\u53D1\u9001\u6B64\u6570\u5B57\u7801\uFF08\u4E0D\u8981\u5E26 !\uFF09\u3002`,
225
- markdown: `## \u7ED1\u5B9A\u7801 \`${data.code}\`
226
-
227
- 1. \u6E38\u620F\u5185\u6267\u884C **!bind**
228
- 2. \u5728 **60 \u79D2\u5185**\u53D1\u9001\u672C\u6570\u5B57\u7801\uFF08\u4E0D\u8981\u5E26 \`!\`\uFF09
229
-
230
- _\u8D85\u65F6\u8BF7\u91CD\u65B0\u7533\u8BF7_`
231
- };
231
+ return formatCard("\u7ED1\u5B9A\u8D26\u53F7", [
232
+ `\u7ED1\u5B9A\u7801\uFF1A${data.code}`,
233
+ "",
234
+ "\u8FDB\u5165\u670D\u52A1\u5668\u540E\u5B8C\u6210\u4E0B\u9762\u4E24\u6B65\uFF1A",
235
+ "1. \u5728\u6E38\u620F\u5185\u6267\u884C /c:bind",
236
+ "2. \u5728 60 \u79D2\u5185\u628A\u4EE5\u4E0A\u7EAF\u6570\u5B57\u9A8C\u8BC1\u7801\u53D1\u5230\u6E38\u620F\u804A\u5929",
237
+ "",
238
+ "\u7ED1\u5B9A\u6210\u529F\u540E\u5373\u53EF\u81EA\u7531\u6D3B\u52A8\u3002",
239
+ "\u8D85\u65F6\u8BF7\u91CD\u65B0\u53D1\u9001\u300C\u7ED1\u5B9A\u300D\u83B7\u53D6\u9A8C\u8BC1\u7801\u3002"
240
+ ]);
232
241
  } catch (e) {
233
- return { text: `\u7ED1\u5B9A\u5931\u8D25\uFF1A${e.message}` };
242
+ return failure(e);
234
243
  }
235
244
  };
236
245
  const unbindHandler = async (ctx) => {
237
246
  const ep = dbEp(ctx);
238
- if (!ep) return { text: "\u89E3\u7ED1\uFF1A\u672A\u914D\u7F6E db_host/db_port" };
239
- try {
240
- const { data } = await postBindUnbind(ep, { openid: ctx.inbound.userId });
241
- if (data.unbound) return { text: "\u5DF2\u89E3\u9664 QQ\u2194MC \u7ED1\u5B9A\u3002" };
242
- return { text: "\u5F53\u524D\u6CA1\u6709\u7ED1\u5B9A\u8BB0\u5F55\u3002" };
243
- } catch (e) {
244
- return { text: `\u89E3\u7ED1\u5931\u8D25\uFF1A${e.message}` };
245
- }
247
+ if (!ep) return failure("db \u672A\u914D\u7F6E");
248
+ const me = await fetchBindMe(ep, ctx.inbound.userId);
249
+ if (me.success === false) return failure("\u7ED1\u5B9A\u67E5\u8BE2\u5931\u8D25");
250
+ if (!me.bound || !me.binding) return { text: "\u5F53\u524D\u6CA1\u6709\u7ED1\u5B9A\u8BB0\u5F55\u3002" };
251
+ const original = me.binding;
252
+ return {
253
+ text: "\u786E\u8BA4\u89E3\u7ED1",
254
+ confirmation: {
255
+ summary: `\u5373\u5C06\u89E3\u9664\u4F60\u4E0E\u300C${original.player_name || "\u672A\u547D\u540D\u89D2\u8272"}\u300D\u7684\u7ED1\u5B9A\u3002\u4E4B\u540E\u9700\u8981\u91CD\u65B0\u7533\u8BF7\u9A8C\u8BC1\u7801\u624D\u80FD\u7ED1\u5B9A\u3002`,
256
+ execute: async (confirmed) => {
257
+ const current = await fetchBindMe(ep, confirmed.inbound.userId);
258
+ if (current.success === false) return failure("\u7ED1\u5B9A\u6838\u9A8C\u5931\u8D25");
259
+ if (!current.bound || current.binding?.player_xuid !== original.player_xuid || current.binding?.bound_at !== original.bound_at) {
260
+ return { text: "\u7ED1\u5B9A\u72B6\u6001\u5DF2\u53D8\u5316\uFF0C\u8BF7\u91CD\u65B0\u67E5\u8BE2\u300C\u6211\u7684\u8D26\u53F7\u300D\u540E\u518D\u64CD\u4F5C\u3002" };
261
+ }
262
+ const { data } = await postBindUnbind(ep, { openid: confirmed.inbound.userId });
263
+ if (data.success === false) return failure(data.error);
264
+ return { text: data.unbound ? "\u5DF2\u89E3\u9664 QQ \u4E0E\u6E38\u620F\u89D2\u8272\u7684\u7ED1\u5B9A\u3002" : "\u5F53\u524D\u6CA1\u6709\u7ED1\u5B9A\u8BB0\u5F55\u3002" };
265
+ }
266
+ }
267
+ };
246
268
  };
247
269
  const groupInfoHandler = async (ctx) => {
248
270
  const lines = await fetchQqGroupLines(ctx);
249
271
  if (lines.length === 0) {
250
- return { text: "\u7FA4\u4FE1\u606F\u4EC5\u5B98\u65B9\u540E\u7AEF\u53EF\u7528\uFF0C\u4E14\u9700\u914D\u7F6E qq_group_openid / \u51ED\u8BC1\u3002" };
272
+ return { text: "\u7FA4\u4FE1\u606F\u4EC5\u5B98\u65B9\u540E\u7AEF\u53EF\u7528\uFF0C\u4E14\u9700\u914D\u7F6E official.group_openid / \u51ED\u8BC1\u3002" };
251
273
  }
252
274
  return { text: ["QQ \u7FA4\u4FE1\u606F", ...lines].join("\n") };
253
275
  };
254
- const joinHandler = async (ctx) => {
276
+ const entryGuideHandler = () => formatCard("\u8FDB\u670D\u8BF4\u660E", [
277
+ "\u76F4\u63A5\u8FDB\u5165\u670D\u52A1\u5668\u5373\u53EF\u3002",
278
+ "",
279
+ "\u672A\u7ED1\u5B9A\u7684\u89D2\u8272\u53EF\u4EE5\u8FDB\u670D\uFF0C\u4F46\u65E0\u6CD5\u79FB\u52A8\uFF0C\u6743\u9650\u4E3A\u8BBF\u5BA2\u3002",
280
+ "\u804A\u5929\u680F\u4F1A\u63D0\u793A\u7ED1\u5B9A\u6B65\u9AA4\u3002",
281
+ "\u5728 QQ \u53D1\u9001\u300C\u7ED1\u5B9A\u300D\u83B7\u53D6\u9A8C\u8BC1\u7801\uFF0C\u8FDB\u670D\u540E\u6267\u884C /c:bind\uFF0C\u518D\u628A\u9A8C\u8BC1\u7801\u53D1\u5230\u6E38\u620F\u804A\u5929\u3002",
282
+ "\u7ED1\u5B9A\u6210\u529F\u540E\u5373\u53EF\u6B63\u5E38\u6E38\u73A9\u3002"
283
+ ]);
284
+ const retiredJoinFlowHandler = () => ({
285
+ text: "\u5165\u670D\u7533\u8BF7\u5DF2\u5173\u95ED\u3002\u76F4\u63A5\u8FDB\u5165\u670D\u52A1\u5668\uFF0C\u5728\u6E38\u620F\u5185\u6309\u804A\u5929\u63D0\u793A\u5B8C\u6210\u7ED1\u5B9A\u540E\u5373\u53EF\u6E38\u73A9\u3002\u7ED1\u5B9A\u6210\u529F\u4F1A\u5199\u5165\u767D\u540D\u5355\u5E76\u89E3\u9664\u8BBF\u5BA2\u9650\u5236\u3002"
286
+ });
287
+ const EVENT_SWITCHES = [
288
+ ["enabled", "\u603B\u5F00\u5173"],
289
+ ["start", "\u542F\u52A8"],
290
+ ["stop", "\u505C\u670D"],
291
+ ["crash", "\u5F02\u5E38\u9000\u51FA"],
292
+ ["join", "\u4E0A\u7EBF"],
293
+ ["leave", "\u4E0B\u7EBF"],
294
+ ["death", "\u6B7B\u4EA1"]
295
+ ];
296
+ function eventSettingsPanel(settings, prefix = "") {
297
+ const rows = [
298
+ ...EVENT_SWITCHES.map(([field, label]) => [label, settings[field] ? "\u5F00" : "\u5173"]),
299
+ ["\u805A\u5408\u95F4\u9694", `${settings.window_sec} \u79D2`]
300
+ ];
301
+ const textRows = rows.map(([label, value]) => `\u2502 ${label}${"\u3000".repeat(4 - label.length)} \u2502 ${value} \u2502`);
302
+ const buttons = EVENT_SWITCHES.map(([field, label]) => ({
303
+ id: `events_${field}`,
304
+ label: `${label} ${settings[field] ? "\u5173" : "\u5F00"}`,
305
+ command: `/events ${label} ${settings[field] ? "\u5173" : "\u5F00"}`
306
+ }));
307
+ buttons.push({ id: "events_window", label: "\u805A\u5408\u95F4\u9694", command: "/events \u95F4\u9694" });
308
+ return {
309
+ text: [
310
+ "\u63A8\u9001\u8BBE\u7F6E",
311
+ ...prefix ? [prefix] : [],
312
+ "\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510",
313
+ "\u2502 \u9879\u76EE\u3000\u3000 \u2502 \u72B6\u6001\u3000 \u2502",
314
+ "\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524",
315
+ ...textRows,
316
+ "\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518",
317
+ `\u73A9\u5BB6\u4E8B\u4EF6\u7EA6 ${settings.window_sec} \u79D2\u5408\u5E76\u53D1\u9001\uFF1B\u542F\u505C\u4E0E\u5F02\u5E38\u9000\u51FA\u5373\u65F6\u53D1\u9001\u3002`
318
+ ].join("\n"),
319
+ markdown: [
320
+ "## \u63A8\u9001\u8BBE\u7F6E",
321
+ ...prefix ? ["", prefix] : [],
322
+ "",
323
+ "| \u9879\u76EE | \u72B6\u6001 |",
324
+ "| --- | --- |",
325
+ ...rows.map(([label, value]) => `| ${label} | **${value}** |`),
326
+ "",
327
+ `_\u73A9\u5BB6\u4E8B\u4EF6\u7EA6 ${settings.window_sec} \u79D2\u5408\u5E76\u53D1\u9001\uFF1B\u542F\u505C\u4E0E\u5F02\u5E38\u9000\u51FA\u5373\u65F6\u53D1\u9001_`
328
+ ].join("\n"),
329
+ buttons
330
+ };
331
+ }
332
+ const eventSettingsHandler = async (ctx) => {
333
+ if (!isAdmin(ctx)) return { text: "\u5F53\u524D\u6CA1\u6709\u7BA1\u7406\u6743\u9650\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u3002" };
255
334
  const ep = dbEp(ctx);
256
- if (!ep) return { text: "\u7533\u8BF7\u5165\u670D\uFF1A\u672A\u914D\u7F6E db_host/db_port" };
257
- let playerName = ctx.inbound.text.replace(/^\/?(申请入服|join)\s*/i, "").trim();
258
- if (!playerName) {
259
- try {
260
- const me = await fetchBindMe(ep, ctx.inbound.userId);
261
- if (me.bound && me.binding?.player_name) playerName = String(me.binding.player_name);
262
- } catch {
263
- }
264
- }
265
- if (!playerName) {
266
- return { text: "\u7528\u6CD5\uFF1A\u7533\u8BF7\u5165\u670D <\u73A9\u5BB6\u540D>\n\uFF08\u5DF2\u7ED1\u5B9A\u53EF\u7701\u7565\u540D\u5B57\uFF09" };
267
- }
335
+ if (!ep) return failure("db \u672A\u914D\u7F6E");
336
+ const rest = ctx.inbound.text.replace(/^\/?(?:events|事件推送|事件配置)\s*/i, "").trim();
268
337
  try {
269
- const { data } = await postJoinRequest(ep, {
270
- openid: ctx.inbound.userId,
271
- player_name: playerName,
272
- qq_backend: ctx.inbound.backend
273
- });
274
- if (!data.success || !data.id) {
275
- if (data.error === "join_allowlist_disabled") {
276
- return { text: "\u5165\u670D\u767D\u540D\u5355\u5DF2\u5173\u95ED\uFF08\u7BA1\u7406\u5458\u53EF\u53D1\u300C\u914D\u7F6E \u767D\u540D\u5355 \u5F00\u300D\uFF09" };
277
- }
278
- return { text: `\u7533\u8BF7\u5931\u8D25\uFF1A${data.error || "unknown"}` };
338
+ if (!rest) {
339
+ const data = await fetchQqEventSettings(ep);
340
+ return data.success && data.settings ? eventSettingsPanel(data.settings) : failure(data.error);
279
341
  }
280
- if (data.auto_approved || data.status === "approved") {
342
+ if (rest === "\u95F4\u9694") {
343
+ const data = await fetchQqEventSettings(ep);
344
+ if (!data.success || !data.settings) return failure(data.error);
281
345
  return {
282
- text: data.note ? `\u300C${playerName}\u300D${data.note}` : `\u5DF2\u81EA\u52A8\u901A\u8FC7\u300C${playerName}\u300D\u5165\u670D\uFF0C\u7B49\u5F85 BDS \u5199\u5165\u767D\u540D\u5355`
346
+ ...formatCard("\u805A\u5408\u95F4\u9694", [
347
+ `\u5F53\u524D\uFF1A${data.settings.window_sec} \u79D2`,
348
+ "\u73A9\u5BB6\u8FDB\u51FA\u53CA\u6B7B\u4EA1\u901A\u77E5\u4F1A\u5728\u8FD9\u6BB5\u65F6\u95F4\u5185\u5408\u5E76\u3002",
349
+ "\u4E5F\u53EF\u53D1\u9001\u300C\u4E8B\u4EF6\u63A8\u9001 \u95F4\u9694 \u79D2\u6570\u300D\uFF0C\u8303\u56F4 5\u2013600 \u79D2\u3002"
350
+ ]),
351
+ buttons: [30, 60, 120].map((seconds) => ({
352
+ id: `events_window_${seconds}`,
353
+ label: `${seconds} \u79D2`,
354
+ command: `/events \u95F4\u9694 ${seconds}`
355
+ }))
283
356
  };
284
357
  }
285
- const admins = ctx.runtimeInfo.adminOpenids ?? [];
286
- const creds = ctx.runtimeInfo.officialCreds;
287
- const gid = ctx.runtimeInfo.groupOpenid || ctx.inbound.groupId;
288
- if (creds && gid && admins.length > 0 && ctx.inbound.backend === "official") {
289
- void sendGroupMessage(creds, {
290
- groupOpenid: gid,
291
- msgType: 2,
292
- markdown: `## \u5165\u670D\u7533\u8BF7
293
-
294
- \u73A9\u5BB6 **${playerName}**
295
- \u7533\u8BF7\u4EBA \`${ctx.inbound.userId.slice(0, 8)}\u2026\`
296
- \u8BF7\u6C42 id=\`${data.id}\``,
297
- keyboardButtons: [
298
- {
299
- id: `ap_${data.id}`,
300
- label: "\u901A\u8FC7",
301
- data: `join:approve:${data.id}`,
302
- actionType: 1,
303
- style: 1,
304
- permission: { type: 0, specify_user_ids: admins }
305
- },
306
- {
307
- id: `rj_${data.id}`,
308
- label: "\u62D2\u7EDD",
309
- data: `join:reject:${data.id}`,
310
- actionType: 1,
311
- style: 0,
312
- permission: { type: 0, specify_user_ids: admins }
313
- }
314
- ]
315
- }).catch(() => void 0);
316
- }
317
- const note = data.note ? `
318
- ${data.note}` : admins.length === 0 ? "\n\u672A\u914D\u7F6E qq_admin_openids\uFF0C\u7BA1\u7406\u5458\u6536\u4E0D\u5230\u901A\u77E5" : "";
319
- return {
320
- text: `\u5DF2\u63D0\u4EA4\u5165\u670D\u7533\u8BF7\u300C${playerName}\u300D${note}`,
321
- buttons: ctx.inbound.backend === "llbot" && admins.length > 0 ? [
322
- { id: "a", label: `\u901A\u8FC7 ${playerName}`, command: `/approve ${data.id}` },
323
- { id: "r", label: `\u62D2\u7EDD ${playerName}`, command: `/reject ${data.id}` }
324
- ] : void 0
325
- };
326
- } catch (e) {
327
- return { text: `\u7533\u8BF7\u5931\u8D25\uFF1A${e.message}` };
328
- }
329
- };
330
- function parseOnOff(raw) {
331
- const s = raw.trim().toLowerCase();
332
- if (["\u5F00", "\u5F00\u542F", "on", "true", "1", "yes"].includes(s)) return true;
333
- if (["\u5173", "\u5173\u95ED", "off", "false", "0", "no"].includes(s)) return false;
334
- return null;
335
- }
336
- const joinConfigHandler = async (ctx) => {
337
- if (!isAdmin(ctx)) return { text: "\u4EC5\u7BA1\u7406\u5458\u53EF\u914D\u7F6E\u5165\u670D\u5F00\u5173\uFF08qq_admin_openids / \u7FA4\u7BA1\u89C6\u4F5C\u7BA1\u7406\u5458\uFF09" };
338
- const ep = dbEp(ctx);
339
- if (!ep) return { text: "\u914D\u7F6E\uFF1A\u672A\u8FDE\u63A5 db" };
340
- const admins = ctx.runtimeInfo.adminOpenids ?? [];
341
- const rest = ctx.inbound.text.replace(/^\/?(配置|config|入服配置)\s*/i, "").trim();
342
- if (!rest) {
343
- try {
344
- const data = await fetchJoinSettings(ep);
345
- if (data.success === false) return { text: `\u8BFB\u53D6\u5931\u8D25\uFF1A${data.error || "unknown"}` };
346
- return buildJoinSettingsPanel({
347
- settings: settingsFromResponse(data),
348
- backend: ctx.inbound.backend,
349
- adminOpenids: admins
358
+ const interval = /^间隔\s+(\d+)$/.exec(rest);
359
+ if (interval) {
360
+ const seconds = Number(interval[1]);
361
+ if (!Number.isInteger(seconds) || seconds < 5 || seconds > 600) return { text: "\u805A\u5408\u95F4\u9694\u9700\u4E3A 5\u2013600 \u79D2\u7684\u6574\u6570\u3002" };
362
+ const data = await postQqEventSettings(ep, {
363
+ openid: ctx.inbound.userId,
364
+ as_group_admin: asGroupAdminField(ctx),
365
+ field: "window_sec",
366
+ value: seconds
350
367
  });
351
- } catch (e) {
352
- return { text: `\u8BFB\u53D6\u5931\u8D25\uFF1A${e.message}` };
353
- }
354
- }
355
- if (/^(群管|群管理员|treat|group.?admin)/i.test(rest)) {
356
- return {
357
- text: "\u300C\u7FA4\u7BA1\u89C6\u4F5C\u7BA1\u7406\u5458\u300D\u53EA\u80FD\u6539 configs/qq_link.json \u7684 treat_group_admins_as_admins\uFF0C\u7FA4\u804A\u4E0D\u53EF\u6539\u3002\u53D1\u300C\u914D\u7F6E\u300D\u53EF\u67E5\u770B\u5F53\u524D\u503C\u3002"
358
- };
359
- }
360
- const m = /^(白名单|allowlist|审批|approval)\s+(\S+)/i.exec(rest);
361
- if (!m) {
362
- return { text: "\u7528\u6CD5\uFF1A\u914D\u7F6E\n\u3000\u3000\u3000\u6216\u70B9\u9762\u677F\u6309\u94AE\u5207\u6362\u767D\u540D\u5355/\u5BA1\u6279" };
363
- }
364
- const which = m[1].toLowerCase();
365
- const onOff = parseOnOff(m[2]);
366
- if (onOff === null) return { text: "\u8BF7\u4F7F\u7528 \u5F00/\u5173\uFF08\u6216 on/off\uFF09" };
367
- const body = {
368
- openid: ctx.inbound.userId,
369
- as_group_admin: asGroupAdminField(ctx)
370
- };
371
- if (which === "\u767D\u540D\u5355" || which === "allowlist") body.allowlist_enabled = onOff;
372
- else body.require_approval = onOff;
373
- try {
374
- const { data } = await postJoinSettings(ep, body);
375
- if (data.success === false) {
376
- if (data.error === "not_admin") {
377
- return { text: "\u65E0\u6743\u9650\uFF08\u7FA4\u7BA1\u9700 configs/qq_link.json \u4E2D treat_group_admins_as_admins=true\uFF09" };
378
- }
379
- if (data.error === "immutable_field") {
380
- return { text: data.note || "\u8BE5\u5B57\u6BB5\u4E0D\u53EF\u7ECF\u7FA4\u804A\u4FEE\u6539" };
381
- }
382
- return { text: `\u4FDD\u5B58\u5931\u8D25\uFF1A${data.error || "unknown"}` };
368
+ return data.success && data.settings ? eventSettingsPanel(data.settings, "\u5DF2\u4FDD\u5B58") : failure(data.error);
383
369
  }
384
- return buildJoinSettingsPanel({
385
- settings: settingsFromResponse(data),
386
- backend: ctx.inbound.backend,
387
- adminOpenids: admins,
388
- prefix: "\u5DF2\u4FDD\u5B58"
389
- });
390
- } catch (e) {
391
- return { text: `\u4FDD\u5B58\u5931\u8D25\uFF1A${e.message}` };
392
- }
393
- };
394
- const pendingHandler = async (ctx) => {
395
- if (!isAdmin(ctx)) return { text: "\u4EC5\u7BA1\u7406\u5458\u53EF\u67E5\u770B\u5F85\u5BA1\u5217\u8868\uFF08\u914D\u7F6E qq_admin_openids\uFF09" };
396
- const ep = dbEp(ctx);
397
- if (!ep) return { text: "\u5F85\u5BA1\uFF1A\u672A\u914D\u7F6E db" };
398
- try {
399
- const data = await fetchJoinPending(ep, ctx.inbound.userId, asGroupAdminField(ctx));
400
- if (data.error) {
401
- if (data.error === "not_admin") {
402
- return { text: "\u65E0\u6743\u9650\uFF08\u7FA4\u7BA1\u9700 treat_group_admins_as_admins=true\uFF09" };
370
+ const match = /^(总开关|启动|停服|异常退出|上线|下线|死亡)\s+(开|关)$/.exec(rest);
371
+ if (!match) return { text: "\u53D1\u9001\u300C\u4E8B\u4EF6\u63A8\u9001\u300D\u6253\u5F00\u5F00\u5173\u9762\u677F\uFF0C\u6216\u9009\u62E9\u9762\u677F\u4E2D\u7684\u64CD\u4F5C\u3002" };
372
+ const item = EVENT_SWITCHES.find(([, label]) => label === match[1]);
373
+ if (!item) return { text: "\u6CA1\u6709\u8FD9\u4E2A\u4E8B\u4EF6\u5F00\u5173\u3002" };
374
+ const [field] = item;
375
+ const value = match[2] === "\u5F00";
376
+ const execute = async (confirmed) => {
377
+ try {
378
+ const data = await postQqEventSettings(ep, {
379
+ openid: confirmed.inbound.userId,
380
+ as_group_admin: asGroupAdminField(confirmed),
381
+ field,
382
+ value
383
+ });
384
+ return data.success && data.settings ? eventSettingsPanel(data.settings, "\u5DF2\u4FDD\u5B58") : failure(data.error);
385
+ } catch (error) {
386
+ return failure(error);
403
387
  }
404
- return { text: `\u5F85\u5BA1\u5931\u8D25\uFF1A${data.error}` };
405
- }
406
- const list = data.pending ?? [];
407
- if (list.length === 0) return { text: "\u6682\u65E0\u5F85\u5BA1\u5165\u670D\u7533\u8BF7" };
408
- const lines = list.map((r, i) => `${i + 1}. ${r.player_name || "?"} (${r.id})`);
409
- return { text: `\u5F85\u5BA1 ${list.length} \u6761
410
- ${lines.join("\n")}` };
411
- } catch (e) {
412
- return { text: `\u5F85\u5BA1\u5931\u8D25\uFF1A${e.message}` };
413
- }
414
- };
415
- const approveHandler = async (ctx) => {
416
- if (!isAdmin(ctx)) return { text: "\u4EC5\u7BA1\u7406\u5458\u53EF\u5BA1\u6279" };
417
- const ep = dbEp(ctx);
418
- if (!ep) return { text: "\u672A\u914D\u7F6E db" };
419
- const id = ctx.inbound.text.replace(/^\/?(approve|通过)\s*/i, "").trim();
420
- if (!id) return { text: "\u7528\u6CD5\uFF1A\u901A\u8FC7 <\u7533\u8BF7id>" };
421
- try {
422
- const { data } = await postJoinDecide(ep, {
423
- id,
424
- decision: "approve",
425
- decided_by: ctx.inbound.userId,
426
- as_group_admin: asGroupAdminField(ctx)
427
- });
428
- if (data.success === false && data.error === "not_admin") {
429
- return { text: "\u65E0\u6743\u9650\uFF08\u7FA4\u7BA1\u9700 treat_group_admins_as_admins=true\uFF09" };
430
- }
431
- return { text: data.success === false ? `\u5931\u8D25\uFF1A${data.error}` : `\u5DF2\u901A\u8FC7\u300C${data.player_name || id}\u300D` };
432
- } catch (e) {
433
- return { text: `\u5931\u8D25\uFF1A${e.message}` };
434
- }
435
- };
436
- const rejectHandler = async (ctx) => {
437
- if (!isAdmin(ctx)) return { text: "\u4EC5\u7BA1\u7406\u5458\u53EF\u5BA1\u6279" };
438
- const ep = dbEp(ctx);
439
- if (!ep) return { text: "\u672A\u914D\u7F6E db" };
440
- const id = ctx.inbound.text.replace(/^\/?(reject|拒绝)\s*/i, "").trim();
441
- if (!id) return { text: "\u7528\u6CD5\uFF1A\u62D2\u7EDD <\u7533\u8BF7id>" };
442
- try {
443
- const { data } = await postJoinDecide(ep, {
444
- id,
445
- decision: "reject",
446
- decided_by: ctx.inbound.userId,
447
- as_group_admin: asGroupAdminField(ctx)
448
- });
449
- if (data.success === false && data.error === "not_admin") {
450
- return { text: "\u65E0\u6743\u9650\uFF08\u7FA4\u7BA1\u9700 treat_group_admins_as_admins=true\uFF09" };
388
+ };
389
+ if (field === "enabled" && !value) {
390
+ return {
391
+ text: "\u786E\u8BA4\u5173\u95ED\u4E8B\u4EF6\u63A8\u9001",
392
+ confirmation: { summary: "\u5373\u5C06\u5173\u95ED\u5168\u90E8\u670D\u52A1\u5668\u4E8B\u4EF6\u7FA4\u63A8\u9001\u3002", execute }
393
+ };
451
394
  }
452
- return { text: data.success === false ? `\u5931\u8D25\uFF1A${data.error}` : `\u5DF2\u62D2\u7EDD\u300C${data.player_name || id}\u300D` };
453
- } catch (e) {
454
- return { text: `\u5931\u8D25\uFF1A${e.message}` };
395
+ return execute(ctx);
396
+ } catch (error) {
397
+ return failure(error);
455
398
  }
456
399
  };
457
- const channelHandler = async (ctx) => {
458
- const id = String(ctx.runtimeInfo.bridgeChannelId ?? "").trim();
459
- const lines = [
460
- "\u3010\u804A\u5929\u4E92\u901A\u3011",
461
- id ? `\u9891\u9053\uFF1A${id}` : "\u9891\u9053\uFF1A\u672A\u914D\u7F6E\uFF08\u8BF7\u8BBE qq_config.json \u2192 bridge_channel_id \u5E76\u91CD\u542F db/qq/BDS\uFF09",
462
- `\u540E\u7AEF\uFF1A${ctx.inbound.backend}${ctx.runtimeInfo.sandbox ? " \xB7 sandbox" : ""}`
463
- ];
400
+ const channelHandler = () => ({
401
+ text: "QQ\u7FA4\u6D88\u606F\u8FDB\u5165\u6E38\u620F\u5185\u53EA\u8BFB\u7684 QQ \u9891\u9053\u3002\u6E38\u620F\u5185\u5404\u9891\u9053\u53EF\u5206\u522B\u8BBE\u7F6E\u662F\u5426\u8F6C\u53D1\u5230 QQ\u3002"
402
+ });
403
+ const doctorHandler = async (ctx) => {
404
+ if (!isAdmin(ctx)) return { text: "\u5F53\u524D\u6CA1\u6709\u7BA1\u7406\u6743\u9650\u3002" };
464
405
  const ep = dbEp(ctx);
465
- if (!ep) {
466
- lines.push("db\uFF1A\u672A\u914D\u7F6E db_host/db_port");
467
- } else {
468
- try {
469
- const st = await fetchSfmcStatus(ep);
470
- const bds = st.processes?.bds;
471
- const db = st.processes?.db;
472
- lines.push(`db-server\uFF1A${db?.state === "stopped" || db?.running === false ? "\u672A\u8FD0\u884C" : db?.uptimeText || "\u53EF\u8FBE"}${db?.pid ? ` (PID ${db.pid})` : ""}`);
473
- lines.push(
474
- `BDS\uFF1A${bds?.state === "running" || bds?.running === true ? `\u8FD0\u884C\u4E2D \xB7 ${bds.uptimeText || "\u2014"}` : "\u672A\u8FD0\u884C"}`
475
- );
476
- } catch (e) {
477
- lines.push(`db\uFF1A\u4E0D\u53EF\u8FBE\uFF08${e.message}\uFF09`);
478
- }
479
- }
480
- lines.push("\u63D0\u793A\uFF1A\u5B98\u65B9\u4EC5 @\u673A\u5668\u4EBA \u7684\u975E\u6307\u4EE4\u6D88\u606F\u8FDB\u6E38\u620F\uFF1B\u6E38\u620F\u804A\u5929\u63A8\u7FA4\u8D70 MC\u2192QQ\u3002");
481
- const text = lines.join("\n");
406
+ if (!ep) return failure("db \u672A\u914D\u7F6E");
407
+ const st = await fetchSfmcStatus(ep);
408
+ const qq = await fetchQqGroupLines(ctx);
482
409
  return {
483
- text,
484
- markdown: text.split("\n").map((l, i) => i === 0 ? `## ${l.replace(/【|】/g, "")}` : `- ${l}`).join("\n")
410
+ text: [
411
+ "\u7BA1\u7406\u81EA\u68C0",
412
+ `\u540E\u7AEF\uFF1A${ctx.inbound.backend}`,
413
+ `QQ \u5165\u7AD9\u9891\u9053\uFF1A${ctx.runtimeInfo.bridgeChannelId || "qq"}`,
414
+ `\u4E3B\u673A\uFF1A${st.host?.hostname || "\u672A\u77E5"} \xB7 ${st.host?.platform || "\u672A\u77E5"}`,
415
+ `CPU\uFF1A${st.host?.cpu?.model || "\u672A\u77E5"} \xB7 ${st.host?.cpu?.cores ?? "\u672A\u77E5"} \u6838`,
416
+ `\u5185\u5B58\uFF1A${st.host?.memory?.usedMb ?? "\u672A\u77E5"}/${st.host?.memory?.totalMb ?? "\u672A\u77E5"} MB`,
417
+ `\u4E3B\u673A\u8FD0\u884C\uFF1A${st.host?.uptimeText || "\u672A\u77E5"}`,
418
+ `BDS\uFF1A${st.processes?.bds?.state || "\u672A\u77E5"} \xB7 PID ${st.processes?.bds?.pid ?? "\u672A\u77E5"} \xB7 ${st.processes?.bds?.uptimeText || "\u672A\u77E5"}`,
419
+ `\u6570\u636E\u5E93\uFF1APID ${st.processes?.db?.pid ?? "\u672A\u77E5"} \xB7 ${st.processes?.db?.uptimeText || "\u672A\u77E5"}`,
420
+ ...qq
421
+ ].join("\n")
485
422
  };
486
423
  };
487
- const doctorHandler = async (ctx) => {
488
- if (!isAdmin(ctx)) return { text: "\u4EC5\u7BA1\u7406\u5458\u53EF\u6267\u884C\u81EA\u68C0" };
489
- const base = await channelHandler(ctx);
490
- const extra = ["", "\u3010\u7BA1\u7406\u81EA\u68C0\u3011"];
491
- const admins = ctx.runtimeInfo.adminOpenids ?? [];
492
- extra.push(`\u7BA1\u7406\u5458 openid \u6570\uFF1A${admins.length}${admins.length === 0 ? "\uFF08\u7A7A\u5219\u65E0\u6CD5\u5BA1\u6279/\u8E22\u4EBA\uFF09" : ""}`);
493
- extra.push(`\u7FA4 openid\uFF1A${ctx.runtimeInfo.groupOpenid ? "\u5DF2\u914D\u7F6E" : "\u672A\u914D\u7F6E"}`);
494
- if (ctx.inbound.backend === "official") {
495
- extra.push(`\u51ED\u8BC1\uFF1A${ctx.runtimeInfo.officialCreds ? "\u5DF2\u6CE8\u5165" : "\u7F3A\u5931"}`);
424
+ const modulesHandler = async (ctx) => {
425
+ const ep = dbEp(ctx);
426
+ if (!ep) return failure("db \u672A\u914D\u7F6E");
427
+ const keyword = ctx.inbound.text.replace(/^\/?(模块|modules)\s*/i, "").trim();
428
+ try {
429
+ const data = await fetchContent(ep);
430
+ if (data.success === false) return failure(data.error);
431
+ return formatCard("\u6A21\u5757", formatModuleLines(data.modules ?? [], keyword));
432
+ } catch (error) {
433
+ return failure(error);
496
434
  }
497
- const text = `${base.text}
498
- ${extra.join("\n")}`;
499
- return { text, markdown: `${base.markdown ?? base.text}
500
-
501
- ### \u7BA1\u7406\u81EA\u68C0
502
-
503
- ${extra.filter(Boolean).map((l) => `- ${l}`).join("\n")}` };
504
435
  };
505
- const kickHandler = async (ctx) => {
506
- if (!isAdmin(ctx)) return { text: "\u4EC5\u7BA1\u7406\u5458\u53EF\u8E22\u4EBA\uFF08\u914D\u7F6E qq_admin_openids\uFF09" };
436
+ const packsHandler = async (ctx) => {
507
437
  const ep = dbEp(ctx);
508
- if (!ep) return { text: "\u8E22\u4EBA\uFF1A\u672A\u914D\u7F6E db" };
509
- const target = ctx.inbound.text.replace(/^\/?(踢人|kick)\s*/i, "").trim();
510
- if (!target) return { text: "\u7528\u6CD5\uFF1A\u8E22\u4EBA <\u73A9\u5BB6\u540D>" };
438
+ if (!ep) return failure("db \u672A\u914D\u7F6E");
511
439
  try {
512
- const { data } = await postAdminKick(ep, {
513
- openid: ctx.inbound.userId,
514
- target_name: target,
515
- reason: "QQ \u7BA1\u7406\u5458\u8E22\u51FA",
516
- as_group_admin: asGroupAdminField(ctx)
517
- });
518
- if (!data.success) {
519
- if (data.error === "not_admin") {
520
- return { text: "\u65E0\u6743\u9650\uFF08\u7FA4\u7BA1\u9700 treat_group_admins_as_admins=true\uFF09" };
521
- }
522
- return { text: `\u8E22\u4EBA\u5165\u961F\u5931\u8D25\uFF1A${data.error || "unknown"}` };
523
- }
524
- return { text: `\u5DF2\u5C06\u300C${target}\u300D\u8E22\u4EBA\u8BF7\u6C42\u5165\u961F\uFF0C\u7B49\u5F85 BDS \u6267\u884C\u3002` };
525
- } catch (e) {
526
- return { text: `\u8E22\u4EBA\u5931\u8D25\uFF1A${e.message}` };
440
+ const data = await fetchContent(ep);
441
+ if (data.success === false) return failure(data.error);
442
+ const packs = data.packs ?? (data.resource_packs ?? []).map((pack) => ({
443
+ kind: "resource",
444
+ enabled: true,
445
+ ...pack.name ? { name: pack.name } : {},
446
+ ...pack.pack_id ? { pack_id: pack.pack_id } : {},
447
+ ...pack.version ? { version: pack.version } : {}
448
+ }));
449
+ return {
450
+ text: ["\u4E16\u754C\u5305", "", ...formatPackLines(packs, data.note)].join("\n"),
451
+ markdown: formatPackMarkdown(packs, data.note)
452
+ };
453
+ } catch (error) {
454
+ return failure(error);
527
455
  }
528
456
  };
529
457
  function registerBuiltinCommands(registry) {
530
- registry.register({
531
- name: "menu",
532
- aliases: ["\u83DC\u5355", "help", "\u5E2E\u52A9", "/help", "/\u83DC\u5355", "/menu"],
533
- description: "\u663E\u793A\u6307\u4EE4\u83DC\u5355",
534
- handler: createMenuHandler(registry)
535
- });
536
- registry.register({
537
- name: "ping",
538
- aliases: ["/ping"],
539
- description: "\u8FDE\u901A\u6027\u63A2\u6D4B",
540
- handler: pingHandler
541
- });
542
- registry.register({
543
- name: "whoami",
544
- aliases: ["/whoami", "\u6211\u7684\u7ED1\u5B9A"],
545
- description: "\u67E5\u770B QQ id \u4E0E MC \u7ED1\u5B9A",
546
- handler: whoamiHandler
547
- });
548
- registry.register({
549
- name: "status",
550
- aliases: ["\u72B6\u6001", "/status", "/\u72B6\u6001"],
551
- description: "\u670D\u52A1\u5668\u72B6\u6001\u6458\u8981",
552
- handler: statusHandler
553
- });
554
- registry.register({
555
- name: "online",
556
- aliases: ["\u5728\u7EBF", "/online", "/\u5728\u7EBF"],
557
- description: "\u5728\u7EBF\u73A9\u5BB6\u540D\u5355",
558
- handler: onlineHandler
559
- });
560
- registry.register({
561
- name: "bind",
562
- aliases: ["\u7ED1\u5B9A", "/bind", "/\u7ED1\u5B9A"],
563
- description: "\u7533\u8BF7 QQ\u2194MC \u7ED1\u5B9A\u7801",
564
- handler: bindHandler
565
- });
566
- registry.register({
567
- name: "unbind",
568
- aliases: ["\u89E3\u7ED1", "/unbind", "/\u89E3\u7ED1"],
569
- description: "\u89E3\u9664 QQ\u2194MC \u7ED1\u5B9A",
570
- handler: unbindHandler
571
- });
572
- registry.register({
573
- name: "join",
574
- aliases: ["\u7533\u8BF7\u5165\u670D", "/join", "/\u7533\u8BF7\u5165\u670D"],
575
- description: "\u7533\u8BF7\u52A0\u5165\u670D\u52A1\u5668\u767D\u540D\u5355",
576
- handler: joinHandler
577
- });
578
- registry.register({
579
- name: "channel",
580
- aliases: ["\u9891\u9053", "/channel", "/\u9891\u9053"],
581
- description: "\u804A\u5929\u4E92\u901A\u9891\u9053\u4E0E\u8FDE\u901A\u81EA\u68C0",
582
- handler: channelHandler
583
- });
584
- registry.register({
585
- name: "admin",
586
- aliases: ["\u7BA1\u7406", "/admin", "/\u7BA1\u7406"],
587
- description: "\u7BA1\u7406\u5B50\u83DC\u5355\uFF08\u7BA1\u7406\u5458\uFF09",
588
- handler: createAdminMenuHandler(registry)
589
- });
590
- registry.register({
591
- name: "doctor",
592
- aliases: ["\u81EA\u68C0", "/doctor", "/\u81EA\u68C0"],
593
- description: "\u7BA1\u7406\u4FA7\u8FDE\u901A/\u914D\u7F6E\u81EA\u68C0",
594
- handler: doctorHandler,
595
- adminMenu: true
596
- });
597
- registry.register({
598
- name: "group",
599
- aliases: ["\u7FA4\u4FE1\u606F", "/group", "/\u7FA4\u4FE1\u606F"],
600
- description: "QQ \u7FA4 OpenAPI \u6458\u8981",
601
- handler: groupInfoHandler,
602
- adminMenu: true
603
- });
604
- registry.register({
605
- name: "config",
606
- aliases: ["\u914D\u7F6E", "\u5165\u670D\u914D\u7F6E", "/config", "/\u914D\u7F6E"],
607
- description: "\u5165\u670D\u767D\u540D\u5355/\u5BA1\u6279\u5F00\u5173\uFF08\u7BA1\u7406\u5458\uFF09",
608
- handler: joinConfigHandler,
609
- adminMenu: true
610
- });
611
- registry.register({
612
- name: "pending",
613
- aliases: ["\u5F85\u5BA1", "/pending", "/\u5F85\u5BA1"],
614
- description: "\u5F85\u5BA1\u5165\u670D\u5217\u8868\uFF08\u7BA1\u7406\u5458\uFF09",
615
- handler: pendingHandler,
616
- adminMenu: true
617
- });
618
- registry.register({
619
- name: "approve",
620
- aliases: ["\u901A\u8FC7", "/approve", "/\u901A\u8FC7"],
621
- description: "\u901A\u8FC7\u5165\u670D\u7533\u8BF7\uFF08\u7BA1\u7406\u5458\uFF09",
622
- handler: approveHandler,
623
- adminMenu: true
624
- });
625
- registry.register({
626
- name: "reject",
627
- aliases: ["\u62D2\u7EDD", "/reject", "/\u62D2\u7EDD"],
628
- description: "\u62D2\u7EDD\u5165\u670D\u7533\u8BF7\uFF08\u7BA1\u7406\u5458\uFF09",
629
- handler: rejectHandler,
630
- adminMenu: true
631
- });
632
- registry.register({
633
- name: "kick",
634
- aliases: ["\u8E22\u4EBA", "/kick", "/\u8E22\u4EBA"],
635
- description: "\u8E22\u51FA\u6E38\u620F\u5185\u73A9\u5BB6\uFF08\u7BA1\u7406\u5458\uFF09",
636
- handler: kickHandler,
637
- adminMenu: true
458
+ const add = (name, aliases, description, handler, group, admin = false) => registry.register({
459
+ name,
460
+ aliases,
461
+ description,
462
+ handler,
463
+ group,
464
+ permission: admin ? "admin" : "player",
465
+ adminMenu: group === "admin"
638
466
  });
467
+ add("menu", ["\u83DC\u5355"], "\u8FD4\u56DE\u9996\u9875", createMenuHandler(registry), "home");
468
+ add(
469
+ "server",
470
+ ["\u670D\u52A1\u5668"],
471
+ "\u67E5\u770B\u8FD0\u884C\u72B6\u6001\u3001\u5728\u7EBF\u73A9\u5BB6\u548C\u8FDE\u63A5\u65B9\u5F0F",
472
+ sectionHandler(registry, "server", "\u670D\u52A1\u5668\u4FE1\u606F"),
473
+ "home"
474
+ );
475
+ add("account", ["\u6211\u7684\u8D26\u53F7"], "\u67E5\u770B\u4E0E\u7BA1\u7406\u6E38\u620F\u89D2\u8272\u7ED1\u5B9A", whoamiHandler, "home");
476
+ add(
477
+ "entry",
478
+ ["\u5165\u670D"],
479
+ "\u76F4\u63A5\u8FDB\u670D\uFF0C\u6E38\u620F\u5185\u7ED1\u5B9A\u540E\u5373\u53EF\u6E38\u73A9",
480
+ entryGuideHandler,
481
+ "home"
482
+ );
483
+ add("help", ["\u5E2E\u52A9"], "\u67E5\u770B\u4F7F\u7528\u8BF4\u660E\u548C\u5E38\u7528\u547D\u4EE4", helpHandler(registry), "home");
484
+ add("admin", ["\u7BA1\u7406"], "\u7BA1\u7406\u83DC\u5355", createAdminMenuHandler(registry), "home", true);
485
+ add("status", ["\u67E5\u670D", "\u72B6\u6001"], "\u670D\u52A1\u5668\u8FD0\u884C\u72B6\u6001", statusHandler, "server");
486
+ add("online", ["\u5728\u7EBF"], "\u5B8C\u6574\u5728\u7EBF\u540D\u5355", onlineHandler, "server");
487
+ add("version", ["\u7248\u672C"], "\u57FA\u5CA9\u7248\u5165\u670D\u7248\u672C", serverVersionHandler, "server");
488
+ add("ip", ["\u5730\u5740"], "\u516C\u5F00\u670D\u52A1\u5668\u5730\u5740\u4E0E\u7AEF\u53E3", serverAddressHandler, "server");
489
+ add("modules", ["\u6A21\u5757"], "\u67E5\u770B\u5DF2\u5B89\u88C5\u6A21\u5757", modulesHandler, "server");
490
+ add("packs", ["\u8D44\u6E90\u5305", "\u884C\u4E3A\u5305", "\u4E16\u754C\u5305"], "\u67E5\u770B\u4E16\u754C\u884C\u4E3A\u5305\u548C\u8D44\u6E90\u5305", packsHandler, "server");
491
+ add("channel", ["\u9891\u9053"], "\u804A\u5929\u4E92\u901A\u72B6\u6001", channelHandler, "server");
492
+ add("ping", [], "\u673A\u5668\u4EBA\u8FDE\u901A\u6027", pingHandler, "server");
493
+ add("whoami", ["\u6211\u7684\u7ED1\u5B9A"], "\u67E5\u770B\u7ED1\u5B9A\u89D2\u8272", whoamiHandler, "account");
494
+ add("bind", ["\u7ED1\u5B9A"], "\u7533\u8BF7\u7ED1\u5B9A\u9A8C\u8BC1\u7801", bindHandler, "account");
495
+ add("unbind", ["\u89E3\u7ED1"], "\u786E\u8BA4\u89E3\u9664\u7ED1\u5B9A", unbindHandler, "account");
496
+ add(
497
+ "join",
498
+ ["\u7533\u8BF7\u5165\u670D", "\u901A\u8FC7", "\u62D2\u7EDD", "\u5F85\u5BA1", "approve", "reject", "pending", "\u914D\u7F6E", "\u5165\u670D\u914D\u7F6E", "config"],
499
+ "\u5165\u670D\u7533\u8BF7\u5DF2\u5173\u95ED\uFF0C\u8BF7\u76F4\u63A5\u8FDB\u670D\u5E76\u7ED1\u5B9A",
500
+ retiredJoinFlowHandler,
501
+ "help"
502
+ );
503
+ add("doctor", ["\u81EA\u68C0"], "\u8FDE\u901A\u4E0E\u8FD0\u884C\u8BCA\u65AD", doctorHandler, "admin", true);
504
+ add("group", ["\u7FA4\u4FE1\u606F"], "\u5B98\u65B9 QQ \u7FA4\u4FE1\u606F", groupInfoHandler, "admin", true);
505
+ add("events", ["\u4E8B\u4EF6\u63A8\u9001", "\u4E8B\u4EF6\u914D\u7F6E"], "\u670D\u52A1\u5668\u4E8B\u4EF6\u63A8\u9001\u5F00\u5173", eventSettingsHandler, "admin", true);
639
506
  }
640
507
  export {
641
- approveHandler,
642
508
  bindHandler,
643
509
  channelHandler,
644
510
  createAdminMenuHandler,
645
511
  createMenuHandler,
646
512
  doctorHandler,
513
+ entryGuideHandler,
514
+ eventSettingsHandler,
647
515
  groupInfoHandler,
648
- joinConfigHandler,
649
- joinHandler,
650
- kickHandler,
516
+ modulesHandler,
651
517
  onlineHandler,
652
- pendingHandler,
518
+ packsHandler,
653
519
  pingHandler,
654
520
  registerBuiltinCommands,
655
- rejectHandler,
521
+ retiredJoinFlowHandler,
522
+ serverAddressHandler,
523
+ serverVersionHandler,
656
524
  statusHandler,
657
525
  unbindHandler,
658
526
  whoamiHandler