Files
TG_autoposter/app/handlers/userbot_manager.py
Andrew K. Choi 57d7c9ace3 Реализована интерактивная авторизация UserBot через бот
- Создан модуль userbot_auth.py для управления авторизацией через Telethon
- Добавлены обработчики для всех этапов авторизации (номер, SMS, 2FA)
- Интегрирована авторизация в меню UserBot
- Добавлена кнопка 🔐 Авторизация в главное меню UserBot
- Полная обработка ошибок и подробные сообщения пользователю
- Сессии сохраняются безопасно в PostgreSQL
- Документация с примерами использования

Этапы авторизации:
1. Пользователь нажимает 🔐 Авторизация в меню UserBot
2. Вводит номер телефона в формате +XX-XXX-XXX-XXXX
3. Получает SMS с кодом подтверждения (5 цифр)
4. При необходимости вводит пароль 2FA
5. Сессия автоматически сохраняется и UserBot готов к работе
2025-12-21 12:23:47 +09:00

484 lines
19 KiB
Python
Raw Permalink 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.

"""
Обработчик управления 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
from app.handlers.userbot_auth import (
auth_menu,
auth_info,
start_phone_input,
handle_phone,
handle_code,
handle_password,
cancel_auth,
AUTH_START,
AUTH_PHONE,
AUTH_CODE,
AUTH_PASSWORD,
)
import logging
import os
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="auth_menu")],
[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
# Экспорт функций авторизации для использования в других модулях
__all__ = [
'userbot_menu',
'userbot_settings',
'userbot_init',
'userbot_collect_groups',
'userbot_collect_members',
'userbot_parse_members',
'cancel_userbot',
'auth_menu',
'auth_info',
'start_phone_input',
'handle_phone',
'handle_code',
'handle_password',
'cancel_auth',
]
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