import os import praw from config import get_reddit, Config import time class ModBot: def __init__(self): import os self.reddit = get_reddit() self.subreddit = self.reddit.subreddit(Config.SUBREDDIT) self.config_path = os.path.join(os.path.dirname(__file__), 'config', 'config.yaml') self.triggers = [] self.comments = [] self.commented_posts = set() self.commented_posts_file = os.path.join(os.path.dirname(__file__), 'DB', 'commented_posts.txt') self.ensure_config_file() self.load_commented_posts() def ensure_config_file(self): import os if not os.path.exists(self.config_path): default_yaml = ( 'triggers:\n' ' - trigger: help\n' ' comment: |\n' ' Thank you for your report!\n' ' This post is now approved.\n' ' - trigger: question\n' ' comment: |\n' ' This post has been approved.\n' ' Your question will be answered soon.\n' ) os.makedirs(os.path.dirname(self.config_path), exist_ok=True) with open(self.config_path, 'w', encoding='utf-8') as f: f.write(default_yaml) def fetch_yaml_config(self): import yaml try: wiki_page = Config.WIKI_PAGE wiki = self.subreddit.wiki[wiki_page] wiki_content = wiki.content_md self._wiki_revision_id = getattr(wiki, 'revision_id', None) config = yaml.safe_load(wiki_content) self.triggers = [] self.comments = [] self.statuses = [] self.tag_comments = {} self.tag_statuses = {} for entry in config.get('triggers', []): self.triggers.append(entry.get('trigger', '').strip()) self.comments.append(entry.get('comment', '').strip()) self.statuses.append(entry.get('status', 'enabled').strip().lower()) for entry in config.get('post_tags', []): tags_str = entry.get('tag', '').strip() comment = entry.get('comment', '').strip() status = entry.get('status', 'enabled').strip().lower() tags = [t.strip().lower() for t in tags_str.split(',') if t.strip()] for tag in tags: self.tag_comments[tag] = comment self.tag_statuses[tag] = status except Exception as e: print(f"Error fetching YAML config from wiki: {e}") def load_commented_posts(self): try: with open(self.commented_posts_file, 'r', encoding='utf-8') as f: for line in f: self.commented_posts.add(line.strip()) except FileNotFoundError: pass def save_commented_post(self, post_id): self.commented_posts.add(post_id) with open(self.commented_posts_file, 'a', encoding='utf-8') as f: f.write(post_id + '\n') def run(self): import threading print("ModReplyBot started. Watching for comments and new posts...") try: self.fetch_yaml_config() print(f"Triggers loaded: {self.triggers}") print(f"Tag comments loaded: {self.tag_comments}") print(f"Reddit user: {self.reddit.user.me()}") print(f"Subreddit: {self.subreddit.display_name}") except Exception as e: print(f"Startup error: {e}") return def comment_watcher(): last_revision = None while True: try: old_revision = last_revision self.fetch_yaml_config() new_revision = getattr(self, '_wiki_revision_id', None) if old_revision and new_revision and old_revision != new_revision: print("Wiki config changed, reloading triggers and tag comments.") self.notify_mods_config_change(new_revision) last_revision = new_revision for comment in self.subreddit.stream.comments(skip_existing=True): self.handle_comment(comment) except Exception as e: print(f"Comment watcher error: {e}") # No sleep needed, stream blocks def submission_watcher(): seen_submissions = set() last_revision = None while True: try: old_revision = last_revision self.fetch_yaml_config() new_revision = getattr(self, '_wiki_revision_id', None) if old_revision and new_revision and old_revision != new_revision: print("Wiki config changed, reloading triggers and tag comments.") self.notify_mods_config_change(new_revision) last_revision = new_revision new_submissions = list(self.subreddit.new(limit=10)) found_submission = False for submission in new_submissions: if submission.id not in seen_submissions: found_submission = True seen_submissions.add(submission.id) self.handle_submission(submission) if not found_submission: print("No submissions detected in new() this cycle.") except Exception as e: print(f"Submission watcher error: {e}") import time time.sleep(5) threading.Thread(target=comment_watcher, daemon=True).start() threading.Thread(target=submission_watcher, daemon=True).start() # Keep main thread alive while True: import time time.sleep(60) def handle_submission(self, submission): # Respond to new posts based on tag(s) in title import re title = submission.title print(f"New post detected: {submission.id} | Title: {title}") tag_matches = re.findall(r"\[(.+?)\]", title) print(f"Tags found in title: {tag_matches}") matched_comment = None matched_tag = None matched_status = None for tag in tag_matches: tag_lower = tag.strip().lower() if tag_lower in self.tag_comments: matched_comment = self.tag_comments[tag_lower] matched_tag = tag_lower matched_status = self.tag_statuses.get(tag_lower, 'enabled') break if matched_comment: if submission.id in self.commented_posts: print(f"Already auto-commented on post {submission.id}, skipping.") return try: footer = "^I ^am ^a ^bot ^and ^this ^comment ^was ^made ^automatically. ^Message ^the ^Mod ^team ^if ^I'm ^not ^working ^correctly." comment_text = matched_comment.replace("{{author}}", submission.author.name if submission.author else "unknown") + "\n\n" + footer if matched_status == 'enabled': comment = submission.reply(comment_text) comment.mod.distinguish(sticky=True) print(f"Commented on new post {submission.id} with tag [{matched_tag}] (auto)") self.save_commented_post(submission.id) elif matched_status == 'log-only': print(f"Log-only: Did not comment on post {submission.id} with tag [{matched_tag}] (auto)") self.save_commented_post(submission.id) elif matched_status == 'disabled': print(f"Disabled: Did not comment/log for post {submission.id} with tag [{matched_tag}] (auto)") else: print(f"Unknown status '{matched_status}' for post {submission.id} with tag [{matched_tag}] (auto)") except Exception as e: print(f"Error commenting on new post: {e}") else: print(f"No matching tag found for post {submission.id}") def handle_comment(self, comment): comment_body = comment.body.lower() for idx, trigger in enumerate(self.triggers): expected = f"!{trigger.lower()}" words = [w.strip() for w in comment_body.split()] # Only respond if author is a moderator if expected in words and comment.author and comment.author in self.subreddit.moderator(): status = self.statuses[idx] if idx < len(self.statuses) else 'enabled' if status == 'disabled': print(f"Trigger '{trigger}' is disabled. Skipping.") continue try: comment.mod.remove() print(f"Removed triggering comment: {comment.id}") except Exception as e: print(f"Error removing comment: {e}") submission = comment.submission self.fetch_yaml_config() self.approve_and_comment(submission, self.comments[idx], status) break def approve_and_comment(self, submission, comment_text, status='enabled'): try: submission.mod.approve() if status == 'enabled': comment = submission.reply(comment_text) comment.mod.distinguish(sticky=True) print(f"Approved and commented on: {submission.id}") self.save_commented_post(submission.id) elif status == 'log-only': print(f"Log-only: Approved but did not comment on: {submission.id}") self.save_commented_post(submission.id) elif status == 'disabled': print(f"Disabled: Did not approve/comment/log for: {submission.id}") else: print(f"Unknown status '{status}' for submission {submission.id}") except Exception as e: print(f"Error approving/commenting: {e}") def notify_mods_config_change(self, revision_id): try: subject = "ModReplyBot config wiki changed" body = f"The config wiki page was updated (revision: {revision_id}).\n\nBot restarted and is running successfully." data = { "subject": subject, "text": body, "to": f"/r/{Config.SUBREDDIT}", } self.reddit.post("api/compose/", data=data) print("Sent modmail notification about config change.") except Exception as e: print(f"Error sending modmail notification: {e}") if __name__ == "__main__": bot = ModBot() bot.run()