koishi-plugin-restart-projectzomboid 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.d.ts +14 -0
- package/lib/index.js +217 -0
- package/package.json +20 -0
- package/readme.md +5 -0
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Context, Schema } from 'koishi';
|
|
2
|
+
export declare const name = "restart-projectzomboid";
|
|
3
|
+
export interface Config {
|
|
4
|
+
sourceDir: string;
|
|
5
|
+
targetDir: string;
|
|
6
|
+
batPath: string;
|
|
7
|
+
fileNames: string[];
|
|
8
|
+
allowedGroups: string[];
|
|
9
|
+
allowedUsers: string[];
|
|
10
|
+
publicRestartLimit: number;
|
|
11
|
+
publicRestartCooldown: number;
|
|
12
|
+
}
|
|
13
|
+
export declare const Config: Schema<Config>;
|
|
14
|
+
export declare function apply(ctx: Context, config: Config): void;
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name2 in all)
|
|
10
|
+
__defProp(target, name2, { get: all[name2], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var src_exports = {};
|
|
32
|
+
__export(src_exports, {
|
|
33
|
+
Config: () => Config,
|
|
34
|
+
apply: () => apply,
|
|
35
|
+
name: () => name
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(src_exports);
|
|
38
|
+
var import_koishi = require("koishi");
|
|
39
|
+
var fs = __toESM(require("fs"));
|
|
40
|
+
var path = __toESM(require("path"));
|
|
41
|
+
var import_child_process = require("child_process");
|
|
42
|
+
var name = "restart-projectzomboid";
|
|
43
|
+
var Config = import_koishi.Schema.object({
|
|
44
|
+
sourceDir: import_koishi.Schema.string().default("C:\\Users\\Administrator\\Desktop\\配置文件目录").description("配置文件源目录路径").required(),
|
|
45
|
+
targetDir: import_koishi.Schema.string().default("C:\\Users\\Administrator\\Zomboid\\server").description("服务器配置目标目录路径").required(),
|
|
46
|
+
batPath: import_koishi.Schema.string().default("C:\\Program Files (x86)\\Steam\\steamapps\\common\\Project Zomboid\\server\\StartServer64.bat").description("StartServer64.bat 的完整路径").required(),
|
|
47
|
+
fileNames: import_koishi.Schema.array(String).default([
|
|
48
|
+
"servertest.ini",
|
|
49
|
+
"servertest_SandboxVars.lua",
|
|
50
|
+
"servertest_spawnpoints.lua",
|
|
51
|
+
"servertest_spawnregions.lua"
|
|
52
|
+
]).description("需要复制的配置文件名列表").required(),
|
|
53
|
+
allowedGroups: import_koishi.Schema.array(String).description("有权执行操作的QQ群号列表").required(),
|
|
54
|
+
allowedUsers: import_koishi.Schema.array(String).description("管理员QQ号列表").required(),
|
|
55
|
+
publicRestartLimit: import_koishi.Schema.number().default(3).description("普通用户每日可使用的重启次数(全局共享)").required(),
|
|
56
|
+
publicRestartCooldown: import_koishi.Schema.number().default(10).description("普通用户使用重启指令的冷却时间(分钟,全局共享)").required()
|
|
57
|
+
}).description("僵尸毁灭工程服务器管理插件");
|
|
58
|
+
function apply(ctx, config) {
|
|
59
|
+
const logger = new import_koishi.Logger("pz-server-manager");
|
|
60
|
+
let serverProcess = null;
|
|
61
|
+
let isMonitoring = false;
|
|
62
|
+
const publicRestartState = {
|
|
63
|
+
remainingUses: config.publicRestartLimit,
|
|
64
|
+
lastUseTime: 0
|
|
65
|
+
};
|
|
66
|
+
const checkAdminPermission = /* @__PURE__ */ __name((groupId, userId) => {
|
|
67
|
+
if (!groupId) return false;
|
|
68
|
+
const isGroupAllowed = config.allowedGroups.includes(groupId);
|
|
69
|
+
const isUserAdmin = config.allowedUsers.includes(userId);
|
|
70
|
+
return isGroupAllowed && isUserAdmin;
|
|
71
|
+
}, "checkAdminPermission");
|
|
72
|
+
const copyConfigFiles = /* @__PURE__ */ __name(() => {
|
|
73
|
+
logger.info("开始复制配置文件...");
|
|
74
|
+
try {
|
|
75
|
+
if (!fs.existsSync(config.targetDir)) {
|
|
76
|
+
fs.mkdirSync(config.targetDir, { recursive: true });
|
|
77
|
+
}
|
|
78
|
+
let successCount = 0;
|
|
79
|
+
for (const fileName of config.fileNames) {
|
|
80
|
+
const src = path.join(config.sourceDir, fileName);
|
|
81
|
+
const dst = path.join(config.targetDir, fileName);
|
|
82
|
+
if (fs.existsSync(src)) {
|
|
83
|
+
fs.copyFileSync(src, dst);
|
|
84
|
+
successCount++;
|
|
85
|
+
} else {
|
|
86
|
+
logger.warn(`源文件不存在: ${src}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
logger.info(`配置文件复制完成,成功: ${successCount}/${config.fileNames.length}`);
|
|
90
|
+
return true;
|
|
91
|
+
} catch (e) {
|
|
92
|
+
logger.error("复制文件失败:", e);
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}, "copyConfigFiles");
|
|
96
|
+
const startServerProcess = /* @__PURE__ */ __name(async (session) => {
|
|
97
|
+
if (serverProcess) {
|
|
98
|
+
await session.send("⚠️ 服务器进程已经在运行中。");
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (!copyConfigFiles()) {
|
|
102
|
+
await session.send("❌ 配置文件复制失败,启动中止。");
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
logger.info("正在启动服务器进程...");
|
|
106
|
+
const batDir = path.dirname(config.batPath);
|
|
107
|
+
serverProcess = (0, import_child_process.spawn)("cmd.exe", ["/c", config.batPath], {
|
|
108
|
+
cwd: batDir,
|
|
109
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
110
|
+
});
|
|
111
|
+
monitorServerOutput(session);
|
|
112
|
+
}, "startServerProcess");
|
|
113
|
+
const monitorServerOutput = /* @__PURE__ */ __name((session) => {
|
|
114
|
+
if (isMonitoring) return;
|
|
115
|
+
isMonitoring = true;
|
|
116
|
+
logger.info("开始监听服务器输出...");
|
|
117
|
+
if (!serverProcess) {
|
|
118
|
+
isMonitoring = false;
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
serverProcess.stdout?.on("data", (data) => {
|
|
122
|
+
const output = data.toString();
|
|
123
|
+
logger.debug(`[Server Output]: ${output}`);
|
|
124
|
+
if (output.includes("Server Terminated.")) {
|
|
125
|
+
session.send("❌ 服务器启动失败,请联系服主,正在关闭服务器。");
|
|
126
|
+
logger.warn("检测到服务器启动失败,正在执行关闭流程...");
|
|
127
|
+
serverProcess?.stdin?.write("quit\r\n");
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (output.includes("LuaNet: Initialization [DONE]")) {
|
|
131
|
+
session.send("✅ 服务器启动成功!");
|
|
132
|
+
isMonitoring = false;
|
|
133
|
+
}
|
|
134
|
+
if (output.includes("PAUSE")) {
|
|
135
|
+
session.send("🛑 服务器已关闭。");
|
|
136
|
+
serverProcess?.stdin?.write("\r\n");
|
|
137
|
+
serverProcess = null;
|
|
138
|
+
isMonitoring = false;
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
serverProcess.stderr?.on("data", (data) => {
|
|
142
|
+
logger.error(`[Server Error]: ${data.toString()}`);
|
|
143
|
+
});
|
|
144
|
+
serverProcess.on("close", (code) => {
|
|
145
|
+
logger.info(`服务器进程已退出,代码: ${code}`);
|
|
146
|
+
serverProcess = null;
|
|
147
|
+
isMonitoring = false;
|
|
148
|
+
});
|
|
149
|
+
}, "monitorServerOutput");
|
|
150
|
+
ctx.command("pz-start", "启动僵尸毁灭工程服务器").action(async ({ session }) => {
|
|
151
|
+
if (!checkAdminPermission(session.guildId, session.userId)) return "❌ 权限不足,请联系服务器管理员。";
|
|
152
|
+
if (serverProcess) {
|
|
153
|
+
return "🟢 服务器已在运行中,无需重复启动。";
|
|
154
|
+
}
|
|
155
|
+
await session.send("🚀 正在启动服务器...");
|
|
156
|
+
await startServerProcess(session);
|
|
157
|
+
});
|
|
158
|
+
ctx.command("pz-restart", "重启僵尸毁灭工程服务器").action(async ({ session }) => {
|
|
159
|
+
const isAdmin = checkAdminPermission(session.guildId, session.userId);
|
|
160
|
+
const now = Date.now();
|
|
161
|
+
const cooldownMs = config.publicRestartCooldown * 60 * 1e3;
|
|
162
|
+
if (!isAdmin) {
|
|
163
|
+
if (now - publicRestartState.lastUseTime < cooldownMs) {
|
|
164
|
+
const remainingTime = Math.ceil((cooldownMs - (now - publicRestartState.lastUseTime)) / 1e3 / 60);
|
|
165
|
+
return `⏳ 服务器正在冷却中,请等待 ${remainingTime} 分钟。`;
|
|
166
|
+
}
|
|
167
|
+
if (publicRestartState.remainingUses <= 0) {
|
|
168
|
+
return `🚫 今日重启次数已用尽,请联系服务器管理员。`;
|
|
169
|
+
}
|
|
170
|
+
if (!serverProcess) {
|
|
171
|
+
return "⚫ 服务器未在运行,无法重启。";
|
|
172
|
+
}
|
|
173
|
+
publicRestartState.remainingUses--;
|
|
174
|
+
publicRestartState.lastUseTime = now;
|
|
175
|
+
await session.send(`✅ 重启请求已接受。今日剩余重启次数:${publicRestartState.remainingUses}。服务器即将在 1 分钟后重启,请在线的玩家到合适的地方下线。`);
|
|
176
|
+
logger.info(`普通用户 ${session.userId} 请求重启,剩余次数: ${publicRestartState.remainingUses}`);
|
|
177
|
+
} else {
|
|
178
|
+
if (!serverProcess) {
|
|
179
|
+
return "⚫ 服务器当前未运行,无法重启。";
|
|
180
|
+
}
|
|
181
|
+
await session.send("@全体成员 服务器即将在 1 分钟后重启,请在线的玩家到合适的地方下线。");
|
|
182
|
+
logger.info("管理员执行重启,等待 60 秒...");
|
|
183
|
+
}
|
|
184
|
+
await new Promise((resolve) => setTimeout(resolve, 6e4));
|
|
185
|
+
logger.info("发送 quit 指令...");
|
|
186
|
+
serverProcess?.stdin?.write("quit\r\n");
|
|
187
|
+
await new Promise((resolve) => setTimeout(resolve, 3e3));
|
|
188
|
+
while (serverProcess) {
|
|
189
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
190
|
+
logger.info("等待旧进程退出...");
|
|
191
|
+
}
|
|
192
|
+
await session.send("🔄 旧服务器已关闭,正在启动新服务器...");
|
|
193
|
+
await startServerProcess(session);
|
|
194
|
+
});
|
|
195
|
+
ctx.command("pz-stop", "关闭僵尸毁灭工程服务器").action(async ({ session }) => {
|
|
196
|
+
if (!checkAdminPermission(session.guildId, session.userId)) return "❌ 权限不足,请联系服务器管理员。";
|
|
197
|
+
if (!serverProcess) {
|
|
198
|
+
return "⚫ 服务器当前未运行,无需关闭。";
|
|
199
|
+
}
|
|
200
|
+
await session.send("@全体成员 服务器即将在 1 分钟后关闭,请在线的玩家到合适的地方下线。");
|
|
201
|
+
logger.info("已发送关闭预告,等待 60 秒...");
|
|
202
|
+
await new Promise((resolve) => setTimeout(resolve, 6e4));
|
|
203
|
+
logger.info("发送 quit 指令...");
|
|
204
|
+
serverProcess?.stdin?.write("quit\r\n");
|
|
205
|
+
return "👉 正在执行关闭流程,请稍候查看“服务器关闭”的消息。";
|
|
206
|
+
});
|
|
207
|
+
ctx.command("pz-status", "查询服务器状态").action(({ session }) => {
|
|
208
|
+
return serverProcess ? "🟢 服务器正在运行" : "⚫ 服务器未在运行";
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
__name(apply, "apply");
|
|
212
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
213
|
+
0 && (module.exports = {
|
|
214
|
+
Config,
|
|
215
|
+
apply,
|
|
216
|
+
name
|
|
217
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "koishi-plugin-restart-projectzomboid",
|
|
3
|
+
"description": "重启僵毁服务器",
|
|
4
|
+
"version": "1.0.1",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"typings": "lib/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"lib",
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"chatbot",
|
|
14
|
+
"koishi",
|
|
15
|
+
"plugin"
|
|
16
|
+
],
|
|
17
|
+
"peerDependencies": {
|
|
18
|
+
"koishi": "^4.18.7"
|
|
19
|
+
}
|
|
20
|
+
}
|