Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6d2d04ea40 | |||
| f4faabe44d | |||
| 87958ba09c | |||
| 592521026a | |||
| a2faff2853 | |||
| 12f62cfb24 | |||
| ca98a3b466 | |||
| af70310743 | |||
| 21cbad7111 | |||
| ca0f165257 |
@@ -1,2 +1,4 @@
|
|||||||
.env
|
.env
|
||||||
config/*
|
config/*
|
||||||
|
tests.py
|
||||||
|
message.md
|
||||||
|
|||||||
+54
-1
@@ -1,5 +1,58 @@
|
|||||||
|
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [2.2.0] - 2026-03-12
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **Configurable Ignore Tags:**
|
||||||
|
- Added `ignore_tags` section to wiki config. Bot now ignores posts with matching tags in the title or flair (case-insensitive).
|
||||||
|
- Updated README.md and bot logic to support this feature.
|
||||||
|
- **Update Checker Integration:**
|
||||||
|
- Added `update_checker.py` module to check for bot updates and notify moderators via modmail.
|
||||||
|
- Update checker runs in a background thread and checks for updates hourly.
|
||||||
|
- **Bot Version & Name Constants:**
|
||||||
|
- Added `BOT_VERSION` and `BOT_NAME` constants at the top of `modreplybot.py` for easy version/name changes.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Integrated update checker startup in main bot entrypoint.
|
||||||
|
- Improved documentation for ignore_tags and update checker features.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- N/A
|
||||||
|
|
||||||
|
|
||||||
|
## [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 +88,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.
|
||||||
|
|
||||||
---
|
|
||||||
|
|||||||
@@ -35,3 +35,10 @@ o846f5z
|
|||||||
5jubgcs
|
5jubgcs
|
||||||
5judtbe
|
5judtbe
|
||||||
5jugfpj
|
5jugfpj
|
||||||
|
5jv66nh
|
||||||
|
5jvi6wk
|
||||||
|
5jvji2r
|
||||||
|
o9wezfi
|
||||||
|
o9wf1xl
|
||||||
|
o9x1jh0
|
||||||
|
o9x1lyh
|
||||||
|
|||||||
@@ -49,3 +49,24 @@
|
|||||||
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
|
||||||
|
1rs5sz0
|
||||||
|
1rs5sxl
|
||||||
|
|||||||
@@ -5,5 +5,6 @@ COPY requirements.txt .
|
|||||||
RUN pip install --no-cache-dir -r requirements.txt
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
COPY config.py .
|
COPY config.py .
|
||||||
COPY modreplybot.py .
|
COPY modreplybot.py .
|
||||||
|
COPY update_checker.py .
|
||||||
ENV PYTHONUNBUFFERED=1
|
ENV PYTHONUNBUFFERED=1
|
||||||
CMD ["python", "modreplybot.py"]
|
CMD ["python", "modreplybot.py"]
|
||||||
|
|||||||
@@ -15,125 +15,110 @@ 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 Reddit’s 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.
|
# New: Ignore tags
|
||||||
|
ignore_tags:
|
||||||
|
- tag: Off-Topic
|
||||||
|
- tag: Meme
|
||||||
|
```
|
||||||
|
|
||||||
## Features
|
- Triggers: Bot responds to mod comments and mod reports containing `!trigger` (e.g., `!help`) with the configured comment and actions.
|
||||||
- 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
|
- post_tags: Bot posts the comment automatically on new posts with matching tags in the title and can set flair.
|
||||||
|
- ignore_tags: Bot will ignore (not comment on) posts with these tags in the title. Tags are case-insensitive and match `[Tag]` in the post title or flair.
|
||||||
|
- Status options: `enabled`, `log-only`, `disabled`.
|
||||||
|
- Optional actions: `flair_id`, `stickied`, `lock_post`, `lock_comment`.
|
||||||
|
|
||||||
### 1. Wiki Page Configuration
|
### 2. Environment Variables
|
||||||
Edit your subreddit wiki page (name set by `REDDIT_WIKI_PAGE` env variable) with YAML like:
|
Create a `.env` file (or set env variables directly) with:
|
||||||
|
|
||||||
```
|
```
|
||||||
triggers:
|
REDDIT_CLIENT_ID=your_client_id
|
||||||
- trigger: help
|
REDDIT_CLIENT_SECRET=your_client_secret
|
||||||
comment: |
|
REDDIT_USERNAME=your_username
|
||||||
Thank you for your report!
|
REDDIT_PASSWORD=your_password
|
||||||
This post is now approved.
|
REDDIT_USER_AGENT=modreplybot by /u/your_username
|
||||||
status: enabled
|
REDDIT_SUBREDDIT=your_subreddit
|
||||||
flair_id: 12345678-aaaa-bbbb-cccc-1234567890ab
|
REDDIT_WIKI_PAGE=modreplybot-config
|
||||||
stickied: true
|
LOG_LEVEL=Default
|
||||||
lock_post: false
|
```
|
||||||
lock_comment: false
|
|
||||||
- trigger: wc
|
|
||||||
comment: |
|
|
||||||
Welcome to the community!
|
|
||||||
status: log-only
|
|
||||||
|
|
||||||
post_tags:
|
## Installation
|
||||||
- tag: Bedrock, Java
|
|
||||||
comment: |
|
|
||||||
__[Click here if your post says "Sorry, this post was removed by Reddit’s 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.
|
### Docker Compose (Recommended)
|
||||||
- post_tags: Bot posts the comment automatically on new posts with matching tags in the title and can set flair.
|
1. Copy `.env.example` to `.env` and fill in your values.
|
||||||
- Status options: `enabled`, `log-only`, `disabled`.
|
2. Run:
|
||||||
- Optional actions: `flair_id`, `stickied`, `lock_post`, `lock_comment`.
|
|
||||||
|
|
||||||
### 2. Environment Variables
|
```
|
||||||
Create a `.env` file (or set env variables directly) with:
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
```
|
* The DB folder is mounted for persistent database storage.
|
||||||
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
|
|
||||||
```
|
|
||||||
|
|
||||||
## Installation
|
### Docker Run
|
||||||
|
1. Copy `.env.example` to `.env` and fill in your values.
|
||||||
|
2. Run:
|
||||||
|
|
||||||
### Docker Compose (Recommended)
|
```
|
||||||
1. Copy `.env.example` to `.env` and fill in your values.
|
docker run --env-file .env -v $(pwd)/DB:/app/DB slfhstd.uk/slfhstd/modreplybot:latest
|
||||||
2. Run:
|
```
|
||||||
|
|
||||||
```
|
### Baremetal (Direct Python)
|
||||||
docker compose up -d
|
1. Install Python 3.11+
|
||||||
```
|
2. Install dependencies:
|
||||||
|
|
||||||
* The DB folder is mounted for persistent database storage.
|
```
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
### Docker Run
|
3. Set environment variables or create a `.env` file.
|
||||||
1. Copy `.env.example` to `.env` and fill in your values.
|
4. Run:
|
||||||
2. Run:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
docker run --env-file .env -v $(pwd)/DB:/app/DB slfhstd.uk/slfhstd/modreplybot:latest
|
python modreplybot.py
|
||||||
```
|
```
|
||||||
|
|
||||||
### Baremetal (Direct Python)
|
## Chat-Based Config Reload
|
||||||
1. Install Python 3.11+
|
- To reload the wiki config, send a chat message containing `reload-config` to the bot account from a moderator account.
|
||||||
2. Install dependencies:
|
- 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
|
||||||
pip install -r requirements.txt
|
- 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.
|
||||||
|
|
||||||
3. Set environment variables or create a `.env` file.
|
## Moderator Guide
|
||||||
4. Run:
|
See `ModGuide.md` for a detailed guide to configuring triggers, auto-post tags, and wiki options.
|
||||||
|
|
||||||
```
|
## License
|
||||||
python modreplybot.py
|
MIT
|
||||||
```
|
|
||||||
|
|
||||||
## 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
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
services:
|
services:
|
||||||
modreplybot:
|
modreplybot:
|
||||||
image: slfhstd.uk/slfhstd/modreplybot:latest
|
image: slfhstd.uk/slfhstd/modreplybot:dev
|
||||||
container_name: modreplybot
|
container_name: modreplybot
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
|
|||||||
+102
-18
@@ -1,6 +1,10 @@
|
|||||||
import os
|
import os
|
||||||
import praw
|
import praw
|
||||||
from config import get_reddit, Config
|
from config import get_reddit, Config
|
||||||
|
from update_checker import start_update_checker
|
||||||
|
|
||||||
|
BOT_VERSION = "2.2.0" # Change this for new releases
|
||||||
|
BOT_NAME = "ModReplyBot" # Change this if bot name changes
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
@@ -151,6 +155,12 @@ class ModReplyBot:
|
|||||||
self.tag_comments[tag] = comment
|
self.tag_comments[tag] = comment
|
||||||
self.tag_statuses[tag] = status
|
self.tag_statuses[tag] = status
|
||||||
self.tag_flair_ids[tag] = flair_id
|
self.tag_flair_ids[tag] = flair_id
|
||||||
|
# Parse ignore_tags
|
||||||
|
self.ignore_tags = set()
|
||||||
|
for entry in config.get('ignore_tags', []):
|
||||||
|
tag_val = entry.get('tag', '').strip().lower() if isinstance(entry, dict) else str(entry).strip().lower()
|
||||||
|
if tag_val:
|
||||||
|
self.ignore_tags.add(tag_val)
|
||||||
self._last_config_error_revision = None
|
self._last_config_error_revision = None
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -247,44 +257,116 @@ 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}")
|
||||||
|
# Ignore if any ignore_tag matches flair or title tag
|
||||||
|
ignore = False
|
||||||
|
if flair in self.ignore_tags:
|
||||||
|
ignore = True
|
||||||
|
else:
|
||||||
|
for tag in title_tags_lower:
|
||||||
|
if tag in self.ignore_tags:
|
||||||
|
ignore = True
|
||||||
|
break
|
||||||
|
if ignore:
|
||||||
|
print(f"[TAG WATCH] Ignoring post {submission.id} due to ignore tag.")
|
||||||
|
continue
|
||||||
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:
|
||||||
@@ -424,4 +506,6 @@ class ModReplyBot:
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
bot = ModReplyBot()
|
bot = ModReplyBot()
|
||||||
|
# Start update checker thread
|
||||||
|
start_update_checker(bot.reddit, Config.SUBREDDIT, BOT_NAME, BOT_VERSION)
|
||||||
bot.run()
|
bot.run()
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
import requests
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import os
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
UPDATE_CHECK_INTERVAL = 3600 # Check every hour
|
||||||
|
UPDATE_COOLDOWN = 86400 # Don't send mail more than once per 24 hours
|
||||||
|
LAST_UPDATE_FILE = os.path.join(os.path.dirname(__file__), 'DB', '.last_update_check.txt')
|
||||||
|
|
||||||
|
|
||||||
|
def get_latest_version(bot_name):
|
||||||
|
"""Fetch latest version info from update server."""
|
||||||
|
try:
|
||||||
|
response = requests.get(f'https://updts.slfhstd.uk/api/version/{bot_name}', timeout=10)
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[UPDATE_CHECKER] Error fetching version: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def send_update_modmail(reddit, subreddit_name, bot_name, current_version, available_version, changelog_url):
|
||||||
|
"""Send modmail to subreddit modteam about available update."""
|
||||||
|
try:
|
||||||
|
subject = f"🤖 {bot_name} Update Available (v{available_version})"
|
||||||
|
message = f"""Hello,
|
||||||
|
|
||||||
|
An update is available for {bot_name}!
|
||||||
|
|
||||||
|
**Current Version:** {current_version}
|
||||||
|
**Available Version:** {available_version}
|
||||||
|
|
||||||
|
Changelog: {changelog_url}
|
||||||
|
|
||||||
|
Please visit the update server for installation instructions.
|
||||||
|
|
||||||
|
---
|
||||||
|
This is an automated message from the Update Checker."""
|
||||||
|
data = {
|
||||||
|
"subject": subject,
|
||||||
|
"text": message,
|
||||||
|
"to": f"/r/{subreddit_name}",
|
||||||
|
}
|
||||||
|
reddit.post("api/compose/", data=data)
|
||||||
|
print(f"[UPDATE_CHECKER] Sent update notification to r/{subreddit_name}")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[UPDATE_CHECKER] Error sending modmail: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def should_send_update_mail():
|
||||||
|
"""Check if enough time has passed since last update mail."""
|
||||||
|
if not os.path.exists(LAST_UPDATE_FILE):
|
||||||
|
return True
|
||||||
|
try:
|
||||||
|
with open(LAST_UPDATE_FILE, 'r') as f:
|
||||||
|
last_check = float(f.read().strip())
|
||||||
|
return (time.time() - last_check) >= UPDATE_COOLDOWN
|
||||||
|
except:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def mark_update_mailed():
|
||||||
|
"""Record when update mail was sent."""
|
||||||
|
os.makedirs(os.path.dirname(LAST_UPDATE_FILE), exist_ok=True)
|
||||||
|
with open(LAST_UPDATE_FILE, 'w') as f:
|
||||||
|
f.write(str(time.time()))
|
||||||
|
|
||||||
|
|
||||||
|
def update_checker_thread(reddit, subreddit_name, bot_name, current_version):
|
||||||
|
"""Background thread that checks for updates periodically."""
|
||||||
|
print(f"[UPDATE_CHECKER] Started for {bot_name} v{current_version}")
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
latest = get_latest_version(bot_name)
|
||||||
|
if latest:
|
||||||
|
available_version = latest.get('version')
|
||||||
|
changelog_url = latest.get('changelog_url', '')
|
||||||
|
if available_version and available_version != current_version:
|
||||||
|
if should_send_update_mail():
|
||||||
|
sent = send_update_modmail(
|
||||||
|
reddit, subreddit_name, bot_name, current_version, available_version, changelog_url
|
||||||
|
)
|
||||||
|
if sent:
|
||||||
|
mark_update_mailed()
|
||||||
|
else:
|
||||||
|
print(f"[UPDATE_CHECKER] No version info received.")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[UPDATE_CHECKER] Error in update checker thread: {e}")
|
||||||
|
time.sleep(UPDATE_CHECK_INTERVAL)
|
||||||
|
|
||||||
|
|
||||||
|
def start_update_checker(reddit, subreddit_name, bot_name, current_version):
|
||||||
|
threading.Thread(
|
||||||
|
target=update_checker_thread,
|
||||||
|
args=(reddit, subreddit_name, bot_name, current_version),
|
||||||
|
daemon=True
|
||||||
|
).start()
|
||||||
Reference in New Issue
Block a user