koishi-plugin-cs2-server-query 2.4.0 → 2.6.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.
Files changed (2) hide show
  1. package/lib/index.js +282 -25
  2. package/package.json +1 -1
package/lib/index.js CHANGED
@@ -40,6 +40,7 @@ var import_steam_server_query = require("steam-server-query");
40
40
  var import_fs = __toESM(require("fs"));
41
41
  var import_path = __toESM(require("path"));
42
42
  var import_puppeteer = __toESM(require("puppeteer"));
43
+ var zeStats = {};
43
44
  var name = "cs2-server-query";
44
45
  var Config = import_koishi.Schema.object({
45
46
  mapTranslationFile: import_koishi.Schema.string().default("map-translation.json"),
@@ -170,6 +171,14 @@ function apply(ctx, config) {
170
171
  if (import_fs.default.existsSync(communityStatsFilePath)) {
171
172
  communityStats = JSON.parse(import_fs.default.readFileSync(communityStatsFilePath, "utf-8"));
172
173
  }
174
+ const zeStatsFilePath = import_path.default.resolve(__dirname, "ze-stats.json");
175
+ if (import_fs.default.existsSync(zeStatsFilePath)) {
176
+ zeStats = JSON.parse(import_fs.default.readFileSync(zeStatsFilePath, "utf-8"));
177
+ }
178
+ function saveZEStats() {
179
+ import_fs.default.writeFileSync(zeStatsFilePath, JSON.stringify(zeStats, null, 2));
180
+ }
181
+ __name(saveZEStats, "saveZEStats");
173
182
  function saveCommunityStats() {
174
183
  import_fs.default.writeFileSync(communityStatsFilePath, JSON.stringify(communityStats, null, 2));
175
184
  }
@@ -282,7 +291,7 @@ function apply(ctx, config) {
282
291
  </head>
283
292
  <body>
284
293
  <div class="overlay">
285
- <h2>${group.name} - 服务器列表</h2>
294
+ <h2>${group.name} - 服务器列表(输入help查指令,快速发送进服IP例如"exg 1")</h2>
286
295
  <p class="total-players">总玩家数: ${totalPlayers}/${totalMaxPlayers} | 在线服务器: ${onlineServers}/${group.servers.length}</p> <!-- 显示在线服务器数量 -->
287
296
  <table>
288
297
  <tr>
@@ -290,7 +299,7 @@ function apply(ctx, config) {
290
299
  <th>服务器名称</th>
291
300
  <th>地图</th>
292
301
  <th>玩家</th>
293
- <th>地图存在时间</th>
302
+ <th>地图运行时间</th>
294
303
  <th>状态</th>
295
304
  <th>IP 地址</th>
296
305
  </tr>
@@ -1167,14 +1176,31 @@ ${mapList}
1167
1176
  communityStats[group.name] = communityStats[group.name].filter(
1168
1177
  (stat) => now - stat.time <= 12 * 60 * 60 * 1e3
1169
1178
  );
1179
+ const zeServers = group.servers.filter(
1180
+ (server) => server.status === "在线" && (server.name.includes("僵尸逃跑") || server.name.includes("ZE") || server.name.includes("Zombie") || server.name.includes("ZOMBIE"))
1181
+ );
1182
+ const zeTotalPlayers = zeServers.reduce((sum, server) => sum + server.players, 0);
1183
+ if (zeServers.length > 0) {
1184
+ if (!zeStats[group.name]) {
1185
+ zeStats[group.name] = [];
1186
+ }
1187
+ zeStats[group.name].push({
1188
+ time: Date.now(),
1189
+ players: zeTotalPlayers
1190
+ });
1191
+ zeStats[group.name] = zeStats[group.name].filter(
1192
+ (stat) => now - stat.time <= 12 * 60 * 60 * 1e3
1193
+ );
1194
+ }
1170
1195
  }
1171
1196
  saveCommunityStats();
1197
+ saveZEStats();
1172
1198
  }, 10 * 60 * 1e3);
1173
- ctx.command("cs2统计", "生成并发送社区服务器总人数统计折线图").action(async ({ session }) => {
1199
+ ctx.command("cs2统计", "生成并发送CS2社区服总人数统计折线图").action(async ({ session }) => {
1174
1200
  const browser = await import_puppeteer.default.launch({ args: ["--no-sandbox", "--disable-setuid-sandbox"] });
1175
1201
  const page = await browser.newPage();
1176
1202
  const colors = ["#FF0000", "#00FF00", "#0000FF", "#FF00FF", "#00FFFF", "#FFFF00", "#FFA500", "#800080", "#008000", "#000080"];
1177
- const margin = { top: 30, right: 30, bottom: 50, left: 50 };
1203
+ const margin = { top: 45, right: 30, bottom: 50, left: 50 };
1178
1204
  const width = 1200 - margin.left - margin.right;
1179
1205
  const height = 600 - margin.top - margin.bottom;
1180
1206
  const generateSVG = /* @__PURE__ */ __name((statsData) => {
@@ -1237,31 +1263,65 @@ ${mapList}
1237
1263
  `;
1238
1264
  }).join("")}
1239
1265
  `;
1240
- const legend = groups2.map(([groupName], i) => `
1241
- <g transform="translate(${width - 220}, ${20 + i * 30})">
1242
- <rect x="0" y="-12" width="24" height="24" rx="4" fill="rgba(0,0,0,0.5)"/>
1243
- <path d="M4 -2 L20 -2" stroke="${colors[i % colors.length]}" stroke-width="3"/>
1244
- <circle cx="12" cy="-2" r="4" fill="${colors[i % colors.length]}"/>
1245
- <text x="30" y="0" fill="#fff" font-size="16" dominant-baseline="middle">${groupName}</text>
1246
- </g>
1247
- `).join("");
1266
+ const legend = groups2.map(([groupName, dataPoints], i) => {
1267
+ const latest = dataPoints[dataPoints.length - 1];
1268
+ const latestPlayers = latest ? latest.players : 0;
1269
+ const playerRatio = latestPlayers / maxPlayers;
1270
+ const playerColor = playerRatio <= 0.5 ? "#33cc00" : playerRatio <= 0.8 ? "#ffcc00" : "#ff4444";
1271
+ return `
1272
+ <g transform="translate(${width - 250}, ${20 + i * 35})">
1273
+ <!-- 社区标识 -->
1274
+ <rect x="0" y="-14" width="28" height="28" rx="6" fill="rgba(0,0,0,0.5)"/>
1275
+ <path d="M6 -4 L22 -4" stroke="${colors[i % colors.length]}" stroke-width="4"/>
1276
+ <circle cx="14" cy="-4" r="5" fill="${colors[i % colors.length]}"/>
1277
+
1278
+ <!-- 社区名称 -->
1279
+ <text x="35" y="0" fill="#fff" font-size="18" font-weight="bold" dominant-baseline="middle">
1280
+ ${groupName}
1281
+ </text>
1282
+
1283
+ <!-- 人数显示 - 使用动态颜色 -->
1284
+ <g transform="translate(180, 0)">
1285
+ <rect x="-15" y="-15" width="70" height="30" rx="8" fill="rgba(0,0,0,0.5)" stroke="${playerColor}" stroke-width="1.5"/>
1286
+ <text x="20" y="0" fill="${playerColor}" font-size="18" font-weight="bold" text-anchor="middle" dominant-baseline="middle">
1287
+ ${latestPlayers}
1288
+ </text>
1289
+ <text x="35" y="0" fill="#aaa" font-size="14" dominant-baseline="middle">人</text>
1290
+ </g>
1291
+ </g>
1292
+ `;
1293
+ }).join("");
1248
1294
  return `
1249
1295
  <svg viewBox="0 0 ${width + margin.left + margin.right} ${height + margin.top + margin.bottom}">
1250
1296
  <style>
1251
- .title { font: bold 24px Arial; }
1252
- .axis-label { font: 16px Arial; }
1297
+ .title {
1298
+ font: bold 28px 'Microsoft YaHei', sans-serif;
1299
+ fill: #fff;
1300
+ text-anchor: middle;
1301
+ text-shadow: 0 0 8px rgba(255, 255, 255, 0.3);
1302
+ }
1303
+ .axis-label {
1304
+ font: 18px 'Microsoft YaHei', sans-serif;
1305
+ fill: #fff;
1306
+ }
1307
+ .latest-title {
1308
+ font: bold 22px 'Microsoft YaHei', sans-serif;
1309
+ fill: #FFD700;
1310
+ text-anchor: end;
1311
+ }
1253
1312
  </style>
1254
1313
  <g transform="translate(${margin.left}, ${margin.top})">
1255
- <text x="${width / 2}" y="-10" class="title" fill="#fff" text-anchor="middle">服务器人数统计(12小时)</text>
1314
+ <!-- 主标题 -->
1315
+ <text x="${width / 2}" y="-15" class="title">
1316
+ CS2社区总人数统计(12小时)
1317
+ </text>
1256
1318
 
1257
- <!-- 渐变背景 -->
1258
- <defs>
1259
- <linearGradient id="grid-gradient" x1="0%" y1="0%" x2="0%" y2="100%">
1260
- <stop offset="0%" stop-color="rgba(255,255,255,0.1)"/>
1261
- <stop offset="100%" stop-color="rgba(255,255,255,0)"/>
1262
- </linearGradient>
1263
- </defs>
1319
+ <!-- 右上角标题 -->
1320
+ <text x="${width - 10}" y="-15" class="latest-title" text-anchor="end">
1321
+ 最新在线人数
1322
+ </text>
1264
1323
 
1324
+ <!-- 图表内容 -->
1265
1325
  ${xAxis}
1266
1326
  ${yAxis}
1267
1327
  <rect width="${width}" height="${height}" fill="url(#grid-gradient)"/>
@@ -1270,10 +1330,14 @@ ${mapList}
1270
1330
  ([_, stats], i) => createPath(stats, colors[i % colors.length])
1271
1331
  ).join("")}
1272
1332
 
1273
- <g transform="translate(20, 20)">${legend}</g>
1333
+ <!-- 图例 -->
1334
+ <g transform="translate(0, 0)">
1335
+ ${legend}
1336
+ </g>
1274
1337
 
1275
- <text x="${width / 2}" y="${height + 45}" class="axis-label" fill="#fff" text-anchor="middle">时间</text>
1276
- <text x="-70" y="${height / 2}" transform="rotate(-90)" class="axis-label" fill="#fff" text-anchor="middle">玩家数量</text>
1338
+ <!-- 坐标轴标签 -->
1339
+ <text x="${width / 2}" y="${height + 45}" class="axis-label" text-anchor="middle">时间</text>
1340
+ <text x="-70" y="${height / 2}" transform="rotate(-90)" class="axis-label" text-anchor="middle">玩家数量</text>
1277
1341
  </g>
1278
1342
  </svg>
1279
1343
  `;
@@ -1305,6 +1369,199 @@ ${mapList}
1305
1369
  </div>
1306
1370
  </body>
1307
1371
  </html>
1372
+ `;
1373
+ await page.setContent(html);
1374
+ await page.setViewport({ width: 1280, height: 720 });
1375
+ const screenshot = await page.screenshot({
1376
+ type: "png",
1377
+ fullPage: true,
1378
+ encoding: "binary"
1379
+ });
1380
+ await browser.close();
1381
+ return import_koishi.h.image(screenshot, "image/png");
1382
+ });
1383
+ ctx.command("ze统计", "生成并发送各社区ZE服务器总人数统计折线图").action(async ({ session }) => {
1384
+ const browser = await import_puppeteer.default.launch({ args: ["--no-sandbox", "--disable-setuid-sandbox"] });
1385
+ const page = await browser.newPage();
1386
+ const colors = ["#FF0000", "#00FF00", "#0000FF", "#FF00FF", "#00FFFF", "#FFFF00", "#FFA500", "#800080", "#008000", "#000080"];
1387
+ const margin = { top: 45, right: 30, bottom: 50, left: 50 };
1388
+ const width = 1200 - margin.left - margin.right;
1389
+ const height = 600 - margin.top - margin.bottom;
1390
+ const generateSVG = /* @__PURE__ */ __name((statsData) => {
1391
+ const communities = Object.entries(statsData);
1392
+ if (communities.length === 0) return "";
1393
+ const allPoints = communities.flatMap(([_, stats]) => stats);
1394
+ const minTime = Math.min(...allPoints.map((p) => p.time));
1395
+ const maxTime = Math.max(...allPoints.map((p) => p.time));
1396
+ const maxPlayers = Math.max(...allPoints.map((p) => p.players)) || 1;
1397
+ const xScale = /* @__PURE__ */ __name((time) => (time - minTime) / (maxTime - minTime) * width, "xScale");
1398
+ const yScale = /* @__PURE__ */ __name((players) => height - players / maxPlayers * height, "yScale");
1399
+ const createPath = /* @__PURE__ */ __name((points, color) => {
1400
+ if (points.length < 2) return "";
1401
+ const first = points[0];
1402
+ let path2 = `M ${xScale(first.time)} ${yScale(first.players)}`;
1403
+ for (let i = 1; i < points.length; i++) {
1404
+ const p = points[i];
1405
+ const prev = points[i - 1];
1406
+ const cp1x = (prev.time + p.time) / 2;
1407
+ const cp1y = prev.players;
1408
+ const cp2x = cp1x;
1409
+ const cp2y = p.players;
1410
+ path2 += ` C ${xScale(cp1x)} ${yScale(cp1y)}, ${xScale(cp2x)} ${yScale(cp2y)}, ${xScale(p.time)} ${yScale(p.players)}`;
1411
+ }
1412
+ return `
1413
+ <path d="${path2}" fill="none" stroke="${color}" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"/>
1414
+ ${points.map((p) => `
1415
+ <circle cx="${xScale(p.time)}" cy="${yScale(p.players)}" r="4" fill="${color}"/>
1416
+ <circle cx="${xScale(p.time)}" cy="${yScale(p.players)}" r="6" fill="${color}" fill-opacity="0.2"/>
1417
+ `).join("")}
1418
+ `;
1419
+ }, "createPath");
1420
+ const xAxis = `
1421
+ <path d="M0 ${height} H${width}" stroke="#fff" stroke-width="2"/>
1422
+ ${Array.from({ length: 12 }).map((_, i) => {
1423
+ const x = width * i / 11;
1424
+ const date = new Date(minTime + (maxTime - minTime) * (i / 11));
1425
+ return `
1426
+ <g transform="translate(${x}, ${height})">
1427
+ <path d="M0 0 v5" stroke="#fff" stroke-width="1"/>
1428
+ <text x="0" y="25" fill="#fff" font-size="14" text-anchor="middle">
1429
+ ${date.toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit" })}
1430
+ </text>
1431
+ </g>
1432
+ `;
1433
+ }).join("")}
1434
+ `;
1435
+ const yAxis = `
1436
+ <path d="M0 0 V${height}" stroke="#fff" stroke-width="2"/>
1437
+ ${Array.from({ length: 6 }).map((_, i) => {
1438
+ const y = height - height * i / 5;
1439
+ return `
1440
+ <g transform="translate(0, ${y})">
1441
+ <path d="M-5 0 h5" stroke="#fff" stroke-width="1"/>
1442
+ <text x="-15" y="5" fill="#fff" font-size="14" text-anchor="end">
1443
+ ${Math.round(maxPlayers * i / 5)}
1444
+ </text>
1445
+ <path d="M0 0 H${width}" stroke="rgba(255,255,255,0.1)" stroke-width="1"/>
1446
+ </g>
1447
+ `;
1448
+ }).join("")}
1449
+ `;
1450
+ const legend = communities.map(([communityName, dataPoints], i) => {
1451
+ const latest = dataPoints[dataPoints.length - 1];
1452
+ const latestPlayers = latest ? latest.players : 0;
1453
+ const playerRatio = latestPlayers / maxPlayers;
1454
+ const playerColor = playerRatio <= 0.5 ? "#33cc00" : playerRatio <= 0.8 ? "#ffcc00" : "#ff4444";
1455
+ return `
1456
+ <g transform="translate(${width - 250}, ${20 + i * 35})">
1457
+ <!-- 社区标识 -->
1458
+ <rect x="0" y="-14" width="28" height="28" rx="6" fill="rgba(0,0,0,0.5)"/>
1459
+ <path d="M6 -4 L22 -4" stroke="${colors[i % colors.length]}" stroke-width="4"/>
1460
+ <circle cx="14" cy="-4" r="5" fill="${colors[i % colors.length]}"/>
1461
+
1462
+ <!-- 社区名称 -->
1463
+ <text x="35" y="0" fill="#fff" font-size="18" font-weight="bold" dominant-baseline="middle">
1464
+ ${communityName}
1465
+ </text>
1466
+
1467
+ <!-- 人数显示 -->
1468
+ <g transform="translate(180, 0)">
1469
+ <rect x="-15" y="-15" width="70" height="30" rx="8" fill="rgba(0,0,0,0.5)" stroke="${playerColor}" stroke-width="1.5"/>
1470
+ <text x="20" y="0" fill="${playerColor}" font-size="18" font-weight="bold" text-anchor="middle" dominant-baseline="middle">
1471
+ ${latestPlayers}
1472
+ </text>
1473
+ <text x="35" y="0" fill="#aaa" font-size="14" dominant-baseline="middle">人</text>
1474
+ </g>
1475
+ </g>
1476
+ `;
1477
+ }).join("");
1478
+ return `
1479
+ <svg viewBox="0 0 ${width + margin.left + margin.right} ${height + margin.top + margin.bottom}">
1480
+ <style>
1481
+ .title {
1482
+ font: bold 28px 'Microsoft YaHei', sans-serif;
1483
+ fill: #fff;
1484
+ text-anchor: middle;
1485
+ text-shadow: 0 0 8px rgba(255, 255, 255, 0.3);
1486
+ }
1487
+ .axis-label {
1488
+ font: 18px 'Microsoft YaHei', sans-serif;
1489
+ fill: #fff;
1490
+ }
1491
+ .latest-title {
1492
+ font: bold 22px 'Microsoft YaHei', sans-serif;
1493
+ fill: #FFD700;
1494
+ text-anchor: end;
1495
+ }
1496
+ </style>
1497
+ <g transform="translate(${margin.left}, ${margin.top})">
1498
+ <!-- 主标题 -->
1499
+ <text x="${width / 2}" y="-15" class="title">
1500
+ ZE服务器总人数统计(12小时)
1501
+ </text>
1502
+
1503
+ <!-- 右上角标题 -->
1504
+ <text x="${width - 10}" y="-15" class="latest-title" text-anchor="end">
1505
+ 最新在线人数
1506
+ </text>
1507
+
1508
+ <!-- 图表内容 -->
1509
+ ${xAxis}
1510
+ ${yAxis}
1511
+
1512
+ ${communities.map(
1513
+ ([_, stats], i) => createPath(stats, colors[i % colors.length])
1514
+ ).join("")}
1515
+
1516
+ <!-- 图例 -->
1517
+ <g transform="translate(0, 0)">
1518
+ ${legend}
1519
+ </g>
1520
+
1521
+ <!-- 坐标轴标签 -->
1522
+ <text x="${width / 2}" y="${height + 45}" class="axis-label" text-anchor="middle">时间</text>
1523
+ <text x="-70" y="${height / 2}" transform="rotate(-90)" class="axis-label" text-anchor="middle">玩家数量</text>
1524
+ </g>
1525
+ </svg>
1526
+ `;
1527
+ }, "generateSVG");
1528
+ if (Object.keys(zeStats).length === 0) {
1529
+ await browser.close();
1530
+ return "暂无ZE服务器统计数据,请等待数据收集(每10分钟更新一次)。";
1531
+ }
1532
+ const html = `
1533
+ <!DOCTYPE html>
1534
+ <html>
1535
+ <head>
1536
+ <style>
1537
+ body {
1538
+ margin: 0;
1539
+ padding: 40px;
1540
+ background: url('${config.statsBackgroundImage}') no-repeat center center/cover;
1541
+ color: white;
1542
+ font-family: 'Arial', sans-serif;
1543
+ }
1544
+ .chart-container {
1545
+ background: rgba(0, 0, 0, 0.8);
1546
+ border-radius: 20px;
1547
+ padding: 30px;
1548
+ box-shadow: 0 0 30px rgba(0,0,0,0.5);
1549
+ backdrop-filter: blur(5px);
1550
+ }
1551
+ .no-data {
1552
+ text-align: center;
1553
+ font-size: 24px;
1554
+ color: #fff;
1555
+ padding: 100px 0;
1556
+ }
1557
+ </style>
1558
+ </head>
1559
+ <body>
1560
+ <div class="chart-container">
1561
+ ${Object.keys(zeStats).length > 0 ? generateSVG(zeStats) : '<div class="no-data">暂无ZE服务器统计数据</div>'}
1562
+ </div>
1563
+ </body>
1564
+ </html>
1308
1565
  `;
1309
1566
  await page.setContent(html);
1310
1567
  await page.setViewport({ width: 1280, height: 720 });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-cs2-server-query",
3
3
  "description": "自用,不推荐下载",
4
- "version": "2.4.0",
4
+ "version": "2.6.0",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [