192 lines
6.0 KiB
Python
192 lines
6.0 KiB
Python
# -*- 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 '<chybi>'}")
|
|
|
|
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)
|