mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-23 11:39:50 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8a5f57787 | ||
|
|
13651650ab | ||
|
|
9644692084 | ||
|
|
b52c3c6c68 | ||
|
|
c1bd91a7f9 | ||
|
|
1d65ff3e0c | ||
|
|
f7fac5ad26 | ||
|
|
e45f055493 | ||
|
|
8946f115c0 | ||
|
|
6094875ffb | ||
|
|
0644c9a67f | ||
|
|
c91ab29a23 | ||
|
|
8f4e33fd5a | ||
|
|
8cf5012602 | ||
|
|
23a23e36bb | ||
|
|
0480445bae | ||
|
|
ae07b8de4c | ||
|
|
4c746cbb86 | ||
|
|
890a4d142b | ||
|
|
ff9bda29cf | ||
|
|
5ca9e29f3d | ||
|
|
c804a3d564 |
No files matched your search
@@ -35,4 +35,4 @@ jobs:
|
|||||||
run: yarn
|
run: yarn
|
||||||
|
|
||||||
- name: Generate leaderboard
|
- name: Generate leaderboard
|
||||||
run: GH_TOKEN=${{ secrets.GH_TOKEN }} yarn publish:ratingsleaderboard --region=asia --target=${{ github.event.schedule == '0 0 * * *' && 'latest' || 'midnight' }}
|
run: GH_TOKEN=${{ secrets.GH_TOKEN }} DISCORD_TOKEN=${{ secrets.DISCORD_TOKEN }} yarn publish:ratingsleaderboard --region=asia --target=${{ github.event.schedule == '0 0 * * *' && 'latest' || 'midnight' }}
|
||||||
@@ -35,4 +35,4 @@ jobs:
|
|||||||
run: yarn
|
run: yarn
|
||||||
|
|
||||||
- name: Generate leaderboard
|
- name: Generate leaderboard
|
||||||
run: GH_TOKEN=${{ secrets.GH_TOKEN }} yarn publish:ratingsleaderboard --region=eu --target=${{ github.event.schedule == '0 0 * * *' && 'latest' || 'midnight' }}
|
run: GH_TOKEN=${{ secrets.GH_TOKEN }} DISCORD_TOKEN=${{ secrets.DISCORD_TOKEN }} yarn publish:ratingsleaderboard --region=eu --target=${{ github.event.schedule == '0 0 * * *' && 'latest' || 'midnight' }}
|
||||||
@@ -35,4 +35,4 @@ jobs:
|
|||||||
run: yarn
|
run: yarn
|
||||||
|
|
||||||
- name: Generate leaderboard
|
- name: Generate leaderboard
|
||||||
run: GH_TOKEN=${{ secrets.GH_TOKEN }} yarn publish:ratingsleaderboard --region=na --target=${{ github.event.schedule == '0 0 * * *' && 'latest' || 'midnight' }}
|
run: GH_TOKEN=${{ secrets.GH_TOKEN }} DISCORD_TOKEN=${{ secrets.DISCORD_TOKEN }} yarn publish:ratingsleaderboard --region=na --target=${{ github.event.schedule == '0 0 * * *' && 'latest' || 'midnight' }}
|
||||||
Vendored
+2
@@ -24,6 +24,7 @@
|
|||||||
"fullstats",
|
"fullstats",
|
||||||
"fuzzysort",
|
"fuzzysort",
|
||||||
"gamemode",
|
"gamemode",
|
||||||
|
"haha",
|
||||||
"immer",
|
"immer",
|
||||||
"JOINTINDEX",
|
"JOINTINDEX",
|
||||||
"JOINTWEIGHT",
|
"JOINTWEIGHT",
|
||||||
@@ -39,6 +40,7 @@
|
|||||||
"ownedtanks",
|
"ownedtanks",
|
||||||
"playerachievements",
|
"playerachievements",
|
||||||
"playerinfo",
|
"playerinfo",
|
||||||
|
"Promisable",
|
||||||
"ratingsleaderboard",
|
"ratingsleaderboard",
|
||||||
"ratingsplot",
|
"ratingsplot",
|
||||||
"researchable",
|
"researchable",
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Blitzkrieg 1.15.10
|
||||||
|
|
||||||
|
The 10th patch for the `1.15` update is here, breaking the record of the most patches for a single update!
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Added `/today`
|
||||||
|
- This is a direct alias for `/breakdown today`
|
||||||
|
- Drastically simplified ratings command
|
||||||
|
- `/ratings current` and `/ratings archive` have been now merged
|
||||||
|
- Where the data is fetched from is now decided directly from the chosen season number
|
||||||
|
- Simplified many error messages
|
||||||
|
- Title bar description text is now centered when there is no image
|
||||||
|
- It always felt awkward when there was no image on the left but the left still aligned to the right
|
||||||
|
- Removed `/link`'s requirement to select a username explicitly from the search results
|
||||||
|
- Full names for regions are now used in the `/link` command
|
||||||
|
- So "NA" turns into "North America"
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
|
||||||
|
- Fixed contrast issues with WN8 percentile colors behind white text
|
||||||
|
- Archived ratings neighbors now functions once again
|
||||||
|
- Disabled tankopedia; this feature isn't finished yet and the changes were accidentally leaked
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Blitzkrieg 1.15.11
|
||||||
|
|
||||||
|
There's an easter egg somewhere in this update 🤔
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Added `/permissions` which lists all permissions required to run the bot and checks if the bot has them or not
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
- Error messages now do no use embeds, instead utilize simple headings and paragraphs
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# Blitzkrieg 1.15.12
|
||||||
|
|
||||||
|
Sometimes embeds need love too.
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
- Session embeds are now translucent by default
|
||||||
|
- Session embed titles are now the username of the player by default
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# Blitzkrieg 1.15.13
|
||||||
|
|
||||||
|
I have run out of things to say here. This patch fixes like 1 thing haha.
|
||||||
|
|
||||||
|
# Technical Fixes
|
||||||
|
|
||||||
|
- Tank deltas are now filtered by last battle time
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
# Permissions
|
|
||||||
|
|
||||||
The following Discord permissions are required by the bot to ensure full functionality:
|
|
||||||
|
|
||||||
- Manage nicknames
|
|
||||||
- Read messages / view channels
|
|
||||||
- Attach files
|
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "blitzkrieg",
|
"name": "blitzkrieg",
|
||||||
"version": "1.15.9",
|
"version": "1.15.13",
|
||||||
"description": "🎉 Tools for everything World of Tanks Blitz",
|
"description": "🎉 Tools for everything World of Tanks Blitz",
|
||||||
"main": "src/server.ts",
|
"main": "src/server.ts",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -14,14 +14,21 @@ import { tankAverages } from '../../../core/blitzstars/tankAverages';
|
|||||||
import calculateWN8 from '../../../core/statistics/calculateWN8';
|
import calculateWN8 from '../../../core/statistics/calculateWN8';
|
||||||
import { deltaTankStats } from '../../../core/statistics/deltaTankStats';
|
import { deltaTankStats } from '../../../core/statistics/deltaTankStats';
|
||||||
import getWN8Percentile from '../../../core/statistics/getWN8Percentile';
|
import getWN8Percentile from '../../../core/statistics/getWN8Percentile';
|
||||||
import { resetSession, useSession } from '../../../stores/session';
|
import mutateSession, {
|
||||||
|
resetSession,
|
||||||
|
useSession,
|
||||||
|
} from '../../../stores/session';
|
||||||
import { IndividualTankStats, TanksStats } from '../../../types/tanksStats';
|
import { IndividualTankStats, TanksStats } from '../../../types/tanksStats';
|
||||||
import { CustomColumnDisplay } from '../../tools/session/components/CustomColumn';
|
import { CustomColumnDisplay } from '../../tools/session/components/CustomColumn';
|
||||||
import { Menu } from '../../tools/session/components/Menu';
|
import { Menu } from '../../tools/session/components/Menu';
|
||||||
|
|
||||||
const REFRESH_RATE = 1 / 5;
|
const REFRESH_RATE = 1 / 5;
|
||||||
|
|
||||||
export default function SessionPage() {
|
interface SessionPageProps {
|
||||||
|
naked?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SessionPage({ naked = true }: SessionPageProps) {
|
||||||
const session = useSession();
|
const session = useSession();
|
||||||
const since = session.isTracking ? new Date(session.time) : undefined;
|
const since = session.isTracking ? new Date(session.time) : undefined;
|
||||||
const [diff, setDiff] = useState<
|
const [diff, setDiff] = useState<
|
||||||
@@ -72,6 +79,14 @@ export default function SessionPage() {
|
|||||||
0,
|
0,
|
||||||
) / careerBattles;
|
) / careerBattles;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (session.title === 'Untitled session') {
|
||||||
|
mutateSession((draft) => {
|
||||||
|
if (draft.isTracking) draft.title = draft.nickname;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [session.title]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function recalculateDiff() {
|
async function recalculateDiff() {
|
||||||
if (session.isTracking) {
|
if (session.isTracking) {
|
||||||
@@ -114,7 +129,7 @@ export default function SessionPage() {
|
|||||||
|
|
||||||
list: (
|
list: (
|
||||||
Object.values(
|
Object.values(
|
||||||
deltaTankStats(session.tankStats, career),
|
deltaTankStats(session.tankStats, career, session.time / 1000),
|
||||||
) as IndividualTankStats[]
|
) as IndividualTankStats[]
|
||||||
).map((stats) => {
|
).map((stats) => {
|
||||||
return {
|
return {
|
||||||
@@ -207,6 +222,7 @@ export default function SessionPage() {
|
|||||||
since !== undefined &&
|
since !== undefined &&
|
||||||
diff !== undefined && (
|
diff !== undefined && (
|
||||||
<Breakdown.Row
|
<Breakdown.Row
|
||||||
|
naked={naked}
|
||||||
color={session.color}
|
color={session.color}
|
||||||
minimized={!session.showCareer}
|
minimized={!session.showCareer}
|
||||||
title={session.title}
|
title={session.title}
|
||||||
@@ -288,6 +304,7 @@ export default function SessionPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Breakdown.Row
|
<Breakdown.Row
|
||||||
|
naked={naked}
|
||||||
color={session.color}
|
color={session.color}
|
||||||
key={stats.tank_id}
|
key={stats.tank_id}
|
||||||
minimized={!session.showCareer}
|
minimized={!session.showCareer}
|
||||||
|
|||||||
@@ -248,7 +248,7 @@ export default function Page() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<SessionPage />
|
<SessionPage naked={false} />
|
||||||
</PageWrapper>
|
</PageWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,6 @@ import { COMMANDS_RAW, CommandRegistry } from '../events/interactionCreate';
|
|||||||
|
|
||||||
const RAW_PATH = `https://raw.githubusercontent.com/tresabhi/blitzkrieg/main/docs/`;
|
const RAW_PATH = `https://raw.githubusercontent.com/tresabhi/blitzkrieg/main/docs/`;
|
||||||
const DOCS = {
|
const DOCS = {
|
||||||
permissions: 'guide/permissions',
|
|
||||||
embeds: 'guide/embeds',
|
embeds: 'guide/embeds',
|
||||||
introduction: 'guide/introduction',
|
introduction: 'guide/introduction',
|
||||||
invite: 'guide/invite',
|
invite: 'guide/invite',
|
||||||
@@ -14,7 +13,6 @@ const DOCS = {
|
|||||||
creators: 'guide/creators',
|
creators: 'guide/creators',
|
||||||
};
|
};
|
||||||
const DOC_DESCRIPTIONS: Record<keyof typeof DOCS, string> = {
|
const DOC_DESCRIPTIONS: Record<keyof typeof DOCS, string> = {
|
||||||
permissions: 'The permissions needed for the bot to function',
|
|
||||||
embeds: 'How embeds work',
|
embeds: 'How embeds work',
|
||||||
introduction: 'About the bot',
|
introduction: 'About the bot',
|
||||||
invite: 'How to invite the bot',
|
invite: 'How to invite the bot',
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { filtersToDescription } from '../core/blitzkrieg/filtersToDescription';
|
|||||||
import { getBlitzStarsLinkButton } from '../core/blitzstars/getBlitzStarsLinkButton';
|
import { getBlitzStarsLinkButton } from '../core/blitzstars/getBlitzStarsLinkButton';
|
||||||
import getStatsInPeriod from '../core/blitzstars/getStatsInPeriod';
|
import getStatsInPeriod from '../core/blitzstars/getStatsInPeriod';
|
||||||
import { tankAverages } from '../core/blitzstars/tankAverages';
|
import { tankAverages } from '../core/blitzstars/tankAverages';
|
||||||
import addFilterOptions from '../core/discord/addFilterOptions';
|
import addPeriodicFilterOptions from '../core/discord/addPeriodicFilterOptions';
|
||||||
import addUsernameChoices from '../core/discord/addUsernameChoices';
|
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';
|
||||||
@@ -33,12 +33,12 @@ import resolvePlayerFromCommand, {
|
|||||||
import calculateWN8 from '../core/statistics/calculateWN8';
|
import calculateWN8 from '../core/statistics/calculateWN8';
|
||||||
import { StatFilters, filterStats } from '../core/statistics/filterStats';
|
import { StatFilters, filterStats } from '../core/statistics/filterStats';
|
||||||
import getWN8Percentile from '../core/statistics/getWN8Percentile';
|
import getWN8Percentile from '../core/statistics/getWN8Percentile';
|
||||||
import { CommandRegistryRaw } from '../events/interactionCreate';
|
import { CommandRegistryPromisable } from '../events/interactionCreate';
|
||||||
|
|
||||||
const ROWS_PER_PAGE = 8;
|
const ROWS_PER_PAGE = 8;
|
||||||
const MAX_PAGES = 10;
|
const MAX_PAGES = 10;
|
||||||
|
|
||||||
async function render(
|
export async function renderBreakdown(
|
||||||
{ region, id }: ResolvedPlayer,
|
{ region, id }: ResolvedPlayer,
|
||||||
{ start, end, name }: ResolvedPeriod,
|
{ start, end, name }: ResolvedPeriod,
|
||||||
filters: StatFilters,
|
filters: StatFilters,
|
||||||
@@ -290,9 +290,9 @@ async function render(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const breakdownCommand = new Promise<CommandRegistryRaw>(
|
export const breakdownCommand = new Promise<CommandRegistryPromisable>(
|
||||||
async (resolve) => {
|
async (resolve) => {
|
||||||
const command = await addFilterOptions(
|
const command = await addPeriodicFilterOptions(
|
||||||
new SlashCommandBuilder()
|
new SlashCommandBuilder()
|
||||||
.setName('breakdown')
|
.setName('breakdown')
|
||||||
.setDescription("A period's breakdown by tanks played"),
|
.setDescription("A period's breakdown by tanks played"),
|
||||||
@@ -316,7 +316,7 @@ export const breakdownCommand = new Promise<CommandRegistryRaw>(
|
|||||||
});
|
});
|
||||||
|
|
||||||
return [
|
return [
|
||||||
...(await render(player, period, filters)),
|
...(await renderBreakdown(player, period, filters)),
|
||||||
buttonPrimary(path, 'Refresh'),
|
buttonPrimary(path, 'Refresh'),
|
||||||
await getBlitzStarsLinkButton(player.region, player.id),
|
await getBlitzStarsLinkButton(player.region, player.id),
|
||||||
];
|
];
|
||||||
@@ -332,7 +332,7 @@ export const breakdownCommand = new Promise<CommandRegistryRaw>(
|
|||||||
const period = resolvePeriodFromButton(player.region, interaction);
|
const period = resolvePeriodFromButton(player.region, interaction);
|
||||||
const filters = getFiltersFromButton(interaction);
|
const filters = getFiltersFromButton(interaction);
|
||||||
|
|
||||||
return await render(player, period, filters);
|
return await renderBreakdown(player, period, filters);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export const debugCommand: CommandRegistry = {
|
|||||||
.setName('debug')
|
.setName('debug')
|
||||||
.setDescription('Debug information about the bot'),
|
.setDescription('Debug information about the bot'),
|
||||||
|
|
||||||
async handler(interaction) {
|
async handler() {
|
||||||
const currentTime = new Date().getTime();
|
const currentTime = new Date().getTime();
|
||||||
const uptime = currentTime - executionStart;
|
const uptime = currentTime - executionStart;
|
||||||
const list = [
|
const list = [
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import resolvePlayerFromButton from '../core/discord/resolvePlayerFromButton';
|
|||||||
import resolvePlayerFromCommand, {
|
import resolvePlayerFromCommand, {
|
||||||
ResolvedPlayer,
|
ResolvedPlayer,
|
||||||
} from '../core/discord/resolvePlayerFromCommand';
|
} from '../core/discord/resolvePlayerFromCommand';
|
||||||
import { CommandRegistryRaw } from '../events/interactionCreate';
|
import { CommandRegistryPromisable } from '../events/interactionCreate';
|
||||||
|
|
||||||
type EvolutionStatType = 'player' | 'tank';
|
type EvolutionStatType = 'player' | 'tank';
|
||||||
|
|
||||||
@@ -107,7 +107,7 @@ async function render(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const evolutionCommand = new Promise<CommandRegistryRaw>(
|
export const evolutionCommand = new Promise<CommandRegistryPromisable>(
|
||||||
async (resolve) => {
|
async (resolve) => {
|
||||||
let playerGroup: SlashCommandSubcommandGroupBuilder;
|
let playerGroup: SlashCommandSubcommandGroupBuilder;
|
||||||
let tankGroup: SlashCommandSubcommandGroupBuilder;
|
let tankGroup: SlashCommandSubcommandGroupBuilder;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { emblemIdToURL } from '../core/blitzkrieg/emblemIdToURL';
|
|||||||
import { filtersToDescription } from '../core/blitzkrieg/filtersToDescription';
|
import { filtersToDescription } from '../core/blitzkrieg/filtersToDescription';
|
||||||
import { getBlitzStarsLinkButton } from '../core/blitzstars/getBlitzStarsLinkButton';
|
import { getBlitzStarsLinkButton } from '../core/blitzstars/getBlitzStarsLinkButton';
|
||||||
import getStatsInPeriod from '../core/blitzstars/getStatsInPeriod';
|
import getStatsInPeriod from '../core/blitzstars/getStatsInPeriod';
|
||||||
import addFilterOptions from '../core/discord/addFilterOptions';
|
import addPeriodicFilterOptions from '../core/discord/addPeriodicFilterOptions';
|
||||||
import addUsernameChoices from '../core/discord/addUsernameChoices';
|
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';
|
||||||
@@ -29,7 +29,7 @@ import resolvePlayerFromCommand, {
|
|||||||
} from '../core/discord/resolvePlayerFromCommand';
|
} from '../core/discord/resolvePlayerFromCommand';
|
||||||
import { StatFilters, filterStats } from '../core/statistics/filterStats';
|
import { StatFilters, filterStats } from '../core/statistics/filterStats';
|
||||||
import { getTierWeights } from '../core/statistics/getTierWeights';
|
import { getTierWeights } from '../core/statistics/getTierWeights';
|
||||||
import { CommandRegistryRaw } from '../events/interactionCreate';
|
import { CommandRegistryPromisable } from '../events/interactionCreate';
|
||||||
|
|
||||||
async function render(
|
async function render(
|
||||||
{ region, id }: ResolvedPlayer,
|
{ region, id }: ResolvedPlayer,
|
||||||
@@ -64,9 +64,9 @@ async function render(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fullStatsCommand = new Promise<CommandRegistryRaw>(
|
export const fullStatsCommand = new Promise<CommandRegistryPromisable>(
|
||||||
async (resolve) => {
|
async (resolve) => {
|
||||||
const command = await addFilterOptions(
|
const command = await addPeriodicFilterOptions(
|
||||||
new SlashCommandBuilder()
|
new SlashCommandBuilder()
|
||||||
.setName('full-stats')
|
.setName('full-stats')
|
||||||
.setDescription('Full in-game statistics'),
|
.setDescription('Full in-game statistics'),
|
||||||
|
|||||||
+140
-154
@@ -1,25 +1,21 @@
|
|||||||
import { teal } from '@radix-ui/colors';
|
import { teal } from '@radix-ui/colors';
|
||||||
import {
|
import { GuildMemberRoleManager, SlashCommandBuilder } from 'discord.js';
|
||||||
AttachmentBuilder,
|
|
||||||
GuildMemberRoleManager,
|
|
||||||
SlashCommandBuilder,
|
|
||||||
} from 'discord.js';
|
|
||||||
import markdownEscape from 'markdown-escape';
|
import markdownEscape from 'markdown-escape';
|
||||||
import discord from '../../discord.json' assert { type: 'json' };
|
import discord from '../../discord.json' assert { type: 'json' };
|
||||||
import { Glow } from '../components/AllStatsOverview/components/WN8Display/components/Glow';
|
import { Glow } from '../components/AllStatsOverview/components/WN8Display/components/Glow';
|
||||||
import Wrapper from '../components/Wrapper';
|
import Wrapper from '../components/Wrapper';
|
||||||
import { REGION_NAMES_SHORT, Region } from '../constants/regions';
|
import { REGION_NAMES } from '../constants/regions';
|
||||||
import { getAccountInfo } from '../core/blitz/getAccountInfo';
|
import { getAccountInfo } from '../core/blitz/getAccountInfo';
|
||||||
import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo';
|
import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo';
|
||||||
import { linkBlitzAndDiscord } from '../core/blitzkrieg/discordBlitz';
|
import { linkBlitzAndDiscord } from '../core/blitzkrieg/discordBlitz';
|
||||||
import addUsernameChoices from '../core/discord/addUsernameChoices';
|
import addUsernameChoices from '../core/discord/addUsernameChoices';
|
||||||
import autocompleteUsername from '../core/discord/autocompleteUsername';
|
import autocompleteUsername from '../core/discord/autocompleteUsername';
|
||||||
|
import embedInfo from '../core/discord/embedInfo';
|
||||||
import embedNegative from '../core/discord/embedNegative';
|
import embedNegative from '../core/discord/embedNegative';
|
||||||
|
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
|
||||||
import { CommandRegistry } from '../events/interactionCreate';
|
import { CommandRegistry } from '../events/interactionCreate';
|
||||||
import { theme } from '../stitches.config';
|
import { theme } from '../stitches.config';
|
||||||
|
|
||||||
const serverAndIdPattern = /(com|eu|asia)\/[0-9]+/;
|
|
||||||
|
|
||||||
export const verifyCommand: CommandRegistry = {
|
export const verifyCommand: CommandRegistry = {
|
||||||
inProduction: true,
|
inProduction: true,
|
||||||
inPublic: true,
|
inPublic: true,
|
||||||
@@ -30,193 +26,183 @@ export const verifyCommand: CommandRegistry = {
|
|||||||
.addStringOption((option) => addUsernameChoices(option).setRequired(true)),
|
.addStringOption((option) => addUsernameChoices(option).setRequired(true)),
|
||||||
|
|
||||||
async handler(interaction) {
|
async handler(interaction) {
|
||||||
const username = interaction.options.getString('username', true);
|
const { id, region } = await resolvePlayerFromCommand(interaction);
|
||||||
|
const discordId = parseInt(interaction.user.id);
|
||||||
|
const accountInfo = await getAccountInfo(region, id);
|
||||||
|
const clanAccountInfo = await getClanAccountInfo(region, id, ['clan']);
|
||||||
|
|
||||||
if (serverAndIdPattern.test(username)) {
|
await linkBlitzAndDiscord(discordId, region, id);
|
||||||
const [region, idString] = username.split('/');
|
|
||||||
const id = parseInt(idString);
|
|
||||||
const discordId = parseInt(interaction.user.id);
|
|
||||||
const accountInfo = await getAccountInfo(region as Region, id);
|
|
||||||
const clanAccountInfo = await getClanAccountInfo(region as Region, id, [
|
|
||||||
'clan',
|
|
||||||
]);
|
|
||||||
|
|
||||||
await linkBlitzAndDiscord(discordId, region as Region, id);
|
if (interaction.guildId === discord.sklld_guild_id) {
|
||||||
|
if (!interaction.guild?.members.me?.permissions.has('ManageRoles')) {
|
||||||
if (interaction.guildId === discord.sklld_guild_id) {
|
return embedNegative(
|
||||||
if (!interaction.guild?.members.me?.permissions.has('ManageRoles')) {
|
`${markdownEscape(interaction.user.username)} failed to verify`,
|
||||||
return embedNegative(
|
"I don't have the permission to change your manage roles.",
|
||||||
`${markdownEscape(interaction.user.username)} failed to verify`,
|
|
||||||
"I don't have the permission to change your manage roles.",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await (interaction.member!.roles as GuildMemberRoleManager).remove(
|
|
||||||
discord.sklld_verify_role,
|
|
||||||
);
|
|
||||||
await (interaction.member!.roles as GuildMemberRoleManager).add(
|
|
||||||
discord.sklld_peasant_role,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
await (interaction.member!.roles as GuildMemberRoleManager).remove(
|
||||||
<Wrapper fat>
|
discord.sklld_verify_role,
|
||||||
|
);
|
||||||
|
await (interaction.member!.roles as GuildMemberRoleManager).add(
|
||||||
|
discord.sklld_peasant_role,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
<Wrapper fat>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Glow color={teal.teal9} rotation={90} />
|
||||||
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'center',
|
||||||
|
gap: 16,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Glow color={teal.teal9} rotation={90} />
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
gap: 8,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={
|
||||||
|
interaction.user.avatarURL({ extension: 'png' }) ??
|
||||||
|
'https://i.stack.imgur.com/l60Hf.png'
|
||||||
|
}
|
||||||
|
style={{
|
||||||
|
width: 64,
|
||||||
|
height: 64,
|
||||||
|
borderRadius: 32,
|
||||||
|
objectFit: 'cover',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: 4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 32,
|
||||||
|
color: theme.colors.textHighContrast,
|
||||||
|
fontWeight: 900,
|
||||||
|
maxWidth: 240,
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{interaction.user.displayName}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 16,
|
||||||
|
color: theme.colors.textLowContrast,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
@{interaction.user.username}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
gap: 16,
|
gap: 4,
|
||||||
|
color: theme.colors.textLowContrast_teal,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<img
|
||||||
|
src="https://i.imgur.com/jIcRgog.png"
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
width: 16,
|
||||||
gap: 8,
|
height: 16,
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
|
<span style={{ fontSize: 16 }}>Accounts linked</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
gap: 8,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{clanAccountInfo?.clan ? (
|
||||||
<img
|
<img
|
||||||
src={
|
|
||||||
interaction.user.avatarURL({ extension: 'png' }) ??
|
|
||||||
'https://i.stack.imgur.com/l60Hf.png'
|
|
||||||
}
|
|
||||||
style={{
|
style={{
|
||||||
width: 64,
|
width: 64,
|
||||||
height: 64,
|
height: 64,
|
||||||
borderRadius: 32,
|
borderRadius: 32,
|
||||||
objectFit: 'cover',
|
objectFit: 'cover',
|
||||||
}}
|
}}
|
||||||
|
src={`https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clanAccountInfo?.clan?.emblem_set_id}.png`}
|
||||||
/>
|
/>
|
||||||
|
) : null}
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
gap: 4,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
fontSize: 32,
|
|
||||||
color: theme.colors.textHighContrast,
|
|
||||||
fontWeight: 900,
|
|
||||||
maxWidth: 240,
|
|
||||||
overflow: 'hidden',
|
|
||||||
textOverflow: 'ellipsis',
|
|
||||||
whiteSpace: 'nowrap',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{interaction.user.displayName}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
fontSize: 16,
|
|
||||||
color: theme.colors.textLowContrast,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
@{interaction.user.username}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
flexDirection: 'column',
|
||||||
justifyContent: 'center',
|
|
||||||
gap: 4,
|
gap: 4,
|
||||||
color: theme.colors.textLowContrast_teal,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<span
|
||||||
src="https://i.imgur.com/jIcRgog.png"
|
|
||||||
style={{
|
style={{
|
||||||
width: 16,
|
fontSize: 32,
|
||||||
height: 16,
|
color: theme.colors.textHighContrast,
|
||||||
}}
|
fontWeight: 900,
|
||||||
/>
|
maxWidth: 240,
|
||||||
<span style={{ fontSize: 16 }}>Accounts linked</span>
|
overflow: 'hidden',
|
||||||
</div>
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
gap: 8,
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{clanAccountInfo?.clan ? (
|
|
||||||
<img
|
|
||||||
style={{
|
|
||||||
width: 64,
|
|
||||||
height: 64,
|
|
||||||
borderRadius: 32,
|
|
||||||
objectFit: 'cover',
|
|
||||||
}}
|
|
||||||
src={`https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clanAccountInfo?.clan?.emblem_set_id}.png`}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
gap: 4,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span
|
{accountInfo.nickname}
|
||||||
style={{
|
</span>
|
||||||
fontSize: 32,
|
<span
|
||||||
color: theme.colors.textHighContrast,
|
style={{
|
||||||
fontWeight: 900,
|
fontSize: 16,
|
||||||
maxWidth: 240,
|
color: theme.colors.textLowContrast,
|
||||||
overflow: 'hidden',
|
}}
|
||||||
textOverflow: 'ellipsis',
|
>
|
||||||
whiteSpace: 'nowrap',
|
{clanAccountInfo?.clan
|
||||||
}}
|
? `[${clanAccountInfo?.clan?.tag}] - `
|
||||||
>
|
: ''}
|
||||||
{accountInfo.nickname}
|
{REGION_NAMES[region]}
|
||||||
</span>
|
</span>
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
fontSize: 16,
|
|
||||||
color: theme.colors.textLowContrast,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{clanAccountInfo?.clan
|
|
||||||
? `[${clanAccountInfo?.clan?.tag}] - `
|
|
||||||
: ''}
|
|
||||||
{REGION_NAMES_SHORT[region as Region]}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Glow color={teal.teal9} rotation={-90} />
|
|
||||||
</div>
|
</div>
|
||||||
</Wrapper>
|
|
||||||
);
|
<Glow color={teal.teal9} rotation={-90} />
|
||||||
} else {
|
</div>
|
||||||
return [
|
</Wrapper>,
|
||||||
embedNegative(
|
|
||||||
'Please select from the options',
|
embedInfo(
|
||||||
'Follow the procedure like the video above. The search results may take a while to show up.',
|
'Incorrect account?',
|
||||||
),
|
'Use the command again and select a username from the search results.',
|
||||||
new AttachmentBuilder('https://i.imgur.com/2p6GFgC.gif'),
|
),
|
||||||
];
|
];
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
autocomplete: autocompleteUsername,
|
autocomplete: autocompleteUsername,
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { SlashCommandBuilder } from 'discord.js';
|
||||||
|
import { CommandRegistry } from '../events/interactionCreate';
|
||||||
|
|
||||||
|
export const permissionsCommand: CommandRegistry = {
|
||||||
|
inProduction: true,
|
||||||
|
inPublic: true,
|
||||||
|
|
||||||
|
command: new SlashCommandBuilder()
|
||||||
|
.setName('permissions')
|
||||||
|
.setDescription('Checks wether the bot has the required permissions'),
|
||||||
|
|
||||||
|
async handler(interaction) {
|
||||||
|
// friday the 13th permissions easter egg
|
||||||
|
const isFridayThe13th =
|
||||||
|
new Date().getDay() === 5 && new Date().getDate() === 13;
|
||||||
|
const permissions = [
|
||||||
|
[
|
||||||
|
interaction.appPermissions?.has('ViewChannel'),
|
||||||
|
'View channels: needed for the refresh button to work',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
interaction.appPermissions?.has('ReadMessageHistory'),
|
||||||
|
'Read message history: also needed for the refresh button to work',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
interaction.appPermissions?.has('AttachFiles'),
|
||||||
|
'Attach files: needed for image-based commands to work',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
const body = permissions
|
||||||
|
.map(([hasPermission, description]) => {
|
||||||
|
if (hasPermission) {
|
||||||
|
return `${isFridayThe13th ? '😇' : '✅'} ${description}`;
|
||||||
|
} else {
|
||||||
|
return `${isFridayThe13th ? '💀' : '❌'} ${description}`;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
return `# Permissions\n${body}`;
|
||||||
|
},
|
||||||
|
};
|
||||||
+164
-192
@@ -2,9 +2,9 @@ import {
|
|||||||
APIApplicationCommandOptionChoice,
|
APIApplicationCommandOptionChoice,
|
||||||
SlashCommandBuilder,
|
SlashCommandBuilder,
|
||||||
SlashCommandSubcommandBuilder,
|
SlashCommandSubcommandBuilder,
|
||||||
SlashCommandSubcommandGroupBuilder,
|
|
||||||
} from 'discord.js';
|
} from 'discord.js';
|
||||||
import { range } from 'lodash';
|
import { range } from 'lodash';
|
||||||
|
import markdownEscape from 'markdown-escape';
|
||||||
import { BlitzkriegRatingsLeaderboard } from '../../scripts/buildRatingsLeaderboard';
|
import { BlitzkriegRatingsLeaderboard } from '../../scripts/buildRatingsLeaderboard';
|
||||||
import * as Leaderboard from '../components/Leaderboard';
|
import * as Leaderboard from '../components/Leaderboard';
|
||||||
import TitleBar from '../components/TitleBar';
|
import TitleBar from '../components/TitleBar';
|
||||||
@@ -17,17 +17,17 @@ import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo';
|
|||||||
import getRatingsInfo from '../core/blitz/getRatingsInfo';
|
import getRatingsInfo from '../core/blitz/getRatingsInfo';
|
||||||
import { getRatingsLeague } from '../core/blitz/getRatingsLeague';
|
import { getRatingsLeague } from '../core/blitz/getRatingsLeague';
|
||||||
import { getRatingsNeighbors } from '../core/blitz/getRatingsNeighbors';
|
import { getRatingsNeighbors } from '../core/blitz/getRatingsNeighbors';
|
||||||
|
import { isOnGoingRatingsSeason } from '../core/blitz/isOnGoingRatingsSeason';
|
||||||
import { getArchivedLatestSeasonNumber } from '../core/blitzkrieg/getArchivedLatestSeasonNumber';
|
import { getArchivedLatestSeasonNumber } from '../core/blitzkrieg/getArchivedLatestSeasonNumber';
|
||||||
import getArchivedRatingsInfo from '../core/blitzkrieg/getArchivedRatingsInfo';
|
import getArchivedRatingsInfo from '../core/blitzkrieg/getArchivedRatingsInfo';
|
||||||
import { getArchivedRatingsLeaderboard } from '../core/blitzkrieg/getArchivedRatingsLeaderboard';
|
import { getArchivedRatingsLeaderboard } from '../core/blitzkrieg/getArchivedRatingsLeaderboard';
|
||||||
import getArchivedRatingsMidnightLeaderboard from '../core/blitzkrieg/getArchivedRatingsMidnightLeaderboard';
|
import getArchivedRatingsMidnightLeaderboard from '../core/blitzkrieg/getArchivedRatingsMidnightLeaderboard';
|
||||||
import { UserError } from '../core/blitzkrieg/userError';
|
import { UserError } from '../core/blitzkrieg/userError';
|
||||||
import addRegionChoices from '../core/discord/addRegionChoices';
|
import addRegionChoices from '../core/discord/addRegionChoices';
|
||||||
import addUsernameChoices from '../core/discord/addUsernameChoices';
|
|
||||||
import autocompleteUsername from '../core/discord/autocompleteUsername';
|
import autocompleteUsername from '../core/discord/autocompleteUsername';
|
||||||
import embedNegative from '../core/discord/embedNegative';
|
import embedNegative from '../core/discord/embedNegative';
|
||||||
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
|
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
|
||||||
import { CommandRegistryRaw } from '../events/interactionCreate';
|
import { CommandRegistryPromisable } from '../events/interactionCreate';
|
||||||
|
|
||||||
export interface RatingsPlayer {
|
export interface RatingsPlayer {
|
||||||
spa_id: number;
|
spa_id: number;
|
||||||
@@ -116,18 +116,22 @@ export interface RatingsNeighbors {
|
|||||||
neighbors: RatingsPlayer[];
|
neighbors: RatingsPlayer[];
|
||||||
}
|
}
|
||||||
|
|
||||||
function addSubcommands(
|
const noOngoingSeason = embedNegative(
|
||||||
option: SlashCommandSubcommandGroupBuilder,
|
'No ongoing season',
|
||||||
addOptions = (option: SlashCommandSubcommandBuilder) => option,
|
"Wargaming didn't provide any data for this season.",
|
||||||
) {
|
);
|
||||||
return option
|
|
||||||
.addSubcommand((option) =>
|
export const ratingsCommand = new Promise<CommandRegistryPromisable>(
|
||||||
addOptions(
|
async (resolve) => {
|
||||||
option
|
const latestArchivedSeasonNumber = await getArchivedLatestSeasonNumber();
|
||||||
.setName('neighbors')
|
const onGoingSeason = await isOnGoingRatingsSeason();
|
||||||
.setDescription('Your position and neighbors'),
|
const seasonNumbers = range(
|
||||||
)
|
FIRST_ARCHIVED_RATINGS_SEASON,
|
||||||
.addStringOption(addUsernameChoices)
|
latestArchivedSeasonNumber + (onGoingSeason ? 1 : 0) + 1,
|
||||||
|
);
|
||||||
|
|
||||||
|
function addSubcommands(option: SlashCommandSubcommandBuilder) {
|
||||||
|
return option
|
||||||
.addIntegerOption((option) =>
|
.addIntegerOption((option) =>
|
||||||
option
|
option
|
||||||
.setName('limit')
|
.setName('limit')
|
||||||
@@ -135,77 +139,57 @@ function addSubcommands(
|
|||||||
.setRequired(false)
|
.setRequired(false)
|
||||||
.setMinValue(5)
|
.setMinValue(5)
|
||||||
.setMaxValue(30),
|
.setMaxValue(30),
|
||||||
),
|
)
|
||||||
)
|
|
||||||
.addSubcommand((option) =>
|
|
||||||
addOptions(option.setName('league').setDescription('Top league players'))
|
|
||||||
.addStringOption((option) =>
|
.addStringOption((option) =>
|
||||||
option
|
option
|
||||||
.setName('league')
|
.setName('season')
|
||||||
.setDescription('The league to display')
|
.setDescription('Season number')
|
||||||
.addChoices(
|
|
||||||
...LEAGUES.map(
|
|
||||||
({ name }, value) =>
|
|
||||||
({
|
|
||||||
name,
|
|
||||||
value: `${value}`,
|
|
||||||
}) satisfies APIApplicationCommandOptionChoice<string>,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.setRequired(true),
|
|
||||||
)
|
|
||||||
.addStringOption(addRegionChoices)
|
|
||||||
.addIntegerOption((option) =>
|
|
||||||
option
|
|
||||||
.setName('limit')
|
|
||||||
.setDescription('How many rows to display (default: 10)')
|
|
||||||
.setRequired(false)
|
.setRequired(false)
|
||||||
.setMinValue(5)
|
.addChoices(
|
||||||
.setMaxValue(30),
|
...seasonNumbers
|
||||||
),
|
.map(
|
||||||
);
|
(number, index) =>
|
||||||
}
|
({
|
||||||
|
name: `${number}${index === seasonNumbers.length - 1 && onGoingSeason ? ' (current)' : ''}`,
|
||||||
const noOngoingSeason = embedNegative(
|
value: `${number}`,
|
||||||
'No ongoing season',
|
}) satisfies APIApplicationCommandOptionChoice<string>,
|
||||||
"Wargaming didn't provide any data for this season.",
|
)
|
||||||
);
|
.reverse(),
|
||||||
|
),
|
||||||
export const ratingsCommand = new Promise<CommandRegistryRaw>(
|
);
|
||||||
async (resolve) => {
|
}
|
||||||
const latestArchivedSeasonNumber = await getArchivedLatestSeasonNumber();
|
|
||||||
const seasonChoices = range(
|
|
||||||
FIRST_ARCHIVED_RATINGS_SEASON,
|
|
||||||
latestArchivedSeasonNumber + 1,
|
|
||||||
).map(
|
|
||||||
(number) =>
|
|
||||||
({
|
|
||||||
name: `Season ${number}`,
|
|
||||||
value: `${number}`,
|
|
||||||
}) satisfies APIApplicationCommandOptionChoice<string>,
|
|
||||||
);
|
|
||||||
|
|
||||||
const command = new SlashCommandBuilder()
|
const command = new SlashCommandBuilder()
|
||||||
.setName('ratings')
|
.setName('ratings')
|
||||||
.setDescription('Ratings battles statistics')
|
.setDescription('Ratings battles statistics')
|
||||||
.addSubcommandGroup((option) =>
|
.addSubcommand((option) =>
|
||||||
addSubcommands(
|
|
||||||
option.setName('archive').setDescription('Any previous season'),
|
|
||||||
(option) =>
|
|
||||||
option.addStringOption((option) =>
|
|
||||||
option
|
|
||||||
.setName('season')
|
|
||||||
.setDescription('The season number')
|
|
||||||
.setChoices(...seasonChoices)
|
|
||||||
.setRequired(true),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.addSubcommandGroup((option) =>
|
|
||||||
addSubcommands(
|
addSubcommands(
|
||||||
option
|
option
|
||||||
.setName('current')
|
.setName('neighbors')
|
||||||
.setDescription('The current running Ratings season'),
|
.setDescription('Shows neighboring players on the leaderboard'),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.addSubcommand((option) =>
|
||||||
|
addSubcommands(
|
||||||
|
option
|
||||||
|
.setName('league')
|
||||||
|
.setDescription('Shows top players from a league')
|
||||||
|
.addStringOption(addRegionChoices)
|
||||||
|
.addStringOption((option) =>
|
||||||
|
option
|
||||||
|
.setName('league')
|
||||||
|
.setDescription('The league to display')
|
||||||
|
.addChoices(
|
||||||
|
...LEAGUES.map(
|
||||||
|
({ name }, value) =>
|
||||||
|
({
|
||||||
|
name,
|
||||||
|
value: `${value}`,
|
||||||
|
}) satisfies APIApplicationCommandOptionChoice<string>,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.setRequired(true),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -224,18 +208,19 @@ export const ratingsCommand = new Promise<CommandRegistryRaw>(
|
|||||||
nickname: string;
|
nickname: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const subcommandGroup = interaction.options.getSubcommandGroup(true) as
|
|
||||||
| 'archive'
|
|
||||||
| 'current';
|
|
||||||
const subcommand = interaction.options.getSubcommand(true) as
|
const subcommand = interaction.options.getSubcommand(true) as
|
||||||
| 'league'
|
| 'league'
|
||||||
| 'neighbors';
|
| 'neighbors';
|
||||||
const limit = interaction.options.getInteger('limit') ?? 10;
|
const limit = interaction.options.getInteger('limit') ?? 10;
|
||||||
const seasonOption = interaction.options.getString('season');
|
const seasonOption = interaction.options.getString('season');
|
||||||
const season = seasonOption ? parseInt(seasonOption) : undefined;
|
const season = seasonOption
|
||||||
|
? parseInt(seasonOption)
|
||||||
|
: onGoingSeason
|
||||||
|
? latestArchivedSeasonNumber + 1
|
||||||
|
: latestArchivedSeasonNumber;
|
||||||
|
const isCurrentSeason = season === latestArchivedSeasonNumber + 1;
|
||||||
let titleName: string;
|
let titleName: string;
|
||||||
let titleImage: string | undefined;
|
let titleImage: string | undefined;
|
||||||
let titleNameDiscriminator: string | undefined;
|
|
||||||
let titleDescription: string;
|
let titleDescription: string;
|
||||||
let playersBefore: number;
|
let playersBefore: number;
|
||||||
let playersAfter: number;
|
let playersAfter: number;
|
||||||
@@ -249,69 +234,66 @@ export const ratingsCommand = new Promise<CommandRegistryRaw>(
|
|||||||
interaction.options.getString('league')!,
|
interaction.options.getString('league')!,
|
||||||
);
|
);
|
||||||
const region = interaction.options.getString('region') as Region;
|
const region = interaction.options.getString('region') as Region;
|
||||||
regionRatingsInfo = await (subcommandGroup === 'current'
|
regionRatingsInfo = await (isCurrentSeason
|
||||||
? getRatingsInfo(region)
|
? getRatingsInfo(region)
|
||||||
: getArchivedRatingsInfo(region, season!));
|
: getArchivedRatingsInfo(region, season!));
|
||||||
|
|
||||||
if (regionRatingsInfo.detail !== undefined) return noOngoingSeason;
|
if (regionRatingsInfo.detail !== undefined) return noOngoingSeason;
|
||||||
|
|
||||||
const result =
|
const result = isCurrentSeason
|
||||||
subcommandGroup === 'current'
|
? await getRatingsLeague(region, leagueIndex).then(async (result) =>
|
||||||
? await getRatingsLeague(region, leagueIndex).then(
|
result.slice(0, limit).map(
|
||||||
async (result) =>
|
(player) =>
|
||||||
result.slice(0, limit).map(
|
({
|
||||||
(player) =>
|
id: player.spa_id,
|
||||||
({
|
score: player.score,
|
||||||
id: player.spa_id,
|
position: player.number,
|
||||||
score: player.score,
|
clan: player.clan_tag,
|
||||||
position: player.number,
|
nickname: player.nickname,
|
||||||
clan: player.clan_tag,
|
}) satisfies SimplifiedPlayer,
|
||||||
nickname: player.nickname,
|
),
|
||||||
}) satisfies SimplifiedPlayer,
|
)
|
||||||
),
|
: await getArchivedRatingsLeaderboard(region, season!).then(
|
||||||
)
|
async (players) => {
|
||||||
: await getArchivedRatingsLeaderboard(region, season!).then(
|
const firstPlayerIndex =
|
||||||
async (players) => {
|
leagueIndex === 0
|
||||||
const firstPlayerIndex =
|
? 0
|
||||||
leagueIndex === 0
|
: players.findIndex(
|
||||||
? 0
|
(player) =>
|
||||||
: players.findIndex(
|
player.score < LEAGUES[leagueIndex - 1].minScore,
|
||||||
(player) =>
|
);
|
||||||
player.score < LEAGUES[leagueIndex - 1].minScore,
|
const lastPlayerIndex = firstPlayerIndex + limit - 1;
|
||||||
);
|
const trimmed = players.slice(
|
||||||
const lastPlayerIndex = firstPlayerIndex + limit - 1;
|
firstPlayerIndex,
|
||||||
const trimmed = players.slice(
|
lastPlayerIndex + 1,
|
||||||
firstPlayerIndex,
|
);
|
||||||
lastPlayerIndex + 1,
|
const clanData = await getClanAccountInfo(
|
||||||
);
|
region,
|
||||||
const clanData = await getClanAccountInfo(
|
trimmed.map(({ id }) => id),
|
||||||
region,
|
['clan'],
|
||||||
trimmed.map(({ id }) => id),
|
);
|
||||||
['clan'],
|
|
||||||
);
|
|
||||||
|
|
||||||
return trimmed.map(
|
return trimmed.map(
|
||||||
(player, index) =>
|
(player, index) =>
|
||||||
({
|
({
|
||||||
id: player.id,
|
id: player.id,
|
||||||
score: player.score,
|
score: player.score,
|
||||||
position: firstPlayerIndex + index + 1,
|
position: firstPlayerIndex + index + 1,
|
||||||
clan: clanData[player.id]?.clan?.tag,
|
clan: clanData[player.id]?.clan?.tag,
|
||||||
nickname:
|
nickname:
|
||||||
clanData[index]?.account_name ??
|
clanData[index]?.account_name ??
|
||||||
`Deleted ${player.id}`,
|
`Deleted ${player.id}`,
|
||||||
}) satisfies SimplifiedPlayer,
|
}) satisfies SimplifiedPlayer,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
const leagueInfo = regionRatingsInfo.leagues[leagueIndex];
|
const leagueInfo = regionRatingsInfo.leagues[leagueIndex];
|
||||||
midnightLeaderboard =
|
midnightLeaderboard = isCurrentSeason
|
||||||
subcommandGroup === 'current'
|
? await getArchivedRatingsMidnightLeaderboard(
|
||||||
? await getArchivedRatingsMidnightLeaderboard(
|
region,
|
||||||
region,
|
regionRatingsInfo.current_season,
|
||||||
regionRatingsInfo.current_season,
|
)
|
||||||
)
|
: await getArchivedRatingsMidnightLeaderboard(region, season!);
|
||||||
: await getArchivedRatingsMidnightLeaderboard(region, season!);
|
|
||||||
|
|
||||||
players = result;
|
players = result;
|
||||||
playersBefore = result ? result[0].position - 1 : 0;
|
playersBefore = result ? result[0].position - 1 : 0;
|
||||||
@@ -326,22 +308,17 @@ export const ratingsCommand = new Promise<CommandRegistryRaw>(
|
|||||||
titleDescription = `Top ${limit} players`;
|
titleDescription = `Top ${limit} players`;
|
||||||
} else {
|
} else {
|
||||||
const { region, id } = await resolvePlayerFromCommand(interaction);
|
const { region, id } = await resolvePlayerFromCommand(interaction);
|
||||||
regionRatingsInfo =
|
regionRatingsInfo = isCurrentSeason
|
||||||
subcommandGroup === 'current'
|
? await getRatingsInfo(region)
|
||||||
? await getRatingsInfo(region)
|
: await getArchivedRatingsInfo(region, season!);
|
||||||
: await getArchivedRatingsInfo(region, season!);
|
|
||||||
|
|
||||||
if (regionRatingsInfo.detail !== undefined) return noOngoingSeason;
|
if (regionRatingsInfo.detail !== undefined) return noOngoingSeason;
|
||||||
|
|
||||||
const accountInfo = await getAccountInfo(region, id);
|
const accountInfo = await getAccountInfo(region, id);
|
||||||
const clan = (await getClanAccountInfo(region, id, ['clan']))?.clan;
|
const clan = (await getClanAccountInfo(region, id, ['clan']))?.clan;
|
||||||
const neighbors =
|
const neighbors = isCurrentSeason
|
||||||
subcommandGroup === 'current'
|
? await getRatingsNeighbors(region, id, Math.round(limit / 2)).then(
|
||||||
? await getRatingsNeighbors(
|
(players) =>
|
||||||
region,
|
|
||||||
id,
|
|
||||||
Math.round(limit / 2),
|
|
||||||
).then((players) =>
|
|
||||||
players.neighbors.map(
|
players.neighbors.map(
|
||||||
(player) =>
|
(player) =>
|
||||||
({
|
({
|
||||||
@@ -352,52 +329,47 @@ export const ratingsCommand = new Promise<CommandRegistryRaw>(
|
|||||||
nickname: player.nickname,
|
nickname: player.nickname,
|
||||||
}) satisfies SimplifiedPlayer,
|
}) satisfies SimplifiedPlayer,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: await getArchivedRatingsLeaderboard(region, season!).then(
|
: await getArchivedRatingsLeaderboard(region, season!).then(
|
||||||
async (players) => {
|
async (players) => {
|
||||||
const playerIndex = players.findIndex(
|
const playerIndex = players.findIndex(
|
||||||
(player) => player.id === id,
|
(player) => player.id === id,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (playerIndex === -1) {
|
||||||
|
throw new UserError(
|
||||||
|
`# ${markdownEscape(accountInfo.nickname)} didn't play ratings in season ${season}\nThis player did not participate in this season or did not get past calibration.`,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (playerIndex === -1) {
|
const halfRange = Math.round(limit / 2);
|
||||||
throw new UserError(
|
const trimmed = players.slice(
|
||||||
`${accountInfo.nickname} didn't player ratings in season ${season}`,
|
playerIndex - halfRange,
|
||||||
{
|
playerIndex + halfRange + 1,
|
||||||
cause:
|
);
|
||||||
'This player did not participate in this season or did not get past calibration.',
|
const clanData = await getClanAccountInfo(
|
||||||
},
|
region,
|
||||||
);
|
trimmed.map((player) => player.id),
|
||||||
}
|
['clan'],
|
||||||
|
);
|
||||||
|
|
||||||
const halfRange = Math.round(limit / 2);
|
return trimmed.map((player, index) => ({
|
||||||
const trimmed = players.slice(
|
id: player.id,
|
||||||
playerIndex - halfRange,
|
score: player.score,
|
||||||
playerIndex + halfRange + 1,
|
position: playerIndex + index + 1,
|
||||||
);
|
clan: clanData[index]?.clan?.tag,
|
||||||
const clanData = await getClanAccountInfo(
|
nickname: clanData[index]
|
||||||
region,
|
? clanData[index]!.account_name
|
||||||
trimmed.map((player) => player.id),
|
: `Deleted ${player.id}`,
|
||||||
['clan'],
|
}));
|
||||||
);
|
},
|
||||||
|
);
|
||||||
return trimmed.map((player, index) => ({
|
midnightLeaderboard = isCurrentSeason
|
||||||
id: player.id,
|
? await getArchivedRatingsMidnightLeaderboard(
|
||||||
score: player.score,
|
region,
|
||||||
position: playerIndex + index + 1,
|
regionRatingsInfo.current_season,
|
||||||
clan: clanData[player.id]!.clan?.tag,
|
)
|
||||||
nickname: clanData[player.id]!.account_name,
|
: await getArchivedRatingsMidnightLeaderboard(region, season!);
|
||||||
}));
|
|
||||||
},
|
|
||||||
);
|
|
||||||
midnightLeaderboard =
|
|
||||||
subcommandGroup === 'current'
|
|
||||||
? await getArchivedRatingsMidnightLeaderboard(
|
|
||||||
region,
|
|
||||||
regionRatingsInfo.current_season,
|
|
||||||
)
|
|
||||||
: await getArchivedRatingsMidnightLeaderboard(region, season!);
|
|
||||||
|
|
||||||
if (clan && neighbors) titleNameDiscriminator = `[${clan.tag}]`;
|
|
||||||
|
|
||||||
players = neighbors;
|
players = neighbors;
|
||||||
titleImage = clan
|
titleImage = clan
|
||||||
|
|||||||
+42
-40
@@ -9,7 +9,7 @@ import { emblemIdToURL } from '../core/blitzkrieg/emblemIdToURL';
|
|||||||
import { filtersToDescription } from '../core/blitzkrieg/filtersToDescription';
|
import { filtersToDescription } from '../core/blitzkrieg/filtersToDescription';
|
||||||
import { getBlitzStarsLinkButton } from '../core/blitzstars/getBlitzStarsLinkButton';
|
import { getBlitzStarsLinkButton } from '../core/blitzstars/getBlitzStarsLinkButton';
|
||||||
import getStatsInPeriod from '../core/blitzstars/getStatsInPeriod';
|
import getStatsInPeriod from '../core/blitzstars/getStatsInPeriod';
|
||||||
import addFilterOptions from '../core/discord/addFilterOptions';
|
import addPeriodicFilterOptions from '../core/discord/addPeriodicFilterOptions';
|
||||||
import addUsernameChoices from '../core/discord/addUsernameChoices';
|
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';
|
||||||
@@ -27,7 +27,7 @@ import resolvePlayerFromCommand, {
|
|||||||
ResolvedPlayer,
|
ResolvedPlayer,
|
||||||
} from '../core/discord/resolvePlayerFromCommand';
|
} from '../core/discord/resolvePlayerFromCommand';
|
||||||
import { StatFilters, filterStats } from '../core/statistics/filterStats';
|
import { StatFilters, filterStats } from '../core/statistics/filterStats';
|
||||||
import { CommandRegistryRaw } from '../events/interactionCreate';
|
import { CommandRegistryPromisable } from '../events/interactionCreate';
|
||||||
|
|
||||||
async function render(
|
async function render(
|
||||||
{ region, id }: ResolvedPlayer,
|
{ region, id }: ResolvedPlayer,
|
||||||
@@ -57,48 +57,50 @@ async function render(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const statsCommand = new Promise<CommandRegistryRaw>(async (resolve) => {
|
export const statsCommand = new Promise<CommandRegistryPromisable>(
|
||||||
const command = await addFilterOptions(
|
async (resolve) => {
|
||||||
new SlashCommandBuilder()
|
const command = await addPeriodicFilterOptions(
|
||||||
.setName('stats')
|
new SlashCommandBuilder()
|
||||||
.setDescription('Regular battles statistics'),
|
.setName('stats')
|
||||||
(option) => option.addStringOption(addUsernameChoices),
|
.setDescription('Regular battles statistics'),
|
||||||
);
|
(option) => option.addStringOption(addUsernameChoices),
|
||||||
|
);
|
||||||
|
|
||||||
resolve({
|
resolve({
|
||||||
inProduction: true,
|
inProduction: true,
|
||||||
inPublic: true,
|
inPublic: true,
|
||||||
|
|
||||||
command,
|
command,
|
||||||
|
|
||||||
async handler(interaction) {
|
async handler(interaction) {
|
||||||
const player = await resolvePlayerFromCommand(interaction);
|
const player = await resolvePlayerFromCommand(interaction);
|
||||||
const period = resolvePeriodFromCommand(player.region, interaction);
|
const period = resolvePeriodFromCommand(player.region, interaction);
|
||||||
const filters = await getFiltersFromCommand(interaction);
|
const filters = await getFiltersFromCommand(interaction);
|
||||||
const path = commandToURL(interaction, {
|
const path = commandToURL(interaction, {
|
||||||
...player,
|
...player,
|
||||||
...getCustomPeriodParams(interaction),
|
...getCustomPeriodParams(interaction),
|
||||||
...filters,
|
...filters,
|
||||||
});
|
});
|
||||||
|
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
render(player, period, filters),
|
render(player, period, filters),
|
||||||
buttonRefresh(interaction, path),
|
buttonRefresh(interaction, path),
|
||||||
getBlitzStarsLinkButton(player.region, player.id),
|
getBlitzStarsLinkButton(player.region, player.id),
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
|
||||||
autocomplete: (interaction) => {
|
autocomplete: (interaction) => {
|
||||||
autocompleteUsername(interaction);
|
autocompleteUsername(interaction);
|
||||||
autocompleteTanks(interaction);
|
autocompleteTanks(interaction);
|
||||||
},
|
},
|
||||||
|
|
||||||
async button(interaction) {
|
async button(interaction) {
|
||||||
const player = await resolvePlayerFromButton(interaction);
|
const player = await resolvePlayerFromButton(interaction);
|
||||||
const period = resolvePeriodFromButton(player.region, interaction);
|
const period = resolvePeriodFromButton(player.region, interaction);
|
||||||
const filters = getFiltersFromButton(interaction);
|
const filters = getFiltersFromButton(interaction);
|
||||||
|
|
||||||
return await render(player, period, filters);
|
return await render(player, period, filters);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
|
);
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import { SlashCommandBuilder } from 'discord.js';
|
||||||
|
import { encyclopediaInfo } from '../core/blitz/encyclopediaInfo';
|
||||||
|
import { getBlitzStarsLinkButton } from '../core/blitzstars/getBlitzStarsLinkButton';
|
||||||
|
import { addFilterOptions } from '../core/discord/addFilterOptions';
|
||||||
|
import addUsernameChoices from '../core/discord/addUsernameChoices';
|
||||||
|
import autocompleteTanks from '../core/discord/autocompleteTanks';
|
||||||
|
import autocompleteUsername from '../core/discord/autocompleteUsername';
|
||||||
|
import buttonPrimary from '../core/discord/buttonPrimary';
|
||||||
|
import commandToURL from '../core/discord/commandToURL';
|
||||||
|
import { getCustomPeriodParams } from '../core/discord/getCustomPeriodParams';
|
||||||
|
import { getFiltersFromButton } from '../core/discord/getFiltersFromButton';
|
||||||
|
import { getFiltersFromCommand } from '../core/discord/getFiltersFromCommand';
|
||||||
|
import resolvePeriodFromButton from '../core/discord/resolvePeriodFromButton';
|
||||||
|
import resolvePeriodFromCommand from '../core/discord/resolvePeriodFromCommand';
|
||||||
|
import resolvePlayerFromButton from '../core/discord/resolvePlayerFromButton';
|
||||||
|
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
|
||||||
|
import { CommandRegistryPromisable } from '../events/interactionCreate';
|
||||||
|
import { renderBreakdown } from './breakdown';
|
||||||
|
|
||||||
|
export const todayCommand = new Promise<CommandRegistryPromisable>(
|
||||||
|
async (resolve) => {
|
||||||
|
const awaitedEncyclopediaInfo = await encyclopediaInfo;
|
||||||
|
const command = addFilterOptions(
|
||||||
|
new SlashCommandBuilder()
|
||||||
|
.setName('today')
|
||||||
|
.setDescription("Today's played tanks and statistics"),
|
||||||
|
awaitedEncyclopediaInfo,
|
||||||
|
).addStringOption(addUsernameChoices);
|
||||||
|
|
||||||
|
resolve({
|
||||||
|
inProduction: true,
|
||||||
|
inPublic: true,
|
||||||
|
|
||||||
|
command,
|
||||||
|
|
||||||
|
async handler(interaction) {
|
||||||
|
const player = await resolvePlayerFromCommand(interaction);
|
||||||
|
const period = resolvePeriodFromCommand(
|
||||||
|
player.region,
|
||||||
|
interaction,
|
||||||
|
'today',
|
||||||
|
);
|
||||||
|
const filters = await getFiltersFromCommand(interaction);
|
||||||
|
const path = commandToURL(interaction, {
|
||||||
|
...player,
|
||||||
|
...getCustomPeriodParams(interaction, true),
|
||||||
|
...filters,
|
||||||
|
});
|
||||||
|
|
||||||
|
return [
|
||||||
|
...(await renderBreakdown(player, period, filters)),
|
||||||
|
buttonPrimary(path, 'Refresh'),
|
||||||
|
await getBlitzStarsLinkButton(player.region, player.id),
|
||||||
|
];
|
||||||
|
},
|
||||||
|
|
||||||
|
autocomplete: (interaction) => {
|
||||||
|
autocompleteUsername(interaction);
|
||||||
|
autocompleteTanks(interaction);
|
||||||
|
},
|
||||||
|
|
||||||
|
async button(interaction) {
|
||||||
|
const player = await resolvePlayerFromButton(interaction);
|
||||||
|
const period = resolvePeriodFromButton(player.region, interaction);
|
||||||
|
const filters = getFiltersFromButton(interaction);
|
||||||
|
|
||||||
|
return await renderBreakdown(player, period, filters);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
@@ -37,7 +37,7 @@ export function WN8Display({ WN8 }: WN8DisplayProps) {
|
|||||||
gap: -16,
|
gap: -16,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Glow color={PERCENTILE_COLORS[percentile]} />
|
<Glow color={color} />
|
||||||
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@@ -71,7 +71,7 @@ export function WN8Display({ WN8 }: WN8DisplayProps) {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Glow color={PERCENTILE_COLORS[percentile]} direction="reverse" />
|
<Glow color={color} direction="reverse" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -18,6 +18,7 @@ export interface RowStatItem {
|
|||||||
|
|
||||||
interface RowProps {
|
interface RowProps {
|
||||||
type?: 'tank' | 'summary';
|
type?: 'tank' | 'summary';
|
||||||
|
naked?: boolean;
|
||||||
title: string;
|
title: string;
|
||||||
minimized?: boolean;
|
minimized?: boolean;
|
||||||
treeType?: TreeTypeEnum;
|
treeType?: TreeTypeEnum;
|
||||||
@@ -33,18 +34,23 @@ export function Row({
|
|||||||
minimized,
|
minimized,
|
||||||
tankType,
|
tankType,
|
||||||
treeType,
|
treeType,
|
||||||
|
naked = false,
|
||||||
type,
|
type,
|
||||||
}: RowProps) {
|
}: RowProps) {
|
||||||
const theme = createColors(PALETTES[`${color}Dark`]);
|
const palette = PALETTES[`${color}Dark`];
|
||||||
|
const theme = createColors(palette);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="session-tracker-row"
|
className="session-tracker-row"
|
||||||
style={{
|
style={{
|
||||||
|
maxWidth: naked ? 600 : undefined,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
backgroundColor: theme.appBackground2,
|
backgroundColor: naked
|
||||||
|
? `${theme.appBackground2}c0`
|
||||||
|
: theme.appBackground2,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -55,7 +61,9 @@ export function Row({
|
|||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
padding: 8,
|
padding: 8,
|
||||||
gap: 4,
|
gap: 4,
|
||||||
backgroundColor: theme.componentInteractive,
|
backgroundColor: naked
|
||||||
|
? `${theme.componentInteractive}c0`
|
||||||
|
: theme.componentInteractive,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{type === 'tank' && tankType !== undefined && (
|
{type === 'tank' && tankType !== undefined && (
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
amber,
|
||||||
blue,
|
blue,
|
||||||
green,
|
green,
|
||||||
lime,
|
lime,
|
||||||
@@ -8,7 +9,6 @@ import {
|
|||||||
red,
|
red,
|
||||||
slate,
|
slate,
|
||||||
teal,
|
teal,
|
||||||
yellow,
|
|
||||||
} from '@radix-ui/colors';
|
} from '@radix-ui/colors';
|
||||||
import { Percentile } from '../constants/percentiles';
|
import { Percentile } from '../constants/percentiles';
|
||||||
|
|
||||||
@@ -20,8 +20,8 @@ export const PERCENTILE_COLORS = {
|
|||||||
[Percentile.VeryBad]: slate.slate9,
|
[Percentile.VeryBad]: slate.slate9,
|
||||||
[Percentile.Bad]: red.red9,
|
[Percentile.Bad]: red.red9,
|
||||||
[Percentile.BelowAverage]: orange.orange9,
|
[Percentile.BelowAverage]: orange.orange9,
|
||||||
[Percentile.Average]: yellow.yellow9,
|
[Percentile.Average]: amber.amber10,
|
||||||
[Percentile.AboveAverage]: lime.lime9,
|
[Percentile.AboveAverage]: lime.lime8,
|
||||||
[Percentile.Good]: green.green9,
|
[Percentile.Good]: green.green9,
|
||||||
[Percentile.VeryGood]: teal.teal9,
|
[Percentile.VeryGood]: teal.teal9,
|
||||||
[Percentile.Great]: blue.blue9,
|
[Percentile.Great]: blue.blue9,
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ export const TANK_TYPES = [
|
|||||||
'heavy',
|
'heavy',
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
// TODO: remove hyphen for underscore
|
|
||||||
export const TREE_TYPES = ['researchable', 'premium', 'collector'] as const;
|
export const TREE_TYPES = ['researchable', 'premium', 'collector'] as const;
|
||||||
|
|
||||||
export type TankType = (typeof TANK_TYPES)[number];
|
export type TankType = (typeof TANK_TYPES)[number];
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ export default function TitleBar({ name, image, description }: TitleBarProps) {
|
|||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
gap: 4,
|
gap: 4,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
@@ -51,9 +53,21 @@ export default function TitleBar({ name, image, description }: TitleBarProps) {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span style={{ color: theme.colors.textLowContrast, fontSize: 16 }}>
|
<div
|
||||||
{description}
|
style={{
|
||||||
</span>
|
display: 'flex',
|
||||||
|
justifyContent: image ? 'flex-start' : 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
color: theme.colors.textLowContrast,
|
||||||
|
fontSize: 16,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{description}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export const TOOLS: Tool[] = [
|
|||||||
id: 'tankopedia',
|
id: 'tankopedia',
|
||||||
title: 'Tankopedia',
|
title: 'Tankopedia',
|
||||||
description: 'Blitz tank encyclopedia',
|
description: 'Blitz tank encyclopedia',
|
||||||
|
disabled: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'inactive',
|
id: 'inactive',
|
||||||
|
|||||||
@@ -4,7 +4,11 @@ import { context } from '../blitzkrieg/context';
|
|||||||
import { patientFetch } from '../blitzkrieg/patientFetch';
|
import { patientFetch } from '../blitzkrieg/patientFetch';
|
||||||
import regionToRegionSubdomain from './regionToRegionSubdomain';
|
import regionToRegionSubdomain from './regionToRegionSubdomain';
|
||||||
|
|
||||||
|
const ratingsInfoCache: Partial<Record<Region, RatingsInfo>> = {};
|
||||||
|
|
||||||
export default async function getRatingsInfo(region: Region) {
|
export default async function getRatingsInfo(region: Region) {
|
||||||
|
if (ratingsInfoCache[region]) return ratingsInfoCache[region]!;
|
||||||
|
|
||||||
const response = await patientFetch(
|
const response = await patientFetch(
|
||||||
context === 'website'
|
context === 'website'
|
||||||
? `/api/${region}/ratings/current/info`
|
? `/api/${region}/ratings/current/info`
|
||||||
@@ -14,6 +18,9 @@ export default async function getRatingsInfo(region: Region) {
|
|||||||
undefined,
|
undefined,
|
||||||
{ cache: 'no-store' },
|
{ cache: 'no-store' },
|
||||||
);
|
);
|
||||||
|
const data = (await response.json()) as RatingsInfo;
|
||||||
|
|
||||||
return (await response.json()) as Promise<RatingsInfo>;
|
ratingsInfoCache[region] = data;
|
||||||
|
|
||||||
|
return data;
|
||||||
}
|
}
|
||||||
@@ -10,10 +10,9 @@ export default async function getTankStats(region: Region, id: number) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (tankStats[id] === null) {
|
if (tankStats[id] === null) {
|
||||||
throw new UserError('No tank stats available', {
|
throw new UserError(
|
||||||
cause:
|
"# No tank stats available\nThis player doesn't have any stats available. This may not be the player you are looking for.",
|
||||||
"This player doesn't have any stats for tanks available. This may not be the player you are looking for.",
|
);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return tankStats[id];
|
return tankStats[id];
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import getRatingsInfo from './getRatingsInfo';
|
||||||
|
|
||||||
|
export async function isOnGoingRatingsSeason() {
|
||||||
|
const ratingsInfo = await getRatingsInfo('com');
|
||||||
|
return ratingsInfo.detail === undefined;
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { go } from 'fuzzysort';
|
import { go } from 'fuzzysort';
|
||||||
|
import markdownEscape from 'markdown-escape';
|
||||||
import {
|
import {
|
||||||
tankDefinitions,
|
tankDefinitions,
|
||||||
tanksDefinitionsArray,
|
tanksDefinitionsArray,
|
||||||
@@ -15,9 +16,9 @@ export default async function resolveTankId(tank: string | number) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (searchResult.length === 0) {
|
if (searchResult.length === 0) {
|
||||||
throw new UserError('Tank not found', {
|
throw new UserError(
|
||||||
cause: `Could not find tank by the name "${tank}".`,
|
`# Tank not found\nCould not find tank by the name "${markdownEscape(`${tank}`)}".`,
|
||||||
});
|
);
|
||||||
} else {
|
} else {
|
||||||
return searchResult[0].obj.id;
|
return searchResult[0].obj.id;
|
||||||
}
|
}
|
||||||
@@ -25,9 +26,9 @@ export default async function resolveTankId(tank: string | number) {
|
|||||||
if ((await tankDefinitions)[number]) {
|
if ((await tankDefinitions)[number]) {
|
||||||
return number;
|
return number;
|
||||||
} else {
|
} else {
|
||||||
throw new UserError('Tank not found', {
|
throw new UserError(
|
||||||
cause: `Could not find tank by the ID "${number}".`,
|
`# Tank not found\nCould not find tank by the ID "${number}". Try typing the name of the tank instead of a number.`,
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Discriminator for user caused errors to not report in logs
|
* Discriminator for user caused errors to not report in logs
|
||||||
*/
|
*/
|
||||||
export class UserError extends Error {}
|
export class UserError {
|
||||||
|
constructor(public message: string) {}
|
||||||
|
}
|
||||||
@@ -1,76 +1,60 @@
|
|||||||
import {
|
import { SlashCommandBuilder, SlashCommandSubcommandBuilder } from 'discord.js';
|
||||||
SlashCommandBuilder,
|
|
||||||
SlashCommandSubcommandBuilder,
|
|
||||||
SlashCommandSubcommandGroupBuilder,
|
|
||||||
} from 'discord.js';
|
|
||||||
import { TREE_TYPE_NAMES, TreeTypeString } from '../../components/Tanks';
|
import { TREE_TYPE_NAMES, TreeTypeString } from '../../components/Tanks';
|
||||||
import { encyclopediaInfo } from '../blitz/encyclopediaInfo';
|
import { EncyclopediaInfo } from '../blitz/encyclopediaInfo';
|
||||||
import addPeriodSubCommands from './addPeriodSubCommands';
|
|
||||||
import addTankChoices from './addTankChoices';
|
import addTankChoices from './addTankChoices';
|
||||||
import addTierChoices from './addTierChoices';
|
import addTierChoices from './addTierChoices';
|
||||||
|
|
||||||
export default async function addFilterOptions<
|
export function addFilterOptions<
|
||||||
OptionType extends SlashCommandBuilder | SlashCommandSubcommandGroupBuilder,
|
Option extends SlashCommandBuilder | SlashCommandSubcommandBuilder,
|
||||||
>(
|
>(option: Option, encyclopediaInfo: EncyclopediaInfo) {
|
||||||
option: OptionType,
|
return option
|
||||||
extra: (
|
.addStringOption((option) =>
|
||||||
option: SlashCommandSubcommandBuilder,
|
option
|
||||||
) => SlashCommandSubcommandBuilder = (option) => option,
|
.setName('nation')
|
||||||
): Promise<OptionType> {
|
.setDescription('Nation')
|
||||||
const awaitedEncyclopediaInfo = await encyclopediaInfo;
|
.addChoices(
|
||||||
|
...Object.entries(encyclopediaInfo.vehicle_nations).map(
|
||||||
addPeriodSubCommands(option, (option) => {
|
([type, name]) => ({ name, value: type }),
|
||||||
extra(option);
|
),
|
||||||
|
)
|
||||||
return option
|
.setRequired(false),
|
||||||
.addStringOption((option) =>
|
)
|
||||||
option
|
.addStringOption((option) => addTierChoices(option).setRequired(false))
|
||||||
.setName('nation')
|
.addStringOption((option) =>
|
||||||
.setDescription('Nation')
|
option
|
||||||
.addChoices(
|
.setName('tank-type')
|
||||||
...Object.entries(awaitedEncyclopediaInfo.vehicle_nations).map(
|
.setDescription('Tank type')
|
||||||
([type, name]) => ({ name, value: type }),
|
.addChoices(
|
||||||
),
|
...Object.entries(encyclopediaInfo.vehicle_types).map(
|
||||||
)
|
([type, name]) => ({
|
||||||
.setRequired(false),
|
name: name.replace(' Tank', ''),
|
||||||
)
|
value: type,
|
||||||
.addStringOption((option) => addTierChoices(option).setRequired(false))
|
}),
|
||||||
.addStringOption((option) =>
|
),
|
||||||
option
|
)
|
||||||
.setName('tank-type')
|
.setRequired(false),
|
||||||
.setDescription('Tank type')
|
)
|
||||||
.addChoices(
|
.addStringOption((option) =>
|
||||||
...Object.entries(awaitedEncyclopediaInfo.vehicle_types).map(
|
option
|
||||||
([type, name]) => ({
|
.setName('tree-type')
|
||||||
name: name.replace(' Tank', ''),
|
.setDescription('Tech tree type')
|
||||||
value: type,
|
.addChoices(
|
||||||
}),
|
{
|
||||||
),
|
name: TREE_TYPE_NAMES['researchable'],
|
||||||
)
|
value: 'researchable' satisfies TreeTypeString,
|
||||||
.setRequired(false),
|
},
|
||||||
)
|
{
|
||||||
.addStringOption((option) =>
|
name: TREE_TYPE_NAMES.premium,
|
||||||
option
|
value: 'premium' satisfies TreeTypeString,
|
||||||
.setName('tree-type')
|
},
|
||||||
.setDescription('Tech tree type')
|
{
|
||||||
.addChoices(
|
name: TREE_TYPE_NAMES.collector,
|
||||||
{
|
value: 'collector' satisfies TreeTypeString,
|
||||||
name: TREE_TYPE_NAMES['researchable'],
|
},
|
||||||
value: 'researchable' satisfies TreeTypeString,
|
)
|
||||||
},
|
.setRequired(false),
|
||||||
{
|
)
|
||||||
name: TREE_TYPE_NAMES.premium,
|
.addStringOption((option) =>
|
||||||
value: 'premium' satisfies TreeTypeString,
|
addTankChoices(option).setRequired(false),
|
||||||
},
|
) as Option;
|
||||||
{
|
|
||||||
name: TREE_TYPE_NAMES.collector,
|
|
||||||
value: 'collector' satisfies TreeTypeString,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.setRequired(false),
|
|
||||||
)
|
|
||||||
.addStringOption((option) => addTankChoices(option).setRequired(false));
|
|
||||||
});
|
|
||||||
|
|
||||||
return option;
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import {
|
||||||
|
SlashCommandBuilder,
|
||||||
|
SlashCommandSubcommandBuilder,
|
||||||
|
SlashCommandSubcommandGroupBuilder,
|
||||||
|
} from 'discord.js';
|
||||||
|
import { encyclopediaInfo } from '../blitz/encyclopediaInfo';
|
||||||
|
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 awaitedEncyclopediaInfo = await encyclopediaInfo;
|
||||||
|
|
||||||
|
addPeriodSubCommands(option, (option) => {
|
||||||
|
return addFilterOptions(extra(option), awaitedEncyclopediaInfo);
|
||||||
|
});
|
||||||
|
|
||||||
|
return option;
|
||||||
|
}
|
||||||
@@ -3,12 +3,17 @@ import { PeriodType } from './addPeriodSubCommands';
|
|||||||
|
|
||||||
export function getCustomPeriodParams(
|
export function getCustomPeriodParams(
|
||||||
interaction: ChatInputCommandInteraction,
|
interaction: ChatInputCommandInteraction,
|
||||||
|
forcedNonCustomPeriod = false,
|
||||||
) {
|
) {
|
||||||
if ((interaction.options.getSubcommand() as PeriodType) === 'custom')
|
if (
|
||||||
|
!forcedNonCustomPeriod &&
|
||||||
|
(interaction.options.getSubcommand() as PeriodType) === 'custom'
|
||||||
|
) {
|
||||||
return {
|
return {
|
||||||
start: interaction.options.getInteger('start', true),
|
start: interaction.options.getInteger('start', true),
|
||||||
end: interaction.options.getInteger('end', true),
|
end: interaction.options.getInteger('end', true),
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { ChatInputCommandInteraction } from 'discord.js';
|
import { ChatInputCommandInteraction } from 'discord.js';
|
||||||
|
import markdownEscape from 'markdown-escape';
|
||||||
import { Region } from '../../constants/regions';
|
import { Region } from '../../constants/regions';
|
||||||
import searchClansAcrossRegions from '../blitz/searchClansAcrossRegions';
|
import searchClansAcrossRegions from '../blitz/searchClansAcrossRegions';
|
||||||
import { UserError } from '../blitzkrieg/userError';
|
import { UserError } from '../blitzkrieg/userError';
|
||||||
@@ -18,9 +19,9 @@ export default async function resolveClanFromCommand(
|
|||||||
if (accounts[0]) {
|
if (accounts[0]) {
|
||||||
return { region: accounts[0].region, id: accounts[0].clan_id };
|
return { region: accounts[0].region, id: accounts[0].clan_id };
|
||||||
} else {
|
} else {
|
||||||
throw new UserError('Could not find clan', {
|
throw new UserError(
|
||||||
cause: `I couldn't find clan \`${clan}\`. Try selecting a username from the search result.`,
|
`# Could not find clan\nCouldn't find "${markdownEscape(clan)}". Try selecting a clan from the search result.`,
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -20,11 +20,14 @@ export interface ResolvedPeriod {
|
|||||||
export default function resolvePeriodFromCommand(
|
export default function resolvePeriodFromCommand(
|
||||||
region: Region,
|
region: Region,
|
||||||
interaction: ChatInputCommandInteraction<CacheType>,
|
interaction: ChatInputCommandInteraction<CacheType>,
|
||||||
|
forcedPeriod?: PeriodType,
|
||||||
) {
|
) {
|
||||||
let name: string;
|
let name: string;
|
||||||
let start: number;
|
let start: number;
|
||||||
let end: number;
|
let end: number;
|
||||||
const periodSubcommand = interaction.options.getSubcommand() as PeriodType;
|
const periodSubcommand = forcedPeriod
|
||||||
|
? forcedPeriod
|
||||||
|
: (interaction.options.getSubcommand() as PeriodType);
|
||||||
|
|
||||||
if (periodSubcommand === 'custom') {
|
if (periodSubcommand === 'custom') {
|
||||||
const startOption = interaction.options.getInteger('start', true);
|
const startOption = interaction.options.getInteger('start', true);
|
||||||
|
|||||||
@@ -34,11 +34,11 @@ export default async function resolvePlayerFromCommand(
|
|||||||
id: accounts[0].account_id,
|
id: accounts[0].account_id,
|
||||||
} satisfies ResolvedPlayer;
|
} satisfies ResolvedPlayer;
|
||||||
} else {
|
} else {
|
||||||
throw new UserError('Could not find user', {
|
throw new UserError(
|
||||||
cause: `I couldn't find user "${markdownEscape(
|
`# Could not find user\nI couldn't find user "${markdownEscape(
|
||||||
commandUsername,
|
commandUsername,
|
||||||
)}". Try picking an user from the search result, typing in a valid username, or using the \`/link\` command.`,
|
)}". Try picking an user from the search result, typing in a valid username, or using the \`/link\` command.`,
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -47,9 +47,9 @@ export default async function resolvePlayerFromCommand(
|
|||||||
if (account) {
|
if (account) {
|
||||||
return { region: account.region, id: account.blitz };
|
return { region: account.region, id: account.blitz };
|
||||||
} else {
|
} else {
|
||||||
throw new UserError('Use the `/link` command', {
|
throw new UserError(
|
||||||
cause: 'Link your Blitz and Discord accounts to get started.',
|
"# I don't know who you are\nUse the `/link` command to get started.",
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,13 +4,17 @@ import {
|
|||||||
emptyIndividualTankStats,
|
emptyIndividualTankStats,
|
||||||
} from '../../types/tanksStats';
|
} from '../../types/tanksStats';
|
||||||
|
|
||||||
export function deltaTankStats(a: NormalizedTankStats, b: NormalizedTankStats) {
|
export function deltaTankStats(
|
||||||
|
a: NormalizedTankStats,
|
||||||
|
b: NormalizedTankStats,
|
||||||
|
time?: number,
|
||||||
|
) {
|
||||||
const bTanks = Object.keys(b);
|
const bTanks = Object.keys(b);
|
||||||
const changedTanks = bTanks.filter((id) => {
|
const changedTanks = bTanks.filter((id) => {
|
||||||
return (
|
return time === undefined
|
||||||
a[id as unknown as number]?.all.battles !==
|
? a[id as unknown as number]?.all.battles !==
|
||||||
b[id as unknown as number].all.battles
|
b[id as unknown as number].all.battles
|
||||||
);
|
: b[id as unknown as number].last_battle_time > time;
|
||||||
});
|
});
|
||||||
const diff = changedTanks.map((id) => {
|
const diff = changedTanks.map((id) => {
|
||||||
const aTank = a[id as unknown as number] ?? emptyIndividualTankStats;
|
const aTank = a[id as unknown as number] ?? emptyIndividualTankStats;
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import { commands } from '..';
|
|||||||
import discord from '../../../../discord.json' assert { type: 'json' };
|
import discord from '../../../../discord.json' assert { type: 'json' };
|
||||||
import { UserError } from '../../../core/blitzkrieg/userError';
|
import { UserError } from '../../../core/blitzkrieg/userError';
|
||||||
import buttonLink from '../../../core/discord/buttonLink';
|
import buttonLink from '../../../core/discord/buttonLink';
|
||||||
import embedNegative from '../../../core/discord/embedNegative';
|
|
||||||
import embedWarning from '../../../core/discord/embedWarning';
|
import embedWarning from '../../../core/discord/embedWarning';
|
||||||
import normalizeInteractionReturnable from '../../../core/discord/normalizeInteractionReturnable';
|
import normalizeInteractionReturnable from '../../../core/discord/normalizeInteractionReturnable';
|
||||||
import { psa } from '../../../core/discord/psa';
|
import { psa } from '../../../core/discord/psa';
|
||||||
@@ -19,7 +18,8 @@ import { psa } from '../../../core/discord/psa';
|
|||||||
export default async function handleChatInputCommand(
|
export default async function handleChatInputCommand(
|
||||||
interaction: ChatInputCommandInteraction<CacheType>,
|
interaction: ChatInputCommandInteraction<CacheType>,
|
||||||
) {
|
) {
|
||||||
const registry = (await commands)[interaction.commandName];
|
const awaitedCommands = await commands;
|
||||||
|
const registry = awaitedCommands[interaction.commandName];
|
||||||
|
|
||||||
await interaction.deferReply();
|
await interaction.deferReply();
|
||||||
|
|
||||||
@@ -118,25 +118,15 @@ export default async function handleChatInputCommand(
|
|||||||
|
|
||||||
if (error instanceof UserError) {
|
if (error instanceof UserError) {
|
||||||
interaction.editReply({
|
interaction.editReply({
|
||||||
embeds: [
|
content: error.message,
|
||||||
embedNegative(
|
|
||||||
error.message,
|
|
||||||
(error.cause as string | undefined) ??
|
|
||||||
'No further information is available.',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
components,
|
components,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.error(interaction.commandName, error);
|
console.error(interaction.commandName, error);
|
||||||
|
|
||||||
interaction.editReply({
|
interaction.editReply({
|
||||||
embeds: [
|
content:
|
||||||
embedNegative(
|
"# Blitzkrieg ran into an error!\nWe're so sorry about this. Feel free to [join the official Discord server](https://discord.gg/nDt7AjGJQH) to get help.",
|
||||||
'Blitzkrieg ran into an error!',
|
|
||||||
"We're so sorry about this. Feel free to [join the official Discord server](https://discord.gg/nDt7AjGJQH) to get help.",
|
|
||||||
),
|
|
||||||
],
|
|
||||||
components,
|
components,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import { fullStatsCommand } from '../../commands/fullStats';
|
|||||||
import { inactiveCommand } from '../../commands/inactive';
|
import { inactiveCommand } from '../../commands/inactive';
|
||||||
import { verifyCommand } from '../../commands/link';
|
import { verifyCommand } from '../../commands/link';
|
||||||
import { ownedTanksCommand } from '../../commands/ownedTanks';
|
import { ownedTanksCommand } from '../../commands/ownedTanks';
|
||||||
|
import { permissionsCommand } from '../../commands/permissions';
|
||||||
import { pingCommand } from '../../commands/ping';
|
import { pingCommand } from '../../commands/ping';
|
||||||
import { playerAchievementsCommand } from '../../commands/playerAchievements';
|
import { playerAchievementsCommand } from '../../commands/playerAchievements';
|
||||||
import { playerInfoCommand } from '../../commands/playerInfo';
|
import { playerInfoCommand } from '../../commands/playerInfo';
|
||||||
@@ -31,6 +32,7 @@ import { searchClansCommand } from '../../commands/searchClans';
|
|||||||
import { searchPlayersCommand } from '../../commands/searchPlayers';
|
import { searchPlayersCommand } from '../../commands/searchPlayers';
|
||||||
import { searchTanksCommand } from '../../commands/searchTanks';
|
import { searchTanksCommand } from '../../commands/searchTanks';
|
||||||
import { statsCommand } from '../../commands/stats';
|
import { statsCommand } from '../../commands/stats';
|
||||||
|
import { todayCommand } from '../../commands/today';
|
||||||
import getClientId from '../../core/blitzkrieg/getClientId';
|
import getClientId from '../../core/blitzkrieg/getClientId';
|
||||||
import isDev from '../../core/blitzkrieg/isDev';
|
import isDev from '../../core/blitzkrieg/isDev';
|
||||||
import { secrets } from '../../core/blitzkrieg/secrets';
|
import { secrets } from '../../core/blitzkrieg/secrets';
|
||||||
@@ -52,38 +54,44 @@ export type InteractionReturnable =
|
|||||||
| InteractionIterableReturnable
|
| InteractionIterableReturnable
|
||||||
| Promise<InteractionIterableReturnable>;
|
| Promise<InteractionIterableReturnable>;
|
||||||
|
|
||||||
export interface Registry {
|
type CommandRegistryBase = {
|
||||||
inProduction: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type CommandRegistryRaw = Registry & {
|
|
||||||
inPublic: boolean;
|
|
||||||
inPreview?: boolean;
|
|
||||||
|
|
||||||
command:
|
command:
|
||||||
| SlashCommandBuilder
|
| SlashCommandBuilder
|
||||||
| SlashCommandSubcommandsOnlyBuilder
|
| SlashCommandSubcommandsOnlyBuilder
|
||||||
| Omit<SlashCommandBuilder, 'addSubcommand' | 'addSubcommandGroup'>;
|
| Omit<SlashCommandBuilder, 'addSubcommand' | 'addSubcommandGroup'>;
|
||||||
|
};
|
||||||
|
type CommandRegistryDefinitionBase = {
|
||||||
|
inProduction: boolean;
|
||||||
|
inPublic: boolean;
|
||||||
|
inPreview?: boolean;
|
||||||
|
|
||||||
autocomplete?: (interaction: AutocompleteInteraction<CacheType>) => void;
|
autocomplete?: (interaction: AutocompleteInteraction<CacheType>) => void;
|
||||||
button?: (interaction: ButtonInteraction<CacheType>) => InteractionReturnable;
|
button?: (interaction: ButtonInteraction<CacheType>) => InteractionReturnable;
|
||||||
} & (
|
};
|
||||||
| {
|
type CommandRegistryDefinitionHandlesInteraction = {
|
||||||
handlesInteraction: true;
|
handlesInteraction: true;
|
||||||
handler: (interaction: ChatInputCommandInteraction<CacheType>) => void;
|
handler: (interaction: ChatInputCommandInteraction<CacheType>) => void;
|
||||||
}
|
};
|
||||||
| {
|
type CommandRegistryDefinitionDoesHandleInteraction = {
|
||||||
handlesInteraction?: false;
|
handlesInteraction?: false;
|
||||||
handler: (
|
handler: (
|
||||||
interaction: ChatInputCommandInteraction<CacheType>,
|
interaction: ChatInputCommandInteraction<CacheType>,
|
||||||
) => InteractionReturnable;
|
) => InteractionReturnable;
|
||||||
}
|
};
|
||||||
);
|
export type CommandRegistryPromisable = CommandRegistryBase &
|
||||||
export type CommandRegistry = CommandRegistryRaw | Promise<CommandRegistryRaw>;
|
(CommandRegistryDefinitionBase &
|
||||||
|
(
|
||||||
|
| CommandRegistryDefinitionHandlesInteraction
|
||||||
|
| CommandRegistryDefinitionDoesHandleInteraction
|
||||||
|
));
|
||||||
|
export type CommandRegistry =
|
||||||
|
| CommandRegistryPromisable
|
||||||
|
| Promise<CommandRegistryPromisable>;
|
||||||
|
|
||||||
const rest = new REST().setToken(secrets.DISCORD_TOKEN);
|
const rest = new REST().setToken(secrets.DISCORD_TOKEN);
|
||||||
|
|
||||||
export const COMMANDS_RAW: CommandRegistry[] = [
|
export const COMMANDS_RAW: CommandRegistry[] = [
|
||||||
|
permissionsCommand,
|
||||||
debugCommand,
|
debugCommand,
|
||||||
eligibleCommand,
|
eligibleCommand,
|
||||||
aboutCommand,
|
aboutCommand,
|
||||||
@@ -101,10 +109,11 @@ export const COMMANDS_RAW: CommandRegistry[] = [
|
|||||||
evolutionCommand,
|
evolutionCommand,
|
||||||
statsCommand,
|
statsCommand,
|
||||||
ratingsCommand,
|
ratingsCommand,
|
||||||
|
todayCommand,
|
||||||
];
|
];
|
||||||
|
|
||||||
export const commands = Promise.all(COMMANDS_RAW).then((rawCommands) =>
|
export const commands = Promise.all(COMMANDS_RAW).then((rawCommands) =>
|
||||||
rawCommands.reduce<Record<string, CommandRegistryRaw>>(
|
rawCommands.reduce<Record<string, CommandRegistryPromisable>>(
|
||||||
(accumulator, registry) => {
|
(accumulator, registry) => {
|
||||||
if (isDev()) registry.command.setDefaultMemberPermissions(0);
|
if (isDev()) registry.command.setDefaultMemberPermissions(0);
|
||||||
|
|
||||||
@@ -123,7 +132,7 @@ export const publicCommands: RESTPostAPIChatInputApplicationCommandsJSONBody[] =
|
|||||||
[];
|
[];
|
||||||
|
|
||||||
commands.then((awaitedCommands) => {
|
commands.then((awaitedCommands) => {
|
||||||
Object.entries(awaitedCommands).forEach(([, registry]) => {
|
Object.values(awaitedCommands).forEach((registry) => {
|
||||||
const json = registry.command.toJSON();
|
const json = registry.command.toJSON();
|
||||||
|
|
||||||
if (registry.inPublic) {
|
if (registry.inPublic) {
|
||||||
|
|||||||
Reference in new issue
Block a user