Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06ae024c2c | ||
|
|
ce8fd0f22f | ||
|
|
7dc1aff1dc | ||
|
|
a60003127f | ||
|
|
7d1ba58806 | ||
|
|
963d71e221 | ||
|
|
2a804fda1c | ||
|
|
597a1a04d2 | ||
|
|
aec6f49ea1 | ||
|
|
e1ad710de5 | ||
|
|
edc8447617 | ||
|
|
f726d4f8e8 | ||
|
|
1f333f6b88 | ||
|
|
9b855fca47 | ||
|
|
c5f433fdf4 | ||
|
|
fd61dae1c1 | ||
|
|
2c45f9759f | ||
|
|
0d3261f748 | ||
|
|
d38e873800 | ||
|
|
ca38914095 | ||
|
|
ee5beeea45 | ||
|
|
a9d40d9f49 | ||
|
|
b8be916c18 | ||
|
|
24d0313fd3 | ||
|
|
c3d2a81327 | ||
|
|
dfeb117663 | ||
|
|
495a830506 | ||
|
|
a730f67ca5 | ||
|
|
9a3867770d | ||
|
|
f8a66bb5e7 | ||
|
|
4b00e1342e | ||
|
|
f9e0584f00 | ||
|
|
900c290e75 | ||
|
|
69e1577d18 | ||
|
|
e3864b86a4 |
No files matched your search
@@ -29,4 +29,4 @@ jobs:
|
||||
unzip bot.zip
|
||||
|
||||
- name: Run bot
|
||||
run: NODE_ENV=production GH_TOKEN=${{ secrets.GH_TOKEN }} DISCORD_TOKEN=${{ secrets.DISCORD_TOKEN }} node --enable-source-maps index.cjs
|
||||
run: NODE_ENV=production GH_TOKEN=${{ secrets.GH_TOKEN }} DISCORD_TOKEN=${{ secrets.DISCORD_TOKEN }} DISCORD_BLITZ_DB=${{ secrets.DISCORD_BLITZ_DB }} node --enable-source-maps index.cjs
|
||||
@@ -15,6 +15,7 @@
|
||||
"kranvagn",
|
||||
"Leaderboard",
|
||||
"leaderboards",
|
||||
"Oopsy",
|
||||
"outdir",
|
||||
"outfile",
|
||||
"ownedtanks",
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# Blitzkrieg 1.14.1
|
||||
|
||||
Oopsy daisy, a memory leak!
|
||||
|
||||
## Changes
|
||||
|
||||
- Simplified message for users to use `/link`
|
||||
|
||||
## Fixes
|
||||
|
||||
- Fixed a memory leak in the render thread manager
|
||||
|
||||
## Technical changes
|
||||
|
||||
- Added `UserError` extending `Error`
|
||||
- This lets the bot avoid spamming the debug console for mistakes users make
|
||||
- Reduced render threads to 2
|
||||
@@ -0,0 +1,8 @@
|
||||
# Blitzkrieg 1.14.2
|
||||
|
||||
A late night hot fix for my homies
|
||||
|
||||
## Fixes
|
||||
|
||||
- Clan info is now fetched using `clan_id` instead of `account_id`
|
||||
- Generic stats row names are now bounded by a max width
|
||||
@@ -0,0 +1,24 @@
|
||||
# Blitzkrieg 1.14.3
|
||||
|
||||
Pull your brush out and start polishing.
|
||||
|
||||
## Changes
|
||||
|
||||

|
||||
|
||||
- Rows in `/breakdown` are now chunked into `8` per image
|
||||
- This is true unless the number of pages exceeds `10` in which case the rows are even distributed across all `10` pages
|
||||
- `10` is the maximum number of images that Discord allows
|
||||
- Short region names are used in username autocomplete instead of their full names
|
||||
- Example: "North America" is "NA" now
|
||||
|
||||
## Fixes
|
||||
|
||||
- Removed reference to username inference in the username option description
|
||||
- Removed an unnecessary type `asd` that did absolutely nothing used nowhere
|
||||
|
||||
## Technical changes
|
||||
|
||||
- Image orders are now preserved even if processed in parallel and all completed out of order
|
||||
- `fetchBlitz` is now throttled to `10` requests per second
|
||||
- Request overflows are managed by queueing
|
||||
@@ -0,0 +1,7 @@
|
||||
# Blitzkrieg 1.14.4
|
||||
|
||||
Streamers get some knowledge today
|
||||
|
||||
## Changes
|
||||
|
||||
- Added a video in the embed button prompt
|
||||
|
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 183 KiB |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "blitzkrieg",
|
||||
"version": "1.14.0",
|
||||
"version": "1.14.4",
|
||||
"description": "🎉 Tools for everything World of Tanks Blitz",
|
||||
"main": "src/server.ts",
|
||||
"type": "module",
|
||||
|
||||
|
Before Width: | Height: | Size: 2.6 MiB After Width: | Height: | Size: 1.7 MiB |
|
Before Width: | Height: | Size: 8.2 MiB After Width: | Height: | Size: 5.4 MiB |
|
Before Width: | Height: | Size: 2.0 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 3.8 MiB After Width: | Height: | Size: 2.4 MiB |
|
Before Width: | Height: | Size: 2.1 MiB After Width: | Height: | Size: 2.1 MiB |
|
Before Width: | Height: | Size: 2.4 MiB After Width: | Height: | Size: 1.3 MiB |
@@ -1,36 +1,44 @@
|
||||
import { build } from 'esbuild';
|
||||
import { readdir } from 'fs/promises';
|
||||
import { cp, readdir } from 'fs/promises';
|
||||
|
||||
const FILES = [
|
||||
'bot',
|
||||
'index',
|
||||
readdir('src/workers').then((workers) => {
|
||||
const files = [
|
||||
'bot',
|
||||
'index',
|
||||
|
||||
(await readdir('src/workers')).map(
|
||||
(file) => `workers/${file.replace('.ts', '')}`,
|
||||
),
|
||||
];
|
||||
...workers.map((file) => `workers/${file.replace('.ts', '')}`),
|
||||
];
|
||||
|
||||
await build({
|
||||
entryPoints: FILES.map((file) => `src/${file}.ts`),
|
||||
outdir: 'dist/bot',
|
||||
build({
|
||||
entryPoints: files.map((file) => `src/${file}.ts`),
|
||||
outdir: 'dist/bot',
|
||||
|
||||
platform: 'node',
|
||||
tsconfig: 'tsconfig.esbuild.json',
|
||||
loader: {
|
||||
'.node': 'copy',
|
||||
'.ttf': 'file',
|
||||
},
|
||||
format: 'cjs',
|
||||
jsx: 'transform',
|
||||
outExtension: {
|
||||
'.js': '.cjs',
|
||||
},
|
||||
platform: 'node',
|
||||
tsconfig: 'tsconfig.esbuild.json',
|
||||
loader: {
|
||||
'.node': 'copy',
|
||||
'.ttf': 'file',
|
||||
},
|
||||
format: 'cjs',
|
||||
jsx: 'transform',
|
||||
outExtension: {
|
||||
'.js': '.cjs',
|
||||
},
|
||||
|
||||
bundle: true,
|
||||
sourcemap: true,
|
||||
minifyIdentifiers: false, // cause goofy errors
|
||||
minifySyntax: true,
|
||||
minifyWhitespace: true,
|
||||
bundle: true,
|
||||
sourcemap: true,
|
||||
minifyIdentifiers: false, // causes goofy errors
|
||||
minifySyntax: true,
|
||||
minifyWhitespace: true,
|
||||
|
||||
logLevel: 'info',
|
||||
logLevel: 'info',
|
||||
});
|
||||
});
|
||||
|
||||
// copy schema.prisma
|
||||
cp('prisma/schema.prisma', 'dist/bot/schema.prisma');
|
||||
readdir('node_modules/prisma').then((files) =>
|
||||
files
|
||||
.filter((file) => file.endsWith('.node'))
|
||||
.map((file) => cp(`node_modules/prisma/${file}`, `dist/bot/${file}`)),
|
||||
);
|
||||
@@ -5,7 +5,7 @@ import { useEffect, useState } from 'react';
|
||||
import * as Breakdown from '../../../components/Breakdown';
|
||||
import { TreeTypeEnum } from '../../../components/Tanks';
|
||||
import { WARGAMING_APPLICATION_ID } from '../../../constants/wargamingApplicationID';
|
||||
import fetchBlitz from '../../../core/blitz/fetchWargaming';
|
||||
import fetchBlitz from '../../../core/blitz/fetchBlitz';
|
||||
import { tankAverages } from '../../../core/blitzstars/tankAverages';
|
||||
import {
|
||||
TankopediaEntry,
|
||||
|
||||
@@ -124,7 +124,7 @@ export default function Page() {
|
||||
<Button
|
||||
variant="soft"
|
||||
onClick={(event) => {
|
||||
if (!session.promptBeforeReset) {
|
||||
if (!session.resetPrompt) {
|
||||
event.preventDefault();
|
||||
resetSession();
|
||||
}
|
||||
@@ -153,7 +153,7 @@ export default function Page() {
|
||||
mr="1"
|
||||
onCheckedChange={(checked) =>
|
||||
useSession.setState({
|
||||
promptBeforeReset: !checked,
|
||||
resetPrompt: !checked,
|
||||
})
|
||||
}
|
||||
/>
|
||||
@@ -183,7 +183,7 @@ export default function Page() {
|
||||
variant="soft"
|
||||
className={styles.toolbarButton}
|
||||
onClick={(event) => {
|
||||
if (!session.showEmbedPrompt) {
|
||||
if (!session.embedPrompt) {
|
||||
event.preventDefault();
|
||||
resetSession();
|
||||
}
|
||||
@@ -202,14 +202,17 @@ export default function Page() {
|
||||
Copied link to your clipboard
|
||||
</AlertDialog.Title>
|
||||
<AlertDialog.Description>
|
||||
Keep in mind:
|
||||
<ul>
|
||||
<li>Paste this link into your browser to view the embed</li>
|
||||
<li>Embeds abide your custom options</li>
|
||||
<li>
|
||||
Options do not transfer across websites/streaming softwares
|
||||
</li>
|
||||
</ul>
|
||||
Instructions for streamers:
|
||||
<iframe
|
||||
style={{
|
||||
width: '100%',
|
||||
aspectRatio: '16 / 9',
|
||||
border: 'none',
|
||||
}}
|
||||
src="https://www.youtube-nocookie.com/embed/e--GwjyrJAE?si=ftEiCmpbMI2FUY5N"
|
||||
title="YouTube video player"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
></iframe>
|
||||
</AlertDialog.Description>
|
||||
|
||||
<Text size="2">
|
||||
@@ -217,7 +220,7 @@ export default function Page() {
|
||||
mr="1"
|
||||
onCheckedChange={(checked) =>
|
||||
useSession.setState({
|
||||
showEmbedPrompt: !checked,
|
||||
embedPrompt: !checked,
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { SlashCommandBuilder } from 'discord.js';
|
||||
import { chunk } from 'lodash';
|
||||
import * as Breakdown from '../components/Breakdown';
|
||||
import NoData, { NoDataType } from '../components/NoData';
|
||||
import TitleBar from '../components/TitleBar';
|
||||
@@ -35,6 +36,9 @@ import { StatFilters, filterStats } from '../core/statistics/filterStats';
|
||||
import getWN8Percentile from '../core/statistics/getWN8Percentile';
|
||||
import { CommandRegistryRaw } from '../events/interactionCreate';
|
||||
|
||||
const ROWS_PER_PAGE = 8;
|
||||
const MAX_PAGES = 10;
|
||||
|
||||
async function render(
|
||||
{ region, id }: ResolvedPlayer,
|
||||
{ start, end, name }: ResolvedPeriod,
|
||||
@@ -132,38 +136,80 @@ async function render(
|
||||
0,
|
||||
) / careerBattles;
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<TitleBar
|
||||
name={accountInfo.nickname}
|
||||
image={
|
||||
clanData?.clan
|
||||
? `https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clanData?.clan?.emblem_set_id}.png`
|
||||
: undefined
|
||||
}
|
||||
description={`${name} • ${filterDescriptions}`}
|
||||
/>
|
||||
const children: JSX.Element[] = [];
|
||||
|
||||
{filteredOrder.length === 0 && (
|
||||
<NoData type={NoDataType.BattlesInPeriod} />
|
||||
)}
|
||||
{filteredOrder.length > 0 && (
|
||||
<Breakdown.Root>
|
||||
if (filteredOrder.length > 0) {
|
||||
children.push(
|
||||
<Breakdown.Row
|
||||
key={id}
|
||||
type="summary"
|
||||
title="Total"
|
||||
stats={[
|
||||
{
|
||||
title: 'Battles',
|
||||
current: currentBattles,
|
||||
career: careerBattles,
|
||||
},
|
||||
{
|
||||
title: 'Winrate',
|
||||
current: `${(100 * currentWinrate).toFixed(2)}%`,
|
||||
career: `${(100 * careerWinrate).toFixed(2)}%`,
|
||||
delta: currentWinrate - careerWinrate,
|
||||
},
|
||||
{
|
||||
title: 'WN8',
|
||||
current:
|
||||
currentWN8 === undefined
|
||||
? undefined
|
||||
: Math.round(currentWN8).toLocaleString(),
|
||||
career:
|
||||
careerWN8 === undefined
|
||||
? undefined
|
||||
: Math.round(careerWN8).toLocaleString(),
|
||||
percentile:
|
||||
currentWN8 === undefined
|
||||
? undefined
|
||||
: getWN8Percentile(currentWN8),
|
||||
},
|
||||
{
|
||||
title: 'Damage',
|
||||
current: Math.round(currentDamage).toLocaleString(),
|
||||
career: Math.round(careerDamage).toLocaleString(),
|
||||
delta: currentDamage - careerDamage,
|
||||
},
|
||||
]}
|
||||
/>,
|
||||
);
|
||||
|
||||
await Promise.all(
|
||||
filteredOrder.map(async (id, index) => {
|
||||
const tankopediaEntry = awaitedTankopedia[id];
|
||||
const current = orderedCurrentStats[index];
|
||||
const career = orderedCareerStats[index];
|
||||
const currentWN8 = orderedCurrentWN8[index];
|
||||
const careerWN8 = orderedCareerWN8[index];
|
||||
|
||||
children.push(
|
||||
<Breakdown.Row
|
||||
key={id}
|
||||
type="summary"
|
||||
title="Total"
|
||||
type="tank"
|
||||
tankType={tankopediaEntry?.type}
|
||||
treeType={tankopediaEntry ? await getTreeType(id) : undefined}
|
||||
title={await resolveTankName(id)}
|
||||
stats={[
|
||||
{
|
||||
title: 'Battles',
|
||||
current: currentBattles,
|
||||
career: careerBattles,
|
||||
current: current.battles.toLocaleString(),
|
||||
career: career.battles.toLocaleString(),
|
||||
},
|
||||
{
|
||||
title: 'Winrate',
|
||||
current: `${(100 * currentWinrate).toFixed(2)}%`,
|
||||
career: `${(100 * careerWinrate).toFixed(2)}%`,
|
||||
delta: currentWinrate - careerWinrate,
|
||||
current: `${(100 * (current.wins / current.battles)).toFixed(
|
||||
2,
|
||||
)}%`,
|
||||
career: `${(100 * (career.wins / career.battles)).toFixed(2)}%`,
|
||||
delta:
|
||||
current.wins / current.battles - career.wins / career.battles,
|
||||
},
|
||||
{
|
||||
title: 'WN8',
|
||||
@@ -182,83 +228,67 @@ async function render(
|
||||
},
|
||||
{
|
||||
title: 'Damage',
|
||||
current: Math.round(currentDamage).toLocaleString(),
|
||||
career: Math.round(careerDamage).toLocaleString(),
|
||||
delta: currentDamage - careerDamage,
|
||||
current: Math.round(
|
||||
current.damage_dealt / current.battles,
|
||||
).toLocaleString(),
|
||||
career: Math.round(
|
||||
career.damage_dealt / career.battles,
|
||||
).toLocaleString(),
|
||||
delta:
|
||||
current.damage_dealt / current.battles -
|
||||
career.damage_dealt / career.battles,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
/>,
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
{await Promise.all(
|
||||
filteredOrder.map(async (id, index) => {
|
||||
const tankopediaEntry = awaitedTankopedia[id];
|
||||
const current = orderedCurrentStats[index];
|
||||
const career = orderedCareerStats[index];
|
||||
const currentWN8 = orderedCurrentWN8[index];
|
||||
const careerWN8 = orderedCareerWN8[index];
|
||||
|
||||
return (
|
||||
<Breakdown.Row
|
||||
key={id}
|
||||
type="tank"
|
||||
tankType={tankopediaEntry?.type}
|
||||
treeType={tankopediaEntry ? await getTreeType(id) : undefined}
|
||||
title={await resolveTankName(id)}
|
||||
stats={[
|
||||
{
|
||||
title: 'Battles',
|
||||
current: current.battles.toLocaleString(),
|
||||
career: career.battles.toLocaleString(),
|
||||
},
|
||||
{
|
||||
title: 'Winrate',
|
||||
current: `${(
|
||||
100 *
|
||||
(current.wins / current.battles)
|
||||
).toFixed(2)}%`,
|
||||
career: `${(100 * (career.wins / career.battles)).toFixed(
|
||||
2,
|
||||
)}%`,
|
||||
delta:
|
||||
current.wins / current.battles -
|
||||
career.wins / career.battles,
|
||||
},
|
||||
{
|
||||
title: 'WN8',
|
||||
current:
|
||||
currentWN8 === undefined
|
||||
? undefined
|
||||
: Math.round(currentWN8).toLocaleString(),
|
||||
career:
|
||||
careerWN8 === undefined
|
||||
? undefined
|
||||
: Math.round(careerWN8).toLocaleString(),
|
||||
percentile:
|
||||
currentWN8 === undefined
|
||||
? undefined
|
||||
: getWN8Percentile(currentWN8),
|
||||
},
|
||||
{
|
||||
title: 'Damage',
|
||||
current: Math.round(
|
||||
current.damage_dealt / current.battles,
|
||||
).toLocaleString(),
|
||||
career: Math.round(
|
||||
career.damage_dealt / career.battles,
|
||||
).toLocaleString(),
|
||||
delta:
|
||||
current.damage_dealt / current.battles -
|
||||
career.damage_dealt / career.battles,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}),
|
||||
)}
|
||||
</Breakdown.Root>
|
||||
)}
|
||||
</Wrapper>
|
||||
const pages = chunk(
|
||||
children,
|
||||
Math.max(Math.ceil(children.length / MAX_PAGES), ROWS_PER_PAGE),
|
||||
);
|
||||
|
||||
if (filteredOrder.length > 0) {
|
||||
return pages
|
||||
.map((page, index) => (
|
||||
<Wrapper>
|
||||
{index === 0 && (
|
||||
<TitleBar
|
||||
name={accountInfo.nickname}
|
||||
image={
|
||||
clanData?.clan
|
||||
? `https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clanData?.clan?.emblem_set_id}.png`
|
||||
: undefined
|
||||
}
|
||||
description={`${name} • ${filterDescriptions}`}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Breakdown.Root>{page}</Breakdown.Root>
|
||||
</Wrapper>
|
||||
))
|
||||
.reverse();
|
||||
} else {
|
||||
return [
|
||||
<Wrapper>
|
||||
<TitleBar
|
||||
name={accountInfo.nickname}
|
||||
image={
|
||||
clanData?.clan
|
||||
? `https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clanData?.clan?.emblem_set_id}.png`
|
||||
: undefined
|
||||
}
|
||||
description={`${name} • ${filterDescriptions}`}
|
||||
/>
|
||||
|
||||
{filteredOrder.length === 0 && (
|
||||
<NoData type={NoDataType.BattlesInPeriod} />
|
||||
)}
|
||||
</Wrapper>,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
export const breakdownCommand = new Promise<CommandRegistryRaw>(
|
||||
@@ -286,11 +316,11 @@ export const breakdownCommand = new Promise<CommandRegistryRaw>(
|
||||
...filters,
|
||||
});
|
||||
|
||||
return Promise.all([
|
||||
render(player, period, filters),
|
||||
return [
|
||||
...(await render(player, period, filters)),
|
||||
buttonPrimary(path, 'Refresh'),
|
||||
getBlitzStarsLinkButton(player.region, player.id),
|
||||
]);
|
||||
await getBlitzStarsLinkButton(player.region, player.id),
|
||||
];
|
||||
},
|
||||
|
||||
autocomplete: (interaction) => {
|
||||
|
||||
@@ -18,7 +18,7 @@ import getMidnightLeaderboard, {
|
||||
DATABASE_REPO,
|
||||
} from '../core/blitzkrieg/getMidnightLeaderboard';
|
||||
import { octokit } from '../core/blitzkrieg/octokit';
|
||||
import throwError from '../core/blitzkrieg/throwError';
|
||||
import { UserError } from '../core/blitzkrieg/userError';
|
||||
import addRegionChoices from '../core/discord/addRegionChoices';
|
||||
import addUsernameChoices from '../core/discord/addUsernameChoices';
|
||||
import autocompleteUsername from '../core/discord/autocompleteUsername';
|
||||
@@ -185,7 +185,7 @@ export const ratingsCommand = new Promise<CommandRegistryRaw>(
|
||||
})
|
||||
.then(({ data }) => {
|
||||
if (!Array.isArray(data)) {
|
||||
throw throwError('Archived ratings data is malformed');
|
||||
throw new Error('Archived ratings data is malformed');
|
||||
}
|
||||
return data.map(
|
||||
(folder) =>
|
||||
@@ -292,7 +292,7 @@ export const ratingsCommand = new Promise<CommandRegistryRaw>(
|
||||
})
|
||||
.then(async ({ data }) => {
|
||||
if (Array.isArray(data) || data.type !== 'file') {
|
||||
throw throwError('Archived ratings data is malformed');
|
||||
throw new Error('Archived ratings data is malformed');
|
||||
}
|
||||
|
||||
const players = (await fetch(data.download_url!).then(
|
||||
@@ -361,7 +361,7 @@ export const ratingsCommand = new Promise<CommandRegistryRaw>(
|
||||
})
|
||||
.then(async ({ data }) => {
|
||||
if (Array.isArray(data) || data.type !== 'file') {
|
||||
throw throwError('Archived ratings info is malformed');
|
||||
throw new Error('Archived ratings info is malformed');
|
||||
}
|
||||
|
||||
const response = fetch(data.download_url!);
|
||||
@@ -404,7 +404,7 @@ export const ratingsCommand = new Promise<CommandRegistryRaw>(
|
||||
})
|
||||
.then(async ({ data }) => {
|
||||
if (Array.isArray(data) || data.type !== 'file') {
|
||||
throw throwError(
|
||||
throw new Error(
|
||||
'Archived ratings latest data is malformed',
|
||||
);
|
||||
}
|
||||
@@ -418,9 +418,12 @@ export const ratingsCommand = new Promise<CommandRegistryRaw>(
|
||||
);
|
||||
|
||||
if (playerIndex === -1) {
|
||||
throw throwError(
|
||||
throw new UserError(
|
||||
`${accountInfo.nickname} didn't player ratings in season ${season}`,
|
||||
'This player did not participate in this season or did not get past calibration.',
|
||||
{
|
||||
cause:
|
||||
'This player did not participate in this season or did not get past calibration.',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { SlashCommandBuilder } from 'discord.js';
|
||||
import markdownEscape from 'markdown-escape';
|
||||
import { REGION_NAMES, Region } from '../constants/regions';
|
||||
import { WARGAMING_APPLICATION_ID } from '../constants/wargamingApplicationID';
|
||||
import fetchBlitz from '../core/blitz/fetchWargaming';
|
||||
import fetchBlitz from '../core/blitz/fetchBlitz';
|
||||
import addClanChoices from '../core/discord/addClanChoices';
|
||||
import addRegionChoices from '../core/discord/addRegionChoices';
|
||||
import embedInfo from '../core/discord/embedInfo';
|
||||
|
||||
@@ -2,7 +2,7 @@ import { SlashCommandBuilder } from 'discord.js';
|
||||
import markdownEscape from 'markdown-escape';
|
||||
import { REGION_NAMES, Region } from '../constants/regions';
|
||||
import { WARGAMING_APPLICATION_ID } from '../constants/wargamingApplicationID';
|
||||
import fetchBlitz from '../core/blitz/fetchWargaming';
|
||||
import fetchBlitz from '../core/blitz/fetchBlitz';
|
||||
import {
|
||||
AccountList,
|
||||
usernamePattern,
|
||||
|
||||
@@ -15,9 +15,18 @@ export default function GenericStatsRow({ stat }: GenericStatsRowProps) {
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<span style={{ color: theme.colors.textLowContrast, fontSize: 16 }}>
|
||||
<span
|
||||
style={{
|
||||
color: theme.colors.textLowContrast,
|
||||
fontSize: 16,
|
||||
whiteSpace: 'nowrap',
|
||||
maxWidth: 128,
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
{stat[0]}
|
||||
</span>
|
||||
|
||||
<span
|
||||
style={{
|
||||
color: theme.colors.textHighContrast,
|
||||
|
||||
@@ -11,7 +11,7 @@ export const REGION_NAMES: Record<Region, string> = {
|
||||
export const REGION_NAMES_SHORT: Record<Region, string> = {
|
||||
com: 'NA',
|
||||
eu: 'EU',
|
||||
asia: 'AS',
|
||||
asia: 'ASIA',
|
||||
};
|
||||
|
||||
export const REGION_SUBDOMAIN_NAMES: Record<RegionSubdomain, string> = {
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
export type BlitzResponse<Data extends object> =
|
||||
| {
|
||||
status: 'error';
|
||||
error: { field: string; message: string; code: 402; value: null };
|
||||
}
|
||||
| {
|
||||
status: 'ok';
|
||||
data: Data;
|
||||
};
|
||||
|
||||
const MAX_CALLS_PER_SECOND = 10;
|
||||
|
||||
const queue: { url: string; resolve: (data: object) => void }[] = [];
|
||||
let inProgress = 0;
|
||||
|
||||
async function manageQueue() {
|
||||
if (queue.length > 0 && inProgress < MAX_CALLS_PER_SECOND) {
|
||||
inProgress++;
|
||||
|
||||
setTimeout(() => {
|
||||
inProgress--;
|
||||
manageQueue();
|
||||
}, 1000);
|
||||
|
||||
const { url, resolve } = queue.shift()!;
|
||||
const data = (await fetch(url)
|
||||
.then((response) => response.json())
|
||||
.catch((error) => ({ status: 'error', error }))) as BlitzResponse<
|
||||
typeof resolve
|
||||
>;
|
||||
|
||||
if (data.status === 'ok') {
|
||||
resolve(data.data);
|
||||
} else {
|
||||
throw new Error(`Wargaming response error status:"${data.status}"`, {
|
||||
cause: `Message: "${data.error.message}"\nURL: "${url}"`,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default function fetchBlitz<Data extends object>(url: string) {
|
||||
return new Promise<Data>((resolve) => {
|
||||
queue.push({
|
||||
url,
|
||||
resolve(data) {
|
||||
resolve(data as Data);
|
||||
},
|
||||
});
|
||||
manageQueue();
|
||||
});
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import throwError from '../blitzkrieg/throwError';
|
||||
|
||||
export type BlitzResponse<Data extends object> =
|
||||
| {
|
||||
status: 'error';
|
||||
error: { field: string; message: string; code: 402; value: null };
|
||||
}
|
||||
| {
|
||||
status: 'ok';
|
||||
data: Data;
|
||||
};
|
||||
|
||||
export default async function fetchBlitz<Data extends object>(url: string) {
|
||||
const data = (await fetch(url).then((response) =>
|
||||
response.json(),
|
||||
)) as BlitzResponse<Data>;
|
||||
|
||||
if (data.status === 'ok') {
|
||||
return data.data;
|
||||
} else {
|
||||
throw throwError(
|
||||
`Wargaming response error status:"${data.status}"`,
|
||||
`Message: "${data.error.message}"\nURL: "${url}"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Region } from '../../constants/regions';
|
||||
import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
|
||||
import fetchBlitz from './fetchWargaming';
|
||||
import fetchBlitz from './fetchBlitz';
|
||||
import { normalizeIds } from './normalizeIds';
|
||||
|
||||
interface IndividualAccountAchievements {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Region } from '../../constants/regions';
|
||||
import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
|
||||
import fetchBlitz from './fetchWargaming';
|
||||
import fetchBlitz from './fetchBlitz';
|
||||
import { normalizeIds } from './normalizeIds';
|
||||
|
||||
export interface AllStats {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Region } from '../../constants/regions';
|
||||
import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
|
||||
import fetchBlitz from './fetchWargaming';
|
||||
import fetchBlitz from './fetchBlitz';
|
||||
import { normalizeExtra } from './normalizeExtra';
|
||||
import { normalizeIds } from './normalizeIds';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Region } from '../../constants/regions';
|
||||
import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
|
||||
import fetchBlitz from './fetchWargaming';
|
||||
import fetchBlitz from './fetchBlitz';
|
||||
import { normalizeIds } from './normalizeIds';
|
||||
|
||||
export interface IndividualClanInfo {
|
||||
@@ -41,7 +41,7 @@ export async function getClanInfo<
|
||||
ReturnType = Ids extends number ? IndividualClanInfo : IndividualClanInfo[],
|
||||
>(region: Region, ids: Ids) {
|
||||
const object = await fetchBlitz<ClanInfo>(
|
||||
`https://api.wotblitz.${region}/wotb/clans/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${normalizeIds(
|
||||
`https://api.wotblitz.${region}/wotb/clans/info/?application_id=${WARGAMING_APPLICATION_ID}&clan_id=${normalizeIds(
|
||||
ids,
|
||||
)}`,
|
||||
);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Region } from '../../constants/regions';
|
||||
import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
|
||||
import { TanksStats } from '../../types/tanksStats';
|
||||
import throwError from '../blitzkrieg/throwError';
|
||||
import fetchBlitz from './fetchWargaming';
|
||||
import { UserError } from '../blitzkrieg/userError';
|
||||
import fetchBlitz from './fetchBlitz';
|
||||
|
||||
export default async function getTankStats(region: Region, id: number) {
|
||||
const tankStats = await fetchBlitz<TanksStats>(
|
||||
@@ -10,10 +10,10 @@ export default async function getTankStats(region: Region, id: number) {
|
||||
);
|
||||
|
||||
if (tankStats[id] === null) {
|
||||
throw throwError(
|
||||
'No tank stats available',
|
||||
"This player doesn't have any stats for tanks available. This may not be the player you are looking for.",
|
||||
);
|
||||
throw new UserError('No tank stats available', {
|
||||
cause:
|
||||
"This player doesn't have any stats for tanks available. This may not be the player you are looking for.",
|
||||
});
|
||||
}
|
||||
|
||||
return tankStats[id];
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { go } from 'fuzzysort';
|
||||
import throwError from '../blitzkrieg/throwError';
|
||||
import { UserError } from '../blitzkrieg/userError';
|
||||
import { TANKS, tankopedia } from '../blitzstars/tankopedia';
|
||||
|
||||
export default async function resolveTankId(tank: string | number) {
|
||||
@@ -12,10 +12,9 @@ export default async function resolveTankId(tank: string | number) {
|
||||
});
|
||||
|
||||
if (searchResult.length === 0) {
|
||||
throw throwError(
|
||||
'Tank not found',
|
||||
`Could not find tank by the name "${tank}".`,
|
||||
);
|
||||
throw new UserError('Tank not found', {
|
||||
cause: `Could not find tank by the name "${tank}".`,
|
||||
});
|
||||
} else {
|
||||
return searchResult[0].obj.tank_id;
|
||||
}
|
||||
@@ -23,10 +22,9 @@ export default async function resolveTankId(tank: string | number) {
|
||||
if ((await tankopedia)[number]) {
|
||||
return number;
|
||||
} else {
|
||||
throw throwError(
|
||||
'Tank not found',
|
||||
`Could not find tank by the ID "${number}".`,
|
||||
);
|
||||
throw new UserError('Tank not found', {
|
||||
cause: `Could not find tank by the ID "${number}".`,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Region } from '../../constants/regions';
|
||||
import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
|
||||
import { Clan, ClanList } from '../../types/clanList';
|
||||
import fetchBlitz from './fetchWargaming';
|
||||
import fetchBlitz from './fetchBlitz';
|
||||
|
||||
export type ClanListWithRegion = (Clan & {
|
||||
region: 'com' | 'eu' | 'asia';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Region } from '../../constants/regions';
|
||||
import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
|
||||
import fetchBlitz from './fetchWargaming';
|
||||
import fetchBlitz from './fetchBlitz';
|
||||
|
||||
interface Account {
|
||||
nickname: string;
|
||||
|
||||
@@ -2,7 +2,6 @@ import { RatingsInfo } from '../../commands/ratings';
|
||||
import { Region } from '../../constants/regions';
|
||||
import { DATABASE_REPO } from './getMidnightLeaderboard';
|
||||
import { octokit } from './octokit';
|
||||
import throwError from './throwError';
|
||||
|
||||
const ARCHIVED_RATINGS_CACHE: Record<
|
||||
Region,
|
||||
@@ -32,7 +31,7 @@ export default async function getArchivedRatingsInfo(
|
||||
});
|
||||
|
||||
if (Array.isArray(data) || data.type !== 'file')
|
||||
throw throwError('Archived ratings info is malformed');
|
||||
throw new Error('Archived ratings info is malformed');
|
||||
|
||||
const content = Buffer.from(data.content, 'base64').toString();
|
||||
const jsonContent = JSON.parse(content) as RatingsInfo & {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Worker } from 'worker_threads';
|
||||
|
||||
const MAX_WORKERS = 3;
|
||||
const MAX_WORKERS = 2;
|
||||
|
||||
const workers = [...Array(MAX_WORKERS)].map(() => {
|
||||
const worker = new Worker(`${__dirname}/workers/render.cjs`);
|
||||
@@ -19,18 +19,22 @@ function manageQueue() {
|
||||
|
||||
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', (png: Uint8Array) => {
|
||||
cleanup();
|
||||
resolve(Buffer.from(png));
|
||||
});
|
||||
worker.on('error', (error) => {
|
||||
cleanup();
|
||||
throw error;
|
||||
});
|
||||
worker.on('message', handleMessage);
|
||||
worker.on('error', handleError);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export default function throwError(message: string, cause?: string) {
|
||||
return new Error(message, { cause });
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Discriminator for user caused errors to not report in logs
|
||||
*/
|
||||
export class UserError extends Error {}
|
||||
@@ -1,7 +1,6 @@
|
||||
import { deburr } from 'lodash';
|
||||
import { TreeTypeString } from '../../components/Tanks';
|
||||
import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
|
||||
import fetchBlitz from '../blitz/fetchWargaming';
|
||||
import fetchBlitz from '../blitz/fetchBlitz';
|
||||
import { context } from '../blitzkrieg/context';
|
||||
|
||||
export interface TankopediaEntry {
|
||||
@@ -16,7 +15,6 @@ export interface TankopediaEntry {
|
||||
type: string;
|
||||
description: string;
|
||||
}
|
||||
type asd = TreeTypeString;
|
||||
export interface Tankopedia {
|
||||
[id: number]: TankopediaEntry | undefined;
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@ import { SlashCommandStringOption } from 'discord.js';
|
||||
export default function addUsernameChoices(option: SlashCommandStringOption) {
|
||||
return option
|
||||
.setName('username')
|
||||
.setDescription(
|
||||
'The username you use in Blitz (inferred from Discord nickname by default)',
|
||||
)
|
||||
.setDescription('The username you use in Blitz')
|
||||
.setAutocomplete(true);
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
AutocompleteInteraction,
|
||||
CacheType,
|
||||
} from 'discord.js';
|
||||
import { REGION_NAMES } from '../../constants/regions';
|
||||
import { REGION_NAMES_SHORT } from '../../constants/regions';
|
||||
import searchPlayersAcrossRegions from '../blitz/searchPlayersAcrossRegions';
|
||||
|
||||
export default async function autocompleteUsername(
|
||||
@@ -19,7 +19,9 @@ export default async function autocompleteUsername(
|
||||
? players.map(
|
||||
(player) =>
|
||||
({
|
||||
name: `${player.nickname} (${REGION_NAMES[player.region]})`,
|
||||
name: `${player.nickname} (${
|
||||
REGION_NAMES_SHORT[player.region]
|
||||
})`,
|
||||
value: `${player.region}/${player.account_id}`,
|
||||
}) satisfies ApplicationCommandOptionChoiceData<string>,
|
||||
)
|
||||
|
||||
@@ -11,6 +11,7 @@ import jsxToPngThreaded from '../blitzkrieg/jsxToPngThreaded';
|
||||
export default async function normalizeInteractionReturnable(
|
||||
returnable: InteractionReturnable,
|
||||
) {
|
||||
const images: [number, Buffer][] = [];
|
||||
const reply: InteractionEditReplyOptions = {};
|
||||
|
||||
const awaitedReturnable = await returnable;
|
||||
@@ -19,7 +20,7 @@ export default async function normalizeInteractionReturnable(
|
||||
: [awaitedReturnable];
|
||||
|
||||
await Promise.all(
|
||||
normalizedReturnable.map(async (item) => {
|
||||
normalizedReturnable.map(async (item, index) => {
|
||||
if (typeof item === 'string') {
|
||||
reply.content = item;
|
||||
} else if (item instanceof EmbedBuilder) {
|
||||
@@ -38,11 +39,16 @@ export default async function normalizeInteractionReturnable(
|
||||
return;
|
||||
} else {
|
||||
const image = await jsxToPngThreaded(item);
|
||||
if (!reply.files) reply.files = [];
|
||||
reply.files.push(image);
|
||||
images.push([index, image]);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
if (images.length > 0) {
|
||||
images.sort(([a], [b]) => b - a);
|
||||
if (!reply.files) reply.files = [];
|
||||
images.forEach(([, image]) => reply.files?.push(image));
|
||||
}
|
||||
|
||||
return reply;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ChatInputCommandInteraction } from 'discord.js';
|
||||
import { Region } from '../../constants/regions';
|
||||
import searchClansAcrossRegions from '../blitz/searchClansAcrossRegions';
|
||||
import throwError from '../blitzkrieg/throwError';
|
||||
import { UserError } from '../blitzkrieg/userError';
|
||||
import { serverAndIdPattern } from './resolvePlayerFromCommand';
|
||||
|
||||
export default async function resolveClanFromCommand(
|
||||
@@ -18,10 +18,9 @@ export default async function resolveClanFromCommand(
|
||||
if (accounts[0]) {
|
||||
return { region: accounts[0].region, id: accounts[0].clan_id };
|
||||
} else {
|
||||
throw throwError(
|
||||
'Could not find clan',
|
||||
`I couldn't find clan \`${clan}\`. Try selecting a username from the search result.`,
|
||||
);
|
||||
throw new UserError('Could not find clan', {
|
||||
cause: `I couldn't find clan \`${clan}\`. Try selecting a username from the search result.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import markdownEscape from 'markdown-escape';
|
||||
import { Region } from '../../constants/regions';
|
||||
import searchPlayersAcrossRegions from '../blitz/searchPlayersAcrossRegions';
|
||||
import { getBlitzFromDiscord } from '../blitzkrieg/discordBlitz';
|
||||
import throwError from '../blitzkrieg/throwError';
|
||||
import { UserError } from '../blitzkrieg/userError';
|
||||
|
||||
export const serverAndIdPattern = /(com|eu|asia)\/[0-9]+/;
|
||||
|
||||
@@ -34,12 +34,11 @@ export default async function resolvePlayerFromCommand(
|
||||
id: accounts[0].account_id,
|
||||
} satisfies ResolvedPlayer;
|
||||
} else {
|
||||
throw throwError(
|
||||
'Could not find user',
|
||||
`I couldn't find user "${markdownEscape(
|
||||
throw new UserError('Could not find user', {
|
||||
cause: `I couldn't find user "${markdownEscape(
|
||||
commandUsername,
|
||||
)}". Try picking an user from the search result, typing in a valid username, or using the \`/link\` command.`,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -48,10 +47,9 @@ export default async function resolvePlayerFromCommand(
|
||||
if (account) {
|
||||
return { region: account.region, id: account.blitz };
|
||||
} else {
|
||||
throw throwError(
|
||||
"You're not linked",
|
||||
'Use the `/link` command to link your Discord and Blitz accounts.',
|
||||
);
|
||||
throw new UserError('Use the `/link` command', {
|
||||
cause: 'Link your Blitz and Discord accounts to get started.',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import { ButtonInteraction, CacheType } from 'discord.js';
|
||||
import { commands } from '..';
|
||||
import throwError from '../../../core/blitzkrieg/throwError';
|
||||
import normalizeInteractionReturnable from '../../../core/discord/normalizeInteractionReturnable';
|
||||
|
||||
export default async function handleButton(
|
||||
@@ -13,8 +12,7 @@ export default async function handleButton(
|
||||
const button = (await commands)[commandName]?.button;
|
||||
|
||||
if (!button) {
|
||||
throwError(`Button handler not found for "${commandName}"`);
|
||||
return;
|
||||
throw new Error(`Button handler not found for "${commandName}"`);
|
||||
}
|
||||
|
||||
if (interaction.message.interaction?.user.id !== interaction.user.id) {
|
||||
|
||||
@@ -7,10 +7,11 @@ import {
|
||||
} from 'discord.js';
|
||||
import { commands } from '..';
|
||||
import discord from '../../../../discord.json' assert { type: 'json' };
|
||||
import { psa } from '../../../core/discord/psa';
|
||||
import { UserError } from '../../../core/blitzkrieg/userError';
|
||||
import embedNegative from '../../../core/discord/embedNegative';
|
||||
import embedWarning from '../../../core/discord/embedWarning';
|
||||
import normalizeInteractionReturnable from '../../../core/discord/normalizeInteractionReturnable';
|
||||
import { psa } from '../../../core/discord/psa';
|
||||
|
||||
export default async function handleChatInputCommand(
|
||||
interaction: ChatInputCommandInteraction<CacheType>,
|
||||
@@ -53,7 +54,9 @@ export default async function handleChatInputCommand(
|
||||
.setStyle(ButtonStyle.Link),
|
||||
);
|
||||
|
||||
console.error(interaction.commandName, error);
|
||||
if (!(error instanceof UserError)) {
|
||||
console.error(interaction.commandName, error);
|
||||
}
|
||||
|
||||
await interaction.editReply({
|
||||
embeds: [
|
||||
|
||||
@@ -4,7 +4,7 @@ import { devtools, persist } from 'zustand/middleware';
|
||||
import { CustomColumnDisplay } from '../app/tools/session/components/CustomColumn';
|
||||
import { Region } from '../constants/regions';
|
||||
import { WARGAMING_APPLICATION_ID } from '../constants/wargamingApplicationID';
|
||||
import fetchBlitz from '../core/blitz/fetchWargaming';
|
||||
import fetchBlitz from '../core/blitz/fetchBlitz';
|
||||
import { NormalizedTankStats, TanksStats } from '../types/tanksStats';
|
||||
|
||||
type Session = (
|
||||
@@ -21,8 +21,8 @@ type Session = (
|
||||
time: number;
|
||||
}
|
||||
) & {
|
||||
promptBeforeReset: boolean;
|
||||
showEmbedPrompt: boolean;
|
||||
resetPrompt: boolean;
|
||||
embedPrompt: boolean;
|
||||
customColumns: {
|
||||
display: CustomColumnDisplay;
|
||||
showDelta: boolean;
|
||||
@@ -67,10 +67,10 @@ export const useSession = create<Session>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
isTracking: false,
|
||||
promptBeforeReset: true,
|
||||
resetPrompt: true,
|
||||
showTotal: true,
|
||||
showCareer: true,
|
||||
showEmbedPrompt: true,
|
||||
embedPrompt: true,
|
||||
|
||||
customColumns: [
|
||||
{ display: 'battles', showDelta: false },
|
||||
|
||||
@@ -185,6 +185,16 @@
|
||||
"@jridgewell/trace-mapping" "^0.3.17"
|
||||
jsesc "^2.5.1"
|
||||
|
||||
"@babel/generator@^7.23.0":
|
||||
version "7.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.0.tgz#df5c386e2218be505b34837acbcb874d7a983420"
|
||||
integrity sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==
|
||||
dependencies:
|
||||
"@babel/types" "^7.23.0"
|
||||
"@jridgewell/gen-mapping" "^0.3.2"
|
||||
"@jridgewell/trace-mapping" "^0.3.17"
|
||||
jsesc "^2.5.1"
|
||||
|
||||
"@babel/helper-compilation-targets@^7.22.15":
|
||||
version "7.22.15"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz#0698fc44551a26cf29f18d4662d5bf545a6cfc52"
|
||||
@@ -201,13 +211,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167"
|
||||
integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==
|
||||
|
||||
"@babel/helper-function-name@^7.22.5":
|
||||
version "7.22.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz#ede300828905bb15e582c037162f99d5183af1be"
|
||||
integrity sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==
|
||||
"@babel/helper-function-name@^7.23.0":
|
||||
version "7.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759"
|
||||
integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==
|
||||
dependencies:
|
||||
"@babel/template" "^7.22.5"
|
||||
"@babel/types" "^7.22.5"
|
||||
"@babel/template" "^7.22.15"
|
||||
"@babel/types" "^7.23.0"
|
||||
|
||||
"@babel/helper-hoist-variables@^7.22.5":
|
||||
version "7.22.5"
|
||||
@@ -291,6 +301,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.16.tgz#180aead7f247305cce6551bea2720934e2fa2c95"
|
||||
integrity sha512-+gPfKv8UWeKKeJTUxe59+OobVcrYHETCsORl61EmSkmgymguYk/X5bp7GuUIXaFsc6y++v8ZxPsLSSuujqDphA==
|
||||
|
||||
"@babel/parser@^7.23.0":
|
||||
version "7.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719"
|
||||
integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==
|
||||
|
||||
"@babel/plugin-syntax-typescript@^7.20.0":
|
||||
version "7.22.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz#aac8d383b062c5072c647a31ef990c1d0af90272"
|
||||
@@ -312,7 +327,7 @@
|
||||
dependencies:
|
||||
regenerator-runtime "^0.14.0"
|
||||
|
||||
"@babel/template@^7.22.15", "@babel/template@^7.22.5":
|
||||
"@babel/template@^7.22.15":
|
||||
version "7.22.15"
|
||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38"
|
||||
integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==
|
||||
@@ -322,18 +337,18 @@
|
||||
"@babel/types" "^7.22.15"
|
||||
|
||||
"@babel/traverse@^7.22.15", "@babel/traverse@^7.22.20":
|
||||
version "7.22.20"
|
||||
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.20.tgz#db572d9cb5c79e02d83e5618b82f6991c07584c9"
|
||||
integrity sha512-eU260mPZbU7mZ0N+X10pxXhQFMGTeLb9eFS0mxehS8HZp9o1uSnFeWQuG1UPrlxgA7QoUzFhOnilHDp0AXCyHw==
|
||||
version "7.23.2"
|
||||
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.2.tgz#329c7a06735e144a506bdb2cad0268b7f46f4ad8"
|
||||
integrity sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.22.13"
|
||||
"@babel/generator" "^7.22.15"
|
||||
"@babel/generator" "^7.23.0"
|
||||
"@babel/helper-environment-visitor" "^7.22.20"
|
||||
"@babel/helper-function-name" "^7.22.5"
|
||||
"@babel/helper-function-name" "^7.23.0"
|
||||
"@babel/helper-hoist-variables" "^7.22.5"
|
||||
"@babel/helper-split-export-declaration" "^7.22.6"
|
||||
"@babel/parser" "^7.22.16"
|
||||
"@babel/types" "^7.22.19"
|
||||
"@babel/parser" "^7.23.0"
|
||||
"@babel/types" "^7.23.0"
|
||||
debug "^4.1.0"
|
||||
globals "^11.1.0"
|
||||
|
||||
@@ -346,6 +361,15 @@
|
||||
"@babel/helper-validator-identifier" "^7.22.19"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@babel/types@^7.23.0":
|
||||
version "7.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb"
|
||||
integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==
|
||||
dependencies:
|
||||
"@babel/helper-string-parser" "^7.22.5"
|
||||
"@babel/helper-validator-identifier" "^7.22.20"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@discordjs/builders@^1.6.5":
|
||||
version "1.6.5"
|
||||
resolved "https://registry.yarnpkg.com/@discordjs/builders/-/builders-1.6.5.tgz#3e23912eaab1d542b61ca0fa7202e5aaef2b7200"
|
||||
|
||||