koishi-plugin-freekill-u 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/lib/index.js +606 -89
  2. package/package.json +1 -1
package/lib/index.js CHANGED
@@ -16,7 +16,7 @@ var __copyProps = (to, from, except, desc) => {
16
16
  };
17
17
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
18
 
19
- // src/index.ts
19
+ // packages/freekill-u/src/index.ts
20
20
  var src_exports = {};
21
21
  __export(src_exports, {
22
22
  Config: () => Config,
@@ -29,70 +29,366 @@ var import_koishi = require("koishi");
29
29
  var { Client } = require("ssh2");
30
30
  var name = "freekill-u";
31
31
  var Config = import_koishi.Schema.object({
32
- host: import_koishi.Schema.string().description("SSH \u670D\u52A1\u5668 IP").default(""),
33
- port: import_koishi.Schema.number().description("SSH \u7AEF\u53E3").default(22).min(1).max(65535),
34
- username: import_koishi.Schema.string().description("SSH \u7528\u6237\u540D").default(""),
35
- password: import_koishi.Schema.string().role("secret").description("SSH \u5BC6\u7801").default(""),
36
- screenSession: import_koishi.Schema.string().description("screen \u4F1A\u8BDD\u6807\u8BC6\uFF08\u591A\u4E2A screen \u65F6\u586B\u5199\uFF0C\u5982 1234 \u6216 freekill\uFF1B\u7559\u7A7A\u5219\u7528 screen -r \u9ED8\u8BA4\u9009\u62E9\uFF09").default(""),
37
- updateCommand: import_koishi.Schema.string().description("\u66F4\u65B0\u547D\u4EE4\uFF08\u5728\u65B0\u6708\u6740\u547D\u4EE4\u884C\u8F93\u5165\u7684\u6307\u4EE4\uFF09").default("u"),
38
- attachDelay: import_koishi.Schema.number().description("screen -r \u540E\u7B49\u5F85 attach \u7684\u6BEB\u79D2\u6570").default(3e3).min(500).max(3e4),
39
- updateWait: import_koishi.Schema.number().description("\u53D1\u9001\u66F4\u65B0\u547D\u4EE4\u540E\u7B49\u5F85\u8F93\u51FA\u7684\u6BEB\u79D2\u6570").default(3e4).min(5e3).max(3e5),
40
- authority: import_koishi.Schema.number().description("\u4F7F\u7528\u8BE5\u547D\u4EE4\u6240\u9700\u7684\u6700\u4F4E\u6743\u9650\u7B49\u7EA7").default(4).min(1).max(5)
32
+ servers: import_koishi.Schema.array(import_koishi.Schema.object({
33
+ name: import_koishi.Schema.string().description('\u670D\u52A1\u5668\u540D\u79F0\uFF08\u7528\u4F5C\u547D\u4EE4\u524D\u7F00\uFF0C\u5982"\u5B98\u670D"\u5219\u547D\u4EE4\u4E3A \u5B98\u670D.u / \u5B98\u670D.stat \u7B49\uFF09').required(),
34
+ host: import_koishi.Schema.string().description("SSH \u670D\u52A1\u5668 IP").default(""),
35
+ port: import_koishi.Schema.number().description("SSH \u7AEF\u53E3").default(22).min(1).max(65535),
36
+ username: import_koishi.Schema.string().description("SSH \u7528\u6237\u540D").default(""),
37
+ password: import_koishi.Schema.string().role("secret").description("SSH \u5BC6\u7801").default(""),
38
+ screenSession: import_koishi.Schema.string().description("screen \u4F1A\u8BDD\u6807\u8BC6\uFF08\u591A\u4E2A screen \u65F6\u586B\u5199\uFF0C\u7559\u7A7A\u5219\u7528 screen -r \u9ED8\u8BA4\u9009\u62E9\uFF09").default(""),
39
+ updateCommand: import_koishi.Schema.string().description("\u66F4\u65B0\u547D\u4EE4\uFF08\u5728\u65B0\u6708\u6740\u547D\u4EE4\u884C\u8F93\u5165\u7684\u6307\u4EE4\uFF09").default("u"),
40
+ operators: import_koishi.Schema.string().description("\u64CD\u4F5C\u5458 QQ \u53F7\u767D\u540D\u5355\uFF08\u591A\u4E2A\u7528\u9017\u53F7/\u7A7A\u683C\u5206\u9694\uFF0C\u7559\u7A7A\u5219\u4E0D\u9650\u5236\uFF09\u3002\u4E0D\u5728\u767D\u540D\u5355\u5185\u7684\u7528\u6237\u6267\u884C\u547D\u4EE4\u65F6\u9759\u9ED8\u5FFD\u7565").default(""),
41
+ attachDelay: import_koishi.Schema.number().description("screen -r \u540E\u7B49\u5F85 attach \u7684\u6BEB\u79D2\u6570").default(3e3).min(500).max(3e4),
42
+ updateWait: import_koishi.Schema.number().description("\u53D1\u9001\u66F4\u65B0\u547D\u4EE4\u540E\u7B49\u5F85\u8F93\u51FA\u7684\u6BEB\u79D2\u6570").default(3e4).min(5e3).max(3e5),
43
+ quitWait: import_koishi.Schema.number().description("\u53D1\u9001 quit \u540E\u7B49\u5F85\u670D\u52A1\u5668\u5173\u95ED\u7684\u6BEB\u79D2\u6570").default(5e3).min(2e3).max(6e4),
44
+ restartWait: import_koishi.Schema.number().description("\u53D1\u9001\u542F\u52A8\u547D\u4EE4\u540E\u7B49\u5F85\u670D\u52A1\u5668\u542F\u52A8\u7684\u6BEB\u79D2\u6570").default(6e4).min(1e4).max(3e5),
45
+ commandWait: import_koishi.Schema.number().description("\u666E\u901A\u67E5\u8BE2\u547D\u4EE4\u7B49\u5F85\u8F93\u51FA\u7684\u6BEB\u79D2\u6570").default(3e3).min(1e3).max(3e4)
46
+ })).description("\u670D\u52A1\u5668\u5217\u8868\uFF0C\u6BCF\u4E2A\u670D\u52A1\u5668\u5BF9\u5E94\u4E00\u7EC4 SSH \u8FDE\u63A5\u4FE1\u606F\u548C\u547D\u4EE4\u524D\u7F00").default([]),
47
+ authority: import_koishi.Schema.number().description("\u4F7F\u7528\u547D\u4EE4\u6240\u9700\u7684\u6700\u4F4E\u6743\u9650\u7B49\u7EA7").default(4).min(1).max(5),
48
+ debugMode: import_koishi.Schema.boolean().description("\u8C03\u8BD5\u6A21\u5F0F\uFF1A\u663E\u793A\u5B8C\u6574 SSH \u8F93\u51FA\uFF08\u542B\u654F\u611F\u5185\u5BB9\uFF0C\u4EC5\u8C03\u8BD5\u65F6\u5F00\u542F\uFF09").default(false)
41
49
  });
42
50
  var usage = `
43
- # \u65B0\u6708\u6740\u8FDC\u7A0B\u66F4\u65B0\u63D2\u4EF6
51
+ # \u65B0\u6708\u6740\u8FDC\u7A0B\u7BA1\u7406\u63D2\u4EF6
44
52
 
45
- \u901A\u8FC7 SSH \u8FDE\u63A5\u670D\u52A1\u5668\uFF0C\u81EA\u52A8\u8FDB\u5165 screen \u4F1A\u8BDD\u6267\u884C\u66F4\u65B0\u547D\u4EE4\u3002
53
+ \u901A\u8FC7 SSH \u8FDE\u63A5\u670D\u52A1\u5668\uFF0C\u81EA\u52A8\u8FDB\u5165 screen \u4F1A\u8BDD\u6267\u884C\u7BA1\u7406\u547D\u4EE4\u3002
54
+ \u5371\u9669\u64CD\u4F5C\uFF0C\u8BF7\u52A1\u5FC5\u8BBE\u7F6E\u8DB3\u591F\u7684\u6743\u9650\u7B49\u7EA7\u3002
46
55
 
47
- ## \u4F7F\u7528\u65B9\u6CD5
56
+ ## \u914D\u7F6E\u65B9\u6CD5
48
57
 
49
- \u5728\u7FA4\u91CC\u53D1\u9001 **\u66F4\u65B0\u65B0\u6708\u6740** \u5373\u53EF\u89E6\u53D1\u66F4\u65B0\u6D41\u7A0B\u3002
58
+ \u5728\u63D2\u4EF6\u914D\u7F6E\u4E2D\u6DFB\u52A0\u670D\u52A1\u5668\u5217\u8868\uFF0C\u6BCF\u4E2A\u670D\u52A1\u5668\u914D\u7F6E\uFF1A
59
+ - **\u540D\u79F0**\uFF1A\u7528\u4F5C\u547D\u4EE4\u524D\u7F00\uFF0C\u5982\u586B\u5199"\u5B98\u670D"\uFF0C\u5219\u547D\u4EE4\u4E3A **\u5B98\u670D.u**\u3001**\u5B98\u670D.stat** \u7B49
60
+ - **SSH \u4FE1\u606F**\uFF1A\u4E3B\u673A\u3001\u7AEF\u53E3\u3001\u7528\u6237\u540D\u3001\u5BC6\u7801
61
+ - **screen \u4F1A\u8BDD**\uFF1A\u6709\u591A\u4E2A screen \u65F6\u586B\u5199\u4F1A\u8BDD\u6807\u8BC6
62
+ - **\u64CD\u4F5C\u5458\u767D\u540D\u5355**\uFF1A\u586B\u5199 QQ \u53F7\uFF08\u9017\u53F7\u5206\u9694\uFF09\uFF0C\u4E0D\u5728\u767D\u540D\u5355\u5185\u7684\u7528\u6237\u6267\u884C\u547D\u4EE4\u65F6 bot \u9759\u9ED8\u4E0D\u54CD\u5E94
50
63
 
51
- ## \u5DE5\u4F5C\u6D41\u7A0B
64
+ ## \u547D\u4EE4\u5217\u8868
52
65
 
53
- 1. SSH \u8FDE\u63A5\u5230\u670D\u52A1\u5668
54
- 2. \u6253\u5F00\u5E26 PTY \u7684\u4EA4\u4E92\u5F0F shell
55
- 3. \u53D1\u9001 \`screen -r [\u4F1A\u8BDDID]\` \u8FDB\u5165\u65B0\u6708\u6740 screen \u4F1A\u8BDD
56
- 4. \u53D1\u9001\u66F4\u65B0\u547D\u4EE4\uFF08\u9ED8\u8BA4 \`u\`\uFF09
57
- 5. \u7B49\u5F85\u66F4\u65B0\u8F93\u51FA
58
- 6. \u53D1\u9001 \`Ctrl-a d\` \u5206\u79BB screen \u4F1A\u8BDD
59
- 7. \u5173\u95ED SSH \u8FDE\u63A5\uFF0C\u8FD4\u56DE\u66F4\u65B0\u65E5\u5FD7
66
+ \u6240\u6709\u547D\u4EE4\u683C\u5F0F\uFF1A\`\u670D\u52A1\u5668\u540D.\u5B50\u547D\u4EE4\`
67
+
68
+ | \u547D\u4EE4 | \u522B\u540D | \u8BF4\u660E |
69
+ |------|------|------|
70
+ | \`\u670D\u52A1\u5668.u\` | \u2014 | \u8FDC\u7A0B\u66F4\u65B0\u670D\u52A1\u5668 |
71
+ | \`\u670D\u52A1\u5668.r\` | \u2014 | \u8FDC\u7A0B\u91CD\u542F\u670D\u52A1\u5668 |
72
+ | \`\u670D\u52A1\u5668.stat\` | \u2014 | \u67E5\u770B\u8FD0\u884C\u72B6\u6001 |
73
+ | \`\u670D\u52A1\u5668.reloadconf\` | \`.r\` (\u91CD\u8F7D) | \u91CD\u8F7D\u914D\u7F6E\u6587\u4EF6 |
74
+ | \`\u670D\u52A1\u5668.lsplayer\` | \u2014 | \u67E5\u770B\u5728\u7EBF\u73A9\u5BB6 |
75
+ | \`\u670D\u52A1\u5668.lsroom [id]\` | \u2014 | \u67E5\u770B\u623F\u95F4\u5217\u8868 |
76
+ | \`\u670D\u52A1\u5668.msg <\u5185\u5BB9>\` | \`.m\` | \u53D1\u5168\u4F53\u516C\u544A |
77
+ | \`\u670D\u52A1\u5668.msgroom <\u623F\u95F4> <\u5185\u5BB9>\` | \`.mr\` | \u5411\u623F\u95F4\u53D1\u6D88\u606F |
78
+ | \`\u670D\u52A1\u5668.kick <\u73A9\u5BB6>\` | \u2014 | \u8E22\u51FA\u73A9\u5BB6 |
79
+ | \`\u670D\u52A1\u5668.killroom <\u623F\u95F4>\` | \u2014 | \u5E9F\u5F03\u623F\u95F4 |
80
+ | \`\u670D\u52A1\u5668.checklobby\` | \u2014 | \u6E05\u5927\u5385\u50F5\u5C38 |
81
+ | \`\u670D\u52A1\u5668.ban <\u76EE\u6807>\` | \u2014 | \u5C01\u7981\u8D26\u53F7 |
82
+ | \`\u670D\u52A1\u5668.unban <\u76EE\u6807>\` | \u2014 | \u89E3\u5C01\u8D26\u53F7 |
83
+ | \`\u670D\u52A1\u5668.banip <IP>\` | \u2014 | \u5C01\u7981 IP |
84
+ | \`\u670D\u52A1\u5668.unbanip <IP>\` | \u2014 | \u89E3\u5C01 IP |
85
+ | \`\u670D\u52A1\u5668.banuuid <UUID>\` | \u2014 | \u5C01\u7981 UUID |
86
+ | \`\u670D\u52A1\u5668.unbanuuid <UUID>\` | \u2014 | \u89E3\u5C01 UUID |
87
+ | \`\u670D\u52A1\u5668.tempban <\u76EE\u6807> <\u65F6\u957F>\` | \u2014 | \u4E34\u65F6\u5C01\u7981 |
88
+ | \`\u670D\u52A1\u5668.tempmute <\u76EE\u6807> <\u65F6\u957F>\` | \u2014 | \u4E34\u65F6\u7981\u8A00 |
89
+ | \`\u670D\u52A1\u5668.unmute <\u76EE\u6807>\` | \u2014 | \u89E3\u9664\u7981\u8A00 |
90
+ | \`\u670D\u52A1\u5668.whitelist <\u64CD\u4F5C>\` | \u2014 | \u7BA1\u7406\u767D\u540D\u5355 |
91
+ | \`\u670D\u52A1\u5668.resetpassword <\u8D26\u53F7>\` | \`.rp\` | \u91CD\u7F6E\u5BC6\u7801 |
92
+ | \`\u670D\u52A1\u5668.pkgs\` | \u2014 | \u67E5\u770B\u6269\u5C55\u5305 |
93
+ | \`\u670D\u52A1\u5668.syncpkgs\` | \u2014 | \u540C\u6B65\u6269\u5C55\u5305 |
94
+ | \`\u670D\u52A1\u5668.install <URL>\` | \u2014 | \u5B89\u88C5\u6269\u5C55\u5305 |
95
+ | \`\u670D\u52A1\u5668.remove <\u540D\u79F0>\` | \u2014 | \u79FB\u9664\u6269\u5C55\u5305 |
96
+ | \`\u670D\u52A1\u5668.enable <\u540D\u79F0>\` | \u2014 | \u542F\u7528\u6269\u5C55\u5305 |
97
+ | \`\u670D\u52A1\u5668.disable <\u540D\u79F0>\` | \u2014 | \u7981\u7528\u6269\u5C55\u5305 |
60
98
 
61
99
  ## \u6CE8\u610F\u4E8B\u9879
62
100
 
63
- - screen \u4F1A\u8BDD\u5FC5\u987B\u662F **detached** \u72B6\u6001\uFF0C\u5982\u679C\u5DF2\u88AB\u4EBA attach\uFF0C\u9700\u8981\u5148\u7528 \`screen -d -r\`
101
+ - screen \u4F1A\u8BDD\u5FC5\u987B\u662F **detached** \u72B6\u6001
64
102
  - \u672C\u63D2\u4EF6\u6D89\u53CA\u670D\u52A1\u5668\u5B89\u5168\uFF0C\u8BF7\u52A1\u5FC5\u8BBE\u7F6E\u8DB3\u591F\u7684\u6743\u9650\u7B49\u7EA7
103
+
104
+ ## \u53CD\u9988\u6E20\u9053
105
+ QQ \u7FA4\uFF1A1078815356
65
106
  `;
66
107
  function apply(ctx, config) {
67
108
  const logger = ctx.logger("freekill-u");
68
- ctx.command("\u66F4\u65B0\u65B0\u6708\u6740", "\u8FDC\u7A0B\u66F4\u65B0\u65B0\u6708\u6740\u670D\u52A1\u5668").userFields(["authority"]).action(async ({ session }) => {
69
- if (!config.host || !config.username) {
70
- return "\u26A0\uFE0F \u8BF7\u5148\u5728\u63D2\u4EF6\u914D\u7F6E\u4E2D\u586B\u5199 SSH \u8FDE\u63A5\u4FE1\u606F\u3002";
109
+ for (const server of config.servers) {
110
+ registerServerCommands(ctx, config, server, logger);
111
+ }
112
+ }
113
+ function registerServerCommands(ctx, config, server, logger) {
114
+ const prefix = server.name;
115
+ function checkAuth(session) {
116
+ if (!server.host || !server.username) return `\u26A0\uFE0F [${prefix}] \u8BF7\u5148\u5728\u63D2\u4EF6\u914D\u7F6E\u4E2D\u586B\u5199 SSH \u8FDE\u63A5\u4FE1\u606F\u3002`;
117
+ if (server.operators && server.operators.trim()) {
118
+ const allowedIds = server.operators.split(/[,,\s]+/).map((s) => s.trim()).filter(Boolean);
119
+ if (allowedIds.length > 0) {
120
+ const userId = session.userId || "";
121
+ if (!allowedIds.includes(userId)) return void 0;
122
+ }
71
123
  }
72
124
  const userAuthority = session.user?.authority ?? 0;
73
- if (userAuthority < config.authority) {
74
- return `\u26A0\uFE0F \u6743\u9650\u4E0D\u8DB3\uFF0C\u9700\u8981\u7B49\u7EA7 ${config.authority} \u4EE5\u4E0A\u3002`;
75
- }
76
- await session.send("\u{1F50C} \u6B63\u5728\u8FDE\u63A5\u670D\u52A1\u5668\u2026");
125
+ if (userAuthority < config.authority) return `\u26A0\uFE0F \u6743\u9650\u4E0D\u8DB3\uFF0C\u9700\u8981\u7B49\u7EA7 ${config.authority} \u4EE5\u4E0A\u3002`;
126
+ return null;
127
+ }
128
+ async function execShellCmd(session, shellCmd, label) {
129
+ const startTime = Date.now();
77
130
  try {
78
- const result = await executeUpdate(config, logger);
79
- let output = result.trim();
80
- if (output.length > 2e3) {
81
- output = output.slice(0, 2e3) + "\n\u2026\uFF08\u8F93\u51FA\u5DF2\u622A\u65AD\uFF09";
131
+ const result = await executeShellCommand(shellCmd, server);
132
+ const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
133
+ const output = cleanShellOutput(result.output);
134
+ if (output.length > 1500) {
135
+ return `\u{1F4CB} [${prefix}] ${label}
136
+ \u23F1 ${elapsed}s
137
+
138
+ ${output.slice(-1500)}
139
+ \u2026\uFF08\u8F93\u51FA\u5DF2\u622A\u65AD\uFF09`;
82
140
  }
83
- await session.send(`\u2705 \u66F4\u65B0\u5B8C\u6210\uFF01
141
+ return `\u{1F4CB} [${prefix}] ${label}
142
+ \u23F1 ${elapsed}s
84
143
 
144
+ ${output || "(\u65E0\u8F93\u51FA)"}`;
145
+ } catch (e) {
146
+ const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
147
+ logger.warn("Shell\u547D\u4EE4\u5931\u8D25", { server: prefix, cmd: shellCmd, error: e?.message || String(e) });
148
+ return `\u274C [${prefix}] ${label} \u5931\u8D25\uFF08${elapsed}s\uFF09\uFF1A${e?.message || String(e)}`;
149
+ }
150
+ }
151
+ ctx.command(`${prefix}`, `${prefix} \u670D\u52A1\u5668\u7BA1\u7406`);
152
+ ctx.command(`${prefix}.u`, `\u66F4\u65B0 ${prefix} \u670D\u52A1\u5668`).userFields(["authority"]).action(async ({ session }) => {
153
+ const err = checkAuth(session);
154
+ if (err === void 0) return;
155
+ if (err) return err;
156
+ const startTime = Date.now();
157
+ await session.send(`\u{1F50C} [${prefix}] \u6B63\u5728\u8FDE\u63A5\u670D\u52A1\u5668\u2026`);
158
+ try {
159
+ const result = await executeUpdate(server);
160
+ const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
161
+ const lines = [`\u2705 [${prefix}] \u66F4\u65B0\u5B8C\u6210\uFF01`, `\u23F1 \u8017\u65F6 ${elapsed} \u79D2`];
162
+ if (result.hasError) lines.push("\u26A0\uFE0F \u68C0\u6D4B\u5230\u53EF\u80FD\u7684\u9519\u8BEF\uFF0C\u5EFA\u8BAE\u68C0\u67E5\u670D\u52A1\u5668\u65E5\u5FD7\u3002");
163
+ if (config.debugMode && result.output) {
164
+ let output = filterSensitive(result.output);
165
+ if (output.length > 2e3) output = output.slice(-2e3) + "\n\u2026\uFF08\u8F93\u51FA\u5DF2\u622A\u65AD\uFF09";
166
+ lines.push(`
167
+ \u{1F4CB} \u66F4\u65B0\u8F93\u51FA\uFF1A
85
168
  ${output}`);
169
+ }
170
+ if (result.hasError) logger.warn("\u66F4\u65B0\u53EF\u80FD\u5931\u8D25", { server: prefix, elapsed, lines: result.lineCount });
171
+ await session.send(lines.join("\n"));
86
172
  } catch (e) {
87
- logger.error("\u66F4\u65B0\u5931\u8D25", e);
88
- await session.send(`\u274C \u66F4\u65B0\u5931\u8D25\uFF1A${e?.message || String(e)}`);
173
+ const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
174
+ logger.warn("\u66F4\u65B0\u5931\u8D25", { server: prefix, error: e?.message || String(e), elapsed });
175
+ await session.send(`\u274C [${prefix}] \u66F4\u65B0\u5931\u8D25\uFF08${elapsed}s\uFF09\uFF1A${e?.message || String(e)}`);
89
176
  }
90
177
  });
178
+ ctx.command(`${prefix}.r`, `\u91CD\u542F ${prefix} \u670D\u52A1\u5668`).userFields(["authority"]).action(async ({ session }) => {
179
+ const err = checkAuth(session);
180
+ if (err === void 0) return;
181
+ if (err) return err;
182
+ const startTime = Date.now();
183
+ await session.send(`\u{1F50C} [${prefix}] \u6B63\u5728\u8FDE\u63A5\u670D\u52A1\u5668\u2026`);
184
+ try {
185
+ const result = await executeRestart(server);
186
+ const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
187
+ const lines = [`\u2705 [${prefix}] \u91CD\u542F\u5B8C\u6210\uFF01`, `\u23F1 \u8017\u65F6 ${elapsed} \u79D2`];
188
+ if (result.hasError) lines.push("\u26A0\uFE0F \u68C0\u6D4B\u5230\u53EF\u80FD\u7684\u9519\u8BEF\uFF0C\u5EFA\u8BAE\u68C0\u67E5\u670D\u52A1\u5668\u65E5\u5FD7\u3002");
189
+ if (config.debugMode && result.output) {
190
+ let output = filterSensitive(result.output);
191
+ if (output.length > 2e3) output = output.slice(-2e3) + "\n\u2026\uFF08\u8F93\u51FA\u5DF2\u622A\u65AD\uFF09";
192
+ lines.push(`
193
+ \u{1F4CB} \u91CD\u542F\u8F93\u51FA\uFF1A
194
+ ${output}`);
195
+ }
196
+ if (result.hasError) logger.warn("\u91CD\u542F\u53EF\u80FD\u5931\u8D25", { server: prefix, elapsed, lines: result.lineCount });
197
+ await session.send(lines.join("\n"));
198
+ } catch (e) {
199
+ const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
200
+ logger.warn("\u91CD\u542F\u5931\u8D25", { server: prefix, error: e?.message || String(e), elapsed });
201
+ await session.send(`\u274C [${prefix}] \u91CD\u542F\u5931\u8D25\uFF08${elapsed}s\uFF09\uFF1A${e?.message || String(e)}`);
202
+ }
203
+ });
204
+ ctx.command(`${prefix}.stat`, "\u67E5\u770B\u670D\u52A1\u5668\u8FD0\u884C\u72B6\u6001\uFF08GC \u7B49\uFF09").userFields(["authority"]).action(async ({ session }) => {
205
+ const err = checkAuth(session);
206
+ if (err === void 0) return;
207
+ if (err) return err;
208
+ await session.send(`\u{1F50C} [${prefix}] \u67E5\u8BE2\u4E2D\u2026`);
209
+ return execShellCmd(session, "stat", "stat");
210
+ });
211
+ ctx.command(`${prefix}.reloadconf`, "\u91CD\u8F7D\u670D\u52A1\u5668\u914D\u7F6E\u6587\u4EF6").userFields(["authority"]).action(async ({ session }) => {
212
+ const err = checkAuth(session);
213
+ if (err === void 0) return;
214
+ if (err) return err;
215
+ await session.send(`\u{1F50C} [${prefix}] \u67E5\u8BE2\u4E2D\u2026`);
216
+ return execShellCmd(session, "reloadconf", "reloadconf");
217
+ });
218
+ ctx.command(`${prefix}.lsplayer`, "\u67E5\u770B\u6240\u6709\u5728\u7EBF\u73A9\u5BB6").userFields(["authority"]).action(async ({ session }) => {
219
+ const err = checkAuth(session);
220
+ if (err === void 0) return;
221
+ if (err) return err;
222
+ await session.send(`\u{1F50C} [${prefix}] \u67E5\u8BE2\u4E2D\u2026`);
223
+ return execShellCmd(session, "lsplayer", "lsplayer");
224
+ });
225
+ ctx.command(`${prefix}.lsroom [id]`, "\u67E5\u770B\u8FD0\u884C\u4E2D\u7684\u623F\u95F4\uFF08\u53EF\u6307\u5B9A\u623F\u95F4ID\u67E5\u770B\u8BE6\u60C5\uFF09").userFields(["authority"]).action(async ({ session }, id) => {
226
+ const err = checkAuth(session);
227
+ if (err === void 0) return;
228
+ if (err) return err;
229
+ await session.send(`\u{1F50C} [${prefix}] \u67E5\u8BE2\u4E2D\u2026`);
230
+ const cmd = id ? `lsroom ${id}` : "lsroom";
231
+ return execShellCmd(session, cmd, cmd);
232
+ });
233
+ ctx.command(`${prefix}.msg <message:text>`, "\u5411\u5168\u4F53\u73A9\u5BB6\u53D1\u9001\u516C\u544A").alias(`${prefix}.m`).userFields(["authority"]).action(async ({ session }, message) => {
234
+ const err = checkAuth(session);
235
+ if (err === void 0) return;
236
+ if (err) return err;
237
+ await session.send(`\u{1F50C} [${prefix}] \u53D1\u9001\u4E2D\u2026`);
238
+ return execShellCmd(session, `msg ${message}`, "msg");
239
+ });
240
+ ctx.command(`${prefix}.msgroom <roomId> <message:text>`, "\u5411\u6307\u5B9A\u623F\u95F4\u53D1\u9001\u6D88\u606F").alias(`${prefix}.mr`).userFields(["authority"]).action(async ({ session }, roomId, message) => {
241
+ const err = checkAuth(session);
242
+ if (err === void 0) return;
243
+ if (err) return err;
244
+ await session.send(`\u{1F50C} [${prefix}] \u53D1\u9001\u4E2D\u2026`);
245
+ return execShellCmd(session, `msgroom ${roomId} ${message}`, "msgroom");
246
+ });
247
+ ctx.command(`${prefix}.kick <name:text>`, "\u8E22\u51FA\u6307\u5B9A\u73A9\u5BB6").userFields(["authority"]).action(async ({ session }, name2) => {
248
+ const err = checkAuth(session);
249
+ if (err === void 0) return;
250
+ if (err) return err;
251
+ await session.send(`\u{1F50C} [${prefix}] \u6267\u884C\u4E2D\u2026`);
252
+ return execShellCmd(session, `kick ${name2}`, "kick");
253
+ });
254
+ ctx.command(`${prefix}.killroom <roomId>`, "\u8E22\u51FA\u623F\u95F4\u6240\u6709\u4EBA\u5E76\u5E9F\u5F03\u623F\u95F4").userFields(["authority"]).action(async ({ session }, roomId) => {
255
+ const err = checkAuth(session);
256
+ if (err === void 0) return;
257
+ if (err) return err;
258
+ await session.send(`\u{1F50C} [${prefix}] \u6267\u884C\u4E2D\u2026`);
259
+ return execShellCmd(session, `killroom ${roomId}`, "killroom");
260
+ });
261
+ ctx.command(`${prefix}.checklobby`, "\u6E05\u9664\u5927\u5385\u4E2D\u7684\u50F5\u5C38\u73A9\u5BB6").userFields(["authority"]).action(async ({ session }) => {
262
+ const err = checkAuth(session);
263
+ if (err === void 0) return;
264
+ if (err) return err;
265
+ await session.send(`\u{1F50C} [${prefix}] \u6267\u884C\u4E2D\u2026`);
266
+ return execShellCmd(session, "checklobby", "checklobby");
267
+ });
268
+ ctx.command(`${prefix}.ban <name:text>`, "\u5C01\u7981\u6307\u5B9A\u8D26\u53F7 / IP / UUID\uFF08\u53EF\u591A\u4E2A\uFF0C\u7A7A\u683C\u5206\u9694\uFF09").userFields(["authority"]).action(async ({ session }, name2) => {
269
+ const err = checkAuth(session);
270
+ if (err === void 0) return;
271
+ if (err) return err;
272
+ await session.send(`\u{1F50C} [${prefix}] \u6267\u884C\u4E2D\u2026`);
273
+ return execShellCmd(session, `ban ${name2}`, "ban");
274
+ });
275
+ ctx.command(`${prefix}.unban <name:text>`, "\u89E3\u5C01\u6307\u5B9A\u8D26\u53F7\uFF08\u53EF\u591A\u4E2A\uFF09").userFields(["authority"]).action(async ({ session }, name2) => {
276
+ const err = checkAuth(session);
277
+ if (err === void 0) return;
278
+ if (err) return err;
279
+ await session.send(`\u{1F50C} [${prefix}] \u6267\u884C\u4E2D\u2026`);
280
+ return execShellCmd(session, `unban ${name2}`, "unban");
281
+ });
282
+ ctx.command(`${prefix}.banip <name:text>`, "\u5C01\u7981 IP \u5730\u5740").userFields(["authority"]).action(async ({ session }, name2) => {
283
+ const err = checkAuth(session);
284
+ if (err === void 0) return;
285
+ if (err) return err;
286
+ await session.send(`\u{1F50C} [${prefix}] \u6267\u884C\u4E2D\u2026`);
287
+ return execShellCmd(session, `banip ${name2}`, "banip");
288
+ });
289
+ ctx.command(`${prefix}.unbanip <name:text>`, "\u89E3\u5C01 IP \u5730\u5740").userFields(["authority"]).action(async ({ session }, name2) => {
290
+ const err = checkAuth(session);
291
+ if (err === void 0) return;
292
+ if (err) return err;
293
+ await session.send(`\u{1F50C} [${prefix}] \u6267\u884C\u4E2D\u2026`);
294
+ return execShellCmd(session, `unbanip ${name2}`, "unbanip");
295
+ });
296
+ ctx.command(`${prefix}.banuuid <name:text>`, "\u5C01\u7981 UUID").userFields(["authority"]).action(async ({ session }, name2) => {
297
+ const err = checkAuth(session);
298
+ if (err === void 0) return;
299
+ if (err) return err;
300
+ await session.send(`\u{1F50C} [${prefix}] \u6267\u884C\u4E2D\u2026`);
301
+ return execShellCmd(session, `banuuid ${name2}`, "banuuid");
302
+ });
303
+ ctx.command(`${prefix}.unbanuuid <name:text>`, "\u89E3\u5C01 UUID").userFields(["authority"]).action(async ({ session }, name2) => {
304
+ const err = checkAuth(session);
305
+ if (err === void 0) return;
306
+ if (err) return err;
307
+ await session.send(`\u{1F50C} [${prefix}] \u6267\u884C\u4E2D\u2026`);
308
+ return execShellCmd(session, `unbanuuid ${name2}`, "unbanuuid");
309
+ });
310
+ ctx.command(`${prefix}.tempban <name:text> <duration:text>`, "\u4E34\u65F6\u5C01\u7981\uFF08\u65F6\u957F\u5982 30m/2h/7d/1mo\uFF09").userFields(["authority"]).action(async ({ session }, name2, duration) => {
311
+ const err = checkAuth(session);
312
+ if (err === void 0) return;
313
+ if (err) return err;
314
+ await session.send(`\u{1F50C} [${prefix}] \u6267\u884C\u4E2D\u2026`);
315
+ return execShellCmd(session, `tempban ${name2} ${duration}`, "tempban");
316
+ });
317
+ ctx.command(`${prefix}.tempmute <name:text> <duration:text>`, "\u4E34\u65F6\u7981\u8A00\uFF08\u65F6\u957F\u5982 30m/2h/7d/1mo\uFF09").userFields(["authority"]).action(async ({ session }, name2, duration) => {
318
+ const err = checkAuth(session);
319
+ if (err === void 0) return;
320
+ if (err) return err;
321
+ await session.send(`\u{1F50C} [${prefix}] \u6267\u884C\u4E2D\u2026`);
322
+ return execShellCmd(session, `tempmute ${name2} ${duration}`, "tempmute");
323
+ });
324
+ ctx.command(`${prefix}.unmute <name:text>`, "\u89E3\u9664\u7981\u8A00\uFF08\u53EF\u591A\u4E2A\uFF09").userFields(["authority"]).action(async ({ session }, name2) => {
325
+ const err = checkAuth(session);
326
+ if (err === void 0) return;
327
+ if (err) return err;
328
+ await session.send(`\u{1F50C} [${prefix}] \u6267\u884C\u4E2D\u2026`);
329
+ return execShellCmd(session, `unmute ${name2}`, "unmute");
330
+ });
331
+ ctx.command(`${prefix}.whitelist <rest:text>`, "\u7BA1\u7406\u767D\u540D\u5355\uFF08add/remove \u540D\u5355\uFF09").userFields(["authority"]).action(async ({ session }, rest) => {
332
+ const err = checkAuth(session);
333
+ if (err === void 0) return;
334
+ if (err) return err;
335
+ await session.send(`\u{1F50C} [${prefix}] \u6267\u884C\u4E2D\u2026`);
336
+ return execShellCmd(session, `whitelist ${rest}`, "whitelist");
337
+ });
338
+ ctx.command(`${prefix}.resetpassword <name:text>`, "\u91CD\u7F6E\u8D26\u53F7\u5BC6\u7801\u4E3A 1234").alias(`${prefix}.rp`).userFields(["authority"]).action(async ({ session }, name2) => {
339
+ const err = checkAuth(session);
340
+ if (err === void 0) return;
341
+ if (err) return err;
342
+ await session.send(`\u{1F50C} [${prefix}] \u6267\u884C\u4E2D\u2026`);
343
+ return execShellCmd(session, `resetpassword ${name2}`, "resetpassword");
344
+ });
345
+ ctx.command(`${prefix}.install <url:text>`, "\u4ECE URL \u5B89\u88C5\u6269\u5C55\u5305").userFields(["authority"]).action(async ({ session }, url) => {
346
+ const err = checkAuth(session);
347
+ if (err === void 0) return;
348
+ if (err) return err;
349
+ await session.send(`\u{1F50C} [${prefix}] \u6267\u884C\u4E2D\u2026`);
350
+ return execShellCmd(session, `install ${url}`, "install");
351
+ });
352
+ ctx.command(`${prefix}.remove <name:text>`, "\u79FB\u9664\u6269\u5C55\u5305").userFields(["authority"]).action(async ({ session }, name2) => {
353
+ const err = checkAuth(session);
354
+ if (err === void 0) return;
355
+ if (err) return err;
356
+ await session.send(`\u{1F50C} [${prefix}] \u6267\u884C\u4E2D\u2026`);
357
+ return execShellCmd(session, `remove ${name2}`, "remove");
358
+ });
359
+ ctx.command(`${prefix}.pkgs`, "\u67E5\u770B\u6240\u6709\u5DF2\u5B89\u88C5\u6269\u5C55\u5305").userFields(["authority"]).action(async ({ session }) => {
360
+ const err = checkAuth(session);
361
+ if (err === void 0) return;
362
+ if (err) return err;
363
+ await session.send(`\u{1F50C} [${prefix}] \u67E5\u8BE2\u4E2D\u2026`);
364
+ return execShellCmd(session, "pkgs", "pkgs");
365
+ });
366
+ ctx.command(`${prefix}.syncpkgs`, "\u4ECE\u6587\u4EF6\u7CFB\u7EDF\u540C\u6B65\u6269\u5C55\u5305\u5230\u6570\u636E\u5E93").userFields(["authority"]).action(async ({ session }) => {
367
+ const err = checkAuth(session);
368
+ if (err === void 0) return;
369
+ if (err) return err;
370
+ await session.send(`\u{1F50C} [${prefix}] \u6267\u884C\u4E2D\u2026`);
371
+ return execShellCmd(session, "syncpkgs", "syncpkgs");
372
+ });
373
+ ctx.command(`${prefix}.enable <name:text>`, "\u542F\u7528\u6307\u5B9A\u6269\u5C55\u5305").userFields(["authority"]).action(async ({ session }, name2) => {
374
+ const err = checkAuth(session);
375
+ if (err === void 0) return;
376
+ if (err) return err;
377
+ await session.send(`\u{1F50C} [${prefix}] \u6267\u884C\u4E2D\u2026`);
378
+ return execShellCmd(session, `enable ${name2}`, "enable");
379
+ });
380
+ ctx.command(`${prefix}.disable <name:text>`, "\u7981\u7528\u6307\u5B9A\u6269\u5C55\u5305").userFields(["authority"]).action(async ({ session }, name2) => {
381
+ const err = checkAuth(session);
382
+ if (err === void 0) return;
383
+ if (err) return err;
384
+ await session.send(`\u{1F50C} [${prefix}] \u6267\u884C\u4E2D\u2026`);
385
+ return execShellCmd(session, `disable ${name2}`, "disable");
386
+ });
91
387
  }
92
- function executeUpdate(config, logger) {
388
+ function executeUpdate(server) {
93
389
  return new Promise((resolve, reject) => {
94
390
  const conn = new Client();
95
- const totalTimeout = config.attachDelay + config.updateWait + 3e4;
391
+ const totalTimeout = server.attachDelay + server.updateWait + 3e4;
96
392
  const timer = setTimeout(() => {
97
393
  try {
98
394
  conn.end();
@@ -101,8 +397,7 @@ function executeUpdate(config, logger) {
101
397
  reject(new Error("SSH \u64CD\u4F5C\u8D85\u65F6"));
102
398
  }, totalTimeout);
103
399
  conn.on("ready", () => {
104
- logger.info("SSH \u5DF2\u8FDE\u63A5");
105
- conn.shell({ term: "xterm-256color", cols: 200, rows: 50 }, async (err, stream) => {
400
+ conn.shell({ term: "xterm-256color", cols: 200, rows: 50 }, (err, stream) => {
106
401
  if (err) {
107
402
  clearTimeout(timer);
108
403
  try {
@@ -112,79 +407,301 @@ function executeUpdate(config, logger) {
112
407
  reject(new Error(`\u6253\u5F00 shell \u5931\u8D25: ${err.message}`));
113
408
  return;
114
409
  }
115
- try {
116
- let output = "";
117
- const collectOutput = (duration) => {
118
- return new Promise((resolveCollected) => {
119
- let buf = "";
120
- const onData = (data) => {
121
- buf += data.toString("utf8");
122
- };
123
- stream.on("data", onData);
124
- setTimeout(() => {
125
- stream.removeListener("data", onData);
126
- resolveCollected(buf);
127
- }, duration);
128
- });
129
- };
130
- const sendAndWait = async (cmd, waitMs) => {
131
- stream.write(cmd);
132
- return collectOutput(waitMs);
133
- };
134
- output += await collectOutput(1e3);
135
- const screenCmd = config.screenSession ? `screen -r ${config.screenSession}
410
+ let output = "";
411
+ let hasError = false;
412
+ const collectOutput = (duration) => {
413
+ return new Promise((resolveCollected) => {
414
+ let buf = "";
415
+ const onData = (data) => {
416
+ buf += data.toString("utf8");
417
+ };
418
+ stream.on("data", onData);
419
+ setTimeout(() => {
420
+ stream.removeListener("data", onData);
421
+ resolveCollected(buf);
422
+ }, duration);
423
+ });
424
+ };
425
+ const sendAndWait = async (cmd, waitMs) => {
426
+ stream.write(cmd);
427
+ return collectOutput(waitMs);
428
+ };
429
+ const checkError = (text) => {
430
+ const lower = text.toLowerCase();
431
+ if (/error|fail|fatal|exception|拒绝|失败|错误/.test(lower)) {
432
+ if (!/0 errors?|no error/i.test(lower)) {
433
+ hasError = true;
434
+ }
435
+ }
436
+ };
437
+ (async () => {
438
+ try {
439
+ output += await collectOutput(1e3);
440
+ const screenCmd = server.screenSession ? `screen -r ${server.screenSession}
136
441
  ` : `screen -r
137
442
  `;
138
- logger.info("\u53D1\u9001\u547D\u4EE4: screen -r");
139
- output += await sendAndWait(screenCmd, config.attachDelay);
140
- logger.info(`\u53D1\u9001\u66F4\u65B0\u547D\u4EE4: ${config.updateCommand}`);
141
- output += await sendAndWait(config.updateCommand + "\n", config.updateWait);
142
- logger.info("\u5206\u79BB screen \u4F1A\u8BDD");
143
- stream.write("d");
144
- output += await collectOutput(1e3);
145
- stream.write("exit\n");
146
- output += await collectOutput(500);
147
- clearTimeout(timer);
148
- try {
149
- stream.close();
150
- } catch {
443
+ output += await sendAndWait(screenCmd, server.attachDelay);
444
+ const updateOutput = await sendAndWait(server.updateCommand + "\n", server.updateWait);
445
+ output += updateOutput;
446
+ checkError(updateOutput);
447
+ stream.write("d");
448
+ output += await collectOutput(1e3);
449
+ stream.write("exit\n");
450
+ output += await collectOutput(500);
451
+ clearTimeout(timer);
452
+ try {
453
+ stream.close();
454
+ } catch {
455
+ }
456
+ try {
457
+ conn.end();
458
+ } catch {
459
+ }
460
+ const cleanOutput = stripAnsi(output);
461
+ const lines = cleanOutput.split("\n").filter((l) => l.trim()).length;
462
+ resolve({ output: cleanOutput, lineCount: lines, hasError });
463
+ } catch (e) {
464
+ clearTimeout(timer);
465
+ try {
466
+ stream.close();
467
+ } catch {
468
+ }
469
+ try {
470
+ conn.end();
471
+ } catch {
472
+ }
473
+ reject(e);
151
474
  }
475
+ })();
476
+ });
477
+ });
478
+ conn.on("error", (err) => {
479
+ clearTimeout(timer);
480
+ reject(new Error(`SSH \u8FDE\u63A5\u9519\u8BEF: ${err.message}`));
481
+ });
482
+ conn.connect({
483
+ host: server.host,
484
+ port: server.port,
485
+ username: server.username,
486
+ password: server.password,
487
+ readyTimeout: 15e3
488
+ });
489
+ });
490
+ }
491
+ function executeRestart(server) {
492
+ return new Promise((resolve, reject) => {
493
+ const conn = new Client();
494
+ const totalTimeout = server.attachDelay + server.quitWait + server.restartWait + 3e4;
495
+ const timer = setTimeout(() => {
496
+ try {
497
+ conn.end();
498
+ } catch {
499
+ }
500
+ reject(new Error("SSH \u64CD\u4F5C\u8D85\u65F6"));
501
+ }, totalTimeout);
502
+ conn.on("ready", () => {
503
+ conn.shell({ term: "xterm-256color", cols: 200, rows: 50 }, (err, stream) => {
504
+ if (err) {
505
+ clearTimeout(timer);
152
506
  try {
153
507
  conn.end();
154
508
  } catch {
155
509
  }
156
- const cleanOutput = stripAnsi(output);
157
- resolve(cleanOutput);
158
- } catch (e) {
159
- clearTimeout(timer);
510
+ reject(new Error(`\u6253\u5F00 shell \u5931\u8D25: ${err.message}`));
511
+ return;
512
+ }
513
+ let output = "";
514
+ let hasError = false;
515
+ const collectOutput = (duration) => {
516
+ return new Promise((resolveCollected) => {
517
+ let buf = "";
518
+ const onData = (data) => {
519
+ buf += data.toString("utf8");
520
+ };
521
+ stream.on("data", onData);
522
+ setTimeout(() => {
523
+ stream.removeListener("data", onData);
524
+ resolveCollected(buf);
525
+ }, duration);
526
+ });
527
+ };
528
+ const sendAndWait = async (cmd, waitMs) => {
529
+ stream.write(cmd);
530
+ return collectOutput(waitMs);
531
+ };
532
+ const checkError = (text) => {
533
+ const lower = text.toLowerCase();
534
+ if (/error|fail|fatal|exception|拒绝|失败|错误/.test(lower)) {
535
+ if (!/0 errors?|no error/i.test(lower)) {
536
+ hasError = true;
537
+ }
538
+ }
539
+ };
540
+ (async () => {
160
541
  try {
161
- stream.close();
162
- } catch {
542
+ output += await collectOutput(1e3);
543
+ const screenCmd = server.screenSession ? `screen -r ${server.screenSession}
544
+ ` : `screen -r
545
+ `;
546
+ output += await sendAndWait(screenCmd, server.attachDelay);
547
+ const quitOutput = await sendAndWait("quit\n", server.quitWait);
548
+ output += quitOutput;
549
+ checkError(quitOutput);
550
+ const restartOutput = await sendAndWait("\x1B[A\n", server.restartWait);
551
+ output += restartOutput;
552
+ checkError(restartOutput);
553
+ stream.write("d");
554
+ output += await collectOutput(1e3);
555
+ stream.write("exit\n");
556
+ output += await collectOutput(500);
557
+ clearTimeout(timer);
558
+ try {
559
+ stream.close();
560
+ } catch {
561
+ }
562
+ try {
563
+ conn.end();
564
+ } catch {
565
+ }
566
+ const cleanOutput = stripAnsi(output);
567
+ const lines = cleanOutput.split("\n").filter((l) => l.trim()).length;
568
+ resolve({ output: cleanOutput, lineCount: lines, hasError });
569
+ } catch (e) {
570
+ clearTimeout(timer);
571
+ try {
572
+ stream.close();
573
+ } catch {
574
+ }
575
+ try {
576
+ conn.end();
577
+ } catch {
578
+ }
579
+ reject(e);
163
580
  }
581
+ })();
582
+ });
583
+ });
584
+ conn.on("error", (err) => {
585
+ clearTimeout(timer);
586
+ reject(new Error(`SSH \u8FDE\u63A5\u9519\u8BEF: ${err.message}`));
587
+ });
588
+ conn.connect({
589
+ host: server.host,
590
+ port: server.port,
591
+ username: server.username,
592
+ password: server.password,
593
+ readyTimeout: 15e3
594
+ });
595
+ });
596
+ }
597
+ function executeShellCommand(shellCmd, server) {
598
+ return new Promise((resolve, reject) => {
599
+ const conn = new Client();
600
+ const totalTimeout = server.attachDelay + server.commandWait + 2e4;
601
+ const timer = setTimeout(() => {
602
+ try {
603
+ conn.end();
604
+ } catch {
605
+ }
606
+ reject(new Error("SSH \u64CD\u4F5C\u8D85\u65F6"));
607
+ }, totalTimeout);
608
+ conn.on("ready", () => {
609
+ conn.shell({ term: "xterm-256color", cols: 200, rows: 50 }, (err, stream) => {
610
+ if (err) {
611
+ clearTimeout(timer);
164
612
  try {
165
613
  conn.end();
166
614
  } catch {
167
615
  }
168
- reject(e);
616
+ reject(new Error(`\u6253\u5F00 shell \u5931\u8D25: ${err.message}`));
617
+ return;
169
618
  }
619
+ let output = "";
620
+ let hasError = false;
621
+ const collectOutput = (duration) => {
622
+ return new Promise((resolveCollected) => {
623
+ let buf = "";
624
+ const onData = (data) => {
625
+ buf += data.toString("utf8");
626
+ };
627
+ stream.on("data", onData);
628
+ setTimeout(() => {
629
+ stream.removeListener("data", onData);
630
+ resolveCollected(buf);
631
+ }, duration);
632
+ });
633
+ };
634
+ const checkError = (text) => {
635
+ const lower = text.toLowerCase();
636
+ if (/error|fail|fatal|exception|拒绝|失败|错误/.test(lower)) {
637
+ if (!/0 errors?|no error/i.test(lower)) {
638
+ hasError = true;
639
+ }
640
+ }
641
+ };
642
+ (async () => {
643
+ try {
644
+ output += await collectOutput(500);
645
+ const screenCmd = server.screenSession ? `screen -r ${server.screenSession}
646
+ ` : `screen -r
647
+ `;
648
+ output += await collectOutput(server.attachDelay);
649
+ stream.write(screenCmd);
650
+ output += await collectOutput(server.attachDelay);
651
+ stream.write(shellCmd + "\n");
652
+ const cmdOutput = await collectOutput(server.commandWait);
653
+ output += cmdOutput;
654
+ checkError(cmdOutput);
655
+ stream.write("d");
656
+ output += await collectOutput(500);
657
+ stream.write("exit\n");
658
+ output += await collectOutput(300);
659
+ clearTimeout(timer);
660
+ try {
661
+ stream.close();
662
+ } catch {
663
+ }
664
+ try {
665
+ conn.end();
666
+ } catch {
667
+ }
668
+ const cleanOutput = stripAnsi(output);
669
+ const lines = cleanOutput.split("\n").filter((l) => l.trim()).length;
670
+ resolve({ output: cleanOutput, lineCount: lines, hasError });
671
+ } catch (e) {
672
+ clearTimeout(timer);
673
+ try {
674
+ stream.close();
675
+ } catch {
676
+ }
677
+ try {
678
+ conn.end();
679
+ } catch {
680
+ }
681
+ reject(e);
682
+ }
683
+ })();
170
684
  });
171
685
  });
172
686
  conn.on("error", (err) => {
173
687
  clearTimeout(timer);
174
688
  reject(new Error(`SSH \u8FDE\u63A5\u9519\u8BEF: ${err.message}`));
175
689
  });
176
- conn.on("close", () => {
177
- logger.info("SSH \u8FDE\u63A5\u5DF2\u5173\u95ED");
178
- });
179
690
  conn.connect({
180
- host: config.host,
181
- port: config.port,
182
- username: config.username,
183
- password: config.password,
691
+ host: server.host,
692
+ port: server.port,
693
+ username: server.username,
694
+ password: server.password,
184
695
  readyTimeout: 15e3
185
696
  });
186
697
  });
187
698
  }
699
+ function cleanShellOutput(text) {
700
+ return text.replace(/\[detached[^\n]*\n?/gi, "").replace(/\[screen[^\n]*\n?/gi, "").replace(/\n{3,}/g, "\n\n").trim();
701
+ }
702
+ function filterSensitive(text) {
703
+ return text.replace(/\/home\/[^/\s]+/g, "/home/***").replace(/\/root\//g, "/root/***/").replace(/(token|secret|password|passwd|key|apikey)\s*[=:]\s*\S+/gi, "$1=***").replace(/\b(\d{1,3})\.(\d{1,3})\.\d{1,3}\.\d{1,3}\b/g, "$1.$2.*.*");
704
+ }
188
705
  function stripAnsi(text) {
189
706
  return text.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").replace(/\x1b\][^\x07]*\x07/g, "").replace(/\x1b[()][AB012]/g, "").replace(/\r/g, "").replace(/\n{3,}/g, "\n\n").trim();
190
707
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-freekill-u",
3
3
  "description": "新月杀远程更新 — 通过 SSH 连接服务器自动进入 screen 会话执行更新",
4
- "version": "1.0.0",
4
+ "version": "1.0.2",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [