From af24287748394e4b702f7537b3acc7fd0a86008a Mon Sep 17 00:00:00 2001 From: "Gan, Jimmy" Date: Sun, 12 Jul 2026 01:41:15 +0800 Subject: [PATCH] chore: tg_filter emoji fix, sprint spec update, tg_digest script --- backend/tg_filter.py | 10 ++-- docs/specs/active.md | 2 +- scripts/tg_digest.py | 106 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 4 deletions(-) create mode 100644 scripts/tg_digest.py diff --git a/backend/tg_filter.py b/backend/tg_filter.py index 2d3d128..0976433 100644 --- a/backend/tg_filter.py +++ b/backend/tg_filter.py @@ -40,7 +40,7 @@ THRESHOLD = 40 # Scan config MSGS_PER_GROUP = 500 -GROUPS_PER_CYCLE = 56 +GROUPS_PER_CYCLE = 0 # 0 = scan all eligible groups (was 56) # Keywords INTERESTING_KEYWORDS = [ @@ -146,7 +146,7 @@ async def scan_and_report(dry_run: bool = False) -> list: else: sorted_candidates.append((cid, title, "")) sorted_candidates.sort(key=lambda x: (x[2] == "", x[2])) - sorted_candidates = sorted_candidates[:GROUPS_PER_CYCLE] + sorted_candidates = sorted_candidates[:len(candidates)] if GROUPS_PER_CYCLE == 0 else sorted_candidates[:GROUPS_PER_CYCLE] log.info(f"Scanning {len(sorted_candidates)} groups this cycle") @@ -169,7 +169,11 @@ async def scan_and_report(dry_run: bool = False) -> list: sender = None if msg.from_user: sender = msg.from_user.first_name or msg.from_user.username or str(msg.from_user.id) - text = (msg.text or msg.caption or "")[:2000] + try: + text = (msg.text or msg.caption or "")[:2000] + except (UnicodeDecodeError, UnicodeEncodeError): + # surrogate-pass decode failure on certain emoji sequences + text = (str(msg.caption or "")[:2000]) if msg.caption else "[text unreadable]" item = { "chat_id": chat_id, "chat_title": chat_title, "message_id": msg.id, "sender": sender, "text": text, "score": score, diff --git a/docs/specs/active.md b/docs/specs/active.md index c02bf07..e439686 120000 --- a/docs/specs/active.md +++ b/docs/specs/active.md @@ -1 +1 @@ -sprint-003-agent-digest.md \ No newline at end of file +sprint-003.5-agent-frontend.md \ No newline at end of file diff --git a/scripts/tg_digest.py b/scripts/tg_digest.py new file mode 100644 index 0000000..144ae97 --- /dev/null +++ b/scripts/tg_digest.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +""" +t-youtube Telegram Digest Cron +Sprint 003 AC T3.2 — Daily summary of today's videos by channel +Run daily at 09:00 UTC+8 via Hermes cron +""" + +import os +import json +import sys +import urllib.request +import urllib.error +from datetime import datetime, timezone, timedelta + +# --- Config --- +BASE_URL = os.environ.get("TY_API_URL", "http://127.0.0.1:8080") +TELEGRAM_BOT_TOKEN = os.environ.get("TELEGRAM_BOT_TOKEN", "") +TELEGRAM_CHAT_ID = os.environ.get("TELEGRAM_CHAT_ID", "") + +# --- Fetch today's videos --- + +def fetch_videos(): + today = datetime.now(timezone.utc).strftime("%Y-%m-%d") + url = f"{BASE_URL}/api/videos?since={today}" + try: + req = urllib.request.Request(url) + with urllib.request.urlopen(req, timeout=15) as resp: + data = json.loads(resp.read().decode()) + return data.get("videos", data.get("items", [])) + except Exception as e: + print(f"[tg_digest] ERROR fetching videos: {e}", file=sys.stderr) + return [] + +# --- Build digest --- + +def build_digest(videos): + if not videos: + return "📭 No new videos today." + + # Group by channel + channels = {} + for v in videos: + ch = v.get("channel", v.get("channel_name", "Unknown")) + channels.setdefault(ch, []).append(v) + + lines = ["📺 **YouTube Digest — Today's Videos**", "", f"_{datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M UTC')}_", ""] + + for ch, items in sorted(channels.items()): + lines.append(f"### {ch}") + lines.append(f"_{len(items)} video(s)_") + lines.append("") + for v in items: + title = v.get("title", "Untitled") + url = v.get("url", v.get("watch_url", "")) + duration = v.get("duration", "?") + lines.append(f"- **{title}** ({duration})") + if url: + lines.append(f" {url}") + lines.append("") + + lines.append("---") + lines.append(f"Total: {len(videos)} videos from {len(channels)} channels") + return "\n".join(lines) + +# --- Send to Telegram --- + +def send_telegram(text): + if not TELEGRAM_BOT_TOKEN or not TELEGRAM_CHAT_ID: + print("[tg_digest] WARNING: TELEGRAM_BOT_TOKEN or TELEGRAM_CHAT_ID not set — skipping send") + print("[tg_digest] Digest would be:\n") + print(text) + return False + + url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendMessage" + body = json.dumps({ + "chat_id": TELEGRAM_CHAT_ID, + "text": text, + "parse_mode": "Markdown", + "disable_web_page_preview": False, + }).encode() + + try: + req = urllib.request.Request(url, data=body, headers={"Content-Type": "application/json"}) + with urllib.request.urlopen(req, timeout=15) as resp: + result = json.loads(resp.read().decode()) + if result.get("ok"): + print(f"[tg_digest] Sent digest to Telegram chat {TELEGRAM_CHAT_ID}") + return True + else: + print(f"[tg_digest] Telegram API error: {result}", file=sys.stderr) + return False + except Exception as e: + print(f"[tg_digest] ERROR sending Telegram: {e}", file=sys.stderr) + return False + +# --- Main --- + +def main(): + videos = fetch_videos() + digest = build_digest(videos) + print(digest) + print("") + send_telegram(digest) + +if __name__ == "__main__": + main()