initializare

This commit is contained in:
2025-11-06 20:55:35 +02:00
commit 9956e9c11e
32 changed files with 5500 additions and 0 deletions

32
.env.example Normal file
View File

@@ -0,0 +1,32 @@
# Credentials (NU comite valorile reale în git!)
BTGO_USERNAME=
BTGO_PASSWORD=
# Configurare
HEADLESS=false
TIMEOUT_2FA_SECONDS=120
SCREENSHOT_ON_ERROR=true
DOWNLOAD_TRANSACTIONS=true
OUTPUT_DIR=./data
LOG_LEVEL=INFO
# Notificări (opțional)
ENABLE_NOTIFICATIONS=false
# Email (SMTP)
EMAIL_ENABLED=false
SMTP_SERVER=smtp.gmail.com
SMTP_PORT=587
SMTP_USERNAME=your-email@gmail.com
SMTP_PASSWORD=your-app-password
EMAIL_FROM=your-email@gmail.com
EMAIL_TO=mmarius28@gmail.com
# Telegram
TELEGRAM_ENABLED=false
TELEGRAM_BOT_TOKEN=123456789:ABCdefGHIjklMNOpqrsTUVwxyz
TELEGRAM_CHAT_ID=your-chat-id
# Telegram Trigger Bot (pentru declanșare remote prin Telegram)
TELEGRAM_ALLOWED_USER_IDS=123456789,987654321 # User IDs autorizați (goală = toți)
TELEGRAM_POLL_TIMEOUT=60 # Long polling timeout în secunde (30-90 recomandat)

25
.gitignore vendored Normal file
View File

@@ -0,0 +1,25 @@
# Python
__pycache__/
*.py[cod]
*$py.class
.venv/
venv/
ENV/
# Playwright
.pytest_cache/
test-results/
# Environment & Secrets
.env
auth.json
# Data & Logs
data/*.csv
data/*.json
data/*.png
logs/*.log
# OS
.DS_Store
Thumbs.db

113
CLAUDE.md Normal file
View File

@@ -0,0 +1,113 @@
# CLAUDE.md
This file provides guidance to Claude Code when working with code in this repository.
## Project Overview
BTGO Scraper - Playwright automation for extracting account balances and transaction CSVs from Banca Transilvania George (btgo.ro).
**Security Context**: Authorized personal banking automation tool for educational purposes.
**⚠️ CRITICAL**: Docker/headless mode is **BLOCKED by WAF**. ONLY works locally with `HEADLESS=false`.
## Running the Scraper
```powershell
# Windows setup
deployment\windows\scripts\setup_dev.ps1
# Run scraper (browser visible)
deployment\windows\scripts\run_scraper.ps1
# Run Telegram bot (manual)
deployment\windows\scripts\run_telegram_bot_manual.ps1
# Interactive menu (all options)
deployment\windows\scripts\menu.ps1
```
## Architecture
### Core Flow
```
login() → handle_2fa_wait() → read_accounts() → download_transactions() → save_results()
```
### Critical Implementation Details
#### 1. Login with Popup (lines ~140-200)
- Main page → Click "LOGIN" → **Opens popup window**
- Use `page.expect_popup()` to capture popup reference
- All operations happen in `self.login_page` (the popup)
#### 2. 2FA Auto-Detection (lines ~202-240)
- URL monitoring: `login.bancatransilvania.ro``goapp.bancatransilvania.ro`
- Poll for `#accountsBtn` element every 2 seconds
- After 2FA: `self.page = self.login_page` (switch page reference)
#### 3. Account Reading (lines ~242-310)
- Elements: `fba-account-details-card`, `#accountsBtn`
- Each card: `h4` (name), `span.text-grayscale-label` (IBAN), `strong.sold` (balance)
- Balance format: `"7,223.26 RON"` → parse to float + currency
#### 4. Transaction Download Modal (lines ~312-420)
**State Machine:**
```
Account 1: Expand card → Click "Tranzacții" → Download → Back → Modal
Account 2+: Select from modal → [ALREADY on page] → Download → Back
```
**Critical**: After modal selection, you're ALREADY on transactions page. Don't expand/click again.
**Modal selectors:**
- Modal: `.modal-content`
- Account buttons: `#accountC14RONCRT{last_10_iban_digits}`
- Example: IBAN `...0637236701``#accountC14RONCRT0637236701`
### Key Selectors
- Login: `get_by_placeholder("ID de logare")`, `get_by_placeholder("Parola")`
- Post-login: `#accountsBtn`, `goapp.bancatransilvania.ro` domain
- Accounts: `fba-account-details-card`, `.collapse-account-btn`, `.account-transactions-btn`
- Modal: `.modal-content`, `#accountC14RONCRT{iban_digits}`
- CSV: `get_by_role("button", name="CSV")`
**Update selectors:** `playwright codegen https://btgo.ro --target python`
## Docker Limitation
**Docker DOES NOT WORK** - Akamai WAF blocks with "Access Denied".
**Symptoms:**
- "Access Denied" page
- 0 links detected
- Screenshot: `data/debug_before_login_*.png` shows error
**Solution:** Use Windows local mode with `HEADLESS=false`.
## Common Issues
### Access Denied (Docker)
- **Cause**: WAF blocking
- **Fix**: Run locally with `HEADLESS=false`
### Transaction Download Timeout
- Modal not detected: Check `.modal-content` selector
- Account ID mismatch: Verify IBAN mapping `#accountC14RONCRT{last_10_digits}`
- For idx > 1: Already on page, don't expand/click
### 2FA Timeout
- Increase `TIMEOUT_2FA_SECONDS` in `.env`
- Verify URL redirect to `goapp.bancatransilvania.ro`
### "Nu exista card la pozitia X"
- Trying to access cards in modal context
- First account needs expand, subsequent accounts don't
## Exit Codes
- `0`: Success
- `1`: General error
- `4`: Config error (.env invalid)
- `99`: Unexpected error

27
Dockerfile Normal file
View File

@@ -0,0 +1,27 @@
# Folosim imaginea oficiala Microsoft Playwright cu Python
FROM mcr.microsoft.com/playwright/python:v1.48.0-noble
# Setare working directory
WORKDIR /app
# Copiaza fisierul de requirements
COPY requirements.txt .
# Instaleaza dependente Python
RUN pip install --no-cache-dir -r requirements.txt
# Copiaza codul aplicatiei
COPY btgo_scraper.py config.py ./
# Creaza directoare pentru output si logs
RUN mkdir -p /app/data /app/logs
# Setare variabile de mediu pentru mod headless
ENV HEADLESS=true
ENV PYTHONUNBUFFERED=1
# Setare timezone (optional)
ENV TZ=Europe/Bucharest
# Ruleaza script-ul principal
CMD ["python", "btgo_scraper.py"]

338
README.md Normal file
View File

@@ -0,0 +1,338 @@
# BTGO Scraper - Automatizare Citire Solduri
## Prezentare
Automatizare Playwright pentru extragerea soldurilor și tranzacțiilor din George (btgo.ro).
## Arhitectură
### BTGO Scraper (btgo_scraper.py)
```
Entry point: btgo_scraper.py
├─ config.py (Config.validate())
│ └─ .env (load credentials)
├─ sync_playwright() → Chromium browser
├─ BTGOScraper.run()
│ ├─ login()
│ │ ├─ page.goto('go.bancatransilvania.ro')
│ │ ├─ page.expect_popup() → self.login_page
│ │ └─ Fill credentials + submit
│ │
│ ├─ handle_2fa_wait()
│ │ └─ Poll URL + #accountsBtn (120s)
│ │
│ ├─ read_accounts()
│ │ ├─ Click #accountsBtn
│ │ ├─ Iterate fba-account-details-card
│ │ └─ Extract: h4, IBAN, strong.sold
│ │
│ ├─ download_transactions() [optional]
│ │ ├─ For each account:
│ │ │ ├─ Expand card (idx=1 only)
│ │ │ ├─ Click "Tranzacții" (idx=1) / Select from modal (idx>1)
│ │ │ ├─ Click CSV button
│ │ │ └─ page.expect_download()
│ │ └─ Save to data/tranzactii_*.csv
│ │
│ ├─ save_results()
│ │ ├─ Write data/solduri_*.csv
│ │ └─ Write data/solduri_*.json
│ │
│ └─ send_notifications() [optional]
│ └─ notifications.py
│ ├─ EmailNotifier.send()
│ │ └─ SMTP (smtplib)
│ └─ TelegramNotifier.send()
│ └─ requests.post(telegram API)
Standalone scripts:
├─ send_notifications.py (manual notification send)
├─ get_telegram_chat_id.py (telegram setup helper)
└─ test_telegram.py (telegram connectivity test)
```
### Telegram Trigger Bot (telegram_trigger_bot.py)
```
Entry point: telegram_trigger_bot.py
├─ .env (TELEGRAM_BOT_TOKEN, TELEGRAM_ALLOWED_USER_IDS)
├─ TelegramTriggerBot.__init__()
│ ├─ Load config from .env
│ ├─ Parse allowed user IDs
│ └─ Initialize Telegram API (api.telegram.org)
├─ run()
│ ├─ send_message(startup notification)
│ └─ While True:
│ ├─ get_updates(long_polling timeout=60s)
│ ├─ Parse incoming messages
│ └─ handle_command()
│ │
│ ├─ /scrape command:
│ │ ├─ is_user_allowed(user_id)
│ │ ├─ send_message("🤖 Scraper pornit...")
│ │ ├─ subprocess.run(['python', 'btgo_scraper.py'])
│ │ │ └─ btgo_scraper.py (full execution)
│ │ └─ send_message("✅ Succes" / "❌ Eroare")
│ │
│ ├─ /status command:
│ │ └─ send_message(bot uptime info)
│ │
│ └─ /help command:
│ └─ send_message(available commands)
Windows Service:
└─ deployment/windows/nssm/
├─ BTGOTelegramBot (Windows Service)
│ ├─ nssm install BTGOTelegramBot
│ ├─ Auto-restart on failure
│ └─ Log: logs/telegram_bot_*.log
Deploy scripts:
├─ deploy.ps1 (full deployment)
├─ install_service.ps1
├─ start_service.ps1
├─ restart_service.ps1
├─ uninstall_service.ps1
└─ menu.ps1 (interactive management)
```
**Puncte cheie depanare:**
- **Scraper login fails**: Check selectors în btgo_scraper.py:159-200
- **Scraper 2FA timeout**: Check URL polling în btgo_scraper.py:202-240
- **Scraper account reading fails**: Check selectors în btgo_scraper.py:242-310
- **Scraper transaction download timeout**: Check modal logic în btgo_scraper.py:312-420
- **Bot commands not working**: Check TELEGRAM_ALLOWED_USER_IDS în .env
- **Bot unauthorized**: User ID not in allowed list
- **Notifications fail**: Check notifications.py + config .env
**Funcționalități:**
- ✅ Citire automată solduri pentru toate conturile
- ✅ Export CSV cu tranzacții pentru fiecare cont
- ✅ Autentificare 2FA automată (auto-detect)
- ✅ Notificări Email și Telegram cu fișierele generate
**⚠️ LIMITARE: Docker/headless NU funcționează** - WAF-ul site-ului blochează activ containere Docker.
## Prerequisites
- Python 3.8+
- Cont BTGO activ cu 2FA
- Windows (Docker nu este suportat)
## Setup și Rulare
### Setup Automat (RECOMANDAT)
```powershell
# PowerShell (recomandat)
deployment\windows\scripts\setup_dev.ps1
# SAU meniu interactiv complet
deployment\windows\scripts\menu.ps1
```
### Rulare Manuală
```powershell
# Scraper direct
deployment\windows\scripts\run_scraper.ps1
# Telegram bot (manual mode)
deployment\windows\scripts\run_telegram_bot_manual.ps1
# SAU meniu interactiv
deployment\windows\scripts\menu.ps1
```
**Ce se întâmplă:**
1. Browser Chrome se deschide vizibil
2. Login automat
3. Așteaptă aprobare 2FA pe telefon (120s timeout)
4. Citește soldurile tuturor conturilor
5. Descarcă CSV-uri cu tranzacții
6. Salvează rezultate în `data/`
### Configurare `.env`
```bash
# Obligatorii
BTGO_USERNAME=your_username
BTGO_PASSWORD=your_password
HEADLESS=false # OBLIGATORIU false!
# Opționale
DOWNLOAD_TRANSACTIONS=true
TIMEOUT_2FA_SECONDS=120
```
## Structură Output
```
data/
├── solduri_2025-11-06_14-30-45.csv # Rezumat solduri
├── solduri_2025-11-06_14-30-45.json # Metadata + solduri
├── tranzactii_Colector_01_2025-11-06.csv # Tranzacții per cont
└── dashboard_2025-11-06_14-30-45.png # Screenshot
logs/
└── scraper_2025-11-06.log # Log zilnic
```
### Format CSV Solduri
```csv
timestamp,nume_cont,iban,sold,moneda,tip_cont
2025-11-06 14:30:45,Colector 01,RO32BTRLRONCRT0637236701,7223.26,RON,colector
```
## Notificări (Email & Telegram)
### Email (SMTP)
```bash
# .env
ENABLE_NOTIFICATIONS=true
EMAIL_ENABLED=true
SMTP_SERVER=smtp.gmail.com
SMTP_PORT=587
SMTP_USERNAME=your-email@gmail.com
SMTP_PASSWORD=your-app-password # Pentru Gmail: https://myaccount.google.com/apppasswords
EMAIL_FROM=your-email@gmail.com
EMAIL_TO=recipient@gmail.com
```
### Telegram
1. **Crează Bot:**
- Caută @BotFather în Telegram
- Trimite `/newbot`
- Copiază token-ul primit
2. **Obține Chat ID:**
```bash
python get_telegram_chat_id.py
```
3. **Configurare .env:**
```bash
ENABLE_NOTIFICATIONS=true
TELEGRAM_ENABLED=true
TELEGRAM_BOT_TOKEN=123456789:ABCdefGHIjklMNOpqrs
TELEGRAM_CHAT_ID=-1001234567890 # Negativ pentru grupuri
```
### Trimitere Manuală Notificări
```bash
python send_notifications.py
```
## Telegram Trigger Bot - Declanșare Remote
### Setup Rapid (Manual Mode)
1. **Configurare .env:**
```bash
TELEGRAM_BOT_TOKEN=123456789:ABCdefGHIjklMNOpqrs
TELEGRAM_ALLOWED_USER_IDS=your_user_id
TELEGRAM_CHAT_ID=-1001234567890 # Pentru grup (negativ!)
```
2. **Pornire bot:**
```powershell
deployment\windows\scripts\run_telegram_bot_manual.ps1
```
3. **Comenzi disponibile:**
- `/scrape` - Execută BTGO scraper
- `/status` - Status bot
- `/help` - Ajutor
### Deploy ca Windows Service (RECOMANDAT pentru VM)
```powershell
# Deploy complet + instalare serviciu
deployment\windows\scripts\deploy.ps1
# SAU folosește meniul interactiv
deployment\windows\scripts\menu.ps1
```
**Avantaje serviciu:**
- ✅ Pornire automată cu Windows
- ✅ Auto-restart la crash
- ✅ Rulează în background (fără RDP activ)
- ✅ Logging centralizat
**Notă:** Fișierele sunt trimise de **scraper** prin notificări (dacă `ENABLE_NOTIFICATIONS=true`), NU de bot.
**⚠️ Cerință pentru scraper:** Desktop Windows activ (RDP conectată) - browser TREBUIE vizibil!
**Ghid complet:** `deployment/windows/README.md` și `TELEGRAM_BOT_SETUP.md`
## Troubleshooting
### "Access Denied" în Docker
**Cauză:** WAF-ul BT George blochează Docker/headless browsers.
**Soluție:** Rulează DOAR local în Windows cu `HEADLESS=false`.
### Timeout la 2FA
- Mărește timeout: `TIMEOUT_2FA_SECONDS=180` în `.env`
- Verifică notificări activate pe telefon
### Selectors nu funcționează
Site-ul s-a schimbat. Re-generează selectors:
```bash
.venv\Scripts\activate
playwright codegen https://btgo.ro --target python
```
### Notificări Email nu funcționează
- Pentru Gmail: folosește App Password, nu parola normală
- Verifică port: 587 (TLS) sau 465 (SSL)
- Testare: `python test_telegram.py`
### Notificări Telegram nu funcționează
- Verifică `TELEGRAM_ENABLED=true`
- Rulează `python test_telegram.py`
- Chat ID pentru grupuri trebuie să fie **negativ** (ex: `-1001234567890`)
- Asigură-te că botul este în grup
## Securitate
**⚠️ IMPORTANT:**
- NU comite `.env` în git (deja în `.gitignore`)
- NU partaja screenshots/logs - conțin date sensibile
- Șterge fișierele vechi periodic:
```bash
# Windows
del /q data\*.csv data\*.json data\*.png
# Linux/Mac
rm data/*.{csv,json,png}
```
## Exit Codes
- `0` - Success
- `1` - Eroare generală
- `4` - Eroare configurare (.env invalid)
- `99` - Eroare neașteptată
## Licență
MIT License
## Disclaimer Legal
Acest tool accesează informații personale bancare. Utilizatorul este singurul responsabil pentru conformarea cu termenii Băncii Transilvania și legislația aplicabilă.

380
TELEGRAM_BOT_SETUP.md Normal file
View File

@@ -0,0 +1,380 @@
# Telegram Trigger Bot - Setup Guide
## Prezentare
Telegram Trigger Bot permite declanșarea BTGO Scraper-ului de la distanță prin comenzi Telegram. Perfect pentru VM Windows 11.
**⚠️ IMPORTANT:** VM-ul Windows 11 TREBUIE să aibă:
- Remote Desktop session activă (sau user logat)
- Browser vizibil (HEADLESS=false)
- WAF-ul BT blochează browsere headless!
## Cum Funcționează
```
Tu (Telegram) → Bot → Rulează scraper → Trimite CSV-uri înapoi
```
## Setup Pas cu Pas
### 1. Configurare Bot Telegram
**Dacă ai deja bot pentru notificări:** Poți folosi același bot!
**Dacă NU ai bot:**
1. Deschide Telegram și caută **@BotFather**
2. Trimite `/newbot`
3. Alege nume pentru bot (ex: "BTGO Scraper Bot")
4. Copiază **token-ul** primit (ex: `123456789:ABCdefGHIjklMNOpqrs`)
### 2. Obține User ID-ul Tău
Ai nevoie de User ID pentru securitate (doar tu poți rula scraper-ul).
**Opțiunea A - Folosește bot existent:**
```bash
python get_telegram_chat_id.py
```
Vei vedea: `User ID: 123456789`
**Opțiunea B - Folosește @userinfobot:**
1. Caută `@userinfobot` în Telegram
2. Trimite `/start`
3. Copiază User ID-ul
### 3. Setup pentru Grup Telegram (RECOMANDAT)
**Avantaj:** Comenzi și rezultate în același grup - mai convenabil decât mesaje directe!
#### 3.1. Adaugă Bot-ul în Grup
1. **Deschide grupul Telegram** unde vrei să trimiți comenzi
2. **Adaugă bot-ul:**
- Click pe numele grupului → "Add Members"
- Caută bot-ul tău (ex: "BTGO Scraper Bot")
- Adaugă
3. **Configurare Privacy Mode (important!):**
Bot-ul trebuie să vadă comenzile din grup. Ai 2 opțiuni:
**Opțiunea A - Dezactivează Privacy Mode (RECOMANDAT):**
```
1. Deschide @BotFather în Telegram
2. Trimite /mybots
3. Selectează bot-ul tău
4. Bot Settings → Group Privacy → Turn OFF
```
Acum bot-ul vede toate mesajele care încep cu `/` (comenzi)
**Opțiunea B - Fă bot-ul Admin:**
```
1. În grup: Click nume grup → Administrators
2. Add Administrator → Selectează bot-ul
3. Permisiuni minime: nicio permisiune specială necesară
```
Admin bots văd toate mesajele automat.
#### 3.2. Obține Chat ID al Grupului
**Metoda 1 - Cu scriptul helper:**
```bash
python get_telegram_chat_id.py
```
- Trimite `/start` în grup
- Script-ul va afișa: `Chat ID: -1001234567890` (NEGATIV pentru grupuri!)
**Metoda 2 - Manual:**
1. Adaugă `@RawDataBot` în grupul tău temporar
2. Bot-ul va afișa informații despre grup
3. Copiază `chat.id` (va fi negativ, ex: `-1001234567890`)
4. Șterge `@RawDataBot` din grup
### 4. Configurare .env
Editează `.env` și adaugă:
```bash
# Bot token (același ca pentru notificări sau nou)
TELEGRAM_BOT_TOKEN=123456789:ABCdefGHIjklMNOpqrs
# User IDs autorizați (separați prin virgulă)
# DOAR acești useri pot rula /scrape din grup
TELEGRAM_ALLOWED_USER_IDS=123456789,987654321
# Chat ID GRUP pentru notificări automate + răspunsuri comenzi
# IMPORTANT: Negativ pentru grupuri! (ex: -1001234567890)
TELEGRAM_CHAT_ID=-1001234567890
```
**Securitate:**
- `TELEGRAM_ALLOWED_USER_IDS` = doar acești useri pot rula `/scrape` din grup
- Lasă gol dacă vrei ca oricine din grup să poată rula (nesigur!)
- Bot-ul verifică User ID-ul celui care trimite comanda, NU group ID-ul
### 4. Pornire Bot
**Opțiunea 1 - Manual (Test):**
```powershell
deployment\windows\scripts\run_telegram_bot_manual.ps1
```
**Opțiunea 2 - Windows Service (RECOMANDAT pentru VM):**
```powershell
# Deploy complet
deployment\windows\scripts\deploy.ps1
# SAU meniu interactiv
deployment\windows\scripts\menu.ps1
```
**Opțiunea 3 - Manual Python:**
```bash
.venv\Scripts\activate
python telegram_trigger_bot.py
```
**Bot-ul va afișa:**
```
Bot inițializat. Useri autorizați: [123456789]
Bot pornit. Așteaptă comenzi...
```
## Utilizare
### Comenzi Disponibile
**În grupul Telegram (sau DM către bot):**
```
/start - Pornește bot-ul și vezi comenzile
/scrape - ⭐ Rulează scraper-ul (2-3 minute)
/status - Vezi status sistem (ultima rulare, fișiere)
/help - Ajutor utilizare
```
**Securitate:** Doar userii din `TELEGRAM_ALLOWED_USER_IDS` pot rula comenzi!
### Flow Tipic în Grup
1. **Scrii în grup:** `/scrape`
2. **Bot răspunde:** "🤖 BTGO Scraper pornit... Așteaptă 2FA!"
3. **Pe VM:** Browser-ul se deschide, face login automat
4. **Pe telefon:** Aprobă notificarea George 2FA
5. **Scraper trimite fișiere:** Prin notificări automate (email + grup)
6. **Bot confirmă:** "✅ Finalizat! Fișierele au fost trimise automat."
**Notă:** Fișierele sunt trimise de **scraper** (`ENABLE_NOTIFICATIONS=true`), NU de bot. Bot-ul doar declanșează execuția și confirmă finalizarea.
### Exemplu Output în Grup
```
Tu: /scrape
Bot: 🤖 BTGO Scraper pornit...
Așteaptă 2FA pe telefon!
[După 2 minute - mesaje de la scraper prin notificări]
Bot: 📧 BTGO Scraper - Sold conturi actualizat
📄 solduri_2025-11-06.csv
📄 tranzactii_Colector_01.csv
📄 tranzactii_Antreprenor_04.csv
... (toate fișierele)
Bot: ✅ Scraper finalizat cu succes!
📧 Fișierele au fost trimise automat prin notificări.
```
**Important:** Mesajele cu fișiere vin din **scraper** (prin `notifications.py`), nu din bot! Bot-ul doar declanșează și confirmă.
## Setup VM Windows 11
### Cerințe Critice
**⚠️ Browser TREBUIE să fie vizibil!** WAF-ul BT blochează headless.
### Metoda Recomandată - Windows Service
```powershell
# Deploy ca serviciu Windows (auto-start, auto-restart)
deployment\windows\scripts\deploy.ps1
```
**Avantaje:**
- ✅ Pornire automată cu Windows
- ✅ Auto-restart la crash
- ✅ Management prin Services.msc
- ✅ Logging centralizat
- ✅ Nu necesită RDP conectată
**Management:**
```powershell
# Meniu interactiv
deployment\windows\scripts\menu.ps1
# SAU comenzi directe
deployment\windows\scripts\start_service.ps1
deployment\windows\scripts\stop_service.ps1
deployment\windows\scripts\restart_service.ps1
deployment\windows\scripts\status.ps1
```
### Alternativă - Task Scheduler
**Doar dacă nu vrei Windows Service:**
1. Deschide Task Scheduler
2. Create Task
3. **General:**
- Name: "BTGO Telegram Bot"
- Run only when user is logged on: ✅
4. **Triggers:**
- At log on (Specific user)
5. **Actions:**
- Action: Start a program
- Program: `C:\path\to\btgo-playwright\deployment\windows\scripts\run_telegram_bot_manual.ps1`
- Start in: `C:\path\to\btgo-playwright`
6. **Conditions:**
- Dezactivează "Start task only if computer is on AC power"
### Verificare Funcționare
```bash
# Pe VM, verifică că bot-ul rulează
tasklist | findstr python
# Ar trebui să vezi:
# python.exe 12345 Console 1 45,678 K
```
## Troubleshooting
### Bot nu răspunde la comenzi
**Cauză:** Token greșit sau bot nestartă.
**Soluție:**
```bash
# Verifică token-ul
python telegram_trigger_bot.py
# Ar trebui să vezi:
# Bot inițializat. Useri autorizați: [123456789]
```
### "⛔ Acces interzis!"
**Cauză:** User ID-ul tău nu e în `TELEGRAM_ALLOWED_USER_IDS`.
**Soluție:**
```bash
# Verifică User ID-ul tău
python get_telegram_chat_id.py
# Adaugă-l în .env
TELEGRAM_ALLOWED_USER_IDS=123456789
```
### "Access Denied" la scraping
**Cauză:** Browser-ul rulează în headless mode sau VM nu are GUI activă.
**Soluție:**
- Conectează-te prin RDP și LASĂ sesiunea deschisă
- Verifică `.env`: `HEADLESS=false`
- Asigură-te că desktop-ul Windows este vizibil
### Scraper timeout (> 10 minute)
**Cauză:** Probabil blocat la 2FA sau browser nu răspunde.
**Soluție:**
- Verifică logs pe VM: `logs\scraper_*.log`
- Mărește timeout în bot: `timeout=900` (15 min)
- Asigură-te că aprobați 2FA în < 2 minute
### Bot se oprește singur
**Cauză:** Sesiune RDP deconectată sau VM suspended.
**Soluție:**
- Folosește Task Scheduler pentru restart automat
- Configurează VM să nu intre în sleep/hibernate
- Verifică că desktop-ul rămâne activ
## Securitate
**⚠️ ATENȚIE:**
- Bot-ul are acces la credentials din `.env`
- `TELEGRAM_ALLOWED_USER_IDS` TREBUIE configurat!
- Nu partaja token-ul botului
- VM-ul trebuie securizat (firewall, VPN)
**Best Practices:**
```bash
# ✅ Bun - doar tu și admin
TELEGRAM_ALLOWED_USER_IDS=123456789,987654321
# ❌ Rău - oricine cu acces la bot
TELEGRAM_ALLOWED_USER_IDS=
# ✅ Bun - notificări separate de trigger
TELEGRAM_BOT_TOKEN=bot_trigger_token
TELEGRAM_CHAT_ID=group_chat_id
```
## Logs și Monitoring
**Logs bot:**
```
[2025-11-06 17:30:00] [INFO] Bot inițializat. Useri autorizați: [123456789]
[2025-11-06 17:30:05] [INFO] Mesaj de la username (ID: 123456789): /scrape
[2025-11-06 17:30:05] [INFO] Comandă /scrape primită
[2025-11-06 17:30:05] [INFO] Pornire scraper...
[2025-11-06 17:32:45] [INFO] Scraper finalizat cu succes
[2025-11-06 17:32:50] [INFO] Trimit: solduri_2025-11-06.csv
```
**Logs scraper:**
```
logs\scraper_2025-11-06.log
```
## Alternative Trigger Methods
### 1. HTTP API (simplă)
```python
from flask import Flask, request
app = Flask(__name__)
@app.route('/scrape', methods=['POST'])
def trigger_scrape():
# Verifică token secret
subprocess.run(['python', 'btgo_scraper.py'])
```
### 2. Cron Job (periodicitate fixă)
```
Task Scheduler → Daily la 9:00 AM
```
### 3. File Watcher
```python
# Detectează fișier "trigger.txt"
# Rulează scraper automat
```
## Performance
**Timpi tipici:**
- Bot startup: < 5 secunde
- Scraper execution: 2-3 minute
- File upload: 5-10 secunde
- **Total:** ~3-4 minute de la `/scrape` la fișiere primite
**Resource usage (VM):**
- RAM: 500MB (bot) + 800MB (browser)
- CPU: 5-10% idle, 30-40% during scrape
- Network: ~5MB download (CSV-uri)

639
btgo_scraper.py Normal file
View File

@@ -0,0 +1,639 @@
"""
BTGO Scraper - Automatizare citire solduri conturi bancare
Folosește Playwright pentru automatizare browser
"""
from playwright.sync_api import sync_playwright, TimeoutError as PlaywrightTimeout
import logging
import csv
import json
import time
import os
from datetime import datetime
from pathlib import Path
from config import Config
class BTGoScraper:
"""Scraper pentru extragerea soldurilor de pe btgo.ro"""
def __init__(self):
"""Initializare scraper cu logging si configurare"""
self.config = Config()
Config.validate()
self._setup_logging()
self._ensure_directories()
self.page = None
self.login_page = None # Pagina de login (popup)
# Setup pentru progress updates prin Telegram (optional)
self.telegram_chat_id = os.getenv('TELEGRAM_CHAT_ID')
self.telegram_message_id = os.getenv('TELEGRAM_MESSAGE_ID')
self.progress_notifier = None
logging.info(f"Environment: TELEGRAM_CHAT_ID={self.telegram_chat_id}, TELEGRAM_MESSAGE_ID={self.telegram_message_id}")
# Inițializează notifier pentru progress updates dacă avem chat_id
if self.telegram_chat_id and self.telegram_message_id:
try:
from notifications import TelegramNotifier
self.progress_notifier = TelegramNotifier(self.config)
logging.info(f"Progress updates activate pentru chat_id={self.telegram_chat_id}, message_id={self.telegram_message_id}")
except Exception as e:
logging.warning(f"Nu am putut inițializa progress notifier: {e}")
else:
logging.warning("Progress updates dezactivate - lipsesc TELEGRAM_CHAT_ID sau TELEGRAM_MESSAGE_ID")
def _setup_logging(self):
"""Configurare logging in consola si fisier zilnic"""
# Creaza director logs daca nu exista
Path(self.config.LOG_DIR).mkdir(exist_ok=True)
# Nume fisier cu data curenta
log_file = Path(self.config.LOG_DIR) / f"scraper_{datetime.now().strftime('%Y-%m-%d')}.log"
# Format log
log_format = '[%(asctime)s] [%(levelname)s] %(message)s'
date_format = '%Y-%m-%d %H:%M:%S'
# Configurare logging
logging.basicConfig(
level=getattr(logging, self.config.LOG_LEVEL),
format=log_format,
datefmt=date_format,
handlers=[
logging.StreamHandler(), # Console
logging.FileHandler(log_file, encoding='utf-8') # Fisier
]
)
logging.info(f"Logging initializat: {log_file}")
def _ensure_directories(self):
"""Creaza directoarele data/ si logs/ daca nu exista"""
Path(self.config.OUTPUT_DIR).mkdir(exist_ok=True)
Path(self.config.LOG_DIR).mkdir(exist_ok=True)
logging.info(f"Directoare verificate: {self.config.OUTPUT_DIR}, {self.config.LOG_DIR}")
def _update_progress(self, message: str):
"""
Trimite update de progres către Telegram (editează mesajul inițial)
Args:
message: Mesajul de progres
"""
if self.progress_notifier and self.telegram_chat_id and self.telegram_message_id:
full_message = f"*{message}*"
self.progress_notifier._edit_message(
self.telegram_chat_id,
self.telegram_message_id,
full_message
)
logging.info(f"Progress update: {message}")
def run(self):
"""Entry point principal - orchestreaza tot flow-ul"""
try:
logging.info("=" * 60)
logging.info("Start BTGO Scraper")
logging.info("=" * 60)
with sync_playwright() as p:
# Lansare browser
browser = p.chromium.launch(
headless=self.config.HEADLESS,
slow_mo=100 if not self.config.HEADLESS else 0 # Slow motion pentru debugging
)
# Creaza pagina cu viewport standard
self.page = browser.new_page(viewport={'width': 1920, 'height': 1080})
logging.info(f"Browser lansat (headless={self.config.HEADLESS})")
# Flow complet
self.login()
self.handle_2fa_wait()
self._update_progress("2FA aprobat! Incarc conturi...")
accounts = self.read_accounts()
csv_path, json_path = self.save_results(accounts)
# Descarcă tranzacții pentru toate conturile (optional)
downloaded_files = []
if self.config.DOWNLOAD_TRANSACTIONS:
downloaded_files = self.download_transactions(accounts)
else:
logging.info("Download tranzacții dezactivat (DOWNLOAD_TRANSACTIONS=false)")
# Screenshot final de confirmare
screenshot_path = Path(self.config.OUTPUT_DIR) / f"dashboard_{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.png"
self.page.screenshot(path=str(screenshot_path))
logging.info(f"Screenshot final salvat: {screenshot_path}")
# Trimite notificări (email, Discord)
if self.config.ENABLE_NOTIFICATIONS:
self.send_notifications(csv_path, downloaded_files, accounts)
# Afișează rezumat final
logging.info("=" * 60)
logging.info("REZUMAT FINAL")
logging.info("=" * 60)
logging.info(f"Conturi citite: {len(accounts)}")
if self.config.DOWNLOAD_TRANSACTIONS:
logging.info(f"Tranzacții descărcate: {len(downloaded_files)}")
for df in downloaded_files:
logging.info(f" - {df['cont']}: {df['fisier']}")
logging.info("=" * 60)
browser.close()
# Nu mai trimitem update progress aici - mesajul final cu soldurile a fost deja editat de notifications.py
logging.info("=" * 60)
logging.info("✓ Scraper finalizat cu succes!")
logging.info("=" * 60)
return True
except Exception as e:
self._handle_error(e)
return False
def login(self):
"""Autentificare cu username si password"""
self._update_progress("Deschid pagina de login...")
logging.info("Navigare catre https://go.bancatransilvania.ro/")
self.page.goto('https://go.bancatransilvania.ro/', wait_until='networkidle')
logging.info("Pagina incarcata")
try:
# Cookie consent - asteapta si accepta
logging.info("Acceptare cookies...")
try:
cookie_button = self.page.get_by_role("button", name="Sunt de acord", exact=True)
cookie_button.click(timeout=5000)
logging.info("✓ Cookies acceptate")
except:
logging.info("Nu a fost necesar acceptul cookies (posibil deja acceptat)")
# Click pe butonul LOGIN - deschide popup
logging.info("Click pe butonul LOGIN...")
with self.page.expect_popup() as popup_info:
login_link = self.page.get_by_role("link", name="LOGIN")
login_link.click()
# Preia referinta la popup-ul de login
self.login_page = popup_info.value
logging.info("✓ Popup login deschis")
# Completare username
logging.info("Completare username...")
username_field = self.login_page.get_by_placeholder("ID de logare")
username_field.fill(self.config.BTGO_USERNAME)
logging.info("✓ Username completat")
# Completare password
logging.info("Completare password...")
password_field = self.login_page.get_by_placeholder("Parola")
password_field.fill(self.config.BTGO_PASSWORD)
logging.info("✓ Password completat")
# Click pe butonul de submit
logging.info("Click pe 'Mergi mai departe'...")
submit_button = self.login_page.get_by_role("button", name="Mergi mai departe")
submit_button.click()
logging.info("✓ Credentials trimise, astept 2FA...")
self._update_progress("Astept aprobare 2FA pe telefon...")
except PlaywrightTimeout as e:
logging.error(f"Timeout la login: {e}")
raise Exception("Nu am gasit elementele de login. Verifica selectors!")
except Exception as e:
logging.error(f"Eroare la login: {e}")
raise
def handle_2fa_wait(self):
"""Asteapta aprobare 2FA cu auto-detect"""
logging.info("=" * 60)
logging.info("🔐 APROBARE 2FA NECESARA")
logging.info("=" * 60)
logging.info("Verifica aplicatia George pe telefon si aproba autentificarea.")
logging.info(f"Timeout: {self.config.TIMEOUT_2FA_SECONDS} secunde")
logging.info("=" * 60)
# Detectam login reusit prin schimbarea URL-ului la goapp.bancatransilvania.ro
# sau prin aparitia butonului de conturi (#accountsBtn)
post_login_selectors = [
"#accountsBtn", # Butonul pentru conturi (apare post-login)
]
start_time = datetime.now()
timeout = self.config.TIMEOUT_2FA_SECONDS
while (datetime.now() - start_time).seconds < timeout:
# Verifică dacă butonul de conturi este CLICKABLE (nu doar vizibil)
# Aceasta este singura verificare sigură - butonul apare doar după 2FA reușit
try:
# Așteaptă ca #accountsBtn să fie clickable (nu doar vizibil în DOM)
accounts_btn = self.login_page.locator("#accountsBtn")
if accounts_btn.is_visible(timeout=1000):
# Verifică că este și clickable (enabled)
if accounts_btn.is_enabled():
logging.info("✓ Autentificare 2FA reusita! (Buton conturi activ)")
time.sleep(2) # Asteapta ca pagina sa se stabilizeze complet
# Update page reference la login_page pentru restul operatiilor
self.page = self.login_page
return True
except:
pass
# Afiseaza countdown
elapsed = (datetime.now() - start_time).seconds
remaining = timeout - elapsed
print(f"\rAsteptam aprobare 2FA... {remaining}s ramas", end='', flush=True)
time.sleep(2)
print() # New line dupa countdown
raise TimeoutError(f"Timeout 2FA dupa {timeout} secunde. Verifica ca ai aprobat pe telefon!")
def download_transactions(self, accounts):
"""Descarca CSV-uri cu tranzactiile pentru fiecare cont"""
logging.info("=" * 60)
logging.info("Descarcare tranzactii pentru toate conturile...")
logging.info("=" * 60)
downloaded_files = []
# IMPORTANT: Collapse toate conturile mai intai
logging.info("Collapse toate conturile...")
all_expanded = self.page.locator(".mat-icon.rotate-90").all()
for expanded_icon in all_expanded:
try:
expanded_icon.click()
time.sleep(0.3)
except:
pass
time.sleep(1)
logging.info("✓ Toate conturile sunt collapse")
# Re-gaseste toate cardurile de conturi
all_cards = self.page.locator("fba-account-details-card").all()
logging.info(f"Gasit {len(all_cards)} carduri de conturi")
for idx, account in enumerate(accounts, 1):
try:
nume_cont = account['nume_cont']
iban = account['iban']
self._update_progress(f"Descarc tranzactii ({idx}/{len(accounts)})...")
logging.info(f"[{idx}/{len(accounts)}] Descarcare tranzactii pentru: {nume_cont}")
# Doar pentru PRIMUL cont trebuie expand + click Tranzacții
# Pentru restul, suntem deja pe pagina de tranzacții (din selectarea din modal)
if idx == 1:
# Primul cont - expand și click Tranzacții
if idx - 1 >= len(all_cards):
logging.error(f" ✗ Nu exista card la pozitia {idx-1}")
continue
card = all_cards[idx - 1]
# Expand contul (click pe săgeată)
expand_button = card.locator(".collapse-account-btn").first
expand_button.click()
time.sleep(2) # Așteaptă expandare
logging.info(f" Contul expandat")
# Click pe butonul Tranzacții
try:
transactions_button = card.locator(".account-transactions-btn").first
transactions_button.click()
time.sleep(3) # Așteaptă încărcarea paginii cu tranzacții
logging.info(f" Click pe buton Tranzactii - pagina se incarca...")
except Exception as e:
logging.error(f" ✗ Nu am gasit butonul Tranzactii: {e}")
try:
expand_button.click()
time.sleep(0.5)
except:
pass
continue
else:
# Conturile 2-5: suntem deja pe pagina de tranzacții (din modal)
logging.info(f" Deja pe pagina tranzactii (selectat din modal)")
time.sleep(2) # Așteaptă stabilizare pagină
# Așteaptă să apară butonul CSV (indica că pagina s-a încărcat)
try:
self.page.wait_for_selector('button:has-text("CSV")', timeout=5000)
logging.info(f" Buton CSV detectat")
except:
logging.warning(f" Timeout asteptand butonul CSV")
# Click pe butonul CSV și așteaptă download
try:
with self.page.expect_download(timeout=15000) as download_info:
csv_button = self.page.get_by_role("button", name="CSV")
csv_button.click()
logging.info(f" Click pe butonul CSV - astept download...")
download = download_info.value
# Salvează fișierul cu un nume descriptiv
timestamp = datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
nume_safe = nume_cont.replace(' ', '_').replace('/', '_')
filename = f"tranzactii_{nume_safe}_{timestamp}.csv"
save_path = Path(self.config.OUTPUT_DIR) / filename
download.save_as(save_path)
logging.info(f" ✓ Salvat: {save_path}")
downloaded_files.append({
'cont': nume_cont,
'iban': iban,
'fisier': str(save_path)
})
except Exception as e:
logging.error(f" ✗ Eroare la descarcarea CSV: {e}")
# Navighează înapoi la lista de conturi
try:
# Click pe butonul back/close (săgeată stânga sau X)
back_button = self.page.locator('button[aria-label="Back"], .back-button, #selectAccountBtn').first
back_button.click()
time.sleep(1.5)
logging.info(f" Navigat inapoi - verific modal...")
except Exception as e:
logging.warning(f" Nu am putut naviga inapoi: {e}")
time.sleep(1)
# Verifică dacă a apărut modal de selectare cont
try:
modal_visible = self.page.locator('.modal-content').is_visible(timeout=2000)
if modal_visible and idx < len(accounts):
logging.info(f" Modal detectat - selectez contul urmator...")
# Calculează ID-ul contului următor
next_account = accounts[idx] # idx este 0-indexed pentru next
next_iban = next_account['iban']
next_iban_digits = ''.join(filter(str.isdigit, next_iban))[-10:]
next_account_id = f"accountC14RONCRT{next_iban_digits}"
# Click pe contul următor din modal
modal_account = self.page.locator(f'#{next_account_id}').first
modal_account.click()
time.sleep(2)
logging.info(f" ✓ Selectat cont din modal: {next_account['nume_cont']}")
else:
# Nu e modal - e ultima iteratie sau nu a aparut modal
logging.info(f" Nu e modal - continuam normal")
except Exception as e:
logging.warning(f" Eroare verificare modal: {e}")
# Re-găsește cardurile (pentru flow normal fără modal)
try:
all_cards = self.page.locator("fba-account-details-card").all()
except:
pass
except Exception as e:
logging.error(f" ✗ Eroare la descarcarea tranzactiilor pentru {nume_cont}: {e}")
# Încearcă să navighezi înapoi
try:
self.page.keyboard.press("Escape")
time.sleep(1)
except:
pass
continue
logging.info("=" * 60)
logging.info(f"✓ Descarcate {len(downloaded_files)}/{len(accounts)} fisiere CSV cu tranzactii")
logging.info("=" * 60)
return downloaded_files
def read_accounts(self):
"""Extrage soldurile tuturor conturilor"""
logging.info("Citire conturi si solduri...")
try:
# Click pe butonul de conturi pentru a afisa lista
logging.info("Click pe butonul Conturi...")
# Simplu selector - doar butonul, nu div-ul interior
accounts_button = self.page.locator("#accountsBtn")
accounts_button.click()
time.sleep(3) # Asteapta ca lista sa se incarce
logging.info("✓ Sectiunea conturi deschisa")
# Update progres DUPĂ ce lista de conturi s-a încărcat
self._update_progress("Citesc solduri conturi...")
# Gaseste toate cardurile de conturi
account_cards = self.page.locator("fba-account-details-card").all()
logging.info(f"Gasit {len(account_cards)} conturi")
accounts = []
for idx, card in enumerate(account_cards, 1):
try:
# Extrage nume cont (din tag <h4>)
nume = card.locator("h4.fw-normal.mb-0").inner_text()
# Extrage IBAN (din span cu clasa specific)
iban = card.locator("span.small.text-grayscale-label").inner_text()
# Extrage sold (din <strong> cu clasa 'sold')
sold_text = card.locator("strong.sold").inner_text()
# Parsing sold: "7,223.26 RON" -> sold_numeric=7223.26, moneda="RON"
# Elimina spatii, inlocuieste virgula cu punct
sold_parts = sold_text.strip().split()
if len(sold_parts) >= 2:
sold_str = sold_parts[0] # "7,223.26"
moneda = sold_parts[1] # "RON"
else:
sold_str = sold_text
moneda = "RON" # Default
# Converteste la float (elimina virgule care sunt separator de mii)
# Format: "7,223.26" -> 7223.26
sold_numeric = float(sold_str.replace(',', ''))
# Determina tip cont din nume (optional)
nume_lower = nume.lower()
if 'colector' in nume_lower:
tip_cont = 'colector'
elif 'profit' in nume_lower:
tip_cont = 'profit'
elif 'operationale' in nume_lower or 'operational' in nume_lower:
tip_cont = 'operational'
elif 'taxe' in nume_lower:
tip_cont = 'taxe'
elif 'antreprenor' in nume_lower:
tip_cont = 'antreprenor'
else:
tip_cont = 'curent'
accounts.append({
'nume_cont': nume.strip(),
'iban': iban.strip(),
'sold': sold_numeric,
'moneda': moneda.strip(),
'tip_cont': tip_cont
})
logging.info(f" [{idx}] {nume}: {sold_numeric} {moneda}")
except Exception as e:
logging.warning(f"Eroare la citirea contului {idx}: {e}")
continue
if not accounts:
raise Exception("Nu am putut citi niciun cont din lista!")
logging.info(f"✓ Citite {len(accounts)} conturi cu succes")
self._update_progress(f"Gasite {len(accounts)} conturi")
return accounts
except Exception as e:
logging.error(f"Eroare la citirea conturilor: {e}")
raise
def save_results(self, accounts):
"""Salveaza rezultate in CSV si JSON"""
if not accounts:
logging.warning("Nu exista conturi de salvat!")
return
timestamp = datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
# Salvare CSV
csv_path = Path(self.config.OUTPUT_DIR) / f'solduri_{timestamp}.csv'
with open(csv_path, 'w', newline='', encoding='utf-8') as f:
fieldnames = ['timestamp', 'nume_cont', 'iban', 'sold', 'moneda', 'tip_cont']
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
for account in accounts:
row = account.copy()
row['timestamp'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
writer.writerow(row)
logging.info(f"✓ CSV salvat: {csv_path}")
# Salvare JSON
json_path = Path(self.config.OUTPUT_DIR) / f'solduri_{timestamp}.json'
# Calculeaza metadata
currencies = list(set(a.get('moneda', 'N/A') for a in accounts))
total_ron = sum(a['sold'] for a in accounts if a.get('moneda') == 'RON')
output = {
'metadata': {
'timestamp': datetime.now().isoformat(),
'scraper_version': '1.0.0',
'total_accounts': len(accounts),
'total_ron': round(total_ron, 2),
'currencies': currencies
},
'accounts': accounts
}
with open(json_path, 'w', encoding='utf-8') as f:
json.dump(output, f, indent=2, ensure_ascii=False)
logging.info(f"✓ JSON salvat: {json_path}")
logging.info(f"Total conturi: {len(accounts)}, Total RON: {total_ron:.2f}")
return csv_path, json_path
def send_notifications(self, csv_path, downloaded_files, accounts):
"""
Trimite notificări email și Discord cu fișierele descărcate
Args:
csv_path: Calea către fișierul CSV cu solduri
downloaded_files: Lista de dicționare cu fișierele de tranzacții descărcate
accounts: Lista cu datele conturilor (solduri, nume, etc.)
"""
try:
from notifications import NotificationService
self._update_progress("Trimit rezultate...")
logging.info("=" * 60)
logging.info("TRIMITERE NOTIFICĂRI")
logging.info("=" * 60)
# Colectează toate fișierele de trimis
files_to_send = [str(csv_path)] # CSV cu solduri
# Adaugă toate CSV-urile cu tranzacții
for df in downloaded_files:
if 'fisier' in df and Path(df['fisier']).exists():
files_to_send.append(df['fisier'])
logging.info(f"Adăugat pentru notificare: {Path(df['fisier']).name}")
logging.info(f"Total fișiere de trimis: {len(files_to_send)}")
# Trimite prin toate canalele configurate
service = NotificationService(self.config)
logging.info(f"Calling send_all with telegram_message_id={self.telegram_message_id}, telegram_chat_id={self.telegram_chat_id}")
results = service.send_all(
files_to_send,
accounts,
telegram_message_id=self.telegram_message_id,
telegram_chat_id=self.telegram_chat_id
)
# Afișează rezumat
if self.config.EMAIL_ENABLED:
status = "✓ Succes" if results['email'] else "✗ Eșuat"
logging.info(f"Email: {status}")
if self.config.TELEGRAM_ENABLED:
status = "✓ Succes" if results['telegram'] else "✗ Eșuat"
logging.info(f"Telegram: {status}")
logging.info("=" * 60)
except Exception as e:
logging.warning(f"Notificările au eșuat: {e}")
logging.warning("Scraper-ul a finalizat cu succes, dar notificările nu au fost trimise")
# Nu aruncă excepția mai departe - notificările sunt opționale
def _handle_error(self, error):
"""Error handling cu screenshot"""
logging.error("=" * 60)
logging.error(f"EROARE: {error}")
logging.error("=" * 60, exc_info=True)
# Salvare screenshot la eroare
if self.page and self.config.SCREENSHOT_ON_ERROR:
try:
error_path = Path(self.config.OUTPUT_DIR) / f"error_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png"
self.page.screenshot(path=str(error_path))
logging.error(f"Screenshot eroare salvat: {error_path}")
except Exception as e:
logging.error(f"Nu am putut salva screenshot: {e}")
def main():
"""Functie main pentru rulare script"""
try:
scraper = BTGoScraper()
success = scraper.run()
exit(0 if success else 1)
except ValueError as e:
# Eroare de configurare
logging.error(f"Eroare configurare: {e}")
exit(4)
except NotImplementedError as e:
# Selectors nu sunt completati
print(str(e))
exit(5)
except Exception as e:
logging.error(f"Eroare neasteptata: {e}", exc_info=True)
exit(99)
if __name__ == "__main__":
main()

58
config.py Normal file
View File

@@ -0,0 +1,58 @@
"""
Configurare pentru BTGO Scraper
Citeste configuratii din variabile de mediu (.env)
"""
from dotenv import load_dotenv
import os
# Incarca variabilele de mediu din fisierul .env
load_dotenv()
class Config:
"""Clasa pentru gestionarea configuratiilor aplicatiei"""
# Credentials
BTGO_USERNAME = os.getenv('BTGO_USERNAME')
BTGO_PASSWORD = os.getenv('BTGO_PASSWORD')
# Browser settings
HEADLESS = os.getenv('HEADLESS', 'false').lower() == 'true'
# Timeouts
TIMEOUT_2FA_SECONDS = int(os.getenv('TIMEOUT_2FA_SECONDS', 120))
# Paths
OUTPUT_DIR = os.getenv('OUTPUT_DIR', './data')
LOG_DIR = './logs'
# Features
SCREENSHOT_ON_ERROR = os.getenv('SCREENSHOT_ON_ERROR', 'true').lower() == 'true'
DOWNLOAD_TRANSACTIONS = os.getenv('DOWNLOAD_TRANSACTIONS', 'true').lower() == 'true'
LOG_LEVEL = os.getenv('LOG_LEVEL', 'INFO')
# Notification settings
ENABLE_NOTIFICATIONS = os.getenv('ENABLE_NOTIFICATIONS', 'false').lower() == 'true'
# Email settings (SMTP)
EMAIL_ENABLED = os.getenv('EMAIL_ENABLED', 'false').lower() == 'true'
SMTP_SERVER = os.getenv('SMTP_SERVER')
SMTP_PORT = int(os.getenv('SMTP_PORT', 587))
SMTP_USERNAME = os.getenv('SMTP_USERNAME')
SMTP_PASSWORD = os.getenv('SMTP_PASSWORD')
EMAIL_FROM = os.getenv('EMAIL_FROM')
EMAIL_TO = os.getenv('EMAIL_TO')
# Telegram settings
TELEGRAM_ENABLED = os.getenv('TELEGRAM_ENABLED', 'false').lower() == 'true'
TELEGRAM_BOT_TOKEN = os.getenv('TELEGRAM_BOT_TOKEN')
TELEGRAM_CHAT_ID = os.getenv('TELEGRAM_CHAT_ID')
@classmethod
def validate(cls):
"""Valideaza ca toate configuratiile obligatorii sunt setate"""
if not cls.BTGO_USERNAME or not cls.BTGO_PASSWORD:
raise ValueError("BTGO_USERNAME si BTGO_PASSWORD trebuie setate in .env")
if cls.TIMEOUT_2FA_SECONDS < 30:
raise ValueError("TIMEOUT_2FA_SECONDS trebuie sa fie minim 30 secunde")

1
data/.gitkeep Normal file
View File

@@ -0,0 +1 @@
# Folder pentru rezultate (CSV, JSON, screenshots)

322
deployment/windows/INDEX.md Normal file
View File

@@ -0,0 +1,322 @@
# 📁 Deployment Scripts - Index
Structura completă de scripturi pentru deployment Windows Service.
## 📂 Structura Directoarelor
```
deployment/windows/
├── README.md # Documentație completă
├── QUICK_START.md # Ghid rapid 5 minute
├── INDEX.md # Acest fișier
├── scripts/ # Scripturi PowerShell
│ ├── menu.ps1 # ⭐ Meniu interactiv (START HERE)
│ ├── deploy.ps1 # 🚀 Deployment complet
│ ├── install_service.ps1 # Instalare serviciu Windows
│ ├── uninstall_service.ps1 # Dezinstalare serviciu
│ ├── restart_service.ps1 # Restart rapid serviciu
│ ├── status.ps1 # Status serviciu și logs
│ └── view_logs.ps1 # Viewer logs interactiv
└── tools/ # Binare (auto-download)
├── .gitignore
└── nssm.exe # Downloaded by install_service
```
---
## 🚦 Quick Navigation
| Dacă vrei să... | Rulează |
|-----------------|---------|
| **⭐ Meniu interactiv** | `menu.ps1` |
| **🆕 Instalezi prima dată** | `deploy.ps1` |
| **📊 Verifici statusul** | `status.ps1` |
| **🔄 Restarți serviciul** | `restart_service.ps1` |
| **📝 Vezi logs-uri** | `view_logs.ps1` |
| **🗑️ Dezinstalezi** | `uninstall_service.ps1` |
| **📖 Citești ghidul** | `README.md` sau `QUICK_START.md` |
**💡 TIP:** Folosește `menu.ps1` pentru o experiență user-friendly!
---
## 📜 Descriere Scripturi
### 🚀 `deploy.bat` (Main Script)
**Scop:** Deployment complet one-click
**Ce face:**
1. Verifică Python și dependențe
2. Configurează `.env` (dacă nu există)
3. Instalează requirements Python
4. Instalează browsere Playwright
5. Creează și pornește serviciul Windows
6. Verifică status final
**Rulare:**
```batch
cd deployment\windows\scripts
deploy.bat
```
**Durată:** 2-5 minute (prima dată)
**Note:**
- Necesită Administrator
- Interactiv (așteaptă configurare `.env`)
---
### ⚙️ `install_service.bat`
**Scop:** Instalează DOAR serviciul Windows (fără dependențe)
**Ce face:**
1. Descarcă NSSM (dacă lipsește)
2. Verifică `.env` și bot script
3. Instalează serviciu cu NSSM
4. Configurează logging, restart policy
5. Pornește serviciul
**Rulare:**
```batch
install_service.bat
```
**Use case:** După ce ai instalat manual Python + requirements
---
### 🗑️ `uninstall_service.bat`
**Scop:** Elimină serviciul din Windows
**Ce face:**
1. Oprește serviciul (dacă rulează)
2. Dezinstalează cu NSSM
3. Verifică eliminare completă
**Rulare:**
```batch
uninstall_service.bat
```
**Note:**
- NU șterge fișiere proiect
- NU șterge logs
- Doar elimină serviciul din registry
---
### 🔄 `restart_service.bat`
**Scop:** Restart rapid serviciu (după modificări cod)
**Ce face:**
1. Stop serviciu
2. Wait 2 secunde
3. Start serviciu
4. Verifică status
**Rulare:**
```batch
restart_service.bat
```
**Use case:** După `git pull` sau modificări `.env`
---
### 📊 `status.bat`
**Scop:** Status detaliat serviciu + logs
**Ce face:**
1. Verifică dacă serviciul există
2. Afișează status detaliat (`sc query`)
3. Afișează configurație (`sc qc`)
4. Afișează ultimele 10 linii din logs
5. Afișează comenzi utile
**Rulare:**
```batch
status.bat
```
**Use case:** Quick health check
---
### 📝 `view_logs.bat`
**Scop:** Viewer interactiv logs
**Meniu:**
1. View stdout (toate logs)
2. View stderr (doar erori)
3. Tail stdout (live updates)
4. Tail stderr (live erori)
5. Open Explorer în folder logs
6. Șterge toate logs
**Rulare:**
```batch
view_logs.bat
```
**Use case:** Debugging, monitoring live
---
## 🔧 Configurare Serviciu Windows
### Detalii Serviciu
| Proprietate | Valoare |
|------------|---------|
| **Service Name** | `BTGOTelegramBot` |
| **Display Name** | `BTGO Telegram Trigger Bot` |
| **Executable** | `python.exe` |
| **Arguments** | `telegram_trigger_bot.py` |
| **Working Dir** | Project root |
| **Start Type** | Automatic (Delayed) |
| **Restart Policy** | On failure, 5s delay |
| **Stdout Log** | `logs/telegram_bot_stdout.log` |
| **Stderr Log** | `logs/telegram_bot_stderr.log` |
| **Log Rotation** | 10 MB max |
### Managed by NSSM
**NSSM** (Non-Sucking Service Manager) este folosit pentru:
- ✅ Simplifică crearea serviciilor din Python scripts
- ✅ Gestionează logging automat
- ✅ Auto-restart la crash
- ✅ Gestiune environment variables
**Download:** Auto-descărcat de `install_service.bat` în `tools/nssm.exe`
**Versiune:** 2.24 (64-bit)
---
## 📋 Workflow Tipic
### Prima Instalare (VM nou)
```batch
# 1. Clone repo
cd E:\proiecte
git clone <repo> btgo-playwright
cd btgo-playwright
# 2. Configurează .env
copy .env.example .env
notepad .env
# 3. Deploy complet
cd deployment\windows\scripts
deploy.bat
# 4. Verifică status
status.bat
# 5. Test în Telegram
# Trimite /start la bot
```
---
### Update Cod (după modificări)
```batch
# 1. Pull changes
cd E:\proiecte\btgo-playwright
git pull
# 2. Update requirements (dacă s-au modificat)
python -m pip install -r requirements.txt
# 3. Restart serviciu
deployment\windows\scripts\restart_service.bat
# 4. Verifică logs
deployment\windows\scripts\view_logs.bat
```
---
### Debug Issues
```batch
# 1. Verifică status
deployment\windows\scripts\status.bat
# 2. Verifică logs
deployment\windows\scripts\view_logs.bat
# Selectează opțiunea 2 (stderr)
# 3. Testează manual (dacă e nevoie)
net stop BTGOTelegramBot
python telegram_trigger_bot.py
# Vezi output direct în terminal
# 4. Restart serviciu
deployment\windows\scripts\restart_service.bat
```
---
### Reinstalare Completă
```batch
# 1. Dezinstalează
deployment\windows\scripts\uninstall_service.bat
# 2. Șterge logs vechi (opțional)
del /q logs\*.log
# 3. Reinstalează
deployment\windows\scripts\deploy.bat
```
---
## 🛡️ Securitate
**Protecție `.env`:**
- `.env` este în `.gitignore` (nu se commitează)
- Conține credențiale sensibile (bot token, parole BTGO)
- Setează permissions doar pentru Administrator
**User IDs Telegram:**
- `TELEGRAM_ALLOWED_USER_IDS` restricționează accesul
- Format: `123456789,987654321` (comma-separated)
- Nu lăsa gol (permite oricui)
---
## 📞 Support
**Documentație:**
- `README.md` - Documentație completă
- `QUICK_START.md` - Ghid rapid 5 minute
- `../../TELEGRAM_BOT_SETUP.md` - Setup Telegram
- `../../CLAUDE.md` - Documentație proiect
**Troubleshooting:**
1. Verifică `status.bat`
2. Verifică `view_logs.bat` → stderr
3. Testează manual: `python telegram_trigger_bot.py`
---
## 📄 Changelog
| Data | Versiune | Modificări |
|------|----------|-----------|
| 2025-11-06 | 1.0 | Initial release - Complete deployment suite |
---
**Happy Deploying! 🚀**

View File

@@ -0,0 +1,264 @@
# 🚀 Quick Start - BTGO Telegram Bot pe VM Windows
Ghid rapid pentru deployment în 5 minute.
## ✅ Pre-requisites Checklist
- [ ] Windows Server/Desktop cu GUI (pentru browser vizibil)
- [ ] Python 3.11+ instalat în PATH
- [ ] Cont Administrator pe Windows
- [ ] Bot Telegram creat cu @BotFather
- [ ] Credențiale BTGO (LOGIN_ID + PAROLA)
---
## 📥 Pas 1: Clone Repository (pe VM)
```batch
cd E:\proiecte
git clone <repo-url> btgo-playwright
cd btgo-playwright
```
Sau copiază manualmente folderul pe VM.
---
## ⚙️ Pas 2: Configurare `.env`
```batch
REM Copiază template
copy .env.example .env
REM Editează cu Notepad
notepad .env
```
**Configurare minimă necesară:**
```ini
# TELEGRAM
TELEGRAM_BOT_TOKEN=123456:ABC-DEF... # De la @BotFather
TELEGRAM_ALLOWED_USER_IDS=123456789 # Telegram User ID-ul tău
# BTGO
LOGIN_ID=your_btgo_username
PAROLA=your_btgo_password
# SCRAPER
HEADLESS=false # ⚠️ CRITICAL: NU schimba în true!
```
**Cum afli User ID-ul tău:**
```batch
REM După ce ai setat TELEGRAM_BOT_TOKEN în .env
python get_telegram_chat_id.py
REM Trimite un mesaj bot-ului în Telegram
REM Script-ul va afișa User ID-ul
```
---
## 🚀 Pas 3: Deploy Automat
**Right-click pe `deploy.ps1` → "Run with PowerShell" (ca Administrator)**
SAU din PowerShell Administrator:
```powershell
cd deployment\windows\scripts
.\deploy.ps1
```
**Scriptul va instala:**
- ✅ Dependențe Python
- ✅ Playwright browsers
- ✅ Serviciu Windows
- ✅ Auto-start la boot
**Durată:** ~2-3 minute
---
## ✅ Pas 4: Verificare
### 1. Verifică serviciul rulează
```batch
sc query BTGOTelegramBot
```
**Output așteptat:**
```
STATE : 4 RUNNING
```
### 2. Verifică logs
```batch
deployment\windows\scripts\view_logs.bat
```
Sau direct:
```batch
type logs\telegram_bot_stdout.log
```
**Ar trebui să vezi:**
```
[2025-11-06 10:15:23] [INFO] Bot inițializat. Useri autorizați: [123456789]
[2025-11-06 10:15:23] [INFO] Bot pornit. Așteaptă comenzi...
```
### 3. Testează în Telegram
1. Deschide Telegram
2. Caută bot-ul tău (numele setat la @BotFather)
3. Trimite `/start`
**Răspuns așteptat:**
```
🤖 BTGO Scraper Trigger Bot
Comenzi disponibile:
• /scrape - Rulează scraper-ul
• /status - Status sistem
• /help - Ajutor
```
4. Trimite `/scrape` pentru test complet
**Flow 2FA:**
- Bot răspunde: "🤖 BTGO Scraper pornit... Așteaptă 2FA pe telefon!"
- Primești notificare pe telefon în George App
- Aprobi 2FA
- Bot trimite fișierele (solduri + tranzacții CSV)
---
## 🎉 Gata!
Serviciul rulează acum non-stop pe VM. La reboot, pornește automat.
---
## 📋 Comenzi Utile Day-to-Day
### Meniu Interactiv (Cel mai ușor!)
```powershell
# Right-click → "Run with PowerShell" (ca Admin)
deployment\windows\scripts\menu.ps1
```
### Comenzi Directe PowerShell
```powershell
# Status rapid
.\status.ps1
# Restart serviciu (după modificări cod)
.\restart_service.ps1
# View logs live
.\view_logs.ps1
# Oprește serviciu
Stop-Service BTGOTelegramBot
# Pornește serviciu
Start-Service BTGOTelegramBot
# Restart serviciu
Restart-Service BTGOTelegramBot
```
---
## 🔄 Update Cod (după modificări)
```powershell
# Pe VM
cd E:\proiecte\btgo-playwright
git pull
# Restart serviciu (PowerShell)
deployment\windows\scripts\restart_service.ps1
# SAU folosește meniul interactiv
deployment\windows\scripts\menu.ps1
# → Selectează opțiunea 6 (Restart)
```
---
## 🐛 Troubleshooting Rapid
### Serviciul nu pornește
```batch
# Verifică logs
type logs\telegram_bot_stderr.log
```
**Erori comune:**
**"ModuleNotFoundError":**
```batch
python -m pip install -r requirements.txt
deployment\windows\scripts\restart_service.bat
```
**"TELEGRAM_BOT_TOKEN nu este setat":**
- Verifică `.env` există și conține token-ul
- Token-ul trebuie pe o singură linie, fără spații extra
**"Access Denied" în browser:**
- Verifică `HEADLESS=false` în `.env`
- Nu folosi Docker/headless mode
### Bot nu răspunde în Telegram
1. **Verifică serviciul:**
```batch
sc query BTGOTelegramBot
```
2. **Testează manual:**
```batch
REM Oprește serviciul
net stop BTGOTelegramBot
REM Rulează manual pentru debug
python telegram_trigger_bot.py
REM Trimite /start în Telegram
REM Vezi output-ul direct în terminal
```
3. **Verifică firewall:**
- Bot-ul trebuie să acceseze `api.telegram.org` (port 443)
### Scraper-ul timeout-ează la 2FA
În `.env`:
```ini
TIMEOUT_2FA_SECONDS=600 # Crește la 10 minute
```
Apoi restart:
```batch
deployment\windows\scripts\restart_service.bat
```
---
## 📞 Ajutor Suplimentar
- **README complet:** `deployment\windows\README.md`
- **Setup Telegram:** `TELEGRAM_BOT_SETUP.md`
- **Documentație proiect:** `CLAUDE.md`
---
**Enjoy! 🎉**

View File

@@ -0,0 +1,341 @@
# BTGO Telegram Bot - Windows Service Deployment
Scripturi pentru instalarea și gestionarea bot-ului Telegram ca serviciu Windows pe VM.
## 📋 Cerințe Preliminare
- **Windows Server** 2016+ sau **Windows 10/11**
- **Python 3.11+** instalat și adăugat în PATH
- **Privilegii de Administrator**
- **NSSM** (Non-Sucking Service Manager) - se descarcă automat
- **Git** (opțional, pentru pull updates)
## 🚀 Deployment Complet (Recomandat)
```powershell
# Right-click pe script → "Run with PowerShell" (ca Administrator)
deployment\windows\scripts\deploy.ps1
# SAU din PowerShell Administrator:
cd deployment\windows\scripts
.\deploy.ps1
```
**Acest script va:**
1. ✅ Verifica Python și dependențe
2. ✅ Copia și configura `.env` (dacă nu există)
3. ✅ Instala dependențele Python (`requirements.txt`)
4. ✅ Instala browsere Playwright
5. ✅ Instala și porni serviciul Windows
6. ✅ Verifica statusul serviciului
### ⚠️ Înainte de Prima Rulare
**Configurați `.env`** cu datele tale:
```bash
# Telegram Bot
TELEGRAM_BOT_TOKEN=123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11
TELEGRAM_ALLOWED_USER_IDS=123456789,987654321 # User IDs autorizați
TELEGRAM_CHAT_ID=123456789 # Optional: pentru notificări
# BTGO Credentials
LOGIN_ID=your_btgo_login
PAROLA=your_btgo_password
# Scraper Settings
HEADLESS=false # IMPORTANT: trebuie false pentru VM Windows (WAF bypass)
TIMEOUT_2FA_SECONDS=300
```
**Cum obții variabilele Telegram:**
1. **Bot Token**: Vorbește cu [@BotFather](https://t.me/BotFather) și creează un bot
2. **User IDs**: Rulează `python get_telegram_chat_id.py` după ce configurezi token-ul
## 📦 Scripturi Disponibile
Toate scripturile sunt **PowerShell** (`.ps1`) pentru Windows 10/11.
### `menu.ps1` - Meniu Interactiv ⭐
Interfață user-friendly pentru toate operațiunile.
```powershell
.\menu.ps1
```
---
### `deploy.ps1` - Deployment Complet
Instalează tot ce e necesar și pornește serviciul.
```powershell
.\deploy.ps1
```
---
### `install_service.ps1` - Doar Instalare Serviciu
Instalează serviciul Windows (după ce ai configurat manual tot restul).
```powershell
.\install_service.ps1
```
**Ce face:**
- Descarcă NSSM dacă nu există
- Creează serviciul Windows `BTGOTelegramBot`
- Configurează logging automat în `logs/`
- Configurează auto-restart la crash
- Pornește serviciul
---
### `uninstall_service.ps1` - Dezinstalare Serviciu
Oprește și elimină serviciul din sistem.
```powershell
.\uninstall_service.ps1
```
---
### `restart_service.ps1` - Restart Rapid
Restart rapid al serviciului (pentru aplicarea unor modificări).
```powershell
.\restart_service.ps1
```
---
### `status.ps1` - Status Serviciu
Afișează status detaliat al serviciului și ultimele logs.
```powershell
.\status.ps1
```
---
### `view_logs.ps1` - Viewer Logs Interactiv
Meniu interactiv pentru vizualizarea logs-urilor în timp real.
```powershell
.\view_logs.ps1
```
**Opțiuni:**
1. View stdout (output normal)
2. View stderr (erori)
3. Tail stdout (timp real)
4. Tail stderr (timp real)
5. Deschide Explorer în director logs
6. Șterge toate logurile
---
## 🔧 Gestionare Serviciu
### Comenzi Windows Standard
```batch
# Status serviciu
sc query BTGOTelegramBot
# Oprește serviciu
net stop BTGOTelegramBot
# Pornește serviciu
net start BTGOTelegramBot
# Restart serviciu
net stop BTGOTelegramBot && net start BTGOTelegramBot
# Configurație serviciu
sc qc BTGOTelegramBot
# Șterge serviciu (fără script)
sc delete BTGOTelegramBot
```
### Logs
Logs-urile se salvează automat în `logs/`:
- `telegram_bot_stdout.log` - Output normal (comenzi, mesaje)
- `telegram_bot_stderr.log` - Erori și avertizări
**View logs în PowerShell (timp real):**
```powershell
Get-Content logs\telegram_bot_stdout.log -Wait -Tail 20
```
**Rotație logs:**
- Max 10 MB per fișier
- Rotație automată când se atinge limita
---
## 🔄 Update și Redeploy
### Update Manual (cu Git)
```batch
# Pe VM Windows
cd E:\proiecte\btgo-playwright
# Pull ultimele modificări
git pull origin main
# Redeploy serviciu
deployment\windows\scripts\restart_service.bat
```
### Redeploy Complet (Reinstalare)
Dacă ai modificat dependențe sau structura proiectului:
```batch
# Dezinstalează
deployment\windows\scripts\uninstall_service.bat
# Reinstalează tot
deployment\windows\scripts\deploy.bat
```
---
## 🐛 Troubleshooting
### Serviciul nu pornește
**Verifică logs:**
```batch
deployment\windows\scripts\view_logs.bat
```
**Probleme comune:**
1. **Python nu este în PATH**
- Reinstalează Python cu "Add to PATH" bifat
- Sau setează manual PATH în Environment Variables
2. **`.env` lipsă sau invalid**
- Verifică că `.env` există în root-ul proiectului
- Verifică că `TELEGRAM_BOT_TOKEN` este setat corect
3. **Playwright browser lipsă**
```batch
python -m playwright install chromium
```
4. **Port ocupat / Serviciu duplicat**
```batch
uninstall_service.bat
# Așteaptă 10 secunde
install_service.bat
```
### Bot nu răspunde în Telegram
1. **Verifică serviciul rulează:**
```batch
sc query BTGOTelegramBot
```
2. **Verifică logs pentru erori:**
```batch
type logs\telegram_bot_stderr.log
```
3. **Testează bot-ul manual:**
```batch
python telegram_trigger_bot.py
# Trimite /start în Telegram
# Ctrl+C pentru a opri
```
4. **Verifică firewall:**
- Bot-ul trebuie să poată accesa `api.telegram.org` (HTTPS/443)
### Scraper-ul nu funcționează (Access Denied)
**CRITICAL:** Docker/headless mode NU funcționează din cauza Akamai WAF.
**Soluție:**
- Setează `HEADLESS=false` în `.env`
- Bot-ul TREBUIE să ruleze pe Windows cu browser vizibil
- Asigură-te că VM-ul are desktop environment activ
### Performance Issues
**Bot lent / Scraper timeout:**
- Crește `TIMEOUT_2FA_SECONDS` în `.env`
- Verifică resursele VM-ului (CPU, RAM)
- Verifică conexiunea la internet a VM-ului
---
## 📊 Arhitectura Serviciului
```
BTGOTelegramBot (Windows Service)
├── Managed by: NSSM
├── Auto-start: Yes (Delayed)
├── Restart on crash: Yes (5s delay)
├── Working dir: E:\proiecte\btgo-playwright
├── Python script: telegram_trigger_bot.py
├── Logs: logs\telegram_bot_*.log
└── Config: .env
```
**Flow:**
```
User /scrape → Telegram API → Bot Service → btgo_scraper.py
├── Playwright login
├── 2FA wait
├── Extract accounts
├── Download CSVs
└── Send files via notifiers
```
---
## 🔐 Securitate
### Best Practices
1. **Restricționează accesul bot-ului:**
- Setează `TELEGRAM_ALLOWED_USER_IDS` în `.env`
- Nu lăsa lista goală (permite oricui)
2. **Protejează `.env`:**
- Nu comite `.env` în Git (este în `.gitignore`)
- Setează permisiuni restrictive pe VM
3. **Monitorizează logs-urile:**
- Verifică periodic `stderr.log` pentru erori
- Setup alerting pentru crash-uri
4. **Rotație credentials:**
- Regenerează `TELEGRAM_BOT_TOKEN` periodic
- Update `LOGIN_ID`/`PAROLA` la schimbări
---
## 📞 Support
**Probleme sau întrebări?**
1. Verifică logs: `deployment\windows\scripts\view_logs.bat`
2. Verifică status: `deployment\windows\scripts\status.bat`
3. Consultă documentația principală: `TELEGRAM_BOT_SETUP.md`
4. Check issue tracker: [GitHub Issues](../../../)
---
## 📄 Licență
Acest tool este pentru **uz personal și educațional**. Asigură-te că respecti termenii și condițiile băncii tale.

View File

@@ -0,0 +1,282 @@
#Requires -RunAsAdministrator
<#
.SYNOPSIS
BTGO Telegram Bot - Complete Deployment Script
.DESCRIPTION
Deployment complet pentru bot-ul Telegram pe Windows VM
Verifica dependente, instaleaza requirements, configureaza serviciul
.NOTES
Rulare: Right-click → "Run with PowerShell" (ca Administrator)
#>
# Error handling
$ErrorActionPreference = "Stop"
# ============================================================================
# FUNCTIONS
# ============================================================================
function Write-ColorOutput {
param(
[string]$Message,
[string]$Color = "White",
[string]$Prefix = ""
)
$prefixColor = switch ($Prefix) {
"[OK]" { "Green" }
"[INFO]" { "Cyan" }
"[EROARE]" { "Red" }
"[AVERTIZARE]" { "Yellow" }
default { "White" }
}
if ($Prefix) {
Write-Host $Prefix -ForegroundColor $prefixColor -NoNewline
Write-Host " $Message" -ForegroundColor $Color
} else {
Write-Host $Message -ForegroundColor $Color
}
}
function Write-Separator {
param([string]$Title = "")
Write-Host ""
Write-Host "=" * 80 -ForegroundColor Cyan
if ($Title) {
Write-Host $Title -ForegroundColor Yellow
Write-Host "=" * 80 -ForegroundColor Cyan
}
}
# ============================================================================
# CONFIGURATION
# ============================================================================
$ScriptDir = Split-Path -Parent $PSCommandPath
$ProjectDir = Resolve-Path (Join-Path $ScriptDir "..\..\..") | Select-Object -ExpandProperty Path
$ServiceName = "BTGOTelegramBot"
Write-Separator "BTGO TELEGRAM BOT - DEPLOYMENT COMPLET"
Write-Host ""
Write-ColorOutput "Director proiect: $ProjectDir" -Prefix "[INFO]"
Write-Host ""
# ============================================================================
# [1/6] VERIFICARE PYTHON
# ============================================================================
Write-Separator "[1/6] VERIFICARE PYTHON"
try {
$pythonVersion = python --version 2>&1
if ($LASTEXITCODE -ne 0) { throw }
Write-ColorOutput "Python gasit: $pythonVersion" -Prefix "[OK]"
} catch {
Write-ColorOutput "Python nu este instalat sau nu este in PATH!" -Prefix "[EROARE]"
Write-Host ""
Write-Host "Instaleaza Python 3.11+ de la: https://www.python.org/downloads/"
Write-Host "Asigura-te ca bifezi 'Add Python to PATH' la instalare"
Read-Host "`nApasa Enter pentru a inchide"
exit 1
}
# ============================================================================
# [2/6] VERIFICARE FISIERE PROIECT
# ============================================================================
Write-Separator "[2/6] VERIFICARE FISIERE PROIECT"
$criticalFiles = @(
"telegram_trigger_bot.py",
"btgo_scraper.py",
"requirements.txt"
)
$filesOk = $true
foreach ($file in $criticalFiles) {
$filePath = Join-Path $ProjectDir $file
if (Test-Path $filePath) {
Write-ColorOutput "$file - OK" -Prefix "[OK]"
} else {
Write-ColorOutput "$file - LIPSESTE!" -Prefix "[EROARE]"
$filesOk = $false
}
}
if (-not $filesOk) {
Write-ColorOutput "Fisiere critice lipsesc din proiect!" -Prefix "[EROARE]"
Read-Host "`nApasa Enter pentru a inchide"
exit 1
}
# ============================================================================
# [3/6] VERIFICARE/CREARE .env
# ============================================================================
Write-Separator "[3/6] VERIFICARE CONFIGURARE (.env)"
$envFile = Join-Path $ProjectDir ".env"
$envExample = Join-Path $ProjectDir ".env.example"
if (-not (Test-Path $envFile)) {
if (Test-Path $envExample) {
Write-ColorOutput ".env nu exista. Copiem .env.example..." -Prefix "[INFO]"
Copy-Item $envExample $envFile
Write-ColorOutput ".env creat din .env.example" -Prefix "[OK]"
Write-Host ""
Write-ColorOutput "IMPORTANT: Editeaza .env si configureaza:" -Prefix "[AVERTIZARE]"
Write-Host " - TELEGRAM_BOT_TOKEN"
Write-Host " - TELEGRAM_ALLOWED_USER_IDS"
Write-Host " - TELEGRAM_CHAT_ID (optional)"
Write-Host " - LOGIN_ID si PAROLA pentru BTGO"
Write-Host ""
Read-Host "Apasa Enter dupa ce ai configurat .env"
} else {
Write-ColorOutput ".env si .env.example lipsesc!" -Prefix "[EROARE]"
Write-Host "Creaza manual .env cu variabilele necesare"
Read-Host "`nApasa Enter pentru a inchide"
exit 1
}
}
# Verifică variabile critice
$envContent = Get-Content $envFile -Raw
if ($envContent -notmatch "TELEGRAM_BOT_TOKEN=(?!your_).+") {
Write-ColorOutput "TELEGRAM_BOT_TOKEN nu este configurat in .env!" -Prefix "[EROARE]"
Write-Host "Editeaza .env si adauga token-ul de la @BotFather"
Read-Host "`nApasa Enter pentru a inchide"
exit 1
}
if ($envContent -notmatch "LOGIN_ID=(?!your_).+") {
Write-ColorOutput "LOGIN_ID nu este configurat in .env!" -Prefix "[AVERTIZARE]"
Write-Host "Scraper-ul nu va functiona fara credentiale BTGO"
}
Write-ColorOutput ".env exista si pare configurat" -Prefix "[OK]"
# ============================================================================
# [4/6] INSTALARE DEPENDENTE PYTHON
# ============================================================================
Write-Separator "[4/6] INSTALARE DEPENDENTE PYTHON"
try {
Write-ColorOutput "Upgrade pip..." -Prefix "[INFO]"
python -m pip install --upgrade pip --quiet
Write-ColorOutput "Instalare requirements.txt..." -Prefix "[INFO]"
$requirementsFile = Join-Path $ProjectDir "requirements.txt"
python -m pip install -r $requirementsFile
if ($LASTEXITCODE -ne 0) { throw }
Write-ColorOutput "Dependente instalate cu succes" -Prefix "[OK]"
} catch {
Write-ColorOutput "Instalarea dependentelor a esuat!" -Prefix "[EROARE]"
Read-Host "`nApasa Enter pentru a inchide"
exit 1
}
# ============================================================================
# [5/6] INSTALARE PLAYWRIGHT BROWSERS (GLOBAL)
# ============================================================================
Write-Separator "[5/6] INSTALARE PLAYWRIGHT BROWSERS (GLOBAL)"
# Setează locație globală pentru browsere (accesibilă pentru serviciul SYSTEM)
$globalBrowserPath = "C:\playwright-browsers"
Write-ColorOutput "Configurare locatie globala browsere: $globalBrowserPath" -Prefix "[INFO]"
# Creează directorul dacă nu există
if (-not (Test-Path $globalBrowserPath)) {
New-Item -ItemType Directory -Path $globalBrowserPath -Force | Out-Null
Write-ColorOutput "Director creat: $globalBrowserPath" -Prefix "[OK]"
}
# Acordă permisiuni SYSTEM la director
Write-ColorOutput "Acordare permisiuni SYSTEM..." -Prefix "[INFO]"
icacls $globalBrowserPath /grant "SYSTEM:(OI)(CI)F" /T 2>&1 | Out-Null
# Setează environment variable la nivel de sistem
[System.Environment]::SetEnvironmentVariable("PLAYWRIGHT_BROWSERS_PATH", $globalBrowserPath, [System.EnvironmentVariableTarget]::Machine)
$env:PLAYWRIGHT_BROWSERS_PATH = $globalBrowserPath
Write-ColorOutput "Environment variable setata: PLAYWRIGHT_BROWSERS_PATH" -Prefix "[OK]"
# Instalează browserele
try {
Write-ColorOutput "Instalare browsere Playwright in $globalBrowserPath..." -Prefix "[INFO]"
Write-Host "Aceasta poate dura 1-2 minute..." -ForegroundColor Gray
python -m playwright install chromium
if ($LASTEXITCODE -ne 0) { throw }
Write-ColorOutput "Playwright browsere instalate cu succes!" -Prefix "[OK]"
} catch {
Write-ColorOutput "Instalarea browserelor Playwright a esuat!" -Prefix "[AVERTIZARE]"
Write-Host "Scraper-ul poate sa nu functioneze corect"
}
# ============================================================================
# CREARE DIRECTOARE NECESARE
# ============================================================================
$dataDir = Join-Path $ProjectDir "data"
$logsDir = Join-Path $ProjectDir "logs"
if (-not (Test-Path $dataDir)) { New-Item -ItemType Directory -Path $dataDir -Force | Out-Null }
if (-not (Test-Path $logsDir)) { New-Item -ItemType Directory -Path $logsDir -Force | Out-Null }
Write-ColorOutput "Directoare create: data, logs" -Prefix "[OK]"
# ============================================================================
# [6/6] INSTALARE/RESTART SERVICIU
# ============================================================================
Write-Separator "[6/6] INSTALARE SERVICIU WINDOWS"
# Verifică dacă serviciul există
$service = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
if ($service) {
Write-ColorOutput "Serviciul exista deja. Reinstalam..." -Prefix "[INFO]"
& "$ScriptDir\uninstall_service.ps1"
Start-Sleep -Seconds 3
}
Write-ColorOutput "Rulare install_service.ps1..." -Prefix "[INFO]"
& "$ScriptDir\install_service.ps1"
if ($LASTEXITCODE -ne 0) {
Write-ColorOutput "Instalarea serviciului a esuat!" -Prefix "[EROARE]"
Read-Host "`nApasa Enter pentru a inchide"
exit 1
}
# ============================================================================
# FINALIZARE
# ============================================================================
Write-Separator "[SUCCES] DEPLOYMENT FINALIZAT!"
Write-Host ""
Write-Host "Serviciul BTGO Telegram Bot este acum instalat si ruleaza." -ForegroundColor Green
Write-Host ""
Write-Host "Comenzi utile:" -ForegroundColor Yellow
Write-Host " - Status: Get-Service $ServiceName"
Write-Host " - Logs: Get-Content '$logsDir\telegram_bot_stdout.log' -Tail 20"
Write-Host " - Opreste: Stop-Service $ServiceName"
Write-Host " - Porneste: Start-Service $ServiceName"
Write-Host " - Restart: Restart-Service $ServiceName"
Write-Host " - Dezinstala: .\uninstall_service.ps1"
Write-Host ""
Write-Host "Teste bot Telegram:" -ForegroundColor Yellow
Write-Host " 1. Deschide Telegram si cauta bot-ul tau"
Write-Host " 2. Trimite /start pentru a vedea comenzile"
Write-Host " 3. Trimite /scrape pentru a testa scraper-ul"
Write-Host ""
Write-Host "Verificare logs in timp real:" -ForegroundColor Yellow
Write-Host " Get-Content '$logsDir\telegram_bot_stdout.log' -Wait"
Write-Host ""
Write-Separator
Read-Host "`nApasa Enter pentru a inchide"

View File

@@ -0,0 +1,275 @@
#Requires -RunAsAdministrator
<#
.SYNOPSIS
BTGO Telegram Bot - Service Installation Script
.DESCRIPTION
Instalează bot-ul Telegram ca serviciu Windows folosind NSSM
.NOTES
Rulare: Right-click → "Run with PowerShell" (ca Administrator)
#>
$ErrorActionPreference = "Stop"
# ============================================================================
# FUNCTIONS
# ============================================================================
function Write-ColorOutput {
param(
[string]$Message,
[string]$Prefix = ""
)
$color = switch ($Prefix) {
"[OK]" { "Green" }
"[INFO]" { "Cyan" }
"[EROARE]" { "Red" }
"[AVERTIZARE]" { "Yellow" }
default { "White" }
}
if ($Prefix) {
Write-Host "$Prefix " -ForegroundColor $color -NoNewline
Write-Host $Message
} else {
Write-Host $Message
}
}
function Write-Separator {
param([string]$Title = "")
Write-Host ""
Write-Host ("=" * 80) -ForegroundColor Cyan
if ($Title) {
Write-Host $Title -ForegroundColor Yellow
Write-Host ("=" * 80) -ForegroundColor Cyan
}
}
# ============================================================================
# CONFIGURATION
# ============================================================================
$ServiceName = "BTGOTelegramBot"
$ServiceDisplayName = "BTGO Telegram Trigger Bot"
$ServiceDescription = "Bot Telegram pentru declansarea BTGO Scraper prin comanda /scrape"
$ScriptDir = Split-Path -Parent $PSCommandPath
$ProjectDir = Resolve-Path (Join-Path $ScriptDir "..\..\..") | Select-Object -ExpandProperty Path
$BotScript = Join-Path $ProjectDir "telegram_trigger_bot.py"
$EnvFile = Join-Path $ProjectDir ".env"
$LogDir = Join-Path $ProjectDir "logs"
$ToolsDir = Join-Path $ProjectDir "deployment\windows\tools"
$NssmPath = Join-Path $ToolsDir "nssm.exe"
Write-Separator "BTGO TELEGRAM BOT - INSTALARE SERVICIU"
Write-ColorOutput "Director proiect: $ProjectDir" -Prefix "[INFO]"
# ============================================================================
# VERIFICARI PRE-INSTALARE
# ============================================================================
Write-Separator "VERIFICARI PRE-INSTALARE"
# Verifică Python
try {
$pythonPath = (Get-Command python).Source
Write-ColorOutput "Python: $pythonPath" -Prefix "[OK]"
} catch {
Write-ColorOutput "Python nu a fost gasit in PATH!" -Prefix "[EROARE]"
Read-Host "`nApasa Enter pentru a inchide"
exit 1
}
# Verifică script bot
if (Test-Path $BotScript) {
Write-ColorOutput "Script bot: $BotScript" -Prefix "[OK]"
} else {
Write-ColorOutput "Script bot nu a fost gasit: $BotScript" -Prefix "[EROARE]"
Read-Host "`nApasa Enter pentru a inchide"
exit 1
}
# Verifică .env
if (Test-Path $EnvFile) {
Write-ColorOutput "Fisier .env: $EnvFile" -Prefix "[OK]"
} else {
Write-ColorOutput "Fisier .env nu a fost gasit: $EnvFile" -Prefix "[EROARE]"
Write-Host "Copiaza .env.example la .env si configureaza-l"
Read-Host "`nApasa Enter pentru a inchide"
exit 1
}
# Verifică variabile critice din .env
$envContent = Get-Content $EnvFile -Raw
if ($envContent -match "TELEGRAM_BOT_TOKEN=.+") {
Write-ColorOutput "TELEGRAM_BOT_TOKEN configurat in .env" -Prefix "[OK]"
} else {
Write-ColorOutput "TELEGRAM_BOT_TOKEN nu este setat in .env!" -Prefix "[EROARE]"
Read-Host "`nApasa Enter pentru a inchide"
exit 1
}
# Creează director logs
if (-not (Test-Path $LogDir)) {
New-Item -ItemType Directory -Path $LogDir -Force | Out-Null
Write-ColorOutput "Director logs creat: $LogDir" -Prefix "[INFO]"
}
# ============================================================================
# DOWNLOAD NSSM (daca nu exista)
# ============================================================================
if (-not (Test-Path $NssmPath)) {
Write-Separator "DOWNLOAD NSSM"
Write-ColorOutput "NSSM nu a fost gasit. Descarcam..." -Prefix "[INFO]"
if (-not (Test-Path $ToolsDir)) {
New-Item -ItemType Directory -Path $ToolsDir -Force | Out-Null
}
try {
$nssmUrl = "https://nssm.cc/release/nssm-2.24.zip"
$nssmZip = Join-Path $env:TEMP "nssm.zip"
$nssmExtract = Join-Path $env:TEMP "nssm"
Write-ColorOutput "Descarcare NSSM..." -Prefix "[INFO]"
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Invoke-WebRequest -Uri $nssmUrl -OutFile $nssmZip -UseBasicParsing
Write-ColorOutput "Extragere NSSM..." -Prefix "[INFO]"
Expand-Archive -Path $nssmZip -DestinationPath $nssmExtract -Force
# Copiază executabilul
$nssmExe = Join-Path $nssmExtract "nssm-2.24\win64\nssm.exe"
Copy-Item $nssmExe $NssmPath
# Curăță
Remove-Item $nssmZip -Force -ErrorAction SilentlyContinue
Remove-Item $nssmExtract -Recurse -Force -ErrorAction SilentlyContinue
Write-ColorOutput "NSSM descarcat: $NssmPath" -Prefix "[OK]"
} catch {
Write-ColorOutput "Descarcarea NSSM a esuat!" -Prefix "[EROARE]"
Write-Host "Descarca manual de la: https://nssm.cc/download"
Read-Host "`nApasa Enter pentru a inchide"
exit 1
}
}
# ============================================================================
# INSTALARE SERVICIU
# ============================================================================
Write-Separator "INSTALARE SERVICIU WINDOWS"
# Verifică dacă serviciul există deja
$existingService = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
if ($existingService) {
Write-ColorOutput "Serviciul $ServiceName exista deja!" -Prefix "[AVERTIZARE]"
Write-ColorOutput "Ruland dezinstalare mai intai..." -Prefix "[INFO]"
& "$ScriptDir\uninstall_service.ps1"
Start-Sleep -Seconds 3
}
Write-ColorOutput "Instalare serviciu: $ServiceName" -Prefix "[INFO]"
# Instalează serviciul
& $NssmPath install $ServiceName $pythonPath $BotScript
if ($LASTEXITCODE -ne 0) {
Write-ColorOutput "Instalarea serviciului a esuat!" -Prefix "[EROARE]"
Read-Host "`nApasa Enter pentru a inchide"
exit 1
}
# ============================================================================
# CONFIGURARE SERVICIU
# ============================================================================
Write-ColorOutput "Configurare serviciu..." -Prefix "[INFO]"
# Display name si descriere
& $NssmPath set $ServiceName DisplayName $ServiceDisplayName
& $NssmPath set $ServiceName Description $ServiceDescription
# Verifică și configurează Playwright browsers global (dacă nu e deja setat)
$globalBrowserPath = [System.Environment]::GetEnvironmentVariable("PLAYWRIGHT_BROWSERS_PATH", [System.EnvironmentVariableTarget]::Machine)
if (-not $globalBrowserPath) {
Write-ColorOutput "PLAYWRIGHT_BROWSERS_PATH nu este setat!" -Prefix "[AVERTIZARE]"
Write-Host "Ruleaza 'deploy.ps1' pentru instalare completa cu browsere globale"
} else {
Write-ColorOutput "Playwright browsere: $globalBrowserPath" -Prefix "[OK]"
}
# Working directory
& $NssmPath set $ServiceName AppDirectory $ProjectDir
# Stdout/Stderr logging
$stdoutLog = Join-Path $LogDir "telegram_bot_stdout.log"
$stderrLog = Join-Path $LogDir "telegram_bot_stderr.log"
& $NssmPath set $ServiceName AppStdout $stdoutLog
& $NssmPath set $ServiceName AppStderr $stderrLog
# Rotatie logs (10 MB max)
& $NssmPath set $ServiceName AppStdoutCreationDisposition 4
& $NssmPath set $ServiceName AppStderrCreationDisposition 4
& $NssmPath set $ServiceName AppRotateFiles 1
& $NssmPath set $ServiceName AppRotateOnline 1
& $NssmPath set $ServiceName AppRotateBytes 10485760
# Restart policy (restart la crash)
& $NssmPath set $ServiceName AppExit Default Restart
& $NssmPath set $ServiceName AppRestartDelay 5000
# Start mode (Automatic Delayed Start)
& $NssmPath set $ServiceName Start SERVICE_DELAYED_AUTO_START
Write-ColorOutput "Serviciu configurat cu succes!" -Prefix "[OK]"
# ============================================================================
# PORNIRE SERVICIU
# ============================================================================
Write-Separator "PORNIRE SERVICIU"
Write-ColorOutput "Pornire serviciu: $ServiceName" -Prefix "[INFO]"
Start-Service -Name $ServiceName
if ((Get-Service -Name $ServiceName).Status -ne "Running") {
Write-ColorOutput "Pornirea serviciului a esuat!" -Prefix "[EROARE]"
Write-Host "Verifica logurile in: $LogDir"
Read-Host "`nApasa Enter pentru a inchide"
exit 1
}
Start-Sleep -Seconds 2
# ============================================================================
# VERIFICARE STATUS
# ============================================================================
$service = Get-Service -Name $ServiceName
if ($service.Status -eq "Running") {
Write-Separator "[SUCCES] SERVICIU INSTALAT SI PORNIT!"
Write-Host ""
Write-Host "Serviciu: $ServiceName" -ForegroundColor Green
Write-Host "Display: $ServiceDisplayName" -ForegroundColor Green
Write-Host "Script: $BotScript" -ForegroundColor Green
Write-Host "Logs: $LogDir" -ForegroundColor Green
Write-Host ""
Write-Host "Comenzi utile:" -ForegroundColor Yellow
Write-Host " - Opreste: Stop-Service $ServiceName"
Write-Host " - Porneste: Start-Service $ServiceName"
Write-Host " - Restart: Restart-Service $ServiceName"
Write-Host " - Status: Get-Service $ServiceName"
Write-Host " - Dezinstaleaza: .\uninstall_service.ps1"
Write-Host ""
Write-Separator
} else {
Write-ColorOutput "Serviciul este instalat dar nu ruleaza!" -Prefix "[AVERTIZARE]"
Write-Host "Verifica logurile: $LogDir"
Get-Service -Name $ServiceName | Format-List
}
Read-Host "`nApasa Enter pentru a inchide"

View File

@@ -0,0 +1,314 @@
#Requires -RunAsAdministrator
<#
.SYNOPSIS
BTGO Telegram Bot - Main Menu Launcher
.DESCRIPTION
Meniu interactiv pentru toate scripturile de management
.NOTES
Rulare: Right-click → "Run with PowerShell" (ca Administrator)
#>
$ErrorActionPreference = "Continue"
$ScriptDir = Split-Path -Parent $PSCommandPath
$ServiceName = "BTGOTelegramBot"
$ProjectDir = Resolve-Path (Join-Path $ScriptDir "..\..\..") | Select-Object -ExpandProperty Path
$LogDir = Join-Path $ProjectDir "logs"
function Show-MainMenu {
Clear-Host
Write-Host ""
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host " BTGO TELEGRAM BOT - MANAGEMENT MENU" -ForegroundColor Yellow
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host ""
# Verifică status serviciu
$service = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
if ($service) {
if ($service.Status -eq "Running") {
Write-Host "Status: " -NoNewline
Write-Host "[RUNNING]" -ForegroundColor Green
} else {
Write-Host "Status: " -NoNewline
Write-Host "[STOPPED]" -ForegroundColor Yellow
}
} else {
Write-Host "Status: " -NoNewline
Write-Host "[NOT INSTALLED]" -ForegroundColor Red
}
Write-Host ""
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host ""
Write-Host " [1] Deploy Complet (First Time Setup)" -ForegroundColor Cyan
Write-Host " [2] Install Service Only" -ForegroundColor Cyan
Write-Host " [3] Uninstall Service" -ForegroundColor Cyan
Write-Host ""
Write-Host " [4] Start Service" -ForegroundColor Green
Write-Host " [5] Stop Service" -ForegroundColor Yellow
Write-Host " [6] Restart Service" -ForegroundColor Magenta
Write-Host " [7] Service Status" -ForegroundColor White
Write-Host ""
Write-Host " [8] View Logs (Interactive)" -ForegroundColor White
Write-Host " [9] Quick Log Tail (Stdout)" -ForegroundColor White
Write-Host ""
Write-Host " Development & Manual Testing:" -ForegroundColor DarkCyan
Write-Host " [S] Setup Development Environment" -ForegroundColor Cyan
Write-Host " [R] Run Scraper (Manual)" -ForegroundColor Cyan
Write-Host " [T] Run Telegram Bot (Manual)" -ForegroundColor Cyan
Write-Host ""
Write-Host " [A] Open Deployment README" -ForegroundColor Gray
Write-Host " [B] Open Quick Start Guide" -ForegroundColor Gray
Write-Host " [C] Open Project in Explorer" -ForegroundColor Gray
Write-Host " [D] Open Logs in Explorer" -ForegroundColor Gray
Write-Host " [U] Update Browsers (Playwright)" -ForegroundColor Gray
Write-Host ""
Write-Host " [0] Exit" -ForegroundColor DarkGray
Write-Host ""
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host ""
}
function Invoke-Deploy {
Clear-Host
Write-Host ""
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host "RULARE DEPLOY COMPLET" -ForegroundColor Yellow
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host ""
& "$ScriptDir\deploy.ps1"
Read-Host "`nApasa Enter pentru a reveni la meniu"
}
function Invoke-Install {
Clear-Host
Write-Host ""
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host "INSTALARE SERVICIU" -ForegroundColor Yellow
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host ""
& "$ScriptDir\install_service.ps1"
Read-Host "`nApasa Enter pentru a reveni la meniu"
}
function Invoke-Uninstall {
Clear-Host
Write-Host ""
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host "DEZINSTALARE SERVICIU" -ForegroundColor Yellow
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host ""
& "$ScriptDir\uninstall_service.ps1"
Read-Host "`nApasa Enter pentru a reveni la meniu"
}
function Invoke-Start {
Clear-Host
Write-Host ""
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host "START SERVICIU" -ForegroundColor Yellow
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host ""
$service = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
if (-not $service) {
Write-Host "[EROARE] Serviciul $ServiceName nu este instalat!" -ForegroundColor Red
Start-Sleep -Seconds 2
return
}
Write-Host "[INFO] Pornire serviciu..." -ForegroundColor Cyan
try {
Start-Service -Name $ServiceName
Write-Host ""
Write-Host "[SUCCES] Serviciu pornit!" -ForegroundColor Green
} catch {
Write-Host ""
Write-Host "[EROARE] Pornirea a esuat: $_" -ForegroundColor Red
}
Start-Sleep -Seconds 3
}
function Invoke-Stop {
Clear-Host
Write-Host ""
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host "STOP SERVICIU" -ForegroundColor Yellow
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host ""
$service = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
if (-not $service) {
Write-Host "[EROARE] Serviciul $ServiceName nu este instalat!" -ForegroundColor Red
Start-Sleep -Seconds 2
return
}
Write-Host "[INFO] Oprire serviciu..." -ForegroundColor Cyan
try {
Stop-Service -Name $ServiceName -Force
Write-Host ""
Write-Host "[SUCCES] Serviciu oprit!" -ForegroundColor Green
} catch {
Write-Host ""
Write-Host "[INFO] Serviciul era deja oprit" -ForegroundColor Yellow
}
Start-Sleep -Seconds 3
}
function Invoke-Restart {
Clear-Host
Write-Host ""
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host "RESTART SERVICIU" -ForegroundColor Yellow
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host ""
& "$ScriptDir\restart_service.ps1"
Read-Host "`nApasa Enter pentru a reveni la meniu"
}
function Invoke-Status {
Clear-Host
Write-Host ""
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host "STATUS SERVICIU" -ForegroundColor Yellow
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host ""
& "$ScriptDir\status.ps1"
Read-Host "`nApasa Enter pentru a reveni la meniu"
}
function Invoke-ViewLogs {
Clear-Host
& "$ScriptDir\view_logs.ps1"
}
function Invoke-TailLogs {
Clear-Host
Write-Host ""
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host "LOG TAIL - STDOUT (LIVE)" -ForegroundColor Yellow
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host "Apasa Ctrl+C pentru a opri" -ForegroundColor Yellow
Write-Host ""
$stdoutLog = Join-Path $LogDir "telegram_bot_stdout.log"
if (Test-Path $stdoutLog) {
Get-Content $stdoutLog -Wait -Tail 30
} else {
Write-Host "[INFO] Log-ul nu exista inca" -ForegroundColor Yellow
Start-Sleep -Seconds 2
}
}
function Open-ReadmeFile {
$readme = Join-Path $ProjectDir "deployment\windows\README.md"
if (Test-Path $readme) {
Start-Process $readme
}
}
function Open-QuickStartFile {
$quickstart = Join-Path $ProjectDir "deployment\windows\QUICK_START.md"
if (Test-Path $quickstart) {
Start-Process $quickstart
}
}
function Open-ProjectExplorer {
Start-Process explorer $ProjectDir
}
function Open-LogsExplorer {
if (Test-Path $LogDir) {
Start-Process explorer $LogDir
} else {
Write-Host "[INFO] Directorul logs nu exista inca" -ForegroundColor Yellow
Start-Sleep -Seconds 2
}
}
function Invoke-SetupDev {
Clear-Host
Write-Host ""
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host "SETUP DEVELOPMENT ENVIRONMENT" -ForegroundColor Yellow
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host ""
& "$ScriptDir\setup_dev.ps1"
}
function Invoke-RunScraper {
Clear-Host
Write-Host ""
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host "RUN SCRAPER (MANUAL MODE)" -ForegroundColor Yellow
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host ""
& "$ScriptDir\run_scraper.ps1"
}
function Invoke-RunTelegramBotManual {
Clear-Host
Write-Host ""
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host "RUN TELEGRAM BOT (MANUAL MODE)" -ForegroundColor Yellow
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host ""
& "$ScriptDir\run_telegram_bot_manual.ps1"
}
function Invoke-UpdateBrowsers {
Clear-Host
Write-Host ""
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host "UPDATE PLAYWRIGHT BROWSERS" -ForegroundColor Yellow
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host ""
& "$ScriptDir\update_browsers.ps1"
Read-Host "`nApasa Enter pentru a reveni la meniu"
}
# Main loop
do {
Show-MainMenu
$choice = Read-Host "Selecteaza optiune"
switch ($choice) {
"1" { Invoke-Deploy }
"2" { Invoke-Install }
"3" { Invoke-Uninstall }
"4" { Invoke-Start }
"5" { Invoke-Stop }
"6" { Invoke-Restart }
"7" { Invoke-Status }
"8" { Invoke-ViewLogs }
"9" { Invoke-TailLogs }
"A" { Open-ReadmeFile }
"a" { Open-ReadmeFile }
"B" { Open-QuickStartFile }
"b" { Open-QuickStartFile }
"C" { Open-ProjectExplorer }
"c" { Open-ProjectExplorer }
"D" { Open-LogsExplorer }
"d" { Open-LogsExplorer }
"S" { Invoke-SetupDev }
"s" { Invoke-SetupDev }
"R" { Invoke-RunScraper }
"r" { Invoke-RunScraper }
"T" { Invoke-RunTelegramBotManual }
"t" { Invoke-RunTelegramBotManual }
"U" { Invoke-UpdateBrowsers }
"u" { Invoke-UpdateBrowsers }
"0" {
Write-Host ""
Write-Host "Goodbye!" -ForegroundColor Green
exit 0
}
default {
Write-Host "[EROARE] Optiune invalida!" -ForegroundColor Red
Start-Sleep -Seconds 1
}
}
} while ($true)

View File

@@ -0,0 +1,87 @@
#Requires -RunAsAdministrator
<#
.SYNOPSIS
BTGO Telegram Bot - Service Restart Script
.DESCRIPTION
Restart rapid al serviciului
#>
$ErrorActionPreference = "Stop"
$ServiceName = "BTGOTelegramBot"
function Write-ColorOutput {
param([string]$Message, [string]$Prefix = "")
$color = switch ($Prefix) {
"[OK]" { "Green" }
"[INFO]" { "Cyan" }
"[EROARE]" { "Red" }
default { "White" }
}
if ($Prefix) {
Write-Host "$Prefix " -ForegroundColor $color -NoNewline
Write-Host $Message
} else {
Write-Host $Message
}
}
function Write-Separator {
Write-Host ""
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host "RESTART SERVICIU: $ServiceName" -ForegroundColor Yellow
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host ""
}
Write-Separator
# Verifică dacă serviciul există
$service = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
if (-not $service) {
Write-ColorOutput "Serviciul $ServiceName nu este instalat!" -Prefix "[EROARE]"
Read-Host "`nApasa Enter pentru a inchide"
exit 1
}
# Oprește serviciul
Write-ColorOutput "Oprire serviciu..." -Prefix "[INFO]"
try {
Stop-Service -Name $ServiceName -Force
Write-ColorOutput "Serviciu oprit" -Prefix "[OK]"
} catch {
Write-ColorOutput "Oprirea a esuat. Incercam oprire fortata..." -Prefix "[AVERTIZARE]"
sc.exe stop $ServiceName | Out-Null
}
Start-Sleep -Seconds 2
# Pornește serviciul
Write-ColorOutput "Pornire serviciu..." -Prefix "[INFO]"
try {
Start-Service -Name $ServiceName
Write-ColorOutput "Serviciu pornit" -Prefix "[OK]"
} catch {
Write-ColorOutput "Pornirea serviciului a esuat!" -Prefix "[EROARE]"
Write-Host "Verificati logurile pentru detalii"
Read-Host "`nApasa Enter pentru a inchide"
exit 1
}
Start-Sleep -Seconds 2
# Verifică status
$service = Get-Service -Name $ServiceName
if ($service.Status -eq "Running") {
Write-Host ""
Write-ColorOutput "[SUCCES] Serviciu restartat cu succes!" -ForegroundColor Green
Write-Host ""
Get-Service -Name $ServiceName | Format-Table -AutoSize
} else {
Write-Host ""
Write-ColorOutput "Serviciul nu ruleaza dupa restart!" -Prefix "[EROARE]"
Get-Service -Name $ServiceName | Format-Table -AutoSize
}
Read-Host "`nApasa Enter pentru a inchide"

View File

@@ -0,0 +1,111 @@
<#
.SYNOPSIS
BTGO Scraper - Rulare Development
.DESCRIPTION
Ruleaza scraper-ul BTGO in modul development (cu browser vizibil)
.NOTES
Echivalent PowerShell pentru scripts\run_dev.bat
Rulare: Right-click → "Run with PowerShell"
#>
$ErrorActionPreference = "Stop"
$ScriptDir = Split-Path -Parent $PSCommandPath
$ProjectDir = Resolve-Path (Join-Path $ScriptDir "..\..\..") | Select-Object -ExpandProperty Path
Set-Location $ProjectDir
Write-Host ""
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host " BTGO SCRAPER - RULARE DEVELOPMENT" -ForegroundColor Yellow
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host ""
Write-Host "Working directory: $ProjectDir" -ForegroundColor Gray
Write-Host ""
# Verifică dacă venv există
$venvPath = Join-Path $ProjectDir ".venv"
$activateScript = Join-Path $venvPath "Scripts\Activate.ps1"
if (-not (Test-Path $activateScript)) {
Write-Host "[ERROR] Virtual environment nu exista sau este incomplet!" -ForegroundColor Red
Write-Host "Ruleaza intai: setup_dev.ps1" -ForegroundColor Yellow
Write-Host ""
Read-Host "Apasa Enter pentru a inchide"
exit 1
}
# Activează venv
Write-Host "Activare virtual environment..." -ForegroundColor Cyan
try {
& $activateScript
Write-Host "[OK] Virtual environment activat." -ForegroundColor Green
Write-Host ""
} catch {
Write-Host "[ERROR] Nu am putut activa venv!" -ForegroundColor Red
Write-Host "Eroare: $_" -ForegroundColor Red
Write-Host ""
Read-Host "Apasa Enter pentru a inchide"
exit 1
}
# Verifică dacă .env există
$envFile = Join-Path $ProjectDir ".env"
$envExample = Join-Path $ProjectDir ".env.example"
if (-not (Test-Path $envFile)) {
Write-Host "[WARNING] Fisierul .env nu exista!" -ForegroundColor Yellow
if (Test-Path $envExample) {
Write-Host "Copiez din .env.example..." -ForegroundColor Yellow
Copy-Item $envExample $envFile
Write-Host ""
Write-Host "IMPORTANT: Editeaza .env cu credentialele tale!" -ForegroundColor Yellow
Start-Process notepad $envFile -Wait
Write-Host ""
Read-Host "Apasa Enter dupa ce salvezi .env"
} else {
Write-Host "[ERROR] .env.example nu exista!" -ForegroundColor Red
Write-Host ""
Read-Host "Apasa Enter pentru a inchide"
exit 1
}
}
# Verifică directoare output
$dataDir = Join-Path $ProjectDir "data"
$logsDir = Join-Path $ProjectDir "logs"
if (-not (Test-Path $dataDir)) {
New-Item -ItemType Directory -Path $dataDir | Out-Null
}
if (-not (Test-Path $logsDir)) {
New-Item -ItemType Directory -Path $logsDir | Out-Null
}
# Rulează scraper
Write-Host ""
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host "Pornire scraper..." -ForegroundColor Yellow
Write-Host "Browser-ul va fi vizibil pentru debugging." -ForegroundColor Gray
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host ""
try {
& python btgo_scraper.py
} catch {
Write-Host ""
Write-Host "[ERROR] Executie esuata: $_" -ForegroundColor Red
Write-Host ""
Read-Host "Apasa Enter pentru a inchide"
exit 1
}
Write-Host ""
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host "Executie terminata." -ForegroundColor Green
Write-Host "Verifica rezultatele in:" -ForegroundColor Cyan
Write-Host " - data\solduri_*.csv" -ForegroundColor Gray
Write-Host " - data\tranzactii_*.csv" -ForegroundColor Gray
Write-Host " - logs\scraper_*.log" -ForegroundColor Gray
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host ""
Read-Host "Apasa Enter pentru a inchide"

View File

@@ -0,0 +1,81 @@
<#
.SYNOPSIS
Telegram Trigger Bot pentru BTGO - Rulare Manuala
.DESCRIPTION
Ruleaza Telegram bot-ul in modul manual (nu ca serviciu Windows)
.NOTES
Echivalent PowerShell pentru scripts\run_telegram_bot.bat
Rulare: Right-click → "Run with PowerShell"
Pentru a opri: Ctrl+C
#>
$ErrorActionPreference = "Stop"
$ScriptDir = Split-Path -Parent $PSCommandPath
$ProjectDir = Resolve-Path (Join-Path $ScriptDir "..\..\..") | Select-Object -ExpandProperty Path
Set-Location $ProjectDir
Write-Host ""
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host " TELEGRAM TRIGGER BOT PENTRU BTGO" -ForegroundColor Yellow
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host ""
Write-Host "Working directory: $ProjectDir" -ForegroundColor Gray
Write-Host ""
# Verifică dacă venv există
$venvPath = Join-Path $ProjectDir ".venv"
$activateScript = Join-Path $venvPath "Scripts\Activate.ps1"
if (-not (Test-Path $activateScript)) {
Write-Host "[ERROR] Virtual environment nu exista!" -ForegroundColor Red
Write-Host "Ruleaza intai: setup_dev.ps1" -ForegroundColor Yellow
Write-Host ""
Read-Host "Apasa Enter pentru a inchide"
exit 1
}
# Activează venv
Write-Host "Activare virtual environment..." -ForegroundColor Cyan
try {
& $activateScript
Write-Host "[OK] Virtual environment activat." -ForegroundColor Green
Write-Host ""
} catch {
Write-Host "[ERROR] Nu am putut activa venv!" -ForegroundColor Red
Write-Host "Eroare: $_" -ForegroundColor Red
Write-Host ""
Read-Host "Apasa Enter pentru a inchide"
exit 1
}
# Verifică .env
$envFile = Join-Path $ProjectDir ".env"
if (-not (Test-Path $envFile)) {
Write-Host "[ERROR] Fisierul .env nu exista!" -ForegroundColor Red
Write-Host "Configureaza .env cu TELEGRAM_BOT_TOKEN si TELEGRAM_ALLOWED_USER_IDS" -ForegroundColor Yellow
Write-Host ""
Read-Host "Apasa Enter pentru a inchide"
exit 1
}
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host "Pornire Telegram Bot..." -ForegroundColor Yellow
Write-Host "Bot-ul va astepta comenzi /scrape" -ForegroundColor Gray
Write-Host "Apasa Ctrl+C pentru a opri bot-ul" -ForegroundColor Yellow
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host ""
try {
& python telegram_trigger_bot.py
} catch {
Write-Host ""
Write-Host "[ERROR] Executie esuata: $_" -ForegroundColor Red
Write-Host ""
Read-Host "Apasa Enter pentru a inchide"
exit 1
}
Write-Host ""
Read-Host "Apasa Enter pentru a inchide"

View File

@@ -0,0 +1,172 @@
<#
.SYNOPSIS
BTGO Scraper - Setup Development Environment
.DESCRIPTION
Configureaza mediul de dezvoltare local (venv, dependente, browsere)
.NOTES
Echivalent PowerShell pentru scripts\setup_windows.bat
Rulare: Right-click → "Run with PowerShell"
#>
$ErrorActionPreference = "Stop"
$ScriptDir = Split-Path -Parent $PSCommandPath
$ProjectDir = Resolve-Path (Join-Path $ScriptDir "..\..\..") | Select-Object -ExpandProperty Path
Set-Location $ProjectDir
Write-Host ""
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host " BTGO SCRAPER - SETUP WINDOWS" -ForegroundColor Yellow
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host ""
Write-Host "Working directory: $ProjectDir" -ForegroundColor Gray
Write-Host ""
# Verifică Python
try {
$pythonVersion = & python --version 2>&1
Write-Host "[OK] Python gasit: $pythonVersion" -ForegroundColor Green
Write-Host ""
} catch {
Write-Host "[ERROR] Python nu este instalat!" -ForegroundColor Red
Write-Host "Descarca Python de la: https://www.python.org/downloads/" -ForegroundColor Yellow
Write-Host "Asigura-te ca bifezi 'Add Python to PATH' la instalare." -ForegroundColor Yellow
Write-Host ""
Read-Host "Apasa Enter pentru a inchide"
exit 1
}
# Verifică dacă venv există deja
$venvPath = Join-Path $ProjectDir ".venv"
if (Test-Path (Join-Path $venvPath "Scripts\Activate.ps1")) {
Write-Host "[INFO] Virtual environment exista deja." -ForegroundColor Yellow
$recreate = Read-Host "Doresti sa il stergi si sa recreezi? (y/N)"
if ($recreate -eq "y" -or $recreate -eq "Y") {
Write-Host "Stergere venv existent..." -ForegroundColor Yellow
Remove-Item -Path $venvPath -Recurse -Force
} else {
# Skip la activare
goto ActivateVenv
}
}
# Creează virtual environment
Write-Host "[STEP 1/5] Creare virtual environment..." -ForegroundColor Cyan
try {
& python -m venv .venv
Write-Host "[OK] Virtual environment creat." -ForegroundColor Green
Write-Host ""
} catch {
Write-Host "[ERROR] Nu am putut crea venv!" -ForegroundColor Red
Write-Host "Eroare: $_" -ForegroundColor Red
Write-Host ""
Read-Host "Apasa Enter pentru a inchide"
exit 1
}
:ActivateVenv
# Activează venv
Write-Host "[STEP 2/5] Activare virtual environment..." -ForegroundColor Cyan
$activateScript = Join-Path $venvPath "Scripts\Activate.ps1"
if (-not (Test-Path $activateScript)) {
Write-Host "[ERROR] Nu am gasit scriptul de activare: $activateScript" -ForegroundColor Red
Write-Host ""
Read-Host "Apasa Enter pentru a inchide"
exit 1
}
try {
& $activateScript
Write-Host "[OK] Virtual environment activat." -ForegroundColor Green
Write-Host ""
} catch {
Write-Host "[ERROR] Nu am putut activa venv!" -ForegroundColor Red
Write-Host "Eroare: $_" -ForegroundColor Red
Write-Host ""
Read-Host "Apasa Enter pentru a inchide"
exit 1
}
# Instalează requirements
Write-Host "[STEP 3/5] Instalare dependente Python..." -ForegroundColor Cyan
try {
& pip install --upgrade pip --quiet
& pip install -r requirements.txt
Write-Host "[OK] Dependente instalate." -ForegroundColor Green
Write-Host ""
} catch {
Write-Host "[ERROR] Instalare dependente esuata!" -ForegroundColor Red
Write-Host "Eroare: $_" -ForegroundColor Red
Write-Host ""
Read-Host "Apasa Enter pentru a inchide"
exit 1
}
# Instalează browsere Playwright
Write-Host "[STEP 4/5] Instalare browsere Playwright (Chromium)..." -ForegroundColor Cyan
Write-Host "Acest pas poate dura cateva minute..." -ForegroundColor Yellow
try {
& playwright install chromium
Write-Host "[OK] Browsere instalate." -ForegroundColor Green
Write-Host ""
} catch {
Write-Host "[ERROR] Instalare browsere esuata!" -ForegroundColor Red
Write-Host "Eroare: $_" -ForegroundColor Red
Write-Host ""
Read-Host "Apasa Enter pentru a inchide"
exit 1
}
# Copiază .env.example la .env dacă nu există
Write-Host "[STEP 5/5] Verificare configuratie..." -ForegroundColor Cyan
$envFile = Join-Path $ProjectDir ".env"
$envExample = Join-Path $ProjectDir ".env.example"
if (-not (Test-Path $envFile)) {
if (Test-Path $envExample) {
Write-Host "Creare fisier .env din template..." -ForegroundColor Yellow
Copy-Item $envExample $envFile
Write-Host "[INFO] Fisierul .env a fost creat." -ForegroundColor Green
Write-Host ""
Write-Host "IMPORTANT: Editeaza .env si completeaza:" -ForegroundColor Yellow
Write-Host " - BTGO_USERNAME=your_username" -ForegroundColor Gray
Write-Host " - BTGO_PASSWORD=your_password" -ForegroundColor Gray
Write-Host ""
} else {
Write-Host "[ERROR] .env.example nu exista!" -ForegroundColor Red
Write-Host ""
Read-Host "Apasa Enter pentru a inchide"
exit 1
}
} else {
Write-Host "[OK] Fisierul .env exista deja." -ForegroundColor Green
}
# Verifică directoarele
$dataDir = Join-Path $ProjectDir "data"
$logsDir = Join-Path $ProjectDir "logs"
if (-not (Test-Path $dataDir)) {
New-Item -ItemType Directory -Path $dataDir | Out-Null
}
if (-not (Test-Path $logsDir)) {
New-Item -ItemType Directory -Path $logsDir | Out-Null
}
Write-Host ""
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host " SETUP COMPLET!" -ForegroundColor Green
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host ""
Write-Host "Next steps:" -ForegroundColor Cyan
Write-Host " 1. Editeaza .env cu credentialele tale" -ForegroundColor Gray
Write-Host " 2. Ruleaza scriptul run_scraper.ps1 sau run_telegram_bot.ps1" -ForegroundColor Gray
Write-Host ""
Write-Host "Pentru debugging selectors (optional):" -ForegroundColor Cyan
Write-Host " .venv\Scripts\Activate.ps1" -ForegroundColor Gray
Write-Host " playwright codegen https://btgo.ro --target python" -ForegroundColor Gray
Write-Host ""
Write-Host "Pentru detalii, vezi README.md" -ForegroundColor Gray
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host ""
Read-Host "Apasa Enter pentru a inchide"

View File

@@ -0,0 +1,65 @@
#Requires -RunAsAdministrator
<#
.SYNOPSIS
BTGO Telegram Bot - Start Service Script
.DESCRIPTION
Pornește serviciul Windows
#>
$ErrorActionPreference = "Stop"
$ServiceName = "BTGOTelegramBot"
function Write-ColorOutput {
param([string]$Message, [string]$Prefix = "")
$color = switch ($Prefix) {
"[OK]" { "Green" }
"[INFO]" { "Cyan" }
"[EROARE]" { "Red" }
default { "White" }
}
if ($Prefix) {
Write-Host "$Prefix " -ForegroundColor $color -NoNewline
Write-Host $Message
} else {
Write-Host $Message
}
}
Write-Host ""
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host "START SERVICIU: $ServiceName" -ForegroundColor Yellow
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host ""
# Verifică dacă serviciul există
$service = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
if (-not $service) {
Write-ColorOutput "Serviciul $ServiceName nu este instalat!" -Prefix "[EROARE]"
Read-Host "`nApasa Enter pentru a inchide"
exit 1
}
# Pornește serviciul
Write-ColorOutput "Pornire serviciu..." -Prefix "[INFO]"
try {
Start-Service -Name $ServiceName
Write-ColorOutput "Serviciu pornit cu succes!" -Prefix "[OK]"
Start-Sleep -Seconds 2
# Verifică status
$service = Get-Service -Name $ServiceName
Write-Host ""
Get-Service -Name $ServiceName | Format-Table -AutoSize
} catch {
Write-ColorOutput "Pornirea serviciului a esuat!" -Prefix "[EROARE]"
Write-Host "Eroare: $_" -ForegroundColor Red
Write-Host ""
Write-Host "Verificati logurile:" -ForegroundColor Yellow
Write-Host " Get-Content logs\telegram_bot_stderr.log"
Read-Host "`nApasa Enter pentru a inchide"
exit 1
}
Read-Host "`nApasa Enter pentru a inchide"

View File

@@ -0,0 +1,101 @@
<#
.SYNOPSIS
BTGO Telegram Bot - Service Status Script
.DESCRIPTION
Afiseaza status detaliat al serviciului
#>
$ServiceName = "BTGOTelegramBot"
$ScriptDir = Split-Path -Parent $PSCommandPath
$ProjectDir = Resolve-Path (Join-Path $ScriptDir "..\..\..") | Select-Object -ExpandProperty Path
$LogDir = Join-Path $ProjectDir "logs"
function Write-ColorOutput {
param([string]$Message, [string]$Prefix = "")
$color = switch ($Prefix) {
"[OK]" { "Green" }
"[INFO]" { "Cyan" }
"[AVERTIZARE]" { "Yellow" }
default { "White" }
}
if ($Prefix) {
Write-Host "$Prefix " -ForegroundColor $color -NoNewline
Write-Host $Message
} else {
Write-Host $Message -ForegroundColor $color
}
}
function Write-Separator {
param([string]$Title)
Write-Host ""
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host $Title -ForegroundColor Yellow
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host ""
}
Write-Separator "BTGO TELEGRAM BOT - STATUS SERVICIU"
# Verifică dacă serviciul există
$service = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
if (-not $service) {
Write-ColorOutput "Serviciul $ServiceName NU este instalat" -Prefix "[INFO]"
Write-Host ""
Write-Host "Pentru instalare, rulati:" -ForegroundColor Yellow
Write-Host " .\deploy.ps1"
Write-Host ""
Read-Host "Apasa Enter pentru a inchide"
exit 0
}
# Afișează informații serviciu
Write-Host "[STATUS SERVICIU]" -ForegroundColor Yellow
$service | Format-Table -Property Status, Name, DisplayName, StartType -AutoSize
# Afișează configurație detaliată
Write-Host "[CONFIGURATIE SERVICIU]" -ForegroundColor Yellow
Get-WmiObject -Class Win32_Service -Filter "Name='$ServiceName'" |
Format-List Name, DisplayName, State, StartMode, PathName
# Verifică ultimele logs
$stdoutLog = Join-Path $LogDir "telegram_bot_stdout.log"
if (Test-Path $stdoutLog) {
Write-Host "[ULTIMELE 10 LINII DIN LOG]" -ForegroundColor Yellow
Get-Content $stdoutLog -Tail 10
Write-Host ""
} else {
Write-ColorOutput "Log stdout nu exista inca" -Prefix "[INFO]"
Write-Host ""
}
# Verifică erori
$stderrLog = Join-Path $LogDir "telegram_bot_stderr.log"
if (Test-Path $stderrLog) {
$errLines = (Get-Content $stderrLog).Count
if ($errLines -gt 0) {
Write-ColorOutput "Exista erori in stderr log ($errLines linii)" -Prefix "[AVERTIZARE]"
Write-Host "Ultimele 5 erori:"
Get-Content $stderrLog -Tail 5
Write-Host ""
}
}
# Comenzi utile
Write-Host "[COMENZI UTILE]" -ForegroundColor Yellow
Write-Host " - Restart: .\restart_service.ps1"
Write-Host " - View logs: .\view_logs.ps1"
Write-Host " - Dezinstaleaza: .\uninstall_service.ps1"
Write-Host " - Redeploy: .\deploy.ps1"
Write-Host ""
# PowerShell Quick Commands
Write-Host "[POWERSHELL COMMANDS]" -ForegroundColor Yellow
Write-Host " - Restart-Service $ServiceName"
Write-Host " - Stop-Service $ServiceName"
Write-Host " - Start-Service $ServiceName"
Write-Host " - Get-Content '$stdoutLog' -Wait -Tail 20"
Write-Host ""
Read-Host "Apasa Enter pentru a inchide"

View File

@@ -0,0 +1,133 @@
#Requires -RunAsAdministrator
<#
.SYNOPSIS
BTGO Telegram Bot - Service Uninstall Script
.DESCRIPTION
Dezinstaleaza serviciul Windows BTGO Telegram Bot
.NOTES
Rulare: Right-click → "Run with PowerShell" (ca Administrator)
#>
$ErrorActionPreference = "Stop"
# ============================================================================
# CONFIGURATION
# ============================================================================
$ServiceName = "BTGOTelegramBot"
$ScriptDir = Split-Path -Parent $PSCommandPath
$ProjectDir = Resolve-Path (Join-Path $ScriptDir "..\..\..") | Select-Object -ExpandProperty Path
$NssmPath = Join-Path $ProjectDir "deployment\windows\tools\nssm.exe"
function Write-ColorOutput {
param([string]$Message, [string]$Prefix = "")
$color = switch ($Prefix) {
"[OK]" { "Green" }
"[INFO]" { "Cyan" }
"[EROARE]" { "Red" }
"[AVERTIZARE]" { "Yellow" }
default { "White" }
}
if ($Prefix) {
Write-Host "$Prefix " -ForegroundColor $color -NoNewline
Write-Host $Message
} else {
Write-Host $Message
}
}
function Write-Separator {
param([string]$Title = "")
Write-Host ""
Write-Host ("=" * 80) -ForegroundColor Cyan
if ($Title) {
Write-Host $Title -ForegroundColor Yellow
Write-Host ("=" * 80) -ForegroundColor Cyan
}
}
# ============================================================================
# VERIFICARE SERVICIU
# ============================================================================
Write-Separator "DEZINSTALARE SERVICIU: $ServiceName"
Write-Host ""
$service = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
if (-not $service) {
Write-ColorOutput "Serviciul $ServiceName nu este instalat." -Prefix "[INFO]"
Write-Host "Nimic de dezinstalat."
Read-Host "`nApasa Enter pentru a inchide"
exit 0
}
# ============================================================================
# OPRIRE SERVICIU
# ============================================================================
Write-ColorOutput "Verificare status serviciu..." -Prefix "[INFO]"
if ($service.Status -eq "Running") {
Write-ColorOutput "Serviciul ruleaza. Oprire..." -Prefix "[INFO]"
try {
Stop-Service -Name $ServiceName -Force
Write-ColorOutput "Serviciu oprit cu succes" -Prefix "[OK]"
Start-Sleep -Seconds 2
} catch {
Write-ColorOutput "Oprirea serviciului a esuat!" -Prefix "[AVERTIZARE]"
Write-ColorOutput "Incercam oprire fortata..." -Prefix "[INFO]"
sc.exe stop $ServiceName | Out-Null
Start-Sleep -Seconds 5
}
} else {
Write-ColorOutput "Serviciul este deja oprit" -Prefix "[INFO]"
}
# ============================================================================
# DEZINSTALARE SERVICIU
# ============================================================================
Write-ColorOutput "Dezinstalare serviciu..." -Prefix "[INFO]"
try {
if (Test-Path $NssmPath) {
# Folosește NSSM pentru dezinstalare
& $NssmPath remove $ServiceName confirm
if ($LASTEXITCODE -ne 0) { throw }
} else {
# Fallback la sc delete
Write-ColorOutput "NSSM nu a fost gasit, folosim sc delete" -Prefix "[INFO]"
sc.exe delete $ServiceName | Out-Null
if ($LASTEXITCODE -ne 0) { throw }
}
} catch {
Write-ColorOutput "Dezinstalarea serviciului a esuat!" -Prefix "[EROARE]"
Read-Host "`nApasa Enter pentru a inchide"
exit 1
}
# ============================================================================
# VERIFICARE FINALA
# ============================================================================
Start-Sleep -Seconds 2
$serviceCheck = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
if (-not $serviceCheck) {
Write-Separator "[SUCCES] SERVICIU DEZINSTALAT CU SUCCES!"
Write-Host ""
Write-Host "Serviciul $ServiceName a fost eliminat din sistem." -ForegroundColor Green
Write-Host ""
Write-Host "Pentru a reinstala serviciul, rulati:" -ForegroundColor Yellow
Write-Host " .\install_service.ps1"
Write-Host ""
Write-Separator
} else {
Write-ColorOutput "Serviciul pare sa mai existe in sistem!" -Prefix "[AVERTIZARE]"
Write-Host "Poate fi nevoie de un restart al sistemului."
Get-Service -Name $ServiceName | Format-List
}
Read-Host "`nApasa Enter pentru a inchide"

View File

@@ -0,0 +1,103 @@
#Requires -RunAsAdministrator
<#
.SYNOPSIS
Update Playwright Browsers (Global Location)
.DESCRIPTION
Actualizeaza browserele Playwright in locatia globala
Foloseste aceasta comanda pentru update-uri de browsere
#>
$ErrorActionPreference = "Stop"
$ServiceName = "BTGOTelegramBot"
function Write-ColorOutput {
param([string]$Message, [string]$Prefix = "")
$color = switch ($Prefix) {
"[OK]" { "Green" }
"[INFO]" { "Cyan" }
"[EROARE]" { "Red" }
"[AVERTIZARE]" { "Yellow" }
default { "White" }
}
if ($Prefix) {
Write-Host "$Prefix " -ForegroundColor $color -NoNewline
Write-Host $Message
} else {
Write-Host $Message -ForegroundColor $color
}
}
Write-Host ""
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host "UPDATE PLAYWRIGHT BROWSERS" -ForegroundColor Yellow
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host ""
# Verifică environment variable
$globalBrowserPath = [System.Environment]::GetEnvironmentVariable("PLAYWRIGHT_BROWSERS_PATH", [System.EnvironmentVariableTarget]::Machine)
if (-not $globalBrowserPath) {
$globalBrowserPath = "C:\playwright-browsers"
Write-ColorOutput "PLAYWRIGHT_BROWSERS_PATH nu este setat. Folosim: $globalBrowserPath" -Prefix "[AVERTIZARE]"
# Setează environment variable
[System.Environment]::SetEnvironmentVariable("PLAYWRIGHT_BROWSERS_PATH", $globalBrowserPath, [System.EnvironmentVariableTarget]::Machine)
Write-ColorOutput "Environment variable setata" -Prefix "[OK]"
}
$env:PLAYWRIGHT_BROWSERS_PATH = $globalBrowserPath
Write-ColorOutput "Locatie browsere: $globalBrowserPath" -Prefix "[INFO]"
# Creează director dacă nu există
if (-not (Test-Path $globalBrowserPath)) {
New-Item -ItemType Directory -Path $globalBrowserPath -Force | Out-Null
icacls $globalBrowserPath /grant "SYSTEM:(OI)(CI)F" /T 2>&1 | Out-Null
Write-ColorOutput "Director creat cu permisiuni SYSTEM" -Prefix "[OK]"
}
# Update browsere
Write-Host ""
Write-ColorOutput "Instalare/Update browsere Playwright..." -Prefix "[INFO]"
Write-Host "Aceasta poate dura 1-2 minute..." -ForegroundColor Gray
Write-Host ""
try {
python -m playwright install chromium
if ($LASTEXITCODE -ne 0) { throw }
Write-Host ""
Write-ColorOutput "Browsere actualizate cu succes!" -Prefix "[OK]"
} catch {
Write-ColorOutput "Update-ul browserelor a esuat!" -Prefix "[EROARE]"
Read-Host "`nApasa Enter pentru a inchide"
exit 1
}
# Restart serviciu dacă rulează
$service = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
if ($service) {
Write-Host ""
Write-ColorOutput "Restart serviciu..." -Prefix "[INFO]"
if ($service.Status -eq "Running") {
Restart-Service -Name $ServiceName -Force
Start-Sleep -Seconds 3
$service = Get-Service -Name $ServiceName
if ($service.Status -eq "Running") {
Write-ColorOutput "Serviciu restartat cu succes!" -Prefix "[OK]"
} else {
Write-ColorOutput "Serviciul nu a pornit" -Prefix "[AVERTIZARE]"
}
} else {
Write-ColorOutput "Serviciul nu rula" -Prefix "[INFO]"
}
}
Write-Host ""
Write-Host ("=" * 80) -ForegroundColor Green
Write-Host "[SUCCES] BROWSERE ACTUALIZATE!" -ForegroundColor Green
Write-Host ("=" * 80) -ForegroundColor Green
Read-Host "`nApasa Enter pentru a inchide"

View File

@@ -0,0 +1,137 @@
<#
.SYNOPSIS
BTGO Telegram Bot - View Logs Script
.DESCRIPTION
Afiseaza logurile serviciului in timp real
#>
$ScriptDir = Split-Path -Parent $PSCommandPath
$ProjectDir = Resolve-Path (Join-Path $ScriptDir "..\..\..") | Select-Object -ExpandProperty Path
$LogDir = Join-Path $ProjectDir "logs"
$StdoutLog = Join-Path $LogDir "telegram_bot_stdout.log"
$StderrLog = Join-Path $LogDir "telegram_bot_stderr.log"
function Show-Menu {
Clear-Host
Write-Host ""
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host " BTGO TELEGRAM BOT - VIEWER LOGS" -ForegroundColor Yellow
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host ""
Write-Host "Logs directory: $LogDir" -ForegroundColor Cyan
Write-Host ""
Write-Host "Selecteaza optiune:" -ForegroundColor Yellow
Write-Host ""
Write-Host " 1. Vizualizare stdout (output normal)"
Write-Host " 2. Vizualizare stderr (erori)"
Write-Host " 3. Tail stdout (timp real - ultim 30 linii)"
Write-Host " 4. Tail stderr (timp real - ultim 30 linii)"
Write-Host " 5. Deschide director logs in Explorer"
Write-Host " 6. Sterge toate logurile"
Write-Host " 0. Iesire"
Write-Host ""
}
function View-Stdout {
Clear-Host
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host "STDOUT LOG" -ForegroundColor Yellow
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host ""
if (Test-Path $StdoutLog) {
Get-Content $StdoutLog
} else {
Write-Host "[INFO] Log-ul nu exista inca" -ForegroundColor Yellow
}
Write-Host ""
Read-Host "Apasa Enter pentru a reveni la meniu"
}
function View-Stderr {
Clear-Host
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host "STDERR LOG" -ForegroundColor Yellow
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host ""
if (Test-Path $StderrLog) {
Get-Content $StderrLog
} else {
Write-Host "[INFO] Log-ul nu exista inca" -ForegroundColor Yellow
}
Write-Host ""
Read-Host "Apasa Enter pentru a reveni la meniu"
}
function Tail-Stdout {
Clear-Host
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host "STDOUT TAIL - TIMP REAL (Apasa Ctrl+C pentru a opri)" -ForegroundColor Yellow
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host ""
if (Test-Path $StdoutLog) {
Get-Content $StdoutLog -Wait -Tail 30
} else {
Write-Host "[INFO] Log-ul nu exista inca" -ForegroundColor Yellow
Start-Sleep -Seconds 2
}
}
function Tail-Stderr {
Clear-Host
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host "STDERR TAIL - TIMP REAL (Apasa Ctrl+C pentru a opri)" -ForegroundColor Yellow
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host ""
if (Test-Path $StderrLog) {
Get-Content $StderrLog -Wait -Tail 30
} else {
Write-Host "[INFO] Log-ul nu exista inca" -ForegroundColor Yellow
Start-Sleep -Seconds 2
}
}
function Open-LogsExplorer {
if (Test-Path $LogDir) {
explorer $LogDir
} else {
Write-Host "[INFO] Directorul logs nu exista" -ForegroundColor Yellow
Start-Sleep -Seconds 2
}
}
function Clear-AllLogs {
Write-Host ""
Write-Host "[AVERTIZARE] Stergi TOATE logurile?" -ForegroundColor Yellow
$confirm = Read-Host "Confirma (Y/N)"
if ($confirm -eq "Y" -or $confirm -eq "y") {
Remove-Item (Join-Path $LogDir "*.log") -Force -ErrorAction SilentlyContinue
Write-Host "[OK] Loguri sterse" -ForegroundColor Green
} else {
Write-Host "[INFO] Anulat" -ForegroundColor Cyan
}
Start-Sleep -Seconds 2
}
# Main loop
do {
Show-Menu
$choice = Read-Host "Optiune (0-6)"
switch ($choice) {
"1" { View-Stdout }
"2" { View-Stderr }
"3" { Tail-Stdout }
"4" { Tail-Stderr }
"5" { Open-LogsExplorer }
"6" { Clear-AllLogs }
"0" {
Write-Host ""
Write-Host "Bye!" -ForegroundColor Green
exit 0
}
default {
Write-Host "[EROARE] Optiune invalida!" -ForegroundColor Red
Start-Sleep -Seconds 1
}
}
} while ($true)

2
deployment/windows/tools/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
# NSSM binaries (downloaded automatically)
nssm.exe

31
docker-compose.yml Normal file
View File

@@ -0,0 +1,31 @@
version: '3.8'
services:
btgo-scraper:
build: .
container_name: btgo-scraper
# Mount volumes pentru persistenta
volumes:
- ./data:/app/data
- ./logs:/app/logs
# Environment variables - citeste din .env
environment:
- BTGO_USERNAME=${BTGO_USERNAME}
- BTGO_PASSWORD=${BTGO_PASSWORD}
- HEADLESS=true
- TIMEOUT_2FA_SECONDS=${TIMEOUT_2FA_SECONDS:-120}
- SCREENSHOT_ON_ERROR=${SCREENSHOT_ON_ERROR:-true}
- LOG_LEVEL=${LOG_LEVEL:-INFO}
- TZ=Europe/Bucharest
# Restart policy (schimba la 'always' pentru rulare automata)
restart: "no"
# Resource limits (optional)
deploy:
resources:
limits:
cpus: '2'
memory: 2G

123
get_telegram_chat_id.py Normal file
View File

@@ -0,0 +1,123 @@
#!/usr/bin/env python3
"""
Helper script pentru obținerea Chat ID Telegram (pentru DM și grupuri)
"""
import os
import requests
from dotenv import load_dotenv
# Load environment
load_dotenv()
BOT_TOKEN = os.getenv('TELEGRAM_BOT_TOKEN')
if not BOT_TOKEN:
print("❌ TELEGRAM_BOT_TOKEN nu este setat în .env!")
print("\nAdaugă în .env:")
print("TELEGRAM_BOT_TOKEN=123456789:ABCdefGHIjklMNOpqrs")
exit(1)
def get_updates():
"""Preia ultimele update-uri de la bot"""
url = f"https://api.telegram.org/bot{BOT_TOKEN}/getUpdates"
response = requests.get(url)
return response.json()
def main():
print("=" * 60)
print(" Telegram Chat ID Helper")
print("=" * 60)
print()
print("📱 Instrucțiuni:")
print("1. Trimite /start către bot (DM)")
print(" SAU")
print("2. Trimite /start în grupul unde ai bot-ul")
print()
print("Apasă Enter după ce ai trimis mesajul...")
input()
print("\n🔍 Căutare mesaje...")
data = get_updates()
if not data.get('ok'):
print(f"❌ Eroare API: {data}")
return
results = data.get('result', [])
if not results:
print("❌ Niciun mesaj găsit!")
print("\nAsigură-te că:")
print("• Ai trimis /start către bot SAU în grup")
print("• Bot-ul este adăugat în grup (dacă folosești grup)")
print("• Token-ul este corect")
return
print(f"\n✅ Găsit {len(results)} mesaje!\n")
print("=" * 60)
# Procesează ultimele mesaje
seen_chats = {}
for update in results:
if 'message' in update:
msg = update['message']
chat = msg['chat']
chat_id = chat['id']
chat_type = chat['type']
# Evită duplicate
if chat_id in seen_chats:
continue
seen_chats[chat_id] = True
# Detalii chat
if chat_type == 'private':
# DM
user = msg['from']
print(f"📱 DM cu {user.get('first_name', 'Unknown')}")
print(f" User ID: {user['id']}")
print(f" Username: @{user.get('username', 'N/A')}")
print(f" Chat ID: {chat_id}")
elif chat_type in ['group', 'supergroup']:
# Grup
print(f"👥 Grup: {chat.get('title', 'Unknown')}")
print(f" Chat ID: {chat_id} ⚠️ NEGATIV pentru grupuri!")
print(f" Tip: {chat_type}")
# User care a trimis mesajul
user = msg['from']
print(f" Mesaj de la: @{user.get('username', 'Unknown')} (ID: {user['id']})")
print()
print("=" * 60)
print("\n💡 Pentru configurare .env:")
print()
# Recomandări
for chat_id, chat_data in seen_chats.items():
if chat_id < 0: # Grup
print(f"# Pentru grup (notificări + comenzi):")
print(f"TELEGRAM_CHAT_ID={chat_id}")
else: # DM
print(f"# Pentru DM:")
print(f"TELEGRAM_CHAT_ID={chat_id}")
print()
print("# User IDs autorizați (pot rula /scrape):")
print("TELEGRAM_ALLOWED_USER_IDS=", end="")
# Colectează user IDs unice
user_ids = set()
for update in results:
if 'message' in update:
user_id = update['message']['from']['id']
user_ids.add(user_id)
print(",".join(str(uid) for uid in sorted(user_ids)))
print()
print("=" * 60)
if __name__ == "__main__":
main()

1
logs/.gitkeep Normal file
View File

@@ -0,0 +1 @@
# Folder pentru log files

504
notifications.py Normal file
View File

@@ -0,0 +1,504 @@
"""
Notification module for BTGO Scraper
Handles email and Discord notifications with file attachments
"""
import smtplib
import logging
import zipfile
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders
from pathlib import Path
from datetime import datetime
from typing import List
import requests
class EmailNotifier:
"""Handles email notifications via SMTP"""
def __init__(self, config):
self.config = config
self.enabled = config.EMAIL_ENABLED
def send(self, files: List[str], account_count: int) -> bool:
"""
Send email with CSV attachments
Args:
files: List of file paths to attach
account_count: Number of accounts processed
Returns:
True if successful, False otherwise
"""
if not self.enabled:
logging.info("Email notifications disabled")
return False
try:
# Validate config
if not all([self.config.SMTP_SERVER, self.config.EMAIL_FROM, self.config.EMAIL_TO]):
logging.error("Email configuration incomplete")
return False
# Create message
msg = MIMEMultipart()
msg['From'] = self.config.EMAIL_FROM
msg['To'] = self.config.EMAIL_TO
msg['Subject'] = f'BTGO Scraper Results - {datetime.now().strftime("%Y-%m-%d %H:%M")}'
# Email body
body = self._create_email_body(files, account_count)
msg.attach(MIMEText(body, 'html'))
# Attach files
total_size = 0
for file_path in files:
if not Path(file_path).exists():
logging.warning(f"File not found for email: {file_path}")
continue
file_size = Path(file_path).stat().st_size
total_size += file_size
# Check size limit (10MB typical SMTP limit)
if total_size > 10 * 1024 * 1024:
logging.warning(f"Email attachments exceed 10MB, creating ZIP archive")
return self._send_with_zip(files, account_count)
with open(file_path, 'rb') as f:
part = MIMEBase('application', 'octet-stream')
part.set_payload(f.read())
encoders.encode_base64(part)
part.add_header(
'Content-Disposition',
f'attachment; filename={Path(file_path).name}'
)
msg.attach(part)
# Send email
logging.info(f"Sending email to {self.config.EMAIL_TO}...")
with smtplib.SMTP(self.config.SMTP_SERVER, self.config.SMTP_PORT) as server:
server.starttls()
if self.config.SMTP_USERNAME and self.config.SMTP_PASSWORD:
server.login(self.config.SMTP_USERNAME, self.config.SMTP_PASSWORD)
server.send_message(msg)
logging.info(f"✓ Email sent successfully to {self.config.EMAIL_TO}")
return True
except Exception as e:
logging.error(f"Failed to send email: {e}")
return False
def _send_with_zip(self, files: List[str], account_count: int) -> bool:
"""Send email with files compressed as ZIP"""
try:
# Create ZIP archive
timestamp = datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
zip_path = Path(self.config.OUTPUT_DIR) / f'btgo_export_{timestamp}.zip'
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
for file_path in files:
if Path(file_path).exists():
zipf.write(file_path, Path(file_path).name)
logging.info(f"Created ZIP archive: {zip_path}")
# Send with ZIP attachment instead
msg = MIMEMultipart()
msg['From'] = self.config.EMAIL_FROM
msg['To'] = self.config.EMAIL_TO
msg['Subject'] = f'BTGO Scraper Results (ZIP) - {datetime.now().strftime("%Y-%m-%d %H:%M")}'
body = self._create_email_body([str(zip_path)], account_count, is_zip=True)
msg.attach(MIMEText(body, 'html'))
with open(zip_path, 'rb') as f:
part = MIMEBase('application', 'zip')
part.set_payload(f.read())
encoders.encode_base64(part)
part.add_header('Content-Disposition', f'attachment; filename={zip_path.name}')
msg.attach(part)
with smtplib.SMTP(self.config.SMTP_SERVER, self.config.SMTP_PORT) as server:
server.starttls()
if self.config.SMTP_USERNAME and self.config.SMTP_PASSWORD:
server.login(self.config.SMTP_USERNAME, self.config.SMTP_PASSWORD)
server.send_message(msg)
logging.info(f"✓ Email with ZIP sent successfully to {self.config.EMAIL_TO}")
return True
except Exception as e:
logging.error(f"Failed to send email with ZIP: {e}")
return False
def _create_email_body(self, files: List[str], account_count: int, is_zip: bool = False) -> str:
"""Create HTML email body"""
file_list = '<br>'.join([f'{Path(f).name}' for f in files])
file_count = 1 if is_zip else len(files)
return f"""
<html>
<body style="font-family: Arial, sans-serif;">
<h2 style="color: #2c3e50;">BTGO Scraper Results</h2>
<p><strong>Execution time:</strong> {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p>
<p><strong>Accounts processed:</strong> {account_count}</p>
<p><strong>Files attached:</strong> {file_count}</p>
<hr>
<h3>{'Archive contents:' if is_zip else 'Attached files:'}</h3>
{file_list}
<hr>
<p style="color: #7f8c8d; font-size: 12px;">
This email was automatically generated by BTGO Scraper.<br>
For issues, check the logs at your deployment location.
</p>
</body>
</html>
"""
class TelegramNotifier:
"""Handles Telegram notifications via Bot API"""
def __init__(self, config):
self.config = config
self.enabled = config.TELEGRAM_ENABLED
self.bot_token = config.TELEGRAM_BOT_TOKEN
self.chat_id = config.TELEGRAM_CHAT_ID
self.max_file_size = 50 * 1024 * 1024 # 50MB Telegram limit
def send(self, files: List[str], accounts: list, telegram_message_id: str = None, telegram_chat_id: str = None) -> bool:
"""
Send Telegram message with file attachments
Args:
files: List of file paths to attach
accounts: List of account data with balances
telegram_message_id: Message ID to edit (for progress updates)
telegram_chat_id: Chat ID to edit (for progress updates)
Returns:
True if successful, False otherwise
"""
if not self.enabled:
logging.info("Telegram notifications disabled")
return False
try:
if not self.bot_token or not self.chat_id:
logging.error("Telegram bot token or chat ID not configured")
return False
# Store message IDs for editing
self.progress_message_id = telegram_message_id
self.progress_chat_id = telegram_chat_id or self.chat_id
logging.info(f"Received telegram_message_id: {telegram_message_id}, telegram_chat_id: {telegram_chat_id}")
logging.info(f"Stored progress_message_id: {self.progress_message_id}, progress_chat_id: {self.progress_chat_id}")
# Check total file size
total_size = sum(Path(f).stat().st_size for f in files if Path(f).exists())
if total_size > self.max_file_size:
logging.warning(f"Files exceed Telegram 50MB limit, creating ZIP archive")
return self._send_with_zip(files, accounts)
else:
return self._send_files(files, accounts)
except Exception as e:
logging.error(f"Failed to send Telegram notification: {e}")
return False
def _send_message(self, text: str) -> bool:
"""Send text message to Telegram"""
try:
url = f"https://api.telegram.org/bot{self.bot_token}/sendMessage"
payload = {
'chat_id': self.chat_id,
'text': text,
'parse_mode': 'HTML'
}
response = requests.post(url, json=payload)
return response.status_code == 200
except Exception as e:
logging.error(f"Failed to send Telegram message: {e}")
return False
def _edit_message(self, chat_id: str, message_id: str, text: str) -> bool:
"""Edit existing Telegram message (for progress updates)"""
try:
url = f"https://api.telegram.org/bot{self.bot_token}/editMessageText"
payload = {
'chat_id': chat_id,
'message_id': message_id,
'text': text,
'parse_mode': 'Markdown'
}
response = requests.post(url, json=payload)
return response.status_code == 200
except Exception as e:
# Silent fail pentru progress updates - nu blochez scraper-ul
logging.debug(f"Progress update failed: {e}")
return False
def _send_files(self, files: List[str], accounts: list) -> bool:
"""Send files directly to Telegram"""
try:
# Calculate total balance
total_ron = sum(acc['sold'] for acc in accounts if acc.get('moneda') == 'RON')
# Build message with account balances
message = f"<b>BTGO SCRAPER - FINALIZAT CU SUCCES</b>\n\n"
message += f"Timp: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"
message += f"Conturi procesate: {len(accounts)}\n"
message += f"Fisiere: {len(files)}\n\n"
message += "<b>SOLDURI:</b>\n"
for acc in accounts:
nume = acc['nume_cont']
sold = acc['sold']
moneda = acc['moneda']
message += f"{nume}: {sold:,.2f} {moneda}\n"
message += f"\n<b>TOTAL: {total_ron:,.2f} RON</b>\n"
logging.info(f"Sending Telegram notification to chat {self.progress_chat_id}...")
logging.info(f"Progress message_id: {self.progress_message_id}, chat_id: {self.progress_chat_id}")
# Edit initial message or send new one
if self.progress_message_id and self.progress_chat_id:
# Edit the initial progress message
logging.info(f"Editing initial message {self.progress_message_id} in chat {self.progress_chat_id}")
url = f"https://api.telegram.org/bot{self.bot_token}/editMessageText"
payload = {
'chat_id': self.progress_chat_id,
'message_id': self.progress_message_id,
'text': message,
'parse_mode': 'HTML'
}
response = requests.post(url, json=payload)
if response.status_code == 200:
logging.info("✓ Message edited successfully with balances")
else:
logging.error(f"Failed to edit message: {response.status_code} - {response.text}")
else:
# Send new message if no message_id provided
logging.warning("No progress_message_id provided, sending new message")
self._send_message(message)
# Send each file as document
url = f"https://api.telegram.org/bot{self.bot_token}/sendDocument"
success_count = 0
for file_path in files:
if not Path(file_path).exists():
logging.warning(f"File not found: {file_path}")
continue
file_size = Path(file_path).stat().st_size
if file_size > self.max_file_size:
logging.warning(f"File too large for Telegram: {Path(file_path).name} ({file_size / 1024 / 1024:.2f} MB)")
continue
with open(file_path, 'rb') as f:
files_data = {'document': f}
data = {
'chat_id': self.chat_id,
'caption': f"{Path(file_path).name}"
}
response = requests.post(url, data=data, files=files_data)
if response.status_code == 200:
logging.info(f"✓ Sent to Telegram: {Path(file_path).name}")
success_count += 1
else:
logging.error(f"Failed to send {Path(file_path).name}: {response.text}")
if success_count > 0:
logging.info(f"✓ Telegram notification sent successfully ({success_count}/{len(files)} files)")
return True
else:
logging.error("No files were sent to Telegram")
return False
except Exception as e:
logging.error(f"Failed to send Telegram files: {e}")
return False
def _send_with_zip(self, files: List[str], accounts: list) -> bool:
"""Send files as ZIP archive to Telegram"""
try:
# Create ZIP archive
timestamp = datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
zip_path = Path(self.config.OUTPUT_DIR) / f'btgo_export_{timestamp}.zip'
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
for file_path in files:
if Path(file_path).exists():
zipf.write(file_path, Path(file_path).name)
zip_size = Path(zip_path).stat().st_size
logging.info(f"Created ZIP archive: {zip_path} ({zip_size / 1024 / 1024:.2f} MB)")
# Check if ZIP is still too large
if zip_size > self.max_file_size:
logging.error(f"ZIP archive exceeds Telegram 50MB limit ({zip_size / 1024 / 1024:.2f} MB)")
# Send message without attachment
return self._send_message_only(accounts, files)
# Calculate total balance
total_ron = sum(acc['sold'] for acc in accounts if acc.get('moneda') == 'RON')
# Build message with balances
message = f"<b>BTGO SCRAPER - FINALIZAT CU SUCCES (ARHIVA ZIP)</b>\n\n"
message += f"Timp: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"
message += f"Conturi procesate: {len(accounts)}\n"
message += f"Dimensiune arhiva: {zip_size / 1024 / 1024:.2f} MB\n"
message += f"Fisiere in arhiva: {len(files)}\n\n"
message += "<b>SOLDURI:</b>\n"
for acc in accounts:
nume = acc['nume_cont']
sold = acc['sold']
moneda = acc['moneda']
message += f"{nume}: {sold:,.2f} {moneda}\n"
message += f"\n<b>TOTAL: {total_ron:,.2f} RON</b>\n"
# Edit initial message or send new one
if self.progress_message_id and self.progress_chat_id:
url = f"https://api.telegram.org/bot{self.bot_token}/editMessageText"
payload = {
'chat_id': self.progress_chat_id,
'message_id': self.progress_message_id,
'text': message,
'parse_mode': 'HTML'
}
response = requests.post(url, json=payload)
if response.status_code != 200:
logging.error(f"Failed to edit message: {response.text}")
else:
self._send_message(message)
# Send ZIP file
url = f"https://api.telegram.org/bot{self.bot_token}/sendDocument"
with open(zip_path, 'rb') as f:
files_data = {'document': f}
data = {
'chat_id': self.chat_id,
'caption': f"BTGO Export Archive - {timestamp}"
}
response = requests.post(url, data=data, files=files_data)
if response.status_code == 200:
logging.info(f"✓ Telegram notification with ZIP sent successfully")
return True
else:
logging.error(f"Telegram send failed: {response.status_code} - {response.text}")
return False
except Exception as e:
logging.error(f"Failed to send Telegram ZIP: {e}")
return False
def _send_message_only(self, accounts: list, files: List[str]) -> bool:
"""Send Telegram message without files (when too large)"""
try:
file_list = '\n'.join([f'{Path(f).name} ({Path(f).stat().st_size / 1024:.1f} KB)' for f in files if Path(f).exists()])
# Calculate total balance
total_ron = sum(acc['sold'] for acc in accounts if acc.get('moneda') == 'RON')
message = f"<b>BTGO SCRAPER - FINALIZAT CU SUCCES</b>\n\n"
message += f"Timp: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"
message += f"Conturi procesate: {len(accounts)}\n\n"
message += "<b>SOLDURI:</b>\n"
for acc in accounts:
nume = acc['nume_cont']
sold = acc['sold']
moneda = acc['moneda']
message += f"{nume}: {sold:,.2f} {moneda}\n"
message += f"\n<b>TOTAL: {total_ron:,.2f} RON</b>\n\n"
message += f"<b>ATENTIE:</b> Fisiere prea mari pentru Telegram\n\n"
message += f"<b>Fisiere generate:</b>\n{file_list}\n\n"
message += f"Verifica locatia de deployment pentru fisiere."
# Edit initial message or send new one
if self.progress_message_id and self.progress_chat_id:
url = f"https://api.telegram.org/bot{self.bot_token}/editMessageText"
payload = {
'chat_id': self.progress_chat_id,
'message_id': self.progress_message_id,
'text': message,
'parse_mode': 'HTML'
}
response = requests.post(url, json=payload)
return response.status_code == 200
else:
return self._send_message(message)
except Exception as e:
logging.error(f"Failed to send Telegram message: {e}")
return False
class NotificationService:
"""Orchestrates all notification channels"""
def __init__(self, config):
self.config = config
self.email = EmailNotifier(config)
self.telegram = TelegramNotifier(config)
def send_all(self, files: List[str], accounts: list, telegram_message_id: str = None, telegram_chat_id: str = None) -> dict:
"""
Send notifications via all enabled channels
Args:
files: List of file paths to send
accounts: List of account data with balances
telegram_message_id: Message ID to edit (for Telegram progress updates)
telegram_chat_id: Chat ID to edit (for Telegram progress updates)
Returns:
Dictionary with status of each channel
"""
results = {
'email': False,
'telegram': False
}
account_count = len(accounts)
logging.info(f"Sending notifications for {len(files)} files...")
# Send via email
if self.config.EMAIL_ENABLED:
results['email'] = self.email.send(files, account_count)
# Send via Telegram
if self.config.TELEGRAM_ENABLED:
results['telegram'] = self.telegram.send(
files,
accounts,
telegram_message_id=telegram_message_id,
telegram_chat_id=telegram_chat_id
)
# Log summary
success_count = sum(results.values())
total_enabled = sum([self.config.EMAIL_ENABLED, self.config.TELEGRAM_ENABLED])
if success_count == total_enabled:
logging.info(f"✓ All notifications sent successfully ({success_count}/{total_enabled})")
elif success_count > 0:
logging.warning(f"Partial notification success ({success_count}/{total_enabled})")
else:
logging.error("All notifications failed")
return results

3
requirements.txt Normal file
View File

@@ -0,0 +1,3 @@
playwright==1.48.0
python-dotenv==1.0.0
requests==2.31.0

159
send_notifications.py Normal file
View File

@@ -0,0 +1,159 @@
"""
Script pentru trimiterea notificărilor cu fișiere existente
Trimite ultimele fișiere CSV generate pe Email și Telegram
"""
import logging
import sys
from pathlib import Path
from datetime import datetime
from config import Config
from notifications import NotificationService
# Setup logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler(sys.stdout)
]
)
def find_latest_files(data_dir='./data', time_window_seconds=300):
"""
Găsește ultimele fișiere CSV generate
Args:
data_dir: Directorul cu fișiere
time_window_seconds: Intervalul de timp (în secunde) pentru a considera fișierele din aceeași sesiune
Returns:
tuple: (solduri_csv_path, list_of_transaction_csvs)
"""
data_path = Path(data_dir)
if not data_path.exists():
logging.error(f"Directorul {data_dir} nu există!")
return None, []
# Găsește ultimul fișier solduri_*.csv
solduri_files = sorted(data_path.glob('solduri_*.csv'), key=lambda x: x.stat().st_mtime, reverse=True)
if not solduri_files:
logging.error("Nu s-a găsit niciun fișier solduri_*.csv!")
return None, []
latest_solduri = solduri_files[0]
solduri_time = latest_solduri.stat().st_mtime
logging.info(f"✓ Găsit fișier solduri: {latest_solduri.name}")
logging.info(f" Timestamp: {datetime.fromtimestamp(solduri_time).strftime('%Y-%m-%d %H:%M:%S')}")
# Găsește toate fișierele tranzactii_*.csv modificate în ultimele X secunde față de solduri
all_transaction_files = list(data_path.glob('tranzactii_*.csv'))
transaction_files = []
for tf in all_transaction_files:
tf_time = tf.stat().st_mtime
time_diff = abs(tf_time - solduri_time)
# Dacă fișierul tranzacții este din aceeași sesiune (în intervalul de timp)
if time_diff <= time_window_seconds:
transaction_files.append(tf)
logging.info(f"{tf.name} (diferență: {int(time_diff)}s)")
# Sortează după timp
transaction_files = sorted(transaction_files, key=lambda x: x.stat().st_mtime)
logging.info(f"✓ Găsite {len(transaction_files)} fișiere tranzacții din aceeași sesiune")
return latest_solduri, transaction_files
def send_existing_files():
"""Trimite fișierele existente pe Email și Telegram"""
try:
logging.info("=" * 60)
logging.info("TRIMITERE NOTIFICĂRI CU FIȘIERE EXISTENTE")
logging.info("=" * 60)
# Validează configurația
Config.validate()
if not Config.ENABLE_NOTIFICATIONS:
logging.error("ENABLE_NOTIFICATIONS=false în .env!")
logging.error("Setează ENABLE_NOTIFICATIONS=true pentru a trimite notificări")
return False
# Găsește ultimele fișiere
solduri_csv, transaction_csvs = find_latest_files(Config.OUTPUT_DIR)
if not solduri_csv:
logging.error("Nu există fișiere de trimis!")
return False
# Pregătește lista de fișiere
files_to_send = [str(solduri_csv)]
for tf in transaction_csvs:
files_to_send.append(str(tf))
logging.info("=" * 60)
logging.info(f"Total fișiere de trimis: {len(files_to_send)}")
logging.info("=" * 60)
# Estimează numărul de conturi din numărul de fișiere tranzacții
account_count = len(transaction_csvs)
# Trimite notificările
service = NotificationService(Config)
results = service.send_all(files_to_send, account_count)
# Afișează rezumat
logging.info("=" * 60)
logging.info("REZUMAT NOTIFICĂRI")
logging.info("=" * 60)
if Config.EMAIL_ENABLED:
status = "✓ Succes" if results['email'] else "✗ Eșuat"
logging.info(f"Email: {status}")
else:
logging.info("Email: Dezactivat")
if Config.TELEGRAM_ENABLED:
status = "✓ Succes" if results['telegram'] else "✗ Eșuat"
logging.info(f"Telegram: {status}")
else:
logging.info("Telegram: Dezactivat")
logging.info("=" * 60)
# Verifică dacă cel puțin un canal a avut succes
success = any(results.values())
if success:
logging.info("✓ Notificări trimise cu succes!")
else:
logging.warning("Notificările au eșuat. Verifică configurația în .env")
return success
except ValueError as e:
logging.error(f"Eroare configurație: {e}")
logging.error("Verifică setările din .env")
return False
except Exception as e:
logging.error(f"Eroare neașteptată: {e}", exc_info=True)
return False
if __name__ == "__main__":
logging.info("")
logging.info("🔔 BTGO Scraper - Trimitere Notificări")
logging.info("")
success = send_existing_files()
if success:
sys.exit(0)
else:
sys.exit(1)

276
telegram_trigger_bot.py Normal file
View File

@@ -0,0 +1,276 @@
#!/usr/bin/env python3
"""
Telegram Trigger Bot - Declanșează BTGO Scraper prin comandă Telegram
"""
import os
import sys
import subprocess
import logging
from pathlib import Path
from datetime import datetime
import glob
import requests
from dotenv import load_dotenv
# Load environment
load_dotenv()
# Configuration
BOT_TOKEN = os.getenv('TELEGRAM_BOT_TOKEN')
ALLOWED_USER_IDS = os.getenv('TELEGRAM_ALLOWED_USER_IDS', '').split(',') # Ex: "123456,789012"
CHAT_ID = os.getenv('TELEGRAM_CHAT_ID')
POLL_TIMEOUT = int(os.getenv('TELEGRAM_POLL_TIMEOUT', 60)) # Default 60 secunde
# Logging - force stdout instead of stderr (for Windows service logging)
logging.basicConfig(
level=logging.INFO,
format='[%(asctime)s] [%(levelname)s] %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
stream=sys.stdout,
force=True
)
class TelegramTriggerBot:
def __init__(self):
self.bot_token = BOT_TOKEN
self.allowed_users = [int(uid.strip()) for uid in ALLOWED_USER_IDS
if uid.strip() and not uid.strip().startswith('#')]
self.base_url = f"https://api.telegram.org/bot{self.bot_token}"
self.last_update_id = 0
self.poll_timeout = POLL_TIMEOUT
if not self.bot_token:
raise ValueError("TELEGRAM_BOT_TOKEN nu este setat în .env!")
logging.info(f"Bot inițializat. Useri autorizați: {self.allowed_users}")
logging.info(f"Long polling timeout: {self.poll_timeout}s")
# Înregistrare comenzi în meniul Telegram
self._register_commands()
def _register_commands(self):
"""Înregistrează comenzile bot în meniul Telegram (pentru DM și grupuri)"""
try:
url = f"{self.base_url}/setMyCommands"
commands = [
{"command": "scrape", "description": "Rulează scraper-ul BTGO"},
{"command": "status", "description": "Status sistem"},
{"command": "help", "description": "Ajutor comenzi"}
]
response = requests.post(url, json={"commands": commands})
if response.status_code == 200 and response.json().get('ok'):
logging.info("✓ Comenzi înregistrate în meniul Telegram")
else:
logging.warning(f"Nu am putut înregistra comenzile: {response.text}")
except Exception as e:
logging.warning(f"Eroare înregistrare comenzi: {e}")
def send_message(self, chat_id, text, reply_to_message_id=None):
"""Trimite mesaj text"""
url = f"{self.base_url}/sendMessage"
data = {
'chat_id': chat_id,
'text': text,
'parse_mode': 'Markdown'
}
if reply_to_message_id:
data['reply_to_message_id'] = reply_to_message_id
response = requests.post(url, json=data)
return response
def send_document(self, chat_id, file_path, caption=None):
"""Trimite document (CSV/JSON)"""
url = f"{self.base_url}/sendDocument"
with open(file_path, 'rb') as file:
files = {'document': file}
data = {'chat_id': chat_id}
if caption:
data['caption'] = caption
response = requests.post(url, data=data, files=files)
return response.json()
def is_user_allowed(self, user_id):
"""Verifică dacă user-ul are permisiune"""
if not self.allowed_users: # Dacă lista e goală, permite oricui
return True
return user_id in self.allowed_users
def run_scraper(self, chat_id, reply_to_message_id=None):
"""Execută scraper-ul"""
# Trimite mesaj inițial și salvează message_id pentru editare ulterioară
response = self.send_message(chat_id, "*BTGO Scraper pornit*\n\nAsteapta 2FA pe telefon.", reply_to_message_id)
message_id = None
try:
message_id = response.json()['result']['message_id']
logging.info(f"Mesaj progress creat cu ID: {message_id}")
except:
logging.warning("Nu am putut salva message_id pentru progress updates")
try:
# Rulează scraper-ul
logging.info("Pornire scraper...")
# Prepare environment with global playwright path + Telegram progress info
env = os.environ.copy()
env['PLAYWRIGHT_BROWSERS_PATH'] = 'C:\\playwright-browsers'
if message_id:
env['TELEGRAM_CHAT_ID'] = str(chat_id)
env['TELEGRAM_MESSAGE_ID'] = str(message_id)
logging.info(f"Setting environment: TELEGRAM_CHAT_ID={chat_id}, TELEGRAM_MESSAGE_ID={message_id}")
else:
logging.warning("No message_id available for progress updates")
result = subprocess.run(
[sys.executable, 'btgo_scraper.py'],
capture_output=True,
text=True,
timeout=600, # 10 minute timeout
cwd=os.path.dirname(os.path.abspath(__file__)), # Run in bot's directory
env=env # Pass environment with playwright path
)
if result.returncode == 0:
# Succes - mesajul final va fi editat de notifications.py
logging.info("Scraper finalizat cu succes")
else:
# Eroare
logging.error(f"Scraper eșuat cu cod {result.returncode}")
error_msg = result.stderr[-1000:] if result.stderr else "Eroare necunoscută"
self.send_message(
chat_id,
f"*EROARE SCRAPER*\n\n```\n{error_msg}\n```",
reply_to_message_id
)
except subprocess.TimeoutExpired:
logging.error("Timeout scraper")
self.send_message(chat_id, "*TIMEOUT*\n\nScraper-ul a depasit 10 minute.", reply_to_message_id)
except Exception as e:
logging.error(f"Eroare execuție: {e}")
self.send_message(chat_id, f"*EROARE EXECUTIE*\n\n```\n{str(e)}\n```", reply_to_message_id)
def handle_command(self, message):
"""Procesează comenzi primite"""
chat_id = message['chat']['id']
chat_type = message['chat']['type'] # 'private', 'group', 'supergroup'
chat_title = message['chat'].get('title', 'DM')
user_id = message['from']['id']
username = message['from'].get('username', 'Unknown')
text = message.get('text', '')
message_id = message.get('message_id')
# Normalizează comanda - elimină @username pentru grupuri (ex: /scrape@botname → /scrape)
if '@' in text:
text = text.split('@')[0]
# Log context
context = f"grup '{chat_title}'" if chat_type in ['group', 'supergroup'] else "DM"
logging.info(f"Mesaj de la {username} (ID: {user_id}) în {context}: {text}")
# Verifică autorizare
if not self.is_user_allowed(user_id):
logging.warning(f"User neautorizat: {user_id} în {context}")
self.send_message(chat_id, "*ACCES INTERZIS*\n\nNu ai permisiunea sa folosesti acest bot.", message_id)
return
# Procesează comenzi
if text == '/start':
welcome_msg = "*BTGO Scraper Trigger Bot*\n\n"
if chat_type in ['group', 'supergroup']:
welcome_msg += f"Bot activ in grupul *{chat_title}*\n\n"
welcome_msg += (
"Comenzi disponibile:\n"
"`/scrape` - Ruleaza scraper-ul\n"
"`/status` - Status sistem\n"
"`/help` - Ajutor"
)
self.send_message(chat_id, welcome_msg, message_id)
elif text == '/scrape':
logging.info(f"Comandă /scrape primită în {context}")
self.run_scraper(chat_id, message_id)
elif text == '/status':
data_dir = Path('data')
csv_count = len(list(data_dir.glob('*.csv')))
json_count = len(list(data_dir.glob('*.json')))
# Ultimul fișier
all_files = sorted(data_dir.glob('solduri_*.csv'), key=os.path.getmtime, reverse=True)
last_run = "N/A"
if all_files:
last_run = datetime.fromtimestamp(os.path.getmtime(all_files[0])).strftime('%Y-%m-%d %H:%M:%S')
self.send_message(
chat_id,
f"*STATUS SISTEM*\n\n"
f"Ultima rulare: `{last_run}`\n"
f"Fisiere CSV: {csv_count}\n"
f"Fisiere JSON: {json_count}\n"
f"Working dir: `{os.getcwd()}`",
message_id
)
elif text == '/help':
help_msg = "*GHID DE UTILIZARE*\n\n"
if chat_type in ['group', 'supergroup']:
help_msg += "IN GRUP: Toti membrii vad comenzile si rezultatele\n\n"
help_msg += (
"1. Trimite `/scrape` pentru a porni scraper-ul\n"
"2. Asteapta notificarea de 2FA pe telefon\n"
"3. Aproba in aplicatia George\n"
"4. Primesti fisierele automat\n\n"
"NOTE:\n"
"- Scraper-ul ruleaza ~2-3 minute\n"
"- Asigura-te ca VM-ul are browser vizibil"
)
self.send_message(chat_id, help_msg, message_id)
else:
self.send_message(chat_id, f"*COMANDA NECUNOSCUTA*\n\n`{text}`\n\nFoloseste /help pentru comenzi.", message_id)
def get_updates(self):
"""Preia update-uri de la Telegram"""
url = f"{self.base_url}/getUpdates"
params = {
'offset': self.last_update_id + 1,
'timeout': self.poll_timeout
}
response = requests.get(url, params=params, timeout=self.poll_timeout + 5)
return response.json()
def run(self):
"""Loop principal bot"""
logging.info("Bot pornit. Așteaptă comenzi...")
while True:
try:
updates = self.get_updates()
if updates.get('ok'):
for update in updates.get('result', []):
self.last_update_id = update['update_id']
if 'message' in update:
self.handle_command(update['message'])
except KeyboardInterrupt:
logging.info("Bot oprit de utilizator")
break
except Exception as e:
logging.error(f"Eroare loop: {e}")
import time
time.sleep(5)
if __name__ == "__main__":
try:
bot = TelegramTriggerBot()
bot.run()
except Exception as e:
logging.error(f"Eroare fatală: {e}")
raise