161 lines
5.5 KiB
Python
161 lines
5.5 KiB
Python
import requests
|
|
import caldav
|
|
from caldav.elements import dav, cdav
|
|
from datetime import datetime
|
|
import os
|
|
import time
|
|
import concurrent.futures
|
|
|
|
# --- CONFIGURACIÓN ---
|
|
# Default to 5 minutes
|
|
SYNC_FREQUENCY_MINUTES = int(os.getenv("SYNC_FREQUENCY", 5))
|
|
SYNC_FREQUENCY_SECONDS = SYNC_FREQUENCY_MINUTES * 60
|
|
|
|
# Tu URL de Outlook
|
|
ICS_URL = os.getenv("ICS_URL")
|
|
|
|
# Tu Baïkal
|
|
BAIKAL_URL = os.getenv("BAIKAL_URL")
|
|
BAIKAL_USER = os.getenv("BAIKAL_USER")
|
|
BAIKAL_PASS = os.getenv("BAIKAL_PASS")
|
|
CALENDAR_ID = os.getenv("CALENDAR_ID")
|
|
|
|
# Headers para parecer un navegador real y evitar 'Connection Reset'
|
|
HEADERS = {
|
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Unraid-Sync/1.0"
|
|
}
|
|
|
|
def delete_event(event):
|
|
"""Helper function to delete a single event."""
|
|
try:
|
|
event.delete()
|
|
return True
|
|
except Exception as e:
|
|
print(f"!!! Error deleting event {event}: {e}")
|
|
return False
|
|
|
|
def delete_all_events(calendar):
|
|
"""
|
|
Deletes all events in the calendar as fast as possible using threads.
|
|
"""
|
|
print("-> Buscando eventos para borrar...")
|
|
try:
|
|
events = calendar.events()
|
|
except Exception as e:
|
|
print(f"!!! Error al obtener eventos: {e}")
|
|
return
|
|
|
|
total_events = len(events)
|
|
if total_events == 0:
|
|
print("-> El calendario ya está vacío.")
|
|
return
|
|
|
|
print(f"-> Borrando {total_events} eventos rápidamente...")
|
|
|
|
# Usamos ThreadPoolExecutor para borrar en paralelo
|
|
deleted_count = 0
|
|
with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
|
|
results = list(executor.map(delete_event, events))
|
|
deleted_count = results.count(True)
|
|
|
|
print(f"-> Limpieza completada. Borrados {deleted_count}/{total_events} eventos.")
|
|
|
|
def sync():
|
|
if not all([ICS_URL, BAIKAL_URL, BAIKAL_USER, BAIKAL_PASS]):
|
|
print(f"[{datetime.now()}] !!! Error: Faltan variables de entorno. Asegúrate de configurar ICS_URL, BAIKAL_URL, BAIKAL_USER y BAIKAL_PASS.")
|
|
return
|
|
|
|
print(f"[{datetime.now()}] Iniciando sincronización...")
|
|
|
|
# 1. Descargar ICS de Outlook
|
|
print("-> Descargando calendario de Outlook...")
|
|
try:
|
|
response = requests.get(ICS_URL, headers=HEADERS, timeout=30)
|
|
response.raise_for_status()
|
|
ics_data = response.text
|
|
print(f"-> Descarga exitosa ({len(ics_data)} bytes).")
|
|
except Exception as e:
|
|
print(f"!!! Error descargando Outlook: {e}")
|
|
return
|
|
|
|
# 2. Conectar a Baïkal
|
|
print("-> Conectando a Baïkal...")
|
|
try:
|
|
client = caldav.DAVClient(
|
|
url=BAIKAL_URL,
|
|
username=BAIKAL_USER,
|
|
password=BAIKAL_PASS,
|
|
headers=HEADERS, # Clave para evitar el bloqueo
|
|
ssl_verify_cert=True # Cambiar a False si tienes problemas de certificado SSL auto-firmado
|
|
)
|
|
principal = client.principal()
|
|
calendars = principal.calendars()
|
|
|
|
# Buscar el calendario correcto por ID si se proporciona
|
|
calendar = None
|
|
if CALENDAR_ID:
|
|
print(f"-> Buscando calendario con ID: {CALENDAR_ID}")
|
|
for cal in calendars:
|
|
# Comprobamos si el ID está en la URL del calendario
|
|
if CALENDAR_ID in str(cal.url):
|
|
calendar = cal
|
|
break
|
|
|
|
if not calendar:
|
|
print(f"!!! Error: No se encontró ningún calendario con el ID '{CALENDAR_ID}'. Calendarios disponibles:")
|
|
for c in calendars:
|
|
print(f" - {c.url}")
|
|
return
|
|
else:
|
|
# Si no hay ID, usar el primero como antes
|
|
if calendars:
|
|
calendar = calendars[0]
|
|
else:
|
|
print("!!! No se encontró ningún calendario en esa URL.")
|
|
return
|
|
|
|
print(f"-> Calendario seleccionado: {calendar}")
|
|
|
|
# 3. Borrar eventos antiguos (NUEVO)
|
|
delete_all_events(calendar)
|
|
|
|
# 4. Importar eventos
|
|
print("-> Procesando archivo ICS...")
|
|
from icalendar import Calendar
|
|
cal = Calendar.from_ical(ics_data)
|
|
|
|
events = cal.walk('vevent')
|
|
total_events = len(events)
|
|
print(f"-> Encontrados {total_events} eventos para importar.")
|
|
|
|
success_count = 0
|
|
error_count = 0
|
|
|
|
for i, component in enumerate(events, 1):
|
|
try:
|
|
# Intentamos pasar el string decodificado
|
|
calendar.add_event(component.to_ical().decode('utf-8'))
|
|
success_count += 1
|
|
except Exception as ev_err:
|
|
error_count += 1
|
|
# Solo imprimimos los primeros 5 errores para no saturar
|
|
if error_count <= 5:
|
|
summary = component.get('summary', 'sin titulo')
|
|
print(f"!!! Error ({i}/{total_events}) '{summary}': {ev_err}")
|
|
|
|
# Print progress every 50 events
|
|
if i % 50 == 0:
|
|
print(f" Procesados {i}/{total_events} (Exitos: {success_count}, Errores: {error_count})")
|
|
|
|
print(f"-> ¡Sincronización finalizada! Éxitos: {success_count}, Errores: {error_count}")
|
|
|
|
except Exception as e:
|
|
print(f"!!! Error en Baïkal: {e}")
|
|
|
|
if __name__ == "__main__":
|
|
print(f"Iniciando servicio de sincronización. Frecuencia: {SYNC_FREQUENCY_MINUTES} minutos ({SYNC_FREQUENCY_SECONDS} segundos).")
|
|
while True:
|
|
sync()
|
|
print(f"[{datetime.now()}] Durmiendo {SYNC_FREQUENCY_MINUTES} minutos...")
|
|
time.sleep(SYNC_FREQUENCY_SECONDS)
|