Initial commit
Claude has done a lot of work, but there are still some errors which cause the mails to be distributed to wrong mail boxes. I am now going to let codex and claude compete. They will have their own branches and merge to main as soon as we are somewhere where we can switch AI.
This commit is contained in:
+253
@@ -0,0 +1,253 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user