V2 release
This commit is contained in:
+204
-57
@@ -5,6 +5,52 @@ from config import get_reddit, Config
|
||||
import time
|
||||
|
||||
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):
|
||||
import os
|
||||
self.reddit = get_reddit()
|
||||
@@ -14,6 +60,11 @@ class ModReplyBot:
|
||||
self.comments = []
|
||||
self.commented_posts = set()
|
||||
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.load_commented_posts()
|
||||
self.log_level = Config.LOG_LEVEL
|
||||
@@ -44,43 +95,64 @@ class ModReplyBot:
|
||||
# Track last error revision to prevent modmail spam
|
||||
if not hasattr(self, '_last_config_error_revision'):
|
||||
self._last_config_error_revision = None
|
||||
import yaml
|
||||
try:
|
||||
wiki_page = Config.WIKI_PAGE
|
||||
wiki = self.subreddit.wiki[wiki_page]
|
||||
wiki_content = wiki.content_md
|
||||
self._wiki_revision_id = getattr(wiki, 'revision_id', None)
|
||||
config = yaml.safe_load(wiki_content)
|
||||
if not isinstance(config, dict) or 'triggers' not in config:
|
||||
raise ValueError("Wiki config missing required 'triggers' key or is not a dict.")
|
||||
self.triggers = []
|
||||
self.comments = []
|
||||
self.statuses = []
|
||||
self.tag_comments = {}
|
||||
self.tag_statuses = {}
|
||||
for entry in config.get('triggers', []):
|
||||
self.triggers.append(entry.get('trigger', '').strip())
|
||||
self.comments.append(entry.get('comment', '').strip())
|
||||
self.statuses.append(entry.get('status', 'enabled').strip().lower())
|
||||
for entry in config.get('post_tags', []):
|
||||
tags_str = entry.get('tag', '').strip()
|
||||
comment = entry.get('comment', '').strip()
|
||||
status = entry.get('status', 'enabled').strip().lower()
|
||||
tags = [t.strip().lower() for t in tags_str.split(',') if t.strip()]
|
||||
for tag in tags:
|
||||
self.tag_comments[tag] = comment
|
||||
self.tag_statuses[tag] = status
|
||||
# Reset error revision tracker on successful config
|
||||
self._last_config_error_revision = None
|
||||
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
|
||||
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:
|
||||
wiki_page = Config.WIKI_PAGE
|
||||
wiki = self.subreddit.wiki[wiki_page]
|
||||
wiki_content = wiki.content_md
|
||||
self._wiki_revision_id = getattr(wiki, 'revision_id', None)
|
||||
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:
|
||||
self.log("Wiki config missing required 'triggers' key or is not a dict.")
|
||||
return False
|
||||
self.triggers = []
|
||||
self.comments = []
|
||||
self.statuses = []
|
||||
self.flair_ids = []
|
||||
self.stickied = []
|
||||
self.lock_post = []
|
||||
self.lock_comment = []
|
||||
self.tag_comments = {}
|
||||
self.tag_statuses = {}
|
||||
self.tag_flair_ids = {}
|
||||
for entry in config.get('triggers', []):
|
||||
self.triggers.append(entry.get('trigger', '').strip())
|
||||
self.comments.append(entry.get('comment', '').strip())
|
||||
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', []):
|
||||
tags_str = entry.get('tag', '').strip()
|
||||
comment = entry.get('comment', '').strip()
|
||||
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()]
|
||||
for tag in tags:
|
||||
self.tag_comments[tag] = comment
|
||||
self.tag_statuses[tag] = status
|
||||
self.tag_flair_ids[tag] = flair_id
|
||||
self._last_config_error_revision = None
|
||||
return True
|
||||
|
||||
def notify_mods_config_error(self, error_message):
|
||||
try:
|
||||
@@ -133,11 +205,10 @@ class ModReplyBot:
|
||||
config_ok = self.fetch_yaml_config()
|
||||
new_revision = getattr(self, '_wiki_revision_id', None)
|
||||
if old_revision and new_revision and old_revision != new_revision:
|
||||
if config_ok:
|
||||
self.log("Wiki config changed, reloading triggers and tag comments.")
|
||||
self.notify_mods_config_change(new_revision)
|
||||
else:
|
||||
self.log("Wiki config error detected, not reloading bot.")
|
||||
if config_ok:
|
||||
self.log("Wiki config changed, reloading triggers and tag comments.")
|
||||
else:
|
||||
self.log("Wiki config error detected, not reloading bot.")
|
||||
self.log_level = Config.LOG_LEVEL
|
||||
last_revision = new_revision
|
||||
for comment in self.subreddit.stream.comments(skip_existing=True):
|
||||
@@ -145,6 +216,8 @@ class ModReplyBot:
|
||||
self.handle_comment(comment)
|
||||
except Exception as e:
|
||||
print(f"Mod comment watcher error: {e}")
|
||||
import time
|
||||
time.sleep(5)
|
||||
|
||||
def mod_report_watcher():
|
||||
last_revision = None
|
||||
@@ -169,6 +242,49 @@ class ModReplyBot:
|
||||
|
||||
threading.Thread(target=mod_comment_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
|
||||
while True:
|
||||
@@ -180,6 +296,7 @@ class ModReplyBot:
|
||||
matched_trigger = None
|
||||
matched_comment = None
|
||||
matched_status = None
|
||||
matched_idx = None
|
||||
# Check report reasons for triggers
|
||||
if hasattr(submission, 'mod_reports') and submission.mod_reports:
|
||||
for report_tuple in submission.mod_reports:
|
||||
@@ -190,6 +307,7 @@ class ModReplyBot:
|
||||
matched_trigger = trigger
|
||||
matched_comment = self.comments[idx]
|
||||
matched_status = self.statuses[idx] if idx < len(self.statuses) else 'enabled'
|
||||
matched_idx = idx
|
||||
break
|
||||
if matched_trigger:
|
||||
break
|
||||
@@ -205,18 +323,8 @@ class ModReplyBot:
|
||||
try:
|
||||
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
|
||||
if matched_status == 'enabled':
|
||||
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)
|
||||
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)")
|
||||
self.approve_and_comment(submission, comment_text, matched_status, matched_idx)
|
||||
self.triggered_posts.add(trigger_key)
|
||||
except Exception as e:
|
||||
print(f"Error commenting on mod report: {e}")
|
||||
else:
|
||||
@@ -240,15 +348,54 @@ class ModReplyBot:
|
||||
print(f"Error removing comment: {e}")
|
||||
submission = comment.submission
|
||||
self.fetch_yaml_config()
|
||||
self.approve_and_comment(submission, self.comments[idx], status)
|
||||
self.approve_and_comment(submission, self.comments[idx], status, idx)
|
||||
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:
|
||||
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':
|
||||
print(f"[DEBUG] Submission object: {submission}, ID: {submission.id}, Type: {type(submission)}")
|
||||
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}")
|
||||
self.save_commented_post(submission.id)
|
||||
elif status == 'log-only':
|
||||
|
||||
Reference in New Issue
Block a user