Files
MessageGateway/app/api/v1/endpoints/message.py
Sergey Antropov b90def35ed Initial commit: Message Gateway project
- FastAPI приложение для отправки мониторинговых алертов в мессенджеры
- Поддержка Telegram и MAX/VK
- Интеграция с Grafana, Zabbix, AlertManager
- Автоматическое создание тикетов в Jira
- Управление группами мессенджеров через API
- Декораторы для авторизации и скрытия эндпоинтов
- Подробная документация в папке docs/

Автор: Сергей Антропов
Сайт: https://devops.org.ru
2025-11-12 20:25:11 +03:00

469 lines
16 KiB
Python

"""
Эндпоинты для отправки простых сообщений в мессенджеры.
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""
import logging
from typing import Dict, Any, Optional
from fastapi import APIRouter, HTTPException, Query, Request, Body
from app.core.metrics import metrics
from app.core.groups import groups_config
from app.core.messenger_factory import MessengerFactory
from app.core.auth import require_api_key, require_api_key_dependency
from app.models.message import (
SendMessageRequest,
SendPhotoRequest,
SendVideoRequest,
SendAudioRequest,
SendDocumentRequest
)
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/message", tags=["message"])
async def _send_message_to_group(
group_name: str,
thread_id: int,
messenger: Optional[str],
send_func,
*args,
**kwargs
) -> Dict[str, Any]:
"""
Вспомогательная функция для отправки сообщений в группу.
Args:
group_name: Имя группы из конфигурации.
thread_id: ID треда в группе (0 для основной группы).
messenger: Тип мессенджера (опционально).
send_func: Функция отправки сообщения.
*args: Дополнительные аргументы для функции отправки.
**kwargs: Дополнительные параметры для функции отправки.
Returns:
Результат отправки сообщения.
Raises:
HTTPException: Если группа не найдена или произошла ошибка отправки.
"""
# Получаем конфигурацию группы
group_config = await groups_config.get_group_config(group_name, messenger)
if group_config is None:
raise HTTPException(
status_code=400,
detail=f"Группа '{group_name}' не найдена в конфигурации"
)
messenger_type = group_config.get("messenger", "telegram")
chat_id = group_config.get("chat_id")
group_thread_id = group_config.get("thread_id", 0)
# Используем thread_id из параметра, если указан, иначе из конфигурации группы
final_thread_id = thread_id if thread_id > 0 else group_thread_id
# Создаем клиент мессенджера
messenger_client = MessengerFactory.create_from_config(group_config)
# Проверяем поддержку тредов
if not messenger_client.supports_threads and final_thread_id > 0:
logger.warning(f"Мессенджер '{messenger_type}' не поддерживает треды, thread_id будет проигнорирован")
final_thread_id = None
elif final_thread_id == 0:
final_thread_id = None
# Вызываем функцию отправки
success = await send_func(
messenger_client,
chat_id=chat_id,
thread_id=final_thread_id,
*args,
**kwargs
)
if not success:
raise HTTPException(
status_code=500,
detail=f"Ошибка отправки сообщения в {messenger_type}"
)
return {
"status": "ok",
"message": f"Сообщение отправлено в чат {group_name}, тред {thread_id if thread_id > 0 else 0}"
}
@require_api_key
@router.post(
"/text",
name="Отправить текстовое сообщение",
response_model=Dict[str, Any],
dependencies=[require_api_key_dependency],
responses={
200: {
"description": "Сообщение отправлено успешно",
"content": {
"application/json": {
"example": {
"status": "ok",
"message": "Сообщение отправлено в чат monitoring, тред 0"
}
}
}
},
400: {
"description": "Ошибка запроса",
"content": {
"application/json": {
"example": {"detail": "Группа 'monitoring' не найдена в конфигурации"}
}
}
},
401: {
"description": "Требуется API ключ",
"content": {
"application/json": {
"example": {"detail": "Неверный или отсутствующий API ключ"}
}
}
},
500: {
"description": "Ошибка отправки сообщения",
"content": {
"application/json": {
"example": {"detail": "Ошибка отправки сообщения в telegram"}
}
}
}
}
)
async def send_text_message(
request: Request,
body: SendMessageRequest = Body(...),
messenger: Optional[str] = Query(None, description="Тип мессенджера (telegram, max). Если не указан, используется из конфигурации группы", examples=["telegram", "max"])
) -> Dict[str, Any]:
"""
Отправить текстовое сообщение в группу мессенджера.
Подробная документация: см. docs/api/message.md
"""
metrics.increment_api_endpoint("message_text")
async def send_func(client, chat_id, thread_id, **kwargs):
return await client.send_message(
chat_id=chat_id,
text=body.text,
thread_id=thread_id,
disable_web_page_preview=body.disable_web_page_preview,
parse_mode=body.parse_mode
)
return await _send_message_to_group(
group_name=body.tg_group,
thread_id=body.tg_thread_id,
messenger=messenger,
send_func=send_func
)
@require_api_key
@router.post(
"/photo",
name="Отправить фото",
response_model=Dict[str, Any],
dependencies=[require_api_key_dependency],
responses={
200: {
"description": "Фото отправлено успешно",
"content": {
"application/json": {
"example": {
"status": "ok",
"message": "Фото отправлено в чат monitoring, тред 0"
}
}
}
},
400: {
"description": "Ошибка запроса",
"content": {
"application/json": {
"example": {"detail": "Группа 'monitoring' не найдена в конфигурации"}
}
}
},
401: {
"description": "Требуется API ключ",
"content": {
"application/json": {
"example": {"detail": "Неверный или отсутствующий API ключ"}
}
}
},
500: {
"description": "Ошибка отправки фото",
"content": {
"application/json": {
"example": {"detail": "Ошибка отправки фото в telegram"}
}
}
}
}
)
async def send_photo(
request: Request,
body: SendPhotoRequest = Body(...),
messenger: Optional[str] = Query(None, description="Тип мессенджера (telegram, max). Если не указан, используется из конфигурации группы", examples=["telegram", "max"])
) -> Dict[str, Any]:
"""
Отправить фото в группу мессенджера.
Подробная документация: см. docs/api/message.md
"""
metrics.increment_api_endpoint("message_photo")
async def send_func(client, chat_id, thread_id, **kwargs):
return await client.send_photo(
chat_id=chat_id,
photo=body.photo,
caption=body.caption,
thread_id=thread_id,
parse_mode=body.parse_mode
)
return await _send_message_to_group(
group_name=body.tg_group,
thread_id=body.tg_thread_id,
messenger=messenger,
send_func=send_func
)
@require_api_key
@router.post(
"/video",
name="Отправить видео",
response_model=Dict[str, Any],
dependencies=[require_api_key_dependency],
responses={
200: {
"description": "Видео отправлено успешно",
"content": {
"application/json": {
"example": {
"status": "ok",
"message": "Видео отправлено в чат monitoring, тред 0"
}
}
}
},
400: {
"description": "Ошибка запроса",
"content": {
"application/json": {
"example": {"detail": "Группа 'monitoring' не найдена в конфигурации"}
}
}
},
401: {
"description": "Требуется API ключ",
"content": {
"application/json": {
"example": {"detail": "Неверный или отсутствующий API ключ"}
}
}
},
500: {
"description": "Ошибка отправки видео",
"content": {
"application/json": {
"example": {"detail": "Ошибка отправки видео в telegram"}
}
}
}
}
)
async def send_video(
request: Request,
body: SendVideoRequest = Body(...),
messenger: Optional[str] = Query(None, description="Тип мессенджера (telegram, max). Если не указан, используется из конфигурации группы", examples=["telegram", "max"])
) -> Dict[str, Any]:
"""
Отправить видео в группу мессенджера.
Подробная документация: см. docs/api/message.md
"""
metrics.increment_api_endpoint("message_video")
async def send_func(client, chat_id, thread_id, **kwargs):
return await client.send_video(
chat_id=chat_id,
video=body.video,
caption=body.caption,
thread_id=thread_id,
parse_mode=body.parse_mode,
duration=body.duration,
width=body.width,
height=body.height
)
return await _send_message_to_group(
group_name=body.tg_group,
thread_id=body.tg_thread_id,
messenger=messenger,
send_func=send_func
)
@require_api_key
@router.post(
"/audio",
name="Отправить аудио",
response_model=Dict[str, Any],
dependencies=[require_api_key_dependency],
responses={
200: {
"description": "Аудио отправлено успешно",
"content": {
"application/json": {
"example": {
"status": "ok",
"message": "Аудио отправлено в чат monitoring, тред 0"
}
}
}
},
400: {
"description": "Ошибка запроса",
"content": {
"application/json": {
"example": {"detail": "Группа 'monitoring' не найдена в конфигурации"}
}
}
},
401: {
"description": "Требуется API ключ",
"content": {
"application/json": {
"example": {"detail": "Неверный или отсутствующий API ключ"}
}
}
},
500: {
"description": "Ошибка отправки аудио",
"content": {
"application/json": {
"example": {"detail": "Ошибка отправки аудио в telegram"}
}
}
}
}
)
async def send_audio(
request: Request,
body: SendAudioRequest = Body(...),
messenger: Optional[str] = Query(None, description="Тип мессенджера (telegram, max). Если не указан, используется из конфигурации группы", examples=["telegram", "max"])
) -> Dict[str, Any]:
"""
Отправить аудио в группу мессенджера.
Подробная документация: см. docs/api/message.md
"""
metrics.increment_api_endpoint("message_audio")
async def send_func(client, chat_id, thread_id, **kwargs):
return await client.send_audio(
chat_id=chat_id,
audio=body.audio,
caption=body.caption,
thread_id=thread_id,
parse_mode=body.parse_mode,
duration=body.duration,
performer=body.performer,
title=body.title
)
return await _send_message_to_group(
group_name=body.tg_group,
thread_id=body.tg_thread_id,
messenger=messenger,
send_func=send_func
)
@require_api_key
@router.post(
"/document",
name="Отправить документ",
response_model=Dict[str, Any],
dependencies=[require_api_key_dependency],
responses={
200: {
"description": "Документ отправлен успешно",
"content": {
"application/json": {
"example": {
"status": "ok",
"message": "Документ отправлен в чат monitoring, тред 0"
}
}
}
},
400: {
"description": "Ошибка запроса",
"content": {
"application/json": {
"example": {"detail": "Группа 'monitoring' не найдена в конфигурации"}
}
}
},
401: {
"description": "Требуется API ключ",
"content": {
"application/json": {
"example": {"detail": "Неверный или отсутствующий API ключ"}
}
}
},
500: {
"description": "Ошибка отправки документа",
"content": {
"application/json": {
"example": {"detail": "Ошибка отправки документа в telegram"}
}
}
}
}
)
async def send_document(
request: Request,
body: SendDocumentRequest = Body(...),
messenger: Optional[str] = Query(None, description="Тип мессенджера (telegram, max). Если не указан, используется из конфигурации группы", examples=["telegram", "max"])
) -> Dict[str, Any]:
"""
Отправить документ в группу мессенджера.
Подробная документация: см. docs/api/message.md
"""
metrics.increment_api_endpoint("message_document")
async def send_func(client, chat_id, thread_id, **kwargs):
return await client.send_document(
chat_id=chat_id,
document=body.document,
caption=body.caption,
thread_id=thread_id,
parse_mode=body.parse_mode,
filename=body.filename
)
return await _send_message_to_group(
group_name=body.tg_group,
thread_id=body.tg_thread_id,
messenger=messenger,
send_func=send_func
)