improvement while reading mails

This commit is contained in:
2026-04-24 18:20:41 +02:00
parent 64e47e05e7
commit 3a3a36082b
6 changed files with 532 additions and 277 deletions
+121 -2
View File
@@ -26,6 +26,8 @@ from dataclasses import dataclass, field
from email.message import Message
from typing import List, Optional, Tuple
import random
import socks
from dotenv import load_dotenv
from imapclient import IMAPClient
@@ -40,7 +42,7 @@ VALIDATION_URL_SUBJECT_FR = "Validation de votre demande de rendez-vous"
VALIDATION_URL_SUBJECT_EN = "Please confirm your appointment request"
VALIDATION_URL_REGEX = (
r"https:\/\/rendezvousparis\.hermes\.com"
r"\/client\/register\/[A-Z0-9]+\/validate\.code=[A-Z0-9]+"
r"\/client\/register\/[A-Z0-9]+\/validate[?.]code=[A-Z0-9]+"
)
DATE_FORMAT = "%d-%b-%Y"
@@ -198,11 +200,122 @@ class ProxyIMAP4_TLS(imaplib.IMAP4):
def shutdown(self) -> None:
imaplib.IMAP4.shutdown(self)
def id(self, parameters: dict) -> tuple:
"""
Envoie la commande IMAP ID (RFC 2971).
parameters : dict ex. {"name": "Thunderbird", "version": "115.0"}
Retourne le tuple brut (typ, data) renvoyé par le serveur.
"""
args = " ".join(
'"{}"'.format(str(v).replace('"', '\\"'))
for pair in parameters.items()
for v in pair
)
return self._simple_command("ID", "({})".format(args))
# ──────────────────────────────────────────────────────────────
# IMAPClient avec proxy
# Profils de clients IMAP réels (pour spoofing du fingerprint)
# ──────────────────────────────────────────────────────────────
_IMAP_CLIENT_PROFILES = [
# Mozilla Thunderbird 115 (ESR) — Windows
{
"name": "Thunderbird",
"version": "115.9.0",
"vendor": "Mozilla",
"support-url": "https://support.mozilla.org/",
"command": "IMAP4rev1",
"os": "Windows NT 10.0",
"os-version": "10.0",
},
# Mozilla Thunderbird 115 — macOS
{
"name": "Thunderbird",
"version": "115.9.0",
"vendor": "Mozilla",
"support-url": "https://support.mozilla.org/",
"command": "IMAP4rev1",
"os": "macOS",
"os-version": "14.4",
},
# Apple Mail — macOS Sonoma
{
"name": "Mac OS X Mail",
"version": "16.0",
"vendor": "Apple Inc.",
"support-url": "https://support.apple.com/mail",
"os": "Mac OS X",
"os-version": "14.4",
},
# Apple Mail — iOS
{
"name": "iPhone Mail",
"version": "17.4",
"vendor": "Apple Inc.",
"os": "iOS",
"os-version": "17.4",
},
# Outlook pour Windows (MAPI/IMAP bridge)
{
"name": "Microsoft Outlook",
"version": "16.0.17531.20108",
"vendor": "Microsoft Corporation",
"support-url": "https://support.microsoft.com/outlook",
"os": "Windows NT 10.0",
"os-version": "10.0",
},
]
def _random_imap_id_params() -> dict:
"""Retourne un profil aléatoire parmi les clients IMAP réels."""
return random.choice(_IMAP_CLIENT_PROFILES)
def send_imap_id(imap, params: Optional[dict] = None) -> None:
"""
Envoie la commande IMAP ID après connexion pour usurper le fingerprint
client. Fonctionne avec IMAPClient (imapclient) et imaplib.IMAP4.
Paramètres
----------
imap : IMAPClient | imaplib.IMAP4
params : dict, optional — si None, un profil aléatoire est choisi.
"""
if params is None:
params = _random_imap_id_params()
try:
if isinstance(imap, IMAPClient):
# imapclient expose _imap (l'objet imaplib sous-jacent)
_raw = imap._imap
if hasattr(_raw, "id"):
_raw.id(params)
else:
# Fallback : commande brute via imapclient
args = " ".join(
'"{}"'.format(str(v).replace('"', '\\"'))
for pair in params.items()
for v in pair
)
imap._imap._simple_command("ID", "({})".format(args))
elif hasattr(imap, "id"):
# ProxyIMAP4_TLS ou tout imaplib.IMAP4 patchable
imap.id(params)
else:
# Dernier recours : commande brute imaplib
args = " ".join(
'"{}"'.format(str(v).replace('"', '\\"'))
for pair in params.items()
for v in pair
)
imap._simple_command("ID", "({})".format(args))
except Exception as exc:
logger.debug("IMAP ID non supporté ou ignoré : %s", exc)
class ProxyIMAPClient(IMAPClient):
"""
Sous-classe d'IMAPClient qui utilise un proxy SOCKS/HTTP.
@@ -239,6 +352,12 @@ class ProxyIMAPClient(IMAPClient):
"Utilisez ssl=True (port 993)."
)
def login(self, username: str, password: str):
"""Surcharge login() pour envoyer IMAP ID juste après l'authentification."""
result = super().login(username, password)
send_imap_id(self)
return result
# ──────────────────────────────────────────────────────────────
# Fonctions utilitaires