diff --git a/.gitignore b/.gitignore index fcc3a3d..8d77223 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ config/* tests.py message.md +docker/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 371b6ea..913ab86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,25 @@ # Changelog +## [2.2.1] - 2026-03-14 + +### Added +- **Post Backfill on Startup:** + - Added `backfill_recent_posts()` to automatically scan and comment on posts from the last 24 hours when bot starts up. + - Prevents missed posts when bot is offline for extended periods. + - Only processes posts created in the last 24 hours to avoid overwhelming backfill. +- **Duplicate Comment Detection:** + - Bot now checks if the exact comment has already been posted on a post before commenting again. + - Prevents duplicate comments on the same post. + +### Changed +- Enhanced tag_post_watcher with better debug output and attempt tracking. +- Startup now prioritizes backfill thread for catching missed posts. + +### Fixed +- Fixed issue where new submissions stream wasn't being processed (skip_existing=True issue). +- Improved stream error handling with attempt counter and better exception messages. + ## [2.2.0] - 2026-03-12 ### Added diff --git a/__pycache__/update_checker.cpython-314.pyc b/__pycache__/update_checker.cpython-314.pyc new file mode 100644 index 0000000..95a5cc1 Binary files /dev/null and b/__pycache__/update_checker.cpython-314.pyc differ diff --git a/docker-compose.yml b/docker-compose.yml index 681eb09..3cc1bfc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,6 +5,6 @@ services: env_file: - .env volumes: - - ./config:/app/config - - ./DB:/app/DB + - ./docker/config:/app/config + - ./docker/DB:/app/DB restart: unless-stopped diff --git a/modreplybot.py b/modreplybot.py index 4ee877f..780bb4c 100644 --- a/modreplybot.py +++ b/modreplybot.py @@ -3,7 +3,7 @@ import praw from config import get_reddit, Config from update_checker import start_update_checker -BOT_VERSION = "2.2.0" # Change this for new releases +BOT_VERSION = "2.2.1" # Change this for new releases BOT_NAME = "ModReplyBot" # Change this if bot name changes import time @@ -254,11 +254,99 @@ class ModReplyBot: threading.Thread(target=mod_report_watcher, daemon=True).start() threading.Thread(target=self.chat_message_watcher, daemon=True).start() + def backfill_recent_posts(): + """Scan recent posts from last 24 hours that bot may have missed while offline.""" + print("[BACKFILL] Starting backfill of recent posts (last 24 hours)...") + import time + current_time = time.time() + one_day_ago = current_time - (24 * 3600) # 24 hours in seconds + backfill_count = 0 + + try: + # Scan new posts, stopping when we hit posts older than 24 hours + for submission in self.subreddit.new(limit=1000): + if submission.created_utc < one_day_ago: + print(f"[BACKFILL] Reached posts older than 24 hours, stopping backfill.") + break + + if submission.id in self.commented_posts: + continue # Already processed + + flair = (getattr(submission, 'link_flair_text', '') or '').strip().lower() + title = submission.title.strip() + import re + title_tags = re.findall(r'\[(.*?)\]', title) + title_tags_lower = [t.strip().lower() for t in title_tags] + + print(f"[BACKFILL] Checking post {submission.id}: title='{title}', flair='{flair}', title_tags={title_tags_lower}") + + # Ignore if any ignore_tag matches + ignore = False + if flair in self.ignore_tags: + ignore = True + else: + for tag in title_tags_lower: + if tag in self.ignore_tags: + ignore = True + break + + if ignore: + print(f"[BACKFILL] Ignoring post {submission.id} due to ignore tag.") + continue + + matched_tag = None + if flair in self.tag_comments: + matched_tag = flair + else: + for tag in title_tags_lower: + if tag in self.tag_comments: + matched_tag = tag + break + + if matched_tag: + status = self.tag_statuses.get(matched_tag, 'enabled') + comment_text = self.tag_comments[matched_tag] + flair_id = self.tag_flair_ids.get(matched_tag, '') + + # Check if bot has already commented this exact text on the post + try: + submission.comments.replace_more(limit=0) + bot_already_commented = False + for comment in submission.comments.list(): + if comment.author and comment.author.name == self.reddit.user.me().name: + if comment.body == comment_text: + bot_already_commented = True + print(f"[BACKFILL] Bot already posted this comment on {submission.id}, skipping.") + break + + if bot_already_commented: + self.save_commented_post(submission.id) + continue + except Exception as e: + print(f"[BACKFILL] Error checking existing comments on {submission.id}: {e}") + + if flair_id: + try: + submission.flair.select(flair_id) + print(f"[BACKFILL] Set flair '{flair_id}' for post {submission.id}") + except Exception as e: + print(f"[BACKFILL] Error setting flair '{flair_id}' for post {submission.id}: {e}") + print(f"[BACKFILL] Auto-commenting on post {submission.id} with tag '{matched_tag}'") + self.comment_only(submission, comment_text) + backfill_count += 1 + except Exception as e: + print(f"[BACKFILL] Error during backfill: {e}") + print(f"[BACKFILL] Backfill complete. Commented on {backfill_count} posts, switching to stream mode.") + def tag_post_watcher(): + print("[TAG WATCH] Starting tag post watcher thread...") + attempt = 0 while True: try: - # Check new submissions + attempt += 1 + print(f"[TAG WATCH] Attempt {attempt}: Waiting for new submissions...") for submission in self.subreddit.stream.submissions(skip_existing=True): + print(f"[TAG WATCH] GOT SUBMISSION: {submission.id}") flair = (getattr(submission, 'link_flair_text', '') or '').strip().lower() title = submission.title.strip() import re @@ -367,6 +455,9 @@ class ModReplyBot: threading.Thread(target=tag_post_watcher, daemon=True).start() threading.Thread(target=modqueue_watcher, daemon=True).start() + + # Start backfill thread first so it catches posts from when bot was offline + threading.Thread(target=backfill_recent_posts, daemon=True).start() # Keep main thread alive while True: