diff --git a/btgo_scraper.py b/btgo_scraper.py index e389dbf..324e8a8 100644 --- a/btgo_scraper.py +++ b/btgo_scraper.py @@ -331,6 +331,50 @@ class BTGoScraper: logging.error(" [SUBMIT] Nu am gasit butonul cu nicio strategie!") return None + # Texte care apar in mesajele BT cand autentificarea e respinsa + LOGIN_ERROR_PATTERNS = ( + "invalid", "incorect", "gresit", "greșit", "eronat", + "blocat", "suspendat", "date de autentificare", + ) + + def _debug_shot(self, page, tag): + """Salveaza un screenshot de diagnostic; nu arunca daca esueaza.""" + try: + path = Path(self.config.OUTPUT_DIR) / f"debug_{tag}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png" + page.screenshot(path=str(path)) + logging.info(f"Screenshot diagnostic salvat: {path}") + return path + except Exception as e: + logging.warning(f"Nu am putut salva screenshot ({tag}): {e}") + return None + + def _detect_login_error(self, page): + """ + Returneaza textul erorii afisate de pagina de login (parola gresita, + cont blocat etc.) sau None. Fara asta, credentialele respinse arata + identic cu un 2FA neaprobat: asteptam degeaba pana la timeout. + """ + try: + candidates = page.locator( + "mat-error, .mat-error, [role='alert'], .alert, " + ".error, .error-message, .invalid-feedback" + ) + count = min(candidates.count(), 10) + except Exception: + return None + + for i in range(count): + try: + el = candidates.nth(i) + if not el.is_visible(timeout=500): + continue + text = (el.inner_text() or "").strip() + except Exception: + continue + if text and any(pat in text.lower() for pat in self.LOGIN_ERROR_PATTERNS): + return " ".join(text.split()) + return None + def _try_field_strategy(self, page, strategy_type, value): """Helper pentru a incerca o strategie de gasire a unui camp""" if strategy_type == "placeholder_exact": @@ -622,6 +666,7 @@ class BTGoScraper: start_time = datetime.now() timeout = self.config.TIMEOUT_2FA_SECONDS + iteration = 0 while (datetime.now() - start_time).seconds < timeout: # Verifică dacă butonul de conturi este CLICKABLE (nu doar vizibil) @@ -644,6 +689,19 @@ class BTGoScraper: except: pass + # Daca BT a respins credentialele, push-ul 2FA nu pleaca niciodata - + # iesim imediat cu motivul real, nu dupa timeout. + iteration += 1 + if iteration % 3 == 0: + login_error = self._detect_login_error(self.login_page) + if login_error: + print() + self._debug_shot(self.login_page, 'login_respins') + raise Exception( + f"Autentificare respinsa de BT: {login_error} " + "(2FA nu a fost trimis - verifica user/parola din .env)" + ) + # Afiseaza countdown elapsed = (datetime.now() - start_time).seconds remaining = timeout - elapsed @@ -652,6 +710,8 @@ class BTGoScraper: time.sleep(2) print() # New line dupa countdown + # Screenshot cat browser-ul e inca viu (dupa raise, event loop-ul e inchis) + self._debug_shot(self.login_page, 'timeout_2fa') raise TimeoutError(f"Timeout 2FA dupa {timeout} secunde. Verifica ca ai aprobat pe telefon!") def download_transactions(self, accounts): diff --git a/config.py b/config.py index 3f5b7ee..028785e 100644 --- a/config.py +++ b/config.py @@ -2,19 +2,29 @@ Configurare pentru BTGO Scraper Citeste configuratii din variabile de mediu (.env) """ -from dotenv import load_dotenv +from dotenv import load_dotenv, dotenv_values import os # Incarca variabilele de mediu din fisierul .env load_dotenv() +# Credentialele se citesc DIRECT din .env, nu din environment. +# Motiv: scraper-ul e pornit ca subproces de telegram_trigger_bot.py, care ii da +# o copie a propriului environment. Bot-ul si-a incarcat .env-ul la pornire, deci +# environment-ul mostenit contine parola veche, iar load_dotenv() (fara override) +# nu suprascrie o variabila deja existenta -> se trimitea parola veche la BT si +# nu mai pleca push-ul 2FA. Nu folosim load_dotenv(override=True) pentru ca +# bot-ul transmite intentionat prin environment variabile de rulare +# (TELEGRAM_MESSAGE_ID, TELEGRAM_CHAT_ID, SEND_AS_ZIP, BALANCES_ONLY). +_ENV_FILE = dotenv_values() + class Config: """Clasa pentru gestionarea configuratiilor aplicatiei""" # Credentials - BTGO_USERNAME = os.getenv('BTGO_USERNAME') - BTGO_PASSWORD = os.getenv('BTGO_PASSWORD') + BTGO_USERNAME = _ENV_FILE.get('BTGO_USERNAME') or os.getenv('BTGO_USERNAME') + BTGO_PASSWORD = _ENV_FILE.get('BTGO_PASSWORD') or os.getenv('BTGO_PASSWORD') # Browser settings HEADLESS = os.getenv('HEADLESS', 'false').lower() == 'true'