# SongRequest — Limnoria IRC Song Request Plugin A Limnoria (Supybot) plugin that watches IRC channels for song requests, validates them against the iTunes Search API, and streams them in real time to an HTMX web dashboard via WebSocket. ## Features - **Passive detection** — recognizes `Artist - Title` patterns in chat and validates against Apple Music - **Explicit command** — `!request Artist - Title` for direct requests - **Disambiguation** — presents top matches when multiple results found; user picks by number - **Feeling Lucky mode** — auto-select the first match, store the rest as alternates (per-channel) - **Explicit/clean filtering** — prefer explicit tracks, filter out clean duplicates, or vice versa (configurable per-channel) - **Last.fm spell correction** — optionally correct misspelled artist/track names before searching iTunes - **Smart search** — dual-strategy iTunes queries with attribute-targeted searches and increased result limits - **Web dashboard** — HTMX-powered UI with album art, Apple Music links, and moderation controls - **Real-time updates** — WebSocket pushes new requests and status changes to all connected dashboards - **Moderation** — approve, reject, or mark requests as played from the web UI - **Bulk actions** — select multiple requests and approve, reject, or mark played in one action - **Alternate matches** — when disambiguating, unchosen tracks appear as collapsible sub-cards with approve and mark-played buttons - **Clickable cards** — the entire request card links to Apple Music - **Session management** — start/stop named sessions, archive them, rename/clear/delete archived sessions - **Channel grouping** — requests grouped by channel with tab filtering; URL-based channel routing - **Auth system** — per-admin login via IRC-managed accounts (`addSongAdmin`), admin presence indicator - **Theme support** — dark/light/system theme toggle - **Rate limiting** — configurable per-user request limits - **Ignore list** — block specific users from making requests - **Auto-approve** — skip the pending queue and auto-approve requests (per-channel) - **Persistence** — SQLite-backed; survives bot restarts - **IRC announcements** — configurable delivery: channel, private message, or NOTICE - **Quiet mode** — suppress the "Queued" IRC confirmation per-channel - **Export history** — download request history as a Markdown file - **Open/close requests** — global toggle (with per-channel override) from IRC or the web panel - **Clear history** — wipe played/rejected entries from IRC or the web panel - **Mobile responsive** — optimized layout for phones and tablets ## Dependencies - `aiohttp` — standalone async web server for the dashboard ```bash pip install aiohttp ``` ## Installation 1. Copy the `SongRequest/` directory into your Limnoria plugins directory (or add its parent to `config directories.plugins`). 2. Load the plugin: ``` @load SongRequest ``` 3. Configure enabled channels: ``` @config plugins.SongRequest.enabledChannels #music #requests ``` 4. Add a web dashboard admin (from IRC, requires bot admin): ``` @addsongadmin alice s3cretK3y ``` 5. (Optional) Set a Last.fm API key for spell correction: ``` @config plugins.SongRequest.lastfmApiKey YOUR_LASTFM_KEY ``` 6. Access the dashboard at `http://:8888/` (default port, configurable via `webPort`). ## Configuration ### Global Settings | Setting | Type | Default | Description | |---------|------|---------|-------------| | `enabledChannels` | Space-separated list | (empty) | Channels for passive detection | | `ignoredUsers` | Space-separated list | (empty) | Nicks/hostmasks to ignore | | `maxRequestsPerUser` | Integer | 10 | Max requests per rate limit window (0 = unlimited) | | `rateLimitWindow` | Integer | 3600 | Rate limit window in seconds | | `webAuthToken` | String (private) | (empty) | (Deprecated) Legacy shared auth token; prefer per-admin accounts | | `lastfmApiKey` | String (private) | (empty) | Last.fm API key for spell correction; empty to disable | | `announceStatus` | Boolean | True | Master switch for all IRC status announcements | | `maxChoices` | Integer | 3 | Disambiguation choices shown | | `webPort` | Integer | 8888 | Port for the web dashboard server | | `webHost` | String | 0.0.0.0 | Bind address for the web dashboard server | | `requestsOpen` | Boolean | True | Global toggle — accept or reject new requests | ### Per-Channel Settings | Setting | Type | Default | Description | |---------|------|---------|-------------| | `requestsOpenOverride` | String | (empty) | `open`, `closed`, or empty for global default | | `quietQueued` | Boolean | False | Suppress the "Queued: ..." IRC confirmation | | `queuedReplyMode` | String | `channel` | Queued confirmation delivery: `channel`, `private`, or `notice` | | `autoApprove` | Boolean | False | Auto-approve requests (skip pending queue) | | `feelingLucky` | Boolean | False | Auto-select first match; store rest as alternates | | `explicitMode` | String | `filter` | `off`, `prefer`, `filter`, or `clean` (see below) | | `announceApproved` | Boolean | True | Announce approved requests in IRC | | `announceRejected` | Boolean | True | Announce rejected requests in IRC | | `announceNowPlaying` | Boolean | True | Announce now-playing requests in IRC | | `announceReplyMode` | String | `channel` | Status announcement delivery: `channel`, `private`, or `notice` | | `passiveDetection` | Boolean | True | Enable passive pattern matching | | `requestCommand` | Boolean | True | Enable the `!request` command | ### Explicit Mode Controls how explicit/clean track versions are handled in search results: - **`off`** — return results as-is from iTunes - **`prefer`** — sort explicit tracks first, keep all results - **`filter`** (default) — drop cleaned versions when an explicit version of the same track exists, sort explicit first - **`clean`** — drop explicit versions when a clean version exists, sort clean first ## Commands | Command | Description | Permission | |---------|-------------|------------| | `request ` | Request a song by artist/title | Anyone | | `pick ` | Pick from disambiguation choices | Anyone | | `ignore ` | Add a nick to the ignore list | Admin | | `unignore ` | Remove a nick from the ignore list | Admin | | `requeststats` | Show queue statistics | Anyone | | `openrequests [channel]` | Open requests globally or per-channel | Admin | | `closerequests [channel]` | Close requests globally or per-channel | Admin | | `clearhistory` | Clear all played/rejected requests | Admin | | `startsession [name]` | Start a new request session | Admin | | `stopsession` | Stop and archive the active session | Admin | | `addsongadmin ` | Add a web dashboard admin account | Admin | | `removesongadmin ` | Remove a web dashboard admin account | Admin | | `listsongadmins` | List all web dashboard admin accounts | Admin | ## Web Dashboard The dashboard runs on a standalone aiohttp server at the configured `webPort` (default `8888`). ### Authentication Admins are managed via IRC commands (`addsongadmin`/`removesongadmin`). The dashboard has a login page at `/login`. Non-admins can view the queue and history read-only; admin controls (moderation buttons, session management, export, etc.) are only visible to logged-in admins. A floating presence indicator shows which admins are currently online. ### Queue View - **Pending** requests awaiting moderation - **Approved** requests ready to play - **Bulk select** mode for mass approve/reject/mark-played - Action buttons (approve/reject/mark played) positioned in the top-right corner of each card ### History View - History of played/rejected requests with **Export .md** and **Clear History** buttons - **Session archives** — previous sessions listed with expand-to-view, plus rename/clear/delete actions ### Other Features - Channel filter tabs with URL-based routing (`/#channelName` or `/channelName`) - Dark/light/system theme toggle - Toast notifications for new requests when viewing history - Custom themed modals (no native browser prompts) - Real-time WebSocket connection with auto-reconnect ## How Passive Detection Works 1. The bot watches messages in `enabledChannels` for lines matching `Something - Something` 2. Lines starting with bot command prefixes (`!`, `.`, `@`, etc.) or URLs are skipped 3. If a Last.fm API key is configured, artist/track names are spell-corrected 4. The extracted text is searched against the iTunes Search API (dual-strategy: combined + attribute-targeted) 5. Results are filtered/sorted based on `explicitMode` 6. If no song matches, the message is silently ignored 7. If one match (or `feelingLucky` is on), it's queued automatically 8. If multiple matches, the user is presented with choices ## Running Tests ```bash pip install limnoria aiohttp pytest python -m pytest SongRequest/test.py -v ```