> [!IMPORTANT] > This project was developed entirely with AI coding assistance (Claude Opus 4.6 via Cursor IDE) and has not undergone rigorous review. It is provided as-is and may require adjustments for other environments. # NtR SoundCloud Fetcher Fetches SoundCloud likes from NicktheRat's profile, builds weekly playlists aligned to the Wednesday 22:00 ET show schedule, and serves them via a JSON API. ## Quick Start ```bash pip install -e ".[dev]" cp .env.example .env # edit with your settings ntr-fetcher ``` The API starts at `http://127.0.0.1:8000`. ## Historical Backfill Seed the database with past shows by providing an anchor episode and its air date: ```bash NTR_ADMIN_TOKEN=token ntr-fetcher --init --show 521 --aired 2026-01-07 ``` This computes every weekly show from the anchor forward to today, batch-fetches the corresponding likes from SoundCloud, and populates the database. Episode numbers are assigned automatically (521, 522, ...). After backfill completes, the normal server mode will auto-increment from the latest episode. ## API Full documentation: [`docs/api.md`](docs/api.md) | Endpoint | Method | Auth | Description | |----------|--------|------|-------------| | `/` | GET | -- | Public index page | | `/health` | GET | -- | Service health check | | `/public/playlist` | GET | -- | Current week's playlist (unannounced tracks censored) | | `/public/shows` | GET | -- | List all shows (paginated) | | `/public/shows/{show_id}` | GET | -- | Past show with full tracklist | | `/playlist` | GET | Bearer | Current week's full playlist | | `/playlist/{position}` | GET | Bearer | Single track by position (1-indexed) | | `/shows` | GET | Bearer | List all shows (paginated) | | `/shows/by-episode/{episode_number}` | GET | Bearer | Look up show by episode number | | `/shows/{show_id}` | GET | Bearer | Specific show by internal ID | | `/login` | GET/POST | -- | Login page | | `/logout` | GET | Session | Clear session | | `/dashboard` | GET | Session | Live playlist dashboard | | `/admin/refresh` | POST | Bearer | Trigger immediate SoundCloud fetch | | `/admin/tracks` | POST | Bearer | Add track to current show | | `/admin/tracks/{track_id}` | DELETE | Bearer | Remove track from current show | | `/admin/tracks/{track_id}/position` | PUT | Bearer | Move track to new position | | `/admin/announce` | POST | Bearer/Session | Announce track to IRC | | `/admin/announced` | POST | Bearer/Session | Toggle track announced state | | `/admin/ping` | POST | Bearer/Session | Send IRC message via connected bots | | `/ws/announce` | WS | Bearer | Bot WebSocket for announcements | | `/ws/public` | WS | -- | Public WebSocket for live track reveals | ## Configuration All settings are read from environment variables (prefix `NTR_`). A `.env` file in the project root is loaded automatically. See [`.env.example`](.env.example) for a template with all available variables. | Variable | Default | Description | |----------|---------|-------------| | `NTR_PORT` | `8000` | API port | | `NTR_HOST` | `127.0.0.1` | Bind address | | `NTR_DB_PATH` | `./ntr_fetcher.db` | SQLite database path | | `NTR_POLL_INTERVAL_SECONDS` | `3600` | How often to check SoundCloud (seconds) | | `NTR_ADMIN_TOKEN` | *(required)* | Bearer token for admin endpoints | | `NTR_SOUNDCLOUD_USER` | `nicktherat` | SoundCloud username to track | | `NTR_SHOW_DAY` | `2` | Day of week for show (0=Mon, 2=Wed) | | `NTR_SHOW_HOUR` | `22` | Hour (Eastern Time) when the show starts | | `NTR_SHOW_ROTATION_DELAY_HOURS` | `0` | Hours to wait after the show boundary before rotating to the next episode (recommended `2` for live recording window) | | `NTR_PING_TARGET` | *(empty)* | Default nick/target for the dashboard ping feature | | `NTR_PING_MESSAGE` | *(empty)* | Default message for the dashboard ping feature | ## Public Index Page The root URL (`/`) serves a public-facing page showing the current show's playlist. Unannounced tracks are hidden behind silhouette cards until the admin announces them from the dashboard. Reveals happen in real time over WebSocket (`/ws/public`), so listeners following along see tracks appear as they are played. Past episodes are browsable from the same page with their full tracklists visible. ## Dashboard An optional web dashboard for managing tracks during live shows. Features: - **Tabbed show interface** -- switch between the current and previous week's playlists in one view. - **Announce button** -- push a track to IRC via connected bot plugins. Also reveals the track on the public index page in real time. - **Announced checkbox** -- persistent per-track flag, auto-checked on announce. Can be toggled manually. - **Copy to clipboard** -- one-click copy of a formatted track line. - **Send IRC Message** -- ping a nick or channel with a custom message through all connected bots. Default target and message are configurable via `NTR_PING_TARGET` / `NTR_PING_MESSAGE`. Enable by setting these environment variables: | Variable | Default | Description | |----------|---------|-------------| | `NTR_WEB_USER` | *(required)* | Dashboard login username | | `NTR_WEB_PASSWORD` | *(required)* | Dashboard login password | | `NTR_SECRET_KEY` | *(required)* | Secret key for cookie signing | ## IRC Commands Both Sopel and Limnoria plugins expose the same commands: | Command | Description | |---------|-------------| | `!1`, `!2`, ... `!N` | Track by position in the current week's playlist | | `!song ` | Track from a specific episode's playlist | | `!playlist [episode]` | Current week's playlist, or a specific episode | | `!lastshow ` | Track from the previous week's show | | `!status` | API health, poller status, and track count | | `!refresh` | Trigger a manual SoundCloud fetch (admin only) | ## IRC Plugin Setup Both plugins require the `websocket-client` package for live announcements: ```bash pip install websocket-client ``` ### Sopel Copy `plugins/sopel/ntr_playlist.py` into your Sopel plugins directory (typically `~/.sopel/plugins/`). Add a `[ntr_playlist]` section to your Sopel config: | Setting | Default | Description | |---------|---------|-------------| | `api_base_url` | `http://127.0.0.1:8000` | NtR API base URL | | `admin_token` | *(empty)* | Bearer token for admin commands | | `admin_nicks` | *(empty)* | Comma-separated list of admin nicknames | | `display_timezone` | `America/New_York` | Timezone for displayed timestamps | | `ws_url` | `ws://127.0.0.1:8000/ws/announce` | WebSocket endpoint for announcements | | `announce_channel` | `#sewerchat` | Channel to send announcements to | | `client_id` | `sopel` | Identifier for this bot in status messages | ### Limnoria Copy the `plugins/limnoria/NtrPlaylist/` directory into your Limnoria bot's plugins folder. Configure via the Limnoria registry: | Registry Value | Default | Description | |----------------|---------|-------------| | `apiBaseUrl` | `http://127.0.0.1:8000` | NtR API base URL | | `adminToken` | *(empty)* | Bearer token for admin commands | | `adminNicks` | *(empty)* | Space-separated list of admin nicknames | | `displayTimezone` | `America/New_York` | Timezone for displayed timestamps | | `wsUrl` | `ws://127.0.0.1:8000/ws/announce` | WebSocket endpoint for announcements | | `announceChannel` | `#sewerchat` | Channel to send announcements to | | `clientId` | `limnoria` | Identifier for this bot in status messages | ## Development ```bash pip install -e ".[dev]" pytest ruff check src/ tests/ ```