Signed-off-by: Omar Sánchez Pizarro <omar.sanchez@pistacero.net>
This commit is contained in:
Omar Sánchez Pizarro
2026-01-20 03:22:56 +01:00
parent 81bf0675ed
commit d28710b927
12 changed files with 1166 additions and 310 deletions

View File

@@ -26,10 +26,29 @@ ITEM_HTML = """
"""
class TelegramManager:
def __init__(self):
def __init__(self, token=None, channel=None, enable_polling=True, username=None):
"""
Inicializa TelegramManager con configuración específica
Args:
token: Token del bot de Telegram (si None, intenta leer de config.yaml)
channel: Canal de Telegram (si None, intenta leer de config.yaml)
enable_polling: Si iniciar el polling del bot
username: Usuario propietario de esta configuración (para logging)
"""
self.logger = logging.getLogger(__name__)
token, channel = self.get_config()
self._username = username
# Si no se proporcionan token/channel, intentar leer de config.yaml (compatibilidad)
if token is None or channel is None:
token, channel, enable_polling = self._get_config_from_file()
if not token or not channel:
raise ValueError("Token y channel de Telegram son requeridos")
self._channel = channel
self._token = token
# Use ApplicationBuilder to create the bot application with increased timeouts
self._application = telegram.ext.ApplicationBuilder() \
.token(token) \
@@ -42,26 +61,35 @@ class TelegramManager:
self._loop = asyncio.new_event_loop()
asyncio.set_event_loop(self._loop)
# Inicializar Redis para favoritos
self._article_cache = self._init_redis_cache()
# Inicializar MongoDB para favoritos
self._article_cache = self._init_mongodb_cache()
# Añadir handlers para comandos y callbacks
self._add_handlers()
# Iniciar polling en un thread separado
self._start_polling()
# Iniciar polling en un thread separado solo si está habilitado
if enable_polling:
self._start_polling()
else:
self.logger.info(f"Polling deshabilitado por configuración{' para usuario ' + username if username else ''}")
def get_config(self):
def _get_config_from_file(self):
"""Lee configuración de config.yaml (para compatibilidad hacia atrás)"""
base_dir = os.path.dirname(os.path.abspath(__file__))
config_file = os.path.join(os.path.dirname(base_dir), 'config.yaml')
with open(config_file, 'r') as file:
config = yaml.safe_load(file)
token = config['telegram_token']
telegram_channel = config['telegram_channel']
return token, telegram_channel
try:
with open(config_file, 'r') as file:
config = yaml.safe_load(file)
token = config.get('telegram_token')
telegram_channel = config.get('telegram_channel')
enable_polling = config.get('enable_polling', True)
return token, telegram_channel, enable_polling
except Exception as e:
self.logger.warning(f"No se pudo leer config.yaml: {e}")
return None, None, False
def _init_redis_cache(self):
"""Inicializa Redis cache para favoritos"""
def _init_mongodb_cache(self):
"""Inicializa MongoDB cache para favoritos"""
try:
base_dir = os.path.dirname(os.path.abspath(__file__))
config_file = os.path.join(os.path.dirname(base_dir), 'config.yaml')
@@ -69,20 +97,22 @@ class TelegramManager:
config = yaml.safe_load(file)
cache_config = config.get('cache', {})
if cache_config.get('type') == 'redis':
redis_config = cache_config.get('redis', {})
if cache_config.get('type') == 'mongodb':
mongodb_config = cache_config.get('mongodb', {})
return create_article_cache(
cache_type='redis',
redis_host=redis_config.get('host', 'localhost'),
redis_port=redis_config.get('port', 6379),
redis_db=redis_config.get('db', 0),
redis_password=redis_config.get('password')
cache_type='mongodb',
mongodb_host=os.environ.get('MONGODB_HOST') or mongodb_config.get('host', 'localhost'),
mongodb_port=int(os.environ.get('MONGODB_PORT') or mongodb_config.get('port', 27017)),
mongodb_database=os.environ.get('MONGODB_DATABASE') or mongodb_config.get('database', 'wallabicher'),
mongodb_username=os.environ.get('MONGODB_USERNAME') or mongodb_config.get('username'),
mongodb_password=os.environ.get('MONGODB_PASSWORD') or mongodb_config.get('password'),
mongodb_auth_source=mongodb_config.get('auth_source', 'admin')
)
else:
self.logger.warning("Redis no configurado para favoritos, se requiere Redis")
self.logger.warning("MongoDB no configurado para favoritos, se requiere MongoDB")
return None
except Exception as e:
self.logger.error(f"Error inicializando Redis para favoritos: {e}")
self.logger.error(f"Error inicializando MongoDB para favoritos: {e}")
return None
def escape_html(self, text):
@@ -198,7 +228,8 @@ class TelegramManager:
"""Inicia el bot en modo polling en un thread separado"""
def run_polling():
try:
self.logger.info("Iniciando polling de Telegram bot...")
user_info = f" para usuario '{self._username}'" if self._username else ""
self.logger.info(f"Iniciando polling de Telegram bot{user_info}...")
# Crear un nuevo event loop para este thread
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
@@ -208,11 +239,13 @@ class TelegramManager:
loop.create_task(self._application.updater.start_polling(allowed_updates=["message", "callback_query"]))
loop.run_forever()
except Exception as e:
self.logger.error(f"Error en polling: {e}")
user_info = f" (usuario: {self._username})" if self._username else ""
self.logger.error(f"Error en polling{user_info}: {e}")
polling_thread = threading.Thread(target=run_polling, daemon=True)
polling_thread.start()
self.logger.info("Thread de polling iniciado")
user_info = f" para usuario '{self._username}'" if self._username else ""
self.logger.info(f"Thread de polling iniciado{user_info}")
async def handle_favorite_callback(self, update: telegram.Update, context: telegram.ext.ContextTypes.DEFAULT_TYPE):
"""Maneja el callback cuando se presiona el botón de favoritos"""
@@ -220,7 +253,7 @@ class TelegramManager:
await query.answer()
if not self._article_cache:
await query.edit_message_text("Redis no está disponible para favoritos")
await query.edit_message_text("MongoDB no está disponible para favoritos")
return
# Extraer plataforma, ID del artículo y nombre de búsqueda del callback_data
@@ -241,23 +274,23 @@ class TelegramManager:
await query.message.reply_text(" Este artículo ya está en favoritos")
return
# Obtener la URL del artículo desde Redis
# Obtener la URL del artículo desde MongoDB
url = ""
try:
redis_client = self._article_cache._redis_client
key = f"notified:{platform}:{article_id}"
value = redis_client.get(key)
if value:
article_data = json.loads(value)
url = article_data.get('url', '')
article = self._article_cache._articles_collection.find_one({
'platform': platform,
'id': str(article_id)
})
if article:
url = article.get('url', '')
except Exception as e:
self.logger.debug(f"Error obteniendo URL: {e}")
# Marcar como favorito en Redis
# Marcar como favorito en MongoDB
success = self._article_cache.set_favorite(platform, article_id, is_favorite=True)
if not success:
await query.edit_message_text("❌ No se pudo encontrar el artículo en Redis")
await query.edit_message_text("❌ No se pudo encontrar el artículo en MongoDB")
return
# Actualizar el mensaje del botón
@@ -320,7 +353,7 @@ class TelegramManager:
async def handle_favs_command(self, update: telegram.Update, context: telegram.ext.ContextTypes.DEFAULT_TYPE):
"""Maneja el comando /favs para mostrar los favoritos"""
if not self._article_cache:
await update.message.reply_text("Redis no está disponible para favoritos")
await update.message.reply_text("MongoDB no está disponible para favoritos")
return
favorites = self._article_cache.get_favorites()
@@ -357,7 +390,7 @@ class TelegramManager:
await query.answer()
if not self._article_cache:
await query.edit_message_text("Redis no está disponible para favoritos")
await query.edit_message_text("MongoDB no está disponible para favoritos")
return
# Extraer plataforma e ID del artículo del callback_data
@@ -372,19 +405,19 @@ class TelegramManager:
platform = parts[1]
article_id = parts[2]
# Obtener la URL del artículo desde Redis antes de desmarcar
# Obtener la URL del artículo desde MongoDB antes de desmarcar
url = ""
try:
redis_client = self._article_cache._redis_client
key = f"notified:{platform}:{article_id}"
value = redis_client.get(key)
if value:
article_data = json.loads(value)
url = article_data.get('url', '')
article = self._article_cache._articles_collection.find_one({
'platform': platform,
'id': str(article_id)
})
if article:
url = article.get('url', '')
except Exception as e:
self.logger.debug(f"Error obteniendo URL: {e}")
# Desmarcar como favorito en Redis
# Desmarcar como favorito en MongoDB
success = self._article_cache.set_favorite(platform, article_id, is_favorite=False)
if not success: