new telegram bot
This commit is contained in:
@@ -1,31 +0,0 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
from src.controller.http.analytics import analytics_router
|
||||
from src.controller.http.auth import auth_router
|
||||
from src.controller.http.channels import channels_router
|
||||
from src.controller.http.creatives import creatives_router
|
||||
from src.controller.http.placements import placements_router
|
||||
from src.controller.http.projects import projects_router
|
||||
from src.controller.http.purchase_plans import purchase_plan_channels_router
|
||||
from src.controller.http.views import views_router
|
||||
from src.controller.http.workspace_invites import workspace_invites_router
|
||||
from src.controller.http.workspace_members import workspace_members_router
|
||||
from src.controller.http.workspaces import workspaces_router
|
||||
|
||||
api_router = APIRouter()
|
||||
|
||||
# API v1 endpoints
|
||||
api_v1_router = APIRouter(prefix='/api/v1')
|
||||
api_v1_router.include_router(auth_router)
|
||||
api_v1_router.include_router(channels_router)
|
||||
api_v1_router.include_router(projects_router)
|
||||
api_v1_router.include_router(purchase_plan_channels_router)
|
||||
api_v1_router.include_router(creatives_router)
|
||||
api_v1_router.include_router(placements_router)
|
||||
api_v1_router.include_router(views_router)
|
||||
api_v1_router.include_router(analytics_router)
|
||||
api_v1_router.include_router(workspaces_router)
|
||||
api_v1_router.include_router(workspace_members_router)
|
||||
api_v1_router.include_router(workspace_invites_router)
|
||||
|
||||
api_router.include_router(api_v1_router)
|
||||
@@ -1,25 +0,0 @@
|
||||
import uuid
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import Depends
|
||||
from fastapi.routing import APIRouter
|
||||
from fastapi_pagination import Page, paginate
|
||||
|
||||
from src import deps, dto
|
||||
from src.adapter.jwt import JWTPayload
|
||||
|
||||
projects_router = APIRouter(prefix='/workspaces/{workspace_id}/projects', tags=['projects'])
|
||||
|
||||
|
||||
@projects_router.get('')
|
||||
async def get_workspace_projects(
|
||||
workspace_id: uuid.UUID,
|
||||
current_user: Annotated[JWTPayload, Depends(deps.get_current_user)],
|
||||
) -> Page[dto.ProjectOutput]:
|
||||
input = dto.GetWorkspaceProjectsInput(
|
||||
user_id=current_user.user_id,
|
||||
workspace_id=workspace_id,
|
||||
)
|
||||
|
||||
result = await deps.get_usecase().get_workspace_projects(input=input)
|
||||
return paginate(result) # type: ignore[no-any-return]
|
||||
34
src/controller/http_v1/__init__.py
Normal file
34
src/controller/http_v1/__init__.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
from src.controller.http_v1.analytics import analytics_router
|
||||
from src.controller.http_v1.auth import auth_router
|
||||
from src.controller.http_v1.channels import channels_router
|
||||
from src.controller.http_v1.creatives import creatives_router
|
||||
from src.controller.http_v1.internal import internal_router
|
||||
from src.controller.http_v1.placements import placements_router
|
||||
from src.controller.http_v1.projects import projects_router
|
||||
from src.controller.http_v1.purchase_plans import purchase_plan_channels_router
|
||||
from src.controller.http_v1.views import views_router
|
||||
from src.controller.http_v1.workspace_invites import workspace_invites_router, workspace_invites_global_router
|
||||
from src.controller.http_v1.workspace_members import workspace_members_router
|
||||
from src.controller.http_v1.workspaces import workspaces_router
|
||||
|
||||
api_router = APIRouter()
|
||||
|
||||
# API v1 endpoints
|
||||
api_v1_router = APIRouter(prefix='/api/v1')
|
||||
api_v1_router.include_router(auth_router)
|
||||
api_v1_router.include_router(internal_router)
|
||||
api_v1_router.include_router(channels_router)
|
||||
api_v1_router.include_router(projects_router)
|
||||
api_v1_router.include_router(purchase_plan_channels_router)
|
||||
api_v1_router.include_router(creatives_router)
|
||||
api_v1_router.include_router(placements_router)
|
||||
api_v1_router.include_router(views_router)
|
||||
api_v1_router.include_router(analytics_router)
|
||||
api_v1_router.include_router(workspaces_router)
|
||||
api_v1_router.include_router(workspace_members_router)
|
||||
api_v1_router.include_router(workspace_invites_router)
|
||||
api_v1_router.include_router(workspace_invites_global_router)
|
||||
|
||||
api_router.include_router(api_v1_router)
|
||||
@@ -18,3 +18,14 @@ async def get_channels(
|
||||
input = dto.GetChannelsInput(username=username)
|
||||
result = await deps.get_usecase().get_channels(input=input)
|
||||
return paginate(result) # type: ignore[no-any-return]
|
||||
|
||||
|
||||
@channels_router.get('/{channel_id}')
|
||||
async def get_channel(
|
||||
channel_id: str,
|
||||
_: Annotated[JWTPayload, Depends(deps.get_current_user)],
|
||||
) -> dto.ChannelOutput:
|
||||
import uuid
|
||||
|
||||
input_data = dto.GetChannelInput(channel_id=uuid.UUID(channel_id))
|
||||
return await deps.get_usecase().get_channel(input=input_data)
|
||||
270
src/controller/http_v1/internal.py
Normal file
270
src/controller/http_v1/internal.py
Normal file
@@ -0,0 +1,270 @@
|
||||
from fastapi.routing import APIRouter
|
||||
from pydantic import BaseModel
|
||||
|
||||
from src import deps, dto
|
||||
|
||||
internal_router = APIRouter(prefix='/internal', tags=['internal'])
|
||||
|
||||
|
||||
|
||||
|
||||
class CreateLoginTokenResponse(BaseModel):
|
||||
token: str
|
||||
|
||||
|
||||
@internal_router.post('/auth/login-token')
|
||||
async def create_login_token(input: dto.CreateLoginTokenRequest) -> str:
|
||||
token = await deps.get_usecase().create_telegram_login_token(
|
||||
telegram_id=input.telegram_id,
|
||||
username=input.username,
|
||||
)
|
||||
return token
|
||||
|
||||
|
||||
class ValidateTokenRequest(BaseModel):
|
||||
token: str
|
||||
telegram_id: int
|
||||
username: str | None = None
|
||||
|
||||
|
||||
|
||||
@internal_router.get('/auth/jwt')
|
||||
async def get_jwt_by_telegram_id(telegram_id: int) -> dto.ValidateLoginTokenOutput:
|
||||
return await deps.get_usecase().get_jwt_by_telegram_id(telegram_id)
|
||||
|
||||
|
||||
@internal_router.get('/projects/{workspace_id}/{project_id}')
|
||||
async def get_project(workspace_id: str, project_id: str) -> dto.ProjectOutput:
|
||||
import uuid
|
||||
|
||||
input_data = dto.GetProjectInput(
|
||||
workspace_id=uuid.UUID(workspace_id),
|
||||
project_id=uuid.UUID(project_id),
|
||||
)
|
||||
return await deps.get_usecase().get_project(input_data)
|
||||
|
||||
|
||||
class AttachChannelToWorkspaceRequest(BaseModel):
|
||||
channel_id: str
|
||||
workspace_id: str
|
||||
user_telegram_id: int
|
||||
|
||||
|
||||
@internal_router.post('/projects')
|
||||
async def attach_channel_to_workspace(input: AttachChannelToWorkspaceRequest) -> dto.ProjectOutput:
|
||||
"""Привязать канал к workspace (вызывается из Golang бота после выбора workspace)"""
|
||||
import uuid
|
||||
|
||||
input_data = dto.AttachChannelToWorkspaceInput(
|
||||
channel_id=uuid.UUID(input.channel_id),
|
||||
workspace_id=uuid.UUID(input.workspace_id),
|
||||
user_telegram_id=input.user_telegram_id,
|
||||
)
|
||||
|
||||
return await deps.get_usecase().attach_channel_to_workspace(input=input_data)
|
||||
|
||||
|
||||
@internal_router.post('/telegram/updates')
|
||||
async def handle_telegram_update(update: dict) -> 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()))
|
||||
|
||||
|
||||
async def _handle_chat_join_request(event: dict) -> 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:
|
||||
log.error('No user in chat_join_request event')
|
||||
return
|
||||
|
||||
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'),
|
||||
)
|
||||
|
||||
|
||||
async def _handle_chat_member_updated(event: dict) -> 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 chat_member_updated event')
|
||||
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_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
|
||||
|
||||
# Проверяем что пользователь покинул канал
|
||||
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
|
||||
|
||||
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'),
|
||||
)
|
||||
|
||||
elif user_left:
|
||||
await deps.get_usecase().handle_unsubscription(
|
||||
user_telegram_id=from_user['id'],
|
||||
channel_telegram_id=chat['id'],
|
||||
)
|
||||
|
||||
|
||||
async def _handle_my_chat_member(event: dict) -> 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')
|
||||
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)
|
||||
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)
|
||||
49
src/controller/http_v1/projects.py
Normal file
49
src/controller/http_v1/projects.py
Normal file
@@ -0,0 +1,49 @@
|
||||
import uuid
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import Depends
|
||||
from fastapi.routing import APIRouter
|
||||
from fastapi_pagination import Page, paginate
|
||||
|
||||
from src import deps, dto
|
||||
from src.adapter.jwt import JWTPayload
|
||||
|
||||
projects_router = APIRouter(prefix='/workspaces/{workspace_id}/projects', tags=['projects'])
|
||||
|
||||
|
||||
@projects_router.get('')
|
||||
async def get_workspace_projects(
|
||||
workspace_id: uuid.UUID,
|
||||
current_user: Annotated[JWTPayload, Depends(deps.get_current_user)],
|
||||
) -> Page[dto.ProjectOutput]:
|
||||
input = dto.GetWorkspaceProjectsInput(
|
||||
user_id=current_user.user_id,
|
||||
workspace_id=workspace_id,
|
||||
)
|
||||
|
||||
result = await deps.get_usecase().get_workspace_projects(input=input)
|
||||
return paginate(result) # type: ignore[no-any-return]
|
||||
|
||||
|
||||
@projects_router.patch('/{project_id}/invite-link-type')
|
||||
async def update_project_invite_link_type(
|
||||
workspace_id: uuid.UUID,
|
||||
project_id: uuid.UUID,
|
||||
input: dto.UpdateProjectInviteLinkTypeInput,
|
||||
current_user: Annotated[JWTPayload, Depends(deps.get_current_user)],
|
||||
) -> dto.ProjectOutput:
|
||||
project = await deps.get_usecase().update_project_invite_link_type(
|
||||
workspace_id=workspace_id,
|
||||
project_id=project_id,
|
||||
invite_link_type=input.invite_link_type,
|
||||
user_id=current_user.user_id,
|
||||
)
|
||||
|
||||
return dto.ProjectOutput(
|
||||
id=project.id,
|
||||
telegram_id=project.channel.telegram_id,
|
||||
title=project.channel.title,
|
||||
username=project.channel.username,
|
||||
status=project.status,
|
||||
invite_link_type=project.invite_link_type,
|
||||
)
|
||||
@@ -34,3 +34,18 @@ async def create_workspace_invite(
|
||||
user_id=current_user.user_id,
|
||||
input=request,
|
||||
)
|
||||
|
||||
|
||||
# Глобальный роутер для invite endpoints (без workspace_id в пути)
|
||||
workspace_invites_global_router = APIRouter(prefix='/invites', tags=['workspace invites'])
|
||||
|
||||
|
||||
@workspace_invites_global_router.post('/{invite_id}/accept')
|
||||
async def accept_workspace_invite(
|
||||
invite_id: uuid.UUID,
|
||||
current_user: Annotated[JWTPayload, Depends(deps.get_current_user)],
|
||||
) -> dto.WorkspaceInviteOutput:
|
||||
return await deps.get_usecase().accept_workspace_invite(
|
||||
invite_id=invite_id,
|
||||
user_id=current_user.user_id,
|
||||
)
|
||||
@@ -1,14 +0,0 @@
|
||||
from aiogram import Router
|
||||
|
||||
telegram_callback_router = Router()
|
||||
|
||||
# Import handlers to register them
|
||||
from . import ( # noqa: E402, F401
|
||||
chat_join_request,
|
||||
chat_member_updated,
|
||||
creative_commands,
|
||||
my_chat_member,
|
||||
project_commands,
|
||||
start_with_login,
|
||||
workspace_invites,
|
||||
)
|
||||
@@ -1,29 +0,0 @@
|
||||
import logging
|
||||
|
||||
from aiogram.types import ChatJoinRequest
|
||||
|
||||
from src import deps
|
||||
from src.controller.telegram_callback import telegram_callback_router
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@telegram_callback_router.chat_join_request()
|
||||
async def on_chat_join_request(event: ChatJoinRequest) -> None:
|
||||
if event.chat.type not in {'channel', 'supergroup'}:
|
||||
return
|
||||
|
||||
if not event.from_user:
|
||||
log.error('Failed to get user data from chat join request')
|
||||
return
|
||||
|
||||
invite_link = event.invite_link
|
||||
if not invite_link or not invite_link.invite_link:
|
||||
log.debug('No invite_link in chat join request event')
|
||||
return
|
||||
|
||||
await deps.get_usecase().handle_subscription(
|
||||
user_telegram_id=event.from_user.id,
|
||||
username=event.from_user.username,
|
||||
invite_link=invite_link.invite_link,
|
||||
)
|
||||
@@ -1,46 +0,0 @@
|
||||
import logging
|
||||
|
||||
from aiogram.types import ChatMemberUpdated
|
||||
|
||||
from src import deps
|
||||
from src.controller.telegram_callback import telegram_callback_router
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@telegram_callback_router.chat_member()
|
||||
async def on_chat_member_updated(event: ChatMemberUpdated) -> None:
|
||||
if event.chat.type not in {'channel', 'supergroup'}:
|
||||
return
|
||||
|
||||
if not event.from_user:
|
||||
log.error('Failed to get user data from chat member update')
|
||||
return
|
||||
|
||||
old_status = event.old_chat_member.status
|
||||
new_status = event.new_chat_member.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
|
||||
|
||||
# Проверяем что пользователь покинул канал
|
||||
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 = event.invite_link.invite_link if event.invite_link else None
|
||||
if not invite_link:
|
||||
log.debug('No invite_link in chat member update event')
|
||||
return
|
||||
|
||||
await deps.get_usecase().handle_subscription(
|
||||
user_telegram_id=event.from_user.id, username=event.from_user.username, invite_link=invite_link
|
||||
)
|
||||
|
||||
elif user_left:
|
||||
await deps.get_usecase().handle_unsubscription(
|
||||
user_telegram_id=event.from_user.id, channel_telegram_id=event.chat.id
|
||||
)
|
||||
@@ -1,68 +0,0 @@
|
||||
import logging
|
||||
|
||||
from aiogram import F
|
||||
from aiogram.filters import Command
|
||||
from aiogram.types import CallbackQuery, Message
|
||||
|
||||
from src import deps
|
||||
from src.controller.telegram_callback import telegram_callback_router
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@telegram_callback_router.message(Command('new_creative'))
|
||||
async def cmd_new_creative(message: Message) -> None:
|
||||
if not message.from_user:
|
||||
log.error('Failed to get user data from message')
|
||||
return
|
||||
|
||||
usecase = deps.get_usecase()
|
||||
await usecase.tg_create_creative_1(telegram_id=message.from_user.id, chat_id=message.chat.id)
|
||||
|
||||
|
||||
@telegram_callback_router.callback_query(F.data.startswith('tg_create_creative_2:'))
|
||||
async def callback_creative_workspace(callback: CallbackQuery) -> None:
|
||||
if not callback.from_user or not callback.message or not callback.data:
|
||||
log.error('Failed to get required data from callback query')
|
||||
return
|
||||
|
||||
usecase = deps.get_usecase()
|
||||
await usecase.tg_create_creative_2(
|
||||
telegram_id=callback.from_user.id,
|
||||
chat_id=callback.message.chat.id,
|
||||
callback_data=callback.data,
|
||||
message_id=callback.message.message_id,
|
||||
)
|
||||
|
||||
await callback.answer()
|
||||
|
||||
|
||||
@telegram_callback_router.callback_query(F.data.startswith('tg_create_creative_3:'))
|
||||
async def callback_creative_channel(callback: CallbackQuery) -> None:
|
||||
if not callback.from_user or not callback.message or not callback.data:
|
||||
log.error('Failed to get required data from callback query')
|
||||
return
|
||||
|
||||
usecase = deps.get_usecase()
|
||||
await usecase.tg_create_creative_3(
|
||||
telegram_id=callback.from_user.id,
|
||||
chat_id=callback.message.chat.id,
|
||||
callback_data=callback.data,
|
||||
message_id=callback.message.message_id,
|
||||
)
|
||||
|
||||
await callback.answer()
|
||||
|
||||
|
||||
@telegram_callback_router.message(F.text & ~F.text.startswith('/'))
|
||||
async def handle_text_message(message: Message) -> None:
|
||||
if not message.from_user or not message.text:
|
||||
return
|
||||
|
||||
usecase = deps.get_usecase()
|
||||
await usecase.tg_create_creative_4(
|
||||
telegram_id=message.from_user.id,
|
||||
chat_id=message.chat.id,
|
||||
text=message.text,
|
||||
text_html=message.html_text,
|
||||
)
|
||||
@@ -1,84 +0,0 @@
|
||||
import logging
|
||||
|
||||
from aiogram.types import ChatMemberAdministrator, ChatMemberUpdated
|
||||
from fastapi import HTTPException
|
||||
|
||||
from src import deps, dto
|
||||
from src.controller.telegram_callback import telegram_callback_router
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@telegram_callback_router.my_chat_member()
|
||||
async def on_bot_added_to_chat(event: ChatMemberUpdated) -> None:
|
||||
if event.chat.type not in {'channel', 'supergroup'}:
|
||||
return
|
||||
|
||||
if not event.from_user:
|
||||
log.error('Failed to get user data from chat member update')
|
||||
return
|
||||
|
||||
usecase = deps.get_usecase()
|
||||
|
||||
old_status = event.old_chat_member.status
|
||||
new_status = event.new_chat_member.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=event.chat.id,
|
||||
user_telegram_id=event.from_user.id,
|
||||
)
|
||||
try:
|
||||
await usecase.disconnect_project_by_tg_id(input=disconnect_input)
|
||||
except HTTPException as e:
|
||||
log.error(e)
|
||||
|
||||
return
|
||||
|
||||
# Проверяем изменение прав администратора
|
||||
permissions_changed = old_status == 'administrator' and new_status == 'administrator'
|
||||
|
||||
# Извлекаем права бота из события
|
||||
new_member = event.new_chat_member
|
||||
|
||||
bot_permissions = dto.ChannelBotPermissions(
|
||||
is_admin=isinstance(new_member, ChatMemberAdministrator),
|
||||
can_invite_users=getattr(new_member, 'can_invite_users', False),
|
||||
can_restrict_members=getattr(new_member, 'can_restrict_members', False),
|
||||
)
|
||||
|
||||
if permissions_changed:
|
||||
permissions_input = dto.UpdateProjectPermissionsInput(
|
||||
telegram_id=event.chat.id,
|
||||
permissions=bot_permissions,
|
||||
chat_title=event.chat.title or f'Channel {event.chat.id}',
|
||||
user_telegram_id=event.from_user.id,
|
||||
)
|
||||
try:
|
||||
await usecase.update_project_permissions(input=permissions_input)
|
||||
except HTTPException as e:
|
||||
log.error(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=event.chat.id,
|
||||
title=event.chat.title or f'Channel {event.chat.id}',
|
||||
username=event.chat.username,
|
||||
user_telegram_id=event.from_user.id,
|
||||
bot_permissions=bot_permissions,
|
||||
)
|
||||
try:
|
||||
await usecase.tg_add_project_1(input=connect_input)
|
||||
except HTTPException as e:
|
||||
log.error(e)
|
||||
@@ -1,26 +0,0 @@
|
||||
import logging
|
||||
|
||||
from aiogram import F
|
||||
from aiogram.types import CallbackQuery
|
||||
|
||||
from src import deps
|
||||
from src.controller.telegram_callback import telegram_callback_router
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@telegram_callback_router.callback_query(F.data.startswith('tg_add_project_2:'))
|
||||
async def callback_project_workspace(callback: CallbackQuery) -> None:
|
||||
if not callback.from_user or not callback.message or not callback.data:
|
||||
log.error('Failed to get required data from callback query')
|
||||
return
|
||||
|
||||
usecase = deps.get_usecase()
|
||||
await usecase.tg_add_project_2(
|
||||
telegram_id=callback.from_user.id,
|
||||
chat_id=callback.message.chat.id,
|
||||
callback_data=callback.data,
|
||||
message_id=callback.message.message_id,
|
||||
)
|
||||
|
||||
await callback.answer()
|
||||
@@ -1,41 +0,0 @@
|
||||
import logging
|
||||
|
||||
from aiogram.filters import CommandStart
|
||||
from aiogram.types import Message
|
||||
|
||||
from src import deps, dto
|
||||
from src.controller.telegram_callback import telegram_callback_router
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@telegram_callback_router.message(CommandStart(deep_link=True))
|
||||
async def cmd_start_with_deeplink(message: Message) -> None:
|
||||
if not message.from_user:
|
||||
log.error('Failed to get user data from tg_message')
|
||||
return
|
||||
|
||||
args = message.text.split() if message.text else []
|
||||
deeplink_param = args[1].split('_')[0] if len(args) > 1 else None
|
||||
|
||||
if deeplink_param != 'login':
|
||||
return
|
||||
|
||||
input = dto.TelegramLoginInput(
|
||||
telegram_id=message.from_user.id,
|
||||
username=message.from_user.username,
|
||||
chat_id=message.chat.id,
|
||||
)
|
||||
|
||||
await deps.get_usecase().telegram_login(input=input)
|
||||
|
||||
|
||||
@telegram_callback_router.message(CommandStart())
|
||||
async def cmd_start(message: Message) -> None:
|
||||
"""Handle /start command without deep link."""
|
||||
if not message.from_user:
|
||||
log.error('Failed to get user data from message')
|
||||
return
|
||||
|
||||
usecase = deps.get_usecase()
|
||||
await usecase.telegram_start(telegram_id=message.from_user.id, chat_id=message.chat.id)
|
||||
@@ -1,27 +0,0 @@
|
||||
import logging
|
||||
|
||||
from aiogram import F
|
||||
from aiogram.types import CallbackQuery
|
||||
|
||||
from src import deps
|
||||
from src.controller.telegram_callback import telegram_callback_router
|
||||
from src.usecase.workspace.create_workspace_invite import INVITE_ACCEPT_CALLBACK_PREFIX
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@telegram_callback_router.callback_query(F.data.startswith(f'{INVITE_ACCEPT_CALLBACK_PREFIX}:'))
|
||||
async def callback_workspace_invite_accept(callback: CallbackQuery) -> None:
|
||||
if not callback.from_user or not callback.message or not callback.data:
|
||||
log.error('Incomplete callback data for workspace invite acceptance')
|
||||
return
|
||||
|
||||
usecase = deps.get_usecase()
|
||||
await usecase.tg_accept_workspace_invite(
|
||||
telegram_id=callback.from_user.id,
|
||||
chat_id=callback.message.chat.id,
|
||||
callback_data=callback.data,
|
||||
message_id=callback.message.message_id,
|
||||
)
|
||||
|
||||
await callback.answer()
|
||||
Reference in New Issue
Block a user