diff --git a/docs/changelogs/1.12.4.md b/docs/changelogs/1.12.4.md new file mode 100644 index 000000000..4714d1774 --- /dev/null +++ b/docs/changelogs/1.12.4.md @@ -0,0 +1,7 @@ +# Blitzkrieg 1.12.4 + +Added very badly-coded temporary `/clan-event` command (without linting) + +## Changes + +- Added `/clan-event` command diff --git a/docs/changelogs/1.12.5.md b/docs/changelogs/1.12.5.md new file mode 100644 index 000000000..e99703bef --- /dev/null +++ b/docs/changelogs/1.12.5.md @@ -0,0 +1,12 @@ +# Blitzkrieg 1.12.4 + +Some patch up work + +## Changes + +- `/clan-event` now is more legible and comes with an approximation warning + +## Fixed + +- `/clan-event` now uses a more optimized way of fetching large data + - This should fix the issue where too many people using the command broke it diff --git a/src/commands/clanEvent.ts b/src/commands/clanEvent.ts new file mode 100644 index 000000000..18e133cd1 --- /dev/null +++ b/src/commands/clanEvent.ts @@ -0,0 +1,94 @@ +import { SlashCommandBuilder } from 'discord.js'; +import markdownEscape from 'markdown-escape'; +import getWargamingResponse from '../core/blitz/getWargamingResponse'; +import { PlayerHistoriesRaw } from '../core/blitzstars/getPlayerHistories'; +import addClanChoices from '../core/discord/addClanChoices'; +import autocompleteClan from '../core/discord/autocompleteClan'; +import embedWarning from '../core/discord/embedWarning'; +import resolveClanFromCommand from '../core/discord/resolveClanFromCommand'; +import { secrets } from '../core/node/secrets'; +import { CommandRegistry } from '../events/interactionCreate'; +import { AccountAchievements } from '../types/accountAchievements'; +import { AccountInfo } from '../types/accountInfo'; +import { ClanInfo } from '../types/clanInfo'; + +const DEFAULT_THRESHOLD = 7; + +export const clanEventCommand: CommandRegistry = { + inProduction: true, + inDevelopment: true, + inPublic: true, + + command: new SlashCommandBuilder() + .setName('clan-event') + .setDescription('Lists all inactive players') + .addStringOption(addClanChoices), + + async handler(interaction) { + const { region, id } = await resolveClanFromCommand(interaction); + const clan = ( + await getWargamingResponse( + `https://api.wotblitz.${region}/wotb/clans/info/?application_id=${secrets.WARGAMING_APPLICATION_ID}&clan_id=${id}`, + ) + )[id]; + const memberIds = clan.members_ids.join(','); + const [players, achievements, previousJointVictories] = await Promise.all([ + getWargamingResponse( + `https://api.wotblitz.${region}/wotb/account/info/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${memberIds}`, + ), + getWargamingResponse( + `https://api.wotblitz.${region}/wotb/account/achievements/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${memberIds}`, + ), + Promise.all( + clan.members_ids.map((id) => + fetch(`https://www.blitzstars.com/api/playerstats/${id}`) + .then((response) => response.json() as Promise) + .then((data) => ({ + id, + jointVictory: data[0]?.achievements.jointVictoryCount as + | number + | undefined, + })), + ), + ).then((data) => + data.reduce>( + (accumulator, value) => ({ + ...accumulator, + [value.id]: value.jointVictory, + }), + {}, + ), + ), + ]); + const jointVictories = clan.members_ids.map((id) => ({ + id, + joinVictory: + previousJointVictories[id] === undefined + ? 0 + : achievements[id].max_series.jointVictory - + previousJointVictories[id]!, + })); + + return [ + `# ${clan.name} [${clan.tag}]'s platoon wins today: ${( + jointVictories.reduce( + (accumulator, { joinVictory }) => accumulator + joinVictory, + 0, + ) / 2 + ).toFixed(0)}\n${jointVictories + .sort((a, b) => b.joinVictory - a.joinVictory) + .map( + ({ id, joinVictory }) => + `- ${markdownEscape(players[id].nickname)}: ${joinVictory}`, + ) + .join('\n')}`, + + embedWarning( + 'This is an approximation!', + 'Wargaming provides use with little to no information about platoons. Platooning with mixed clans may inflate the number and platooning in non-regular battles may deflate.', + ), + ]; + }, + + autocomplete: autocompleteClan, +}; diff --git a/src/commands/inactive.tsx b/src/commands/inactive.tsx index a003cd75e..f453d9c64 100644 --- a/src/commands/inactive.tsx +++ b/src/commands/inactive.tsx @@ -3,10 +3,10 @@ import GenericStats from '../components/GenericStats'; import NoData, { NoDataType } from '../components/NoData'; import TitleBar from '../components/TitleBar'; import Wrapper from '../components/Wrapper'; -import getBlitzClan from '../core/blitz/getBlitzClan'; import getWargamingResponse from '../core/blitz/getWargamingResponse'; import addClanChoices from '../core/discord/addClanChoices'; import autocompleteClan from '../core/discord/autocompleteClan'; +import resolveClanFromCommand from '../core/discord/resolveClanFromCommand'; import { secrets } from '../core/node/secrets'; import { CommandRegistry } from '../events/interactionCreate'; import { AccountInfo } from '../types/accountInfo'; @@ -32,19 +32,18 @@ export const inactiveCommand: CommandRegistry = { ), async handler(interaction) { - const clanName = interaction.options.getString('clan')!; - const { server, id } = await getBlitzClan(clanName); + const { region, id } = await resolveClanFromCommand(interaction); const threshold = interaction.options.getNumber('threshold')! ?? DEFAULT_THRESHOLD; const time = new Date().getTime() / 1000; const clanData = ( await getWargamingResponse( - `https://api.wotblitz.${server}/wotb/clans/info/?application_id=${secrets.WARGAMING_APPLICATION_ID}&clan_id=${id}`, + `https://api.wotblitz.${region}/wotb/clans/info/?application_id=${secrets.WARGAMING_APPLICATION_ID}&clan_id=${id}`, ) )[id]; const memberIds = clanData.members_ids; const accounts = await getWargamingResponse( - `https://api.wotblitz.${server}/wotb/account/info/?application_id=${ + `https://api.wotblitz.${region}/wotb/account/info/?application_id=${ secrets.WARGAMING_APPLICATION_ID }&account_id=${memberIds.join(',')}`, ); diff --git a/src/core/blitz/getBlitzClan.ts b/src/core/blitz/getBlitzClan.ts deleted file mode 100644 index 68f32222b..000000000 --- a/src/core/blitz/getBlitzClan.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Region } from '../../constants/regions'; -import { serverAndIdPattern } from '../discord/resolvePlayerFromCommand'; -import throwError from '../node/throwError'; -import listClans from './listClans'; - -export default async function getBlitzClan(clan: string) { - if (serverAndIdPattern.test(clan)) { - const [server, accountId] = clan.split('/'); - return { server: server as Region, id: Number(accountId) }; - } else { - const accounts = await listClans(clan); - - if (accounts[0]) { - return { server: accounts[0].server, id: accounts[0].clan_id }; - } else { - throw throwError( - 'Could not find clan', - `I couldn't find clan \`${clan}\`. Try selecting a username from the search result.`, - ); - } - } -} diff --git a/src/core/blitz/listClans.ts b/src/core/blitz/listClans.ts index 3c7e3fab8..b2e99d0dc 100644 --- a/src/core/blitz/listClans.ts +++ b/src/core/blitz/listClans.ts @@ -3,8 +3,8 @@ import { Clan, ClanList } from '../../types/clanList'; import { secrets } from '../node/secrets'; import getWargamingResponse from './getWargamingResponse'; -export type ClanListWithServer = (Clan & { - server: 'com' | 'eu' | 'asia'; +export type ClanListWithRegion = (Clan & { + region: 'com' | 'eu' | 'asia'; })[]; export default async function listClans(search: string, limit = 9) { @@ -21,7 +21,7 @@ export default async function listClans(search: string, limit = 9) { value && value.map((account) => ({ ...account, - server: 'com' as Region, + region: 'com' as Region, })), ), getWargamingResponse( @@ -31,7 +31,7 @@ export default async function listClans(search: string, limit = 9) { value && value.map((account) => ({ ...account, - server: 'eu' as Region, + region: 'eu' as Region, })), ), getWargamingResponse( @@ -41,7 +41,7 @@ export default async function listClans(search: string, limit = 9) { value && value.map((account) => ({ ...account, - server: 'asia' as Region, + region: 'asia' as Region, })), ), ]) diff --git a/src/core/discord/autocompleteClan.ts b/src/core/discord/autocompleteClan.ts index 91277e8a0..c80d0c2c5 100644 --- a/src/core/discord/autocompleteClan.ts +++ b/src/core/discord/autocompleteClan.ts @@ -19,8 +19,8 @@ export default async function autocompleteClan( ? players.map( (player) => ({ - name: `${player.name} (${REGION_NAMES[player.server]})`, - value: `${player.server}/${player.clan_id}`, + name: `${player.name} (${REGION_NAMES[player.region]})`, + value: `${player.region}/${player.clan_id}`, }) satisfies ApplicationCommandOptionChoiceData, ) : [], diff --git a/src/core/discord/resolveClanFromCommand.ts b/src/core/discord/resolveClanFromCommand.ts new file mode 100644 index 000000000..0e47d700b --- /dev/null +++ b/src/core/discord/resolveClanFromCommand.ts @@ -0,0 +1,27 @@ +import { ChatInputCommandInteraction } from 'discord.js'; +import { Region } from '../../constants/regions'; +import listClans from '../blitz/listClans'; +import throwError from '../node/throwError'; +import { serverAndIdPattern } from './resolvePlayerFromCommand'; + +export default async function resolveClanFromCommand( + interaction: ChatInputCommandInteraction, +) { + const clan = interaction.options.getString('clan', true); + + if (serverAndIdPattern.test(clan)) { + const [server, accountId] = clan.split('/'); + return { region: server as Region, id: Number(accountId) }; + } else { + const accounts = await listClans(clan); + + if (accounts[0]) { + return { region: accounts[0].region, id: accounts[0].clan_id }; + } else { + throw throwError( + 'Could not find clan', + `I couldn't find clan \`${clan}\`. Try selecting a username from the search result.`, + ); + } + } +} diff --git a/src/events/interactionCreate/index.ts b/src/events/interactionCreate/index.ts index d068efca2..3e3644998 100644 --- a/src/events/interactionCreate/index.ts +++ b/src/events/interactionCreate/index.ts @@ -13,6 +13,7 @@ import { SlashCommandSubcommandsOnlyBuilder, } from 'discord.js'; import discord from '../../../discord.json' assert { type: 'json' }; +import { clanEventCommand } from '../../commands/clanEvent'; import { debugCommand } from '../../commands/debug'; import { eligibleCommand } from '../../commands/eligible'; import { evolutionCommand } from '../../commands/evolution'; @@ -98,6 +99,7 @@ export const COMMANDS_RAW: CommandRegistry[] = [ evolutionCommand, statsCommand, ratingsCommand, + clanEventCommand, ]; export const commands = Promise.all(COMMANDS_RAW).then((rawCommands) =>