feat: moved tankopedia and tankaverages into a runtime cache

This commit is contained in:
TresAbhi committed 2023-07-05 02:09:36 +00:00
1 parent 95b105cb65
commit 00b721f34f
29 files changed
+268 -235

No files matched your search

+1 -24
View File
@@ -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'),
+4 -4
View File
@@ -12,14 +12,14 @@ const executionStart = new Date().getTime();
export const debugCommand: CommandRegistry = { 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,8 +37,8 @@ export const debugCommand: CommandRegistry = {
(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}`],
]), ]),
); );
}, },
+45 -29
View File
@@ -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';
@@ -25,7 +26,7 @@ export const SKILLED_CLANS: Record<SkilledClan, string> = {
export const eligibleCommand: CommandRegistry = { 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 const eligibleCommand: CommandRegistry = {
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})`]);
+1 -1
View File
@@ -36,7 +36,7 @@ export const evolutionCommand: CommandRegistry = {
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 } = (
+1 -1
View File
@@ -10,7 +10,7 @@ import {
export const helpCommand: CommandRegistry = { export const helpCommand: CommandRegistry = {
inProduction: true, inProduction: true,
inDevelopment: false, inDevelopment: true,
inPublic: true, inPublic: true,
command: new SlashCommandBuilder() command: new SlashCommandBuilder()
+1 -1
View File
@@ -18,7 +18,7 @@ const DEFAULT_THRESHOLD = 7;
export const inactiveCommand: CommandRegistry = { export const inactiveCommand: CommandRegistry = {
inProduction: true, inProduction: true,
inDevelopment: false, inDevelopment: true,
inPublic: true, inPublic: true,
command: new SlashCommandBuilder() command: new SlashCommandBuilder()
+6 -4
View File
@@ -43,7 +43,7 @@ const COMP_TANKS = [
export const ownedTanksCommand: CommandRegistry = { 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 const ownedTanksCommand: CommandRegistry = {
`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`,
); );
+1 -1
View File
@@ -3,7 +3,7 @@ import { CommandRegistry } from '../events/interactionCreate/index.js';
export const pingCommand: CommandRegistry<true> = { export const pingCommand: CommandRegistry<true> = {
inProduction: true, inProduction: true,
inDevelopment: false, inDevelopment: true,
inPublic: true, inPublic: true,
handlesInteraction: true, handlesInteraction: true,
+1 -1
View File
@@ -18,7 +18,7 @@ type SortBy = 'name' | 'count';
export const playerAchievementsCommand: CommandRegistry = { export const playerAchievementsCommand: CommandRegistry = {
inProduction: true, inProduction: true,
inDevelopment: false, inDevelopment: true,
inPublic: true, inPublic: true,
command: new SlashCommandBuilder() command: new SlashCommandBuilder()
+1 -1
View File
@@ -12,7 +12,7 @@ import { AccountInfo } from '../types/accountInfo.js';
export const playerInfoCommand: CommandRegistry = { export const playerInfoCommand: CommandRegistry = {
inProduction: true, inProduction: true,
inDevelopment: false, inDevelopment: true,
inPublic: true, inPublic: true,
command: new SlashCommandBuilder() command: new SlashCommandBuilder()
+1 -1
View File
@@ -11,7 +11,7 @@ import { ClanList } from '../types/clanList.js';
export const searchClansCommand: CommandRegistry = { export const searchClansCommand: CommandRegistry = {
inProduction: true, inProduction: true,
inDevelopment: false, inDevelopment: true,
inPublic: true, inPublic: true,
command: new SlashCommandBuilder() command: new SlashCommandBuilder()
+1 -1
View File
@@ -12,7 +12,7 @@ import { AccountList } from '../types/accountList.js';
export const searchPlayersCommand: CommandRegistry = { export const searchPlayersCommand: CommandRegistry = {
inProduction: true, inProduction: true,
inDevelopment: false, inDevelopment: true,
inPublic: true, inPublic: true,
command: new SlashCommandBuilder() command: new SlashCommandBuilder()
+1 -1
View File
@@ -8,7 +8,7 @@ import { CommandRegistry } from '../events/interactionCreate/index.js';
export const searchTanksCommand: CommandRegistry = { export const searchTanksCommand: CommandRegistry = {
inProduction: true, inProduction: true,
inDevelopment: false, inDevelopment: true,
inPublic: true, inPublic: true,
command: new SlashCommandBuilder() command: new SlashCommandBuilder()
+2 -1
View File
@@ -36,7 +36,8 @@ export const statsCommand: CommandRegistry = {
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, {
+3 -2
View File
@@ -19,7 +19,7 @@ import { AccountInfo } from '../types/accountInfo.js';
export const statsFullCommand: CommandRegistry = { 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 const statsFullCommand: CommandRegistry = {
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, {
+1 -1
View File
@@ -14,7 +14,7 @@ import { AccountInfo } from '../types/accountInfo.js';
export const todayCommand: CommandRegistry = { export const todayCommand: CommandRegistry = {
inProduction: true, inProduction: true,
inDevelopment: false, inDevelopment: true,
inPublic: true, inPublic: true,
command: new SlashCommandBuilder() command: new SlashCommandBuilder()
+1 -1
View File
@@ -14,7 +14,7 @@ import { PlayerClanData } from '../types/playerClanData.js';
export const verifyCommand: CommandRegistry = { export const verifyCommand: CommandRegistry = {
inProduction: true, inProduction: true,
inDevelopment: false, inDevelopment: true,
inPublic: true, inPublic: true,
command: new SlashCommandBuilder() command: new SlashCommandBuilder()
+2 -2
View File
@@ -2,7 +2,7 @@ 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)) {
@@ -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(
+2 -2
View File
@@ -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}`;
} }
+7 -8
View File
@@ -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,11 +16,13 @@ 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');
return (wrapperTankopedia as { data: Tankopedia }).data;
});
const entries = Object.entries(tankopedia); const entries = Object.entries(tankopedia);
export const TANK_IDS = entries.map(([, { tank_id }]) => tank_id as number); export const TANK_IDS = entries.map(([, { tank_id }]) => tank_id as number);
export const TANKS = entries.map(([, entry]) => entry as TankopediaEntry); export const TANKS = entries.map(([, entry]) => entry as TankopediaEntry);
+1 -1
View File
@@ -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]);
}, },
+19 -5
View File
@@ -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;
});
+10 -8
View File
@@ -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, 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>),
),
) )
: [], : [],
); );
+2 -2
View File
@@ -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;
+48 -40
View File
@@ -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,8 +48,8 @@ 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);
@@ -59,54 +60,61 @@ export default async function stats<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 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;
@@ -118,9 +126,9 @@ export default async function stats<Type extends StatType>(
supplementaryStats = { supplementaryStats = {
WN8: stats WN8: stats
? calculateWN8(tankAverages[tankId!].all, tankStats[tankId!]) ? calculateWN8((await tankAverages)[tankId!].all, tankStats[tankId!])
: undefined, : undefined,
tier: tankopedia[tankId!].tier, tier: (await tankopedia)[tankId!].tier,
}; };
} }
+48 -40
View File
@@ -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,61 @@ 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 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;
@@ -115,8 +123,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
View File
@@ -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>
+1 -1
View File
@@ -6,7 +6,7 @@ import { RouteRegistry } from '../server.js';
export const statsRoute: RouteRegistry = { 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;
+1
View File
@@ -0,0 +1 @@
export type PossiblyPromise<Type> = Promise<Type> | Type;