mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 03:59:50 +02:00
feat: some period translations
This commit is contained in:
1 parent
267f23cad8
commit
adad668157
24 files changed
+1309
-1216
No files matched your search
@@ -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,
|
||||||
|
|||||||
+30
-28
@@ -6,33 +6,35 @@ 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) => {
|
||||||
inProduction: true,
|
resolve({
|
||||||
inPublic: true,
|
inProduction: true,
|
||||||
|
inPublic: true,
|
||||||
|
|
||||||
command: createLocalizedCommand('debug'),
|
command: createLocalizedCommand('debug'),
|
||||||
|
|
||||||
async handler(interaction) {
|
async handler(interaction) {
|
||||||
const { t } = translator(interaction.locale);
|
const { t } = translator(interaction.locale);
|
||||||
const currentTime = new Date().getTime();
|
const currentTime = new Date().getTime();
|
||||||
const uptime = currentTime - executionStart;
|
const uptime = currentTime - executionStart;
|
||||||
const list = [
|
const list = [
|
||||||
[t`bot.commands.debug.body.version`, packageJSON.version],
|
[t`bot.commands.debug.body.version`, packageJSON.version],
|
||||||
[
|
[
|
||||||
t`bot.commands.debug.body.shard`,
|
t`bot.commands.debug.body.shard`,
|
||||||
client.shard?.ids[0] ?? t`bot.commands.debug.body.shard.default`,
|
client.shard?.ids[0] ?? t`bot.commands.debug.body.shard.default`,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
t`bot.commands.debug.body.uptime`,
|
t`bot.commands.debug.body.uptime`,
|
||||||
`${Math.floor((uptime / 1000 / 60 / 60) % 24)}h ${Math.floor(
|
`${Math.floor((uptime / 1000 / 60 / 60) % 24)}h ${Math.floor(
|
||||||
(uptime / 1000 / 60) % 60,
|
(uptime / 1000 / 60) % 60,
|
||||||
)}m ${Math.floor((uptime / 1000) % 60)}s ${Math.floor(
|
)}m ${Math.floor((uptime / 1000) % 60)}s ${Math.floor(
|
||||||
uptime % 1000,
|
uptime % 1000,
|
||||||
)}ms`,
|
)}ms`,
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
return t`bot.commands.debug.body.title${list
|
return t`bot.commands.debug.body.title${list
|
||||||
.map(([key, value]) => `- ${key}: ${value}`)
|
.map(([key, value]) => `- ${key}: ${value}`)
|
||||||
.join('\n')}`;
|
.join('\n')}`;
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
});
|
||||||
+185
-178
@@ -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
@@ -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'),
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
+53
-50
@@ -12,58 +12,61 @@ import { CommandRegistry } from '../events/interactionCreate';
|
|||||||
|
|
||||||
const DEFAULT_THRESHOLD = 7;
|
const DEFAULT_THRESHOLD = 7;
|
||||||
|
|
||||||
export const inactiveCommand: CommandRegistry = {
|
export const inactiveCommand = new Promise<CommandRegistry>((resolve) => {
|
||||||
inProduction: true,
|
resolve({
|
||||||
inPublic: true,
|
inProduction: true,
|
||||||
|
inPublic: true,
|
||||||
|
|
||||||
command: new SlashCommandBuilder()
|
command: new SlashCommandBuilder()
|
||||||
.setName('inactive')
|
.setName('inactive')
|
||||||
.setDescription('Lists all inactive players')
|
.setDescription('Lists all inactive players')
|
||||||
.addStringOption(addClanChoices)
|
.addStringOption(addClanChoices)
|
||||||
.addNumberOption((option) =>
|
.addNumberOption((option) =>
|
||||||
option
|
option
|
||||||
.setName('threshold')
|
.setName('threshold')
|
||||||
.setDescription(
|
.setDescription(
|
||||||
`The number of days inactive (default: ${DEFAULT_THRESHOLD})`,
|
`The number of days inactive (default: ${DEFAULT_THRESHOLD})`,
|
||||||
|
)
|
||||||
|
.setMinValue(0),
|
||||||
|
),
|
||||||
|
|
||||||
|
async handler(interaction) {
|
||||||
|
const { region, id } = await resolveClanFromCommand(interaction);
|
||||||
|
const threshold =
|
||||||
|
interaction.options.getNumber('threshold')! ?? DEFAULT_THRESHOLD;
|
||||||
|
const time = new Date().getTime() / 1000;
|
||||||
|
const clanInfo = await getClanInfo(region, id);
|
||||||
|
const accountInfo = await getAccountInfo(region, clanInfo.members_ids);
|
||||||
|
const inactive = accountInfo
|
||||||
|
.map(
|
||||||
|
(account) =>
|
||||||
|
[
|
||||||
|
account.nickname,
|
||||||
|
(time - account.last_battle_time) / 60 / 60 / 24,
|
||||||
|
] as [string, number],
|
||||||
)
|
)
|
||||||
.setMinValue(0),
|
.filter(([, inactiveDays]) => inactiveDays >= threshold)
|
||||||
),
|
.sort((a, b) => b[1] - a[1])
|
||||||
|
.map(
|
||||||
|
([name, days]) =>
|
||||||
|
[name, `${days.toFixed(0)} days`] as [string, string],
|
||||||
|
);
|
||||||
|
const hasInactiveMembers = inactive.length > 0;
|
||||||
|
|
||||||
async handler(interaction) {
|
return (
|
||||||
const { region, id } = await resolveClanFromCommand(interaction);
|
<CommandWrapper>
|
||||||
const threshold =
|
<TitleBar
|
||||||
interaction.options.getNumber('threshold')! ?? DEFAULT_THRESHOLD;
|
title={clanInfo.name}
|
||||||
const time = new Date().getTime() / 1000;
|
image={`https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clanInfo.emblem_set_id}.png`}
|
||||||
const clanInfo = await getClanInfo(region, id);
|
description={`Inactive for ${threshold}+ Days • ${new Date().toDateString()}`}
|
||||||
const accountInfo = await getAccountInfo(region, clanInfo.members_ids);
|
/>
|
||||||
const inactive = accountInfo
|
|
||||||
.map(
|
{!hasInactiveMembers && <NoData type={NoDataType.PlayersInPeriod} />}
|
||||||
(account) =>
|
{hasInactiveMembers && <GenericStats stats={inactive} />}
|
||||||
[
|
</CommandWrapper>
|
||||||
account.nickname,
|
|
||||||
(time - account.last_battle_time) / 60 / 60 / 24,
|
|
||||||
] as [string, number],
|
|
||||||
)
|
|
||||||
.filter(([, inactiveDays]) => inactiveDays >= threshold)
|
|
||||||
.sort((a, b) => b[1] - a[1])
|
|
||||||
.map(
|
|
||||||
([name, days]) => [name, `${days.toFixed(0)} days`] as [string, string],
|
|
||||||
);
|
);
|
||||||
const hasInactiveMembers = inactive.length > 0;
|
},
|
||||||
|
|
||||||
return (
|
autocomplete: autocompleteClan,
|
||||||
<CommandWrapper>
|
});
|
||||||
<TitleBar
|
});
|
||||||
title={clanInfo.name}
|
|
||||||
image={`https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clanInfo.emblem_set_id}.png`}
|
|
||||||
description={`Inactive for ${threshold}+ Days • ${new Date().toDateString()}`}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{!hasInactiveMembers && <NoData type={NoDataType.PlayersInPeriod} />}
|
|
||||||
{hasInactiveMembers && <GenericStats stats={inactive} />}
|
|
||||||
</CommandWrapper>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
autocomplete: autocompleteClan,
|
|
||||||
};
|
|
||||||
+148
-144
@@ -16,194 +16,198 @@ 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) => {
|
||||||
inProduction: true,
|
resolve({
|
||||||
inPublic: true,
|
inProduction: 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);
|
||||||
const discordId = parseInt(interaction.user.id);
|
const discordId = parseInt(interaction.user.id);
|
||||||
const accountInfo = await getAccountInfo(region, id);
|
const accountInfo = await getAccountInfo(region, id);
|
||||||
const clanAccountInfo = await getClanAccountInfo(region, id, ['clan']);
|
const clanAccountInfo = await getClanAccountInfo(region, id, ['clan']);
|
||||||
|
|
||||||
await linkBlitzAndDiscord(discordId, region, id);
|
await linkBlitzAndDiscord(discordId, region, id);
|
||||||
|
|
||||||
if (interaction.guildId === discord.sklld_guild_id) {
|
if (interaction.guildId === discord.sklld_guild_id) {
|
||||||
if (!interaction.guild?.members.me?.permissions.has('ManageRoles')) {
|
if (!interaction.guild?.members.me?.permissions.has('ManageRoles')) {
|
||||||
return embedNegative(
|
return embedNegative(
|
||||||
`${markdownEscape(interaction.user.username)} failed to verify`,
|
`${markdownEscape(interaction.user.username)} failed to verify`,
|
||||||
"I don't have the permission to change your manage roles.",
|
"I don't have the permission to change your manage roles.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await (interaction.member!.roles as GuildMemberRoleManager).remove(
|
||||||
|
discord.sklld_verify_role,
|
||||||
|
);
|
||||||
|
await (interaction.member!.roles as GuildMemberRoleManager).add(
|
||||||
|
discord.sklld_peasant_role,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await (interaction.member!.roles as GuildMemberRoleManager).remove(
|
return [
|
||||||
discord.sklld_verify_role,
|
<CommandWrapper fat>
|
||||||
);
|
|
||||||
await (interaction.member!.roles as GuildMemberRoleManager).add(
|
|
||||||
discord.sklld_peasant_role,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
<CommandWrapper fat>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Glow color={teal.teal9} rotation={90} />
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'space-between',
|
||||||
gap: 16,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<Glow color={teal.teal9} rotation={90} />
|
||||||
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
gap: 8,
|
flexDirection: 'column',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
|
gap: 16,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
|
||||||
src={
|
|
||||||
interaction.user.avatarURL({ extension: 'png' }) ??
|
|
||||||
'https://i.stack.imgur.com/l60Hf.png'
|
|
||||||
}
|
|
||||||
style={{
|
|
||||||
width: 64,
|
|
||||||
height: 64,
|
|
||||||
borderRadius: 32,
|
|
||||||
objectFit: 'cover',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
gap: 8,
|
||||||
gap: 4,
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
fontSize: 32,
|
|
||||||
color: theme.colors.textHighContrast,
|
|
||||||
fontWeight: 900,
|
|
||||||
maxWidth: 240,
|
|
||||||
overflow: 'hidden',
|
|
||||||
textOverflow: 'ellipsis',
|
|
||||||
whiteSpace: 'nowrap',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{interaction.user.displayName}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
fontSize: 16,
|
|
||||||
color: theme.colors.textLowContrast,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
@{interaction.user.username}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
gap: 4,
|
|
||||||
color: theme.colors.textLowContrast_teal,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src="https://i.imgur.com/jIcRgog.png"
|
|
||||||
style={{
|
|
||||||
width: 16,
|
|
||||||
height: 16,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<span style={{ fontSize: 16 }}>Accounts linked</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
gap: 8,
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{clanAccountInfo?.clan ? (
|
|
||||||
<img
|
<img
|
||||||
|
src={
|
||||||
|
interaction.user.avatarURL({ extension: 'png' }) ??
|
||||||
|
'https://i.stack.imgur.com/l60Hf.png'
|
||||||
|
}
|
||||||
style={{
|
style={{
|
||||||
width: 64,
|
width: 64,
|
||||||
height: 64,
|
height: 64,
|
||||||
borderRadius: 32,
|
borderRadius: 32,
|
||||||
objectFit: 'cover',
|
objectFit: 'cover',
|
||||||
}}
|
}}
|
||||||
src={`https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clanAccountInfo?.clan?.emblem_set_id}.png`}
|
|
||||||
/>
|
/>
|
||||||
) : null}
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: 4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 32,
|
||||||
|
color: theme.colors.textHighContrast,
|
||||||
|
fontWeight: 900,
|
||||||
|
maxWidth: 240,
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{interaction.user.displayName}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 16,
|
||||||
|
color: theme.colors.textLowContrast,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
@{interaction.user.username}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
gap: 4,
|
gap: 4,
|
||||||
|
color: theme.colors.textLowContrast_teal,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span
|
<img
|
||||||
|
src="https://i.imgur.com/jIcRgog.png"
|
||||||
style={{
|
style={{
|
||||||
fontSize: 32,
|
width: 16,
|
||||||
color: theme.colors.textHighContrast,
|
height: 16,
|
||||||
fontWeight: 900,
|
}}
|
||||||
maxWidth: 240,
|
/>
|
||||||
overflow: 'hidden',
|
<span style={{ fontSize: 16 }}>Accounts linked</span>
|
||||||
textOverflow: 'ellipsis',
|
</div>
|
||||||
whiteSpace: 'nowrap',
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
gap: 8,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{clanAccountInfo?.clan ? (
|
||||||
|
<img
|
||||||
|
style={{
|
||||||
|
width: 64,
|
||||||
|
height: 64,
|
||||||
|
borderRadius: 32,
|
||||||
|
objectFit: 'cover',
|
||||||
|
}}
|
||||||
|
src={`https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clanAccountInfo?.clan?.emblem_set_id}.png`}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: 4,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{accountInfo.nickname}
|
<span
|
||||||
</span>
|
style={{
|
||||||
<span
|
fontSize: 32,
|
||||||
style={{
|
color: theme.colors.textHighContrast,
|
||||||
fontSize: 16,
|
fontWeight: 900,
|
||||||
color: theme.colors.textLowContrast,
|
maxWidth: 240,
|
||||||
}}
|
overflow: 'hidden',
|
||||||
>
|
textOverflow: 'ellipsis',
|
||||||
{clanAccountInfo?.clan
|
whiteSpace: 'nowrap',
|
||||||
? `[${clanAccountInfo?.clan?.tag}] • `
|
}}
|
||||||
: ''}
|
>
|
||||||
{REGION_NAMES[region]}
|
{accountInfo.nickname}
|
||||||
</span>
|
</span>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 16,
|
||||||
|
color: theme.colors.textLowContrast,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{clanAccountInfo?.clan
|
||||||
|
? `[${clanAccountInfo?.clan?.tag}] • `
|
||||||
|
: ''}
|
||||||
|
{REGION_NAMES[region]}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Glow color={teal.teal9} rotation={-90} />
|
||||||
</div>
|
</div>
|
||||||
|
</CommandWrapper>,
|
||||||
|
|
||||||
<Glow color={teal.teal9} rotation={-90} />
|
embedInfo(
|
||||||
</div>
|
'Incorrect account?',
|
||||||
</CommandWrapper>,
|
'Use the command again and select a username from the search results.',
|
||||||
|
),
|
||||||
|
];
|
||||||
|
},
|
||||||
|
|
||||||
embedInfo(
|
autocomplete: autocompleteUsername,
|
||||||
'Incorrect account?',
|
});
|
||||||
'Use the command again and select a username from the search results.',
|
});
|
||||||
),
|
|
||||||
];
|
|
||||||
},
|
|
||||||
|
|
||||||
autocomplete: autocompleteUsername,
|
|
||||||
};
|
|
||||||
+99
-95
@@ -21,108 +21,112 @@ 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) => {
|
||||||
inProduction: true,
|
resolve({
|
||||||
inPublic: true,
|
inProduction: true,
|
||||||
|
inPublic: true,
|
||||||
|
|
||||||
command: new SlashCommandBuilder()
|
command: new SlashCommandBuilder()
|
||||||
.setName('owned-tanks')
|
.setName('owned-tanks')
|
||||||
.setDescription("Shows a player's owned tanks")
|
.setDescription("Shows a player's owned tanks")
|
||||||
.addStringOption(addTierChoices)
|
.addStringOption(addTierChoices)
|
||||||
.addStringOption(addUsernameChoices),
|
.addStringOption(addUsernameChoices),
|
||||||
|
|
||||||
async handler(interaction) {
|
async handler(interaction) {
|
||||||
const tier = Number(interaction.options.getString('tier'));
|
const tier = Number(interaction.options.getString('tier'));
|
||||||
const account = await resolvePlayerFromCommand(interaction);
|
const account = await resolvePlayerFromCommand(interaction);
|
||||||
const { id, region: server } = account;
|
const { id, region: server } = account;
|
||||||
const accountInfo = await getAccountInfo(server, id);
|
const accountInfo = await getAccountInfo(server, id);
|
||||||
const tankStats = await getTankStats(server, id);
|
const tankStats = await getTankStats(server, id);
|
||||||
const filteredTanks = (
|
const filteredTanks = (
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
tankStats.map(async (tankData) => ({
|
tankStats.map(async (tankData) => ({
|
||||||
tankDefinitions: (await tankDefinitions)[tankData.tank_id]!,
|
tankDefinitions: (await tankDefinitions)[tankData.tank_id]!,
|
||||||
id: tankData.tank_id,
|
id: tankData.tank_id,
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
).filter((tank) => tank.tankDefinitions?.tier === tier);
|
).filter((tank) => tank.tankDefinitions?.tier === tier);
|
||||||
const clanAccountInfo = await getClanAccountInfo(server, id, ['clan']);
|
const clanAccountInfo = await getClanAccountInfo(server, id, ['clan']);
|
||||||
const groupedTanks: Record<string, TankDefinition[]> = {};
|
const groupedTanks: Record<string, TankDefinition[]> = {};
|
||||||
const nations: string[] = [];
|
const nations: string[] = [];
|
||||||
|
|
||||||
filteredTanks.forEach((tank) => {
|
filteredTanks.forEach((tank) => {
|
||||||
if (groupedTanks[tank.tankDefinitions.nation] === undefined) {
|
if (groupedTanks[tank.tankDefinitions.nation] === undefined) {
|
||||||
groupedTanks[tank.tankDefinitions.nation] = [tank.tankDefinitions];
|
groupedTanks[tank.tankDefinitions.nation] = [tank.tankDefinitions];
|
||||||
nations.push(tank.tankDefinitions.nation);
|
nations.push(tank.tankDefinitions.nation);
|
||||||
} else {
|
} else {
|
||||||
groupedTanks[tank.tankDefinitions.nation].push(tank.tankDefinitions);
|
groupedTanks[tank.tankDefinitions.nation].push(tank.tankDefinitions);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
nations.sort();
|
nations.sort();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CommandWrapper>
|
<CommandWrapper>
|
||||||
<TitleBar
|
<TitleBar
|
||||||
title={accountInfo.nickname}
|
title={accountInfo.nickname}
|
||||||
image={
|
image={
|
||||||
clanAccountInfo?.clan
|
clanAccountInfo?.clan
|
||||||
? emblemIdToURL(clanAccountInfo.clan.emblem_set_id)
|
? emblemIdToURL(clanAccountInfo.clan.emblem_set_id)
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
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(
|
||||||
nations.map(async (nation) => {
|
nations.map(async (nation) => {
|
||||||
const tanks = groupedTanks[nation];
|
const tanks = groupedTanks[nation];
|
||||||
const leftColumnSize = Math.ceil(tanks.length / 2);
|
const leftColumnSize = Math.ceil(tanks.length / 2);
|
||||||
const leftColumn = tanks.slice(0, leftColumnSize);
|
const leftColumn = tanks.slice(0, leftColumnSize);
|
||||||
const rightColumn = tanks.slice(leftColumnSize);
|
const rightColumn = tanks.slice(leftColumnSize);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tanks.Root>
|
<Tanks.Root>
|
||||||
<Tanks.Title>
|
<Tanks.Title>
|
||||||
{(await encyclopediaInfo).vehicle_nations[nation]}
|
{(await encyclopediaInfo).vehicle_nations[nation]}
|
||||||
</Tanks.Title>
|
</Tanks.Title>
|
||||||
|
|
||||||
<Tanks.Row>
|
<Tanks.Row>
|
||||||
<Tanks.Column>
|
<Tanks.Column>
|
||||||
{await Promise.all(
|
{await Promise.all(
|
||||||
leftColumn.map(async (tank) => (
|
leftColumn.map(async (tank) => (
|
||||||
<Tanks.Item
|
<Tanks.Item
|
||||||
key={tank.id}
|
key={tank.id}
|
||||||
name={tank.name}
|
name={tank.name}
|
||||||
tankType={tank.type}
|
tankType={tank.type}
|
||||||
image={tankIcon(tank.id)}
|
image={tankIcon(tank.id)}
|
||||||
treeType={tank.treeType}
|
treeType={tank.treeType}
|
||||||
/>
|
/>
|
||||||
)),
|
)),
|
||||||
)}
|
)}
|
||||||
</Tanks.Column>
|
</Tanks.Column>
|
||||||
<Tanks.Column>
|
<Tanks.Column>
|
||||||
{await Promise.all(
|
{await Promise.all(
|
||||||
rightColumn.map(async (tank) => (
|
rightColumn.map(async (tank) => (
|
||||||
<Tanks.Item
|
<Tanks.Item
|
||||||
key={tank.id}
|
key={tank.id}
|
||||||
name={tank.name}
|
name={tank.name}
|
||||||
tankType={tank.type}
|
tankType={tank.type}
|
||||||
image={tankIcon(tank.id)}
|
image={tankIcon(tank.id)}
|
||||||
treeType={tank.treeType}
|
treeType={tank.treeType}
|
||||||
/>
|
/>
|
||||||
)),
|
)),
|
||||||
)}
|
)}
|
||||||
</Tanks.Column>
|
</Tanks.Column>
|
||||||
</Tanks.Row>
|
</Tanks.Row>
|
||||||
</Tanks.Root>
|
</Tanks.Root>
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
))}
|
))}
|
||||||
</CommandWrapper>
|
</CommandWrapper>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
autocomplete: autocompleteUsername,
|
autocomplete: autocompleteUsername,
|
||||||
};
|
});
|
||||||
|
});
|
||||||
+38
-36
@@ -1,42 +1,44 @@
|
|||||||
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) => {
|
||||||
inProduction: true,
|
resolve({
|
||||||
inPublic: true,
|
inProduction: true,
|
||||||
|
inPublic: true,
|
||||||
|
|
||||||
command: new SlashCommandBuilder()
|
command: new SlashCommandBuilder()
|
||||||
.setName('permissions')
|
.setName('permissions')
|
||||||
.setDescription('Checks wether the bot has the required permissions'),
|
.setDescription('Checks wether the bot has the required permissions'),
|
||||||
|
|
||||||
async handler(interaction) {
|
async handler(interaction) {
|
||||||
// friday the 13th permissions easter egg
|
// friday the 13th permissions easter egg
|
||||||
const isFridayThe13th =
|
const isFridayThe13th =
|
||||||
new Date().getDay() === 5 && new Date().getDate() === 13;
|
new Date().getDay() === 5 && new Date().getDate() === 13;
|
||||||
const permissions = [
|
const permissions = [
|
||||||
[
|
[
|
||||||
interaction.appPermissions?.has('ViewChannel'),
|
interaction.appPermissions?.has('ViewChannel'),
|
||||||
'View channels: needed for the refresh button to work',
|
'View channels: needed for the refresh button to work',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
interaction.appPermissions?.has('ReadMessageHistory'),
|
interaction.appPermissions?.has('ReadMessageHistory'),
|
||||||
'Read message history: also needed for the refresh button to work',
|
'Read message history: also needed for the refresh button to work',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
interaction.appPermissions?.has('AttachFiles'),
|
interaction.appPermissions?.has('AttachFiles'),
|
||||||
'Attach files: needed for image-based commands to work',
|
'Attach files: needed for image-based commands to work',
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
const body = permissions
|
const body = permissions
|
||||||
.map(([hasPermission, description]) => {
|
.map(([hasPermission, description]) => {
|
||||||
if (hasPermission) {
|
if (hasPermission) {
|
||||||
return `${isFridayThe13th ? '😇' : '✅'} ${description}`;
|
return `${isFridayThe13th ? '😇' : '✅'} ${description}`;
|
||||||
} else {
|
} else {
|
||||||
return `${isFridayThe13th ? '💀' : '❌'} ${description}`;
|
return `${isFridayThe13th ? '💀' : '❌'} ${description}`;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.join('\n');
|
.join('\n');
|
||||||
|
|
||||||
return `# Permissions\n${body}`;
|
return `# Permissions\n${body}`;
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
});
|
||||||
+27
-25
@@ -1,32 +1,34 @@
|
|||||||
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) => {
|
||||||
inProduction: true,
|
resolve({
|
||||||
inPublic: true,
|
inProduction: true,
|
||||||
handlesInteraction: true,
|
inPublic: true,
|
||||||
|
handlesInteraction: true,
|
||||||
|
|
||||||
command: new SlashCommandBuilder()
|
command: new SlashCommandBuilder()
|
||||||
.setName('ping')
|
.setName('ping')
|
||||||
.setDescription('Check if the bot is alive')
|
.setDescription('Check if the bot is alive')
|
||||||
.addSubcommand((option) =>
|
.addSubcommand((option) =>
|
||||||
option.setName('blitzkrieg').setDescription('Ping Blitzkrieg'),
|
option.setName('blitzkrieg').setDescription('Ping Blitzkrieg'),
|
||||||
)
|
)
|
||||||
.addSubcommand((option) =>
|
.addSubcommand((option) =>
|
||||||
option.setName('blitz').setDescription('Ping Blitz'),
|
option.setName('blitz').setDescription('Ping Blitz'),
|
||||||
),
|
),
|
||||||
|
|
||||||
async handler(interaction) {
|
async handler(interaction) {
|
||||||
const subcommand = interaction.options.getSubcommand();
|
const subcommand = interaction.options.getSubcommand();
|
||||||
const executionStart = Date.now();
|
const executionStart = Date.now();
|
||||||
|
|
||||||
if (subcommand === 'blitzkrieg') {
|
if (subcommand === 'blitzkrieg') {
|
||||||
await interaction.editReply('Pong 🏓');
|
await interaction.editReply('Pong 🏓');
|
||||||
} else {
|
} else {
|
||||||
await fetch('https://api.wotblitz.com/');
|
await fetch('https://api.wotblitz.com/');
|
||||||
}
|
}
|
||||||
|
|
||||||
const executionTime = Date.now() - executionStart;
|
const executionTime = Date.now() - executionStart;
|
||||||
interaction.editReply(`Pong 🏓 - ${executionTime}ms`);
|
interaction.editReply(`Pong 🏓 - ${executionTime}ms`);
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
});
|
||||||
@@ -12,57 +12,62 @@ import { CommandRegistry } from '../events/interactionCreate';
|
|||||||
|
|
||||||
type SortBy = 'name' | 'count';
|
type SortBy = 'name' | 'count';
|
||||||
|
|
||||||
export const playerAchievementsCommand: CommandRegistry = {
|
export const playerAchievementsCommand = new Promise<CommandRegistry>(
|
||||||
inProduction: true,
|
(resolve) => {
|
||||||
inPublic: true,
|
resolve({
|
||||||
|
inProduction: true,
|
||||||
|
inPublic: true,
|
||||||
|
|
||||||
command: new SlashCommandBuilder()
|
command: new SlashCommandBuilder()
|
||||||
.setName('player-achievements')
|
.setName('player-achievements')
|
||||||
.setDescription("All the player's achievements")
|
.setDescription("All the player's achievements")
|
||||||
.addStringOption(addUsernameChoices)
|
.addStringOption(addUsernameChoices)
|
||||||
.addStringOption((option) =>
|
.addStringOption((option) =>
|
||||||
option
|
option
|
||||||
.setName('sort')
|
.setName('sort')
|
||||||
.setDescription('What to sort by (default: name)')
|
.setDescription('What to sort by (default: name)')
|
||||||
.addChoices(
|
.addChoices(
|
||||||
{ name: 'By name', value: 'name' satisfies SortBy },
|
{ name: 'By name', value: 'name' satisfies SortBy },
|
||||||
{ name: 'By count', value: 'count' satisfies SortBy },
|
{ name: 'By count', value: 'count' satisfies SortBy },
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
|
|
||||||
async handler(interaction) {
|
async handler(interaction) {
|
||||||
const sortBy = (interaction.options.getString('sort') ?? 'name') as SortBy;
|
const sortBy = (interaction.options.getString('sort') ??
|
||||||
const account = await resolvePlayerFromCommand(interaction);
|
'name') as SortBy;
|
||||||
const { id, region: region } = account;
|
const account = await resolvePlayerFromCommand(interaction);
|
||||||
const accountInfo = await getAccountInfo(region, id);
|
const { id, region: region } = account;
|
||||||
const accountsAchievements = await getAccountAchievements(region, id);
|
const accountInfo = await getAccountInfo(region, id);
|
||||||
const accountAchievements = accountsAchievements;
|
const accountsAchievements = await getAccountAchievements(region, id);
|
||||||
const compound = {
|
const accountAchievements = accountsAchievements;
|
||||||
...accountAchievements.achievements,
|
const compound = {
|
||||||
...accountAchievements.max_series,
|
...accountAchievements.achievements,
|
||||||
};
|
...accountAchievements.max_series,
|
||||||
|
};
|
||||||
|
|
||||||
return embedInfo(
|
return embedInfo(
|
||||||
`${markdownEscape(accountInfo.nickname)}'s information`,
|
`${markdownEscape(accountInfo.nickname)}'s information`,
|
||||||
|
|
||||||
markdownTable(
|
markdownTable(
|
||||||
Object.keys(compound)
|
Object.keys(compound)
|
||||||
.filter((achievement) => compound[achievement] > 0)
|
.filter((achievement) => compound[achievement] > 0)
|
||||||
.sort(
|
.sort(
|
||||||
sortBy === 'count'
|
sortBy === 'count'
|
||||||
? (a, b) => compound[b] - compound[a]
|
? (a, b) => compound[b] - compound[a]
|
||||||
: undefined,
|
: undefined,
|
||||||
)
|
)
|
||||||
.map(
|
.map(
|
||||||
(achievement) =>
|
(achievement) =>
|
||||||
[
|
[
|
||||||
startCase(achievement),
|
startCase(achievement),
|
||||||
compound[achievement],
|
compound[achievement],
|
||||||
] as TableInputEntry,
|
] as TableInputEntry,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
},
|
||||||
},
|
|
||||||
|
|
||||||
autocomplete: autocompleteUsername,
|
autocomplete: autocompleteUsername,
|
||||||
};
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
+41
-39
@@ -8,47 +8,49 @@ 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) => {
|
||||||
inProduction: true,
|
resolve({
|
||||||
inPublic: true,
|
inProduction: true,
|
||||||
|
inPublic: true,
|
||||||
|
|
||||||
command: new SlashCommandBuilder()
|
command: new SlashCommandBuilder()
|
||||||
.setName('player-info')
|
.setName('player-info')
|
||||||
.setDescription('Basic information about a player')
|
.setDescription('Basic information about a player')
|
||||||
.addStringOption(addUsernameChoices),
|
.addStringOption(addUsernameChoices),
|
||||||
|
|
||||||
async handler(interaction) {
|
async handler(interaction) {
|
||||||
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);
|
||||||
|
|
||||||
return embedInfo(
|
return embedInfo(
|
||||||
`${markdownEscape(accountInfo.nickname)}'s information`,
|
`${markdownEscape(accountInfo.nickname)}'s information`,
|
||||||
|
|
||||||
markdownTable([
|
markdownTable([
|
||||||
['Nickname', `${accountInfo.nickname}`],
|
['Nickname', `${accountInfo.nickname}`],
|
||||||
['Battles', `${accountInfo.statistics.all.battles}`],
|
['Battles', `${accountInfo.statistics.all.battles}`],
|
||||||
[
|
[
|
||||||
'Winrate',
|
'Winrate',
|
||||||
`${(
|
`${(
|
||||||
100 *
|
100 *
|
||||||
(accountInfo.statistics.all.wins /
|
(accountInfo.statistics.all.wins /
|
||||||
accountInfo.statistics.all.battles)
|
accountInfo.statistics.all.battles)
|
||||||
).toFixed(2)}%`,
|
).toFixed(2)}%`,
|
||||||
],
|
],
|
||||||
[],
|
[],
|
||||||
['Account ID', `${accountInfo.account_id}`],
|
['Account ID', `${accountInfo.account_id}`],
|
||||||
[
|
[
|
||||||
'Created',
|
'Created',
|
||||||
`${new Date(accountInfo.created_at * 1000).toDateString()}`,
|
`${new Date(accountInfo.created_at * 1000).toDateString()}`,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'Last battle',
|
'Last battle',
|
||||||
`${new Date(accountInfo.last_battle_time * 1000).toDateString()}`,
|
`${new Date(accountInfo.last_battle_time * 1000).toDateString()}`,
|
||||||
],
|
],
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
autocomplete: autocompleteUsername,
|
autocomplete: autocompleteUsername,
|
||||||
};
|
});
|
||||||
|
});
|
||||||
+303
-308
@@ -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,334 +122,329 @@ 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(
|
FIRST_ARCHIVED_RATINGS_SEASON,
|
||||||
FIRST_ARCHIVED_RATINGS_SEASON,
|
latestArchivedSeasonNumber + (onGoingSeason ? 1 : 0) + 1,
|
||||||
latestArchivedSeasonNumber + (onGoingSeason ? 1 : 0) + 1,
|
);
|
||||||
|
|
||||||
|
function addSubcommands(option: SlashCommandSubcommandBuilder) {
|
||||||
|
return option
|
||||||
|
.addStringOption(addUsernameChoices)
|
||||||
|
.addIntegerOption((option) =>
|
||||||
|
option
|
||||||
|
.setName('limit')
|
||||||
|
.setDescription('How many neighbors to display (default: 10)')
|
||||||
|
.setRequired(false)
|
||||||
|
.setMinValue(5)
|
||||||
|
.setMaxValue(30),
|
||||||
|
)
|
||||||
|
.addStringOption((option) =>
|
||||||
|
option
|
||||||
|
.setName('season')
|
||||||
|
.setDescription('Season number')
|
||||||
|
.setRequired(false)
|
||||||
|
.addChoices(
|
||||||
|
...seasonNumbers
|
||||||
|
.map(
|
||||||
|
(number, index) =>
|
||||||
|
({
|
||||||
|
name: `${number}${index === seasonNumbers.length - 1 && onGoingSeason ? ' (current)' : ''}`,
|
||||||
|
value: `${number}`,
|
||||||
|
}) satisfies APIApplicationCommandOptionChoice<string>,
|
||||||
|
)
|
||||||
|
.reverse(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const command = new SlashCommandBuilder()
|
||||||
|
.setName('ratings')
|
||||||
|
.setDescription('Ratings battles statistics')
|
||||||
|
.addSubcommand((option) =>
|
||||||
|
addSubcommands(
|
||||||
|
option
|
||||||
|
.setName('neighbors')
|
||||||
|
.setDescription('Shows neighboring players on the leaderboard'),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.addSubcommand((option) =>
|
||||||
|
addSubcommands(
|
||||||
|
option
|
||||||
|
.setName('league')
|
||||||
|
.setDescription('Shows top players from a league')
|
||||||
|
.addStringOption(addRegionChoices)
|
||||||
|
.addStringOption((option) =>
|
||||||
|
option
|
||||||
|
.setName('league')
|
||||||
|
.setDescription('The league to display')
|
||||||
|
.addChoices(
|
||||||
|
...LEAGUES.map(
|
||||||
|
({ name }, value) =>
|
||||||
|
({
|
||||||
|
name,
|
||||||
|
value: `${value}`,
|
||||||
|
}) satisfies APIApplicationCommandOptionChoice<string>,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.setRequired(true),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
function addSubcommands(option: SlashCommandSubcommandBuilder) {
|
resolve({
|
||||||
return option
|
inProduction: true,
|
||||||
.addStringOption(addUsernameChoices)
|
inPublic: true,
|
||||||
.addIntegerOption((option) =>
|
|
||||||
option
|
command,
|
||||||
.setName('limit')
|
|
||||||
.setDescription('How many neighbors to display (default: 10)')
|
async handler(interaction) {
|
||||||
.setRequired(false)
|
interface SimplifiedPlayer {
|
||||||
.setMinValue(5)
|
id: number;
|
||||||
.setMaxValue(30),
|
score: number;
|
||||||
)
|
position: number;
|
||||||
.addStringOption((option) =>
|
clan: string | undefined;
|
||||||
option
|
nickname: string;
|
||||||
.setName('season')
|
}
|
||||||
.setDescription('Season number')
|
|
||||||
.setRequired(false)
|
const subcommand = interaction.options.getSubcommand(true) as
|
||||||
.addChoices(
|
| 'league'
|
||||||
...seasonNumbers
|
| 'neighbors';
|
||||||
.map(
|
const limit = interaction.options.getInteger('limit') ?? 10;
|
||||||
(number, index) =>
|
const seasonOption = interaction.options.getString('season');
|
||||||
|
const season = seasonOption
|
||||||
|
? parseInt(seasonOption)
|
||||||
|
: onGoingSeason
|
||||||
|
? latestArchivedSeasonNumber + 1
|
||||||
|
: latestArchivedSeasonNumber;
|
||||||
|
const isCurrentSeason = season === latestArchivedSeasonNumber + 1;
|
||||||
|
let titleName: string;
|
||||||
|
let titleImage: string | undefined;
|
||||||
|
let titleDescription: string;
|
||||||
|
let playersBefore: number;
|
||||||
|
let playersAfter: number;
|
||||||
|
let players: SimplifiedPlayer[] | undefined;
|
||||||
|
let midnightLeaderboard: BlitzkriegRatingsLeaderboard | undefined;
|
||||||
|
let playerId: number | undefined;
|
||||||
|
let regionRatingsInfo: RatingsInfo;
|
||||||
|
|
||||||
|
if (subcommand === 'league') {
|
||||||
|
const leagueIndex = parseInt(interaction.options.getString('league')!);
|
||||||
|
const region = interaction.options.getString('region') as Region;
|
||||||
|
regionRatingsInfo = await (isCurrentSeason
|
||||||
|
? getRatingsInfo(region)
|
||||||
|
: getArchivedRatingsInfo(region, season!));
|
||||||
|
|
||||||
|
if (regionRatingsInfo.detail !== undefined) return noOngoingSeason;
|
||||||
|
|
||||||
|
const result = isCurrentSeason
|
||||||
|
? await getRatingsLeague(region, leagueIndex).then(async (result) =>
|
||||||
|
result.slice(0, limit).map(
|
||||||
|
(player) =>
|
||||||
|
({
|
||||||
|
id: player.spa_id,
|
||||||
|
score: player.score,
|
||||||
|
position: player.number,
|
||||||
|
clan: player.clan_tag,
|
||||||
|
nickname: player.nickname,
|
||||||
|
}) satisfies SimplifiedPlayer,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: await getArchivedRatingsLeaderboard(region, season!).then(
|
||||||
|
async (players) => {
|
||||||
|
const firstPlayerIndex =
|
||||||
|
leagueIndex === 0
|
||||||
|
? 0
|
||||||
|
: players.findIndex(
|
||||||
|
(player) =>
|
||||||
|
player.score < LEAGUES[leagueIndex - 1].minScore,
|
||||||
|
);
|
||||||
|
const lastPlayerIndex = firstPlayerIndex + limit - 1;
|
||||||
|
const trimmed = players.slice(
|
||||||
|
firstPlayerIndex,
|
||||||
|
lastPlayerIndex + 1,
|
||||||
|
);
|
||||||
|
const clanData = await getClanAccountInfo(
|
||||||
|
region,
|
||||||
|
trimmed.map(({ id }) => id),
|
||||||
|
['clan'],
|
||||||
|
);
|
||||||
|
|
||||||
|
return trimmed.map(
|
||||||
|
(player, index) =>
|
||||||
({
|
({
|
||||||
name: `${number}${index === seasonNumbers.length - 1 && onGoingSeason ? ' (current)' : ''}`,
|
id: player.id,
|
||||||
value: `${number}`,
|
score: player.score,
|
||||||
}) satisfies APIApplicationCommandOptionChoice<string>,
|
position: firstPlayerIndex + index + 1,
|
||||||
)
|
clan: clanData[player.id]?.clan?.tag,
|
||||||
.reverse(),
|
nickname:
|
||||||
),
|
clanData[index]?.account_name ?? `Deleted ${player.id}`,
|
||||||
);
|
}) satisfies SimplifiedPlayer,
|
||||||
}
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const leagueInfo = regionRatingsInfo.leagues[leagueIndex];
|
||||||
|
midnightLeaderboard = isCurrentSeason
|
||||||
|
? await getArchivedRatingsMidnightLeaderboard(
|
||||||
|
region,
|
||||||
|
regionRatingsInfo.current_season,
|
||||||
|
)
|
||||||
|
: await getArchivedRatingsMidnightLeaderboard(region, season!);
|
||||||
|
|
||||||
const command = new SlashCommandBuilder()
|
players = result;
|
||||||
.setName('ratings')
|
playersBefore = result ? result[0].position - 1 : 0;
|
||||||
.setDescription('Ratings battles statistics')
|
playersAfter =
|
||||||
.addSubcommand((option) =>
|
regionRatingsInfo.count - result[result.length - 1].position;
|
||||||
addSubcommands(
|
titleName = `${
|
||||||
option
|
leagueInfo ? leagueInfo.title : 'No Ongoing Season'
|
||||||
.setName('neighbors')
|
} - ${REGION_NAMES_SHORT[region]}`;
|
||||||
.setDescription('Shows neighboring players on the leaderboard'),
|
titleImage = leagueInfo.big_icon.startsWith('http')
|
||||||
),
|
? leagueInfo.big_icon
|
||||||
)
|
: `https:${leagueInfo.big_icon}`;
|
||||||
.addSubcommand((option) =>
|
titleDescription = `Top ${limit} players`;
|
||||||
addSubcommands(
|
} else {
|
||||||
option
|
const { region, id } = await resolvePlayerFromCommand(interaction);
|
||||||
.setName('league')
|
regionRatingsInfo = isCurrentSeason
|
||||||
.setDescription('Shows top players from a league')
|
? await getRatingsInfo(region)
|
||||||
.addStringOption(addRegionChoices)
|
: await getArchivedRatingsInfo(region, season!);
|
||||||
.addStringOption((option) =>
|
|
||||||
option
|
|
||||||
.setName('league')
|
|
||||||
.setDescription('The league to display')
|
|
||||||
.addChoices(
|
|
||||||
...LEAGUES.map(
|
|
||||||
({ name }, value) =>
|
|
||||||
({
|
|
||||||
name,
|
|
||||||
value: `${value}`,
|
|
||||||
}) satisfies APIApplicationCommandOptionChoice<string>,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.setRequired(true),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
resolve({
|
if (regionRatingsInfo.detail !== undefined) return noOngoingSeason;
|
||||||
inProduction: true,
|
|
||||||
inPublic: true,
|
|
||||||
|
|
||||||
command,
|
const accountInfo = await getAccountInfo(region, id);
|
||||||
|
const clan = (await getClanAccountInfo(region, id, ['clan']))?.clan;
|
||||||
async handler(interaction) {
|
const neighbors = isCurrentSeason
|
||||||
interface SimplifiedPlayer {
|
? await getRatingsNeighbors(region, id, Math.round(limit / 2)).then(
|
||||||
id: number;
|
(players) =>
|
||||||
score: number;
|
players.neighbors.map(
|
||||||
position: number;
|
|
||||||
clan: string | undefined;
|
|
||||||
nickname: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const subcommand = interaction.options.getSubcommand(true) as
|
|
||||||
| 'league'
|
|
||||||
| 'neighbors';
|
|
||||||
const limit = interaction.options.getInteger('limit') ?? 10;
|
|
||||||
const seasonOption = interaction.options.getString('season');
|
|
||||||
const season = seasonOption
|
|
||||||
? parseInt(seasonOption)
|
|
||||||
: onGoingSeason
|
|
||||||
? latestArchivedSeasonNumber + 1
|
|
||||||
: latestArchivedSeasonNumber;
|
|
||||||
const isCurrentSeason = season === latestArchivedSeasonNumber + 1;
|
|
||||||
let titleName: string;
|
|
||||||
let titleImage: string | undefined;
|
|
||||||
let titleDescription: string;
|
|
||||||
let playersBefore: number;
|
|
||||||
let playersAfter: number;
|
|
||||||
let players: SimplifiedPlayer[] | undefined;
|
|
||||||
let midnightLeaderboard: BlitzkriegRatingsLeaderboard | undefined;
|
|
||||||
let playerId: number | undefined;
|
|
||||||
let regionRatingsInfo: RatingsInfo;
|
|
||||||
|
|
||||||
if (subcommand === 'league') {
|
|
||||||
const leagueIndex = parseInt(
|
|
||||||
interaction.options.getString('league')!,
|
|
||||||
);
|
|
||||||
const region = interaction.options.getString('region') as Region;
|
|
||||||
regionRatingsInfo = await (isCurrentSeason
|
|
||||||
? getRatingsInfo(region)
|
|
||||||
: getArchivedRatingsInfo(region, season!));
|
|
||||||
|
|
||||||
if (regionRatingsInfo.detail !== undefined) return noOngoingSeason;
|
|
||||||
|
|
||||||
const result = isCurrentSeason
|
|
||||||
? await getRatingsLeague(region, leagueIndex).then(async (result) =>
|
|
||||||
result.slice(0, limit).map(
|
|
||||||
(player) =>
|
(player) =>
|
||||||
({
|
({
|
||||||
id: player.spa_id,
|
id: player.spa_id,
|
||||||
score: player.score,
|
|
||||||
position: player.number,
|
position: player.number,
|
||||||
|
score: player.score,
|
||||||
clan: player.clan_tag,
|
clan: player.clan_tag,
|
||||||
nickname: player.nickname,
|
nickname: player.nickname,
|
||||||
}) satisfies SimplifiedPlayer,
|
}) satisfies SimplifiedPlayer,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: await getArchivedRatingsLeaderboard(region, season!).then(
|
: await getArchivedRatingsLeaderboard(region, season!).then(
|
||||||
async (players) => {
|
async (players) => {
|
||||||
const firstPlayerIndex =
|
const playerIndex = players.findIndex(
|
||||||
leagueIndex === 0
|
(player) => player.id === id,
|
||||||
? 0
|
);
|
||||||
: players.findIndex(
|
|
||||||
(player) =>
|
if (playerIndex === -1) {
|
||||||
player.score < LEAGUES[leagueIndex - 1].minScore,
|
throw new UserError(
|
||||||
);
|
`# ${markdownEscape(accountInfo.nickname)} didn't play ratings in season ${season}\nThis player did not participate in this season or did not get past calibration.`,
|
||||||
const lastPlayerIndex = firstPlayerIndex + limit - 1;
|
|
||||||
const trimmed = players.slice(
|
|
||||||
firstPlayerIndex,
|
|
||||||
lastPlayerIndex + 1,
|
|
||||||
);
|
|
||||||
const clanData = await getClanAccountInfo(
|
|
||||||
region,
|
|
||||||
trimmed.map(({ id }) => id),
|
|
||||||
['clan'],
|
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return trimmed.map(
|
const halfRange = Math.round(limit / 2);
|
||||||
(player, index) =>
|
const trimmed = players.slice(
|
||||||
({
|
playerIndex - halfRange,
|
||||||
id: player.id,
|
playerIndex + halfRange + 1,
|
||||||
score: player.score,
|
);
|
||||||
position: firstPlayerIndex + index + 1,
|
const clanData = await getClanAccountInfo(
|
||||||
clan: clanData[player.id]?.clan?.tag,
|
region,
|
||||||
nickname:
|
trimmed.map((player) => player.id),
|
||||||
clanData[index]?.account_name ??
|
['clan'],
|
||||||
`Deleted ${player.id}`,
|
);
|
||||||
}) satisfies SimplifiedPlayer,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
const leagueInfo = regionRatingsInfo.leagues[leagueIndex];
|
|
||||||
midnightLeaderboard = isCurrentSeason
|
|
||||||
? await getArchivedRatingsMidnightLeaderboard(
|
|
||||||
region,
|
|
||||||
regionRatingsInfo.current_season,
|
|
||||||
)
|
|
||||||
: await getArchivedRatingsMidnightLeaderboard(region, season!);
|
|
||||||
|
|
||||||
players = result;
|
return trimmed.map((player, index) => ({
|
||||||
playersBefore = result ? result[0].position - 1 : 0;
|
id: player.id,
|
||||||
playersAfter =
|
score: player.score,
|
||||||
regionRatingsInfo.count - result[result.length - 1].position;
|
position: playerIndex + index + 1,
|
||||||
titleName = `${
|
clan: clanData[index]?.clan?.tag,
|
||||||
leagueInfo ? leagueInfo.title : 'No Ongoing Season'
|
nickname: clanData[index]
|
||||||
} - ${REGION_NAMES_SHORT[region]}`;
|
? clanData[index]!.account_name
|
||||||
titleImage = leagueInfo.big_icon.startsWith('http')
|
: `Deleted ${player.id}`,
|
||||||
? leagueInfo.big_icon
|
}));
|
||||||
: `https:${leagueInfo.big_icon}`;
|
},
|
||||||
titleDescription = `Top ${limit} players`;
|
);
|
||||||
} else {
|
midnightLeaderboard = isCurrentSeason
|
||||||
const { region, id } = await resolvePlayerFromCommand(interaction);
|
? await getArchivedRatingsMidnightLeaderboard(
|
||||||
regionRatingsInfo = isCurrentSeason
|
region,
|
||||||
? await getRatingsInfo(region)
|
regionRatingsInfo.current_season,
|
||||||
: await getArchivedRatingsInfo(region, season!);
|
)
|
||||||
|
: await getArchivedRatingsMidnightLeaderboard(region, season!);
|
||||||
|
|
||||||
if (regionRatingsInfo.detail !== undefined) return noOngoingSeason;
|
players = neighbors;
|
||||||
|
titleImage = clan
|
||||||
|
? `https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clan.emblem_set_id}.png`
|
||||||
|
: undefined;
|
||||||
|
titleName = neighbors
|
||||||
|
? accountInfo.nickname
|
||||||
|
: `No Ongoing Season - ${REGION_NAMES_SHORT[region]}`;
|
||||||
|
titleDescription = 'Ratings neighbors';
|
||||||
|
playersBefore = neighbors ? neighbors[0].position - 1 : 0;
|
||||||
|
playersAfter =
|
||||||
|
regionRatingsInfo.count - neighbors[neighbors.length - 1].position;
|
||||||
|
playerId = id;
|
||||||
|
}
|
||||||
|
|
||||||
const accountInfo = await getAccountInfo(region, id);
|
const resolvedRegionRatingsInfo: typeof regionRatingsInfo & {
|
||||||
const clan = (await getClanAccountInfo(region, id, ['clan']))?.clan;
|
detail: undefined;
|
||||||
const neighbors = isCurrentSeason
|
} = regionRatingsInfo;
|
||||||
? await getRatingsNeighbors(region, id, Math.round(limit / 2)).then(
|
|
||||||
(players) =>
|
|
||||||
players.neighbors.map(
|
|
||||||
(player) =>
|
|
||||||
({
|
|
||||||
id: player.spa_id,
|
|
||||||
position: player.number,
|
|
||||||
score: player.score,
|
|
||||||
clan: player.clan_tag,
|
|
||||||
nickname: player.nickname,
|
|
||||||
}) satisfies SimplifiedPlayer,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: await getArchivedRatingsLeaderboard(region, season!).then(
|
|
||||||
async (players) => {
|
|
||||||
const playerIndex = players.findIndex(
|
|
||||||
(player) => player.id === id,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (playerIndex === -1) {
|
const items = players.map((player) => {
|
||||||
throw new UserError(
|
const reward = resolvedRegionRatingsInfo.rewards.find(
|
||||||
`# ${markdownEscape(accountInfo.nickname)} didn't play ratings in season ${season}\nThis player did not participate in this season or did not get past calibration.`,
|
(reward) =>
|
||||||
);
|
player.position >= reward.from_position &&
|
||||||
}
|
player.position <= reward.to_position,
|
||||||
|
);
|
||||||
const halfRange = Math.round(limit / 2);
|
const midnightIndex = midnightLeaderboard?.findIndex(
|
||||||
const trimmed = players.slice(
|
(item) => item.id === player.id,
|
||||||
playerIndex - halfRange,
|
);
|
||||||
playerIndex + halfRange + 1,
|
const midnightScore =
|
||||||
);
|
midnightIndex === undefined
|
||||||
const clanData = await getClanAccountInfo(
|
? player.score
|
||||||
region,
|
: midnightLeaderboard![midnightIndex].score;
|
||||||
trimmed.map((player) => player.id),
|
const midnightPosition =
|
||||||
['clan'],
|
midnightIndex === undefined ? player.position : midnightIndex + 1;
|
||||||
);
|
|
||||||
|
|
||||||
return trimmed.map((player, index) => ({
|
|
||||||
id: player.id,
|
|
||||||
score: player.score,
|
|
||||||
position: playerIndex + index + 1,
|
|
||||||
clan: clanData[index]?.clan?.tag,
|
|
||||||
nickname: clanData[index]
|
|
||||||
? clanData[index]!.account_name
|
|
||||||
: `Deleted ${player.id}`,
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
);
|
|
||||||
midnightLeaderboard = isCurrentSeason
|
|
||||||
? await getArchivedRatingsMidnightLeaderboard(
|
|
||||||
region,
|
|
||||||
regionRatingsInfo.current_season,
|
|
||||||
)
|
|
||||||
: await getArchivedRatingsMidnightLeaderboard(region, season!);
|
|
||||||
|
|
||||||
players = neighbors;
|
|
||||||
titleImage = clan
|
|
||||||
? `https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clan.emblem_set_id}.png`
|
|
||||||
: undefined;
|
|
||||||
titleName = neighbors
|
|
||||||
? accountInfo.nickname
|
|
||||||
: `No Ongoing Season - ${REGION_NAMES_SHORT[region]}`;
|
|
||||||
titleDescription = 'Ratings neighbors';
|
|
||||||
playersBefore = neighbors ? neighbors[0].position - 1 : 0;
|
|
||||||
playersAfter =
|
|
||||||
regionRatingsInfo.count - neighbors[neighbors.length - 1].position;
|
|
||||||
playerId = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
const resolvedRegionRatingsInfo: typeof regionRatingsInfo & {
|
|
||||||
detail: undefined;
|
|
||||||
} = regionRatingsInfo;
|
|
||||||
|
|
||||||
const items = players.map((player) => {
|
|
||||||
const reward = resolvedRegionRatingsInfo.rewards.find(
|
|
||||||
(reward) =>
|
|
||||||
player.position >= reward.from_position &&
|
|
||||||
player.position <= reward.to_position,
|
|
||||||
);
|
|
||||||
const midnightIndex = midnightLeaderboard?.findIndex(
|
|
||||||
(item) => item.id === player.id,
|
|
||||||
);
|
|
||||||
const midnightScore =
|
|
||||||
midnightIndex === undefined
|
|
||||||
? player.score
|
|
||||||
: midnightLeaderboard![midnightIndex].score;
|
|
||||||
const midnightPosition =
|
|
||||||
midnightIndex === undefined ? player.position : midnightIndex + 1;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Leaderboard.Item
|
|
||||||
nickname={player.nickname}
|
|
||||||
score={player.score}
|
|
||||||
position={player.position}
|
|
||||||
clan={player.clan}
|
|
||||||
reward={reward}
|
|
||||||
deltaScore={player.score - midnightScore}
|
|
||||||
deltaPosition={midnightPosition - player.position}
|
|
||||||
key={player.id}
|
|
||||||
highlight={player.id === playerId}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CommandWrapper>
|
<Leaderboard.Item
|
||||||
<TitleBar
|
nickname={player.nickname}
|
||||||
title={titleName}
|
score={player.score}
|
||||||
image={titleImage}
|
position={player.position}
|
||||||
description={titleDescription}
|
clan={player.clan}
|
||||||
/>
|
reward={reward}
|
||||||
|
deltaScore={player.score - midnightScore}
|
||||||
{items && (
|
deltaPosition={midnightPosition - player.position}
|
||||||
<Leaderboard.Root>
|
key={player.id}
|
||||||
{playersBefore > 0 && (
|
highlight={player.id === playerId}
|
||||||
<Leaderboard.Gap
|
/>
|
||||||
message={`+ ${playersBefore.toLocaleString()} more`}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{items}
|
|
||||||
{playersAfter > 0 && (
|
|
||||||
<Leaderboard.Gap
|
|
||||||
message={`+ ${playersAfter.toLocaleString()} more`}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Leaderboard.Root>
|
|
||||||
)}
|
|
||||||
</CommandWrapper>
|
|
||||||
);
|
);
|
||||||
},
|
});
|
||||||
|
|
||||||
autocomplete: autocompleteUsername,
|
return (
|
||||||
});
|
<CommandWrapper>
|
||||||
},
|
<TitleBar
|
||||||
);
|
title={titleName}
|
||||||
|
image={titleImage}
|
||||||
|
description={titleDescription}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{items && (
|
||||||
|
<Leaderboard.Root>
|
||||||
|
{playersBefore > 0 && (
|
||||||
|
<Leaderboard.Gap
|
||||||
|
message={`+ ${playersBefore.toLocaleString()} more`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{items}
|
||||||
|
{playersAfter > 0 && (
|
||||||
|
<Leaderboard.Gap
|
||||||
|
message={`+ ${playersAfter.toLocaleString()} more`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Leaderboard.Root>
|
||||||
|
)}
|
||||||
|
</CommandWrapper>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
autocomplete: autocompleteUsername,
|
||||||
|
});
|
||||||
|
});
|
||||||
+36
-32
@@ -9,38 +9,42 @@ 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) => {
|
||||||
inProduction: true,
|
resolve({
|
||||||
inPublic: true,
|
inProduction: true,
|
||||||
|
inPublic: true,
|
||||||
|
|
||||||
command: new SlashCommandBuilder()
|
command: new SlashCommandBuilder()
|
||||||
.setName('search-clans')
|
.setName('search-clans')
|
||||||
.setDescription('Search clans')
|
.setDescription('Search clans')
|
||||||
.addStringOption(addRegionChoices)
|
.addStringOption(addRegionChoices)
|
||||||
.addStringOption((option) => addClanChoices(option).setAutocomplete(false))
|
.addStringOption((option) =>
|
||||||
.addIntegerOption((option) =>
|
addClanChoices(option).setAutocomplete(false),
|
||||||
option
|
)
|
||||||
.setName('limit')
|
.addIntegerOption((option) =>
|
||||||
.setDescription('The size of the search result (default: 25)')
|
option
|
||||||
.setMinValue(1)
|
.setName('limit')
|
||||||
.setMaxValue(100),
|
.setDescription('The size of the search result (default: 25)')
|
||||||
),
|
.setMinValue(1)
|
||||||
|
.setMaxValue(100),
|
||||||
|
),
|
||||||
|
|
||||||
async handler(interaction) {
|
async handler(interaction) {
|
||||||
const server = interaction.options.getString('region') as Region;
|
const server = interaction.options.getString('region') as Region;
|
||||||
const clan = interaction.options.getString('clan')!;
|
const clan = interaction.options.getString('clan')!;
|
||||||
const limit = interaction.options.getInteger('limit') ?? 25;
|
const limit = interaction.options.getInteger('limit') ?? 25;
|
||||||
const clanList = await fetchBlitz<ClanList>(
|
const clanList = await fetchBlitz<ClanList>(
|
||||||
`https://api.wotblitz.${server}/wotb/clans/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${clan}&limit=${limit}`,
|
`https://api.wotblitz.${server}/wotb/clans/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${clan}&limit=${limit}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
return embedInfo(
|
return embedInfo(
|
||||||
`Clan search for "${markdownEscape(clan)}" in ${REGION_NAMES[server]}`,
|
`Clan search for "${markdownEscape(clan)}" in ${REGION_NAMES[server]}`,
|
||||||
clanList.length === 0
|
clanList.length === 0
|
||||||
? 'No clans found.'
|
? 'No clans found.'
|
||||||
: `\`\`\`\n${clanList
|
: `\`\`\`\n${clanList
|
||||||
.map((clan) => `${clan.name} [${clan.tag}]`)
|
.map((clan) => `${clan.name} [${clan.tag}]`)
|
||||||
.join('\n')}\n\`\`\``,
|
.join('\n')}\n\`\`\``,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
});
|
||||||
@@ -12,45 +12,47 @@ 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) => {
|
||||||
inProduction: true,
|
resolve({
|
||||||
inPublic: true,
|
inProduction: true,
|
||||||
|
inPublic: true,
|
||||||
|
|
||||||
command: new SlashCommandBuilder()
|
command: new SlashCommandBuilder()
|
||||||
.setName('search-players')
|
.setName('search-players')
|
||||||
.setDescription('Search players in a Blitz server')
|
.setDescription('Search players in a Blitz server')
|
||||||
.addStringOption(addRegionChoices)
|
.addStringOption(addRegionChoices)
|
||||||
.addStringOption((option) =>
|
.addStringOption((option) =>
|
||||||
addUsernameChoices(option).setAutocomplete(false).setRequired(true),
|
addUsernameChoices(option).setAutocomplete(false).setRequired(true),
|
||||||
)
|
)
|
||||||
.addIntegerOption((option) =>
|
.addIntegerOption((option) =>
|
||||||
option
|
option
|
||||||
.setName('limit')
|
.setName('limit')
|
||||||
.setDescription('The size of the search result (default: 25)')
|
.setDescription('The size of the search result (default: 25)')
|
||||||
.setMinValue(1)
|
.setMinValue(1)
|
||||||
.setMaxValue(100),
|
.setMaxValue(100),
|
||||||
),
|
),
|
||||||
|
|
||||||
async handler(interaction) {
|
async handler(interaction) {
|
||||||
const server = interaction.options.getString('region') as Region;
|
const server = interaction.options.getString('region') as Region;
|
||||||
const name = interaction.options.getString('username')!;
|
const name = interaction.options.getString('username')!;
|
||||||
const limit = interaction.options.getInteger('limit') ?? 25;
|
const limit = interaction.options.getInteger('limit') ?? 25;
|
||||||
const trimmedSearch = name.trim();
|
const trimmedSearch = name.trim();
|
||||||
const players = usernamePattern.test(trimmedSearch)
|
const players = usernamePattern.test(trimmedSearch)
|
||||||
? await fetchBlitz<AccountList>(
|
? await fetchBlitz<AccountList>(
|
||||||
`https://api.wotblitz.${server}/wotb/account/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${encodeURIComponent(
|
`https://api.wotblitz.${server}/wotb/account/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${encodeURIComponent(
|
||||||
trimmedSearch,
|
trimmedSearch,
|
||||||
)}&limit=${limit}`,
|
)}&limit=${limit}`,
|
||||||
)
|
)
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
return embedInfo(
|
return embedInfo(
|
||||||
`Player search for "${markdownEscape(name)}" in ${REGION_NAMES[server]}`,
|
`Player search for "${markdownEscape(name)}" in ${REGION_NAMES[server]}`,
|
||||||
`\`\`\`${
|
`\`\`\`${
|
||||||
players.length === 0
|
players.length === 0
|
||||||
? 'No players found.'
|
? 'No players found.'
|
||||||
: players.map((player) => player.nickname).join('\n')
|
: players.map((player) => player.nickname).join('\n')
|
||||||
}\`\`\``,
|
}\`\`\``,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
});
|
||||||
+33
-29
@@ -6,35 +6,39 @@ 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) => {
|
||||||
inProduction: true,
|
resolve({
|
||||||
inPublic: true,
|
inProduction: 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) =>
|
||||||
.addIntegerOption((option) =>
|
addTankChoices(option).setAutocomplete(false),
|
||||||
option
|
)
|
||||||
.setName('limit')
|
.addIntegerOption((option) =>
|
||||||
.setDescription('The size of the search result (default: 25)')
|
option
|
||||||
.setMinValue(1)
|
.setName('limit')
|
||||||
.setMaxValue(100),
|
.setDescription('The size of the search result (default: 25)')
|
||||||
),
|
.setMinValue(1)
|
||||||
|
.setMaxValue(100),
|
||||||
|
),
|
||||||
|
|
||||||
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, await tankNames, {
|
const results = go(tank, await tankNames, {
|
||||||
limit,
|
limit,
|
||||||
keys: ['combined'],
|
keys: ['combined'],
|
||||||
}).map((result) => result.obj.original);
|
}).map((result) => result.obj.original);
|
||||||
|
|
||||||
return embedInfo(
|
return embedInfo(
|
||||||
`Tank search for "${markdownEscape(tank)}"`,
|
`Tank search for "${markdownEscape(tank)}"`,
|
||||||
results.length === 0
|
results.length === 0
|
||||||
? 'No tanks found.'
|
? 'No tanks found.'
|
||||||
: `\`\`\`\n${results.join('\n')}\n\`\`\``,
|
: `\`\`\`\n${results.join('\n')}\n\`\`\``,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
});
|
||||||
+39
-41
@@ -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,50 +57,48 @@ 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')
|
.setDescription('Regular battles statistics'),
|
||||||
.setDescription('Regular battles statistics'),
|
(option) => option.addStringOption(addUsernameChoices),
|
||||||
(option) => option.addStringOption(addUsernameChoices),
|
);
|
||||||
);
|
|
||||||
|
|
||||||
resolve({
|
resolve({
|
||||||
inProduction: true,
|
inProduction: true,
|
||||||
inPublic: true,
|
inPublic: true,
|
||||||
|
|
||||||
command,
|
command,
|
||||||
|
|
||||||
async handler(interaction) {
|
async handler(interaction) {
|
||||||
const player = await resolvePlayerFromCommand(interaction);
|
const player = await resolvePlayerFromCommand(interaction);
|
||||||
const period = resolvePeriodFromCommand(player.region, interaction);
|
const period = resolvePeriodFromCommand(player.region, interaction);
|
||||||
const filters = await getFiltersFromCommand(interaction);
|
const filters = await getFiltersFromCommand(interaction);
|
||||||
const path = commandToURL(interaction, {
|
const path = commandToURL(interaction, {
|
||||||
...player,
|
...player,
|
||||||
...getCustomPeriodParams(interaction),
|
...getCustomPeriodParams(interaction),
|
||||||
...filters,
|
...filters,
|
||||||
});
|
});
|
||||||
|
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
render(player, period, filters),
|
render(player, period, filters),
|
||||||
buttonRefresh(interaction, path),
|
buttonRefresh(interaction, path),
|
||||||
getBlitzStarsLinkButton(player.region, player.id),
|
getBlitzStarsLinkButton(player.region, player.id),
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
|
||||||
autocomplete: (interaction) => {
|
autocomplete: (interaction) => {
|
||||||
autocompleteUsername(interaction);
|
autocompleteUsername(interaction);
|
||||||
autocompleteTanks(interaction);
|
autocompleteTanks(interaction);
|
||||||
},
|
},
|
||||||
|
|
||||||
async button(interaction) {
|
async button(interaction) {
|
||||||
const player = await resolvePlayerFromButton(interaction);
|
const player = await resolvePlayerFromButton(interaction);
|
||||||
const period = resolvePeriodFromButton(player.region, interaction);
|
const period = resolvePeriodFromButton(player.region, interaction);
|
||||||
const filters = getFiltersFromButton(interaction);
|
const filters = getFiltersFromButton(interaction);
|
||||||
|
|
||||||
return await render(player, period, filters);
|
return await render(player, period, filters);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
});
|
||||||
);
|
|
||||||
+44
-46
@@ -14,58 +14,56 @@ 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()
|
.setName('today')
|
||||||
.setName('today')
|
.setDescription("Today's played tanks and statistics"),
|
||||||
.setDescription("Today's played tanks and statistics"),
|
awaitedEncyclopediaInfo,
|
||||||
awaitedEncyclopediaInfo,
|
).addStringOption(addUsernameChoices);
|
||||||
).addStringOption(addUsernameChoices);
|
|
||||||
|
|
||||||
resolve({
|
resolve({
|
||||||
inProduction: true,
|
inProduction: true,
|
||||||
inPublic: true,
|
inPublic: true,
|
||||||
|
|
||||||
command,
|
command,
|
||||||
|
|
||||||
async handler(interaction) {
|
async handler(interaction) {
|
||||||
const player = await resolvePlayerFromCommand(interaction);
|
const player = await resolvePlayerFromCommand(interaction);
|
||||||
const period = resolvePeriodFromCommand(
|
const period = resolvePeriodFromCommand(
|
||||||
player.region,
|
player.region,
|
||||||
interaction,
|
interaction,
|
||||||
'today',
|
'today',
|
||||||
);
|
);
|
||||||
const filters = await getFiltersFromCommand(interaction);
|
const filters = await getFiltersFromCommand(interaction);
|
||||||
const path = commandToURL(interaction, {
|
const path = commandToURL(interaction, {
|
||||||
...player,
|
...player,
|
||||||
...getCustomPeriodParams(interaction, true),
|
...getCustomPeriodParams(interaction, true),
|
||||||
...filters,
|
...filters,
|
||||||
});
|
});
|
||||||
|
|
||||||
return [
|
return [
|
||||||
...(await renderBreakdown(player, period, filters)),
|
...(await renderBreakdown(player, period, filters)),
|
||||||
buttonPrimary(path, 'Refresh'),
|
buttonPrimary(path, 'Refresh'),
|
||||||
await getBlitzStarsLinkButton(player.region, player.id),
|
await getBlitzStarsLinkButton(player.region, player.id),
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
|
||||||
autocomplete: (interaction) => {
|
autocomplete: (interaction) => {
|
||||||
autocompleteUsername(interaction);
|
autocompleteUsername(interaction);
|
||||||
autocompleteTanks(interaction);
|
autocompleteTanks(interaction);
|
||||||
},
|
},
|
||||||
|
|
||||||
async button(interaction) {
|
async button(interaction) {
|
||||||
const player = await resolvePlayerFromButton(interaction);
|
const player = await resolvePlayerFromButton(interaction);
|
||||||
const period = resolvePeriodFromButton(player.region, interaction);
|
const period = resolvePeriodFromButton(player.region, interaction);
|
||||||
const filters = getFiltersFromButton(interaction);
|
const filters = getFiltersFromButton(interaction);
|
||||||
|
|
||||||
return await renderBreakdown(player, period, filters);
|
return await renderBreakdown(player, period, filters);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
});
|
||||||
);
|
|
||||||
@@ -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) => {
|
||||||
const subcommandNameLocalizations = localizationObject(
|
if ('subcommand' in subItem) {
|
||||||
`bot.commands.${command}.subcommands.${subcommand}`,
|
const subcommandNameLocalizations = localizationObject(
|
||||||
);
|
`bot.commands.${command}.subcommands.${subItem.subcommand}`,
|
||||||
const subcommandDescriptionLocalizations = localizationObject(
|
);
|
||||||
`bot.commands.${command}.subcommands.${subcommand}.description`,
|
const subcommandDescriptionLocalizations = localizationObject(
|
||||||
);
|
`bot.commands.${command}.subcommands.${subItem.subcommand}.description`,
|
||||||
|
);
|
||||||
|
|
||||||
slashCommand.addSubcommand((option) =>
|
slashCommand.addSubcommand((option) => {
|
||||||
option
|
if (subItem.modify) subItem.modify(option);
|
||||||
.setName(subcommand)
|
|
||||||
.setNameLocalizations(subcommandNameLocalizations)
|
return option
|
||||||
.setDescription(
|
.setName(subItem.subcommand)
|
||||||
translate(
|
.setNameLocalizations(subcommandNameLocalizations)
|
||||||
`bot.commands.${command}.subcommands.${subcommand}.description`,
|
.setDescription(
|
||||||
),
|
translate(
|
||||||
)
|
`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);
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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[]) {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in new issue
Block a user