A premium, high-quality, and open-source music bot for Fluxer.
Invite to Server · Report a Bug · Request a Feature
- About The Project
- How Audio Playback Works
- Features
- Getting Started (Users)
- Commands
- Self-Hosting
- Project Architecture
- The Layered Codebase
- Localization
- Scripts
- Credits
Remix is a free and open-source music bot for Fluxer, built with @fluxerjs/core for the Fluxer API and @fluxerjs/voice for LiveKit voice connections. Track search and streaming are handled by lavalink-client talking to a NodeLink audio node (Lavalink-compatible), and the bot publishes Opus audio to LiveKit as a participant — with a custom WebM/Opus pipeline (zero-re-encode passthrough and remux where possible).
We believe music features shouldn't be locked behind paywalls — all commands on Remix are 100% free and always will be.
Remix runs on @fluxerjs/core / @fluxerjs/util / @fluxerjs/voice 3.0.0 plus @fluxerjs/sharding 3.0.0 (see the changelog). The v3 DX overhaul required only five code-level changes in this codebase — everything else (event names, gateway opcodes, permission flags, the voice/LiveKit API, EmbedBuilder, message reply/edit/react, the raw VOICE_STATE_UPDATE / VoiceStatesSync payloads this bot tracks) is unchanged between 2.2 and 3.0:
package.json— the three@fluxerjs/*ranges moved from^2.2.0to^3.0.0(a^2.2.0range will not install 3.0).src/core/Bot.mjs— theClientpresenceoption now uses the normalized v3 shape (customStatus/emojiName/emojiId) instead of the 2.2 wire format (custom_status); the deprecated no-opsuppressIntentWarningoption was dropped.src/voice/gateway/GatewayHandler.mjs— the presence-rotation sender wraps itsclient.wsaccess in a try/catch, because 3.0 exposesclient.wsas a getter that throws when the gateway is not connected (2.2 exposed a plain optional). The rotation payload itself stays in wire format because it sends opcode 3 straight to the shard.src/ui/MessageHandler.mjs—joinChannel()checkschannel.isGuild?.()first (the new ChannelType-based guard) and keeps the legacy"guildId" in channelcheck as a fallback for stub objects.src/utils/ShardingUtils.mjs(new) — 3.0 removed the WebSocketManager'sshardsMap property in favour ofgetShards()/getShard()methods, which had silently disabled the bot's raw-socket listeners (they no-op'd behind their existing guards). All five readers — Bot.mjs's WS error handlers, GatewayHandler's raw listener and presence sender, LavalinkManager's payload routing and raw listener — now go through version-agnostic helpers that also make them shard-aware (see below).
Two 3.0 library-level notes that need no code change here: the WS manager no longer emits shardCreate (the bot's 5-second handler re-arm loop covers re-attachment), and Guild.description was removed from the core structures (the dashboard serializer already null-guards it, so server descriptions simply render empty). Passing intents or suppressIntentWarning inside config["fluxer.js"] is still accepted — both are documented deprecated no-ops in 3.0.
@fluxerjs/sharding 3.0.0 (beta) is wired in as an opt-in layer — the default single-process boot (npm start / node index.mjs) is byte-for-byte unchanged, and none of the sharding code paths activate unless the process was actually forked by the manager (it sets FLUXER_SHARD_IDS and provides an IPC channel; manually exporting the variables does nothing).
- Supervisor:
node shard.mjs(ornpm run shard) forks one child per shard slice, each running the ordinaryindex.mjsboot. The manager owns the shared per-IP IDENTIFY budget so children can never collectively exceed the gateway limit, respawns dead children, and exposesbroadcastEval/fetchClientValues/respawnAll. - Child side:
src/core/Bot.mjsattaches the library'sShardClientUtilbeforelogin()(it applies the shard slice to the client options) and callsnotifyReady()after login so the supervisor's spawn promise resolves. A failed attach under a manager is fatal by design — without its slice the child would identify as shard 0 like every other child and thrash the gateway sessions. - Guild→shard routing: opcode-4 voice payloads from LavalinkManager now target
shardIdForGuildId(guildId)instead of hardcoded shard 0, presence updates fan out to every gateway shard in the process, and raw-socket listeners attach to all local shard sockets. Unsharded, every one of these degrades to exactly the previous shard-0 behavior.
Configuration (config.json, all optional — defaults shown):
"sharding": { "totalShards": 1, "shardsPerProcess": 1, "respawn": true,
"spawnTimeout": 30000, "spawnDelay": 5000 }FLUXER_TOTAL_SHARDS / FLUXER_SHARDS_PER_PROCESS env vars override the config (useful in Docker, where you switch the container command to node shard.mjs). Prefer an explicit totalShards — Fluxer's /gateway/bot always reports shards: 1, so there is no reliable "auto".
Beta caveats worth knowing: DMs and guild-less events only reach shard 0 (library limitation); each child opens its own MySQL/Redis/Lavalink connections and its own dashboard RPC subscription, so the dashboard is currently single-process-first — guilds on other shards won't be reachable from it, and concurrent storage/stats.json writes across children can race. The stock node index.mjs deployment has none of these issues.
Understanding the pipeline helps when debugging or contributing:
%play → LavalinkManager.search() ──► NodeLink (track resolution only)
→ Player queue → FluxerAudioBridge.play(voiceConnection, track)
├─ /v4/trackstream → direct WebM/Opus passthrough (no re-encode)
├─ /v4/loadstream → magic-byte sniffing:
│ • WebM (1A45DFA3) → passthrough to LiveKit
│ • OggS → OggDemuxer → WebMOpusMuxer (remux, no re-encode)
│ • raw PCM → OpusEncoder (opusscript)
│ → WebMOpusMuxer → LiveKit
└─ conn.play() → @fluxerjs/voice LiveKit connection (bot publishes audio
as a LiveKit participant)
A few things worth knowing:
- No Lavalink players are created. NodeLink is used for search and its REST stream/lyrics endpoints (
/v4/loadstream,/v4/trackstream,/v4/loadlyrics); playback itself is pure LiveKit publishing. - Seek / pause / resume work by stopping the current stream and re-requesting it at an offset from NodeLink.
- Filters (bassboost, nightcore, etc.) are applied server-side by NodeLink, so they take effect on the next track that starts.
- Volume is applied client-side by the LiveKit connection (1–200).
- Radio metadata (StreamTitle) is read with ffprobe (
ffprobe-static). - Bilibili playback (fully anonymous — no cookies) —
%play https://www.bilibili.com/video/BV…(b23.tv short links and?p=parts included) resolves the video through Bilibili's web API and feeds it to the node through a signed localhost proxy that attaches the Referer/User-Agent headers Bilibili's CDN requires. Multi-part videos queue like a playlist. Requests use Bilibili's WBI-signed endpoints (derived anonymously from the nav payload), which survive the HTTP 412 risk-control blocks that reject plain unsigned API calls on many hosting IPs; on any WBI failure the bot automatically retries through the legacy unsigned endpoints. Playurl calls carry the anonymoustry_look=1flag and prefer the best DASH audio stream (~130–170 kbps AAC) — real DASH audio works without login — falling back to the html5 progressive mp4 for videos without DASH audio, and multi-segment progressive streams are served back-to-back through the proxy's concatenating mode with Range/seek support. The bot generates and manages its Bilibili device fingerprint (buvid3/buvid4/b_nut) automatically and retries once with a fresh fingerprint when risk control interferes. The proxy binds127.0.0.1by default — setbilibili.advertiseHost(andbind: "0.0.0.0") when your node runs on another machine.
- High-quality audio playback — NodeLink streaming with a zero-re-encode WebM/Opus pipeline, published over LiveKit
- Multi-source search — YouTube, YT Music, Spotify, SoundCloud, Deezer, Apple Music, Tidal, Bandcamp and 40+ more provider prefixes, plus direct URLs and Bilibili videos
- 24/7 mode — keep the bot in a voice channel permanently, with staggered auto-rejoin on boot and rejoin retries on connection loss
- Interactive emoji player — reaction-based control panel with live progress, lyrics viewer, and a filter submenu
- Lyrics — synced lyrics via NodeLink
- Radio stations — built-in support for custom radio streams with keyword-based search
- Last.fm integration — account linking, scrobbling, now-playing, play loved/top/recent/albums, whoknows, crowns, compare, leaderboards, and profiles
- Autoplay — automatically play similar tracks when the queue ends (powered by Last.fm)
- Seek — jump to a specific position in the current track
- Track options — set custom start/end times per track, great for album compilations and hidden tracks
- Queue move — reorder tracks by moving them to a different position
- Audio filters — bassboost, speed, nightcore and more (applied server-side by NodeLink)
- Server settings — per-guild configuration (prefix, volume, locale, 24/7 channels, …) stored in MySQL
- Dashboard backend — optional Redis-RPC backend that an external web frontend uses to monitor players and control playback remotely
- Multi-language support — English, Arabic, German, Kurdish (Sorani), and Brazilian Portuguese
- Configurable logging — granular control over which log categories appear in the console
- Graceful shutdown — destroys players and closes MySQL/Redis/NodeLink sessions cleanly on SIGINT/SIGTERM/SIGUSR2
- Module system — pluggable module architecture for extending bot functionality (
storage/modules.json)
Want to use Remix in your server right away?
- Invite Remix to your Fluxer server.
- Join a voice channel.
- Use the
%helpcommand to see everything the bot can do, or jump straight in with%play <song name>.
Below is the complete list of Remix's commands. The default prefix is %.
| Command | Description | Usage | Aliases |
|---|---|---|---|
play |
Play a song from a URL, search query, or playlist | %play Never Gonna Give You Up / %play lastfm:loved |
p |
playnext |
Add a song/playlist to the top of the queue | %playnext query: text |
pn |
pause |
Pause the current playback | %pause |
|
resume |
Resume the paused playback | %resume |
|
skip |
Skip the currently playing song | %skip |
s |
np |
Show the currently playing song | %np |
current, nowplaying |
list |
View the upcoming queue | %list |
queue, q |
loop |
Toggle loop mode (song or queue) | %loop queue |
|
shuffle |
Randomize the queue order | %shuffle |
|
remove |
Remove a specific song by its queue index | %remove 3 |
|
clear |
Clear the entire queue | %clear |
c |
volume |
Change the playback volume (1–200) | %volume 50 |
v, vol |
volumedefault |
Set the default volume for the server | %volumedefault 80 |
vd |
search |
Search for a track and pick from results | %search query |
|
lyrics |
Display synced lyrics from NodeLink | %lyrics |
lyric, ly |
thumbnail |
Get the thumbnail of the current track | %thumbnail |
thumb |
radio |
Play a built-in or custom radio station | %radio |
r |
filter |
Manage audio filters (bass, speed, nightcore, etc.) | %filter bass 50 |
filters, fx, effect |
player |
Create an interactive emoji control panel with live progress | %player |
|
join |
Make the bot join a specific voice channel | %join 123456789 |
|
leave |
Make the bot leave the current voice channel | %leave |
l, stop |
forceleave |
Force the bot to leave any channel (requires Manage Channels) | %forceleave |
fl |
seek |
Seek to a specific position in the current track | %seek 1:30 / %seek 90 |
|
move |
Move a track from one position to another in the queue | %move 2 5 |
mv, m |
autoplay |
Toggle autoplay — automatically play similar tracks when queue ends | %autoplay |
ap |
trackopt |
Set custom start/end times for tracks | %trackopt set 0:30 3:45 |
to |
| Command | Description | Usage | Aliases |
|---|---|---|---|
settings |
View or change server settings (requires Manage Server) | %settings set |
prefix, pfx, 247 |
stats |
Display bot stats (uptime, ping, player count, stored scrobbles) | %stats |
info |
invite |
Get the bot invite link | %invite |
addbot, remix |
support |
Get an invite to the support server | %support |
server |
lastfm |
Link Last.fm, toggle scrobbling, view profile, love/unlove tracks, top artists, play tracks, leaderboard | %lastfm link / %lastfm love / %lastfm artists / %lastfm lb |
lf, lfm |
vote |
Check FluxerList voters for the bot | %vote |
|
reload |
Reload commands or modules at runtime (owner) | %reload |
|
servers |
List servers the bot is in (owner) | %servers |
|
eval |
Evaluate JavaScript (owner only) | %eval 1+1 |
|
debug |
Debug voice connections and player state (owner) | %debug voice |
|
test |
Show voice channel user counts (owner) | %test |
If you prefer to host Remix yourself, please note: You must make it clear that your bot is an instance of Remix. Change the bot's name and give credit in the bot's profile (e.g., "Powered by Remix").
The fastest way to self-host Remix is with Docker. Everything — the bot, MySQL, Redis, and NodeLink — runs in containers with a single command. All Docker files live in the docker/ folder.
-
Clone and configure:
git clone https://github.com/remix-bot/fluxer.git cd fluxer/docker cp config_example.json config.json cp .env.example .env # optional — compose has working defaults
-
Edit
config.json— fill in your bot token, MySQL credentials (defaults match the compose MySQL service), NodeLink details (defaults match the compose NodeLink service), and your owner IDs. Spotify/Deezer/Apple Music credentials are configured on the NodeLink side (nodelink.config.json), not in the bot config. -
Edit
.env(optional) — MySQL passwords, host port mappings (WEB_PORT,NODELINK_PORT), and timezone. -
Start everything:
docker compose up -d
-
Check logs:
docker compose logs -f bot
That's it. The bot will start, connect to MySQL (Last.fm and track-options tables are auto-created), connect to NodeLink, and log in to Fluxer.
docker/
├── Dockerfile # Multi-stage build (Node 22 + tini, non-root user, healthcheck)
├── docker-entrypoint.sh # Writes config.json from CONFIG_JSON env var on first boot
├── docker-compose.yml # bot + MySQL + Redis + NodeLink
├── .env.example # Compose env template (MySQL creds, ports, TZ)
├── config_example.json # Docker-friendly config template
├── config.json # You create this (gitignored)
├── .env # You create this (gitignored)
└── nodelink.config.json # NodeLink audio node config
| Service | Container | Port | Purpose |
|---|---|---|---|
bot |
remix-bot | ${WEB_PORT:-8080} → 80 |
The Remix bot (+ optional dashboard backend) |
mysql |
remix-mysql | — | Settings, Last.fm users, and track options storage |
redis |
remix-redis | — | Dashboard RPC pub/sub (optional) |
nodelink |
remix-nodelink | ${NODELINK_PORT:-3000} |
Lavalink-compatible audio node |
# Run from the docker/ folder
cd docker
# Start all services
docker compose up -d
# View live bot logs
docker compose logs -f bot
# Restart the bot
docker compose restart bot
# Stop everything
docker compose down
# Stop and delete data volumes (full reset)
docker compose down -v
# Rebuild after code changes
docker compose up -d --build botIf you prefer to keep your config in an environment variable (useful for CI/CD or secret managers), set CONFIG_JSON in your .env:
CONFIG_JSON={"token":"YOUR_TOKEN","mysql":{"host":"mysql","port":3306,"user":"remix","password":"remix_pw","database":"remix"},"nodelink":{"host":"nodelink","port":3000,"password":"youshallnotpass"}}The entrypoint will write it to /app/config.json on first boot if no config file is mounted.
- Node.js >= 22.13.0
- MySQL 8.0+ with JSON column support
- NodeLink instance (Lavalink-compatible audio node)
- Redis (optional — required only for the dashboard backend)
-
Clone the repository:
git clone https://github.com/remix-bot/fluxer.git cd fluxer -
Install dependencies:
npm install
-
Configure the bot:
cp config_example.json config.json
Open
config.jsonand fill in the required values:token— your Fluxer bot tokenmysql— your MySQL connection details (host, port, user, password, database)prefix— the command prefix (default:%)nodelink— your NodeLink instance connection detailslastfm— (optional) Last.fm API credentials for scrobbling/autoplay featuresowners— array of Fluxer user IDs with owner-only command access
-
Set up the database: (See Database Setup below)
-
Start the bot:
npm start
For development with inspector:
npm run dev
Remix requires a MySQL database to store per-guild settings and user data.
-
Create a dedicated database for Remix:
CREATE DATABASE remix;
-
Enter your MySQL connection details into
config.json:"mysql": { "host": "localhost", "port": 3306, "user": "remix", "password": "your-password", "database": "remix" }
-
Create the required
settingstable:CREATE TABLE `settings` ( `id` varchar(70) NOT NULL, `data` json NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
-
Everything else is auto-created on startup if missing:
track_options— per-user per-track start/end times (%trackopt)lastfm_users— Last.fm session keys and scrobble opt-inslastfm_stats— stored scrobble/link counts
-
(Optional) If you need to clone or repair the settings table across bot IDs, run:
npm run migrate
Remix ships the backend half of a web dashboard: a Redis-RPC service that an external frontend project talks to. There is no HTTP server or web UI in this repository.
-
Enable it in
config.json:"dashboard": { "enabled": true, "redis": { "url": "redis://localhost:6379" } }
-
How it works:
- The bot listens on Redis pub/sub channels (
request/response/info) and answers JSON-RPC style requests with anidfor correlation. - Supported requests:
fetchPlayers,user,sharedServers,server,allServers,commands, andfunction(remote actions:join,pausePlayback,resumePlayback,skip,volume,addToQueue,voiceState,leave,testConnection). - Player updates are broadcast (debounced) on per-bot/per-player Redis channels so the frontend can render live state.
- Login flow: the external frontend writes login codes into the MySQL
login_codestable; the bot verifies them with bcrypt hashes and marks them verified. Player control additionally requires the user to be in the same voice channel (owners are exempt).
- The bot listens on Redis pub/sub channels (
-
Security note: the RPC channel has no shared secret — anything that can publish to your Redis can invoke the remote actions. Keep Redis network-isolated (as the Docker setup does) and don't expose it publicly.
Key configuration options in config.json:
| Option | Type | Default | Description |
|---|---|---|---|
token |
string | — | Required. Fluxer bot token |
prefix |
string | % |
Default command prefix |
embedColor |
string | 0xe9196c |
Hex color for embed messages |
owners |
string[] | [] |
User IDs with owner privileges |
playerAFKTimeout |
number | 60000 |
Inactivity timeout in ms before the player panel session ends |
customStatsFooter |
string | — | Custom text shown in the %stats embed footer |
presenceInterval |
number | 30000 |
Interval in ms for rotating bot presence status |
presenceContents |
array | [] |
Presence status messages to cycle through (strings or objects with text/emoji_name/emoji_id/activity) |
mysql |
object | — | Required. MySQL connection settings |
nodelink |
object | — | NodeLink connection (host, port, password, requestTimeout) |
lastfm |
object | — | Last.fm integration (apiKey, apiSecret, scrobbleThreshold, scrobbleMinMs) |
fluxerlist |
object | — | FluxerList integration (apiKey, serverId, botId, serverSlug, botSlug) |
dashboard |
object | — | Dashboard backend: enabled, redis.url |
radio |
array | [] |
Custom radio station definitions |
logging |
object | — | Per-category log toggles: enabled, warn, and 14 categories (player, inactivity, aloneCheck, voiceState, voice247, voice, mediaplayer, commands, guild, recovery, settings, lavalink, dashboard, redis) |
timers |
object | — | Timing values in ms: inactivityTimeout, aloneCheckInterval, aloneCheckDebounce, rejoin247Delay, leave247RejoinDelay, playerUpdateInterval, searchSessionTimeout, playerSessionTimeout, intentionalLeaveTTL |
fluxer.js |
object | — | Fluxer.js REST options (timeout, retries) |
fluxer/
├── index.mjs # Entry point — creates the Remix instance, process-level
│ # error guards, and signal-based graceful shutdown
├── shard.mjs # OPTIONAL sharding supervisor (@fluxerjs/sharding) — forks
│ # one child process per shard slice, each running index.mjs
├── config_example.json # Configuration template
├── package.json
├── commands/ # 37 command entry files (one per command); the three
│ │ # largest (lastfm, settings, debug) are split into action-family
│ │ # modules inside commands/<name>/ subdirectories
│ ├── lastfm/ # lastfm action families (account, playback, listening,
│ │ # whoknows, info, tags, discovery, help, shared)
│ ├── settings/ # settings helpers (utils, channels247, setters)
│ └── debug/ # debug helpers (consts, gateway, rejoin, voiceDiagnostic)
├── settings/ # Settings system entry points (re-export, migrate, validators)
├── storage/ # Runtime data: defaults, modules.json, locales
├── docker/ # Docker self-hosting (Dockerfile, compose, entrypoint,
│ # config templates, .env.example)
└── src/
├── core/ # Bot composition
│ ├── Bot.mjs # Remix class — boot sequence, service wiring, alone-check,
│ │ # presence rotation, WebSocket error guards
│ ├── BotVoiceMixin.mjs # Voice-channel resolution, 24/7 player spawning with
│ │ # announcements, programmatic leave, shared servers
│ ├── Logger.mjs # Structured logger with per-category control
│ └── Locale.mjs # i18n translation engine
├── commands/ # Command framework layer
│ ├── index.mjs # Public surface (re-exports)
│ ├── CommandBuilder.mjs # Fluent builder + permission requirements
│ ├── Option.mjs # Typed options + flags with validation
│ ├── CommandHandler.mjs # Dispatcher: parsing, cooldowns, permission checks
│ ├── CommandLoader.mjs # Dynamic command loading + run-handler binding
│ ├── HelpHandler.mjs # Text help generation (usage/aliases/options)
│ └── PrefixManager.mjs # Per-guild prefix resolution
├── ui/ # Message/embed layer
│ ├── index.mjs # Public surface (re-exports)
│ ├── MessageHandler.mjs # Reply/send/edit embeds, reaction + message observers,
│ │ # permission checks, pagination entry
│ ├── Wrappers.mjs # Message & Channel wrappers
│ ├── Paginators.mjs # PageBuilder, RichPaginator (tabbed), QueuePaginator
│ ├── HelpCommand.mjs # Rich tabbed help command (Home/Music/Utilities/Support)
│ ├── Permissions.mjs # Required/critical/optional bot permission catalog
│ └── Embeds.mjs # Global embed color + message-shape helpers
├── voice/ # Gateway & voice-state layer
│ ├── VoiceStateCache.mjs # Dual LRU voice-state caches (humans / bots)
│ ├── VoiceStateResolver.mjs # Voice-state normalization + humans-in-channel check
│ └── gateway/
│ ├── index.mjs # Public surface
│ ├── GatewayHandler.mjs # Raw WS dispatch, presence rotation, boot recovery
│ ├── VoiceStateRouting.mjs # Voice-state update handling, inactivity triggers
│ ├── GuildSync.mjs # Guild seeding (cache + REST), guild delete
│ └── RejoinManager.mjs # 24/7 rejoin scheduling with retries
├── music/ # Player + audio engine layer
│ ├── PlayerManager.mjs # Player lifecycle & registry, getPlayer/initPlayer
│ ├── PlayerEventsMixin.mjs # Per-player event wiring, scrobbles, broadcasts
│ ├── PlayerLifecycleMixin.mjs# Voice checks, prompts, joins, leaves
│ ├── LavalinkManager.mjs # lavalink-client wrapper (NodeLink mode)
│ ├── probe.mjs # ffprobe wrapper for radio stream metadata
│ ├── providers.mjs # 45+ audio source provider definitions
│ ├── player/
│ │ ├── index.mjs # Public surface (default export: Player)
│ │ ├── Player.mjs # Core state machine: join/leave/destroy, controls, 24/7
│ │ ├── Queue.mjs # Queue data structure with loop modes + events
│ │ ├── PlaybackMixin.mjs # playNext pipeline, track-end timers, lyrics
│ │ ├── SearchMixin.mjs # Lavalink search, fallbacks, radio/external builders
│ │ └── DisplayMixin.mjs # Now-playing, queue listing, announcements
│ └── audio/
│ ├── index.mjs # Public surface
│ ├── FluxerAudioBridge.mjs # NodeLink streams → LiveKit publishing core
│ ├── StreamPipeline.mjs # Magic-byte sniffing, remux, Opus encode
│ ├── HttpStreams.mjs # Redirect-following HTTP JSON/stream helpers
│ └── WebMOpusMuxer.mjs # Streaming EBML/Matroska muxer (Opus → WebM)
├── services/ # Integrations
│ ├── FluxerListManager.mjs # FluxerList voters API client (TTL cache)
│ ├── TrackOptionsManager.mjs # Per-user per-track start/end times (MySQL + LRU)
│ └── lastfm/
│ ├── index.mjs # Public surface
│ ├── LastFmManager.mjs # Base: config, MySQL pool, shared helpers
│ ├── UserStoreMixin.mjs # Linking, sessions, scrobble opt-ins
│ ├── ScrobblingMixin.mjs # Scrobble + now-playing
│ ├── TrackQueriesMixin.mjs # Track/artist/album/tag/geo/chart queries
│ ├── UserQueriesMixin.mjs # User tops, charts, playlists, play categories
│ ├── ServerStatsMixin.mjs # Whoknows, crowns, leaderboards, affinity
│ ├── constants.mjs # Signed API-call plumbing
│ └── urlUtils.mjs # lastfm:// URL parsing
├── db/ # Persistence
│ ├── Settings.mjs # SettingsManager / ServerSettings / RemoteSettingsManager
│ │ # (MySQL-backed, debounced JSON_SET writes)
│ └── DatabaseManager.mjs # mysql2 pool + parameterized queries + bcrypt helpers
├── utils/ # Cross-layer primitives (leaf layer)
│ ├── mixins.mjs # applyMixins — god-class splitting helper
│ ├── ShardingUtils.mjs # Shard-aware gateway helpers (2.2/3.0 compatible,
│ │ # guild→shard routing, local shard enumeration)
│ ├── Utils.mjs # Formatting, validation, ID cleaning
│ ├── API.mjs # REST call helpers (status/error wrapping)
│ ├── UI.mjs # Shared UI constants (colors)
│ └── Helpers247.mjs # 24/7 channel-mode helpers
└── dashboard/ # Dashboard backend
├── index.mjs # Public surface
├── Dashboard.mjs # Redis RPC routing, player/user broadcasts
├── RpcHandlersMixin.mjs# Request handlers + authorization checks
├── Serializers.mjs # Channel/user/player/command serializers (statics)
└── RedisHandler.mjs # Redis pub/sub RPC transport with reconnect handling
The rewrite organises the codebase into strict layers with one-way dependencies (no layer reaches down past another):
index.mjs
└── core/ ──────────────► composition root: wires every layer together
├── commands/ ─────► command framework (parsing, cooldowns, help)
│ └── ui/ ─────► message/embed primitives used by the framework
├── ui/ ──── message wrappers, paginators, permissions
├── voice/ ──── gateway events, voice-state caches, 24/7 rejoin
├── music/ ──── players, queue, audio engine, Lavalink client
├── services/ ──── Last.fm, FluxerList, track options
├── db/ ──── MySQL settings + dashboard database pool
└── dashboard/ ──── Redis-RPC backend
God classes were eliminated. The five largest modules (Player, PlayerManager,
GatewayHandler, FluxerAudioBridge, LastFmManager, Dashboard) are each split into a
small base class plus concern mixins — one file per responsibility, applied onto
the class at load time via src/utils/mixins.mjs (applyMixins). All methods keep
the same names, so callers (including your own command files and modules) are
unaffected.
Big commands are grouped the same way. The three commands that outgrew a
single file (lastfm 2.5k lines, settings 705, debug 669) keep their entry
file at commands/<name>.mjs (same path the loader, %reload and help system
reference) and move their implementation into commands/<name>/ — one module per
action family, dispatched by the entry's run() via per-module action sets:
// commands/lastfm.mjs (entry) — dispatch
if (ACCOUNT_ACTIONS.has(action))
return runAccountActions.call(this, msg, data, lastfm, prefix, userId, targetUserId, action);The loader only reads top-level *.mjs files, so the subdirectories are invisible
to command discovery; case bodies and helpers were moved verbatim (verified by
per-command runtime parity harnesses under scripts/), and every entry file keeps
its original exports (playLastFmCategory is still importable from
commands/lastfm.mjs). commands/player.mjs intentionally stays single-file: it
is one cohesive interactive UI flow (a single 500-line run() closure) with no
seams that could be split without rewriting behavior.
Public surfaces are barrels. Each layer folder has an index.mjs that
re-exports its public API, so imports stay short and implementation files can move
without touching consumers:
import { CommandBuilder } from "../src/commands/index.mjs";
import { Message, getGlobalColor } from "../src/ui/index.mjs";
import Player from "../src/music/player/index.mjs";
import { LastFmManager } from "../src/services/lastfm/index.mjs";Compatibility is drop-in: config.json keys, the MySQL schema, the Redis RPC
protocol, command names/aliases, locale files, and the Docker entrypoint
(node index.mjs at the repository root) are all unchanged from the previous
single-layer layout.
Remix supports multiple languages out of the box. The locale system loads JSON translation files from storage/locales/bot/ and serves the appropriate language based on each guild's locale setting.
Currently supported languages:
| Code | Language |
|---|---|
en |
English (default) |
ar-SA |
Arabic |
de-DE |
German |
ckb |
Kurdish (Sorani) |
pt-BR |
Brazilian Portuguese |
tr |
Turkish |
To add a new language, place a JSON file in storage/locales/bot/ following the same key structure as en.json, then set the locale per guild with %settings set locale <code>.
| Script | Command | Description |
|---|---|---|
npm start |
node index.mjs |
Start the bot (single process — default) |
npm run shard |
node shard.mjs |
Start under the sharding supervisor (optional) |
npm run dev |
node --inspect index.mjs --trace-warnings |
Start with Node.js inspector |
npm run migrate |
node settings/migrate.mjs |
Clone/repair the remote settings table |
Development:
- ShadowLp174 — Lead developer
- NoLogicAlan — Lead developer
- Fantic — Community Manager
Powered by:
@fluxerjs/core— Fluxer API client@fluxerjs/voice— LiveKit voice connections and playbacklavalink-client— Lavalink/NodeLink client (search + streaming)NodeLink— Lavalink-compatible audio nodeprism-media— Opus encoding and stream demuxing
© 2026 Remix. Code licensed under the MIT License.
The Remix name, logo, and branding are proprietary and may not be reused.