koishi-plugin-cs2-server-query 1.3.2 → 1.4.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 +232 -16
  2. package/package.json +1 -1
package/lib/index.js CHANGED
@@ -84,6 +84,7 @@ function apply(ctx, config) {
84
84
  import_fs.default.writeFileSync(subscriptionsFilePath, JSON.stringify(subscriptions, null, 2));
85
85
  }
86
86
  __name(saveSubscriptions, "saveSubscriptions");
87
+ const pendingSubscriptions = /* @__PURE__ */ new Map();
87
88
  const difficultyColors = {
88
89
  "简单": "#33cc00",
89
90
  "普通": "#ffcc00",
@@ -220,6 +221,69 @@ function apply(ctx, config) {
220
221
  return screenshot;
221
222
  }
222
223
  __name(generateServerImage, "generateServerImage");
224
+ async function generatePushImage(server, mapTranslations2) {
225
+ const browser = await import_puppeteer.default.launch({ args: ["--no-sandbox", "--disable-setuid-sandbox"] });
226
+ const page = await browser.newPage();
227
+ const html = `
228
+ <!DOCTYPE html>
229
+ <html>
230
+ <head>
231
+ <style>
232
+ body {
233
+ margin: 0;
234
+ padding: 20px;
235
+ background: url('${config.backgroundImage}') no-repeat center center fixed;
236
+ background-size: cover;
237
+ height: 100vh;
238
+ position: relative;
239
+ }
240
+ .overlay {
241
+ background: rgba(0, 0, 0, 0.7);
242
+ border-radius: 10px;
243
+ padding: 20px;
244
+ color: white;
245
+ font-family: Arial, sans-serif;
246
+ text-align: center;
247
+ }
248
+ h2 {
249
+ color: #fff;
250
+ margin: 0 0 15px 0;
251
+ border-bottom: 2px solid #fff;
252
+ padding-bottom: 10px;
253
+ text-align: center;
254
+ }
255
+ .server-info {
256
+ text-align: center;
257
+ margin-bottom: 20px;
258
+ }
259
+ .map-name {
260
+ color: #00ff9d;
261
+ }
262
+ </style>
263
+ </head>
264
+ <body>
265
+ <div class="overlay">
266
+ <h2>您订阅的地图已出现</h2>
267
+ <div class="server-info">
268
+ <p>服务器名称: ${server.name}</p>
269
+ <p>地图: <span class="map-name">${server.map}</span> (${mapTranslations2[server.map]?.translation || "无翻译"})</p>
270
+ <p>当前人数: ${server.players}/${server.maxPlayers}</p>
271
+ </div>
272
+ </div>
273
+ </body>
274
+ </html>
275
+ `;
276
+ await page.setContent(html);
277
+ await page.setViewport({ width: 1280, height: 300 });
278
+ const screenshot = await page.screenshot({
279
+ type: "png",
280
+ fullPage: true,
281
+ encoding: "binary"
282
+ });
283
+ await browser.close();
284
+ return screenshot;
285
+ }
286
+ __name(generatePushImage, "generatePushImage");
223
287
  async function handleOfflineServers(groups2) {
224
288
  for (const group of groups2) {
225
289
  for (const server of group.servers) {
@@ -268,7 +332,8 @@ function apply(ctx, config) {
268
332
  return "无效的服务器编号";
269
333
  }
270
334
  const server = group.servers[serverIndex - 1];
271
- return `connect ${server.ip}`;
335
+ return `服务器名称: ${server.name}
336
+ connect ${server.ip}`;
272
337
  } else {
273
338
  try {
274
339
  const imageBuffer = await generateServerImage(group, mapTranslations);
@@ -404,30 +469,146 @@ function apply(ctx, config) {
404
469
  if (Object.keys(commandMappings).length === 0) return "未找到任何指令映射。";
405
470
  return `指令映射列表:${Object.entries(commandMappings).map(([command, groupName]) => `${command} -> ${groupName}`).join(", ")}`;
406
471
  });
407
- ctx.command("cs2订阅 <groupName> <mapName> [pushType]", "订阅指定社区服务器的地图").option("all", "-a 订阅所有社区服务器").action(({ session, options }, groupName, mapName, pushType = "私聊") => {
472
+ function fuzzyMatchMap(input) {
473
+ const lowerInput = input.toLowerCase();
474
+ const maps = Object.keys(mapTranslations);
475
+ return maps.filter((map) => {
476
+ let inputIndex = 0;
477
+ const lowerMap = map.toLowerCase();
478
+ for (const char of lowerMap) {
479
+ if (char === lowerInput[inputIndex]) {
480
+ inputIndex++;
481
+ if (inputIndex === lowerInput.length) break;
482
+ }
483
+ }
484
+ return inputIndex === lowerInput.length;
485
+ });
486
+ }
487
+ __name(fuzzyMatchMap, "fuzzyMatchMap");
488
+ ctx.middleware(async (session, next) => {
489
+ const pendingSub = pendingSubscriptions.get(session.userId);
490
+ if (pendingSub) {
491
+ if (Date.now() - pendingSub.timestamp > 3e4) {
492
+ pendingSubscriptions.delete(session.userId);
493
+ return next();
494
+ }
495
+ const input = session.content.trim();
496
+ const index = parseInt(input) - 1;
497
+ if (isNaN(index) || index < 0 || index >= pendingSub.maps.length) {
498
+ session.send("无效的选择,请回复正确的数字。");
499
+ return;
500
+ }
501
+ const selectedMap = pendingSub.maps[index];
502
+ const userId = session.userId;
503
+ const channelId = session.channelId;
504
+ if (pendingSub.isAll) {
505
+ groups.forEach((group) => {
506
+ const existing = subscriptions.find(
507
+ (sub) => sub.userId === userId && sub.groupName === group.name && sub.mapName === selectedMap
508
+ );
509
+ if (!existing) {
510
+ subscriptions.push({
511
+ userId,
512
+ groupName: group.name,
513
+ mapName: selectedMap,
514
+ pushType: pendingSub.pushType,
515
+ channelId
516
+ });
517
+ }
518
+ });
519
+ saveSubscriptions();
520
+ session.send(`已订阅所有社区的地图 "${selectedMap}",推送类型为 ${pendingSub.pushType}。`);
521
+ } else {
522
+ const group = groups.find((g) => g.name === pendingSub.groupName);
523
+ if (!group) {
524
+ session.send("社区不存在,订阅失败。");
525
+ pendingSubscriptions.delete(session.userId);
526
+ return;
527
+ }
528
+ const existing = subscriptions.find(
529
+ (sub) => sub.userId === userId && sub.groupName === pendingSub.groupName && sub.mapName === selectedMap
530
+ );
531
+ if (!existing) {
532
+ subscriptions.push({
533
+ userId,
534
+ groupName: pendingSub.groupName,
535
+ mapName: selectedMap,
536
+ pushType: pendingSub.pushType,
537
+ channelId
538
+ });
539
+ saveSubscriptions();
540
+ session.send(`已成功订阅社区 "${pendingSub.groupName}" 的地图 "${selectedMap}",推送类型为 ${pendingSub.pushType}。`);
541
+ } else {
542
+ session.send(`您已经订阅了社区 "${pendingSub.groupName}" 的地图 "${selectedMap}"。`);
543
+ }
544
+ }
545
+ pendingSubscriptions.delete(session.userId);
546
+ return;
547
+ }
548
+ return next();
549
+ });
550
+ ctx.command("cs2订阅 <groupName> <mapInput> [pushType]", "订阅指定社区服务器的地图").option("all", "-a 订阅所有社区服务器").action(async ({ session, options }, groupName, mapInput, pushType = "私聊") => {
408
551
  const userId = session.userId;
409
552
  const channelId = session.channelId;
410
- const pushTypeValid = pushType === "私聊" || pushType === "群聊";
411
- if (!pushTypeValid) return "推送类型无效,请选择 私聊 或 群聊。";
553
+ if (pushType !== "私聊" && pushType !== "群聊") {
554
+ return '推送类型无效,请选择 "私聊" 或 "群聊"。';
555
+ }
556
+ if (!groupName || !mapInput) return "参数不足,请提供社区名和地图名。";
557
+ const matchedMaps = fuzzyMatchMap(mapInput);
558
+ if (matchedMaps.length === 0) return "未找到匹配的地图。";
559
+ if (matchedMaps.length > 1) {
560
+ pendingSubscriptions.set(userId, {
561
+ groupName,
562
+ pushType,
563
+ // 这里已经是类型安全的
564
+ maps: matchedMaps,
565
+ timestamp: Date.now(),
566
+ isAll: options.all
567
+ });
568
+ const mapList = matchedMaps.map((m, i) => `${i + 1}. ${m} (${mapTranslations[m]?.translation || "无翻译"})`).join("\n");
569
+ return `找到多个匹配的地图,请回复数字选择:
570
+ ${mapList}
571
+ (请在30秒内回复)`;
572
+ }
573
+ const mapName = matchedMaps[0];
412
574
  if (options.all) {
413
575
  groups.forEach((group) => {
414
- const existingSubscription = subscriptions.find((sub) => sub.userId === userId && sub.groupName === group.name && sub.mapName === mapName);
415
- if (!existingSubscription) {
416
- subscriptions.push({ userId, groupName: group.name, mapName, pushType, channelId });
576
+ const existing = subscriptions.find(
577
+ (sub) => sub.userId === userId && sub.groupName === group.name && sub.mapName === mapName
578
+ );
579
+ if (!existing) {
580
+ subscriptions.push({
581
+ userId,
582
+ groupName: group.name,
583
+ mapName,
584
+ pushType,
585
+ // 这里已经是类型安全的
586
+ channelId
587
+ });
417
588
  }
418
589
  });
419
590
  saveSubscriptions();
420
591
  return `已订阅所有社区服务器的地图 "${mapName}",推送类型为 ${pushType}。`;
421
592
  } else {
422
- const group = groups.find((group2) => group2.name === groupName);
593
+ const group = groups.find((g) => g.name === groupName);
423
594
  if (!group) return "未找到该社区。";
424
- const existingSubscription = subscriptions.find((sub) => sub.userId === userId && sub.groupName === groupName && sub.mapName === mapName);
425
- if (existingSubscription) return `您已经订阅了社区 "${groupName}" 的地图 "${mapName}"。`;
595
+ const existing = subscriptions.find(
596
+ (sub) => sub.userId === userId && sub.groupName === groupName && sub.mapName === mapName
597
+ );
598
+ if (existing) return `您已经订阅了社区 "${groupName}" 的地图 "${mapName}"。`;
426
599
  subscriptions.push({ userId, groupName, mapName, pushType, channelId });
427
600
  saveSubscriptions();
428
601
  return `已订阅社区 "${groupName}" 的地图 "${mapName}",推送类型为 ${pushType}。`;
429
602
  }
430
603
  });
604
+ ctx.setInterval(() => {
605
+ const now = Date.now();
606
+ for (const [userId, sub] of pendingSubscriptions.entries()) {
607
+ if (now - sub.timestamp > 3e4) {
608
+ pendingSubscriptions.delete(userId);
609
+ }
610
+ }
611
+ }, 6e4);
431
612
  ctx.command("cs2取消订阅 <groupName> <mapName>", "取消订阅指定社区服务器的地图").option("all", "-a 取消订阅所有社区服务器").action(({ session, options }, groupName, mapName) => {
432
613
  const userId = session.userId;
433
614
  if (options.all) {
@@ -466,6 +647,30 @@ ${subscriptionList}`;
466
647
  }
467
648
  }
468
649
  __name(sendMessageToTarget, "sendMessageToTarget");
650
+ function levenshtein(a, b) {
651
+ const m = a.length;
652
+ const n = b.length;
653
+ const dp = Array.from({ length: m + 1 }, (_, i) => Array(n + 1).fill(0));
654
+ for (let i = 0; i <= m; i++) dp[i][0] = i;
655
+ for (let j = 0; j <= n; j++) dp[0][j] = j;
656
+ for (let i = 1; i <= m; i++) {
657
+ for (let j = 1; j <= n; j++) {
658
+ if (a[i - 1] === b[j - 1]) {
659
+ dp[i][j] = dp[i - 1][j - 1];
660
+ } else {
661
+ dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + 1);
662
+ }
663
+ }
664
+ }
665
+ return dp[m][n];
666
+ }
667
+ __name(levenshtein, "levenshtein");
668
+ function similarity(a, b) {
669
+ const distance = levenshtein(a, b);
670
+ const maxLength = Math.max(a.length, b.length);
671
+ return maxLength === 0 ? 100 : (1 - distance / maxLength) * 100;
672
+ }
673
+ __name(similarity, "similarity");
469
674
  ctx.setInterval(async () => {
470
675
  for (const group of groups) {
471
676
  for (const server of group.servers) {
@@ -474,12 +679,23 @@ ${subscriptionList}`;
474
679
  const newMap = info.map;
475
680
  if (server.map !== newMap) {
476
681
  server.map = newMap;
477
- const relevantSubscriptions = subscriptions.filter((sub) => sub.groupName === group.name && (sub.mapName === newMap || sub.mapName === mapTranslations[newMap]?.translation));
682
+ const relevantSubscriptions = subscriptions.filter((sub) => sub.groupName === group.name);
478
683
  for (const sub of relevantSubscriptions) {
479
- const message = `您订阅的地图已出现,服务器 "${server.name}" 已更换地图为 "${newMap}" (${mapTranslations[newMap]?.translation || "无翻译"}),当前玩家数: ${server.players}/${server.maxPlayers}
480
- connect ${server.ip}`;
481
- const targetId = sub.pushType === "私聊" ? sub.userId : sub.channelId;
482
- await sendMessageToTarget(ctx, targetId, sub.pushType, message);
684
+ const mapNameToCompare = sub.mapName;
685
+ const translatedMapName = mapTranslations[mapNameToCompare]?.translation;
686
+ let matchPercentage = 0;
687
+ if (translatedMapName) {
688
+ matchPercentage = similarity(newMap, mapNameToCompare);
689
+ } else {
690
+ matchPercentage = similarity(newMap, mapNameToCompare);
691
+ }
692
+ if (matchPercentage >= 80) {
693
+ const imageBuffer = await generatePushImage(server, mapTranslations);
694
+ const message = `connect ${server.ip}`;
695
+ const targetId = sub.pushType === "私聊" ? sub.userId : sub.channelId;
696
+ await sendMessageToTarget(ctx, targetId, sub.pushType, import_koishi.h.image(imageBuffer, "image/png"));
697
+ await sendMessageToTarget(ctx, targetId, sub.pushType, message);
698
+ }
483
699
  }
484
700
  }
485
701
  } catch (err) {
@@ -489,7 +705,7 @@ connect ${server.ip}`;
489
705
  }
490
706
  await handleOfflineServers(groups);
491
707
  saveData();
492
- }, 3e4);
708
+ }, 6e4);
493
709
  }
494
710
  __name(apply, "apply");
495
711
  // Annotate the CommonJS export names for ESM import in node:
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-cs2-server-query",
3
3
  "description": "自用,不推荐下载",
4
- "version": "1.3.2",
4
+ "version": "1.4.0",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [