mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 20:19:49 +02:00
feat: better subcommand organiztion
This commit is contained in:
1 parent
2cabcc57f4
commit
23dd088466
15 files changed
+749
-867
No files matched your search
@@ -29,4 +29,4 @@ jobs:
|
|||||||
unzip dist.zip
|
unzip dist.zip
|
||||||
|
|
||||||
- name: Run bot
|
- name: Run bot
|
||||||
run: NODE_ENV=production GH_TOKEN=${{ secrets.GH_TOKEN }} DISCORD_TOKEN=${{ secrets.DISCORD_TOKEN }} WARGAMING_APPLICATION_ID=${{ secrets.WARGAMING_APPLICATION_ID }} node --enable-source-maps --trace-warnings bot.cjs
|
run: NODE_ENV=production GH_TOKEN=${{ secrets.GH_TOKEN }} DISCORD_TOKEN=${{ secrets.DISCORD_TOKEN }} WARGAMING_APPLICATION_ID=${{ secrets.WARGAMING_APPLICATION_ID }} node --enable-source-maps --trace-warnings bot.js
|
||||||
+3
-2
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"main": "server.cjs",
|
"main": "server.js",
|
||||||
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node --enable-source-maps --trace-warnings server.cjs"
|
"start": "node --enable-source-maps --trace-warnings server.js"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-1
@@ -27,7 +27,7 @@
|
|||||||
"@radix-ui/colors": "^0.1.8",
|
"@radix-ui/colors": "^0.1.8",
|
||||||
"@resvg/resvg-js": "^2.4.1",
|
"@resvg/resvg-js": "^2.4.1",
|
||||||
"bepaint": "^0.1.0-beta.3",
|
"bepaint": "^0.1.0-beta.3",
|
||||||
"discord.js": "^14.9.0",
|
"discord.js": "^14.12.0",
|
||||||
"esbuild": "^0.18.10",
|
"esbuild": "^0.18.10",
|
||||||
"escape-html": "^1.0.3",
|
"escape-html": "^1.0.3",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
|
|||||||
+5
-4
@@ -1,5 +1,5 @@
|
|||||||
import { BuildOptions, build } from 'esbuild';
|
import { BuildOptions, build } from 'esbuild';
|
||||||
import { copyFile, mkdir, rm } from 'fs/promises';
|
import { copyFile, mkdirSync, rmSync } from 'fs';
|
||||||
import { argv } from 'process';
|
import { argv } from 'process';
|
||||||
|
|
||||||
const isProd = !argv.includes('--dev');
|
const isProd = !argv.includes('--dev');
|
||||||
@@ -10,15 +10,15 @@ const buildServer = argv.includes('--build=server') || buildAll;
|
|||||||
if (isProd) {
|
if (isProd) {
|
||||||
// only remove in production to ensure no time is wasted in dev
|
// only remove in production to ensure no time is wasted in dev
|
||||||
console.log('Removing dist...');
|
console.log('Removing dist...');
|
||||||
await rm('dist', { recursive: true, force: true });
|
rmSync('dist', { recursive: true, force: true });
|
||||||
console.log('Dist removed');
|
console.log('Dist removed');
|
||||||
|
|
||||||
console.log('Creating dist...');
|
console.log('Creating dist...');
|
||||||
await mkdir('dist');
|
mkdirSync('dist');
|
||||||
console.log('Dist created');
|
console.log('Dist created');
|
||||||
|
|
||||||
console.log('Copying package.json...');
|
console.log('Copying package.json...');
|
||||||
copyFile('package.dist.json', 'dist/package.json').then(() =>
|
copyFile('package.dist.json', 'dist/package.json', () =>
|
||||||
console.log('package.json copied'),
|
console.log('package.json copied'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -29,6 +29,7 @@ const commonOptions: BuildOptions = {
|
|||||||
'.node': 'copy',
|
'.node': 'copy',
|
||||||
'.ttf': 'file',
|
'.ttf': 'file',
|
||||||
},
|
},
|
||||||
|
format: 'cjs',
|
||||||
|
|
||||||
bundle: true,
|
bundle: true,
|
||||||
sourcemap: true,
|
sourcemap: true,
|
||||||
|
|||||||
+72
-66
@@ -13,75 +13,81 @@ 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 { secrets } from '../core/node/secrets';
|
import { secrets } from '../core/node/secrets';
|
||||||
import { CommandRegistry } from '../events/interactionCreate';
|
import { CommandRegistryRaw } from '../events/interactionCreate';
|
||||||
import evolution from '../renderers/evolution';
|
import evolution from '../renderers/evolution';
|
||||||
import { StatType } from '../renderers/fullStats';
|
import { StatType } from '../renderers/fullStats';
|
||||||
import { AccountInfo } from '../types/accountInfo';
|
import { AccountInfo } from '../types/accountInfo';
|
||||||
|
|
||||||
export const evolutionCommand: CommandRegistry = {
|
export const evolutionCommand = new Promise<CommandRegistryRaw>(
|
||||||
inProduction: true,
|
async (resolve) => {
|
||||||
inDevelopment: false,
|
const command = await addStatTypeSubCommandGroups(
|
||||||
inPublic: true,
|
new SlashCommandBuilder()
|
||||||
|
.setName('evolution')
|
||||||
command: addStatTypeSubCommandGroups(
|
.setDescription('Evolution of statistics'),
|
||||||
new SlashCommandBuilder()
|
|
||||||
.setName('evolution')
|
|
||||||
.setDescription('Evolution of statistics'),
|
|
||||||
),
|
|
||||||
|
|
||||||
async handler(interaction) {
|
|
||||||
const commandGroup = interaction.options.getSubcommandGroup(
|
|
||||||
true,
|
|
||||||
) as StatType;
|
|
||||||
const player = await resolvePlayerFromCommand(interaction);
|
|
||||||
const period = resolvePeriodFromCommand(player.region, interaction);
|
|
||||||
const tankIdRaw = interaction.options.getString('tank')!;
|
|
||||||
const tankId =
|
|
||||||
commandGroup === 'tank' ? await resolveTankId(tankIdRaw) : null;
|
|
||||||
const start = interaction.options.getInteger('start');
|
|
||||||
const end = interaction.options.getInteger('end');
|
|
||||||
const { nickname } = (
|
|
||||||
await getWargamingResponse<AccountInfo>(
|
|
||||||
`https://api.wotblitz.${player.region}/wotb/account/info/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${player.id}`,
|
|
||||||
)
|
|
||||||
)[player.id];
|
|
||||||
const path = interactionToURL(interaction, {
|
|
||||||
...player,
|
|
||||||
tankId,
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
});
|
|
||||||
|
|
||||||
return [
|
|
||||||
await evolution(commandGroup, period, player, tankId),
|
|
||||||
primaryButton(path, 'Refresh'),
|
|
||||||
// linkButton(`${CYCLIC_API}/${path}`, 'Embed'),
|
|
||||||
linkButton(
|
|
||||||
`https://www.blitzstars.com/player/${player.region}/${nickname}${
|
|
||||||
commandGroup === 'tank' ? `/tank/${tankId!}` : ''
|
|
||||||
}`,
|
|
||||||
'BlitzStars',
|
|
||||||
),
|
|
||||||
];
|
|
||||||
},
|
|
||||||
|
|
||||||
autocomplete: (interaction) => {
|
|
||||||
autocompleteUsername(interaction);
|
|
||||||
autocompleteTanks(interaction);
|
|
||||||
},
|
|
||||||
|
|
||||||
async button(interaction) {
|
|
||||||
const url = new URL(`${CYCLIC_API}/${interaction.customId}`);
|
|
||||||
const path = url.pathname.split('/').filter(Boolean);
|
|
||||||
const commandGroup = path[1] as StatType;
|
|
||||||
const player = await resolvePlayerFromButton(interaction);
|
|
||||||
const period = resolvePeriodFromButton(player.region, interaction);
|
|
||||||
|
|
||||||
return await evolution(
|
|
||||||
commandGroup,
|
|
||||||
period,
|
|
||||||
player,
|
|
||||||
parseInt(url.searchParams.get('tankId')!),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
resolve({
|
||||||
|
inProduction: true,
|
||||||
|
inDevelopment: false,
|
||||||
|
inPublic: true,
|
||||||
|
|
||||||
|
command,
|
||||||
|
|
||||||
|
async handler(interaction) {
|
||||||
|
const commandGroup = interaction.options.getSubcommandGroup(
|
||||||
|
true,
|
||||||
|
) as StatType;
|
||||||
|
const player = await resolvePlayerFromCommand(interaction);
|
||||||
|
const period = resolvePeriodFromCommand(player.region, interaction);
|
||||||
|
const tankIdRaw = interaction.options.getString('tank')!;
|
||||||
|
const tankId =
|
||||||
|
commandGroup === 'tank' ? await resolveTankId(tankIdRaw) : null;
|
||||||
|
const start = interaction.options.getInteger('start');
|
||||||
|
const end = interaction.options.getInteger('end');
|
||||||
|
const { nickname } = (
|
||||||
|
await getWargamingResponse<AccountInfo>(
|
||||||
|
`https://api.wotblitz.${player.region}/wotb/account/info/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${player.id}`,
|
||||||
|
)
|
||||||
|
)[player.id];
|
||||||
|
const path = interactionToURL(interaction, {
|
||||||
|
...player,
|
||||||
|
tankId,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
});
|
||||||
|
|
||||||
|
return [
|
||||||
|
await evolution(commandGroup, period, player, tankId),
|
||||||
|
primaryButton(path, 'Refresh'),
|
||||||
|
// linkButton(`${CYCLIC_API}/${path}`, 'Embed'),
|
||||||
|
linkButton(
|
||||||
|
`https://www.blitzstars.com/player/${player.region}/${nickname}${
|
||||||
|
commandGroup === 'tank' ? `/tank/${tankId!}` : ''
|
||||||
|
}`,
|
||||||
|
'BlitzStars',
|
||||||
|
),
|
||||||
|
];
|
||||||
|
},
|
||||||
|
|
||||||
|
autocomplete: (interaction) => {
|
||||||
|
autocompleteUsername(interaction);
|
||||||
|
autocompleteTanks(interaction);
|
||||||
|
},
|
||||||
|
|
||||||
|
async button(interaction) {
|
||||||
|
const url = new URL(`${CYCLIC_API}/${interaction.customId}`);
|
||||||
|
const path = url.pathname.split('/').filter(Boolean);
|
||||||
|
const commandGroup = path[1] as StatType;
|
||||||
|
const player = await resolvePlayerFromButton(interaction);
|
||||||
|
const period = resolvePeriodFromButton(player.region, interaction);
|
||||||
|
|
||||||
|
return await evolution(
|
||||||
|
commandGroup,
|
||||||
|
period,
|
||||||
|
player,
|
||||||
|
parseInt(url.searchParams.get('tankId')!),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
},
|
},
|
||||||
};
|
);
|
||||||
+70
-64
@@ -13,72 +13,78 @@ 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 { secrets } from '../core/node/secrets';
|
import { secrets } from '../core/node/secrets';
|
||||||
import { CommandRegistry } from '../events/interactionCreate';
|
import { CommandRegistryRaw } from '../events/interactionCreate';
|
||||||
import fullStats, { StatType } from '../renderers/fullStats';
|
import fullStats, { StatType } from '../renderers/fullStats';
|
||||||
import { AccountInfo } from '../types/accountInfo';
|
import { AccountInfo } from '../types/accountInfo';
|
||||||
|
|
||||||
export const fullStatsCommand: CommandRegistry = {
|
export const fullStatsCommand = new Promise<CommandRegistryRaw>(
|
||||||
inProduction: true,
|
async (resolve) => {
|
||||||
inDevelopment: false,
|
const command = await addStatTypeSubCommandGroups(
|
||||||
inPublic: true,
|
new SlashCommandBuilder()
|
||||||
|
.setName('fullstats')
|
||||||
command: addStatTypeSubCommandGroups(
|
.setDescription('Full in-game statistics'),
|
||||||
new SlashCommandBuilder()
|
|
||||||
.setName('fullstats')
|
|
||||||
.setDescription('Full in-game statistics'),
|
|
||||||
),
|
|
||||||
|
|
||||||
async handler(interaction) {
|
|
||||||
const commandGroup = interaction.options.getSubcommandGroup(
|
|
||||||
true,
|
|
||||||
) as StatType;
|
|
||||||
const player = await resolvePlayerFromCommand(interaction);
|
|
||||||
const period = resolvePeriodFromCommand(player.region, interaction);
|
|
||||||
const tankIdRaw = interaction.options.getString('tank')!;
|
|
||||||
const tankId =
|
|
||||||
commandGroup === 'tank' ? await resolveTankId(tankIdRaw) : null;
|
|
||||||
const start = interaction.options.getInteger('start');
|
|
||||||
const end = interaction.options.getInteger('end');
|
|
||||||
const path = interactionToURL(interaction, {
|
|
||||||
...player,
|
|
||||||
tankId,
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
});
|
|
||||||
const { nickname } = (
|
|
||||||
await getWargamingResponse<AccountInfo>(
|
|
||||||
`https://api.wotblitz.${player.region}/wotb/account/info/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${player.id}`,
|
|
||||||
)
|
|
||||||
)[player.id];
|
|
||||||
|
|
||||||
return [
|
|
||||||
await fullStats(commandGroup, period, player, tankId),
|
|
||||||
primaryButton(path, 'Refresh'),
|
|
||||||
// linkButton(`${CYCLIC_API}/${path}`, 'Embed'),
|
|
||||||
linkButton(
|
|
||||||
`https://www.blitzstars.com/player/${player.region}/${nickname}`,
|
|
||||||
'BlitzStars',
|
|
||||||
),
|
|
||||||
];
|
|
||||||
},
|
|
||||||
|
|
||||||
autocomplete: (interaction) => {
|
|
||||||
autocompleteUsername(interaction);
|
|
||||||
autocompleteTanks(interaction);
|
|
||||||
},
|
|
||||||
|
|
||||||
async button(interaction) {
|
|
||||||
const url = new URL(`${CYCLIC_API}/${interaction.customId}`);
|
|
||||||
const path = url.pathname.split('/').filter(Boolean);
|
|
||||||
const commandGroup = path[1] as StatType;
|
|
||||||
const player = await resolvePlayerFromButton(interaction);
|
|
||||||
const period = resolvePeriodFromButton(player.region, interaction);
|
|
||||||
|
|
||||||
return await fullStats(
|
|
||||||
commandGroup,
|
|
||||||
period,
|
|
||||||
player,
|
|
||||||
parseInt(url.searchParams.get('tankId')!),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
resolve({
|
||||||
|
inProduction: true,
|
||||||
|
inDevelopment: false,
|
||||||
|
inPublic: true,
|
||||||
|
|
||||||
|
command,
|
||||||
|
|
||||||
|
async handler(interaction) {
|
||||||
|
const commandGroup = interaction.options.getSubcommandGroup(
|
||||||
|
true,
|
||||||
|
) as StatType;
|
||||||
|
const player = await resolvePlayerFromCommand(interaction);
|
||||||
|
const period = resolvePeriodFromCommand(player.region, interaction);
|
||||||
|
const tankIdRaw = interaction.options.getString('tank')!;
|
||||||
|
const tankId =
|
||||||
|
commandGroup === 'tank' ? await resolveTankId(tankIdRaw) : null;
|
||||||
|
const start = interaction.options.getInteger('start');
|
||||||
|
const end = interaction.options.getInteger('end');
|
||||||
|
const path = interactionToURL(interaction, {
|
||||||
|
...player,
|
||||||
|
tankId,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
});
|
||||||
|
const { nickname } = (
|
||||||
|
await getWargamingResponse<AccountInfo>(
|
||||||
|
`https://api.wotblitz.${player.region}/wotb/account/info/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${player.id}`,
|
||||||
|
)
|
||||||
|
)[player.id];
|
||||||
|
|
||||||
|
return [
|
||||||
|
await fullStats(commandGroup, period, player, tankId),
|
||||||
|
primaryButton(path, 'Refresh'),
|
||||||
|
// linkButton(`${CYCLIC_API}/${path}`, 'Embed'),
|
||||||
|
linkButton(
|
||||||
|
`https://www.blitzstars.com/player/${player.region}/${nickname}`,
|
||||||
|
'BlitzStars',
|
||||||
|
),
|
||||||
|
];
|
||||||
|
},
|
||||||
|
|
||||||
|
autocomplete: (interaction) => {
|
||||||
|
autocompleteUsername(interaction);
|
||||||
|
autocompleteTanks(interaction);
|
||||||
|
},
|
||||||
|
|
||||||
|
async button(interaction) {
|
||||||
|
const url = new URL(`${CYCLIC_API}/${interaction.customId}`);
|
||||||
|
const path = url.pathname.split('/').filter(Boolean);
|
||||||
|
const commandGroup = path[1] as StatType;
|
||||||
|
const player = await resolvePlayerFromButton(interaction);
|
||||||
|
const period = resolvePeriodFromButton(player.region, interaction);
|
||||||
|
|
||||||
|
return await fullStats(
|
||||||
|
commandGroup,
|
||||||
|
period,
|
||||||
|
player,
|
||||||
|
parseInt(url.searchParams.get('tankId')!),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
},
|
},
|
||||||
};
|
);
|
||||||
@@ -121,7 +121,7 @@ export const DATABASE_REPO = { owner: 'tresabhi', repo: 'blitzkrieg-db' };
|
|||||||
|
|
||||||
export const ratingsCommand: CommandRegistry = {
|
export const ratingsCommand: CommandRegistry = {
|
||||||
inProduction: true,
|
inProduction: true,
|
||||||
inDevelopment: true,
|
inDevelopment: false,
|
||||||
inPublic: true,
|
inPublic: true,
|
||||||
|
|
||||||
command: new SlashCommandBuilder()
|
command: new SlashCommandBuilder()
|
||||||
@@ -165,7 +165,6 @@ export const ratingsCommand: CommandRegistry = {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
async handler(interaction) {
|
async handler(interaction) {
|
||||||
const subcommandGroup = interaction.options.getSubcommandGroup(true);
|
const subcommandGroup = interaction.options.getSubcommandGroup(true);
|
||||||
|
|
||||||
|
|||||||
+62
-58
@@ -13,73 +13,77 @@ 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 { secrets } from '../core/node/secrets';
|
import { secrets } from '../core/node/secrets';
|
||||||
import { CommandRegistry } from '../events/interactionCreate';
|
import { CommandRegistryRaw } from '../events/interactionCreate';
|
||||||
import { StatType } from '../renderers/fullStats';
|
import { StatType } from '../renderers/fullStats';
|
||||||
import stats from '../renderers/stats';
|
import stats from '../renderers/stats';
|
||||||
import { AccountInfo } from '../types/accountInfo';
|
import { AccountInfo } from '../types/accountInfo';
|
||||||
|
|
||||||
export const statsCommand: CommandRegistry = {
|
export const statsCommand = new Promise<CommandRegistryRaw>(async (resolve) => {
|
||||||
inProduction: true,
|
const command = await addStatTypeSubCommandGroups(
|
||||||
inDevelopment: false,
|
|
||||||
inPublic: true,
|
|
||||||
|
|
||||||
command: addStatTypeSubCommandGroups(
|
|
||||||
new SlashCommandBuilder()
|
new SlashCommandBuilder()
|
||||||
.setName('stats')
|
.setName('stats')
|
||||||
.setDescription('Regular battles statistics'),
|
.setDescription('Regular battles statistics'),
|
||||||
),
|
);
|
||||||
|
|
||||||
async handler(interaction) {
|
resolve({
|
||||||
const commandGroup = interaction.options.getSubcommandGroup(
|
inProduction: true,
|
||||||
true,
|
inDevelopment: true,
|
||||||
) as StatType;
|
inPublic: true,
|
||||||
const player = await resolvePlayerFromCommand(interaction);
|
|
||||||
const period = resolvePeriodFromCommand(player.region, interaction);
|
|
||||||
const tankIdRaw = interaction.options.getString('tank')!;
|
|
||||||
const tankId =
|
|
||||||
commandGroup === 'tank' ? await resolveTankId(tankIdRaw) : null;
|
|
||||||
const start = interaction.options.getInteger('start');
|
|
||||||
const end = interaction.options.getInteger('end');
|
|
||||||
const path = interactionToURL(interaction, {
|
|
||||||
...player,
|
|
||||||
tankId,
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
});
|
|
||||||
const { nickname } = (
|
|
||||||
await getWargamingResponse<AccountInfo>(
|
|
||||||
`https://api.wotblitz.${player.region}/wotb/account/info/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${player.id}`,
|
|
||||||
)
|
|
||||||
)[player.id];
|
|
||||||
|
|
||||||
return [
|
command,
|
||||||
await stats(commandGroup, period, player, tankId),
|
|
||||||
primaryButton(path, 'Refresh'),
|
|
||||||
linkButton(`${CYCLIC_API}/${path}`, 'Embed'),
|
|
||||||
linkButton(
|
|
||||||
`https://www.blitzstars.com/player/${player.region}/${nickname}`,
|
|
||||||
'BlitzStars',
|
|
||||||
),
|
|
||||||
];
|
|
||||||
},
|
|
||||||
|
|
||||||
autocomplete: (interaction) => {
|
async handler(interaction) {
|
||||||
autocompleteUsername(interaction);
|
const commandGroup = interaction.options.getSubcommandGroup(
|
||||||
autocompleteTanks(interaction);
|
true,
|
||||||
},
|
) as StatType;
|
||||||
|
const player = await resolvePlayerFromCommand(interaction);
|
||||||
|
const period = resolvePeriodFromCommand(player.region, interaction);
|
||||||
|
const tankIdRaw = interaction.options.getString('tank')!;
|
||||||
|
const tankId =
|
||||||
|
commandGroup === 'tank' ? await resolveTankId(tankIdRaw) : null;
|
||||||
|
const start = interaction.options.getInteger('start');
|
||||||
|
const end = interaction.options.getInteger('end');
|
||||||
|
const path = interactionToURL(interaction, {
|
||||||
|
...player,
|
||||||
|
tankId,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
});
|
||||||
|
const { nickname } = (
|
||||||
|
await getWargamingResponse<AccountInfo>(
|
||||||
|
`https://api.wotblitz.${player.region}/wotb/account/info/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${player.id}`,
|
||||||
|
)
|
||||||
|
)[player.id];
|
||||||
|
|
||||||
async button(interaction) {
|
return [
|
||||||
const url = new URL(`${CYCLIC_API}/${interaction.customId}`);
|
await stats(commandGroup, period, player, tankId),
|
||||||
const path = url.pathname.split('/').filter(Boolean);
|
primaryButton(path, 'Refresh'),
|
||||||
const commandGroup = path[1] as StatType;
|
linkButton(`${CYCLIC_API}/${path}`, 'Embed'),
|
||||||
const player = await resolvePlayerFromButton(interaction);
|
linkButton(
|
||||||
const period = resolvePeriodFromButton(player.region, interaction);
|
`https://www.blitzstars.com/player/${player.region}/${nickname}`,
|
||||||
|
'BlitzStars',
|
||||||
|
),
|
||||||
|
];
|
||||||
|
},
|
||||||
|
|
||||||
return await stats(
|
autocomplete: (interaction) => {
|
||||||
commandGroup,
|
autocompleteUsername(interaction);
|
||||||
period,
|
autocompleteTanks(interaction);
|
||||||
player,
|
},
|
||||||
parseInt(url.searchParams.get('tankId')!),
|
|
||||||
);
|
async button(interaction) {
|
||||||
},
|
const url = new URL(`${CYCLIC_API}/${interaction.customId}`);
|
||||||
};
|
const path = url.pathname.split('/').filter(Boolean);
|
||||||
|
const commandGroup = path[1] as StatType;
|
||||||
|
const player = await resolvePlayerFromButton(interaction);
|
||||||
|
const period = resolvePeriodFromButton(player.region, interaction);
|
||||||
|
|
||||||
|
return await stats(
|
||||||
|
commandGroup,
|
||||||
|
period,
|
||||||
|
player,
|
||||||
|
parseInt(url.searchParams.get('tankId')!),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import { secrets } from '../node/secrets';
|
||||||
|
import getWargamingResponse from './getWargamingResponse';
|
||||||
|
|
||||||
export interface TankopediaEntry {
|
export interface TankopediaEntry {
|
||||||
name: string;
|
name: string;
|
||||||
nation: string;
|
nation: string;
|
||||||
@@ -32,6 +35,22 @@ export const TANK_NAMES = new Promise<string[]>(async (resolve) => {
|
|||||||
resolve((await entries).map(({ name }) => name));
|
resolve((await entries).map(({ name }) => name));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export interface TankopediaInfo {
|
||||||
|
achievement_sections: Record<string, { name: string; order: number }>;
|
||||||
|
tanks_updated_at: number;
|
||||||
|
languages: Record<string, string>;
|
||||||
|
vehicle_types: Record<string, string>;
|
||||||
|
vehicle_nations: Record<string, string>;
|
||||||
|
game_version: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is blocking because info is needed for command creation
|
||||||
|
console.log('Caching tankopedia info...');
|
||||||
|
export const tankopediaInfo = getWargamingResponse<TankopediaInfo>(
|
||||||
|
`https://api.wotblitz.com/wotb/encyclopedia/info/?application_id=${secrets.WARGAMING_APPLICATION_ID}`,
|
||||||
|
);
|
||||||
|
console.log('Cached tankopedia info');
|
||||||
|
|
||||||
export const TANK_ICONS: Record<string, string> = {
|
export const TANK_ICONS: Record<string, string> = {
|
||||||
'AT-SPG': 'https://i.imgur.com/BIHSEH0.png',
|
'AT-SPG': 'https://i.imgur.com/BIHSEH0.png',
|
||||||
lightTank: 'https://i.imgur.com/CSNha5V.png',
|
lightTank: 'https://i.imgur.com/CSNha5V.png',
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ import getTimeDaysAgo from './getTimeDaysAgo';
|
|||||||
|
|
||||||
export default function getPeriodStart(
|
export default function getPeriodStart(
|
||||||
server: Region,
|
server: Region,
|
||||||
period: Exclude<Period, 'custom'>,
|
period: Exclude<Period, 'customperiod'>,
|
||||||
) {
|
) {
|
||||||
if (period === 'career') {
|
if (period === 'career') {
|
||||||
return -Infinity;
|
return -Infinity;
|
||||||
} else if (period === 'today') {
|
} else if (period === 'today') {
|
||||||
return getTimeDaysAgo(server, 1);
|
return getTimeDaysAgo(server, 1);
|
||||||
} else {
|
} else {
|
||||||
return getTimeDaysAgo(server, parseInt(period));
|
return getTimeDaysAgo(server, parseInt(period.match(/\d+/)?.[0]!));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
} from 'discord.js';
|
} from 'discord.js';
|
||||||
import { PERIOD_NAMES } from '../discord/resolvePeriodFromCommand';
|
import { PERIOD_NAMES } from '../discord/resolvePeriodFromCommand';
|
||||||
|
|
||||||
export type Period = 'today' | '30' | '60' | '90' | 'career' | 'custom';
|
export type Period = 'period' | 'customperiod';
|
||||||
|
|
||||||
export default function addPeriodSubCommands(
|
export default function addPeriodSubCommands(
|
||||||
option: SlashCommandSubcommandGroupBuilder,
|
option: SlashCommandSubcommandGroupBuilder,
|
||||||
@@ -14,25 +14,30 @@ export default function addPeriodSubCommands(
|
|||||||
) {
|
) {
|
||||||
return option
|
return option
|
||||||
.addSubcommand((option) =>
|
.addSubcommand((option) =>
|
||||||
extra(option.setName('today').setDescription(PERIOD_NAMES.today)),
|
extra(
|
||||||
)
|
option
|
||||||
.addSubcommand((option) =>
|
.setName('period' satisfies Period)
|
||||||
extra(option.setName('30').setDescription(PERIOD_NAMES[30])),
|
.setDescription(PERIOD_NAMES.period)
|
||||||
)
|
.addStringOption((option) =>
|
||||||
.addSubcommand((option) =>
|
option
|
||||||
extra(option.setName('60').setDescription(PERIOD_NAMES[60])),
|
.setName('days')
|
||||||
)
|
.setDescription('A preset period')
|
||||||
.addSubcommand((option) =>
|
.setChoices(
|
||||||
extra(option.setName('90').setDescription(PERIOD_NAMES[90])),
|
{ name: 'Today', value: '1' },
|
||||||
)
|
{ name: '30 days', value: '30' },
|
||||||
.addSubcommand((option) =>
|
{ name: '60 days', value: '60' },
|
||||||
extra(option.setName('career').setDescription(PERIOD_NAMES.career)),
|
{ name: '90 days', value: '90' },
|
||||||
|
{ name: 'Career', value: 'Infinity' },
|
||||||
|
)
|
||||||
|
.setRequired(true),
|
||||||
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.addSubcommand((option) =>
|
.addSubcommand((option) =>
|
||||||
extra(
|
extra(
|
||||||
option
|
option
|
||||||
.setName('custom')
|
.setName('customperiod' satisfies Period)
|
||||||
.setDescription(PERIOD_NAMES.custom)
|
.setDescription(PERIOD_NAMES.customperiod)
|
||||||
.addIntegerOption((option) =>
|
.addIntegerOption((option) =>
|
||||||
option
|
option
|
||||||
.setName('start')
|
.setName('start')
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
import { SlashCommandBuilder } from 'discord.js';
|
import { SlashCommandBuilder } from 'discord.js';
|
||||||
|
import { tankopediaInfo } from '../blitz/tankopedia';
|
||||||
import addPeriodSubCommands from './addPeriodSubCommands';
|
import addPeriodSubCommands from './addPeriodSubCommands';
|
||||||
import addTankChoices from './addTankChoices';
|
import addTankChoices from './addTankChoices';
|
||||||
import addUsernameChoices from './addUsernameChoices';
|
import addUsernameChoices from './addUsernameChoices';
|
||||||
|
|
||||||
export default function addStatTypeSubCommandGroups(
|
export default async function addStatTypeSubCommandGroups(
|
||||||
option: SlashCommandBuilder,
|
option: SlashCommandBuilder,
|
||||||
) {
|
) {
|
||||||
|
const awaitedTankopediaInfo = await tankopediaInfo;
|
||||||
|
|
||||||
return option
|
return option
|
||||||
.addSubcommandGroup((option) =>
|
.addSubcommandGroup((option) =>
|
||||||
addPeriodSubCommands(option, (option) =>
|
addPeriodSubCommands(option, (option) =>
|
||||||
option.addStringOption(addUsernameChoices),
|
option.addStringOption(addUsernameChoices),
|
||||||
)
|
)
|
||||||
.setName('player')
|
.setName('player')
|
||||||
.setDescription("Player's statistics"),
|
.setDescription('Player statistics'),
|
||||||
)
|
)
|
||||||
.addSubcommandGroup((option) =>
|
.addSubcommandGroup((option) =>
|
||||||
addPeriodSubCommands(option, (option) =>
|
addPeriodSubCommands(option, (option) =>
|
||||||
@@ -21,6 +24,58 @@ export default function addStatTypeSubCommandGroups(
|
|||||||
.addStringOption(addUsernameChoices),
|
.addStringOption(addUsernameChoices),
|
||||||
)
|
)
|
||||||
.setName('tank')
|
.setName('tank')
|
||||||
.setDescription("Tank's statistics"),
|
.setDescription('Tank statistics'),
|
||||||
|
)
|
||||||
|
.addSubcommandGroup((option) =>
|
||||||
|
addPeriodSubCommands(option, (option) =>
|
||||||
|
option
|
||||||
|
.addStringOption((option) => {
|
||||||
|
option
|
||||||
|
.setName('nation')
|
||||||
|
.setDescription('Nation')
|
||||||
|
.addChoices(
|
||||||
|
...Object.entries(awaitedTankopediaInfo.vehicle_nations).map(
|
||||||
|
([type, name]) => ({ name, value: type }),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return option;
|
||||||
|
})
|
||||||
|
.addStringOption((option) =>
|
||||||
|
option
|
||||||
|
.setName('tanktype')
|
||||||
|
.setDescription('Type')
|
||||||
|
.addChoices(
|
||||||
|
...Object.entries(awaitedTankopediaInfo.vehicle_types).map(
|
||||||
|
([type, name]) => ({
|
||||||
|
name: name.replace(' Tank', ''),
|
||||||
|
value: type,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.setRequired(false),
|
||||||
|
)
|
||||||
|
.addIntegerOption((option) =>
|
||||||
|
option
|
||||||
|
.setName('tier')
|
||||||
|
.setDescription('Tier')
|
||||||
|
.setMinValue(1)
|
||||||
|
.setMaxValue(10)
|
||||||
|
.setRequired(false),
|
||||||
|
)
|
||||||
|
.addStringOption((option) =>
|
||||||
|
option
|
||||||
|
.setName('treetype')
|
||||||
|
.setDescription('Tech tree type')
|
||||||
|
.addChoices(
|
||||||
|
{ name: 'Tech Tree', value: 'techtree' },
|
||||||
|
{ name: 'Premium', value: 'premium' },
|
||||||
|
{ name: 'Collector', value: 'collector' },
|
||||||
|
)
|
||||||
|
.setRequired(false),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.setName('tanks')
|
||||||
|
.setDescription('Multiple tanks'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -7,20 +7,16 @@ import { Period } from '../discord/addPeriodSubCommands';
|
|||||||
|
|
||||||
export const PERIOD_NAMES: Record<Period, string> = {
|
export const PERIOD_NAMES: Record<Period, string> = {
|
||||||
today: "Today's statistics",
|
today: "Today's statistics",
|
||||||
30: "30 days' statistics",
|
period: "Set number of days' statistics",
|
||||||
60: "60 days' statistics",
|
|
||||||
90: "90 days' statistics",
|
|
||||||
career: 'Career statistics',
|
career: 'Career statistics',
|
||||||
custom: 'Custom period',
|
customperiod: 'Custom period',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const EVOLUTION_PERIOD_NAMES: Record<Period, string> = {
|
export const EVOLUTION_PERIOD_NAMES: Record<Period, string> = {
|
||||||
today: "Today's evolution",
|
today: "Today's evolution",
|
||||||
30: "30 days' evolution",
|
period: "Set number of days' evolution",
|
||||||
60: "60 days' evolution",
|
|
||||||
90: "90 days' evolution",
|
|
||||||
career: 'Career evolution',
|
career: 'Career evolution',
|
||||||
custom: 'Custom period',
|
customperiod: 'Custom period',
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface ResolvedPeriod {
|
export interface ResolvedPeriod {
|
||||||
@@ -40,7 +36,7 @@ export default function resolvePeriodFromCommand(
|
|||||||
let end: number;
|
let end: number;
|
||||||
const period = interaction.options.getSubcommand() as Period;
|
const period = interaction.options.getSubcommand() as Period;
|
||||||
|
|
||||||
if (period === 'custom') {
|
if (period === 'customperiod') {
|
||||||
const startRaw = interaction.options.getInteger('start')!;
|
const startRaw = interaction.options.getInteger('start')!;
|
||||||
const endRaw = interaction.options.getInteger('end')!;
|
const endRaw = interaction.options.getInteger('end')!;
|
||||||
const startMin = Math.min(startRaw, endRaw);
|
const startMin = Math.min(startRaw, endRaw);
|
||||||
|
|||||||
@@ -54,22 +54,30 @@ export interface Registry {
|
|||||||
inProduction: boolean;
|
inProduction: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CommandRegistry<HandlesInteraction extends boolean = boolean>
|
export type CommandRegistryRaw = Registry & {
|
||||||
extends Registry {
|
|
||||||
inPublic: boolean;
|
inPublic: boolean;
|
||||||
inPreview?: boolean;
|
inPreview?: boolean;
|
||||||
handlesInteraction?: HandlesInteraction;
|
|
||||||
command:
|
command:
|
||||||
| SlashCommandBuilder
|
| SlashCommandBuilder
|
||||||
| SlashCommandSubcommandsOnlyBuilder
|
| SlashCommandSubcommandsOnlyBuilder
|
||||||
| Omit<SlashCommandBuilder, 'addSubcommand' | 'addSubcommandGroup'>;
|
| Omit<SlashCommandBuilder, 'addSubcommand' | 'addSubcommandGroup'>;
|
||||||
|
|
||||||
handler: (
|
|
||||||
interaction: ChatInputCommandInteraction<CacheType>,
|
|
||||||
) => HandlesInteraction extends true ? void : InteractionReturnable;
|
|
||||||
autocomplete?: (interaction: AutocompleteInteraction<CacheType>) => void;
|
autocomplete?: (interaction: AutocompleteInteraction<CacheType>) => void;
|
||||||
button?: (interaction: ButtonInteraction<CacheType>) => InteractionReturnable;
|
button?: (interaction: ButtonInteraction<CacheType>) => InteractionReturnable;
|
||||||
}
|
} & (
|
||||||
|
| {
|
||||||
|
handlesInteraction: true;
|
||||||
|
handler: (interaction: ChatInputCommandInteraction<CacheType>) => void;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
handlesInteraction?: false;
|
||||||
|
handler: (
|
||||||
|
interaction: ChatInputCommandInteraction<CacheType>,
|
||||||
|
) => InteractionReturnable;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
export type CommandRegistry = CommandRegistryRaw | Promise<CommandRegistryRaw>;
|
||||||
|
|
||||||
const rest = new REST().setToken(secrets.DISCORD_TOKEN);
|
const rest = new REST().setToken(secrets.DISCORD_TOKEN);
|
||||||
|
|
||||||
@@ -92,12 +100,19 @@ export const COMMANDS_RAW: CommandRegistry[] = [
|
|||||||
statsCommand,
|
statsCommand,
|
||||||
ratingsCommand,
|
ratingsCommand,
|
||||||
];
|
];
|
||||||
export const commands: Record<string, CommandRegistry> = COMMANDS_RAW.reduce(
|
|
||||||
(accumulator, registry) => {
|
export const commands = Promise.all(COMMANDS_RAW).then((rawCommands) =>
|
||||||
if (isDev()) registry.command.setDefaultMemberPermissions(0);
|
rawCommands.reduce<Record<string, CommandRegistryRaw>>(
|
||||||
return { ...accumulator, [registry.command.name]: registry };
|
(accumulator, registry) => {
|
||||||
},
|
if (isDev()) registry.command.setDefaultMemberPermissions(0);
|
||||||
{},
|
|
||||||
|
return {
|
||||||
|
...accumulator,
|
||||||
|
[registry.command.name]: registry,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
export const guildCommands: RESTPostAPIChatInputApplicationCommandsJSONBody[] =
|
export const guildCommands: RESTPostAPIChatInputApplicationCommandsJSONBody[] =
|
||||||
@@ -105,48 +120,50 @@ export const guildCommands: RESTPostAPIChatInputApplicationCommandsJSONBody[] =
|
|||||||
export const publicCommands: RESTPostAPIChatInputApplicationCommandsJSONBody[] =
|
export const publicCommands: RESTPostAPIChatInputApplicationCommandsJSONBody[] =
|
||||||
[];
|
[];
|
||||||
|
|
||||||
Object.entries(commands).forEach(([, registry]) => {
|
commands.then((awaitedCommands) => {
|
||||||
if (isDev() ? registry.inDevelopment : registry.inProduction) {
|
Object.entries(awaitedCommands).forEach(([, registry]) => {
|
||||||
const json = registry.command.toJSON();
|
if (isDev() ? registry.inDevelopment : registry.inProduction) {
|
||||||
|
const json = registry.command.toJSON();
|
||||||
|
|
||||||
if (registry.inPublic) {
|
if (registry.inPublic) {
|
||||||
publicCommands.push(json);
|
publicCommands.push(json);
|
||||||
} else {
|
} else {
|
||||||
guildCommands.push(json);
|
guildCommands.push(json);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log(`Refreshing ${publicCommands.length} public command(s).`);
|
||||||
|
rest
|
||||||
|
.put(Routes.applicationCommands(getClientId()), { body: publicCommands })
|
||||||
|
.then((publicData) => {
|
||||||
|
console.log(
|
||||||
|
`Successfully refreshed ${
|
||||||
|
(publicData as RESTPostAPIChatInputApplicationCommandsJSONBody[])
|
||||||
|
.length
|
||||||
|
} public command(s).`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Refreshing ${guildCommands.length} guild command(s).`);
|
||||||
|
rest
|
||||||
|
.put(
|
||||||
|
Routes.applicationGuildCommands(getClientId(), discord.tres_guild_id),
|
||||||
|
{ body: guildCommands },
|
||||||
|
)
|
||||||
|
.then((guildData) => {
|
||||||
|
console.log(
|
||||||
|
`Successfully refreshed ${
|
||||||
|
(guildData as unknown[]).length
|
||||||
|
} guild command(s).`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
|
||||||
console.log(`Refreshing ${publicCommands.length} public command(s).`);
|
|
||||||
rest
|
|
||||||
.put(Routes.applicationCommands(getClientId()), { body: publicCommands })
|
|
||||||
.then((publicData) => {
|
|
||||||
console.log(
|
|
||||||
`Successfully refreshed ${
|
|
||||||
(publicData as RESTPostAPIChatInputApplicationCommandsJSONBody[])
|
|
||||||
.length
|
|
||||||
} public command(s).`,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`Refreshing ${guildCommands.length} guild command(s).`);
|
|
||||||
rest
|
|
||||||
.put(
|
|
||||||
Routes.applicationGuildCommands(getClientId(), discord.tres_guild_id),
|
|
||||||
{ body: guildCommands },
|
|
||||||
)
|
|
||||||
.then((guildData) => {
|
|
||||||
console.log(
|
|
||||||
`Successfully refreshed ${
|
|
||||||
(guildData as unknown[]).length
|
|
||||||
} guild command(s).`,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function interactionCreate(
|
export default async function interactionCreate(
|
||||||
interaction: Interaction<CacheType>,
|
interaction: Interaction<CacheType>,
|
||||||
) {
|
) {
|
||||||
|
|||||||
Reference in new issue
Block a user