тестово events события вместо telegram updates

This commit is contained in:
Artem Tsyrulnikov
2026-01-19 19:39:34 +03:00
parent 7f6b2ac336
commit f7ad52f543
3 changed files with 253 additions and 207 deletions

View File

@@ -1,7 +1,7 @@
from typing import Any
from typing import Annotated, Literal, Union
from fastapi.routing import APIRouter
from pydantic import BaseModel
from pydantic import BaseModel, Field
from src import deps, dto
@@ -58,210 +58,74 @@ async def attach_channel_to_workspace(input: AttachChannelToWorkspaceRequest) ->
return await deps.get_usecase().attach_channel_to_workspace(input=input_data)
@internal_router.post('/telegram/updates')
async def handle_telegram_update(update: dict[str, Any]) -> None:
"""Обрабатывает системные события от Golang бота (echotron формат)"""
import json
import logging
log = logging.getLogger(__name__)
# Логируем что пришло для отладки
log.info('Received update from Golang bot, keys: %s', list(update.keys()))
# Обрабатываем ChatJoinRequest (запрос на вступление в канал)
if 'chat_join_request' in update:
log.info('ChatJoinRequest event: %s', json.dumps(update['chat_join_request'], ensure_ascii=False))
await _handle_chat_join_request(update['chat_join_request'])
return
# Обрабатываем ChatMember (пользователь вступил/вышел из канала)
if 'chat_member' in update:
event = update['chat_member']
old_status = event.get('old_chat_member', {}).get('status')
new_status = event.get('new_chat_member', {}).get('status')
log.info('ChatMember event: status change %s -> %s', old_status, new_status)
await _handle_chat_member_updated(event)
return
# Обрабатываем MyChatMember (бот добавлен/удален из канала)
if 'my_chat_member' in update:
event = update['my_chat_member']
old_status = event.get('old_chat_member', {}).get('status')
new_status = event.get('new_chat_member', {}).get('status')
log.info('MyChatMember event: bot status changed %s -> %s in chat', old_status, new_status)
await _handle_my_chat_member(event)
return
log.warning('Unknown update type from Golang bot: %s', list(update.keys()))
class SubscriptionEventRequest(BaseModel):
type: Literal['subscription']
user_telegram_id: int
invite_link: str
username: str | None = None
first_name: str | None = None
last_name: str | None = None
async def _handle_chat_join_request(event: dict[str, Any]) -> None:
"""Обработка запроса на вступление в канал через invite link"""
import logging
log = logging.getLogger(__name__)
chat = event.get('chat', {})
if chat.get('type') not in ['channel', 'supergroup']:
return
from_user = event.get('from')
if not from_user or not from_user.get('id'):
user_chat_id = event.get('user_chat_id')
if not user_chat_id:
log.error('No user or user_chat_id in chat_join_request event')
return
from_user = {'id': user_chat_id}
invite_link_obj = event.get('invite_link', {})
invite_link = invite_link_obj.get('invite_link')
if not invite_link:
log.debug('No invite_link in chat_join_request event')
return
await deps.get_usecase().handle_subscription(
user_telegram_id=from_user['id'],
username=from_user.get('username'),
invite_link=invite_link,
first_name=from_user.get('first_name'),
last_name=from_user.get('last_name'),
)
class UnsubscriptionEventRequest(BaseModel):
type: Literal['unsubscription']
user_telegram_id: int
channel_telegram_id: int
async def _handle_chat_member_updated(event: dict[str, Any]) -> None:
"""Обработка изменения статуса участника канала (вступление/выход)"""
import logging
class BotAddedEventRequest(dto.ConnectProjectInput):
type: Literal['bot_added']
log = logging.getLogger(__name__)
chat = event.get('chat', {})
if chat.get('type') not in ['channel', 'supergroup']:
return
class BotRemovedEventRequest(dto.DisconnectProjectByTgIdInput):
type: Literal['bot_removed']
member_user = event.get('new_chat_member', {}).get('user')
if not member_user:
log.error('No user in chat_member_updated event')
return
old_member = event.get('old_chat_member', {})
new_member = event.get('new_chat_member', {})
class BotPermissionsEventRequest(dto.UpdateProjectPermissionsInput):
type: Literal['bot_permissions']
old_status = old_member.get('status')
new_status = new_member.get('status')
# Проверяем что пользователь присоединился к каналу
was_not_member = old_status in ['left', 'kicked']
is_now_member = new_status in ['member', 'administrator', 'creator']
user_joined = was_not_member and is_now_member
EventRequest = Annotated[
Union[
SubscriptionEventRequest,
UnsubscriptionEventRequest,
BotAddedEventRequest,
BotRemovedEventRequest,
BotPermissionsEventRequest,
],
Field(discriminator='type'),
]
# Проверяем что пользователь покинул канал
was_member = old_status in ['member', 'administrator', 'creator', 'restricted']
is_now_not_member = new_status in ['left', 'kicked']
user_left = was_member and is_now_not_member
if user_joined:
invite_link_obj = event.get('invite_link', {})
invite_link = invite_link_obj.get('invite_link') if invite_link_obj else None
if not invite_link:
log.debug('No invite_link in chat_member_updated event (user joined)')
return
@internal_router.post('/events')
async def handle_event(input: EventRequest) -> None:
if isinstance(input, SubscriptionEventRequest):
await deps.get_usecase().handle_subscription(
user_telegram_id=member_user['id'],
username=member_user.get('username'),
invite_link=invite_link,
first_name=member_user.get('first_name'),
last_name=member_user.get('last_name'),
user_telegram_id=input.user_telegram_id,
username=input.username,
invite_link=input.invite_link,
first_name=input.first_name,
last_name=input.last_name,
)
return
elif user_left:
if isinstance(input, UnsubscriptionEventRequest):
await deps.get_usecase().handle_unsubscription(
user_telegram_id=member_user['id'],
channel_telegram_id=chat['id'],
user_telegram_id=input.user_telegram_id,
channel_telegram_id=input.channel_telegram_id,
)
async def _handle_my_chat_member(event: dict[str, Any]) -> None:
"""Обработка добавления/удаления бота из канала"""
import logging
log = logging.getLogger(__name__)
chat = event.get('chat', {})
if chat.get('type') not in ['channel', 'supergroup']:
return
from_user = event.get('from')
if not from_user:
log.error('No user in my_chat_member event')
if isinstance(input, BotAddedEventRequest):
data = dto.ConnectProjectInput(**input.dict(exclude={'type'}))
await deps.get_usecase().tg_add_project(input=data)
return
old_member = event.get('old_chat_member', {})
new_member = event.get('new_chat_member', {})
old_status = old_member.get('status')
new_status = new_member.get('status')
# Проверяем что бот был удален из канала
was_member = old_status in ['administrator', 'member']
is_now_not_member = new_status in ['left', 'kicked']
bot_removed = was_member and is_now_not_member
if bot_removed:
# Отключаем проект
disconnect_input = dto.DisconnectProjectByTgIdInput(
telegram_id=chat['id'],
user_telegram_id=from_user['id'],
)
try:
await deps.get_usecase().disconnect_project_by_tg_id(input=disconnect_input)
log.info('Bot removed from channel %s by user %s', chat['id'], from_user['id'])
except Exception as e:
log.error('Failed to disconnect project: %s', e)
if isinstance(input, BotRemovedEventRequest):
data = dto.DisconnectProjectByTgIdInput(**input.dict(exclude={'type'}))
await deps.get_usecase().disconnect_project_by_tg_id(input=data)
return
# Проверяем изменение прав администратора
permissions_changed = old_status == 'administrator' and new_status == 'administrator'
# Извлекаем права бота
bot_permissions = dto.ChannelBotPermissions(
is_admin=(new_status == 'administrator'),
can_invite_users=new_member.get('can_invite_users', False),
can_restrict_members=new_member.get('can_restrict_members', False),
)
if permissions_changed:
# Обновляем права бота
permissions_input = dto.UpdateProjectPermissionsInput(
telegram_id=chat['id'],
permissions=bot_permissions,
chat_title=chat.get('title') or f'Channel {chat["id"]}',
user_telegram_id=from_user['id'],
)
try:
await deps.get_usecase().update_project_permissions(input=permissions_input)
log.info('Bot permissions updated in channel %s', chat['id'])
except Exception as e:
log.error('Failed to update permissions: %s', e)
return
# Проверяем что бот был добавлен в канал
was_not_member = old_status in ['left', 'kicked']
is_now_member = new_status in ['administrator', 'member']
bot_added = was_not_member and is_now_member
if bot_added:
# Добавляем канал как проект
connect_input = dto.ConnectProjectInput(
telegram_id=chat['id'],
title=chat.get('title') or f'Channel {chat["id"]}',
username=chat.get('username'),
user_telegram_id=from_user['id'],
bot_permissions=bot_permissions,
)
try:
await deps.get_usecase().tg_add_project(input=connect_input)
log.info('Bot added to channel %s by user %s', chat['id'], from_user['id'])
except Exception as e:
log.error('Failed to add project: %s', e)
if isinstance(input, BotPermissionsEventRequest):
data = dto.UpdateProjectPermissionsInput(**input.dict(exclude={'type'}))
await deps.get_usecase().update_project_permissions(input=data)