#!/bin/bash
# shark-draft-reminder.sh — Send 24h and 1h draft reminder emails to league members
# Runs every 15 minutes via cron
set -euo pipefail
exec python3 - "$@" << 'PYTHON_SCRIPT'
#!/usr/bin/env python3
"""
Shark Draft Reminder — sends 24h and 1h email reminders to league members
before their draft starts. Runs every 15 minutes via cron; idempotent.
"""
import json
import logging
import os
import random
import smtplib
import sqlite3
import ssl
import sys
import time
from datetime import datetime, timedelta, timezone
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from importlib.util import spec_from_file_location, module_from_spec
# ── Paths ──────────────────────────────────────────────────────────────────
DB_PATH = "/root/shark-game/backend/game.db"
TRACKING_FILE = "/root/shark-game/data/draft-reminders.json"
LOG_FILE = "/var/log/shark-reminder.log"
PASS_FILE = "/root/.config/himalaya/shonuff.pass"
TITLES_FILE = "/root/.hermes/references/shonuff-titles.py"
CLOSINGS_FILE = "/root/.hermes/references/shonuff-closings.py"
SIG_IMAGE_FILE = "/root/.hermes/references/shonuff-image-b64.txt"
SIG_HTML_FILE = "/root/.hermes/references/shonuff-email-signature.html"
# ── SMTP Config ────────────────────────────────────────────────────────────
SMTP_HOST = "mail.germainebrown.com"
SMTP_PORT = 2525
SMTP_USER = "shonuff@germainebrown.com"
SMTP_FROM = "shonuff@germainebrown.com"
SMTP_FROM_NAME = "Sho'Nuff Brown"
BCC_ADDR = "g@germainebrown.com"
# ── Base URL for draft room links ─────────────────────────────────────────
BASE_URL = "https://shark.itpropartner.com"
# ── Time windows (±5 minutes) ──────────────────────────────────────────────
WINDOW_SECONDS = 5 * 60 # ±5 minutes
# ── Logging ────────────────────────────────────────────────────────────────
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
handlers=[
logging.FileHandler(LOG_FILE),
logging.StreamHandler(sys.stdout),
],
)
log = logging.getLogger("shark-draft-reminder")
# ── Helpers ────────────────────────────────────────────────────────────────
def load_password():
"""Read SMTP password from file."""
with open(PASS_FILE, "r") as f:
return f.read().strip()
def load_closings():
"""Load random closing line from reference file."""
spec = spec_from_file_location("closings", CLOSINGS_FILE)
mod = module_from_spec(spec)
spec.loader.exec_module(mod)
return random.choice(mod.SHONUFF_CLOSINGS)
def load_titles():
"""Load random title from reference file."""
spec = spec_from_file_location("titles", TITLES_FILE)
mod = module_from_spec(spec)
spec.loader.exec_module(mod)
return random.choice(mod.SHONUFF_TITLES)
def load_signature_html(title):
"""Build Sho'Nuff's HTML signature block."""
try:
with open(SIG_IMAGE_FILE) as f:
b64 = f.read().strip()
with open(SIG_HTML_FILE) as f:
sig = f.read()
return sig.replace("%SHONUFF_IMAGE%", b64).replace("%SHONUFF_TITLE%", title)
except FileNotFoundError:
log.warning("Signature image/html files not found — using plain sig")
return ""
def get_db():
"""Get SQLite connection."""
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
conn.execute("PRAGMA journal_mode=WAL")
return conn
def load_tracking():
"""Load sent-reminder tracking from JSON file."""
if not os.path.exists(TRACKING_FILE):
return {}
with open(TRACKING_FILE, "r") as f:
try:
return json.load(f)
except json.JSONDecodeError:
log.warning("Corrupt tracking file, resetting")
return {}
def save_tracking(tracking):
"""Persist sent-reminder tracking to JSON file."""
os.makedirs(os.path.dirname(TRACKING_FILE), exist_ok=True)
with open(TRACKING_FILE, "w") as f:
json.dump(tracking, f, indent=2, sort_keys=True)
def tracking_key(league_id, user_id, reminder_type):
"""Generate a unique key for tracking sent reminders."""
return f"league_{league_id}_user_{user_id}_{reminder_type}"
def already_sent(tracking, league_id, user_id, reminder_type):
"""Check if a reminder has already been sent."""
key = tracking_key(league_id, user_id, reminder_type)
return key in tracking
def mark_sent(tracking, league_id, user_id, reminder_type):
"""Record that a reminder was sent (with ISO timestamp)."""
key = tracking_key(league_id, user_id, reminder_type)
tracking[key] = datetime.now(timezone.utc).isoformat()
def get_user_regions_preview(conn, league_id, user_id):
"""Get user's drafted regions for the 'your regions preview' in 24h email."""
rows = conn.execute(
"""SELECT r.emoji, r.name
FROM draft_picks dp
JOIN regions r ON r.id = dp.region_id
WHERE dp.league_id = ? AND dp.user_id = ?
ORDER BY dp.round, dp.pick_number""",
(league_id, user_id),
).fetchall()
if not rows:
return " • No picks yet — get ready to draft!"
return "\n".join(f" • {r['emoji']} {r['name']}" for r in rows)
def build_email_body_24h(league_name, display_name, league_id, regions_preview, draft_start_at_str):
"""Build the 24-hour reminder email body."""
draft_url = f"{BASE_URL}/draft-room.html?league_id={league_id}"
dt = datetime.fromisoformat(draft_start_at_str)
draft_time_str = dt.strftime("%A, %B %d at %I:%M %p %Z")
body = f"""Howdy {display_name},
🦈 SHARK ALERT — Your draft for {league_name} starts in 24 hours!
⏰ Draft Time: {draft_time_str}
Get ready to claim your regions and build the ultimate shark attack fantasy team. Here's your draft room link (bookmark it!):
🔗 {draft_url}
📋 Your Current Regions:
{regions_preview}
Make sure you're logged in and ready to go when the draft starts. Each pick is timed, so don't be late — or the sharks will swim right past you!
Good luck, and may the biggest bites be yours."""
return body
def build_email_body_1h(league_name, display_name, league_id, draft_start_at_str):
"""Build the 1-hour reminder email body."""
draft_url = f"{BASE_URL}/draft-room.html?league_id={league_id}"
dt = datetime.fromisoformat(draft_start_at_str)
draft_time_str = dt.strftime("%I:%M %p %Z")
body = f"""Yo {display_name},
🔥 The draft is T-1 HOUR away! {league_name} starts in just 60 minutes!
⏰ Draft Time: {draft_time_str}
Head to the draft room NOW and make sure everything is ready:
🔗 {draft_url}
📌 Pro tips:
• Make sure your browser notifications are enabled
• Have your region research ready
• Don't step away — your picks are on a timer!
This is it — time to make your picks and show everyone who the real apex predator is.
Let's go!"""
return body
def send_email(to_addr, subject, body, closing, title):
"""Send an email via SMTP with Sho'Nuff's signature."""
sig_html = load_signature_html(title)
msg = MIMEMultipart("alternative")
msg["From"] = f"{SMTP_FROM_NAME} <{SMTP_FROM}>"
msg["To"] = to_addr
msg["Bcc"] = BCC_ADDR
msg["Subject"] = subject
# Plain text part
plain_text = f"{body}\n\n{closing}\n\n-- \nSho'Nuff Brown\n{title}\nIT Pro Partner\n{SMTP_FROM}"
msg.attach(MIMEText(plain_text, "plain"))
# HTML part
html_body = body.replace("\n", "
\n")
if sig_html:
html_content = f"
{html_body}
{closing}
{sig_html}" else: html_content = f"{html_body}
{closing}
--
Sho'Nuff Brown
{title}
IT Pro Partner