koishi-plugin-freekill-u 1.0.2 → 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 +199 -20
  2. package/package.json +1 -2
package/lib/index.js CHANGED
@@ -36,7 +36,7 @@ var Config = import_koishi.Schema.object({
36
36
  username: import_koishi.Schema.string().description("SSH \u7528\u6237\u540D").default(""),
37
37
  password: import_koishi.Schema.string().role("secret").description("SSH \u5BC6\u7801").default(""),
38
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"),
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
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
41
  attachDelay: import_koishi.Schema.number().description("screen -r \u540E\u7B49\u5F85 attach \u7684\u6BEB\u79D2\u6570").default(3e3).min(500).max(3e4),
42
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),
@@ -60,6 +60,17 @@ var usage = `
60
60
  - **SSH \u4FE1\u606F**\uFF1A\u4E3B\u673A\u3001\u7AEF\u53E3\u3001\u7528\u6237\u540D\u3001\u5BC6\u7801
61
61
  - **screen \u4F1A\u8BDD**\uFF1A\u6709\u591A\u4E2A screen \u65F6\u586B\u5199\u4F1A\u8BDD\u6807\u8BC6
62
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
64
+
65
+ ## \u91CD\u542F\u5E8F\u5217\u793A\u4F8B
66
+
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
63
74
 
64
75
  ## \u547D\u4EE4\u5217\u8868
65
76
 
@@ -68,9 +79,10 @@ var usage = `
68
79
  | \u547D\u4EE4 | \u522B\u540D | \u8BF4\u660E |
69
80
  |------|------|------|
70
81
  | \`\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 |
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 |
72
84
  | \`\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 |
85
+ | \`\u670D\u52A1\u5668.reloadconf\` | \`.r\` | \u91CD\u8F7D\u914D\u7F6E\u6587\u4EF6 |
74
86
  | \`\u670D\u52A1\u5668.lsplayer\` | \u2014 | \u67E5\u770B\u5728\u7EBF\u73A9\u5BB6 |
75
87
  | \`\u670D\u52A1\u5668.lsroom [id]\` | \u2014 | \u67E5\u770B\u623F\u95F4\u5217\u8868 |
76
88
  | \`\u670D\u52A1\u5668.msg <\u5185\u5BB9>\` | \`.m\` | \u53D1\u5168\u4F53\u516C\u544A |
@@ -106,6 +118,30 @@ QQ \u7FA4\uFF1A1078815356
106
118
  `;
107
119
  function apply(ctx, config) {
108
120
  const logger = ctx.logger("freekill-u");
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
+ });
109
145
  for (const server of config.servers) {
110
146
  registerServerCommands(ctx, config, server, logger);
111
147
  }
@@ -130,18 +166,11 @@ function registerServerCommands(ctx, config, server, logger) {
130
166
  try {
131
167
  const result = await executeShellCommand(shellCmd, server);
132
168
  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`;
140
- }
169
+ const output = smartCleanOutput(result.output, shellCmd);
141
170
  return `\u{1F4CB} [${prefix}] ${label}
142
171
  \u23F1 ${elapsed}s
143
172
 
144
- ${output || "(\u65E0\u8F93\u51FA)"}`;
173
+ ${output}`;
145
174
  } catch (e) {
146
175
  const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
147
176
  logger.warn("Shell\u547D\u4EE4\u5931\u8D25", { server: prefix, cmd: shellCmd, error: e?.message || String(e) });
@@ -175,7 +204,7 @@ ${output}`);
175
204
  await session.send(`\u274C [${prefix}] \u66F4\u65B0\u5931\u8D25\uFF08${elapsed}s\uFF09\uFF1A${e?.message || String(e)}`);
176
205
  }
177
206
  });
178
- ctx.command(`${prefix}.r`, `\u91CD\u542F ${prefix} \u670D\u52A1\u5668`).userFields(["authority"]).action(async ({ session }) => {
207
+ ctx.command(`${prefix}.reboot`, `\u91CD\u542F ${prefix} \u670D\u52A1\u5668`).userFields(["authority"]).action(async ({ session }) => {
179
208
  const err = checkAuth(session);
180
209
  if (err === void 0) return;
181
210
  if (err) return err;
@@ -201,6 +230,19 @@ ${output}`);
201
230
  await session.send(`\u274C [${prefix}] \u91CD\u542F\u5931\u8D25\uFF08${elapsed}s\uFF09\uFF1A${e?.message || String(e)}`);
202
231
  }
203
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)}`);
244
+ }
245
+ });
204
246
  ctx.command(`${prefix}.stat`, "\u67E5\u770B\u670D\u52A1\u5668\u8FD0\u884C\u72B6\u6001\uFF08GC \u7B49\uFF09").userFields(["authority"]).action(async ({ session }) => {
205
247
  const err = checkAuth(session);
206
248
  if (err === void 0) return;
@@ -208,7 +250,7 @@ ${output}`);
208
250
  await session.send(`\u{1F50C} [${prefix}] \u67E5\u8BE2\u4E2D\u2026`);
209
251
  return execShellCmd(session, "stat", "stat");
210
252
  });
211
- ctx.command(`${prefix}.reloadconf`, "\u91CD\u8F7D\u670D\u52A1\u5668\u914D\u7F6E\u6587\u4EF6").userFields(["authority"]).action(async ({ session }) => {
253
+ ctx.command(`${prefix}.reloadconf`, "\u91CD\u8F7D\u670D\u52A1\u5668\u914D\u7F6E\u6587\u4EF6").alias(`${prefix}.r`).userFields(["authority"]).action(async ({ session }) => {
212
254
  const err = checkAuth(session);
213
255
  if (err === void 0) return;
214
256
  if (err) return err;
@@ -441,7 +483,7 @@ function executeUpdate(server) {
441
483
  ` : `screen -r
442
484
  `;
443
485
  output += await sendAndWait(screenCmd, server.attachDelay);
444
- const updateOutput = await sendAndWait(server.updateCommand + "\n", server.updateWait);
486
+ const updateOutput = await sendAndWait("u\n", server.updateWait);
445
487
  output += updateOutput;
446
488
  checkError(updateOutput);
447
489
  stream.write("d");
@@ -547,9 +589,18 @@ function executeRestart(server) {
547
589
  const quitOutput = await sendAndWait("quit\n", server.quitWait);
548
590
  output += quitOutput;
549
591
  checkError(quitOutput);
550
- const restartOutput = await sendAndWait("\x1B[A\n", server.restartWait);
551
- output += restartOutput;
552
- checkError(restartOutput);
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
+ }
553
604
  stream.write("d");
554
605
  output += await collectOutput(1e3);
555
606
  stream.write("exit\n");
@@ -594,6 +645,89 @@ function executeRestart(server) {
594
645
  });
595
646
  });
596
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
+ }
597
731
  function executeShellCommand(shellCmd, server) {
598
732
  return new Promise((resolve, reject) => {
599
733
  const conn = new Client();
@@ -696,8 +830,53 @@ function executeShellCommand(shellCmd, server) {
696
830
  });
697
831
  });
698
832
  }
699
- function cleanShellOutput(text) {
700
- return text.replace(/\[detached[^\n]*\n?/gi, "").replace(/\[screen[^\n]*\n?/gi, "").replace(/\n{3,}/g, "\n\n").trim();
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)";
701
880
  }
702
881
  function filterSensitive(text) {
703
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.*.*");
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.2",
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",