#!/usr/bin/env python3 """ Dagelijks overzicht van automatisch gesorteerde mail. Zoekt in alle IMAP-mappen (behalve Inbox, Sent, Trash, Spam, Drafts) naar berichten die gisteren zijn binnengekomen, en stuurt een HTML-samenvatting naar hans@australius.nl. Eenmalige setup: python3 dagelijks_overzicht.py --setup Handmatig testen: python3 dagelijks_overzicht.py [--gisteren | --datum 2026-07-01] [--mail-to adres] Plannen (macOS): zie dagelijks_overzicht.plist """ import imaplib import email import email.header import email.utils import json import sys import ssl import smtplib import re import config from datetime import date, timedelta, datetime from pathlib import Path from collections import defaultdict from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from imap_utils import list_folders, quote_mailbox from mail_imap_ops import fetch_body # Mappen die NIET in het overzicht komen (systeem + inbox zelf) UITSLUITINGEN = { "INBOX", "INBOX.Sent", "INBOX.Trash", "INBOX.Spam", "INBOX.Drafts", "INBOX.Spam", "INBOX.Archief", "INBOX.Archief.2020", "INBOX.Archief.2021", "INBOX.Archief.2022", "INBOX.Archief.2023", "INBOX.Archief.2024", "INBOX.Archief.2025", "INBOX.Archief.2026", } # Patroon: archief-mappen en technisch-interne mappen overslaan UITSLUIT_PATRONEN = re.compile( r"^INBOX\.(Archief|Trash|Spam|Sent|Drafts|Technisch)", re.I ) # ── config ──────────────────────────────────────────────────────────────────── def load_config(): """Laad account via centrale config.py. Gebruik --account om te wisselen.""" cfg = config.load() return { "username": cfg.username, "password": cfg.password, "imap_host": cfg.imap_host, "imap_port": cfg.imap_port, "smtp_host": cfg.smtp_host, "smtp_port": cfg.smtp_port, "from": cfg.username, "to": cfg.username, # stuur digest naar zichzelf } def arg_value(args: list[str], flag: str) -> str | None: """Return the value after a simple command-line flag.""" if flag not in args: return None idx = args.index(flag) if idx + 1 >= len(args): print(f"Ontbrekende waarde voor {flag}") sys.exit(2) return args[idx + 1] # ── helpers ─────────────────────────────────────────────────────────────────── def decode_hdr(value) -> str: if not value: return "" try: parts = email.header.decode_header(value) out = [] for raw, cs in parts: if isinstance(raw, bytes): out.append(raw.decode(cs or "utf-8", errors="replace")) else: out.append(str(raw)) return " ".join(out).strip() except Exception: return str(value or "") def imap_date(d: date) -> str: """Zet date om naar IMAP-zoekformaat: 02-Jul-2026""" return d.strftime("%d-%b-%Y") def leesbare_map(imap_naam: str) -> str: """Maak de mapnaam leesbaar door INBOX. te verwijderen.""" return imap_naam.removeprefix("INBOX.").replace(".", " › ") # ── IMAP ophalen ────────────────────────────────────────────────────────────── def haal_berichten_op(mail: imaplib.IMAP4_SSL, zoekdatum: date) -> dict[str, list[dict]]: """ Doorzoek alle relevante mappen op berichten van zoekdatum. Geeft {mapnaam: [{from, subject, time}, ...]} terug. """ since = imap_date(zoekdatum) before = imap_date(zoekdatum + timedelta(days=1)) alle_mappen = list_folders(mail) if not alle_mappen: return {} resultaat: dict[str, list[dict]] = defaultdict(list) for folder in sorted(alle_mappen): if folder in UITSLUITINGEN: continue if UITSLUIT_PATRONEN.match(folder): continue status, data = mail.select(quote_mailbox(folder), readonly=True) if status != "OK": continue # Zoek op INTERNALDATE (serverontvangst, niet Date-header) status, data = mail.search( None, f'SINCE {since} BEFORE {before}' ) if status != "OK" or not data[0]: continue ids = data[0].split() if not ids: continue for uid in ids: status, msg_data = mail.fetch( uid, "(BODY.PEEK[HEADER.FIELDS (FROM SUBJECT DATE)])" ) raw_header = fetch_body(msg_data) if status != "OK" or raw_header is None: continue msg = email.message_from_bytes(raw_header) from_raw = decode_hdr(msg.get("From", "")) subject = decode_hdr(msg.get("Subject", "(geen onderwerp)")) date_str = msg.get("Date", "") # Tijdstip opmaken try: dt = email.utils.parsedate_to_datetime(date_str) tijdstip = dt.strftime("%H:%M") except Exception: tijdstip = "?" # Afzendernaam uit From-header m = re.match(r'^"?([^"<@\n]{2,})"?\s*<', from_raw) afzender = m.group(1).strip() if m else from_raw.split("@")[0] resultaat[folder].append({ "from": afzender[:40], "subject": subject[:80], "time": tijdstip, }) return resultaat # ── HTML opbouwen ───────────────────────────────────────────────────────────── def bouw_html(berichten: dict[str, list[dict]], zoekdatum: date) -> str: datum_nl = zoekdatum.strftime("%-d %B %Y") totaal = sum(len(v) for v in berichten.values()) html = f"""
| {item["time"]} | ' f'{item["from"]} | ' f'{item["subject"]} | ' f'