Repository
MammaMxir
Lets a group of friends control the music in a Discord voice chat from a web page, instead of typing one command at a time.
A Discord music queue you can actually see.
Private repository. Everything below is written from the code; none of it is a link you can follow.
What it is
A Spotify-powered music queue for Discord with a web interface. A bot joins voice; the web app is where people manage the queue — search, add, drag to reorder, vote to skip, or let a DJ mode pick what comes next.
Why it exists
Every Discord music bot has the same gap: the queue is a list you can only interrogate one command at a time. Reordering means removing and re-adding. Seeing what is next means spamming a command. And when a popular bot dies, which they periodically do, the replacement has the same interface. So: put the queue on a page, keep the bot for audio, and let a group manipulate a shared list directly.
As a business
None, deliberately. Discord music bots that touch Spotify audio live in a licensing grey area, which is why the large ones get shut down — not a category to build a business in. This is a tool built for a specific group, deployed properly, and left running.
Stack
- Client
- React · Vite · dnd-kit · Tailwind
- Bot
- discord.js voice · Docker
- API
- Spotify Web API · Edge functions
- Infra
- Caddy · docker-compose
The decision
The problem
Once a queue is directly manipulable by several people at once, it is concurrent shared state. Two people dragging simultaneously, someone reordering while the bot advances, a skip vote landing as the track ends. A command-driven bot never faces any of this, because commands serialise naturally.
What I did
Split the surfaces by what they own. The bot owns playback and is the authority on what is playing now; the web app owns the queue's order. Skipping is a vote rather than a command, which sidesteps the argument about who is allowed to skip by making it a group decision instead of a permission.
What it costs
Two sources of truth that have to agree, and a real chance of the displayed queue and the bot's actual next track diverging under load. That is the question I would ask about this project, and the answer is in the reconciliation between them.
Where it ended
Finished and running. It does what it set out to do and there is no obvious next feature, which is the correct end state for a utility.
A piece of the code
bot/src/player.ts
// Returns the vote state AFTER registering this voter.// When votes >= required, the track is skipped immediately.voteSkip( voterId: string, listeners: number,): { votes: number; required: number; skipped: boolean; alreadyVoted: boolean } { if (this.ap.state.status !== AudioPlayerStatus.Playing) { return { votes: 0, required: 0, skipped: false, alreadyVoted: false }; } const alreadyVoted = this.skipVoters.has(voterId); this.skipVoters.add(voterId); const required = Math.max(1, Math.ceil(listeners / 2)); const votes = this.skipVoters.size; if (votes >= required) { this.skip(); return { votes, required, skipped: true, alreadyVoted }; } return { votes, required, skipped: false, alreadyVoted };}Excerpt — trimmed for reading, not a full file.