mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 18:57:03 +02:00
feat: simpler subcommands
This commit is contained in:
1 parent
f965452c5d
commit
78e4c82f20
28 files changed
+397
-461
No files matched your search
@@ -58,7 +58,7 @@ export const eligibleCommand: CommandRegistry = {
|
||||
let body = '';
|
||||
|
||||
if (clan === 'SKLLD') {
|
||||
const { diffed } = await getDiffedTankStats(
|
||||
const { diff: diffed } = await getDiffedTankStats(
|
||||
server,
|
||||
id,
|
||||
getTimeDaysAgo(server, 30),
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { SlashCommandBuilder } from 'discord.js';
|
||||
import { CYCLIC_API } from '../constants/cyclic';
|
||||
import { WARGAMING_APPLICATION_ID } from '../constants/wargamingApplicationID';
|
||||
import getWargamingResponse from '../core/blitz/getWargamingResponse';
|
||||
import resolveTankId from '../core/blitz/resolveTankId';
|
||||
import addStatTypeSubCommandGroups from '../core/discord/addStatTypeSubCommandGroups';
|
||||
import addUsernameChoices from '../core/discord/addUsernameChoices';
|
||||
import autocompleteTanks from '../core/discord/autocompleteTanks';
|
||||
import autocompleteUsername from '../core/discord/autocompleteUsername';
|
||||
import interactionToURL from '../core/discord/interactionToURL';
|
||||
@@ -24,7 +24,7 @@ export const evolutionCommand = new Promise<CommandRegistryRaw>(
|
||||
new SlashCommandBuilder()
|
||||
.setName('evolution')
|
||||
.setDescription('Evolution of statistics'),
|
||||
false,
|
||||
(option) => option.addStringOption(addUsernameChoices),
|
||||
);
|
||||
|
||||
resolve({
|
||||
@@ -59,7 +59,6 @@ export const evolutionCommand = new Promise<CommandRegistryRaw>(
|
||||
return [
|
||||
await evolution(commandGroup, period, player, tankId),
|
||||
primaryButton(path, 'Refresh'),
|
||||
// linkButton(`${CYCLIC_API}/${path}`, 'Embed'),
|
||||
linkButton(
|
||||
`https://www.blitzstars.com/player/${player.region}/${nickname}${
|
||||
commandGroup === 'tank' ? `/tank/${tankId!}` : ''
|
||||
@@ -75,7 +74,7 @@ export const evolutionCommand = new Promise<CommandRegistryRaw>(
|
||||
},
|
||||
|
||||
async button(interaction) {
|
||||
const url = new URL(`${CYCLIC_API}/${interaction.customId}`);
|
||||
const url = new URL(`https://example.com/${interaction.customId}`);
|
||||
const path = url.pathname.split('/').filter(Boolean);
|
||||
const commandGroup = path[1] as StatType;
|
||||
const player = await resolvePlayerFromButton(interaction);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { SlashCommandBuilder } from 'discord.js';
|
||||
import { TreeTypeString } from '../components/Tanks';
|
||||
import { CYCLIC_API } from '../constants/cyclic';
|
||||
import { WARGAMING_APPLICATION_ID } from '../constants/wargamingApplicationID';
|
||||
import getWargamingResponse from '../core/blitz/getWargamingResponse';
|
||||
import resolveTankId from '../core/blitz/resolveTankId';
|
||||
import addStatTypeSubCommandGroups from '../core/discord/addStatTypeSubCommandGroups';
|
||||
import addUsernameChoices from '../core/discord/addUsernameChoices';
|
||||
import autocompleteTanks from '../core/discord/autocompleteTanks';
|
||||
import autocompleteUsername from '../core/discord/autocompleteUsername';
|
||||
import interactionToURL from '../core/discord/interactionToURL';
|
||||
@@ -25,6 +25,7 @@ export const fullStatsCommand = new Promise<CommandRegistryRaw>(
|
||||
new SlashCommandBuilder()
|
||||
.setName('full-stats')
|
||||
.setDescription('Full in-game statistics'),
|
||||
(option) => option.addStringOption(addUsernameChoices),
|
||||
);
|
||||
|
||||
resolve({
|
||||
@@ -82,7 +83,6 @@ export const fullStatsCommand = new Promise<CommandRegistryRaw>(
|
||||
: null,
|
||||
),
|
||||
primaryButton(path, 'Refresh'),
|
||||
// linkButton(`${CYCLIC_API}/${path}`, 'Embed'),
|
||||
linkButton(
|
||||
`https://www.blitzstars.com/player/${player.region}/${nickname}`,
|
||||
'BlitzStars',
|
||||
@@ -96,7 +96,7 @@ export const fullStatsCommand = new Promise<CommandRegistryRaw>(
|
||||
},
|
||||
|
||||
async button(interaction) {
|
||||
const url = new URL(`${CYCLIC_API}/${interaction.customId}`);
|
||||
const url = new URL(`https://example.com/${interaction.customId}`);
|
||||
const path = url.pathname.split('/').filter(Boolean);
|
||||
const commandGroup = path[1] as StatType;
|
||||
const player = await resolvePlayerFromButton(interaction);
|
||||
|
||||
+91
-71
@@ -1,28 +1,77 @@
|
||||
import { SlashCommandBuilder } from 'discord.js';
|
||||
import AllStatsOverview from '../components/AllStatsOverview';
|
||||
import NoData, { NoDataType } from '../components/NoData';
|
||||
import { TreeTypeString } from '../components/Tanks';
|
||||
import { CYCLIC_API } from '../constants/cyclic';
|
||||
import TitleBar from '../components/TitleBar';
|
||||
import Wrapper from '../components/Wrapper';
|
||||
import { WARGAMING_APPLICATION_ID } from '../constants/wargamingApplicationID';
|
||||
import { StatFilters, filterStats } from '../core/blitz/filterStats';
|
||||
import getWargamingResponse from '../core/blitz/getWargamingResponse';
|
||||
import resolveTankId from '../core/blitz/resolveTankId';
|
||||
import { filtersToDescription } from '../core/blitzkrieg/filtersToDescription';
|
||||
import { getBlitzStarsLinkButton } from '../core/blitzstars/getBlitzStarsLinkButton';
|
||||
import getDiffedTankStats from '../core/blitzstars/getDiffedTankStats';
|
||||
import { PeriodType } from '../core/discord/addPeriodSubCommands';
|
||||
import addStatTypeSubCommandGroups from '../core/discord/addStatTypeSubCommandGroups';
|
||||
import addUsernameChoices from '../core/discord/addUsernameChoices';
|
||||
import autocompleteTanks from '../core/discord/autocompleteTanks';
|
||||
import autocompleteUsername from '../core/discord/autocompleteUsername';
|
||||
import interactionToURL from '../core/discord/interactionToURL';
|
||||
import linkButton from '../core/discord/linkButton';
|
||||
import primaryButton from '../core/discord/primaryButton';
|
||||
import { refreshButton } from '../core/discord/refreshButton';
|
||||
import resolvePeriodFromButton from '../core/discord/resolvePeriodFromButton';
|
||||
import resolvePeriodFromCommand from '../core/discord/resolvePeriodFromCommand';
|
||||
import resolvePeriodFromCommand, {
|
||||
ResolvedPeriod,
|
||||
} from '../core/discord/resolvePeriodFromCommand';
|
||||
import resolvePlayerFromButton from '../core/discord/resolvePlayerFromButton';
|
||||
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
|
||||
import resolvePlayerFromCommand, {
|
||||
ResolvedPlayer,
|
||||
} from '../core/discord/resolvePlayerFromCommand';
|
||||
import { CommandRegistryRaw } from '../events/interactionCreate';
|
||||
import stats, { MultiTankFilters, StatType } from '../renderers/stats';
|
||||
import { AccountInfo } from '../types/accountInfo';
|
||||
import { PlayerClanData } from '../types/playerClanData';
|
||||
|
||||
export default async function render(
|
||||
{ region, id }: ResolvedPlayer,
|
||||
{ start, end, name: statsName }: ResolvedPeriod,
|
||||
filters: StatFilters,
|
||||
) {
|
||||
const nickname = (
|
||||
await getWargamingResponse<AccountInfo>(
|
||||
`https://api.wotblitz.${region}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`,
|
||||
)
|
||||
)[id].nickname;
|
||||
const clan = (
|
||||
await getWargamingResponse<PlayerClanData>(
|
||||
`https://api.wotblitz.${region}/wotb/clans/accountinfo/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}&extra=clan`,
|
||||
)
|
||||
)[id]?.clan;
|
||||
const clanImage = clan
|
||||
? `https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clan.emblem_set_id}.png`
|
||||
: undefined;
|
||||
const diffedTankStats = await getDiffedTankStats(region, id, start, end);
|
||||
const { stats, supplementary } = await filterStats(diffedTankStats, filters);
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<TitleBar
|
||||
name={nickname}
|
||||
image={clanImage}
|
||||
description={`${statsName} • ${await filtersToDescription(filters)}`}
|
||||
/>
|
||||
|
||||
{!stats?.battles && <NoData type={NoDataType.BattlesInPeriod} />}
|
||||
{stats?.battles > 0 && (
|
||||
<AllStatsOverview stats={stats} supplementaryStats={supplementary} />
|
||||
)}
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
|
||||
export const statsCommand = new Promise<CommandRegistryRaw>(async (resolve) => {
|
||||
const command = await addStatTypeSubCommandGroups(
|
||||
new SlashCommandBuilder()
|
||||
.setName('stats')
|
||||
.setDescription('Regular battles statistics'),
|
||||
(option) => option.addStringOption(addUsernameChoices),
|
||||
);
|
||||
|
||||
resolve({
|
||||
@@ -32,59 +81,38 @@ export const statsCommand = new Promise<CommandRegistryRaw>(async (resolve) => {
|
||||
command,
|
||||
|
||||
async handler(interaction) {
|
||||
const commandGroup = interaction.options.getSubcommandGroup(
|
||||
true,
|
||||
) as StatType;
|
||||
const player = await resolvePlayerFromCommand(interaction);
|
||||
const resolvedPlayer = await resolvePlayerFromCommand(interaction);
|
||||
const resolvedPeriod = resolvePeriodFromCommand(
|
||||
player.region,
|
||||
resolvedPlayer.region,
|
||||
interaction,
|
||||
);
|
||||
const period = interaction.options.getString('period');
|
||||
const tankIdRaw = interaction.options.getString('tank')!;
|
||||
const tankId =
|
||||
commandGroup === 'tank' ? await resolveTankId(tankIdRaw) : null;
|
||||
const start = interaction.options.getInteger('start');
|
||||
const end = interaction.options.getInteger('end');
|
||||
const filters = {
|
||||
nation: interaction.options.getString('nation') ?? undefined,
|
||||
tier:
|
||||
parseInt(interaction.options.getString('tier') ?? '0') || undefined,
|
||||
tankType: interaction.options.getString('tank-type') ?? undefined,
|
||||
treeType:
|
||||
(interaction.options.getString('tree-type') as
|
||||
| TreeTypeString
|
||||
| undefined) ?? undefined,
|
||||
tank:
|
||||
parseInt(interaction.options.getString('tank') ?? '0') || undefined,
|
||||
} satisfies StatFilters;
|
||||
const path = interactionToURL(interaction, {
|
||||
...player,
|
||||
tankId,
|
||||
start,
|
||||
end,
|
||||
period,
|
||||
...resolvedPlayer,
|
||||
...((interaction.options.getSubcommand() as PeriodType) === 'custom'
|
||||
? {
|
||||
start: interaction.options.getInteger('start', true),
|
||||
end: interaction.options.getInteger('end', true),
|
||||
}
|
||||
: undefined),
|
||||
...filters,
|
||||
});
|
||||
const { nickname } = (
|
||||
await getWargamingResponse<AccountInfo>(
|
||||
`https://api.wotblitz.${player.region}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${player.id}`,
|
||||
)
|
||||
)[player.id];
|
||||
|
||||
const tierOption = interaction.options.getString('tier');
|
||||
|
||||
return [
|
||||
await stats(
|
||||
commandGroup,
|
||||
resolvedPeriod,
|
||||
player,
|
||||
commandGroup === 'tank'
|
||||
? tankId
|
||||
: commandGroup === 'multi-tank'
|
||||
? ({
|
||||
'tank-type':
|
||||
interaction.options.getString('tank-type') ?? undefined,
|
||||
nation: interaction.options.getString('nation') ?? undefined,
|
||||
tier: tierOption ? parseInt(tierOption) : undefined,
|
||||
'tree-type': (interaction.options.getString('tree-type') ??
|
||||
undefined) as TreeTypeString | undefined,
|
||||
} satisfies Partial<MultiTankFilters>)
|
||||
: null,
|
||||
),
|
||||
primaryButton(path, 'Refresh'),
|
||||
// linkButton(`${CYCLIC_API}/${path}`, 'Embed'),
|
||||
linkButton(
|
||||
`https://www.blitzstars.com/player/${player.region}/${nickname}`,
|
||||
'BlitzStars',
|
||||
),
|
||||
await render(resolvedPlayer, resolvedPeriod, filters),
|
||||
refreshButton(interaction, path),
|
||||
await getBlitzStarsLinkButton(resolvedPlayer.region, resolvedPlayer.id),
|
||||
];
|
||||
},
|
||||
|
||||
@@ -94,27 +122,19 @@ export const statsCommand = new Promise<CommandRegistryRaw>(async (resolve) => {
|
||||
},
|
||||
|
||||
async button(interaction) {
|
||||
const url = new URL(`${CYCLIC_API}/${interaction.customId}`);
|
||||
const path = url.pathname.split('/').filter(Boolean);
|
||||
const commandGroup = path[1] as StatType;
|
||||
const url = new URL(`https://example.com${interaction.customId}`);
|
||||
const player = await resolvePlayerFromButton(interaction);
|
||||
const period = resolvePeriodFromButton(player.region, interaction);
|
||||
|
||||
return await stats(
|
||||
commandGroup,
|
||||
period,
|
||||
player,
|
||||
commandGroup === 'tank'
|
||||
? parseInt(url.searchParams.get('tankId')!)
|
||||
: commandGroup === 'multi-tank'
|
||||
? ({
|
||||
'tank-type': url.searchParams.get('tank-type')!,
|
||||
nation: url.searchParams.get('nation')!,
|
||||
tier: parseInt(url.searchParams.get('tier')!),
|
||||
'tree-type': url.searchParams.get('tree-type') as TreeTypeString,
|
||||
} satisfies MultiTankFilters)
|
||||
: null,
|
||||
);
|
||||
return await render(player, period, {
|
||||
nation: url.searchParams.get('nation') ?? undefined,
|
||||
tank: parseInt(url.searchParams.get('tank') ?? '0') || undefined,
|
||||
tankType: url.searchParams.get('tank-type') ?? undefined,
|
||||
treeType:
|
||||
(url.searchParams.get('tree-type') as TreeTypeString | undefined) ??
|
||||
undefined,
|
||||
tier: parseInt(url.searchParams.get('tier') ?? '0') || undefined,
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -65,7 +65,7 @@ export const todayCommand: CommandRegistry = {
|
||||
return [
|
||||
await today(player, cutoff, maximized, showTotal, false),
|
||||
primaryButton(path, 'Refresh'),
|
||||
// linkButton(`${CYCLIC_API}/${path}`, 'Embed'),
|
||||
// linkButton(`https://example.com/${path}`, 'Embed'),
|
||||
linkButton(
|
||||
`https://www.blitzstars.com/player/${player.region}/${nickname}`,
|
||||
'BlitzStars',
|
||||
|
||||
@@ -2,7 +2,6 @@ import {
|
||||
TANK_ICONS,
|
||||
TANK_ICONS_COLLECTOR,
|
||||
TANK_ICONS_PREMIUM,
|
||||
TankType,
|
||||
} from '../../../core/blitz/tankopedia';
|
||||
import { theme } from '../../../stitches.config';
|
||||
|
||||
@@ -12,11 +11,17 @@ export enum TreeTypeEnum {
|
||||
Collector,
|
||||
}
|
||||
|
||||
export type TreeTypeString = 'techtree' | 'premium' | 'collector';
|
||||
export type TreeTypeString = 'tech-tree' | 'premium' | 'collector';
|
||||
|
||||
export const TREE_TYPE_NAMES: Record<TreeTypeString, string> = {
|
||||
'tech-tree': 'Tech tree',
|
||||
premium: 'Premium',
|
||||
collector: 'Collector',
|
||||
};
|
||||
|
||||
export interface ItemProps {
|
||||
image?: string;
|
||||
tankType?: TankType;
|
||||
tankType?: string;
|
||||
name: string;
|
||||
treeType: TreeTypeEnum;
|
||||
}
|
||||
|
||||
@@ -2,17 +2,11 @@ import { theme } from '../stitches.config';
|
||||
|
||||
export interface TitleBarProps {
|
||||
name: string;
|
||||
nameDiscriminator?: string;
|
||||
image?: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export default function TitleBar({
|
||||
name,
|
||||
nameDiscriminator,
|
||||
image,
|
||||
description,
|
||||
}: TitleBarProps) {
|
||||
export default function TitleBar({ name, image, description }: TitleBarProps) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
@@ -33,9 +27,8 @@ export default function TitleBar({
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
gap: 4,
|
||||
}}
|
||||
>
|
||||
@@ -51,24 +44,11 @@ export default function TitleBar({
|
||||
color: theme.colors.textHighContrast,
|
||||
fontWeight: 900,
|
||||
whiteSpace: 'nowrap',
|
||||
maxWidth: nameDiscriminator === undefined ? Infinity : 240,
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
{name}
|
||||
</span>
|
||||
{nameDiscriminator && (
|
||||
<span
|
||||
style={{
|
||||
fontSize: 32,
|
||||
color: theme.colors.textLowContrast,
|
||||
fontWeight: 900,
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
{nameDiscriminator}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<span style={{ color: theme.colors.textLowContrast, fontSize: 16 }}>
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export const CYCLIC_API = 'https://blitzkrieg-embeds.cyclic.app';
|
||||
@@ -0,0 +1,79 @@
|
||||
// BIG TODO: reorganize code folder
|
||||
|
||||
import { TreeTypeString } from '../../components/Tanks';
|
||||
import { DiffedTankStats } from '../blitzstars/getDiffedTankStats';
|
||||
import { tankAverages } from '../blitzstars/tankAverages';
|
||||
import calculateWN8 from './calculateWN8';
|
||||
import sumStats from './sumStats';
|
||||
import { tankopedia } from './tankopedia';
|
||||
|
||||
export interface StatFilters {
|
||||
nation?: string;
|
||||
tier?: number;
|
||||
tankType?: string;
|
||||
treeType?: TreeTypeString;
|
||||
tank?: number;
|
||||
}
|
||||
|
||||
export async function filterStats(
|
||||
{ diff, order }: DiffedTankStats,
|
||||
filters: StatFilters,
|
||||
) {
|
||||
// tank, if provided, takes priority over all other filters
|
||||
if (filters.tank) filters = { tank: filters.tank };
|
||||
|
||||
const awaitedTankopedia = await tankopedia;
|
||||
const awaitedTankAverages = await tankAverages;
|
||||
const filteredOrder = order.filter((id) => {
|
||||
const entry = awaitedTankopedia[id];
|
||||
|
||||
return (
|
||||
entry &&
|
||||
(filters.nation === undefined || entry.nation === filters.nation) &&
|
||||
(filters.tier === undefined || entry.tier === filters.tier) &&
|
||||
(filters.tankType === undefined || entry.type === filters.tankType) &&
|
||||
(filters.treeType === undefined ||
|
||||
(filters.treeType === 'collector' && entry.is_collectible) ||
|
||||
(filters.treeType === 'premium' &&
|
||||
entry.is_premium &&
|
||||
!entry.is_collectible) ||
|
||||
(filters.treeType === 'tech-tree' &&
|
||||
!entry.is_collectible &&
|
||||
!entry.is_premium)) &&
|
||||
(filters.tank === undefined || entry.tank_id === filters.tank)
|
||||
);
|
||||
});
|
||||
const stats = sumStats(filteredOrder.map((id) => diff[id]));
|
||||
const battlesOfTanksWithAverages = filteredOrder.reduce<number>(
|
||||
(accumulator, id) =>
|
||||
awaitedTankAverages[id] ? accumulator + diff[id].battles : accumulator,
|
||||
0,
|
||||
);
|
||||
const battlesOfTanksWithTankopediaEntry = filteredOrder.reduce<number>(
|
||||
(accumulator, id) =>
|
||||
awaitedTankopedia[id] ? accumulator + diff[id].battles : accumulator,
|
||||
0,
|
||||
);
|
||||
const supplementary = {
|
||||
WN8:
|
||||
filteredOrder.reduce<number>(
|
||||
(accumulator, id) =>
|
||||
awaitedTankAverages[id]
|
||||
? accumulator +
|
||||
calculateWN8(awaitedTankAverages[id].all, diff[id]) *
|
||||
diff[id].battles
|
||||
: accumulator,
|
||||
0,
|
||||
) / battlesOfTanksWithAverages,
|
||||
tier:
|
||||
filteredOrder.reduce<number>(
|
||||
(accumulator, id) =>
|
||||
awaitedTankopedia[id]
|
||||
? accumulator + awaitedTankopedia[id]!.tier * diff[id].battles
|
||||
: accumulator,
|
||||
0,
|
||||
) / battlesOfTanksWithTankopediaEntry,
|
||||
};
|
||||
|
||||
return { stats, supplementary };
|
||||
}
|
||||
@@ -2,8 +2,7 @@ import { deburr } from 'lodash';
|
||||
import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
|
||||
import { context } from '../blitzkrieg/context';
|
||||
import getWargamingResponse from './getWargamingResponse';
|
||||
|
||||
export type TankType = 'AT-SPG' | 'lightTank' | 'mediumTank' | 'heavyTank';
|
||||
import { TreeTypeString } from '../../components/Tanks';
|
||||
|
||||
export interface TankopediaEntry {
|
||||
name: string;
|
||||
@@ -14,10 +13,10 @@ export interface TankopediaEntry {
|
||||
cost: { price_credit: number; price_gold: number };
|
||||
images: { preview: string; normal: string };
|
||||
tank_id: number;
|
||||
type: TankType;
|
||||
type: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
type asd = TreeTypeString
|
||||
export interface Tankopedia {
|
||||
[id: number]: TankopediaEntry | undefined;
|
||||
}
|
||||
@@ -77,21 +76,21 @@ export const tankopediaInfo = getWargamingResponse<TankopediaInfo>(
|
||||
);
|
||||
console.log('Cached tankopedia info');
|
||||
|
||||
export const TANK_ICONS: Record<TankType, string> = {
|
||||
export const TANK_ICONS: Record<string, string> = {
|
||||
'AT-SPG': 'https://i.imgur.com/BIHSEH0.png',
|
||||
lightTank: 'https://i.imgur.com/CSNha5V.png',
|
||||
mediumTank: 'https://i.imgur.com/wvf3ltm.png',
|
||||
heavyTank: 'https://i.imgur.com/ECeqlZa.png',
|
||||
};
|
||||
|
||||
export const TANK_ICONS_PREMIUM: Record<TankType, string> = {
|
||||
export const TANK_ICONS_PREMIUM: Record<string, string> = {
|
||||
'AT-SPG': 'https://i.imgur.com/TCu3EdR.png',
|
||||
lightTank: 'https://i.imgur.com/zdkpTRb.png',
|
||||
mediumTank: 'https://i.imgur.com/3z7eHX6.png',
|
||||
heavyTank: 'https://i.imgur.com/P3vbmyA.png',
|
||||
};
|
||||
|
||||
export const TANK_ICONS_COLLECTOR: Record<TankType, string> = {
|
||||
export const TANK_ICONS_COLLECTOR: Record<string, string> = {
|
||||
'AT-SPG': 'https://i.imgur.com/WTjeirB.png',
|
||||
lightTank: 'https://i.imgur.com/EwhtKkU.png',
|
||||
mediumTank: 'https://i.imgur.com/u8YDMBh.png',
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { TREE_TYPE_NAMES } from '../../components/Tanks';
|
||||
import { StatFilters } from '../blitz/filterStats';
|
||||
import resolveTankName from '../blitz/resolveTankName';
|
||||
import { TIER_ROMAN_NUMERALS, Tier, tankopediaInfo } from '../blitz/tankopedia';
|
||||
|
||||
export async function filtersToDescription({
|
||||
nation,
|
||||
tier,
|
||||
tankType,
|
||||
treeType,
|
||||
tank,
|
||||
}: StatFilters) {
|
||||
const awaitedTankopediaInfo = await tankopediaInfo;
|
||||
const info: string[] = [];
|
||||
|
||||
if (tank) return await resolveTankName(tank);
|
||||
if (nation) info.push(awaitedTankopediaInfo.vehicle_nations[nation]);
|
||||
if (tier) info.push(`Tier ${TIER_ROMAN_NUMERALS[tier as Tier]}`);
|
||||
if (tankType) info.push(awaitedTankopediaInfo.vehicle_types[tankType]);
|
||||
if (treeType) info.push(TREE_TYPE_NAMES[treeType]);
|
||||
|
||||
return info.length === 0 ? 'No filters' : info.join(', ');
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Region } from '../../constants/regions';
|
||||
import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
|
||||
import { AccountInfo } from '../../types/accountInfo';
|
||||
import getWargamingResponse from '../blitz/getWargamingResponse';
|
||||
import linkButton from '../discord/linkButton';
|
||||
|
||||
export async function getBlitzStarsLinkButton(region: Region, id: number) {
|
||||
const { nickname } = (
|
||||
await getWargamingResponse<AccountInfo>(
|
||||
`https://api.wotblitz.${region}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`,
|
||||
)
|
||||
)[id];
|
||||
|
||||
return linkButton(
|
||||
`https://www.blitzstars.com/player/${region}/${nickname}`,
|
||||
'BlitzStars',
|
||||
);
|
||||
}
|
||||
@@ -34,6 +34,11 @@ export const emptyTankHistoryNode: TankHistoryRaw = {
|
||||
all: emptyAllStats,
|
||||
};
|
||||
|
||||
export interface DiffedTankStats {
|
||||
diff: Record<number, AllStats>;
|
||||
order: number[];
|
||||
}
|
||||
|
||||
// BIG TODO: merge this neatly with diffNormalizedTankStats
|
||||
export default async function getDiffedTankStats(
|
||||
server: Region,
|
||||
@@ -66,7 +71,7 @@ export default async function getDiffedTankStats(
|
||||
const playedTanks: number[] = [];
|
||||
|
||||
// fetch history node right before "start" and right before "end"
|
||||
const diffed: Record<number, AllStats> = {};
|
||||
const diff: Record<number, AllStats> = {};
|
||||
inRangeTanks.forEach((tankId) => {
|
||||
const sortedHistory = tankSortedHistory[tankId];
|
||||
const previousIndex =
|
||||
@@ -77,30 +82,30 @@ export default async function getDiffedTankStats(
|
||||
const latest =
|
||||
sortedHistory[latestIndex] ?? sortedHistory[sortedHistory.length - 1];
|
||||
|
||||
function diff(get: (allStats: AllStats) => number) {
|
||||
function d(get: (allStats: AllStats) => number) {
|
||||
return get(latest.all) - get(previous.all);
|
||||
}
|
||||
|
||||
// check if there was a change in battles as games in ratings do update last_battle_time
|
||||
if (diff((a) => a.battles) > 0) {
|
||||
diffed[tankId] = {
|
||||
battles: diff((a) => a.battles),
|
||||
capture_points: diff((a) => a.capture_points),
|
||||
damage_dealt: diff((a) => a.damage_dealt),
|
||||
damage_received: diff((a) => a.damage_received),
|
||||
dropped_capture_points: diff((a) => a.dropped_capture_points),
|
||||
frags: diff((a) => a.frags),
|
||||
frags8p: diff((a) => a.frags8p),
|
||||
hits: diff((a) => a.hits),
|
||||
losses: diff((a) => a.losses),
|
||||
if (d((a) => a.battles) > 0) {
|
||||
diff[tankId] = {
|
||||
battles: d((a) => a.battles),
|
||||
capture_points: d((a) => a.capture_points),
|
||||
damage_dealt: d((a) => a.damage_dealt),
|
||||
damage_received: d((a) => a.damage_received),
|
||||
dropped_capture_points: d((a) => a.dropped_capture_points),
|
||||
frags: d((a) => a.frags),
|
||||
frags8p: d((a) => a.frags8p),
|
||||
hits: d((a) => a.hits),
|
||||
losses: d((a) => a.losses),
|
||||
max_frags: latest.all.max_frags,
|
||||
max_xp: latest.all.max_xp,
|
||||
shots: diff((a) => a.shots),
|
||||
spotted: diff((a) => a.spotted),
|
||||
survived_battles: diff((a) => a.survived_battles),
|
||||
win_and_survived: diff((a) => a.win_and_survived),
|
||||
wins: diff((a) => a.wins),
|
||||
xp: diff((a) => a.xp),
|
||||
shots: d((a) => a.shots),
|
||||
spotted: d((a) => a.spotted),
|
||||
survived_battles: d((a) => a.survived_battles),
|
||||
win_and_survived: d((a) => a.win_and_survived),
|
||||
wins: d((a) => a.wins),
|
||||
xp: d((a) => a.xp),
|
||||
};
|
||||
|
||||
playedTanks.push(tankId);
|
||||
@@ -116,5 +121,5 @@ export default async function getDiffedTankStats(
|
||||
return bLatest.last_battle_time - aLatest.last_battle_time;
|
||||
});
|
||||
|
||||
return { diffed, order };
|
||||
return { diff, order } satisfies DiffedTankStats;
|
||||
}
|
||||
@@ -3,5 +3,7 @@ import { PeriodSize } from '../discord/addPeriodSubCommands';
|
||||
import getTimeDaysAgo from './getTimeDaysAgo';
|
||||
|
||||
export default function getPeriodStart(region: Region, period: PeriodSize) {
|
||||
return period === 'career' ? 0 : getTimeDaysAgo(region, parseInt(period));
|
||||
if (period === 'career') return 0;
|
||||
if (period === 'today') return getTimeDaysAgo(region, 1);
|
||||
return getTimeDaysAgo(region, parseInt(period));
|
||||
}
|
||||
@@ -1,55 +1,42 @@
|
||||
import {
|
||||
SlashCommandSubcommandBuilder,
|
||||
SlashCommandSubcommandGroupBuilder,
|
||||
} from 'discord.js';
|
||||
import { SlashCommandBuilder, SlashCommandSubcommandBuilder } from 'discord.js';
|
||||
|
||||
export type PeriodType = 'period' | 'custom';
|
||||
export type PeriodSize = `${number}` | 'career';
|
||||
export type PeriodType = 'today' | '30' | '60' | '90' | 'custom';
|
||||
export type PeriodSize = 'today' | `${number}` | 'career';
|
||||
|
||||
export default function addPeriodSubCommands(
|
||||
option: SlashCommandSubcommandGroupBuilder,
|
||||
option: SlashCommandBuilder,
|
||||
extra: (
|
||||
option: SlashCommandSubcommandBuilder,
|
||||
) => SlashCommandSubcommandBuilder = (option) => option,
|
||||
) {
|
||||
return option
|
||||
option
|
||||
.addSubcommand((option) =>
|
||||
extra(
|
||||
option
|
||||
.setName('period' satisfies PeriodType)
|
||||
.setDescription("Preset period's statistics")
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName('period')
|
||||
.setDescription('A preset period')
|
||||
.setChoices(
|
||||
{ name: 'Today', value: '1' satisfies PeriodSize },
|
||||
{ name: '30 days', value: '30' satisfies PeriodSize },
|
||||
{ name: '60 days', value: '60' satisfies PeriodSize },
|
||||
{ name: '90 days', value: '90' satisfies PeriodSize },
|
||||
{ name: 'Career', value: 'career' satisfies PeriodSize },
|
||||
)
|
||||
.setRequired(true),
|
||||
),
|
||||
),
|
||||
extra(option.setName('today').setDescription('Today')),
|
||||
)
|
||||
.addSubcommand((option) =>
|
||||
extra(option.setName('30').setDescription('30 days')),
|
||||
)
|
||||
.addSubcommand((option) =>
|
||||
extra(option.setName('60').setDescription('60 days')),
|
||||
)
|
||||
.addSubcommand((option) =>
|
||||
extra(option.setName('90').setDescription('90 days')),
|
||||
)
|
||||
.addSubcommand((option) =>
|
||||
extra(
|
||||
option
|
||||
.setName('custom' satisfies PeriodType)
|
||||
.setDescription("Custom period's statistics")
|
||||
.setName('custom')
|
||||
.setDescription('Custom range')
|
||||
.addIntegerOption((option) =>
|
||||
option
|
||||
.setName('start')
|
||||
.setDescription('Days ago from today')
|
||||
.setMinValue(0)
|
||||
.setDescription('Start in number of days ago from today')
|
||||
.setRequired(true),
|
||||
)
|
||||
.addIntegerOption((option) =>
|
||||
option
|
||||
.setName('end')
|
||||
.setDescription('Days ago from today')
|
||||
.setMinValue(0)
|
||||
.setDescription('End in number of days')
|
||||
.setRequired(true),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,91 +1,70 @@
|
||||
import { SlashCommandBuilder } from 'discord.js';
|
||||
import { TreeTypeString } from '../../components/Tanks';
|
||||
import { StatType } from '../../renderers/stats';
|
||||
import { SlashCommandBuilder, SlashCommandSubcommandBuilder } from 'discord.js';
|
||||
import { TREE_TYPE_NAMES, TreeTypeString } from '../../components/Tanks';
|
||||
import { tankopediaInfo } from '../blitz/tankopedia';
|
||||
import addPeriodSubCommands from './addPeriodSubCommands';
|
||||
import addTankChoices from './addTankChoices';
|
||||
import addTierChoices from './addTierChoices';
|
||||
import addUsernameChoices from './addUsernameChoices';
|
||||
|
||||
export default async function addStatTypeSubCommandGroups(
|
||||
option: SlashCommandBuilder,
|
||||
addMultiTank = true,
|
||||
extra: (
|
||||
option: SlashCommandSubcommandBuilder,
|
||||
) => SlashCommandSubcommandBuilder = (option) => option,
|
||||
) {
|
||||
const awaitedTankopediaInfo = await tankopediaInfo;
|
||||
|
||||
option
|
||||
.addSubcommandGroup((option) =>
|
||||
addPeriodSubCommands(option, (option) =>
|
||||
option.addStringOption(addUsernameChoices),
|
||||
)
|
||||
.setName('player' satisfies StatType)
|
||||
.setDescription('Player statistics'),
|
||||
)
|
||||
.addSubcommandGroup((option) =>
|
||||
addPeriodSubCommands(option, (option) =>
|
||||
option
|
||||
.addStringOption(addTankChoices)
|
||||
.addStringOption(addUsernameChoices),
|
||||
)
|
||||
.setName('tank' satisfies StatType)
|
||||
.setDescription('Tank statistics'),
|
||||
);
|
||||
addPeriodSubCommands(option, (option) => {
|
||||
extra(option);
|
||||
|
||||
if (addMultiTank)
|
||||
option.addSubcommandGroup((option) =>
|
||||
addPeriodSubCommands(option, (option) =>
|
||||
return option
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.addStringOption(addUsernameChoices)
|
||||
.addStringOption((option) => {
|
||||
option
|
||||
.setName('nation')
|
||||
.setDescription('Nation')
|
||||
.addChoices(
|
||||
...Object.entries(awaitedTankopediaInfo.vehicle_nations).map(
|
||||
([type, name]) => ({ name, value: type }),
|
||||
),
|
||||
);
|
||||
|
||||
return option;
|
||||
})
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName('tank-type')
|
||||
.setDescription('Type')
|
||||
.addChoices(
|
||||
...Object.entries(awaitedTankopediaInfo.vehicle_types).map(
|
||||
([type, name]) => ({
|
||||
name: name.replace(' Tank', ''),
|
||||
value: type,
|
||||
}),
|
||||
),
|
||||
)
|
||||
.setRequired(false),
|
||||
.setName('nation')
|
||||
.setDescription('Nation')
|
||||
.addChoices(
|
||||
...Object.entries(awaitedTankopediaInfo.vehicle_nations).map(
|
||||
([type, name]) => ({ name, value: type }),
|
||||
),
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
addTierChoices(option).setRequired(false),
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName('tree-type')
|
||||
.setDescription('Tech tree type')
|
||||
.addChoices(
|
||||
{
|
||||
name: 'Tech Tree',
|
||||
value: 'techtree' satisfies TreeTypeString,
|
||||
},
|
||||
{ name: 'Premium', value: 'premium' satisfies TreeTypeString },
|
||||
{
|
||||
name: 'Collector',
|
||||
value: 'collector' satisfies TreeTypeString,
|
||||
},
|
||||
)
|
||||
.setRequired(false),
|
||||
),
|
||||
.setRequired(false),
|
||||
)
|
||||
.setName('multi-tank' satisfies StatType)
|
||||
.setDescription('Multiple tanks'),
|
||||
);
|
||||
.addStringOption((option) => addTierChoices(option).setRequired(false))
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName('tank-type')
|
||||
.setDescription('Tank type')
|
||||
.addChoices(
|
||||
...Object.entries(awaitedTankopediaInfo.vehicle_types).map(
|
||||
([type, name]) => ({
|
||||
name: name.replace(' Tank', ''),
|
||||
value: type,
|
||||
}),
|
||||
),
|
||||
)
|
||||
.setRequired(false),
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName('tree-type')
|
||||
.setDescription('Tech tree type')
|
||||
.addChoices(
|
||||
{
|
||||
name: TREE_TYPE_NAMES['tech-tree'],
|
||||
value: 'tech-tree' satisfies TreeTypeString,
|
||||
},
|
||||
{
|
||||
name: TREE_TYPE_NAMES.premium,
|
||||
value: 'premium' satisfies TreeTypeString,
|
||||
},
|
||||
{
|
||||
name: TREE_TYPE_NAMES.collector,
|
||||
value: 'collector' satisfies TreeTypeString,
|
||||
},
|
||||
)
|
||||
.setRequired(false),
|
||||
)
|
||||
.addStringOption((option) => addTankChoices(option).setRequired(false));
|
||||
});
|
||||
|
||||
return option;
|
||||
}
|
||||
@@ -1,11 +1,7 @@
|
||||
import {
|
||||
CacheType,
|
||||
ChatInputCommandInteraction,
|
||||
CommandInteractionOption,
|
||||
} from 'discord.js';
|
||||
import { CacheType, ChatInputCommandInteraction } from 'discord.js';
|
||||
|
||||
export interface Parameters {
|
||||
[key: string]: CommandInteractionOption['value'] | undefined | null;
|
||||
[key: string]: string | number | boolean | undefined | null;
|
||||
}
|
||||
|
||||
export default function interactionToURL(
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { ChatInputCommandInteraction } from 'discord.js';
|
||||
import primaryButton from './primaryButton';
|
||||
|
||||
export function refreshButton(
|
||||
interaction: ChatInputCommandInteraction,
|
||||
path: string,
|
||||
) {
|
||||
if (
|
||||
interaction.appPermissions?.has('AttachFiles') &&
|
||||
interaction.appPermissions?.has('ViewChannel')
|
||||
) {
|
||||
return primaryButton(path, 'Refresh');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { ButtonInteraction, CacheType } from 'discord.js';
|
||||
import { CYCLIC_API } from '../../constants/cyclic';
|
||||
import { Region } from '../../constants/regions';
|
||||
import resolvePeriodFromURL from './resolvePeriodFromURL';
|
||||
|
||||
@@ -7,5 +6,8 @@ export default function resolvePeriodFromButton(
|
||||
server: Region,
|
||||
interaction: ButtonInteraction<CacheType>,
|
||||
) {
|
||||
return resolvePeriodFromURL(server, `${CYCLIC_API}/${interaction.customId}`);
|
||||
return resolvePeriodFromURL(
|
||||
server,
|
||||
`https://example.com/${interaction.customId}`,
|
||||
);
|
||||
}
|
||||
@@ -6,12 +6,13 @@ import getTimeDaysAgo from '../blitzstars/getTimeDaysAgo';
|
||||
import { PeriodSize, PeriodType } from '../discord/addPeriodSubCommands';
|
||||
|
||||
export function getPeriodOptionName(period: PeriodSize) {
|
||||
return period === 'career' ? 'Career' : `${period}-day`;
|
||||
if (period === 'career') return 'Career';
|
||||
if (period === 'today') return 'Today';
|
||||
return `${period} days`;
|
||||
}
|
||||
|
||||
export interface ResolvedPeriod {
|
||||
statsName: string;
|
||||
evolutionName: string;
|
||||
name: string;
|
||||
start: number;
|
||||
end: number;
|
||||
}
|
||||
@@ -20,33 +21,28 @@ export default function resolvePeriodFromCommand(
|
||||
region: Region,
|
||||
interaction: ChatInputCommandInteraction<CacheType>,
|
||||
) {
|
||||
let statsName: string;
|
||||
let evolutionName: string;
|
||||
let name: string;
|
||||
let start: number;
|
||||
let end: number;
|
||||
const periodSubcommand = interaction.options.getSubcommand() as PeriodType;
|
||||
const periodOption = interaction.options.getString('period') as PeriodSize;
|
||||
|
||||
if (periodSubcommand === 'custom') {
|
||||
const startRaw = interaction.options.getInteger('start', true);
|
||||
const endRaw = interaction.options.getInteger('end', true);
|
||||
const startDaysAgoMin = Math.min(startRaw, endRaw);
|
||||
const endDaysAgoMax = Math.max(startRaw, endRaw);
|
||||
const startOption = interaction.options.getInteger('start', true);
|
||||
const endOption = interaction.options.getInteger('end', true);
|
||||
const startDaysAgoMin = Math.min(startOption, endOption);
|
||||
const endDaysAgoMax = Math.max(startOption, endOption);
|
||||
|
||||
statsName = `${startDaysAgoMin} to ${endDaysAgoMax} days' statistics`;
|
||||
evolutionName = `${startDaysAgoMin} to ${endDaysAgoMax} days' evolution`;
|
||||
name = `${startDaysAgoMin} - ${endDaysAgoMax} days`;
|
||||
start = getTimeDaysAgo(region, endDaysAgoMax);
|
||||
end = getTimeDaysAgo(region, startDaysAgoMin);
|
||||
} else {
|
||||
statsName = `${getPeriodOptionName(periodOption)} Statistics`;
|
||||
evolutionName = `${getPeriodOptionName(periodOption)} Evolution`;
|
||||
start = getPeriodStart(region, periodOption);
|
||||
name = getPeriodOptionName(periodSubcommand);
|
||||
start = getPeriodStart(region, periodSubcommand);
|
||||
end = getPeriodNow();
|
||||
}
|
||||
|
||||
return {
|
||||
statsName,
|
||||
evolutionName,
|
||||
name,
|
||||
start,
|
||||
end,
|
||||
} satisfies ResolvedPeriod;
|
||||
|
||||
@@ -2,24 +2,22 @@ import { Region } from '../../constants/regions';
|
||||
import getPeriodNow from '../blitzstars/getPeriodNow';
|
||||
import getPeriodStart from '../blitzstars/getPeriodStart';
|
||||
import getTimeDaysAgo from '../blitzstars/getTimeDaysAgo';
|
||||
import { PeriodSize, PeriodType } from './addPeriodSubCommands';
|
||||
import { PeriodType } from './addPeriodSubCommands';
|
||||
import {
|
||||
ResolvedPeriod,
|
||||
getPeriodOptionName,
|
||||
} from './resolvePeriodFromCommand';
|
||||
|
||||
export default function resolvePeriodFromURL(
|
||||
server: Region,
|
||||
region: Region,
|
||||
urlString: string,
|
||||
) {
|
||||
let statsName: string;
|
||||
let evolutionName: string;
|
||||
let name: string;
|
||||
let start: number;
|
||||
let end: number;
|
||||
const url = new URL(urlString);
|
||||
const path = url.pathname.split('/').filter(Boolean);
|
||||
const periodSubcommand = path[path.length - 1] as PeriodType;
|
||||
const periodOption = url.searchParams.get('period') as PeriodSize;
|
||||
const path = url.pathname.split('/');
|
||||
const periodSubcommand = path[2] as PeriodType;
|
||||
|
||||
if (periodSubcommand === 'custom') {
|
||||
const startRaw = parseInt(url!.searchParams.get('start')!);
|
||||
@@ -27,16 +25,18 @@ export default function resolvePeriodFromURL(
|
||||
const startDaysAgoMin = Math.min(startRaw, endRaw);
|
||||
const endDaysAgoMax = Math.max(startRaw, endRaw);
|
||||
|
||||
statsName = `${startDaysAgoMin} to ${endDaysAgoMax} days' statistics`;
|
||||
evolutionName = `${startDaysAgoMin} to ${endDaysAgoMax} days' evolution`;
|
||||
start = getTimeDaysAgo(server, endDaysAgoMax);
|
||||
end = getTimeDaysAgo(server, startDaysAgoMin);
|
||||
name = `${startDaysAgoMin} - ${endDaysAgoMax} days`;
|
||||
start = getTimeDaysAgo(region, endDaysAgoMax);
|
||||
end = getTimeDaysAgo(region, startDaysAgoMin);
|
||||
} else {
|
||||
statsName = `${getPeriodOptionName(periodOption)} Statistics`;
|
||||
evolutionName = `${getPeriodOptionName(periodOption)} Evolution`;
|
||||
start = getPeriodStart(server, periodOption);
|
||||
name = getPeriodOptionName(periodSubcommand);
|
||||
start = getPeriodStart(region, periodSubcommand);
|
||||
end = getPeriodNow();
|
||||
}
|
||||
|
||||
return { statsName, evolutionName, start, end } satisfies ResolvedPeriod;
|
||||
return {
|
||||
name,
|
||||
start,
|
||||
end,
|
||||
} satisfies ResolvedPeriod;
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
import { ButtonInteraction, CacheType } from 'discord.js';
|
||||
import { CYCLIC_API } from '../../constants/cyclic';
|
||||
import resolvePlayerFromURL from './resolvePlayerFromURL';
|
||||
|
||||
export default async function resolvePlayerFromButton(
|
||||
interaction: ButtonInteraction<CacheType>,
|
||||
) {
|
||||
return resolvePlayerFromURL(`${CYCLIC_API}/${interaction.customId}`);
|
||||
return resolvePlayerFromURL(`https://exmaple.com/${interaction.customId}`);
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import { ButtonInteraction, CacheType } from 'discord.js';
|
||||
import { commands } from '..';
|
||||
import { CYCLIC_API } from '../../../constants/cyclic';
|
||||
import normalizeInteractionReturnable from '../../../core/discord/normalizeInteractionReturnable';
|
||||
import throwError from '../../../core/node/throwError';
|
||||
|
||||
@@ -9,7 +8,7 @@ export default async function handleButton(
|
||||
) {
|
||||
await interaction.deferUpdate();
|
||||
|
||||
const url = new URL(`${CYCLIC_API}/${interaction.customId}`);
|
||||
const url = new URL(`https://example.com/${interaction.customId}`);
|
||||
const commandName = url.pathname.split('/')[1];
|
||||
const button = (await commands)[commandName]?.button;
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ export default async function handleChatInputCommand(
|
||||
) {
|
||||
const registry = (await commands)[interaction.commandName];
|
||||
|
||||
console.log(interaction.toString());
|
||||
await interaction.deferReply();
|
||||
|
||||
try {
|
||||
|
||||
@@ -44,7 +44,8 @@ export type InteractionRawReturnable =
|
||||
| EmbedBuilder
|
||||
| ButtonBuilder
|
||||
| AttachmentBuilder
|
||||
| JSX.Element;
|
||||
| JSX.Element
|
||||
| null;
|
||||
export type InteractionIterableReturnable =
|
||||
| InteractionRawReturnable
|
||||
| InteractionRawReturnable[];
|
||||
|
||||
@@ -23,7 +23,7 @@ import { MultiTankFilters, StatType } from './stats';
|
||||
|
||||
export default async function fullStats<Type extends StatType>(
|
||||
type: Type,
|
||||
{ start, end, statsName }: ResolvedPeriod,
|
||||
{ start, end, name: statsName }: ResolvedPeriod,
|
||||
{ region: server, id }: ResolvedPlayer,
|
||||
filters: Type extends 'tank'
|
||||
? number
|
||||
@@ -50,7 +50,12 @@ export default async function fullStats<Type extends StatType>(
|
||||
image = (await tankopedia)[filters as number]?.images.normal;
|
||||
}
|
||||
|
||||
const { diffed, order } = await getDiffedTankStats(server, id, start, end);
|
||||
const { diff: diffed, order } = await getDiffedTankStats(
|
||||
server,
|
||||
id,
|
||||
start,
|
||||
end,
|
||||
);
|
||||
const awaitedTankopedia = await tankopedia;
|
||||
let stats: AllStats | undefined;
|
||||
let supplementaryStats: SupplementaryStats;
|
||||
|
||||
@@ -1,172 +0,0 @@
|
||||
import AllStatsOverview from '../components/AllStatsOverview';
|
||||
import NoData, { NoDataType } from '../components/NoData';
|
||||
import { TreeTypeString } from '../components/Tanks';
|
||||
import TitleBar from '../components/TitleBar';
|
||||
import Wrapper from '../components/Wrapper';
|
||||
import { WARGAMING_APPLICATION_ID } from '../constants/wargamingApplicationID';
|
||||
import calculateWN8 from '../core/blitz/calculateWN8';
|
||||
import getWargamingResponse from '../core/blitz/getWargamingResponse';
|
||||
import resolveTankName from '../core/blitz/resolveTankName';
|
||||
import sumStats from '../core/blitz/sumStats';
|
||||
import { tankopedia } from '../core/blitz/tankopedia';
|
||||
import getDiffedTankStats from '../core/blitzstars/getDiffedTankStats';
|
||||
import { tankAverages } from '../core/blitzstars/tankAverages';
|
||||
import { ResolvedPeriod } from '../core/discord/resolvePeriodFromCommand';
|
||||
import { ResolvedPlayer } from '../core/discord/resolvePlayerFromCommand';
|
||||
import { theme } from '../stitches.config';
|
||||
import {
|
||||
AccountInfo,
|
||||
AllStats,
|
||||
SupplementaryStats,
|
||||
} from '../types/accountInfo';
|
||||
import { PlayerClanData } from '../types/playerClanData';
|
||||
|
||||
export type StatType = 'player' | 'tank' | 'multi-tank';
|
||||
|
||||
export interface MultiTankFilters {
|
||||
nation: string;
|
||||
'tank-type': string;
|
||||
tier: number;
|
||||
'tree-type': TreeTypeString;
|
||||
}
|
||||
|
||||
export default async function stats<Type extends StatType>(
|
||||
type: Type,
|
||||
{ start, end, statsName }: ResolvedPeriod,
|
||||
{ region: server, id }: ResolvedPlayer,
|
||||
filters: Type extends 'tank'
|
||||
? number
|
||||
: Type extends 'multi-tank'
|
||||
? Partial<MultiTankFilters>
|
||||
: null,
|
||||
naked = false,
|
||||
) {
|
||||
let nameDiscriminator: string | undefined;
|
||||
let image: string | undefined;
|
||||
|
||||
if (type === 'player' || type === 'multi-tank') {
|
||||
const clan = (
|
||||
await getWargamingResponse<PlayerClanData>(
|
||||
`https://api.wotblitz.${server}/wotb/clans/accountinfo/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}&extra=clan`,
|
||||
)
|
||||
)[id]?.clan;
|
||||
|
||||
if (clan) nameDiscriminator = `[${clan.tag}]`;
|
||||
image = clan
|
||||
? `https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clan.emblem_set_id}.png`
|
||||
: undefined;
|
||||
} else if (type === 'tank') {
|
||||
nameDiscriminator = `(${await resolveTankName(filters as number)})`;
|
||||
image = (await tankopedia)[filters as number]?.images.normal;
|
||||
} else {
|
||||
}
|
||||
|
||||
const { diffed, order } = await getDiffedTankStats(server, id, start, end);
|
||||
const awaitedTankopedia = await tankopedia;
|
||||
let stats: AllStats | undefined;
|
||||
let supplementaryStats: SupplementaryStats;
|
||||
|
||||
if (type === 'player' || type === 'multi-tank') {
|
||||
const typedFilters = filters as Partial<MultiTankFilters> | null;
|
||||
const filteredOrder = order.filter(
|
||||
(id) =>
|
||||
typedFilters === null ||
|
||||
((typedFilters.nation === undefined ||
|
||||
awaitedTankopedia[id]?.nation === typedFilters.nation) &&
|
||||
(typedFilters['tank-type'] === undefined ||
|
||||
awaitedTankopedia[id]?.type === typedFilters['tank-type']) &&
|
||||
(typedFilters.tier === undefined ||
|
||||
awaitedTankopedia[id]?.tier === typedFilters.tier) &&
|
||||
(typedFilters['tree-type'] === undefined ||
|
||||
(typedFilters['tree-type'] === 'collector' &&
|
||||
awaitedTankopedia[id]?.is_collectible) ||
|
||||
(typedFilters['tree-type'] === 'premium' &&
|
||||
awaitedTankopedia[id]?.is_premium) ||
|
||||
(typedFilters['tree-type'] === 'techtree' &&
|
||||
!awaitedTankopedia[id]?.is_collectible &&
|
||||
!awaitedTankopedia[id]?.is_premium))),
|
||||
);
|
||||
const awaitedTankAverages = await tankAverages;
|
||||
stats = sumStats(filteredOrder.map((id) => diffed[id]));
|
||||
const battlesOfTanksWithAverages = filteredOrder.reduce<number>(
|
||||
(accumulator, id) =>
|
||||
awaitedTankAverages[id]
|
||||
? accumulator + diffed[id].battles
|
||||
: accumulator,
|
||||
0,
|
||||
);
|
||||
const battlesOfTanksWithTankopediaEntry = filteredOrder.reduce<number>(
|
||||
(accumulator, id) =>
|
||||
awaitedTankopedia[id] ? accumulator + diffed[id].battles : accumulator,
|
||||
0,
|
||||
);
|
||||
|
||||
supplementaryStats = {
|
||||
WN8:
|
||||
filteredOrder.reduce<number>(
|
||||
(accumulator, id) =>
|
||||
awaitedTankAverages[id]
|
||||
? accumulator +
|
||||
calculateWN8(awaitedTankAverages[id].all, diffed[id]) *
|
||||
diffed[id].battles
|
||||
: accumulator,
|
||||
0,
|
||||
) / battlesOfTanksWithAverages,
|
||||
tier:
|
||||
filteredOrder.reduce<number>(
|
||||
(accumulator, id) =>
|
||||
awaitedTankopedia[id]
|
||||
? accumulator + awaitedTankopedia[id]!.tier * diffed[id].battles
|
||||
: accumulator,
|
||||
0,
|
||||
) / battlesOfTanksWithTankopediaEntry,
|
||||
};
|
||||
} else {
|
||||
stats = diffed[(filters as number)!];
|
||||
|
||||
supplementaryStats = {
|
||||
WN8: stats
|
||||
? calculateWN8(
|
||||
(await tankAverages)[(filters as number)!].all,
|
||||
diffed[(filters as number)!],
|
||||
)
|
||||
: undefined,
|
||||
tier: (await tankopedia)[(filters as number)!]?.tier,
|
||||
};
|
||||
}
|
||||
|
||||
const accountInfo = await getWargamingResponse<AccountInfo>(
|
||||
`https://api.wotblitz.${server}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`,
|
||||
);
|
||||
const overview = (
|
||||
<AllStatsOverview stats={stats} supplementaryStats={supplementaryStats} />
|
||||
);
|
||||
const footer = (
|
||||
<span
|
||||
style={{
|
||||
color: theme.colors.textLowContrast,
|
||||
fontSize: 16,
|
||||
display: 'flex',
|
||||
gap: 4,
|
||||
}}
|
||||
>
|
||||
<u>/full-stats</u> for all statistics
|
||||
</span>
|
||||
);
|
||||
|
||||
return naked ? (
|
||||
<Wrapper naked>{overview}</Wrapper>
|
||||
) : (
|
||||
<Wrapper>
|
||||
<TitleBar
|
||||
name={accountInfo[id].nickname}
|
||||
image={image}
|
||||
nameDiscriminator={nameDiscriminator}
|
||||
description={`${statsName} • ${new Date().toDateString()}`}
|
||||
/>
|
||||
|
||||
{!stats?.battles && <NoData type={NoDataType.BattlesInPeriod} />}
|
||||
{stats?.battles > 0 && overview}
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
@@ -27,7 +27,7 @@ export default async function today(
|
||||
showTotal = true,
|
||||
naked?: boolean,
|
||||
) {
|
||||
const { diffed, order } = await getDiffedTankStats(
|
||||
const { diff: diffed, order } = await getDiffedTankStats(
|
||||
server,
|
||||
id,
|
||||
getTimeDaysAgo(server, 1),
|
||||
|
||||
Reference in new issue
Block a user