v0.5.2: digest skips redundant/empty sections, splits long bodies
Daily-digest fixes for issue #1: - Drop the Completed section unless a finished release isn't already represented in Recently Added (slug-substring match on titles). - Skip the Queued section when both Sonarr and Radarr queues are empty. - Split the message into numbered chunks (~3000 chars) so Sunday digests with the emby-cleaner recap don't get truncated by Matrix clients.
This commit is contained in:
parent
c62bb9c03e
commit
b23eb8b403
2 changed files with 83 additions and 18 deletions
|
|
@ -1,6 +1,6 @@
|
|||
maubot: 0.3.1
|
||||
id: com.3ddbrewery.media
|
||||
version: 0.5.1
|
||||
version: 0.5.2
|
||||
license: MIT
|
||||
modules:
|
||||
- media_bot
|
||||
|
|
|
|||
|
|
@ -1422,7 +1422,13 @@ class MediaBot(Plugin):
|
|||
self.log.exception("DB schema bootstrap failed")
|
||||
|
||||
async def _send_digest(self, room: str) -> None:
|
||||
"""Build and post the daily digest to `room`."""
|
||||
"""Build and post the daily digest to `room`.
|
||||
|
||||
Skips the Completed section entirely when every finished release is
|
||||
already represented in Recently Added; skips Queued when both arrs are
|
||||
empty; splits long bodies (e.g. Sunday cleanup recap) across numbered
|
||||
messages so Matrix clients don't truncate.
|
||||
"""
|
||||
cutoff = datetime.now(timezone.utc).timestamp() - 86400
|
||||
emby_uid = self._any_emby_uid("")
|
||||
added: list[dict] = []
|
||||
|
|
@ -1449,6 +1455,19 @@ class MediaBot(Plugin):
|
|||
s_q = sonarr_q if not isinstance(sonarr_q, Exception) else []
|
||||
r_q = radarr_q if not isinstance(radarr_q, Exception) else []
|
||||
|
||||
added_titles = {self._title_slug(self._emby_match_title(it)) for it in added}
|
||||
added_titles.discard("")
|
||||
|
||||
def _is_unique(release_name: str) -> bool:
|
||||
slug = self._title_slug(release_name)
|
||||
if not slug:
|
||||
return True
|
||||
return not any(t and t in slug for t in added_titles)
|
||||
|
||||
nzb_unique = [h for h in nzb_recent
|
||||
if _is_unique(h.get("Name") or h.get("NZBName") or "")]
|
||||
qbt_unique = [t for t in qbt_recent if _is_unique(t.get("name") or "")]
|
||||
|
||||
local = _local_now()
|
||||
is_sunday = local.weekday() == 6 # Mon=0..Sun=6
|
||||
today_str = local.strftime("%A, %b %d")
|
||||
|
|
@ -1458,16 +1477,22 @@ class MediaBot(Plugin):
|
|||
lines.append("- " + self._fmt_emby_item(it))
|
||||
if not added:
|
||||
lines.append("- (nothing in the last sweep)")
|
||||
|
||||
if nzb_unique or qbt_unique:
|
||||
lines.append("")
|
||||
total_completed = len(nzb_recent) + len(qbt_recent)
|
||||
lines.append(f"**✅ Completed last 24h: {total_completed}** "
|
||||
f"(NZBGet: {len(nzb_recent)} · qBt: {len(qbt_recent)})")
|
||||
for h in nzb_recent[:5]:
|
||||
lines.append(
|
||||
f"**✅ Also completed (not yet in Emby): "
|
||||
f"{len(nzb_unique) + len(qbt_unique)}** "
|
||||
f"(NZBGet: {len(nzb_unique)} · qBt: {len(qbt_unique)})"
|
||||
)
|
||||
for h in nzb_unique[:5]:
|
||||
name = h.get("Name") or h.get("NZBName") or "?"
|
||||
size_mb = h.get("FileSizeMB") or 0
|
||||
lines.append(f"- *{name}* — {size_mb / 1024:.1f} GB")
|
||||
for t in qbt_recent[:5]:
|
||||
for t in qbt_unique[:5]:
|
||||
lines.append(f"- *{t.get('name','?')}* — {_human_bytes(t.get('size') or 0)}")
|
||||
|
||||
if s_q or r_q:
|
||||
lines.append("")
|
||||
lines.append(f"**📥 Queued: {len(s_q)} TV · {len(r_q)} movies**")
|
||||
|
||||
|
|
@ -1478,11 +1503,51 @@ class MediaBot(Plugin):
|
|||
lines.append("**🧹 Weekly cleanup (emby-cleaner):**")
|
||||
lines.extend(cleaner_lines)
|
||||
|
||||
chunks = self._chunk_lines(lines)
|
||||
total = len(chunks)
|
||||
for idx, chunk in enumerate(chunks, 1):
|
||||
body = chunk if total == 1 else f"**({idx}/{total})**\n\n{chunk}"
|
||||
await self.client.send_message(
|
||||
RoomID(room),
|
||||
TextMessageEventContent(msgtype=MessageType.NOTICE, body="\n".join(lines)),
|
||||
TextMessageEventContent(msgtype=MessageType.NOTICE, body=body),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _emby_match_title(it: dict) -> str:
|
||||
"""Pick the title that should match a release name — series name for
|
||||
episodes, item name otherwise."""
|
||||
if it.get("Type") == "Episode":
|
||||
return it.get("SeriesName") or it.get("Name") or ""
|
||||
return it.get("Name") or ""
|
||||
|
||||
@staticmethod
|
||||
def _title_slug(s: str) -> str:
|
||||
"""Normalize a title or release name to lowercase alphanumerics so
|
||||
'The.Bear.S03E10.1080p...' and 'The Bear' compare equal-ish."""
|
||||
return "".join(c for c in s.lower() if c.isalnum())
|
||||
|
||||
@staticmethod
|
||||
def _chunk_lines(lines: list[str], max_len: int = 3000) -> list[str]:
|
||||
"""Pack lines into chunks <= max_len chars on line boundaries."""
|
||||
body = "\n".join(lines)
|
||||
if len(body) <= max_len:
|
||||
return [body]
|
||||
chunks: list[str] = []
|
||||
cur: list[str] = []
|
||||
cur_len = 0
|
||||
for line in lines:
|
||||
extra = len(line) + (1 if cur else 0)
|
||||
if cur and cur_len + extra > max_len:
|
||||
chunks.append("\n".join(cur))
|
||||
cur = [line]
|
||||
cur_len = len(line)
|
||||
else:
|
||||
cur.append(line)
|
||||
cur_len += extra
|
||||
if cur:
|
||||
chunks.append("\n".join(cur))
|
||||
return chunks
|
||||
|
||||
async def _fetch_emby_cleaner_recap(self) -> list[str]:
|
||||
"""Pull the last 12h of messages from the emby-cleaner ntfy topic."""
|
||||
ntfy = (self.config["ntfy_url"] or "").rstrip("/")
|
||||
|
|
|
|||
Loading…
Reference in a new issue