#!/usr/bin/env python3 """ Maak alle nieuwe IMAP-mappen aan op australius.nl. Bestaande mappen worden overgeslagen. Gebruik: python3 maak_mappen.py """ import imaplib import base64 import re import sys import config # Sommige Dovecot-servers eisen "INBOX." als prefix voor submappen. # Zet op "" als de server mappen zonder prefix accepteert. PREFIX = "INBOX." # ── mUTF-7 helpers (RFC 3501) ───────────────────────────────────────────────── def to_mutf7(s: str) -> str: """Codeer een mapnaam naar Modified UTF-7 voor IMAP-aanroepen (RFC 3501). Verschillen met standaard base64: geen padding, '/' vervangen door ','.""" res = [] buf = [] for c in s: if 0x20 <= ord(c) <= 0x7e and c != "&": if buf: b64 = ( base64.b64encode("".join(buf).encode("utf-16-be")) .decode("ascii") .rstrip("=") .replace("/", ",") ) res.append(f"&{b64}-") buf.clear() res.append(c) elif c == "&": if buf: b64 = ( base64.b64encode("".join(buf).encode("utf-16-be")) .decode("ascii") .rstrip("=") .replace("/", ",") ) res.append(f"&{b64}-") buf.clear() res.append("&-") else: buf.append(c) if buf: b64 = ( base64.b64encode("".join(buf).encode("utf-16-be")) .decode("ascii") .rstrip("=") .replace("/", ",") ) res.append(f"&{b64}-") return "".join(res) def from_mutf7(s: str) -> str: """Decodeer een mUTF-7-mapnaam (van server) terug naar Unicode.""" res = [] i = 0 while i < len(s): if s[i] == "&": j = s.index("-", i + 1) if j == i + 1: res.append("&") else: encoded = s[i + 1 : j].replace(",", "/") pad = (4 - len(encoded) % 4) % 4 res.append( base64.b64decode(encoded + "=" * pad).decode("utf-16-be") ) i = j + 1 else: res.append(s[i]) i += 1 return "".join(res) def mappen() -> list[str]: """Alle te maken mappen, zonder prefix (prefix wordt automatisch toegevoegd).""" folders = [ # Nieuwsbrieven "Nieuwsbrieven", "Nieuwsbrieven.Leren", "Nieuwsbrieven.Leren.AI Report", "Nieuwsbrieven.Leren.Ben Tiggelaar", "Nieuwsbrieven.Leren.Brian Keating", "Nieuwsbrieven.Leren.Coursera", "Nieuwsbrieven.Leren.Elizabeth Butler", "Nieuwsbrieven.Leren.Eva Keiffenheim", "Nieuwsbrieven.Leren.MIT Open Learning", "Nieuwsbrieven.Leren.MIT Technology Review", "Nieuwsbrieven.Leren.Nick Milo", "Nieuwsbrieven.Leren.Storytelling with Data", "Nieuwsbrieven.Nieuws & Vakbladen", "Nieuwsbrieven.Nieuws & Vakbladen.Computable", "Nieuwsbrieven.Nieuws & Vakbladen.The Presentation Guru", "Nieuwsbrieven.Nieuws & Vakbladen.Vakblad Crisismanager", "Nieuwsbrieven.Overig", "Nieuwsbrieven.Overig.Lendahand", "Nieuwsbrieven.Overig.The Good Roll", # Financieel "Financieel", "Financieel.Bank", "Financieel.Bank.ABN AMRO", "Financieel.Bank.ICS", "Financieel.Bank.NPEX", "Financieel.Bank.SnelStart", "Financieel.Bank.Tellow", "Financieel.Verzekering", "Financieel.Verzekering.Univé", "Financieel.Verzekering.VEZA", "Financieel.Facturen", # Facturen per kwartaal: 2020 t/m huidig jaar + 1 *[ f"Financieel.Facturen.{jaar}.{kwartaal}" for jaar in range(2020, 2027) for kwartaal in ["Q1", "Q2", "Q3", "Q4"] ], # Werk "Werk", "Werk.Opdrachten", "Werk.Opdrachten.Circle8", "Werk.Opdrachten.Flextender", "Werk.Opdrachten.FutureXL", "Werk.Opdrachten.Get There", "Werk.Opdrachten.Huckr", "Werk.Opdrachten.IMMENS", "Werk.Opdrachten.Jamie", "Werk.Opdrachten.OneStopSourcing", "Werk.Overig", "Werk.Overig.BOIP", "Werk.Overig.Gemeente Groningen", # Diensten "Diensten", "Diensten.Hosting", "Diensten.Hosting.Backblaze", "Diensten.Hosting.Lets Encrypt", "Diensten.Hosting.STRATO", "Diensten.Hosting.TransIP", "Diensten.Software", "Diensten.Software.1Password", "Diensten.Software.CogSci Apps", "Diensten.Software.Claude", "Diensten.Software.Cursor", "Diensten.Software.Kahoot", "Diensten.Software.Matter", "Diensten.Software.Microsoft Teams", "Diensten.Software.MindNode", "Diensten.Software.Papers", "Diensten.Software.Perplexity", "Diensten.Software.Sophos", "Diensten.Telecom", "Diensten.Telecom.KPN", "Diensten.Telecom.Vodafone", # Mobiliteit "Mobiliteit", "Mobiliteit.EV", "Mobiliteit.EV.Alfen", "Mobiliteit.EV.Eneco eMobility", "Mobiliteit.EV.FastNed", "Mobiliteit.EV.Mitsubishi", "Mobiliteit.EV.OPnGO", "Mobiliteit.EV.Shell Recharge", "Mobiliteit.EV.Tesla", "Mobiliteit.OV & Reizen", "Mobiliteit.OV & Reizen.Booking", "Mobiliteit.OV & Reizen.Flitsmeister", "Mobiliteit.OV & Reizen.NS", # Bestellingen "Bestellingen", "Bestellingen.123inkt", "Bestellingen.Allekabels", "Bestellingen.bol", "Bestellingen.Coolblue", "Bestellingen.DHL", "Bestellingen.GLS", "Bestellingen.Makro", "Bestellingen.Office Centre", "Bestellingen.PostNL", "Bestellingen.Sligro", "Bestellingen.Smartphonehoesjes", "Bestellingen.Viking", # Archief nieuwe jaren "Archief.2023", "Archief.2024", "Archief.2025", "Archief.2026", ] return folders def run(): cfg = config.load() print(f"Account: {cfg.username}\n") print(f"Verbinden met {cfg.imap_host}:{cfg.imap_port} ...") try: mail = imaplib.IMAP4_SSL(cfg.imap_host, cfg.imap_port) mail.login(cfg.username, cfg.password) except Exception as e: print(f"Verbinding mislukt: {e}") sys.exit(1) # Bestaande mappen ophalen en decoderen vanuit mUTF-7 status, raw = mail.list() existing = set() for item in raw: if not item: continue # Serverresponse is ASCII (mUTF-7); decodeer naar leesbare naam raw_str = item.decode("ascii", errors="replace") m = re.search(r'"?" (.*?)$', raw_str) if m: raw_name = m.group(1).strip('"') try: existing.add(from_mutf7(raw_name)) except Exception: existing.add(raw_name) te_maken = mappen() print(f"\n{len(te_maken)} mappen te controleren ({len(existing)} bestaan al op server)\n") aangemaakt = 0 overgeslagen = 0 fouten = 0 for folder in te_maken: volledige_naam = PREFIX + folder if volledige_naam in existing or folder in existing: print(f" ✓ bestaat {folder}") overgeslagen += 1 continue # Codeer naar mUTF-7 voor IMAP (puur ASCII, ook voor &, é, enz.) gecodeerd = to_mutf7(volledige_naam) status, data = mail.create(f'"{gecodeerd}"') if status == "OK": print(f" + aangemaakt {folder}") aangemaakt += 1 else: print(f" ✗ FOUT {folder}: {data}") fouten += 1 mail.logout() print(f"\nKlaar: {aangemaakt} aangemaakt, {overgeslagen} bestonden al, {fouten} fouten.") if __name__ == "__main__": run()