@sjtdev/koishi-plugin-dota2tracker 1.1.2 → 1.1.4
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 +180 -58
- package/package.json +3 -2
- package/readme.md +5 -4
- package/template/player/player_1.ejs +17 -22
package/lib/index.js
CHANGED
|
@@ -50,6 +50,7 @@ __export(utils_exports, {
|
|
|
50
50
|
playerisValid: () => playerisValid,
|
|
51
51
|
query: () => query,
|
|
52
52
|
readDirectoryFilesSync: () => readDirectoryFilesSync,
|
|
53
|
+
roundToDecimalPlaces: () => roundToDecimalPlaces,
|
|
53
54
|
sec2time: () => sec2time,
|
|
54
55
|
winRateColor: () => winRateColor
|
|
55
56
|
});
|
|
@@ -189,6 +190,32 @@ function MATCH_INFO(matchId) {
|
|
|
189
190
|
`;
|
|
190
191
|
}
|
|
191
192
|
__name(MATCH_INFO, "MATCH_INFO");
|
|
193
|
+
function MATCHES_FOR_DAILY(steamAccountIds, seconds) {
|
|
194
|
+
return `
|
|
195
|
+
{
|
|
196
|
+
players(steamAccountIds:${JSON.stringify(steamAccountIds)}) {
|
|
197
|
+
steamAccount{id name}
|
|
198
|
+
matches(request:{startDateTime:${seconds} take:50}){
|
|
199
|
+
id
|
|
200
|
+
didRadiantWin
|
|
201
|
+
parsedDateTime
|
|
202
|
+
startDateTime
|
|
203
|
+
players {
|
|
204
|
+
kills
|
|
205
|
+
deaths
|
|
206
|
+
assists
|
|
207
|
+
imp
|
|
208
|
+
isRadiant
|
|
209
|
+
steamAccount {
|
|
210
|
+
id
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
`;
|
|
217
|
+
}
|
|
218
|
+
__name(MATCHES_FOR_DAILY, "MATCHES_FOR_DAILY");
|
|
192
219
|
function VERIFYING_PLAYER(steamAccountId) {
|
|
193
220
|
return `
|
|
194
221
|
{
|
|
@@ -221,7 +248,7 @@ function PLAYERS_LASTMATCH(steamAccountIds) {
|
|
|
221
248
|
`;
|
|
222
249
|
}
|
|
223
250
|
__name(PLAYERS_LASTMATCH, "PLAYERS_LASTMATCH");
|
|
224
|
-
function PLAYER_INFO_WITH_25_MATCHES(steamAccountId) {
|
|
251
|
+
function PLAYER_INFO_WITH_25_MATCHES(steamAccountId, heroId) {
|
|
225
252
|
return `
|
|
226
253
|
{
|
|
227
254
|
player(steamAccountId: ${steamAccountId}) {
|
|
@@ -242,7 +269,7 @@ function PLAYER_INFO_WITH_25_MATCHES(steamAccountId) {
|
|
|
242
269
|
performance {
|
|
243
270
|
imp
|
|
244
271
|
}
|
|
245
|
-
heroesPerformance(take: 25, request: {matchGroupOrderBy: WIN_COUNT
|
|
272
|
+
heroesPerformance(take: 25, request: {matchGroupOrderBy: WIN_COUNT take: 25 ${heroId ? "heroIds:" + heroId : ""}}) {
|
|
246
273
|
hero {
|
|
247
274
|
id
|
|
248
275
|
shortName
|
|
@@ -251,7 +278,7 @@ function PLAYER_INFO_WITH_25_MATCHES(steamAccountId) {
|
|
|
251
278
|
winCount
|
|
252
279
|
matchCount
|
|
253
280
|
}
|
|
254
|
-
matches(request: {take: 25}) {
|
|
281
|
+
matches(request: {take: 25 ${heroId ? "heroIds:" + heroId : ""}}) {
|
|
255
282
|
id
|
|
256
283
|
rank
|
|
257
284
|
lobbyType
|
|
@@ -286,10 +313,10 @@ function PLAYER_INFO_WITH_25_MATCHES(steamAccountId) {
|
|
|
286
313
|
`;
|
|
287
314
|
}
|
|
288
315
|
__name(PLAYER_INFO_WITH_25_MATCHES, "PLAYER_INFO_WITH_25_MATCHES");
|
|
289
|
-
function PLAYER_EXTRA_INFO(steamAccountId, matchCount, totalHeroCount) {
|
|
316
|
+
function PLAYER_EXTRA_INFO(steamAccountId, matchCount, totalHeroCount, heroId) {
|
|
290
317
|
return `{
|
|
291
318
|
player(steamAccountId: ${steamAccountId}) {
|
|
292
|
-
heroesPerformance(take: ${totalHeroCount}, request: {matchGroupOrderBy: MATCH_COUNT, take: ${matchCount}}) {
|
|
319
|
+
heroesPerformance(take: ${totalHeroCount}, request: {matchGroupOrderBy: MATCH_COUNT, take: ${matchCount} ${heroId ? "heroIds:" + heroId : ""}}) {
|
|
293
320
|
hero {
|
|
294
321
|
id
|
|
295
322
|
shortName
|
|
@@ -536,7 +563,7 @@ function getFormattedMatchData(match) {
|
|
|
536
563
|
match[player.team].experience += Math.floor(player.experiencePerMinute / 60 * match.durationSeconds);
|
|
537
564
|
player.titles = [];
|
|
538
565
|
player.mvpScore = // 计算MVP分数
|
|
539
|
-
player.kills * 5 + player.assists * 3 + player.stats.heroDamageReport.dealtTotal.stunDuration / 100 * 0.1 +
|
|
566
|
+
player.kills * 5 + player.assists * 3 + player.stats.heroDamageReport.dealtTotal.stunDuration / 100 * 0.1 + player.stats.heroDamageReport.dealtTotal.disableDuration / 100 * 0.05 + player.stats.heroDamageReport.dealtTotal.slowDuration / 100 * 0.025 + player.heroDamage * 1e-3 + player.towerDamage * 0.01 + player.heroHealing * 2e-3 + player.imp * 0.25;
|
|
540
567
|
player.order = heroOrderList[player.hero.id];
|
|
541
568
|
if (player.partyId != null) {
|
|
542
569
|
if (!match.party[player.partyId])
|
|
@@ -695,7 +722,7 @@ function getFormattedMatchData(match) {
|
|
|
695
722
|
findMaxByProperty("networth").titles.push({ name: "富", color: "#FFD700" });
|
|
696
723
|
findMaxByProperty("experiencePerMinute").titles.push({ name: "睿", color: "#8888FF" });
|
|
697
724
|
match.players.reduce(
|
|
698
|
-
(max, player) => player.stats.heroDamageReport.dealtTotal.stunDuration + player.stats.heroDamageReport.dealtTotal.disableDuration + player.stats.heroDamageReport.dealtTotal.slowDuration /
|
|
725
|
+
(max, player) => player.stats.heroDamageReport.dealtTotal.stunDuration + player.stats.heroDamageReport.dealtTotal.disableDuration / 2 + player.stats.heroDamageReport.dealtTotal.slowDuration / 4 > max.stats.heroDamageReport.dealtTotal.stunDuration + max.stats.heroDamageReport.dealtTotal.disableDuration / 2 + max.stats.heroDamageReport.dealtTotal.slowDuration / 4 ? player : max
|
|
699
726
|
).titles.push({ name: "控", color: "#FF00FF" });
|
|
700
727
|
findMaxByProperty("heroDamage").titles.push({ name: "爆", color: "#CC0088" });
|
|
701
728
|
findMaxByProperty("kills", "heroDamage").titles.push({ name: "破", color: "#DD0000" });
|
|
@@ -778,6 +805,11 @@ async function playerisValid(steamAccountId) {
|
|
|
778
805
|
}
|
|
779
806
|
}
|
|
780
807
|
__name(playerisValid, "playerisValid");
|
|
808
|
+
function roundToDecimalPlaces(number, decimalPlaces) {
|
|
809
|
+
const factor = Math.pow(10, decimalPlaces);
|
|
810
|
+
return Math.round(number * factor) / factor;
|
|
811
|
+
}
|
|
812
|
+
__name(roundToDecimalPlaces, "roundToDecimalPlaces");
|
|
781
813
|
|
|
782
814
|
// src/index.ts
|
|
783
815
|
var import_fs2 = __toESM(require("fs"));
|
|
@@ -1055,12 +1087,24 @@ var dotaconstants_add_default = {
|
|
|
1055
1087
|
var import_koishi2 = require("koishi");
|
|
1056
1088
|
var ejs = __toESM(require("ejs"));
|
|
1057
1089
|
var name = "dota2tracker";
|
|
1058
|
-
var usage =
|
|
1090
|
+
var usage = `
|
|
1091
|
+
DOTA2Bot插件-提供自动追踪群友的最新对局的功能(需群友绑定),以及一系列查询功能。
|
|
1092
|
+
**更多信息请进入插件主页查看。**`;
|
|
1059
1093
|
var inject = ["database", "puppeteer", "cron"];
|
|
1060
1094
|
var Config = import_koishi.Schema.intersect([
|
|
1061
1095
|
import_koishi.Schema.object({
|
|
1062
1096
|
STRATZ_API_TOKEN: import_koishi.Schema.string().required().description("※必须。stratz.com的API TOKEN,可在 https://stratz.com/api 获取")
|
|
1063
1097
|
}).description("基础设置"),
|
|
1098
|
+
import_koishi.Schema.object({
|
|
1099
|
+
dailyReportSwitch: import_koishi.Schema.boolean().default(false).description("日报功能").experimental()
|
|
1100
|
+
}),
|
|
1101
|
+
import_koishi.Schema.union([
|
|
1102
|
+
import_koishi.Schema.object({
|
|
1103
|
+
dailyReportSwitch: import_koishi.Schema.const(true).required(),
|
|
1104
|
+
dailyReportHours: import_koishi.Schema.number().min(0).max(23).default(6).description("日报时间小时")
|
|
1105
|
+
}),
|
|
1106
|
+
import_koishi.Schema.object({})
|
|
1107
|
+
]),
|
|
1064
1108
|
import_koishi.Schema.object({
|
|
1065
1109
|
template_match: import_koishi.Schema.union([...readDirectoryFilesSync(`./node_modules/@sjtdev/koishi-plugin-${name}/template/match`)]).default("match_1").description("生成比赛信息图片使用的模板,见 https://github.com/sjtdev/koishi-plugin-dota2tracker/wiki 有模板展示。"),
|
|
1066
1110
|
template_player: import_koishi.Schema.union([...readDirectoryFilesSync(`./node_modules/@sjtdev/koishi-plugin-${name}/template/player`)]).default("player_1").description("生成玩家信息图片使用的模板。(目前仅有一张模板)"),
|
|
@@ -1254,7 +1298,7 @@ async function apply(ctx, config) {
|
|
|
1254
1298
|
queryAndDisplayMatch(session, lastMatchId);
|
|
1255
1299
|
}
|
|
1256
1300
|
});
|
|
1257
|
-
ctx.command("查询玩家 <input_data>", "
|
|
1301
|
+
ctx.command("查询玩家 <input_data>", "查询玩家信息,可指定英雄").usage("查询指定玩家的个人信息与最近战绩,生成图片发布。\n参数可输入该玩家的SteamID或已在本群绑定玩家的别名,无参数时尝试查询调用指令玩家的SteamID").option("hero", "-o <value:string> 查询玩家指定英雄使用情况(同其他英雄查询,可用简称与ID)").example("-查询玩家 123456789").example("-查询玩家 张三").example("-查询玩家 张三 hero 敌法师").action(async ({ session, options }, input_data) => {
|
|
1258
1302
|
if (session.guild) {
|
|
1259
1303
|
let sessionPlayer;
|
|
1260
1304
|
if (!input_data) {
|
|
@@ -1270,15 +1314,16 @@ async function apply(ctx, config) {
|
|
|
1270
1314
|
return;
|
|
1271
1315
|
}
|
|
1272
1316
|
session.send("正在获取玩家数据,请稍后...");
|
|
1317
|
+
let hero = findingHero(options.hero);
|
|
1273
1318
|
let steamId = flagBindedPlayer?.steamId ?? input_data;
|
|
1274
1319
|
let player;
|
|
1275
1320
|
try {
|
|
1276
|
-
let queryRes = await query(PLAYER_INFO_WITH_25_MATCHES(steamId));
|
|
1321
|
+
let queryRes = await query(PLAYER_INFO_WITH_25_MATCHES(steamId, hero.id));
|
|
1277
1322
|
if (queryRes.status == 200) {
|
|
1278
1323
|
player = queryRes.data.data.player;
|
|
1279
1324
|
} else
|
|
1280
1325
|
throw 0;
|
|
1281
|
-
let queryRes2 = await query(PLAYER_EXTRA_INFO(steamId, player.matchCount, Object.keys(dotaconstants3.heroes).length));
|
|
1326
|
+
let queryRes2 = await query(PLAYER_EXTRA_INFO(steamId, player.matchCount, Object.keys(dotaconstants3.heroes).length, hero.id));
|
|
1282
1327
|
if (queryRes2.status == 200) {
|
|
1283
1328
|
let playerExtra = queryRes2.data.data.player;
|
|
1284
1329
|
let filteredDotaPlus = {};
|
|
@@ -1290,13 +1335,19 @@ async function apply(ctx, config) {
|
|
|
1290
1335
|
};
|
|
1291
1336
|
}
|
|
1292
1337
|
});
|
|
1293
|
-
playerExtra.heroesPerformance.forEach((
|
|
1294
|
-
if (filteredDotaPlus[
|
|
1295
|
-
filteredDotaPlus[
|
|
1296
|
-
filteredDotaPlus[
|
|
1297
|
-
filteredDotaPlus[
|
|
1338
|
+
playerExtra.heroesPerformance.forEach((hero2) => {
|
|
1339
|
+
if (filteredDotaPlus[hero2.hero.id]) {
|
|
1340
|
+
filteredDotaPlus[hero2.hero.id].shortName = hero2.hero.shortName;
|
|
1341
|
+
filteredDotaPlus[hero2.hero.id].winCount = hero2.winCount;
|
|
1342
|
+
filteredDotaPlus[hero2.hero.id].matchCount = hero2.matchCount;
|
|
1298
1343
|
}
|
|
1299
1344
|
});
|
|
1345
|
+
player.rank = {
|
|
1346
|
+
medal: parseInt(player.steamAccount.seasonRank?.toString().split("")[0] ?? 0),
|
|
1347
|
+
star: parseInt(player.steamAccount.seasonRank?.toString().split("")[1] ?? 0),
|
|
1348
|
+
leaderboard: player.steamAccount.seasonLeaderboardRank,
|
|
1349
|
+
inTop100: player.steamAccount.seasonLeaderboardRank ? player.steamAccount.seasonLeaderboardRank <= 10 ? "8c" : player.steamAccount.seasonLeaderboardRank <= 100 ? "8b" : void 0 : void 0
|
|
1350
|
+
};
|
|
1300
1351
|
player.dotaPlus = Object.values(filteredDotaPlus);
|
|
1301
1352
|
player.dotaPlus.sort((a, b) => {
|
|
1302
1353
|
if (b.level !== a.level) {
|
|
@@ -1307,6 +1358,14 @@ async function apply(ctx, config) {
|
|
|
1307
1358
|
player.heroesPerformanceTop10 = playerExtra.heroesPerformance.slice(0, 10);
|
|
1308
1359
|
} else
|
|
1309
1360
|
throw 0;
|
|
1361
|
+
if (hero) {
|
|
1362
|
+
const { matchCount, winCount, imp } = player.heroesPerformanceTop10[0];
|
|
1363
|
+
player.matchCount = matchCount;
|
|
1364
|
+
player.winCount = winCount;
|
|
1365
|
+
player.performance.imp = imp;
|
|
1366
|
+
player.dotaPlus = player.dotaPlus.filter((dpHero) => dpHero.heroId == hero.id);
|
|
1367
|
+
}
|
|
1368
|
+
player.genHero = hero;
|
|
1310
1369
|
session.send(await ctx.puppeteer.render(genImageHTML(player, config.template_player, "player" /* Player */)));
|
|
1311
1370
|
} catch (error) {
|
|
1312
1371
|
ctx.logger.error(error);
|
|
@@ -1316,24 +1375,8 @@ async function apply(ctx, config) {
|
|
|
1316
1375
|
});
|
|
1317
1376
|
ctx.command("查询英雄 <input_data>", "查询英雄技能/面板信息").usage("查询英雄的技能说明与各项数据,生成图片发布。\n参数可输入英雄ID、英雄名、英雄常用别名").example("-查询英雄 15").example("-查询英雄 雷泽").example("-查询英雄 电魂").action(async ({ session }, input_data) => {
|
|
1318
1377
|
if (input_data) {
|
|
1319
|
-
let
|
|
1320
|
-
|
|
1321
|
-
id: parseInt(key),
|
|
1322
|
-
names_cn: HEROES_CHINESE[key]
|
|
1323
|
-
}));
|
|
1324
|
-
const mergedMap = /* @__PURE__ */ new Map();
|
|
1325
|
-
[dc_heroes, cn_heroes].forEach((array) => {
|
|
1326
|
-
array.forEach((item) => {
|
|
1327
|
-
const existingItem = mergedMap.get(item.id);
|
|
1328
|
-
if (existingItem)
|
|
1329
|
-
mergedMap.set(item.id, { ...existingItem, ...item });
|
|
1330
|
-
else
|
|
1331
|
-
mergedMap.set(item.id, item);
|
|
1332
|
-
});
|
|
1333
|
-
});
|
|
1334
|
-
let heroes3 = Array.from(mergedMap.values());
|
|
1335
|
-
let findingHero = heroes3.find((hero) => hero.names_cn.includes(input_data) || hero.shortName === input_data.toLowerCase() || hero.id == input_data);
|
|
1336
|
-
if (!findingHero) {
|
|
1378
|
+
let hero = findingHero(input_data);
|
|
1379
|
+
if (!hero) {
|
|
1337
1380
|
session.send("未找到输入的英雄,请确认后重新输入。");
|
|
1338
1381
|
return;
|
|
1339
1382
|
}
|
|
@@ -1347,7 +1390,7 @@ async function apply(ctx, config) {
|
|
|
1347
1390
|
session.send("初次使用或版本更新,正在更新英雄技能数据中……");
|
|
1348
1391
|
let queryRes2 = await query(ALL_ABILITIES_CHINESE_NAME());
|
|
1349
1392
|
if (queryRes2.status == 200) {
|
|
1350
|
-
AbilitiesConstantsCN
|
|
1393
|
+
AbilitiesConstantsCN = { data: queryRes2.data.data.constants };
|
|
1351
1394
|
await ctx.database.upsert("dt_constants_abilities_cn", (row) => [
|
|
1352
1395
|
{ id: 1, data: AbilitiesConstantsCN, gameVersionId: queryConstants.gameVersions[0].id, gameVersionName: queryConstants.gameVersions[0].name }
|
|
1353
1396
|
]);
|
|
@@ -1356,11 +1399,11 @@ async function apply(ctx, config) {
|
|
|
1356
1399
|
}
|
|
1357
1400
|
} else
|
|
1358
1401
|
throw 0;
|
|
1359
|
-
let queryRes3 = await query(HERO_INFO(
|
|
1402
|
+
let queryRes3 = await query(HERO_INFO(hero.id));
|
|
1360
1403
|
if (queryRes3.status == 200) {
|
|
1361
|
-
let
|
|
1362
|
-
|
|
1363
|
-
await session.send(await ctx.puppeteer.render(genImageHTML(
|
|
1404
|
+
let hero2 = queryRes3.data.data.constants.hero;
|
|
1405
|
+
hero2.talents.forEach((talent) => talent.name_cn = AbilitiesConstantsCN.data.abilities.find((item) => item.id == talent.abilityId).language.displayName);
|
|
1406
|
+
await session.send(await ctx.puppeteer.render(genImageHTML(hero2, config.template_hero, "hero" /* Hero */)));
|
|
1364
1407
|
} else
|
|
1365
1408
|
throw 0;
|
|
1366
1409
|
} catch (error) {
|
|
@@ -1374,29 +1417,13 @@ async function apply(ctx, config) {
|
|
|
1374
1417
|
});
|
|
1375
1418
|
ctx.command("查询英雄对战 <input_data:string>", "查询英雄近一周的最佳搭档与最佳克星英雄").usage("根据输入英雄查询最近一周比赛数据(传奇~万古分段)中与该英雄组合胜率最高英雄和与该英雄对抗胜率最低英雄。\n参数可输入英雄ID、英雄名、英雄常用别名").option("limit", "-l <value:number> 返回英雄个数(默认值 5)", { fallback: 5 }).option("filter", "-f <value:number> 过滤场数过低的组合(单位:%,默认值0.75)", { fallback: 0.5 }).example("-查询英雄对战 敌法师 (无额外参数默认返回5个英雄,过滤舍弃场次占比0.75%以下)").example("-查询英雄对战 敌法师 -l=10 -f=1 (返回10个英雄,过滤舍弃场次占比1%以下)").example("-查询英雄对战 敌法师 -l 10 -f 1 (等同于上例,参数接空格也可使用)").action(async ({ session, options }, input_data) => {
|
|
1376
1419
|
if (input_data) {
|
|
1377
|
-
let
|
|
1378
|
-
|
|
1379
|
-
id: parseInt(key),
|
|
1380
|
-
names_cn: HEROES_CHINESE[key]
|
|
1381
|
-
}));
|
|
1382
|
-
const mergedMap = /* @__PURE__ */ new Map();
|
|
1383
|
-
[dc_heroes, cn_heroes].forEach((array) => {
|
|
1384
|
-
array.forEach((item) => {
|
|
1385
|
-
const existingItem = mergedMap.get(item.id);
|
|
1386
|
-
if (existingItem)
|
|
1387
|
-
mergedMap.set(item.id, { ...existingItem, ...item });
|
|
1388
|
-
else
|
|
1389
|
-
mergedMap.set(item.id, item);
|
|
1390
|
-
});
|
|
1391
|
-
});
|
|
1392
|
-
let heroes3 = Array.from(mergedMap.values());
|
|
1393
|
-
let findingHero = heroes3.find((hero) => hero.names_cn.includes(input_data) || hero.shortName === input_data.toLowerCase() || hero.id == input_data);
|
|
1394
|
-
if (!findingHero) {
|
|
1420
|
+
let hero = findingHero(input_data);
|
|
1421
|
+
if (!hero) {
|
|
1395
1422
|
session.send("未找到输入的英雄,请确认后重新输入。");
|
|
1396
1423
|
return;
|
|
1397
1424
|
}
|
|
1398
1425
|
try {
|
|
1399
|
-
let queryRes = await query(HERO_MATCHUP_WINRATE(
|
|
1426
|
+
let queryRes = await query(HERO_MATCHUP_WINRATE(hero.id));
|
|
1400
1427
|
if (queryRes.status == 200) {
|
|
1401
1428
|
let heroStats = queryRes.data.data.heroStats;
|
|
1402
1429
|
let withTopFive = heroStats.matchUp[0].with.filter((item) => item.matchCount / heroStats.matchUp[0].matchCountWith > Math.max(0, Math.min(5, options.filter)) / 100).map((item) => {
|
|
@@ -1421,6 +1448,28 @@ async function apply(ctx, config) {
|
|
|
1421
1448
|
}
|
|
1422
1449
|
}
|
|
1423
1450
|
});
|
|
1451
|
+
function findingHero(input) {
|
|
1452
|
+
if (!input)
|
|
1453
|
+
return;
|
|
1454
|
+
let dc_heroes = Object.values(dotaconstants3.heroes).map((hero) => ({ id: hero["id"], name: hero["name"], shortName: hero["name"].match(/^npc_dota_hero_(.+)$/)[1] }));
|
|
1455
|
+
let cn_heroes = Object.keys(HEROES_CHINESE).map((key) => ({
|
|
1456
|
+
id: parseInt(key),
|
|
1457
|
+
names_cn: HEROES_CHINESE[key]
|
|
1458
|
+
}));
|
|
1459
|
+
const mergedMap = /* @__PURE__ */ new Map();
|
|
1460
|
+
[dc_heroes, cn_heroes].forEach((array) => {
|
|
1461
|
+
array.forEach((item) => {
|
|
1462
|
+
const existingItem = mergedMap.get(item.id);
|
|
1463
|
+
if (existingItem)
|
|
1464
|
+
mergedMap.set(item.id, { ...existingItem, ...item });
|
|
1465
|
+
else
|
|
1466
|
+
mergedMap.set(item.id, item);
|
|
1467
|
+
});
|
|
1468
|
+
});
|
|
1469
|
+
let heroes3 = Array.from(mergedMap.values());
|
|
1470
|
+
return heroes3.find((hero) => hero.names_cn.includes(input) || hero.shortName === input.toLowerCase() || hero.id == input);
|
|
1471
|
+
}
|
|
1472
|
+
__name(findingHero, "findingHero");
|
|
1424
1473
|
ctx.on("ready", async () => {
|
|
1425
1474
|
const tables = await ctx.database.tables;
|
|
1426
1475
|
if (!("dt_subscribed_guilds" in tables)) {
|
|
@@ -1443,6 +1492,78 @@ async function apply(ctx, config) {
|
|
|
1443
1492
|
ctx.database.remove("dt_sended_match_id", { sendTime: { $lt: oneMonthAgo } });
|
|
1444
1493
|
ctx.database.remove("dt_previous_query_results", { queryTime: { $lt: oneMonthAgo } });
|
|
1445
1494
|
});
|
|
1495
|
+
if (config.dailyReportSwitch) {
|
|
1496
|
+
ctx.cron(`0 ${config.dailyReportHours} * * *`, async function() {
|
|
1497
|
+
const oneDayAgo = (0, import_moment.default)().subtract(1, "days").unix();
|
|
1498
|
+
const subscribedGuilds = await ctx.database.get("dt_subscribed_guilds", void 0);
|
|
1499
|
+
const subscribedPlayersInGuild = (await ctx.database.get("dt_subscribed_players", void 0)).filter((player) => subscribedGuilds.some((guild) => guild.guildId == player.guildId));
|
|
1500
|
+
let queryRes = await query(
|
|
1501
|
+
MATCHES_FOR_DAILY(
|
|
1502
|
+
subscribedPlayersInGuild.map((player) => player.steamId).filter((value, index, self) => self.indexOf(value) === index),
|
|
1503
|
+
oneDayAgo
|
|
1504
|
+
)
|
|
1505
|
+
);
|
|
1506
|
+
const players = queryRes.data.data.players.filter((player) => player.matches.length > 0);
|
|
1507
|
+
const matches = players.map((player) => player.matches.map((match) => match)).flat().filter((item, index, self) => index === self.findIndex((t) => t.id === item.id));
|
|
1508
|
+
for (let subPlayer of subscribedPlayersInGuild) {
|
|
1509
|
+
let player = players.find((player2) => subPlayer.steamId == player2.steamAccount.id);
|
|
1510
|
+
if (!player)
|
|
1511
|
+
continue;
|
|
1512
|
+
const guildMember = await ctx.bots.find((bot) => bot.platform == subPlayer.platform)?.getGuildMember(subPlayer.guildId, subPlayer.userId);
|
|
1513
|
+
subPlayer.name = subPlayer.nickName || (guildMember?.nick ?? players.find((player2) => player2.steamAccount.id == subPlayer.steamId)?.steamAccount.name);
|
|
1514
|
+
player.winCount = player.matches.filter((match) => match.didRadiantWin == match.players.find((innerPlayer) => innerPlayer.steamAccount.id == player.steamAccount.id).isRadiant).length;
|
|
1515
|
+
player.loseCount = player.matches.length - player.winCount;
|
|
1516
|
+
player.avgKills = roundToDecimalPlaces(player.matches.reduce((acc, match) => acc + match.players.find((innerPlayer) => innerPlayer.steamAccount.id == player.steamAccount.id).kills, 0) / player.matches.length, 2);
|
|
1517
|
+
player.avgDeaths = roundToDecimalPlaces(player.matches.reduce((acc, match) => acc + match.players.find((innerPlayer) => innerPlayer.steamAccount.id == player.steamAccount.id).deaths, 0) / player.matches.length, 2);
|
|
1518
|
+
player.avgAssists = roundToDecimalPlaces(
|
|
1519
|
+
player.matches.reduce((acc, match) => acc + match.players.find((innerPlayer) => innerPlayer.steamAccount.id == player.steamAccount.id).assists, 0) / player.matches.length,
|
|
1520
|
+
2
|
|
1521
|
+
);
|
|
1522
|
+
player.avgKDA = roundToDecimalPlaces((player.avgKills + player.avgAssists) / (player.avgDeaths || 1), 2);
|
|
1523
|
+
player.avgImp = roundToDecimalPlaces(player.matches.reduce((acc, match) => acc + match.players.find((innerPlayer) => innerPlayer.steamAccount.id == player.steamAccount.id).imp, 0) / player.matches.length, 0);
|
|
1524
|
+
subPlayer = Object.assign(subPlayer, player);
|
|
1525
|
+
}
|
|
1526
|
+
for (let guild of subscribedGuilds) {
|
|
1527
|
+
const currentsubscribedPlayers = subscribedPlayersInGuild.filter((player) => player.platform == guild.platform && player.guildId == guild.guildId && player.matches?.length);
|
|
1528
|
+
if (currentsubscribedPlayers.length) {
|
|
1529
|
+
const currentsubscribedPlayersIds = currentsubscribedPlayers.map((player) => player.steamId);
|
|
1530
|
+
const combinationsMap = /* @__PURE__ */ new Map();
|
|
1531
|
+
matches.forEach((match) => {
|
|
1532
|
+
const sortedPlayerIds = match.players.map((player) => player.steamAccount.id).filter((id) => currentsubscribedPlayersIds.includes(id)).sort((a, b) => a.steamId - b.steamId);
|
|
1533
|
+
const key = sortedPlayerIds.join(",");
|
|
1534
|
+
if (!combinationsMap.has(key)) {
|
|
1535
|
+
const players2 = currentsubscribedPlayers.filter((subPlayer) => sortedPlayerIds.includes(subPlayer.steamId));
|
|
1536
|
+
if (players2.length > 0) {
|
|
1537
|
+
const name2 = players2.map((subPlayer) => subPlayer.name).join("/");
|
|
1538
|
+
combinationsMap.set(key, {
|
|
1539
|
+
players: players2,
|
|
1540
|
+
name: name2,
|
|
1541
|
+
winCount: match.didRadiantWin == match.players.find((innerPlayer) => innerPlayer.steamAccount.id == players2[0].steamId).isRadiant ? 1 : 0,
|
|
1542
|
+
matches: [match]
|
|
1543
|
+
});
|
|
1544
|
+
}
|
|
1545
|
+
} else {
|
|
1546
|
+
const combi = combinationsMap.get(key);
|
|
1547
|
+
combi.matches.push(match);
|
|
1548
|
+
combi.winCount += match.didRadiantWin == match.players.find((innerPlayer) => innerPlayer.steamAccount.id == combi.players[0].steamId).isRadiant ? 1 : 0;
|
|
1549
|
+
}
|
|
1550
|
+
});
|
|
1551
|
+
const combinations = Array.from(combinationsMap.values());
|
|
1552
|
+
await ctx.broadcast(
|
|
1553
|
+
[`${guild.platform}:${guild.guildId}`],
|
|
1554
|
+
`昨日总结:
|
|
1555
|
+
${currentsubscribedPlayers.map(
|
|
1556
|
+
(player) => `${player.name}: ${player.winCount}胜${player.loseCount}负 胜率${Math.round(player.winCount / player.matches.length * 100)}%,平均KDA: [${player.avgKills}/${player.avgDeaths}/${player.avgAssists}](${player.avgKDA}),平均表现: ${player.avgImp > 0 ? "+" : ""}${player.avgImp}`
|
|
1557
|
+
).join("\n")}
|
|
1558
|
+
${combinations.map((combi) => `组合[${combi.name}]: ${combi.winCount}胜${combi.matches.length - combi.winCount}负 胜率${Math.round(combi.winCount / combi.matches.length * 100)}%`).join("\n")}`.replace(
|
|
1559
|
+
/\s*\n\s*/g,
|
|
1560
|
+
"\n"
|
|
1561
|
+
)
|
|
1562
|
+
);
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
});
|
|
1566
|
+
}
|
|
1446
1567
|
ctx.cron("* * * * *", async function() {
|
|
1447
1568
|
const subscribedGuilds = await ctx.database.get("dt_subscribed_guilds", void 0);
|
|
1448
1569
|
const subscribedPlayersInGuild = (await ctx.database.get("dt_subscribed_players", void 0)).filter((player) => subscribedGuilds.some((guild) => guild.guildId == player.guildId));
|
|
@@ -1513,6 +1634,7 @@ KDA:${((player.kills + player.assists) / (player.deaths || 1)).toFixed(2)} [${
|
|
|
1513
1634
|
broadMatchMessage += broadPlayerMessage + "\n";
|
|
1514
1635
|
}
|
|
1515
1636
|
await ctx.broadcast([`${commingGuild.platform}:${commingGuild.guildId}`], broadMatchMessage + img);
|
|
1637
|
+
ctx.logger.info(`已解析${match.id}并发布于${commingGuild.platform}:${commingGuild.guildId}。`);
|
|
1516
1638
|
}
|
|
1517
1639
|
ctx.database.upsert("dt_previous_query_results", (row) => [{ matchId: match.id, data: match, queryTime: /* @__PURE__ */ new Date() }]);
|
|
1518
1640
|
ctx.database.create("dt_sended_match_id", { matchId: match.id, sendTime: /* @__PURE__ */ new Date() });
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sjtdev/koishi-plugin-dota2tracker",
|
|
3
3
|
"description": "",
|
|
4
|
-
"version": "1.1.
|
|
4
|
+
"version": "1.1.4",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"typings": "lib/index.d.ts",
|
|
7
7
|
"files": [
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"template"
|
|
12
12
|
],
|
|
13
13
|
"contributors": [
|
|
14
|
-
"sjtdev <sh1j1n9ta0@foxmail.com>"
|
|
14
|
+
"sjtdev <sh1j1n9ta0@foxmail.com>",
|
|
15
|
+
"sjtdev"
|
|
15
16
|
],
|
|
16
17
|
"homepage": "https://github.com/sjtdev/koishi-plugin-dota2tracker",
|
|
17
18
|
"repository": {
|
package/readme.md
CHANGED
|
@@ -8,9 +8,10 @@ DOTA2Bot插件-提供自动追踪群友的最新对局的功能(需群友绑
|
|
|
8
8
|
|
|
9
9
|
### 使用
|
|
10
10
|
需在插件配置页填入STRATZ API TOKEN,否则无法使用。(配置中提供了API的获取链接)
|
|
11
|
-
|
|
11
|
+
在希望推送战报信息的群组使用指令`订阅本群`,玩家可使用指令`绑定`来将自身账号与Steam账号绑定,bot会尝试追踪已订阅群组中的绑定玩家的最新对局信息。
|
|
12
12
|
其他查询功能见下方指令说明。
|
|
13
|
-
**直接调用help指令可获取更详细的说明,调用【指令 -h】还会有用法示例。(例如:订阅本群 -h)**
|
|
13
|
+
**直接调用help指令可获取更详细的说明,调用【指令 -h】还会有用法示例。(例如:订阅本群 -h)**
|
|
14
|
+
**更新日志见[changelog](changelog.md)**
|
|
14
15
|
|
|
15
16
|
### 指令
|
|
16
17
|
指令 <必填参数> [可选参数]
|
|
@@ -24,8 +25,8 @@ DOTA2Bot插件-提供自动追踪群友的最新对局的功能(需群友绑
|
|
|
24
25
|
* <input type="checkbox" checked>`取消绑定`
|
|
25
26
|
* <input type="checkbox" checked>`改名 <新玩家别名>`
|
|
26
27
|
##### 查询
|
|
27
|
-
* <input type="checkbox" checked>`查询玩家 [SteamID|别名]`
|
|
28
|
-
|
|
28
|
+
* <input type="checkbox" checked>`查询玩家 [SteamID|别名] [<--hero|-o> <英雄ID|英雄名|英雄常用别名>]`
|
|
29
|
+
返回一张图片,包含玩家各类信息。(缺省参数时并且调用者已绑定将自查)(输入--hero或-o并跟上查询英雄的参数时,将查询玩家指定英雄)
|
|
29
30
|
* <input type="checkbox" checked>`查询比赛 <比赛ID>`
|
|
30
31
|
返回一张图片,包含比赛对战信息。
|
|
31
32
|
* <input type="checkbox" checked>`查询最近比赛 [SteamID|别名]`
|
|
@@ -408,14 +408,12 @@
|
|
|
408
408
|
<div class="wrapper">
|
|
409
409
|
<div class="player">
|
|
410
410
|
<%- `
|
|
411
|
-
<div class="avatar"><img src="${player.steamAccount
|
|
411
|
+
<div class="avatar"><img src="${player.steamAccount?.avatar}" alt="" /></div>
|
|
412
412
|
<div class="info">
|
|
413
|
-
<p class="name">${player.steamAccount.name}${player.guildMember ? ` <span class="guild ${guildLevel(player.guildMember.guild.currentPercentile)}">[${player.guildMember.guild.tag}]</span
|
|
414
|
-
<p class="matches"><span>场次:${player.matchCount}(<span class="win">${player.winCount}</span>/<span class="lose">${player.matchCount - player.winCount}</span>)</span>胜率:<span style="color:${utils.winRateColor(
|
|
415
|
-
player.winCount / player.matchCount
|
|
416
|
-
)};">${((player.winCount / player.matchCount) * 100).toFixed(2)}%</span></p>
|
|
413
|
+
<p class="name">${player.steamAccount.name}${player.guildMember ? ` <span class="guild ${guildLevel(player.guildMember.guild.currentPercentile)}">[${player.guildMember.guild.tag}]</span>${player.genHero?` >${player.genHero.names_cn[0]}<`:""}</p>` : ""}
|
|
414
|
+
<p class="matches"><span>场次:${player.matchCount}(<span class="win">${player.winCount}</span>/<span class="lose">${player.matchCount - player.winCount}</span>)</span>胜率:<span style="color:${utils.winRateColor(player.winCount / player.matchCount)};">${((player.winCount / player.matchCount) * 100).toFixed(2)}%</span></p>
|
|
417
415
|
<p class="matches"><span>最近25场:<span class="win">${nearWinCount}</span>/<span class="lose">${nearMatchCount - nearWinCount}</span></span><span>胜率:<span style="color:${utils.winRateColor(nearWinCount / nearMatchCount)};">${(
|
|
418
|
-
(nearWinCount / nearMatchCount) * 100).toFixed(2)}%</span></span><span>评分:${player.performance.imp}</span></span></p>
|
|
416
|
+
(nearWinCount / nearMatchCount) * 100).toFixed(2)}%</span></span><span>评分:${(player.performance.imp > 0 ? "+" : "") + player.performance.imp}</span></span></p>
|
|
419
417
|
<p class="matches"><span>对线:<span class="victory">${outcomeCounts.victory + outcomeCounts.stomp}(<span class="stomp">${outcomeCounts.stomp}</span>)</span>-<span class="tie">${outcomeCounts.tie}</span>-<span class="fail">${
|
|
420
418
|
outcomeCounts.fail + outcomeCounts.stomped
|
|
421
419
|
}(<span class="stomped">${outcomeCounts.stomped}</span>)</span></span><span>线优:<span style="color:${utils.winRateColor(
|
|
@@ -423,23 +421,17 @@
|
|
|
423
421
|
)};">${(((outcomeCounts.victory + outcomeCounts.stomp) / (outcomeCounts.victory + outcomeCounts.stomp + outcomeCounts.fail + outcomeCounts.stomped)) * 100).toFixed(2)}%</span></span></p>
|
|
424
422
|
</div>
|
|
425
423
|
<div class="rank">
|
|
426
|
-
<img class="medal" src="${utils.getImageUrl(
|
|
427
|
-
|
|
428
|
-
((player.steamAccount.seasonLeaderboardRank
|
|
429
|
-
? player.steamAccount.seasonLeaderboardRank <= 100
|
|
430
|
-
? player.steamAccount.seasonLeaderboardRank <= 10
|
|
431
|
-
? "8c"
|
|
432
|
-
: "8b"
|
|
433
|
-
: player.steamAccount.seasonRank?.toString().split("")[0]
|
|
434
|
-
: player.steamAccount.seasonRank?.toString().split("")[0]) ?? "0")
|
|
435
|
-
)}" alt="" />
|
|
436
|
-
<img class="star" src="${utils.getImageUrl("star_" + (player.steamAccount.seasonRank?.toString().split("")[1] ?? "0"))}" alt="" />
|
|
424
|
+
<img class="medal" src="${utils.getImageUrl('medal_' +(player.inTop100??player.rank.medal))}" alt="" />
|
|
425
|
+
<img class="star" src="${utils.getImageUrl('star_' + player.rank.star)}" alt="" />
|
|
437
426
|
<p>${player.steamAccount.seasonLeaderboardRank ?? ""}</p>
|
|
438
427
|
</div>` %>
|
|
439
428
|
</div>
|
|
440
429
|
<div class="hero_winrate">
|
|
441
430
|
<div class="heroes">
|
|
442
|
-
|
|
431
|
+
<%- !player.genHero ?
|
|
432
|
+
`<p class="tip row total">全期场次前十的英雄:</p>`:
|
|
433
|
+
`<p class="tip row total">全期场次:</p>`
|
|
434
|
+
%>
|
|
443
435
|
<span class="tip">英雄</span>
|
|
444
436
|
<span class="tip" style="margin: 0 4px">场次</span>
|
|
445
437
|
<span class="tip" style="margin: 0 4px">胜率</span>
|
|
@@ -454,7 +446,10 @@
|
|
|
454
446
|
<span class="imp">${(hero.imp > 0 ? "+" : "") + hero.imp}</span>
|
|
455
447
|
<span class="win" style="${hero.winCount == 0 ? "visibility:hidden;" : ""}width: ${hero.winCount * pixelOfPerMatchInTotal}px">${hero.winCount}</span>
|
|
456
448
|
<span class="lose" style="${hero.matchCount - hero.winCount == 0 ? "visibility:hidden;" : ""}width: ${(hero.matchCount - hero.winCount) * pixelOfPerMatchInTotal}px">${hero.matchCount - hero.winCount}</span>`).join("") %>
|
|
457
|
-
|
|
449
|
+
<%- !player.genHero ?
|
|
450
|
+
`<p class="tip row near">近期使用场次大于1的英雄:</p>`:
|
|
451
|
+
`<p class="tip row total">近25场:</p>`
|
|
452
|
+
%>
|
|
458
453
|
<%- player.heroesPerformance
|
|
459
454
|
.filter((hero) => hero.matchCount > 1)
|
|
460
455
|
.map((hero, index) => `
|
|
@@ -481,7 +476,7 @@
|
|
|
481
476
|
<span class="lose" style="${position.matchCount - position.winCount == 0 ? "visibility:hidden;" : ""}width: ${(position.matchCount - position.winCount) * pixelOfPerMatchInPosition}px">${position.matchCount - position.winCount}</span>`).join("") %>
|
|
482
477
|
</div>
|
|
483
478
|
</div>
|
|
484
|
-
<%- player.streak>1?`<div class="streak" style="box-shadow:none;color:${utils.winRateColor((player.streak + 10) / 20)};">${Math.abs(player.streak) + (player.streak > 0 ? "连胜" : "连败")}</div>`:"" %>
|
|
479
|
+
<%- Math.abs(player.streak)>1?`<div class="streak" style="box-shadow:none;color:${utils.winRateColor((player.streak + 10) / 20)};">${Math.abs(player.streak) + (player.streak > 0 ? "连胜" : "连败")}</div>`:"" %>
|
|
485
480
|
<table class="matches">
|
|
486
481
|
<colgroup>
|
|
487
482
|
<col style="width: auto" />
|
|
@@ -518,14 +513,14 @@
|
|
|
518
513
|
<td><img alt="" src="${utils.getImageUrl(match.players[0].hero.shortName, ImageType.HeroIcons)}" /></td>
|
|
519
514
|
<td style="line-height: 20px">
|
|
520
515
|
<p>${((match.players[0].kills + match.players[0].assists) / Math.max(1, match.players[0].deaths)).toFixed(2)} (${(((match.players[0].kills + match.players[0].assists) /
|
|
521
|
-
(match.players[0].isRadiant ? match.radiantKills
|
|
516
|
+
(match.players[0].isRadiant ? match.radiantKills?.reduce((acc, cva) => acc + cva, 0) : match.direKills?.reduce((acc, cva) => acc + cva, 0))) * 100)?.toFixed(0)}%)</p>
|
|
522
517
|
<p>${match.players[0].kills}/${match.players[0].deaths}/${match.players[0].assists}</p>
|
|
523
518
|
</td>
|
|
524
519
|
<td><div class="player_lane ${match.laneResult}">${laneSVG[match.laneResult]}</div></td>
|
|
525
520
|
<td style="line-height: 20px">${moment(new Date(match.startDateTime * 1000)).format("YYYY-MM-DD HH:mm:ss").slice(2)}</td>
|
|
526
521
|
<td>${utils.sec2time(match.durationSeconds)}</td>
|
|
527
522
|
<td>${(match.players[0].imp > 0 ? "+" : "") + match.players[0].imp}</td>
|
|
528
|
-
<td><img class="medal" src="${utils.getImageUrl("medal_" + match.rank
|
|
523
|
+
<td><img class="medal" src="${utils.getImageUrl("medal_" + match.rank?.toString().split("")[0])}" style="width: 100%" /></td>
|
|
529
524
|
</tr>`).join("")%>
|
|
530
525
|
</tbody>
|
|
531
526
|
</table>
|