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
+77 -99
View File
@@ -15,125 +15,103 @@ 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 - trigger: help
comment: |
Thank you for your report!
This post is now approved.
status: enabled
flair_id: 12345678-aaaa-bbbb-cccc-1234567890ab
stickied: true
lock_post: false
lock_comment: false
- trigger: wc
comment: |
Welcome to the community!
status: log-only
# ModReplyBot post_tags:
- tag: Bedrock, Java
comment: |
__[Click here if your post says "Sorry, this post was removed by Reddits filters"](...)__
status: enabled
flair_id: 12345678-aaaa-bbbb-cccc-1234567890ab
```
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. - 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.
- Status options: `enabled`, `log-only`, `disabled`.
- Optional actions: `flair_id`, `stickied`, `lock_post`, `lock_comment`.
## Features ### 2. Environment Variables
- Responds to moderator comments containing trigger phrases (starting with `!`) Create a `.env` file (or set env variables directly) 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 ```
REDDIT_CLIENT_ID=your_client_id
REDDIT_CLIENT_SECRET=your_client_secret
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
LOG_LEVEL=Default
```
### 1. Wiki Page Configuration ## Installation
Edit your subreddit wiki page (name set by `REDDIT_WIKI_PAGE` env variable) with YAML like:
``` ### Docker Compose (Recommended)
triggers: 1. Copy `.env.example` to `.env` and fill in your values.
- trigger: help 2. Run:
comment: |
Thank you for your report!
This post is now approved.
status: enabled
flair_id: 12345678-aaaa-bbbb-cccc-1234567890ab
stickied: true
lock_post: false
lock_comment: false
- trigger: wc
comment: |
Welcome to the community!
status: log-only
post_tags: ```
- tag: Bedrock, Java docker compose up -d
comment: | ```
__[Click here if your post says "Sorry, this post was removed by Reddits filters"](...)__
status: enabled
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. * The DB folder is mounted for persistent database storage.
- 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`.
- Optional actions: `flair_id`, `stickied`, `lock_post`, `lock_comment`.
### 2. Environment Variables ### Docker Run
Create a `.env` file (or set env variables directly) with: 1. Copy `.env.example` to `.env` and fill in your values.
2. Run:
``` ```
REDDIT_CLIENT_ID=your_client_id docker run --env-file .env -v $(pwd)/DB:/app/DB slfhstd.uk/slfhstd/modreplybot:latest
REDDIT_CLIENT_SECRET=your_client_secret ```
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
LOG_LEVEL=Default
```
## Installation ### Baremetal (Direct Python)
1. Install Python 3.11+
2. Install dependencies:
### Docker Compose (Recommended) ```
1. Copy `.env.example` to `.env` and fill in your values. pip install -r requirements.txt
2. Run: ```
``` 3. Set environment variables or create a `.env` file.
docker compose up -d 4. Run:
```
* The DB folder is mounted for persistent database storage. ```
python modreplybot.py
```
### Docker Run ## Chat-Based Config Reload
1. Copy `.env.example` to `.env` and fill in your values. - To reload the wiki config, send a chat message containing `reload-config` to the bot account from a moderator account.
2. Run: - 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.
``` ## Troubleshooting
docker run --env-file .env -v $(pwd)/DB:/app/DB slfhstd.uk/slfhstd/modreplybot:latest - Ensure your Reddit credentials are correct and have moderator permissions.
``` - The bot must be able to read the wiki page and approve posts.
- Check logs for errors.
- 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.
- If the wiki config is invalid, the bot will reply to the chat message with an error.
### Baremetal (Direct Python) ## Moderator Guide
1. Install Python 3.11+ See `ModGuide.md` for a detailed guide to configuring triggers, auto-post tags, and wiki options.
2. Install dependencies:
``` ## License
pip install -r requirements.txt MIT
```
3. Set environment variables or create a `.env` file.
4. Run:
```
python modreplybot.py
```
## Chat-Based Config Reload
- 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.
- Chat message IDs are tracked in `/DB/chat_wiki_requests.txt` to prevent duplicate reloads after restarts.
## Troubleshooting
- Ensure your Reddit credentials are correct and have moderator permissions.
- The bot must be able to read the wiki page and approve posts.
- Check logs for errors.
- 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.
- If the wiki config is invalid, the bot will reply to the chat message with an error.
## Moderator Guide
See `ModGuide.md` for a detailed guide to configuring triggers, auto-post tags, and wiki options.
## License
MIT
+78 -18
View File
@@ -247,44 +247,104 @@ 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, '') if flair_id:
if flair_id: try:
try: submission.flair.select(flair_id)
submission.flair.select(flair_id) print(f"[TAG WATCH] Set flair '{flair_id}' for post {submission.id}")
print(f"[TAG WATCH] Set flair '{flair_id}' for post {submission.id}") except Exception as e:
except Exception as e: print(f"[TAG WATCH] Error setting flair '{flair_id}' for post {submission.id}: {e}")
print(f"[TAG WATCH] Error setting flair '{flair_id}' for post {submission.id}: {e}") print(f"Auto-commenting on post {submission.id} with tag '{matched_tag}'")
print(f"Auto-commenting on post {submission.id} with tag '{matched_tag}'") self.comment_only(submission, comment_text)
self.comment_only(submission, comment_text)
except Exception as e: except Exception as e:
print(f"Tag post watcher error: {e}") print(f"Tag post watcher error: {e}")
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: