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.
- package/lib/index.js +280 -52
- 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 >=
|
|
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
|
-
|
|
429
|
-
|
|
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
|
|
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 (
|
|
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,
|
|
828
|
+
function handleUnsubscription(userId, groupIdentifier, mapName) {
|
|
793
829
|
const initialLength = subscriptions.length;
|
|
794
|
-
subscriptions = subscriptions.filter(
|
|
795
|
-
|
|
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 `未找到社区 "${
|
|
836
|
+
return groupIdentifier === "__ALL__" ? `未找到关于地图 "${mapName}" 的任何订阅。` : `未找到社区 "${groupIdentifier}" 中关于地图 "${mapName}" 的订阅。`;
|
|
799
837
|
}
|
|
800
838
|
saveSubscriptions();
|
|
801
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|