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",
|
"ownedtanks",
|
||||||
"Resvg",
|
"Resvg",
|
||||||
"searchplayers",
|
"searchplayers",
|
||||||
|
"SKLLD",
|
||||||
"Smri",
|
"Smri",
|
||||||
"strvk",
|
"strvk",
|
||||||
"tankopedia",
|
"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) {
|
async execute(interaction) {
|
||||||
const period = interaction.options.getString('period') as StatPeriod;
|
const period = interaction.options.getString('period') as StatPeriod;
|
||||||
const blitzAccount = await getBlitzAccount(interaction);
|
const { id, server } = await getBlitzAccount(interaction);
|
||||||
const { id, server } = blitzAccount;
|
|
||||||
const accountInfo = await getWargamingResponse<AccountInfo>(
|
const accountInfo = await getWargamingResponse<AccountInfo>(
|
||||||
`https://api.wotblitz.${server}/wotb/account/info/?application_id=${args['wargaming-application-id']}&account_id=${id}`,
|
`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 { CacheType, ChatInputCommandInteraction } from 'discord.js';
|
||||||
import { BlitzServer } from '../../constants/servers.js';
|
import { BlitzServer } from '../../constants/servers.js';
|
||||||
import errorEmbed from '../interaction/errorEmbed.js';
|
import negativeEmbed from '../interaction/negativeEmbed.js';
|
||||||
import listAccountsPanServer, {
|
import listAccountsPanServer, {
|
||||||
usernamePatternWithoutPosition,
|
usernamePatternWithoutPosition,
|
||||||
} from './listAccountsPanServer.js';
|
} from './listAccountsPanServer.js';
|
||||||
@@ -19,7 +19,7 @@ export default async function getBlitzAccount(
|
|||||||
if (username === undefined) {
|
if (username === undefined) {
|
||||||
await interaction.editReply({
|
await interaction.editReply({
|
||||||
embeds: [
|
embeds: [
|
||||||
errorEmbed(
|
negativeEmbed(
|
||||||
'Username could not be inferred',
|
'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.",
|
"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 {
|
} else {
|
||||||
await interaction.editReply({
|
await interaction.editReply({
|
||||||
embeds: [
|
embeds: [
|
||||||
errorEmbed(
|
negativeEmbed(
|
||||||
'Could not find user',
|
'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.`,
|
`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 { CacheType, ChatInputCommandInteraction } from 'discord.js';
|
||||||
import { BlitzServer } from '../../constants/servers.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 { serverAndIdPattern } from './getBlitzAccount.js';
|
||||||
import listClansPanServer from './listClansPanServer.js';
|
import listClansPanServer from './listClansPanServer.js';
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ export default async function getBlitzClan(
|
|||||||
} else {
|
} else {
|
||||||
await interaction.editReply({
|
await interaction.editReply({
|
||||||
embeds: [
|
embeds: [
|
||||||
errorEmbed(
|
negativeEmbed(
|
||||||
'Could not find clan',
|
'Could not find clan',
|
||||||
`I couldn't find clan \`${clan}\`. Try selecting a username from the search result.`,
|
`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 { CacheType, ChatInputCommandInteraction } from 'discord.js';
|
||||||
import { BLITZ_SERVERS, BlitzServer } from '../../constants/servers.js';
|
import { BLITZ_SERVERS, BlitzServer } from '../../constants/servers.js';
|
||||||
import { TanksStats } from '../../types/tanksStats.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 { args } from '../process/args.js';
|
||||||
import getWargamingResponse from './getWargamingResponse.js';
|
import getWargamingResponse from './getWargamingResponse.js';
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ export default async function getTankStats(
|
|||||||
if (tankStats[id] === null) {
|
if (tankStats[id] === null) {
|
||||||
interaction.editReply({
|
interaction.editReply({
|
||||||
embeds: [
|
embeds: [
|
||||||
errorEmbed(
|
negativeEmbed(
|
||||||
'No tank stats available',
|
'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.`,
|
`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';
|
import { Percentile } from '../../constants/percentiles.js';
|
||||||
|
|
||||||
const WN8_PERCENTILES: [number, Percentile][] = [
|
export const WN8_PERCENTILES: [number, Percentile][] = [
|
||||||
[0, Percentile.VeryBad],
|
[0, Percentile.VeryBad],
|
||||||
[300, Percentile.Bad],
|
[300, Percentile.Bad],
|
||||||
[450, Percentile.BelowAverage],
|
[450, Percentile.BelowAverage],
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { CacheType, ChatInputCommandInteraction } from 'discord.js';
|
import { CacheType, ChatInputCommandInteraction } from 'discord.js';
|
||||||
import { go } from 'fuzzysort';
|
import { go } from 'fuzzysort';
|
||||||
import errorEmbed from '../interaction/errorEmbed.js';
|
import negativeEmbed from '../interaction/negativeEmbed.js';
|
||||||
import { TANKS, tankopedia } from './tankopedia.js';
|
import { TANKS, tankopedia } from './tankopedia.js';
|
||||||
|
|
||||||
export default async function resolveTankId(
|
export default async function resolveTankId(
|
||||||
@@ -15,7 +15,7 @@ export default async function resolveTankId(
|
|||||||
if (searchResult.length === 0) {
|
if (searchResult.length === 0) {
|
||||||
await interaction.editReply({
|
await interaction.editReply({
|
||||||
embeds: [
|
embeds: [
|
||||||
errorEmbed(
|
negativeEmbed(
|
||||||
'Tank not found',
|
'Tank not found',
|
||||||
`Could not find tank by the name "${tank}".`,
|
`Could not find tank by the name "${tank}".`,
|
||||||
),
|
),
|
||||||
@@ -32,7 +32,7 @@ export default async function resolveTankId(
|
|||||||
} else {
|
} else {
|
||||||
await interaction.editReply({
|
await interaction.editReply({
|
||||||
embeds: [
|
embeds: [
|
||||||
errorEmbed(
|
negativeEmbed(
|
||||||
'Tank not found',
|
'Tank not found',
|
||||||
`Could not find tank by the ID "${number}".`,
|
`Could not find tank by the ID "${number}".`,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { EmbedBuilder } from 'discord.js';
|
import { EmbedBuilder } from 'discord.js';
|
||||||
import { NEGATIVE_COLOR } from '../../constants/colors.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()
|
return new EmbedBuilder()
|
||||||
.setColor(NEGATIVE_COLOR)
|
.setColor(NEGATIVE_COLOR)
|
||||||
.setTitle(title)
|
.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',
|
'User of command is not a member',
|
||||||
'Discord claims that the user of this command does not have an account... strange!',
|
'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',
|
'No data to display',
|
||||||
'Player is not tracked by BlitzStars.',
|
'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(`Successfully refreshed ${publicData.length} public command(s).`);
|
||||||
|
|
||||||
console.log(`Refreshing ${guildCommands.length} guild command(s).`);
|
console.log(`Refreshing ${guildCommands.length} guild command(s).`);
|
||||||
const guildData = (await rest.put(
|
const guildData = [
|
||||||
Routes.applicationGuildCommands(getClientId(), discord.sklld_guild_id),
|
...((await rest.put(
|
||||||
{ body: guildCommands },
|
Routes.applicationGuildCommands(getClientId(), discord.sklld_guild_id),
|
||||||
)) as RESTPostAPIChatInputApplicationCommandsJSONBody[];
|
{ body: guildCommands },
|
||||||
console.log(`Successfully refreshed ${guildData.length} guild command(s).`);
|
)) 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) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user