@sjtdev/koishi-plugin-dota2tracker 2.3.4 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/changelog.md +16 -0
- package/lib/index.js +357 -124
- package/lib/templates/common/utils/match.ejs +37 -0
- package/lib/templates/hero/hero_1.ejs +1 -1
- package/lib/templates/match/match_1/player.ejs +1 -1
- package/lib/templates/match/match_1.ejs +1 -1
- package/lib/templates/match/match_2/original.ejs +1 -1
- package/package.json +5 -8
package/changelog.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# 更新日志
|
|
2
2
|
|
|
3
|
+
## [2.4.0](https://github.com/sjtdev/koishi-plugin-dota2tracker/compare/v2.3.4...v2.4.0) (2026-02-15)
|
|
4
|
+
|
|
5
|
+
### ✨ 新增功能
|
|
6
|
+
|
|
7
|
+
* **fonts:** **破坏性更新** 弃用`templateFonts`,重构实现更完善的模板字体配置服务 ([2883322](https://github.com/sjtdev/koishi-plugin-dota2tracker/commit/2883322dcaabeaadad26bbf88e6b419a97ced594))
|
|
8
|
+
|
|
9
|
+
### 🚀 功能优化
|
|
10
|
+
|
|
11
|
+
* **dep:** 依赖 `ejs` 从`3.1.10`升级至`4.0.1`,略微提升性能(?) ([35d79ea](https://github.com/sjtdev/koishi-plugin-dota2tracker/commit/35d79ea8bc1c63dcab3794405096231d293f34dc))
|
|
12
|
+
* **locale-en/template/titles:** update English titles to full words for adaptive rendering ([6ce279d](https://github.com/sjtdev/koishi-plugin-dota2tracker/commit/6ce279daeafed77478618b95b77a83991c5b428a))
|
|
13
|
+
* **view:** 切换 ejs 获取本地图片资源的方式,优化模板生成速度 ([6687537](https://github.com/sjtdev/koishi-plugin-dota2tracker/commit/66875377e685d8de71e4c5edd7e3b2a1c79b9895))
|
|
14
|
+
|
|
15
|
+
### 📝 文档
|
|
16
|
+
|
|
17
|
+
* **config/autoRecallTips:** 补充说明 ([1dfb38d](https://github.com/sjtdev/koishi-plugin-dota2tracker/commit/1dfb38d3755d32ce10e8dd1d5e4170b8852760a6))
|
|
18
|
+
|
|
3
19
|
### [2.3.4](https://github.com/sjtdev/koishi-plugin-dota2tracker/compare/v2.3.3...v2.3.4) (2026-02-03)
|
|
4
20
|
|
|
5
21
|
### ✨ 新增功能
|
package/lib/index.js
CHANGED
|
@@ -5,10 +5,10 @@ var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
|
5
5
|
var __getProtoOf = Object.getPrototypeOf;
|
|
6
6
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
7
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
8
|
-
var __glob = (map) => (
|
|
9
|
-
var fn = map[
|
|
8
|
+
var __glob = (map) => (path6) => {
|
|
9
|
+
var fn = map[path6];
|
|
10
10
|
if (fn) return fn();
|
|
11
|
-
throw new Error("Module not found in bundle: " +
|
|
11
|
+
throw new Error("Module not found in bundle: " + path6);
|
|
12
12
|
};
|
|
13
13
|
var __commonJS = (cb, mod) => function __require() {
|
|
14
14
|
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
@@ -775,21 +775,21 @@ var require_en_US_command = __commonJS({
|
|
|
775
775
|
// src/locales/en-US.schema.yml
|
|
776
776
|
var require_en_US_schema = __commonJS({
|
|
777
777
|
"src/locales/en-US.schema.yml"(exports2, module2) {
|
|
778
|
-
module2.exports = { _config: { base: { $desc: "Basic Settings", STRATZ_API_TOKEN: "Required. API TOKEN from stratz.com, available at https://stratz.com/api.", dataParsingTimeoutMinutes: "Time to wait for match data parsing (in minutes). If the data parsing time exceeds the waiting time, the report will be generated directly without waiting for the parsing to complete.", proxyAddress: "Proxy address. Leave blank if not using a proxy. \n※Cannot use the global proxy address configured by the `proxy-agent` plugin. This option must be set if you want to use a proxy.", suppressStratzNetworkErrors: "**Please use the `suppressApiNetworkErrors` option below, which applies to both Stratz and OpenDota. \nThis option is still effective. If either this or `suppressApiNetworkErrors` is enabled, Stratz/OpenDota logs will be downgraded to debug output. \nThis option will be removed in a future version.**", suppressApiNetworkErrors: "When enabled, Stratz/OpenDota network error logs will be output at the debug level. \n(e.g., timeouts, network connection issues, but excludes 403 Forbidden) \nKoishi does not display debug-level logs by default. To enable debug log display, please see [📖 Configs#suppressapinetworkerrors](
|
|
778
|
+
module2.exports = { _config: { base: { $desc: "Basic Settings", STRATZ_API_TOKEN: "Required. API TOKEN from stratz.com, available at https://stratz.com/api.", dataParsingTimeoutMinutes: "Time to wait for match data parsing (in minutes). If the data parsing time exceeds the waiting time, the report will be generated directly without waiting for the parsing to complete.", proxyAddress: "Proxy address. Leave blank if not using a proxy. \n※Cannot use the global proxy address configured by the `proxy-agent` plugin. This option must be set if you want to use a proxy.", suppressStratzNetworkErrors: "**Please use the `suppressApiNetworkErrors` option below, which applies to both Stratz and OpenDota. \nThis option is still effective. If either this or `suppressApiNetworkErrors` is enabled, Stratz/OpenDota logs will be downgraded to debug output. \nThis option will be removed in a future version.**", suppressApiNetworkErrors: "When enabled, Stratz/OpenDota network error logs will be output at the debug level. \n(e.g., timeouts, network connection issues, but excludes 403 Forbidden) \nKoishi does not display debug-level logs by default. To enable debug log display, please see [📖 Configs#suppressapinetworkerrors](https://sjtdev.github.io/koishi-plugin-dota2tracker/en-US/configs.html#suppressapinetworkerrors-boolean)", enableOpenDotaFallback: "Enable OpenDota as a fallback data source for match tracking and query-match features.", OPENDOTA_API_KEY: "Your paid subscription API key for OpenDota. \nSee https://www.opendota.com/api-keys for details. \nFree users should leave this blank.", OpenDotaIPStack: "If you experience frequent failures when accessing the OpenDota API, it might be caused by a faulty IPv6 environment. \nSetting this option will force the use of IPv4 when accessing the OpenDota API to try and resolve the issue." }, message: { $desc: "Message Settings", useHeroNicknames: "When disabled, only the official hero names will be used.", urlInMessageType: { $desc: "Include links in messages, <br/>please select the message type:", $inner: ["Include stratz match page link in match query and report messages", "Include stratz player page link in player information query messages", "Include Dota Encyclopedia hero page link in hero data query messages"] }, rankBroadSwitch: "Rank change broadcast", rankBroadStar: "Star change broadcast", rankBroadLeader: "Leaderboard rank change broadcast", rankBroadFun: "Fun broadcast template", maxSendItemCount: "Maximum number of item images to send<br/>When exceeded, the following option determines whether to send the item list", showItemListAtTooMuchItems: "Send item list when exceeding max count<br/>Controls whether to send the item list image when search results exceed maxSendItemCount", customItemAlias: { $desc: "Custom item aliases<br/>\nAdd additional aliases when built-in list is insufficient. \nFor widely-used missing aliases, please submit issues/pull requests to the source repository.<br/>\n(Example **Keyword**: Blink Dagger,**Alias**: Blink)", keyword: "Keyword", alias: "Alias" }, autoRecallTips: 'Automatically recall tip messages after the command finishes, e.g., "Searching for match details, please wait..."' }, report: { $desc: "Summary Settings", dailyReportSwitch: "Daily Report Function", dailyReportHours: "Daily report time in hours", dailyReportShowCombi: "Show combinations in daily report", weeklyReportSwitch: "Weekly Report Function", weeklyReportDayHours: "Weekly report published on (day) at (hour)", weeklyReportShowCombi: "Show combinations in weekly report" }, template: { $desc: "Template Settings", template_match: "Template used for generating match information images. See [📖 Template Info#Match Info Template](https://sjtdev.github.io/koishi-plugin-dota2tracker/en-US/template-match.html) for display effects.", template_player: "Template used for generating player information images. (Currently only one template available)", template_hero: "Template used for generating hero information images. (Currently only one template available)", playerRankEstimate: "Estimate rank for unranked players in the player template <br>Estimated ranks will be displayed as gray images", templateFonts: '**>Deprecated!<** \n**If you need to configure fonts, please use the `fonts.*` configuration option below instead!** \nFont names used in the template. Requires font files to be installed on the device running Koishi. \nMultiple font names can be added, and it will fallback from top to bottom to the first available font; if no fonts are available, the system default font will be used. \nIf a font name contains spaces or special characters, quotes must be added around the name (it is recommended to always use quotes here); \nIf using a generic font family name, you must **NOT use quotes**, e.g.:\n```\n"Microsoft YaHei"\nsans-serif\n```\nFor more information on font-family, please refer to [📖 MDN: font-family](https://developer.mozilla.org/en-US/docs/Web/CSS/font-family)', fontPath: "Font directory path", fonts: { description: "**Interim solution. A more comprehensive and flexible font configuration scheme will be implemented in the console in the future.** \nFont configuration used in the template. \nIt will automatically scan font files in the `fontPath` directory to generate the selectable font list below. \nYou can separately configure fallback lists for sans-serif (sans), serif (serif), and monospace (mono) font families. \nThe main font type used in the templates is **sans-serif**, while some text in certain templates uses **serif** and **monospace** fonts. \nFor more information on font configuration, please refer to [📖 Template Info#Template Fonts](https://sjtdev.github.io/koishi-plugin-dota2tracker/en-US/template-fonts.html)", sans: { $desc: "Sans-serif fonts (sans-serif)" }, serif: { $desc: "Serif fonts (serif)" }, mono: { $desc: "Monospace fonts (monospace)" } } } } };
|
|
779
779
|
}
|
|
780
780
|
});
|
|
781
781
|
|
|
782
782
|
// src/locales/en-US.template.yml
|
|
783
783
|
var require_en_US_template = __commonJS({
|
|
784
784
|
"src/locales/en-US.template.yml"(exports2, module2) {
|
|
785
|
-
module2.exports = { dota2tracker: { template: { radiant: "Radiant", dire: "Dire", won: "Won", lost: "Lost", match_id_: "Match ID: {0}", game_mode_: "Mode: {0}", start_time_: "Start Time: {0}", end_time_: "End Time: {0}", pick_order: "#{0}", random: "R", hero_damage_: "Damage: {0}", building_damage_: "Building: {0}", damage_received_: "Received: {0}", lasthit_: "LastHit: {0}", deny_: "Deny: {0}", "lh/dn_": "LH/DN: {0}", GPM: "GPM", XPM: "XPM", heal_: "Heal: {0}", crowd_control_duration_: "CCD: {0}", "GPM/XPM_": "GPM/XPM: {0}", lane: "Lane", lane_: "Lane: ", lane_advantage: "Lane +", lane_disadvantage: "Lane -", lane_jungle: "Jungle", lane_stomp: "Lane+++", lane_stomped: "Lane---", lane_tie: "Lane ==", analysis_successful: "Analysis successful", analysis_incomplete: "Analysis incomplete", analysis_by_opendota: "Analysis by OpenDota", kill: "Kill", kill_contribution_: "KC: {0}", position: "Position", position_: "Position: ", position_1: "Carry", position_2: "Mid", position_3: "OffLane", position_4: "Softsup", position_5: "Hardsup", dire_won: "Dire Won", radiant_won: "Radiant Won", total_damage: "Damage", total_experience: "Exp.", total_gold: "Gold", region_: "Region: {0}", duration_: "Duration: {0}", position_undefined: "?", top10_: "Top 10 Heroes by Matches: ", match_count_: "Matches: ", last25matches_: "Last 25 Matches: ", winrate_: "Winrate: ", imp_: "IMP: ", lane_advantage_rate_: "Lane Advantage Rate: ", hero: "Hero", all_matches_: "All Matches: ", match_count: "Matches", winrate: "Winrate", imp: "IMP", win_count: "Wins", lose_count: "Losses", recently_heroes: "Heroes used more than once recently: ", recently_positions: "Performance in the last 25 matches across all positions: ", winning_streak: "Winning Streak", losing_streak: "Losing Streak", id: "ID", mode: "Mode", kda_kc: "KDA(KC)", time: "Time", duration: "Duration", rank: "Rank", un_parsed: "(Unparsed)", combined_win_loss_summary: "Combined Win/Loss Summary: ", yesterdays_summary: "Yesterday's Summary", last_weeks_summary: "Last Week's Summary", report_won: "W", report_lost: "L", report_winrate: "WR", anonymous_player_1: "This profile is private.", anonymous_player_2: "Background is for display purposes only. It is not {player}’s data.", rank_fun_down_message: "AVATAR_PLACEHOLDER<br/>Sad", rank_fun_up_message: "Hip hip hooray! Our awesome member AVATAR_PLACEHOLDER{name} has leveled up from PREV_PLACEHOLDER to CURR_PLACEHOLDER!", titles: { MVP: "MVP-#FFA500", Soul: "Soul-#66CCFF", Rich: "
|
|
785
|
+
module2.exports = { dota2tracker: { template: { radiant: "Radiant", dire: "Dire", won: "Won", lost: "Lost", match_id_: "Match ID: {0}", game_mode_: "Mode: {0}", start_time_: "Start Time: {0}", end_time_: "End Time: {0}", pick_order: "#{0}", random: "R", hero_damage_: "Damage: {0}", building_damage_: "Building: {0}", damage_received_: "Received: {0}", lasthit_: "LastHit: {0}", deny_: "Deny: {0}", "lh/dn_": "LH/DN: {0}", GPM: "GPM", XPM: "XPM", heal_: "Heal: {0}", crowd_control_duration_: "CCD: {0}", "GPM/XPM_": "GPM/XPM: {0}", lane: "Lane", lane_: "Lane: ", lane_advantage: "Lane +", lane_disadvantage: "Lane -", lane_jungle: "Jungle", lane_stomp: "Lane+++", lane_stomped: "Lane---", lane_tie: "Lane ==", analysis_successful: "Analysis successful", analysis_incomplete: "Analysis incomplete", analysis_by_opendota: "Analysis by OpenDota", kill: "Kill", kill_contribution_: "KC: {0}", position: "Position", position_: "Position: ", position_1: "Carry", position_2: "Mid", position_3: "OffLane", position_4: "Softsup", position_5: "Hardsup", dire_won: "Dire Won", radiant_won: "Radiant Won", total_damage: "Damage", total_experience: "Exp.", total_gold: "Gold", region_: "Region: {0}", duration_: "Duration: {0}", position_undefined: "?", top10_: "Top 10 Heroes by Matches: ", match_count_: "Matches: ", last25matches_: "Last 25 Matches: ", winrate_: "Winrate: ", imp_: "IMP: ", lane_advantage_rate_: "Lane Advantage Rate: ", hero: "Hero", all_matches_: "All Matches: ", match_count: "Matches", winrate: "Winrate", imp: "IMP", win_count: "Wins", lose_count: "Losses", recently_heroes: "Heroes used more than once recently: ", recently_positions: "Performance in the last 25 matches across all positions: ", winning_streak: "Winning Streak", losing_streak: "Losing Streak", id: "ID", mode: "Mode", kda_kc: "KDA(KC)", time: "Time", duration: "Duration", rank: "Rank", un_parsed: "(Unparsed)", combined_win_loss_summary: "Combined Win/Loss Summary: ", yesterdays_summary: "Yesterday's Summary", last_weeks_summary: "Last Week's Summary", report_won: "W", report_lost: "L", report_winrate: "WR", anonymous_player_1: "This profile is private.", anonymous_player_2: "Background is for display purposes only. It is not {player}’s data.", rank_fun_down_message: "AVATAR_PLACEHOLDER<br/>Sad", rank_fun_up_message: "Hip hip hooray! Our awesome member AVATAR_PLACEHOLDER{name} has leveled up from PREV_PLACEHOLDER to CURR_PLACEHOLDER!", titles: { MVP: "MVP-#FFA500", Soul: "Soul-#66CCFF", Rich: "Rich-#FFD700", Wise: "Wise-#8888FF", Controller: "Controller-#FF00FF", Nuker: "Nuker-#CC0088", Breaker: "Breaker-#DD0000", Ghost: "Ghost-#CCCCCC", Utility: "Utility-#20B2AA", Assister: "Assister-#006400", Demolisher: "Demolisher-#FEDCBA", Healer: "Healer-#00FF00", Tank: "Tank-#84A1C7", Idle: "Idle-#DDDDDD" }, situation: "Situation", networth: "Net Worth", experience: "Experience", OUTCOME_MAP: { RADIANT_VICTORY: "RADIANT VICTORY", RADIANT_STOMP: "RADIANT STOMP", DIRE_VICTORY: "DIRE VICTORY", DIRE_STOMP: "DIRE STOMP", TIE: "TIE" }, lane_top: "Top", lane_mid: "Mid", lane_bottom: "Bottom", empty_extra_info: "No extra info", opendota: { networth_unavailable: "Net Worth Chart Unavailable", networth_unavailable_reason: "Data source OpenDota does not provide per-minute net worth data.", lane_outcome_tip: "(Based on gold earned, not net worth; ref only.)", gold_t: "Gold Earned" } } } };
|
|
786
786
|
}
|
|
787
787
|
});
|
|
788
788
|
|
|
789
789
|
// src/locales/en-US.yml
|
|
790
790
|
var require_en_US = __commonJS({
|
|
791
791
|
"src/locales/en-US.yml"(exports2, module2) {
|
|
792
|
-
module2.exports = { dota2tracker: { heroes_nicknames: { "0": 'Please strictly follow the format of "", "" when filling out, if the format is incorrect, only the default name of the hero will be used. The default name of the hero may be omitted.', "1": '"Anti-Mage"', "2": '"Axe"', "3": '"Bane"', "4": '"Bloodseeker"', "5": '"Crystal Maiden"', "6": '"Drow Ranger"', "7": '"Earthshaker"', "8": '"Juggernaut"', "9": '"Mirana"', "10": '"Morphling"', "11": '"Shadow Fiend"', "12": '"Phantom Lancer"', "13": '"Puck"', "14": '"Pudge"', "15": '"Razor"', "16": '"Sand King"', "17": '"Storm Spirit"', "18": '"Sven"', "19": '"Tiny"', "20": '"Vengeful Spirit"', "21": '"Windranger"', "22": '"Zeus"', "23": '"Kunkka"', "25": '"Lina"', "26": '"Lion"', "27": '"Shadow Shaman"', "28": '"Slardar"', "29": '"Tidehunter"', "30": '"Witch Doctor"', "31": '"Lich"', "32": '"Riki"', "33": '"Enigma"', "34": '"Tinker"', "35": '"Sniper"', "36": '"Necrophos"', "37": '"Warlock"', "38": '"Beastmaster"', "39": '"Queen of Pain"', "40": '"Venomancer"', "41": '"Faceless Void"', "42": '"Wraith King"', "43": '"Death Prophet"', "44": '"Phantom Assassin"', "45": '"Pugna"', "46": '"Templar Assassin"', "47": '"Viper"', "48": '"Luna"', "49": '"Dragon Knight"', "50": '"Dazzle"', "51": '"Clockwerk"', "52": '"Leshrac"', "53": `"Nature's Prophet"`, "54": '"Lifestealer"', "55": '"Dark Seer"', "56": '"Clinkz"', "57": '"Omniknight"', "58": '"Enchantress"', "59": '"Huskar"', "60": '"Night Stalker"', "61": '"Broodmother"', "62": '"Bounty Hunter"', "63": '"Weaver"', "64": '"Jakiro"', "65": '"Batrider"', "66": '"Chen"', "67": '"Spectre"', "68": '"Ancient Apparition"', "69": '"Doom"', "70": '"Ursa"', "71": '"Spirit Breaker"', "72": '"Gyrocopter"', "73": '"Alchemist"', "74": '"Invoker"', "75": '"Silencer"', "76": '"Outworld Devourer"', "77": '"Lycan"', "78": '"Brewmaster"', "79": '"Shadow Demon"', "80": '"Lone Druid"', "81": '"Chaos Knight"', "82": '"Meepo"', "83": '"Treant Protector"', "84": '"Ogre Magi"', "85": '"Undying"', "86": '"Rubick"', "87": '"Disruptor"', "88": '"Nyx Assassin"', "89": '"Naga Siren"', "90": '"Keeper of the Light"', "91": '"Io"', "92": '"Visage"', "93": '"Slark"', "94": '"Medusa"', "95": '"Troll Warlord"', "96": '"Centaur Warrunner"', "97": '"Magnus"', "98": '"Timbersaw"', "99": '"Bristleback"', "100": '"Tusk"', "101": '"Skywrath Mage"', "102": '"Abaddon"', "103": '"Elder Titan"', "104": '"Legion Commander"', "105": '"Techies"', "106": '"Ember Spirit"', "107": '"Earth Spirit"', "108": '"Underlord"', "109": '"Terrorblade"', "110": '"Phoenix"', "111": '"Oracle"', "112": '"Winter Wyvern"', "113": '"Arc Warden"', "114": '"Monkey King"', "119": '"Dark Willow"', "120": '"Pangolier"', "121": '"Grimstroke"', "123": '"Hoodwink"', "126": '"Void Spirit"', "128": '"Snapfire"', "129": '"Mars"', "131": '"Ring Master"', "135": '"Dawnbreaker"', "136": '"Marci"', "137": '"Primal Beast"', "138": '"Muerta"', "145": '"Kez"', "155": '"Largo"' }, broadcast: { WIN_NEGATIVE: `"Won the match by sheer luck", "Won the match by a stroke of bad luck", "Coasted to victory", "Didn't even show up for the team fight, but my teammates won 4v5"`, WIN_POSITIVE: '"Led the team to victory", "Dominated the opponents and secured the win", "Carried the game to victory", "Treated the opponents like pigs and won", "Won again; this game is just so monotonous and dull", "Simply achieved a win in the match"', LOSE_NEGATIVE: '"Got crushed and lost the match", "Lost the match miserably", "Got my head knocked sideways and lost the match with a blown mindset", "Went fishing but got eaten by the fish, lost the match", "Played terribly", "Simply suffered a loss in the match"', LOSE_POSITIVE: `"Lost the match with no way to turn it around", "Gave it my all, but still lost the match", "Though we lost, we still have honor", "Couldn't carry my teammates, lost the match", "Lost again, it hurts; I'd rather it be me losing"`, message: "{name}'s {hero_name} {comment}.\nKDA: {kda}, GPM/XPM: {gpm_xpm}, Last Hits/Denies: {lh_dn}, Damage/Tower Damage: {damage}, Kill/Death Contribution Rate: {kc_dc}", rank_changed: "Player {name} rank changed: {prev.medal} {prev.star} → {curr.medal} {curr.star}" }, logger: { fetch_guilds_failed: "Failed to fetch guild information.", match_tracked: "Tracked new match {match.id} from {#each messageToLogger as item}users in guild {item.platform}:{item.guildId} [{#each item.players as player}{player.nickname}({player.steamId}){#if player !== item.players[item.players.length - 1]}, {/if}{/each}]{#if item !== messageToLogger[messageToLogger.length - 1]}, {/if}{/each}.", parse_request_sent: "The parsing request for match {matchId} has been successfully sent to the STRATZ server.", parse_request_failed: "The parsing request for match {matchId} failed to send.", match_parsed: "Match {matchId} has been parsed{#if odParsed} by OpenDota{/if}, an image was generated and published to {#each guilds as guild}{guild.platform}:{guild.guildId}{#if guild !== guilds[guilds.length - 1]}, {/if}{/each}.", match_unparsed: "Match {matchId} exceeded the waiting time [{timeout}] and remains unparsed, an image was generated and published to {#each guilds as guild}{guild.platform}:{guild.guildId}{#if guild !== guilds[guilds.length - 1]}, {/if}{/each}.", waiting_for_parse: "Match {matchId} has not been parsed yet, has been waiting for {time} minutes.", report_sent: "Posted {title} on {platform}:{guildId}.", rank_sent: "Posted rank change information of {player.nickName} ({player.steamId}) on {platform}:{guildId}.", ejs_error: "Error rendering EJS template: {error}", cron_not_enabled: "Cron service is not enabled; match report tracking cannot run.", stratz_token_banned: "Stratz API request rejected (403). This usually means your token or IP has been temporarily restricted due to unusual activity. For details, see the documentation: http://sjtdev.github.io/koishi-plugin-dota2tracker/en-US/api-403.html", opendota_token_banned: "OpenDota API request rejected (403). Please check if the access limit has been exceeded.", stratz_api_query_error: "Stratz API returned partial data with errors: {cause}", opendota_parse_request_sent: "Parse request for match {matchId} has been sent to OpenDota servers successfully.", opendota_parse_request_failed: "Failed to send parse request for match {matchId} to OpenDota servers." }, time: { years_months_ago: "{years} years and {months} months ago", years_ago: "{years} years ago" } } };
|
|
792
|
+
module2.exports = { dota2tracker: { heroes_nicknames: { "0": 'Please strictly follow the format of "", "" when filling out, if the format is incorrect, only the default name of the hero will be used. The default name of the hero may be omitted.', "1": '"Anti-Mage"', "2": '"Axe"', "3": '"Bane"', "4": '"Bloodseeker"', "5": '"Crystal Maiden"', "6": '"Drow Ranger"', "7": '"Earthshaker"', "8": '"Juggernaut"', "9": '"Mirana"', "10": '"Morphling"', "11": '"Shadow Fiend"', "12": '"Phantom Lancer"', "13": '"Puck"', "14": '"Pudge"', "15": '"Razor"', "16": '"Sand King"', "17": '"Storm Spirit"', "18": '"Sven"', "19": '"Tiny"', "20": '"Vengeful Spirit"', "21": '"Windranger"', "22": '"Zeus"', "23": '"Kunkka"', "25": '"Lina"', "26": '"Lion"', "27": '"Shadow Shaman"', "28": '"Slardar"', "29": '"Tidehunter"', "30": '"Witch Doctor"', "31": '"Lich"', "32": '"Riki"', "33": '"Enigma"', "34": '"Tinker"', "35": '"Sniper"', "36": '"Necrophos"', "37": '"Warlock"', "38": '"Beastmaster"', "39": '"Queen of Pain"', "40": '"Venomancer"', "41": '"Faceless Void"', "42": '"Wraith King"', "43": '"Death Prophet"', "44": '"Phantom Assassin"', "45": '"Pugna"', "46": '"Templar Assassin"', "47": '"Viper"', "48": '"Luna"', "49": '"Dragon Knight"', "50": '"Dazzle"', "51": '"Clockwerk"', "52": '"Leshrac"', "53": `"Nature's Prophet"`, "54": '"Lifestealer"', "55": '"Dark Seer"', "56": '"Clinkz"', "57": '"Omniknight"', "58": '"Enchantress"', "59": '"Huskar"', "60": '"Night Stalker"', "61": '"Broodmother"', "62": '"Bounty Hunter"', "63": '"Weaver"', "64": '"Jakiro"', "65": '"Batrider"', "66": '"Chen"', "67": '"Spectre"', "68": '"Ancient Apparition"', "69": '"Doom"', "70": '"Ursa"', "71": '"Spirit Breaker"', "72": '"Gyrocopter"', "73": '"Alchemist"', "74": '"Invoker"', "75": '"Silencer"', "76": '"Outworld Devourer"', "77": '"Lycan"', "78": '"Brewmaster"', "79": '"Shadow Demon"', "80": '"Lone Druid"', "81": '"Chaos Knight"', "82": '"Meepo"', "83": '"Treant Protector"', "84": '"Ogre Magi"', "85": '"Undying"', "86": '"Rubick"', "87": '"Disruptor"', "88": '"Nyx Assassin"', "89": '"Naga Siren"', "90": '"Keeper of the Light"', "91": '"Io"', "92": '"Visage"', "93": '"Slark"', "94": '"Medusa"', "95": '"Troll Warlord"', "96": '"Centaur Warrunner"', "97": '"Magnus"', "98": '"Timbersaw"', "99": '"Bristleback"', "100": '"Tusk"', "101": '"Skywrath Mage"', "102": '"Abaddon"', "103": '"Elder Titan"', "104": '"Legion Commander"', "105": '"Techies"', "106": '"Ember Spirit"', "107": '"Earth Spirit"', "108": '"Underlord"', "109": '"Terrorblade"', "110": '"Phoenix"', "111": '"Oracle"', "112": '"Winter Wyvern"', "113": '"Arc Warden"', "114": '"Monkey King"', "119": '"Dark Willow"', "120": '"Pangolier"', "121": '"Grimstroke"', "123": '"Hoodwink"', "126": '"Void Spirit"', "128": '"Snapfire"', "129": '"Mars"', "131": '"Ring Master"', "135": '"Dawnbreaker"', "136": '"Marci"', "137": '"Primal Beast"', "138": '"Muerta"', "145": '"Kez"', "155": '"Largo"' }, broadcast: { WIN_NEGATIVE: `"Won the match by sheer luck", "Won the match by a stroke of bad luck", "Coasted to victory", "Didn't even show up for the team fight, but my teammates won 4v5"`, WIN_POSITIVE: '"Led the team to victory", "Dominated the opponents and secured the win", "Carried the game to victory", "Treated the opponents like pigs and won", "Won again; this game is just so monotonous and dull", "Simply achieved a win in the match"', LOSE_NEGATIVE: '"Got crushed and lost the match", "Lost the match miserably", "Got my head knocked sideways and lost the match with a blown mindset", "Went fishing but got eaten by the fish, lost the match", "Played terribly", "Simply suffered a loss in the match"', LOSE_POSITIVE: `"Lost the match with no way to turn it around", "Gave it my all, but still lost the match", "Though we lost, we still have honor", "Couldn't carry my teammates, lost the match", "Lost again, it hurts; I'd rather it be me losing"`, message: "{name}'s {hero_name} {comment}.\nKDA: {kda}, GPM/XPM: {gpm_xpm}, Last Hits/Denies: {lh_dn}, Damage/Tower Damage: {damage}, Kill/Death Contribution Rate: {kc_dc}", rank_changed: "Player {name} rank changed: {prev.medal} {prev.star} → {curr.medal} {curr.star}" }, logger: { fetch_guilds_failed: "Failed to fetch guild information.", match_tracked: "Tracked new match {match.id} from {#each messageToLogger as item}users in guild {item.platform}:{item.guildId} [{#each item.players as player}{player.nickname}({player.steamId}){#if player !== item.players[item.players.length - 1]}, {/if}{/each}]{#if item !== messageToLogger[messageToLogger.length - 1]}, {/if}{/each}.", parse_request_sent: "The parsing request for match {matchId} has been successfully sent to the STRATZ server.", parse_request_failed: "The parsing request for match {matchId} failed to send.", match_parsed: "Match {matchId} has been parsed{#if odParsed} by OpenDota{/if}, an image was generated and published to {#each guilds as guild}{guild.platform}:{guild.guildId}{#if guild !== guilds[guilds.length - 1]}, {/if}{/each}.", match_unparsed: "Match {matchId} exceeded the waiting time [{timeout}] and remains unparsed, an image was generated and published to {#each guilds as guild}{guild.platform}:{guild.guildId}{#if guild !== guilds[guilds.length - 1]}, {/if}{/each}.", waiting_for_parse: "Match {matchId} has not been parsed yet, has been waiting for {time} minutes.", report_sent: "Posted {title} on {platform}:{guildId}.", rank_sent: "Posted rank change information of {player.nickName} ({player.steamId}) on {platform}:{guildId}.", ejs_error: "Error rendering EJS template: {error}", cron_not_enabled: "Cron service is not enabled; match report tracking cannot run.", stratz_token_banned: "Stratz API request rejected (403). This usually means your token or IP has been temporarily restricted due to unusual activity. For details, see the documentation: http://sjtdev.github.io/koishi-plugin-dota2tracker/en-US/api-403.html", opendota_token_banned: "OpenDota API request rejected (403). Please check if the access limit has been exceeded.", stratz_api_query_error: "Stratz API returned partial data with errors: {cause}", opendota_parse_request_sent: "Parse request for match {matchId} has been sent to OpenDota servers successfully.", opendota_parse_request_failed: "Failed to send parse request for match {matchId} to OpenDota servers.", font_loaded: "Font directory changed: {filename} ({eventType}), reloading font list..." }, time: { years_months_ago: "{years} years and {months} months ago", years_ago: "{years} years ago" } } };
|
|
793
793
|
}
|
|
794
794
|
});
|
|
795
795
|
|
|
@@ -803,7 +803,7 @@ var require_zh_CN_command = __commonJS({
|
|
|
803
803
|
// src/locales/zh-CN.schema.yml
|
|
804
804
|
var require_zh_CN_schema = __commonJS({
|
|
805
805
|
"src/locales/zh-CN.schema.yml"(exports2, module2) {
|
|
806
|
-
module2.exports = { _config: { base: { $desc: "基础设置", STRATZ_API_TOKEN: "※必须。stratz.com的API TOKEN,可在 https://stratz.com/api 获取。", dataParsingTimeoutMinutes: "等待比赛数据解析的时间(单位:分钟)。如果数据解析时间超过等待时间,将直接生成战报而不再等待解析完成。", proxyAddress: "代理地址,不使用代理请留空。 \n※无法使用`proxy-agent`插件配置的全局代理地址,欲使用代理必须设置此项。", suppressStratzNetworkErrors: "**请使用下方通用于 stratz 与 opendota 的配置项`suppressApiNetworkErrors`。 \n此配置项仍然生效,当与`suppressApiNetworkErrors`任一启用时将会使 stratz/opendota 日志降级到debug输出。 \n此配置项将于下版本被移除。**", suppressApiNetworkErrors: "开启后将 stratz/opendota 网络错误日志使用debug级别输出。 \n(如超时、网络不通等,但403 Forbidden除外) \nkoishi默认不显示debug级日志,若需要开启debug日志显示,请见 [📖 配置项#suppressapinetworkerrors](http://sjtdev.github.io/koishi-plugin-dota2tracker/configs.html#suppressapinetworkerrors-boolean)", enableOpenDotaFallback: "启用 OpenDota 作为战报追踪与查询比赛功能的备用数据源。", OPENDOTA_API_KEY: "OpenDota 的订阅付费APIKEY, \n可在 https://www.opendota.com/api-keys 查看详情。 \nOpenDota 的免费用户此处请留空。", OpenDotaIPStack: "若访问 OpenDota API 时频繁失败,可能是由于错误的 IPv6 环境导致的。 \n设置此选项可在访问 OpenDota API 时强制使用 IPv4 尝试解决问题。" }, message: { $desc: "消息设置", useHeroNicknames: "是否使用英雄别名。关闭后仅使用英雄正式名称。", urlInMessageType: { $desc: "在消息中附带链接,<br/>请选择消息类型:", $inner: ["在查询比赛与战报消息中附带stratz比赛页面链接", "在查询玩家信息消息中附带stratz玩家页面链接", "在查询英雄数据消息中附带刀塔百科对应英雄页面链接"] }, rankBroadSwitch: "段位变动播报", rankBroadStar: "星级变动播报", rankBroadLeader: "冠绝名次变动播报", rankBroadFun: "整活播报模板", maxSendItemCount: "最大发送物品图片数量,<br/> 当超过指定数量时将由下方选项决定是否发送查询结果的物品列表图片", showItemListAtTooMuchItems: "在查询结果的物品数量超过指定数量时,是否发送查询结果的物品列表图片", customItemAlias: { $desc: "额外物品别名设置<br/>当插件内置的[物品别名列表](https://github.com/sjtdev/koishi-plugin-dota2tracker/blob/master/src/locales/zh-CN.constants.json#L304-L407)中没有想要的物品别名可在此处追加,如果是插件疏漏的广为人知的物品别名推荐到源码仓库提交issue或pull request完善列表。<br/>(例如 **关键词**: 闪烁匕首,**别名**: 跳刀)", keyword: "关键词", alias: "别名" }, autoRecallTips: "在指令调用结束后自动撤回提示消息,如:“正在搜索对局详情,请稍后……”" }, report: { $desc: "总结设置", dailyReportSwitch: "日报功能", dailyReportHours: "日报时间小时", dailyReportShowCombi: "日报是否显示组合", weeklyReportSwitch: "周报功能", weeklyReportDayHours: "周报发布于周(几)的(几)点", weeklyReportShowCombi: "周报是否显示组合" }, template: { $desc: "模板设置", template_match: "生成比赛信息图片使用的模板,显示效果见 [📖
|
|
806
|
+
module2.exports = { _config: { base: { $desc: "基础设置", STRATZ_API_TOKEN: "※必须。stratz.com的API TOKEN,可在 https://stratz.com/api 获取。", dataParsingTimeoutMinutes: "等待比赛数据解析的时间(单位:分钟)。如果数据解析时间超过等待时间,将直接生成战报而不再等待解析完成。", proxyAddress: "代理地址,不使用代理请留空。 \n※无法使用`proxy-agent`插件配置的全局代理地址,欲使用代理必须设置此项。", suppressStratzNetworkErrors: "**请使用下方通用于 stratz 与 opendota 的配置项`suppressApiNetworkErrors`。 \n此配置项仍然生效,当与`suppressApiNetworkErrors`任一启用时将会使 stratz/opendota 日志降级到debug输出。 \n此配置项将于下版本被移除。**", suppressApiNetworkErrors: "开启后将 stratz/opendota 网络错误日志使用debug级别输出。 \n(如超时、网络不通等,但403 Forbidden除外) \nkoishi默认不显示debug级日志,若需要开启debug日志显示,请见 [📖 配置项#suppressapinetworkerrors](http://sjtdev.github.io/koishi-plugin-dota2tracker/configs.html#suppressapinetworkerrors-boolean)", enableOpenDotaFallback: "启用 OpenDota 作为战报追踪与查询比赛功能的备用数据源。", OPENDOTA_API_KEY: "OpenDota 的订阅付费APIKEY, \n可在 https://www.opendota.com/api-keys 查看详情。 \nOpenDota 的免费用户此处请留空。", OpenDotaIPStack: "若访问 OpenDota API 时频繁失败,可能是由于错误的 IPv6 环境导致的。 \n设置此选项可在访问 OpenDota API 时强制使用 IPv4 尝试解决问题。" }, message: { $desc: "消息设置", useHeroNicknames: "是否使用英雄别名。关闭后仅使用英雄正式名称。", urlInMessageType: { $desc: "在消息中附带链接,<br/>请选择消息类型:", $inner: ["在查询比赛与战报消息中附带stratz比赛页面链接", "在查询玩家信息消息中附带stratz玩家页面链接", "在查询英雄数据消息中附带刀塔百科对应英雄页面链接"] }, rankBroadSwitch: "段位变动播报", rankBroadStar: "星级变动播报", rankBroadLeader: "冠绝名次变动播报", rankBroadFun: "整活播报模板", maxSendItemCount: "最大发送物品图片数量,<br/> 当超过指定数量时将由下方选项决定是否发送查询结果的物品列表图片", showItemListAtTooMuchItems: "在查询结果的物品数量超过指定数量时,是否发送查询结果的物品列表图片", customItemAlias: { $desc: "额外物品别名设置<br/>当插件内置的[物品别名列表](https://github.com/sjtdev/koishi-plugin-dota2tracker/blob/master/src/locales/zh-CN.constants.json#L304-L407)中没有想要的物品别名可在此处追加,如果是插件疏漏的广为人知的物品别名推荐到源码仓库提交issue或pull request完善列表。<br/>(例如 **关键词**: 闪烁匕首,**别名**: 跳刀)", keyword: "关键词", alias: "别名" }, autoRecallTips: "在指令调用结束后自动撤回提示消息,如:“正在搜索对局详情,请稍后……”" }, report: { $desc: "总结设置", dailyReportSwitch: "日报功能", dailyReportHours: "日报时间小时", dailyReportShowCombi: "日报是否显示组合", weeklyReportSwitch: "周报功能", weeklyReportDayHours: "周报发布于周(几)的(几)点", weeklyReportShowCombi: "周报是否显示组合" }, template: { $desc: "模板设置", template_match: "生成比赛信息图片使用的模板,显示效果见 [📖 模板相关#对局信息模板](https://sjtdev.github.io/koishi-plugin-dota2tracker/template-match.html)。", template_player: "生成玩家信息图片使用的模板。(目前仅有一张模板)", template_hero: "生成英雄信息图片使用的模板。(目前仅有一张模板)", playerRankEstimate: "在player模板中对没有段位的玩家进行段位估算 <br>估算的段位将以灰色图片显示", templateFonts: '**>已弃用!<** \n**如果需要配置字体请使用下方`fonts.*`配置项代替!** \n模板所使用的字体名。需要 koishi 所在设备安装字体文件。 \n可添加多个字体名,将从上到下回退到第一个可用字体;若所有字体都不可用,则使用系统默认字体。 \n其中字体名若包含空格或特殊字符需要在名称首尾添加引号(此处建议尽量强制使用引号); \n若使用字体族名则必须**不使用引号**,如:\n```\n"Microsoft YaHei"\nsans-serif\n```\n有关font-family的更多信息,请查阅 [📖 MDN: font-family](https://developer.mozilla.org/zh-CN/docs/Web/CSS/font-family)', fontPath: "字体文件文件夹路径", fonts: { description: "**过渡方案,之后会重启控制台页面并在其中实现更完善更灵活的字体配置方案。** \n模板所使用的字体配置。\n会自动读取配置项 `fontPath` 目录下的字体文件为下方配置项生成可选字体列表。 \n可分别配置无衬线(sans)、衬线(serif)、等宽(mono)字体族的备选表。 \n模板的主要使用字体类型为**无衬线字体**,部分模板的一些文本会使用到**衬线字体**及**等宽字体**。 \n关于字体文件配置的更多信息,请查阅 [📖 模板相关#模板字体](https://sjtdev.github.io/koishi-plugin-dota2tracker/template-fonts.html)", sans: { $desc: "无衬线字体(sans-serif)" }, serif: { $desc: "衬线字体(serif)" }, mono: { $desc: "等宽字体(monospace)" } } } } };
|
|
807
807
|
}
|
|
808
808
|
});
|
|
809
809
|
|
|
@@ -817,7 +817,7 @@ var require_zh_CN_template = __commonJS({
|
|
|
817
817
|
// src/locales/zh-CN.yml
|
|
818
818
|
var require_zh_CN = __commonJS({
|
|
819
819
|
"src/locales/zh-CN.yml"(exports2, module2) {
|
|
820
|
-
module2.exports = { dota2tracker: { heroes_nicknames: { "0": '请严格遵循 "", "" 格式填写(如下方默认数据,注意是英文半角符号),如果格式有误将仅使用英雄默认名称。可以不包含英雄默认名称。', "1": '"敌法师", "敌法", "AM"', "2": '"斧王"', "3": '"祸乱之源", "祸乱", "水桶腰"', "4": '"血魔"', "5": '"水晶室女", "冰女", "CM"', "6": '"卓尔游侠", "小黑"', "7": '"撼地者", "小牛", "牛头"', "8": '"主宰", "剑圣", "jugg", "奶棒人"', "9": '"米拉娜", "白虎", "pom"', "10": '"变体精灵", "水人"', "11": '"影魔", "影魔王", "SF", "影儿魔儿"', "12": '"幻影长矛手", "PL"', "13": '"帕克"', "14": '"帕吉", "屠夫", "扒鸡", "啪唧"', "15": '"雷泽", "电魂", "电棍"', "16": '"沙王", "SK"', "17": '"风暴之灵", "蓝猫"', "18": '"斯温", "流浪剑客", "流浪"', "19": '"小小"', "20": '"复仇之魂", "复仇", "VS"', "21": '"风行者", "风行", "WR"', "22": '"宙斯"', "23": '"昆卡", "船长"', "25": '"莉娜", "火女"', "26": '"莱恩", "恶魔巫师", "Lion"', "27": '"暗影萨满", "小Y", "小歪"', "28": '"斯拉达", "大鱼", "大鱼人"', "29": '"潮汐猎人", "潮汐", "西瓜皮"', "30": '"巫医"', "31": '"巫妖"', "32": '"力丸", "隐形刺客", "隐刺"', "33": '"谜团"', "34": '"修补匠", "TK", "Tinker"', "35": '"狙击手", "矮人火枪手", "火枪", "传说哥"', "36": '"瘟疫法师", "死灵法", "NEC"', "37": '"术士", "Warlock"', "38": '"兽王"', "39": '"痛苦女王", "女王", "QOP"', "40": '"剧毒术士", "剧毒"', "41": '"虚空假面", "虚空", "JB脸"', "42": '"冥魂大帝", "骷髅王"', "43": '"死亡先知", "DP"', "44": '"幻影刺客", "幻刺", "PA"', "45": '"帕格纳", "骨法", "湮灭法师"', "46": '"圣堂刺客", "圣堂", "TA"', "47": '"冥界亚龙", "毒龙", "Viper"', "48": '"露娜", "月骑", "Luna"', "49": '"龙骑士", "龙骑"', "50": '"戴泽", "暗影牧师", "暗牧"', "51": '"发条技师", "发条"', "52": '"拉席克", "老鹿"', "53": '"先知"', "54": '"噬魂鬼", "小狗"', "55": '"黑暗贤者", "黑贤"', "56": '"克林克兹", "小骷髅"', "57": '"全能骑士", "全能"', "58": '"魅惑魔女", "小鹿"', "59": '"哈斯卡", "神灵", "神灵武士"', "60": '"暗夜魔王", "夜魔"', "61": '"育母蜘蛛", "蜘蛛"', "62": '"赏金猎人", "赏金"', "63": '"编织者", "蚂蚁"', "64": '"杰奇洛", "双头龙"', "65": '"蝙蝠骑士", "蝙蝠"', "66": '"陈", "老陈"', "67": '"幽鬼", "SPE", "UG"', "68": '"远古冰魄", "冰魂"', "69": '"末日使者", "末日", "Doom"', "70": '"熊战士", "拍拍", "拍拍熊"', "71": '"裂魂人", "白牛", "sb"', "72": '"矮人直升机", "飞机"', "73": '"炼金术士", "炼金"', "74": '"祈求者", "卡尔"', "75": '"沉默术士", "沉默"', "76": '"殁境神蚀者", "黑鸟"', "77": '"狼人"', "78": '"酒仙", "熊猫", "熊猫酒仙"', "79": '"暗影恶魔", "毒狗"', "80": '"德鲁伊", "熊德"', "81": '"混沌骑士", "混沌", "CK"', "82": '"米波"', "83": '"树精卫士", "大树", "树精"', "84": '"食人魔魔法师", "蓝胖"', "85": '"不朽尸王", "尸王"', "86": '"拉比克"', "87": '"干扰者", "萨尔"', "88": '"司夜刺客", "小强"', "89": '"娜迦海妖", "小娜迦"', "90": '"光之守卫", "光法"', "91": '"艾欧", "小精灵", "精灵", "IO"', "92": '"维萨吉", "死灵龙", "死灵飞龙"', "93": '"斯拉克", "小鱼", "小鱼人"', "94": '"美杜莎", "一姐", "美杜莎"', "95": '"巨魔战将", "巨魔", "巨馍蘸酱"', "96": '"半人马战行者", "人马", "半人马"', "97": '"马格纳斯", "猛犸"', "98": '"伐木机", "花母鸡"', "99": '"钢背兽", "钢背"', "100": '"巨牙海民", "海民"', "101": '"天怒法师", "天怒"', "102": '"亚巴顿"', "103": '"上古巨神", "大牛"', "104": '"军团指挥官", "军团"', "105": '"工程师", "炸弹", "炸弹人"', "106": '"灰烬之灵", "火猫"', "107": '"大地之灵", "土猫"', "108": '"孽主", "大屁股"', "109": '"恐怖利刃", "TB"', "110": '"凤凰", "烧鸡"', "111": '"神谕者", "神谕"', "112": '"寒冬飞龙", "冰龙"', "113": '"天穹守望者", "电狗"', "114": '"齐天大圣", "大圣"', "119": '"邪影芳灵", "小仙女", "花仙子"', "120": '"石鳞剑士", "滚滚"', "121": '"天涯墨客", "墨客"', "123": '"森海飞霞", "松鼠", "小松鼠", "小松许"', "126": '"虚无之灵", "紫猫"', "128": '"电炎绝手", "老奶奶"', "129": '"玛尔斯"', "131": '"百戏大王"', "135": '"破晓辰星", "大锤"', "136": '"玛西"', "137": '"獸", "畜"', "138": '"琼英碧灵", "奶绿", "绿奶奶"', "145": '"凯", "鸟人"', "155": '"朗戈"' }, broadcast: { WIN_NEGATIVE: '"侥幸赢得了比赛", "走狗屎运赢得了比赛", "躺赢了比赛", "打团都没来, 队友4V5赢得了比赛"', WIN_POSITIVE: '"带领团队走向了胜利", "暴打对面后赢得了胜利", " CARRY全场赢得了胜利", "把对面当猪宰了, 赢得了胜利", "又赢了, 这游戏就是这么枯燥, 且乏味", "直接进行一个比赛的赢"', LOSE_NEGATIVE: '"被人按在地上摩擦, 输掉了这场比赛", "悲惨地输掉了比赛", "头都被打歪了, 心态爆炸地输掉了比赛", "捕鱼被鱼吃了, 输掉了比赛", "打的是个几把", "直接进行一个比赛的输"', LOSE_POSITIVE: '"无力回天输掉了比赛", "尽力了, 但还是输了比赛", "背靠世界树, 虽败犹荣", "带不动队友, 输了比赛", "又输了, 很难受, 宁愿输的是我"', message: "{name}的{hero_name}{comment}。\nKDA:{kda},GPM/XPM:{gpm_xpm},补刀/反补:{lh_dn},伤害/塔伤:{damage},参战/参葬率:{kc_dc}", rank_changed: "群友 {name} 段位变动:{prev.medal}{prev.star} → {curr.medal}{curr.star}" }, logger: { fetch_guilds_failed: "获取群组信息失败,将继续后续步骤。", match_tracked: "追踪到最新比赛 {match.id} 来自{#each messageToLogger as item}群组 {item.platform}:{item.guildId} 的玩家 [{#each item.players as player}{player.nickname}({player.steamId}){#if player !== item.players[item.players.length - 1]}、{/if}{/each}]{#if item !== messageToLogger[messageToLogger.length - 1]}、{/if}{/each}。", parse_request_sent: "比赛 {matchId} 解析请求已成功发送至STRATZ服务器。", parse_request_failed: "比赛 {matchId} 解析请求发送失败。", match_parsed: "比赛 {matchId} 已{#if odParsed}由 OpenDota {/if}解析,生成图片并发布于{#each guilds as guild}{guild.platform}:{guild.guildId}{#if guild !== guilds[guilds.length - 1]}、{/if}{/each}。", match_unparsed: "比赛 {matchId} 超过等待时间[{timeout}分钟]仍未解析,生成图片并发布于{#each guilds as guild}{guild.platform}:{guild.guildId}{#if guild !== guilds[guilds.length - 1]}、{/if}{/each}。", waiting_for_parse: "比赛 {matchId} 尚未解析完成,已等待{time}分钟。", report_sent: "发布{title}于{platform}:{guildId}。", rank_sent: "向{platform}:{guildId}发布{player.nickName}({player.steamId})的段位变动信息。", ejs_error: "EJS模板渲染错误:{error}", cron_not_enabled: "未启用cron服务,无法运行战报追踪等定时任务。", stratz_token_banned: "Stratz API 请求被拒绝(403),若频繁发生很有可能意味着您的Token或IP因异常使用被临时限制访问。有关此报错请见文档:http://sjtdev.github.io/koishi-plugin-dota2tracker/api-403.html", opendota_token_banned: "OpenDota API 请求被拒绝(403),请注意访问次数是否超限。", stratz_api_query_error: "Stratz API 返回了有效数据,但同时也返回了查询报错。错误信息: {cause}", opendota_parse_request_sent: "比赛 {matchId} 解析请求已成功发送至 OpenDota 服务器。", opendota_parse_request_failed: "比赛 {matchId} 解析请求向 OpenDota 服务器发送失败。" }, time: { years_months_ago: "{years}年{months}个月前", years_ago: "{years}年前" } } };
|
|
820
|
+
module2.exports = { dota2tracker: { heroes_nicknames: { "0": '请严格遵循 "", "" 格式填写(如下方默认数据,注意是英文半角符号),如果格式有误将仅使用英雄默认名称。可以不包含英雄默认名称。', "1": '"敌法师", "敌法", "AM"', "2": '"斧王"', "3": '"祸乱之源", "祸乱", "水桶腰"', "4": '"血魔"', "5": '"水晶室女", "冰女", "CM"', "6": '"卓尔游侠", "小黑"', "7": '"撼地者", "小牛", "牛头"', "8": '"主宰", "剑圣", "jugg", "奶棒人"', "9": '"米拉娜", "白虎", "pom"', "10": '"变体精灵", "水人"', "11": '"影魔", "影魔王", "SF", "影儿魔儿"', "12": '"幻影长矛手", "PL"', "13": '"帕克"', "14": '"帕吉", "屠夫", "扒鸡", "啪唧"', "15": '"雷泽", "电魂", "电棍"', "16": '"沙王", "SK"', "17": '"风暴之灵", "蓝猫"', "18": '"斯温", "流浪剑客", "流浪"', "19": '"小小"', "20": '"复仇之魂", "复仇", "VS"', "21": '"风行者", "风行", "WR"', "22": '"宙斯"', "23": '"昆卡", "船长"', "25": '"莉娜", "火女"', "26": '"莱恩", "恶魔巫师", "Lion"', "27": '"暗影萨满", "小Y", "小歪"', "28": '"斯拉达", "大鱼", "大鱼人"', "29": '"潮汐猎人", "潮汐", "西瓜皮"', "30": '"巫医"', "31": '"巫妖"', "32": '"力丸", "隐形刺客", "隐刺"', "33": '"谜团"', "34": '"修补匠", "TK", "Tinker"', "35": '"狙击手", "矮人火枪手", "火枪", "传说哥"', "36": '"瘟疫法师", "死灵法", "NEC"', "37": '"术士", "Warlock"', "38": '"兽王"', "39": '"痛苦女王", "女王", "QOP"', "40": '"剧毒术士", "剧毒"', "41": '"虚空假面", "虚空", "JB脸"', "42": '"冥魂大帝", "骷髅王"', "43": '"死亡先知", "DP"', "44": '"幻影刺客", "幻刺", "PA"', "45": '"帕格纳", "骨法", "湮灭法师"', "46": '"圣堂刺客", "圣堂", "TA"', "47": '"冥界亚龙", "毒龙", "Viper"', "48": '"露娜", "月骑", "Luna"', "49": '"龙骑士", "龙骑"', "50": '"戴泽", "暗影牧师", "暗牧"', "51": '"发条技师", "发条"', "52": '"拉席克", "老鹿"', "53": '"先知"', "54": '"噬魂鬼", "小狗"', "55": '"黑暗贤者", "黑贤"', "56": '"克林克兹", "小骷髅"', "57": '"全能骑士", "全能"', "58": '"魅惑魔女", "小鹿"', "59": '"哈斯卡", "神灵", "神灵武士"', "60": '"暗夜魔王", "夜魔"', "61": '"育母蜘蛛", "蜘蛛"', "62": '"赏金猎人", "赏金"', "63": '"编织者", "蚂蚁"', "64": '"杰奇洛", "双头龙"', "65": '"蝙蝠骑士", "蝙蝠"', "66": '"陈", "老陈"', "67": '"幽鬼", "SPE", "UG"', "68": '"远古冰魄", "冰魂"', "69": '"末日使者", "末日", "Doom"', "70": '"熊战士", "拍拍", "拍拍熊"', "71": '"裂魂人", "白牛", "sb"', "72": '"矮人直升机", "飞机"', "73": '"炼金术士", "炼金"', "74": '"祈求者", "卡尔"', "75": '"沉默术士", "沉默"', "76": '"殁境神蚀者", "黑鸟"', "77": '"狼人"', "78": '"酒仙", "熊猫", "熊猫酒仙"', "79": '"暗影恶魔", "毒狗"', "80": '"德鲁伊", "熊德"', "81": '"混沌骑士", "混沌", "CK"', "82": '"米波"', "83": '"树精卫士", "大树", "树精"', "84": '"食人魔魔法师", "蓝胖"', "85": '"不朽尸王", "尸王"', "86": '"拉比克"', "87": '"干扰者", "萨尔"', "88": '"司夜刺客", "小强"', "89": '"娜迦海妖", "小娜迦"', "90": '"光之守卫", "光法"', "91": '"艾欧", "小精灵", "精灵", "IO"', "92": '"维萨吉", "死灵龙", "死灵飞龙"', "93": '"斯拉克", "小鱼", "小鱼人"', "94": '"美杜莎", "一姐", "美杜莎"', "95": '"巨魔战将", "巨魔", "巨馍蘸酱"', "96": '"半人马战行者", "人马", "半人马"', "97": '"马格纳斯", "猛犸"', "98": '"伐木机", "花母鸡"', "99": '"钢背兽", "钢背"', "100": '"巨牙海民", "海民"', "101": '"天怒法师", "天怒"', "102": '"亚巴顿"', "103": '"上古巨神", "大牛"', "104": '"军团指挥官", "军团"', "105": '"工程师", "炸弹", "炸弹人"', "106": '"灰烬之灵", "火猫"', "107": '"大地之灵", "土猫"', "108": '"孽主", "大屁股"', "109": '"恐怖利刃", "TB"', "110": '"凤凰", "烧鸡"', "111": '"神谕者", "神谕"', "112": '"寒冬飞龙", "冰龙"', "113": '"天穹守望者", "电狗"', "114": '"齐天大圣", "大圣"', "119": '"邪影芳灵", "小仙女", "花仙子"', "120": '"石鳞剑士", "滚滚"', "121": '"天涯墨客", "墨客"', "123": '"森海飞霞", "松鼠", "小松鼠", "小松许"', "126": '"虚无之灵", "紫猫"', "128": '"电炎绝手", "老奶奶"', "129": '"玛尔斯"', "131": '"百戏大王"', "135": '"破晓辰星", "大锤"', "136": '"玛西"', "137": '"獸", "畜"', "138": '"琼英碧灵", "奶绿", "绿奶奶"', "145": '"凯", "鸟人"', "155": '"朗戈"' }, broadcast: { WIN_NEGATIVE: '"侥幸赢得了比赛", "走狗屎运赢得了比赛", "躺赢了比赛", "打团都没来, 队友4V5赢得了比赛"', WIN_POSITIVE: '"带领团队走向了胜利", "暴打对面后赢得了胜利", " CARRY全场赢得了胜利", "把对面当猪宰了, 赢得了胜利", "又赢了, 这游戏就是这么枯燥, 且乏味", "直接进行一个比赛的赢"', LOSE_NEGATIVE: '"被人按在地上摩擦, 输掉了这场比赛", "悲惨地输掉了比赛", "头都被打歪了, 心态爆炸地输掉了比赛", "捕鱼被鱼吃了, 输掉了比赛", "打的是个几把", "直接进行一个比赛的输"', LOSE_POSITIVE: '"无力回天输掉了比赛", "尽力了, 但还是输了比赛", "背靠世界树, 虽败犹荣", "带不动队友, 输了比赛", "又输了, 很难受, 宁愿输的是我"', message: "{name}的{hero_name}{comment}。\nKDA:{kda},GPM/XPM:{gpm_xpm},补刀/反补:{lh_dn},伤害/塔伤:{damage},参战/参葬率:{kc_dc}", rank_changed: "群友 {name} 段位变动:{prev.medal}{prev.star} → {curr.medal}{curr.star}" }, logger: { fetch_guilds_failed: "获取群组信息失败,将继续后续步骤。", match_tracked: "追踪到最新比赛 {match.id} 来自{#each messageToLogger as item}群组 {item.platform}:{item.guildId} 的玩家 [{#each item.players as player}{player.nickname}({player.steamId}){#if player !== item.players[item.players.length - 1]}、{/if}{/each}]{#if item !== messageToLogger[messageToLogger.length - 1]}、{/if}{/each}。", parse_request_sent: "比赛 {matchId} 解析请求已成功发送至STRATZ服务器。", parse_request_failed: "比赛 {matchId} 解析请求发送失败。", match_parsed: "比赛 {matchId} 已{#if odParsed}由 OpenDota {/if}解析,生成图片并发布于{#each guilds as guild}{guild.platform}:{guild.guildId}{#if guild !== guilds[guilds.length - 1]}、{/if}{/each}。", match_unparsed: "比赛 {matchId} 超过等待时间[{timeout}分钟]仍未解析,生成图片并发布于{#each guilds as guild}{guild.platform}:{guild.guildId}{#if guild !== guilds[guilds.length - 1]}、{/if}{/each}。", waiting_for_parse: "比赛 {matchId} 尚未解析完成,已等待{time}分钟。", report_sent: "发布{title}于{platform}:{guildId}。", rank_sent: "向{platform}:{guildId}发布{player.nickName}({player.steamId})的段位变动信息。", ejs_error: "EJS模板渲染错误:{error}", cron_not_enabled: "未启用cron服务,无法运行战报追踪等定时任务。", stratz_token_banned: "Stratz API 请求被拒绝(403),若频繁发生很有可能意味着您的Token或IP因异常使用被临时限制访问。有关此报错请见文档:http://sjtdev.github.io/koishi-plugin-dota2tracker/api-403.html", opendota_token_banned: "OpenDota API 请求被拒绝(403),请注意访问次数是否超限。", stratz_api_query_error: "Stratz API 返回了有效数据,但同时也返回了查询报错。错误信息: {cause}", opendota_parse_request_sent: "比赛 {matchId} 解析请求已成功发送至 OpenDota 服务器。", opendota_parse_request_failed: "比赛 {matchId} 解析请求向 OpenDota 服务器发送失败。", font_loaded: "字体目录发生变化: {filename} ({eventType}),重新加载字体列表..." }, time: { years_months_ago: "{years}年{months}个月前", years_ago: "{years}年前" } } };
|
|
821
821
|
}
|
|
822
822
|
});
|
|
823
823
|
|
|
@@ -831,7 +831,7 @@ __export(src_exports, {
|
|
|
831
831
|
usage: () => usage
|
|
832
832
|
});
|
|
833
833
|
module.exports = __toCommonJS(src_exports);
|
|
834
|
-
var
|
|
834
|
+
var import_node_path5 = __toESM(require("node:path"));
|
|
835
835
|
|
|
836
836
|
// src/app/common/i18n.ts
|
|
837
837
|
var import_koishi = require("koishi");
|
|
@@ -1393,7 +1393,7 @@ var ItemService = class _ItemService extends import_koishi3.Service {
|
|
|
1393
1393
|
var import_koishi4 = require("koishi");
|
|
1394
1394
|
|
|
1395
1395
|
// src/app/common/error.ts
|
|
1396
|
-
var
|
|
1396
|
+
var import_node_util = require("node:util");
|
|
1397
1397
|
var import_axios = __toESM(require("axios"));
|
|
1398
1398
|
var NetworkError = class extends Error {
|
|
1399
1399
|
static {
|
|
@@ -1510,7 +1510,7 @@ function handleError(error, logger, i18n, config) {
|
|
|
1510
1510
|
if (error instanceof Error && error.stack) {
|
|
1511
1511
|
output += error.stack;
|
|
1512
1512
|
} else {
|
|
1513
|
-
output += (0,
|
|
1513
|
+
output += (0, import_node_util.inspect)(error, { depth: null });
|
|
1514
1514
|
}
|
|
1515
1515
|
logger.error(output);
|
|
1516
1516
|
}
|
|
@@ -2387,9 +2387,9 @@ var DatabaseService = class extends import_koishi7.Service {
|
|
|
2387
2387
|
};
|
|
2388
2388
|
|
|
2389
2389
|
// src/app/data/stratz.api.ts
|
|
2390
|
-
var
|
|
2390
|
+
var import_node_fs = __toESM(require("node:fs"));
|
|
2391
2391
|
var import_koishi8 = require("koishi");
|
|
2392
|
-
var
|
|
2392
|
+
var import_node_path = __toESM(require("node:path"));
|
|
2393
2393
|
var import_axios2 = __toESM(require("axios"));
|
|
2394
2394
|
var import_https_proxy_agent = require("https-proxy-agent");
|
|
2395
2395
|
var StratzAPI = class extends import_koishi8.Service {
|
|
@@ -2404,7 +2404,7 @@ var StratzAPI = class extends import_koishi8.Service {
|
|
|
2404
2404
|
constructor(ctx, currentDir) {
|
|
2405
2405
|
super(ctx, "dota2tracker.stratz-api", true);
|
|
2406
2406
|
this.config = ctx.config;
|
|
2407
|
-
this.graphqlQueriesDir =
|
|
2407
|
+
this.graphqlQueriesDir = import_node_path.default.join(currentDir, "queries");
|
|
2408
2408
|
this.queue = new MiniQueue(ctx, { interval: 200 });
|
|
2409
2409
|
this.http = import_axios2.default.create({ timeout: 15e3, signal: this.abortController.signal });
|
|
2410
2410
|
ctx.on("dispose", () => this.dispose());
|
|
@@ -2531,7 +2531,7 @@ var StratzAPI = class extends import_koishi8.Service {
|
|
|
2531
2531
|
});
|
|
2532
2532
|
}
|
|
2533
2533
|
loadGraphqlFile(queryName) {
|
|
2534
|
-
return
|
|
2534
|
+
return import_node_fs.default.readFileSync(import_node_path.default.join(this.graphqlQueriesDir, `${queryName}.graphql`), { encoding: "utf-8" }).replace(/[\r\n]+/g, " ");
|
|
2535
2535
|
}
|
|
2536
2536
|
};
|
|
2537
2537
|
var MiniQueue = class {
|
|
@@ -2605,7 +2605,7 @@ var ValveAPI = class extends import_koishi9.Service {
|
|
|
2605
2605
|
this.abortController.abort();
|
|
2606
2606
|
}
|
|
2607
2607
|
// 7. 提取通用的 fetchData
|
|
2608
|
-
async fetchData(
|
|
2608
|
+
async fetchData(path6, languageTag) {
|
|
2609
2609
|
const config = {
|
|
2610
2610
|
headers: {},
|
|
2611
2611
|
httpAgent: void 0,
|
|
@@ -2620,10 +2620,10 @@ var ValveAPI = class extends import_koishi9.Service {
|
|
|
2620
2620
|
config.params.language = this.ctx.dota2tracker.i18n.getValveLanguageTag(languageTag);
|
|
2621
2621
|
}
|
|
2622
2622
|
try {
|
|
2623
|
-
const response = await this.http.get(
|
|
2623
|
+
const response = await this.http.get(path6, config);
|
|
2624
2624
|
return response.data;
|
|
2625
2625
|
} catch (error) {
|
|
2626
|
-
processFetchError(error, this.name,
|
|
2626
|
+
processFetchError(error, this.name, path6);
|
|
2627
2627
|
}
|
|
2628
2628
|
}
|
|
2629
2629
|
async queryHeroDetailsFromValve(heroId, languageTag = "zh-CN") {
|
|
@@ -2647,8 +2647,8 @@ var ValveAPI = class extends import_koishi9.Service {
|
|
|
2647
2647
|
// src/app/presentation/view.renderer.ts
|
|
2648
2648
|
var import_koishi10 = require("koishi");
|
|
2649
2649
|
var import_ejs = __toESM(require("ejs"));
|
|
2650
|
-
var
|
|
2651
|
-
var
|
|
2650
|
+
var import_node_fs2 = __toESM(require("node:fs"));
|
|
2651
|
+
var import_node_path2 = __toESM(require("node:path"));
|
|
2652
2652
|
|
|
2653
2653
|
// src/app/common/types.ts
|
|
2654
2654
|
var ImageType = /* @__PURE__ */ ((ImageType2) => {
|
|
@@ -2670,6 +2670,7 @@ var ImageFormat = /* @__PURE__ */ ((ImageFormat2) => {
|
|
|
2670
2670
|
|
|
2671
2671
|
// src/app/presentation/view.renderer.ts
|
|
2672
2672
|
var import_luxon5 = require("luxon");
|
|
2673
|
+
var import_node_url = require("node:url");
|
|
2673
2674
|
var ViewRenderer = class extends import_koishi10.Service {
|
|
2674
2675
|
static {
|
|
2675
2676
|
__name(this, "ViewRenderer");
|
|
@@ -2678,18 +2679,56 @@ var ViewRenderer = class extends import_koishi10.Service {
|
|
|
2678
2679
|
constructor(ctx, currentDir) {
|
|
2679
2680
|
super(ctx, "dota2tracker.image", true);
|
|
2680
2681
|
this.config = ctx.config;
|
|
2681
|
-
this.templateDir =
|
|
2682
|
+
this.templateDir = import_node_path2.default.join(currentDir, "templates");
|
|
2682
2683
|
}
|
|
2683
2684
|
async renderToImageByFile(data, templateName, type, languageTag) {
|
|
2684
2685
|
const html = await this.generateHTML(data, { source: "FILE", templateName, type }, languageTag);
|
|
2685
|
-
return this.
|
|
2686
|
+
return this.render(html);
|
|
2686
2687
|
}
|
|
2687
2688
|
async renderToImageByEJSCode(data, ejsCode, languageTag) {
|
|
2688
2689
|
const html = await this.generateHTML(data, { source: "CODE", code: ejsCode }, languageTag);
|
|
2689
|
-
return this.
|
|
2690
|
+
return this.render(html);
|
|
2690
2691
|
}
|
|
2691
2692
|
async renderToImageByHTML(html) {
|
|
2692
|
-
return this.
|
|
2693
|
+
return this.render(html);
|
|
2694
|
+
}
|
|
2695
|
+
async render(html) {
|
|
2696
|
+
const { fonts } = this.config;
|
|
2697
|
+
const finalHtml = html.replace("</head>", `${this.getFontStyleBlock()}</head>`);
|
|
2698
|
+
const fontFamilies = Array.from(/* @__PURE__ */ new Set([
|
|
2699
|
+
...fonts.sans || [],
|
|
2700
|
+
...fonts.serif || [],
|
|
2701
|
+
...fonts.mono || []
|
|
2702
|
+
])).filter(Boolean);
|
|
2703
|
+
return this.ctx.puppeteer.render(
|
|
2704
|
+
finalHtml,
|
|
2705
|
+
fontFamilies.length === 0 ? void 0 : async (page, next) => {
|
|
2706
|
+
let fontInfos = [];
|
|
2707
|
+
try {
|
|
2708
|
+
fontInfos = this.ctx.dota2tracker.font.getFonts(fontFamilies);
|
|
2709
|
+
} catch (e) {
|
|
2710
|
+
this.ctx.logger.warn("Failed to get font info, using fallback fonts:", e);
|
|
2711
|
+
}
|
|
2712
|
+
if (fontInfos.length > 0) {
|
|
2713
|
+
await page.exposeFunction("dota2tracker_font_service_get_format", (format) => {
|
|
2714
|
+
return this.ctx.dota2tracker.font.getFontFormat(format);
|
|
2715
|
+
});
|
|
2716
|
+
await page.evaluate(async (fonts2) => {
|
|
2717
|
+
const win = window;
|
|
2718
|
+
const loaders = fonts2.map(async (font) => {
|
|
2719
|
+
const format = await win.dota2tracker_font_service_get_format(font.format);
|
|
2720
|
+
const fontFace = new win.FontFace(font.family, `url("${font.path}") format("${format}")`, font.descriptors);
|
|
2721
|
+
win.document.fonts.add(fontFace);
|
|
2722
|
+
await fontFace.load();
|
|
2723
|
+
});
|
|
2724
|
+
await Promise.all(loaders);
|
|
2725
|
+
await win.document.fonts.ready;
|
|
2726
|
+
}, fontInfos.map((f) => ({ ...f, path: (0, import_node_url.pathToFileURL)(f.path).href })));
|
|
2727
|
+
}
|
|
2728
|
+
const body = await page.$("body");
|
|
2729
|
+
return next(body);
|
|
2730
|
+
}
|
|
2731
|
+
);
|
|
2693
2732
|
}
|
|
2694
2733
|
async generateHTML(data, template, languageTag) {
|
|
2695
2734
|
const templateData = {
|
|
@@ -2707,7 +2746,7 @@ var ViewRenderer = class extends import_koishi10.Service {
|
|
|
2707
2746
|
try {
|
|
2708
2747
|
let html;
|
|
2709
2748
|
if (template.source === "FILE") {
|
|
2710
|
-
const templatePath =
|
|
2749
|
+
const templatePath = import_node_path2.default.join(this.templateDir, template.type, `${template.templateName}.ejs`);
|
|
2711
2750
|
html = await import_ejs.default.renderFile(templatePath, templateData, {
|
|
2712
2751
|
strict: false
|
|
2713
2752
|
});
|
|
@@ -2717,7 +2756,7 @@ var ViewRenderer = class extends import_koishi10.Service {
|
|
|
2717
2756
|
async: true
|
|
2718
2757
|
});
|
|
2719
2758
|
}
|
|
2720
|
-
if (process.env.NODE_ENV === "development")
|
|
2759
|
+
if (process.env.NODE_ENV === "development") import_node_fs2.default.writeFileSync(import_node_path2.default.resolve(process.cwd(), "temp.html"), html);
|
|
2721
2760
|
return html;
|
|
2722
2761
|
} catch (error) {
|
|
2723
2762
|
this.logger.error(error);
|
|
@@ -2727,22 +2766,208 @@ var ViewRenderer = class extends import_koishi10.Service {
|
|
|
2727
2766
|
getImageUrl(image, type = "local" /* Local */, format = "png" /* png */) {
|
|
2728
2767
|
if (type === "local" /* Local */) {
|
|
2729
2768
|
try {
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
const base64Data = imageData.toString("base64");
|
|
2733
|
-
return `data:image/png;base64,${base64Data}`;
|
|
2769
|
+
const absolutePath = import_node_path2.default.join(this.templateDir, "images", `${image}.${format}`);
|
|
2770
|
+
return (0, import_node_url.pathToFileURL)(absolutePath).href;
|
|
2734
2771
|
} catch (error) {
|
|
2735
2772
|
console.error(error);
|
|
2736
2773
|
return "";
|
|
2737
2774
|
}
|
|
2738
2775
|
} else return `https://cdn.akamai.steamstatic.com/apps/dota2/images/dota_react/${type}/${image}.${format}`;
|
|
2739
2776
|
}
|
|
2777
|
+
// 1. 生成 CSS 注入块
|
|
2778
|
+
getFontStyleBlock() {
|
|
2779
|
+
const { fonts } = this.config;
|
|
2780
|
+
const formatFontStack = /* @__PURE__ */ __name((fontList, fallback) => {
|
|
2781
|
+
const quoted = fontList.map((f) => `"${f}"`).join(", ");
|
|
2782
|
+
return quoted ? `${quoted}, ${fallback}` : fallback;
|
|
2783
|
+
}, "formatFontStack");
|
|
2784
|
+
const sans = formatFontStack(fonts.sans, "sans-serif");
|
|
2785
|
+
const serif = formatFontStack(fonts.serif, "serif");
|
|
2786
|
+
const mono = formatFontStack(fonts.mono, "monospace");
|
|
2787
|
+
return `
|
|
2788
|
+
<style>
|
|
2789
|
+
/* 1. 定义 CSS 变量 (供原生 CSS 使用) */
|
|
2790
|
+
:root {
|
|
2791
|
+
--font-sans: ${sans};
|
|
2792
|
+
--font-serif: ${serif};
|
|
2793
|
+
--font-mono: ${mono};
|
|
2794
|
+
}
|
|
2795
|
+
|
|
2796
|
+
/* 2. 暴力劫持 Tailwind 类 (供 Tailwind 使用) */
|
|
2797
|
+
/* 使用 !important 确保优先级最高,覆盖 Tailwind 默认生成的样式 */
|
|
2798
|
+
.font-sans { font-family: var(--font-sans) !important; }
|
|
2799
|
+
.font-serif { font-family: var(--font-serif) !important; }
|
|
2800
|
+
.font-mono { font-family: var(--font-mono) !important; }
|
|
2801
|
+
|
|
2802
|
+
/* 3. 设置全局默认字体 */
|
|
2803
|
+
body {
|
|
2804
|
+
font-family: var(--font-sans);
|
|
2805
|
+
}
|
|
2806
|
+
</style>
|
|
2807
|
+
`;
|
|
2808
|
+
}
|
|
2740
2809
|
};
|
|
2741
2810
|
|
|
2742
|
-
// src/app/presentation/
|
|
2811
|
+
// src/app/presentation/font.service.ts
|
|
2743
2812
|
var import_koishi11 = require("koishi");
|
|
2813
|
+
var import_node_path3 = __toESM(require("node:path"));
|
|
2814
|
+
var import_node_fs3 = __toESM(require("node:fs"));
|
|
2815
|
+
var import_fontkit = __toESM(require("fontkit"));
|
|
2816
|
+
var FontFormats = {
|
|
2817
|
+
WEB_OPEN_FONT_FORMAT: "woff",
|
|
2818
|
+
WEB_OPEN_FONT_FORMAT_2: "woff2",
|
|
2819
|
+
TRUE_TYPE_FONT: "ttf",
|
|
2820
|
+
OPEN_TYPE_FONT: "otf",
|
|
2821
|
+
SPLINE_FONT: "sfnt",
|
|
2822
|
+
TRUE_TYPE_COLLECTION: "ttc",
|
|
2823
|
+
GOOGLE_FONT: "google",
|
|
2824
|
+
MANIFEST: "manifest"
|
|
2825
|
+
};
|
|
2826
|
+
var FontService = class extends import_koishi11.Service {
|
|
2827
|
+
static {
|
|
2828
|
+
__name(this, "FontService");
|
|
2829
|
+
}
|
|
2830
|
+
watcher;
|
|
2831
|
+
debounceTimer;
|
|
2832
|
+
fonts = [];
|
|
2833
|
+
constructor(ctx) {
|
|
2834
|
+
super(ctx, "dota2tracker.font", true);
|
|
2835
|
+
this.config = ctx.config;
|
|
2836
|
+
this.ctx.on("ready", async () => await this.initialize());
|
|
2837
|
+
}
|
|
2838
|
+
async initialize() {
|
|
2839
|
+
const fontsPath = import_node_path3.default.resolve(this.ctx.baseDir, this.config.fontPath);
|
|
2840
|
+
if (!import_node_fs3.default.existsSync(fontsPath)) {
|
|
2841
|
+
try {
|
|
2842
|
+
import_node_fs3.default.mkdirSync(fontsPath, { recursive: true });
|
|
2843
|
+
} catch (e) {
|
|
2844
|
+
this.logger.warn(`Failed to create font directory: ${e.message}`);
|
|
2845
|
+
return;
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
await this.loadFonts(fontsPath);
|
|
2849
|
+
try {
|
|
2850
|
+
this.watcher = import_node_fs3.default.watch(fontsPath, (eventType, filename) => {
|
|
2851
|
+
if (filename && /\.(ttf|otf|woff2?|ttc|sfnt)$/.test(filename)) {
|
|
2852
|
+
this.logger.debug(this.ctx.dota2tracker.i18n.gt("dota2tracker.logger.font_loader.reload", { filename, eventType }));
|
|
2853
|
+
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
|
2854
|
+
this.debounceTimer = setTimeout(() => this.loadFonts(fontsPath), 200);
|
|
2855
|
+
}
|
|
2856
|
+
});
|
|
2857
|
+
} catch (e) {
|
|
2858
|
+
this.logger.warn(`Failed to watch font directory: ${e.message}`);
|
|
2859
|
+
}
|
|
2860
|
+
this.ctx.on("dispose", () => {
|
|
2861
|
+
if (this.watcher) this.watcher.close();
|
|
2862
|
+
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
|
2863
|
+
});
|
|
2864
|
+
}
|
|
2865
|
+
async loadFonts(fontsPath) {
|
|
2866
|
+
if (!import_node_fs3.default.existsSync(fontsPath)) return;
|
|
2867
|
+
const newFonts = [];
|
|
2868
|
+
try {
|
|
2869
|
+
import_node_fs3.default.readdirSync(fontsPath).forEach((file) => {
|
|
2870
|
+
if (!/\.(ttf|otf|woff2?|ttc|sfnt)$/.test(file)) return;
|
|
2871
|
+
const fullPath = import_node_path3.default.join(fontsPath, file);
|
|
2872
|
+
try {
|
|
2873
|
+
const fontOrCollection = import_fontkit.default.openSync(fullPath);
|
|
2874
|
+
const parsedFonts = "fonts" in fontOrCollection ? fontOrCollection.fonts : [fontOrCollection];
|
|
2875
|
+
parsedFonts.forEach((font) => {
|
|
2876
|
+
const family = this.getFontFamily(font);
|
|
2877
|
+
const descriptors = this.getFontDescriptors(font);
|
|
2878
|
+
newFonts.push({
|
|
2879
|
+
path: fullPath,
|
|
2880
|
+
family,
|
|
2881
|
+
format: this.getFileFormat(file),
|
|
2882
|
+
descriptors
|
|
2883
|
+
});
|
|
2884
|
+
});
|
|
2885
|
+
} catch (err) {
|
|
2886
|
+
this.logger.warn(`Failed to parse font ${file}: ${err.message}`);
|
|
2887
|
+
}
|
|
2888
|
+
});
|
|
2889
|
+
this.fonts = newFonts;
|
|
2890
|
+
const fontFamilies = Array.from(new Set(newFonts.map((f) => f.family))).sort();
|
|
2891
|
+
this.ctx.schema.set("dota2tracker.fonts", import_koishi11.Schema.union(fontFamilies));
|
|
2892
|
+
this.logger.info(`Loaded ${newFonts.length} fonts from ${fontsPath}`);
|
|
2893
|
+
} catch (e) {
|
|
2894
|
+
this.ctx.logger.warn(`Failed to load fonts: ${e.message}`);
|
|
2895
|
+
}
|
|
2896
|
+
}
|
|
2897
|
+
getFonts(families) {
|
|
2898
|
+
const uniquePaths = /* @__PURE__ */ new Set();
|
|
2899
|
+
const result = [];
|
|
2900
|
+
const uniqueFamilies = Array.from(new Set(families));
|
|
2901
|
+
for (const family of uniqueFamilies) {
|
|
2902
|
+
const matched = this.fonts.filter((f) => f.family === family);
|
|
2903
|
+
for (const font of matched) {
|
|
2904
|
+
if (!uniquePaths.has(font.path)) {
|
|
2905
|
+
uniquePaths.add(font.path);
|
|
2906
|
+
result.push(font);
|
|
2907
|
+
}
|
|
2908
|
+
}
|
|
2909
|
+
}
|
|
2910
|
+
return result;
|
|
2911
|
+
}
|
|
2912
|
+
getFontFamily(font) {
|
|
2913
|
+
const records = font.name?.records;
|
|
2914
|
+
if (records?.preferredFamily) {
|
|
2915
|
+
const langKeys = Object.keys(records.preferredFamily);
|
|
2916
|
+
const enKey = langKeys.find((k) => k.startsWith("en"));
|
|
2917
|
+
if (enKey) return records.preferredFamily[enKey];
|
|
2918
|
+
if (langKeys.length > 0) return records.preferredFamily[langKeys[0]];
|
|
2919
|
+
}
|
|
2920
|
+
return font.familyName;
|
|
2921
|
+
}
|
|
2922
|
+
getFontDescriptors(font) {
|
|
2923
|
+
const descriptors = {};
|
|
2924
|
+
if (font.usWeightClass) {
|
|
2925
|
+
descriptors.weight = font.usWeightClass.toString();
|
|
2926
|
+
} else if (font["OS/2"] && font["OS/2"].usWeightClass) {
|
|
2927
|
+
descriptors.weight = font["OS/2"].usWeightClass.toString();
|
|
2928
|
+
}
|
|
2929
|
+
if (font.italicAngle && font.italicAngle !== 0) {
|
|
2930
|
+
descriptors.style = "italic";
|
|
2931
|
+
}
|
|
2932
|
+
const widthClass = font["OS/2"]?.usWidthClass;
|
|
2933
|
+
if (widthClass) {
|
|
2934
|
+
const widthMap = {
|
|
2935
|
+
1: "ultra-condensed",
|
|
2936
|
+
2: "extra-condensed",
|
|
2937
|
+
3: "condensed",
|
|
2938
|
+
4: "semi-condensed",
|
|
2939
|
+
5: "normal",
|
|
2940
|
+
6: "semi-expanded",
|
|
2941
|
+
7: "expanded",
|
|
2942
|
+
8: "extra-expanded",
|
|
2943
|
+
9: "ultra-expanded"
|
|
2944
|
+
};
|
|
2945
|
+
if (widthMap[widthClass] && widthMap[widthClass] !== "normal") {
|
|
2946
|
+
descriptors.stretch = widthMap[widthClass];
|
|
2947
|
+
}
|
|
2948
|
+
}
|
|
2949
|
+
return descriptors;
|
|
2950
|
+
}
|
|
2951
|
+
getFileFormat(fileName) {
|
|
2952
|
+
return import_node_path3.default.extname(fileName).slice(1);
|
|
2953
|
+
}
|
|
2954
|
+
getFontFormat(format) {
|
|
2955
|
+
const formatMap = {
|
|
2956
|
+
[FontFormats.TRUE_TYPE_FONT]: "truetype",
|
|
2957
|
+
[FontFormats.OPEN_TYPE_FONT]: "opentype",
|
|
2958
|
+
[FontFormats.WEB_OPEN_FONT_FORMAT]: "woff",
|
|
2959
|
+
[FontFormats.WEB_OPEN_FONT_FORMAT_2]: "woff2",
|
|
2960
|
+
[FontFormats.TRUE_TYPE_COLLECTION]: "collection",
|
|
2961
|
+
[FontFormats.SPLINE_FONT]: "sfnt"
|
|
2962
|
+
};
|
|
2963
|
+
return formatMap[format] || format;
|
|
2964
|
+
}
|
|
2965
|
+
};
|
|
2966
|
+
|
|
2967
|
+
// src/app/presentation/message.builder.ts
|
|
2968
|
+
var import_koishi12 = require("koishi");
|
|
2744
2969
|
var import_luxon6 = require("luxon");
|
|
2745
|
-
var MessageBuilder = class extends
|
|
2970
|
+
var MessageBuilder = class extends import_koishi12.Service {
|
|
2746
2971
|
static {
|
|
2747
2972
|
__name(this, "MessageBuilder");
|
|
2748
2973
|
}
|
|
@@ -2866,7 +3091,7 @@ var MessageBuilder = class extends import_koishi11.Service {
|
|
|
2866
3091
|
const playerIds = players.map((player) => player.steamId);
|
|
2867
3092
|
let broadPlayers = match.players.filter((item) => playerIds.includes(item.steamAccountId));
|
|
2868
3093
|
for (let player of broadPlayers) {
|
|
2869
|
-
const random = new
|
|
3094
|
+
const random = new import_koishi12.Random(() => enhancedSimpleHashToSeed(`${match.id}-${player.steamAccountId}-${player.playerSlot}`));
|
|
2870
3095
|
let comment;
|
|
2871
3096
|
if (player.isRadiant == match.didRadiantWin) {
|
|
2872
3097
|
if (player.deathContribution < 0.2 || player.killContribution > 0.75 || player.heroDamage / player.networth > 1.5 || player.towerDamage > 1e4 || player.imp > 0)
|
|
@@ -3029,9 +3254,9 @@ function customConvertArrayOfString(str) {
|
|
|
3029
3254
|
__name(customConvertArrayOfString, "customConvertArrayOfString");
|
|
3030
3255
|
|
|
3031
3256
|
// src/app/tasks/match-watcher.task.ts
|
|
3032
|
-
var
|
|
3257
|
+
var import_koishi13 = require("koishi");
|
|
3033
3258
|
var import_luxon7 = require("luxon");
|
|
3034
|
-
var MatchWatcherTask = class extends
|
|
3259
|
+
var MatchWatcherTask = class extends import_koishi13.Service {
|
|
3035
3260
|
static {
|
|
3036
3261
|
__name(this, "MatchWatcherTask");
|
|
3037
3262
|
}
|
|
@@ -3154,9 +3379,9 @@ var MatchWatcherTask = class extends import_koishi12.Service {
|
|
|
3154
3379
|
};
|
|
3155
3380
|
|
|
3156
3381
|
// src/app/tasks/parse-polling.task.ts
|
|
3157
|
-
var
|
|
3382
|
+
var import_koishi14 = require("koishi");
|
|
3158
3383
|
var import_luxon8 = require("luxon");
|
|
3159
|
-
var ParsePollingTask = class extends
|
|
3384
|
+
var ParsePollingTask = class extends import_koishi14.Service {
|
|
3160
3385
|
static {
|
|
3161
3386
|
__name(this, "ParsePollingTask");
|
|
3162
3387
|
}
|
|
@@ -3291,9 +3516,9 @@ var ParsePollingTask = class extends import_koishi13.Service {
|
|
|
3291
3516
|
};
|
|
3292
3517
|
|
|
3293
3518
|
// src/app/tasks/report.task.ts
|
|
3294
|
-
var
|
|
3519
|
+
var import_koishi15 = require("koishi");
|
|
3295
3520
|
var import_luxon9 = require("luxon");
|
|
3296
|
-
var ReportTask = class extends
|
|
3521
|
+
var ReportTask = class extends import_koishi15.Service {
|
|
3297
3522
|
static {
|
|
3298
3523
|
__name(this, "ReportTask");
|
|
3299
3524
|
}
|
|
@@ -3790,12 +4015,12 @@ function registerUserCommand(ctx) {
|
|
|
3790
4015
|
__name(registerUserCommand, "registerUserCommand");
|
|
3791
4016
|
|
|
3792
4017
|
// src/app/data/opendota.api.ts
|
|
3793
|
-
var
|
|
4018
|
+
var import_koishi16 = require("koishi");
|
|
3794
4019
|
var import_axios4 = __toESM(require("axios"));
|
|
3795
4020
|
var import_https_proxy_agent3 = require("https-proxy-agent");
|
|
3796
|
-
var
|
|
3797
|
-
var
|
|
3798
|
-
var OpenDotaAPI = class extends
|
|
4021
|
+
var import_node_http = require("node:http");
|
|
4022
|
+
var import_node_https = require("node:https");
|
|
4023
|
+
var OpenDotaAPI = class extends import_koishi16.Service {
|
|
3799
4024
|
static {
|
|
3800
4025
|
__name(this, "OpenDotaAPI");
|
|
3801
4026
|
}
|
|
@@ -3812,18 +4037,18 @@ var OpenDotaAPI = class extends import_koishi15.Service {
|
|
|
3812
4037
|
this.abortController.abort();
|
|
3813
4038
|
}
|
|
3814
4039
|
async queryMatchInfo(matchId) {
|
|
3815
|
-
const
|
|
3816
|
-
const data = await this.fetchData("GET",
|
|
3817
|
-
this.ctx.dota2tracker.cache.addOpendotaAPIRequestLog(
|
|
4040
|
+
const path6 = `/matches/${matchId}`;
|
|
4041
|
+
const data = await this.fetchData("GET", path6);
|
|
4042
|
+
this.ctx.dota2tracker.cache.addOpendotaAPIRequestLog(path6, 1);
|
|
3818
4043
|
return data;
|
|
3819
4044
|
}
|
|
3820
4045
|
async requestParseMatch(matchId) {
|
|
3821
|
-
const
|
|
3822
|
-
const job = await this.fetchData("POST",
|
|
3823
|
-
this.ctx.dota2tracker.cache.addOpendotaAPIRequestLog(
|
|
4046
|
+
const path6 = `/request/${matchId}`;
|
|
4047
|
+
const job = await this.fetchData("POST", path6);
|
|
4048
|
+
this.ctx.dota2tracker.cache.addOpendotaAPIRequestLog(path6, 10);
|
|
3824
4049
|
return job;
|
|
3825
4050
|
}
|
|
3826
|
-
async fetchData(type,
|
|
4051
|
+
async fetchData(type, path6, data) {
|
|
3827
4052
|
const config = {
|
|
3828
4053
|
headers: {},
|
|
3829
4054
|
httpAgent: void 0,
|
|
@@ -3833,8 +4058,8 @@ var OpenDotaAPI = class extends import_koishi15.Service {
|
|
|
3833
4058
|
config.httpsAgent = new import_https_proxy_agent3.HttpsProxyAgent(this.config.proxyAddress);
|
|
3834
4059
|
config.httpAgent = new import_https_proxy_agent3.HttpsProxyAgent(this.config.proxyAddress);
|
|
3835
4060
|
} else if (this.config.OpenDotaIPStack === "ipv4") {
|
|
3836
|
-
config.httpAgent = new
|
|
3837
|
-
config.httpsAgent = new
|
|
4061
|
+
config.httpAgent = new import_node_http.Agent({ family: 4 });
|
|
4062
|
+
config.httpsAgent = new import_node_https.Agent({ family: 4 });
|
|
3838
4063
|
}
|
|
3839
4064
|
if (this.config.OPENDOTA_API_KEY) {
|
|
3840
4065
|
config.headers["Authorization"] = `Bearer ${this.config.OPENDOTA_API_KEY}`;
|
|
@@ -3842,20 +4067,20 @@ var OpenDotaAPI = class extends import_koishi15.Service {
|
|
|
3842
4067
|
try {
|
|
3843
4068
|
let response;
|
|
3844
4069
|
if (type === "GET") {
|
|
3845
|
-
response = await this.http.get(
|
|
4070
|
+
response = await this.http.get(path6, config);
|
|
3846
4071
|
} else {
|
|
3847
|
-
response = await this.http.post(
|
|
4072
|
+
response = await this.http.post(path6, data, config);
|
|
3848
4073
|
}
|
|
3849
4074
|
return response.data;
|
|
3850
4075
|
} catch (error) {
|
|
3851
|
-
processFetchError(error, "OpenDota",
|
|
4076
|
+
processFetchError(error, "OpenDota", path6);
|
|
3852
4077
|
}
|
|
3853
4078
|
}
|
|
3854
4079
|
};
|
|
3855
4080
|
|
|
3856
4081
|
// src/app/core/opendota.adapter.ts
|
|
3857
|
-
var
|
|
3858
|
-
var OpenDotaAdapter = class extends
|
|
4082
|
+
var import_koishi17 = require("koishi");
|
|
4083
|
+
var OpenDotaAdapter = class extends import_koishi17.Service {
|
|
3859
4084
|
static {
|
|
3860
4085
|
__name(this, "OpenDotaAdapter");
|
|
3861
4086
|
}
|
|
@@ -4311,8 +4536,8 @@ function convertBuildingEvents(objectives) {
|
|
|
4311
4536
|
__name(convertBuildingEvents, "convertBuildingEvents");
|
|
4312
4537
|
|
|
4313
4538
|
// src/app/core/report.service.ts
|
|
4314
|
-
var
|
|
4315
|
-
var ReportService = class extends
|
|
4539
|
+
var import_koishi18 = require("koishi");
|
|
4540
|
+
var ReportService = class extends import_koishi18.Service {
|
|
4316
4541
|
static {
|
|
4317
4542
|
__name(this, "ReportService");
|
|
4318
4543
|
}
|
|
@@ -4338,9 +4563,9 @@ var ReportService = class extends import_koishi17.Service {
|
|
|
4338
4563
|
};
|
|
4339
4564
|
|
|
4340
4565
|
// src/config.ts
|
|
4341
|
-
var
|
|
4342
|
-
var
|
|
4343
|
-
var
|
|
4566
|
+
var import_koishi19 = require("koishi");
|
|
4567
|
+
var import_node_fs4 = __toESM(require("node:fs"));
|
|
4568
|
+
var import_node_path4 = __toESM(require("node:path"));
|
|
4344
4569
|
|
|
4345
4570
|
// require("./locales/**/*.schema.yml") in src/config.ts
|
|
4346
4571
|
var globRequire_locales_schema_yml = __glob({
|
|
@@ -4349,81 +4574,88 @@ var globRequire_locales_schema_yml = __glob({
|
|
|
4349
4574
|
});
|
|
4350
4575
|
|
|
4351
4576
|
// src/config.ts
|
|
4352
|
-
var templateDir =
|
|
4577
|
+
var templateDir = import_node_path4.default.join(__dirname, "templates");
|
|
4353
4578
|
var allI18nConfigs = Object.fromEntries(Object.keys(LanguageTags).map((lang) => [lang, globRequire_locales_schema_yml(`./locales/${lang}.schema.yml`)._config]));
|
|
4354
|
-
var Config =
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
STRATZ_API_TOKEN:
|
|
4358
|
-
dataParsingTimeoutMinutes:
|
|
4359
|
-
proxyAddress:
|
|
4360
|
-
suppressApiNetworkErrors:
|
|
4361
|
-
enableOpenDotaFallback:
|
|
4579
|
+
var Config = import_koishi19.Schema.intersect([
|
|
4580
|
+
import_koishi19.Schema.intersect([
|
|
4581
|
+
import_koishi19.Schema.object({
|
|
4582
|
+
STRATZ_API_TOKEN: import_koishi19.Schema.string().required().role("secret"),
|
|
4583
|
+
dataParsingTimeoutMinutes: import_koishi19.Schema.number().default(60).min(0).max(1440),
|
|
4584
|
+
proxyAddress: import_koishi19.Schema.string(),
|
|
4585
|
+
suppressApiNetworkErrors: import_koishi19.Schema.boolean().default(false),
|
|
4586
|
+
enableOpenDotaFallback: import_koishi19.Schema.boolean().default(false)
|
|
4362
4587
|
}).i18n(getI18n("base")),
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
enableOpenDotaFallback:
|
|
4366
|
-
OPENDOTA_API_KEY:
|
|
4367
|
-
OpenDotaIPStack:
|
|
4588
|
+
import_koishi19.Schema.union([
|
|
4589
|
+
import_koishi19.Schema.object({
|
|
4590
|
+
enableOpenDotaFallback: import_koishi19.Schema.const(true).required(),
|
|
4591
|
+
OPENDOTA_API_KEY: import_koishi19.Schema.string().role("secret"),
|
|
4592
|
+
OpenDotaIPStack: import_koishi19.Schema.union(["auto", "ipv4"]).default("auto")
|
|
4368
4593
|
}),
|
|
4369
|
-
|
|
4594
|
+
import_koishi19.Schema.object({})
|
|
4370
4595
|
]).i18n(getI18n("base"))
|
|
4371
4596
|
]),
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
useHeroNicknames:
|
|
4375
|
-
urlInMessageType:
|
|
4376
|
-
maxSendItemCount:
|
|
4377
|
-
showItemListAtTooMuchItems:
|
|
4378
|
-
customItemAlias:
|
|
4379
|
-
|
|
4380
|
-
keyword:
|
|
4381
|
-
alias:
|
|
4597
|
+
import_koishi19.Schema.intersect([
|
|
4598
|
+
import_koishi19.Schema.object({
|
|
4599
|
+
useHeroNicknames: import_koishi19.Schema.boolean().default(true),
|
|
4600
|
+
urlInMessageType: import_koishi19.Schema.array(import_koishi19.Schema.union([import_koishi19.Schema.const("match"), import_koishi19.Schema.const("player"), import_koishi19.Schema.const("hero")])).role("checkbox"),
|
|
4601
|
+
maxSendItemCount: import_koishi19.Schema.number().default(5).min(1).max(10),
|
|
4602
|
+
showItemListAtTooMuchItems: import_koishi19.Schema.boolean().default(true),
|
|
4603
|
+
customItemAlias: import_koishi19.Schema.array(
|
|
4604
|
+
import_koishi19.Schema.object({
|
|
4605
|
+
keyword: import_koishi19.Schema.string().required(),
|
|
4606
|
+
alias: import_koishi19.Schema.string().required()
|
|
4382
4607
|
})
|
|
4383
4608
|
).default([]).role("table"),
|
|
4384
|
-
autoRecallTips:
|
|
4385
|
-
rankBroadSwitch:
|
|
4609
|
+
autoRecallTips: import_koishi19.Schema.boolean().default(true),
|
|
4610
|
+
rankBroadSwitch: import_koishi19.Schema.boolean().default(false)
|
|
4386
4611
|
}).i18n(getI18n("message")),
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
rankBroadSwitch:
|
|
4390
|
-
rankBroadStar:
|
|
4391
|
-
rankBroadLeader:
|
|
4392
|
-
rankBroadFun:
|
|
4612
|
+
import_koishi19.Schema.union([
|
|
4613
|
+
import_koishi19.Schema.object({
|
|
4614
|
+
rankBroadSwitch: import_koishi19.Schema.const(true).required(),
|
|
4615
|
+
rankBroadStar: import_koishi19.Schema.boolean().default(true),
|
|
4616
|
+
rankBroadLeader: import_koishi19.Schema.boolean().default(true),
|
|
4617
|
+
rankBroadFun: import_koishi19.Schema.boolean().default(false)
|
|
4393
4618
|
}),
|
|
4394
|
-
|
|
4619
|
+
import_koishi19.Schema.object({})
|
|
4395
4620
|
]).i18n(getI18n("message"))
|
|
4396
4621
|
]),
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
dailyReportSwitch:
|
|
4622
|
+
import_koishi19.Schema.intersect([
|
|
4623
|
+
import_koishi19.Schema.object({
|
|
4624
|
+
dailyReportSwitch: import_koishi19.Schema.boolean().default(false)
|
|
4400
4625
|
}).i18n(getI18n("report")),
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
dailyReportSwitch:
|
|
4404
|
-
dailyReportHours:
|
|
4405
|
-
dailyReportShowCombi:
|
|
4626
|
+
import_koishi19.Schema.union([
|
|
4627
|
+
import_koishi19.Schema.object({
|
|
4628
|
+
dailyReportSwitch: import_koishi19.Schema.const(true).required(),
|
|
4629
|
+
dailyReportHours: import_koishi19.Schema.number().min(0).max(23).default(6),
|
|
4630
|
+
dailyReportShowCombi: import_koishi19.Schema.boolean().default(true)
|
|
4406
4631
|
}),
|
|
4407
|
-
|
|
4632
|
+
import_koishi19.Schema.object({})
|
|
4408
4633
|
]).i18n(getI18n("report")),
|
|
4409
|
-
|
|
4410
|
-
weeklyReportSwitch:
|
|
4634
|
+
import_koishi19.Schema.object({
|
|
4635
|
+
weeklyReportSwitch: import_koishi19.Schema.boolean().default(false)
|
|
4411
4636
|
}).i18n(getI18n("report")).description(void 0),
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
weeklyReportSwitch:
|
|
4415
|
-
weeklyReportDayHours:
|
|
4416
|
-
weeklyReportShowCombi:
|
|
4637
|
+
import_koishi19.Schema.union([
|
|
4638
|
+
import_koishi19.Schema.object({
|
|
4639
|
+
weeklyReportSwitch: import_koishi19.Schema.const(true).required(),
|
|
4640
|
+
weeklyReportDayHours: import_koishi19.Schema.tuple([import_koishi19.Schema.number().min(1).max(7), import_koishi19.Schema.number().min(0).max(23)]).default([1, 10]),
|
|
4641
|
+
weeklyReportShowCombi: import_koishi19.Schema.boolean().default(true)
|
|
4417
4642
|
}),
|
|
4418
|
-
|
|
4643
|
+
import_koishi19.Schema.object({})
|
|
4419
4644
|
]).i18n(getI18n("report"))
|
|
4420
4645
|
]),
|
|
4421
|
-
|
|
4422
|
-
template_match:
|
|
4423
|
-
template_player:
|
|
4424
|
-
template_hero:
|
|
4425
|
-
playerRankEstimate:
|
|
4426
|
-
templateFonts:
|
|
4646
|
+
import_koishi19.Schema.object({
|
|
4647
|
+
template_match: import_koishi19.Schema.union([...readDirectoryFilesSync(import_node_path4.default.join(templateDir, "match"))]).default("match_1"),
|
|
4648
|
+
template_player: import_koishi19.Schema.union([...readDirectoryFilesSync(import_node_path4.default.join(templateDir, "player"))]).default("player_1"),
|
|
4649
|
+
template_hero: import_koishi19.Schema.union([...readDirectoryFilesSync(import_node_path4.default.join(templateDir, "hero"))]).default("hero_1"),
|
|
4650
|
+
playerRankEstimate: import_koishi19.Schema.boolean().default(true),
|
|
4651
|
+
templateFonts: import_koishi19.Schema.array(String).default([]).role("table").deprecated(),
|
|
4652
|
+
fontPath: import_koishi19.Schema.path({ filters: ["directory"] }).default("data/fonts/dota2tracker"),
|
|
4653
|
+
fonts: import_koishi19.Schema.object({
|
|
4654
|
+
description: import_koishi19.Schema.never(),
|
|
4655
|
+
sans: import_koishi19.Schema.array(import_koishi19.Schema.dynamic("dota2tracker.fonts")).collapse(),
|
|
4656
|
+
serif: import_koishi19.Schema.array(import_koishi19.Schema.dynamic("dota2tracker.fonts")).collapse(),
|
|
4657
|
+
mono: import_koishi19.Schema.array(import_koishi19.Schema.dynamic("dota2tracker.fonts")).collapse()
|
|
4658
|
+
})
|
|
4427
4659
|
}).i18n(getI18n("template"))
|
|
4428
4660
|
]);
|
|
4429
4661
|
function getI18n(key) {
|
|
@@ -4438,8 +4670,8 @@ function getI18n(key) {
|
|
|
4438
4670
|
__name(getI18n, "getI18n");
|
|
4439
4671
|
function readDirectoryFilesSync(directoryPath) {
|
|
4440
4672
|
try {
|
|
4441
|
-
const files =
|
|
4442
|
-
const fileNames = files.filter((file) =>
|
|
4673
|
+
const files = import_node_fs4.default.readdirSync(directoryPath);
|
|
4674
|
+
const fileNames = files.filter((file) => import_node_path4.default.extname(file).toLowerCase() === ".ejs").map((file) => import_node_path4.default.basename(file, ".ejs"));
|
|
4443
4675
|
return fileNames;
|
|
4444
4676
|
} catch (error) {
|
|
4445
4677
|
console.error("Error reading directory:", error);
|
|
@@ -4453,14 +4685,14 @@ var name = "dota2tracker";
|
|
|
4453
4685
|
var usage = "";
|
|
4454
4686
|
var inject = {
|
|
4455
4687
|
required: ["database", "puppeteer", "cache"],
|
|
4456
|
-
optional: ["cron"
|
|
4688
|
+
optional: ["cron"]
|
|
4457
4689
|
};
|
|
4458
4690
|
async function apply(ctx, config) {
|
|
4459
4691
|
const lib = await import("dotaconstants");
|
|
4460
4692
|
const dotaconstants = lib.default || lib;
|
|
4461
4693
|
const logger = ctx.logger("dota2tracker");
|
|
4462
|
-
const currentDir =
|
|
4463
|
-
const pluginVersion = require(
|
|
4694
|
+
const currentDir = import_node_path5.default.resolve(__dirname);
|
|
4695
|
+
const pluginVersion = require(import_node_path5.default.join(currentDir, "..", "package.json")).version;
|
|
4464
4696
|
ctx.dota2tracker = {};
|
|
4465
4697
|
ctx.dota2tracker.dotaconstants = dotaconstants;
|
|
4466
4698
|
ctx.dota2tracker.i18n = new I18NService(ctx);
|
|
@@ -4490,6 +4722,7 @@ async function apply(ctx, config) {
|
|
|
4490
4722
|
ctx.dota2tracker.opendotaAPI = new OpenDotaAPI(ctx);
|
|
4491
4723
|
ctx.dota2tracker.opendotaAdapter = new OpenDotaAdapter(ctx);
|
|
4492
4724
|
}
|
|
4725
|
+
ctx.dota2tracker.font = new FontService(ctx);
|
|
4493
4726
|
ctx.dota2tracker = ctx.dota2tracker;
|
|
4494
4727
|
usage = await ctx.dota2tracker.i18n.generateUsage();
|
|
4495
4728
|
registerHelpCommand(ctx);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<%
|
|
2
|
+
/**
|
|
3
|
+
* 智能解析称号徽章
|
|
4
|
+
*/
|
|
5
|
+
parseBadge = function(key, $t) {
|
|
6
|
+
// 1. 获取翻译内容 "富-#FFD700"
|
|
7
|
+
const rawTranslation = $t(key);
|
|
8
|
+
|
|
9
|
+
// 2. 分割颜色
|
|
10
|
+
const parts = rawTranslation.split('-#');
|
|
11
|
+
const text = parts[0];
|
|
12
|
+
const color = parts.length > 1 ? '#' + parts[1] : '#9CA3AF';
|
|
13
|
+
|
|
14
|
+
// 3. 提取 Key 后缀
|
|
15
|
+
const keySuffix = key.split('.').pop() || '';
|
|
16
|
+
|
|
17
|
+
// 4. 白名单 (核心称号不缩写)
|
|
18
|
+
const FORCE_FULL = ['MVP', 'Soul'];
|
|
19
|
+
|
|
20
|
+
// 5. 生成短文本 (战报逻辑)
|
|
21
|
+
let shortText = text;
|
|
22
|
+
|
|
23
|
+
// 逻辑:不是核心称号 && 纯英文 && 长度 > 3 -> 缩写
|
|
24
|
+
if (!FORCE_FULL.includes(keySuffix) &&
|
|
25
|
+
/^[A-Za-z0-9]+$/.test(text) &&
|
|
26
|
+
text.length > 3) {
|
|
27
|
+
shortText = text.charAt(0).toUpperCase();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
fullText: text,
|
|
32
|
+
shortText: shortText,
|
|
33
|
+
color: color,
|
|
34
|
+
style: `color: ${color}; border-color: ${color};`
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
%>
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
// 预先计算是否需要显示冷却/耗蓝 (非空 且 不全为0)
|
|
13
13
|
const hasCd = cdStats?.values_float.length && !(cdStats.values_float.length === 1 && cdStats.values_float[0] === 0);
|
|
14
14
|
const hasMana = manaStats?.values_float.length && !(manaStats.values_float.length === 1 && manaStats.values_float[0] === 0);
|
|
15
|
-
%> <div class="skill<%= item.facet ? ' facet' : '' %>" data-innate="<%= item.ability_is_innate && !item.ability_is_facet %>"><p class="title<%= item.facet ? (' name_back type_' + item.facet?.color) : '' %>"> <% if (item.facet) { %> <img src="<%= getImageUrl(item.facet?.icon, ImageType.IconsFacets) %>"> <% } %> <span class="name"><%= item.name_loc %></span> <% if (item.ability_is_innate && !item.ability_is_facet) { %> <span class="is_innate"><%= $t("dota2tracker.template.innate") %></span> <% } %> <% if (item.ability_is_granted_by_scepter) { %> <img src="<%= getImageUrl("scepter") %>" class="scepter"> <% } %> <% if (item.ability_is_granted_by_shard) { %> <img src="<%= getImageUrl("shard") %>" class="shard"> <% } %> </p><div class="img_stats"><img src="<%= getImageUrl(item.name, ImageType.Abilities) %>" onerror="this.onerror=null; this.src='<%= getImageUrl(`innate_icon`, ImageType.Icons) %>';"/><div class="stats"><p class="behavior"> <%= $t("dota2tracker.template.ability") %> <%= [].concat(ability_dc?.behavior)
|
|
15
|
+
%> <div class="skill<%= item.facet ? ' facet' : '' %>" data-innate="<%= item.ability_is_innate && !item.ability_is_facet %>"><p class="title<%= item.facet ? (' name_back type_' + item.facet?.color) : '' %>"> <% if (item.facet) { %> <img src="<%= getImageUrl(item.facet?.icon, ImageType.IconsFacets) %>"> <% } %> <span class="name font-serif"><%= item.name_loc %></span> <% if (item.ability_is_innate && !item.ability_is_facet) { %> <span class="is_innate"><%= $t("dota2tracker.template.innate") %></span> <% } %> <% if (item.ability_is_granted_by_scepter) { %> <img src="<%= getImageUrl("scepter") %>" class="scepter"> <% } %> <% if (item.ability_is_granted_by_shard) { %> <img src="<%= getImageUrl("shard") %>" class="shard"> <% } %> </p><div class="img_stats"><img src="<%= getImageUrl(item.name, ImageType.Abilities) %>" onerror="this.onerror=null; this.src='<%= getImageUrl(`innate_icon`, ImageType.Icons) %>';"/><div class="stats"><p class="behavior"> <%= $t("dota2tracker.template.ability") %> <%= [].concat(ability_dc?.behavior)
|
|
16
16
|
.filter((beh) => beh !== "Hidden" || !(item.ability_is_granted_by_shard || item.ability_is_granted_by_scepter))
|
|
17
17
|
.map((beh) => $t("dota2tracker.template.behavior." + beh)).join("/") %> </p> <% if (ability_dc?.target_team) { %> <p class="target_team"> <%= $t("dota2tracker.template.affects") %> <%= [].concat(ability_dc?.target_team).map((tt) => $t("dota2tracker.template.target_team." + tt)).join("/") %> </p> <% } %> <% if (!Array.isArray(ability_dc?.dmg_type) && ability_dc?.dmg_type) { %> <p class="dmg_type <%= ability_dc?.dmg_type %>"> <%= $t("dota2tracker.template.damage_type") %> <span><%= $t("dota2tracker.template.damage_type_" + ability_dc?.dmg_type) %></span></p> <% } %> <% if (ability_dc?.dispellable) { %> <p class="dispellable <%= ability_dc?.dispellable == 'Strong Dispels Only' ? 'Strong' : ability_dc?.dispellable %>"> <%= $t("dota2tracker.template.dispellable") %> <span><%= $t("dota2tracker.template." + (ability_dc?.dispellable == "Strong Dispels Only" ? "dispellable_Strong" : ability_dc?.dispellable)) %></span></p> <% } %> <% if (!Array.isArray(ability_dc?.bkbpierce) && ability_dc?.bkbpierce) { %> <p class="bkbpierce <%= ability_dc?.bkbpierce %>"> <%= $t("dota2tracker.template.bkbpierce") %> <span><%= $t("dota2tracker.template." + ability_dc?.bkbpierce) %></span></p> <% } %> </div></div><p class="description"><%- item.desc_loc %></p> <%
|
|
18
18
|
const facetStartIndex = hero.facets.length - item.facets_loc.length;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
<% const p = player; %> <% const isLD = p.hero.id == 80; %> <% const gridCol = !p.isRadiant + 1; %> <% const gridRow = (p.position ? parseInt(p.position.slice(-1)) : 0) % 5; %> <% const gridStyle = `grid-column: ${gridCol}; grid-row: ${gridRow}`; %> <% const pColor = partyColor[p.partyId]; %> <% const partyLineStyle = `background-color: ${pColor};`; %> <% const partyMarkStyle = `color: ${pColor};`; %> <% const orderColors = ['red', 'yellow', 'green']; %> <% const orderColor = orderColors[Math.floor(p.order / 4)] || 'white'; %> <% const kcVal = Math.floor(p.killContribution * 100); %> <% const dcVal = Math.floor(p.deathContribution * 100); %> <% const kcStyle = `color: ${utils.kc(kcVal)}`; %> <% const dcStyle = `color: ${utils.dc(dcVal)}`; %> <% const facetGradient = facetColor[p.facet?.color ?? 'Black']; %> <% const facetIcon = p.facet?.icon; %> <% const facetName = p.facet?.displayName ?? "?"; %> <% const damageReport = p.stats?.heroDamageReport?.receivedTotal || {}; %> <% const dealtReport = p.stats?.heroDamageReport?.dealtTotal || {}; %> <% const damageReceived = (damageReport.physicalDamage || 0) + (damageReport.magicalDamage || 0) + (damageReport.pureDamage || 0); %> <% const ccStun = ((dealtReport.stunDuration || 0) / 100).toFixed(2); %> <% const ccSlow = ((dealtReport.slowDuration || 0) / 100).toFixed(2); %> <% const ccDisable = ((dealtReport.disableDuration || 0) / 100).toFixed(2); %> <div class="box h-[320px] flex flex-col gap-[4px] rounded-[5px]" <%- `style="${gridStyle}"` %>><section class="grid grid-cols-[112px_1fr_100px] grid-rows-[63px_24px]"><div class="avatar relative h-full relative row-1 col-1 text-[10px] text-shadow-[-1px_1px_2px_#000,1px_-1px_2px_#000,-1px_1px_2px_#000,1px_1px_2px_#000]"> <% if (p.leaverStatus != "NONE" && p.leaverStatus != "DISCONNECTED") { %> <img class="leaver absolute w-full h-full object-contain" src="<%= getImageUrl("disconnected") %>"/> <% } %> <div class="party contents"><i class="party_line absolute w-full h-[2px]" <%- `style="${partyLineStyle}"` %>></i> <b class="party_mark absolute leading-[1.5] w-[16px] top-[3px] left-[2px] bg-black/80 text-center" <%- `style="${partyMarkStyle}"` %>> <%= match.party[p.partyId] %> </b></div><div class="hero_info absolute top-[3px] right-0 text-right leading-[1.25]"><p class="order" <%- `style="color: ${orderColor};"` %>> <%= p.isRandom ? $t("dota2tracker.template.random") : $t("dota2tracker.template.pick_order", [`${p.order == null ? "?" : p.order + 1}`]) %> </p><p class="position"><%= $t("dota2tracker.template.position_" + p.position?.slice(-1)) ?? '' %></p></div><p class="level absolute bottom-0 right-0 text-xs px-[2px]"><%= p.level %></p><img class="h-full" src="<%= getImageUrl(p.hero.shortName, ImageType.Heroes) %>"/></div><div class="player_profile mx-[10px] row-1 col-2 w-auto h-full overflow-hidden text-center flex flex-col justify-around leading-[1]"><p class="player_name truncate max-w-full"><%= p.steamAccount.name %></p><p class="flex gap-[12px] justify-center"><span class="kda"><%= p.kills %>/<%= p.deaths %>/<%= p.assists %></span><span class="kc" <%- `style="${kcStyle}"` %>><%= kcVal %>%</span> <span class="dc" <%- `style="${dcStyle}"` %>><%= dcVal %>%</span></p><p class="flex gap-[16px] justify-center"><span class="networth text-[rgb(203,176,42)]"><%= p.networth %></span><span class="score"><%= (p.heroDamage / p.networth)?.toFixed(2) %></span></p></div><div class="lane_rank row-1 col-3 flex w-full h-full justify-between"><div class="lane w-[44px] h-full"><p class="text-[10px] text-center"><%= $t("dota2tracker.template.lane_" + p.laneResult) %></p><div class="h-[44px] w-[44px]"><%- utils.laneSVG[p.laneResult] %></div></div><div class="rank_plus w-[36px] h-full relative text-[10px] leading-[1.15] text-shadow-[-1px_1px_0_#000,1px_-1px_0_#000,-1px_1px_0_#000,1px_1px_0_#000]"><div class="rank absolute top-0 size-[36px] z-2"><img class="medal absolute inset-0" src="<%= getImageUrl('medal_' + (p.rank.inTop100 ?? p.rank.medal)) %>"/> <img class="stars absolute inset-0" src="<%= getImageUrl('star_' + p.rank.star) %>"/><p class="leader absolute bottom-0 text-center w-full"><%= p.steamAccount?.seasonLeaderboardRank ?? "" %></p></div> <% if (p.dotaPlus) { %> <div class="plus absolute bottom-0 size-[36px] z-1"><img class="" src="<%= getImageUrl("hero_badge_" + (p.dotaPlus ? Math.ceil((p.dotaPlus?.level + 1) / 6) : 1)) %>"><p class="level absolute bottom-0 text-center w-full"><%= p.dotaPlus?.level %></p></div> <% } %> </div></div><div class="facet row-2 col-1 w-full h-full grid grid-cols-[24px_1fr] grid-rows-1 bg-linear-to-r <%- facetGradient %>"> <% if (p.facet) { %> <div class="w-[24px] h-[24px] flex items-center justify-center bg-[#4444]"><img class="h-[18px]" src="<%= getImageUrl(facetIcon, ImageType.IconsFacets) %>"/></div> <% } %> <div class="facet_name w-full truncate grow px-[4px] text-sm flex justify-center items-center"><span class="w-full truncate text-center"><%= facetName %></span></div></div><div class="titles row-2 col-start-2 col-end-3 w-full flex items-center pl-[12px] gap-[6px]"> <% p.titles.forEach(item => { %> <% const
|
|
1
|
+
<% const p = player; %> <% const isLD = p.hero.id == 80; %> <% const gridCol = !p.isRadiant + 1; %> <% const gridRow = (p.position ? parseInt(p.position.slice(-1)) : 0) % 5; %> <% const gridStyle = `grid-column: ${gridCol}; grid-row: ${gridRow}`; %> <% const pColor = partyColor[p.partyId]; %> <% const partyLineStyle = `background-color: ${pColor};`; %> <% const partyMarkStyle = `color: ${pColor};`; %> <% const orderColors = ['red', 'yellow', 'green']; %> <% const orderColor = orderColors[Math.floor(p.order / 4)] || 'white'; %> <% const kcVal = Math.floor(p.killContribution * 100); %> <% const dcVal = Math.floor(p.deathContribution * 100); %> <% const kcStyle = `color: ${utils.kc(kcVal)}`; %> <% const dcStyle = `color: ${utils.dc(dcVal)}`; %> <% const facetGradient = facetColor[p.facet?.color ?? 'Black']; %> <% const facetIcon = p.facet?.icon; %> <% const facetName = p.facet?.displayName ?? "?"; %> <% const damageReport = p.stats?.heroDamageReport?.receivedTotal || {}; %> <% const dealtReport = p.stats?.heroDamageReport?.dealtTotal || {}; %> <% const damageReceived = (damageReport.physicalDamage || 0) + (damageReport.magicalDamage || 0) + (damageReport.pureDamage || 0); %> <% const ccStun = ((dealtReport.stunDuration || 0) / 100).toFixed(2); %> <% const ccSlow = ((dealtReport.slowDuration || 0) / 100).toFixed(2); %> <% const ccDisable = ((dealtReport.disableDuration || 0) / 100).toFixed(2); %> <div class="box h-[320px] flex flex-col gap-[4px] rounded-[5px]" <%- `style="${gridStyle}"` %>><section class="grid grid-cols-[112px_1fr_100px] grid-rows-[63px_24px]"><div class="avatar relative h-full relative row-1 col-1 text-[10px] text-shadow-[-1px_1px_2px_#000,1px_-1px_2px_#000,-1px_1px_2px_#000,1px_1px_2px_#000]"> <% if (p.leaverStatus != "NONE" && p.leaverStatus != "DISCONNECTED") { %> <img class="leaver absolute w-full h-full object-contain" src="<%= getImageUrl("disconnected") %>"/> <% } %> <div class="party contents"><i class="party_line absolute w-full h-[2px]" <%- `style="${partyLineStyle}"` %>></i> <b class="party_mark absolute leading-[1.5] w-[16px] top-[3px] left-[2px] bg-black/80 text-center" <%- `style="${partyMarkStyle}"` %>> <%= match.party[p.partyId] %> </b></div><div class="hero_info absolute top-[3px] right-0 text-right leading-[1.25]"><p class="order" <%- `style="color: ${orderColor};"` %>> <%= p.isRandom ? $t("dota2tracker.template.random") : $t("dota2tracker.template.pick_order", [`${p.order == null ? "?" : p.order + 1}`]) %> </p><p class="position"><%= $t("dota2tracker.template.position_" + p.position?.slice(-1)) ?? '' %></p></div><p class="level absolute bottom-0 right-0 text-xs px-[2px]"><%= p.level %></p><img class="h-full" src="<%= getImageUrl(p.hero.shortName, ImageType.Heroes) %>"/></div><div class="player_profile mx-[10px] row-1 col-2 w-auto h-full overflow-hidden text-center flex flex-col justify-around leading-[1]"><p class="player_name truncate max-w-full"><%= p.steamAccount.name %></p><p class="flex gap-[12px] justify-center"><span class="kda"><%= p.kills %>/<%= p.deaths %>/<%= p.assists %></span><span class="kc" <%- `style="${kcStyle}"` %>><%= kcVal %>%</span> <span class="dc" <%- `style="${dcStyle}"` %>><%= dcVal %>%</span></p><p class="flex gap-[16px] justify-center"><span class="networth text-[rgb(203,176,42)]"><%= p.networth %></span><span class="score"><%= (p.heroDamage / p.networth)?.toFixed(2) %></span></p></div><div class="lane_rank row-1 col-3 flex w-full h-full justify-between"><div class="lane w-[44px] h-full"><p class="text-[10px] text-center"><%= $t("dota2tracker.template.lane_" + p.laneResult) %></p><div class="h-[44px] w-[44px]"><%- utils.laneSVG[p.laneResult] %></div></div><div class="rank_plus w-[36px] h-full relative text-[10px] leading-[1.15] text-shadow-[-1px_1px_0_#000,1px_-1px_0_#000,-1px_1px_0_#000,1px_1px_0_#000]"><div class="rank absolute top-0 size-[36px] z-2"><img class="medal absolute inset-0" src="<%= getImageUrl('medal_' + (p.rank.inTop100 ?? p.rank.medal)) %>"/> <img class="stars absolute inset-0" src="<%= getImageUrl('star_' + p.rank.star) %>"/><p class="leader absolute bottom-0 text-center w-full"><%= p.steamAccount?.seasonLeaderboardRank ?? "" %></p></div> <% if (p.dotaPlus) { %> <div class="plus absolute bottom-0 size-[36px] z-1"><img class="" src="<%= getImageUrl("hero_badge_" + (p.dotaPlus ? Math.ceil((p.dotaPlus?.level + 1) / 6) : 1)) %>"><p class="level absolute bottom-0 text-center w-full"><%= p.dotaPlus?.level %></p></div> <% } %> </div></div><div class="facet row-2 col-1 w-full h-full grid grid-cols-[24px_1fr] grid-rows-1 bg-linear-to-r <%- facetGradient %>"> <% if (p.facet) { %> <div class="w-[24px] h-[24px] flex items-center justify-center bg-[#4444]"><img class="h-[18px]" src="<%= getImageUrl(facetIcon, ImageType.IconsFacets) %>"/></div> <% } %> <div class="facet_name w-full truncate grow px-[4px] text-sm flex justify-center items-center"><span class="w-full truncate text-center"><%= facetName %></span></div></div><div class="titles row-2 col-start-2 col-end-3 w-full flex items-center pl-[12px] gap-[6px]"> <% p.titles.forEach(item => { %> <% const title = parseBadge(item, $t); %> <span <%- `style="color: ${title.color};"` %>><%= title.shortText %></span> <% }); %> </div></section> <% if (!isLD) { %> <section class="items w-full grid grid-cols-[repeat(3,3fr)_2fr_3fr] grid-rows-2"><div class="items_main contents"> <% for(let i = 0; i < 6; i++) { %> <%- include("./item.ejs", {item: p.items[i], style: `col-${i%3+1}`, isNeutral: false}) %> <% } %> </div><div class="items_backpack grayscale col-4 row-start-1 row-span-2 flex flex-col"> <% for(let i = 0; i < 3; i++) { %> <%- include("./item.ejs", {item: p.backpacks[i], style: "", isNeutral: false}) %> <% } %> </div><div class="items_neutral contents"> <%- include("./item.ejs", {item: p.neutral0Id, style: "row-start-1 row-span-2 col-5 flex items-center h-full", isNeutral: true}) %> </div></section> <% } else { %> <section class="items w-full grid grid-cols-[repeat(6,3fr)_10fr] grid-rows-4"><div class="items_hero contents"><div class="items_main contents"> <% for(let i = 0; i < 6; i++) { %> <%- include("./item.ejs", {item: p.items[i], style: `row-1 col-${i+1}`, isNeutral: false}) %> <% } %> </div><div class="items_backpack grayscale contents"> <% for(let i = 0; i < 3; i++) { %> <%- include("./item.ejs", {item: p.backpacks[i], style: `row-2 col-${i+1}`, isNeutral: false}) %> <% } %> </div><div class="items_neutral contents"> <%- include("./item.ejs", {item: p.neutral0Id, style: "row-2 col-6", isNeutral: true}) %> </div></div><div class="items_unit contents"><div class="items_main contents"> <% for(let i = 0; i < 6; i++) { %> <%- include("./item.ejs", {item: p.unitItems[i], style: `row-3 col-${i+1}`, isNeutral: false}) %> <% } %> </div><div class="items_backpack grayscale contents"> <% for(let i = 0; i < 3; i++) { %> <%- include("./item.ejs", {item: p.unitBackpacks[i], style: `row-4 col-${i+1}`, isNeutral: false}) %> <% } %> </div><div class="items_neutral contents"> <%- include("./item.ejs", {item: p.additionalUnit.neutral0Id, style: "row-4 col-6", isNeutral: true}) %> </div></div></section> <% } %> <section class="buffs_supports w-full h-[24px] flex justify-between"><div class="buffs flex h-[24px]"> <% p.buffs.forEach(buff => { %> <div class="relative"><img class="w-[33px] h-[24px]" src="<%= getImageUrl(dotaconstants[`${buff.type}_ids`][buff.id], ImageType[buff.type === "ability" ? "Abilities" : "Items"]) %>"/><p class="count absolute w-full bottom-0 leading-[1] text-center bg-stone-700/50 text-[10px]"><%= buff.stackCount || "" %></p></div> <% }); %> </div><div class="supports flex h-[24px]"> <% p.supportItemsCount.forEach(supportItem => { %> <div class="relative"><img class="w-[33px] h-[24px]" src="<%= getImageUrl(supportItem.name, ImageType.Items) %>"/><p class="count absolute w-full bottom-0 leading-[1] text-center bg-stone-700/50 text-[10px]"><%= supportItem.count || "" %></p></div> <% }); %> </div></section><section class="player-stat h-full flex flex-col justify-around text-[13px] leading-[24px] whitespace-nowrap"><p class="w-full flex justify-around"><span><%= $t("dota2tracker.template.hero_damage_") %><span class="hero_damage"><%= p.heroDamage %></span></span><span><%= $t("dota2tracker.template.building_damage_") %><span class="building_damage"><%= p.towerDamage %></span></span><span><%= $t("dota2tracker.template.damage_received_") %><span class="tak"><%= damageReceived %></span></span></p><p class="w-full flex justify-around"><span><%= $t("dota2tracker.template.lasthit_") %><span class="lh"><%= p.numLastHits %></span>/<span class="dn"><%= p.numDenies %></span></span><span><%= $t("dota2tracker.template.GPM/XPM_") %><span class="gpm"><%= p.goldPerMinute %></span>/<span class="xpm"><%= p.experiencePerMinute %></span></span><span><%= $t("dota2tracker.template.heal_") %><span class="heal"><%= p.heroHealing %></span></span></p><p class="w-full flex justify-center"><span><%= $t("dota2tracker.template.crowd_control_duration_") %></span><span><%= ccStun + "/" %></span><span><%= ccSlow + "/" %></span><span><%= ccDisable %></span>s</p></section></div>
|
|
@@ -15,4 +15,4 @@
|
|
|
15
15
|
tie: getImageUrl("lane_tie", undefined, ImageFormat.svg),
|
|
16
16
|
jungle: getImageUrl("lane_jungle", undefined, ImageFormat.svg),
|
|
17
17
|
};
|
|
18
|
-
%> <!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>Match</title> <%- `<style>` %> <%- include(`./match_1/base.css`) %> <%- include(`./match_1/style.css`) %> <% if (fontFamily) { %><%- `body { font-family: ${fontFamily}; }` %><% } %> <%- `</style>` %> </head><body class="bg-black w-[800px] text-white"> <%- include(`./match_1/main.ejs`, { match, partyColor, facetColor, kc, dc, laneSVG }) %> </body></html>
|
|
18
|
+
%> <%- include("../common/utils/match") %> <!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>Match</title> <%- `<style>` %> <%- include(`./match_1/base.css`) %> <%- include(`./match_1/style.css`) %> <% if (fontFamily) { %><%- `body { font-family: ${fontFamily}; }` %><% } %> <%- `</style>` %> </head><body class="bg-black w-[800px] text-white"> <%- include(`./match_1/main.ejs`, { match, partyColor, facetColor, kc, dc, laneSVG }) %> </body></html>
|
|
@@ -7,4 +7,4 @@
|
|
|
7
7
|
const b = Math.max(0, (num & 0x0000FF) - Math.round(2.55 * amt));
|
|
8
8
|
return `#${(0x1000000 + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
|
|
9
9
|
};
|
|
10
|
-
%> <% const getFacetSize = n => { const l=n?.length||0; return l<=4?"11px":l===5?"8px":l>=6?"7px":"10px"; }; %> <% const startTime = DateTime.fromSeconds(match.startDateTime).toFormat("yyyy-MM-dd HH:mm:ss").slice(2); %> <% const regionName = $t("dota2tracker.template.regions." + match.regionId); %> <% const modeName = ($t("dota2tracker.template.lobby_types." + match.lobbyType) || match.lobbyType) + "/" + ($t("dota2tracker.template.game_modes." + match.gameMode) || match.gameMode); %> <% const statusClass = match.odParsed ? "opendota" : (match.parsedDateTime ? "success" : "fail"); %> <% const rankStyle = (match.odParsed && match.lobbyType !== "RANKED") ? "filter:grayscale(1)" : ""; %> <nav><div class="match_id"><p><%= $t("dota2tracker.template.match_id_").slice(0, -1) %> <%- match.id %></p><p class="<%= statusClass %>"></p></div><div class="start_time"><p><%= $t("dota2tracker.template.start_time_").slice(0, -1) %></p><p><%- startTime %></p></div><div class="duration"><p><%= $t("dota2tracker.template.duration_").slice(0, -1) %></p><p><%- match.durationTime %></p></div><div class="region"><p><%= $t("dota2tracker.template.region_").slice(0, -1) %></p><p><%- regionName %></p></div><div class="mode"><p><%= $t("dota2tracker.template.game_mode_").slice(0, -1) %></p><p><%- modeName %></p></div><div class="rank" <%- `style="${rankStyle}"` %>><img src="<%- getImageUrl('medal_' + (match.rank?.toString().split('')[0] ?? '0')) %>" alt=""/> <img style="z-index:1" src="<%- getImageUrl('star_' + (match.rank?.toString().split('')[1] ?? '0')) %>" alt=""/></div></nav><section class="match_result"><span class="kills radiant"><%- match.radiant.killsCount %></span><span class="win <%- match.didRadiantWin ? 'radiant' : 'dire' %>"></span> <span class="kills dire"><%- match.dire.killsCount %></span></section><section class="players"> <% ['radiant', 'dire'].forEach(team => { %> <% const isRadiant = team === 'radiant'; %> <% const teamData = match[team]; %> <% const orderStyle = `order: ${isRadiant ? 0 : 50}`; %> <% const teamName = isRadiant ? ['Radiant', '天辉'] : ['Dire', '夜魇']; %> <% const showWin = (isRadiant === match.didRadiantWin); %> <section class="panel <%= team %>" <%- `style="${orderStyle}"` %>><img src="<%- getImageUrl('logo_' + team) %>"><p><%- teamName.join('<br>') %></p> <% if (showWin) { %> <p class="win"><%= $t('dota2tracker.template.won') %></p> <% } else { %> <p></p> <% } %> <p class="data"><%= $t('dota2tracker.template.kill') %><br><%= teamData.killsCount %></p><p class="data"><%= $t('dota2tracker.template.total_damage') %><br><%= teamData.heroDamage %></p><p class="data"><%= $t('dota2tracker.template.total_gold') %><br><%= teamData.networth %></p><p class="data"><%= $t('dota2tracker.template.total_experience') %><br><%= teamData.experience %></p></section> <% }); %> <% match.players.forEach(p => { %> <% const isBear = p.hero.id == 80; %> <% const orderStyle = `order: ${p.team === 'radiant' ? 1 : 100}`; %> <% const partyClass = p.partyId != null ? ' party_' + match.party[p.partyId] : ''; %> <% const facetColor = p.facet?.color ?? 'Black'; %> <% const facetStyle = `font-size: ${getFacetSize(p.facet?.displayName)}`; %> <% const facetName = p.facet?.displayName ?? '?'; %> <% const teamTotalDmg = match[p.team].heroDamage || 1; %> <% const teamTotalTaken = match[p.team].damageReceived || 1; %> <% const dmgPct = (p.heroDamage / teamTotalDmg * 100).toFixed(2); %> <% const takenPct = (p.damageReceived / teamTotalTaken * 100).toFixed(2); %> <% const kdaVal = ((p.kills + p.assists) / (p.deaths || 1)).toFixed(2); %> <% const kcVal = (p.killContribution * 100).toFixed(2); %> <% const stunVal = ((p.stats.heroDamageReport?.dealtTotal.stunDuration ?? 0) / 100).toFixed(2); %> <% const pBuffs = p.stats?.matchPlayerBuffEvent || []; %> <% const hasAghs = p.items.some(i => i?.id == 108) || p.backpacks.some(i => i?.id == 108) || pBuffs.some(b => b.itemId == 108); %> <% const hasShard = pBuffs.some(b => b.itemId == 609); %> <div class="player <%= p.team %><%= isBear ? ' bear' : '' %>" <%- `style="${orderStyle}"` %>><div class="hero_avatar row-1<%= partyClass %>"><img src="<%- getImageUrl(p.hero.shortName, ImageType.Heroes) %>"/><p class="level"><%= p.level %></p><p class="party_line"></p><p class="party_mark"></p></div><div class="facet <%= facetColor %>"> <% if (p.facet) { %> <img src="<%- getImageUrl(p.facet.icon, ImageType.IconsFacets) %>"> <% } %> <span <%- `style="${facetStyle}"` %>><p><%= facetName %></p></span></div><div class="rank"><img src="<%- getImageUrl('medal_' + (p.rank.inTop100 ?? p.rank.medal)) %>" class="medal"/> <img src="<%- getImageUrl('star_' + p.rank.star) %>" class="stars"/><p><%= p.steamAccount.seasonLeaderboardRank ?? '' %></p></div><div class="titles"> <% p.titles.forEach(item => { %> <% const
|
|
10
|
+
%> <% const getFacetSize = n => { const l=n?.length||0; return l<=4?"11px":l===5?"8px":l>=6?"7px":"10px"; }; %> <% const startTime = DateTime.fromSeconds(match.startDateTime).toFormat("yyyy-MM-dd HH:mm:ss").slice(2); %> <% const regionName = $t("dota2tracker.template.regions." + match.regionId); %> <% const modeName = ($t("dota2tracker.template.lobby_types." + match.lobbyType) || match.lobbyType) + "/" + ($t("dota2tracker.template.game_modes." + match.gameMode) || match.gameMode); %> <% const statusClass = match.odParsed ? "opendota" : (match.parsedDateTime ? "success" : "fail"); %> <% const rankStyle = (match.odParsed && match.lobbyType !== "RANKED") ? "filter:grayscale(1)" : ""; %> <%- include("../../common/utils/match") %> <nav><div class="match_id"><p><%= $t("dota2tracker.template.match_id_").slice(0, -1) %> <%- match.id %></p><p class="<%= statusClass %>"></p></div><div class="start_time"><p><%= $t("dota2tracker.template.start_time_").slice(0, -1) %></p><p><%- startTime %></p></div><div class="duration"><p><%= $t("dota2tracker.template.duration_").slice(0, -1) %></p><p><%- match.durationTime %></p></div><div class="region"><p><%= $t("dota2tracker.template.region_").slice(0, -1) %></p><p><%- regionName %></p></div><div class="mode"><p><%= $t("dota2tracker.template.game_mode_").slice(0, -1) %></p><p><%- modeName %></p></div><div class="rank" <%- `style="${rankStyle}"` %>><img src="<%- getImageUrl('medal_' + (match.rank?.toString().split('')[0] ?? '0')) %>" alt=""/> <img style="z-index:1" src="<%- getImageUrl('star_' + (match.rank?.toString().split('')[1] ?? '0')) %>" alt=""/></div></nav><section class="match_result"><span class="kills radiant"><%- match.radiant.killsCount %></span><span class="win <%- match.didRadiantWin ? 'radiant' : 'dire' %>"></span> <span class="kills dire"><%- match.dire.killsCount %></span></section><section class="players"> <% ['radiant', 'dire'].forEach(team => { %> <% const isRadiant = team === 'radiant'; %> <% const teamData = match[team]; %> <% const orderStyle = `order: ${isRadiant ? 0 : 50}`; %> <% const teamName = isRadiant ? ['Radiant', '天辉'] : ['Dire', '夜魇']; %> <% const showWin = (isRadiant === match.didRadiantWin); %> <section class="panel <%= team %>" <%- `style="${orderStyle}"` %>><img src="<%- getImageUrl('logo_' + team) %>"><p><%- teamName.join('<br>') %></p> <% if (showWin) { %> <p class="win"><%= $t('dota2tracker.template.won') %></p> <% } else { %> <p></p> <% } %> <p class="data"><%= $t('dota2tracker.template.kill') %><br><%= teamData.killsCount %></p><p class="data"><%= $t('dota2tracker.template.total_damage') %><br><%= teamData.heroDamage %></p><p class="data"><%= $t('dota2tracker.template.total_gold') %><br><%= teamData.networth %></p><p class="data"><%= $t('dota2tracker.template.total_experience') %><br><%= teamData.experience %></p></section> <% }); %> <% match.players.forEach(p => { %> <% const isBear = p.hero.id == 80; %> <% const orderStyle = `order: ${p.team === 'radiant' ? 1 : 100}`; %> <% const partyClass = p.partyId != null ? ' party_' + match.party[p.partyId] : ''; %> <% const facetColor = p.facet?.color ?? 'Black'; %> <% const facetStyle = `font-size: ${getFacetSize(p.facet?.displayName)}`; %> <% const facetName = p.facet?.displayName ?? '?'; %> <% const teamTotalDmg = match[p.team].heroDamage || 1; %> <% const teamTotalTaken = match[p.team].damageReceived || 1; %> <% const dmgPct = (p.heroDamage / teamTotalDmg * 100).toFixed(2); %> <% const takenPct = (p.damageReceived / teamTotalTaken * 100).toFixed(2); %> <% const kdaVal = ((p.kills + p.assists) / (p.deaths || 1)).toFixed(2); %> <% const kcVal = (p.killContribution * 100).toFixed(2); %> <% const stunVal = ((p.stats.heroDamageReport?.dealtTotal.stunDuration ?? 0) / 100).toFixed(2); %> <% const pBuffs = p.stats?.matchPlayerBuffEvent || []; %> <% const hasAghs = p.items.some(i => i?.id == 108) || p.backpacks.some(i => i?.id == 108) || pBuffs.some(b => b.itemId == 108); %> <% const hasShard = pBuffs.some(b => b.itemId == 609); %> <div class="player <%= p.team %><%= isBear ? ' bear' : '' %>" <%- `style="${orderStyle}"` %>><div class="hero_avatar row-1<%= partyClass %>"><img src="<%- getImageUrl(p.hero.shortName, ImageType.Heroes) %>"/><p class="level"><%= p.level %></p><p class="party_line"></p><p class="party_mark"></p></div><div class="facet <%= facetColor %>"> <% if (p.facet) { %> <img src="<%- getImageUrl(p.facet.icon, ImageType.IconsFacets) %>"> <% } %> <span <%- `style="${facetStyle}"` %>><p><%= facetName %></p></span></div><div class="rank"><img src="<%- getImageUrl('medal_' + (p.rank.inTop100 ?? p.rank.medal)) %>" class="medal"/> <img src="<%- getImageUrl('star_' + p.rank.star) %>" class="stars"/><p><%= p.steamAccount.seasonLeaderboardRank ?? '' %></p></div><div class="titles"> <% p.titles.forEach(item => { %> <% const title = parseBadge(item, $t); %> <span <%- `style="color: ${darken(title.color, 25)};"` %>><%= title.shortText %></span> <% }); %> </div><div class="player_name row-1"><span class="rank">[<%= $t('dota2tracker.template.ranks.' + p.rank.medal) %><%= p.rank.star || '' %>]</span> <span class="name"><%= p.steamAccount.name %></span></div><p class="pick"> <%- p.isRandom ? $t('dota2tracker.template.random') : $t('dota2tracker.template.pick_order', [p.order == null ? '?' : p.order + 1]) %> <%= $t('dota2tracker.template.position_' + p.position?.slice(-1)) ?? '' %> </p><p class="networth"><span class="gold"><%= p.formattedNetworth %></span>(<%= (p.heroDamage / p.networth)?.toFixed(2) %>)</p><p class="hero_damage"> <%= $t('dota2tracker.template.hero_damage_') %><%= p.heroDamage %> (<%= dmgPct %>%)</p><p class="damage_received"> <%= $t('dota2tracker.template.damage_received_') %><%= p.damageReceived %> (<%= takenPct %>%)</p><p class="tower_damage"> <%= $t('dota2tracker.template.building_damage_') %><%= p.towerDamage %> </p><p class="kda row-1"> <%= p.kills %>/<%= p.deaths %>/<%= p.assists %> (<%= kdaVal %>)</p><p class="kill_contribution"> <%= $t('dota2tracker.template.kill_contribution_') %><%= kcVal %>%</p><p class="stun_duration"> <%= $t('dota2tracker.template.crowd_control_duration_') %> <%= stunVal %>s</p><p class="heal"> <%= $t('dota2tracker.template.heal_') %><%= p.heroHealing %> </p><div class="items row-1"><div class="normal"> <% p.items.forEach(item => { %> <div class="item<%= item?.isRecipe ? ' recipe' : '' %>" data-id="<%= item?.id ?? 0 %>"><img src="<%- item ? getImageUrl(item.name, ImageType.Items) : '' %>" alt=""/> <% if (item) { %> <p class="time"><%= item.time %></p> <% } %> </div> <% }); %> </div> <% if (!isBear) { %> <div class="backpack"> <% p.backpacks.forEach(item => { %> <div class="item<%= item?.isRecipe ? ' recipe' : '' %>"><img src="<%- item ? getImageUrl(item.name, ImageType.Items) : '' %>" alt=""/></div> <% }); %> </div> <% } else { %> <img class="bear_icon" src="<%- getImageUrl('lone_druid_spirit_bear', ImageType.Abilities) %>" alt=""><div class="bear"> <% p.unitItems.forEach(item => { %> <div class="item<%= item?.isRecipe ? ' recipe' : '' %>" data-id="<%= item?.id ?? 0 %>"><img src="<%- item ? getImageUrl(item.name, ImageType.Items) : '' %>" alt=""/> <% if (item) { %> <p class="time"><%= item.time %></p> <% } %> </div> <% }); %> </div><div class="neutral_item" <%- `style="background-image: url(${getImageUrl(dotaconstants.item_ids[p.additionalUnit.neutral0Id], ImageType.Items)})"` %>></div> <% } %> </div><div class="neutral_item row-1" <%- `style="background-image: url(${getImageUrl(dotaconstants.item_ids[p.neutral0Id], ImageType.Items)})"` %>></div><div class="ahgs row-1"><img src="<%- getImageUrl('scepter_' + (hasAghs ? 1 : 0)) %>" alt=""/> <img src="<%- getImageUrl('shard_' + (hasShard ? 1 : 0)) %>" alt=""/></div></div> <% }); %> </section>
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sjtdev/koishi-plugin-dota2tracker",
|
|
3
3
|
"description": "koishi插件-追踪群友的DOTA2对局 | A Koishi plugin to track Dota 2 matches",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.4.0",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"typings": "lib/index.d.ts",
|
|
7
7
|
"files": [
|
|
@@ -24,18 +24,15 @@
|
|
|
24
24
|
"dota2"
|
|
25
25
|
],
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"axios": "^1.13.
|
|
27
|
+
"axios": "^1.13.5",
|
|
28
28
|
"dotaconstants": "^10.7.0",
|
|
29
|
-
"ejs": "^
|
|
29
|
+
"ejs": "^4.0.1",
|
|
30
|
+
"fontkit": "^2.0.4",
|
|
30
31
|
"https-proxy-agent": "^7.0.6",
|
|
31
32
|
"luxon": "^3.8.0-alpha.1"
|
|
32
33
|
},
|
|
33
|
-
"devDependencies": {
|
|
34
|
-
"@koishijs/client": "^5.30.11"
|
|
35
|
-
},
|
|
36
34
|
"peerDependencies": {
|
|
37
|
-
"
|
|
38
|
-
"koishi": "^4.18.9"
|
|
35
|
+
"koishi": "^4.18.10"
|
|
39
36
|
},
|
|
40
37
|
"publishConfig": {
|
|
41
38
|
"access": "public",
|