feat: heavy usage of await

This commit is contained in:
TresAbhi committed 2023-05-08 01:45:59 +00:00
1 parent 18360dfe8f
commit 91af52b4b8
14 files changed
+569 -666

No files matched your search

+2 -2
View File
@@ -77,6 +77,7 @@ export default async function interactionCreate(
}
try {
await interaction.deferReply();
command.execute(interaction);
} catch (error) {
console.error(error);
@@ -86,9 +87,8 @@ export default async function interactionCreate(
ephemeral: true,
});
} else {
await interaction.reply({
await interaction.editReply({
content: 'There was an error while executing this command!',
ephemeral: true,
});
}
}
+2 -2
View File
@@ -11,7 +11,7 @@ const executionStart = new Date().getTime();
export default {
inProduction: true,
inDevelopment: false,
inDevelopment: true,
inPublic: true,
command: new SlashCommandBuilder()
@@ -22,7 +22,7 @@ export default {
const currentTime = new Date().getTime();
const uptime = currentTime - executionStart;
await interaction.reply({
await interaction.editReply({
embeds: [
new EmbedBuilder()
.setTitle(`${client.user?.username} debug information`)
+96 -95
View File
@@ -43,106 +43,107 @@ export default {
if (!extractedUser) return;
const { name, server } = extractedUser;
const command = `eligible ${server} ${name} ${clan}`;
getBlitzAccount(interaction, command, name, server, async (account) => {
getBlitzStarsAccount(
interaction,
command,
account.account_id,
name,
async (data) => {
const issues: string[] = [];
const blitzAccount = await getBlitzAccount(interaction, name, server);
if (!blitzAccount) return;
if (clan === 'sklld') {
if (data.period30d.all.battles < 150) {
issues.push(
`**Battles**: ${data.period30d.all.battles} (${
data.period30d.all.battles - 150
})`,
);
}
if (data.period30d.avg_tier < 8) {
issues.push(
`**Tier**: ${data.period30d.avg_tier.toFixed(2)} (${(
data.period30d.avg_tier - 8
).toFixed(2)})`,
);
}
if (data.period30d.special.winrate < 60) {
issues.push(
`**Winrate**: ${data.period30d.special.winrate.toFixed(2)}% (${(
data.period30d.special.winrate - 60
).toFixed(2)}%)`,
);
}
if (data.period30d.wn8 < 2100) {
issues.push(
`**WN8**: ${data.period30d.wn8.toFixed(0)} (${(
data.period30d.wn8 - 2100
).toFixed(0)})`,
);
}
if (data.period30d.special.dpb < 2400) {
issues.push(
`**Damage per battle**: ${data.period30d.special.dpb.toFixed(
0,
)} (${(data.period30d.special.dpb - 2400).toFixed(0)})`,
);
}
} else {
if (data.statistics.all.battles < 1500) {
issues.push(
`**Battles**: ${data.statistics.all.battles} (${
data.statistics.all.battles - 1500
})`,
);
}
if (data.statistics.special.winrate < 53) {
issues.push(
`**Winrate**: ${data.statistics.special.winrate.toFixed(
2,
)}% (${(data.statistics.special.winrate - 53).toFixed(2)}%)`,
);
}
}
const blitzStarsAccount = await getBlitzStarsAccount(
interaction,
blitzAccount.account_id,
name,
);
if (!blitzStarsAccount) return;
const eligible = issues.length === 0;
const issues: string[] = [];
await interaction.editReply({
embeds: [
poweredByBlitzStars(
new EmbedBuilder()
.setTitle(
`${markdownEscape(data.nickname)} is ${
eligible ? '' : 'not '
}eligible for ${CLANS[clan].name}`,
)
.setColor(eligible ? POSITIVE_COLOR : NEGATIVE_COLOR)
.setDescription(
`${
eligible
? `${markdownEscape(
data.nickname,
)} meets all the requirements set for ${
CLANS[clan].name
}.`
: `${markdownEscape(
data.nickname,
)}'s shortcomings for ${CLANS[clan].name} (${
clan === 'sklld' ? '30 days stats' : 'career stats'
}):\n\n${issues.join('\n')}`
}\n\n${blitzLinks(data)}`,
),
),
],
});
if (clan === 'sklld') {
if (blitzStarsAccount.period30d.all.battles < 150) {
issues.push(
`**Battles**: ${blitzStarsAccount.period30d.all.battles} (${
blitzStarsAccount.period30d.all.battles - 150
})`,
);
}
if (blitzStarsAccount.period30d.avg_tier < 8) {
issues.push(
`**Tier**: ${blitzStarsAccount.period30d.avg_tier.toFixed(2)} (${(
blitzStarsAccount.period30d.avg_tier - 8
).toFixed(2)})`,
);
}
if (blitzStarsAccount.period30d.special.winrate < 60) {
issues.push(
`**Winrate**: ${blitzStarsAccount.period30d.special.winrate.toFixed(
2,
)}% (${(blitzStarsAccount.period30d.special.winrate - 60).toFixed(
2,
)}%)`,
);
}
if (blitzStarsAccount.period30d.wn8 < 2100) {
issues.push(
`**WN8**: ${blitzStarsAccount.period30d.wn8.toFixed(0)} (${(
blitzStarsAccount.period30d.wn8 - 2100
).toFixed(0)})`,
);
}
if (blitzStarsAccount.period30d.special.dpb < 2400) {
issues.push(
`**Damage per battle**: ${blitzStarsAccount.period30d.special.dpb.toFixed(
0,
)} (${(blitzStarsAccount.period30d.special.dpb - 2400).toFixed(0)})`,
);
}
} else {
if (blitzStarsAccount.statistics.all.battles < 1500) {
issues.push(
`**Battles**: ${blitzStarsAccount.statistics.all.battles} (${
blitzStarsAccount.statistics.all.battles - 1500
})`,
);
}
if (blitzStarsAccount.statistics.special.winrate < 53) {
issues.push(
`**Winrate**: ${blitzStarsAccount.statistics.special.winrate.toFixed(
2,
)}% (${(blitzStarsAccount.statistics.special.winrate - 53).toFixed(
2,
)}%)`,
);
}
}
console.log(
`Showing ${data.nickname}'s eligibility for ${CLANS[clan].name}`,
);
},
);
const eligible = issues.length === 0;
await interaction.editReply({
embeds: [
poweredByBlitzStars(
new EmbedBuilder()
.setTitle(
`${markdownEscape(blitzStarsAccount.nickname)} is ${
eligible ? '' : 'not '
}eligible for ${CLANS[clan].name}`,
)
.setColor(eligible ? POSITIVE_COLOR : NEGATIVE_COLOR)
.setDescription(
`${
eligible
? `${markdownEscape(
blitzStarsAccount.nickname,
)} meets all the requirements set for ${CLANS[clan].name}.`
: `${markdownEscape(
blitzStarsAccount.nickname,
)}'s shortcomings for ${CLANS[clan].name} (${
clan === 'sklld' ? '30 days stats' : 'career stats'
}):\n\n${issues.join('\n')}`
}\n\n${blitzLinks(blitzStarsAccount)}`,
),
),
],
});
console.log(
`Showing ${blitzStarsAccount.nickname}'s eligibility for ${CLANS[clan].name}`,
);
},
} satisfies CommandRegistry;
+2 -2
View File
@@ -10,7 +10,7 @@ import { client } from '../index.js';
export default {
inProduction: true,
inDevelopment: false,
inDevelopment: true,
inPublic: true,
command: new SlashCommandBuilder()
@@ -18,7 +18,7 @@ export default {
.setDescription('All the help you need about the bot'),
async execute(interaction) {
await interaction.reply({
await interaction.editReply({
embeds: [
new EmbedBuilder()
.setTitle(`${client.user?.username} help`)
+56 -64
View File
@@ -3,8 +3,9 @@ import markdownEscape from 'markdown-escape';
import { CommandRegistry } from '../behaviors/interactionCreate.js';
import { NEGATIVE_COLOR, POSITIVE_COLOR } from '../constants/colors.js';
import { BlitzServer } from '../constants/servers.js';
import { ClanInfo } from '../types/clanInfo.js';
import { AccountInfo } from '../types/accountInfo.js';
import { ClanInfo } from '../types/clanInfo.js';
import { Clan } from '../types/clanList.js';
import addServerChoices from '../utilities/addServerChoices.js';
import { args } from '../utilities/args.js';
import getClan from '../utilities/getClan.js';
@@ -14,13 +15,13 @@ const DEFAULT_THRESHOLD = 7;
export default {
inProduction: true,
inDevelopment: false,
inDevelopment: true,
inPublic: true,
command: new SlashCommandBuilder()
.setName('inactive')
.setDescription('Lists all inactive players')
.addStringOption(addServerChoices)
.addStringOption((option) => addServerChoices(option).setRequired(true))
.addStringOption((option) =>
option
.setName('name')
@@ -39,73 +40,64 @@ export default {
async execute(interaction) {
const name = interaction.options.getString('name')!;
const server = interaction.options.getString('server') as BlitzServer;
const command = `inactive ${server} ${name}`;
getClan(interaction, command, name, server, async (clan) => {
const threshold =
interaction.options.getNumber('threshold')! ?? DEFAULT_THRESHOLD;
const time = new Date().getTime() / 1000;
const clan = (await getClan(interaction, name, server)) as Clan;
if (!clan) return;
getWargamingResponse<ClanInfo>(
`https://api.wotblitz.${server}/wotb/clans/info/?application_id=${args['wargaming-application-id']}&clan_id=${clan.clan_id}`,
interaction,
command,
async (clanInfo) => {
const memberIds = clanInfo[clan.clan_id].members_ids;
const threshold =
interaction.options.getNumber('threshold')! ?? DEFAULT_THRESHOLD;
const time = new Date().getTime() / 1000;
getWargamingResponse<AccountInfo>(
`https://api.wotblitz.${server}/wotb/account/info/?application_id=${
args['wargaming-application-id']
}&account_id=${memberIds.join(',')}`,
interaction,
command,
async (playerPersonalData) => {
const inactiveInfo = memberIds
.map((memberId) => {
const member = playerPersonalData[memberId];
const inactiveDays =
(time - member.last_battle_time) / 60 / 60 / 24;
const clanInfo = await getWargamingResponse<ClanInfo>(
`https://api.wotblitz.${server}/wotb/clans/info/?application_id=${args['wargaming-application-id']}&clan_id=${clan.clan_id}`,
);
return [member.nickname, inactiveDays] as const;
})
.filter(([, inactiveDays]) => inactiveDays >= threshold)
.sort((a, b) => b[1] - a[1])
.map(([nickname, inactiveDays]) => {
const days = Math.floor(inactiveDays);
return `**${markdownEscape(nickname)}**: ${days} day${
days === 1 ? '' : 's'
}`;
});
const hasInactiveMembers = inactiveInfo.length > 0;
const memberIds = clanInfo[clan.clan_id].members_ids;
await interaction.editReply({
embeds: [
new EmbedBuilder()
.setColor(
hasInactiveMembers ? NEGATIVE_COLOR : POSITIVE_COLOR,
)
.setTitle(
hasInactiveMembers
? `${markdownEscape(clan.name)}'s inactive members`
: `No inactive members in ${markdownEscape(clan.name)}`,
)
.setDescription(
hasInactiveMembers
? `Players that exceed the threshold of ${threshold} days:\n\n${inactiveInfo.join(
'\n',
)}`
: `No players exceed the threshold of ${threshold} days.`,
),
],
});
const accountInfo = await getWargamingResponse<AccountInfo>(
`https://api.wotblitz.${server}/wotb/account/info/?application_id=${
args['wargaming-application-id']
}&account_id=${memberIds.join(',')}`,
);
console.log(
`Displaying inactive members in ${clan.name} for ${threshold} days`,
);
},
);
},
);
const inactiveInfo = memberIds
.map((memberId) => {
const member = accountInfo[memberId];
const inactiveDays = (time - member.last_battle_time) / 60 / 60 / 24;
return [member.nickname, inactiveDays] as const;
})
.filter(([, inactiveDays]) => inactiveDays >= threshold)
.sort((a, b) => b[1] - a[1])
.map(([nickname, inactiveDays]) => {
const days = Math.floor(inactiveDays);
return `**${markdownEscape(nickname)}**: ${days} day${
days === 1 ? '' : 's'
}`;
});
const hasInactiveMembers = inactiveInfo.length > 0;
await interaction.editReply({
embeds: [
new EmbedBuilder()
.setColor(hasInactiveMembers ? NEGATIVE_COLOR : POSITIVE_COLOR)
.setTitle(
hasInactiveMembers
? `${markdownEscape(clan.name)}'s inactive members`
: `No inactive members in ${markdownEscape(clan.name)}`,
)
.setDescription(
hasInactiveMembers
? `Players that exceed the threshold of ${threshold} days:\n\n${inactiveInfo.join(
'\n',
)}`
: `No players exceed the threshold of ${threshold} days.`,
),
],
});
console.log(
`Displaying inactive members in ${clan.name} for ${threshold} days`,
);
},
} satisfies CommandRegistry;
+22 -26
View File
@@ -9,15 +9,15 @@ import { args } from '../utilities/args.js';
import getWargamingResponse from '../utilities/getWargamingResponse.js';
export default {
inDevelopment: false,
inDevelopment: true,
inProduction: true,
inPublic: true,
command: new SlashCommandBuilder()
.setName('searchplayers')
.setDescription('Search players in a Blitz server')
.addStringOption(addServerChoices)
.addStringOption(addIGNOption)
.addStringOption((option) => addServerChoices(option).setRequired(true))
.addStringOption((option) => addIGNOption(option).setRequired(true))
.addIntegerOption((option) =>
option
.setName('limit')
@@ -26,34 +26,30 @@ export default {
.setMaxValue(100),
),
execute(interaction) {
async execute(interaction) {
const server = interaction.options.getString('server')!;
const name = interaction.options.getString('name')!;
const limit = interaction.options.getInteger('limit') ?? 25;
const command = `searchplayers ${server} ${name} ${limit}`;
getWargamingResponse<Players>(
const players = await getWargamingResponse<Players>(
`https://api.wotblitz.${server}/wotb/account/list/?application_id=${args['wargaming-application-id']}&search=${name}&limit=${limit}`,
interaction,
command,
(players) => {
interaction.editReply({
embeds: [
new EmbedBuilder()
.setColor(SKILLED_COLOR)
.setTitle(`Player search results for "${markdownEscape(name)}"`)
.setDescription(
`\`\`\`${
players.length === 0
? 'No players found.'
: players
.map((player) => markdownEscape(player.nickname))
.join('\n')
}\`\`\``,
),
],
});
},
);
await interaction.editReply({
embeds: [
new EmbedBuilder()
.setColor(SKILLED_COLOR)
.setTitle(`Player search results for "${markdownEscape(name)}"`)
.setDescription(
`\`\`\`${
players.length === 0
? 'No players found.'
: players
.map((player) => markdownEscape(player.nickname))
.join('\n')
}\`\`\``,
),
],
});
},
} satisfies CommandRegistry;
+143 -172
View File
@@ -3,7 +3,7 @@ import markdownEscape from 'markdown-escape';
import { CommandRegistry } from '../behaviors/interactionCreate.js';
import { SKILLED_COLOR } from '../constants/colors.js';
import { AccountInfo, AllStats } from '../types/accountInfo.js';
import { PeriodStatistics } from '../types/statistics.js';
import { PeriodStatistics, PlayerStatistics } from '../types/statistics.js';
import addIGNOption from '../utilities/addIGNOption.js';
import addServerChoices from '../utilities/addServerChoices.js';
import { args } from '../utilities/args.js';
@@ -18,7 +18,7 @@ type Period = 'today' | '30' | '90' | 'career';
export default {
inProduction: true,
inDevelopment: false,
inDevelopment: true,
inPublic: true,
command: new SlashCommandBuilder()
@@ -48,185 +48,156 @@ export default {
const { name, server } = extractedUser;
const command = `stats ${server} ${name} ${period}`;
getBlitzAccount(
const blitzAccount = await getBlitzAccount(interaction, name, server);
if (!blitzAccount) return;
const blitzStarsAccount = (await getBlitzStarsAccount(
interaction,
command,
blitzAccount.account_id,
name,
server,
async (blitzAccount) => {
getBlitzStarsAccount(
interaction,
command,
blitzAccount.account_id,
name,
async (blitzStarsAccount) => {
async function reply(stats: PeriodStatistics) {
await interaction.editReply({
embeds: [
poweredByBlitzStars(
new EmbedBuilder()
.setColor(SKILLED_COLOR)
.setTitle(
`${markdownEscape(blitzStarsAccount.nickname)}'s ${
period === 'career' ? period : `${period} day`
} stats`,
)
.setDescription(
`${
period === 'today'
? '**⚠ Some stats are not available**\n\n'
: ''
}${[
['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)],
[],
[
'Shots per battle',
(stats.all.shots / stats.all.battles).toFixed(2),
],
['Hits per battle', stats.special.hpb.toFixed(2)],
['Damage per battle', stats.special.dpb.toFixed(0)],
['Kills per battle', stats.special.kpb.toFixed(2)],
['Spots per battle', stats.special.spb.toFixed(2)],
[
'XP per battle',
(stats.all.xp / stats.all.battles).toFixed(0),
],
[],
[
'Damage ratio',
stats.special.damageRatio.toFixed(2),
],
[
'Kills to death ratio',
stats.special.kdr.toFixed(2),
],
]
.filter((array) => array[1] !== `${-Infinity}`)
.map((array) =>
array.length === 0
? ''
: `**${array[0]}**: ${array[1]}`,
)
.join('\n')}\n\n${blitzLinks(blitzStarsAccount)}`,
),
),
],
});
)) as PlayerStatistics;
if (!blitzStarsAccount) return;
console.log(
`Showing ${blitzStarsAccount.nickname}'s ${period} stats`,
);
}
async function reply(stats: PeriodStatistics) {
await interaction.editReply({
embeds: [
poweredByBlitzStars(
new EmbedBuilder()
.setColor(SKILLED_COLOR)
.setTitle(
`${markdownEscape(blitzStarsAccount.nickname)}'s ${
period === 'career' ? period : `${period} day`
} stats`,
)
.setDescription(
`${
period === 'today'
? '**⚠ "Today" stats are in development. Some stats are not available/wrong.**\n\n'
: ''
}${[
['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)],
[],
[
'Shots per battle',
(stats.all.shots / stats.all.battles).toFixed(2),
],
['Hits per battle', stats.special.hpb.toFixed(2)],
['Damage per battle', stats.special.dpb.toFixed(0)],
['Kills per battle', stats.special.kpb.toFixed(2)],
['Spots per battle', stats.special.spb.toFixed(2)],
[
'XP per battle',
(stats.all.xp / stats.all.battles).toFixed(0),
],
[],
['Damage ratio', stats.special.damageRatio.toFixed(2)],
['Kills to death ratio', stats.special.kdr.toFixed(2)],
]
.filter((array) => array[1] !== `${-Infinity}`)
.map((array) =>
array.length === 0 ? '' : `**${array[0]}**: ${array[1]}`,
)
.join('\n')}\n\n${blitzLinks(blitzStarsAccount)}`,
),
),
],
});
let stats: PeriodStatistics | null = null;
console.log(`Showing ${blitzStarsAccount.nickname}'s ${period} stats`);
}
if (period === 'career') {
stats = blitzStarsAccount.statistics;
} else if (period === 'today') {
getWargamingResponse<AccountInfo>(
`https://api.wotblitz.${server}/wotb/account/info/?application_id=${args['wargaming-application-id']}&account_id=${blitzAccount.account_id}`,
interaction,
command,
async (accountInfo) => {
const a1 = blitzStarsAccount.statistics;
const a2 = accountInfo[blitzAccount.account_id].statistics;
let stats: PeriodStatistics | null = null;
const battles = a2.all.battles - a1.all.battles;
if (period === 'career') {
stats = blitzStarsAccount.statistics;
} else if (period === 'today') {
const accountInfo = await getWargamingResponse<AccountInfo>(
`https://api.wotblitz.${server}/wotb/account/info/?application_id=${args['wargaming-application-id']}&account_id=${blitzAccount.account_id}`,
);
function pb(value: (allStats: AllStats) => number) {
return (value(a2.all) - value(a1.all)) / battles;
}
function diff(value: (allStats: AllStats) => number) {
return value(a2.all) - value(a1.all);
}
function span(
value1: (allStats: AllStats) => number,
value2: (allStats: AllStats) => number,
) {
return (
(value1(a2.all) - value1(a1.all)) /
(value2(a2.all) - value2(a1.all))
);
}
const a1 = blitzStarsAccount.statistics;
const a2 = accountInfo[blitzAccount.account_id].statistics;
stats = {
all: {
battles,
capture_points: pb((a) => a.capture_points),
damage_dealt: pb((a) => a.damage_dealt),
damage_received: pb((a) => a.damage_received),
dropped_capture_points: diff(
(a) => a.dropped_capture_points,
),
frags: pb((a) => a.frags),
frags8p: pb((a) => a.frags8p),
hits: diff((a) => a.hits),
losses: diff((a) => a.losses),
max_frags: pb((a) => a.max_frags),
max_frags_tank_id: pb((a) => a.max_frags_tank_id),
max_xp: pb((a) => a.max_xp),
max_xp_tank_id: pb((a) => a.max_xp_tank_id),
shots: diff((a) => a.shots),
spotted: pb((a) => a.spotted),
survived_battles: pb((a) => a.survived_battles),
win_and_survived: pb((a) => a.win_and_survived),
wins: diff((a) => a.wins),
xp: diff((a) => a.xp),
},
special: {
winrate: pb((a) => a.wins) * 100,
dpb: pb((a) => a.damage_dealt),
survivalRate: pb((a) => a.survived_battles) * 100,
damageRatio: span(
(a) => a.damage_dealt,
(a) => a.damage_received,
),
hitRate: pb((a) => a.hits / a.shots),
hpb: pb((a) => a.hits),
kdr:
diff((a) => a.frags) /
diff((a) => a.battles - a.survived_battles),
kpb: pb((a) => a.frags),
spb: pb((a) => a.spotted),
},
const battles = a2.all.battles - a1.all.battles;
avg_tier: -Infinity,
pwp: -Infinity,
wn7: -Infinity,
wn8: -Infinity,
};
reply(stats);
},
);
} else {
stats = blitzStarsAccount[`period${period}d`];
}
if (stats) {
reply(stats);
}
},
function pb(value: (allStats: AllStats) => number) {
return (value(a2.all) - value(a1.all)) / battles;
}
function diff(value: (allStats: AllStats) => number) {
return value(a2.all) - value(a1.all);
}
function span(
value1: (allStats: AllStats) => number,
value2: (allStats: AllStats) => number,
) {
return (
(value1(a2.all) - value1(a1.all)) / (value2(a2.all) - value2(a1.all))
);
},
);
}
stats = {
all: {
battles,
capture_points: pb((a) => a.capture_points),
damage_dealt: pb((a) => a.damage_dealt),
damage_received: pb((a) => a.damage_received),
dropped_capture_points: diff((a) => a.dropped_capture_points),
frags: pb((a) => a.frags),
frags8p: pb((a) => a.frags8p),
hits: diff((a) => a.hits),
losses: diff((a) => a.losses),
max_frags: pb((a) => a.max_frags),
max_frags_tank_id: pb((a) => a.max_frags_tank_id),
max_xp: pb((a) => a.max_xp),
max_xp_tank_id: pb((a) => a.max_xp_tank_id),
shots: diff((a) => a.shots),
spotted: pb((a) => a.spotted),
survived_battles: pb((a) => a.survived_battles),
win_and_survived: pb((a) => a.win_and_survived),
wins: diff((a) => a.wins),
xp: diff((a) => a.xp),
},
special: {
winrate: pb((a) => a.wins) * 100,
dpb: pb((a) => a.damage_dealt),
survivalRate: pb((a) => a.survived_battles) * 100,
damageRatio: span(
(a) => a.damage_dealt,
(a) => a.damage_received,
),
hitRate: pb((a) => a.hits / a.shots),
hpb: pb((a) => a.hits),
kdr:
diff((a) => a.frags) / diff((a) => a.battles - a.survived_battles),
kpb: pb((a) => a.frags),
spb: pb((a) => a.spotted),
},
avg_tier: -Infinity,
pwp: -Infinity,
wn7: -Infinity,
wn8: -Infinity,
};
reply(stats);
} else {
stats = blitzStarsAccount[`period${period}d`];
}
if (stats) {
reply(stats);
}
},
} satisfies CommandRegistry;
+43 -52
View File
@@ -34,7 +34,7 @@ const COMP_TANKS = [
export default {
inProduction: true,
inDevelopment: false,
inDevelopment: true,
inPublic: true,
command: new SlashCommandBuilder()
@@ -53,64 +53,55 @@ export default {
async execute(interaction) {
const tier = interaction.options.getInteger('tier')!;
await interaction.deferReply();
const extractedUser = await extractUser(interaction);
if (!extractedUser) return;
const { name, server } = extractedUser;
const command = `tanks ${server} ${name} ${tier}`;
getBlitzAccount(interaction, command, name, server, async (account) => {
getWargamingResponse<TanksStats>(
`https://api.wotblitz.${server}/wotb/tanks/stats/?application_id=${args['wargaming-application-id']}&account_id=${account.account_id}`,
interaction,
command,
async (tankStats) => {
const tanks = tankStats[account.account_id]
.map((tankData) => tankopedia.data[tankData.tank_id])
.filter((tank) => tank.tier === tier);
const account = await getBlitzAccount(interaction, name, server);
if (!account) return;
await interaction.editReply({
embeds: [
new EmbedBuilder()
.setTitle(
`${markdownEscape(account.nickname)}'s owned ${
tier === null ? '' : `tier ${tier} `
}tanks`,
)
.setDescription(
`${
tanks.length === 0
? `No tanks found in tier ${tier}`
: tanks
.map(
(tank) =>
`${TANK_TYPE_EMOJIS[tank.type]} ${markdownEscape(
tank.name,
)} ${tank.is_premium ? '⭐' : ''}${
COMP_TANKS.includes(tank.tank_id) ? '🏆' : ''
}`,
)
.join('\n')
}\n\n**Legend**\n⭐ = Premium/Collector\n${
tier === 10 ? '🏆 = Comp Tank\n' : ''
}${TANK_TYPE_EMOJIS.heavyTank} = Heavy\n${
TANK_TYPE_EMOJIS.mediumTank
} = Medium\n${TANK_TYPE_EMOJIS.lightTank} = Light\n${
TANK_TYPE_EMOJIS['AT-SPG']
} = Tank Destroyer`,
)
.setColor(SKILLED_COLOR),
],
});
const tankStats = await getWargamingResponse<TanksStats>(
`https://api.wotblitz.${server}/wotb/tanks/stats/?application_id=${args['wargaming-application-id']}&account_id=${account.account_id}`,
);
const tanks = tankStats[account.account_id]
.map((tankData) => tankopedia.data[tankData.tank_id])
.filter((tank) => tank.tier === tier);
console.log(
`Displaying ${account.nickname}'s owned tanks in tier ${tier}`,
);
},
);
await interaction.editReply({
embeds: [
new EmbedBuilder()
.setTitle(
`${markdownEscape(account.nickname)}'s owned ${
tier === null ? '' : `tier ${tier} `
}tanks`,
)
.setDescription(
`${
tanks.length === 0
? `No tanks found in tier ${tier}`
: tanks
.map(
(tank) =>
`${TANK_TYPE_EMOJIS[tank.type]} ${markdownEscape(
tank.name,
)} ${tank.is_premium ? '⭐' : ''}${
COMP_TANKS.includes(tank.tank_id) ? '🏆' : ''
}`,
)
.join('\n')
}\n\n**Legend**\n⭐ = Premium/Collector\n${
tier === 10 ? '🏆 = Comp Tank\n' : ''
}${TANK_TYPE_EMOJIS.heavyTank} = Heavy\n${
TANK_TYPE_EMOJIS.mediumTank
} = Medium\n${TANK_TYPE_EMOJIS.lightTank} = Light\n${
TANK_TYPE_EMOJIS['AT-SPG']
} = Tank Destroyer`,
)
.setColor(SKILLED_COLOR),
],
});
console.log(`Displaying ${account.nickname}'s owned tanks in tier ${tier}`);
},
} satisfies CommandRegistry;
+102 -119
View File
@@ -13,7 +13,7 @@ import getWargamingResponse from '../utilities/getWargamingResponse.js';
export default {
inProduction: true,
inDevelopment: false,
inDevelopment: true,
inPublic: true,
command: new SlashCommandBuilder()
@@ -22,131 +22,114 @@ export default {
.addStringOption((option) => addServerChoices(option).setRequired(true))
.addStringOption((Option) => addIGNOption(Option).setRequired(true)),
execute(interaction) {
async execute(interaction) {
let name = interaction.options.getString('name')!;
const server = interaction.options.getString('server') as BlitzServer;
const command = `verify ${server} ${name}`;
const account = await getBlitzAccount(interaction, name, server);
getBlitzAccount(interaction, command, name, server, async (account) => {
name = account.nickname;
if (!account) return;
getWargamingResponse<PlayerClanData>(
`https://api.wotblitz.${server}/wotb/clans/accountinfo/?application_id=${args['wargaming-application-id']}&account_id=${account.account_id}&extra=clan`,
interaction,
command,
async (clanData) => {
const clan = clanData[account.account_id].clan;
const clanTag = clan === null ? '' : ` [${clan!.tag}]`;
name = account.nickname;
if (
!interaction.guild?.members.me?.permissions.has('ManageNicknames')
) {
interaction.editReply({
embeds: [
new EmbedBuilder()
.setColor(NEGATIVE_COLOR)
.setTitle(
`${markdownEscape(
interaction.user.username,
)} failed to verify`,
)
.setDescription(
"I don't have the permission to change your nickname.",
),
],
});
const clanData = await getWargamingResponse<PlayerClanData>(
`https://api.wotblitz.${server}/wotb/clans/accountinfo/?application_id=${args['wargaming-application-id']}&account_id=${account.account_id}&extra=clan`,
);
const clan = clanData[account.account_id]?.clan;
const clanTag = clan ? ` [${clan!.tag}]` : '';
console.warn(
`${interaction.user.username} failed to verify because of no nickname permission.`,
);
if (!interaction.guild?.members.me?.permissions.has('ManageNicknames')) {
interaction.editReply({
embeds: [
new EmbedBuilder()
.setColor(NEGATIVE_COLOR)
.setTitle(
`${markdownEscape(interaction.user.username)} failed to verify`,
)
.setDescription(
"I don't have the permission to change your nickname.",
),
],
});
return;
}
if (interaction.member) {
const member = interaction.guild?.members.cache.get(
interaction.member?.user.id,
);
if (member) {
await member
?.setNickname(
`${name} (${BLITZ_SERVERS_SHORT[server]})${clanTag}`,
)
.then(async () => {
if (interaction.guildId === discord.guild_id) {
if (
!interaction.guild?.members.me?.permissions.has(
'ManageRoles',
)
) {
await interaction.editReply({
embeds: [
new EmbedBuilder()
.setColor(NEGATIVE_COLOR)
.setTitle(
`${markdownEscape(
interaction.user.username,
)} failed to verify`,
)
.setDescription(
"I don't have the permission to change your manage roles.",
),
],
});
console.warn(
`${interaction.user.username} failed to verify because of no manage roles permission.`,
);
return;
}
await member.roles.remove(discord.verify_role);
await member.roles.add(discord.peasant_role);
}
await interaction.editReply({
embeds: [
new EmbedBuilder()
.setColor(POSITIVE_COLOR)
.setTitle(`${interaction.user.username} is verified`)
.setDescription(
`The user is now verified as ${markdownEscape(
name,
)} (${BLITZ_SERVERS_SHORT[server]})${markdownEscape(
clanTag,
)}`,
),
],
});
console.log(
`${interaction.user.username} verified as ${name} (${BLITZ_SERVERS_SHORT[server]})${clanTag}`,
);
})
.catch(async () => {
await interaction.editReply({
embeds: [
new EmbedBuilder()
.setColor(NEGATIVE_COLOR)
.setTitle(
`${interaction.user.username} failed to verify`,
)
.setDescription(
"I can't change your nickname because you have higher permissions than me.",
),
],
});
console.warn(
'Failed to verify because of higher permissions.',
);
});
}
}
},
console.warn(
`${interaction.user.username} failed to verify because of no nickname permission.`,
);
});
return;
}
if (interaction.member) {
const member = interaction.guild?.members.cache.get(
interaction.member?.user.id,
);
if (member) {
try {
await member.setNickname(
`${name} (${BLITZ_SERVERS_SHORT[server]})${clanTag}`,
);
if (interaction.guildId === discord.guild_id) {
if (
!interaction.guild?.members.me?.permissions.has('ManageRoles')
) {
await interaction.editReply({
embeds: [
new EmbedBuilder()
.setColor(NEGATIVE_COLOR)
.setTitle(
`${markdownEscape(
interaction.user.username,
)} failed to verify`,
)
.setDescription(
"I don't have the permission to change your manage roles.",
),
],
});
console.warn(
`${interaction.user.username} failed to verify because of no manage roles permission.`,
);
return;
}
await member.roles.remove(discord.verify_role);
await member.roles.add(discord.peasant_role);
}
await interaction.editReply({
embeds: [
new EmbedBuilder()
.setColor(POSITIVE_COLOR)
.setTitle(`${interaction.user.username} is verified`)
.setDescription(
`The user is now verified as ${markdownEscape(name)} (${
BLITZ_SERVERS_SHORT[server]
})${markdownEscape(clanTag)}`,
),
],
});
console.log(
`${interaction.user.username} verified as ${name} (${BLITZ_SERVERS_SHORT[server]})${clanTag}`,
);
} catch (error) {
await interaction.editReply({
embeds: [
new EmbedBuilder()
.setColor(NEGATIVE_COLOR)
.setTitle(`${interaction.user.username} failed to verify`)
.setDescription(
"I can't change your nickname because you have higher permissions than me.",
),
],
});
console.warn('Failed to verify because of higher permissions.');
}
}
}
},
} satisfies CommandRegistry;
+1 -1
View File
@@ -14,5 +14,5 @@ export type PlayerClanData = {
clan_id: number;
emblem_set_id: number;
};
};
} | null;
};
+33 -42
View File
@@ -6,58 +6,49 @@ import {
import markdownEscape from 'markdown-escape';
import { NEGATIVE_COLOR } from '../constants/colors.js';
import { BLITZ_SERVERS, BlitzServer } from '../constants/servers.js';
import { Player, Players } from '../types/players.js';
import { Players } from '../types/players.js';
import { args } from './args.js';
import getWargamingResponse from './getWargamingResponse.js';
export default async function getBlitzAccount(
interaction: ChatInputCommandInteraction<CacheType>,
command: string,
name: string,
server: BlitzServer,
callback: (account: Player) => void,
) {
const serverName = BLITZ_SERVERS[server];
getWargamingResponse<Players>(
const players = await getWargamingResponse<Players>(
`https://api.wotblitz.${server}/wotb/account/list/?application_id=${args['wargaming-application-id']}&search=${name}`,
interaction,
command,
async (players) => {
if (
players.length > 0 &&
players[0].nickname.toLowerCase() === name.toLowerCase()
) {
callback(players[0]);
} else {
// no exact match
await interaction.editReply({
embeds: [
new EmbedBuilder()
.setColor(NEGATIVE_COLOR)
.setTitle(`Account not found`)
.setDescription(
`I couldn't find "${markdownEscape(
name,
)}" in the ${serverName} server. I found ${
players
? players.length < 100
? players.length
: 'over 100'
: 0
} similarly spelled account${
players?.length !== 1 ? 's' : ''
}. ${
players && players.length > 0
? `Did you mean "${markdownEscape(players[0].nickname)}"? `
: ''
}Re-run the command and don't make typos.`,
),
],
});
console.log(`Account not found for ${name} in ${serverName} server.`);
}
},
);
if (
players.length > 0 &&
players[0].nickname.toLowerCase() === name.toLowerCase()
) {
return players[0];
} else {
// no exact match
await interaction.editReply({
embeds: [
new EmbedBuilder()
.setColor(NEGATIVE_COLOR)
.setTitle(`Account not found`)
.setDescription(
`I couldn't find "${markdownEscape(
name,
)}" in the ${serverName} server. I found ${
players ? (players.length < 100 ? players.length : 'over 100') : 0
} similarly spelled account${players?.length !== 1 ? 's' : ''}. ${
players && players.length > 0
? `Did you mean "${markdownEscape(players[0].nickname)}"? `
: ''
}Use the \`/verify\` command to fix any issues or re-run the command and don't make typos.`,
),
],
});
console.log(`Account not found for ${name} in ${serverName} server.`);
return null;
}
}
+22 -30
View File
@@ -1,43 +1,35 @@
import {
CacheType,
ChatInputCommandInteraction,
EmbedBuilder,
} from 'discord.js';
import { CacheType, ChatInputCommandInteraction } from 'discord.js';
import markdownEscape from 'markdown-escape';
import fetch from 'node-fetch';
import { NEGATIVE_COLOR } from '../constants/colors.js';
import { PlayerStatistics } from '../types/statistics.js';
import blitzStarsError from './blitzStarsError.js';
import errorEmbed from './errorEmbed.js';
export default async function getBlitzStarsAccount(
interaction: ChatInputCommandInteraction<CacheType>,
command: string,
accountId: number,
name: string,
callback: (account: PlayerStatistics) => void,
) {
if (!interaction.deferred) await interaction.deferReply();
try {
const response = await fetch(
`https://www.blitzstars.com/api/top/player/${accountId}`,
);
const parsed = (await response.json()) as PlayerStatistics;
fetch(`https://www.blitzstars.com/api/top/player/${accountId}`)
.then(async (response) => {
const data = (await response.json()) as PlayerStatistics;
if (parsed.statistics) {
return parsed;
} else throw new Error('No data to display');
} catch (error) {
await interaction.editReply({
embeds: [
errorEmbed(
'No data to display',
`${markdownEscape(name)} is not tracked by BlitzStars.`,
),
],
});
if (data.statistics) {
callback(data);
} else {
await interaction.editReply({
embeds: [
new EmbedBuilder()
.setTitle('No data to display')
.setDescription(
`${markdownEscape(name)} is not tracked by BlitzStars.`,
)
.setColor(NEGATIVE_COLOR),
],
});
console.log(`${name} is not tracked by BlitzStars.`);
console.log(`${name} is not tracked by BlitzStars.`);
}
})
.catch((error) => blitzStarsError(interaction, error, command));
return null;
}
}
+36 -39
View File
@@ -6,55 +6,52 @@ import {
import markdownEscape from 'markdown-escape';
import { NEGATIVE_COLOR } from '../constants/colors.js';
import { BLITZ_SERVERS, BlitzServer } from '../constants/servers.js';
import { Clan, ClanList } from '../types/clanList.js';
import { ClanList } from '../types/clanList.js';
import { args } from './args.js';
import getWargamingResponse from './getWargamingResponse.js';
export default async function getClan(
interaction: ChatInputCommandInteraction<CacheType>,
command: string,
name: string,
server: BlitzServer,
callback: (account: Clan) => void,
) {
const serverName = BLITZ_SERVERS[server];
getWargamingResponse<ClanList>(
const clans = await getWargamingResponse<ClanList>(
`https://api.wotblitz.${server}/wotb/clans/list/?application_id=${args['wargaming-application-id']}&search=${name}`,
interaction,
command,
async (clans) => {
if (
clans.length > 0 &&
(clans[0].name.toLowerCase() === name.toLowerCase() ||
clans[0].tag.toLowerCase() === name.toLowerCase())
) {
callback(clans[0]);
} else {
// no exact match
await interaction.editReply({
embeds: [
new EmbedBuilder()
.setColor(NEGATIVE_COLOR)
.setTitle(`Clan not found`)
.setDescription(
`I couldn't find "${markdownEscape(
name,
)}" in the ${serverName} server. I found ${
clans ? (clans.length < 100 ? clans.length : 'over 100') : 0
} similarly spelled clan${clans?.length !== 1 ? 's' : ''}. ${
clans && clans.length > 0
? `Did you mean "${markdownEscape(
clans[0].name,
)}" or "${markdownEscape(clans[0].tag)}"? `
: ''
}Re-run the command, don't make typos.`,
),
],
});
console.log(`Clan not found for ${name} in ${serverName} server.`);
}
},
);
if (
clans.length > 0 &&
(clans[0].name.toLowerCase() === name.toLowerCase() ||
clans[0].tag.toLowerCase() === name.toLowerCase())
) {
return clans[0];
} else {
// no exact match
await interaction.editReply({
embeds: [
new EmbedBuilder()
.setColor(NEGATIVE_COLOR)
.setTitle(`Clan not found`)
.setDescription(
`I couldn't find "${markdownEscape(
name,
)}" in the ${serverName} server. I found ${
clans ? (clans.length < 100 ? clans.length : 'over 100') : 0
} similarly spelled clan${clans?.length !== 1 ? 's' : ''}. ${
clans && clans.length > 0
? `Did you mean "${markdownEscape(
clans[0].name,
)}" or "${markdownEscape(clans[0].tag)}"? `
: ''
}Re-run the command, don't make typos.`,
),
],
});
console.log(`Clan not found for ${name} in ${serverName} server.`);
return null;
}
}
+9 -20
View File
@@ -1,28 +1,17 @@
import { CacheType, ChatInputCommandInteraction } from 'discord.js';
import fetch from 'node-fetch';
import { WargamingResponse } from '../types/wargamingResponse.js';
import wargamingError from './wargamingError.js';
export default async function getWargamingResponse<Data extends object>(
url: string,
interaction: ChatInputCommandInteraction<CacheType>,
command: string,
callback: (data: Data) => void,
) {
if (!interaction.deferred) await interaction.deferReply();
let parsed: WargamingResponse<Data>;
fetch(url)
.then((response) => response.json() as Promise<WargamingResponse<Data>>)
.then((response) => {
if (response.status === 'ok') {
callback(response.data);
} else {
wargamingError(
interaction,
new Error(`Status ${response.status}`),
command,
);
}
})
.catch((error) => wargamingError(interaction, error, command));
const response = await fetch(url);
parsed = (await response.json()) as WargamingResponse<Data>;
if (parsed.status === 'ok') {
return parsed.data;
} else {
throw new Error(`Wargaming response status ${parsed.status}`);
}
}