2025-12-16 20:06:16 +07:00
|
|
|
|
from aiogram import Router, F
|
|
|
|
|
|
from aiogram.filters import Command
|
|
|
|
|
|
from aiogram.types import Message, CallbackQuery
|
|
|
|
|
|
|
|
|
|
|
|
from keyboards.main_menu import get_main_menu
|
2026-01-04 02:47:38 +07:00
|
|
|
|
from keyboards.inline import get_marathons_keyboard, get_marathon_details_keyboard, get_settings_keyboard
|
2025-12-16 20:06:16 +07:00
|
|
|
|
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
|
|
|
|
|
|
|
2026-01-04 02:47:38 +07:00
|
|
|
|
# Get current notification settings
|
|
|
|
|
|
settings = await api_client.get_notification_settings(message.from_user.id)
|
|
|
|
|
|
if not settings:
|
|
|
|
|
|
settings = {"notify_events": True, "notify_disputes": True, "notify_moderation": True}
|
|
|
|
|
|
|
2025-12-16 20:06:16 +07:00
|
|
|
|
await message.answer(
|
2026-01-04 02:47:38 +07:00
|
|
|
|
"<b>⚙️ Настройки уведомлений</b>\n\n"
|
|
|
|
|
|
"Нажми на категорию, чтобы включить/выключить:\n\n"
|
|
|
|
|
|
"✅ — уведомления включены\n"
|
|
|
|
|
|
"❌ — уведомления выключены\n\n"
|
|
|
|
|
|
"<i>Уведомления о старте/финише марафонов и коды безопасности нельзя отключить.</i>",
|
|
|
|
|
|
reply_markup=get_settings_keyboard(settings)
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.callback_query(F.data.startswith("toggle:"))
|
|
|
|
|
|
async def toggle_notification(callback: CallbackQuery):
|
|
|
|
|
|
"""Toggle notification setting."""
|
|
|
|
|
|
setting_name = callback.data.split(":")[1]
|
|
|
|
|
|
|
|
|
|
|
|
# Get current settings
|
|
|
|
|
|
current_settings = await api_client.get_notification_settings(callback.from_user.id)
|
|
|
|
|
|
if not current_settings:
|
|
|
|
|
|
await callback.answer("Не удалось загрузить настройки", show_alert=True)
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# Toggle the setting
|
|
|
|
|
|
current_value = current_settings.get(setting_name, True)
|
|
|
|
|
|
new_value = not current_value
|
|
|
|
|
|
|
|
|
|
|
|
# Update on backend
|
|
|
|
|
|
result = await api_client.update_notification_settings(
|
|
|
|
|
|
callback.from_user.id,
|
|
|
|
|
|
{setting_name: new_value}
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if not result or result.get("error"):
|
|
|
|
|
|
await callback.answer("Не удалось сохранить настройки", show_alert=True)
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# Update keyboard with new values
|
|
|
|
|
|
await callback.message.edit_reply_markup(
|
|
|
|
|
|
reply_markup=get_settings_keyboard(result)
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
status = "включены" if new_value else "выключены"
|
|
|
|
|
|
setting_names = {
|
|
|
|
|
|
"notify_events": "События",
|
|
|
|
|
|
"notify_disputes": "Споры",
|
|
|
|
|
|
"notify_moderation": "Модерация"
|
|
|
|
|
|
}
|
|
|
|
|
|
await callback.answer(f"{setting_names.get(setting_name, setting_name)}: {status}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.callback_query(F.data == "back_to_menu")
|
|
|
|
|
|
async def back_to_menu(callback: CallbackQuery):
|
|
|
|
|
|
"""Go back to main menu from settings."""
|
|
|
|
|
|
await callback.message.delete()
|
|
|
|
|
|
await callback.message.answer(
|
|
|
|
|
|
"Главное меню",
|
2025-12-16 20:06:16 +07:00
|
|
|
|
reply_markup=get_main_menu()
|
|
|
|
|
|
)
|
2026-01-04 02:47:38 +07:00
|
|
|
|
await callback.answer()
|