feat: some period translations

This commit is contained in:
TrèsAbhi committed 2024-03-06 15:34:10 -06:00
1 parent 267f23cad8
commit adad668157
24 files changed
+388 -295

No files matched your search

+2 -2
View File
@@ -35,7 +35,7 @@ import resolvePlayerFromCommand, {
import calculateWN8 from '../core/statistics/calculateWN8'; import calculateWN8 from '../core/statistics/calculateWN8';
import { StatFilters, filterStats } from '../core/statistics/filterStats'; import { StatFilters, filterStats } from '../core/statistics/filterStats';
import getWN8Percentile from '../core/statistics/getWN8Percentile'; import getWN8Percentile from '../core/statistics/getWN8Percentile';
import { CommandRegistryPromisable } from '../events/interactionCreate'; import { CommandRegistry } from '../events/interactionCreate';
const ROWS_PER_PAGE = 8; const ROWS_PER_PAGE = 8;
const MAX_PAGES = 9; const MAX_PAGES = 9;
@@ -290,7 +290,7 @@ export async function renderBreakdown(
} }
} }
export const breakdownCommand = new Promise<CommandRegistryPromisable>( export const breakdownCommand = new Promise<CommandRegistry>(
async (resolve) => { async (resolve) => {
resolve({ resolve({
inProduction: true, inProduction: true,
+4 -2
View File
@@ -6,7 +6,8 @@ import { CommandRegistry } from '../events/interactionCreate';
const executionStart = new Date().getTime(); const executionStart = new Date().getTime();
export const debugCommand: CommandRegistry = { export const debugCommand = new Promise<CommandRegistry>((resolve) => {
resolve({
inProduction: true, inProduction: true,
inPublic: true, inPublic: true,
@@ -35,4 +36,5 @@ export const debugCommand: CommandRegistry = {
.map(([key, value]) => `- ${key}: ${value}`) .map(([key, value]) => `- ${key}: ${value}`)
.join('\n')}`; .join('\n')}`;
}, },
}; });
});
+185 -178
View File
@@ -1,198 +1,205 @@
import { SlashCommandBuilder } from 'discord.js'; // _____ _____ _ _ _ _ _____ ____ __ __ ______ ____ _____ _ __
import { getAccountInfo } from '../core/blitz/getAccountInfo'; // |_ _| / ____| | | | /\ | | | | / ____/ __ \| \/ | ____| | _ \ /\ / ____| |/ /
import getPeriodNow from '../core/blitzkrieg/getPeriodNow'; // | | | (___ | |__| | / \ | | | | | | | | | | \ / | |__ | |_) | / \ | | | ' /
import getTimeDaysAgo from '../core/blitzkrieg/getTimeDaysAgo'; // | | \___ \| __ | / /\ \ | | | | | | | | | | |\/| | __| | _ < / /\ \| | | <
import { tankDefinitions } from '../core/blitzkrieg/tankDefinitions'; // _| |_ ____) | | | |/ ____ \| |____| |____ | |___| |__| | | | | |____ | |_) / ____ \ |____| . \
import getStatsInPeriod from '../core/blitzstars/getStatsInPeriod'; // |_____| |_____/|_| |_/_/ \_\______|______| \_____\____/|_| |_|______| |____/_/ \_\_____|_|\_\
import { tankAverages } from '../core/blitzstars/tankAverages';
import addUsernameChoices from '../core/discord/addUsernameChoices';
import embedNegative from '../core/discord/embedNegative';
import embedPositive from '../core/discord/embedPositive';
import markdownTable, { TableInput } from '../core/discord/markdownTable';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
import calculateWN8 from '../core/statistics/calculateWN8';
import sumStats from '../core/statistics/sumStats';
import { CommandRegistry } from '../events/interactionCreate';
import { PossiblyPromise } from '../types/possiblyPromise';
export type SkilledClan = 'SKLLD' | 'SMRI'; // import { SlashCommandBuilder } from 'discord.js';
// import { getAccountInfo } from '../core/blitz/getAccountInfo';
// import getPeriodNow from '../core/blitzkrieg/getPeriodNow';
// import getTimeDaysAgo from '../core/blitzkrieg/getTimeDaysAgo';
// import { tankDefinitions } from '../core/blitzkrieg/tankDefinitions';
// import getStatsInPeriod from '../core/blitzstars/getStatsInPeriod';
// import { tankAverages } from '../core/blitzstars/tankAverages';
// import addUsernameChoices from '../core/discord/addUsernameChoices';
// import embedNegative from '../core/discord/embedNegative';
// import embedPositive from '../core/discord/embedPositive';
// import markdownTable, { TableInput } from '../core/discord/markdownTable';
// import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
// import calculateWN8 from '../core/statistics/calculateWN8';
// import sumStats from '../core/statistics/sumStats';
// import { CommandRegistry } from '../events/interactionCreate';
// import { PossiblyPromise } from '../types/possiblyPromise';
export const SKILLED_CLANS: Record<SkilledClan, string> = { // export type SkilledClan = 'SKLLD' | 'SMRI';
SKLLD: 'Skilled',
SMRI: 'Samurai',
};
export const eligibleCommand: CommandRegistry = { // export const SKILLED_CLANS: Record<SkilledClan, string> = {
inProduction: true, // SKLLD: 'Skilled',
inPublic: false, // SMRI: 'Samurai',
// };
command: new SlashCommandBuilder() // export const eligibleCommand: CommandRegistry = {
.setName('eligible') // inProduction: true,
.setDescription('Checks eligibility for any Skilled clan') // inPublic: false,
.addStringOption((option) =>
option
.setName('clan')
.setDescription('The clan to check')
.setChoices(
{
name: SKILLED_CLANS.SKLLD,
value: 'SKLLD' satisfies SkilledClan,
},
{ name: SKILLED_CLANS.SMRI, value: 'SMRI' satisfies SkilledClan },
)
.setRequired(true),
)
.addStringOption(addUsernameChoices),
async handler(interaction) { // command: new SlashCommandBuilder()
const clan = interaction.options.getString('clan') as SkilledClan; // .setName('eligible')
const { id, region: region } = await resolvePlayerFromCommand(interaction); // .setDescription('Checks eligibility for any Skilled clan')
const accountInfo = await getAccountInfo(region, id); // .addStringOption((option) =>
const problems: TableInput = []; // option
const title = `${accountInfo.nickname}'s eligibility for ${SKILLED_CLANS[clan]}`; // .setName('clan')
let isEligible = false; // .setDescription('The clan to check')
let body = ''; // .setChoices(
// {
// name: SKILLED_CLANS.SKLLD,
// value: 'SKLLD' satisfies SkilledClan,
// },
// { name: SKILLED_CLANS.SMRI, value: 'SMRI' satisfies SkilledClan },
// )
// .setRequired(true),
// )
// .addStringOption(addUsernameChoices),
if (clan === 'SKLLD') { // async handler(interaction) {
const { diff: diffed } = await getStatsInPeriod( // const clan = interaction.options.getString('clan') as SkilledClan;
region, // const { id, region: region } = await resolvePlayerFromCommand(interaction);
id, // const accountInfo = await getAccountInfo(region, id);
getTimeDaysAgo(region, 30), // const problems: TableInput = [];
getPeriodNow(), // const title = `${accountInfo.nickname}'s eligibility for ${SKILLED_CLANS[clan]}`;
); // let isEligible = false;
const entries = Object.entries(diffed); // let body = '';
const stats = sumStats(entries.map(([, stats]) => stats));
const averageTier =
(await entries.reduce<PossiblyPromise<number>>(
async (accumulator, [tankIdString, stats]) => {
const tankId = parseInt(tankIdString);
const tankDefinition = (await tankDefinitions)[tankId];
if (!tankDefinition) return accumulator; // if (clan === 'SKLLD') {
// const { diff: diffed } = await getStatsInPeriod(
// region,
// id,
// getTimeDaysAgo(region, 30),
// getPeriodNow(),
// );
// const entries = Object.entries(diffed);
// const stats = sumStats(entries.map(([, stats]) => stats));
// const averageTier =
// (await entries.reduce<PossiblyPromise<number>>(
// async (accumulator, [tankIdString, stats]) => {
// const tankId = parseInt(tankIdString);
// const tankDefinition = (await tankDefinitions)[tankId];
const tankTier = tankDefinition.tier; // if (!tankDefinition) return accumulator;
return (await accumulator) + tankTier * stats.battles; // const tankTier = tankDefinition.tier;
},
0,
)) /
(await entries.reduce<PossiblyPromise<number>>(
async (accumulator, [tankIdString, stats]) => {
const tankId = parseInt(tankIdString);
const tankDefinition = (await tankDefinitions)[tankId];
if (!tankDefinition) return accumulator; // return (await accumulator) + tankTier * stats.battles;
// },
// 0,
// )) /
// (await entries.reduce<PossiblyPromise<number>>(
// async (accumulator, [tankIdString, stats]) => {
// const tankId = parseInt(tankIdString);
// const tankDefinition = (await tankDefinitions)[tankId];
return stats.battles; // if (!tankDefinition) return accumulator;
},
0,
));
const WN8 =
(await entries.reduce<PossiblyPromise<number>>(
async (accumulator, [tankIdString, stats]) => {
const tankId = parseInt(tankIdString);
// edge case where new tanks don't have averages // return stats.battles;
if ((await tankAverages)[tankId]) { // },
const tankWN8 = calculateWN8( // 0,
(await tankAverages)[tankId].all, // ));
stats, // const WN8 =
); // (await entries.reduce<PossiblyPromise<number>>(
// async (accumulator, [tankIdString, stats]) => {
// const tankId = parseInt(tankIdString);
if (isNaN(tankWN8)) return accumulator; // // edge case where new tanks don't have averages
return (await accumulator) + tankWN8 * stats.battles; // if ((await tankAverages)[tankId]) {
} else return accumulator; // const tankWN8 = calculateWN8(
}, // (await tankAverages)[tankId].all,
0, // stats,
)) / stats.battles; // );
const tier10Damage =
(await entries.reduce<PossiblyPromise<number>>(
async (accumulator, [tankIdString, stats]) => {
const tankId = parseInt(tankIdString);
if ((await tankDefinitions)[tankId]?.tier === 10) { // if (isNaN(tankWN8)) return accumulator;
const tankStats = diffed[tankId]; // return (await accumulator) + tankWN8 * stats.battles;
return (await accumulator) + tankStats.damage_dealt; // } else return accumulator;
} else return accumulator; // },
}, // 0,
0, // )) / stats.battles;
)) / // const tier10Damage =
(await entries.reduce<PossiblyPromise<number>>( // (await entries.reduce<PossiblyPromise<number>>(
async (accumulator, [tankIdString, stats]) => { // async (accumulator, [tankIdString, stats]) => {
const tankId = parseInt(tankIdString); // const tankId = parseInt(tankIdString);
if ((await tankDefinitions)[tankId]?.tier === 10) { // if ((await tankDefinitions)[tankId]?.tier === 10) {
const tankStats = diffed[tankId]; // const tankStats = diffed[tankId];
return (await accumulator) + tankStats.battles; // return (await accumulator) + tankStats.damage_dealt;
} else return accumulator; // } else return accumulator;
}, // },
0, // 0,
)); // )) /
// (await entries.reduce<PossiblyPromise<number>>(
// async (accumulator, [tankIdString, stats]) => {
// const tankId = parseInt(tankIdString);
if (stats.battles < 126) { // if ((await tankDefinitions)[tankId]?.tier === 10) {
problems.push(['Battles', `${stats.battles} (${stats.battles - 126})`]); // const tankStats = diffed[tankId];
} // return (await accumulator) + tankStats.battles;
if (averageTier < 8) { // } else return accumulator;
problems.push([ // },
'Average tier', // 0,
`${averageTier.toFixed(2)} (${(averageTier - 8).toFixed(2)})`, // ));
]);
}
if (stats.wins / stats.battles < 0.6) {
problems.push([
'Winrate',
`${(100 * (stats.wins / stats.battles)).toFixed(2)}% (${(
100 *
(stats.wins / stats.battles - 0.6)
).toFixed(2)}%)`,
]);
}
if (WN8 < 2450) {
problems.push([
'WN8',
`${WN8.toFixed(0)} (${(WN8 - 2450).toFixed(0)})`,
]);
}
if (tier10Damage < 1250) {
problems.push([
'Tier 10 damage',
`${tier10Damage.toFixed(0)} (${tier10Damage - 1250})`,
]);
}
isEligible = problems.length === 0; // if (stats.battles < 126) {
body = isEligible // problems.push(['Battles', `${stats.battles} (${stats.battles - 126})`]);
? 'Player is fully eligible.' // }
: `Player is not eligible. Shortcomings in 30-day statistics:\n\n${markdownTable( // if (averageTier < 8) {
problems, // problems.push([
)}`; // 'Average tier',
} else if (clan === 'SMRI') { // `${averageTier.toFixed(2)} (${(averageTier - 8).toFixed(2)})`,
const stats = accountInfo.statistics.all; // ]);
// }
// if (stats.wins / stats.battles < 0.6) {
// problems.push([
// 'Winrate',
// `${(100 * (stats.wins / stats.battles)).toFixed(2)}% (${(
// 100 *
// (stats.wins / stats.battles - 0.6)
// ).toFixed(2)}%)`,
// ]);
// }
// if (WN8 < 2450) {
// problems.push([
// 'WN8',
// `${WN8.toFixed(0)} (${(WN8 - 2450).toFixed(0)})`,
// ]);
// }
// if (tier10Damage < 1250) {
// problems.push([
// 'Tier 10 damage',
// `${tier10Damage.toFixed(0)} (${tier10Damage - 1250})`,
// ]);
// }
if (stats.battles < 1500) { // isEligible = problems.length === 0;
problems.push([ // body = isEligible
'Battles', // ? 'Player is fully eligible.'
`${stats.battles} (${stats.battles - 1500})`, // : `Player is not eligible. Shortcomings in 30-day statistics:\n\n${markdownTable(
]); // problems,
} // )}`;
if (stats.wins / stats.battles <= 0.53) { // } else if (clan === 'SMRI') {
problems.push([ // const stats = accountInfo.statistics.all;
'Winrate',
`${(100 * (stats.wins / stats.battles)).toFixed(2)}% (${(
100 *
(stats.wins / stats.battles - 0.53)
).toFixed(2)}%)`,
]);
}
isEligible = problems.length === 0; // if (stats.battles < 1500) {
body = isEligible // problems.push([
? 'Player is fully eligible.' // 'Battles',
: `Player is not eligible. Shortcomings in career statistics:\n\n${markdownTable( // `${stats.battles} (${stats.battles - 1500})`,
problems, // ]);
)}`; // }
} // if (stats.wins / stats.battles <= 0.53) {
// problems.push([
// 'Winrate',
// `${(100 * (stats.wins / stats.battles)).toFixed(2)}% (${(
// 100 *
// (stats.wins / stats.battles - 0.53)
// ).toFixed(2)}%)`,
// ]);
// }
return (isEligible ? embedPositive : embedNegative)(title, body); // isEligible = problems.length === 0;
}, // body = isEligible
}; // ? 'Player is fully eligible.'
// : `Player is not eligible. Shortcomings in career statistics:\n\n${markdownTable(
// problems,
// )}`;
// }
// return (isEligible ? embedPositive : embedNegative)(title, body);
// },
// };
+24 -36
View File
@@ -1,7 +1,4 @@
import { import { SlashCommandSubcommandGroupBuilder } from 'discord.js';
SlashCommandBuilder,
SlashCommandSubcommandGroupBuilder,
} from 'discord.js';
import CommandWrapper from '../components/CommandWrapper'; import CommandWrapper from '../components/CommandWrapper';
import * as Graph from '../components/Graph'; import * as Graph from '../components/Graph';
import NoData, { NoDataType } from '../components/NoData'; import NoData, { NoDataType } from '../components/NoData';
@@ -20,6 +17,7 @@ import autocompleteTanks from '../core/discord/autocompleteTanks';
import autocompleteUsername from '../core/discord/autocompleteUsername'; import autocompleteUsername from '../core/discord/autocompleteUsername';
import buttonPrimary from '../core/discord/buttonPrimary'; import buttonPrimary from '../core/discord/buttonPrimary';
import commandToURL from '../core/discord/commandToURL'; import commandToURL from '../core/discord/commandToURL';
import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
import { getCustomPeriodParams } from '../core/discord/getCustomPeriodParams'; import { getCustomPeriodParams } from '../core/discord/getCustomPeriodParams';
import resolvePeriodFromButton from '../core/discord/resolvePeriodFromButton'; import resolvePeriodFromButton from '../core/discord/resolvePeriodFromButton';
import resolvePeriodFromCommand, { import resolvePeriodFromCommand, {
@@ -29,7 +27,7 @@ import resolvePlayerFromButton from '../core/discord/resolvePlayerFromButton';
import resolvePlayerFromCommand, { import resolvePlayerFromCommand, {
ResolvedPlayer, ResolvedPlayer,
} from '../core/discord/resolvePlayerFromCommand'; } from '../core/discord/resolvePlayerFromCommand';
import { CommandRegistryPromisable } from '../events/interactionCreate'; import { CommandRegistry } from '../events/interactionCreate';
type EvolutionStatType = 'player' | 'tank'; type EvolutionStatType = 'player' | 'tank';
@@ -107,40 +105,32 @@ async function render(
); );
} }
export const evolutionCommand = new Promise<CommandRegistryPromisable>( export const evolutionCommand = new Promise<CommandRegistry>(
async (resolve) => { async (resolve) => {
let playerGroup: SlashCommandSubcommandGroupBuilder;
let tankGroup: SlashCommandSubcommandGroupBuilder;
const command = new SlashCommandBuilder()
.setName('evolution')
.setDescription('Evolution of statistics')
.addSubcommandGroup((option) => {
playerGroup = option
.setName('player')
.setDescription('Player statistics');
return playerGroup;
})
.addSubcommandGroup((option) => {
tankGroup = option.setName('tank').setDescription('Tank statistics');
return tankGroup;
});
addPeriodSubCommands(playerGroup!, (option) =>
option.addStringOption(addUsernameChoices),
);
addPeriodSubCommands(tankGroup!, (option) =>
option
.addStringOption(addTankChoices)
.addStringOption(addUsernameChoices),
);
resolve({ resolve({
inProduction: true, inProduction: true,
inPublic: true, inPublic: true,
command, command: createLocalizedCommand('evolution', [
{
group: 'player',
modify(option: SlashCommandSubcommandGroupBuilder) {
addPeriodSubCommands(option, (option) =>
option.addStringOption(addUsernameChoices),
);
},
},
{
group: 'tank',
modify(option: SlashCommandSubcommandGroupBuilder) {
addPeriodSubCommands(option, (option) =>
option
.addStringOption(addTankChoices)
.addStringOption(addUsernameChoices),
);
},
},
]),
async handler(interaction) { async handler(interaction) {
const commandGroup = const commandGroup =
@@ -156,8 +146,6 @@ export const evolutionCommand = new Promise<CommandRegistryPromisable>(
tankId, tankId,
}); });
getBlitzStarsLinkButton;
return [ return [
await render(player, period, commandGroup, tankId), await render(player, period, commandGroup, tankId),
buttonPrimary(path, 'Refresh'), buttonPrimary(path, 'Refresh'),
+2 -2
View File
@@ -29,7 +29,7 @@ import resolvePlayerFromCommand, {
} from '../core/discord/resolvePlayerFromCommand'; } from '../core/discord/resolvePlayerFromCommand';
import { StatFilters, filterStats } from '../core/statistics/filterStats'; import { StatFilters, filterStats } from '../core/statistics/filterStats';
import { getTierWeights } from '../core/statistics/getTierWeights'; import { getTierWeights } from '../core/statistics/getTierWeights';
import { CommandRegistryPromisable } from '../events/interactionCreate'; import { CommandRegistry } from '../events/interactionCreate';
async function render( async function render(
{ region, id }: ResolvedPlayer, { region, id }: ResolvedPlayer,
@@ -64,7 +64,7 @@ async function render(
); );
} }
export const fullStatsCommand = new Promise<CommandRegistryPromisable>( export const fullStatsCommand = new Promise<CommandRegistry>(
async (resolve) => { async (resolve) => {
const command = await addPeriodicFilterOptions( const command = await addPeriodicFilterOptions(
new SlashCommandBuilder() new SlashCommandBuilder()
+6 -3
View File
@@ -12,7 +12,8 @@ import { CommandRegistry } from '../events/interactionCreate';
const DEFAULT_THRESHOLD = 7; const DEFAULT_THRESHOLD = 7;
export const inactiveCommand: CommandRegistry = { export const inactiveCommand = new Promise<CommandRegistry>((resolve) => {
resolve({
inProduction: true, inProduction: true,
inPublic: true, inPublic: true,
@@ -47,7 +48,8 @@ export const inactiveCommand: CommandRegistry = {
.filter(([, inactiveDays]) => inactiveDays >= threshold) .filter(([, inactiveDays]) => inactiveDays >= threshold)
.sort((a, b) => b[1] - a[1]) .sort((a, b) => b[1] - a[1])
.map( .map(
([name, days]) => [name, `${days.toFixed(0)} days`] as [string, string], ([name, days]) =>
[name, `${days.toFixed(0)} days`] as [string, string],
); );
const hasInactiveMembers = inactive.length > 0; const hasInactiveMembers = inactive.length > 0;
@@ -66,4 +68,5 @@ export const inactiveCommand: CommandRegistry = {
}, },
autocomplete: autocompleteClan, autocomplete: autocompleteClan,
}; });
});
+7 -3
View File
@@ -16,14 +16,17 @@ import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
import { CommandRegistry } from '../events/interactionCreate'; import { CommandRegistry } from '../events/interactionCreate';
import { theme } from '../stitches.config'; import { theme } from '../stitches.config';
export const verifyCommand: CommandRegistry = { export const verifyCommand = new Promise<CommandRegistry>((resolve) => {
resolve({
inProduction: true, inProduction: true,
inPublic: true, inPublic: true,
command: new SlashCommandBuilder() command: new SlashCommandBuilder()
.setName('link') .setName('link')
.setDescription('Links your Blitz and Discord account') .setDescription('Links your Blitz and Discord account')
.addStringOption((option) => addUsernameChoices(option).setRequired(true)), .addStringOption((option) =>
addUsernameChoices(option).setRequired(true),
),
async handler(interaction) { async handler(interaction) {
const { id, region } = await resolvePlayerFromCommand(interaction); const { id, region } = await resolvePlayerFromCommand(interaction);
@@ -206,4 +209,5 @@ export const verifyCommand: CommandRegistry = {
}, },
autocomplete: autocompleteUsername, autocomplete: autocompleteUsername,
}; });
});
+7 -3
View File
@@ -21,7 +21,8 @@ import autocompleteUsername from '../core/discord/autocompleteUsername';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand'; import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
import { CommandRegistry } from '../events/interactionCreate'; import { CommandRegistry } from '../events/interactionCreate';
export const ownedTanksCommand: CommandRegistry = { export const ownedTanksCommand = new Promise<CommandRegistry>((resolve) => {
resolve({
inProduction: true, inProduction: true,
inPublic: true, inPublic: true,
@@ -72,7 +73,9 @@ export const ownedTanksCommand: CommandRegistry = {
description={`Tier ${TIER_ROMAN_NUMERALS[tier as Tier]} tanks`} description={`Tier ${TIER_ROMAN_NUMERALS[tier as Tier]} tanks`}
/> />
{filteredTanks.length === 0 && <NoData type={NoDataType.TanksFound} />} {filteredTanks.length === 0 && (
<NoData type={NoDataType.TanksFound} />
)}
{filteredTanks.length > 0 && {filteredTanks.length > 0 &&
(await Promise.all( (await Promise.all(
@@ -125,4 +128,5 @@ export const ownedTanksCommand: CommandRegistry = {
}, },
autocomplete: autocompleteUsername, autocomplete: autocompleteUsername,
}; });
});
+4 -2
View File
@@ -1,7 +1,8 @@
import { SlashCommandBuilder } from 'discord.js'; import { SlashCommandBuilder } from 'discord.js';
import { CommandRegistry } from '../events/interactionCreate'; import { CommandRegistry } from '../events/interactionCreate';
export const permissionsCommand: CommandRegistry = { export const permissionsCommand = new Promise<CommandRegistry>((resolve) => {
resolve({
inProduction: true, inProduction: true,
inPublic: true, inPublic: true,
@@ -39,4 +40,5 @@ export const permissionsCommand: CommandRegistry = {
return `# Permissions\n${body}`; return `# Permissions\n${body}`;
}, },
}; });
});
+4 -2
View File
@@ -1,7 +1,8 @@
import { SlashCommandBuilder } from 'discord.js'; import { SlashCommandBuilder } from 'discord.js';
import { CommandRegistry } from '../events/interactionCreate'; import { CommandRegistry } from '../events/interactionCreate';
export const pingCommand: CommandRegistry = { export const pingCommand = new Promise<CommandRegistry>((resolve) => {
resolve({
inProduction: true, inProduction: true,
inPublic: true, inPublic: true,
handlesInteraction: true, handlesInteraction: true,
@@ -29,4 +30,5 @@ export const pingCommand: CommandRegistry = {
const executionTime = Date.now() - executionStart; const executionTime = Date.now() - executionStart;
interaction.editReply(`Pong 🏓 - ${executionTime}ms`); interaction.editReply(`Pong 🏓 - ${executionTime}ms`);
}, },
}; });
});
+8 -3
View File
@@ -12,7 +12,9 @@ import { CommandRegistry } from '../events/interactionCreate';
type SortBy = 'name' | 'count'; type SortBy = 'name' | 'count';
export const playerAchievementsCommand: CommandRegistry = { export const playerAchievementsCommand = new Promise<CommandRegistry>(
(resolve) => {
resolve({
inProduction: true, inProduction: true,
inPublic: true, inPublic: true,
@@ -31,7 +33,8 @@ export const playerAchievementsCommand: CommandRegistry = {
), ),
async handler(interaction) { async handler(interaction) {
const sortBy = (interaction.options.getString('sort') ?? 'name') as SortBy; const sortBy = (interaction.options.getString('sort') ??
'name') as SortBy;
const account = await resolvePlayerFromCommand(interaction); const account = await resolvePlayerFromCommand(interaction);
const { id, region: region } = account; const { id, region: region } = account;
const accountInfo = await getAccountInfo(region, id); const accountInfo = await getAccountInfo(region, id);
@@ -65,4 +68,6 @@ export const playerAchievementsCommand: CommandRegistry = {
}, },
autocomplete: autocompleteUsername, autocomplete: autocompleteUsername,
}; });
},
);
+4 -2
View File
@@ -8,7 +8,8 @@ import markdownTable from '../core/discord/markdownTable';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand'; import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
import { CommandRegistry } from '../events/interactionCreate'; import { CommandRegistry } from '../events/interactionCreate';
export const playerInfoCommand: CommandRegistry = { export const playerInfoCommand = new Promise<CommandRegistry>((resolve) => {
resolve({
inProduction: true, inProduction: true,
inPublic: true, inPublic: true,
@@ -51,4 +52,5 @@ export const playerInfoCommand: CommandRegistry = {
}, },
autocomplete: autocompleteUsername, autocomplete: autocompleteUsername,
}; });
});
+5 -10
View File
@@ -27,7 +27,7 @@ import addUsernameChoices from '../core/discord/addUsernameChoices';
import autocompleteUsername from '../core/discord/autocompleteUsername'; import autocompleteUsername from '../core/discord/autocompleteUsername';
import embedNegative from '../core/discord/embedNegative'; import embedNegative from '../core/discord/embedNegative';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand'; import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
import { CommandRegistryPromisable } from '../events/interactionCreate'; import { CommandRegistry } from '../events/interactionCreate';
import { UserError } from '../hooks/userError'; import { UserError } from '../hooks/userError';
export interface RatingsPlayer { export interface RatingsPlayer {
@@ -122,8 +122,7 @@ const noOngoingSeason = embedNegative(
"Wargaming didn't provide any data for this season.", "Wargaming didn't provide any data for this season.",
); );
export const ratingsCommand = new Promise<CommandRegistryPromisable>( export const ratingsCommand = new Promise<CommandRegistry>(async (resolve) => {
async (resolve) => {
const latestArchivedSeasonNumber = await getArchivedLatestSeasonNumber(); const latestArchivedSeasonNumber = await getArchivedLatestSeasonNumber();
const onGoingSeason = await isOnGoingRatingsSeason(); const onGoingSeason = await isOnGoingRatingsSeason();
const seasonNumbers = range( const seasonNumbers = range(
@@ -232,9 +231,7 @@ export const ratingsCommand = new Promise<CommandRegistryPromisable>(
let regionRatingsInfo: RatingsInfo; let regionRatingsInfo: RatingsInfo;
if (subcommand === 'league') { if (subcommand === 'league') {
const leagueIndex = parseInt( const leagueIndex = parseInt(interaction.options.getString('league')!);
interaction.options.getString('league')!,
);
const region = interaction.options.getString('region') as Region; const region = interaction.options.getString('region') as Region;
regionRatingsInfo = await (isCurrentSeason regionRatingsInfo = await (isCurrentSeason
? getRatingsInfo(region) ? getRatingsInfo(region)
@@ -283,8 +280,7 @@ export const ratingsCommand = new Promise<CommandRegistryPromisable>(
position: firstPlayerIndex + index + 1, position: firstPlayerIndex + index + 1,
clan: clanData[player.id]?.clan?.tag, clan: clanData[player.id]?.clan?.tag,
nickname: nickname:
clanData[index]?.account_name ?? clanData[index]?.account_name ?? `Deleted ${player.id}`,
`Deleted ${player.id}`,
}) satisfies SimplifiedPlayer, }) satisfies SimplifiedPlayer,
); );
}, },
@@ -451,5 +447,4 @@ export const ratingsCommand = new Promise<CommandRegistryPromisable>(
autocomplete: autocompleteUsername, autocomplete: autocompleteUsername,
}); });
}, });
);
+7 -3
View File
@@ -9,7 +9,8 @@ import embedInfo from '../core/discord/embedInfo';
import { CommandRegistry } from '../events/interactionCreate'; import { CommandRegistry } from '../events/interactionCreate';
import { ClanList } from '../types/clanList'; import { ClanList } from '../types/clanList';
export const searchClansCommand: CommandRegistry = { export const searchClansCommand = new Promise<CommandRegistry>((resolve) => {
resolve({
inProduction: true, inProduction: true,
inPublic: true, inPublic: true,
@@ -17,7 +18,9 @@ export const searchClansCommand: CommandRegistry = {
.setName('search-clans') .setName('search-clans')
.setDescription('Search clans') .setDescription('Search clans')
.addStringOption(addRegionChoices) .addStringOption(addRegionChoices)
.addStringOption((option) => addClanChoices(option).setAutocomplete(false)) .addStringOption((option) =>
addClanChoices(option).setAutocomplete(false),
)
.addIntegerOption((option) => .addIntegerOption((option) =>
option option
.setName('limit') .setName('limit')
@@ -43,4 +46,5 @@ export const searchClansCommand: CommandRegistry = {
.join('\n')}\n\`\`\``, .join('\n')}\n\`\`\``,
); );
}, },
}; });
});
+4 -2
View File
@@ -12,7 +12,8 @@ import addUsernameChoices from '../core/discord/addUsernameChoices';
import embedInfo from '../core/discord/embedInfo'; import embedInfo from '../core/discord/embedInfo';
import { CommandRegistry } from '../events/interactionCreate'; import { CommandRegistry } from '../events/interactionCreate';
export const searchPlayersCommand: CommandRegistry = { export const searchPlayersCommand = new Promise<CommandRegistry>((resolve) => {
resolve({
inProduction: true, inProduction: true,
inPublic: true, inPublic: true,
@@ -53,4 +54,5 @@ export const searchPlayersCommand: CommandRegistry = {
}\`\`\``, }\`\`\``,
); );
}, },
}; });
});
+7 -3
View File
@@ -6,14 +6,17 @@ import addTankChoices from '../core/discord/addTankChoices';
import embedInfo from '../core/discord/embedInfo'; import embedInfo from '../core/discord/embedInfo';
import { CommandRegistry } from '../events/interactionCreate'; import { CommandRegistry } from '../events/interactionCreate';
export const searchTanksCommand: CommandRegistry = { export const searchTanksCommand = new Promise<CommandRegistry>((resolve) => {
resolve({
inProduction: true, inProduction: true,
inPublic: true, inPublic: true,
command: new SlashCommandBuilder() command: new SlashCommandBuilder()
.setName('search-tanks') .setName('search-tanks')
.setDescription('Search tanks') .setDescription('Search tanks')
.addStringOption((option) => addTankChoices(option).setAutocomplete(false)) .addStringOption((option) =>
addTankChoices(option).setAutocomplete(false),
)
.addIntegerOption((option) => .addIntegerOption((option) =>
option option
.setName('limit') .setName('limit')
@@ -37,4 +40,5 @@ export const searchTanksCommand: CommandRegistry = {
: `\`\`\`\n${results.join('\n')}\n\`\`\``, : `\`\`\`\n${results.join('\n')}\n\`\`\``,
); );
}, },
}; });
});
+3 -5
View File
@@ -27,7 +27,7 @@ import resolvePlayerFromCommand, {
ResolvedPlayer, ResolvedPlayer,
} from '../core/discord/resolvePlayerFromCommand'; } from '../core/discord/resolvePlayerFromCommand';
import { StatFilters, filterStats } from '../core/statistics/filterStats'; import { StatFilters, filterStats } from '../core/statistics/filterStats';
import { CommandRegistryPromisable } from '../events/interactionCreate'; import { CommandRegistry } from '../events/interactionCreate';
async function render( async function render(
{ region, id }: ResolvedPlayer, { region, id }: ResolvedPlayer,
@@ -57,8 +57,7 @@ async function render(
); );
} }
export const statsCommand = new Promise<CommandRegistryPromisable>( export const statsCommand = new Promise<CommandRegistry>(async (resolve) => {
async (resolve) => {
const command = await addPeriodicFilterOptions( const command = await addPeriodicFilterOptions(
new SlashCommandBuilder() new SlashCommandBuilder()
.setName('stats') .setName('stats')
@@ -102,5 +101,4 @@ export const statsCommand = new Promise<CommandRegistryPromisable>(
return await render(player, period, filters); return await render(player, period, filters);
}, },
}); });
}, });
);
+3 -5
View File
@@ -14,11 +14,10 @@ import resolvePeriodFromButton from '../core/discord/resolvePeriodFromButton';
import resolvePeriodFromCommand from '../core/discord/resolvePeriodFromCommand'; import resolvePeriodFromCommand from '../core/discord/resolvePeriodFromCommand';
import resolvePlayerFromButton from '../core/discord/resolvePlayerFromButton'; import resolvePlayerFromButton from '../core/discord/resolvePlayerFromButton';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand'; import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
import { CommandRegistryPromisable } from '../events/interactionCreate'; import { CommandRegistry } from '../events/interactionCreate';
import { renderBreakdown } from './breakdown'; import { renderBreakdown } from './breakdown';
export const todayCommand = new Promise<CommandRegistryPromisable>( export const todayCommand = new Promise<CommandRegistry>(async (resolve) => {
async (resolve) => {
const awaitedEncyclopediaInfo = await encyclopediaInfo; const awaitedEncyclopediaInfo = await encyclopediaInfo;
const command = addFilterOptions( const command = addFilterOptions(
new SlashCommandBuilder() new SlashCommandBuilder()
@@ -67,5 +66,4 @@ export const todayCommand = new Promise<CommandRegistryPromisable>(
return await renderBreakdown(player, period, filters); return await renderBreakdown(player, period, filters);
}, },
}); });
}, });
);
+49 -11
View File
@@ -1,14 +1,27 @@
import { Locale, SlashCommandBuilder } from 'discord.js'; import {
Locale,
SlashCommandBuilder,
SlashCommandSubcommandBuilder,
SlashCommandSubcommandGroupBuilder,
} from 'discord.js';
import { translator } from '../localization/translator'; import { translator } from '../localization/translator';
import { localizationObject } from './localizationObject'; import { localizationObject } from './localizationObject';
interface CreateLocalizedCommandSubcommand { interface CreateLocalizedCommandSubcommand {
subcommand: string; subcommand: string;
modify?: (option: SlashCommandSubcommandBuilder) => void;
}
interface CreateLocalizedCommandSubcommandGroup {
group: string;
modify?: (option: SlashCommandSubcommandGroupBuilder) => void;
} }
export function createLocalizedCommand( export function createLocalizedCommand(
command: string, command: string,
subcommands?: CreateLocalizedCommandSubcommand[], sub?:
| CreateLocalizedCommandSubcommand[]
| CreateLocalizedCommandSubcommandGroup[],
) { ) {
const { translate } = translator(Locale.EnglishUS); const { translate } = translator(Locale.EnglishUS);
const slashCommand = new SlashCommandBuilder() const slashCommand = new SlashCommandBuilder()
@@ -19,26 +32,51 @@ export function createLocalizedCommand(
localizationObject(`bot.commands.${command}.description`), localizationObject(`bot.commands.${command}.description`),
); );
if (subcommands) { if (sub) {
subcommands.map(({ subcommand }) => { sub.map((subItem) => {
if ('subcommand' in subItem) {
const subcommandNameLocalizations = localizationObject( const subcommandNameLocalizations = localizationObject(
`bot.commands.${command}.subcommands.${subcommand}`, `bot.commands.${command}.subcommands.${subItem.subcommand}`,
); );
const subcommandDescriptionLocalizations = localizationObject( const subcommandDescriptionLocalizations = localizationObject(
`bot.commands.${command}.subcommands.${subcommand}.description`, `bot.commands.${command}.subcommands.${subItem.subcommand}.description`,
); );
slashCommand.addSubcommand((option) => slashCommand.addSubcommand((option) => {
option if (subItem.modify) subItem.modify(option);
.setName(subcommand)
return option
.setName(subItem.subcommand)
.setNameLocalizations(subcommandNameLocalizations) .setNameLocalizations(subcommandNameLocalizations)
.setDescription( .setDescription(
translate( translate(
`bot.commands.${command}.subcommands.${subcommand}.description`, `bot.commands.${command}.subcommands.${subItem.subcommand}.description`,
), ),
) )
.setDescriptionLocalizations(subcommandDescriptionLocalizations), .setDescriptionLocalizations(subcommandDescriptionLocalizations);
});
} else {
const subcommandNameLocalizations = localizationObject(
`bot.commands.${command}.groups.${subItem.group}`,
); );
const subcommandDescriptionLocalizations = localizationObject(
`bot.commands.${command}.groups.${subItem.group}.description`,
);
slashCommand.addSubcommandGroup((option) => {
if (subItem.modify) subItem.modify(option);
return option
.setName(subItem.group)
.setNameLocalizations(subcommandNameLocalizations)
.setDescription(
translate(
`bot.commands.${command}.groups.${subItem.group}.description`,
),
)
.setDescriptionLocalizations(subcommandDescriptionLocalizations);
});
}
}); });
} }
+1 -1
View File
@@ -30,7 +30,7 @@ export default function resolvePeriodFromButton(
start = getTimeDaysAgo(region, endDaysAgoMax); start = getTimeDaysAgo(region, endDaysAgoMax);
end = getTimeDaysAgo(region, startDaysAgoMin); end = getTimeDaysAgo(region, startDaysAgoMin);
} else { } else {
name = getPeriodOptionName(periodSubcommand); name = getPeriodOptionName(periodSubcommand, interaction.locale);
start = getPeriodStart(region, periodSubcommand); start = getPeriodStart(region, periodSubcommand);
end = getPeriodNow(); end = getPeriodNow();
} }
+9 -6
View File
@@ -1,14 +1,17 @@
import { CacheType, ChatInputCommandInteraction } from 'discord.js'; import { CacheType, ChatInputCommandInteraction, Locale } from 'discord.js';
import { Region } from '../../constants/regions'; import { Region } from '../../constants/regions';
import getPeriodNow from '../blitzkrieg/getPeriodNow'; import getPeriodNow from '../blitzkrieg/getPeriodNow';
import getPeriodStart from '../blitzkrieg/getPeriodStart'; import getPeriodStart from '../blitzkrieg/getPeriodStart';
import getTimeDaysAgo from '../blitzkrieg/getTimeDaysAgo'; import getTimeDaysAgo from '../blitzkrieg/getTimeDaysAgo';
import { translator } from '../localization/translator';
import { PeriodSize, PeriodType } from './addPeriodSubCommands'; import { PeriodSize, PeriodType } from './addPeriodSubCommands';
export function getPeriodOptionName(period: PeriodSize) { export function getPeriodOptionName(period: PeriodSize, locale: Locale) {
if (period === 'career') return 'Career'; const { translate } = translator(locale);
if (period === 'today') return 'Today';
return `${period} days`; if (period === 'career') return translate('bot.common.periods.career');
if (period === 'today') return translate('bot.common.periods.today');
return translate('bot.common.periods.days', [period]);
} }
export interface ResolvedPeriod { export interface ResolvedPeriod {
@@ -39,7 +42,7 @@ export default function resolvePeriodFromCommand(
start = getTimeDaysAgo(region, endDaysAgoMax); start = getTimeDaysAgo(region, endDaysAgoMax);
end = getTimeDaysAgo(region, startDaysAgoMin); end = getTimeDaysAgo(region, startDaysAgoMin);
} else { } else {
name = getPeriodOptionName(periodSubcommand); name = getPeriodOptionName(periodSubcommand, interaction.locale);
start = getPeriodStart(region, periodSubcommand); start = getPeriodStart(region, periodSubcommand);
end = getPeriodNow(); end = getPeriodNow();
} }
+16 -3
View File
@@ -5,9 +5,10 @@ export function translator(localeRaw: Locale) {
const locale = locales.includes(localeRaw) ? localeRaw : Locale.EnglishUS; const locale = locales.includes(localeRaw) ? localeRaw : Locale.EnglishUS;
const strings = translations[locale]; const strings = translations[locale];
function translate(path: string): string { function translate(path: string, literals?: string[]): string {
const pathArray = path.split('.'); const pathArray = path.split('.');
let fragment = strings; let fragment = strings;
let resolvedString: string;
for (const pathItem of pathArray) { for (const pathItem of pathArray) {
if (typeof fragment === 'string') { if (typeof fragment === 'string') {
@@ -33,14 +34,26 @@ export function translator(localeRaw: Locale) {
} }
if (typeof fragment === 'string') { if (typeof fragment === 'string') {
return fragment; resolvedString = fragment;
} else if (fragment.$) { } else if (fragment.$) {
return fragment.$!; resolvedString = fragment.$!;
} else { } else {
throw new Error( throw new Error(
`Unresolved tree ending for "${path}" in locale "${locale}"`, `Unresolved tree ending for "${path}" in locale "${locale}"`,
); );
} }
if (literals) {
const chunks = resolvedString.split('%s');
let embeddedString = '';
chunks.forEach((chunk, index) => {
if (index !== 0) embeddedString += literals[index - 1];
embeddedString += chunk;
});
}
return resolvedString;
} }
function t(paths: TemplateStringsArray, ...embeds: string[]) { function t(paths: TemplateStringsArray, ...embeds: string[]) {
+3 -8
View File
@@ -17,7 +17,6 @@ import discord from '../../../discord.json' assert { type: 'json' };
import { aboutCommand } from '../../commands/about'; import { aboutCommand } from '../../commands/about';
import { breakdownCommand } from '../../commands/breakdown'; import { breakdownCommand } from '../../commands/breakdown';
import { debugCommand } from '../../commands/debug'; import { debugCommand } from '../../commands/debug';
import { eligibleCommand } from '../../commands/eligible';
import { evolutionCommand } from '../../commands/evolution'; import { evolutionCommand } from '../../commands/evolution';
import { fullStatsCommand } from '../../commands/fullStats'; import { fullStatsCommand } from '../../commands/fullStats';
import { inactiveCommand } from '../../commands/inactive'; import { inactiveCommand } from '../../commands/inactive';
@@ -78,22 +77,18 @@ type CommandRegistryDefinitionDoesHandleInteraction = {
interaction: ChatInputCommandInteraction<CacheType>, interaction: ChatInputCommandInteraction<CacheType>,
) => InteractionReturnable; ) => InteractionReturnable;
}; };
export type CommandRegistryPromisable = CommandRegistryBase & export type CommandRegistry = CommandRegistryBase &
(CommandRegistryDefinitionBase & (CommandRegistryDefinitionBase &
( (
| CommandRegistryDefinitionHandlesInteraction | CommandRegistryDefinitionHandlesInteraction
| CommandRegistryDefinitionDoesHandleInteraction | CommandRegistryDefinitionDoesHandleInteraction
)); ));
export type CommandRegistry =
| CommandRegistryPromisable
| Promise<CommandRegistryPromisable>;
const rest = new REST().setToken(secrets.DISCORD_TOKEN); const rest = new REST().setToken(secrets.DISCORD_TOKEN);
export const COMMANDS_RAW: CommandRegistry[] = [ export const COMMANDS_RAW: Promise<CommandRegistry>[] = [
permissionsCommand, permissionsCommand,
debugCommand, debugCommand,
eligibleCommand,
aboutCommand, aboutCommand,
inactiveCommand, inactiveCommand,
ownedTanksCommand, ownedTanksCommand,
@@ -113,7 +108,7 @@ export const COMMANDS_RAW: CommandRegistry[] = [
]; ];
export const commands = Promise.allSettled(COMMANDS_RAW).then((rawCommands) => { export const commands = Promise.allSettled(COMMANDS_RAW).then((rawCommands) => {
return rawCommands.reduce<Record<string, CommandRegistryPromisable>>( return rawCommands.reduce<Record<string, CommandRegistry>>(
(commands, registry) => { (commands, registry) => {
if (registry.status === 'rejected') return commands; if (registry.status === 'rejected') return commands;
if (isDev()) registry.value.command.setDefaultMemberPermissions(0); if (isDev()) registry.value.command.setDefaultMemberPermissions(0);
+24
View File
@@ -51,6 +51,13 @@
} }
} }
} }
},
"periods": {
"career": "Career",
"today": "Today",
"days": "%s days",
"custom": "%s - %s days"
} }
}, },
@@ -112,6 +119,23 @@
}, },
"uptime": "Uptime" "uptime": "Uptime"
} }
},
"evolution": {
"$": "evolution",
"description": "Evolution of statistics",
"groups": {
"player": {
"$": "player",
"description": "Player statistics"
},
"tank": {
"$": "tank",
"description": "Tank statistics"
}
}
} }
} }
}, },