@sjtdev/koishi-plugin-dota2tracker 2.2.2 → 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 (106) hide show
  1. package/changelog.md +32 -0
  2. package/dist/index.js +1 -1
  3. package/lib/index.js +128 -94
  4. package/{queries → lib/queries}/MatchInfo.graphql +13 -0
  5. package/lib/templates/common/components/building_icons.ejs +20 -0
  6. package/lib/templates/common/styles/normalize.min.css +1 -0
  7. package/lib/templates/hero/hero_1.ejs +69 -0
  8. package/lib/templates/images/7.38_simple_minimap.png +0 -0
  9. package/lib/templates/item/item/recipe.ejs +9 -0
  10. package/lib/templates/item/item/style.css +1 -0
  11. package/lib/templates/item/item.ejs +52 -0
  12. package/lib/templates/item/itemlist.ejs +11 -0
  13. package/lib/templates/match/match_1/base.css +1 -0
  14. package/lib/templates/match/match_1/item.ejs +1 -0
  15. package/lib/templates/match/match_1/main.ejs +8 -0
  16. package/lib/templates/match/match_1/player.ejs +1 -0
  17. package/lib/templates/match/match_1/style.css +1 -0
  18. package/lib/templates/match/match_1.ejs +18 -0
  19. package/lib/templates/match/match_2/original.css +1 -0
  20. package/lib/templates/match/match_2/original.ejs +10 -0
  21. package/lib/templates/match/match_2+/charts.ejs +1 -0
  22. package/lib/templates/match/match_2+/extra.css +1 -0
  23. package/lib/templates/match/match_2+/lane_outcome.ejs +56 -0
  24. package/lib/templates/match/match_2+/map.ejs +160 -0
  25. package/lib/templates/match/match_2+.ejs +1 -0
  26. package/lib/templates/match/match_2.ejs +1 -0
  27. package/lib/templates/player/player_1/base.css +1 -0
  28. package/lib/templates/player/player_1/private.ejs +1 -0
  29. package/lib/templates/player/player_1.ejs +78 -0
  30. package/lib/templates/rank/rank_fun.ejs +1 -0
  31. package/lib/templates/report/daily/base.css +1 -0
  32. package/lib/templates/report/daily.ejs +29 -0
  33. package/package.json +2 -2
  34. package/template/hero/hero_1.ejs +0 -900
  35. package/template/item/item/recipe.ejs +0 -51
  36. package/template/item/item/style.css +0 -244
  37. package/template/item/item.ejs +0 -140
  38. package/template/item/itemlist.ejs +0 -99
  39. package/template/match/match_1/item.ejs +0 -11
  40. package/template/match/match_1/main.ejs +0 -37
  41. package/template/match/match_1/player.ejs +0 -154
  42. package/template/match/match_1/style.css +0 -764
  43. package/template/match/match_1.ejs +0 -56
  44. package/template/match/match_2/original.css +0 -463
  45. package/template/match/match_2/original.ejs +0 -192
  46. package/template/match/match_2+/charts.ejs +0 -261
  47. package/template/match/match_2+/extra.css +0 -143
  48. package/template/match/match_2+/lane_outcome.ejs +0 -157
  49. package/template/match/match_2+.ejs +0 -27
  50. package/template/match/match_2.ejs +0 -18
  51. package/template/player/player_1/private.ejs +0 -5
  52. package/template/player/player_1.ejs +0 -654
  53. package/template/rank/rank_fun.ejs +0 -131
  54. package/template/report/daily.ejs +0 -191
  55. /package/{queries → lib/queries}/Constants.graphql +0 -0
  56. /package/{queries → lib/queries}/GetWeeklyMetaByPosition.graphql +0 -0
  57. /package/{queries → lib/queries}/PlayerExtraInfo.graphql +0 -0
  58. /package/{queries → lib/queries}/PlayerInfoWith25Matches.graphql +0 -0
  59. /package/{queries → lib/queries}/PlayerPerformanceForHeroRecommendation.graphql +0 -0
  60. /package/{queries → lib/queries}/PlayersInfoWith10MatchesForGuild.graphql +0 -0
  61. /package/{queries → lib/queries}/PlayersLastmatchRankinfo.graphql +0 -0
  62. /package/{queries → lib/queries}/PlayersMatchesForDaily.graphql +0 -0
  63. /package/{queries → lib/queries}/RequestMatchDataAnalysis.graphql +0 -0
  64. /package/{queries → lib/queries}/VerifyingPlayer.graphql +0 -0
  65. /package/{template → lib/templates}/images/bei.jpg +0 -0
  66. /package/{template → lib/templates}/images/disconnected.png +0 -0
  67. /package/{template → lib/templates}/images/flag_dire.png +0 -0
  68. /package/{template → lib/templates}/images/flag_radiant.png +0 -0
  69. /package/{template → lib/templates}/images/hero_badge_1.png +0 -0
  70. /package/{template → lib/templates}/images/hero_badge_2.png +0 -0
  71. /package/{template → lib/templates}/images/hero_badge_3.png +0 -0
  72. /package/{template → lib/templates}/images/hero_badge_4.png +0 -0
  73. /package/{template → lib/templates}/images/hero_badge_5.png +0 -0
  74. /package/{template → lib/templates}/images/hero_badge_6.png +0 -0
  75. /package/{template → lib/templates}/images/lane_fail.svg +0 -0
  76. /package/{template → lib/templates}/images/lane_jungle.svg +0 -0
  77. /package/{template → lib/templates}/images/lane_stomp.svg +0 -0
  78. /package/{template → lib/templates}/images/lane_stomped.svg +0 -0
  79. /package/{template → lib/templates}/images/lane_tie.svg +0 -0
  80. /package/{template → lib/templates}/images/lane_victory.svg +0 -0
  81. /package/{template → lib/templates}/images/logo_dire.png +0 -0
  82. /package/{template → lib/templates}/images/logo_radiant.png +0 -0
  83. /package/{template → lib/templates}/images/medal_0.png +0 -0
  84. /package/{template → lib/templates}/images/medal_1.png +0 -0
  85. /package/{template → lib/templates}/images/medal_2.png +0 -0
  86. /package/{template → lib/templates}/images/medal_3.png +0 -0
  87. /package/{template → lib/templates}/images/medal_4.png +0 -0
  88. /package/{template → lib/templates}/images/medal_5.png +0 -0
  89. /package/{template → lib/templates}/images/medal_6.png +0 -0
  90. /package/{template → lib/templates}/images/medal_7.png +0 -0
  91. /package/{template → lib/templates}/images/medal_8.png +0 -0
  92. /package/{template → lib/templates}/images/medal_8b.png +0 -0
  93. /package/{template → lib/templates}/images/medal_8c.png +0 -0
  94. /package/{template → lib/templates}/images/scepter.png +0 -0
  95. /package/{template → lib/templates}/images/scepter_0.png +0 -0
  96. /package/{template → lib/templates}/images/scepter_1.png +0 -0
  97. /package/{template → lib/templates}/images/shard.png +0 -0
  98. /package/{template → lib/templates}/images/shard_0.png +0 -0
  99. /package/{template → lib/templates}/images/shard_1.png +0 -0
  100. /package/{template → lib/templates}/images/star_0.png +0 -0
  101. /package/{template → lib/templates}/images/star_1.png +0 -0
  102. /package/{template → lib/templates}/images/star_2.png +0 -0
  103. /package/{template → lib/templates}/images/star_3.png +0 -0
  104. /package/{template → lib/templates}/images/star_4.png +0 -0
  105. /package/{template → lib/templates}/images/star_5.png +0 -0
  106. /package/{template → lib/templates}/images/xi.jpg +0 -0
package/changelog.md CHANGED
@@ -1,5 +1,37 @@
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
+
25
+ ### [2.2.3](https://github.com/sjtdev/koishi-plugin-dota2tracker/compare/v2.2.2...v2.2.3) (2025-11-20)
26
+
27
+ ### 🎨 样式
28
+
29
+ * **console:** 为`OpenDotaAPI访问次数`添加“今日”使其描述更准确。 ([a77ced6](https://github.com/sjtdev/koishi-plugin-dota2tracker/commit/a77ced6619ea07612e2afbf762f0a4fa1e7a0e9a))
30
+
31
+ ### 🐛 Bug 修复
32
+
33
+ * **opendota:** 修复`cache`调用配置项失败导致`opendota`访问缓存失败的问题。 ([30fdb90](https://github.com/sjtdev/koishi-plugin-dota2tracker/commit/30fdb90a69d231590d665235f92c7f8287995278))
34
+
3
35
  ### [2.2.2](https://github.com/sjtdev/koishi-plugin-dota2tracker/compare/v2.2.1...v2.2.2) (2025-11-19)
4
36
 
5
37
  ### ✨ 新增功能
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{store as u,icons as _}from"@koishijs/client";import{resolveComponent as a,createBlock as m,openBlock as p,withCtx as n,createVNode as l,createTextVNode as s,toDisplayString as k,unref as f,createElementVNode as i,createElementBlock as h,createStaticVNode as g}from"vue";const w={__name:"page",setup(t){return(e,o)=>{const r=a("k-card"),c=a("k-comment"),d=a("k-layout");return p(),m(d,null,{default:n(()=>[l(r,null,{default:n(()=>[s("OpenDotaAPI 已访问 "+k(f(u).apiCount.opendota)+" 次。",1)]),_:1}),l(r,null,{default:n(()=>[l(c,{type:"warning"},{default:n(()=>[...o[0]||(o[0]=[i("p",null,[s("该页面正在开发中,关于这个页面有任何想法或建议欢迎投稿至 "),i("a",{target:"_blank",href:"https://github.com/sjtdev/koishi-plugin-dota2tracker/issues"},"项目issues"),s("。")],-1)])]),_:1})]),_:1})]),_:1})}}},x=(t,e)=>{const o=t.__vccOpts||t;for(const[r,c]of e)o[r]=c;return o},C={},v={xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 600 600",fill:"currentColor"};function y(t,e){return p(),h("svg",v,[...e[0]||(e[0]=[g('<g><rect height="544" width="540" y="28.5" x="32" stroke="currentColor" fill="none" stroke-width="35" rx="50" ry="58"></rect><g transform="translate(300,300) scale(0.75) translate(-300,-300)" stroke-linejoin="round" stroke-linecap="round"><path d="m95,145.5l54,-30.5l361,306l-36,86l-113,-26l-266,-335.5z" stroke="currentColor" fill="none" stroke-width="35"></path><path transform="rotate(3.87406 449.593 151.279)" d="m458.26078,90.18786c0,0 61.75777,47.47474 61.75777,46.77658c0,0.69816 -8.66776,76.09922 -8.66776,75.40106c0,0.69816 -132.18329,-101.23291 -132.18329,-101.93106c0,0.69816 79.09328,-20.24658 79.09328,-20.24658z" stroke="currentColor" fill="none" stroke-width="35"></path><path d="m84,466.55926c0,0 16.71429,-92.78604 16.71429,-93.55926c0,0.77322 127.28571,110.57003 127.28571,109.79682c0,0.77322 -96.42857,23.96973 -96.42857,23.19651c0,0.77322 -47.57143,-39.43407 -47.57143,-39.43407z" stroke="currentColor" fill="none" stroke-width="35"></path></g></g>',1)])])}const B=x(C,[["render",y]]),V=t=>{_.register("dota2",B),t.page({name:"DOTA2Tracker(实验性)",path:"/dota2tracker",component:w,fields:["apiCount"],icon:"dota2"})};export{V as default};
1
+ import{store as u,icons as _}from"@koishijs/client";import{resolveComponent as a,createBlock as m,openBlock as p,withCtx as n,createVNode as l,createTextVNode as s,toDisplayString as k,unref as f,createElementVNode as i,createElementBlock as h,createStaticVNode as g}from"vue";const w={__name:"page",setup(t){return(e,o)=>{const r=a("k-card"),c=a("k-comment"),d=a("k-layout");return p(),m(d,null,{default:n(()=>[l(r,null,{default:n(()=>[s("OpenDotaAPI 今日已访问 "+k(f(u).apiCount.opendota)+" 次。",1)]),_:1}),l(r,null,{default:n(()=>[l(c,{type:"warning"},{default:n(()=>[...o[0]||(o[0]=[i("p",null,[s("该页面正在开发中,关于这个页面有任何想法或建议欢迎投稿至 "),i("a",{target:"_blank",href:"https://github.com/sjtdev/koishi-plugin-dota2tracker/issues"},"项目issues"),s("。")],-1)])]),_:1})]),_:1})]),_:1})}}},x=(t,e)=>{const o=t.__vccOpts||t;for(const[r,c]of e)o[r]=c;return o},C={},v={xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 600 600",fill:"currentColor"};function y(t,e){return p(),h("svg",v,[...e[0]||(e[0]=[g('<g><rect height="544" width="540" y="28.5" x="32" stroke="currentColor" fill="none" stroke-width="35" rx="50" ry="58"></rect><g transform="translate(300,300) scale(0.75) translate(-300,-300)" stroke-linejoin="round" stroke-linecap="round"><path d="m95,145.5l54,-30.5l361,306l-36,86l-113,-26l-266,-335.5z" stroke="currentColor" fill="none" stroke-width="35"></path><path transform="rotate(3.87406 449.593 151.279)" d="m458.26078,90.18786c0,0 61.75777,47.47474 61.75777,46.77658c0,0.69816 -8.66776,76.09922 -8.66776,75.40106c0,0.69816 -132.18329,-101.23291 -132.18329,-101.93106c0,0.69816 79.09328,-20.24658 79.09328,-20.24658z" stroke="currentColor" fill="none" stroke-width="35"></path><path d="m84,466.55926c0,0 16.71429,-92.78604 16.71429,-93.55926c0,0.77322 127.28571,110.57003 127.28571,109.79682c0,0.77322 -96.42857,23.96973 -96.42857,23.19651c0,0.77322 -47.57143,-39.43407 -47.57143,-39.43407z" stroke="currentColor" fill="none" stroke-width="35"></path></g></g>',1)])])}const B=x(C,[["render",y]]),V=t=>{_.register("dota2",B),t.page({name:"DOTA2Tracker(实验性)",path:"/dota2tracker",component:w,fields:["apiCount"],icon:"dota2"})};export{V as default};
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({
@@ -2221,6 +2223,7 @@ var CacheService = class extends import_koishi6.Service {
2221
2223
  }
2222
2224
  constructor(ctx) {
2223
2225
  super(ctx, "dota2tracker.cache", true);
2226
+ this.config = ctx.config;
2224
2227
  }
2225
2228
  get msUntilUTCEndOfDay() {
2226
2229
  const now = import_luxon4.DateTime.utc();
@@ -2257,8 +2260,8 @@ var CacheService = class extends import_koishi6.Service {
2257
2260
  async getMatchCache(matchId) {
2258
2261
  return this.ctx.cache.get("dt_previous_query_results", String(matchId));
2259
2262
  }
2260
- setMatchCache(matchId, matchQuery, pluginVersion2) {
2261
- 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);
2262
2265
  }
2263
2266
  markMatchAsSended(matchId) {
2264
2267
  this.ctx.cache.set("dt_sended_match_id", String(matchId), void 0, DAYS_30);
@@ -2387,14 +2390,6 @@ var import_path = __toESM(require("path"));
2387
2390
  var import_axios2 = __toESM(require("axios"));
2388
2391
  var import_https_proxy_agent = require("https-proxy-agent");
2389
2392
  var StratzAPI = class extends import_koishi8.Service {
2390
- constructor(ctx, pluginDir3) {
2391
- super(ctx, "dota2tracker.stratz-api", true);
2392
- this.pluginDir = pluginDir3;
2393
- this.config = ctx.config;
2394
- this.queue = new MiniQueue(ctx, { interval: 200 });
2395
- this.http = import_axios2.default.create({ timeout: 1e4, signal: this.abortController.signal });
2396
- ctx.on("dispose", () => this.dispose());
2397
- }
2398
2393
  static {
2399
2394
  __name(this, "StratzAPI");
2400
2395
  }
@@ -2402,12 +2397,21 @@ var StratzAPI = class extends import_koishi8.Service {
2402
2397
  queue;
2403
2398
  http;
2404
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
+ }
2405
2409
  dispose() {
2406
2410
  this.queue.dispose();
2407
2411
  this.abortController.abort();
2408
2412
  }
2409
2413
  async queryGetWeeklyMetaByPosition({ bracketIds }) {
2410
- return this.query("GetWeeklyMetaByPosition", { bracketIds }, (data) => !!data.heroStats);
2414
+ return this.query("GetWeeklyMetaByPosition", { bracketIds }, (data) => !!data?.heroStats);
2411
2415
  }
2412
2416
  async queryPlayerPerformanceForHeroRecommendation({ steamAccountId, recentDateTime }) {
2413
2417
  return this.query(
@@ -2416,7 +2420,7 @@ var StratzAPI = class extends import_koishi8.Service {
2416
2420
  steamAccountId,
2417
2421
  recentDateTime
2418
2422
  },
2419
- (data) => !!data.player
2423
+ (data) => !!data?.player
2420
2424
  );
2421
2425
  }
2422
2426
  async queryPlayersMatchesForDaily(steamAccountIds, seconds) {
@@ -2426,11 +2430,11 @@ var StratzAPI = class extends import_koishi8.Service {
2426
2430
  steamAccountIds,
2427
2431
  seconds
2428
2432
  },
2429
- (data) => !!data.players
2433
+ (data) => !!data?.players
2430
2434
  );
2431
2435
  }
2432
2436
  async queryVerifyingPlayer(steamAccountId) {
2433
- return this.query("VerifyingPlayer", { steamAccountId }, (data) => !!data.player);
2437
+ return this.query("VerifyingPlayer", { steamAccountId }, (data) => !!data?.player);
2434
2438
  }
2435
2439
  async queryPlayerExtraInfo({ steamAccountId, matchCount, heroIds }) {
2436
2440
  return this.query(
@@ -2440,11 +2444,11 @@ var StratzAPI = class extends import_koishi8.Service {
2440
2444
  matchCount,
2441
2445
  heroIds
2442
2446
  },
2443
- (data) => !!data.player
2447
+ (data) => !!data?.player
2444
2448
  );
2445
2449
  }
2446
2450
  async queryPlayersInfoWith10MatchesForGuild({ steamAccountIds }) {
2447
- return this.query("PlayersInfoWith10MatchesForGuild", { steamAccountIds }, (data) => !!data.players);
2451
+ return this.query("PlayersInfoWith10MatchesForGuild", { steamAccountIds }, (data) => !!data?.players);
2448
2452
  }
2449
2453
  async queryPlayerInfoWith25Matches({ steamAccountId, heroIds }) {
2450
2454
  return this.query(
@@ -2453,17 +2457,17 @@ var StratzAPI = class extends import_koishi8.Service {
2453
2457
  steamAccountId,
2454
2458
  heroIds
2455
2459
  },
2456
- (data) => !!data.player
2460
+ (data) => !!data?.player
2457
2461
  );
2458
2462
  }
2459
2463
  async queryPlayersLastMatchRankInfo({ steamAccountIds }) {
2460
- return this.query("PlayersLastmatchRankinfo", { steamAccountIds }, (data) => !!data.players);
2464
+ return this.query("PlayersLastmatchRankinfo", { steamAccountIds }, (data) => !!data?.players);
2461
2465
  }
2462
2466
  async queryConstants(languageTag) {
2463
- 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);
2464
2468
  }
2465
2469
  async queryMatchInfo(matchId) {
2466
- return this.query("MatchInfo", { matchId }, (data) => !!data.match);
2470
+ return this.query("MatchInfo", { matchId }, (data) => !!data?.match);
2467
2471
  }
2468
2472
  async requestParseMatch(matchId) {
2469
2473
  const response = await this.query("RequestMatchDataAnalysis", {
@@ -2524,7 +2528,7 @@ var StratzAPI = class extends import_koishi8.Service {
2524
2528
  });
2525
2529
  }
2526
2530
  loadGraphqlFile(queryName) {
2527
- 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, " ");
2528
2532
  }
2529
2533
  };
2530
2534
  var MiniQueue = class {
@@ -2591,7 +2595,7 @@ var ValveAPI = class extends import_koishi9.Service {
2591
2595
  constructor(ctx) {
2592
2596
  super(ctx, "dota2tracker.valve-api", true);
2593
2597
  this.config = ctx.config;
2594
- 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 });
2595
2599
  ctx.on("dispose", () => this.dispose());
2596
2600
  }
2597
2601
  dispose() {
@@ -2665,14 +2669,15 @@ var ImageFormat = /* @__PURE__ */ ((ImageFormat2) => {
2665
2669
  // src/app/presentation/image.renderer.ts
2666
2670
  var import_luxon5 = require("luxon");
2667
2671
  var ImageRenderer = class extends import_koishi10.Service {
2668
- constructor(ctx, pluginDir3) {
2669
- super(ctx, "dota2tracker.image", true);
2670
- this.pluginDir = pluginDir3;
2671
- this.config = ctx.config;
2672
- }
2673
2672
  static {
2674
2673
  __name(this, "ImageRenderer");
2675
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
+ }
2676
2681
  async renderToImageByFile(data, templateName, type, languageTag) {
2677
2682
  const html = await this.generateHTML(data, { source: "FILE", templateName, type }, languageTag);
2678
2683
  return this.ctx.puppeteer.render(html);
@@ -2700,7 +2705,7 @@ var ImageRenderer = class extends import_koishi10.Service {
2700
2705
  try {
2701
2706
  let html;
2702
2707
  if (template.source === "FILE") {
2703
- 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`);
2704
2709
  html = await import_ejs.default.renderFile(templatePath, templateData, {
2705
2710
  strict: false
2706
2711
  });
@@ -2710,7 +2715,7 @@ var ImageRenderer = class extends import_koishi10.Service {
2710
2715
  async: true
2711
2716
  });
2712
2717
  }
2713
- 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);
2714
2719
  return html;
2715
2720
  } catch (error) {
2716
2721
  this.logger.error(error);
@@ -2720,8 +2725,8 @@ var ImageRenderer = class extends import_koishi10.Service {
2720
2725
  getImageUrl(image, type = "local" /* Local */, format = "png" /* png */) {
2721
2726
  if (type === "local" /* Local */) {
2722
2727
  try {
2723
- if (format === "svg" /* svg */) return import_fs2.default.readFileSync(import_path2.default.join(this.pluginDir, "template", "images", `${image}.svg`));
2724
- 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}`));
2725
2730
  const base64Data = imageData.toString("base64");
2726
2731
  return `data:image/png;base64,${base64Data}`;
2727
2732
  } catch (error) {
@@ -3455,20 +3460,25 @@ __name(resolvePlayerAndHandleErrors, "resolvePlayerAndHandleErrors");
3455
3460
  // src/app/commands/hero-of-the-day.command.ts
3456
3461
  var import_luxon10 = require("luxon");
3457
3462
  function registerHeroOfTheDayCommand(ctx) {
3458
- ctx.command("dota2tracker.hero-of-the-day <input_data>").alias("今日英雄").option("days", "-d <value:number>").action(async ({ session, options }, input_data) => {
3459
- const steamId = await resolvePlayerAndHandleErrors(ctx, session, input_data);
3460
- if (steamId === null) return;
3461
- const days = clamp(options.days, 1, 180, 30);
3462
- const result = await ctx.dota2tracker.stratzAPI.queryPlayerPerformanceForHeroRecommendation({
3463
- steamAccountId: steamId,
3464
- recentDateTime: import_luxon10.DateTime.now().minus({ days }).toUnixInteger()
3465
- });
3466
- const recommendationPromise = ctx.dota2tracker.player.getHeroRecommendation(steamId, result.player);
3467
- const metaPromise = ctx.dota2tracker.hero.getWeeklyHeroMeta(PlayerService.estimateWeightedRank(result.player));
3468
- const [recommendation, weeklyHeroMeta] = await Promise.all([recommendationPromise, metaPromise]);
3469
- const languageTag = await ctx.dota2tracker.i18n.getLanguageTag({ session });
3470
- const message = ctx.dota2tracker.messageBuilder.buildHeroOfTheDayMessage(languageTag, recommendation, weeklyHeroMeta);
3471
- 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
+ }
3472
3482
  });
3473
3483
  }
3474
3484
  __name(registerHeroOfTheDayCommand, "registerHeroOfTheDayCommand");
@@ -3476,14 +3486,19 @@ __name(registerHeroOfTheDayCommand, "registerHeroOfTheDayCommand");
3476
3486
  // src/app/commands/query-hero.command.ts
3477
3487
  function registerQueryHeroCommand(ctx) {
3478
3488
  ctx.command("dota2tracker.query-hero <input_data>").option("random", "-r").alias("查询英雄").action(async ({ session, options }, input_data) => {
3479
- if (input_data) {
3480
- await session.send(session.text(".querying_hero"));
3481
- const languageTag = await ctx.dota2tracker.i18n.getLanguageTag({ session });
3482
- const heroData = await ctx.dota2tracker.hero.getHeroDetails(input_data, languageTag, options.random);
3483
- if (!heroData) return session.text(".not_found");
3484
- const image = await ctx.dota2tracker.image.renderToImageByFile(heroData, ctx.config.template_hero, "hero" /* Hero */, languageTag);
3485
- const message = ctx.dota2tracker.messageBuilder.buildHeroMessage(heroData);
3486
- 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);
3487
3502
  }
3488
3503
  });
3489
3504
  }
@@ -3539,25 +3554,40 @@ __name(registerQueryItemCommand, "registerQueryItemCommand");
3539
3554
  // src/app/commands/query-match.command.ts
3540
3555
  function registerQueryMatchCommand(ctx) {
3541
3556
  ctx.command("dota2tracker.query-match <match_id>").alias("查询比赛").option("parse", "-p").option("template", "-t <value:string>").action(async ({ session, options }, match_id) => {
3542
- if (!match_id) return session.text(".empty_input");
3543
- if (!/^\d{1,11}$/.test(match_id)) return session.text(".match_id_invalid");
3544
- await session.send(session.text(".querying_match"));
3545
- 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
+ }
3546
3566
  });
3547
3567
  ctx.command("dota2tracker.query-recent-match [input_data]").alias("查询最近比赛").option("parse", "-p").option("template", "-t <value:string>").action(async ({ session, options }, input_data) => {
3548
- const steamId = await resolvePlayerAndHandleErrors(ctx, session, input_data);
3549
- if (steamId === null) return;
3550
- session.send(session.text(".querying_match"));
3551
- const lastMatchId = await ctx.dota2tracker.player.getLastMatchId(Number(steamId));
3552
- if (!lastMatchId?.matchId) return session.text(".query_failed");
3553
- if (lastMatchId.isAnonymous) return session.text(".is_anonymous");
3554
- 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
+ }
3555
3585
  });
3556
3586
  }
3557
3587
  __name(registerQueryMatchCommand, "registerQueryMatchCommand");
3558
3588
  async function handleQueryMatchCommand(ctx, config, session, options, matchId) {
3559
3589
  const languageTag = await ctx.dota2tracker.i18n.getLanguageTag({ session });
3560
- 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 });
3561
3591
  if (result.status === "PENDING") {
3562
3592
  const subscriber = ctx.dota2tracker.parsePolling.createSubscriberByCommand(session, languageTag, { templateName: options?.template });
3563
3593
  ctx.dota2tracker.parsePolling.add(result.matchId, [subscriber]);
@@ -3568,7 +3598,6 @@ async function handleQueryMatchCommand(ctx, config, session, options, matchId) {
3568
3598
  const formattedMatchData = await ctx.dota2tracker.match.generateMatchData(result.matchData, languageTag);
3569
3599
  const message = ctx.dota2tracker.messageBuilder.buildMatchMessage(languageTag, formattedMatchData, []);
3570
3600
  const image = await ctx.dota2tracker.image.renderToImageByFile(formattedMatchData, options.template || config.template_match, "match" /* Match */, languageTag);
3571
- await ctx.dota2tracker.image.renderToImageByFile(formattedMatchData, options.template || config.template_match, "match" /* Match */, languageTag);
3572
3601
  return message + image;
3573
3602
  }
3574
3603
  }
@@ -3589,18 +3618,23 @@ __name(registerQueryMembersCommand, "registerQueryMembersCommand");
3589
3618
  // src/app/commands/query-player.command.ts
3590
3619
  function registerQueryPlayerCommand(ctx) {
3591
3620
  ctx.command("dota2tracker.query-player <input_data>").option("hero", "-o <value:string>").alias("查询玩家").action(async ({ session, options }, input_data) => {
3592
- if (session.guild || !session.guild && input_data) {
3593
- const steamId = await resolvePlayerAndHandleErrors(ctx, session, input_data);
3594
- if (steamId === null) return;
3595
- session.send(session.text(".querying_player"));
3596
- const heroId = ctx.dota2tracker.i18n.findHeroIdInLocale(options.hero);
3597
- const languageTag = await ctx.dota2tracker.i18n.getLanguageTag({ session });
3598
- const formattedPlayerData = await ctx.dota2tracker.player.getFormattedPlayerData(steamId, heroId, languageTag);
3599
- const image = await ctx.dota2tracker.image.renderToImageByFile(formattedPlayerData, ctx.config.template_player, "player" /* Player */, languageTag);
3600
- const message = ctx.dota2tracker.messageBuilder.buildPlayerMessage(steamId);
3601
- return message + image;
3602
- } else {
3603
- 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);
3604
3638
  }
3605
3639
  });
3606
3640
  }
@@ -3685,7 +3719,7 @@ var OpenDotaAPI = class extends import_koishi15.Service {
3685
3719
  constructor(ctx) {
3686
3720
  super(ctx, "dota2tracker.opendota-api", true);
3687
3721
  this.config = ctx.config;
3688
- 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 });
3689
3723
  ctx.on("dispose", () => this.dispose());
3690
3724
  }
3691
3725
  dispose() {
@@ -4177,7 +4211,7 @@ var globRequire_locales_schema_yml = __glob({
4177
4211
  });
4178
4212
 
4179
4213
  // src/config.ts
4180
- var pluginDir = import_path4.default.resolve(__dirname, "..");
4214
+ var templateDir = import_path4.default.join(__dirname, "templates");
4181
4215
  var allI18nConfigs = Object.fromEntries(Object.keys(LanguageTags).map((lang) => [lang, globRequire_locales_schema_yml(`./locales/${lang}.schema.yml`)._config]));
4182
4216
  var Config = import_koishi17.Schema.intersect([
4183
4217
  import_koishi17.Schema.intersect([
@@ -4248,9 +4282,9 @@ var Config = import_koishi17.Schema.intersect([
4248
4282
  ]).i18n(getI18n("report"))
4249
4283
  ]),
4250
4284
  import_koishi17.Schema.object({
4251
- template_match: import_koishi17.Schema.union([...readDirectoryFilesSync(import_path4.default.join(pluginDir, "template", "match"))]).default("match_1"),
4252
- template_player: import_koishi17.Schema.union([...readDirectoryFilesSync(import_path4.default.join(pluginDir, "template", "player"))]).default("player_1"),
4253
- 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"),
4254
4288
  playerRankEstimate: import_koishi17.Schema.boolean().default(true),
4255
4289
  templateFonts: import_koishi17.Schema.array(String).default([]).role("table")
4256
4290
  }).i18n(getI18n("template"))
@@ -4284,13 +4318,13 @@ var inject = {
4284
4318
  required: ["database", "puppeteer", "cache"],
4285
4319
  optional: ["cron", "console"]
4286
4320
  };
4287
- var pluginDir2 = import_path5.default.resolve(__dirname, "..");
4288
- var pluginVersion = require(import_path5.default.join(pluginDir2, "package.json")).version;
4289
4321
  async function apply(ctx, config) {
4290
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;
4291
4325
  ctx.dota2tracker = {};
4292
4326
  ctx.dota2tracker.i18n = new I18NService(ctx);
4293
- ctx.dota2tracker.image = new ImageRenderer(ctx, pluginDir2);
4327
+ ctx.dota2tracker.image = new ImageRenderer(ctx, currentDir);
4294
4328
  ctx.dota2tracker.messageBuilder = new MessageBuilder(ctx);
4295
4329
  ctx.dota2tracker.match = new MatchService(ctx, pluginVersion);
4296
4330
  ctx.dota2tracker.player = new PlayerService(ctx);
@@ -4310,7 +4344,7 @@ async function apply(ctx, config) {
4310
4344
  ctx.dota2tracker.cache = new CacheService(ctx);
4311
4345
  ctx.dota2tracker.database = new DatabaseService(ctx);
4312
4346
  ctx.dota2tracker.valveAPI = new ValveAPI(ctx);
4313
- ctx.dota2tracker.stratzAPI = new StratzAPI(ctx, pluginDir2);
4347
+ ctx.dota2tracker.stratzAPI = new StratzAPI(ctx, currentDir);
4314
4348
  if (config.enableOpenDotaFallback) {
4315
4349
  ctx.dota2tracker.opendotaAPI = new OpenDotaAPI(ctx);
4316
4350
  ctx.dota2tracker.opendotaAdapter = new OpenDotaAdapter(ctx);
@@ -4326,7 +4360,7 @@ async function apply(ctx, config) {
4326
4360
  registerQueryHeroCommand(ctx);
4327
4361
  registerQueryItemCommand(ctx);
4328
4362
  registerHeroOfTheDayCommand(ctx);
4329
- if (config.enableConsole) registerConsolePage(ctx);
4363
+ if (ctx.console && config.enableConsole) registerConsolePage(ctx);
4330
4364
  }
4331
4365
  __name(apply, "apply");
4332
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}