fix: исправление флоу добавления каналов после workspaces
This commit is contained in:
@@ -9,4 +9,5 @@ from . import ( # noqa: E402, F401
|
|||||||
creative_commands,
|
creative_commands,
|
||||||
my_chat_member,
|
my_chat_member,
|
||||||
start_with_login,
|
start_with_login,
|
||||||
|
target_channel_commands,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -17,17 +17,34 @@ async def cmd_new_creative(message: Message) -> None:
|
|||||||
return
|
return
|
||||||
|
|
||||||
usecase = deps.get_usecase()
|
usecase = deps.get_usecase()
|
||||||
await usecase.start_creative_creation(telegram_id=message.from_user.id, chat_id=message.chat.id)
|
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('cc_ch:'))
|
@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:
|
async def callback_creative_channel(callback: CallbackQuery) -> None:
|
||||||
if not callback.from_user or not callback.message or not callback.data:
|
if not callback.from_user or not callback.message or not callback.data:
|
||||||
log.error('Failed to get required data from callback query')
|
log.error('Failed to get required data from callback query')
|
||||||
return
|
return
|
||||||
|
|
||||||
usecase = deps.get_usecase()
|
usecase = deps.get_usecase()
|
||||||
await usecase.handle_creative_callback(
|
await usecase.tg_create_creative_3(
|
||||||
telegram_id=callback.from_user.id,
|
telegram_id=callback.from_user.id,
|
||||||
chat_id=callback.message.chat.id,
|
chat_id=callback.message.chat.id,
|
||||||
callback_data=callback.data,
|
callback_data=callback.data,
|
||||||
@@ -43,7 +60,7 @@ async def handle_text_message(message: Message) -> None:
|
|||||||
return
|
return
|
||||||
|
|
||||||
usecase = deps.get_usecase()
|
usecase = deps.get_usecase()
|
||||||
await usecase.handle_creative_text_input(
|
await usecase.tg_create_creative_4(
|
||||||
telegram_id=message.from_user.id,
|
telegram_id=message.from_user.id,
|
||||||
chat_id=message.chat.id,
|
chat_id=message.chat.id,
|
||||||
text=message.text,
|
text=message.text,
|
||||||
|
|||||||
@@ -79,6 +79,6 @@ async def on_bot_added_to_chat(event: ChatMemberUpdated) -> None:
|
|||||||
bot_permissions=bot_permissions,
|
bot_permissions=bot_permissions,
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
await usecase.connect_target_chan(input=connect_input)
|
await usecase.tg_add_target_chan_1(input=connect_input)
|
||||||
except HTTPException as e:
|
except HTTPException as e:
|
||||||
log.error(e)
|
log.error(e)
|
||||||
|
|||||||
26
src/controller/telegram_callback/target_channel_commands.py
Normal file
26
src/controller/telegram_callback/target_channel_commands.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
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_target_chan_2:'))
|
||||||
|
async def callback_target_channel_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_target_chan_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()
|
||||||
@@ -6,9 +6,11 @@ from .base import TimestampedModel
|
|||||||
|
|
||||||
|
|
||||||
class TelegramStateEnum(str, enum.Enum):
|
class TelegramStateEnum(str, enum.Enum):
|
||||||
|
CREATIVE_WAITING_WORKSPACE = 'creative_waiting_workspace'
|
||||||
CREATIVE_WAITING_CHANNEL = 'creative_waiting_channel'
|
CREATIVE_WAITING_CHANNEL = 'creative_waiting_channel'
|
||||||
CREATIVE_WAITING_NAME = 'creative_waiting_name'
|
CREATIVE_WAITING_NAME = 'creative_waiting_name'
|
||||||
CREATIVE_WAITING_TEXT = 'creative_waiting_text'
|
CREATIVE_WAITING_TEXT = 'creative_waiting_text'
|
||||||
|
TARGET_CHANNEL_WAITING_WORKSPACE = 'target_channel_waiting_workspace'
|
||||||
|
|
||||||
|
|
||||||
class TelegramState(TimestampedModel):
|
class TelegramState(TimestampedModel):
|
||||||
|
|||||||
@@ -48,11 +48,6 @@ __all__ = (
|
|||||||
'SpendingDataPoint',
|
'SpendingDataPoint',
|
||||||
'GetSpendingAnalyticsInput',
|
'GetSpendingAnalyticsInput',
|
||||||
'GetSpendingAnalyticsOutput',
|
'GetSpendingAnalyticsOutput',
|
||||||
'ParserTrackTaskRequest',
|
|
||||||
'ParserGetViewsRequest',
|
|
||||||
'ParserPostFoundEvent',
|
|
||||||
'ParserPostDeletedEvent',
|
|
||||||
'ParserViewsUpdatedEvent',
|
|
||||||
'WorkspaceMembershipOutput',
|
'WorkspaceMembershipOutput',
|
||||||
'GetWorkspacesOutput',
|
'GetWorkspacesOutput',
|
||||||
'CreateWorkspaceInput',
|
'CreateWorkspaceInput',
|
||||||
@@ -93,13 +88,6 @@ from .external_channel import (
|
|||||||
UpdateExternalChannelInput,
|
UpdateExternalChannelInput,
|
||||||
UpdateExternalChannelLinksInput,
|
UpdateExternalChannelLinksInput,
|
||||||
)
|
)
|
||||||
from .nats_messages import (
|
|
||||||
ParserGetViewsRequest,
|
|
||||||
ParserPostDeletedEvent,
|
|
||||||
ParserPostFoundEvent,
|
|
||||||
ParserTrackTaskRequest,
|
|
||||||
ParserViewsUpdatedEvent,
|
|
||||||
)
|
|
||||||
from .placement import (
|
from .placement import (
|
||||||
CreatePlacementInput,
|
CreatePlacementInput,
|
||||||
DeletePlacementInput,
|
DeletePlacementInput,
|
||||||
|
|||||||
@@ -20,10 +20,10 @@ from .creative.create_creative import create_creative
|
|||||||
from .creative.delete_creative import delete_creative
|
from .creative.delete_creative import delete_creative
|
||||||
from .creative.get_creative import get_creative
|
from .creative.get_creative import get_creative
|
||||||
from .creative.get_creatives import get_creatives
|
from .creative.get_creatives import get_creatives
|
||||||
from .creative.handle_creative_callback import handle_creative_callback
|
from .creative.tg_create_creative_1 import tg_create_creative_1
|
||||||
from .creative.handle_creative_text_input import handle_creative_text_input
|
from .creative.tg_create_creative_2 import tg_create_creative_2
|
||||||
from .creative.handle_creative_workspace_callback import handle_creative_workspace_callback
|
from .creative.tg_create_creative_3 import tg_create_creative_3
|
||||||
from .creative.start_creative_creation import start_creative_creation
|
from .creative.tg_create_creative_4 import tg_create_creative_4
|
||||||
from .creative.update_creative import update_creative
|
from .creative.update_creative import update_creative
|
||||||
from .external_channel.create_external_channel import create_external_channel
|
from .external_channel.create_external_channel import create_external_channel
|
||||||
from .external_channel.delete_external_channel import delete_external_channel
|
from .external_channel.delete_external_channel import delete_external_channel
|
||||||
@@ -37,9 +37,10 @@ from .placement.get_placements import get_placements
|
|||||||
from .placement.update_placement import update_placement
|
from .placement.update_placement import update_placement
|
||||||
from .subscription.handle_subscription import handle_subscription
|
from .subscription.handle_subscription import handle_subscription
|
||||||
from .subscription.handle_unsubscription import handle_unsubscription
|
from .subscription.handle_unsubscription import handle_unsubscription
|
||||||
from .target_channel.connect_target_chan import connect_target_chan
|
|
||||||
from .target_channel.disconnect_target_chan_by_tg_id import disconnect_target_chan_by_tg_id
|
from .target_channel.disconnect_target_chan_by_tg_id import disconnect_target_chan_by_tg_id
|
||||||
from .target_channel.get_user_target_chans import get_user_target_chans
|
from .target_channel.get_user_target_chans import get_user_target_chans
|
||||||
|
from .target_channel.tg_add_target_chan_1 import tg_add_target_chan_1
|
||||||
|
from .target_channel.tg_add_target_chan_2 import tg_add_target_chan_2
|
||||||
from .target_channel.update_target_chan_permissions import update_target_chan_permissions
|
from .target_channel.update_target_chan_permissions import update_target_chan_permissions
|
||||||
from .views.get_views_history import get_views_history
|
from .views.get_views_history import get_views_history
|
||||||
from .workspace.create_workspace import create_workspace
|
from .workspace.create_workspace import create_workspace
|
||||||
@@ -214,11 +215,26 @@ class Usecase:
|
|||||||
|
|
||||||
return workspace
|
return workspace
|
||||||
|
|
||||||
|
async def get_or_create_personal_workspace(self, user: domain.User) -> domain.Workspace:
|
||||||
|
workspace = await self.database.get_default_workspace_for_user(user.id)
|
||||||
|
if workspace:
|
||||||
|
return workspace
|
||||||
|
|
||||||
|
workspace_name = user.username or f'Workspace {user.telegram_id}'
|
||||||
|
workspace = domain.Workspace(name=workspace_name)
|
||||||
|
|
||||||
|
async with self.database.transaction():
|
||||||
|
await self.database.create_workspace(workspace)
|
||||||
|
await self.database.add_user_to_workspace(workspace.id, user.id)
|
||||||
|
|
||||||
|
return workspace
|
||||||
|
|
||||||
validate_login_token = validate_login_token
|
validate_login_token = validate_login_token
|
||||||
telegram_login = telegram_login
|
telegram_login = telegram_login
|
||||||
telegram_start = telegram_start
|
telegram_start = telegram_start
|
||||||
get_me = get_me
|
get_me = get_me
|
||||||
connect_target_chan = connect_target_chan
|
tg_add_target_chan_1 = tg_add_target_chan_1
|
||||||
|
tg_add_target_chan_2 = tg_add_target_chan_2
|
||||||
get_user_target_chans = get_user_target_chans
|
get_user_target_chans = get_user_target_chans
|
||||||
disconnect_target_chan_by_tg_id = disconnect_target_chan_by_tg_id
|
disconnect_target_chan_by_tg_id = disconnect_target_chan_by_tg_id
|
||||||
update_target_chan_permissions = update_target_chan_permissions
|
update_target_chan_permissions = update_target_chan_permissions
|
||||||
@@ -232,10 +248,10 @@ class Usecase:
|
|||||||
create_creative = create_creative
|
create_creative = create_creative
|
||||||
update_creative = update_creative
|
update_creative = update_creative
|
||||||
delete_creative = delete_creative
|
delete_creative = delete_creative
|
||||||
start_creative_creation = start_creative_creation
|
tg_create_creative_1 = tg_create_creative_1
|
||||||
handle_creative_callback = handle_creative_callback
|
tg_create_creative_2 = tg_create_creative_2
|
||||||
handle_creative_workspace_callback = handle_creative_workspace_callback
|
tg_create_creative_3 = tg_create_creative_3
|
||||||
handle_creative_text_input = handle_creative_text_input
|
tg_create_creative_4 = tg_create_creative_4
|
||||||
get_placements = get_placements
|
get_placements = get_placements
|
||||||
get_placement = get_placement
|
get_placement = get_placement
|
||||||
create_placement = create_placement
|
create_placement = create_placement
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
import logging
|
|
||||||
from typing import TYPE_CHECKING
|
|
||||||
|
|
||||||
from aiogram.types import InlineKeyboardButton
|
|
||||||
|
|
||||||
from src import domain
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from .. import Usecase
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
async def start_creative_creation(self: 'Usecase', telegram_id: int, chat_id: int) -> None:
|
|
||||||
user = await self.database.get_user(telegram_id=telegram_id)
|
|
||||||
if not user:
|
|
||||||
await self.telegram_bot.send_message('❌ Вы не авторизованы. Используйте /start для входа.', chat_id)
|
|
||||||
return
|
|
||||||
|
|
||||||
channels = await self.database.get_user_target_channels(user.id)
|
|
||||||
if not channels:
|
|
||||||
await self.telegram_bot.send_message(
|
|
||||||
'❌ У вас нет подключенных каналов. Добавьте бота в свой канал для начала работы.', chat_id
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
buttons = [[InlineKeyboardButton(text=channel.title, callback_data=f'cc_ch:{channel.id}')] for channel in channels]
|
|
||||||
|
|
||||||
await self.telegram_bot.send_message_with_inline_keyboard(
|
|
||||||
'✨ Создание нового креатива\n\nВыберите канал, для которого создается креатив:', chat_id, buttons
|
|
||||||
)
|
|
||||||
|
|
||||||
await self.database.set_telegram_state(telegram_id, domain.TelegramStateEnum.CREATIVE_WAITING_CHANNEL, context={})
|
|
||||||
82
src/usecase/creative/tg_create_creative_1.py
Normal file
82
src/usecase/creative/tg_create_creative_1.py
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import logging
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from aiogram.types import InlineKeyboardButton
|
||||||
|
|
||||||
|
from src import domain
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .. import Usecase
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
async def tg_create_creative_1(self: 'Usecase', telegram_id: int, chat_id: int) -> None:
|
||||||
|
user = await self.database.get_user(telegram_id=telegram_id)
|
||||||
|
if not user:
|
||||||
|
await self.telegram_bot.send_message('❌ Вы не авторизованы. Используйте /start для входа.', chat_id)
|
||||||
|
return
|
||||||
|
|
||||||
|
memberships = await self.database.get_user_workspaces(user.id)
|
||||||
|
if not memberships:
|
||||||
|
await self.telegram_bot.send_message(
|
||||||
|
'❌ У вас нет рабочих пространств. Создайте workspace в веб-панели, чтобы продолжить.', chat_id
|
||||||
|
)
|
||||||
|
await self.database.clear_telegram_state(telegram_id)
|
||||||
|
return
|
||||||
|
|
||||||
|
if len(memberships) == 1:
|
||||||
|
workspace: domain.Workspace | None = memberships[0].workspace
|
||||||
|
if not workspace:
|
||||||
|
workspace = await self.database.get_workspace(memberships[0].workspace_id)
|
||||||
|
|
||||||
|
if not workspace:
|
||||||
|
log.error('Workspace %s not found for user %s', memberships[0].workspace_id, user.id)
|
||||||
|
await self.telegram_bot.send_message('❌ Рабочее пространство не найдено. Попробуйте позже.', chat_id)
|
||||||
|
await self.database.clear_telegram_state(telegram_id)
|
||||||
|
return
|
||||||
|
|
||||||
|
channels = await self.database.get_workspace_target_channels(workspace.id)
|
||||||
|
if not channels:
|
||||||
|
await self.telegram_bot.send_message(
|
||||||
|
'❌ В выбранном рабочем пространстве нет подключенных каналов. Добавьте канал и попробуйте снова.',
|
||||||
|
chat_id,
|
||||||
|
)
|
||||||
|
await self.database.clear_telegram_state(telegram_id)
|
||||||
|
return
|
||||||
|
|
||||||
|
buttons = [
|
||||||
|
[InlineKeyboardButton(text=channel.title, callback_data=f'tg_create_creative_3:{channel.id}')]
|
||||||
|
for channel in channels
|
||||||
|
]
|
||||||
|
|
||||||
|
await self.telegram_bot.send_message_with_inline_keyboard(
|
||||||
|
'✨ Создание нового креатива\n\nВыберите канал, для которого создается креатив:', chat_id, buttons
|
||||||
|
)
|
||||||
|
|
||||||
|
await self.database.set_telegram_state(
|
||||||
|
telegram_id,
|
||||||
|
domain.TelegramStateEnum.CREATIVE_WAITING_CHANNEL,
|
||||||
|
context={'workspace_id': str(workspace.id)},
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
buttons = [
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text=membership.workspace.name if membership.workspace else 'Workspace',
|
||||||
|
callback_data=f'tg_create_creative_2:{membership.workspace_id}',
|
||||||
|
)
|
||||||
|
]
|
||||||
|
for membership in memberships
|
||||||
|
]
|
||||||
|
|
||||||
|
await self.telegram_bot.send_message_with_inline_keyboard(
|
||||||
|
'✨ Создание нового креатива\n\nВыберите рабочее пространство:', chat_id, buttons
|
||||||
|
)
|
||||||
|
|
||||||
|
await self.database.set_telegram_state(
|
||||||
|
telegram_id,
|
||||||
|
domain.TelegramStateEnum.CREATIVE_WAITING_WORKSPACE,
|
||||||
|
context={},
|
||||||
|
)
|
||||||
72
src/usecase/creative/tg_create_creative_2.py
Normal file
72
src/usecase/creative/tg_create_creative_2.py
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import logging
|
||||||
|
import uuid
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from aiogram.types import InlineKeyboardButton
|
||||||
|
|
||||||
|
from src import domain
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .. import Usecase
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
async def tg_create_creative_2(
|
||||||
|
self: 'Usecase', telegram_id: int, chat_id: int, callback_data: str, message_id: int
|
||||||
|
) -> None:
|
||||||
|
state = await self.database.get_telegram_state(telegram_id)
|
||||||
|
if not state or state.state != domain.TelegramStateEnum.CREATIVE_WAITING_WORKSPACE:
|
||||||
|
log.debug('User %s has no active creative workspace selection flow', telegram_id)
|
||||||
|
return
|
||||||
|
|
||||||
|
if not callback_data.startswith('tg_create_creative_2:'):
|
||||||
|
log.warning('Invalid workspace callback data format: %s', callback_data)
|
||||||
|
return
|
||||||
|
|
||||||
|
workspace_id_str = callback_data.split(':', 1)[1]
|
||||||
|
try:
|
||||||
|
workspace_id = uuid.UUID(workspace_id_str)
|
||||||
|
except ValueError:
|
||||||
|
log.error('Invalid workspace UUID in callback data: %s', workspace_id_str)
|
||||||
|
await self.telegram_bot.send_message('❌ Некорректное рабочее пространство.', chat_id)
|
||||||
|
await self.database.clear_telegram_state(telegram_id)
|
||||||
|
return
|
||||||
|
|
||||||
|
user = await self.database.get_user(telegram_id=telegram_id)
|
||||||
|
if not user:
|
||||||
|
await self.telegram_bot.send_message('Вы не зарегистрированы на платформе.', chat_id)
|
||||||
|
await self.database.clear_telegram_state(telegram_id)
|
||||||
|
return
|
||||||
|
|
||||||
|
workspace = await self.database.get_workspace_for_user(workspace_id, user.id)
|
||||||
|
if not workspace:
|
||||||
|
await self.telegram_bot.send_message('❌ Рабочее пространство недоступно.', chat_id)
|
||||||
|
await self.database.clear_telegram_state(telegram_id)
|
||||||
|
return
|
||||||
|
|
||||||
|
await self.telegram_bot.edit_message_reply_markup(chat_id, message_id)
|
||||||
|
|
||||||
|
channels = await self.database.get_workspace_target_channels(workspace.id)
|
||||||
|
if not channels:
|
||||||
|
await self.telegram_bot.send_message(
|
||||||
|
'❌ В выбранном рабочем пространстве нет подключенных каналов. Добавьте канал и попробуйте снова.',
|
||||||
|
chat_id,
|
||||||
|
)
|
||||||
|
await self.database.clear_telegram_state(telegram_id)
|
||||||
|
return
|
||||||
|
|
||||||
|
buttons = [
|
||||||
|
[InlineKeyboardButton(text=channel.title, callback_data=f'tg_create_creative_3:{channel.id}')]
|
||||||
|
for channel in channels
|
||||||
|
]
|
||||||
|
|
||||||
|
await self.telegram_bot.send_message_with_inline_keyboard(
|
||||||
|
'✨ Создание нового креатива\n\nВыберите канал, для которого создается креатив:', chat_id, buttons
|
||||||
|
)
|
||||||
|
|
||||||
|
await self.database.set_telegram_state(
|
||||||
|
telegram_id,
|
||||||
|
domain.TelegramStateEnum.CREATIVE_WAITING_CHANNEL,
|
||||||
|
context={'workspace_id': str(workspace.id)},
|
||||||
|
)
|
||||||
@@ -10,7 +10,7 @@ if TYPE_CHECKING:
|
|||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
async def handle_creative_callback(
|
async def tg_create_creative_3(
|
||||||
self: 'Usecase', telegram_id: int, chat_id: int, callback_data: str, message_id: int
|
self: 'Usecase', telegram_id: int, chat_id: int, callback_data: str, message_id: int
|
||||||
) -> None:
|
) -> None:
|
||||||
state = await self.database.get_telegram_state(telegram_id)
|
state = await self.database.get_telegram_state(telegram_id)
|
||||||
@@ -18,7 +18,7 @@ async def handle_creative_callback(
|
|||||||
log.debug('User %s has no active creative creation flow or wrong state', telegram_id)
|
log.debug('User %s has no active creative creation flow or wrong state', telegram_id)
|
||||||
return
|
return
|
||||||
|
|
||||||
if not callback_data.startswith('cc_ch:'):
|
if not callback_data.startswith('tg_create_creative_3:'):
|
||||||
log.warning('Invalid callback data format: %s', callback_data)
|
log.warning('Invalid callback data format: %s', callback_data)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -31,11 +31,34 @@ async def handle_creative_callback(
|
|||||||
|
|
||||||
user = await self.database.get_user(telegram_id=telegram_id)
|
user = await self.database.get_user(telegram_id=telegram_id)
|
||||||
if not user:
|
if not user:
|
||||||
await self.telegram_bot.send_message('❌ Ошибка: пользователь не найден.', chat_id)
|
await self.telegram_bot.send_message('Вы не зарегистрированы на платформе.', chat_id)
|
||||||
await self.database.clear_telegram_state(telegram_id)
|
await self.database.clear_telegram_state(telegram_id)
|
||||||
return
|
return
|
||||||
|
|
||||||
channel = await self.database.get_target_channel(user.id, channel_id=channel_id)
|
workspace = None
|
||||||
|
workspace_id = None
|
||||||
|
|
||||||
|
workspace_id_str = state.context.get('workspace_id') if state.context else None
|
||||||
|
if workspace_id_str:
|
||||||
|
try:
|
||||||
|
workspace_id = uuid.UUID(workspace_id_str)
|
||||||
|
except ValueError:
|
||||||
|
workspace_id = None
|
||||||
|
|
||||||
|
if workspace_id:
|
||||||
|
workspace = await self.database.get_workspace_for_user(workspace_id, user.id)
|
||||||
|
|
||||||
|
if not workspace:
|
||||||
|
workspace = await self.get_or_create_personal_workspace(user)
|
||||||
|
workspace_id = workspace.id
|
||||||
|
|
||||||
|
if not workspace_id:
|
||||||
|
log.error('Workspace ID missing for user %s when selecting channel %s', telegram_id, channel_id)
|
||||||
|
await self.telegram_bot.send_message('❌ Ошибка: рабочее пространство не найдено. Начните заново.', chat_id)
|
||||||
|
await self.database.clear_telegram_state(telegram_id)
|
||||||
|
return
|
||||||
|
|
||||||
|
channel = await self.database.get_target_channel(workspace_id, channel_id=channel_id)
|
||||||
if not channel:
|
if not channel:
|
||||||
await self.telegram_bot.send_message('❌ Канал не найден или не принадлежит вам.', chat_id)
|
await self.telegram_bot.send_message('❌ Канал не найден или не принадлежит вам.', chat_id)
|
||||||
await self.database.clear_telegram_state(telegram_id)
|
await self.database.clear_telegram_state(telegram_id)
|
||||||
@@ -44,7 +67,11 @@ async def handle_creative_callback(
|
|||||||
await self.database.set_telegram_state(
|
await self.database.set_telegram_state(
|
||||||
telegram_id,
|
telegram_id,
|
||||||
domain.TelegramStateEnum.CREATIVE_WAITING_NAME,
|
domain.TelegramStateEnum.CREATIVE_WAITING_NAME,
|
||||||
{'target_channel_id': str(channel_id), 'channel_title': channel.title},
|
{
|
||||||
|
'target_channel_id': str(channel_id),
|
||||||
|
'channel_title': channel.title,
|
||||||
|
'workspace_id': str(workspace_id),
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
await self.telegram_bot.edit_message_reply_markup(chat_id, message_id)
|
await self.telegram_bot.edit_message_reply_markup(chat_id, message_id)
|
||||||
@@ -12,7 +12,7 @@ if TYPE_CHECKING:
|
|||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
async def handle_creative_text_input(
|
async def tg_create_creative_4(
|
||||||
self: 'Usecase', telegram_id: int, chat_id: int, text: str, text_html: str | None = None
|
self: 'Usecase', telegram_id: int, chat_id: int, text: str, text_html: str | None = None
|
||||||
) -> None:
|
) -> None:
|
||||||
state = await self.database.get_telegram_state(telegram_id)
|
state = await self.database.get_telegram_state(telegram_id)
|
||||||
@@ -40,6 +40,7 @@ async def handle_creative_text_input(
|
|||||||
elif state.state == domain.TelegramStateEnum.CREATIVE_WAITING_TEXT:
|
elif state.state == domain.TelegramStateEnum.CREATIVE_WAITING_TEXT:
|
||||||
target_channel_id_str = state.context.get('target_channel_id')
|
target_channel_id_str = state.context.get('target_channel_id')
|
||||||
creative_name = state.context.get('creative_name')
|
creative_name = state.context.get('creative_name')
|
||||||
|
workspace_id_str = state.context.get('workspace_id')
|
||||||
|
|
||||||
if not target_channel_id_str or not creative_name:
|
if not target_channel_id_str or not creative_name:
|
||||||
log.error('Missing context data for creative creation: %s', state.context)
|
log.error('Missing context data for creative creation: %s', state.context)
|
||||||
@@ -55,7 +56,29 @@ async def handle_creative_text_input(
|
|||||||
await self.database.clear_telegram_state(telegram_id)
|
await self.database.clear_telegram_state(telegram_id)
|
||||||
return
|
return
|
||||||
|
|
||||||
channel = await self.database.get_target_channel(user.id, channel_id=target_channel_id)
|
workspace = None
|
||||||
|
workspace_id = None
|
||||||
|
|
||||||
|
if workspace_id_str:
|
||||||
|
try:
|
||||||
|
workspace_id = uuid.UUID(workspace_id_str)
|
||||||
|
except ValueError:
|
||||||
|
workspace_id = None
|
||||||
|
|
||||||
|
if workspace_id:
|
||||||
|
workspace = await self.database.get_workspace_for_user(workspace_id, user.id)
|
||||||
|
|
||||||
|
if not workspace:
|
||||||
|
workspace = await self.get_or_create_personal_workspace(user)
|
||||||
|
workspace_id = workspace.id
|
||||||
|
|
||||||
|
if not workspace_id:
|
||||||
|
log.error('Workspace ID missing while finalizing creative for user %s', telegram_id)
|
||||||
|
await self.telegram_bot.send_message('❌ Ошибка: рабочее пространство не найдено. Начните заново.', chat_id)
|
||||||
|
await self.database.clear_telegram_state(telegram_id)
|
||||||
|
return
|
||||||
|
|
||||||
|
channel = await self.database.get_target_channel(workspace_id, channel_id=target_channel_id)
|
||||||
if not channel:
|
if not channel:
|
||||||
await self.telegram_bot.send_message('❌ Канал не найден.', chat_id)
|
await self.telegram_bot.send_message('❌ Канал не найден.', chat_id)
|
||||||
await self.database.clear_telegram_state(telegram_id)
|
await self.database.clear_telegram_state(telegram_id)
|
||||||
@@ -68,7 +91,7 @@ async def handle_creative_text_input(
|
|||||||
name=creative_name,
|
name=creative_name,
|
||||||
text=creative_text,
|
text=creative_text,
|
||||||
status=domain.CreativeStatus.ACTIVE,
|
status=domain.CreativeStatus.ACTIVE,
|
||||||
user_id=user.id,
|
workspace_id=workspace_id,
|
||||||
target_channel_id=target_channel_id,
|
target_channel_id=target_channel_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
import logging
|
|
||||||
from typing import TYPE_CHECKING
|
|
||||||
|
|
||||||
from src import domain, dto
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from .. import Usecase
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
async def connect_target_chan(self: 'Usecase', input: dto.ConnectTargetChanInput) -> dto.ConnectTargetChanOutput:
|
|
||||||
permissions = input.bot_permissions
|
|
||||||
|
|
||||||
if not permissions.is_admin:
|
|
||||||
log.warning(f'Bot is not admin in channel {input.telegram_id}. Attempted by user {input.user_telegram_id}')
|
|
||||||
await self.telegram_bot.send_message(
|
|
||||||
f'⚠️ Бот был добавлен в канал "{input.title}", но не является админом.\n\n'
|
|
||||||
'Пожалуйста, сделайте бота администратором канала.',
|
|
||||||
input.user_telegram_id,
|
|
||||||
)
|
|
||||||
raise domain.ChannelNoAdminRights()
|
|
||||||
|
|
||||||
missing_permissions = []
|
|
||||||
if not permissions.can_invite_users:
|
|
||||||
missing_permissions.append('Создание инвайт-ссылок')
|
|
||||||
if not permissions.can_restrict_members:
|
|
||||||
missing_permissions.append('Управление пользователями (видеть вступления)')
|
|
||||||
|
|
||||||
if missing_permissions:
|
|
||||||
log.warning(
|
|
||||||
f'Bot lacks required permissions in channel {input.telegram_id}: {missing_permissions}. '
|
|
||||||
f'Attempted by user {input.user_telegram_id}'
|
|
||||||
)
|
|
||||||
permissions_text = '\n'.join(f'• {p}' for p in missing_permissions)
|
|
||||||
await self.telegram_bot.send_message(
|
|
||||||
f'⚠️ Бот был добавлен в канал "{input.title}", но не имеет необходимых прав.\n\n'
|
|
||||||
f'Отсутствующие права:\n{permissions_text}\n\n'
|
|
||||||
'Пожалуйста, предоставьте эти права боту в настройках канала.',
|
|
||||||
input.user_telegram_id,
|
|
||||||
)
|
|
||||||
raise domain.ChannelNoAdminRights()
|
|
||||||
|
|
||||||
user = await self.database.get_user(telegram_id=input.user_telegram_id)
|
|
||||||
if not user:
|
|
||||||
log.warning(f'User {input.user_telegram_id} not found when trying to connect channel {input.telegram_id}')
|
|
||||||
await self.telegram_bot.send_message(
|
|
||||||
f'⚠️ Канал "{input.title}" не может быть подключен.\n\n'
|
|
||||||
'Вы должны сначала авторизоваться в веб-панели перед подключением каналов.',
|
|
||||||
input.user_telegram_id,
|
|
||||||
)
|
|
||||||
raise domain.UserNotFound()
|
|
||||||
|
|
||||||
channel = domain.TargetChannel(
|
|
||||||
telegram_id=input.telegram_id,
|
|
||||||
title=input.title,
|
|
||||||
username=input.username,
|
|
||||||
user_id=user.id,
|
|
||||||
status=domain.TargetChannelStatus.ACTIVE,
|
|
||||||
)
|
|
||||||
|
|
||||||
await self.database.upsert_target_channel(channel)
|
|
||||||
log.info(f'Channel {input.telegram_id} connected/updated successfully by user {input.user_telegram_id}')
|
|
||||||
|
|
||||||
await self.telegram_bot.send_message(f'✅ Канал "{input.title}" успешно подключен!', input.user_telegram_id)
|
|
||||||
|
|
||||||
return dto.ConnectTargetChanOutput(
|
|
||||||
id=channel.id,
|
|
||||||
telegram_id=channel.telegram_id,
|
|
||||||
title=channel.title,
|
|
||||||
username=channel.username,
|
|
||||||
status=channel.status,
|
|
||||||
is_active=channel.status == domain.TargetChannelStatus.ACTIVE,
|
|
||||||
)
|
|
||||||
143
src/usecase/target_channel/tg_add_target_chan_1.py
Normal file
143
src/usecase/target_channel/tg_add_target_chan_1.py
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
import logging
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from aiogram.types import InlineKeyboardButton
|
||||||
|
|
||||||
|
from src import domain, dto
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .. import Usecase
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
async def tg_add_target_chan_1(
|
||||||
|
self: 'Usecase', input: dto.ConnectTargetChanInput
|
||||||
|
) -> dto.ConnectTargetChanOutput | None:
|
||||||
|
permissions = input.bot_permissions
|
||||||
|
|
||||||
|
if not permissions.is_admin:
|
||||||
|
log.warning(f'Bot is not admin in channel {input.telegram_id}. Attempted by user {input.user_telegram_id}')
|
||||||
|
await self.telegram_bot.send_message(
|
||||||
|
f'⚠️ Бот был добавлен в канал "{input.title}", но не является админом.\n\n'
|
||||||
|
'Пожалуйста, сделайте бота администратором канала.',
|
||||||
|
input.user_telegram_id,
|
||||||
|
)
|
||||||
|
raise domain.ChannelNoAdminRights()
|
||||||
|
|
||||||
|
missing_permissions = []
|
||||||
|
if not permissions.can_invite_users:
|
||||||
|
missing_permissions.append('Создание инвайт-ссылок')
|
||||||
|
if not permissions.can_restrict_members:
|
||||||
|
missing_permissions.append('Управление пользователями (видеть вступления)')
|
||||||
|
|
||||||
|
if missing_permissions:
|
||||||
|
log.warning(
|
||||||
|
f'Bot lacks required permissions in channel {input.telegram_id}: {missing_permissions}. '
|
||||||
|
f'Attempted by user {input.user_telegram_id}'
|
||||||
|
)
|
||||||
|
permissions_text = '\n'.join(f'• {p}' for p in missing_permissions)
|
||||||
|
await self.telegram_bot.send_message(
|
||||||
|
f'⚠️ Бот был добавлен в канал "{input.title}", но не имеет необходимых прав.\n\n'
|
||||||
|
f'Отсутствующие права:\n{permissions_text}\n\n'
|
||||||
|
'Пожалуйста, предоставьте эти права боту в настройках канала.',
|
||||||
|
input.user_telegram_id,
|
||||||
|
)
|
||||||
|
raise domain.ChannelNoAdminRights()
|
||||||
|
|
||||||
|
user = await self.database.get_user(telegram_id=input.user_telegram_id)
|
||||||
|
if not user:
|
||||||
|
log.warning(f'User {input.user_telegram_id} not found when trying to connect channel {input.telegram_id}')
|
||||||
|
await self.telegram_bot.send_message(
|
||||||
|
f'⚠️ Канал "{input.title}" не может быть подключен.\n\n'
|
||||||
|
'Вы должны сначала авторизоваться в веб-панели перед подключением каналов.',
|
||||||
|
input.user_telegram_id,
|
||||||
|
)
|
||||||
|
raise domain.UserNotFound()
|
||||||
|
|
||||||
|
memberships = await self.database.get_user_workspaces(user.id)
|
||||||
|
workspaces: list[domain.Workspace] = []
|
||||||
|
for membership in memberships:
|
||||||
|
workspace: domain.Workspace | None = membership.workspace
|
||||||
|
if not workspace:
|
||||||
|
workspace = await self.database.get_workspace(membership.workspace_id)
|
||||||
|
|
||||||
|
if workspace:
|
||||||
|
workspaces.append(workspace)
|
||||||
|
|
||||||
|
if not workspaces:
|
||||||
|
workspace = await self.get_or_create_personal_workspace(user)
|
||||||
|
workspaces = [workspace]
|
||||||
|
|
||||||
|
if len(workspaces) == 1:
|
||||||
|
workspace = workspaces[0]
|
||||||
|
channel = domain.TargetChannel(
|
||||||
|
telegram_id=input.telegram_id,
|
||||||
|
title=input.title,
|
||||||
|
username=input.username,
|
||||||
|
workspace_id=workspace.id,
|
||||||
|
status=domain.TargetChannelStatus.ACTIVE,
|
||||||
|
)
|
||||||
|
|
||||||
|
await self.database.upsert_target_channel(channel)
|
||||||
|
log.info(
|
||||||
|
'Channel %s connected/updated successfully in workspace %s by user %s',
|
||||||
|
input.telegram_id,
|
||||||
|
workspace.id,
|
||||||
|
input.user_telegram_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
await self.telegram_bot.send_message(
|
||||||
|
f'✅ Канал "{input.title}" добавлен в рабочее пространство "{workspace.name}".', input.user_telegram_id
|
||||||
|
)
|
||||||
|
|
||||||
|
return dto.ConnectTargetChanOutput(
|
||||||
|
id=channel.id,
|
||||||
|
telegram_id=channel.telegram_id,
|
||||||
|
title=channel.title,
|
||||||
|
username=channel.username,
|
||||||
|
status=channel.status,
|
||||||
|
is_active=channel.status == domain.TargetChannelStatus.ACTIVE,
|
||||||
|
)
|
||||||
|
|
||||||
|
current_state = await self.database.get_telegram_state(user.telegram_id)
|
||||||
|
if current_state and current_state.state == domain.TelegramStateEnum.TARGET_CHANNEL_WAITING_WORKSPACE:
|
||||||
|
await self.telegram_bot.send_message(
|
||||||
|
'⚠️ Сначала завершите выбор рабочего пространства для предыдущего канала.', user.telegram_id
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
context: dict[str, object] = {
|
||||||
|
'channel_data': {
|
||||||
|
'telegram_id': input.telegram_id,
|
||||||
|
'title': input.title,
|
||||||
|
'username': input.username,
|
||||||
|
'permissions': input.bot_permissions.model_dump(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if current_state:
|
||||||
|
context['previous_state'] = current_state.state.value
|
||||||
|
context['previous_context'] = dict(current_state.context or {})
|
||||||
|
|
||||||
|
await self.database.set_telegram_state(
|
||||||
|
user.telegram_id,
|
||||||
|
domain.TelegramStateEnum.TARGET_CHANNEL_WAITING_WORKSPACE,
|
||||||
|
context,
|
||||||
|
)
|
||||||
|
|
||||||
|
buttons = [
|
||||||
|
[InlineKeyboardButton(text=workspace.name, callback_data=f'tg_add_target_chan_2:{workspace.id}')]
|
||||||
|
for workspace in workspaces
|
||||||
|
]
|
||||||
|
|
||||||
|
await self.telegram_bot.send_message_with_inline_keyboard(
|
||||||
|
(
|
||||||
|
'✨ Подключение канала\n\n'
|
||||||
|
f'Выберите рабочее пространство, в которое добавить канал "{input.title}":'
|
||||||
|
),
|
||||||
|
user.telegram_id,
|
||||||
|
buttons,
|
||||||
|
)
|
||||||
|
|
||||||
|
return None
|
||||||
93
src/usecase/target_channel/tg_add_target_chan_2.py
Normal file
93
src/usecase/target_channel/tg_add_target_chan_2.py
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import logging
|
||||||
|
import uuid
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from src import domain
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .. import Usecase
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
async def tg_add_target_chan_2(
|
||||||
|
self: 'Usecase', telegram_id: int, chat_id: int, callback_data: str, message_id: int
|
||||||
|
) -> None:
|
||||||
|
state = await self.database.get_telegram_state(telegram_id)
|
||||||
|
if not state or state.state != domain.TelegramStateEnum.TARGET_CHANNEL_WAITING_WORKSPACE:
|
||||||
|
log.debug('User %s has no active target channel workspace selection flow', telegram_id)
|
||||||
|
return
|
||||||
|
|
||||||
|
context = state.context or {}
|
||||||
|
previous_state_value = context.get('previous_state')
|
||||||
|
previous_context = context.get('previous_context') or {}
|
||||||
|
previous_state = None
|
||||||
|
if previous_state_value:
|
||||||
|
try:
|
||||||
|
previous_state = domain.TelegramStateEnum(previous_state_value)
|
||||||
|
except ValueError:
|
||||||
|
previous_state = None
|
||||||
|
|
||||||
|
if not callback_data.startswith('tg_add_target_chan_2:'):
|
||||||
|
log.warning('Invalid target channel workspace callback data: %s', callback_data)
|
||||||
|
return
|
||||||
|
|
||||||
|
workspace_id_str = callback_data.split(':', 1)[1]
|
||||||
|
try:
|
||||||
|
workspace_id = uuid.UUID(workspace_id_str)
|
||||||
|
except ValueError:
|
||||||
|
log.error('Invalid workspace UUID in callback data: %s', workspace_id_str)
|
||||||
|
await self.telegram_bot.send_message('❌ Некорректное рабочее пространство.', chat_id)
|
||||||
|
if previous_state:
|
||||||
|
await self.database.set_telegram_state(telegram_id, previous_state, previous_context)
|
||||||
|
else:
|
||||||
|
await self.database.clear_telegram_state(telegram_id)
|
||||||
|
return
|
||||||
|
|
||||||
|
channel_data = state.context.get('channel_data') if state.context else None
|
||||||
|
if not channel_data:
|
||||||
|
log.error('Missing channel data for user %s in target channel flow', telegram_id)
|
||||||
|
await self.telegram_bot.send_message('❌ Не удалось подключить канал. Попробуйте снова.', chat_id)
|
||||||
|
if previous_state:
|
||||||
|
await self.database.set_telegram_state(telegram_id, previous_state, previous_context)
|
||||||
|
else:
|
||||||
|
await self.database.clear_telegram_state(telegram_id)
|
||||||
|
return
|
||||||
|
|
||||||
|
user = await self.database.get_user(telegram_id=telegram_id)
|
||||||
|
if not user:
|
||||||
|
await self.telegram_bot.send_message('Вы не зарегистрированы на платформе.', chat_id)
|
||||||
|
await self.database.clear_telegram_state(telegram_id)
|
||||||
|
return
|
||||||
|
|
||||||
|
workspace = await self.database.get_workspace_for_user(workspace_id, user.id)
|
||||||
|
if not workspace:
|
||||||
|
await self.telegram_bot.send_message('❌ У вас нет доступа к выбранному рабочему пространству.', chat_id)
|
||||||
|
return
|
||||||
|
|
||||||
|
channel = domain.TargetChannel(
|
||||||
|
telegram_id=int(channel_data['telegram_id']),
|
||||||
|
title=str(channel_data['title']),
|
||||||
|
username=channel_data.get('username'),
|
||||||
|
workspace_id=workspace.id,
|
||||||
|
status=domain.TargetChannelStatus.ACTIVE,
|
||||||
|
)
|
||||||
|
|
||||||
|
await self.database.upsert_target_channel(channel)
|
||||||
|
log.info(
|
||||||
|
'Channel %s connected/updated successfully in workspace %s by user %s',
|
||||||
|
channel.telegram_id,
|
||||||
|
workspace.id,
|
||||||
|
telegram_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
await self.telegram_bot.send_message(
|
||||||
|
f'✅ Канал "{channel.title}" добавлен в рабочее пространство "{workspace.name}".', telegram_id
|
||||||
|
)
|
||||||
|
|
||||||
|
await self.telegram_bot.edit_message_reply_markup(chat_id, message_id)
|
||||||
|
|
||||||
|
if previous_state:
|
||||||
|
await self.database.set_telegram_state(telegram_id, previous_state, previous_context)
|
||||||
|
else:
|
||||||
|
await self.database.clear_telegram_state(telegram_id)
|
||||||
@@ -6,6 +6,6 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
async def delete_workspace(self: 'Usecase', workspace_id: uuid.UUID, user_id: uuid.UUID) -> None:
|
async def delete_workspace(self: 'Usecase', workspace_id: uuid.UUID, user_id: uuid.UUID) -> None:
|
||||||
self.ensure_workspace_access(workspace_id, user_id)
|
await self.ensure_workspace_access(workspace_id, user_id)
|
||||||
|
|
||||||
await self.database.delete_workspace(workspace_id)
|
await self.database.delete_workspace(workspace_id)
|
||||||
|
|||||||
Reference in New Issue
Block a user