diff --git a/src/news/monitor.py b/src/news/monitor.py index 56954ee..6675223 100644 --- a/src/news/monitor.py +++ b/src/news/monitor.py @@ -36,6 +36,16 @@ _SHORT_TOKEN_MAXLEN = 4 # Límite de Telegram 4096; troceamos por debajo con margen. _TG_LIMIT = 4000 +# Reddit sin OAuth ratelimita por IP con ventana larga (>30s verificado): la 2ª +# petición seguida a reddit.com devuelve 429 SIEMPRE, así que solo se pollea un +# feed de reddit por run, rotando. Contador por proceso: tras un reinicio se +# empieza por el primero, lo cual es inocuo. +_reddit_turn = 0 + + +def _is_reddit(feed: dict) -> bool: + return "reddit.com" in feed["url"] + @dataclass class NewsItem: @@ -89,13 +99,32 @@ async def poll_feeds(db, settings) -> list[NewsItem]: Best-effort: cada feed va en su propio try/except — uno caído nunca tumba el run. feedparser.parse es bloqueante (I/O de red), así que se ejecuta en un hilo para no congelar el event loop del bot durante el poll. + + Reddit: solo se pollea UN feed de reddit.com por run, rotando entre ellos + (ver _reddit_turn) — dos peticiones seguidas garantizan un 429 en la 2ª. """ + global _reddit_turn keywords = [k.strip() for k in settings.NEWS_KEYWORDS.split(",") if k.strip()] new_items: list[NewsItem] = [] + reddit_feeds = [f for f in FEEDS if _is_reddit(f)] + skip: set[str] = set() + if len(reddit_feeds) > 1: + chosen = reddit_feeds[_reddit_turn % len(reddit_feeds)] + _reddit_turn += 1 + skip = {f["name"] for f in reddit_feeds if f["name"] != chosen["name"]} + for feed in FEEDS: + if feed["name"] in skip: + continue try: parsed = await asyncio.to_thread(feedparser.parse, feed["url"], agent=UA) + status = getattr(parsed, "status", None) + # Un 429/403 llega con bozo=False y 0 entries: sin este guard se + # tragaba en silencio (así estuvo r/UFOB sin sembrar desde F1). + if status and status >= 400: + log.warning("feed %s HTTP %s; salto", feed["name"], status) + continue if parsed.bozo and not parsed.entries: log.warning("feed bozo sin entries: %s (%s)", feed["name"], getattr(parsed, "bozo_exception", ""))