Skip to content

About

Import .ics files and iCalendar feeds into Google Calendar - a serverless browser app, plus a Flask variant with SIGA university timetable sync

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

📅 ICS → Google Calendar

Importez un fichier .ics ou un flux iCalendar directement dans Google Agenda, depuis votre navigateur.

L'application est en ligne sur bretzelcoder.github.io/GoogleAgendaAssistant : rien à télécharger, rien à installer, aucun serveur à lancer.

Étape À faire Quand
1 Créer votre Client ID Google (mise en place) une seule fois, ~10 min
2 Ouvrir l'application et coller le Client ID une seule fois
3 Se connecter avec Google, déposer le .ics, vérifier, importer à chaque import

✨ Fonctionnalités

  • 📂 Upload d'un fichier .ics (glisser-déposer)
  • 🔗 Import depuis une URL de flux iCalendar
  • 👀 Prévisualisation des événements avant import (avec sélection individuelle)
  • 🗂 Choix de l'agenda de destination
  • 🔎 Gestion des doublons : ignorer · mettre à jour · dupliquer
  • 🧬 Lecture des .ics encodés en base64 (exports LinkedIn notamment), décodés automatiquement
  • 🔐 OAuth2 côté client — aucun mot de passe stocké, aucun serveur

🚀 Mise en place

À faire une seule fois, en environ 10 minutes. Il vous faut seulement un compte Google et un navigateur récent.

L'application a besoin d'un Client ID Google pour accéder à votre agenda. Vous créez le vôtre dans Google Cloud : vous gardez ainsi la main sur les autorisations, et personne d'autre que vous n'y a accès.

Étape 1 — Créer un projet Google Cloud

  1. Rendez-vous sur console.cloud.google.com
  2. Cliquez sur le sélecteur de projet en haut → Nouveau projet
  3. Donnez-lui un nom (ex. ics-to-gcal) et confirmez

Étape 2 — Activer l'API Google Calendar

  1. Dans le menu : APIs & Services → Enable APIs and Services
  2. Recherchez Google Calendar API
  3. Cliquez sur le résultat puis sur Enable

⚠️ Sans cette étape, vous obtiendrez l'erreur "Google Calendar API has not been used in project" lors de l'import.

Étape 3 — Configurer l'écran de consentement OAuth

  1. APIs & Services → OAuth consent screen
  2. Choisissez External → Create
  3. Remplissez les champs obligatoires (nom de l'app, email de support)
  4. Cliquez Save and Continue jusqu'à la section Test users
  5. Dans Test users → + Add users → ajoutez votre adresse Gmail
  6. Terminez la configuration

⚠️ Tant que l'app est en mode Testing, seuls les emails de la liste peuvent se connecter. Pour un usage personnel, c'est suffisant.

Étape 4 — Créer un identifiant OAuth

  1. APIs & Services → Credentials → Create Credentials → OAuth client ID
  2. Type d'application : Application Web
  3. Dans Origines JavaScript autorisées, ajoutez exactement :
    https://bretzelcoder.github.io
    

    Sans chemin ni / final : Google compare l'origine, pas l'adresse complète de la page.

  4. Cliquez Create
  5. Copiez le Client ID généré (format XXXXXXXX.apps.googleusercontent.com)

Le Client ID est une valeur publique — il identifie votre application, pas vous.

Étape 5 — Enregistrer le Client ID dans l'application

  1. Ouvrez l'application
  2. Collez votre Client ID dans le champ en haut de la page
  3. Cliquez Enregistrer — la page se recharge

Le Client ID est mémorisé dans votre navigateur (localStorage). Les fois suivantes, il suffit d'ouvrir la page. Sur un autre navigateur ou appareil, recollez-le.


📖 Utilisation

1. Se connecter

Cliquez sur Se connecter avec Google et sélectionnez votre compte Gmail.

Si Chrome bloque la popup : cliquez sur l'icône de popup bloquée dans la barre d'adresse et autorisez bretzelcoder.github.io.

2. Choisir la source ICS

Deux options :

  • Fichier .ics — glissez-déposez le fichier ou cliquez pour parcourir
  • URL — collez l'adresse d'un flux iCalendar distant

Cliquez ensuite sur 🔍 Analyser.

💡 Certains services, comme LinkedIn, exportent leurs événements dans un .ics encodé en base64 (le fichier commence par QkVHSU46VkNBTEVOREFS… au lieu de BEGIN:VCALENDAR). Rien à faire : l'application le détecte et le décode avant l'analyse, que la source soit un fichier ou une URL.

⚠️ Certaines URLs de flux iCalendar refusent les requêtes depuis un navigateur (CORS). Si l'analyse échoue avec une URL, téléchargez le fichier .ics manuellement et utilisez l'upload.

3. Prévisualiser et sélectionner

Un tableau liste tous les événements détectés avec leur titre, dates et indicateurs (récurrent, lieu, invalide). Vous pouvez :

  • Décocher des événements pour les exclure de l'import
  • Utiliser la case en en-tête pour tout sélectionner / désélectionner

4. Choisir les options d'import

  • Agenda de destination — sélectionnez l'agenda Google dans lequel importer
  • Gestion des doublons :
    • Ignorer — les événements déjà présents (même UID) sont ignorés
    • Mettre à jour — les événements existants sont écrasés
    • Importer quand même — crée un doublon sans vérification

5. Lancer l'import

Cliquez sur ⬆️ Importer. Une barre de progression s'affiche. À la fin, un résumé indique le nombre d'événements importés, mis à jour, ignorés et les éventuelles erreurs.


❓ Erreurs fréquentes

Erreur Cause Solution
access_denied Votre email n'est pas dans les utilisateurs test APIs & Services → OAuth consent screen → Test users → ajoutez votre email
Google Calendar API has not been used L'API n'est pas activée APIs & Services → Enable APIs → activez Google Calendar API
La popup a été bloquée Chrome bloque la popup OAuth Autorisez les popups pour ce domaine dans la barre d'adresse
Origine non autorisée L'origine de la page n'est pas déclarée Ajoutez https://bretzelcoder.github.io dans Origines JavaScript autorisées de votre Client ID (étape 4)
Fichier ICS invalide Le fichier est corrompu ou mal encodé Vérifiez que le fichier s'ouvre correctement dans un éditeur de texte
invalid line (no token ";" or ":") Fichier .ics encodé en base64 (export LinkedIn), sur une version antérieure de l'application Rechargez la page sans cache (Ctrl+F5) : le décodage est désormais automatique
CORS ou réseau L'URL du flux refuse les requêtes navigateur Téléchargez le .ics manuellement et utilisez l'upload, ou passez par la variante Flask
Client ID invalide Le format saisi ne se termine pas par .apps.googleusercontent.com Recopiez le Client ID complet depuis Google Cloud Console
Impossible de charger Google Le script Google Identity Services n'a pas pu être chargé (bloqueur de pub, hors-ligne) Désactivez le bloqueur sur ce domaine et rechargez
Le bouton Se connecter reste grisé Aucun Client ID enregistré Renseignez le Client ID dans le champ en haut de page (étape 5)
La page ne fait rien en file:// L'OAuth Google exige une origine HTTP Utilisez l'application en ligne, ou servez le dossier en local (Installation locale)

💻 Installation locale ou copie personnelle

Inutile pour utiliser l'outil : l'application en ligne suffit. Cette section s'adresse à qui veut modifier le code, travailler hors ligne ou héberger sa propre copie.

Deux implémentations coexistent dans ce dépôt. La version statique (index.html + js/) est celle déployée sur GitHub Pages. La version Flask (app.py + templates/) offre moins de fonctionnalités et s'exécute en local uniquement — voir Variante Flask.

Prérequis

Version statique Variante Flask
Python 3.x — uniquement pour servir les fichiers ; n'importe quel serveur HTTP fait l'affaire 3.8 ou plus (imposé par Flask 3.0)
Dépendances à installer ❌ aucune — les bibliothèques sont déjà dans js/ pip install -r requirements.txt

Récupérer le projet

git clone https://github.com/BretzelCoder/GoogleAgendaAssistant.git
cd GoogleAgendaAssistant

Sans Git : Code → Download ZIP sur la page GitHub du dépôt, puis décompressez-le.

Lancer la version statique en local

  1. Suivez les étapes 1 à 4, en ajoutant l'origine http://localhost:8080 aux Origines JavaScript autorisées de votre Client ID
  2. Servez le dossier en HTTP, sur le port 8080 — Google refuse l'OAuth depuis file:// :
    python -m http.server 8080
  3. Ouvrez http://localhost:8080 et enregistrez le Client ID

Toute autre méthode fonctionne (npx serve -l 8080, extension Live Server, …) tant que l'URL finale correspond exactement à une origine autorisée de votre Client ID.

Héberger votre propre copie sur GitHub Pages

  1. Forkez ce dépôt sur votre compte GitHub
  2. Dans les paramètres du dépôt : Settings → Pages → Source : Deploy from a branch → main / (root)
  3. Votre app sera disponible sur https://votre-compte.github.io/nom-du-repo
  4. Ajoutez https://votre-compte.github.io dans les Origines JavaScript autorisées de votre Client ID

🐍 Variante Flask (serveur local)

Le dépôt contient aussi une implémentation Flask (app.py), antérieure et plus simple. Elle fait l'OAuth côté serveur, ce qui implique un client secret et donc un vrai serveur : elle ne peut pas être déployée sur GitHub Pages.

Quand l'utiliser

Elle reste utile si vous préférez ne pas exposer de Client ID dans le navigateur, ou si les restrictions CORS de votre flux ICS bloquent la version statique (le téléchargement de l'URL est fait par le serveur Python, pas par le navigateur).

Fonctionnalités par rapport à la version statique

Fonctionnalité Statique Flask
Upload .ics / URL de flux ✅ ✅
Téléchargement d'URL sans blocage CORS ❌ ✅
Prévisualisation + sélection des événements ✅ ❌
Choix de l'agenda de destination ✅ ✅
Stratégie de doublons configurable ✅ ❌ (toujours import par UID)
Événements récurrents (RRULE) ✅ ❌ (récurrence non transmise)
Barre de progression ✅ ❌
Planning universitaire SIGA (USM) ❌ (impossible) ✅
Hébergeable sur GitHub Pages ✅ ❌

La synchronisation SIGA ne peut pas exister dans la version statique : un navigateur ne peut ni poster vers siga.usm.cl ni lire sa réponse depuis un autre domaine (le portail n'envoie aucun en-tête CORS), ni porter les cookies de session nécessaires. Il faut un serveur, donc la variante Flask.

Installation

python -m venv .venv
# Windows
.venv\Scripts\activate
# macOS / Linux
source .venv/bin/activate

pip install -r requirements.txt

Identifiants Google

Cette variante nécessite un identifiant OAuth avec secret, différent de celui de la mise en place :

  1. APIs & Services → Credentials → Create Credentials → OAuth client ID → Application Web
  2. Dans URI de redirection autorisés, ajoutez :
    http://localhost:5000/oauth2callback
    
  3. Download JSON et enregistrez le fichier sous credentials.json à la racine du dépôt

🔐 credentials.json contient un client secret : ne le commitez jamais. Il est listé dans .gitignore.

Lancement

# Facultatif : fixer la clé de session pour rester connecté d'un redémarrage à l'autre
# Windows PowerShell
$env:SECRET_KEY = "une-valeur-aleatoire"
# macOS / Linux
export SECRET_KEY="une-valeur-aleatoire"

python app.py

L'application écoute sur http://localhost:5000.

Variables d'environnement reconnues :

Variable Défaut Rôle
SECRET_KEY aléatoire à chaque démarrage Clé de signature des sessions Flask
GOOGLE_CLIENT_SECRETS credentials.json Chemin du fichier d'identifiants OAuth
FLASK_DEBUG désactivé 1 active le débogueur Werkzeug (console d'exécution de code — développement uniquement)

⚠️ app.py force OAUTHLIB_INSECURE_TRANSPORT=1 (OAuth autorisé en HTTP). C'est acceptable parce que le serveur n'écoute que sur 127.0.0.1, pas pour une exposition sur le réseau.

🎓 Synchroniser le planning universitaire (SIGA — USM)

La variante Flask sait aussi se connecter au portail SIGA de l'Universidad Técnica Federico Santa María, lire la page Horario del alumno et pousser tous les cours du semestre dans Google Agenda, en une fois.

Marche à suivre :

  1. Connectez-vous d'abord à Google Calendar (bouton habituel)
  2. Dans la carte 🎓 Planning universitaire, saisissez :
    • votre identifiant USM (nombre.apellido) et le domaine correspondant (@usm.cl, @alumnos.usm.cl, @sansano.usm.cl…)
    • votre mot de passe de courriel USM
    • éventuellement une autre page de planning que celle proposée par défaut
  3. 🔍 Lire mon planning SIGA — comptez quelques secondes : le portail passe par une salle d'attente (Queue-it)
  4. Vérifiez la grille extraite : jour, horaire, cours, salle, enseignant. Décochez ce que vous ne voulez pas
  5. Choisissez la période (début et fin du semestre), le fuseau et l'agenda de destination, puis ⬆️ Synchroniser

Chaque cours devient un événement hebdomadaire récurrent borné à la période choisie, avec salle, enseignant, paralelo et modules dans la description.

Rejouable sans doublon. L'identifiant de chaque événement est calculé à partir du cours (code, paralelo, jour, horaire) : relancer la synchronisation met à jour les événements existants au lieu d'en créer de nouveaux. Un cours qui change de salle est corrigé sur place.

⚠️ Un cours dont l'horaire n'est pas affiché par SIGA (seul le numéro de module l'est) est daté d'après la grille standard des modules et signalé « horaire déduit » dans la prévisualisation. Vérifiez-le avant d'importer.

Si aucun créneau n'est extrait : la page réellement récupérée est consultable sur http://localhost:5000/siga/diagnostic. Elle permet d'ajuster les heuristiques de siga.py à la mise en page rencontrée.

🔐 Vos identifiants USM ne servent qu'à ouvrir la session SIGA le temps de la requête : ils ne sont ni enregistrés sur disque, ni placés dans le cookie de session, ni journalisés.


🔒 Vie privée & sécurité

Version statique

  • Aucun serveur : toute la logique s'exécute dans votre navigateur
  • Aucune donnée transmise : ni le fichier ICS, ni le token Google ne quittent votre navigateur — les seules requêtes sortantes vont vers les API Google
  • Token en mémoire : le token d'accès n'est jamais persisté ; il disparaît au rechargement de la page. Seul le Client ID est conservé (localStorage)
  • Token temporaire : le token OAuth expire après 1 heure (standard Google)
  • Client ID public : c'est normal — seuls les domaines que vous autorisez explicitement peuvent l'utiliser

Variante Flask

  • Aucun jeton dans le cookie : les sessions Flask sont signées mais non chiffrées, donc lisibles par le navigateur. Le cookie ne contient qu'un identifiant opaque ; token et refresh token restent en mémoire du serveur et disparaissent à l'arrêt
  • Écoute locale seulement (127.0.0.1), débogueur désactivé par défaut
  • URLs distantes filtrées : le téléchargement d'un ICS par URL refuse les adresses privées, locales et réservées, y compris après redirection (protection SSRF)
  • Identifiants universitaires jamais conservés : le mot de passe USM sert à l'appel de connexion puis est abandonné avec la session SIGA. Il n'est ni écrit sur disque, ni mis en session, ni journalisé — seuls les créneaux extraits restent en mémoire du serveur, jusqu'à l'import ou la déconnexion
  • Navigation SIGA cloisonnée : les redirections sont suivies une par une et l'hôte revalidé à chaque saut ; seuls *.usm.cl et *.queue-it.net sont acceptés, pour qu'une redirection inattendue n'emporte pas les cookies de session
  • credentials.json contient un client secret : gardez-le hors du dépôt
  • Prévue pour un usage local uniquement (OAuth en HTTP autorisé)

🗂 Structure du projet

GoogleAgendaAssistant/
│
│  ── Version statique (principale, déployée sur GitHub Pages) ──
├── index.html          # Page principale (app complète, 3 étapes)
├── css/
│   └── style.css       # Styles
├── js/
│   ├── ical.min.js     # Bibliothèque tierce de parsing iCalendar (ical.js 1.5.0, MPL 2.0)
│   ├── parser.js       # ICSParser — VEVENT → objets Google Calendar
│   ├── gcal.js         # GCal — client REST Calendar API + OAuth (GIS)
│   └── app.js          # Logique & interface utilisateur
│
│  ── Variante Flask (locale, optionnelle) ──
├── app.py              # Serveur Flask (OAuth côté serveur + import)
├── siga.py             # Connecteur SIGA (USM) — session, extraction du planning
├── test_siga.py        # Tests du parseur SIGA (python test_siga.py)
├── templates/
│   └── index.html      # Gabarit Jinja2 (CSS et JS inline)
├── requirements.txt    # Dépendances Python de la variante Flask
│
├── README.md                # Ce fichier — installation et usage
├── CLAUDE.md                # Documentation interne (architecture, conventions)
├── LICENSE                  # MIT
└── THIRD-PARTY-NOTICES.md   # Licences des composants tiers (ical.js — MPL 2.0)

requirements.txt ne concerne que la variante Flask. La version statique n'a aucune dépendance à installer : les bibliothèques sont déjà dans js/.


📄 Licence

MIT — libre d'utilisation, de modification et de redistribution. Voir LICENSE.

Exception : js/ical.min.js est une bibliothèque tierce (ical.js) sous MPL 2.0. Cette licence étant un copyleft au niveau du fichier, elle n'affecte pas le reste du projet, qui demeure sous MIT.

Détail des composants tiers et de leurs licences : THIRD-PARTY-NOTICES.md.

About

Import .ics files and iCalendar feeds into Google Calendar - a serverless browser app, plus a Flask variant with SIGA university timetable sync

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages