Files
telegram_new_socias/modules/database.py

150 lines
5.6 KiB
Python

import logging
import os
from datetime import datetime
from sqlalchemy import Column, DateTime, Integer, MetaData, String, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
import gspread
from google.oauth2.service_account import Credentials
# Configuración de logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
# --- GOOGLE SHEETS SETUP ---
GSHEET_URL = os.getenv("GOOGLE_SHEET_URL")
GOOGLE_CREDENTIALS_FILE = os.getenv("GOOGLE_CREDENTIALS_FILE", "google_credentials.json")
SHEET_COLUMN_INDEX = 40 # AN is the 40th column
def get_gsheet_client():
"""Retorna un cliente de gspread autenticado o None si falla."""
if not GSHEET_URL:
logging.warning("GOOGLE_SHEET_URL no está configurada. La verificación de duplicados está deshabilitada.")
return None
if not os.path.exists(GOOGLE_CREDENTIALS_FILE):
logging.warning(f"No se encontró el archivo de credenciales '{GOOGLE_CREDENTIALS_FILE}'. La verificación de duplicados está deshabilitada.")
return None
try:
scopes = ["https://www.googleapis.com/auth/spreadsheets.readonly"]
creds = Credentials.from_service_account_file(GOOGLE_CREDENTIALS_FILE, scopes=scopes)
client = gspread.authorize(creds)
return client
except Exception as e:
logging.error(f"Error al autenticar con Google Sheets: {e}")
return None
def chat_id_exists(chat_id: int) -> bool:
"""Verifica si un chat_id de Telegram ya existe en la columna AN de la hoja de cálculo."""
client = get_gsheet_client()
if not client:
return False # Si no hay cliente, no podemos verificar, así que asumimos que no existe.
try:
spreadsheet = client.open_by_url(GSHEET_URL)
worksheet = spreadsheet.get_worksheet(0) # Primera hoja
# Obtener todos los valores de la columna AN (índice 40)
chat_ids_in_sheet = worksheet.col_values(SHEET_COLUMN_INDEX)
# El ID de chat puede venir como número o texto, así que comparamos como string
return str(chat_id) in chat_ids_in_sheet
except gspread.exceptions.SpreadsheetNotFound:
logging.error(f"No se pudo encontrar la hoja de cálculo en la URL proporcionada.")
return False
except Exception as e:
logging.error(f"Error al leer la hoja de cálculo: {e}")
return False
# --- DATABASE (MySQL) SETUP ---
# Base para los modelos declarativos
Base = declarative_base()
# Clase que mapea a la tabla de logs
class RequestLog(Base):
__tablename__ = 'request_logs'
id = Column(Integer, primary_key=True)
telegram_id = Column(String(50))
username = Column(String(100))
command = Column(String(100))
message = Column(String(500))
created_at = Column(DateTime, default=datetime.utcnow)
def _build_engine():
"""Crea un engine de SQLAlchemy si hay variables de entorno suficientes."""
user = os.getenv("MYSQL_USER")
password = os.getenv("MYSQL_PASSWORD")
database = os.getenv("MYSQL_DATABASE")
host = os.getenv("MYSQL_HOST") or "db" # Permitimos override para uso local
if not all([user, password, database]):
logging.warning("DB logging deshabilitado: faltan MYSQL_USER/MYSQL_PASSWORD/MYSQL_DATABASE.")
return None
try:
db_url = f"mysql+mysqlconnector://{user}:{password}@{host}:3306/{database}"
return create_engine(db_url, pool_pre_ping=True)
except Exception as exc:
logging.error(f"No se pudo crear el engine de base de datos: {exc}")
return None
def _disable_db_logging(reason: str):
"""Deshabilita el logging a DB después de un error para evitar spam."""
global engine, SessionLocal
engine = None
SessionLocal = None
logging.warning(f"DB logging deshabilitado: {reason}")
# Crear el engine y sesión si es posible
engine = _build_engine()
metadata = MetaData() if engine else None
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) if engine else None
# Función para inicializar la base de datos
def init_db():
global engine, SessionLocal
if not engine:
return
try:
logging.info("Inicializando la base de datos y creando tablas si no existen...")
Base.metadata.create_all(bind=engine)
logging.info("Tablas verificadas/creadas correctamente.")
except Exception as e:
logging.error(f"Error al inicializar la base de datos: {e}")
_disable_db_logging("no se pudo inicializar la base de datos (se omitirán logs).")
# No propagamos para que el bot pueda seguir levantando aunque no haya DB
# Función para registrar una solicitud en la base de datos
def log_request(telegram_id, username, command, message):
if not SessionLocal:
logging.debug("Log de DB omitido (DB no configurada).")
return
try:
db_session = SessionLocal()
except Exception as exc:
logging.error(f"No se pudo crear sesión DB, se deshabilita el log: {exc}")
_disable_db_logging("no se pudo abrir sesión")
return
try:
log_entry = RequestLog(
telegram_id=str(telegram_id),
username=username,
command=command,
message=message
)
db_session.add(log_entry)
db_session.commit()
logging.info(f"Log guardado: {command} de {username}")
except Exception as e:
logging.error(f"Error al guardar el log: {e}")
db_session.rollback()
finally:
db_session.close()
# Inicializar la base de datos al arrancar el módulo
init_db()