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
450 lines
18 KiB
Python
450 lines
18 KiB
Python
"""
|
||
Обработчик управления UserBot для сбора групп и участников
|
||
"""
|
||
from telegram import Update, InlineKeyboardMarkup, InlineKeyboardButton
|
||
from telegram.ext import ContextTypes, ConversationHandler
|
||
from app.userbot.parser import userbot_parser
|
||
from app.database import AsyncSessionLocal
|
||
from app.database.repository import GroupRepository
|
||
from app.database.member_repository import GroupMemberRepository
|
||
from app.utils.keyboards import CallbackType
|
||
import logging
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
# Состояния для ConversationHandler
|
||
USERBOT_MENU = 1
|
||
USERBOT_SETTINGS = 2
|
||
USERBOT_COLLECTING_GROUPS = 3
|
||
USERBOT_SELECT_GROUP = 4
|
||
USERBOT_COLLECTING_MEMBERS = 5
|
||
|
||
|
||
async def userbot_menu(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
||
"""Меню управления UserBot"""
|
||
try:
|
||
query = update.callback_query
|
||
if query:
|
||
await query.answer()
|
||
|
||
text = """🤖 <b>UserBot - Управление парсингом</b>
|
||
|
||
Что вы хотите сделать?
|
||
|
||
<i>UserBot собирает информацию о группах и их участниках от имени пользователя</i>"""
|
||
|
||
keyboard = [
|
||
[InlineKeyboardButton("⚙️ Настройки", callback_data="userbot_settings")],
|
||
[InlineKeyboardButton("📥 Собрать группы", callback_data="userbot_collect_groups")],
|
||
[InlineKeyboardButton("👥 Собрать участников", callback_data="userbot_collect_members")],
|
||
[InlineKeyboardButton("⬅️ Назад в меню", callback_data=CallbackType.MAIN_MENU.value)],
|
||
]
|
||
|
||
if query:
|
||
await query.edit_message_text(
|
||
text,
|
||
parse_mode='HTML',
|
||
reply_markup=InlineKeyboardMarkup(keyboard)
|
||
)
|
||
else:
|
||
await update.message.reply_text(
|
||
text,
|
||
parse_mode='HTML',
|
||
reply_markup=InlineKeyboardMarkup(keyboard)
|
||
)
|
||
|
||
return USERBOT_MENU
|
||
except Exception as e:
|
||
logger.error(f"❌ Ошибка в userbot_menu: {e}", exc_info=True)
|
||
return ConversationHandler.END
|
||
|
||
|
||
async def userbot_settings(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
||
"""Настройки UserBot"""
|
||
try:
|
||
query = update.callback_query
|
||
await query.answer()
|
||
|
||
# Проверяем статус UserBot
|
||
is_initialized = userbot_parser.is_initialized
|
||
|
||
status = "✅ Инициализирован" if is_initialized else "❌ Не инициализирован"
|
||
|
||
text = f"""⚙️ <b>Настройки UserBot</b>
|
||
|
||
<b>Статус:</b> {status}
|
||
|
||
<b>Возможности:</b>
|
||
• Собирать информацию о группах
|
||
• Собирать списки участников
|
||
• Сохранять данные в БД
|
||
• Работать в фоне через Celery
|
||
|
||
Нажмите кнопку для продолжения"""
|
||
|
||
keyboard = [
|
||
[InlineKeyboardButton("🔄 Инициализировать", callback_data="userbot_init")],
|
||
[InlineKeyboardButton("⬅️ Назад", callback_data="userbot_menu")],
|
||
]
|
||
|
||
await query.edit_message_text(
|
||
text,
|
||
parse_mode='HTML',
|
||
reply_markup=InlineKeyboardMarkup(keyboard)
|
||
)
|
||
|
||
return USERBOT_SETTINGS
|
||
except Exception as e:
|
||
logger.error(f"❌ Ошибка в userbot_settings: {e}", exc_info=True)
|
||
return ConversationHandler.END
|
||
|
||
|
||
async def userbot_init(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
||
"""Инициализация UserBot"""
|
||
try:
|
||
query = update.callback_query
|
||
await query.answer()
|
||
|
||
await query.edit_message_text(
|
||
"⏳ Инициализирую UserBot...",
|
||
parse_mode='HTML'
|
||
)
|
||
|
||
# Инициализируем UserBot
|
||
success = await userbot_parser.initialize()
|
||
|
||
if success:
|
||
text = """✅ <b>UserBot успешно инициализирован!</b>
|
||
|
||
Теперь вы можете:
|
||
• Собирать информацию о группах
|
||
• Собирать списки участников
|
||
• Синхронизировать данные в БД
|
||
|
||
Перейдите в меню для продолжения."""
|
||
keyboard = [[InlineKeyboardButton("🔄 Меню", callback_data="userbot_menu")]]
|
||
else:
|
||
text = """❌ <b>Ошибка инициализации UserBot</b>
|
||
|
||
Убедитесь, что:
|
||
• Переменные окружения установлены
|
||
• TELETHON_API_ID и TELETHON_API_HASH верные
|
||
• Сессия сохранена в sessions/userbot_session.session
|
||
|
||
Попробуйте позже."""
|
||
keyboard = [[InlineKeyboardButton("⬅️ Назад", callback_data="userbot_settings")]]
|
||
|
||
await query.edit_message_text(
|
||
text,
|
||
parse_mode='HTML',
|
||
reply_markup=InlineKeyboardMarkup(keyboard)
|
||
)
|
||
|
||
return USERBOT_SETTINGS
|
||
except Exception as e:
|
||
logger.error(f"❌ Ошибка инициализации UserBot: {e}", exc_info=True)
|
||
|
||
text = f"""❌ <b>Ошибка при инициализации:</b>
|
||
|
||
<code>{str(e)[:200]}</code>
|
||
|
||
Проверьте логи бота."""
|
||
|
||
keyboard = [[InlineKeyboardButton("⬅️ Назад", callback_data="userbot_settings")]]
|
||
|
||
query = update.callback_query
|
||
await query.edit_message_text(
|
||
text,
|
||
parse_mode='HTML',
|
||
reply_markup=InlineKeyboardMarkup(keyboard)
|
||
)
|
||
|
||
return USERBOT_SETTINGS
|
||
|
||
|
||
async def userbot_collect_groups(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
||
"""Сбор групп от пользователя"""
|
||
try:
|
||
query = update.callback_query
|
||
await query.answer()
|
||
|
||
if not userbot_parser.is_initialized:
|
||
text = "❌ UserBot не инициализирован!\n\nПерейдите в настройки для инициализации."
|
||
keyboard = [[InlineKeyboardButton("⚙️ Настройки", callback_data="userbot_settings")]]
|
||
|
||
await query.edit_message_text(
|
||
text,
|
||
parse_mode='HTML',
|
||
reply_markup=InlineKeyboardMarkup(keyboard)
|
||
)
|
||
return USERBOT_MENU
|
||
|
||
await query.edit_message_text(
|
||
"⏳ <b>Собираю информацию о группах...</b>\n\nЭто может занять некоторое время...",
|
||
parse_mode='HTML'
|
||
)
|
||
|
||
# Собираем группы
|
||
logger.info(f"📥 Начало сбора групп для пользователя {update.effective_user.id}")
|
||
groups = await userbot_parser.parse_groups_user_in()
|
||
|
||
if not groups:
|
||
text = "❌ <b>Не найдено групп</b>\n\nУбедитесь, что вы состоите в группах."
|
||
keyboard = [[InlineKeyboardButton("🔄 Попробовать снова", callback_data="userbot_collect_groups")],
|
||
[InlineKeyboardButton("⬅️ Назад", callback_data="userbot_menu")]]
|
||
else:
|
||
# Сохраняем группы в контексте
|
||
context.user_data['available_groups'] = groups
|
||
|
||
text = f"""✅ <b>Найдено групп: {len(groups)}</b>
|
||
|
||
<b>Список групп:</b>"""
|
||
|
||
for i, group in enumerate(groups, 1):
|
||
title = group.get('title', 'Неизвестная группа')
|
||
chat_id = group.get('chat_id', 'Unknown')
|
||
members = group.get('members_count', 0)
|
||
text += f"\n{i}. {title}\n 👥 Участников: {members}"
|
||
|
||
text += "\n\n💾 Группы сохранены в базе данных!"
|
||
|
||
# Сохраняем группы в БД
|
||
async with AsyncSessionLocal() as session:
|
||
repo = GroupRepository(session)
|
||
for group in groups:
|
||
await repo.add_or_update_group({
|
||
'chat_id': group.get('chat_id'),
|
||
'title': group.get('title'),
|
||
'description': group.get('description', ''),
|
||
'members_count': group.get('members_count', 0),
|
||
'is_active': True
|
||
})
|
||
|
||
logger.info(f"✅ Сохранено {len(groups)} групп")
|
||
|
||
keyboard = [[InlineKeyboardButton("⬅️ Назад", callback_data="userbot_menu")]]
|
||
|
||
await query.edit_message_text(
|
||
text,
|
||
parse_mode='HTML',
|
||
reply_markup=InlineKeyboardMarkup(keyboard)
|
||
)
|
||
|
||
return USERBOT_COLLECTING_GROUPS
|
||
except Exception as e:
|
||
logger.error(f"❌ Ошибка при сборе групп: {e}", exc_info=True)
|
||
|
||
text = f"""❌ <b>Ошибка при сборе групп:</b>
|
||
|
||
<code>{str(e)[:200]}</code>"""
|
||
|
||
keyboard = [[InlineKeyboardButton("🔄 Попробовать снова", callback_data="userbot_collect_groups")],
|
||
[InlineKeyboardButton("⬅️ Назад", callback_data="userbot_menu")]]
|
||
|
||
query = update.callback_query
|
||
await query.edit_message_text(
|
||
text,
|
||
parse_mode='HTML',
|
||
reply_markup=InlineKeyboardMarkup(keyboard)
|
||
)
|
||
|
||
return USERBOT_COLLECTING_GROUPS
|
||
|
||
|
||
async def userbot_collect_members(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
||
"""Выбор группы для сбора участников"""
|
||
try:
|
||
query = update.callback_query
|
||
await query.answer()
|
||
|
||
if not userbot_parser.is_initialized:
|
||
text = "❌ UserBot не инициализирован!\n\nПерейдите в настройки для инициализации."
|
||
keyboard = [[InlineKeyboardButton("⚙️ Настройки", callback_data="userbot_settings")]]
|
||
|
||
await query.edit_message_text(
|
||
text,
|
||
parse_mode='HTML',
|
||
reply_markup=InlineKeyboardMarkup(keyboard)
|
||
)
|
||
return USERBOT_MENU
|
||
|
||
# Получаем группы из БД
|
||
async with AsyncSessionLocal() as session:
|
||
repo = GroupRepository(session)
|
||
groups = await repo.get_active_groups()
|
||
|
||
if not groups:
|
||
text = "❌ <b>Не найдено активных групп</b>\n\nСначала соберите информацию о группах."
|
||
keyboard = [[InlineKeyboardButton("📥 Собрать группы", callback_data="userbot_collect_groups")],
|
||
[InlineKeyboardButton("⬅️ Назад", callback_data="userbot_menu")]]
|
||
|
||
await query.edit_message_text(
|
||
text,
|
||
parse_mode='HTML',
|
||
reply_markup=InlineKeyboardMarkup(keyboard)
|
||
)
|
||
return USERBOT_SELECT_GROUP
|
||
|
||
text = """👥 <b>Выберите группу для сбора участников</b>
|
||
|
||
Нажмите на группу для сбора списка участников:"""
|
||
|
||
keyboard = []
|
||
for group in groups:
|
||
title = group.title if hasattr(group, 'title') else group.get('title', 'Unknown')
|
||
group_id = group.id if hasattr(group, 'id') else group.get('id', 0)
|
||
callback_data = f"userbot_members_{group_id}"
|
||
keyboard.append([InlineKeyboardButton(f"👥 {title}", callback_data=callback_data)])
|
||
|
||
keyboard.append([InlineKeyboardButton("⬅️ Назад", callback_data="userbot_menu")])
|
||
|
||
await query.edit_message_text(
|
||
text,
|
||
parse_mode='HTML',
|
||
reply_markup=InlineKeyboardMarkup(keyboard)
|
||
)
|
||
|
||
return USERBOT_SELECT_GROUP
|
||
except Exception as e:
|
||
logger.error(f"❌ Ошибка при выборе группы: {e}", exc_info=True)
|
||
|
||
text = f"""❌ <b>Ошибка:</b>
|
||
|
||
<code>{str(e)[:200]}</code>"""
|
||
|
||
keyboard = [[InlineKeyboardButton("⬅️ Назад", callback_data="userbot_menu")]]
|
||
|
||
query = update.callback_query
|
||
await query.edit_message_text(
|
||
text,
|
||
parse_mode='HTML',
|
||
reply_markup=InlineKeyboardMarkup(keyboard)
|
||
)
|
||
|
||
return USERBOT_SELECT_GROUP
|
||
|
||
|
||
async def userbot_parse_members(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
||
"""Сбор участников для выбранной группы"""
|
||
try:
|
||
query = update.callback_query
|
||
await query.answer()
|
||
|
||
# Извлекаем group_id из callback_data
|
||
group_id = int(query.data.replace("userbot_members_", ""))
|
||
|
||
await query.edit_message_text(
|
||
f"⏳ <b>Собираю участников группы...</b>\n\ngroup_id: {group_id}\n\nЭто может занять некоторое время...",
|
||
parse_mode='HTML'
|
||
)
|
||
|
||
# Получаем информацию о группе из БД
|
||
async with AsyncSessionLocal() as session:
|
||
repo = GroupRepository(session)
|
||
group = await repo.get_group_by_id(group_id)
|
||
|
||
if not group:
|
||
text = "❌ Группа не найдена в базе данных."
|
||
keyboard = [[InlineKeyboardButton("⬅️ Назад", callback_data="userbot_collect_members")]]
|
||
|
||
await query.edit_message_text(
|
||
text,
|
||
parse_mode='HTML',
|
||
reply_markup=InlineKeyboardMarkup(keyboard)
|
||
)
|
||
return USERBOT_COLLECTING_MEMBERS
|
||
|
||
chat_id = group.chat_id if hasattr(group, 'chat_id') else group.get('chat_id')
|
||
title = group.title if hasattr(group, 'title') else group.get('title', 'Unknown')
|
||
|
||
logger.info(f"👥 Начало сбора участников для группы {title} (chat_id: {chat_id})")
|
||
|
||
# Собираем участников
|
||
members = await userbot_parser.parse_group_members(chat_id=chat_id, limit=10000)
|
||
|
||
if not members:
|
||
text = f"❌ <b>Не удалось собрать участников группы:</b>\n{title}"
|
||
keyboard = [[InlineKeyboardButton("⬅️ Назад", callback_data="userbot_collect_members")]]
|
||
else:
|
||
# Сохраняем участников в БД
|
||
async with AsyncSessionLocal() as session:
|
||
member_repo = GroupMemberRepository(session)
|
||
for member in members:
|
||
await member_repo.add_or_update_member({
|
||
'group_id': group_id,
|
||
'user_id': member.get('user_id'),
|
||
'username': member.get('username'),
|
||
'first_name': member.get('first_name'),
|
||
'last_name': member.get('last_name'),
|
||
'is_bot': member.get('is_bot', False),
|
||
'is_admin': member.get('is_admin', False),
|
||
'is_owner': member.get('is_owner', False),
|
||
})
|
||
|
||
# Статистика
|
||
total = len(members)
|
||
bots = len([m for m in members if m.get('is_bot')])
|
||
admins = len([m for m in members if m.get('is_admin')])
|
||
owners = len([m for m in members if m.get('is_owner')])
|
||
|
||
text = f"""✅ <b>Участники собраны!</b>
|
||
|
||
<b>Группа:</b> {title}
|
||
|
||
<b>Статистика:</b>
|
||
• 👥 Всего участников: {total}
|
||
• 🤖 Ботов: {bots}
|
||
• 👑 Администраторов: {admins}
|
||
• 🔑 Владельцев: {owners}
|
||
|
||
💾 Данные сохранены в базе данных!"""
|
||
|
||
logger.info(f"✅ Сохранено {total} участников группы {title}")
|
||
|
||
keyboard = [[InlineKeyboardButton("⬅️ Назад", callback_data="userbot_collect_members")]]
|
||
|
||
await query.edit_message_text(
|
||
text,
|
||
parse_mode='HTML',
|
||
reply_markup=InlineKeyboardMarkup(keyboard)
|
||
)
|
||
|
||
return USERBOT_COLLECTING_MEMBERS
|
||
except Exception as e:
|
||
logger.error(f"❌ Ошибка при сборе участников: {e}", exc_info=True)
|
||
|
||
text = f"""❌ <b>Ошибка при сборе участников:</b>
|
||
|
||
<code>{str(e)[:200]}</code>
|
||
|
||
Это может быть вызвано:
|
||
• FloodWait от Telegram
|
||
• Недостатком прав доступа
|
||
• Проблемой с сессией UserBot"""
|
||
|
||
keyboard = [[InlineKeyboardButton("⬅️ Назад", callback_data="userbot_collect_members")],
|
||
[InlineKeyboardButton("🏠 Меню", callback_data="userbot_menu")]]
|
||
|
||
query = update.callback_query
|
||
await query.edit_message_text(
|
||
text,
|
||
parse_mode='HTML',
|
||
reply_markup=InlineKeyboardMarkup(keyboard)
|
||
)
|
||
|
||
return USERBOT_COLLECTING_MEMBERS
|
||
|
||
|
||
async def cancel_userbot(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
||
"""Отмена диалога"""
|
||
query = update.callback_query
|
||
if query:
|
||
await query.answer()
|
||
keyboard = [[InlineKeyboardButton("🏠 Главное меню", callback_data=CallbackType.MAIN_MENU.value)]]
|
||
await query.edit_message_text(
|
||
"❌ Диалог отменен",
|
||
parse_mode='HTML',
|
||
reply_markup=InlineKeyboardMarkup(keyboard)
|
||
)
|
||
return ConversationHandler.END
|