@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 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, take: 25}) {
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 + (player.stats.heroDamageReport.dealtTotal.slowDuration + player.stats.heroDamageReport.dealtTotal.disableDuration) / 100 * 0.05 + player.heroDamage * 1e-3 + player.towerDamage * 0.01 + player.heroHealing * 2e-3 + player.imp * 0.25;
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 / 2 > max.stats.heroDamageReport.dealtTotal.stunDuration + max.stats.heroDamageReport.dealtTotal.disableDuration + max.stats.heroDamageReport.dealtTotal.slowDuration / 2 ? player : max
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 = "DOTA2Bot插件-提供自动追踪群友的最新对局的功能(需群友绑定),以及一系列查询功能。";
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>", "查询玩家信息").usage("查询指定玩家的个人信息与最近战绩,生成图片发布。\n参数可输入该玩家的SteamID或已在本群绑定玩家的别名,无参数时尝试查询调用指令玩家的SteamID").example("-查询玩家 123456789").example("-查询玩家 张三").action(async ({ session }, 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((hero) => {
1294
- if (filteredDotaPlus[hero.hero.id]) {
1295
- filteredDotaPlus[hero.hero.id].shortName = hero.hero.shortName;
1296
- filteredDotaPlus[hero.hero.id].winCount = hero.winCount;
1297
- filteredDotaPlus[hero.hero.id].matchCount = hero.matchCount;
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 dc_heroes = Object.values(dotaconstants3.heroes).map((hero) => ({ id: hero["id"], name: hero["name"], shortName: hero["name"].match(/^npc_dota_hero_(.+)$/)[1] }));
1320
- let cn_heroes = Object.keys(HEROES_CHINESE).map((key) => ({
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.data = queryRes2.data.data.constants;
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(findingHero.id));
1402
+ let queryRes3 = await query(HERO_INFO(hero.id));
1360
1403
  if (queryRes3.status == 200) {
1361
- let hero = queryRes3.data.data.constants.hero;
1362
- hero.talents.forEach((talent) => talent.name_cn = AbilitiesConstantsCN.data.abilities.find((item) => item.id == talent.abilityId).language.displayName);
1363
- await session.send(await ctx.puppeteer.render(genImageHTML(hero, config.template_hero, "hero" /* Hero */)));
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 dc_heroes = Object.values(dotaconstants3.heroes).map((hero) => ({ id: hero["id"], name: hero["name"], shortName: hero["name"].match(/^npc_dota_hero_(.+)$/)[1] }));
1378
- let cn_heroes = Object.keys(HEROES_CHINESE).map((key) => ({
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(findingHero.id));
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.2",
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
- 在希望推送战报信息的群组使用指令【-订阅本群】,玩家可使用指令【-绑定】来将自身账号与Steam账号绑定,bot会尝试追踪已订阅群组中的绑定玩家的最新对局信息。
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.avatar}" alt="" /></div>
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></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(
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
- "medal_" +
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
- <p class="tip row total">全期场次前十的英雄:</p>
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
- <p class="tip row near">近期使用场次大于1的英雄:</p>
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.reduce((acc, cva) => acc + cva, 0) : match.direKills.reduce((acc, cva) => acc + cva, 0))) * 100).toFixed(0)}%)</p>
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.toString().split("")[0])}" style="width: 100%" /></td>
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>