spacecommands
v3.7.2
Published
A modern, feature-rich Discord.js v14 command handler with slash commands, polls, AutoMod, permissions, cooldowns, modals, buttons, select menus, context menus, and Discord monetization support
Maintainers
Keywords
Readme
Please consult RULES.md before making any changes.
SpaceCommands
SpaceCommands is a modern, feature-rich Discord.js command handler library. Built on Discord.js v14, it provides an easy-to-use framework for creating Discord bots with slash commands, permissions, cooldowns, and more.
Features
- ✨ Modern Discord.js v14 - Built for the latest Discord features
- 🎯 Slash Commands - Full support for Discord's slash commands with autocomplete
- 📝 Prefix Commands - Traditional message-based commands
- 🔒 Permission System - Role and permission-based command restrictions
- ⏱️ Cooldown System - Per-user and global cooldowns with MongoDB persistence
- 🌍 Multi-Language Support - Built-in internationalization
- 🎨 Category System - Organize commands with custom categories
- 🗄️ MongoDB Integration - Optional database support for persistence
- 📦 TypeScript Support - Full TypeScript support with type definitions
- 🎮 Interactive Components - Buttons, select menus, and modals
- 📋 Context Menus - User and message context menu commands
- 💎 Premium Features - Discord entitlement and monetization support
- 🛠️ Component Utilities - Simplified builders and interaction collectors
- 📊 Poll Support - Native Discord polls with result tracking
- 🛡️ AutoMod Integration - Full AutoMod rule creation and management
⚠️ Developer Notes (Common Pitfalls)
Critical information for maintainers:
- SlashCommandBuilder Support: The library MUST explicitly extract options from
configuration.data.toJSON().options. NativeSlashCommandBuilderobjects hide thetypefield, which causesDiscordAPIError[50035]: Invalid Form Bodyif passed directly. DO NOT revert the.toJSON()extraction logic inCommandHandler.ts. - Command Registration Timing: Global commands fail silently if registered before the client is ready. The
CommandHandlerconstructor now waits forclient.once('ready'). DO NOT remove this check. - Update Logic: The
didOptionsChangelogic inSlashCommands.tsmust use||(OR) operators to correctly detect changes. DO NOT change this back to&&.
Installation
NPM
npm install spacecommandsYarn
yarn add spacecommandsQuick Start
const { Client, IntentsBitField } = require('discord.js');
const SpaceCommands = require('spacecommands');
const path = require('path');
const client = new Client({
intents: [
IntentsBitField.Flags.Guilds,
IntentsBitField.Flags.GuildMessages,
IntentsBitField.Flags.MessageContent,
],
});
client.on('ready', () => {
new SpaceCommands(client, {
commandsDir: path.join(__dirname, 'commands'),
featuresDir: path.join(__dirname, 'features'),
testServers: ['YOUR_TEST_SERVER_ID'],
botOwners: ['YOUR_USER_ID'],
// Database (optional - Supabase recommended)
supabaseUrl: process.env.SUPABASE_URL,
supabaseKey: process.env.SUPABASE_KEY,
});
});
client.login(process.env.BOT_TOKEN);Supabase Setup (Recommended)
SpaceCommands now uses Supabase for data persistence instead of MongoDB. Here's how to set it up:
1. Create a Supabase Project
- Go to supabase.com and create a free account
- Create a new project
- Copy your project URL and anon/public key
2. Set Up the Database
- In your Supabase dashboard, go to the SQL Editor
- Copy the contents of
supabase-schema.sqlfrom this package - Run the SQL to create all required tables
Or download the schema:
curl -o supabase-schema.sql https://raw.githubusercontent.com/VicToMeyeZR/SpaceCommands/main/supabase-schema.sql3. Configure Your Bot
new SpaceCommands(client, {
commandsDir: path.join(__dirname, 'commands'),
supabaseUrl: process.env.SUPABASE_URL, // Your Supabase project URL
supabaseKey: process.env.SUPABASE_ANON_KEY, // Your Supabase anon/public key
});4. Environment Variables
Create a .env file:
DISCORD_TOKEN=your_bot_token_here
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_ANON_KEY=your_anon_key_hereBenefits of Supabase
- ✅ Free tier with generous limits
- ✅ Real-time subscriptions (optional)
- ✅ Built-in authentication (if needed)
- ✅ Auto-generated APIs
- ✅ Better performance than MongoDB for most use cases
- ✅ PostgreSQL under the hood
Creating Commands
Slash Command Example
module.exports = {
category: 'Utility',
description: 'Ping command',
slash: true, // or 'both' for slash and prefix
callback: ({ interaction }) => {
return 'Pong!';
},
};Prefix Command Example
module.exports = {
category: 'Utility',
description: 'Ping command',
aliases: ['pong'],
callback: ({ message }) => {
return 'Pong!';
},
};Configuration Options
| Option | Type | Description | Default |
|--------|------|-------------|---------|
| commandsDir | string | Absolute path to commands directory | Required |
| featuresDir | string | Absolute path to features directory | Optional |
| supabaseUrl | string | Supabase project URL | Optional |
| supabaseKey | string | Supabase anon/public key | Optional |
| mongoUri | string | MongoDB URI (deprecated, use Supabase) | Optional |
| testServers | string[] | Guild IDs for testing commands | [] |
| botOwners | string[] | User IDs of bot owners | [] |
| defaultLanguage | string | Default language for messages | 'english' |
| ephemeral | boolean | Slash commands reply ephemerally | true |
| showWarns | boolean | Show warning messages | true |
| typeScript | boolean | Enable TypeScript support | false |
Command Options
Basic Options
category- Command category for organizationdescription- Command description (required for slash commands)aliases/names- Alternative command namesslash- Enable as slash command (true, false, or 'both')
Permissions & Access
permissions/requiredPermissions- Required Discord permissionsrequireRoles- Require specific rolesownerOnly- Restrict to bot owners onlyguildOnly- Disable in DMstestOnly- Only available in test servers
Arguments
minArgs- Minimum required argumentsmaxArgs- Maximum allowed argumentsexpectedArgs- Argument format stringsyntaxError- Custom error messages per language
Advanced
cooldown- Per-user cooldown (e.g., "5s", "1m")globalCooldown- Global cooldown (minimum 1m)hidden- Hide from help commandoptions- Slash command options array
Interactive Components
SpaceCommands provides full support for Discord's interactive components including buttons, select menus, and modals.
Button Handlers
Register button handlers to respond to button clicks:
// In your bot initialization
const instance = new SpaceCommands(client, { ... });
// Register a button handler
instance.componentHandler.registerButtonHandler({
customId: 'my-button',
callback: async (interaction, instance) => {
await interaction.reply('Button clicked!');
},
});
// Use regex for dynamic button IDs
instance.componentHandler.registerButtonHandler({
customId: /^page-\d+$/,
callback: async (interaction, instance) => {
const pageNum = interaction.customId.split('-')[1];
await interaction.reply(`Showing page ${pageNum}`);
},
});Select Menu Handlers
Handle all types of select menus (string, user, role, channel, mentionable):
instance.componentHandler.registerSelectMenuHandler({
customId: 'role-select',
callback: async (interaction, instance) => {
const selectedRoles = interaction.values;
await interaction.reply(`Selected: ${selectedRoles.join(', ')}`);
},
});Component Builders
Use the simplified component builder utilities:
const { ComponentUtils } = require('spacecommands');
// Create a button
const button = ComponentUtils.createButton(
'my-button',
'Click Me',
ButtonStyle.Primary,
{ emoji: '👋' }
);
// Create a select menu
const select = ComponentUtils.createStringSelect(
'my-select',
'Choose an option',
[
{ label: 'Option 1', value: 'opt1' },
{ label: 'Option 2', value: 'opt2' },
]
);
// Create an action row
const row = ComponentUtils.createActionRow(button);Modals
Create and handle modal forms for user input:
const { ComponentUtils } = require('spacecommands');
const { TextInputStyle } = require('discord.js');
// Create a modal
const modal = ComponentUtils.createModal(
'feedback-modal',
'Submit Feedback',
ComponentUtils.createTextInputRow(
'feedback-text',
'Your Feedback',
TextInputStyle.Paragraph,
{ placeholder: 'Tell us what you think...' }
)
);
// Show the modal
await interaction.showModal(modal);
// Register a modal handler
instance.modalHandler.registerModalHandler({
customId: 'feedback-modal',
callback: async (interaction, instance) => {
const feedback = interaction.fields.getTextInputValue('feedback-text');
await interaction.reply(`Thanks for your feedback: ${feedback}`);
},
});Context Menu Commands
Add user and message context menu commands (right-click menus):
instance.contextMenuHandler.registerContextMenu({
name: 'Get User Info',
type: ApplicationCommandType.User,
callback: async (interaction, instance) => {
const user = interaction.targetUser;
await interaction.reply(`User: ${user.tag}`);
},
});
instance.contextMenuHandler.registerContextMenu({
name: 'Quote Message',
type: ApplicationCommandType.Message,
callback: async (interaction, instance) => {
const message = interaction.targetMessage;
await interaction.reply(`"${message.content}" - ${message.author.tag}`);
},
});Autocomplete
Add autocomplete suggestions to slash command options:
instance.slashCommands.registerAutocomplete('search', async (interaction) => {
const focusedValue = interaction.options.getFocused();
const choices = ['apple', 'banana', 'cherry', 'date'];
const filtered = choices.filter(choice =>
choice.startsWith(focusedValue.toLowerCase())
);
await interaction.respond(
filtered.map(choice => ({ name: choice, value: choice }))
);
});Premium Features
Monetize your bot with Discord's entitlement system:
Register SKUs
instance.entitlementHandler.registerSKU({
skuId: '1234567890',
name: 'Premium Tier',
description: 'Access to premium features',
});Premium-Only Commands
module.exports = {
category: 'Premium',
description: 'Premium-only command',
// Require specific entitlement
requiredEntitlements: ['1234567890'],
// OR require any active entitlement
premiumOnly: true,
callback: ({ interaction }) => {
return 'Welcome, premium user!';
},
};Check Entitlements Programmatically
const { hasEntitlement } = await instance.entitlementHandler.hasEntitlement(
user.id,
'sku-id'
);
if (hasEntitlement) {
// Grant premium features
}Interaction Collectors
Easily collect component interactions:
const { InteractionCollectorUtils } = require('spacecommands');
// Await a button click
const button = await InteractionCollectorUtils.awaitButton(
message,
(i) => i.user.id === interaction.user.id,
30000 // 30 second timeout
);
if (button) {
await button.reply('Button clicked!');
}
// Create a select menu collector
const collector = InteractionCollectorUtils.createSelectMenuCollector(
message,
(i) => i.user.id === interaction.user.id,
{ time: 60000 }
);
collector.on('collect', async (i) => {
await i.reply(`Selected: ${i.values.join(', ')}`);
});Polls
Create and manage Discord's native polls:
// Access the poll handler
const pollHandler = instance.pollHandler;
// Get a poll from a message
const poll = message.poll;
if (poll) {
// Get poll results
const results = await pollHandler.getPollResults(poll);
// Get winning answer(s)
const winners = pollHandler.getWinningAnswers(poll);
console.log(`Winning answer: ${winners[0].text}`);
// Get poll statistics
const stats = pollHandler.getPollStats(poll);
console.log(`Total votes: ${stats.totalVotes}`);
// Get formatted results
const formatted = await pollHandler.getFormattedResults(poll);
await message.channel.send(formatted);
// Check if a user voted
const hasVoted = await pollHandler.hasUserVoted(poll, userId);
// Get user's votes
const userVotes = await pollHandler.getUserVotes(poll, userId);
// End poll early
await pollHandler.endPoll(poll);
}
// Register handler for when a poll ends
pollHandler.registerPollEndHandler({
pollId: /poll-.+/, // Regex or specific message ID
callback: async (poll, instance) => {
const results = await instance.pollHandler.getFormattedResults(poll);
await poll.message.channel.send(`Poll ended!\n${results}`);
},
});
// Fetch a poll from a message ID
const fetchedPoll = await pollHandler.fetchPoll(messageId, channelId);AutoMod
Manage Discord's AutoMod rules programmatically:
const { AutoModerationRuleEventType, AutoModerationActionType } = require('discord.js');
// Access the AutoMod handler
const autoModHandler = instance.autoModHandler;
// Create a keyword filter rule
const keywordRule = await autoModHandler.createKeywordRule(
guild,
'No Profanity',
['badword1', 'badword2'],
[
{
type: AutoModerationActionType.BlockMessage,
metadata: { customMessage: 'Please keep chat family-friendly!' }
},
{
type: AutoModerationActionType.Timeout,
metadata: { durationSeconds: 60 }
}
],
{
allowList: ['allowed-phrase'],
exemptRoles: ['moderator-role-id'],
exemptChannels: ['staff-channel-id']
}
);
// Create a spam rule
const spamRule = await autoModHandler.createSpamRule(
guild,
'Anti-Spam',
[{ type: AutoModerationActionType.BlockMessage }]
);
// Create a mention spam rule
const mentionRule = await autoModHandler.createMentionSpamRule(
guild,
'Mention Limit',
5, // Maximum 5 mentions
[{ type: AutoModerationActionType.BlockMessage }],
{ raidProtection: true }
);
// Create a regex pattern rule
const regexRule = await autoModHandler.createRegexRule(
guild,
'Link Blocker',
['(https?://)?([\\da-z\\.-]+)\\.([a-z\\.]{2,6})'],
[{ type: AutoModerationActionType.BlockMessage }]
);
// Create a preset keyword rule
const { AutoModerationRuleKeywordPresetType } = require('discord.js');
const presetRule = await autoModHandler.createPresetRule(
guild,
'Block Profanity',
[AutoModerationRuleKeywordPresetType.Profanity],
[{ type: AutoModerationActionType.BlockMessage }]
);
// Register handler for AutoMod actions
autoModHandler.registerActionHandler({
ruleId: 'specific-rule-id', // Optional: specific rule or regex
callback: async (execution, instance) => {
console.log(`AutoMod triggered by ${execution.userId}`);
console.log(`Rule: ${execution.ruleId}`);
console.log(`Action: ${execution.action.type}`);
// Log to a channel
const logChannel = execution.guild.channels.cache.get('log-channel-id');
if (logChannel) {
await logChannel.send(`AutoMod: User <@${execution.userId}> triggered rule ${execution.ruleTriggerType}`);
}
},
});
// Fetch all AutoMod rules for a guild
const rules = await autoModHandler.fetchGuildRules(guild);
// Update an existing rule
await autoModHandler.updateRule(guild, ruleId, {
enabled: false, // Disable the rule
});
// Delete a rule
await autoModHandler.deleteRule(guild, ruleId);
// Clear cache
autoModHandler.clearGuildCache(guildId);Documentation
For detailed documentation, examples, and guides, visit our GitHub repository.
Support
If you need help or have questions, please open an issue on our GitHub repository.
License
MIT License - see LICENSE file for details.
