Backend Overview
The backend is a TypeScript application that extends the Discord.js Client class into a central Manager orchestrator. This page describes the architecture, lifecycle, and major subsystems.
Entry Point (index.ts)
The process starts in src/index.ts:
- Reads
config.ymlviaConfigDataService. - Creates a
Managerinstance with the parsed configuration. - Calls
manager.start()to boot all services. - Attaches global anti-crash listeners for
unhandledRejectionanduncaughtException.
Manager Class (manager.ts)
Manager extends discord.js Client and is the root object. It holds references to every major service:
| Property | Type | Purpose |
|---|---|---|
config | Config | Parsed config.yml |
logger | LoggerService | Winston logger with daily rotation |
db | DatabaseService | Database abstraction (all table references) |
mewslink | Mewslink | Custom Lavalink v4 client |
commands | Collection<Command> | Loaded slash commands |
dashboardEmit | Function | Emits events to Socket.IO rooms |
Boot Sequence (Manager.start())
The start() method initializes services in this order:
- Handlers -
loadCommand,loadEvents,loadPlayer(registers commands, events, player events). - Database - Connects the configured driver, registers all tables, runs setup/migrations.
- Discord Login - Calls
client.login(TOKEN). - REST API - Starts Fastify on the configured port (default 2555). Registers the
DashboardPlugin. - WebServer - Starts Express on the configured port (default 2444). Registers webhook routes.
- Premium Services - Initializes
PatreonServiceandKofiServiceif enabled. - Notification Services - Initializes
TopggServiceif enabled. - Deploy - Registers slash commands globally via
DeployService.
Discord Gateway Intents
The Manager requests these privileged intents:
| Intent | Required For |
|---|---|
Guilds | Guild cache, channel access |
GuildMembers | Member events, statistics, voice state |
GuildMessages | Prefix commands, message content |
GuildVoiceStates | Voice channel join/leave detection |
MessageContent | Reading message text for prefix commands |
GuildPresences | Presence/activity tracking |
All three privileged intents (Presence, Server Members, Message Content) must be enabled in the Discord Developer Portal.
Mewslink (Custom Lavalink Client)
Mewslink is a custom Lavalink v4 client library built specifically for this project. It lives in src/mewslink/ and provides:
| Component | File | Purpose |
|---|---|---|
Mewslink | Mewslink.ts | Main class - manages nodes and players |
MewslinkPlayer | main.ts | Extended player with custom data store |
Manager/ | Directory | Node registry and player allocation |
Node/ | Directory | Individual node connection and communication |
Player/ | Directory | Player state, queue, filters, voice handling |
Drivers/ | Directory | Protocol drivers (Lavalink v4 REST + WebSocket) |
Library/ | Directory | Discord.js adapter for voice state forwarding |
Plugin/ | Directory | Plugin system for Lavalink plugins |
Player Data Store
Each MewslinkPlayer has a key-value data map used to store runtime flags:
| Key | Type | Purpose |
|---|---|---|
autoplay | boolean | Whether autoplay is active |
isMusicTrivia | boolean | Flags trivia players to bypass normal events |
sudoDestroy | boolean | Prevents reconnect on intentional destroy |
Database Layer (database/)
Driver Selection
DatabaseService reads features.DATABASE.driver from config and instantiates the matching driver:
| Value | Driver File | Library |
|---|---|---|
json | driver/json.ts | mewwme.quick.db/JSONDriver |
mongodb | driver/mongodb.ts | mewwme.quick.db/MongoDriver + mongoose |
mysql | driver/mysql.ts | mewwme.quick.db/MySQLDriver + mysql2 |
postgres | driver/postgres.ts | mewwme.quick.db/PostgresDriver + pg |
Table Registration
setup/table.ts registers all database tables. Each table is a QuickDatabasePlus instance with typed schema from schema/. The tables cover: guild settings, playlists, premium codes, user profiles, liked songs, trivia stats, session logins, AI chat history, and more.
Cache Layer
QuickDatabasePlus wraps mewwme.quick.db with an in-memory cache layer. Cache clearing is scheduled via the ClearCache cron expression in config (default: every 60 seconds).
Handlers (handlers/)
| Handler | File | Purpose |
|---|---|---|
| Command Loader | loadCommand.ts | Scans commands/ and buttons/, registers them in the commands collection |
| Event Loader | loadEvents.ts | Scans events/, attaches listeners to the Discord client |
| Player Loader | loadPlayer.ts | Scans events/player/ and events/track/, attaches listeners to Mewslink |
| Autofix Check | loadCheck.ts | Starts Lavalink auto-recovery if AUTOFIX_LAVALINK is enabled |
Command Handler (CommandHandler.ts)
Receives interactionCreate events and routes them:
- Resolves the command from the
commandscollection. - Checks access level against
COMMANDS_ACCESSconfig. - Checks premium requirements if applicable.
- Checks DJ role restrictions.
- Executes the command handler.
Services (services/)
| Service | File | Purpose |
|---|---|---|
ConfigDataService | ConfigDataService.ts | Parses config.yml with environment variable interpolation (${TOKEN} etc.) |
DeployService | DeployService.ts | Registers slash commands globally on Discord |
LoggerService | LoggerService.ts | Winston logger with console + daily rotating file transport |
ManifestService | ManifestService.ts | Reads package.json for version and name metadata |
PatreonService | PatreonService.ts | Manages Patreon pledges, webhook events, and guild redemption |
KofiService | KofiService.ts | Manages Ko-fi subscriptions and webhook processing |
TopggService | TopggService.ts | Tracks Top.gg votes and voter status |
MysqlBackup | MysqlBackup.ts | Scheduled MySQL dump to a Discord channel (cron-based) |
TempVoiceService | TempVoiceService.ts | Manages auto-created temporary voice channels |
FilterMenuService | FilterMenuService.ts | Builds select menus for audio filter selection |
MusicTriviaService | MusicTriviaService.ts | Music trivia game logic, scoring, badges, stats |
SpotifyTopChartService | SpotifyTopChartService.ts | Fetches global top chart data |
RecommendationService | RecommendationService.ts | Fetches song recommendations via Last.fm API |
Web Layer (web/)
The web layer runs two separate servers:
RestAPI (Fastify - port 2555)
- Internal
/v1/*API routes for bot control (player state, settings, search, moderation). - Requires
Authorizationheader matchingconfig.features.RestAPI.auth. - Enforces IP/domain whitelist from
config.features.RestAPI.whitelist. - Hosts the
DashboardPluginwhich provides:- Discord OAuth2 login flow (
/auth/discord,/auth/discord/callback). - Session management (Fastify session with configurable store).
/api/*proxy routes for the dashboard frontend.- Socket.IO server for real-time player state.
- Discord OAuth2 login flow (
WebServer (Express - port 2444)
/vote- Top.gg vote webhook with signature verification./kofi- Ko-fi payment webhook with token verification./lastfm- Last.fm OAuth callback for account linking.- Serves as the public-facing webhook receiver.
Lavalink Auto-Recovery
The autofix/ module provides automatic Lavalink failover:
CheckLavalinkServer- periodically pings configured Lavalink nodes.GetLavalinkServer- queries fallback node lists if primary nodes are down.AutoFixLavalink- reconnects or replaces failed nodes automatically.
Enabled via features.AUTOFIX_LAVALINK in config.
Logging
LoggerService uses Winston with two transports:
| Transport | Output | Rotation |
|---|---|---|
| Console | stdout | - |
| Daily Rotate File | logs/ directory | Daily, with configurable retention |
Log levels: error, warn, info, debug. Debug mode is toggled via bot.DEBUG_MODE in config.
Track Start & Now Playing
When a track begins playing, the trackStart.ts event handler runs a complete pipeline covering statistics, history, embed rendering, and interactive controls. This section documents the two embed modes and the recommendation system.
Event Flow
Track starts playing
|
v
1. Skip if isMusicTrivia player
2. Update music setup channel (if configured)
3. Update music status channel
4. Schedule Last.fm scrobble
5. Emit playerState to Socket.IO
6. Save auto-reconnect data (if enabled)
7. Increment global + guild play counters
8. Save listening history (per-user + per-guild, last 100 entries)
9. Start top stats session (60s listen rule)
10. Build and send now-playing message (V1 or V2)
11. Attach button collector for playback controlsIf the playback was triggered from the dashboard (dashboardPlayback flag), the event stops after step 9 and does not send a Discord message.
Embed Mode V1 - Music Card (Default)
When musicCard is enabled (default), the bot generates a visual music card image using the mewcard (opens in a new tab) library and sends it as an embed attachment.
The music card includes:
- Track title (truncated to 40 characters)
- Artist name (truncated to 15 characters)
- Album artwork thumbnail
- Requester display name
- Dynamic color extraction from artwork
- Configurable theme
EmbedBuilder
Author: localized "Now Playing" text
Description: Track title with hyperlink
Image: mewwme.png (generated card)
Fields:
- Author name + duration
- Volume + source/autoplay indicator
Components:
- Recommendation select menu (if available)
- Filter select menu
- Playback button row 1 (Shuffle, Previous, Pause, Skip, Loop)
- Playback button row 2 (Autoplay, Vol-, Stop, Vol+, Queue)Mewcard Themes
The music card theme is configurable per guild. The default theme is set via features.DEFAULT_MUSIC_CARD_THEME.Themes in config.yml (defaults to "Dynamic").
Users can customize the card by visiting the mewcard repository (opens in a new tab) and creating their own themes.
Source Detection
The embed displays a source indicator based on where the track was resolved from:
| Source | Display |
|---|---|
youtube | YouTube icon |
spotify | Spotify icon |
soundcloud | SoundCloud icon |
deezer | Deezer icon |
tidal | Tidal icon |
twitch | Twitch icon |
apple / applemusic | Apple Music icon |
youtube_music | YouTube Music icon |
http | HTTP icon |
| AutoPlay active | AutoPlay indicator |
Embed Mode V2 - Container (No Music Card)
When musicCard is disabled, the bot uses Discord's Components V2 ContainerBuilder for a lightweight text-based now-playing message.
ContainerBuilder (accent color: primary embed color)
Section:
- "Now Playing" header (localized)
- Track title + artist with search link
- Duration + requested by
Thumbnail: album artwork
Separator
Components:
- Recommendation select menu
- Filter select menu
- Playback button rowsV2 mode sends with MessageFlags.IsComponentsV2 and MessageFlags.SuppressNotifications. It does not generate an image, making it faster and lighter on resources.
Guild Player Embed Configuration
Each guild can customize the now-playing message via the dashboard. The configuration is resolved by GuildPlayerEmbedConfig.ts:
| Setting | Default | Description |
|---|---|---|
musicCard | true (if enabled in config) | Use V1 music card image or V2 text container |
filterMenu | true | Show the audio filter select menu |
buttonLabel | true | Show text labels on playback buttons |
recommendationMenu | true | Show the song recommendation select menu |
These settings are stored per-guild in the database and can be toggled individually.
Recommendation Select Menu
The now-playing message includes a "Recommended For You" select menu that suggests similar tracks based on the currently playing song.
How It Works
Current track (title + artist)
|
v
RecommendationService.ts
|
+---> Last.fm API: track.getSimilar (up to 15 similar tracks)
|
+---> Last.fm API: artist.getSimilar (up to 8 similar artists)
| |
| v
| Last.fm API: artist.getTopTracks (top tracks from similar artists)
|
v
Merge + deduplicate results (max 10)
|
v
Cache for 10 minutes
|
v
StringSelectMenu with track optionsRecommendation Logic
-
Similar Tracks - Calls
track.getSimilaron the Last.fm API with the current track name and artist. Returns up to 15 similar tracks. -
Similar Artists Fallback - If fewer than 10 recommendations are found from similar tracks, calls
artist.getSimilarto find up to 8 similar artists. Then fetches top tracks from the top 3 similar artists to fill the remaining slots. -
Deduplication - All recommendations are deduplicated by
trackName::artistName(lowercase) to avoid showing the same song twice. -
Caching - Results are cached in-memory by track+artist key for 10 minutes to avoid repeated API calls when the same song plays again.
-
Graceful Fallback - If no Last.fm API key is configured or the API call fails, the select menu is silently omitted from the message.
Requirements
- The
Last.fm API Keymust be configured inconfig.ymlunderfeatures.WebServer.LAST_FM_SCROBBLED.ApiKey. - The
recommendationMenumust be enabled in the guild's player embed config (enabled by default).
When a user selects a recommendation from the menu, the selected track's searchQuery (formatted as "Artist - Title") is used to search and add the track to the queue.
Playback Control Buttons
The now-playing message includes two rows of interactive buttons:
Row 1:
| Button | Custom ID | Action |
|---|---|---|
| Shuffle | shuffle | Shuffle the queue |
| Previous | replay | Replay the current or previous track |
| Pause/Play | pause | Toggle pause state |
| Skip | skip | Skip to the next track |
| Loop | loop | Cycle loop mode (off, track, queue) |
Row 2:
| Button | Custom ID | Action |
|---|---|---|
| Autoplay | autoplay | Toggle autoplay mode |
| Volume - | voldown | Decrease volume |
| Stop | stop | Stop playback and disconnect |
| Volume + | volup | Increase volume |
| Queue | queue | Display the current queue |
All button interactions require the user to be in the same voice channel as the bot. If not, an ephemeral error message is returned.
Control Button Toggle
Playback control buttons can be completely disabled per guild via the ControlButton database setting (configurable through the dashboard). When set to ControlButtonEnum.Disable, the trackStart event skips sending the now-playing message entirely.