library/calendar_sync.py aktualisiert

This commit is contained in:
2025-04-30 09:39:17 +00:00
parent 45869addb1
commit 3c18f55d24

View File

@ -3,8 +3,24 @@
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
from caldav import DAVClient from caldav import DAVClient
import vobject import vobject
import requests
from urllib.parse import urlparse
def connect_calendar(url, user=None, password=None, verify_ssl=True): def fetch_ics_events(url, user=None, password=None):
"""Lädt Events aus einer ICS-URL"""
try:
auth = (user, password) if user and password else None
response = requests.get(url, auth=auth)
response.raise_for_status()
ics_data = response.text
cal = vobject.readOne(ics_data)
return [comp for comp in cal.components() if comp.name == 'VEVENT']
except Exception as e:
raise Exception(f"ICS-Download fehlgeschlagen: {str(e)}")
def connect_caldav(url, user=None, password=None, verify_ssl=True):
"""Stellt Verbindung zum CalDAV-Kalender her"""
try: try:
client = DAVClient( client = DAVClient(
url, url,
@ -12,53 +28,67 @@ def connect_calendar(url, user=None, password=None, verify_ssl=True):
password=password, password=password,
ssl_verify_cert=verify_ssl ssl_verify_cert=verify_ssl
) )
calendar = client.calendar(url=url) # Kalender-URL aus Pfad extrahieren
# Test-Zugriff um sicherzustellen, dass der Kalender existiert path = urlparse(url).path
calendar.get_properties(['displayname']) principal = client.principal()
return calendar for calendar in principal.calendars():
if path in calendar.url:
return calendar
raise Exception("Kalender nicht gefunden")
except Exception as e: except Exception as e:
raise Exception(f"Kalenderverbindung fehlgeschlagen: {str(e)}") raise Exception(f"CalDAV-Verbindungsfehler: {str(e)}")
def get_event_uid_map(calendar): def sync_ics_to_caldav(module):
uid_map = {} """Haupt-Synchronisationslogik"""
for event in calendar.events(): # ICS-Events holen
try: ics_events = fetch_ics_events(
cal = vobject.readOne(event.data) module.params['source_url'],
uid = cal.vevent.uid.value module.params['source_user'],
uid_map[uid] = event module.params['source_password']
except Exception as e: )
continue # Fehlerhafte Events überspringen
return uid_map
def sync_calendars(source_cal, target_cal): # CalDAV-Ziel verbinden
src_events = get_event_uid_map(source_cal) target_cal = connect_caldav(
tgt_events = get_event_uid_map(target_cal) module.params['target_url'],
module.params['target_user'],
module.params['target_password'],
module.params['verify_ssl']
)
# Existierende Events mappen
existing_events = {
event.icalendar_component['UID']: event
for event in target_cal.events()
}
changed = False changed = False
added, updated, removed = [], [], [] results = {'added': [], 'updated': [], 'removed': []}
# Neue/aktualisierte Events # Neue/aktualisierte Events syncen
for uid, src_event in src_events.items(): for vevent in ics_events:
tgt_event = tgt_events.get(uid) uid = str(vevent['UID'].value)
src_data = src_event.data ical_data = vevent.serialize()
if not tgt_event: if uid not in existing_events:
target_cal.add_event(src_data) target_cal.add_event(ical_data)
added.append(uid) results['added'].append(uid)
changed = True changed = True
elif src_data != tgt_event.data: else:
tgt_event.data = src_data # Nur aktualisieren wenn unterschiedlich
tgt_event.save() if ical_data != existing_events[uid].data:
updated.append(uid) existing_events[uid].data = ical_data
existing_events[uid].save()
results['updated'].append(uid)
changed = True
# Optionale Löschung nicht vorhandener Events
if module.params.get('purge'):
for uid in set(existing_events.keys()) - {str(e['UID'].value) for e in ics_events}:
existing_events[uid].delete()
results['removed'].append(uid)
changed = True changed = True
# Entfernte Events löschen return changed, results
for uid in set(tgt_events.keys()) - set(src_events.keys()):
tgt_events[uid].delete()
removed.append(uid)
changed = True
return changed, added, updated, removed
def run_module(): def run_module():
module_args = dict( module_args = dict(
@ -68,52 +98,17 @@ def run_module():
target_url=dict(type='str', required=True), target_url=dict(type='str', required=True),
target_user=dict(type='str', required=False, default=None), target_user=dict(type='str', required=False, default=None),
target_password=dict(type='str', required=False, no_log=True, default=None), target_password=dict(type='str', required=False, no_log=True, default=None),
verify_ssl=dict(type='bool', required=False, default=True) verify_ssl=dict(type='bool', default=True),
purge=dict(type='bool', default=False)
) )
module = AnsibleModule( module = AnsibleModule(argument_spec=module_args, supports_check_mode=False)
argument_spec=module_args,
supports_check_mode=False
)
result = dict(
changed=False,
added=[],
updated=[],
removed=[]
)
try: try:
# Kalender verbinden changed, results = sync_ics_to_caldav(module)
source_cal = connect_calendar( module.exit_json(changed=changed, **results)
url=module.params['source_url'],
user=module.params['source_user'],
password=module.params['source_password'],
verify_ssl=module.params['verify_ssl']
)
target_cal = connect_calendar(
url=module.params['target_url'],
user=module.params['target_user'],
password=module.params['target_password'],
verify_ssl=module.params['verify_ssl']
)
# Synchronisation durchführen
changed, added, updated, removed = sync_calendars(source_cal, target_cal)
result.update({
'changed': changed,
'added': added,
'updated': updated,
'removed': removed,
'msg': f"Synchronisiert: +{len(added)}/~{len(updated)}/-{len(removed)}"
})
except Exception as e: except Exception as e:
module.fail_json(msg=f"Fehler bei der Synchronisation: {str(e)}", **result) module.fail_json(msg=str(e))
module.exit_json(**result)
if __name__ == '__main__': if __name__ == '__main__':
run_module() run_module()