From b7a8f4f60d192a3b17268e7a729248955da8d81d Mon Sep 17 00:00:00 2001 From: vvzvlad Date: Tue, 10 Jun 2025 02:40:23 +0300 Subject: [PATCH] refactor requirements and rss_generator: replace direct get_chat calls with cached_get_chat for improved performance and update requirements.txt to uncomment Kurigram --- requirements.txt | 4 +- rss_generator.py | 12 +++-- tg_cache.py | 124 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 134 insertions(+), 6 deletions(-) create mode 100644 tg_cache.py diff --git a/requirements.txt b/requirements.txt index b9c2c2c..d67bb19 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,8 @@ fastapi==0.115.8 uvicorn==0.34.0 python-multipart==0.0.20 -#Kurigram -git+https://github.com/KurimuzonAkuma/pyrogram.git@dev#egg=Kurigram +Kurigram +#git+https://github.com/KurimuzonAkuma/pyrogram.git@dev#egg=Kurigram TgCrypto uvloop pillow==11.1.0 diff --git a/rss_generator.py b/rss_generator.py index c84e00d..dcd359b 100644 --- a/rss_generator.py +++ b/rss_generator.py @@ -333,10 +333,12 @@ async def generate_channel_rss(channel: str | int, channel_info_start_time = time.time() try: + from tg_cache import cached_get_chat + channel = post_parser.channel_name_prepare(channel) - channel_info = await post_parser.client.get_chat(channel) + channel_info = await cached_get_chat(post_parser.client, channel) channel_title = channel_info.title or f"Telegram: {channel}" - channel_username = post_parser.get_channel_username(channel_info) + channel_username = channel_info.username or (str(channel_info.id) if channel_info.id and str(channel_info.id).startswith('-100') else None) if not channel_username: # Use prepared channel (which could be int) for error feed if username fails logger.warning(f"Could not get username for channel {channel}, using identifier for error feed.") @@ -468,10 +470,12 @@ async def generate_channel_html(channel: str | int, channel_info_start_time = time.time() try: + from tg_cache import cached_get_chat + channel = post_parser.channel_name_prepare(channel) logger.debug(f"Prepared channel identifier for HTML: {channel} (type: {type(channel)})") # Log prepared channel - channel_info = await post_parser.client.get_chat(channel) - channel_username = post_parser.get_channel_username(channel_info) + channel_info = await cached_get_chat(post_parser.client, channel) + channel_username = channel_info.username or (str(channel_info.id) if channel_info.id and str(channel_info.id).startswith('-100') else None) if not channel_username: logger.warning(f"Could not get username for channel {channel} in HTML generation, returning error feed structure (as string). NOTE: This should ideally return HTML error page.") # For HTML, returning an error feed string might not be ideal. Consider returning a dedicated HTML error page. diff --git a/tg_cache.py b/tg_cache.py new file mode 100644 index 0000000..9742d41 --- /dev/null +++ b/tg_cache.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import os +import json +import pickle +import logging +import time +from datetime import datetime, timedelta +from typing import Any, Optional, Union +from pyrogram import Client +from pyrogram.types import Chat, Message + +logger = logging.getLogger(__name__) + +# Путь к директории кеша +CACHE_DIR = os.path.join('data', 'tgcache') + +def _get_cache_file_path(channel_id: Union[str, int]) -> str: + """Возвращает путь к файлу кеша для канала""" + if not os.path.exists(CACHE_DIR): + os.makedirs(CACHE_DIR, exist_ok=True) + logger.debug(f"cache_dir_created: path {CACHE_DIR}") + # Преобразуем в строку для унификации + channel_id_str = str(channel_id) + # Заменяем потенциально проблемные символы + safe_filename = channel_id_str.replace('/', '_').replace('\\', '_') + return os.path.join(CACHE_DIR, f"{safe_filename}.cache") + +def _save_to_cache(channel_id: Union[str, int], chat_data: Chat) -> None: + """Сохраняет данные чата в кеш""" + try: + cache_file = _get_cache_file_path(channel_id) + + # Создаем метаданные кеша + cache_data = { + 'timestamp': time.time(), + 'chat_data': pickle.dumps(chat_data) + } + + with open(cache_file, 'wb') as f: + pickle.dump(cache_data, f) + + logger.debug(f"chat_cache_saved: channel {channel_id}, file {cache_file}") + except Exception as e: + logger.error(f"cache_save_error: channel {channel_id}, error {str(e)}") + +def _get_from_cache(channel_id: Union[str, int], max_age_seconds: int = 86400) -> Optional[Chat]: + """ + Получает данные чата из кеша если они не старше указанного возраста + + Args: + channel_id: ID или username канала + max_age_seconds: Максимальный возраст кеша в секундах (по умолчанию 1 день) + + Returns: + Chat объект или None если кеш не найден или устарел + """ + try: + cache_file = _get_cache_file_path(channel_id) + + if not os.path.exists(cache_file): + logger.debug(f"chat_cache_miss: channel {channel_id}, cache file not found") + return None + + with open(cache_file, 'rb') as f: + cache_data = pickle.load(f) + + # Проверяем возраст кеша + cache_age = time.time() - cache_data['timestamp'] + if cache_age > max_age_seconds: + logger.debug(f"chat_cache_expired: channel {channel_id}, age {cache_age:.1f}s > max {max_age_seconds}s") + return None + + # Восстанавливаем объект Chat из пикла + chat_data = pickle.loads(cache_data['chat_data']) + logger.debug(f"chat_cache_hit: channel {channel_id}, age {cache_age:.1f}s") + return chat_data + + except Exception as e: + logger.error(f"cache_read_error: channel {channel_id}, error {str(e)}") + # В случае ошибки чтения кеша лучше вернуть None и запросить свежие данные + return None + +def adapt_chat_for_get_username(chat: Chat) -> Chat: + """ + Адаптирует объект Chat для использования в методе get_channel_username + + Метод get_channel_username ожидает объект Message, но использует только его атрибут chat, + поэтому мы можем передать сам объект Chat без обертки + """ + return chat + +async def cached_get_chat(client: Client, channel_id: Union[str, int], cache_ttl: int = 86400) -> Chat: + """ + Получает информацию о чате с кешированием. + + Args: + client: Pyrogram клиент + channel_id: ID или username канала + cache_ttl: Время жизни кеша в секундах (по умолчанию 1 день) + + Returns: + Chat объект, как и оригинальный client.get_chat() + """ + # Пробуем получить из кеша + cached_chat = _get_from_cache(channel_id, cache_ttl) + + if cached_chat is not None: + return cached_chat + + # Если в кеше нет или кеш устарел, запрашиваем через API + try: + logger.debug(f"chat_cache_request: fetching fresh data for channel {channel_id}") + chat_data = await client.get_chat(channel_id) + + # Сохраняем в кеш + _save_to_cache(channel_id, chat_data) + + return chat_data + except Exception as e: + logger.error(f"chat_request_error: channel {channel_id}, error {str(e)}") + # Пробрасываем ошибку дальше, как и оригинальный метод + raise