notebook
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
#!/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)
|
||||
Reference in New Issue
Block a user