Commit 3ab30c41 authored by erdnaxe's avatar erdnaxe 🎇

Merge branch 'caldav' into 'master'


See merge request erdnaxe/saphsync!2
parents 180f58bc d74090db
url: ''
ue_101: ['TD_3', 'TP_1', 'BE_1']
ue_102: ['TD_3', 'TP_1', 'BE_1']
......@@ -10,10 +10,3 @@ selected_groups:
ue_107: ['TD_3', 'TP_1', 'BE_1']
ue_112: ['TD_1', 'TP_1', 'BE_1']
ue_113: ['TD_1', 'TP_1', 'BE_1']
base_url: ''
- 'PUT_YOUR_USERNAME/Calendar/personal.ics'
- 'OTHER_USERNAME_TO_SUSCRIBE/Calendar/example.ics'
from urllib.parse import urljoin
import keyring
import yaml
class Configuration:
"""Regroup app configuration"""
def __init__(self, yaml_file):
with open(yaml_file, 'r') as f:
config = yaml.load(f)
self.base_url = config['base_url']
self.calendars_url = [urljoin(self.base_url, r) for r in config['calendars_url']]
self.login = config['login']
self.selected_groups = config['selected_groups']
# Load password
self.password = keyring.get_password('sogo', self.login)
import logging
import re
from datetime import date, timedelta
from icalendar_tools import get_event_start_date
logger = logging.getLogger('Filters')
def filter_date(event) -> bool:
"""Return true if event day is after the beginning of last week"""
event_date = get_event_start_date(event)
if event_date:
from_date = - timedelta(weeks=1)
return event_date >= from_date
# If there is a bug in data then do not filter
summary = event.get('summary')
logger.warn('There was an issue with the date of {}'.format(summary))
return True
def filter_group(event, selected_groups) -> bool:
def group(event, selected_groups) -> bool:
"""Filter group according to the group selected"""
summary = event.get('summary')
summary_search ='([0-9]{3}) - ([A-Z]{2}) \(grpe : ([^\)]*)\)', summary, re.IGNORECASE)
summary_search ='([0-9]{3}) - ([A-Z]{2}) \(grpe : ([^\)]*)\)',
summary, re.IGNORECASE)
if summary_search:
ue, groups = 'ue_' +,' - ')
ue, groups = 'ue_' +,
3).split(' - ')
if ue in selected_groups:
# UE is configured
for group in groups:
if group in selected_groups[ue]:
# Group was selected
return True
# Group is not the one selected'Removed {} because it had not been chosen'.format(summary))"Remove '{summary}' because it had not been chosen")
return False
# If there is not a group specified do not filter
import logging
from datetime import datetime, date
from typing import Union
from icalendar import Calendar
from requests import get
from requests.auth import HTTPBasicAuth
class OnlineCalendar:
logger = logging.getLogger('OnlineCalendar')
def __init__(self, url: str, login: str, password: str):
"""Download and parse an icalendar file with authentication""""Downloading {}".format(url))
request = get(url, auth=HTTPBasicAuth(login, password))
content = request.text
# Parse components
self.components = Calendar.from_ical(content)
def get_events(self):
"""Return all calendar events"""
for component in self.components.walk():
if == 'VEVENT':
yield component
def get_todos(self):
"""Return all calendar tasks"""
for component in self.components.walk():
if == 'VTODO':
yield component
def get_timezone(self):
"""Return one timezone"""
for component in self.components.walk():
if == 'VTIMEZONE':
return component
def get_other_components(self):
"""Return all unrecognised components"""
for component in self.components.walk():
if not in blacklist:
yield component
def get_event_start_date(event) -> Union[date, bool]:
"""Return start date of an event"""
event_date = event.decoded('dtstart')
if isinstance(event_date, datetime):
elif isinstance(event_date, date):
return event_date
return False
import logging
from datetime import datetime, timedelta
from icalendar import Calendar
import caldav
import icalendar
import keyring
import yaml
from caldav.objects import dav, Calendar
from configuration import Configuration
from filters import filter_date, filter_group
from icalendar_tools import OnlineCalendar
import filters
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO)
config = Configuration('config.yml')
# Create master calendar
calendar = Calendar()
class Configuration:
"""Regroup user configuration"""
def __init__(self, yaml_file):
with open(yaml_file, 'r') as f:
config = yaml.load(f)
url = config['url']
username = config['username']
self.selected_groups = config['selected_groups']
# Load password
password = keyring.get_password('sogo', username)
# Load user calendar list
self.calendars = get_dav_calendars(url, username, password)
def get_dav_calendars(url: str, username: str, password: str):
"""Get all calendars from DAV server""""Downloading {}".format(url))
client = caldav.DAVClient(url, username=username, password=password)
principal = client.principal()
calendars = principal.calendars()
return calendars
def get_dav_calendar_name(cal: Calendar) -> str:
"""Return calendar name"""
properties = cal.get_properties([dav.DisplayName(), ])
return properties['{DAV:}displayname']
def get_all_dav_components(calendars: [Calendar]):
"""Return all components from the given calendars"""
events, todos, journals = [], [], []
for cal in calendars:"Loading '{}'".format(get_dav_calendar_name(cal)))
# Get events that are in the future
from_date = - timedelta(weeks=1)
events += cal.date_search(from_date)
# Get other components
todos += cal.todos()
journals += cal.journals()
return events, todos, journals
# Configure logging system
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
# Load user configuration, then user components
configuration = Configuration('config.yml')
events, todos, journals = get_all_dav_components(configuration.calendars)
# Create master calendar and populate with components
calendar = icalendar.Calendar()
calendar.add('version', '2.0')
calendar.add('prodid', '-//SaphSync//EN')
calendar.add('method', 'PUBLISH')
calendar.add('x-wr-calname', 'SaphSync')
# Fetch online calendar and copy components
for url in config.calendars_url:
online_calendar = OnlineCalendar(url, config.login, config.password)
# Copy timezone definitions
tz = online_calendar.get_timezone()
# Copy events
for event in online_calendar.get_events():
# If it is an event more recent than past week and in the correct group
if filter_date(event) and filter_group(event, config.selected_groups):
logging.debug('An event was added : {}'.format(event.get('summary')))
# Copy tasks
for todo in online_calendar.get_todos():
logging.debug('A task was added : {}'.format(todo.get('summary')))
# Copy other components
for component in online_calendar.get_other_components():'A unrecognised component ({}) was added : {}'.format(, component.to_ical()))
# Copy events
for event_dav in events:
event_ical = icalendar.Event.from_ical(
# Get the event
# TODO Fix this part because there is always only one event
event = None
for component in event_ical.walk():
if == "VEVENT":
event = component
# If it is in the correct group
if, configuration.selected_groups):
logging.debug('An event was added : {}'.format(event.get('summary')))
# Write resulting calendar
with open('calendar.ics', 'wb') as f:
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment