receipts/daily-todo.py
2025-02-27 01:38:12 -05:00

158 lines
4.1 KiB
Python

import logging
import os
import sys
from datetime import date, datetime, timedelta
from random import randint
from caldav import DAVClient
from dotenv import find_dotenv, load_dotenv
from printer import p
logging.basicConfig(
format="{asctime} {levelname:8} {name}: {message}",
datefmt="%Y-%m-%d %H:%M:%S",
style="{",
stream=sys.stdout,
force=True,
)
log = logging.getLogger("todo-receipt")
log.setLevel(logging.DEBUG)
if load_dotenv(find_dotenv(usecwd=True)):
log.debug("Loaded .env")
else:
log.debug("Didn't find .env")
CALDAV_URL = os.getenv("CALDAV_URL")
CALDAV_USERNAME = os.getenv("CALDAV_USERNAME")
CALDAV_PASSWORD = os.getenv("CALDAV_PASSWORD")
day = timedelta(days=1)
week = timedelta(days=7)
today = date.today()
yesterday = today - day
tomorrow = today + day
next_week = today + week
def coerce_date(datetime):
try:
return datetime.date()
except:
return datetime
def shuffle_with_priority(tasks):
return sorted(
tasks,
key=lambda task: (
task.icalendar_component.get("priority", 5),
randint(0, 10),
),
)
with DAVClient(
url=CALDAV_URL, username=CALDAV_USERNAME, password=CALDAV_PASSWORD
) as client:
log.debug("Loading calendars")
principal = client.principal()
calendars = principal.calendars()
events = []
todos = []
for c in calendars:
log.debug(f"Fetching from {c.name}")
if c.name not in ["Schedule"]:
log.debug("Fetching events and todos")
events.extend(c.events())
todos.extend(c.todos())
else:
log.debug("Finding cancelled classes")
for event in c.events():
if event.icalendar_component.get("status") == "CANCELED":
log.debug(
f"Found cancelled class {event.icalendar_component.get('summary')}"
)
events.append(event)
must_do = []
should_do = []
want_to_do = []
log.debug("Processing todos")
for task in todos:
due = task.get_due()
if due is None:
want_to_do.append(task)
continue
task.expand_rrule(yesterday, next_week)
due = coerce_date(due)
try:
start = coerce_date(task.icalendar_component.get("dtstart").dt or yesterday)
except:
start = yesterday
if today < start:
continue
if due < tomorrow:
must_do.append(task)
elif due < next_week:
should_do.append(task)
else:
want_to_do.append(task)
scheduled = []
log.debug("Processing events")
for event in events:
event.expand_rrule(yesterday, next_week)
start_day = coerce_date(event.icalendar_component.get("dtstart").dt)
end_day = coerce_date(event.icalendar_component.get("dtend").dt)
if end_day < today or today < start_day:
continue
else:
scheduled.append(event)
should_want = shuffle_with_priority(should_do) + shuffle_with_priority(want_to_do)
categories = {
"Must do": must_do,
"Should do": should_want[:3],
"Want to do": should_want[3:6],
}
if len(should_want) > 6:
categories["Leftover"] = should_want[7:]
with p:
log.debug("Printing output")
p.title(" TODO List ", size=4)
p.subtitle(datetime.today().strftime("%Y-%m-%d"))
p.ln()
if scheduled:
p.textln("Scheduled:")
for event in scheduled:
summary = event.icalendar_component["summary"]
if event.icalendar_component.get("status") == "CANCELED":
summary += " (CANCELED)"
start = event.icalendar_component.get("dtstart").dt
p.textln(f"[ ] {summary} @ {start.strftime('%H:%M')}")
else:
p.textln("Scheduled: (None)")
for k, v in categories.items():
p.ln()
if v:
p.textln(f"{k}:")
for task in v:
p.textln(f"[ ] {task.icalendar_component['summary']}")
else:
p.textln(f"{k}: (None)")