Add telegram bot

This commit is contained in:
2025-12-16 20:06:16 +07:00
parent 9fd93a185c
commit 412de3bf05
32 changed files with 1721 additions and 3 deletions

1
bot/handlers/__init__.py Normal file
View File

@@ -0,0 +1 @@
# Bot handlers

60
bot/handlers/link.py Normal file
View File

@@ -0,0 +1,60 @@
from aiogram import Router, F
from aiogram.filters import Command
from aiogram.types import Message
from keyboards.main_menu import get_main_menu
from services.api_client import api_client
router = Router()
@router.message(Command("unlink"))
async def cmd_unlink(message: Message):
"""Handle /unlink command to disconnect Telegram account."""
user = await api_client.get_user_by_telegram_id(message.from_user.id)
if not user:
await message.answer(
"Твой аккаунт не привязан к Game Marathon.\n"
"Привяжи его через настройки профиля на сайте.",
reply_markup=get_main_menu()
)
return
result = await api_client.unlink_telegram(message.from_user.id)
if result.get("success"):
await message.answer(
"<b>Аккаунт отвязан</b>\n\n"
"Ты больше не будешь получать уведомления.\n"
"Чтобы привязать аккаунт снова, используй кнопку в настройках профиля на сайте.",
reply_markup=get_main_menu()
)
else:
await message.answer(
"Произошла ошибка при отвязке аккаунта.\n"
"Попробуй позже или обратись к администратору.",
reply_markup=get_main_menu()
)
@router.message(Command("status"))
async def cmd_status(message: Message):
"""Check account link status."""
user = await api_client.get_user_by_telegram_id(message.from_user.id)
if user:
await message.answer(
f"<b>Статус аккаунта</b>\n\n"
f"✅ Аккаунт привязан\n"
f"👤 Никнейм: <b>{user.get('nickname', 'N/A')}</b>\n"
f"🆔 ID: {user.get('id', 'N/A')}",
reply_markup=get_main_menu()
)
else:
await message.answer(
"<b>Статус аккаунта</b>\n\n"
"❌ Аккаунт не привязан\n\n"
"Привяжи его через настройки профиля на сайте.",
reply_markup=get_main_menu()
)

211
bot/handlers/marathons.py Normal file
View File

@@ -0,0 +1,211 @@
from aiogram import Router, F
from aiogram.filters import Command
from aiogram.types import Message, CallbackQuery
from keyboards.main_menu import get_main_menu
from keyboards.inline import get_marathons_keyboard, get_marathon_details_keyboard
from services.api_client import api_client
router = Router()
@router.message(Command("marathons"))
@router.message(F.text == "📊 Мои марафоны")
async def cmd_marathons(message: Message):
"""Show user's marathons."""
user = await api_client.get_user_by_telegram_id(message.from_user.id)
if not user:
await message.answer(
"Сначала привяжи аккаунт через настройки профиля на сайте.",
reply_markup=get_main_menu()
)
return
marathons = await api_client.get_user_marathons(message.from_user.id)
if not marathons:
await message.answer(
"<b>Мои марафоны</b>\n\n"
"У тебя пока нет активных марафонов.\n"
"Присоединись к марафону на сайте!",
reply_markup=get_main_menu()
)
return
text = "<b>📊 Мои марафоны</b>\n\n"
for m in marathons:
status_emoji = {
"preparing": "",
"active": "🎮",
"finished": "🏁"
}.get(m.get("status"), "")
text += f"{status_emoji} <b>{m.get('title')}</b>\n"
text += f" Очки: {m.get('total_points', 0)} | "
text += f"Место: #{m.get('position', '?')}\n\n"
await message.answer(
text,
reply_markup=get_marathons_keyboard(marathons)
)
@router.callback_query(F.data.startswith("marathon:"))
async def marathon_details(callback: CallbackQuery):
"""Show marathon details."""
marathon_id = int(callback.data.split(":")[1])
details = await api_client.get_marathon_details(
marathon_id=marathon_id,
telegram_id=callback.from_user.id
)
if not details:
await callback.answer("Не удалось загрузить данные марафона", show_alert=True)
return
marathon = details.get("marathon", {})
participant = details.get("participant", {})
active_events = details.get("active_events", [])
current_assignment = details.get("current_assignment")
status_text = {
"preparing": "⏳ Подготовка",
"active": "🎮 Активен",
"finished": "🏁 Завершён"
}.get(marathon.get("status"), "")
text = f"<b>{marathon.get('title')}</b>\n"
text += f"Статус: {status_text}\n\n"
text += f"<b>📈 Твоя статистика:</b>\n"
text += f"• Очки: <b>{participant.get('total_points', 0)}</b>\n"
text += f"• Место: <b>#{details.get('position', '?')}</b>\n"
text += f"• Стрик: <b>{participant.get('current_streak', 0)}</b> 🔥\n"
text += f"• Дропов: <b>{participant.get('drop_count', 0)}</b>\n\n"
if active_events:
text += "<b>⚡ Активные события:</b>\n"
for event in active_events:
event_emoji = {
"golden_hour": "🌟",
"jackpot": "🎰",
"double_risk": "",
"common_enemy": "👥",
"swap": "🔄",
"game_choice": "🎲"
}.get(event.get("type"), "📌")
text += f"{event_emoji} {event.get('type', '').replace('_', ' ').title()}\n"
text += "\n"
if current_assignment:
challenge = current_assignment.get("challenge", {})
game = challenge.get("game", {})
text += f"<b>🎯 Текущее задание:</b>\n"
text += f"Игра: {game.get('title', 'N/A')}\n"
text += f"Задание: {challenge.get('title', 'N/A')}\n"
text += f"Сложность: {challenge.get('difficulty', 'N/A')}\n"
text += f"Очки: {challenge.get('points', 0)}\n"
await callback.message.edit_text(
text,
reply_markup=get_marathon_details_keyboard(marathon_id)
)
await callback.answer()
@router.callback_query(F.data == "back_to_marathons")
async def back_to_marathons(callback: CallbackQuery):
"""Go back to marathons list."""
marathons = await api_client.get_user_marathons(callback.from_user.id)
if not marathons:
await callback.message.edit_text(
"<b>Мои марафоны</b>\n\n"
"У тебя пока нет активных марафонов."
)
await callback.answer()
return
text = "<b>📊 Мои марафоны</b>\n\n"
for m in marathons:
status_emoji = {
"preparing": "",
"active": "🎮",
"finished": "🏁"
}.get(m.get("status"), "")
text += f"{status_emoji} <b>{m.get('title')}</b>\n"
text += f" Очки: {m.get('total_points', 0)} | "
text += f"Место: #{m.get('position', '?')}\n\n"
await callback.message.edit_text(
text,
reply_markup=get_marathons_keyboard(marathons)
)
await callback.answer()
@router.message(Command("stats"))
@router.message(F.text == "📈 Статистика")
async def cmd_stats(message: Message):
"""Show user's overall statistics."""
user = await api_client.get_user_by_telegram_id(message.from_user.id)
if not user:
await message.answer(
"Сначала привяжи аккаунт через настройки профиля на сайте.",
reply_markup=get_main_menu()
)
return
stats = await api_client.get_user_stats(message.from_user.id)
if not stats:
await message.answer(
"<b>📈 Статистика</b>\n\n"
"Пока нет данных для отображения.\n"
"Начни участвовать в марафонах!",
reply_markup=get_main_menu()
)
return
text = f"<b>📈 Общая статистика</b>\n\n"
text += f"👤 <b>{user.get('nickname', 'Игрок')}</b>\n\n"
text += f"🏆 Марафонов завершено: <b>{stats.get('marathons_completed', 0)}</b>\n"
text += f"🎮 Марафонов активно: <b>{stats.get('marathons_active', 0)}</b>\n"
text += f"✅ Заданий выполнено: <b>{stats.get('challenges_completed', 0)}</b>\n"
text += f"💰 Всего очков: <b>{stats.get('total_points', 0)}</b>\n"
text += f"🔥 Лучший стрик: <b>{stats.get('best_streak', 0)}</b>\n"
await message.answer(text, reply_markup=get_main_menu())
@router.message(Command("settings"))
@router.message(F.text == "⚙️ Настройки")
async def cmd_settings(message: Message):
"""Show notification settings."""
user = await api_client.get_user_by_telegram_id(message.from_user.id)
if not user:
await message.answer(
"Сначала привяжи аккаунт через настройки профиля на сайте.",
reply_markup=get_main_menu()
)
return
await message.answer(
"<b>⚙️ Настройки</b>\n\n"
"Управление уведомлениями будет доступно в следующем обновлении.\n\n"
"Сейчас ты получаешь все уведомления:\n"
"• 🌟 События (Golden Hour, Jackpot и др.)\n"
"• 🚀 Старт/финиш марафонов\n"
"• ⚠️ Споры по заданиям\n\n"
"Команды:\n"
"/unlink - Отвязать аккаунт\n"
"/status - Проверить привязку",
reply_markup=get_main_menu()
)

120
bot/handlers/start.py Normal file
View File

@@ -0,0 +1,120 @@
import logging
from aiogram import Router, F
from aiogram.filters import CommandStart, Command, CommandObject
from aiogram.types import Message
from keyboards.main_menu import get_main_menu
from services.api_client import api_client
logger = logging.getLogger(__name__)
router = Router()
@router.message(CommandStart())
async def cmd_start(message: Message, command: CommandObject):
"""Handle /start command with or without deep link."""
logger.info(f"[START] ==================== START COMMAND ====================")
logger.info(f"[START] Telegram user: id={message.from_user.id}, username=@{message.from_user.username}")
logger.info(f"[START] Full message text: '{message.text}'")
logger.info(f"[START] Deep link args (command.args): '{command.args}'")
# Check if there's a deep link token (for account linking)
token = command.args
if token:
logger.info(f"[START] -------- TOKEN RECEIVED --------")
logger.info(f"[START] Token: {token}")
logger.info(f"[START] Token length: {len(token)} chars")
logger.info(f"[START] -------- CALLING API --------")
logger.info(f"[START] Sending to /telegram/confirm-link:")
logger.info(f"[START] - token: {token}")
logger.info(f"[START] - telegram_id: {message.from_user.id}")
logger.info(f"[START] - telegram_username: {message.from_user.username}")
result = await api_client.confirm_telegram_link(
token=token,
telegram_id=message.from_user.id,
telegram_username=message.from_user.username
)
logger.info(f"[START] -------- API RESPONSE --------")
logger.info(f"[START] Response: {result}")
logger.info(f"[START] Success: {result.get('success')}")
if result.get("success"):
user_nickname = result.get("nickname", "пользователь")
logger.info(f"[START] ✅ LINK SUCCESS! User '{user_nickname}' linked to telegram_id={message.from_user.id}")
await message.answer(
f"<b>Аккаунт успешно привязан!</b>\n\n"
f"Привет, <b>{user_nickname}</b>!\n\n"
f"Теперь ты будешь получать уведомления о:\n"
f"• Начале и окончании событий (Golden Hour, Jackpot и др.)\n"
f"• Старте и завершении марафонов\n"
f"• Спорах по твоим заданиям\n\n"
f"Используй меню ниже для навигации:",
reply_markup=get_main_menu()
)
return
else:
error = result.get("error", "Неизвестная ошибка")
logger.error(f"[START] ❌ LINK FAILED!")
logger.error(f"[START] Error: {error}")
logger.error(f"[START] Token was: {token}")
await message.answer(
f"<b>Ошибка привязки аккаунта</b>\n\n"
f"{error}\n\n"
f"Попробуй получить новую ссылку на сайте.",
reply_markup=get_main_menu()
)
return
# No token - regular start
logger.info(f"[START] No token, checking if user is already linked...")
user = await api_client.get_user_by_telegram_id(message.from_user.id)
logger.info(f"[START] API response: {user}")
if user:
await message.answer(
f"<b>С возвращением, {user.get('nickname', 'игрок')}!</b>\n\n"
f"Твой аккаунт привязан. Используй меню для навигации:",
reply_markup=get_main_menu()
)
else:
await message.answer(
"<b>Добро пожаловать в Game Marathon Bot!</b>\n\n"
"Этот бот поможет тебе следить за марафонами и "
"получать уведомления о важных событиях.\n\n"
"<b>Для начала работы:</b>\n"
"1. Зайди на сайт в настройки профиля\n"
"2. Нажми кнопку «Привязать Telegram»\n"
"3. Перейди по полученной ссылке\n\n"
"После привязки ты сможешь:\n"
"• Смотреть свои марафоны\n"
"• Получать уведомления о событиях\n"
"• Следить за статистикой",
reply_markup=get_main_menu()
)
@router.message(Command("help"))
@router.message(F.text == "❓ Помощь")
async def cmd_help(message: Message):
"""Handle /help command."""
await message.answer(
"<b>Справка по командам:</b>\n\n"
"/start - Начать работу с ботом\n"
"/marathons - Мои марафоны\n"
"/stats - Моя статистика\n"
"/settings - Настройки уведомлений\n"
"/help - Эта справка\n\n"
"<b>Уведомления:</b>\n"
"Бот присылает уведомления о:\n"
"• 🌟 Golden Hour - очки x1.5\n"
"• 🎰 Jackpot - очки x3\n"
"• ⚡ Double Risk - половина очков, дропы бесплатны\n"
"• 👥 Common Enemy - общий челлендж\n"
"• 🚀 Старт/финиш марафонов\n"
"• ⚠️ Споры по заданиям",
reply_markup=get_main_menu()
)