2026-02-25 22:18:02 +00:00
|
|
|
|
2026-02-25 19:53:47 +00:00
|
|
|
import praw
|
|
|
|
|
import os
|
2026-02-22 20:03:36 +00:00
|
|
|
import os.path
|
|
|
|
|
import json
|
|
|
|
|
import time
|
2026-02-25 19:53:47 +00:00
|
|
|
|
2026-02-25 22:18:02 +00:00
|
|
|
|
|
|
|
|
# Helper to get env var or default
|
|
|
|
|
def env_or_default(var, default):
|
|
|
|
|
return os.environ.get(var, default)
|
|
|
|
|
|
2026-03-05 17:07:19 +00:00
|
|
|
|
2026-02-25 22:18:02 +00:00
|
|
|
# Create config/config.py from environment if missing or empty
|
2026-02-25 19:53:47 +00:00
|
|
|
default_config_path = os.path.join('config', 'config.py')
|
2026-03-05 17:07:19 +00:00
|
|
|
|
2026-03-05 18:53:30 +00:00
|
|
|
|
2026-02-25 22:18:02 +00:00
|
|
|
def write_config_from_env():
|
2026-02-25 19:53:47 +00:00
|
|
|
os.makedirs('config', exist_ok=True)
|
|
|
|
|
with open(default_config_path, 'w') as f:
|
|
|
|
|
f.write(
|
2026-02-25 22:18:02 +00:00
|
|
|
f'username = "{env_or_default("USERNAME", "")}"\n'
|
|
|
|
|
f'password = "{env_or_default("PASSWORD", "")}"\n'
|
|
|
|
|
f'client_id = "{env_or_default("CLIENT_ID", "")}"\n'
|
|
|
|
|
f'client_secret = "{env_or_default("CLIENT_SECRET", "")}"\n'
|
|
|
|
|
f'user_agent = "{env_or_default("USER_AGENT", "Flair Timer Comment Bot")}"\n'
|
2026-02-25 19:53:47 +00:00
|
|
|
'\n'
|
2026-02-25 22:18:02 +00:00
|
|
|
f'subreddit = "{env_or_default("SUBREDDIT", "")}"\n'
|
|
|
|
|
f'interval = {env_or_default("INTERVAL", "30")}\n'
|
|
|
|
|
f'searchlimit = {env_or_default("SEARCHLIMIT", "600")}\n'
|
2026-02-25 19:53:47 +00:00
|
|
|
)
|
2026-02-25 22:18:02 +00:00
|
|
|
print(f"Configuration file auto-populated from environment variables at {default_config_path}.")
|
|
|
|
|
|
2026-03-05 18:53:30 +00:00
|
|
|
|
2026-02-25 22:18:02 +00:00
|
|
|
# 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):
|
|
|
|
|
return True
|
|
|
|
|
try:
|
|
|
|
|
with open(default_config_path, 'r') as f:
|
|
|
|
|
content = f.read().strip()
|
|
|
|
|
return len(content) == 0
|
|
|
|
|
except Exception:
|
|
|
|
|
return True
|
2026-03-05 18:53:30 +00:00
|
|
|
|
|
|
|
|
|
2026-02-25 22:18:02 +00:00
|
|
|
if config_needs_populating():
|
|
|
|
|
write_config_from_env()
|
2026-02-25 19:53:47 +00:00
|
|
|
|
2026-03-05 18:53:30 +00:00
|
|
|
|
|
|
|
|
# Import main config
|
2026-02-25 19:53:47 +00:00
|
|
|
import config
|
2026-03-05 18:53:30 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
# 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", [])
|
|
|
|
|
|
|
|
|
|
|
2026-02-22 20:03:36 +00:00
|
|
|
def authentication():
|
2026-03-05 18:53:30 +00:00
|
|
|
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()))
|
2026-02-22 20:03:36 +00:00
|
|
|
return reddit
|
2026-03-05 18:53:30 +00:00
|
|
|
|
2026-03-05 17:07:19 +00:00
|
|
|
|
|
|
|
|
def main(reddit, all_posts: dict):
|
|
|
|
|
# all_posts structure: {flair_text: {submission_id: timestamp}}
|
2026-02-22 20:03:36 +00:00
|
|
|
while True:
|
2026-03-05 18:53:30 +00:00
|
|
|
for flair_cfg in flair_times:
|
2026-03-05 17:07:19 +00:00
|
|
|
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:
|
2026-02-22 22:21:11 +00:00
|
|
|
try:
|
2026-03-05 17:07:19 +00:00
|
|
|
subm.mod.lock()
|
2026-02-22 22:21:11 +00:00
|
|
|
except Exception as e:
|
|
|
|
|
print(f"Could not lock submission: {e}")
|
|
|
|
|
|
2026-03-05 17:07:19 +00:00
|
|
|
comment = subm.reply(body=comment_message)
|
2026-02-22 22:52:40 +00:00
|
|
|
try:
|
2026-03-05 17:07:19 +00:00
|
|
|
if distinguish_sticky:
|
2026-02-22 22:52:40 +00:00
|
|
|
comment.mod.distinguish(how="yes", sticky=True)
|
|
|
|
|
else:
|
|
|
|
|
comment.mod.distinguish(how="yes")
|
2026-03-05 17:07:19 +00:00
|
|
|
print(f"Distinguished comment (sticky={distinguish_sticky})")
|
2026-02-22 22:52:40 +00:00
|
|
|
except Exception as e:
|
|
|
|
|
print(f"Could not distinguish comment: {e}")
|
2026-03-05 17:07:19 +00:00
|
|
|
print(f"Post {submission_id} has been flaired {flair_text} for {hours} hours, posted comment")
|
|
|
|
|
|
|
|
|
|
save_posts(all_posts)
|
2026-02-22 20:03:36 +00:00
|
|
|
time.sleep(config.interval)
|
2026-03-05 18:53:30 +00:00
|
|
|
|
2026-03-05 17:07:19 +00:00
|
|
|
|
2026-02-22 20:03:36 +00:00
|
|
|
def load_posts():
|
2026-02-22 22:41:30 +00:00
|
|
|
if not os.path.exists("config/posts.json"):
|
|
|
|
|
with open("config/posts.json", "w+") as file:
|
2026-02-22 20:03:36 +00:00
|
|
|
json.dump({}, file)
|
2026-02-22 22:41:30 +00:00
|
|
|
with open("config/posts.json", "r+") as file:
|
2026-02-22 20:03:36 +00:00
|
|
|
data = json.load(file)
|
2026-03-05 17:07:19 +00:00
|
|
|
# Ensure structure: {flair_text: {submission_id: timestamp}}
|
|
|
|
|
if not isinstance(data, dict):
|
|
|
|
|
return {}
|
2026-02-22 20:03:36 +00:00
|
|
|
return data
|
2026-03-05 18:53:30 +00:00
|
|
|
|
2026-03-05 17:07:19 +00:00
|
|
|
|
2026-02-22 20:03:36 +00:00
|
|
|
def save_posts(data):
|
2026-02-22 22:41:30 +00:00
|
|
|
with open('config/posts.json', 'w+') as file:
|
2026-02-22 20:03:36 +00:00
|
|
|
json.dump(data, file)
|
2026-03-05 18:53:30 +00:00
|
|
|
|
2026-03-05 17:07:19 +00:00
|
|
|
|
2026-02-22 20:03:36 +00:00
|
|
|
while True:
|
|
|
|
|
try:
|
|
|
|
|
posts = load_posts()
|
2026-03-05 17:07:19 +00:00
|
|
|
main(reddit=authentication(), all_posts=posts)
|
2026-02-22 20:03:36 +00:00
|
|
|
except Exception as e:
|
|
|
|
|
print(e)
|