-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.py
More file actions
166 lines (146 loc) · 5.67 KB
/
main.py
File metadata and controls
166 lines (146 loc) · 5.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
import getpass
import os
import time
from dotenv import load_dotenv
load_dotenv()
from utils import config # noqa: E402
from browser import close_stealth_browser, launch_stealth_browser # noqa: E402
from flows import ( # noqa: E402
FLOW_RUNNERS,
login_flow,
search_flow,
)
from flows.flow_detector import ( # noqa: E402
detect_bid_participation_state,
detect_current_flow_from_url,
)
from logger import logger # noqa: E402
def _save_page_html(page, run_id: str, suffix: str = "final") -> None:
if not page:
return
try:
config._ensure_debug_dirs()
html_dir = os.path.join(config.ARTIFACTS_DIR, "html")
os.makedirs(html_dir, exist_ok=True)
html_path = os.path.join(html_dir, f"page_{suffix}_{run_id}.html")
content = page.content()
with open(html_path, "w", encoding="utf-8") as f:
f.write(content)
logger.info("HTML saved: %s (%d bytes)", html_path, len(content))
except Exception as e:
logger.warning("HTML not saved: %s", e)
def _run_bid_participation_from_current_step(page) -> None:
"""
Detects current Bid Participation step from sidebar and runs flows from it to the end:
detect → run current flow → re-detect until all steps are completed.
"""
otp_env = os.environ.get("GEM_VERIFY_OTP", "").strip() or None
last_run_flow_id = None
same_count = 0
while True:
state = detect_bid_participation_state(page)
if state is None:
logger.debug("Flow detector: page is not Bid Participation, exiting")
break
if state.all_completed:
logger.info("Flow detector: all Bid Participation steps completed")
break
current = state.current_flow_id
if not current:
current = detect_current_flow_from_url(page)
if not current or current not in FLOW_RUNNERS:
logger.debug(
"Flow detector: current flow not determined or unknown: %s", current
)
break
if last_run_flow_id == current:
same_count += 1
if same_count >= 2:
logger.warning(
"Flow detector: current step unchanged twice in a row (%s), exiting",
current,
)
break
else:
same_count = 0
last_run_flow_id = current
runner = FLOW_RUNNERS[current]
logger.info("Running flow: %s", current)
try:
if current == "verify_bid":
runner.run(page, otp=otp_env)
else:
runner.run(page)
except Exception as e:
logger.exception("Flow %s failed with error: %s", current, e)
break
time.sleep(1.5)
def _save_artifacts_on_error(page, context, run_id: str, exc: Exception) -> None:
if not config.DEBUG or not page:
return
try:
trace_path = os.path.join(config.TRACE_DIR, f"error_{run_id}.zip")
context.tracing.stop(path=trace_path)
logger.info("Trace: %s (patchright show-trace %s)", trace_path, trace_path)
except Exception as e:
logger.warning("Trace not saved: %s", e)
try:
shot_path = os.path.join(config.SCREENSHOT_DIR, f"error_{run_id}.png")
page.screenshot(path=shot_path)
logger.info("Screenshot: %s", shot_path)
except Exception as e:
logger.warning("Screenshot not saved: %s", e)
logger.error("Error: %s | URL: %s", exc, page.url if page else "N/A")
def main():
login_id = os.environ.get("GEM_LOGIN", "").strip() or input("Login ID: ").strip()
password = os.environ.get("GEM_PASSWORD", "").strip() or getpass.getpass(
"Password: "
)
run_id = config._run_id()
pw, browser, context, page = launch_stealth_browser()
if config.DEBUG and config.DEBUG_TRACE:
context.tracing.start(screenshots=True, snapshots=True)
logger.info(
"DEBUG: tracing enabled | run_id=%s | artifacts: %s",
run_id,
config.ARTIFACTS_DIR,
)
trace_saved = False
try:
ok = login_flow.run(page, login_id=login_id, password=password, otp=None)
if ok:
logger.info("Login successful.")
_save_page_html(page, run_id)
search_value = os.environ.get("GEM_SEARCH_BID", "").strip()
if not search_value:
search_value = input("Bid number (Exact Bid Search): ").strip()
if search_value:
if search_flow.run(page, search_value=search_value):
_run_bid_participation_from_current_step(page)
_save_page_html(page, run_id)
else:
logger.info("Search skipped (no value provided).")
else:
logger.warning("Login failed.")
_save_page_html(page, run_id, suffix="fail")
except Exception as e:
logger.exception("Fatal error: %s", e)
if config.DEBUG_TRACE:
_save_artifacts_on_error(page, context, run_id, e)
trace_saved = True
if config.DEBUG:
logger.info("Artifacts: %s", config.ARTIFACTS_DIR)
_save_page_html(page, run_id, suffix="error")
raise
finally:
input("Press Enter to close the browser...")
if config.DEBUG and config.DEBUG_TRACE and not trace_saved:
try:
trace_path = os.path.join(config.TRACE_DIR, f"session_{run_id}.zip")
context.tracing.stop(path=trace_path)
logger.info("Trace: %s", trace_path)
except Exception: # TODO: proper error handling
pass
close_stealth_browser(pw, browser, context)
if __name__ == "__main__":
main()