diff --git a/README.md b/README.md index 4a9f194..438bf0c 100644 --- a/README.md +++ b/README.md @@ -257,6 +257,11 @@ in three kinds: - Artwork (Radarr posters, Plex thumbs) is relayed through `/api/widgets/{id}/image?u=...` - the browser cannot reach the internal host, and the relay only ever fetches from that widget's own origin. +- Calendar cards always name the show, not just the episode: Sonarr's calendar + items carry only `seriesId` plus an episode `title`, so the request asks for + `includeSeries=true` and reads the name off the nested `series` object (Radarr + ignores that parameter). Plex episodes take the show from `grandparentTitle` + (`parentTitle` is the season), skipped when it repeats the episode title. - `severity` is notice-only: `urgent`/`heads-up`/`info` tints the card edge, the chip, and the icon on Home. Setting it on a live widget is a validation error (live rows have no severity to sort by). diff --git a/src/uwudash_mk3/widgets.py b/src/uwudash_mk3/widgets.py index ab1e84d..48cf329 100644 --- a/src/uwudash_mk3/widgets.py +++ b/src/uwudash_mk3/widgets.py @@ -201,7 +201,13 @@ class WidgetData: return {} today = datetime.now() end = today + timedelta(days=widget.days) - return {"start": today.strftime("%Y-%m-%d"), "end": end.strftime("%Y-%m-%d")} + # Sonarr's calendar items carry only seriesId unless asked for the series + # object; Radarr ignores this unknown parameter. + return { + "start": today.strftime("%Y-%m-%d"), + "end": end.strftime("%Y-%m-%d"), + "includeSeries": "true", + } def _headers(self, widget: Widget) -> Dict[str, str]: headers = dict(widget.headers) @@ -252,15 +258,21 @@ class WidgetData: if not date: continue season, episode = item.get("seasonNumber"), item.get("episodeNumber") + # `title` is the episode title; the show name only arrives with + # ?includeSeries=true as a nested `series` object (SeriesResource + # calls it `title`). Older/other shapes use `seriesTitle` directly. + series = item.get("series") or {} + show = str(item.get("seriesTitle") or series.get("title") or "") + episode_title = str(item.get("episodeTitle") or item.get("title") or "") bits = [] if season is not None and episode is not None: bits.append(f"S{season} E{episode}") - if item.get("episodeTitle"): - bits.append(str(item["episodeTitle"])) + if episode_title and show: + bits.append(episode_title) rows.append( { "date": date, - "label": str(item.get("seriesTitle") or item.get("title") or "?"), + "label": show or episode_title or "?", "sub": " ยท ".join(bits), "type": "episode", "source": widget.name, @@ -307,9 +319,14 @@ class WidgetData: bits.append(str(item["type"])) if item.get("year"): bits.append(str(item["year"])) - if item.get("parentTitle"): - index = f"{item.get('parentIndex', '')}x{item.get('index', '')}" - bits.append(f"{item['parentTitle']} {index}") + # Plex nests the show under grandparentTitle for episodes (parentTitle + # is the season); recordings only carry parentTitle. + show = item.get("grandparentTitle") or item.get("parentTitle") + if show and show != item.get("title"): + if item.get("index") is not None: + bits.append(f"{show} {item.get('parentIndex', '')}x{item['index']}") + else: + bits.append(str(show)) rows.append( { "date": date,