From df2273a81c5d08aae7cd5a6b45f71de47c5e90d0 Mon Sep 17 00:00:00 2001 From: smolkik-code Date: Wed, 17 Jun 2026 12:37:44 +0700 Subject: [PATCH] feat: use reels_tray for stories (single request for all users) - /feed/reels_tray/ fetches all stories in one API call - Stories sent before posts (they're more time-sensitive) - Per-user story fetch kept as fallback - Much faster: 1 request vs 85 individual requests --- instagram_monitor.py | 104 +++++++++++++++++++++++++++++-------------- 1 file changed, 70 insertions(+), 34 deletions(-) diff --git a/instagram_monitor.py b/instagram_monitor.py index 2cbe362..cd9c21b 100644 --- a/instagram_monitor.py +++ b/instagram_monitor.py @@ -156,8 +156,6 @@ class InstagramMonitor: timeout=30, ) if resp.status_code != 200: - if resp.status_code != 404: - logger.debug(f"Stories {user_pk}: HTTP {resp.status_code}") return [] data = resp.json() @@ -171,14 +169,49 @@ class InstagramMonitor: if media_id in self._state: continue stories.append(item) - - if stories: - logger.info(f"Found {len(stories)} new stories for user {user_pk}") return stories except Exception as e: logger.error(f"Error fetching stories for {user_pk}: {e}") return [] + def _get_all_stories(self) -> dict[int, list[dict]]: + """Fetch all stories from reels_tray (faster, single request).""" + user_stories: dict[int, list[dict]] = {} + try: + resp = self.session.get( + f"{API_BASE}/feed/reels_tray/", + timeout=30, + ) + if resp.status_code != 200: + logger.warning(f"reels_tray: HTTP {resp.status_code} - {resp.text[:200]}") + return user_stories + + data = resp.json() + trays = data.get("tray", []) + logger.info(f"reels_tray: found {len(trays)} users with stories") + + for tray in trays: + user_info = tray.get("user", {}) + user_pk = user_info.get("pk", 0) + if not user_pk: + continue + + items = tray.get("items", []) + stories = [] + for item in items: + media_id = str(item.get("pk", item.get("id", ""))) + if media_id in self._state: + continue + stories.append(item) + + if stories: + user_stories[user_pk] = stories + + return user_stories + except Exception as e: + logger.error(f"Error fetching reels_tray: {e}") + return user_stories + def _get_user_posts(self, user_pk: int) -> list[dict]: try: resp = self.session.get( @@ -283,40 +316,44 @@ class InstagramMonitor: followees = self._get_followees() logger.info(f"Checking {len(followees)} followees") + # Fetch all stories at once via reels_tray + all_stories = self._get_all_stories() + total_stories = sum(len(s) for s in all_stories.values()) + logger.info(f"Found {total_stories} new stories from {len(all_stories)} users") + + # Send stories first + for user_pk, stories in all_stories.items(): + username = next( + (u["username"] for u in followees if u["pk"] == user_pk), + str(user_pk), + ) + for story in stories: + media_path = await asyncio.to_thread( + self._download_media, story, username, True + ) + if media_path: + media_id = str(story.get("pk", story.get("id", ""))) + self._state[media_id] = datetime.utcnow().isoformat() + self._save_state() + yield { + "type": "story", + "username": username, + "media_path": media_path, + "caption": "", + "shortcode": media_id, + } + await asyncio.to_thread(self._random_delay, 0.5, 1.5) + + # Then fetch posts in parallel semaphore = asyncio.Semaphore(config.concurrent_checks) - async def process_followee(user: dict) -> list[dict]: + async def process_followee_posts(user: dict) -> list[dict]: username = user["username"] user_pk = user["pk"] results = [] async with semaphore: - self._random_delay(1, 3) - - # Fetch stories and posts in parallel - stories_task = asyncio.to_thread(self._get_user_stories, user_pk) - posts_task = asyncio.to_thread(self._get_user_posts, user_pk) - stories, posts = await asyncio.gather(stories_task, posts_task) - - if stories: - logger.info(f"@{username}: {len(stories)} new stories") - - for story in stories: - media_path = await asyncio.to_thread( - self._download_media, story, username, True - ) - if media_path: - media_id = str(story.get("pk", story.get("id", ""))) - self._state[media_id] = datetime.utcnow().isoformat() - self._save_state() - results.append({ - "type": "story", - "username": username, - "media_path": media_path, - "caption": "", - "shortcode": media_id, - }) - await asyncio.to_thread(self._random_delay, 0.5, 1.5) + posts = await asyncio.to_thread(self._get_user_posts, user_pk) if posts: logger.info(f"@{username}: {len(posts)} new posts") @@ -342,8 +379,7 @@ class InstagramMonitor: return results - # Process all followees in parallel (with semaphore limit) - tasks = [process_followee(user) for user in followees] + tasks = [process_followee_posts(user) for user in followees] done_results = await asyncio.gather(*tasks, return_exceptions=True) for result in done_results: