All code

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 };}
Skipping is a vote rather than a permission, which sidesteps the argument about who is allowed to skip by making it a group decision. The threshold is derived from who is actually listening, not from a role, so it moves as people join and leave. The state is returned after the vote is registered so the caller can render the count it just changed rather than the one before it.

Excerpt — trimmed for reading, not a full file.

Ask about this project