9 Commits

Author SHA1 Message Date
slfhstd f4faabe44d changes 2026-03-11 00:06:12 +00:00
slfhstd 87958ba09c 2.1.2 2026-03-10 23:52:19 +00:00
slfhstd 592521026a V2.1.1 2026-03-10 23:48:39 +00:00
slfhstd a2faff2853 changelog 2026-03-10 23:38:34 +00:00
slfhstd 12f62cfb24 V2.1.0 2026-03-10 23:36:40 +00:00
slfhstd ca98a3b466 guess 2026-03-10 22:40:17 +00:00
slfhstd af70310743 formatting 2026-03-10 22:39:11 +00:00
slfhstd 21cbad7111 formatting 2026-03-10 22:38:05 +00:00
slfhstd ca0f165257 formatting fixes 2026-03-10 22:36:57 +00:00
6 changed files with 211 additions and 119 deletions
+2
View File
@@ -1,2 +1,4 @@
.env .env
config/* config/*
tests.py
message.md
+33 -1
View File
@@ -1,5 +1,37 @@
# Changelog # Changelog
## [2.1.2] - 2026-03-10
### Fixed
- Prevented AttributeError in modqueue watcher by checking for the 'title' attribute before accessing it. This ensures safe handling of Comment objects that do not have a 'title'.
## [2.1.1] - 2026-03-10
### Fixed
- Prevented AttributeError in modqueue watcher and tag_post_watcher by checking for 'link_flair_text' only on Submission objects. This avoids errors when processing Comment objects.
### Changed
- Updated logic in modqueue watcher and tag_post_watcher to safely handle both Submission and Comment objects.
## [2.1.0] - 2026-03-10
### Changed
- Improved tag_post_watcher and modqueue_watcher logic: now runs modqueue watcher in a dedicated thread for reliable detection and commenting on filtered/removed posts.
- Enhanced debug output for modqueue posts, including detailed attribute printing.
- Updated .gitignore to include tests.py.
- Updated DB/commented_posts.txt and DB/chat_wiki_requests.txt for persistent tracking.
### Fixed
- Fixed issue where bot did not comment on posts in modqueue due to threading/loop logic.
- Fixed detection of automod_filtered/removed posts in modqueue.
### Added
- Added modqueue_watcher thread for independent modqueue processing.
- Added debug print statements for modqueue post attributes and loop entry.
---
## [V2 release] ## [V2 release]
### Major Features & Enhancements ### Major Features & Enhancements
@@ -35,4 +67,4 @@
- All modmail-based notifications and config reloads. - All modmail-based notifications and config reloads.
- Legacy approval logic and unnecessary config options. - Legacy approval logic and unnecessary config options.
---
+1
View File
@@ -35,3 +35,4 @@ o846f5z
5jubgcs 5jubgcs
5judtbe 5judtbe
5jugfpj 5jugfpj
5jv66nh
+19
View File
@@ -49,3 +49,22 @@
1rqbf4n 1rqbf4n
1rqbf4n 1rqbf4n
1rqbf4n 1rqbf4n
1rqcduj
1rqcma5
1rqcm8p
1rqcr1v
1rqct6y
1rqcr0o
1rqct5q
1rqcvk4
1rqcvit
1rqd94l
1rqd966
1rqd97c
1rqd98i
1rqd99u
1rqd9aw
1rqd9cc
1rqd9ds
1rqd966
1rqd9aw
+60 -82
View File
@@ -15,34 +15,12 @@ ModReplyBot is a Reddit bot for moderators that automates post approval and stic
## Configuration ## Configuration
### 1. Wiki Page Configuration ### 1. Wiki Page Configuration
Edit your subreddit wiki page (name set by `REDDIT_WIKI_PAGE` env variable) with YAML like: Edit your subreddit wiki page (name set by `REDDIT_WIKI_PAGE` env variable) with YAML like:
``` ```yaml
triggers: triggers:
- trigger: help
# ModReplyBot
ModReplyBot is a Reddit bot for moderators that automates post actions and stickied comments based on subreddit wiki configuration. It responds to mod comments and mod reports containing trigger phrases, auto-comments on posts with configured tags, and now supports chat-based config reloads. All configuration is managed via a subreddit wiki page and environment variables.
## Features
- Responds to moderator comments containing trigger phrases (starting with `!`)
- Responds to moderator reports containing trigger phrases (starting with `!`)
- Sets post flair, locks posts/comments, and posts stickied comments
- Posts automatic comments based on tags in post titles (e.g., `[Bedrock]`, `[Java]`)
- Triggers, tag comments, and bot config are managed via a subreddit wiki page
- Chat-based config reload: send a chat message containing `reload-config` to the bot from a moderator account to reload the wiki config
- Persistent database for auto-commented posts and processed chat requests (survives restarts and container recreations)
- Docker and baremetal support
## Configuration
### 1. Wiki Page Configuration
Edit your subreddit wiki page (name set by `REDDIT_WIKI_PAGE` env variable) with YAML like:
```
triggers:
- trigger: help - trigger: help
comment: | comment: |
Thank you for your report! Thank you for your report!
@@ -57,83 +35,83 @@ triggers:
Welcome to the community! Welcome to the community!
status: log-only status: log-only
post_tags: post_tags:
- tag: Bedrock, Java - tag: Bedrock, Java
comment: | comment: |
__[Click here if your post says "Sorry, this post was removed by Reddits filters"](...)__ __[Click here if your post says "Sorry, this post was removed by Reddits filters"](...)__
status: enabled status: enabled
flair_id: 12345678-aaaa-bbbb-cccc-1234567890ab flair_id: 12345678-aaaa-bbbb-cccc-1234567890ab
``` ```
- Triggers: Bot responds to mod comments and mod reports containing `!trigger` (e.g., `!help`) with the configured comment and actions. - Triggers: Bot responds to mod comments and mod reports containing `!trigger` (e.g., `!help`) with the configured comment and actions.
- post_tags: Bot posts the comment automatically on new posts with matching tags in the title and can set flair. - post_tags: Bot posts the comment automatically on new posts with matching tags in the title and can set flair.
- Status options: `enabled`, `log-only`, `disabled`. - Status options: `enabled`, `log-only`, `disabled`.
- Optional actions: `flair_id`, `stickied`, `lock_post`, `lock_comment`. - Optional actions: `flair_id`, `stickied`, `lock_post`, `lock_comment`.
### 2. Environment Variables ### 2. Environment Variables
Create a `.env` file (or set env variables directly) with: Create a `.env` file (or set env variables directly) with:
``` ```
REDDIT_CLIENT_ID=your_client_id REDDIT_CLIENT_ID=your_client_id
REDDIT_CLIENT_SECRET=your_client_secret REDDIT_CLIENT_SECRET=your_client_secret
REDDIT_USERNAME=your_username REDDIT_USERNAME=your_username
REDDIT_PASSWORD=your_password REDDIT_PASSWORD=your_password
REDDIT_USER_AGENT=modreplybot by /u/your_username REDDIT_USER_AGENT=modreplybot by /u/your_username
REDDIT_SUBREDDIT=your_subreddit REDDIT_SUBREDDIT=your_subreddit
REDDIT_WIKI_PAGE=modreplybot-config REDDIT_WIKI_PAGE=modreplybot-config
LOG_LEVEL=Default LOG_LEVEL=Default
``` ```
## Installation ## Installation
### Docker Compose (Recommended) ### Docker Compose (Recommended)
1. Copy `.env.example` to `.env` and fill in your values. 1. Copy `.env.example` to `.env` and fill in your values.
2. Run: 2. Run:
``` ```
docker compose up -d docker compose up -d
``` ```
* The DB folder is mounted for persistent database storage. * The DB folder is mounted for persistent database storage.
### Docker Run ### Docker Run
1. Copy `.env.example` to `.env` and fill in your values. 1. Copy `.env.example` to `.env` and fill in your values.
2. Run: 2. Run:
``` ```
docker run --env-file .env -v $(pwd)/DB:/app/DB slfhstd.uk/slfhstd/modreplybot:latest docker run --env-file .env -v $(pwd)/DB:/app/DB slfhstd.uk/slfhstd/modreplybot:latest
``` ```
### Baremetal (Direct Python) ### Baremetal (Direct Python)
1. Install Python 3.11+ 1. Install Python 3.11+
2. Install dependencies: 2. Install dependencies:
``` ```
pip install -r requirements.txt pip install -r requirements.txt
``` ```
3. Set environment variables or create a `.env` file. 3. Set environment variables or create a `.env` file.
4. Run: 4. Run:
``` ```
python modreplybot.py python modreplybot.py
``` ```
## Chat-Based Config Reload ## Chat-Based Config Reload
- To reload the wiki config, send a chat message containing `reload-config` to the bot account from a moderator account. - To reload the wiki config, send a chat message containing `reload-config` to the bot account from a moderator account.
- The bot will reply to the chat message indicating whether the config is valid or not. - The bot will reply to the chat message indicating whether the config is valid or not.
- Chat message IDs are tracked in `/DB/chat_wiki_requests.txt` to prevent duplicate reloads after restarts. - Chat message IDs are tracked in `/DB/chat_wiki_requests.txt` to prevent duplicate reloads after restarts.
## Troubleshooting ## Troubleshooting
- Ensure your Reddit credentials are correct and have moderator permissions. - Ensure your Reddit credentials are correct and have moderator permissions.
- The bot must be able to read the wiki page and approve posts. - The bot must be able to read the wiki page and approve posts.
- Check logs for errors. - Check logs for errors.
- The bot only responds to mod comments and mod reports for triggers. - The bot only responds to mod comments and mod reports for triggers.
- Database is stored in `/DB/commented_posts.txt` and `/DB/chat_wiki_requests.txt` and survives container restarts. - Database is stored in `/DB/commented_posts.txt` and `/DB/chat_wiki_requests.txt` and survives container restarts.
- If the wiki config is invalid, the bot will reply to the chat message with an error. - If the wiki config is invalid, the bot will reply to the chat message with an error.
## Moderator Guide ## Moderator Guide
See `ModGuide.md` for a detailed guide to configuring triggers, auto-post tags, and wiki options. See `ModGuide.md` for a detailed guide to configuring triggers, auto-post tags, and wiki options.
## License ## License
MIT MIT
+67 -7
View File
@@ -247,27 +247,24 @@ class ModReplyBot:
def tag_post_watcher(): def tag_post_watcher():
while True: while True:
try: try:
# Check new submissions
for submission in self.subreddit.stream.submissions(skip_existing=True): for submission in self.subreddit.stream.submissions(skip_existing=True):
flair = (submission.link_flair_text or '').strip().lower() flair = (getattr(submission, 'link_flair_text', '') or '').strip().lower()
title = submission.title.strip() title = submission.title.strip()
# Extract tags from title in square brackets
import re import re
title_tags = re.findall(r'\[(.*?)\]', title) title_tags = re.findall(r'\[(.*?)\]', title)
title_tags_lower = [t.strip().lower() for t in title_tags] title_tags_lower = [t.strip().lower() for t in title_tags]
print(f"[TAG WATCH] Post {submission.id}: title='{title}', flair='{flair}', title_tags={title_tags_lower}") print(f"[TAG WATCH] Post {submission.id}: title='{title}', flair='{flair}', title_tags={title_tags_lower}")
matched_tag = None matched_tag = None
# Check flair first
if flair in self.tag_comments: if flair in self.tag_comments:
matched_tag = flair matched_tag = flair
else: else:
# Check each tag in title
for tag in title_tags_lower: for tag in title_tags_lower:
if tag in self.tag_comments: if tag in self.tag_comments:
matched_tag = tag matched_tag = tag
break break
if matched_tag: # Comment on all posts with matching tags
# Only comment if not already actioned if matched_tag and (submission.id not in self.commented_posts):
if submission.id not in self.commented_posts:
status = self.tag_statuses.get(matched_tag, 'enabled') status = self.tag_statuses.get(matched_tag, 'enabled')
comment_text = self.tag_comments[matched_tag] comment_text = self.tag_comments[matched_tag]
flair_id = self.tag_flair_ids.get(matched_tag, '') flair_id = self.tag_flair_ids.get(matched_tag, '')
@@ -284,7 +281,70 @@ class ModReplyBot:
import time import time
time.sleep(30) time.sleep(30)
def modqueue_watcher():
while True:
try:
print("[MODQUEUE DEBUG] Entering modqueue loop...")
modqueue_posts = list(self.subreddit.mod.modqueue(limit=100))
print(f"[MODQUEUE DEBUG] Fetched {len(modqueue_posts)} posts from modqueue.")
for submission in modqueue_posts:
# Only access link_flair_text if it's a Submission
if hasattr(submission, 'link_flair_text'):
flair = (submission.link_flair_text or '').strip().lower()
else:
flair = ''
# Only access title if it's a Submission
if hasattr(submission, 'title'):
title = submission.title.strip()
else:
title = ''
import re
title_tags = re.findall(r'\[(.*?)\]', title)
title_tags_lower = [t.strip().lower() for t in title_tags]
# Debug print all relevant attributes
print(f"[MODQUEUE DEBUG] Post {submission.id}: title='{title}', flair='{flair}', title_tags={title_tags_lower}, author={submission.author}, removed_by_category={getattr(submission, 'removed_by_category', None)}, banned_by={getattr(submission, 'banned_by', None)}, mod_reason_title={getattr(submission, 'mod_reason_title', None)}, spam={getattr(submission, 'spam', None)}, removed={getattr(submission, 'removed', None)}")
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
# Detect filtered/removed posts
is_filtered = False
if getattr(submission, 'removed_by_category', None):
is_filtered = True
if getattr(submission, 'banned_by', None):
is_filtered = True
if getattr(submission, 'mod_reason_title', None):
is_filtered = True
if getattr(submission, 'spam', None):
is_filtered = True
if getattr(submission, 'removed', None):
is_filtered = True
if is_filtered:
print(f"[MODQUEUE WATCH] Post {submission.id} is filtered/removed.")
# Comment only on filtered/removed posts with matching tags
if matched_tag and is_filtered and (submission.id not in self.commented_posts):
status = self.tag_statuses.get(matched_tag, 'enabled')
comment_text = self.tag_comments[matched_tag]
flair_id = self.tag_flair_ids.get(matched_tag, '')
if flair_id:
try:
submission.flair.select(flair_id)
print(f"[MODQUEUE WATCH] Set flair '{flair_id}' for post {submission.id}")
except Exception as e:
print(f"[MODQUEUE WATCH] Error setting flair '{flair_id}' for post {submission.id}: {e}")
print(f"Auto-commenting on filtered/removed post {submission.id} with tag '{matched_tag}' from modqueue")
self.comment_only(submission, comment_text)
except Exception as e:
print(f"Modqueue watcher error: {e}")
import time
time.sleep(30)
threading.Thread(target=tag_post_watcher, daemon=True).start() threading.Thread(target=tag_post_watcher, daemon=True).start()
threading.Thread(target=modqueue_watcher, daemon=True).start()
# Keep main thread alive # Keep main thread alive
while True: while True: