Files
echo-du-huit/docs/RECHERCHE.md
T

171 lines
8.4 KiB
Markdown

# Recherche technique — faits vérifiés (2026-06-29)
Document de référence pour l'implémentation. Toutes les valeurs ci-dessous sont
**vérifiées sur le HTML/ICS réel**, pas supposées. Re-vérifier si > 6 mois.
---
## 1. Source : calendrier Nextcloud (CalDAV)
- **URL** : `https://atelier-huit.frama.space/remote.php/dav/calendars/Pierre/latelier-du-huit-sbastien_shared_by_admin/?export`
- **Auth** : session navigateur (cookies). Depuis l'extension → `fetch(url, { credentials: 'include' })` + `host_permissions` sur `https://atelier-huit.frama.space/*`. **Aucun mot de passe stocké.**
- **Réponse** : flux iCalendar brut (VCALENDAR/VEVENT).
- Si non connecté → la requête échoue : prévoir un message "connecte-toi à Nextcloud dans un onglet".
### Cas limites du flux réel (échantillon 2026-06-29, 473 VEVENT)
| Réalité | Chiffre | Conséquence parsing |
|---|---|---|
| VEVENT total | 473 | |
| UID **uniques** | 381 | **Dédoublonner par UID** (récurrences = même UID répété) |
| Récurrents (RRULE) | 54 | Gérer/ignorer les occurrences passées |
| Journée entière (`DTSTART;VALUE=DATE`) | 7 | **Pas d'heure** → le formulaire mairie attend une heure : cas à traiter |
| Avec LOCATION | 51 / 473 | La **majorité n'a pas de lieu** → défaut = adresse du café |
| Avec DESCRIPTION | 142 / 473 | La majorité n'a pas de description → champ souvent vide |
| Fuseaux | `Europe/Paris` **mais aussi `Africa/Lagos`** | Ne PAS supposer Europe/Paris : lire le vrai `TZID` |
> Recommandation parsing : `ical.js` (Mozilla) plutôt que regex maison — gère RRULE, TZID, VALUE=DATE.
> **T1 — parseur** : `ical.js` **v2.2.1** vendoré (build ESM `dist/ical.js`) dans
> `extension/vendor/ical.js`. ⚠️ Les `VTIMEZONE` embarqués dans le flux doivent
> être enregistrés (`ICAL.TimezoneService.register`) **avant** `toJSDate()`,
> sinon l'offset est faux pour les TZID non standards (ex. `Africa/Lagos`).
### Écriture CalDAV — **mesuré le 2026-08-13** (spike navigateur, T2)
Mesures faites depuis la console de la page d'extension, session Nextcloud
ouverte. La séquence complète a été jouée, `PUT` **no-op** compris (contenu
reposé à l'identique : rien modifié).
| Étape | Résultat mesuré |
|---|---|
| `GET /csrftoken` | **200**, corps `{"token":"…=:…="}` (le repli `data-requesttoken` n'a pas servi) |
| `REPORT` sur la collection (`calendar-query` filtrée sur `UID`) | **207**, en-têtes `Depth: 1` + `Content-Type: application/xml; charset=utf-8` |
| `PUT` avec `If-Match` | **204** → **le partage est en écriture** |
| `REPORT` avec un `requesttoken` **invalide** | **207 quand même** : l'endpoint DAV **n'exige pas** le jeton CSRF |
- **Collection** = URL du calendrier **sans** `?export` (d'où `URL_COLLECTION`
dérivée dans `nextcloud.js`).
- **Préfixes réels** du `multistatus` : `d:` → `DAV:`, `cal:` →
`urn:ietf:params:xml:ns:caldav` (plus `s:`, `cs:`, `oc:`, `nc:`). Ce sont des
**préfixes**, pas un contrat : sélectionner **par namespace**
(`getElementsByTagNameNS`), jamais par préfixe.
- ⚠️ **Le nom de la ressource n'est PAS l'UID** : l'événement d'UID
`db2d146b-8e18-…` vit dans `…/00D03211-DA2E-4D06-AAAE-6E8C2355BA2B.ics`.
Déduire l'URL en `<uid>.ics` **aurait échoué** — d'où le `REPORT` préalable.
- `href` du `multistatus` = chemin **absolu depuis la racine**
(`/remote.php/dav/…`) → à résoudre contre la collection.
- `getetag` est rendu **avec ses guillemets** (`"8ee26a8f…"`) : `If-Match` le
reprend **verbatim**.
---
## 2. Cible : formulaire mairie (Gravity Forms, form id = 6)
URL : `https://www.ville-cugnaux.fr/mes-loisirs/associations/proposer-un-evenement-dans-lagenda/`
### Stratégie de soumission
Content script qui **remplit le DOM par id**, puis **l'utilisateur soumet à la main**.
- Pas de params URL : Gravity Forms n'accepte `?input_X=` que si "population dynamique" est activé par champ côté mairie (non).
- Les tokens CSRF (`gform_currency`, `state_6`, `version_hash`) sont **dans la page** et gérés par le formulaire lui-même : on n'y touche pas. ⚠️ Les tokens du curl capturé sont **par session, non réutilisables**.
### Mapping des champs (ids confirmés présents dans le HTML)
> ⚠️ **La colonne « balise réelle » a été payée par un bug** (2026-07-17). Le
> corps de la tâche T1 affirmait « Heures = 2 **selects** » : c'est **faux**, ce
> sont des `<input type="number">`. Le code l'a suivi et les 4 champs d'heures
> ont échoué (`champ.options` undefined → TypeError). **Ne pas déduire le type
> d'un champ de son intitulé : le HTML est la seule source de vérité.** Types
> ci-dessous vérifiés sur la page réelle le 2026-07-17.
| id DOM | name | Balise réelle | Contenu | Source |
|---|---|---|---|---|
| `#input_6_42` | `input_42` | `input[type=text]` | Organisateur | config (« L'Atelier du Huit ») |
| `#input_6_41` | `input_41` | `input[type=email]` | Email | config |
| `#input_6_1` | `input_1` | `input[type=text]` | Titre | SUMMARY |
| `#input_6_3` | `input_3` | `textarea` | Description | DESCRIPTION |
| `#input_6_20` | `input_20` | **`select`** | Thème | mapping (voir §3) |
| `#input_6_31` | `input_31` | `input[type=text]` (datepicker) | Date début `jj/mm/aaaa` | DTSTART |
| `#input_6_32` | `input_32` | `input[type=text]` (datepicker) | Date fin `jj/mm/aaaa` | DTEND |
| `#input_6_38_1` / `#input_6_38_2` | `input_38[]` | **`input[type=number]`** (`min/max`, placeholder `HH`/`MM`) | Heure début [HH, MM] | DTSTART |
| `#input_6_39_1` / `#input_6_39_2` | `input_39[]` | **`input[type=number]`** | Heure fin [HH, MM] | DTEND |
| `#input_6_35` (`_1`) | `input_35.1` | `input[type=checkbox]` | Accessibilité | ⚠️ laissé **intact** : la machine ne sait pas si le lieu est accessible |
| `#input_6_36` | `input_36` | | Public ciblé | optionnel |
| `#input_6_40` | `input_40` | **`textarea`** | Tarifs | défaut « Gratuit » |
| `#nova_address` | `nova_address` | `input[type=text]` | Adresse texte | défaut café |
| `#input_6_18` | `input_18` | `input[type=hidden]` | `lat\|lng\|adresse` | défaut café |
| `#input_6_34` | `input_34` | `input[type=file]` | **Image** | ⚠️ non remplissable par script (sécurité) → manuel |
| `#input_6_23`, `#input_6_43` | | | Honeypot | laisser vide |
Les 4 champs d'heures acceptent le **zéro-padding** (`"09"`, `"00"`) : une suite
de chiffres est un *valid floating-point number* au sens HTML.
**Défaut café** : `8 Rue du Pré Vicinal 31270 Cugnaux`
`input_18` = `43.53753132806989|1.342981890597508|8 Rue du Pré Vicinal 31270 Cugnaux`
---
## 3. Valeurs exactes du dropdown Thème (`#input_6_20`)
Chaînes `value` à utiliser telles quelles :
```
Culture
Atelier - Stage
Economie - Emploi
Exposition
Photographie
Rencontre
Spectacle
Enfance - Jeunesse
Jeunesse
Parentalité
Petite enfance
Scolarité
Loisirs
Mobilité
Nature - Environnement
Numérique
Participation citoyenne
Cérémonies officielles
Réunion publique
Santé
Seniors
Solidarité
Sport
```
> Le calendrier Nextcloud n'a pas de thème → prévoir un choix par l'utilisateur dans l'UI, défaut « Culture ».
---
## 4. Vérification publication (agenda public mairie)
URL filtrée par mois :
`https://www.ville-cugnaux.fr/mes-loisirs/agenda/?f=1&theme=&date=periode&date_debut=MM%2FYYYY&date_fin=MM%2FYYYY&public=`
### Structure HTML d'un résultat (confirmée)
Chaque événement publié = `<article class="card card-thumbnail card-event ...">`
contenant (balise `<article>`, PAS `<div>` : vérifié sur l'agenda réel le
2026-07-17 — matcher sur la classe `.card-event`, agnostique de balise) :
- **Date** : `<time datetime="YYYY-MM-DD">` (ancre de match la plus fiable)
- **Lien direct** : `<a href="/agenda/<slug>/">` — parfois **relatif**, à
résoudre contre l'agenda avant affichage ; c'est le lien à afficher dans l'extension
- **Titre** : `.card-title > a`
- **Thème** : classe `event_theme-<slug>`
Exemples de liens réels observés : `/agenda/spectacle-jhabite-ici/`, `/agenda/atelier-bebe-gym/`, `/agenda/fete-du-14-juillet-inscription-au-repas-republicain/`.
### Matching Nextcloud ↔ mairie
Comparer **date** (`<time datetime>` vs DTSTART) **+ titre** (normalisé : sans accents/emojis, ou via le slug). Tolérer les écarts mineurs.
---
## 5. Modèle de statut (rappel)
- **ignoré** / **soumis:AAAA-MM-JJ** → tag `CATEGORIES` écrit en CalDAV (partagé, durable)
- **publié** → **dérivé** de l'agenda mairie (§4), donne le lien direct
- **à faire** → futur, ni ignoré, ni soumis, ni publié