This commit is contained in:
2026-06-16 16:56:27 +03:30
parent a5d09be46e
commit 24155ad6bf
22 changed files with 2485 additions and 762 deletions
+57 -11
View File
@@ -8,15 +8,18 @@ import time
import uvicorn
from config import config
from db import flow_config_repo, init_db
from db.repositories.runs import runs_repo
from config import config
from logger import get_logger
log = get_logger(__name__)
def _load_active_flow() -> dict[str, object]:
cfg = asyncio.run(flow_config_repo.get_active())
if cfg is None:
print("[WARN] No active flow config — running without dynamic flow steps.", file=sys.stderr)
log.warning("No active flow config — running without dynamic flow steps.")
return cfg or {}
@@ -32,7 +35,7 @@ def _record(
total_ms = int((time.monotonic() - t0) * 1000)
if exc is not None:
asyncio.run(runs_repo.insert(scenario, identifier, False, str(exc), [], total_ms))
print(f"[FAIL] {identifier}: {exc}", file=sys.stderr)
log.error("%s failed: %s", identifier, exc)
return
if isinstance(flow_result, DynamicFlowResult):
@@ -44,12 +47,13 @@ def _record(
scenario, identifier, flow_result.success,
flow_result.failure_reason, steps_data, total_ms,
))
status = "OK" if flow_result.success else f"FAIL ({flow_result.failure_reason})"
if flow_result.success:
log.info("%s OK — %d ms", identifier, total_ms)
else:
log.error("%s FAIL (%s) — %d ms", identifier, flow_result.failure_reason, total_ms)
else:
asyncio.run(runs_repo.insert(scenario, identifier, True, None, [], total_ms))
status = "OK (no flow config)"
print(f"[{status}] {identifier}{total_ms} ms")
log.info("%s OK (no flow config)%d ms", identifier, total_ms)
def run_otp(phone: str) -> None:
@@ -58,14 +62,14 @@ def run_otp(phone: str) -> None:
from crawler.scenarios.otp_login import OTPLoginScenario
def otp_resolver(p: str) -> str:
# TODO: wire up your SMS gateway / webhook here.
raise NotImplementedError("Implement otp_resolver to fetch OTP from your SMS gateway")
flow_cfg = _load_active_flow()
target_url: str = flow_cfg.get("target_url") or "" if flow_cfg else ""
t0 = time.monotonic()
try:
with driver_session() as driver:
OTPLoginScenario(driver, phone, otp_resolver).run()
OTPLoginScenario(driver, phone, otp_resolver, target_url).run()
result = DynamicFlow(driver, flow_cfg).run() if flow_cfg else None
except Exception as exc:
_record("otp", phone, None, exc, t0)
@@ -79,10 +83,11 @@ def run_fresh() -> None:
from crawler.scenarios.fresh_session import FreshSessionScenario
flow_cfg = _load_active_flow()
target_url: str = flow_cfg.get("target_url") or "" if flow_cfg else ""
t0 = time.monotonic()
try:
with driver_session() as driver:
FreshSessionScenario(driver).run()
FreshSessionScenario(driver, target_url).run()
result = DynamicFlow(driver, flow_cfg).run() if flow_cfg else None
except Exception as exc:
_record("fresh", "fresh", None, exc, t0)
@@ -90,6 +95,34 @@ def run_fresh() -> None:
_record("fresh", "fresh", result, None, t0)
def run_batch(workers: int, total_runs: int, stagger_ms: int, headless: bool) -> None:
from crawler.batch import run_batch as _run_batch
flow_cfg = _load_active_flow()
if not flow_cfg:
log.error("No active flow config — activate one in the admin panel first.")
sys.exit(1)
completed_count = [0]
def _progress(completed: int, succeeded: int, failed: int) -> None:
completed_count[0] = completed
log.info("Progress: %d/%d (ok=%d fail=%d)", completed, total_runs, succeeded, failed)
result = _run_batch(
cfg=flow_cfg,
workers=workers,
total_runs=total_runs,
headless=headless,
stagger_ms=stagger_ms,
on_progress=_progress,
)
log.info(
"Batch complete — succeeded=%d/%d failed=%d",
result["succeeded"], result["total"], result["failed"],
)
def run_admin() -> None:
init_db()
from admin.main import app
@@ -107,6 +140,12 @@ def main() -> None:
sub.add_parser("fresh", help="Run Scenario 2 (fresh session)")
batch_p = sub.add_parser("batch", help="Run many parallel fresh-session runners")
batch_p.add_argument("--workers", type=int, default=3, help="Parallel Chrome instances (default: 3)")
batch_p.add_argument("--runs", type=int, default=10, help="Total runs to complete (default: 10)")
batch_p.add_argument("--stagger", type=int, default=500, help="Ms delay between worker launches (default: 500)")
batch_p.add_argument("--no-headless", dest="headless", action="store_false", help="Show browser windows")
args = parser.parse_args()
if args.cmd == "admin":
@@ -114,12 +153,19 @@ def main() -> None:
elif args.cmd == "otp":
phones = [args.phone] if args.phone else config.phone_numbers
if not phones:
print("No phone numbers configured. Set PHONE_NUMBERS in .env or pass --phone", file=sys.stderr)
log.error("No phone numbers configured. Set PHONE_NUMBERS in .env or pass --phone")
sys.exit(1)
for phone in phones:
run_otp(phone)
elif args.cmd == "fresh":
run_fresh()
elif args.cmd == "batch":
run_batch(
workers=args.workers,
total_runs=args.runs,
stagger_ms=args.stagger,
headless=args.headless,
)
if __name__ == "__main__":