mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 10:47:04 +02:00
feat: eligible command
This commit is contained in:
1 parent
1d0b428921
commit
804fa58d27
13 files changed
+219
-23
No files matched your search
Vendored
+1
@@ -6,6 +6,7 @@
|
||||
"ownedtanks",
|
||||
"Resvg",
|
||||
"searchplayers",
|
||||
"SKLLD",
|
||||
"Smri",
|
||||
"strvk",
|
||||
"tankopedia",
|
||||
|
||||
@@ -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;
|
||||
@@ -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}`,
|
||||
);
|
||||
|
||||
@@ -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.`,
|
||||
),
|
||||
|
||||
@@ -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.`,
|
||||
),
|
||||
|
||||
@@ -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,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],
|
||||
|
||||
@@ -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)
|
||||
@@ -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.',
|
||||
);
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user