Adauga comanda /tranzactii pentru vizualizare tranzactii interactive
- Sistem interactiv de selectie conturi cu state management - Filtrare pe perioade: [nr cont] [nr zile] (ex: 2 30 = ultimele 30 zile) - Format compact: tranzactii grupate pe date, fara sold - Extragere inteligenta comercianti din platile POS (TID pattern) - Escape automat caractere speciale Markdown pentru Telegram - Timeout 5 minute pentru sesiuni de selectie - Suport comenzi: doar numar = ultimele 10, numar + zile = filtrare Corectari: - Fix import timedelta pentru filtrare pe date - Fix conflict nume variabila period vs csv_period - Fix Markdown parsing errors (underscore, dot, etc) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -12,7 +12,7 @@ import json
|
|||||||
import csv
|
import csv
|
||||||
import zipfile
|
import zipfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from datetime import datetime
|
from datetime import datetime, timedelta
|
||||||
import glob
|
import glob
|
||||||
import requests
|
import requests
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
@@ -51,6 +51,9 @@ class TelegramTriggerBot:
|
|||||||
self.last_update_id = 0
|
self.last_update_id = 0
|
||||||
self.poll_timeout = POLL_TIMEOUT
|
self.poll_timeout = POLL_TIMEOUT
|
||||||
|
|
||||||
|
# State management pentru selecție interactivă conturi
|
||||||
|
self.pending_account_selection = {} # {user_id: {'accounts': [...], 'timestamp': ...}}
|
||||||
|
|
||||||
if not self.bot_token:
|
if not self.bot_token:
|
||||||
raise ValueError("TELEGRAM_BOT_TOKEN nu este setat în .env!")
|
raise ValueError("TELEGRAM_BOT_TOKEN nu este setat în .env!")
|
||||||
|
|
||||||
@@ -70,6 +73,7 @@ class TelegramTriggerBot:
|
|||||||
{"command": "scrape_zip", "description": "Rulează scraper + trimite ZIP"},
|
{"command": "scrape_zip", "description": "Rulează scraper + trimite ZIP"},
|
||||||
{"command": "scrape_solduri", "description": "Extrage doar soldurile (fără CSV)"},
|
{"command": "scrape_solduri", "description": "Extrage doar soldurile (fără CSV)"},
|
||||||
{"command": "solduri", "description": "Afișează ultimul fișier solduri"},
|
{"command": "solduri", "description": "Afișează ultimul fișier solduri"},
|
||||||
|
{"command": "tranzactii", "description": "Afișează tranzacții recente din cont"},
|
||||||
{"command": "zip", "description": "Trimite ultimele fișiere ca ZIP"},
|
{"command": "zip", "description": "Trimite ultimele fișiere ca ZIP"},
|
||||||
{"command": "status", "description": "Status sistem"},
|
{"command": "status", "description": "Status sistem"},
|
||||||
{"command": "help", "description": "Ajutor comenzi"}
|
{"command": "help", "description": "Ajutor comenzi"}
|
||||||
@@ -318,6 +322,321 @@ class TelegramTriggerBot:
|
|||||||
logging.error(f"Eroare show_cached_balances: {e}", exc_info=True)
|
logging.error(f"Eroare show_cached_balances: {e}", exc_info=True)
|
||||||
self.send_message(chat_id, f"*EROARE*\n\n```\n{str(e)}\n```", reply_to_message_id)
|
self.send_message(chat_id, f"*EROARE*\n\n```\n{str(e)}\n```", reply_to_message_id)
|
||||||
|
|
||||||
|
def show_transactions_menu(self, chat_id, user_id, reply_to_message_id=None):
|
||||||
|
"""Afișează meniu cu conturi disponibile pentru selecție tranzacții"""
|
||||||
|
try:
|
||||||
|
data_dir = Path('data')
|
||||||
|
|
||||||
|
if not data_dir.exists():
|
||||||
|
self.send_message(chat_id, "*EROARE*\n\nDirectorul 'data' nu există!", reply_to_message_id)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Găsește toate fișierele de tranzacții
|
||||||
|
transaction_files = sorted(
|
||||||
|
data_dir.glob('tranzactii_*.csv'),
|
||||||
|
key=lambda x: x.stat().st_mtime,
|
||||||
|
reverse=True
|
||||||
|
)
|
||||||
|
|
||||||
|
if not transaction_files:
|
||||||
|
self.send_message(
|
||||||
|
chat_id,
|
||||||
|
"*EROARE*\n\nNu s-au găsit fișiere cu tranzacții!\n\nRulează mai întâi /scrape pentru a descărca tranzacții.",
|
||||||
|
reply_to_message_id
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Extrage nume conturi unice din numele fișierelor
|
||||||
|
# Format: tranzactii_Nume_Cont_YYYY-MM-DD_HH-MM-SS.csv
|
||||||
|
accounts = {}
|
||||||
|
for file_path in transaction_files:
|
||||||
|
filename = file_path.stem # fără extensie
|
||||||
|
# Elimină prefixul "tranzactii_" și sufixul timestamp
|
||||||
|
# rsplit('_', 3) split-uiește: ['Nume', 'Cont', 'YYYY-MM-DD', 'HH-MM-SS']
|
||||||
|
parts = filename.replace('tranzactii_', '').rsplit('_', 3)
|
||||||
|
if len(parts) >= 4:
|
||||||
|
# parts[0] = nume de bază, parts[1] = număr cont
|
||||||
|
account_name = f"{parts[0]}_{parts[1]}" # "Nume_Cont"
|
||||||
|
# Convertește Nume_Cont → Nume Cont
|
||||||
|
display_name = account_name.replace('_', ' ')
|
||||||
|
|
||||||
|
# Păstrează doar cel mai recent fișier pentru fiecare cont
|
||||||
|
if display_name not in accounts:
|
||||||
|
accounts[display_name] = file_path
|
||||||
|
|
||||||
|
if not accounts:
|
||||||
|
self.send_message(
|
||||||
|
chat_id,
|
||||||
|
"*EROARE*\n\nNu s-au putut procesa fișierele de tranzacții!",
|
||||||
|
reply_to_message_id
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Sortează conturile alfabetic
|
||||||
|
sorted_accounts = sorted(accounts.items())
|
||||||
|
|
||||||
|
# Construiește mesaj
|
||||||
|
message = "*TRANZACTII BANCARE*\n\n"
|
||||||
|
message += "Conturi disponibile:\n\n"
|
||||||
|
|
||||||
|
for idx, (account_name, file_path) in enumerate(sorted_accounts, 1):
|
||||||
|
message += f"{idx}. {account_name}\n"
|
||||||
|
|
||||||
|
message += f"\n*Scrie numarul contului (1-{len(sorted_accounts)}):*\n"
|
||||||
|
message += " • Doar numar (ex: 2) = ultimele 10\n"
|
||||||
|
message += " • Numar + zile (ex: 2 7, 2 30)"
|
||||||
|
|
||||||
|
# Salvează starea pentru user
|
||||||
|
self.pending_account_selection[user_id] = {
|
||||||
|
'accounts': sorted_accounts,
|
||||||
|
'timestamp': datetime.now().timestamp()
|
||||||
|
}
|
||||||
|
|
||||||
|
self.send_message(chat_id, message, reply_to_message_id)
|
||||||
|
logging.info(f"Afișat meniu tranzacții pentru user {user_id}: {len(sorted_accounts)} conturi")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Eroare show_transactions_menu: {e}", exc_info=True)
|
||||||
|
self.send_message(chat_id, f"*EROARE*\n\n```\n{str(e)}\n```", reply_to_message_id)
|
||||||
|
|
||||||
|
def show_account_transactions(self, chat_id, user_id, account_index, reply_to_message_id=None, period="10"):
|
||||||
|
"""Afișează tranzacții pentru contul selectat (perioada: 10/luna/sapt)"""
|
||||||
|
try:
|
||||||
|
# Verifică dacă userul are o selecție pending
|
||||||
|
if user_id not in self.pending_account_selection:
|
||||||
|
self.send_message(
|
||||||
|
chat_id,
|
||||||
|
"*EROARE*\n\nNu există selecție activă. Folosește /tranzactii pentru a începe.",
|
||||||
|
reply_to_message_id
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
pending_data = self.pending_account_selection[user_id]
|
||||||
|
accounts = pending_data['accounts']
|
||||||
|
|
||||||
|
# Verifică timeout (5 minute)
|
||||||
|
if datetime.now().timestamp() - pending_data['timestamp'] > 300:
|
||||||
|
del self.pending_account_selection[user_id]
|
||||||
|
self.send_message(
|
||||||
|
chat_id,
|
||||||
|
"*TIMEOUT*\n\nSelecția a expirat. Folosește /tranzactii pentru a începe din nou.",
|
||||||
|
reply_to_message_id
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Verifică index valid
|
||||||
|
if account_index < 1 or account_index > len(accounts):
|
||||||
|
self.send_message(
|
||||||
|
chat_id,
|
||||||
|
f"*EROARE*\n\nNumăr invalid! Scrie un număr între 1 și {len(accounts)}.",
|
||||||
|
reply_to_message_id
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Obține contul selectat
|
||||||
|
account_name, csv_path = accounts[account_index - 1]
|
||||||
|
|
||||||
|
# Verifică dacă fișierul există
|
||||||
|
if not csv_path.exists():
|
||||||
|
del self.pending_account_selection[user_id]
|
||||||
|
self.send_message(
|
||||||
|
chat_id,
|
||||||
|
f"*EROARE*\n\nFișierul pentru contul {account_name} nu mai există!",
|
||||||
|
reply_to_message_id
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Citește CSV-ul
|
||||||
|
# Format BT: Primele 17 linii = metadata, linia 18 = header, linia 19+ = date
|
||||||
|
transactions = []
|
||||||
|
account_iban = ""
|
||||||
|
csv_period = "" # Perioada din CSV (nu parametrul funcției!)
|
||||||
|
|
||||||
|
with open(csv_path, 'r', encoding='utf-8') as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
|
||||||
|
# Extrage metadate
|
||||||
|
if len(lines) > 8:
|
||||||
|
# Linia 9: Perioada:,29.10.2025-12.11.2025
|
||||||
|
period_line = lines[8].strip()
|
||||||
|
if 'Perioada:' in period_line:
|
||||||
|
csv_period = period_line.split(',')[1] if ',' in period_line else ""
|
||||||
|
|
||||||
|
# Linia 8: Numar cont:,RO32BTRLRONCRT0637236701 RON
|
||||||
|
iban_line = lines[7].strip()
|
||||||
|
if 'Numar cont:' in iban_line:
|
||||||
|
account_iban = iban_line.split(',')[1] if ',' in iban_line else ""
|
||||||
|
|
||||||
|
# Citește tranzacțiile (de la header - linia 18, index 17)
|
||||||
|
if len(lines) > 17:
|
||||||
|
csv_content = ''.join(lines[17:]) # Include header + date
|
||||||
|
reader = csv.DictReader(csv_content.splitlines())
|
||||||
|
|
||||||
|
for row in reader:
|
||||||
|
if row.get('Data tranzactie'): # Skip linii goale
|
||||||
|
transactions.append(row)
|
||||||
|
|
||||||
|
# Șterge selecția pending
|
||||||
|
del self.pending_account_selection[user_id]
|
||||||
|
|
||||||
|
if not transactions:
|
||||||
|
self.send_message(
|
||||||
|
chat_id,
|
||||||
|
f"*{account_name}*\n\nNu există tranzacții în acest fișier.",
|
||||||
|
reply_to_message_id
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Filtrează tranzacțiile în funcție de perioada selectată (nr zile)
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
recent_transactions = []
|
||||||
|
period_description = ""
|
||||||
|
|
||||||
|
# Convertește period în număr de zile
|
||||||
|
try:
|
||||||
|
num_days = int(period)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
num_days = 10 # Default
|
||||||
|
|
||||||
|
if num_days <= 0:
|
||||||
|
num_days = 10 # Sigură
|
||||||
|
|
||||||
|
# Filtrează pe bază de zile
|
||||||
|
if num_days == 10:
|
||||||
|
# Optimizare: ultimele 10 tranzacții direct
|
||||||
|
recent_transactions = transactions[:10]
|
||||||
|
period_description = "Ultimele 10 tranzactii"
|
||||||
|
else:
|
||||||
|
# Filtrare pe bază de dată
|
||||||
|
now = datetime.now()
|
||||||
|
cutoff_date = now - timedelta(days=num_days)
|
||||||
|
|
||||||
|
for tx in transactions:
|
||||||
|
tx_date_str = tx.get('Data tranzactie', '')
|
||||||
|
if tx_date_str:
|
||||||
|
try:
|
||||||
|
tx_date = datetime.strptime(tx_date_str, '%Y-%m-%d')
|
||||||
|
if tx_date >= cutoff_date:
|
||||||
|
recent_transactions.append(tx)
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
period_description = f"Ultimele {num_days} zile"
|
||||||
|
|
||||||
|
# Grupează tranzacțiile pe date
|
||||||
|
transactions_by_date = defaultdict(list)
|
||||||
|
|
||||||
|
for tx in recent_transactions:
|
||||||
|
date = tx.get('Data tranzactie', '')
|
||||||
|
if date:
|
||||||
|
transactions_by_date[date].append(tx)
|
||||||
|
|
||||||
|
# Construiește mesaj
|
||||||
|
message = f"*TRANZACTII - {account_name}*\n\n"
|
||||||
|
|
||||||
|
if period_description:
|
||||||
|
message += f"Perioada: {period_description}\n"
|
||||||
|
|
||||||
|
message += f"Total: {len(recent_transactions)} tranzactii\n"
|
||||||
|
message += "=" * 30 + "\n\n"
|
||||||
|
|
||||||
|
# Sortează datele descrescător (cele mai recente primul)
|
||||||
|
sorted_dates = sorted(transactions_by_date.keys(), reverse=True)
|
||||||
|
|
||||||
|
for date in sorted_dates:
|
||||||
|
# Header pentru dată
|
||||||
|
message += f"*{date}*\n"
|
||||||
|
|
||||||
|
# Tranzacțiile din acea zi
|
||||||
|
for tx in transactions_by_date[date]:
|
||||||
|
description = tx.get('Descriere', '')
|
||||||
|
debit = tx.get('Debit', '').strip().replace('"', '').replace(',', '')
|
||||||
|
credit = tx.get('Credit', '').strip().replace('"', '').replace(',', '')
|
||||||
|
|
||||||
|
# Extrage nume mai inteligent din descriere
|
||||||
|
display_name = description
|
||||||
|
desc_parts = description.split(';')
|
||||||
|
|
||||||
|
if len(desc_parts) > 2:
|
||||||
|
# Caz special: Plăți POS - extrage comerciantul din Parts[1]
|
||||||
|
if 'POS' in desc_parts[0] and len(desc_parts) > 1:
|
||||||
|
# Căutăm pattern: "TID:XXXXXXX <COMERCIANT> <REST>"
|
||||||
|
import re
|
||||||
|
tid_match = re.search(r'TID:\S+\s+(.+?)\s{2,}', desc_parts[1])
|
||||||
|
if tid_match:
|
||||||
|
candidate = tid_match.group(1).strip()
|
||||||
|
else:
|
||||||
|
# Fallback: încearcă să extragă după TID până la două spații
|
||||||
|
if 'TID:' in desc_parts[1]:
|
||||||
|
after_tid = desc_parts[1].split('TID:')[1]
|
||||||
|
# Skip ID-ul TID și ia textul până la două spații consecutive
|
||||||
|
parts_after = after_tid.split(None, 1) # Split la primul spațiu
|
||||||
|
if len(parts_after) > 1:
|
||||||
|
# Ia textul până la " " sau până la sfârșitul
|
||||||
|
merchant_text = parts_after[1]
|
||||||
|
double_space_idx = merchant_text.find(' ')
|
||||||
|
if double_space_idx > 0:
|
||||||
|
candidate = merchant_text[:double_space_idx].strip()
|
||||||
|
else:
|
||||||
|
candidate = merchant_text.strip()
|
||||||
|
else:
|
||||||
|
candidate = desc_parts[2].strip()
|
||||||
|
else:
|
||||||
|
candidate = desc_parts[2].strip()
|
||||||
|
else:
|
||||||
|
# Încearcă part[2] (de obicei numele)
|
||||||
|
candidate = desc_parts[2].strip()
|
||||||
|
|
||||||
|
# Dacă part[2] este doar număr/scurt/REF, încearcă part[3]
|
||||||
|
if (candidate.isdigit() or
|
||||||
|
len(candidate) < 3 or
|
||||||
|
candidate.startswith('REF:')):
|
||||||
|
if len(desc_parts) > 3:
|
||||||
|
candidate = desc_parts[3].strip()
|
||||||
|
|
||||||
|
# Dacă tot e invalid, folosește tipul tranzacției (part[0])
|
||||||
|
if len(candidate) < 3 or candidate.startswith('REF:'):
|
||||||
|
candidate = desc_parts[0].strip()
|
||||||
|
|
||||||
|
display_name = candidate
|
||||||
|
|
||||||
|
# Truncate dacă prea lung
|
||||||
|
if len(display_name) > 35:
|
||||||
|
display_name = display_name[:32] + "..."
|
||||||
|
|
||||||
|
# Escape caractere speciale Markdown pentru Telegram
|
||||||
|
# Caracterele care trebuie escapate: _ * [ ] ( ) ~ ` > # + - = | { } . !
|
||||||
|
markdown_chars = ['_', '*', '[', ']', '(', ')', '~', '`', '>', '#', '+', '-', '=', '|', '{', '}', '.', '!']
|
||||||
|
for char in markdown_chars:
|
||||||
|
display_name = display_name.replace(char, '\\' + char)
|
||||||
|
|
||||||
|
# Determină suma (debit poate avea deja minus în CSV)
|
||||||
|
if credit:
|
||||||
|
amount_str = f"+{credit}"
|
||||||
|
elif debit:
|
||||||
|
# Elimină minus-ul dacă există deja
|
||||||
|
debit_clean = debit.lstrip('-')
|
||||||
|
amount_str = f"-{debit_clean}"
|
||||||
|
else:
|
||||||
|
amount_str = "0.00"
|
||||||
|
|
||||||
|
# Format compact: " • Nume: +suma RON"
|
||||||
|
message += f" {display_name}: {amount_str} RON\n"
|
||||||
|
|
||||||
|
message += "\n"
|
||||||
|
|
||||||
|
self.send_message(chat_id, message, reply_to_message_id)
|
||||||
|
logging.info(f"Afișat {len(recent_transactions)} tranzacții pentru {account_name}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# Curăță selecția în caz de eroare
|
||||||
|
if user_id in self.pending_account_selection:
|
||||||
|
del self.pending_account_selection[user_id]
|
||||||
|
|
||||||
|
logging.error(f"Eroare show_account_transactions: {e}", exc_info=True)
|
||||||
|
self.send_message(chat_id, f"*EROARE*\n\n```\n{str(e)}\n```", reply_to_message_id)
|
||||||
|
|
||||||
def send_zip_files(self, chat_id, reply_to_message_id=None):
|
def send_zip_files(self, chat_id, reply_to_message_id=None):
|
||||||
"""Trimite ultimele fișiere ca arhivă ZIP"""
|
"""Trimite ultimele fișiere ca arhivă ZIP"""
|
||||||
try:
|
try:
|
||||||
@@ -471,6 +790,25 @@ class TelegramTriggerBot:
|
|||||||
self.send_message(chat_id, "*ACCES INTERZIS*\n\nNu ai permisiunea sa folosesti acest bot.", message_id)
|
self.send_message(chat_id, "*ACCES INTERZIS*\n\nNu ai permisiunea sa folosesti acest bot.", message_id)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Procesează răspuns numeric pentru selecție cont tranzacții
|
||||||
|
if user_id in self.pending_account_selection:
|
||||||
|
# Parse număr cont + opțional nr zile
|
||||||
|
# Formate acceptate: "2" (default 10), "2 7" (7 zile), "2 30" (30 zile)
|
||||||
|
parts = text.strip().split()
|
||||||
|
|
||||||
|
|
||||||
|
if len(parts) >= 1 and parts[0].isdigit():
|
||||||
|
account_index = int(parts[0])
|
||||||
|
|
||||||
|
# Determină numărul de zile
|
||||||
|
period = "10" # default: ultimele 10 tranzacții
|
||||||
|
if len(parts) >= 2 and parts[1].isdigit():
|
||||||
|
period = parts[1] # număr de zile
|
||||||
|
|
||||||
|
logging.info(f"Răspuns selecție cont: {account_index}, perioada: {period} zile de la user {user_id}")
|
||||||
|
self.show_account_transactions(chat_id, user_id, account_index, message_id, period)
|
||||||
|
return
|
||||||
|
|
||||||
# Procesează comenzi
|
# Procesează comenzi
|
||||||
if text == '/start':
|
if text == '/start':
|
||||||
welcome_msg = "*BTGO Scraper Trigger Bot*\n\n"
|
welcome_msg = "*BTGO Scraper Trigger Bot*\n\n"
|
||||||
@@ -482,6 +820,7 @@ class TelegramTriggerBot:
|
|||||||
"`/scrape_zip` - Ruleaza scraper + trimite ZIP\n"
|
"`/scrape_zip` - Ruleaza scraper + trimite ZIP\n"
|
||||||
"`/scrape_solduri` - Extrage doar soldurile (rapid)\n"
|
"`/scrape_solduri` - Extrage doar soldurile (rapid)\n"
|
||||||
"`/solduri` - Afiseaza ultimul fisier solduri\n"
|
"`/solduri` - Afiseaza ultimul fisier solduri\n"
|
||||||
|
"`/tranzactii` - Afiseaza tranzactii recente\n"
|
||||||
"`/zip` - Trimite ultimele fisiere ca ZIP\n"
|
"`/zip` - Trimite ultimele fisiere ca ZIP\n"
|
||||||
"`/status` - Status sistem\n"
|
"`/status` - Status sistem\n"
|
||||||
"`/help` - Ajutor"
|
"`/help` - Ajutor"
|
||||||
@@ -504,6 +843,10 @@ class TelegramTriggerBot:
|
|||||||
logging.info(f"Comandă /solduri primită în {context}")
|
logging.info(f"Comandă /solduri primită în {context}")
|
||||||
self.show_cached_balances(chat_id, message_id)
|
self.show_cached_balances(chat_id, message_id)
|
||||||
|
|
||||||
|
elif text == '/tranzactii':
|
||||||
|
logging.info(f"Comandă /tranzactii primită în {context}")
|
||||||
|
self.show_transactions_menu(chat_id, user_id, message_id)
|
||||||
|
|
||||||
elif text == '/zip':
|
elif text == '/zip':
|
||||||
logging.info(f"Comandă /zip primită în {context}")
|
logging.info(f"Comandă /zip primită în {context}")
|
||||||
self.send_zip_files(chat_id, message_id)
|
self.send_zip_files(chat_id, message_id)
|
||||||
@@ -539,6 +882,7 @@ class TelegramTriggerBot:
|
|||||||
"`/scrape_zip` - Ruleaza scraper + trimite arhiva ZIP\n"
|
"`/scrape_zip` - Ruleaza scraper + trimite arhiva ZIP\n"
|
||||||
"`/scrape_solduri` - Extrage doar soldurile (fara CSV tranzactii)\n"
|
"`/scrape_solduri` - Extrage doar soldurile (fara CSV tranzactii)\n"
|
||||||
"`/solduri` - Afiseaza ultimul fisier solduri (instant)\n"
|
"`/solduri` - Afiseaza ultimul fisier solduri (instant)\n"
|
||||||
|
"`/tranzactii` - Afiseaza tranzactii recente din cont (interactiv)\n"
|
||||||
"`/zip` - Trimite ultimele fisiere ca arhiva ZIP (fara scraping)\n"
|
"`/zip` - Trimite ultimele fisiere ca arhiva ZIP (fara scraping)\n"
|
||||||
"`/status` - Informatii sistem\n"
|
"`/status` - Informatii sistem\n"
|
||||||
"`/help` - Acest mesaj\n\n"
|
"`/help` - Acest mesaj\n\n"
|
||||||
|
|||||||
Reference in New Issue
Block a user