Initial commit: Message Gateway project

- FastAPI приложение для отправки мониторинговых алертов в мессенджеры
- Поддержка Telegram и MAX/VK
- Интеграция с Grafana, Zabbix, AlertManager
- Автоматическое создание тикетов в Jira
- Управление группами мессенджеров через API
- Декораторы для авторизации и скрытия эндпоинтов
- Подробная документация в папке docs/

Автор: Сергей Антропов
Сайт: https://devops.org.ru
This commit is contained in:
2025-11-12 20:25:11 +03:00
commit b90def35ed
72 changed files with 10609 additions and 0 deletions

View File

@@ -0,0 +1,255 @@
"""
Адаптер для работы с Telegram через MessengerClient интерфейс.
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""
import logging
import io
from typing import Optional, Union, Dict, Any
from telegram import InlineKeyboardMarkup
from app.core.messengers.base import MessengerClient
from app.core.telegram_client import TelegramClient
from app.core.button_utils import convert_dict_to_telegram_buttons
logger = logging.getLogger(__name__)
class TelegramMessengerClient(MessengerClient):
"""Адаптер для Telegram, реализующий интерфейс MessengerClient."""
def __init__(self, bot_token: Optional[str] = None):
"""
Инициализация клиента Telegram.
Args:
bot_token: Токен бота Telegram. Если не указан, используется из настроек.
"""
self._client = TelegramClient(bot_token=bot_token)
@property
def messenger_type(self) -> str:
"""Тип мессенджера."""
return "telegram"
@property
def supports_threads(self) -> bool:
"""Telegram поддерживает треды."""
return True
async def send_message(
self,
chat_id: Union[str, int],
text: str,
thread_id: Optional[int] = None,
reply_markup: Optional[Dict[str, Any]] = None,
disable_web_page_preview: bool = True,
parse_mode: str = "HTML",
**kwargs
) -> bool:
"""
Отправить текстовое сообщение в Telegram.
Args:
chat_id: ID чата или группы (преобразуется в int).
text: Текст сообщения.
thread_id: ID треда в группе (опционально).
reply_markup: Клавиатура с кнопками (опционально).
disable_web_page_preview: Отключить превью ссылок.
parse_mode: Режим парсинга (HTML, Markdown).
**kwargs: Дополнительные параметры (игнорируются).
Returns:
True если сообщение отправлено успешно, False в противном случае.
"""
# Преобразуем chat_id в int
chat_id_int = int(chat_id) if isinstance(chat_id, str) else chat_id
# Преобразуем reply_markup из Dict в InlineKeyboardMarkup, если нужно
telegram_reply_markup = None
if reply_markup:
if isinstance(reply_markup, InlineKeyboardMarkup):
telegram_reply_markup = reply_markup
elif isinstance(reply_markup, dict):
# Преобразуем словарь в InlineKeyboardMarkup
telegram_reply_markup = convert_dict_to_telegram_buttons(reply_markup)
return await self._client.send_message(
chat_id=chat_id_int,
text=text,
message_thread_id=thread_id,
reply_markup=telegram_reply_markup,
disable_web_page_preview=disable_web_page_preview,
parse_mode=parse_mode
)
async def send_photo(
self,
chat_id: Union[str, int],
photo: Union[str, bytes],
caption: Optional[str] = None,
thread_id: Optional[int] = None,
parse_mode: str = "HTML",
**kwargs
) -> bool:
"""
Отправить фото в Telegram.
Args:
chat_id: ID чата или группы (преобразуется в int).
photo: Путь к файлу, URL, bytes или BytesIO объект с фото.
caption: Подпись к фото (опционально).
thread_id: ID треда в группе (опционально).
parse_mode: Режим парсинга (HTML, Markdown).
**kwargs: Дополнительные параметры (игнорируются).
Returns:
True если фото отправлено успешно, False в противном случае.
"""
chat_id_int = int(chat_id) if isinstance(chat_id, str) else chat_id
# Преобразуем bytes в BytesIO, если нужно
if isinstance(photo, bytes):
photo = io.BytesIO(photo)
return await self._client.send_photo(
chat_id=chat_id_int,
photo=photo,
caption=caption,
message_thread_id=thread_id,
parse_mode=parse_mode
)
async def send_video(
self,
chat_id: Union[str, int],
video: Union[str, bytes],
caption: Optional[str] = None,
thread_id: Optional[int] = None,
parse_mode: str = "HTML",
duration: Optional[int] = None,
width: Optional[int] = None,
height: Optional[int] = None,
**kwargs
) -> bool:
"""
Отправить видео в Telegram.
Args:
chat_id: ID чата или группы (преобразуется в int).
video: Путь к файлу, URL, bytes или BytesIO объект с видео.
caption: Подпись к видео (опционально).
thread_id: ID треда в группе (опционально).
parse_mode: Режим парсинга (HTML, Markdown).
duration: Длительность видео в секундах (опционально).
width: Ширина видео (опционально).
height: Высота видео (опционально).
**kwargs: Дополнительные параметры (игнорируются).
Returns:
True если видео отправлено успешно, False в противном случае.
"""
chat_id_int = int(chat_id) if isinstance(chat_id, str) else chat_id
# Преобразуем bytes в BytesIO, если нужно
if isinstance(video, bytes):
video = io.BytesIO(video)
return await self._client.send_video(
chat_id=chat_id_int,
video=video,
caption=caption,
message_thread_id=thread_id,
parse_mode=parse_mode,
duration=duration,
width=width,
height=height
)
async def send_audio(
self,
chat_id: Union[str, int],
audio: Union[str, bytes],
caption: Optional[str] = None,
thread_id: Optional[int] = None,
parse_mode: str = "HTML",
duration: Optional[int] = None,
performer: Optional[str] = None,
title: Optional[str] = None,
**kwargs
) -> bool:
"""
Отправить аудио в Telegram.
Args:
chat_id: ID чата или группы (преобразуется в int).
audio: Путь к файлу, URL, bytes или BytesIO объект с аудио.
caption: Подпись к аудио (опционально).
thread_id: ID треда в группе (опционально).
parse_mode: Режим парсинга (HTML, Markdown).
duration: Длительность аудио в секундах (опционально).
performer: Исполнитель (опционально).
title: Название трека (опционально).
**kwargs: Дополнительные параметры (игнорируются).
Returns:
True если аудио отправлено успешно, False в противном случае.
"""
chat_id_int = int(chat_id) if isinstance(chat_id, str) else chat_id
# Преобразуем bytes в BytesIO, если нужно
if isinstance(audio, bytes):
audio = io.BytesIO(audio)
return await self._client.send_audio(
chat_id=chat_id_int,
audio=audio,
caption=caption,
message_thread_id=thread_id,
parse_mode=parse_mode,
duration=duration,
performer=performer,
title=title
)
async def send_document(
self,
chat_id: Union[str, int],
document: Union[str, bytes],
caption: Optional[str] = None,
thread_id: Optional[int] = None,
parse_mode: str = "HTML",
filename: Optional[str] = None,
**kwargs
) -> bool:
"""
Отправить документ в Telegram.
Args:
chat_id: ID чата или группы (преобразуется в int).
document: Путь к файлу, URL, bytes или BytesIO объект с документом.
caption: Подпись к документу (опционально).
thread_id: ID треда в группе (опционально).
parse_mode: Режим парсинга (HTML, Markdown).
filename: Имя файла (опционально).
**kwargs: Дополнительные параметры (игнорируются).
Returns:
True если документ отправлен успешно, False в противном случае.
"""
chat_id_int = int(chat_id) if isinstance(chat_id, str) else chat_id
# Преобразуем bytes в BytesIO, если нужно
if isinstance(document, bytes):
document = io.BytesIO(document)
return await self._client.send_document(
chat_id=chat_id_int,
document=document,
caption=caption,
message_thread_id=thread_id,
parse_mode=parse_mode,
filename=filename
)