9c7ba6fa5d
Prompts used since the previous commit: - User said they do not want final destination folders in Archief. - User clarified: only treat INBOX.Facturen - verwerkt as already sorted; treat all other mailboxes as sources except Sent, Drafts, Trash and Spam. Special observations: - Updated source-folder selection accordingly. - Removed Archief final destination creation and added Technisch destination folders. - Verified Python compilation and route/folder consistency.
252 lines
7.9 KiB
Python
252 lines
7.9 KiB
Python
#!/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",
|
|
# Technisch
|
|
"Technisch",
|
|
"Technisch.DMARC",
|
|
]
|
|
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()
|