Skip to content

standleypg-dev/LocalRadioAndMusicDiscordBot

Repository files navigation

Discord Music Bot

A .NET 10 Discord bot for music playback, supporting YouTube, SoundCloud, Spotify (playlist metadata), and radio streams. Supports multiple servers concurrently - each server gets its own queue, player, and voice connection. Includes a React-based web dashboard for statistics and radio-source management.

Note: This application is designed to run on Linux via Docker. A Dockerfile and docker-compose setup are provided.

Installation

  1. Create a Discord bot and add it to your server. Follow the Discord Developer docs.
  2. Set up the necessary bot permissions. See Permissions.
  3. Obtain a Discord bot token. See OAuth2 Bots.
  4. Clone this repository.
  5. Configure deployment/.env with your values (Discord token, Spotify credentials, database, JWT, etc.).
  6. Build and run with Docker:
    docker-compose -f deployment/docker-compose.yml up --build
    

Development

# Build the whole solution (requires Bun for the frontend build)
dotnet build discord-project.slnx

# Run all tests (integration tests need a local Docker daemon for Testcontainers)
dotnet test src/Tests/Tests.csproj

# Unit tests only (no Docker required)
dotnet test src/Tests/Tests.csproj --filter "FullyQualifiedName~Tests.Unit"

Tests run automatically in CI (.github/workflows/ci.yml) on pushes and pull requests to master; pushes to master are deployed via .github/workflows/release.yml.

Architecture

One bot process serves many servers. Commands address a guild through IGuildMusicService; GuildPlayerManager lazily creates one GuildPlayer per guild, each with its own queue, consumer loop, voice client, and FFmpeg process, so playback in one server never affects another. Statistics and the blacklist are shared across all servers.

flowchart TD
    UA["User in Server A"] -->|"/play /skip /stop"| CMD
    UB["User in Server B"] -->|"/play /skip /stop"| CMD

    subgraph Worker["Worker process"]
        CMD["Slash commands + NetCordInteraction"]
        CMD -->|"Enqueue(guildId, PlayRequest)"| MGR
        CMD -->|"EventType.Skip / Stop (GuildId)"| PH["EventDispatcher -> PlayerHandler"]
        PH -->|"Skip(guildId) / Stop(guildId)"| MGR
        MGR["GuildPlayerManager<br/>(IGuildMusicService)<br/>one GuildPlayer per guild"]

        subgraph PA["GuildPlayer - Server A"]
            QA["MusicQueueService<br/>(list + channel signal)"]
            LA["Consumer loop<br/>(retry x3, per-track cancellation)"]
            AA["AudioPlayerService<br/>(voice client A)"]
            FA["FfmpegProcessService<br/>(ffmpeg process A)"]
            QA -->|"signal wakes"| LA
            LA -->|"PlayTrackAsync"| AA
            AA -->|"spawn"| FA
        end

        subgraph PB["GuildPlayer - Server B"]
            QB["MusicQueueService"]
            LB["Consumer loop"]
            AB["AudioPlayerService<br/>(voice client B)"]
            FB["FfmpegProcessService"]
            QB -->|"signal wakes"| LB
            LB -->|"PlayTrackAsync"| AB
            AB -->|"spawn"| FB
        end

        MGR -->|"guild A ops"| QA
        MGR -->|"guild B ops"| QB

        SS["Stream resolvers<br/>yt-dlp / YoutubeExplode /<br/>SoundCloud / radio URL"]
        DB[("PostgreSQL<br/>stats + blacklist<br/>shared by all servers")]
        AA -.->|"resolve stream URL"| SS
        AB -.-> SS
        AA -.->|"log play"| DB
        AB -.-> DB
    end

    FA -->|"PCM to Opus stream"| VA(("Voice channel<br/>Server A"))
    FB -->|"PCM to Opus stream"| VB(("Voice channel<br/>Server B"))
Loading

Technologies Used

Backend

Frontend

About

Local Radio and Music Discord Bot Player

Topics

Resources

Stars

3 stars

Watchers

1 watching

Forks

Contributors