diff --git a/docs/guide/about.md b/docs/guide/about.md new file mode 100644 index 000000000..40c3b25c3 --- /dev/null +++ b/docs/guide/about.md @@ -0,0 +1,10 @@ +# About + +Blitzkrieg is a multipurpose bot for anything and everything World of Tanks Blitz. + +- 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 diff --git a/docs/guide/embeds.md b/docs/guide/embeds.md new file mode 100644 index 000000000..b663d0bfc --- /dev/null +++ b/docs/guide/embeds.md @@ -0,0 +1,23 @@ +# Embeds + +Embeds are generated by a few commands as a URL that can be loaded in a browser/streaming software. + +## Creating Embeds + +1. Use a command of your liking (for example, `/stats`). +2. Customize the command however you like and the URL generated for the embed will match your needs. +3. Send the command and when the bot is "done thinking," an "Embed" button will appear below the bot's reply. + +## Using Embeds in Streaming Softwares + +1. Click on the "Embed" button. +2. Copy the URL from your browser. +3. Paste the URL into a Browser Source. +4. Set the refresh rate/FPS to something very low like `0.0167` (that's 1 frame every 60 seconds). + +## How Embeds Behave + +- They load like any normal website albeit with a significant delay. +- They auto refresh every `60s`. +- All embeds and images generated by Blitzkrieg have a width of `640px` but the height varies and must be found manually through trial and error. + - It's okay to have a Browser Source that has a height larger than needed as Blitzkrieg embeds are transparent. diff --git a/docs/guide/invite.md b/docs/guide/invite.md new file mode 100644 index 000000000..307d0fa89 --- /dev/null +++ b/docs/guide/invite.md @@ -0,0 +1,10 @@ +# Inviting the Bot + +It's quite simple really. Just follow the steps below: + +1. Join a Discord server that already has Blitzkrieg installed or [the official Discord Server](https://discord.gg/nDt7AjGJQH). +2. Click on the bot's profile. +3. Click the "Add to Server" button under the bot's name and ID. +4. Select what server you want it in and see what permissions you seem fit for the bot. + +Voila! You're ready to go. diff --git a/src/commands/help.ts b/src/commands/help.ts index 408fe78b7..32e280296 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -1,36 +1,105 @@ -import { SlashCommandBuilder } from 'discord.js'; -import discord from '../../discord.json' assert { type: 'json' }; -import { client } from '../bot.js'; +import { + SlashCommandBuilder, + SlashCommandSubcommandBuilder, + SlashCommandSubcommandGroupBuilder, +} from 'discord.js'; import embedInfo from '../core/discord/embedInfo.js'; import { + COMMANDS_RAW, CommandRegistry, - guildCommands, - publicCommands, } from '../events/interactionCreate/index.js'; +const RAW_PATH = `https://raw.githubusercontent.com/tresabhi/blitzkrieg/main/docs/`; +const DOCS: Record = { + permissions: 'guide/permissions', + embeds: 'guide/embeds', + about: 'guide/about', + invite: 'guide/invite', +}; + export const helpCommand: CommandRegistry = { inProduction: true, - inDevelopment: false, + inDevelopment: true, inPublic: true, command: new SlashCommandBuilder() .setName('help') - .setDescription('All the help you need about the bot'), + .setDescription('All the help you need about the bot') + .addSubcommand((option) => + option + .setName('commands') + .setDescription('List of all commands and their descriptions'), + ) + .addSubcommand((option) => + option + .setName('permissions') + .setDescription('The permissions needed for the bot to function'), + ) + .addSubcommand((option) => + option.setName('about').setDescription('About the bot'), + ) + .addSubcommand((option) => + option.setName('embeds').setDescription('How embeds work'), + ) + .addSubcommand((option) => + option.setName('invite').setDescription('How to invite the bot'), + ), handler(interaction) { - return embedInfo( - `${client.user?.username} help`, - `**About**\n${ - client.user?.username - } automates many mundane tasks and provide statistics in numerous flexible ways.\n\nMade by TresAbhi from the Skilled [SKLLD] clan.\nGitHub: https://github.com/tresabhi/blitzkrieg\nDiscord server: https://discord.gg/nDt7AjGJQH\nSkilled server: https://discord.gg/ZPvcEG7DS8\n\n**Commands**\n${(interaction.guildId === - discord.sklld_guild_id - ? [...guildCommands, ...publicCommands] - : publicCommands - ) - .map((command) => `**/${command.name}**: ${command.description}`) - .join( - '\n', - )}\n\n*I am a new bot; I may make mistakes and go offline unexpectedly from time to time.*`, - ); + const subcommand = interaction.options.getSubcommand(); + + if (subcommand === 'commands') { + return [ + `# Commands\n\nBlitzkrieg offers the following commands:`, + embedInfo( + '', + COMMANDS_RAW.filter( + (registry) => registry.inPublic && registry.inProduction, + ) + .map((registry) => { + const info = `- \`/${registry.command.name}\`: ${registry.command.description}`; + const subcommandGroups = registry.command.options.filter( + (option) => + option instanceof SlashCommandSubcommandGroupBuilder, + ) as SlashCommandSubcommandGroupBuilder[]; + const subcommands = registry.command.options.filter( + (option) => option instanceof SlashCommandSubcommandBuilder, + ) as SlashCommandSubcommandBuilder[]; + + const tree = + subcommandGroups.length > 0 + ? subcommandGroups + .map((subcommandGroup) => { + const info = ` - \`${subcommandGroup.name}\`: ${subcommandGroup.description}`; + const subcommands = subcommandGroup.options + .map( + (subcommand) => + ` - \`${subcommand.name}\`: ${subcommand.description}`, + ) + .join('\n'); + + return `${info}${ + subcommands ? `\n${subcommands}` : '' + }`; + }) + .join('\n') + : subcommands + .map( + (subcommand) => + ` - \`${subcommand.name}\`: ${subcommand.description}`, + ) + .join('\n'); + + return `${info}${tree ? `\n${tree}` : ''}`; + }) + .join('\n'), + ), + ]; + } + + const url = `${RAW_PATH}${DOCS[subcommand]}.md`; + const content = fetch(url).then((response) => response.text()); + + return content; }, }; diff --git a/src/commands/ownedTanks.tsx b/src/commands/ownedTanks.tsx index d27980cde..d02575688 100644 --- a/src/commands/ownedTanks.tsx +++ b/src/commands/ownedTanks.tsx @@ -45,7 +45,7 @@ const COMP_TANKS = [ export const ownedTanksCommand: CommandRegistry = { inProduction: true, - inDevelopment: true, + inDevelopment: false, inPublic: true, command: new SlashCommandBuilder() diff --git a/src/commands/stats.tsx b/src/commands/stats.tsx index 24b1583bb..d302a286e 100644 --- a/src/commands/stats.tsx +++ b/src/commands/stats.tsx @@ -20,7 +20,7 @@ import { AccountInfo } from '../types/accountInfo.js'; export const statsCommand: CommandRegistry = { inProduction: true, - inDevelopment: true, + inDevelopment: false, inPublic: true, command: addStatTypeSubCommandGroups( diff --git a/src/commands/today.tsx b/src/commands/today.tsx index d3329380c..3b269819c 100644 --- a/src/commands/today.tsx +++ b/src/commands/today.tsx @@ -15,7 +15,7 @@ import { AccountInfo } from '../types/accountInfo.js'; export const todayCommand: CommandRegistry = { inProduction: true, - inDevelopment: true, + inDevelopment: false, inPublic: true, command: new SlashCommandBuilder() diff --git a/src/components/Breakdown/components/Row.tsx b/src/components/Breakdown/components/Row.tsx index 27e8ab831..e418f4b52 100644 --- a/src/components/Breakdown/components/Row.tsx +++ b/src/components/Breakdown/components/Row.tsx @@ -40,11 +40,7 @@ export function Row({ display: 'flex', borderRadius: 4, backgroundColor: isListing - ? naked - ? theme.colors.componentInteractive_transparent - : theme.colors.componentInteractive - : naked - ? theme.colors.appBackground2_transparent + ? theme.colors.componentInteractive : theme.colors.appBackground2, padding: 8, }} diff --git a/src/core/discord/embedInfo.ts b/src/core/discord/embedInfo.ts index 4c591e1f6..a188a1643 100644 --- a/src/core/discord/embedInfo.ts +++ b/src/core/discord/embedInfo.ts @@ -2,8 +2,11 @@ import { EmbedBuilder } from 'discord.js'; import { ACCENT_COLOR } from '../../constants/colors.js'; export default function embedInfo(title: string, description: string) { - return new EmbedBuilder() + const embed = new EmbedBuilder() .setColor(ACCENT_COLOR) - .setTitle(title) .setDescription(description); + + if (title) embed.setTitle(title); + + return embed; } diff --git a/src/core/discord/normalizeInteractionReturnable.ts b/src/core/discord/normalizeInteractionReturnable.ts index d935763a3..828812eb2 100644 --- a/src/core/discord/normalizeInteractionReturnable.ts +++ b/src/core/discord/normalizeInteractionReturnable.ts @@ -19,7 +19,9 @@ export default async function normalizeInteractionReturnable( await Promise.all( normalizedReturnable.map(async (item) => { - if (item instanceof EmbedBuilder) { + if (typeof item === 'string') { + reply.content = item; + } else if (item instanceof EmbedBuilder) { if (!reply.embeds) reply.embeds = []; reply.embeds.push(item); } else if (item instanceof ButtonBuilder) { diff --git a/src/events/interactionCreate/index.ts b/src/events/interactionCreate/index.ts index 3a5ee2cf2..952155d16 100644 --- a/src/events/interactionCreate/index.ts +++ b/src/events/interactionCreate/index.ts @@ -16,6 +16,7 @@ import discord from '../../../discord.json' assert { type: 'json' }; import { debugCommand } from '../../commands/debug.js'; import { eligibleCommand } from '../../commands/eligible.js'; import { evolutionCommand } from '../../commands/evolution.js'; +import { fullStatsCommand } from '../../commands/fullStats.js'; import { helpCommand } from '../../commands/help.js'; import { inactiveCommand } from '../../commands/inactive.js'; import { ownedTanksCommand } from '../../commands/ownedTanks.js'; @@ -26,7 +27,6 @@ import { searchClansCommand } from '../../commands/searchClans.js'; import { searchPlayersCommand } from '../../commands/searchPlayers.js'; import { searchTanksCommand } from '../../commands/searchTanks.js'; import { statsCommand } from '../../commands/stats.js'; -import { fullStatsCommand } from '../../commands/fullStats.js'; import { todayCommand } from '../../commands/today.js'; import { verifyCommand } from '../../commands/verify.js'; import { DISCORD_TOKEN } from '../../core/node/arguments.js'; @@ -37,6 +37,7 @@ import handleButton from './handlers/button.js'; import handleChatInputCommand from './handlers/chatInputCommand.js'; export type InteractionRawReturnable = + | string | EmbedBuilder | ButtonBuilder | JSX.Element; @@ -70,29 +71,31 @@ export interface CommandRegistry const rest = new REST().setToken(DISCORD_TOKEN); -export const commands: Record = ( - [ - debugCommand, - eligibleCommand, - helpCommand, - inactiveCommand, - ownedTanksCommand, - playerAchievementsCommand, - playerInfoCommand, - searchClansCommand, - searchPlayersCommand, - searchTanksCommand, - fullStatsCommand, - todayCommand, - verifyCommand, - pingCommand, - evolutionCommand, - statsCommand, - ] as CommandRegistry[] -).reduce((accumulator, registry) => { - if (isDev()) registry.command.setDefaultMemberPermissions(0); - return { ...accumulator, [registry.command.name]: registry }; -}, {}); +export const COMMANDS_RAW: CommandRegistry[] = [ + debugCommand, + eligibleCommand, + helpCommand, + inactiveCommand, + ownedTanksCommand, + playerAchievementsCommand, + playerInfoCommand, + searchClansCommand, + searchPlayersCommand, + searchTanksCommand, + fullStatsCommand, + todayCommand, + verifyCommand, + pingCommand, + evolutionCommand, + statsCommand, +]; +export const commands: Record = COMMANDS_RAW.reduce( + (accumulator, registry) => { + if (isDev()) registry.command.setDefaultMemberPermissions(0); + return { ...accumulator, [registry.command.name]: registry }; + }, + {}, +); export const guildCommands: RESTPostAPIChatInputApplicationCommandsJSONBody[] = []; diff --git a/src/routes/today.tsx b/src/routes/today.tsx index b4bf67298..257c79473 100644 --- a/src/routes/today.tsx +++ b/src/routes/today.tsx @@ -5,7 +5,7 @@ import { RouteRegistry } from '../server.js'; export const todayRoute: RouteRegistry = { route: '/today', inProduction: true, - inDevelopment: true, + inDevelopment: false, async handler(req) { const player = resolvePlayerFromRequest(req); diff --git a/src/stitches.config.ts b/src/stitches.config.ts index b75ceefae..30036ab13 100644 --- a/src/stitches.config.ts +++ b/src/stitches.config.ts @@ -1,10 +1,4 @@ -import { - amberDark, - blueDark, - slate, - slateDark, - slateDarkA, -} from '@radix-ui/colors'; +import { amberDark, blueDark, slate, slateDark } from '@radix-ui/colors'; import { createBorderStyles, createColors } from 'bepaint'; // this isn't really a stitches config, but the object is compliant @@ -13,8 +7,6 @@ export const theme = { ...createColors(slateDark), ...createColors(slate, 'light'), - ...createColors(slateDarkA, 'transparent'), - ...createColors(amberDark, 'amber'), ...createColors(blueDark, 'blue'), },