Catégorie : Geek

  • Tout sauve­gar­der – mars 2025

    Nettoyer les écuries d’Au­gias c’est de la nioniotte à côté du projet de rapa­trier toutes mes données en local pour les sauve­gar­der.

    Ce que j’ai déjà

    1. Où sauve­gar­der en local
    2. Sauve­gar­der Gmail et Amélio­rer ma sauve­garde Gmail.
    3. Sauve­gar­der les SMS et les histo­riques d’ap­pel
    4. Sauve­gar­der Google Drive et Google Photos
    5. Sauve­gar­der Github
    6. Sauve­gar­der mes sites web
    7. Sauve­gar­der Stan­dard Notes
    8. Sauve­gar­der les calen­driers Google
    9. Sauve­gar­der les contacts Google
    10. Sauve­gar­der Bitwar­den

    Ce qui manque encore

    Je vais étendre la liste au fur et à mesure. Il me manque encore pas mal de choses, et pas le plus simple. Au moins :

    • Les liens Pocket (en cours mais c’est une tannée)
    • Les favo­ris Fire­fox Sync
    • Les abon­ne­ments RSS Inorea­der
    • Les abon­ne­ments, listes et peut-être messages Masto­don
    • Les abon­ne­ments, listes et peut-être messages Bluesky
    • Abon­ne­ments et messages Insta­gram
    • Les messages Tele­gram
    • Les messages What­sapp
    • Les messages Signal
    • Mes abon­ne­ments Newpipe (local Android)
    • Mon histo­rique et docu­ments Docto­lib
    • Mes contacts Linke­din, peut-être les messages aussi
    • Mes histo­riques Spotify, Netflix, etc.
    • Les discus­sions privées Slack
    • Les factures et histo­riques d’un peu partout (boutiques en ligne, abon­ne­ments divers, edf, sncf, hôtel, inter­net, télé­phone, etc.)
    • Les rele­vés sécu et mutuelle
    • Les rele­vés banque

    Je regrette qu’on n’ait pas un vrai gros projet Open Source dont l’objec­tif est d’avoir des connec­teurs pour tous les services en ligne de façon à rapa­trier toutes nos données en local.

    Cozy Cloud aurait pu faire ça mais la direc­tion prise ne se centrait pas sur les connec­teurs et le projet commer­cial n’a pas pu trou­ver sa place.


    Auto­ma­ti­sa­tion en trois niveaux

    Proba­ble­ment qu’il me faudra faire évoluer mes outils. Je ne peux pas lais­ser les mots de passe de tout et n’im­porte quoi en clair.

    J’ima­gine trois niveaux :

    1. Les sauve­gardes auto­ma­tiques. J’ai des token Oauth voire des mots de passe en clair dans les fichiers de confi­gu­ra­tion. C’est valable quand les données ne sont pas sensibles et que je tiens à ce que ça sauve­garde « sans moi »
    2. Les sauve­gardes que je lance­rai à la main, quand les données ou les mots de passe sont sensibles. Je pense faire des programmes qui s’in­ter­facent direc­te­ment avec le coffre Bitwar­den, que je déver­rouille­rai dans la session pour l’oc­ca­sion.
    3. Ce qui va être une énorme galère à coder : les service en ligne sans API ouverte avec de l’of­fus­ca­tion sur l’au­then­ti­fi­ca­tion, ainsi que les services en ligne derrière un 2FA non auto­ma­ti­sable ou un capt­cha complexe à mimer. Là j’ima­gine une exten­sion navi­ga­teur qui sauve­garde ce dont j’ai besoin quand je passe sur le site.

    Copie en ligne

    Bon, chaque chose en son temps.

    Avant tout ça il faudra déjà que je branche BorgBase ou BackB­laze pour avoir une copie chif­frée en ligne, parce que pour l’ins­tant ça ne fait que reco­pier en local.

    Avec dans les 3 To, ça me pren­dra bien un bon mois pour faire la première synchro­ni­sa­tion. Je sais envoyer plus vite mais je doute qu’on me libère des Gb/s pour moi tout seul.

  • Sauve­gar­der Bitwar­den

    Je conti­nue mes sauve­gardes.

    Le coffre avec tous mes mots de passe est parti­cu­lier. J’ai fait le choix de le stocker en ligne pour synchro­ni­ser tous mes appa­reils mais perdre tous mes mots de passe n’est pas une option.

    Bitwar­den a effec­ti­ve­ment une copie locale sur tous les appa­reils mais ça ne me couvre pas si quelque chose est supprimé sur le serveur et que la suppres­sion se réplique alors sur tous mes appa­reils.

    J’ai besoin d’une vraie copie locale, à moi.

    Je n’ai cepen­dant pas besoin que la copie soit en clair. Plus exac­te­ment, les données sont trop sensibles et je préfère n’avoir que la copie chif­frée. Je sais que je trou­ve­rais comment la déchif­frer à la main en cas de besoin (je l’ai déjà fait par le passé).

    Je pour­rais utili­ser la ligne de commande offi­cielle et synchro­ni­ser le coffre avec une clé d’API. Il n’y a pas besoin du mot de passe maitre pour ça. Je ne maitrise cepen­dant pas où il stocke le coffre et j’avais moyen­ne­ment envie de ça sur des tâches de sauve­garde.

    J’ai réim­plé­menté ça à la main avec un programme généré par IA. Il télé­charge les para­mètres de login, le profil utili­sa­teur et le coffre (chif­fré).


    C’est du Rust parce que j’es­pé­rais utili­ser le SDK offi­ciel. Malheu­reu­se­ment ils n’ex­portent pas les appels bas niveau que je souhaite. J’ai perdu bien trop long­temps à le comprendre et à batailler. J’ai fini par faire mon implé­men­ta­tion à la main.

    Si j’avais su que je fini­rais avec juste quelques appels HTTP, ça ne serait pas en Rust. Tant pis.

  • Sauve­gar­der les contacts Google

    Je conti­nue mes sauve­gardes mais je n’ai pas trouvé d’ou­til adapté pour aller télé­char­ger les contacts google. J’en suis même étonné.

    Je suis passé quelques minutes par IA et j’ai pu récu­pé­rer ce qui m’in­té­res­sait avec https://github.com/edas/google-contacts-backup

    Le programme récu­père le format brut en prove­nance de Google, avec juste une sauve­garde en base64 des images en lien.


    J’au­rais préféré un outil plus abouti qui sache ne reté­lé­charge que ce qui a changé. On verra une prochaine fois.

    Si toute­fois vous connais­sez un outil un peu smart, glis­sez-moi le nom en commen­taire.

  • Sauve­gar­der les calen­driers Google

    Je conti­nue mes sauve­gardes.

    Les calen­driers Google c’est fina­le­ment plus simple que le reste. Dans les para­mètres de chaque calen­drier, tout à la fin, il y a une adresse ics privée.

    Je peux me conten­ter de faire un appel régu­liè­re­ment et sauve­gar­der ça.

    /opt/homebrew/bin/wget -nv -O /path/to/target.ics https://calendar.google.com/calendar/ical/xxxxx/private-xxxxx/basic.ics

    Le plus gros calen­drier fait tout juste 2 Mo. Il change tous les jours mais ça reste encore un poids accep­table.


    Il faut juste bien penser à le faire pour chaque calen­drier, y compris ceux que je crée­rai dans le futur. Ça doit pouvoir s’au­to­ma­ti­ser via les API Google Calen­dar mais je ne suis pas certain que ça vaille le coup pour l’ins­tant.

  • Sauve­gar­der Stan­dard Notes

    Je conti­nue mes sauve­gardes.

    Stan­dard Notes j’ai beau­coup de choses dessus, surtout que j’y ai rapa­trier mes anciennes notes de nvalt puis de Simple Note.

    Il y a un méca­nisme spéci­fique de backup dans Stan­dard Notes, donc un qui permet d’en­voyer une archive par email toutes les semaines. Chez moi ça envoyait en plusieurs exem­plaires, et sans reti­rer le chif­fre­ment.

    Il y en a un autre qui permet de garder une trace locale. Je pour­rais l’ac­ti­ver sur mon poste quoti­dien et faire en sorte que cette trace locale soit ensuite récu­pé­rée par Google Drive ou Treso­rit. En pratique j’ai toujours eu des ennuis dès que je chaîne les outils de synchro­ni­sa­tion. Je préfère éviter.

    J’ai trouvé sn-cli, qui fait ce que j’es­pé­rais.

    #!/bin/sh
    APP=$1
    OUT=$2
    EMAIL=xxxx
    PASS='xxxx'
    
    SN_EMAIL=$EMAIL SN_PASSWORD=$PASS $APP get items > $OUT/standardnotes.items.json
    SN_EMAIL=$EMAIL SN_PASSWORD=$PASS $APP get notes > $OUT/standardnotes.notes.json
    SN_EMAIL=$EMAIL SN_PASSWORD=$PASS $APP get tags > $OUT/standardnotes.tags.json

    Ça me demande de lais­ser mon mot de passe en clair sur le disque de sauve­garde (qui lui est chif­fré). Pas idéal mais ce n’est pas un jeu de données très sensible alors ça peut passer.


    Plus tard je segmen­te­rai proba­ble­ment deux types de sauve­gardes, une auto­ma­tique et une que je lance­rai à la main qui utili­sera la CLI bitwar­den avec une session unique­ment le temps de la sauve­garde. Ce jour là ça bascu­lera peut-être dans mes sauve­gardes à la main. Ça reste accep­table entre temps.

  • Sauve­gar­der mes sites web

    Toujours dans mes sauve­gardes.

    J’en ai profité pour passer quelques sites statiques basiques direc­te­ment sur Github. Ils seront sauve­gar­dés avec Github.

    C’est moins évident pour les word­press et ce qui a une base de données. Là j’ai fait un sous-réper­toire « data » pour chaque projet avec un script cron qui y fait un dump quoti­dien.

    J’ai ensuite un rsync qui va cher­cher régu­liè­re­ment les données qui sont censées bouger (fichiers télé­char­gés, dump de base de données).

    J’ai enfin un script cron qui sauve­garde la confi­gu­ra­tion du serveur web (nginx, php) sur le serveur lui-même, et là aussi un rsync pour la rapa­trier en local.

    C’est fait à la main mais c’est proba­ble­ment suffi­sant;

  • Sauve­gar­der Github

    Bon, pas tout Github, juste les projets auxquels je parti­cipe.

    Il y a moyen de faire ça manuel­le­ment avec git clone --mirror. Je voulais quelque chose qui aille au moins lister mes projets pour les synchro­ni­ser auto­ma­tique­ment sans que j’ai besoin de les ajou­ter manuel­le­ment à mes scripts de sauve­garde.

    J’ai trouvé git-sync (atten­tion, il y a beau­coup d’ou­tils du même nom) qui fait exac­te­ment ce que je veux.

    La confi­gu­ra­tion

    # Repository settings
    include_forks: true # Include forked repositories
    include_wiki: true # Include wiki's
    include_repos: [] # Include specific repositories
    exclude_repos: [] # Exclude specific repositories
    include_orgs: [] # Include repositories from specific organizations
    exclude_orgs: [] # Exclude repositories from specific organizations
    raw_git_urls: [] # Raw valid git URLs
    
    # Authentication
    username: username
    tokens: [xxxxxx]
    
    # Server settings
    backup_dir: /path/to/backup-dir
    clone_type: mirror # Default: bare
    concurrency: 5
    retry:
      count: 3
      delay: 10 # in seconds
    platform: github
    server:
      domain: github.com
      protocol: https
    notification:
      enabled: false

    Et pour lancer git-sync --config /path/to/config.yaml

    Au premier essai ça a synchro­nisé énor­mé­ment de choses. Ça liste tous les dépôts où vous êtes membres, donc aussi tous les dépôts privés qui vous ont été parta­gés. Je me suis rendu compte qu’il y en avait un paquet pour moi, et j’ai pu faire un peu de ménage. Ça m’a aussi permis de marquer comme archives un paquet de vieux dépôts inuti­li­sés, et de suppri­mer des trucs vides ou à peine commen­cés.


    Je n’ai pas besoin de sauve­gar­der tout ça très fréquem­ment. Une fois par semaine devrait suffire. Main­te­nant, comme ça ne télé­charge que ce qui change, j’exé­cu­te­rai peut-être en quoti­dien.

  • Sauve­gar­der Google Drive et Google Photos

    Je conti­nue toujours sur mes sauve­gardes.

    Pour Google Drive j’au­rais pu utili­ser le client offi­ciel mais j’ai besoin qu’il synchro­nise même quand je n’ai pas de session ouverte. J’ai­me­rais aussi pouvoir gérer plusieurs utili­sa­teurs faci­le­ment.

    J’ai fouillé et trouvé rclone qui fait ça pour moi.

    La procé­dure pour créer ses propres clés d’API est un peu pénible mais le reste fonc­tionne assez faci­le­ment. J’ai créé une clé d’API pour chaque compte. Rétros­pec­ti­ve­ment je ne suis pas certain que ça valait le coup.

    L’ou­til est dispo­nible sous home­brew

    brew install rclone

    Ma config

    [gdrive-x]
    type = drive
    client_id = xxx.apps.googleusercontent.com
    client_secret = xxx
    scope = drive
    token = {"access_token":"xxxx","token_type":"Bearer","refresh_token":"xxx","expiry":"xxx"}
    team_drive = 
    
    [gphotos-x]
    type = gphotos
    client_id = xxxx.apps.googleusercontent.com
    client_secret = xxxx
    include_archived = true
    read_only = true
    start_year = 1970
    token = {"access_token":"xxxx","token_type":"Bearer","refresh_token":"xxx","expiry":"xxx"}
    

    Et mon script de lance­ment

    /opt/homebrew/bin/rclone --config=/path/to/rclone.conf --log-level NOTICE --fast-list --drive-skip-shortcuts sync gdrive-x: /path/to/gdrive-x/
    
    /opt/homebrew/bin/rclone --config=/path/to/rclone.conf --retries-sleep 10s --log-level NOTICE --fast-list --exclude /media/all/** --exclude /media/by-year/** --exclude /media/by-day/** --exclude /upload/** sync gphotos-survol: /path/to/gphotos-x/

    Le fonc­tion­ne­ment par défaut sur Google Photos implique beau­coup de doublons. La même image est sauve­gar­dée dans les albums où elle est, plus dans /media/all, dans l’an­née concer­née de /media/by-year, dans le mois concerné de /media/by-month et dans le jour concerné dans /media/by-day.

    On peut faci­le­ment exclure une partie de ces réper­toires (j’ai gardé la répar­ti­tion par mois, ils décon­seillent de garder celle par jour qui impose trop d’ap­pels à Google Photos). Toutes les images ne sont pas toute­fois dans des albums donc soit on a des doublons, soit on perd la notion d’al­bums.

    Il faut penser à exclure /upload aussi pour ne pas avoir d’er­reur inutiles. Ce réper­toire ne sert que pour envoyer des images à Google.

    La partie Photo est longue. Il est facile d’ou­tre­pas­ser les quotas qui sont assez réduit sur ce produit, même avec sa propre clé d’API. En cas de diffi­cul­tés on peut faire une première passe avec les options suivantes : --tpslimit 0.3 --tpslimit-burst 10 --low-level-retries 1 --retries 1 --transfers 1 --checkers 1. Atten­tion, dans ce cas ça va réel­le­ment durer des plombes.


    Je vais faire lancer ça entre une fois par jour et une fois par semaine, ça suffira très bien pour juste un filet de sécu­rité.

  • Amélio­rer ma sauve­garde Gmail

    J’ai détaillé la sauve­garde Gmail mais je me suis retrouvé avec un énorme mail­dir de plus de 30 Go.

    J’ai fina­le­ment préféré faire une boite par année. J’ar­rive à des tailles plus raison­nables, de quelques centaines de Mo à 4 Go pour la plus grosse.

    Pour ça j’ai utilisé un script généré pour l’oc­ca­sion : Lire chaque email, récu­pé­rer l’an­née, le dépo­ser dans un mail­dir spéci­fique à cette année.

    Par dessus j’ai aussi utilisé fdupes. J’ai l’im­pres­sion que soit Getmail a télé­chargé quelques doublons, soit (plus proba­ble­ment) j’ai créé des doublons dans ma boite Gmail quand j’ai bidouillé par le passé.

    fdupes -n -f -d -N -p -r by-year

  • Un petit programme par l’IA

    J’avance sur mes outils de sauve­garde mais aussi sur mes explo­ra­tions IA.

    J’ai eu besoin d’un second programme qui va lire tous les emails d’une boite au format mail­dir, regar­der l’an­née du mail, et le dépla­cer dans une boite mail­dir spéci­fique à cette année là.

    J’au­rais pu le faire en Javas­cript ou en Ruby mais vu ce que m’a fait l’IA en quelques minutes précé­dem­ment, je me suis dit que j’al­lais conti­nuer et refaire un script Go (je n’ai jamais codé une seule ligne de Go).

    Voici le résul­tat : github.com/edas/split-mail­dir-by-year.

    Les 300 lignes de Go ont analysé l’in­té­gra­lité de mon archive Gmail (537 000 emails quand même).

    • Je ne crois pas avoir touché le code source à la main
    • Le code a toujours compilé du premier coup
    • Le code a toujours fait ce que je souhai­tais, sans erreur

    Le code est correc­te­ment struc­turé, des fonc­tions ont été créées au fur et à mesure des besoins quand le code a évolué. Quand une fonc­tion est un peu longue, il sépare en blocs et ajoute une ligne de commen­taire pour dire ce que fait le bloc, ce qui me permet de ne pas avoir à déco­der un code dans un langage que je ne connais pas.

    10 demandes courtes pour avoir le programme et le faire évoluer vers mes besoins, ques­tions de relec­ture incluses. 17 ajouts par la suite pour trai­ter des cas spéci­fiques rencon­trés.

    Je n’au­rais pas fait plus vite moi-même, ni en Go ni dans un langage que je connais très bien. Peut-être que ça aurait été un peu plus diffi­cile pour un non-déve­lop­peur, mais je vois mal ce que j’au­rais eu à y gagner à le coder à la main.

    À chaque modi­fi­ca­tion j’ai le diff à vali­der mais aussi une bonne expli­ca­tion de l’IA sur ce qui a été modi­fié, comment et pourquoi. Ma relec­ture s’est souvent faite en diago­nale sur la base des commen­taires de code. L’IA a su répondre à mes ques­tions quand j’ai rencon­tré des éléments moins évide

    Plutôt que lister les étapes, je copie direc­te­ment mes prompts.


    I have hundreds thousands of files in the "maildir/new" directory. Each file contains a raw email with headers.

    I want a program which reads all emails one by one, look for the "Date" header, return an error in the header doesn't exists or is unreadable, and otherwise move the email file in the directory "by-year/{year}/new" where {year} is the year in the Date header.

    Je relis parce que ça va toucher des données réelles et que j’ai la flemme de faire des données de tests.

    Je vois qu’il retourne toujours une date même quand il y a une erreur. Inha­bi­tué de Go, j’ai peur de certaines erreurs de débu­tants en PHP ou en JS, où on utilise une date du jour plutôt que gérer l’er­reur. Je pose ma ques­tion et je suis rassuré par sa réponse (que je trouve logique après coup vu le fonc­tion­ne­ment des erreurs en Go)

    can the parseEmailDate return nil when it doesn't find a Date header ?

    Je vois aussi un mkdir sans test d’exis­tence préa­lable. Dans d’autres langages ça jette une erreur si le réper­toire existe. Je pose la ques­tion et là aussi je suis rassuré par sa réponse.

    what if the directory already exists line 63 ?

    Je demande une adap­ta­tion, non stric­te­ment néces­saire, pour que chaque réper­toire soit bien une boite mail­dir avec les 3 réper­toires obli­ga­toires. Ce n’est pas néces­saire à ma sauve­garde mais je préfère, au cas où ça m’évite des erreurs un jour.

    Oui, j’ai parfois basculé en français. Je ne sais ni pourquoi j’ai du français ici, ni pourquoi j’ai mis de l’an­glais avant. L’IA est confi­gu­rée pour toujours me répondre en français. Le code est toujours commenté en anglais. Je pense que propres entrées dépendent ce sur quoi mon atten­tion était à ce moment là (code, page web, etc.)

    Si le répertoire "by-year/{year}" n'existe pas, il faut aussi créer "by-year/{year}/cur" et "by-year/{year}/tmp", même si nous ne nous en servons pas

    Seconde adap­ta­tion : L’IA m’a dit plus haut qu’elle avait un code de gestion de conflit. Je vois le commen­taire dans le code qui dit qu’en cas de conflit le code ajoute un suffixe avec le times­tamp du moment pour éviter d’écra­ser un fichier exis­tant. Norma­le­ment ça ne devrait jamais arri­ver mais un suffixe risque­rait de casser le format de nommage des fichiers mail­dir donc je préfère qu’on s’ar­rête avec une erreur et que j’avise.

    if there is a conflict, to not append a timestamp to make it unique. Return an error.

    Troi­sième adap­ta­tion. Je traite un demi-million de fichiers. Je préfère que ça traite les fichiers au fil de l’eau plutôt qu’a­voir la liste d’un demi-million de fichiers en mémoire.

    Au départ c’est d’abord une ques­tion. Je ne sais pas si Go retourne un tableau ou un itéra­teur (oui, j’ai été flem­mard jusqu’à ne même pas faire atten­tion au typage). Je m’at­ten­dais à deman­der la correc­tion dans le premier cas. Au final il modi­fie de lui-même le code à partir de la seconde ques­tion pour faire des itéra­tions par lots 100 fichiers, sans que je ne le demande expli­ci­te­ment.

    En réalité c’est du script maison, qui sera lancé juste une poignée de fois. L’op­ti­mi­sa­tion est tota­le­ment inutile mais je n’ai pas encore appris à tota­le­ment lâcher prise vis-a-vis de ce que j’au­rais codé moi-même.

    What does return ReadDir in line 88 ?
    Si le répertoire contient des millions de fichiers, est-ce que la variable files ligne 88 va tout avoir en mémoire ?

    Je vais jouer avec de vraies données. Je veux voir les erreurs et m’ar­rê­ter pour corri­ger, pas que ça conti­nue et que j’ai à remon­ter voir s’il y a eu des erreurs.

    The programm should stop at the first error, not continue with the next file

    Et, parce que je n’y avais pas pensé avant :

    Le programme doit aussi prendre un chemin en argument. C'est là que se trouveront les différents répertoires prévus.

    La première phase est faite. Je passe au test en condi­tions réelles, sur le demi-million d’email de mon archive. Chaque fois que j’ai une erreur, je lui indique et j’avance.

    C’est là que je vois que chaque client email fait bien ce qu’il veut avec les entêtes. J’ai croisé un nombre inat­tendu de formats diffé­rents et d’er­reurs dans les entêtes. Chaque fois le programme m’af­fiche l’er­reur, je copie-colle la date problé­ma­tique, l’IA corrige, et je relance jusqu’à l’er­reur suivante.

    We should also parse the format for "Mon, 21 Aug 2006 16:47:08 +0200 (CEST)"
    We should also parse the date "Mon, 1 Dec 2008 10:57:10 UT"

    Sur une erreur étrange, j’ouvre l’email et je me rends compte qu’il prend en compte la conti­nua­tion d’une entête Recei­ved comme si c’était une date, parce qu’il ne prend pas en compte les espaces avant le mot clé Date.

    TrimSpave at line 30 should only trim right space, not left space

    Parti­cu­la­rité Gmail, quand il récu­père un email d’une boite tierce (via POP3 ou IMAP), il crée des entêtes à lui, saute une ligne et après pose le vrai email. Rétros­pec­ti­ve­ment je pense que j’au­rais dû reti­rer la section ajou­tée par Gmail pour retrou­ver un email normal. Je le ferais peut-être plus tard. Là je me suis contenté de lui faire contour­ner le problème.

    When we find the header "X-Gmail-fetch-Info", we should ignore the blank line following if it exists

    Encore des ques­tions de dates…

    We should be able to parse the Date "Tuesday 29 May 2007, 16:03"
    We should also parse "Wed, 03 Mar 2010 22:36:13 +0100 CET"
    We should also parse "Thu, 22 Jul 2010 23:02:50"
    We should also parse "Mon, 30 Mar 2009 20:11:22 +0100"

    Ce coup-ci ça ne corrige pas mon problème. Rétros­pec­ti­ve­ment j’au­rais pu le comprendre parce que le message d’er­reur n’était pas exac­te­ment le même, mais je le laisse trou­ver seul. Le mot clé DATE était en majus­cules, c’était la première fois.

    pourtant le script fait une erreur sur la ligne "DATE: Mon, 30 Mar 2009 20:11:22 +0100". Pourquoi ?

    Le code qu’il me génère imbrique quatre fonc­tions de mani­pu­la­tion de texte sur une seule ligne. Je ne trouve pas ça lisible. Je pose la ques­tion.

    que fait la ligne 49 ?

    Ça semble redon­dant avec la ligne suivante, pré-exis­tante. Effec­ti­ve­ment, quand je pose la ques­tion il iden­ti­fie le doublon et le supprime.

    Il faut penser à relire (même si l’er­reur aurait juste était du code inutile). Cursor me fait vali­der chaque chan­ge­ment sous forme de diff donc c’est assez rapide et facile à faire.

    que fait la ligne 50 ?

    Encore des formats de date…

    Encore un format : "mon, 10 jul 2006 01:02:08 gmt"
    encore un : "wed, 23 jun 2004 01:19:32 cest"
    encore un "mon, 22 mar 2010 14:20:15 +0100 +0100". C'est probablement une erreur d'écriture mais il faut la prendre en compte
    "wen, 16 jul 2008 22:09:05 +0200"

    Les deux derniers cas sont forcé­ment des erreurs de la part de clients emails. Pour la première erreur il choi­sit d’igno­rer toutes les répé­ti­tions du déca­lage horaire.

    La seconde erreur est inté­res­sante parce que « wen » est proba­ble­ment là pour « wed » (wednes­day). Il iden­ti­fie l’er­reur et ajoute un code qui remplace toute une liste d’er­reurs de frappes habi­tuelles pour les code courts de jour de la semaine. Parfait.

    "wed, 19 apr 2006 12:15 -0800"

    J’ai mon premier cas d’email sans entête « Date ». Je ne sais pas si c’est auto­risé ou non mais peu importe. Je lui dis de fouiller les entêtes « Recei­ved » à la place. Je sais que ces entêtes peuvent être sur plusieurs lignes.

    L’IA va plus loin que moi, sait que la date est en seconde posi­tion dans ces entêtes, et regarde unique­ment après le premier point virgule. Elle sais aussi comment s’ap­pellent ses entêtes sur plusieurs lignes (lignes de conti­nua­tion). Mieux que ce que j’au­rais fait.

    Je note que je tape vite, avec des erreurs de frappe, un guille­met en trop, etc. Peu importe, c’est destiné à l’IA. Me relire est super­flu : je peux reve­nir en arrière si c’est mal compris.

    If you don't find any Date header, try again to look if you can find a date somewhere in a "Received" header (theere may be multiple "Received" headers") or in the lines begining with a space and following a "Received" header