feat(news): F2 — scheduler automático del monitor RSS (inerte)
Build & Deploy ResearchOwl / build-and-push (push) Successful in 7s
Build & Deploy ResearchOwl / build-and-push (push) Successful in 7s
Engancha el monitor de noticias al scheduler loop EXISTENTE de watched_topics (sin segunda task asyncio). Cada tick, si NEWS_ENABLED, y si ha pasado NEWS_POLL_INTERVAL_HOURS desde el último poll (estado en memoria), pollea los feeds, empuja los pendientes (notified=0) al chat destino (NEWS_CHAT_ID o 1er TELEGRAM_ALLOWED_USERS) y los marca. Best-effort: la rama de noticias va en su propio try/except — un fallo del news-poll NUNCA tumba el scheduler de watched_topics. Deploy inerte: NEWS_ENABLED sigue False; el flip se hará aparte en k8s-manifests. - db: get_unnotified(limit=None) — pendientes, recientes primero (NULLS LAST) - bot: rama news al final del tick del scheduler, reusa poll_feeds / item_from_row / format_digest de F1; "…y N más" si excede NEWS_MAX_ITEMS Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
c0b0c1ba0a
commit
b1c5bc737d
+38
-1
@@ -5,7 +5,7 @@ Main user interface — all commands handled here
|
||||
import asyncio
|
||||
import os
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Optional
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
@@ -879,6 +879,9 @@ async def _safe_send(bot, chat_id, text: str):
|
||||
|
||||
|
||||
async def _scheduler_loop(app: Application):
|
||||
# Estado en memoria (no en DB): en un reinicio re-polla una vez, inofensivo
|
||||
# porque solo se empujan items notified=0 y se marcan tras enviarlos.
|
||||
_last_news_poll: Optional[datetime] = None
|
||||
while True:
|
||||
db_conn = None
|
||||
try:
|
||||
@@ -938,6 +941,40 @@ async def _scheduler_loop(app: Application):
|
||||
app.bot, chat_id,
|
||||
f"🔄 Investigación automática iniciada: `{topic}`"
|
||||
)
|
||||
|
||||
# --- Monitor de noticias (F2) — inerte si NEWS_ENABLED=False.
|
||||
# Best-effort: un fallo del news-poll NUNCA tumba el scheduler de
|
||||
# watched_topics (va en su propio try/except).
|
||||
if settings.NEWS_ENABLED:
|
||||
now = datetime.now(timezone.utc)
|
||||
interval = timedelta(hours=settings.NEWS_POLL_INTERVAL_HOURS)
|
||||
due_news = (_last_news_poll is None
|
||||
or (now - _last_news_poll) >= interval)
|
||||
if due_news:
|
||||
_last_news_poll = now # marca antes de pollear: evita reintentos en bucle si falla
|
||||
news_chat_id = settings.news_chat_id
|
||||
if not news_chat_id:
|
||||
logger.warning("NEWS_ENABLED pero sin chat destino; salto poll")
|
||||
else:
|
||||
try:
|
||||
await poll_feeds(db, settings) # inserta novedades notified=0
|
||||
pending = await db.get_unnotified() # TODOS los pendientes
|
||||
if pending:
|
||||
shown = pending[:settings.NEWS_MAX_ITEMS]
|
||||
extra = len(pending) - len(shown)
|
||||
items = [item_from_row(r) for r in shown]
|
||||
chunks = format_digest(items, header="🛸 Novedades UAP/OVNI")
|
||||
if extra > 0 and chunks:
|
||||
chunks[-1] += f"\n…y {extra} más."
|
||||
for c in chunks:
|
||||
await app.bot.send_message(
|
||||
chat_id=news_chat_id, text=c,
|
||||
disable_web_page_preview=False,
|
||||
)
|
||||
# Marca todos los pendientes (incluidos los "…y N más")
|
||||
await db.mark_news_notified([r["id"] for r in pending])
|
||||
except Exception as e:
|
||||
logger.warning("News poll/notify falló", error=str(e))
|
||||
except Exception as e:
|
||||
logger.warning("Scheduler loop error", error=str(e))
|
||||
finally:
|
||||
|
||||
Reference in New Issue
Block a user