diff --git a/.gitignore b/.gitignore index 9e20f4a27..2124182fd 100644 --- a/.gitignore +++ b/.gitignore @@ -27,7 +27,6 @@ target .astro .vercel /src -/packages/bot/src/prisma dev-dist # Logs logs diff --git a/README.md b/README.md index 8e1b06629..4a7de2108 100644 --- a/README.md +++ b/README.md @@ -8,15 +8,14 @@ If you intend to contribute to the code, note that you may need to check out sen ## Packages -| Package | Title | Source | Memo | -| ------------------------------------ | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------ | -| [packages/app](packages/app) | BlitzKit | [Play Store](https://play.google.com/store/apps/details?id=com.tresabhi.blitzkit), [App Store](https://apps.apple.com/app/4321), [Microsoft Store](https://apps.microsoft.com/detail/zyxw) | The native app of BlitzKit for all platforms. Unavailable from stores at this time. | -| [packages/bot](packages/bot) | BlitzKit Discord Bot | [Discord Application Directory](https://discord.com/discovery/applications/1097673957865443370) | The one-in-all Discord bot for statistics. | -| [packages/core](packages/core) | | | Mutual internal code shared between all other packages. | -| [packages/i18n](packages/i18n) | | [Crowdin](https://crowdin.com/project/blitzkrieg) | Translations served as JSON shared across all other packages, including some utility code. | -| [packages/scripts](packages/scripts) | | | CLI utilities for the development and deployment of BlitzKit services. | -| [packages/varuna](packages/varuna) | Varuna | [NPM](https://www.npmjs.com/package/varuna) | React and Astro-oriented state management library, awaiting separation from BlitzKit. Unavailable from NPM at this time. | -| [packages/website](packages/website) | BlitzKit | https://blitzkit.app | The BlitzKit website provides a wide range of tools, APIs, and interconnects all other services. | +| Package | Title | Source | Memo | +| ------------------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------ | +| [packages/app](packages/app) | BlitzKit | [Play Store](https://play.google.com/store/apps/details?id=com.tresabhi.blitzkit), [App Store](https://apps.apple.com/app/4321), [Microsoft Store](https://apps.microsoft.com/detail/zyxw) | The native app of BlitzKit for all platforms. Unavailable from stores at this time. | +| [packages/core](packages/core) | | | Mutual internal code shared between all other packages. | +| [packages/i18n](packages/i18n) | | [Crowdin](https://crowdin.com/project/blitzkrieg) | Translations served as JSON shared across all other packages, including some utility code. | +| [packages/scripts](packages/scripts) | | | CLI utilities for the development and deployment of BlitzKit services. | +| [packages/varuna](packages/varuna) | Varuna | [NPM](https://www.npmjs.com/package/varuna) | React and Astro-oriented state management library, awaiting separation from BlitzKit. Unavailable from NPM at this time. | +| [packages/website](packages/website) | BlitzKit | https://blitzkit.app | The BlitzKit website provides a wide range of tools, APIs, and interconnects all other services. | ## Submodules @@ -40,8 +39,6 @@ If you intend to contribute to the code, note that you may need to check out sen | `PUBLIC_BRANCH` | | `main` | The branch that the website is operating on. This is completely nominal. | | `PUBLIC_GUIDE_WEBSITE` | [GuidesBlitz](https://guidesblitz.com/) | `https://guidesblitz.com` | The website that serves the encyclopedia-like content in BlitzKit. | | `SERVICING` | | `false` | Wether the website is down for servicing. | -| `DISCORD_CLIENT_ID` | [Discord Developer Portal](https://discord.com/developers/applications) | `1097673957865443370` | The BlitzKit Discord Bot's user ID. | | `GH_TOKEN` | [GitHub Fine-Grained Tokens](https://github.com/settings/personal-access-tokens) | | The GitHub API key used for checking out private submodules and pushing content to the asset repository. | -| `DISCORD_TOKEN` | [Discord Developer Portal](https://discord.com/developers/applications) | | The Discord API Key for the BlitzKit Discord Bot. | | `DATABASE_URL` | [CockroachDB](https://www.cockroachlabs.com/) | | The CockroachDB-SQL database URL used to link Wargaming and Discord users. | | `WOTB_DLC_CDN` | | | | diff --git a/docs/guides/introduction.md b/docs/guides/introduction.md index f56f1c1ae..712323555 100644 --- a/docs/guides/introduction.md +++ b/docs/guides/introduction.md @@ -2,17 +2,6 @@ 🎉 Services for everything and anything World of Tanks Blitz -## Bot - -BlitzKit is available as a Discord bot and can be [installed very easily](/guide/invite). - -- Track statistics -- Find inactive players in a clan -- Lookup owned tanks -- Create embeds for streams -- Track a player/tanks statistical evolution -- ...and a variety of other similar things - ## Website Provides a bunch of very helpful tools; https://blitzkit.app/ diff --git a/docs/guides/invite.md b/docs/guides/invite.md deleted file mode 100644 index 7a1af2ff1..000000000 --- a/docs/guides/invite.md +++ /dev/null @@ -1,3 +0,0 @@ -# Inviting the Bot - -Go to [application directory for BlitzKit](https://discord.com/application-directory/1097673957865443370) and click "Add to Server." Voila! You're ready to go. diff --git a/package.json b/package.json index a61810c71..8064764f0 100644 --- a/package.json +++ b/package.json @@ -6,15 +6,12 @@ "scripts": { "build:assets": "cd packages/scripts && bun run build:assets", "build:averages": "bun --filter=@blitzkit/scripts run build:averages", - "build:bot": "cd packages/scripts && bun run build:bot", "build:website": "cd packages/website && bun run build", "build:prisma": "prisma generate", "build:proxyclient": "cd packages/scripts && bun run build:proxyclient", "build:catalogitemaccesssor": "cd packages/scripts && bun run build:catalogitemaccesssor", "build:ratingleaderboard": "cd packages/scripts && bun run build:ratingleaderboard", "build:app": "cd packages/app && bun run build", - "dev:bot": "bun run build:bot --dev && bun start:bot --dev", - "start:bot": "bun dist/bot/main.js", "dev:docs": "vitepress dev docs", "dev:website": "cd packages/website && bun run dev", "dev:app": "cd packages/app && bun run dev", diff --git a/packages/bot/package.json b/packages/bot/package.json deleted file mode 100644 index 6e18e80ef..000000000 --- a/packages/bot/package.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "@blitzkit/bot", - "dependencies": { - "@blitzkit/core": "workspace:*", - "@blitzkit/i18n": "workspace:*", - "@radix-ui/colors": "^3.0.0", - "@radix-ui/themes": "^3.3.0", - "@resvg/resvg-js": "^2.6.2", - "@types/adm-zip": "^0.5.7", - "@types/bun": "^1.3.9", - "@types/lodash-es": "^4.17.12", - "@types/markdown-escape": "^1.1.3", - "@types/react": "^19.2.14", - "adm-zip": "^0.5.16", - "bepaint": "^0.1.0-beta.3", - "discord.js": "^14.25.1", - "dotenv": "^17.3.1", - "fuzzysort": "^3.1.0", - "lodash-es": "^4.17.23", - "markdown-escape": "^2.0.0", - "pickleparser": "^0.2.1", - "prisma": "^6.19.2", - "protobufjs": "^7.5.4", - "react": "^19.2.4", - "satori": "^0.18.4", - "sharp": "^0.34.5" - }, - "private": true, - "scripts": { - "dev": "bun --watch src/main.ts", - "start": "bun src/main.ts" - }, - "type": "module", - "types": "src/types.d.ts" -} diff --git a/packages/bot/src/commands/about.ts b/packages/bot/src/commands/about.ts deleted file mode 100644 index 82552eca3..000000000 --- a/packages/bot/src/commands/about.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { translator } from "../core/localization/translator"; -import { - commandsArray, - type CommandRegistry, -} from "../events/interactionCreate"; - -const subcommands = [ - "embeds", - "introduction", - "invite", - "timezones", - "credits", -] as const; -type Subcommand = (typeof subcommands)[number]; - -const DOCS: Record = { - embeds: "guide/embeds", - introduction: "guide/introduction", - invite: "guide/invite", - timezones: "guide/timezones", - credits: "about/credits", -}; - -export const aboutCommand = new Promise(async (resolve) => { - resolve({ - command: createLocalizedCommand("about", [ - { subcommand: "commands" }, - ...subcommands.map((subcommand) => ({ subcommand })), - ]), - - async handler(interaction) { - const subcommand = interaction.options.getSubcommand(); - const { strings } = translator(interaction.locale); - - if (subcommand === "commands") { - return `${strings.bot.commands.about.subcommands.commands.body}${( - await Promise.all(commandsArray) - ) - .sort((a, b) => - (a.command.name_localizations?.[interaction.locale] ?? - a.command.name) < - (b.command.name_localizations?.[interaction.locale] ?? - b.command.name) - ? -1 - : 1, - ) - .map( - (registry) => - `- \`/${ - registry.command.name_localizations?.[interaction.locale] ?? - registry.command.name - }\`: ${ - registry.command.description_localizations?.[ - interaction.locale - ] ?? registry.command.description - }`, - ) - .join("\n")}`; - } - - const url = `https://raw.githubusercontent.com/tresabhi/blitzkit/main/docs/${DOCS[subcommand as Subcommand]}.md`; - const content = fetch(url).then((response) => response.text()); - - return content; - }, - }); -}); diff --git a/packages/bot/src/commands/breakdown/index.tsx b/packages/bot/src/commands/breakdown/index.tsx deleted file mode 100644 index 958e8abd2..000000000 --- a/packages/bot/src/commands/breakdown/index.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { addPeriodicFilterOptions } from "../../core/discord/addPeriodicFilterOptions"; -import { addUsernameChoices } from "../../core/discord/addUsernameChoices"; -import { autocompleteTanks } from "../../core/discord/autocompleteTanks"; -import { autocompleteUsername } from "../../core/discord/autocompleteUsername"; -import { createLocalizedCommand } from "../../core/discord/createLocalizedCommand"; -import { getFiltersFromButton } from "../../core/discord/getFiltersFromButton"; -import { resolvePeriodFromButton } from "../../core/discord/resolvePeriodFromButton"; -import { resolvePlayerFromButton } from "../../core/discord/resolvePlayerFromButton"; -import { translator } from "../../core/localization/translator"; -import type { CommandRegistry } from "../../events/interactionCreate"; -import { renderBreakdown } from "./render"; - -export const breakdownCommand = new Promise( - async (resolve) => { - resolve({ - command: await addPeriodicFilterOptions( - createLocalizedCommand("breakdown"), - (option) => option.addStringOption(addUsernameChoices), - ), - - async handler(interaction) { - const { strings } = translator(interaction.locale); - return strings.bot.commands.dead; - - // const player = await resolvePlayerFromCommand(interaction); - // const period = resolvePeriodFromCommand(player.region, interaction); - // const filters = await getFiltersFromCommand(interaction); - // const path = commandToURL(interaction, { - // ...player, - // ...getCustomPeriodParams(interaction), - // ...filters, - // }); - - // return [ - // ...(await renderBreakdown( - // player, - // period, - // filters, - // interaction.locale, - // )), - // buttonRefresh(interaction, path), - // 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, - interaction.locale, - ); - }, - }); - }, -); diff --git a/packages/bot/src/commands/breakdown/render.tsx b/packages/bot/src/commands/breakdown/render.tsx deleted file mode 100644 index 91fda4e2b..000000000 --- a/packages/bot/src/commands/breakdown/render.tsx +++ /dev/null @@ -1,295 +0,0 @@ -import { - type BlitzStats, - calculateWN8, - getAccountInfo, - getClanAccountInfo, - getTankStats, - getWN8Percentile, - TankDefinition, -} from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import { Locale } from "discord.js"; -import { chunk } from "lodash-es"; -import * as Breakdown from "../../components/Breakdown"; -import { CommandWrapper } from "../../components/CommandWrapper"; -import { NoData } from "../../components/NoData"; -import { TitleBar } from "../../components/TitleBar"; -import { filtersToDescription } from "../../core/blitzkit/filtersToDescription"; -import { - averageDefinitions, - tankDefinitions, -} from "../../core/blitzkit/nonBlockingPromises"; -import { UserError } from "../../core/blitzkit/userError"; -import { - filterStats, - type StatFilters, -} from "../../core/blitzstars/filterStats"; -import { getStatsInPeriod } from "../../core/blitzstars/getStatsInPeriod"; -import type { ResolvedPeriod } from "../../core/discord/resolvePeriodFromCommand"; -import type { ResolvedPlayer } from "../../core/discord/resolvePlayerFromCommand"; -import { translator } from "../../core/localization/translator"; - -const ROWS_PER_PAGE = 8; -const MAX_PAGES = 9; - -export async function renderBreakdown( - { region, id }: ResolvedPlayer, - { start, end, name }: ResolvedPeriod, - filters: StatFilters, - locale: Locale, -) { - const { strings, unwrap } = translator(locale); - const awaitedTankDefinitions = await tankDefinitions; - const awaitedTankAverages = await averageDefinitions; - const statsInPeriod = await getStatsInPeriod(region, id, start, end, locale); - const { filteredOrder } = await filterStats(statsInPeriod, filters); - const accountInfo = await getAccountInfo(region, id); - const clanData = await getClanAccountInfo(region, id, ["clan"]); - const tankStats = await getTankStats(region, id); - const filterDescriptions = await filtersToDescription(filters, locale); - const orderedCurrentStats: BlitzStats[] = []; - const orderedCareerStats: BlitzStats[] = []; - const orderedCurrentWN8: (number | undefined)[] = []; - const orderedCareerWN8: (number | undefined)[] = []; - const orderedCareerWN8Full: (number | undefined)[] = []; - - if (tankStats === null) { - throw new UserError(strings.bot.common.errors.no_tank_stats); - } - - filteredOrder.forEach((id) => { - const tankAveragesEntry = awaitedTankAverages.averages[id]; - const current = statsInPeriod.diff[id]; - const career = tankStats.find((tank) => tank.tank_id === id)!.all; - const currentWN8 = tankAveragesEntry - ? calculateWN8(tankAveragesEntry.mu, current) - : undefined; - const careerWN8 = tankAveragesEntry - ? calculateWN8(tankAveragesEntry.mu, career) - : undefined; - - orderedCurrentStats.push(current); - orderedCareerStats.push(career); - orderedCurrentWN8.push(currentWN8); - orderedCareerWN8.push(careerWN8); - }); - tankStats.forEach((tank) => { - const tankAveragesEntry = awaitedTankAverages.averages[tank.tank_id]; - - orderedCareerWN8Full.push( - tankAveragesEntry - ? calculateWN8(tankAveragesEntry.mu, tank.all) - : undefined, - ); - }); - - const currentBattles = orderedCurrentStats.reduce( - (accumulator, current) => accumulator + current.battles, - 0, - ); - const careerBattles = tankStats.reduce( - (accumulator, current) => accumulator + current.all.battles, - 0, - ); - const currentWinrate = - orderedCurrentStats.reduce( - (accumulator, current) => accumulator + current.wins, - 0, - ) / currentBattles; - const careerWinrate = - tankStats.reduce( - (accumulator, current) => accumulator + current.all.wins, - 0, - ) / careerBattles; - const currentWN8 = - orderedCurrentWN8.reduce( - (accumulator, current, index) => - accumulator + (current ?? 0) * orderedCurrentStats[index].battles, - 0, - ) / - orderedCurrentStats.reduce( - (accumulator, current, index) => - accumulator + - (orderedCurrentWN8[index] === undefined ? 0 : current.battles), - 0, - ); - const careerWN8 = - orderedCareerWN8Full.reduce( - (accumulator, current) => accumulator + (current ?? 0), - 0, - ) / - tankStats.reduce( - (accumulator, current, index) => - accumulator + - (orderedCareerWN8Full[index] === undefined ? 0 : current.all.battles), - 0, - ); - const currentDamage = - orderedCurrentStats.reduce( - (accumulator, current) => accumulator + current.damage_dealt, - 0, - ) / currentBattles; - const careerDamage = - tankStats.reduce( - (accumulator, current) => accumulator + current.all.damage_dealt, - 0, - ) / careerBattles; - - const children: React.JSX.Element[] = []; - - if (filteredOrder.length > 0) { - children.push( - , - ); - - filteredOrder.forEach((id, index) => { - const tankDefinition = awaitedTankDefinitions.tanks[id] as - | TankDefinition - | undefined; - const current = orderedCurrentStats[index]; - const career = orderedCareerStats[index]; - const currentWN8 = orderedCurrentWN8[index]; - const careerWN8 = orderedCareerWN8[index]; - - children.push( - , - ); - }); - } - - const pages = chunk( - children, - Math.max(Math.ceil(children.length / MAX_PAGES), ROWS_PER_PAGE), - ); - - if (filteredOrder.length > 0) { - return pages - .map((page, index) => ( - - {index === 0 && ( - - )} - - {page} - - )) - .reverse(); - } else { - return [ - - - - - , - ]; - } -} diff --git a/packages/bot/src/commands/debug.ts b/packages/bot/src/commands/debug.ts deleted file mode 100644 index 08f43e8ba..000000000 --- a/packages/bot/src/commands/debug.ts +++ /dev/null @@ -1,37 +0,0 @@ -import packageJSON from "../../../../package.json"; -import { client } from "../core/discord/client"; -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { translator } from "../core/localization/translator"; -import type { CommandRegistry } from "../events/interactionCreate"; - -const executionStart = new Date().getTime(); - -export const debugCommand = new Promise((resolve) => { - resolve({ - command: createLocalizedCommand("debug"), - - async handler(interaction) { - const { strings } = translator(interaction.locale); - const currentTime = new Date().getTime(); - const uptime = currentTime - executionStart; - const list = [ - [strings.bot.commands.debug.body.version, packageJSON.version], - [ - strings.bot.commands.debug.body.shard.label, - client.shard?.ids[0] ?? strings.bot.commands.debug.body.shard.default, - ], - [ - strings.bot.commands.debug.body.uptime, - `${Math.floor((uptime / 1000 / 60 / 60) % 24)}h ${Math.floor( - (uptime / 1000 / 60) % 60 - )}m ${Math.floor((uptime / 1000) % 60)}s ${Math.floor( - uptime % 1000 - )}ms`, - ], - ]; - return `${strings.bot.commands.debug.body.title}${list - .map(([key, value]) => `- ${key}: ${value}`) - .join("\n")}`; - }, - }); -}); diff --git a/packages/bot/src/commands/evolution.tsx b/packages/bot/src/commands/evolution.tsx deleted file mode 100644 index 9717938f9..000000000 --- a/packages/bot/src/commands/evolution.tsx +++ /dev/null @@ -1,182 +0,0 @@ -import { emblemURL, getAccountInfo, getClanAccountInfo } from "@blitzkit/core"; -import { Locale, SlashCommandSubcommandGroupBuilder } from "discord.js"; -import { CommandWrapper } from "../components/CommandWrapper"; -import * as Graph from "../components/Graph"; -import { LineColor } from "../components/Graph/components/Line/constants"; -import { NoData } from "../components/NoData"; -import { TitleBar } from "../components/TitleBar"; -import { getPlayerHistories } from "../core/blitzstars/getPlayerHistories"; -import { getTankHistories } from "../core/blitzstars/getTankHistories"; -import { addPeriodSubCommands } from "../core/discord/addPeriodSubCommands"; -import { addTankChoices } from "../core/discord/addTankChoices"; -import { addUsernameChoices } from "../core/discord/addUsernameChoices"; -import { autocompleteTanks } from "../core/discord/autocompleteTanks"; -import { autocompleteUsername } from "../core/discord/autocompleteUsername"; -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { resolvePeriodFromButton } from "../core/discord/resolvePeriodFromButton"; -import { type ResolvedPeriod } from "../core/discord/resolvePeriodFromCommand"; -import { resolvePlayerFromButton } from "../core/discord/resolvePlayerFromButton"; -import { type ResolvedPlayer } from "../core/discord/resolvePlayerFromCommand"; -import { translator } from "../core/localization/translator"; -import type { CommandRegistry } from "../events/interactionCreate"; - -type EvolutionStatType = "player" | "tank"; - -async function render( - { region, id }: ResolvedPlayer, - { start, end, name }: ResolvedPeriod, - type: "player" | "tank", - tankId: number | null, - locale: Locale, -) { - const accountInfo = await getAccountInfo(region, id); - const clan = (await getClanAccountInfo(region, id, ["clan"]))?.clan; - const logo = clan ? emblemURL(clan.emblem_set_id) : undefined; - const histories = await (type === "player" - ? getPlayerHistories(region, id, { - start, - end, - includeLatestHistories: true, - includePreviousHistories: true, - }) - : getTankHistories(region, id, locale, { - tankId: tankId!, - start, - end, - includeLatestHistories: true, - includePreviousHistories: true, - })); - const plot = histories.map( - (history) => - [ - history.all.battles, - (history.all.wins / history.all.battles) * 100, - ] as Graph.PlotItem, - ); - const times = histories.map((history) => history.last_battle_time); - const minTime = Math.min(...times); - const maxTime = Math.max(...times); - const ys = plot.map(([, y]) => y); - const xs = plot.map(([x]) => x); - const maxY = Math.max(...ys); - const minY = Math.min(...ys); - const maxX = Math.max(...xs); - const minX = Math.min(...xs); - - return ( - - - - {plot.length > 0 && ( - - {Graph.Line({ - color: LineColor.Red, - plot: plot, - minY, - maxY, - })} - - )} - - {plot.length === 0 && } - - ); -} - -export const evolutionCommand = new Promise( - async (resolve) => { - resolve({ - command: createLocalizedCommand("evolution", [ - { - group: "player", - modify(option: SlashCommandSubcommandGroupBuilder) { - addPeriodSubCommands(option, (option) => - option.addStringOption(addUsernameChoices), - ); - }, - }, - { - group: "tank", - modify(option: SlashCommandSubcommandGroupBuilder) { - addPeriodSubCommands(option, (option) => - option - .addStringOption(addTankChoices) - .addStringOption(addUsernameChoices), - ); - }, - }, - ]), - - async handler(interaction) { - const { strings } = translator(interaction.locale); - return strings.bot.commands.dead; - - // const commandGroup = - // interaction.options.getSubcommandGroup() as EvolutionStatType; - // const player = await resolvePlayerFromCommand(interaction); - // const period = resolvePeriodFromCommand(player.region, interaction); - // const tankIdRaw = interaction.options.getString("tank")!; - // const tankId = - // commandGroup === "tank" - // ? await resolveTankId(tankIdRaw, interaction.locale) - // : null; - // const path = commandToURL(interaction, { - // ...player, - // ...getCustomPeriodParams(interaction), - // tankId, - // }); - - // return [ - // await render( - // player, - // period, - // commandGroup, - // tankId, - // interaction.locale - // ), - // buttonRefresh(interaction, path), - // await getBlitzStarsLinkButton( - // player.region, - // player.id, - // tankId ?? undefined - // ), - // ]; - }, - - autocomplete: (interaction) => { - autocompleteUsername(interaction); - autocompleteTanks(interaction); - }, - - async button(interaction) { - const url = new URL(`https://example.com/${interaction.customId}`); - const path = url.pathname.split("/"); - const commandGroup = path[2] as EvolutionStatType; - const player = await resolvePlayerFromButton(interaction); - const period = resolvePeriodFromButton(player.region, interaction); - - return await render( - player, - period, - commandGroup, - parseInt(url.searchParams.get("tankId") ?? "0") || null, - interaction.locale, - ); - }, - }); - }, -); diff --git a/packages/bot/src/commands/fullStats.tsx b/packages/bot/src/commands/fullStats.tsx deleted file mode 100644 index 3cc13ce3d..000000000 --- a/packages/bot/src/commands/fullStats.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import { - emblemURL, - getAccountInfo, - getClanAccountInfo, - getTierWeights, -} from "@blitzkit/core"; -import { Locale } from "discord.js"; -import { CommandWrapper } from "../components/CommandWrapper"; -import { GenericAllStats } from "../components/GenericAllStats"; -import { NoData } from "../components/NoData"; -import { TierWeights } from "../components/TierWeights"; -import { TitleBar } from "../components/TitleBar"; -import { filtersToDescription } from "../core/blitzkit/filtersToDescription"; -import { filterStats, type StatFilters } from "../core/blitzstars/filterStats"; -import { getStatsInPeriod } from "../core/blitzstars/getStatsInPeriod"; -import { addPeriodicFilterOptions } from "../core/discord/addPeriodicFilterOptions"; -import { addUsernameChoices } from "../core/discord/addUsernameChoices"; -import { autocompleteTanks } from "../core/discord/autocompleteTanks"; -import { autocompleteUsername } from "../core/discord/autocompleteUsername"; -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { getFiltersFromButton } from "../core/discord/getFiltersFromButton"; -import { resolvePeriodFromButton } from "../core/discord/resolvePeriodFromButton"; -import { type ResolvedPeriod } from "../core/discord/resolvePeriodFromCommand"; -import { resolvePlayerFromButton } from "../core/discord/resolvePlayerFromButton"; -import { type ResolvedPlayer } from "../core/discord/resolvePlayerFromCommand"; -import { translator } from "../core/localization/translator"; -import type { CommandRegistry } from "../events/interactionCreate"; - -async function render( - { region, id }: ResolvedPlayer, - { start, end, name }: ResolvedPeriod, - filters: StatFilters, - locale: Locale, -) { - const { nickname } = await getAccountInfo(region, id); - const clan = (await getClanAccountInfo(region, id, ["clan"]))?.clan; - const clanImage = clan ? emblemURL(clan.emblem_set_id) : undefined; - const diffedTankStats = await getStatsInPeriod( - region, - id, - start, - end, - locale, - ); - const { stats, supplementary, filteredOrder } = await filterStats( - diffedTankStats, - filters, - ); - const filterDescriptions = await filtersToDescription(filters, locale); - const tierWeights = await getTierWeights(diffedTankStats.diff, filteredOrder); - - return ( - - - - {stats.battles === 0 && ( - - )} - {stats.battles > 0 && } - {stats.battles > 0 && ( - - )} - - ); -} - -export const fullStatsCommand = new Promise( - async (resolve) => { - resolve({ - command: await addPeriodicFilterOptions( - createLocalizedCommand("full-stats"), - (option) => option.addStringOption(addUsernameChoices), - ), - - async handler(interaction) { - const { strings } = translator(interaction.locale); - return strings.bot.commands.dead; - - // const player = await resolvePlayerFromCommand(interaction); - // const period = resolvePeriodFromCommand(player.region, interaction); - // const filters = await getFiltersFromCommand(interaction); - // const path = commandToURL(interaction, { - // ...player, - // ...getCustomPeriodParams(interaction), - // ...filters, - // }); - - // return Promise.all([ - // render(player, period, filters, interaction.locale), - // buttonRefresh(interaction, path), - // 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 render(player, period, filters, interaction.locale); - }, - }); - }, -); diff --git a/packages/bot/src/commands/inactive.tsx b/packages/bot/src/commands/inactive.tsx deleted file mode 100644 index 128384a9a..000000000 --- a/packages/bot/src/commands/inactive.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import { getAccountInfo, getClanInfo } from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import { Locale } from "discord.js"; -import { CommandWrapper } from "../components/CommandWrapper"; -import { GenericStats } from "../components/GenericStats"; -import { NoData } from "../components/NoData"; -import { TitleBar } from "../components/TitleBar"; -import { addClanChoices } from "../core/discord/addClanChoices"; -import { autocompleteClan } from "../core/discord/autocompleteClan"; -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { localizationObject } from "../core/discord/localizationObject"; -import { resolveClanFromCommand } from "../core/discord/resolveClanFromCommand"; -import { translator } from "../core/localization/translator"; -import type { CommandRegistry } from "../events/interactionCreate"; - -const DEFAULT_THRESHOLD = 7; - -export const inactiveCommand = new Promise((resolve) => { - resolve({ - command: createLocalizedCommand("inactive") - .addStringOption(addClanChoices) - .addNumberOption((option) => { - const { strings } = translator(Locale.EnglishUS); - - return option - .setName("threshold") - .setNameLocalizations( - localizationObject( - (strings) => strings.bot.commands.inactive.options.threshold.name, - undefined, - true - ) - ) - .setDescription( - strings.bot.commands.inactive.options.threshold.description - ) - .setDescriptionLocalizations( - localizationObject( - (strings) => - strings.bot.commands.inactive.options.threshold.description - ) - ) - .setMinValue(0); - }), - - async handler(interaction) { - const { strings } = translator(interaction.locale); - const { region, id } = await resolveClanFromCommand(interaction); - const threshold = - interaction.options.getNumber("threshold")! ?? DEFAULT_THRESHOLD; - const time = new Date().getTime() / 1000; - const clanInfo = await getClanInfo(region, id); - const accountInfo = await getAccountInfo(region, clanInfo.members_ids); - const inactive = accountInfo - .map( - (account) => - [ - account.nickname, - (time - account.last_battle_time) / 60 / 60 / 24, - ] as [string, number] - ) - .filter(([, inactiveDays]) => inactiveDays >= threshold) - .sort((a, b) => b[1] - a[1]) - .map( - ([name, days]) => - [ - name, - literals(strings.bot.commands.inactive.body.listing, { - days: days.toFixed(0), - }), - ] as [string, string] - ); - const hasInactiveMembers = inactive.length > 0; - - return ( - - - - {!hasInactiveMembers && ( - - )} - {hasInactiveMembers && } - - ); - }, - - autocomplete: autocompleteClan, - }); -}); diff --git a/packages/bot/src/commands/link.tsx b/packages/bot/src/commands/link.tsx deleted file mode 100644 index 3961a6687..000000000 --- a/packages/bot/src/commands/link.tsx +++ /dev/null @@ -1,193 +0,0 @@ -import { getAccountInfo, getClanAccountInfo } from '@blitzkit/core'; -import { teal } from '@radix-ui/colors'; -import { Glow } from '../components/AllStatsOverview/components/HeroStat/components/Glow'; -import { CommandWrapper } from '../components/CommandWrapper'; -import { linkBlitzAndDiscord } from '../core/db/linkBlitzAndDiscord'; -import { addUsernameChoices } from '../core/discord/addUsernameChoices'; -import { autocompleteUsername } from '../core/discord/autocompleteUsername'; -import { createLocalizedCommand } from '../core/discord/createLocalizedCommand'; -import { embedInfo } from '../core/discord/embedInfo'; -import { resolvePlayerFromCommand } from '../core/discord/resolvePlayerFromCommand'; -import { translator } from '../core/localization/translator'; -import type { CommandRegistry } from '../events/interactionCreate'; -import { theme } from '../stitches.config'; - -export const verifyCommand = new Promise((resolve) => { - resolve({ - command: createLocalizedCommand('link').addStringOption((option) => - addUsernameChoices(option).setRequired(true), - ), - - async handler(interaction) { - const { strings } = translator(interaction.locale); - const { id, region } = await resolvePlayerFromCommand(interaction); - const discordId = BigInt(interaction.user.id); - const accountInfo = await getAccountInfo(region, id); - const clanAccountInfo = await getClanAccountInfo(region, id, ['clan']); - - await linkBlitzAndDiscord(discordId, id); - - return [ - -
- - -
-
- avatar - -
- - {interaction.user.displayName} - - - @{interaction.user.username} - -
-
- -
- accounts linked - - {strings.bot.commands.link.body.accounts_linked} - -
- -
- {clanAccountInfo?.clan ? ( - clan emblem - ) : null} -
- - {accountInfo.nickname} - - - {clanAccountInfo?.clan - ? `[${clanAccountInfo?.clan?.tag}] • ` - : ''} - {strings.common.regions.normal[region]} - -
-
-
- - -
-
, - - embedInfo( - strings.bot.commands.link.embed.title, - strings.bot.commands.link.embed.description, - ), - ]; - }, - - autocomplete: autocompleteUsername, - }); -}); diff --git a/packages/bot/src/commands/ownedTanks.tsx b/packages/bot/src/commands/ownedTanks.tsx deleted file mode 100644 index ba4b6e549..000000000 --- a/packages/bot/src/commands/ownedTanks.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import { - TANK_CLASSES, - TIER_ROMAN_NUMERALS, - TankDefinition, - flags, - getAccountInfo, - getTankStats, -} from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import markdownEscape from "markdown-escape"; -import { - gameDefinitions, - tankDefinitions, -} from "../core/blitzkit/nonBlockingPromises"; -import { addTierChoices } from "../core/discord/addTierChoices"; -import { addUsernameChoices } from "../core/discord/addUsernameChoices"; -import { autocompleteUsername } from "../core/discord/autocompleteUsername"; -import { chunkLines } from "../core/discord/chunkLines"; -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { resolvePlayerFromCommand } from "../core/discord/resolvePlayerFromCommand"; -import { translator } from "../core/localization/translator"; -import { type CommandRegistry } from "../events/interactionCreate"; - -export const ownedTanksCommand = new Promise((resolve) => { - resolve({ - command: createLocalizedCommand("owned-tanks") - .addStringOption(addTierChoices) - .addStringOption(addUsernameChoices), - - async handler(interaction) { - const { strings, unwrap } = translator(interaction.locale); - const tier = Number(interaction.options.getString("tier")); - const { id, region } = await resolvePlayerFromCommand(interaction); - const accountInfo = await getAccountInfo(region, id); - const tankStats = await getTankStats(region, id); - - if (tankStats === null) { - return strings.bot.common.errors.no_tank_stats; - } - - const filteredTanks = ( - await Promise.all( - tankStats.map(async (tankData) => ({ - tankDefinitions: (await tankDefinitions).tanks[tankData.tank_id]!, - id: tankData.tank_id, - })), - ) - ).filter((tank) => tank.tankDefinitions?.tier === tier); - const groupedTanks: Record = {}; - const nations: string[] = []; - - filteredTanks.forEach((tank) => { - if (groupedTanks[tank.tankDefinitions.nation] === undefined) { - groupedTanks[tank.tankDefinitions.nation] = [tank.tankDefinitions]; - nations.push(tank.tankDefinitions.nation); - } else { - groupedTanks[tank.tankDefinitions.nation].push(tank.tankDefinitions); - } - }); - - const awaitedTankDefinitions = await tankDefinitions; - const awaitedGameDefinitions = await gameDefinitions; - const title = literals(strings.bot.commands.owned_tanks.body.title, { - name: markdownEscape(accountInfo.nickname), - tier: TIER_ROMAN_NUMERALS[tier], - }); - const tankList = tankStats - .map(({ tank_id }) => awaitedTankDefinitions.tanks[tank_id]) - .filter((tank) => tank.tier === tier) - .sort( - (a, b) => - TANK_CLASSES.indexOf(a.class) - TANK_CLASSES.indexOf(b.class), - ) - .sort( - (a, b) => - awaitedGameDefinitions.nations.indexOf(a.nation) - - awaitedGameDefinitions.nations.indexOf(b.nation), - ) - .map( - (tank) => - `[${flags[tank.nation]} ${markdownEscape(unwrap(tank.name!))}]()`, - ); - const lines = [title, ...tankList]; - - return chunkLines(lines); - }, - - autocomplete: autocompleteUsername, - }); -}); diff --git a/packages/bot/src/commands/permissions.ts b/packages/bot/src/commands/permissions.ts deleted file mode 100644 index 1885c699e..000000000 --- a/packages/bot/src/commands/permissions.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { literals } from "@blitzkit/i18n"; -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { translator } from "../core/localization/translator"; -import { CommandRegistry } from "../events/interactionCreate"; - -export const permissionsCommand = new Promise((resolve) => { - resolve({ - command: createLocalizedCommand("permissions"), - - async handler(interaction) { - const { strings } = translator(interaction.locale); - // friday the 13th permissions easter egg - const isFridayThe13th = - new Date().getDay() === 5 && new Date().getDate() === 13; - const permissions = [ - [ - interaction.appPermissions?.has("ViewChannel"), - strings.bot.commands.permissions.body.view_channels, - ], - [ - interaction.appPermissions?.has("ReadMessageHistory"), - strings.bot.commands.permissions.body.read_message_history, - ], - [ - interaction.appPermissions?.has("AttachFiles"), - strings.bot.commands.permissions.body.attach_files, - ], - [ - interaction.appPermissions?.has("UseExternalEmojis"), - strings.bot.commands.permissions.body.external_emojis, - ], - ]; - const body = permissions - .map(([hasPermission, description]) => { - if (hasPermission) { - return `${isFridayThe13th ? "😇" : "✅"} ${description}`; - } else { - return `${isFridayThe13th ? "💀" : "❌"} ${description}`; - } - }) - .join("\n"); - - return literals(strings.bot.commands.permissions.body.title, { body }); - }, - }); -}); diff --git a/packages/bot/src/commands/ping.ts b/packages/bot/src/commands/ping.ts deleted file mode 100644 index e73a09fef..000000000 --- a/packages/bot/src/commands/ping.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { literals } from "@blitzkit/i18n"; -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { translator } from "../core/localization/translator"; -import type { CommandRegistry } from "../events/interactionCreate"; - -export const pingCommand = new Promise((resolve) => { - resolve({ - handlesInteraction: true, - - command: createLocalizedCommand("ping", [ - { subcommand: "blitzkit" }, - { subcommand: "wotb" }, - ]), - - async handler(interaction) { - const { strings } = translator(interaction.locale); - const subcommand = interaction.options.getSubcommand(); - const executionStart = Date.now(); - - if (subcommand === "blitzkit") { - await interaction.editReply(strings.bot.commands.ping.body.pong); - } else { - await fetch("https://api.wotblitz.com/"); - } - - const executionTime = Date.now() - executionStart; - interaction.editReply( - literals(strings.bot.commands.ping.body.pong_with_time, { - time: executionTime.toFixed(0), - }) - ); - }, - }); -}); diff --git a/packages/bot/src/commands/playerInfo.ts b/packages/bot/src/commands/playerInfo.ts deleted file mode 100644 index 2b4c61268..000000000 --- a/packages/bot/src/commands/playerInfo.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { getAccountInfo } from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import markdownEscape from "markdown-escape"; -import { addUsernameChoices } from "../core/discord/addUsernameChoices"; -import { autocompleteUsername } from "../core/discord/autocompleteUsername"; -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { embedInfo } from "../core/discord/embedInfo"; -import { markdownTable } from "../core/discord/markdownTable"; -import { resolvePlayerFromCommand } from "../core/discord/resolvePlayerFromCommand"; -import { translator } from "../core/localization/translator"; -import { CommandRegistry } from "../events/interactionCreate"; - -export const playerInfoCommand = new Promise((resolve) => { - resolve({ - command: - createLocalizedCommand("player-info").addStringOption(addUsernameChoices), - - async handler(interaction) { - const { strings } = translator(interaction.locale); - const account = await resolvePlayerFromCommand(interaction); - const { id, region: region } = account; - const accountInfo = await getAccountInfo(region, id); - - return embedInfo( - literals(strings.bot.commands.player_info.body.title, { - name: markdownEscape(accountInfo.nickname), - }), - - markdownTable([ - [ - strings.bot.commands.player_info.body.nickname, - `${accountInfo.nickname}`, - ], - [ - strings.bot.commands.player_info.body.battles, - `${accountInfo.statistics.all.battles}`, - ], - [ - strings.bot.commands.player_info.body.winrate, - `${( - 100 * - (accountInfo.statistics.all.wins / - accountInfo.statistics.all.battles) - ).toFixed(2)}%`, - ], - [], - [ - strings.bot.commands.player_info.body.account_id, - `${accountInfo.account_id}`, - ], - [ - strings.bot.commands.player_info.body.created, - new Date(accountInfo.created_at * 1000).toLocaleDateString( - interaction.locale - ), - ], - [ - strings.bot.commands.player_info.body.last_battle, - new Date(accountInfo.last_battle_time * 1000).toLocaleDateString( - interaction.locale - ), - ], - ]) - ); - }, - - autocomplete: autocompleteUsername, - }); -}); diff --git a/packages/bot/src/commands/rating.tsx b/packages/bot/src/commands/rating.tsx deleted file mode 100644 index 16b1c7feb..000000000 --- a/packages/bot/src/commands/rating.tsx +++ /dev/null @@ -1,535 +0,0 @@ -import { - deltaBkrlBlitzStats, - emblemURL, - getAccountInfo, - getArchivedRatingLeaderboard, - getClanAccountInfo, - getLeagueFromScore, - getRatingInfo, - getRatingNeighbors, - imgur, -} from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import { SlashCommandSubcommandBuilder } from "discord.js"; -import markdownEscape from "markdown-escape"; -import { Glow } from "../components/AllStatsOverview/components/HeroStat/components/Glow"; -import { CommandWrapper } from "../components/CommandWrapper"; -import { DeltaCaret } from "../components/DeltaCaret"; -import { TitleBar } from "../components/TitleBar"; -import { normalizeLeagueIcon } from "../core/blitz/normalizeLeagueIcon"; -import { iconPng } from "../core/blitzkit/iconPng"; -import { addUsernameChoices } from "../core/discord/addUsernameChoices"; -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { resolvePlayerFromCommand } from "../core/discord/resolvePlayerFromCommand"; -import { translator } from "../core/localization/translator"; -import type { CommandRegistry } from "../events/interactionCreate"; -import { theme } from "../stitches.config"; - -const LEAGUE_ACCENT = [ - "_purple", - "_sand", - "_amber", - "_mauve", - "_brown", -] as const; - -export const ratingCommand = new Promise((resolve) => { - resolve({ - command: createLocalizedCommand("rating", [ - { - subcommand: "today", - modify(option: SlashCommandSubcommandBuilder) { - option.addStringOption(addUsernameChoices); - }, - }, - { - subcommand: "season", - modify(option: SlashCommandSubcommandBuilder) { - option.addStringOption(addUsernameChoices); - }, - }, - ]), - - async handler(interaction) { - const subcommand = interaction.options.getSubcommand(true) as - | "season" - | "today"; - const { strings } = translator(interaction.locale); - const { id, region } = await resolvePlayerFromCommand(interaction); - const clan = (await getClanAccountInfo(region, id, ["clan"]))?.clan; - const clanImage = clan ? emblemURL(clan.emblem_set_id) : undefined; - - const ratingInfo = await getRatingInfo(region); - - if (ratingInfo.detail) { - throw new Error(`Rating info not available for ${region}`); - } - - const leaderboard = await getArchivedRatingLeaderboard( - region, - ratingInfo.current_season - (subcommand === "today" ? 0 : 1) - ); - - if (leaderboard.version!.$case === "v1") { - throw new Error( - "Encountered bkrl minimal format in latest season. Wait till next cron?" - ); - } - - const accountInfo = await getAccountInfo(region, id, [ - "statistics.rating", - ]); - const statsIndex = leaderboard.version!.value.entries.findIndex( - (entry) => entry.id === id - ); - const statsA = leaderboard - ? leaderboard.version!.value.entries[statsIndex!] - : null; - const positionA = leaderboard ? statsIndex! + 1 : undefined; - const statsB1 = accountInfo.statistics.rating!; - const statsB2Array = (await getRatingNeighbors(region, id, 0)).neighbors; - - if (statsB2Array === undefined) { - return literals(strings.bot.commands.rating.errors.no_participation, { - name: markdownEscape(accountInfo.nickname), - }); - } - - const [statsB2] = statsB2Array; - const league = getLeagueFromScore(statsB2.score); - - if (!statsA) { - return strings.bot.commands.rating.errors.unrecorded_stats; - } - if (!statsB1) { - return literals(strings.bot.commands.rating.errors.no_participation, { - name: markdownEscape(accountInfo.nickname), - }); - } - - const reward = ratingInfo.rewards.find( - (reward) => - statsB2.number >= reward.from_position && - statsB2.number <= reward.to_position - ); - const positionDelta = leaderboard - ? statsB2.number - positionA! - : undefined; - const delta = deltaBkrlBlitzStats( - statsA, - accountInfo.statistics.rating!, - statsB2 - ); - const leagueTheme = LEAGUE_ACCENT[league.index]; - let rewardImage = reward - ? reward.type === "vehicle" - ? reward.vehicle.preview_image_url - : reward.stuff.image_url - : undefined; - - if (rewardImage) { - const { ok } = await fetch(rewardImage); - - if (!ok) { - rewardImage = imgur("kP88vqr"); - } - } - - return ( - - - -
-
- {reward === undefined && ( - - )} - -
- {`League - -
-
- - {statsB2.score.toLocaleString(interaction.locale)} - - {delta.score !== 0 && ( - <> - - 0 - ? theme.colors.textLowContrast_green - : theme.colors.textLowContrast_red, - fontSize: 16, - }} - > - {Math.abs(delta.score).toLocaleString( - interaction.locale - )} - - - )} -
- -
- - #{statsB2.number.toLocaleString(interaction.locale)} - - {positionDelta !== undefined && positionDelta !== 0 && ( - <> - - 0 - ? theme.colors.textLowContrast_red - : theme.colors.textLowContrast_green, - fontSize: 16, - }} - > - {Math.abs(positionDelta).toLocaleString( - interaction.locale - )} - - - )} -
-
-
- - {reward && ( -
-
-
- - {reward.type === "vehicle" - ? reward.vehicle.user_string - : reward.stuff.title} - -
- -
- - Reward - {reward.type === "stuff" ? ` x${reward.count}` : ""} - -
-
- - -
- )} - - {reward === undefined && ( - - )} -
- - {/* separated because fragment middle man invalidates relative parent: https://github.com/vercel/satori/issues/605 */} - {reward && ( - - )} - {reward && ( - - )} -
- - {delta.battles > 0 && ( -
-
- - {delta.battles.toLocaleString(interaction.locale)} - - - Battles - -
-
- - {(100 * (delta.wins / delta.battles)).toFixed(0)}% - - - Winrate - -
-
- - {Math.round(delta.damage / delta.battles).toLocaleString( - interaction.locale - )} - - - Damage - -
-
- - {(100 * (delta.survived / delta.battles)).toFixed(0)}% - - - Survival - -
-
- - {(delta.kills / delta.battles).toFixed(2)} - - - Kills - -
-
- )} -
- ); - }, - }); -}); diff --git a/packages/bot/src/commands/ratingLeaderboard.tsx b/packages/bot/src/commands/ratingLeaderboard.tsx deleted file mode 100644 index 458e70d52..000000000 --- a/packages/bot/src/commands/ratingLeaderboard.tsx +++ /dev/null @@ -1,468 +0,0 @@ -import { - FIRST_MINIMAL_ARCHIVED_RATING_SEASON, - getAccountInfo, - getArchivedLatestSeasonNumber, - getArchivedRatingLeaderboard, - getClanAccountInfo, - getRatingInfo, - getRatingLeague, - getRatingNeighbors, - imgur, - isOnGoingRatingSeason, - LEAGUES, - type RatingInfo, - RatingLeaderboard, - type Region, -} from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import { - type APIApplicationCommandOptionChoice, - Locale, - SlashCommandSubcommandBuilder, -} from "discord.js"; -import { range } from "lodash-es"; -import markdownEscape from "markdown-escape"; -import { CommandWrapper } from "../components/CommandWrapper"; -import * as Leaderboard from "../components/Leaderboard"; -import { TitleBar } from "../components/TitleBar"; -import { UserError } from "../core/blitzkit/userError"; -import { addRegionChoices } from "../core/discord/addRegionChoices"; -import { addUsernameChoices } from "../core/discord/addUsernameChoices"; -import { autocompleteUsername } from "../core/discord/autocompleteUsername"; -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { localizationObject } from "../core/discord/localizationObject"; -import { resolvePlayerFromCommand } from "../core/discord/resolvePlayerFromCommand"; -import { translator } from "../core/localization/translator"; -import type { CommandRegistry } from "../events/interactionCreate"; - -interface SimplifiedPlayer { - id: number; - score: number; - position: number; - clan: string | undefined; - nickname: string; -} - -const DEFAULT_LIMIT = 10; - -export const ratingLeaderboardCommand = new Promise( - async (resolve) => { - const latestArchivedSeasonNumber = await getArchivedLatestSeasonNumber(); - const onGoingSeason = await isOnGoingRatingSeason(); - const seasonNumbers = range( - FIRST_MINIMAL_ARCHIVED_RATING_SEASON, - latestArchivedSeasonNumber + (onGoingSeason ? 1 : 0) + 1 - ).slice(-25); - - function addOptions(option: SlashCommandSubcommandBuilder) { - const { strings } = translator(Locale.EnglishUS); - - return option - .addIntegerOption((option) => - option - .setName(strings.bot.commands.rating_leaderboard.options.limit.name) - .setNameLocalizations( - localizationObject( - (strings) => - strings.bot.commands.rating_leaderboard.options.limit.name, - undefined, - true - ) - ) - .setDescription( - literals( - strings.bot.commands.rating_leaderboard.options.limit - .description, - { count: DEFAULT_LIMIT } - ) - ) - .setDescriptionLocalizations( - localizationObject( - (strings) => - strings.bot.commands.rating_leaderboard.options.limit - .description, - { count: DEFAULT_LIMIT } - ) - ) - .setRequired(false) - .setMinValue(5) - .setMaxValue(30) - ) - .addStringOption((option) => - option - .setName( - strings.bot.commands.rating_leaderboard.options.season.name - ) - .setNameLocalizations( - localizationObject( - (strings) => - strings.bot.commands.rating_leaderboard.options.season.name, - undefined, - true - ) - ) - .setDescription( - strings.bot.commands.rating_leaderboard.options.season.description - ) - .setDescriptionLocalizations( - localizationObject( - (strings) => - strings.bot.commands.rating_leaderboard.options.season - .description - ) - ) - .setRequired(false) - .addChoices( - ...seasonNumbers - .map((number, index) => { - const current = - index === seasonNumbers.length - 1 && onGoingSeason; - - return { - name: current - ? literals( - strings.bot.commands.rating_leaderboard.options.season - .choices.current, - { season: number } - ) - : `${number}`, - value: `${number}`, - name_localizations: current - ? localizationObject( - (strings) => - strings.bot.commands.rating_leaderboard.options - .season.choices.current, - { season: number } - ) - : undefined, - } satisfies APIApplicationCommandOptionChoice; - }) - .reverse() - ) - ); - } - - resolve({ - command: createLocalizedCommand("rating-leaderboard", [ - { - subcommand: "neighbors", - modify(option: SlashCommandSubcommandBuilder) { - option.addStringOption(addUsernameChoices); - addOptions(option); - }, - }, - { - subcommand: "league", - modify(option: SlashCommandSubcommandBuilder) { - option - .addStringOption((option) => { - const { strings } = translator(Locale.EnglishUS); - - return option - .setName( - strings.bot.commands.rating_leaderboard.options.league.name - ) - .setNameLocalizations( - localizationObject( - (strings) => - strings.bot.commands.rating_leaderboard.options.league - .name, - undefined, - true - ) - ) - .setDescription( - strings.bot.commands.rating_leaderboard.options.league - .description - ) - .setDescriptionLocalizations( - localizationObject( - (strings) => - strings.bot.commands.rating_leaderboard.options.league - .description - ) - ) - .addChoices( - ...LEAGUES.map( - ({ name }, value) => - ({ - name: ( - strings.common.leagues as Record - )[name], - value: `${value}`, - name_localizations: localizationObject( - (strings) => - ( - strings.common.leagues as Record - )[name] - ), - } satisfies APIApplicationCommandOptionChoice) - ) - ) - .setRequired(true); - }) - .addStringOption(addRegionChoices); - addOptions(option); - }, - }, - ]), - - // BIG TODO: title seems to be in english - - async handler(interaction) { - const { strings } = translator(interaction.locale); - const subcommand = interaction.options.getSubcommand(true) as - | "league" - | "neighbors"; - const limit = interaction.options.getInteger("limit") ?? DEFAULT_LIMIT; - const seasonOption = interaction.options.getString("season"); - const season = seasonOption - ? parseInt(seasonOption) - : onGoingSeason - ? latestArchivedSeasonNumber + 1 - : latestArchivedSeasonNumber; - const isCurrentSeason = season === latestArchivedSeasonNumber + 1; - let titleName: string; - let titleImage: string | undefined; - let titleDescription: string; - let playersAfter: number; - let players: SimplifiedPlayer[] | undefined; - let archivedLeaderboard: RatingLeaderboard | null; - let playerId: number | undefined; - let regionRatingInfo: RatingInfo | null; - - if (subcommand === "league") { - const leagueIndex = parseInt( - interaction.options.getString("league")! - ); - const region = interaction.options.getString("region") as Region; - regionRatingInfo = await (isCurrentSeason - ? getRatingInfo(region) - : null); - - if ( - regionRatingInfo !== null && - regionRatingInfo.detail !== undefined - ) { - return strings.bot.commands.rating_leaderboard.body - .no_ongoing_season; - } - - const result = isCurrentSeason - ? await getRatingLeague(region, leagueIndex).then(async (result) => - result.slice(0, limit).map( - (player) => - ({ - id: player.spa_id, - score: player.score, - position: player.number, - clan: player.clan_tag, - nickname: player.nickname, - } satisfies SimplifiedPlayer) - ) - ) - : await getArchivedRatingLeaderboard(region, season!).then( - async (players) => { - const firstPlayerIndex = - leagueIndex === 0 - ? 0 - : players.version!.value.entries.findIndex( - (player) => - player.score < LEAGUES[leagueIndex - 1].minScore - ); - const lastPlayerIndex = firstPlayerIndex + limit - 1; - const trimmed = players.version!.value.entries.slice( - firstPlayerIndex, - lastPlayerIndex + 1 - ); - const clanData = await getClanAccountInfo( - region, - trimmed.map(({ id }) => id), - ["clan"] - ); - - return trimmed.map( - (player, index) => - ({ - id: player.id, - score: player.score, - position: firstPlayerIndex + index + 1, - clan: clanData[player.id]?.clan?.tag, - nickname: - clanData[index]?.account_name ?? - literals( - strings.bot.commands.rating_leaderboard.body - .deleted_player, - { id: player.id } - ), - } satisfies SimplifiedPlayer) - ); - } - ); - const leagueInfo = LEAGUES[leagueIndex]; - archivedLeaderboard = await getArchivedRatingLeaderboard( - region, - season - ); - - players = result; - playersAfter = - (regionRatingInfo === null - ? archivedLeaderboard.version!.value.entries.length - : regionRatingInfo.count) - result[result.length - 1].position; - titleName = `${ - leagueInfo - ? (strings.common.leagues as Record)[ - LEAGUES[leagueInfo.index].name - ] - : "" - } - ${strings.common.regions.short[region]}`; - titleImage = imgur(leagueInfo.icon); - titleDescription = literals( - strings.bot.commands.rating_leaderboard.body.top_players, - { count: limit } - ); - } else { - const { region, id } = await resolvePlayerFromCommand(interaction); - regionRatingInfo = isCurrentSeason - ? await getRatingInfo(region) - : null; - - if ( - regionRatingInfo !== null && - regionRatingInfo.detail !== undefined - ) { - return strings.bot.commands.rating_leaderboard.body - .no_ongoing_season; - } - - const accountInfo = await getAccountInfo(region, id); - const clan = (await getClanAccountInfo(region, id, ["clan"]))?.clan; - const neighbors = isCurrentSeason - ? await getRatingNeighbors(region, id, Math.round(limit / 2)).then( - (players) => - players.neighbors.map( - (player) => - ({ - id: player.spa_id, - position: player.number, - score: player.score, - clan: player.clan_tag, - nickname: player.nickname, - } satisfies SimplifiedPlayer) - ) - ) - : await getArchivedRatingLeaderboard(region, season!).then( - async (players) => { - const playerIndex = players.version!.value.entries.findIndex( - (player) => player.id === id - ); - - if (playerIndex === -1) { - throw new UserError( - literals( - strings.bot.commands.rating_leaderboard.body - .no_participation, - { player: markdownEscape(accountInfo.nickname), season } - ) - ); - } - - const halfRange = Math.round(limit / 2); - const trimmed = players.version!.value.entries.slice( - playerIndex - halfRange, - playerIndex + halfRange + 1 - ); - const clanData = await getClanAccountInfo( - region, - trimmed.map((player) => player.id), - ["clan"] - ); - - return trimmed.map((player, index) => ({ - id: player.id, - score: player.score, - position: playerIndex + index + 1, - clan: clanData[index]?.clan?.tag, - nickname: clanData[index] - ? clanData[index]!.account_name - : `Deleted ${player.id}`, - })); - } - ); - archivedLeaderboard = await getArchivedRatingLeaderboard( - region, - season - ); - - players = neighbors; - titleImage = clan - ? `https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clan.emblem_set_id}.png` - : undefined; - titleName = neighbors ? accountInfo.nickname : ""; - titleDescription = - strings.bot.commands.rating_leaderboard.body.neighbors; - playersAfter = - (regionRatingInfo === null - ? archivedLeaderboard.version!.value.entries.length - : regionRatingInfo.count) - - neighbors[neighbors.length - 1].position; - playerId = id; - } - - const items = players.map((player) => { - const midnightIndex = - archivedLeaderboard?.version!.value.entries.findIndex( - (item) => item.id === player.id - ); - const midnightScore = - midnightIndex === undefined - ? player.score - : archivedLeaderboard!.version!.value.entries[midnightIndex] - .score; - const midnightPosition = - midnightIndex === undefined ? player.position : midnightIndex + 1; - - return ( - - ); - }); - - return ( - - - - {items && ( - - {items} - {playersAfter > 0 && ( - - )} - - )} - - ); - }, - - autocomplete: autocompleteUsername, - }); - } -); diff --git a/packages/bot/src/commands/replay.tsx b/packages/bot/src/commands/replay.tsx deleted file mode 100644 index ed6da65fc..000000000 --- a/packages/bot/src/commands/replay.tsx +++ /dev/null @@ -1,592 +0,0 @@ -import { - calculateWN8, - emblemURL, - getClanAccountInfo, - idToRegion, - tankIcon, - TIER_ROMAN_NUMERALS, -} from "@blitzkit/core"; -import { Locale } from "discord.js"; -import { clamp } from "lodash-es"; -import { CommandWrapper } from "../components/CommandWrapper"; -import { TitleBar } from "../components/TitleBar"; -import { iconPng } from "../core/blitzkit/iconPng"; -import { - averageDefinitions, - mapDefinitions, - tankDefinitions, -} from "../core/blitzkit/nonBlockingPromises"; -import { RenderConfiguration } from "../core/blitzkit/renderConfiguration"; -import { buttonLink } from "../core/discord/buttonLink"; -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { localizationObject } from "../core/discord/localizationObject"; -import { translator } from "../core/localization/translator"; -import { type CommandRegistry } from "../events/interactionCreate"; -import { theme } from "../stitches.config"; - -type WotInspectorReplaySubmission = - | { status: "error"; data: unknown; error: { message: string } } - | { - status: "ok"; - data: { - download_url: string; - view_url: string; - summary: { - battle_start_time: string; - map_name: string; - battle_start_timestamp: number; - battle_duration: number; - title: string; - arena_unique_id: string; - allies: number[]; - enemies: number[]; - protagonist: number; - player_name: string; - vehicle: string; - exp_base: number; - exp_total: number; - credits_base: number; - credits_total: number; - room_type: number; - battle_type: number; - mastery_badge: number; - battle_result: number; - vehicle_tier: number; - vehicle_type: number; - protagonist_team: number; - winner_team: number; - details: { - achievements: unknown[]; - base_capture_points: number; - base_defend_points: number; - chassis_id: unknown | null; - clan_tag: string; - clanid: number; - credits: number; - damage_assisted: number; - damage_assisted_track: number; - damage_blocked: unknown | null; - damage_made: number; - damage_received: number; - dbid: number; - death_reason: number; - distance_travelled: number; - enemies_damaged: number; - enemies_destroyed: number; - enemies_spotted: number; - entity_id: number; - exp: number; - exp_for_assist: number; - exp_for_damage: number; - exp_team_bonus: number; - gun_id: unknown | null; - hero_bonus_credits: number; - hero_bonus_exp: number; - hitpoints_left: number; - hits_bounced: number; - hits_pen: number; - hits_received: number; - hits_splash: number; - killed_by: number; - name: string; - shots_hit: number; - shots_made: number; - shots_pen: number; - shots_splash: number; - squad_index: number; - team: number; - time_alive: number; - turret_id: unknown | null; - vehicle_descr: number; - wp_points_earned: number; - wp_points_stolen: number; - }; - }; - }; - error: {}; - }; - -interface WotInspectorReplay { - id: string; - map_id: number; - battle_duration: number; - title: unknown | null; - player_name: string; - protagonist: number; - vehicle_descr: number; - mastery_badge: number; - exp_base: number; - enemies_spotted: number; - enemies_destroyed: number; - damage_assisted: number; - damage_made: number; - details_url: string; - download_url: string; - game_version: { - name: string; - package: string; - }; - arena_unique_id: string; - download_count: number; - data_version: number; - private: boolean; - private_clan: boolean; - battle_start_time: Date; - upload_time: Date; - allies: number[]; - enemies: number[]; - protagonist_clan: number; - protagonist_team: number; - battle_result: number; - credits_base: number; - tags: number[]; - battle_type: number; - room_type: number; - last_accessed_time: Date; - winner_team: number; - finish_reason: number; - players_data: WotInspectorReplayPlayerData[]; - exp_total: number; - credits_total: number; - repair_cost: number; - exp_free: number; - exp_free_base: number; - exp_penalty: number; - credits_penalty: number; - credits_contribution_in: number; - credits_contribution_out: number; - camouflage_id: number; -} - -export interface WotInspectorReplayPlayerData { - team: number; - name: string; - entity_id: number; - dbid: number; - clanid: number | null; - clan_tag: null | string; - hitpoints_left: number; - credits: number; - exp: number; - shots_made: number; - shots_hit: number; - shots_splash: number; - shots_pen: number; - damage_made: number; - damage_received: number; - damage_assisted: number; - damage_assisted_track: number; - hits_received: number; - hits_bounced: number; - hits_splash: number; - hits_pen: number; - enemies_spotted: number; - enemies_damaged: number; - enemies_destroyed: number; - time_alive: number; - distance_travelled: number; - killed_by: number; - base_capture_points: number; - base_defend_points: number; - exp_for_damage: number; - exp_for_assist: number; - exp_team_bonus: number; - wp_points_earned: number; - wp_points_stolen: number; - hero_bonus_credits: number; - hero_bonus_exp: number; - death_reason: number; - achievements: any[]; - vehicle_descr: number; - turret_id: number; - gun_id: number; - chassis_id: number; - squad_index: number; - damage_blocked: number; -} - -function stat(value: string | number, flex = 2) { - return ( -
- {value} -
- ); -} - -async function playerListing( - locale: Locale, - player: WotInspectorReplayPlayerData, - protagonistTeam: boolean, - winningTeam: number, - recordingPlayer = false, -) { - const { unwrap } = translator(locale); - const awaitedTankAverages = await averageDefinitions; - const awaitedTankDefinitions = await tankDefinitions; - const tank = awaitedTankDefinitions.tanks[player.vehicle_descr]; - const blockAccent = recordingPlayer ? "_amber" : ""; - const healthAccent = protagonistTeam - ? recordingPlayer - ? "_amber" - : "_green" - : "_red"; - - // our turret ids are always off by 1 or 2 lol so imma just grab the closest - // TODO: 💀 - const turret = tank.turrets.sort( - (a, b) => - Math.abs(a.id - player.turret_id) - Math.abs(b.id - player.turret_id), - )[0]; - const totalHealth = turret.health + tank.health; - const healthLeft = clamp(player.hitpoints_left / totalHealth, 0, 1); - const tankAveragesEntry = awaitedTankAverages.averages[player.vehicle_descr]; - const wn8 = - tankAveragesEntry === undefined - ? -1 - : calculateWN8(tankAveragesEntry.mu, { - battles: 1, - damage_dealt: player.damage_made, - dropped_capture_points: player.base_defend_points, - frags: player.enemies_destroyed, - spotted: player.enemies_spotted, - wins: winningTeam === player.team ? 1 : 0, - }); - - return ( -
-
- {unwrap(tank.name!)} - -
-
- - {TIER_ROMAN_NUMERALS[tank.tier]} - - - {unwrap(tank.name!)} - -
- - - {player.name} - {player.clan_tag ? ` [${player.clan_tag}]` : ""} - -
-
- -
- {stat(player.damage_made.toLocaleString())} - {stat(wn8 === -1 ? "--" : Math.round(wn8).toLocaleString())} - {stat(player.exp.toLocaleString())} - {stat(player.enemies_destroyed, 1)} -
- -
-
-
-
- ); -} - -function Header() { - return ( -
-
- -
-
- - Damage - -
-
- - WN8 - -
-
- - XP - -
-
- - Kills - -
-
-
- ); -} - -export const replayCommand = new Promise((resolve) => { - const { strings } = translator(Locale.EnglishUS); - - resolve({ - command: createLocalizedCommand("replay").addAttachmentOption((option) => - option - .setName(strings.bot.commands.replay.options.file.name) - .setNameLocalizations( - localizationObject( - (strings) => strings.bot.commands.replay.options.file.name, - undefined, - true, - ), - ) - .setDescription(strings.bot.commands.replay.options.file.description) - .setDescriptionLocalizations( - localizationObject( - (strings) => strings.bot.commands.replay.options.file.description, - ), - ) - .setRequired(true), - ), - - async handler(interaction) { - const awaitedMapDefinitions = await mapDefinitions; - const file = interaction.options.getAttachment("file")!; - const submissionData = (await fetch( - `https://wotinspector.com/api/replay/upload/?url=${encodeURIComponent( - file.url, - )}`, - ).then((response) => response.json())) as WotInspectorReplaySubmission; - - if (submissionData.status === "error") { - return `# Uh oh! WoT Inspector didn't like that.\n\nThere was an error processing your replay. Get help on [the official Discord server](https://discord.gg/nDt7AjGJQH).`; - } - - const replayId = submissionData.data.download_url.split("/").at(-1)!; - const replayData = (await fetch( - `https://api.wotinspector.com/v2/blitz/replays/${replayId}/`, - ).then((response) => response.json())) as WotInspectorReplay; - const antagonistTeamId = 3 - replayData.protagonist_team; - const players = replayData.players_data.sort( - (a, b) => b.damage_made - a.damage_made, - ); - const clan = ( - await getClanAccountInfo( - idToRegion(replayData.protagonist), - replayData.protagonist, - ["clan"], - ) - )?.clan; - const clanImage = clan ? emblemURL(clan.emblem_set_id) : undefined; - - return [ - new RenderConfiguration({ width: 960 }), - - - -
-
-
- - {await Promise.all( - players - .filter( - (player) => player.team === replayData.protagonist_team, - ) - .map((player) => - playerListing( - interaction.locale, - player, - true, - replayData.winner_team, - player.dbid === replayData.protagonist, - ), - ), - )} -
- -
-
- - {await Promise.all( - players - .filter((player) => player.team === antagonistTeamId) - .map((player) => - playerListing( - interaction.locale, - player, - false, - replayData.winner_team, - ), - ), - )} -
-
-
, - buttonLink(submissionData.data.view_url, "WoT Inspector"), - buttonLink(submissionData.data.download_url, "Download"), - ]; - }, - }); -}); diff --git a/packages/bot/src/commands/research.tsx b/packages/bot/src/commands/research.tsx deleted file mode 100644 index e0a987044..000000000 --- a/packages/bot/src/commands/research.tsx +++ /dev/null @@ -1,626 +0,0 @@ -import { - asset, - emblemURL, - equipmentPriceMatrix, - getAccountInfo, - getClanAccountInfo, - getTankStats, - TANK_ICONS, - TankType, - TIER_ROMAN_NUMERALS, -} from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import { escapeMarkdown, Locale } from "discord.js"; -import { CommandWrapper } from "../components/CommandWrapper"; -import { TitleBar } from "../components/TitleBar"; -import { resolveTankId } from "../core/blitz/resolveTankId"; -import { buildTechTreeLine } from "../core/blitzkit/buildTechTreeLine"; -import { iconPng } from "../core/blitzkit/iconPng"; -import { tankDefinitions } from "../core/blitzkit/nonBlockingPromises"; -import { resolveAncestry } from "../core/blitzkit/resolveAncestry"; -import { tankIconPng } from "../core/blitzkit/tankIconPng"; -import { addUsernameChoices } from "../core/discord/addUsernameChoices"; -import { autocompleteTanks } from "../core/discord/autocompleteTanks"; -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { localizationObject } from "../core/discord/localizationObject"; -import { resolvePlayerFromCommand } from "../core/discord/resolvePlayerFromCommand"; -import { translator } from "../core/localization/translator"; -import { type CommandRegistry } from "../events/interactionCreate"; -import { theme } from "../stitches.config"; - -export const researchCommand = new Promise((resolve) => { - const { strings } = translator(Locale.EnglishUS); - - resolve({ - command: createLocalizedCommand("research") - .addStringOption((option) => - option - .setName(strings.bot.commands.research.options.target_tank.name!) - .setNameLocalizations( - localizationObject( - (strings) => - strings.bot.commands.research.options.target_tank.name, - undefined, - true, - ), - ) - .setDescription( - strings.bot.commands.research.options.target_tank.description, - ) - .setDescriptionLocalizations( - localizationObject( - (strings) => - strings.bot.commands.research.options.target_tank.description, - ), - ) - .setAutocomplete(true) - .setRequired(true), - ) - .addStringOption((option) => - option - .setName(strings.bot.commands.research.options.starting_tank.name!) - .setNameLocalizations( - localizationObject( - (strings) => - strings.bot.commands.research.options.starting_tank.name, - undefined, - true, - ), - ) - .setDescription( - strings.bot.commands.research.options.starting_tank.description, - ) - .setDescriptionLocalizations( - localizationObject( - (strings) => - strings.bot.commands.research.options.starting_tank.description, - ), - ) - .setAutocomplete(true) - .setRequired(false), - ) - .addStringOption(addUsernameChoices), - - async handler(interaction) { - const { strings, unwrap } = translator(interaction.locale); - const awaitedTankDefinitions = await tankDefinitions; - const targetTankId = await resolveTankId( - interaction.options.getString("target-tank", true), - interaction.locale, - true, - ); - const startingTankRaw = interaction.options.getString("starting-tank"); - const targetTank = awaitedTankDefinitions.tanks[targetTankId]; - const { id, region } = await resolvePlayerFromCommand(interaction); - let startingTankId: number; - const targetTankAncestry = await resolveAncestry(targetTankId); - - if (startingTankRaw) { - startingTankId = await resolveTankId( - startingTankRaw, - interaction.locale, - true, - ); - - if (targetTankId === startingTankId) { - return literals( - strings.bot.commands.research.errors.start_end_equal, - { tank: escapeMarkdown(unwrap(targettank.name!)) }, - ); - } - - const startingTank = awaitedTankDefinitions.tanks[startingTankId]; - - if (startingTank.tier > targetTank.tier) { - return literals(strings.bot.commands.research.errors.unordered, { - tank_1: escapeMarkdown(unwrap(startingtank.name!)), - tank_2: escapeMarkdown(unwrap(targettank.name!)), - }); - } - - if (!targetTankAncestry.includes(startingTankId)) { - return literals( - strings.bot.commands.research.errors.tanks_not_on_line, - { - tank_1: escapeMarkdown(unwrap(targettank.name!)), - tank_2: escapeMarkdown(unwrap(startingtank.name!)), - }, - ); - } - } else { - if (targetTank.type !== TankType.TANK_TYPE_RESEARCHABLE) { - return literals(strings.bot.commands.research.errors.non_tech_tree, { - tank: escapeMarkdown(unwrap(targettank.name!)), - }); - } - - if (targetTank.tier === 1) { - return literals(strings.bot.commands.research.errors.tier_1, { - tank: escapeMarkdown(unwrap(targettank.name!)), - }); - } - - const tankStats = await getTankStats(region, id); - - if (tankStats === null) { - return strings.bot.common.errors.no_tank_stats; - } - - if (tankStats.some(({ tank_id }) => tank_id === targetTankId)) { - return literals( - strings.bot.commands.research.errors.already_researched, - { tank: escapeMarkdown(unwrap(targettank.name!)) }, - ); - } - - const foundAncestor = targetTankAncestry.find( - (id) => - awaitedTankDefinitions.tanks[id].tier === 1 || - tankStats.some(({ tank_id }) => tank_id === id), - ); - - if (foundAncestor === undefined) { - throw new Error("No first owned tech tree found."); - } - - startingTankId = foundAncestor; - } - - const techTreeLine = await buildTechTreeLine( - startingTankId, - targetTankId, - ); - - const line = [...techTreeLine, startingTankId].reverse(); - const { nickname } = await getAccountInfo(region, id); - const clan = (await getClanAccountInfo(region, id, ["clan"]))?.clan; - const clanImage = clan ? emblemURL(clan.emblem_set_id) : undefined; - const costs = await Promise.all( - line.map(async (id) => { - const tank = awaitedTankDefinitions.tanks[id]; - const turretXps = new Map(); - const gunXps = new Map(); - const engineXps = new Map(); - const trackXps = new Map(); - - tank.turrets.forEach((turret) => { - if ( - turret.research_cost !== undefined && - typeof turret.research_cost.research_cost_type!.value === "number" - ) { - turretXps.set( - turret.id, - turret.research_cost.research_cost_type!.value, - ); - } - - turret.guns.forEach((gun) => { - if ( - gun.research_cost !== undefined && - typeof gun.research_cost.research_cost_type!.value === "number" - ) { - gunXps.set(gun.id, gun.research_cost.research_cost_type!.value); - } - }); - }); - - tank.engines.forEach((engine) => { - if ( - engine.research_cost !== undefined && - typeof engine.research_cost.research_cost_type!.value === "number" - ) { - engineXps.set( - engine.id, - engine.research_cost.research_cost_type!.value, - ); - } - }); - - tank.tracks.forEach((track) => { - if ( - track.research_cost !== undefined && - typeof track.research_cost.research_cost_type!.value === "number" - ) { - trackXps.set( - track.id, - track.research_cost.research_cost_type!.value, - ); - } - }); - - let moduleXp = 0; - turretXps.forEach((value) => { - moduleXp += value; - }); - gunXps.forEach((value) => { - moduleXp += value; - }); - engineXps.forEach((value) => { - moduleXp += value; - }); - trackXps.forEach((value) => { - moduleXp += value; - }); - - return { - research: tank.research_cost!.research_cost_type!.value as number, - upgrades: moduleXp, - purchase: tank.price!.value, - equipment: equipmentPriceMatrix[tank.tier].reduce( - (a, b) => a + 3 * b, - 0, - ), - }; - }), - ); - - const image = ( - - - -
-
-
- -
- - {strings.bot.commands.research.body.total} - -
- -
-
- XP icon - - Experience:{" "} - {costs - .reduce((a, b) => a + (b.research ?? 0) + b.upgrades, 0) - .toLocaleString(interaction.locale)} - -
- -
- Silver icon - - Purchase:{" "} - {costs - .reduce((a, b) => a + b.purchase, 0) - .toLocaleString(interaction.locale)} - -
- -
- Silver icon - - Equipment:{" "} - {costs - .reduce((a, b) => a + b.equipment, 0) - .toLocaleString(interaction.locale)} - -
-
-
- - {await Promise.all( - line.map(async (id, index) => { - const tank = awaitedTankDefinitions.tanks[id]; - - return ( -
-
- - {TIER_ROMAN_NUMERALS[tank.tier]} - -
- -
- Tank icon - -
- {unwrap(tank.name!)} - - - {unwrap(tank.name!)} - -
-
- -
-
- XP icon - - {literals( - strings.bot.commands.research.body.research, - { - cost: costs[index].research?.toLocaleString( - interaction.locale, - ), - }, - )} - -
- - {costs[index].upgrades > 0 && ( -
- Free XP icon - - {literals( - strings.bot.commands.research.body.upgrades, - { cost: costs[index].upgrades.toLocaleString() }, - )} - -
- )} - -
- Silver icon - - {literals( - strings.bot.commands.research.body.purchase, - { - cost: costs[index].purchase.toLocaleString( - interaction.locale, - ), - }, - )} - -
- -
- Silver icon - - {literals( - strings.bot.commands.research.body.equipment, - { - cost: costs[index].equipment.toLocaleString( - interaction.locale, - ), - }, - )} - -
-
-
- ); - }), - )} -
-
- ); - - return startingTankRaw - ? image - : [strings.bot.commands.research.body.estimation, image]; - }, - - autocomplete(interaction) { - autocompleteTanks(interaction, true, ["target-tank", "starting-tank"]); - }, - }); -}); diff --git a/packages/bot/src/commands/searchClans.ts b/packages/bot/src/commands/searchClans.ts deleted file mode 100644 index 45c0c6aee..000000000 --- a/packages/bot/src/commands/searchClans.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { type ClanList, fetchBlitz, type Region } from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import { Locale } from "discord.js"; -import markdownEscape from "markdown-escape"; -import { addClanChoices } from "../core/discord/addClanChoices"; -import { addRegionChoices } from "../core/discord/addRegionChoices"; -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { embedInfo } from "../core/discord/embedInfo"; -import { localizationObject } from "../core/discord/localizationObject"; -import { translator } from "../core/localization/translator"; -import type { CommandRegistry } from "../events/interactionCreate"; - -const DEFAULT_LIMIT = 25; - -export const searchClansCommand = new Promise((resolve) => { - resolve({ - command: createLocalizedCommand("search-clans") - .addStringOption(addRegionChoices) - .addStringOption((option) => - addClanChoices(option).setAutocomplete(false) - ) - .addIntegerOption((option) => { - const { strings } = translator(Locale.EnglishUS); - - return option - .setName(strings.bot.commands.search_clans.options.limit.name) - .setNameLocalizations( - localizationObject( - (strings) => strings.bot.commands.search_clans.options.limit.name, - undefined, - true - ) - ) - .setDescription( - literals( - strings.bot.commands.search_clans.options.limit.description, - { limit: DEFAULT_LIMIT } - ) - ) - .setDescriptionLocalizations( - localizationObject( - (strings) => - strings.bot.commands.search_clans.options.limit.description, - { limit: DEFAULT_LIMIT } - ) - ) - .setMinValue(1) - .setMaxValue(100); - }), - - async handler(interaction) { - const { strings } = translator(interaction.locale); - const region = interaction.options.getString("region") as Region; - const clan = interaction.options.getString("clan")!; - const limit = interaction.options.getInteger("limit") ?? DEFAULT_LIMIT; - const clanList = await fetchBlitz(region, "clans/list", { - search: clan, - limit, - }); - - return embedInfo( - literals(strings.bot.commands.search_clans.body.title, { - search: markdownEscape(clan), - region: strings.common.regions.normal[region], - }), - clanList.length === 0 - ? strings.bot.commands.search_clans.body.no_results - : `\`\`\`\n${clanList - .map((clan) => `${clan.name} [${clan.tag}]`) - .join("\n")}\n\`\`\`` - ); - }, - }); -}); diff --git a/packages/bot/src/commands/searchPlayers.ts b/packages/bot/src/commands/searchPlayers.ts deleted file mode 100644 index 54bfa4dc1..000000000 --- a/packages/bot/src/commands/searchPlayers.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { - type AccountList, - fetchBlitz, - type Region, - usernamePattern, -} from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import { Locale } from "discord.js"; -import markdownEscape from "markdown-escape"; -import { addRegionChoices } from "../core/discord/addRegionChoices"; -import { addUsernameChoices } from "../core/discord/addUsernameChoices"; -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { embedInfo } from "../core/discord/embedInfo"; -import { localizationObject } from "../core/discord/localizationObject"; -import { translator } from "../core/localization/translator"; -import type { CommandRegistry } from "../events/interactionCreate"; - -const DEFAULT_LIMIT = 25; - -export const searchPlayersCommand = new Promise((resolve) => { - const { strings } = translator(Locale.EnglishUS); - - resolve({ - command: createLocalizedCommand("search-players") - .addStringOption(addRegionChoices) - .addStringOption((option) => - addUsernameChoices(option).setAutocomplete(false).setRequired(true) - ) - .addIntegerOption((option) => - option - .setName(strings.bot.commands.search_players.options.limit.name) - .setNameLocalizations( - localizationObject( - (strings) => - strings.bot.commands.search_players.options.limit.name, - undefined, - true - ) - ) - .setDescription( - literals( - strings.bot.commands.search_players.options.limit.description, - { limit: DEFAULT_LIMIT } - ) - ) - .setDescriptionLocalizations( - localizationObject( - (strings) => - strings.bot.commands.search_players.options.limit.description, - { limit: DEFAULT_LIMIT } - ) - ) - .setMinValue(1) - .setMaxValue(100) - ), - - async handler(interaction) { - const { strings } = translator(interaction.locale); - const region = interaction.options.getString("region") as Region; - const name = interaction.options.getString("username")!; - const limit = interaction.options.getInteger("limit") ?? 25; - const trimmedSearch = name.trim(); - const players = usernamePattern.test(trimmedSearch) - ? await fetchBlitz(region, "account/list", { - search: trimmedSearch, - limit, - }) - : []; - - return embedInfo( - literals(strings.bot.commands.search_players.body.title, { - search: markdownEscape(trimmedSearch), - region: strings.common.regions.normal[region], - }), - `\`\`\`${ - players.length === 0 - ? strings.bot.commands.search_players.body.no_results - : players.map((player) => player.nickname).join("\n") - }\`\`\`` - ); - }, - }); -}); diff --git a/packages/bot/src/commands/stats.tsx b/packages/bot/src/commands/stats.tsx deleted file mode 100644 index 38337cd09..000000000 --- a/packages/bot/src/commands/stats.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import { emblemURL, getAccountInfo, getClanAccountInfo } from "@blitzkit/core"; -import { Locale } from "discord.js"; -import { AllStatsOverview } from "../components/AllStatsOverview"; -import { CommandWrapper } from "../components/CommandWrapper"; -import { NoData } from "../components/NoData"; -import { TitleBar } from "../components/TitleBar"; -import { filtersToDescription } from "../core/blitzkit/filtersToDescription"; -import { filterStats, type StatFilters } from "../core/blitzstars/filterStats"; -import { getStatsInPeriod } from "../core/blitzstars/getStatsInPeriod"; -import { addPeriodicFilterOptions } from "../core/discord/addPeriodicFilterOptions"; -import { addUsernameChoices } from "../core/discord/addUsernameChoices"; -import { autocompleteTanks } from "../core/discord/autocompleteTanks"; -import { autocompleteUsername } from "../core/discord/autocompleteUsername"; -import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { getFiltersFromButton } from "../core/discord/getFiltersFromButton"; -import { resolvePeriodFromButton } from "../core/discord/resolvePeriodFromButton"; -import { type ResolvedPeriod } from "../core/discord/resolvePeriodFromCommand"; -import { resolvePlayerFromButton } from "../core/discord/resolvePlayerFromButton"; -import { type ResolvedPlayer } from "../core/discord/resolvePlayerFromCommand"; -import { translator } from "../core/localization/translator"; -import type { CommandRegistry } from "../events/interactionCreate"; - -async function render( - { region, id }: ResolvedPlayer, - { start, end, name }: ResolvedPeriod, - filters: StatFilters, - locale: Locale, -) { - const { nickname } = await getAccountInfo(region, id); - const clan = (await getClanAccountInfo(region, id, ["clan"]))?.clan; - const clanImage = clan ? emblemURL(clan.emblem_set_id) : undefined; - const diffedTankStats = await getStatsInPeriod( - region, - id, - start, - end, - locale, - ); - const { stats, supplementary } = await filterStats(diffedTankStats, filters); - const filterDescriptions = await filtersToDescription(filters, locale); - - return ( - - - - {!stats.battles && } - {stats.battles > 0 && ( - - )} - - ); -} - -export const statsCommand = new Promise(async (resolve) => { - resolve({ - command: await addPeriodicFilterOptions( - createLocalizedCommand("stats"), - (option) => option.addStringOption(addUsernameChoices), - ), - - async handler(interaction) { - const { strings } = translator(interaction.locale); - return strings.bot.commands.dead; - - // const player = await resolvePlayerFromCommand(interaction); - // const period = resolvePeriodFromCommand(player.region, interaction); - // const filters = await getFiltersFromCommand(interaction); - // const path = commandToURL(interaction, { - // ...player, - // ...getCustomPeriodParams(interaction), - // ...filters, - // }); - - // return Promise.all([ - // render(player, period, filters, interaction.locale), - // buttonRefresh(interaction, path), - // 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 render(player, period, filters, interaction.locale); - }, - }); -}); diff --git a/packages/bot/src/commands/today.ts b/packages/bot/src/commands/today.ts deleted file mode 100644 index 65256cad9..000000000 --- a/packages/bot/src/commands/today.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { gameDefinitions } from "../core/blitzkit/nonBlockingPromises"; -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 { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; -import { getFiltersFromButton } from "../core/discord/getFiltersFromButton"; -import { resolvePeriodFromButton } from "../core/discord/resolvePeriodFromButton"; -import { resolvePlayerFromButton } from "../core/discord/resolvePlayerFromButton"; -import { translator } from "../core/localization/translator"; -import { type CommandRegistry } from "../events/interactionCreate"; -import { renderBreakdown } from "./breakdown/render"; - -export const todayCommand = new Promise(async (resolve) => { - const awaitedGameDefinitions = await gameDefinitions; - - resolve({ - command: addFilterOptions( - createLocalizedCommand("today"), - awaitedGameDefinitions.nations, - ).addStringOption(addUsernameChoices), - - async handler(interaction) { - const { strings } = translator(interaction.locale); - return strings.bot.commands.dead; - - // 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, interaction.locale)), - // buttonRefresh(interaction, path), - // 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, interaction.locale); - }, - }); -}); diff --git a/packages/bot/src/components/AllStatsOverview/components/HeroStat/components/Glow.tsx b/packages/bot/src/components/AllStatsOverview/components/HeroStat/components/Glow.tsx deleted file mode 100644 index 1a3a00277..000000000 --- a/packages/bot/src/components/AllStatsOverview/components/HeroStat/components/Glow.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { type ComponentProps } from "react"; - -export interface GlowProps extends ComponentProps<"div"> { - color: string; - direction?: "default" | "reverse"; - rotation?: number; - startOpacity?: number; - endOpacity?: number; -} - -const width = 16; - -export function Glow({ - color, - direction = "default", - rotation = 0, - style, - startOpacity = 0, - endOpacity = 1, - ...props -}: GlowProps) { - const color1 = `${color}${Math.round(endOpacity * 255) - .toString(16) - .padStart(2, "0")}`; - const color2 = `${color}${Math.round(startOpacity * 255) - .toString(16) - .padStart(2, "0")}`; - const background = `linear-gradient(${ - direction === "default" ? 0 : 180 - }deg, ${color1} 0%, ${color2} 100%)`; - - return ( -
-
-
-
-
-
-
- ); -} diff --git a/packages/bot/src/components/AllStatsOverview/components/HeroStat/index.tsx b/packages/bot/src/components/AllStatsOverview/components/HeroStat/index.tsx deleted file mode 100644 index be3b5a0f2..000000000 --- a/packages/bot/src/components/AllStatsOverview/components/HeroStat/index.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { theme } from '../../../../stitches.config'; -import { Glow } from './components/Glow'; - -export interface WN8DisplayProps { - subtitle: string; - color: string; - value: string | number; -} - -export function HeroStat({ color, subtitle, value }: WN8DisplayProps) { - return ( -
- - -
-
- - {value} - - - {subtitle} - -
-
- - -
- ); -} diff --git a/packages/bot/src/components/AllStatsOverview/components/WN8SurroundingStat.tsx b/packages/bot/src/components/AllStatsOverview/components/WN8SurroundingStat.tsx deleted file mode 100644 index 62db93c41..000000000 --- a/packages/bot/src/components/AllStatsOverview/components/WN8SurroundingStat.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { theme } from '../../../stitches.config'; - -export enum WN8SurroundingStatAlign { - Left, - Right, -} - -export interface WN8SurroundingStatProps { - label: string; - value: string | number; - align?: WN8SurroundingStatAlign; - padded?: boolean; -} - -export function WN8SurroundingStat({ - label, - value, - align = WN8SurroundingStatAlign.Left, - padded = false, -}: WN8SurroundingStatProps) { - return ( -
- - {value} - - - {label} - -
- ); -} diff --git a/packages/bot/src/components/AllStatsOverview/index.tsx b/packages/bot/src/components/AllStatsOverview/index.tsx deleted file mode 100644 index 62073cb00..000000000 --- a/packages/bot/src/components/AllStatsOverview/index.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import { - getWN8Percentile, - Percentile, - type SupplementaryStats, -} from '@blitzkit/core'; -import { Locale } from 'discord.js'; -import { translator } from '../../core/localization/translator'; -import { PERCENTILE_COLORS } from '../PercentileIndicator/constants'; -import { HeroStat } from './components/HeroStat'; -import { - WN8SurroundingStat, - WN8SurroundingStatAlign, -} from './components/WN8SurroundingStat'; - -export interface AllStatsOverviewProps { - stats: { - battles: number; - wins: number; - damage_dealt: number; - damage_received: number; - survived_battles: number; - hits: number; - shots: number; - frags: number; - }; - supplementaryStats: SupplementaryStats; - locale: Locale; -} - -export function AllStatsOverview({ - stats, - locale, - supplementaryStats, -}: AllStatsOverviewProps) { - const { strings } = translator(locale); - const percentile = - typeof supplementaryStats.WN8 === 'number' - ? getWN8Percentile(supplementaryStats.WN8) - : Percentile.VeryBad; - const color = PERCENTILE_COLORS[percentile]; - - const heroStat = - typeof supplementaryStats.WN8 === 'number' - ? supplementaryStats.WN8.toFixed(0) - : '--'; - - return ( -
-
- - - - -
- - - -
- - - - -
-
- ); -} diff --git a/packages/bot/src/components/Breakdown/components/Root.tsx b/packages/bot/src/components/Breakdown/components/Root.tsx deleted file mode 100644 index cbbe20573..000000000 --- a/packages/bot/src/components/Breakdown/components/Root.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { type ReactNode } from "react"; - -interface RootProps { - children: ReactNode; -} - -export function Root({ children }: RootProps) { - return ( -
- {children} -
- ); -} diff --git a/packages/bot/src/components/Breakdown/components/Row.tsx b/packages/bot/src/components/Breakdown/components/Row.tsx deleted file mode 100644 index a5af946d6..000000000 --- a/packages/bot/src/components/Breakdown/components/Row.tsx +++ /dev/null @@ -1,126 +0,0 @@ -import { - Percentile, - TankClass, - TankType, - TREE_TYPE_ICONS, -} from "@blitzkit/core"; -import { createColors } from "bepaint"; -import { - type AccentColor, - type GrayColor, - PALETTES, -} from "../../../core/radix/radixColors"; -import { RowStat } from "./RowStat"; - -export interface RowStatItem { - title: string; - current?: number | string; - career?: number | string; - delta?: number; - percentile?: Percentile; -} - -interface RowProps { - displayType?: "tank" | "summary"; - naked?: boolean; - title: string; - minimized?: boolean; - type?: TankType; - class?: TankClass; - color?: AccentColor | GrayColor; - stats: (RowStatItem | undefined)[]; -} - -export function Row({ - color = "slate", - stats, - title, - minimized, - class: tankClass, - type, - naked = false, - displayType, -}: RowProps) { - const palette = PALETTES[`${color}Dark`]; - const theme = createColors(palette); - - return ( -
-
- {displayType === "tank" && tankClass !== undefined && ( - {TankClass[tankClass]} - )} - - - {title} - -
- -
- {stats.map( - (row, index) => - row && ( - - ), - )} -
-
- ); -} diff --git a/packages/bot/src/components/Breakdown/components/RowStat.tsx b/packages/bot/src/components/Breakdown/components/RowStat.tsx deleted file mode 100644 index 430f8b6a6..000000000 --- a/packages/bot/src/components/Breakdown/components/RowStat.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import { Percentile } from "@blitzkit/core"; -import { createColors } from "bepaint"; -import { - type AccentColor, - type GrayColor, - PALETTES, -} from "../../../core/radix/radixColors"; -import { PercentileIndicator } from "../../PercentileIndicator"; - -interface RowStatProps { - name: string; - value?: string | number; - delta?: number; - percentile?: Percentile; - minimized?: boolean; - color?: AccentColor | GrayColor; -} - -export function RowStat({ - name, - value, - delta, - percentile, - minimized = false, - color = "slate", -}: RowStatProps) { - const theme = createColors(PALETTES[`${color}Dark`]); - - return ( -
-
- {percentile === undefined && delta !== undefined && delta !== 0 && ( - {delta 0 ? "Increase" : "Decrease"} - src={ - (delta ?? 0) > 0 - ? "https://i.imgur.com/qbjiXa1.png" - : "https://i.imgur.com/3uyNhun.png" - } - style={{ width: 12, height: 12 }} - /> - )} - {percentile !== undefined && ( - - )} - - - {value} - -
- - {!minimized && ( - - {name} - - )} -
- ); -} diff --git a/packages/bot/src/components/Breakdown/index.ts b/packages/bot/src/components/Breakdown/index.ts deleted file mode 100644 index bf85c5f20..000000000 --- a/packages/bot/src/components/Breakdown/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './components/Root'; -export * from './components/Row'; -export * from './components/RowStat'; diff --git a/packages/bot/src/components/CommandWrapper.tsx b/packages/bot/src/components/CommandWrapper.tsx deleted file mode 100644 index c4dd6b066..000000000 --- a/packages/bot/src/components/CommandWrapper.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { type ReactNode } from "react"; -import { theme } from "../stitches.config"; - -interface CommandWrapperProps { - children: ReactNode; - fat?: boolean; -} - -export function CommandWrapper({ children, fat }: CommandWrapperProps) { - return ( -
- {children} -
- ); -} diff --git a/packages/bot/src/components/DeltaCaret.tsx b/packages/bot/src/components/DeltaCaret.tsx deleted file mode 100644 index 15c1ae8aa..000000000 --- a/packages/bot/src/components/DeltaCaret.tsx +++ /dev/null @@ -1,21 +0,0 @@ -interface DeltaCaretProps { - delta: number; -} - -export function DeltaCaret({ delta }: DeltaCaretProps) { - return ( - {delta 0 ? 'Increase' : 'Decrease'} - style={{ - width: 16, - height: 16, - objectFit: 'contain', - }} - src={ - delta > 0 - ? 'https://i.imgur.com/gjI4a5f.png' - : 'https://i.imgur.com/lLGZygN.png' - } - /> - ); -} diff --git a/packages/bot/src/components/Flag.tsx b/packages/bot/src/components/Flag.tsx deleted file mode 100644 index 110b71f95..000000000 --- a/packages/bot/src/components/Flag.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { asset } from '@blitzkit/core'; - -interface FlagProps { - nation: string; -} - -export function Flag({ nation }: FlagProps) { - return ( -
- {nation} -
- ); -} diff --git a/packages/bot/src/components/GenericAllStats/components/Card.tsx b/packages/bot/src/components/GenericAllStats/components/Card.tsx deleted file mode 100644 index 2f0922c29..000000000 --- a/packages/bot/src/components/GenericAllStats/components/Card.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { Percentile } from '@blitzkit/core'; -import { theme } from '../../../stitches.config'; -import { PercentileIndicator } from '../../PercentileIndicator'; - -interface CardItem { - label: string; - value: string | number | undefined; - percentile?: Percentile; -} - -interface CardProps { - items: CardItem[]; - title: string; -} - -export function Card({ items, title }: CardProps) { - return ( -
- - {title} - - -
- {items.map((item) => ( -
- - {item.label} - - -
- -
- {item.percentile !== undefined && ( - - )} - - {item.value === undefined || Number.isNaN(item.value) - ? '--' - : item.value} - -
-
- ))} -
-
- ); -} diff --git a/packages/bot/src/components/GenericAllStats/components/Root.tsx b/packages/bot/src/components/GenericAllStats/components/Root.tsx deleted file mode 100644 index 5c362f44b..000000000 --- a/packages/bot/src/components/GenericAllStats/components/Root.tsx +++ /dev/null @@ -1,17 +0,0 @@ -interface RootProps { - children: React.ReactNode; -} - -export function Root({ children }: RootProps) { - return ( -
- {children} -
- ); -} diff --git a/packages/bot/src/components/GenericAllStats/components/Row.tsx b/packages/bot/src/components/GenericAllStats/components/Row.tsx deleted file mode 100644 index bd5cfa17e..000000000 --- a/packages/bot/src/components/GenericAllStats/components/Row.tsx +++ /dev/null @@ -1,7 +0,0 @@ -interface RowProps { - children: React.ReactNode; -} - -export function Row({ children }: RowProps) { - return
{children}
; -} diff --git a/packages/bot/src/components/GenericAllStats/index.tsx b/packages/bot/src/components/GenericAllStats/index.tsx deleted file mode 100644 index 36b687132..000000000 --- a/packages/bot/src/components/GenericAllStats/index.tsx +++ /dev/null @@ -1,149 +0,0 @@ -import { - type BlitzStats, - getWN8Percentile, - isNumber, - type SupplementaryStats, -} from '@blitzkit/core'; -import { Locale } from 'discord.js'; -import { translator } from '../../core/localization/translator'; -import { Card } from './components/Card'; -import { Root } from './components/Root'; -import { Row } from './components/Row'; - -interface GenericAllStatsProps { - stats: BlitzStats; - supplementaryStats?: SupplementaryStats; - locale: Locale; -} - -export function GenericAllStats({ - stats, - supplementaryStats, - locale, -}: GenericAllStatsProps) { - const { strings } = translator(locale); - const damageRatio = stats.damage_dealt / stats.damage_received; - const killsToDeathRatio = - stats.frags / (stats.battles - stats.survived_battles); - - return ( - - - - - - - - - - - ); -} diff --git a/packages/bot/src/components/GenericStats/components/GenericStatsColumn.tsx b/packages/bot/src/components/GenericStats/components/GenericStatsColumn.tsx deleted file mode 100644 index 5346cde64..000000000 --- a/packages/bot/src/components/GenericStats/components/GenericStatsColumn.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { type GenericStat } from ".."; -import { GenericStatsRow } from "./GenericStatsRow"; - -interface GenericStatsColumnProps { - stats: GenericStat[]; -} - -export function GenericStatsColumn({ stats }: GenericStatsColumnProps) { - return ( -
- {stats.map((stat) => ( - - ))} -
- ); -} diff --git a/packages/bot/src/components/GenericStats/components/GenericStatsRow.tsx b/packages/bot/src/components/GenericStats/components/GenericStatsRow.tsx deleted file mode 100644 index 1db1c6bda..000000000 --- a/packages/bot/src/components/GenericStats/components/GenericStatsRow.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { type GenericStat } from ".."; -import { theme } from "../../../stitches.config"; -import { PercentileIndicator } from "../../PercentileIndicator"; - -interface GenericStatsRowProps { - stat: GenericStat; -} - -export function GenericStatsRow({ stat }: GenericStatsRowProps) { - return ( -
- - {stat[0]} - - - - {stat[2] !== undefined && } - {stat[1]} - -
- ); -} diff --git a/packages/bot/src/components/GenericStats/index.tsx b/packages/bot/src/components/GenericStats/index.tsx deleted file mode 100644 index ae23b18e9..000000000 --- a/packages/bot/src/components/GenericStats/index.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { Percentile } from '@blitzkit/core'; -import { GenericStatsColumn } from './components/GenericStatsColumn'; - -export type GenericStat = - | [string, string | number | undefined] - | [string, string | number | undefined, Percentile | undefined]; - -export interface GenericStatsProps { - stats: GenericStat[]; - columnCount?: number; -} - -export function GenericStats({ stats, columnCount = 2 }: GenericStatsProps) { - const filteredStats = stats.filter(([, value]) => value !== undefined); - const itemsPerRow = Math.ceil(filteredStats.length / columnCount); - const columns: GenericStat[][] = []; - - filteredStats.forEach((stat, index) => { - const column = Math.floor(index / itemsPerRow); - if (!columns[column]) columns[column] = []; - columns[column].push(stat); - }); - - return ( -
- {columns.map((stats) => ( - stat[0]).join('-')} - stats={stats} - /> - ))} -
- ); -} diff --git a/packages/bot/src/components/Graph/components/Line/constants.ts b/packages/bot/src/components/Graph/components/Line/constants.ts deleted file mode 100644 index 74170a3a8..000000000 --- a/packages/bot/src/components/Graph/components/Line/constants.ts +++ /dev/null @@ -1,11 +0,0 @@ -export enum LineColor { - Red, - Green, - Blue, - White, -} -export enum LineStyle { - Solid, - Dashes, - DashesAndDots, -} diff --git a/packages/bot/src/components/Graph/components/Line/index.tsx b/packages/bot/src/components/Graph/components/Line/index.tsx deleted file mode 100644 index ad0beda82..000000000 --- a/packages/bot/src/components/Graph/components/Line/index.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { blue, green, red, slateDark } from '@radix-ui/colors'; -import { GRAPH_HEIGHT, GRAPH_WIDTH } from '../Root/constants'; -import { LineColor, LineStyle } from './constants'; - -const LINE_COLORS = { - [LineColor.Red]: red.red9, - [LineColor.Green]: green.green9, - [LineColor.Blue]: blue.blue9, - [LineColor.White]: slateDark.slate11, -}; - -const LINE_STYLES = { - [LineStyle.Solid]: undefined, - [LineStyle.Dashes]: '8, 8', - [LineStyle.DashesAndDots]: '1, 4, 8, 4', -}; - -export type PlotItem = [number, number]; -type Plot = PlotItem[]; - -interface LineProps { - plot: Plot; - minY?: number; - maxY?: number; - color?: LineColor; - style?: LineStyle; - lineWidth?: number; -} - -export function Line({ - plot, - minY: providedMinY, - maxY: providedMaxY, - color = LineColor.Red, - style = LineStyle.Solid, - lineWidth = 2, -}: LineProps) { - const xs = plot.map(([x]) => x); - const minX = Math.min(...xs); - const maxX = Math.max(...xs); - const ys = plot.map(([, y]) => y); - const minY = providedMinY ?? Math.min(...ys); - const maxY = providedMaxY ?? Math.max(...ys); - const rangeX = maxX - minX; - const rangeY = maxY - minY; - const points = plot - .map( - ([x, y]) => - `${(x - minX) * (GRAPH_WIDTH / rangeX)},${ - GRAPH_HEIGHT - (y - minY) * (GRAPH_HEIGHT / rangeY) - }`, - ) - .join(' '); - - return ( - - ); -} diff --git a/packages/bot/src/components/Graph/components/Margin/constants.ts b/packages/bot/src/components/Graph/components/Margin/constants.ts deleted file mode 100644 index fab153d6c..000000000 --- a/packages/bot/src/components/Graph/components/Margin/constants.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum MarginOrientation { - Vertical, - Horizontal, -} diff --git a/packages/bot/src/components/Graph/components/Margin/index.tsx b/packages/bot/src/components/Graph/components/Margin/index.tsx deleted file mode 100644 index eb15c732c..000000000 --- a/packages/bot/src/components/Graph/components/Margin/index.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { times } from 'lodash-es'; -import { theme } from '../../../../stitches.config'; -import { type MarginInputProps } from '../Root'; -import { MarginOrientation } from './constants'; - -interface MarginProps extends MarginInputProps { - verticalSeparations: number; - orientation?: MarginOrientation; -} - -export function Margin({ - verticalSeparations, - max, - min, - suffix, - precision = 0, - orientation = MarginOrientation.Vertical, -}: MarginProps) { - const isVertical = orientation === MarginOrientation.Vertical; - - return ( -
- {times(verticalSeparations, (index) => ( - - {`${( - (max - min) * - ((verticalSeparations - (index + 1)) / - (verticalSeparations - 1)) + - min - ).toFixed(precision)}${suffix ?? ''}`} - - ))} -
- ); -} diff --git a/packages/bot/src/components/Graph/components/Root/constants.ts b/packages/bot/src/components/Graph/components/Root/constants.ts deleted file mode 100644 index 0ff4e7d6b..000000000 --- a/packages/bot/src/components/Graph/components/Root/constants.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const GRAPH_WIDTH = 384; -export const GRAPH_HEIGHT = 320; diff --git a/packages/bot/src/components/Graph/components/Root/index.tsx b/packages/bot/src/components/Graph/components/Root/index.tsx deleted file mode 100644 index 86ac910f7..000000000 --- a/packages/bot/src/components/Graph/components/Root/index.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import { times } from "lodash-es"; -import { type ReactNode } from "react"; -import { theme } from "../../../../stitches.config"; -import { Margin } from "../Margin"; -import { MarginOrientation } from "../Margin/constants"; -import { GRAPH_HEIGHT, GRAPH_WIDTH } from "./constants"; - -export interface MarginInputProps { - min: number; - max: number; - suffix?: string; - precision?: number; -} - -interface RootProps { - children: ReactNode; - verticalMargin?: MarginInputProps; - horizontalMargin?: MarginInputProps; - separations?: number; - xMinLabel: string; - xMaxLabel: string; - xTitle?: string; -} - -export function Root({ - children, - verticalMargin, - horizontalMargin, - separations = 5, - xMinLabel, - xMaxLabel, - xTitle, -}: RootProps) { - return ( -
-
- - {xMinLabel} - - - {xMaxLabel} - -
- -
- {verticalMargin && ( - - )} - - - {times(separations - 2, (index) => { - const y = GRAPH_HEIGHT * ((index + 1) / (separations - 1)); - - return ( - - ); - })} - - {children} - -
- - {horizontalMargin && ( - - )} - - {xTitle !== undefined && ( -
- - {xTitle} - -
- )} -
- ); -} diff --git a/packages/bot/src/components/Graph/index.ts b/packages/bot/src/components/Graph/index.ts deleted file mode 100644 index 5aaadb77e..000000000 --- a/packages/bot/src/components/Graph/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './components/Line'; -export * from './components/Margin'; -export * from './components/Root'; diff --git a/packages/bot/src/components/Leaderboard/components/Gap.tsx b/packages/bot/src/components/Leaderboard/components/Gap.tsx deleted file mode 100644 index f1fa3f16c..000000000 --- a/packages/bot/src/components/Leaderboard/components/Gap.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { theme } from '../../../stitches.config'; - -interface GapProps { - message: string; -} - -export function Gap({ message: number }: GapProps) { - return ( -
- - {number.toLocaleString()} - -
- ); -} diff --git a/packages/bot/src/components/Leaderboard/components/Item.tsx b/packages/bot/src/components/Leaderboard/components/Item.tsx deleted file mode 100644 index 249221e41..000000000 --- a/packages/bot/src/components/Leaderboard/components/Item.tsx +++ /dev/null @@ -1,171 +0,0 @@ -import { type ComponentProps } from "react"; -import { theme } from "../../../stitches.config"; - -interface ItemProps extends ComponentProps<"div"> { - position: number; - deltaPosition?: number; - score?: number; - deltaScore?: number; - nickname: string; - clan?: string; - highlight?: boolean; -} - -export function Item({ - position, - deltaPosition, - score, - deltaScore, - nickname, - clan, - highlight, - ...props -}: ItemProps) { - return ( -
- - {position.toLocaleString()}. - - -
- - {nickname} - - {clan && ( - - [{clan}] - - )} -
- - {deltaPosition !== undefined && deltaPosition !== 0 && ( -
- {deltaPosition 0 ? "Increase" : "Decrease"} - style={{ - width: 8, - height: 8, - }} - src={ - deltaPosition > 0 - ? "https://i.imgur.com/qbjiXa1.png" - : "https://i.imgur.com/3uyNhun.png" - } - /> - - {Math.abs(deltaPosition).toLocaleString()} - -
- )} - - {/* TODO: lol remove this spacer div for a better solution */} -
- -
- {deltaScore !== 0 && deltaScore !== undefined && ( - - {Math.abs(deltaScore).toLocaleString()} - - )} - {deltaScore !== 0 && deltaScore !== undefined && ( - {deltaScore 0 ? "Increase" : "Decrease"} - style={{ - width: 8, - height: 8, - }} - src={ - deltaScore > 0 - ? "https://i.imgur.com/qbjiXa1.png" - : "https://i.imgur.com/3uyNhun.png" - } - /> - )} - - {score !== undefined && ( - - {score.toLocaleString()} - - )} -
-
- ); -} diff --git a/packages/bot/src/components/Leaderboard/components/Root.tsx b/packages/bot/src/components/Leaderboard/components/Root.tsx deleted file mode 100644 index d812cf3c7..000000000 --- a/packages/bot/src/components/Leaderboard/components/Root.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { type ReactNode } from 'react'; - -interface RootProps { - children: ReactNode; -} - -export function Root({ children }: RootProps) { - return ( -
- {children} -
- ); -} diff --git a/packages/bot/src/components/Leaderboard/index.ts b/packages/bot/src/components/Leaderboard/index.ts deleted file mode 100644 index 4334194b1..000000000 --- a/packages/bot/src/components/Leaderboard/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './components/Gap'; -export * from './components/Item'; -export * from './components/Root'; diff --git a/packages/bot/src/components/NoData/constants.ts b/packages/bot/src/components/NoData/constants.ts deleted file mode 100644 index a01763279..000000000 --- a/packages/bot/src/components/NoData/constants.ts +++ /dev/null @@ -1,5 +0,0 @@ -export enum NoDataType { - BattlesInPeriod, - PlayersInPeriod, - TanksFound, -} diff --git a/packages/bot/src/components/NoData/index.tsx b/packages/bot/src/components/NoData/index.tsx deleted file mode 100644 index 7fa7227aa..000000000 --- a/packages/bot/src/components/NoData/index.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { Locale } from 'discord.js'; -import { translator } from '../../core/localization/translator'; -import { theme } from '../../stitches.config'; - -interface NoDataProps { - type: - | 'battles_in_period' - | 'players_in_period' - | 'tanks_found' - | 'battles_in_season'; - locale: Locale; -} - -export function NoData({ type, locale }: NoDataProps) { - const { strings } = translator(locale); - - return ( -
- - {strings.bot.common.no_data[type]} - -
- ); -} diff --git a/packages/bot/src/components/PercentileIndicator/constants.ts b/packages/bot/src/components/PercentileIndicator/constants.ts deleted file mode 100644 index b5cefb554..000000000 --- a/packages/bot/src/components/PercentileIndicator/constants.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Percentile } from '@blitzkit/core'; -import { - amber, - blue, - green, - lime, - orange, - pink, - purple, - red, - slate, - teal, -} from '@radix-ui/colors'; - -export const PERCENTILE_COLORS = { - [Percentile.VeryBad]: slate.slate9, - [Percentile.Bad]: red.red9, - [Percentile.BelowAverage]: orange.orange9, - [Percentile.Average]: amber.amber10, - [Percentile.AboveAverage]: lime.lime8, - [Percentile.Good]: green.green9, - [Percentile.VeryGood]: teal.teal9, - [Percentile.Great]: blue.blue9, - [Percentile.Unicum]: pink.pink9, - [Percentile.SuperUnicum]: purple.purple9, -}; diff --git a/packages/bot/src/components/PercentileIndicator/index.tsx b/packages/bot/src/components/PercentileIndicator/index.tsx deleted file mode 100644 index f7c0f5b68..000000000 --- a/packages/bot/src/components/PercentileIndicator/index.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { Percentile } from '@blitzkit/core'; -import { PERCENTILE_COLORS } from './constants'; - -interface PercentileIndicatorProps { - percentile: Percentile; -} - -export function PercentileIndicator({ percentile }: PercentileIndicatorProps) { - return ( -
- ); -} diff --git a/packages/bot/src/components/Tanks/components/Column.tsx b/packages/bot/src/components/Tanks/components/Column.tsx deleted file mode 100644 index 6e3549c89..000000000 --- a/packages/bot/src/components/Tanks/components/Column.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { type ReactNode } from 'react'; - -interface ColumnProps { - children: ReactNode; -} - -export function Column({ children }: ColumnProps) { - return ( -
- {children} -
- ); -} diff --git a/packages/bot/src/components/Tanks/components/Item/index.tsx b/packages/bot/src/components/Tanks/components/Item/index.tsx deleted file mode 100644 index f33342c3a..000000000 --- a/packages/bot/src/components/Tanks/components/Item/index.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { TankClass, TankType, TREE_TYPE_ICONS } from "@blitzkit/core"; -import { theme } from "../../../../stitches.config"; - -export interface ItemProps { - tankClass?: TankClass; - name: string; - tankType: TankType; -} - -const TREE_TYPE_COLOR = { - [TankType.TANK_TYPE_RESEARCHABLE]: "", - [TankType.TANK_TYPE_PREMIUM]: "_amber", - [TankType.TANK_TYPE_COLLECTOR]: "_blue", -} as const; - -export function Item({ - tankClass: tankType, - name, - tankType: treeType, -}: ItemProps) { - return ( -
-
- {tankType && ( - {TankType[tankType]} - )} - - {name} - -
- - {/* {image && ( - // TODO: remove hardcoded dimensions when satori fixes non-width unloadable images - {name} - )} */} -
- ); -} diff --git a/packages/bot/src/components/Tanks/components/Root.tsx b/packages/bot/src/components/Tanks/components/Root.tsx deleted file mode 100644 index a5d2e3107..000000000 --- a/packages/bot/src/components/Tanks/components/Root.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { type ReactNode } from "react"; - -interface RootProps { - children: ReactNode; -} - -export function Root({ children }: RootProps) { - return ( -
- {children} -
- ); -} diff --git a/packages/bot/src/components/Tanks/components/Row.tsx b/packages/bot/src/components/Tanks/components/Row.tsx deleted file mode 100644 index f5520127d..000000000 --- a/packages/bot/src/components/Tanks/components/Row.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { type ReactNode } from "react"; - -interface RowProps { - children: ReactNode; -} - -export function Row({ children }: RowProps) { - return ( -
- {children} -
- ); -} diff --git a/packages/bot/src/components/Tanks/components/Title.tsx b/packages/bot/src/components/Tanks/components/Title.tsx deleted file mode 100644 index 10e7d6281..000000000 --- a/packages/bot/src/components/Tanks/components/Title.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { theme } from '../../../stitches.config'; - -interface TitleProps { - children: string; -} - -export function Title({ children }: TitleProps) { - return ( - - {children} - - ); -} diff --git a/packages/bot/src/components/Tanks/index.ts b/packages/bot/src/components/Tanks/index.ts deleted file mode 100644 index 7042919a3..000000000 --- a/packages/bot/src/components/Tanks/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './components/Column'; -export * from './components/Item'; -export * from './components/Root'; -export * from './components/Row'; -export * from './components/Title'; diff --git a/packages/bot/src/components/TierWeights.tsx b/packages/bot/src/components/TierWeights.tsx deleted file mode 100644 index 415c6b6bc..000000000 --- a/packages/bot/src/components/TierWeights.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import type { TierWeightsRecord } from "@blitzkit/core"; -import { theme } from "../stitches.config"; - -interface TierWeightsProps { - weights: TierWeightsRecord; -} - -export function TierWeights({ weights }: TierWeightsProps) { - return ( -
- {Object.entries(weights).map(([tier, weight]) => ( -
- {tier} -
- ))} -
- ); -} diff --git a/packages/bot/src/components/TitleBar.tsx b/packages/bot/src/components/TitleBar.tsx deleted file mode 100644 index e64d0db7b..000000000 --- a/packages/bot/src/components/TitleBar.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { theme } from '../stitches.config'; - -interface TitleBarProps { - title: string; - image?: string; - description?: string; -} - -export function TitleBar({ title, image, description }: TitleBarProps) { - return ( -
-
- {image && ( - {title} - )} - - - {title} - -
- - - {description} - -
- ); -} diff --git a/packages/bot/src/core/blitz/normalizeLeagueIcon.ts b/packages/bot/src/core/blitz/normalizeLeagueIcon.ts deleted file mode 100644 index 4c450ed19..000000000 --- a/packages/bot/src/core/blitz/normalizeLeagueIcon.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function normalizeLeagueIcon(url: string) { - return url.startsWith('http') ? url : `https:${url}`; -} diff --git a/packages/bot/src/core/blitz/resolveTankId.ts b/packages/bot/src/core/blitz/resolveTankId.ts deleted file mode 100644 index 4415a8362..000000000 --- a/packages/bot/src/core/blitz/resolveTankId.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { SEARCH_KEYS } from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import { Locale } from "discord.js"; -import { go } from "fuzzysort"; -import markdownEscape from "markdown-escape"; -import { tankDefinitions, tankNames } from "../blitzkit/nonBlockingPromises"; -import { UserError } from "../blitzkit/userError"; -import { tankNamesTechTreeOnly } from "../discord/autocompleteTanks"; -import { translator } from "../localization/translator"; - -export async function resolveTankId( - tank: string | number, - locale: Locale, - techTreeOnly = false -) { - const { strings } = translator(locale); - const number = typeof tank === "string" ? Number(tank) : tank; - - if (Number.isNaN(number)) { - const searchResult = go( - `${tank}`, - await (techTreeOnly ? tankNamesTechTreeOnly : tankNames), - { keys: SEARCH_KEYS, limit: 1 } - ); - - if (searchResult.length === 0) { - throw new UserError( - literals(strings.bot.common.errors.tank_name_not_found, { - search: markdownEscape(`${tank}`), - }) - ); - } else { - return searchResult[0].obj.id; - } - } else { - const awaitedTankDefinitions = await tankDefinitions; - - if (awaitedTankDefinitions.tanks[number]) { - return number; - } else { - throw new UserError( - literals(strings.bot.common.errors.tank_id_not_found, { - search: number, - }) - ); - } - } -} diff --git a/packages/bot/src/core/blitz/searchClansAcrossRegionsBotWrapper.ts b/packages/bot/src/core/blitz/searchClansAcrossRegionsBotWrapper.ts deleted file mode 100644 index c06e03b30..000000000 --- a/packages/bot/src/core/blitz/searchClansAcrossRegionsBotWrapper.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { searchClansAcrossRegions } from '@blitzkit/core'; -import { Locale } from 'discord.js'; -import { UserError } from '../blitzkit/userError'; -import { translator } from '../localization/translator'; - -export async function searchClansAcrossRegionsBotWrapper( - search: string, - locale: Locale, - limit?: number, -) { - const { strings } = translator(locale); - const trimmed = search.trim(); - - if (trimmed.length < 3) { - throw new UserError(strings.bot.common.errors.query_too_small); - } - - if (trimmed.length > 100) { - throw new UserError(strings.bot.common.errors.query_too_long); - } - - return await searchClansAcrossRegions(trimmed, limit); -} diff --git a/packages/bot/src/core/blitz/searchPlayersAcrossRegionsBotWrapper.ts b/packages/bot/src/core/blitz/searchPlayersAcrossRegionsBotWrapper.ts deleted file mode 100644 index 89e473a8d..000000000 --- a/packages/bot/src/core/blitz/searchPlayersAcrossRegionsBotWrapper.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { searchPlayersAcrossRegions } from '@blitzkit/core'; -import { Locale } from 'discord.js'; -import { UserError } from '../blitzkit/userError'; -import { translator } from '../localization/translator'; - -export async function searchPlayersAcrossRegionsBotWrapper( - search: string, - locale: Locale, - limit?: number, -) { - const { strings } = translator(locale); - const trimmed = search.trim(); - - if (trimmed.length < 3) { - throw new UserError(strings.bot.common.errors.query_too_small); - } - - if (trimmed.length > 100) { - throw new UserError(strings.bot.common.errors.query_too_long); - } - - return await searchPlayersAcrossRegions(trimmed, limit); -} diff --git a/packages/bot/src/core/blitzkit/buildTechTreeLine.ts b/packages/bot/src/core/blitzkit/buildTechTreeLine.ts deleted file mode 100644 index e8e1ed463..000000000 --- a/packages/bot/src/core/blitzkit/buildTechTreeLine.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { fetchTankDefinitions } from '@blitzkit/core'; -import { resolveAncestry } from './resolveAncestry'; - -export async function buildTechTreeLine(start: number, end: number) { - const tankDefinitions = await fetchTankDefinitions(); - const endTank = tankDefinitions.tanks[end]; - if (!endTank.ancestors) throw new Error('End tank has no ancestors'); - const line = [end]; - - if (start === end) return line; - for (const ancestorId of endTank.ancestors) { - if ((await resolveAncestry(ancestorId)).includes(start)) { - const ancestorTree = await buildTechTreeLine(start, ancestorId); - line.push(...ancestorTree); - break; - } - } - - return line; -} diff --git a/packages/bot/src/core/blitzkit/filtersToDescription.ts b/packages/bot/src/core/blitzkit/filtersToDescription.ts deleted file mode 100644 index a8c8377cd..000000000 --- a/packages/bot/src/core/blitzkit/filtersToDescription.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { fetchTankDefinitions, TIER_ROMAN_NUMERALS } from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import { Locale } from "discord.js"; -import { type StatFilters } from "../blitzstars/filterStats"; -import { translator } from "../localization/translator"; - -export async function filtersToDescription( - { nation, tier, type, class: tankClass, tank }: StatFilters, - locale: Locale, -) { - const { strings, bkLocale } = translator(locale); - const awaitedTankDefinitions = await fetchTankDefinitions(); - const info: string[] = []; - - // TODO: somehow localize tank names? - if (tank) return awaitedTankDefinitions.tanks[tank].name!.locales[bkLocale]; - if (nation) - info.push((strings.common.nations as Record)[nation]); - if (tier) { - info.push( - literals(strings.bot.common.filters.tier, { - tier: TIER_ROMAN_NUMERALS[tier], - }), - ); - } - if (tankClass) info.push(strings.common.tank_class_short[tankClass]); - if (type) info.push(strings.common.tree_type[type]); - - return info.length === 0 ? strings.bot.common.filters.none : info.join(", "); -} diff --git a/packages/bot/src/core/blitzkit/getBlitzFromDiscord.ts b/packages/bot/src/core/blitzkit/getBlitzFromDiscord.ts deleted file mode 100644 index 16088c64b..000000000 --- a/packages/bot/src/core/blitzkit/getBlitzFromDiscord.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { idToRegion } from '@blitzkit/core'; -import { flagUserActivity } from '../db/flagUserActivity'; -import { usersDatabase } from '../db/users'; - -export async function getBlitzFromDiscord(discordId: bigint) { - const unique = await usersDatabase.discordUser.findUnique({ - where: { discord_id: discordId }, - }); - - if (unique === null) return null; - - const id = Number(unique.blitz_id); - - await flagUserActivity(id); - return { id, region: idToRegion(id) }; -} diff --git a/packages/bot/src/core/blitzkit/getPeriodNow.ts b/packages/bot/src/core/blitzkit/getPeriodNow.ts deleted file mode 100644 index 030f2aa94..000000000 --- a/packages/bot/src/core/blitzkit/getPeriodNow.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function getPeriodNow() { - return Date.now() / 1000; -} diff --git a/packages/bot/src/core/blitzkit/getPeriodStart.ts b/packages/bot/src/core/blitzkit/getPeriodStart.ts deleted file mode 100644 index aef8b4328..000000000 --- a/packages/bot/src/core/blitzkit/getPeriodStart.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { getTimeDaysAgo, type Region } from '@blitzkit/core'; -import type { PeriodSize } from '../discord/addPeriodSubCommands'; - -export function getPeriodStart(region: Region, period: PeriodSize) { - if (period === 'career') return 0; - if (period === 'today') return getTimeDaysAgo(region, 1); - return getTimeDaysAgo(region, parseInt(period)); -} diff --git a/packages/bot/src/core/blitzkit/iconPng.ts b/packages/bot/src/core/blitzkit/iconPng.ts deleted file mode 100644 index c575b07b7..000000000 --- a/packages/bot/src/core/blitzkit/iconPng.ts +++ /dev/null @@ -1,16 +0,0 @@ -import sharp from "sharp"; - -const cache: Record = {}; - -export async function iconPng(url: string) { - if (!url.endsWith(".webp")) return url; - - if (!cache[url]) { - const imageResponse = await fetch(url); - const imageBufferWEBP = await imageResponse.arrayBuffer(); - const imageBufferPNG = await sharp(imageBufferWEBP).png().toBuffer(); - cache[url] = imageBufferPNG; - } - - return `data:image/png;base64,${cache[url].toString("base64")}`; -} diff --git a/packages/bot/src/core/blitzkit/jsxToPng.ts b/packages/bot/src/core/blitzkit/jsxToPng.ts deleted file mode 100644 index bc8ae24d0..000000000 --- a/packages/bot/src/core/blitzkit/jsxToPng.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { jsxToSvg } from "./jsxToSvg"; -import { svgToPng } from "./svgToPng"; - -export async function jsxToPng(jsx: React.JSX.Element) { - const svg = await jsxToSvg(jsx); - const png = svgToPng(svg); - - return png; -} diff --git a/packages/bot/src/core/blitzkit/jsxToPngThreaded.ts b/packages/bot/src/core/blitzkit/jsxToPngThreaded.ts deleted file mode 100644 index 16ee14401..000000000 --- a/packages/bot/src/core/blitzkit/jsxToPngThreaded.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { jsxToSvg } from "./jsxToSvg"; -import { RenderConfiguration } from "./renderConfiguration"; -import { svgToPngThreaded } from "./svgToPngThreaded"; - -export async function jsxToPngThreaded( - jsx: React.JSX.Element, - renderConfiguration: RenderConfiguration -) { - const svg = await jsxToSvg(jsx, renderConfiguration); - const png = await svgToPngThreaded(svg); - return png; -} diff --git a/packages/bot/src/core/blitzkit/jsxToSvg/Roboto-Black.ttf b/packages/bot/src/core/blitzkit/jsxToSvg/Roboto-Black.ttf deleted file mode 100644 index 96ddf0003..000000000 Binary files a/packages/bot/src/core/blitzkit/jsxToSvg/Roboto-Black.ttf and /dev/null differ diff --git a/packages/bot/src/core/blitzkit/jsxToSvg/Roboto-Bold.ttf b/packages/bot/src/core/blitzkit/jsxToSvg/Roboto-Bold.ttf deleted file mode 100644 index 7a876e28e..000000000 Binary files a/packages/bot/src/core/blitzkit/jsxToSvg/Roboto-Bold.ttf and /dev/null differ diff --git a/packages/bot/src/core/blitzkit/jsxToSvg/Roboto.ttf b/packages/bot/src/core/blitzkit/jsxToSvg/Roboto.ttf deleted file mode 100644 index 3033308a6..000000000 Binary files a/packages/bot/src/core/blitzkit/jsxToSvg/Roboto.ttf and /dev/null differ diff --git a/packages/bot/src/core/blitzkit/jsxToSvg/index.ts b/packages/bot/src/core/blitzkit/jsxToSvg/index.ts deleted file mode 100644 index 40c0e7bb0..000000000 --- a/packages/bot/src/core/blitzkit/jsxToSvg/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { readFile } from "fs/promises"; -import satori from "satori"; -import { RenderConfiguration } from "../renderConfiguration"; -import robotoBlack from "./Roboto-Black.ttf"; -import robotoBold from "./Roboto-Bold.ttf"; -import roboto from "./Roboto.ttf"; - -const fontsPromise = [ - { file: roboto, weight: 400 as const }, - { file: robotoBold, weight: 700 as const }, - { file: robotoBlack, weight: 900 as const }, -].map(async ({ file, weight }) => { - const data = await readFile(`${__dirname}/${file}`); - return { name: "Roboto", weight, data }; -}); - -export async function jsxToSvg( - jsx: React.JSX.Element, - renderConfiguration = new RenderConfiguration() -) { - const fonts = await Promise.all(fontsPromise); - return await satori(jsx, { width: renderConfiguration.width, fonts }); -} diff --git a/packages/bot/src/core/blitzkit/nonBlockingPromises.ts b/packages/bot/src/core/blitzkit/nonBlockingPromises.ts deleted file mode 100644 index 647a955d0..000000000 --- a/packages/bot/src/core/blitzkit/nonBlockingPromises.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { - fetchAverageDefinitions, - fetchDiscoveredIdsDefinitions, - fetchGameDefinitions, - fetchMapDefinitions, - fetchTankDefinitions, - fetchTankNames, -} from "@blitzkit/core"; - -export const averageDefinitions = fetchAverageDefinitions(); -export const tankDefinitions = fetchTankDefinitions(); -export const gameDefinitions = fetchGameDefinitions(); -export const tankNames = fetchTankNames(); -export const mapDefinitions = fetchMapDefinitions(); -export const discoveredIdsDefinitions = fetchDiscoveredIdsDefinitions(); diff --git a/packages/bot/src/core/blitzkit/processes.ts b/packages/bot/src/core/blitzkit/processes.ts deleted file mode 100644 index a639e25cc..000000000 --- a/packages/bot/src/core/blitzkit/processes.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { usersDatabase } from "../db/users"; -import { client } from "../discord/client"; - -const EXIT_EVENTS = ["SIGINT", "SIGTERM", "SIGUSR1", "SIGUSR2"]; - -let cleaningUp = false; - -async function cleanup(signal: string) { - if (cleaningUp) return; - cleaningUp = true; - - console.log(`Cleaning up... (${signal})`); - - try { - await Promise.all([usersDatabase.$disconnect(), client.destroy()]); - } catch (err) { - console.error("Cleanup error:", err); - } - - console.log("Gracefully exiting..."); - - process.exit(0); -} - -export function registerProcesses() { - process.on("uncaughtException", (err) => { - console.error("uncaughtException:", err); - }); - - process.on("unhandledRejection", (err) => { - console.error("unhandledRejection:", err); - }); - - client.on("error", console.error); - - EXIT_EVENTS.forEach((event) => process.on(event, () => cleanup(event))); -} diff --git a/packages/bot/src/core/blitzkit/renderConfiguration.ts b/packages/bot/src/core/blitzkit/renderConfiguration.ts deleted file mode 100644 index 508e20591..000000000 --- a/packages/bot/src/core/blitzkit/renderConfiguration.ts +++ /dev/null @@ -1,11 +0,0 @@ -export interface RenderConfiguration { - width: number; -} - -export class RenderConfiguration implements RenderConfiguration { - width = 480; - - constructor(configuration?: Partial) { - Object.assign(this, configuration); - } -} diff --git a/packages/bot/src/core/blitzkit/resolveAncestry.ts b/packages/bot/src/core/blitzkit/resolveAncestry.ts deleted file mode 100644 index e5ac8b5db..000000000 --- a/packages/bot/src/core/blitzkit/resolveAncestry.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { fetchTankDefinitions } from '@blitzkit/core'; - -export async function resolveAncestry(id: number) { - const tankDefinitions = await fetchTankDefinitions(); - const tank = tankDefinitions.tanks[id]; - const ancestors: number[] = []; - - if (tank.ancestors) { - for (const ancestor of tank.ancestors) { - if (!ancestors.includes(ancestor)) { - ancestors.push(ancestor); - - for (const ancestorLayer2 of await resolveAncestry(ancestor)) { - if (!ancestors.includes(ancestorLayer2)) { - ancestors.push(ancestorLayer2); - } - } - } - } - } - - return ancestors.sort( - (a, b) => tankDefinitions.tanks[b].tier - tankDefinitions.tanks[a].tier, - ); -} diff --git a/packages/bot/src/core/blitzkit/svgToPng.ts b/packages/bot/src/core/blitzkit/svgToPng.ts deleted file mode 100644 index 06ace7446..000000000 --- a/packages/bot/src/core/blitzkit/svgToPng.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Resvg } from '@resvg/resvg-js'; - -export function svgToPng(svg: string) { - return new Resvg(svg).render().asPng(); -} diff --git a/packages/bot/src/core/blitzkit/svgToPngThreaded.ts b/packages/bot/src/core/blitzkit/svgToPngThreaded.ts deleted file mode 100644 index 344fb4627..000000000 --- a/packages/bot/src/core/blitzkit/svgToPngThreaded.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Worker } from 'worker_threads'; - -const RENDER_WORKERS = 1; - -const workers = [...Array(RENDER_WORKERS)].map(() => { - const worker = new Worker('dist/bot/workers/render.js'); - console.log(`🟡 Launching render worker ${worker.threadId}`); - return worker; -}); -const queue: { - svg: string; - resolve: (png: Buffer) => void; -}[] = []; - -function manageQueue() { - if (queue.length > 0 && workers.length > 0) { - const { svg, resolve } = queue.shift()!; - const worker = workers.shift()!; - - function cleanup() { - workers.push(worker); - worker.off('message', handleMessage); - worker.off('error', handleError); - manageQueue(); - } - function handleMessage(png: Uint8Array) { - cleanup(); - resolve(Buffer.from(png)); - } - function handleError(error: Error) { - cleanup(); - throw error; - } - - worker.postMessage(svg); - worker.on('message', handleMessage); - worker.on('error', handleError); - } -} - -export function svgToPngThreaded(svg: string) { - return new Promise((resolve) => { - queue.push({ svg, resolve }); - manageQueue(); - }); -} diff --git a/packages/bot/src/core/blitzkit/tankIconPng.ts b/packages/bot/src/core/blitzkit/tankIconPng.ts deleted file mode 100644 index a15b09c8b..000000000 --- a/packages/bot/src/core/blitzkit/tankIconPng.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { tankIcon } from '@blitzkit/core'; -import { iconPng } from './iconPng'; - -export function tankIconPng(id: number) { - return iconPng(tankIcon(id)); -} diff --git a/packages/bot/src/core/blitzkit/userError.ts b/packages/bot/src/core/blitzkit/userError.ts deleted file mode 100644 index 44a647f15..000000000 --- a/packages/bot/src/core/blitzkit/userError.ts +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Discriminator for user caused errors to not report in logs - */ -export class UserError { - constructor(public message: string) {} -} diff --git a/packages/bot/src/core/blitzstars/filterStats.ts b/packages/bot/src/core/blitzstars/filterStats.ts deleted file mode 100644 index 2936170a9..000000000 --- a/packages/bot/src/core/blitzstars/filterStats.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { TankClass, TankType } from "@blitzkit/core"; -import type { SupplementaryStats } from "@blitzkit/core/src/blitz/getAccountInfo"; -import { calculateWN8 } from "@blitzkit/core/src/statistics/calculateWN8"; -import type { BlitzStats } from "@blitzkit/core/src/statistics/compositeStats"; -import { sumAllStats } from "@blitzkit/core/src/statistics/sumAllStats"; -import { - averageDefinitions, - tankDefinitions, -} from "../blitzkit/nonBlockingPromises"; - -export interface StatFilters { - nation?: string; - tier?: number; - class?: TankClass; - type?: TankType; - tank?: number; -} - -export async function filterStats( - { diff, order }: DiffedTankStats, - filters: StatFilters, -) { - // tank, if provided, takes priority over all other filters - if (filters.tank) filters = { tank: filters.tank }; - - const awaitedTankDefinitions = await tankDefinitions; - const awaitedTankAverages = await averageDefinitions; - const filteredOrder = order.filter((id) => { - const entry = awaitedTankDefinitions.tanks[id]; - - return ( - entry && - (filters.nation === undefined || entry.nation === filters.nation) && - (filters.tier === undefined || entry.tier === filters.tier) && - (filters.class === undefined || entry.class === filters.class) && - (filters.type === undefined || - (filters.type === TankType.TANK_TYPE_COLLECTOR && - entry.type === TankType.TANK_TYPE_COLLECTOR) || - (filters.type === TankType.TANK_TYPE_PREMIUM && - entry.type === TankType.TANK_TYPE_PREMIUM) || - (filters.type === TankType.TANK_TYPE_RESEARCHABLE && - entry.type === TankType.TANK_TYPE_RESEARCHABLE)) && - (filters.tank === undefined || entry.id === filters.tank) - ); - }); - const stats = sumAllStats(filteredOrder.map((id) => diff[id])); - const battlesOfTanksWithAverages = filteredOrder.reduce( - (accumulator, id) => - awaitedTankAverages.averages[id] - ? accumulator + diff[id].battles - : accumulator, - 0, - ); - const battlesOfTanksWithTankDefinition = filteredOrder.reduce( - (accumulator, id) => - awaitedTankDefinitions.tanks[id] - ? accumulator + diff[id].battles - : accumulator, - 0, - ); - const supplementary = { - WN8: - filteredOrder.reduce( - (accumulator, id) => - awaitedTankAverages.averages[id] - ? accumulator + - calculateWN8(awaitedTankAverages.averages[id].mu, diff[id]) * - diff[id].battles - : accumulator, - 0, - ) / battlesOfTanksWithAverages, - tier: - filteredOrder.reduce( - (accumulator, id) => - awaitedTankDefinitions.tanks[id] - ? accumulator + - awaitedTankDefinitions.tanks[id]!.tier * diff[id].battles - : accumulator, - 0, - ) / battlesOfTanksWithTankDefinition, - } satisfies SupplementaryStats; - - return { stats, supplementary, filteredOrder }; -} - -export interface DiffedTankStats { - diff: Record; - order: number[]; -} diff --git a/packages/bot/src/core/blitzstars/getBlitzStarsLinkButton.ts b/packages/bot/src/core/blitzstars/getBlitzStarsLinkButton.ts deleted file mode 100644 index 9a17855e7..000000000 --- a/packages/bot/src/core/blitzstars/getBlitzStarsLinkButton.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { getAccountInfo, type Region } from "@blitzkit/core"; -import { buttonLink } from "../discord/buttonLink"; - -export async function getBlitzStarsLinkButton( - region: Region, - id: number, - tankId?: number -) { - const { nickname } = await getAccountInfo(region, id); - - return buttonLink( - `https://www.blitzstars.com/player/${region}/${nickname}${ - tankId ? `/tank/${tankId!}` : "" - }`, - "BlitzStars" - ); -} diff --git a/packages/bot/src/core/blitzstars/getPlayerHistories.ts b/packages/bot/src/core/blitzstars/getPlayerHistories.ts deleted file mode 100644 index 0434b8116..000000000 --- a/packages/bot/src/core/blitzstars/getPlayerHistories.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { type Region, emptyAllStats, getAccountInfo } from '@blitzkit/core'; -import { - type GetHistoriesOptions, - type Histories, - type History, - getHistoriesDefaultOptions, -} from '../../types/histories'; -import type { PlayerStats } from './getPlayerStats'; - -export async function getPlayerHistories( - server: Region, - id: number, - options?: Partial, -) { - const mergedOptions = { ...getHistoriesDefaultOptions, ...options }; - const response = await fetch( - `https://www.blitzstars.com/api/playerstats/${id}`, - ); - const playerHistories = ((await response.json()) as PlayerStats).map( - (history) => - ({ - all: history.statistics.all, - last_battle_time: history.last_battle_time, - }) satisfies History, - ); - let lastNonCompliantIndex = -1; - let hasCompliedOnce = false; - const filtered = playerHistories.filter(({ last_battle_time }, index) => { - const complies = - last_battle_time >= mergedOptions.start && - last_battle_time <= mergedOptions.end; - - if (complies && !hasCompliedOnce) hasCompliedOnce = true; - if (!hasCompliedOnce) lastNonCompliantIndex = index; - - return complies; - }); - - const latestNodes: Histories = []; - - if (mergedOptions.includeLatestHistories) { - const { last_battle_time, statistics } = await getAccountInfo(server, id); - - latestNodes.push({ all: statistics.all, last_battle_time }); - } - - const previousNodes: Histories = []; - - if (mergedOptions.includePreviousHistories) { - if (lastNonCompliantIndex !== -1) { - previousNodes.push( - playerHistories[lastNonCompliantIndex] ?? - ({ - all: emptyAllStats, - last_battle_time: -Infinity, - } satisfies History), - ); - } - } - - return [...latestNodes, ...filtered, ...previousNodes] satisfies Histories; -} diff --git a/packages/bot/src/core/blitzstars/getPlayerStats.ts b/packages/bot/src/core/blitzstars/getPlayerStats.ts deleted file mode 100644 index 1555bc147..000000000 --- a/packages/bot/src/core/blitzstars/getPlayerStats.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { BlitzStats, Region } from "@blitzkit/core"; - -interface IndividualPlayerStats { - clan: { clan_id: number; name: string; tag: string }; - statistics: { - all: BlitzStats; - wn8: number; - wn7: number; - average_tier: number; - }; - last_battle_time: number; - nickname: string; - account_id: number; - achievements: { - account_id: number; - [key: string]: number; - }; - region: Region; -} - -export type PlayerStats = IndividualPlayerStats[]; - -export async function getPlayerStats< - Ids extends number | number[], - ReturnType = Ids extends number ? PlayerStats : PlayerStats[], ->(region: Region, ids: Ids) { - const stats = await Promise.all( - ((typeof ids === "number" ? [ids] : ids) as number[]).map((id) => - fetch(`https://www.blitzstars.com/api/playerstats/${id}`).then( - (response) => response.json() as Promise - ) - ) - ); - - if (typeof ids === "number") return stats[0] as ReturnType; - return stats as ReturnType; -} diff --git a/packages/bot/src/core/blitzstars/getStatsInPeriod.ts b/packages/bot/src/core/blitzstars/getStatsInPeriod.ts deleted file mode 100644 index 861f5c8a5..000000000 --- a/packages/bot/src/core/blitzstars/getStatsInPeriod.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { type BlitzStats, emptyAllStats, type Region } from "@blitzkit/core"; -import { Locale } from "discord.js"; -import type { DiffedTankStats } from "./filterStats"; -import { - getTankHistories, - type TankHistories, - type TankHistoryRaw, -} from "./getTankHistories"; - -const emptyTankHistoryNode: TankHistoryRaw = { - account_id: 0, - battle_life_time: 0, - last_battle_time: 0, - mark_of_mastery: 0, - tank_id: 0, - all: emptyAllStats, -}; - -// BIG TODO: merge this neatly with diffNormalizedTankStats -export async function getStatsInPeriod( - server: Region, - id: number, - start: number, - end: number, - locale: Locale -) { - const history = await getTankHistories(server, id, locale, { - includeLatestHistories: true, - }); - - // sort them even though most won't be used - const tankSortedHistory: Record = {}; - const tanks: number[] = []; - history.forEach((node) => { - if (!tankSortedHistory[node.tank_id]) { - tankSortedHistory[node.tank_id] = [node]; - tanks.push(node.tank_id); - } else { - tankSortedHistory[node.tank_id].push(node); - } - }); - - // find the ones played within the range - const inRangeTanks = tanks.filter((tankId) => - tankSortedHistory[tankId].some( - (node) => node.last_battle_time > start && node.last_battle_time < end - ) - ); - const playedTanks: number[] = []; - - // fetch history node right before "start" and right before "end" - const diff: Record = {}; - inRangeTanks.forEach((tankId) => { - const sortedHistory = tankSortedHistory[tankId]; - const previousIndex = - sortedHistory.findIndex((node) => node.last_battle_time > start) - 1; - const latestIndex = - sortedHistory.findIndex((node) => node.last_battle_time > end) - 1; - const previous = sortedHistory[previousIndex] ?? emptyTankHistoryNode; - const latest = - sortedHistory[latestIndex] ?? sortedHistory[sortedHistory.length - 1]; - - function d(get: (allStats: BlitzStats) => number) { - return get(latest.all) - get(previous.all); - } - - // check if there was a change in battles as games in rating do update last_battle_time - if (d((a) => a.battles) > 0) { - diff[tankId] = { - battles: d((a) => a.battles), - capture_points: d((a) => a.capture_points), - damage_dealt: d((a) => a.damage_dealt), - damage_received: d((a) => a.damage_received), - dropped_capture_points: d((a) => a.dropped_capture_points), - frags: d((a) => a.frags), - frags8p: d((a) => a.frags8p), - hits: d((a) => a.hits), - losses: d((a) => a.losses), - max_frags: latest.all.max_frags, - max_xp: latest.all.max_xp, - shots: d((a) => a.shots), - spotted: d((a) => a.spotted), - survived_battles: d((a) => a.survived_battles), - win_and_survived: d((a) => a.win_and_survived), - wins: d((a) => a.wins), - xp: d((a) => a.xp), - }; - - playedTanks.push(tankId); - } - }); - - const order = playedTanks.sort((a, b) => { - const aHistories = tankSortedHistory[a]; - const bHistories = tankSortedHistory[b]; - const aLatest = aHistories[aHistories.length - 1]; - const bLatest = bHistories[bHistories.length - 1]; - - return bLatest.last_battle_time - aLatest.last_battle_time; - }); - - return { diff, order } satisfies DiffedTankStats; -} diff --git a/packages/bot/src/core/blitzstars/getTankHistories.ts b/packages/bot/src/core/blitzstars/getTankHistories.ts deleted file mode 100644 index a8cbb9f95..000000000 --- a/packages/bot/src/core/blitzstars/getTankHistories.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { type Region, emptyAllStats, getTankStats } from "@blitzkit/core"; -import { Locale } from "discord.js"; -import { - type GetHistoriesOptions, - type Histories, - type History, - getHistoriesDefaultOptions, -} from "../../types/histories"; -import { UserError } from "../blitzkit/userError"; -import { translator } from "../localization/translator"; - -export interface TankHistory extends History { - tank_id: number; -} - -export type TankHistories = TankHistory[]; - -export interface TankHistoryRaw extends TankHistory { - mark_of_mastery: number; - battle_life_time: number; - account_id: number; -} - -export type TankHistoriesRaw = TankHistoryRaw[]; - -export interface GetTankHistoriesOptions extends GetHistoriesOptions { - tankId?: number; -} - -const getTankHistoriesDefaultOptions: GetTankHistoriesOptions = { - ...getHistoriesDefaultOptions, - tankId: undefined, -}; - -export async function getTankHistories( - server: Region, - id: number, - locale: Locale, - options?: Partial -) { - const { strings } = translator(locale); - const mergedOptions = { ...getTankHistoriesDefaultOptions, ...options }; - const tankHistoriesResponse = await fetch( - `https://www.blitzstars.com/api/tankhistories/for/${id}/` - ); - const tankHistories = ( - (await tankHistoriesResponse.json()) as TankHistoriesRaw - ).map( - (history) => - ({ - all: history.all, - last_battle_time: history.last_battle_time, - tank_id: history.tank_id, - }) satisfies TankHistory - ); - const compliantTanks: number[] = []; - const lastBattles: Record = {}; - const lastNonCompliantIndex: Record = {}; - const hasCompliedOnce: Record = {}; - const filtered = tankHistories.filter( - ({ last_battle_time, tank_id, all: { battles } }, index) => { - const complies = - last_battle_time >= mergedOptions.start && - last_battle_time <= mergedOptions.end && - (mergedOptions.tankId === undefined || - tank_id === mergedOptions.tankId); - - if (complies && !hasCompliedOnce[tank_id]) { - hasCompliedOnce[tank_id] = true; - compliantTanks.push(tank_id); - } - if (!hasCompliedOnce[tank_id]) lastNonCompliantIndex[tank_id] = index; - lastBattles[tank_id] = battles; - - return complies; - } - ); - - const latestNodes: TankHistories = []; - - if (mergedOptions.includeLatestHistories) { - const tankStats = await getTankStats(server, id); - - if (tankStats === null) { - throw new UserError(strings.bot.common.errors.no_tank_stats); - } - - tankStats.forEach((tankHistory) => { - if ( - tankHistory.all.battles > (lastBattles[tankHistory.tank_id] ?? 0) && - (mergedOptions.tankId === undefined || - tankHistory.tank_id === mergedOptions.tankId) - ) { - if (!hasCompliedOnce[tankHistory.tank_id]) { - // was played today for the first time... I think??? - hasCompliedOnce[tankHistory.tank_id] = true; - compliantTanks.push(tankHistory.tank_id); - } - latestNodes.push(tankHistory); - } - }); - } - - const previousNodes: TankHistories = []; - - if (mergedOptions.includePreviousHistories) { - compliantTanks.forEach((tankId) => { - if (tankId in lastNonCompliantIndex) { - previousNodes.push( - tankHistories[lastNonCompliantIndex[tankId]] ?? - ({ - all: emptyAllStats, - last_battle_time: -Infinity, - tank_id: tankId, - } satisfies TankHistory) - ); - } - }); - } - - return [...previousNodes, ...filtered, ...latestNodes] satisfies Histories; -} diff --git a/packages/bot/src/core/db/flagUserActivity.ts b/packages/bot/src/core/db/flagUserActivity.ts deleted file mode 100644 index 4fa958410..000000000 --- a/packages/bot/src/core/db/flagUserActivity.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { usersDatabase } from './users'; - -export async function flagUserActivity(blitzId: number) { - await usersDatabase.user.upsert({ - where: { blitz_id: blitzId }, - update: {}, - create: { blitz_id: blitzId }, - }); -} diff --git a/packages/bot/src/core/db/linkBlitzAndDiscord.ts b/packages/bot/src/core/db/linkBlitzAndDiscord.ts deleted file mode 100644 index 229aaeb1c..000000000 --- a/packages/bot/src/core/db/linkBlitzAndDiscord.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { flagUserActivity } from './flagUserActivity'; -import { usersDatabase } from './users'; - -export async function linkBlitzAndDiscord(discordId: bigint, blitzId: number) { - await usersDatabase.discordUser.upsert({ - where: { discord_id: discordId }, - update: { blitz_id: blitzId }, - create: { discord_id: discordId, blitz_id: blitzId }, - }); - await flagUserActivity(blitzId); -} diff --git a/packages/bot/src/core/db/users.ts b/packages/bot/src/core/db/users.ts deleted file mode 100644 index bfa600844..000000000 --- a/packages/bot/src/core/db/users.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { PrismaClient } from '../../prisma'; - -export const usersDatabase = new PrismaClient(); diff --git a/packages/bot/src/core/discord/addClanChoices.ts b/packages/bot/src/core/discord/addClanChoices.ts deleted file mode 100644 index 328ee5c34..000000000 --- a/packages/bot/src/core/discord/addClanChoices.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Locale, SlashCommandStringOption } from 'discord.js'; -import { translator } from '../localization/translator'; -import { localizationObject } from './localizationObject'; - -export function addClanChoices(option: SlashCommandStringOption) { - const { strings } = translator(Locale.EnglishUS); - - return option - .setName('clan') - .setNameLocalizations( - localizationObject( - (strings) => strings.bot.common.options.clan.name, - undefined, - true, - ), - ) - .setDescription(strings.bot.common.options.clan.description) - .setDescriptionLocalizations( - localizationObject( - (strings) => strings.bot.common.options.clan.description, - ), - ) - .setAutocomplete(true) - .setMinLength(2) - .setRequired(true); -} diff --git a/packages/bot/src/core/discord/addFilterOptions.ts b/packages/bot/src/core/discord/addFilterOptions.ts deleted file mode 100644 index 7e1efd606..000000000 --- a/packages/bot/src/core/discord/addFilterOptions.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { - TANK_CLASSES, - TANK_TYPE_COMMAND_NAMES, - TANK_TYPES, -} from '@blitzkit/core'; -import { - type APIApplicationCommandOptionChoice, - Locale, - SlashCommandBuilder, - SlashCommandSubcommandBuilder, -} from 'discord.js'; -import { translator } from '../localization/translator'; -import { addTankChoices } from './addTankChoices'; -import { addTierChoices } from './addTierChoices'; -import { localizationObject } from './localizationObject'; - -export function addFilterOptions< - Option extends SlashCommandBuilder | SlashCommandSubcommandBuilder, ->(option: Option, nations: string[]) { - const { strings } = translator(Locale.EnglishUS); - - return ( - option - .addStringOption((option) => - option - .setName(strings.bot.common.options.filters.nation.name) - .setNameLocalizations( - localizationObject( - (strings) => strings.bot.common.options.filters.nation.name, - undefined, - true, - ), - ) - .setDescription(strings.bot.common.options.filters.nation.description) - .setDescriptionLocalizations( - localizationObject( - (strings) => - strings.bot.common.options.filters.nation.description, - ), - ) - .addChoices( - ...nations.map( - (nation) => - ({ - value: nation, - name: (strings.common.nations as Record)[ - nation - ], - name_localizations: localizationObject( - (strings) => - (strings.common.nations as Record)[ - nation - ], - ), - }) satisfies APIApplicationCommandOptionChoice, - ), - ) - .setRequired(false), - ) - .addStringOption((option) => addTierChoices(option).setRequired(false)) - .addStringOption((option) => - option - .setName(strings.bot.common.options.filters.class.name) - .setNameLocalizations( - localizationObject( - (strings) => strings.bot.common.options.filters.class.name, - undefined, - true, - ), - ) - .setDescription(strings.bot.common.options.filters.class.description) - .setDescriptionLocalizations( - localizationObject( - (strings) => strings.bot.common.options.filters.class.description, - ), - ) - .addChoices( - ...TANK_CLASSES.map( - (tankType) => - ({ - value: `${tankType}`, - name: strings.common.tank_class_short[tankType], - name_localizations: localizationObject( - (strings) => strings.common.tank_class_short[tankType], - ), - }) satisfies APIApplicationCommandOptionChoice, - ), - ) - .setRequired(false), - ) - .addStringOption((option) => - option - .setName(strings.bot.common.options.filters.type.name) - .setNameLocalizations( - localizationObject( - (strings) => strings.bot.common.options.filters.type.name, - undefined, - true, - ), - ) - .setDescription(strings.bot.common.options.filters.type.description) - .setDescriptionLocalizations( - localizationObject( - (strings) => strings.bot.common.options.filters.type.description, - ), - ) - .addChoices( - ...TANK_TYPES.map( - (treeType) => - ({ - value: TANK_TYPE_COMMAND_NAMES[treeType], - name: strings.common.tree_type[treeType], - name_localizations: localizationObject( - (strings) => strings.common.tree_type[treeType], - ), - }) satisfies APIApplicationCommandOptionChoice, - ), - ) - .setRequired(false), - ) - // TODO: somehow localize this? - .addStringOption((option) => - addTankChoices(option).setRequired(false), - ) as Option - ); -} diff --git a/packages/bot/src/core/discord/addPeriodSubCommands.ts b/packages/bot/src/core/discord/addPeriodSubCommands.ts deleted file mode 100644 index 94cf335d4..000000000 --- a/packages/bot/src/core/discord/addPeriodSubCommands.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { - Locale, - SlashCommandBuilder, - SlashCommandSubcommandBuilder, - SlashCommandSubcommandGroupBuilder, -} from 'discord.js'; -import { translator } from '../localization/translator'; -import { localizationObject } from './localizationObject'; - -export type PeriodType = 'today' | '30' | '60' | '90' | 'career' | 'custom'; -export type PeriodSize = 'today' | `${number}` | 'career'; - -export function addPeriodSubCommands( - option: SlashCommandBuilder | SlashCommandSubcommandGroupBuilder, - extra: ( - option: SlashCommandSubcommandBuilder, - ) => SlashCommandSubcommandBuilder = (option) => option, -) { - const { strings } = translator(Locale.EnglishUS); - const subcommandNameLocalizations = { - today: localizationObject( - (strings) => strings.bot.common.subcommands.today.name, - undefined, - true, - ), - 30: localizationObject( - (strings) => strings.bot.common.subcommands[30].name, - undefined, - true, - ), - 60: localizationObject( - (strings) => strings.bot.common.subcommands[60].name, - undefined, - true, - ), - 90: localizationObject( - (strings) => strings.bot.common.subcommands[90].name, - undefined, - true, - ), - career: localizationObject( - (strings) => strings.bot.common.subcommands.career.name, - undefined, - true, - ), - custom: localizationObject( - (strings) => strings.bot.common.subcommands.custom.name, - undefined, - true, - ), - }; - const subcommandDescriptionLocalizations = { - today: localizationObject( - (strings) => strings.bot.common.subcommands.today.description, - ), - 30: localizationObject( - (strings) => strings.bot.common.subcommands[30].description, - ), - 60: localizationObject( - (strings) => strings.bot.common.subcommands[60].description, - ), - 90: localizationObject( - (strings) => strings.bot.common.subcommands[90].description, - ), - career: localizationObject( - (strings) => strings.bot.common.subcommands.career.description, - ), - custom: localizationObject( - (strings) => strings.bot.common.subcommands.custom.description, - ), - }; - - option - .addSubcommand((option) => - extra( - option - .setName(strings.bot.common.subcommands.today.name) - .setNameLocalizations(subcommandNameLocalizations.today) - .setDescription(strings.bot.common.subcommands.today.description) - .setDescriptionLocalizations( - subcommandDescriptionLocalizations.today, - ), - ), - ) - .addSubcommand((option) => - extra( - option - .setName(strings.bot.common.subcommands[30].name) - .setNameLocalizations(subcommandNameLocalizations[30]) - .setDescription(strings.bot.common.subcommands[30].description) - .setDescriptionLocalizations(subcommandDescriptionLocalizations[30]), - ), - ) - .addSubcommand((option) => - extra( - option - .setName(strings.bot.common.subcommands[60].name) - .setNameLocalizations(subcommandNameLocalizations[60]) - .setDescription(strings.bot.common.subcommands[60].description) - .setDescriptionLocalizations(subcommandDescriptionLocalizations[60]), - ), - ) - .addSubcommand((option) => - extra( - option - .setName(strings.bot.common.subcommands[90].name) - .setNameLocalizations(subcommandNameLocalizations[90]) - .setDescription(strings.bot.common.subcommands[90].description) - .setDescriptionLocalizations(subcommandDescriptionLocalizations[90]), - ), - ) - .addSubcommand((option) => - extra( - option - .setName(strings.bot.common.subcommands.career.name) - .setNameLocalizations(subcommandNameLocalizations.career) - .setDescription(strings.bot.common.subcommands.career.description) - .setDescriptionLocalizations( - subcommandDescriptionLocalizations.career, - ), - ), - ) - .addSubcommand((option) => - extra( - option - .setName(strings.bot.common.subcommands.custom.name) - .setNameLocalizations(subcommandNameLocalizations.custom) - .setDescription(strings.bot.common.subcommands.custom.description) - .setDescriptionLocalizations( - subcommandDescriptionLocalizations.custom, - ) - .addIntegerOption((option) => - option - .setName(strings.bot.common.subcommands.custom.options.start.name) - .setDescription( - strings.bot.common.subcommands.custom.options.start.description, - ) - .setRequired(true), - ) - .addIntegerOption((option) => - option - .setName(strings.bot.common.subcommands.custom.options.end.name) - .setDescription( - strings.bot.common.subcommands.custom.options.end.description, - ) - .setRequired(true), - ), - ), - ); -} diff --git a/packages/bot/src/core/discord/addPeriodicFilterOptions.ts b/packages/bot/src/core/discord/addPeriodicFilterOptions.ts deleted file mode 100644 index 8e9d2f04b..000000000 --- a/packages/bot/src/core/discord/addPeriodicFilterOptions.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { - SlashCommandBuilder, - SlashCommandSubcommandBuilder, - SlashCommandSubcommandGroupBuilder, -} from 'discord.js'; -import { gameDefinitions } from '../blitzkit/nonBlockingPromises'; -import { addFilterOptions } from './addFilterOptions'; -import { addPeriodSubCommands } from './addPeriodSubCommands'; - -export async function addPeriodicFilterOptions< - OptionType extends SlashCommandBuilder | SlashCommandSubcommandGroupBuilder, ->( - option: OptionType, - extra: ( - option: SlashCommandSubcommandBuilder, - ) => SlashCommandSubcommandBuilder = (option) => option, -): Promise { - const awaitedGameDefinitions = await gameDefinitions; - - addPeriodSubCommands(option, (option) => - addFilterOptions(extra(option), awaitedGameDefinitions.nations), - ); - - return option; -} diff --git a/packages/bot/src/core/discord/addRegionChoices.ts b/packages/bot/src/core/discord/addRegionChoices.ts deleted file mode 100644 index 38adf10c9..000000000 --- a/packages/bot/src/core/discord/addRegionChoices.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { REGIONS } from "@blitzkit/core"; -import { - type APIApplicationCommandOptionChoice, - Locale, - SlashCommandStringOption, -} from "discord.js"; -import { translator } from "../localization/translator"; -import { localizationObject } from "./localizationObject"; - -export function addRegionChoices(option: SlashCommandStringOption) { - const { strings } = translator(Locale.EnglishUS); - - return option - .setName(strings.bot.common.options.region.name) - .setNameLocalizations( - localizationObject( - (strings) => strings.bot.common.options.region.name, - undefined, - true - ) - ) - .setDescription(strings.bot.common.options.region.description) - .setDescriptionLocalizations( - localizationObject( - (strings) => strings.bot.common.options.region.description - ) - ) - .addChoices( - ...REGIONS.map( - (region) => - ({ - value: region, - name: strings.common.regions.normal[region], - name_localizations: localizationObject( - (strings) => strings.common.regions.normal[region] - ), - }) satisfies APIApplicationCommandOptionChoice - ) - ) - .setRequired(true); -} diff --git a/packages/bot/src/core/discord/addTankChoices.ts b/packages/bot/src/core/discord/addTankChoices.ts deleted file mode 100644 index 3b38e220b..000000000 --- a/packages/bot/src/core/discord/addTankChoices.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Locale, SlashCommandStringOption } from "discord.js"; -import { translator } from "../localization/translator"; -import { localizationObject } from "./localizationObject"; - -export function addTankChoices(option: SlashCommandStringOption) { - const { strings } = translator(Locale.EnglishUS); - - return option - .setName(strings.bot.common.options.tank.name!) - .setNameLocalizations( - localizationObject( - (strings) => strings.bot.common.options.tank.name, - undefined, - true, - ), - ) - .setDescription(strings.bot.common.options.tank.description) - .setDescriptionLocalizations( - localizationObject( - (strings) => strings.bot.common.options.tank.description, - ), - ) - .setAutocomplete(true) - .setRequired(true); -} diff --git a/packages/bot/src/core/discord/addTierChoices.ts b/packages/bot/src/core/discord/addTierChoices.ts deleted file mode 100644 index 40ac51671..000000000 --- a/packages/bot/src/core/discord/addTierChoices.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { numberToRomanNumeral } from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import { - APIApplicationCommandOptionChoice, - Locale, - SlashCommandStringOption, -} from "discord.js"; -import { range } from "lodash-es"; -import { translator } from "../localization/translator"; -import { localizationObject } from "./localizationObject"; - -export function addTierChoices(option: SlashCommandStringOption) { - const { strings } = translator(Locale.EnglishUS); - - return option - .setName(strings.bot.common.options.tier.name) - .setNameLocalizations( - localizationObject( - (strings) => strings.bot.common.options.tier.name, - undefined, - true - ) - ) - .setDescription(strings.bot.common.options.tier.description) - .setDescriptionLocalizations( - localizationObject( - (strings) => strings.bot.common.options.tier.description - ) - ) - .setChoices( - ...range(10, 0).map( - (tier) => - ({ - name: literals(strings.bot.common.options.tier.choices.tier, { - tier_1: tier, - tier_2: numberToRomanNumeral(tier), - }), - value: `${tier}`, - name_localizations: localizationObject( - (strings) => strings.bot.common.options.tier.choices.tier, - { tier_1: tier, tier_2: numberToRomanNumeral(tier) } - ), - }) satisfies APIApplicationCommandOptionChoice - ) - ) - .setRequired(true); -} diff --git a/packages/bot/src/core/discord/addUsernameChoices.ts b/packages/bot/src/core/discord/addUsernameChoices.ts deleted file mode 100644 index 35d851fdd..000000000 --- a/packages/bot/src/core/discord/addUsernameChoices.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Locale, SlashCommandStringOption } from 'discord.js'; -import { translator } from '../localization/translator'; -import { localizationObject } from './localizationObject'; - -export function addUsernameChoices(option: SlashCommandStringOption) { - const { strings } = translator(Locale.EnglishUS); - - return option - .setName(strings.bot.common.options.username.name) - .setNameLocalizations( - localizationObject( - (strings) => strings.bot.common.options.username.name, - undefined, - true, - ), - ) - .setDescription(strings.bot.common.options.username.description) - .setDescriptionLocalizations( - localizationObject( - (strings) => strings.bot.common.options.username.description, - ), - ) - .setAutocomplete(true); -} diff --git a/packages/bot/src/core/discord/autocompleteClan/constants.ts b/packages/bot/src/core/discord/autocompleteClan/constants.ts deleted file mode 100644 index 26b7f01f6..000000000 --- a/packages/bot/src/core/discord/autocompleteClan/constants.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const DISCORD_CHOICES_MAX_NAME_SIZE = 25; -export const OVERFLOW_SUFFIX = '…'; diff --git a/packages/bot/src/core/discord/autocompleteClan/index.ts b/packages/bot/src/core/discord/autocompleteClan/index.ts deleted file mode 100644 index 5ed5d8d3f..000000000 --- a/packages/bot/src/core/discord/autocompleteClan/index.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { searchClansAcrossRegions } from '@blitzkit/core'; -import { - ApplicationCommandOptionChoiceData, - AutocompleteInteraction, - CacheType, -} from 'discord.js'; -import { translator } from '../../localization/translator'; -import { DISCORD_CHOICES_MAX_NAME_SIZE, OVERFLOW_SUFFIX } from './constants'; - -export async function autocompleteClan( - interaction: AutocompleteInteraction, -) { - const { strings } = translator(interaction.locale); - const focusedOption = interaction.options.getFocused(true); - if (focusedOption.name !== 'clan') return; - if (focusedOption.value.length < 2) return interaction.respond([]); - const clans = await searchClansAcrossRegions(focusedOption.value); - - await interaction.respond( - clans - ? clans.map((clan) => { - let name = `${clan.name} (${strings.common.regions.short[clan.region]})`; - - if (name.length > DISCORD_CHOICES_MAX_NAME_SIZE) { - const overSize = name.length - DISCORD_CHOICES_MAX_NAME_SIZE; - - name = `${clan.name.slice( - 0, - clan.name.length - overSize - OVERFLOW_SUFFIX.length, - )}${OVERFLOW_SUFFIX} (${strings.common.regions.short[clan.region]})`; - } - - return { - name, - value: `${clan.region}/${clan.clan_id}`, - } satisfies ApplicationCommandOptionChoiceData; - }) - : [], - ); -} diff --git a/packages/bot/src/core/discord/autocompleteTanks.ts b/packages/bot/src/core/discord/autocompleteTanks.ts deleted file mode 100644 index e43c975fb..000000000 --- a/packages/bot/src/core/discord/autocompleteTanks.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { fetchTankNames, SEARCH_KEYS, TankType } from "@blitzkit/core"; -import { - type ApplicationCommandOptionChoiceData, - AutocompleteInteraction, - type CacheType, -} from "discord.js"; -import { go } from "fuzzysort"; -import { tankNames } from "../blitzkit/nonBlockingPromises"; -import { translator } from "../localization/translator"; -import { - DISCORD_CHOICES_MAX_NAME_SIZE, - OVERFLOW_SUFFIX, -} from "./autocompleteClan/constants"; - -export const tankNamesTechTreeOnly = fetchTankNames().then((names) => - names.filter((tank) => tank.treeType === TankType.TANK_TYPE_RESEARCHABLE), -); - -export async function autocompleteTanks( - interaction: AutocompleteInteraction, - techTreeOnly = false, - fields = ["tank"], -) { - const { unwrap } = translator(interaction.locale); - const focusedOption = interaction.options.getFocused(true); - if (!fields.includes(focusedOption.name)) return; - - await interaction.respond( - focusedOption.value - ? await Promise.all( - go( - focusedOption.value, - await (techTreeOnly ? tankNamesTechTreeOnly : tankNames), - { keys: SEARCH_KEYS, limit: 10 }, - ).map(async (item) => { - let name = unwrap(item.obj.searchableName); - - if (name.length > DISCORD_CHOICES_MAX_NAME_SIZE) { - const overSize = name.length - DISCORD_CHOICES_MAX_NAME_SIZE; - - name = `${unwrap(item.obj.searchableName).slice( - 0, - unwrap(item.obj.searchableName).length - - overSize - - OVERFLOW_SUFFIX.length, - )}${OVERFLOW_SUFFIX}`; - } - - return { - name, - value: `${item.obj.id}`, - } satisfies ApplicationCommandOptionChoiceData; - }), - ) - : [], - ); -} diff --git a/packages/bot/src/core/discord/autocompleteUsername.ts b/packages/bot/src/core/discord/autocompleteUsername.ts deleted file mode 100644 index 522721c84..000000000 --- a/packages/bot/src/core/discord/autocompleteUsername.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { searchPlayersAcrossRegions } from '@blitzkit/core'; -import { - ApplicationCommandOptionChoiceData, - AutocompleteInteraction, - CacheType, -} from 'discord.js'; -import { translator } from '../localization/translator'; -import { - DISCORD_CHOICES_MAX_NAME_SIZE, - OVERFLOW_SUFFIX, -} from './autocompleteClan/constants'; - -export async function autocompleteUsername( - interaction: AutocompleteInteraction, -) { - const { strings } = translator(interaction.locale); - const focusedOption = interaction.options.getFocused(true); - if (focusedOption.name !== 'username') return; - const players = await searchPlayersAcrossRegions(focusedOption.value); - - try { - await interaction.respond( - players - ? players.map((player) => { - let name = `${player.nickname} (${ - strings.common.regions.short[player.region] - })`; - - if (name.length > DISCORD_CHOICES_MAX_NAME_SIZE) { - const overSize = name.length - DISCORD_CHOICES_MAX_NAME_SIZE; - - name = `${player.nickname.slice( - 0, - player.nickname.length - overSize - OVERFLOW_SUFFIX.length, - )}${OVERFLOW_SUFFIX} (${ - strings.common.regions.short[player.region] - })`; - } - - return { - name, - value: `${player.account_id}`, - } satisfies ApplicationCommandOptionChoiceData; - }) - : [], - ); - } catch (error) {} -} diff --git a/packages/bot/src/core/discord/buttonLink.ts b/packages/bot/src/core/discord/buttonLink.ts deleted file mode 100644 index 65751dd57..000000000 --- a/packages/bot/src/core/discord/buttonLink.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { - ButtonBuilder, - ButtonStyle, - type ComponentEmojiResolvable, -} from "discord.js"; - -export function buttonLink( - url: string, - label: string, - emoji?: ComponentEmojiResolvable -) { - const button = new ButtonBuilder() - .setStyle(ButtonStyle.Link) - .setURL(url) - .setLabel(label); - - if (emoji) button.setEmoji(emoji); - - return button; -} diff --git a/packages/bot/src/core/discord/buttonPrimary.ts b/packages/bot/src/core/discord/buttonPrimary.ts deleted file mode 100644 index 2fa4733a0..000000000 --- a/packages/bot/src/core/discord/buttonPrimary.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { - ButtonBuilder, - ButtonStyle, - type ComponentEmojiResolvable, -} from "discord.js"; - -export function buttonPrimary( - id: string, - label: string, - emoji?: ComponentEmojiResolvable -) { - const button = new ButtonBuilder() - .setCustomId(id) - .setStyle(ButtonStyle.Primary) - .setLabel(label); - - if (emoji) button.setEmoji(emoji); - - return button; -} diff --git a/packages/bot/src/core/discord/buttonRefresh.ts b/packages/bot/src/core/discord/buttonRefresh.ts deleted file mode 100644 index fdf21e961..000000000 --- a/packages/bot/src/core/discord/buttonRefresh.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { ChatInputCommandInteraction } from 'discord.js'; -import { translator } from '../localization/translator'; -import { buttonPrimary } from './buttonPrimary'; - -export function buttonRefresh( - interaction: ChatInputCommandInteraction, - path: string, -) { - const { strings } = translator(interaction.locale); - - if ( - interaction.appPermissions?.has('ReadMessageHistory') && - interaction.appPermissions?.has('ViewChannel') - ) { - return buttonPrimary(path, strings.bot.common.buttons.refresh); - } - - return null; -} diff --git a/packages/bot/src/core/discord/chunkLines.ts b/packages/bot/src/core/discord/chunkLines.ts deleted file mode 100644 index dfcdef6d0..000000000 --- a/packages/bot/src/core/discord/chunkLines.ts +++ /dev/null @@ -1,17 +0,0 @@ -export function chunkLines(lines: string[], joiner = '\n', maxSize = 2000) { - const chunks = ['']; - - lines.map((line) => { - if (chunks.at(-1)!.length + line.length > maxSize) { - chunks.push(''); - } - - if (chunks.at(-1)!.length === 0) { - chunks[chunks.length - 1] += line; - } else { - chunks[chunks.length - 1] += joiner + line; - } - }); - - return chunks; -} diff --git a/packages/bot/src/core/discord/client.ts b/packages/bot/src/core/discord/client.ts deleted file mode 100644 index a83fb3822..000000000 --- a/packages/bot/src/core/discord/client.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Client, GatewayIntentBits } from "discord.js"; -import { interactionCreate } from "../../events/interactionCreate"; -import { ready } from "../../events/ready"; - -export const client = new Client({ - intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers], -}) - .on("clientReady", ready) - .on("interactionCreate", interactionCreate); diff --git a/packages/bot/src/core/discord/colors.ts b/packages/bot/src/core/discord/colors.ts deleted file mode 100644 index bb2c7f51d..000000000 --- a/packages/bot/src/core/discord/colors.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const ACCENT_COLOR = '#ecedee'; -export const POSITIVE_COLOR = '#82ff29'; -export const NEGATIVE_COLOR = '#ff4747'; -export const WARNING_COLOR = '#ffab00'; diff --git a/packages/bot/src/core/discord/commandToURL.ts b/packages/bot/src/core/discord/commandToURL.ts deleted file mode 100644 index 91455adeb..000000000 --- a/packages/bot/src/core/discord/commandToURL.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { type CacheType, ChatInputCommandInteraction } from "discord.js"; - -export interface Parameters { - [key: string]: string | number | boolean | undefined | null; -} - -// TODO: automatically infer command options??? -// but how would that work with the region and id options? - -export function commandToURL( - interaction: ChatInputCommandInteraction, - parameters?: Parameters -) { - const pathname = [ - interaction.commandName, - interaction.options.getSubcommandGroup(false), - interaction.options.getSubcommand(false), - ] - .filter(Boolean) - .join("/"); - const params = (parameters ? Object.entries(parameters) : []) - .filter(([, value]) => value !== undefined && value !== null) - .map(([key, value]) => `${key}=${value}`) - .join("&"); - - return `${pathname}${params.length > 0 ? `?${params}` : ""}`; -} diff --git a/packages/bot/src/core/discord/createLocalizedCommand.ts b/packages/bot/src/core/discord/createLocalizedCommand.ts deleted file mode 100644 index acb00ca49..000000000 --- a/packages/bot/src/core/discord/createLocalizedCommand.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { type BlitzKitStrings } from "@blitzkit/i18n"; -import { - Locale, - SlashCommandBuilder, - SlashCommandSubcommandBuilder, - SlashCommandSubcommandGroupBuilder, -} from "discord.js"; -import { translator } from "../localization/translator"; -import { localizationObject } from "./localizationObject"; - -interface CreateLocalizedCommandSubcommand { - subcommand: string; - modify?: (option: SlashCommandSubcommandBuilder) => void; -} - -interface CreateLocalizedCommandSubcommandGroup { - group: string; - modify?: (option: SlashCommandSubcommandGroupBuilder) => void; -} - -export function createLocalizedCommand( - command: string, - sub?: - | CreateLocalizedCommandSubcommand[] - | CreateLocalizedCommandSubcommandGroup[], -) { - const commandPathItem = command.replaceAll( - "-", - "_", - ) as keyof BlitzKitStrings["bot"]["commands"]; - const { strings } = translator(Locale.EnglishUS); - const slashCommand = new SlashCommandBuilder() - .setName(command) - .setNameLocalizations( - localizationObject( - (strings) => strings.bot.commands[commandPathItem].name, - undefined, - true, - ), - ) - .setDescription(strings.bot.commands[commandPathItem].description) - .setDescriptionLocalizations( - localizationObject( - (strings) => strings.bot.commands[commandPathItem].description, - ), - ); - - if (sub) { - sub.map((subItem) => { - if ("subcommand" in subItem) { - const subcommandNameLocalizations = localizationObject( - (strings) => - ( - strings.bot.commands[commandPathItem] as unknown as { - subcommands: Record; - } - ).subcommands[subItem.subcommand].name, - undefined, - true, - ); - const subcommandDescriptionLocalizations = localizationObject( - (strings) => - ( - strings.bot.commands[commandPathItem] as unknown as { - subcommands: Record; - } - ).subcommands[subItem.subcommand].description, - ); - - slashCommand.addSubcommand((option) => { - if (subItem.modify) subItem.modify(option); - - return option - .setName(subItem.subcommand) - .setNameLocalizations(subcommandNameLocalizations) - .setDescription( - ( - strings.bot.commands[commandPathItem] as unknown as { - subcommands: Record; - } - ).subcommands[subItem.subcommand].description, - ) - .setDescriptionLocalizations(subcommandDescriptionLocalizations); - }); - } else { - const subcommandNameLocalizations = localizationObject( - (strings) => - ( - strings.bot.commands[commandPathItem] as unknown as { - groups: Record; - } - ).groups[subItem.group].name, - undefined, - true, - ); - const subcommandDescriptionLocalizations = localizationObject( - (strings) => - ( - strings.bot.commands[commandPathItem] as unknown as { - groups: Record; - } - ).groups[subItem.group].description, - ); - - slashCommand.addSubcommandGroup((option) => { - if (subItem.modify) subItem.modify(option); - - return option - .setName(subItem.group) - .setNameLocalizations(subcommandNameLocalizations) - .setDescription( - ( - strings.bot.commands[commandPathItem] as unknown as { - groups: Record; - } - ).groups[subItem.group].description, - ) - .setDescriptionLocalizations(subcommandDescriptionLocalizations); - }); - } - }); - } - - return slashCommand; -} diff --git a/packages/bot/src/core/discord/embedInfo.ts b/packages/bot/src/core/discord/embedInfo.ts deleted file mode 100644 index 5359e3212..000000000 --- a/packages/bot/src/core/discord/embedInfo.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { EmbedBuilder } from 'discord.js'; -import { ACCENT_COLOR } from './colors'; - -export function embedInfo(title: string, description: string) { - const embed = new EmbedBuilder() - .setColor(ACCENT_COLOR) - .setDescription(description); - - if (title) embed.setTitle(title); - - return embed; -} diff --git a/packages/bot/src/core/discord/embedNegative.ts b/packages/bot/src/core/discord/embedNegative.ts deleted file mode 100644 index 1d520500a..000000000 --- a/packages/bot/src/core/discord/embedNegative.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { EmbedBuilder } from 'discord.js'; -import { NEGATIVE_COLOR } from './colors'; - -export function embedNegative(title: string, description: string) { - return new EmbedBuilder() - .setColor(NEGATIVE_COLOR) - .setTitle(title) - .setDescription(description); -} diff --git a/packages/bot/src/core/discord/embedPositive.ts b/packages/bot/src/core/discord/embedPositive.ts deleted file mode 100644 index 5f157a851..000000000 --- a/packages/bot/src/core/discord/embedPositive.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { EmbedBuilder } from 'discord.js'; -import { POSITIVE_COLOR } from './colors'; - -export function embedPositive(title: string, description: string) { - return new EmbedBuilder() - .setColor(POSITIVE_COLOR) - .setTitle(title) - .setDescription(description); -} diff --git a/packages/bot/src/core/discord/embedWarning.ts b/packages/bot/src/core/discord/embedWarning.ts deleted file mode 100644 index 0cb912d9e..000000000 --- a/packages/bot/src/core/discord/embedWarning.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { EmbedBuilder } from 'discord.js'; -import { WARNING_COLOR } from './colors'; - -export function embedWarning(title: string, description: string) { - return new EmbedBuilder() - .setColor(WARNING_COLOR) - .setTitle(title) - .setDescription(description); -} diff --git a/packages/bot/src/core/discord/getCustomPeriodParams.ts b/packages/bot/src/core/discord/getCustomPeriodParams.ts deleted file mode 100644 index eaa19e7fa..000000000 --- a/packages/bot/src/core/discord/getCustomPeriodParams.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { ChatInputCommandInteraction } from "discord.js"; -import type { PeriodType } from "./addPeriodSubCommands"; - -export function getCustomPeriodParams( - interaction: ChatInputCommandInteraction, - forcedNonCustomPeriod = false -) { - if ( - !forcedNonCustomPeriod && - (interaction.options.getSubcommand() as PeriodType) === "custom" - ) { - return { - start: interaction.options.getInteger("start", true), - end: interaction.options.getInteger("end", true), - }; - } - - return undefined; -} diff --git a/packages/bot/src/core/discord/getFiltersFromButton.ts b/packages/bot/src/core/discord/getFiltersFromButton.ts deleted file mode 100644 index 37689b7f0..000000000 --- a/packages/bot/src/core/discord/getFiltersFromButton.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { TankClass, TankType } from "@blitzkit/core"; -import { ButtonInteraction } from "discord.js"; -import type { StatFilters } from "../blitzstars/filterStats"; - -export function getFiltersFromButton(interaction: ButtonInteraction) { - const url = new URL(`https://example.com/${interaction.customId}`); - - return { - nation: url.searchParams.get("nation") ?? undefined, - tank: parseInt(url.searchParams.get("tank") ?? "0") || undefined, - class: - (Number(url.searchParams.get("class")) as TankClass | undefined) ?? - undefined, - type: - (Number(url.searchParams.get("type")) as TankType | undefined) ?? - undefined, - tier: parseInt(url.searchParams.get("tier") ?? "0") || undefined, - } satisfies StatFilters; -} diff --git a/packages/bot/src/core/discord/getFiltersFromCommand.ts b/packages/bot/src/core/discord/getFiltersFromCommand.ts deleted file mode 100644 index d4cac40cf..000000000 --- a/packages/bot/src/core/discord/getFiltersFromCommand.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { ChatInputCommandInteraction } from 'discord.js'; -import { resolveTankId } from '../blitz/resolveTankId'; -import type { StatFilters } from '../blitzstars/filterStats'; - -export async function getFiltersFromCommand( - interaction: ChatInputCommandInteraction, -) { - const nationRaw = interaction.options.getString('nation'); - const tierRaw = interaction.options.getString('tier'); - const classRaw = interaction.options.getString('class'); - const typeRaw = interaction.options.getString('type'); - const tankRaw = interaction.options.getString('tank'); - - return { - nation: nationRaw ?? undefined, - tier: tierRaw === null ? undefined : Number(tierRaw), - class: classRaw === null ? undefined : Number(classRaw), - type: typeRaw === null ? undefined : Number(typeRaw), - tank: - tankRaw === null - ? undefined - : await resolveTankId(tankRaw, interaction.locale), - } satisfies StatFilters; -} diff --git a/packages/bot/src/core/discord/getPeriodOptionName.ts b/packages/bot/src/core/discord/getPeriodOptionName.ts deleted file mode 100644 index 1eaea0772..000000000 --- a/packages/bot/src/core/discord/getPeriodOptionName.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { literals } from "@blitzkit/i18n"; -import { Locale } from "discord.js"; -import { translator } from "../localization/translator"; -import type { PeriodSize } from "./addPeriodSubCommands"; - -export function getPeriodOptionName(period: PeriodSize, locale: Locale) { - const { strings } = translator(locale); - if (period === "career") return strings.bot.common.periods.career; - if (period === "today") return strings.bot.common.periods.today; - return literals(strings.bot.common.periods.days, { days: period }); -} diff --git a/packages/bot/src/core/discord/localizationObject.ts b/packages/bot/src/core/discord/localizationObject.ts deleted file mode 100644 index 637a6d604..000000000 --- a/packages/bot/src/core/discord/localizationObject.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { type BlitzKitStrings, literals as literalsFunc } from "@blitzkit/i18n"; -import { Locale } from "discord.js"; -import { SUPPORTED_LOCALES_DISCORD } from "../localization/strings/constants"; -import { translator } from "../localization/translator"; - -const validNameRegex = - /^[\p{Ll}\p{Lm}\p{Lo}\p{N}\p{sc=Devanagari}\p{sc=Thai}_-]+$/u; - -export function localizationObject( - path: (strings: BlitzKitStrings) => string, - literals?: Record, - strict = false -) { - return SUPPORTED_LOCALES_DISCORD.reduce>>( - (localizations, locale) => { - const { strings } = translator(locale); - const translation = literalsFunc(path(strings), literals); - const isValid = - (!strict || validNameRegex.test(translation)) && translation.length > 0; - - if (!isValid) { - // console.warn( - // `Invalid localization for ${locale} (${path}): ${translation}; skipping...` - // ); - return localizations; - } - - return { - ...localizations, - [locale]: translation, - }; - }, - {} - ); -} diff --git a/packages/bot/src/core/discord/markdownTable.ts b/packages/bot/src/core/discord/markdownTable.ts deleted file mode 100644 index 6ad0b6f64..000000000 --- a/packages/bot/src/core/discord/markdownTable.ts +++ /dev/null @@ -1,9 +0,0 @@ -export type TableInputEntry = [string, string | number] | []; -export type TableInput = TableInputEntry[]; - -export function markdownTable(input: TableInput) { - return input - .filter((array) => array[1] !== undefined) - .map((array) => (array.length === 0 ? '' : `**${array[0]}**: ${array[1]}`)) - .join('\n'); -} diff --git a/packages/bot/src/core/discord/normalizeInteractionReturnable.tsx b/packages/bot/src/core/discord/normalizeInteractionReturnable.tsx deleted file mode 100644 index 7e67b9767..000000000 --- a/packages/bot/src/core/discord/normalizeInteractionReturnable.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { - ActionRowBuilder, - AttachmentBuilder, - ButtonBuilder, - EmbedBuilder, - type InteractionEditReplyOptions, - type InteractionReplyOptions, - type MessageEditOptions, -} from "discord.js"; -import type { InteractionReturnable } from "../../events/interactionCreate"; -import type { Writeable } from "../../types/writable"; -import { jsxToPngThreaded } from "../blitzkit/jsxToPngThreaded"; -import { RenderConfiguration } from "../blitzkit/renderConfiguration"; - -export async function normalizeInteractionReturnable( - returnable: InteractionReturnable -) { - const images: [number, Buffer][] = []; - const reply: InteractionEditReplyOptions & - InteractionReplyOptions & - MessageEditOptions = {}; - const awaitedReturnable = await returnable; - const normalizedReturnable = Array.isArray(awaitedReturnable) - ? awaitedReturnable - : [awaitedReturnable]; - let renderConfiguration = new RenderConfiguration(); - - await Promise.all( - normalizedReturnable.map(async (item, index) => { - if (typeof item === "string") { - reply.content = item; - } else if (item instanceof EmbedBuilder) { - if (!reply.embeds) reply.embeds = []; - (reply.embeds as Writeable).push(item); - } else if (item instanceof ButtonBuilder) { - if (!reply.components) - reply.components = [new ActionRowBuilder()]; - (reply.components[0] as ActionRowBuilder).addComponents( - item - ); - } else if (item instanceof AttachmentBuilder) { - if (!reply.files) reply.files = []; - (reply.files as Writeable).push(item); - } else if (item instanceof RenderConfiguration) { - renderConfiguration = item; - } else if (item === null) { - return; - } else { - const image = await jsxToPngThreaded(item, renderConfiguration); - images.push([index, image]); - } - }) - ); - - if (images.length > 0) { - images.sort(([a], [b]) => b - a); - if (!reply.files) reply.files = []; - images.forEach(([, image]) => - (reply.files as Writeable)?.push(image) - ); - } - - return reply; -} diff --git a/packages/bot/src/core/discord/psa.ts b/packages/bot/src/core/discord/psa.ts deleted file mode 100644 index 42dfe93eb..000000000 --- a/packages/bot/src/core/discord/psa.ts +++ /dev/null @@ -1,36 +0,0 @@ -export type PSAData = { - secret?: boolean; - links?: { - title: string; - url: string; - }[]; - commands?: string[]; -} & ( - | { - type?: 'embed'; - title: string; - description: string; - image?: string; - } - | { - type: 'image'; - image: string; - title?: string; - } -); - -export interface PSA { - data: PSAData | null; -} - -export const psa: PSA = { - data: null, -}; - -fetch( - 'https://gist.githubusercontent.com/tresabhi/ed4b136f08856e615212e573aabe1968/raw', -) - .then((response) => response.json()) - .then((data) => { - psa.data = data as PSAData; - }); diff --git a/packages/bot/src/core/discord/resolveClanFromCommand.ts b/packages/bot/src/core/discord/resolveClanFromCommand.ts deleted file mode 100644 index 2a78793b6..000000000 --- a/packages/bot/src/core/discord/resolveClanFromCommand.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { Region } from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import { ChatInputCommandInteraction } from "discord.js"; -import markdownEscape from "markdown-escape"; -import { searchClansAcrossRegionsBotWrapper } from "../blitz/searchClansAcrossRegionsBotWrapper"; -import { UserError } from "../blitzkit/userError"; -import { translator } from "../localization/translator"; - -export const serverAndIdPattern = /(com|eu|asia)\/[0-9]+/; - -export async function resolveClanFromCommand( - interaction: ChatInputCommandInteraction -) { - const { strings } = translator(interaction.locale); - const clan = interaction.options.getString("clan", true); - - if (serverAndIdPattern.test(clan)) { - const [server, accountId] = clan.split("/"); - return { region: server as Region, id: Number(accountId) }; - } else { - const accounts = await searchClansAcrossRegionsBotWrapper( - clan, - interaction.locale - ); - - if (accounts[0]) { - return { region: accounts[0].region, id: accounts[0].clan_id }; - } else { - throw new UserError( - literals(strings.bot.common.errors.clan_not_found, { - search: markdownEscape(clan), - }) - ); - } - } -} diff --git a/packages/bot/src/core/discord/resolvePeriodFromButton.ts b/packages/bot/src/core/discord/resolvePeriodFromButton.ts deleted file mode 100644 index cb9388bdf..000000000 --- a/packages/bot/src/core/discord/resolvePeriodFromButton.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { getTimeDaysAgo, type Region } from "@blitzkit/core"; -import { ButtonInteraction } from "discord.js"; -import { getPeriodNow } from "../blitzkit/getPeriodNow"; -import { getPeriodStart } from "../blitzkit/getPeriodStart"; -import type { PeriodType } from "./addPeriodSubCommands"; -import { getPeriodOptionName } from "./getPeriodOptionName"; -import type { ResolvedPeriod } from "./resolvePeriodFromCommand"; - -export function resolvePeriodFromButton( - region: Region, - interaction: ButtonInteraction -) { - let name: string; - let start: number; - let end: number; - const url = new URL(`https://example.com/${interaction.customId}`); - const path = url.pathname.split("/"); - const periodSubcommand = path.at(-1) as PeriodType; - - if (periodSubcommand === "custom") { - const startRaw = parseInt(url!.searchParams.get("start")!); - const endRaw = parseInt(url!.searchParams.get("end")!); - const startDaysAgoMin = Math.min(startRaw, endRaw); - const endDaysAgoMax = Math.max(startRaw, endRaw); - - name = `${startDaysAgoMin} - ${endDaysAgoMax} days`; - start = getTimeDaysAgo(region, endDaysAgoMax); - end = getTimeDaysAgo(region, startDaysAgoMin); - } else { - name = getPeriodOptionName(periodSubcommand, interaction.locale); - start = getPeriodStart(region, periodSubcommand); - end = getPeriodNow(); - } - - return { - name, - start, - end, - } satisfies ResolvedPeriod; -} diff --git a/packages/bot/src/core/discord/resolvePeriodFromCommand.ts b/packages/bot/src/core/discord/resolvePeriodFromCommand.ts deleted file mode 100644 index 55ad2af4e..000000000 --- a/packages/bot/src/core/discord/resolvePeriodFromCommand.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { getTimeDaysAgo, type Region } from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import { type CacheType, ChatInputCommandInteraction } from "discord.js"; -import { getPeriodNow } from "../blitzkit/getPeriodNow"; -import { getPeriodStart } from "../blitzkit/getPeriodStart"; -import { translator } from "../localization/translator"; -import type { PeriodType } from "./addPeriodSubCommands"; -import { getPeriodOptionName } from "./getPeriodOptionName"; - -export interface ResolvedPeriod { - name: string; - start: number; - end: number; -} - -export function resolvePeriodFromCommand( - region: Region, - interaction: ChatInputCommandInteraction, - forcedPeriod?: PeriodType -) { - const { strings } = translator(interaction.locale); - let name: string; - let start: number; - let end: number; - const periodSubcommand = forcedPeriod - ? forcedPeriod - : (interaction.options.getSubcommand() as PeriodType); - - if (periodSubcommand === "custom") { - const startOption = interaction.options.getInteger("start", true); - const endOption = interaction.options.getInteger("end", true); - const startDaysAgoMin = Math.min(startOption, endOption); - const endDaysAgoMax = Math.max(startOption, endOption); - - name = literals(strings.bot.common.periods.custom, { - day_1: startDaysAgoMin, - day_2: endDaysAgoMax, - }); - start = getTimeDaysAgo(region, endDaysAgoMax); - end = getTimeDaysAgo(region, startDaysAgoMin); - } else { - name = getPeriodOptionName(periodSubcommand, interaction.locale); - start = getPeriodStart(region, periodSubcommand); - end = getPeriodNow(); - } - - return { - name, - start, - end, - } satisfies ResolvedPeriod; -} diff --git a/packages/bot/src/core/discord/resolvePlayerFromButton.ts b/packages/bot/src/core/discord/resolvePlayerFromButton.ts deleted file mode 100644 index 06b3e2895..000000000 --- a/packages/bot/src/core/discord/resolvePlayerFromButton.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { Region } from "@blitzkit/core"; -import { ButtonInteraction } from "discord.js"; -import type { ResolvedPlayer } from "./resolvePlayerFromCommand"; - -export async function resolvePlayerFromButton(interaction: ButtonInteraction) { - const url = new URL(`https://exmaple.com/${interaction.customId}`); - - return { - id: parseInt(url.searchParams.get("id")!), - region: url.searchParams.get("region") as Region, - } satisfies ResolvedPlayer; -} diff --git a/packages/bot/src/core/discord/resolvePlayerFromCommand/index.ts b/packages/bot/src/core/discord/resolvePlayerFromCommand/index.ts deleted file mode 100644 index c76e6c70e..000000000 --- a/packages/bot/src/core/discord/resolvePlayerFromCommand/index.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { idToRegion, type Region } from "@blitzkit/core"; -import { literals } from "@blitzkit/i18n"; -import { type CacheType, ChatInputCommandInteraction } from "discord.js"; -import markdownEscape from "markdown-escape"; -import { searchPlayersAcrossRegionsBotWrapper } from "../../blitz/searchPlayersAcrossRegionsBotWrapper"; -import { getBlitzFromDiscord } from "../../blitzkit/getBlitzFromDiscord"; -import { UserError } from "../../blitzkit/userError"; -import { translator } from "../../localization/translator"; - -export interface ResolvedPlayer { - region: Region; - id: number; -} - -export async function resolvePlayerFromCommand( - interaction: ChatInputCommandInteraction -) { - const { strings } = translator(interaction.locale); - const commandUsername = interaction.options.getString("username"); - - if (commandUsername !== null) { - const id = parseInt(commandUsername); - - if (!isNaN(id)) { - const region = idToRegion(id); - return { region, id } satisfies ResolvedPlayer; - } else { - const accounts = await searchPlayersAcrossRegionsBotWrapper( - commandUsername, - interaction.locale - ); - - if (accounts[0]) { - return { - region: accounts[0].region, - id: accounts[0].account_id, - } satisfies ResolvedPlayer; - } else { - throw new UserError( - literals(strings.bot.common.errors.player_not_found, { - search: markdownEscape(commandUsername), - }) - ); - } - } - } else { - const account = await getBlitzFromDiscord(BigInt(interaction.user.id)); - - if (account) return account; - - throw new UserError(strings.bot.common.errors.player_not_linked); - } -} diff --git a/packages/bot/src/core/localization/strings/constants.ts b/packages/bot/src/core/localization/strings/constants.ts deleted file mode 100644 index 4cb69a89c..000000000 --- a/packages/bot/src/core/localization/strings/constants.ts +++ /dev/null @@ -1,25 +0,0 @@ -import locales from "@blitzkit/i18n/locales.json"; -import { Locale } from "discord.js"; - -const SUPPORTED_LOCALES_DISCORD_MAP: Record = { - en: Locale.EnglishUS, - es: Locale.SpanishES, - pt: Locale.PortugueseBR, - ru: Locale.Russian, - uk: Locale.Ukrainian, - zh: Locale.ChineseCN, - fr: Locale.French, - ja: Locale.Japanese, - pl: Locale.Polish, -}; - -export const SUPPORTED_LOCALES_DISCORD_MAP_INVERSE = Object.fromEntries( - Object.entries(SUPPORTED_LOCALES_DISCORD_MAP).map(([k, v]) => [v, k]) -) as Record; - -export const SUPPORTED_LOCALES_DISCORD = Object.values( - SUPPORTED_LOCALES_DISCORD_MAP -); - -export const DEFAULT_LOCALE_DISCORD = - SUPPORTED_LOCALES_DISCORD_MAP[locales.default]; diff --git a/packages/bot/src/core/localization/translator.ts b/packages/bot/src/core/localization/translator.ts deleted file mode 100644 index 37168ac30..000000000 --- a/packages/bot/src/core/localization/translator.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { STRINGS, unwrapper } from '@blitzkit/i18n'; -import { Locale } from 'discord.js'; -import { - DEFAULT_LOCALE_DISCORD, - SUPPORTED_LOCALES_DISCORD, - SUPPORTED_LOCALES_DISCORD_MAP_INVERSE, -} from './strings/constants'; - -export function translator(localeRaw: Locale) { - const locale = - localeRaw !== DEFAULT_LOCALE_DISCORD && - SUPPORTED_LOCALES_DISCORD.includes(localeRaw) - ? localeRaw - : DEFAULT_LOCALE_DISCORD; - const bkLocale = SUPPORTED_LOCALES_DISCORD_MAP_INVERSE[locale]; - const strings = STRINGS[bkLocale]; - const unwrap = unwrapper(bkLocale); - - return { locale, strings, unwrap, bkLocale }; -} diff --git a/packages/bot/src/core/radix/radixColors.ts b/packages/bot/src/core/radix/radixColors.ts deleted file mode 100644 index dfe17a359..000000000 --- a/packages/bot/src/core/radix/radixColors.ts +++ /dev/null @@ -1,55 +0,0 @@ -import * as colors from "@radix-ui/colors"; -import { Theme } from "@radix-ui/themes"; -import type { ComponentProps } from "react"; - -export type AccentColor = Exclude< - ComponentProps["accentColor"], - "gray" | undefined ->; -export type AccentColorDark = `${AccentColor}Dark`; -export type GrayColor = Exclude< - ComponentProps["grayColor"], - "auto" | undefined ->; -export type GrayColorDark = `${GrayColor}Dark`; - -export const ACCENT_COLORS: AccentColor[] = [ - "tomato", - "red", - "ruby", - "crimson", - "pink", - "plum", - "purple", - "violet", - "iris", - "indigo", - "blue", - "cyan", - "teal", - "jade", - "green", - "grass", - "brown", - "orange", - "sky", - "mint", - "lime", - "yellow", - "amber", - "gold", - "bronze", -]; -export const GRAY_COLORS: GrayColor[] = [ - "gray", - "mauve", - "slate", - "sage", - "olive", - "sand", -]; - -export const PALETTES: Record< - AccentColor | AccentColorDark | GrayColor | GrayColorDark, - { [key: `${string}${number}`]: string } -> = colors; diff --git a/packages/bot/src/core/streams/wotbreplay.ts b/packages/bot/src/core/streams/wotbreplay.ts deleted file mode 100644 index 47d18d9d3..000000000 --- a/packages/bot/src/core/streams/wotbreplay.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { ReadStream } from "@blitzkit/core"; -import AdmZip from "adm-zip"; -import { readFile } from "fs/promises"; -import { Parser } from "pickleparser"; -import protobuf from "protobufjs"; - -interface WotbReplayMetaJson { - version: string; - title: string; - dbid: string; - playerName: string; - battleStartTime: string; - playerVehicleName: string; - mapName: string; - arenaUniqueId: string; - battleDuration: number; - vehicleCompDescriptor: number; - camouflageId: number; - mapId: number; - arenaBonusType: number; -} - -export class WotbReplayReadStream extends ReadStream { - async wotbReplay() { - const pickleParser = new Parser(); - const zip = new AdmZip(Buffer.from(this.buffer)); - const metaJsonBuffer = zip.getEntry("meta.json")?.getData(); - const battleResultsDat = zip.getEntry("battle_results.dat")?.getData(); - - if (!metaJsonBuffer || !battleResultsDat) - throw new SyntaxError("meta.json not found"); - - const metaJson = JSON.parse( - metaJsonBuffer.toString() - ) as WotbReplayMetaJson; - const unpickledBattleResults = pickleParser.parse(battleResultsDat); - const [, battleResultsString] = unpickledBattleResults as [number, string]; - const root = await protobuf.load( - `${__dirname}/../../protos/BattleResultsClient.proto` - ); - const BattleResults = root.lookupType( - "BattleResultsGenerated.BattleResultsClient" - ); - - const reader = protobuf.Reader.create(Buffer.from(battleResultsString)); - - // const decodedBattleResults = BattleResults.decodeDelimited( - // Buffer.from(battleResultsString), - // ); - // console.log(decodedBattleResults.toJSON()); - } -} - -const replay = await readFile("temp/test3.wotbreplay"); -const stream = new WotbReplayReadStream(replay.buffer as ArrayBuffer); - -stream.wotbReplay(); diff --git a/packages/bot/src/events/interactionCreate/handlers/autocomplete.ts b/packages/bot/src/events/interactionCreate/handlers/autocomplete.ts deleted file mode 100644 index 59ec8f072..000000000 --- a/packages/bot/src/events/interactionCreate/handlers/autocomplete.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { AutocompleteInteraction, CacheType } from "discord.js"; -import { commands } from ".."; - -export async function handleAutocomplete( - interaction: AutocompleteInteraction, -) { - const command = commands[interaction.commandName]; - - if (!command) { - console.error(`No command matching ${interaction.commandName} was found.`); - return; - } - - if (command.autocomplete) command.autocomplete(interaction); -} diff --git a/packages/bot/src/events/interactionCreate/handlers/button.tsx b/packages/bot/src/events/interactionCreate/handlers/button.tsx deleted file mode 100644 index 8ac4b7269..000000000 --- a/packages/bot/src/events/interactionCreate/handlers/button.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { ButtonInteraction, CacheType } from "discord.js"; -import { commands } from ".."; -import { normalizeInteractionReturnable } from "../../../core/discord/normalizeInteractionReturnable"; - -export async function handleButton(interaction: ButtonInteraction) { - await interaction.deferUpdate(); - - const url = new URL(`https://example.com/${interaction.customId}`); - const commandName = url.pathname.split("/")[1]; - const button = commands[commandName]?.button; - - if (!button) { - throw new Error(`Button handler not found for "${commandName}"`); - } - - if (interaction.message.interaction?.user.id !== interaction.user.id) { - interaction.followUp({ - ephemeral: true, - content: "You are not the author of this command.", - }); - return; - } - - await interaction.message.edit( - await normalizeInteractionReturnable(button(interaction)), - ); -} diff --git a/packages/bot/src/events/interactionCreate/handlers/chatInputCommand.ts b/packages/bot/src/events/interactionCreate/handlers/chatInputCommand.ts deleted file mode 100644 index d385a036a..000000000 --- a/packages/bot/src/events/interactionCreate/handlers/chatInputCommand.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { - ActionRowBuilder, - AttachmentBuilder, - ButtonBuilder, - ButtonStyle, - type CacheType, - ChatInputCommandInteraction, - type InteractionReplyOptions, -} from "discord.js"; -import { type InteractionRawReturnable, commands } from ".."; -import { UserError } from "../../../core/blitzkit/userError"; -import { buttonLink } from "../../../core/discord/buttonLink"; -import { embedWarning } from "../../../core/discord/embedWarning"; -import { normalizeInteractionReturnable } from "../../../core/discord/normalizeInteractionReturnable"; -import { psa } from "../../../core/discord/psa"; -import { translator } from "../../../core/localization/translator"; -import type { Writeable } from "../../../types/writable"; - -export async function handleChatInputCommand( - interaction: ChatInputCommandInteraction, -) { - await interaction.deferReply(); - - const registry = commands[interaction.commandName]; - - try { - const returnable = await registry.handler(interaction); - - if (registry.handlesInteraction) return; - - const chunks: InteractionRawReturnable[][] = [[]]; - - (Array.isArray(returnable) ? returnable : [returnable]).forEach( - (object) => { - if (typeof object === "string") { - chunks.push([object]); - } else chunks.at(-1)?.push(object!); - }, - ); - - let index = 0; - for (const chunk of chunks.filter((chunk) => chunk.length > 0)) { - const reply = await normalizeInteractionReturnable(chunk); - - if ( - psa.data && - !psa.data.secret && - (psa.data.commands === undefined || - psa.data.commands.includes(interaction.commandName)) - ) { - if (psa.data.type === undefined || psa.data.type === "embed") { - if (!reply.embeds) reply.embeds = []; - - const embed = embedWarning(psa.data.title, psa.data.description); - - if (psa.data.image) embed.setImage(psa.data.image); - (reply.embeds as Writeable).push(embed); - } else if (psa.data.type === "image") { - if (!reply.files) reply.files = []; - (reply.files as Writeable).push( - new AttachmentBuilder(psa.data.image), - ); - reply.content = reply.content ?? psa.data.title; - } - - if (psa.data.links) { - if (!reply.components?.[0]) { - reply.components = [new ActionRowBuilder()]; - } - - ( - reply.components[0] as ActionRowBuilder - ).addComponents( - psa.data.links.map(({ title, url }) => buttonLink(url, title)), - ); - } - } - - if (index === 0) { - await interaction.editReply(reply); - } else { - await interaction.followUp(reply); - } - - if ( - psa.data && - psa.data.secret && - (psa.data.commands === undefined || - psa.data.commands.includes(interaction.commandName)) - ) { - const followUp: InteractionReplyOptions = { - ephemeral: true, - }; - - if (psa.data.type === undefined || psa.data.type === "embed") { - if (!followUp.embeds) followUp.embeds = []; - - const embed = embedWarning(psa.data.title, psa.data.description); - - if (psa.data.image) embed.setImage(psa.data.image); - (followUp.embeds as Writeable).push(embed); - } else if (psa.data.type === "image") { - if (!followUp.files) followUp.files = []; - (followUp.files as Writeable).push( - new AttachmentBuilder(psa.data.image), - ); - followUp.content = followUp.content ?? psa.data.title; - } - - if (psa.data.links) { - if (!followUp.components?.[0]) { - followUp.components = [new ActionRowBuilder()]; - } - - ( - followUp.components[0] as ActionRowBuilder - ).addComponents( - psa.data.links.map(({ title, url }) => buttonLink(url, title)), - ); - } - - interaction.followUp(followUp); - } - - index++; - } - } catch (error) { - const { strings } = translator(interaction.locale); - - const components = [ - new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setLabel(strings.bot.common.errors.get_help) - .setURL("https://discord.gg/nDt7AjGJQH") - .setStyle(ButtonStyle.Link), - ), - ]; - if (error instanceof UserError) { - interaction.editReply({ - content: error.message, - components, - }); - } else { - console.error(interaction.commandName, error); - interaction.editReply({ - content: strings.bot.common.errors.uncaught_error, - components, - }); - } - } -} diff --git a/packages/bot/src/events/interactionCreate/index.ts b/packages/bot/src/events/interactionCreate/index.ts deleted file mode 100644 index 9eef8b024..000000000 --- a/packages/bot/src/events/interactionCreate/index.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { assertSecret } from "@blitzkit/core"; -import { - AttachmentBuilder, - AutocompleteInteraction, - ButtonBuilder, - ButtonInteraction, - type CacheType, - ChatInputCommandInteraction, - EmbedBuilder, - type Interaction, - REST, - Routes, - SlashCommandBuilder, - type SlashCommandOptionsOnlyBuilder, - type SlashCommandSubcommandsOnlyBuilder, -} from "discord.js"; -import { aboutCommand } from "../../commands/about"; -import { breakdownCommand } from "../../commands/breakdown"; -import { debugCommand } from "../../commands/debug"; -import { evolutionCommand } from "../../commands/evolution"; -import { fullStatsCommand } from "../../commands/fullStats"; -import { inactiveCommand } from "../../commands/inactive"; -import { verifyCommand } from "../../commands/link"; -import { ownedTanksCommand } from "../../commands/ownedTanks"; -import { permissionsCommand } from "../../commands/permissions"; -import { pingCommand } from "../../commands/ping"; -import { playerInfoCommand } from "../../commands/playerInfo"; -import { ratingCommand } from "../../commands/rating"; -import { ratingLeaderboardCommand } from "../../commands/ratingLeaderboard"; -import { replayCommand } from "../../commands/replay"; -import { researchCommand } from "../../commands/research"; -import { searchClansCommand } from "../../commands/searchClans"; -import { searchPlayersCommand } from "../../commands/searchPlayers"; -import { statsCommand } from "../../commands/stats"; -import { todayCommand } from "../../commands/today"; -import { RenderConfiguration } from "../../core/blitzkit/renderConfiguration"; -import { handleAutocomplete } from "./handlers/autocomplete"; -import { handleButton } from "./handlers/button"; -import { handleChatInputCommand } from "./handlers/chatInputCommand"; - -export type InteractionRawReturnable = - | string - | EmbedBuilder - | ButtonBuilder - | AttachmentBuilder - | RenderConfiguration - | React.JSX.Element - | null; -export type InteractionIterableReturnable = - | InteractionRawReturnable - | InteractionRawReturnable[]; -export type InteractionReturnable = - | InteractionIterableReturnable - | Promise; - -interface CommandRegistryBase { - command: - | SlashCommandBuilder - | SlashCommandSubcommandsOnlyBuilder - | SlashCommandOptionsOnlyBuilder - | Omit; - - autocomplete?: (interaction: AutocompleteInteraction) => void; - button?: (interaction: ButtonInteraction) => InteractionReturnable; -} - -interface CommandRegistryImplicit extends CommandRegistryBase { - handlesInteraction: true; - handler: (interaction: ChatInputCommandInteraction) => void; -} - -interface CommandRegistryExplicit extends CommandRegistryBase { - handlesInteraction?: false; - handler: ( - interaction: ChatInputCommandInteraction, - ) => InteractionReturnable; -} - -export type CommandRegistry = CommandRegistryImplicit | CommandRegistryExplicit; - -export const commandsArray: Promise[] = [ - // auctionCommand, - permissionsCommand, - debugCommand, - aboutCommand, - inactiveCommand, - ownedTanksCommand, - playerInfoCommand, - searchClansCommand, - searchPlayersCommand, - fullStatsCommand, - breakdownCommand, - verifyCommand, - pingCommand, - evolutionCommand, - statsCommand, - ratingLeaderboardCommand, - todayCommand, - researchCommand, - ratingCommand, - replayCommand, -]; - -export const commands: Record = {}; -const commandsFulfilled: CommandRegistry[] = []; - -let index = 0; -for (const registry of await Promise.allSettled(commandsArray)) { - if (registry.status === "rejected") { - console.warn(`Command ${index} rejected`, registry.reason); - continue; - } - - commandsFulfilled.push(registry.value); - commands[registry.value.command.name] = registry.value; - index++; -} - -const rest = new REST().setToken(assertSecret(import.meta.env.DISCORD_TOKEN)); -const body = commandsFulfilled.map((registry) => registry.command.toJSON()); -const applicationCommandsRoute = Routes.applicationCommands( - assertSecret(import.meta.env.DISCORD_CLIENT_ID), -); - -await rest.put(applicationCommandsRoute, { body }); - -export async function interactionCreate(interaction: Interaction) { - if (interaction.isAutocomplete()) { - handleAutocomplete(interaction); - } else if (interaction.isChatInputCommand()) { - handleChatInputCommand(interaction); - } else if (interaction.isButton()) { - handleButton(interaction); - } -} diff --git a/packages/bot/src/events/ready.ts b/packages/bot/src/events/ready.ts deleted file mode 100644 index 2612b1908..000000000 --- a/packages/bot/src/events/ready.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Client } from 'discord.js'; - -export function ready(client: Client) { - console.log(`🟢 Launched bot ${client.shard?.ids[0] ?? 'default'}`); -} diff --git a/packages/bot/src/main.ts b/packages/bot/src/main.ts deleted file mode 100644 index 8bac09c3a..000000000 --- a/packages/bot/src/main.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { assertSecret } from "@blitzkit/core"; -import { ActivityType, ShardingManager } from "discord.js"; -import { - discoveredIdsDefinitions, - tankDefinitions, -} from "./core/blitzkit/nonBlockingPromises"; - -let manager: ShardingManager; -let iteration = 0; -const interval = setInterval(async () => { - console.log(`Attempting launch on iteration ${++iteration}`); - - manager?.respawnAll(); - manager = new ShardingManager("dist/bot/workers/bot.js", { - token: assertSecret(import.meta.env.DISCORD_TOKEN), - }); - - const shards = await manager - .on("shardCreate", (shard) => { - clearInterval(interval); - console.log(`🟡 Launching shard ${shard.id}`); - shard.on("ready", () => console.log(`🟢 Launched shard ${shard.id}`)); - }) - .spawn({ timeout: 60000 }); - - try { - let servers = 0; - let channels = 0; - let users = 0; - - for (const [, shard] of shards) { - const guilds = (await shard.fetchClientValue("guilds.cache")) as any; - - servers += guilds.length; - - for (const guild of guilds) { - channels += guild.channels.length; - users += guild.memberCount; - } - } - - const awaitedTankDefinitions = await tankDefinitions; - const tanks = Object.values(awaitedTankDefinitions.tanks).length; - const awaitedDiscoveredIdsDefinitions = await discoveredIdsDefinitions; - const usernames = awaitedDiscoveredIdsDefinitions.count; - - manager.broadcastEval( - async (client, { servers, users, channels, tanks, usernames }) => { - const bios = [ - `Living in ${servers.toLocaleString()} servers`, - `Serving ${users.toLocaleString()} users`, - `Observing ${channels.toLocaleString()} channels`, - `Tinkering with ${tanks.toLocaleString()} tanks`, - `Memorizing ${usernames.toLocaleString()} usernames`, - ]; - - client.user?.setPresence({ - activities: [ - { - type: ActivityType.Custom, - name: bios[Math.floor(Math.random() * bios.length)], - }, - ], - }); - - for (const guild of client.guilds.cache.values()) { - try { - const botMember = await guild.members.fetch(client.user.id); - await botMember.setNickname("BlitzKit"); - } catch {} - } - }, - { context: { servers, channels, users, tanks, usernames } }, - ); - } catch (error) { - console.warn( - "An error occurred when setting up presence, ignoring to keep the process alive", - error, - ); - } -}, 15000); diff --git a/packages/bot/src/stitches.config.ts b/packages/bot/src/stitches.config.ts deleted file mode 100644 index f50f1a5fa..000000000 --- a/packages/bot/src/stitches.config.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { - amberDark, - amberDarkA, - blueDark, - brownDark, - crimsonDark, - greenDark, - mauveDark, - orangeDark, - purpleDark, - redDark, - rubyDark, - sandDark, - slate, - slateDark, - slateDarkA, - tealDark, -} from '@radix-ui/colors'; -import { createBorderStyles, createColors, createSpaces } from 'bepaint'; - -// this isn't really a stitches config, but the object is compliant -export const theme = { - spaces: createSpaces(), - - colors: { - ...createColors(mauveDark), - ...createColors(slateDarkA, 'alpha'), - ...createColors(slate, 'light'), - - ...createColors(amberDark, 'amber'), - ...createColors(amberDarkA, 'amberAlpha'), - ...createColors(orangeDark, 'orange'), - ...createColors(purpleDark, 'purple'), - ...createColors(blueDark, 'blue'), - ...createColors(tealDark, 'teal'), - ...createColors(greenDark, 'green'), - ...createColors(redDark, 'red'), - ...createColors(brownDark, 'brown'), - ...createColors(mauveDark, 'mauve'), - ...createColors(sandDark, 'sand'), - ...createColors(crimsonDark, 'crimson'), - ...createColors(rubyDark, 'ruby'), - }, - - borderStyles: { - subtle: `1px solid ${slateDark.slate2}`, - ...createBorderStyles(slateDark), - ...createBorderStyles(blueDark, undefined, 'blue'), - }, - - durations: { - regular: '0.2s', - }, -}; diff --git a/packages/bot/src/types.d.ts b/packages/bot/src/types.d.ts deleted file mode 100644 index 29102695c..000000000 --- a/packages/bot/src/types.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -declare module '*.ttf' { - const path: string; - export default path; -} diff --git a/packages/bot/src/types/histories.ts b/packages/bot/src/types/histories.ts deleted file mode 100644 index 297552339..000000000 --- a/packages/bot/src/types/histories.ts +++ /dev/null @@ -1,24 +0,0 @@ -// a cleaner simplification of Blitz Stars' histories because it's a mess there - -import { type BlitzStats } from '@blitzkit/core'; - -export interface History { - all: BlitzStats; - last_battle_time: number; -} - -export type Histories = History[]; - -export interface GetHistoriesOptions { - start: number; - end: number; - includeLatestHistories: boolean; - includePreviousHistories: boolean; -} - -export const getHistoriesDefaultOptions: GetHistoriesOptions = { - start: -Infinity, - end: Infinity, - includeLatestHistories: false, - includePreviousHistories: false, -}; diff --git a/packages/bot/src/types/possiblyPromise.ts b/packages/bot/src/types/possiblyPromise.ts deleted file mode 100644 index a4658c873..000000000 --- a/packages/bot/src/types/possiblyPromise.ts +++ /dev/null @@ -1 +0,0 @@ -export type PossiblyPromise = Promise | Type; diff --git a/packages/bot/src/types/ttf.d.ts b/packages/bot/src/types/ttf.d.ts deleted file mode 100644 index 29102695c..000000000 --- a/packages/bot/src/types/ttf.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -declare module '*.ttf' { - const path: string; - export default path; -} diff --git a/packages/bot/src/types/writable.ts b/packages/bot/src/types/writable.ts deleted file mode 100644 index 2f45b1129..000000000 --- a/packages/bot/src/types/writable.ts +++ /dev/null @@ -1 +0,0 @@ -export type Writeable = { -readonly [P in keyof T]: T[P] }; diff --git a/packages/bot/src/workers/bot.ts b/packages/bot/src/workers/bot.ts deleted file mode 100644 index b3af429ca..000000000 --- a/packages/bot/src/workers/bot.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { assertSecret } from "@blitzkit/core"; -import { registerProcesses } from "../core/blitzkit/processes"; -import { client } from "../core/discord/client"; - -console.log(`🟡 Launching bot ${client.shard?.ids[0]}`); - -registerProcesses(); - -console.log(`post register processes on shard ${client.shard?.ids[0]}`); - -client.login(assertSecret(import.meta.env.DISCORD_TOKEN)); - -console.log(`post client login on shard ${client.shard?.ids[0]}`); diff --git a/packages/bot/src/workers/render.ts b/packages/bot/src/workers/render.ts deleted file mode 100644 index 1838e44ad..000000000 --- a/packages/bot/src/workers/render.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { parentPort, threadId } from 'worker_threads'; -import { svgToPng } from '../core/blitzkit/svgToPng'; - -console.log(`🟢 Launched render worker ${threadId}`); - -parentPort?.on('message', async (svg: string) => { - const png = svgToPng(svg); - parentPort?.postMessage(png); -}); - -console.log(`post render thread parent port on thread ${threadId}`); diff --git a/packages/bot/tsconfig.json b/packages/bot/tsconfig.json deleted file mode 100644 index d24722b0d..000000000 --- a/packages/bot/tsconfig.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "include": ["src/**/*", "../core/src/blitzkit/supportedLocales.ts", "../i18n/src/unwrapper.ts"] -} diff --git a/packages/i18n/strings/en.json b/packages/i18n/strings/en.json index 8e5746268..418c90783 100644 --- a/packages/i18n/strings/en.json +++ b/packages/i18n/strings/en.json @@ -204,536 +204,6 @@ } }, - "bot": { - "common": { - "errors": { - "clan_not_found": "# Could not find clan\nCouldn't find \"{search}\". Try selecting a clan from the search result.", - "player_not_found": "# Could not find user\nI couldn't find user \"{search}\". Try picking an user from the search result, typing in a valid username, or using the `/link` command.", - "player_not_linked": "# Use `/link` to get started\nI don't know who you are but you can tell me today!", - "tank_name_not_found": "# Tank not found\nCould not find tank by the name \"{search}\".", - "tank_id_not_found": "# Tank not found\nCould not find tank by the ID \"{search}\". Try typing the name of the tank instead of a number.", - "no_tank_stats": "# No tank stats available\nThis player doesn't have any stats available. This may not be the player you are looking for.", - "get_help": "Help Discord server", - "uncaught_error": "# BlitzKit 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.", - "preview": "# This command is in preview\n\nYou can only access this command in [the official Discord server](https://discord.gg/nDt7AjGJQH). The command will be generally available soon.", - "query_too_small": "# Your search is too short\nYour search must be at least 3 characters long.", - "query_too_long": "# Your search is too long\nYour search must be at most 100 characters long." - }, - - "wn8": { - "name": "WN8", - - "types": { - "very_bad": "Very bad", - "bad": "Bad", - "below_average": "Below average", - "average": "Average", - "above_average": "Above average", - "good": "Good", - "very_good": "Very good", - "great": "Great", - "unicum": "Unicum", - "super_unicum": "Super unicum" - } - }, - - "subcommands": { - "today": { - "name": "today", - "description": "Today" - }, - "30": { - "name": "30", - "description": "30 days" - }, - "60": { - "name": "60", - "description": "60 days" - }, - "90": { - "name": "90", - "description": "90 days" - }, - "career": { - "name": "career", - "description": "Career" - }, - "custom": { - "name": "custom", - "description": "Custom range", - - "options": { - "start": { - "name": "start", - "description": "Start in number of days ago from today" - }, - "end": { - "name": "end", - "description": "End in number of days ago from today" - } - } - } - }, - - "options": { - "clan": { - "name": "clan", - "description": "The clan name or tag" - }, - - "tank": { - "name": "tank", - "description": "The tank to get stats for" - }, - - "username": { - "name": "username", - "description": "The Blitz username of the player" - }, - - "filters": { - "nation": { - "name": "nation", - "description": "Nation" - }, - "class": { - "name": "class", - "description": "Class" - }, - "type": { - "name": "type", - "description": "Type" - } - }, - - "region": { - "name": "region", - "description": "Region" - }, - - "tier": { - "name": "tier", - "description": "Tier", - - "choices": { - "tier": "Tier {tier_1} - {tier_2}" - } - } - }, - - "periods": { - "career": "Career", - "today": "Today", - "days": "{days} days", - "custom": "{day_1} - {day_2} days" - }, - - "filters": { - "none": "No filters", - "tier": "Tier {tier}" - }, - - "no_data": { - "battles_in_period": "No battles played in this period", - "battles_in_season": "No battles played in this season", - "players_in_period": "No players in this period", - "tanks_found": "No tanks found" - }, - - "buttons": { - "refresh": "Refresh" - }, - - "hero_stats": { - "battles": "Battles", - "winrate": "Winrate", - "damage": "Damage", - "damage_ratio": "Damage ratio", - "tier": "Tier", - "survival": "Survival", - "accuracy": "Accuracy", - "kills": "Kills" - } - }, - - "commands": { - "dead": "# Stat Commands Are Dead\n\nThe BlitzKit Discord bot used to use BlitzStars to fetch periodic statistics. BlitzStars has enabled aggressive Cloudflare protections; hence, disabling BlitzKit from using BlitzStar's API. This has impacted the following commands:\n\n- `/stats`\n- `/today`\n- `/breakdown`\n- `/evolution`\n- `/full-stats`\n\nPlease use [Aftermath]() as an alternative which is completely independent of BlitzStars. BlitzKit-hosted stat commands will return in the near future.", - - "replay": { - "name": "replay", - "description": "Display replay results", - - "options": { - "file": { - "name": "file", - "description": "Replay file" - } - } - }, - - "research": { - "name": "research", - "description": "Lists the expenses in researching a line", - - "options": { - "target_tank": { - "name": "target-tank", - "description": "The final tank to research in the line" - }, - "starting_tank": { - "name": "starting-tank", - "description": "The starting tank in the line (inferred automatically if not provided)" - } - }, - - "body": { - "subtitle": "Research", - "total": "Total", - "research": "Research: {cost}", - "upgrades": "Upgrades: {cost}", - "purchase": "Purchase: {cost}", - "equipment": "Equipment: {cost}", - "estimation": "# This is an estimation!\n\nThis list may include already researched tanks because of the inaccurate information Wargaming provides. Try providing a `starting-tank` from the line when using the command if you can." - }, - - "errors": { - "non_tech_tree": "# {tank} is not on the tech tree\n\nYou cannot research a premium/collector tank.", - "tier_1": "# {tank} is a tier I\n\nTier I tanks are always unlocked for free for all players.", - "already_researched": "# {tank} is already researched\n\nTry providing a `starting-tank` when using the command to view the totals for a line you have already researched.", - "start_end_equal": "# {tank} was provided twice\n\nMake sure `starting-tank` and `target-tank` are different.", - "tanks_not_on_line": "# {tank_1} and {tank_2} are not on the same line\n\nMake sure `starting-tank` and `target-tank` are on the same tech-tree line.", - "unordered": "# {tank_1} higher tiered to {tank_2}\n\nMake sure to provide {tank_2} as the `starting-tank` instead as it is the lower tiered tank." - } - }, - - "about": { - "name": "about", - "description": "All the info you need about the bot", - "subcommands": { - "commands": { - "name": "commands", - "description": "List of all commands and their descriptions", - "body": "# Commands\n\nBlitzKit offers the following commands:\n\n" - }, - "embeds": { - "name": "embeds", - "description": "How embeds work" - }, - "introduction": { - "name": "introduction", - "description": "About the bot" - }, - "invite": { - "name": "invite", - "description": "How to invite the bot" - }, - "timezones": { - "name": "timezones", - "description": "How BlitzKit infers and uses your timezone" - }, - "credits": { - "name": "credits", - "description": "People who have helped BlitzKit" - } - } - }, - - "breakdown": { - "name": "breakdown", - "description": "A period's breakdown by tanks played", - - "body": { - "total": "Total", - "battles": "Battles", - "wn8": "WN8", - "winrate": "Winrate", - "damage": "Damage", - "unknown_tank": "Unknown tank {id}" - } - }, - - "debug": { - "name": "debug", - "description": "Debug information about the bot", - - "body": { - "title": "# Debug\n\n", - "version": "Version", - "shard": { - "label": "Shard", - "default": "default" - }, - "uptime": "Uptime" - } - }, - - "evolution": { - "name": "evolution", - "description": "Evolution of statistics", - - "groups": { - "player": { - "name": "player", - "description": "Player statistics" - }, - - "tank": { - "name": "tank", - "description": "Tank statistics" - } - } - }, - - "inactive": { - "name": "inactive", - "description": "Lists all inactive players in a clan above a threshold", - - "options": { - "threshold": { - "name": "threshold", - "description": "The number of days inactive (default: {days} days)" - } - }, - - "body": { - "subtitle": "Inactive for {days}+ days", - "listing": "{days} days" - } - }, - - "full_stats": { - "name": "full-stats", - "description": "Full in-game statistics", - - "body": { - "cards": { - "games": { - "title": "Games", - "winrate": "Winrate", - "battles": "Battles", - "wins_losses": "Wins / losses" - }, - "efficiency": { - "title": "Efficiency", - "wn8": "WN8", - "survival": "Survival", - "accuracy": "Accuracy" - }, - "lethality": { - "title": "Lethality", - "average_damage": "Average damage", - "damage_per_tier": "Damage per tier", - "damage_ratio": "Damage ratio", - "average_kills": "Average kills" - }, - "miscellaneous": { - "title": "Miscellaneous", - "average_tier": "Average tier", - "average_spots": "Average spots", - "average_xp": "Average XP", - "kills_to_death_ratio": "Kills to death ratio" - } - } - } - }, - - "link": { - "name": "link", - "description": "Links your Blitz and Discord account", - - "body": { - "accounts_linked": "Accounts linked", - "no_role_permissions": { - "title": "{name} failed to verify", - "description": "I don't have the permission to manage roles." - } - }, - - "embed": { - "title": "Incorrect account?", - "description": "Use the command again and select a username from the search results." - } - }, - - "search_clans": { - "name": "search-clans", - "description": "Search clans", - - "options": { - "limit": { - "name": "limit", - "description": "The max number of clan results (default: {limit})" - } - }, - - "body": { - "title": "Clan search for {search} in region {region}", - "no_results": "No clans found" - } - }, - - "search_players": { - "name": "search-players", - "description": "Search players", - - "options": { - "limit": { - "name": "limit", - "description": "The max number of player results (default: {limit})" - } - }, - - "body": { - "title": "Player search for {search} in region {region}", - "no_results": "No players found" - } - }, - - "rating_leaderboard": { - "name": "rating-leaderboard", - "description": "Rating battles statistics", - - "subcommands": { - "neighbors": { - "name": "neighbors", - "description": "Neighboring players on the leaderboard" - }, - - "league": { - "name": "league", - "description": "Top players of a league" - } - }, - - "options": { - "limit": { - "name": "limit", - "description": "How many neighbors to display (default: {count})" - }, - - "season": { - "name": "season", - "description": "Season number (default: current)", - - "choices": { - "current": "{season} (current)" - } - }, - - "league": { - "name": "league", - "description": "The league to display" - } - }, - - "body": { - "top_players": "Top {count} players", - "no_participation": "# {player} didn't play rating in season {season}\n\nThis player did not participate in this season or did not get past calibration.", - "no_ongoing_season": "# No ongoing season\n\nThere isn't a currently running rating season.", - "deleted_player": "Deleted {id}", - "neighbors": "Rating neighbors", - "more": "+ {count} more" - } - }, - - "today": { - "name": "today", - "description": "Today's played tanks and statistics" - }, - - "stats": { - "name": "stats", - "description": "Quick statistics for a period" - }, - - "owned_tanks": { - "name": "owned-tanks", - "description": "Shows all own tanks in a tier", - - "body": { - "title": "# {name}'s owned tier {tier} tanks\n\n" - } - }, - - "permissions": { - "name": "permissions", - "description": "Checks wether the bot has the required permissions to function", - - "body": { - "title": "# Permissions\n\n{body}", - "view_channels": "View channels: needed to find the message the refresh button was used on", - "read_message_history": "Read message history: needed to find the message to refresh", - "attach_files": "Attach files: needed to add images to image-based commands", - "external_emojis": "External emojis: needed to add national flags and tank class emojis" - } - }, - - "ping": { - "name": "ping", - "description": "Check wether the bot is alive.", - - "body": { - "pong": "Pong 🏓", - "pong_with_time": "Pong 🏓 - {time}ms" - }, - - "subcommands": { - "blitzkit": { - "name": "blitzkit", - "description": "Ping BlitzKit" - }, - "wotb": { - "name": "wotb", - "description": "Ping World of Tanks Blitz" - } - } - }, - - "player_info": { - "name": "player-info", - "description": "Basic information about a player", - - "body": { - "title": "{name}'s information", - "nickname": "Nickname", - "battles": "Battles", - "winrate": "Winrate", - "account_id": "Account ID", - "created": "Created", - "last_battle": "Last battle" - } - }, - - "rating": { - "name": "rating", - "description": "Rating player's statistics", - - "subcommands": { - "today": { - "name": "today", - "description": "Today's rating statistics" - }, - "season": { - "name": "season", - "description": "This season's rating statistics" - } - }, - - "body": { - "subtitle": { - "today": "Today's rating statistics", - "season": "This season's rating statistics" - } - }, - - "errors": { - "no_participation": "# {name} hasn't played rating\n\nThis player hasn't participated in the rating gamemode this season.", - "unrecorded_stats": "# Statistics currently unavailable\n\nWargaming may be currently be facing server downtime. If there is an on-going season, statistics should be available withing 24 hours." - } - } - } - }, - "website": { "redirect": { "title": "301: moved permanently", diff --git a/packages/scripts/package.json b/packages/scripts/package.json index 8e788dc2d..41678e3ad 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -41,7 +41,6 @@ "setup:patches": "bun src/applyPatches.ts", "build:reviews": "bun src/reviews.ts", "build:ids": "bun src/discoverIds.ts", - "build:bot": "bun src/buildBot.ts", "build:ratingleaderboard": "bun src/buildRatingLeaderboard.ts", "build:proxyclient": "bun src/buildProxyClient.ts", "build:catalogitemaccesssor": "bun src/buildCatalogItemAccessor.ts", diff --git a/packages/scripts/src/buildBot.ts b/packages/scripts/src/buildBot.ts deleted file mode 100644 index 379bfc72a..000000000 --- a/packages/scripts/src/buildBot.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { build } from "esbuild"; -import { cp, readdir, rm } from "fs/promises"; - -const projectDir = "../.."; -const srcRoot = `${projectDir}/packages/bot`; -const distRoot = `${projectDir}/dist/bot`; -const modulesRoot = "../../node_modules"; -const workers = await readdir(`${srcRoot}/src/workers`); - -await rm(distRoot, { recursive: true, force: true }); - -build({ - entryPoints: [ - `${srcRoot}/src/main.ts`, - ...workers.map((worker) => `${srcRoot}/src/workers/${worker}`), - ], - bundle: true, - outdir: "../../dist/bot", - platform: "node", - format: "esm", - loader: { - ".ttf": "file", - ".node": "empty", - ".json": "json", - }, - external: ["sharp"], - minifyIdentifiers: false, // causes goofy ahh issues - minifySyntax: true, - minifyWhitespace: true, - sourcemap: true, - jsxImportSource: "react", - jsx: "automatic", -}); - -for (const module of await readdir(`${modulesRoot}/@resvg`)) { - readdir(`${modulesRoot}/@resvg/${module}`).then((files) => - files - .filter((file) => file.endsWith(".node")) - .map((file) => - cp( - `${modulesRoot}/@resvg/${module}/${file}`, - `${distRoot}/workers/${file}`, - ), - ), - ); -} - -await cp(`${projectDir}/prisma`, `${distRoot}/prisma`, { recursive: true }); - -for (const file of await readdir(`${modulesRoot}/prisma`).then((files) => - files.filter((file) => file.endsWith(".node")), -)) { - cp(`${modulesRoot}/prisma/${file}`, `${distRoot}/prisma/${file}`); -} diff --git a/prisma/schema.prisma b/prisma/schema.prisma deleted file mode 100644 index dcc620ae3..000000000 --- a/prisma/schema.prisma +++ /dev/null @@ -1,24 +0,0 @@ -generator client { - provider = "prisma-client-js" - binaryTargets = ["native", "debian-openssl-1.1.x", "debian-openssl-3.0.x"] - output = "../packages/bot/src/prisma" -} - -datasource db { - provider = "cockroachdb" - url = env("DATABASE_URL") -} - -model User { - blitz_id BigInt @id @map("blitz") // map: "blitz" was too vague - last_used DateTime? @default(now()) - - @@map("Users") // map: "Users" was mistakenly plural -} - -model DiscordUser { - discord_id BigInt @id @map("discord") // map: "discord" was too vague - blitz_id BigInt @map("blitz") // map: "blitz" was too vague - - @@map("DiscordUsers") // map: "DiscordUsers" was mistakenly plural -}