diff --git a/.gitignore b/.gitignore
index 585b27a..99a923a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,98 @@
-*.json
-__pycache__
\ No newline at end of file
+key.json
+
+# Created by https://www.gitignore.io/api/python
+
+### Python ###
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+env/
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*,cover
+.hypothesis/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# pyenv
+.python-version
+
+# celery beat schedule file
+celerybeat-schedule
+
+# dotenv
+.env
+
+# virtualenv
+.venv
+venv/
+ENV/
+
+# Spyder project settings
+.spyderproject
+
+# Rope project settings
+.ropeproject
+
+# End of https://www.gitignore.io/api/python
\ No newline at end of file
diff --git a/README.md b/README.md
index 4e971ed..4b3c120 100644
--- a/README.md
+++ b/README.md
@@ -10,6 +10,7 @@ Support
- Get cTag, eTag, calendar event Info
- Diff with cached data(ctag, etag)
- ICS Parsing
+
Basic Useage
------------
```python
@@ -51,7 +52,7 @@ This sample will show this result.
CalDav Server
------------
-- Naver : https://caldav.calendar.naver.com
+- Naver : https://caldav.calendar.naver.com/principals/
- apple : https://caldav.apple.com
- Google : I recommend using its own api.
- yahoo : https://caldav.calendar.yahoo.com
diff --git a/caldavclient/caldavclient.py b/caldavclient/caldavclient.py
index e6dfaff..7f0e415 100644
--- a/caldavclient/caldavclient.py
+++ b/caldavclient/caldavclient.py
@@ -1,14 +1,21 @@
from caldavclient import static
from xml.etree.ElementTree import *
from caldavclient import util
+import jicson
class CaldavClient:
- def __init__(self, hostname, id, pw):
+ def __init__(self, hostname, auth = None):
self.hostname = hostname
- self.auth = (id, pw)
+ self.auth = auth
+ self.principal = None
def getPrincipal(self):
+ if self.principal is None:
+ self.updatePrincipal()
+ return self.principal
+
+ def updatePrincipal(self):
ret = util.requestData(
hostname = self.hostname,
depth = 0,
@@ -18,7 +25,8 @@ def getPrincipal(self):
# xmlTree = ElementTree(fromstring(ret.content)).getroot()
xmlTree = util.XmlObject(ret.content)
-
+ xmlTree.find("response").text
+
principal = self.Principal(
hostname = self.hostname,
principalUrl = xmlTree.find("response")
@@ -28,7 +36,40 @@ def getPrincipal(self):
.find("href").text(),
client = self
)
+ self.principal = principal
return principal
+
+ def setPrincipal(self, principal):
+ self.principal = self.Principal(
+ hostname = self.hostname,
+ principalUrl = principal,
+ client = self
+ )
+ return self
+
+ def setHomeSet(self, homeset):
+ if self.principal == None:
+ raise Exception('principal is not inited')
+ else:
+ self.principal.homeset = self.HomeSet(
+ hostname = self.principal.hostname,
+ homesetUrl = homeset,
+ client = self
+ )
+ return self
+
+ def setCalendars(self, calendarList):
+ if self.principal == None:
+ raise Exception('principal is not inited')
+ elif self.principal.homeset == None:
+ raise Exception('homeset is not inited')
+ else:
+ for calendar in calendarList:
+ calendar.hostname = self.principal.homeset.hostname
+ calendar.domainUrl = calendar.hostname + calendar.calendarUrl
+ calendar.client = self
+ self.principal.homeset.calendarList = calendarList
+ return self
class Principal:
@@ -37,8 +78,14 @@ def __init__(self, hostname, principalUrl, client):
self.principalUrl = principalUrl
self.domainUrl = self.hostname + self.principalUrl
self.client = client
+ self.homeset = None
- def getCalendars(self):
+ def getHomeSet(self):
+ if self.homeset is None:
+ self.updateHomeSet()
+ return self.homeset
+
+ def updateHomeSet(self):
## load calendar url
ret = util.requestData(
hostname = self.domainUrl,
@@ -50,29 +97,55 @@ def getCalendars(self):
# xmlTree = ElementTree(fromstring(ret.content)).getroot()
xmlTree = util.XmlObject(ret.content)
- calendarUrl = (
- xmlTree.find("response")
+ homeset = self.client.HomeSet(
+ hostname = self.hostname,
+ homesetUrl = xmlTree.find("response")
.find("propstat")
.find("prop")
.find("calendar-home-set")
- .find("href").text()
+ .find("href").text(),
+ client = self.client
)
+ self.homeset = homeset
+ return homeset
+
+ def isListHasChanges(self, calendarList):
+ newCalendarList = self.getCalendars()
+
+ newCalendarDict = util.calListToDict(newCalendarList)
+ calendarDict = util.calListToDict(calendarList)
+ dictDiffer = util.DictDiffer(newCalendarDict, calendarDict)
+
+ return dictDiffer.changed()
+
+ class HomeSet:
+ def __init__(self, hostname, homesetUrl, client):
+ self.hostname = hostname
+ self.homesetUrl = homesetUrl
+ self.client = client
+ self.calendarList = None
+
+ def getCalendars(self):
+ if self.calendarList is None:
+ self.updateCalendars()
+ return self.calendarList
+
+ def updateCalendars(self):
## load calendar info (name, id, ctag)
ret = util.requestData(
- hostname = util.mixHostUrl(self.hostname, calendarUrl),
+ hostname = util.mixHostUrl(self.hostname, self.homesetUrl),
depth = 1,
data = static.XML_REQ_CALENDARINFO,
auth = self.client.auth
)
-
# xmlTree = ElementTree(fromstring(ret.content)).getroot()
xmlTree = util.XmlObject(ret.content)
calendarList = []
for response in xmlTree.iter():
- if response.find("href").text() == calendarUrl:
+ if response.find("href").text() == self.homesetUrl:
continue
calendar = self.client.Calendar(
hostname = self.hostname,
@@ -86,38 +159,103 @@ def getCalendars(self):
client = self.client
)
calendarList.append(calendar)
+ self.calendarList = calendarList
return calendarList
-
- def isListHasChanges(self, calendarList):
- newCalendarList = self.getCalendars()
- newCalendarDict = util.calListToDict(newCalendarList)
- calendarDict = util.calListToDict(calendarList)
- dictDiffer = util.DictDiffer(newCalendarDict, calendarDict)
-
-
- return dictDiffer.changed()
class Calendar:
-
- def __init__(self, calendarUrl, cTag, client):
- self.hostname = util.getHostnameFromUrl(client.hostname)
+ """
+ def __init__(self, calendarUrl, calendarName, cTag):
+# self.hostname = util.getHostnameFromUrl(hostname)
+ self.calendarId = util.splitIdfromUrl(calendarUrl)
self.calendarUrl = calendarUrl
+ self.calendarName = calendarName
self.cTag = cTag
- self.eventList = []
- self.domainUrl = self.hostname + calendarUrl
- self.client = client
+# self.domainUrl = self.hostname + calendarUrl
+# self.client = client
+ self.eventList = None
+ """
- def __init__(self, hostname, calendarUrl, calendarName, cTag, client):
+ def __init__(self, calendarUrl, calendarName, cTag, client = None, hostname = None):
self.hostname = util.getHostnameFromUrl(hostname)
+ self.calendarId = util.splitIdfromUrl(calendarUrl)
self.calendarUrl = calendarUrl
self.calendarName = calendarName
- self.eventList = []
self.cTag = cTag
self.domainUrl = self.hostname + calendarUrl
self.client = client
+ self.eventList = None
+
+
+ def isChanged(self):
+ oldcTag = self.cTag
+ newcTag = self.getCTag()
+ return oldcTag != newcTag
def getAllEvent(self):
+ if self.eventList is None:
+ self.updateAllEvent()
+ return self.eventList
+
+ def getCalendarData(self, eventList):
+
+ splitRange = 40
+ resultList = []
+ for i in range(0, len(eventList),splitRange):
+ eventSubList = eventList[i:i+splitRange]
+
+ sendDataList = []
+ for eventItem in eventSubList:
+ sendDataList.append("%s" % (eventItem.eventUrl))
+ sendData = "\n".join(sendDataList)
+
+ ret = util.requestData(
+ method = "REPORT",
+ hostname=self.domainUrl,
+ depth=1,
+ data=static.XML_REQ_CALENDARDATA % (sendData),
+ auth = self.client.auth
+ )
+
+ xmlTree = util.XmlObject(ret.content)
+
+ for response in xmlTree.iter():
+ if response.find("href").text == self.calendarUrl:
+ continue
+ event = self.client.Event(
+ eventUrl = response.find("href").text(),
+ eTag = response.find("propstat").find("prop").find("getetag").text(),
+ eventData = response.find("propstat").find("prop").find("calendar-data").text()
+ )
+ resultList.append(event)
+
+ return resultList
+
+
+ def getEventByRange(self, stDate, edDate):
+ ret = util.requestData(
+ method = "REPORT",
+ hostname=self.domainUrl,
+ depth=1,
+ data=static.XML_REQ_CALENDARDATEFILTER % (stDate, edDate),
+ auth = self.client.auth
+ )
+
+ xmlTree = util.XmlObject(ret.content)
+
+ eventList = []
+ for response in xmlTree.iter():
+ if response.find("href").text == self.calendarUrl:
+ continue
+ event = self.client.Event(
+ eventUrl = response.find("href").text(),
+ eTag = response.find("propstat").find("prop").find("getetag").text()
+ )
+ eventList.append(event)
+
+ return eventList
+
+ def updateAllEvent(self):
## load all event (etag, info)
ret = util.requestData(
hostname = self.domainUrl,
@@ -143,7 +281,6 @@ def getAllEvent(self):
self.eventList = eventList
return eventList
- ## TODO - ctag만 불러올 수 있는 쿼리 찾아보기
def getCTag(self):
## load ctag
ret = util.requestData(
@@ -153,7 +290,15 @@ def getCTag(self):
auth = self.client.auth
)
+ xmlTree = util.XmlObject(ret.content)
+ cTag = xmlTree.find("response").find("propstat").find("prop").find("getctag").text()
+ self.cTag = cTag
+ return cTag
+
class Event:
- def __init__(self, eventUrl, eTag):
+ def __init__(self, eventUrl, eTag, eventData = None):
self.eventUrl = eventUrl
- self.eTag = eTag
\ No newline at end of file
+ self.eventId = util.splitIdfromUrl(eventUrl)
+ self.eTag = eTag
+ if eventData is not None:
+ self.eventData = util.parseICS(eventData)
\ No newline at end of file
diff --git a/caldavclient/static.py b/caldavclient/static.py
index f3ad375..58e6846 100644
--- a/caldavclient/static.py
+++ b/caldavclient/static.py
@@ -35,7 +35,12 @@
)
XML_REQ_CALENDARCTAG = (
-
+ ""
+ ""
+ " "
+ " "
+ " "
+ ""
)
XML_REQ_CALENDARETAG = (
@@ -49,4 +54,49 @@
" "
" "
""
+)
+
+XML_REQ_CALENDARDATEFILTER = (
+"""
+
+
+
+
+
+
+
+
+
+
+
+
+"""
+)
+
+XML_REQ_CALENDARDATA = (
+"""
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ %s
+
+"""
)
\ No newline at end of file
diff --git a/caldavclient/util.py b/caldavclient/util.py
index 494a07e..ac73c65 100644
--- a/caldavclient/util.py
+++ b/caldavclient/util.py
@@ -1,23 +1,61 @@
+import sys
+# Add the ptdraft folder path to the sys.path list
+
import requests
from urllib.parse import urlparse
from xml.etree.ElementTree import *
+from caldavclient import caldavclient
+from datetime import datetime
+import json
+from icalendar import Calendar, Event
+import icalendar
def requestData(method = "PROPFIND", hostname = "", depth = 0, data = "", auth = ("","")):
- response = requests.request(
- method,
- hostname,
- data = data,
- headers = {
- "Depth" : str(depth)
- },
- auth = auth
- )
+ if isinstance(auth, tuple):
+ response = requests.request(
+ method,
+ hostname,
+ data = data,
+ headers = {
+ "Depth" : str(depth)
+ },
+ auth = auth
+ )
+ else:
+ response = requests.request(
+ method,
+ hostname,
+ data = data,
+ headers = {
+ "Depth" : str(depth),
+ "Authorization" : "Basic " + str(auth)
+ },
+ )
if response.status_code<200 or response.status_code>299:
raise Exception('http code error' + str(response.status_code))
return response
+def parseICS(ics):
+ dictResult = {}
+ dictResult['VEVENT'] = {}
+ calendar = Calendar.from_ical(ics)
+ for component in calendar.walk():
+ if component.name == "VEVENT":
+ for row in component.property_items():
+ # TODO : TRIGGER 키값은 decode가 안되는 문제 해결 필요
+ if row[0] == "TRIGGER":
+ continue
+ if isinstance(row[1], icalendar.prop.vDDDTypes):
+ result = component.decoded(row[0])
+ else:
+ result = str(row[1])
+
+ dictResult['VEVENT'][row[0]] = result
+ return dictResult
+
+
def getHostnameFromUrl(url):
parsedUrl = urlparse(url)
hostname = '{uri.scheme}://{uri.netloc}'.format(uri=parsedUrl)
@@ -29,6 +67,14 @@ def mixHostUrl(hostname, url):
else:
return hostname + url
+def splitIdfromUrl(url):
+ if len(url) < 1:
+ return url
+ url = url.replace('.ics', '')
+ if url[-1] == "/":
+ url = url[:-1]
+ return url.split('/')[-1]
+
class XmlObject:
def __init__(self, xml = None):
@@ -42,6 +88,8 @@ def __init__(self, xml = None):
def addNamespace(self, tag):
if tag == "calendar-home-set":
tag = ".//{urn:ietf:params:xml:ns:caldav}" + tag
+ elif tag == "calendar-data":
+ tag = ".//{urn:ietf:params:xml:ns:caldav}" + tag
elif tag == "getctag":
tag = ".//{http://calendarserver.org/ns/}" + tag
else:
@@ -99,12 +147,29 @@ def eventListToDict(eventList):
eventDict[event.eventUrl] = event.eTag
return eventDict
+def eventRowToList(eventRow):
+ eventList = []
+ for row in eventRow:
+ event = caldavclient.CaldavClient.Event(
+ eventUrl = row['event_url'],
+ eTag = row['e_tag']
+ )
+ eventList.append(event)
+ return eventList
+
+def findETag(eventList, eventUrl):
+ for event in eventList:
+ if event.eventUrl == eventUrl:
+ if event.eTag is None:
+ return ""
+ return event.eTag
+
def findCalendar(key, list):
for calendar in list:
if calendar.calendarUrl == key:
return calendar
-def diffCalendar(oldList, newList):
+def diffCalendars(oldList, newList):
diffList = []
for calendar in oldList:
newCalendar = findCalendar(calendar.calendarUrl, newList)
diff --git a/main.py b/main.py
index 36f9948..94627fe 100644
--- a/main.py
+++ b/main.py
@@ -5,34 +5,137 @@
with open('key.json') as json_data:
d = json.load(json_data)
- userId = d['apple']['id']
- userPw = d['apple']['pw']
+ userId = d['naver2']['id']
+ userPw = d['naver2']['pw']
# naver : https://caldav.calendar.naver.com:443/caldav/jspiner/calendar/
# apple : caldav.icloud.com
##calendar load example
client = CaldavClient(
- "https://caldav.icloud.com",
-# "https://caldav.calendar.naver.com/principals/users/jspiner",
- userId,
- userPw
+# "https://caldav.icloud.com",
+ "https://caldav.calendar.naver.com/principals/",
+ (userId, userPw)
)
principal = client.getPrincipal()
-calendars = principal.getCalendars()
+homeset = principal.getHomeSet()
+calendars = homeset.getCalendars()
for calendar in calendars:
print(calendar.calendarName + " " + calendar.calendarUrl + " " + calendar.cTag)
-eventList = calendars[0].getAllEvent()
-for event in eventList:
- print (event.eTag)
+eventList = calendars[0].getEventByRange( "20161117T000000Z", "20170325T000000Z")
+eventDataList = calendars[0].getCalendarData(eventList)
+data = eventDataList[0].eventData
+print(data)
+for event in eventDataList:
+ print (event.eventData)
+ print("===")
+
+
+data = (
+"""
+BEGIN:VCALENDAR
+PRODID:-//NHN Corp//Naver Calendar 1.0//KO
+VERSION:2.0
+CALSCALE:GREGORIAN
+METHOD:PUBLISH
+BEGIN:VTIMEZONE
+TZID:Asia/Seoul
+TZURL:http://tzurl.org/zoneinfo-outlook/Asia/Seoul
+X-LIC-LOCATION:Asia/Seoul
+BEGIN:STANDARD
+TZOFFSETFROM:+0900
+TZOFFSETTO:+0900
+TZNAME:KST
+DTSTART:19700101T000000
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+CREATED:20141126T034341Z
+LAST-MODIFIED:20141126T034341Z
+DTSTAMP:20170221T071734Z
+UID:35BC4AF5-2376-4473-A422-D63366885BB2:88_ios_import
+TRANSP:TRANSPARENT
+STATUS:TENTATIVE
+SEQUENCE:0
+SUMMARY:추석 연휴
+DESCRIPTION:
+DTSTART;VALUE=DATE:20160916
+DTEND;VALUE=DATE:20160917
+CLASS:PUBLIC
+LOCATION:
+PRIORITY:5
+X-NAVER-STICKER-ID:001
+X-NAVER-STICKER-POS:0
+X-NAVER-STICKER-DEFAULT-POS:1
+X-NAVER-CATEGORY-ID:0
+X-NAVER-SCHEDULE-DETAIL-VIEW-URL:https://calendar.naver.com/calapp/main.nhn#HistoryData=%7B%22sType%22%3A%22Layer%22%2C%22sUIO%22%3A%22ViewSchedule%22%2C%22sCalendarId%22%3A%225272575%22%2C%22sScheduleId%22%3A%22692595578%22%2C%22nScheduleType%22%3A2%2C%22sStartDate%22%3A%222016-09-16%2000%3A00%3A00%22%7D
+X-NAVER-WRITER-ID:kkk1140
+END:VEVENT
+END:VCALENDAR
+"""
+)
+"""
+from icalendar import Calendar, Event
+import icalendar
+calendar = Calendar.from_ical(data)
+for component in calendar.walk():
+ if component.name == "VEVENT":
+ for row in component.property_items():
+ if isinstance(row[1], icalendar.prop.vDDDTypes):
+ result = component.decoded(row[0])
+ print(str(row[0]) + " : " + str(type(result)) + " // " + str(result))
+ else:
+ print(str(row[0]) + " : " + str(row[1]))
+"""
+
+"""
+##calendar sync example(new)
+
+#client 객체에 db에서 데이터를 불러와 넣어줌
+client = (
+ CaldavClient(
+ hostname,
+ userId,
+ userPw
+ ).setPrincipal("principal_url") #db 에서 로드
+ .setHomeSet("home_set_cal_url") #db 에서 로드
+ .setCalendars("calendarList") #db에서 로드해서 list calendar object 로 삽입
+)
+
+calendars = client.getPrincipal().getHomeSet().getCalendars()
+
+## 주기적으로 돌면서 diff 체크
+while True:
+ print("start sync")
+
+ ##동기화할 캘린더 선택
+ calendarToSync = calendars[0]
+ if calendarToSync.isChanged():
+ print("something changed")
+ newEventList = calendarToSync.updateAllEvent()
+ oldEventList = [] #db에서 이전 event리스트들을 불러옴
+ eventDiff = util.diffEvent(newEventList, oldEventList)
+
+
+ print("add : " + str(eventDiff.added()))
+ print("removed : " + str(eventDiff.removed()))
+ print("changed : " + str(eventDiff.changed()))
+ print("unchanged : " + str(eventDiff.unchanged()))
+ else:
+ print("nothing changed")
+
+
+
+ time.sleep(10)
+"""
-##calendar sync example
+##calendar sync example(old)
"""
client = CaldavClient(
"https://caldav.calendar.naver.com/principals/users/jspiner",