Added possibility to update calendar

This commit is contained in:
2019-05-01 17:44:58 +02:00
parent 9d5c153d8d
commit 5136add2d6
6 changed files with 257 additions and 6 deletions

View File

@@ -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:

View File

@@ -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,

View File

@@ -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'''<?xml version="1.0" encoding="utf-8"?><propfind xmlns='DAV:'>'''
u'''<prop><current-user-principal/></prop></propfind>'''
)
propfind_calendar_home_set = (
u'''<?xml version="1.0" encoding="utf-8"?><propfind xmlns='DAV:' '''
u'''xmlns:cd='urn:ietf:params:xml:ns:caldav'><prop>'''
u'''<cd:calendar-home-set/></prop></propfind>'''
)
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

View File

@@ -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())

View File

@@ -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 "<ApiStatus(ok='%s', NOKs='%d', info='%s')>" % (
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 "<ICloudCalendar(user='%s' cal='%s')>" % (
self.icloud_user, self.calendarname)

View File

@@ -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' ],
)