diff options
author | B. Stack <bgstack15@gmail.com> | 2023-05-17 15:20:53 -0400 |
---|---|---|
committer | B. Stack <bgstack15@gmail.com> | 2023-05-17 15:20:53 -0400 |
commit | 8dcd3a6e78eb1b1729b172080ec94dbcb8c8a96c (patch) | |
tree | a4e6c79d7bf355e5152de14f5a45829efab013e5 | |
download | agenda-8dcd3a6e78eb1b1729b172080ec94dbcb8c8a96c.tar.gz agenda-8dcd3a6e78eb1b1729b172080ec94dbcb8c8a96c.tar.bz2 agenda-8dcd3a6e78eb1b1729b172080ec94dbcb8c8a96c.zip |
initial commit
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | README.md | 29 | ||||
-rwxr-xr-x | agenda.py | 210 |
3 files changed, 240 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c18dd8d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..347f065 --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +# README for agenda +This small python project uses [python3-caldav](https://github.com/python-caldav/caldav) to connect to my calendar, and then generates a summary agenda for my day. It is very basic, but it gets my job done. I use this in a cron job to send an email to myself before the day starts. I always check email, but I apparently forget to check my calendar. + +## Upstream +This project's upstream is at <https://bgstack15.ddns.net/cgit/agenda>. + +## Alternatives +None researched. There's a million ways to get a summary of your day in any calendaring program. + +## Reason for existence +For myself. + +## Using +Only a basic library exists; no frontend exists. In an interactive python3 shell, you would run: + + import importlib, agenda + print(agenda.summarize("2023-05-17",url="https://example.com/radicale/",username="bgstack15",password="plaintext")) + +You can also pass a datetime.datetime or datetime.date object instead of a `YYYY-MM-DD` string. + +## Dependencies + +* python3-caldav + +## Building +None + +## References +See references heading in the [file](agenda.py). diff --git a/agenda.py b/agenda.py new file mode 100755 index 0000000..fa1fa16 --- /dev/null +++ b/agenda.py @@ -0,0 +1,210 @@ +#!/usr/bin/env python3 +# File: agenda.py +# Location: https://bgstack15.ddns.net/cgit/agenda +# Author: bgstack15 +# Startdate: 2023-05-16-3 14:09 +# Title: Generate daily agenda html message +# Purpose: remind myself of what is happening today +# History: +# Usage: See function summarize() +# Reference: +# 1. basic_usage_examples.py from caldav +# 2. https://stackoverflow.com/questions/29643352/converting-hex-to-rgb-value-in-python +# 3. https://stackoverflow.com/questions/3942878/how-to-decide-font-color-in-white-or-black-depending-on-background-color +# Improve: +# Dependencies: +# python3-caldav + +import sys, caldav +from datetime import date, datetime, timedelta + +def get_calendar_color(calendar): + """ + Author: bgstack15 + Purpose: return the hexadecimal color (and alpha channel if present) for the given caldav calendar. + """ + color = prop = None + try: + colorprop = caldav.elements.ical.CalendarColor() + prop = calendar.get_properties(props=[colorprop]) + if prop: + for j in prop: + # only the one + color = prop[j] + except Exception as e: + print(f"Debug: no color info available for calendar {calendar}, e: {e}") + return color + +def print_calendars_demo(calendars): + """ + This example prints the name and URL for every calendar on the list + """ + if calendars: + ## Some calendar servers will include all calendars you have + ## access to in this list, and not only the calendars owned by + ## this principal. + print("your principal has %i calendars:" % len(calendars)) + for c in calendars: + color = get_calendar_color(c) + print("Name: %-36s Color: %-9s URL: %s" % (c.name, color, c.url)) + else: + print("your principal has no calendars") + +def show_event(event): + response = "" + all_dtstart_dt = [] + all_dtstart_str_dt = [] + #print(event.data) + #for i in event.icalendar_component: + # print(f"{i} IS {event.icalendar_component[i]}") + dtstart = event.icalendar_component["DTSTART"] + dtstart_dt = dtstart and dtstart.dt + if str(dtstart_dt) not in all_dtstart_str_dt: + all_dtstart_dt.append(dtstart_dt) + all_dtstart_str_dt.append(str(dtstart_dt)) + if all_dtstart_dt: + for i in all_dtstart_dt: + summary = event.icalendar_component["SUMMARY"].strip() + thishour = int(i.strftime("%I")) + thistime = str(thishour) + i.strftime(":%M%P") + if type(event.icalendar_component["DTEND"].dt) == date: + thistime = "All day" + response += f"{thistime}: {summary}<br/>\n" + return response + +# From reference 2 +def hex_to_rgb(value): + value = value.lstrip('#').rstrip(";") + lv = len(value) + return tuple(int(value[i:i + lv // 3], 16) for i in range(0, lv, lv // 3)) +def rgb_to_hex(rgb): + return '#%02x%02x%02x' % rgb + +# bgstack15 +def get_faded_color(hexcolor): + """ Given hexcolor as string of '#ffccaa', return a faded color """ + outcolor = "" + try: + r, g, b = hex_to_rgb(hexcolor) + except: + try: + r, g, b, a = hex_to_rgb(hexcolor) + except: + print(f"Warning! Cannot understand color {hexcolor} for getting a faded value.") + return hexcolor + # so now we have r, g, b. We do not know yet how to use alpha. + if len(hexcolor) == 4: + # that is, if hexcolor is "#fff" or similar + r = r * 16 + g = g * 16 + b = b * 16 + r1 = int((255 + r)/2) + g1 = int((255 + g)/2) + b1 = int((255 + b)/2) + outcolor = rgb_to_hex((r1,g1,b1)) + return outcolor + +# Adapted from reference 3 +def get_text_color_based_on_background_color(bgColor, lightColor, darkColor): + color = bgColor.lstrip("#").rstrip(";") + r, g, b = hex_to_rgb(color) + uicolors = [r/255, g/255, b/255] + adjusted = [] + for col in uicolors: + col2 = col + if col <= 0.03928: + col2 = col/12.92 + col2 = pow((col2 + 0.055)/1.055,2.4) + adjusted.append(col2) + L = (0.2126 * adjusted[0] + 0.7152 * adjusted[1] + (0.072 * adjusted[2])) + return darkColor if L > 0.179 else lightColor + +#function pickTextColorBasedOnBgColorAdvanced(bgColor, lightColor, darkColor) { +# var color = (bgColor.charAt(0) === '#') ? bgColor.substring(1, 7) : bgColor; +# var r = parseInt(color.substring(0, 2), 16); // hexToR +# var g = parseInt(color.substring(2, 4), 16); // hexToG +# var b = parseInt(color.substring(4, 6), 16); // hexToB +# var uicolors = [r / 255, g / 255, b / 255]; +# var c = uicolors.map((col) => { +# if (col <= 0.03928) { +# return col / 12.92; +# } +# return Math.pow((col + 0.055) / 1.055, 2.4); +# }); +# var L = (0.2126 * c[0]) + (0.7152 * c[1]) + (0.0722 * c[2]); +# return (L > 0.179) ? darkColor : lightColor; +#} + +def event_key(i): + return i.icalendar_component["DTSTART"].dt + +def summarize_calendar(calendar, thisdate = None, count = 0): + """ + Examine calendar for given thisdate (of type datetime.date) + """ + response = "" + #d = date.today()+timedelta(days=-1) + if type(thisdate) == datetime: + thisdate = thisdate.date() + if type(thisdate) != date: + thisdate = datetime.today() + # so now, thisdate should be of type datetime.date + d = thisdate + d2 = d + timedelta(days=1) + year, month, day = d2.year, d2.month, d2.day + events_fetched = calendar.search( + start=d, + end=datetime(year,month,day), + event=True, + expand=True, + ) + x = 0 + events_wholeday = [i for i in events_fetched if type(i.icalendar_component["DTSTART"].dt) == date] + events_regular = [i for i in events_fetched if type(i.icalendar_component["DTSTART"].dt) != date] + events_sorted = sorted(events_regular, key=event_key) + for i in events_wholeday + events_sorted: + x = x + 1 + if 1 == x: + if 1 == count: + datestring = thisdate.strftime("%B %d") + response += f"<h1>Summary for {datestring}</h1>\n" + color = get_calendar_color(calendar) + color2 = get_faded_color(color) + h2_text_color = get_text_color_based_on_background_color(color,"#ffffff","#000000") + text_color = get_text_color_based_on_background_color(color2,"#ffffff","#000000") + response += f"<div style='background-color: {color2}; color: {text_color}'><h2 style='background-color: {color}; color: {h2_text_color}'>{calendar.name}</h2>\n" + response += show_event(i) + if x > 0: + response += "</div>\n" + return response + +def summarize(thisdate = None, url = "http://example.com/radicale/", username = "username", password = "insecure"): + """ + Given a date ("YYYY-MM-DD" or datetime.date object), produce a string of html suitable for an agenda email. + Example: + with open("agenda.html","w") as w: + w.write(summarize("2023-05-14",url="https://server3.ipa.example.com/radicale/",username="username",password="password")) + """ + response = "" + if type(thisdate) == str: + try: + thisdate = datetime.strptime(thisdate,"%Y-%m-%d") + except: + print("Warning! Expected YYYY-MM-DD or a datetime.date or datetime.datetime object. Using today's date.") + thisdate = datetime.today() + if type(thisdate) == datetime: + thisdate = thisdate.date() + elif thisdate is None: + thisdate = date.today() + if type(thisdate) != date: + print(f"Warning! Unknown date: \"{thisdate}\". Using today.") + thisdate = date.today() + with caldav.DAVClient(url=url, username=username, password=password) as client: + my_principal = client.principal() + calendars = my_principal.calendars() + #print_calendars_demo(calendars) + x = 0 + for cal in calendars: + x = x + 1 + response += summarize_calendar(cal, thisdate, x) + return response |