Files
Eric Jungbauer 20a8987dd9 The Pirate Phase 1.b: Lidarr + Whisparr + Overseerr tools
Adds 9 read-only tools across three new services:
- Lidarr: queue, artist_search, library_stats (music library awareness)
- Whisparr: queue, series_search, library_stats (adult content, v3 API)
- Overseerr: search, requests, request_counts (cross-library availability,
  request tracking via X-Api-Key header reusing arr_get helper)

Fix _common.py urlencode to use %20 instead of + for query values —
Overseerr rejects + as reserved. Safe for all arr services.

Pirate catalog: 11 → 20 tools.
2026-04-20 22:23:26 +00:00

101 lines
3.8 KiB
Python

"""Lidarr tools — music library queries (read-only)."""
from ._common import arr_get
def _compact_artist(a):
st = a.get("statistics", {}) or {}
return {
"id": a.get("id"),
"name": a.get("artistName"),
"status": a.get("status"),
"monitored": a.get("monitored"),
"genres": a.get("genres", []),
"album_count": st.get("albumCount"),
"tracks_on_disk": st.get("trackFileCount"),
"tracks_total": st.get("totalTrackCount"),
"size_on_disk_gb": round((st.get("sizeOnDisk", 0) or 0) / 1e9, 1),
}
def lidarr_queue(limit=20):
"""Music albums/tracks currently downloading or pending import."""
data = arr_get("lidarr", "/api/v1/queue", {
"pageSize": limit,
"includeArtist": "true",
"includeAlbum": "true",
})
items = []
for r in data.get("records", []):
items.append({
"title": r.get("title"),
"artist": r.get("artist", {}).get("artistName") if r.get("artist") else None,
"album": r.get("album", {}).get("title") if r.get("album") else None,
"status": r.get("status"),
"timeleft": r.get("timeleft"),
"size_mb": round((r.get("size", 0) or 0) / 1e6, 1),
"size_left_mb": round((r.get("sizeleft", 0) or 0) / 1e6, 1),
"download_client": r.get("downloadClient"),
})
return {"total_records": data.get("totalRecords", 0), "items": items}
def lidarr_artist_search(query):
"""Look up an artist in the user's Lidarr library by partial name match.
Does NOT search Prowlarr/indexers — only what's already tracked."""
all_artists = arr_get("lidarr", "/api/v1/artist")
q = query.lower().strip()
hits = [a for a in all_artists if q in (a.get("artistName") or "").lower()]
return {"query": query, "count": len(hits), "artists": [_compact_artist(a) for a in hits[:20]]}
def lidarr_library_stats():
"""Summary stats of the Lidarr music library."""
all_artists = arr_get("lidarr", "/api/v1/artist")
total_albums = total_tracks = total_on_disk = total_size = 0
for a in all_artists:
st = a.get("statistics", {}) or {}
total_albums += st.get("albumCount", 0) or 0
total_tracks += st.get("totalTrackCount", 0) or 0
total_on_disk += st.get("trackFileCount", 0) or 0
total_size += st.get("sizeOnDisk", 0) or 0
return {
"artist_count": len(all_artists),
"album_count": total_albums,
"tracks_total": total_tracks,
"tracks_on_disk": total_on_disk,
"total_size_gb": round(total_size / 1e9, 1),
}
TOOLS = [
{
"name": "lidarr_queue",
"description": "List music albums/tracks Lidarr is currently downloading or importing. Use when the user asks 'what music is downloading', 'is the new album in yet'.",
"input_schema": {
"type": "object",
"properties": {"limit": {"type": "integer", "description": "Max rows to return (default 20)", "default": 20}},
},
"read_only": True,
"fn": lidarr_queue,
},
{
"name": "lidarr_artist_search",
"description": "Check whether an artist is in the user's Lidarr library by partial name match. Does NOT search for new artists to add.",
"input_schema": {
"type": "object",
"properties": {"query": {"type": "string", "description": "Partial or full artist name"}},
"required": ["query"],
},
"read_only": True,
"fn": lidarr_artist_search,
},
{
"name": "lidarr_library_stats",
"description": "High-level stats about the Lidarr music library: artist count, album count, tracks on disk, total size.",
"input_schema": {"type": "object", "properties": {}},
"read_only": True,
"fn": lidarr_library_stats,
},
]