"""IMAP ophalen en SMTP versturen, alleen met de standaardbibliotheek.""" import email import html import imaplib import re import smtplib from datetime import datetime, timedelta from email.message import EmailMessage from email.policy import default as policy from email.utils import formatdate, make_msgid, parseaddr, parsedate_to_datetime MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] def _text(msg): part = msg.get_body(preferencelist=('plain', 'html')) if part is None: return '' try: content = part.get_content() except Exception: content = (part.get_payload(decode=True) or b'').decode('utf-8', 'replace') if part.get_content_type() == 'text/html': content = re.sub(r'(?is)<(script|style|head).*?', '', content) content = re.sub(r'(?i)|

||||', '\n', content) content = html.unescape(re.sub(r'<[^>]+>', '', content)) content = re.sub(r'[ \t]+\n', '\n', content) return re.sub(r'\n{3,}', '\n\n', content).strip() def _connect_imap(cfg): host, port = cfg['imap_host'], int(cfg.get('imap_port') or 993) if port == 993: conn = imaplib.IMAP4_SSL(host, port, timeout=30) else: conn = imaplib.IMAP4(host, port, timeout=30) conn.starttls() conn.login(cfg['imap_user'], cfg.get('imap_pass', '')) return conn def fetch(cfg, known, limit=200): """Haal berichten van de laatste `imap_days` dagen op die nog niet in `known` staan.""" folder = cfg.get('imap_folder') or 'INBOX' days = int(cfg.get('imap_days') or 30) since = datetime.now() - timedelta(days=days) since_str = f'{since.day:02d}-{MONTHS[since.month - 1]}-{since.year}' conn = _connect_imap(cfg) try: typ, _ = conn.select(f'"{folder}"', readonly=True) if typ != 'OK': raise RuntimeError(f'Map "{folder}" niet gevonden') _, data = conn.uid('search', None, 'SINCE', since_str) uids = data[0].split()[-limit:] out = [] for uid in uids: key = f'{folder}:{uid.decode()}' if key in known: continue _, msgdata = conn.uid('fetch', uid, '(BODY.PEEK[])') if not msgdata or not isinstance(msgdata[0], tuple): continue msg = email.message_from_bytes(msgdata[0][1], policy=policy) name, addr = parseaddr(str(msg.get('From', ''))) try: date = parsedate_to_datetime(msg['Date']).astimezone().strftime('%Y-%m-%d %H:%M:%S') except Exception: date = datetime.now().strftime('%Y-%m-%d %H:%M:%S') out.append({ 'uid': key, 'message_id': str(msg.get('Message-ID', '')), 'from_name': name, 'from_addr': addr.lower(), 'to_addr': str(msg.get('To', '')), 'subject': str(msg.get('Subject', '')) or '(geen onderwerp)', 'body': _text(msg), 'date': date, }) return out finally: try: conn.logout() except Exception: pass def send(cfg, to, subject, body, in_reply_to=None): host = cfg.get('smtp_host') if not host: raise RuntimeError('Geen SMTP-server ingesteld') user = cfg.get('smtp_user') or cfg.get('imap_user', '') password = cfg.get('smtp_pass') or cfg.get('imap_pass', '') port = int(cfg.get('smtp_port') or 587) msg = EmailMessage() msg['From'] = cfg.get('smtp_from') or user msg['To'] = to msg['Subject'] = subject msg['Date'] = formatdate(localtime=True) msg['Message-ID'] = make_msgid() if in_reply_to: msg['In-Reply-To'] = in_reply_to msg['References'] = in_reply_to msg.set_content(body) if port == 465: server = smtplib.SMTP_SSL(host, port, timeout=30) else: server = smtplib.SMTP(host, port, timeout=30) server.starttls() with server: if user: server.login(user, password) server.send_message(msg) return msg['Message-ID']