Based on this library I've written python2 script.
First step is to install client library. Download the archive and follow these steps:
NOTE: you might need to install some additional packages: python2 python2-setuptools libxslt
NOTE: for version 0.2.1 see Using caldav 0.2.1 with owncloud DAV services
- tar xf caldav-0.1.12.tar.gz
- cd caldav-0.1.12
- python2 setup.py build
- sudo python2 setup.py install
- local IP address to access from LAN network
- public IP address to access from the internet
- your name and password for owncloud
- URL to your calendar
- look at the script and replace <TAGS> with your information
#!/usr/bin/python2 from datetime import datetime, timedelta import caldav from caldav.elements import dav, cdav import sys import time import os, binascii import re import socket #get the right IP address try: s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.connect(("8.8.8.8",80)) #true if ip is from local network, else use public ip if (s.getsockname()[0] == "<LAN IP of machine which is executing this script>"): ipAddress = "<LAN IP of machine with owncloud>" else: ipAddress = "<Public IP of machine with owncloud>" s.close() except: print "Unable to connect" exit(1) #NOTE #if password contains special characters, fill them in hex format in url e.g. https://name:passd#f@... > https://name:passd%23f@... #then edit the file /usr/lib/python2.7/site-packages/caldav-0.1.12-py2.7.egg/caldav/davclient.py and add corresponding replace on line 65 #do not use < or > in name/password, as this is parsed in XML and causes issues with parsing #set caldav url url = "https://<username>:<password>@" + ipAddress + "/owncloud/remote.php/caldav/calendars/<user>/<calendar name>" #default number of days to show defDaysSh=7 #default number of days for creating event defEventLen=7 #timezone +2 hours calTimeZone=2 #default reminder time, in minutes (defined as str(), because it's part of reminderData structure) defReminderTime="10" #connect to the calendar def connect(): client = caldav.DAVClient(url) principal = caldav.Principal(client, url) calendars = principal.calendars() if len(calendars) > 0: return (calendars[0], client) def setAlert(UID, reminderTime): reminderData = """ BEGIN:VALARM TRIGGER;VALUE=DURATION:-PT""" + reminderTime + """M ACTION:DISPLAY DESCRIPTION:Default Event Notification X-WR-ALARMUID:""" + UID + """ END:VALARM""" return reminderData def help(): print "Script for ownCloud calDAV manipulation.\n KrisKo 2013" print "\nUSAGE: ", sys.argv[0], " OPTIONS" print " sh [days] \n\tshow upcoming events for n-days, default is", defDaysSh print " add [days] [hrs] (event summary) [remind]\n\tadd event for n-days, default is", defEventLen, ", \"remind<nr>\" at the end sets notification <nr> minutes before begin" print " rem (search string) \n\tremove matching event" print "\nEXAMPLE USAGE:" print " ", sys.argv[0], "add 3 9-14 Event for 3 days from 9:00 until 14:00" print " ", sys.argv[0], "add 9-14 Event for today from 9:00 until 14:00, with 15 minute before start reminder remind15" print " ", sys.argv[0], "add +3 9-14 Event for 3 days, beginning in 3 days from 9:00 until 14:00" print " ", sys.argv[0], "add +2-5 9-14 Event for 5 days, beginning in 2 days from 9:00 until 14:00" print " ", sys.argv[0], "rem some event\tremove event containing \"some event\"" def show(days): #TODO add det param to show detailed events #get connector data connector = connect() calendar = connector[0] client = connector[1] #get events within date range results = calendar.date_search(datetime.now().date()+timedelta(days=1), datetime.now().date()+timedelta(days=days+1)) #here is a bug when creating event from phone; there are 3 DTSTART tags and the above function parses the first/second one #which belongs to section BEGIN:DAYLIGHT/STANDARD instead correct one: BEGIN:VEVENT (this is maybe connected to winter daylight saving time) #this may not occur on Android newer than 2.3.7, further testing needed for event in results: events = str(event) eventDetail = caldav.Event(client, url=events, parent = calendar).load() for line in eventDetail.data.split('\n'): if "DTSTART" in line: start = line.split(":", 1)[1].strip() elif "DTEND" in line: end = line.split(":", 1)[1].strip() elif "SUMMARY" in line: sum = line.split(":", 1)[1].strip() #true if all day event if (len(start) == 8) and (len(end) == 8): print time.strftime('%d.%m', time.strptime(start, '%Y%m%d')), "-", time.strftime('%d.%m', time.strptime(end, '%Y%m%d')) print " ", sum #else if timerange specified elif (len(start) == 16) and (len(end) == 16): print time.strftime('%d.%m %H:%M', time.localtime(time.mktime(time.strptime(start, '%Y%m%dT%H%M%SZ'))+60*60*calTimeZone)), "-", time.strftime('%d.%m %H:%M', time.localtime(time.mktime(time.strptime(end, '%Y%m%dT%H%M%SZ'))+60*60*calTimeZone)) print " ", sum #else if timerange event created from phone elif (len(start) == 15) and (len(end) == 15): print time.strftime('%d.%m %H:%M', time.localtime(time.mktime(time.strptime(start, '%Y%m%dT%H%M%S')))), "-", time.strftime('%d.%m %H:%M', time.localtime(time.mktime(time.strptime(end, '%Y%m%dT%H%M%S')))) print " ", sum def add(event): #initialize variables reminderData = "" reminderMsg = "" daysAdd = 0 #generate random UID UID = binascii.hexlify(os.urandom(5)) #is this future event? if (event[0][0] == "+"): #remove trailing + event[0] = event[0].replace("+", "", 1) daysAdd = int(event[0].split("-")[0])*3600*24 #try to set event length try: event[0] = event[0].split("-")[1] except: event[0] = event[0].split("-")[0] #is there a reminder to be set? if (event[-1][0:6] == "remind"): if event[-1][6:9].isdigit(): reminderTime = event[-1][6:9] else: reminderTime = defReminderTime reminderData = setAlert(UID, reminderTime) reminderMsg = "with reminder" del event[-1] #create timestamps DTstart = time.strftime('%Y%m%d', time.localtime(time.mktime(time.localtime())+daysAdd)) try: #if only time range is specified if (bool(re.compile('^[0-9]+-+[0-9]+$').match(event[0]+'\n'))): startTime = int(event[0].split("-")[0])-calTimeZone endTime = int(event[0].split("-")[1])-calTimeZone #swap times if needed if (startTime >= endTime): startTime, endTime = endTime, startTime DTstart = time.strftime('%Y%m%dT' + str(startTime).zfill(2) + '0000Z', time.localtime(time.mktime(time.localtime())+daysAdd)) DTend = time.strftime('%Y%m%dT' + str(endTime).zfill(2) + '0000Z', time.localtime(time.mktime(time.localtime())+daysAdd)) del event[0] print "Creating event for today from", startTime+calTimeZone, "-", endTime+calTimeZone, reminderMsg #else if also day range is specified elif (bool(re.compile('^[0-9]+-+[0-9]+$').match(event[1]+'\n')) and int(event[0]) > 0): startTime = int(event[1].split("-")[0])-calTimeZone endTime = int(event[1].split("-")[1])-calTimeZone DTstart = time.strftime('%Y%m%dT' + str(startTime).zfill(2) + '0000Z', time.localtime(time.mktime(time.localtime())+daysAdd)) DTend = time.strftime('%Y%m%dT' + str(endTime).zfill(2) + '0000Z', time.localtime(time.mktime(time.localtime())+3600*24*int(event[0])+daysAdd)) print "Creating event for", event[0], "days", reminderMsg del event[0:2] #else create all day event else: DTend = time.strftime('%Y%m%d', time.localtime(time.mktime(time.localtime())+3600*24*int(event[0])+daysAdd)) print "Creating event for", event[0], "days." del event[0] except: DTend = time.strftime('%Y%m%d', time.localtime(time.mktime(time.localtime())+3600*24*defEventLen)) print "Creating event for 7 days." #current timestamp DTcurr = time.strftime('%Y%m%dT%H%M%SZ', time.localtime()) summary = " ".join(event) #TODO implement (now are these variables not used) location="" description="" category="" #prepare calDav event vcal = """ BEGIN:VCALENDAR VERSION:2.0 PRODID:ownCloud Calendar BEGIN:VEVENT CREATED;VALUE=DATE-TIME:""" + DTcurr + """ UID:""" + UID + """ LAST-MODIFIED;VALUE=DATE-TIME:""" + DTcurr + """ DTSTAMP;VALUE=DATE-TIME:""" + DTcurr + """ SUMMARY:""" + summary + """ DTSTART;VALUE=DATE-TIME:""" + DTstart + """ DTEND;VALUE=DATE-TIME:""" + DTend + """ CLASS:PUBLIC LOCATION:""" + location + """ DESCRIPTION:""" + description + """ CATEGORIES:""" + category + reminderData + """ END:VEVENT END:VCALENDAR """ #get connector data connector = connect() calendar = connector[0] client = connector[1] #create event event = caldav.Event(client, data = vcal, parent = calendar).save() def rem(string): #get connector data connector = connect() calendar = connector[0] client = connector[1] #get all events, search for match #download only events from past 7 days and month from future calEvents = calendar.date_search(datetime.now().date()-timedelta(days=7), datetime.now().date()+timedelta(days=31)) for event in calEvents: eventDetail = caldav.Event(client, url=str(event), parent = calendar).load() match = False for line in eventDetail.data.split('\n'): if "DTSTART" in line: start = line.split(":")[1].strip() if "DTEND" in line: end = line.split(":")[1].strip() #test for match with string in SUMMARY if ("SUMMARY" in line and " ".join(string) in line): sum = line.split(":")[1].strip() match = True if ( match ): #true if all day events if (len(start) == 8) and (len(end) == 8): print "Found matching event:" print " ", time.strftime('%d.%m', time.strptime(start, '%Y%m%d')), "-", time.strftime('%d.%m', time.strptime(end, '%Y%m%d')) print " ", sum answer = raw_input("Remove event? [Y/n]: ") if (bool(re.compile('^[y|Y]').match(answer)) or answer == ""): eventDetail = caldav.Event(client, url=str(event), parent = calendar).delete() #true if event with time range elif (len(start) == 16) and (len(end) == 16): print "Found matching event:" print " ", time.strftime('%d.%m %H:%M', time.localtime(time.mktime(time.strptime(start, '%Y%m%dT%H%M%SZ'))+60*60*calTimeZone)), "-", time.strftime('%d.%m %H:%M', time.localtime(time.mktime(time.strptime(end, '%Y%m%dT%H%M%SZ'))+60*60*calTimeZone)) print " ", sum answer = raw_input("Remove event? [Y/n]: ") if (bool(re.compile('^[y|Y]').match(answer)) or answer == ""): eventDetail = caldav.Event(client, url=str(event), parent = calendar).delete() #true if event was created with phone elif (len(start) == 15) and (len(end) == 15): print "Found matching event:" print " ", time.strftime('%d.%m %H:%M', time.localtime(time.mktime(time.strptime(start, '%Y%m%dT%H%M%S')))), "-", time.strftime('%d.%m %H:%M', time.localtime(time.mktime(time.strptime(end, '%Y%m%dT%H%M%S')))) print " ", sum answer = raw_input("Remove event? [Y/n]: ") if (bool(re.compile('^[y|Y]').match(answer)) or answer == ""): eventDetail = caldav.Event(client, url=str(event), parent = calendar).delete() # # # # # # BEGIN # # # # # #show event for Ndays or default 7 days if no value is specified if (len(sys.argv) >= 2) and (sys.argv[1] == "sh"): try: show(int(sys.argv[2])) except: show(defDaysSh) #add event, syntax .py add [nr] (description) elif (len(sys.argv) > 2) and (sys.argv[1] == "add"): #remove first two arguments del sys.argv[0:2] add(sys.argv) elif (len(sys.argv) > 2) and (sys.argv[1] == "rem"): #remove first two arguments del sys.argv[0:2] rem(sys.argv) else: help() exit(0)
Additional info:
If your password contains special characters, you need to enter them in script in HEX format (to get HEX values see man ascii).
Example:
https://name:pass.d#f@... (you need to replace '.' and '#' characters)
https://name:pass%2ed%23@...
Then edit file /usr/lib/python2.7/site-packages/caldav-0.1.12-py2.7.egg/caldav/davclient.py and add corresponding replace on line 65:
hash = (("%s:%s" % (self.url.username.replace('%40', '@'), self.url.password.replace('%23', '#').replace('%2e', '.')))\ .encode('base64')[:-1])
NOTE: do not use '<' or '>' in name/password, as this is parsed as XML and will cause issues with parsing.
USAGE: ./owcal OPTIONS
sh [days]
show upcoming events for n-days, default is 7
add [days] [hrs] (event summary) [remind]
add event for n-days, default is 7 , "remind<NR>" at the end sets notification <NR> minutes before begin
rem (search string)
remove matching event
EXAMPLE USAGE:
./owcal add 3 9-14 Event for 3 days from 9:00 until 14:00
./owcal add 9-14 Event for today from 9:00 until 14:00, with 15 minute before start reminder remind15
./owcal add +3 9-14 Event for 3 days, beginning in 3 days from 9:00 until 14:00
./owcal add +2-5 9-14 Event for 5 days, beginning in 2 days from 9:00 until 14:00
./owcal rem some event remove event containing "some event"
Troubleshooting:
If you get following error:
Traceback (most recent call last): File "./owcal", line 265, in <module> add(sys.argv) File "./owcal", line 198, in add calendar = connector[0] TypeError: 'NoneType' object has no attribute '__getitem__'check if you have valid calendar name filled in script.
Would it be possible to add a function that removes events older than X days?
ReplyDeleteEasy or not? ;-)
Hi, it should be easy, you just have to specify some date range, e.g. find events between 6 days and one year in the past and delete them all...
Delete-from the rem function
calEvents = calendar.date_search(datetime.now().date()-timedelta(days=365), datetime.now().date()-timedelta(days=6))
Or you can get all events (if you don't want to specify date range) by using
http://pythonhosted.org/caldav/caldav/objects.html#caldav.objects.Calendar.events
and go through all events looking on timestamp and deleting older ones.
Great blog I enjooyed reading
ReplyDelete