From 5dc209858121ea5a83951d6e2ad978357bb4b5a5 Mon Sep 17 00:00:00 2001 From: Slfhstd Date: Thu, 5 Mar 2026 17:07:19 +0000 Subject: [PATCH 1/3] Updates to docker configs --- config/config.py | 47 +++++++++++-------- example.env | 11 ++--- flairtimercomment.py | 104 ++++++++++++++++++++++++++++++------------- 3 files changed, 107 insertions(+), 55 deletions(-) diff --git a/config/config.py b/config/config.py index 2894081..204722f 100644 --- a/config/config.py +++ b/config/config.py @@ -1,27 +1,36 @@ + +# Bot authentication and global settings username = "" password = "" client_id = "" client_secret = "" user_agent = "Flair Timer Comment Bot" - -#Subreddits -subreddit = "" # "INEEEEDIT" "Ofcoursethatsathing" "All" - -flair_text = "Waiting for OP" # Case Sensitive - -interval = 30 # How often should the bot scan the subreddit for these posts, in seconds. Higher = slower/less accurate/save resources, lower = faster/more accurate/use more resources. - -hours = 48 # How many hours must the flair been on the post to send the notification - -searchlimit = 600 # Max: 1000, this should only be limited to save on resources. The bot sorts by new and if it isn't catching posts that are being changed to the flair simply because they are too old (say the 301st post on the subreddit is changed to the flair) then increase this limit. -# Comment message to post on old posts -comment_message = "" +# Subreddit to monitor +subreddit = "" # e.g. "INEEEEDIT", "Ofcoursethatsathing", "All" -# Whether the bot should lock the post after posting the comment (True/False) -# Default is False to avoid accidental locking; set to True to enable locking. -lock_post = False +# How often should the bot scan the subreddit for these posts, in seconds +interval = 30 -# Whether the distinguished comment should be stickied (True/False) -# Some subreddits may require `True` to keep moderator comments visible. -distinguish_sticky = False +# Max posts to search (for performance) +searchlimit = 600 + +# Multiple flair time configs +# Each entry can have: flair_text, hours, comment_message, lock_post, distinguish_sticky +flair_times = [ + { + "flair_text": "Waiting for OP", # Case Sensitive + "hours": 48, # How many hours must the flair been on the post to send the notification + "comment_message": "This post has had the 'Waiting for OP' flair for 48 hours.", + "lock_post": False, + "distinguish_sticky": False + }, + # Add more configs as needed + # { + # "flair_text": "Needs Info", + # "hours": 24, + # "comment_message": "This post has had the 'Needs Info' flair for 24 hours.", + # "lock_post": True, + # "distinguish_sticky": True + # }, +] diff --git a/example.env b/example.env index bb71cc1..9534e64 100644 --- a/example.env +++ b/example.env @@ -7,11 +7,12 @@ CLIENT_ID=your_client_id CLIENT_SECRET=your_client_secret USER_AGENT=Flair Timer Comment Bot +# Subreddit to monitor SUBREDDIT=your_subreddit -FLAIR_TEXT=Waiting for OP INTERVAL=30 -HOURS=48 SEARCHLIMIT=600 -COMMENT_MESSAGE=Your comment message here -LOCK_POST=False -DISTINGUISH_STICKY=False + +# Multiple flair configs as JSON string +# Example: +# FLAIR_TIMES_JSON=[{"flair_text": "Waiting for OP", "hours": 48, "comment_message": "Waiting for OP for 48 hours.", "lock_post": false, "distinguish_sticky": false}, {"flair_text": "Needs Info", "hours": 24, "comment_message": "Needs Info for 24 hours.", "lock_post": true, "distinguish_sticky": true}] +FLAIR_TIMES_JSON=[{"flair_text": "Waiting for OP", "hours": 48, "comment_message": "Waiting for OP for 48 hours.", "lock_post": false, "distinguish_sticky": false}] diff --git a/flairtimercomment.py b/flairtimercomment.py index 7402975..5667b21 100644 --- a/flairtimercomment.py +++ b/flairtimercomment.py @@ -7,13 +7,26 @@ import time # Helper to get env var or default + def env_or_default(var, default): return os.environ.get(var, default) +# Helper to get flair_times from env +def get_flair_times_from_env(): + flair_times_json = os.environ.get("FLAIR_TIMES_JSON", "") + if flair_times_json: + try: + return json.loads(flair_times_json) + except Exception as e: + print(f"Could not parse FLAIR_TIMES_JSON: {e}") + return None + # Create config/config.py from environment if missing or empty default_config_path = os.path.join('config', 'config.py') + def write_config_from_env(): os.makedirs('config', exist_ok=True) + flair_times = get_flair_times_from_env() with open(default_config_path, 'w') as f: f.write( f'username = "{env_or_default("USERNAME", "")}"\n' @@ -23,14 +36,23 @@ def write_config_from_env(): f'user_agent = "{env_or_default("USER_AGENT", "Flair Timer Comment Bot")}"\n' '\n' f'subreddit = "{env_or_default("SUBREDDIT", "")}"\n' - f'flair_text = "{env_or_default("FLAIR_TEXT", "Waiting for OP")}"\n' f'interval = {env_or_default("INTERVAL", "30")}\n' - f'hours = {env_or_default("HOURS", "48")}\n' f'searchlimit = {env_or_default("SEARCHLIMIT", "600")}\n' - f'comment_message = "{env_or_default("COMMENT_MESSAGE", "")}"\n' - f'lock_post = {env_or_default("LOCK_POST", "False")}\n' - f'distinguish_sticky = {env_or_default("DISTINGUISH_STICKY", "False")}\n' ) + # Write flair_times as a Python list + if flair_times: + f.write(f'flair_times = {json.dumps(flair_times)}\n') + else: + # Fallback to single flair config from env vars + f.write('flair_times = [\n') + f.write(' {\n') + f.write(f' "flair_text": "{env_or_default("FLAIR_TEXT", "Waiting for OP")}",\n') + f.write(f' "hours": {env_or_default("HOURS", "48")},\n') + f.write(f' "comment_message": "{env_or_default("COMMENT_MESSAGE", "")}",\n') + f.write(f' "lock_post": {env_or_default("LOCK_POST", "False")},\n') + f.write(f' "distinguish_sticky": {env_or_default("DISTINGUISH_STICKY", "False")}\n') + f.write(' }\n') + f.write(']\n') print(f"Configuration file auto-populated from environment variables at {default_config_path}.") # Check if config file exists and is non-empty, else generate from env @@ -59,60 +81,80 @@ def authentication(): print ("Authenticated as {}.".format(reddit.user.me())) return reddit -def main(reddit, posts: dict): + +def main(reddit, all_posts: dict): + # all_posts structure: {flair_text: {submission_id: timestamp}} while True: - for submission in reddit.subreddit(config.subreddit).new(limit=config.searchlimit): - if not submission.saved: - if submission.id not in posts.keys() and submission.link_flair_text == config.flair_text: - posts[submission.id] = time.time() - print(f"Post {submission} has been flaired {config.flair_text}") - if submission.id in posts.keys() and submission.link_flair_text != config.flair_text: - posts.pop(submission.id) - print(f"Post {submission} has been unflaired {config.flair_text}") - - for submission in posts: - if time.time() > posts[submission] + (config.hours * 60 * 60): - posts.pop(submission) - reddit.submission(submission).save() - # Optionally lock the post if configured - if getattr(config, 'lock_post', False): + for flair_cfg in config.flair_times: + flair_text = flair_cfg["flair_text"] + hours = flair_cfg["hours"] + comment_message = flair_cfg["comment_message"] + lock_post = flair_cfg.get("lock_post", False) + distinguish_sticky = flair_cfg.get("distinguish_sticky", False) + + # Ensure posts dict for this flair + posts = all_posts.setdefault(flair_text, {}) + + for submission in reddit.subreddit(config.subreddit).new(limit=config.searchlimit): + if not submission.saved: + if submission.id not in posts and submission.link_flair_text == flair_text: + posts[submission.id] = time.time() + print(f"Post {submission} has been flaired {flair_text}") + if submission.id in posts and submission.link_flair_text != flair_text: + posts.pop(submission.id) + print(f"Post {submission} has been unflaired {flair_text}") + + expired = [] + for submission_id, flair_time in posts.items(): + if time.time() > flair_time + (hours * 60 * 60): + expired.append(submission_id) + + for submission_id in expired: + posts.pop(submission_id) + subm = reddit.submission(submission_id) + subm.save() + if lock_post: try: - reddit.submission(submission).mod.lock() + subm.mod.lock() except Exception as e: print(f"Could not lock submission: {e}") - comment = reddit.submission(submission).reply(body=config.comment_message) + comment = subm.reply(body=comment_message) try: - sticky = getattr(config, 'distinguish_sticky', False) - if sticky: + if distinguish_sticky: comment.mod.distinguish(how="yes", sticky=True) else: comment.mod.distinguish(how="yes") - print(f"Distinguished comment (sticky={sticky})") + print(f"Distinguished comment (sticky={distinguish_sticky})") except Exception as e: print(f"Could not distinguish comment: {e}") - print(f"Post {submission} has been flaired {config.flair_text} for {config.hours} hours, posted comment") - break - - save_posts(posts) + print(f"Post {submission_id} has been flaired {flair_text} for {hours} hours, posted comment") + + save_posts(all_posts) time.sleep(config.interval) + def load_posts(): if not os.path.exists("config/posts.json"): with open("config/posts.json", "w+") as file: json.dump({}, file) with open("config/posts.json", "r+") as file: data = json.load(file) + # Ensure structure: {flair_text: {submission_id: timestamp}} + if not isinstance(data, dict): + return {} return data + def save_posts(data): with open('config/posts.json', 'w+') as file: json.dump(data, file) + while True: try: posts = load_posts() - main(reddit = authentication(), posts = posts) + main(reddit=authentication(), all_posts=posts) except Exception as e: print(e) From 1e22463db065400b1dcf9279d33873eb84075e4e Mon Sep 17 00:00:00 2001 From: Slfhstd Date: Thu, 5 Mar 2026 17:16:28 +0000 Subject: [PATCH 2/3] name change --- example.env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example.env b/example.env index 9534e64..825e744 100644 --- a/example.env +++ b/example.env @@ -1,4 +1,4 @@ -# Example .env file for FlairTimerComment +# Example .env file for FlairTimerCommentBot # Fill in your Reddit API and bot settings USERNAME=your_reddit_username From 818df5622296f86e1a1618013c8bc1e55f5452f7 Mon Sep 17 00:00:00 2001 From: Slfhstd Date: Thu, 5 Mar 2026 18:53:30 +0000 Subject: [PATCH 3/3] Final changes before merge --- config/flairconfig.py | 22 ++++++++++ flairtimercomment.py | 95 +++++++++++++++++++++++++------------------ 2 files changed, 77 insertions(+), 40 deletions(-) create mode 100644 config/flairconfig.py diff --git a/config/flairconfig.py b/config/flairconfig.py new file mode 100644 index 0000000..d2451ea --- /dev/null +++ b/config/flairconfig.py @@ -0,0 +1,22 @@ +# flairconfig.py +# This file defines the list of flair time configs for the bot. +# Edit this file to customize flair behaviors. + +flair_times = [ + { + "flair_text": "Waiting for OP", + "hours": 48, + "comment_message": "This post has had the 'Waiting for OP' flair for 48 hours.", + "lock_post": False, + "distinguish_sticky": False + }, + + { + "flair_text": "Solved", + "hours": 0.01, + "comment_message": "This post has had the 'Waiting for OP' flair for 48 hours.", + "lock_post": False, + "distinguish_sticky": False + }, + # Add more configs as needed +] diff --git a/flairtimercomment.py b/flairtimercomment.py index 5667b21..9433d7c 100644 --- a/flairtimercomment.py +++ b/flairtimercomment.py @@ -7,26 +7,16 @@ import time # Helper to get env var or default - def env_or_default(var, default): return os.environ.get(var, default) -# Helper to get flair_times from env -def get_flair_times_from_env(): - flair_times_json = os.environ.get("FLAIR_TIMES_JSON", "") - if flair_times_json: - try: - return json.loads(flair_times_json) - except Exception as e: - print(f"Could not parse FLAIR_TIMES_JSON: {e}") - return None # Create config/config.py from environment if missing or empty default_config_path = os.path.join('config', 'config.py') + def write_config_from_env(): os.makedirs('config', exist_ok=True) - flair_times = get_flair_times_from_env() with open(default_config_path, 'w') as f: f.write( f'username = "{env_or_default("USERNAME", "")}"\n' @@ -39,22 +29,9 @@ def write_config_from_env(): f'interval = {env_or_default("INTERVAL", "30")}\n' f'searchlimit = {env_or_default("SEARCHLIMIT", "600")}\n' ) - # Write flair_times as a Python list - if flair_times: - f.write(f'flair_times = {json.dumps(flair_times)}\n') - else: - # Fallback to single flair config from env vars - f.write('flair_times = [\n') - f.write(' {\n') - f.write(f' "flair_text": "{env_or_default("FLAIR_TEXT", "Waiting for OP")}",\n') - f.write(f' "hours": {env_or_default("HOURS", "48")},\n') - f.write(f' "comment_message": "{env_or_default("COMMENT_MESSAGE", "")}",\n') - f.write(f' "lock_post": {env_or_default("LOCK_POST", "False")},\n') - f.write(f' "distinguish_sticky": {env_or_default("DISTINGUISH_STICKY", "False")}\n') - f.write(' }\n') - f.write(']\n') print(f"Configuration file auto-populated from environment variables at {default_config_path}.") + # Check if config file exists and is non-empty, else generate from env def config_needs_populating(): if not os.path.exists(default_config_path): @@ -65,27 +42,66 @@ def config_needs_populating(): return len(content) == 0 except Exception: return True - + + if config_needs_populating(): write_config_from_env() - # Optionally exit after populating, or continue to run + +# Import main config import config + + +# Create default flairconfig.py if missing +flair_config_path = os.path.join('config', 'flairconfig.py') + + +def write_default_flairconfig(): + if not os.path.exists(flair_config_path): + os.makedirs(os.path.dirname(flair_config_path), exist_ok=True) + with open(flair_config_path, 'w') as f: + f.write('# flairconfig.py\n') + f.write('# This file defines the list of flair time configs for the bot.\n') + f.write('flair_times = [\n') + f.write(' {\n') + f.write(' "flair_text": "Waiting for OP",\n') + f.write(' "hours": 48,\n') + f.write(' "comment_message": "This post has had the \'Waiting for OP\' flair for 48 hours.",\n') + f.write(' "lock_post": False,\n') + f.write(' "distinguish_sticky": False\n') + f.write(' },\n') + f.write(']\n') + print(f"Default flairconfig.py created at {flair_config_path}.") + + +write_default_flairconfig() + + +# Load flair_times from flairconfig.py +import importlib.util +spec = importlib.util.spec_from_file_location("flairconfig", flair_config_path) +flairconfig = importlib.util.module_from_spec(spec) +spec.loader.exec_module(flairconfig) +flair_times = getattr(flairconfig, "flair_times", []) + + def authentication(): - print ("Authenticating...") - reddit = praw.Reddit(username = config.username, - password = config.password, - client_id = config.client_id, - client_secret = config.client_secret, - user_agent = config.user_agent) - print ("Authenticated as {}.".format(reddit.user.me())) + print("Authenticating...") + reddit = praw.Reddit( + username=config.username, + password=config.password, + client_id=config.client_id, + client_secret=config.client_secret, + user_agent=config.user_agent + ) + print("Authenticated as {}.".format(reddit.user.me())) return reddit - + def main(reddit, all_posts: dict): # all_posts structure: {flair_text: {submission_id: timestamp}} while True: - for flair_cfg in config.flair_times: + for flair_cfg in flair_times: flair_text = flair_cfg["flair_text"] hours = flair_cfg["hours"] comment_message = flair_cfg["comment_message"] @@ -132,7 +148,7 @@ def main(reddit, all_posts: dict): save_posts(all_posts) time.sleep(config.interval) - + def load_posts(): if not os.path.exists("config/posts.json"): @@ -144,13 +160,12 @@ def load_posts(): if not isinstance(data, dict): return {} return data - + def save_posts(data): with open('config/posts.json', 'w+') as file: json.dump(data, file) - - + while True: try: