mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 10:47:04 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
72c4cd664b | ||
|
|
f907c79d68 | ||
|
|
ae3d0a08cd | ||
|
|
fb66027de0 | ||
|
|
423ad4e433 | ||
|
|
cf291c89a2 | ||
|
|
604e356736 | ||
|
|
00b721f34f | ||
|
|
95b105cb65 | ||
|
|
433ffba1e3 | ||
|
|
423a8b8088 | ||
|
|
d2d575bd72 | ||
|
|
2958124499 | ||
|
|
5addc55dd0 | ||
|
|
c78e58106a |
No files matched your search
@@ -0,0 +1,128 @@
|
|||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
We as members, contributors, and leaders pledge to make participation in our
|
||||||
|
community a harassment-free experience for everyone, regardless of age, body
|
||||||
|
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||||
|
identity and expression, level of experience, education, socio-economic status,
|
||||||
|
nationality, personal appearance, race, religion, or sexual identity
|
||||||
|
and orientation.
|
||||||
|
|
||||||
|
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||||
|
diverse, inclusive, and healthy community.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
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,
|
||||||
|
and learning from the experience
|
||||||
|
* 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
|
||||||
|
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
|
||||||
|
address, without their explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a
|
||||||
|
professional setting
|
||||||
|
|
||||||
|
## Enforcement Responsibilities
|
||||||
|
|
||||||
|
Community leaders are responsible for clarifying and enforcing our standards of
|
||||||
|
acceptable behavior and will take appropriate and fair corrective action in
|
||||||
|
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||||
|
or harmful.
|
||||||
|
|
||||||
|
Community leaders have the right and responsibility to remove, edit, or reject
|
||||||
|
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||||
|
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||||
|
decisions when appropriate.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies within all community spaces, and also applies when
|
||||||
|
an individual is officially representing the community in public spaces.
|
||||||
|
Examples of representing our community include using an official e-mail address,
|
||||||
|
posting via an official social media account, or acting as an appointed
|
||||||
|
representative at an online or offline event.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
|
reported to the community leaders responsible for enforcement at
|
||||||
|
the [Official Discord Server](https://discord.gg/nDt7AjGJQH).
|
||||||
|
All complaints will be reviewed and investigated promptly and fairly.
|
||||||
|
|
||||||
|
All community leaders are obligated to respect the privacy and security of the
|
||||||
|
reporter of any incident.
|
||||||
|
|
||||||
|
## Enforcement Guidelines
|
||||||
|
|
||||||
|
Community leaders will follow these Community Impact Guidelines in determining
|
||||||
|
the consequences for any action they deem in violation of this Code of Conduct:
|
||||||
|
|
||||||
|
### 1. Correction
|
||||||
|
|
||||||
|
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||||
|
unprofessional or unwelcome in the community.
|
||||||
|
|
||||||
|
**Consequence**: A private, written warning from community leaders, providing
|
||||||
|
clarity around the nature of the violation and an explanation of why the
|
||||||
|
behavior was inappropriate. A public apology may be requested.
|
||||||
|
|
||||||
|
### 2. Warning
|
||||||
|
|
||||||
|
**Community Impact**: A violation through a single incident or series
|
||||||
|
of actions.
|
||||||
|
|
||||||
|
**Consequence**: A warning with consequences for continued behavior. No
|
||||||
|
interaction with the people involved, including unsolicited interaction with
|
||||||
|
those enforcing the Code of Conduct, for a specified period of time. This
|
||||||
|
includes avoiding interactions in community spaces as well as external channels
|
||||||
|
like social media. Violating these terms may lead to a temporary or
|
||||||
|
permanent ban.
|
||||||
|
|
||||||
|
### 3. Temporary Ban
|
||||||
|
|
||||||
|
**Community Impact**: A serious violation of community standards, including
|
||||||
|
sustained inappropriate behavior.
|
||||||
|
|
||||||
|
**Consequence**: A temporary ban from any sort of interaction or public
|
||||||
|
communication with the community for a specified period of time. No public or
|
||||||
|
private interaction with the people involved, including unsolicited interaction
|
||||||
|
with those enforcing the Code of Conduct, is allowed during this period.
|
||||||
|
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
|
||||||
|
individual, or aggression toward or disparagement of classes of individuals.
|
||||||
|
|
||||||
|
**Consequence**: A permanent ban from any sort of public interaction within
|
||||||
|
the community.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||||
|
version 2.0, available at
|
||||||
|
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||||
|
|
||||||
|
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||||
|
enforcement ladder](https://github.com/mozilla/diversity).
|
||||||
|
|
||||||
|
[homepage]: https://www.contributor-covenant.org
|
||||||
|
|
||||||
|
For answers to common questions about this code of conduct, see the FAQ at
|
||||||
|
https://www.contributor-covenant.org/faq. Translations are available at
|
||||||
|
https://www.contributor-covenant.org/translations.
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2023 TrèsAbhi
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -4,6 +4,8 @@ import { getSidebar } from 'vitepress-plugin-auto-sidebar';
|
|||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
title: 'blitzkrieg',
|
title: 'blitzkrieg',
|
||||||
description: '🎉 All-in-one Discord bot for everything World of Tanks Blitz',
|
description: '🎉 All-in-one Discord bot for everything World of Tanks Blitz',
|
||||||
|
base: '/blitzkrieg/',
|
||||||
|
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
nav: [
|
nav: [
|
||||||
{ text: 'Home', link: '/' },
|
{ text: 'Home', link: '/' },
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Blitzkrieg 1.9.2
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
|
||||||
|
- Implemented `/blitzkrieg/` as the base path
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# Blitzkrieg 1.9.3
|
||||||
|
|
||||||
|
A few fixes before we tackle the next major release.
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
|
||||||
|
- `/stats`, `/evolution`, and `/statsfull` now properly handle unowned or unplayed tanks
|
||||||
|
|
||||||
|
## Technical Changes
|
||||||
|
|
||||||
|
- Added a `Command` suffix after all command exports
|
||||||
|
- Added a `Route` suffix after all route exports
|
||||||
|
- Moved `tankopedia` and `tankaverages` into a runtime cache
|
||||||
|
- But, this time, it's a promise so we get the best of both worlds
|
||||||
@@ -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
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "blitzkrieg",
|
"name": "blitzkrieg",
|
||||||
"version": "1.9.0",
|
"version": "1.9.4",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "src/server.ts",
|
"main": "src/server.ts",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
+1
-24
@@ -1,5 +1,5 @@
|
|||||||
import { build } from 'esbuild';
|
import { build } from 'esbuild';
|
||||||
import { copyFile, mkdir, rm, writeFile } from 'fs/promises';
|
import { copyFile, mkdir, rm } from 'fs/promises';
|
||||||
import { argv } from 'process';
|
import { argv } from 'process';
|
||||||
|
|
||||||
const isProd = !argv.includes('--dev');
|
const isProd = !argv.includes('--dev');
|
||||||
@@ -17,29 +17,6 @@ if (isProd) {
|
|||||||
await mkdir('dist');
|
await mkdir('dist');
|
||||||
console.log('✅ Dist created');
|
console.log('✅ Dist created');
|
||||||
|
|
||||||
console.log('🟡 Caching tankopedia...');
|
|
||||||
fetch('https://www.blitzstars.com/bs-tankopedia.json')
|
|
||||||
.then((response) => response.text())
|
|
||||||
.then((tankopedia) => writeFile('./dist/tankopedia.json', tankopedia))
|
|
||||||
.then(() => console.log('✅ Tankopedia cached'));
|
|
||||||
|
|
||||||
console.log('🟡 Caching tank averages...');
|
|
||||||
fetch('https://www.blitzstars.com/api/tankaverages.json')
|
|
||||||
.then((response) => response.json())
|
|
||||||
.then((individualTankAverages) => {
|
|
||||||
const tankAverages = {};
|
|
||||||
|
|
||||||
individualTankAverages.forEach((individualTankAverage) => {
|
|
||||||
tankAverages[individualTankAverage.tank_id] = individualTankAverage;
|
|
||||||
});
|
|
||||||
|
|
||||||
return writeFile(
|
|
||||||
'./dist/tankaverages.json',
|
|
||||||
JSON.stringify(tankAverages),
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.then(() => console.log('✅ Tank averages cached'));
|
|
||||||
|
|
||||||
console.log('🟡 Copying package.json...');
|
console.log('🟡 Copying package.json...');
|
||||||
copyFile('package.dist.json', 'dist/package.json').then(() =>
|
copyFile('package.dist.json', 'dist/package.json').then(() =>
|
||||||
console.log('✅ package.json copied'),
|
console.log('✅ package.json copied'),
|
||||||
|
|||||||
@@ -10,16 +10,16 @@ import { CommandRegistry } from '../events/interactionCreate/index.js';
|
|||||||
|
|
||||||
const executionStart = new Date().getTime();
|
const executionStart = new Date().getTime();
|
||||||
|
|
||||||
export default {
|
export const debugCommand: CommandRegistry = {
|
||||||
inProduction: true,
|
inProduction: true,
|
||||||
inDevelopment: false,
|
inDevelopment: true,
|
||||||
inPublic: true,
|
inPublic: true,
|
||||||
|
|
||||||
command: new SlashCommandBuilder()
|
command: new SlashCommandBuilder()
|
||||||
.setName('debug')
|
.setName('debug')
|
||||||
.setDescription('Debug information about the bot'),
|
.setDescription('Debug information about the bot'),
|
||||||
|
|
||||||
handler(interaction) {
|
async handler(interaction) {
|
||||||
const currentTime = new Date().getTime();
|
const currentTime = new Date().getTime();
|
||||||
const uptime = currentTime - executionStart;
|
const uptime = currentTime - executionStart;
|
||||||
|
|
||||||
@@ -37,9 +37,9 @@ export default {
|
|||||||
(uptime / 1000) % 60,
|
(uptime / 1000) % 60,
|
||||||
)}s ${Math.floor(uptime % 1000)}ms`,
|
)}s ${Math.floor(uptime % 1000)}ms`,
|
||||||
],
|
],
|
||||||
['Tankopedia cached', `${tankopedia !== undefined}`],
|
['Tankopedia cached', `${(await tankopedia) !== undefined}`],
|
||||||
['Tank averages cached', `${tankAverages !== undefined}`],
|
['Tank averages cached', `${(await tankAverages) !== undefined}`],
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
} satisfies CommandRegistry;
|
};
|
||||||
+47
-31
@@ -15,6 +15,7 @@ import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand.j
|
|||||||
import { WARGAMING_APPLICATION_ID } from '../core/node/arguments.js';
|
import { WARGAMING_APPLICATION_ID } from '../core/node/arguments.js';
|
||||||
import { CommandRegistry } from '../events/interactionCreate/index.js';
|
import { CommandRegistry } from '../events/interactionCreate/index.js';
|
||||||
import { AccountInfo } from '../types/accountInfo.js';
|
import { AccountInfo } from '../types/accountInfo.js';
|
||||||
|
import { PossiblyPromise } from '../types/possiblyPromise.js';
|
||||||
|
|
||||||
export type SkilledClan = 'SKLLD' | 'SMRI';
|
export type SkilledClan = 'SKLLD' | 'SMRI';
|
||||||
|
|
||||||
@@ -23,9 +24,9 @@ export const SKILLED_CLANS: Record<SkilledClan, string> = {
|
|||||||
SMRI: 'Samurai',
|
SMRI: 'Samurai',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export const eligibleCommand: CommandRegistry = {
|
||||||
inProduction: true,
|
inProduction: true,
|
||||||
inDevelopment: false,
|
inDevelopment: true,
|
||||||
inPublic: false,
|
inPublic: false,
|
||||||
|
|
||||||
command: new SlashCommandBuilder()
|
command: new SlashCommandBuilder()
|
||||||
@@ -67,41 +68,56 @@ export default {
|
|||||||
const entries = Object.entries(tankStatsOverTime);
|
const entries = Object.entries(tankStatsOverTime);
|
||||||
const stats = sumStats(entries.map(([, stats]) => stats));
|
const stats = sumStats(entries.map(([, stats]) => stats));
|
||||||
const averageTier =
|
const averageTier =
|
||||||
entries.reduce((accumulator, [tankIdString, stats]) => {
|
(await entries.reduce<PossiblyPromise<number>>(
|
||||||
const tankId = parseInt(tankIdString);
|
async (accumulator, [tankIdString, stats]) => {
|
||||||
const tankTier = tankopedia[tankId].tier;
|
const tankId = parseInt(tankIdString);
|
||||||
|
const tankTier = (await tankopedia)[tankId].tier;
|
||||||
|
|
||||||
return accumulator + tankTier * stats.battles;
|
return (await accumulator) + tankTier * stats.battles;
|
||||||
}, 0) / stats.battles;
|
},
|
||||||
|
0,
|
||||||
|
)) / stats.battles;
|
||||||
const WN8 =
|
const WN8 =
|
||||||
entries.reduce((accumulator, [tankIdString, stats]) => {
|
(await entries.reduce<PossiblyPromise<number>>(
|
||||||
const tankId = parseInt(tankIdString);
|
async (accumulator, [tankIdString, stats]) => {
|
||||||
|
const tankId = parseInt(tankIdString);
|
||||||
|
|
||||||
// edge case where new tanks don't have averages
|
// edge case where new tanks don't have averages
|
||||||
if (tankAverages[tankId]) {
|
if ((await tankAverages)[tankId]) {
|
||||||
const tankWN8 = calculateWN8(tankAverages[tankId].all, stats);
|
const tankWN8 = calculateWN8(
|
||||||
|
(await tankAverages)[tankId].all,
|
||||||
|
stats,
|
||||||
|
);
|
||||||
|
|
||||||
if (isNaN(tankWN8)) return accumulator;
|
if (isNaN(tankWN8)) return accumulator;
|
||||||
return accumulator + tankWN8 * stats.battles;
|
return (await accumulator) + tankWN8 * stats.battles;
|
||||||
} else return accumulator;
|
} else return accumulator;
|
||||||
}, 0) / stats.battles;
|
},
|
||||||
|
0,
|
||||||
|
)) / stats.battles;
|
||||||
const tier10Damage =
|
const tier10Damage =
|
||||||
entries.reduce((accumulator, [tankIdString, stats]) => {
|
(await entries.reduce<PossiblyPromise<number>>(
|
||||||
const tankId = parseInt(tankIdString);
|
async (accumulator, [tankIdString, stats]) => {
|
||||||
|
const tankId = parseInt(tankIdString);
|
||||||
|
|
||||||
if (tankopedia[tankId]?.tier === 10) {
|
if ((await tankopedia)[tankId]?.tier === 10) {
|
||||||
const tankStats = tankStatsOverTime[tankId];
|
const tankStats = tankStatsOverTime[tankId];
|
||||||
return accumulator + tankStats.damage_dealt;
|
return (await accumulator) + tankStats.damage_dealt;
|
||||||
} else return accumulator;
|
} else return accumulator;
|
||||||
}, 0) /
|
},
|
||||||
entries.reduce((accumulator, [tankIdString, stats]) => {
|
0,
|
||||||
const tankId = parseInt(tankIdString);
|
)) /
|
||||||
|
(await entries.reduce<PossiblyPromise<number>>(
|
||||||
|
async (accumulator, [tankIdString, stats]) => {
|
||||||
|
const tankId = parseInt(tankIdString);
|
||||||
|
|
||||||
if (tankopedia[tankId]?.tier === 10) {
|
if ((await tankopedia)[tankId]?.tier === 10) {
|
||||||
const tankStats = tankStatsOverTime[tankId];
|
const tankStats = tankStatsOverTime[tankId];
|
||||||
return accumulator + tankStats.battles;
|
return (await accumulator) + tankStats.battles;
|
||||||
} else return accumulator;
|
} else return accumulator;
|
||||||
}, 0);
|
},
|
||||||
|
0,
|
||||||
|
));
|
||||||
|
|
||||||
if (stats.battles < 126) {
|
if (stats.battles < 126) {
|
||||||
problems.push(['Battles', `${stats.battles} (${stats.battles - 126})`]);
|
problems.push(['Battles', `${stats.battles} (${stats.battles - 126})`]);
|
||||||
@@ -169,4 +185,4 @@ export default {
|
|||||||
|
|
||||||
return (isEligible ? embedPositive : embedNegative)(title, body);
|
return (isEligible ? embedPositive : embedNegative)(title, body);
|
||||||
},
|
},
|
||||||
} satisfies CommandRegistry;
|
};
|
||||||
@@ -18,7 +18,7 @@ import evolution from '../renderers/evolution.js';
|
|||||||
import { StatType } from '../renderers/statsfull.js';
|
import { StatType } from '../renderers/statsfull.js';
|
||||||
import { AccountInfo } from '../types/accountInfo.js';
|
import { AccountInfo } from '../types/accountInfo.js';
|
||||||
|
|
||||||
export default {
|
export const evolutionCommand: CommandRegistry = {
|
||||||
inProduction: true,
|
inProduction: true,
|
||||||
inDevelopment: true,
|
inDevelopment: true,
|
||||||
inPublic: true,
|
inPublic: true,
|
||||||
@@ -36,7 +36,7 @@ export default {
|
|||||||
const player = await resolvePlayerFromCommand(interaction);
|
const player = await resolvePlayerFromCommand(interaction);
|
||||||
const period = resolvePeriodFromCommand(interaction);
|
const period = resolvePeriodFromCommand(interaction);
|
||||||
const tankIdRaw = interaction.options.getString('tank')!;
|
const tankIdRaw = interaction.options.getString('tank')!;
|
||||||
const tankId = commandGroup === 'tank' ? resolveTankId(tankIdRaw) : null;
|
const tankId = commandGroup === 'tank' ? await resolveTankId(tankIdRaw) : null;
|
||||||
const start = interaction.options.getInteger('start');
|
const start = interaction.options.getInteger('start');
|
||||||
const end = interaction.options.getInteger('end');
|
const end = interaction.options.getInteger('end');
|
||||||
const { nickname } = (
|
const { nickname } = (
|
||||||
@@ -83,4 +83,4 @@ export default {
|
|||||||
parseInt(url.searchParams.get('tankId')!),
|
parseInt(url.searchParams.get('tankId')!),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
} satisfies CommandRegistry;
|
};
|
||||||
@@ -8,9 +8,9 @@ import {
|
|||||||
publicCommands,
|
publicCommands,
|
||||||
} from '../events/interactionCreate/index.js';
|
} from '../events/interactionCreate/index.js';
|
||||||
|
|
||||||
export default {
|
export const helpCommand: CommandRegistry = {
|
||||||
inProduction: true,
|
inProduction: true,
|
||||||
inDevelopment: false,
|
inDevelopment: true,
|
||||||
inPublic: true,
|
inPublic: true,
|
||||||
|
|
||||||
command: new SlashCommandBuilder()
|
command: new SlashCommandBuilder()
|
||||||
@@ -33,4 +33,4 @@ export default {
|
|||||||
)}\n\n*I am a new bot; I may make mistakes and go offline unexpectedly from time to time.*`,
|
)}\n\n*I am a new bot; I may make mistakes and go offline unexpectedly from time to time.*`,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
} satisfies CommandRegistry;
|
};
|
||||||
@@ -16,9 +16,9 @@ import { ClanInfo } from '../types/clanInfo.js';
|
|||||||
|
|
||||||
const DEFAULT_THRESHOLD = 7;
|
const DEFAULT_THRESHOLD = 7;
|
||||||
|
|
||||||
export default {
|
export const inactiveCommand: CommandRegistry = {
|
||||||
inProduction: true,
|
inProduction: true,
|
||||||
inDevelopment: false,
|
inDevelopment: true,
|
||||||
inPublic: true,
|
inPublic: true,
|
||||||
|
|
||||||
command: new SlashCommandBuilder()
|
command: new SlashCommandBuilder()
|
||||||
@@ -85,4 +85,4 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
autocomplete: autocompleteClan,
|
autocomplete: autocompleteClan,
|
||||||
} satisfies CommandRegistry;
|
};
|
||||||
@@ -41,9 +41,9 @@ const COMP_TANKS = [
|
|||||||
6753, // type 71
|
6753, // type 71
|
||||||
];
|
];
|
||||||
|
|
||||||
export default {
|
export const ownedTanksCommand: CommandRegistry = {
|
||||||
inProduction: true,
|
inProduction: true,
|
||||||
inDevelopment: false,
|
inDevelopment: true,
|
||||||
inPublic: true,
|
inPublic: true,
|
||||||
|
|
||||||
command: new SlashCommandBuilder()
|
command: new SlashCommandBuilder()
|
||||||
@@ -77,9 +77,11 @@ export default {
|
|||||||
`https://api.wotblitz.${server}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`,
|
`https://api.wotblitz.${server}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`,
|
||||||
);
|
);
|
||||||
const tankStats = await getTankStats(server, id);
|
const tankStats = await getTankStats(server, id);
|
||||||
const tanks = tankStats
|
const tanks = (
|
||||||
.map((tankData) => tankopedia[tankData.tank_id])
|
await Promise.all(
|
||||||
.filter((tank) => tank?.tier === tier);
|
tankStats.map(async (tankData) => (await tankopedia)[tankData.tank_id]),
|
||||||
|
)
|
||||||
|
).filter((tank) => tank?.tier === tier);
|
||||||
const clanData = await getWargamingResponse<PlayerClanData>(
|
const clanData = await getWargamingResponse<PlayerClanData>(
|
||||||
`https://api.wotblitz.${server}/wotb/clans/accountinfo/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}&extra=clan`,
|
`https://api.wotblitz.${server}/wotb/clans/accountinfo/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}&extra=clan`,
|
||||||
);
|
);
|
||||||
@@ -121,4 +123,4 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
autocomplete: autocompleteUsername,
|
autocomplete: autocompleteUsername,
|
||||||
} satisfies CommandRegistry;
|
};
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import { SlashCommandBuilder } from 'discord.js';
|
import { SlashCommandBuilder } from 'discord.js';
|
||||||
import { CommandRegistry } from '../events/interactionCreate/index.js';
|
import { CommandRegistry } from '../events/interactionCreate/index.js';
|
||||||
|
|
||||||
export default {
|
export const pingCommand: CommandRegistry<true> = {
|
||||||
inProduction: true,
|
inProduction: true,
|
||||||
inDevelopment: false,
|
inDevelopment: true,
|
||||||
inPublic: true,
|
inPublic: true,
|
||||||
handlesInteraction: true,
|
handlesInteraction: true,
|
||||||
|
|
||||||
@@ -30,4 +30,4 @@ export default {
|
|||||||
const executionTime = Date.now() - executionStart;
|
const executionTime = Date.now() - executionStart;
|
||||||
interaction.editReply(`Pong 🏓 - ${executionTime}ms`);
|
interaction.editReply(`Pong 🏓 - ${executionTime}ms`);
|
||||||
},
|
},
|
||||||
} satisfies CommandRegistry<true>;
|
};
|
||||||
@@ -16,9 +16,9 @@ import { AccountInfo } from '../types/accountInfo.js';
|
|||||||
|
|
||||||
type SortBy = 'name' | 'count';
|
type SortBy = 'name' | 'count';
|
||||||
|
|
||||||
export default {
|
export const playerAchievementsCommand: CommandRegistry = {
|
||||||
inProduction: true,
|
inProduction: true,
|
||||||
inDevelopment: false,
|
inDevelopment: true,
|
||||||
inPublic: true,
|
inPublic: true,
|
||||||
|
|
||||||
command: new SlashCommandBuilder()
|
command: new SlashCommandBuilder()
|
||||||
@@ -75,4 +75,4 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
autocomplete: autocompleteUsername,
|
autocomplete: autocompleteUsername,
|
||||||
} satisfies CommandRegistry;
|
};
|
||||||
@@ -10,9 +10,9 @@ import { WARGAMING_APPLICATION_ID } from '../core/node/arguments.js';
|
|||||||
import { CommandRegistry } from '../events/interactionCreate/index.js';
|
import { CommandRegistry } from '../events/interactionCreate/index.js';
|
||||||
import { AccountInfo } from '../types/accountInfo.js';
|
import { AccountInfo } from '../types/accountInfo.js';
|
||||||
|
|
||||||
export default {
|
export const playerInfoCommand: CommandRegistry = {
|
||||||
inProduction: true,
|
inProduction: true,
|
||||||
inDevelopment: false,
|
inDevelopment: true,
|
||||||
inPublic: true,
|
inPublic: true,
|
||||||
|
|
||||||
command: new SlashCommandBuilder()
|
command: new SlashCommandBuilder()
|
||||||
@@ -57,4 +57,4 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
autocomplete: autocompleteUsername,
|
autocomplete: autocompleteUsername,
|
||||||
} satisfies CommandRegistry;
|
};
|
||||||
@@ -9,9 +9,9 @@ import { WARGAMING_APPLICATION_ID } from '../core/node/arguments.js';
|
|||||||
import { CommandRegistry } from '../events/interactionCreate/index.js';
|
import { CommandRegistry } from '../events/interactionCreate/index.js';
|
||||||
import { ClanList } from '../types/clanList.js';
|
import { ClanList } from '../types/clanList.js';
|
||||||
|
|
||||||
export default {
|
export const searchClansCommand: CommandRegistry = {
|
||||||
inProduction: true,
|
inProduction: true,
|
||||||
inDevelopment: false,
|
inDevelopment: true,
|
||||||
inPublic: true,
|
inPublic: true,
|
||||||
|
|
||||||
command: new SlashCommandBuilder()
|
command: new SlashCommandBuilder()
|
||||||
@@ -44,4 +44,4 @@ export default {
|
|||||||
.join('\n')}\n\`\`\``,
|
.join('\n')}\n\`\`\``,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
} satisfies CommandRegistry;
|
};
|
||||||
@@ -10,9 +10,9 @@ import { WARGAMING_APPLICATION_ID } from '../core/node/arguments.js';
|
|||||||
import { CommandRegistry } from '../events/interactionCreate/index.js';
|
import { CommandRegistry } from '../events/interactionCreate/index.js';
|
||||||
import { AccountList } from '../types/accountList.js';
|
import { AccountList } from '../types/accountList.js';
|
||||||
|
|
||||||
export default {
|
export const searchPlayersCommand: CommandRegistry = {
|
||||||
inProduction: true,
|
inProduction: true,
|
||||||
inDevelopment: false,
|
inDevelopment: true,
|
||||||
inPublic: true,
|
inPublic: true,
|
||||||
|
|
||||||
command: new SlashCommandBuilder()
|
command: new SlashCommandBuilder()
|
||||||
@@ -52,4 +52,4 @@ export default {
|
|||||||
}\`\`\``,
|
}\`\`\``,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
} satisfies CommandRegistry;
|
};
|
||||||
@@ -6,9 +6,9 @@ import addTankChoices from '../core/discord/addTankChoices.js';
|
|||||||
import embedInfo from '../core/discord/embedInfo.js';
|
import embedInfo from '../core/discord/embedInfo.js';
|
||||||
import { CommandRegistry } from '../events/interactionCreate/index.js';
|
import { CommandRegistry } from '../events/interactionCreate/index.js';
|
||||||
|
|
||||||
export default {
|
export const searchTanksCommand: CommandRegistry = {
|
||||||
inProduction: true,
|
inProduction: true,
|
||||||
inDevelopment: false,
|
inDevelopment: true,
|
||||||
inPublic: true,
|
inPublic: true,
|
||||||
|
|
||||||
command: new SlashCommandBuilder()
|
command: new SlashCommandBuilder()
|
||||||
@@ -26,7 +26,7 @@ export default {
|
|||||||
async handler(interaction) {
|
async handler(interaction) {
|
||||||
const tank = interaction.options.getString('tank')!;
|
const tank = interaction.options.getString('tank')!;
|
||||||
const limit = interaction.options.getInteger('limit') ?? 25;
|
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,
|
(result) => result.target,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -37,4 +37,4 @@ export default {
|
|||||||
: `\`\`\`\n${results.join('\n')}\n\`\`\``,
|
: `\`\`\`\n${results.join('\n')}\n\`\`\``,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
} satisfies CommandRegistry;
|
};
|
||||||
@@ -18,7 +18,7 @@ import stats from '../renderers/stats.js';
|
|||||||
import { StatType } from '../renderers/statsfull.js';
|
import { StatType } from '../renderers/statsfull.js';
|
||||||
import { AccountInfo } from '../types/accountInfo.js';
|
import { AccountInfo } from '../types/accountInfo.js';
|
||||||
|
|
||||||
export default {
|
export const statsCommand: CommandRegistry = {
|
||||||
inProduction: true,
|
inProduction: true,
|
||||||
inDevelopment: true,
|
inDevelopment: true,
|
||||||
inPublic: true,
|
inPublic: true,
|
||||||
@@ -36,7 +36,8 @@ export default {
|
|||||||
const player = await resolvePlayerFromCommand(interaction);
|
const player = await resolvePlayerFromCommand(interaction);
|
||||||
const period = resolvePeriodFromCommand(interaction);
|
const period = resolvePeriodFromCommand(interaction);
|
||||||
const tankIdRaw = interaction.options.getString('tank')!;
|
const tankIdRaw = interaction.options.getString('tank')!;
|
||||||
const tankId = commandGroup === 'tank' ? resolveTankId(tankIdRaw) : null;
|
const tankId =
|
||||||
|
commandGroup === 'tank' ? await resolveTankId(tankIdRaw) : null;
|
||||||
const start = interaction.options.getInteger('start');
|
const start = interaction.options.getInteger('start');
|
||||||
const end = interaction.options.getInteger('end');
|
const end = interaction.options.getInteger('end');
|
||||||
const path = interactionToURL(interaction, {
|
const path = interactionToURL(interaction, {
|
||||||
@@ -81,4 +82,4 @@ export default {
|
|||||||
parseInt(url.searchParams.get('tankId')!),
|
parseInt(url.searchParams.get('tankId')!),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
} satisfies CommandRegistry;
|
};
|
||||||
@@ -17,9 +17,9 @@ import { CommandRegistry } from '../events/interactionCreate/index.js';
|
|||||||
import statsfull, { StatType } from '../renderers/statsfull.js';
|
import statsfull, { StatType } from '../renderers/statsfull.js';
|
||||||
import { AccountInfo } from '../types/accountInfo.js';
|
import { AccountInfo } from '../types/accountInfo.js';
|
||||||
|
|
||||||
export default {
|
export const statsFullCommand: CommandRegistry = {
|
||||||
inProduction: true,
|
inProduction: true,
|
||||||
inDevelopment: false,
|
inDevelopment: true,
|
||||||
inPublic: true,
|
inPublic: true,
|
||||||
|
|
||||||
command: addStatTypeSubCommandGroups(
|
command: addStatTypeSubCommandGroups(
|
||||||
@@ -35,7 +35,8 @@ export default {
|
|||||||
const player = await resolvePlayerFromCommand(interaction);
|
const player = await resolvePlayerFromCommand(interaction);
|
||||||
const period = resolvePeriodFromCommand(interaction);
|
const period = resolvePeriodFromCommand(interaction);
|
||||||
const tankIdRaw = interaction.options.getString('tank')!;
|
const tankIdRaw = interaction.options.getString('tank')!;
|
||||||
const tankId = commandGroup === 'tank' ? resolveTankId(tankIdRaw) : null;
|
const tankId =
|
||||||
|
commandGroup === 'tank' ? await resolveTankId(tankIdRaw) : null;
|
||||||
const start = interaction.options.getInteger('start');
|
const start = interaction.options.getInteger('start');
|
||||||
const end = interaction.options.getInteger('end');
|
const end = interaction.options.getInteger('end');
|
||||||
const path = interactionToURL(interaction, {
|
const path = interactionToURL(interaction, {
|
||||||
@@ -80,4 +81,4 @@ export default {
|
|||||||
parseInt(url.searchParams.get('tankId')!),
|
parseInt(url.searchParams.get('tankId')!),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
} satisfies CommandRegistry;
|
};
|
||||||
@@ -12,9 +12,9 @@ import { CommandRegistry } from '../events/interactionCreate/index.js';
|
|||||||
import today from '../renderers/today.js';
|
import today from '../renderers/today.js';
|
||||||
import { AccountInfo } from '../types/accountInfo.js';
|
import { AccountInfo } from '../types/accountInfo.js';
|
||||||
|
|
||||||
export default {
|
export const todayCommand: CommandRegistry = {
|
||||||
inProduction: true,
|
inProduction: true,
|
||||||
inDevelopment: false,
|
inDevelopment: true,
|
||||||
inPublic: true,
|
inPublic: true,
|
||||||
|
|
||||||
command: new SlashCommandBuilder()
|
command: new SlashCommandBuilder()
|
||||||
@@ -49,4 +49,4 @@ export default {
|
|||||||
|
|
||||||
return await today(player);
|
return await today(player);
|
||||||
},
|
},
|
||||||
} satisfies CommandRegistry;
|
};
|
||||||
@@ -12,9 +12,9 @@ import { CommandRegistry } from '../events/interactionCreate/index.js';
|
|||||||
import { AccountInfo } from '../types/accountInfo.js';
|
import { AccountInfo } from '../types/accountInfo.js';
|
||||||
import { PlayerClanData } from '../types/playerClanData.js';
|
import { PlayerClanData } from '../types/playerClanData.js';
|
||||||
|
|
||||||
export default {
|
export const verifyCommand: CommandRegistry = {
|
||||||
inProduction: true,
|
inProduction: true,
|
||||||
inDevelopment: false,
|
inDevelopment: true,
|
||||||
inPublic: true,
|
inPublic: true,
|
||||||
|
|
||||||
command: new SlashCommandBuilder()
|
command: new SlashCommandBuilder()
|
||||||
@@ -80,4 +80,4 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
autocomplete: autocompleteUsername,
|
autocomplete: autocompleteUsername,
|
||||||
} satisfies CommandRegistry;
|
};
|
||||||
@@ -2,11 +2,11 @@ import { go } from 'fuzzysort';
|
|||||||
import throwError from '../node/throwError.js';
|
import throwError from '../node/throwError.js';
|
||||||
import { TANKS, tankopedia } from './tankopedia.js';
|
import { TANKS, tankopedia } from './tankopedia.js';
|
||||||
|
|
||||||
export default function resolveTankId(tank: string) {
|
export default async function resolveTankId(tank: string) {
|
||||||
const number = parseInt(tank);
|
const number = parseInt(tank);
|
||||||
|
|
||||||
if (Number.isNaN(number)) {
|
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) {
|
if (searchResult.length === 0) {
|
||||||
throw throwError(
|
throw throwError(
|
||||||
@@ -17,7 +17,7 @@ export default function resolveTankId(tank: string) {
|
|||||||
return searchResult[0].obj.tank_id;
|
return searchResult[0].obj.tank_id;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (tankopedia[number]) {
|
if ((await tankopedia)[number]) {
|
||||||
return number;
|
return number;
|
||||||
} else {
|
} else {
|
||||||
throw throwError(
|
throw throwError(
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { tankopedia } from './tankopedia.js';
|
import { tankopedia } from './tankopedia.js';
|
||||||
|
|
||||||
export default function resolveTankName(tankId: number) {
|
export default async function resolveTankName(tankId: number) {
|
||||||
const tankopediaName = tankopedia[tankId].name;
|
const tankopediaName = (await tankopedia)[tankId].name;
|
||||||
return tankopediaName ? tankopediaName : `Unknown Tank ${tankId}`;
|
return tankopediaName ? tankopediaName : `Unknown Tank ${tankId}`;
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,3 @@
|
|||||||
import { readFileSync } from 'fs';
|
|
||||||
import { join } from 'path';
|
|
||||||
|
|
||||||
export type TankType = 'AT-SPG' | 'lightTank' | 'mediumTank' | 'heavyTank';
|
export type TankType = 'AT-SPG' | 'lightTank' | 'mediumTank' | 'heavyTank';
|
||||||
|
|
||||||
export interface TankopediaEntry {
|
export interface TankopediaEntry {
|
||||||
@@ -19,15 +16,22 @@ export interface Tankopedia {
|
|||||||
[id: number]: TankopediaEntry;
|
[id: number]: TankopediaEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const tankopedia = (
|
console.log('Caching tankopedia...');
|
||||||
JSON.parse(
|
export const tankopedia = fetch('https://www.blitzstars.com/bs-tankopedia.json')
|
||||||
readFileSync(join(__dirname, 'tankopedia.json')) as unknown as string,
|
.then((response) => response.json())
|
||||||
) as { data: Tankopedia }
|
.then((wrapperTankopedia) => {
|
||||||
).data;
|
console.log('Tankopedia cached');
|
||||||
const entries = Object.entries(tankopedia);
|
return (wrapperTankopedia as { data: Tankopedia }).data;
|
||||||
export const TANK_IDS = entries.map(([, { tank_id }]) => tank_id as number);
|
});
|
||||||
export const TANKS = entries.map(([, entry]) => entry as TankopediaEntry);
|
const entries = new Promise<TankopediaEntry[]>(async (resolve) => {
|
||||||
export const TANK_NAMES = entries.map(([, { name }]) => name);
|
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<TankType, string> = {
|
||||||
'AT-SPG': 'https://i.imgur.com/BIHSEH0.png',
|
'AT-SPG': 'https://i.imgur.com/BIHSEH0.png',
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { emptyAllStats } from './getTankStatsDiffed.js';
|
|||||||
import { tankAverages } from './tankAverages.js';
|
import { tankAverages } from './tankAverages.js';
|
||||||
|
|
||||||
console.log('Calculating player average...');
|
console.log('Calculating player average...');
|
||||||
export const playerAverage = Object.entries(tankAverages).reduce(
|
export const playerAverage = Object.entries(await tankAverages).reduce(
|
||||||
(accumulator, [, tankAverage]) => {
|
(accumulator, [, tankAverage]) => {
|
||||||
return sumStats([accumulator, tankAverage.all]);
|
return sumStats([accumulator, tankAverage.all]);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { readFileSync } from 'fs';
|
|
||||||
import { join } from 'path';
|
|
||||||
import { AllStats } from '../../types/accountInfo.js';
|
import { AllStats } from '../../types/accountInfo.js';
|
||||||
|
|
||||||
export interface SpecialStats {
|
export interface SpecialStats {
|
||||||
@@ -82,6 +80,22 @@ export interface IndividualTankAverage {
|
|||||||
|
|
||||||
export type TankAverages = Record<number, IndividualTankAverage>;
|
export type TankAverages = Record<number, IndividualTankAverage>;
|
||||||
|
|
||||||
export const tankAverages = JSON.parse(
|
console.log('Caching tank averages...');
|
||||||
readFileSync(join(__dirname, 'tankaverages.json')) as unknown as string,
|
export const tankAverages = fetch(
|
||||||
) as TankAverages;
|
'https://www.blitzstars.com/api/tankaverages.json',
|
||||||
|
)
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((individualTankAverages) => {
|
||||||
|
const partialTankAverages: TankAverages = {};
|
||||||
|
|
||||||
|
(individualTankAverages as IndividualTankAverage[]).forEach(
|
||||||
|
(individualTankAverage) => {
|
||||||
|
partialTankAverages[individualTankAverage.tank_id] =
|
||||||
|
individualTankAverage;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('Tank averages cached');
|
||||||
|
|
||||||
|
return partialTankAverages;
|
||||||
|
});
|
||||||
@@ -15,14 +15,16 @@ export default async function autocompleteTanks(
|
|||||||
|
|
||||||
await interaction.respond(
|
await interaction.respond(
|
||||||
focusedOption.value
|
focusedOption.value
|
||||||
? go(focusedOption.value, TANKS, { keys: ['name'], limit: 10 }).map(
|
? await Promise.all(
|
||||||
(item) =>
|
go(focusedOption.value, await TANKS, { keys: ['name'], limit: 10 }).map(
|
||||||
({
|
async (item) =>
|
||||||
name: item.obj.name
|
({
|
||||||
? item.obj.name
|
name: item.obj.name
|
||||||
: resolveTankName(item.obj.tank_id),
|
? item.obj.name
|
||||||
value: `${item.obj.tank_id}`,
|
: await resolveTankName(item.obj.tank_id),
|
||||||
} satisfies ApplicationCommandOptionChoiceData<string>),
|
value: `${item.obj.tank_id}`,
|
||||||
|
} satisfies ApplicationCommandOptionChoiceData<string>),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
: [],
|
: [],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -13,22 +13,22 @@ import {
|
|||||||
SlashCommandSubcommandsOnlyBuilder,
|
SlashCommandSubcommandsOnlyBuilder,
|
||||||
} from 'discord.js';
|
} from 'discord.js';
|
||||||
import discord from '../../../discord.json' assert { type: 'json' };
|
import discord from '../../../discord.json' assert { type: 'json' };
|
||||||
import debug from '../../commands/debug.js';
|
import { debugCommand } from '../../commands/debug.js';
|
||||||
import eligible from '../../commands/eligible.js';
|
import { eligibleCommand } from '../../commands/eligible.js';
|
||||||
import evolution from '../../commands/evolution.js';
|
import { evolutionCommand } from '../../commands/evolution.js';
|
||||||
import help from '../../commands/help.js';
|
import { helpCommand } from '../../commands/help.js';
|
||||||
import inactive from '../../commands/inactive.js';
|
import { inactiveCommand } from '../../commands/inactive.js';
|
||||||
import ownedtanks from '../../commands/ownedtanks.js';
|
import { ownedTanksCommand } from '../../commands/ownedTanks.js';
|
||||||
import ping from '../../commands/ping.js';
|
import { pingCommand } from '../../commands/ping.js';
|
||||||
import playerachievements from '../../commands/playerachievements.js';
|
import { playerAchievementsCommand } from '../../commands/playerAchievements.js';
|
||||||
import playerinfo from '../../commands/playerinfo.js';
|
import { playerInfoCommand } from '../../commands/playerInfo.js';
|
||||||
import searchclans from '../../commands/searchclans.js';
|
import { searchClansCommand } from '../../commands/searchClans.js';
|
||||||
import searchplayers from '../../commands/searchplayers.js';
|
import { searchPlayersCommand } from '../../commands/searchPlayers.js';
|
||||||
import searchtanks from '../../commands/searchtanks.js';
|
import { searchTanksCommand } from '../../commands/searchTanks.js';
|
||||||
import stats from '../../commands/stats.js';
|
import { statsCommand } from '../../commands/stats.js';
|
||||||
import statsfull from '../../commands/statsfull.js';
|
import { statsFullCommand } from '../../commands/statsFull.js';
|
||||||
import today from '../../commands/today.js';
|
import { todayCommand } from '../../commands/today.js';
|
||||||
import verify from '../../commands/verify.js';
|
import { verifyCommand } from '../../commands/verify.js';
|
||||||
import { DISCORD_TOKEN } from '../../core/node/arguments.js';
|
import { DISCORD_TOKEN } from '../../core/node/arguments.js';
|
||||||
import getClientId from '../../core/node/getClientId.js';
|
import getClientId from '../../core/node/getClientId.js';
|
||||||
import isDev from '../../core/node/isDev.js';
|
import isDev from '../../core/node/isDev.js';
|
||||||
@@ -72,26 +72,25 @@ const rest = new REST().setToken(DISCORD_TOKEN);
|
|||||||
|
|
||||||
export const commands: Record<string, CommandRegistry> = (
|
export const commands: Record<string, CommandRegistry> = (
|
||||||
[
|
[
|
||||||
debug,
|
debugCommand,
|
||||||
eligible,
|
eligibleCommand,
|
||||||
help,
|
helpCommand,
|
||||||
inactive,
|
inactiveCommand,
|
||||||
ownedtanks,
|
ownedTanksCommand,
|
||||||
playerachievements,
|
playerAchievementsCommand,
|
||||||
playerinfo,
|
playerInfoCommand,
|
||||||
searchclans,
|
searchClansCommand,
|
||||||
searchplayers,
|
searchPlayersCommand,
|
||||||
searchtanks,
|
searchTanksCommand,
|
||||||
statsfull,
|
statsFullCommand,
|
||||||
today,
|
todayCommand,
|
||||||
verify,
|
verifyCommand,
|
||||||
ping,
|
pingCommand,
|
||||||
evolution,
|
evolutionCommand,
|
||||||
stats,
|
statsCommand,
|
||||||
] as CommandRegistry[]
|
] as CommandRegistry[]
|
||||||
).reduce((accumulator, registry) => {
|
).reduce((accumulator, registry) => {
|
||||||
if (isDev()) registry.command.setDefaultMemberPermissions(0);
|
if (isDev()) registry.command.setDefaultMemberPermissions(0);
|
||||||
|
|
||||||
return { ...accumulator, [registry.command.name]: registry };
|
return { ...accumulator, [registry.command.name]: registry };
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
|
|||||||
@@ -38,8 +38,8 @@ export default async function evolution<Type extends StatType>(
|
|||||||
? `https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clan.emblem_set_id}.png`
|
? `https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clan.emblem_set_id}.png`
|
||||||
: undefined;
|
: undefined;
|
||||||
} else {
|
} else {
|
||||||
nameDiscriminator = `(${resolveTankName(tankId!)})`;
|
nameDiscriminator = `(${await resolveTankName(tankId!)})`;
|
||||||
image = tankopedia[tankId!].images.normal;
|
image = (await tankopedia)[tankId!].images.normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
let histories: Histories;
|
let histories: Histories;
|
||||||
|
|||||||
+53
-43
@@ -22,6 +22,7 @@ import {
|
|||||||
SupplementaryStats,
|
SupplementaryStats,
|
||||||
} from '../types/accountInfo.js';
|
} from '../types/accountInfo.js';
|
||||||
import { PlayerClanData } from '../types/playerClanData.js';
|
import { PlayerClanData } from '../types/playerClanData.js';
|
||||||
|
import { PossiblyPromise } from '../types/possiblyPromise.js';
|
||||||
|
|
||||||
export type StatType = 'player' | 'tank';
|
export type StatType = 'player' | 'tank';
|
||||||
|
|
||||||
@@ -47,66 +48,73 @@ export default async function stats<Type extends StatType>(
|
|||||||
? `https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clan.emblem_set_id}.png`
|
? `https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clan.emblem_set_id}.png`
|
||||||
: undefined;
|
: undefined;
|
||||||
} else if (type === 'tank') {
|
} else if (type === 'tank') {
|
||||||
nameDiscriminator = `(${resolveTankName(tankId!)})`;
|
nameDiscriminator = `(${await resolveTankName(tankId!)})`;
|
||||||
image = tankopedia[tankId!].images.normal;
|
image = (await tankopedia)[tankId!].images.normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tankStats = await getTankStatsDiffed(server, id, start, end);
|
const tankStats = await getTankStatsDiffed(server, id, start, end);
|
||||||
let stats: AllStats;
|
let stats: AllStats | undefined;
|
||||||
let supplementaryStats: SupplementaryStats;
|
let supplementaryStats: SupplementaryStats;
|
||||||
let tierWeights: TierWeightsRecord;
|
let tierWeights: TierWeightsRecord;
|
||||||
|
|
||||||
if (type === 'player') {
|
if (type === 'player') {
|
||||||
const entries = Object.entries(tankStats);
|
const entries = Object.entries(tankStats);
|
||||||
stats = sumStats(entries.map(([, stats]) => stats));
|
stats = sumStats(entries.map(([, stats]) => stats));
|
||||||
const battlesOfTanksWithAverages = entries.reduce(
|
const battlesOfTanksWithAverages = await entries.reduce<
|
||||||
(accumulator, [tankIdString, stats]) => {
|
PossiblyPromise<number>
|
||||||
const tankId = parseInt(tankIdString);
|
>(async (accumulator, [tankIdString, stats]) => {
|
||||||
const tankAverage = tankAverages[tankId];
|
const tankId = parseInt(tankIdString);
|
||||||
|
const tankAverage = (await tankAverages)[tankId];
|
||||||
|
|
||||||
return tankAverage ? accumulator + stats.battles : accumulator;
|
return tankAverage ? (await accumulator) + stats.battles : accumulator;
|
||||||
},
|
}, 0);
|
||||||
0,
|
const battlesOfTanksWithTankopediaEntry = await entries.reduce<
|
||||||
);
|
PossiblyPromise<number>
|
||||||
const battlesOfTanksWithTankopediaEntry = entries.reduce(
|
>(async (accumulator, [tankIdString, stats]) => {
|
||||||
(accumulator, [tankIdString, stats]) => {
|
const tankId = parseInt(tankIdString);
|
||||||
const tankId = parseInt(tankIdString);
|
const tankopediaEntry = (await tankopedia)[tankId];
|
||||||
const tankopediaEntry = tankopedia[tankId];
|
|
||||||
|
|
||||||
return tankopediaEntry ? accumulator + stats.battles : accumulator;
|
return tankopediaEntry
|
||||||
},
|
? (await accumulator) + stats.battles
|
||||||
0,
|
: accumulator;
|
||||||
);
|
}, 0);
|
||||||
|
|
||||||
supplementaryStats = {
|
supplementaryStats = {
|
||||||
WN8:
|
WN8:
|
||||||
entries.reduce((accumulator, [tankIdString, stats]) => {
|
(await entries.reduce<PossiblyPromise<number>>(
|
||||||
const tankId = parseInt(tankIdString);
|
async (accumulator, [tankIdString, stats]) => {
|
||||||
const tankAverage = tankAverages[tankId];
|
const tankId = parseInt(tankIdString);
|
||||||
|
const tankAverage = (await tankAverages)[tankId];
|
||||||
|
|
||||||
return tankAverage
|
return tankAverage
|
||||||
? accumulator + calculateWN8(tankAverage.all, stats) * stats.battles
|
? (await accumulator) +
|
||||||
: accumulator;
|
calculateWN8(tankAverage.all, stats) * stats.battles
|
||||||
}, 0) / battlesOfTanksWithAverages,
|
: accumulator;
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
)) / battlesOfTanksWithAverages,
|
||||||
tier:
|
tier:
|
||||||
entries.reduce((accumulator, [tankIdString, stats]) => {
|
(await entries.reduce<PossiblyPromise<number>>(
|
||||||
const tankId = parseInt(tankIdString);
|
async (accumulator, [tankIdString, stats]) => {
|
||||||
const tankopediaEntry = tankopedia[tankId];
|
const tankId = parseInt(tankIdString);
|
||||||
|
const tankopediaEntry = (await tankopedia)[tankId];
|
||||||
|
|
||||||
return tankopediaEntry
|
return tankopediaEntry
|
||||||
? accumulator + tankopediaEntry.tier * stats.battles
|
? (await accumulator) + tankopediaEntry.tier * stats.battles
|
||||||
: accumulator;
|
: accumulator;
|
||||||
}, 0) / battlesOfTanksWithTankopediaEntry,
|
},
|
||||||
|
0,
|
||||||
|
)) / battlesOfTanksWithTankopediaEntry,
|
||||||
};
|
};
|
||||||
tierWeights = entries.reduce<TierWeightsRecord>(
|
tierWeights = await entries.reduce<PossiblyPromise<TierWeightsRecord>>(
|
||||||
(accumulator, [tankIdString, stats]) => {
|
async (accumulator, [tankIdString, stats]) => {
|
||||||
const tankId = parseInt(tankIdString);
|
const tankId = parseInt(tankIdString);
|
||||||
const tier = tankopedia[tankId].tier as Tier;
|
const tier = (await tankopedia)[tankId].tier as Tier;
|
||||||
|
|
||||||
if (accumulator[tier]) {
|
if ((await accumulator)[tier]) {
|
||||||
accumulator[tier]! += stats.battles;
|
(await accumulator)[tier]! += stats.battles;
|
||||||
} else {
|
} else {
|
||||||
accumulator[tier] = stats.battles;
|
(await accumulator)[tier] = stats.battles;
|
||||||
}
|
}
|
||||||
|
|
||||||
return accumulator;
|
return accumulator;
|
||||||
@@ -117,8 +125,10 @@ export default async function stats<Type extends StatType>(
|
|||||||
stats = tankStats[tankId!];
|
stats = tankStats[tankId!];
|
||||||
|
|
||||||
supplementaryStats = {
|
supplementaryStats = {
|
||||||
WN8: calculateWN8(tankAverages[tankId!].all, tankStats[tankId!]),
|
WN8: stats
|
||||||
tier: tankopedia[tankId!].tier,
|
? calculateWN8((await tankAverages)[tankId!].all, tankStats[tankId!])
|
||||||
|
: undefined,
|
||||||
|
tier: (await tankopedia)[tankId!].tier,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,8 +164,8 @@ export default async function stats<Type extends StatType>(
|
|||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{stats.battles === 0 && <NoData type={NoDataType.BattlesInPeriod} />}
|
{!stats?.battles && <NoData type={NoDataType.BattlesInPeriod} />}
|
||||||
{stats.battles > 0 && overview}
|
{stats?.battles > 0 && overview}
|
||||||
|
|
||||||
<PoweredBy type={PoweredByType.BlitzStars} footer={footer} />
|
<PoweredBy type={PoweredByType.BlitzStars} footer={footer} />
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
|
|||||||
+52
-40
@@ -21,6 +21,7 @@ import {
|
|||||||
SupplementaryStats,
|
SupplementaryStats,
|
||||||
} from '../types/accountInfo.js';
|
} from '../types/accountInfo.js';
|
||||||
import { PlayerClanData } from '../types/playerClanData.js';
|
import { PlayerClanData } from '../types/playerClanData.js';
|
||||||
|
import { PossiblyPromise } from '../types/possiblyPromise.js';
|
||||||
|
|
||||||
export type StatType = 'player' | 'tank';
|
export type StatType = 'player' | 'tank';
|
||||||
|
|
||||||
@@ -45,8 +46,8 @@ export default async function statsfull<Type extends StatType>(
|
|||||||
? `https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clan.emblem_set_id}.png`
|
? `https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clan.emblem_set_id}.png`
|
||||||
: undefined;
|
: undefined;
|
||||||
} else if (type === 'tank') {
|
} else if (type === 'tank') {
|
||||||
nameDiscriminator = `(${resolveTankName(tankId!)})`;
|
nameDiscriminator = `(${await resolveTankName(tankId!)})`;
|
||||||
image = tankopedia[tankId!].images.normal;
|
image = (await tankopedia)[tankId!].images.normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tankStats = await getTankStatsDiffed(server, id, start, end);
|
const tankStats = await getTankStatsDiffed(server, id, start, end);
|
||||||
@@ -57,54 +58,65 @@ export default async function statsfull<Type extends StatType>(
|
|||||||
if (type === 'player') {
|
if (type === 'player') {
|
||||||
const entries = Object.entries(tankStats);
|
const entries = Object.entries(tankStats);
|
||||||
stats = sumStats(entries.map(([, stats]) => stats));
|
stats = sumStats(entries.map(([, stats]) => stats));
|
||||||
const battlesOfTanksWithAverages = entries.reduce(
|
const battlesOfTanksWithAverages = await entries.reduce<
|
||||||
(accumulator, [tankIdString, stats]) => {
|
PossiblyPromise<number>
|
||||||
const tankId = parseInt(tankIdString);
|
>(async (accumulator, [tankIdString, stats]) => {
|
||||||
const tankAverage = tankAverages[tankId];
|
const tankId = parseInt(tankIdString);
|
||||||
|
const tankAverage = (await tankAverages)[tankId];
|
||||||
|
|
||||||
return tankAverage ? accumulator + stats.battles : accumulator;
|
return tankAverage ? (await accumulator) + stats.battles : accumulator;
|
||||||
},
|
}, 0);
|
||||||
0,
|
const battlesOfTanksWithTankopediaEntry = await entries.reduce<
|
||||||
);
|
PossiblyPromise<number>
|
||||||
const battlesOfTanksWithTankopediaEntry = entries.reduce(
|
>(async (accumulator, [tankIdString, stats]) => {
|
||||||
(accumulator, [tankIdString, stats]) => {
|
const tankId = parseInt(tankIdString);
|
||||||
const tankId = parseInt(tankIdString);
|
const tankopediaEntry = (await tankopedia)[tankId];
|
||||||
const tankopediaEntry = tankopedia[tankId];
|
|
||||||
|
|
||||||
return tankopediaEntry ? accumulator + stats.battles : accumulator;
|
return tankopediaEntry
|
||||||
},
|
? (await accumulator) + stats.battles
|
||||||
0,
|
: accumulator;
|
||||||
);
|
}, 0);
|
||||||
|
|
||||||
supplementaryStats = {
|
supplementaryStats = {
|
||||||
WN8:
|
WN8:
|
||||||
entries.reduce((accumulator, [tankIdString, stats]) => {
|
(await entries.reduce<PossiblyPromise<number>>(
|
||||||
const tankId = parseInt(tankIdString);
|
async (accumulator, [tankIdString, stats]) => {
|
||||||
const tankAverage = tankAverages[tankId];
|
const tankId = parseInt(tankIdString);
|
||||||
|
const tankAverage = (await tankAverages)[tankId];
|
||||||
|
|
||||||
return tankAverage
|
return tankAverage
|
||||||
? accumulator + calculateWN8(tankAverage.all, stats) * stats.battles
|
? (await accumulator) +
|
||||||
: accumulator;
|
calculateWN8(tankAverage.all, stats) * stats.battles
|
||||||
}, 0) / battlesOfTanksWithAverages,
|
: accumulator;
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
)) / battlesOfTanksWithAverages,
|
||||||
tier:
|
tier:
|
||||||
entries.reduce((accumulator, [tankIdString, stats]) => {
|
(await entries.reduce<PossiblyPromise<number>>(
|
||||||
const tankId = parseInt(tankIdString);
|
async (accumulator, [tankIdString, stats]) => {
|
||||||
const tankopediaEntry = tankopedia[tankId];
|
const tankId = parseInt(tankIdString);
|
||||||
|
const tankopediaEntry = (await tankopedia)[tankId];
|
||||||
|
|
||||||
return tankopediaEntry
|
return tankopediaEntry
|
||||||
? accumulator + tankopediaEntry.tier * stats.battles
|
? (await accumulator) + tankopediaEntry.tier * stats.battles
|
||||||
: accumulator;
|
: accumulator;
|
||||||
}, 0) / battlesOfTanksWithTankopediaEntry,
|
},
|
||||||
|
0,
|
||||||
|
)) / battlesOfTanksWithTankopediaEntry,
|
||||||
};
|
};
|
||||||
tierWeights = entries.reduce<TierWeightsRecord>(
|
tierWeights = await entries.reduce<PossiblyPromise<TierWeightsRecord>>(
|
||||||
(accumulator, [tankIdString, stats]) => {
|
async (accumulator, [tankIdString, stats]) => {
|
||||||
const tankId = parseInt(tankIdString);
|
const tankId = parseInt(tankIdString);
|
||||||
const tier = tankopedia[tankId].tier as Tier;
|
const tankopediaEntry = (await tankopedia)[tankId];
|
||||||
|
|
||||||
if (accumulator[tier]) {
|
if (!tankopediaEntry) return accumulator;
|
||||||
accumulator[tier]! += stats.battles;
|
|
||||||
|
const tier = (await tankopedia)[tankId].tier as Tier;
|
||||||
|
|
||||||
|
if ((await accumulator)[tier]) {
|
||||||
|
(await accumulator)[tier]! += stats.battles;
|
||||||
} else {
|
} else {
|
||||||
accumulator[tier] = stats.battles;
|
(await accumulator)[tier] = stats.battles;
|
||||||
}
|
}
|
||||||
|
|
||||||
return accumulator;
|
return accumulator;
|
||||||
@@ -115,8 +127,8 @@ export default async function statsfull<Type extends StatType>(
|
|||||||
stats = tankStats[tankId!];
|
stats = tankStats[tankId!];
|
||||||
|
|
||||||
supplementaryStats = {
|
supplementaryStats = {
|
||||||
WN8: calculateWN8(tankAverages[tankId!].all, tankStats[tankId!]),
|
WN8: calculateWN8((await tankAverages)[tankId!].all, tankStats[tankId!]),
|
||||||
tier: tankopedia[tankId!].tier,
|
tier: (await tankopedia)[tankId!].tier,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+55
-51
@@ -18,6 +18,7 @@ import { ResolvedPlayer } from '../core/discord/resolvePlayerFromCommand.js';
|
|||||||
import { WARGAMING_APPLICATION_ID } from '../core/node/arguments.js';
|
import { WARGAMING_APPLICATION_ID } from '../core/node/arguments.js';
|
||||||
import { AccountInfo, AllStats } from '../types/accountInfo.js';
|
import { AccountInfo, AllStats } from '../types/accountInfo.js';
|
||||||
import { PlayerClanData } from '../types/playerClanData.js';
|
import { PlayerClanData } from '../types/playerClanData.js';
|
||||||
|
import { PossiblyPromise } from '../types/possiblyPromise.js';
|
||||||
|
|
||||||
export default async function today({ server, id }: ResolvedPlayer) {
|
export default async function today({ server, id }: ResolvedPlayer) {
|
||||||
const tankStatsOverTime = await getTankStatsDiffed(
|
const tankStatsOverTime = await getTankStatsDiffed(
|
||||||
@@ -52,33 +53,31 @@ export default async function today({ server, id }: ResolvedPlayer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const tankStatsOverTimeEntries = Object.entries(tankStatsOverTime);
|
const tankStatsOverTimeEntries = Object.entries(tankStatsOverTime);
|
||||||
const todayWN8s = tankStatsOverTimeEntries.reduce<Record<number, number>>(
|
const todayWN8s = await tankStatsOverTimeEntries.reduce<
|
||||||
(accumulator, [tankIdString, tankStats]) => {
|
PossiblyPromise<Record<number, number>>
|
||||||
const tankId = Number(tankIdString);
|
>(async (accumulator, [tankIdString, tankStats]) => {
|
||||||
|
const tankId = Number(tankIdString);
|
||||||
|
|
||||||
return tankId === 0 || tankAverages[tankId] === undefined
|
return tankId === 0 || (await tankAverages)[tankId] === undefined
|
||||||
? accumulator
|
? accumulator
|
||||||
: {
|
: {
|
||||||
...accumulator,
|
...accumulator,
|
||||||
[tankId]: calculateWN8(tankAverages[tankId].all, tankStats),
|
[tankId]: calculateWN8((await tankAverages)[tankId].all, tankStats),
|
||||||
};
|
};
|
||||||
},
|
}, {});
|
||||||
{},
|
const careerWN8s = await careerTankStatsRaw.reduce<
|
||||||
);
|
PossiblyPromise<Record<number, number>>
|
||||||
const careerWN8s = careerTankStatsRaw.reduce<Record<number, number>>(
|
>(async (accumulator, { tank_id }) => {
|
||||||
(accumulator, { tank_id }) => {
|
return tank_id === 0 || (await tankAverages)[tank_id] === undefined
|
||||||
return tank_id === 0 || tankAverages[tank_id] === undefined
|
? accumulator
|
||||||
? accumulator
|
: {
|
||||||
: {
|
...accumulator,
|
||||||
...accumulator,
|
[tank_id]: calculateWN8(
|
||||||
[tank_id]: calculateWN8(
|
(await tankAverages)[tank_id].all,
|
||||||
tankAverages[tank_id].all,
|
careerStats[tank_id],
|
||||||
careerStats[tank_id],
|
),
|
||||||
),
|
};
|
||||||
};
|
}, {});
|
||||||
},
|
|
||||||
{},
|
|
||||||
);
|
|
||||||
const todayWN8sEntries = Object.entries(todayWN8s);
|
const todayWN8sEntries = Object.entries(todayWN8s);
|
||||||
const careerWN8sEntries = Object.entries(careerWN8s);
|
const careerWN8sEntries = Object.entries(careerWN8s);
|
||||||
|
|
||||||
@@ -109,32 +108,37 @@ export default async function today({ server, id }: ResolvedPlayer) {
|
|||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
|
|
||||||
const rows = tankStatsOverTimeEntries.map(([tankIdString, tankStats]) => {
|
const rows = await Promise.all(
|
||||||
const tankId = Number(tankIdString);
|
tankStatsOverTimeEntries.map(async ([tankIdString, tankStats]) => {
|
||||||
const career = careerStats[tankId];
|
const tankId = Number(tankIdString);
|
||||||
|
const career = careerStats[tankId];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Breakdown.Row
|
<Breakdown.Row
|
||||||
key={tankId}
|
key={tankId}
|
||||||
name={tankId === 0 ? 'Total' : resolveTankName(Number(tankIdString))}
|
name={
|
||||||
winrate={tankStats.wins / tankStats.battles}
|
tankId === 0 ? 'Total' : await resolveTankName(Number(tankIdString))
|
||||||
careerWinrate={career.wins / career.battles}
|
}
|
||||||
WN8={isNaN(todayWN8s[tankId]) ? undefined : todayWN8s[tankId]}
|
winrate={tankStats.wins / tankStats.battles}
|
||||||
careerWN8={isNaN(careerWN8s[tankId]) ? undefined : careerWN8s[tankId]}
|
careerWinrate={career.wins / career.battles}
|
||||||
damage={tankStats.damage_dealt / tankStats.battles}
|
WN8={isNaN(todayWN8s[tankId]) ? undefined : todayWN8s[tankId]}
|
||||||
careerDamage={career.damage_dealt / career.battles}
|
careerWN8={isNaN(careerWN8s[tankId]) ? undefined : careerWN8s[tankId]}
|
||||||
survival={tankStats.survived_battles / tankStats.battles}
|
damage={tankStats.damage_dealt / tankStats.battles}
|
||||||
careerSurvival={career.survived_battles / career.battles}
|
careerDamage={career.damage_dealt / career.battles}
|
||||||
battles={tankStats.battles}
|
survival={tankStats.survived_battles / tankStats.battles}
|
||||||
careerBattles={career.battles}
|
careerSurvival={career.survived_battles / career.battles}
|
||||||
icon={
|
battles={tankStats.battles}
|
||||||
tankId === 0
|
careerBattles={career.battles}
|
||||||
? undefined
|
icon={
|
||||||
: tankopedia[tankIdString as unknown as number].images.normal
|
tankId === 0
|
||||||
}
|
? undefined
|
||||||
/>
|
: (await tankopedia)[tankIdString as unknown as number].images
|
||||||
);
|
.normal
|
||||||
});
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ import resolvePlayerFromRequest from '../core/express/resolvePlayerFromRequest.j
|
|||||||
import stats, { StatType } from '../renderers/stats.js';
|
import stats, { StatType } from '../renderers/stats.js';
|
||||||
import { RouteRegistry } from '../server.js';
|
import { RouteRegistry } from '../server.js';
|
||||||
|
|
||||||
export default {
|
export const statsRoute: RouteRegistry = {
|
||||||
route: '/stats/:type/:period',
|
route: '/stats/:type/:period',
|
||||||
inProduction: true,
|
inProduction: true,
|
||||||
inDevelopment: false,
|
inDevelopment: true,
|
||||||
|
|
||||||
async handler(req) {
|
async handler(req) {
|
||||||
const type = req.params.type as StatType;
|
const type = req.params.type as StatType;
|
||||||
@@ -21,4 +21,4 @@ export default {
|
|||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
} satisfies RouteRegistry;
|
};
|
||||||
+2
-2
@@ -3,7 +3,7 @@ import normalizeRouteReturnable from './core/express/normalizeRouteReturnable.js
|
|||||||
import withAutoRefresh from './core/express/withAutoRefresh.js';
|
import withAutoRefresh from './core/express/withAutoRefresh.js';
|
||||||
import isDev from './core/node/isDev.js';
|
import isDev from './core/node/isDev.js';
|
||||||
import { Registry } from './events/interactionCreate/index.js';
|
import { Registry } from './events/interactionCreate/index.js';
|
||||||
import stats from './routes/stats.js';
|
import { statsRoute } from './routes/stats.js';
|
||||||
|
|
||||||
export type RouteReturnable = JSX.Element | Promise<JSX.Element>;
|
export type RouteReturnable = JSX.Element | Promise<JSX.Element>;
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ export interface RouteRegistry<HandlesInteraction extends boolean = false>
|
|||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
([stats] as RouteRegistry[]).forEach((registry) => {
|
([statsRoute] as RouteRegistry[]).forEach((registry) => {
|
||||||
if (!(isDev() ? registry.inDevelopment : registry.inProduction)) return;
|
if (!(isDev() ? registry.inDevelopment : registry.inProduction)) return;
|
||||||
|
|
||||||
app.get(registry.route, async (req, res) => {
|
app.get(registry.route, async (req, res) => {
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export type PossiblyPromise<Type> = Promise<Type> | Type;
|
||||||
Reference in new issue
Block a user