try to use proxy for gmx mail
This commit is contained in:
@@ -1,140 +0,0 @@
|
||||
import datetime
|
||||
import email
|
||||
import imaplib
|
||||
import uuid
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from email.header import decode_header
|
||||
from email.message import Message
|
||||
|
||||
from builtins import list
|
||||
from src.db.mongo_manager import MONGO_STORE_MANAGER
|
||||
from src.logs.AppLogging import init_logger
|
||||
from src.mail.mail_constants import DOMAIN_163, DOMAIN_YAHOO, DOMAIN_SINA, IMAP_SERVER_163, YAHOO_IMAP_SERVER, \
|
||||
IMAP_SERVER_SINA, AOL_IMAP_SERVER, create_imap
|
||||
from src.pojo.accepted_appointment_pojo import AcceptedAppointmentPojo
|
||||
from src.pojo.mail.mail_pojo import MailPojo
|
||||
|
||||
INVOICE_SUBJECT_fr = 'Votre facture'
|
||||
INVOICE_SUBJECT_EN = 'Your invoice'
|
||||
VALIDATION_URL_REGEX = """https:\/\/rendezvousparis.hermes.com\/client\/register\/[A-Z0-9]+\/validate.code=[A-Z0-9]+"""
|
||||
HERMES_INVOICE_EMAIL = "no-reply@hermes.com"
|
||||
|
||||
date_format = "%d-%b-%Y" # DD-Mon-YYYY e.g., 3-Mar-2014
|
||||
|
||||
|
||||
class InvoiceGetter():
|
||||
def __init__(self, login, password):
|
||||
self.login = login
|
||||
self.password = password
|
||||
|
||||
@staticmethod
|
||||
def show_folders(imap):
|
||||
for i in imap.list()[1]:
|
||||
l = i.decode().split(' "/" ')
|
||||
print(l[0] + " = " + l[1])
|
||||
|
||||
|
||||
def read_emails(self, mails_messages: list) -> list:
|
||||
imap = create_imap(self.login)
|
||||
|
||||
# authenticate
|
||||
type, dat = imap.login(self.login, self.password)
|
||||
mail_list = []
|
||||
print("read mails from {}".format(self.login))
|
||||
# self.show_folders(imap)
|
||||
# total number of emails
|
||||
# get mails from inbox
|
||||
# (\Archive \HasNoChildren) = "Archive"
|
||||
# (\Junk \HasNoChildren) = "Bulk"
|
||||
# (\Drafts \HasNoChildren) = "Draft"
|
||||
# (\HasNoChildren) = "Inbox"
|
||||
# (\Sent \HasNoChildren) = "Sent"
|
||||
# (\Trash \HasNoChildren) = "Trash"
|
||||
mail_list.extend(self._get_messages_from_folder(imap, subject=INVOICE_SUBJECT_fr))
|
||||
mail_list.extend(self._get_messages_from_folder(imap, subject=INVOICE_SUBJECT_EN))
|
||||
# mail_list.extend(self._get_messages_from_folder(imap, folder="Bulk"))
|
||||
# close the connection and logout
|
||||
imap.close()
|
||||
imap.logout()
|
||||
mails_messages.extend(mail_list)
|
||||
return mail_list
|
||||
|
||||
def _get_messages_from_folder(self, imap, subject, folder="INBOX") -> list:
|
||||
imap.select(folder)
|
||||
mail_messages = []
|
||||
typ, data = imap.search(None, '(SUBJECT "{}" SINCE "{}")'.format(subject, datetime.datetime.today().strftime(
|
||||
date_format)))
|
||||
for i in data[0].split():
|
||||
# fetch the email message by ID
|
||||
res, msg = imap.fetch(i.decode("utf-8"), "(RFC822)")
|
||||
body = ''
|
||||
for response in msg:
|
||||
if isinstance(response, tuple):
|
||||
# parse a bytes email into a message object
|
||||
msg = email.message_from_bytes(response[1])
|
||||
# decode the email subject
|
||||
subject, subject_encoded = decode_header(msg["Subject"])[0]
|
||||
received_date = msg["Date"]
|
||||
if isinstance(subject, bytes):
|
||||
# if it's a bytes, decode to str
|
||||
subject = subject.decode(subject_encoded)
|
||||
# decode email sender
|
||||
from_address, subject_encoded = decode_header(msg.get("From"))[0]
|
||||
if isinstance(from_address, bytes):
|
||||
from_address = from_address.decode(subject_encoded)
|
||||
print("From:", from_address)
|
||||
print("Subject:", subject)
|
||||
# if the email message is multipart
|
||||
if msg.is_multipart():
|
||||
# iterate over email parts
|
||||
for part in msg.walk():
|
||||
try:
|
||||
# get the email body
|
||||
payloads = part.get_payload()
|
||||
if isinstance(payloads, list):
|
||||
for payload in payloads:
|
||||
if isinstance(payload, Message):
|
||||
if payload.get_content_type() == 'text/html':
|
||||
body = body + payload.get_payload(decode=True).decode("iso-8859-1")
|
||||
elif payload.get_content_type() == 'application/pdf':
|
||||
# save to pdf
|
||||
open("./" + self.login + str(uuid.uuid4()) + '.pdf', 'wb').write(
|
||||
payload.get_payload(decode=True))
|
||||
|
||||
# print(body)
|
||||
except Exception as Error:
|
||||
print(Error)
|
||||
else:
|
||||
body = msg.get_payload(decode=True).decode()
|
||||
print(body)
|
||||
if INVOICE_SUBJECT_fr in subject or INVOICE_SUBJECT_EN in subject:
|
||||
mail = MailPojo(subject=subject, body=body, from_address=from_address)
|
||||
mail_messages.append(mail)
|
||||
return mail_messages
|
||||
|
||||
|
||||
def get_invoices():
|
||||
# get email address
|
||||
mail_list = MONGO_STORE_MANAGER.get_destination_emails()
|
||||
mail_list_to_check = []
|
||||
# yesterday = str(datetime.date.today() - datetime.timedelta(days=2))
|
||||
yesterday = str(datetime.date.today())
|
||||
collection = MONGO_STORE_MANAGER.get_accepted_items_for_one_day(yesterday)
|
||||
for valid_appointment in collection:
|
||||
accepted_pojo = AcceptedAppointmentPojo.from_reserve(valid_appointment)
|
||||
for mail in mail_list:
|
||||
if mail.mail == accepted_pojo.email:
|
||||
mail_list_to_check.append(mail)
|
||||
# mail_list = [mail_address1]
|
||||
mails_messages = []
|
||||
with ThreadPoolExecutor(max_workers=20) as executor:
|
||||
for mail in mail_list_to_check:
|
||||
# check whether we need to read mail
|
||||
mail_reader = InvoiceGetter(mail.mail, mail.password)
|
||||
executor.submit(mail_reader.read_emails, mails_messages)
|
||||
|
||||
|
||||
# check whether the url has already been clicked
|
||||
if __name__ == '__main__':
|
||||
init_logger()
|
||||
get_invoices()
|
||||
@@ -0,0 +1,534 @@
|
||||
"""
|
||||
imap_proxy_reader.py
|
||||
====================
|
||||
Lire des emails via IMAPClient en passant par un proxy SOCKS5/SOCKS4/HTTP.
|
||||
|
||||
Fonctionnement :
|
||||
- ProxyIMAP4_TLS : sous-classe de imaplib.IMAP4 qui ouvre la socket
|
||||
à travers un proxy SOCKS via PySocks.
|
||||
- ProxyIMAPClient : sous-classe de IMAPClient qui injecte ProxyIMAP4_TLS
|
||||
au lieu de la connexion directe habituelle.
|
||||
|
||||
Dépendances :
|
||||
pip install imapclient PySocks
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import email
|
||||
import imaplib
|
||||
import io
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import ssl
|
||||
import socket
|
||||
from dataclasses import dataclass, field
|
||||
from email.message import Message
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
import socks
|
||||
from dotenv import load_dotenv
|
||||
from imapclient import IMAPClient
|
||||
|
||||
load_dotenv()
|
||||
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
# Constantes
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
|
||||
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]+"
|
||||
)
|
||||
|
||||
DATE_FORMAT = "%d-%b-%Y"
|
||||
|
||||
# Correspondance domaine → serveur IMAP (identique à mail_constants.py)
|
||||
IMAP_SERVER_MAP: List[Tuple[str, str]] = [
|
||||
("163.com", "imap.163.com"),
|
||||
("yahoo.com", "imap.mail.yahoo.com"),
|
||||
("firemail.de", "imap.firemail.de"),
|
||||
("gmail.com", "imap.gmail.com"),
|
||||
("sina.com", "imap.sina.com"),
|
||||
("hotmail.com", "outlook.office365.com"),
|
||||
("outlook.com", "outlook.office365.com"),
|
||||
("rambler.ru", "imap.rambler.ru"),
|
||||
("btvm.ne.jp", "imap.btvm.ne.jp"),
|
||||
("mars.dti.ne.jp", "imap.cm.dream.jp"),
|
||||
("aurora.dti.ne.jp", "imap.cm.dream.jp"),
|
||||
("naver.com", "imap.naver.com"),
|
||||
("onet.pl", "imap.poczta.onet.pl"),
|
||||
("gazeta.pl", "imap.gazeta.pl"),
|
||||
("tim.it", "imap.tim.it"),
|
||||
("alice.it", "in.alice.it"),
|
||||
("gmx.com", "imap.gmx.com"),
|
||||
("gmx.fr", "imap.gmx.com"),
|
||||
("gmx.us", "imap.gmx.com"),
|
||||
("gmx.ch", "imap.gmx.com"),
|
||||
("gmx.pt", "imap.gmx.com"),
|
||||
("gmx.sg", "imap.gmx.com"),
|
||||
("gmx.net", "imap.gmx.net"),
|
||||
("gmx.de", "imap.gmx.net"),
|
||||
("gmx.at", "imap.gmx.at"),
|
||||
("web.de", "imap.web.de"),
|
||||
("inbox.lv", "mail.inbox.lv"),
|
||||
("pissmail.com", "mail.pissmail.com"),
|
||||
("incel.email", "mail.pissmail.com"),
|
||||
("shitposting.expert","mail.pissmail.com"),
|
||||
("hatesje.ws", "mail.pissmail.com"),
|
||||
("child.pizza", "mail.pissmail.com"),
|
||||
("genocide.fun", "mail.pissmail.com"),
|
||||
("dmc.chat", "mail.pissmail.com"),
|
||||
("aol.com", "imap.aol.com"), # fallback AOL
|
||||
]
|
||||
|
||||
PROXY_TYPE_MAP = {
|
||||
"SOCKS5": socks.SOCKS5,
|
||||
"SOCKS4": socks.SOCKS4,
|
||||
"HTTP": socks.HTTP,
|
||||
}
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
# Modèles de données
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
|
||||
@dataclass
|
||||
class ProxyConfig:
|
||||
"""Configuration du proxy."""
|
||||
host: str
|
||||
port: int
|
||||
proxy_type: str = "SOCKS5" # "SOCKS5" | "SOCKS4" | "HTTP"
|
||||
username: Optional[str] = None
|
||||
password: Optional[str] = None
|
||||
|
||||
@property
|
||||
def socks_type(self) -> int:
|
||||
t = self.proxy_type.upper()
|
||||
if t not in PROXY_TYPE_MAP:
|
||||
raise ValueError(f"proxy_type invalide : {self.proxy_type!r}. "
|
||||
f"Valeurs autorisées : {list(PROXY_TYPE_MAP)}")
|
||||
return PROXY_TYPE_MAP[t]
|
||||
|
||||
def __repr__(self) -> str:
|
||||
auth = f"{self.username}:***@" if self.username else ""
|
||||
return f"{self.proxy_type}://{auth}{self.host}:{self.port}"
|
||||
|
||||
|
||||
@dataclass
|
||||
class MailAccount:
|
||||
"""Compte email à lire."""
|
||||
login: str
|
||||
password: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class MailResult:
|
||||
"""Résultat d'une lecture d'email."""
|
||||
account: str
|
||||
subject: str
|
||||
from_address: str
|
||||
to_address: str
|
||||
body: str
|
||||
validation_urls: List[str] = field(default_factory=list)
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
# Connexion IMAP via proxy (bas niveau)
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
|
||||
class ProxyIMAP4_TLS(imaplib.IMAP4):
|
||||
"""
|
||||
Variante TLS de imaplib.IMAP4 qui route la connexion
|
||||
à travers un proxy SOCKS5/SOCKS4/HTTP grâce à PySocks.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
host: str,
|
||||
port: int,
|
||||
ssl_context: Optional[ssl.SSLContext],
|
||||
proxy: ProxyConfig,
|
||||
timeout: Optional[float] = None,
|
||||
):
|
||||
self._ssl_context = ssl_context
|
||||
self._proxy = proxy
|
||||
self._timeout = timeout
|
||||
# imaplib.IMAP4.__init__ appelle self.open()
|
||||
imaplib.IMAP4.__init__(self, host, port)
|
||||
self.file: io.BufferedReader
|
||||
|
||||
def open(self, host: str = "", port: int = 993, timeout: Optional[float] = None) -> None:
|
||||
self.host = host
|
||||
self.port = port
|
||||
effective_timeout = timeout if timeout is not None else self._timeout
|
||||
|
||||
# ── Créer la socket SOCKS ────────────────────────────
|
||||
sock = socks.socksocket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.set_proxy(
|
||||
proxy_type=self._proxy.socks_type,
|
||||
addr=self._proxy.host,
|
||||
port=self._proxy.port,
|
||||
username=self._proxy.username,
|
||||
password=self._proxy.password,
|
||||
)
|
||||
if effective_timeout:
|
||||
sock.settimeout(effective_timeout)
|
||||
sock.connect((host, port))
|
||||
|
||||
# ── Envelopper avec SSL/TLS ──────────────────────────
|
||||
ctx = self._ssl_context or ssl.create_default_context()
|
||||
self.sock = ctx.wrap_socket(sock, server_hostname=host)
|
||||
self.file = self.sock.makefile("rb")
|
||||
|
||||
# ── Méthodes requises par imaplib.IMAP4 ─────────────────
|
||||
def read(self, size: int) -> bytes:
|
||||
return self.file.read(size) # type: ignore[return-value]
|
||||
|
||||
def readline(self) -> bytes:
|
||||
return self.file.readline() # type: ignore[return-value]
|
||||
|
||||
def send(self, data) -> None:
|
||||
self.sock.sendall(data)
|
||||
|
||||
def shutdown(self) -> None:
|
||||
imaplib.IMAP4.shutdown(self)
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
# IMAPClient avec proxy
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
|
||||
class ProxyIMAPClient(IMAPClient):
|
||||
"""
|
||||
Sous-classe d'IMAPClient qui utilise un proxy SOCKS/HTTP.
|
||||
|
||||
Usage :
|
||||
proxy = ProxyConfig(host="127.0.0.1", port=1080, proxy_type="SOCKS5")
|
||||
client = ProxyIMAPClient("imap.gmail.com", proxy=proxy, use_uid=True)
|
||||
client.login("user@gmail.com", "password")
|
||||
"""
|
||||
|
||||
def __init__(self, host: str, proxy: ProxyConfig, **kwargs):
|
||||
self._proxy = proxy
|
||||
super().__init__(host, **kwargs)
|
||||
|
||||
def _create_IMAP4(self):
|
||||
"""Remplace la méthode d'IMAPClient pour injecter ProxyIMAP4_TLS."""
|
||||
if self.ssl:
|
||||
return ProxyIMAP4_TLS(
|
||||
host=self.host,
|
||||
port=self.port,
|
||||
ssl_context=self.ssl_context,
|
||||
proxy=self._proxy,
|
||||
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)."
|
||||
)
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
# Fonctions utilitaires
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
|
||||
def get_imap_server(login: str) -> str:
|
||||
"""Retourne le serveur IMAP correspondant au domaine du login."""
|
||||
login_lower = login.lower()
|
||||
for domain, server in IMAP_SERVER_MAP:
|
||||
if domain in login_lower:
|
||||
return server
|
||||
return "imap.aol.com" # fallback
|
||||
|
||||
|
||||
def extract_body(email_message: Message) -> str:
|
||||
"""Extrait le corps HTML ou texte d'un email."""
|
||||
body = ""
|
||||
for part in email_message.walk():
|
||||
content_type = part.get_content_type()
|
||||
try:
|
||||
if content_type == "text/html":
|
||||
payload = part.get_payload(decode=True)
|
||||
if payload:
|
||||
body += payload.decode("utf-8", errors="ignore")
|
||||
elif content_type == "text/plain":
|
||||
payload = part.get_payload()
|
||||
if payload:
|
||||
body += str(payload)
|
||||
except Exception as exc:
|
||||
logger.warning("Erreur extraction body : %s", exc)
|
||||
return body
|
||||
|
||||
|
||||
def find_validation_urls(text: str) -> List[str]:
|
||||
"""Recherche toutes les URLs de validation Hermes dans un texte."""
|
||||
return re.findall(VALIDATION_URL_REGEX, text)
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
# Lecteur principal
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
|
||||
class ProxyMailReader:
|
||||
"""
|
||||
Lit les emails d'un compte via IMAPClient en passant par un proxy.
|
||||
|
||||
Paramètres
|
||||
----------
|
||||
account : MailAccount
|
||||
Identifiants du compte email.
|
||||
proxy : ProxyConfig
|
||||
Configuration du proxy.
|
||||
timeout : float, optional
|
||||
Timeout de connexion en secondes (défaut : 30 s).
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
account: MailAccount,
|
||||
proxy: ProxyConfig,
|
||||
timeout: float = 30.0,
|
||||
):
|
||||
self.account = account
|
||||
self.proxy = proxy
|
||||
self.timeout = timeout
|
||||
|
||||
# ── Connexion ────────────────────────────────────────────
|
||||
|
||||
def _connect(self) -> ProxyIMAPClient:
|
||||
imap_server = get_imap_server(self.account.login)
|
||||
logger.info(
|
||||
"[%s] Connexion via %s → %s:993",
|
||||
self.account.login, self.proxy, imap_server,
|
||||
)
|
||||
client = ProxyIMAPClient(
|
||||
host=imap_server,
|
||||
proxy=self.proxy,
|
||||
use_uid=True,
|
||||
ssl=True,
|
||||
timeout=self.timeout,
|
||||
)
|
||||
client.login(self.account.login, self.account.password)
|
||||
logger.info("[%s] Connecté.", self.account.login)
|
||||
return client
|
||||
|
||||
# ── Lecture des dossiers ─────────────────────────────────
|
||||
|
||||
def _list_folders(self, client: ProxyIMAPClient) -> List[str]:
|
||||
return [info[-1] for info in client.list_folders()]
|
||||
|
||||
# ── Lecture des messages ─────────────────────────────────
|
||||
|
||||
def _read_folder(
|
||||
self,
|
||||
client: ProxyIMAPClient,
|
||||
folder: str,
|
||||
since: Optional[datetime.datetime] = None,
|
||||
) -> List[MailResult]:
|
||||
results: List[MailResult] = []
|
||||
since = since or datetime.datetime.today()
|
||||
|
||||
try:
|
||||
client.select_folder(folder, readonly=True)
|
||||
except Exception as exc:
|
||||
logger.warning("[%s] Impossible d'ouvrir '%s' : %s",
|
||||
self.account.login, folder, exc)
|
||||
return results
|
||||
|
||||
try:
|
||||
uids = client.search(["SINCE", since])
|
||||
except Exception as exc:
|
||||
logger.warning("[%s] Recherche échouée dans '%s' : %s",
|
||||
self.account.login, folder, exc)
|
||||
return results
|
||||
|
||||
if not uids:
|
||||
return results
|
||||
|
||||
logger.info("[%s] %d message(s) dans '%s'",
|
||||
self.account.login, len(uids), folder)
|
||||
|
||||
for uid, msg_data in client.fetch(uids, "RFC822").items():
|
||||
try:
|
||||
raw = msg_data.get(b"RFC822") or msg_data.get("RFC822")
|
||||
if raw is None:
|
||||
continue
|
||||
em = email.message_from_bytes(raw)
|
||||
|
||||
subject = em.get("Subject", "")
|
||||
from_addr = em.get("From", "")
|
||||
to_addr = em.get("To", self.account.login)
|
||||
|
||||
# Filtrer : on ne garde que les emails de validation Hermes
|
||||
is_validation = (
|
||||
VALIDATION_URL_SUBJECT_FR in subject
|
||||
or VALIDATION_URL_SUBJECT_EN in subject
|
||||
or "no-reply@hermes.com" in from_addr.lower()
|
||||
)
|
||||
if not is_validation:
|
||||
continue
|
||||
|
||||
body = extract_body(em)
|
||||
urls = find_validation_urls(body)
|
||||
|
||||
result = MailResult(
|
||||
account=self.account.login,
|
||||
subject=subject,
|
||||
from_address=from_addr,
|
||||
to_address=to_addr,
|
||||
body=body,
|
||||
validation_urls=urls,
|
||||
)
|
||||
results.append(result)
|
||||
logger.info(
|
||||
"[%s] Email de validation trouvé (uid=%s) — URLs : %s",
|
||||
self.account.login, uid, urls or "aucune",
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.warning(
|
||||
"[%s] Erreur traitement uid=%s : %s",
|
||||
self.account.login, uid, exc,
|
||||
)
|
||||
|
||||
return results
|
||||
|
||||
# ── Point d'entrée public ────────────────────────────────
|
||||
|
||||
def read(
|
||||
self,
|
||||
since: Optional[datetime.datetime] = None,
|
||||
skip_folders: Optional[List[str]] = None,
|
||||
) -> List[MailResult]:
|
||||
"""
|
||||
Se connecte au serveur IMAP via le proxy et retourne la liste
|
||||
des emails de validation trouvés depuis `since` (aujourd'hui par défaut).
|
||||
|
||||
Paramètres
|
||||
----------
|
||||
since : datetime, optional — date de début de recherche
|
||||
skip_folders : list[str], optional — dossiers à ignorer
|
||||
(défaut : ["Sent", "Drafts", "Trash", "Junk", "Spam"])
|
||||
"""
|
||||
if skip_folders is None:
|
||||
skip_folders = ["Sent", "Drafts", "Trash", "Junk", "Spam"]
|
||||
|
||||
all_results: List[MailResult] = []
|
||||
client = self._connect()
|
||||
|
||||
try:
|
||||
folders = self._list_folders(client)
|
||||
logger.info("[%s] Dossiers : %s", self.account.login, folders)
|
||||
|
||||
for folder in folders:
|
||||
if folder in skip_folders:
|
||||
logger.debug("[%s] Dossier ignoré : %s",
|
||||
self.account.login, folder)
|
||||
continue
|
||||
all_results.extend(self._read_folder(client, folder, since))
|
||||
finally:
|
||||
try:
|
||||
client.logout()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return all_results
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
# Lecture parallèle de plusieurs comptes
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
|
||||
|
||||
def read_multiple_accounts(
|
||||
accounts: List[MailAccount],
|
||||
proxy: ProxyConfig,
|
||||
since: Optional[datetime.datetime] = None,
|
||||
max_workers: int = 10,
|
||||
timeout: float = 30.0,
|
||||
) -> List[MailResult]:
|
||||
"""
|
||||
Lit plusieurs comptes email en parallèle via le même proxy.
|
||||
|
||||
Retourne la liste consolidée de tous les MailResult trouvés.
|
||||
"""
|
||||
all_results: List[MailResult] = []
|
||||
|
||||
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
||||
future_map = {
|
||||
executor.submit(
|
||||
ProxyMailReader(acc, proxy, timeout).read, since
|
||||
): acc.login
|
||||
for acc in accounts
|
||||
}
|
||||
for future in as_completed(future_map):
|
||||
login = future_map[future]
|
||||
try:
|
||||
results = future.result()
|
||||
logger.info("[%s] %d email(s) de validation récupéré(s).",
|
||||
login, len(results))
|
||||
all_results.extend(results)
|
||||
except Exception as exc:
|
||||
logger.error("[%s] Erreur : %s", login, exc)
|
||||
|
||||
return all_results
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
# Point d'entrée — exemple d'utilisation
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s %(levelname)-8s %(message)s",
|
||||
datefmt="%H:%M:%S",
|
||||
)
|
||||
|
||||
# ── 1. Configurer le proxy ───────────────────────────────
|
||||
proxy = ProxyConfig(
|
||||
host=os.environ.get("GMX_PROXY_HOST", ""),
|
||||
port=int(os.environ.get("GMX_PROXY_PORT", "443")),
|
||||
proxy_type=os.environ.get("GMX_PROXY_TYPE", "SOCKS5"),
|
||||
username=os.environ.get("GMX_PROXY_USERNAME"),
|
||||
password=os.environ.get("GMX_PROXY_PASSWORD"),
|
||||
)
|
||||
|
||||
# ── 2. Définir les comptes à lire ────────────────────────
|
||||
accounts = [
|
||||
MailAccount(login="birgitnaya@gmx.net", password="XEeUF3Y1yaO"),
|
||||
# MailAccount(login="user@gmail.com", password="apppassword"),
|
||||
# MailAccount(login="user@outlook.com", password="password"),
|
||||
]
|
||||
|
||||
# ── 3. Lancer la lecture ─────────────────────────────────
|
||||
results = read_multiple_accounts(
|
||||
accounts=accounts,
|
||||
proxy=proxy,
|
||||
since=datetime.datetime.today(),
|
||||
max_workers=5,
|
||||
timeout=30.0,
|
||||
)
|
||||
|
||||
# ── 4. Afficher les résultats ────────────────────────────
|
||||
print(f"\n{'='*60}")
|
||||
print(f" {len(results)} email(s) de validation trouvé(s)")
|
||||
print(f"{'='*60}\n")
|
||||
|
||||
for r in results:
|
||||
print(f" Compte : {r.account}")
|
||||
print(f" De : {r.from_address}")
|
||||
print(f" Sujet : {r.subject}")
|
||||
print(f" URLs : {r.validation_urls or 'aucune'}")
|
||||
print(f" {'-'*56}")
|
||||
|
||||
|
||||
|
||||
@@ -126,5 +126,5 @@ def check_mails():
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# remove_invalid_email()
|
||||
check_mails()
|
||||
remove_invalid_email()
|
||||
# check_mails()
|
||||
@@ -11,7 +11,9 @@ 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.mail_constants import create_imap, show_folders
|
||||
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
|
||||
@@ -26,6 +28,17 @@ 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:
|
||||
account = MailAccount(login=mail.mail, password=mail.password)
|
||||
results = ProxyMailReader(account, proxy_config).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
|
||||
mail_pojo.to_address = result.to_address or mail.mail
|
||||
mail_pojo.isImapClient = True
|
||||
mails_messages.append(mail_pojo)
|
||||
|
||||
|
||||
class MailConfirmationReader():
|
||||
def __init__(self, login, password):
|
||||
self.login = login
|
||||
@@ -154,7 +167,7 @@ def accept_appointment_found(accepted_result_list: list):
|
||||
for user in _all_register_account:
|
||||
if user.mail == result.email:
|
||||
result.account_password = user.password
|
||||
mailer.send_email(result, to_all=True)
|
||||
mailer.send_email(result, to_all=False)
|
||||
MONGO_STORE_MANAGER.update_reserve_result(reserve.id, ResultEnum.ACCEPTED, reserve.message)
|
||||
# sginal.send_result(result)
|
||||
|
||||
@@ -181,13 +194,15 @@ def find_confirmation_contacts_for_today():
|
||||
|
||||
|
||||
def find_confirmation_contacts_mail_list(mail_list):
|
||||
# mail_list = [MailAddress("saigecong1990@pissmail.com", "cvExXKOP8oY1D@")]
|
||||
mail_list.append(MailAddress("saigecong1990@pissmail.com", "cvExXKOP8oY1D@"))
|
||||
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 DOMAIN_HOTMAIL not in mail.mail:
|
||||
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)
|
||||
else:
|
||||
mail_reader = MailConfirmationReader(mail.mail, mail.password)
|
||||
executor.submit(mail_reader.read_emails, mails_messages)
|
||||
accepted_appointment_list = []
|
||||
@@ -229,11 +244,6 @@ def read_mails_and_find_confirmation_contacts(all_mails=False):
|
||||
else:
|
||||
mail_list = find_confirmation_contacts_for_today()
|
||||
return find_confirmation_contacts_mail_list(mail_list)
|
||||
# mail_list.reverse()
|
||||
# excel_reader = ExcelHelper()
|
||||
# mail_list =excel_reader.read_email_pojo(file_name="/Users/lpan/Desktop/hotmail_list.xlsx")
|
||||
# mail_address3 = MailAddress(mail="taibenchragu1978@onet.pl", password="2J)kyfNgyOZ")
|
||||
# mail_list = [mail_address3]
|
||||
|
||||
|
||||
# init_logger()
|
||||
|
||||
@@ -27,6 +27,23 @@ DOMAIN_NAVER = "naver.com"
|
||||
DOMAIN_INBOX_LV = "inbox.lv"
|
||||
DOMAIN_GMX_DE = "gmx.de"
|
||||
|
||||
GMX_DOMAINS = {
|
||||
DOMAIN_GMX,
|
||||
DOMAIN_GMX_NET,
|
||||
DOMAIN_GMX_AT,
|
||||
DOMAIN_GMX_FR,
|
||||
DOMAIN_GMX_US,
|
||||
DOMAIN_GMX_SG,
|
||||
DOMAIN_GMX_CH,
|
||||
DOMAIN_GMX_PT,
|
||||
DOMAIN_GMX_DE,
|
||||
}
|
||||
|
||||
|
||||
def is_gmx_address(login: str) -> bool:
|
||||
return any(domain in login for domain in GMX_DOMAINS)
|
||||
|
||||
|
||||
DOMAIN_PISS_MAIL = "pissmail.com"
|
||||
DOMAIN_INCEL_EMAIL = "incel.email"
|
||||
DOMAIN_SHITPOSTING_EXPERT = "shitposting.expert"
|
||||
|
||||
+35
-1
@@ -1,6 +1,8 @@
|
||||
import datetime
|
||||
import email
|
||||
import re
|
||||
import os
|
||||
from typing import Optional
|
||||
from builtins import list
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from datetime import time
|
||||
@@ -11,7 +13,8 @@ from imapclient import IMAPClient
|
||||
|
||||
from src.db.mongo_manager import MONGO_STORE_MANAGER
|
||||
from src.logs.AppLogging import init_logger
|
||||
from src.mail.mail_constants import create_imap, show_folders
|
||||
from src.mail.mail_constants import create_imap, show_folders, is_gmx_address
|
||||
from src.mail.imap_proxy_reader import ProxyMailReader, ProxyConfig, MailAccount
|
||||
from src.pojo.mail.mail_pojo import MailPojo
|
||||
from src.utils.timeutiles import is_time_between
|
||||
|
||||
@@ -51,6 +54,33 @@ def find_from_mail(param):
|
||||
return from_address.strip(" ").strip(">").strip("<")
|
||||
|
||||
|
||||
def get_gmx_proxy_config() -> Optional[ProxyConfig]:
|
||||
host = os.environ.get("GMX_PROXY_HOST", "")
|
||||
if not host:
|
||||
return None
|
||||
try:
|
||||
port = int(os.environ.get("GMX_PROXY_PORT", "443"))
|
||||
except ValueError:
|
||||
port = 443
|
||||
return ProxyConfig(
|
||||
host=host,
|
||||
port=port,
|
||||
proxy_type=os.environ.get("GMX_PROXY_TYPE", "SOCKS5"),
|
||||
username=os.environ.get("GMX_PROXY_USERNAME"),
|
||||
password=os.environ.get("GMX_PROXY_PASSWORD"),
|
||||
)
|
||||
|
||||
|
||||
def read_gmx_proxy_emails(mail, mails_messages: list, proxy_config: ProxyConfig) -> None:
|
||||
account = MailAccount(login=mail.mail, password=mail.password)
|
||||
results = ProxyMailReader(account, proxy_config).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
|
||||
mail_pojo.to_address = result.to_address or mail.mail
|
||||
mails_messages.append(mail_pojo)
|
||||
|
||||
|
||||
class MailReader():
|
||||
def __init__(self, login, password):
|
||||
self.login = login
|
||||
@@ -241,6 +271,7 @@ def read_mails():
|
||||
if is_time_between(time(7, 30), time(23, 30)):
|
||||
# get email address
|
||||
mail_list = MONGO_STORE_MANAGER.get_destination_emails()
|
||||
gmx_proxy_config = get_gmx_proxy_config()
|
||||
# excel_reader = ExcelHelper()
|
||||
# mail_list = excel_reader.read_email_pojo(file_name="/Users/panlei/Downloads/hotmail_list.xlsx")
|
||||
# mail_address1 = MailAddress(mail="casandrakaamv@onet.pl", password="8F0o0APeAp0z")
|
||||
@@ -251,6 +282,9 @@ def read_mails():
|
||||
for mail in mail_list:
|
||||
# check whether we need to read mail
|
||||
if need_to_check_email(mail.mail, successful_items):
|
||||
if is_gmx_address(mail.mail) and gmx_proxy_config is not None:
|
||||
executor.submit(read_gmx_proxy_emails, mail, mails_messages, gmx_proxy_config)
|
||||
else:
|
||||
mail_reader = MailReader(mail.mail, mail.password)
|
||||
executor.submit(mail_reader.read_emails, mails_messages)
|
||||
# get ip_country info
|
||||
|
||||
@@ -11,8 +11,8 @@ from imapclient import IMAPClient
|
||||
|
||||
from src.db.mongo_manager import MONGO_STORE_MANAGER
|
||||
from src.logs.AppLogging import init_logger
|
||||
from src.mail.mail_constants import DOMAIN_HOTMAIL, create_imap
|
||||
from src.mail.mail_reader import need_to_valid_url
|
||||
from src.mail.mail_constants import DOMAIN_HOTMAIL, create_imap, is_gmx_address
|
||||
from src.mail.mail_reader import need_to_valid_url, get_gmx_proxy_config, read_gmx_proxy_emails
|
||||
from src.pojo.mail.mail_pojo import MailPojo
|
||||
from src.utils.excel_reader import read_contacts
|
||||
from src.utils.timeutiles import is_time_between
|
||||
@@ -262,6 +262,7 @@ def read_all_mails(contact_to_book_list=None):
|
||||
if contact_to_book_list is None:
|
||||
contact_to_book_list = MONGO_STORE_MANAGER.get_all_contact_to_book_list()
|
||||
mail_list = MONGO_STORE_MANAGER.get_destination_emails()
|
||||
gmx_proxy_config = get_gmx_proxy_config()
|
||||
mail_list_to_check = []
|
||||
for contact in contact_to_book_list:
|
||||
for mail in mail_list:
|
||||
@@ -273,6 +274,9 @@ def read_all_mails(contact_to_book_list=None):
|
||||
for mail in mail_list_to_check:
|
||||
# check whether we need to read mail
|
||||
if need_to_check_email(mail.mail, successful_items):
|
||||
if is_gmx_address(mail.mail) and gmx_proxy_config is not None:
|
||||
executor.submit(read_gmx_proxy_emails, mail, mails_messages, gmx_proxy_config)
|
||||
else:
|
||||
mail_reader = MailReader(mail.mail, mail.password)
|
||||
executor.submit(mail_reader.read_emails, mails_messages)
|
||||
# 在读邮件时候,可能会有其他的约会提交或者约会的链接确认,所以需要刷新一下成功的列表
|
||||
@@ -300,56 +304,29 @@ if __name__ == '__main__':
|
||||
contact_to_book_list = read_contacts(
|
||||
# file_name="/Users/lpan/Desktop/contact_list_2025-01-16_yahoo_100.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/contact_list_2025-03-15.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/contact_list_2025-03-24.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/contact_list_2025-03-15.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/contact_list_2025-03-21.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/contact_list_2025-03-20.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/contact_list_2025-03-14.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/contact_aol_200_2025-01-15.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/contact_list_2025-03-18.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/contact_list_2025-03-17.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/contact_list_2025-03-19.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/contact_list_2025-03-15.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/contact_list_2025-01-25.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/contact_list_2025-03-13.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/contact_list_2025-03-12.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/contact_list_2025-03-11.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/contact_list_2025-03-04.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/contact_list_2025-03-10.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/contact_list_2025-03-09.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/contact_list_2025-03-06.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/contact_list_2025-03-07.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/extracted_yahoo_contacts_129_24_03_win.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/contact_list_2025-04-02.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/extracted_aol_contacts_292_24_03_mac.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/real_name_contacts_197_24_03.xlsx")x
|
||||
# file_name="/Users/lpan/Desktop/contact_list_2025-03-29.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/contact_list_2025-03-31.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/contact_list_all_21.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/contact_list_all_10.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/contact_list_all_14.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/contact_list_all_19.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/contact_list_all_22.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/contact_list_all_2_win.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/contact_list_2025-03-29.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/17_18_04_to_test_win.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/real_name_contacts_200_aol_win.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/real_name_contacts_200_aol_mac.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/real_name_contacts_500_27_03_25_fixed_mac.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/contact_list_2025-03-28_mac.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/contact_list_2025-03-27_mac.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/contact_list_2025-03-27_win.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/contact_list_all_1.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/contact_list_all_12.xlsx")
|
||||
# file_name="/Users/panlei/Desktop/contact_list_2025-04-08.xlsx")
|
||||
# file_name="/Users/panlei/Desktop/contact_list_2025-04-10.xlsx")
|
||||
# file_name="/Users/panlei/Desktop/contact_list_2024-10-02.xlsx")
|
||||
file_name="/Users/panlei/Desktop/contact_list_2025-08-18_no_ms.xlsx")
|
||||
# file_name="/Users/panlei/Desktop/real_name_contacts_200_gmx_ch_8_04_mac.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/contact_list_2025-04-05.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/real_name_contacts_400_aol_mac.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/contact_list_2025-04-07.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/contact_list_2025-04-05.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/real_name_contacts_100_gmx_ch_05_04_mac.xlsx")
|
||||
file_name="/Users/lpan/Desktop/real_name_contacts_292_win.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/real_name_contacts_292_win.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/real_name_contacts_400_mac_04_04.xlsx")
|
||||
|
||||
# file_name="/Users/lpan/Desktop/contact_list_2025-04-01.xlsx")
|
||||
# file_name="/Users/lpan/Desktop/contact_list_2025-03-08_aol_400.xlsx")
|
||||
read_all_mails(contact_to_book_list=contact_to_book_list)
|
||||
# read_all_mails()
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
from mrz.generator.td1 import TD1CodeGenerator
|
||||
|
||||
first_name = "eryan"
|
||||
last_name = "dai"
|
||||
first_name = "kele"
|
||||
last_name = "mi"
|
||||
document_number = "XKJ0WSK30"
|
||||
birth_day = "951211"
|
||||
# sex = "F"
|
||||
sex = "M"
|
||||
birth_day = "991125"
|
||||
sex = "F"
|
||||
# sex = "M"
|
||||
# optinal_data = "MFMLMANK<<<<A9" #14位
|
||||
nationality = "CHN"
|
||||
country_code = "FRA"
|
||||
# optinal_data = "<E10805074" # 14位
|
||||
optinal_data = "<967343145" #总共11位,最前面那位为空,所以加<
|
||||
optinal_data = "<570873213" #总共11位,最前面那位为空,所以加<
|
||||
document_prefix = "IR"
|
||||
expire_date = "260815"
|
||||
expire_date = "290818"
|
||||
if optinal_data is not None:
|
||||
code = TD1CodeGenerator(document_prefix, country_code, document_number, birth_day, sex, expire_date, nationality,
|
||||
last_name, first_name, optional_data1=optinal_data)
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import math
|
||||
|
||||
|
||||
def sanitize_excel_value(value):
|
||||
if value is None:
|
||||
return ""
|
||||
if isinstance(value, float) and (math.isnan(value) or math.isinf(value)):
|
||||
return ""
|
||||
return value
|
||||
|
||||
Reference in New Issue
Block a user