Files
janssen/mailstore/mailstore_folder_v1.0.py
2026-06-11 21:49:04 +02:00

177 lines
5.6 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
================================================================================
Nazev: mailstore_folder_v1.0.py
Verze: 1.0
Datum: 2026-06-11
Autor: Vladimir Buzalka (asistovano Claude)
Popis: Vypise obsah jedne MailStore slozky jako seznam zprav
(datum | od | predmet) pres davkovy IMAP FETCH hlavicek.
Predstupen ingestu - overuje davkove cteni hlavicek.
Argument = plna cesta slozky (fullName z mapy), napr.:
"vladimir.buzalka@buzalka.cz/Exchange vladimir.buzalka/Sent Items"
Zdroj: MailStore IMAP server, port 143, STARTTLS, auth Prosty text (LOGIN).
IMAP FETCH BODY.PEEK[HEADER.FIELDS (...)] = hlavicky bez oznaceni
jako precteno. Davkove jednim prikazem, ne po jedne zprave.
Spusteni:
python mailstore_folder_v1.0.py "...slozka..." # poslednich 50
python mailstore_folder_v1.0.py "...slozka..." --limit 200
python mailstore_folder_v1.0.py "...slozka..." --all # vse (pozor velke slozky)
python mailstore_folder_v1.0.py "...slozka..." --oldest # od nejstarsich
================================================================================
"""
from __future__ import annotations
import argparse
import email
import imaplib
import re
import ssl
import sys
from email.header import decode_header
from email.utils import parsedate_to_datetime
# --- konfigurace ------------------------------------------------------------
HOST = "192.168.1.53"
PORT = 143
USER = "admin"
PASS = "*$N(B)vMUym!%"
DEFAULT_LIMIT = 50
# --- helpery ----------------------------------------------------------------
def connect() -> imaplib.IMAP4:
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
M = imaplib.IMAP4(HOST, PORT)
M.starttls(ssl_context=ctx)
M.login(USER, PASS)
return M
def encode_mutf7(s: str) -> str:
"""Nazev IMAP slozky -> modified UTF-7 (RFC 3501) kvuli diakritice
(MailStore neumi UTF8=ACCEPT). Vysledek je cisty ASCII."""
import base64 as _b64
res = []
i, n = 0, len(s)
while i < n:
ch = s[i]; o = ord(ch)
if 0x20 <= o <= 0x7e:
res.append("&-" if ch == "&" else ch); i += 1
else:
j = i
while j < n and not (0x20 <= ord(s[j]) <= 0x7e):
j += 1
enc = _b64.b64encode(s[i:j].encode("utf-16-be")).decode("ascii").rstrip("=").replace("/", ",")
res.append("&" + enc + "-"); i = j
return "".join(res)
def dec(s: str | None) -> str:
"""Dekoduje MIME-encoded hlavicku (=?utf-8?...?=) na citelny text."""
if not s:
return ""
out = []
for txt, enc in decode_header(s):
if isinstance(txt, bytes):
out.append(txt.decode(enc or "utf-8", errors="replace"))
else:
out.append(txt)
return "".join(out).replace("\r", " ").replace("\n", " ").strip()
def fmt_date(raw: str | None) -> str:
if not raw:
return "?"
try:
dt = parsedate_to_datetime(raw)
return dt.strftime("%Y-%m-%d %H:%M")
except Exception:
return (raw or "")[:16]
def short(s: str, n: int) -> str:
s = s or ""
return s if len(s) <= n else s[: n - 1] + ""
# IMAP FETCH header bloky prijdou jako tuple (b'N (BODY[...] {len}', b'<headers>')
_NUM_RX = re.compile(rb"^(\d+)\s")
def main() -> int:
ap = argparse.ArgumentParser(description="Vypis obsahu MailStore slozky")
ap.add_argument("folder", help="Plna cesta slozky (fullName z mapy)")
ap.add_argument("--limit", type=int, default=DEFAULT_LIMIT,
help=f"Pocet zprav (default {DEFAULT_LIMIT})")
ap.add_argument("--all", action="store_true", help="Vsechny zpravy (ignoruje --limit)")
ap.add_argument("--oldest", action="store_true",
help="Od nejstarsich (default: od nejnovejsich)")
args = ap.parse_args()
M = connect()
typ, data = M.select(f'"{encode_mutf7(args.folder)}"', readonly=True)
if typ != "OK":
print(f"Slozku nelze otevrit: {data}", file=sys.stderr)
return 1
total = int(data[0]) if data and data[0] else 0
print(f"Slozka: {args.folder}")
print(f"Zprav celkem: {total:,}")
if total == 0:
M.logout()
return 0
# urci rozsah porad. cisel (1 = nejstarsi, total = nejnovejsi)
if args.all:
lo, hi = 1, total
else:
n = min(args.limit, total)
lo, hi = (1, n) if args.oldest else (total - n + 1, total)
rng = f"{lo}:{hi}"
shown = hi - lo + 1
order = "nejstarsi" if args.oldest else "nejnovejsi"
print(f"Zobrazuji {shown} zprav ({order} prvni), rozsah #{rng}")
print("=" * 100)
# davkovy FETCH hlavicek
typ, msgs = M.fetch(rng, "(BODY.PEEK[HEADER.FIELDS (DATE FROM SUBJECT)])")
rows = []
for item in msgs:
if not isinstance(item, tuple):
continue
meta, hdr_bytes = item[0], item[1]
m = _NUM_RX.match(meta or b"")
seqno = int(m.group(1)) if m else 0
hdr = email.message_from_bytes(hdr_bytes)
rows.append((seqno, fmt_date(hdr.get("Date")),
dec(hdr.get("From")), dec(hdr.get("Subject"))))
rows.sort(key=lambda r: r[0], reverse=not args.oldest)
print(f"{'#':>6} {'Datum':<16} {'Od':<32} Predmet")
print("-" * 100)
for seqno, d, frm, subj in rows:
print(f"{seqno:>6} {d:<16} {short(frm, 32):<32} {short(subj, 40)}")
M.logout()
print("=" * 100)
print(f"Vypsano {len(rows)} zprav.")
return 0
if __name__ == "__main__":
try:
sys.exit(main())
except KeyboardInterrupt:
print("\nPreruseno", file=sys.stderr)
sys.exit(1)