Skip to content

MediaBot

A Discord bot that surfaces download status, failures, health warnings, and media requests directly from Discord. Includes interactive buttons for managing requests and stuck downloads, and an optional natural language interface powered by Claude for conversational control.

Repo: rampantlemming/mediabot

Deployment

Property Value
Host arrstack (192.168.1.252)
Container mediabot
Network arr (shared Docker network with media stack)
Working directory /opt/mediabot
Managed by Ansible arrstack role

The Ansible role clones the repo to /opt/mediabot and the container is built and run locally. Secrets are injected via .env from Ansible Vault.


Commands

Command Description
/queue Active downloads across Sonarr and Radarr with progress %
/failures Items with errors or import warnings — with action buttons
/health Aggregated health warnings from Sonarr, Radarr, and Seerr
/requests Pending Seerr media requests with approve/decline buttons
/history Recent download history (grabs, imports, failures)
/rescan Trigger a disk rescan in Sonarr, Radarr, or both
/search Search library by title and trigger a download search
@MediaBot <message> Natural language control via Claude AI (optional)

Interactive actions

/failures — Queue failure management

Stuck or failed queue items show three action buttons per item:

Button Action
Retry Removes the item from the queue (no blocklist) and triggers a fresh automatic search. Use when an import failed due to a transient issue.
Remove Removes the item without blocklisting. The release can be grabbed again if a search runs later.
Blocklist Removes the item and blocklists the specific release. Sonarr/Radarr will automatically search for an alternative. Use when the release itself is the problem (bad encode, wrong format, etc.).

All action responses are ephemeral — only visible to the person who clicked. Buttons have a 5-minute timeout; run the command again if the embed is stale.

/requests — Seerr request management

Pending requests display with approve/decline buttons:

Button Action
Approve Approves the request, triggering Sonarr/Radarr to begin searching
Decline Declines the request

Buttons disable automatically after use. Already-approved or declined requests display as read-only.

/search — Library search and trigger

Searches Sonarr and Radarr libraries by title. Each result shows a Search button that triggers an automatic download search for that item. Results include year, series status, and whether a file exists on disk for movies.

Use this when something is in your library but missing from disk, or to force a fresh search for a better release.

/rescan — Disk rescan

Triggers RescanSeries / RescanMovie commands in Sonarr and/or Radarr. Useful after manually moving or renaming files — forces the services to re-read the filesystem and update their databases.


Natural language interface

When ANTHROPIC_API_KEY is configured, you can @mention the bot with plain English instead of using slash commands. The bot sends your message to Claude (Sonnet), which decides which tools to call, executes them, and responds conversationally.

Claude can chain multiple actions in a single message — e.g. "check if anything is stuck, blocklist it, and search for a replacement" works as one request.

Monitoring examples

@MediaBot anything stuck?
@MediaBot what's downloading right now?
@MediaBot what's the overall health of the system?
@MediaBot what's been requested but isn't available yet?
@MediaBot is Severance available to watch?

Action examples

@MediaBot approve all pending requests
@MediaBot remove that stuck download and blocklist it
@MediaBot grab The Bear
@MediaBot can you get me Severance season 2
@MediaBot find me some sci-fi movies from 2025

Request submission goes through Seerr — Claude searches TMDB, finds the title, and submits a request. Seerr then routes it to Sonarr or Radarr automatically.

Note

This feature is entirely optional. All slash commands work without an Anthropic API key.

Cost

Claude Sonnet costs ~$3 per million input tokens. A typical interaction is 2,000–3,000 tokens, so each query costs well under a cent. Normal homelab usage is pennies per month.

Discord setup requirement

The Message Content Intent must be enabled in the Discord Developer Portal for the bot to read @mention messages:

→ Developer Portal → Your Application → Bot → Privileged Gateway Intents → Message Content Intent


Proactive alerts

When ALERT_CHANNEL_ID is configured, a background task checks the Sonarr and Radarr queues on an interval. Any item in warning or error state for longer than STUCK_THRESHOLD_MINUTES triggers an alert embed to the configured channel.

  • Each stuck item is alerted once only until it leaves the queue and reappears
  • Alerts are pruned automatically when items resolve or are removed
  • The alert embed references /failures for interactive management
  • Set ALERT_CHANNEL_ID=0 to disable entirely

Configuration

All configuration is via environment variables, injected at runtime from the Ansible-managed .env file. Secrets are sourced from Ansible Vault.

Variable Required Default Description
DISCORD_TOKEN Discord bot authentication token
SONARR_URL http://sonarr:8989 Sonarr base URL
SONARR_API_KEY Sonarr API key
RADARR_URL http://radarr:7878 Radarr base URL
RADARR_API_KEY Radarr API key
SEERR_URL http://seerr:5055 Seerr base URL
SEERR_API_KEY Seerr API key
ALERT_CHANNEL_ID 0 (disabled) Discord channel ID for proactive stuck-item alerts
STUCK_THRESHOLD_MINUTES 30 Minutes stuck before an alert fires
CHECK_INTERVAL_MINUTES 5 Background check frequency
ANTHROPIC_API_KEY Anthropic API key — enables @mention NLP interface

Note

Default URLs use Docker container names (sonarr, radarr, seerr) because MediaBot runs on the same arr Docker network as the media stack. If on a different network, use IP addresses.


Deployment (manual)

The Ansible role handles this automatically, but for reference:

Discord bot setup (one-time)

  1. Go to discord.com/developers/applications
  2. New Application → name it (e.g. "Media Bot")
  3. Bot sidebar → Reset Token → copy token → this is DISCORD_TOKEN
  4. Under Privileged Gateway Intents, enable Message Content Intent (required for @mention NLP)
  5. OAuth2 → URL Generator → scopes: bot, applications.commands → permissions: Send Messages, Embed Links
  6. Open the generated URL to invite the bot to your server

Getting API keys

  • Sonarr / Radarr: Settings → General → API Key
  • Seerr: Settings → General → API Key
  • Anthropic (optional): console.anthropic.com → Settings → API Keys

Build and run

cd /opt/mediabot
git pull
docker build -t mediabot:latest .
docker stop mediabot && docker rm mediabot
docker run -d \
  --name mediabot \
  --restart unless-stopped \
  --network arr \
  --env-file /opt/mediabot/.env \
  mediabot:latest

Slash commands sync automatically on startup (allow a few minutes; global Discord propagation can take up to an hour).


Tech stack

Component Detail
Language Python 3.12
Discord framework discord.py 2.3+
HTTP client aiohttp 3.9+
AI integration anthropic 0.39+
Base image python:3.12-slim
Architecture Async/await with asyncio.gather() for concurrent API calls

API endpoints used

Sonarr / Radarr (v3)

Method Endpoint Used by
GET /api/v3/queue /queue, /failures, proactive alerts, Claude
GET /api/v3/health /health, Claude
GET /api/v3/history /history, Claude
GET /api/v3/series /search, Claude
GET /api/v3/movie /search, Claude
DELETE /api/v3/queue/{id} Retry / Remove / Blocklist buttons, Claude
POST /api/v3/command /rescan, Retry button, Claude

Seerr (v1)

Method Endpoint Used by
GET /api/v1/request /requests, Claude
GET /api/v1/status /health, Claude
GET /api/v1/media Claude (availability checks)
GET /api/v1/search Claude (TMDB search for request submission)
GET /api/v1/movie/{tmdbId} /requests, Claude (title resolution)
GET /api/v1/tv/{tmdbId} /requests, Claude (title resolution)
POST /api/v1/request Claude (submit new media request)
POST /api/v1/request/{id}/approve Approve button, Claude
POST /api/v1/request/{id}/decline Decline button, Claude

Troubleshooting

Slash commands don't appear : Wait a few minutes after startup. Check container logs for Synced X slash command(s). If it shows 0 or an error, verify the bot has applications.commands OAuth scope.

"Forbidden" or empty responses : Double-check API keys. For Seerr — if CSRF protection is enabled, API access from external clients may be blocked.

Bot can't reach services : Confirm the bot container is on the arr Docker network. If using IPs instead of container names, verify they are reachable from arrstack.

Buttons not working / "This interaction failed" : Buttons time out after 5 minutes. Run the command again to get fresh buttons. Check bot logs for API errors.

Seerr shows TMDB IDs instead of titles : The bot resolves titles via Seerr's media endpoints. If IDs still appear, check SEERR_API_KEY is correct and Seerr is reachable.

@mention doesn't respond : Check that ANTHROPIC_API_KEY is set in .env, Message Content Intent is enabled in the Discord Developer Portal, and bot logs show Claude natural language interface enabled. If the bot says the interface isn't configured, the key isn't being loaded — recreate the container to pick up .env changes.

Request submission fails (via Claude) : The media may already be requested or available. Check /requests or ask the bot to check availability. Also verify Seerr has default Sonarr/Radarr servers configured under Settings → Services.