diff --git a/infomentor/__main__.py b/infomentor/__main__.py
index 2270d2d..e7b90f6 100644
--- a/infomentor/__main__.py
+++ b/infomentor/__main__.py
@@ -27,8 +27,11 @@ def parse_args(arglist):
parser = argparse.ArgumentParser(description='Infomentor Grabber and Notifier')
parser.add_argument('--nolog', action='store_true', help='print log instead of logging to file')
parser.add_argument('--adduser', type=str, help='add user')
+ parser.add_argument('--addfake', type=str, help='add fake')
parser.add_argument('--addpushover', type=str, help='add pushover')
parser.add_argument('--addmail', type=str, help='add mail')
+ parser.add_argument('--addcalendar', type=str, help='add icloud calendar')
+ parser.add_argument('--test', action='store_true', help='test')
args = parser.parse_args(arglist)
return args
@@ -62,6 +65,32 @@ def add_pushover(username):
user.notification = model.Notification(ntype=model.Notification.Types.PUSHOVER, info=id)
session.commit()
+def add_fake(username):
+ session = db.get_db()
+ user = session.query(model.User).filter(model.User.name == username).one_or_none()
+ if user is None:
+ print('user does not exist')
+ return
+ else:
+ print(f'Adding FAKE for user: {username}')
+ user.notification = model.Notification(ntype=model.Notification.Types.FAKE, info='')
+ session.commit()
+
+def add_calendar(username):
+ session = db.get_db()
+ user = session.query(model.User).filter(model.User.name == username).one_or_none()
+ if user is None:
+ print('user does not exist')
+ return
+ else:
+ print(f'Adding icloud calendar for user: {username}')
+ id = input('Apple ID: ')
+ import getpass
+ password = getpass.getpass(prompt='iCloud Password: ')
+ calendar = input('Calendar: ')
+ user.icalendar = model.ICloudCalendar(icloud_user=id, password=password, calendarname=calendar)
+ session.commit()
+
def add_mail(username):
session = db.get_db()
user = session.query(model.User).filter(model.User.name == username).one_or_none()
@@ -94,6 +123,7 @@ def notify_users():
i = informer.Informer(user, im, logger=logger)
i.update_news()
i.update_homework()
+ i.update_calendar()
statusinfo['ok'] = True
statusinfo['degraded'] = False
except Exception as e:
@@ -124,6 +154,8 @@ def notify_users():
def main():
args = parse_args(sys.argv[1:])
+ if args.test:
+ return
if args.nolog:
logtoconsole()
else:
@@ -135,10 +167,14 @@ def main():
if not lock.aquire():
logger.info('EXITING - PREVIOUS IS RUNNING')
raise Exception()
- if args.adduser:
+ if args.addfake:
+ add_fake(args.addfake)
+ elif args.adduser:
add_user(args.adduser)
elif args.addpushover:
add_pushover(args.addpushover)
+ elif args.addcalendar:
+ add_calendar(args.addcalendar)
else:
notify_users()
except Exception as e:
diff --git a/infomentor/connector.py b/infomentor/connector.py
index 4ca2d9b..8695bdd 100644
--- a/infomentor/connector.py
+++ b/infomentor/connector.py
@@ -307,6 +307,15 @@ class Infomentor(object):
)
return self.get_json_return()
+ def get_event(self, eventid):
+ self.logger.info('fetching calendar entry')
+ data = {'id': eventid}
+ self._do_post(
+ self._mim_url('Calendar/Calendar/getEntry'),
+ data=data
+ )
+ return self.get_json_return()
+
def get_homework(self, offset=0):
self.logger.info('fetching homework')
startofweek = self._get_start_of_week(offset)
@@ -376,7 +385,7 @@ class Infomentor(object):
def _get_week_dates(self, offset=0, weeks=1):
weekoffset = datetime.timedelta(days=7*offset)
- startofweek = self._get_start_of_weekdays()
+ startofweek = self._get_start_of_week()
endofweek = startofweek + datetime.timedelta(days=5+7*(weeks-1))
startofweek += weekoffset
@@ -384,7 +393,7 @@ class Infomentor(object):
now = datetime.datetime.now()
utctime = datetime.datetime.utcnow()
- utcoffset = (now.tm_hour - utctime.tm_hour)*60
+ utcoffset = (now.hour - utctime.hour)*60
data = {
'UTCOffset': utcoffset,
diff --git a/infomentor/icloudcalendar.py b/infomentor/icloudcalendar.py
new file mode 100644
index 0000000..c942f4a
--- /dev/null
+++ b/infomentor/icloudcalendar.py
@@ -0,0 +1,127 @@
+from datetime import datetime
+import sys
+
+from bs4 import BeautifulSoup
+import caldav
+from caldav.elements import dav, cdav
+from lxml import etree
+import requests
+from requests.auth import HTTPBasicAuth
+
+class iCloudConnector(object):
+
+ icloud_url = "https://caldav.icloud.com"
+ username = None
+ password = None
+ propfind_principal = (
+ u''''''
+ u''''''
+ )
+ propfind_calendar_home_set = (
+ u''''''
+ u''''''
+ )
+
+ def __init__(self, username, password, **kwargs):
+ self.username = username
+ self.password = password
+ if 'icloud_url' in kwargs:
+ self.icloud_url = kwargs['icloud_url']
+ self.discover()
+ self.get_calendars()
+
+ # discover: connect to icloud using the provided credentials and discover
+ #
+ # 1. The principal URL
+ # 2 The calendar home URL
+ #
+ # These URL's vary from user to user
+ # once doscivered, these can then be used to manage calendars
+
+ def discover(self):
+ # Build and dispatch a request to discover the prncipal us for the
+ # given credentials
+ headers = {
+ 'Depth': '1',
+ }
+ auth = HTTPBasicAuth(self.username, self.password)
+ principal_response = requests.request(
+ 'PROPFIND',
+ self.icloud_url,
+ auth=auth,
+ headers=headers,
+ data=self.propfind_principal.encode('utf-8')
+ )
+ if principal_response.status_code != 207:
+ print('Failed to retrieve Principal: ',
+ principal_response.status_code)
+ exit(-1)
+ # Parse the resulting XML response
+ soup = BeautifulSoup(principal_response.content, 'lxml')
+ self.principal_path = soup.find(
+ 'current-user-principal'
+ ).find('href').get_text()
+ discovery_url = self.icloud_url + self.principal_path
+ # Next use the discovery URL to get more detailed properties - such as
+ # the calendar-home-set
+ home_set_response = requests.request(
+ 'PROPFIND',
+ discovery_url,
+ auth=auth,
+ headers=headers,
+ data=self.propfind_calendar_home_set.encode('utf-8')
+ )
+ if home_set_response.status_code != 207:
+ print('Failed to retrieve calendar-home-set',
+ home_set_response.status_code)
+ exit(-1)
+ # And then extract the calendar-home-set URL
+ soup = BeautifulSoup(home_set_response.content, 'lxml')
+ self.calendar_home_set_url = soup.find(
+ 'href',
+ attrs={'xmlns':'DAV:'}
+ ).get_text()
+
+ # get_calendars
+ # Having discovered the calendar-home-set url
+ # we can create a local object to control calendars (thin wrapper around
+ # CALDAV library)
+ def get_calendars(self):
+ self.caldav = caldav.DAVClient(self.calendar_home_set_url,
+ username=self.username,
+ password=self.password)
+ self.principal = self.caldav.principal()
+ self.calendars = self.principal.calendars()
+
+ def get_named_calendar(self, name):
+
+ if len(self.calendars) > 0:
+ for calendar in self.calendars:
+ properties = calendar.get_properties([dav.DisplayName(), ])
+ display_name = properties['{DAV:}displayname']
+ if display_name == name:
+ return calendar
+ return None
+
+ def create_calendar(self,name):
+ return self.principal.make_calendar(name=name)
+
+ def delete_all_events(self,calendar):
+ for event in calendar.events():
+ event.delete()
+ return True
+
+ def create_events_from_ical(self, ical):
+ # to do
+ pass
+
+ def create_simple_timed_event(self,start_datetime, end_datetime, summary,
+ description):
+ # to do
+ pass
+
+ def create_simple_dated_event(self,start_datetime, end_datetime, summary,
+ description):
+ # to do
+ pass
diff --git a/infomentor/informer.py b/infomentor/informer.py
index f98f830..810da61 100755
--- a/infomentor/informer.py
+++ b/infomentor/informer.py
@@ -1,4 +1,4 @@
-from infomentor import model, db
+from infomentor import model, db, icloudcalendar
import logging
import uuid
import os
@@ -7,6 +7,7 @@ import dateparser
import datetime
import math
import pushover
+from icalendar import Event, vDate, Calendar
pushover.init('***REMOVED***')
class Informer(object):
@@ -19,7 +20,7 @@ class Informer(object):
self.im = im
def send_status_update(self, text):
- '''In case something unexpected happends and the user has activated the feature to get notified about it, this will send out the information'''
+ '''In case something unexpected happends and the user has activated the feature to get notified about it, this will send out the information'''
try:
if self.user.notification.ntype == model.Notification.Types.PUSHOVER:
pushover.Client(self.user.notification.info).send_message(
@@ -56,6 +57,9 @@ class Informer(object):
self._notify_news_pushover(news)
elif self.user.notification.ntype == model.Notification.Types.EMAIL:
self._notify_news_mail(news)
+ elif self.user.notification.ntype == model.Notification.Types.FAKE:
+ with open('{}.txt'.format(self.user.name), 'a+') as f:
+ f.write('Notification:\n---------8<-------\n{}\n---------8<-------\n\n'.format(news.content))
else:
raise Exception('invalid notification')
pass
@@ -150,6 +154,9 @@ class Informer(object):
self._notify_hw_pushover(hw)
elif self.user.notification.ntype == model.Notification.Types.EMAIL:
self._notify_hw_mail(hw)
+ elif self.user.notification.ntype == model.Notification.Types.FAKE:
+ with open('{}.txt'.format(self.user.name), 'a+') as f:
+ f.write('Notification:\n---------8<-------\n{}\n---------8<-------\n\n'.format(hw.text))
else:
raise Exception('invalid notification')
pass
@@ -210,3 +217,37 @@ class Informer(object):
s.login('infomentor@09a.de', '***REMOVED***')
s.send_message(mail)
s.quit()
+
+ def update_calendar(self):
+ session = db.get_db()
+ print(self.user.icalendar)
+ if self.user.icalendar is None:
+ return
+ icx = icloudcalendar.iCloudConnector(self.user.icalendar.icloud_user, self.user.icalendar.password)
+ cname = self.user.icalendar.calendarname
+ cal = icx.get_named_calendar(cname)
+ if not cal:
+ cal = icx.create_calendar(cname)
+ calentries = self.im.get_calendar(weeks=4)
+ known_entries = {}
+ for calevent in cal.events():
+ if calevent.data is None:
+ continue
+ uid = re.findall('UID:(.*)', calevent.data)[0]
+ known_entries[uid] = calevent
+
+ for entry in calentries:
+ self.logger.debug(entry)
+ event_details = self.im.get_event(entry['id'])
+ self.logger.debug(event_details)
+ calend = Calendar()
+ event = Event()
+ event.add('uid', 'infomentor_{}'.format(entry['id']))
+ event.add('summary', entry['title'])
+ event.add('description', event_details['notes'])
+ event.add('dtstart', vDate(dateparser.parse(entry['start'])))
+ event.add('dtend', vDate(dateparser.parse(entry['end'])))
+ self.logger.debug(event.to_ical())
+ calend.add_component(event)
+ cal.add_event(calend.to_ical())
+
diff --git a/infomentor/model.py b/infomentor/model.py
index 72afc06..7714d9d 100755
--- a/infomentor/model.py
+++ b/infomentor/model.py
@@ -25,6 +25,7 @@ class User(ModelBase):
enc_password = Column(String)
notification = relationship("Notification", back_populates="user", uselist=False)
apistatus = relationship("ApiStatus", back_populates="user", uselist=False)
+ icalendar = relationship("ICloudCalendar", back_populates="user", uselist=False)
wantstatus = Column(Boolean)
homeworks = relationship("Homework", back_populates="user")
news = relationship("News",back_populates="user")
@@ -63,6 +64,7 @@ class Notification(ModelBase):
'''Supported notification types'''
PUSHOVER = 1
EMAIL = 2
+ FAKE = 3
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey('users.id'))
@@ -149,3 +151,39 @@ class ApiStatus(ModelBase):
return "" % (
self.ok, self.degraded_count, self.info)
+class ICloudCalendar(ModelBase):
+ '''An icloud account with a calendar name'''
+ __tablename__ = 'icloud_calendar'
+
+ id = Column(Integer, primary_key=True)
+ user_id = Column(Integer, ForeignKey('users.id'))
+ icloud_user = Column(String)
+ icloud_pwd = Column(String)
+ calendarname = Column(String)
+ user = relationship("User", back_populates="icalendar", uselist=False)
+
+ def __init__(self, *args, **kwargs):
+ self._setup_cipher()
+ super().__init__(*args, **kwargs)
+
+ def _setup_cipher(self):
+ if not hasattr(self, 'cipher'):
+ aeskey = hashlib.sha256(_PASSWORD_SECRET_KEY.encode()).digest()
+ self.cipher = AES.new(aeskey,AES.MODE_ECB)
+
+ @property
+ def password(self):
+ self._setup_cipher()
+ decoded = self.cipher.decrypt(base64.b64decode(self.icloud_pwd))
+ return unpad(decoded)
+
+ @password.setter
+ def password(self, value):
+ self._setup_cipher()
+ encoded = base64.b64encode(self.cipher.encrypt(pad(value)))
+ self.icloud_pwd = encoded
+
+ def __repr__(self):
+ return "" % (
+ self.icloud_user, self.calendarname)
+
diff --git a/setup.py b/setup.py
index 347a495..c53cd1b 100644
--- a/setup.py
+++ b/setup.py
@@ -8,5 +8,5 @@ setup(
author_email = 'matthias@bilger.info',
description = 'grab infomentor news and push or mail them',
packages = find_packages(),
- install_requires = ['pycrypto', 'request', 'sqlalchemy', 'dateparser', 'python-pushover' ],
+ install_requires = ['pycrypto', 'request', 'sqlalchemy', 'dateparser', 'python-pushover', 'caldav', 'bs4', 'icalendar' ],
)