139 lines
6.3 KiB
Python
139 lines
6.3 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# flake8: noqa
|
|
# pylint: disable=broad-exception-raised, raise-missing-from, too-many-arguments, redefined-outer-name
|
|
# pylint: disable=multiple-statements, logging-fstring-interpolation, trailing-whitespace, line-too-long
|
|
# pylint: disable=broad-exception-caught, missing-function-docstring, missing-class-docstring
|
|
# pylint: disable=f-string-without-interpolation
|
|
# pylance: disable=reportMissingImports, reportMissingModuleSource
|
|
|
|
|
|
import os
|
|
import json
|
|
import pickle
|
|
import logging
|
|
import random
|
|
import time
|
|
from datetime import datetime, timedelta
|
|
from typing import Any, Optional, Union, List
|
|
from pyrogram import Client
|
|
from pyrogram.types import Chat, Message
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Путь к директории кеша
|
|
CACHE_DIR = os.path.join('data', 'tgcache')
|
|
|
|
def _get_history_cache_file_path(channel_id: Union[str, int]) -> str:
|
|
"""Возвращает путь к файлу кеша истории сообщений для канала"""
|
|
if not os.path.exists(CACHE_DIR):
|
|
os.makedirs(CACHE_DIR, exist_ok=True)
|
|
logger.info(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_history_to_cache(channel_id: Union[str, int], messages: List[Message], limit: int) -> None:
|
|
"""Сохраняет историю сообщений в кеш"""
|
|
try:
|
|
cache_file = _get_history_cache_file_path(channel_id)
|
|
|
|
# Создаем метаданные кеша
|
|
cache_data = {
|
|
'timestamp': time.time(),
|
|
'limit': limit,
|
|
'messages': pickle.dumps(messages)
|
|
}
|
|
|
|
with open(cache_file, 'wb') as f:
|
|
pickle.dump(cache_data, f)
|
|
|
|
logger.info(f"history_cache_saved: channel {channel_id}, limit {limit}, messages {len(messages)}, file {cache_file}")
|
|
except Exception as e:
|
|
logger.error(f"history_cache_save_error: channel {channel_id}, limit {limit}, error {str(e)}")
|
|
|
|
def _get_history_from_cache(channel_id: Union[str, int], limit: int, max_age_hours: int = 8) -> Optional[List[Message]]:
|
|
"""
|
|
Получает историю сообщений из кеша если они не старше указанного возраста и соответствуют лимиту
|
|
|
|
Args:
|
|
channel_id: ID или username канала
|
|
limit: Требуемый лимит сообщений
|
|
max_age_hours: Максимальный возраст кеша в часах (по умолчанию 8 час)
|
|
|
|
Returns:
|
|
Список сообщений или None если кеш не найден, устарел или лимит не соответствует
|
|
"""
|
|
try:
|
|
cache_file = _get_history_cache_file_path(channel_id)
|
|
|
|
if not os.path.exists(cache_file):
|
|
logger.info(f"history_cache_miss: channel {channel_id}, limit {limit}, 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']
|
|
# Добавляем случайность до 20% от max_age_seconds
|
|
random_factor = 1 - random.uniform(0, 0.2)
|
|
adjusted_max_age = max_age_hours * 3600 * random_factor
|
|
|
|
if cache_age > adjusted_max_age:
|
|
logger.info(f"history_cache_expired: channel {channel_id}, limit {limit}, age {cache_age:.1f}s > adjusted max {adjusted_max_age:.1f}s (random factor: {random_factor:.2f})")
|
|
return None
|
|
|
|
# Проверяем соответствие лимита
|
|
cached_limit = cache_data.get('limit', 0)
|
|
if cached_limit != limit:
|
|
logger.info(f"history_cache_limit_mismatch: channel {channel_id}, cached limit {cached_limit}, requested limit {limit}")
|
|
return None
|
|
|
|
# Восстанавливаем список сообщений из пикла
|
|
messages = pickle.loads(cache_data['messages'])
|
|
logger.info(f"history_cache_hit: channel {channel_id}, limit {limit}, messages {len(messages)}, age {cache_age:.1f}s")
|
|
return messages
|
|
|
|
except Exception as e:
|
|
logger.error(f"history_cache_read_error: channel {channel_id}, limit {limit}, error {str(e)}")
|
|
# В случае ошибки чтения кеша лучше вернуть None и запросить свежие данные
|
|
return None
|
|
|
|
async def cached_get_chat_history(client: Client, channel_id: Union[str, int], limit: int = 20) -> List[Message]:
|
|
"""
|
|
Получает историю сообщений чата с кешированием.
|
|
|
|
Args:
|
|
client: Pyrogram клиент
|
|
channel_id: ID или username канала
|
|
limit: Максимальное количество сообщений для получения
|
|
|
|
Returns:
|
|
Список сообщений, как и оригинальный client.get_chat_history()
|
|
"""
|
|
# Пробуем получить из кеша
|
|
cached_messages = _get_history_from_cache(channel_id, limit)
|
|
|
|
if cached_messages is not None:
|
|
return cached_messages
|
|
|
|
# Если в кеше нет или кеш устарел, запрашиваем через API
|
|
try:
|
|
logger.info(f"history_cache_request: fetching fresh history for channel {channel_id}, limit {limit}")
|
|
messages = []
|
|
async for message in client.get_chat_history(channel_id, limit=limit):
|
|
messages.append(message)
|
|
|
|
# Сохраняем в кеш
|
|
_save_history_to_cache(channel_id, messages, limit)
|
|
|
|
return messages
|
|
except Exception as e:
|
|
logger.error(f"history_cache_request_error: channel {channel_id}, limit {limit}, error {str(e)}")
|
|
# Пробрасываем ошибку дальше, как и оригинальный метод
|
|
raise
|