Initial commit

This commit is contained in:
Alexander Dörflinger
2025-12-08 10:58:53 +01:00
commit 783a95f4b4
19 changed files with 933 additions and 0 deletions

5
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,5 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/

10
.idea/ChristmanEvent.iml generated Normal file
View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.10 (ChristmanEvent)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

7
.idea/dictionaries/project.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<component name="ProjectDictionaryState">
<dictionary name="project">
<words>
<w>flaired</w>
</words>
</dictionary>
</component>

View File

@@ -0,0 +1,20 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PyMethodMayBeStaticInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PyPep8NamingInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="ignoredErrors">
<list>
<option value="N813" />
</list>
</option>
</inspection_tool>
<inspection_tool class="PyUnresolvedReferencesInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredIdentifiers">
<list>
<option value="google.genai" />
</list>
</option>
</inspection_tool>
</profile>
</component>

View File

@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

7
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.10 (ChristmanEvent)" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (ChristmanEvent)" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/ChristmanEvent.iml" filepath="$PROJECT_DIR$/.idea/ChristmanEvent.iml" />
</modules>
</component>
</project>

442
ChristmasEvent.py Executable file
View File

@@ -0,0 +1,442 @@
#!/usr/bin/env python3
import praw
import sqlite3
import time
import datetime
import re
import config
# ==============================================================================
# DATABASE MANAGER
# ==============================================================================
class DatabaseManager:
def __init__(self, db_name="christmas_event.db"):
self.conn = sqlite3.connect(db_name, check_same_thread=False)
self.cursor = self.conn.cursor()
self.create_tables()
def create_tables(self):
# Participants table
self.cursor.execute('''
CREATE TABLE IF NOT EXISTS participants (
username TEXT PRIMARY KEY,
joined_at REAL
)
''')
# Wishes table
self.cursor.execute('''
CREATE TABLE IF NOT EXISTS wishes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
recipient TEXT,
sender TEXT,
message TEXT,
received_at REAL,
FOREIGN KEY(recipient) REFERENCES participants(username),
UNIQUE(sender, recipient)
)
''')
# Conversations table
self.cursor.execute('''
CREATE TABLE IF NOT EXISTS conversations (
sender TEXT PRIMARY KEY,
state TEXT,
pending_recipient TEXT,
pending_message TEXT,
timestamp REAL
)
''')
# Tracks if the final gift has been sent to a user
self.cursor.execute('''
CREATE TABLE IF NOT EXISTS deliveries (
username TEXT PRIMARY KEY,
delivered_at TIMESTAMP
)
''')
self.conn.commit()
def add_participant(self, username):
try:
self.cursor.execute("INSERT INTO participants VALUES (?, ?)",
(username, datetime.datetime.now()))
self.conn.commit()
return True
except sqlite3.IntegrityError:
return False
def save_wish(self, sender, recipient, message):
"""Inserts or Replaces a wish"""
self.cursor.execute("SELECT username FROM participants WHERE username = ?", (recipient,))
if not self.cursor.fetchone():
self.add_participant(recipient)
self.cursor.execute('''
INSERT OR REPLACE INTO wishes (recipient, sender, message, received_at)
VALUES (?, ?, ?, ?)
''', (recipient, sender, message, datetime.datetime.now()))
self.conn.commit()
return True
def check_existing_wish(self, sender, recipient):
self.cursor.execute("SELECT message FROM wishes WHERE sender = ? AND recipient = ?", (sender, recipient))
result = self.cursor.fetchone()
return result[0] if result else None
def get_forest_data(self):
self.cursor.execute('''
SELECT p.username, COUNT(w.id) as wish_count
FROM participants p
LEFT JOIN wishes w ON p.username = w.recipient
GROUP BY p.username
ORDER BY p.username ASC
''')
return self.cursor.fetchall()
def set_conversation_state(self, sender, state, recipient, message):
self.cursor.execute('''
INSERT OR REPLACE INTO conversations (sender, state, pending_recipient, pending_message, timestamp)
VALUES (?, ?, ?, ?, ?)
''', (sender, state, recipient, message, datetime.datetime.now()))
self.conn.commit()
def get_conversation(self, sender):
self.cursor.execute("SELECT state, pending_recipient, pending_message FROM conversations WHERE sender = ?",
(sender,))
return self.cursor.fetchone()
def clear_conversation(self, sender):
self.cursor.execute("DELETE FROM conversations WHERE sender = ?", (sender,))
self.conn.commit()
def get_undelivered_users(self):
"""Returns list of usernames who have wishes but haven't received them yet."""
self.cursor.execute('''
SELECT DISTINCT p.username
FROM participants p
JOIN wishes w ON p.username = w.recipient
LEFT JOIN deliveries d ON p.username = d.username
WHERE d.username IS NULL
''')
return [row[0] for row in self.cursor.fetchall()]
def get_user_wishes(self, username):
self.cursor.execute("SELECT sender, message FROM wishes WHERE recipient = ?", (username,))
return self.cursor.fetchall()
def mark_delivered(self, username):
self.cursor.execute("INSERT INTO deliveries VALUES (?, ?)", (username, datetime.datetime.now()))
self.conn.commit()
# ==============================================================================
# SAFETY FILTER
# ==============================================================================
class SafetyFilter:
def __init__(self, blocklist):
self.blocklist = blocklist
def is_safe(self, text):
"""
Returns True if text is safe, False if it contains blocked terms.
Performs a simple substring check (case-insensitive).
"""
if not text:
return True
text_lower = text.lower()
for bad_word in self.blocklist:
# Check if the bad word exists in the text
# Note: logic can be improved with regex \b word boundaries if needed
if bad_word in text_lower:
return False
return True
# ==============================================================================
# BOT LOGIC
# ==============================================================================
class ChristmasBot:
def __init__(self):
print("🎄 Initializing Christmas Helper...")
self.reddit = praw.Reddit(
client_id=config.eventBotClientId,
client_secret=config.eventBotSecret,
user_agent=config.user_agent,
username=config.username,
password=config.password
)
self.reddit.validate_on_submit = True
self.db = DatabaseManager()
self.safety = SafetyFilter(config.blocklist)
self.subreddit = self.reddit.subreddit(config.subreddit_name)
self.last_dashboard_update = 0
self.username_pattern = re.compile(r"^u/[A-Za-z0-9_-]{3,20}$", re.IGNORECASE)
self.yes_pattern = re.compile(r"^(yes|y|yeah|yup|ja|si|oui|confirm)$", re.IGNORECASE)
self.no_pattern = re.compile(r"^(no|nop|cancel|nah|not quite|negative|nuh uh)$", re.IGNORECASE)
def get_visual_icon(self, count):
if count == 0:
return "🌱"
elif count == 1:
return "🎄 (1 Gift)"
elif count <= 5:
return "🎄" * count + f" ({count})"
else:
return f"🌟🌟🌟 ({count} Gifts!)"
def verify_user_flair(self, username):
try:
flair_list = list(self.subreddit.flair(redditor=username))
if not flair_list: return False
user_flair = flair_list[0]
if user_flair['flair_text'] or user_flair['flair_css_class']: return True
return False
except Exception as e:
print(f"⚠️ Error checking flair for {username}: {e}")
return False
def update_dashboard(self):
now = time.time()
if now - self.last_dashboard_update < config.update_post_interval: return
print("📝 Updating Forest Dashboard...")
data = self.db.get_forest_data()
body = "# 🎄 The Christmas Forest 🎄\n\n"
body += "### How to Participate:\n"
body += "1. Send a DM to u/{}\n".format(config.username)
body += "2. **First Line:** The username (MUST start with `u/` e.g. `u/SexyElf69`)\n"
body += "3. **Next Lines:** Your message.\n\n"
body += "**Note:** The bot will ask you to confirm before saving.\n"
body += "---\n\n"
body += "| User | Tree Status |\n| :--- | :--- |\n"
if not data: body += "| The Forest is quiet... | Be the first to send a wish! |\n"
for username, count in data:
icon = self.get_visual_icon(count)
body += f"| u/{username} | {icon} |\n"
body += "\n\n---\n*Updated: " + datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + "*"
try:
submission = self.reddit.submission(id=config.master_post_id)
submission.edit(body, )
self.last_dashboard_update = now
print("✅ Dashboard updated.")
except Exception as e:
print(f"❌ Error updating dashboard: {e}")
def parse_new_wish_request(self, message):
body_lines = message.body.strip().split('\n')
body_lines = [line.strip() for line in body_lines if line.strip()]
if not body_lines:
return None, None
first_line = body_lines[0]
if self.username_pattern.match(first_line):
target_user = first_line[2:]
wish_content = '\n'.join(body_lines[1:])
return target_user, wish_content
return None, None
def process_inbox(self):
try:
for message in self.reddit.inbox.unread():
if message.was_comment: continue
sender = message.author.name if message.author else "Unknown"
print(f"📩 Message from {sender}")
conversation = self.db.get_conversation(sender)
if conversation:
self.handle_confirmation(message, sender, conversation)
else:
self.handle_new_request(message, sender)
message.mark_read()
except Exception as e:
print(f"⚠️ Error checking inbox: {e}")
def handle_new_request(self, message, sender):
target_user, wish_content = self.parse_new_wish_request(message)
# ERROR: Bad Format
if not target_user:
reply = ("I couldn't understand your request. Please format your message exactly like this:\n\n"
"`u/TargetUsername`\n\n"
"`Your message here...`\n\n"
"Make sure the username is on the **first line** and starts with **u/**.")
message.reply(reply)
return
# ERROR: Empty Body
if not wish_content:
message.reply(f"You didn't write a message for u/{target_user}! Please try again with text in the body.")
return
# SAFETY CHECK
if not self.safety.is_safe(wish_content):
print(f"⛔ Blocked unsafe content from {sender}")
message.reply("⛔ **Message Rejected**\n\n"
"Your message contains words or phrases that are not allowed in this event. "
"Please ensure your wish is kind and follows the community guidelines.")
return
# ERROR: No Flair
if not self.verify_user_flair(target_user):
message.reply(f"Sorry, u/{target_user} does not have a User Flair in r/{config.subreddit_name}.\n"
"We only allow wishes for active, flaired community members.")
return
# CHECK: Existing Wish
existing_msg = self.db.check_existing_wish(sender, target_user)
if existing_msg:
self.db.set_conversation_state(sender, "CONFIRM_REPLACE", target_user, wish_content)
reply = (f"⚠️ **You already sent a wish to u/{target_user}.**\n\n"
f"**Old Message:** {existing_msg[:100]}...\n\n"
f"**Do you want to REPLACE it with:**\n"
f"> {wish_content}\n\n"
f"Reply **YES** to confirm or **NO** to cancel.")
message.reply(reply)
else:
self.db.set_conversation_state(sender, "CONFIRM_NEW", target_user, wish_content)
reply = (f"🎄 **Christmas Confirmation** 🎄\n\n"
f"I extracted that you want to send a wish to **u/{target_user}**.\n\n"
f"**Message:**\n"
f"> {wish_content}\n\n"
f"Is this correct? Reply **YES** to save or **NO** to cancel.")
message.reply(reply)
def handle_confirmation(self, message, sender, conversation):
state, target, content = conversation
user_response = message.body.strip().lower()
# SAFETY CHECK (Re-check in case DB state was manipulated or rules changed)
if not self.safety.is_safe(content):
self.db.clear_conversation(sender)
message.reply("⛔ **Message Rejected** during confirmation. Content violates safety rules.")
return
if self.yes_pattern.match(user_response):
self.db.save_wish(sender, target, content)
self.db.clear_conversation(sender)
print(f"💾 Wish saved for {target}")
if state == "CONFIRM_REPLACE":
message.reply(f"✅ Your wish for u/{target} has been **updated**! 🎄")
print(f"{sender} has updated their wish for u/{target} to **{content}**.")
else:
message.reply(f"✅ Your wish for u/{target} has been **saved**! 🎄")
print(f"{sender} has saved their wish for u/{target}: **{content}**.")
elif self.no_pattern.match(user_response):
self.db.clear_conversation(sender)
message.reply("❌ Cancelled. You can send a new message to start over.")
print(f"🚫 Action cancelled by {sender}")
else:
message.reply("I didn't catch that. Please reply **YES** to confirm or **NO** to cancel.")
def run(self):
print("🚀 Christmas Bot Started. Press Ctrl+C to stop.")
while True:# 1. Process new wishes
self.process_inbox()
# 2. Update the public post
self.update_dashboard()
# 3. Check if it's Christmas yet (NEW)
self.check_distribution()
time.sleep(config.check_interval)
def check_distribution(self):
"""Checks if it is Dec 24th UTC (Midnight) or later and triggers distribution."""
now_utc = datetime.datetime.now(datetime.timezone.utc)
# Check: Is it December AND is it the 24th or later?
if now_utc.month == 12 and now_utc.day >= 24:
self.distribute_gifts()
def distribute_gifts(self):
recipients = self.db.get_undelivered_users()
# Silent exit if no pending deliveries (prevents log spam)
if not recipients:
return
print(f"🎅 HO HO HO! It's Christmas! Distributing gifts to {len(recipients)} users...")
for username in recipients:
wishes = self.db.get_user_wishes(username)
if not wishes: continue
print(f"🎁 Preparing gifts for u/{username}...")
# --- PAGINATION LOGIC ---
messages_to_send = []
current_part_content = ""
current_part_num = 1
# Header for first message
header = f"Merry Christmas u/{username}!\n\nThe wait is over. Here are your wishes:\n\n---\n\n"
current_part_content += header
for sender, message_text in wishes:
# Format the single wish
wish_block = f"**From: u/{sender}**\n> {message_text}\n\n---\n\n"
# Check character limit (safe buffer of 9000 chars)
if len(current_part_content) + len(wish_block) > 9000:
# Current part full. Close it and start new one.
current_part_content += f"\n*(Continued in Part {current_part_num + 1}...)*"
messages_to_send.append(current_part_content)
current_part_num += 1
current_part_content = f"**Christmas Wishes Part {current_part_num}**\n\n---\n\n"
current_part_content += wish_block
else:
current_part_content += wish_block
# Append the footer to the last part
current_part_content += f"\nYou received {len(wishes)} ornament(s). Have a wonderful holiday!\n"
current_part_content += f"\n*Automated message from r/{config.subreddit_name}*"
messages_to_send.append(current_part_content)
# --- SENDING LOOP ---
try:
for index, body in enumerate(messages_to_send):
subject_line = "🎄 Open your Christmas Time Capsule! 🎁"
if len(messages_to_send) > 1:
subject_line += f" (Part {index + 1}/{len(messages_to_send)})"
self.reddit.redditor(username).message(subject=subject_line, message=body)
# Small sleep between parts to ensure order and avoid spam trigger
time.sleep(2)
self.db.mark_delivered(username)
print(f"✅ Delivered {len(messages_to_send)} part(s) to u/{username}")
time.sleep(5) # Delay between USERS
except Exception as e:
print(f"❌ Failed to send to u/{username}: {e}")
if __name__ == "__main__":
bot = ChristmasBot()
bot.run()

248
StatsHelper.py Normal file
View File

@@ -0,0 +1,248 @@
#!/usr/bin/env python3
import praw
import time
from datetime import datetime, timezone
from collections import Counter, defaultdict
import config
# --- SETTINGS ---
START_YEAR = 2024
START_MONTH = 12 # December
AVAILABLE_FLAIRS=[
"Moderator Announcement 📢",
"Horny😈",
"Appreciation ☺️",
"Meme!😋",
"Lore📚✍",
"Hunter😈",
"Truth or Dare - NNYC Edition - 🥵",
"Truth or Dare - NNYC Edition - 💞",
"Triumph Diary",
"Defeat Diary",
"cursed meme🙃👿",
"Gamble 🎰🎲🃏",
"DDD Challenge",
"Holly Jolly Time 🎄⛄🦌🎁🍪🌟",
"Happy New Year 🎊🥂🎉🍀",
"Easter Egg-Stravaganza 🐰🐇",
"[Game] - Would you rather...⁉️",
":pokeball::pokeball: Gotta catch 'em all :pokeball::pokeball:",
"Will you be my Valentine 💖❤️💌"
]
class StatsHelper:
def __init__(self):
print("🎄 Initializing StatsHelper...")
def get_next_month_start(self, year, month):
"""Helper to calculate the first day of the NEXT month."""
if month == 12:
return year + 1, 1
else:
return year, month + 1
def process_batch(self, batch_name, post_list):
"""
Helper function to process a list of posts and print stats.
"""
print(f"\n🔵 PROCESSING: {batch_name} (Limit: ~1000)")
print("-" * 60)
unknown_flairs = Counter()
matched_count = 0
no_flair_count = 0
total_scanned = 0
for post in post_list:
total_scanned += 1
flair = post.link_flair_text
# Check 1: No Flair
if not flair:
no_flair_count += 1
continue
# Check 2: Known List
if flair in AVAILABLE_FLAIRS:
matched_count += 1
else:
unknown_flairs[flair] += 1
# --- REPORT FOR THIS BATCH ---
print(f"Total Retrieved: {total_scanned}")
print(f"✅ Known Flairs: {matched_count}")
print(f"👻 No Flair: {no_flair_count}")
if unknown_flairs:
print(f"⚠️ UNKNOWN FLAIRS: {sum(unknown_flairs.values())}")
print(f" (Check these for typos/spaces)")
for f, c in unknown_flairs.most_common():
# Use repr() to see hidden spaces, e.g. 'Meme '
print(f" {repr(f):<40} : {c}")
else:
print("🎉 No unknown flairs found.")
print("-" * 60)
def analyze(self):
reddit = praw.Reddit(
client_id=config.eventBotClientId,
client_secret=config.eventBotSecret,
user_agent=config.user_agent,
username=config.megathread_username,
password=config.megathread_password
)
# Trackers
unknown_flairs = Counter()
no_flair_count = 0
matched_count = 0
subreddit = reddit.subreddit(config.subreddit_name)
# # We use a dictionary of Counters:
# # Structure: { "2023-10": Counter({'FlairA': 10, 'FlairB': 2}), "2023-11": ... }
# monthly_stats = defaultdict(Counter)
# # We will also create a counter for the TOTAL stats across all months
# grand_total_stats = Counter()
#
# print(f"Starting precise calendar analysis for r/{config.subreddit_name}...")
#
# total_posts_processed = 0
#
# for available_flair in AVAILABLE_FLAIRS:
# # Prepare the query
# # syntax='cloudsearch' requires the timestamp field
# # include_over_18 ensures we see posts even if sub is flagged mature
# query = f'flair:"{available_flair}"'
# print(f"Checking {available_flair}...")
#
# try:
# posts = list(subreddit.search(
# query,
# limit=None,
# syntax="cloudsearch",
# params={'include_over_18': 'on'}
# ))
#
# overall_count = len(posts)
# print(f"Found {overall_count} posts for {available_flair}...")
#
# count_for_flair = 0
# for post in posts:
# # 2. Convert timestamp to "YYYY-MM" string key
# dt = datetime.fromtimestamp(post.created_utc, timezone.utc)
# month_key = dt.strftime('%Y-%m')
#
# # 3. Add to stats
# # We use 'available_flair' to ensure the naming is consistent
# # (even if the post has slightly messed up encoding)
# monthly_stats[month_key][available_flair] += 1
# # 4. Add to Grand Total stats
# grand_total_stats[available_flair] += 1
# total_posts_processed += 1
# count_for_flair += 1
#
# if count_for_flair == 0:
# print("⚠️ (0 in range)")
# else:
# print(f"✅ ({count_for_flair} found)")
#
# except Exception as e:
# print(f"\n❌ Error: {e}")
#
# # --- OUTPUT RESULTS ---
# print("\n" + "=" * 60)
# print(f"FINAL REPORT: {total_posts_processed} POSTS ANALYZED")
# print("=" * 60)
#
# # Sort keys to ensure chronological printing (2024-12, then 2025-01)
# sorted_months = sorted(monthly_stats.keys())
#
# if not sorted_months:
# print("No posts found in the specified timeframe.")
#
# for month_key in sorted_months:
# print(f"\n📅 PERIOD: {month_key}")
# print(f"{'FLAIR':<40} | {'COUNT':<5} | {'%'}")
# print("-" * 60)
#
# total_in_month = sum(monthly_stats[month_key].values())
#
# # Sort flairs by popularity within that month
# for flair, count in monthly_stats[month_key].most_common():
# percentage = (count / total_in_month) * 100
#
# # Truncate flair name if it's too long for the table
# display_flair = (flair[:37] + '..') if len(flair) > 37 else flair
#
# print(f"{display_flair:<40} | {count:<5} | {percentage:.1f}%")
#
# # --- OUTPUT 2: GRAND TOTAL SUMMARY ---
# print("\n" + "=" * 60)
# print(f"PART 2: GRAND TOTAL SUMMARY (ALL MONTHS COMBINED)")
# print(f"Total Posts: {total_posts_processed}")
# print("=" * 60)
#
# print(f"\n📅 PERIOD: ALL TIME ({sorted_months[0]} to {sorted_months[-1]})")
# print(f"{'FLAIR':<40} | {'COUNT':<5} | {'%'}")
# print("-" * 60)
#
# for flair, count in grand_total_stats.most_common():
# # Percentage relative to the TOTAL posts analyzed across the whole period
# percentage = (count / total_posts_processed) * 100
#
# display_flair = (flair[:37] + '..') if len(flair) > 37 else flair
# print(f"{display_flair:<40} | {count:<5} | {percentage:.1f}%")
#One more search for potentially missed posts
try:
# print("\nFetching .new() ...")
# posts_new = list(subreddit.new(limit=None))
# self.process_batch("SUBREDDIT.NEW", posts_new)
#
# # 2. FETCH HOT (Algorithm sorted, max ~1000)
# print("\nFetching .hot() ...")
# posts_hot = list(subreddit.hot(limit=None))
# self.process_batch("SUBREDDIT.HOT", posts_hot)
#
# # 2. FETCH TOP (Algorithm sorted, max ~1000)
# print("\nFetching .top() ...")
# posts_hot = list(subreddit.top(limit=None))
# self.process_batch("SUBREDDIT.TOP", posts_hot)
#
# # 2. FETCH CONTROVERSIAL (Algorithm sorted, max ~1000)
# print("\nFetching .controversial() ...")
# posts_hot = list(subreddit.controversial(limit=None))
# self.process_batch("SUBREDDIT.CONTROVERSIAL", posts_hot)
#
# # 2. FETCH RISING (Algorithm sorted, max ~1000)
# print("\nFetching .rising() ...")
# posts_hot = list(subreddit.rising(limit=None))
# self.process_batch("SUBREDDIT.RISING", posts_hot)
random_counter=0
print("\nFetching .random() ...")
while random_counter < 10000:
rand_post = subreddit.random()
flair = rand_post.link_flair_text
# Check 1: No Flair
if not flair:
print(f"Found unflaired post => {rand_post.id}")
continue
# Check 2: Known List
if not flair in AVAILABLE_FLAIRS:
print(f"Found unexpected flair => {flair}")
if random_counter % 10 == 0:
time.sleep(2)
except Exception as e:
print(f"\n❌ Error: {e}")

Binary file not shown.

Binary file not shown.

BIN
christmas_event.db Normal file

Binary file not shown.

102
config.py Normal file
View File

@@ -0,0 +1,102 @@
#!/usr/bin/env python3
#The client_id of the EventBot ass set in https://www.reddit.com/prefs/apps
eventBotClientId='helOP0151Te4ytmwHoG6jg'
#The client_secret of the EventBot ass set in https://www.reddit.com/prefs/apps
eventBotSecret='JbBOr8-FbgDc8-S2kD5NE4DdTqMddA'
#User chosen unique identifier for this agent.
user_agent='linux:Event-Bot by u/droid_tect v0.1'
#The user name to post under
username='NNYC-EventBot'
#The password of the user name to post under.
password='q}7Y~6f-xNzLdBt'
#The name of the Subreddit to carry out this event on
#subreddit_name="EventBotTestingGround"
subreddit_name="NoNutYearlyCommunity"
#The ID of the "Master Post" to keep track of the wishes
master_post_id="1p5dtbr"
#Interval in seconds to check for new messages
check_interval=60
#Interval in seconds to update the dashboard if something changed
update_post_interval=300
megathread_username="droid_tect"
megathread_password="lwwh1b-9gK-Ds_o"
# SAFETY NET: Lowercase words/phrases that trigger immediate rejection
# You can expand this list with specific slurs or harassment terms relevant to your community.
blocklist=[
# --- DIRECT THREATS & SELF-HARM ENCOURAGEMENT ---
"kill yourself",
"kys",
"hang yourself",
"drink bleach",
"hope you die",
"go die",
"suicide",
"commit suicide",
"die in a fire",
"cut yourself",
# --- COMMON HARASSMENT & INSULTS ---
# In an 18+ sub, you might tolerate "trashy", but these cross the line
"you are disgusting",
"you look disgusting",
"ugly",
"fat pig",
"whale",
"vomit",
"waste of space",
"waste of oxygen",
"nobody loves you",
"nobody likes you",
"incel",
"femcel",
"neckbeard",
"retard", # Ableist slur
"retarded",
# --- SERIOUS ACCUSATIONS (DRAMA/TROLLING) ---
"pedophile",
"pedo",
"groomer",
"rapist",
"scammer",
"animal abuser",
# --- HATE SPEECH & EXTREMISM ---
# (Please add specific racial/homophobic slurs from your AutoMod here)
"nazi",
"hitler",
"white power",
"white supremacy",
"black supremacy",
# --- SPAM & SCAMS ---
"bitcoin",
"crypto",
"giveaway",
"telegram",
"whatsapp",
"investment",
"forex",
"nft",
"wallet",
"cashapp",
"venmo",
"paypal",
"buy my",
"selling",
"discount",
"promo code",
"click here",
"check out my onlyfans", # Depending on if you allow self-promo in wishes
"subscribe to",
# --- DOXXING ---
"real name is",
"lives at",
"phone number",
"address is",
"ip address",
]

BIN
pictures/1201_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

BIN
pictures/1201_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

0
praw Normal file
View File

0
sqlite3 Normal file
View File

78
stats_config.py Normal file
View File

@@ -0,0 +1,78 @@
#!/usr/bin/env python3
base_title="[Day {day_of_month}] Advent Calendar: {topic_title} 🎄"
base_message="""🎅 **Ho Ho Ho, Community!**
Welcome to **Day {day_of_month}** of our **Subreddit Advent Calendar**! We are counting down to the big holiday with a new prompt every single day.
# 🌟 Today's Theme:
{topic_description}
# 🎁 How to Participate
The rules are simple: **Interpret the theme however you want!**
* **Wholesome?** ✅ (e.g., Share a recipe, a childhood memory, a cozy SFW pic, or a meme.)
* **Spicy (+18)?** ✅ (e.g., NSFW stories, artistic photos, or "creative" interpretations. **Please ensure all NSFW content is tagged appropriately!**)
* **Off-the-wall?** ✅ (Surprise us!)
Post your submissions in the comments below or to the subreddit! Let's fill this thread and the subreddit with holiday spirit.
*(Reminder: Keep it respectful. All standard subreddit rules regarding consent and harassment apply.)*
"""
topic_titles=[
"Deck the Halls",
"Baby It's Cold Outside",
"Sugar & Spice",
"Code Red",
"Naughty or Nice?",
"Stocking Stuffers",
"Under Wraps",
"Jingle Bell Rock",
"Lights Out",
"Santas Helpers",
"Comfort & Joy",
"Mid-Month Madness",
"Winter Whiteout",
"Toy Story",
"Sweater Weather",
"Silent Night",
"All That Glitters",
"Unboxing",
"Spirits & Spirits",
"Mistletoe",
"Jack Frost",
"The Night Before",
"Feast Mode",
"The Grand Reveal"
]
topic_descriptions=[
"Were kicking things off with decorations! Show us your Christmas tree setup, your festive room decor, or perhaps... how youve chosen to \"decorate\" yourself for the season. Tinsel, ornaments, and lights are encouraged—wherever you choose to hang them.",
"The temperature is dropping, so show us how you stay warm. Are you curling up in fluffy socks and a blanket? Sipping hot cocoa by the fire? Or have you found a warmer way to generate some body heat? Let's get cozy.",
"Today is all about the sweetness of the season. Share your favorite holiday cookie recipes, photos of your baking disasters, or show us that you are the sweetest treat in the house. Are you made of sugar, spice, or something nicer?",
"Red is the color of the season. We want to see it! Red lipstick, red wine, red Santa hats, or perhaps some stunning red lingerie. If its rouge, crimson, or scarlet, it belongs in this thread.",
"Its time for a confession. Have you been an angel this year, volunteering and helping others? Or have you been remarkably wicked? Tell us a story of a good deed... or a very naughty one. Santa is listening.",
"Happy St. Nicholas Day! Today we celebrate the \"Stocking.¸\" Whether that means discussing small, thoughtful gifts, showing off your favorite festive socks, or showcasing incredible legs in nylon stockings—fill the thread up!",
"Wrapping gifts is an art form. Show us your perfectly wrapped presents (or your messy tape disasters). Alternatively, show us yourself all wrapped up—in ribbons, in a duvet, or waiting to be unwrapped by someone special.",
"Lets get loud! Post your favorite Christmas party anthems, a video of your terrible dancing, or a picture of you ready for a night out. How do you rock around the Christmas tree?",
"When the sun goes down, the mood changes. We want to see glow-in-the-dark themes, fairy lights in a dark room, artistic silhouettes, or stories about what happens after the lights go out.",
"Santa cant do it all alone. Today celebrates the Elves! Show us your best Elf on the Shelf memes, your own Elf costume, or tell us a story about how you \"serviced\" the community or a partner recently.",
"Mid-week relaxation is vital. What brings you pure joy? Is it a bubble bath, a glass of whiskey, a specific video game, or a massage? Share your self-care routines (SFW or NSFW) that keep the stress away.",
"We are halfway there! Lets blow off some steam. Post your funniest holiday memes, bloopers, or vent about the chaos of holiday shopping. Lets laugh at the madness together.",
"Dreaming of a White Christmas? We want to see snow! Real snow landscapes, white outfits, foam, bubbles, or... other white substances. Lets turn this thread into a blizzard.",
"Christmas is for toys! Gamers, show us your rigs. Collectors, show us your figures. And for our 18+ crowd... feel free to showcase your favorite toys that don't belong in a child's stocking. Reviews and recommendations welcome!",
"The Ugly Christmas Sweater: a fashion icon. Post your most hideous knitwear. Or, if youre feeling warm, show us what it looks like when the sweater comes off.",
"Sometimes the best moments are the quiet ones. Share a story of a solo evening, a peaceful view, or the intimate, silent moments shared between two people. Shhh...",
"Sparkle season is here. We want glitter, sequins, shiny jewelry, and glossy lips. If it reflects light and catches the eye, post it here. Lets make this thread shine.",
"The thrill of opening something new. Post a haul of gifts youve bought, an unboxing video of a new gadget, or a \"reveal\" photo set where you unbox... yourself.",
"Cheers! Share your favorite mulled wine recipe, a photo of your holiday cocktail, or a funny story about a time you had a few too many spirits. Drink responsibly, post recklessly (within the rules)!",
"Pucker up! Today is about romance and kissing. Share a wholesome couple pic, a story about a holiday crush, or something a bit more passionate happening under the greenery.",
"Its nippy out there. Were exploring the sensation of cold. Ice cubes, cold showers, shivering in the snow, or how you warm up strictly after getting caught in the freeze.",
"The anticipation is palpable. Are you excited? Nervous? Show us your prep work. Food prep, outfit prep, or preparing the bedroom for a guest. What are you doing right now to get ready?",
"The big meal is coming. Post your food porn! Roasts, veggies, desserts... or describe a time you were the main course. Lets get hungry.",
"MERRY CHRISTMAS!\nToday is the day! The Bot is currently distributing all your secret wishes via DM. Use this thread to react to your messages, thank your anonymous well-wishers, and celebrate the finale of our event!"
]

0
time Normal file
View File