1181 lines
35 KiB
Markdown
1181 lines
35 KiB
Markdown
# Guide d'ouverture des fichiers .mbox de Google Takeout
|
|
|
|
Ce guide explique comment ouvrir et exploiter un fichier .mbox exporté par Google Takeout.
|
|
|
|
## 1. Qu'est-ce qu'un fichier .mbox
|
|
|
|
Le format **mbox** (mailbox) est un format standard pour stocker des collections d'emails. Google Takeout exporte les emails Gmail dans ce format.
|
|
|
|
### Structure du fichier :
|
|
- **Un seul fichier** contenant tous les emails
|
|
- **Format texte** avec séparateurs spéciaux
|
|
- **Headers et corps** des emails en texte brut
|
|
- **Pièces jointes** encodées en base64
|
|
|
|
### ⚠️ Limitations importantes concernant l'arborescence :
|
|
|
|
#### Ce qui est préservé :
|
|
- ✅ **Contenu des emails** (headers, corps, pièces jointes)
|
|
- ✅ **Métadonnées** (expéditeur, destinataire, date, sujet)
|
|
- ✅ **Labels Gmail** (dans les headers comme `X-Gmail-Labels`)
|
|
- ✅ **Conversations** (via `Message-ID` et `In-Reply-To`)
|
|
|
|
#### Ce qui est perdu :
|
|
- ❌ **Structure des dossiers Gmail** (hiérarchie)
|
|
- ❌ **Organisation en dossiers** telle qu'elle apparaît dans Gmail
|
|
- ❌ **Statuts des messages** (lu/non-lu, important, etc.)
|
|
- ❌ **Tri chronologique** exact des conversations
|
|
|
|
**Important** : Gmail utilise un système de **labels** plutôt que de vrais dossiers. Dans le .mbox, ces labels apparaissent comme :
|
|
```
|
|
X-Gmail-Labels: Important,Work,Project-Alpha
|
|
```
|
|
Ces labels sont inclus mais **pas l'arborescence visuelle** de Gmail.
|
|
|
|
## 2. Méthodes d'ouverture
|
|
|
|
### Méthode 1 : Thunderbird (Recommandée)
|
|
|
|
#### Import direct :
|
|
1. **Ouvrir Thunderbird**
|
|
2. **Outils** → **Importer** → **Importer à partir d'un fichier**
|
|
3. Sélectionner **"Fichier mbox"**
|
|
4. Choisir le fichier `.mbox` de Google Takeout
|
|
5. Sélectionner le dossier de destination
|
|
|
|
#### Avec ImportExportTools NG :
|
|
```bash
|
|
# Installer l'extension ImportExportTools NG d'abord
|
|
```
|
|
1. **Clic droit** sur un dossier dans Thunderbird
|
|
2. **ImportExportTools NG** → **Importer fichier mbox**
|
|
3. Sélectionner le fichier `.mbox`
|
|
4. Les emails apparaissent dans le dossier choisi
|
|
|
|
### Méthode 2 : Apple Mail (macOS)
|
|
|
|
1. **Ouvrir Apple Mail**
|
|
2. **Fichier** → **Importer des boîtes aux lettres**
|
|
3. Sélectionner **"Fichiers au format mbox"**
|
|
4. Choisir le fichier et importer
|
|
|
|
### Méthode 3 : Outils en ligne de commande
|
|
|
|
#### Avec Python et mailbox :
|
|
```python
|
|
#!/usr/bin/env python3
|
|
import mailbox
|
|
import email
|
|
from email.header import decode_header
|
|
|
|
def read_mbox(mbox_path):
|
|
"""
|
|
Lire et afficher les emails d'un fichier mbox
|
|
"""
|
|
mbox = mailbox.mbox(mbox_path)
|
|
|
|
for i, message in enumerate(mbox):
|
|
print(f"\n=== Email {i+1} ===")
|
|
|
|
# Décoder l'objet
|
|
subject = decode_header(message['Subject'])[0][0]
|
|
if isinstance(subject, bytes):
|
|
subject = subject.decode('utf-8')
|
|
print(f"Sujet: {subject}")
|
|
|
|
# Expéditeur et destinataire
|
|
print(f"De: {message['From']}")
|
|
print(f"À: {message['To']}")
|
|
print(f"Date: {message['Date']}")
|
|
|
|
# Corps du message
|
|
if message.is_multipart():
|
|
for part in message.walk():
|
|
if part.get_content_type() == "text/plain":
|
|
body = part.get_payload(decode=True)
|
|
if body:
|
|
print(f"Corps: {body.decode('utf-8', errors='ignore')[:200]}...")
|
|
else:
|
|
body = message.get_payload(decode=True)
|
|
if body:
|
|
print(f"Corps: {body.decode('utf-8', errors='ignore')[:200]}...")
|
|
|
|
# Utilisation
|
|
read_mbox('/path/to/gmail.mbox')
|
|
```
|
|
|
|
#### Avec mutt (Linux) :
|
|
```bash
|
|
# Installer mutt
|
|
sudo apt install mutt
|
|
|
|
# Ouvrir le fichier mbox
|
|
mutt -f /path/to/gmail.mbox
|
|
```
|
|
|
|
### Méthode 4 : Outils graphiques
|
|
|
|
#### MailStore Home (Windows - Gratuit) :
|
|
1. Télécharger MailStore Home
|
|
2. **Archiver** → **Email** → **Fichiers Email**
|
|
3. Sélectionner le format **"Unix Mailbox (mbox)"**
|
|
4. Choisir le fichier et importer
|
|
|
|
#### Evolution (Linux) :
|
|
1. **Fichier** → **Importer**
|
|
2. Sélectionner **"Importer un seul fichier"**
|
|
3. Choisir le fichier `.mbox`
|
|
|
|
## 3. Conversion vers d'autres formats
|
|
|
|
### Vers PST (Outlook) :
|
|
```python
|
|
#!/usr/bin/env python3
|
|
import mailbox
|
|
import os
|
|
|
|
def mbox_to_eml(mbox_path, output_dir):
|
|
"""
|
|
Convertir mbox vers fichiers EML individuels
|
|
"""
|
|
if not os.path.exists(output_dir):
|
|
os.makedirs(output_dir)
|
|
|
|
mbox = mailbox.mbox(mbox_path)
|
|
|
|
for i, message in enumerate(mbox):
|
|
# Créer un nom de fichier basé sur l'objet
|
|
subject = message.get('Subject', f'Email_{i}')
|
|
# Nettoyer le nom de fichier
|
|
filename = "".join(c for c in subject if c.isalnum() or c in (' ', '-', '_')).rstrip()
|
|
filename = f"{i:04d}_{filename[:50]}.eml"
|
|
|
|
# Écrire le fichier EML
|
|
with open(os.path.join(output_dir, filename), 'w', encoding='utf-8') as f:
|
|
f.write(str(message))
|
|
|
|
print(f"Exporté: {filename}")
|
|
|
|
# Utilisation
|
|
mbox_to_eml('/path/to/gmail.mbox', '/path/to/eml_output/')
|
|
```
|
|
|
|
### Vers CSV (métadonnées) :
|
|
```python
|
|
#!/usr/bin/env python3
|
|
import mailbox
|
|
import csv
|
|
from email.header import decode_header
|
|
from email.utils import parsedate_to_datetime
|
|
|
|
def mbox_to_csv(mbox_path, csv_path):
|
|
"""
|
|
Exporter les métadonnées mbox vers CSV
|
|
"""
|
|
mbox = mailbox.mbox(mbox_path)
|
|
|
|
with open(csv_path, 'w', newline='', encoding='utf-8') as csvfile:
|
|
writer = csv.writer(csvfile)
|
|
|
|
# En-têtes
|
|
writer.writerow(['Index', 'Date', 'De', 'À', 'Sujet', 'Taille'])
|
|
|
|
for i, message in enumerate(mbox):
|
|
# Décoder l'objet
|
|
subject = message.get('Subject', '')
|
|
if subject:
|
|
decoded = decode_header(subject)[0][0]
|
|
if isinstance(decoded, bytes):
|
|
subject = decoded.decode('utf-8', errors='ignore')
|
|
|
|
# Date
|
|
date_str = message.get('Date', '')
|
|
try:
|
|
date_obj = parsedate_to_datetime(date_str)
|
|
date_formatted = date_obj.strftime('%Y-%m-%d %H:%M:%S')
|
|
except:
|
|
date_formatted = date_str
|
|
|
|
# Taille du message
|
|
size = len(str(message))
|
|
|
|
writer.writerow([
|
|
i + 1,
|
|
date_formatted,
|
|
message.get('From', ''),
|
|
message.get('To', ''),
|
|
subject,
|
|
size
|
|
])
|
|
|
|
print(f"Export CSV terminé: {csv_path}")
|
|
|
|
# Utilisation
|
|
mbox_to_csv('/path/to/gmail.mbox', '/path/to/emails_metadata.csv')
|
|
```
|
|
|
|
## 4. Extraction des pièces jointes
|
|
|
|
```python
|
|
#!/usr/bin/env python3
|
|
import mailbox
|
|
import os
|
|
import email
|
|
|
|
def extract_attachments(mbox_path, output_dir):
|
|
"""
|
|
Extraire toutes les pièces jointes d'un fichier mbox
|
|
"""
|
|
if not os.path.exists(output_dir):
|
|
os.makedirs(output_dir)
|
|
|
|
mbox = mailbox.mbox(mbox_path)
|
|
attachment_count = 0
|
|
|
|
for i, message in enumerate(mbox):
|
|
if message.is_multipart():
|
|
for part in message.walk():
|
|
# Vérifier si c'est une pièce jointe
|
|
if part.get_content_disposition() == 'attachment':
|
|
filename = part.get_filename()
|
|
if filename:
|
|
# Nettoyer le nom de fichier
|
|
filename = f"{i:04d}_{filename}"
|
|
filepath = os.path.join(output_dir, filename)
|
|
|
|
# Sauvegarder la pièce jointe
|
|
with open(filepath, 'wb') as f:
|
|
f.write(part.get_payload(decode=True))
|
|
|
|
print(f"Pièce jointe extraite: {filename}")
|
|
attachment_count += 1
|
|
|
|
print(f"Total des pièces jointes extraites: {attachment_count}")
|
|
|
|
# Utilisation
|
|
extract_attachments('/path/to/gmail.mbox', '/path/to/attachments/')
|
|
```
|
|
|
|
## 5. Recherche dans le fichier mbox
|
|
|
|
```python
|
|
#!/usr/bin/env python3
|
|
import mailbox
|
|
import re
|
|
from email.header import decode_header
|
|
|
|
def search_mbox(mbox_path, search_term, search_in=['subject', 'body', 'from']):
|
|
"""
|
|
Rechercher dans un fichier mbox
|
|
"""
|
|
mbox = mailbox.mbox(mbox_path)
|
|
results = []
|
|
|
|
for i, message in enumerate(mbox):
|
|
found = False
|
|
|
|
# Recherche dans l'objet
|
|
if 'subject' in search_in:
|
|
subject = message.get('Subject', '')
|
|
if subject and search_term.lower() in subject.lower():
|
|
found = True
|
|
|
|
# Recherche dans l'expéditeur
|
|
if 'from' in search_in:
|
|
from_addr = message.get('From', '')
|
|
if search_term.lower() in from_addr.lower():
|
|
found = True
|
|
|
|
# Recherche dans le corps
|
|
if 'body' in search_in:
|
|
if message.is_multipart():
|
|
for part in message.walk():
|
|
if part.get_content_type() == "text/plain":
|
|
body = part.get_payload(decode=True)
|
|
if body and search_term.lower() in body.decode('utf-8', errors='ignore').lower():
|
|
found = True
|
|
break
|
|
else:
|
|
body = message.get_payload(decode=True)
|
|
if body and search_term.lower() in body.decode('utf-8', errors='ignore').lower():
|
|
found = True
|
|
|
|
if found:
|
|
subject = decode_header(message.get('Subject', ''))[0][0]
|
|
if isinstance(subject, bytes):
|
|
subject = subject.decode('utf-8', errors='ignore')
|
|
|
|
results.append({
|
|
'index': i + 1,
|
|
'subject': subject,
|
|
'from': message.get('From', ''),
|
|
'date': message.get('Date', '')
|
|
})
|
|
|
|
return results
|
|
|
|
# Utilisation
|
|
results = search_mbox('/path/to/gmail.mbox', 'contrat', ['subject', 'body'])
|
|
for result in results:
|
|
print(f"Email {result['index']}: {result['subject']} - {result['from']}")
|
|
```
|
|
|
|
## 6. Outils recommandés par plateforme
|
|
|
|
### Windows :
|
|
- **Thunderbird** (gratuit, multiplateforme)
|
|
- **MailStore Home** (gratuit pour usage personnel)
|
|
- **Outlook** (avec conversion préalable)
|
|
|
|
### macOS :
|
|
- **Apple Mail** (intégré)
|
|
- **Thunderbird** (gratuit)
|
|
|
|
### Linux :
|
|
- **Thunderbird** (recommandé)
|
|
- **Evolution** (GNOME)
|
|
- **mutt** (ligne de commande)
|
|
- **Scripts Python** (pour traitement automatisé)
|
|
|
|
## 7. Troubleshooting
|
|
|
|
### Problème : Fichier mbox corrompu
|
|
```bash
|
|
# Vérifier l'intégrité
|
|
file gmail.mbox
|
|
head -n 10 gmail.mbox
|
|
|
|
# Réparer si nécessaire (supprimer les caractères problématiques)
|
|
sed 's/\x00//g' gmail.mbox > gmail_clean.mbox
|
|
```
|
|
|
|
### Problème : Encodage des caractères
|
|
```python
|
|
# Forcer l'encodage UTF-8
|
|
with open('gmail.mbox', 'r', encoding='utf-8', errors='ignore') as f:
|
|
content = f.read()
|
|
```
|
|
|
|
### Problème : Fichier trop volumineux
|
|
```bash
|
|
# Diviser le fichier mbox
|
|
split -l 1000 gmail.mbox gmail_part_
|
|
|
|
# Chaque partie peut ensuite être importée séparément
|
|
```
|
|
|
|
## 8. Scripts utilitaires complets
|
|
|
|
### Script d'analyse complète d'un fichier mbox :
|
|
```python
|
|
#!/usr/bin/env python3
|
|
import mailbox
|
|
import os
|
|
import sys
|
|
from email.header import decode_header
|
|
from email.utils import parsedate_to_datetime
|
|
from collections import Counter
|
|
import argparse
|
|
|
|
def analyze_mbox(mbox_path):
|
|
"""
|
|
Analyser complètement un fichier mbox
|
|
"""
|
|
if not os.path.exists(mbox_path):
|
|
print(f"Erreur: Le fichier {mbox_path} n'existe pas")
|
|
return
|
|
|
|
print(f"Analyse du fichier: {mbox_path}")
|
|
print("=" * 50)
|
|
|
|
mbox = mailbox.mbox(mbox_path)
|
|
|
|
total_emails = 0
|
|
total_size = 0
|
|
senders = Counter()
|
|
years = Counter()
|
|
months = Counter()
|
|
has_attachments = 0
|
|
|
|
for message in mbox:
|
|
total_emails += 1
|
|
total_size += len(str(message))
|
|
|
|
# Analyser l'expéditeur
|
|
sender = message.get('From', 'Inconnu')
|
|
senders[sender] += 1
|
|
|
|
# Analyser la date
|
|
date_str = message.get('Date', '')
|
|
try:
|
|
date_obj = parsedate_to_datetime(date_str)
|
|
years[date_obj.year] += 1
|
|
months[f"{date_obj.year}-{date_obj.month:02d}"] += 1
|
|
except:
|
|
pass
|
|
|
|
# Vérifier les pièces jointes
|
|
if message.is_multipart():
|
|
for part in message.walk():
|
|
if part.get_content_disposition() == 'attachment':
|
|
has_attachments += 1
|
|
break
|
|
|
|
# Affichage des statistiques
|
|
print(f"Nombre total d'emails: {total_emails}")
|
|
print(f"Taille totale: {total_size / (1024*1024):.2f} MB")
|
|
print(f"Emails avec pièces jointes: {has_attachments}")
|
|
print(f"Taille moyenne par email: {total_size / total_emails / 1024:.2f} KB")
|
|
|
|
print("\nTop 10 des expéditeurs:")
|
|
for sender, count in senders.most_common(10):
|
|
print(f" {count:4d} - {sender}")
|
|
|
|
print("\nRépartition par année:")
|
|
for year in sorted(years.keys()):
|
|
print(f" {year}: {years[year]} emails")
|
|
|
|
print("\nDerniers mois:")
|
|
for month in sorted(months.keys())[-12:]:
|
|
print(f" {month}: {months[month]} emails")
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description='Analyser un fichier mbox')
|
|
parser.add_argument('mbox_file', help='Chemin vers le fichier mbox')
|
|
|
|
args = parser.parse_args()
|
|
analyze_mbox(args.mbox_file)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|
|
```
|
|
|
|
### Script de conversion universelle :
|
|
```python
|
|
#!/usr/bin/env python3
|
|
import mailbox
|
|
import os
|
|
import argparse
|
|
import json
|
|
from email.header import decode_header
|
|
|
|
def convert_mbox(mbox_path, output_format, output_dir):
|
|
"""
|
|
Convertir un fichier mbox vers différents formats
|
|
"""
|
|
if not os.path.exists(output_dir):
|
|
os.makedirs(output_dir)
|
|
|
|
mbox = mailbox.mbox(mbox_path)
|
|
|
|
if output_format == 'eml':
|
|
convert_to_eml(mbox, output_dir)
|
|
elif output_format == 'json':
|
|
convert_to_json(mbox, output_dir)
|
|
elif output_format == 'txt':
|
|
convert_to_txt(mbox, output_dir)
|
|
else:
|
|
print(f"Format {output_format} non supporté")
|
|
|
|
def convert_to_eml(mbox, output_dir):
|
|
"""Convertir vers des fichiers EML individuels"""
|
|
for i, message in enumerate(mbox):
|
|
filename = f"{i:06d}.eml"
|
|
with open(os.path.join(output_dir, filename), 'w', encoding='utf-8') as f:
|
|
f.write(str(message))
|
|
print(f"Exporté: {filename}")
|
|
|
|
def convert_to_json(mbox, output_dir):
|
|
"""Convertir vers JSON"""
|
|
emails = []
|
|
for i, message in enumerate(mbox):
|
|
email_data = {
|
|
'index': i,
|
|
'subject': decode_header_safe(message.get('Subject', '')),
|
|
'from': message.get('From', ''),
|
|
'to': message.get('To', ''),
|
|
'date': message.get('Date', ''),
|
|
'body': extract_body(message)
|
|
}
|
|
emails.append(email_data)
|
|
|
|
with open(os.path.join(output_dir, 'emails.json'), 'w', encoding='utf-8') as f:
|
|
json.dump(emails, f, indent=2, ensure_ascii=False)
|
|
print(f"Exporté: emails.json ({len(emails)} emails)")
|
|
|
|
def decode_header_safe(header):
|
|
"""Décoder un header de manière sécurisée"""
|
|
try:
|
|
decoded = decode_header(header)[0][0]
|
|
if isinstance(decoded, bytes):
|
|
return decoded.decode('utf-8', errors='ignore')
|
|
return decoded
|
|
except:
|
|
return header
|
|
|
|
def extract_body(message):
|
|
"""Extraire le corps d'un message"""
|
|
if message.is_multipart():
|
|
for part in message.walk():
|
|
if part.get_content_type() == "text/plain":
|
|
body = part.get_payload(decode=True)
|
|
if body:
|
|
return body.decode('utf-8', errors='ignore')
|
|
else:
|
|
body = message.get_payload(decode=True)
|
|
if body:
|
|
return body.decode('utf-8', errors='ignore')
|
|
return ""
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description='Convertir un fichier mbox')
|
|
parser.add_argument('mbox_file', help='Fichier mbox source')
|
|
parser.add_argument('format', choices=['eml', 'json', 'txt'], help='Format de sortie')
|
|
parser.add_argument('output_dir', help='Répertoire de sortie')
|
|
|
|
args = parser.parse_args()
|
|
convert_mbox(args.mbox_file, args.format, args.output_dir)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|
|
```
|
|
|
|
## 9. Utilisation avec Thunderbird (Détaillée)
|
|
|
|
### Import pas à pas :
|
|
1. **Télécharger ImportExportTools NG** :
|
|
```
|
|
https://addons.thunderbird.net/addon/importexporttools-ng/
|
|
```
|
|
|
|
2. **Installation de l'extension** :
|
|
- Outils → Modules complémentaires
|
|
- Installer depuis un fichier
|
|
- Redémarrer Thunderbird
|
|
|
|
3. **Import du fichier mbox** :
|
|
- Créer un nouveau dossier local
|
|
- Clic droit → ImportExportTools NG
|
|
- Importer fichier mbox
|
|
- Sélectionner le fichier de Google Takeout
|
|
|
|
4. **Vérification** :
|
|
- Compter les emails importés
|
|
- Vérifier quelques emails au hasard
|
|
- Tester la recherche
|
|
|
|
---
|
|
|
|
**Note importante** : Le format .mbox de Google Takeout est un standard ouvert qui peut être lu par la plupart des clients email. Thunderbird reste la solution la plus simple et fiable pour ouvrir ces fichiers.
|
|
|
|
## 10. Reconstitution de l'arborescence Gmail
|
|
|
|
### Pourquoi l'arborescence est perdue :
|
|
1. **Format historique** : .mbox date des années 1970, avant les dossiers hierarchiques
|
|
2. **Gmail utilise des labels** : Pas de vraie hiérarchie de dossiers
|
|
3. **Un seul fichier** : Tous les emails dans un flux continu
|
|
4. **Pas de métadonnées de structure** : Le format ne stocke pas l'organisation visuelle
|
|
|
|
### Script pour extraire les labels Gmail :
|
|
```python
|
|
#!/usr/bin/env python3
|
|
import mailbox
|
|
import re
|
|
from collections import defaultdict
|
|
|
|
def extract_gmail_structure(mbox_path):
|
|
"""
|
|
Extraire la structure des labels Gmail depuis un fichier mbox
|
|
"""
|
|
mbox = mailbox.mbox(mbox_path)
|
|
|
|
folders = defaultdict(list)
|
|
conversations = defaultdict(list)
|
|
|
|
for i, message in enumerate(mbox):
|
|
# Extraire les labels Gmail
|
|
gmail_labels = message.get('X-Gmail-Labels', '')
|
|
if gmail_labels:
|
|
labels = [label.strip() for label in gmail_labels.split(',')]
|
|
|
|
for label in labels:
|
|
# Nettoyer les labels système Gmail
|
|
clean_label = clean_gmail_label(label)
|
|
if clean_label:
|
|
folders[clean_label].append({
|
|
'index': i,
|
|
'subject': message.get('Subject', ''),
|
|
'from': message.get('From', ''),
|
|
'date': message.get('Date', ''),
|
|
'message_id': message.get('Message-ID', '')
|
|
})
|
|
|
|
# Identifier les conversations
|
|
msg_id = message.get('Message-ID', '')
|
|
in_reply_to = message.get('In-Reply-To', '')
|
|
references = message.get('References', '')
|
|
|
|
if in_reply_to or references:
|
|
thread_id = in_reply_to or references.split()[0] if references else msg_id
|
|
conversations[thread_id].append({
|
|
'message_id': msg_id,
|
|
'subject': message.get('Subject', ''),
|
|
'index': i
|
|
})
|
|
|
|
return folders, conversations
|
|
|
|
def clean_gmail_label(label):
|
|
"""
|
|
Nettoyer les labels système Gmail
|
|
"""
|
|
# Labels système à ignorer
|
|
system_labels = {
|
|
'INBOX', 'SENT', 'DRAFT', 'SPAM', 'TRASH', 'UNREAD',
|
|
'STARRED', 'IMPORTANT', 'CATEGORY_PERSONAL', 'CATEGORY_SOCIAL',
|
|
'CATEGORY_PROMOTIONS', 'CATEGORY_UPDATES', 'CATEGORY_FORUMS'
|
|
}
|
|
|
|
if label in system_labels:
|
|
return None
|
|
|
|
# Décoder les labels avec caractères spéciaux
|
|
if label.startswith('"') and label.endswith('"'):
|
|
label = label[1:-1]
|
|
|
|
return label
|
|
|
|
def generate_folder_structure(folders):
|
|
"""
|
|
Générer une structure de dossiers lisible
|
|
"""
|
|
print("Structure des dossiers extraite de Gmail:")
|
|
print("=" * 50)
|
|
|
|
for folder_name, emails in sorted(folders.items()):
|
|
print(f"\n📁 {folder_name} ({len(emails)} emails)")
|
|
|
|
# Afficher les 5 premiers emails du dossier
|
|
for email in emails[:5]:
|
|
subject = email['subject'][:50] + "..." if len(email['subject']) > 50 else email['subject']
|
|
print(f" 📧 {subject} - {email['from']}")
|
|
|
|
if len(emails) > 5:
|
|
print(f" ... et {len(emails) - 5} autres emails")
|
|
|
|
# Utilisation
|
|
folders, conversations = extract_gmail_structure('/path/to/gmail.mbox')
|
|
generate_folder_structure(folders)
|
|
```
|
|
|
|
### Reconstitution pour Thunderbird :
|
|
```python
|
|
#!/usr/bin/env python3
|
|
import mailbox
|
|
import os
|
|
|
|
def create_thunderbird_folders(mbox_path, output_dir):
|
|
"""
|
|
Créer des fichiers mbox séparés par label pour Thunderbird
|
|
"""
|
|
if not os.path.exists(output_dir):
|
|
os.makedirs(output_dir)
|
|
|
|
mbox = mailbox.mbox(mbox_path)
|
|
folders = {}
|
|
|
|
# Créer des fichiers mbox par label
|
|
for message in mbox:
|
|
gmail_labels = message.get('X-Gmail-Labels', '')
|
|
if gmail_labels:
|
|
labels = [label.strip() for label in gmail_labels.split(',')]
|
|
|
|
for label in labels:
|
|
clean_label = clean_gmail_label(label)
|
|
if clean_label:
|
|
# Créer le fichier mbox pour ce label s'il n'existe pas
|
|
if clean_label not in folders:
|
|
folder_path = os.path.join(output_dir, f"{clean_label}.mbox")
|
|
folders[clean_label] = mailbox.mbox(folder_path)
|
|
|
|
# Ajouter le message au dossier approprié
|
|
folders[clean_label].add(message)
|
|
|
|
# Fermer tous les fichiers
|
|
for folder in folders.values():
|
|
folder.close()
|
|
|
|
print(f"Dossiers créés dans {output_dir}:")
|
|
for folder_name in folders.keys():
|
|
print(f" - {folder_name}.mbox")
|
|
|
|
# Utilisation
|
|
create_thunderbird_folders('/path/to/gmail.mbox', '/path/to/thunderbird_folders/')
|
|
```
|
|
|
|
### Alternatives pour préserver l'arborescence :
|
|
|
|
#### 1. **Export par dossier Gmail** :
|
|
- Utiliser Google Takeout en sélectionnant "Inclure tous les messages dans Mail"
|
|
- Choisir "Exporter par dossier" si disponible
|
|
|
|
#### 2. **Scripts d'export personnalisés** :
|
|
```python
|
|
# Utiliser l'API Gmail pour exporter avec structure
|
|
from googleapiclient.discovery import build
|
|
|
|
def export_with_structure(service, user_id='me'):
|
|
"""
|
|
Exporter avec préservation de la structure
|
|
"""
|
|
# Lister tous les labels
|
|
labels = service.users().labels().list(userId=user_id).execute()
|
|
|
|
for label in labels['labels']:
|
|
label_id = label['id']
|
|
label_name = label['name']
|
|
|
|
# Exporter les messages de ce label
|
|
messages = service.users().messages().list(
|
|
userId=user_id,
|
|
labelIds=[label_id]
|
|
).execute()
|
|
|
|
# Créer un fichier mbox pour ce label
|
|
# ... code d'export ...
|
|
```
|
|
|
|
### Recommandations pratiques :
|
|
|
|
#### Pour une migration complète :
|
|
1. **Utiliser l'API Gmail** directement plutôt que Takeout
|
|
2. **Exporter par labels** avec scripts personnalisés
|
|
3. **Documenter la structure** avant export
|
|
4. **Reconstituer manuellement** les dossiers importants
|
|
|
|
#### Pour l'archivage simple :
|
|
1. **Accepter la perte de structure** pour l'archivage légal
|
|
2. **Utiliser les labels** dans les métadonnées pour retrouver les emails
|
|
3. **Créer un index** des emails importants avec leurs labels
|
|
|
|
## 11. Conversion mbox vers Maildir (format Dovecot)
|
|
|
|
Le format **Maildir** est utilisé par de nombreux serveurs de messagerie modernes comme Dovecot, Postfix, et Courier. Contrairement au format mbox (un seul fichier), Maildir stocke chaque email dans un fichier séparé.
|
|
|
|
### Avantages du format Maildir :
|
|
- **Sécurité** : Pas de corruption si un email est corrompu
|
|
- **Performance** : Accès plus rapide aux emails individuels
|
|
- **Concurrence** : Plusieurs processus peuvent accéder simultanément
|
|
- **Fiabilité** : Moins de risques de perte de données
|
|
|
|
### Structure Maildir :
|
|
```
|
|
Maildir/
|
|
├── cur/ # Messages lus
|
|
├── new/ # Messages non lus
|
|
├── tmp/ # Messages temporaires
|
|
└── .Subfolder/ # Sous-dossiers (avec point en préfixe)
|
|
├── cur/
|
|
├── new/
|
|
└── tmp/
|
|
```
|
|
|
|
### Méthode 1 : Avec Python (Script personnalisé)
|
|
|
|
#### Script de conversion complet :
|
|
```python
|
|
#!/usr/bin/env python3
|
|
import mailbox
|
|
import os
|
|
import time
|
|
import email.utils
|
|
import hashlib
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
def mbox_to_maildir(mbox_path, maildir_path):
|
|
"""
|
|
Convertir un fichier mbox vers le format Maildir
|
|
"""
|
|
# Créer la structure Maildir
|
|
create_maildir_structure(maildir_path)
|
|
|
|
# Ouvrir le fichier mbox source
|
|
mbox = mailbox.mbox(mbox_path)
|
|
maildir = mailbox.Maildir(maildir_path)
|
|
|
|
print(f"Conversion de {mbox_path} vers {maildir_path}")
|
|
print("=" * 50)
|
|
|
|
converted_count = 0
|
|
error_count = 0
|
|
|
|
for i, message in enumerate(mbox):
|
|
try:
|
|
# Ajouter le message au Maildir
|
|
key = maildir.add(message)
|
|
converted_count += 1
|
|
|
|
if converted_count % 100 == 0:
|
|
print(f"Converti {converted_count} messages...")
|
|
|
|
except Exception as e:
|
|
print(f"Erreur pour le message {i}: {e}")
|
|
error_count += 1
|
|
|
|
print(f"\nConversion terminée:")
|
|
print(f" Messages convertis: {converted_count}")
|
|
print(f" Erreurs: {error_count}")
|
|
print(f" Maildir créé dans: {maildir_path}")
|
|
|
|
def create_maildir_structure(maildir_path):
|
|
"""
|
|
Créer la structure de base d'un Maildir
|
|
"""
|
|
Path(maildir_path).mkdir(parents=True, exist_ok=True)
|
|
Path(maildir_path, 'cur').mkdir(exist_ok=True)
|
|
Path(maildir_path, 'new').mkdir(exist_ok=True)
|
|
Path(maildir_path, 'tmp').mkdir(exist_ok=True)
|
|
|
|
def mbox_to_maildir_with_labels(mbox_path, base_maildir_path):
|
|
"""
|
|
Convertir mbox vers Maildir en créant des sous-dossiers par label Gmail
|
|
"""
|
|
mbox = mailbox.mbox(mbox_path)
|
|
|
|
# Analyser les labels d'abord
|
|
labels_map = analyze_gmail_labels(mbox_path)
|
|
|
|
# Créer le Maildir principal
|
|
create_maildir_structure(base_maildir_path)
|
|
main_maildir = mailbox.Maildir(base_maildir_path)
|
|
|
|
# Créer les sous-dossiers Maildir pour chaque label
|
|
label_maildirs = {}
|
|
for label in labels_map.keys():
|
|
if label and label not in ['INBOX', 'SENT', 'DRAFT']:
|
|
label_path = os.path.join(base_maildir_path, f'.{label}')
|
|
create_maildir_structure(label_path)
|
|
label_maildirs[label] = mailbox.Maildir(label_path)
|
|
|
|
print(f"Conversion avec labels vers {base_maildir_path}")
|
|
print("=" * 50)
|
|
|
|
# Convertir les messages
|
|
for i, message in enumerate(mbox):
|
|
try:
|
|
# Ajouter au Maildir principal
|
|
main_maildir.add(message)
|
|
|
|
# Extraire les labels Gmail
|
|
gmail_labels = message.get('X-Gmail-Labels', '')
|
|
if gmail_labels:
|
|
labels = [label.strip() for label in gmail_labels.split(',')]
|
|
|
|
for label in labels:
|
|
clean_label = clean_gmail_label(label)
|
|
if clean_label and clean_label in label_maildirs:
|
|
# Ajouter aussi au sous-dossier correspondant
|
|
label_maildirs[clean_label].add(message)
|
|
|
|
if (i + 1) % 100 == 0:
|
|
print(f"Converti {i + 1} messages...")
|
|
|
|
except Exception as e:
|
|
print(f"Erreur pour le message {i}: {e}")
|
|
|
|
# Fermer tous les Maildir
|
|
main_maildir.close()
|
|
for md in label_maildirs.values():
|
|
md.close()
|
|
|
|
print(f"\nConversion terminée avec {len(label_maildirs)} sous-dossiers")
|
|
|
|
def analyze_gmail_labels(mbox_path):
|
|
"""
|
|
Analyser les labels Gmail présents dans le mbox
|
|
"""
|
|
mbox = mailbox.mbox(mbox_path)
|
|
labels_count = {}
|
|
|
|
for message in mbox:
|
|
gmail_labels = message.get('X-Gmail-Labels', '')
|
|
if gmail_labels:
|
|
labels = [label.strip() for label in gmail_labels.split(',')]
|
|
for label in labels:
|
|
clean_label = clean_gmail_label(label)
|
|
if clean_label:
|
|
labels_count[clean_label] = labels_count.get(clean_label, 0) + 1
|
|
|
|
return labels_count
|
|
|
|
def clean_gmail_label(label):
|
|
"""
|
|
Nettoyer les labels Gmail pour les noms de dossiers
|
|
"""
|
|
system_labels = {
|
|
'INBOX', 'SENT', 'DRAFT', 'SPAM', 'TRASH', 'UNREAD',
|
|
'STARRED', 'IMPORTANT', 'CATEGORY_PERSONAL', 'CATEGORY_SOCIAL',
|
|
'CATEGORY_PROMOTIONS', 'CATEGORY_UPDATES', 'CATEGORY_FORUMS'
|
|
}
|
|
|
|
if label in system_labels:
|
|
return None
|
|
|
|
# Nettoyer le nom pour le système de fichiers
|
|
clean = label.replace('"', '').replace('/', '_').replace(' ', '_')
|
|
return clean if clean else None
|
|
|
|
def main():
|
|
if len(sys.argv) < 3:
|
|
print("Usage: python3 mbox2maildir.py <fichier.mbox> <dossier_maildir> [--with-labels]")
|
|
sys.exit(1)
|
|
|
|
mbox_path = sys.argv[1]
|
|
maildir_path = sys.argv[2]
|
|
with_labels = len(sys.argv) > 3 and sys.argv[3] == '--with-labels'
|
|
|
|
if not os.path.exists(mbox_path):
|
|
print(f"Erreur: Le fichier {mbox_path} n'existe pas")
|
|
sys.exit(1)
|
|
|
|
if with_labels:
|
|
mbox_to_maildir_with_labels(mbox_path, maildir_path)
|
|
else:
|
|
mbox_to_maildir(mbox_path, maildir_path)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|
|
```
|
|
|
|
### Méthode 2 : Avec mb2md (outil spécialisé)
|
|
|
|
#### Installation :
|
|
```bash
|
|
# Sur Ubuntu/Debian
|
|
sudo apt install mb2md
|
|
|
|
# Sur CentOS/RHEL
|
|
sudo yum install mb2md
|
|
|
|
# Ou téléchargement manuel
|
|
wget http://batleth.sapienti-sat.org/projects/mb2md/mb2md-3.20.pl
|
|
chmod +x mb2md-3.20.pl
|
|
```
|
|
|
|
#### Utilisation :
|
|
```bash
|
|
# Conversion simple
|
|
mb2md -s /path/to/gmail.mbox -d /path/to/maildir
|
|
|
|
# Avec création de sous-dossiers
|
|
mb2md -s /path/to/gmail.mbox -d /path/to/maildir -R
|
|
|
|
# Verbose pour voir le progrès
|
|
mb2md -s /path/to/gmail.mbox -d /path/to/maildir -v
|
|
```
|
|
|
|
### Méthode 3 : Avec formail et procmail
|
|
|
|
#### Script bash utilisant formail :
|
|
```bash
|
|
#!/bin/bash
|
|
# Convertir mbox vers Maildir avec formail
|
|
|
|
MBOX_FILE="$1"
|
|
MAILDIR_PATH="$2"
|
|
|
|
if [ -z "$MBOX_FILE" ] || [ -z "$MAILDIR_PATH" ]; then
|
|
echo "Usage: $0 <fichier.mbox> <dossier_maildir>"
|
|
exit 1
|
|
fi
|
|
|
|
# Créer la structure Maildir
|
|
mkdir -p "$MAILDIR_PATH"/{cur,new,tmp}
|
|
|
|
echo "Conversion de $MBOX_FILE vers $MAILDIR_PATH"
|
|
|
|
# Utiliser formail pour séparer les messages
|
|
cat "$MBOX_FILE" | formail -ds sh -c '
|
|
# Générer un nom de fichier unique
|
|
TIMESTAMP=$(date +%s)
|
|
RANDOM_NUM=$RANDOM
|
|
HOSTNAME=$(hostname)
|
|
FILENAME="${TIMESTAMP}.${RANDOM_NUM}.${HOSTNAME}"
|
|
|
|
# Sauvegarder le message dans new/
|
|
cat > "'$MAILDIR_PATH'/new/$FILENAME"
|
|
echo "Message sauvé: $FILENAME"
|
|
'
|
|
|
|
echo "Conversion terminée"
|
|
echo "Messages dans: $MAILDIR_PATH/new/"
|
|
ls -1 "$MAILDIR_PATH/new/" | wc -l | xargs echo "Nombre de messages:"
|
|
```
|
|
|
|
### Méthode 4 : Avec Dovecot dsync
|
|
|
|
#### Si Dovecot est installé :
|
|
```bash
|
|
# Créer une configuration temporaire Dovecot
|
|
cat > /tmp/dovecot-convert.conf << EOF
|
|
mail_location = mbox:~/mbox:INBOX=/path/to/gmail.mbox
|
|
namespace inbox {
|
|
inbox = yes
|
|
location =
|
|
mailbox Drafts {
|
|
special_use = \Drafts
|
|
}
|
|
mailbox Junk {
|
|
special_use = \Junk
|
|
}
|
|
mailbox Sent {
|
|
special_use = \Sent
|
|
}
|
|
mailbox "Sent Messages" {
|
|
special_use = \Sent
|
|
}
|
|
mailbox Trash {
|
|
special_use = \Trash
|
|
}
|
|
prefix =
|
|
}
|
|
EOF
|
|
|
|
# Conversion avec dsync
|
|
doveadm -c /tmp/dovecot-convert.conf backup -u user@domain.com maildir:/path/to/output/maildir
|
|
```
|
|
|
|
### Vérification de la conversion
|
|
|
|
#### Script de vérification :
|
|
```python
|
|
#!/usr/bin/env python3
|
|
import mailbox
|
|
import os
|
|
|
|
def verify_conversion(mbox_path, maildir_path):
|
|
"""
|
|
Vérifier que la conversion mbox -> maildir est correcte
|
|
"""
|
|
print("Vérification de la conversion")
|
|
print("=" * 30)
|
|
|
|
# Compter les messages dans le mbox original
|
|
mbox = mailbox.mbox(mbox_path)
|
|
mbox_count = len(mbox)
|
|
print(f"Messages dans mbox: {mbox_count}")
|
|
|
|
# Compter les messages dans le Maildir
|
|
maildir = mailbox.Maildir(maildir_path)
|
|
maildir_count = len(maildir)
|
|
print(f"Messages dans Maildir: {maildir_count}")
|
|
|
|
# Vérifier la structure
|
|
cur_count = len(os.listdir(os.path.join(maildir_path, 'cur')))
|
|
new_count = len(os.listdir(os.path.join(maildir_path, 'new')))
|
|
print(f" - Dans cur/: {cur_count}")
|
|
print(f" - Dans new/: {new_count}")
|
|
|
|
# Résultat
|
|
if mbox_count == maildir_count:
|
|
print("✅ Conversion réussie!")
|
|
else:
|
|
print(f"❌ Différence détectée: {mbox_count - maildir_count} messages")
|
|
|
|
return mbox_count == maildir_count
|
|
|
|
# Utilisation
|
|
verify_conversion('/path/to/gmail.mbox', '/path/to/maildir')
|
|
```
|
|
|
|
### Optimisations et bonnes pratiques
|
|
|
|
#### Pour de gros fichiers mbox :
|
|
```python
|
|
def mbox_to_maildir_chunked(mbox_path, maildir_path, chunk_size=1000):
|
|
"""
|
|
Conversion par chunks pour économiser la mémoire
|
|
"""
|
|
mbox = mailbox.mbox(mbox_path)
|
|
maildir = mailbox.Maildir(maildir_path)
|
|
|
|
count = 0
|
|
for message in mbox:
|
|
maildir.add(message)
|
|
count += 1
|
|
|
|
if count % chunk_size == 0:
|
|
print(f"Traité {count} messages...")
|
|
# Forcer l'écriture
|
|
maildir.flush()
|
|
|
|
maildir.close()
|
|
print(f"Conversion terminée: {count} messages")
|
|
```
|
|
|
|
#### Préservation des métadonnées :
|
|
```python
|
|
def preserve_metadata(message, maildir_path, filename):
|
|
"""
|
|
Préserver les métadonnées lors de la conversion
|
|
"""
|
|
# Extraire la date du message
|
|
date_str = message.get('Date', '')
|
|
if date_str:
|
|
try:
|
|
import email.utils
|
|
timestamp = email.utils.mktime_tz(email.utils.parsedate_tz(date_str))
|
|
|
|
# Appliquer la date au fichier
|
|
filepath = os.path.join(maildir_path, 'new', filename)
|
|
os.utime(filepath, (timestamp, timestamp))
|
|
except:
|
|
pass
|
|
```
|
|
|
|
### Utilisation avec différents serveurs
|
|
|
|
#### Pour Dovecot :
|
|
```bash
|
|
# Configuration dans dovecot.conf
|
|
mail_location = maildir:~/Maildir
|
|
|
|
# Test de la configuration
|
|
doveadm mailbox list -u user@domain.com
|
|
```
|
|
|
|
#### Pour Postfix + Dovecot :
|
|
```bash
|
|
# Dans main.cf
|
|
home_mailbox = Maildir/
|
|
|
|
# Redémarrage des services
|
|
systemctl restart postfix dovecot
|
|
```
|
|
|
|
#### Pour Courier :
|
|
```bash
|
|
# Courier utilise maildir par défaut
|
|
# Configuration dans /etc/courier/authdaemonrc
|
|
```
|
|
|
|
### Commandes utiles post-conversion
|
|
|
|
```bash
|
|
# Statistiques du Maildir
|
|
find /path/to/maildir -name "*" -type f | wc -l
|
|
|
|
# Vérifier l'intégrité
|
|
for dir in cur new tmp; do
|
|
echo "$dir: $(ls /path/to/maildir/$dir | wc -l) messages"
|
|
done
|
|
|
|
# Indexation Dovecot (si applicable)
|
|
doveadm index -u user@domain.com INBOX
|
|
|
|
# Reconstruction des index
|
|
doveadm force-resync -u user@domain.com '*'
|
|
```
|
|
|
|
**Date de création** : 22 octobre 2025 |