|
|
@ -1,6 +1,8 @@
|
|
|
|
#!/usr/bin/python
|
|
|
|
#!/usr/bin/python
|
|
|
|
|
|
|
|
# if you're reading the source code for this (oof), feel free to suggest improvements for this, or, well, anything above or below this comment (as long as it's not just "rewrite this entire thing in C++ for me because i think python bad", "idk how but optimize stuff kthx". just don't be a dick, ok? thanks).
|
|
|
|
import configparser
|
|
|
|
import configparser
|
|
|
|
import datetime
|
|
|
|
import datetime
|
|
|
|
|
|
|
|
from datetime import date, timedelta
|
|
|
|
import calendar
|
|
|
|
import calendar
|
|
|
|
import caldav
|
|
|
|
import caldav
|
|
|
|
import argparse
|
|
|
|
import argparse
|
|
|
@ -11,7 +13,7 @@ Version = "%(prog)s 0.1"
|
|
|
|
configpath = os.getenv("HOME")+"/.config/decal.conf"
|
|
|
|
configpath = os.getenv("HOME")+"/.config/decal.conf"
|
|
|
|
config.read(configpath)
|
|
|
|
config.read(configpath)
|
|
|
|
#define arguments
|
|
|
|
#define arguments
|
|
|
|
today = datetime.date.today()
|
|
|
|
today = date.today()
|
|
|
|
parser = argparse.ArgumentParser(description="Cal with events.")
|
|
|
|
parser = argparse.ArgumentParser(description="Cal with events.")
|
|
|
|
parser.add_argument("year",
|
|
|
|
parser.add_argument("year",
|
|
|
|
action="store",
|
|
|
|
action="store",
|
|
|
@ -41,6 +43,12 @@ parser.add_argument("--create",
|
|
|
|
parser.add_argument("--calendar",
|
|
|
|
parser.add_argument("--calendar",
|
|
|
|
action="append",
|
|
|
|
action="append",
|
|
|
|
help="Specify a calendar (or multiple calendars) to sync from")
|
|
|
|
help="Specify a calendar (or multiple calendars) to sync from")
|
|
|
|
|
|
|
|
parser.add_argument("-d",
|
|
|
|
|
|
|
|
action="store_true",
|
|
|
|
|
|
|
|
help="Show event details for selected day")
|
|
|
|
|
|
|
|
parser.add_argument("-m",
|
|
|
|
|
|
|
|
action="store_true",
|
|
|
|
|
|
|
|
help="Show event details for entire month")
|
|
|
|
parser.add_argument("-1",
|
|
|
|
parser.add_argument("-1",
|
|
|
|
action="store_true",
|
|
|
|
action="store_true",
|
|
|
|
help="show only a single month (default)")
|
|
|
|
help="show only a single month (default)")
|
|
|
@ -54,6 +62,9 @@ parser.add_argument("-n",
|
|
|
|
action="store",
|
|
|
|
action="store",
|
|
|
|
type=int,
|
|
|
|
type=int,
|
|
|
|
help="show n months")
|
|
|
|
help="show n months")
|
|
|
|
|
|
|
|
parser.add_argument("-s","--sync",
|
|
|
|
|
|
|
|
action="store_true",
|
|
|
|
|
|
|
|
help="sync the calendar cache")
|
|
|
|
|
|
|
|
|
|
|
|
args = vars(parser.parse_args())
|
|
|
|
args = vars(parser.parse_args())
|
|
|
|
|
|
|
|
|
|
|
@ -61,14 +72,15 @@ args = vars(parser.parse_args())
|
|
|
|
if args["create"]:
|
|
|
|
if args["create"]:
|
|
|
|
print("Not implemented")
|
|
|
|
print("Not implemented")
|
|
|
|
exit(0)
|
|
|
|
exit(0)
|
|
|
|
if args["json"]:
|
|
|
|
|
|
|
|
print("Not implemented")
|
|
|
|
|
|
|
|
exit(0)
|
|
|
|
|
|
|
|
#check some stuff, do some warnings, initiate the config, etc.
|
|
|
|
#check some stuff, do some warnings, initiate the config, etc.
|
|
|
|
if not os.path.exists(configpath):
|
|
|
|
if not os.path.exists(configpath):
|
|
|
|
config['DEFAULT'] = {'uri': 'your caldav server here',
|
|
|
|
config['DEFAULT'] = {'uri': 'your caldav server here',
|
|
|
|
'user': 'your username here',
|
|
|
|
'user': 'your username here',
|
|
|
|
'password': 'your pass here'}
|
|
|
|
'password': 'your pass here',
|
|
|
|
|
|
|
|
'cache':os.getenv('HOME')+'/.cache/decal.json',
|
|
|
|
|
|
|
|
'cacheEnabled':1,
|
|
|
|
|
|
|
|
'syncAfter':1}
|
|
|
|
print("Creating an empty config in ~/.config/decal.conf")
|
|
|
|
print("Creating an empty config in ~/.config/decal.conf")
|
|
|
|
with open(configpath,'w') as configfile:
|
|
|
|
with open(configpath,'w') as configfile:
|
|
|
|
config.write(configfile)
|
|
|
|
config.write(configfile)
|
|
|
@ -89,17 +101,14 @@ if config['DEFAULT']['uri'] == "your caldav server here":
|
|
|
|
|
|
|
|
|
|
|
|
#generate the actual calendar, line by line, output an array of lines.
|
|
|
|
#generate the actual calendar, line by line, output an array of lines.
|
|
|
|
#it works trust me, idk what is happening in this one but it works.
|
|
|
|
#it works trust me, idk what is happening in this one but it works.
|
|
|
|
def gencal(year,month,start_on_sunday=True,cell_modifier=lambda d: d,append_year=True):
|
|
|
|
def gencal(year,month,firstweekday=6,cell_modifier=lambda d: d,append_year=True):
|
|
|
|
firstweekday = 0
|
|
|
|
|
|
|
|
if start_on_sunday:
|
|
|
|
|
|
|
|
firstweekday = 6
|
|
|
|
|
|
|
|
cal = calendar.Calendar(firstweekday=firstweekday)
|
|
|
|
cal = calendar.Calendar(firstweekday=firstweekday)
|
|
|
|
lines = [""]*6
|
|
|
|
lines = [""]*6
|
|
|
|
monthstart = False
|
|
|
|
monthstart = False
|
|
|
|
counter = 0
|
|
|
|
counter = 0
|
|
|
|
for date in cal.itermonthdates(year,month):
|
|
|
|
for curdate in cal.itermonthdates(year,month):
|
|
|
|
lines[counter//7]
|
|
|
|
lines[counter//7]
|
|
|
|
day = str(date)[-2:]
|
|
|
|
day = str(curdate)[-2:]
|
|
|
|
if day == "01":
|
|
|
|
if day == "01":
|
|
|
|
monthstart = not monthstart
|
|
|
|
monthstart = not monthstart
|
|
|
|
if monthstart:
|
|
|
|
if monthstart:
|
|
|
@ -108,14 +117,11 @@ def gencal(year,month,start_on_sunday=True,cell_modifier=lambda d: d,append_year
|
|
|
|
lines[counter//7] += " "
|
|
|
|
lines[counter//7] += " "
|
|
|
|
lines[counter//7] +=" "
|
|
|
|
lines[counter//7] +=" "
|
|
|
|
counter+=1
|
|
|
|
counter+=1
|
|
|
|
month = datetime.date(year,month,1).strftime("%B %Y")
|
|
|
|
weeklines = ["Mo","Tu","We","Th","Fr","Sa","Su"]
|
|
|
|
padding = (21-len(month))//2
|
|
|
|
for times in range(firstweekday):
|
|
|
|
rpadding = 21%(padding+len(month)+padding)
|
|
|
|
weeklines.append(weeklines.pop(0))
|
|
|
|
if start_on_sunday:
|
|
|
|
lines.insert(0," ".join(weeklines)+" ")
|
|
|
|
lines.insert(0,"Su Mo Tu We Th Fr Sa ")
|
|
|
|
lines.insert(0,date(year,month,1).strftime("%B %Y").center(21))
|
|
|
|
else:
|
|
|
|
|
|
|
|
lines.insert(0,"Mo Tu We Th Fr Sa Su ")
|
|
|
|
|
|
|
|
lines.insert(0,(" "*padding)+month+(" "*(padding+rpadding)))
|
|
|
|
|
|
|
|
lines[-1] += " "*(21-len(lines[-1]))
|
|
|
|
lines[-1] += " "*(21-len(lines[-1]))
|
|
|
|
return lines
|
|
|
|
return lines
|
|
|
|
|
|
|
|
|
|
|
@ -155,12 +161,12 @@ def span(year,month,offset):
|
|
|
|
|
|
|
|
|
|
|
|
#get 2 date values - start value to scan from and end value to scan up to.
|
|
|
|
#get 2 date values - start value to scan from and end value to scan up to.
|
|
|
|
def getbounds(y,m,offset):
|
|
|
|
def getbounds(y,m,offset):
|
|
|
|
start = datetime.date(y,m,1)
|
|
|
|
start = date(y,m,1)
|
|
|
|
postnextmonth = span(y,m,offset)
|
|
|
|
postnextmonth = span(y,m,offset)
|
|
|
|
nextmonth = span(postnextmonth[0],postnextmonth[1],-1)
|
|
|
|
nextmonth = span(postnextmonth[0],postnextmonth[1],-1)
|
|
|
|
end = datetime.date(nextmonth[0],
|
|
|
|
end = date(nextmonth[0],
|
|
|
|
nextmonth[1],
|
|
|
|
nextmonth[1],
|
|
|
|
(datetime.date(postnextmonth[0],postnextmonth[1],1)-datetime.timedelta(days=1)).day)
|
|
|
|
(date(postnextmonth[0],postnextmonth[1],1)-timedelta(days=1)).day)
|
|
|
|
return start,end
|
|
|
|
return start,end
|
|
|
|
|
|
|
|
|
|
|
|
# generator for year/month pairs
|
|
|
|
# generator for year/month pairs
|
|
|
@ -189,57 +195,101 @@ else:
|
|
|
|
start,end = getbounds(args["year"],args["month"],1)
|
|
|
|
start,end = getbounds(args["year"],args["month"],1)
|
|
|
|
offset = 1
|
|
|
|
offset = 1
|
|
|
|
|
|
|
|
|
|
|
|
# connect to the DAV and receive calendars
|
|
|
|
|
|
|
|
client = caldav.DAVClient(url = config['DEFAULT']['uri'],
|
|
|
|
|
|
|
|
username = config['DEFAULT']['user'],
|
|
|
|
|
|
|
|
password = config['DEFAULT']['password'])
|
|
|
|
|
|
|
|
principal = client.principal()
|
|
|
|
|
|
|
|
calendars = principal.calendars()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# aggregate selected calendars
|
|
|
|
# aggregate selected calendars
|
|
|
|
if "calendars" in config['DEFAULT']:
|
|
|
|
def aggregateCalendars(calendars):
|
|
|
|
calendars2 = []
|
|
|
|
calendars2 = []
|
|
|
|
cals = config['DEFAULT']["calendars"].split(",")
|
|
|
|
cals = config['DEFAULT']["calendars"].split(",")
|
|
|
|
for cal in calendars:
|
|
|
|
for cal in calendars:
|
|
|
|
if cal.name in cal:
|
|
|
|
if cal.name in cal:
|
|
|
|
calendars2.append(cal)
|
|
|
|
calendars2.append(cal)
|
|
|
|
calendars = calendars2
|
|
|
|
return calendars2
|
|
|
|
|
|
|
|
|
|
|
|
# hooo boy, fun things start here.
|
|
|
|
def daysOfEvent(event):
|
|
|
|
# we generate a dict of (year,month) pairs for easier indexing
|
|
|
|
event = event.vobject_instance.vevent.contents
|
|
|
|
events = {}
|
|
|
|
curdate = event["dtstart"][0].value
|
|
|
|
for ympair in ympairs((args['year'],args['month']),offset,dstart=args['3']):
|
|
|
|
enddate = event["dtend"][0].value
|
|
|
|
events[ympair] = {}
|
|
|
|
while curdate <= enddate:
|
|
|
|
# next, we iterate over events in each calendar, sorting them the folliwng way:
|
|
|
|
if type(curdate) == datetime.datetime:
|
|
|
|
# events[(int year,int month)][int day] = [event event1, event event2, ...]
|
|
|
|
yield str(curdate.date())
|
|
|
|
# looks awful, is awful, overall i love this.
|
|
|
|
else:
|
|
|
|
for cal in calendars:
|
|
|
|
yield str(curdate)
|
|
|
|
|
|
|
|
curdate += timedelta(days=1)
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def jsonifyEvent(event):
|
|
|
|
|
|
|
|
event = event.vobject_instance.vevent.contents
|
|
|
|
|
|
|
|
evdata = {
|
|
|
|
|
|
|
|
"uid": event["uid"][0].value,
|
|
|
|
|
|
|
|
"dtstart": str(event["dtstart"][0].value),
|
|
|
|
|
|
|
|
"dtend": str(event["dtend"][0].value)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
# may or may not be there, idk why. vobject format is really weird
|
|
|
|
|
|
|
|
for key in ["summary","description","status"]:
|
|
|
|
|
|
|
|
if key in event:
|
|
|
|
|
|
|
|
evdata[key] = event[key][0].value
|
|
|
|
|
|
|
|
return evdata
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def generateDateTree(calendars):
|
|
|
|
|
|
|
|
events = {}
|
|
|
|
|
|
|
|
for cal in calendars:
|
|
|
|
events_fetched = cal.date_search(start,end)
|
|
|
|
events_fetched = cal.date_search(start,end)
|
|
|
|
for event in events_fetched:
|
|
|
|
for event in events_fetched:
|
|
|
|
event = event.vobject_instance.vevent.contents
|
|
|
|
for date in daysOfEvent(event):
|
|
|
|
curdate = event["dtstart"][0].value + datetime.timedelta(days=-1)
|
|
|
|
if not date in events:
|
|
|
|
while curdate < event["dtend"][0].value:
|
|
|
|
events[date] = []
|
|
|
|
curdate += datetime.timedelta(days=1)
|
|
|
|
events[date].append(jsonifyEvent(event))
|
|
|
|
curdindex = (curdate.year,curdate.month)
|
|
|
|
return events
|
|
|
|
if curdindex in events:
|
|
|
|
|
|
|
|
if not curdate.day in events[curdindex]:
|
|
|
|
cache = None
|
|
|
|
events[curdindex][curdate.day] = []
|
|
|
|
if "cache" in config['DEFAULT']:
|
|
|
|
events[curdindex][curdate.day].append(event)
|
|
|
|
if os.path.exists(config['DEFAULT']['cache']):
|
|
|
|
else:
|
|
|
|
with open(config['DEFAULT']['cache'],"r") as file:
|
|
|
|
break
|
|
|
|
cache = json.loads(file.read())
|
|
|
|
# if you're reading the source code for this (oof), feel free to suggest improvements for this, or, well, anything above or below this comment (as long as it's not just "rewrite this entire thing in C++ for me because i think python bad", "idk how but optimize stuff kthx". just don't be a dick, ok? thanks).
|
|
|
|
|
|
|
|
|
|
|
|
def updateCriteria():
|
|
|
|
|
|
|
|
if not ("cacheEnabled" in config["DEFAULT"]):
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
if (not (config['DEFAULT']["cacheEnabled"] == "1")):
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
if not cache:
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
date = datetime.datetime.strptime(cache["lastsync"],"%Y-%m-%d")
|
|
|
|
|
|
|
|
if args["sync"]:
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
return (date.date() <= today - timedelta(days=int(config['DEFAULT']["syncAfter"])))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#Update cache if update criteria are met.
|
|
|
|
|
|
|
|
if updateCriteria():
|
|
|
|
|
|
|
|
# connect to the DAV and receive calendars
|
|
|
|
|
|
|
|
client = caldav.DAVClient(url = config['DEFAULT']['uri'],
|
|
|
|
|
|
|
|
username = config['DEFAULT']['user'],
|
|
|
|
|
|
|
|
password = config['DEFAULT']['password'])
|
|
|
|
|
|
|
|
principal = client.principal()
|
|
|
|
|
|
|
|
calendars = principal.calendars()
|
|
|
|
|
|
|
|
if "calendars" in config['DEFAULT']:
|
|
|
|
|
|
|
|
calendars = aggregateCalendars(calendars)
|
|
|
|
|
|
|
|
cache = generateDateTree(calendars)
|
|
|
|
|
|
|
|
cache["lastsync"] = str(today)
|
|
|
|
|
|
|
|
with open(config['DEFAULT']['cache'],"w") as file:
|
|
|
|
|
|
|
|
file.write(json.dumps(cache))
|
|
|
|
|
|
|
|
events = cache
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if args["json"]:
|
|
|
|
|
|
|
|
print(json.dumps(events))
|
|
|
|
|
|
|
|
exit(0)
|
|
|
|
|
|
|
|
|
|
|
|
# and now we're just generating calendar lines
|
|
|
|
# and now we're just generating calendar lines
|
|
|
|
cal_prints = []
|
|
|
|
cal_prints = []
|
|
|
|
|
|
|
|
selected_date = date(args['year'],args['month'],args['day'])
|
|
|
|
for year,month in ympairs((args['year'],args['month']),offset,dstart=args['3']):
|
|
|
|
for year,month in ympairs((args['year'],args['month']),offset,dstart=args['3']):
|
|
|
|
# a function to colorize cells in a more or less generic way
|
|
|
|
# a function to colorize cells in a more or less generic way
|
|
|
|
def lambdafunc(cell):
|
|
|
|
def lambdafunc(cell):
|
|
|
|
day = int(cell)
|
|
|
|
day = date(year,month,int(cell))
|
|
|
|
if day in events[(year,month)]:
|
|
|
|
if str(day) in events:
|
|
|
|
event = events[(year,month)][day]
|
|
|
|
event = events[str(day)]
|
|
|
|
uid = event[0]["uid"][0].value.encode()
|
|
|
|
uid = event[0]["uid"].encode()
|
|
|
|
cell = colorize(cell,uid)
|
|
|
|
cell = colorize(cell,uid)
|
|
|
|
if datetime.date(year,month,day) == datetime.date.today():
|
|
|
|
if day == selected_date:
|
|
|
|
cell = colorize(cell,"inverse")
|
|
|
|
cell = colorize(cell,"inverse")
|
|
|
|
return cell
|
|
|
|
return cell
|
|
|
|
cal_prints.append(gencal(year,
|
|
|
|
cal_prints.append(gencal(year,
|
|
|
@ -263,3 +313,21 @@ for count in range(3-(len(cal_prints)%3)):
|
|
|
|
for cal in range(0,len(cal_prints),3):
|
|
|
|
for cal in range(0,len(cal_prints),3):
|
|
|
|
printlines(cal_prints[cal],cal_prints[cal+1],cal_prints[cal+2])
|
|
|
|
printlines(cal_prints[cal],cal_prints[cal+1],cal_prints[cal+2])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def printDailyEvents(events,evtrack=[]):
|
|
|
|
|
|
|
|
for event in events:
|
|
|
|
|
|
|
|
if not event in evtrack:
|
|
|
|
|
|
|
|
uid = event["uid"].encode()
|
|
|
|
|
|
|
|
print(colorize(event["dtstart"]+" - "+event["dtend"],uid)+":\n- "+event["summary"])
|
|
|
|
|
|
|
|
evtrack.append(event)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
evtrack = []
|
|
|
|
|
|
|
|
if args["d"]:
|
|
|
|
|
|
|
|
printDailyEvents(events[str(selected_date)],evtrack=evtrack)
|
|
|
|
|
|
|
|
exit(0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if args["m"]:
|
|
|
|
|
|
|
|
for day in range(*calendar.monthrange(args['year'],args['month'])):
|
|
|
|
|
|
|
|
key = str(date(args['year'],args['month'],day))
|
|
|
|
|
|
|
|
if key in events:
|
|
|
|
|
|
|
|
printDailyEvents(events[key],evtrack=evtrack)
|
|
|
|