You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CLAUDE.md
+73-41Lines changed: 73 additions & 41 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -4,87 +4,119 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
4
4
5
5
## Project Overview
6
6
7
-
Discord Music Bot - A .NET 10 application that provides music playback functionality for Discord servers. Supports various audio sources, Spotify (for playlist fetching), and radio streams.
7
+
Discord Music Bot - A .NET 10 application that plays music in Discord voice channels from YouTube, SoundCloud, Spotify (playlist metadata only), and radio streams. Ships with a React dashboard for stats and radio-source management. Designed to run on Linux via Docker; native voice dependencies (libdave, libsodium, libopus) make local Windows runs impractical.
8
8
9
9
## Build & Run Commands
10
10
11
11
```bash
12
-
#Build and run with Docker (recommended)
12
+
#Recommended: build and run everything (bot + Postgres) via Docker
13
13
docker-compose -f deployment/docker-compose.yml up --build
14
14
15
-
# Build .NET solution
15
+
# Build the whole solution (uses .slnx format)
16
16
dotnet build discord-project.slnx
17
17
18
-
# Run the Worker (main entry point)
18
+
# Run the Worker (composition root, hosts Discord bot + web API on :5000)
19
19
dotnet run --project src/Worker/Worker.csproj
20
20
21
-
#Run with specific configuration
21
+
#Release configuration
22
22
dotnet run --project src/Worker/Worker.csproj -c Release
23
23
24
-
#Frontend dev server (from src/UI/App)
25
-
bun run dev
24
+
#Run tests (unit tests always; integration tests need a local Docker daemon for Testcontainers)
25
+
dotnet test src/Tests/Tests.csproj
26
26
27
-
# Frontend production build
28
-
bun run build
27
+
# Unit tests only (no Docker required)
28
+
dotnet test src/Tests/Tests.csproj --filter "FullyQualifiedName~Tests.Unit"
29
+
```
30
+
31
+
Frontend (run from `src/UI/App/`, Bun is the package manager):
29
32
30
-
# Frontend development build
31
-
bun run build:dev
33
+
```bash
34
+
bun run dev # Vite dev server
35
+
bun run build # Type-check + production build (output: src/Worker/wwwroot/)
36
+
bun run build:dev # Same, but development mode
37
+
bun run lint # ESLint
38
+
bun run format # Prettier write
39
+
bun run format:check # Prettier check
32
40
```
33
41
42
+
Tests live in `src/Tests/` (xUnit + NSubstitute; integration tests use Testcontainers PostgreSQL against the real migrations). The test project references Domain/Application/Infrastructure but deliberately NOT Worker (whose build shells out to Bun for the frontend).
43
+
34
44
## Architecture
35
45
36
46
### Clean Architecture Layers
37
47
38
-
-**Domain** (`src/Domain/`) - Core entities (Song, User, PlayHistory, RadioSource), event interfaces (`IEventHandler<T>`, `IAsyncEventHandler<T>`), and common abstractions
39
-
-**Application** (`src/Application/`) - Business logic services, DTOs, configuration models, event dispatching system, and service interfaces
40
-
-**Infrastructure** (`src/Infrastructure/`) - Discord bot implementation using NetCord, audio processing with FFmpeg/NAudio, database access via EF Core, YouTube/SoundCloud integration
41
-
-**UI** (`src/UI/`) - Contains two sub-projects:
42
-
-`Api/` - ASP.NET Core REST API with JWT authentication
43
-
-`App/` - React TypeScript frontend (Vite build, Bun package manager)
44
-
-**Worker** (`src/Worker/`) - Main entry point that composes all layers, hosts the Discord bot and web API
-**Application** (`src/Application/`) - Business services (`SpotifyService`, `JokeService`, `QuoteService`, `HttpRequestService`), DTOs, config binding helpers, the in-process event dispatcher plus `AddEventing` registration (`Application.Eventing`), and the `GlobalStore` singleton.
50
+
-**Infrastructure** (`src/Infrastructure/`) - NetCord commands and interactions, audio pipeline (`MusicPlayerBackgroundService`, `AudioPlayerService`, `FfmpegProcessService`, `MusicQueueService`), `PlayerHandler` (thin Skip/Stop event adapter), YouTube/SoundCloud stream services, EF Core `DiscordBotContext` with compiled models, and radio/user/blacklist/statistics services.
51
+
-**Tests** (`src/Tests/`) - xUnit test project: `Unit/` (queue, player background service, eventing) and `Integration/` (blacklist/statistics/user services against Testcontainers PostgreSQL).
52
+
-**UI** (`src/UI/`)
53
+
-`Api/` - ASP.NET Core minimal-API endpoints (see `ControllerExtensions.cs`) with JWT bearer auth. Login checks against `JwtSettings:InternalPassword` (no user password hashing).
54
+
-`App/` - React 19 + TypeScript SPA. Build output writes into `src/Worker/wwwroot/`, which is served by the Worker as static files with SPA fallback to `index.html`.
55
+
-**Worker** (`src/Worker/`) - Composition root. Program.cs builds a `WebApplication` that hosts the NetCord Discord gateway (registered via `AddDiscordGateway`), the ASP.NET Core web API, and the `MusicPlayerBackgroundService` hosted service in one process on port 5000.
45
56
46
57
### Key Patterns
47
58
48
-
**Event System**: Custom domain event dispatching via `IEventHandler<T>` and `IAsyncEventHandler<T>`. Handlers are auto-discovered from assemblies at startup via `AddEventing()`.
59
+
**Event system**: Custom in-process dispatcher (`IEventDispatcher`, `IAsyncEventDispatcher`) plus a `HandlerRegistry`. `Application.Eventing.EventingServiceCollectionExtensions.AddEventing(assemblies...)` scans supplied assemblies for `IEventHandler<T>` / `IAsyncEventHandler<T>` implementations and registers them as scoped. When adding a new event handler, ensure its assembly is passed to `AddEventing` in `DependencyInjection.cs` (currently `Application.AssemblyMarker` and `Infrastructure.Services.AssemblyMarker`). Only `EventType.Skip` / `EventType.Stop` are dispatched today (handled by `PlayerHandler`, which forwards to `IMusicPlayerController`); `EventType.Play` is not dispatched anymore because enqueueing wakes the player's channel consumer directly.
60
+
61
+
**Queue service pattern**: `MusicQueueService` (singleton) pairs a lock-protected list with an unbounded `System.Threading.Channels` signal channel, following the Microsoft queue-service guidance. `MusicPlayerBackgroundService` (a `BackgroundService`, also registered as `IMusicPlayerController`) is the single consumer: it dequeues a `PlayRequest` into the `NowPlaying` slot, awaits `AudioPlayerService.PlayTrackAsync` (retrying failed tracks up to 3 times), and disconnects from voice when the queue runs empty. Skip/Stop cancel the per-track linked `CancellationTokenSource`.
62
+
63
+
**Keyed services**: Multiple implementations of `IStreamService` and `IRandomService` are registered by name and resolved with `[FromKeyedServices(nameof(...))]`:
49
64
50
-
**Service Registration**: Keyed services pattern for multiple implementations:
Commands and the `NetCordInteraction` component module are wired in `Worker.DependencyInjection.AddWebApplication`.
79
+
80
+
**Scoped work from singletons**: `IScopeExecutor` (`ScopeExecutor`) is used by singleton services (like commands calling into scoped `DiscordBotContext`) to open a DI scope on demand.
61
81
62
82
### Database
63
83
64
-
PostgreSQL with EF Core. Context: `Infrastructure/Data/DiscordBotContext.cs`. Migrations run automatically on startup.
84
+
PostgreSQL + EF Core 10. Context: `Infrastructure/Data/DiscordBotContext.cs`. Uses **compiled models** (`Infrastructure.CompiledModels.DiscordBotContextModel`) for startup performance; if you change the model, regenerate the compiled model in addition to adding a migration. Migrations live in `src/Infrastructure/Data/Migrations/` and are applied automatically at startup by `context.Database.MigrateAsync()` in `Program.cs`.
A play interaction enqueues a `PlayRequest` into `MusicQueueService`; the channel signal wakes `MusicPlayerBackgroundService`, which calls `AudioPlayerService.PlayTrackAsync`. That method joins the voice channel if needed, resolves the stream URL (`YoutubeExplode` / `YoutubeDLSharp` via `yt-dlp` at runtime, or a radio source URL when the selection is a Guid), logs the play via `IStatisticsService` (radio plays are logged with the station name as the title), spawns FFmpeg through`FfmpegProcessService` (`Ffmpeg:Path` config, default `/usr/bin/ffmpeg`), copies FFmpeg stdout into a NetCord `OpusEncodeStream`, then awaits process exit and maps the exit code to a `TrackPlayResult` (`Completed`/`Failed`/`Skipped`/`NotInVoiceChannel`). There are no C# events in this pipeline; user-facing messages flow through `PlayRequest.Callbacks` and failures are retried by the background service.
69
89
70
-
### Native Dependencies (Linux/Docker)
90
+
### Native Dependencies (installed in the Docker image)
71
91
72
92
- FFmpeg
73
-
- libsodium
74
-
-libopus
75
-
-libdave
76
-
- yt-dlp
93
+
- libsodium, libopus
94
+
-libdave (fetched from the Discord libdave releases zip in the Dockerfile)
95
+
-yt-dlp (pulled from the yt-dlp `latest` release at image build time; `YT_DLP_CACHE_BUST` build arg forces a re-download)
96
+
-python3 (required by yt-dlp)
77
97
78
98
## Configuration
79
99
80
-
Environment variables (via docker-compose or appsettings.json):
81
-
-`Discord__Token` - Discord bot token
82
-
-`SpotifySettings__ClientId/ClientSecret` - Spotify API credentials
-`Cors:AllowedOrigins` (JSON array in env var form: `[origin1,origin2]`)
108
+
-`Ffmpeg:Path` (optional, defaults to `/usr/bin/ffmpeg`)
109
+
110
+
For local Docker runs, populate `deployment/.env` (see `.github/workflows/release.yml` for the exact key list).
111
+
112
+
## Toolchain
113
+
114
+
- .NET SDK: `global.json` pins 9.0.3 with `rollForward: latestMajor`, so SDK 10+ satisfies it. All projects target `net10.0` via `src/Directory.Build.props` (nullable + implicit usings enabled).
115
+
- Central package management: all NuGet versions live in `src/Directory.Packages.props`.
116
+
- Frontend: React 19, Vite 8, TanStack Router + Query, Recharts, Sonner. Uses `babel-plugin-react-compiler` via the Vite React plugin.
117
+
118
+
## Deployment
87
119
88
-
## Frontend Stack
120
+
`.github/workflows/release.yml` deploys on push to `master` via a self-hosted Linux runner: tears down the running compose stack, writes `deployment/.env` from GitHub secrets, then rebuilds and restarts with `docker compose up -d`. The build passes `YT_DLP_CACHE_BUST=$(date +%Y%m%d)` so yt-dlp refreshes daily.
89
121
90
-
React 19 with TypeScript, TanStack Router, TanStack Query, Recharts. Uses Bun as the package manager and Vite 8 for bundling. Build output goes to `src/Worker/wwwroot/`.
122
+
`.github/workflows/ci.yml` runs `dotnet test src/Tests/Tests.csproj` on GitHub-hosted Ubuntu for pushes and PRs (Docker is preinstalled there, so the Testcontainers integration tests run too). It intentionally does not build the full `.slnx` because the Worker frontend build requires Bun.
0 commit comments