feat: eligible command

This commit is contained in:
TresAbhi committed 2023-06-04 00:01:10 +00:00
1 parent 1d0b428921
commit 804fa58d27
13 files changed
+219 -23

No files matched your search

+1
View File
@@ -6,6 +6,7 @@
"ownedtanks",
"Resvg",
"searchplayers",
"SKLLD",
"Smri",
"strvk",
"tankopedia",
+179
View File
@@ -0,0 +1,179 @@
import { SlashCommandBuilder } from 'discord.js';
import getBlitzAccount from '../core/blitz/getBlitzAccount.js';
import getWN8 from '../core/blitz/getWN8.js';
import getWargamingResponse from '../core/blitz/getWargamingResponse.js';
import sumStats from '../core/blitz/sumStats.js';
import { tankopedia } from '../core/blitz/tankopedia.js';
import getPeriodNow from '../core/blitzstars/getPeriodNow.js';
import getPeriodicStart from '../core/blitzstars/getPeriodStart.js';
import getTankStatsOverTime from '../core/blitzstars/getTankStatsOverTime.js';
import { tankAverages } from '../core/blitzstars/tankAverages.js';
import cleanTable, { TableInput } from '../core/interaction/cleanTable.js';
import cmdName from '../core/interaction/cmdName.js';
import negativeEmbed from '../core/interaction/negativeEmbed.js';
import positiveEmbed from '../core/interaction/positiveEmbed.js';
import addUsernameOption from '../core/options/addUsernameOption.js';
import { args } from '../core/process/args.js';
import { CommandRegistry } from '../events/interactionCreate.js';
import { AccountInfo } from '../types/accountInfo.js';
export type SkilledClan = 'SKLLD' | 'SMRI';
export const SKILLED_CLANS: Record<SkilledClan, string> = {
SKLLD: 'Skilled',
SMRI: 'Samurai',
};
export default {
inProduction: true,
inDevelopment: true,
inPublic: false,
command: new SlashCommandBuilder()
.setName(cmdName('eligible'))
.setDescription('Checks eligibility for any Skilled clan')
.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(addUsernameOption),
async execute(interaction) {
const clan = interaction.options.getString('clan') as SkilledClan;
const { id, server } = await getBlitzAccount(interaction);
const accountInfo = await getWargamingResponse<AccountInfo>(
`https://api.wotblitz.${server}/wotb/account/info/?application_id=${args['wargaming-application-id']}&account_id=${id}`,
);
const problems: TableInput = [];
const title = `${accountInfo[id].nickname}'s eligibility for ${SKILLED_CLANS[clan]}`;
let isEligible = false;
let body = '';
if (clan === 'SKLLD') {
const tankStatsOverTime = await getTankStatsOverTime(
interaction,
server,
id,
getPeriodicStart('30'),
getPeriodNow(),
);
const entries = Object.entries(tankStatsOverTime);
const stats = sumStats(entries.map(([, stats]) => stats));
const averageTier =
entries.reduce((accumulator, [tankIdString, stats]) => {
const tankId = parseInt(tankIdString);
const tankTier = tankopedia[tankId].tier;
return accumulator + tankTier * stats.battles;
}, 0) / stats.battles;
const WN8 =
entries.reduce((accumulator, [tankIdString, stats]) => {
const tankId = parseInt(tankIdString);
// edge case where new tanks don't have averages
if (tankAverages[tankId]) {
const tankWN8 = getWN8(tankAverages[tankId].all, stats);
if (isNaN(tankWN8)) return accumulator;
return accumulator + tankWN8 * stats.battles;
} else return accumulator;
}, 0) / stats.battles;
const tier10Damage =
entries.reduce((accumulator, [tankIdString, stats]) => {
const tankId = parseInt(tankIdString);
if (tankopedia[tankId]?.tier === 10) {
const tankStats = tankStatsOverTime[tankId];
return accumulator + tankStats.damage_dealt;
} else return accumulator;
}, 0) /
entries.reduce((accumulator, [tankIdString, stats]) => {
const tankId = parseInt(tankIdString);
if (tankopedia[tankId]?.tier === 10) {
const tankStats = tankStatsOverTime[tankId];
return accumulator + tankStats.battles;
} else return accumulator;
}, 0);
if (stats.battles < 126) {
problems.push(['Battles', `${stats.battles} (${stats.battles - 126})`]);
}
if (averageTier < 8) {
problems.push([
'Average tier',
`${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})`]);
}
if (tier10Damage < 1250) {
problems.push([
'Tier 10 damage',
`${tier10Damage.toFixed(0)} (${tier10Damage - 1250})`,
]);
}
isEligible = problems.length === 0;
body = isEligible
? 'Player is fully eligible.'
: `Player is not eligible. Shortcomings in 30-day statistics:\n\n${cleanTable(
problems,
)}`;
} else if (clan === 'SMRI') {
const stats = accountInfo[id].statistics.all;
if (stats.battles < 1500) {
problems.push([
'Battles',
`${stats.battles} (${stats.battles - 1500})`,
]);
}
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)}%)`,
]);
}
isEligible = problems.length === 0;
body = isEligible
? 'Player is fully eligible.'
: `Player is not eligible. Shortcomings in career statistics:\n\n${cleanTable(
problems,
)}`;
}
await interaction.editReply({
embeds: isEligible
? [positiveEmbed(title, body)]
: [negativeEmbed(title, body)],
});
console.log(
`Displaying ${server}/${id}'s eligibility information for ${clan}`,
);
},
} satisfies CommandRegistry;
+1 -2
View File
@@ -51,8 +51,7 @@ export default {
async execute(interaction) {
const period = interaction.options.getString('period') as StatPeriod;
const blitzAccount = await getBlitzAccount(interaction);
const { id, server } = blitzAccount;
const { id, server } = await getBlitzAccount(interaction);
const accountInfo = await getWargamingResponse<AccountInfo>(
`https://api.wotblitz.${server}/wotb/account/info/?application_id=${args['wargaming-application-id']}&account_id=${id}`,
);
+3 -3
View File
@@ -1,6 +1,6 @@
import { CacheType, ChatInputCommandInteraction } from 'discord.js';
import { BlitzServer } from '../../constants/servers.js';
import errorEmbed from '../interaction/errorEmbed.js';
import negativeEmbed from '../interaction/negativeEmbed.js';
import listAccountsPanServer, {
usernamePatternWithoutPosition,
} from './listAccountsPanServer.js';
@@ -19,7 +19,7 @@ export default async function getBlitzAccount(
if (username === undefined) {
await interaction.editReply({
embeds: [
errorEmbed(
negativeEmbed(
'Username could not be inferred',
"You didn't provide me a username in the command nor was I able to infer your Blitz username from your Discord nickname. Try providing a username manually in the command or using the `/verify` command to set your Discord nickname to your Blitz username.",
),
@@ -42,7 +42,7 @@ export default async function getBlitzAccount(
} else {
await interaction.editReply({
embeds: [
errorEmbed(
negativeEmbed(
'Could not find user',
`I couldn't find user \`${username}\`. Try providing a username manually or using the \`/verify\` command to set your Discord nickname to your Blitz username.`,
),
+2 -2
View File
@@ -1,6 +1,6 @@
import { CacheType, ChatInputCommandInteraction } from 'discord.js';
import { BlitzServer } from '../../constants/servers.js';
import errorEmbed from '../interaction/errorEmbed.js';
import negativeEmbed from '../interaction/negativeEmbed.js';
import { serverAndIdPattern } from './getBlitzAccount.js';
import listClansPanServer from './listClansPanServer.js';
@@ -19,7 +19,7 @@ export default async function getBlitzClan(
} else {
await interaction.editReply({
embeds: [
errorEmbed(
negativeEmbed(
'Could not find clan',
`I couldn't find clan \`${clan}\`. Try selecting a username from the search result.`,
),
+2 -2
View File
@@ -1,7 +1,7 @@
import { CacheType, ChatInputCommandInteraction } from 'discord.js';
import { BLITZ_SERVERS, BlitzServer } from '../../constants/servers.js';
import { TanksStats } from '../../types/tanksStats.js';
import errorEmbed from '../interaction/errorEmbed.js';
import negativeEmbed from '../interaction/negativeEmbed.js';
import { args } from '../process/args.js';
import getWargamingResponse from './getWargamingResponse.js';
@@ -17,7 +17,7 @@ export default async function getTankStats(
if (tankStats[id] === null) {
interaction.editReply({
embeds: [
errorEmbed(
negativeEmbed(
'No tank stats available',
`Wargaming says there is no tank stats for this account. This account may not have any battles/tanks or exist in the ${BLITZ_SERVERS[server]} server.`,
),
+1 -1
View File
@@ -1,6 +1,6 @@
import { Percentile } from '../../constants/percentiles.js';
const WN8_PERCENTILES: [number, Percentile][] = [
export const WN8_PERCENTILES: [number, Percentile][] = [
[0, Percentile.VeryBad],
[300, Percentile.Bad],
[450, Percentile.BelowAverage],
+3 -3
View File
@@ -1,6 +1,6 @@
import { CacheType, ChatInputCommandInteraction } from 'discord.js';
import { go } from 'fuzzysort';
import errorEmbed from '../interaction/errorEmbed.js';
import negativeEmbed from '../interaction/negativeEmbed.js';
import { TANKS, tankopedia } from './tankopedia.js';
export default async function resolveTankId(
@@ -15,7 +15,7 @@ export default async function resolveTankId(
if (searchResult.length === 0) {
await interaction.editReply({
embeds: [
errorEmbed(
negativeEmbed(
'Tank not found',
`Could not find tank by the name "${tank}".`,
),
@@ -32,7 +32,7 @@ export default async function resolveTankId(
} else {
await interaction.editReply({
embeds: [
errorEmbed(
negativeEmbed(
'Tank not found',
`Could not find tank by the ID "${number}".`,
),
@@ -1,7 +1,7 @@
import { EmbedBuilder } from 'discord.js';
import { NEGATIVE_COLOR } from '../../constants/colors.js';
export default function errorEmbed(title: string, description: string) {
export default function negativeEmbed(title: string, description: string) {
return new EmbedBuilder()
.setColor(NEGATIVE_COLOR)
.setTitle(title)
+2 -2
View File
@@ -1,6 +1,6 @@
import errorEmbed from './errorEmbed.js';
import negativeEmbed from './negativeEmbed.js';
export const noMember = errorEmbed(
export const noMember = negativeEmbed(
'User of command is not a member',
'Discord claims that the user of this command does not have an account... strange!',
);
@@ -1,6 +1,6 @@
import errorEmbed from './errorEmbed.js';
import negativeEmbed from './negativeEmbed.js';
export const notTrackedByBlitzStars = errorEmbed(
export const notTrackedByBlitzStars = negativeEmbed(
'No data to display',
'Player is not tracked by BlitzStars.',
);
+9
View File
@@ -0,0 +1,9 @@
import { EmbedBuilder } from 'discord.js';
import { POSITIVE_COLOR } from '../../constants/colors.js';
export default function positiveEmbed(title: string, description: string) {
return new EmbedBuilder()
.setColor(POSITIVE_COLOR)
.setTitle(title)
.setDescription(description);
}
+13 -5
View File
@@ -58,11 +58,19 @@ try {
console.log(`Successfully refreshed ${publicData.length} public command(s).`);
console.log(`Refreshing ${guildCommands.length} guild command(s).`);
const guildData = (await rest.put(
Routes.applicationGuildCommands(getClientId(), discord.sklld_guild_id),
{ body: guildCommands },
)) as RESTPostAPIChatInputApplicationCommandsJSONBody[];
console.log(`Successfully refreshed ${guildData.length} guild command(s).`);
const guildData = [
...((await rest.put(
Routes.applicationGuildCommands(getClientId(), discord.sklld_guild_id),
{ body: guildCommands },
)) as RESTPostAPIChatInputApplicationCommandsJSONBody[]),
...((await rest.put(
Routes.applicationGuildCommands(getClientId(), discord.tres_guild_id),
{ body: guildCommands },
)) as RESTPostAPIChatInputApplicationCommandsJSONBody[]),
];
console.log(
`Successfully refreshed ${guildData.length / 2} guild command(s).`,
);
} catch (error) {
console.error(error);
}