diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/ValentinesEvent.py b/ValentinesEvent.py new file mode 100644 index 0000000..65320ad --- /dev/null +++ b/ValentinesEvent.py @@ -0,0 +1,493 @@ +#!/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="valentines_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 + ) + ''') + + self.cursor.execute(''' + CREATE TABLE IF NOT EXISTS commented_users ( + username TEXT PRIMARY KEY + ) + ''') + + # 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 add_commented_user(self, username): + try: + self.cursor.execute("INSERT INTO commented_users VALUES (?)", + (username, )) + self.conn.commit() + return True + except sqlite3.IntegrityError: + return False + + def get_commented_users(self, username): + try: + self.cursor.execute("SELECT * FROM commented_users WHERE username=?", (username,)) + return self.cursor.fetchone() + except sqlite3.IntegrityError: + return None + + 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 ValentinesBot: + + # Store the last updated overall count of wishes. + last_updated_count = 0 + + def __init__(self): + print("💕 Initializing Valentine's 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 Heart)" + elif count <= 5: + return "💕" * count + f" ({count} Hearts)" + else: + return f"💖💖💖 ({count} Hearts!)" + + 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 + + data = self.db.get_forest_data() + overall_count = 0 + if data: + for username, count in data: + overall_count += count + + if overall_count == self.last_updated_count: + # Nothing changed. + print(f"No change in dashboard, last checked {datetime.datetime.fromtimestamp(now).strftime('%Y-%m-%d %H:%M:%S')}...", end="\r") + return + + print("📝 Updating Valentine's Garden Dashboard...") + self.last_updated_count = overall_count + body = "# 💝 The Valentine's Garden 💝\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/AmoreMio666`)\n" + body += "3. **Next Lines:** Your message.\n\n" + body += "**Note:** The bot will ask you to confirm before saving.\n\n" + body += "See the announcement post [here](https://www.reddit.com/r/NoNutYearlyCommunity/comments/1pkmvwh/introducing_the_nnyc_christmas_wishbot/)\n" + body += "---\n\n" + body += "| User | Garden Status |\n| :--- | :--- |\n" + + if not data: body += "| The Garden is quiet... | Be the first to send a message! |\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.valentines_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:].lower() + 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) + + if not self.verify_user_flair(sender): + message.reply(f"Sorry, u/{sender} but you do not have an active Flair in r/{config.subreddit_name}.\n" + "We only allow messages for active, flaired community members.") + return + + # 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.lower()}! 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 message is kind and follows the community guidelines.") + return + + # ERROR: No Flair + if not self.verify_user_flair(target_user.lower()): + message.reply(f"Sorry, u/{target_user.lower()} does not have a User Flair in r/{config.subreddit_name}.\n" + "We only allow messages for active, flaired community members.") + return + + # CHECK: Existing Wish + existing_msg = self.db.check_existing_wish(sender, target_user.lower()) + + if existing_msg: + self.db.set_conversation_state(sender, "CONFIRM_REPLACE", target_user.lower(), wish_content) + reply = (f"⚠️ **You already sent a message 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.lower(), wish_content) + reply = (f"💌 **Valentine's Confirmation** 💌\n\n" + f"I extracted that you want to send a message to **u/{target_user.lower()}**.\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.lower(), content) + self.db.clear_conversation(sender) + + print(f"💾 Message saved for {target.lower()}") + if self.db.get_commented_users(target.lower()): + print("User already existed before, no need to update") + else: + print("New user, add comment") + self.db.add_commented_user(target.lower()) + submission = self.reddit.submission(id=config.valentines_post_id) + submission.reply(config.comment_base_text.format(username=target.lower())) + + if state == "CONFIRM_REPLACE": + message.reply(f"✅ Your message for u/{target.lower()} has been **updated**! 💕") + print(f"{sender} has updated their message for u/{target.lower()} to **{content}**.") + else: + message.reply(f"✅ Your message for u/{target.lower()} has been **saved**! 💕") + print(f"{sender} has saved their message for u/{target.lower()}: **{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("🚀 Valentine's 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 Valentine's Day yet + self.check_distribution() + + time.sleep(config.check_interval) + + def check_distribution(self): + """Checks if it is Feb 14th UTC (Midnight) or later and triggers distribution.""" + now_utc = datetime.datetime.now(datetime.timezone.utc) + + # Check: Is it February AND is it the 14th or later? + if now_utc.month == 2 and now_utc.day >= 14: + 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"💝 Happy Valentine's Day! Distributing messages to {len(recipients)} users...") + + for username in recipients: + wishes = self.db.get_user_wishes(username.lower()) + if not wishes: continue + + print(f"💌 Preparing messages for u/{username.lower()}...") + + # --- PAGINATION LOGIC --- + messages_to_send = [] + current_part_content = "" + current_part_num = 1 + + # Header for first message + header = f"Happy Valentine's Day u/{username.lower()}!\n\nThe wait is over. Here are your messages:\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"**Valentine's Messages 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)} heart(s). Have a wonderful Valentine's Day!\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 Valentine's 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.lower()) + print(f"✅ Delivered {len(messages_to_send)} part(s) to u/{username.lower()}") + time.sleep(5) # Delay between USERS + + except Exception as e: + print(f"❌ Failed to send to u/{username.lower()}: {e}") + + +if __name__ == "__main__": + bot = ValentinesBot() + bot.run() diff --git a/config.py b/config.py index 3e179e4..80036a7 100644 --- a/config.py +++ b/config.py @@ -15,6 +15,8 @@ password='q}7Y~6f-xNzLdBt' subreddit_name="NoNutYearlyCommunity" #The ID of the "Master Post" to keep track of the wishes master_post_id="1pkmvbo" +#The ID of the Valentines Master Post to keep track of messages +valentines_post_id="1pkmvwh" #Interval in seconds to check for new messages check_interval=60 #Interval in seconds to update the dashboard if something changed diff --git a/nnwc_wrapped_data_2025.json b/nnwc_wrapped_data_2025.json new file mode 100644 index 0000000..10dbea2 --- /dev/null +++ b/nnwc_wrapped_data_2025.json @@ -0,0 +1,806 @@ +{ + "droid_tect": { + "total_annual_karma": 6075, + "total_posts": 1073, + "total_comments": 1221, + "top_flairs": [ + { + "flair": "Gamble \ud83c\udfb0\ud83c\udfb2\ud83c\udccf", + "percentage": "51.7%" + }, + { + "flair": "Holly Jolly Time \ud83c\udf84\u26c4\ud83e\udd8c\ud83c\udf81\ud83c\udf6a\ud83c\udf1f", + "percentage": "27.6%" + }, + { + "flair": "Moderator Announcement \ud83d\udce2", + "percentage": "10.3%" + } + ], + "top_posts": [ + { + "id": "1or2hbn", + "title": "This ain't a goodbye, rather a see you later... Probably", + "score": 14, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1or2hbn" + }, + { + "id": "1or2hbn", + "title": "This ain't a goodbye, rather a see you later... Probably", + "score": 14, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1or2hbn" + }, + { + "id": "1or2hbn", + "title": "This ain't a goodbye, rather a see you later... Probably", + "score": 14, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1or2hbn" + } + ], + "top_comments": [ + { + "id": "nswv4at", + "text_preview": "Hmmm\\~ \nNot sure yet, but I would think that till...", + "score": 2, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pgx97h/_/nswv4at" + }, + { + "id": "nswueqa", + "text_preview": "Thank you darling~ \u2764\ufe0f\ud83d\udda4\u2764\ufe0f\ud83d\udda4", + "score": 2, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pgx97h/_/nswueqa" + }, + { + "id": "nsqago3", + "text_preview": "Oh my, i love surprises \ud83d\ude0d\ud83d\ude0d\ud83d\ude0d\ud83d\ude0d\ud83d\ude0d", + "score": 2, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pg2n4g/_/nsqago3" + } + ], + "most_active_time": "Sunday @ 7 UTC", + "top_interactors": [ + { + "username": "shad2shad2", + "count": 111 + }, + { + "username": "AltForHornyShit999", + "count": 74 + }, + { + "username": "Nixtius", + "count": 74 + } + ] + }, + "Trying_out_new_stuff": { + "total_annual_karma": 8696, + "total_posts": 1258, + "total_comments": 481, + "top_flairs": [ + { + "flair": "Appreciation \u263a\ufe0f", + "percentage": "64.7%" + }, + { + "flair": "Holly Jolly Time \ud83c\udf84\u26c4\ud83e\udd8c\ud83c\udf81\ud83c\udf6a\ud83c\udf1f", + "percentage": "23.5%" + }, + { + "flair": "\ud83c\udf30 Year 1 in a Nutshell! \ud83e\udd5c", + "percentage": "11.8%" + } + ], + "top_posts": [ + { + "id": "1ou19e7", + "title": "Let's enjoy some winter season cookies together!", + "score": 13, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1ou19e7" + }, + { + "id": "1opqtc7", + "title": "How was your week so far? Feeling motivated still?", + "score": 13, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1opqtc7" + }, + { + "id": "1pds4cc", + "title": "No matter the time of year...", + "score": 13, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pds4cc" + } + ], + "top_comments": [ + { + "id": "nsmbnxu", + "text_preview": "True!", + "score": 2, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pfiv4h/_/nsmbnxu" + }, + { + "id": "nska8ue", + "text_preview": "Hugs please", + "score": 2, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pfiv4h/_/nska8ue" + }, + { + "id": "nsmbnxu", + "text_preview": "True!", + "score": 2, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pfiv4h/_/nsmbnxu" + } + ], + "most_active_time": "Saturday @ 5 UTC", + "top_interactors": [ + { + "username": "shad2shad2", + "count": 74 + }, + { + "username": "Splicer3", + "count": 37 + }, + { + "username": "ai_chat_dude", + "count": 37 + } + ] + }, + "ai_chat_dude": { + "total_annual_karma": 1771, + "total_posts": 222, + "total_comments": 592, + "top_flairs": [ + { + "flair": "Holly Jolly Time \ud83c\udf84\u26c4\ud83e\udd8c\ud83c\udf81\ud83c\udf6a\ud83c\udf1f", + "percentage": "66.7%" + }, + { + "flair": "Meme!\ud83d\ude0b", + "percentage": "16.7%" + }, + { + "flair": "Horny\ud83d\ude08", + "percentage": "16.7%" + } + ], + "top_posts": [ + { + "id": "1pe0a9j", + "title": "Code Red you say? I see only jingling bells... \ud83d\udd14\ud83d\udd14", + "score": 19, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pe0a9j" + }, + { + "id": "1pe0a9j", + "title": "Code Red you say? I see only jingling bells... \ud83d\udd14\ud83d\udd14", + "score": 19, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pe0a9j" + }, + { + "id": "1pe0a9j", + "title": "Code Red you say? I see only jingling bells... \ud83d\udd14\ud83d\udd14", + "score": 19, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pe0a9j" + } + ], + "top_comments": [ + { + "id": "nslelr0", + "text_preview": "You can still put your boots out even if the stock...", + "score": 2, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pfiv4h/_/nslelr0" + }, + { + "id": "nslelr0", + "text_preview": "You can still put your boots out even if the stock...", + "score": 2, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pfiv4h/_/nslelr0" + }, + { + "id": "nslelr0", + "text_preview": "You can still put your boots out even if the stock...", + "score": 2, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pfiv4h/_/nslelr0" + } + ], + "most_active_time": "Sunday @ 7 UTC", + "top_interactors": [ + { + "username": "Loyal_Diver", + "count": 37 + }, + { + "username": "droid_tect", + "count": 37 + } + ] + }, + "WallLordBBC1": { + "total_annual_karma": 1551, + "total_posts": 555, + "total_comments": 0, + "top_flairs": [ + { + "flair": "Meme!\ud83d\ude0b", + "percentage": "53.3%" + }, + { + "flair": "Holly Jolly Time \ud83c\udf84\u26c4\ud83e\udd8c\ud83c\udf81\ud83c\udf6a\ud83c\udf1f", + "percentage": "13.3%" + }, + { + "flair": "Defeat Diary", + "percentage": "6.7%" + } + ], + "top_posts": [ + { + "id": "1p4ezbn", + "title": "Special Appreciation post for a certain mommy", + "score": 7, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1p4ezbn" + }, + { + "id": "1p4ezbn", + "title": "Special Appreciation post for a certain mommy", + "score": 7, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1p4ezbn" + }, + { + "id": "1p4ezbn", + "title": "Special Appreciation post for a certain mommy", + "score": 7, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1p4ezbn" + } + ], + "top_comments": [], + "most_active_time": "Thursday @ 4 UTC", + "top_interactors": [] + }, + "Extension-Rain-2314": { + "total_annual_karma": 701, + "total_posts": 111, + "total_comments": 0, + "top_flairs": [ + { + "flair": "Holly Jolly Time \ud83c\udf84\u26c4\ud83e\udd8c\ud83c\udf81\ud83c\udf6a\ud83c\udf1f", + "percentage": "33.3%" + }, + { + "flair": "\ud83c\udf30 Year 1 in a Nutshell! \ud83e\udd5c", + "percentage": "33.3%" + }, + { + "flair": "Moderator Announcement \ud83d\udce2", + "percentage": "33.3%" + } + ], + "top_posts": [ + { + "id": "1or3fbu", + "title": "This isn\u2019t a bye but it\u2019s more of a I don\u2019t know.", + "score": 15, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1or3fbu" + }, + { + "id": "1or3fbu", + "title": "This isn\u2019t a bye but it\u2019s more of a I don\u2019t know.", + "score": 15, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1or3fbu" + }, + { + "id": "1or3fbu", + "title": "This isn\u2019t a bye but it\u2019s more of a I don\u2019t know.", + "score": 15, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1or3fbu" + } + ], + "top_comments": [], + "most_active_time": "Thursday @ 15 UTC", + "top_interactors": [] + }, + "shad2shad2": { + "total_annual_karma": 2373, + "total_posts": 185, + "total_comments": 814, + "top_flairs": [ + { + "flair": "Horny\ud83d\ude08", + "percentage": "60.0%" + }, + { + "flair": "Meme!\ud83d\ude0b", + "percentage": "20.0%" + }, + { + "flair": "\ud83c\udf30 Year 1 in a Nutshell! \ud83e\udd5c", + "percentage": "20.0%" + } + ], + "top_posts": [ + { + "id": "1p5jzq9", + "title": "The debt collector is here for Des again \ud83e\udd2d (read the instructions)", + "score": 9, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1p5jzq9" + }, + { + "id": "1p5jzq9", + "title": "The debt collector is here for Des again \ud83e\udd2d (read the instructions)", + "score": 9, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1p5jzq9" + }, + { + "id": "1p5jzq9", + "title": "The debt collector is here for Des again \ud83e\udd2d (read the instructions)", + "score": 9, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1p5jzq9" + } + ], + "top_comments": [ + { + "id": "nsqd0cz", + "text_preview": "oh.... Maiden is the bestest gift \ud83d\ude0d\ud83e\udd70\ud83e\udd70\ud83e\udd70", + "score": 3, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pgcdo7/_/nsqd0cz" + }, + { + "id": "nsqay65", + "text_preview": "umm Maiden.... you should wrap the gifts, not your...", + "score": 3, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pgcdo7/_/nsqay65" + }, + { + "id": "nsqd0cz", + "text_preview": "oh.... Maiden is the bestest gift \ud83d\ude0d\ud83e\udd70\ud83e\udd70\ud83e\udd70", + "score": 3, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pgcdo7/_/nsqd0cz" + } + ], + "most_active_time": "Sunday @ 8 UTC", + "top_interactors": [] + }, + "pensiveColliders": { + "total_annual_karma": 111, + "total_posts": 37, + "total_comments": 0, + "top_flairs": [ + { + "flair": "Lore\ud83d\udcda\u270d\ufe0f", + "percentage": "100.0%" + } + ], + "top_posts": [ + { + "id": "1pdth3f", + "title": "2025 No Nut November Highlights", + "score": 3, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pdth3f" + }, + { + "id": "1pdth3f", + "title": "2025 No Nut November Highlights", + "score": 3, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pdth3f" + }, + { + "id": "1pdth3f", + "title": "2025 No Nut November Highlights", + "score": 3, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pdth3f" + } + ], + "top_comments": [], + "most_active_time": "Thursday @ 7 UTC", + "top_interactors": [] + }, + "the_redpandaguy": { + "total_annual_karma": 111, + "total_posts": 37, + "total_comments": 0, + "top_flairs": [ + { + "flair": "Appreciation \u263a\ufe0f", + "percentage": "100.0%" + } + ], + "top_posts": [ + { + "id": "1pd2c0a", + "title": "To late for the other sub so I'm doing it here \ud83e\udee0\ud83d\ude05", + "score": 3, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pd2c0a" + }, + { + "id": "1pd2c0a", + "title": "To late for the other sub so I'm doing it here \ud83e\udee0\ud83d\ude05", + "score": 3, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pd2c0a" + }, + { + "id": "1pd2c0a", + "title": "To late for the other sub so I'm doing it here \ud83e\udee0\ud83d\ude05", + "score": 3, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pd2c0a" + } + ], + "top_comments": [], + "most_active_time": "Wednesday @ 12 UTC", + "top_interactors": [] + }, + "formallyknwnaswraith": { + "total_annual_karma": 111, + "total_posts": 37, + "total_comments": 0, + "top_flairs": [ + { + "flair": "Holly Jolly Time \ud83c\udf84\u26c4\ud83e\udd8c\ud83c\udf81\ud83c\udf6a\ud83c\udf1f", + "percentage": "100.0%" + } + ], + "top_posts": [ + { + "id": "1pc9lbp", + "title": "Its Christmas season and Lust says hello \u2661", + "score": 3, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pc9lbp" + }, + { + "id": "1pc9lbp", + "title": "Its Christmas season and Lust says hello \u2661", + "score": 3, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pc9lbp" + }, + { + "id": "1pc9lbp", + "title": "Its Christmas season and Lust says hello \u2661", + "score": 3, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pc9lbp" + } + ], + "top_comments": [], + "most_active_time": "Tuesday @ 14 UTC", + "top_interactors": [] + }, + "Twinklesoles91": { + "total_annual_karma": 2571, + "total_posts": 74, + "total_comments": 0, + "top_flairs": [ + { + "flair": "Hunter\ud83d\ude08", + "percentage": "100.0%" + } + ], + "top_posts": [ + { + "id": "1opvhjf", + "title": "How's your week been boys", + "score": 54, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1opvhjf" + }, + { + "id": "1opvhjf", + "title": "How's your week been boys", + "score": 54, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1opvhjf" + }, + { + "id": "1opvhjf", + "title": "How's your week been boys", + "score": 54, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1opvhjf" + } + ], + "top_comments": [], + "most_active_time": "Tuesday @ 10 UTC", + "top_interactors": [] + }, + "desperatesoldier420": { + "total_annual_karma": 111, + "total_posts": 37, + "total_comments": 0, + "top_flairs": [ + { + "flair": "Holly Jolly Time \ud83c\udf84\u26c4\ud83e\udd8c\ud83c\udf81\ud83c\udf6a\ud83c\udf1f", + "percentage": "100.0%" + } + ], + "top_posts": [ + { + "id": "1pbywyr", + "title": "A Decemberween Mackerel", + "score": 3, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pbywyr" + }, + { + "id": "1pbywyr", + "title": "A Decemberween Mackerel", + "score": 3, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pbywyr" + }, + { + "id": "1pbywyr", + "title": "A Decemberween Mackerel", + "score": 3, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pbywyr" + } + ], + "top_comments": [], + "most_active_time": "Tuesday @ 4 UTC", + "top_interactors": [] + }, + "[deleted]": { + "total_annual_karma": 74, + "total_posts": 37, + "total_comments": 0, + "top_flairs": [ + { + "flair": "Meme!\ud83d\ude0b", + "percentage": "100.0%" + } + ], + "top_posts": [ + { + "id": "1owmy6k", + "title": "Rolling into My 20 Day Streak Like", + "score": 2, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1owmy6k" + }, + { + "id": "1owmy6k", + "title": "Rolling into My 20 Day Streak Like", + "score": 2, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1owmy6k" + }, + { + "id": "1owmy6k", + "title": "Rolling into My 20 Day Streak Like", + "score": 2, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1owmy6k" + } + ], + "top_comments": [], + "most_active_time": "Friday @ 4 UTC", + "top_interactors": [] + }, + "StillWrap9384": { + "total_annual_karma": 404, + "total_posts": 37, + "total_comments": 0, + "top_flairs": [ + { + "flair": "Appreciation \u263a\ufe0f", + "percentage": "100.0%" + } + ], + "top_posts": [ + { + "id": "1or0x7d", + "title": "ITS MY BIRTHDAY EVERYONE \u2727\u2060\u25dd\u2060(\u2060\u2070\u2060\u25bf\u2060\u2070\u2060)\u2060\u25dc\u2060\u2727", + "score": 12, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1or0x7d" + }, + { + "id": "1or0x7d", + "title": "ITS MY BIRTHDAY EVERYONE \u2727\u2060\u25dd\u2060(\u2060\u2070\u2060\u25bf\u2060\u2070\u2060)\u2060\u25dc\u2060\u2727", + "score": 12, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1or0x7d" + }, + { + "id": "1or0x7d", + "title": "ITS MY BIRTHDAY EVERYONE \u2727\u2060\u25dd\u2060(\u2060\u2070\u2060\u25bf\u2060\u2070\u2060)\u2060\u25dc\u2060\u2727", + "score": 12, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1or0x7d" + } + ], + "top_comments": [], + "most_active_time": "Friday @ 17 UTC", + "top_interactors": [] + }, + "AltForHornyShit999": { + "total_annual_karma": 148, + "total_posts": 0, + "total_comments": 148, + "top_flairs": [], + "top_posts": [], + "top_comments": [ + { + "id": "nswvem7", + "text_preview": "Because um... y-youre too pretty ;-; some of us ca...", + "score": 1, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pgx97h/_/nswvem7" + }, + { + "id": "nswun8u", + "text_preview": "Y-youre not really going to dress up like this all...", + "score": 1, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pgx97h/_/nswun8u" + }, + { + "id": "nswsh3n", + "text_preview": "Omg Droid ;-; youre so pretty", + "score": 1, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pgx97h/_/nswsh3n" + } + ], + "most_active_time": "Monday @ 9 UTC", + "top_interactors": [] + }, + "Nixtius": { + "total_annual_karma": 111, + "total_posts": 0, + "total_comments": 111, + "top_flairs": [], + "top_posts": [], + "top_comments": [ + { + "id": "nsv4dwp", + "text_preview": ":o", + "score": 1, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pg2n4g/_/nsv4dwp" + }, + { + "id": "nsv49o1", + "text_preview": ":o", + "score": 1, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pgx97h/_/nsv49o1" + }, + { + "id": "nskba4b", + "text_preview": "I\u2019ll try \ud83e\udee3", + "score": 1, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pf9n4j/_/nskba4b" + } + ], + "most_active_time": "Monday @ 1 UTC", + "top_interactors": [] + }, + "Loyal_Diver": { + "total_annual_karma": 148, + "total_posts": 0, + "total_comments": 111, + "top_flairs": [], + "top_posts": [], + "top_comments": [ + { + "id": "nsmc8yi", + "text_preview": "Thanks for the lesson, I didn't know this \ud83d\ude0a", + "score": 2, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pfq5z0/_/nsmc8yi" + }, + { + "id": "nsmc8yi", + "text_preview": "Thanks for the lesson, I didn't know this \ud83d\ude0a", + "score": 2, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pfq5z0/_/nsmc8yi" + }, + { + "id": "nsmc8yi", + "text_preview": "Thanks for the lesson, I didn't know this \ud83d\ude0a", + "score": 2, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pfq5z0/_/nsmc8yi" + } + ], + "most_active_time": "Sunday @ 21 UTC", + "top_interactors": [] + }, + "Splicer3": { + "total_annual_karma": 74, + "total_posts": 0, + "total_comments": 37, + "top_flairs": [], + "top_posts": [], + "top_comments": [ + { + "id": "nsrzsz0", + "text_preview": "*unwraps the gift*\n\nIts you! Yay friendship!", + "score": 2, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pgcdo7/_/nsrzsz0" + }, + { + "id": "nsrzsz0", + "text_preview": "*unwraps the gift*\n\nIts you! Yay friendship!", + "score": 2, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pgcdo7/_/nsrzsz0" + }, + { + "id": "nsrzsz0", + "text_preview": "*unwraps the gift*\n\nIts you! Yay friendship!", + "score": 2, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pgcdo7/_/nsrzsz0" + } + ], + "most_active_time": "Sunday @ 15 UTC", + "top_interactors": [] + }, + "NoZookeepergame2958": { + "total_annual_karma": 37, + "total_posts": 0, + "total_comments": 37, + "top_flairs": [], + "top_posts": [], + "top_comments": [ + { + "id": "nsl14kk", + "text_preview": "There... Might be some truth in that...\n\n*Takes an...", + "score": 1, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pbrx96/_/nsl14kk" + }, + { + "id": "nsl14kk", + "text_preview": "There... Might be some truth in that...\n\n*Takes an...", + "score": 1, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pbrx96/_/nsl14kk" + }, + { + "id": "nsl14kk", + "text_preview": "There... Might be some truth in that...\n\n*Takes an...", + "score": 1, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pbrx96/_/nsl14kk" + } + ], + "most_active_time": "Saturday @ 12 UTC", + "top_interactors": [] + }, + "Wonderful-Title6868": { + "total_annual_karma": 74, + "total_posts": 0, + "total_comments": 74, + "top_flairs": [], + "top_posts": [], + "top_comments": [ + { + "id": "nskxdqk", + "text_preview": "\u2764\ufe0f", + "score": 1, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pf9n4j/_/nskxdqk" + }, + { + "id": "nskvw99", + "text_preview": "No no don\u2019t be sowy \ud83e\udec2 it was a compliment~\u263a\ufe0f", + "score": 1, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pf9n4j/_/nskvw99" + }, + { + "id": "nskxdqk", + "text_preview": "\u2764\ufe0f", + "score": 1, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pf9n4j/_/nskxdqk" + } + ], + "most_active_time": "Saturday @ 11 UTC", + "top_interactors": [] + }, + "NerveCapable1513": { + "total_annual_karma": 148, + "total_posts": 0, + "total_comments": 74, + "top_flairs": [], + "top_posts": [], + "top_comments": [ + { + "id": "nskftqk", + "text_preview": "It\u2019s a little chilly in my house\ud83d\ude23but I\u2019m glad you ...", + "score": 2, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pfiv4h/_/nskftqk" + }, + { + "id": "nskfko2", + "text_preview": "Maiden looks a little cold\u2026 maybe she should put o...", + "score": 2, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pfiv4h/_/nskfko2" + }, + { + "id": "nskftqk", + "text_preview": "It\u2019s a little chilly in my house\ud83d\ude23but I\u2019m glad you ...", + "score": 2, + "permalink": "https://reddit.com/r/NoNutYearlyCommunity/comments/1pfiv4h/_/nskftqk" + } + ], + "most_active_time": "Saturday @ 8 UTC", + "top_interactors": [] + } +} \ No newline at end of file diff --git a/valentines_announcement.md b/valentines_announcement.md new file mode 100644 index 0000000..0f83b15 --- /dev/null +++ b/valentines_announcement.md @@ -0,0 +1,86 @@ +# 💝 Introducing the NNYC Valentine's Wish Bot! 💌 + +Hey NNYC Community! 💕 + +Remember our magical Christmas Wish Bot that brought so much joy last month? Well, **Cupid's got an upgrade**, and we're bringing back the love for Valentine's Day! + +## 🌹 What's This All About? + +Just like our Christmas time capsule, this is your chance to **send heartfelt messages** to your fellow community members that will be delivered **all at once on Valentine's Day (February 14th)**! + +Whether it's appreciation, friendship, encouragement, or just spreading some love – this is your moment to make someone's day special! ❤️ + +## 📬 How to Participate (Super Easy!) + +1. **Send a DM** to u/NNYC-EventBot +2. **First line:** The recipient's username starting with `u/` (example: `u/AmoreMio666`) +3. **Next lines:** Your heartfelt message! + +**Example:** +``` +u/AmoreMio666 + +You've been an amazing support in this community! +Your positivity brightens everyone's day. +Happy Valentine's Day! 💕 +``` + +The bot will ask you to confirm before saving – no accidents, no worries! 😊 + +## 🌱 Watch Your Garden Grow! + +Just like the Christmas Forest, we now have **The Valentine's Garden**! 🌺 + +Check the pinned dashboard post to see who's receiving messages. The more hearts you have, the more love is coming your way on Valentine's Day! Each message = 💕 in your garden! + +## ⏰ Important Dates + +- **NOW - February 13th:** Send your Valentine's messages! +- **February 14th:** All messages will be delivered at once! 💌 + +You have **4 DAYS** to spread the love before the big reveal! + +## 🎭 The Rules (Keep It Sweet!) + +- ✅ **Be kind and genuine** – This is about spreading love and positivity +- ✅ **You can send to multiple people** – Share the love around! +- ✅ **You can update your message** – Changed your mind? Just send a new one to the same person +- ❌ **No spam or inappropriate content** – The bot has safety filters +- ❌ **Recipients must have user flair** – Active community members only + +## 💭 Why Should I Participate? + +Because imagine waking up on Valentine's Day to discover that people in this community took the time to send YOU heartfelt messages! 🥰 + +Last time with Christmas, we saw incredible messages of support, friendship, and community spirit. Let's make this Valentine's Day even more special! + +## 🤔 FAQ + +**Q: Can I send to the same person I sent a Christmas wish to?** +A: Absolutely! Spread that love! 💖 + +**Q: What if I make a typo?** +A: Just send another message to the same person – it will replace the old one! + +**Q: Will people know who sent them messages before Valentine's Day?** +A: Nope! It's a surprise until February 14th! 🎁 + +**Q: Can I send anonymous messages?** +A: No, all messages will show who they're from. Keep it genuine and kind! + +**Q: What if someone doesn't have flair?** +A: The bot will let you know! Only active, flaired members can participate. + +--- + +## 💝 Let's Make This Valentine's Day Unforgettable! + +The Christmas event showed us how powerful a few kind words can be. Let's do it again! + +**Start sending your Valentine's messages NOW!** The garden is waiting to bloom! 🌸 + +*Questions? Drop them in the comments below!* + +--- + +^(Bot created by u/droid_tect | This is an automated event for r/NoNutYearlyCommunity)