gene

mix dis if

2026Elixir · BEAM · Phoenixactive

The always-on half — gateway, queue, routing — runs as a controller on Fly. The downloading happens on nodes: machines people own, wherever they are, because YouTube blocks datacenter addresses and a signed-in browser profile is personal.

Every hosted music bot eventually hits the same wall: YouTube blocks datacenter IPs, and the credentials that would unblock it are personal. This splits along that line instead of fighting it.

how it works

Discord ──► controller (Fly) ◄── wss ── node (your laptop) ──► YouTube
              │                 ◄── wss ── node
              └──► session dashboard, in a browser

The controller holds the bot token, the node registry, the dispatcher, and one playback session per guild. It is not Discord-specific: a bot is an adapter implementing one behaviour, so the same controller can front something else. A node is a machine someone owns; it dials out, serves search, metadata and download jobs, manages its own yt-dlp and ffmpeg, and exposes a control page on 127.0.0.1:7070. Nothing ever dials a node.

Adding one takes no terminal: run /dashboard in the server, press Add a node, and type the single-use MDI-XXXX-XXXX pairing code into the app you downloaded. Codes are minted only by a session that already controls that guild, and a machine keeps a random instance id, so re-pairing it replaces its credential instead of creating a second node.

A node ships as one file per platform — macOS arm64 and x86_64, Linux, Windows — with no Erlang to install, because dropping the Discord dependency leaves no native code but the runtime itself.

status

Controller and node both running, 565 and 350 tests between them. The macOS build is signed and notarised, so Gatekeeper accepts it with no quarantine workaround. Audio reaches the controller’s cache over the fleet; playing it into a voice channel through that path has not been run end to end. The binaries are not published anywhere public yet, and the Windows build is unsigned.

The three repositories are mid-split — controller, node and bot adapter still share two checkouts — and the code carries the project’s previous name in places.