fix(positions): sync latest scraper fixes from main repository
All checks were successful
Build and Push Docker Image / build (push) Successful in 36s
All checks were successful
Build and Push Docker Image / build (push) Successful in 36s
This commit is contained in:
@@ -1006,8 +1006,11 @@ async def login_to_schwab(username: str, password: str) -> Optional[List[Dict[st
|
||||
mfa_code = None
|
||||
|
||||
try:
|
||||
logger.info("Waiting 5 seconds for email code to arrive before checking webhook...")
|
||||
await asyncio.sleep(5)
|
||||
async with aiohttp.ClientSession() as session:
|
||||
for idx in range(60): # 2 minutes, every 2 seconds
|
||||
for attempt in range(2):
|
||||
print(f"Checking webhook for code (attempt {attempt + 1}/2)...")
|
||||
try:
|
||||
async with session.get("https://n8n.ext.ben.io/webhook/schwab-token") as resp:
|
||||
if resp.status == 200:
|
||||
@@ -1016,34 +1019,47 @@ async def login_to_schwab(username: str, password: str) -> Optional[List[Dict[st
|
||||
# Parse based on expected n8n output formats
|
||||
code = None
|
||||
if isinstance(data, dict):
|
||||
code = data.get("code") or data.get("token") or data.get("body", {}).get("code")
|
||||
code = data.get("code") or data.get("token") or data.get("login_code") or data.get("body", {}).get("code")
|
||||
elif isinstance(data, list) and len(data) > 0:
|
||||
code = data[-1].get("code") or data[-1].get("token")
|
||||
code = data[-1].get("code") or data[-1].get("token") or data[-1].get("login_code")
|
||||
if code:
|
||||
mfa_code = code
|
||||
logger.info(f"Got MFA code from webhook: {mfa_code}")
|
||||
break
|
||||
else:
|
||||
logger.warning("Webhook returned data but no code found inside.")
|
||||
else:
|
||||
logger.warning(f"Webhook returned status code {resp.status}")
|
||||
except Exception as e:
|
||||
logger.debug(f"Webhook poll error: {e}")
|
||||
|
||||
if idx % 10 == 0:
|
||||
print(f"Still waiting for webhook code... ({idx*2}s/120s)")
|
||||
await asyncio.sleep(2)
|
||||
|
||||
if not mfa_code and attempt == 0:
|
||||
logger.info("Token not found, waiting 10 seconds before 1 retry...")
|
||||
await asyncio.sleep(10)
|
||||
except Exception as loop_e:
|
||||
logger.error(f"Error during webhook polling loop: {loop_e}")
|
||||
logger.error(f"Error during webhook checking: {loop_e}")
|
||||
|
||||
if mfa_code:
|
||||
logger.info("Entering MFA code into form...")
|
||||
try:
|
||||
target = page
|
||||
iframe_element = await page.query_selector('#lmsIframe')
|
||||
if iframe_element:
|
||||
target = await iframe_element.content_frame() or page
|
||||
# When on the sws-gateway-nr OTP page, the form is rendered
|
||||
# directly on the page — there is no #lmsIframe wrapper here.
|
||||
# Only look for the iframe when on the client.schwab.com login page.
|
||||
current_page_url = page.url
|
||||
if 'sws-gateway-nr' in current_page_url or 'otp' in current_page_url:
|
||||
logger.debug(f"OTP page detected ({current_page_url}), querying form directly on page")
|
||||
target = page
|
||||
else:
|
||||
target = page
|
||||
iframe_element = await page.query_selector('#lmsIframe')
|
||||
if iframe_element:
|
||||
target = await iframe_element.content_frame() or page
|
||||
|
||||
# Commonly used ids and attributes for OTP inputs on Schwab
|
||||
code_input = await target.query_selector('input[type="text"], input[type="tel"], input[name*="code" i], input[id*="code" i], input[autocomplete*="one-time-code" i]')
|
||||
if code_input:
|
||||
await code_input.fill(str(mfa_code))
|
||||
logger.info(f"Filled OTP field with code: {mfa_code}")
|
||||
|
||||
# Sometimes the submit button specifically says 'Trust device' or similar
|
||||
submit_btn = await target.query_selector('button[type="submit"], button:has-text("Continue"), button:has-text("Verify"), button:has-text("Submit"), button:has-text("Log in"), button[id*="submit"], button[id*="continue"]')
|
||||
@@ -1052,7 +1068,10 @@ async def login_to_schwab(username: str, password: str) -> Optional[List[Dict[st
|
||||
print("Submitted MFA code successfully.")
|
||||
await page.wait_for_timeout(5000)
|
||||
else:
|
||||
logger.warning("Submit button not found after filling OTP — waiting anyway")
|
||||
await page.wait_for_timeout(5000)
|
||||
else:
|
||||
logger.error("OTP input field not found on page")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to enter MFA code: {e}")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user