koishi-plugin-csss 2.0.0 → 2.1.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.js CHANGED
@@ -28,7 +28,7 @@ __export(src_exports, {
28
28
  module.exports = __toCommonJS(src_exports);
29
29
  var import_koishi = require("koishi");
30
30
  var name = "csss";
31
- var inject = ["canvas", "gamedig", "database"];
31
+ var inject = ["puppeteer", "gamedig", "database"];
32
32
  var Config = import_koishi.Schema.object({
33
33
  timeout: import_koishi.Schema.number().min(1e3).max(3e4).default(5e3).description("查询超时时间(毫秒)"),
34
34
  cacheTime: import_koishi.Schema.number().min(0).max(3e5).default(3e4).description("缓存时间(毫秒,0为禁用缓存)"),
@@ -51,8 +51,8 @@ var Config = import_koishi.Schema.object({
51
51
  batchTimeout: import_koishi.Schema.number().min(1e3).max(6e4).default(15e3).description("批量查询总超时时间(毫秒)")
52
52
  });
53
53
  var COLORS = {
54
- background: "rgba(28,28,31,0.80)",
55
- text: "rgb(113, 113, 122)",
54
+ background: "#1c1c1fcc",
55
+ text: "#71717a",
56
56
  textLight: "#aaaaaa",
57
57
  textLighter: "#dddddd",
58
58
  textWhite: "#ffffff",
@@ -72,7 +72,7 @@ var COLORS = {
72
72
  divider: "#555555",
73
73
  timestamp: "#666666",
74
74
  gold: "#FFD700",
75
- playerName: "rgb(252, 248, 222)"
75
+ playerName: "#fcf8de"
76
76
  };
77
77
  var utils = {
78
78
  formatPing(ping) {
@@ -109,9 +109,9 @@ function apply(ctx, config) {
109
109
  console.error("koishi-plugin-gamedig 未安装或未启用");
110
110
  return ctx.logger("cs-server-status").error("需要安装并启用 koishi-plugin-gamedig 插件");
111
111
  }
112
- if (!ctx.canvas) {
113
- console.error("koishi-plugin-canvas 未安装或未启用");
114
- return ctx.logger("cs-server-status").error("需要安装并启用 koishi-plugin-canvas 插件");
112
+ if (!ctx.puppeteer) {
113
+ console.error("koishi-plugin-puppeteer 未安装或未启用");
114
+ return ctx.logger("cs-server-status").error("需要安装并启用 koishi-plugin-puppeteer 插件");
115
115
  }
116
116
  async function queryServers(serversToQuery) {
117
117
  const startTime = Date.now();
@@ -271,300 +271,349 @@ function apply(ctx, config) {
271
271
  return message.trim();
272
272
  }
273
273
  __name(formatPlayers, "formatPlayers");
274
- const imageUtils = {
275
- calculateServerNameFontSize(ctx2, name2, maxWidth, baseFontSize) {
276
- try {
277
- if (!ctx2 || typeof ctx2.measureText !== "function") {
278
- console.warn("Canvas context not available, returning default font size");
279
- return baseFontSize * 1.5;
280
- }
281
- let fontSize = baseFontSize * 1.5;
282
- while (fontSize > baseFontSize * 0.8) {
283
- ctx2.font = `bold ${fontSize}px ${config.fontFamily}`;
284
- const measurement = ctx2.measureText(name2);
285
- if (measurement && measurement.width <= maxWidth) break;
286
- fontSize -= 1;
287
- }
288
- return fontSize;
289
- } catch (error) {
290
- console.error("Error in calculateServerNameFontSize:", error);
291
- return baseFontSize * 1.5;
292
- }
293
- },
294
- calculatePlayerListParams(playerCount) {
295
- const shouldEnlarge = playerCount > 0 && playerCount < 10;
296
- return {
297
- shouldEnlarge,
298
- fontSizeMultiplier: shouldEnlarge ? 1.2 : 0.9,
299
- rowHeight: shouldEnlarge ? 40 : 30,
300
- nameMaxLength: shouldEnlarge ? 40 : 30,
301
- needTwoColumns: playerCount > 10
302
- };
303
- },
304
- drawBackground(ctx2, width, height, color = COLORS.background) {
305
- ctx2.fillStyle = color;
306
- ctx2.fillRect(0, 0, width, height);
307
- },
308
- drawTitle(ctx2, text, x, y, fontSize, fontFamily, color = COLORS.textWhite) {
309
- ctx2.fillStyle = color;
310
- ctx2.font = `bold ${fontSize}px ${fontFamily}`;
311
- ctx2.textAlign = "center";
312
- ctx2.fillText(text, x, y);
313
- },
314
- drawDivider(ctx2, x1, y1, x2, y2, color = COLORS.divider, width = 2) {
315
- ctx2.strokeStyle = color;
316
- ctx2.lineWidth = width;
317
- ctx2.beginPath();
318
- ctx2.moveTo(x1, y1);
319
- ctx2.lineTo(x2, y2);
320
- ctx2.stroke();
321
- },
322
- drawText(ctx2, text, x, y, options = {}) {
323
- const {
324
- color = COLORS.text,
325
- fontSize = config.fontSize,
326
- fontFamily = config.fontFamily,
327
- align = "left",
328
- bold = false,
329
- italic = false
330
- } = options;
331
- ctx2.fillStyle = color;
332
- ctx2.textAlign = align;
333
- const fontStyle = `${bold ? "bold" : ""} ${italic ? "italic" : ""} ${fontSize}px ${fontFamily}`;
334
- ctx2.font = fontStyle.trim() || `${fontSize}px ${fontFamily}`;
335
- ctx2.fillText(text, x, y);
336
- },
337
- drawPlayerList(ctx2, players, startY, width, maxHeight, params) {
338
- let y = startY;
339
- if (players.length === 0) {
340
- this.drawText(ctx2, "服务器当前无玩家在线", 80, y, { color: COLORS.textLight });
341
- return { y: y + 35, displayedCount: 0 };
342
- }
343
- const sortedPlayers = [...players].sort((a, b) => {
344
- const nameA = utils.cleanName(a.name).toLowerCase();
345
- const nameB = utils.cleanName(b.name).toLowerCase();
346
- return nameA.localeCompare(nameB);
347
- });
348
- if (params.needTwoColumns) {
349
- const leftColumnX = 80;
350
- const rightColumnX = width / 2 + 80;
351
- const playersPerColumn = Math.ceil(players.length / 2);
352
- const displayPerColumn = Math.min(playersPerColumn, Math.ceil(config.maxPlayers / 2));
353
- const leftPlayers = sortedPlayers.slice(0, displayPerColumn);
354
- const rightPlayers = sortedPlayers.slice(displayPerColumn, displayPerColumn * 2);
355
- let currentY = y;
356
- let displayedCount = 0;
357
- leftPlayers.forEach((player) => {
358
- const name2 = utils.truncateText(utils.cleanName(player.name), params.nameMaxLength);
359
- this.drawText(ctx2, name2, leftColumnX, currentY, {
360
- fontSize: config.fontSize * params.fontSizeMultiplier,
361
- color: COLORS.textLighter
362
- });
363
- currentY += params.rowHeight;
364
- displayedCount++;
365
- });
366
- currentY = y;
367
- rightPlayers.forEach((player) => {
368
- const name2 = utils.truncateText(utils.cleanName(player.name), params.nameMaxLength);
369
- this.drawText(ctx2, name2, rightColumnX, currentY, {
370
- fontSize: config.fontSize * params.fontSizeMultiplier,
371
- color: COLORS.textLighter
372
- });
373
- currentY += params.rowHeight;
374
- displayedCount++;
375
- });
376
- y = Math.max(currentY + 15, y);
377
- const totalDisplayed = leftPlayers.length + rightPlayers.length;
378
- if (players.length > totalDisplayed) {
379
- this.drawText(ctx2, `... 还有 ${players.length - totalDisplayed} 位玩家未显示`, leftColumnX, y, {
380
- fontSize: config.fontSize * 0.8,
381
- color: COLORS.textLight,
382
- italic: true
383
- });
384
- y += 30;
385
- }
386
- return { y, displayedCount };
387
- } else {
388
- const displayPlayers = sortedPlayers.slice(0, config.maxPlayers);
389
- displayPlayers.forEach((player) => {
390
- const name2 = utils.truncateText(utils.cleanName(player.name), params.nameMaxLength);
391
- this.drawText(ctx2, name2, 80, y, {
392
- fontSize: config.fontSize * params.fontSizeMultiplier,
393
- color: COLORS.textLighter
394
- });
395
- y += params.rowHeight;
396
- });
397
- return { y, displayedCount: displayPlayers.length };
398
- }
399
- },
400
- // 边框绘制函数
401
- drawBorder(ctx2, width, height) {
402
- this.drawDivider(ctx2, 1, 1, width - 1, 1, COLORS.border, 2);
403
- this.drawDivider(ctx2, width - 1, 1, width - 1, height - 1, COLORS.border, 2);
404
- this.drawDivider(ctx2, width - 1, height - 1, 1, height - 1, COLORS.border, 2);
405
- this.drawDivider(ctx2, 1, height - 1, 1, 1, COLORS.border, 2);
406
- this.drawDivider(ctx2, 5, 0.5 * height - 0.05 * height, 5, height - 0.5 * height + 0.05 * height, COLORS.border, 6);
407
- this.drawDivider(ctx2, width - 5, 0.5 * height - 0.05 * height, width - 5, height - 0.5 * height + 0.05 * height, COLORS.border, 6);
408
- this.drawDivider(ctx2, 2, 2, 0.025 * width, 2, COLORS.accent, 3);
409
- this.drawDivider(ctx2, 2, 2, 2, 0.025 * width, COLORS.accent, 3);
410
- this.drawDivider(ctx2, width - 2, 2, width - 2, 0.025 * width, COLORS.accent, 3);
411
- this.drawDivider(ctx2, width - 2, 2, width - 0.025 * width, 2, COLORS.accent, 3);
412
- this.drawDivider(ctx2, width - 2, height - 2, width - 2, height - 0.025 * width, COLORS.accent, 3);
413
- this.drawDivider(ctx2, width - 2, height - 2, width - 0.025 * width, height - 2, COLORS.accent, 3);
414
- this.drawDivider(ctx2, 2, height - 2, 0.025 * width, height - 2, COLORS.accent, 3);
415
- this.drawDivider(ctx2, 2, height - 2, 2, height - 0.025 * width, COLORS.accent, 3);
416
- }
417
- };
418
- function calculateImageHeight(data) {
274
+ function generateServerHTML(data, host, port) {
419
275
  const { result } = data;
420
276
  const playerCount = result.players?.length || 0;
421
- const playerParams = imageUtils.calculatePlayerListParams(playerCount);
422
- let baseHeight = 280;
277
+ const botCount = result.bots?.length || 0;
278
+ const maxPlayers = result.maxplayers || 0;
279
+ const cleanName = result.name ? utils.cleanName(result.name) : "未知服务器";
280
+ const now = (/* @__PURE__ */ new Date()).toLocaleString("zh-CN");
281
+ let playersHTML = "";
423
282
  if (playerCount === 0) {
424
- baseHeight += 60;
283
+ playersHTML = `<div class="player-row" style="color: ${COLORS.textLight};">服务器当前无玩家在线</div>`;
425
284
  } else {
426
- baseHeight += 90;
427
- if (playerParams.needTwoColumns) {
428
- const rows = Math.ceil(Math.min(playerCount, config.maxPlayers) / 2);
429
- baseHeight += rows * playerParams.rowHeight;
285
+ const sortedPlayers = [...result.players].sort(
286
+ (a, b) => utils.cleanName(a.name).localeCompare(utils.cleanName(b.name))
287
+ );
288
+ const displayPlayers = sortedPlayers.slice(0, config.maxPlayers);
289
+ const needTwoColumns = playerCount > 10;
290
+ if (needTwoColumns) {
291
+ const half = Math.ceil(displayPlayers.length / 2);
292
+ const left = displayPlayers.slice(0, half);
293
+ const right = displayPlayers.slice(half, half * 2);
294
+ playersHTML = '<div style="display: flex; gap: 40px;">';
295
+ playersHTML += "<div>" + left.map(
296
+ (p) => `<div class="player-row">${utils.truncateText(utils.cleanName(p.name), 30)}</div>`
297
+ ).join("") + "</div>";
298
+ playersHTML += "<div>" + right.map(
299
+ (p) => `<div class="player-row">${utils.truncateText(utils.cleanName(p.name), 30)}</div>`
300
+ ).join("") + "</div>";
301
+ playersHTML += "</div>";
430
302
  } else {
431
- const rows = Math.min(playerCount, config.maxPlayers);
432
- baseHeight += rows * playerParams.rowHeight;
303
+ playersHTML = displayPlayers.map(
304
+ (p) => `<div class="player-row">${utils.truncateText(utils.cleanName(p.name), 40)}</div>`
305
+ ).join("");
433
306
  }
434
307
  if (playerCount > config.maxPlayers) {
435
- baseHeight += 40;
308
+ playersHTML += `<div class="player-row" style="color: ${COLORS.textLight}; font-style: italic;">... 还有 ${playerCount - config.maxPlayers} 位玩家未显示</div>`;
436
309
  }
437
310
  }
438
- if (config.showPassword && result.password !== void 0) {
439
- baseHeight += 35;
311
+ return `<!DOCTYPE html>
312
+ <html>
313
+ <head>
314
+ <meta charset="UTF-8">
315
+ <style>
316
+ * { margin: 0; padding: 0; box-sizing: border-box; }
317
+ body {
318
+ background: rgba(28,28,31,0.8);
319
+ font-family: ${config.fontFamily};
320
+ width: ${config.imageWidth}px;
321
+ min-height: ${config.imageHeight}px;
322
+ padding: 40px;
323
+ color: ${COLORS.text};
324
+ position: relative;
325
+ border: 2px solid ${COLORS.border};
440
326
  }
441
- if (config.showVAC && result.raw?.secure !== void 0) {
442
- baseHeight += 35;
327
+ /* 边框装饰 */
328
+ .corner {
329
+ position: absolute;
330
+ width: 25px;
331
+ height: 25px;
332
+ border-color: ${COLORS.accent};
333
+ border-style: solid;
334
+ border-width: 0;
443
335
  }
444
- const height = Math.max(baseHeight, config.imageHeight);
445
- return Math.min(height, 2500);
446
- }
447
- __name(calculateImageHeight, "calculateImageHeight");
448
- async function generateServerImage(data, host, port) {
449
- const { result } = data;
450
- const width = config.imageWidth;
451
- const height = calculateImageHeight(data);
452
- const canvas = await ctx.canvas.createCanvas(width, height);
453
- const ctx2d = canvas.getContext("2d");
454
- imageUtils.drawBackground(ctx2d, width, height);
455
- const titleY = 80;
456
- imageUtils.drawTitle(ctx2d, "[服务器状态查询]", width / 2, titleY, config.fontSize * 1.5, config.fontFamily, COLORS.title);
457
- if (result.name) {
458
- const cleanName = utils.cleanName(result.name);
459
- const fontSize = imageUtils.calculateServerNameFontSize(ctx2d, cleanName, width - 160, config.fontSize);
460
- imageUtils.drawTitle(ctx2d, cleanName, width / 2, titleY + 50, fontSize * 1.8, config.fontFamily, COLORS.highlight);
461
- }
462
- imageUtils.drawDivider(ctx2d, 80, titleY + 80, width - 80, titleY + 80, COLORS.border, 2);
463
- let y = titleY + 120;
464
- if (result.map) {
465
- imageUtils.drawText(ctx2d, `地图: ${result.map}`, 80, y);
466
- }
467
- imageUtils.drawText(ctx2d, `IP: ${host}:${port}`, width - 80, y, { align: "right" });
468
- y += 40;
469
- const playerCount = result.players?.length || 0;
470
- const botCount = result.bots?.length || 0;
471
- const maxPlayers = result.maxplayers || 0;
472
- const playerText = `人数: ${playerCount}/${maxPlayers}${botCount > 0 ? ` (${botCount} Bot)` : ""}`;
473
- imageUtils.drawText(ctx2d, playerText, 80, y, { color: utils.getPlayerColor(playerCount) });
474
- if (result.ping) {
475
- imageUtils.drawText(ctx2d, `Ping: ${result.ping}ms`, width - 80, y, {
476
- align: "right",
477
- color: utils.getPingColor(result.ping)
478
- });
336
+ .corner-tl { top: 2px; left: 2px; border-top-width: 3px; border-left-width: 3px; }
337
+ .corner-tr { top: 2px; right: 2px; border-top-width: 3px; border-right-width: 3px; }
338
+ .corner-bl { bottom: 2px; left: 2px; border-bottom-width: 3px; border-left-width: 3px; }
339
+ .corner-br { bottom: 2px; right: 2px; border-bottom-width: 3px; border-right-width: 3px; }
340
+
341
+
342
+ .title {
343
+ text-align: center;
344
+ font-size: ${config.fontSize * 1.5}px;
345
+ color: ${COLORS.title};
346
+ margin-bottom: 20px;
479
347
  }
480
- y += 50;
481
- const playerParams = imageUtils.calculatePlayerListParams(playerCount);
482
- imageUtils.drawText(ctx2d, "在线玩家", 80, y, { color: COLORS.playerName, bold: true, fontSize: config.fontSize });
483
- y += 40;
484
- imageUtils.drawDivider(ctx2d, 80, y - 15, width - 80, y - 15, COLORS.divider, 1.5);
485
- y += 25;
486
- const playerListResult = imageUtils.drawPlayerList(ctx2d, result.players || [], y, width, height, playerParams);
487
- y = playerListResult.y;
488
- y += 30;
489
- const now = /* @__PURE__ */ new Date();
490
- imageUtils.drawText(ctx2d, `查询时间: ${now.toLocaleString("zh-CN")}`, 80, height - 20, {
491
- fontSize: config.fontSize * 0.8,
492
- color: COLORS.timestamp
493
- });
494
- imageUtils.drawBorder(ctx2d, width, height);
495
- return canvas.toBuffer("image/png");
348
+ .server-name {
349
+ text-align: center;
350
+ font-size: ${config.fontSize * 1.8}px;
351
+ font-weight: bold;
352
+ color: ${COLORS.highlight};
353
+ margin: 10px 0 20px;
354
+ word-break: break-word;
355
+ }
356
+ .divider {
357
+ height: 2px;
358
+ background: ${COLORS.border};
359
+ margin: 20px 0;
360
+ }
361
+ .info-row {
362
+ display: flex;
363
+ justify-content: space-between;
364
+ margin: 15px 0;
365
+ font-size: ${config.fontSize}px;
366
+ }
367
+ .player-section {
368
+ margin-top: 20px;
369
+ }
370
+ .player-section-title {
371
+ font-size: ${config.fontSize}px;
372
+ font-weight: bold;
373
+ color: ${COLORS.playerName};
374
+ margin-bottom: 10px;
375
+ }
376
+ .player-row {
377
+ font-size: ${config.fontSize * 0.9}px;
378
+ color: ${COLORS.textLighter};
379
+ line-height: 1.8;
380
+ }
381
+ .timestamp {
382
+ margin-top: 30px;
383
+ font-size: ${config.fontSize * 0.8}px;
384
+ color: ${COLORS.timestamp};
385
+ text-align: left;
386
+ }
387
+ </style>
388
+ </head>
389
+ <body>
390
+ <div class="corner corner-tl"></div>
391
+ <div class="corner corner-tr"></div>
392
+ <div class="corner corner-bl"></div>
393
+ <div class="corner corner-br"></div>
394
+
395
+ <div class="title">[服务器状态查询]</div>
396
+ <div class="server-name">${cleanName}</div>
397
+ <div class="divider"></div>
398
+
399
+ <div class="info-row">
400
+ <span>地图: ${result.map || "未知"}</span>
401
+ <span>IP: ${host}:${port}</span>
402
+ </div>
403
+ <div class="info-row">
404
+ <span style="color: ${utils.getPlayerColor(playerCount)};">人数: ${playerCount}/${maxPlayers}${botCount ? ` (${botCount} Bot)` : ""}</span>
405
+ <span style="color: ${utils.getPingColor(result.ping)};">Ping: ${result.ping ? result.ping + "ms" : "未知"}</span>
406
+ </div>
407
+
408
+ <div class="player-section">
409
+ <div class="player-section-title">在线玩家</div>
410
+ <div class="divider" style="margin: 5px 0 15px;"></div>
411
+ ${playersHTML}
412
+ </div>
413
+
414
+ <div class="timestamp">查询时间: ${now}</div>
415
+ </body>
416
+ </html>`;
496
417
  }
497
- __name(generateServerImage, "generateServerImage");
498
- async function generateBatchImage(results, serversToQuery, queryTime) {
418
+ __name(generateServerHTML, "generateServerHTML");
419
+ function generateBatchHTML(results, serversToQuery, queryTime) {
499
420
  const successful = results.filter((r) => r.status === "fulfilled" && r.value.success).length;
500
421
  const failed = results.length - successful;
501
- const baseHeight = 200;
502
- const serverHeight = 100;
503
- const width = config.imageWidth;
504
- const height = baseHeight + results.length * serverHeight;
505
- const canvas = await ctx.canvas.createCanvas(width, height);
506
- const ctx2d = canvas.getContext("2d");
507
- imageUtils.drawBackground(ctx2d, width, height);
508
- imageUtils.drawTitle(ctx2d, "[服务器状态批量查询]", width / 2, 100, config.fontSize * 1.8, config.fontFamily, COLORS.title);
509
- const now = /* @__PURE__ */ new Date();
510
- imageUtils.drawText(ctx2d, `查询时间: ${now.toLocaleString("zh-CN")}`, 80, 150);
511
- imageUtils.drawText(ctx2d, `耗时: ${utils.formatTime(queryTime)} 成功: ${successful}/${results.length}`, width - 80, 150, { align: "right" });
512
- imageUtils.drawDivider(ctx2d, 80, 165, width - 80, 165, COLORS.gold, 2);
513
- let y = 200;
422
+ const now = (/* @__PURE__ */ new Date()).toLocaleString("zh-CN");
423
+ let serversHTML = "";
514
424
  results.forEach((result, index) => {
515
425
  const server = serversToQuery[index];
516
- if (result.status === "fulfilled") {
517
- const { success, data, error } = result.value;
518
- if (success && data) {
519
- const serverData = data.result;
520
- const serverName = serverData.name ? utils.cleanName(serverData.name) : "未知";
521
- const playerCount = serverData.players?.length || 0;
522
- const maxPlayers = serverData.maxplayers || 0;
523
- imageUtils.drawText(ctx2d, `${index + 1}. ${serverName}`, 80, y, {
524
- color: COLORS.textWhite,
525
- bold: true,
526
- fontSize: config.fontSize * 1.1
527
- });
528
- imageUtils.drawText(ctx2d, server, 80, y + 30, {
529
- fontSize: config.fontSize * 0.8,
530
- color: COLORS.textLight
531
- });
532
- const playerText = `${playerCount}/${maxPlayers}`;
533
- const playerColor = playerCount > 0 ? COLORS.success : COLORS.error;
534
- imageUtils.drawText(ctx2d, playerText, width - 80, y, {
535
- align: "right",
536
- color: playerColor,
537
- bold: true
538
- });
539
- if (serverData.map) {
540
- imageUtils.drawText(ctx2d, `地图: ${serverData.map}`, 80, y + 60, {
541
- fontSize: config.fontSize * 0.8,
542
- color: COLORS.textLight
543
- });
544
- }
545
- if (serverData.ping) {
546
- const pingColor = utils.getPingColor(serverData.ping);
547
- imageUtils.drawText(ctx2d, `延迟: ${serverData.ping}ms`, width - 80, y + 60, {
548
- align: "right",
549
- fontSize: config.fontSize * 0.9,
550
- color: pingColor
551
- });
552
- }
553
- } else {
554
- imageUtils.drawText(ctx2d, `${index + 1}. ${server}`, 80, y, { color: COLORS.textWhite, bold: true });
555
- imageUtils.drawText(ctx2d, `❌ 查询失败: ${error}`, 200, y + 35, { color: COLORS.error });
556
- }
426
+ if (result.status === "fulfilled" && result.value.success) {
427
+ const data = result.value.data.result;
428
+ const name2 = data.name ? utils.cleanName(data.name) : "未知";
429
+ const playerCount = data.players?.length || 0;
430
+ const maxPlayers = data.maxplayers || 0;
431
+ const map = data.map || "";
432
+ const ping = data.ping || "?";
433
+ const pingColor = utils.getPingColor(ping);
434
+ const playerColor = playerCount > 0 ? COLORS.success : COLORS.error;
435
+ serversHTML += `
436
+ <div class="server-item">
437
+ <div class="server-header">
438
+ <span class="server-index">${index + 1}.</span>
439
+ <span class="server-name">${name2}</span>
440
+ <span class="server-players" style="color: ${playerColor};">${playerCount}/${maxPlayers}</span>
441
+ </div>
442
+ <div class="server-details">
443
+ <span class="server-addr">${server}</span>
444
+ <span class="server-map">地图: ${map}</span>
445
+ <span class="server-ping" style="color: ${pingColor};">延迟: ${ping}ms</span>
446
+ </div>
447
+ </div>
448
+ `;
557
449
  } else {
558
- imageUtils.drawText(ctx2d, `${index + 1}. ${server}`, 80, y, { color: COLORS.textWhite, bold: true });
559
- imageUtils.drawText(ctx2d, "❌ 查询失败", 200, y + 35, { color: COLORS.error });
560
- }
561
- if (index < results.length - 1) {
562
- imageUtils.drawDivider(ctx2d, 80, y + 70, width - 80, y + 70, COLORS.divider, 1);
450
+ const errorMsg = result.value?.error || "未知错误";
451
+ serversHTML += `
452
+ <div class="server-item error">
453
+ <div class="server-header">
454
+ <span class="server-index">${index + 1}.</span>
455
+ <span class="server-name">${server}</span>
456
+ <span class="server-status">❌ 查询失败</span>
457
+ </div>
458
+ <div class="server-details error-msg">${errorMsg}</div>
459
+ </div>
460
+ `;
563
461
  }
564
- y += 100;
565
462
  });
566
- imageUtils.drawBorder(ctx2d, width, height);
567
- return canvas.toBuffer("image/png");
463
+ return `<!DOCTYPE html>
464
+ <html>
465
+ <head>
466
+ <meta charset="UTF-8">
467
+ <style>
468
+ * { margin: 0; padding: 0; box-sizing: border-box; }
469
+ body {
470
+ background: rgba(28,28,31,0.8);
471
+ font-family: ${config.fontFamily};
472
+ width: ${config.imageWidth}px;
473
+ min-height: ${config.imageHeight}px;
474
+ padding: 40px;
475
+ color: ${COLORS.text};
476
+ position: relative;
477
+ border: 2px solid ${COLORS.border};
478
+ }
479
+ .corner {
480
+ position: absolute;
481
+ width: 25px;
482
+ height: 25px;
483
+ border-color: ${COLORS.accent};
484
+ border-style: solid;
485
+ border-width: 0;
486
+ }
487
+ .corner-tl { top: 2px; left: 2px; border-top-width: 3px; border-left-width: 3px; }
488
+ .corner-tr { top: 2px; right: 2px; border-top-width: 3px; border-right-width: 3px; }
489
+ .corner-bl { bottom: 2px; left: 2px; border-bottom-width: 3px; border-left-width: 3px; }
490
+ .corner-br { bottom: 2px; right: 2px; border-bottom-width: 3px; border-right-width: 3px; }
491
+
492
+ .title {
493
+ text-align: center;
494
+ font-size: ${config.fontSize * 1.8}px;
495
+ color: ${COLORS.title};
496
+ margin-bottom: 20px;
497
+ }
498
+ .stats {
499
+ display: flex;
500
+ justify-content: space-between;
501
+ font-size: ${config.fontSize}px;
502
+ margin-bottom: 10px;
503
+ }
504
+ .divider {
505
+ height: 2px;
506
+ background: ${COLORS.gold};
507
+ margin: 15px 0 30px;
508
+ }
509
+ .server-item {
510
+ margin-bottom: 30px;
511
+ border-bottom: 1px solid ${COLORS.divider};
512
+ padding-bottom: 20px;
513
+ }
514
+ .server-item:last-child {
515
+ border-bottom: none;
516
+ }
517
+ .server-header {
518
+ display: flex;
519
+ align-items: center;
520
+ gap: 10px;
521
+ font-size: ${config.fontSize * 1.1}px;
522
+ font-weight: bold;
523
+ color: ${COLORS.textWhite};
524
+ margin-bottom: 8px;
525
+ }
526
+ .server-index {
527
+ color: ${COLORS.accent};
528
+ }
529
+ .server-players {
530
+ margin-left: auto;
531
+ }
532
+ .server-details {
533
+ display: flex;
534
+ flex-wrap: wrap;
535
+ gap: 20px;
536
+ font-size: ${config.fontSize * 0.9}px;
537
+ color: ${COLORS.textLight};
538
+ }
539
+ .server-details span {
540
+ white-space: nowrap;
541
+ }
542
+ .error .server-name {
543
+ color: ${COLORS.error};
544
+ }
545
+ .error-msg {
546
+ color: ${COLORS.error};
547
+ font-size: ${config.fontSize}px;
548
+ }
549
+ .timestamp {
550
+ margin-top: 20px;
551
+ font-size: ${config.fontSize * 0.8}px;
552
+ color: ${COLORS.timestamp};
553
+ }
554
+ </style>
555
+ </head>
556
+ <body>
557
+ <!-- 边框装饰 -->
558
+ <div class="corner corner-tl"></div>
559
+ <div class="corner corner-tr"></div>
560
+ <div class="corner corner-bl"></div>
561
+ <div class="corner corner-br"></div>
562
+
563
+ <div class="title">[服务器状态批量查询]</div>
564
+ <div class="stats">
565
+ <span>查询时间: ${now}</span>
566
+ <span>耗时: ${utils.formatTime(queryTime)} 成功: ${successful}/${results.length}</span>
567
+ </div>
568
+ <div class="divider"></div>
569
+
570
+ ${serversHTML}
571
+
572
+ <div class="timestamp">📋 输入 \`cs <服务器地址>\` 查询单个服务器</div>
573
+ </body>
574
+ </html>`;
575
+ }
576
+ __name(generateBatchHTML, "generateBatchHTML");
577
+ async function generateServerImage(data, host, port) {
578
+ const html = generateServerHTML(data, host, port);
579
+ const page = await ctx.puppeteer.page();
580
+ try {
581
+ await page.setViewport({
582
+ width: config.imageWidth,
583
+ height: config.imageHeight,
584
+ deviceScaleFactor: 2
585
+ });
586
+ await page.setContent(html, { waitUntil: "networkidle0" });
587
+ const buffer = await page.screenshot({
588
+ fullPage: true,
589
+ type: "png"
590
+ });
591
+ return buffer;
592
+ } finally {
593
+ await page.close().catch(() => {
594
+ });
595
+ }
596
+ }
597
+ __name(generateServerImage, "generateServerImage");
598
+ async function generateBatchImage(results, serversToQuery, queryTime) {
599
+ const html = generateBatchHTML(results, serversToQuery, queryTime);
600
+ const page = await ctx.puppeteer.page();
601
+ try {
602
+ await page.setViewport({
603
+ width: config.imageWidth,
604
+ height: config.imageHeight,
605
+ deviceScaleFactor: 2
606
+ });
607
+ await page.setContent(html, { waitUntil: "networkidle0" });
608
+ const buffer = await page.screenshot({
609
+ fullPage: true,
610
+ type: "png"
611
+ });
612
+ return buffer;
613
+ } finally {
614
+ await page.close().catch(() => {
615
+ });
616
+ }
568
617
  }
569
618
  __name(generateBatchImage, "generateBatchImage");
570
619
  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) => {
@@ -584,7 +633,11 @@ function apply(ctx, config) {
584
633
  return import_koishi.h.image(imageBuffer, "image/png");
585
634
  } catch (imageError) {
586
635
  console.error("生成图片失败:", imageError);
587
- return `生成图片失败: ${imageError.message}`;
636
+ return `生成图片失败: ${imageError.message},已转为文本输出。
637
+
638
+ ${formatServerInfo(data)}
639
+
640
+ ${formatPlayers(data.result.players || [])}`;
588
641
  }
589
642
  }
590
643
  let message = formatServerInfo(data);
@@ -615,21 +668,20 @@ function apply(ctx, config) {
615
668
  ctx.command("cs.status", "检查插件状态和配置").action(async () => {
616
669
  try {
617
670
  const gamedigStatus = ctx.gamedig ? "✅ 可用" : "❌ 不可用";
618
- let canvasStatus = "❌ 不可用";
619
- if (ctx.canvas) {
671
+ let puppeteerStatus = "❌ 不可用";
672
+ if (ctx.puppeteer) {
620
673
  try {
621
- const canvas = await ctx.canvas.createCanvas(1, 1);
622
- const ctx2d = canvas.getContext("2d");
623
- canvasStatus = "✅ 可用";
624
- } catch (error) {
625
- canvasStatus = `❌ 不可用: ${error.message}`;
674
+ await ctx.puppeteer.render("<div>test</div>");
675
+ puppeteerStatus = "✅ 可用";
676
+ } catch (e) {
677
+ puppeteerStatus = `❌ 不可用: ${e.message}`;
626
678
  }
627
679
  }
628
680
  const cacheSize = cache.size;
629
681
  return `✅ CS服务器查询插件状态
630
682
  💾 缓存数量: ${cacheSize} 条
631
683
  🕹️ Gamedig插件: ${gamedigStatus}
632
- 🖼️ Canvas插件: ${canvasStatus}
684
+ 🖼️ Puppeteer插件: ${puppeteerStatus}
633
685
  ⚙️ 配置参数:
634
686
  超时时间: ${config.timeout}ms
635
687
  缓存时间: ${config.cacheTime}ms
@@ -638,14 +690,16 @@ function apply(ctx, config) {
638
690
  显示VAC状态: ${config.showVAC ? "是" : "否"}
639
691
  显示密码保护: ${config.showPassword ? "是" : "否"}
640
692
  生成图片横幅: ${config.generateImage ? "是" : "否"}
693
+ 图片宽度: ${config.imageWidth}px
641
694
  图片最小高度: ${config.imageHeight}px
642
695
  字体大小: ${config.fontSize}px
696
+ 字体: ${config.fontFamily}
643
697
 
644
698
  📝 使用: cs [地址:端口]
645
699
  📝 选项: -i 生成图片, -t 输出文本, -c 清除缓存`;
646
700
  } catch (error) {
647
701
  return `❌ 插件状态异常: ${error.message}
648
- 请确保已安装并启用 koishi-plugin-gamedig 和 koishi-plugin-canvas 插件`;
702
+ 请确保已安装并启用 koishi-plugin-gamedig 和 koishi-plugin-puppeteer`;
649
703
  }
650
704
  });
651
705
  ctx.command("cs.help", "查看帮助").action(() => {
@@ -670,7 +724,7 @@ cs.help - 显示此帮助
670
724
  1. 如果不指定端口,默认使用27015
671
725
  2. 只支持CS服务器查询
672
726
  3. 查询结果缓存${config.cacheTime}ms,使用 -c 清除缓存
673
- 4. 需要安装 koishi-plugin-gamedig 和 koishi-plugin-canvas 插件`;
727
+ 4. 需要安装 koishi-plugin-gamedig 和 koishi-plugin-puppeteer 插件`;
674
728
  });
675
729
  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) => {
676
730
  if (options.list) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-csss",
3
3
  "description": "Check the status of the CS server",
4
- "version": "2.0.0",
4
+ "version": "2.1.0",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [
@@ -21,12 +21,12 @@
21
21
  ],
22
22
  "devDependencies": {
23
23
  "koishi": "^4.18.7",
24
- "koishi-plugin-canvas": "^0.2.2",
24
+ "koishi-plugin-puppeteer": "^3.9.0",
25
25
  "koishi-plugin-gamedig": "^1.2.2"
26
26
  },
27
27
  "peerDependencies": {
28
28
  "koishi": "^4.18.7",
29
- "koishi-plugin-canvas": "^0.2.2",
29
+ "koishi-plugin-puppeteer": "^3.9.0",
30
30
  "koishi-plugin-gamedig": "^1.2.2"
31
31
  },
32
32
  "koishi": {
@@ -35,7 +35,7 @@
35
35
  },
36
36
  "service": {
37
37
  "required": [
38
- "canvas",
38
+ "puppeteer",
39
39
  "gamedig"
40
40
  ]
41
41
  }
package/readme.md CHANGED
@@ -3,3 +3,18 @@
3
3
  [![npm](https://img.shields.io/npm/v/koishi-plugin-csss?style=flat-square)](https://www.npmjs.com/package/koishi-plugin-csss)
4
4
 
5
5
  cs server status
6
+
7
+ ## 提供指令
8
+ - ```cs [地址:端口] <-i | -t | -c>```
9
+
10
+ > [ ]: 必填 < >: 选填
11
+ > -i 生成图片横幅
12
+ > -t 输出文本信息
13
+ > -c 清除缓存
14
+
15
+ - `csss`
16
+
17
+ > 选项:
18
+ > -i 生成图片横幅
19
+ > -t 输出文本信息
20
+ > -c 清除缓存
package/lib/index.d.ts DELETED
@@ -1,20 +0,0 @@
1
- import { Context, Schema } from 'koishi';
2
- export declare const name = "csss";
3
- export declare const inject: string[];
4
- export interface Config {
5
- timeout: number;
6
- cacheTime: number;
7
- maxPlayers: number;
8
- retryCount: number;
9
- showVAC: boolean;
10
- showPassword: boolean;
11
- generateImage: boolean;
12
- imageWidth: number;
13
- imageHeight: number;
14
- fontSize: number;
15
- fontFamily: string;
16
- serverList: string[];
17
- batchTimeout: number;
18
- }
19
- export declare const Config: Schema<Config>;
20
- export declare function apply(ctx: Context, config: Config): void;