Files
ManikandanandClaude Opus 5.5 ba15a574ab Initial import: eCW Charge Validation bot and results Portal
Snapshot of the working project as of 2026-10-23 (local 2d4d4fc), one
commit with no earlier history: the tracked files were de-identified
first (synthetic claim ids and patient data only; see Context.md
section 7). Credentials, run logs, screenshots, claim reports and client
workbooks are gitignored and not included.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 18:38:17 +05:30

170 lines
6.7 KiB
Python

"""
Mailbox probe — run this before trusting `OTP_MODE=imap`.
IMAP_HOST in `.env` is a guess. All we were handed is a Roundcube webmail link
on `cpanel1.methodhosting.net`; cPanel serves IMAP from the cPanel host on some
setups and from `mail.<domain>` on others, and the only way to know which is to
try. This connects to each candidate in turn, logs in, and prints the newest
subjects so it is obvious the eCW code actually lands in this inbox.
Nothing is modified — mail is opened with readonly=True, so nothing gets marked
as read.
Run:
python "D:\\Ecw Process\\src\\check_imap.py"
Then copy the host it reports as WORKING into IMAP_HOST in `.env`.
"""
# Run standalone from the project root: python tools\check_imap.py
# The package lives under src\ , which is not on the path for a loose script.
import sys as _sys
from pathlib import Path as _Path
_sys.path.insert(0, str(_Path(__file__).resolve().parent.parent / "src"))
# Windows hands these scripts a cp1252 console, which cannot encode the box
# characters and arrows below — check_imap.py died on its own output with
# UnicodeEncodeError. See runlog._force_utf8_console.
for _s in (_sys.stdout, _sys.stderr):
try:
_s.reconfigure(encoding="utf-8", errors="replace")
except Exception:
pass
import ssl
import sys
import email
import socket
import imaplib
from email.header import decode_header, make_header
from ecw_bot import settings as cfg
from ecw_bot.auth.mailbox import _extract_code
# Order matters — the configured value is tried first so a already-correct
# .env passes on the first attempt instead of hammering the other hosts.
def _candidates() -> list:
domain = cfg.IMAP_USERNAME.partition("@")[2].strip()
hosts = [cfg.IMAP_HOST]
if domain:
hosts += [f"mail.{domain}", domain]
# cpanel1.methodhosting.net → methodhosting.net is not a mail host, but
# mail.cpanel1... occasionally is. Cheap to include, harmless if it fails.
if cfg.IMAP_HOST:
hosts.append(f"mail.{cfg.IMAP_HOST}")
seen, out = set(), []
for h in hosts:
h = (h or "").strip().rstrip("/")
if h and h not in seen:
seen.add(h)
out.append(h)
return out
def _subject(msg) -> str:
try:
return str(make_header(decode_header(msg.get("Subject") or "")))
except Exception:
return str(msg.get("Subject") or "")
def _connect(host: str, port: int, use_ssl: bool, timeout: int = 15):
# imaplib has no timeout parameter before 3.9 and ignores it for IMAP4_SSL
# on some builds, so set it globally — otherwise a wrong host hangs for the
# full TCP retry window instead of failing in a few seconds.
socket.setdefaulttimeout(timeout)
if use_ssl:
return imaplib.IMAP4_SSL(host, port, ssl_context=ssl.create_default_context())
conn = imaplib.IMAP4(host, port)
conn.starttls(ssl_context=ssl.create_default_context())
return conn
def _try_host(host: str) -> bool:
"""Connect, log in, and print recent subjects. True if the host worked."""
label = f"{host}:{cfg.IMAP_PORT} ssl={cfg.IMAP_SSL}"
print(f"\n── trying {label}")
conn = None
try:
conn = _connect(host, cfg.IMAP_PORT, cfg.IMAP_SSL)
except Exception as exc:
print(f" connect failed: {type(exc).__name__}: {exc}")
return False
try:
conn.login(cfg.IMAP_USERNAME, cfg.IMAP_PASSWORD)
except Exception as exc:
# Reaching the server but failing the login is a different problem from
# a wrong host — say so, because it means the host is probably right.
print(f" connected, but LOGIN failed: {exc}")
print(f" → host looks right; check IMAP_USERNAME / IMAP_PASSWORD")
# Specifically NOT the webmail 2FA. cPanel's Two-Factor Authentication
# guards the cPanel and Roundcube web interfaces only; IMAP/POP3/SMTP
# are exempt, verified on this account (CAPABILITY offers AUTH=PLAIN and
# AUTH=LOGIN, no 2FA mechanism). So a failure here is the password.
print(f" → not the webmail security code: cPanel 2FA does not apply "
f"to IMAP")
try:
conn.logout()
except Exception:
pass
return False
print(" login OK")
try:
typ, data = conn.list()
if typ == "OK":
names = [l.decode(errors="replace").split(' "." ')[-1].strip('"')
for l in (data or [])]
print(f" folders: {', '.join(names[:12])}")
except Exception:
pass
try:
typ, data = conn.select(cfg.IMAP_FOLDER, readonly=True)
if typ != "OK":
print(f" cannot select folder {cfg.IMAP_FOLDER!r}: {data}")
return False
total = int(data[0]) if data and data[0] else 0
print(f" {cfg.IMAP_FOLDER}: {total} message(s)")
typ, data = conn.search(None, "ALL")
ids = data[0].split() if typ == "OK" and data and data[0] else []
if not ids:
print(" inbox is empty — trigger a login so eCW mails a code, then rerun")
else:
print(" newest messages:")
for num in reversed(ids[-10:]):
typ, raw = conn.fetch(num, "(RFC822.HEADER)")
if typ != "OK" or not raw or not raw[0]:
continue
msg = email.message_from_bytes(raw[0][1])
subj = _subject(msg)
code = _extract_code(subj)
hit = f" [code in subject: {code}]" if code else ""
print(f" {msg.get('Date', '?')[:31]:<32} {subj[:60]}{hit}")
return True
finally:
try:
conn.logout()
except Exception:
pass
def main() -> int:
if not (cfg.IMAP_USERNAME and cfg.IMAP_PASSWORD):
print("IMAP_USERNAME / IMAP_PASSWORD not set in .env — nothing to probe")
return 2
print(f"mailbox : {cfg.IMAP_USERNAME}")
print(f"port : {cfg.IMAP_PORT} (ssl={cfg.IMAP_SSL})")
print(f"folder : {cfg.IMAP_FOLDER}")
print(f"configured IMAP_HOST: {cfg.IMAP_HOST or '(blank)'}")
for host in _candidates():
if _try_host(host):
print(f"\n✓ WORKING host: {host}")
if host != cfg.IMAP_HOST:
print(f" → set IMAP_HOST={host} in .env")
else:
print(" → .env is already correct, OTP_MODE=imap is good to go")
return 0
print("\n✗ No candidate host worked.")
print(" Ask hosting for the IMAP server name, or read it from Roundcube:")
print(" Settings → Mail Accounts / Configure → IMAP server.")
print(" Port 143 with IMAP_SSL=false is worth a try if 993 is blocked.")
return 1
if __name__ == "__main__":
sys.exit(main())