need to test read confirmation mails

This commit is contained in:
2026-03-29 19:13:20 +02:00
parent 9802848c5f
commit e8b0a4aae9
2 changed files with 141 additions and 38 deletions
+98 -10
View File
@@ -20,13 +20,14 @@ import io
import logging
import os
import re
import ssl
import socket
import ssl
from dataclasses import dataclass, field
from email.message import Message
from typing import List, Optional, Tuple
import socks
import sys
from dotenv import load_dotenv
from imapclient import IMAPClient
@@ -90,7 +91,10 @@ PROXY_TYPE_MAP = {
"HTTP": socks.HTTP,
}
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
logger.addHandler(logging.StreamHandler(stream=sys.stdout))
# ──────────────────────────────────────────────────────────────
@@ -209,12 +213,31 @@ class ProxyIMAPClient(IMAPClient):
Usage :
proxy = ProxyConfig(host="127.0.0.1", port=1080, proxy_type="SOCKS5")
client = ProxyIMAPClient("imap.gmail.com", proxy=proxy, use_uid=True)
client = ProxyIMAPClient("imap.gmail.com", proxy=proxy, use_uid=True,
subjects=["Confirmation", "Appointment"])
client.login("user@gmail.com", "password")
Paramètres supplémentaires
--------------------------
proxy : ProxyConfig
Configuration du proxy SOCKS/HTTP.
subjects : list[str], optional
Sujets (ou sous-chaînes) à utiliser pour filtrer les emails.
Accessibles via ``client.subjects``.
Utilisés par ``search_by_subjects()`` pour construire
automatiquement les critères IMAP SUBJECT.
"""
def __init__(self, host: str, proxy: ProxyConfig, **kwargs):
def __init__(
self,
host: str,
proxy: ProxyConfig,
subjects: Optional[List[str]] = None,
**kwargs,
):
self._proxy = proxy
# Sujets à rechercher, injectables depuis l'extérieur
self.subjects: List[str] = list(subjects) if subjects else []
super().__init__(host, **kwargs)
def _create_IMAP4(self):
@@ -228,12 +251,59 @@ class ProxyIMAPClient(IMAPClient):
timeout=getattr(self._timeout, "connect", None),
)
# Connexion non-SSL à travers le proxy (rare, mais supporté)
# On monkey-patch juste la connexion TCP
raise NotImplementedError(
"Connexion IMAP non-SSL via proxy non implémentée. "
"Utilisez ssl=True (port 993)."
)
def search_by_subjects(
self,
since: Optional[datetime.datetime] = None,
extra_criteria: Optional[List] = None,
) -> List[int]:
"""
Recherche les UIDs des emails dont le sujet correspond à l'un
des sujets stockés dans ``self.subjects``.
Si ``self.subjects`` est vide, retourne tous les messages
depuis ``since`` (sans filtre par sujet).
Paramètres
----------
since : datetime, optional
Filtre SINCE (aujourd'hui par défaut).
extra_criteria : list, optional
Critères IMAP supplémentaires à combiner (AND implicite).
Retourne
--------
list[int] — UIDs correspondants (peut être vide).
Exemple
-------
client.subjects = ["Confirmation RDV", "confirmed"]
uids = client.search_by_subjects(since=datetime.datetime.today())
"""
since = since or datetime.datetime.today()
base: List = ["SINCE", since]
if extra_criteria:
base.extend(extra_criteria)
if not self.subjects:
return self.search(base)
# Construire OR enchaîné : OR SUBJECT "A" (OR SUBJECT "B" SUBJECT "C")
# IMAPClient accepte des listes imbriquées pour les OR
def _build_or(subjects: List[str]) -> List:
if len(subjects) == 1:
return ["SUBJECT", subjects[0]]
return ["OR", ["SUBJECT", subjects[0]], _build_or(subjects[1:])]
subject_filter = _build_or(self.subjects)
# Combiner avec les critères de base (AND implicite dans IMAP)
criteria = base + subject_filter
return self.search(criteria)
# ──────────────────────────────────────────────────────────────
# Fonctions utilitaires
@@ -288,6 +358,14 @@ class ProxyMailReader:
Configuration du proxy.
timeout : float, optional
Timeout de connexion en secondes (défaut : 30 s).
subjects : list[str], optional
Liste de sujets (ou sous-chaînes) à rechercher dans les emails.
Si None ou vide, on utilise les sujets Hermès par défaut
(VALIDATION_URL_SUBJECT_FR et VALIDATION_URL_SUBJECT_EN).
Les sujets fournis s'ajoutent aux critères par défaut (OR).
from_addresses : list[str], optional
Liste d'adresses expéditeur à accepter en complément.
Si None ou vide, on conserve uniquement "no-reply@hermes.com".
"""
def __init__(
@@ -295,10 +373,19 @@ class ProxyMailReader:
account: MailAccount,
proxy: ProxyConfig,
timeout: float = 30.0,
subjects: Optional[List[str]] = None,
from_addresses: Optional[List[str]] = None,
):
self.account = account
self.proxy = proxy
self.timeout = timeout
self._subjects = []
if subjects:
self._subjects.extend(subjects)
# Adresses expéditeur acceptées
self._from_addresses: List[str] = ["no-reply@hermes.com"]
if from_addresses:
self._from_addresses.extend(from_addresses)
# ── Connexion ────────────────────────────────────────────
@@ -311,12 +398,13 @@ class ProxyMailReader:
client = ProxyIMAPClient(
host=imap_server,
proxy=self.proxy,
subjects=self._subjects, # propagation des sujets vers le client bas niveau
use_uid=True,
ssl=True,
timeout=self.timeout,
)
client.login(self.account.login, self.account.password)
logger.info("[%s] Connecté.", self.account.login)
logger.info("[%s] Connecté. Sujets recherchés : %s", self.account.login, self._subjects)
return client
# ── Lecture des dossiers ─────────────────────────────────
@@ -343,7 +431,8 @@ class ProxyMailReader:
return results
try:
uids = client.search(["SINCE", since])
# Utilise les sujets injectés dans client pour filtrer dès la requête IMAP
uids = client.search_by_subjects(since=since)
except Exception as exc:
logger.warning("[%s] Recherche échouée dans '%s' : %s",
self.account.login, folder, exc)
@@ -366,11 +455,10 @@ class ProxyMailReader:
from_addr = em.get("From", "")
to_addr = em.get("To", self.account.login)
# Filtrer : on ne garde que les emails de validation Hermes
# Filtrer : on ne garde que les emails correspondant aux sujets/expéditeurs configurés
is_validation = (
VALIDATION_URL_SUBJECT_FR in subject
or VALIDATION_URL_SUBJECT_EN in subject
or "no-reply@hermes.com" in from_addr.lower()
any(s in subject for s in self._subjects)
or any(addr in from_addr.lower() for addr in self._from_addresses)
)
if not is_validation:
continue
+38 -23
View File
@@ -1,23 +1,22 @@
import datetime
import email
import logging
import sys
from builtins import list
from concurrent.futures import ThreadPoolExecutor
from email.header import decode_header
from email.message import Message
import sys
from builtins import list
from imapclient import IMAPClient
from src.db.mirgration.migration_tools import migre_accepted_appointment
from src.db.mongo_manager import MONGO_STORE_MANAGER
from src.mail.imap_proxy_reader import ProxyMailReader, MailAccount, ProxyConfig
from src.mail.mail_constants import create_imap, show_folders, is_gmx_address
from src.mail.mail_reader import get_gmx_proxy_config
from src.mail.imap_proxy_reader import ProxyMailReader, MailAccount, ProxyConfig
from src.notification.AcceptedResultPojo import get_accepted_result_from
from src.notification.mailer import Mailer
from src.pojo.ResultEnum import ResultEnum
from src.pojo.mail.mail_pojo import MailPojo, MailAddress
from src.pojo.mail.mail_pojo import MailPojo
CONFIRMATION_SUBJECT_FR = 'Votre=20rendez-vous=20est=20confirm=C3'
CONFIRMATION_SUBJECT_EN = 'confirmed'
@@ -28,9 +27,15 @@ date_format = "%d-%b-%Y" # DD-Mon-YYYY e.g., 3-Mar-2014
FRENCH_CONFIRMED_MESSAGE = "Nous aurons le plaisir de vous accueillir"
def read_gmx_proxy_confirmation_emails(mail, mails_messages: list, proxy_config: ProxyConfig) -> None:
def read_gmx_proxy_confirmation_emails(
mail,
mails_messages: list,
proxy_config: ProxyConfig,
subjects: list = None,
) -> None:
account = MailAccount(login=mail.mail, password=mail.password)
results = ProxyMailReader(account, proxy_config).read(since=datetime.datetime.today())
reader = ProxyMailReader(account, proxy_config, subjects=subjects)
results = reader.read(since=datetime.datetime.today())
for result in results:
mail_pojo = MailPojo(subject=result.subject, body=result.body, from_address=result.from_address)
mail_pojo.mail_address = mail.mail
@@ -160,22 +165,28 @@ def accept_appointment_found(accepted_result_list: list):
_all_contact_list = MONGO_STORE_MANAGER.get_all_contact_to_book_list()
_all_register_account = MONGO_STORE_MANAGER.get_all_registered_users()
mailer = Mailer()
# sginal = SignalSender()
print(accepted_result_list)
for reserve in accepted_result_list:
result = get_accepted_result_from(reserve, MONGO_STORE_MANAGER, _all_contact_list)
for user in _all_register_account:
if user.mail == result.email:
result.account_password = user.password
mailer.send_email(result, to_all=False)
MONGO_STORE_MANAGER.update_reserve_result(reserve.id, ResultEnum.ACCEPTED, reserve.message)
# sginal.send_result(result)
# mailer.send_email(result, to_all=False)
# MONGO_STORE_MANAGER.update_reserve_result(reserve.id, ResultEnum.ACCEPTED, reserve.message)
if len(accepted_result_list) > 0:
migre_accepted_appointment(str(datetime.date.today()))
def find_confirmation_contacts_for_today():
def find_confirmation_contacts_for_today(mode: str = 'default'):
"""
Retourne la liste des boîtes mail à scanner pour aujourd'hui.
Modes disponibles :
- 'default' : comportement habituel (exclut les adresses outlook.com)
- 'all' : toutes les adresses liées aux rendez-vous du jour (y compris outlook)
- 'gmx_only' : uniquement les adresses GMX liées aux rendez-vous du jour
"""
_all_mail_list = MONGO_STORE_MANAGER.get_destination_emails()
_all_appointments_today = MONGO_STORE_MANAGER.get_all_successful_items_for_day()
if len(_all_appointments_today) == 0:
@@ -184,24 +195,28 @@ def find_confirmation_contacts_for_today():
for _item in _all_appointments_today:
for _mail in _all_mail_list:
if _mail.mail == _item.mail:
if mode == 'all':
_mail_list_to_scan.append(_mail)
elif mode == 'gmx_only':
if is_gmx_address(_mail.mail):
_mail_list_to_scan.append(_mail)
else: # 'default'
# do not need to scan outlook
if "outlook.com" not in _mail.mail:
# if _item.url_validated is True:
_mail_list_to_scan.append(_mail)
break
print("Found {} emails to scan".format(len(_mail_list_to_scan)))
print("Found {} emails to scan (mode={})".format(len(_mail_list_to_scan), mode))
return _mail_list_to_scan
def find_confirmation_contacts_mail_list(mail_list):
mail_list.append(MailAddress("saigecong1990@pissmail.com", "cvExXKOP8oY1D@"))
def find_confirmation_contacts_mail_list(mail_list, subjects: list = None):
mails_messages = []
gmx_proxy_config = get_gmx_proxy_config()
# read all the emails
with ThreadPoolExecutor(max_workers=200) as executor:
for mail in mail_list:
if is_gmx_address(mail.mail) and gmx_proxy_config is not None:
executor.submit(read_gmx_proxy_confirmation_emails, mail, mails_messages, gmx_proxy_config)
executor.submit(read_gmx_proxy_confirmation_emails, mail, mails_messages, gmx_proxy_config, subjects)
else:
mail_reader = MailConfirmationReader(mail.mail, mail.password)
executor.submit(mail_reader.read_emails, mails_messages)
@@ -210,7 +225,7 @@ def find_confirmation_contacts_mail_list(mail_list):
successful_items = MONGO_STORE_MANAGER.get_all_successful_items_for_day()
# check the hours
current_hour = datetime.datetime.now().hour
if current_hour < 15:
if current_hour < 22:
# add yesterday's appointment only for morning
successful_items.extend(MONGO_STORE_MANAGER.get_all_successful_items_for_yesterday())
for mail in mails_messages:
@@ -238,12 +253,12 @@ def find_confirmation_contacts_mail_list(mail_list):
return accepted_appointment_list
def read_mails_and_find_confirmation_contacts(all_mails=False):
def read_mails_and_find_confirmation_contacts(all_mails=False, mode: str = 'default', subjects: list = None):
if all_mails:
mail_list = MONGO_STORE_MANAGER.get_destination_emails()
else:
mail_list = find_confirmation_contacts_for_today()
return find_confirmation_contacts_mail_list(mail_list)
mail_list = find_confirmation_contacts_for_today(mode=mode)
return find_confirmation_contacts_mail_list(mail_list, subjects=subjects)
# init_logger()
@@ -252,8 +267,8 @@ logger.addHandler(logging.StreamHandler(stream=sys.stdout))
# check whether the url has already been clicked
if __name__ == '__main__':
# read_mails_and_find_confirmation_contacts()
_mail_list_today = find_confirmation_contacts_for_today()
_mail_list_today = find_confirmation_contacts_for_today(mode="gmx_only")
# print("size is {}".format(len(_mail_list_today)))
find_confirmation_contacts_mail_list(_mail_list_today)
find_confirmation_contacts_mail_list(_mail_list_today, subjects=[CONFIRMATION_SUBJECT_FR, CONFIRMATION_SUBJECT_EN])
# _items = MONGO_STORE_MANAGER.get_all_successful_items_for_day()
# accept_appointment_found([random.choice(_items)])