@sjtdev/koishi-plugin-dota2tracker 2.1.0 → 2.2.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/lib/index.js
CHANGED
|
@@ -766,56 +766,56 @@ var require_zh_CN_constants = __commonJS({
|
|
|
766
766
|
// src/locales/en-US.command.yml
|
|
767
767
|
var require_en_US_command = __commonJS({
|
|
768
768
|
"src/locales/en-US.command.yml"(exports2, module2) {
|
|
769
|
-
module2.exports = { commands: { dota2tracker: { subscribe: { description: "After subscribing, players need to bind their Steam ID to this group.", usage: "After subscribing, players need to bind their Steam ID to this group. BOT will subscribe to the new game data of bound players in this group. After the STRATZ game analysis is completed, the game data will be generated into a picture battle report and published to this group.", messages: { subscribe_success: "Subscription successful.", subscribed: "This Channel has been subscribed, no need to subscribe again." } }, unsubscribe: { description: "Unsubscribe from this group.", messages: { unsubscribe_success: "Unsubscription successful.", not_subscribed: "This Channel has not been subscribed yet, so there is no need to unsubscribe." } }, bind: { description: "Bind your SteamID and optionally set a nickname.", usage: "Bind your SteamID to your account. If the group is subscribed, your new match data will be posted in the group in real-time.", examples: 'bind 123456789\nbind 123456789 John\nbind 123456789 "John Doe"', messages: { steam_id_invalid: "Invalid SteamID.", bind_success: "Binding successful,\nID: {userId}\nNickname: {nickName}\nSteamID: {steamId}", bind_failed: "Binding failed, {0}", reason_without_match: "Invalid SteamID or no matches found.", reason_fetch_failed: "Poor network conditions or other reasons prevented the verification of the SteamID. Please try again later.", already_binded: "You are already bound, no need to bind again.\nHere is your personal information:\nID: {userId}\nNickname: {nickName}\nSteamID: {steamId}", nick_name_too_long: "Nickname is too long, please limit it to 20 characters or less. (It can also be left blank)", is_anonymous: 'Please note: Your Steam player data is not public, and you will not be able to use the main functions of the BOT, such as "battle report tracking," "query-recent-match commands," etc.\nIf you need to make data public, please set it to public in the DOTA2 game settings.' } }, unbind: { description: "Unbind your personal information.", messages: { unbind_success: "Unbinding successful.", not_binded: "Not bound, no need to unbind." } }, rename: { description: "Change the nickname set during binding.", examples: 'rename John\nrename "John Doe"', messages: { rename_success: "Rename successful, now you are called {nick_name}.", empty_input: "Please enter your nickname.", not_binded: "Please bind first, you can set a nickname during binding.", nick_name_too_long: "Nickname is too long, please limit it to 20 characters.", nick_name_same: "The input content is the same as the original nickname and does not need to be renamed." } }, "query-members": { description: "Query the players bound in this group.", messages: { title: "Guild DOTA 2 Roster (Total: {count})", table_headers: { nickname: "Nickname", winrate: "Win Rate (L10)", last_match: "Last Match" }, no_members: "No players bound in this group.", query_failed: "Failed to query group members." } }, "query-match": { description: "Query the match data of the specified match ID and generate a picture.", options: { parse: "Whether to wait for match data parsing" }, examples: "query-match 1234567890\nquery-match 1234567890 -p\nquery-match 1234567890 --parse", messages: { empty_input: "Please enter the match ID.", match_id_invalid: "Invalid match ID.", querying_match: "Searching for match details, please wait...", query_failed: "Failed to get match data.", waiting_for_parse: "Match data has not been parsed yet, a parse request has been sent to the server. The battle report will be sent once parsing is complete or times out." } }, "query-recent-match": { description: "Query the most recent match data and generate a picture.", options: { parse: "Whether to wait for match data parsing" }, usage: "Query the most recent match data of the specified player and generate a picture.\nThe parameter can be the player's SteamID or the nickname of a player bound in this group. If no parameter is provided, it will try to query the SteamID of the command caller.", examples: "query-recent-match\nquery-recent-match 123456789\nquery-recent-match John\nquery-recent-match 123456789 -p\nquery-recent-match John --parse", messages: { querying_match: "Searching for match details, please wait...", query_failed: "Failed to get the player's recent match.", is_anonymous: "Your player data is not public, and recent match data cannot be obtained.\nIf you need to make data public, please set it to public in the DOTA2 game settings." } }, "query-player": { description: "Query the player's personal information, optionally specify a hero.", options: { hero: "Query the player's usage of the specified hero (same as querying a hero, can use nickname or ID)" }, usage: "Query the personal information of the specified player and generate a picture, optionally specify a hero.\nThe parameter can be the player's SteamID or the nickname of a player bound in this group. If no parameter is provided, it will try to query the SteamID of the command caller.", examples: "query-player\nquery-player 123456789\nquery-player John\nquery-player John --hero Anti-Mage\nquery-player John -o Anti-Mage", messages: { querying_player: "Retrieving player data, please wait...", query_failed: "Failed to get player information." } }, "query-hero": { description: "Query hero skills/stats information.", options: { random: "Randomly select a hero." }, usage: "Query the hero's skill descriptions and various stats, generate a picture.\nThe parameter can be the hero's ID, name, or common nickname.", examples: "query-hero 15\nquery-hero Razor\nquery-hero -r", messages: { not_found: "Hero not found, please confirm and re-enter.", querying_hero: "Retrieving hero data, please wait...", query_failed: "Failed to get hero data.", empty_input: "Please enter a parameter." } }, "query-item": { description: "Query item information", usage: "Query item descriptions and attributes, then generate and publish an image report.\nParameters can be item name (supports fuzzy search), item alias, or item ID.\nYou can set the maximum number of items to send per query on the configuration page, as well as whether to send the item list when the limit is exceeded or parameters are not entered.", examples: "query-item Vanguard", messages: { query_list_failed: "Failed to retrieve item list data", query_item_failed: "Failed to retrieve data for item '{0}'", querying_item: "Querying item data, please wait...", cache_building: "Initializing or rebuilding item cache for the current version, please wait...", empty_input: "No keywords provided. \n{#if show}Displaying full item list per current configuration\n{:else}No content available\n{/if}", not_found: "No items found matching the keywords, please verify and retry", too_many_items: "Found {count} items, exceeding maximum display limit ({max} items)\n{#if show}(Displaying item list){/if}", finded_items: "Matching items: \n{#each items as item}\n{item.name_loc}{#if item !== items[items.length - 1]}, {/if}\n{/each}" } }, "hero-of-the-day": { description: "Get hero recommendations for the day.", usage: "Fetches recent and lifetime match history to recommend heroes based on parameters like wins, performance score, and hot streaks.\nThe parameter can be a player's SteamID or the nickname of a player bound in this group. If no parameter is provided, it will try to look up the command caller's SteamID.", options: { days: "-d <number> The range of recent days to consider, default is 30." }, examples: 'hero-of-the-day\nhero-of-the-day -d 60\nhero-of-the-day 1234567890\nhero-of-the-day "John Doe"', messages: { title_recommendation: "Today's Recommendation:", recommendation_intro: "The recommended heroes for you today are:", recommendation_heroes: "{#each heroes as hero}{hero}{#if hero !== heroes[heroes.length - 1]}, {/if}{/each}", recommendation_type_lifetime_only: "Your recent match history is empty. This recommendation is based on your lifetime statistics.", recommendation_type_no_record: "Recommendations cannot be generated due to a lack of recent and lifetime match data.", recommendation_type_anonymous: "Recommendations cannot be generated because your profile data is private.", details: { pool_description: "The recommendation is generated by scoring your recent and lifetime hero performance, sorting by total score, and then randomly selecting from the top 10 heroes weighted by their scores.", table_intro: "Below is the detailed score breakdown for the top 10 heroes.", table_headers: { hero: "Hero", recent_wins: "Recent Wins Score", lifetime_wins: "Lifetime Wins<br>(Logarithmic)", imp_bonus: "IMP Bonus", is_hot_streak: "Hot Streak", total_score: "Total Score" }, scoring_formula: "Current Scoring Formula: [Recent Wins x 1] + [log(Lifetime Wins + 1) x 5] + [Recent IMP x 0.1]", hot_streak_desc: "If a hero was played in the last 3 days, it's considered a 'Hot Streak' hero, receiving a 20% bonus to its total score." }, title_meta: "Meta Trends:", meta_intro: "Top 3 advantage heroes for each position with a <b>pick rate ≥2%</b> within ±1 of your rank bracket ({tiers}) over the last week, sorted by win rate:", meta_table_header: "Hero (Pick% Win%)", meta_position: "Pos {pos}:" } }, common: { messages: { user_not_binded_in_channel: "By default, it tries to find your information from the bound SteamID players, but it seems you are not bound.\nPlease bind your SteamID in this group. (You can enter [bind -h] for help)\nOr follow the command with the SteamID or nickname of the player you want to query.", user_not_in_group: "Command failed.\nCurrently not in a group chat, you must provide the specified player's SteamID.", invalid_input_include_steam_id: "Invalid SteamID and the player was not found in this group by the given input." } }, help: { description: "Get the link to the detailed online documentation for the plugin.", messages: { content: "For a detailed guide on DOTA2Tracker features, command usage, and algorithm explanations, please visit the online documentation:\nhttps://sjtdev.github.io/koishi-plugin-dota2tracker/en-US/" } } } } };
|
|
769
|
+
module2.exports = { commands: { dota2tracker: { description: 'A series of commands for Dota 2 Tracker. Use "dota2tracker -h" to see all available commands.', subscribe: { description: "Subscribes the current channel to Dota 2 match tracking.", usage: "After subscribing, players need to bind their Steam ID in this channel. The bot will then track new matches of bound players and post image-based reports upon completion of parsing by Stratz.", examples: "subscribe", messages: { subscribe_success: "Subscription successful.", subscribed: "This Channel has been subscribed, no need to subscribe again." } }, unsubscribe: { description: "Unsubscribes the current channel from match tracking.", usage: "Unsubscribes the current channel from match tracking.", examples: "unsubscribe", messages: { unsubscribe_success: "Unsubscription successful.", not_subscribed: "This Channel has not been subscribed yet, so there is no need to unsubscribe." } }, bind: { description: "Binds your SteamID to your account in the current channel.", usage: 'Bind your SteamID to your account. If the channel is subscribed, your new match data will be posted automatically. Nicknames containing spaces must be enclosed in double quotes ("").', examples: 'bind 123456789\nbind 123456789 John\nbind 123456789 "John Doe"', messages: { steam_id_invalid: "Invalid SteamID.", bind_success: "Binding successful,\nID: {userId}\nNickname: {nickName}\nSteamID: {steamId}", bind_failed: "Binding failed, {0}", reason_without_match: "Invalid SteamID or no matches found.", reason_fetch_failed: "Poor network conditions or other reasons prevented the verification of the SteamID. Please try again later.", already_binded: "You are already bound, no need to bind again.\nHere is your personal information:\nID: {userId}\nNickname: {nickName}\nSteamID: {steamId}", nick_name_too_long: "Nickname is too long, please limit it to 20 characters or less. (It can also be left blank)", is_anonymous: 'Please note: Your Steam player data is not public, and you will not be able to use the main functions of the BOT, such as "battle report tracking," "query-recent-match commands," etc.\nIf you need to make data public, please set it to public in the DOTA2 game settings.' } }, unbind: { description: "Unbinds your personal information in the current channel.", usage: "Unbind your personal information in the current channel.", examples: "unbind", messages: { unbind_success: "Unbinding successful.", not_binded: "Not bound, no need to unbind." } }, rename: { description: "Changes the nickname set during binding.", usage: 'Change the nickname set during binding. Nicknames containing spaces must be enclosed in double quotes ("").', examples: 'rename John\nrename "John Doe"', messages: { rename_success: "Rename successful, now you are called {nick_name}.", empty_input: "Please enter your nickname.", not_binded: "Please bind first, you can set a nickname during binding.", nick_name_too_long: "Nickname is too long, please limit it to 20 characters.", nick_name_same: "The input content is the same as the original nickname and does not need to be renamed." } }, "query-members": { description: "Queries the players bound in this channel and generates an info image.", usage: "Queries the players bound in this channel and generates an informational image.", examples: "query-members", messages: { title: "Guild DOTA 2 Roster (Total: {count})", table_headers: { nickname: "Nickname", winrate: "Win Rate (L10)", last_match: "Last Match" }, no_members: "No players bound in this group.", query_failed: "Failed to query group members." } }, "query-match": { description: "Query the match data of the specified match ID and generate a picture.", usage: "Query the match data of the specified match ID and generate a picture.", options: { parse: "Whether to wait for match data parsing" }, examples: "query-match 1234567890\nquery-match 1234567890 -p\nquery-match 1234567890 --parse", messages: { empty_input: "Please enter the match ID.", match_id_invalid: "Invalid match ID.", querying_match: "Searching for match details, please wait...", query_failed: "Failed to get match data.", waiting_for_parse: "Match data has not been parsed yet, a parse request has been sent to the server. The battle report will be sent once parsing is complete or times out." } }, "query-recent-match": { description: "Query the most recent match data and generate a picture.", options: { parse: "Whether to wait for match data parsing" }, usage: "Query the most recent match data of the specified player and generate a picture.\nThe parameter can be the player's SteamID or the nickname of a player bound in this group. If no parameter is provided, it will try to query the SteamID of the command caller.", examples: "query-recent-match\nquery-recent-match 123456789\nquery-recent-match John\nquery-recent-match 123456789 -p\nquery-recent-match John --parse", messages: { querying_match: "Searching for match details, please wait...", query_failed: "Failed to get the player's recent match.", is_anonymous: "Your player data is not public, and recent match data cannot be obtained.\nIf you need to make data public, please set it to public in the DOTA2 game settings." } }, "query-player": { description: "Query the player's personal information, optionally specify a hero.", options: { hero: "Query the player's usage of the specified hero (same as querying a hero, can use nickname or ID)" }, usage: "Query the personal information of the specified player and generate a picture, optionally specify a hero.\nThe parameter can be the player's SteamID or the nickname of a player bound in this group. If no parameter is provided, it will try to query the SteamID of the command caller.", examples: "query-player\nquery-player 123456789\nquery-player John\nquery-player John --hero Anti-Mage\nquery-player John -o Anti-Mage", messages: { querying_player: "Retrieving player data, please wait...", query_failed: "Failed to get player information." } }, "query-hero": { description: "Query hero skills/stats information.", options: { random: "Randomly select a hero." }, usage: "Query the hero's skill descriptions and various stats, generate a picture.\nThe parameter can be the hero's ID, name, or common nickname.", examples: "query-hero 15\nquery-hero Razor\nquery-hero -r", messages: { not_found: "Hero not found, please confirm and re-enter.", querying_hero: "Retrieving hero data, please wait...", query_failed: "Failed to get hero data.", empty_input: "Please enter a parameter." } }, "query-item": { description: "Query item information", usage: "Query item descriptions and attributes, then generate and publish an image report.\nParameters can be item name (supports fuzzy search), item alias, or item ID.\nYou can set the maximum number of items to send per query on the configuration page, as well as whether to send the item list when the limit is exceeded or parameters are not entered.", examples: "query-item Vanguard", messages: { query_list_failed: "Failed to retrieve item list data", query_item_failed: "Failed to retrieve data for item '{0}'", querying_item: "Querying item data, please wait...", cache_building: "Initializing or rebuilding item cache for the current version, please wait...", empty_input: "No keywords provided. \n{#if show}Displaying full item list per current configuration\n{:else}No content available\n{/if}", not_found: "No items found matching the keywords, please verify and retry", too_many_items: "Found {count} items, exceeding maximum display limit ({max} items)\n{#if show}(Displaying item list){/if}", finded_items: "Matching items: \n{#each items as item}\n{item.name_loc}{#if item !== items[items.length - 1]}, {/if}\n{/each}" } }, "hero-of-the-day": { description: "Get hero recommendations for the day.", usage: "Fetches recent and lifetime match history to recommend heroes based on parameters like wins, performance score, and hot streaks.\nThe parameter can be a player's SteamID or the nickname of a player bound in this group. If no parameter is provided, it will try to look up the command caller's SteamID.", options: { days: "-d <number> The range of recent days to consider, default is 30." }, examples: 'hero-of-the-day\nhero-of-the-day -d 60\nhero-of-the-day 1234567890\nhero-of-the-day "John Doe"', messages: { title_recommendation: "Today's Recommendation:", recommendation_intro: "The recommended heroes for you today are:", recommendation_heroes: "{#each heroes as hero}{hero}{#if hero !== heroes[heroes.length - 1]}, {/if}{/each}", recommendation_type_lifetime_only: "Your recent match history is empty. This recommendation is based on your lifetime statistics.", recommendation_type_no_record: "Recommendations cannot be generated due to a lack of recent and lifetime match data.", recommendation_type_anonymous: "Recommendations cannot be generated because your profile data is private.", details: { pool_description: "The recommendation is generated by scoring your recent and lifetime hero performance, sorting by total score, and then randomly selecting from the top 10 heroes weighted by their scores.", table_intro: "Below is the detailed score breakdown for the top 10 heroes.", table_headers: { hero: "Hero", recent_wins: "Recent Wins Score", lifetime_wins: "Lifetime Wins<br>(Logarithmic)", imp_bonus: "IMP Bonus", is_hot_streak: "Hot Streak", total_score: "Total Score" }, scoring_formula: "Current Scoring Formula: [Recent Wins x 1] + [log(Lifetime Wins + 1) x 5] + [Recent IMP x 0.1]", hot_streak_desc: "If a hero was played in the last 3 days, it's considered a 'Hot Streak' hero, receiving a 20% bonus to its total score." }, title_meta: "Meta Trends:", meta_intro: "Top 3 advantage heroes for each position with a <b>pick rate ≥2%</b> within ±1 of your rank bracket ({tiers}) over the last week, sorted by win rate:", meta_table_header: "Hero (Pick% Win%)", meta_position: "Pos {pos}:" } }, common: { messages: { user_not_binded_in_channel: "By default, it tries to find your information from the bound SteamID players, but it seems you are not bound.\nPlease bind your SteamID in this group. (You can enter [bind -h] for help)\nOr follow the command with the SteamID or nickname of the player you want to query.", user_not_in_group: "Command failed.\nCurrently not in a group chat, you must provide the specified player's SteamID.", invalid_input_include_steam_id: "Invalid SteamID and the player was not found in this group by the given input." } }, help: { description: "Get detailed information for all commands and the link to the online documentation.", usage: "Get detailed information for all commands and the link to the online documentation.", examples: "help", messages: { header: "Below is the full list of plugin commands.\n※Note: <arg> is a required argument, and [arg] is an optional argument. Please see the 'Examples' column for specific usage.", footer: "For more information on plugin configuration, template showcases, and other details, please visit the online documentation:\nhttps://sjtdev.github.io/koishi-plugin-dota2tracker/en-US/", table_headers: { command: "Command", alias: "Alias", arguments: "Arguments", description: "Description", options: "Options", examples: "Examples" } } } } } };
|
|
770
770
|
}
|
|
771
771
|
});
|
|
772
772
|
|
|
773
773
|
// src/locales/en-US.schema.yml
|
|
774
774
|
var require_en_US_schema = __commonJS({
|
|
775
775
|
"src/locales/en-US.schema.yml"(exports2, module2) {
|
|
776
|
-
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 to disable the proxy.", suppressStratzNetworkErrors: "When enabled, downgrades Stratz network errors (e.g. timeouts) to lower-priority output. Debug-level logs are hidden by default. To enable debug logging, see [📖 Documentation: Configuration](http://sjtdev.github.io/koishi-plugin-dota2tracker/en-US/configs.html#suppressstratznetworkerrors-boolean)" }, 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" } }, 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 to generate match information images, see [📖 Template Display](https://sjtdev.github.io/koishi-plugin-dota2tracker/template-match.html) for template display.", template_player: "Template used to generate player information images. (Currently only one template available)", template_hero: "Template used to generate hero information images. (Currently only one template available)", playerRankEstimate: "Estimate the rank of players without a rank in the player template <br>Estimated rank will be displayed as a gray image", templateFonts: 'Font names used in the template. Requires font files installed on the koishi host machine. \nMultiple fonts can be added; the system will use the first available font from top to bottom. \nIf all fonts are unavailable, falls back to system defaults. \nImportant formatting rules: \n- Enclose font names in quotes (" ") if they contain spaces or special characters (recommended for all font names)\n- Do NOT enclose generic font family names (e.g. sans-serif, monospace) in quotes\nExamples:\n```\n"Microsoft YaHei"\nsans-serif\n```\nFor details on font-family syntax, see:\n[📖 MDN: font-family](https://developer.mozilla.org/en-US/docs/Web/CSS/font-family)' } } };
|
|
776
|
+
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 to disable the proxy.", suppressStratzNetworkErrors: "When enabled, downgrades Stratz network errors (e.g. timeouts) to lower-priority output. Debug-level logs are hidden by default. To enable debug logging, see [📖 Documentation: Configuration](http://sjtdev.github.io/koishi-plugin-dota2tracker/en-US/configs.html#suppressstratznetworkerrors-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." }, 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" } }, 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 to generate match information images, see [📖 Template Display](https://sjtdev.github.io/koishi-plugin-dota2tracker/template-match.html) for template display.", template_player: "Template used to generate player information images. (Currently only one template available)", template_hero: "Template used to generate hero information images. (Currently only one template available)", playerRankEstimate: "Estimate the rank of players without a rank in the player template <br>Estimated rank will be displayed as a gray image", templateFonts: 'Font names used in the template. Requires font files installed on the koishi host machine. \nMultiple fonts can be added; the system will use the first available font from top to bottom. \nIf all fonts are unavailable, falls back to system defaults. \nImportant formatting rules: \n- Enclose font names in quotes (" ") if they contain spaces or special characters (recommended for all font names)\n- Do NOT enclose generic font family names (e.g. sans-serif, monospace) in quotes\nExamples:\n```\n"Microsoft YaHei"\nsans-serif\n```\nFor details on font-family syntax, see:\n[📖 MDN: font-family](https://developer.mozilla.org/en-US/docs/Web/CSS/font-family)' } } };
|
|
777
777
|
}
|
|
778
778
|
});
|
|
779
779
|
|
|
780
780
|
// src/locales/en-US.template.yml
|
|
781
781
|
var require_en_US_template = __commonJS({
|
|
782
782
|
"src/locales/en-US.template.yml"(exports2, module2) {
|
|
783
|
-
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", 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}<br/>Sad", rank_fun_up_message: "Hip hip hooray! Our awesome member {avatar}{name} has leveled up from {prev} to {curr}!", titles: { MVP: "MVP-#FFA500", Soul: "Soul-#66CCFF", Rich: "R-#FFD700", Wise: "W-#8888FF", Controller: "C-#FF00FF", Nuker: "N-#CC0088", Breaker: "B-#DD0000", Ghost: "G-#CCCCCC", Utility: "U-#20B2AA", Assister: "A-#006400", Demolisher: "D-#FEDCBA", Healer: "H-#00FF00", Tank: "T-#84A1C7", Idle: "I-#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" } } };
|
|
783
|
+
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}<br/>Sad", rank_fun_up_message: "Hip hip hooray! Our awesome member {avatar}{name} has leveled up from {prev} to {curr}!", titles: { MVP: "MVP-#FFA500", Soul: "Soul-#66CCFF", Rich: "R-#FFD700", Wise: "W-#8888FF", Controller: "C-#FF00FF", Nuker: "N-#CC0088", Breaker: "B-#DD0000", Ghost: "G-#CCCCCC", Utility: "U-#20B2AA", Assister: "A-#006400", Demolisher: "D-#FEDCBA", Healer: "H-#00FF00", Tank: "T-#84A1C7", Idle: "I-#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" } } };
|
|
784
784
|
}
|
|
785
785
|
});
|
|
786
786
|
|
|
787
787
|
// src/locales/en-US.yml
|
|
788
788
|
var require_en_US = __commonJS({
|
|
789
789
|
"src/locales/en-US.yml"(exports2, module2) {
|
|
790
|
-
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"' }, 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
|
|
790
|
+
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"' }, 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 denied (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", stratz_api_query_error: "Stratz API returned partial data with errors: {cause}", opendota_parse_request_on_later: "OpenDota fallback is enabled. A parse request will be sent to OpenDota every {timeout} minutes, and the status will be checked every minute.", 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" } } };
|
|
791
791
|
}
|
|
792
792
|
});
|
|
793
793
|
|
|
794
794
|
// src/locales/zh-CN.command.yml
|
|
795
795
|
var require_zh_CN_command = __commonJS({
|
|
796
796
|
"src/locales/zh-CN.command.yml"(exports2, module2) {
|
|
797
|
-
module2.exports = { commands: { dota2tracker: { description: "dota2tracker的一系列指令,可以使用dota2tracker -h查看所有可用指令。", subscribe: { description: "[订阅本群]", usage: "订阅后还需玩家在本群绑定SteamID,BOT将订阅本群中已绑定玩家的新比赛数据,在STRATZ比赛解析完成后将比赛数据生成为图片战报发布至本群中。", messages: { subscribed: "本群已订阅,无需重复订阅。", subscribe_success: "订阅成功。" } }, unsubscribe: { description: "[取消订阅] 取消订阅本群。", messages: { unsubscribe_success: "取消订阅成功。", not_subscribed: "本群尚未订阅,无需取消订阅。" } }, bind: { description: "[绑定] 绑定SteamID,并起一个别名(也可以不起)。", usage:
|
|
797
|
+
module2.exports = { commands: { dota2tracker: { description: "dota2tracker的一系列指令,可以使用dota2tracker -h查看所有可用指令。", subscribe: { description: "[订阅本群]", usage: "订阅后还需玩家在本群绑定SteamID,BOT将订阅本群中已绑定玩家的新比赛数据,在STRATZ比赛解析完成后将比赛数据生成为图片战报发布至本群中。", examples: "订阅本群", messages: { subscribed: "本群已订阅,无需重复订阅。", subscribe_success: "订阅成功。" } }, unsubscribe: { description: "[取消订阅] 取消订阅本群。", usage: "取消订阅本群。", examples: "取消订阅", messages: { unsubscribe_success: "取消订阅成功。", not_subscribed: "本群尚未订阅,无需取消订阅。" } }, bind: { description: "[绑定] 绑定SteamID,并起一个别名(也可以不起)。", usage: '将你的SteamID与你的账号绑定,若本群已订阅将会实时获取你的新比赛数据发布至群中。名称中含有空格时需要使用""引号包裹(英文半角引号)。', examples: '绑定 123456789\n绑定 123456789 张三\n绑定 123456789 "张 三"', messages: { steam_id_invalid: "SteamID无效。", bind_success: "绑定成功,\nID:{userId}\n别名:{nickName}\nSteamID:{steamId}", bind_failed: "绑定失败,{0}", reason_without_match: "SteamID无效或无任何场次。", reason_fetch_failed: "网络状况不佳或其他原因无法验证SteamID,请稍后重试。", already_binded: "你已绑定,无需重复绑定。\n以下是你的个人信息:\nID:{userId}\n别名:{nickName}\nSteamID:{steamId}", nick_name_too_long: "别名过长,请限制在20个字符以内。(也可以留空)", is_anonymous: "请注意:你的Steam玩家数据并未公开,将无法使用BOT的主要功能,如“战报追踪”、“查询最近指令”等。\n如需公开数据,请在DOTA2游戏内设置中公开。" } }, unbind: { description: "[取消绑定] 取消绑定你的个人信息。", usage: "取消绑定你的个人信息。", examples: "取消绑定", messages: { unbind_success: "取消绑定成功。", not_binded: "尚未绑定,无需取消绑定。" } }, rename: { description: "[改名] 修改绑定时设定的别名。", usage: '修改绑定时设定的别名。名称中含有空格时需要使用""引号包裹(英文半角引号)。', examples: '改名 李四\n改名 "李 四"', messages: { rename_success: "改名成功,现在你叫{nick_name}了。", empty_input: "请输入你的别名。", not_binded: "请先绑定,绑定时即可设定别名。", nick_name_too_long: "别名过长,请限制在20个字符以内。", nick_name_same: "目标别名与原始别名相同,无需改名。" } }, "query-members": { description: "[查询群友] 查询本群已绑定的玩家。", usage: "查询本群已绑定的玩家,生成简单信息图片发布。", examples: "查询群友", messages: { title: "本群 DOTA2 玩家名册 (共 {count} 人)", table_headers: { nickname: "昵称/别名", winrate: "胜率 (近10场)", last_match: "最近比赛" }, no_members: "本群尚无绑定玩家。", query_failed: "查询群友失败。" } }, "query-match": { description: "[查询比赛] 查询指定比赛ID的比赛数据,生成图片发布。", usage: "查询指定MatchID的比赛数据,生成图片发布。", options: { parse: "-p 是否等待解析比赛数据" }, examples: "查询比赛 1234567890\n查询比赛 1234567890 -p\n查询比赛 1234567890 --parse", messages: { empty_input: "请输入比赛ID。", match_id_invalid: "比赛ID无效。", querying_match: "正在搜索对局详情,请稍后……", query_failed: "获取比赛数据失败。", waiting_for_parse: "比赛数据尚未解析,已发送解析请求到服务器,战报将在解析完成或超时后发送。" } }, "query-recent-match": { description: "[查询最近比赛] 查询最近的比赛数据,生成图片发布。", options: { parse: "-p 是否等待解析比赛数据" }, usage: "查询指定玩家的最近一场比赛的比赛数据,生成图片发布。\n参数可输入该玩家的SteamID或已在本群绑定玩家的别名,无参数时尝试查询调用指令玩家的SteamID。", examples: "查询最近比赛\n查询最近比赛 123456789\n查询最近比赛 张三\n查询最近比赛 123456789 -p\n查询最近比赛 张三 --parse", messages: { querying_match: "正在搜索对局详情,请稍后……", query_failed: "获取玩家最近比赛失败。", not_in_group: "指令调用失败。\n当前不属于群聊状态,必须提供指定玩家的SteamID。", is_anonymous: "你的比赛数据未公开,无法获取最近比赛数据。\n如需公开数据,请在DOTA2游戏内设置中公开。" } }, "query-player": { description: "[查询玩家] 查询玩家的个人信息,可指定英雄。", options: { hero: "-o 查询玩家指定英雄使用情况(同查询英雄,可用别名或ID)" }, usage: "查询指定玩家的个人信息,生成图片发布,可指定英雄。\n参数可输入该玩家的SteamID或已在本群绑定玩家的别名,无参数时尝试查询调用指令玩家的SteamID。", examples: "查询玩家\n查询玩家 123456789\n查询玩家 张三\n查询玩家 张三 --hero 敌法师\n查询玩家 张三 -o 15", messages: { querying_player: "正在获取玩家数据,请稍后……", query_failed: "获取玩家信息失败。", not_in_group: "指令调用失败。\n当前不属于群聊状态,必须提供指定玩家的SteamID。" } }, "query-hero": { description: "[查询英雄] 查询英雄技能/面板信息。", options: { random: "-r 随机选择英雄" }, usage: "查询英雄的技能说明与各项数据,生成图片发布。\n参数可输入英雄ID、英雄名、英雄常用别名。", examples: "查询英雄 15\n查询英雄 雷泽\n查询英雄 电魂\n查询英雄 -r", messages: { not_found: "未找到输入的英雄,请确认后重新输入。", querying_hero: "正在获取英雄数据,请稍后……", query_failed: "获取英雄数据失败。", empty_input: "请输入参数。" } }, "query-item": { description: "[查询物品] 查询物品信息。", usage: "查询物品的描述与各项数据,生成图片发布。\n参数可输入物品名(可模糊查找)、物品别名、物品ID。\n可在配置页中设置每次查询的最大发送数量、以及是否在超过限制或未输入参数时发送物品列表。", examples: "查询物品 先锋盾", messages: { query_list_failed: "获取物品列表数据失败。", query_item_failed: "获取物品「{0}」数据失败", querying_item: "正在查询物品数据,请稍候…", cache_building: "初次使用或缓存已过期,正在生成当前版本的物品缓存,请稍后……", empty_input: "未输入关键字参数。根据当前配置{#if show},将返回全部物品列表{:else}无内容可发送{/if}。", not_found: "未找到与关键字匹配的物品,请确认后重试。", too_many_items: "找到{count}个物品,超过最大发送限制({max}个){#if show},将发送物品列表{/if}。", finded_items: "找到以下物品:{#each items as item}{item.name_loc}{#if item !== items[items.length - 1]}、{/if}{/each}" } }, "hero-of-the-day": { description: "[今日英雄] 获取今日英雄推荐。", usage: "获取近期比赛记录、生涯比赛记录,根据胜场、表现分、是否手热等参数计算推荐英雄。\n参数可输入该玩家的SteamID或已在本群绑定玩家的别名,无参数时尝试查询调用指令玩家的SteamID。", options: { days: "-d <number> 近期数据的获取范围,单位为天数,默认值为30" }, examples: "今日英雄\n今日英雄 -d 60\n今日英雄 1234567890\n今日英雄 张三", messages: { title_recommendation: "今日推荐:", recommendation_intro: "今日为您推荐的英雄是:", recommendation_heroes: "{#each heroes as hero}{hero}{#if hero !== heroes[heroes.length - 1]}、{/if}{/each}", recommendation_type_lifetime_only: "您的近期数据为空,本次推荐结果基于您的生涯数据。", recommendation_type_no_record: "您的近期与生涯数据为空,无法生成推荐信息。", recommendation_type_anonymous: "您的个人数据未公开,无法生成推荐信息。", details: { pool_description: "推荐结果根据对您的近期与生涯英雄使用记录计分后,按总分排序后对前10位英雄以分数为权重随机取得。", table_intro: "以下是前10位英雄具体得分表。", table_headers: { hero: "英雄名称", recent_wins: "近期胜场分", lifetime_wins: "生涯胜场分<br>(对数)", imp_bonus: "imp奖励分", is_hot_streak: "是否手热", total_score: "总分" }, scoring_formula: "当前计分规则:[近期胜场数 x 1] + [log(生涯胜场数+1) x 5] + [近期imp x 0.1]", hot_streak_desc: "若英雄在3天内使用过,则记为手热英雄,总分提升20%。" }, title_meta: "环境趋势:", meta_intro: "一周内,基于您段位±1 ({tiers}) 范围内各位置<b>选择率≥2%</b>按胜率从高到低前三名优势英雄:", meta_table_header: "英雄名称(选择率% 胜率%)", meta_position: "{pos}号位:" } }, common: { messages: { user_not_binded_in_channel: "无参数时默认从已绑定SteamID玩家中寻找你的信息,但你似乎并没有绑定。\n请在本群绑定SteamID。(可输入【绑定 -h】获取帮助)\n或在指令后跟上希望查询的SteamID或已绑定玩家的别名。", user_not_in_group: "指令调用失败。\n当前不属于群聊状态,必须提供指定玩家的SteamID。", invalid_input_include_steam_id: "SteamID无效并且未在本群根据输入信息找到玩家。" } }, help: { description: "[DOTA2指南] 获取插件的全部指令详细信息与在线文档链接。", usage: "获取插件的全部指令详细信息与在线文档链接。", examples: "DOTA2指南\nDOTA2帮助\nDOTA2说明", messages: { header: "以下是插件的全部指令。\n※注意 <arg> 是必须参数,[arg] 是可选参数,具体使用方法请看“用法示例”列。", footer: "插件配置、模板展示等更多信息请访问在线文档:\nhttps://sjtdev.github.io/koishi-plugin-dota2tracker/", table_headers: { command: "指令名", alias: "指令中文", arguments: "参数", description: "说明", options: "选项", examples: "用法示例" } } } } } };
|
|
798
798
|
}
|
|
799
799
|
});
|
|
800
800
|
|
|
801
801
|
// src/locales/zh-CN.schema.yml
|
|
802
802
|
var require_zh_CN_schema = __commonJS({
|
|
803
803
|
"src/locales/zh-CN.schema.yml"(exports2, module2) {
|
|
804
|
-
module2.exports = { _config: { base: { $desc: "基础设置", STRATZ_API_TOKEN: "※必须。stratz.com的API TOKEN,可在 https://stratz.com/api 获取。", dataParsingTimeoutMinutes: "等待比赛数据解析的时间(单位:分钟)。如果数据解析时间超过等待时间,将直接生成战报而不再等待解析完成。", proxyAddress: "代理地址,留空时不使用代理", suppressStratzNetworkErrors: "开启后将stratz网络错误日志(如超时、网络不通等,但403 Forbidden除外)使用debug级别输出,默认不显示debug级日志。 \n若需要开启debug日志显示,请见 [📖 配置项#suppressstratznetworkerrors](http://sjtdev.github.io/koishi-plugin-dota2tracker/configs.html#suppressstratznetworkerrors-boolean)" }, 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: "别名" } }, 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: '模板所使用的字体名。需要 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) ' } } };
|
|
804
|
+
module2.exports = { _config: { base: { $desc: "基础设置", STRATZ_API_TOKEN: "※必须。stratz.com的API TOKEN,可在 https://stratz.com/api 获取。", dataParsingTimeoutMinutes: "等待比赛数据解析的时间(单位:分钟)。如果数据解析时间超过等待时间,将直接生成战报而不再等待解析完成。", proxyAddress: "代理地址,留空时不使用代理", suppressStratzNetworkErrors: "开启后将stratz网络错误日志(如超时、网络不通等,但403 Forbidden除外)使用debug级别输出,默认不显示debug级日志。 \n若需要开启debug日志显示,请见 [📖 配置项#suppressstratznetworkerrors](http://sjtdev.github.io/koishi-plugin-dota2tracker/configs.html#suppressstratznetworkerrors-boolean)", enableOpenDotaFallback: "启用 OpenDota 作为战报追踪与查询比赛功能的备用数据源。", OPENDOTA_API_KEY: "OpenDota 的订阅付费APIKEY, \n可在 https://www.opendota.com/api-keys 查看详情。 \nOpenDota 的免费用户此处请留空。" }, 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: "别名" } }, 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: '模板所使用的字体名。需要 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) ' } } };
|
|
805
805
|
}
|
|
806
806
|
});
|
|
807
807
|
|
|
808
808
|
// src/locales/zh-CN.template.yml
|
|
809
809
|
var require_zh_CN_template = __commonJS({
|
|
810
810
|
"src/locales/zh-CN.template.yml"(exports2, module2) {
|
|
811
|
-
module2.exports = { dota2tracker: { template: { radiant: "天辉", dire: "夜魇", won: "获胜", lost: "失败", match_id_: "比赛编号:{0}", game_mode_: "模式:{0}", start_time_: "起始时间:{0}", end_time_: "结束时间:{0}", pick_order: "第{0}手", random: "随机", hero_damage_: "英雄伤害:{0}", building_damage_: "建筑伤害:{0}", damage_received_: "受到伤害:{0}", lasthit_: "补刀:{0}", deny_: "反补:{0}", "lh/dn_": "补刀:{0}/{1}", GPM: "GPM", XPM: "XPM", heal_: "治疗量:{0}", crowd_control_duration_: "控制时间:{0}", "GPM/XPM_": "GPM/XPM:{0}", lane: "对线", lane_: "对线:", lane_advantage: "对线优势", lane_disadvantage: "对线劣势", lane_stomp: "对线碾压", lane_stomped: "对线被碾", lane_tie: "对线平手", lane_jungle: "野区霸主", analysis_successful: "录像分析成功", analysis_incomplete: "分析结果不完整", kill: "击杀", kill_contribution_: "参战率:{0}", position: "位置", position_: "位置:", position_1: "优势路", position_2: "中路", position_3: "烈士路", position_4: "采灵芝", position_5: "工具人", position_undefined: "?", total_damage: "总伤害", total_gold: "总经济", total_experience: "总经验", radiant_won: "天辉获胜", dire_won: "夜魇获胜", duration_: "持续时间:{0}", region_: "地区:{0}", match_count_: "场次:", last25matches_: "最近25场:", winrate_: "胜率:", imp_: "表现:", lane_advantage_rate_: "线优率:", top10_: "全期场次前十的英雄:", hero: "英雄", all_matches_: "全期场次:", match_count: "场次", winrate: "胜率", imp: "表现", win_count: "胜场", lose_count: "败场", recently_heroes: "近期使用场次大于1的英雄:", recently_positions: "近25场各个位置的表现:", winning_streak: "连胜", losing_streak: "连败", id: "ID", mode: "模式", kda_kc: "KDA(参战率)", time: "时间", duration: "时长", rank: "段位", un_parsed: "(未解析)", combined_win_loss_summary: "组合胜负情况:", yesterdays_summary: "昨日总结", last_weeks_summary: "上周总结", report_won: "胜", report_lost: "负", report_winrate: "胜率", anonymous_player_1: "数据未公开", anonymous_player_2: "背景仅供展示目的,不属于{player}的数据。", rank_fun_up_message: "热烈祝贺群友 {avatar}{name} 在天梯中再获进步,<br/>由 {prev} 升为 {curr},再接再厉,再创辉煌!", rank_fun_down_message: "{avatar}<br/>寄", titles: { MVP: "MVP-#FFA500", Soul: "魂-#66CCFF", Rich: "富-#FFD700", Wise: "睿-#8888FF", Controller: "控-#FF00FF", Nuker: "爆-#CC0088", Breaker: "破-#DD0000", Ghost: "鬼-#CCCCCC", Utility: "辅-#20B2AA", Assister: "助-#006400", Demolisher: "拆-#FEDCBA", Healer: "奶-#00FF00", Tank: "耐-#84A1C7", Idle: "摸-#DDDDDD" }, situation: "局势", networth: "经济", experience: "经验", OUTCOME_MAP: { RADIANT_VICTORY: "天辉优势", RADIANT_STOMP: "天辉碾压", DIRE_VICTORY: "夜魇优势", DIRE_STOMP: "夜魇碾压", TIE: "势均力敌" }, lane_top: "上路", lane_mid: "中路", lane_bottom: "下路", empty_extra_info: "比赛未解析或信息缺失,无法展示更多数据。" } } };
|
|
811
|
+
module2.exports = { dota2tracker: { template: { radiant: "天辉", dire: "夜魇", won: "获胜", lost: "失败", match_id_: "比赛编号:{0}", game_mode_: "模式:{0}", start_time_: "起始时间:{0}", end_time_: "结束时间:{0}", pick_order: "第{0}手", random: "随机", hero_damage_: "英雄伤害:{0}", building_damage_: "建筑伤害:{0}", damage_received_: "受到伤害:{0}", lasthit_: "补刀:{0}", deny_: "反补:{0}", "lh/dn_": "补刀:{0}/{1}", GPM: "GPM", XPM: "XPM", heal_: "治疗量:{0}", crowd_control_duration_: "控制时间:{0}", "GPM/XPM_": "GPM/XPM:{0}", lane: "对线", lane_: "对线:", lane_advantage: "对线优势", lane_disadvantage: "对线劣势", lane_stomp: "对线碾压", lane_stomped: "对线被碾", lane_tie: "对线平手", lane_jungle: "野区霸主", analysis_successful: "录像分析成功", analysis_incomplete: "分析结果不完整", analysis_by_opendota: "数据解析自 OpenDota", kill: "击杀", kill_contribution_: "参战率:{0}", position: "位置", position_: "位置:", position_1: "优势路", position_2: "中路", position_3: "烈士路", position_4: "采灵芝", position_5: "工具人", position_undefined: "?", total_damage: "总伤害", total_gold: "总经济", total_experience: "总经验", radiant_won: "天辉获胜", dire_won: "夜魇获胜", duration_: "持续时间:{0}", region_: "地区:{0}", match_count_: "场次:", last25matches_: "最近25场:", winrate_: "胜率:", imp_: "表现:", lane_advantage_rate_: "线优率:", top10_: "全期场次前十的英雄:", hero: "英雄", all_matches_: "全期场次:", match_count: "场次", winrate: "胜率", imp: "表现", win_count: "胜场", lose_count: "败场", recently_heroes: "近期使用场次大于1的英雄:", recently_positions: "近25场各个位置的表现:", winning_streak: "连胜", losing_streak: "连败", id: "ID", mode: "模式", kda_kc: "KDA(参战率)", time: "时间", duration: "时长", rank: "段位", un_parsed: "(未解析)", combined_win_loss_summary: "组合胜负情况:", yesterdays_summary: "昨日总结", last_weeks_summary: "上周总结", report_won: "胜", report_lost: "负", report_winrate: "胜率", anonymous_player_1: "数据未公开", anonymous_player_2: "背景仅供展示目的,不属于{player}的数据。", rank_fun_up_message: "热烈祝贺群友 {avatar}{name} 在天梯中再获进步,<br/>由 {prev} 升为 {curr},再接再厉,再创辉煌!", rank_fun_down_message: "{avatar}<br/>寄", titles: { MVP: "MVP-#FFA500", Soul: "魂-#66CCFF", Rich: "富-#FFD700", Wise: "睿-#8888FF", Controller: "控-#FF00FF", Nuker: "爆-#CC0088", Breaker: "破-#DD0000", Ghost: "鬼-#CCCCCC", Utility: "辅-#20B2AA", Assister: "助-#006400", Demolisher: "拆-#FEDCBA", Healer: "奶-#00FF00", Tank: "耐-#84A1C7", Idle: "摸-#DDDDDD" }, situation: "局势", networth: "经济", experience: "经验", OUTCOME_MAP: { RADIANT_VICTORY: "天辉优势", RADIANT_STOMP: "天辉碾压", DIRE_VICTORY: "夜魇优势", DIRE_STOMP: "夜魇碾压", TIE: "势均力敌" }, lane_top: "上路", lane_mid: "中路", lane_bottom: "下路", empty_extra_info: "比赛未解析或信息缺失,无法展示更多数据。" } } };
|
|
812
812
|
}
|
|
813
813
|
});
|
|
814
814
|
|
|
815
815
|
// src/locales/zh-CN.yml
|
|
816
816
|
var require_zh_CN = __commonJS({
|
|
817
817
|
"src/locales/zh-CN.yml"(exports2, module2) {
|
|
818
|
-
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": '"凯", "鸟人"' }, 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: "
|
|
818
|
+
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": '"凯", "鸟人"' }, 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", stratz_api_query_error: "Stratz API 返回了有效数据,但报错: {cause}", opendota_parse_request_on_later: "已启用 OpenDota 备用查询,将每{timeout}分钟发送一次 OpenDota 解析请求,并每分钟检测比赛是否已被 OpenDota 解析。", opendota_parse_request_sent: "比赛 {matchId} 解析请求已成功发送至 OpenDota 服务器。", opendota_parse_request_failed: "比赛 {matchId} 解析请求向 OpenDota 服务器发送失败。" }, time: { years_months_ago: "{years}年{months}个月前", years_ago: "{years}年前" } } };
|
|
819
819
|
}
|
|
820
820
|
});
|
|
821
821
|
|
|
@@ -960,7 +960,7 @@ var I18NService = class extends import_koishi.Service {
|
|
|
960
960
|
render(...args) {
|
|
961
961
|
return this.i18n.render.apply(this.i18n, args);
|
|
962
962
|
}
|
|
963
|
-
$t(languageTag, key, param) {
|
|
963
|
+
$t(languageTag, key, param, options) {
|
|
964
964
|
const keys = Array.isArray(key) ? key : key.split(".");
|
|
965
965
|
const params = Array.isArray(param) ? param : [param];
|
|
966
966
|
const constantTranslation = keys.reduce((result2, k) => {
|
|
@@ -973,11 +973,20 @@ var I18NService = class extends import_koishi.Service {
|
|
|
973
973
|
if (typeof params === "object" && params !== null) {
|
|
974
974
|
return constantTranslation.replace(/\{(\w+)\}/g, (_, key2) => params[key2] || "");
|
|
975
975
|
}
|
|
976
|
-
|
|
976
|
+
let result2 = constantTranslation;
|
|
977
|
+
const targetFormat2 = options?.target || "text";
|
|
978
|
+
if (targetFormat2 === "html") {
|
|
979
|
+
result2 = result2.replace(/\n/g, "<br/>");
|
|
980
|
+
}
|
|
981
|
+
return result2;
|
|
977
982
|
}
|
|
978
983
|
const originalKey = Array.isArray(key) ? key : [key];
|
|
979
984
|
const elements = this.render([languageTag], originalKey, param ?? {});
|
|
980
|
-
|
|
985
|
+
let result = elements.map((el) => el.toString()).join("");
|
|
986
|
+
const targetFormat = options?.target || "text";
|
|
987
|
+
if (targetFormat === "html") {
|
|
988
|
+
result = result.replace(/\n/g, "<br/>");
|
|
989
|
+
}
|
|
981
990
|
if (result == key) return;
|
|
982
991
|
return result;
|
|
983
992
|
}
|
|
@@ -1125,10 +1134,7 @@ var HeroService = class _HeroService extends import_koishi2.Service {
|
|
|
1125
1134
|
hero.winRate = hero.matchCount > 0 ? hero.winCount / hero.matchCount : 0;
|
|
1126
1135
|
});
|
|
1127
1136
|
}
|
|
1128
|
-
|
|
1129
|
-
const endOfDay = now.endOf("day");
|
|
1130
|
-
const ttl = endOfDay.diff(now).toMillis();
|
|
1131
|
-
this.ctx.dota2tracker.cache.setWweeklyMetaCache(cacheKey, weeklyHeroMeta, ttl);
|
|
1137
|
+
this.ctx.dota2tracker.cache.setWweeklyMetaCache(cacheKey, weeklyHeroMeta);
|
|
1132
1138
|
return weeklyHeroMeta;
|
|
1133
1139
|
}
|
|
1134
1140
|
async getHeroDetails(input, languageTag, isRandom = false) {
|
|
@@ -1292,7 +1298,7 @@ var ItemService = class _ItemService extends import_koishi3.Service {
|
|
|
1292
1298
|
}
|
|
1293
1299
|
static getFormattedItemListData(rawItems) {
|
|
1294
1300
|
const processItemName = /* @__PURE__ */ __name((name2) => name2.replace(/^item_/i, "").replace(/^recipe_/i, "recipe_"), "processItemName");
|
|
1295
|
-
const [recipes,
|
|
1301
|
+
const [recipes, items3] = rawItems.reduce(
|
|
1296
1302
|
(acc, item) => {
|
|
1297
1303
|
const processed = {
|
|
1298
1304
|
...item,
|
|
@@ -1306,14 +1312,14 @@ var ItemService = class _ItemService extends import_koishi3.Service {
|
|
|
1306
1312
|
[[], []]
|
|
1307
1313
|
);
|
|
1308
1314
|
const itemMap = /* @__PURE__ */ new Map();
|
|
1309
|
-
|
|
1315
|
+
items3.concat(recipes).forEach(
|
|
1310
1316
|
(item) => itemMap.set(item.id, {
|
|
1311
1317
|
id: item.id,
|
|
1312
1318
|
name: item.name,
|
|
1313
1319
|
name_loc: item.name_loc
|
|
1314
1320
|
})
|
|
1315
1321
|
);
|
|
1316
|
-
const processedItems =
|
|
1322
|
+
const processedItems = items3.map((baseItem) => {
|
|
1317
1323
|
const recipe = recipes.find((r) => r.name === `recipe_${baseItem.name.replace("item_", "")}`);
|
|
1318
1324
|
return {
|
|
1319
1325
|
...baseItem,
|
|
@@ -1346,19 +1352,19 @@ var ItemService = class _ItemService extends import_koishi3.Service {
|
|
|
1346
1352
|
}))
|
|
1347
1353
|
}));
|
|
1348
1354
|
}
|
|
1349
|
-
searchItems(
|
|
1355
|
+
searchItems(items3, keyword, languageTag, config) {
|
|
1350
1356
|
if (!keyword) return [];
|
|
1351
1357
|
const alias = this.ctx.dota2tracker.i18n.getConstantLocale(languageTag).dota2tracker.items_alias?.[keyword] ?? config.customItemAlias.filter((cia) => cia.alias == keyword).map((cia) => cia.keyword);
|
|
1352
|
-
const exactMatch =
|
|
1358
|
+
const exactMatch = items3.filter(
|
|
1353
1359
|
(item) => alias?.some((a) => item.name_loc.trim().toLowerCase() == a.toLowerCase()) || item.name_loc.trim().toLowerCase() === keyword.trim().toLowerCase() || Number.isInteger(Number(keyword)) && item.id === Number(keyword)
|
|
1354
1360
|
);
|
|
1355
1361
|
if (exactMatch.length) return exactMatch;
|
|
1356
|
-
return this.fuzzySearchItems(alias.length ? alias : [keyword],
|
|
1362
|
+
return this.fuzzySearchItems(alias.length ? alias : [keyword], items3);
|
|
1357
1363
|
}
|
|
1358
|
-
fuzzySearchItems(keywords,
|
|
1364
|
+
fuzzySearchItems(keywords, items3) {
|
|
1359
1365
|
const resultMap = /* @__PURE__ */ new Map();
|
|
1360
1366
|
if (!keywords.length) return [];
|
|
1361
|
-
for (const item of
|
|
1367
|
+
for (const item of items3) {
|
|
1362
1368
|
const cleanName = item.name_loc.toLowerCase().replace(/[^\p{L}\p{N}]/gu, "").trim();
|
|
1363
1369
|
let matchAllKeywords = true;
|
|
1364
1370
|
for (const keyword of keywords) {
|
|
@@ -1394,10 +1400,23 @@ var MatchService = class _MatchService extends import_koishi4.Service {
|
|
|
1394
1400
|
static {
|
|
1395
1401
|
__name(this, "MatchService");
|
|
1396
1402
|
}
|
|
1397
|
-
async getMatchResult({
|
|
1403
|
+
async getMatchResult({
|
|
1404
|
+
matchId,
|
|
1405
|
+
requestParse,
|
|
1406
|
+
requsetOpenDota
|
|
1407
|
+
}) {
|
|
1398
1408
|
const matchQuery = await this.getMatchData(matchId);
|
|
1399
1409
|
if (matchQuery) {
|
|
1400
1410
|
if (!_MatchService.isMatchParsed(matchQuery) && requestParse && this.ctx.cron) {
|
|
1411
|
+
if (requsetOpenDota) {
|
|
1412
|
+
const odMatchQuery = await this.getOpenDotaMatchData(matchId);
|
|
1413
|
+
if (odMatchQuery) {
|
|
1414
|
+
return {
|
|
1415
|
+
status: "READY",
|
|
1416
|
+
matchData: odMatchQuery
|
|
1417
|
+
};
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1401
1420
|
return {
|
|
1402
1421
|
status: "PENDING",
|
|
1403
1422
|
matchId
|
|
@@ -1434,14 +1453,29 @@ var MatchService = class _MatchService extends import_koishi4.Service {
|
|
|
1434
1453
|
throw error;
|
|
1435
1454
|
}
|
|
1436
1455
|
}
|
|
1456
|
+
async getOpenDotaMatchData(matchId) {
|
|
1457
|
+
const odMatch = await this.ctx.dota2tracker.opendotaAPI.queryMatchInfo(matchId);
|
|
1458
|
+
if (odMatch?.od_data?.has_parsed) {
|
|
1459
|
+
const odMatchQuery = { match: this.ctx.dota2tracker.opendotaAdapter.transform(odMatch) };
|
|
1460
|
+
this.ctx.dota2tracker.cache.setMatchCache(matchId, odMatchQuery, this.pluginVersion);
|
|
1461
|
+
return odMatchQuery;
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1437
1464
|
async formatMatchData(matchQuery, languageTag) {
|
|
1438
1465
|
try {
|
|
1439
1466
|
let constantsQuery = await this.ctx.dota2tracker.cache.getFacetConstantsCache(languageTag);
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
!constantsQuery
|
|
1443
|
-
|
|
1467
|
+
const isFromOpenDota = matchQuery.match?.odParsed === true;
|
|
1468
|
+
let needsRefetch = false;
|
|
1469
|
+
if (!constantsQuery) {
|
|
1470
|
+
needsRefetch = true;
|
|
1471
|
+
} else if (!isFromOpenDota) {
|
|
1472
|
+
if (!matchQuery.constants?.gameVersions?.[0]?.id || !constantsQuery.constants?.gameVersions?.[0]?.id || matchQuery.constants.gameVersions[0].id !== constantsQuery.constants.gameVersions[0].id) {
|
|
1473
|
+
needsRefetch = true;
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
if (needsRefetch) {
|
|
1444
1477
|
constantsQuery = await this.ctx.dota2tracker.stratzAPI.queryConstants(languageTag);
|
|
1478
|
+
}
|
|
1445
1479
|
const facetData = await _MatchService.constantsInjectFacetData(constantsQuery, matchQuery, languageTag, this.ctx.dota2tracker.hero);
|
|
1446
1480
|
this.ctx.dota2tracker.cache.setFacetConstantsCache(languageTag, constantsQuery);
|
|
1447
1481
|
const match = _MatchService.extendMatchData(matchQuery, facetData);
|
|
@@ -1455,8 +1489,8 @@ var MatchService = class _MatchService extends import_koishi4.Service {
|
|
|
1455
1489
|
const facetData = {};
|
|
1456
1490
|
for (let player of matchQuery.match.players) {
|
|
1457
1491
|
if (player.variant != null) {
|
|
1458
|
-
const constantsFacet = constantsQuery.constants.facets.find((facet) => facet.id
|
|
1459
|
-
let displayName = constantsFacet
|
|
1492
|
+
const constantsFacet = constantsQuery.constants.facets.find((facet) => facet.id === player.hero.facets[player.variant - 1]?.facetId || facet.name === player.hero.facets[player.variant - 1]?.name);
|
|
1493
|
+
let displayName = constantsFacet?.language?.displayName;
|
|
1460
1494
|
if (!displayName && heroService) {
|
|
1461
1495
|
const valveFacet = (await heroService.getHeroDetails(player.hero.id, languageTag)).facets.find((facet) => facet.index === player.variant - 1);
|
|
1462
1496
|
constantsFacet.language.displayName = valveFacet.title_loc;
|
|
@@ -1706,8 +1740,8 @@ var MatchService = class _MatchService extends import_koishi4.Service {
|
|
|
1706
1740
|
match.durationTime = sec2time(match.durationSeconds);
|
|
1707
1741
|
return match;
|
|
1708
1742
|
}
|
|
1709
|
-
static isMatchParsed(
|
|
1710
|
-
return match
|
|
1743
|
+
static isMatchParsed(matchQuery) {
|
|
1744
|
+
return matchQuery?.match?.parsedDateTime && matchQuery?.match?.players.filter((player) => player?.stats?.heroDamageReport?.dealtTotal).length > 0;
|
|
1711
1745
|
}
|
|
1712
1746
|
};
|
|
1713
1747
|
function createItemObject(itemId, purchaseTimesMap, purchaseTimeIndices) {
|
|
@@ -2031,6 +2065,7 @@ __name(validateRank, "validateRank");
|
|
|
2031
2065
|
|
|
2032
2066
|
// src/app/data/cache.ts
|
|
2033
2067
|
var import_koishi6 = require("koishi");
|
|
2068
|
+
var import_luxon4 = require("luxon");
|
|
2034
2069
|
var CacheService = class extends import_koishi6.Service {
|
|
2035
2070
|
static {
|
|
2036
2071
|
__name(this, "CacheService");
|
|
@@ -2038,8 +2073,24 @@ var CacheService = class extends import_koishi6.Service {
|
|
|
2038
2073
|
constructor(ctx) {
|
|
2039
2074
|
super(ctx, "dota2tracker.cache", true);
|
|
2040
2075
|
}
|
|
2041
|
-
|
|
2042
|
-
|
|
2076
|
+
get msUntilUTCEndOfDay() {
|
|
2077
|
+
const now = import_luxon4.DateTime.utc();
|
|
2078
|
+
const endOfDay = now.endOf("day");
|
|
2079
|
+
const ttl = endOfDay.diff(now).toMillis();
|
|
2080
|
+
return ttl;
|
|
2081
|
+
}
|
|
2082
|
+
addOpendotaAPIRequestLog(request, count = 1) {
|
|
2083
|
+
this.ctx.cache.set("dt_opendota_api_request_log", String(Date.now()), { count, request }, this.msUntilUTCEndOfDay);
|
|
2084
|
+
}
|
|
2085
|
+
async getTodayOpendotaAPIRequestCount() {
|
|
2086
|
+
let count = 0;
|
|
2087
|
+
for await (const value of this.ctx.cache.values("dt_opendota_api_request_log")) {
|
|
2088
|
+
count += value.count;
|
|
2089
|
+
}
|
|
2090
|
+
return count;
|
|
2091
|
+
}
|
|
2092
|
+
setWweeklyMetaCache(key, value) {
|
|
2093
|
+
this.ctx.cache.set("dt_weekly_metadata", key, value, this.msUntilUTCEndOfDay);
|
|
2043
2094
|
}
|
|
2044
2095
|
async getWeeklyMetaCache(key) {
|
|
2045
2096
|
return this.ctx.cache.get("dt_weekly_metadata", key);
|
|
@@ -2467,7 +2518,7 @@ var ImageFormat = /* @__PURE__ */ ((ImageFormat2) => {
|
|
|
2467
2518
|
})(ImageFormat || {});
|
|
2468
2519
|
|
|
2469
2520
|
// src/app/presentation/image.renderer.ts
|
|
2470
|
-
var
|
|
2521
|
+
var import_luxon5 = require("luxon");
|
|
2471
2522
|
var ImageRenderer = class extends import_koishi10.Service {
|
|
2472
2523
|
constructor(ctx, pluginDir3) {
|
|
2473
2524
|
super(ctx, "dota2tracker.image", true);
|
|
@@ -2485,13 +2536,16 @@ var ImageRenderer = class extends import_koishi10.Service {
|
|
|
2485
2536
|
const html = await this.generateHTML(data, { source: "CODE", code: ejsCode }, languageTag);
|
|
2486
2537
|
return this.ctx.puppeteer.render(html);
|
|
2487
2538
|
}
|
|
2539
|
+
async renderToImageByHTML(html) {
|
|
2540
|
+
return this.ctx.puppeteer.render(html);
|
|
2541
|
+
}
|
|
2488
2542
|
async generateHTML(data, template, languageTag) {
|
|
2489
2543
|
const templateData = {
|
|
2490
2544
|
data,
|
|
2491
2545
|
ImageType,
|
|
2492
2546
|
ImageFormat,
|
|
2493
2547
|
dotaconstants: dotaconstants5,
|
|
2494
|
-
DateTime:
|
|
2548
|
+
DateTime: import_luxon5.DateTime,
|
|
2495
2549
|
$t: /* @__PURE__ */ __name((key, params) => this.ctx.dota2tracker.i18n.$t(languageTag, key, params), "$t"),
|
|
2496
2550
|
languageTag,
|
|
2497
2551
|
Random: import_koishi10.Random,
|
|
@@ -2535,7 +2589,7 @@ var ImageRenderer = class extends import_koishi10.Service {
|
|
|
2535
2589
|
|
|
2536
2590
|
// src/app/presentation/message.builder.ts
|
|
2537
2591
|
var import_koishi11 = require("koishi");
|
|
2538
|
-
var
|
|
2592
|
+
var import_luxon6 = require("luxon");
|
|
2539
2593
|
var MessageBuilder = class extends import_koishi11.Service {
|
|
2540
2594
|
static {
|
|
2541
2595
|
__name(this, "MessageBuilder");
|
|
@@ -2544,6 +2598,67 @@ var MessageBuilder = class extends import_koishi11.Service {
|
|
|
2544
2598
|
super(ctx, "dota2tracker.message-builder", true);
|
|
2545
2599
|
this.config = ctx.config;
|
|
2546
2600
|
}
|
|
2601
|
+
async buildHelpMessage(languageTag, pluginName) {
|
|
2602
|
+
let message = "";
|
|
2603
|
+
let html = "";
|
|
2604
|
+
const $t_header = /* @__PURE__ */ __name((key) => this.ctx.dota2tracker.i18n.$t(languageTag, `commands.dota2tracker.help.messages.table_headers.${key}`), "$t_header");
|
|
2605
|
+
const header = [
|
|
2606
|
+
{ content: $t_header("command") },
|
|
2607
|
+
{ content: $t_header("alias") },
|
|
2608
|
+
{ content: $t_header("arguments") },
|
|
2609
|
+
{ content: $t_header("description") },
|
|
2610
|
+
{ content: $t_header("options") },
|
|
2611
|
+
{ content: $t_header("examples") }
|
|
2612
|
+
];
|
|
2613
|
+
const table = [header];
|
|
2614
|
+
const rootCommand = this.ctx.$commander.get(pluginName);
|
|
2615
|
+
const commandList = rootCommand?.children;
|
|
2616
|
+
for (const command of commandList) {
|
|
2617
|
+
const names = Object.keys(command._aliases);
|
|
2618
|
+
const localeKey = "commands." + names.at(0);
|
|
2619
|
+
const optionsFromCommand = command._options;
|
|
2620
|
+
const localeData = this.ctx.i18n._data[languageTag];
|
|
2621
|
+
const formattedOptions = [];
|
|
2622
|
+
if (optionsFromCommand) {
|
|
2623
|
+
for (const optionKey in optionsFromCommand) {
|
|
2624
|
+
const i18nKey = `commands.${command.name}.options.${optionKey}`;
|
|
2625
|
+
let description = localeData[i18nKey];
|
|
2626
|
+
const optionInfo = optionsFromCommand[optionKey];
|
|
2627
|
+
if (optionInfo && description) {
|
|
2628
|
+
const syntax = optionInfo.syntax;
|
|
2629
|
+
const flagsRegex = /-{1,2}[a-zA-Z-]+/g;
|
|
2630
|
+
const flags = syntax.match(flagsRegex);
|
|
2631
|
+
if (flags) {
|
|
2632
|
+
for (const flag of flags) {
|
|
2633
|
+
if (description.startsWith(flag + " ")) {
|
|
2634
|
+
description = description.substring(flag.length + 1);
|
|
2635
|
+
break;
|
|
2636
|
+
}
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
formattedOptions.push({
|
|
2640
|
+
// 现在 description 已经是被清理过的干净版本了
|
|
2641
|
+
syntax: `[${syntax}]`,
|
|
2642
|
+
description
|
|
2643
|
+
});
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2646
|
+
}
|
|
2647
|
+
const row = [
|
|
2648
|
+
{ content: command.name },
|
|
2649
|
+
{ content: names.slice(1).join("\n") },
|
|
2650
|
+
{ content: command._arguments["stripped"] },
|
|
2651
|
+
{ content: this.ctx.dota2tracker.i18n.$t(languageTag, localeKey + ".usage") },
|
|
2652
|
+
{ content: formattedOptions.map((option) => `${option.syntax} ${option.description}`).join("\n") },
|
|
2653
|
+
{ content: this.ctx.dota2tracker.i18n.$t(languageTag, localeKey + ".examples") }
|
|
2654
|
+
];
|
|
2655
|
+
table.push(row);
|
|
2656
|
+
}
|
|
2657
|
+
html += this._createTableHTML(table, void 0, this.ctx.i18n._data[languageTag]["commands.dota2tracker.help.messages.header"]);
|
|
2658
|
+
message += await this.ctx.dota2tracker.image.renderToImageByEJSCode(void 0, html, languageTag);
|
|
2659
|
+
message += this.ctx.dota2tracker.i18n.$t(languageTag, "commands.dota2tracker.help.messages.footer");
|
|
2660
|
+
return message;
|
|
2661
|
+
}
|
|
2547
2662
|
async buildHeroOfTheDayMessage(languageTag, heroRcmd, metaRcmd) {
|
|
2548
2663
|
const $t = /* @__PURE__ */ __name((key, params) => this.ctx.dota2tracker.i18n.$t(languageTag, `commands.dota2tracker.hero-of-the-day.messages.${key}`, params), "$t");
|
|
2549
2664
|
let ejs2 = "<html><head><style>body{width:fit-content;height:fit-content;margin:0;padding:12px;font-family:<%-fontFamily%>;}</style></head><body>";
|
|
@@ -2692,7 +2807,7 @@ var MessageBuilder = class extends import_koishi11.Service {
|
|
|
2692
2807
|
`;
|
|
2693
2808
|
for (const member of members) {
|
|
2694
2809
|
const winRate = typeof member.winRate === "number" && !isNaN(member.winRate) ? `${roundToDecimalPlaces(member.winRate * 100).toFixed(1)}%` : "-----";
|
|
2695
|
-
const lastMatch = member.lastMatchTime ? formatCustomRelativeTime(
|
|
2810
|
+
const lastMatch = member.lastMatchTime ? formatCustomRelativeTime(import_luxon6.DateTime.fromSeconds(member.lastMatchTime), this.ctx.dota2tracker.i18n, languageTag) : "----------";
|
|
2696
2811
|
ejs2 += `
|
|
2697
2812
|
<tr>
|
|
2698
2813
|
<td>${member.name}</td>
|
|
@@ -2716,6 +2831,40 @@ var MessageBuilder = class extends import_koishi11.Service {
|
|
|
2716
2831
|
curr: { medal: this.ctx.dota2tracker.i18n.$t(languageTag, "dota2tracker.template.ranks." + currRank.medal), star: currRank.star }
|
|
2717
2832
|
});
|
|
2718
2833
|
}
|
|
2834
|
+
_createTableHTML(data, totalAlign = "left", header) {
|
|
2835
|
+
const maxLength = data.reduce((max, row) => Math.max(max, row.length), 0);
|
|
2836
|
+
let html = `<html><head><style>body{width:fit-content;height:fit-content;margin:0;padding:12px;font-family:${this.config.templateFonts};}.table div:not(.row){background-color: #fff;padding: 4px;}</style></head><body>`;
|
|
2837
|
+
if (header) html += `<header>${escapeHtml(header)}</header>`;
|
|
2838
|
+
html += `<div class="table" style="display:grid;grid-template-columns:repeat(${maxLength},auto);background-color:#000;gap:1px;border:#000 solid 1px;text-align:${totalAlign}">`;
|
|
2839
|
+
for (const row of data) {
|
|
2840
|
+
html += `<div class="row" style="display:contents;">`;
|
|
2841
|
+
for (const cell of row) {
|
|
2842
|
+
const style = {
|
|
2843
|
+
...cell.align ? { "text-align": cell.align } : {},
|
|
2844
|
+
...cell.colSpan ? { "grid-column": "span " + cell.colSpan } : {},
|
|
2845
|
+
...cell.rowSpan ? { "grid-row": "span " + cell.rowSpan } : {}
|
|
2846
|
+
};
|
|
2847
|
+
const styleStr = Object.entries(style).map(([key, value]) => `${key}:${value}`).join(";");
|
|
2848
|
+
const styleAttribute = styleStr ? ` style="${styleStr}"` : "";
|
|
2849
|
+
html += `<div${styleAttribute}>${escapeHtml(cell.content)}</div>`;
|
|
2850
|
+
}
|
|
2851
|
+
html += `</div>`;
|
|
2852
|
+
}
|
|
2853
|
+
html += `</div></body></html>`;
|
|
2854
|
+
return html;
|
|
2855
|
+
function escapeHtml(str) {
|
|
2856
|
+
return (str || "").replace(/[&<>"']/g, function(match) {
|
|
2857
|
+
return {
|
|
2858
|
+
"&": "&",
|
|
2859
|
+
"<": "<",
|
|
2860
|
+
">": ">",
|
|
2861
|
+
'"': """,
|
|
2862
|
+
"'": "'"
|
|
2863
|
+
}[match];
|
|
2864
|
+
}).replace(/\n/g, "<br/>");
|
|
2865
|
+
}
|
|
2866
|
+
__name(escapeHtml, "escapeHtml");
|
|
2867
|
+
}
|
|
2719
2868
|
};
|
|
2720
2869
|
function customConvertArrayOfString(str) {
|
|
2721
2870
|
try {
|
|
@@ -2728,7 +2877,7 @@ __name(customConvertArrayOfString, "customConvertArrayOfString");
|
|
|
2728
2877
|
|
|
2729
2878
|
// src/app/tasks/match-watcher.task.ts
|
|
2730
2879
|
var import_koishi12 = require("koishi");
|
|
2731
|
-
var
|
|
2880
|
+
var import_luxon7 = require("luxon");
|
|
2732
2881
|
var MatchWatcherTask = class extends import_koishi12.Service {
|
|
2733
2882
|
static {
|
|
2734
2883
|
__name(this, "MatchWatcherTask");
|
|
@@ -2750,7 +2899,7 @@ var MatchWatcherTask = class extends import_koishi12.Service {
|
|
|
2750
2899
|
}
|
|
2751
2900
|
async discoverNewMatches(playersData, activePlayers) {
|
|
2752
2901
|
const sendedMatchIds = await this.ctx.dota2tracker.cache.getSendedMatchIds();
|
|
2753
|
-
const lastMatches = playersData.map((player) => player.matches[0]).filter((match) => match && match.id).filter((item, index, self) => index === self.findIndex((t) => t.id === item.id)).filter((match) =>
|
|
2902
|
+
const lastMatches = playersData.map((player) => player.matches[0]).filter((match) => match && match.id).filter((item, index, self) => index === self.findIndex((t) => t.id === item.id)).filter((match) => import_luxon7.DateTime.fromSeconds(match.startDateTime) > import_luxon7.DateTime.now().minus({ days: 1 })).filter((match) => !this.ctx.dota2tracker.parsePolling.isPending(match.id)).filter((match) => !sendedMatchIds.has(match.id));
|
|
2754
2903
|
for (const match of lastMatches) {
|
|
2755
2904
|
const steamIdsInMatch = new Set(match.players.map((p) => p.steamAccount.id));
|
|
2756
2905
|
const relevantActivePlayers = activePlayers.filter((p) => steamIdsInMatch.has(p.steamId));
|
|
@@ -2829,7 +2978,7 @@ var MatchWatcherTask = class extends import_koishi12.Service {
|
|
|
2829
2978
|
isRising: rankMap.get(subPlayer.steamId).rank > subPlayer.rank.rank || rankMap.get(subPlayer.steamId).rank == subPlayer.rank.rank && rankMap.get(subPlayer.steamId).leader < subPlayer.rank.leader || rankMap.get(subPlayer.steamId).leader > 0 && subPlayer.rank.leader == null,
|
|
2830
2979
|
prevRank,
|
|
2831
2980
|
currRank,
|
|
2832
|
-
date:
|
|
2981
|
+
date: import_luxon7.DateTime.now().toFormat(languageTag === "zh-CN" ? "yyyy/MM/dd HH时mm分" : "yyyy/MM/dd HH:mm")
|
|
2833
2982
|
},
|
|
2834
2983
|
"rank_fun",
|
|
2835
2984
|
"rank" /* Rank */,
|
|
@@ -2853,7 +3002,7 @@ var MatchWatcherTask = class extends import_koishi12.Service {
|
|
|
2853
3002
|
|
|
2854
3003
|
// src/app/tasks/parse-polling.task.ts
|
|
2855
3004
|
var import_koishi13 = require("koishi");
|
|
2856
|
-
var
|
|
3005
|
+
var import_luxon8 = require("luxon");
|
|
2857
3006
|
var ParsePollingTask = class extends import_koishi13.Service {
|
|
2858
3007
|
static {
|
|
2859
3008
|
__name(this, "ParsePollingTask");
|
|
@@ -2895,7 +3044,12 @@ var ParsePollingTask = class extends import_koishi13.Service {
|
|
|
2895
3044
|
entry.subscribers.push(...subscribers);
|
|
2896
3045
|
this.pendingMatches.set(matchId, entry);
|
|
2897
3046
|
if (isNewEntry) {
|
|
2898
|
-
this.ctx.dota2tracker.stratzAPI.requestParseMatch(matchId).then((value) =>
|
|
3047
|
+
this.ctx.dota2tracker.stratzAPI.requestParseMatch(matchId).then((value) => {
|
|
3048
|
+
this.logger.info(this.ctx.dota2tracker.i18n.gt(`dota2tracker.logger.parse_request_${value ? "sent" : "failed"}`, { matchId }));
|
|
3049
|
+
if (this.config.enableOpenDotaFallback) {
|
|
3050
|
+
this.logger.info(this.ctx.dota2tracker.i18n.gt("dota2tracker.logger.opendota_parse_request_on_later", { timeout: 5 }));
|
|
3051
|
+
}
|
|
3052
|
+
});
|
|
2899
3053
|
}
|
|
2900
3054
|
}
|
|
2901
3055
|
async polling() {
|
|
@@ -2905,10 +3059,18 @@ var ParsePollingTask = class extends import_koishi13.Service {
|
|
|
2905
3059
|
if (this.pollingIndex >= matches.length) this.pollingIndex = 0;
|
|
2906
3060
|
const pendingMatch = matches[this.pollingIndex];
|
|
2907
3061
|
this.pollingIndex++;
|
|
2908
|
-
const
|
|
2909
|
-
const
|
|
2910
|
-
const
|
|
3062
|
+
const requestTime = import_luxon8.DateTime.fromJSDate(pendingMatch.requestTime);
|
|
3063
|
+
const timeout = requestTime.plus({ minutes: this.config.dataParsingTimeoutMinutes });
|
|
3064
|
+
const needToWait = import_luxon8.DateTime.now() < timeout;
|
|
3065
|
+
const result = await this.ctx.dota2tracker.match.getMatchResult({ matchId: pendingMatch.matchId, requestParse: needToWait, requsetOpenDota: this.config.enableOpenDotaFallback });
|
|
2911
3066
|
if (result.status === "PENDING") {
|
|
3067
|
+
const waitingTime = import_luxon8.DateTime.now().diff(requestTime, "minutes");
|
|
3068
|
+
const waitingTimeMinutes = Math.floor(waitingTime.minutes);
|
|
3069
|
+
if (waitingTimeMinutes > 0 && waitingTimeMinutes % 5 === 0) {
|
|
3070
|
+
this.logger.info(this.ctx.dota2tracker.i18n.gt("dota2tracker.logger.waiting_for_parse", { matchId: pendingMatch.matchId, time: waitingTimeMinutes }));
|
|
3071
|
+
if (this.config.enableOpenDotaFallback)
|
|
3072
|
+
this.ctx.dota2tracker.opendotaAPI.requestParseMatch(pendingMatch.matchId).then((value) => this.logger.info(this.ctx.dota2tracker.i18n.gt(`dota2tracker.logger.opendota_parse_request_${value ? "sent" : "failed"}`, { matchId: pendingMatch.matchId })));
|
|
3073
|
+
}
|
|
2912
3074
|
return;
|
|
2913
3075
|
}
|
|
2914
3076
|
if (result.status === "READY") {
|
|
@@ -2941,7 +3103,8 @@ var ParsePollingTask = class extends import_koishi13.Service {
|
|
|
2941
3103
|
this.ctx.dota2tracker.i18n.gt(`dota2tracker.logger.match_${result.matchData.match.parsedDateTime ? "parsed" : "unparsed"}`, {
|
|
2942
3104
|
matchId: result.matchData.match.id,
|
|
2943
3105
|
timeout: this.config.dataParsingTimeoutMinutes,
|
|
2944
|
-
guilds: guildsToLogger
|
|
3106
|
+
guilds: guildsToLogger,
|
|
3107
|
+
odParsed: result.matchData.match["odParsed"]
|
|
2945
3108
|
})
|
|
2946
3109
|
);
|
|
2947
3110
|
this.ctx.dota2tracker.cache.markMatchAsSended(pendingMatch.matchId);
|
|
@@ -2973,7 +3136,7 @@ var ParsePollingTask = class extends import_koishi13.Service {
|
|
|
2973
3136
|
|
|
2974
3137
|
// src/app/tasks/report.task.ts
|
|
2975
3138
|
var import_koishi14 = require("koishi");
|
|
2976
|
-
var
|
|
3139
|
+
var import_luxon9 = require("luxon");
|
|
2977
3140
|
var ReportTask = class extends import_koishi14.Service {
|
|
2978
3141
|
static {
|
|
2979
3142
|
__name(this, "ReportTask");
|
|
@@ -2987,7 +3150,7 @@ var ReportTask = class extends import_koishi14.Service {
|
|
|
2987
3150
|
if (this.config.dailyReportSwitch) {
|
|
2988
3151
|
ctx.cron(`0 ${this.config.dailyReportHours} * * *`, async () => {
|
|
2989
3152
|
try {
|
|
2990
|
-
const oneDayAgo =
|
|
3153
|
+
const oneDayAgo = import_luxon9.DateTime.now().minus({ days: 1 }).toSeconds();
|
|
2991
3154
|
await this.report(oneDayAgo, "dota2tracker.template.yesterdays_summary", this.config.dailyReportShowCombi);
|
|
2992
3155
|
} catch (error) {
|
|
2993
3156
|
handleError(error, this.logger, this.ctx.dota2tracker.i18n, this.config);
|
|
@@ -2997,7 +3160,7 @@ var ReportTask = class extends import_koishi14.Service {
|
|
|
2997
3160
|
if (this.config.weeklyReportSwitch) {
|
|
2998
3161
|
ctx.cron(`0 ${this.config.weeklyReportDayHours[1]} * * ${this.config.weeklyReportDayHours[0]}`, async () => {
|
|
2999
3162
|
try {
|
|
3000
|
-
const oneWeekAgo =
|
|
3163
|
+
const oneWeekAgo = import_luxon9.DateTime.now().minus({ weeks: 1 }).toSeconds();
|
|
3001
3164
|
await this.report(oneWeekAgo, "dota2tracker.template.last_weeks_summary", this.config.weeklyReportShowCombi);
|
|
3002
3165
|
} catch (error) {
|
|
3003
3166
|
handleError(error, this.logger, this.ctx.dota2tracker.i18n, this.config);
|
|
@@ -3110,7 +3273,9 @@ __name(registerSubscibeCommand, "registerSubscibeCommand");
|
|
|
3110
3273
|
// src/app/commands/help.command.ts
|
|
3111
3274
|
function registerHelpCommand(ctx) {
|
|
3112
3275
|
ctx.command("dota2tracker.help").alias("DOTA2指南").alias("DOTA2帮助").alias("DOTA2说明").action(async ({ session }) => {
|
|
3113
|
-
|
|
3276
|
+
const languageTag = await ctx.dota2tracker.i18n.getLanguageTag({ session });
|
|
3277
|
+
const pluginName = "dota2tracker";
|
|
3278
|
+
return ctx.dota2tracker.messageBuilder.buildHelpMessage(languageTag, pluginName);
|
|
3114
3279
|
});
|
|
3115
3280
|
}
|
|
3116
3281
|
__name(registerHelpCommand, "registerHelpCommand");
|
|
@@ -3139,7 +3304,7 @@ async function resolvePlayerAndHandleErrors(ctx, session, input) {
|
|
|
3139
3304
|
__name(resolvePlayerAndHandleErrors, "resolvePlayerAndHandleErrors");
|
|
3140
3305
|
|
|
3141
3306
|
// src/app/commands/hero-of-the-day.command.ts
|
|
3142
|
-
var
|
|
3307
|
+
var import_luxon10 = require("luxon");
|
|
3143
3308
|
function registerHeroOfTheDayCommand(ctx) {
|
|
3144
3309
|
ctx.command("dota2tracker.hero-of-the-day <input_data>").alias("今日英雄").option("days", "-d <value:number>").action(async ({ session, options }, input_data) => {
|
|
3145
3310
|
const steamId = await resolvePlayerAndHandleErrors(ctx, session, input_data);
|
|
@@ -3147,7 +3312,7 @@ function registerHeroOfTheDayCommand(ctx) {
|
|
|
3147
3312
|
const days = clamp(options.days, 1, 180, 30);
|
|
3148
3313
|
const result = await ctx.dota2tracker.stratzAPI.queryPlayerPerformanceForHeroRecommendation({
|
|
3149
3314
|
steamAccountId: steamId,
|
|
3150
|
-
recentDateTime:
|
|
3315
|
+
recentDateTime: import_luxon10.DateTime.now().minus({ days }).toUnixInteger()
|
|
3151
3316
|
});
|
|
3152
3317
|
const recommendationPromise = ctx.dota2tracker.player.getHeroRecommendation(steamId, result.player);
|
|
3153
3318
|
const metaPromise = ctx.dota2tracker.hero.getWeeklyHeroMeta(PlayerService.estimateWeightedRank(result.player));
|
|
@@ -3177,7 +3342,7 @@ __name(registerQueryHeroCommand, "registerQueryHeroCommand");
|
|
|
3177
3342
|
|
|
3178
3343
|
// src/app/commands/query-item.command.ts
|
|
3179
3344
|
function registerQueryItemCommand(ctx) {
|
|
3180
|
-
ctx.command("dota2tracker.query-item").alias("查询物品").action(async ({ session }, input_data) => {
|
|
3345
|
+
ctx.command("dota2tracker.query-item <input_data>").alias("查询物品").action(async ({ session }, input_data) => {
|
|
3181
3346
|
if (!input_data) {
|
|
3182
3347
|
if (ctx.config.showItemListAtTooMuchItems) {
|
|
3183
3348
|
await session.send(session.text(".querying_item"));
|
|
@@ -3251,7 +3416,7 @@ async function handleQueryMatchCommand(ctx, config, session, options, matchId) {
|
|
|
3251
3416
|
if (result.status === "PENDING") {
|
|
3252
3417
|
const subscriber = ctx.dota2tracker.parsePolling.createSubscriberByCommand(session, languageTag, { templateName: options?.template });
|
|
3253
3418
|
ctx.dota2tracker.parsePolling.add(result.matchId, [subscriber]);
|
|
3254
|
-
return session.text(".waiting_for_parse");
|
|
3419
|
+
return session.text("commands.dota2tracker.query-match.messages.waiting_for_parse");
|
|
3255
3420
|
} else if (result.status === "NOT_FOUND") {
|
|
3256
3421
|
return session.text(".query_failed");
|
|
3257
3422
|
} else {
|
|
@@ -3359,8 +3524,412 @@ function registerUserCommand(ctx) {
|
|
|
3359
3524
|
}
|
|
3360
3525
|
__name(registerUserCommand, "registerUserCommand");
|
|
3361
3526
|
|
|
3362
|
-
// src/
|
|
3527
|
+
// src/app/data/opendota.api.ts
|
|
3363
3528
|
var import_koishi15 = require("koishi");
|
|
3529
|
+
var OpenDotaAPI = class extends import_koishi15.Service {
|
|
3530
|
+
static {
|
|
3531
|
+
__name(this, "OpenDotaAPI");
|
|
3532
|
+
}
|
|
3533
|
+
BASE_URL = "https://api.opendota.com/api";
|
|
3534
|
+
constructor(ctx) {
|
|
3535
|
+
super(ctx, "opendota-api", true);
|
|
3536
|
+
this.config = ctx.config;
|
|
3537
|
+
}
|
|
3538
|
+
async queryMatchInfo(matchId) {
|
|
3539
|
+
const path5 = `${this.BASE_URL}/matches/${matchId}`;
|
|
3540
|
+
const data = await this.fetchData("GET", path5);
|
|
3541
|
+
this.ctx.dota2tracker.cache.addOpendotaAPIRequestLog(path5, 1);
|
|
3542
|
+
return data;
|
|
3543
|
+
}
|
|
3544
|
+
async requestParseMatch(matchId) {
|
|
3545
|
+
const path5 = `${this.BASE_URL}/request/${matchId}`;
|
|
3546
|
+
const job = await this.fetchData("POST", path5);
|
|
3547
|
+
this.ctx.dota2tracker.cache.addOpendotaAPIRequestLog(path5, 10);
|
|
3548
|
+
return job;
|
|
3549
|
+
}
|
|
3550
|
+
async fetchData(type, path5, data) {
|
|
3551
|
+
const config = {
|
|
3552
|
+
responseType: "json",
|
|
3553
|
+
proxyAgent: this.config.proxyAddress || void 0
|
|
3554
|
+
};
|
|
3555
|
+
if (this.config.OPENDOTA_API_KEY) {
|
|
3556
|
+
config.headers = {
|
|
3557
|
+
...config.headers,
|
|
3558
|
+
Authorization: `Bearer ${this.config.OPENDOTA_API_KEY}`
|
|
3559
|
+
};
|
|
3560
|
+
}
|
|
3561
|
+
switch (type) {
|
|
3562
|
+
case "GET":
|
|
3563
|
+
return await this.ctx.http.get(path5, config);
|
|
3564
|
+
case "POST":
|
|
3565
|
+
return await this.ctx.http.post(path5, data, config);
|
|
3566
|
+
// POST需要空数据占位
|
|
3567
|
+
default:
|
|
3568
|
+
throw new Error(`Unsupported HTTP method: ${type}`);
|
|
3569
|
+
}
|
|
3570
|
+
}
|
|
3571
|
+
};
|
|
3572
|
+
|
|
3573
|
+
// src/app/core/opendota.adapter.ts
|
|
3574
|
+
var import_koishi16 = require("koishi");
|
|
3575
|
+
var dotaconstants6 = __toESM(require("dotaconstants"));
|
|
3576
|
+
var OpenDotaAdapter = class extends import_koishi16.Service {
|
|
3577
|
+
static {
|
|
3578
|
+
__name(this, "OpenDotaAdapter");
|
|
3579
|
+
}
|
|
3580
|
+
constructor(ctx) {
|
|
3581
|
+
super(ctx, "opendota-adapter", true);
|
|
3582
|
+
}
|
|
3583
|
+
transform(_match) {
|
|
3584
|
+
determinePlayerPositions(_match);
|
|
3585
|
+
const players = [];
|
|
3586
|
+
for (const _player of _match.players) {
|
|
3587
|
+
const player = {
|
|
3588
|
+
steamAccountId: _player.account_id,
|
|
3589
|
+
level: _player.level,
|
|
3590
|
+
variant: _player.hero_variant,
|
|
3591
|
+
leaverStatus: convertLeaverStatus(_player.leaver_status),
|
|
3592
|
+
partyId: _player.party_id,
|
|
3593
|
+
position: convertPosition(_player.calculatedPosition),
|
|
3594
|
+
playerSlot: _player.player_slot,
|
|
3595
|
+
lane: convertLane(_player.lane),
|
|
3596
|
+
imp: determineIMP(_player),
|
|
3597
|
+
kills: _player.kills,
|
|
3598
|
+
deaths: _player.deaths,
|
|
3599
|
+
assists: _player.assists,
|
|
3600
|
+
isRadiant: _player.isRadiant,
|
|
3601
|
+
networth: _player.net_worth,
|
|
3602
|
+
// 因万恶的TypeScript严格类型检查,此处优雅的reduce赋值物品算法无法使用!
|
|
3603
|
+
// ...[0, 1, 2, 3, 4, 5].reduce((acc, i) => {
|
|
3604
|
+
// acc[`item${i}Id`] = _player[`item_${i}`];
|
|
3605
|
+
// if (i < 3) acc[`backpack${i}Id`] = _player[`backpack_${i}`];
|
|
3606
|
+
// return acc;
|
|
3607
|
+
// }, {}),
|
|
3608
|
+
item0Id: _player.item_0,
|
|
3609
|
+
item1Id: _player.item_1,
|
|
3610
|
+
item2Id: _player.item_2,
|
|
3611
|
+
item3Id: _player.item_3,
|
|
3612
|
+
item4Id: _player.item_4,
|
|
3613
|
+
item5Id: _player.item_5,
|
|
3614
|
+
backpack0Id: _player.backpack_0,
|
|
3615
|
+
backpack1Id: _player.backpack_1,
|
|
3616
|
+
backpack2Id: _player.backpack_2,
|
|
3617
|
+
neutral0Id: _player.item_neutral,
|
|
3618
|
+
// _player.item_neutral2 是一个因stratz不再更新而缺失的字段,而opendota更新了的功能:中立物品的附魔。在此记录以留待未来考量决定是否使用。
|
|
3619
|
+
heroDamage: _player.hero_damage,
|
|
3620
|
+
towerDamage: _player.tower_damage,
|
|
3621
|
+
numLastHits: _player.last_hits,
|
|
3622
|
+
numDenies: _player.denies,
|
|
3623
|
+
goldPerMinute: _player.gold_per_min,
|
|
3624
|
+
experiencePerMinute: _player.xp_per_min,
|
|
3625
|
+
heroHealing: _player.hero_healing,
|
|
3626
|
+
isRandom: _player.randomed,
|
|
3627
|
+
steamAccount: { name: _player.personaname, seasonRank: _player.rank_tier, seasonLeaderboardRank: null },
|
|
3628
|
+
hero: {
|
|
3629
|
+
id: _player.hero_id,
|
|
3630
|
+
name: dotaconstants6.heroes[_player.hero_id].name,
|
|
3631
|
+
shortName: dotaconstants6.heroes[_player.hero_id].name.match(/^npc_dota_hero_(.+)$/)[1],
|
|
3632
|
+
facets: [...dotaconstants6.hero_abilities[dotaconstants6.heroes[_player.hero_id].name].facets.map((f) => ({ id: -1, name: f.name }))]
|
|
3633
|
+
},
|
|
3634
|
+
dotaPlus: null,
|
|
3635
|
+
stats: {
|
|
3636
|
+
networthPerMinute: _player.gold_t,
|
|
3637
|
+
experiencePerMinute: _player.xp_t,
|
|
3638
|
+
campStack: [_player.camps_stacked],
|
|
3639
|
+
matchPlayerBuffEvent: [],
|
|
3640
|
+
killEvents: [],
|
|
3641
|
+
deathEvents: [],
|
|
3642
|
+
assistEvents: [],
|
|
3643
|
+
heroDamageReport: {
|
|
3644
|
+
receivedTotal: {
|
|
3645
|
+
// 由于opendota获取不到伤害类型,此处只能粗暴地归一化。
|
|
3646
|
+
physicalDamage: Object.entries(_player.damage_taken).filter(([source, damage]) => source.startsWith("npc_dota_hero_")).reduce((total, [source, damage]) => total + damage, 0),
|
|
3647
|
+
magicalDamage: 0,
|
|
3648
|
+
pureDamage: 0
|
|
3649
|
+
},
|
|
3650
|
+
dealtTotal: {
|
|
3651
|
+
// 同理,opendota只能获取到眩晕相关数据。
|
|
3652
|
+
// 据测stratz的控制时间单位为10毫秒,此处乘以100来模拟。
|
|
3653
|
+
stunDuration: _player.stuns * 100,
|
|
3654
|
+
stunCount: _player.stuns > 0 ? 1 : 0,
|
|
3655
|
+
slowDuration: 0,
|
|
3656
|
+
slowCount: 0,
|
|
3657
|
+
disableDuration: 0,
|
|
3658
|
+
disableCount: 0
|
|
3659
|
+
}
|
|
3660
|
+
},
|
|
3661
|
+
itemPurchases: _player.purchase_log.map((p) => ({ time: p.time, itemId: dotaconstants6.items[p.key].id }))
|
|
3662
|
+
},
|
|
3663
|
+
additionalUnit: null
|
|
3664
|
+
};
|
|
3665
|
+
if (_player.additional_units) {
|
|
3666
|
+
const additionalUnit = _player.additional_units[0];
|
|
3667
|
+
player.additionalUnit = {
|
|
3668
|
+
// ...[0, 1, 2, 3, 4, 5].reduce((acc, i) => {
|
|
3669
|
+
// acc[`item${i}Id`] = additionalUnit[`item_${i}`];
|
|
3670
|
+
// if (i < 3) acc[`backpack${i}Id`] = additionalUnit[`backpack_${i}`];
|
|
3671
|
+
// return acc;
|
|
3672
|
+
// }, {}),
|
|
3673
|
+
item0Id: additionalUnit.item_0,
|
|
3674
|
+
item1Id: additionalUnit.item_1,
|
|
3675
|
+
item2Id: additionalUnit.item_2,
|
|
3676
|
+
item3Id: additionalUnit.item_3,
|
|
3677
|
+
item4Id: additionalUnit.item_4,
|
|
3678
|
+
item5Id: additionalUnit.item_5,
|
|
3679
|
+
backpack0Id: additionalUnit.backpack_0,
|
|
3680
|
+
backpack1Id: additionalUnit.backpack_1,
|
|
3681
|
+
backpack2Id: additionalUnit.backpack_2,
|
|
3682
|
+
neutral0Id: additionalUnit.item_neutral
|
|
3683
|
+
};
|
|
3684
|
+
}
|
|
3685
|
+
players.push(player);
|
|
3686
|
+
}
|
|
3687
|
+
const match = {
|
|
3688
|
+
id: _match.match_id,
|
|
3689
|
+
didRadiantWin: _match.radiant_win,
|
|
3690
|
+
lobbyType: convertLobbyType(_match.lobby_type),
|
|
3691
|
+
gameMode: convertGameMode(_match.game_mode),
|
|
3692
|
+
regionId: _match.region,
|
|
3693
|
+
parsedDateTime: _match.start_time + _match.duration,
|
|
3694
|
+
startDateTime: _match.start_time,
|
|
3695
|
+
endDateTime: _match.start_time + _match.duration,
|
|
3696
|
+
rank: (({ sum, count }) => count ? sum / count : 0)(_match.players.reduce((acc, player) => player.rank_tier != null ? { sum: acc.sum + player.rank_tier, count: acc.count + 1 } : acc, { sum: 0, count: 0 })),
|
|
3697
|
+
actualRank: 0,
|
|
3698
|
+
averageRank: 0,
|
|
3699
|
+
durationSeconds: _match.duration,
|
|
3700
|
+
topLaneOutcome: null,
|
|
3701
|
+
midLaneOutcome: null,
|
|
3702
|
+
bottomLaneOutcome: null,
|
|
3703
|
+
...determineLaneOutcome(_match),
|
|
3704
|
+
radiantKills: [_match.radiant_score],
|
|
3705
|
+
direKills: [_match.dire_score],
|
|
3706
|
+
radiantNetworthLeads: _match.radiant_gold_adv,
|
|
3707
|
+
radiantExperienceLeads: _match.radiant_xp_adv,
|
|
3708
|
+
winRates: null,
|
|
3709
|
+
players,
|
|
3710
|
+
pickBans: _match.picks_bans.map((pb) => ({ isPick: pb.is_pick, ...pb.is_pick ? { heroId: pb.hero_id, bannedHeroId: null } : { bannedHeroId: pb.hero_id, heroId: null }, order: pb.order })),
|
|
3711
|
+
odParsed: true
|
|
3712
|
+
};
|
|
3713
|
+
return match;
|
|
3714
|
+
}
|
|
3715
|
+
};
|
|
3716
|
+
function convertLeaverStatus(openDotaStatus) {
|
|
3717
|
+
switch (openDotaStatus) {
|
|
3718
|
+
case 0:
|
|
3719
|
+
return "NONE" /* None */;
|
|
3720
|
+
case 1:
|
|
3721
|
+
return "DISCONNECTED_TOO_LONG" /* DisconnectedTooLong */;
|
|
3722
|
+
case 2:
|
|
3723
|
+
return "ABANDONED" /* Abandoned */;
|
|
3724
|
+
case 3:
|
|
3725
|
+
return "AFK" /* Afk */;
|
|
3726
|
+
case 4:
|
|
3727
|
+
return "NEVER_CONNECTED" /* NeverConnected */;
|
|
3728
|
+
case 5:
|
|
3729
|
+
return "NEVER_CONNECTED_TOO_LONG" /* NeverConnectedTooLong */;
|
|
3730
|
+
case 6:
|
|
3731
|
+
return "FAILED_TO_READY_UP" /* FailedToReadyUp */;
|
|
3732
|
+
default:
|
|
3733
|
+
return "NONE" /* None */;
|
|
3734
|
+
}
|
|
3735
|
+
}
|
|
3736
|
+
__name(convertLeaverStatus, "convertLeaverStatus");
|
|
3737
|
+
function convertLane(openDotaLane) {
|
|
3738
|
+
switch (openDotaLane) {
|
|
3739
|
+
case 1:
|
|
3740
|
+
return "SAFE_LANE" /* SafeLane */;
|
|
3741
|
+
case 2:
|
|
3742
|
+
return "MID_LANE" /* MidLane */;
|
|
3743
|
+
case 3:
|
|
3744
|
+
return "OFF_LANE" /* OffLane */;
|
|
3745
|
+
case 4:
|
|
3746
|
+
return "JUNGLE" /* Jungle */;
|
|
3747
|
+
case 5:
|
|
3748
|
+
return "ROAMING" /* Roaming */;
|
|
3749
|
+
default:
|
|
3750
|
+
return "UNKNOWN" /* Unknown */;
|
|
3751
|
+
}
|
|
3752
|
+
}
|
|
3753
|
+
__name(convertLane, "convertLane");
|
|
3754
|
+
function convertPosition(openDotaPosition) {
|
|
3755
|
+
switch (openDotaPosition) {
|
|
3756
|
+
case 1:
|
|
3757
|
+
return "POSITION_1" /* Position_1 */;
|
|
3758
|
+
case 2:
|
|
3759
|
+
return "POSITION_2" /* Position_2 */;
|
|
3760
|
+
case 3:
|
|
3761
|
+
return "POSITION_3" /* Position_3 */;
|
|
3762
|
+
case 4:
|
|
3763
|
+
return "POSITION_4" /* Position_4 */;
|
|
3764
|
+
case 5:
|
|
3765
|
+
return "POSITION_5" /* Position_5 */;
|
|
3766
|
+
default:
|
|
3767
|
+
return "UNKNOWN" /* Unknown */;
|
|
3768
|
+
}
|
|
3769
|
+
}
|
|
3770
|
+
__name(convertPosition, "convertPosition");
|
|
3771
|
+
function convertLobbyType(openDotaLobbyType) {
|
|
3772
|
+
const map = {
|
|
3773
|
+
lobby_type_normal: "UNRANKED" /* Unranked */,
|
|
3774
|
+
lobby_type_practice: "PRACTICE" /* Practice */,
|
|
3775
|
+
lobby_type_tournament: "TOURNAMENT" /* Tournament */,
|
|
3776
|
+
lobby_type_tutorial: "TUTORIAL" /* Tutorial */,
|
|
3777
|
+
lobby_type_coop_bots: "COOP_VS_BOTS" /* CoopVsBots */,
|
|
3778
|
+
lobby_type_ranked_team_mm: "TEAM_MATCH" /* TeamMatch */,
|
|
3779
|
+
lobby_type_ranked_solo_mm: "SOLO_QUEUE" /* SoloQueue */,
|
|
3780
|
+
lobby_type_ranked: "RANKED" /* Ranked */,
|
|
3781
|
+
lobby_type_1v1_mid: "SOLO_MID" /* SoloMid */,
|
|
3782
|
+
lobby_type_battle_cup: "BATTLE_CUP" /* BattleCup */,
|
|
3783
|
+
lobby_type_event: "EVENT" /* Event */,
|
|
3784
|
+
lobby_type_gauntlet: "EVENT" /* Event */,
|
|
3785
|
+
lobby_type_new_player: "COOP_VS_BOTS" /* CoopVsBots */,
|
|
3786
|
+
lobby_type_featured: "EVENT" /* Event */
|
|
3787
|
+
};
|
|
3788
|
+
return map[dotaconstants6.lobby_type[openDotaLobbyType].name] || "EVENT" /* Event */;
|
|
3789
|
+
}
|
|
3790
|
+
__name(convertLobbyType, "convertLobbyType");
|
|
3791
|
+
function convertGameMode(openDotaGameModeId) {
|
|
3792
|
+
const gameModeName = dotaconstants6.game_mode[openDotaGameModeId]?.name;
|
|
3793
|
+
switch (gameModeName) {
|
|
3794
|
+
case "game_mode_all_pick":
|
|
3795
|
+
return "ALL_PICK" /* AllPick */;
|
|
3796
|
+
case "game_mode_captains_mode":
|
|
3797
|
+
return "CAPTAINS_MODE" /* CaptainsMode */;
|
|
3798
|
+
case "game_mode_random_draft":
|
|
3799
|
+
return "RANDOM_DRAFT" /* RandomDraft */;
|
|
3800
|
+
case "game_mode_single_draft":
|
|
3801
|
+
return "SINGLE_DRAFT" /* SingleDraft */;
|
|
3802
|
+
case "game_mode_all_random":
|
|
3803
|
+
return "ALL_RANDOM" /* AllRandom */;
|
|
3804
|
+
case "game_mode_intro":
|
|
3805
|
+
return "INTRO" /* Intro */;
|
|
3806
|
+
case "game_mode_diretide":
|
|
3807
|
+
return "THE_DIRETIDE" /* TheDiretide */;
|
|
3808
|
+
case "game_mode_reverse_captains_mode":
|
|
3809
|
+
return "REVERSE_CAPTAINS_MODE" /* ReverseCaptainsMode */;
|
|
3810
|
+
case "game_mode_greeviling":
|
|
3811
|
+
return "THE_GREEVILING" /* TheGreeviling */;
|
|
3812
|
+
case "game_mode_tutorial":
|
|
3813
|
+
return "TUTORIAL" /* Tutorial */;
|
|
3814
|
+
case "game_mode_mid_only":
|
|
3815
|
+
return "MID_ONLY" /* MidOnly */;
|
|
3816
|
+
case "game_mode_least_played":
|
|
3817
|
+
return "LEAST_PLAYED" /* LeastPlayed */;
|
|
3818
|
+
case "game_mode_compendium_matchmaking":
|
|
3819
|
+
return "COMPENDIUM_MATCHMAKING" /* CompendiumMatchmaking */;
|
|
3820
|
+
case "game_mode_custom":
|
|
3821
|
+
return "CUSTOM" /* Custom */;
|
|
3822
|
+
case "game_mode_captains_draft":
|
|
3823
|
+
return "CAPTAINS_DRAFT" /* CaptainsDraft */;
|
|
3824
|
+
case "game_mode_balanced_draft":
|
|
3825
|
+
return "BALANCED_DRAFT" /* BalancedDraft */;
|
|
3826
|
+
case "game_mode_ability_draft":
|
|
3827
|
+
return "ABILITY_DRAFT" /* AbilityDraft */;
|
|
3828
|
+
case "game_mode_event":
|
|
3829
|
+
return "EVENT" /* Event */;
|
|
3830
|
+
case "game_mode_all_random_death_match":
|
|
3831
|
+
return "ALL_RANDOM_DEATH_MATCH" /* AllRandomDeathMatch */;
|
|
3832
|
+
case "game_mode_1v1_mid":
|
|
3833
|
+
return "SOLO_MID" /* SoloMid */;
|
|
3834
|
+
case "game_mode_all_draft":
|
|
3835
|
+
return "ALL_PICK_RANKED" /* AllPickRanked */;
|
|
3836
|
+
// 天梯 AP
|
|
3837
|
+
case "game_mode_turbo":
|
|
3838
|
+
return "TURBO" /* Turbo */;
|
|
3839
|
+
case "game_mode_mutation":
|
|
3840
|
+
return "MUTATION" /* Mutation */;
|
|
3841
|
+
default:
|
|
3842
|
+
return "UNKNOWN" /* Unknown */;
|
|
3843
|
+
}
|
|
3844
|
+
}
|
|
3845
|
+
__name(convertGameMode, "convertGameMode");
|
|
3846
|
+
function determinePlayerPositions(match) {
|
|
3847
|
+
const players = match.players;
|
|
3848
|
+
players.filter((p) => p.lane === 2).forEach((p) => {
|
|
3849
|
+
p.calculatedPosition = 2;
|
|
3850
|
+
});
|
|
3851
|
+
const sideLanePlayers = players.filter((p) => p.lane !== 2);
|
|
3852
|
+
const radiant = sideLanePlayers.filter((p) => p.isRadiant).sort((a, b) => b.last_hits - a.last_hits);
|
|
3853
|
+
const dire = sideLanePlayers.filter((p) => !p.isRadiant).sort((a, b) => b.last_hits - a.last_hits);
|
|
3854
|
+
for (const team of [radiant, dire]) {
|
|
3855
|
+
team[0].calculatedPosition = 1;
|
|
3856
|
+
team[1].calculatedPosition = 3;
|
|
3857
|
+
const supA = team[2];
|
|
3858
|
+
const supB = team[3];
|
|
3859
|
+
const pos1 = team[0];
|
|
3860
|
+
let pos4 = supA;
|
|
3861
|
+
let pos5 = supB;
|
|
3862
|
+
if (supA.lane === pos1.lane) {
|
|
3863
|
+
pos5 = supA;
|
|
3864
|
+
pos4 = supB;
|
|
3865
|
+
}
|
|
3866
|
+
pos4.calculatedPosition = 4;
|
|
3867
|
+
pos5.calculatedPosition = 5;
|
|
3868
|
+
}
|
|
3869
|
+
}
|
|
3870
|
+
__name(determinePlayerPositions, "determinePlayerPositions");
|
|
3871
|
+
function determineLaneOutcome(match) {
|
|
3872
|
+
const laneGold = {
|
|
3873
|
+
radiant: { top: 0, mid: 0, bottom: 0 },
|
|
3874
|
+
dire: { top: 0, mid: 0, bottom: 0 }
|
|
3875
|
+
};
|
|
3876
|
+
for (const player of match.players) {
|
|
3877
|
+
const team = player.isRadiant ? "radiant" : "dire";
|
|
3878
|
+
const goldAt10 = player.gold_t?.[10] || 0;
|
|
3879
|
+
switch (player.calculatedPosition) {
|
|
3880
|
+
case 1:
|
|
3881
|
+
// 核心C位,在优势路
|
|
3882
|
+
case 5:
|
|
3883
|
+
const safeLane = player.isRadiant ? "bottom" : "top";
|
|
3884
|
+
laneGold[team][safeLane] += goldAt10;
|
|
3885
|
+
break;
|
|
3886
|
+
case 2:
|
|
3887
|
+
laneGold[team]["mid"] += goldAt10;
|
|
3888
|
+
break;
|
|
3889
|
+
case 3:
|
|
3890
|
+
// 劣势路核心
|
|
3891
|
+
case 4:
|
|
3892
|
+
const offLane = player.isRadiant ? "top" : "bottom";
|
|
3893
|
+
laneGold[team][offLane] += goldAt10;
|
|
3894
|
+
break;
|
|
3895
|
+
}
|
|
3896
|
+
}
|
|
3897
|
+
const topDiff = laneGold.radiant.top - laneGold.dire.top;
|
|
3898
|
+
const midDiff = laneGold.radiant.mid - laneGold.dire.mid;
|
|
3899
|
+
const bottomDiff = laneGold.radiant.bottom - laneGold.dire.bottom;
|
|
3900
|
+
return {
|
|
3901
|
+
topLaneOutcome: judge(topDiff),
|
|
3902
|
+
midLaneOutcome: judge(midDiff),
|
|
3903
|
+
bottomLaneOutcome: judge(bottomDiff)
|
|
3904
|
+
};
|
|
3905
|
+
function judge(goldAdv) {
|
|
3906
|
+
const STOMP_THRESHOLD = 2500;
|
|
3907
|
+
const VICTORY_THRESHOLD = 800;
|
|
3908
|
+
if (goldAdv > STOMP_THRESHOLD) {
|
|
3909
|
+
return "RADIANT_STOMP" /* RadiantStomp */;
|
|
3910
|
+
} else if (goldAdv > VICTORY_THRESHOLD) {
|
|
3911
|
+
return "RADIANT_VICTORY" /* RadiantVictory */;
|
|
3912
|
+
} else if (goldAdv < -STOMP_THRESHOLD) {
|
|
3913
|
+
return "DIRE_STOMP" /* DireStomp */;
|
|
3914
|
+
} else if (goldAdv < -VICTORY_THRESHOLD) {
|
|
3915
|
+
return "DIRE_VICTORY" /* DireVictory */;
|
|
3916
|
+
} else {
|
|
3917
|
+
return "TIE" /* Tie */;
|
|
3918
|
+
}
|
|
3919
|
+
}
|
|
3920
|
+
__name(judge, "judge");
|
|
3921
|
+
}
|
|
3922
|
+
__name(determineLaneOutcome, "determineLaneOutcome");
|
|
3923
|
+
function determineIMP(player) {
|
|
3924
|
+
const values = Object.values(player.benchmarks).filter((value) => value.raw > 0);
|
|
3925
|
+
if (values.length === 0) return 0;
|
|
3926
|
+
const totalScore = values.reduce((acc, cur) => acc + cur.pct, 0);
|
|
3927
|
+
return Math.round(totalScore / values.length * 100 - 50);
|
|
3928
|
+
}
|
|
3929
|
+
__name(determineIMP, "determineIMP");
|
|
3930
|
+
|
|
3931
|
+
// src/config.ts
|
|
3932
|
+
var import_koishi17 = require("koishi");
|
|
3364
3933
|
var import_fs3 = __toESM(require("fs"));
|
|
3365
3934
|
var import_path3 = __toESM(require("path"));
|
|
3366
3935
|
|
|
@@ -3372,67 +3941,77 @@ var globRequire_locales_schema_yml = __glob({
|
|
|
3372
3941
|
|
|
3373
3942
|
// src/config.ts
|
|
3374
3943
|
var pluginDir = import_path3.default.resolve(__dirname, "..");
|
|
3375
|
-
var Config =
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3944
|
+
var Config = import_koishi17.Schema.intersect([
|
|
3945
|
+
import_koishi17.Schema.intersect([
|
|
3946
|
+
import_koishi17.Schema.object({
|
|
3947
|
+
STRATZ_API_TOKEN: import_koishi17.Schema.string().required().role("secret"),
|
|
3948
|
+
dataParsingTimeoutMinutes: import_koishi17.Schema.number().default(60).min(0).max(1440),
|
|
3949
|
+
proxyAddress: import_koishi17.Schema.string(),
|
|
3950
|
+
suppressStratzNetworkErrors: import_koishi17.Schema.boolean().default(false),
|
|
3951
|
+
enableOpenDotaFallback: import_koishi17.Schema.boolean().default(false)
|
|
3952
|
+
}).i18n(Object.keys(LanguageTags).reduce((acc, cur) => (acc[cur] = globRequire_locales_schema_yml(`./locales/${cur}.schema.yml`)._config.base, acc), {})),
|
|
3953
|
+
import_koishi17.Schema.union([
|
|
3954
|
+
import_koishi17.Schema.object({
|
|
3955
|
+
enableOpenDotaFallback: import_koishi17.Schema.const(true).required(),
|
|
3956
|
+
OPENDOTA_API_KEY: import_koishi17.Schema.string().role("secret")
|
|
3957
|
+
}),
|
|
3958
|
+
import_koishi17.Schema.object({})
|
|
3959
|
+
]).i18n(Object.keys(LanguageTags).reduce((acc, cur) => (acc[cur] = globRequire_locales_schema_yml(`./locales/${cur}.schema.yml`)._config.base, acc), {}))
|
|
3960
|
+
]),
|
|
3961
|
+
import_koishi17.Schema.intersect([
|
|
3962
|
+
import_koishi17.Schema.object({
|
|
3963
|
+
useHeroNicknames: import_koishi17.Schema.boolean().default(true),
|
|
3964
|
+
urlInMessageType: import_koishi17.Schema.array(import_koishi17.Schema.union([import_koishi17.Schema.const("match"), import_koishi17.Schema.const("player"), import_koishi17.Schema.const("hero")])).role("checkbox"),
|
|
3965
|
+
maxSendItemCount: import_koishi17.Schema.number().default(5).min(1).max(10),
|
|
3966
|
+
showItemListAtTooMuchItems: import_koishi17.Schema.boolean().default(true),
|
|
3967
|
+
customItemAlias: import_koishi17.Schema.array(
|
|
3968
|
+
import_koishi17.Schema.object({
|
|
3969
|
+
keyword: import_koishi17.Schema.string().required(),
|
|
3970
|
+
alias: import_koishi17.Schema.string().required()
|
|
3392
3971
|
})
|
|
3393
3972
|
).default([]).role("table"),
|
|
3394
|
-
rankBroadSwitch:
|
|
3973
|
+
rankBroadSwitch: import_koishi17.Schema.boolean().default(false)
|
|
3395
3974
|
}).i18n(Object.keys(LanguageTags).reduce((acc, cur) => (acc[cur] = globRequire_locales_schema_yml(`./locales/${cur}.schema.yml`)._config.message, acc), {})),
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
rankBroadSwitch:
|
|
3399
|
-
rankBroadStar:
|
|
3400
|
-
rankBroadLeader:
|
|
3401
|
-
rankBroadFun:
|
|
3975
|
+
import_koishi17.Schema.union([
|
|
3976
|
+
import_koishi17.Schema.object({
|
|
3977
|
+
rankBroadSwitch: import_koishi17.Schema.const(true).required(),
|
|
3978
|
+
rankBroadStar: import_koishi17.Schema.boolean().default(true),
|
|
3979
|
+
rankBroadLeader: import_koishi17.Schema.boolean().default(true),
|
|
3980
|
+
rankBroadFun: import_koishi17.Schema.boolean().default(false)
|
|
3402
3981
|
}),
|
|
3403
|
-
|
|
3982
|
+
import_koishi17.Schema.object({})
|
|
3404
3983
|
]).i18n(Object.keys(LanguageTags).reduce((acc, cur) => (acc[cur] = globRequire_locales_schema_yml(`./locales/${cur}.schema.yml`)._config.message, acc), {}))
|
|
3405
3984
|
]),
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
dailyReportSwitch:
|
|
3985
|
+
import_koishi17.Schema.intersect([
|
|
3986
|
+
import_koishi17.Schema.object({
|
|
3987
|
+
dailyReportSwitch: import_koishi17.Schema.boolean().default(false)
|
|
3409
3988
|
}).i18n(Object.keys(LanguageTags).reduce((acc, cur) => (acc[cur] = globRequire_locales_schema_yml(`./locales/${cur}.schema.yml`)._config.report, acc), {})),
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
dailyReportSwitch:
|
|
3413
|
-
dailyReportHours:
|
|
3414
|
-
dailyReportShowCombi:
|
|
3989
|
+
import_koishi17.Schema.union([
|
|
3990
|
+
import_koishi17.Schema.object({
|
|
3991
|
+
dailyReportSwitch: import_koishi17.Schema.const(true).required(),
|
|
3992
|
+
dailyReportHours: import_koishi17.Schema.number().min(0).max(23).default(6),
|
|
3993
|
+
dailyReportShowCombi: import_koishi17.Schema.boolean().default(true)
|
|
3415
3994
|
}),
|
|
3416
|
-
|
|
3995
|
+
import_koishi17.Schema.object({})
|
|
3417
3996
|
]).i18n(Object.keys(LanguageTags).reduce((acc, cur) => (acc[cur] = globRequire_locales_schema_yml(`./locales/${cur}.schema.yml`)._config.report, acc), {})),
|
|
3418
|
-
|
|
3419
|
-
weeklyReportSwitch:
|
|
3997
|
+
import_koishi17.Schema.object({
|
|
3998
|
+
weeklyReportSwitch: import_koishi17.Schema.boolean().default(false)
|
|
3420
3999
|
}).i18n(Object.keys(LanguageTags).reduce((acc, cur) => (acc[cur] = globRequire_locales_schema_yml(`./locales/${cur}.schema.yml`)._config.report, acc), {})).description(void 0),
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
weeklyReportSwitch:
|
|
3424
|
-
weeklyReportDayHours:
|
|
3425
|
-
weeklyReportShowCombi:
|
|
4000
|
+
import_koishi17.Schema.union([
|
|
4001
|
+
import_koishi17.Schema.object({
|
|
4002
|
+
weeklyReportSwitch: import_koishi17.Schema.const(true).required(),
|
|
4003
|
+
weeklyReportDayHours: import_koishi17.Schema.tuple([import_koishi17.Schema.number().min(1).max(7), import_koishi17.Schema.number().min(0).max(23)]).default([1, 10]),
|
|
4004
|
+
weeklyReportShowCombi: import_koishi17.Schema.boolean().default(true)
|
|
3426
4005
|
}),
|
|
3427
|
-
|
|
4006
|
+
import_koishi17.Schema.object({})
|
|
3428
4007
|
]).i18n(Object.keys(LanguageTags).reduce((acc, cur) => (acc[cur] = globRequire_locales_schema_yml(`./locales/${cur}.schema.yml`)._config.report, acc), {}))
|
|
3429
4008
|
]),
|
|
3430
|
-
|
|
3431
|
-
template_match:
|
|
3432
|
-
template_player:
|
|
3433
|
-
template_hero:
|
|
3434
|
-
playerRankEstimate:
|
|
3435
|
-
templateFonts:
|
|
4009
|
+
import_koishi17.Schema.object({
|
|
4010
|
+
template_match: import_koishi17.Schema.union([...readDirectoryFilesSync(import_path3.default.join(pluginDir, "template", "match"))]).default("match_1"),
|
|
4011
|
+
template_player: import_koishi17.Schema.union([...readDirectoryFilesSync(import_path3.default.join(pluginDir, "template", "player"))]).default("player_1"),
|
|
4012
|
+
template_hero: import_koishi17.Schema.union([...readDirectoryFilesSync(import_path3.default.join(pluginDir, "template", "hero"))]).default("hero_1"),
|
|
4013
|
+
playerRankEstimate: import_koishi17.Schema.boolean().default(true),
|
|
4014
|
+
templateFonts: import_koishi17.Schema.array(String).default([]).role("table")
|
|
3436
4015
|
}).i18n(Object.keys(LanguageTags).reduce((acc, cur) => (acc[cur] = globRequire_locales_schema_yml(`./locales/${cur}.schema.yml`)._config.template, acc), {}))
|
|
3437
4016
|
]);
|
|
3438
4017
|
function readDirectoryFilesSync(directoryPath) {
|
|
@@ -3472,7 +4051,7 @@ async function apply(ctx, config) {
|
|
|
3472
4051
|
await ctx.dota2tracker.parsePolling.polling();
|
|
3473
4052
|
});
|
|
3474
4053
|
} else {
|
|
3475
|
-
ctx.logger.info(ctx.dota2tracker.i18n.gt("dota2tracker.logger.cron_not_enabled"));
|
|
4054
|
+
ctx.logger("dota2tracker").info(ctx.dota2tracker.i18n.gt("dota2tracker.logger.cron_not_enabled"));
|
|
3476
4055
|
}
|
|
3477
4056
|
ctx.dota2tracker.hero = new HeroService(ctx);
|
|
3478
4057
|
ctx.dota2tracker.item = new ItemService(ctx);
|
|
@@ -3480,6 +4059,10 @@ async function apply(ctx, config) {
|
|
|
3480
4059
|
ctx.dota2tracker.database = new DatabaseService(ctx);
|
|
3481
4060
|
ctx.dota2tracker.valveAPI = new ValveAPI(ctx);
|
|
3482
4061
|
ctx.dota2tracker.stratzAPI = new StratzAPI(ctx, pluginDir2);
|
|
4062
|
+
if (config.enableOpenDotaFallback) {
|
|
4063
|
+
ctx.dota2tracker.opendotaAPI = new OpenDotaAPI(ctx);
|
|
4064
|
+
ctx.dota2tracker.opendotaAdapter = new OpenDotaAdapter(ctx);
|
|
4065
|
+
}
|
|
3483
4066
|
ctx.dota2tracker = ctx.dota2tracker;
|
|
3484
4067
|
usage = await ctx.dota2tracker.i18n.generateUsage();
|
|
3485
4068
|
registerHelpCommand(ctx);
|