koishi-plugin-csss 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 +25 -0
- package/lib/index.js +948 -0
- package/package.json +34 -0
- package/readme.md +5 -0
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Context, Schema } from 'koishi';
|
|
2
|
+
export declare const name = "cs-server-status";
|
|
3
|
+
export interface Config {
|
|
4
|
+
timeout: number;
|
|
5
|
+
cacheTime: number;
|
|
6
|
+
maxPlayers: number;
|
|
7
|
+
retryCount: number;
|
|
8
|
+
showVAC: boolean;
|
|
9
|
+
showPassword: boolean;
|
|
10
|
+
generateImage: boolean;
|
|
11
|
+
imageWidth: number;
|
|
12
|
+
imageHeight: number;
|
|
13
|
+
fontSize: number;
|
|
14
|
+
fontFamily: string;
|
|
15
|
+
serverList: string[];
|
|
16
|
+
batchTimeout: number;
|
|
17
|
+
scheduleEnabled: boolean;
|
|
18
|
+
scheduleInterval: number;
|
|
19
|
+
scheduleStartTime: string;
|
|
20
|
+
scheduleEndTime: string;
|
|
21
|
+
scheduleGroups: string[];
|
|
22
|
+
scheduleUseImage: boolean;
|
|
23
|
+
}
|
|
24
|
+
export declare const Config: Schema<Config>;
|
|
25
|
+
export declare function apply(ctx: Context, config: Config): void;
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,948 @@
|
|
|
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_canvas = require("canvas");
|
|
40
|
+
var name = "cs-server-status";
|
|
41
|
+
var Config = import_koishi.Schema.object({
|
|
42
|
+
timeout: import_koishi.Schema.number().min(1e3).max(3e4).default(5e3).description("查询超时时间(毫秒)"),
|
|
43
|
+
cacheTime: import_koishi.Schema.number().min(0).max(3e5).default(3e4).description("缓存时间(毫秒,0为禁用缓存)"),
|
|
44
|
+
maxPlayers: import_koishi.Schema.number().min(0).max(100).default(20).description("最大显示玩家数"),
|
|
45
|
+
retryCount: import_koishi.Schema.number().min(0).max(5).default(2).description("查询失败重试次数"),
|
|
46
|
+
showVAC: import_koishi.Schema.boolean().default(true).description("是否显示VAC状态"),
|
|
47
|
+
showPassword: import_koishi.Schema.boolean().default(true).description("是否显示密码保护信息"),
|
|
48
|
+
generateImage: import_koishi.Schema.boolean().default(true).description("是否生成图片横幅(影响cs和csss命令)"),
|
|
49
|
+
imageWidth: import_koishi.Schema.number().min(600).max(2e3).default(1200).description("图片宽度(像素)"),
|
|
50
|
+
imageHeight: import_koishi.Schema.number().min(200).max(2500).default(500).description("图片最小高度(像素),实际高度会根据内容自适应"),
|
|
51
|
+
fontSize: import_koishi.Schema.number().min(12).max(48).default(24).description("字体大小"),
|
|
52
|
+
fontFamily: import_koishi.Schema.string().default("Microsoft YaHei, sans-serif").description("字体家族"),
|
|
53
|
+
serverList: import_koishi.Schema.array(import_koishi.Schema.string()).role("table").description("批量查询服务器列表(格式: 地址:端口,每行一个)").default([
|
|
54
|
+
"edgebug.cn:27015",
|
|
55
|
+
"edgebug.cn:27016",
|
|
56
|
+
"edgebug.cn:27017",
|
|
57
|
+
"edgebug.cn:27018"
|
|
58
|
+
]),
|
|
59
|
+
batchTimeout: import_koishi.Schema.number().min(1e3).max(6e4).default(15e3).description("批量查询总超时时间(毫秒)"),
|
|
60
|
+
// 新增定时任务配置
|
|
61
|
+
scheduleEnabled: import_koishi.Schema.boolean().default(false).description("是否启用定时自动查询功能"),
|
|
62
|
+
scheduleInterval: import_koishi.Schema.number().min(1).max(1440).default(5).description("定时查询间隔时间(分钟)"),
|
|
63
|
+
scheduleStartTime: import_koishi.Schema.string().pattern(/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/).default("08:00").description("定时任务开始时间(24小时制,格式: HH:MM)"),
|
|
64
|
+
scheduleEndTime: import_koishi.Schema.string().pattern(/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/).default("23:00").description("定时任务结束时间(24小时制,格式: HH:MM)"),
|
|
65
|
+
scheduleGroups: import_koishi.Schema.array(import_koishi.Schema.string()).role("table").description("定时发送的群组ID列表(每行一个群组ID)").default([]),
|
|
66
|
+
scheduleUseImage: import_koishi.Schema.boolean().default(true).description("定时任务是否使用图片格式输出")
|
|
67
|
+
});
|
|
68
|
+
var utils = {
|
|
69
|
+
formatPing(ping) {
|
|
70
|
+
if (!ping || ping < 0) return "未知";
|
|
71
|
+
if (ping < 50) return `🟢 ${ping}ms`;
|
|
72
|
+
if (ping < 100) return `🟡 ${ping}ms`;
|
|
73
|
+
if (ping < 200) return `🟠 ${ping}ms`;
|
|
74
|
+
return `🔴 ${ping}ms`;
|
|
75
|
+
},
|
|
76
|
+
cleanName(name2) {
|
|
77
|
+
return name2 ? name2.replace(/\^[0-9]/g, "").replace(/[\u0000-\u001F]/g, "").trim() : "未知";
|
|
78
|
+
},
|
|
79
|
+
truncateText(text, maxLength) {
|
|
80
|
+
return text.length > maxLength ? text.substring(0, maxLength) + "..." : text;
|
|
81
|
+
},
|
|
82
|
+
getPingColor(ping) {
|
|
83
|
+
if (ping < 50) return "#4CAF50";
|
|
84
|
+
if (ping < 100) return "#FFC107";
|
|
85
|
+
if (ping < 200) return "#FF9800";
|
|
86
|
+
return "#c03f36";
|
|
87
|
+
},
|
|
88
|
+
getPlayerColor(count) {
|
|
89
|
+
return count > 0 ? "#4CAF50" : "#c03f36";
|
|
90
|
+
},
|
|
91
|
+
// 新增:格式化时间
|
|
92
|
+
formatTime(ms) {
|
|
93
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
94
|
+
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}秒`;
|
|
95
|
+
return `${(ms / 1e3).toFixed(0)}秒`;
|
|
96
|
+
},
|
|
97
|
+
// 新增:解析时间字符串为分钟数
|
|
98
|
+
parseTimeToMinutes(timeStr) {
|
|
99
|
+
const [hours, minutes] = timeStr.split(":").map(Number);
|
|
100
|
+
return hours * 60 + minutes;
|
|
101
|
+
},
|
|
102
|
+
// 新增:检查当前时间是否在定时任务时间范围内
|
|
103
|
+
isWithinScheduleTime(startTime, endTime) {
|
|
104
|
+
const now = /* @__PURE__ */ new Date();
|
|
105
|
+
const currentMinutes = now.getHours() * 60 + now.getMinutes();
|
|
106
|
+
const startMinutes = this.parseTimeToMinutes(startTime);
|
|
107
|
+
const endMinutes = this.parseTimeToMinutes(endTime);
|
|
108
|
+
return currentMinutes >= startMinutes && currentMinutes <= endMinutes;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
function apply(ctx, config) {
|
|
112
|
+
const cache = /* @__PURE__ */ new Map();
|
|
113
|
+
let scheduleTimer = null;
|
|
114
|
+
async function executeScheduleTask() {
|
|
115
|
+
if (!config.scheduleEnabled || config.scheduleGroups.length === 0 || config.serverList.length === 0) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (!utils.isWithinScheduleTime(config.scheduleStartTime, config.scheduleEndTime)) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
const startTime = Date.now();
|
|
123
|
+
const results = await Promise.allSettled(
|
|
124
|
+
config.serverList.map(async (server, index) => {
|
|
125
|
+
try {
|
|
126
|
+
const { host, port } = parseAddress(server);
|
|
127
|
+
const data = await queryServer(host, port);
|
|
128
|
+
return {
|
|
129
|
+
index: index + 1,
|
|
130
|
+
server,
|
|
131
|
+
success: true,
|
|
132
|
+
data
|
|
133
|
+
};
|
|
134
|
+
} catch (error) {
|
|
135
|
+
return {
|
|
136
|
+
index: index + 1,
|
|
137
|
+
server,
|
|
138
|
+
success: false,
|
|
139
|
+
error: error.message
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
})
|
|
143
|
+
);
|
|
144
|
+
const endTime = Date.now();
|
|
145
|
+
const queryTime = endTime - startTime;
|
|
146
|
+
const now = /* @__PURE__ */ new Date();
|
|
147
|
+
const timeStr = now.toLocaleString("zh-CN");
|
|
148
|
+
let outputContent;
|
|
149
|
+
if (config.scheduleUseImage) {
|
|
150
|
+
try {
|
|
151
|
+
const imageBuffer = await generateBatchImage(results, config.serverList, queryTime);
|
|
152
|
+
outputContent = import_koishi.h.image(imageBuffer, "image/png");
|
|
153
|
+
} catch (imageError) {
|
|
154
|
+
console.error("定时任务生成图片失败:", imageError);
|
|
155
|
+
outputContent = `🕒 ${timeStr} 服务器状态更新
|
|
156
|
+
生成图片失败,使用文本格式:
|
|
157
|
+
|
|
158
|
+
`;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (typeof outputContent === "string" || !config.scheduleUseImage) {
|
|
162
|
+
const successful = results.filter((r) => r.status === "fulfilled" && r.value.success).length;
|
|
163
|
+
const failed = results.length - successful;
|
|
164
|
+
let textMessage = `🕒 ${timeStr} 服务器状态更新 (耗时: ${utils.formatTime(queryTime)})
|
|
165
|
+
`;
|
|
166
|
+
textMessage += `✅ 成功: ${successful} 个 | ❌ 失败: ${failed} 个
|
|
167
|
+
|
|
168
|
+
`;
|
|
169
|
+
textMessage += "序号 服务器名称 在线人数\n";
|
|
170
|
+
textMessage += "──────────────────────────────\n";
|
|
171
|
+
results.forEach((result, index) => {
|
|
172
|
+
const serverInfo = config.serverList[index];
|
|
173
|
+
if (result.status === "fulfilled") {
|
|
174
|
+
const { success, data, error } = result.value;
|
|
175
|
+
if (success && data) {
|
|
176
|
+
const { result: serverData } = data;
|
|
177
|
+
const serverName = serverData.name ? utils.cleanName(serverData.name) : "未知";
|
|
178
|
+
const playerCount = serverData.players?.length || 0;
|
|
179
|
+
const maxPlayers = serverData.maxplayers || 0;
|
|
180
|
+
const truncatedName = utils.truncateText(serverName, 20);
|
|
181
|
+
const paddedName = truncatedName.padEnd(20, " ");
|
|
182
|
+
textMessage += `${(index + 1).toString().padStart(2, " ")} ${paddedName} ${playerCount}/${maxPlayers}
|
|
183
|
+
`;
|
|
184
|
+
} else {
|
|
185
|
+
textMessage += `${(index + 1).toString().padStart(2, " ")} ${serverInfo} ❌ 查询失败: ${error}
|
|
186
|
+
`;
|
|
187
|
+
}
|
|
188
|
+
} else {
|
|
189
|
+
textMessage += `${(index + 1).toString().padStart(2, " ")} ${serverInfo} ❌ 查询失败
|
|
190
|
+
`;
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
outputContent = typeof outputContent === "string" ? outputContent + textMessage : textMessage;
|
|
194
|
+
}
|
|
195
|
+
for (const groupId of config.scheduleGroups) {
|
|
196
|
+
try {
|
|
197
|
+
await ctx.broadcast([`onebot:${groupId}`], outputContent);
|
|
198
|
+
} catch (error) {
|
|
199
|
+
console.error(`定时任务发送消息到群组 ${groupId} 失败:`, error);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
} catch (error) {
|
|
203
|
+
console.error("定时任务执行失败:", error);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
__name(executeScheduleTask, "executeScheduleTask");
|
|
207
|
+
function startScheduleTask() {
|
|
208
|
+
if (scheduleTimer) {
|
|
209
|
+
clearInterval(scheduleTimer);
|
|
210
|
+
}
|
|
211
|
+
if (config.scheduleEnabled && config.scheduleInterval > 0) {
|
|
212
|
+
const intervalMs = config.scheduleInterval * 60 * 1e3;
|
|
213
|
+
executeScheduleTask();
|
|
214
|
+
scheduleTimer = setInterval(executeScheduleTask, intervalMs);
|
|
215
|
+
console.log(`定时任务已启动,间隔: ${config.scheduleInterval}分钟,时间范围: ${config.scheduleStartTime}-${config.scheduleEndTime}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
__name(startScheduleTask, "startScheduleTask");
|
|
219
|
+
function stopScheduleTask() {
|
|
220
|
+
if (scheduleTimer) {
|
|
221
|
+
clearInterval(scheduleTimer);
|
|
222
|
+
scheduleTimer = null;
|
|
223
|
+
console.log("定时任务已停止");
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
__name(stopScheduleTask, "stopScheduleTask");
|
|
227
|
+
ctx.on("config", () => {
|
|
228
|
+
if (config.scheduleEnabled) {
|
|
229
|
+
startScheduleTask();
|
|
230
|
+
} else {
|
|
231
|
+
stopScheduleTask();
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
async function loadGamedig() {
|
|
235
|
+
try {
|
|
236
|
+
const gamedigModule = await import("gamedig");
|
|
237
|
+
return gamedigModule.default || gamedigModule.GameDig || gamedigModule;
|
|
238
|
+
} catch (error) {
|
|
239
|
+
throw new Error(`无法加载 gamedig 模块:${error.message}
|
|
240
|
+
请确保已安装 gamedig:npm install gamedig`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
__name(loadGamedig, "loadGamedig");
|
|
244
|
+
function parseAddress(input) {
|
|
245
|
+
let address = input.replace(/^(http|https|udp|tcp):\/\//, "");
|
|
246
|
+
if (address.includes("[")) {
|
|
247
|
+
const match = address.match(/^\[([^\]]+)\](?::(\d+))?$/);
|
|
248
|
+
if (match) {
|
|
249
|
+
const host = match[1];
|
|
250
|
+
const port = match[2] ? parseInt(match[2]) : 27015;
|
|
251
|
+
if (port >= 1 && port <= 65535) return { host, port };
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
const parts = address.split(":");
|
|
255
|
+
if (parts.length === 2) {
|
|
256
|
+
const host = parts[0];
|
|
257
|
+
const port = parseInt(parts[1]);
|
|
258
|
+
if (!isNaN(port) && port >= 1 && port <= 65535) return { host, port };
|
|
259
|
+
} else if (parts.length === 1) {
|
|
260
|
+
return { host: parts[0], port: 27015 };
|
|
261
|
+
}
|
|
262
|
+
throw new Error(`无效的地址格式: ${input}
|
|
263
|
+
正确格式: 地址:端口 或 地址`);
|
|
264
|
+
}
|
|
265
|
+
__name(parseAddress, "parseAddress");
|
|
266
|
+
async function queryServer(host, port) {
|
|
267
|
+
const cacheKey = `${host}:${port}`;
|
|
268
|
+
const now = Date.now();
|
|
269
|
+
if (config.cacheTime > 0) {
|
|
270
|
+
const cached = cache.get(cacheKey);
|
|
271
|
+
if (cached && now - cached.timestamp < config.cacheTime) {
|
|
272
|
+
return cached.data;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
const Gamedig = await loadGamedig();
|
|
276
|
+
let lastError;
|
|
277
|
+
for (let i = 0; i <= config.retryCount; i++) {
|
|
278
|
+
try {
|
|
279
|
+
const result = await Gamedig.query({
|
|
280
|
+
type: "csgo",
|
|
281
|
+
host,
|
|
282
|
+
port,
|
|
283
|
+
maxAttempts: 1,
|
|
284
|
+
socketTimeout: config.timeout,
|
|
285
|
+
attemptTimeout: config.timeout
|
|
286
|
+
});
|
|
287
|
+
const data = { game: "csgo", result };
|
|
288
|
+
if (config.cacheTime > 0) {
|
|
289
|
+
cache.set(cacheKey, { timestamp: now, data });
|
|
290
|
+
}
|
|
291
|
+
return data;
|
|
292
|
+
} catch (error) {
|
|
293
|
+
lastError = error;
|
|
294
|
+
if (i < config.retryCount) {
|
|
295
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
throw new Error(`无法连接到服务器: ${lastError?.message || "未知错误"}`);
|
|
300
|
+
}
|
|
301
|
+
__name(queryServer, "queryServer");
|
|
302
|
+
function formatServerInfo(data) {
|
|
303
|
+
const { result } = data;
|
|
304
|
+
const lines = [
|
|
305
|
+
` Counter-Strike 服务器
|
|
306
|
+
`,
|
|
307
|
+
result.name ? `🏷️ 名称: ${utils.cleanName(result.name)}` : null,
|
|
308
|
+
result.map ? `🗺️ 地图: ${result.map}` : null,
|
|
309
|
+
`👥 玩家: ${result.players?.length || 0}/${result.maxplayers || 0}${result.bots?.length ? ` (${result.bots.length} Bot)` : ""}`,
|
|
310
|
+
config.showPassword && result.password !== void 0 ? `🔒 密码: ${result.password ? "是 🔐" : "否 🔓"}` : null,
|
|
311
|
+
result.ping ? `📶 Ping: ${utils.formatPing(result.ping)}` : null,
|
|
312
|
+
result.connect ? `🔗 连接: ${result.connect}` : `📍 地址: ${result.host || "未知"}:${result.port || "未知"}`,
|
|
313
|
+
config.showVAC && result.raw?.secure !== void 0 ? `🛡️ VAC: ${result.raw.secure ? "启用 ✅" : "关闭 ❌"}` : null
|
|
314
|
+
];
|
|
315
|
+
return lines.filter(Boolean).join("\n");
|
|
316
|
+
}
|
|
317
|
+
__name(formatServerInfo, "formatServerInfo");
|
|
318
|
+
function formatPlayers(players) {
|
|
319
|
+
if (!players || players.length === 0) {
|
|
320
|
+
return "👤 服务器当前无在线玩家";
|
|
321
|
+
}
|
|
322
|
+
const sortedPlayers = [...players].sort((a, b) => {
|
|
323
|
+
const nameA = utils.cleanName(a.name).toLowerCase();
|
|
324
|
+
const nameB = utils.cleanName(b.name).toLowerCase();
|
|
325
|
+
return nameA.localeCompare(nameB);
|
|
326
|
+
});
|
|
327
|
+
const displayPlayers = sortedPlayers.slice(0, config.maxPlayers);
|
|
328
|
+
let message = `👤 在线玩家 (${players.length}人):
|
|
329
|
+
`;
|
|
330
|
+
displayPlayers.forEach((player, index) => {
|
|
331
|
+
message += `${index + 1}. ${utils.cleanName(player.name)}
|
|
332
|
+
`;
|
|
333
|
+
});
|
|
334
|
+
if (players.length > config.maxPlayers) {
|
|
335
|
+
message += `... 还有 ${players.length - config.maxPlayers} 位玩家未显示`;
|
|
336
|
+
}
|
|
337
|
+
return message.trim();
|
|
338
|
+
}
|
|
339
|
+
__name(formatPlayers, "formatPlayers");
|
|
340
|
+
const imageUtils = {
|
|
341
|
+
calculateServerNameFontSize(ctx2, name2, maxWidth, baseFontSize) {
|
|
342
|
+
let fontSize = baseFontSize * 1.5;
|
|
343
|
+
while (fontSize > baseFontSize * 0.8) {
|
|
344
|
+
ctx2.font = `bold ${fontSize}px ${config.fontFamily}`;
|
|
345
|
+
if (ctx2.measureText(name2).width <= maxWidth) break;
|
|
346
|
+
fontSize -= 1;
|
|
347
|
+
}
|
|
348
|
+
return fontSize;
|
|
349
|
+
},
|
|
350
|
+
calculatePlayerListParams(playerCount) {
|
|
351
|
+
const shouldEnlarge = playerCount > 0 && playerCount < 10;
|
|
352
|
+
return {
|
|
353
|
+
shouldEnlarge,
|
|
354
|
+
fontSizeMultiplier: shouldEnlarge ? 1.2 : 0.9,
|
|
355
|
+
rowHeight: shouldEnlarge ? 40 : 30,
|
|
356
|
+
nameMaxLength: shouldEnlarge ? 40 : 30,
|
|
357
|
+
needTwoColumns: playerCount > 10
|
|
358
|
+
};
|
|
359
|
+
},
|
|
360
|
+
drawBackground(ctx2, width, height) {
|
|
361
|
+
const gradient = ctx2.createLinearGradient(0, 0, width, height);
|
|
362
|
+
gradient.addColorStop(0, "#1a1a2e");
|
|
363
|
+
gradient.addColorStop(1, "#16213e");
|
|
364
|
+
ctx2.fillStyle = gradient;
|
|
365
|
+
ctx2.fillRect(0, 0, width, height);
|
|
366
|
+
},
|
|
367
|
+
drawTitle(ctx2, text, x, y, fontSize, fontFamily, color = "#ffffff") {
|
|
368
|
+
ctx2.fillStyle = color;
|
|
369
|
+
ctx2.font = `bold ${fontSize}px ${fontFamily}`;
|
|
370
|
+
ctx2.textAlign = "center";
|
|
371
|
+
ctx2.fillText(text, x, y);
|
|
372
|
+
},
|
|
373
|
+
drawDivider(ctx2, x1, y1, x2, y2, color, width = 2) {
|
|
374
|
+
ctx2.strokeStyle = color;
|
|
375
|
+
ctx2.lineWidth = width;
|
|
376
|
+
ctx2.beginPath();
|
|
377
|
+
ctx2.moveTo(x1, y1);
|
|
378
|
+
ctx2.lineTo(x2, y2);
|
|
379
|
+
ctx2.stroke();
|
|
380
|
+
},
|
|
381
|
+
drawText(ctx2, text, x, y, options = {}) {
|
|
382
|
+
const {
|
|
383
|
+
color = "#cccccc",
|
|
384
|
+
fontSize = config.fontSize,
|
|
385
|
+
fontFamily = config.fontFamily,
|
|
386
|
+
align = "left",
|
|
387
|
+
bold = false,
|
|
388
|
+
italic = false
|
|
389
|
+
} = options;
|
|
390
|
+
ctx2.fillStyle = color;
|
|
391
|
+
ctx2.textAlign = align;
|
|
392
|
+
const fontStyle = `${bold ? "bold" : ""} ${italic ? "italic" : ""} ${fontSize}px ${fontFamily}`;
|
|
393
|
+
ctx2.font = fontStyle.trim() || `${fontSize}px ${fontFamily}`;
|
|
394
|
+
ctx2.fillText(text, x, y);
|
|
395
|
+
},
|
|
396
|
+
drawPlayerList(ctx2, players, startY, width, maxHeight, params) {
|
|
397
|
+
let y = startY;
|
|
398
|
+
if (players.length === 0) {
|
|
399
|
+
this.drawText(ctx2, "服务器当前无在线玩家", 80, y, { italic: true, color: "#aaaaaa" });
|
|
400
|
+
return { y: y + 35, displayedCount: 0 };
|
|
401
|
+
}
|
|
402
|
+
const sortedPlayers = [...players].sort((a, b) => {
|
|
403
|
+
const nameA = utils.cleanName(a.name).toLowerCase();
|
|
404
|
+
const nameB = utils.cleanName(b.name).toLowerCase();
|
|
405
|
+
return nameA.localeCompare(nameB);
|
|
406
|
+
});
|
|
407
|
+
if (params.needTwoColumns) {
|
|
408
|
+
const leftColumnX = 80;
|
|
409
|
+
const rightColumnX = width / 2 + 80;
|
|
410
|
+
const playersPerColumn = Math.ceil(players.length / 2);
|
|
411
|
+
const displayPerColumn = Math.min(playersPerColumn, Math.ceil(config.maxPlayers / 2));
|
|
412
|
+
const leftPlayers = sortedPlayers.slice(0, displayPerColumn);
|
|
413
|
+
const rightPlayers = sortedPlayers.slice(displayPerColumn, displayPerColumn * 2);
|
|
414
|
+
let currentY = y;
|
|
415
|
+
let displayedCount = 0;
|
|
416
|
+
leftPlayers.forEach((player) => {
|
|
417
|
+
const name2 = utils.truncateText(utils.cleanName(player.name), params.nameMaxLength);
|
|
418
|
+
this.drawText(ctx2, name2, leftColumnX, currentY, {
|
|
419
|
+
fontSize: config.fontSize * params.fontSizeMultiplier,
|
|
420
|
+
color: "#dddddd"
|
|
421
|
+
});
|
|
422
|
+
currentY += params.rowHeight;
|
|
423
|
+
displayedCount++;
|
|
424
|
+
});
|
|
425
|
+
currentY = y;
|
|
426
|
+
rightPlayers.forEach((player) => {
|
|
427
|
+
const name2 = utils.truncateText(utils.cleanName(player.name), params.nameMaxLength);
|
|
428
|
+
this.drawText(ctx2, name2, rightColumnX, currentY, {
|
|
429
|
+
fontSize: config.fontSize * params.fontSizeMultiplier,
|
|
430
|
+
color: "#dddddd"
|
|
431
|
+
});
|
|
432
|
+
currentY += params.rowHeight;
|
|
433
|
+
displayedCount++;
|
|
434
|
+
});
|
|
435
|
+
y = Math.max(currentY + 15, y);
|
|
436
|
+
const totalDisplayed = leftPlayers.length + rightPlayers.length;
|
|
437
|
+
if (players.length > totalDisplayed) {
|
|
438
|
+
this.drawText(ctx2, `... 还有 ${players.length - totalDisplayed} 位玩家未显示`, leftColumnX, y, {
|
|
439
|
+
fontSize: config.fontSize * 0.8,
|
|
440
|
+
color: "#aaaaaa",
|
|
441
|
+
italic: true
|
|
442
|
+
});
|
|
443
|
+
y += 30;
|
|
444
|
+
}
|
|
445
|
+
return { y, displayedCount };
|
|
446
|
+
} else {
|
|
447
|
+
const displayPlayers = sortedPlayers.slice(0, config.maxPlayers);
|
|
448
|
+
displayPlayers.forEach((player) => {
|
|
449
|
+
const name2 = utils.truncateText(utils.cleanName(player.name), params.nameMaxLength);
|
|
450
|
+
this.drawText(ctx2, name2, 80, y, {
|
|
451
|
+
fontSize: config.fontSize * params.fontSizeMultiplier,
|
|
452
|
+
color: "#dddddd"
|
|
453
|
+
});
|
|
454
|
+
y += params.rowHeight;
|
|
455
|
+
});
|
|
456
|
+
return { y, displayedCount: displayPlayers.length };
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
function calculateImageHeight(data) {
|
|
461
|
+
const { result } = data;
|
|
462
|
+
const playerCount = result.players?.length || 0;
|
|
463
|
+
const playerParams = imageUtils.calculatePlayerListParams(playerCount);
|
|
464
|
+
let baseHeight = 280;
|
|
465
|
+
if (playerCount === 0) {
|
|
466
|
+
baseHeight += 60;
|
|
467
|
+
} else {
|
|
468
|
+
baseHeight += 90;
|
|
469
|
+
if (playerParams.needTwoColumns) {
|
|
470
|
+
const rows = Math.ceil(Math.min(playerCount, config.maxPlayers) / 2);
|
|
471
|
+
baseHeight += rows * playerParams.rowHeight;
|
|
472
|
+
} else {
|
|
473
|
+
const rows = Math.min(playerCount, config.maxPlayers);
|
|
474
|
+
baseHeight += rows * playerParams.rowHeight;
|
|
475
|
+
}
|
|
476
|
+
if (playerCount > config.maxPlayers) {
|
|
477
|
+
baseHeight += 40;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
if (config.showPassword && result.password !== void 0) {
|
|
481
|
+
baseHeight += 35;
|
|
482
|
+
}
|
|
483
|
+
if (config.showVAC && result.raw?.secure !== void 0) {
|
|
484
|
+
baseHeight += 35;
|
|
485
|
+
}
|
|
486
|
+
const height = Math.max(baseHeight, config.imageHeight);
|
|
487
|
+
return Math.min(height, 2500);
|
|
488
|
+
}
|
|
489
|
+
__name(calculateImageHeight, "calculateImageHeight");
|
|
490
|
+
async function generateServerImage(data, host, port) {
|
|
491
|
+
const { result } = data;
|
|
492
|
+
const width = config.imageWidth;
|
|
493
|
+
const height = calculateImageHeight(data);
|
|
494
|
+
const canvas = (0, import_canvas.createCanvas)(width, height);
|
|
495
|
+
const ctx2 = canvas.getContext("2d");
|
|
496
|
+
imageUtils.drawBackground(ctx2, width, height);
|
|
497
|
+
const titleY = 80;
|
|
498
|
+
imageUtils.drawTitle(ctx2, "服务器状态", width / 2, titleY, config.fontSize * 2, config.fontFamily);
|
|
499
|
+
if (result.name) {
|
|
500
|
+
const cleanName = utils.cleanName(result.name);
|
|
501
|
+
const fontSize = imageUtils.calculateServerNameFontSize(ctx2, cleanName, width - 160, config.fontSize);
|
|
502
|
+
imageUtils.drawTitle(ctx2, cleanName, width / 2, titleY + 50, fontSize, config.fontFamily, "#FFD700");
|
|
503
|
+
}
|
|
504
|
+
imageUtils.drawDivider(ctx2, 80, titleY + 80, width - 80, titleY + 80, "#FFD700", 3);
|
|
505
|
+
let y = titleY + 120;
|
|
506
|
+
if (result.map) {
|
|
507
|
+
imageUtils.drawText(ctx2, `地图: ${result.map}`, 80, y);
|
|
508
|
+
}
|
|
509
|
+
imageUtils.drawText(ctx2, `IP: ${host}:${port}`, width - 80, y, { align: "right", color: "#bbbbbb" });
|
|
510
|
+
y += 40;
|
|
511
|
+
const playerCount = result.players?.length || 0;
|
|
512
|
+
const botCount = result.bots?.length || 0;
|
|
513
|
+
const maxPlayers = result.maxplayers || 0;
|
|
514
|
+
const playerText = `人数: ${playerCount}/${maxPlayers}${botCount > 0 ? ` (${botCount} Bot)` : ""}`;
|
|
515
|
+
imageUtils.drawText(ctx2, playerText, 80, y, { color: utils.getPlayerColor(playerCount) });
|
|
516
|
+
if (result.ping) {
|
|
517
|
+
imageUtils.drawText(ctx2, `Ping: ${result.ping}ms`, width - 80, y, {
|
|
518
|
+
align: "right",
|
|
519
|
+
color: utils.getPingColor(result.ping)
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
y += 50;
|
|
523
|
+
const playerParams = imageUtils.calculatePlayerListParams(playerCount);
|
|
524
|
+
imageUtils.drawText(ctx2, "在线玩家", 80, y, { color: "#ffffff", bold: true, fontSize: config.fontSize * 1.2 });
|
|
525
|
+
y += 40;
|
|
526
|
+
imageUtils.drawDivider(ctx2, 80, y - 15, width - 80, y - 15, "#555555", 1.5);
|
|
527
|
+
y += 25;
|
|
528
|
+
const playerListResult = imageUtils.drawPlayerList(ctx2, result.players || [], y, width, height, playerParams);
|
|
529
|
+
y = playerListResult.y;
|
|
530
|
+
y += 30;
|
|
531
|
+
const now = /* @__PURE__ */ new Date();
|
|
532
|
+
imageUtils.drawText(ctx2, `查询时间: ${now.toLocaleString("zh-CN")}`, 80, height - 20, {
|
|
533
|
+
fontSize: config.fontSize * 0.8,
|
|
534
|
+
color: "#666666"
|
|
535
|
+
});
|
|
536
|
+
imageUtils.drawDivider(ctx2, 8, 8, width - 8, 8, "#7D8B92", 4);
|
|
537
|
+
imageUtils.drawDivider(ctx2, width - 8, 8, width - 8, height - 8, "#7D8B92", 4);
|
|
538
|
+
imageUtils.drawDivider(ctx2, width - 8, height - 8, 8, height - 8, "#7D8B92", 4);
|
|
539
|
+
imageUtils.drawDivider(ctx2, 8, height - 8, 8, 8, "#7D8B92", 4);
|
|
540
|
+
return canvas.toBuffer("image/png");
|
|
541
|
+
}
|
|
542
|
+
__name(generateServerImage, "generateServerImage");
|
|
543
|
+
async function generateBatchImage(results, serversToQuery, queryTime) {
|
|
544
|
+
const successful = results.filter((r) => r.status === "fulfilled" && r.value.success).length;
|
|
545
|
+
const failed = results.length - successful;
|
|
546
|
+
const baseHeight = 200;
|
|
547
|
+
const serverHeight = 100;
|
|
548
|
+
const height = baseHeight + results.length * serverHeight;
|
|
549
|
+
const width = 1200;
|
|
550
|
+
const canvas = (0, import_canvas.createCanvas)(width, height);
|
|
551
|
+
const ctx2 = canvas.getContext("2d");
|
|
552
|
+
imageUtils.drawBackground(ctx2, width, height);
|
|
553
|
+
imageUtils.drawTitle(ctx2, "服务器状态批量查询", width / 2, 100, config.fontSize * 2, config.fontFamily);
|
|
554
|
+
const now = /* @__PURE__ */ new Date();
|
|
555
|
+
imageUtils.drawText(ctx2, `查询时间: ${now.toLocaleString("zh-CN")}`, 80, 150);
|
|
556
|
+
imageUtils.drawText(ctx2, `耗时: ${utils.formatTime(queryTime)} 成功: ${successful}/${results.length}`, width - 80, 150, { align: "right" });
|
|
557
|
+
imageUtils.drawDivider(ctx2, 80, 165, width - 80, 165, "#FFD700", 3);
|
|
558
|
+
let y = 200;
|
|
559
|
+
results.forEach((result, index) => {
|
|
560
|
+
const server = serversToQuery[index];
|
|
561
|
+
if (result.status === "fulfilled") {
|
|
562
|
+
const { success, data, error } = result.value;
|
|
563
|
+
if (success && data) {
|
|
564
|
+
const serverData = data.result;
|
|
565
|
+
const serverName = serverData.name ? utils.cleanName(serverData.name) : "未知";
|
|
566
|
+
const playerCount = serverData.players?.length || 0;
|
|
567
|
+
const maxPlayers = serverData.maxplayers || 0;
|
|
568
|
+
const botCount = serverData.bots?.length || 0;
|
|
569
|
+
imageUtils.drawText(ctx2, `${index + 1}. ${serverName}`, 80, y, {
|
|
570
|
+
color: "#ffffff",
|
|
571
|
+
bold: true,
|
|
572
|
+
fontSize: config.fontSize * 1.1
|
|
573
|
+
});
|
|
574
|
+
imageUtils.drawText(ctx2, server, 80, y + 30, {
|
|
575
|
+
fontSize: config.fontSize * 0.8,
|
|
576
|
+
color: "#aaaaaa"
|
|
577
|
+
});
|
|
578
|
+
const playerText = `${playerCount}/${maxPlayers}`;
|
|
579
|
+
const playerColor = playerCount > 0 ? "#4CAF50" : "#c03f36";
|
|
580
|
+
imageUtils.drawText(ctx2, playerText, width - 80, y, {
|
|
581
|
+
align: "right",
|
|
582
|
+
color: playerColor,
|
|
583
|
+
bold: true
|
|
584
|
+
});
|
|
585
|
+
if (serverData.map) {
|
|
586
|
+
imageUtils.drawText(ctx2, `地图: ${serverData.map}`, 80, y + 60, {
|
|
587
|
+
fontSize: config.fontSize * 0.8,
|
|
588
|
+
color: "#aaaaaa"
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
if (serverData.ping) {
|
|
592
|
+
const pingColor = utils.getPingColor(serverData.ping);
|
|
593
|
+
imageUtils.drawText(ctx2, `延迟: ${serverData.ping}ms`, width - 80, y + 60, {
|
|
594
|
+
align: "right",
|
|
595
|
+
fontSize: config.fontSize * 0.9,
|
|
596
|
+
color: pingColor
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
} else {
|
|
600
|
+
imageUtils.drawText(ctx2, `${index + 1}. ${server}`, 80, y, { color: "#ffffff", bold: true });
|
|
601
|
+
imageUtils.drawText(ctx2, `❌ 查询失败: ${error}`, 200, y + 35, { color: "#c03f36" });
|
|
602
|
+
}
|
|
603
|
+
} else {
|
|
604
|
+
imageUtils.drawText(ctx2, `${index + 1}. ${server}`, 80, y, { color: "#ffffff", bold: true });
|
|
605
|
+
imageUtils.drawText(ctx2, "❌ 查询失败", 200, y + 35, { color: "#c03f36" });
|
|
606
|
+
}
|
|
607
|
+
if (index < results.length - 1) {
|
|
608
|
+
imageUtils.drawDivider(ctx2, 80, y + 70, width - 80, y + 70, "#555555", 1);
|
|
609
|
+
}
|
|
610
|
+
y += 100;
|
|
611
|
+
});
|
|
612
|
+
imageUtils.drawDivider(ctx2, 8, 8, width - 8, 8, "#7D8B92", 4);
|
|
613
|
+
imageUtils.drawDivider(ctx2, width - 8, 8, width - 8, height - 8, "#7D8B92", 4);
|
|
614
|
+
imageUtils.drawDivider(ctx2, width - 8, height - 8, 8, height - 8, "#7D8B92", 4);
|
|
615
|
+
imageUtils.drawDivider(ctx2, 8, height - 8, 8, 8, "#7D8B92", 4);
|
|
616
|
+
return canvas.toBuffer("image/png");
|
|
617
|
+
}
|
|
618
|
+
__name(generateBatchImage, "generateBatchImage");
|
|
619
|
+
ctx.command("cs.schedule", "定时任务管理").alias("定时任务").option("status", "-s 查看定时任务状态", { type: Boolean, fallback: false }).option("start", "-S 启动定时任务", { type: Boolean, fallback: false }).option("stop", "-T 停止定时任务", { type: Boolean, fallback: false }).option("test", "-t 测试定时任务", { type: Boolean, fallback: false }).option("addGroup", "-a <groupId> 添加群组到定时任务", { type: String }).option("removeGroup", "-r <groupId> 从定时任务移除群组", { type: String }).option("listGroups", "-l 列出定时任务群组", { type: Boolean, fallback: false }).option("run", "-R 立即执行一次定时任务", { type: Boolean, fallback: false }).action(async ({ session, options }) => {
|
|
620
|
+
if (options.status) {
|
|
621
|
+
const status = config.scheduleEnabled ? "✅ 已启用" : "❌ 已禁用";
|
|
622
|
+
const nextRun = scheduleTimer ? "运行中" : "未运行";
|
|
623
|
+
const groups = config.scheduleGroups.length;
|
|
624
|
+
return `📅 定时任务状态
|
|
625
|
+
状态: ${status}
|
|
626
|
+
定时器: ${nextRun}
|
|
627
|
+
间隔: ${config.scheduleInterval}分钟
|
|
628
|
+
时间范围: ${config.scheduleStartTime} - ${config.scheduleEndTime}
|
|
629
|
+
输出格式: ${config.scheduleUseImage ? "图片" : "文本"}
|
|
630
|
+
监控服务器: ${config.serverList.length}个
|
|
631
|
+
目标群组: ${groups}个
|
|
632
|
+
|
|
633
|
+
使用 cs.schedule -h 查看所有命令选项`;
|
|
634
|
+
}
|
|
635
|
+
if (options.start) {
|
|
636
|
+
config.scheduleEnabled = true;
|
|
637
|
+
startScheduleTask();
|
|
638
|
+
return "✅ 定时任务已启动";
|
|
639
|
+
}
|
|
640
|
+
if (options.stop) {
|
|
641
|
+
config.scheduleEnabled = false;
|
|
642
|
+
stopScheduleTask();
|
|
643
|
+
return "✅ 定时任务已停止";
|
|
644
|
+
}
|
|
645
|
+
if (options.test) {
|
|
646
|
+
await executeScheduleTask();
|
|
647
|
+
return "✅ 定时任务测试执行完成";
|
|
648
|
+
}
|
|
649
|
+
if (options.run) {
|
|
650
|
+
await executeScheduleTask();
|
|
651
|
+
return "✅ 已立即执行一次定时任务";
|
|
652
|
+
}
|
|
653
|
+
if (options.addGroup) {
|
|
654
|
+
if (!config.scheduleGroups.includes(options.addGroup)) {
|
|
655
|
+
config.scheduleGroups.push(options.addGroup);
|
|
656
|
+
return `✅ 已添加群组 ${options.addGroup} 到定时任务`;
|
|
657
|
+
} else {
|
|
658
|
+
return `❌ 群组 ${options.addGroup} 已在列表中`;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
if (options.removeGroup) {
|
|
662
|
+
const index = config.scheduleGroups.indexOf(options.removeGroup);
|
|
663
|
+
if (index !== -1) {
|
|
664
|
+
config.scheduleGroups.splice(index, 1);
|
|
665
|
+
return `✅ 已从定时任务移除群组 ${options.removeGroup}`;
|
|
666
|
+
} else {
|
|
667
|
+
return `❌ 群组 ${options.removeGroup} 不在列表中`;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
if (options.listGroups) {
|
|
671
|
+
if (config.scheduleGroups.length === 0) {
|
|
672
|
+
return "📋 定时任务群组列表为空\n使用 cs.schedule -a <群组ID> 添加群组";
|
|
673
|
+
}
|
|
674
|
+
let message = "📋 定时任务群组列表:\n";
|
|
675
|
+
config.scheduleGroups.forEach((groupId, index) => {
|
|
676
|
+
message += `${index + 1}. ${groupId}
|
|
677
|
+
`;
|
|
678
|
+
});
|
|
679
|
+
return message;
|
|
680
|
+
}
|
|
681
|
+
return `📅 定时任务管理命令
|
|
682
|
+
|
|
683
|
+
选项:
|
|
684
|
+
-s, -status 查看定时任务状态
|
|
685
|
+
-S, -start 启动定时任务
|
|
686
|
+
-T, -stop 停止定时任务
|
|
687
|
+
-t, -test 测试定时任务
|
|
688
|
+
-R, -run 立即执行一次定时任务
|
|
689
|
+
-a, -addGroup 添加群组到定时任务
|
|
690
|
+
-r, -removeGroup 从定时任务移除群组
|
|
691
|
+
-l, -listGroups 列出定时任务群组
|
|
692
|
+
|
|
693
|
+
示例:
|
|
694
|
+
cs.schedule -s # 查看状态
|
|
695
|
+
cs.schedule -S # 启动定时任务
|
|
696
|
+
cs.schedule -a 123456 # 添加群组123456
|
|
697
|
+
cs.schedule -t # 测试执行`;
|
|
698
|
+
});
|
|
699
|
+
ctx.command("cs <address>", "查询服务器状态").alias("查询").alias("server").option("noPlayers", "-n 隐藏玩家列表", { type: Boolean, fallback: false }).option("image", "-i 生成图片横幅", { type: Boolean, fallback: false }).option("text", "-t 输出文本信息", { type: Boolean, fallback: false }).option("clear", "-c 清除缓存", { type: Boolean, fallback: false }).action(async ({ session, options }, address) => {
|
|
700
|
+
if (!address) return "使用格式: cs [地址:端口]\n示例: cs 127.0.0.1:27015\n示例: cs edgebug.cn";
|
|
701
|
+
if (options.clear) {
|
|
702
|
+
const count = cache.size;
|
|
703
|
+
cache.clear();
|
|
704
|
+
return `已清除 ${count} 条缓存记录`;
|
|
705
|
+
}
|
|
706
|
+
try {
|
|
707
|
+
const { host, port } = parseAddress(address);
|
|
708
|
+
const data = await queryServer(host, port);
|
|
709
|
+
const shouldGenerateImage = options.image || config.generateImage && !options.text;
|
|
710
|
+
if (shouldGenerateImage) {
|
|
711
|
+
try {
|
|
712
|
+
const imageBuffer = await generateServerImage(data, host, port);
|
|
713
|
+
return import_koishi.h.image(imageBuffer, "image/png");
|
|
714
|
+
} catch (imageError) {
|
|
715
|
+
console.error("生成图片失败:", imageError);
|
|
716
|
+
return `生成图片失败: ${imageError.message}
|
|
717
|
+
将返回文本信息。`;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
let message = formatServerInfo(data);
|
|
721
|
+
message += "\n\n" + formatPlayers(data.result.players || []);
|
|
722
|
+
return message;
|
|
723
|
+
} catch (error) {
|
|
724
|
+
let errorMessage = `查询失败: ${error.message}
|
|
725
|
+
|
|
726
|
+
`;
|
|
727
|
+
if (error.message.includes("无法加载 gamedig")) {
|
|
728
|
+
errorMessage += "请确保已安装 gamedig:\n";
|
|
729
|
+
errorMessage += "1. 在插件目录运行:npm install gamedig\n";
|
|
730
|
+
errorMessage += "2. 重启 Koishi";
|
|
731
|
+
} else if (error.message.includes("无效的地址格式")) {
|
|
732
|
+
errorMessage += "地址格式应为: 地址:端口\n";
|
|
733
|
+
errorMessage += "示例: 127.0.0.1:27015 或 edgebug.cn:27015\n";
|
|
734
|
+
errorMessage += "如果不指定端口,默认使用 27015";
|
|
735
|
+
} else {
|
|
736
|
+
errorMessage += "请检查:\n";
|
|
737
|
+
errorMessage += "1. 服务器地址和端口是否正确\n";
|
|
738
|
+
errorMessage += "2. 服务器是否已开启并允许查询\n";
|
|
739
|
+
errorMessage += "3. 防火墙是否允许访问\n";
|
|
740
|
+
errorMessage += "4. 服务器是否为CS服务器";
|
|
741
|
+
}
|
|
742
|
+
return errorMessage;
|
|
743
|
+
}
|
|
744
|
+
});
|
|
745
|
+
ctx.command("cs.status", "检查插件状态和配置").action(async () => {
|
|
746
|
+
try {
|
|
747
|
+
await loadGamedig();
|
|
748
|
+
const cacheSize = cache.size;
|
|
749
|
+
let canvasStatus = "❌ 不可用";
|
|
750
|
+
try {
|
|
751
|
+
(0, import_canvas.createCanvas)(1, 1);
|
|
752
|
+
canvasStatus = "✅ 可用";
|
|
753
|
+
} catch (error) {
|
|
754
|
+
canvasStatus = `❌ 不可用: ${error.message}`;
|
|
755
|
+
}
|
|
756
|
+
const scheduleStatus = config.scheduleEnabled ? "✅ 已启用" : "❌ 已禁用";
|
|
757
|
+
const scheduleTimerStatus = scheduleTimer ? "运行中" : "未运行";
|
|
758
|
+
return `✅ CS服务器查询插件状态
|
|
759
|
+
💾 缓存数量: ${cacheSize} 条
|
|
760
|
+
🖼️ 图片生成: ${canvasStatus}
|
|
761
|
+
📅 定时任务: ${scheduleStatus} (${scheduleTimerStatus})
|
|
762
|
+
⚙️ 配置参数:
|
|
763
|
+
超时时间: ${config.timeout}ms
|
|
764
|
+
缓存时间: ${config.cacheTime}ms
|
|
765
|
+
重试次数: ${config.retryCount}
|
|
766
|
+
最大显示玩家数: ${config.maxPlayers}
|
|
767
|
+
显示VAC状态: ${config.showVAC ? "是" : "否"}
|
|
768
|
+
显示密码保护: ${config.showPassword ? "是" : "否"}
|
|
769
|
+
生成图片横幅: ${config.generateImage ? "是" : "否"}
|
|
770
|
+
图片宽度: ${config.imageWidth}px
|
|
771
|
+
图片最小高度: ${config.imageHeight}px
|
|
772
|
+
字体大小: ${config.fontSize}px
|
|
773
|
+
|
|
774
|
+
📝 使用: cs [地址:端口]
|
|
775
|
+
📝 选项: -i 生成图片, -t 输出文本, -c 清除缓存
|
|
776
|
+
📅 定时任务: cs.schedule 查看定时任务管理`;
|
|
777
|
+
} catch (error) {
|
|
778
|
+
return `❌ 插件状态异常: ${error.message}
|
|
779
|
+
请运行: npm install gamedig`;
|
|
780
|
+
}
|
|
781
|
+
});
|
|
782
|
+
ctx.command("cs.help", "查看帮助").action(() => {
|
|
783
|
+
return `🔫 CS服务器查询插件帮助
|
|
784
|
+
|
|
785
|
+
📝 基本用法:
|
|
786
|
+
cs [地址:端口]
|
|
787
|
+
示例: cs 127.0.0.1:27015
|
|
788
|
+
示例: cs edgebug.cn
|
|
789
|
+
|
|
790
|
+
🔧 选项:
|
|
791
|
+
-i 生成图片横幅
|
|
792
|
+
-t 输出文本信息
|
|
793
|
+
-c 清除缓存
|
|
794
|
+
|
|
795
|
+
🎯 快捷命令:
|
|
796
|
+
csss - 批量查询服务器状态
|
|
797
|
+
cs.schedule - 定时任务管理
|
|
798
|
+
|
|
799
|
+
📋 其他命令:
|
|
800
|
+
cs.status - 检查插件状态和配置
|
|
801
|
+
cs.help - 显示此帮助
|
|
802
|
+
|
|
803
|
+
📅 定时任务:
|
|
804
|
+
定时自动向指定QQ群组发送服务器状态
|
|
805
|
+
配置: 插件配置面板中设置
|
|
806
|
+
管理: cs.schedule 命令
|
|
807
|
+
群组ID: 填写QQ群号即可
|
|
808
|
+
|
|
809
|
+
💡 提示:
|
|
810
|
+
1. 如果不指定端口,默认使用27015
|
|
811
|
+
2. 只支持CS服务器查询
|
|
812
|
+
3. 查询结果缓存${config.cacheTime}ms,使用 -c 清除缓存`;
|
|
813
|
+
});
|
|
814
|
+
ctx.command("csss", "批量查询服务器状态").alias("batch").alias("multi").alias("批量查询").option("list", "-l 显示配置的服务器列表", { type: Boolean, fallback: false }).option("add", "-a <address> 添加服务器到列表", { type: String }).option("remove", "-r <index> 从列表中移除服务器", { type: Number }).option("clear", "-c 清空服务器列表", { type: Boolean, fallback: false }).option("image", "-i 生成图片横幅", { type: Boolean, fallback: false }).option("text", "-t 输出文本信息", { type: Boolean, fallback: false }).action(async ({ session, options }, ...addresses) => {
|
|
815
|
+
if (options.list) {
|
|
816
|
+
let listMessage = "📋 配置的服务器列表:\n";
|
|
817
|
+
config.serverList.forEach((server, index) => {
|
|
818
|
+
listMessage += `${index + 1}. ${server}
|
|
819
|
+
`;
|
|
820
|
+
});
|
|
821
|
+
return listMessage;
|
|
822
|
+
}
|
|
823
|
+
if (options.add) {
|
|
824
|
+
try {
|
|
825
|
+
parseAddress(options.add);
|
|
826
|
+
config.serverList.push(options.add);
|
|
827
|
+
return `✅ 已添加服务器: ${options.add}
|
|
828
|
+
当前列表: ${config.serverList.length} 个服务器`;
|
|
829
|
+
} catch (error) {
|
|
830
|
+
return `❌ 添加失败: ${error.message}
|
|
831
|
+
正确格式: 地址:端口 (例如: 127.0.0.1:27015)`;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
if (options.remove !== void 0) {
|
|
835
|
+
const index = options.remove - 1;
|
|
836
|
+
if (index >= 0 && index < config.serverList.length) {
|
|
837
|
+
const removed = config.serverList.splice(index, 1)[0];
|
|
838
|
+
return `✅ 已移除服务器: ${removed}
|
|
839
|
+
当前列表: ${config.serverList.length} 个服务器`;
|
|
840
|
+
} else {
|
|
841
|
+
return `❌ 索引无效,请输入 1-${config.serverList.length} 之间的数字`;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
if (options.clear) {
|
|
845
|
+
const count = config.serverList.length;
|
|
846
|
+
config.serverList.length = 0;
|
|
847
|
+
return `✅ 已清空服务器列表,共移除 ${count} 个服务器`;
|
|
848
|
+
}
|
|
849
|
+
let serversToQuery;
|
|
850
|
+
if (addresses.length > 0) {
|
|
851
|
+
serversToQuery = addresses;
|
|
852
|
+
} else if (config.serverList.length > 0) {
|
|
853
|
+
serversToQuery = config.serverList;
|
|
854
|
+
} else {
|
|
855
|
+
return "❌ 没有可查询的服务器\n请使用: csss -a <地址:端口> 添加服务器\n或使用: csss <地址1> <地址2> ... 临时查询";
|
|
856
|
+
}
|
|
857
|
+
const maxServers = 10;
|
|
858
|
+
if (serversToQuery.length > maxServers) {
|
|
859
|
+
serversToQuery = serversToQuery.slice(0, maxServers);
|
|
860
|
+
session?.send(`⚠️ 服务器数量超过限制,仅查询前 ${maxServers} 个`);
|
|
861
|
+
}
|
|
862
|
+
try {
|
|
863
|
+
const startTime = Date.now();
|
|
864
|
+
const results = await Promise.allSettled(
|
|
865
|
+
serversToQuery.map(async (server, index) => {
|
|
866
|
+
try {
|
|
867
|
+
const { host, port } = parseAddress(server);
|
|
868
|
+
const data = await queryServer(host, port);
|
|
869
|
+
return {
|
|
870
|
+
index: index + 1,
|
|
871
|
+
server,
|
|
872
|
+
success: true,
|
|
873
|
+
data
|
|
874
|
+
};
|
|
875
|
+
} catch (error) {
|
|
876
|
+
return {
|
|
877
|
+
index: index + 1,
|
|
878
|
+
server,
|
|
879
|
+
success: false,
|
|
880
|
+
error: error.message
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
})
|
|
884
|
+
);
|
|
885
|
+
const endTime = Date.now();
|
|
886
|
+
const queryTime = endTime - startTime;
|
|
887
|
+
const shouldGenerateImage = options.image || config.generateImage && !options.text;
|
|
888
|
+
if (shouldGenerateImage) {
|
|
889
|
+
try {
|
|
890
|
+
const imageBuffer = await generateBatchImage(results, serversToQuery, queryTime);
|
|
891
|
+
return import_koishi.h.image(imageBuffer, "image/png");
|
|
892
|
+
} catch (imageError) {
|
|
893
|
+
console.error("生成批量查询图片失败:", imageError);
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
const successful = results.filter((r) => r.status === "fulfilled" && r.value.success).length;
|
|
897
|
+
const failed = serversToQuery.length - successful;
|
|
898
|
+
let message = `📊 批量查询结果 (${utils.formatTime(queryTime)})
|
|
899
|
+
`;
|
|
900
|
+
message += `✅ 成功: ${successful} 个 | ❌ 失败: ${failed} 个
|
|
901
|
+
|
|
902
|
+
`;
|
|
903
|
+
message += "序号 服务器名称 在线人数\n";
|
|
904
|
+
message += "──────────────────────────────\n";
|
|
905
|
+
results.forEach((result, index) => {
|
|
906
|
+
const serverInfo = serversToQuery[index];
|
|
907
|
+
if (result.status === "fulfilled") {
|
|
908
|
+
const { success, data, error } = result.value;
|
|
909
|
+
if (success && data) {
|
|
910
|
+
const { result: serverData } = data;
|
|
911
|
+
const serverName = serverData.name ? utils.cleanName(serverData.name) : "未知";
|
|
912
|
+
const playerCount = serverData.players?.length || 0;
|
|
913
|
+
const maxPlayers = serverData.maxplayers || 0;
|
|
914
|
+
const truncatedName = utils.truncateText(serverName, 20);
|
|
915
|
+
const paddedName = truncatedName.padEnd(20, " ");
|
|
916
|
+
message += `${(index + 1).toString().padStart(2, " ")} ${paddedName} ${playerCount}/${maxPlayers}
|
|
917
|
+
`;
|
|
918
|
+
} else {
|
|
919
|
+
message += `${(index + 1).toString().padStart(2, " ")} ${serverInfo} ❌ 查询失败: ${error}
|
|
920
|
+
`;
|
|
921
|
+
}
|
|
922
|
+
} else {
|
|
923
|
+
message += `${(index + 1).toString().padStart(2, " ")} ${serverInfo} ❌ 查询失败
|
|
924
|
+
`;
|
|
925
|
+
}
|
|
926
|
+
});
|
|
927
|
+
message += "\n📋 输入 `cs <序号>` 查看服务器详情";
|
|
928
|
+
message += "\n📋 输入 `cs <服务器地址>` 查询单个服务器";
|
|
929
|
+
return message;
|
|
930
|
+
} catch (error) {
|
|
931
|
+
return `❌ 批量查询失败: ${error.message}`;
|
|
932
|
+
}
|
|
933
|
+
});
|
|
934
|
+
if (config.scheduleEnabled) {
|
|
935
|
+
startScheduleTask();
|
|
936
|
+
}
|
|
937
|
+
ctx.on("dispose", () => {
|
|
938
|
+
cache.clear();
|
|
939
|
+
stopScheduleTask();
|
|
940
|
+
});
|
|
941
|
+
}
|
|
942
|
+
__name(apply, "apply");
|
|
943
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
944
|
+
0 && (module.exports = {
|
|
945
|
+
Config,
|
|
946
|
+
apply,
|
|
947
|
+
name
|
|
948
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "koishi-plugin-csss",
|
|
3
|
+
"description": "Check the status of the CS server",
|
|
4
|
+
"version": "1.0.0",
|
|
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
|
+
"dependencies": {
|
|
21
|
+
"canvas": "^3.2.1",
|
|
22
|
+
"gamedig": "^5.3.2"
|
|
23
|
+
},
|
|
24
|
+
"koishi": {
|
|
25
|
+
"description": {
|
|
26
|
+
"zh": "CS服务器状态查询,包括CS1.6/CSS/CSGO/CS2"
|
|
27
|
+
},
|
|
28
|
+
"service": {
|
|
29
|
+
"required": [
|
|
30
|
+
"database"
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|