Refactor caching and Telegram integration

- Updated configuration to enforce Redis caching for notified articles, removing memory cache options.
- Enhanced wallamonitor.py to load Redis cache settings and handle errors more effectively.
- Implemented new API endpoints for clearing Redis cache and retrieving Telegram forum topics.
- Improved frontend components to support fetching and displaying available Telegram threads.
- Added functionality for clearing cache from the UI, ensuring better management of notified articles.
This commit is contained in:
Omar Sánchez Pizarro
2026-01-19 21:24:46 +01:00
parent 96db30ff00
commit 5cc96a2371
7 changed files with 382 additions and 88 deletions

View File

@@ -1,40 +1,8 @@
import logging
import redis
import json
from collections import deque
NOTIFIED_ARTICLE_TTL = 7 * 24 * 60 * 60 # TTL de 7 días en segundos para artículos notificados (solo Redis)
DEFAULT_MEMORY_LIMIT = 300 # Límite por defecto de artículos en memoria
class MemoryArticleCache:
"""Maneja el cache de artículos notificados usando memoria (lista con límite)"""
def __init__(self, limit=DEFAULT_MEMORY_LIMIT):
self.logger = logging.getLogger(__name__)
self._notified_articles = deque(maxlen=limit)
self._limit = limit
self.logger.info(f"Cache de artículos en memoria inicializado (límite: {limit})")
def is_article_notified(self, article):
"""Verifica si un artículo ya ha sido notificado"""
return article in self._notified_articles
def mark_article_as_notified(self, article):
"""Marca un artículo como notificado en memoria"""
if article not in self._notified_articles:
self._notified_articles.append(article)
self.logger.debug(f"Artículo marcado como notificado (total en memoria: {len(self._notified_articles)})")
def mark_articles_as_notified(self, articles):
"""Añade múltiples artículos a la lista de artículos ya notificados en memoria"""
article_list = articles if isinstance(articles, list) else [articles]
added = 0
for article in article_list:
if article not in self._notified_articles:
self._notified_articles.append(article)
added += 1
self.logger.debug(f"{added} artículos marcados como notificados (total en memoria: {len(self._notified_articles)}/{self._limit})")
NOTIFIED_ARTICLE_TTL = 7 * 24 * 60 * 60 # TTL de 7 días en segundos para artículos notificados
class RedisArticleCache:
"""Maneja el cache de artículos notificados usando Redis"""
@@ -115,10 +83,30 @@ class RedisArticleCache:
article_list = articles if isinstance(articles, list) else [articles]
try:
# Usar pipeline para mejor rendimiento al añadir múltiples artículos
# Verificar qué artículos ya existen antes de añadirlos
# Usar pipeline para mejor rendimiento al verificar múltiples artículos
pipe = self._redis_client.pipeline()
keys_to_check = []
for article in article_list:
key = self._get_article_key(article)
keys_to_check.append((article, key))
pipe.exists(key)
# Ejecutar las verificaciones
exists_results = pipe.execute()
# Ahora añadir solo los artículos que no existen
pipe = self._redis_client.pipeline()
added_count = 0
skipped_count = 0
for (article, key), exists in zip(keys_to_check, exists_results):
if exists > 0:
# El artículo ya existe, no hacer nada
skipped_count += 1
continue
# El artículo no existe, añadirlo
# Guardar toda la información del artículo como JSON
article_data = {
'id': article.get_id(),
@@ -135,8 +123,13 @@ class RedisArticleCache:
'is_favorite': False, # Por defecto no es favorito
}
pipe.setex(key, NOTIFIED_ARTICLE_TTL, json.dumps(article_data))
pipe.execute()
self.logger.debug(f"{len(article_list)} artículos marcados como notificados en Redis")
added_count += 1
# Ejecutar solo si hay artículos para añadir
if added_count > 0:
pipe.execute()
self.logger.debug(f"{added_count} artículos añadidos, {skipped_count} ya existían en Redis")
except Exception as e:
self.logger.error(f"Error añadiendo artículos a Redis: {e}")
@@ -191,31 +184,53 @@ class RedisArticleCache:
except Exception as e:
self.logger.error(f"Error verificando favorito en Redis: {e}")
return False
def clear_cache(self):
"""Elimina toda la caché de artículos notificados en Redis"""
try:
# Obtener todas las claves que empiezan con 'notified:'
keys = self._redis_client.keys('notified:*')
if not keys:
self.logger.info("Cache de Redis ya está vacío")
return 0
# Eliminar todas las claves usando pipeline para mejor rendimiento
count = len(keys)
pipe = self._redis_client.pipeline()
for key in keys:
pipe.delete(key)
pipe.execute()
self.logger.info(f"Cache de Redis limpiado: {count} artículos eliminados")
return count
except Exception as e:
self.logger.error(f"Error limpiando cache de Redis: {e}")
return 0
def create_article_cache(cache_type='memory', **kwargs):
def create_article_cache(cache_type='redis', **kwargs):
"""
Factory function para crear el cache de artículos apropiado.
Factory function para crear el cache de artículos usando Redis.
Args:
cache_type: 'memory' o 'redis'
**kwargs: Argumentos adicionales según el tipo de cache:
- Para 'memory': limit (opcional, default=300)
- Para 'redis': redis_host, redis_port, redis_db, redis_password
cache_type: 'redis' (solo Redis está soportado)
**kwargs: Argumentos para Redis:
- redis_host: host de Redis (default: 'localhost')
- redis_port: puerto de Redis (default: 6379)
- redis_db: base de datos de Redis (default: 0)
- redis_password: contraseña de Redis (opcional)
Returns:
MemoryArticleCache o RedisArticleCache
RedisArticleCache
"""
if cache_type == 'memory':
limit = kwargs.get('limit', DEFAULT_MEMORY_LIMIT)
return MemoryArticleCache(limit=limit)
elif cache_type == 'redis':
return RedisArticleCache(
redis_host=kwargs.get('redis_host', 'localhost'),
redis_port=kwargs.get('redis_port', 6379),
redis_db=kwargs.get('redis_db', 0),
redis_password=kwargs.get('redis_password')
)
else:
raise ValueError(f"Tipo de cache desconocido: {cache_type}. Debe ser 'memory' o 'redis'")
if cache_type != 'redis':
raise ValueError(f"Tipo de cache desconocido: {cache_type}. Solo se soporta 'redis'")
return RedisArticleCache(
redis_host=kwargs.get('redis_host', 'localhost'),
redis_port=kwargs.get('redis_port', 6379),
redis_db=kwargs.get('redis_db', 0),
redis_password=kwargs.get('redis_password')
)