126 lines
4.8 KiB
Python
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.")
|