koishi-plugin-cs2-server-query 1.0.0

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 ADDED
@@ -0,0 +1,10 @@
1
+ import { Context, Schema } from 'koishi';
2
+ export declare const name = "cs2-server-query";
3
+ export interface Config {
4
+ mapTranslationFile: string;
5
+ backgroundImage: string;
6
+ dataFile: string;
7
+ commandMappingFile: string;
8
+ }
9
+ export declare const Config: Schema<Config>;
10
+ export declare function apply(ctx: Context, config: Config): void;
package/lib/index.js ADDED
@@ -0,0 +1,347 @@
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 import_steam_server_query = require("steam-server-query");
40
+ var import_fs = __toESM(require("fs"));
41
+ var import_path = __toESM(require("path"));
42
+ var import_puppeteer = __toESM(require("puppeteer"));
43
+ var name = "cs2-server-query";
44
+ var Config = import_koishi.Schema.object({
45
+ mapTranslationFile: import_koishi.Schema.string().default("map-translation.json"),
46
+ backgroundImage: import_koishi.Schema.string().description("背景图片URL (建议尺寸 800x600)").default("image/background.jpg"),
47
+ dataFile: import_koishi.Schema.string().default("server-data.json"),
48
+ commandMappingFile: import_koishi.Schema.string().default("command-mapping.json")
49
+ });
50
+ function apply(ctx, config) {
51
+ const dataFilePath = import_path.default.resolve(__dirname, config.dataFile);
52
+ const commandMappingFilePath = import_path.default.resolve(__dirname, config.commandMappingFile);
53
+ let groups = [];
54
+ let mapTranslations = {};
55
+ let commandMappings = {};
56
+ const mapTranslationFilePath = import_path.default.resolve(__dirname, config.mapTranslationFile);
57
+ if (import_fs.default.existsSync(mapTranslationFilePath)) {
58
+ mapTranslations = JSON.parse(import_fs.default.readFileSync(mapTranslationFilePath, "utf-8"));
59
+ }
60
+ if (import_fs.default.existsSync(dataFilePath)) {
61
+ groups = JSON.parse(import_fs.default.readFileSync(dataFilePath, "utf-8"));
62
+ }
63
+ if (import_fs.default.existsSync(commandMappingFilePath)) {
64
+ commandMappings = JSON.parse(import_fs.default.readFileSync(commandMappingFilePath, "utf-8"));
65
+ }
66
+ function saveMapTranslations() {
67
+ import_fs.default.writeFileSync(mapTranslationFilePath, JSON.stringify(mapTranslations, null, 2));
68
+ }
69
+ __name(saveMapTranslations, "saveMapTranslations");
70
+ function saveData() {
71
+ import_fs.default.writeFileSync(dataFilePath, JSON.stringify(groups, null, 2));
72
+ }
73
+ __name(saveData, "saveData");
74
+ function saveCommandMappings() {
75
+ import_fs.default.writeFileSync(commandMappingFilePath, JSON.stringify(commandMappings, null, 2));
76
+ }
77
+ __name(saveCommandMappings, "saveCommandMappings");
78
+ const difficultyColors = {
79
+ "简单": "#00ff00",
80
+ "普通": "#7fff00",
81
+ "困难": "#ff7f00",
82
+ "高难": "#ff4f00",
83
+ "史诗": "#ff2f00",
84
+ "梦魇": "#ff0f00",
85
+ "绝境": "#ff0000"
86
+ };
87
+ async function generateServerImage(group, mapTranslations2) {
88
+ const browser = await import_puppeteer.default.launch({ args: ["--no-sandbox", "--disable-setuid-sandbox"] });
89
+ const page = await browser.newPage();
90
+ const totalPlayers = group.servers.reduce((sum, server) => sum + (server.status === "在线" ? server.players : 0), 0);
91
+ const totalMaxPlayers = group.servers.reduce((sum, server) => sum + (server.status === "在线" ? server.maxPlayers : 0), 0);
92
+ const baseHeight = 150;
93
+ const rowHeight = 50;
94
+ const totalHeight = baseHeight + rowHeight * group.servers.length;
95
+ const html = `
96
+ <!DOCTYPE html>
97
+ <html>
98
+ <head>
99
+ <style>
100
+ body {
101
+ margin: 0;
102
+ padding: 20px;
103
+ background: url('${config.backgroundImage}') no-repeat center center fixed;
104
+ background-size: cover;
105
+ height: 100vh;
106
+ position: relative;
107
+ }
108
+ .overlay {
109
+ background: rgba(0, 0, 0, 0.7);
110
+ border-radius: 10px;
111
+ padding: 20px;
112
+ color: white;
113
+ font-family: Arial, sans-serif;
114
+ }
115
+ h2 {
116
+ color: #fff;
117
+ margin: 0 0 15px 0;
118
+ border-bottom: 2px solid #fff;
119
+ padding-bottom: 10px;
120
+ }
121
+ table {
122
+ width: 100%;
123
+ border-collapse: collapse;
124
+ background: rgba(255, 255, 255, 0.1);
125
+ }
126
+ th, td {
127
+ padding: 12px;
128
+ text-align: left;
129
+ border-bottom: 1px solid rgba(255, 255, 255, 0.2);
130
+ }
131
+ th {
132
+ background: rgba(0, 0, 0, 0.5);
133
+ }
134
+ tr:hover {
135
+ background: rgba(255, 255, 255, 0.05);
136
+ }
137
+ .map-name {
138
+ color: #00ff9d;
139
+ }
140
+ .status-online {
141
+ color: #00ff00;
142
+ }
143
+ .status-offline {
144
+ color: #ff0000;
145
+ }
146
+ .difficulty {
147
+ display: inline-block;
148
+ padding: 2px 6px;
149
+ border-radius: 4px;
150
+ color: white;
151
+ font-size: 12px;
152
+ margin-left: 5px;
153
+ }
154
+ </style>
155
+ </head>
156
+ <body>
157
+ <div class="overlay">
158
+ <h2>${group.name} - 服务器列表</h2>
159
+ <p>总玩家数: ${totalPlayers}/${totalMaxPlayers}</p>
160
+ <table>
161
+ <tr>
162
+ <th>编号</th>
163
+ <th>服务器名称</th>
164
+ <th>地图</th>
165
+ <th>玩家</th>
166
+ <th>延迟</th>
167
+ <th>状态</th>
168
+ <th>IP 地址</th>
169
+ </tr>
170
+ ${group.servers.map((server, index) => {
171
+ const translation = mapTranslations2[server.map];
172
+ const difficulty = translation?.difficulty;
173
+ const difficultyColor = difficulty ? difficultyColors[difficulty] : "#000000";
174
+ return `
175
+ <tr>
176
+ <td>${index + 1}</td>
177
+ <td>${server.name}</td>
178
+ <td>
179
+ <span class="map-name">${server.map}</span>
180
+ ${translation ? `<br>(${translation.translation})` : ""}
181
+ ${difficulty ? `<span class="difficulty" style="background-color: ${difficultyColor};">${difficulty}</span>` : ""}
182
+ </td>
183
+ <td>${server.status === "在线" ? `${server.players}/${server.maxPlayers}` : "-"}</td>
184
+ <td>${server.status === "在线" ? `${server.latency}ms` : "-"}</td>
185
+ <td class="status-${server.status === "在线" ? "online" : "offline"}">${server.status}</td>
186
+ <td><code>${server.ip}</code></td>
187
+ </tr>
188
+ `;
189
+ }).join("")}
190
+ </table>
191
+ </div>
192
+ </body>
193
+ </html>
194
+ `;
195
+ await page.setContent(html);
196
+ await page.setViewport({ width: 1280, height: totalHeight });
197
+ const screenshot = await page.screenshot({
198
+ type: "png",
199
+ fullPage: true,
200
+ encoding: "binary"
201
+ });
202
+ await browser.close();
203
+ return screenshot;
204
+ }
205
+ __name(generateServerImage, "generateServerImage");
206
+ ctx.middleware(async (session, next) => {
207
+ const content = session.content.trim();
208
+ const commandMatch = content.match(/^(.+?)(\s+(\d+))?$/);
209
+ if (!commandMatch) return next();
210
+ const command = commandMatch[1];
211
+ const serverIndex = commandMatch[3] ? parseInt(commandMatch[3]) : null;
212
+ const groupName = commandMappings[command];
213
+ if (!groupName) return next();
214
+ const group = groups.find((g) => g.name === groupName);
215
+ if (!group) return "未找到该组";
216
+ for (const server of group.servers) {
217
+ try {
218
+ const info = await (0, import_steam_server_query.queryGameServerInfo)(server.ip);
219
+ server.name = info.name || server.name;
220
+ server.map = info.map;
221
+ server.players = info.players;
222
+ server.maxPlayers = info.maxPlayers;
223
+ server.status = "在线";
224
+ } catch (err) {
225
+ server.status = "离线";
226
+ }
227
+ }
228
+ saveData();
229
+ if (serverIndex !== null) {
230
+ if (isNaN(serverIndex) || serverIndex < 1 || serverIndex > group.servers.length) {
231
+ return "无效的服务器编号";
232
+ }
233
+ const server = group.servers[serverIndex - 1];
234
+ return `connect ${server.ip}`;
235
+ } else {
236
+ try {
237
+ const imageBuffer = await generateServerImage(group, mapTranslations);
238
+ return import_koishi.h.image(imageBuffer, "image/png");
239
+ } catch (err) {
240
+ ctx.logger("cs2-server-query").error(err);
241
+ return "生成服务器列表图片失败,请检查后台日志";
242
+ }
243
+ }
244
+ });
245
+ ctx.command("cs2组创建 <name>", "添加一个新组").action((_, name2) => {
246
+ if (!name2) return "请提供组名。";
247
+ if (groups.some((group) => group.name === name2)) return "组已存在。";
248
+ groups.push({ name: name2, servers: [] });
249
+ saveData();
250
+ return `组 "${name2}" 添加成功。`;
251
+ });
252
+ ctx.command("cs2组列表", "列出所有组").action(() => {
253
+ if (groups.length === 0) return "未找到任何组。";
254
+ return `组列表:${groups.map((group) => group.name).join(", ")}`;
255
+ });
256
+ ctx.command("cs2添加 <groupName> <ip>", "向组中添加一个服务器").action(async (_, groupName, ip) => {
257
+ const group = groups.find((group2) => group2.name === groupName);
258
+ if (!group) return "未找到该组。";
259
+ if (group.servers.some((server) => server.ip === ip)) return "该服务器已存在于该组中。";
260
+ try {
261
+ const info = await (0, import_steam_server_query.queryGameServerInfo)(ip);
262
+ const serverName = info.name || `服务器 ${ip}`;
263
+ group.servers.push({
264
+ name: serverName,
265
+ ip,
266
+ map: info.map,
267
+ players: info.players,
268
+ maxPlayers: info.maxPlayers,
269
+ latency: 0,
270
+ status: "在线"
271
+ // 默认状态为在线
272
+ });
273
+ saveData();
274
+ return `服务器 "${serverName}" (${ip}) 已成功添加到组 "${groupName}" 中。`;
275
+ } catch (err) {
276
+ const serverName = `服务器 ${ip}`;
277
+ group.servers.push({
278
+ name: serverName,
279
+ ip,
280
+ map: "未知",
281
+ players: 0,
282
+ maxPlayers: 0,
283
+ latency: 0,
284
+ status: "离线"
285
+ });
286
+ saveData();
287
+ return `服务器 "${serverName}" (${ip}) 已添加到组 "${groupName}" 中,但当前状态为离线。`;
288
+ }
289
+ });
290
+ ctx.command("cs2组删除 <groupName>", "删除一个组").action((_, groupName) => {
291
+ const index = groups.findIndex((group) => group.name === groupName);
292
+ if (index === -1) return "未找到该组。";
293
+ groups.splice(index, 1);
294
+ saveData();
295
+ return `组 "${groupName}" 删除成功。`;
296
+ });
297
+ ctx.command("cs2删除 <groupName> <serverIndex>", "从组中删除一个服务器").action((_, groupName, serverIndex) => {
298
+ const group = groups.find((group2) => group2.name === groupName);
299
+ if (!group) return "未找到该组。";
300
+ const index = parseInt(serverIndex) - 1;
301
+ if (isNaN(index) || index < 0 || index >= group.servers.length) return "无效的服务器编号。";
302
+ group.servers.splice(index, 1);
303
+ saveData();
304
+ return `服务器 ${serverIndex} 已从组 "${groupName}" 中删除。`;
305
+ });
306
+ ctx.command("cs2地图翻译 <mapName> <translation> [difficulty]", "添加地图翻译").action((_, mapName, translation, difficulty) => {
307
+ mapTranslations[mapName] = { translation, difficulty };
308
+ saveMapTranslations();
309
+ return `地图 "${mapName}" 的翻译已成功添加。`;
310
+ });
311
+ ctx.command("cs2地图翻译删除 <mapName>", "删除地图翻译").action((_, mapName) => {
312
+ if (!mapTranslations[mapName]) return "未找到该地图的翻译。";
313
+ delete mapTranslations[mapName];
314
+ saveMapTranslations();
315
+ return `地图 "${mapName}" 的翻译已删除。`;
316
+ });
317
+ ctx.command("cs2更新地图翻译 <mapName> <translation> [difficulty]", "更新地图翻译").action((_, mapName, translation, difficulty) => {
318
+ if (!mapTranslations[mapName]) return "未找到该地图的翻译。";
319
+ mapTranslations[mapName] = { translation, difficulty };
320
+ saveMapTranslations();
321
+ return `地图 "${mapName}" 的翻译已更新。`;
322
+ });
323
+ ctx.command("cs2指令添加 <command> <groupName>", "添加一个指令映射").action((_, command, groupName) => {
324
+ if (commandMappings[command]) return `指令 "${command}" 已存在。`;
325
+ if (!groups.some((group) => group.name === groupName)) return `组 "${groupName}" 不存在。`;
326
+ commandMappings[command] = groupName;
327
+ saveCommandMappings();
328
+ return `指令 "${command}" 已成功映射到组 "${groupName}"。`;
329
+ });
330
+ ctx.command("cs2指令删除 <command>", "删除一个指令映射").action((_, command) => {
331
+ if (!commandMappings[command]) return `指令 "${command}" 不存在。`;
332
+ delete commandMappings[command];
333
+ saveCommandMappings();
334
+ return `指令 "${command}" 已删除。`;
335
+ });
336
+ ctx.command("cs2指令列表", "列出所有指令映射").action(() => {
337
+ if (Object.keys(commandMappings).length === 0) return "未找到任何指令映射。";
338
+ return `指令映射列表:${Object.entries(commandMappings).map(([command, groupName]) => `${command} -> ${groupName}`).join(", ")}`;
339
+ });
340
+ }
341
+ __name(apply, "apply");
342
+ // Annotate the CommonJS export names for ESM import in node:
343
+ 0 && (module.exports = {
344
+ Config,
345
+ apply,
346
+ name
347
+ });
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "koishi-plugin-cs2-server-query",
3
+ "description": "自用,不推荐下载",
4
+ "version": "1.0.0",
5
+ "main": "lib/index.js",
6
+ "typings": "lib/index.d.ts",
7
+ "files": [
8
+ "lib",
9
+ "puppeteer",
10
+ "steam-server-query",
11
+ "dist"
12
+ ],
13
+
14
+ "license": "MIT",
15
+ "keywords": [
16
+ "chatbot",
17
+ "koishi",
18
+ "plugin"
19
+ ],
20
+ "peerDependencies": {
21
+ "koishi": "^4.18.7"
22
+ },
23
+ "dependencies": {
24
+ "steam-server-query": "^1.1.3",
25
+ "puppeteer": "^24.2.0"
26
+ },
27
+ "devDependencies": {
28
+ "esbuild": "^0.19.0",
29
+ "typescript": "^5.0.0"
30
+ }
31
+ }
package/readme.md ADDED
@@ -0,0 +1,5 @@
1
+ # koishi-plugin-cs2-server-query
2
+
3
+ [![npm](https://img.shields.io/npm/v/koishi-plugin-cs2-server-query?style=flat-square)](https://www.npmjs.com/package/koishi-plugin-cs2-server-query)
4
+
5
+ 自用CS2社区服查询插件