Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 00cebd44dc | |||
| 56acc187a9 |
@@ -0,0 +1,38 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## [V2 release]
|
||||||
|
|
||||||
|
### Major Features & Enhancements
|
||||||
|
- **Chat-Based Config Reload:**
|
||||||
|
- Bot reloads wiki config when a moderator sends a chat message containing `reload-config`.
|
||||||
|
- Bot replies to chat messages indicating config validity.
|
||||||
|
- Chat message IDs are tracked in `/DB/chat_wiki_requests.txt` for persistence across restarts.
|
||||||
|
|
||||||
|
- **Configurable Actions for Triggers and Tags:**
|
||||||
|
- Added support for optional `flair_id`, `stickied`, `lock_post`, and `lock_comment` in both triggers and post_tags.
|
||||||
|
- Bot can set flair, sticky comments, lock posts/comments as specified in wiki config.
|
||||||
|
|
||||||
|
- **Persistent Tracking:**
|
||||||
|
- Auto-commented posts and processed chat requests are tracked in `/DB/commented_posts.txt` and `/DB/chat_wiki_requests.txt`.
|
||||||
|
|
||||||
|
### Code & Documentation Updates
|
||||||
|
- **modreplybot.py:**
|
||||||
|
- Refactored to remove all modmail notification code.
|
||||||
|
- All config reloads and error notifications are now handled via chat messages.
|
||||||
|
- Improved error handling and logging.
|
||||||
|
- Updated logic for flair/tag actions and chat message processing.
|
||||||
|
|
||||||
|
- **README.md & ModGuide.md:**
|
||||||
|
- Updated to reflect chat-based config reload, new config options, and persistent tracking.
|
||||||
|
- Added detailed examples for triggers and post_tags with new fields.
|
||||||
|
|
||||||
|
- **DB Folder:**
|
||||||
|
- Added `/DB/chat_wiki_requests.txt` for persistent chat request tracking.
|
||||||
|
- Updated `/DB/commented_posts.txt` for improved tracking.
|
||||||
|
|
||||||
|
### Other Changes
|
||||||
|
- **Removed:**
|
||||||
|
- All modmail-based notifications and config reloads.
|
||||||
|
- Legacy approval logic and unnecessary config options.
|
||||||
|
|
||||||
|
---
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
3cmp3o4
|
||||||
|
3fx1qfn
|
||||||
|
3g7v9uc
|
||||||
|
3jeabq9
|
||||||
|
3wskhjw
|
||||||
|
3x39w4t
|
||||||
|
3yuu0aa
|
||||||
|
3zzls88
|
||||||
|
3zzlt5d
|
||||||
|
3zzlvdb
|
||||||
|
3zzm08q
|
||||||
|
402nk2v
|
||||||
|
4abxjnc
|
||||||
|
4abxt0q
|
||||||
|
4abxurh
|
||||||
|
4dl3vu7
|
||||||
|
4lq8osg
|
||||||
|
4n5ukvi
|
||||||
|
4n5ullm
|
||||||
|
4uvaecx
|
||||||
|
4v59c9t
|
||||||
|
4z0edd4
|
||||||
|
51digym
|
||||||
|
52yjb3r
|
||||||
|
52yjj84
|
||||||
|
o8428zh
|
||||||
|
o844qn1
|
||||||
|
o844sou
|
||||||
|
o846cxy
|
||||||
|
o846f5z
|
||||||
|
5hmwzis
|
||||||
|
5ju7six
|
||||||
|
5ju8grz
|
||||||
|
5ju8ve3
|
||||||
|
5jubgcs
|
||||||
|
5judtbe
|
||||||
|
5jugfpj
|
||||||
+51
-27
@@ -1,27 +1,51 @@
|
|||||||
1roi8zs
|
1rq7d74
|
||||||
1roi5cl
|
1rq7d8m
|
||||||
1roi245
|
1rq7jgx
|
||||||
1roi088
|
1rq7jif
|
||||||
1rlvzus
|
1rq7t4b
|
||||||
1rlvufz
|
1rq7t2q
|
||||||
1rlvqyo
|
1rq7yfy
|
||||||
1rlvfof
|
1rq7yes
|
||||||
1rlpiwm
|
1rq857k
|
||||||
1rlpivb
|
1rq856a
|
||||||
1rpysqt
|
1rq7p24
|
||||||
1rpyvk2
|
1rq8h7x
|
||||||
1rpz5j5
|
1rq8h62
|
||||||
1rpzgxq
|
1rq8h62
|
||||||
1rq014r
|
1rq856a
|
||||||
1rpyv9d
|
1rq8q3r
|
||||||
1rpxb6z
|
1rq8q2l
|
||||||
1rpsp6z
|
1rq8q2l
|
||||||
1rpsgbi
|
1rq8q2l
|
||||||
1rps6ab
|
1rq8xxd
|
||||||
1rprg5y
|
1rq8xwd
|
||||||
1rpqfb7
|
1rq94gj
|
||||||
1rpju64
|
1rq94ex
|
||||||
1rpir3o
|
1rq97u4
|
||||||
1rq2nve
|
1rq97st
|
||||||
1rq2nwm
|
1rq9dmi
|
||||||
1rq2nwm
|
1rq9dlb
|
||||||
|
1rq9lze
|
||||||
|
1rq9lxo
|
||||||
|
1rq9pgz
|
||||||
|
1rq9pfg
|
||||||
|
1rq9ubo
|
||||||
|
1rq9uaq
|
||||||
|
1rq9z7v
|
||||||
|
1rq9z6p
|
||||||
|
1rqa30g
|
||||||
|
1rqa2yy
|
||||||
|
1rqa6th
|
||||||
|
1rqa6rz
|
||||||
|
1rqaawn
|
||||||
|
1rqaerv
|
||||||
|
1rqaeq6
|
||||||
|
1rqaeq6
|
||||||
|
1rqaeq6
|
||||||
|
1rqaerv
|
||||||
|
1rqb476
|
||||||
|
1rqbf64
|
||||||
|
1rqb45z
|
||||||
|
1rqbf4n
|
||||||
|
1rqbf4n
|
||||||
|
1rqbf4n
|
||||||
|
|||||||
+26
-5
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
# ModReplyBot Moderator Guide
|
# ModReplyBot Moderator Guide
|
||||||
|
|
||||||
## Wiki Configuration Page
|
## Wiki Configuration Page
|
||||||
@@ -12,7 +13,7 @@ https://old.reddit.com/r/<your_subreddit>/wiki/<wiki_page>
|
|||||||
|
|
||||||
## Triggers
|
## Triggers
|
||||||
|
|
||||||
Triggers allow moderators to perform bot actions by commenting with a trigger phrase or by reporting a post with a trigger phrase in the report reason. Triggers must start with a `!` (ex: `!help`).
|
Triggers allow moderators to perform bot actions by commenting with a trigger phrase or by reporting a post with a trigger phrase in the report reason. Triggers must start with a `!` (ex: `!test`).
|
||||||
|
|
||||||
### Example Trigger Configuration
|
### Example Trigger Configuration
|
||||||
```
|
```
|
||||||
@@ -22,6 +23,10 @@ triggers:
|
|||||||
Thank you for your report!
|
Thank you for your report!
|
||||||
This post is now approved.
|
This post is now approved.
|
||||||
status: enabled
|
status: enabled
|
||||||
|
flair_id: 12345678-aaaa-bbbb-cccc-1234567890ab
|
||||||
|
stickied: true
|
||||||
|
lock_post: false
|
||||||
|
lock_comment: false
|
||||||
- trigger: wc
|
- trigger: wc
|
||||||
comment: |
|
comment: |
|
||||||
Welcome to the community!
|
Welcome to the community!
|
||||||
@@ -31,9 +36,13 @@ triggers:
|
|||||||
- **trigger**: The phrase (without the `!`) that mods use in comments or report reasons.
|
- **trigger**: The phrase (without the `!`) that mods use in comments or report reasons.
|
||||||
- **comment**: The text the bot will post as a stickied comment.
|
- **comment**: The text the bot will post as a stickied comment.
|
||||||
- **status**:
|
- **status**:
|
||||||
- `enabled`: Bot will approve the post and comment.
|
- `enabled`: Bot will comment and perform actions.
|
||||||
- `log-only`: Bot will approve the post but not comment.
|
- `log-only`: Bot will log but not comment.
|
||||||
- `disabled`: Bot will not act on this trigger.
|
- `disabled`: Bot will not act on this trigger.
|
||||||
|
- **flair_id**: Optional. Set post flair by ID.
|
||||||
|
- **stickied**: Optional. Sticky the bot's comment.
|
||||||
|
- **lock_post**: Optional. Lock the post.
|
||||||
|
- **lock_comment**: Optional. Lock the bot's comment.
|
||||||
|
|
||||||
## Auto-Post Tags
|
## Auto-Post Tags
|
||||||
|
|
||||||
@@ -46,6 +55,7 @@ post_tags:
|
|||||||
comment: |
|
comment: |
|
||||||
__[Click here if your post says "Sorry, this post was removed by Reddit’s filters"](...)__
|
__[Click here if your post says "Sorry, this post was removed by Reddit’s filters"](...)__
|
||||||
status: enabled
|
status: enabled
|
||||||
|
flair_id: 12345678-aaaa-bbbb-cccc-1234567890ab
|
||||||
```
|
```
|
||||||
|
|
||||||
- **tag**: Comma-separated list of tags. The bot matches tags in post titles (case-insensitive).
|
- **tag**: Comma-separated list of tags. The bot matches tags in post titles (case-insensitive).
|
||||||
@@ -54,17 +64,23 @@ post_tags:
|
|||||||
- `enabled`: Bot will comment automatically.
|
- `enabled`: Bot will comment automatically.
|
||||||
- `log-only`: Bot will log but not comment.
|
- `log-only`: Bot will log but not comment.
|
||||||
- `disabled`: Bot will not act on this tag.
|
- `disabled`: Bot will not act on this tag.
|
||||||
|
- **flair_id**: Optional. Set post flair by ID.
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
## Additional Notes
|
## Additional Notes
|
||||||
- The bot only comments once per trigger per post (even if triggered multiple times).
|
- The bot only comments once per trigger per post (even if triggered multiple times).
|
||||||
- The bot only auto-comments once per post for each tag.
|
- The bot only auto-comments once per post for each tag.
|
||||||
- All bot actions are logged for transparency.
|
- All bot actions are logged for transparency.
|
||||||
- If the wiki config is invalid, the bot will notify mods via modmail and pause until fixed.
|
- If the wiki config is invalid, the bot will reply to the chat message with an error.
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
- Make sure your wiki config is valid YAML and includes both `triggers` and `post_tags` sections.
|
- Make sure your wiki config is valid YAML and includes both `triggers` and `post_tags` sections.
|
||||||
- Use old.reddit.com for wiki editing to avoid formatting issues.
|
- Use old.reddit.com for wiki editing to avoid formatting issues.
|
||||||
- Check bot logs for errors and modmail for config issues.
|
- Check bot logs for errors and chat replies for config issues.
|
||||||
|
|
||||||
## Example Wiki Config Excerpt
|
## Example Wiki Config Excerpt
|
||||||
```
|
```
|
||||||
@@ -74,9 +90,14 @@ triggers:
|
|||||||
Thank you for your report!
|
Thank you for your report!
|
||||||
This post is now approved.
|
This post is now approved.
|
||||||
status: enabled
|
status: enabled
|
||||||
|
flair_id: 12345678-aaaa-bbbb-cccc-1234567890ab
|
||||||
|
stickied: true
|
||||||
|
lock_post: false
|
||||||
|
lock_comment: false
|
||||||
post_tags:
|
post_tags:
|
||||||
- tag: Bedrock, Java
|
- tag: Bedrock, Java
|
||||||
comment: |
|
comment: |
|
||||||
__[Click here if your post says "Sorry, this post was removed by Reddit’s filters"](...)__
|
__[Click here if your post says "Sorry, this post was removed by Reddit’s filters"](...)__
|
||||||
status: enabled
|
status: enabled
|
||||||
|
flair_id: 12345678-aaaa-bbbb-cccc-1234567890ab
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -18,6 +18,29 @@ ModReplyBot is a Reddit bot for moderators that automates post approval and stic
|
|||||||
### 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:
|
||||||
|
|
||||||
|
```
|
||||||
|
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:
|
triggers:
|
||||||
- trigger: help
|
- trigger: help
|
||||||
@@ -25,6 +48,10 @@ triggers:
|
|||||||
Thank you for your report!
|
Thank you for your report!
|
||||||
This post is now approved.
|
This post is now approved.
|
||||||
status: enabled
|
status: enabled
|
||||||
|
flair_id: 12345678-aaaa-bbbb-cccc-1234567890ab
|
||||||
|
stickied: true
|
||||||
|
lock_post: false
|
||||||
|
lock_comment: false
|
||||||
- trigger: wc
|
- trigger: wc
|
||||||
comment: |
|
comment: |
|
||||||
Welcome to the community!
|
Welcome to the community!
|
||||||
@@ -35,16 +62,17 @@ post_tags:
|
|||||||
comment: |
|
comment: |
|
||||||
__[Click here if your post says "Sorry, this post was removed by Reddit’s filters"](...)__
|
__[Click here if your post says "Sorry, this post was removed by Reddit’s filters"](...)__
|
||||||
status: enabled
|
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.
|
- 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.
|
- 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`.
|
||||||
|
|
||||||
### 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
|
||||||
@@ -56,10 +84,6 @@ REDDIT_WIKI_PAGE=modreplybot-config
|
|||||||
LOG_LEVEL=Default
|
LOG_LEVEL=Default
|
||||||
```
|
```
|
||||||
|
|
||||||
docker compose up -d
|
|
||||||
docker run --env-file .env -v $(pwd)/DB:/app/DB slfhstd.uk/slfhstd/modreplybot:latest
|
|
||||||
pip install -r requirements.txt
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Docker Compose (Recommended)
|
### Docker Compose (Recommended)
|
||||||
@@ -95,14 +119,18 @@ pip install -r requirements.txt
|
|||||||
python modreplybot.py
|
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
|
## 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 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 notify mods via modmail and pause until fixed.
|
- 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.
|
||||||
|
|||||||
+174
-27
@@ -5,6 +5,52 @@ from config import get_reddit, Config
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
class ModReplyBot:
|
class ModReplyBot:
|
||||||
|
def chat_message_watcher(self):
|
||||||
|
chat_requests_file = os.path.join(os.path.dirname(__file__), 'DB', 'chat_wiki_requests.txt')
|
||||||
|
processed_message_ids = set()
|
||||||
|
# Load processed IDs from file
|
||||||
|
if os.path.exists(chat_requests_file):
|
||||||
|
with open(chat_requests_file, 'r', encoding='utf-8') as f:
|
||||||
|
for line in f:
|
||||||
|
processed_message_ids.add(line.strip())
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
for message in self.reddit.inbox.stream():
|
||||||
|
if not hasattr(message, 'id') or message.id in processed_message_ids:
|
||||||
|
continue
|
||||||
|
processed_message_ids.add(message.id)
|
||||||
|
# Save processed ID to file
|
||||||
|
with open(chat_requests_file, 'a', encoding='utf-8') as f:
|
||||||
|
f.write(message.id + '\n')
|
||||||
|
if hasattr(message, 'body') and 'reload-config' in message.body.lower():
|
||||||
|
# Check if sender is a moderator
|
||||||
|
author = getattr(message, 'author', None)
|
||||||
|
if author and author in self.subreddit.moderator():
|
||||||
|
print(f"[CHAT WATCH] Moderator '{author}' requested config reload.")
|
||||||
|
result = self.fetch_yaml_config()
|
||||||
|
if result:
|
||||||
|
print("[CHAT WATCH] Wiki config reloaded successfully.")
|
||||||
|
reply_text = "Config reloaded successfully. Config is valid."
|
||||||
|
else:
|
||||||
|
print("[CHAT WATCH] Wiki config reload failed.")
|
||||||
|
reply_text = "Config reload failed. Config is invalid."
|
||||||
|
try:
|
||||||
|
message.reply(reply_text)
|
||||||
|
print(f"[CHAT WATCH] Replied to chat message {message.id}.")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[CHAT WATCH] Error replying to chat message {message.id}: {e}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Chat message watcher error: {e}")
|
||||||
|
import time
|
||||||
|
time.sleep(30)
|
||||||
|
def comment_only(self, submission, comment_text):
|
||||||
|
try:
|
||||||
|
comment = submission.reply(comment_text)
|
||||||
|
comment.mod.distinguish(sticky=True)
|
||||||
|
print(f"Commented (no approval) on: {submission.id}")
|
||||||
|
self.save_commented_post(submission.id)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error commenting (no approval): {e}")
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
import os
|
import os
|
||||||
self.reddit = get_reddit()
|
self.reddit = get_reddit()
|
||||||
@@ -14,6 +60,11 @@ class ModReplyBot:
|
|||||||
self.comments = []
|
self.comments = []
|
||||||
self.commented_posts = set()
|
self.commented_posts = set()
|
||||||
self.commented_posts_file = os.path.join(os.path.dirname(__file__), 'DB', 'commented_posts.txt')
|
self.commented_posts_file = os.path.join(os.path.dirname(__file__), 'DB', 'commented_posts.txt')
|
||||||
|
self.tagged_commented_posts_file = os.path.join(os.path.dirname(__file__), 'DB', 'tagged_commented_posts.txt')
|
||||||
|
self.tagged_commented_posts = set()
|
||||||
|
self._wiki_config_cache = None
|
||||||
|
self._wiki_config_cache_time = 0
|
||||||
|
self._wiki_config_cache_ttl = 300 # seconds (5 minutes)
|
||||||
self.ensure_config_file()
|
self.ensure_config_file()
|
||||||
self.load_commented_posts()
|
self.load_commented_posts()
|
||||||
self.log_level = Config.LOG_LEVEL
|
self.log_level = Config.LOG_LEVEL
|
||||||
@@ -44,43 +95,64 @@ class ModReplyBot:
|
|||||||
# Track last error revision to prevent modmail spam
|
# Track last error revision to prevent modmail spam
|
||||||
if not hasattr(self, '_last_config_error_revision'):
|
if not hasattr(self, '_last_config_error_revision'):
|
||||||
self._last_config_error_revision = None
|
self._last_config_error_revision = None
|
||||||
import yaml
|
import yaml, time
|
||||||
|
now = time.time()
|
||||||
|
# Use cache if not expired
|
||||||
|
if self._wiki_config_cache and (now - self._wiki_config_cache_time < self._wiki_config_cache_ttl):
|
||||||
|
config = self._wiki_config_cache
|
||||||
|
self._wiki_revision_id = getattr(self, '_wiki_revision_id', None)
|
||||||
|
else:
|
||||||
try:
|
try:
|
||||||
wiki_page = Config.WIKI_PAGE
|
wiki_page = Config.WIKI_PAGE
|
||||||
wiki = self.subreddit.wiki[wiki_page]
|
wiki = self.subreddit.wiki[wiki_page]
|
||||||
wiki_content = wiki.content_md
|
wiki_content = wiki.content_md
|
||||||
self._wiki_revision_id = getattr(wiki, 'revision_id', None)
|
self._wiki_revision_id = getattr(wiki, 'revision_id', None)
|
||||||
config = yaml.safe_load(wiki_content)
|
config = yaml.safe_load(wiki_content)
|
||||||
|
self._wiki_config_cache = config
|
||||||
|
self._wiki_config_cache_time = now
|
||||||
|
except Exception as e:
|
||||||
|
self.log(f"Error fetching YAML config from wiki: {e}")
|
||||||
|
revision = getattr(self, '_wiki_revision_id', None)
|
||||||
|
if revision != self._last_config_error_revision:
|
||||||
|
self._last_config_error_revision = revision
|
||||||
|
return False
|
||||||
if not isinstance(config, dict) or 'triggers' not in config:
|
if not isinstance(config, dict) or 'triggers' not in config:
|
||||||
raise ValueError("Wiki config missing required 'triggers' key or is not a dict.")
|
self.log("Wiki config missing required 'triggers' key or is not a dict.")
|
||||||
|
return False
|
||||||
self.triggers = []
|
self.triggers = []
|
||||||
self.comments = []
|
self.comments = []
|
||||||
self.statuses = []
|
self.statuses = []
|
||||||
|
self.flair_ids = []
|
||||||
|
self.stickied = []
|
||||||
|
self.lock_post = []
|
||||||
|
self.lock_comment = []
|
||||||
self.tag_comments = {}
|
self.tag_comments = {}
|
||||||
self.tag_statuses = {}
|
self.tag_statuses = {}
|
||||||
|
self.tag_flair_ids = {}
|
||||||
for entry in config.get('triggers', []):
|
for entry in config.get('triggers', []):
|
||||||
self.triggers.append(entry.get('trigger', '').strip())
|
self.triggers.append(entry.get('trigger', '').strip())
|
||||||
self.comments.append(entry.get('comment', '').strip())
|
self.comments.append(entry.get('comment', '').strip())
|
||||||
self.statuses.append(entry.get('status', 'enabled').strip().lower())
|
self.statuses.append(entry.get('status', 'enabled').strip().lower())
|
||||||
|
self.flair_ids.append(entry.get('flair_id', '').strip())
|
||||||
|
self.stickied.append(bool(entry.get('stickied', False)))
|
||||||
|
# Parse lock_post as a proper boolean
|
||||||
|
lock_post_val = entry.get('lock_post', False)
|
||||||
|
if isinstance(lock_post_val, str):
|
||||||
|
lock_post_val = lock_post_val.lower() in ['true', '1', 'yes']
|
||||||
|
self.lock_post.append(bool(lock_post_val))
|
||||||
|
self.lock_comment.append(bool(entry.get('lock_comment', False)))
|
||||||
for entry in config.get('post_tags', []):
|
for entry in config.get('post_tags', []):
|
||||||
tags_str = entry.get('tag', '').strip()
|
tags_str = entry.get('tag', '').strip()
|
||||||
comment = entry.get('comment', '').strip()
|
comment = entry.get('comment', '').strip()
|
||||||
status = entry.get('status', 'enabled').strip().lower()
|
status = entry.get('status', 'enabled').strip().lower()
|
||||||
|
flair_id = entry.get('flair_id', '').strip()
|
||||||
tags = [t.strip().lower() for t in tags_str.split(',') if t.strip()]
|
tags = [t.strip().lower() for t in tags_str.split(',') if t.strip()]
|
||||||
for tag in tags:
|
for tag in tags:
|
||||||
self.tag_comments[tag] = comment
|
self.tag_comments[tag] = comment
|
||||||
self.tag_statuses[tag] = status
|
self.tag_statuses[tag] = status
|
||||||
# Reset error revision tracker on successful config
|
self.tag_flair_ids[tag] = flair_id
|
||||||
self._last_config_error_revision = None
|
self._last_config_error_revision = None
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
|
||||||
self.log(f"Error fetching YAML config from wiki: {e}")
|
|
||||||
# Only send modmail if revision is new
|
|
||||||
revision = getattr(self, '_wiki_revision_id', None)
|
|
||||||
if revision != self._last_config_error_revision:
|
|
||||||
self.notify_mods_config_error(str(e))
|
|
||||||
self._last_config_error_revision = revision
|
|
||||||
return False
|
|
||||||
|
|
||||||
def notify_mods_config_error(self, error_message):
|
def notify_mods_config_error(self, error_message):
|
||||||
try:
|
try:
|
||||||
@@ -135,7 +207,6 @@ class ModReplyBot:
|
|||||||
if old_revision and new_revision and old_revision != new_revision:
|
if old_revision and new_revision and old_revision != new_revision:
|
||||||
if config_ok:
|
if config_ok:
|
||||||
self.log("Wiki config changed, reloading triggers and tag comments.")
|
self.log("Wiki config changed, reloading triggers and tag comments.")
|
||||||
self.notify_mods_config_change(new_revision)
|
|
||||||
else:
|
else:
|
||||||
self.log("Wiki config error detected, not reloading bot.")
|
self.log("Wiki config error detected, not reloading bot.")
|
||||||
self.log_level = Config.LOG_LEVEL
|
self.log_level = Config.LOG_LEVEL
|
||||||
@@ -145,6 +216,8 @@ class ModReplyBot:
|
|||||||
self.handle_comment(comment)
|
self.handle_comment(comment)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Mod comment watcher error: {e}")
|
print(f"Mod comment watcher error: {e}")
|
||||||
|
import time
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
def mod_report_watcher():
|
def mod_report_watcher():
|
||||||
last_revision = None
|
last_revision = None
|
||||||
@@ -169,6 +242,49 @@ class ModReplyBot:
|
|||||||
|
|
||||||
threading.Thread(target=mod_comment_watcher, daemon=True).start()
|
threading.Thread(target=mod_comment_watcher, daemon=True).start()
|
||||||
threading.Thread(target=mod_report_watcher, daemon=True).start()
|
threading.Thread(target=mod_report_watcher, daemon=True).start()
|
||||||
|
threading.Thread(target=self.chat_message_watcher, daemon=True).start()
|
||||||
|
|
||||||
|
def tag_post_watcher():
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
for submission in self.subreddit.stream.submissions(skip_existing=True):
|
||||||
|
flair = (submission.link_flair_text or '').strip().lower()
|
||||||
|
title = submission.title.strip()
|
||||||
|
# Extract tags from title in square brackets
|
||||||
|
import re
|
||||||
|
title_tags = re.findall(r'\[(.*?)\]', title)
|
||||||
|
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}")
|
||||||
|
matched_tag = None
|
||||||
|
# Check flair first
|
||||||
|
if flair in self.tag_comments:
|
||||||
|
matched_tag = flair
|
||||||
|
else:
|
||||||
|
# Check each tag in title
|
||||||
|
for tag in title_tags_lower:
|
||||||
|
if tag in self.tag_comments:
|
||||||
|
matched_tag = tag
|
||||||
|
break
|
||||||
|
if matched_tag:
|
||||||
|
# Only comment if not already actioned
|
||||||
|
if 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"[TAG WATCH] Set flair '{flair_id}' for post {submission.id}")
|
||||||
|
except Exception as 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}'")
|
||||||
|
self.comment_only(submission, comment_text)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Tag post watcher error: {e}")
|
||||||
|
import time
|
||||||
|
time.sleep(30)
|
||||||
|
|
||||||
|
threading.Thread(target=tag_post_watcher, daemon=True).start()
|
||||||
|
|
||||||
# Keep main thread alive
|
# Keep main thread alive
|
||||||
while True:
|
while True:
|
||||||
@@ -180,6 +296,7 @@ class ModReplyBot:
|
|||||||
matched_trigger = None
|
matched_trigger = None
|
||||||
matched_comment = None
|
matched_comment = None
|
||||||
matched_status = None
|
matched_status = None
|
||||||
|
matched_idx = None
|
||||||
# Check report reasons for triggers
|
# Check report reasons for triggers
|
||||||
if hasattr(submission, 'mod_reports') and submission.mod_reports:
|
if hasattr(submission, 'mod_reports') and submission.mod_reports:
|
||||||
for report_tuple in submission.mod_reports:
|
for report_tuple in submission.mod_reports:
|
||||||
@@ -190,6 +307,7 @@ class ModReplyBot:
|
|||||||
matched_trigger = trigger
|
matched_trigger = trigger
|
||||||
matched_comment = self.comments[idx]
|
matched_comment = self.comments[idx]
|
||||||
matched_status = self.statuses[idx] if idx < len(self.statuses) else 'enabled'
|
matched_status = self.statuses[idx] if idx < len(self.statuses) else 'enabled'
|
||||||
|
matched_idx = idx
|
||||||
break
|
break
|
||||||
if matched_trigger:
|
if matched_trigger:
|
||||||
break
|
break
|
||||||
@@ -205,18 +323,8 @@ class ModReplyBot:
|
|||||||
try:
|
try:
|
||||||
footer = "^I ^am ^a ^bot ^and ^this ^comment ^was ^made ^automatically. ^Message ^the ^Mod ^team ^if ^I'm ^not ^working ^correctly."
|
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
|
comment_text = matched_comment.replace("{{author}}", submission.author.name if submission.author else "unknown") + "\n\n" + footer
|
||||||
if matched_status == 'enabled':
|
self.approve_and_comment(submission, comment_text, matched_status, matched_idx)
|
||||||
comment = submission.reply(comment_text)
|
|
||||||
comment.mod.distinguish(sticky=True)
|
|
||||||
print(f"Commented on mod report {submission.id} for trigger [{matched_trigger}] (auto)")
|
|
||||||
self.triggered_posts.add(trigger_key)
|
self.triggered_posts.add(trigger_key)
|
||||||
elif matched_status == 'log-only':
|
|
||||||
print(f"Log-only: Did not comment on mod report {submission.id} for trigger [{matched_trigger}] (auto)")
|
|
||||||
self.triggered_posts.add(trigger_key)
|
|
||||||
elif matched_status == 'disabled':
|
|
||||||
print(f"Disabled: Did not comment/log for mod report {submission.id} for trigger [{matched_trigger}] (auto)")
|
|
||||||
else:
|
|
||||||
print(f"Unknown status '{matched_status}' for mod report {submission.id} for trigger [{matched_trigger}] (auto)")
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error commenting on mod report: {e}")
|
print(f"Error commenting on mod report: {e}")
|
||||||
else:
|
else:
|
||||||
@@ -240,15 +348,54 @@ class ModReplyBot:
|
|||||||
print(f"Error removing comment: {e}")
|
print(f"Error removing comment: {e}")
|
||||||
submission = comment.submission
|
submission = comment.submission
|
||||||
self.fetch_yaml_config()
|
self.fetch_yaml_config()
|
||||||
self.approve_and_comment(submission, self.comments[idx], status)
|
self.approve_and_comment(submission, self.comments[idx], status, idx)
|
||||||
break
|
break
|
||||||
|
|
||||||
def approve_and_comment(self, submission, comment_text, status='enabled'):
|
def approve_and_comment(self, submission, comment_text, status='enabled', trigger_idx=None):
|
||||||
try:
|
try:
|
||||||
submission.mod.approve()
|
print(f"[DEBUG] approve_and_comment called with trigger_idx={trigger_idx}")
|
||||||
|
if trigger_idx is not None:
|
||||||
|
print(f"[DEBUG] Config for trigger_idx={trigger_idx}: lock_post={self.lock_post[trigger_idx]}, stickied={self.stickied[trigger_idx]}, lock_comment={self.lock_comment[trigger_idx]}, flair_id={self.flair_ids[trigger_idx]}")
|
||||||
|
# Approve post if configured for this trigger
|
||||||
|
# Set flair, stickied, lock_post, lock_comment if configured for this trigger
|
||||||
|
if trigger_idx is not None:
|
||||||
|
flair_id = self.flair_ids[trigger_idx]
|
||||||
|
if flair_id:
|
||||||
|
try:
|
||||||
|
submission.flair.select(flair_id)
|
||||||
|
print(f"[DEBUG] Set flair '{flair_id}' for post {submission.id}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[DEBUG] Error setting flair '{flair_id}' for post {submission.id}: {e}")
|
||||||
|
print(f"[DEBUG] lock_post[{trigger_idx}] = {self.lock_post[trigger_idx]}")
|
||||||
|
if self.lock_post[trigger_idx]:
|
||||||
|
try:
|
||||||
|
submission.mod.lock()
|
||||||
|
print(f"[DEBUG] Locked post {submission.id}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[DEBUG] Error locking post {submission.id}: {e}")
|
||||||
if status == 'enabled':
|
if status == 'enabled':
|
||||||
|
print(f"[DEBUG] Submission object: {submission}, ID: {submission.id}, Type: {type(submission)}")
|
||||||
comment = submission.reply(comment_text)
|
comment = submission.reply(comment_text)
|
||||||
comment.mod.distinguish(sticky=True)
|
print(f"[DEBUG] Comment object: {comment}, ID: {comment.id}, Type: {type(comment)}")
|
||||||
|
# Always sticky if stickied is True, match tag logic
|
||||||
|
if trigger_idx is not None and self.stickied[trigger_idx]:
|
||||||
|
try:
|
||||||
|
result = comment.mod.distinguish(sticky=True)
|
||||||
|
print(f"[DEBUG] Stickied bot comment {comment.id} on post {submission.id}, result: {result}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[DEBUG] Error stickying comment {comment.id} on post {submission.id}: {e}")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
result = comment.mod.distinguish()
|
||||||
|
print(f"[DEBUG] Distinguished bot comment {comment.id} on post {submission.id}, result: {result}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[DEBUG] Error distinguishing comment {comment.id} on post {submission.id}: {e}")
|
||||||
|
if trigger_idx is not None and self.lock_comment[trigger_idx]:
|
||||||
|
try:
|
||||||
|
comment.mod.lock()
|
||||||
|
print(f"[DEBUG] Locked bot comment {comment.id} on post {submission.id}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[DEBUG] Error locking comment {comment.id} on post {submission.id}: {e}")
|
||||||
print(f"Approved and commented on: {submission.id}")
|
print(f"Approved and commented on: {submission.id}")
|
||||||
self.save_commented_post(submission.id)
|
self.save_commented_post(submission.id)
|
||||||
elif status == 'log-only':
|
elif status == 'log-only':
|
||||||
|
|||||||
Reference in New Issue
Block a user