Compare commits

..
36 Commits
Author SHA1 Message Date
TrèsAbhi 098e6c09b9 Merge pull request #19 from tresabhi/1.10.0 2023-07-12 14:07:25 -05:00
TresAbhi 186d45e3d9 fix: module type 2023-07-12 19:06:22 +00:00
TresAbhi 0fdc87a88c feat: docs 2023-07-12 18:35:31 +00:00
TresAbhi 1d4ac2f133 feat: switched to Node module resolution 2023-07-11 19:54:57 +00:00
TresAbhi 21158e9081 feat: updated /fullstats with figma 2023-07-11 19:43:22 +00:00
TresAbhi fedad8df3e feat: added timezone help 2023-07-11 19:00:10 +00:00
TresAbhi 6c8f5ca478 feat: simplified commands help 2023-07-11 18:44:32 +00:00
TresAbhi 463af106be feat: added more help commands 2023-07-10 19:14:16 +00:00
TresAbhi feeb850ca9 feat: added collector tanks to /ownedtanks 2023-07-10 18:16:33 +00:00
TresAbhi 80ffff322c feat: merge themes 2023-07-10 17:44:56 +00:00
TresAbhi 0833375562 feat: reset stats at midnight 2023-07-10 17:42:29 +00:00
TresAbhi 297f2f6580 feat: added limits to /today 2023-07-10 17:41:48 +00:00
TresAbhi 438feefa83 feat: today embeds 2023-07-10 01:44:05 +00:00
TresAbhi 8f6388b42b fix: time resets at 5:00 am 2023-07-10 01:27:13 +00:00
TresAbhi 405f544774 fix: time zone compliant commands 2023-07-10 01:23:49 +00:00
TresAbhi dd0644e09f fix: no tanks played now does not display a total 2023-07-10 01:23:35 +00:00
TresAbhi d8ac0b2ef6 feat: added order to getDiffedTankStats 2023-07-09 17:49:11 +00:00
TresAbhi 87955214e3 feat: updated /today with figma 2023-07-09 17:37:44 +00:00
TresAbhi f985cbf70b feat: updated evolution from figma 2023-07-09 01:12:02 +00:00
TresAbhi f64e72c312 feat: renamed statsfull to fullstats 2023-07-09 01:02:30 +00:00
TresAbhi ce6715cf2c fix: normal image instead of preview 2023-07-09 00:54:19 +00:00
TresAbhi 18e5405c54 fix: duplicate named interface export 2023-07-09 00:52:13 +00:00
TresAbhi a0f15fc1c4 fix: revert back to blitz stars tankopedia for missing tanks 2023-07-09 00:51:53 +00:00
TresAbhi cf20ae38d4 feat: updated /ownedtanks to new design 2023-07-09 00:47:03 +00:00
TresAbhi 2bf5eb78be feat: optimized tankopedia information 2023-07-08 19:47:21 +00:00
TresAbhi 57271e3c23 feat: infrastructure for ownedtanks columns 2023-07-08 19:39:35 +00:00
TresAbhi 2258b0f936 fix: extra tilda 2023-07-07 01:29:13 +00:00
TrèsAbhi aa8115d260 Merge pull request #18 from tresabhi/1.9.5 2023-07-06 20:27:59 -05:00
TrèsAbhi f8dfa386a0 Merge branch 'main' into 1.9.5 2023-07-06 20:23:42 -05:00
TresAbhi 00771e130f feat: changelogs 2023-07-07 01:22:54 +00:00
TresAbhi bd612bf777 fix: /today now preserves all WN8 values 2023-07-07 01:18:18 +00:00
TresAbhi ca208e44a1 fix: all commands now work with a lack of tankopedia entries 2023-07-07 01:08:00 +00:00
TrèsAbhi 72c4cd664b Merge pull request #17 from tresabhi/1.9.4 2023-07-05 12:47:06 -05:00
TresAbhi f907c79d68 feat: changelogs 2023-07-05 17:46:28 +00:00
TresAbhi ae3d0a08cd fix: tier weights now ignores tanks without entries 2023-07-05 17:43:15 +00:00
TresAbhi fb66027de0 fix: formatted tankopedia arrays not using promise 2023-07-05 17:42:14 +00:00
126 changed files with 1404 additions and 810 deletions

No files matched your search

+2 -1
View File
@@ -10,4 +10,5 @@ dist.zip
commands.txt
/test.*
/docs/.vitepress/dist
/docs/.vitepress/cache
/docs/.vitepress/cache
servers.csv
+1
View File
@@ -7,6 +7,7 @@
"cleandist",
"customperiod",
"Everytime",
"fullstats",
"fuzzysort",
"kranvagn",
"outfile",
+11 -11
View File
@@ -17,23 +17,23 @@ diverse, inclusive, and healthy community.
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
- Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
- The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
@@ -106,7 +106,7 @@ Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
+6 -6
View File
@@ -1,16 +1,16 @@
// https://vitepress.dev/guide/custom-theme
import { h } from 'vue'
import Theme from 'vitepress/theme'
import './style.css'
import { h } from 'vue';
import Theme from 'vitepress/theme';
import './style.css';
export default {
extends: Theme,
Layout: () => {
return h(Theme.Layout, null, {
// https://vitepress.dev/guide/extending-default-theme#layout-slots
})
});
},
enhanceApp({ app, router, siteData }) {
// ...
}
}
},
};
+1 -2
View File
@@ -7,7 +7,7 @@
* Colors
* -------------------------------------------------------------------------- */
:root {
:root {
--vp-c-brand: #646cff;
--vp-c-brand-light: #747bff;
--vp-c-brand-lighter: #9499ff;
@@ -88,4 +88,3 @@
.DocSearch {
--docsearch-primary-color: var(--vp-c-brand) !important;
}
+37
View File
@@ -0,0 +1,37 @@
# Blitzkrieg 1.10.0
This update addresses many readability issues that users have reported over time.
## Changes
- `/ownedtanks`
- Tanks are now displayed in two columns
- Collector and premium tanks now have their own colors
- Tank types are also displayed
- A stylistic image of the tank is also displayed for each tank to the right
- `/fullstats`
- Was previously called `/statsfull`; this was changed to avoid the clutter that would show up when searching for `/stats`
- Added four catagories in which all the stats are sorted into
- Catagories now have backgrounds to compartmentalize them
- `/evolution`
- Gave the axis markings a high contrast color
- Added a label for the x axis affirming that it is plotted over the number of battles
- `/today`
- Added a border to all the rows to compartmentalize them
- This command can now generate embeds too
- Removed survivability
- Added a limit option to cut off after a certain number of tanks played (this is mainly intended to be used by streamers to limit the content of the embeds)
- Tanks are now displayed in order of the most recent battle
- `/help`
- A significant amount of subcommands were added
- These new subcommands now return normal Discord messages instead of embeds in favor of readability
- The contents of the replies are directly fetched from the Blitzkrieg documentation
- Time zones
- What is considered a "day's worth of battles" has been adjusted as the "reset time" was the same for all Blitz servers
- The reset time is now dependent on the server of the player
- Use `/help timezones` to learn more
## Technical Changes
- All themes have been merged to leverage Bepaint's suffixes
- Switched to Node module resolution
+12
View File
@@ -0,0 +1,12 @@
# Blitzkrieg 1.9.4
Haha, I lied. More fixes.
## Fixes
- Tank names once again show when searching for tank stats
- Tier weights now ignore tanks without entries in the `tankopedia`
## Technical Changes
- All `tankopedia` derivatives are now promises
+9
View File
@@ -0,0 +1,9 @@
# Blitzkrieg 1.9.5
Hopefully this is the last round of fixes for Blitzkrieg `1.9`.
## Fixes
- All commands have been tweaked to work even with a lack of a tankopedia entry
- This should fix test tanks not showing up
- `/today` now properly preserves all WN8 entries
+10
View File
@@ -0,0 +1,10 @@
# About
Blitzkrieg is a multipurpose bot for anything and everything World of Tanks Blitz.
- Track statistics
- Find inactive players in a clan
- Lookup owned tanks
- Create embeds for streams
- Track a player/tanks statistical evolution
- ...and a variety of other similar things
+23
View File
@@ -0,0 +1,23 @@
# Embeds
Embeds are generated by a few commands as a URL that can be loaded in a browser/streaming software.
## Creating Embeds
1. Use a command of your liking (for example, `/stats`).
2. Customize the command however you like and the URL generated for the embed will match your needs.
3. Send the command and when the bot is "done thinking," an "Embed" button will appear below the bot's reply.
## Using Embeds in Streaming Softwares
1. Click on the "Embed" button.
2. Copy the URL from your browser.
3. Paste the URL into a Browser Source.
4. Set the refresh rate/FPS to something very low like `0.0167` (that's 1 frame every 60 seconds).
## How Embeds Behave
- They load like any normal website albeit with a significant delay.
- They auto refresh every `60s`.
- All embeds and images generated by Blitzkrieg have a width of `640px` but the height varies and must be found manually through trial and error.
- It's okay to have a Browser Source that has a height larger than needed as Blitzkrieg embeds are transparent.
+10
View File
@@ -0,0 +1,10 @@
# Inviting the Bot
It's quite simple really. Just follow the steps below:
1. Join a Discord server that already has Blitzkrieg installed or [the official Discord Server](https://discord.gg/nDt7AjGJQH).
2. Click on the bot's profile.
3. Click the "Add to Server" button under the bot's name and ID.
4. Select what server you want it in and see what permissions you seem fit for the bot.
Voila! You're ready to go.
+9
View File
@@ -0,0 +1,9 @@
# Timezones
What is considered a "day's worth of games" is determined by the reset time. The reset time is generally at midnight everyday after which newer games are counted towards the next day. Reset times vary from region to region:
- North America: `UTC-5:00` (Central North American Time midnight)
- Europe: `UTC+1:00` (Central European Time midnight)
- Asia: `UTC+7:00` (Central Asia Standard Time midnight)
**Reset times are different from Blitz Stars** (Blitz Stars resets at 5:00 am locally relative to the user opening their website). Inconsistencies with Blitz Stars only occur with sleep-deprived players and players who play in the wrong region.
+4 -2
View File
@@ -1,6 +1,6 @@
{
"name": "blitzkrieg",
"version": "1.9.3",
"version": "1.10.0",
"description": "",
"main": "src/server.ts",
"type": "module",
@@ -16,7 +16,8 @@
"start:server": "node --enable-source-maps --trace-warnings dist/server.cjs",
"host:docs": "vitepress preview docs",
"zip": "zip -r dist.zip dist",
"lint": "npx tsc"
"lint": "npx tsc",
"format": "prettier --write \"**/*.@(json|js|jsx|css|md|ts|tsx|html|xml|yml|gltf)\""
},
"license": "MIT",
"dependencies": {
@@ -42,6 +43,7 @@
"@types/markdown-escape": "^1.1.0",
"@types/node": "^20.2.1",
"@types/react": "^18.2.7",
"prettier": "^3.0.0",
"typescript": "^5.0.4",
"vitepress": "^1.0.0-beta.3"
}
+5 -5
View File
@@ -1,9 +1,9 @@
import { Client, GatewayIntentBits } from 'discord.js';
import { DISCORD_TOKEN } from './core/node/arguments.js';
import { registerErrorHandlers } from './events/error.js';
import guildMemberAdd from './events/guildMemberAdd.js';
import interactionCreate from './events/interactionCreate/index.js';
import ready from './events/ready.js';
import { DISCORD_TOKEN } from './core/node/arguments';
import { registerErrorHandlers } from './events/error';
import guildMemberAdd from './events/guildMemberAdd';
import interactionCreate from './events/interactionCreate';
import ready from './events/ready';
export const client = new Client({
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers],
+8 -8
View File
@@ -1,18 +1,18 @@
import { SlashCommandBuilder } from 'discord.js';
import packageJSON from '../../package.json' assert { type: 'json' };
import { client } from '../bot.js';
import { tankopedia } from '../core/blitz/tankopedia.js';
import { tankAverages } from '../core/blitzstars/tankAverages.js';
import embedInfo from '../core/discord/embedInfo.js';
import markdownTable from '../core/discord/markdownTable.js';
import getClientId from '../core/node/getClientId.js';
import { CommandRegistry } from '../events/interactionCreate/index.js';
import { client } from '../bot';
import { tankopedia } from '../core/blitz/tankopedia';
import { tankAverages } from '../core/blitzstars/tankAverages';
import embedInfo from '../core/discord/embedInfo';
import markdownTable from '../core/discord/markdownTable';
import getClientId from '../core/node/getClientId';
import { CommandRegistry } from '../events/interactionCreate';
const executionStart = new Date().getTime();
export const debugCommand: CommandRegistry = {
inProduction: true,
inDevelopment: true,
inDevelopment: false,
inPublic: true,
command: new SlashCommandBuilder()
+40 -25
View File
@@ -1,21 +1,21 @@
import { SlashCommandBuilder } from 'discord.js';
import calculateWN8 from '../core/blitz/calculateWN8.js';
import getWargamingResponse from '../core/blitz/getWargamingResponse.js';
import sumStats from '../core/blitz/sumStats.js';
import { tankopedia } from '../core/blitz/tankopedia.js';
import getPeriodNow from '../core/blitzstars/getPeriodNow.js';
import getPeriodStart from '../core/blitzstars/getPeriodStart.js';
import getTankStatsDiffed from '../core/blitzstars/getTankStatsDiffed.js';
import { tankAverages } from '../core/blitzstars/tankAverages.js';
import addUsernameChoices from '../core/discord/addUsernameChoices.js';
import embedNegative from '../core/discord/embedNegative.js';
import embedPositive from '../core/discord/embedPositive.js';
import markdownTable, { TableInput } from '../core/discord/markdownTable.js';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand.js';
import { WARGAMING_APPLICATION_ID } from '../core/node/arguments.js';
import { CommandRegistry } from '../events/interactionCreate/index.js';
import { AccountInfo } from '../types/accountInfo.js';
import { PossiblyPromise } from '../types/possiblyPromise.js';
import calculateWN8 from '../core/blitz/calculateWN8';
import getWargamingResponse from '../core/blitz/getWargamingResponse';
import sumStats from '../core/blitz/sumStats';
import { tankopedia } from '../core/blitz/tankopedia';
import getDiffedTankStats from '../core/blitzstars/getDiffedTankStats';
import getPeriodNow from '../core/blitzstars/getPeriodNow';
import getTimeDaysAgo from '../core/blitzstars/getTimeDaysAgo';
import { tankAverages } from '../core/blitzstars/tankAverages';
import addUsernameChoices from '../core/discord/addUsernameChoices';
import embedNegative from '../core/discord/embedNegative';
import embedPositive from '../core/discord/embedPositive';
import markdownTable, { TableInput } from '../core/discord/markdownTable';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
import { WARGAMING_APPLICATION_ID } from '../core/node/arguments';
import { CommandRegistry } from '../events/interactionCreate';
import { AccountInfo } from '../types/accountInfo';
import { PossiblyPromise } from '../types/possiblyPromise';
export type SkilledClan = 'SKLLD' | 'SMRI';
@@ -26,7 +26,7 @@ export const SKILLED_CLANS: Record<SkilledClan, string> = {
export const eligibleCommand: CommandRegistry = {
inProduction: true,
inDevelopment: true,
inDevelopment: false,
inPublic: false,
command: new SlashCommandBuilder()
@@ -59,24 +59,39 @@ export const eligibleCommand: CommandRegistry = {
let body = '';
if (clan === 'SKLLD') {
const tankStatsOverTime = await getTankStatsDiffed(
const { diffed } = await getDiffedTankStats(
server,
id,
getPeriodStart('30'),
getTimeDaysAgo(server, 30),
getPeriodNow(),
);
const entries = Object.entries(tankStatsOverTime);
const entries = Object.entries(diffed);
const stats = sumStats(entries.map(([, stats]) => stats));
const averageTier =
(await entries.reduce<PossiblyPromise<number>>(
async (accumulator, [tankIdString, stats]) => {
const tankId = parseInt(tankIdString);
const tankTier = (await tankopedia)[tankId].tier;
const tankopediaEntry = (await tankopedia)[tankId];
if (!tankopediaEntry) return accumulator;
const tankTier = tankopediaEntry.tier;
return (await accumulator) + tankTier * stats.battles;
},
0,
)) / stats.battles;
)) /
(await entries.reduce<PossiblyPromise<number>>(
async (accumulator, [tankIdString, stats]) => {
const tankId = parseInt(tankIdString);
const tankopediaEntry = (await tankopedia)[tankId];
if (!tankopediaEntry) return accumulator;
return stats.battles;
},
0,
));
const WN8 =
(await entries.reduce<PossiblyPromise<number>>(
async (accumulator, [tankIdString, stats]) => {
@@ -101,7 +116,7 @@ export const eligibleCommand: CommandRegistry = {
const tankId = parseInt(tankIdString);
if ((await tankopedia)[tankId]?.tier === 10) {
const tankStats = tankStatsOverTime[tankId];
const tankStats = diffed[tankId];
return (await accumulator) + tankStats.damage_dealt;
} else return accumulator;
},
@@ -112,7 +127,7 @@ export const eligibleCommand: CommandRegistry = {
const tankId = parseInt(tankIdString);
if ((await tankopedia)[tankId]?.tier === 10) {
const tankStats = tankStatsOverTime[tankId];
const tankStats = diffed[tankId];
return (await accumulator) + tankStats.battles;
} else return accumulator;
},
+23 -22
View File
@@ -1,26 +1,26 @@
import { SlashCommandBuilder } from 'discord.js';
import { CYCLIC_API } from '../constants/cyclic.js';
import getWargamingResponse from '../core/blitz/getWargamingResponse.js';
import resolveTankId from '../core/blitz/resolveTankId.js';
import addStatTypeSubCommandGroups from '../core/discord/addStatTypeSubCommandGroups.js';
import autocompleteTanks from '../core/discord/autocompleteTanks.js';
import autocompleteUsername from '../core/discord/autocompleteUsername.js';
import interactionToURL from '../core/discord/interactionToURL.js';
import linkButton from '../core/discord/linkButton.js';
import primaryButton from '../core/discord/primaryButton.js';
import resolvePeriodFromButton from '../core/discord/resolvePeriodFromButton.js';
import resolvePeriodFromCommand from '../core/discord/resolvePeriodFromCommand.js';
import resolvePlayerFromButton from '../core/discord/resolvePlayerFromButton.js';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand.js';
import { WARGAMING_APPLICATION_ID } from '../core/node/arguments.js';
import { CommandRegistry } from '../events/interactionCreate/index.js';
import evolution from '../renderers/evolution.js';
import { StatType } from '../renderers/statsfull.js';
import { AccountInfo } from '../types/accountInfo.js';
import { CYCLIC_API } from '../constants/cyclic';
import getWargamingResponse from '../core/blitz/getWargamingResponse';
import resolveTankId from '../core/blitz/resolveTankId';
import addStatTypeSubCommandGroups from '../core/discord/addStatTypeSubCommandGroups';
import autocompleteTanks from '../core/discord/autocompleteTanks';
import autocompleteUsername from '../core/discord/autocompleteUsername';
import interactionToURL from '../core/discord/interactionToURL';
import linkButton from '../core/discord/linkButton';
import primaryButton from '../core/discord/primaryButton';
import resolvePeriodFromButton from '../core/discord/resolvePeriodFromButton';
import resolvePeriodFromCommand from '../core/discord/resolvePeriodFromCommand';
import resolvePlayerFromButton from '../core/discord/resolvePlayerFromButton';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
import { WARGAMING_APPLICATION_ID } from '../core/node/arguments';
import { CommandRegistry } from '../events/interactionCreate';
import evolution from '../renderers/evolution';
import { StatType } from '../renderers/fullStats';
import { AccountInfo } from '../types/accountInfo';
export const evolutionCommand: CommandRegistry = {
inProduction: true,
inDevelopment: true,
inDevelopment: false,
inPublic: true,
command: addStatTypeSubCommandGroups(
@@ -34,9 +34,10 @@ export const evolutionCommand: CommandRegistry = {
true,
) as StatType;
const player = await resolvePlayerFromCommand(interaction);
const period = resolvePeriodFromCommand(interaction);
const period = resolvePeriodFromCommand(player.server, interaction);
const tankIdRaw = interaction.options.getString('tank')!;
const tankId = commandGroup === 'tank' ? await resolveTankId(tankIdRaw) : null;
const tankId =
commandGroup === 'tank' ? await resolveTankId(tankIdRaw) : null;
const start = interaction.options.getInteger('start');
const end = interaction.options.getInteger('end');
const { nickname } = (
@@ -73,8 +74,8 @@ export const evolutionCommand: CommandRegistry = {
const url = new URL(`${CYCLIC_API}/${interaction.customId}`);
const path = url.pathname.split('/').filter(Boolean);
const commandGroup = path[1] as StatType;
const period = resolvePeriodFromButton(interaction);
const player = await resolvePlayerFromButton(interaction);
const period = resolvePeriodFromButton(player.server, interaction);
return await evolution(
commandGroup,
@@ -1,30 +1,30 @@
import { SlashCommandBuilder } from 'discord.js';
import { CYCLIC_API } from '../constants/cyclic.js';
import getWargamingResponse from '../core/blitz/getWargamingResponse.js';
import resolveTankId from '../core/blitz/resolveTankId.js';
import addStatTypeSubCommandGroups from '../core/discord/addStatTypeSubCommandGroups.js';
import autocompleteTanks from '../core/discord/autocompleteTanks.js';
import autocompleteUsername from '../core/discord/autocompleteUsername.js';
import interactionToURL from '../core/discord/interactionToURL.js';
import linkButton from '../core/discord/linkButton.js';
import primaryButton from '../core/discord/primaryButton.js';
import resolvePeriodFromButton from '../core/discord/resolvePeriodFromButton.js';
import resolvePeriodFromCommand from '../core/discord/resolvePeriodFromCommand.js';
import resolvePlayerFromButton from '../core/discord/resolvePlayerFromButton.js';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand.js';
import { WARGAMING_APPLICATION_ID } from '../core/node/arguments.js';
import { CommandRegistry } from '../events/interactionCreate/index.js';
import statsfull, { StatType } from '../renderers/statsfull.js';
import { AccountInfo } from '../types/accountInfo.js';
import { CYCLIC_API } from '../constants/cyclic';
import getWargamingResponse from '../core/blitz/getWargamingResponse';
import resolveTankId from '../core/blitz/resolveTankId';
import addStatTypeSubCommandGroups from '../core/discord/addStatTypeSubCommandGroups';
import autocompleteTanks from '../core/discord/autocompleteTanks';
import autocompleteUsername from '../core/discord/autocompleteUsername';
import interactionToURL from '../core/discord/interactionToURL';
import linkButton from '../core/discord/linkButton';
import primaryButton from '../core/discord/primaryButton';
import resolvePeriodFromButton from '../core/discord/resolvePeriodFromButton';
import resolvePeriodFromCommand from '../core/discord/resolvePeriodFromCommand';
import resolvePlayerFromButton from '../core/discord/resolvePlayerFromButton';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
import { WARGAMING_APPLICATION_ID } from '../core/node/arguments';
import { CommandRegistry } from '../events/interactionCreate';
import fullStats, { StatType } from '../renderers/fullStats';
import { AccountInfo } from '../types/accountInfo';
export const statsFullCommand: CommandRegistry = {
export const fullStatsCommand: CommandRegistry = {
inProduction: true,
inDevelopment: true,
inDevelopment: false,
inPublic: true,
command: addStatTypeSubCommandGroups(
new SlashCommandBuilder()
.setName('statsfull')
.setName('fullstats')
.setDescription('Full in-game statistics'),
),
@@ -33,7 +33,7 @@ export const statsFullCommand: CommandRegistry = {
true,
) as StatType;
const player = await resolvePlayerFromCommand(interaction);
const period = resolvePeriodFromCommand(interaction);
const period = resolvePeriodFromCommand(player.server, interaction);
const tankIdRaw = interaction.options.getString('tank')!;
const tankId =
commandGroup === 'tank' ? await resolveTankId(tankIdRaw) : null;
@@ -52,7 +52,7 @@ export const statsFullCommand: CommandRegistry = {
)[player.id];
return [
await statsfull(commandGroup, period, player, tankId),
await fullStats(commandGroup, period, player, tankId),
primaryButton(path, 'Refresh'),
// linkButton(`${CYCLIC_API}/${path}`, 'Embed'),
linkButton(
@@ -71,10 +71,10 @@ export const statsFullCommand: CommandRegistry = {
const url = new URL(`${CYCLIC_API}/${interaction.customId}`);
const path = url.pathname.split('/').filter(Boolean);
const commandGroup = path[1] as StatType;
const period = resolvePeriodFromButton(interaction);
const player = await resolvePlayerFromButton(interaction);
const period = resolvePeriodFromButton(player.server, interaction);
return await statsfull(
return await fullStats(
commandGroup,
period,
player,
+55 -25
View File
@@ -1,36 +1,66 @@
import { SlashCommandBuilder } from 'discord.js';
import discord from '../../discord.json' assert { type: 'json' };
import { client } from '../bot.js';
import embedInfo from '../core/discord/embedInfo.js';
import {
CommandRegistry,
guildCommands,
publicCommands,
} from '../events/interactionCreate/index.js';
SlashCommandBuilder,
SlashCommandSubcommandsOnlyBuilder,
} from 'discord.js';
import { COMMANDS_RAW, CommandRegistry } from '../events/interactionCreate';
const RAW_PATH = `https://raw.githubusercontent.com/tresabhi/blitzkrieg/main/docs/`;
const DOCS = {
permissions: 'guide/permissions',
embeds: 'guide/embeds',
about: 'guide/about',
invite: 'guide/invite',
timezones: 'guide/timezones',
};
const DOC_DESCRIPTIONS: Record<keyof typeof DOCS, string> = {
permissions: 'The permissions needed for the bot to function',
embeds: 'How embeds work',
about: 'About the bot',
invite: 'How to invite the bot',
timezones: 'How Blitzkrieg infers and uses your timezone',
};
function addDocsSubcommands(option: SlashCommandSubcommandsOnlyBuilder) {
Object.entries(DOC_DESCRIPTIONS).forEach(([key, value]) => {
option.addSubcommand((sub) => sub.setName(key).setDescription(value));
});
return option;
}
export const helpCommand: CommandRegistry = {
inProduction: true,
inDevelopment: true,
inDevelopment: false,
inPublic: true,
command: new SlashCommandBuilder()
.setName('help')
.setDescription('All the help you need about the bot'),
command: addDocsSubcommands(
new SlashCommandBuilder()
.setName('help')
.setDescription('All the help you need about the bot')
.addSubcommand((option) =>
option
.setName('commands')
.setDescription('List of all commands and their descriptions'),
),
),
handler(interaction) {
return embedInfo(
`${client.user?.username} help`,
`**About**\n${
client.user?.username
} automates many mundane tasks and provide statistics in numerous flexible ways.\n\nMade by TresAbhi from the Skilled [SKLLD] clan.\nGitHub: https://github.com/tresabhi/blitzkrieg\nDiscord server: https://discord.gg/nDt7AjGJQH\nSkilled server: https://discord.gg/ZPvcEG7DS8\n\n**Commands**\n${(interaction.guildId ===
discord.sklld_guild_id
? [...guildCommands, ...publicCommands]
: publicCommands
const subcommand = interaction.options.getSubcommand();
if (subcommand === 'commands') {
return `# Commands\n\nBlitzkrieg offers the following commands:\n\n${COMMANDS_RAW.filter(
(registry) => registry.inPublic && registry.inProduction,
)
.map((command) => `**/${command.name}**: ${command.description}`)
.join(
'\n',
)}\n\n*I am a new bot; I may make mistakes and go offline unexpectedly from time to time.*`,
);
.map(
(registry) =>
`- \`/${registry.command.name}\`: ${registry.command.description}`,
)
.join('\n')}`;
}
const url = `${RAW_PATH}${DOCS[subcommand as keyof typeof DOCS]}.md`;
const content = fetch(url).then((response) => response.text());
return content;
},
};
+15 -15
View File
@@ -1,24 +1,24 @@
import { SlashCommandBuilder } from 'discord.js';
import GenericStats from '../components/GenericStats/index.js';
import NoData, { NoDataType } from '../components/NoData.js';
import PoweredBy, { PoweredByType } from '../components/PoweredBy.js';
import TitleBar from '../components/TitleBar.js';
import Wrapper from '../components/Wrapper.js';
import { BLITZ_SERVERS } from '../constants/servers.js';
import getBlitzClan from '../core/blitz/getBlitzClan.js';
import getWargamingResponse from '../core/blitz/getWargamingResponse.js';
import addClanChoices from '../core/discord/addClanChoices.js';
import autocompleteClan from '../core/discord/autocompleteClan.js';
import { WARGAMING_APPLICATION_ID } from '../core/node/arguments.js';
import { CommandRegistry } from '../events/interactionCreate/index.js';
import { AccountInfo } from '../types/accountInfo.js';
import { ClanInfo } from '../types/clanInfo.js';
import GenericStats from '../components/GenericStats';
import NoData, { NoDataType } from '../components/NoData';
import PoweredBy, { PoweredByType } from '../components/PoweredBy';
import TitleBar from '../components/TitleBar';
import Wrapper from '../components/Wrapper';
import { BLITZ_SERVERS } from '../constants/servers';
import getBlitzClan from '../core/blitz/getBlitzClan';
import getWargamingResponse from '../core/blitz/getWargamingResponse';
import addClanChoices from '../core/discord/addClanChoices';
import autocompleteClan from '../core/discord/autocompleteClan';
import { WARGAMING_APPLICATION_ID } from '../core/node/arguments';
import { CommandRegistry } from '../events/interactionCreate';
import { AccountInfo } from '../types/accountInfo';
import { ClanInfo } from '../types/clanInfo';
const DEFAULT_THRESHOLD = 7;
export const inactiveCommand: CommandRegistry = {
inProduction: true,
inDevelopment: true,
inDevelopment: false,
inPublic: true,
command: new SlashCommandBuilder()
+52 -27
View File
@@ -1,24 +1,26 @@
import { SlashCommandBuilder } from 'discord.js';
import NoData, { NoDataType } from '../components/NoData.js';
import PoweredBy, { PoweredByType } from '../components/PoweredBy.js';
import * as Tanks from '../components/Tanks/index.js';
import TitleBar from '../components/TitleBar.js';
import Wrapper from '../components/Wrapper.js';
import { BLITZ_SERVERS } from '../constants/servers.js';
import getTankStats from '../core/blitz/getTankStats.js';
import getWargamingResponse from '../core/blitz/getWargamingResponse.js';
import NoData, { NoDataType } from '../components/NoData';
import PoweredBy, { PoweredByType } from '../components/PoweredBy';
import * as Tanks from '../components/Tanks';
import TitleBar from '../components/TitleBar';
import Wrapper from '../components/Wrapper';
import { BLITZ_SERVERS } from '../constants/servers';
import getTankStats from '../core/blitz/getTankStats';
import getTreeType from '../core/blitz/getTreeType';
import getWargamingResponse from '../core/blitz/getWargamingResponse';
import resolveTankName from '../core/blitz/resolveTankName';
import {
TIER_ROMAN_NUMERALS,
Tier,
tankopedia,
} from '../core/blitz/tankopedia.js';
import addUsernameChoices from '../core/discord/addUsernameChoices.js';
import autocompleteUsername from '../core/discord/autocompleteUsername.js';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand.js';
import { WARGAMING_APPLICATION_ID } from '../core/node/arguments.js';
import { CommandRegistry } from '../events/interactionCreate/index.js';
import { AccountInfo } from '../types/accountInfo.js';
import { PlayerClanData } from '../types/playerClanData.js';
} from '../core/blitz/tankopedia';
import addUsernameChoices from '../core/discord/addUsernameChoices';
import autocompleteUsername from '../core/discord/autocompleteUsername';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
import { WARGAMING_APPLICATION_ID } from '../core/node/arguments';
import { CommandRegistry } from '../events/interactionCreate';
import { AccountInfo } from '../types/accountInfo';
import { PlayerClanData } from '../types/playerClanData';
const COMP_TANKS = [
// light tanks
@@ -43,7 +45,7 @@ const COMP_TANKS = [
export const ownedTanksCommand: CommandRegistry = {
inProduction: true,
inDevelopment: true,
inDevelopment: false,
inPublic: true,
command: new SlashCommandBuilder()
@@ -79,16 +81,21 @@ export const ownedTanksCommand: CommandRegistry = {
const tankStats = await getTankStats(server, id);
const tanks = (
await Promise.all(
tankStats.map(async (tankData) => (await tankopedia)[tankData.tank_id]),
tankStats.map(async (tankData) => ({
...(await tankopedia)[tankData.tank_id],
tank_id: tankData.tank_id,
})),
)
).filter((tank) => tank?.tier === tier);
const clanData = await getWargamingResponse<PlayerClanData>(
`https://api.wotblitz.${server}/wotb/clans/accountinfo/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}&extra=clan`,
);
const leftColumnSize = Math.ceil(tanks.length / 2);
const leftColumn = tanks.slice(0, leftColumnSize);
const rightColumn = tanks.slice(leftColumnSize);
return (
<Wrapper>
{/* TODO: integrate some of these into title bar */}
<TitleBar
name={accountInfo[id].nickname}
nameDiscriminator={`(Tier ${TIER_ROMAN_NUMERALS[tier as Tier]})`}
@@ -106,14 +113,32 @@ export const ownedTanksCommand: CommandRegistry = {
{tanks.length > 0 && (
<Tanks.Root>
{tanks.map((tank) => (
<Tanks.Item
key={tank.tank_id}
name={tank.name}
type={tank.type}
icon={tank.images.normal}
/>
))}
<Tanks.Column>
{await Promise.all(
leftColumn.map(async (tank) => (
<Tanks.Item
key={tank.tank_id}
name={await resolveTankName(tank.tank_id)}
tankType={tank.type}
image={tank.images?.normal}
treeType={await getTreeType(tank.tank_id)}
/>
)),
)}
</Tanks.Column>
<Tanks.Column>
{await Promise.all(
rightColumn.map(async (tank) => (
<Tanks.Item
key={tank.tank_id}
name={await resolveTankName(tank.tank_id)}
tankType={tank.type}
image={tank.images?.normal}
treeType={await getTreeType(tank.tank_id)}
/>
)),
)}
</Tanks.Column>
</Tanks.Root>
)}
+2 -2
View File
@@ -1,9 +1,9 @@
import { SlashCommandBuilder } from 'discord.js';
import { CommandRegistry } from '../events/interactionCreate/index.js';
import { CommandRegistry } from '../events/interactionCreate';
export const pingCommand: CommandRegistry<true> = {
inProduction: true,
inDevelopment: true,
inDevelopment: false,
inPublic: true,
handlesInteraction: true,
+11 -13
View File
@@ -1,24 +1,22 @@
import { SlashCommandBuilder } from 'discord.js';
import { startCase } from 'lodash';
import markdownEscape from 'markdown-escape';
import getWargamingResponse from '../core/blitz/getWargamingResponse.js';
import addUsernameChoices from '../core/discord/addUsernameChoices.js';
import autocompleteUsername from '../core/discord/autocompleteUsername.js';
import embedInfo from '../core/discord/embedInfo.js';
import markdownTable, {
TableInputEntry,
} from '../core/discord/markdownTable.js';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand.js';
import { WARGAMING_APPLICATION_ID } from '../core/node/arguments.js';
import { CommandRegistry } from '../events/interactionCreate/index.js';
import { AccountAchievements } from '../types/accountAchievements.js';
import { AccountInfo } from '../types/accountInfo.js';
import getWargamingResponse from '../core/blitz/getWargamingResponse';
import addUsernameChoices from '../core/discord/addUsernameChoices';
import autocompleteUsername from '../core/discord/autocompleteUsername';
import embedInfo from '../core/discord/embedInfo';
import markdownTable, { TableInputEntry } from '../core/discord/markdownTable';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
import { WARGAMING_APPLICATION_ID } from '../core/node/arguments';
import { CommandRegistry } from '../events/interactionCreate';
import { AccountAchievements } from '../types/accountAchievements';
import { AccountInfo } from '../types/accountInfo';
type SortBy = 'name' | 'count';
export const playerAchievementsCommand: CommandRegistry = {
inProduction: true,
inDevelopment: true,
inDevelopment: false,
inPublic: true,
command: new SlashCommandBuilder()
+10 -10
View File
@@ -1,18 +1,18 @@
import { SlashCommandBuilder } from 'discord.js';
import markdownEscape from 'markdown-escape';
import getWargamingResponse from '../core/blitz/getWargamingResponse.js';
import addUsernameChoices from '../core/discord/addUsernameChoices.js';
import autocompleteUsername from '../core/discord/autocompleteUsername.js';
import embedInfo from '../core/discord/embedInfo.js';
import markdownTable from '../core/discord/markdownTable.js';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand.js';
import { WARGAMING_APPLICATION_ID } from '../core/node/arguments.js';
import { CommandRegistry } from '../events/interactionCreate/index.js';
import { AccountInfo } from '../types/accountInfo.js';
import getWargamingResponse from '../core/blitz/getWargamingResponse';
import addUsernameChoices from '../core/discord/addUsernameChoices';
import autocompleteUsername from '../core/discord/autocompleteUsername';
import embedInfo from '../core/discord/embedInfo';
import markdownTable from '../core/discord/markdownTable';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
import { WARGAMING_APPLICATION_ID } from '../core/node/arguments';
import { CommandRegistry } from '../events/interactionCreate';
import { AccountInfo } from '../types/accountInfo';
export const playerInfoCommand: CommandRegistry = {
inProduction: true,
inDevelopment: true,
inDevelopment: false,
inPublic: true,
command: new SlashCommandBuilder()
+9 -9
View File
@@ -1,17 +1,17 @@
import { SlashCommandBuilder } from 'discord.js';
import markdownEscape from 'markdown-escape';
import { BLITZ_SERVERS, BlitzServer } from '../constants/servers.js';
import getWargamingResponse from '../core/blitz/getWargamingResponse.js';
import addClanChoices from '../core/discord/addClanChoices.js';
import addServerChoices from '../core/discord/addServerChoices.js';
import embedInfo from '../core/discord/embedInfo.js';
import { WARGAMING_APPLICATION_ID } from '../core/node/arguments.js';
import { CommandRegistry } from '../events/interactionCreate/index.js';
import { ClanList } from '../types/clanList.js';
import { BLITZ_SERVERS, BlitzServer } from '../constants/servers';
import getWargamingResponse from '../core/blitz/getWargamingResponse';
import addClanChoices from '../core/discord/addClanChoices';
import addServerChoices from '../core/discord/addServerChoices';
import embedInfo from '../core/discord/embedInfo';
import { WARGAMING_APPLICATION_ID } from '../core/node/arguments';
import { CommandRegistry } from '../events/interactionCreate';
import { ClanList } from '../types/clanList';
export const searchClansCommand: CommandRegistry = {
inProduction: true,
inDevelopment: true,
inDevelopment: false,
inPublic: true,
command: new SlashCommandBuilder()
+10 -10
View File
@@ -1,18 +1,18 @@
import { SlashCommandBuilder } from 'discord.js';
import markdownEscape from 'markdown-escape';
import { BLITZ_SERVERS, BlitzServer } from '../constants/servers.js';
import getWargamingResponse from '../core/blitz/getWargamingResponse.js';
import { usernamePattern } from '../core/blitz/listPlayers.js';
import addServerChoices from '../core/discord/addServerChoices.js';
import addUsernameChoices from '../core/discord/addUsernameChoices.js';
import embedInfo from '../core/discord/embedInfo.js';
import { WARGAMING_APPLICATION_ID } from '../core/node/arguments.js';
import { CommandRegistry } from '../events/interactionCreate/index.js';
import { AccountList } from '../types/accountList.js';
import { BLITZ_SERVERS, BlitzServer } from '../constants/servers';
import getWargamingResponse from '../core/blitz/getWargamingResponse';
import { usernamePattern } from '../core/blitz/listPlayers';
import addServerChoices from '../core/discord/addServerChoices';
import addUsernameChoices from '../core/discord/addUsernameChoices';
import embedInfo from '../core/discord/embedInfo';
import { WARGAMING_APPLICATION_ID } from '../core/node/arguments';
import { CommandRegistry } from '../events/interactionCreate';
import { AccountList } from '../types/accountList';
export const searchPlayersCommand: CommandRegistry = {
inProduction: true,
inDevelopment: true,
inDevelopment: false,
inPublic: true,
command: new SlashCommandBuilder()
+6 -6
View File
@@ -1,14 +1,14 @@
import { SlashCommandBuilder } from 'discord.js';
import { go } from 'fuzzysort';
import markdownEscape from 'markdown-escape';
import { TANK_NAMES } from '../core/blitz/tankopedia.js';
import addTankChoices from '../core/discord/addTankChoices.js';
import embedInfo from '../core/discord/embedInfo.js';
import { CommandRegistry } from '../events/interactionCreate/index.js';
import { TANK_NAMES } from '../core/blitz/tankopedia';
import addTankChoices from '../core/discord/addTankChoices';
import embedInfo from '../core/discord/embedInfo';
import { CommandRegistry } from '../events/interactionCreate';
export const searchTanksCommand: CommandRegistry = {
inProduction: true,
inDevelopment: true,
inDevelopment: false,
inPublic: true,
command: new SlashCommandBuilder()
@@ -26,7 +26,7 @@ export const searchTanksCommand: CommandRegistry = {
async handler(interaction) {
const tank = interaction.options.getString('tank')!;
const limit = interaction.options.getInteger('limit') ?? 25;
const results = go(tank, TANK_NAMES, { limit }).map(
const results = go(tank, await TANK_NAMES, { limit }).map(
(result) => result.target,
);
+21 -21
View File
@@ -1,26 +1,26 @@
import { SlashCommandBuilder } from 'discord.js';
import { CYCLIC_API } from '../constants/cyclic.js';
import getWargamingResponse from '../core/blitz/getWargamingResponse.js';
import resolveTankId from '../core/blitz/resolveTankId.js';
import addStatTypeSubCommandGroups from '../core/discord/addStatTypeSubCommandGroups.js';
import autocompleteTanks from '../core/discord/autocompleteTanks.js';
import autocompleteUsername from '../core/discord/autocompleteUsername.js';
import interactionToURL from '../core/discord/interactionToURL.js';
import linkButton from '../core/discord/linkButton.js';
import primaryButton from '../core/discord/primaryButton.js';
import resolvePeriodFromButton from '../core/discord/resolvePeriodFromButton.js';
import resolvePeriodFromCommand from '../core/discord/resolvePeriodFromCommand.js';
import resolvePlayerFromButton from '../core/discord/resolvePlayerFromButton.js';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand.js';
import { WARGAMING_APPLICATION_ID } from '../core/node/arguments.js';
import { CommandRegistry } from '../events/interactionCreate/index.js';
import stats from '../renderers/stats.js';
import { StatType } from '../renderers/statsfull.js';
import { AccountInfo } from '../types/accountInfo.js';
import { CYCLIC_API } from '../constants/cyclic';
import getWargamingResponse from '../core/blitz/getWargamingResponse';
import resolveTankId from '../core/blitz/resolveTankId';
import addStatTypeSubCommandGroups from '../core/discord/addStatTypeSubCommandGroups';
import autocompleteTanks from '../core/discord/autocompleteTanks';
import autocompleteUsername from '../core/discord/autocompleteUsername';
import interactionToURL from '../core/discord/interactionToURL';
import linkButton from '../core/discord/linkButton';
import primaryButton from '../core/discord/primaryButton';
import resolvePeriodFromButton from '../core/discord/resolvePeriodFromButton';
import resolvePeriodFromCommand from '../core/discord/resolvePeriodFromCommand';
import resolvePlayerFromButton from '../core/discord/resolvePlayerFromButton';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
import { WARGAMING_APPLICATION_ID } from '../core/node/arguments';
import { CommandRegistry } from '../events/interactionCreate';
import { StatType } from '../renderers/fullStats';
import stats from '../renderers/stats';
import { AccountInfo } from '../types/accountInfo';
export const statsCommand: CommandRegistry = {
inProduction: true,
inDevelopment: true,
inDevelopment: false,
inPublic: true,
command: addStatTypeSubCommandGroups(
@@ -34,7 +34,7 @@ export const statsCommand: CommandRegistry = {
true,
) as StatType;
const player = await resolvePlayerFromCommand(interaction);
const period = resolvePeriodFromCommand(interaction);
const period = resolvePeriodFromCommand(player.server, interaction);
const tankIdRaw = interaction.options.getString('tank')!;
const tankId =
commandGroup === 'tank' ? await resolveTankId(tankIdRaw) : null;
@@ -72,8 +72,8 @@ export const statsCommand: CommandRegistry = {
const url = new URL(`${CYCLIC_API}/${interaction.customId}`);
const path = url.pathname.split('/').filter(Boolean);
const commandGroup = path[1] as StatType;
const period = resolvePeriodFromButton(interaction);
const player = await resolvePlayerFromButton(interaction);
const period = resolvePeriodFromButton(player.server, interaction);
return await stats(
commandGroup,
+27 -16
View File
@@ -1,29 +1,40 @@
import { SlashCommandBuilder } from 'discord.js';
import getWargamingResponse from '../core/blitz/getWargamingResponse.js';
import addUsernameChoices from '../core/discord/addUsernameChoices.js';
import autocompleteUsername from '../core/discord/autocompleteUsername.js';
import interactionToURL from '../core/discord/interactionToURL.js';
import linkButton from '../core/discord/linkButton.js';
import primaryButton from '../core/discord/primaryButton.js';
import resolvePlayerFromButton from '../core/discord/resolvePlayerFromButton.js';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand.js';
import { WARGAMING_APPLICATION_ID } from '../core/node/arguments.js';
import { CommandRegistry } from '../events/interactionCreate/index.js';
import today from '../renderers/today.js';
import { AccountInfo } from '../types/accountInfo.js';
import { CYCLIC_API } from '../constants/cyclic';
import getWargamingResponse from '../core/blitz/getWargamingResponse';
import addUsernameChoices from '../core/discord/addUsernameChoices';
import autocompleteUsername from '../core/discord/autocompleteUsername';
import interactionToURL from '../core/discord/interactionToURL';
import linkButton from '../core/discord/linkButton';
import primaryButton from '../core/discord/primaryButton';
import resolvePlayerFromButton from '../core/discord/resolvePlayerFromButton';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
import { WARGAMING_APPLICATION_ID } from '../core/node/arguments';
import { CommandRegistry } from '../events/interactionCreate';
import today from '../renderers/today';
import { AccountInfo } from '../types/accountInfo';
export const todayCommand: CommandRegistry = {
inProduction: true,
inDevelopment: true,
inDevelopment: false,
inPublic: true,
command: new SlashCommandBuilder()
.setName('today')
.setDescription('A general daily breakdown of your performance')
.addStringOption(addUsernameChoices),
.addStringOption(addUsernameChoices)
.addIntegerOption((option) =>
option
.setName('limit')
.setDescription(
'The maximum number of tanks to display (default: Infinity)',
)
.setRequired(false)
.setMinValue(1),
),
async handler(interaction) {
const player = await resolvePlayerFromCommand(interaction);
const limit = interaction.options.getInteger('limit') ?? Infinity;
const { nickname } = (
await getWargamingResponse<AccountInfo>(
`https://api.wotblitz.${player.server}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${player.id}`,
@@ -32,9 +43,9 @@ export const todayCommand: CommandRegistry = {
const path = interactionToURL(interaction, player);
return [
await today(player),
await today(player, limit, false),
primaryButton(path, 'Refresh'),
// linkButton(`${CYCLIC_API}/${path}`, 'Embed'),
linkButton(`${CYCLIC_API}/${path}`, 'Embed'),
linkButton(
`https://www.blitzstars.com/player/${player.server}/${nickname}`,
'BlitzStars',
+11 -11
View File
@@ -1,20 +1,20 @@
import { SlashCommandBuilder } from 'discord.js';
import markdownEscape from 'markdown-escape';
import discord from '../../discord.json' assert { type: 'json' };
import getWargamingResponse from '../core/blitz/getWargamingResponse.js';
import addUsernameChoices from '../core/discord/addUsernameChoices.js';
import autocompleteUsername from '../core/discord/autocompleteUsername.js';
import embedNegative from '../core/discord/embedNegative.js';
import embedPositive from '../core/discord/embedPositive.js';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand.js';
import { WARGAMING_APPLICATION_ID } from '../core/node/arguments.js';
import { CommandRegistry } from '../events/interactionCreate/index.js';
import { AccountInfo } from '../types/accountInfo.js';
import { PlayerClanData } from '../types/playerClanData.js';
import getWargamingResponse from '../core/blitz/getWargamingResponse';
import addUsernameChoices from '../core/discord/addUsernameChoices';
import autocompleteUsername from '../core/discord/autocompleteUsername';
import embedNegative from '../core/discord/embedNegative';
import embedPositive from '../core/discord/embedPositive';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
import { WARGAMING_APPLICATION_ID } from '../core/node/arguments';
import { CommandRegistry } from '../events/interactionCreate';
import { AccountInfo } from '../types/accountInfo';
import { PlayerClanData } from '../types/playerClanData';
export const verifyCommand: CommandRegistry = {
inProduction: true,
inDevelopment: true,
inDevelopment: false,
inPublic: true,
command: new SlashCommandBuilder()
@@ -1,6 +1,6 @@
import { Percentile } from '../../../../../constants/percentiles.js';
import hslToHex from '../../../../../core/node/hslToHex.js';
import { PERCENTILE_COLORS } from '../../../../PercentileIndicator.js';
import { Percentile } from '../../../../../constants/percentiles';
import hslToHex from '../../../../../core/node/hslToHex';
import { PERCENTILE_COLORS } from '../../../../PercentileIndicator';
export enum GlowSide {
Top,
@@ -1,8 +1,8 @@
import { Percentile } from '../../../../constants/percentiles.js';
import getWN8Percentile from '../../../../core/blitz/getWN8Percentile.js';
import { theme } from '../../../../stitches.config.js';
import { PERCENTILE_COLORS } from '../../../PercentileIndicator.js';
import { Glow, GlowSide } from './components/Glow.js';
import { Percentile } from '../../../../constants/percentiles';
import getWN8Percentile from '../../../../core/blitz/getWN8Percentile';
import { theme } from '../../../../stitches.config';
import { PERCENTILE_COLORS } from '../../../PercentileIndicator';
import { Glow, GlowSide } from './components/Glow';
export interface WN8DisplayProps {
WN8?: number;
@@ -1,4 +1,4 @@
import { theme } from '../../../stitches.config.js';
import { theme } from '../../../stitches.config';
export enum WN8SurroundingStatAlign {
Left,
+6 -4
View File
@@ -1,9 +1,9 @@
import { AllStats, SupplementaryStats } from '../../types/accountInfo.js';
import { WN8Display } from './components/WN8Display/index.js';
import { AllStats, SupplementaryStats } from '../../types/accountInfo';
import { WN8Display } from './components/WN8Display';
import {
WN8SurroundingStat,
WN8SurroundingStatAlign,
} from './components/WN8SurroundingStat.js';
} from './components/WN8SurroundingStat';
export interface AllStatsOverviewProps {
stats: AllStats;
@@ -69,7 +69,9 @@ export default function AllStatsOverview({
>
<WN8SurroundingStat
label="Tier"
value={supplementaryStats.tier.toFixed(2)}
value={
supplementaryStats.tier ? supplementaryStats.tier.toFixed(2) : '--'
}
/>
<WN8SurroundingStat
label="Survival"
+1 -1
View File
@@ -6,7 +6,7 @@ export interface RootProps {
export function Root({ children }: RootProps) {
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
{children}
</div>
);
+30 -24
View File
@@ -1,6 +1,7 @@
import getWN8Percentile from '../../../core/blitz/getWN8Percentile.js';
import { RowDiscriminator } from './RowDiscriminator.js';
import { RowStat } from './RowStat.js';
import getWN8Percentile from '../../../core/blitz/getWN8Percentile';
import { theme } from '../../../stitches.config';
import { RowDiscriminator } from './RowDiscriminator';
import { RowStat } from './RowStat';
export interface RowProps {
name: string;
@@ -10,14 +11,17 @@ export interface RowProps {
careerWN8?: number;
damage: number;
careerDamage: number;
survival: number;
careerSurvival: number;
battles: number;
careerBattles: number;
icon?: string;
minimized: boolean;
isListing: boolean;
naked?: boolean;
}
export function Row({
isListing,
minimized,
name,
winrate,
careerWinrate,
@@ -25,44 +29,46 @@ export function Row({
careerWN8,
damage,
careerDamage,
survival,
careerSurvival,
battles,
careerBattles,
icon,
naked,
}: RowProps) {
return (
<div style={{ display: 'flex', gap: 16 }}>
<RowDiscriminator name={name} icon={icon} />
<div
style={{
display: 'flex',
borderRadius: 4,
backgroundColor: isListing
? theme.colors.componentInteractive
: theme.colors.appBackground2,
padding: 8,
}}
>
<RowDiscriminator minimized={minimized} name={name} icon={icon} />
<RowStat
name="Winrate"
minimized={minimized}
name={`Winrate • ${(careerWinrate * 100).toFixed(2)}%`}
value={`${(winrate * 100).toFixed(2)}%`}
career={`${(careerWinrate * 100).toFixed(2)}%`}
delta={winrate - careerWinrate}
/>
<RowStat
name="WN8"
minimized={minimized}
name={`WN8 • ${careerWN8 === undefined ? '--' : careerWN8.toFixed(0)}`}
value={WN8 === undefined ? '--' : WN8.toFixed(0)}
career={careerWN8 === undefined ? '--' : careerWN8.toFixed(0)}
percentile={WN8 === undefined ? undefined : getWN8Percentile(WN8)}
/>
<RowStat
name="Damage"
value={damage.toFixed(2)}
career={careerDamage.toFixed(2)}
minimized={minimized}
name={`Damage • ${careerDamage.toFixed(0)}`}
value={damage.toFixed(0)}
delta={damage - careerDamage}
/>
<RowStat
name="Survival"
value={`${(survival * 100).toFixed(2)}%`}
career={`${(careerSurvival * 100).toFixed(2)}%`}
delta={survival - careerSurvival}
/>
<RowStat
name="Battles"
minimized={minimized}
name={`Battles • ${careerBattles.toFixed(0)}`}
value={battles.toFixed(0)}
career={careerBattles.toFixed(0)}
/>
</div>
);
@@ -1,11 +1,16 @@
import { theme } from '../../../stitches.config.js';
import { theme } from '../../../stitches.config';
export interface RowDiscriminatorProps {
name: string;
icon?: string;
minimized: boolean;
}
export function RowDiscriminator({ name, icon }: RowDiscriminatorProps) {
export function RowDiscriminator({
name,
icon,
minimized,
}: RowDiscriminatorProps) {
return (
<div
style={{
@@ -19,7 +24,7 @@ export function RowDiscriminator({ name, icon }: RowDiscriminatorProps) {
justifyContent: 'center',
}}
>
{icon && (
{!minimized && icon && (
<img
src={icon}
height={0}
+11 -14
View File
@@ -1,21 +1,21 @@
import { Percentile } from '../../../constants/percentiles.js';
import { theme } from '../../../stitches.config.js';
import PercentileIndicator from '../../PercentileIndicator.js';
import { Percentile } from '../../../constants/percentiles';
import { theme } from '../../../stitches.config';
import PercentileIndicator from '../../PercentileIndicator';
export interface RowStatProps {
name: string;
value: string;
career: string;
value: string | number;
delta?: number;
percentile?: Percentile;
minimized?: boolean;
}
export function RowStat({
name,
value,
career,
delta,
percentile,
minimized,
}: RowStatProps) {
return (
<div
@@ -28,10 +28,6 @@ export function RowStat({
flex: 1,
}}
>
<span style={{ color: theme.colors.textLowContrast, fontSize: 12 }}>
{career}
</span>
<div
style={{
display: 'flex',
@@ -58,16 +54,17 @@ export function RowStat({
style={{
color: theme.colors.textHighContrast,
fontSize: 16,
fontWeight: 'bold',
}}
>
{value}
</span>
</div>
<span style={{ color: theme.colors.textLowContrast, fontSize: 12 }}>
{name}
</span>
{!minimized && (
<span style={{ color: theme.colors.textLowContrast, fontSize: 12 }}>
{name}
</span>
)}
</div>
);
}
+4 -4
View File
@@ -1,4 +1,4 @@
export * from './components/Root.js';
export * from './components/Row.js';
export * from './components/RowDiscriminator.js';
export * from './components/RowStat.js';
export * from './components/Root';
export * from './components/Row';
export * from './components/RowDiscriminator';
export * from './components/RowStat';
-57
View File
@@ -1,57 +0,0 @@
import getWN8Percentile from '../core/blitz/getWN8Percentile.js';
import isNumber from '../core/node/isNumber.js';
import { AllStats, SupplementaryStats } from '../types/accountInfo.js';
import GenericStats from './GenericStats/index.js';
export interface GenericAllStatsProps {
stats: AllStats;
supplementaryStats?: SupplementaryStats;
}
export default function GenericAllStats({
stats,
supplementaryStats,
}: GenericAllStatsProps) {
const damageRatio = stats.damage_dealt / stats.damage_received;
const killsToDeathRatio =
stats.frags / (stats.battles - stats.survived_battles);
return (
<GenericStats
stats={[
['Winrate', `${(100 * (stats.wins / stats.battles)).toFixed(2)}%`],
[
'WN8',
supplementaryStats
? isNumber(supplementaryStats.WN8)
? supplementaryStats.WN8!.toFixed(0)
: '--'
: undefined,
isNumber(supplementaryStats?.WN8)
? getWN8Percentile(supplementaryStats!.WN8!)
: undefined,
],
[
'Survival',
`${(100 * (stats.survived_battles / stats.battles)).toFixed(2)}%`,
],
['Accuracy', `${((stats.hits / stats.shots) * 100).toFixed(2)}%`],
['Battles', stats.battles],
['Wins', stats.wins],
['Losses', stats.losses],
['Average damage', (stats.damage_dealt / stats.battles).toFixed(0)],
['Average tier', supplementaryStats?.tier.toFixed(2)],
['Average XP', (stats.xp / stats.battles).toFixed(0)],
['Average shots', (stats.shots / stats.battles).toFixed(2)],
['Average hits', (stats.hits / stats.battles).toFixed(2)],
['Average kills', (stats.frags / stats.battles).toFixed(2)],
['Average spots', (stats.spotted / stats.battles).toFixed(2)],
['Damage ratio', isFinite(damageRatio) ? damageRatio.toFixed(2) : '--'],
[
'Kills to death ratio',
isFinite(killsToDeathRatio) ? killsToDeathRatio.toFixed(2) : '--',
],
]}
/>
);
}
@@ -0,0 +1,91 @@
import { Percentile } from '../../../constants/percentiles';
import { theme } from '../../../stitches.config';
import PercentileIndicator from '../../PercentileIndicator';
export interface CardItem {
label: string;
value: string | number | undefined;
percentile?: Percentile;
}
export interface CardProps {
items: CardItem[];
title: string;
}
export function Card({ items, title }: CardProps) {
return (
<div
style={{
display: 'flex',
flexDirection: 'column',
gap: 16,
flex: 1,
borderRadius: 4,
backgroundColor: theme.colors.componentInteractive,
padding: 8,
}}
>
<span
style={{
color: theme.colors.textHighContrast,
fontSize: 24,
}}
>
{title}
</span>
<div
style={{
display: 'flex',
flexDirection: 'column',
gap: 8,
}}
>
{items.map((item) => (
<div
style={{
display: 'flex',
gap: 8,
alignItems: 'center',
justifyContent: 'center',
}}
>
<span style={{ fontSize: 16, color: theme.colors.textLowContrast }}>
{item.label}
</span>
<div
style={{
flex: 1,
height: 1,
borderRadius: 0.5,
backgroundColor: theme.colors.borderNonInteractive,
}}
/>
<div
style={{
display: 'flex',
gap: 4,
alignItems: 'center',
justifyContent: 'center',
}}
>
{item.percentile !== undefined && (
<PercentileIndicator percentile={item.percentile} />
)}
<span
style={{ fontSize: 16, color: theme.colors.textHighContrast }}
>
{item.value === undefined || Number.isNaN(item.value)
? '--'
: item.value}
</span>
</div>
</div>
))}
</div>
</div>
);
}
@@ -0,0 +1,17 @@
export interface RootProps {
children: React.ReactNode;
}
export function Root({ children }: RootProps) {
return (
<div
style={{
display: 'flex',
flexDirection: 'column',
gap: 8,
}}
>
{children}
</div>
);
}
@@ -0,0 +1,7 @@
export interface RowProps {
children: React.ReactNode;
}
export function Row({ children }: RowProps) {
return <div style={{ display: 'flex', gap: 8 }}>{children}</div>;
}
+124
View File
@@ -0,0 +1,124 @@
import getWN8Percentile from '../../core/blitz/getWN8Percentile';
import isNumber from '../../core/node/isNumber';
import { AllStats, SupplementaryStats } from '../../types/accountInfo';
import { Card } from './components/Card';
import { Root } from './components/Root';
import { Row } from './components/Row';
// TODO: remove the .js endings
export interface GenericAllStatsProps {
stats: AllStats;
supplementaryStats?: SupplementaryStats;
}
export default function GenericAllStats({
stats,
supplementaryStats,
}: GenericAllStatsProps) {
const damageRatio = stats.damage_dealt / stats.damage_received;
const killsToDeathRatio =
stats.frags / (stats.battles - stats.survived_battles);
return (
<Root>
<Row>
<Card
title="Games"
items={[
{
label: 'Winrate',
value: `${(100 * (stats.wins / stats.battles)).toFixed(2)}%`,
},
{
label: 'Battles',
value: stats.battles,
},
{
label: 'Wins / Losses',
value: `${stats.wins} / ${stats.losses}`,
},
]}
/>
<Card
title="Efficiency"
items={[
{
label: 'WN8',
value:
supplementaryStats && isNumber(supplementaryStats.WN8)
? supplementaryStats.WN8!.toFixed(0)
: undefined,
percentile: isNumber(supplementaryStats?.WN8)
? getWN8Percentile(supplementaryStats!.WN8!)
: undefined,
},
{
label: 'Survival',
value: `${(
100 *
(stats.survived_battles / stats.battles)
).toFixed(2)}%`,
},
{
label: 'Accuracy',
value: `${((stats.hits / stats.shots) * 100).toFixed(2)}%`,
},
]}
/>
</Row>
<Row>
<Card
title="Lethality"
items={[
{
label: 'Average damage',
value: (stats.damage_dealt / stats.battles).toFixed(0),
},
{
label: 'Damage per tier',
value: supplementaryStats?.tier
? (
stats.damage_dealt /
stats.battles /
supplementaryStats.tier
).toFixed(0)
: undefined,
},
{
label: 'Damage ratio',
value: isFinite(damageRatio) ? damageRatio.toFixed(2) : undefined,
},
{
label: 'Average kills',
value: (stats.frags / stats.battles).toFixed(0),
},
]}
/>
<Card
title="Efficiency"
items={[
{
label: 'Average tier',
value: supplementaryStats?.tier?.toFixed(2),
},
{
label: 'Average spots',
value: (stats.spotted / stats.battles).toFixed(2),
},
{
label: 'Average XP',
value: (stats.xp / stats.battles).toFixed(0),
},
{
label: 'Kills to death ratio',
value: isFinite(killsToDeathRatio)
? killsToDeathRatio.toFixed(2)
: undefined,
},
]}
/>
</Row>
</Root>
);
}
@@ -1,5 +1,5 @@
import { Stat } from '../index.js';
import GenericStatsRow from './GenericStatsRow.js';
import { Stat } from '..';
import GenericStatsRow from './GenericStatsRow';
export interface GenericStatsColumnProps {
stats: Stat[];
@@ -1,6 +1,6 @@
import { theme } from '../../../stitches.config.js';
import PercentileIndicator from '../../PercentileIndicator.js';
import { Stat } from '../index.js';
import { Stat } from '..';
import { theme } from '../../../stitches.config';
import PercentileIndicator from '../../PercentileIndicator';
export interface GenericStatsRowProps {
stat: Stat;
+2 -2
View File
@@ -1,5 +1,5 @@
import { Percentile } from '../../constants/percentiles.js';
import GenericStatsColumn from './components/GenericStatsColumn.js';
import { Percentile } from '../../constants/percentiles';
import GenericStatsColumn from './components/GenericStatsColumn';
export type Stat =
| [string, string | number | undefined]
+1 -1
View File
@@ -1,5 +1,5 @@
import { blue, green, red, slateDark } from '@radix-ui/colors';
import { GRAPH_HEIGHT, GRAPH_WIDTH } from './Root.js';
import { GRAPH_HEIGHT, GRAPH_WIDTH } from './Root';
export enum LineColor {
Red,
+3 -3
View File
@@ -1,6 +1,6 @@
import { times } from 'lodash';
import { theme } from '../../../stitches.config.js';
import { MarginInputProps } from './Root.js';
import { theme } from '../../../stitches.config';
import { MarginInputProps } from './Root';
export enum MarginOrientation {
Vertical,
@@ -37,7 +37,7 @@ export function Margin({
key={index}
style={{
fontSize: 16,
color: theme.colors.textLowContrast,
color: theme.colors.textHighContrast,
}}
>
{`${(
+19 -4
View File
@@ -1,7 +1,7 @@
import { times } from 'lodash';
import { ReactNode } from 'react';
import { theme } from '../../../stitches.config.js';
import { Margin, MarginOrientation } from './Margin.js';
import { theme } from '../../../stitches.config';
import { Margin, MarginOrientation } from './Margin';
export const GRAPH_WIDTH = 544;
export const GRAPH_HEIGHT = 320;
@@ -20,6 +20,7 @@ export interface RootProps {
separations?: number;
xMinLabel: string;
xMaxLabel: string;
xTitle: string;
}
export function Root({
@@ -29,6 +30,7 @@ export function Root({
separations = 5,
xMinLabel,
xMaxLabel,
xTitle,
}: RootProps) {
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
@@ -39,10 +41,10 @@ export function Root({
justifyContent: 'space-between',
}}
>
<span style={{ fontSize: 16, color: theme.colors.textLowContrast }}>
<span style={{ fontSize: 16, color: theme.colors.textHighContrast }}>
{xMinLabel}
</span>
<span style={{ fontSize: 16, color: theme.colors.textLowContrast }}>
<span style={{ fontSize: 16, color: theme.colors.textHighContrast }}>
{xMaxLabel}
</span>
</div>
@@ -88,6 +90,19 @@ export function Root({
orientation={MarginOrientation.Horizontal}
/>
)}
<div
style={{
paddingLeft: 64,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<span style={{ fontSize: 16, color: theme.colors.textLowContrast }}>
{xTitle}
</span>
</div>
</div>
);
}
+3 -3
View File
@@ -1,3 +1,3 @@
export * from './components/Line.js';
export * from './components/Margin.js';
export * from './components/Root.js';
export * from './components/Line';
export * from './components/Margin';
export * from './components/Root';
+1 -1
View File
@@ -1,4 +1,4 @@
import { theme } from '../stitches.config.js';
import { theme } from '../stitches.config';
export enum NoDataType {
BattlesInPeriod,
+1 -1
View File
@@ -10,7 +10,7 @@ import {
teal,
yellow,
} from '@radix-ui/colors';
import { Percentile } from '../constants/percentiles.js';
import { Percentile } from '../constants/percentiles';
export interface PercentileIndicatorProps {
percentile: Percentile;
+1 -1
View File
@@ -1,5 +1,5 @@
import { ReactNode } from 'react';
import { theme } from '../stitches.config.js';
import { theme } from '../stitches.config';
export enum PoweredByType {
BlitzStars,
@@ -0,0 +1,20 @@
import { ReactNode } from 'react';
export interface ColumnProps {
children: ReactNode;
}
export function Column({ children }: ColumnProps) {
return (
<div
style={{
display: 'flex',
flexDirection: 'column',
gap: 8,
flex: 1,
}}
>
{children}
</div>
);
}
+60 -17
View File
@@ -1,35 +1,78 @@
import { TANK_ICONS, TankType } from '../../../core/blitz/tankopedia.js';
import {
TANK_ICONS,
TANK_ICONS_COLLECTOR,
TANK_ICONS_PREMIUM,
} from '../../../core/blitz/tankopedia';
import { theme } from '../../../stitches.config';
export interface ItemProps {
icon?: string;
type: TankType;
name: string;
export enum TreeType {
TechTree,
Premium,
Collector,
}
export function Item({ icon, type, name }: ItemProps) {
export interface ItemProps {
image?: string;
tankType?: string;
name: string;
treeType: TreeType;
}
const TREE_TYPE_COLOR = {
[TreeType.TechTree]: '',
[TreeType.Premium]: '_amber',
[TreeType.Collector]: '_blue',
} as const;
const TREE_TYPE_ICONS = {
[TreeType.TechTree]: TANK_ICONS,
[TreeType.Premium]: TANK_ICONS_PREMIUM,
[TreeType.Collector]: TANK_ICONS_COLLECTOR,
};
export function Item({ image, tankType, name, treeType }: ItemProps) {
return (
<div
style={{
flex: 1,
minWidth: 128,
height: 128,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
height: 32,
paddingLeft: 8,
paddingRight: 8,
overflow: 'hidden',
alignItems: 'center',
borderRadius: 4,
backgroundColor:
theme.colors[`componentInteractive${TREE_TYPE_COLOR[treeType]}`],
}}
>
{icon && <img src={icon} style={{ flex: 1, objectFit: 'contain' }} />}
<div style={{ display: 'flex', margin: 'auto', gap: 4 }}>
<img src={TANK_ICONS[type]} style={{ width: 14, height: 14 }} />
<div
style={{
display: 'flex',
gap: 4,
flex: 1,
alignItems: 'center',
justifyContent: 'flex-start',
overflow: 'hidden',
}}
>
{tankType && (
<img
src={TREE_TYPE_ICONS[treeType][tankType]}
style={{ width: 14, height: 14 }}
/>
)}
<span
style={{ fontSize: 16, textAlign: 'center', whiteSpace: 'nowrap' }}
style={{
fontSize: 16,
whiteSpace: 'nowrap',
color: theme.colors[`textLowContrast${TREE_TYPE_COLOR[treeType]}`],
}}
>
{name}
</span>
</div>
{image && <img src={image} style={{ flex: 1 }} />}
</div>
);
}
+1 -4
View File
@@ -9,10 +9,7 @@ export function Root({ children }: RootProps) {
<div
style={{
display: 'flex',
flexWrap: 'wrap',
gap: 16,
alignItems: 'center',
justifyContent: 'center',
gap: 8,
}}
>
{children}
+3 -2
View File
@@ -1,2 +1,3 @@
export * from './components/Item.js';
export * from './components/Root.js';
export * from './components/Column';
export * from './components/Item';
export * from './components/Root';
+4 -4
View File
@@ -1,5 +1,5 @@
import { TIER_ROMAN_NUMERALS, Tier } from '../core/blitz/tankopedia.js';
import { themeLight } from '../stitches.config.js';
import { TIER_ROMAN_NUMERALS, Tier } from '../core/blitz/tankopedia';
import { theme } from '../stitches.config';
export type TierWeightsRecord = Partial<Record<Tier, number>>;
@@ -14,9 +14,9 @@ export default function TierWeights({ weights }: TierWeightsProps) {
<div
key={tier}
style={{
backgroundColor: themeLight.colors.appBackground1,
backgroundColor: theme.colors.appBackground1_light,
flex: weight,
color: themeLight.colors.textHighContrast,
color: theme.colors.textHighContrast_light,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
+2 -2
View File
@@ -1,5 +1,5 @@
import isDev from '../core/node/isDev.js';
import { theme } from '../stitches.config.js';
import isDev from '../core/node/isDev';
import { theme } from '../stitches.config';
export interface TitleBarProps {
name: string;
+7 -3
View File
@@ -1,11 +1,12 @@
import { ReactNode } from 'react';
import { theme } from '../stitches.config.js';
import { theme } from '../stitches.config';
export interface WrapperProps {
children: ReactNode;
naked?: boolean;
}
export default function Wrapper({ children }: WrapperProps) {
export default function Wrapper({ children, naked }: WrapperProps) {
return (
<div
style={{
@@ -14,8 +15,11 @@ export default function Wrapper({ children }: WrapperProps) {
padding: 16,
gap: 32,
width: 640,
background: 'url(https://i.imgur.com/PhS06NJ.png)',
color: theme.colors.textHighContrast,
...(!naked && {
background: 'url(https://i.imgur.com/PhS06NJ.png)',
}),
}}
>
{children}
+1 -1
View File
@@ -1,4 +1,4 @@
import { AllStats } from '../../types/accountInfo.js';
import { AllStats } from '../../types/accountInfo';
/**
* Formula from https://web.archive.org/web/20190105064817/http://wiki.wnefficiency.net/pages/WN8
+1 -1
View File
@@ -1,4 +1,4 @@
import { AllStats } from '../../types/accountInfo.js';
import { AllStats } from '../../types/accountInfo';
export default function diffStats(stats1: AllStats, stats2: AllStats) {
function diff(value: (allStats: AllStats) => number) {
+4 -4
View File
@@ -1,7 +1,7 @@
import { BlitzServer } from '../../constants/servers.js';
import { serverAndIdPattern } from '../discord/resolvePlayerFromCommand.js';
import throwError from '../node/throwError.js';
import listClans from './listClans.js';
import { BlitzServer } from '../../constants/servers';
import { serverAndIdPattern } from '../discord/resolvePlayerFromCommand';
import throwError from '../node/throwError';
import listClans from './listClans';
export default async function getBlitzClan(clan: string) {
if (serverAndIdPattern.test(clan)) {
+5 -5
View File
@@ -1,8 +1,8 @@
import { BLITZ_SERVERS, BlitzServer } from '../../constants/servers.js';
import { TanksStats } from '../../types/tanksStats.js';
import { WARGAMING_APPLICATION_ID } from '../node/arguments.js';
import throwError from '../node/throwError.js';
import getWargamingResponse from './getWargamingResponse.js';
import { BLITZ_SERVERS, BlitzServer } from '../../constants/servers';
import { TanksStats } from '../../types/tanksStats';
import { WARGAMING_APPLICATION_ID } from '../node/arguments';
import throwError from '../node/throwError';
import getWargamingResponse from './getWargamingResponse';
export default async function getTankStats(server: BlitzServer, id: number) {
const tankStats = await getWargamingResponse<TanksStats>(
+11
View File
@@ -0,0 +1,11 @@
import { TreeType } from '../../components/Tanks';
import { tankopedia } from './tankopedia';
export default async function getTreeType(id: number) {
const entry = (await tankopedia)[id];
if (entry?.is_collectible) return TreeType.Collector;
if (entry?.is_premium) return TreeType.Premium;
return TreeType.TechTree;
}
+1 -1
View File
@@ -1,4 +1,4 @@
import { Percentile } from '../../constants/percentiles.js';
import { Percentile } from '../../constants/percentiles';
export const WN8_PERCENTILES: [number, Percentile][] = [
[0, Percentile.VeryBad],
+1 -1
View File
@@ -1,4 +1,4 @@
import throwError from '../node/throwError.js';
import throwError from '../node/throwError';
export type WargamingResponse<Data extends object> =
| {
+4 -4
View File
@@ -1,7 +1,7 @@
import { BlitzServer } from '../../constants/servers.js';
import { Clan, ClanList } from '../../types/clanList.js';
import { WARGAMING_APPLICATION_ID } from '../node/arguments.js';
import getWargamingResponse from './getWargamingResponse.js';
import { BlitzServer } from '../../constants/servers';
import { Clan, ClanList } from '../../types/clanList';
import { WARGAMING_APPLICATION_ID } from '../node/arguments';
import getWargamingResponse from './getWargamingResponse';
export type ClanListWithServer = (Clan & {
server: 'com' | 'eu' | 'asia';
+4 -4
View File
@@ -1,7 +1,7 @@
import { BlitzServer } from '../../constants/servers.js';
import { Account, AccountList } from '../../types/accountList.js';
import { WARGAMING_APPLICATION_ID } from '../node/arguments.js';
import getWargamingResponse from './getWargamingResponse.js';
import { BlitzServer } from '../../constants/servers';
import { Account, AccountList } from '../../types/accountList';
import { WARGAMING_APPLICATION_ID } from '../node/arguments';
import getWargamingResponse from './getWargamingResponse';
export type AccountListWithServer = (Account & {
server: 'com' | 'eu' | 'asia';
+3 -3
View File
@@ -1,12 +1,12 @@
import { go } from 'fuzzysort';
import throwError from '../node/throwError.js';
import { TANKS, tankopedia } from './tankopedia.js';
import throwError from '../node/throwError';
import { TANKS, tankopedia } from './tankopedia';
export default async function resolveTankId(tank: string) {
const number = parseInt(tank);
if (Number.isNaN(number)) {
const searchResult = go(tank, TANKS, { keys: ['name'], limit: 1 });
const searchResult = go(tank, await TANKS, { keys: ['name'], limit: 1 });
if (searchResult.length === 0) {
throw throwError(
+2 -2
View File
@@ -1,6 +1,6 @@
import { tankopedia } from './tankopedia.js';
import { tankopedia } from './tankopedia';
export default async function resolveTankName(tankId: number) {
const tankopediaName = (await tankopedia)[tankId].name;
const tankopediaName = (await tankopedia)[tankId]?.name;
return tankopediaName ? tankopediaName : `Unknown Tank ${tankId}`;
}
+2 -2
View File
@@ -1,5 +1,5 @@
import { AllStats } from '../../types/accountInfo.js';
import { emptyAllStats } from '../blitzstars/getTankStatsDiffed.js';
import { AllStats } from '../../types/accountInfo';
import { emptyAllStats } from '../blitzstars/getDiffedTankStats';
export default function sumStats(allStatsCollection: AllStats[]) {
return allStatsCollection.reduce((accumulated, current) => {
+27 -9
View File
@@ -1,19 +1,18 @@
export type TankType = 'AT-SPG' | 'lightTank' | 'mediumTank' | 'heavyTank';
export interface TankopediaEntry {
name: string;
nation: string;
is_premium: boolean;
is_collectible: boolean;
tier: number;
cost: { price_credit: number; price_gold: number };
images: { preview: string; normal: string };
tank_id: number;
type: TankType;
type: string;
description: string;
}
export interface Tankopedia {
[id: number]: TankopediaEntry;
[id: number]: TankopediaEntry | undefined;
}
console.log('Caching tankopedia...');
@@ -23,18 +22,37 @@ export const tankopedia = fetch('https://www.blitzstars.com/bs-tankopedia.json')
console.log('Tankopedia cached');
return (wrapperTankopedia as { data: Tankopedia }).data;
});
const entries = Object.entries(tankopedia);
export const TANK_IDS = entries.map(([, { tank_id }]) => tank_id as number);
export const TANKS = entries.map(([, entry]) => entry as TankopediaEntry);
export const TANK_NAMES = entries.map(([, { name }]) => name);
const entries = new Promise<TankopediaEntry[]>(async (resolve) => {
resolve(Object.entries(await tankopedia).map(([, entry]) => entry));
});
export const TANKS = new Promise<TankopediaEntry[]>(async (resolve) => {
resolve((await entries).map((entry) => entry));
});
export const TANK_NAMES = new Promise<string[]>(async (resolve) => {
resolve((await entries).map(({ name }) => name));
});
export const TANK_ICONS: Record<TankType, string> = {
export const TANK_ICONS: Record<string, string> = {
'AT-SPG': 'https://i.imgur.com/BIHSEH0.png',
lightTank: 'https://i.imgur.com/CSNha5V.png',
mediumTank: 'https://i.imgur.com/wvf3ltm.png',
heavyTank: 'https://i.imgur.com/ECeqlZa.png',
};
export const TANK_ICONS_PREMIUM: Record<string, string> = {
'AT-SPG': 'https://i.imgur.com/TCu3EdR.png',
lightTank: 'https://i.imgur.com/zdkpTRb.png',
mediumTank: 'https://i.imgur.com/3z7eHX6.png',
heavyTank: 'https://i.imgur.com/P3vbmyA.png',
};
export const TANK_ICONS_COLLECTOR: Record<string, string> = {
'AT-SPG': 'https://i.imgur.com/WTjeirB.png',
lightTank: 'https://i.imgur.com/EwhtKkU.png',
mediumTank: 'https://i.imgur.com/u8YDMBh.png',
heavyTank: 'https://i.imgur.com/8xRf3nc.png',
};
export type Tier = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;
export const TIER_ROMAN_NUMERALS: Record<Tier, string> = {
@@ -1,9 +1,9 @@
import { BlitzServer } from '../../constants/servers.js';
import { AllStats } from '../../types/accountInfo.js';
import { BlitzServer } from '../../constants/servers';
import { AllStats } from '../../types/accountInfo';
import getTankHistories, {
TankHistories,
TankHistoryRaw,
} from './getTankHistories.js';
} from './getTankHistories';
export const emptyAllStats: AllStats = {
battles: 0,
@@ -34,7 +34,7 @@ export const emptyTankHistoryNode: TankHistoryRaw = {
all: emptyAllStats,
};
export default async function getTankStatsDiffed(
export default async function getDiffedTankStats(
server: BlitzServer,
id: number,
start: number,
@@ -46,26 +46,27 @@ export default async function getTankStatsDiffed(
// sort them even though most won't be used
const tankSortedHistory: Record<number, TankHistories> = {};
const tankIds: number[] = [];
const tanks: number[] = [];
history.forEach((node) => {
if (!tankSortedHistory[node.tank_id]) {
tankSortedHistory[node.tank_id] = [node];
tankIds.push(node.tank_id);
tanks.push(node.tank_id);
} else {
tankSortedHistory[node.tank_id].push(node);
}
});
// find the ones played within the range
const tankIdsInRange = tankIds.filter((tankId) =>
const inRangeTanks = tanks.filter((tankId) =>
tankSortedHistory[tankId].some(
(node) => node.last_battle_time > start && node.last_battle_time < end,
),
);
const playedTanks: number[] = [];
// fetch history node right before "start" and right before "end"
const tanksBeforeAndAfter: Record<number, AllStats> = {};
tankIdsInRange.forEach((tankId) => {
const diffed: Record<number, AllStats> = {};
inRangeTanks.forEach((tankId) => {
const sortedHistory = tankSortedHistory[tankId];
const previousIndex =
sortedHistory.findIndex((node) => node.last_battle_time > start) - 1;
@@ -81,7 +82,7 @@ export default async function getTankStatsDiffed(
// check if there was a change in battles as games in ratings do update last_battle_time
if (diff((a) => a.battles) > 0) {
tanksBeforeAndAfter[tankId] = {
diffed[tankId] = {
battles: diff((a) => a.battles),
capture_points: diff((a) => a.capture_points),
damage_dealt: diff((a) => a.damage_dealt),
@@ -100,8 +101,19 @@ export default async function getTankStatsDiffed(
wins: diff((a) => a.wins),
xp: diff((a) => a.xp),
};
playedTanks.push(tankId);
}
});
return tanksBeforeAndAfter;
const order = playedTanks.sort((a, b) => {
const aHistories = tankSortedHistory[a];
const bHistories = tankSortedHistory[b];
const aLatest = aHistories[aHistories.length - 1];
const bLatest = bHistories[bHistories.length - 1];
return bLatest.last_battle_time - aLatest.last_battle_time;
});
return { diffed, order };
}
+9 -5
View File
@@ -1,12 +1,16 @@
import { Period } from '../discord/addPeriodSubCommands.js';
import getTimeDaysAgo from './getTimeDaysAgo.js';
import { BlitzServer } from '../../constants/servers';
import { Period } from '../discord/addPeriodSubCommands';
import getTimeDaysAgo from './getTimeDaysAgo';
export default function getPeriodStart(period: Exclude<Period, 'custom'>) {
export default function getPeriodStart(
server: BlitzServer,
period: Exclude<Period, 'custom'>,
) {
if (period === 'career') {
return -Infinity;
} else if (period === 'today') {
return getTimeDaysAgo(0);
return getTimeDaysAgo(server, 1);
} else {
return getTimeDaysAgo(parseInt(period));
return getTimeDaysAgo(server, parseInt(period));
}
}
+7 -7
View File
@@ -1,14 +1,14 @@
import { BlitzServer } from '../../constants/servers.js';
import { AccountInfo, AllStats } from '../../types/accountInfo.js';
import { BlitzServer } from '../../constants/servers';
import { AccountInfo, AllStats } from '../../types/accountInfo';
import {
GetHistoriesOptions,
Histories,
History,
getHistoriesDefaultOptions,
} from '../../types/histories.js';
import getWargamingResponse from '../blitz/getWargamingResponse.js';
import { WARGAMING_APPLICATION_ID } from '../node/arguments.js';
import { emptyAllStats } from './getTankStatsDiffed.js';
} from '../../types/histories';
import getWargamingResponse from '../blitz/getWargamingResponse';
import { WARGAMING_APPLICATION_ID } from '../node/arguments';
import { emptyAllStats } from './getDiffedTankStats';
export interface PlayerHistoryRaw {
clan: { clan_id: number; name: string; tag: string };
@@ -44,7 +44,7 @@ export default async function getPlayerHistories(
({
all: history.statistics.all,
last_battle_time: history.last_battle_time,
} satisfies History),
}) satisfies History,
);
let lastNonCompliantIndex = -1;
let hasCompliedOnce = false;
+5 -5
View File
@@ -1,12 +1,12 @@
import { BlitzServer } from '../../constants/servers.js';
import { BlitzServer } from '../../constants/servers';
import {
GetHistoriesOptions,
Histories,
History,
getHistoriesDefaultOptions,
} from '../../types/histories.js';
import getTankStats from '../blitz/getTankStats.js';
import { emptyAllStats } from './getTankStatsDiffed.js';
} from '../../types/histories';
import getTankStats from '../blitz/getTankStats';
import { emptyAllStats } from './getDiffedTankStats';
export interface TankHistory extends History {
tank_id: number;
@@ -48,7 +48,7 @@ export default async function getTankHistories(
all: history.all,
last_battle_time: history.last_battle_time,
tank_id: history.tank_id,
} satisfies TankHistory),
}) satisfies TankHistory,
);
const compliantTanks: number[] = [];
const lastBattles: Record<number, number> = {};
+15 -7
View File
@@ -1,12 +1,20 @@
export default function getTimeDaysAgo(daysAgo: number) {
import { BlitzServer } from '../../constants/servers';
export const TIME_ZONE_MAPPINGS: Record<BlitzServer, number> = {
com: -5, // Central North American Time
eu: +1, // Central European Time
asia: +7, // Central Asia Standard Time
};
export default function getTimeDaysAgo(server: BlitzServer, daysAgo: number) {
const now = new Date();
now.setHours(5, 0, 0, 0);
if (daysAgo === 0) now.getTime() / 1000;
// if it's after 5:00 AM today, subtract one day
if (now.getTime() > Date.now()) now.setDate(now.getDate() - 1);
const time = new Date();
// subtract the period number of days from now
now.setDate(now.getDate() - daysAgo);
time.setUTCHours(-TIME_ZONE_MAPPINGS[server], 0, 0, 0);
if (time > now) time.setUTCDate(time.getUTCDate() - 1);
time.setUTCDate(time.getUTCDate() - daysAgo + 1);
return now.getTime() / 1000;
return time.getTime() / 1000;
}
+3 -3
View File
@@ -1,6 +1,6 @@
import sumStats from '../blitz/sumStats.js';
import { emptyAllStats } from './getTankStatsDiffed.js';
import { tankAverages } from './tankAverages.js';
import sumStats from '../blitz/sumStats';
import { emptyAllStats } from './getDiffedTankStats';
import { tankAverages } from './tankAverages';
console.log('Calculating player average...');
export const playerAverage = Object.entries(await tankAverages).reduce(
+1 -1
View File
@@ -1,4 +1,4 @@
import { AllStats } from '../../types/accountInfo.js';
import { AllStats } from '../../types/accountInfo';
export interface SpecialStats {
winrate: number;
+1 -1
View File
@@ -2,7 +2,7 @@ import {
SlashCommandSubcommandBuilder,
SlashCommandSubcommandGroupBuilder,
} from 'discord.js';
import { PERIOD_NAMES } from '../discord/resolvePeriodFromCommand.js';
import { PERIOD_NAMES } from '../discord/resolvePeriodFromCommand';
export type Period = 'today' | '30' | '60' | '90' | 'career' | 'custom';
+1 -1
View File
@@ -1,5 +1,5 @@
import { SlashCommandStringOption } from 'discord.js';
import { BLITZ_SERVERS } from '../../constants/servers.js';
import { BLITZ_SERVERS } from '../../constants/servers';
export default function addServerChoices(option: SlashCommandStringOption) {
return option
@@ -1,7 +1,7 @@
import { SlashCommandBuilder } from 'discord.js';
import addPeriodSubCommands from './addPeriodSubCommands.js';
import addTankChoices from './addTankChoices.js';
import addUsernameChoices from './addUsernameChoices.js';
import addPeriodSubCommands from './addPeriodSubCommands';
import addTankChoices from './addTankChoices';
import addUsernameChoices from './addUsernameChoices';
export default function addStatTypeSubCommandGroups(
option: SlashCommandBuilder,
+3 -3
View File
@@ -3,8 +3,8 @@ import {
AutocompleteInteraction,
CacheType,
} from 'discord.js';
import { BLITZ_SERVERS } from '../../constants/servers.js';
import listClans from '../blitz/listClans.js';
import { BLITZ_SERVERS } from '../../constants/servers';
import listClans from '../blitz/listClans';
export default async function autocompleteClan(
interaction: AutocompleteInteraction<CacheType>,
@@ -20,7 +20,7 @@ export default async function autocompleteClan(
({
name: `${player.name} (${BLITZ_SERVERS[player.server]})`,
value: `${player.server}/${player.clan_id}`,
} satisfies ApplicationCommandOptionChoiceData<string>),
}) satisfies ApplicationCommandOptionChoiceData<string>,
)
: [],
);
+7 -4
View File
@@ -4,8 +4,8 @@ import {
CacheType,
} from 'discord.js';
import { go } from 'fuzzysort';
import resolveTankName from '../blitz/resolveTankName.js';
import { TANKS } from '../blitz/tankopedia.js';
import resolveTankName from '../blitz/resolveTankName';
import { TANKS } from '../blitz/tankopedia';
export default async function autocompleteTanks(
interaction: AutocompleteInteraction<CacheType>,
@@ -16,14 +16,17 @@ export default async function autocompleteTanks(
await interaction.respond(
focusedOption.value
? await Promise.all(
go(focusedOption.value, TANKS, { keys: ['name'], limit: 10 }).map(
go(focusedOption.value, await TANKS, {
keys: ['name'],
limit: 10,
}).map(
async (item) =>
({
name: item.obj.name
? item.obj.name
: await resolveTankName(item.obj.tank_id),
value: `${item.obj.tank_id}`,
} satisfies ApplicationCommandOptionChoiceData<string>),
}) satisfies ApplicationCommandOptionChoiceData<string>,
),
)
: [],
+3 -3
View File
@@ -3,8 +3,8 @@ import {
AutocompleteInteraction,
CacheType,
} from 'discord.js';
import { BLITZ_SERVERS } from '../../constants/servers.js';
import listPlayers from '../blitz/listPlayers.js';
import { BLITZ_SERVERS } from '../../constants/servers';
import listPlayers from '../blitz/listPlayers';
export default async function autocompleteUsername(
interaction: AutocompleteInteraction<CacheType>,
@@ -21,7 +21,7 @@ export default async function autocompleteUsername(
({
name: `${player.nickname} (${BLITZ_SERVERS[player.server]})`,
value: `${player.server}/${player.account_id}`,
} satisfies ApplicationCommandOptionChoiceData<string>),
}) satisfies ApplicationCommandOptionChoiceData<string>,
)
: [],
);
+6 -3
View File
@@ -1,9 +1,12 @@
import { EmbedBuilder } from 'discord.js';
import { ACCENT_COLOR } from '../../constants/colors.js';
import { ACCENT_COLOR } from '../../constants/colors';
export default function embedInfo(title: string, description: string) {
return new EmbedBuilder()
const embed = new EmbedBuilder()
.setColor(ACCENT_COLOR)
.setTitle(title)
.setDescription(description);
if (title) embed.setTitle(title);
return embed;
}
+1 -1
View File
@@ -1,5 +1,5 @@
import { EmbedBuilder } from 'discord.js';
import { NEGATIVE_COLOR } from '../../constants/colors.js';
import { NEGATIVE_COLOR } from '../../constants/colors';
export default function embedNegative(title: string, description: string) {
return new EmbedBuilder()
+1 -1
View File
@@ -1,5 +1,5 @@
import { EmbedBuilder } from 'discord.js';
import { POSITIVE_COLOR } from '../../constants/colors.js';
import { POSITIVE_COLOR } from '../../constants/colors';
export default function embedPositive(title: string, description: string) {
return new EmbedBuilder()
+1 -1
View File
@@ -1,5 +1,5 @@
import { EmbedBuilder } from 'discord.js';
import { WARNING_COLOR } from '../../constants/colors.js';
import { WARNING_COLOR } from '../../constants/colors';
export default function embedWarning(title: string, description: string) {
return new EmbedBuilder()
@@ -4,8 +4,8 @@ import {
EmbedBuilder,
InteractionEditReplyOptions,
} from 'discord.js';
import { InteractionReturnable } from '../../events/interactionCreate/index.js';
import jsxToPng from '../node/jsxToPng.js';
import { InteractionReturnable } from '../../events/interactionCreate';
import jsxToPng from '../node/jsxToPng';
export default async function normalizeInteractionReturnable(
returnable: InteractionReturnable,
@@ -19,7 +19,9 @@ export default async function normalizeInteractionReturnable(
await Promise.all(
normalizedReturnable.map(async (item) => {
if (item instanceof EmbedBuilder) {
if (typeof item === 'string') {
reply.content = item;
} else if (item instanceof EmbedBuilder) {
if (!reply.embeds) reply.embeds = [];
reply.embeds.push(item);
} else if (item instanceof ButtonBuilder) {
+5 -3
View File
@@ -1,9 +1,11 @@
import { ButtonInteraction, CacheType } from 'discord.js';
import { CYCLIC_API } from '../../constants/cyclic.js';
import resolvePeriodFromURL from '../express/resolvePeriodFromURL.js';
import { CYCLIC_API } from '../../constants/cyclic';
import { BlitzServer } from '../../constants/servers';
import resolvePeriodFromURL from '../express/resolvePeriodFromURL';
export default function resolvePeriodFromButton(
server: BlitzServer,
interaction: ButtonInteraction<CacheType>,
) {
return resolvePeriodFromURL(`${CYCLIC_API}/${interaction.customId}`);
return resolvePeriodFromURL(server, `${CYCLIC_API}/${interaction.customId}`);
}
+9 -7
View File
@@ -1,8 +1,9 @@
import { CacheType, ChatInputCommandInteraction } from 'discord.js';
import getPeriodNow from '../blitzstars/getPeriodNow.js';
import getPeriodStart from '../blitzstars/getPeriodStart.js';
import getTimeDaysAgo from '../blitzstars/getTimeDaysAgo.js';
import { Period } from '../discord/addPeriodSubCommands.js';
import { BlitzServer } from '../../constants/servers';
import getPeriodNow from '../blitzstars/getPeriodNow';
import getPeriodStart from '../blitzstars/getPeriodStart';
import getTimeDaysAgo from '../blitzstars/getTimeDaysAgo';
import { Period } from '../discord/addPeriodSubCommands';
export const PERIOD_NAMES: Record<Period, string> = {
today: "Today's statistics",
@@ -30,6 +31,7 @@ export interface ResolvedPeriod {
}
export default function resolvePeriodFromCommand(
server: BlitzServer,
interaction: ChatInputCommandInteraction<CacheType>,
) {
let statsName: string;
@@ -46,12 +48,12 @@ export default function resolvePeriodFromCommand(
statsName = `${startMin} to ${endMax} days' statistics`;
evolutionName = `${startMin} to ${endMax} days' evolution`;
start = getTimeDaysAgo(startMin);
end = getTimeDaysAgo(endMax);
start = getTimeDaysAgo(server, startMin);
end = getTimeDaysAgo(server, endMax);
} else {
statsName = PERIOD_NAMES[period];
evolutionName = EVOLUTION_PERIOD_NAMES[period];
start = getPeriodStart(period);
start = getPeriodStart(server, period);
end = getPeriodNow();
}
+2 -2
View File
@@ -1,6 +1,6 @@
import { ButtonInteraction, CacheType } from 'discord.js';
import { CYCLIC_API } from '../../constants/cyclic.js';
import resolvePlayerFromURL from '../express/resolvePlayerFromURL.js';
import { CYCLIC_API } from '../../constants/cyclic';
import resolvePlayerFromURL from '../express/resolvePlayerFromURL';
export default async function resolvePlayerFromButton(
interaction: ButtonInteraction<CacheType>,
+3 -3
View File
@@ -1,9 +1,9 @@
import { CacheType, ChatInputCommandInteraction } from 'discord.js';
import { BlitzServer } from '../../constants/servers.js';
import { BlitzServer } from '../../constants/servers';
import listPlayers, {
usernamePatternWithoutPosition,
} from '../blitz/listPlayers.js';
import throwError from '../node/throwError.js';
} from '../blitz/listPlayers';
import throwError from '../node/throwError';
export const serverAndIdPattern = /(com|eu|asia)\/[0-9]+/;
+2 -2
View File
@@ -1,5 +1,5 @@
import { RouteReturnable } from '../../server.js';
import jsxToSvg from '../node/jsxToSvg.js';
import { RouteReturnable } from '../../server';
import jsxToSvg from '../node/jsxToSvg';
export default async function normalizeRouteReturnable(
returnable: RouteReturnable,
+8 -4
View File
@@ -1,7 +1,11 @@
import { Request } from 'express';
import { CYCLIC_API } from '../../constants/cyclic.js';
import resolvePeriodFromURL from './resolvePeriodFromURL.js';
import { CYCLIC_API } from '../../constants/cyclic';
import { BlitzServer } from '../../constants/servers';
import resolvePeriodFromURL from './resolvePeriodFromURL';
export default function resolvePeriodFromRequest(request: Request) {
return resolvePeriodFromURL(`${CYCLIC_API}${request.path}`);
export default function resolvePeriodFromRequest(
server: BlitzServer,
request: Request,
) {
return resolvePeriodFromURL(server, `${CYCLIC_API}${request.path}`);
}
+13 -9
View File
@@ -1,14 +1,18 @@
import getPeriodNow from '../blitzstars/getPeriodNow.js';
import getPeriodStart from '../blitzstars/getPeriodStart.js';
import getTimeDaysAgo from '../blitzstars/getTimeDaysAgo.js';
import { Period } from '../discord/addPeriodSubCommands.js';
import { BlitzServer } from '../../constants/servers';
import getPeriodNow from '../blitzstars/getPeriodNow';
import getPeriodStart from '../blitzstars/getPeriodStart';
import getTimeDaysAgo from '../blitzstars/getTimeDaysAgo';
import { Period } from '../discord/addPeriodSubCommands';
import {
EVOLUTION_PERIOD_NAMES,
PERIOD_NAMES,
ResolvedPeriod,
} from '../discord/resolvePeriodFromCommand.js';
} from '../discord/resolvePeriodFromCommand';
export default function resolvePeriodFromURL(urlString: string) {
export default function resolvePeriodFromURL(
server: BlitzServer,
urlString: string,
) {
let statsName: string;
let evolutionName: string;
let start: number;
@@ -25,12 +29,12 @@ export default function resolvePeriodFromURL(urlString: string) {
statsName = `${startMin} to ${endMax} days' statistics`;
evolutionName = `${startMin} to ${endMax} days' evolution`;
start = getTimeDaysAgo(startMin);
end = getTimeDaysAgo(endMax);
start = getTimeDaysAgo(server, startMin);
end = getTimeDaysAgo(server, endMax);
} else {
statsName = PERIOD_NAMES[period];
evolutionName = EVOLUTION_PERIOD_NAMES[period];
start = getPeriodStart(period);
start = getPeriodStart(server, period);
end = getPeriodNow();
}
+2 -2
View File
@@ -1,6 +1,6 @@
import { Request } from 'express';
import { CYCLIC_API } from '../../constants/cyclic.js';
import resolvePlayerFromURL from './resolvePlayerFromURL.js';
import { CYCLIC_API } from '../../constants/cyclic';
import resolvePlayerFromURL from './resolvePlayerFromURL';
export default function resolvePlayerFromRequest(request: Request) {
return resolvePlayerFromURL(`${CYCLIC_API}${request.originalUrl}`);
Loaded 100 of 126 files, more files were not shown because too many files have changed in this diff. Show more