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
eb9fc07021
commit
9dd04067bb
2 changed files with 83 additions and 18 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
maubot: 0.3.1
|
maubot: 0.3.1
|
||||||
id: com.3ddbrewery.media
|
id: com.3ddbrewery.media
|
||||||
version: 0.5.1
|
version: 0.5.2
|
||||||
license: MIT
|
license: MIT
|
||||||
modules:
|
modules:
|
||||||
- media_bot
|
- media_bot
|
||||||
|
|
|
||||||
|
|
@ -1422,7 +1422,13 @@ class MediaBot(Plugin):
|
||||||
self.log.exception("DB schema bootstrap failed")
|
self.log.exception("DB schema bootstrap failed")
|
||||||
|
|
||||||
async def _send_digest(self, room: str) -> None:
|
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
|
cutoff = datetime.now(timezone.utc).timestamp() - 86400
|
||||||
emby_uid = self._any_emby_uid("")
|
emby_uid = self._any_emby_uid("")
|
||||||
added: list[dict] = []
|
added: list[dict] = []
|
||||||
|
|
@ -1449,6 +1455,19 @@ class MediaBot(Plugin):
|
||||||
s_q = sonarr_q if not isinstance(sonarr_q, Exception) else []
|
s_q = sonarr_q if not isinstance(sonarr_q, Exception) else []
|
||||||
r_q = radarr_q if not isinstance(radarr_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()
|
local = _local_now()
|
||||||
is_sunday = local.weekday() == 6 # Mon=0..Sun=6
|
is_sunday = local.weekday() == 6 # Mon=0..Sun=6
|
||||||
today_str = local.strftime("%A, %b %d")
|
today_str = local.strftime("%A, %b %d")
|
||||||
|
|
@ -1458,16 +1477,22 @@ class MediaBot(Plugin):
|
||||||
lines.append("- " + self._fmt_emby_item(it))
|
lines.append("- " + self._fmt_emby_item(it))
|
||||||
if not added:
|
if not added:
|
||||||
lines.append("- (nothing in the last sweep)")
|
lines.append("- (nothing in the last sweep)")
|
||||||
|
|
||||||
|
if nzb_unique or qbt_unique:
|
||||||
lines.append("")
|
lines.append("")
|
||||||
total_completed = len(nzb_recent) + len(qbt_recent)
|
lines.append(
|
||||||
lines.append(f"**✅ Completed last 24h: {total_completed}** "
|
f"**✅ Also completed (not yet in Emby): "
|
||||||
f"(NZBGet: {len(nzb_recent)} · qBt: {len(qbt_recent)})")
|
f"{len(nzb_unique) + len(qbt_unique)}** "
|
||||||
for h in nzb_recent[:5]:
|
f"(NZBGet: {len(nzb_unique)} · qBt: {len(qbt_unique)})"
|
||||||
|
)
|
||||||
|
for h in nzb_unique[:5]:
|
||||||
name = h.get("Name") or h.get("NZBName") or "?"
|
name = h.get("Name") or h.get("NZBName") or "?"
|
||||||
size_mb = h.get("FileSizeMB") or 0
|
size_mb = h.get("FileSizeMB") or 0
|
||||||
lines.append(f"- *{name}* — {size_mb / 1024:.1f} GB")
|
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)}")
|
lines.append(f"- *{t.get('name','?')}* — {_human_bytes(t.get('size') or 0)}")
|
||||||
|
|
||||||
|
if s_q or r_q:
|
||||||
lines.append("")
|
lines.append("")
|
||||||
lines.append(f"**📥 Queued: {len(s_q)} TV · {len(r_q)} movies**")
|
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.append("**🧹 Weekly cleanup (emby-cleaner):**")
|
||||||
lines.extend(cleaner_lines)
|
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(
|
await self.client.send_message(
|
||||||
RoomID(room),
|
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]:
|
async def _fetch_emby_cleaner_recap(self) -> list[str]:
|
||||||
"""Pull the last 12h of messages from the emby-cleaner ntfy topic."""
|
"""Pull the last 12h of messages from the emby-cleaner ntfy topic."""
|
||||||
ntfy = (self.config["ntfy_url"] or "").rstrip("/")
|
ntfy = (self.config["ntfy_url"] or "").rstrip("/")
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue