koishi-plugin-freekill-u 1.0.1 → 1.0.3

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 +719 -55
  2. package/package.json +1 -2
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,40 +29,88 @@ 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
+ restartSequence: import_koishi.Schema.string().role("textarea").description("\u91CD\u542F\u547D\u4EE4\u5E8F\u5217\uFF08quit \u540E\u6267\u884C\uFF09\u3002\u6BCF\u884C\u4E00\u6761\u547D\u4EE4\uFF0C\u7A7A\u884C = \u989D\u5916\u56DE\u8F66\u3002\u4F8B\u5982\uFF1A\nscreen\n\ncd /home/fk/freekill-asio-static\n./freekill-asio").default(""),
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),
41
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)
42
49
  });
43
50
  var usage = `
44
- # \u65B0\u6708\u6740\u8FDC\u7A0B\u66F4\u65B0\u63D2\u4EF6
51
+ # \u65B0\u6708\u6740\u8FDC\u7A0B\u7BA1\u7406\u63D2\u4EF6
45
52
 
46
- \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
47
54
  \u5371\u9669\u64CD\u4F5C\uFF0C\u8BF7\u52A1\u5FC5\u8BBE\u7F6E\u8DB3\u591F\u7684\u6743\u9650\u7B49\u7EA7\u3002
48
55
 
49
- ## \u4F7F\u7528\u65B9\u6CD5
56
+ ## \u914D\u7F6E\u65B9\u6CD5
50
57
 
51
- \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
63
+ - **\u91CD\u542F\u547D\u4EE4\u5E8F\u5217**\uFF1Aquit \u540E\u7684\u91CD\u542F\u547D\u4EE4\uFF0C\u6BCF\u884C\u4E00\u6761\u547D\u4EE4\uFF0C\u7A7A\u884C=\u989D\u5916\u56DE\u8F66\uFF08\u7559\u7A7A\u5219\u7528\u4E0A\u7BAD\u5934\u56DE\u653E\u4E0A\u6B21\u547D\u4EE4\uFF09
52
64
 
53
- ## \u5DE5\u4F5C\u6D41\u7A0B
65
+ ## \u91CD\u542F\u5E8F\u5217\u793A\u4F8B
54
66
 
55
- 1. SSH \u8FDE\u63A5\u5230\u670D\u52A1\u5668
56
- 2. \u6253\u5F00\u5E26 PTY \u7684\u4EA4\u4E92\u5F0F shell
57
- 3. \u53D1\u9001 \`screen -r [\u4F1A\u8BDDID]\` \u8FDB\u5165\u65B0\u6708\u6740 screen \u4F1A\u8BDD
58
- 4. \u53D1\u9001\u66F4\u65B0\u547D\u4EE4\uFF08\u9ED8\u8BA4 \`u\`\uFF09
59
- 5. \u7B49\u5F85\u66F4\u65B0\u8F93\u51FA
60
- 6. \u53D1\u9001 \`Ctrl-a d\` \u5206\u79BB screen \u4F1A\u8BDD
61
- 7. \u5173\u95ED SSH \u8FDE\u63A5\uFF0C\u8FD4\u56DE\u66F4\u65B0\u65E5\u5FD7
67
+ \`\`\`
68
+ screen
69
+
70
+ cd /home/fk/freekill-asio-static
71
+ ./freekill-asio
72
+ \`\`\`
73
+ \u4E0A\u65B9\u7A7A\u884C = \u591A\u6309\u4E00\u6B21\u56DE\u8F66\uFF1B\u4E0B\u9762\u4E24\u884C = cd \u548C\u542F\u52A8\u547D\u4EE4\u3002
74
+
75
+ ## \u547D\u4EE4\u5217\u8868
76
+
77
+ \u6240\u6709\u547D\u4EE4\u683C\u5F0F\uFF1A\`\u670D\u52A1\u5668\u540D.\u5B50\u547D\u4EE4\`
78
+
79
+ | \u547D\u4EE4 | \u522B\u540D | \u8BF4\u660E |
80
+ |------|------|------|
81
+ | \`\u670D\u52A1\u5668.u\` | \u2014 | \u8FDC\u7A0B\u66F4\u65B0\u670D\u52A1\u5668 |
82
+ | \`\u670D\u52A1\u5668.reboot\` | \u2014 | \u8FDC\u7A0B\u91CD\u542F\u670D\u52A1\u5668 |
83
+ | \`\u670D\u52A1\u5668.detached\` | \u2014 | \u5F3A\u884C\u5206\u79BB\u88AB\u5360\u7528\u7684 screen |
84
+ | \`\u670D\u52A1\u5668.stat\` | \u2014 | \u67E5\u770B\u8FD0\u884C\u72B6\u6001 |
85
+ | \`\u670D\u52A1\u5668.reloadconf\` | \`.r\` | \u91CD\u8F7D\u914D\u7F6E\u6587\u4EF6 |
86
+ | \`\u670D\u52A1\u5668.lsplayer\` | \u2014 | \u67E5\u770B\u5728\u7EBF\u73A9\u5BB6 |
87
+ | \`\u670D\u52A1\u5668.lsroom [id]\` | \u2014 | \u67E5\u770B\u623F\u95F4\u5217\u8868 |
88
+ | \`\u670D\u52A1\u5668.msg <\u5185\u5BB9>\` | \`.m\` | \u53D1\u5168\u4F53\u516C\u544A |
89
+ | \`\u670D\u52A1\u5668.msgroom <\u623F\u95F4> <\u5185\u5BB9>\` | \`.mr\` | \u5411\u623F\u95F4\u53D1\u6D88\u606F |
90
+ | \`\u670D\u52A1\u5668.kick <\u73A9\u5BB6>\` | \u2014 | \u8E22\u51FA\u73A9\u5BB6 |
91
+ | \`\u670D\u52A1\u5668.killroom <\u623F\u95F4>\` | \u2014 | \u5E9F\u5F03\u623F\u95F4 |
92
+ | \`\u670D\u52A1\u5668.checklobby\` | \u2014 | \u6E05\u5927\u5385\u50F5\u5C38 |
93
+ | \`\u670D\u52A1\u5668.ban <\u76EE\u6807>\` | \u2014 | \u5C01\u7981\u8D26\u53F7 |
94
+ | \`\u670D\u52A1\u5668.unban <\u76EE\u6807>\` | \u2014 | \u89E3\u5C01\u8D26\u53F7 |
95
+ | \`\u670D\u52A1\u5668.banip <IP>\` | \u2014 | \u5C01\u7981 IP |
96
+ | \`\u670D\u52A1\u5668.unbanip <IP>\` | \u2014 | \u89E3\u5C01 IP |
97
+ | \`\u670D\u52A1\u5668.banuuid <UUID>\` | \u2014 | \u5C01\u7981 UUID |
98
+ | \`\u670D\u52A1\u5668.unbanuuid <UUID>\` | \u2014 | \u89E3\u5C01 UUID |
99
+ | \`\u670D\u52A1\u5668.tempban <\u76EE\u6807> <\u65F6\u957F>\` | \u2014 | \u4E34\u65F6\u5C01\u7981 |
100
+ | \`\u670D\u52A1\u5668.tempmute <\u76EE\u6807> <\u65F6\u957F>\` | \u2014 | \u4E34\u65F6\u7981\u8A00 |
101
+ | \`\u670D\u52A1\u5668.unmute <\u76EE\u6807>\` | \u2014 | \u89E3\u9664\u7981\u8A00 |
102
+ | \`\u670D\u52A1\u5668.whitelist <\u64CD\u4F5C>\` | \u2014 | \u7BA1\u7406\u767D\u540D\u5355 |
103
+ | \`\u670D\u52A1\u5668.resetpassword <\u8D26\u53F7>\` | \`.rp\` | \u91CD\u7F6E\u5BC6\u7801 |
104
+ | \`\u670D\u52A1\u5668.pkgs\` | \u2014 | \u67E5\u770B\u6269\u5C55\u5305 |
105
+ | \`\u670D\u52A1\u5668.syncpkgs\` | \u2014 | \u540C\u6B65\u6269\u5C55\u5305 |
106
+ | \`\u670D\u52A1\u5668.install <URL>\` | \u2014 | \u5B89\u88C5\u6269\u5C55\u5305 |
107
+ | \`\u670D\u52A1\u5668.remove <\u540D\u79F0>\` | \u2014 | \u79FB\u9664\u6269\u5C55\u5305 |
108
+ | \`\u670D\u52A1\u5668.enable <\u540D\u79F0>\` | \u2014 | \u542F\u7528\u6269\u5C55\u5305 |
109
+ | \`\u670D\u52A1\u5668.disable <\u540D\u79F0>\` | \u2014 | \u7981\u7528\u6269\u5C55\u5305 |
62
110
 
63
111
  ## \u6CE8\u610F\u4E8B\u9879
64
112
 
65
- - screen \u4F1A\u8BDD\u5FC5\u987B\u662F **detached** \u72B6\u6001\uFF0C\u5982\u679C\u5DF2\u88AB\u4EBA attach\uFF0C\u9700\u8981\u5148\u7528 \`screen -d -r\`
113
+ - screen \u4F1A\u8BDD\u5FC5\u987B\u662F **detached** \u72B6\u6001
66
114
  - \u672C\u63D2\u4EF6\u6D89\u53CA\u670D\u52A1\u5668\u5B89\u5168\uFF0C\u8BF7\u52A1\u5FC5\u8BBE\u7F6E\u8DB3\u591F\u7684\u6743\u9650\u7B49\u7EA7
67
115
 
68
116
  ## \u53CD\u9988\u6E20\u9053
@@ -70,47 +118,319 @@ QQ \u7FA4\uFF1A1078815356
70
118
  `;
71
119
  function apply(ctx, config) {
72
120
  const logger = ctx.logger("freekill-u");
73
- ctx.command("\u66F4\u65B0\u65B0\u6708\u6740", "\u8FDC\u7A0B\u66F4\u65B0\u65B0\u6708\u6740\u670D\u52A1\u5668").userFields(["authority"]).action(async ({ session }) => {
74
- if (!config.host || !config.username) {
75
- return "\u26A0\uFE0F \u8BF7\u5148\u5728\u63D2\u4EF6\u914D\u7F6E\u4E2D\u586B\u5199 SSH \u8FDE\u63A5\u4FE1\u606F\u3002";
121
+ ctx.middleware(async (session, next) => {
122
+ const content = (session.content || "").trim();
123
+ if (!content) return next();
124
+ for (const server of config.servers) {
125
+ const prefix = server.name;
126
+ if (!prefix) continue;
127
+ const lower = content.toLowerCase();
128
+ const pfxLower = prefix.toLowerCase();
129
+ if (!lower.startsWith(pfxLower)) continue;
130
+ const rest = content.slice(prefix.length);
131
+ const isCommand = rest === "" || rest.startsWith(".") || rest.startsWith(" ") || rest === prefix;
132
+ if (!isCommand) continue;
133
+ if (server.operators && server.operators.trim()) {
134
+ const allowedIds = server.operators.split(/[,,\s]+/).map((s) => s.trim()).filter(Boolean);
135
+ if (allowedIds.length > 0) {
136
+ const userId = session.userId || "";
137
+ if (!allowedIds.includes(userId)) {
138
+ return;
139
+ }
140
+ }
141
+ }
142
+ }
143
+ return next();
144
+ });
145
+ for (const server of config.servers) {
146
+ registerServerCommands(ctx, config, server, logger);
147
+ }
148
+ }
149
+ function registerServerCommands(ctx, config, server, logger) {
150
+ const prefix = server.name;
151
+ function checkAuth(session) {
152
+ 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`;
153
+ if (server.operators && server.operators.trim()) {
154
+ const allowedIds = server.operators.split(/[,,\s]+/).map((s) => s.trim()).filter(Boolean);
155
+ if (allowedIds.length > 0) {
156
+ const userId = session.userId || "";
157
+ if (!allowedIds.includes(userId)) return void 0;
158
+ }
76
159
  }
77
160
  const userAuthority = session.user?.authority ?? 0;
78
- if (userAuthority < config.authority) {
79
- return `\u26A0\uFE0F \u6743\u9650\u4E0D\u8DB3\uFF0C\u9700\u8981\u7B49\u7EA7 ${config.authority} \u4EE5\u4E0A\u3002`;
161
+ if (userAuthority < config.authority) return `\u26A0\uFE0F \u6743\u9650\u4E0D\u8DB3\uFF0C\u9700\u8981\u7B49\u7EA7 ${config.authority} \u4EE5\u4E0A\u3002`;
162
+ return null;
163
+ }
164
+ async function execShellCmd(session, shellCmd, label) {
165
+ const startTime = Date.now();
166
+ try {
167
+ const result = await executeShellCommand(shellCmd, server);
168
+ const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
169
+ const output = smartCleanOutput(result.output, shellCmd);
170
+ return `\u{1F4CB} [${prefix}] ${label}
171
+ \u23F1 ${elapsed}s
172
+
173
+ ${output}`;
174
+ } catch (e) {
175
+ const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
176
+ logger.warn("Shell\u547D\u4EE4\u5931\u8D25", { server: prefix, cmd: shellCmd, error: e?.message || String(e) });
177
+ return `\u274C [${prefix}] ${label} \u5931\u8D25\uFF08${elapsed}s\uFF09\uFF1A${e?.message || String(e)}`;
80
178
  }
179
+ }
180
+ ctx.command(`${prefix}`, `${prefix} \u670D\u52A1\u5668\u7BA1\u7406`);
181
+ ctx.command(`${prefix}.u`, `\u66F4\u65B0 ${prefix} \u670D\u52A1\u5668`).userFields(["authority"]).action(async ({ session }) => {
182
+ const err = checkAuth(session);
183
+ if (err === void 0) return;
184
+ if (err) return err;
81
185
  const startTime = Date.now();
82
- await session.send("\u{1F50C} \u6B63\u5728\u8FDE\u63A5\u670D\u52A1\u5668\u2026");
186
+ await session.send(`\u{1F50C} [${prefix}] \u6B63\u5728\u8FDE\u63A5\u670D\u52A1\u5668\u2026`);
83
187
  try {
84
- const result = await executeUpdate(config);
188
+ const result = await executeUpdate(server);
85
189
  const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
86
- const lines = ["\u2705 \u65B0\u6708\u6740\u66F4\u65B0\u5B8C\u6210\uFF01", `\u23F1 \u8017\u65F6 ${elapsed} \u79D2`];
87
- if (result.hasError) {
88
- lines.push("\u26A0\uFE0F \u68C0\u6D4B\u5230\u53EF\u80FD\u7684\u9519\u8BEF\uFF0C\u5EFA\u8BAE\u68C0\u67E5\u670D\u52A1\u5668\u65E5\u5FD7\u3002");
89
- }
190
+ const lines = [`\u2705 [${prefix}] \u66F4\u65B0\u5B8C\u6210\uFF01`, `\u23F1 \u8017\u65F6 ${elapsed} \u79D2`];
191
+ 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");
90
192
  if (config.debugMode && result.output) {
91
193
  let output = filterSensitive(result.output);
92
- if (output.length > 2e3) {
93
- output = output.slice(-2e3) + "\n\u2026\uFF08\u8F93\u51FA\u5DF2\u622A\u65AD\uFF0C\u4EC5\u663E\u793A\u672B\u5C3E 2000 \u5B57\u7B26\uFF09";
94
- }
194
+ if (output.length > 2e3) output = output.slice(-2e3) + "\n\u2026\uFF08\u8F93\u51FA\u5DF2\u622A\u65AD\uFF09";
95
195
  lines.push(`
96
196
  \u{1F4CB} \u66F4\u65B0\u8F93\u51FA\uFF1A
97
197
  ${output}`);
98
198
  }
99
- if (result.hasError) {
100
- logger.warn("\u66F4\u65B0\u53EF\u80FD\u5931\u8D25", { elapsed, lines: result.lineCount });
199
+ if (result.hasError) logger.warn("\u66F4\u65B0\u53EF\u80FD\u5931\u8D25", { server: prefix, elapsed, lines: result.lineCount });
200
+ await session.send(lines.join("\n"));
201
+ } catch (e) {
202
+ const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
203
+ logger.warn("\u66F4\u65B0\u5931\u8D25", { server: prefix, error: e?.message || String(e), elapsed });
204
+ await session.send(`\u274C [${prefix}] \u66F4\u65B0\u5931\u8D25\uFF08${elapsed}s\uFF09\uFF1A${e?.message || String(e)}`);
205
+ }
206
+ });
207
+ ctx.command(`${prefix}.reboot`, `\u91CD\u542F ${prefix} \u670D\u52A1\u5668`).userFields(["authority"]).action(async ({ session }) => {
208
+ const err = checkAuth(session);
209
+ if (err === void 0) return;
210
+ if (err) return err;
211
+ const startTime = Date.now();
212
+ await session.send(`\u{1F50C} [${prefix}] \u6B63\u5728\u8FDE\u63A5\u670D\u52A1\u5668\u2026`);
213
+ try {
214
+ const result = await executeRestart(server);
215
+ const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
216
+ const lines = [`\u2705 [${prefix}] \u91CD\u542F\u5B8C\u6210\uFF01`, `\u23F1 \u8017\u65F6 ${elapsed} \u79D2`];
217
+ 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");
218
+ if (config.debugMode && result.output) {
219
+ let output = filterSensitive(result.output);
220
+ if (output.length > 2e3) output = output.slice(-2e3) + "\n\u2026\uFF08\u8F93\u51FA\u5DF2\u622A\u65AD\uFF09";
221
+ lines.push(`
222
+ \u{1F4CB} \u91CD\u542F\u8F93\u51FA\uFF1A
223
+ ${output}`);
101
224
  }
225
+ if (result.hasError) logger.warn("\u91CD\u542F\u53EF\u80FD\u5931\u8D25", { server: prefix, elapsed, lines: result.lineCount });
102
226
  await session.send(lines.join("\n"));
103
227
  } catch (e) {
104
228
  const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
105
- logger.warn("\u66F4\u65B0\u5931\u8D25", { error: e?.message || String(e), elapsed });
106
- await session.send(`\u274C \u66F4\u65B0\u5931\u8D25\uFF08\u8017\u65F6 ${elapsed} \u79D2\uFF09\uFF1A${e?.message || String(e)}`);
229
+ logger.warn("\u91CD\u542F\u5931\u8D25", { server: prefix, error: e?.message || String(e), elapsed });
230
+ await session.send(`\u274C [${prefix}] \u91CD\u542F\u5931\u8D25\uFF08${elapsed}s\uFF09\uFF1A${e?.message || String(e)}`);
231
+ }
232
+ });
233
+ ctx.command(`${prefix}.detached`, `\u5F3A\u5236\u5206\u79BB ${prefix} \u7684 screen \u4F1A\u8BDD`).userFields(["authority"]).action(async ({ session }) => {
234
+ const err = checkAuth(session);
235
+ if (err === void 0) return;
236
+ if (err) return err;
237
+ await session.send(`\u{1F50C} [${prefix}] \u6B63\u5728\u5206\u79BB screen\u2026`);
238
+ try {
239
+ await executeDetached(server);
240
+ await session.send(`\u2705 [${prefix}] screen \u5DF2\u5206\u79BB\u3002`);
241
+ } catch (e) {
242
+ logger.warn("\u5206\u79BBscreen\u5931\u8D25", { server: prefix, error: e?.message || String(e) });
243
+ await session.send(`\u274C [${prefix}] \u5206\u79BB\u5931\u8D25\uFF1A${e?.message || String(e)}`);
107
244
  }
108
245
  });
246
+ ctx.command(`${prefix}.stat`, "\u67E5\u770B\u670D\u52A1\u5668\u8FD0\u884C\u72B6\u6001\uFF08GC \u7B49\uFF09").userFields(["authority"]).action(async ({ session }) => {
247
+ const err = checkAuth(session);
248
+ if (err === void 0) return;
249
+ if (err) return err;
250
+ await session.send(`\u{1F50C} [${prefix}] \u67E5\u8BE2\u4E2D\u2026`);
251
+ return execShellCmd(session, "stat", "stat");
252
+ });
253
+ ctx.command(`${prefix}.reloadconf`, "\u91CD\u8F7D\u670D\u52A1\u5668\u914D\u7F6E\u6587\u4EF6").alias(`${prefix}.r`).userFields(["authority"]).action(async ({ session }) => {
254
+ const err = checkAuth(session);
255
+ if (err === void 0) return;
256
+ if (err) return err;
257
+ await session.send(`\u{1F50C} [${prefix}] \u67E5\u8BE2\u4E2D\u2026`);
258
+ return execShellCmd(session, "reloadconf", "reloadconf");
259
+ });
260
+ ctx.command(`${prefix}.lsplayer`, "\u67E5\u770B\u6240\u6709\u5728\u7EBF\u73A9\u5BB6").userFields(["authority"]).action(async ({ session }) => {
261
+ const err = checkAuth(session);
262
+ if (err === void 0) return;
263
+ if (err) return err;
264
+ await session.send(`\u{1F50C} [${prefix}] \u67E5\u8BE2\u4E2D\u2026`);
265
+ return execShellCmd(session, "lsplayer", "lsplayer");
266
+ });
267
+ 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) => {
268
+ const err = checkAuth(session);
269
+ if (err === void 0) return;
270
+ if (err) return err;
271
+ await session.send(`\u{1F50C} [${prefix}] \u67E5\u8BE2\u4E2D\u2026`);
272
+ const cmd = id ? `lsroom ${id}` : "lsroom";
273
+ return execShellCmd(session, cmd, cmd);
274
+ });
275
+ ctx.command(`${prefix}.msg <message:text>`, "\u5411\u5168\u4F53\u73A9\u5BB6\u53D1\u9001\u516C\u544A").alias(`${prefix}.m`).userFields(["authority"]).action(async ({ session }, message) => {
276
+ const err = checkAuth(session);
277
+ if (err === void 0) return;
278
+ if (err) return err;
279
+ await session.send(`\u{1F50C} [${prefix}] \u53D1\u9001\u4E2D\u2026`);
280
+ return execShellCmd(session, `msg ${message}`, "msg");
281
+ });
282
+ 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) => {
283
+ const err = checkAuth(session);
284
+ if (err === void 0) return;
285
+ if (err) return err;
286
+ await session.send(`\u{1F50C} [${prefix}] \u53D1\u9001\u4E2D\u2026`);
287
+ return execShellCmd(session, `msgroom ${roomId} ${message}`, "msgroom");
288
+ });
289
+ ctx.command(`${prefix}.kick <name:text>`, "\u8E22\u51FA\u6307\u5B9A\u73A9\u5BB6").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, `kick ${name2}`, "kick");
295
+ });
296
+ ctx.command(`${prefix}.killroom <roomId>`, "\u8E22\u51FA\u623F\u95F4\u6240\u6709\u4EBA\u5E76\u5E9F\u5F03\u623F\u95F4").userFields(["authority"]).action(async ({ session }, roomId) => {
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, `killroom ${roomId}`, "killroom");
302
+ });
303
+ ctx.command(`${prefix}.checklobby`, "\u6E05\u9664\u5927\u5385\u4E2D\u7684\u50F5\u5C38\u73A9\u5BB6").userFields(["authority"]).action(async ({ session }) => {
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, "checklobby", "checklobby");
309
+ });
310
+ 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) => {
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, `ban ${name2}`, "ban");
316
+ });
317
+ ctx.command(`${prefix}.unban <name:text>`, "\u89E3\u5C01\u6307\u5B9A\u8D26\u53F7\uFF08\u53EF\u591A\u4E2A\uFF09").userFields(["authority"]).action(async ({ session }, name2) => {
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, `unban ${name2}`, "unban");
323
+ });
324
+ ctx.command(`${prefix}.banip <name:text>`, "\u5C01\u7981 IP \u5730\u5740").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, `banip ${name2}`, "banip");
330
+ });
331
+ ctx.command(`${prefix}.unbanip <name:text>`, "\u89E3\u5C01 IP \u5730\u5740").userFields(["authority"]).action(async ({ session }, name2) => {
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, `unbanip ${name2}`, "unbanip");
337
+ });
338
+ ctx.command(`${prefix}.banuuid <name:text>`, "\u5C01\u7981 UUID").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, `banuuid ${name2}`, "banuuid");
344
+ });
345
+ ctx.command(`${prefix}.unbanuuid <name:text>`, "\u89E3\u5C01 UUID").userFields(["authority"]).action(async ({ session }, name2) => {
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, `unbanuuid ${name2}`, "unbanuuid");
351
+ });
352
+ 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) => {
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, `tempban ${name2} ${duration}`, "tempban");
358
+ });
359
+ 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) => {
360
+ const err = checkAuth(session);
361
+ if (err === void 0) return;
362
+ if (err) return err;
363
+ await session.send(`\u{1F50C} [${prefix}] \u6267\u884C\u4E2D\u2026`);
364
+ return execShellCmd(session, `tempmute ${name2} ${duration}`, "tempmute");
365
+ });
366
+ ctx.command(`${prefix}.unmute <name:text>`, "\u89E3\u9664\u7981\u8A00\uFF08\u53EF\u591A\u4E2A\uFF09").userFields(["authority"]).action(async ({ session }, name2) => {
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, `unmute ${name2}`, "unmute");
372
+ });
373
+ ctx.command(`${prefix}.whitelist <rest:text>`, "\u7BA1\u7406\u767D\u540D\u5355\uFF08add/remove \u540D\u5355\uFF09").userFields(["authority"]).action(async ({ session }, rest) => {
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, `whitelist ${rest}`, "whitelist");
379
+ });
380
+ ctx.command(`${prefix}.resetpassword <name:text>`, "\u91CD\u7F6E\u8D26\u53F7\u5BC6\u7801\u4E3A 1234").alias(`${prefix}.rp`).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, `resetpassword ${name2}`, "resetpassword");
386
+ });
387
+ ctx.command(`${prefix}.install <url:text>`, "\u4ECE URL \u5B89\u88C5\u6269\u5C55\u5305").userFields(["authority"]).action(async ({ session }, url) => {
388
+ const err = checkAuth(session);
389
+ if (err === void 0) return;
390
+ if (err) return err;
391
+ await session.send(`\u{1F50C} [${prefix}] \u6267\u884C\u4E2D\u2026`);
392
+ return execShellCmd(session, `install ${url}`, "install");
393
+ });
394
+ ctx.command(`${prefix}.remove <name:text>`, "\u79FB\u9664\u6269\u5C55\u5305").userFields(["authority"]).action(async ({ session }, name2) => {
395
+ const err = checkAuth(session);
396
+ if (err === void 0) return;
397
+ if (err) return err;
398
+ await session.send(`\u{1F50C} [${prefix}] \u6267\u884C\u4E2D\u2026`);
399
+ return execShellCmd(session, `remove ${name2}`, "remove");
400
+ });
401
+ ctx.command(`${prefix}.pkgs`, "\u67E5\u770B\u6240\u6709\u5DF2\u5B89\u88C5\u6269\u5C55\u5305").userFields(["authority"]).action(async ({ session }) => {
402
+ const err = checkAuth(session);
403
+ if (err === void 0) return;
404
+ if (err) return err;
405
+ await session.send(`\u{1F50C} [${prefix}] \u67E5\u8BE2\u4E2D\u2026`);
406
+ return execShellCmd(session, "pkgs", "pkgs");
407
+ });
408
+ ctx.command(`${prefix}.syncpkgs`, "\u4ECE\u6587\u4EF6\u7CFB\u7EDF\u540C\u6B65\u6269\u5C55\u5305\u5230\u6570\u636E\u5E93").userFields(["authority"]).action(async ({ session }) => {
409
+ const err = checkAuth(session);
410
+ if (err === void 0) return;
411
+ if (err) return err;
412
+ await session.send(`\u{1F50C} [${prefix}] \u6267\u884C\u4E2D\u2026`);
413
+ return execShellCmd(session, "syncpkgs", "syncpkgs");
414
+ });
415
+ ctx.command(`${prefix}.enable <name:text>`, "\u542F\u7528\u6307\u5B9A\u6269\u5C55\u5305").userFields(["authority"]).action(async ({ session }, name2) => {
416
+ const err = checkAuth(session);
417
+ if (err === void 0) return;
418
+ if (err) return err;
419
+ await session.send(`\u{1F50C} [${prefix}] \u6267\u884C\u4E2D\u2026`);
420
+ return execShellCmd(session, `enable ${name2}`, "enable");
421
+ });
422
+ ctx.command(`${prefix}.disable <name:text>`, "\u7981\u7528\u6307\u5B9A\u6269\u5C55\u5305").userFields(["authority"]).action(async ({ session }, name2) => {
423
+ const err = checkAuth(session);
424
+ if (err === void 0) return;
425
+ if (err) return err;
426
+ await session.send(`\u{1F50C} [${prefix}] \u6267\u884C\u4E2D\u2026`);
427
+ return execShellCmd(session, `disable ${name2}`, "disable");
428
+ });
109
429
  }
110
- function executeUpdate(config) {
430
+ function executeUpdate(server) {
111
431
  return new Promise((resolve, reject) => {
112
432
  const conn = new Client();
113
- const totalTimeout = config.attachDelay + config.updateWait + 3e4;
433
+ const totalTimeout = server.attachDelay + server.updateWait + 3e4;
114
434
  const timer = setTimeout(() => {
115
435
  try {
116
436
  conn.end();
@@ -159,11 +479,11 @@ function executeUpdate(config) {
159
479
  (async () => {
160
480
  try {
161
481
  output += await collectOutput(1e3);
162
- const screenCmd = config.screenSession ? `screen -r ${config.screenSession}
482
+ const screenCmd = server.screenSession ? `screen -r ${server.screenSession}
163
483
  ` : `screen -r
164
484
  `;
165
- output += await sendAndWait(screenCmd, config.attachDelay);
166
- const updateOutput = await sendAndWait(config.updateCommand + "\n", config.updateWait);
485
+ output += await sendAndWait(screenCmd, server.attachDelay);
486
+ const updateOutput = await sendAndWait("u\n", server.updateWait);
167
487
  output += updateOutput;
168
488
  checkError(updateOutput);
169
489
  stream.write("d");
@@ -181,11 +501,122 @@ function executeUpdate(config) {
181
501
  }
182
502
  const cleanOutput = stripAnsi(output);
183
503
  const lines = cleanOutput.split("\n").filter((l) => l.trim()).length;
184
- resolve({
185
- output: cleanOutput,
186
- lineCount: lines,
187
- hasError
188
- });
504
+ resolve({ output: cleanOutput, lineCount: lines, hasError });
505
+ } catch (e) {
506
+ clearTimeout(timer);
507
+ try {
508
+ stream.close();
509
+ } catch {
510
+ }
511
+ try {
512
+ conn.end();
513
+ } catch {
514
+ }
515
+ reject(e);
516
+ }
517
+ })();
518
+ });
519
+ });
520
+ conn.on("error", (err) => {
521
+ clearTimeout(timer);
522
+ reject(new Error(`SSH \u8FDE\u63A5\u9519\u8BEF: ${err.message}`));
523
+ });
524
+ conn.connect({
525
+ host: server.host,
526
+ port: server.port,
527
+ username: server.username,
528
+ password: server.password,
529
+ readyTimeout: 15e3
530
+ });
531
+ });
532
+ }
533
+ function executeRestart(server) {
534
+ return new Promise((resolve, reject) => {
535
+ const conn = new Client();
536
+ const totalTimeout = server.attachDelay + server.quitWait + server.restartWait + 3e4;
537
+ const timer = setTimeout(() => {
538
+ try {
539
+ conn.end();
540
+ } catch {
541
+ }
542
+ reject(new Error("SSH \u64CD\u4F5C\u8D85\u65F6"));
543
+ }, totalTimeout);
544
+ conn.on("ready", () => {
545
+ conn.shell({ term: "xterm-256color", cols: 200, rows: 50 }, (err, stream) => {
546
+ if (err) {
547
+ clearTimeout(timer);
548
+ try {
549
+ conn.end();
550
+ } catch {
551
+ }
552
+ reject(new Error(`\u6253\u5F00 shell \u5931\u8D25: ${err.message}`));
553
+ return;
554
+ }
555
+ let output = "";
556
+ let hasError = false;
557
+ const collectOutput = (duration) => {
558
+ return new Promise((resolveCollected) => {
559
+ let buf = "";
560
+ const onData = (data) => {
561
+ buf += data.toString("utf8");
562
+ };
563
+ stream.on("data", onData);
564
+ setTimeout(() => {
565
+ stream.removeListener("data", onData);
566
+ resolveCollected(buf);
567
+ }, duration);
568
+ });
569
+ };
570
+ const sendAndWait = async (cmd, waitMs) => {
571
+ stream.write(cmd);
572
+ return collectOutput(waitMs);
573
+ };
574
+ const checkError = (text) => {
575
+ const lower = text.toLowerCase();
576
+ if (/error|fail|fatal|exception|拒绝|失败|错误/.test(lower)) {
577
+ if (!/0 errors?|no error/i.test(lower)) {
578
+ hasError = true;
579
+ }
580
+ }
581
+ };
582
+ (async () => {
583
+ try {
584
+ output += await collectOutput(1e3);
585
+ const screenCmd = server.screenSession ? `screen -r ${server.screenSession}
586
+ ` : `screen -r
587
+ `;
588
+ output += await sendAndWait(screenCmd, server.attachDelay);
589
+ const quitOutput = await sendAndWait("quit\n", server.quitWait);
590
+ output += quitOutput;
591
+ checkError(quitOutput);
592
+ const reSeq = parseRestartSequence(server.restartSequence);
593
+ if (reSeq.length > 0) {
594
+ for (const cmd of reSeq) {
595
+ stream.write(cmd + "\n");
596
+ output += await collectOutput(1e3);
597
+ }
598
+ output += await collectOutput(server.restartWait);
599
+ } else {
600
+ const restartOutput = await sendAndWait("\x1B[A\n", server.restartWait);
601
+ output += restartOutput;
602
+ checkError(restartOutput);
603
+ }
604
+ stream.write("d");
605
+ output += await collectOutput(1e3);
606
+ stream.write("exit\n");
607
+ output += await collectOutput(500);
608
+ clearTimeout(timer);
609
+ try {
610
+ stream.close();
611
+ } catch {
612
+ }
613
+ try {
614
+ conn.end();
615
+ } catch {
616
+ }
617
+ const cleanOutput = stripAnsi(output);
618
+ const lines = cleanOutput.split("\n").filter((l) => l.trim()).length;
619
+ resolve({ output: cleanOutput, lineCount: lines, hasError });
189
620
  } catch (e) {
190
621
  clearTimeout(timer);
191
622
  try {
@@ -206,14 +637,247 @@ function executeUpdate(config) {
206
637
  reject(new Error(`SSH \u8FDE\u63A5\u9519\u8BEF: ${err.message}`));
207
638
  });
208
639
  conn.connect({
209
- host: config.host,
210
- port: config.port,
211
- username: config.username,
212
- password: config.password,
640
+ host: server.host,
641
+ port: server.port,
642
+ username: server.username,
643
+ password: server.password,
213
644
  readyTimeout: 15e3
214
645
  });
215
646
  });
216
647
  }
648
+ function executeDetached(server) {
649
+ return new Promise((resolve, reject) => {
650
+ const conn = new Client();
651
+ const totalTimeout = server.attachDelay + 15e3;
652
+ const timer = setTimeout(() => {
653
+ try {
654
+ conn.end();
655
+ } catch {
656
+ }
657
+ reject(new Error("SSH \u64CD\u4F5C\u8D85\u65F6"));
658
+ }, totalTimeout);
659
+ conn.on("ready", () => {
660
+ conn.shell({ term: "xterm-256color", cols: 200, rows: 50 }, (err, stream) => {
661
+ if (err) {
662
+ clearTimeout(timer);
663
+ try {
664
+ conn.end();
665
+ } catch {
666
+ }
667
+ reject(new Error(`\u6253\u5F00 shell \u5931\u8D25: ${err.message}`));
668
+ return;
669
+ }
670
+ const collectOutput = (duration) => {
671
+ return new Promise((resolveCollected) => {
672
+ let buf = "";
673
+ const onData = (data) => {
674
+ buf += data.toString("utf8");
675
+ };
676
+ stream.on("data", onData);
677
+ setTimeout(() => {
678
+ stream.removeListener("data", onData);
679
+ resolveCollected(buf);
680
+ }, duration);
681
+ });
682
+ };
683
+ (async () => {
684
+ try {
685
+ await collectOutput(500);
686
+ const detachCmd = server.screenSession ? `screen -d ${server.screenSession}
687
+ ` : `screen -d
688
+ `;
689
+ stream.write(detachCmd);
690
+ await collectOutput(1e3);
691
+ stream.write("exit\n");
692
+ await collectOutput(300);
693
+ clearTimeout(timer);
694
+ try {
695
+ stream.close();
696
+ } catch {
697
+ }
698
+ try {
699
+ conn.end();
700
+ } catch {
701
+ }
702
+ resolve();
703
+ } catch (e) {
704
+ clearTimeout(timer);
705
+ try {
706
+ stream.close();
707
+ } catch {
708
+ }
709
+ try {
710
+ conn.end();
711
+ } catch {
712
+ }
713
+ reject(e);
714
+ }
715
+ })();
716
+ });
717
+ });
718
+ conn.on("error", (err) => {
719
+ clearTimeout(timer);
720
+ reject(new Error(`SSH \u8FDE\u63A5\u9519\u8BEF: ${err.message}`));
721
+ });
722
+ conn.connect({
723
+ host: server.host,
724
+ port: server.port,
725
+ username: server.username,
726
+ password: server.password,
727
+ readyTimeout: 15e3
728
+ });
729
+ });
730
+ }
731
+ function executeShellCommand(shellCmd, server) {
732
+ return new Promise((resolve, reject) => {
733
+ const conn = new Client();
734
+ const totalTimeout = server.attachDelay + server.commandWait + 2e4;
735
+ const timer = setTimeout(() => {
736
+ try {
737
+ conn.end();
738
+ } catch {
739
+ }
740
+ reject(new Error("SSH \u64CD\u4F5C\u8D85\u65F6"));
741
+ }, totalTimeout);
742
+ conn.on("ready", () => {
743
+ conn.shell({ term: "xterm-256color", cols: 200, rows: 50 }, (err, stream) => {
744
+ if (err) {
745
+ clearTimeout(timer);
746
+ try {
747
+ conn.end();
748
+ } catch {
749
+ }
750
+ reject(new Error(`\u6253\u5F00 shell \u5931\u8D25: ${err.message}`));
751
+ return;
752
+ }
753
+ let output = "";
754
+ let hasError = false;
755
+ const collectOutput = (duration) => {
756
+ return new Promise((resolveCollected) => {
757
+ let buf = "";
758
+ const onData = (data) => {
759
+ buf += data.toString("utf8");
760
+ };
761
+ stream.on("data", onData);
762
+ setTimeout(() => {
763
+ stream.removeListener("data", onData);
764
+ resolveCollected(buf);
765
+ }, duration);
766
+ });
767
+ };
768
+ const checkError = (text) => {
769
+ const lower = text.toLowerCase();
770
+ if (/error|fail|fatal|exception|拒绝|失败|错误/.test(lower)) {
771
+ if (!/0 errors?|no error/i.test(lower)) {
772
+ hasError = true;
773
+ }
774
+ }
775
+ };
776
+ (async () => {
777
+ try {
778
+ output += await collectOutput(500);
779
+ const screenCmd = server.screenSession ? `screen -r ${server.screenSession}
780
+ ` : `screen -r
781
+ `;
782
+ output += await collectOutput(server.attachDelay);
783
+ stream.write(screenCmd);
784
+ output += await collectOutput(server.attachDelay);
785
+ stream.write(shellCmd + "\n");
786
+ const cmdOutput = await collectOutput(server.commandWait);
787
+ output += cmdOutput;
788
+ checkError(cmdOutput);
789
+ stream.write("d");
790
+ output += await collectOutput(500);
791
+ stream.write("exit\n");
792
+ output += await collectOutput(300);
793
+ clearTimeout(timer);
794
+ try {
795
+ stream.close();
796
+ } catch {
797
+ }
798
+ try {
799
+ conn.end();
800
+ } catch {
801
+ }
802
+ const cleanOutput = stripAnsi(output);
803
+ const lines = cleanOutput.split("\n").filter((l) => l.trim()).length;
804
+ resolve({ output: cleanOutput, lineCount: lines, hasError });
805
+ } catch (e) {
806
+ clearTimeout(timer);
807
+ try {
808
+ stream.close();
809
+ } catch {
810
+ }
811
+ try {
812
+ conn.end();
813
+ } catch {
814
+ }
815
+ reject(e);
816
+ }
817
+ })();
818
+ });
819
+ });
820
+ conn.on("error", (err) => {
821
+ clearTimeout(timer);
822
+ reject(new Error(`SSH \u8FDE\u63A5\u9519\u8BEF: ${err.message}`));
823
+ });
824
+ conn.connect({
825
+ host: server.host,
826
+ port: server.port,
827
+ username: server.username,
828
+ password: server.password,
829
+ readyTimeout: 15e3
830
+ });
831
+ });
832
+ }
833
+ function parseRestartSequence(sequence) {
834
+ if (!sequence || !sequence.trim()) return [];
835
+ const lines = sequence.split("\n");
836
+ const commands = [];
837
+ for (const line of lines) {
838
+ const trimmed = line.trim();
839
+ if (trimmed) {
840
+ commands.push(trimmed);
841
+ } else {
842
+ commands.push("");
843
+ }
844
+ }
845
+ return commands;
846
+ }
847
+ function smartCleanOutput(text, shellCmd) {
848
+ let result = text.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").replace(/\x1b\][^\x07]*\x07/g, "").replace(/\x1b[()][AB012]/g, "").replace(/\x1b\[\?[0-9]+[hl]/g, "").replace(/\r/g, "");
849
+ const lines = result.split("\n");
850
+ const isNoise = (line) => {
851
+ const trimmed = line.trim();
852
+ if (!trimmed) return true;
853
+ if (/\[detached|\[screen|\[remote/i.test(trimmed)) return true;
854
+ if (/^\d{2}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}/.test(trimmed)) return true;
855
+ if ((trimmed.match(/█/g) || []).length >= 5) return true;
856
+ if ((trimmed.match(/═/g) || []).length >= 5) return true;
857
+ if (/^(Git|Doc|Version|Build|Commit):/i.test(trimmed)) return true;
858
+ if (/^\w+@[\w-]+ in ~/.test(trimmed)) return true;
859
+ if (/^(Free|Open|Flexible)/i.test(trimmed) && trimmed.length < 20) return true;
860
+ if (/^\S+>\s*$/.test(trimmed)) return true;
861
+ if (/^\s*[#$%]\s*$/.test(trimmed)) return true;
862
+ if (/^\s*>\s*$/.test(trimmed)) return true;
863
+ if (trimmed.startsWith("fk-asio>")) return true;
864
+ if (/^(exit|logout|Ctrl-a d)\s*$/i.test(trimmed)) return true;
865
+ if (trimmed.startsWith("[?") || trimmed.startsWith("[>")) return true;
866
+ return false;
867
+ };
868
+ const kept = [];
869
+ for (const line of lines) {
870
+ if (!isNoise(line)) {
871
+ kept.push(line);
872
+ }
873
+ }
874
+ result = kept.join("\n").trim();
875
+ result = result.replace(/\n{3,}/g, "\n\n");
876
+ if (result.length > 1500) {
877
+ result = result.slice(-1500) + "\n\u2026\uFF08\u8F93\u51FA\u5DF2\u622A\u65AD\uFF09";
878
+ }
879
+ return result || "(\u65E0\u8F93\u51FA)";
880
+ }
217
881
  function filterSensitive(text) {
218
882
  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.*.*");
219
883
  }
package/package.json CHANGED
@@ -1,14 +1,13 @@
1
1
  {
2
2
  "name": "koishi-plugin-freekill-u",
3
3
  "description": "新月杀远程更新 — 通过 SSH 连接服务器自动进入 screen 会话执行更新",
4
- "version": "1.0.1",
4
+ "version": "1.0.3",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [
8
8
  "lib"
9
9
  ],
10
10
  "license": "MIT",
11
- "scripts": {},
12
11
  "keywords": [
13
12
  "chatbot",
14
13
  "koishi",