From 8070ab73dda5d3dfd0155f788cbd7761e1bd1bd4 Mon Sep 17 00:00:00 2001 From: ChiefGyk3D <19499446+ChiefGyk3D@users.noreply.github.com> Date: Tue, 14 Jul 2026 19:13:52 -0400 Subject: [PATCH] fix(youtube): resume from last posted video after interrupted batch Previously check_for_new_videos() advanced and persisted last_video_id to the newest video BEFORE the daemon posted the batch. If the process was killed mid-batch (e.g. container restart), the unposted videos were silently marked as seen and never posted. Now state is advanced per-video via a new mark_posted() method that the daemon calls after each video is successfully handled, so an interrupted batch resumes from the next unposted video. --- boon_tube_daemon/main.py | 5 +++++ boon_tube_daemon/media/youtube_videos.py | 19 ++++++++++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/boon_tube_daemon/main.py b/boon_tube_daemon/main.py index 8639dc1..0638528 100644 --- a/boon_tube_daemon/main.py +++ b/boon_tube_daemon/main.py @@ -179,6 +179,11 @@ def check_platforms(self): logger.info(f" ⏱ Waiting {multi_video_delay}s before posting next video...") time.sleep(multi_video_delay) self.notify_new_video(platform, video_data) + # Persist progress after each video so an interrupted batch + # resumes from the next unposted video instead of skipping + # the remainder if the process is killed mid-batch. + if hasattr(platform, 'mark_posted'): + platform.mark_posted(video_data) else: # Legacy single-video path (TikTok etc.) is_new, video_data = platform.check_for_new_video() diff --git a/boon_tube_daemon/media/youtube_videos.py b/boon_tube_daemon/media/youtube_videos.py index 8107df7..77986b2 100644 --- a/boon_tube_daemon/media/youtube_videos.py +++ b/boon_tube_daemon/media/youtube_videos.py @@ -90,6 +90,18 @@ def _save_state(self): logger.debug(f"💾 Saved YouTube state to {state_file}") except Exception as e: logger.warning("⚠ Could not save YouTube state") + + def mark_posted(self, video_data: dict): + """Advance and persist the last-posted marker after a video is posted. + + Called by the daemon after each video is successfully handled so that an + interrupted batch resumes from the next unposted video rather than + silently skipping the remainder. + """ + video_id = video_data.get('video_id') + if video_id: + self.last_video_id = video_id + self._save_state() def authenticate(self) -> bool: """Authenticate with YouTube API.""" @@ -444,9 +456,10 @@ def check_for_new_videos(self, username: Optional[str] = None) -> List[dict]: if len(new_videos) > 1: logger.info(f"📦 YouTube: {len(new_videos)} new videos detected since last check") - # Update state to newest - self.last_video_id = newest_id - self._save_state() + # NOTE: Do NOT advance last_video_id here. State is advanced per-video via + # mark_posted() after each video is successfully posted, so an interrupted + # batch (e.g. container restart) resumes from the next unposted video + # instead of silently skipping the remainder. return new_videos def _resolve_channel_id(self, username: str) -> Optional[str]: