# Reddit Test Posts Bot # This script reads config from a subreddit wiki page and makes test posts # when a moderator sends a chat message containing a configured trigger. import praw import time import os import threading from config import fetch_config_from_wiki, validate_config_from_wiki, get_trigger_posts from update_checker import start_update_checker # ==================== VERSION ==================== BOT_VERSION = "2.1.0" BOT_NAME = "TestPostsBot" # ================================================== 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') REDDIT_USER_AGENT = os.environ.get('REDDIT_USER_AGENT', f'{BOT_NAME}/{BOT_VERSION} on {REDDIT_USERNAME}') SUBREDDIT = os.environ.get('SUBREDDIT') WIKI_PAGE = os.environ.get('WIKI_PAGE', 'testpostsbot_config') def is_moderator(reddit, subreddit_name): """Check if the bot is a moderator of the subreddit.""" 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): """Make the specified posts to the subreddit.""" subreddit = reddit.subreddit(subreddit_name) for post in posts: title = post.get('title', 'Test Post') body = post.get('body', '') 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') # 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 except Exception as e: print(f"[CHAT WATCH] Chat message watcher error: {e}") import traceback traceback.print_exc() time.sleep(30) def main(): """Main function - runs bot constantly.""" reddit = praw.Reddit( client_id=REDDIT_CLIENT_ID, client_secret=REDDIT_CLIENT_SECRET, username=REDDIT_USERNAME, password=REDDIT_PASSWORD, user_agent=REDDIT_USER_AGENT ) if not is_moderator(reddit, SUBREDDIT): print(f"Bot is not a moderator of r/{SUBREDDIT}. Cannot continue.") return # 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.") # Start update checker in background thread start_update_checker(reddit, SUBREDDIT, BOT_NAME, BOT_VERSION) print("[STARTUP] Update checker started.") # Keep main thread alive try: while True: time.sleep(60) except KeyboardInterrupt: print("[SHUTDOWN] Bot shutting down...") if __name__ == '__main__': main() if __name__ == '__main__': main()