Files
Moon-Household-Budget/reminders.py
2026-06-06 14:22:57 -05:00

126 lines
4.8 KiB
Python

import smtplib
import subprocess
import json
import os
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from datetime import date, timedelta
import calendar as cal_module
from bills import load_bills
EMAIL = "bonna61@gmail.com"
APP_PASSWORD = "qowu zcff dbtk kqbs"
def send_email(subject, body_html, body_text):
msg = MIMEMultipart("alternative")
msg["Subject"] = subject
msg["From"] = f"Moon Household Budget <{EMAIL}>"
msg["To"] = EMAIL
msg.attach(MIMEText(body_text, "plain"))
msg.attach(MIMEText(body_html, "html"))
with smtplib.SMTP_SSL("smtp.gmail.com", 465) as server:
server.login(EMAIL, APP_PASSWORD)
server.sendmail(EMAIL, EMAIL, msg.as_string())
def mac_notification(title, message):
script = f'display notification "{message}" with title "{title}" sound name "Default"'
subprocess.run(["osascript", "-e", script])
def bills_due_in(days):
today = date.today()
target = today + timedelta(days=days)
all_bills = load_bills()
due = []
for b in all_bills:
if not b.get("active", True):
continue
if b.get("autopay", False):
continue
if not b.get("remind", True):
continue
due_day = b.get("due_day")
if not due_day:
continue
_, num_days = cal_module.monthrange(target.year, target.month)
due_day_clamped = min(due_day, num_days)
bill_date = date(target.year, target.month, due_day_clamped)
if bill_date == target:
due.append({**b, "due_date": bill_date})
return due
def check_and_send_reminders():
sent = []
for days_ahead, label in [(3, "in 3 days"), (0, "TODAY")]:
due = bills_due_in(days_ahead)
if not due:
continue
total = sum(b.get("amount", 0) for b in due if b.get("amount"))
# Mac notifications — one per bill
for b in due:
amt = f"${b['amount']:.2f}" if b.get("amount") else "amount varies"
mac_notification(
f"Bill Due {label.title()}",
f"{b['name']}{amt}"
)
# Email — one digest
subject = f"🌙 Moon Budget: {len(due)} bill{'s' if len(due) != 1 else ''} due {label}"
rows = ""
for b in due:
amt = f"${b['amount']:.2f}" if b.get("amount") else ""
cat = b.get("category", "")
rows += f"""
<tr>
<td style="padding:8px 12px;border-bottom:1px solid #EDE8E2;">{b['name']}</td>
<td style="padding:8px 12px;border-bottom:1px solid #EDE8E2;font-weight:600;">{amt}</td>
<td style="padding:8px 12px;border-bottom:1px solid #EDE8E2;color:#7A6E68;">{cat}</td>
</tr>"""
total_row = f"${total:.2f}" if total else ""
body_html = f"""
<div style="font-family:'Helvetica Neue',Arial,sans-serif;max-width:520px;margin:0 auto;background:#FAF7F2;padding:24px;border-radius:12px;">
<h2 style="font-size:22px;color:#3A3330;margin-bottom:4px;">🌙 Moon Household Budget</h2>
<p style="color:#7A6E68;margin-bottom:20px;font-size:14px;">Bill reminder — due <strong>{label}</strong></p>
<table style="width:100%;border-collapse:collapse;background:#fff;border-radius:8px;overflow:hidden;box-shadow:0 2px 8px rgba(80,60,50,0.08);">
<thead>
<tr style="background:#F5F1EC;">
<th style="text-align:left;padding:8px 12px;font-size:11px;text-transform:uppercase;letter-spacing:0.07em;color:#B0A89E;">Bill</th>
<th style="text-align:left;padding:8px 12px;font-size:11px;text-transform:uppercase;letter-spacing:0.07em;color:#B0A89E;">Amount</th>
<th style="text-align:left;padding:8px 12px;font-size:11px;text-transform:uppercase;letter-spacing:0.07em;color:#B0A89E;">Category</th>
</tr>
</thead>
<tbody>{rows}</tbody>
{f'<tfoot><tr><td style="padding:8px 12px;font-weight:700;" colspan="2">Total: {total_row}</td><td></td></tr></tfoot>' if total_row else ''}
</table>
<p style="color:#B0A89E;font-size:12px;margin-top:20px;text-align:center;">Moon Household Budget · sent to bonna61@gmail.com</p>
</div>"""
body_text = f"Moon Budget — Bills due {label}:\n\n"
for b in due:
amt = f"${b['amount']:.2f}" if b.get("amount") else "varies"
body_text += f"{b['name']}{amt}\n"
if total:
body_text += f"\nTotal: ${total:.2f}\n"
send_email(subject, body_html, body_text)
sent.append(f"{len(due)} bills due {label}")
return sent
if __name__ == "__main__":
results = check_and_send_reminders()
if results:
print("Sent reminders:", ", ".join(results))
else:
print("No reminders to send today.")