From f5bab8a698b20b33dd3d40c45e970886ae7f1af0 Mon Sep 17 00:00:00 2001 From: tresabhi Date: Thu, 7 Sep 2023 01:24:12 +0000 Subject: [PATCH 1/3] faet: clan-event --- docs/changelogs/1.12.4.md | 7 ++ src/commands/clanEvent.ts | 107 ++++++++++++++++++++++++++ src/events/interactionCreate/index.ts | 2 + tsconfig.json | 2 +- 4 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 docs/changelogs/1.12.4.md create mode 100644 src/commands/clanEvent.ts 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/src/commands/clanEvent.ts b/src/commands/clanEvent.ts new file mode 100644 index 000000000..fe015dbe2 --- /dev/null +++ b/src/commands/clanEvent.ts @@ -0,0 +1,107 @@ +import { SlashCommandBuilder } from 'discord.js'; +import markdownEscape from 'markdown-escape'; +import autocompleteClan from '../core/discord/autocompleteClan'; +import embedInfo from '../core/discord/embedInfo'; +import { secrets } from '../core/node/secrets'; +import { CommandRegistry } from '../events/interactionCreate'; + +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((option) => + option + .setName('clan') + .setDescription('The clan name or tag you are checking') + .setRequired(true), + ), + + async handler(interaction) { + const REGION = 'com'; + const CLAN_SEARCH = interaction.options.getString('clan', true); + + const { + clan_id, + name: clan_name, + tag: clan_tag, + } = await fetch( + `https://api.wotblitz.${REGION}/wotb/clans/list/?application_id=${secrets.WARGAMING_APPLICATION_ID}&search=${CLAN_SEARCH}`, + ) + .then((response) => response.json()) + .then(({ data }) => data[0]); + const memebers = await fetch( + `https://api.wotblitz.${REGION}/wotb/clans/info/?application_id=${secrets.WARGAMING_APPLICATION_ID}&clan_id=${clan_id}`, + ) + .then((response) => response.json()) + .then(({ data }) => data[clan_id].members_ids); + const stats = await Promise.all( + memebers.map(async (id, index) => { + await new Promise((resolve) => setTimeout(resolve, index * 200)); + + const [yesterdayPlatoonWins, name, todayPlatoonWins] = + await Promise.all([ + fetch(`https://www.blitzstars.com/api/playerstats/${id}`) + .then((response) => response.json()) + .then((data) => data[0]?.achievements.jointVictoryCount), + fetch( + `https://api.wotblitz.${REGION}/wotb/account/info/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${id}`, + ) + .then((response) => response.json()) + .then(({ data }) => data[id].nickname), + fetch( + `https://api.wotblitz.${REGION}/wotb/account/achievements/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${id}`, + ) + .then((response) => response.json()) + .then(({ data }) => data[id].max_series.jointVictory), + ]); + + return { + id, + name, + yesterdayPlatoonWins, + todayPlatoonWins, + }; + }), + ); + + return embedInfo( + `Estimation of ${clan_name} [${clan_tag}]'s total platoon wins today: ${( + stats.reduce( + (accumulator, player) => + accumulator + + (player.yesterdayPlatoonWins === undefined + ? 0 + : player.todayPlatoonWins - player.yesterdayPlatoonWins), + 0, + ) / 2 + ).toFixed(0)}`, + stats + .sort( + (playerA, playerB) => + (playerB.yesterdayPlatoonWins === undefined + ? 0 + : playerB.todayPlatoonWins - playerB.yesterdayPlatoonWins) - + (playerA.yesterdayPlatoonWins === undefined + ? 0 + : playerA.todayPlatoonWins - playerA.yesterdayPlatoonWins), + ) + .map( + (player) => + `${markdownEscape(player.name)}: ${ + player.yesterdayPlatoonWins === undefined + ? 0 + : player.todayPlatoonWins - player.yesterdayPlatoonWins + }`, + ) + .join('\n'), + ); + }, + + autocomplete: autocompleteClan, +}; diff --git a/src/events/interactionCreate/index.ts b/src/events/interactionCreate/index.ts index 6664952cc..00aea17cf 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'; @@ -99,6 +100,7 @@ export const COMMANDS_RAW: CommandRegistry[] = [ evolutionCommand, statsCommand, ratingsCommand, + clanEventCommand, ]; export const commands = Promise.all(COMMANDS_RAW).then((rawCommands) => diff --git a/tsconfig.json b/tsconfig.json index 65c75994f..7b1732b23 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,5 +11,5 @@ "resolveJsonModule": true, "jsx": "react-jsx" }, - "include": ["src/**/*.ts", "src/components/Tanks/components/Row.tsx"] + "include": ["src/**/*.ts", "src/components/Tanks/components/Row.tsx", "src/commands/clanEvent.ts"] } From 7037aa9c3b8c5067980d806e2d568989c8e5ce59 Mon Sep 17 00:00:00 2001 From: tresabhi Date: Thu, 7 Sep 2023 03:24:07 +0000 Subject: [PATCH 2/3] feat: 1.12.5 --- docs/changelogs/1.12.5.md | 12 ++ src/commands/clanEvent.ts | 147 ++++++++++----------- src/commands/inactive.tsx | 9 +- src/core/blitz/getBlitzClan.ts | 22 --- src/core/blitz/listClans.ts | 10 +- src/core/discord/autocompleteClan.ts | 4 +- src/core/discord/resolveClanFromCommand.ts | 27 ++++ 7 files changed, 117 insertions(+), 114 deletions(-) create mode 100644 docs/changelogs/1.12.5.md delete mode 100644 src/core/blitz/getBlitzClan.ts create mode 100644 src/core/discord/resolveClanFromCommand.ts diff --git a/docs/changelogs/1.12.5.md b/docs/changelogs/1.12.5.md new file mode 100644 index 000000000..6fbbf0ade --- /dev/null +++ b/docs/changelogs/1.12.5.md @@ -0,0 +1,12 @@ +# Blitzkrieg 1.12.4 + +Some path 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 index fe015dbe2..18e133cd1 100644 --- a/src/commands/clanEvent.ts +++ b/src/commands/clanEvent.ts @@ -1,9 +1,16 @@ 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 embedInfo from '../core/discord/embedInfo'; +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; @@ -15,92 +22,72 @@ export const clanEventCommand: CommandRegistry = { command: new SlashCommandBuilder() .setName('clan-event') .setDescription('Lists all inactive players') - .addStringOption((option) => - option - .setName('clan') - .setDescription('The clan name or tag you are checking') - .setRequired(true), - ), + .addStringOption(addClanChoices), async handler(interaction) { - const REGION = 'com'; - const CLAN_SEARCH = interaction.options.getString('clan', true); + 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]!, + })); - const { - clan_id, - name: clan_name, - tag: clan_tag, - } = await fetch( - `https://api.wotblitz.${REGION}/wotb/clans/list/?application_id=${secrets.WARGAMING_APPLICATION_ID}&search=${CLAN_SEARCH}`, - ) - .then((response) => response.json()) - .then(({ data }) => data[0]); - const memebers = await fetch( - `https://api.wotblitz.${REGION}/wotb/clans/info/?application_id=${secrets.WARGAMING_APPLICATION_ID}&clan_id=${clan_id}`, - ) - .then((response) => response.json()) - .then(({ data }) => data[clan_id].members_ids); - const stats = await Promise.all( - memebers.map(async (id, index) => { - await new Promise((resolve) => setTimeout(resolve, index * 200)); - - const [yesterdayPlatoonWins, name, todayPlatoonWins] = - await Promise.all([ - fetch(`https://www.blitzstars.com/api/playerstats/${id}`) - .then((response) => response.json()) - .then((data) => data[0]?.achievements.jointVictoryCount), - fetch( - `https://api.wotblitz.${REGION}/wotb/account/info/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${id}`, - ) - .then((response) => response.json()) - .then(({ data }) => data[id].nickname), - fetch( - `https://api.wotblitz.${REGION}/wotb/account/achievements/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${id}`, - ) - .then((response) => response.json()) - .then(({ data }) => data[id].max_series.jointVictory), - ]); - - return { - id, - name, - yesterdayPlatoonWins, - todayPlatoonWins, - }; - }), - ); - - return embedInfo( - `Estimation of ${clan_name} [${clan_tag}]'s total platoon wins today: ${( - stats.reduce( - (accumulator, player) => - accumulator + - (player.yesterdayPlatoonWins === undefined - ? 0 - : player.todayPlatoonWins - player.yesterdayPlatoonWins), + return [ + `# ${clan.name} [${clan.tag}]'s platoon wins today: ${( + jointVictories.reduce( + (accumulator, { joinVictory }) => accumulator + joinVictory, 0, ) / 2 - ).toFixed(0)}`, - stats - .sort( - (playerA, playerB) => - (playerB.yesterdayPlatoonWins === undefined - ? 0 - : playerB.todayPlatoonWins - playerB.yesterdayPlatoonWins) - - (playerA.yesterdayPlatoonWins === undefined - ? 0 - : playerA.todayPlatoonWins - playerA.yesterdayPlatoonWins), - ) + ).toFixed(0)}\n${jointVictories + .sort((a, b) => b.joinVictory - a.joinVictory) .map( - (player) => - `${markdownEscape(player.name)}: ${ - player.yesterdayPlatoonWins === undefined - ? 0 - : player.todayPlatoonWins - player.yesterdayPlatoonWins - }`, + ({ id, joinVictory }) => + `- ${markdownEscape(players[id].nickname)}: ${joinVictory}`, ) - .join('\n'), - ); + .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 6d2ccb712..8ec2a8e79 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'; @@ -33,19 +33,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.`, + ); + } + } +} From 04086f31b9d6b4eb848a9c56dee8f8a62c052b98 Mon Sep 17 00:00:00 2001 From: tresabhi Date: Thu, 7 Sep 2023 03:25:07 +0000 Subject: [PATCH 3/3] fix: spelling --- docs/changelogs/1.12.5.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelogs/1.12.5.md b/docs/changelogs/1.12.5.md index 6fbbf0ade..e99703bef 100644 --- a/docs/changelogs/1.12.5.md +++ b/docs/changelogs/1.12.5.md @@ -1,6 +1,6 @@ # Blitzkrieg 1.12.4 -Some path up work +Some patch up work ## Changes