Initial resurrection kit — 81 scripts, 62 references, configs, systemd units, crons, Docker compose files, Caddy config, master README
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
#!/usr/bin/env python3
|
||||
"""BoxPilot Logistics email triage — detect and move solicitations.
|
||||
|
||||
Heuristic: if sender is from a free/personal email domain (gmail, yahoo, etc.)
|
||||
AND the email body or subject references a different business domain,
|
||||
it's likely solicitation. Move to INBOX.Solicitation.
|
||||
"""
|
||||
import imaplib, email, re, os, json, hashlib
|
||||
from email.utils import parsedate_to_datetime
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
HOST = "mail.boxpilotlogistics.com"
|
||||
PORT = 993
|
||||
USER = "hello@boxpilotlogistics.com"
|
||||
PW = "1New.opportunity"
|
||||
SOLICIT_FOLDER = "Solicitation"
|
||||
CHECK_HOURS = 48
|
||||
|
||||
# Free/personal email domains — if sender is from these AND claims another company, it's sus
|
||||
FREE_DOMAINS = {
|
||||
"gmail.com", "yahoo.com", "yahoo.co.uk", "ymail.com", "outlook.com",
|
||||
"hotmail.com", "live.com", "msn.com", "aol.com", "icloud.com",
|
||||
"me.com", "mac.com", "protonmail.com", "proton.me", "pm.me",
|
||||
"mail.com", "inbox.com", "zoho.com", "yandex.com", "gmx.com",
|
||||
"fastmail.com", "tutanota.com", "rediffmail.com", "libero.it",
|
||||
"web.de", "gmx.de", "t-online.de", "online.de",
|
||||
}
|
||||
|
||||
def safe_str(val):
|
||||
if val is None: return ""
|
||||
if isinstance(val, str): return val
|
||||
try: return str(val)
|
||||
except: return ""
|
||||
|
||||
def get_body_text(msg):
|
||||
"""Extract plain text body."""
|
||||
body = ""
|
||||
if msg.is_multipart():
|
||||
for part in msg.walk():
|
||||
ct = part.get_content_type()
|
||||
if ct == "text/plain":
|
||||
payload = part.get_payload(decode=True)
|
||||
if payload:
|
||||
body += payload.decode("utf-8", errors="replace")
|
||||
elif ct == "text/html" and not body:
|
||||
payload = part.get_payload(decode=True)
|
||||
if payload:
|
||||
body += payload.decode("utf-8", errors="replace")
|
||||
else:
|
||||
payload = msg.get_payload(decode=True)
|
||||
if payload:
|
||||
body = payload.decode("utf-8", errors="replace")
|
||||
return body
|
||||
|
||||
def extract_domains(text):
|
||||
"""Extract all domain names from text (from URLs and email addresses)."""
|
||||
domains = set()
|
||||
# URLs
|
||||
for m in re.finditer(r'https?://(?:www\.)?([a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.[a-z]{2,}(?:\.[a-z]{2,})?)', text, re.IGNORECASE):
|
||||
d = m.group(1).lower()
|
||||
# Skip common non-business domains
|
||||
if d not in ('google.com', 'youtube.com', 'facebook.com', 'linkedin.com', 'twitter.com', 'x.com', 'instagram.com'):
|
||||
domains.add(d)
|
||||
# Email addresses in body
|
||||
for m in re.finditer(r'[a-z0-9._%+-]+@([a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.[a-z]{2,}(?:\.[a-z]{2,})?)', text, re.IGNORECASE):
|
||||
d = m.group(1).lower()
|
||||
if d not in FREE_DOMAINS:
|
||||
domains.add(d)
|
||||
return domains
|
||||
|
||||
def is_solicitation(msg):
|
||||
"""Determine if a message is likely solicitation."""
|
||||
sender = safe_str(msg.get("From", "")).lower()
|
||||
subject = safe_str(msg.get("Subject", "")).lower()
|
||||
body = get_body_text(msg).lower()
|
||||
full_text = subject + " " + body
|
||||
|
||||
# Extract sender's domain
|
||||
sender_domain = ""
|
||||
m = re.search(r'@([a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.[a-z]{2,}(?:\.[a-z]{2,})?)', sender)
|
||||
if m:
|
||||
sender_domain = m.group(1).lower()
|
||||
|
||||
# If sender is from a free domain, check if they claim another company
|
||||
if sender_domain in FREE_DOMAINS:
|
||||
# Extract business domains mentioned in the email
|
||||
biz_domains = extract_domains(full_text)
|
||||
# If they reference a business domain different from their sender domain
|
||||
# AND that business domain is a real company domain (not another free domain)
|
||||
legit_biz = [d for d in biz_domains if d not in FREE_DOMAINS]
|
||||
if legit_biz:
|
||||
return True, f"Free domain ({sender_domain}) referencing business domain(s): {', '.join(list(legit_biz)[:3])}"
|
||||
|
||||
# Also flag common solicitation subject patterns from free domains
|
||||
if sender_domain in FREE_DOMAINS:
|
||||
spam_kw = ["quote", "insurance", "factoring", "fuel", "dispatch", "load board",
|
||||
"freight", "logistics", "carrier", "trucking", "broker", "authority",
|
||||
"mc authority", "dot authority", "compliance", "ifta", "irp",
|
||||
"elog", "eld", "safety", "audit", "lease", "warranty", "extended warranty"]
|
||||
found = [kw for kw in spam_kw if kw in subject]
|
||||
if found:
|
||||
return True, f"Free domain ({sender_domain}) with solicitation subject keywords: {', '.join(found)}"
|
||||
|
||||
return False, ""
|
||||
|
||||
def main():
|
||||
try:
|
||||
conn = imaplib.IMAP4_SSL(HOST, PORT)
|
||||
conn.login(USER, PW)
|
||||
except Exception as e:
|
||||
print(f"ERROR: Connection failed: {e}")
|
||||
return
|
||||
|
||||
# Select inbox
|
||||
conn.select("INBOX")
|
||||
|
||||
# Search for recent unread messages
|
||||
since = (datetime.now(timezone.utc) - timedelta(hours=CHECK_HOURS)).strftime("%d-%b-%Y")
|
||||
status, data = conn.search(None, f'(UNSEEN SINCE {since})')
|
||||
if status != "OK" or not data[0]:
|
||||
conn.logout()
|
||||
return # silent
|
||||
|
||||
moved = 0
|
||||
for num in data[0].split():
|
||||
status, msg_data = conn.fetch(num, "(RFC822)")
|
||||
if status != "OK":
|
||||
continue
|
||||
raw = email.message_from_bytes(msg_data[0][1])
|
||||
result, reason = is_solicitation(raw)
|
||||
if result:
|
||||
# Copy to Solicitation folder, then delete from inbox
|
||||
copy_status = conn.copy(num, SOLICIT_FOLDER)
|
||||
if copy_status[0] == "OK":
|
||||
conn.store(num, "+FLAGS", "\\Deleted")
|
||||
moved += 1
|
||||
subj = safe_str(raw.get("Subject", "(no subject)"))
|
||||
sender = safe_str(raw.get("From", "(unknown)"))
|
||||
|
||||
# Expunge deleted messages
|
||||
if moved > 0:
|
||||
conn.expunge()
|
||||
|
||||
conn.logout()
|
||||
|
||||
if moved > 0:
|
||||
print(f"📬 BoxPilot — Moved {moved} solicitation(s) to Solicitation folder")
|
||||
# silent if nothing moved
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user