214 lines
7.3 KiB
Python
214 lines
7.3 KiB
Python
from __future__ import annotations
|
||
import logging
|
||
from typing import Dict, Any, Optional, Iterable, Tuple
|
||
|
||
from telegram import Bot, Message, InlineKeyboardMarkup
|
||
from telegram.error import InvalidToken, TelegramError
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
def make_keyboard_payload(buttons: Optional[Iterable[Tuple[str, str]]]) -> Optional[Dict]:
|
||
"""
|
||
Создает структуру inline-клавиатуры для API Telegram.
|
||
|
||
Args:
|
||
buttons: Список кнопок в формате [(text, url), ...]
|
||
|
||
Returns:
|
||
Dict в формате {"rows": [[{"text": text, "url": url}], ...]}
|
||
"""
|
||
if not buttons:
|
||
return None
|
||
rows = [[{"text": t, "url": u}] for t, u in buttons]
|
||
return {"rows": rows}
|
||
|
||
def build_payload(
|
||
ptype: str,
|
||
text: Optional[str] = None,
|
||
media_file_id: Optional[str] = None,
|
||
parse_mode: Optional[str] = None,
|
||
keyboard: Optional[Dict[str, Any]] = None
|
||
) -> Dict[str, Any]:
|
||
"""
|
||
Строит payload для отправки поста.
|
||
|
||
Args:
|
||
ptype: Тип поста (text/photo/video/animation)
|
||
text: Текст сообщения
|
||
media_file_id: ID медиафайла в Telegram
|
||
parse_mode: Формат разметки (HTML/MarkdownV2)
|
||
keyboard: Inline клавиатура
|
||
|
||
Returns:
|
||
Dict содержащий все необходимые поля для отправки
|
||
"""
|
||
payload: Dict[str, Any] = {
|
||
"type": str(ptype),
|
||
"text": text if text is not None else "",
|
||
"parse_mode": str(parse_mode) if parse_mode is not None else "html",
|
||
"keyboard": keyboard if keyboard is not None else {},
|
||
}
|
||
if media_file_id:
|
||
payload["media_file_id"] = media_file_id
|
||
return payload
|
||
|
||
async def validate_bot_token(token: str) -> Tuple[bool, Optional[str], Optional[int]]:
|
||
"""
|
||
Проверяет валидность токена бота и возвращает его username и ID.
|
||
|
||
Args:
|
||
token: Токен бота для проверки
|
||
|
||
Returns:
|
||
tuple[bool, Optional[str], Optional[int]]: (is_valid, username, bot_id)
|
||
"""
|
||
try:
|
||
bot = Bot(token)
|
||
me = await bot.get_me()
|
||
return True, me.username, me.id
|
||
except InvalidToken:
|
||
logger.warning(f"Invalid bot token provided: {token[:10]}...")
|
||
return False, None, None
|
||
except TelegramError as e:
|
||
logger.error(f"Telegram error while validating bot token: {e}")
|
||
return False, None, None
|
||
except Exception as e:
|
||
logger.exception(f"Unexpected error while validating bot token: {e}")
|
||
return False, None, None
|
||
finally:
|
||
if 'bot' in locals():
|
||
await bot.close()
|
||
|
||
def validate_message_length(text: str) -> bool:
|
||
"""
|
||
Проверяет длину сообщения на соответствие лимитам Telegram.
|
||
|
||
Args:
|
||
text: Текст для проверки
|
||
|
||
Returns:
|
||
bool: True если длина в пределах лимита
|
||
"""
|
||
return len(text) <= 4096 # Максимальная длина текста в Telegram
|
||
|
||
def is_valid_webhook_url(url: str) -> bool:
|
||
"""Проверяет соответствие URL требованиям Telegram для вебхуков.
|
||
|
||
Args:
|
||
url: URL для проверки
|
||
|
||
Returns:
|
||
bool: True если URL валидный, иначе False
|
||
"""
|
||
if not url:
|
||
return False
|
||
|
||
return True # TODO: implement proper validation
|
||
|
||
|
||
class PostService:
|
||
"""Сервис для работы с постами."""
|
||
|
||
@staticmethod
|
||
async def preview_post(message: Message, post_data: Dict[str, Any]) -> None:
|
||
"""Показывает предпросмотр поста.
|
||
|
||
Args:
|
||
message (Message): Telegram сообщение
|
||
post_data (Dict[str, Any]): Данные поста из сессии
|
||
"""
|
||
text = post_data.get('text', '')
|
||
parse_mode = post_data.get('parse_mode', 'HTML')
|
||
keyboard = post_data.get('keyboard')
|
||
|
||
if keyboard:
|
||
# Создаем разметку клавиатуры
|
||
rows = keyboard.get('rows', [])
|
||
markup = InlineKeyboardMarkup(rows) if rows else None
|
||
else:
|
||
markup = None
|
||
|
||
media_file_id = post_data.get('media_file_id')
|
||
if media_file_id:
|
||
# Отправляем медиафайл с подписью
|
||
try:
|
||
await message.reply_photo(
|
||
photo=media_file_id,
|
||
caption=text,
|
||
parse_mode=parse_mode,
|
||
reply_markup=markup
|
||
)
|
||
except TelegramError as e:
|
||
# В случае ошибки отправляем только текст
|
||
logger.error(f"Error sending photo preview: {e}")
|
||
await message.reply_text(
|
||
text=text,
|
||
parse_mode=parse_mode,
|
||
reply_markup=markup
|
||
)
|
||
else:
|
||
# Отправляем только текст
|
||
await message.reply_text(
|
||
text=text,
|
||
parse_mode=parse_mode,
|
||
reply_markup=markup
|
||
)
|
||
|
||
@staticmethod
|
||
async def create_post(bot: Bot, chat_id: int, post_data: Dict[str, Any]) -> bool:
|
||
"""Создает новый пост в канале.
|
||
|
||
Args:
|
||
bot (Bot): Экземпляр бота
|
||
chat_id (int): ID канала
|
||
post_data (Dict[str, Any]): Данные поста
|
||
|
||
Returns:
|
||
bool: Успешность создания
|
||
"""
|
||
try:
|
||
text = post_data.get('text', '')
|
||
parse_mode = post_data.get('parse_mode', 'HTML')
|
||
keyboard = post_data.get('keyboard')
|
||
|
||
if keyboard:
|
||
rows = keyboard.get('rows', [])
|
||
markup = InlineKeyboardMarkup(rows) if rows else None
|
||
else:
|
||
markup = None
|
||
|
||
media_file_id = post_data.get('media_file_id')
|
||
if media_file_id:
|
||
await bot.send_photo(
|
||
chat_id=chat_id,
|
||
photo=media_file_id,
|
||
caption=text,
|
||
parse_mode=parse_mode,
|
||
reply_markup=markup
|
||
)
|
||
else:
|
||
await bot.send_message(
|
||
chat_id=chat_id,
|
||
text=text,
|
||
parse_mode=parse_mode,
|
||
reply_markup=markup
|
||
)
|
||
return True
|
||
|
||
except TelegramError as e:
|
||
logger.error(f"Error creating post: {e}")
|
||
return False
|
||
def validate_url(url: str) -> bool:
|
||
"""Проверяет соответствие URL требованиям.
|
||
|
||
Args:
|
||
url (str): URL для проверки
|
||
|
||
Returns:
|
||
bool: True если URL соответствует требованиям
|
||
"""
|
||
return (
|
||
url.startswith("https://") and
|
||
not url.startswith("https://telegram.org") and
|
||
len(url) <= 512
|
||
) |