Added possibility to update calendar
This commit is contained in:
@@ -27,8 +27,11 @@ def parse_args(arglist):
|
|||||||
parser = argparse.ArgumentParser(description='Infomentor Grabber and Notifier')
|
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('--nolog', action='store_true', help='print log instead of logging to file')
|
||||||
parser.add_argument('--adduser', type=str, help='add user')
|
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('--addpushover', type=str, help='add pushover')
|
||||||
parser.add_argument('--addmail', type=str, help='add mail')
|
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)
|
args = parser.parse_args(arglist)
|
||||||
return args
|
return args
|
||||||
|
|
||||||
@@ -62,6 +65,32 @@ def add_pushover(username):
|
|||||||
user.notification = model.Notification(ntype=model.Notification.Types.PUSHOVER, info=id)
|
user.notification = model.Notification(ntype=model.Notification.Types.PUSHOVER, info=id)
|
||||||
session.commit()
|
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):
|
def add_mail(username):
|
||||||
session = db.get_db()
|
session = db.get_db()
|
||||||
user = session.query(model.User).filter(model.User.name == username).one_or_none()
|
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 = informer.Informer(user, im, logger=logger)
|
||||||
i.update_news()
|
i.update_news()
|
||||||
i.update_homework()
|
i.update_homework()
|
||||||
|
i.update_calendar()
|
||||||
statusinfo['ok'] = True
|
statusinfo['ok'] = True
|
||||||
statusinfo['degraded'] = False
|
statusinfo['degraded'] = False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -124,6 +154,8 @@ def notify_users():
|
|||||||
|
|
||||||
def main():
|
def main():
|
||||||
args = parse_args(sys.argv[1:])
|
args = parse_args(sys.argv[1:])
|
||||||
|
if args.test:
|
||||||
|
return
|
||||||
if args.nolog:
|
if args.nolog:
|
||||||
logtoconsole()
|
logtoconsole()
|
||||||
else:
|
else:
|
||||||
@@ -135,10 +167,14 @@ def main():
|
|||||||
if not lock.aquire():
|
if not lock.aquire():
|
||||||
logger.info('EXITING - PREVIOUS IS RUNNING')
|
logger.info('EXITING - PREVIOUS IS RUNNING')
|
||||||
raise Exception()
|
raise Exception()
|
||||||
if args.adduser:
|
if args.addfake:
|
||||||
|
add_fake(args.addfake)
|
||||||
|
elif args.adduser:
|
||||||
add_user(args.adduser)
|
add_user(args.adduser)
|
||||||
elif args.addpushover:
|
elif args.addpushover:
|
||||||
add_pushover(args.addpushover)
|
add_pushover(args.addpushover)
|
||||||
|
elif args.addcalendar:
|
||||||
|
add_calendar(args.addcalendar)
|
||||||
else:
|
else:
|
||||||
notify_users()
|
notify_users()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -307,6 +307,15 @@ class Infomentor(object):
|
|||||||
)
|
)
|
||||||
return self.get_json_return()
|
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):
|
def get_homework(self, offset=0):
|
||||||
self.logger.info('fetching homework')
|
self.logger.info('fetching homework')
|
||||||
startofweek = self._get_start_of_week(offset)
|
startofweek = self._get_start_of_week(offset)
|
||||||
@@ -376,7 +385,7 @@ class Infomentor(object):
|
|||||||
def _get_week_dates(self, offset=0, weeks=1):
|
def _get_week_dates(self, offset=0, weeks=1):
|
||||||
weekoffset = datetime.timedelta(days=7*offset)
|
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))
|
endofweek = startofweek + datetime.timedelta(days=5+7*(weeks-1))
|
||||||
|
|
||||||
startofweek += weekoffset
|
startofweek += weekoffset
|
||||||
@@ -384,7 +393,7 @@ class Infomentor(object):
|
|||||||
|
|
||||||
now = datetime.datetime.now()
|
now = datetime.datetime.now()
|
||||||
utctime = datetime.datetime.utcnow()
|
utctime = datetime.datetime.utcnow()
|
||||||
utcoffset = (now.tm_hour - utctime.tm_hour)*60
|
utcoffset = (now.hour - utctime.hour)*60
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'UTCOffset': utcoffset,
|
'UTCOffset': utcoffset,
|
||||||
|
|||||||
127
infomentor/icloudcalendar.py
Normal file
127
infomentor/icloudcalendar.py
Normal 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
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
from infomentor import model, db
|
from infomentor import model, db, icloudcalendar
|
||||||
import logging
|
import logging
|
||||||
import uuid
|
import uuid
|
||||||
import os
|
import os
|
||||||
@@ -7,6 +7,7 @@ import dateparser
|
|||||||
import datetime
|
import datetime
|
||||||
import math
|
import math
|
||||||
import pushover
|
import pushover
|
||||||
|
from icalendar import Event, vDate, Calendar
|
||||||
pushover.init('***REMOVED***')
|
pushover.init('***REMOVED***')
|
||||||
|
|
||||||
class Informer(object):
|
class Informer(object):
|
||||||
@@ -19,7 +20,7 @@ class Informer(object):
|
|||||||
self.im = im
|
self.im = im
|
||||||
|
|
||||||
def send_status_update(self, text):
|
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:
|
try:
|
||||||
if self.user.notification.ntype == model.Notification.Types.PUSHOVER:
|
if self.user.notification.ntype == model.Notification.Types.PUSHOVER:
|
||||||
pushover.Client(self.user.notification.info).send_message(
|
pushover.Client(self.user.notification.info).send_message(
|
||||||
@@ -56,6 +57,9 @@ class Informer(object):
|
|||||||
self._notify_news_pushover(news)
|
self._notify_news_pushover(news)
|
||||||
elif self.user.notification.ntype == model.Notification.Types.EMAIL:
|
elif self.user.notification.ntype == model.Notification.Types.EMAIL:
|
||||||
self._notify_news_mail(news)
|
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:
|
else:
|
||||||
raise Exception('invalid notification')
|
raise Exception('invalid notification')
|
||||||
pass
|
pass
|
||||||
@@ -150,6 +154,9 @@ class Informer(object):
|
|||||||
self._notify_hw_pushover(hw)
|
self._notify_hw_pushover(hw)
|
||||||
elif self.user.notification.ntype == model.Notification.Types.EMAIL:
|
elif self.user.notification.ntype == model.Notification.Types.EMAIL:
|
||||||
self._notify_hw_mail(hw)
|
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:
|
else:
|
||||||
raise Exception('invalid notification')
|
raise Exception('invalid notification')
|
||||||
pass
|
pass
|
||||||
@@ -210,3 +217,37 @@ class Informer(object):
|
|||||||
s.login('infomentor@09a.de', '***REMOVED***')
|
s.login('infomentor@09a.de', '***REMOVED***')
|
||||||
s.send_message(mail)
|
s.send_message(mail)
|
||||||
s.quit()
|
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())
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ class User(ModelBase):
|
|||||||
enc_password = Column(String)
|
enc_password = Column(String)
|
||||||
notification = relationship("Notification", back_populates="user", uselist=False)
|
notification = relationship("Notification", back_populates="user", uselist=False)
|
||||||
apistatus = relationship("ApiStatus", back_populates="user", uselist=False)
|
apistatus = relationship("ApiStatus", back_populates="user", uselist=False)
|
||||||
|
icalendar = relationship("ICloudCalendar", back_populates="user", uselist=False)
|
||||||
wantstatus = Column(Boolean)
|
wantstatus = Column(Boolean)
|
||||||
homeworks = relationship("Homework", back_populates="user")
|
homeworks = relationship("Homework", back_populates="user")
|
||||||
news = relationship("News",back_populates="user")
|
news = relationship("News",back_populates="user")
|
||||||
@@ -63,6 +64,7 @@ class Notification(ModelBase):
|
|||||||
'''Supported notification types'''
|
'''Supported notification types'''
|
||||||
PUSHOVER = 1
|
PUSHOVER = 1
|
||||||
EMAIL = 2
|
EMAIL = 2
|
||||||
|
FAKE = 3
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True)
|
id = Column(Integer, primary_key=True)
|
||||||
user_id = Column(Integer, ForeignKey('users.id'))
|
user_id = Column(Integer, ForeignKey('users.id'))
|
||||||
@@ -149,3 +151,39 @@ class ApiStatus(ModelBase):
|
|||||||
return "<ApiStatus(ok='%s', NOKs='%d', info='%s')>" % (
|
return "<ApiStatus(ok='%s', NOKs='%d', info='%s')>" % (
|
||||||
self.ok, self.degraded_count, self.info)
|
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)
|
||||||
|
|
||||||
|
|||||||
2
setup.py
2
setup.py
@@ -8,5 +8,5 @@ setup(
|
|||||||
author_email = 'matthias@bilger.info',
|
author_email = 'matthias@bilger.info',
|
||||||
description = 'grab infomentor news and push or mail them',
|
description = 'grab infomentor news and push or mail them',
|
||||||
packages = find_packages(),
|
packages = find_packages(),
|
||||||
install_requires = ['pycrypto', 'request', 'sqlalchemy', 'dateparser', 'python-pushover' ],
|
install_requires = ['pycrypto', 'request', 'sqlalchemy', 'dateparser', 'python-pushover', 'caldav', 'bs4', 'icalendar' ],
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user