diff --git a/.vscode/settings.json b/.vscode/settings.json index d7afa0167..e504606c9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,5 @@ { - "cSpell.words": ["Abhi", "accountinfo", "SKLLD", "wargaming"], + "cSpell.words": ["Abhi", "accountinfo", "SKLLD", "wargaming", "winrate"], "deno.enable": false, "deno.unstable": false } diff --git a/src/behaviors/guildMemberAdd.ts b/src/behaviors/guildMemberAdd.ts index 59a43c855..6846abfe2 100644 --- a/src/behaviors/guildMemberAdd.ts +++ b/src/behaviors/guildMemberAdd.ts @@ -1,6 +1,7 @@ import { faker } from '@faker-js/faker'; import { EmbedBuilder, GuildMember, TextChannel } from 'discord.js'; import discord from '../../discord.json' assert { type: 'json' }; +import { SKILLED_COLOR } from '../constants/colors.js'; export default async function (member: GuildMember) { console.log(`${member.user.tag} joined`); @@ -10,7 +11,7 @@ export default async function (member: GuildMember) { ).send({ embeds: [ new EmbedBuilder() - .setColor('#8e3cf5') + .setColor(SKILLED_COLOR) .setTitle(`Welcome ${member.user.username}`) .setDescription( `Welcome to the Skilled server, **${ diff --git a/src/commands/stats.ts b/src/commands/stats.ts index 87abcbf96..df219ff29 100644 --- a/src/commands/stats.ts +++ b/src/commands/stats.ts @@ -1,14 +1,88 @@ import { CacheType, ChatInputCommandInteraction, + EmbedBuilder, SlashCommandBuilder, } from 'discord.js'; +import fetch from 'node-fetch'; +import { SKILLED_COLOR } from '../constants/colors.js'; import { SERVERS } from '../constants/servers.js'; +import { PlayerStatistics } from '../types/statistics.js'; +import getBlitzAccount from '../utilities/getBlitzAccount.js'; export async function execute( interaction: ChatInputCommandInteraction, ) { - interaction.reply('YOU SUCK MORON GO PLAY ROBLOX'); + const ign = interaction.options.getString('ign')!; + const server = interaction.options.getString( + 'server', + ) as keyof typeof SERVERS; + const period = interaction.options.getString('period')! as + | '30' + | '90' + | 'career'; + + getBlitzAccount(interaction, ign, server, async (account) => { + const data = (await ( + await fetch( + `https://www.blitzstars.com/api/top/player/${account.account_id}`, + ) + ).json()) as PlayerStatistics; + const stats = + period === 'career' ? data.statistics : data[`period${period}d`]; + + interaction.reply({ + embeds: [ + new EmbedBuilder() + .setColor(SKILLED_COLOR) + .setTitle( + `${data.nickname}'s ${ + period === 'career' ? period : `${period} day` + } stats`, + ) + .setDescription( + `${[ + ['Winrate', `${stats.special.winrate.toFixed(2)}%`], + ['Survival', `${stats.special.survivalRate.toFixed(2)}%`], + [ + 'Accuracy', + `${((stats.all.hits / stats.all.shots) * 100).toFixed(2)}%`, + ], + ['WN8', stats.wn8.toFixed(0)], + ['WN7', stats.wn7.toFixed(0)], + [], + ['Battles', stats.all.battles], + ['Wins', stats.all.wins], + ['Losses', stats.all.losses], + ['Average tier', stats.avg_tier.toFixed(2)], + [], + ['Hits per battle', stats.special.hpb.toFixed(0)], + ['Damage per battle', stats.special.dpb.toFixed(0)], + ['Kills per battle', stats.special.kpb.toFixed(2)], + ['Spots per battle', stats.special.spb.toFixed(2)], + [ + 'Shots per battle', + (stats.all.shots / stats.all.battles).toFixed(2), + ], + [ + 'Hits per battle', + (stats.all.hits / stats.all.battles).toFixed(2), + ], + ['XP per battle', (stats.all.xp / stats.all.battles).toFixed(2)], + [], + ['Damage ratio', stats.special.damageRatio.toFixed(2)], + ['Kills to death ratio', stats.special.kdr.toFixed(2)], + ] + .map((array) => + array.length === 0 ? '' : `**${array[0]}**: ${array[1]}`, + ) + .join( + '\n', + )}\n\nPowered by [BlitzStars](https://www.blitzstars.com/)`, + ), + ], + }); + }); } export const data = new SlashCommandBuilder() @@ -37,7 +111,6 @@ export const data = new SlashCommandBuilder() .setDescription('The last number of days of stats') .setChoices( { name: '30 Days', value: '30' }, - { name: '60 Days', value: '60' }, { name: '90 Days', value: '90' }, { name: 'Career', value: 'career' }, ) diff --git a/src/commands/verify.ts b/src/commands/verify.ts index c5579b1ee..1e56cace2 100644 --- a/src/commands/verify.ts +++ b/src/commands/verify.ts @@ -6,8 +6,9 @@ import { } from 'discord.js'; import fetch from 'node-fetch'; import discord from '../../discord.json' assert { type: 'json' }; +import { NEGATIVE_COLOR, POSITIVE_COLOR } from '../constants/colors.js'; import { SERVERS } from '../constants/servers.js'; -import AccountList from '../types/accountList.js'; +import getBlitzAccount from '../utilities/getBlitzAccount.js'; export const disabled = true; @@ -15,20 +16,18 @@ export async function execute( interaction: ChatInputCommandInteraction, ) { const ign = interaction.options.getString('ign')!; - const server = interaction.options.getString('server')!; - const serverName = SERVERS[server as keyof typeof SERVERS]; - const players = (await fetch( - `https://api.wotblitz.${server}/wotb/account/list/?application_id=${process.env.WARGAMING_APPLICATION_ID}&search=${ign}`, - ).then((response) => response.json())) as AccountList; + const server = interaction.options.getString( + 'server', + ) as keyof typeof SERVERS; - if (players?.data?.[0]?.nickname === ign) { + getBlitzAccount(interaction, ign, server, async (account) => { // good match const clanData = (await fetch( - `https://api.wotblitz.${server}/wotb/clans/accountinfo/?application_id=${process.env.WARGAMING_APPLICATION_ID}&account_id=${players.data[0].account_id}&extra=clan`, + `https://api.wotblitz.${server}/wotb/clans/accountinfo/?application_id=${process.env.WARGAMING_APPLICATION_ID}&account_id=${account.account_id}&extra=clan`, ).then((response) => response.json())) as { data: { [key: number]: { clan: { tag: string } | null } }; }; - const clan = clanData.data[players.data[0].account_id].clan; + const clan = clanData.data[account.account_id].clan; const clanTag = clan === null ? '' : ` [${clan.tag}]`; if (interaction.member && interaction.guild) { @@ -44,7 +43,7 @@ export async function execute( await interaction.reply({ embeds: [ new EmbedBuilder() - .setColor('#82ff29') + .setColor(POSITIVE_COLOR) .setTitle(`${interaction.user.username} is verified`) .setDescription(`The user is now verified as ${ign}${clanTag}`), ], @@ -58,7 +57,7 @@ export async function execute( await interaction.reply({ embeds: [ new EmbedBuilder() - .setColor('#ff4747') + .setColor(NEGATIVE_COLOR) .setTitle(`${interaction.user.username} failed to verify`) .setDescription( 'I may not have the permission to change your nickname.', @@ -71,27 +70,7 @@ export async function execute( ); }); } - } else { - // no exact match - interaction.reply({ - embeds: [ - new EmbedBuilder() - .setColor('#ff4747') - .setTitle(`Account not found`) - .setDescription( - `Are you sure your username is exactly "${ign}" in the ${serverName} server? I found ${ - players?.data - ? players.data.length < 100 - ? players.data.length - : 'over 100' - : 'no' - } similarly spelled account${ - players?.data?.length !== 1 ? 's' : '' - }. Try re-running the command. Don't make typos and capitalize correctly.`, - ), - ], - }); - } + }); } export const data = new SlashCommandBuilder() diff --git a/src/constants/colors.ts b/src/constants/colors.ts new file mode 100644 index 000000000..e486d0847 --- /dev/null +++ b/src/constants/colors.ts @@ -0,0 +1,3 @@ +export const SKILLED_COLOR = '#8e3cf5'; +export const POSITIVE_COLOR = '#82ff29'; +export const NEGATIVE_COLOR = '#ff4747'; diff --git a/src/types/accountList.ts b/src/types/accountList.ts index bfa8e23ed..d2fbdb563 100644 --- a/src/types/accountList.ts +++ b/src/types/accountList.ts @@ -1,6 +1,11 @@ +export interface Account { + nickname: string; + account_id: number; +} + type AccountList = | { - data?: { nickname: string; account_id: number }[]; + data?: Account[]; } | undefined; diff --git a/src/types/statistics.ts b/src/types/statistics.ts new file mode 100644 index 000000000..f26a77388 --- /dev/null +++ b/src/types/statistics.ts @@ -0,0 +1,50 @@ +export interface PeriodStatistics { + all: { + spotted: number; + hits: number; + frags: number; + wins: number; + losses: number; + capture_points: number; + battles: number; + damage_dealt: number; + damage_received: number; + shots: number; + frags8p: number; + xp: number; + win_and_survived: number; + survived_battles: number; + dropped_capture_points: number; + }; + special: { + winrate: number; + damageRatio: number; + kdr: number; + dpb: number; + kpb: number; + hpb: number; + spb: number; + hitRate: number; + survivalRate: number; + }; + + wn7: number; + wn8: number; + avg_tier: number; + pwp: number; +} + +export interface PlayerStatistics { + statistics: PeriodStatistics; + period30d: PeriodStatistics; + period90d: PeriodStatistics; + + _id: string; + nickname: string; + nickname_lowercase: string; + region: string; + last_battle_time: number; + __v: number; + updatedAt: string; + createdAt: string; +} diff --git a/src/utilities/getBlitzAccount.ts b/src/utilities/getBlitzAccount.ts new file mode 100644 index 000000000..e25055ec8 --- /dev/null +++ b/src/utilities/getBlitzAccount.ts @@ -0,0 +1,44 @@ +import { + CacheType, + ChatInputCommandInteraction, + EmbedBuilder, +} from 'discord.js'; +import { NEGATIVE_COLOR } from '../constants/colors.js'; +import { SERVERS } from '../constants/servers.js'; +import AccountList, { Account } from '../types/accountList.js'; + +export default async function getBlitzAccount( + interaction: ChatInputCommandInteraction, + ign: string, + server: keyof typeof SERVERS, + callback: (account: Account) => void, +) { + const serverName = SERVERS[server]; + const players = (await fetch( + `https://api.wotblitz.${server}/wotb/account/list/?application_id=${process.env.WARGAMING_APPLICATION_ID}&search=${ign}`, + ).then((response) => response.json())) as AccountList; + + if (players?.data?.[0]?.nickname === ign) { + callback(players.data[0]); + } else { + // no exact match + interaction.reply({ + embeds: [ + new EmbedBuilder() + .setColor(NEGATIVE_COLOR) + .setTitle(`Account not found`) + .setDescription( + `Are you sure your username is exactly "${ign}" in the ${serverName} server? I found ${ + players?.data + ? players.data.length < 100 + ? players.data.length + : 'over 100' + : 0 + } similarly spelled account${ + players?.data?.length !== 1 ? 's' : '' + }. Try re-running the command. Don't make typos and capitalize correctly.`, + ), + ], + }); + } +}