koishi-plugin-cs2-server-query 2.0.0 → 2.2.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 +280 -52
  2. package/package.json +1 -1
package/lib/index.js CHANGED
@@ -302,7 +302,7 @@ function apply(ctx, config) {
302
302
  const recentCount = (mapStats[group.name]?.[server.map] || []).filter(
303
303
  (ts) => Date.now() - ts <= 7 * 24 * 60 * 60 * 1e3
304
304
  ).length;
305
- const isHot = recentCount >= 20;
305
+ const isHot = recentCount >= 15;
306
306
  return `
307
307
 
308
308
  <tr>
@@ -419,17 +419,50 @@ function apply(ctx, config) {
419
419
  return screenshot;
420
420
  }
421
421
  __name(generatePushImage, "generatePushImage");
422
+ async function processMapChange(group, server, newMap) {
423
+ checkAndAddMapTranslation(newMap, mapTranslations);
424
+ if (!mapStats[group.name]) mapStats[group.name] = {};
425
+ if (!mapStats[group.name][newMap]) mapStats[group.name][newMap] = [];
426
+ mapStats[group.name][newMap].push(Date.now());
427
+ saveMapStats();
428
+ const relevantSubs = subscriptions.filter(
429
+ (sub) => sub.groupName === group.name && sub.mapName === newMap && canPush(server.ip, newMap)
430
+ );
431
+ for (const sub of relevantSubs) {
432
+ const image = await generatePushImage(server, mapTranslations);
433
+ const targetId = sub.pushType === "私聊" ? sub.userId : sub.channelId;
434
+ const message = [
435
+ import_koishi.h.image(image, "image/png"),
436
+ `connect ${server.ip}`
437
+ ];
438
+ await sendMessageToTarget(
439
+ ctx,
440
+ targetId,
441
+ sub.pushType,
442
+ message,
443
+ sub.userId
444
+ );
445
+ updatePushRecord(server.ip, newMap);
446
+ }
447
+ }
448
+ __name(processMapChange, "processMapChange");
422
449
  async function handleOfflineServers(groups2) {
423
450
  for (const group of groups2) {
424
451
  for (const server of group.servers) {
425
452
  if (server.status === "离线") {
426
453
  try {
427
454
  const info = await (0, import_steam_server_query.queryGameServerInfo)(server.ip);
428
- server.name = info.name || `服务器 ${server.ip}`;
429
- server.map = info.map;
455
+ const oldMap = server.map;
456
+ const newMap = info.map;
457
+ server.name = info.name || server.name;
458
+ server.map = newMap;
430
459
  server.players = info.players;
431
460
  server.maxPlayers = info.maxPlayers;
432
461
  server.status = "在线";
462
+ if (oldMap !== newMap) {
463
+ server.mapChangeTime = Date.now();
464
+ await processMapChange(group, server, newMap);
465
+ }
433
466
  } catch (err) {
434
467
  server.status = "离线";
435
468
  }
@@ -749,14 +782,17 @@ ${mapList}
749
782
  }
750
783
  }
751
784
  }, 6e4);
752
- ctx.command("cs2取消订阅 <groupName> <mapInput>", "取消订阅指定社区服务器的地图,需输入以下指定社区名称:EXG 风云社 僵尸乐园 UB社区 ZE国际服 X社区").action(async ({ session }, groupName, mapInput) => {
785
+ ctx.command("cs2取消订阅 <groupName> <mapInput>", "取消订阅指定社区服务器的地图,需输入以下指定社区名称:EXG 风云社 僵尸乐园 UB社区 ZE国际服 X社区,例:cs2取消订阅 EXG 狮子王 -a").option("all", "-a 取消该地图的所有社区订阅").action(async ({ session, options }, groupName, mapInput) => {
753
786
  const userId = session.userId;
754
- if (!groupName || !mapInput) return "参数不足,请提供社区名和地图名。";
787
+ if (options.all) groupName = null;
788
+ if (!options.all && !groupName) return "参数不足,请提供以下指定社区名称:EXG 风云社 僵尸乐园 UB社区 ZE国际服 X社区,以及地图名,末端可使用 -a 取消所有社区订阅。例:cs2取消订阅 EXG 狮子王 -a";
789
+ if (!mapInput) return "参数不足,请提供以下指定社区名称:EXG 风云社 僵尸乐园 UB社区 ZE国际服 X社区,以及地图名,末端可使用 -a 取消所有社区订阅。例:cs2取消订阅 EXG 狮子王 -a";
755
790
  const matchedMaps = fuzzyMatchMap(mapInput);
756
791
  if (matchedMaps.length === 0) return "未找到匹配的地图。";
757
792
  if (matchedMaps.length > 1) {
758
793
  pendingUnsubscriptions.set(userId, {
759
- groupName,
794
+ groupName: options.all ? "__ALL__" : groupName,
795
+ // 使用特殊标记表示所有社区
760
796
  maps: matchedMaps,
761
797
  timestamp: Date.now()
762
798
  });
@@ -768,7 +804,7 @@ ${mapList}
768
804
  (请在30秒内回复)`;
769
805
  }
770
806
  const mapName = matchedMaps[0];
771
- return handleUnsubscription(userId, groupName, mapName);
807
+ return handleUnsubscription(userId, options.all ? "__ALL__" : groupName, mapName);
772
808
  });
773
809
  ctx.middleware(async (session, next) => {
774
810
  const pendingUnsub = pendingUnsubscriptions.get(session.userId);
@@ -789,16 +825,18 @@ ${mapList}
789
825
  }
790
826
  return next();
791
827
  });
792
- function handleUnsubscription(userId, groupName, mapName) {
828
+ function handleUnsubscription(userId, groupIdentifier, mapName) {
793
829
  const initialLength = subscriptions.length;
794
- subscriptions = subscriptions.filter(
795
- (sub) => !(sub.userId === userId && sub.groupName === groupName && sub.mapName === mapName)
796
- );
830
+ subscriptions = subscriptions.filter((sub) => {
831
+ const isTargetMap = sub.mapName === mapName;
832
+ const isTargetGroup = groupIdentifier === "__ALL__" ? true : sub.groupName === groupIdentifier;
833
+ return !(sub.userId === userId && isTargetMap && isTargetGroup);
834
+ });
797
835
  if (subscriptions.length === initialLength) {
798
- return `未找到社区 "${groupName}" 中关于地图 "${mapName}" 的订阅。`;
836
+ return groupIdentifier === "__ALL__" ? `未找到关于地图 "${mapName}" 的任何订阅。` : `未找到社区 "${groupIdentifier}" 中关于地图 "${mapName}" 的订阅。`;
799
837
  }
800
838
  saveSubscriptions();
801
- return `已成功取消订阅社区 "${groupName}" 的地图 "${mapName}"。`;
839
+ return groupIdentifier === "__ALL__" ? `已成功取消所有社区中地图 "${mapName}" 的订阅。` : `已成功取消社区 "${groupIdentifier}" 中地图 "${mapName}" 的订阅。`;
802
840
  }
803
841
  __name(handleUnsubscription, "handleUnsubscription");
804
842
  ctx.setInterval(() => {
@@ -809,18 +847,235 @@ ${mapList}
809
847
  }
810
848
  }
811
849
  }, 6e4);
812
- ctx.command("cs2我的订阅", "列出当前用户订阅的地图").action(({ session }) => {
850
+ async function generateSubscriptionImage(subscriptions2, mapTranslations2) {
851
+ const browser = await import_puppeteer.default.launch({ args: ["--no-sandbox", "--disable-setuid-sandbox"] });
852
+ const page = await browser.newPage();
853
+ const difficultyColors2 = {
854
+ "未知": "#3C3C3C",
855
+ "简单": "#33cc00",
856
+ "普通": "#ffcc00",
857
+ "困难": "#cc6600",
858
+ "高难": "#cc6699",
859
+ "极难": "#cc0099",
860
+ "史诗": "#ff33cc",
861
+ "梦魇": "#cc3300",
862
+ "绝境": "#8b1c62"
863
+ };
864
+ const generateItemsHTML = /* @__PURE__ */ __name(() => {
865
+ return subscriptions2.map((sub, index) => {
866
+ const translation = mapTranslations2[sub.mapName] || { translation: "未翻译", difficulty: "未知" };
867
+ const diffColor = difficultyColors2[translation.difficulty] || "#3C3C3C";
868
+ return `
869
+ <div class="subscription-item">
870
+ <div class="item-header">
871
+ <span class="item-index">#${index + 1}</span>
872
+ <span class="community-name">${sub.groupName}</span>
873
+ <span class="push-type ${sub.pushType === "私聊" ? "private" : "group"}">
874
+ ${sub.pushType}
875
+ </span>
876
+ </div>
877
+ <div class="item-body">
878
+ <div class="map-name">
879
+ <span class="en-name">${sub.mapName}</span>
880
+ <span class="cn-name">${translation.translation}</span>
881
+ </div>
882
+ <div class="meta-info">
883
+ <span class="difficulty-tag" style="background: ${diffColor}">
884
+ ${translation.difficulty}
885
+ </span>
886
+ </div>
887
+ </div>
888
+ </div>
889
+ `;
890
+ }).join("");
891
+ }, "generateItemsHTML");
892
+ const generateStatsHTML = /* @__PURE__ */ __name(() => {
893
+ const stats = {};
894
+ subscriptions2.forEach((sub) => {
895
+ const diff = mapTranslations2[sub.mapName]?.difficulty || "未知";
896
+ stats[diff] = (stats[diff] || 0) + 1;
897
+ });
898
+ return Object.entries(stats).map(([diff, count]) => `
899
+ <div class="stat-item">
900
+ <span class="diff-tag" style="background: ${difficultyColors2[diff]}">${diff}</span>
901
+ <span class="diff-count">${count} 个订阅</span>
902
+ </div>
903
+ `).join("");
904
+ }, "generateStatsHTML");
905
+ const html = `
906
+ <!DOCTYPE html>
907
+ <html>
908
+ <head>
909
+ <style>
910
+ body {
911
+ margin: 0;
912
+ padding: 40px;
913
+ background: url('${config.pushBackgroundImage}') no-repeat center center/cover;
914
+ font-family: 'Microsoft YaHei', sans-serif;
915
+ }
916
+ .container {
917
+ background: rgba(0, 0, 0, 0.85);
918
+ border-radius: 15px;
919
+ padding: 30px;
920
+ backdrop-filter: blur(8px);
921
+ box-shadow: 0 0 30px rgba(0,0,0,0.5);
922
+ }
923
+ .title {
924
+ color: #fff;
925
+ font-size: 28px;
926
+ margin-bottom: 25px;
927
+ text-align: center;
928
+ text-shadow: 0 2px 4px rgba(0,0,0,0.5);
929
+ }
930
+ .subscription-list {
931
+ display: grid;
932
+ gap: 20px;
933
+ }
934
+ .subscription-item {
935
+ background: rgba(255,255,255,0.1);
936
+ border-radius: 12px;
937
+ padding: 20px;
938
+ transition: transform 0.2s;
939
+ }
940
+ .item-header {
941
+ display: flex;
942
+ align-items: center;
943
+ margin-bottom: 15px;
944
+ border-bottom: 1px solid rgba(255,255,255,0.1);
945
+ padding-bottom: 12px;
946
+ }
947
+ .item-index {
948
+ font-size: 18px;
949
+ color: #FFD700;
950
+ margin-right: 15px;
951
+ }
952
+ .community-name {
953
+ font-size: 20px;
954
+ color: #00ff9d;
955
+ flex-grow: 1;
956
+ }
957
+ .push-type {
958
+ padding: 4px 12px;
959
+ border-radius: 6px;
960
+ font-size: 14px;
961
+ }
962
+ .push-type.private {
963
+ background: rgba(0, 255, 157, 0.15);
964
+ color: #00ff9d;
965
+ }
966
+ .push-type.group {
967
+ background: rgba(255, 215, 0, 0.15);
968
+ color: #FFD700;
969
+ }
970
+ .map-name {
971
+ margin-bottom: 12px;
972
+ }
973
+ .en-name {
974
+ font-size: 18px;
975
+ color: #fff;
976
+ display: block;
977
+ margin-bottom: 5px;
978
+ }
979
+ .cn-name {
980
+ font-size: 16px;
981
+ color: #aaa;
982
+ }
983
+ .meta-info {
984
+ display: flex;
985
+ gap: 15px;
986
+ align-items: center;
987
+ }
988
+ .difficulty-tag {
989
+ padding: 4px 12px;
990
+ border-radius: 6px;
991
+ font-size: 14px;
992
+ }
993
+ .channel-id {
994
+ color: #888;
995
+ font-size: 14px;
996
+ }
997
+ .stats-section {
998
+ margin-top: 30px;
999
+ padding-top: 25px;
1000
+ border-top: 1px solid rgba(255,255,255,0.1);
1001
+ }
1002
+ .stats-title {
1003
+ color: #fff;
1004
+ font-size: 20px;
1005
+ margin-bottom: 15px;
1006
+ }
1007
+ .stat-item {
1008
+ display: flex;
1009
+ align-items: center;
1010
+ gap: 10px;
1011
+ margin-bottom: 10px;
1012
+ }
1013
+ .diff-tag {
1014
+ padding: 4px 12px;
1015
+ border-radius: 6px;
1016
+ font-size: 14px;
1017
+ min-width: 60px;
1018
+ text-align: center;
1019
+ }
1020
+ .diff-count {
1021
+ color: #ddd;
1022
+ font-size: 14px;
1023
+ }
1024
+ </style>
1025
+ </head>
1026
+ <body>
1027
+ <div class="container">
1028
+ <h1 class="title">我的 CS2 地图订阅(共 ${subscriptions2.length} 个)</h1>
1029
+
1030
+ <div class="subscription-list">
1031
+ ${generateItemsHTML()}
1032
+ </div>
1033
+
1034
+ <div class="stats-section">
1035
+ <h2 class="stats-title">难度分布统计</h2>
1036
+ ${generateStatsHTML()}
1037
+ </div>
1038
+ </div>
1039
+ </body>
1040
+ </html>
1041
+ `;
1042
+ await page.setContent(html);
1043
+ await page.setViewport({ width: 800, height: 600 + subscriptions2.length * 120 });
1044
+ const screenshot = await page.screenshot({
1045
+ type: "png",
1046
+ fullPage: true,
1047
+ encoding: "binary"
1048
+ });
1049
+ await browser.close();
1050
+ return screenshot;
1051
+ }
1052
+ __name(generateSubscriptionImage, "generateSubscriptionImage");
1053
+ ctx.command("cs2我的订阅", "显示当前用户的订阅列表(图片版)").action(async ({ session }) => {
813
1054
  const userId = session.userId;
814
1055
  const userSubscriptions = subscriptions.filter((sub) => sub.userId === userId);
815
1056
  if (userSubscriptions.length === 0) {
816
- return "您当前没有订阅任何地图。";
1057
+ return "🎮 您当前没有活跃的地图订阅。";
1058
+ }
1059
+ try {
1060
+ const imageBuffer = await generateSubscriptionImage(userSubscriptions, mapTranslations);
1061
+ return [
1062
+ import_koishi.h.image(imageBuffer, "image/png"),
1063
+ '\n💡 提示:使用 "cs2取消订阅 社区名 地图名" 可取消订阅'
1064
+ ];
1065
+ } catch (err) {
1066
+ ctx.logger("cs2-server-query").error("生成订阅图片失败:", err);
1067
+ return "生成订阅列表失败,请稍后重试。";
817
1068
  }
818
- const subscriptionList = userSubscriptions.map((sub) => {
819
- return `社区: ${sub.groupName}, 地图: ${sub.mapName}, 推送类型: ${sub.pushType}`;
820
- }).join("\n");
821
- return `您当前订阅的地图列表:
822
- ${subscriptionList}`;
823
1069
  });
1070
+ function calculateDifficultyStats(subs) {
1071
+ const stats = {};
1072
+ subs.forEach((sub) => {
1073
+ const difficulty = mapTranslations[sub.mapName]?.difficulty || "未标记";
1074
+ stats[difficulty] = (stats[difficulty] || 0) + 1;
1075
+ });
1076
+ return "🎚️ 难度分布:\n" + Object.entries(stats).map(([diff, count]) => `▫️ ${diff.padEnd(6)}:${count} 个`).join("\n");
1077
+ }
1078
+ __name(calculateDifficultyStats, "calculateDifficultyStats");
824
1079
  async function sendMessageToTarget(ctx2, targetId, pushType, message, userId) {
825
1080
  try {
826
1081
  if (pushType === "群聊" && userId) {
@@ -853,42 +1108,15 @@ ${subscriptionList}`;
853
1108
  try {
854
1109
  const info = await (0, import_steam_server_query.queryGameServerInfo)(server.ip);
855
1110
  const newMap = info.map;
1111
+ const oldMap = server.map;
856
1112
  if (server.map !== newMap) {
857
1113
  server.mapChangeTime = Date.now();
858
1114
  server.map = newMap;
859
- checkAndAddMapTranslation(newMap, mapTranslations);
860
- const relevantSubscriptions = subscriptions.filter((sub) => sub.groupName === group.name);
861
- for (const sub of relevantSubscriptions) {
862
- const mapNameToCompare = sub.mapName;
863
- const translatedMapName = mapTranslations[mapNameToCompare]?.translation;
864
- let matchPercentage = 0;
865
- if (server.map !== newMap) {
866
- server.mapChangeTime = Date.now();
867
- server.map = newMap;
868
- if (!mapStats[group.name]) {
869
- mapStats[group.name] = {};
870
- }
871
- if (!mapStats[group.name][newMap]) {
872
- mapStats[group.name][newMap] = [];
873
- }
874
- mapStats[group.name][newMap].push(Date.now());
875
- saveMapStats();
876
- }
877
- if (newMap === mapNameToCompare && canPush(server.ip, mapNameToCompare)) {
878
- const imageBuffer = await generatePushImage(server, mapTranslations);
879
- const message = `connect ${server.ip}`;
880
- const targetId = sub.pushType === "私聊" ? sub.userId : sub.channelId;
881
- const combinedMessage = [
882
- import_koishi.h.image(imageBuffer, "image/png"),
883
- // 图片
884
- message
885
- // 文字
886
- ];
887
- await sendMessageToTarget(ctx, targetId, sub.pushType, combinedMessage, sub.userId);
888
- updatePushRecord(server.ip, mapNameToCompare);
889
- }
890
- }
1115
+ await processMapChange(group, server, newMap);
891
1116
  }
1117
+ server.status = "在线";
1118
+ server.players = info.players;
1119
+ server.maxPlayers = info.maxPlayers;
892
1120
  } catch (err) {
893
1121
  server.status = "离线";
894
1122
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-cs2-server-query",
3
3
  "description": "自用,不推荐下载",
4
- "version": "2.0.0",
4
+ "version": "2.2.0",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [