From 95410c64bf730ced6008e8b771b22518947d2336 Mon Sep 17 00:00:00 2001 From: Slfhstd Date: Sun, 8 Mar 2026 21:32:51 +0000 Subject: [PATCH] ModReplyBot 1.0.0 release --- .gitignore | 1 + Dockerfile | 1 + README.md | 11 +++--- docker-compose.yml | 3 +- modreplybot.py | 87 ++++++++++++++++++++++++++++++---------------- 5 files changed, 67 insertions(+), 36 deletions(-) diff --git a/.gitignore b/.gitignore index 4c49bd7..2337a5f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .env +config/* \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 04a1782..77d3a37 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,4 +5,5 @@ COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY config.py . COPY modreplybot.py . +ENV PYTHONUNBUFFERED=1 CMD ["python", "modreplybot.py"] diff --git a/README.md b/README.md index 3ab0feb..1b94d79 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,18 @@ # ModReplyBot Reddit Bot -This bot watches a subreddit for moderator reports containing triggers, approves posts, and leaves stickied comments. Triggers and comments are configured via a subreddit wiki page. All other settings are handled via environment variables. +This bot watches a subreddit for moderator reports containing triggers, approves posts, and leaves stickied comments. Triggers and comments are configured via a local YAML file. All other settings are handled via environment variables. ## Features - Watches for moderator reports with triggers - Approves posts and leaves stickied comments -- Triggers/comments configured via subreddit wiki +- Triggers/comments configured via config/config.yaml - Supports multiple triggers/comments - Docker and baremetal support ## Configuration -### 1. Subreddit Wiki Page -Create a wiki page (e.g. `modreplybot-config`) in your subreddit using Automoderator YAML format. Example: +### 1. Trigger and Comment Configuration +Edit the file at `config/config.yaml` in your project directory. Example: ``` triggers: @@ -27,7 +27,7 @@ triggers: Your question will be answered soon. ``` -Each entry under `triggers` defines a trigger and its associated multi-line comment. +Each entry under `triggers` defines a trigger and its associated multi-line comment. The bot will automatically create this file with example content if it does not exist. ### 2. Environment Variables Create a `.env` file (or set env variables directly) with: @@ -39,7 +39,6 @@ REDDIT_USERNAME=your_username REDDIT_PASSWORD=your_password REDDIT_USER_AGENT=modreplybot by /u/your_username REDDIT_SUBREDDIT=your_subreddit -REDDIT_WIKI_PAGE=modreplybot-config ``` ## Installation diff --git a/docker-compose.yml b/docker-compose.yml index 0f6e73b..4b75287 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,8 +1,9 @@ -version: '3.8' services: modbot: image: slfhstd.uk/slfhstd/modreplybot:latest container_name: modreplybot env_file: - .env + volumes: + - ./config:/app/config restart: unless-stopped diff --git a/modreplybot.py b/modreplybot.py index e82fdf2..d030661 100644 --- a/modreplybot.py +++ b/modreplybot.py @@ -5,51 +5,80 @@ import time class ModBot: def __init__(self): + import os self.reddit = get_reddit() self.subreddit = self.reddit.subreddit(Config.SUBREDDIT) - self.wiki_page = Config.WIKI_PAGE + self.config_path = os.path.join(os.path.dirname(__file__), 'config', 'config.yaml') self.triggers = [] self.comments = [] + self.ensure_config_file() - def fetch_wiki_config(self): + 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_content = self.subreddit.wiki[self.wiki_page].content_md - # Example Automoderator YAML format: - # triggers: - # - trigger: help - # comment: | - # Thank you for your report! - # This post is now approved. - # - trigger: question - # comment: | - # This post has been approved. - config = yaml.safe_load(wiki_content) + with open(self.config_path, 'r', encoding='utf-8') as f: + config = yaml.safe_load(f) self.triggers = [] self.comments = [] for entry in config.get('triggers', []): self.triggers.append(entry.get('trigger', '').strip()) self.comments.append(entry.get('comment', '').strip()) except Exception as e: - print(f"Error fetching wiki config: {e}") + print(f"Error fetching YAML config: {e}") def run(self): - print("ModBot started. Watching for mod reports...") - while True: - self.fetch_wiki_config() - for report in self.subreddit.mod.reports(limit=25): - self.handle_report(report) - time.sleep(30) # Poll every 30 seconds - - def handle_report(self, report): - if not hasattr(report, 'mod_reports') or not report.mod_reports: + print("ModReplyBot started. Watching for comments...") + try: + self.fetch_yaml_config() + print(f"Triggers loaded: {self.triggers}") + 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 - for mod_report in report.mod_reports: - report_text = mod_report[0].lower() - for idx, trigger in enumerate(self.triggers): - if trigger.lower() in report_text: - self.approve_and_comment(report, self.comments[idx]) - break + while True: + try: + self.fetch_yaml_config() + for comment in self.subreddit.stream.comments(skip_existing=True): + self.handle_comment(comment) + except Exception as e: + print(f"Main loop error: {e}") + time.sleep(5) # Poll every 5 seconds + + def handle_comment(self, comment): + comment_body = comment.body.lower() + for idx, trigger in enumerate(self.triggers): + expected = f"!{trigger.lower()}" + if expected in comment_body: + # Remove the triggering comment + try: + comment.mod.remove() + print(f"Removed triggering comment: {comment.id}") + except Exception as e: + print(f"Error removing comment: {e}") + # Approve the submission and post bot's comment + submission = comment.submission + self.fetch_yaml_config() + self.approve_and_comment(submission, self.comments[idx]) + break def approve_and_comment(self, submission, comment_text): try: