UNPKG

@zerospacegg/vynthra

Version:
360 lines (283 loc) 9.65 kB
# Vynthra Discord Bot Library A flexible, embeddable Discord bot library for ZeroSpace.gg (and other games) that allows you to easily create custom Discord integrations with configurable commands and subcommands. ## Features - 🔧 **Configurable Root Command**: Change the command name from `/zsgg` to anything you want - 🎯 **Custom Subcommands**: Add your own subcommands alongside the built-in stats functionality - 📦 **Embeddable Library**: Use as a dependency in your own Discord bot projects - 🛡️ **Type Safe**: Full TypeScript support with comprehensive type definitions - 🔄 **Hot-swappable**: Replace built-in functionality with your own implementations - 📊 **Built-in Stats**: Includes ZeroSpace.gg game data search functionality ## Installation ```bash npm install @zsgg/vynthra ``` ## Quick Start ### Basic Usage ```typescript import { createBot, deployCommands, type BotConfig } from '@zsgg/vynthra/bot'; const config: BotConfig = { token: process.env.DISCORD_TOKEN!, clientId: process.env.DISCORD_CLIENT_ID!, guildId: process.env.DISCORD_GUILD_ID, // Optional: for development }; const bot = createBot(config); await deployCommands(config); await bot.start(); ``` This creates a bot with the default `/zsgg stats` command. ### Custom Command Name ```typescript const config: BotConfig = { token: process.env.DISCORD_TOKEN!, clientId: process.env.DISCORD_CLIENT_ID!, rootCommandName: "mygame", rootCommandDescription: "Search MyGame data", }; ``` Now your command will be `/mygame stats` instead of `/zsgg stats`. ## Adding Custom Subcommands ### Simple Custom Subcommand ```typescript import { createSubcommand } from '@zsgg/vynthra/bot'; const helpCommand = createSubcommand( "help", "Get help information", (subcommand) => subcommand .addStringOption(option => option .setName("topic") .setDescription("Help topic") .setRequired(false) ), async (interaction) => { const topic = interaction.options.getString("topic"); await interaction.reply(`Help for: ${topic || "general"}`); } ); const config: BotConfig = { token: process.env.DISCORD_TOKEN!, clientId: process.env.DISCORD_CLIENT_ID!, rootCommandName: "mygame", subcommands: [helpCommand], }; ``` ### Query-based Subcommand Helper For commands that follow the same pattern as the stats command (single query parameter): ```typescript import { createQuerySubcommand } from '@zsgg/vynthra/bot'; const searchCommand = createQuerySubcommand( "search", "Search the game database", "What to search for", async (interaction) => { const query = interaction.options.getString("query", true); await interaction.deferReply(); // Your search logic here const results = await searchGameDatabase(query); await interaction.editReply(`Found ${results.length} results for "${query}"`); } ); ``` ### Advanced Custom Subcommand ```typescript const compareCommand = createSubcommand( "compare", "Compare two units", (subcommand) => subcommand .addStringOption(option => option .setName("first") .setDescription("First unit") .setRequired(true) ) .addStringOption(option => option .setName("second") .setDescription("Second unit") .setRequired(true) ) .addStringOption(option => option .setName("aspect") .setDescription("What to compare") .addChoices( { name: "Stats", value: "stats" }, { name: "Cost", value: "cost" }, { name: "Abilities", value: "abilities" } ) .setRequired(false) ), async (interaction) => { const first = interaction.options.getString("first", true); const second = interaction.options.getString("second", true); const aspect = interaction.options.getString("aspect") || "stats"; await interaction.deferReply(); // Your comparison logic here const comparison = await compareUnits(first, second, aspect); await interaction.editReply({ content: `⚖️ **${first} vs ${second}**\n\n${comparison}`, }); } ); ``` ## Replacing Built-in Commands You can override the built-in `stats` command by providing your own: ```typescript const customStatsCommand = createQuerySubcommand( "stats", // Same name as built-in command "Get custom game stats", "Search term", async (interaction) => { // Your custom stats implementation const query = interaction.options.getString("query", true); await interaction.reply(`Custom stats for: ${query}`); } ); const config: BotConfig = { token: process.env.DISCORD_TOKEN!, clientId: process.env.DISCORD_CLIENT_ID!, subcommands: [customStatsCommand], // This overrides the built-in stats }; ``` ## Complete Example ```typescript import { createBot, deployCommands, createSubcommand, createQuerySubcommand, type BotConfig, } from '@zsgg/vynthra/bot'; // Custom subcommands const helpCommand = createSubcommand( "help", "Get help information", (subcommand) => subcommand, async (interaction) => { await interaction.reply({ content: "📖 **Available Commands:**\n" + "• `/mygame stats <query>` - Search game data\n" + "• `/mygame help` - Show this help\n" + "• `/mygame player <username>` - Get player info", ephemeral: true, }); } ); const playerCommand = createSubcommand( "player", "Get player information", (subcommand) => subcommand .addStringOption(option => option .setName("username") .setDescription("Player username") .setRequired(true) ), async (interaction) => { const username = interaction.options.getString("username", true); await interaction.deferReply(); try { const playerData = await fetchPlayerData(username); await interaction.editReply({ content: `👤 **${playerData.username}**\n` + `🏆 Level: ${playerData.level}\n` + `⭐ Rank: ${playerData.rank}`, }); } catch (error) { await interaction.editReply({ content: `❌ Player "${username}" not found.`, }); } } ); // Bot configuration const config: BotConfig = { token: process.env.DISCORD_TOKEN!, clientId: process.env.DISCORD_CLIENT_ID!, guildId: process.env.DISCORD_GUILD_ID, // Optional rootCommandName: "mygame", rootCommandDescription: "MyGame Discord integration", subcommands: [helpCommand, playerCommand], }; // Start the bot async function main() { try { const bot = createBot(config); await deployCommands(config); await bot.start(); console.log("🤖 Bot started successfully!"); } catch (error) { console.error("❌ Failed to start bot:", error); process.exit(1); } } main(); ``` ## API Reference ### Types #### `BotConfig` ```typescript interface BotConfig { token: string; // Discord bot token clientId: string; // Discord application client ID guildId?: string; // Optional: Guild ID for development rootCommandName?: string; // Optional: Root command name (default: "zsgg") rootCommandDescription?: string; // Optional: Root command description subcommands?: BotSubcommand[]; // Optional: Custom subcommands } ``` #### `BotSubcommand` ```typescript interface BotSubcommand { name: string; description: string; builder: (subcommand: SlashCommandSubcommandBuilder) => SlashCommandSubcommandBuilder; execute: (interaction: ChatInputCommandInteraction) => Promise<void>; } ``` ### Functions #### `createBot(config: BotConfig): VynthraBot` Creates a new bot instance with the given configuration. #### `deployCommands(config: BotConfig): Promise<void>` Deploys the bot's commands to Discord. Must be called before starting the bot. #### `createSubcommand(...)` Helper function to create custom subcommands. See examples above. #### `createQuerySubcommand(...)` Helper function to create query-based subcommands (single string parameter). ### Built-in Subcommands #### `statsSubcommand` The built-in stats subcommand that searches ZeroSpace.gg game data. You can import and use this if you want to include it alongside your custom commands, or replace it entirely. ```typescript import { statsSubcommand } from '@zsgg/vynthra/bot'; const config: BotConfig = { // ... other config subcommands: [ statsSubcommand, // Include built-in stats myCustomCommand, // Plus your custom commands ], }; ``` ## Environment Variables Set these environment variables: ```env DISCORD_TOKEN=your_bot_token_here DISCORD_CLIENT_ID=your_application_client_id DISCORD_GUILD_ID=your_guild_id_for_development # Optional ``` ## Error Handling The bot includes comprehensive error handling: - Invalid subcommand configurations are validated at startup - Runtime command errors are caught and reported to users - Graceful shutdown on SIGINT/SIGTERM signals - Detailed logging for debugging ## Discord Message Limits The bot automatically handles Discord's 2000 character message limit by: - Splitting long responses into multiple messages - Preserving markdown formatting when possible - Using ephemeral responses for multi-match results ## Development vs Production Use `guildId` in your config for development to deploy commands instantly to a specific server. Remove it for production to deploy commands globally (takes up to 1 hour to propagate). ## Contributing This library is part of the ZeroSpace.gg project. Contributions are welcome! ## License MIT License - see LICENSE file for details.