init commit
This commit is contained in:
281
app/handlers/telethon_client.py
Normal file
281
app/handlers/telethon_client.py
Normal file
@@ -0,0 +1,281 @@
|
||||
import logging
|
||||
import os
|
||||
from typing import List, Optional, Dict
|
||||
from telethon import TelegramClient, events
|
||||
from telethon.tl.types import ChatMember, User
|
||||
from telethon.errors import (
|
||||
FloodWaitError, UserDeactivatedError, ChatAdminRequiredError,
|
||||
PeerIdInvalidError, ChannelInvalidError, UserNotParticipantError
|
||||
)
|
||||
from app.settings import Config
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TelethonClientManager:
|
||||
"""Менеджер для работы с Telethon клиентом"""
|
||||
|
||||
def __init__(self):
|
||||
self.client: Optional[TelegramClient] = None
|
||||
self.is_initialized = False
|
||||
|
||||
async def initialize(self) -> bool:
|
||||
"""Инициализировать Telethon клиент"""
|
||||
try:
|
||||
if not Config.USE_TELETHON:
|
||||
logger.warning("Telethon отключен в конфигурации")
|
||||
return False
|
||||
|
||||
if not (Config.TELETHON_API_ID and Config.TELETHON_API_HASH):
|
||||
logger.error("TELETHON_API_ID или TELETHON_API_HASH не установлены")
|
||||
return False
|
||||
|
||||
# Получить путь для сессии
|
||||
session_dir = os.path.join(os.path.dirname(__file__), '..', 'sessions')
|
||||
os.makedirs(session_dir, exist_ok=True)
|
||||
session_path = os.path.join(session_dir, 'telethon_session')
|
||||
|
||||
self.client = TelegramClient(
|
||||
session_path,
|
||||
api_id=Config.TELETHON_API_ID,
|
||||
api_hash=Config.TELETHON_API_HASH
|
||||
)
|
||||
|
||||
await self.client.connect()
|
||||
|
||||
# Проверить авторизацию
|
||||
if not await self.client.is_user_authorized():
|
||||
logger.error("Telethon клиент не авторизован. Требуется повторный вход")
|
||||
return False
|
||||
|
||||
self.is_initialized = True
|
||||
me = await self.client.get_me()
|
||||
logger.info(f"✅ Telethon клиент инициализирован: {me.first_name}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при инициализации Telethon: {e}")
|
||||
return False
|
||||
|
||||
async def shutdown(self):
|
||||
"""Остановить Telethon клиент"""
|
||||
if self.client and self.is_initialized:
|
||||
try:
|
||||
await self.client.disconnect()
|
||||
self.is_initialized = False
|
||||
logger.info("✅ Telethon клиент остановлен")
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при остановке Telethon: {e}")
|
||||
|
||||
async def send_message(self, chat_id: int, text: str,
|
||||
parse_mode: str = "html",
|
||||
disable_web_page_preview: bool = True) -> Optional[int]:
|
||||
"""
|
||||
Отправить сообщение в чат
|
||||
|
||||
Returns:
|
||||
Optional[int]: ID отправленного сообщения или None при ошибке
|
||||
"""
|
||||
if not self.is_initialized:
|
||||
logger.error("Telethon клиент не инициализирован")
|
||||
return None
|
||||
|
||||
try:
|
||||
message = await self.client.send_message(
|
||||
chat_id,
|
||||
text,
|
||||
parse_mode=parse_mode,
|
||||
link_preview=not disable_web_page_preview
|
||||
)
|
||||
logger.info(f"✅ Сообщение отправлено в чат {chat_id} (Telethon)")
|
||||
return message.id
|
||||
|
||||
except FloodWaitError as e:
|
||||
logger.warning(f"⏳ FloodWait: нужно ждать {e.seconds} секунд")
|
||||
raise
|
||||
|
||||
except (ChatAdminRequiredError, UserNotParticipantError):
|
||||
logger.error(f"❌ Клиент не администратор или не участник чата {chat_id}")
|
||||
return None
|
||||
|
||||
except PeerIdInvalidError:
|
||||
logger.error(f"❌ Неверный ID чата: {chat_id}")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Ошибка при отправке сообщения: {e}")
|
||||
return None
|
||||
|
||||
async def get_chat_members(self, chat_id: int, limit: int = None) -> List[Dict]:
|
||||
"""
|
||||
Получить список участников чата
|
||||
|
||||
Returns:
|
||||
List[Dict]: Список участников с информацией
|
||||
"""
|
||||
if not self.is_initialized:
|
||||
logger.error("Telethon клиент не инициализирован")
|
||||
return []
|
||||
|
||||
try:
|
||||
members = []
|
||||
async for member in self.client.iter_participants(chat_id, limit=limit):
|
||||
member_info = {
|
||||
'user_id': str(member.id),
|
||||
'username': member.username,
|
||||
'first_name': member.first_name,
|
||||
'last_name': member.last_name,
|
||||
'is_bot': member.bot,
|
||||
'is_admin': member.is_self, # self-check для упрощения
|
||||
}
|
||||
members.append(member_info)
|
||||
|
||||
logger.info(f"✅ Получено {len(members)} участников из чата {chat_id}")
|
||||
return members
|
||||
|
||||
except (ChatAdminRequiredError, UserNotParticipantError):
|
||||
logger.error(f"❌ Нет прав получить участников чата {chat_id}")
|
||||
return []
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Ошибка при получении участников: {e}")
|
||||
return []
|
||||
|
||||
async def get_chat_info(self, chat_id: int) -> Optional[Dict]:
|
||||
"""Получить информацию о чате"""
|
||||
if not self.is_initialized:
|
||||
logger.error("Telethon клиент не инициализирован")
|
||||
return None
|
||||
|
||||
try:
|
||||
chat = await self.client.get_entity(chat_id)
|
||||
|
||||
members_count = None
|
||||
if hasattr(chat, 'participants_count'):
|
||||
members_count = chat.participants_count
|
||||
|
||||
return {
|
||||
'id': chat.id,
|
||||
'title': chat.title if hasattr(chat, 'title') else str(chat.id),
|
||||
'description': chat.about if hasattr(chat, 'about') else None,
|
||||
'members_count': members_count,
|
||||
'is_supergroup': hasattr(chat, 'megagroup') and chat.megagroup,
|
||||
'is_channel': hasattr(chat, 'broadcast'),
|
||||
'is_group': hasattr(chat, 'gigagroup')
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Ошибка при получении информации о чате {chat_id}: {e}")
|
||||
return None
|
||||
|
||||
async def join_chat(self, chat_link: str) -> Optional[int]:
|
||||
"""
|
||||
Присоединиться к чату по ссылке
|
||||
|
||||
Returns:
|
||||
Optional[int]: ID чата или None при ошибке
|
||||
"""
|
||||
if not self.is_initialized:
|
||||
logger.error("Telethon клиент не инициализирован")
|
||||
return None
|
||||
|
||||
try:
|
||||
# Попытаться присоединиться
|
||||
result = await self.client(ImportChatInviteRequest(hash))
|
||||
chat_id = result.chats[0].id
|
||||
logger.info(f"✅ Присоединился к чату: {chat_id}")
|
||||
return chat_id
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Ошибка при присоединении к чату: {e}")
|
||||
return None
|
||||
|
||||
async def leave_chat(self, chat_id: int) -> bool:
|
||||
"""Покинуть чат"""
|
||||
if not self.is_initialized:
|
||||
logger.error("Telethon клиент не инициализирован")
|
||||
return False
|
||||
|
||||
try:
|
||||
await self.client.delete_dialog(chat_id, revoke=True)
|
||||
logger.info(f"✅ Покинул чат: {chat_id}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Ошибка при выходе из чата: {e}")
|
||||
return False
|
||||
|
||||
async def edit_message(self, chat_id: int, message_id: int, text: str) -> Optional[int]:
|
||||
"""
|
||||
Отредактировать сообщение
|
||||
|
||||
Returns:
|
||||
Optional[int]: ID отредактированного сообщения или None при ошибке
|
||||
"""
|
||||
if not self.is_initialized:
|
||||
logger.error("Telethon клиент не инициализирован")
|
||||
return None
|
||||
|
||||
try:
|
||||
message = await self.client.edit_message(
|
||||
chat_id,
|
||||
message_id,
|
||||
text,
|
||||
parse_mode="html"
|
||||
)
|
||||
logger.info(f"✅ Сообщение отредактировано: {chat_id}/{message_id}")
|
||||
return message.id
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Ошибка при редактировании сообщения: {e}")
|
||||
return None
|
||||
|
||||
async def delete_message(self, chat_id: int, message_id: int) -> bool:
|
||||
"""Удалить сообщение"""
|
||||
if not self.is_initialized:
|
||||
logger.error("Telethon клиент не инициализирован")
|
||||
return False
|
||||
|
||||
try:
|
||||
await self.client.delete_messages(chat_id, message_id)
|
||||
logger.info(f"✅ Сообщение удалено: {chat_id}/{message_id}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Ошибка при удалении сообщения: {e}")
|
||||
return False
|
||||
|
||||
async def search_messages(self, chat_id: int, query: str, limit: int = 100) -> List[Dict]:
|
||||
"""
|
||||
Искать сообщения в чате
|
||||
|
||||
Returns:
|
||||
List[Dict]: Список найденных сообщений
|
||||
"""
|
||||
if not self.is_initialized:
|
||||
logger.error("Telethon клиент не инициализирован")
|
||||
return []
|
||||
|
||||
try:
|
||||
messages = []
|
||||
async for message in self.client.iter_messages(chat_id, search=query, limit=limit):
|
||||
messages.append({
|
||||
'id': message.id,
|
||||
'text': message.text,
|
||||
'date': message.date
|
||||
})
|
||||
|
||||
logger.info(f"✅ Найдено {len(messages)} сообщений по запросу '{query}'")
|
||||
return messages
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Ошибка при поиске сообщений: {e}")
|
||||
return []
|
||||
|
||||
def is_connected(self) -> bool:
|
||||
"""Проверить, подключен ли клиент"""
|
||||
return self.is_initialized and self.client is not None
|
||||
|
||||
|
||||
# Глобальный экземпляр менеджера
|
||||
telethon_manager = TelethonClientManager()
|
||||
Reference in New Issue
Block a user