""" Engagement polling service. Periodically fetches score, comments, and awards for recent successful Reddit posts and records snapshots in the engagement table. """ from __future__ import annotations import asyncio import logging from app.db.store import Store logger = logging.getLogger(__name__) def _poll_sync(db_path: str, max_age_hours: int = 72, recheck_hours: int = 1) -> dict: store = Store(db_path) try: posts = store.list_posts_needing_poll(max_age_hours, recheck_hours) if not posts: logger.debug("Engagement poll: no posts due for a check") return {"polled": 0, "errors": 0} # Import here to avoid circular deps; session validation deferred to first use. from app.services.reddit.client import RedditClient try: client = RedditClient() except Exception: logger.warning( "Reddit session unavailable — engagement poll will run without auth" ) client = None polled = errors = 0 for post in posts: try: if client is not None: stats = client.fetch_stats(post["url"]) else: # Unauthenticated fallback: still works for public posts. import re import httpx match = re.search(r"/comments/([a-z0-9]+)/", post["url"]) if not match: continue resp = httpx.get( f"https://www.reddit.com/by_id/t3_{match.group(1)}.json", headers={"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) Chrome/124.0.0.0"}, timeout=15, ) children = resp.json().get("data", {}).get("children", []) if resp.status_code == 200 else [] if not children: continue d = children[0].get("data", {}) stats = { "score": d.get("score"), "upvotes": d.get("ups"), "comments": d.get("num_comments"), "awards": d.get("total_awards_received", 0), } if stats: store.record_engagement( post["id"], score=stats.get("score"), upvotes=stats.get("upvotes"), comments=stats.get("comments"), awards=stats.get("awards", 0), ) logger.debug( "Post %d engagement: score=%s comments=%s", post["id"], stats.get("score"), stats.get("comments"), ) polled += 1 except Exception: logger.exception("Failed to poll engagement for post %d", post["id"]) errors += 1 logger.info("Engagement poll done: %d polled, %d errors", polled, errors) return {"polled": polled, "errors": errors} finally: store.close() async def poll_recent_posts( db_path: str, max_age_hours: int = 72, recheck_hours: int = 1 ) -> dict: """Async wrapper for the scheduler and API trigger.""" return await asyncio.to_thread(_poll_sync, db_path, max_age_hours, recheck_hours)