forked from snxraven/ravenscott-blog
248 lines
14 KiB
Markdown
248 lines
14 KiB
Markdown
|
## Deep Dive into RayAI Chat Bot User-Installable Version
|
|||
|
<!-- lead -->
|
|||
|
Enabling users to use RayAI within any Channel on Discord.
|
|||
|
|
|||
|
This deep dive will explain how I built a user-installable version of the RayAI Chat Bot, highlighting key aspects such as the bot’s architecture, command handling, user privacy settings, and interaction with APIs. The bot integrates with Discord using its Slash Commands interface and makes use of Axios to send requests to a backend service. Below is a detailed walkthrough of the components that make this bot user-installable and functional.
|
|||
|
|
|||
|
# Source:
|
|||
|
|
|||
|
## https://s.shells.lol/4rBj_qRTw
|
|||
|
|
|||
|
|
|||
|
### Project Structure and Dependencies
|
|||
|
|
|||
|
Before diving into specific sections of the code, here's an overview of the main components and dependencies used:
|
|||
|
|
|||
|
```javascript
|
|||
|
const { Client, GatewayIntentBits, REST, Routes, EmbedBuilder, SlashCommandBuilder } = require('discord.js');
|
|||
|
const axios = require('axios');
|
|||
|
const he = require('he');
|
|||
|
const fs = require('fs');
|
|||
|
require('dotenv').config();
|
|||
|
const { userResetMessages } = require('./assets/messages.js');
|
|||
|
```
|
|||
|
|
|||
|
- **Discord.js**: Provides the essential framework to interact with Discord. Key classes such as `Client`, `GatewayIntentBits`, `REST`, `Routes`, and `EmbedBuilder` manage communication between the bot and Discord's API.
|
|||
|
- **Axios**: Used to send HTTP requests to external services, particularly the RayAI backend, handling operations like sending user messages and resetting conversations.
|
|||
|
- **`he`**: A library for encoding HTML entities, ensuring that user inputs are safely transmitted over HTTP.
|
|||
|
- **File System (fs)**: Utilized to store and retrieve user privacy settings, allowing the bot to persist data between sessions.
|
|||
|
- **dotenv**: Manages sensitive information like tokens and API paths by loading environment variables from a `.env` file.
|
|||
|
|
|||
|
### Discord Client Initialization
|
|||
|
|
|||
|
The bot is instantiated using the `Client` class, specifically configured to only handle events related to guilds (`GatewayIntentBits.Guilds`). This limits the bot's scope to only manage messages and interactions within servers:
|
|||
|
|
|||
|
```javascript
|
|||
|
const client = new Client({
|
|||
|
intents: [GatewayIntentBits.Guilds]
|
|||
|
});
|
|||
|
```
|
|||
|
|
|||
|
### Managing User Privacy Settings
|
|||
|
|
|||
|
The bot includes a privacy feature where users can toggle between ephemeral (private) and standard (public) responses. This is achieved through the `userPrivacySettings.json` file, which stores these preferences:
|
|||
|
|
|||
|
```javascript
|
|||
|
// Load or initialize the user privacy settings
|
|||
|
const userPrivacyFilePath = './userPrivacySettings.json';
|
|||
|
let userPrivacySettings = {};
|
|||
|
if (fs.existsSync(userPrivacyFilePath)) {
|
|||
|
userPrivacySettings = JSON.parse(fs.readFileSync(userPrivacyFilePath));
|
|||
|
}
|
|||
|
|
|||
|
// Save the user privacy settings
|
|||
|
function saveUserPrivacySettings() {
|
|||
|
fs.writeFileSync(userPrivacyFilePath, JSON.stringify(userPrivacySettings, null, 2));
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
The privacy settings are initialized by checking if the `userPrivacySettings.json` file exists. If it does, the bot loads the settings into memory; otherwise, a new file is created. The `saveUserPrivacySettings()` function is used to persist updates when a user toggles their privacy settings.
|
|||
|
|
|||
|
### Slash Command Registration with Extras
|
|||
|
|
|||
|
The bot supports four commands:
|
|||
|
|
|||
|
- `/reset`: Resets the current conversation with the AI.
|
|||
|
- `/restartcore`: Restarts the core service.
|
|||
|
- `/chat`: Sends a user message to the AI.
|
|||
|
- `/privacy`: Toggles between ephemeral (private) and standard (public) responses.
|
|||
|
|
|||
|
The commands are defined using `SlashCommandBuilder` and registered with Discord using the REST API:
|
|||
|
|
|||
|
```javascript
|
|||
|
const commands = [
|
|||
|
new SlashCommandBuilder().setName('reset').setDescription('Reset the conversation'),
|
|||
|
new SlashCommandBuilder().setName('restartcore').setDescription('Restart the core service'),
|
|||
|
new SlashCommandBuilder().setName('chat').setDescription('Send a chat message')
|
|||
|
.addStringOption(option =>
|
|||
|
option.setName('message')
|
|||
|
.setDescription('Message to send')
|
|||
|
.setRequired(true)),
|
|||
|
new SlashCommandBuilder().setName('privacy').setDescription('Toggle between ephemeral and standard responses')
|
|||
|
].map(command => {
|
|||
|
const commandJSON = command.toJSON();
|
|||
|
|
|||
|
const extras = {
|
|||
|
"integration_types": [0, 1], // 0 for guild, 1 for user
|
|||
|
"contexts": [0, 1, 2] // 0 for guild, 1 for app DMs, 2 for GDMs and other DMs
|
|||
|
};
|
|||
|
|
|||
|
Object.keys(extras).forEach(key => commandJSON[key] = extras[key]);
|
|||
|
|
|||
|
return commandJSON;
|
|||
|
});
|
|||
|
|
|||
|
// Register commands with Discord
|
|||
|
const rest = new REST({ version: '10' }).setToken(process.env.THE_TOKEN_2);
|
|||
|
|
|||
|
client.once('ready', async () => {
|
|||
|
try {
|
|||
|
console.log(`Logged in as ${client.user.tag}!`);
|
|||
|
await rest.put(Routes.applicationCommands(process.env.DISCORD_CLIENT_ID), { body: commands });
|
|||
|
console.log('Successfully registered application commands with extras.');
|
|||
|
} catch (error) {
|
|||
|
console.error('Error registering commands: ', error);
|
|||
|
}
|
|||
|
});
|
|||
|
```
|
|||
|
|
|||
|
The bot registers the commands upon startup using the `REST.put()` method. Note that each command includes extra metadata for `integration_types` (whether the command is for guild or user contexts) and `contexts` (indicating where the command applies: guilds, direct messages, etc.).
|
|||
|
|
|||
|
### Handling User Interactions
|
|||
|
|
|||
|
Each command has its respective handler, triggered when a user interacts with the bot:
|
|||
|
|
|||
|
```javascript
|
|||
|
client.on('interactionCreate', async interaction => {
|
|||
|
if (!interaction.isCommand()) return;
|
|||
|
|
|||
|
const { commandName, options } = interaction;
|
|||
|
|
|||
|
if (commandName === 'reset') {
|
|||
|
return await resetConversation(interaction);
|
|||
|
} else if (commandName === 'restartcore') {
|
|||
|
await restartCore(interaction);
|
|||
|
} else if (commandName === 'chat') {
|
|||
|
const content = options.getString('message');
|
|||
|
await handleUserMessage(interaction, content);
|
|||
|
} else if (commandName === 'privacy') {
|
|||
|
await togglePrivacy(interaction);
|
|||
|
}
|
|||
|
});
|
|||
|
```
|
|||
|
|
|||
|
Each interaction is checked for its command name, and the corresponding function is invoked:
|
|||
|
|
|||
|
- **`resetConversation()`**: Sends a POST request to the RayAI backend to reset the current conversation.
|
|||
|
- **`restartCore()`**: Sends a POST request to restart the core service.
|
|||
|
- **`handleUserMessage()`**: Sends the user's message to the backend for processing, encodes it using `he.encode()` for safety, and handles the bot's typing indicators and replies.
|
|||
|
- **`togglePrivacy()`**: Toggles the user's privacy setting between ephemeral and standard responses, storing this preference for future use.
|
|||
|
|
|||
|
### Privacy Toggle
|
|||
|
|
|||
|
The `/privacy` command allows users to control whether their responses are private or visible to everyone:
|
|||
|
|
|||
|
```javascript
|
|||
|
async function togglePrivacy(interaction) {
|
|||
|
const userId = interaction.user.id;
|
|||
|
const currentSetting = userPrivacySettings[userId] || false;
|
|||
|
userPrivacySettings[userId] = !currentSetting; // Toggle the setting
|
|||
|
saveUserPrivacySettings();
|
|||
|
|
|||
|
const message = userPrivacySettings[userId]
|
|||
|
? 'Your responses are now set to ephemeral (visible only to you).'
|
|||
|
: 'Your responses are now standard (visible to everyone).';
|
|||
|
await interaction.reply({ content: message, ephemeral: true });
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
This function checks the current privacy setting for the user, toggles it, and saves the updated setting. A response is then sent to the user, confirming the new privacy mode.
|
|||
|
|
|||
|
### Sending Long Messages
|
|||
|
|
|||
|
The bot can handle large responses from the AI by splitting them into chunks and sending them in sequence:
|
|||
|
|
|||
|
```javascript
|
|||
|
async function sendLongMessage(interaction, responseText) {
|
|||
|
const limit = 8096;
|
|||
|
|
|||
|
if (responseText.length > limit) {
|
|||
|
const lines = responseText.split('\n');
|
|||
|
const chunks = [];
|
|||
|
let currentChunk = '';
|
|||
|
|
|||
|
for (const line of lines) {
|
|||
|
if (currentChunk.length + line.length > limit) {
|
|||
|
chunks.push(currentChunk);
|
|||
|
currentChunk = '';
|
|||
|
}
|
|||
|
currentChunk += line + '\n';
|
|||
|
}
|
|||
|
|
|||
|
if (currentChunk.trim() !== '') {
|
|||
|
chunks.push(currentChunk.trim());
|
|||
|
}
|
|||
|
|
|||
|
if (chunks.length >= 80) return await interaction.reply({ content: "Response chunks too large. Try again", ephemeral: isEphemeral(interaction.user.id) });
|
|||
|
|
|||
|
for (let i = 0; i < chunks.length; i++) {
|
|||
|
const chunk = chunks[i];
|
|||
|
const embed = new EmbedBuilder()
|
|||
|
.setDescription(chunk)
|
|||
|
.setColor("#3498DB")
|
|||
|
.setTimestamp();
|
|||
|
|
|||
|
setTimeout(() => {
|
|||
|
interaction.followUp({
|
|||
|
embeds: [embed],
|
|||
|
ephemeral: isEphemeral(interaction.user.id)
|
|||
|
});
|
|||
|
}, i * (process.env.OVERFLOW_DELAY || 3) * 1000);
|
|||
|
}
|
|||
|
} else {
|
|||
|
const embed = new EmbedBuilder()
|
|||
|
.setDescription(responseText)
|
|||
|
.setColor("#3498DB")
|
|||
|
.setTimestamp();
|
|||
|
|
|||
|
interaction.editReply({
|
|||
|
embeds: [embed],
|
|||
|
ephemeral: isEphemeral(interaction.user.id)
|
|||
|
});
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
This function ensures that responses longer than Discord's character limit are split and delivered to the user in manageable chunks. Each chunk is sent with a delay to avoid overwhelming the bot or Discord API.
|
|||
|
|
|||
|
### The results
|
|||
|
|
|||
|
The RayAI Chat Bot is a robust, user-centric Discord bot designed to provide seamless interaction with an AI backend, featuring rich customization options for user preferences and flexible handling of both short and long messages. This project exemplifies how powerful a user-installable bot can be when built with attention to detail and user experience.
|
|||
|
|
|||
|
#### Key Aspects of the Project:
|
|||
|
|
|||
|
1. **Slash Command Integration**:
|
|||
|
The bot leverages Discord's Slash Command API to create an intuitive, user-friendly interface where commands are easy to use and comprehend. The choice of commands—such as `/reset`, `/restartcore`, and `/chat`—ensures that the user has full control over interactions with the AI service. By dynamically registering these commands using Discord’s REST API, the bot can be installed and updated without manual intervention, adding to its user-friendly nature.
|
|||
|
|
|||
|
2. **User Privacy Customization**:
|
|||
|
The integration of a privacy toggle function demonstrates the bot’s emphasis on user autonomy and personalization. Users can easily switch between public and private message responses via the `/privacy` command, with the bot persisting these settings across sessions using a JSON file. This feature enhances trust and usability, making the bot suitable for varied contexts where privacy may be a concern.
|
|||
|
|
|||
|
3. **Interaction with External APIs**:
|
|||
|
The bot’s communication with the AI backend is elegantly handled through Axios, enabling the bot to send user inputs securely and receive intelligent responses. The careful encoding of user messages via `he.encode()` mitigates security risks, such as cross-site scripting (XSS) or injection attacks, ensuring that the bot operates safely even when interacting with untrusted input.
|
|||
|
|
|||
|
4. **Handling Long Messages**:
|
|||
|
The long-message handling functionality solves a common challenge when integrating chatbots into platforms like Discord, which has strict message limits. By intelligently splitting long AI responses into manageable chunks and sending them sequentially, the bot maintains the fluidity of conversation while respecting Discord’s limitations. This ensures that users receive complete responses without abrupt truncation, even when dealing with complex or detailed queries.
|
|||
|
|
|||
|
5. **Automatic Command Registration with Extras**:
|
|||
|
The ability to dynamically register commands at startup using the Discord REST API eliminates the need for pre-configuration, making the bot easy to install and update. Additionally, by adding custom `integration_types` and `contexts` to the command registration, the bot can function across various Discord environments (guilds, DMs, etc.), extending its versatility and appeal to a broader user base.
|
|||
|
|
|||
|
6. **Resilient Error Handling**:
|
|||
|
The bot is built with robust error handling, particularly in scenarios where the AI backend may be busy or encounter rate limits. The user is promptly notified if the service is unavailable, and fallback mechanisms ensure that the user experience remains smooth, even in the face of external service issues. This kind of resilience is crucial for any production-grade bot, minimizing downtime and ensuring reliability.
|
|||
|
|
|||
|
7. **User-Installable Design**:
|
|||
|
A key highlight of this project is the bot's user-installable nature. By storing configuration details like tokens and API paths in environment variables and using simple file-based storage for privacy settings, the bot is easy to configure and deploy in any environment. The ability for users to install and manage the bot themselves adds to its flexibility, making it accessible to a wide range of users, from individuals to server administrators looking for a custom chatbot solution.
|
|||
|
|
|||
|
|
|||
|
The RayAI Chat Bot showcases the power of combining modern web technologies like Discord.js, Axios, and file-based storage to create a sophisticated, user-installable chatbot. Its thoughtful features—ranging from user privacy customization to efficient message handling—make it a highly functional tool that serves both casual and professional needs.
|
|||
|
|
|||
|
This bot is not just a technical achievement but also a product of careful consideration of user needs, focusing on ease of installation, flexibility, and resilience. Whether you're managing a Discord server, interacting with AI, or looking for a chatbot solution that’s easy to deploy and scale, the RayAI Chat Bot sets a high standard for future projects in this domain.
|