import logging import json import re from typing import List, Dict, Optional from datetime import datetime from telegram.ext import ContextTypes from app.handlers.telethon_client import telethon_manager from app.database.member_repository import GroupKeywordRepository, GroupStatisticsRepository from app.database.repository import GroupRepository, MessageGroupRepository logger = logging.getLogger(__name__) class GroupParser: """Парсер для поиска и анализа групп по ключевым словам""" def __init__(self, db_session, bot=None): self.db_session = db_session self.bot = bot self.keyword_repo = GroupKeywordRepository(db_session) self.stats_repo = GroupStatisticsRepository(db_session) self.group_repo = GroupRepository(db_session) async def parse_group_by_keywords(self, keywords: List[str], chat_id: int) -> Dict: """ Проанализировать группу и проверить совпадение с ключевыми словами Args: keywords: Список ключевых слов для поиска chat_id: ID группы в Telegram Returns: dict: Результаты анализа группы """ if not telethon_manager.is_connected(): logger.warning("Telethon клиент не подключен, не могу получить информацию о группе") return {'matched': False, 'keywords_found': []} try: chat_info = await telethon_manager.get_chat_info(chat_id) if not chat_info: return {'matched': False, 'keywords_found': []} # Объединить название и описание для поиска search_text = f"{chat_info.get('title', '')} {chat_info.get('description', '')}".lower() # Найти совпадения ключевых слов matched_keywords = [] for keyword in keywords: if keyword.lower() in search_text: matched_keywords.append(keyword) result = { 'matched': len(matched_keywords) > 0, 'keywords_found': matched_keywords, 'chat_info': chat_info, 'match_count': len(matched_keywords), 'total_keywords': len(keywords), 'match_percentage': (len(matched_keywords) / len(keywords) * 100) if keywords else 0 } logger.info(f"✅ Анализ группы {chat_id}: найдено {len(matched_keywords)} совпадений из {len(keywords)}") return result except Exception as e: logger.error(f"❌ Ошибка при анализе группы {chat_id}: {e}") return {'matched': False, 'keywords_found': []} async def extract_keywords_from_text(self, text: str) -> List[str]: """ Извлечь ключевые слова из текста Args: text: Текст для извлечения ключевых слов Returns: List[str]: Список ключевых слов """ # Удалить спецсимволы и разбить на слова words = re.findall(r'\b\w+\b', text.lower()) # Отфильтровать стоп-слова stop_words = {'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'the', 'is', 'are', 'was', 'were', 'be', 'been', 'being', 'я', 'ты', 'он', 'она', 'оно', 'мы', 'вы', 'они', 'и', 'или', 'но', 'в', 'на', 'к', 'по', 'с', 'о', 'об', 'что', 'как', 'где', 'когда', 'зачем', 'откуда', 'куда'} keywords = [w for w in words if len(w) > 3 and w not in stop_words] # Убрать дубликаты return list(set(keywords)) async def parse_group_members(self, chat_id: int, member_repo, limit: int = 100) -> Dict: """ Получить и сохранить список участников группы Args: chat_id: ID группы member_repo: Репозиторий участников limit: Максимум участников для загрузки Returns: dict: Статистика загруженных участников """ if not telethon_manager.is_connected(): logger.warning("Telethon клиент не подключен, не могу получить участников") return {'success': False, 'members_added': 0} try: # Получить группу из БД db_group = await self.group_repo.get_by_chat_id(str(chat_id)) if not db_group: logger.warning(f"Группа {chat_id} не найдена в БД") return {'success': False, 'members_added': 0} # Получить участников members = await telethon_manager.get_chat_members(chat_id, limit) if not members: return {'success': True, 'members_added': 0} # Сохранить в БД members_data = members # Уже в нужном формате из telethon_manager # Очистить старых участников и добавить новых await member_repo.clear_members(db_group.id) added = await member_repo.bulk_add_members(db_group.id, members_data) # Обновить статистику admins = len([m for m in members_data if m.get('is_admin')]) bots = len([m for m in members_data if m.get('is_bot')]) await self.stats_repo.update_members_count( db_group.id, total=len(members_data), admins=admins, bots=bots ) result = { 'success': True, 'members_added': len(added), 'admins_count': admins, 'bots_count': bots, 'users_count': len(members_data) - bots } logger.info(f"✅ Загружены участники группы {chat_id}: {result}") return result except Exception as e: logger.error(f"❌ Ошибка при загрузке участников группы {chat_id}: {e}") return {'success': False, 'members_added': 0} async def search_groups_by_keywords(self, keywords: List[str], group_ids: List[int] = None) -> Dict: """ Искать группы по ключевым словам из списка Args: keywords: Список ключевых слов для поиска group_ids: Список ID групп для проверки (если None - проверить все) Returns: dict: Результаты поиска """ if not group_ids: # Получить все активные группы all_groups = await self.group_repo.get_active_groups() group_ids = [g.id for g in all_groups] results = { 'total_checked': len(group_ids), 'matched_groups': [], 'no_match': [], 'errors': [] } for group_id in group_ids: try: # Получить группу db_group = await self.group_repo.get_by_id(group_id) if not db_group: results['errors'].append({'group_id': group_id, 'error': 'Not found in DB'}) continue # Анализировать match_result = await self.parse_group_by_keywords(keywords, int(db_group.chat_id)) if match_result['matched']: results['matched_groups'].append({ 'group_id': group_id, 'chat_id': db_group.chat_id, 'title': db_group.title, 'keywords_found': match_result['keywords_found'], 'match_percentage': match_result['match_percentage'] }) else: results['no_match'].append({ 'group_id': group_id, 'chat_id': db_group.chat_id, 'title': db_group.title }) except Exception as e: logger.error(f"Ошибка при проверке группы {group_id}: {e}") results['errors'].append({'group_id': group_id, 'error': str(e)}) logger.info(f"Поиск по ключевым словам завершен: найдено {len(results['matched_groups'])} групп") return results async def set_group_keywords(self, group_id: int, keywords: List[str], description: str = None) -> bool: """ Установить ключевые слова для группы Args: group_id: ID группы в БД keywords: Список ключевых слов description: Описание для поиска Returns: bool: Успешность операции """ try: # Сериализовать список в JSON keywords_json = json.dumps(keywords) # Проверить наличие записи existing = await self.keyword_repo.get_keywords(group_id) if existing: await self.keyword_repo.update_keywords(group_id, keywords_json, description) else: await self.keyword_repo.add_keywords(group_id, keywords_json, description) logger.info(f"Ключевые слова установлены для группы {group_id}: {keywords}") return True except Exception as e: logger.error(f"Ошибка при установке ключевых слов: {e}") return False async def get_group_keywords(self, group_id: int) -> Optional[List[str]]: """ Получить ключевые слова для группы Args: group_id: ID группы в БД Returns: List[str]: Список ключевых слов или None """ try: keyword_obj = await self.keyword_repo.get_keywords(group_id) if not keyword_obj: return None return json.loads(keyword_obj.keywords) except Exception as e: logger.error(f"Ошибка при получении ключевых слов: {e}") return None async def format_group_info(self, group_id: int) -> str: """ Форматировать информацию о группе для вывода Args: group_id: ID группы в БД Returns: str: Отформатированная информация """ try: group = await self.group_repo.get_by_id(group_id) if not group: return "Группа не найдена" stats = await self.stats_repo.get_statistics(group_id) keywords = await self.get_group_keywords(group_id) info = f"Группа: {group.title}\n" info += f"Chat ID: {group.chat_id}\n" info += f"Активна: {'✅ Да' if group.is_active else '❌ Нет'}\n" if stats: info += f"\nСтатистика:\n" info += f" Участников: {stats.total_members}\n" info += f" Администраторов: {stats.total_admins}\n" info += f" Ботов: {stats.total_bots}\n" info += f" Отправлено: {stats.messages_sent}\n" info += f" Через клиент: {stats.messages_via_client}\n" info += f" Может отправлять как бот: {'✅' if stats.can_send_as_bot else '❌'}\n" info += f" Может отправлять как клиент: {'✅' if stats.can_send_as_client else '❌'}\n" if keywords: info += f"\nКлючевые слова:\n" for kw in keywords: info += f" • {kw}\n" return info except Exception as e: logger.error(f"Ошибка при форматировании информации: {e}") return "Ошибка при получении информации"