Compare commits

..
8 Commits
Author SHA1 Message Date
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
16 changed files with 115 additions and 89 deletions

No files matched your search

+1 -1
View File
@@ -29,4 +29,4 @@ jobs:
unzip bot.zip unzip bot.zip
- name: Run bot - 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", "kranvagn",
"Leaderboard", "Leaderboard",
"leaderboards", "leaderboards",
"Oopsy",
"outdir", "outdir",
"outfile", "outfile",
"ownedtanks", "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
+36 -28
View File
@@ -1,36 +1,44 @@
import { build } from 'esbuild'; import { build } from 'esbuild';
import { readdir } from 'fs/promises'; import { cp, readdir } from 'fs/promises';
const FILES = [ readdir('src/workers').then((workers) => {
'bot', const files = [
'index', 'bot',
'index',
(await readdir('src/workers')).map( ...workers.map((file) => `workers/${file.replace('.ts', '')}`),
(file) => `workers/${file.replace('.ts', '')}`, ];
),
];
await build({ build({
entryPoints: FILES.map((file) => `src/${file}.ts`), entryPoints: files.map((file) => `src/${file}.ts`),
outdir: 'dist/bot', outdir: 'dist/bot',
platform: 'node', platform: 'node',
tsconfig: 'tsconfig.esbuild.json', tsconfig: 'tsconfig.esbuild.json',
loader: { loader: {
'.node': 'copy', '.node': 'copy',
'.ttf': 'file', '.ttf': 'file',
}, },
format: 'cjs', format: 'cjs',
jsx: 'transform', jsx: 'transform',
outExtension: { outExtension: {
'.js': '.cjs', '.js': '.cjs',
}, },
bundle: true, bundle: true,
sourcemap: true, sourcemap: true,
minifyIdentifiers: false, // cause goofy errors minifyIdentifiers: false, // cause goofy errors
minifySyntax: true, minifySyntax: true,
minifyWhitespace: 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, DATABASE_REPO,
} from '../core/blitzkrieg/getMidnightLeaderboard'; } from '../core/blitzkrieg/getMidnightLeaderboard';
import { octokit } from '../core/blitzkrieg/octokit'; 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 addRegionChoices from '../core/discord/addRegionChoices';
import addUsernameChoices from '../core/discord/addUsernameChoices'; import addUsernameChoices from '../core/discord/addUsernameChoices';
import autocompleteUsername from '../core/discord/autocompleteUsername'; import autocompleteUsername from '../core/discord/autocompleteUsername';
@@ -185,7 +185,7 @@ export const ratingsCommand = new Promise<CommandRegistryRaw>(
}) })
.then(({ data }) => { .then(({ data }) => {
if (!Array.isArray(data)) { if (!Array.isArray(data)) {
throw throwError('Archived ratings data is malformed'); throw new Error('Archived ratings data is malformed');
} }
return data.map( return data.map(
(folder) => (folder) =>
@@ -292,7 +292,7 @@ export const ratingsCommand = new Promise<CommandRegistryRaw>(
}) })
.then(async ({ data }) => { .then(async ({ data }) => {
if (Array.isArray(data) || data.type !== 'file') { 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( const players = (await fetch(data.download_url!).then(
@@ -361,7 +361,7 @@ export const ratingsCommand = new Promise<CommandRegistryRaw>(
}) })
.then(async ({ data }) => { .then(async ({ data }) => {
if (Array.isArray(data) || data.type !== 'file') { 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!); const response = fetch(data.download_url!);
@@ -404,7 +404,7 @@ export const ratingsCommand = new Promise<CommandRegistryRaw>(
}) })
.then(async ({ data }) => { .then(async ({ data }) => {
if (Array.isArray(data) || data.type !== 'file') { if (Array.isArray(data) || data.type !== 'file') {
throw throwError( throw new Error(
'Archived ratings latest data is malformed', 'Archived ratings latest data is malformed',
); );
} }
@@ -418,9 +418,12 @@ export const ratingsCommand = new Promise<CommandRegistryRaw>(
); );
if (playerIndex === -1) { if (playerIndex === -1) {
throw throwError( throw new UserError(
`${accountInfo.nickname} didn't player ratings in season ${season}`, `${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.',
},
); );
} }
+3 -6
View File
@@ -1,5 +1,3 @@
import throwError from '../blitzkrieg/throwError';
export type BlitzResponse<Data extends object> = export type BlitzResponse<Data extends object> =
| { | {
status: 'error'; status: 'error';
@@ -18,9 +16,8 @@ export default async function fetchBlitz<Data extends object>(url: string) {
if (data.status === 'ok') { if (data.status === 'ok') {
return data.data; return data.data;
} else { } else {
throw throwError( throw new Error(`Wargaming response error status:"${data.status}"`, {
`Wargaming response error status:"${data.status}"`, cause: `Message: "${data.error.message}"\nURL: "${url}"`,
`Message: "${data.error.message}"\nURL: "${url}"`, });
);
} }
} }
+5 -5
View File
@@ -1,7 +1,7 @@
import { Region } from '../../constants/regions'; import { Region } from '../../constants/regions';
import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID'; import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
import { TanksStats } from '../../types/tanksStats'; import { TanksStats } from '../../types/tanksStats';
import throwError from '../blitzkrieg/throwError'; import { UserError } from '../blitzkrieg/userError';
import fetchBlitz from './fetchWargaming'; import fetchBlitz from './fetchWargaming';
export default async function getTankStats(region: Region, id: number) { 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) { if (tankStats[id] === null) {
throw throwError( throw new UserError('No tank stats available', {
'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.", "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];
+7 -9
View File
@@ -1,5 +1,5 @@
import { go } from 'fuzzysort'; import { go } from 'fuzzysort';
import throwError from '../blitzkrieg/throwError'; import { UserError } from '../blitzkrieg/userError';
import { TANKS, tankopedia } from '../blitzstars/tankopedia'; import { TANKS, tankopedia } from '../blitzstars/tankopedia';
export default async function resolveTankId(tank: string | number) { export default async function resolveTankId(tank: string | number) {
@@ -12,10 +12,9 @@ export default async function resolveTankId(tank: string | number) {
}); });
if (searchResult.length === 0) { if (searchResult.length === 0) {
throw throwError( throw new UserError('Tank not found', {
'Tank not found', cause: `Could not find tank by the name "${tank}".`,
`Could not find tank by the name "${tank}".`, });
);
} else { } else {
return searchResult[0].obj.tank_id; return searchResult[0].obj.tank_id;
} }
@@ -23,10 +22,9 @@ export default async function resolveTankId(tank: string | number) {
if ((await tankopedia)[number]) { if ((await tankopedia)[number]) {
return number; return number;
} else { } else {
throw throwError( throw new UserError('Tank not found', {
'Tank not found', cause: `Could not find tank by the ID "${number}".`,
`Could not find tank by the ID "${number}".`, });
);
} }
} }
} }
@@ -2,7 +2,6 @@ import { RatingsInfo } from '../../commands/ratings';
import { Region } from '../../constants/regions'; import { Region } from '../../constants/regions';
import { DATABASE_REPO } from './getMidnightLeaderboard'; import { DATABASE_REPO } from './getMidnightLeaderboard';
import { octokit } from './octokit'; import { octokit } from './octokit';
import throwError from './throwError';
const ARCHIVED_RATINGS_CACHE: Record< const ARCHIVED_RATINGS_CACHE: Record<
Region, Region,
@@ -32,7 +31,7 @@ export default async function getArchivedRatingsInfo(
}); });
if (Array.isArray(data) || data.type !== 'file') 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 content = Buffer.from(data.content, 'base64').toString();
const jsonContent = JSON.parse(content) as RatingsInfo & { const jsonContent = JSON.parse(content) as RatingsInfo & {
+13 -9
View File
@@ -1,6 +1,6 @@
import { Worker } from 'worker_threads'; import { Worker } from 'worker_threads';
const MAX_WORKERS = 3; const MAX_WORKERS = 2;
const workers = [...Array(MAX_WORKERS)].map(() => { const workers = [...Array(MAX_WORKERS)].map(() => {
const worker = new Worker(`${__dirname}/workers/render.cjs`); const worker = new Worker(`${__dirname}/workers/render.cjs`);
@@ -19,18 +19,22 @@ function manageQueue() {
function cleanup() { function cleanup() {
workers.push(worker); workers.push(worker);
worker.off('message', handleMessage);
worker.off('error', handleError);
manageQueue(); manageQueue();
} }
function handleMessage(png: Uint8Array) {
cleanup();
resolve(Buffer.from(png));
}
function handleError(error: Error) {
cleanup();
throw error;
}
worker.postMessage(svg); worker.postMessage(svg);
worker.on('message', (png: Uint8Array) => { worker.on('message', handleMessage);
cleanup(); worker.on('error', handleError);
resolve(Buffer.from(png));
});
worker.on('error', (error) => {
cleanup();
throw error;
});
} }
} }
-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 { ChatInputCommandInteraction } from 'discord.js';
import { Region } from '../../constants/regions'; import { Region } from '../../constants/regions';
import searchClansAcrossRegions from '../blitz/searchClansAcrossRegions'; import searchClansAcrossRegions from '../blitz/searchClansAcrossRegions';
import throwError from '../blitzkrieg/throwError'; import { UserError } from '../blitzkrieg/userError';
import { serverAndIdPattern } from './resolvePlayerFromCommand'; import { serverAndIdPattern } from './resolvePlayerFromCommand';
export default async function resolveClanFromCommand( export default async function resolveClanFromCommand(
@@ -18,10 +18,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 throwError( throw new UserError('Could not find clan', {
'Could not find clan', cause: `I couldn't find clan \`${clan}\`. Try selecting a username from the search result.`,
`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 { Region } from '../../constants/regions';
import searchPlayersAcrossRegions from '../blitz/searchPlayersAcrossRegions'; import searchPlayersAcrossRegions from '../blitz/searchPlayersAcrossRegions';
import { getBlitzFromDiscord } from '../blitzkrieg/discordBlitz'; import { getBlitzFromDiscord } from '../blitzkrieg/discordBlitz';
import throwError from '../blitzkrieg/throwError'; import { UserError } from '../blitzkrieg/userError';
export const serverAndIdPattern = /(com|eu|asia)\/[0-9]+/; export const serverAndIdPattern = /(com|eu|asia)\/[0-9]+/;
@@ -34,12 +34,11 @@ export default async function resolvePlayerFromCommand(
id: accounts[0].account_id, id: accounts[0].account_id,
} satisfies ResolvedPlayer; } satisfies ResolvedPlayer;
} else { } else {
throw throwError( throw new UserError('Could not find user', {
'Could not find user', cause: `I couldn't find user "${markdownEscape(
`I 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 {
@@ -48,10 +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 throwError( throw new UserError('Use the `/link` command', {
"You're not linked", cause: 'Link your Blitz and Discord accounts to get started.',
'Use the `/link` command to link your Discord and Blitz accounts.', });
);
} }
} }
} }
@@ -1,6 +1,5 @@
import { ButtonInteraction, CacheType } from 'discord.js'; import { ButtonInteraction, CacheType } from 'discord.js';
import { commands } from '..'; import { commands } from '..';
import throwError from '../../../core/blitzkrieg/throwError';
import normalizeInteractionReturnable from '../../../core/discord/normalizeInteractionReturnable'; import normalizeInteractionReturnable from '../../../core/discord/normalizeInteractionReturnable';
export default async function handleButton( export default async function handleButton(
@@ -13,8 +12,7 @@ export default async function handleButton(
const button = (await commands)[commandName]?.button; const button = (await commands)[commandName]?.button;
if (!button) { if (!button) {
throwError(`Button handler not found for "${commandName}"`); throw new Error(`Button handler not found for "${commandName}"`);
return;
} }
if (interaction.message.interaction?.user.id !== interaction.user.id) { if (interaction.message.interaction?.user.id !== interaction.user.id) {
@@ -7,10 +7,11 @@ import {
} from 'discord.js'; } from 'discord.js';
import { commands } from '..'; import { commands } from '..';
import discord from '../../../../discord.json' assert { type: 'json' }; 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 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';
export default async function handleChatInputCommand( export default async function handleChatInputCommand(
interaction: ChatInputCommandInteraction<CacheType>, interaction: ChatInputCommandInteraction<CacheType>,
@@ -53,7 +54,9 @@ export default async function handleChatInputCommand(
.setStyle(ButtonStyle.Link), .setStyle(ButtonStyle.Link),
); );
console.error(interaction.commandName, error); if (!(error instanceof UserError)) {
console.error(interaction.commandName, error);
}
await interaction.editReply({ await interaction.editReply({
embeds: [ embeds: [