#!/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'') _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)