Add Everything-Bot Discord bot with reaction roles

Initial setup of a modular Discord bot featuring reaction-role management. Includes slash commands for creating, listing, removing, and clearing reaction roles with support for unicode and custom emojis. Features persistent SQLite storage, automatic command and event loading, graceful shutdown handling, and structured logging. Designed with extensibility in mind for future commands and events.
This commit is contained in:
2026-07-16 15:38:43 +12:00
parent 024f1082ef
commit 932cfc57d3
29 changed files with 4473 additions and 0 deletions
+307
View File
@@ -0,0 +1,307 @@
# Everything Bot
Modular Discord bot built with Node.js, Discord.js, and SQLite. The first feature is **reaction roles**, with an architecture designed so new commands and events can be added without rewriting the core.
## Requirements
- **Node.js** 20 or newer (Node 22 recommended)
- A Discord application and bot user
- npm
## Features
- Guild-only slash commands for creating, listing, removing, and clearing reaction roles
- Unicode, static custom, and animated custom emoji support
- Multiple mappings per message, shared roles across messages, multi-guild storage
- Persistent SQLite storage that survives restarts
- Automatic command and event loading from folders
- Graceful shutdown and structured logging (secrets never logged)
## Installation
```bash
npm install
cp .env.example .env
```
Edit `.env` with your Discord credentials (see below).
## Discord application setup
1. Open the [Discord Developer Portal](https://discord.com/developers/applications).
2. Click **New Application**, name it, and create it.
3. Open the **Bot** tab and click **Add Bot** if needed.
4. Under **Token**, click **Reset Token** / **Copy** and save it for `DISCORD_TOKEN`.
5. Open the **OAuth2 → General** tab and copy the **Application ID** for `DISCORD_CLIENT_ID`.
### Required gateway intents
In the **Bot** tab, enable:
| Intent | Required | Notes |
| --- | --- | --- |
| Server Members Intent | Yes | Needed to fetch members and assign/remove roles |
| Message Content Intent | No | Not required for reaction roles |
The bot also requests these gateway intents in code:
- Guilds
- Guild Members
- Guild Message Reactions
### Required bot permissions
When inviting the bot, grant at least:
```text
View Channels
Read Message History
Add Reactions
Manage Roles
Use Application Commands
```
**Role hierarchy:** the bots highest role must sit **above** every role it needs to assign. In **Server Settings → Roles**, drag the bots role above those roles.
### Invite the bot
1. Open **OAuth2 → URL Generator**.
2. Scopes: `bot` and `applications.commands`.
3. Bot permissions: select the permissions listed above (or use permission integer `268438560` as a starting point — verify in the UI).
4. Open the generated URL, choose your server, and authorize.
## Environment variables
Copy `.env.example` to `.env`:
```env
DISCORD_TOKEN=
DISCORD_CLIENT_ID=
DISCORD_GUILD_ID=
DATABASE_PATH=./data/bot.sqlite
```
| Variable | Required | Description |
| --- | --- | --- |
| `DISCORD_TOKEN` | Yes | Bot token |
| `DISCORD_CLIENT_ID` | Yes | Application ID |
| `DISCORD_GUILD_ID` | No | Development server ID for fast command updates |
| `DATABASE_PATH` | No | SQLite file path (default `./data/bot.sqlite`) |
The bot validates required variables on startup and exits with a clear error if any are missing.
## Database
SQLite is initialized automatically on startup. The `data/` directory and database file are created if missing. No manual migration step is required.
The `reaction_roles` table stores:
- `id`, `guild_id`, `channel_id`, `message_id`, `role_id`
- `emoji_identifier`, `emoji_display`
- `created_by`, `created_at`
A uniqueness constraint on `(guild_id, message_id, emoji_identifier)` prevents duplicate emoji mappings on the same message.
## Slash command deployment
```bash
npm run deploy-commands
```
- **With `DISCORD_GUILD_ID` set:** commands are registered to that guild and usually appear within a few seconds. Prefer this while developing.
- **Without `DISCORD_GUILD_ID`:** commands are registered globally and may take up to about an hour to propagate to all servers.
Re-run deployment whenever you add or change slash command definitions.
## Running the bot
Development (restarts on file changes):
```bash
npm run dev
```
Production:
```bash
npm start
```
Lint:
```bash
npm run lint
```
## Reaction-role commands
All of these require **Manage Roles** and are guild-only. Responses are ephemeral.
### Create
```text
/reaction-role message_id:<message ID> role:<Discord role> emoji:<emoji>
```
Example:
```text
/reaction-role message_id:123456789012345678 role:@Game Updates emoji:🎮
```
The command:
1. Checks Manage Roles for the user
2. Finds the message in the **current channel**
3. Validates the role and bot hierarchy / permissions
4. Rejects integration-managed roles
5. Validates the emoji and adds the bots reaction
6. Saves the mapping in SQLite
7. Confirms with message, role, and emoji
### List
```text
/reaction-role-list
```
Shows all mappings for the current server in an embed (channel, message ID, emoji, role), with pagination when needed.
### Remove one mapping
```text
/reaction-role-remove message_id:<message ID> emoji:<emoji>
```
Deletes the mapping and attempts to remove **only the bots** reaction.
### Clear a message
```text
/reaction-role-clear message_id:<message ID>
```
Deletes every mapping for that message and attempts to remove the bots configured reactions.
## Custom emoji syntax
| Type | Example | Stored identifier |
| --- | --- | --- |
| Unicode | `🎮` | The character itself |
| Static custom | `<:gaming:123456789012345678>` | Custom emoji ID |
| Animated custom | `<a:dance:123456789012345678>` | Custom emoji ID |
To insert custom emoji markup in the command: in Discord, type `\:emojiName:` in chat to reveal the raw form, then paste it into the `emoji` option.
Custom emojis must be available to the bot (from a server the bot shares). Matching always uses the stored identifier so unicode and custom emojis stay consistent across restarts.
## Discord role hierarchy
Discord only allows a bot to assign roles **strictly below** its highest role. If assignment fails:
1. Open **Server Settings → Roles**
2. Move the bots role above the target role
3. Confirm the bot still has **Manage Roles**
4. Confirm the target role is not managed by an integration (boosts, bots, Linked Roles, etc.)
The bot also refuses to assign the `@everyone` role and roles managed by integrations.
## Project structure
```text
src/
commands/
reactionRoles/
createReactionRole.js
listReactionRoles.js
removeReactionRole.js
clearReactionRoles.js
events/
interactionCreate.js
messageReactionAdd.js
messageReactionRemove.js
ready.js
services/
reactionRoleService.js
database/
database.js
migrations.js
utilities/
emoji.js
permissions.js
logger.js
errors.js
loadCommands.js
loadEvents.js
reactionRoleHandler.js
config/
environment.js
deployCommands.js
index.js
```
Responsibilities are split across commands, events, services, database, utilities, and config so features stay isolated.
## Adding future commands
1. Create a new file under `src/commands/` (any subfolder).
2. Export a default object:
```javascript
import { SlashCommandBuilder } from 'discord.js';
export default {
data: new SlashCommandBuilder()
.setName('ping')
.setDescription('Replies with Pong'),
async execute(interaction) {
await interaction.reply('Pong!');
},
};
```
3. Run `npm run deploy-commands`.
4. Restart the bot if it is already running.
Commands are discovered automatically — no central switch statement to edit.
## Adding future event handlers
1. Create a file under `src/events/`.
2. Export a default object:
```javascript
import { Events } from 'discord.js';
export default {
name: Events.GuildCreate,
once: false,
async execute(guild) {
// ...
},
};
```
Set `once: true` for one-time events such as `ClientReady`. Events are registered automatically on startup.
## Troubleshooting
| Problem | What to try |
| --- | --- |
| Commands do not appear | Run `npm run deploy-commands`. For global commands, wait up to an hour, or set `DISCORD_GUILD_ID` for instant guild deploy. |
| “Message not found” | Use a message in the **same channel** where you run the command. Enable Developer Mode to copy the message ID. |
| `invalid ELF header` / better-sqlite3 crash | `node_modules` was installed on a different OS (e.g. Windows) then copied to Linux. On the **server**, run `rm -rf node_modules && npm install`. Do not copy `node_modules` between machines. |
| Roles are not assigned | Enable **Server Members Intent**. Ensure the bot role is above the target role and has Manage Roles. |
| Custom emoji rejected | The bot must be in a server that has that emoji. Paste full `<:name:id>` / `<a:name:id>` markup. |
| Duplicate mapping error | That emoji is already configured on the message — remove it first or pick another emoji. |
| Bot cannot react | Grant **Add Reactions**, **View Channels**, and **Read Message History** in that channel. |
| Database errors | Ensure the process can write to `DATABASE_PATH` (default `./data/`). |
| Login fails | Check `DISCORD_TOKEN` in `.env` (no quotes/spaces). Reset the token in the Developer Portal if needed. |
## Graceful shutdown
The bot handles `SIGINT`, `SIGTERM`, unhandled rejections, and uncaught exceptions. On shutdown it stops accepting new work, destroys the Discord client, closes SQLite, and logs completion.
## License
MIT