Compare commits

..
12 Commits
Author SHA1 Message Date
TrèsAbhi b8be916c18 Merge pull request #43 from tresabhi/1.14.2 2023-10-17 22:08:51 -05:00
TrèsAbhi 24d0313fd3 feat: changelogs 2023-10-17 22:08:39 -05:00
TrèsAbhi c3d2a81327 fix: generic stats name overflow 2023-10-17 22:06:50 -05:00
TrèsAbhi dfeb117663 fix: getClanInfo now uses clan_id 2023-10-17 22:04:27 -05:00
TrèsAbhi 495a830506 Merge pull request #42 from tresabhi/1.14.1 2023-10-17 21:14:13 -05:00
TrèsAbhi a730f67ca5 feat: changelogs 2023-10-17 21:14:30 -05:00
TrèsAbhi 9a3867770d feat: reduced render workers to 2 2023-10-17 21:01:26 -05:00
TrèsAbhi f8a66bb5e7 feat: simpler /link message 2023-10-17 20:59:33 -05:00
TrèsAbhi 4b00e1342e feat: user error discrimination 2023-10-17 20:58:36 -05:00
TrèsAbhi f9e0584f00 fix: memory leak in render threads 2023-10-17 20:45:45 -05:00
TrèsAbhi 900c290e75 Update cd-host.yml 2023-10-16 20:50:40 -05:00
TrèsAbhi 69e1577d18 fix: prisma copy over 2023-10-16 20:44:47 -05:00
19 changed files with 134 additions and 91 deletions

No files matched your search

+1 -1
View File
@@ -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
+1
View File
@@ -15,6 +15,7 @@
"kranvagn",
"Leaderboard",
"leaderboards",
"Oopsy",
"outdir",
"outfile",
"ownedtanks",
+17
View File
@@ -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
+8
View File
@@ -0,0 +1,8 @@
# Blitzkrieg 1.14.1
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
+36 -28
View File
@@ -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, // cause 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}`)),
);
+10 -7
View File
@@ -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.',
},
);
}
@@ -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,
+3 -6
View File
@@ -1,5 +1,3 @@
import throwError from '../blitzkrieg/throwError';
export type BlitzResponse<Data extends object> =
| {
status: 'error';
@@ -18,9 +16,8 @@ export default async function fetchBlitz<Data extends object>(url: string) {
if (data.status === 'ok') {
return data.data;
} else {
throw throwError(
`Wargaming response error status:"${data.status}"`,
`Message: "${data.error.message}"\nURL: "${url}"`,
);
throw new Error(`Wargaming response error status:"${data.status}"`, {
cause: `Message: "${data.error.message}"\nURL: "${url}"`,
});
}
}
+1 -1
View File
@@ -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,
)}`,
);
+5 -5
View File
@@ -1,7 +1,7 @@
import { Region } from '../../constants/regions';
import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
import { TanksStats } from '../../types/tanksStats';
import throwError from '../blitzkrieg/throwError';
import { UserError } from '../blitzkrieg/userError';
import fetchBlitz from './fetchWargaming';
export default async function getTankStats(region: Region, id: number) {
@@ -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];
+7 -9
View File
@@ -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}".`,
});
}
}
}
@@ -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 & {
+13 -9
View File
@@ -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);
}
}
-3
View File
@@ -1,3 +0,0 @@
export default function throwError(message: string, cause?: string) {
return new Error(message, { cause });
}
+4
View File
@@ -0,0 +1,4 @@
/**
* Discriminator for user caused errors to not report in logs
*/
export class UserError extends Error {}
+4 -5
View File
@@ -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.`,
});
}
}
}
+7 -9
View File
@@ -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: [