# -*- coding: utf-8 -*- # ============================================================================= # Nazev: jnj_scan_failed_sent_v1.0.py # Verze: 1.0 # Datum: 2026-06-16 # Popis: JNJ-native (MAPI / pywin32). Projde slozku Odeslane (Sent Items) za # poslednich N dni a najde PODEZRELE e-maily = pravdepodobne NEODESLANE # (napr. SendAs denied). Kazdy podezrely ULOZI jako .msg a vypise, ktere # priznaky se trefily. NIC neodesila ani nemaze, jen CTE a uklada. # Priznaky podezreni (cteno ze ZIVE polozky): # FAIL_BODY = telo/ReportText obsahuje "could not be sent" / "SendAsDenied" # / "permission to send the message on behalf" / "TransportSend" # SENDAS_BUZ = PrimarySendAccount/SentRepresenting/Sender obsahuje "buzalka.cz" # NO_MSGID = chybi Internet Message-ID (0x1035) -- slabsi priznak # Pouziti: JNJ Python (Thonny), Outlook s JNJ schrankou. # pip install pywin32 ; python jnj_scan_failed_sent_v1.0.py # ============================================================================= import os import re import sys import datetime import win32com.client # pywin32 # ----------------------------- KONFIGURACE ----------------------------------- SENDER_SMTP = "vbuzalka@its.jnj.com" DAYS = 60 # okno: poslednich N dni OUTPUT_DIR = r"C:\Users\vbuzalka\sent_suspects" # Ukladat i polozky, ktere maji JEN slaby priznak NO_MSGID (bez FAIL/SENDAS)? # True = vc. provizornich kopii bez Message-ID (muze byt vic souboru). INCLUDE_NO_MSGID = True # ----------------------------------------------------------------------------- OL_MSG = 3 OL_FOLDER_SENT = 5 PA = "http://schemas.microsoft.com/mapi/proptag/0x{:s}" P_MSGID = "1035001F" P_SENDACCT = "0E28001F" # PrimarySendAccount P_SENTREP_EM = "0065001F" # SentRepresentingEmailAddress P_SENDER_EM = "0C1F001F" # SenderEmailAddress P_REPORTTEXT = "1001001F" # ReportText (kdyz existuje) FAIL_SIGNS = [ "could not be sent", "sendasdenied", "permission to send the message on behalf", "transportsend operation has failed", "mapiexceptionsendasdenied", "tuto zpravu nelze odeslat", # pro pripad lokalizace ] def gp(item, tag): try: return item.PropertyAccessor.GetProperty(PA.format(tag)) except Exception: return None def get_sent_folder(ns): try: for acct in ns.Accounts: if (acct.SmtpAddress or "").lower() == SENDER_SMTP.lower(): return acct.DeliveryStore.GetDefaultFolder(OL_FOLDER_SENT) except Exception: pass return ns.GetDefaultFolder(OL_FOLDER_SENT) def safe(s, n=34): return re.sub(r"[^A-Za-z0-9._-]+", "_", (s or ""))[:n].strip("_") def analyze(item): """Vrati seznam priznaku (flags) pro polozku.""" flags = [] # 1) FAIL_BODY: telo + ReportText blob = "" try: blob += (item.Body or "") except Exception: pass rt = gp(item, P_REPORTTEXT) if rt: blob += "\n" + str(rt) low = blob.lower() if any(s in low for s in FAIL_SIGNS): flags.append("FAIL_BODY") # 2) SENDAS_BUZ: nektera z odesilatelskych poloz. obsahuje buzalka.cz for tag in (P_SENDACCT, P_SENTREP_EM, P_SENDER_EM): v = gp(item, tag) if v and "buzalka.cz" in str(v).lower(): flags.append("SENDAS_BUZ") break # 3) NO_MSGID mid = gp(item, P_MSGID) if not mid: flags.append("NO_MSGID") return flags, (mid or "") def main(): os.makedirs(OUTPUT_DIR, exist_ok=True) cutoff = datetime.date.today() - datetime.timedelta(days=DAYS) outlook = win32com.client.Dispatch("Outlook.Application") ns = outlook.GetNamespace("MAPI") sent = get_sent_folder(ns) items = sent.Items items.Sort("[SentOn]", True) # nejnovejsi prvni print(f"Slozka : {sent.FolderPath}") print(f"Okno : poslednich {DAYS} dni (od {cutoff.isoformat()})") print(f"Vystup : {OUTPUT_DIR}") print(f"NO_MSGID se uklada: {INCLUDE_NO_MSGID}") print("=" * 90) scanned = saved = strong = 0 for it in list(items): try: if it.Class != 43: continue except Exception: continue # datum + early stop try: s = it.SentOn sdate = datetime.date(s.year, s.month, s.day) except Exception: sdate = None if sdate is not None: if sdate < cutoff: break # dale uz jen starsi (serazeno desc) scanned += 1 flags, mid = analyze(it) if not flags: continue is_strong = ("FAIL_BODY" in flags) or ("SENDAS_BUZ" in flags) if not is_strong and not (INCLUDE_NO_MSGID and "NO_MSGID" in flags): continue saved += 1 if is_strong: strong += 1 subj = "" try: subj = it.Subject or "" except Exception: pass try: tail = (it.EntryID or "")[-20:] except Exception: tail = "" tagstr = "+".join(flags) print(f"\n[{saved}] {sdate} flags={tagstr}") print(f" subj : {subj}") print(f" msgid: {mid if mid else ''}") fn = f"{('STRONG' if is_strong else 'weak')}_{sdate}_{safe(subj,30)}_{tail}.msg" path = os.path.join(OUTPUT_DIR, fn) try: it.SaveAs(path, OL_MSG) print(f" ulozeno: {fn}") except Exception as e: print(f" !! SaveAs chyba: {e}") print("\n" + "=" * 90) print(f"Prohledano (v okne): {scanned}") print(f"Ulozeno podezrelych: {saved} (z toho silnych FAIL/SENDAS: {strong})") print(f"Soubory v: {OUTPUT_DIR} -> prines je domu ke kontrole.") print("Pozn.: STRONG_* = telo NDR nebo send-account buzalka.cz (skoro jiste neodeslano).") print(" weak_* = jen chybi Message-ID (muze byt i provizorni kopie, co se pozdeji dokonci).") if __name__ == "__main__": try: main() except Exception as e: print("CHYBA:", e) sys.exit(1)