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 |
- 📂 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
.icsencodés en base64 (exports LinkedIn notamment), décodés automatiquement - 🔐 OAuth2 côté client — aucun mot de passe stocké, aucun serveur
À 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.
- Rendez-vous sur console.cloud.google.com
- Cliquez sur le sélecteur de projet en haut → Nouveau projet
- Donnez-lui un nom (ex.
ics-to-gcal) et confirmez
- Dans le menu : APIs & Services → Enable APIs and Services
- Recherchez Google Calendar API
- 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.
- APIs & Services → OAuth consent screen
- Choisissez External → Create
- Remplissez les champs obligatoires (nom de l'app, email de support)
- Cliquez Save and Continue jusqu'à la section Test users
- Dans Test users → + Add users → ajoutez votre adresse Gmail
- 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.
- APIs & Services → Credentials → Create Credentials → OAuth client ID
- Type d'application : Application Web
- Dans Origines JavaScript autorisées, ajoutez exactement :
https://bretzelcoder.github.ioSans chemin ni
/final : Google compare l'origine, pas l'adresse complète de la page. - Cliquez Create
- 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.
- Ouvrez l'application
- Collez votre Client ID dans le champ en haut de la page
- 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.
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.
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
.icsencodé en base64 (le fichier commence parQkVHSU46VkNBTEVOREFS…au lieu deBEGIN: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.icsmanuellement et utilisez l'upload.
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
- 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
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.
| 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) |
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.
| 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 |
git clone https://github.com/BretzelCoder/GoogleAgendaAssistant.git
cd GoogleAgendaAssistantSans Git : Code → Download ZIP sur la page GitHub du dépôt, puis décompressez-le.
- Suivez les étapes 1 à 4, en ajoutant l'origine
http://localhost:8080aux Origines JavaScript autorisées de votre Client ID - Servez le dossier en HTTP, sur le port 8080 — Google refuse l'OAuth depuis
file://:python -m http.server 8080
- 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.
- Forkez ce dépôt sur votre compte GitHub
- Dans les paramètres du dépôt : Settings → Pages → Source : Deploy from a branch → main / (root)
- Votre app sera disponible sur
https://votre-compte.github.io/nom-du-repo - Ajoutez
https://votre-compte.github.iodans les Origines JavaScript autorisées de votre Client ID
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.
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é | 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.
python -m venv .venv
# Windows
.venv\Scripts\activate
# macOS / Linux
source .venv/bin/activate
pip install -r requirements.txtCette variante nécessite un identifiant OAuth avec secret, différent de celui de la mise en place :
- APIs & Services → Credentials → Create Credentials → OAuth client ID → Application Web
- Dans URI de redirection autorisés, ajoutez :
http://localhost:5000/oauth2callback - Download JSON et enregistrez le fichier sous
credentials.jsonà la racine du dépôt
🔐
credentials.jsoncontient un client secret : ne le commitez jamais. Il est listé dans.gitignore.
# 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.pyL'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.pyforceOAUTHLIB_INSECURE_TRANSPORT=1(OAuth autorisé en HTTP). C'est acceptable parce que le serveur n'écoute que sur127.0.0.1, pas pour une exposition sur le réseau.
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 :
- Connectez-vous d'abord à Google Calendar (bouton habituel)
- 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
- votre identifiant USM (
- 🔍 Lire mon planning SIGA — comptez quelques secondes : le portail passe par une salle d'attente (Queue-it)
- Vérifiez la grille extraite : jour, horaire, cours, salle, enseignant. Décochez ce que vous ne voulez pas
- 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.
- 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
- 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.clet*.queue-it.netsont acceptés, pour qu'une redirection inattendue n'emporte pas les cookies de session credentials.jsoncontient un client secret : gardez-le hors du dépôt- Prévue pour un usage local uniquement (OAuth en HTTP autorisé)
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.txtne concerne que la variante Flask. La version statique n'a aucune dépendance à installer : les bibliothèques sont déjà dansjs/.
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.