2026-03-04 23:01:36 +00:00
|
|
|
# Reddit Test Posts Bot
|
2026-03-11 16:36:10 +00:00
|
|
|
# This script reads config from a subreddit wiki page and makes test posts
|
|
|
|
|
# when a moderator sends a chat message containing a configured trigger.
|
2026-03-04 23:01:36 +00:00
|
|
|
import praw
|
|
|
|
|
import time
|
|
|
|
|
import os
|
2026-03-11 16:36:10 +00:00
|
|
|
import threading
|
|
|
|
|
from config import fetch_config_from_wiki, validate_config_from_wiki, get_trigger_posts
|
2026-03-11 20:51:11 +00:00
|
|
|
from update_checker import start_update_checker
|
|
|
|
|
|
|
|
|
|
# ==================== VERSION ====================
|
2026-03-11 21:01:49 +00:00
|
|
|
BOT_VERSION = "2.1.0"
|
2026-03-11 20:51:11 +00:00
|
|
|
BOT_NAME = "TestPostsBot"
|
|
|
|
|
# ==================================================
|
2026-03-04 23:01:36 +00:00
|
|
|
|
|
|
|
|
REDDIT_CLIENT_ID = os.environ.get('REDDIT_CLIENT_ID')
|
|
|
|
|
REDDIT_CLIENT_SECRET = os.environ.get('REDDIT_CLIENT_SECRET')
|
|
|
|
|
REDDIT_USERNAME = os.environ.get('REDDIT_USERNAME')
|
|
|
|
|
REDDIT_PASSWORD = os.environ.get('REDDIT_PASSWORD')
|
2026-03-11 20:51:11 +00:00
|
|
|
REDDIT_USER_AGENT = os.environ.get('REDDIT_USER_AGENT', f'{BOT_NAME}/{BOT_VERSION} on {REDDIT_USERNAME}')
|
2026-03-04 23:01:36 +00:00
|
|
|
|
|
|
|
|
SUBREDDIT = os.environ.get('SUBREDDIT')
|
|
|
|
|
WIKI_PAGE = os.environ.get('WIKI_PAGE', 'testpostsbot_config')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def is_moderator(reddit, subreddit_name):
|
2026-03-11 16:36:10 +00:00
|
|
|
"""Check if the bot is a moderator of the subreddit."""
|
2026-03-04 23:01:36 +00:00
|
|
|
subreddit = reddit.subreddit(subreddit_name)
|
|
|
|
|
mods = [str(mod) for mod in subreddit.moderator()]
|
|
|
|
|
return reddit.user.me().name in mods
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def make_posts(reddit, subreddit_name, posts):
|
2026-03-11 16:36:10 +00:00
|
|
|
"""Make the specified posts to the subreddit."""
|
2026-03-04 23:01:36 +00:00
|
|
|
subreddit = reddit.subreddit(subreddit_name)
|
|
|
|
|
for post in posts:
|
|
|
|
|
title = post.get('title', 'Test Post')
|
|
|
|
|
body = post.get('body', '')
|
2026-03-11 16:36:10 +00:00
|
|
|
print(f"[POSTING] Posting: {title}")
|
|
|
|
|
try:
|
|
|
|
|
subreddit.submit(title, selftext=body)
|
|
|
|
|
time.sleep(2) # avoid rate limits
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"[POSTING] Error posting '{title}': {e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def chat_message_watcher(reddit, subreddit_name):
|
|
|
|
|
"""
|
|
|
|
|
Watches for chat messages from moderators containing trigger keywords.
|
|
|
|
|
When a trigger is found, posts the configured posts for that trigger.
|
|
|
|
|
"""
|
|
|
|
|
chat_requests_file = os.path.join(os.path.dirname(__file__), 'DB', 'chat_wiki_requests.txt')
|
|
|
|
|
os.makedirs(os.path.dirname(chat_requests_file), exist_ok=True)
|
|
|
|
|
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())
|
|
|
|
|
|
|
|
|
|
subreddit = reddit.subreddit(subreddit_name)
|
|
|
|
|
|
|
|
|
|
while True:
|
|
|
|
|
try:
|
|
|
|
|
for message in 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')
|
|
|
|
|
|
2026-03-11 17:26:02 +00:00
|
|
|
# Check if message has body and author
|
|
|
|
|
if not hasattr(message, 'body'):
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
author = getattr(message, 'author', None)
|
|
|
|
|
if not author:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
# Check if sender is a moderator
|
|
|
|
|
try:
|
|
|
|
|
is_mod = author in subreddit.moderator()
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"[CHAT WATCH] Error checking if {author} is mod: {e}")
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
if not is_mod:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
message_body_lower = message.body.lower()
|
|
|
|
|
print(f"[CHAT WATCH] Moderator '{author}' sent message: {message.body[:100]}")
|
|
|
|
|
|
|
|
|
|
# Handle special 'reload-config' command
|
|
|
|
|
if 'reload-config' in message_body_lower:
|
|
|
|
|
print(f"[CHAT WATCH] Reload-config command detected.")
|
|
|
|
|
result = validate_config_from_wiki(reddit, subreddit_name, WIKI_PAGE)
|
|
|
|
|
if result:
|
|
|
|
|
print("[CHAT WATCH] Wiki config validated successfully.")
|
|
|
|
|
reply_text = "Config validated successfully. Config is valid YAML."
|
|
|
|
|
else:
|
|
|
|
|
print("[CHAT WATCH] Wiki config validation failed.")
|
|
|
|
|
reply_text = "Config validation failed. Check the wiki config YAML formatting."
|
|
|
|
|
try:
|
|
|
|
|
message.reply(reply_text)
|
|
|
|
|
print(f"[CHAT WATCH] Replied to message {message.id}.")
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"[CHAT WATCH] Error replying to message {message.id}: {e}")
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
# Load current config to check for triggers
|
|
|
|
|
config = fetch_config_from_wiki(reddit, subreddit_name, WIKI_PAGE)
|
|
|
|
|
posts_config = config.get('posts', [])
|
|
|
|
|
|
|
|
|
|
trigger_found = False
|
|
|
|
|
|
|
|
|
|
# Check if message contains any trigger
|
|
|
|
|
for post_config in posts_config:
|
|
|
|
|
if not isinstance(post_config, dict):
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
trigger = post_config.get('trigger', '').lower()
|
|
|
|
|
if trigger and trigger in message_body_lower:
|
|
|
|
|
trigger_found = True
|
|
|
|
|
print(f"[CHAT WATCH] Matched trigger '{trigger}' in message.")
|
|
|
|
|
|
|
|
|
|
# Get posts for this trigger
|
|
|
|
|
trigger_posts = get_trigger_posts(reddit, subreddit_name, WIKI_PAGE, trigger)
|
|
|
|
|
|
|
|
|
|
if trigger_posts:
|
|
|
|
|
print(f"[CHAT WATCH] Found {len(trigger_posts)} post(s) for trigger '{trigger}'.")
|
|
|
|
|
make_posts(reddit, subreddit_name, trigger_posts)
|
|
|
|
|
reply_text = f"Successfully posted {len(trigger_posts)} post(s) for trigger '{trigger}'."
|
|
|
|
|
else:
|
|
|
|
|
print(f"[CHAT WATCH] No posts found for trigger '{trigger}'.")
|
|
|
|
|
reply_text = f"No posts configured for trigger '{trigger}'."
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
message.reply(reply_text)
|
|
|
|
|
print(f"[CHAT WATCH] Replied to message {message.id}.")
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"[CHAT WATCH] Error replying to message {message.id}: {e}")
|
|
|
|
|
|
|
|
|
|
# Only process the first matching trigger per message
|
|
|
|
|
break
|
2026-03-11 16:36:10 +00:00
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"[CHAT WATCH] Chat message watcher error: {e}")
|
2026-03-11 17:26:02 +00:00
|
|
|
import traceback
|
|
|
|
|
traceback.print_exc()
|
2026-03-11 16:36:10 +00:00
|
|
|
|
|
|
|
|
time.sleep(30)
|
2026-03-04 23:01:36 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
2026-03-11 16:36:10 +00:00
|
|
|
"""Main function - runs bot constantly."""
|
2026-03-04 23:01:36 +00:00
|
|
|
reddit = praw.Reddit(
|
|
|
|
|
client_id=REDDIT_CLIENT_ID,
|
|
|
|
|
client_secret=REDDIT_CLIENT_SECRET,
|
|
|
|
|
username=REDDIT_USERNAME,
|
|
|
|
|
password=REDDIT_PASSWORD,
|
|
|
|
|
user_agent=REDDIT_USER_AGENT
|
|
|
|
|
)
|
|
|
|
|
|
2026-03-11 16:36:10 +00:00
|
|
|
if not is_moderator(reddit, SUBREDDIT):
|
|
|
|
|
print(f"Bot is not a moderator of r/{SUBREDDIT}. Cannot continue.")
|
|
|
|
|
return
|
2026-03-04 23:01:36 +00:00
|
|
|
|
2026-03-11 16:36:10 +00:00
|
|
|
# Validate initial config
|
|
|
|
|
if not validate_config_from_wiki(reddit, SUBREDDIT, WIKI_PAGE):
|
|
|
|
|
print(f"Initial wiki config is invalid. Please fix the config at r/{SUBREDDIT}/wiki/{WIKI_PAGE}")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
print(f"[STARTUP] TestPostsBot started for r/{SUBREDDIT}")
|
|
|
|
|
print(f"[STARTUP] Config page: r/{SUBREDDIT}/wiki/{WIKI_PAGE}")
|
|
|
|
|
print(f"[STARTUP] Bot username: {reddit.user.me()}")
|
|
|
|
|
|
|
|
|
|
# Start chat message watcher in background thread
|
|
|
|
|
chat_thread = threading.Thread(
|
|
|
|
|
target=chat_message_watcher,
|
|
|
|
|
args=(reddit, SUBREDDIT),
|
|
|
|
|
daemon=True
|
|
|
|
|
)
|
|
|
|
|
chat_thread.start()
|
|
|
|
|
print("[STARTUP] Chat message watcher started.")
|
|
|
|
|
|
2026-03-11 20:51:11 +00:00
|
|
|
# Start update checker in background thread
|
|
|
|
|
start_update_checker(reddit, SUBREDDIT, BOT_NAME, BOT_VERSION)
|
|
|
|
|
print("[STARTUP] Update checker started.")
|
|
|
|
|
|
2026-03-11 16:36:10 +00:00
|
|
|
# Keep main thread alive
|
|
|
|
|
try:
|
|
|
|
|
while True:
|
|
|
|
|
time.sleep(60)
|
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
|
print("[SHUTDOWN] Bot shutting down...")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
main()
|
2026-03-04 23:01:36 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
main()
|