9.0 KiB
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.
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 - Titlepatterns in chat and validates against Apple Music - Explicit command —
!request Artist - Titlefor 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
pip install aiohttp
Installation
-
Copy the
SongRequest/directory into your Limnoria plugins directory (or add its parent toconfig directories.plugins). -
Load the plugin:
@load SongRequest -
Configure enabled channels:
@config plugins.SongRequest.enabledChannels #music #requests -
Add a web dashboard admin (from IRC, requires bot admin):
@addsongadmin alice s3cretK3y -
(Optional) Set a Last.fm API key for spell correction:
@config plugins.SongRequest.lastfmApiKey YOUR_LASTFM_KEY -
Access the dashboard at
http://<bot-host>:8888/(default port, configurable viawebPort).
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 iTunesprefer— sort explicit tracks first, keep all resultsfilter(default) — drop cleaned versions when an explicit version of the same track exists, sort explicit firstclean— drop explicit versions when a clean version exists, sort clean first
Commands
| Command | Description | Permission |
|---|---|---|
request <text> |
Request a song by artist/title | Anyone |
pick <number> |
Pick from disambiguation choices | Anyone |
ignore <nick> |
Add a nick to the ignore list | Admin |
unignore <nick> |
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 <user> <key> |
Add a web dashboard admin account | Admin |
removesongadmin <user> |
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 (
/#channelNameor/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
- The bot watches messages in
enabledChannelsfor lines matchingSomething - Something - Lines starting with bot command prefixes (
!,.,@, etc.) or URLs are skipped - If a Last.fm API key is configured, artist/track names are spell-corrected
- The extracted text is searched against the iTunes Search API (dual-strategy: combined + attribute-targeted)
- Results are filtered/sorted based on
explicitMode - If no song matches, the message is silently ignored
- If one match (or
feelingLuckyis on), it's queued automatically - If multiple matches, the user is presented with choices
Running Tests
pip install limnoria aiohttp pytest
python -m pytest SongRequest/test.py -v