fix(login): citeste credentialele din .env, nu din env-ul mostenit
Scraper-ul e pornit ca subproces de telegram_trigger_bot.py cu env=os.environ.copy(). Bot-ul isi incarca .env la pornire, deci copia contine parola veche; load_dotenv() din config.py nu suprascrie o variabila deja existenta, asa ca dupa schimbarea parolei scraper-ul trimitea in continuare parola veche - BT respingea login-ul, push-ul 2FA nu mai pleca si ramaneam blocati 300s in "Timeout 2FA". - config.py: BTGO_USERNAME/BTGO_PASSWORD se citesc direct din .env (dotenv_values), restul variabilelor raman din environment ca sa nu strice TELEGRAM_MESSAGE_ID / SEND_AS_ZIP transmise de bot la fiecare run. - btgo_scraper.py: detectie a mesajului de eroare de pe pagina de login in bucla de asteptare 2FA -> esec rapid cu motivul real, plus screenshot de diagnostic (si la respingere, si la timeout, cat browser-ul e viu). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YbuWZ699EFeYfPA3nNraa9
This commit is contained in:
@@ -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):
|
||||
|
||||
16
config.py
16
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'
|
||||
|
||||
Reference in New Issue
Block a user