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>
154 lines
6.6 KiB
Python
154 lines
6.6 KiB
Python
"""
|
|
Network preflight — proves the two halves of the login can run at the same time.
|
|
|
|
Manually, this account is a deadlock. The eCW portal only answers through the
|
|
corporate proxy, and the proxy refuses the webmail host outright, so a human
|
|
has to flip the system proxy on to log in, off to read the approval mail, and
|
|
on again to confirm it.
|
|
|
|
The bot sidesteps that by never touching the webmail *website*:
|
|
|
|
eCW portal + approval link → Playwright, launched with an explicit proxy
|
|
the approval mail → imaplib, a raw TCP socket to port 993
|
|
|
|
Two different network paths, so both work concurrently and nothing gets
|
|
toggled. The one rule that keeps it true: the SYSTEM proxy must stay OFF.
|
|
Playwright is handed the proxy per-browser; imaplib ignores WinINET and the
|
|
HTTP(S)_PROXY environment variables because it never speaks HTTP.
|
|
|
|
Run:
|
|
python "D:\\Ecw Process\\src\\check_net.py"
|
|
"""
|
|
|
|
# Run standalone from the project root: python tools\check_net.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 re
|
|
import ssl
|
|
import sys
|
|
import time
|
|
import socket
|
|
import urllib.request as urlreq
|
|
from urllib.parse import urlsplit
|
|
from ecw_bot import settings as cfg
|
|
TIMEOUT = 25
|
|
def _host_of(url: str) -> str:
|
|
return urlsplit(url).hostname or url
|
|
def _try_http(url: str, proxy: str) -> tuple:
|
|
"""(ok, detail). proxy="" forces a direct connection."""
|
|
handler = urlreq.ProxyHandler({"http": proxy, "https": proxy} if proxy else {})
|
|
opener = urlreq.build_opener(handler)
|
|
start = time.time()
|
|
try:
|
|
resp = opener.open(url, timeout=TIMEOUT)
|
|
return True, f"HTTP {resp.status} ({time.time() - start:.1f}s)"
|
|
except Exception as exc:
|
|
return False, f"{type(exc).__name__}: {str(exc)[:80]} ({time.time() - start:.1f}s)"
|
|
def _try_tcp(host: str, port: int) -> tuple:
|
|
start = time.time()
|
|
sock = socket.socket()
|
|
sock.settimeout(TIMEOUT)
|
|
try:
|
|
sock.connect((host, port))
|
|
return True, f"connected ({time.time() - start:.1f}s)"
|
|
except Exception as exc:
|
|
return False, f"{type(exc).__name__}: {exc}"
|
|
finally:
|
|
sock.close()
|
|
def _row(label: str, ok: bool, detail: str) -> None:
|
|
print(f" {label:<22} {'OK ' if ok else 'FAIL'} {detail}")
|
|
def _system_proxy_enabled():
|
|
"""True/False, or None when it cannot be read (non-Windows)."""
|
|
try:
|
|
import winreg
|
|
key = winreg.OpenKey(
|
|
winreg.HKEY_CURRENT_USER,
|
|
r"Software\Microsoft\Windows\CurrentVersion\Internet Settings")
|
|
with key:
|
|
return bool(winreg.QueryValueEx(key, "ProxyEnable")[0])
|
|
except Exception:
|
|
return None
|
|
def main() -> int:
|
|
proxy = cfg.proxy_settings()
|
|
if not proxy:
|
|
print("No proxy configured in .env — set PROXY_IP / PROXY_PORT")
|
|
return 2
|
|
proxy_url = proxy["server"]
|
|
problems = []
|
|
print(f"proxy : {proxy_url}")
|
|
print(f"portal : {_host_of(cfg.ECW_URL)}")
|
|
print(f"mailbox : {cfg.IMAP_HOST}:{cfg.IMAP_PORT}\n")
|
|
# ── the proxy itself ──────────────────────────────────────────────────────
|
|
m = re.match(r"https?://([^:/]+):(\d+)", proxy_url)
|
|
print("proxy reachability")
|
|
if m:
|
|
ok, detail = _try_tcp(m.group(1), int(m.group(2)))
|
|
_row("TCP to proxy", ok, detail)
|
|
if not ok:
|
|
problems.append("the proxy itself is unreachable — check VPN / network")
|
|
print()
|
|
# ── eCW: must need the proxy ──────────────────────────────────────────────
|
|
print("eCW portal (browser goes through the proxy)")
|
|
direct_ok, direct_detail = _try_http(cfg.ECW_URL, "")
|
|
proxy_ok, proxy_detail = _try_http(cfg.ECW_URL, proxy_url)
|
|
_row("without proxy", direct_ok, direct_detail)
|
|
_row("through proxy", proxy_ok, proxy_detail)
|
|
if not proxy_ok:
|
|
problems.append("eCW is unreachable through the proxy — the bot cannot log in")
|
|
print()
|
|
# ── mailbox: must NOT need the proxy ──────────────────────────────────────
|
|
print("mailbox (imaplib — raw socket, never proxied)")
|
|
imap_ok, imap_detail = _try_tcp(cfg.IMAP_HOST, cfg.IMAP_PORT)
|
|
_row(f"TCP :{cfg.IMAP_PORT}", imap_ok, imap_detail)
|
|
if imap_ok:
|
|
start = time.time()
|
|
try:
|
|
conn = (imap_ssl(cfg) if cfg.IMAP_SSL else None)
|
|
conn.login(cfg.IMAP_USERNAME, cfg.IMAP_PASSWORD)
|
|
conn.logout()
|
|
_row("IMAP login", True, f"{cfg.IMAP_USERNAME} ({time.time() - start:.1f}s)")
|
|
except Exception as exc:
|
|
_row("IMAP login", False, f"{type(exc).__name__}: {str(exc)[:80]}")
|
|
problems.append("IMAP login failed — run check_imap.py")
|
|
else:
|
|
problems.append("port 993 is blocked — the bot cannot read the approval mail")
|
|
print()
|
|
# ── the rule that makes it work ───────────────────────────────────────────
|
|
print("system proxy (must stay OFF)")
|
|
sysproxy = _system_proxy_enabled()
|
|
if sysproxy is None:
|
|
_row("ProxyEnable", True, "not readable (non-Windows?) — skipped")
|
|
elif sysproxy:
|
|
_row("ProxyEnable", False, "1 — system proxy is ON")
|
|
problems.append(
|
|
"the system proxy is ON. Turn it OFF: the bot proxies only its own "
|
|
"browser, and a system-wide proxy is what blocks the webmail host")
|
|
else:
|
|
_row("ProxyEnable", True, "0 — off, correct")
|
|
print()
|
|
|
|
if problems:
|
|
print("PROBLEMS")
|
|
for p in problems:
|
|
print(f" - {p}")
|
|
return 1
|
|
print("All good — the browser can reach eCW through the proxy while imaplib")
|
|
print("reads the mailbox directly. No proxy toggling needed.")
|
|
return 0
|
|
def imap_ssl(c):
|
|
import imaplib
|
|
return imaplib.IMAP4_SSL(c.IMAP_HOST, c.IMAP_PORT,
|
|
ssl_context=ssl.create_default_context())
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|