Extract shared mail routing and IMAP folder helpers
Prompt since previous commit: execute step 1 Centralized route targets, source-folder policy, and modified UTF-7 mailbox quoting. Verification: py_compile, route/folder consistency, and git diff --check passed.
This commit is contained in:
+6
-198
@@ -5,191 +5,15 @@ Bestaande mappen worden overgeslagen.
|
||||
Gebruik: python3 maak_mappen.py
|
||||
"""
|
||||
|
||||
import imaplib
|
||||
import base64
|
||||
import re
|
||||
import sys
|
||||
import imaplib
|
||||
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)
|
||||
from imap_utils import list_folders, quote_mailbox
|
||||
from mail_routes import PREFIX, destination_folders
|
||||
|
||||
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",
|
||||
# Technisch
|
||||
"Technisch",
|
||||
"Technisch.DMARC",
|
||||
]
|
||||
return folders
|
||||
return destination_folders()
|
||||
|
||||
|
||||
def run():
|
||||
@@ -204,21 +28,7 @@ def run():
|
||||
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)
|
||||
existing = set(list_folders(mail))
|
||||
|
||||
te_maken = mappen()
|
||||
print(f"\n{len(te_maken)} mappen te controleren ({len(existing)} bestaan al op server)\n")
|
||||
@@ -233,9 +43,7 @@ def run():
|
||||
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}"')
|
||||
status, data = mail.create(quote_mailbox(volledige_naam))
|
||||
if status == "OK":
|
||||
print(f" + aangemaakt {folder}")
|
||||
aangemaakt += 1
|
||||
|
||||
Reference in New Issue
Block a user