Initial status for developed files on dagda

This commit is contained in:
mailcat
2026-07-20 20:49:36 +02:00
commit 337378a362
102 changed files with 37778 additions and 0 deletions
+217
View File
@@ -0,0 +1,217 @@
#!/usr/bin/env python3
"""
Afmelden van mailinglijsten op basis van decisions.json.
Verwerkt List-Unsubscribe headers: HTTP (GET/POST) en mailto.
Gebruik: python3 unsubscribe.py
Log: unsubscribe_log.json (resultaat per lijst)
"""
import json
import re
import sys
import time
import smtplib
import ssl
import urllib.request
import urllib.parse
import urllib.error
import config
from email.mime.text import MIMEText
from pathlib import Path
DECISIONS_FILE = Path(__file__).parent / "decisions.json"
LOG_FILE = Path(__file__).parent / "unsubscribe_log.json"
REQUEST_TIMEOUT = 10 # seconden per HTTP-verzoek
DELAY_BETWEEN = 1.5 # seconden tussen verzoeken (beleefd)
# ── helpers ──────────────────────────────────────────────────────────────────
def parse_unsub_header(raw: str) -> dict[str, list[str]]:
"""
Parseer List-Unsubscribe header naar {'http': [...], 'mailto': [...]}.
Formaat: <url>, <url>, ... (RFC 2369)
"""
result = {"http": [], "mailto": []}
for match in re.finditer(r"<([^>]+)>", raw):
url = match.group(1).strip()
if url.startswith("http"):
result["http"].append(url)
elif url.startswith("mailto:"):
result["mailto"].append(url)
return result
def try_http_unsub(url: str) -> tuple[bool, str]:
"""Probeer afmelden via HTTP GET. Geeft (succes, melding)."""
try:
req = urllib.request.Request(
url,
headers={"User-Agent": "Mozilla/5.0 (compatible; list-unsubscribe)"},
)
with urllib.request.urlopen(req, timeout=REQUEST_TIMEOUT) as resp:
code = resp.getcode()
return (code == 200), f"HTTP {code}"
except urllib.error.HTTPError as e:
return False, f"HTTP {e.code}"
except Exception as e:
return False, str(e)
def try_mailto_unsub(mailto: str, smtp: smtplib.SMTP, from_addr: str) -> tuple[bool, str]:
"""Stuur een lege e-mail naar het afmeldadres."""
try:
parsed = urllib.parse.urlparse(mailto)
to_addr = parsed.path
params = urllib.parse.parse_qs(parsed.query)
subject = params.get("subject", ["unsubscribe"])[0]
msg = MIMEText("")
msg["From"] = from_addr
msg["To"] = to_addr
msg["Subject"] = subject
smtp.sendmail(from_addr, [to_addr], msg.as_string())
return True, f"mailto verzonden → {to_addr}"
except Exception as e:
return False, str(e)
# ── main ──────────────────────────────────────────────────────────────────────
def run():
if not DECISIONS_FILE.exists():
print("decisions.json niet gevonden. Draai eerst review_mailinglists.py.")
sys.exit(1)
decisions = json.loads(DECISIONS_FILE.read_text(encoding="utf-8"))
te_afmelden = {
domain: info
for domain, info in decisions.items()
if isinstance(info, dict) and info.get("keuze") == "a"
and info.get("unsubscribe")
}
handmatig = {
domain: info
for domain, info in decisions.items()
if isinstance(info, dict) and info.get("keuze") == "a"
and not info.get("unsubscribe")
}
print(f"Afmelden: {len(te_afmelden)} via List-Unsubscribe, "
f"{len(handmatig)} handmatig (geen header).\n")
# Laad bestaand log (herstart-safe)
log: dict[str, dict] = {}
if LOG_FILE.exists():
try:
log = json.loads(LOG_FILE.read_text(encoding="utf-8"))
al_klaar = sum(1 for v in log.values() if v.get("succes"))
print(f"Bestaand log geladen: {al_klaar} al verwerkt.\n")
except Exception:
pass
cfg = config.load()
# SMTP verbinding voor mailto-afmeldingen
smtp = None
heeft_mailto = any(
any(u.startswith("mailto:") for u in info.get("unsubscribe", []))
for info in te_afmelden.values()
)
if heeft_mailto:
print(f"Sommige afmeldingen gaan via e-mail (mailto:). Account: {cfg.username}")
try:
context = ssl.create_default_context()
smtp = smtplib.SMTP(cfg.smtp_host, cfg.smtp_port)
smtp.starttls(context=context)
smtp.login(cfg.username, cfg.password)
print("SMTP verbonden.\n")
except Exception as e:
print(f"SMTP mislukt: {e}\n mailto-afmeldingen worden overgeslagen.")
smtp = None
# Verwerk afmeldingen
total = len(te_afmelden)
for idx, (domain, info) in enumerate(te_afmelden.items(), 1):
naam = info.get("naam", domain)
if domain in log and log[domain].get("succes"):
print(f"[{idx}/{total}] {naam} — al afgemeld, overgeslagen.")
continue
print(f"[{idx}/{total}] {naam} ({domain})")
succes = False
methode = ""
melding = ""
for raw_unsub in info.get("unsubscribe", []):
parsed = parse_unsub_header(raw_unsub)
# Probeer HTTP eerst
for url in parsed["http"]:
ok, msg = try_http_unsub(url)
if ok:
succes = True
methode = "HTTP GET"
melding = f"{msg}{url[:80]}"
break
else:
melding = f"{msg}{url[:60]}"
if succes:
break
# Dan mailto
if smtp:
for mailto in parsed["mailto"]:
ok, msg = try_mailto_unsub(mailto, smtp, cfg.username)
if ok:
succes = True
methode = "mailto"
melding = msg
break
if succes:
break
status = "" if succes else ""
print(f" {status} {methode or 'mislukt'} {melding}")
log[domain] = {
"naam": naam,
"succes": succes,
"methode": methode,
"melding": melding,
}
LOG_FILE.write_text(json.dumps(log, indent=2, ensure_ascii=False), encoding="utf-8")
time.sleep(DELAY_BETWEEN)
if smtp:
smtp.quit()
# Samenvatting
gelukt = sum(1 for v in log.values() if v.get("succes"))
mislukt = sum(1 for v in log.values() if not v.get("succes"))
print(f"\n{''*50}")
print(f"Klaar. {gelukt} afgemeld, {mislukt} mislukt.")
if mislukt:
print("\nMislukt (handmatig afmelden):")
for domain, v in log.items():
if not v.get("succes"):
print(f" {v['naam']} ({domain})")
if handmatig:
print(f"\nGeen List-Unsubscribe header gevonden ({len(handmatig)}) — handmatig afmelden:")
for domain, info in handmatig.items():
print(f" {info.get('naam', domain)} ({domain})")
print(f"\nLog opgeslagen in: {LOG_FILE}")
if __name__ == "__main__":
run()