# IRC Bot Plugins — Sopel + Limnoria **Date**: 2026-03-12 **Task**: Design and implement two functionally-identical IRC bot plugins (Sopel and Limnoria) that query the NtR SoundCloud Fetcher API. ## Changes Made ### New files - `plugins/sopel/ntr_playlist.py` — Single-file Sopel plugin - `plugins/limnoria/NtrPlaylist/__init__.py` — Limnoria plugin package init - `plugins/limnoria/NtrPlaylist/config.py` — Limnoria registry config - `plugins/limnoria/NtrPlaylist/plugin.py` — Limnoria command handlers - `plugins/limnoria/NtrPlaylist/test.py` — Limnoria test stub - `tests/test_plugin_helpers.py` — 13 tests for formatting and API helpers - `docs/plans/2026-03-12-irc-plugins-design.md` — Design document - `docs/plans/2026-03-12-irc-plugins-implementation.md` — Implementation plan ### Commands implemented (identical in both) - `!1`, `!2`, etc. — Track by position (regex catch-all) - `!song ` — Track from specific episode - `!playlist [episode]` — Full playlist (comma-separated, truncated for IRC) - `!status` — API health info - `!refresh` — Admin-only manual refresh ### Design decisions - Fully independent plugins (no shared code) for easy deployment - stdlib `urllib.request` only — zero external dependencies - Admin check via plugin-config nickname list (case-insensitive) - No caching — always fetch fresh from API - Playlist output truncated at 430 chars for IRC line limits ## Commits - `2a00cc2` feat(sopel): add NtR playlist IRC plugin - `6dd7aee` feat(limnoria): add NtrPlaylist IRC plugin - `5c22776` test: add tests for IRC plugin formatting and API helpers - `b63c851` docs: add IRC bot plugins design and implementation plan - `05bcf18` fix: playlist truncation overflow and align logging across plugins ## Follow-up items - Limnoria `test.py` is a stub — could add real PluginTestCase tests if Limnoria is a test dependency - Both plugins log API errors; Sopel uses `LOGGER` (defined but previously unused) - The `!N` regex command uses `!` explicitly; named commands use the bot's configured prefix (Sopel default is `.`, so configure `core.command_prefix = "!"` for consistency)