fix: types

This commit is contained in:
TrèsAbhi committed 2024-09-07 21:45:00 -05:00
1 parent 9ae8b0ac49
commit 3c80a43435
255 files changed
+797 -838

No files matched your search

@@ -1,4 +1,4 @@
import { getBlitzStarsLinkButton } from '../../core/blitzstars/getBlitzStarsLinkButton';
import { getBlitzStarsLinkButton } from '../../../../website/src/core/blitzstars/getBlitzStarsLinkButton';
import addPeriodicFilterOptions from '../../core/discord/addPeriodicFilterOptions';
import addUsernameChoices from '../../core/discord/addUsernameChoices';
import autocompleteTanks from '../../core/discord/autocompleteTanks';
+16 -15
View File
@@ -1,27 +1,28 @@
import getStatsInPeriod from '@blitzkit/core/src/blitzstars/getStatsInPeriod';
import {
BlitzStats,
filterStats,
StatFilters,
TankDefinition,
tankDefinitions,
} from '@blitzkit/core';
import calculateWN8 from '@blitzkit/core/src/statistics/calculateWN8';
import getWN8Percentile from '@blitzkit/core/src/statistics/getWN8Percentile';
import { Locale } from 'discord.js';
import { chunk } from 'lodash';
import { getAccountInfo } from '../../../../website/src/core/blitz/getAccountInfo';
import { getClanAccountInfo } from '../../../../website/src/core/blitz/getClanAccountInfo';
import getTankStats from '../../../../website/src/core/blitz/getTankStats';
import { filtersToDescription } from '../../../../website/src/core/blitzkit/filtersToDescription';
import { tankAverages } from '../../../../website/src/core/blitzstars/tankAverages';
import { UserError } from '../../../../website/src/hooks/userError';
import * as Breakdown from '../../components/Breakdown';
import CommandWrapper from '../../components/CommandWrapper';
import NoData from '../../components/NoData';
import TitleBar from '../../components/TitleBar';
import { getAccountInfo } from '../../core/blitz/getAccountInfo';
import { getClanAccountInfo } from '../../core/blitz/getClanAccountInfo';
import getTankStats from '../../core/blitz/getTankStats';
import { filtersToDescription } from '../../core/blitzkit/filtersToDescription';
import {
TankDefinition,
tankDefinitions,
} from '../../core/blitzkit/tankDefinitions';
import { tankAverages } from '../../core/blitzstars/tankAverages';
import getStatsInPeriod from '../../core/blitzstars/getStatsInPeriod';
import { ResolvedPeriod } from '../../core/discord/resolvePeriodFromCommand';
import { ResolvedPlayer } from '../../core/discord/resolvePlayerFromCommand';
import { translator } from '../../core/localization/translator';
import calculateWN8 from '../../core/statistics/calculateWN8';
import { BlitzStats } from '../../core/statistics/compositeStats/constants';
import { StatFilters, filterStats } from '../../core/statistics/filterStats';
import getWN8Percentile from '../../core/statistics/getWN8Percentile';
import { UserError } from '../../hooks/userError';
const ROWS_PER_PAGE = 8;
const MAX_PAGES = 9;
+1 -1
View File
@@ -1,4 +1,4 @@
import packageJSON from '../../package.json' assert { type: 'json' };
import packageJSON from '../../../../package.json' assert { type: 'json' };
import { client } from '../core/discord/client';
import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
import { translator } from '../core/localization/translator';
+7 -7
View File
@@ -1,16 +1,16 @@
import { Locale, SlashCommandSubcommandGroupBuilder } from 'discord.js';
import { getAccountInfo } from '../../../website/src/core/blitz/getAccountInfo';
import { getClanAccountInfo } from '../../../website/src/core/blitz/getClanAccountInfo';
import resolveTankId from '../../../website/src/core/blitz/resolveTankId';
import { emblemURL } from '../../../website/src/core/blitzkit/emblemURL';
import { getBlitzStarsLinkButton } from '../../../website/src/core/blitzstars/getBlitzStarsLinkButton';
import getPlayerHistories from '../../../website/src/core/blitzstars/getPlayerHistories';
import CommandWrapper from '../components/CommandWrapper';
import * as Graph from '../components/Graph';
import { LineColor } from '../components/Graph/components/Line/constants';
import NoData from '../components/NoData';
import TitleBar from '../components/TitleBar';
import { getAccountInfo } from '../core/blitz/getAccountInfo';
import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo';
import resolveTankId from '../core/blitz/resolveTankId';
import { emblemURL } from '../core/blitzkit/emblemURL';
import getTankHistories from '../core/blitzkit/getTankHistories';
import { getBlitzStarsLinkButton } from '../core/blitzstars/getBlitzStarsLinkButton';
import getPlayerHistories from '../core/blitzstars/getPlayerHistories';
import getTankHistories from '../core/blitzstars/getTankHistories';
import { addPeriodSubCommands } from '../core/discord/addPeriodSubCommands';
import addTankChoices from '../core/discord/addTankChoices';
import addUsernameChoices from '../core/discord/addUsernameChoices';
+7 -8
View File
@@ -1,15 +1,16 @@
import getStatsInPeriod from '@blitzkit/core/src/blitzstars/getStatsInPeriod';
import { StatFilters, filterStats, getTierWeights } from '@blitzkit/core';
import { Locale } from 'discord.js';
import { getAccountInfo } from '../../../website/src/core/blitz/getAccountInfo';
import { getClanAccountInfo } from '../../../website/src/core/blitz/getClanAccountInfo';
import { emblemURL } from '../../../website/src/core/blitzkit/emblemURL';
import { filtersToDescription } from '../../../website/src/core/blitzkit/filtersToDescription';
import { getBlitzStarsLinkButton } from '../../../website/src/core/blitzstars/getBlitzStarsLinkButton';
import CommandWrapper from '../components/CommandWrapper';
import GenericAllStats from '../components/GenericAllStats';
import NoData from '../components/NoData';
import TierWeights from '../components/TierWeights';
import TitleBar from '../components/TitleBar';
import { getAccountInfo } from '../core/blitz/getAccountInfo';
import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo';
import { emblemURL } from '../core/blitzkit/emblemURL';
import { filtersToDescription } from '../core/blitzkit/filtersToDescription';
import { getBlitzStarsLinkButton } from '../core/blitzstars/getBlitzStarsLinkButton';
import getStatsInPeriod from '../core/blitzstars/getStatsInPeriod';
import addPeriodicFilterOptions from '../core/discord/addPeriodicFilterOptions';
import addUsernameChoices from '../core/discord/addUsernameChoices';
import autocompleteTanks from '../core/discord/autocompleteTanks';
@@ -28,8 +29,6 @@ import resolvePlayerFromButton from '../core/discord/resolvePlayerFromButton';
import resolvePlayerFromCommand, {
ResolvedPlayer,
} from '../core/discord/resolvePlayerFromCommand';
import { StatFilters, filterStats } from '../core/statistics/filterStats';
import { getTierWeights } from '../core/statistics/getTierWeights';
import { CommandRegistry } from '../events/interactionCreate';
async function render(
+5 -3
View File
@@ -1,10 +1,10 @@
import { Locale } from 'discord.js';
import { getAccountInfo } from '../../../website/src/core/blitz/getAccountInfo';
import { getClanInfo } from '../../../website/src/core/blitz/getClanInfo';
import CommandWrapper from '../components/CommandWrapper';
import GenericStats from '../components/GenericStats';
import NoData from '../components/NoData';
import TitleBar from '../components/TitleBar';
import { getAccountInfo } from '../core/blitz/getAccountInfo';
import { getClanInfo } from '../core/blitz/getClanInfo';
import addClanChoices from '../core/discord/addClanChoices';
import autocompleteClan from '../core/discord/autocompleteClan';
import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
@@ -72,7 +72,9 @@ export const inactiveCommand = new Promise<CommandRegistry>((resolve) => {
<TitleBar
title={clanInfo.name}
image={`https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clanInfo.emblem_set_id}.png`}
description={`${translate('bot.commands.inactive.body.subtitle', [`${threshold}`])} • ${new Date().toLocaleDateString(interaction.locale)}`}
description={`${translate('bot.commands.inactive.body.subtitle', [
`${threshold}`,
])} • ${new Date().toLocaleDateString(interaction.locale)}`}
/>
{!hasInactiveMembers && (
+3 -3
View File
@@ -1,9 +1,9 @@
import { teal } from '@radix-ui/colors';
import { getAccountInfo } from '../../../website/src/core/blitz/getAccountInfo';
import { getClanAccountInfo } from '../../../website/src/core/blitz/getClanAccountInfo';
import { linkBlitzAndDiscord } from '../../../website/src/core/blitzkit/linkBlitzAndDiscord';
import { Glow } from '../components/AllStatsOverview/components/HeroStat/components/Glow';
import CommandWrapper from '../components/CommandWrapper';
import { getAccountInfo } from '../core/blitz/getAccountInfo';
import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo';
import { linkBlitzAndDiscord } from '../core/blitzkit/linkBlitzAndDiscord';
import addUsernameChoices from '../core/discord/addUsernameChoices';
import autocompleteUsername from '../core/discord/autocompleteUsername';
import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
+8 -8
View File
@@ -1,16 +1,16 @@
import { gameDefinitions } from '@blitzkit/core/src/blitzkit/gameDefinitions';
import { chunk } from 'lodash';
import markdownEscape from 'markdown-escape';
import { TANK_CLASSES } from '../components/Tanks/components/Item/constants';
import { getAccountInfo } from '../core/blitz/getAccountInfo';
import getTankStats from '../core/blitz/getTankStats';
import {
TANK_CLASSES,
TIER_ROMAN_NUMERALS,
TankDefinition,
Tier,
flags,
tankDefinitions,
} from '../core/blitzkit/tankDefinitions';
import { TIER_ROMAN_NUMERALS } from '../core/blitzkit/tankDefinitions/constants';
} from '@blitzkit/core';
import { gameDefinitions } from '@blitzkit/core/src/blitzkit/gameDefinitions';
import { chunk } from 'lodash';
import markdownEscape from 'markdown-escape';
import { getAccountInfo } from '../../../website/src/core/blitz/getAccountInfo';
import getTankStats from '../../../website/src/core/blitz/getTankStats';
import addTierChoices from '../core/discord/addTierChoices';
import addUsernameChoices from '../core/discord/addUsernameChoices';
import autocompleteUsername from '../core/discord/autocompleteUsername';
+1 -1
View File
@@ -1,5 +1,5 @@
import markdownEscape from 'markdown-escape';
import { getAccountInfo } from '../core/blitz/getAccountInfo';
import { getAccountInfo } from '../../../website/src/core/blitz/getAccountInfo';
import addUsernameChoices from '../core/discord/addUsernameChoices';
import autocompleteUsername from '../core/discord/autocompleteUsername';
import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
+16 -15
View File
@@ -1,24 +1,23 @@
import { BkrlFormat, deltaBkrlBlitzStats } from '@blitzkit/core';
import { SlashCommandSubcommandBuilder } from 'discord.js';
import markdownEscape from 'markdown-escape';
import { getAccountInfo } from '../../../website/src/core/blitz/getAccountInfo';
import { getClanAccountInfo } from '../../../website/src/core/blitz/getClanAccountInfo';
import { getLeagueFromScore } from '../../../website/src/core/blitz/getLeagueFromScore';
import getRatingInfo from '../../../website/src/core/blitz/getRatingInfo';
import { getRatingNeighbors } from '../../../website/src/core/blitz/getRatingNeighbors';
import { normalizeLeagueIcon } from '../../../website/src/core/blitz/normalizeLeagueIcon';
import { emblemURL } from '../../../website/src/core/blitzkit/emblemURL';
import getArchivedRatingMidnightLeaderboard from '../../../website/src/core/blitzkit/getArchivedRatingMidnightLeaderboard';
import { iconPng } from '../../../website/src/core/blitzkit/iconPng';
import { Glow } from '../components/AllStatsOverview/components/HeroStat/components/Glow';
import CommandWrapper from '../components/CommandWrapper';
import { DeltaCaret } from '../components/DeltaCaret';
import TitleBar from '../components/TitleBar';
import { getAccountInfo } from '../core/blitz/getAccountInfo';
import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo';
import { getLeagueFromScore } from '../core/blitz/getLeagueFromScore';
import getRatingInfo from '../core/blitz/getRatingInfo';
import { getRatingNeighbors } from '../core/blitz/getRatingNeighbors';
import { normalizeLeagueIcon } from '../core/blitz/normalizeLeagueIcon';
import { emblemURL } from '../core/blitzkit/emblemURL';
import getArchivedRatingMidnightLeaderboard from '../core/blitzkit/getArchivedRatingMidnightLeaderboard';
import { iconPng } from '../core/blitzkit/iconPng';
import addUsernameChoices from '../core/discord/addUsernameChoices';
import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
import { translator } from '../core/localization/translator';
import { deltaBkrlBlitzStats } from '../core/statistics/deltaBkrlBlitzStats';
import { BkrlFormat } from '../core/streams/bkrl';
import { CommandRegistry } from '../events/interactionCreate';
import { theme } from '../stitches.config';
@@ -122,9 +121,9 @@ export const ratingCommand = new Promise<CommandRegistry>((resolve) => {
<TitleBar
title={accountInfo.nickname}
image={clanImage}
description={`${translate(`bot.commands.rating.body.subtitle.${subcommand}`)} • ${new Date().toLocaleDateString(
interaction.locale,
)}`}
description={`${translate(
`bot.commands.rating.body.subtitle.${subcommand}`,
)} • ${new Date().toLocaleDateString(interaction.locale)}`}
/>
<div
@@ -140,7 +139,9 @@ export const ratingCommand = new Promise<CommandRegistry>((resolve) => {
padding: 16,
backgroundColor: theme.colors[`appBackground2${leagueTheme}`],
borderRadius: 4,
border: `1px solid ${theme.colors[`componentInteractive${leagueTheme}`]}`,
border: `1px solid ${
theme.colors[`componentInteractive${leagueTheme}`]
}`,
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
+23 -21
View File
@@ -1,3 +1,9 @@
import {
BkrlDiscriminatedEntries,
FIRST_MINIMAL_ARCHIVED_RATING_SEASON,
LEAGUES,
Region,
} from '@blitzkit/core';
import {
APIApplicationCommandOptionChoice,
Locale,
@@ -5,22 +11,20 @@ import {
} from 'discord.js';
import { range } from 'lodash';
import markdownEscape from 'markdown-escape';
import { getAccountInfo } from '../../../website/src/core/blitz/getAccountInfo';
import { getClanAccountInfo } from '../../../website/src/core/blitz/getClanAccountInfo';
import getRatingInfo from '../../../website/src/core/blitz/getRatingInfo';
import { getRatingLeague } from '../../../website/src/core/blitz/getRatingLeague';
import { getRatingNeighbors } from '../../../website/src/core/blitz/getRatingNeighbors';
import { isOnGoingRatingSeason } from '../../../website/src/core/blitz/isOnGoingRatingSeason';
import { getArchivedLatestSeasonNumber } from '../../../website/src/core/blitzkit/getArchivedLatestSeasonNumber';
import getArchivedRatingInfo from '../../../website/src/core/blitzkit/getArchivedRatingInfo';
import { getArchivedRatingLeaderboard } from '../../../website/src/core/blitzkit/getArchivedRatingLeaderboard';
import getArchivedRatingMidnightLeaderboard from '../../../website/src/core/blitzkit/getArchivedRatingMidnightLeaderboard';
import { UserError } from '../../../website/src/hooks/userError';
import CommandWrapper from '../components/CommandWrapper';
import * as Leaderboard from '../components/Leaderboard';
import TitleBar from '../components/TitleBar';
import { LEAGUES } from '../constants/leagues';
import { FIRST_MINIMAL_ARCHIVED_RATING_SEASON } from '../constants/rating';
import { Region } from '../constants/regions';
import { getAccountInfo } from '../core/blitz/getAccountInfo';
import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo';
import getRatingInfo from '../core/blitz/getRatingInfo';
import { getRatingLeague } from '../core/blitz/getRatingLeague';
import { getRatingNeighbors } from '../core/blitz/getRatingNeighbors';
import { isOnGoingRatingSeason } from '../core/blitz/isOnGoingRatingSeason';
import { getArchivedLatestSeasonNumber } from '../core/blitzkit/getArchivedLatestSeasonNumber';
import getArchivedRatingInfo from '../core/blitzkit/getArchivedRatingInfo';
import { getArchivedRatingLeaderboard } from '../core/blitzkit/getArchivedRatingLeaderboard';
import getArchivedRatingMidnightLeaderboard from '../core/blitzkit/getArchivedRatingMidnightLeaderboard';
import addRegionChoices from '../core/discord/addRegionChoices';
import addUsernameChoices from '../core/discord/addUsernameChoices';
import autocompleteUsername from '../core/discord/autocompleteUsername';
@@ -28,9 +32,7 @@ import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
import { localizationObject } from '../core/discord/localizationObject';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
import { translator } from '../core/localization/translator';
import { BkrlDiscriminatedEntries } from '../core/streams/bkrl';
import { CommandRegistry } from '../events/interactionCreate';
import { UserError } from '../hooks/userError';
export interface RatingPlayer {
spa_id: number;
@@ -254,7 +256,7 @@ export const ratingLeaderboardCommand = new Promise<CommandRegistry>(
name_localizations: localizationObject(
`common.leagues.${name}`,
),
}) satisfies APIApplicationCommandOptionChoice<string>,
} satisfies APIApplicationCommandOptionChoice<string>),
),
)
.setRequired(true);
@@ -277,8 +279,8 @@ export const ratingLeaderboardCommand = new Promise<CommandRegistry>(
const season = seasonOption
? parseInt(seasonOption)
: onGoingSeason
? latestArchivedSeasonNumber + 1
: latestArchivedSeasonNumber;
? latestArchivedSeasonNumber + 1
: latestArchivedSeasonNumber;
const isCurrentSeason = season === latestArchivedSeasonNumber + 1;
let titleName: string;
let titleImage: string | undefined;
@@ -311,7 +313,7 @@ export const ratingLeaderboardCommand = new Promise<CommandRegistry>(
position: player.number,
clan: player.clan_tag,
nickname: player.nickname,
}) satisfies SimplifiedPlayer,
} satisfies SimplifiedPlayer),
),
)
: await getArchivedRatingLeaderboard(region, season!).then(
@@ -347,7 +349,7 @@ export const ratingLeaderboardCommand = new Promise<CommandRegistry>(
'bot.commands.rating_leaderboard.body.deleted_player',
[`${player.id}`],
),
}) satisfies SimplifiedPlayer,
} satisfies SimplifiedPlayer),
);
},
);
@@ -396,7 +398,7 @@ export const ratingLeaderboardCommand = new Promise<CommandRegistry>(
score: player.score,
clan: player.clan_tag,
nickname: player.nickname,
}) satisfies SimplifiedPlayer,
} satisfies SimplifiedPlayer),
),
)
: await getArchivedRatingLeaderboard(region, season!).then(
+12 -10
View File
@@ -1,19 +1,21 @@
import {
idToRegion,
tankDefinitions,
TIER_ROMAN_NUMERALS,
} from '@blitzkit/core';
import { tankIcon } from '@blitzkit/core/src/blitzkit/tankIcon';
import calculateWN8 from '@blitzkit/core/src/statistics/calculateWN8';
import { clamp } from 'lodash';
import { getClanAccountInfo } from '../../../website/src/core/blitz/getClanAccountInfo';
import { emblemURL } from '../../../website/src/core/blitzkit/emblemURL';
import { iconPng } from '../../../website/src/core/blitzkit/iconPng';
import { mapDefinitions } from '../../../website/src/core/blitzkit/mapDefinitions';
import { RenderConfiguration } from '../../../website/src/core/blitzkit/renderConfiguration';
import { tankAverages } from '../../../website/src/core/blitzstars/tankAverages';
import CommandWrapper from '../components/CommandWrapper';
import TitleBar from '../components/TitleBar';
import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo';
import { idToRegion } from '../core/blitz/idToRegion';
import { emblemURL } from '../core/blitzkit/emblemURL';
import { iconPng } from '../core/blitzkit/iconPng';
import { mapDefinitions } from '../core/blitzkit/mapDefinitions';
import { RenderConfiguration } from '../core/blitzkit/renderConfiguration';
import { tankDefinitions } from '../core/blitzkit/tankDefinitions';
import { TIER_ROMAN_NUMERALS } from '../core/blitzkit/tankDefinitions/constants';
import { tankAverages } from '../core/blitzstars/tankAverages';
import buttonLink from '../core/discord/buttonLink';
import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
import calculateWN8 from '../core/statistics/calculateWN8';
import { CommandRegistry } from '../events/interactionCreate';
import { theme } from '../stitches.config';
+20 -18
View File
@@ -1,22 +1,22 @@
import { Locale, escapeMarkdown } from 'discord.js';
import {
asset,
equipmentPriceMatrix,
TANK_ICONS,
tankDefinitions,
TIER_ROMAN_NUMERALS,
} from '@blitzkit/core';
import { escapeMarkdown, Locale } from 'discord.js';
import { getAccountInfo } from '../../../website/src/core/blitz/getAccountInfo';
import { getClanAccountInfo } from '../../../website/src/core/blitz/getClanAccountInfo';
import getTankStats from '../../../website/src/core/blitz/getTankStats';
import resolveTankId from '../../../website/src/core/blitz/resolveTankId';
import { buildTechTreeLine } from '../../../website/src/core/blitzkit/buildTechTreeLine';
import { emblemURL } from '../../../website/src/core/blitzkit/emblemURL';
import { iconPng } from '../../../website/src/core/blitzkit/iconPng';
import { resolveAncestry } from '../../../website/src/core/blitzkit/resolveAncestry';
import { tankIconPng } from '../../../website/src/core/blitzkit/tankIconPng';
import CommandWrapper from '../components/CommandWrapper';
import TitleBar from '../components/TitleBar';
import { equipmentPriceMatrix } from '../constants/equipmentPrice';
import { getAccountInfo } from '../core/blitz/getAccountInfo';
import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo';
import getTankStats from '../core/blitz/getTankStats';
import resolveTankId from '../core/blitz/resolveTankId';
import { asset } from '../core/blitzkit/asset';
import { buildTechTreeLine } from '../core/blitzkit/buildTechTreeLine';
import { emblemURL } from '../core/blitzkit/emblemURL';
import { iconPng } from '../core/blitzkit/iconPng';
import { resolveAncestry } from '../core/blitzkit/resolveAncestry';
import { tankDefinitions } from '../core/blitzkit/tankDefinitions';
import {
TANK_ICONS,
TIER_ROMAN_NUMERALS,
} from '../core/blitzkit/tankDefinitions/constants';
import { tankIconPng } from '../core/blitzkit/tankIconPng';
import addUsernameChoices from '../core/discord/addUsernameChoices';
import autocompleteTanks from '../core/discord/autocompleteTanks';
import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
@@ -220,7 +220,9 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
<TitleBar
title={nickname}
image={clanImage}
description={`${t`bot.commands.research.body.subtitle`} • ${targetTank.name}`}
description={`${t`bot.commands.research.body.subtitle`} • ${
targetTank.name
}`}
/>
<div
+2 -2
View File
@@ -1,7 +1,7 @@
import { Region } from '@blitzkit/core';
import { Locale } from 'discord.js';
import markdownEscape from 'markdown-escape';
import { Region } from '../constants/regions';
import fetchBlitz from '../core/blitz/fetchBlitz';
import fetchBlitz from '../../../website/src/core/blitz/fetchBlitz';
import addClanChoices from '../core/discord/addClanChoices';
import addRegionChoices from '../core/discord/addRegionChoices';
import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
+4 -4
View File
@@ -1,9 +1,9 @@
import { Region } from '@blitzkit/core';
import { Locale } from 'discord.js';
import markdownEscape from 'markdown-escape';
import { Region } from '../constants/regions';
import fetchBlitz from '../core/blitz/fetchBlitz';
import { AccountList } from '../core/blitz/searchPlayersAcrossRegions';
import { usernamePattern } from '../core/blitz/searchPlayersAcrossRegions/constants';
import fetchBlitz from '../../../website/src/core/blitz/fetchBlitz';
import { AccountList } from '../../../website/src/core/blitz/searchPlayersAcrossRegions';
import { usernamePattern } from '../../../website/src/core/blitz/searchPlayersAcrossRegions/constants';
import addRegionChoices from '../core/discord/addRegionChoices';
import addUsernameChoices from '../core/discord/addUsernameChoices';
import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
+7 -7
View File
@@ -1,14 +1,15 @@
import getStatsInPeriod from '@blitzkit/core/src/blitzstars/getStatsInPeriod';
import { StatFilters, filterStats } from '@blitzkit/core';
import { Locale } from 'discord.js';
import { getAccountInfo } from '../../../website/src/core/blitz/getAccountInfo';
import { getClanAccountInfo } from '../../../website/src/core/blitz/getClanAccountInfo';
import { emblemURL } from '../../../website/src/core/blitzkit/emblemURL';
import { filtersToDescription } from '../../../website/src/core/blitzkit/filtersToDescription';
import { getBlitzStarsLinkButton } from '../../../website/src/core/blitzstars/getBlitzStarsLinkButton';
import AllStatsOverview from '../components/AllStatsOverview';
import CommandWrapper from '../components/CommandWrapper';
import NoData from '../components/NoData';
import TitleBar from '../components/TitleBar';
import { getAccountInfo } from '../core/blitz/getAccountInfo';
import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo';
import { emblemURL } from '../core/blitzkit/emblemURL';
import { filtersToDescription } from '../core/blitzkit/filtersToDescription';
import { getBlitzStarsLinkButton } from '../core/blitzstars/getBlitzStarsLinkButton';
import getStatsInPeriod from '../core/blitzstars/getStatsInPeriod';
import addPeriodicFilterOptions from '../core/discord/addPeriodicFilterOptions';
import addUsernameChoices from '../core/discord/addUsernameChoices';
import autocompleteTanks from '../core/discord/autocompleteTanks';
@@ -27,7 +28,6 @@ import resolvePlayerFromButton from '../core/discord/resolvePlayerFromButton';
import resolvePlayerFromCommand, {
ResolvedPlayer,
} from '../core/discord/resolvePlayerFromCommand';
import { StatFilters, filterStats } from '../core/statistics/filterStats';
import { CommandRegistry } from '../events/interactionCreate';
async function render(
+1 -1
View File
@@ -1,5 +1,5 @@
import { gameDefinitions } from '@blitzkit/core/src/blitzkit/gameDefinitions';
import { getBlitzStarsLinkButton } from '../core/blitzstars/getBlitzStarsLinkButton';
import { getBlitzStarsLinkButton } from '../../../website/src/core/blitzstars/getBlitzStarsLinkButton';
import { addFilterOptions } from '../core/discord/addFilterOptions';
import addUsernameChoices from '../core/discord/addUsernameChoices';
import autocompleteTanks from '../core/discord/autocompleteTanks';
@@ -1,14 +1,14 @@
import { Percentile } from '@blitzkit/core/src/statistics/percentiles';
import { Locale } from 'discord.js';
import { SupplementaryStats } from '../../core/blitz/getAccountInfo';
import { translator } from '../../core/localization/translator';
import getWN8Percentile from '../../core/statistics/getWN8Percentile';
import { SupplementaryStats } from '../../../../website/src/core/blitz/getAccountInfo';
import { PERCENTILE_COLORS } from '../PercentileIndicator/constants';
import { HeroStat } from './components/HeroStat';
import {
WN8SurroundingStat,
WN8SurroundingStatAlign,
} from './components/WN8SurroundingStat';
import { translator } from '../../core/localization/translator';
import getWN8Percentile from '@blitzkit/core/src/statistics/getWN8Percentile';
export interface AllStatsOverviewProps {
stats: {
@@ -1,11 +1,11 @@
import { TankClass, TreeType } from '@blitzkit/core';
import { Percentile } from '@blitzkit/core/src/statistics/percentiles';
import { createColors } from 'bepaint';
import {
AccentColor,
GrayColor,
PALETTES,
} from '../../../constants/radixColors';
import { TankClass, TreeType } from '../../Tanks';
} from '../../../core/radix/radixColors';
import { TREE_TYPE_ICONS } from '../../Tanks/components/Item/constants';
import { RowStat } from './RowStat';
@@ -4,7 +4,7 @@ import {
AccentColor,
GrayColor,
PALETTES,
} from '../../../constants/radixColors';
} from '../../../core/radix/radixColors';
import PercentileIndicator from '../../PercentileIndicator';
interface RowStatProps {
@@ -1,9 +1,9 @@
import { BlitzStats } from '@blitzkit/core';
import isNumber from '@blitzkit/core/src/math/isNumber';
import getWN8Percentile from '@blitzkit/core/src/statistics/getWN8Percentile';
import { Locale } from 'discord.js';
import { SupplementaryStats } from '../../core/blitz/getAccountInfo';
import { SupplementaryStats } from '../../../../website/src/core/blitz/getAccountInfo';
import { translator } from '../../core/localization/translator';
import { BlitzStats } from '../../core/statistics/compositeStats/constants';
import getWN8Percentile from '../../core/statistics/getWN8Percentile';
import { Card } from './components/Card';
import { Root } from './components/Root';
import { Row } from './components/Row';
@@ -1,9 +1,10 @@
import { TankClass, TreeType } from '.';
import {
TANK_ICONS,
TANK_ICONS_COLLECTOR,
TANK_ICONS_PREMIUM,
} from '../../../../core/blitzkit/tankDefinitions/constants';
TankClass,
TreeType,
} from '@blitzkit/core';
export const TREE_TYPE_NAMES: Record<TreeType, string> = {
researchable: 'Tech tree',
@@ -1,8 +1,6 @@
import { TankClass, TreeType } from '@blitzkit/core';
import { theme } from '../../../../stitches.config';
import { TANK_CLASSES, TREE_TYPES, TREE_TYPE_ICONS } from './constants';
export type TankClass = (typeof TANK_CLASSES)[number];
export type TreeType = (typeof TREE_TYPES)[number];
import { TREE_TYPE_ICONS } from './constants';
export interface ItemProps {
tankType?: TankClass;
+1 -1
View File
@@ -1,4 +1,4 @@
import { Tier } from '../core/blitzkit/tankDefinitions';
import { Tier } from '@blitzkit/core';
import { theme } from '../stitches.config';
export type TierWeightsRecord = Partial<Record<Tier, number>>;
@@ -0,0 +1,13 @@
import { idToRegion, usersDatabase } from '@blitzkit/core';
import { flagUserActivity } from '../../../../website/src/core/blitzkit/flagUserActivity';
export async function getBlitzFromDiscord(discordId: bigint) {
const unique = await usersDatabase.discordUser.findUnique({
where: { discord_id: discordId },
});
if (unique === null) return null;
await flagUserActivity(unique.blitz_id);
return { id: unique.blitz_id, region: idToRegion(unique.blitz_id) };
}
@@ -0,0 +1,3 @@
export default function getPeriodNow() {
return Date.now() / 1000;
}
@@ -0,0 +1,9 @@
import { Region } from '@blitzkit/core';
import getTimeDaysAgo from '../../../../website/src/core/blitzkit/getTimeDaysAgo';
import { PeriodSize } from '../discord/addPeriodSubCommands';
export default function getPeriodStart(region: Region, period: PeriodSize) {
if (period === 'career') return 0;
if (period === 'today') return getTimeDaysAgo(region, 1);
return getTimeDaysAgo(region, parseInt(period));
}
@@ -0,0 +1,9 @@
import jsxToSvg from './jsxToSvg';
import svgToPng from './svgToPng';
export default async function jsxToPng(jsx: JSX.Element) {
const svg = await jsxToSvg(jsx);
const png = svgToPng(svg);
return png;
}
@@ -0,0 +1,12 @@
import { RenderConfiguration } from '../../../../website/src/core/blitzkit/renderConfiguration';
import jsxToSvg from './jsxToSvg';
import { svgToPngThreaded } from './svgToPngThreaded';
export default async function jsxToPngThreaded(
jsx: JSX.Element,
renderConfiguration: RenderConfiguration,
) {
const svg = await jsxToSvg(jsx, renderConfiguration);
const png = await svgToPngThreaded(svg);
return png;
}
Binary file not shown.
@@ -0,0 +1,23 @@
import { readFile } from 'fs/promises';
import satori from 'satori';
import { RenderConfiguration } from '../../../../../website/src/core/blitzkit/renderConfiguration';
import robotoBlack from './Roboto-Black.ttf';
import robotoBold from './Roboto-Bold.ttf';
import roboto from './Roboto.ttf';
const fontsPromise = [
{ file: roboto, weight: 400 as const },
{ file: robotoBold, weight: 700 as const },
{ file: robotoBlack, weight: 900 as const },
].map(async ({ file, weight }) => {
const data = await readFile(`dist/bot/workers/${file}`);
return { name: 'Roboto', weight, data };
});
export default async function jsxToSvg(
jsx: JSX.Element,
renderConfiguration = new RenderConfiguration(),
) {
const fonts = await Promise.all(fontsPromise);
return await satori(jsx, { width: renderConfiguration.width, fonts });
}
@@ -0,0 +1,5 @@
import { Resvg } from '@resvg/resvg-js';
export default function svgToPng(svg: string) {
return new Resvg(svg).render().asPng();
}
@@ -0,0 +1,46 @@
import { Worker } from 'worker_threads';
const RENDER_WORKERS = 1;
const workers = [...Array(RENDER_WORKERS)].map(() => {
const worker = new Worker('dist/bot/workers/render.js');
console.log(`🟡 Launching render worker ${worker.threadId}`);
return worker;
});
const queue: {
svg: string;
resolve: (png: Buffer) => void;
}[] = [];
function manageQueue() {
if (queue.length > 0 && workers.length > 0) {
const { svg, resolve } = queue.shift()!;
const worker = workers.shift()!;
function cleanup() {
workers.push(worker);
worker.off('message', handleMessage);
worker.off('error', handleError);
manageQueue();
}
function handleMessage(png: Uint8Array) {
cleanup();
resolve(Buffer.from(png));
}
function handleError(error: Error) {
cleanup();
throw error;
}
worker.postMessage(svg);
worker.on('message', handleMessage);
worker.on('error', handleError);
}
}
export function svgToPngThreaded(svg: string) {
return new Promise<Buffer>((resolve) => {
queue.push({ svg, resolve });
manageQueue();
});
}
@@ -1,11 +1,9 @@
import { BlitzStats, emptyAllStats, Region } from '@blitzkit/core';
import { Locale } from 'discord.js';
import { Region } from '../../../../../src/constants/regions';
import getTankHistories, {
TankHistories,
TankHistoryRaw,
} from '../../../../../src/core/blitzkit/getTankHistories';
import { BlitzStats } from '../../../../../src/core/statistics/compositeStats/constants';
import { emptyAllStats } from './constants';
} from './getTankHistories';
const emptyTankHistoryNode: TankHistoryRaw = {
account_id: 0,
@@ -106,5 +104,3 @@ export default async function getStatsInPeriod(
return { diff, order } satisfies DiffedTankStats;
}
export * from './constants';
@@ -0,0 +1,123 @@
import { Region, emptyAllStats } from '@blitzkit/core';
import { Locale } from 'discord.js';
import getTankStats from '../../../../website/src/core/blitz/getTankStats';
import { UserError } from '../../../../website/src/hooks/userError';
import {
GetHistoriesOptions,
Histories,
History,
getHistoriesDefaultOptions,
} from '../../types/histories';
import { translator } from '../localization/translator';
export interface TankHistory extends History {
tank_id: number;
}
export type TankHistories = TankHistory[];
export interface TankHistoryRaw extends TankHistory {
mark_of_mastery: number;
battle_life_time: number;
account_id: number;
}
export type TankHistoriesRaw = TankHistoryRaw[];
export interface GetTankHistoriesOptions extends GetHistoriesOptions {
tankId?: number;
}
const getTankHistoriesDefaultOptions: GetTankHistoriesOptions = {
...getHistoriesDefaultOptions,
tankId: undefined,
};
export default async function getTankHistories(
server: Region,
id: number,
locale: Locale,
options?: Partial<GetTankHistoriesOptions>,
) {
const { t } = translator(locale);
const mergedOptions = { ...getTankHistoriesDefaultOptions, ...options };
const tankHistoriesResponse = await fetch(
`https://www.blitzstars.com/api/tankhistories/for/${id}/`,
);
const tankHistories = (
(await tankHistoriesResponse.json()) as TankHistoriesRaw
).map(
(history) =>
({
all: history.all,
last_battle_time: history.last_battle_time,
tank_id: history.tank_id,
} satisfies TankHistory),
);
const compliantTanks: number[] = [];
const lastBattles: Record<number, number> = {};
const lastNonCompliantIndex: Record<number, number> = {};
const hasCompliedOnce: Record<number, boolean> = {};
const filtered = tankHistories.filter(
({ last_battle_time, tank_id, all: { battles } }, index) => {
const complies =
last_battle_time >= mergedOptions.start &&
last_battle_time <= mergedOptions.end &&
(mergedOptions.tankId === undefined ||
tank_id === mergedOptions.tankId);
if (complies && !hasCompliedOnce[tank_id]) {
hasCompliedOnce[tank_id] = true;
compliantTanks.push(tank_id);
}
if (!hasCompliedOnce[tank_id]) lastNonCompliantIndex[tank_id] = index;
lastBattles[tank_id] = battles;
return complies;
},
);
const latestNodes: TankHistories = [];
if (mergedOptions.includeLatestHistories) {
const tankStats = await getTankStats(server, id);
if (tankStats === null) {
throw new UserError(t`bot.common.errors.no_tank_stats`);
}
tankStats.forEach((tankHistory) => {
if (
tankHistory.all.battles > (lastBattles[tankHistory.tank_id] ?? 0) &&
(mergedOptions.tankId === undefined ||
tankHistory.tank_id === mergedOptions.tankId)
) {
if (!hasCompliedOnce[tankHistory.tank_id]) {
// was played today for the first time... I think???
hasCompliedOnce[tankHistory.tank_id] = true;
compliantTanks.push(tankHistory.tank_id);
}
latestNodes.push(tankHistory);
}
});
}
const previousNodes: TankHistories = [];
if (mergedOptions.includePreviousHistories) {
compliantTanks.forEach((tankId) => {
if (tankId in lastNonCompliantIndex) {
previousNodes.push(
tankHistories[lastNonCompliantIndex[tankId]] ??
({
all: emptyAllStats,
last_battle_time: -Infinity,
tank_id: tankId,
} satisfies TankHistory),
);
}
});
}
return [...previousNodes, ...filtered, ...latestNodes] satisfies Histories;
}
@@ -0,0 +1,18 @@
import { Locale, SlashCommandStringOption } from 'discord.js';
import { translator } from '../localization/translator';
import { localizationObject } from './localizationObject';
export default function addClanChoices(option: SlashCommandStringOption) {
const { translate } = translator(Locale.EnglishUS);
return option
.setName('clan')
.setNameLocalizations(localizationObject('bot.common.options.clan'))
.setDescription(translate('bot.common.options.clan.description'))
.setDescriptionLocalizations(
localizationObject('bot.common.options.clan.description'),
)
.setAutocomplete(true)
.setMinLength(2)
.setRequired(true);
}
@@ -0,0 +1,109 @@
import { TANK_CLASSES, TREE_TYPES } from '@blitzkit/core';
import {
APIApplicationCommandOptionChoice,
Locale,
SlashCommandBuilder,
SlashCommandSubcommandBuilder,
} from 'discord.js';
import {} from '../../components/Tanks/components/Item/constants';
import { translator } from '../localization/translator';
import addTankChoices from './addTankChoices';
import addTierChoices from './addTierChoices';
import { localizationObject } from './localizationObject';
export function addFilterOptions<
Option extends SlashCommandBuilder | SlashCommandSubcommandBuilder,
>(option: Option, nations: string[]) {
const { translate } = translator(Locale.EnglishUS);
return (
option
.addStringOption((option) =>
option
.setName(translate('bot.common.options.filters.nation'))
.setNameLocalizations(
localizationObject('bot.common.options.filters.nation'),
)
.setDescription(
translate('bot.common.options.filters.nation.description'),
)
.setDescriptionLocalizations(
localizationObject('bot.common.options.filters.nation.description'),
)
.addChoices(
...nations.map(
(nation) =>
({
value: nation,
name: translate(`common.nations.${nation}`),
name_localizations: localizationObject(
`common.nations.${nation}`,
),
} satisfies APIApplicationCommandOptionChoice),
),
)
.setRequired(false),
)
.addStringOption((option) => addTierChoices(option).setRequired(false))
.addStringOption((option) =>
option
.setName(translate('bot.common.options.filters.tank_class'))
.setNameLocalizations(
localizationObject('bot.common.options.filters.tank_class'),
)
.setDescription(
translate('bot.common.options.filters.tank_class.description'),
)
.setDescriptionLocalizations(
localizationObject(
'bot.common.options.filters.tank_class.description',
),
)
.addChoices(
...TANK_CLASSES.map(
(tankType) =>
({
value: tankType,
name: translate(`common.tank_class_short.${tankType}`),
name_localizations: localizationObject(
`common.tank_class_short.${tankType}`,
),
} satisfies APIApplicationCommandOptionChoice),
),
)
.setRequired(false),
)
.addStringOption((option) =>
option
.setName(translate('bot.common.options.filters.tree_type'))
.setNameLocalizations(
localizationObject('bot.common.options.filters.tree_type'),
)
.setDescription(
translate('bot.common.options.filters.tree_type.description'),
)
.setDescriptionLocalizations(
localizationObject(
'bot.common.options.filters.tree_type.description',
),
)
.addChoices(
...TREE_TYPES.map(
(treeType) =>
({
value: treeType,
name: translate(`common.tree_type.${treeType}`),
name_localizations: localizationObject(
`common.tree_type.${treeType}`,
),
} satisfies APIApplicationCommandOptionChoice),
),
)
.setRequired(false),
)
// TODO: somehow localize this?
.addStringOption((option) =>
addTankChoices(option).setRequired(false),
) as Option
);
}
@@ -0,0 +1,122 @@
import {
Locale,
SlashCommandBuilder,
SlashCommandSubcommandBuilder,
SlashCommandSubcommandGroupBuilder,
} from 'discord.js';
import { translator } from '../localization/translator';
import { localizationObject } from './localizationObject';
export type PeriodType = 'today' | '30' | '60' | '90' | 'career' | 'custom';
export type PeriodSize = 'today' | `${number}` | 'career';
export function addPeriodSubCommands(
option: SlashCommandBuilder | SlashCommandSubcommandGroupBuilder,
extra: (
option: SlashCommandSubcommandBuilder,
) => SlashCommandSubcommandBuilder = (option) => option,
) {
const { translate } = translator(Locale.EnglishUS);
const subcommandNameLocalizations = {
today: localizationObject('bot.common.subcommands.today'),
30: localizationObject('bot.common.subcommands.30'),
60: localizationObject('bot.common.subcommands.60'),
90: localizationObject('bot.common.subcommands.90'),
career: localizationObject('bot.common.subcommands.career'),
custom: localizationObject('bot.common.subcommands.custom'),
};
const subcommandDescriptionLocalizations = {
today: localizationObject('bot.common.subcommands.today.description'),
30: localizationObject('bot.common.subcommands.30.description'),
60: localizationObject('bot.common.subcommands.60.description'),
90: localizationObject('bot.common.subcommands.90.description'),
career: localizationObject('bot.common.subcommands.career.description'),
custom: localizationObject('bot.common.subcommands.custom.description'),
};
option
.addSubcommand((option) =>
extra(
option
.setName(translate('bot.common.subcommands.today'))
.setNameLocalizations(subcommandNameLocalizations.today)
.setDescription(translate('bot.common.subcommands.today.description'))
.setDescriptionLocalizations(
subcommandDescriptionLocalizations.today,
),
),
)
.addSubcommand((option) =>
extra(
option
.setName(translate('bot.common.subcommands.30'))
.setNameLocalizations(subcommandNameLocalizations[30])
.setDescription(translate('bot.common.subcommands.30.description'))
.setDescriptionLocalizations(subcommandDescriptionLocalizations[30]),
),
)
.addSubcommand((option) =>
extra(
option
.setName(translate('bot.common.subcommands.60'))
.setNameLocalizations(subcommandNameLocalizations[60])
.setDescription(translate('bot.common.subcommands.60.description'))
.setDescriptionLocalizations(subcommandDescriptionLocalizations[60]),
),
)
.addSubcommand((option) =>
extra(
option
.setName(translate('bot.common.subcommands.90'))
.setNameLocalizations(subcommandNameLocalizations[90])
.setDescription(translate('bot.common.subcommands.90.description'))
.setDescriptionLocalizations(subcommandDescriptionLocalizations[90]),
),
)
.addSubcommand((option) =>
extra(
option
.setName(translate('bot.common.subcommands.career'))
.setNameLocalizations(subcommandNameLocalizations.career)
.setDescription(
translate('bot.common.subcommands.career.description'),
)
.setDescriptionLocalizations(
subcommandDescriptionLocalizations.career,
),
),
)
.addSubcommand((option) =>
extra(
option
.setName(translate('bot.common.subcommands.custom'))
.setNameLocalizations(subcommandNameLocalizations.custom)
.setDescription(
translate('bot.common.subcommands.custom.description'),
)
.setDescriptionLocalizations(
subcommandDescriptionLocalizations.custom,
)
.addIntegerOption((option) =>
option
.setName(translate('bot.common.subcommands.custom.options.start'))
.setDescription(
translate(
'bot.common.subcommands.custom.options.start.description',
),
)
.setRequired(true),
)
.addIntegerOption((option) =>
option
.setName(translate('bot.common.subcommands.custom.options.end'))
.setDescription(
translate(
'bot.common.subcommands.custom.options.end.description',
),
)
.setRequired(true),
),
),
);
}
@@ -0,0 +1,25 @@
import { gameDefinitions } from '@blitzkit/core/src/blitzkit/gameDefinitions';
import {
SlashCommandBuilder,
SlashCommandSubcommandBuilder,
SlashCommandSubcommandGroupBuilder,
} from 'discord.js';
import { addFilterOptions } from './addFilterOptions';
import { addPeriodSubCommands } from './addPeriodSubCommands';
export default async function addPeriodicFilterOptions<
OptionType extends SlashCommandBuilder | SlashCommandSubcommandGroupBuilder,
>(
option: OptionType,
extra: (
option: SlashCommandSubcommandBuilder,
) => SlashCommandSubcommandBuilder = (option) => option,
): Promise<OptionType> {
const awaitedGameDefinitions = await gameDefinitions;
addPeriodSubCommands(option, (option) =>
addFilterOptions(extra(option), awaitedGameDefinitions.nations),
);
return option;
}
@@ -0,0 +1,33 @@
import { REGIONS } from '@blitzkit/core';
import {
APIApplicationCommandOptionChoice,
Locale,
SlashCommandStringOption,
} from 'discord.js';
import { translator } from '../localization/translator';
import { localizationObject } from './localizationObject';
export default function addRegionChoices(option: SlashCommandStringOption) {
const { t, translate } = translator(Locale.EnglishUS);
return option
.setName(t`bot.common.options.region`)
.setNameLocalizations(localizationObject('bot.common.options.region'))
.setDescription(t`bot.common.options.region.description`)
.setDescriptionLocalizations(
localizationObject('bot.common.options.region.description'),
)
.addChoices(
...REGIONS.map(
(region) =>
({
value: region,
name: translate(`common.regions.normal.${region}`),
name_localizations: localizationObject(
`common.regions.normal.${region}`,
),
} satisfies APIApplicationCommandOptionChoice),
),
)
.setRequired(true);
}
@@ -0,0 +1,17 @@
import { Locale, SlashCommandStringOption } from 'discord.js';
import { translator } from '../localization/translator';
import { localizationObject } from './localizationObject';
export default function addTankChoices(option: SlashCommandStringOption) {
const { translate } = translator(Locale.EnglishUS);
return option
.setName(translate('bot.common.options.tank'))
.setNameLocalizations(localizationObject('bot.common.options.tank'))
.setDescription(translate('bot.common.options.tank.description'))
.setDescriptionLocalizations(
localizationObject('bot.common.options.tank.description'),
)
.setAutocomplete(true)
.setRequired(true);
}
@@ -0,0 +1,38 @@
import numberToRomanNumeral from '@blitzkit/core/src/math/numberToRomanNumeral';
import {
APIApplicationCommandOptionChoice,
Locale,
SlashCommandStringOption,
} from 'discord.js';
import { range } from 'lodash';
import { translator } from '../localization/translator';
import { localizationObject } from './localizationObject';
export default function addTierChoices(option: SlashCommandStringOption) {
const { t, translate } = translator(Locale.EnglishUS);
return option
.setName(t`bot.common.options.tier`)
.setNameLocalizations(localizationObject('bot.common.options.tier'))
.setDescription(t`bot.common.options.tier.description`)
.setDescriptionLocalizations(
localizationObject('bot.common.options.tier.description'),
)
.setChoices(
...range(10, 0).map(
(tier) =>
({
name: translate('bot.common.options.tier.choices.tier', [
`${tier}`,
numberToRomanNumeral(tier),
]),
value: `${tier}`,
name_localizations: localizationObject(
'bot.common.options.tier.choices.tier',
[`${tier}`, numberToRomanNumeral(tier)],
),
} satisfies APIApplicationCommandOptionChoice),
),
)
.setRequired(true);
}
@@ -0,0 +1,16 @@
import { Locale, SlashCommandStringOption } from 'discord.js';
import { translator } from '../localization/translator';
import { localizationObject } from './localizationObject';
export default function addUsernameChoices(option: SlashCommandStringOption) {
const { t } = translator(Locale.EnglishUS);
return option
.setName(t`bot.common.options.username`)
.setNameLocalizations(localizationObject('bot.common.options.username'))
.setDescription(t`bot.common.options.username.description`)
.setDescriptionLocalizations(
localizationObject('bot.common.options.username.description'),
)
.setAutocomplete(true);
}
@@ -0,0 +1,2 @@
export const DISCORD_CHOICES_MAX_NAME_SIZE = 25;
export const OVERFLOW_SUFFIX = '…';
@@ -0,0 +1,44 @@
import {
ApplicationCommandOptionChoiceData,
AutocompleteInteraction,
CacheType,
} from 'discord.js';
import searchClansAcrossRegions from '../../../../../website/src/core/blitz/searchClansAcrossRegions';
import { translator } from '../../localization/translator';
import { DISCORD_CHOICES_MAX_NAME_SIZE, OVERFLOW_SUFFIX } from './constants';
export default async function autocompleteClan(
interaction: AutocompleteInteraction<CacheType>,
) {
const { translate } = translator(interaction.locale);
const focusedOption = interaction.options.getFocused(true);
if (focusedOption.name !== 'clan') return;
if (focusedOption.value.length < 2) return interaction.respond([]);
const clans = await searchClansAcrossRegions(focusedOption.value);
await interaction.respond(
clans
? clans.map((clan) => {
let name = `${clan.name} (${translate(
`common.regions.short.${clan.region}`,
)})`;
if (name.length > DISCORD_CHOICES_MAX_NAME_SIZE) {
const overSize = name.length - DISCORD_CHOICES_MAX_NAME_SIZE;
name = `${clan.name.slice(
0,
clan.name.length - overSize - OVERFLOW_SUFFIX.length,
)}${OVERFLOW_SUFFIX} (${translate(
`common.regions.short.${clan.region}`,
)})`;
}
return {
name,
value: `${clan.region}/${clan.clan_id}`,
} satisfies ApplicationCommandOptionChoiceData<string>;
})
: [],
);
}
@@ -0,0 +1,57 @@
import {
ApplicationCommandOptionChoiceData,
AutocompleteInteraction,
CacheType,
} from 'discord.js';
import { go } from 'fuzzysort';
import {
DISCORD_CHOICES_MAX_NAME_SIZE,
OVERFLOW_SUFFIX,
} from './autocompleteClan/constants';
import { tankNames } from '@blitzkit/core';
export const tankNamesTechTreeOnly = tankNames.then((names) =>
names.filter((tank) => tank.treeType === 'researchable'),
);
export default async function autocompleteTanks(
interaction: AutocompleteInteraction<CacheType>,
techTreeOnly = false,
fields = ['tank'],
) {
const focusedOption = interaction.options.getFocused(true);
if (!fields.includes(focusedOption.name)) return;
await interaction.respond(
focusedOption.value
? await Promise.all(
go(
focusedOption.value,
await (techTreeOnly ? tankNamesTechTreeOnly : tankNames),
{
keys: ['searchableName', 'searchableNameDeburr', 'camouflages'],
limit: 10,
},
).map(async (item) => {
let name = item.obj.searchableName;
if (name.length > DISCORD_CHOICES_MAX_NAME_SIZE) {
const overSize = name.length - DISCORD_CHOICES_MAX_NAME_SIZE;
name = `${item.obj.searchableName.slice(
0,
item.obj.searchableName.length -
overSize -
OVERFLOW_SUFFIX.length,
)}${OVERFLOW_SUFFIX}`;
}
return {
name,
value: `${item.obj.id}`,
} satisfies ApplicationCommandOptionChoiceData<string>;
}),
)
: [],
);
}
@@ -0,0 +1,48 @@
import {
ApplicationCommandOptionChoiceData,
AutocompleteInteraction,
CacheType,
} from 'discord.js';
import searchPlayersAcrossRegions from '../../../../website/src/core/blitz/searchPlayersAcrossRegions';
import { translator } from '../localization/translator';
import {
DISCORD_CHOICES_MAX_NAME_SIZE,
OVERFLOW_SUFFIX,
} from './autocompleteClan/constants';
export default async function autocompleteUsername(
interaction: AutocompleteInteraction<CacheType>,
) {
const { translate } = translator(interaction.locale);
const focusedOption = interaction.options.getFocused(true);
if (focusedOption.name !== 'username') return;
const players = await searchPlayersAcrossRegions(focusedOption.value);
try {
await interaction.respond(
players
? players.map((player) => {
let name = `${player.nickname} (${translate(
`common.regions.short.${player.region}`,
)})`;
if (name.length > DISCORD_CHOICES_MAX_NAME_SIZE) {
const overSize = name.length - DISCORD_CHOICES_MAX_NAME_SIZE;
name = `${player.nickname.slice(
0,
player.nickname.length - overSize - OVERFLOW_SUFFIX.length,
)}${OVERFLOW_SUFFIX} (${translate(
`common.regions.short.${player.region}`,
)})`;
}
return {
name,
value: `${player.region}/${player.account_id}`,
} satisfies ApplicationCommandOptionChoiceData<string>;
})
: [],
);
} catch (error) {}
}
@@ -0,0 +1,20 @@
import {
ButtonBuilder,
ButtonStyle,
ComponentEmojiResolvable,
} from 'discord.js';
export default function buttonLink(
url: string,
label: string,
emoji?: ComponentEmojiResolvable,
) {
const button = new ButtonBuilder()
.setStyle(ButtonStyle.Link)
.setURL(url)
.setLabel(label);
if (emoji) button.setEmoji(emoji);
return button;
}
@@ -0,0 +1,20 @@
import {
ButtonBuilder,
ButtonStyle,
ComponentEmojiResolvable,
} from 'discord.js';
export default function buttonPrimary(
id: string,
label: string,
emoji?: ComponentEmojiResolvable,
) {
const button = new ButtonBuilder()
.setCustomId(id)
.setStyle(ButtonStyle.Primary)
.setLabel(label);
if (emoji) button.setEmoji(emoji);
return button;
}
@@ -0,0 +1,19 @@
import { ChatInputCommandInteraction } from 'discord.js';
import { translator } from '../localization/translator';
import buttonPrimary from './buttonPrimary';
export function buttonRefresh(
interaction: ChatInputCommandInteraction,
path: string,
) {
const { t } = translator(interaction.locale);
if (
interaction.appPermissions?.has('ReadMessageHistory') &&
interaction.appPermissions?.has('ViewChannel')
) {
return buttonPrimary(path, t`bot.common.buttons.refresh`);
}
return null;
}
+9
View File
@@ -0,0 +1,9 @@
import { Client, GatewayIntentBits } from 'discord.js';
import interactionCreate from '../../events/interactionCreate';
import ready from '../../events/ready';
export const client = new Client({
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers],
})
.on('ready', ready)
.on('interactionCreate', interactionCreate);
@@ -0,0 +1,27 @@
import { CacheType, ChatInputCommandInteraction } from 'discord.js';
export interface Parameters {
[key: string]: string | number | boolean | undefined | null;
}
// TODO: automatically infer command options???
// but how would that work with the region and id options?
export default function commandToURL(
interaction: ChatInputCommandInteraction<CacheType>,
parameters?: Parameters,
) {
const pathname = [
interaction.commandName,
interaction.options.getSubcommandGroup(false),
interaction.options.getSubcommand(false),
]
.filter(Boolean)
.join('/');
const params = (parameters ? Object.entries(parameters) : [])
.filter(([, value]) => value !== undefined && value !== null)
.map(([key, value]) => `${key}=${value}`)
.join('&');
return `${pathname}${params.length > 0 ? `?${params}` : ''}`;
}
@@ -0,0 +1,85 @@
import {
Locale,
SlashCommandBuilder,
SlashCommandSubcommandBuilder,
SlashCommandSubcommandGroupBuilder,
} from 'discord.js';
import { translator } from '../localization/translator';
import { localizationObject } from './localizationObject';
interface CreateLocalizedCommandSubcommand {
subcommand: string;
modify?: (option: SlashCommandSubcommandBuilder) => void;
}
interface CreateLocalizedCommandSubcommandGroup {
group: string;
modify?: (option: SlashCommandSubcommandGroupBuilder) => void;
}
export function createLocalizedCommand(
command: string,
sub?:
| CreateLocalizedCommandSubcommand[]
| CreateLocalizedCommandSubcommandGroup[],
) {
const commandPathItem = command.replaceAll('-', '_');
const { translate } = translator(Locale.EnglishUS);
const slashCommand = new SlashCommandBuilder()
.setName(command)
.setNameLocalizations(localizationObject(`bot.commands.${commandPathItem}`))
.setDescription(translate(`bot.commands.${commandPathItem}.description`))
.setDescriptionLocalizations(
localizationObject(`bot.commands.${commandPathItem}.description`),
);
if (sub) {
sub.map((subItem) => {
if ('subcommand' in subItem) {
const subcommandNameLocalizations = localizationObject(
`bot.commands.${commandPathItem}.subcommands.${subItem.subcommand}`,
);
const subcommandDescriptionLocalizations = localizationObject(
`bot.commands.${commandPathItem}.subcommands.${subItem.subcommand}.description`,
);
slashCommand.addSubcommand((option) => {
if (subItem.modify) subItem.modify(option);
return option
.setName(subItem.subcommand)
.setNameLocalizations(subcommandNameLocalizations)
.setDescription(
translate(
`bot.commands.${commandPathItem}.subcommands.${subItem.subcommand}.description`,
),
)
.setDescriptionLocalizations(subcommandDescriptionLocalizations);
});
} else {
const subcommandNameLocalizations = localizationObject(
`bot.commands.${commandPathItem}.groups.${subItem.group}`,
);
const subcommandDescriptionLocalizations = localizationObject(
`bot.commands.${commandPathItem}.groups.${subItem.group}.description`,
);
slashCommand.addSubcommandGroup((option) => {
if (subItem.modify) subItem.modify(option);
return option
.setName(subItem.group)
.setNameLocalizations(subcommandNameLocalizations)
.setDescription(
translate(
`bot.commands.${commandPathItem}.groups.${subItem.group}.description`,
),
)
.setDescriptionLocalizations(subcommandDescriptionLocalizations);
});
}
});
}
return slashCommand;
}
@@ -0,0 +1,12 @@
import { EmbedBuilder } from 'discord.js';
import { ACCENT_COLOR } from './colors';
export default function embedInfo(title: string, description: string) {
const embed = new EmbedBuilder()
.setColor(ACCENT_COLOR)
.setDescription(description);
if (title) embed.setTitle(title);
return embed;
}
@@ -0,0 +1,9 @@
import { EmbedBuilder } from 'discord.js';
import { NEGATIVE_COLOR } from './colors';
export default function embedNegative(title: string, description: string) {
return new EmbedBuilder()
.setColor(NEGATIVE_COLOR)
.setTitle(title)
.setDescription(description);
}
@@ -0,0 +1,9 @@
import { EmbedBuilder } from 'discord.js';
import { POSITIVE_COLOR } from './colors';
export default function embedPositive(title: string, description: string) {
return new EmbedBuilder()
.setColor(POSITIVE_COLOR)
.setTitle(title)
.setDescription(description);
}
@@ -0,0 +1,9 @@
import { EmbedBuilder } from 'discord.js';
import { WARNING_COLOR } from './colors';
export default function embedWarning(title: string, description: string) {
return new EmbedBuilder()
.setColor(WARNING_COLOR)
.setTitle(title)
.setDescription(description);
}
@@ -0,0 +1,19 @@
import { ChatInputCommandInteraction } from 'discord.js';
import { PeriodType } from './addPeriodSubCommands';
export function getCustomPeriodParams(
interaction: ChatInputCommandInteraction,
forcedNonCustomPeriod = false,
) {
if (
!forcedNonCustomPeriod &&
(interaction.options.getSubcommand() as PeriodType) === 'custom'
) {
return {
start: interaction.options.getInteger('start', true),
end: interaction.options.getInteger('end', true),
};
}
return undefined;
}
@@ -0,0 +1,15 @@
import { StatFilters, TreeType } from '@blitzkit/core';
import { ButtonInteraction } from 'discord.js';
export function getFiltersFromButton(interaction: ButtonInteraction) {
const url = new URL(`https://example.com/${interaction.customId}`);
return {
nation: url.searchParams.get('nation') ?? undefined,
tank: parseInt(url.searchParams.get('tank') ?? '0') || undefined,
tankType: url.searchParams.get('tank-class') ?? undefined,
treeType:
(url.searchParams.get('tree-type') as TreeType | undefined) ?? undefined,
tier: parseInt(url.searchParams.get('tier') ?? '0') || undefined,
} satisfies StatFilters;
}
@@ -0,0 +1,22 @@
import { StatFilters, TreeType } from '@blitzkit/core';
import { ChatInputCommandInteraction } from 'discord.js';
import resolveTankId from '../../../../website/src/core/blitz/resolveTankId';
export async function getFiltersFromCommand(
interaction: ChatInputCommandInteraction,
) {
const tankRaw = interaction.options.getString('tank');
return {
nation: interaction.options.getString('nation') ?? undefined,
tier: parseInt(interaction.options.getString('tier') ?? '0') || undefined,
tankType: interaction.options.getString('tank-class') ?? undefined,
treeType:
(interaction.options.getString('tree-type') as TreeType | undefined) ??
undefined,
tank:
tankRaw === null
? undefined
: await resolveTankId(tankRaw, interaction.locale),
} satisfies StatFilters;
}
@@ -0,0 +1,10 @@
import { Locale } from 'discord.js';
import { translator } from '../localization/translator';
import { PeriodSize } from './addPeriodSubCommands';
export function getPeriodOptionName(period: PeriodSize, locale: Locale) {
const { translate } = translator(locale);
if (period === 'career') return translate('bot.common.periods.career');
if (period === 'today') return translate('bot.common.periods.today');
return translate('bot.common.periods.days', [period]);
}
@@ -0,0 +1,16 @@
import { Locale } from 'discord.js';
import { SUPPORTED_LOCALES } from '../localization/strings/constants';
import { translator } from '../localization/translator';
export function localizationObject(path: string, literals?: string[]) {
return SUPPORTED_LOCALES.reduce<Partial<Record<Locale, string>>>(
(localizations, locale) => {
const { translate } = translator(locale);
return {
...localizations,
[locale]: translate(path, literals),
};
},
{},
);
}
+6
View File
@@ -0,0 +1,6 @@
import { assertSecret } from '@blitzkit/core';
import { ShardingManager } from 'discord.js';
export const manager = new ShardingManager('dist/bot/workers/bot.js', {
token: assertSecret(process.env.DISCORD_TOKEN),
});
@@ -0,0 +1,9 @@
export type TableInputEntry = [string, string | number] | [];
export type TableInput = TableInputEntry[];
export default function markdownTable(input: TableInput) {
return input
.filter((array) => array[1] !== undefined)
.map((array) => (array.length === 0 ? '' : `**${array[0]}**: ${array[1]}`))
.join('\n');
}
@@ -0,0 +1,64 @@
import {
ActionRowBuilder,
AttachmentBuilder,
ButtonBuilder,
EmbedBuilder,
InteractionEditReplyOptions,
InteractionReplyOptions,
MessageEditOptions,
} from 'discord.js';
import { RenderConfiguration } from '../../../../website/src/core/blitzkit/renderConfiguration';
import { InteractionReturnable } from '../../events/interactionCreate';
import { Writeable } from '../../types/writable';
import jsxToPngThreaded from '../blitzkit/jsxToPngThreaded';
export default async function normalizeInteractionReturnable(
returnable: InteractionReturnable,
) {
const images: [number, Buffer][] = [];
const reply: InteractionEditReplyOptions &
InteractionReplyOptions &
MessageEditOptions = {};
const awaitedReturnable = await returnable;
const normalizedReturnable = Array.isArray(awaitedReturnable)
? awaitedReturnable
: [awaitedReturnable];
let renderConfiguration = new RenderConfiguration();
await Promise.all(
normalizedReturnable.map(async (item, index) => {
if (typeof item === 'string') {
reply.content = item;
} else if (item instanceof EmbedBuilder) {
if (!reply.embeds) reply.embeds = [];
(reply.embeds as Writeable<typeof reply.embeds>).push(item);
} else if (item instanceof ButtonBuilder) {
if (!reply.components)
reply.components = [new ActionRowBuilder<ButtonBuilder>()];
(reply.components[0] as ActionRowBuilder<ButtonBuilder>).addComponents(
item,
);
} else if (item instanceof AttachmentBuilder) {
if (!reply.files) reply.files = [];
(reply.files as Writeable<typeof reply.files>).push(item);
} else if (item instanceof RenderConfiguration) {
renderConfiguration = item;
} else if (item === null) {
return;
} else {
const image = await jsxToPngThreaded(item, renderConfiguration);
images.push([index, image]);
}
}),
);
if (images.length > 0) {
images.sort(([a], [b]) => b - a);
if (!reply.files) reply.files = [];
images.forEach(([, image]) =>
(reply.files as Writeable<typeof reply.files>)?.push(image),
);
}
return reply;
}
+36
View File
@@ -0,0 +1,36 @@
export type PSAData = {
secret?: boolean;
links?: {
title: string;
url: string;
}[];
commands?: string[];
} & (
| {
type?: 'embed';
title: string;
description: string;
image?: string;
}
| {
type: 'image';
image: string;
title?: string;
}
);
export interface PSA {
data: PSAData | null;
}
export const psa: PSA = {
data: null,
};
fetch(
'https://gist.githubusercontent.com/tresabhi/ed4b136f08856e615212e573aabe1968/raw',
)
.then((response) => response.json())
.then((data) => {
psa.data = data as PSAData;
});
@@ -0,0 +1,29 @@
import { Region } from '@blitzkit/core';
import { ChatInputCommandInteraction } from 'discord.js';
import markdownEscape from 'markdown-escape';
import searchClansAcrossRegions from '../../../../website/src/core/blitz/searchClansAcrossRegions';
import { UserError } from '../../../../website/src/hooks/userError';
import { translator } from '../localization/translator';
import { serverAndIdPattern } from './resolvePlayerFromCommand/constants';
export default async function resolveClanFromCommand(
interaction: ChatInputCommandInteraction,
) {
const { translate } = translator(interaction.locale);
const clan = interaction.options.getString('clan', true);
if (serverAndIdPattern.test(clan)) {
const [server, accountId] = clan.split('/');
return { region: server as Region, id: Number(accountId) };
} else {
const accounts = await searchClansAcrossRegions(clan);
if (accounts[0]) {
return { region: accounts[0].region, id: accounts[0].clan_id };
} else {
throw new UserError(
translate('bot.common.errors.clan_not_found', [markdownEscape(clan)]),
);
}
}
}
@@ -0,0 +1,41 @@
import { Region } from '@blitzkit/core';
import { ButtonInteraction } from 'discord.js';
import getTimeDaysAgo from '../../../../website/src/core/blitzkit/getTimeDaysAgo';
import getPeriodNow from '../blitzkit/getPeriodNow';
import getPeriodStart from '../blitzkit/getPeriodStart';
import { PeriodType } from './addPeriodSubCommands';
import { getPeriodOptionName } from './getPeriodOptionName';
import { ResolvedPeriod } from './resolvePeriodFromCommand';
export default function resolvePeriodFromButton(
region: Region,
interaction: ButtonInteraction,
) {
let name: string;
let start: number;
let end: number;
const url = new URL(`https://example.com/${interaction.customId}`);
const path = url.pathname.split('/');
const periodSubcommand = path.at(-1) as PeriodType;
if (periodSubcommand === 'custom') {
const startRaw = parseInt(url!.searchParams.get('start')!);
const endRaw = parseInt(url!.searchParams.get('end')!);
const startDaysAgoMin = Math.min(startRaw, endRaw);
const endDaysAgoMax = Math.max(startRaw, endRaw);
name = `${startDaysAgoMin} - ${endDaysAgoMax} days`;
start = getTimeDaysAgo(region, endDaysAgoMax);
end = getTimeDaysAgo(region, startDaysAgoMin);
} else {
name = getPeriodOptionName(periodSubcommand, interaction.locale);
start = getPeriodStart(region, periodSubcommand);
end = getPeriodNow();
}
return {
name,
start,
end,
} satisfies ResolvedPeriod;
}
@@ -0,0 +1,52 @@
import { Region } from '@blitzkit/core';
import { CacheType, ChatInputCommandInteraction } from 'discord.js';
import getTimeDaysAgo from '../../../../website/src/core/blitzkit/getTimeDaysAgo';
import getPeriodNow from '../blitzkit/getPeriodNow';
import getPeriodStart from '../blitzkit/getPeriodStart';
import { translator } from '../localization/translator';
import { PeriodType } from './addPeriodSubCommands';
import { getPeriodOptionName } from './getPeriodOptionName';
export interface ResolvedPeriod {
name: string;
start: number;
end: number;
}
export default function resolvePeriodFromCommand(
region: Region,
interaction: ChatInputCommandInteraction<CacheType>,
forcedPeriod?: PeriodType,
) {
const { translate } = translator(interaction.locale);
let name: string;
let start: number;
let end: number;
const periodSubcommand = forcedPeriod
? forcedPeriod
: (interaction.options.getSubcommand() as PeriodType);
if (periodSubcommand === 'custom') {
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);
name = translate('bot.common.periods.custom', [
`${startDaysAgoMin}`,
`${endDaysAgoMax}`,
]);
start = getTimeDaysAgo(region, endDaysAgoMax);
end = getTimeDaysAgo(region, startDaysAgoMin);
} else {
name = getPeriodOptionName(periodSubcommand, interaction.locale);
start = getPeriodStart(region, periodSubcommand);
end = getPeriodNow();
}
return {
name,
start,
end,
} satisfies ResolvedPeriod;
}
@@ -0,0 +1,14 @@
import { Region } from '@blitzkit/core';
import { ButtonInteraction } from 'discord.js';
import { ResolvedPlayer } from './resolvePlayerFromCommand';
export default async function resolvePlayerFromButton(
interaction: ButtonInteraction,
) {
const url = new URL(`https://exmaple.com/${interaction.customId}`);
return {
id: parseInt(url.searchParams.get('id')!),
region: url.searchParams.get('region') as Region,
} satisfies ResolvedPlayer;
}
@@ -0,0 +1 @@
export const serverAndIdPattern = /(com|eu|asia)\/[0-9]+/;
@@ -0,0 +1,52 @@
import { Region } from '@blitzkit/core';
import { CacheType, ChatInputCommandInteraction } from 'discord.js';
import markdownEscape from 'markdown-escape';
import searchPlayersAcrossRegions from '../../../../../website/src/core/blitz/searchPlayersAcrossRegions';
import { UserError } from '../../../../../website/src/hooks/userError';
import { getBlitzFromDiscord } from '../../blitzkit/getBlitzFromDiscord';
import { translator } from '../../localization/translator';
import { serverAndIdPattern } from './constants';
export interface ResolvedPlayer {
region: Region;
id: number;
}
export default async function resolvePlayerFromCommand(
interaction: ChatInputCommandInteraction<CacheType>,
) {
const { t, translate } = translator(interaction.locale);
const commandUsername = interaction.options.getString('username');
if (commandUsername) {
if (serverAndIdPattern.test(commandUsername)) {
const [server, accountId] = commandUsername.split('/');
return {
region: server as Region,
id: Number(accountId),
} satisfies ResolvedPlayer;
} else {
const accounts = await searchPlayersAcrossRegions(commandUsername);
if (accounts[0]) {
return {
region: accounts[0].region,
id: accounts[0].account_id,
} satisfies ResolvedPlayer;
} else {
throw new UserError(
translate('bot.common.errors.player_not_found', [
markdownEscape(commandUsername),
]),
);
}
}
} else {
const account = await getBlitzFromDiscord(BigInt(interaction.user.id));
if (account) return account;
throw new UserError(t`bot.common.errors.player_not_linked`);
}
}
@@ -0,0 +1,3 @@
import { Locale } from 'discord.js';
export const SUPPORTED_LOCALES: Locale[] = [Locale.EnglishUS, Locale.SpanishES];
@@ -0,0 +1,19 @@
import { Locale } from 'discord.js';
import { SUPPORTED_LOCALES } from './constants';
export type TranslationFragmentTree = {
[key: string]: TranslationFragment;
} & {
$?: string;
};
export type TranslationFragment = string | TranslationFragmentTree;
export const translations = SUPPORTED_LOCALES.reduce<
Partial<Record<Locale, TranslationFragment>>
>(
(table, locale) => ({
...table,
[locale]: require(`../lang/${locale}.json`),
}),
{},
);
@@ -0,0 +1,74 @@
import { Locale } from 'discord.js';
import { translations } from './strings';
import { SUPPORTED_LOCALES } from './strings/constants';
export function translator(localeRaw: Locale) {
const locale =
localeRaw !== Locale.EnglishUS && SUPPORTED_LOCALES.includes(localeRaw)
? localeRaw
: Locale.EnglishUS;
const strings = translations[locale]!;
function translate(path: string, literals?: string[]): string {
const pathArray = path.split('.');
let fragment = strings;
let resolvedString: string;
for (const pathItem of pathArray) {
if (typeof fragment === 'string') {
throw new SyntaxError(
`Attempted to access string with key "${pathItem}" in "${path}" from within a string instead of an object in locale "${locale}"`,
);
} else {
fragment = fragment[pathItem];
if (typeof fragment === 'undefined') {
const message = `Undefined translation at "${pathItem}" in "${path}" for locale "${locale}"`;
if (locale === Locale.EnglishUS) {
throw new Error(message);
} else {
console.warn(`${message} falling back to en-US`);
return translator(Locale.EnglishUS).translate(path, literals);
}
}
}
}
if (typeof fragment === 'string') {
resolvedString = fragment;
} else if (fragment.$) {
resolvedString = fragment.$!;
} else {
throw new Error(
`Unresolved tree ending for "${path}" in locale "${locale}"`,
);
}
if (literals) {
const chunks = resolvedString.split('%s');
let embeddedString = '';
chunks.forEach((chunk, index) => {
if (index !== 0) embeddedString += literals[index - 1];
embeddedString += chunk;
});
return embeddedString;
}
return resolvedString;
}
function t(paths: TemplateStringsArray, ...embeds: string[]) {
return paths
.map((path, index) =>
path.length === 0
? ''
: `${translate(path)}${index === embeds.length ? '' : embeds[index]}`,
)
.join('');
}
return { locale, translate, t };
}
@@ -8,12 +8,12 @@ import {
InteractionReplyOptions,
} from 'discord.js';
import { InteractionRawReturnable, commands } from '..';
import { UserError } from '../../../../../website/src/hooks/userError';
import buttonLink from '../../../core/discord/buttonLink';
import embedWarning from '../../../core/discord/embedWarning';
import normalizeInteractionReturnable from '../../../core/discord/normalizeInteractionReturnable';
import { psa } from '../../../core/discord/psa';
import { translator } from '../../../core/localization/translator';
import { UserError } from '../../../hooks/userError';
import { Writeable } from '../../../types/writable';
export default async function handleChatInputCommand(
@@ -1,3 +1,4 @@
import { assertSecret } from '@blitzkit/core';
import {
AttachmentBuilder,
AutocompleteInteraction,
@@ -13,7 +14,7 @@ import {
SlashCommandOptionsOnlyBuilder,
SlashCommandSubcommandsOnlyBuilder,
} from 'discord.js';
import { assertSecret } from '../../../packages/core/src/blitzkit/assertSecret';
import { RenderConfiguration } from '../../../../website/src/core/blitzkit/renderConfiguration';
import { aboutCommand } from '../../commands/about';
import { breakdownCommand } from '../../commands/breakdown';
import { debugCommand } from '../../commands/debug';
@@ -33,7 +34,6 @@ import { searchClansCommand } from '../../commands/searchClans';
import { searchPlayersCommand } from '../../commands/searchPlayers';
import { statsCommand } from '../../commands/stats';
import { todayCommand } from '../../commands/today';
import { RenderConfiguration } from '../../core/blitzkit/renderConfiguration';
import handleAutocomplete from './handlers/autocomplete';
import handleButton from './handlers/button';
import handleChatInputCommand from './handlers/chatInputCommand';
+1 -1
View File
@@ -1,6 +1,6 @@
// a cleaner simplification of Blitz Stars' histories because it's a mess there
import { BlitzStats } from '../core/statistics/compositeStats/constants';
import { BlitzStats } from '@blitzkit/core';
export interface History {
all: BlitzStats;
+2 -2
View File
@@ -1,5 +1,5 @@
import { assertSecret } from '../../packages/core/src/blitzkit/assertSecret';
import { registerProcesses } from '../core/blitzkit/processes';
import { assertSecret } from '@blitzkit/core';
import { registerProcesses } from '../../../website/src/core/blitzkit/processes';
import { client } from '../core/discord/client';
console.log(`🟡 Launching bot ${client.shard?.ids[0]}`);