Files
TG_autoposter/app/handlers/message_manager.py
Andrew K. Choi 48f8c6f0eb UserBot Integration Complete: Fixed container startup, integrated UserBot menu to main bot
MAJOR FIXES:
 Fixed UserBot container startup by making TELEGRAM_BOT_TOKEN optional
 Broke circular import chain between app modules
 Made Config.validate() conditional for UserBot-only mode
 Removed unused celery import from userbot_service.py

INTEGRATION:
 UserBot menu now accessible from main bot /start command
 Added 🤖 UserBot button to main keyboard
 Integrated userbot_manager.py handlers:
   - userbot_menu: Main UserBot interface
   - userbot_settings: Configuration
   - userbot_collect_groups: Gather all user groups
   - userbot_collect_members: Parse group members
 UserBot handlers properly registered in ConversationHandler

CONTAINERS:
 tg_autoposter_bot: Running and handling /start commands
 tg_autoposter_userbot: Running as standalone microservice
 All dependent services (Redis, PostgreSQL, Celery workers) operational

STATUS: Bot is fully operational and ready for testing
2025-12-21 12:09:11 +09:00

237 lines
9.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from telegram import Update, InlineKeyboardMarkup, InlineKeyboardButton
from telegram.ext import ContextTypes
from app.database import AsyncSessionLocal
from app.database.repository import (
GroupRepository, MessageRepository, MessageGroupRepository
)
from app.utils.keyboards import (
get_back_keyboard, get_main_keyboard, CallbackType
)
import logging
logger = logging.getLogger(__name__)
# Состояния (теперь управляются через context.user_data)
STATE_WAITING_MSG_TITLE = "waiting_title"
STATE_WAITING_MSG_TEXT = "waiting_text"
STATE_SELECTING_GROUPS = "selecting_groups"
async def handle_message_input(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Распределяет текстовый ввод в зависимости от текущего состояния"""
state = context.user_data.get('message_state')
if state == STATE_WAITING_MSG_TITLE:
await create_message_title(update, context)
elif state == STATE_WAITING_MSG_TEXT:
await create_message_text(update, context)
# Если не в состоянии ввода - игнорируем
async def create_message_start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Начало создания нового сообщения"""
query = update.callback_query
logger.info(f"📝 Начало создания сообщения от пользователя {update.effective_user.id}")
await query.answer()
# Инициализируем состояние
context.user_data['message_state'] = STATE_WAITING_MSG_TITLE
context.user_data['message_title'] = None
context.user_data['message_text'] = None
context.user_data['selected_groups'] = set()
text = "📝 Введите название сообщения (короткое описание):"
await query.edit_message_text(text)
async def create_message_title(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Получаем название и просим текст"""
message = update.message
# Проверяем что мы в правильном состоянии
if context.user_data.get('message_state') != STATE_WAITING_MSG_TITLE:
return
logger.info(f"📝 Получено название сообщения: {message.text[:50]}")
title = message.text.strip()
if len(title) > 100:
await message.reply_text("❌ Название слишком длинное (макс 100 символов)")
return
context.user_data['message_title'] = title
context.user_data['message_state'] = STATE_WAITING_MSG_TEXT
text = """✏️ Теперь введите текст сообщения.
Вы можете использовать HTML форматирование:
<b>жирный</b>
<i>курсив</i>
<u>подчеркивание</u>
<code>код</code>
Введите /cancel для отмены"""
await message.reply_text(text, parse_mode='HTML')
async def create_message_text(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Получаем текст и показываем выбор групп"""
message = update.message
# Проверяем что мы в правильном состоянии
if context.user_data.get('message_state') != STATE_WAITING_MSG_TEXT:
return
logger.info(f"📝 Получен текст сообщения от пользователя {update.effective_user.id}")
if message.text == '/cancel':
context.user_data['message_state'] = None
await message.reply_text("❌ Отменено", reply_markup=get_main_keyboard())
return
text = message.text.strip()
if len(text) > 4096:
await message.reply_text("❌ Текст слишком длинный (макс 4096 символов)")
return
context.user_data['message_text'] = text
# Сохраняем сообщение в БД
async with AsyncSessionLocal() as session:
msg_repo = MessageRepository(session)
msg = await msg_repo.add_message(
text=text,
title=context.user_data['message_title']
)
context.user_data['message_id'] = msg.id
# Теперь показываем список групп для выбора
async with AsyncSessionLocal() as session:
group_repo = GroupRepository(session)
groups = await group_repo.get_all_active_groups()
if not groups:
context.user_data['message_state'] = None
await message.reply_text(
"❌ Нет активных групп. Сначала добавьте бота в группы.",
reply_markup=get_main_keyboard()
)
return
# Создаем клавиатуру с группами
keyboard = []
for group in groups:
callback = f"select_group_{group.id}"
keyboard.append([InlineKeyboardButton(
f"{group.title} (delay: {group.slow_mode_delay}s)",
callback_data=callback
)])
keyboard.append([InlineKeyboardButton("✔️ Готово", callback_data="done_groups")])
keyboard.append([InlineKeyboardButton("❌ Отмена", callback_data=CallbackType.MAIN_MENU.value)])
text = f"""✅ Сообщение создано: <b>{context.user_data['message_title']}</b>
Выберите группы для отправки (нажмите на каждую):"""
await message.reply_text(
text,
parse_mode='HTML',
reply_markup=InlineKeyboardMarkup(keyboard)
)
context.user_data['selected_groups'] = set()
context.user_data['message_state'] = STATE_SELECTING_GROUPS
async def select_groups(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Выбор групп для отправки"""
query = update.callback_query
callback_data = query.data
# Проверяем что мы в правильном состоянии
if context.user_data.get('message_state') != STATE_SELECTING_GROUPS:
return
logger.info(f"🔘 Получен callback select_groups: {callback_data} от пользователя {update.effective_user.id}")
await query.answer()
if callback_data == "done_groups":
# Подтверждаем выбор
selected = context.user_data.get('selected_groups', set())
if not selected:
await query.answer("❌ Выберите хотя бы одну группу", show_alert=True)
return
# Добавляем сообщение в выбранные группы
message_id = context.user_data['message_id']
async with AsyncSessionLocal() as session:
mg_repo = MessageGroupRepository(session)
for group_id in selected:
await mg_repo.add_message_to_group(message_id, group_id)
text = f"""✅ <b>Сообщение готово!</b>
Название: {context.user_data['message_title']}
Групп выбрано: {len(selected)}
Теперь вы можете отправить сообщение нажав кнопку "Отправить" в списке сообщений."""
context.user_data['message_state'] = None
await query.edit_message_text(text, parse_mode='HTML', reply_markup=get_main_keyboard())
return
elif callback_data.startswith("select_group_"):
group_id = int(callback_data.split("_")[2])
selected = context.user_data.get('selected_groups', set())
if group_id in selected:
selected.discard(group_id)
else:
selected.add(group_id)
context.user_data['selected_groups'] = selected
# Обновляем клавиатуру
async with AsyncSessionLocal() as session:
group_repo = GroupRepository(session)
groups = await group_repo.get_all_active_groups()
keyboard = []
for group in groups:
callback = f"select_group_{group.id}"
is_selected = group.id in selected
prefix = "" if is_selected else ""
keyboard.append([InlineKeyboardButton(
f"{prefix} {group.title} (delay: {group.slow_mode_delay}s)",
callback_data=callback
)])
keyboard.append([InlineKeyboardButton("✔️ Готово", callback_data="done_groups")])
keyboard.append([InlineKeyboardButton("❌ Отмена", callback_data=CallbackType.MAIN_MENU.value)])
await query.edit_message_text(
f"Выбрано групп: {len(selected)}",
reply_markup=InlineKeyboardMarkup(keyboard)
)
await query.answer()
return SELECT_GROUPS
elif callback_data == CallbackType.MAIN_MENU:
# Отмена
await query.answer()
await query.edit_message_text(
"❌ Создание сообщения отменено",
reply_markup=get_main_keyboard()
)
return ConversationHandler.END
await query.answer()
return SELECT_GROUPS