@sjtdev/koishi-plugin-dota2tracker 2.2.3 → 2.3.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 (105) hide show
  1. package/changelog.md +22 -0
  2. package/lib/index.js +127 -94
  3. package/{queries → lib/queries}/MatchInfo.graphql +13 -0
  4. package/lib/templates/common/components/building_icons.ejs +20 -0
  5. package/lib/templates/common/styles/normalize.min.css +1 -0
  6. package/lib/templates/hero/hero_1.ejs +69 -0
  7. package/lib/templates/images/7.38_simple_minimap.png +0 -0
  8. package/lib/templates/item/item/recipe.ejs +9 -0
  9. package/lib/templates/item/item/style.css +1 -0
  10. package/lib/templates/item/item.ejs +52 -0
  11. package/lib/templates/item/itemlist.ejs +11 -0
  12. package/lib/templates/match/match_1/base.css +1 -0
  13. package/lib/templates/match/match_1/item.ejs +1 -0
  14. package/lib/templates/match/match_1/main.ejs +8 -0
  15. package/lib/templates/match/match_1/player.ejs +1 -0
  16. package/lib/templates/match/match_1/style.css +1 -0
  17. package/lib/templates/match/match_1.ejs +18 -0
  18. package/lib/templates/match/match_2/original.css +1 -0
  19. package/lib/templates/match/match_2/original.ejs +10 -0
  20. package/lib/templates/match/match_2+/charts.ejs +1 -0
  21. package/lib/templates/match/match_2+/extra.css +1 -0
  22. package/lib/templates/match/match_2+/lane_outcome.ejs +56 -0
  23. package/lib/templates/match/match_2+/map.ejs +160 -0
  24. package/lib/templates/match/match_2+.ejs +1 -0
  25. package/lib/templates/match/match_2.ejs +1 -0
  26. package/lib/templates/player/player_1/base.css +1 -0
  27. package/lib/templates/player/player_1/private.ejs +1 -0
  28. package/lib/templates/player/player_1.ejs +78 -0
  29. package/lib/templates/rank/rank_fun.ejs +1 -0
  30. package/lib/templates/report/daily/base.css +1 -0
  31. package/lib/templates/report/daily.ejs +29 -0
  32. package/package.json +2 -2
  33. package/template/hero/hero_1.ejs +0 -900
  34. package/template/item/item/recipe.ejs +0 -51
  35. package/template/item/item/style.css +0 -244
  36. package/template/item/item.ejs +0 -140
  37. package/template/item/itemlist.ejs +0 -99
  38. package/template/match/match_1/item.ejs +0 -11
  39. package/template/match/match_1/main.ejs +0 -37
  40. package/template/match/match_1/player.ejs +0 -154
  41. package/template/match/match_1/style.css +0 -764
  42. package/template/match/match_1.ejs +0 -56
  43. package/template/match/match_2/original.css +0 -463
  44. package/template/match/match_2/original.ejs +0 -192
  45. package/template/match/match_2+/charts.ejs +0 -261
  46. package/template/match/match_2+/extra.css +0 -143
  47. package/template/match/match_2+/lane_outcome.ejs +0 -157
  48. package/template/match/match_2+.ejs +0 -27
  49. package/template/match/match_2.ejs +0 -18
  50. package/template/player/player_1/private.ejs +0 -5
  51. package/template/player/player_1.ejs +0 -654
  52. package/template/rank/rank_fun.ejs +0 -131
  53. package/template/report/daily.ejs +0 -191
  54. /package/{queries → lib/queries}/Constants.graphql +0 -0
  55. /package/{queries → lib/queries}/GetWeeklyMetaByPosition.graphql +0 -0
  56. /package/{queries → lib/queries}/PlayerExtraInfo.graphql +0 -0
  57. /package/{queries → lib/queries}/PlayerInfoWith25Matches.graphql +0 -0
  58. /package/{queries → lib/queries}/PlayerPerformanceForHeroRecommendation.graphql +0 -0
  59. /package/{queries → lib/queries}/PlayersInfoWith10MatchesForGuild.graphql +0 -0
  60. /package/{queries → lib/queries}/PlayersLastmatchRankinfo.graphql +0 -0
  61. /package/{queries → lib/queries}/PlayersMatchesForDaily.graphql +0 -0
  62. /package/{queries → lib/queries}/RequestMatchDataAnalysis.graphql +0 -0
  63. /package/{queries → lib/queries}/VerifyingPlayer.graphql +0 -0
  64. /package/{template → lib/templates}/images/bei.jpg +0 -0
  65. /package/{template → lib/templates}/images/disconnected.png +0 -0
  66. /package/{template → lib/templates}/images/flag_dire.png +0 -0
  67. /package/{template → lib/templates}/images/flag_radiant.png +0 -0
  68. /package/{template → lib/templates}/images/hero_badge_1.png +0 -0
  69. /package/{template → lib/templates}/images/hero_badge_2.png +0 -0
  70. /package/{template → lib/templates}/images/hero_badge_3.png +0 -0
  71. /package/{template → lib/templates}/images/hero_badge_4.png +0 -0
  72. /package/{template → lib/templates}/images/hero_badge_5.png +0 -0
  73. /package/{template → lib/templates}/images/hero_badge_6.png +0 -0
  74. /package/{template → lib/templates}/images/lane_fail.svg +0 -0
  75. /package/{template → lib/templates}/images/lane_jungle.svg +0 -0
  76. /package/{template → lib/templates}/images/lane_stomp.svg +0 -0
  77. /package/{template → lib/templates}/images/lane_stomped.svg +0 -0
  78. /package/{template → lib/templates}/images/lane_tie.svg +0 -0
  79. /package/{template → lib/templates}/images/lane_victory.svg +0 -0
  80. /package/{template → lib/templates}/images/logo_dire.png +0 -0
  81. /package/{template → lib/templates}/images/logo_radiant.png +0 -0
  82. /package/{template → lib/templates}/images/medal_0.png +0 -0
  83. /package/{template → lib/templates}/images/medal_1.png +0 -0
  84. /package/{template → lib/templates}/images/medal_2.png +0 -0
  85. /package/{template → lib/templates}/images/medal_3.png +0 -0
  86. /package/{template → lib/templates}/images/medal_4.png +0 -0
  87. /package/{template → lib/templates}/images/medal_5.png +0 -0
  88. /package/{template → lib/templates}/images/medal_6.png +0 -0
  89. /package/{template → lib/templates}/images/medal_7.png +0 -0
  90. /package/{template → lib/templates}/images/medal_8.png +0 -0
  91. /package/{template → lib/templates}/images/medal_8b.png +0 -0
  92. /package/{template → lib/templates}/images/medal_8c.png +0 -0
  93. /package/{template → lib/templates}/images/scepter.png +0 -0
  94. /package/{template → lib/templates}/images/scepter_0.png +0 -0
  95. /package/{template → lib/templates}/images/scepter_1.png +0 -0
  96. /package/{template → lib/templates}/images/shard.png +0 -0
  97. /package/{template → lib/templates}/images/shard_0.png +0 -0
  98. /package/{template → lib/templates}/images/shard_1.png +0 -0
  99. /package/{template → lib/templates}/images/star_0.png +0 -0
  100. /package/{template → lib/templates}/images/star_1.png +0 -0
  101. /package/{template → lib/templates}/images/star_2.png +0 -0
  102. /package/{template → lib/templates}/images/star_3.png +0 -0
  103. /package/{template → lib/templates}/images/star_4.png +0 -0
  104. /package/{template → lib/templates}/images/star_5.png +0 -0
  105. /package/{template → lib/templates}/images/xi.jpg +0 -0
package/changelog.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # 更新日志
2
2
 
3
+ ## [2.3.0](https://github.com/sjtdev/koishi-plugin-dota2tracker/compare/v2.2.3...v2.3.0) (2025-12-17)
4
+
5
+ ### ✨ 新增功能
6
+
7
+ * 适配`7.40`新英雄 **朗戈**,更新`dotaconstants`依赖。 ([8308ed9](https://github.com/sjtdev/koishi-plugin-dota2tracker/commit/8308ed96c641519ec52a39329e2c3312b0fa2dc8))
8
+ * **templates/match_2+:** 添加小地图,显示防御塔、兵营、基地的存活状态,数据可用时显示建筑被拆毁时间。(高地仅显示各路近战兵营被破时间、4塔被全部拆除时间、基地时间) ([923b40f](https://github.com/sjtdev/koishi-plugin-dota2tracker/commit/923b40fc8f24e05ce4b28dfa9a20ad3166c8e2f5))
9
+
10
+ ### 🚀 功能优化
11
+
12
+ * **api:** 将超时时间从10秒统一上调到15秒,解决某些数据量大的耗时任务失败概率高的问题。 ([b3f51de](https://github.com/sjtdev/koishi-plugin-dota2tracker/commit/b3f51defd49ac7c9c176f894aa9d94d9b392b3aa))
13
+ * **logger:** 优化部分查询情况下的报错提示日志。 ([79ba28b](https://github.com/sjtdev/koishi-plugin-dota2tracker/commit/79ba28b4822df8858e4f4c8d71303b339d407569))
14
+
15
+ ### 🐛 Bug 修复
16
+
17
+ * **command/query-hero:** 修复了指令`查询英雄`无参数但携带`-r/--random`选项无响应的问题。(即无法通过`查询英雄 -r`直接随机查询英雄的问题) ([c56a22c](https://github.com/sjtdev/koishi-plugin-dota2tracker/commit/c56a22caacf65eef573e6434b1032b06f589c2b8))
18
+ * **command/query-match:** 修复`2.2.2`更新导致指令`查询比赛`不可用的问题。 ([be5061c](https://github.com/sjtdev/koishi-plugin-dota2tracker/commit/be5061c9b67593cceb8067d480b965cb47c53d4d))
19
+
20
+ ### ⚡ 性能提升
21
+
22
+ * **command/query-match:** 清理`2.0.0`大型重构时遗留耗时代码,提升执行速度。 ([4c0bf34](https://github.com/sjtdev/koishi-plugin-dota2tracker/commit/4c0bf34c358af3d508bf1be6be88fd1e86fefc17))
23
+ * **templates:** 重构大部分模板,使其构建打包后体积更小,略微提升生成速度。 ([fdf1d33](https://github.com/sjtdev/koishi-plugin-dota2tracker/commit/fdf1d3367e12f8e46124df31d6f432e7fd0a7187))
24
+
3
25
  ### [2.2.3](https://github.com/sjtdev/koishi-plugin-dota2tracker/compare/v2.2.2...v2.2.3) (2025-11-20)
4
26
 
5
27
  ### 🎨 样式
package/lib/index.js CHANGED
@@ -259,7 +259,8 @@ var require_en_US_constants = __commonJS({
259
259
  "136": "Marci",
260
260
  "137": "Primal Beast",
261
261
  "138": "Muerta",
262
- "145": "Kez"
262
+ "145": "Kez",
263
+ "155": "Largo"
263
264
  },
264
265
  behavior: {
265
266
  "Unit Target": "Unit Target",
@@ -571,7 +572,8 @@ var require_zh_CN_constants = __commonJS({
571
572
  "136": "玛西",
572
573
  "137": "獸",
573
574
  "138": "琼英碧灵",
574
- "145": "凯"
575
+ "145": "凯",
576
+ "155": "朗戈"
575
577
  },
576
578
  behavior: {
577
579
  "Unit Target": "单位目标",
@@ -1520,9 +1522,9 @@ __name(handleError, "handleError");
1520
1522
 
1521
1523
  // src/app/core/match.service.ts
1522
1524
  var MatchService = class _MatchService extends import_koishi4.Service {
1523
- constructor(ctx, pluginVersion2) {
1525
+ constructor(ctx, pluginVersion) {
1524
1526
  super(ctx, "dota2tracker.match", true);
1525
- this.pluginVersion = pluginVersion2;
1527
+ this.pluginVersion = pluginVersion;
1526
1528
  }
1527
1529
  static {
1528
1530
  __name(this, "MatchService");
@@ -2027,12 +2029,12 @@ var PlayerService = class _PlayerService extends import_koishi5.Service {
2027
2029
  const lastMatchQuery = await this.ctx.dota2tracker.stratzAPI.queryPlayersLastMatchRankInfo({
2028
2030
  steamAccountIds: [steamId]
2029
2031
  });
2030
- if (lastMatchQuery.players[0].steamAccount.isAnonymous) return { matchId: 0, isAnonymous: true };
2032
+ if (lastMatchQuery.players[0].steamAccount.isAnonymous) return { id: 0, isAnonymous: true };
2031
2033
  lastMatchId = lastMatchQuery.players[0].matches[0]?.id;
2032
2034
  } catch (error) {
2033
2035
  this.logger.error(error);
2034
2036
  }
2035
- return { matchId: lastMatchId };
2037
+ return { id: lastMatchId };
2036
2038
  }
2037
2039
  async getFormattedPlayerData(steamId, heroId, languageTag) {
2038
2040
  const playerQuery = await this.ctx.dota2tracker.stratzAPI.queryPlayerInfoWith25Matches({
@@ -2258,8 +2260,8 @@ var CacheService = class extends import_koishi6.Service {
2258
2260
  async getMatchCache(matchId) {
2259
2261
  return this.ctx.cache.get("dt_previous_query_results", String(matchId));
2260
2262
  }
2261
- setMatchCache(matchId, matchQuery, pluginVersion2) {
2262
- this.ctx.cache.set("dt_previous_query_results", String(matchQuery.match.id), { data: matchQuery, pluginVersion: pluginVersion2 }, DAYS_30);
2263
+ setMatchCache(matchId, matchQuery, pluginVersion) {
2264
+ this.ctx.cache.set("dt_previous_query_results", String(matchQuery.match.id), { data: matchQuery, pluginVersion }, DAYS_30);
2263
2265
  }
2264
2266
  markMatchAsSended(matchId) {
2265
2267
  this.ctx.cache.set("dt_sended_match_id", String(matchId), void 0, DAYS_30);
@@ -2388,14 +2390,6 @@ var import_path = __toESM(require("path"));
2388
2390
  var import_axios2 = __toESM(require("axios"));
2389
2391
  var import_https_proxy_agent = require("https-proxy-agent");
2390
2392
  var StratzAPI = class extends import_koishi8.Service {
2391
- constructor(ctx, pluginDir3) {
2392
- super(ctx, "dota2tracker.stratz-api", true);
2393
- this.pluginDir = pluginDir3;
2394
- this.config = ctx.config;
2395
- this.queue = new MiniQueue(ctx, { interval: 200 });
2396
- this.http = import_axios2.default.create({ timeout: 1e4, signal: this.abortController.signal });
2397
- ctx.on("dispose", () => this.dispose());
2398
- }
2399
2393
  static {
2400
2394
  __name(this, "StratzAPI");
2401
2395
  }
@@ -2403,12 +2397,21 @@ var StratzAPI = class extends import_koishi8.Service {
2403
2397
  queue;
2404
2398
  http;
2405
2399
  abortController = new AbortController();
2400
+ graphqlQueriesDir;
2401
+ constructor(ctx, currentDir) {
2402
+ super(ctx, "dota2tracker.stratz-api", true);
2403
+ this.config = ctx.config;
2404
+ this.graphqlQueriesDir = import_path.default.join(currentDir, "queries");
2405
+ this.queue = new MiniQueue(ctx, { interval: 200 });
2406
+ this.http = import_axios2.default.create({ timeout: 15e3, signal: this.abortController.signal });
2407
+ ctx.on("dispose", () => this.dispose());
2408
+ }
2406
2409
  dispose() {
2407
2410
  this.queue.dispose();
2408
2411
  this.abortController.abort();
2409
2412
  }
2410
2413
  async queryGetWeeklyMetaByPosition({ bracketIds }) {
2411
- return this.query("GetWeeklyMetaByPosition", { bracketIds }, (data) => !!data.heroStats);
2414
+ return this.query("GetWeeklyMetaByPosition", { bracketIds }, (data) => !!data?.heroStats);
2412
2415
  }
2413
2416
  async queryPlayerPerformanceForHeroRecommendation({ steamAccountId, recentDateTime }) {
2414
2417
  return this.query(
@@ -2417,7 +2420,7 @@ var StratzAPI = class extends import_koishi8.Service {
2417
2420
  steamAccountId,
2418
2421
  recentDateTime
2419
2422
  },
2420
- (data) => !!data.player
2423
+ (data) => !!data?.player
2421
2424
  );
2422
2425
  }
2423
2426
  async queryPlayersMatchesForDaily(steamAccountIds, seconds) {
@@ -2427,11 +2430,11 @@ var StratzAPI = class extends import_koishi8.Service {
2427
2430
  steamAccountIds,
2428
2431
  seconds
2429
2432
  },
2430
- (data) => !!data.players
2433
+ (data) => !!data?.players
2431
2434
  );
2432
2435
  }
2433
2436
  async queryVerifyingPlayer(steamAccountId) {
2434
- return this.query("VerifyingPlayer", { steamAccountId }, (data) => !!data.player);
2437
+ return this.query("VerifyingPlayer", { steamAccountId }, (data) => !!data?.player);
2435
2438
  }
2436
2439
  async queryPlayerExtraInfo({ steamAccountId, matchCount, heroIds }) {
2437
2440
  return this.query(
@@ -2441,11 +2444,11 @@ var StratzAPI = class extends import_koishi8.Service {
2441
2444
  matchCount,
2442
2445
  heroIds
2443
2446
  },
2444
- (data) => !!data.player
2447
+ (data) => !!data?.player
2445
2448
  );
2446
2449
  }
2447
2450
  async queryPlayersInfoWith10MatchesForGuild({ steamAccountIds }) {
2448
- return this.query("PlayersInfoWith10MatchesForGuild", { steamAccountIds }, (data) => !!data.players);
2451
+ return this.query("PlayersInfoWith10MatchesForGuild", { steamAccountIds }, (data) => !!data?.players);
2449
2452
  }
2450
2453
  async queryPlayerInfoWith25Matches({ steamAccountId, heroIds }) {
2451
2454
  return this.query(
@@ -2454,17 +2457,17 @@ var StratzAPI = class extends import_koishi8.Service {
2454
2457
  steamAccountId,
2455
2458
  heroIds
2456
2459
  },
2457
- (data) => !!data.player
2460
+ (data) => !!data?.player
2458
2461
  );
2459
2462
  }
2460
2463
  async queryPlayersLastMatchRankInfo({ steamAccountIds }) {
2461
- return this.query("PlayersLastmatchRankinfo", { steamAccountIds }, (data) => !!data.players);
2464
+ return this.query("PlayersLastmatchRankinfo", { steamAccountIds }, (data) => !!data?.players);
2462
2465
  }
2463
2466
  async queryConstants(languageTag) {
2464
- return this.query("Constants", { language: this.ctx.dota2tracker.i18n.getGraphqlLanguageTag(languageTag) }, (data) => !!data.constants);
2467
+ return this.query("Constants", { language: this.ctx.dota2tracker.i18n.getGraphqlLanguageTag(languageTag) }, (data) => !!data?.constants);
2465
2468
  }
2466
2469
  async queryMatchInfo(matchId) {
2467
- return this.query("MatchInfo", { matchId }, (data) => !!data.match);
2470
+ return this.query("MatchInfo", { matchId }, (data) => !!data?.match);
2468
2471
  }
2469
2472
  async requestParseMatch(matchId) {
2470
2473
  const response = await this.query("RequestMatchDataAnalysis", {
@@ -2525,7 +2528,7 @@ var StratzAPI = class extends import_koishi8.Service {
2525
2528
  });
2526
2529
  }
2527
2530
  loadGraphqlFile(queryName) {
2528
- return import_fs.default.readFileSync(import_path.default.join(this.pluginDir, "queries", `${queryName}.graphql`), { encoding: "utf-8" }).replace(/[\r\n]+/g, " ");
2531
+ return import_fs.default.readFileSync(import_path.default.join(this.graphqlQueriesDir, `${queryName}.graphql`), { encoding: "utf-8" }).replace(/[\r\n]+/g, " ");
2529
2532
  }
2530
2533
  };
2531
2534
  var MiniQueue = class {
@@ -2592,7 +2595,7 @@ var ValveAPI = class extends import_koishi9.Service {
2592
2595
  constructor(ctx) {
2593
2596
  super(ctx, "dota2tracker.valve-api", true);
2594
2597
  this.config = ctx.config;
2595
- this.http = import_axios3.default.create({ timeout: 1e4, signal: this.abortController.signal, baseURL: this.baseURL });
2598
+ this.http = import_axios3.default.create({ timeout: 15e3, signal: this.abortController.signal, baseURL: this.baseURL });
2596
2599
  ctx.on("dispose", () => this.dispose());
2597
2600
  }
2598
2601
  dispose() {
@@ -2666,14 +2669,15 @@ var ImageFormat = /* @__PURE__ */ ((ImageFormat2) => {
2666
2669
  // src/app/presentation/image.renderer.ts
2667
2670
  var import_luxon5 = require("luxon");
2668
2671
  var ImageRenderer = class extends import_koishi10.Service {
2669
- constructor(ctx, pluginDir3) {
2670
- super(ctx, "dota2tracker.image", true);
2671
- this.pluginDir = pluginDir3;
2672
- this.config = ctx.config;
2673
- }
2674
2672
  static {
2675
2673
  __name(this, "ImageRenderer");
2676
2674
  }
2675
+ templateDir;
2676
+ constructor(ctx, currentDir) {
2677
+ super(ctx, "dota2tracker.image", true);
2678
+ this.config = ctx.config;
2679
+ this.templateDir = import_path2.default.join(currentDir, "templates");
2680
+ }
2677
2681
  async renderToImageByFile(data, templateName, type, languageTag) {
2678
2682
  const html = await this.generateHTML(data, { source: "FILE", templateName, type }, languageTag);
2679
2683
  return this.ctx.puppeteer.render(html);
@@ -2701,7 +2705,7 @@ var ImageRenderer = class extends import_koishi10.Service {
2701
2705
  try {
2702
2706
  let html;
2703
2707
  if (template.source === "FILE") {
2704
- const templatePath = import_path2.default.join(this.pluginDir, "template", template.type, `${template.templateName}.ejs`);
2708
+ const templatePath = import_path2.default.join(this.templateDir, template.type, `${template.templateName}.ejs`);
2705
2709
  html = await import_ejs.default.renderFile(templatePath, templateData, {
2706
2710
  strict: false
2707
2711
  });
@@ -2711,7 +2715,7 @@ var ImageRenderer = class extends import_koishi10.Service {
2711
2715
  async: true
2712
2716
  });
2713
2717
  }
2714
- if (process.env.NODE_ENV === "development") import_fs2.default.writeFileSync(import_path2.default.join(this.pluginDir, "temp.html"), html);
2718
+ if (process.env.NODE_ENV === "development") import_fs2.default.writeFileSync(import_path2.default.resolve(process.cwd(), "temp.html"), html);
2715
2719
  return html;
2716
2720
  } catch (error) {
2717
2721
  this.logger.error(error);
@@ -2721,8 +2725,8 @@ var ImageRenderer = class extends import_koishi10.Service {
2721
2725
  getImageUrl(image, type = "local" /* Local */, format = "png" /* png */) {
2722
2726
  if (type === "local" /* Local */) {
2723
2727
  try {
2724
- if (format === "svg" /* svg */) return import_fs2.default.readFileSync(import_path2.default.join(this.pluginDir, "template", "images", `${image}.svg`));
2725
- const imageData = import_fs2.default.readFileSync(import_path2.default.join(this.pluginDir, "template", "images", `${image}.${format}`));
2728
+ if (format === "svg" /* svg */) return import_fs2.default.readFileSync(import_path2.default.join(this.templateDir, "images", `${image}.svg`));
2729
+ const imageData = import_fs2.default.readFileSync(import_path2.default.join(this.templateDir, "images", `${image}.${format}`));
2726
2730
  const base64Data = imageData.toString("base64");
2727
2731
  return `data:image/png;base64,${base64Data}`;
2728
2732
  } catch (error) {
@@ -3456,20 +3460,25 @@ __name(resolvePlayerAndHandleErrors, "resolvePlayerAndHandleErrors");
3456
3460
  // src/app/commands/hero-of-the-day.command.ts
3457
3461
  var import_luxon10 = require("luxon");
3458
3462
  function registerHeroOfTheDayCommand(ctx) {
3459
- ctx.command("dota2tracker.hero-of-the-day <input_data>").alias("今日英雄").option("days", "-d <value:number>").action(async ({ session, options }, input_data) => {
3460
- const steamId = await resolvePlayerAndHandleErrors(ctx, session, input_data);
3461
- if (steamId === null) return;
3462
- const days = clamp(options.days, 1, 180, 30);
3463
- const result = await ctx.dota2tracker.stratzAPI.queryPlayerPerformanceForHeroRecommendation({
3464
- steamAccountId: steamId,
3465
- recentDateTime: import_luxon10.DateTime.now().minus({ days }).toUnixInteger()
3466
- });
3467
- const recommendationPromise = ctx.dota2tracker.player.getHeroRecommendation(steamId, result.player);
3468
- const metaPromise = ctx.dota2tracker.hero.getWeeklyHeroMeta(PlayerService.estimateWeightedRank(result.player));
3469
- const [recommendation, weeklyHeroMeta] = await Promise.all([recommendationPromise, metaPromise]);
3470
- const languageTag = await ctx.dota2tracker.i18n.getLanguageTag({ session });
3471
- const message = ctx.dota2tracker.messageBuilder.buildHeroOfTheDayMessage(languageTag, recommendation, weeklyHeroMeta);
3472
- return message;
3463
+ const name2 = "hero-of-the-day";
3464
+ ctx.command(`dota2tracker.${name2} <input_data>`).alias("今日英雄").option("days", "-d <value:number>").action(async ({ session, options }, input_data) => {
3465
+ try {
3466
+ const steamId = await resolvePlayerAndHandleErrors(ctx, session, input_data);
3467
+ if (steamId === null) return;
3468
+ const days = clamp(options.days, 1, 180, 30);
3469
+ const result = await ctx.dota2tracker.stratzAPI.queryPlayerPerformanceForHeroRecommendation({
3470
+ steamAccountId: steamId,
3471
+ recentDateTime: import_luxon10.DateTime.now().minus({ days }).toUnixInteger()
3472
+ });
3473
+ const recommendationPromise = ctx.dota2tracker.player.getHeroRecommendation(steamId, result.player);
3474
+ const metaPromise = ctx.dota2tracker.hero.getWeeklyHeroMeta(PlayerService.estimateWeightedRank(result.player));
3475
+ const [recommendation, weeklyHeroMeta] = await Promise.all([recommendationPromise, metaPromise]);
3476
+ const languageTag = await ctx.dota2tracker.i18n.getLanguageTag({ session });
3477
+ const message = ctx.dota2tracker.messageBuilder.buildHeroOfTheDayMessage(languageTag, recommendation, weeklyHeroMeta);
3478
+ return message;
3479
+ } catch (error) {
3480
+ handleError(error, ctx.logger(name2), ctx.dota2tracker.i18n, ctx.config);
3481
+ }
3473
3482
  });
3474
3483
  }
3475
3484
  __name(registerHeroOfTheDayCommand, "registerHeroOfTheDayCommand");
@@ -3477,14 +3486,19 @@ __name(registerHeroOfTheDayCommand, "registerHeroOfTheDayCommand");
3477
3486
  // src/app/commands/query-hero.command.ts
3478
3487
  function registerQueryHeroCommand(ctx) {
3479
3488
  ctx.command("dota2tracker.query-hero <input_data>").option("random", "-r").alias("查询英雄").action(async ({ session, options }, input_data) => {
3480
- if (input_data) {
3481
- await session.send(session.text(".querying_hero"));
3482
- const languageTag = await ctx.dota2tracker.i18n.getLanguageTag({ session });
3483
- const heroData = await ctx.dota2tracker.hero.getHeroDetails(input_data, languageTag, options.random);
3484
- if (!heroData) return session.text(".not_found");
3485
- const image = await ctx.dota2tracker.image.renderToImageByFile(heroData, ctx.config.template_hero, "hero" /* Hero */, languageTag);
3486
- const message = ctx.dota2tracker.messageBuilder.buildHeroMessage(heroData);
3487
- await session.send(message + image);
3489
+ const name2 = "query-hero";
3490
+ try {
3491
+ if (input_data || options.random) {
3492
+ await session.send(session.text(".querying_hero"));
3493
+ const languageTag = await ctx.dota2tracker.i18n.getLanguageTag({ session });
3494
+ const heroData = await ctx.dota2tracker.hero.getHeroDetails(input_data, languageTag, options.random);
3495
+ if (!heroData) return session.text(".not_found");
3496
+ const image = await ctx.dota2tracker.image.renderToImageByFile(heroData, ctx.config.template_hero, "hero" /* Hero */, languageTag);
3497
+ const message = ctx.dota2tracker.messageBuilder.buildHeroMessage(heroData);
3498
+ await session.send(message + image);
3499
+ }
3500
+ } catch (error) {
3501
+ handleError(error, ctx.logger(name2), ctx.dota2tracker.i18n, ctx.config);
3488
3502
  }
3489
3503
  });
3490
3504
  }
@@ -3540,25 +3554,40 @@ __name(registerQueryItemCommand, "registerQueryItemCommand");
3540
3554
  // src/app/commands/query-match.command.ts
3541
3555
  function registerQueryMatchCommand(ctx) {
3542
3556
  ctx.command("dota2tracker.query-match <match_id>").alias("查询比赛").option("parse", "-p").option("template", "-t <value:string>").action(async ({ session, options }, match_id) => {
3543
- if (!match_id) return session.text(".empty_input");
3544
- if (!/^\d{1,11}$/.test(match_id)) return session.text(".match_id_invalid");
3545
- await session.send(session.text(".querying_match"));
3546
- return await handleQueryMatchCommand(ctx, ctx.config, session, options, match_id);
3557
+ const name2 = "query-match";
3558
+ try {
3559
+ if (!match_id) return session.text(".empty_input");
3560
+ if (!/^\d{1,11}$/.test(match_id)) return session.text(".match_id_invalid");
3561
+ await session.send(session.text(".querying_match"));
3562
+ return await handleQueryMatchCommand(ctx, ctx.config, session, options, match_id);
3563
+ } catch (error) {
3564
+ handleError(error, ctx.logger(name2), ctx.dota2tracker.i18n, ctx.config);
3565
+ }
3547
3566
  });
3548
3567
  ctx.command("dota2tracker.query-recent-match [input_data]").alias("查询最近比赛").option("parse", "-p").option("template", "-t <value:string>").action(async ({ session, options }, input_data) => {
3549
- const steamId = await resolvePlayerAndHandleErrors(ctx, session, input_data);
3550
- if (steamId === null) return;
3551
- session.send(session.text(".querying_match"));
3552
- const lastMatchId = await ctx.dota2tracker.player.getLastMatchId(Number(steamId));
3553
- if (!lastMatchId?.matchId) return session.text(".query_failed");
3554
- if (lastMatchId.isAnonymous) return session.text(".is_anonymous");
3555
- return await handleQueryMatchCommand(ctx, ctx.config, session, options, lastMatchId.matchId);
3568
+ const name2 = "query-recent-match";
3569
+ try {
3570
+ const steamId = await resolvePlayerAndHandleErrors(ctx, session, input_data);
3571
+ if (steamId === null) return;
3572
+ session.send(session.text(".querying_match"));
3573
+ let lastMatch;
3574
+ try {
3575
+ lastMatch = await ctx.dota2tracker.player.getLastMatchId(Number(steamId));
3576
+ } catch (error) {
3577
+ handleError(error, ctx.logger(name2), ctx.dota2tracker.i18n, ctx.config);
3578
+ }
3579
+ if (!lastMatch?.id) return session.text(".query_failed");
3580
+ if (lastMatch.isAnonymous) return session.text(".is_anonymous");
3581
+ return await handleQueryMatchCommand(ctx, ctx.config, session, options, lastMatch.id);
3582
+ } catch (error) {
3583
+ handleError(error, ctx.logger(name2), ctx.dota2tracker.i18n, ctx.config);
3584
+ }
3556
3585
  });
3557
3586
  }
3558
3587
  __name(registerQueryMatchCommand, "registerQueryMatchCommand");
3559
3588
  async function handleQueryMatchCommand(ctx, config, session, options, matchId) {
3560
3589
  const languageTag = await ctx.dota2tracker.i18n.getLanguageTag({ session });
3561
- const result = await ctx.dota2tracker.match.getMatchResult({ matchId: Number(matchId), requestParse: options.parse });
3590
+ const result = await ctx.dota2tracker.match.getMatchResult({ matchId: Number(matchId), waitForParse: options.parse, allowFallback: config.enableOpenDotaFallback });
3562
3591
  if (result.status === "PENDING") {
3563
3592
  const subscriber = ctx.dota2tracker.parsePolling.createSubscriberByCommand(session, languageTag, { templateName: options?.template });
3564
3593
  ctx.dota2tracker.parsePolling.add(result.matchId, [subscriber]);
@@ -3569,7 +3598,6 @@ async function handleQueryMatchCommand(ctx, config, session, options, matchId) {
3569
3598
  const formattedMatchData = await ctx.dota2tracker.match.generateMatchData(result.matchData, languageTag);
3570
3599
  const message = ctx.dota2tracker.messageBuilder.buildMatchMessage(languageTag, formattedMatchData, []);
3571
3600
  const image = await ctx.dota2tracker.image.renderToImageByFile(formattedMatchData, options.template || config.template_match, "match" /* Match */, languageTag);
3572
- await ctx.dota2tracker.image.renderToImageByFile(formattedMatchData, options.template || config.template_match, "match" /* Match */, languageTag);
3573
3601
  return message + image;
3574
3602
  }
3575
3603
  }
@@ -3590,18 +3618,23 @@ __name(registerQueryMembersCommand, "registerQueryMembersCommand");
3590
3618
  // src/app/commands/query-player.command.ts
3591
3619
  function registerQueryPlayerCommand(ctx) {
3592
3620
  ctx.command("dota2tracker.query-player <input_data>").option("hero", "-o <value:string>").alias("查询玩家").action(async ({ session, options }, input_data) => {
3593
- if (session.guild || !session.guild && input_data) {
3594
- const steamId = await resolvePlayerAndHandleErrors(ctx, session, input_data);
3595
- if (steamId === null) return;
3596
- session.send(session.text(".querying_player"));
3597
- const heroId = ctx.dota2tracker.i18n.findHeroIdInLocale(options.hero);
3598
- const languageTag = await ctx.dota2tracker.i18n.getLanguageTag({ session });
3599
- const formattedPlayerData = await ctx.dota2tracker.player.getFormattedPlayerData(steamId, heroId, languageTag);
3600
- const image = await ctx.dota2tracker.image.renderToImageByFile(formattedPlayerData, ctx.config.template_player, "player" /* Player */, languageTag);
3601
- const message = ctx.dota2tracker.messageBuilder.buildPlayerMessage(steamId);
3602
- return message + image;
3603
- } else {
3604
- return session.text("commands.dota2tracker.common.messages.user_not_in_group");
3621
+ const name2 = "query-player";
3622
+ try {
3623
+ if (session.guild || !session.guild && input_data) {
3624
+ const steamId = await resolvePlayerAndHandleErrors(ctx, session, input_data);
3625
+ if (steamId === null) return;
3626
+ session.send(session.text(".querying_player"));
3627
+ const heroId = ctx.dota2tracker.i18n.findHeroIdInLocale(options.hero);
3628
+ const languageTag = await ctx.dota2tracker.i18n.getLanguageTag({ session });
3629
+ const formattedPlayerData = await ctx.dota2tracker.player.getFormattedPlayerData(steamId, heroId, languageTag);
3630
+ const image = await ctx.dota2tracker.image.renderToImageByFile(formattedPlayerData, ctx.config.template_player, "player" /* Player */, languageTag);
3631
+ const message = ctx.dota2tracker.messageBuilder.buildPlayerMessage(steamId);
3632
+ return message + image;
3633
+ } else {
3634
+ return session.text("commands.dota2tracker.common.messages.user_not_in_group");
3635
+ }
3636
+ } catch (error) {
3637
+ handleError(error, ctx.logger(name2), ctx.dota2tracker.i18n, ctx.config);
3605
3638
  }
3606
3639
  });
3607
3640
  }
@@ -3686,7 +3719,7 @@ var OpenDotaAPI = class extends import_koishi15.Service {
3686
3719
  constructor(ctx) {
3687
3720
  super(ctx, "dota2tracker.opendota-api", true);
3688
3721
  this.config = ctx.config;
3689
- this.http = import_axios4.default.create({ timeout: 1e4, signal: this.abortController.signal, baseURL: this.BASE_URL });
3722
+ this.http = import_axios4.default.create({ timeout: 15e3, signal: this.abortController.signal, baseURL: this.BASE_URL });
3690
3723
  ctx.on("dispose", () => this.dispose());
3691
3724
  }
3692
3725
  dispose() {
@@ -4178,7 +4211,7 @@ var globRequire_locales_schema_yml = __glob({
4178
4211
  });
4179
4212
 
4180
4213
  // src/config.ts
4181
- var pluginDir = import_path4.default.resolve(__dirname, "..");
4214
+ var templateDir = import_path4.default.join(__dirname, "templates");
4182
4215
  var allI18nConfigs = Object.fromEntries(Object.keys(LanguageTags).map((lang) => [lang, globRequire_locales_schema_yml(`./locales/${lang}.schema.yml`)._config]));
4183
4216
  var Config = import_koishi17.Schema.intersect([
4184
4217
  import_koishi17.Schema.intersect([
@@ -4249,9 +4282,9 @@ var Config = import_koishi17.Schema.intersect([
4249
4282
  ]).i18n(getI18n("report"))
4250
4283
  ]),
4251
4284
  import_koishi17.Schema.object({
4252
- template_match: import_koishi17.Schema.union([...readDirectoryFilesSync(import_path4.default.join(pluginDir, "template", "match"))]).default("match_1"),
4253
- template_player: import_koishi17.Schema.union([...readDirectoryFilesSync(import_path4.default.join(pluginDir, "template", "player"))]).default("player_1"),
4254
- template_hero: import_koishi17.Schema.union([...readDirectoryFilesSync(import_path4.default.join(pluginDir, "template", "hero"))]).default("hero_1"),
4285
+ template_match: import_koishi17.Schema.union([...readDirectoryFilesSync(import_path4.default.join(templateDir, "match"))]).default("match_1"),
4286
+ template_player: import_koishi17.Schema.union([...readDirectoryFilesSync(import_path4.default.join(templateDir, "player"))]).default("player_1"),
4287
+ template_hero: import_koishi17.Schema.union([...readDirectoryFilesSync(import_path4.default.join(templateDir, "hero"))]).default("hero_1"),
4255
4288
  playerRankEstimate: import_koishi17.Schema.boolean().default(true),
4256
4289
  templateFonts: import_koishi17.Schema.array(String).default([]).role("table")
4257
4290
  }).i18n(getI18n("template"))
@@ -4285,13 +4318,13 @@ var inject = {
4285
4318
  required: ["database", "puppeteer", "cache"],
4286
4319
  optional: ["cron", "console"]
4287
4320
  };
4288
- var pluginDir2 = import_path5.default.resolve(__dirname, "..");
4289
- var pluginVersion = require(import_path5.default.join(pluginDir2, "package.json")).version;
4290
4321
  async function apply(ctx, config) {
4291
4322
  const logger = ctx.logger("dota2tracker");
4323
+ const currentDir = import_path5.default.resolve(__dirname);
4324
+ const pluginVersion = require(import_path5.default.join(currentDir, "..", "package.json")).version;
4292
4325
  ctx.dota2tracker = {};
4293
4326
  ctx.dota2tracker.i18n = new I18NService(ctx);
4294
- ctx.dota2tracker.image = new ImageRenderer(ctx, pluginDir2);
4327
+ ctx.dota2tracker.image = new ImageRenderer(ctx, currentDir);
4295
4328
  ctx.dota2tracker.messageBuilder = new MessageBuilder(ctx);
4296
4329
  ctx.dota2tracker.match = new MatchService(ctx, pluginVersion);
4297
4330
  ctx.dota2tracker.player = new PlayerService(ctx);
@@ -4311,7 +4344,7 @@ async function apply(ctx, config) {
4311
4344
  ctx.dota2tracker.cache = new CacheService(ctx);
4312
4345
  ctx.dota2tracker.database = new DatabaseService(ctx);
4313
4346
  ctx.dota2tracker.valveAPI = new ValveAPI(ctx);
4314
- ctx.dota2tracker.stratzAPI = new StratzAPI(ctx, pluginDir2);
4347
+ ctx.dota2tracker.stratzAPI = new StratzAPI(ctx, currentDir);
4315
4348
  if (config.enableOpenDotaFallback) {
4316
4349
  ctx.dota2tracker.opendotaAPI = new OpenDotaAPI(ctx);
4317
4350
  ctx.dota2tracker.opendotaAdapter = new OpenDotaAdapter(ctx);
@@ -4327,7 +4360,7 @@ async function apply(ctx, config) {
4327
4360
  registerQueryHeroCommand(ctx);
4328
4361
  registerQueryItemCommand(ctx);
4329
4362
  registerHeroOfTheDayCommand(ctx);
4330
- if (config.enableConsole) registerConsolePage(ctx);
4363
+ if (ctx.console && config.enableConsole) registerConsolePage(ctx);
4331
4364
  }
4332
4365
  __name(apply, "apply");
4333
4366
  // Annotate the CommonJS export names for ESM import in node:
@@ -20,6 +20,19 @@ query MatchInfo($matchId: Long!) {
20
20
  radiantNetworthLeads
21
21
  radiantExperienceLeads
22
22
  winRates
23
+
24
+ towerStatusRadiant
25
+ towerStatusDire
26
+ barracksStatusRadiant
27
+ barracksStatusDire
28
+
29
+ playbackData {
30
+ buildingEvents {
31
+ time
32
+ npcId
33
+ }
34
+ }
35
+
23
36
  players {
24
37
  steamAccountId
25
38
  steamAccount {
@@ -0,0 +1,20 @@
1
+ <svg style="display:none"><defs><symbol id="tower" viewBox="0 0 64 64"><rect x="8" y="8" width="48" height="48" fill="#000"/><rect x="14" y="14" width="36" height="36" fill="currentColor"/><rect x="14" y="36" width="36" height="14" fill="#000" fill-opacity="0.2"/></symbol><symbol id="tower_angle" viewBox="0 0 64 64"><path d="M32,4 L8,22 L8,40 L32,58 L56,40 L56,22 Z" stroke="#000" stroke-width="10" stroke-linejoin="round"/><path d="M32,4 L8,22 L8,40 L32,58 L56,40 L56,22 Z" fill="currentColor"/><path d="M8,22 L32,40 L32,58 L8,40 Z" fill="#000" fill-opacity="0.2"/><path d="M56,22 L32,40 L32,58 L56,40 Z" fill="#000" fill-opacity="0.4"/></symbol><symbol id="rax" viewBox="0 0 64 64"><rect x="8" y="8" width="48" height="48" fill="#000"/><rect x="14" y="14" width="36" height="36" fill="currentColor"/><path d="M 14 14 L 14 39 L 19 34 L 19 16 Z" fill="#000" fill-opacity="0.05"/><path d="M 14 14 L 50 14 L 45 16 L 19 16 Z" fill="#000" fill-opacity="0.5"/><path d="M 45 16 L 50 14 L 50 39 L 45 34 Z" fill="#000" fill-opacity="0.25"/><path d="M 14 39 L 19 34 L 45 34 L 50 39 Z" fill="#000" fill-opacity="0.15"/><rect x="14" y="39" width="36" height="11" fill="#000" fill-opacity="0.25"/><rect x="19" y="8" width="26" height="26" fill="#000"/><rect x="20" y="9" width="24" height="24" fill="currentColor"/><path d="M 20 9 L 32 13 L 20 25 Z" fill="#000" fill-opacity="0.05"/><path d="M 20 9 L 32 13 L 44 9 Z" fill="#000" fill-opacity="0.5"/><path d="M 44 9 L 32 13 L 44 25 Z" fill="#000" fill-opacity="0.25"/><path d="M 20 25 L 32 13 L 44 25 Z" fill="#000" fill-opacity="0.15"/><rect x="20" y="25" width="24" height="8" fill="#000" fill-opacity="0.25"/></symbol><symbol id="rax_angle" viewBox="0 0 48 48"><path d="M14 12 L24 4 L34 12 L34 18 L24 26 L14 18 Z" fill="#000" stroke="#000" stroke-width="8" stroke-linejoin="round"/><path d="M6 22 L24 6 L42 22 L42 30 L24 44 L6 30 Z" fill="#000" stroke="#000" stroke-width="8" stroke-linejoin="round"/><path d="M6 22 L24 6 L42 22 L42 30 L24 44 L6 30 Z" fill="currentColor"/><path d="M6 22 L24 36 L24 44 L6 30 Z" fill="#000" fill-opacity="0.25"/><path d="M42 22 L24 36 L24 44 L42 30 Z" fill="#000" fill-opacity="0.35"/><path d="M6 22 L24 13 L24 6 Z" fill="#000" fill-opacity="0.22"/><path d="M42 22 L24 13 L24 6 Z" fill="#000" fill-opacity="0.38"/><path d="M42 22 L24 13 L24 36 Z" fill="#000" fill-opacity="0.32"/><path d="M14 12 L24 4 L34 12 L34 18 L24 26 L14 18 Z" fill="#000" stroke="#000" stroke-width="2"/><path d="M14 12 L24 4 L34 12 L34 18 L24 26 L14 18 Z" fill="currentColor"/><path d="M14 12 L24 20 L24 26 L14 18 Z" fill="#000" fill-opacity="0.25"/><path d="M34 12 L24 20 L24 26 L34 18 Z" fill="#000" fill-opacity="0.35"/><path d="M14 12 L24 4 L24 7Z" fill="#000" fill-opacity="0.22"/><path d="M34 12 L24 4 L24 7Z" fill="#000" fill-opacity="0.38"/><path d="M24 20 L24 7L34 12 Z" fill="#000" fill-opacity="0.32"/></symbol><symbol id="fort" viewBox="0 0 64 64"><path fill="#000" stroke="#000" stroke-width="10" stroke-linejoin="round" d="M 50.5 23.7
2
+ L 48.4 20.2
3
+ L 45.4 15.1 L 41.0 18.0 L 39.1 15.8 L 38.2 9.9 L 37.7 6.9 L 35.3 6.9
4
+ L 32.5 8.3 L 27.2 14.5 L 24.1 19.5 L 22.9 20.2 L 21.7 20.4 L 19.3 19.7
5
+ L 15.4 23.5 L 15.4 24.9 L 19.7 29.6 L 19.5 32.8 L 17.9 34.5 L 15.4 34.9 L 15.3 37.2
6
+ L 23.7 49.2 L 37.1 57.5 L 52.4 37.8 L 52.5 27.7 L 47.1 30.0 L 50.5 23.7 Z"/><path fill="currentColor" d="M 50.5 23.7
7
+ L 48.4 20.2
8
+ L 45.4 15.1 L 41.0 18.0 L 39.1 15.8 L 38.2 9.9 L 37.7 6.9 L 35.3 6.9
9
+ L 32.5 8.3 L 27.2 14.5 L 24.1 19.5 L 22.9 20.2 L 21.7 20.4 L 19.3 19.7
10
+ L 15.4 23.5 L 15.4 24.9 L 19.7 29.6 L 19.5 32.8 L 17.9 34.5 L 15.4 34.9 L 15.3 37.2
11
+ L 23.7 49.2 L 37.1 57.5 L 52.4 37.8 L 52.5 27.7 L 47.1 30.0 L 50.5 23.7 Z"/><path fill="#000" fill-opacity="0.25" d="M 39.1 15.8 L 29.3 28.5 L 31.1 34.2 L 26.5 40.5 L 28.2 49.5 L 37.1 57.5
12
+ L 23.7 49.2 L 15.3 37.2 L 22.1 39.9 L 22.3 34.9 L 21.1 33.3 L 19.5 32.8
13
+ L 19.7 29.6 L 15.4 24.9 C 15.4 25.0 15.4 23.5 15.4 23.5 L 16.8 22.3
14
+ L 23.2 30.1 L 26.1 27.2 L 25.9 23.2 L 27.4 20.6 L 29.1 17.0 L 38.2 9.9 L 39.1 15.8 Z
15
+ M 36.3 52.7 L 41.4 39.0 L 45.8 34.8 L 52.5 31.5 L 52.5 27.7 L 47.1 30.0
16
+ L 44.3 33.0 L 39.9 30.7 L 33.8 36.0 L 38.6 39.0 L 29.7 46.5 L 36.3 52.7 Z"/><path fill="#000" fill-opacity="0.4" d="M 33.8 36.0 L 39.9 30.7 L 44.3 33.0 L 47.1 30.0
17
+ L 50.5 23.7 L 48.4 20.2 L 37.0 27.9 L 41.0 18.0 L 39.1 15.8
18
+ L 29.3 28.5 L 31.1 34.2 L 33.8 36.0 Z
19
+ M 26.5 40.5 L 29.7 46.5 L 36.3 52.7 L 41.4 39.0 L 45.8 34.8 L 52.5 31.5
20
+ L 52.5 37.8 L 37.1 57.5 L 28.2 49.5 L 26.5 40.5 Z"/></symbol></defs></svg>
@@ -0,0 +1 @@
1
+ /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}