mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-27 04:29:50 +01: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 = '';
|
let body = '';
|
||||||
|
|
||||||
if (clan === 'SKLLD') {
|
if (clan === 'SKLLD') {
|
||||||
const { diffed } = await getDiffedTankStats(
|
const { diff: diffed } = await getDiffedTankStats(
|
||||||
server,
|
server,
|
||||||
id,
|
id,
|
||||||
getTimeDaysAgo(server, 30),
|
getTimeDaysAgo(server, 30),
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { SlashCommandBuilder } from 'discord.js';
|
import { SlashCommandBuilder } from 'discord.js';
|
||||||
import { CYCLIC_API } from '../constants/cyclic';
|
|
||||||
import { WARGAMING_APPLICATION_ID } from '../constants/wargamingApplicationID';
|
import { WARGAMING_APPLICATION_ID } from '../constants/wargamingApplicationID';
|
||||||
import getWargamingResponse from '../core/blitz/getWargamingResponse';
|
import getWargamingResponse from '../core/blitz/getWargamingResponse';
|
||||||
import resolveTankId from '../core/blitz/resolveTankId';
|
import resolveTankId from '../core/blitz/resolveTankId';
|
||||||
import addStatTypeSubCommandGroups from '../core/discord/addStatTypeSubCommandGroups';
|
import addStatTypeSubCommandGroups from '../core/discord/addStatTypeSubCommandGroups';
|
||||||
|
import addUsernameChoices from '../core/discord/addUsernameChoices';
|
||||||
import autocompleteTanks from '../core/discord/autocompleteTanks';
|
import autocompleteTanks from '../core/discord/autocompleteTanks';
|
||||||
import autocompleteUsername from '../core/discord/autocompleteUsername';
|
import autocompleteUsername from '../core/discord/autocompleteUsername';
|
||||||
import interactionToURL from '../core/discord/interactionToURL';
|
import interactionToURL from '../core/discord/interactionToURL';
|
||||||
@@ -24,7 +24,7 @@ export const evolutionCommand = new Promise<CommandRegistryRaw>(
|
|||||||
new SlashCommandBuilder()
|
new SlashCommandBuilder()
|
||||||
.setName('evolution')
|
.setName('evolution')
|
||||||
.setDescription('Evolution of statistics'),
|
.setDescription('Evolution of statistics'),
|
||||||
false,
|
(option) => option.addStringOption(addUsernameChoices),
|
||||||
);
|
);
|
||||||
|
|
||||||
resolve({
|
resolve({
|
||||||
@@ -59,7 +59,6 @@ export const evolutionCommand = new Promise<CommandRegistryRaw>(
|
|||||||
return [
|
return [
|
||||||
await evolution(commandGroup, period, player, tankId),
|
await evolution(commandGroup, period, player, tankId),
|
||||||
primaryButton(path, 'Refresh'),
|
primaryButton(path, 'Refresh'),
|
||||||
// linkButton(`${CYCLIC_API}/${path}`, 'Embed'),
|
|
||||||
linkButton(
|
linkButton(
|
||||||
`https://www.blitzstars.com/player/${player.region}/${nickname}${
|
`https://www.blitzstars.com/player/${player.region}/${nickname}${
|
||||||
commandGroup === 'tank' ? `/tank/${tankId!}` : ''
|
commandGroup === 'tank' ? `/tank/${tankId!}` : ''
|
||||||
@@ -75,7 +74,7 @@ export const evolutionCommand = new Promise<CommandRegistryRaw>(
|
|||||||
},
|
},
|
||||||
|
|
||||||
async button(interaction) {
|
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 path = url.pathname.split('/').filter(Boolean);
|
||||||
const commandGroup = path[1] as StatType;
|
const commandGroup = path[1] as StatType;
|
||||||
const player = await resolvePlayerFromButton(interaction);
|
const player = await resolvePlayerFromButton(interaction);
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { SlashCommandBuilder } from 'discord.js';
|
import { SlashCommandBuilder } from 'discord.js';
|
||||||
import { TreeTypeString } from '../components/Tanks';
|
import { TreeTypeString } from '../components/Tanks';
|
||||||
import { CYCLIC_API } from '../constants/cyclic';
|
|
||||||
import { WARGAMING_APPLICATION_ID } from '../constants/wargamingApplicationID';
|
import { WARGAMING_APPLICATION_ID } from '../constants/wargamingApplicationID';
|
||||||
import getWargamingResponse from '../core/blitz/getWargamingResponse';
|
import getWargamingResponse from '../core/blitz/getWargamingResponse';
|
||||||
import resolveTankId from '../core/blitz/resolveTankId';
|
import resolveTankId from '../core/blitz/resolveTankId';
|
||||||
import addStatTypeSubCommandGroups from '../core/discord/addStatTypeSubCommandGroups';
|
import addStatTypeSubCommandGroups from '../core/discord/addStatTypeSubCommandGroups';
|
||||||
|
import addUsernameChoices from '../core/discord/addUsernameChoices';
|
||||||
import autocompleteTanks from '../core/discord/autocompleteTanks';
|
import autocompleteTanks from '../core/discord/autocompleteTanks';
|
||||||
import autocompleteUsername from '../core/discord/autocompleteUsername';
|
import autocompleteUsername from '../core/discord/autocompleteUsername';
|
||||||
import interactionToURL from '../core/discord/interactionToURL';
|
import interactionToURL from '../core/discord/interactionToURL';
|
||||||
@@ -25,6 +25,7 @@ export const fullStatsCommand = new Promise<CommandRegistryRaw>(
|
|||||||
new SlashCommandBuilder()
|
new SlashCommandBuilder()
|
||||||
.setName('full-stats')
|
.setName('full-stats')
|
||||||
.setDescription('Full in-game statistics'),
|
.setDescription('Full in-game statistics'),
|
||||||
|
(option) => option.addStringOption(addUsernameChoices),
|
||||||
);
|
);
|
||||||
|
|
||||||
resolve({
|
resolve({
|
||||||
@@ -82,7 +83,6 @@ export const fullStatsCommand = new Promise<CommandRegistryRaw>(
|
|||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
primaryButton(path, 'Refresh'),
|
primaryButton(path, 'Refresh'),
|
||||||
// linkButton(`${CYCLIC_API}/${path}`, 'Embed'),
|
|
||||||
linkButton(
|
linkButton(
|
||||||
`https://www.blitzstars.com/player/${player.region}/${nickname}`,
|
`https://www.blitzstars.com/player/${player.region}/${nickname}`,
|
||||||
'BlitzStars',
|
'BlitzStars',
|
||||||
@@ -96,7 +96,7 @@ export const fullStatsCommand = new Promise<CommandRegistryRaw>(
|
|||||||
},
|
},
|
||||||
|
|
||||||
async button(interaction) {
|
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 path = url.pathname.split('/').filter(Boolean);
|
||||||
const commandGroup = path[1] as StatType;
|
const commandGroup = path[1] as StatType;
|
||||||
const player = await resolvePlayerFromButton(interaction);
|
const player = await resolvePlayerFromButton(interaction);
|
||||||
|
|||||||
+91
-71
@@ -1,28 +1,77 @@
|
|||||||
import { SlashCommandBuilder } from 'discord.js';
|
import { SlashCommandBuilder } from 'discord.js';
|
||||||
|
import AllStatsOverview from '../components/AllStatsOverview';
|
||||||
|
import NoData, { NoDataType } from '../components/NoData';
|
||||||
import { TreeTypeString } from '../components/Tanks';
|
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 { WARGAMING_APPLICATION_ID } from '../constants/wargamingApplicationID';
|
||||||
|
import { StatFilters, filterStats } from '../core/blitz/filterStats';
|
||||||
import getWargamingResponse from '../core/blitz/getWargamingResponse';
|
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 addStatTypeSubCommandGroups from '../core/discord/addStatTypeSubCommandGroups';
|
||||||
|
import addUsernameChoices from '../core/discord/addUsernameChoices';
|
||||||
import autocompleteTanks from '../core/discord/autocompleteTanks';
|
import autocompleteTanks from '../core/discord/autocompleteTanks';
|
||||||
import autocompleteUsername from '../core/discord/autocompleteUsername';
|
import autocompleteUsername from '../core/discord/autocompleteUsername';
|
||||||
import interactionToURL from '../core/discord/interactionToURL';
|
import interactionToURL from '../core/discord/interactionToURL';
|
||||||
import linkButton from '../core/discord/linkButton';
|
import { refreshButton } from '../core/discord/refreshButton';
|
||||||
import primaryButton from '../core/discord/primaryButton';
|
|
||||||
import resolvePeriodFromButton from '../core/discord/resolvePeriodFromButton';
|
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 resolvePlayerFromButton from '../core/discord/resolvePlayerFromButton';
|
||||||
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
|
import resolvePlayerFromCommand, {
|
||||||
|
ResolvedPlayer,
|
||||||
|
} from '../core/discord/resolvePlayerFromCommand';
|
||||||
import { CommandRegistryRaw } from '../events/interactionCreate';
|
import { CommandRegistryRaw } from '../events/interactionCreate';
|
||||||
import stats, { MultiTankFilters, StatType } from '../renderers/stats';
|
|
||||||
import { AccountInfo } from '../types/accountInfo';
|
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) => {
|
export const statsCommand = new Promise<CommandRegistryRaw>(async (resolve) => {
|
||||||
const command = await addStatTypeSubCommandGroups(
|
const command = await addStatTypeSubCommandGroups(
|
||||||
new SlashCommandBuilder()
|
new SlashCommandBuilder()
|
||||||
.setName('stats')
|
.setName('stats')
|
||||||
.setDescription('Regular battles statistics'),
|
.setDescription('Regular battles statistics'),
|
||||||
|
(option) => option.addStringOption(addUsernameChoices),
|
||||||
);
|
);
|
||||||
|
|
||||||
resolve({
|
resolve({
|
||||||
@@ -32,59 +81,38 @@ export const statsCommand = new Promise<CommandRegistryRaw>(async (resolve) => {
|
|||||||
command,
|
command,
|
||||||
|
|
||||||
async handler(interaction) {
|
async handler(interaction) {
|
||||||
const commandGroup = interaction.options.getSubcommandGroup(
|
const resolvedPlayer = await resolvePlayerFromCommand(interaction);
|
||||||
true,
|
|
||||||
) as StatType;
|
|
||||||
const player = await resolvePlayerFromCommand(interaction);
|
|
||||||
const resolvedPeriod = resolvePeriodFromCommand(
|
const resolvedPeriod = resolvePeriodFromCommand(
|
||||||
player.region,
|
resolvedPlayer.region,
|
||||||
interaction,
|
interaction,
|
||||||
);
|
);
|
||||||
const period = interaction.options.getString('period');
|
const filters = {
|
||||||
const tankIdRaw = interaction.options.getString('tank')!;
|
nation: interaction.options.getString('nation') ?? undefined,
|
||||||
const tankId =
|
tier:
|
||||||
commandGroup === 'tank' ? await resolveTankId(tankIdRaw) : null;
|
parseInt(interaction.options.getString('tier') ?? '0') || undefined,
|
||||||
const start = interaction.options.getInteger('start');
|
tankType: interaction.options.getString('tank-type') ?? undefined,
|
||||||
const end = interaction.options.getInteger('end');
|
treeType:
|
||||||
|
(interaction.options.getString('tree-type') as
|
||||||
|
| TreeTypeString
|
||||||
|
| undefined) ?? undefined,
|
||||||
|
tank:
|
||||||
|
parseInt(interaction.options.getString('tank') ?? '0') || undefined,
|
||||||
|
} satisfies StatFilters;
|
||||||
const path = interactionToURL(interaction, {
|
const path = interactionToURL(interaction, {
|
||||||
...player,
|
...resolvedPlayer,
|
||||||
tankId,
|
...((interaction.options.getSubcommand() as PeriodType) === 'custom'
|
||||||
start,
|
? {
|
||||||
end,
|
start: interaction.options.getInteger('start', true),
|
||||||
period,
|
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 [
|
return [
|
||||||
await stats(
|
await render(resolvedPlayer, resolvedPeriod, filters),
|
||||||
commandGroup,
|
refreshButton(interaction, path),
|
||||||
resolvedPeriod,
|
await getBlitzStarsLinkButton(resolvedPlayer.region, resolvedPlayer.id),
|
||||||
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',
|
|
||||||
),
|
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -94,27 +122,19 @@ export const statsCommand = new Promise<CommandRegistryRaw>(async (resolve) => {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async button(interaction) {
|
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);
|
const player = await resolvePlayerFromButton(interaction);
|
||||||
const period = resolvePeriodFromButton(player.region, interaction);
|
const period = resolvePeriodFromButton(player.region, interaction);
|
||||||
|
|
||||||
return await stats(
|
return await render(player, period, {
|
||||||
commandGroup,
|
nation: url.searchParams.get('nation') ?? undefined,
|
||||||
period,
|
tank: parseInt(url.searchParams.get('tank') ?? '0') || undefined,
|
||||||
player,
|
tankType: url.searchParams.get('tank-type') ?? undefined,
|
||||||
commandGroup === 'tank'
|
treeType:
|
||||||
? parseInt(url.searchParams.get('tankId')!)
|
(url.searchParams.get('tree-type') as TreeTypeString | undefined) ??
|
||||||
: commandGroup === 'multi-tank'
|
undefined,
|
||||||
? ({
|
tier: parseInt(url.searchParams.get('tier') ?? '0') || undefined,
|
||||||
'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,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -65,7 +65,7 @@ export const todayCommand: CommandRegistry = {
|
|||||||
return [
|
return [
|
||||||
await today(player, cutoff, maximized, showTotal, false),
|
await today(player, cutoff, maximized, showTotal, false),
|
||||||
primaryButton(path, 'Refresh'),
|
primaryButton(path, 'Refresh'),
|
||||||
// linkButton(`${CYCLIC_API}/${path}`, 'Embed'),
|
// linkButton(`https://example.com/${path}`, 'Embed'),
|
||||||
linkButton(
|
linkButton(
|
||||||
`https://www.blitzstars.com/player/${player.region}/${nickname}`,
|
`https://www.blitzstars.com/player/${player.region}/${nickname}`,
|
||||||
'BlitzStars',
|
'BlitzStars',
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import {
|
|||||||
TANK_ICONS,
|
TANK_ICONS,
|
||||||
TANK_ICONS_COLLECTOR,
|
TANK_ICONS_COLLECTOR,
|
||||||
TANK_ICONS_PREMIUM,
|
TANK_ICONS_PREMIUM,
|
||||||
TankType,
|
|
||||||
} from '../../../core/blitz/tankopedia';
|
} from '../../../core/blitz/tankopedia';
|
||||||
import { theme } from '../../../stitches.config';
|
import { theme } from '../../../stitches.config';
|
||||||
|
|
||||||
@@ -12,11 +11,17 @@ export enum TreeTypeEnum {
|
|||||||
Collector,
|
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 {
|
export interface ItemProps {
|
||||||
image?: string;
|
image?: string;
|
||||||
tankType?: TankType;
|
tankType?: string;
|
||||||
name: string;
|
name: string;
|
||||||
treeType: TreeTypeEnum;
|
treeType: TreeTypeEnum;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,17 +2,11 @@ import { theme } from '../stitches.config';
|
|||||||
|
|
||||||
export interface TitleBarProps {
|
export interface TitleBarProps {
|
||||||
name: string;
|
name: string;
|
||||||
nameDiscriminator?: string;
|
|
||||||
image?: string;
|
image?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function TitleBar({
|
export default function TitleBar({ name, image, description }: TitleBarProps) {
|
||||||
name,
|
|
||||||
nameDiscriminator,
|
|
||||||
image,
|
|
||||||
description,
|
|
||||||
}: TitleBarProps) {
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@@ -33,9 +27,8 @@ export default function TitleBar({
|
|||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center',
|
||||||
gap: 4,
|
gap: 4,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -51,24 +44,11 @@ export default function TitleBar({
|
|||||||
color: theme.colors.textHighContrast,
|
color: theme.colors.textHighContrast,
|
||||||
fontWeight: 900,
|
fontWeight: 900,
|
||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
maxWidth: nameDiscriminator === undefined ? Infinity : 240,
|
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{name}
|
{name}
|
||||||
</span>
|
</span>
|
||||||
{nameDiscriminator && (
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
fontSize: 32,
|
|
||||||
color: theme.colors.textLowContrast,
|
|
||||||
fontWeight: 900,
|
|
||||||
whiteSpace: 'nowrap',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{nameDiscriminator}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span style={{ color: theme.colors.textLowContrast, fontSize: 16 }}>
|
<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 { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
|
||||||
import { context } from '../blitzkrieg/context';
|
import { context } from '../blitzkrieg/context';
|
||||||
import getWargamingResponse from './getWargamingResponse';
|
import getWargamingResponse from './getWargamingResponse';
|
||||||
|
import { TreeTypeString } from '../../components/Tanks';
|
||||||
export type TankType = 'AT-SPG' | 'lightTank' | 'mediumTank' | 'heavyTank';
|
|
||||||
|
|
||||||
export interface TankopediaEntry {
|
export interface TankopediaEntry {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -14,10 +13,10 @@ export interface TankopediaEntry {
|
|||||||
cost: { price_credit: number; price_gold: number };
|
cost: { price_credit: number; price_gold: number };
|
||||||
images: { preview: string; normal: string };
|
images: { preview: string; normal: string };
|
||||||
tank_id: number;
|
tank_id: number;
|
||||||
type: TankType;
|
type: string;
|
||||||
description: string;
|
description: string;
|
||||||
}
|
}
|
||||||
|
type asd = TreeTypeString
|
||||||
export interface Tankopedia {
|
export interface Tankopedia {
|
||||||
[id: number]: TankopediaEntry | undefined;
|
[id: number]: TankopediaEntry | undefined;
|
||||||
}
|
}
|
||||||
@@ -77,21 +76,21 @@ export const tankopediaInfo = getWargamingResponse<TankopediaInfo>(
|
|||||||
);
|
);
|
||||||
console.log('Cached tankopedia info');
|
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',
|
'AT-SPG': 'https://i.imgur.com/BIHSEH0.png',
|
||||||
lightTank: 'https://i.imgur.com/CSNha5V.png',
|
lightTank: 'https://i.imgur.com/CSNha5V.png',
|
||||||
mediumTank: 'https://i.imgur.com/wvf3ltm.png',
|
mediumTank: 'https://i.imgur.com/wvf3ltm.png',
|
||||||
heavyTank: 'https://i.imgur.com/ECeqlZa.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',
|
'AT-SPG': 'https://i.imgur.com/TCu3EdR.png',
|
||||||
lightTank: 'https://i.imgur.com/zdkpTRb.png',
|
lightTank: 'https://i.imgur.com/zdkpTRb.png',
|
||||||
mediumTank: 'https://i.imgur.com/3z7eHX6.png',
|
mediumTank: 'https://i.imgur.com/3z7eHX6.png',
|
||||||
heavyTank: 'https://i.imgur.com/P3vbmyA.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',
|
'AT-SPG': 'https://i.imgur.com/WTjeirB.png',
|
||||||
lightTank: 'https://i.imgur.com/EwhtKkU.png',
|
lightTank: 'https://i.imgur.com/EwhtKkU.png',
|
||||||
mediumTank: 'https://i.imgur.com/u8YDMBh.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,
|
all: emptyAllStats,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface DiffedTankStats {
|
||||||
|
diff: Record<number, AllStats>;
|
||||||
|
order: number[];
|
||||||
|
}
|
||||||
|
|
||||||
// BIG TODO: merge this neatly with diffNormalizedTankStats
|
// BIG TODO: merge this neatly with diffNormalizedTankStats
|
||||||
export default async function getDiffedTankStats(
|
export default async function getDiffedTankStats(
|
||||||
server: Region,
|
server: Region,
|
||||||
@@ -66,7 +71,7 @@ export default async function getDiffedTankStats(
|
|||||||
const playedTanks: number[] = [];
|
const playedTanks: number[] = [];
|
||||||
|
|
||||||
// fetch history node right before "start" and right before "end"
|
// fetch history node right before "start" and right before "end"
|
||||||
const diffed: Record<number, AllStats> = {};
|
const diff: Record<number, AllStats> = {};
|
||||||
inRangeTanks.forEach((tankId) => {
|
inRangeTanks.forEach((tankId) => {
|
||||||
const sortedHistory = tankSortedHistory[tankId];
|
const sortedHistory = tankSortedHistory[tankId];
|
||||||
const previousIndex =
|
const previousIndex =
|
||||||
@@ -77,30 +82,30 @@ export default async function getDiffedTankStats(
|
|||||||
const latest =
|
const latest =
|
||||||
sortedHistory[latestIndex] ?? sortedHistory[sortedHistory.length - 1];
|
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);
|
return get(latest.all) - get(previous.all);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if there was a change in battles as games in ratings do update last_battle_time
|
// check if there was a change in battles as games in ratings do update last_battle_time
|
||||||
if (diff((a) => a.battles) > 0) {
|
if (d((a) => a.battles) > 0) {
|
||||||
diffed[tankId] = {
|
diff[tankId] = {
|
||||||
battles: diff((a) => a.battles),
|
battles: d((a) => a.battles),
|
||||||
capture_points: diff((a) => a.capture_points),
|
capture_points: d((a) => a.capture_points),
|
||||||
damage_dealt: diff((a) => a.damage_dealt),
|
damage_dealt: d((a) => a.damage_dealt),
|
||||||
damage_received: diff((a) => a.damage_received),
|
damage_received: d((a) => a.damage_received),
|
||||||
dropped_capture_points: diff((a) => a.dropped_capture_points),
|
dropped_capture_points: d((a) => a.dropped_capture_points),
|
||||||
frags: diff((a) => a.frags),
|
frags: d((a) => a.frags),
|
||||||
frags8p: diff((a) => a.frags8p),
|
frags8p: d((a) => a.frags8p),
|
||||||
hits: diff((a) => a.hits),
|
hits: d((a) => a.hits),
|
||||||
losses: diff((a) => a.losses),
|
losses: d((a) => a.losses),
|
||||||
max_frags: latest.all.max_frags,
|
max_frags: latest.all.max_frags,
|
||||||
max_xp: latest.all.max_xp,
|
max_xp: latest.all.max_xp,
|
||||||
shots: diff((a) => a.shots),
|
shots: d((a) => a.shots),
|
||||||
spotted: diff((a) => a.spotted),
|
spotted: d((a) => a.spotted),
|
||||||
survived_battles: diff((a) => a.survived_battles),
|
survived_battles: d((a) => a.survived_battles),
|
||||||
win_and_survived: diff((a) => a.win_and_survived),
|
win_and_survived: d((a) => a.win_and_survived),
|
||||||
wins: diff((a) => a.wins),
|
wins: d((a) => a.wins),
|
||||||
xp: diff((a) => a.xp),
|
xp: d((a) => a.xp),
|
||||||
};
|
};
|
||||||
|
|
||||||
playedTanks.push(tankId);
|
playedTanks.push(tankId);
|
||||||
@@ -116,5 +121,5 @@ export default async function getDiffedTankStats(
|
|||||||
return bLatest.last_battle_time - aLatest.last_battle_time;
|
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';
|
import getTimeDaysAgo from './getTimeDaysAgo';
|
||||||
|
|
||||||
export default function getPeriodStart(region: Region, period: PeriodSize) {
|
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 {
|
import { SlashCommandBuilder, SlashCommandSubcommandBuilder } from 'discord.js';
|
||||||
SlashCommandSubcommandBuilder,
|
|
||||||
SlashCommandSubcommandGroupBuilder,
|
|
||||||
} from 'discord.js';
|
|
||||||
|
|
||||||
export type PeriodType = 'period' | 'custom';
|
export type PeriodType = 'today' | '30' | '60' | '90' | 'custom';
|
||||||
export type PeriodSize = `${number}` | 'career';
|
export type PeriodSize = 'today' | `${number}` | 'career';
|
||||||
|
|
||||||
export default function addPeriodSubCommands(
|
export default function addPeriodSubCommands(
|
||||||
option: SlashCommandSubcommandGroupBuilder,
|
option: SlashCommandBuilder,
|
||||||
extra: (
|
extra: (
|
||||||
option: SlashCommandSubcommandBuilder,
|
option: SlashCommandSubcommandBuilder,
|
||||||
) => SlashCommandSubcommandBuilder = (option) => option,
|
) => SlashCommandSubcommandBuilder = (option) => option,
|
||||||
) {
|
) {
|
||||||
return option
|
option
|
||||||
.addSubcommand((option) =>
|
.addSubcommand((option) =>
|
||||||
extra(
|
extra(option.setName('today').setDescription('Today')),
|
||||||
option
|
)
|
||||||
.setName('period' satisfies PeriodType)
|
.addSubcommand((option) =>
|
||||||
.setDescription("Preset period's statistics")
|
extra(option.setName('30').setDescription('30 days')),
|
||||||
.addStringOption((option) =>
|
)
|
||||||
option
|
.addSubcommand((option) =>
|
||||||
.setName('period')
|
extra(option.setName('60').setDescription('60 days')),
|
||||||
.setDescription('A preset period')
|
)
|
||||||
.setChoices(
|
.addSubcommand((option) =>
|
||||||
{ name: 'Today', value: '1' satisfies PeriodSize },
|
extra(option.setName('90').setDescription('90 days')),
|
||||||
{ 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),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.addSubcommand((option) =>
|
.addSubcommand((option) =>
|
||||||
extra(
|
extra(
|
||||||
option
|
option
|
||||||
.setName('custom' satisfies PeriodType)
|
.setName('custom')
|
||||||
.setDescription("Custom period's statistics")
|
.setDescription('Custom range')
|
||||||
.addIntegerOption((option) =>
|
.addIntegerOption((option) =>
|
||||||
option
|
option
|
||||||
.setName('start')
|
.setName('start')
|
||||||
.setDescription('Days ago from today')
|
.setDescription('Start in number of days ago from today')
|
||||||
.setMinValue(0)
|
|
||||||
.setRequired(true),
|
.setRequired(true),
|
||||||
)
|
)
|
||||||
.addIntegerOption((option) =>
|
.addIntegerOption((option) =>
|
||||||
option
|
option
|
||||||
.setName('end')
|
.setName('end')
|
||||||
.setDescription('Days ago from today')
|
.setDescription('End in number of days')
|
||||||
.setMinValue(0)
|
|
||||||
.setRequired(true),
|
.setRequired(true),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,91 +1,70 @@
|
|||||||
import { SlashCommandBuilder } from 'discord.js';
|
import { SlashCommandBuilder, SlashCommandSubcommandBuilder } from 'discord.js';
|
||||||
import { TreeTypeString } from '../../components/Tanks';
|
import { TREE_TYPE_NAMES, TreeTypeString } from '../../components/Tanks';
|
||||||
import { StatType } from '../../renderers/stats';
|
|
||||||
import { tankopediaInfo } from '../blitz/tankopedia';
|
import { tankopediaInfo } from '../blitz/tankopedia';
|
||||||
import addPeriodSubCommands from './addPeriodSubCommands';
|
import addPeriodSubCommands from './addPeriodSubCommands';
|
||||||
import addTankChoices from './addTankChoices';
|
import addTankChoices from './addTankChoices';
|
||||||
import addTierChoices from './addTierChoices';
|
import addTierChoices from './addTierChoices';
|
||||||
import addUsernameChoices from './addUsernameChoices';
|
|
||||||
|
|
||||||
export default async function addStatTypeSubCommandGroups(
|
export default async function addStatTypeSubCommandGroups(
|
||||||
option: SlashCommandBuilder,
|
option: SlashCommandBuilder,
|
||||||
addMultiTank = true,
|
extra: (
|
||||||
|
option: SlashCommandSubcommandBuilder,
|
||||||
|
) => SlashCommandSubcommandBuilder = (option) => option,
|
||||||
) {
|
) {
|
||||||
const awaitedTankopediaInfo = await tankopediaInfo;
|
const awaitedTankopediaInfo = await tankopediaInfo;
|
||||||
|
|
||||||
option
|
addPeriodSubCommands(option, (option) => {
|
||||||
.addSubcommandGroup((option) =>
|
extra(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'),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (addMultiTank)
|
return option
|
||||||
option.addSubcommandGroup((option) =>
|
.addStringOption((option) =>
|
||||||
addPeriodSubCommands(option, (option) =>
|
|
||||||
option
|
option
|
||||||
.addStringOption(addUsernameChoices)
|
.setName('nation')
|
||||||
.addStringOption((option) => {
|
.setDescription('Nation')
|
||||||
option
|
.addChoices(
|
||||||
.setName('nation')
|
...Object.entries(awaitedTankopediaInfo.vehicle_nations).map(
|
||||||
.setDescription('Nation')
|
([type, name]) => ({ name, value: type }),
|
||||||
.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),
|
|
||||||
)
|
)
|
||||||
.addStringOption((option) =>
|
.setRequired(false),
|
||||||
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),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.setName('multi-tank' satisfies StatType)
|
.addStringOption((option) => addTierChoices(option).setRequired(false))
|
||||||
.setDescription('Multiple tanks'),
|
.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;
|
return option;
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,7 @@
|
|||||||
import {
|
import { CacheType, ChatInputCommandInteraction } from 'discord.js';
|
||||||
CacheType,
|
|
||||||
ChatInputCommandInteraction,
|
|
||||||
CommandInteractionOption,
|
|
||||||
} from 'discord.js';
|
|
||||||
|
|
||||||
export interface Parameters {
|
export interface Parameters {
|
||||||
[key: string]: CommandInteractionOption['value'] | undefined | null;
|
[key: string]: string | number | boolean | undefined | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function interactionToURL(
|
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 { ButtonInteraction, CacheType } from 'discord.js';
|
||||||
import { CYCLIC_API } from '../../constants/cyclic';
|
|
||||||
import { Region } from '../../constants/regions';
|
import { Region } from '../../constants/regions';
|
||||||
import resolvePeriodFromURL from './resolvePeriodFromURL';
|
import resolvePeriodFromURL from './resolvePeriodFromURL';
|
||||||
|
|
||||||
@@ -7,5 +6,8 @@ export default function resolvePeriodFromButton(
|
|||||||
server: Region,
|
server: Region,
|
||||||
interaction: ButtonInteraction<CacheType>,
|
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';
|
import { PeriodSize, PeriodType } from '../discord/addPeriodSubCommands';
|
||||||
|
|
||||||
export function getPeriodOptionName(period: PeriodSize) {
|
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 {
|
export interface ResolvedPeriod {
|
||||||
statsName: string;
|
name: string;
|
||||||
evolutionName: string;
|
|
||||||
start: number;
|
start: number;
|
||||||
end: number;
|
end: number;
|
||||||
}
|
}
|
||||||
@@ -20,33 +21,28 @@ export default function resolvePeriodFromCommand(
|
|||||||
region: Region,
|
region: Region,
|
||||||
interaction: ChatInputCommandInteraction<CacheType>,
|
interaction: ChatInputCommandInteraction<CacheType>,
|
||||||
) {
|
) {
|
||||||
let statsName: string;
|
let name: string;
|
||||||
let evolutionName: string;
|
|
||||||
let start: number;
|
let start: number;
|
||||||
let end: number;
|
let end: number;
|
||||||
const periodSubcommand = interaction.options.getSubcommand() as PeriodType;
|
const periodSubcommand = interaction.options.getSubcommand() as PeriodType;
|
||||||
const periodOption = interaction.options.getString('period') as PeriodSize;
|
|
||||||
|
|
||||||
if (periodSubcommand === 'custom') {
|
if (periodSubcommand === 'custom') {
|
||||||
const startRaw = interaction.options.getInteger('start', true);
|
const startOption = interaction.options.getInteger('start', true);
|
||||||
const endRaw = interaction.options.getInteger('end', true);
|
const endOption = interaction.options.getInteger('end', true);
|
||||||
const startDaysAgoMin = Math.min(startRaw, endRaw);
|
const startDaysAgoMin = Math.min(startOption, endOption);
|
||||||
const endDaysAgoMax = Math.max(startRaw, endRaw);
|
const endDaysAgoMax = Math.max(startOption, endOption);
|
||||||
|
|
||||||
statsName = `${startDaysAgoMin} to ${endDaysAgoMax} days' statistics`;
|
name = `${startDaysAgoMin} - ${endDaysAgoMax} days`;
|
||||||
evolutionName = `${startDaysAgoMin} to ${endDaysAgoMax} days' evolution`;
|
|
||||||
start = getTimeDaysAgo(region, endDaysAgoMax);
|
start = getTimeDaysAgo(region, endDaysAgoMax);
|
||||||
end = getTimeDaysAgo(region, startDaysAgoMin);
|
end = getTimeDaysAgo(region, startDaysAgoMin);
|
||||||
} else {
|
} else {
|
||||||
statsName = `${getPeriodOptionName(periodOption)} Statistics`;
|
name = getPeriodOptionName(periodSubcommand);
|
||||||
evolutionName = `${getPeriodOptionName(periodOption)} Evolution`;
|
start = getPeriodStart(region, periodSubcommand);
|
||||||
start = getPeriodStart(region, periodOption);
|
|
||||||
end = getPeriodNow();
|
end = getPeriodNow();
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
statsName,
|
name,
|
||||||
evolutionName,
|
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
} satisfies ResolvedPeriod;
|
} satisfies ResolvedPeriod;
|
||||||
|
|||||||
@@ -2,24 +2,22 @@ import { Region } from '../../constants/regions';
|
|||||||
import getPeriodNow from '../blitzstars/getPeriodNow';
|
import getPeriodNow from '../blitzstars/getPeriodNow';
|
||||||
import getPeriodStart from '../blitzstars/getPeriodStart';
|
import getPeriodStart from '../blitzstars/getPeriodStart';
|
||||||
import getTimeDaysAgo from '../blitzstars/getTimeDaysAgo';
|
import getTimeDaysAgo from '../blitzstars/getTimeDaysAgo';
|
||||||
import { PeriodSize, PeriodType } from './addPeriodSubCommands';
|
import { PeriodType } from './addPeriodSubCommands';
|
||||||
import {
|
import {
|
||||||
ResolvedPeriod,
|
ResolvedPeriod,
|
||||||
getPeriodOptionName,
|
getPeriodOptionName,
|
||||||
} from './resolvePeriodFromCommand';
|
} from './resolvePeriodFromCommand';
|
||||||
|
|
||||||
export default function resolvePeriodFromURL(
|
export default function resolvePeriodFromURL(
|
||||||
server: Region,
|
region: Region,
|
||||||
urlString: string,
|
urlString: string,
|
||||||
) {
|
) {
|
||||||
let statsName: string;
|
let name: string;
|
||||||
let evolutionName: string;
|
|
||||||
let start: number;
|
let start: number;
|
||||||
let end: number;
|
let end: number;
|
||||||
const url = new URL(urlString);
|
const url = new URL(urlString);
|
||||||
const path = url.pathname.split('/').filter(Boolean);
|
const path = url.pathname.split('/');
|
||||||
const periodSubcommand = path[path.length - 1] as PeriodType;
|
const periodSubcommand = path[2] as PeriodType;
|
||||||
const periodOption = url.searchParams.get('period') as PeriodSize;
|
|
||||||
|
|
||||||
if (periodSubcommand === 'custom') {
|
if (periodSubcommand === 'custom') {
|
||||||
const startRaw = parseInt(url!.searchParams.get('start')!);
|
const startRaw = parseInt(url!.searchParams.get('start')!);
|
||||||
@@ -27,16 +25,18 @@ export default function resolvePeriodFromURL(
|
|||||||
const startDaysAgoMin = Math.min(startRaw, endRaw);
|
const startDaysAgoMin = Math.min(startRaw, endRaw);
|
||||||
const endDaysAgoMax = Math.max(startRaw, endRaw);
|
const endDaysAgoMax = Math.max(startRaw, endRaw);
|
||||||
|
|
||||||
statsName = `${startDaysAgoMin} to ${endDaysAgoMax} days' statistics`;
|
name = `${startDaysAgoMin} - ${endDaysAgoMax} days`;
|
||||||
evolutionName = `${startDaysAgoMin} to ${endDaysAgoMax} days' evolution`;
|
start = getTimeDaysAgo(region, endDaysAgoMax);
|
||||||
start = getTimeDaysAgo(server, endDaysAgoMax);
|
end = getTimeDaysAgo(region, startDaysAgoMin);
|
||||||
end = getTimeDaysAgo(server, startDaysAgoMin);
|
|
||||||
} else {
|
} else {
|
||||||
statsName = `${getPeriodOptionName(periodOption)} Statistics`;
|
name = getPeriodOptionName(periodSubcommand);
|
||||||
evolutionName = `${getPeriodOptionName(periodOption)} Evolution`;
|
start = getPeriodStart(region, periodSubcommand);
|
||||||
start = getPeriodStart(server, periodOption);
|
|
||||||
end = getPeriodNow();
|
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 { ButtonInteraction, CacheType } from 'discord.js';
|
||||||
import { CYCLIC_API } from '../../constants/cyclic';
|
|
||||||
import resolvePlayerFromURL from './resolvePlayerFromURL';
|
import resolvePlayerFromURL from './resolvePlayerFromURL';
|
||||||
|
|
||||||
export default async function resolvePlayerFromButton(
|
export default async function resolvePlayerFromButton(
|
||||||
interaction: ButtonInteraction<CacheType>,
|
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 { ButtonInteraction, CacheType } from 'discord.js';
|
||||||
import { commands } from '..';
|
import { commands } from '..';
|
||||||
import { CYCLIC_API } from '../../../constants/cyclic';
|
|
||||||
import normalizeInteractionReturnable from '../../../core/discord/normalizeInteractionReturnable';
|
import normalizeInteractionReturnable from '../../../core/discord/normalizeInteractionReturnable';
|
||||||
import throwError from '../../../core/node/throwError';
|
import throwError from '../../../core/node/throwError';
|
||||||
|
|
||||||
@@ -9,7 +8,7 @@ export default async function handleButton(
|
|||||||
) {
|
) {
|
||||||
await interaction.deferUpdate();
|
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 commandName = url.pathname.split('/')[1];
|
||||||
const button = (await commands)[commandName]?.button;
|
const button = (await commands)[commandName]?.button;
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ export default async function handleChatInputCommand(
|
|||||||
) {
|
) {
|
||||||
const registry = (await commands)[interaction.commandName];
|
const registry = (await commands)[interaction.commandName];
|
||||||
|
|
||||||
console.log(interaction.toString());
|
|
||||||
await interaction.deferReply();
|
await interaction.deferReply();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -44,7 +44,8 @@ export type InteractionRawReturnable =
|
|||||||
| EmbedBuilder
|
| EmbedBuilder
|
||||||
| ButtonBuilder
|
| ButtonBuilder
|
||||||
| AttachmentBuilder
|
| AttachmentBuilder
|
||||||
| JSX.Element;
|
| JSX.Element
|
||||||
|
| null;
|
||||||
export type InteractionIterableReturnable =
|
export type InteractionIterableReturnable =
|
||||||
| InteractionRawReturnable
|
| InteractionRawReturnable
|
||||||
| InteractionRawReturnable[];
|
| InteractionRawReturnable[];
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import { MultiTankFilters, StatType } from './stats';
|
|||||||
|
|
||||||
export default async function fullStats<Type extends StatType>(
|
export default async function fullStats<Type extends StatType>(
|
||||||
type: Type,
|
type: Type,
|
||||||
{ start, end, statsName }: ResolvedPeriod,
|
{ start, end, name: statsName }: ResolvedPeriod,
|
||||||
{ region: server, id }: ResolvedPlayer,
|
{ region: server, id }: ResolvedPlayer,
|
||||||
filters: Type extends 'tank'
|
filters: Type extends 'tank'
|
||||||
? number
|
? number
|
||||||
@@ -50,7 +50,12 @@ export default async function fullStats<Type extends StatType>(
|
|||||||
image = (await tankopedia)[filters as number]?.images.normal;
|
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;
|
const awaitedTankopedia = await tankopedia;
|
||||||
let stats: AllStats | undefined;
|
let stats: AllStats | undefined;
|
||||||
let supplementaryStats: SupplementaryStats;
|
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,
|
showTotal = true,
|
||||||
naked?: boolean,
|
naked?: boolean,
|
||||||
) {
|
) {
|
||||||
const { diffed, order } = await getDiffedTankStats(
|
const { diff: diffed, order } = await getDiffedTankStats(
|
||||||
server,
|
server,
|
||||||
id,
|
id,
|
||||||
getTimeDaysAgo(server, 1),
|
getTimeDaysAgo(server, 1),
|
||||||
|
|||||||
Reference in new issue
Block a user