api for telegram interaction
This commit is contained in:
parent
dd9015bc24
commit
922d226719
4 changed files with 399 additions and 0 deletions
5
telegram-bridge/api/__init__.py
Normal file
5
telegram-bridge/api/__init__.py
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
"""
|
||||||
|
API module for Telegram interaction.
|
||||||
|
|
||||||
|
Provides client, middleware, and models for interacting with the Telegram API.
|
||||||
|
"""
|
||||||
190
telegram-bridge/api/client.py
Normal file
190
telegram-bridge/api/client.py
Normal file
|
|
@ -0,0 +1,190 @@
|
||||||
|
"""
|
||||||
|
Telegram API client.
|
||||||
|
|
||||||
|
Handles communication with the Telegram API using the Telethon library.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
import asyncio
|
||||||
|
from typing import Optional, List, Dict, Any, Callable, Tuple
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from telethon import TelegramClient
|
||||||
|
from telethon.tl.types import User, Chat, Channel, Message, Dialog
|
||||||
|
from telethon.utils import get_display_name
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class TelegramApiClient:
|
||||||
|
"""Client for interacting with the Telegram API."""
|
||||||
|
|
||||||
|
def __init__(self, session_file: str, api_id: str, api_hash: str):
|
||||||
|
"""Initialize the Telegram API client.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
session_file: Path to the session file for authentication
|
||||||
|
api_id: Telegram API ID
|
||||||
|
api_hash: Telegram API hash
|
||||||
|
"""
|
||||||
|
self.session_file = session_file
|
||||||
|
self.api_id = api_id
|
||||||
|
self.api_hash = api_hash
|
||||||
|
self.client = TelegramClient(session_file, api_id, api_hash)
|
||||||
|
self._me = None
|
||||||
|
|
||||||
|
async def connect(self) -> bool:
|
||||||
|
"""Connect to the Telegram API.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if successfully connected, False otherwise
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
await self.client.connect()
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to connect to Telegram: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def is_authorized(self) -> bool:
|
||||||
|
"""Check if the client is authorized.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if authorized, False otherwise
|
||||||
|
"""
|
||||||
|
return await self.client.is_user_authorized()
|
||||||
|
|
||||||
|
async def send_code_request(self, phone: str) -> bool:
|
||||||
|
"""Send a code request to the given phone number.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
phone: Phone number to send code to
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if code sent successfully, False otherwise
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
await self.client.send_code_request(phone)
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to send code request: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def sign_in(self, phone: Optional[str] = None, code: Optional[str] = None,
|
||||||
|
password: Optional[str] = None) -> bool:
|
||||||
|
"""Sign in to Telegram.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
phone: Phone number (optional)
|
||||||
|
code: Verification code (optional)
|
||||||
|
password: Two-factor authentication password (optional)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if signed in successfully, False otherwise
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if password:
|
||||||
|
await self.client.sign_in(password=password)
|
||||||
|
else:
|
||||||
|
await self.client.sign_in(phone, code)
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to sign in: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def get_me(self) -> Optional[User]:
|
||||||
|
"""Get the current user.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
User: Current user object
|
||||||
|
"""
|
||||||
|
if not self._me:
|
||||||
|
self._me = await self.client.get_me()
|
||||||
|
return self._me
|
||||||
|
|
||||||
|
async def get_dialogs(self, limit: int = 100) -> List[Dialog]:
|
||||||
|
"""Get dialogs (chats) from Telegram.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
limit: Maximum number of dialogs to retrieve
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Dialog]: List of dialogs
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return await self.client.get_dialogs(limit=limit)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to get dialogs: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
async def get_entity(self, entity_id: Any) -> Optional[Any]:
|
||||||
|
"""Get an entity from Telegram.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
entity_id: ID of the entity to retrieve
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Any: Entity object (User, Chat, or Channel)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return await self.client.get_entity(entity_id)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to get entity: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def get_messages(self, entity: Any, limit: int = 100) -> List[Message]:
|
||||||
|
"""Get messages from a chat.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
entity: Chat entity
|
||||||
|
limit: Maximum number of messages to retrieve
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Message]: List of messages
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return await self.client.get_messages(entity, limit=limit)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to get messages: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
async def send_message(self, entity: Any, message: str) -> Optional[Message]:
|
||||||
|
"""Send a message to a chat.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
entity: Chat entity
|
||||||
|
message: Message text to send
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Message: Sent message object
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return await self.client.send_message(entity, message)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to send message: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def add_event_handler(self, callback: Callable, event: Any) -> None:
|
||||||
|
"""Add an event handler.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
callback: Callback function to handle the event
|
||||||
|
event: Event to handle
|
||||||
|
"""
|
||||||
|
self.client.add_event_handler(callback, event)
|
||||||
|
|
||||||
|
async def disconnect(self) -> None:
|
||||||
|
"""Disconnect from the Telegram API."""
|
||||||
|
await self.client.disconnect()
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
"""Cleanup when the client is deleted."""
|
||||||
|
try:
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
if loop.is_running():
|
||||||
|
loop.create_task(self.disconnect())
|
||||||
|
else:
|
||||||
|
loop.run_until_complete(self.disconnect())
|
||||||
|
except:
|
||||||
|
pass
|
||||||
155
telegram-bridge/api/middleware.py
Normal file
155
telegram-bridge/api/middleware.py
Normal file
|
|
@ -0,0 +1,155 @@
|
||||||
|
"""
|
||||||
|
Telegram API middleware.
|
||||||
|
|
||||||
|
Handles common operations between the application and Telegram API such as
|
||||||
|
authentication, error handling, and entity type conversion.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Dict, Any, Optional, Tuple, List, Callable
|
||||||
|
from datetime import datetime
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
|
from telethon.tl.types import User, Chat, Channel, Message, Dialog
|
||||||
|
from telethon.utils import get_display_name
|
||||||
|
|
||||||
|
from api.client import TelegramApiClient
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_telegram_errors(func):
|
||||||
|
"""Decorator to handle Telegram API errors."""
|
||||||
|
@wraps(func)
|
||||||
|
async def wrapper(*args, **kwargs):
|
||||||
|
try:
|
||||||
|
return await func(*args, **kwargs)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Telegram API error in {func.__name__}: {e}")
|
||||||
|
return None
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
class TelegramMiddleware:
|
||||||
|
"""Middleware for Telegram API operations."""
|
||||||
|
|
||||||
|
def __init__(self, client: TelegramApiClient):
|
||||||
|
"""Initialize the middleware with a Telegram client.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
client: Initialized Telegram API client
|
||||||
|
"""
|
||||||
|
self.client = client
|
||||||
|
|
||||||
|
async def process_chat_entity(self, entity: Any) -> Dict[str, Any]:
|
||||||
|
"""Process a chat entity and convert it to a dictionary.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
entity: Chat entity from Telegram API
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict: Standardized chat representation
|
||||||
|
"""
|
||||||
|
if isinstance(entity, User):
|
||||||
|
chat_type = "user"
|
||||||
|
title = get_display_name(entity)
|
||||||
|
username = entity.username
|
||||||
|
elif isinstance(entity, Chat):
|
||||||
|
chat_type = "group"
|
||||||
|
title = entity.title
|
||||||
|
username = None
|
||||||
|
elif isinstance(entity, Channel):
|
||||||
|
chat_type = "channel" if entity.broadcast else "supergroup"
|
||||||
|
title = entity.title
|
||||||
|
username = entity.username
|
||||||
|
else:
|
||||||
|
logger.warning(f"Unknown chat type: {type(entity)}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"id": entity.id,
|
||||||
|
"title": title,
|
||||||
|
"username": username,
|
||||||
|
"type": chat_type
|
||||||
|
}
|
||||||
|
|
||||||
|
@handle_telegram_errors
|
||||||
|
async def process_dialog(self, dialog: Dialog) -> Dict[str, Any]:
|
||||||
|
"""Process a dialog and convert it to a dictionary.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
dialog: Dialog from Telegram API
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict: Standardized dialog representation
|
||||||
|
"""
|
||||||
|
chat_info = await self.process_chat_entity(dialog.entity)
|
||||||
|
chat_info["last_message_time"] = dialog.date
|
||||||
|
return chat_info
|
||||||
|
|
||||||
|
@handle_telegram_errors
|
||||||
|
async def process_message(self, message: Message) -> Optional[Dict[str, Any]]:
|
||||||
|
"""Process a message and convert it to a dictionary.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
message: Message from Telegram API
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict: Standardized message representation
|
||||||
|
"""
|
||||||
|
if not message.text:
|
||||||
|
return None # Skip non-text messages
|
||||||
|
|
||||||
|
# Get the chat
|
||||||
|
chat = message.chat
|
||||||
|
if not chat:
|
||||||
|
return None
|
||||||
|
|
||||||
|
chat_info = await self.process_chat_entity(chat)
|
||||||
|
|
||||||
|
# Get sender information
|
||||||
|
sender = await message.get_sender()
|
||||||
|
sender_id = sender.id if sender else 0
|
||||||
|
sender_name = get_display_name(sender) if sender else "Unknown"
|
||||||
|
|
||||||
|
# Check if the message is from the current user
|
||||||
|
my_id = (await self.client.get_me()).id
|
||||||
|
is_from_me = sender_id == my_id
|
||||||
|
|
||||||
|
return {
|
||||||
|
"id": message.id,
|
||||||
|
"chat_id": chat_info["id"],
|
||||||
|
"chat_title": chat_info["title"],
|
||||||
|
"sender_id": sender_id,
|
||||||
|
"sender_name": sender_name,
|
||||||
|
"content": message.text,
|
||||||
|
"timestamp": message.date,
|
||||||
|
"is_from_me": is_from_me
|
||||||
|
}
|
||||||
|
|
||||||
|
@handle_telegram_errors
|
||||||
|
async def find_entity_by_name_or_id(self, recipient: str) -> Optional[Any]:
|
||||||
|
"""Find an entity by name or ID.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
recipient: Recipient identifier (ID, username, or title)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Any: Found entity or None
|
||||||
|
"""
|
||||||
|
# Try to parse as an integer (chat ID)
|
||||||
|
try:
|
||||||
|
chat_id = int(recipient)
|
||||||
|
return await self.client.get_entity(chat_id)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Not an integer, try as username
|
||||||
|
if recipient.startswith("@"):
|
||||||
|
recipient = recipient[1:] # Remove @ if present
|
||||||
|
|
||||||
|
try:
|
||||||
|
return await self.client.get_entity(recipient)
|
||||||
|
except Exception:
|
||||||
|
logger.error(f"Could not find entity: {recipient}")
|
||||||
|
return None
|
||||||
49
telegram-bridge/api/models.py
Normal file
49
telegram-bridge/api/models.py
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
"""
|
||||||
|
API models for data transfer.
|
||||||
|
|
||||||
|
Defines Pydantic models for request and response data validation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import Optional, List
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
|
||||||
|
class ChatModel(BaseModel):
|
||||||
|
"""Model representing a Telegram chat."""
|
||||||
|
id: int
|
||||||
|
title: str
|
||||||
|
username: Optional[str] = None
|
||||||
|
type: str
|
||||||
|
last_message_time: Optional[datetime] = None
|
||||||
|
|
||||||
|
|
||||||
|
class MessageModel(BaseModel):
|
||||||
|
"""Model representing a Telegram message."""
|
||||||
|
id: int
|
||||||
|
chat_id: int
|
||||||
|
chat_title: str
|
||||||
|
sender_id: int
|
||||||
|
sender_name: str
|
||||||
|
content: str
|
||||||
|
timestamp: datetime
|
||||||
|
is_from_me: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
class MessageContextModel(BaseModel):
|
||||||
|
"""Model representing a message with its context."""
|
||||||
|
message: MessageModel
|
||||||
|
before: List[MessageModel] = []
|
||||||
|
after: List[MessageModel] = []
|
||||||
|
|
||||||
|
|
||||||
|
class SendMessageRequest(BaseModel):
|
||||||
|
"""Model for sending message requests."""
|
||||||
|
recipient: str
|
||||||
|
message: str
|
||||||
|
|
||||||
|
|
||||||
|
class SendMessageResponse(BaseModel):
|
||||||
|
"""Model for sending message responses."""
|
||||||
|
success: bool
|
||||||
|
message: str
|
||||||
Loading…
Add table
Reference in a new issue