aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorB. Stack <bgstack15@gmail.com>2023-05-17 15:20:53 -0400
committerB. Stack <bgstack15@gmail.com>2023-05-17 15:20:53 -0400
commit8dcd3a6e78eb1b1729b172080ec94dbcb8c8a96c (patch)
treea4e6c79d7bf355e5152de14f5a45829efab013e5
downloadagenda-8dcd3a6e78eb1b1729b172080ec94dbcb8c8a96c.tar.gz
agenda-8dcd3a6e78eb1b1729b172080ec94dbcb8c8a96c.tar.bz2
agenda-8dcd3a6e78eb1b1729b172080ec94dbcb8c8a96c.zip
initial commit
-rw-r--r--.gitignore1
-rw-r--r--README.md29
-rwxr-xr-xagenda.py210
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
bgstack15