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