Skip to main content
Version: 1.0.17

Discord Technical Integration

Complete technical guide for integrating ElizaOS with Discord, including advanced features, event handling, and custom implementations.

Architecture Overview

Discord Service Architecture

interface DiscordService extends Service {
client: Client;
voiceManager: VoiceManager;
messageHandler: MessageHandler;
eventEmitter: EventEmitter;
}

Event Flow

Implementation Guide

1. Service Registration

import { DiscordService } from '@elizaos/plugin-discord';

// Register the Discord service
runtime.registerService(
new DiscordService({
botToken: process.env.DISCORD_API_TOKEN,
applicationId: process.env.DISCORD_APPLICATION_ID,
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent,
GatewayIntentBits.GuildVoiceStates,
],
})
);

2. Custom Event Handlers

// Custom message handler
class CustomDiscordHandler extends MessageHandler {
async handleMessage(message: Message): Promise<void> {
// Pre-processing
if (message.author.bot) return;

// Custom logic
const context = await this.buildContext(message);
const response = await this.runtime.process(context);

// Post-processing
await this.sendResponse(message, response);
}

private async buildContext(message: Message): Promise<Context> {
return {
userId: message.author.id,
roomId: message.channelId,
worldId: message.guildId,
content: message.content,
attachments: message.attachments.map((a) => ({
url: a.url,
type: a.contentType,
})),
};
}
}

3. Voice Integration

// Voice connection manager
class VoiceManager {
private connections: Map<string, VoiceConnection> = new Map();

async joinChannel(channelId: string): Promise<VoiceConnection> {
const channel = await this.client.channels.fetch(channelId);
if (!channel.isVoiceBased()) {
throw new Error('Not a voice channel');
}

const connection = joinVoiceChannel({
channelId: channel.id,
guildId: channel.guild.id,
adapterCreator: channel.guild.voiceAdapterCreator,
});

this.connections.set(channelId, connection);
return connection;
}

async playAudio(channelId: string, audioUrl: string): Promise<void> {
const connection = this.connections.get(channelId);
if (!connection) throw new Error('Not connected to channel');

const player = createAudioPlayer();
const resource = createAudioResource(audioUrl);

player.play(resource);
connection.subscribe(player);
}
}

Advanced Features

1. Slash Commands Implementation

// Command registration
const commands = [
{
name: 'ask',
description: 'Ask the AI a question',
options: [
{
name: 'question',
type: ApplicationCommandOptionType.String,
description: 'Your question',
required: true,
},
],
},
{
name: 'imagine',
description: 'Generate an image',
options: [
{
name: 'prompt',
type: ApplicationCommandOptionType.String,
description: 'Image description',
required: true,
},
],
},
];

// Command handler
client.on('interactionCreate', async (interaction) => {
if (!interaction.isChatInputCommand()) return;

switch (interaction.commandName) {
case 'ask':
await handleAskCommand(interaction);
break;
case 'imagine':
await handleImagineCommand(interaction);
break;
}
});

async function handleAskCommand(interaction: ChatInputCommandInteraction) {
await interaction.deferReply();

const question = interaction.options.getString('question');
const response = await runtime.process({
content: question,
userId: interaction.user.id,
roomId: interaction.channelId,
});

await interaction.editReply(response.text);
}

2. Embed Builders

// Rich embed responses
function createRichEmbed(data: any): EmbedBuilder {
return new EmbedBuilder()
.setTitle(data.title)
.setDescription(data.description)
.setColor(0x0099ff)
.addFields(
{ name: 'Status', value: data.status, inline: true },
{ name: 'Time', value: new Date().toISOString(), inline: true }
)
.setFooter({ text: 'Powered by ElizaOS' })
.setTimestamp();
}

// Usage in response
const embed = createRichEmbed({
title: 'Analysis Complete',
description: 'Here are the results...',
status: 'Success',
});

await message.channel.send({ embeds: [embed] });

3. Reaction Handlers

// Reaction-based interactions
client.on('messageReactionAdd', async (reaction, user) => {
if (user.bot) return;

// Handle specific reactions
switch (reaction.emoji.name) {
case '👍':
await handlePositiveFeedback(reaction.message, user);
break;
case '👎':
await handleNegativeFeedback(reaction.message, user);
break;
case '🔄':
await regenerateResponse(reaction.message, user);
break;
}
});

async function regenerateResponse(message: Message, user: User) {
// Get original context
const context = await getMessageContext(message.id);

// Generate new response
const newResponse = await runtime.process({
...context,
regenerate: true,
});

// Update message
await message.edit(newResponse.text);
}

Permission Management

1. Role-Based Access Control

// Permission checker
class PermissionManager {
private roleHierarchy: Map<string, number> = new Map([
['admin', 100],
['moderator', 50],
['member', 10],
['guest', 0],
]);

async checkPermission(userId: string, guildId: string, requiredLevel: number): Promise<boolean> {
const member = await this.getMember(guildId, userId);
const userLevel = this.getUserLevel(member);

return userLevel >= requiredLevel;
}

private getUserLevel(member: GuildMember): number {
let maxLevel = 0;

member.roles.cache.forEach((role) => {
const level = this.roleHierarchy.get(role.name.toLowerCase());
if (level && level > maxLevel) {
maxLevel = level;
}
});

return maxLevel;
}
}

2. Command Restrictions

// Restricted command example
const restrictedCommands = {
admin: ['shutdown', 'config', 'reset'],
moderator: ['mute', 'kick', 'warn'],
member: ['help', 'info', 'stats'],
};

async function executeCommand(command: string, user: User, guild: Guild): Promise<void> {
const permission = await permissionManager.getUserRole(user.id, guild.id);

const allowedCommands = Object.entries(restrictedCommands)
.filter(([role, _]) => permission.includes(role))
.flatMap(([_, commands]) => commands);

if (!allowedCommands.includes(command)) {
throw new Error('Insufficient permissions');
}

// Execute command
}

Performance Optimization

1. Message Caching

// LRU cache for messages
class MessageCache {
private cache: LRUCache<string, ProcessedMessage>;

constructor(maxSize: number = 1000) {
this.cache = new LRUCache({ max: maxSize });
}

async get(messageId: string): Promise<ProcessedMessage | null> {
return this.cache.get(messageId) || null;
}

async set(messageId: string, data: ProcessedMessage): Promise<void> {
this.cache.set(messageId, data);
}
}

2. Rate Limiting

// Rate limiter implementation
class RateLimiter {
private limits: Map<string, number[]> = new Map();

constructor(
private maxRequests: number = 5,
private windowMs: number = 60000
) {}

async checkLimit(userId: string): Promise<boolean> {
const now = Date.now();
const userLimits = this.limits.get(userId) || [];

// Remove old entries
const recent = userLimits.filter((time) => now - time < this.windowMs);

if (recent.length >= this.maxRequests) {
return false;
}

recent.push(now);
this.limits.set(userId, recent);
return true;
}
}

Error Handling

1. Comprehensive Error Management

// Error handler
class DiscordErrorHandler {
async handle(error: Error, context: ErrorContext): Promise<void> {
console.error(`Discord Error: ${error.message}`, context);

// Notify user
if (context.channel) {
await context.channel.send({
content: 'An error occurred. Please try again later.',
ephemeral: true,
});
}

// Log to monitoring
await this.logToMonitoring(error, context);

// Attempt recovery
if (error.code === 'CONNECTION_LOST') {
await this.reconnect();
}
}

private async reconnect(): Promise<void> {
// Implement reconnection logic
}
}

2. Graceful Degradation

// Fallback mechanisms
async function sendMessage(channel: TextChannel, content: string): Promise<void> {
try {
// Try rich embed
const embed = createRichEmbed(content);
await channel.send({ embeds: [embed] });
} catch (embedError) {
try {
// Fallback to plain text
await channel.send(content);
} catch (textError) {
// Log failure
console.error('Failed to send message', textError);
}
}
}

Testing Strategies

1. Unit Testing

// Test Discord service
describe('DiscordService', () => {
let service: DiscordService;
let mockClient: jest.Mocked<Client>;

beforeEach(() => {
mockClient = createMockClient();
service = new DiscordService({ client: mockClient });
});

test('handles message correctly', async () => {
const mockMessage = createMockMessage({
content: 'Hello bot',
author: { bot: false },
});

await service.handleMessage(mockMessage);

expect(mockClient.channels.send).toHaveBeenCalledWith(
expect.objectContaining({
content: expect.stringContaining('Hello'),
})
);
});
});

2. Integration Testing

// Test full flow
describe('Discord Integration', () => {
test('processes command end-to-end', async () => {
const response = await sendCommand('/ask What is ElizaOS?');

expect(response).toMatchObject({
success: true,
reply: expect.stringContaining('ElizaOS'),
});
});
});

Deployment Considerations

1. Environment Configuration

# Required
DISCORD_APPLICATION_ID=123456789012345678
DISCORD_API_TOKEN=Bot.Token.Here
DISCORD_PUBLIC_KEY=public_key_for_interactions

# Optional
DISCORD_GUILD_ID=specific_guild_id
DISCORD_LOG_CHANNEL=logs_channel_id
DISCORD_ERROR_WEBHOOK=webhook_url

2. Scaling Strategies

// Sharding for large bots
const manager = new ShardingManager('./bot.js', {
token: process.env.DISCORD_API_TOKEN,
totalShards: 'auto',
});

manager.on('shardCreate', (shard) => {
console.log(`Launched shard ${shard.id}`);
});

manager.spawn();

Best Practices

1. Security

  • Never expose bot tokens
  • Validate all user inputs
  • Implement proper rate limiting
  • Use environment variables

2. Performance

  • Cache frequently accessed data
  • Use bulk operations when possible
  • Implement connection pooling
  • Monitor memory usage

3. User Experience

  • Provide clear error messages
  • Use typing indicators for long operations
  • Implement command help system
  • Support multiple languages

Resources


Next: Telegram Technical Integration →