@zerospacegg/vynthra
Version:
Discord bot for ZeroSpace.gg data
360 lines (283 loc) • 9.65 kB
Markdown
# 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.