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!")
|
logging.error(" [SUBMIT] Nu am gasit butonul cu nicio strategie!")
|
||||||
return None
|
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):
|
def _try_field_strategy(self, page, strategy_type, value):
|
||||||
"""Helper pentru a incerca o strategie de gasire a unui camp"""
|
"""Helper pentru a incerca o strategie de gasire a unui camp"""
|
||||||
if strategy_type == "placeholder_exact":
|
if strategy_type == "placeholder_exact":
|
||||||
@@ -622,6 +666,7 @@ class BTGoScraper:
|
|||||||
|
|
||||||
start_time = datetime.now()
|
start_time = datetime.now()
|
||||||
timeout = self.config.TIMEOUT_2FA_SECONDS
|
timeout = self.config.TIMEOUT_2FA_SECONDS
|
||||||
|
iteration = 0
|
||||||
|
|
||||||
while (datetime.now() - start_time).seconds < timeout:
|
while (datetime.now() - start_time).seconds < timeout:
|
||||||
# Verifică dacă butonul de conturi este CLICKABLE (nu doar vizibil)
|
# Verifică dacă butonul de conturi este CLICKABLE (nu doar vizibil)
|
||||||
@@ -644,6 +689,19 @@ class BTGoScraper:
|
|||||||
except:
|
except:
|
||||||
pass
|
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
|
# Afiseaza countdown
|
||||||
elapsed = (datetime.now() - start_time).seconds
|
elapsed = (datetime.now() - start_time).seconds
|
||||||
remaining = timeout - elapsed
|
remaining = timeout - elapsed
|
||||||
@@ -652,6 +710,8 @@ class BTGoScraper:
|
|||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
|
|
||||||
print() # New line dupa countdown
|
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!")
|
raise TimeoutError(f"Timeout 2FA dupa {timeout} secunde. Verifica ca ai aprobat pe telefon!")
|
||||||
|
|
||||||
def download_transactions(self, accounts):
|
def download_transactions(self, accounts):
|
||||||
|
|||||||
16
config.py
16
config.py
@@ -2,19 +2,29 @@
|
|||||||
Configurare pentru BTGO Scraper
|
Configurare pentru BTGO Scraper
|
||||||
Citeste configuratii din variabile de mediu (.env)
|
Citeste configuratii din variabile de mediu (.env)
|
||||||
"""
|
"""
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv, dotenv_values
|
||||||
import os
|
import os
|
||||||
|
|
||||||
# Incarca variabilele de mediu din fisierul .env
|
# Incarca variabilele de mediu din fisierul .env
|
||||||
load_dotenv()
|
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:
|
class Config:
|
||||||
"""Clasa pentru gestionarea configuratiilor aplicatiei"""
|
"""Clasa pentru gestionarea configuratiilor aplicatiei"""
|
||||||
|
|
||||||
# Credentials
|
# Credentials
|
||||||
BTGO_USERNAME = os.getenv('BTGO_USERNAME')
|
BTGO_USERNAME = _ENV_FILE.get('BTGO_USERNAME') or os.getenv('BTGO_USERNAME')
|
||||||
BTGO_PASSWORD = os.getenv('BTGO_PASSWORD')
|
BTGO_PASSWORD = _ENV_FILE.get('BTGO_PASSWORD') or os.getenv('BTGO_PASSWORD')
|
||||||
|
|
||||||
# Browser settings
|
# Browser settings
|
||||||
HEADLESS = os.getenv('HEADLESS', 'false').lower() == 'true'
|
HEADLESS = os.getenv('HEADLESS', 'false').lower() == 'true'
|
||||||
|
|||||||
Reference in New Issue
Block a user