"""Клавиатуры бота.""" from __future__ import annotations from aiogram.types import ( InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton, ReplyKeyboardMarkup, WebAppInfo, ) from services.tariffs import Tariff def main_menu(is_admin: bool = False, *, webapp_url: str | None = None) -> ReplyKeyboardMarkup: rows: list[list[KeyboardButton]] = [] if webapp_url: rows.append( [ KeyboardButton( text="🚀 Mini App", web_app=WebAppInfo(url=webapp_url), ) ] ) rows.extend( [ [KeyboardButton(text="🛒 Тарифы"), KeyboardButton(text="💰 Баланс")], [KeyboardButton(text="📱 Моя подписка"), KeyboardButton(text="📡 Статус серверов")], [KeyboardButton(text="📥 Скачать клиент"), KeyboardButton(text="❓ FAQ")], [KeyboardButton(text="🎫 Тикеты"), KeyboardButton(text="ℹ️ Помощь")], ] ) if is_admin: rows.append([KeyboardButton(text="🛠 Админка")]) return ReplyKeyboardMarkup( keyboard=rows, resize_keyboard=True, input_field_placeholder="Выберите действие…", ) def faq_root_inline() -> InlineKeyboardMarkup: from services.faq import FAQ_ITEMS rows: list[list[InlineKeyboardButton]] = [] for item in FAQ_ITEMS: rows.append( [ InlineKeyboardButton( text=item.question, callback_data=f"faq:view:{item.id}", ) ] ) rows.append([InlineKeyboardButton(text="🏠 Меню", callback_data="menu:home")]) return InlineKeyboardMarkup(inline_keyboard=rows) def faq_back_inline() -> InlineKeyboardMarkup: return InlineKeyboardMarkup( inline_keyboard=[ [InlineKeyboardButton(text="⬅️ Все вопросы", callback_data="faq:root")], [InlineKeyboardButton(text="🏠 Меню", callback_data="menu:home")], ] ) def clients_root_inline() -> InlineKeyboardMarkup: from services.clients import TOP_CLIENTS rows: list[list[InlineKeyboardButton]] = [] row: list[InlineKeyboardButton] = [] for client in TOP_CLIENTS: prefix = "⭐ " if client.featured else "" row.append( InlineKeyboardButton( text=f"{prefix}{client.name}", callback_data=f"clients:view:{client.id}", ) ) if len(row) == 2: rows.append(row) row = [] if row: rows.append(row) rows.append( [InlineKeyboardButton(text="📚 Все клиенты (docs.rw)", url="https://docs.rw/clients")] ) rows.append([InlineKeyboardButton(text="🏠 Меню", callback_data="menu:home")]) return InlineKeyboardMarkup(inline_keyboard=rows) def clients_links_inline(links: tuple, *, back: str = "clients:root") -> InlineKeyboardMarkup: rows: list[list[InlineKeyboardButton]] = [] for link in links: rows.append([InlineKeyboardButton(text=f"⬇️ {link.title}", url=link.url)]) rows.append([InlineKeyboardButton(text="⬅️ Назад", callback_data=back)]) return InlineKeyboardMarkup(inline_keyboard=rows) def servers_inline() -> InlineKeyboardMarkup: return InlineKeyboardMarkup( inline_keyboard=[ [InlineKeyboardButton(text="🔄 Обновить", callback_data="servers:refresh")], [InlineKeyboardButton(text="🏠 Меню", callback_data="menu:home")], ] ) def balance_inline( *, crypto_enabled: bool = False, heleket_enabled: bool = False, digiseller_enabled: bool = False, lava_enabled: bool = False, anypay_enabled: bool = False, stars_enabled: bool = False, ) -> InlineKeyboardMarkup: rows: list[list[InlineKeyboardButton]] = [] if stars_enabled: rows.append( [ InlineKeyboardButton( text="⭐ Telegram Stars", callback_data="pay:stars", ) ] ) if crypto_enabled or heleket_enabled: rows.append( [InlineKeyboardButton(text="🪙 Пополнить криптой", callback_data="pay:topup")] ) if lava_enabled: rows.append( [ InlineKeyboardButton( text="💳 Lava (карта / СБП)", callback_data="pay:lava", ) ] ) if anypay_enabled: rows.append( [ InlineKeyboardButton( text="💳 AnyPay", callback_data="pay:anypay", ) ] ) if digiseller_enabled: rows.append( [ InlineKeyboardButton( text="💳 Digiseller", callback_data="pay:digiseller", ) ] ) rows.append( [ InlineKeyboardButton( text="🎫 Код пополнения", callback_data="pay:topup_code", ) ] ) rows.append([InlineKeyboardButton(text="🛒 Купить тариф", callback_data="shop:list")]) rows.append([InlineKeyboardButton(text="🏠 Меню", callback_data="menu:home")]) return InlineKeyboardMarkup(inline_keyboard=rows) def digiseller_inline(*, pay_url: str) -> InlineKeyboardMarkup: rows: list[list[InlineKeyboardButton]] = [] if pay_url: rows.append([InlineKeyboardButton(text="💳 Оплатить на Digiseller", url=pay_url)]) rows.append( [ InlineKeyboardButton( text="🔄 Не зачислилось? Ввести код", callback_data="pay:digiseller_code", ) ] ) rows.append([InlineKeyboardButton(text="⬅️ Назад", callback_data="pay:balance")]) return InlineKeyboardMarkup(inline_keyboard=rows) def digiseller_cancel_inline() -> InlineKeyboardMarkup: return InlineKeyboardMarkup( inline_keyboard=[ [InlineKeyboardButton(text="❌ Отмена", callback_data="pay:digiseller_cancel")] ] ) def topup_code_inline(*, pay_url: str = "") -> InlineKeyboardMarkup: rows: list[list[InlineKeyboardButton]] = [] if pay_url: rows.append([InlineKeyboardButton(text="🛒 Купить код", url=pay_url)]) rows.append( [InlineKeyboardButton(text="❌ Отмена", callback_data="pay:topup_code_cancel")] ) return InlineKeyboardMarkup(inline_keyboard=rows) def topup_providers_inline( *, crypto_enabled: bool, heleket_enabled: bool, ) -> InlineKeyboardMarkup: rows: list[list[InlineKeyboardButton]] = [] if crypto_enabled: rows.append( [ InlineKeyboardButton( text="Crypto Bot", callback_data="pay:topup_provider:cryptobot", ) ] ) if heleket_enabled: rows.append( [ InlineKeyboardButton( text="Heleket (резерв)", callback_data="pay:topup_provider:heleket", ) ] ) rows.append([InlineKeyboardButton(text="⬅️ Назад", callback_data="pay:balance")]) return InlineKeyboardMarkup(inline_keyboard=rows) def topup_amounts_inline(provider: str = "cryptobot") -> InlineKeyboardMarkup: amounts = [50, 100, 200, 500, 1000, 2000, 3000] rows: list[list[InlineKeyboardButton]] = [] row: list[InlineKeyboardButton] = [] for amount in amounts: row.append( InlineKeyboardButton( text=f"{amount} ₽", callback_data=f"pay:topup_amount:{provider}:{amount}", ) ) if len(row) == 3: rows.append(row) row = [] if row: rows.append(row) back = "pay:stars" if provider == "stars" else "pay:topup" rows.append([InlineKeyboardButton(text="⬅️ Назад", callback_data=back)]) return InlineKeyboardMarkup(inline_keyboard=rows) def stars_amounts_inline(*, rub_per_star: float) -> InlineKeyboardMarkup: from services.stars import STAR_TOPUP_AMOUNTS_RUB, quote_topup rows: list[list[InlineKeyboardButton]] = [] for amount in STAR_TOPUP_AMOUNTS_RUB: q = quote_topup(amount, rub_per_star=rub_per_star) rows.append( [ InlineKeyboardButton( text=f"{q.rub_label} · {q.stars_label}", callback_data=f"pay:stars_amount:{amount}", ) ] ) rows.append([InlineKeyboardButton(text="⬅️ Назад", callback_data="pay:balance")]) return InlineKeyboardMarkup(inline_keyboard=rows) def tariffs_inline(tariffs: list[Tariff]) -> InlineKeyboardMarkup: rows: list[list[InlineKeyboardButton]] = [] for t in tariffs: rows.append( [ InlineKeyboardButton( text=f"{t.title} · {t.format_price()}", callback_data=f"shop:view:{t.id}", ) ] ) rows.append([InlineKeyboardButton(text="🏠 Меню", callback_data="menu:home")]) return InlineKeyboardMarkup(inline_keyboard=rows) def renew_tariffs_inline(tariffs: list[Tariff]) -> InlineKeyboardMarkup: rows: list[list[InlineKeyboardButton]] = [] for t in tariffs: rows.append( [ InlineKeyboardButton( text=f"➕ {t.title} · {t.format_price()}", callback_data=f"renew:view:{t.id}", ) ] ) rows.append([InlineKeyboardButton(text="⬅️ К подписке", callback_data="sub:mine")]) return InlineKeyboardMarkup(inline_keyboard=rows) def renew_confirm_inline(tariff_id: str) -> InlineKeyboardMarkup: return InlineKeyboardMarkup( inline_keyboard=[ [ InlineKeyboardButton( text="✅ Продлить с баланса", callback_data=f"renew:buy:{tariff_id}", ) ], [InlineKeyboardButton(text="⬅️ Назад", callback_data="renew:list")], ] ) def subscription_inline( *, has_subscription: bool, devices_over: bool = False, ) -> InlineKeyboardMarkup: rows: list[list[InlineKeyboardButton]] = [] if has_subscription: if devices_over: rows.append( [ InlineKeyboardButton( text="⚠️ Оплатить лишние устройства", callback_data="devices:list", ) ] ) rows.append( [ InlineKeyboardButton( text="🔄 Продлить с баланса", callback_data="renew:list", ) ] ) rows.append( [ InlineKeyboardButton( text="📦 Докупить трафик", callback_data="traffic:list", ) ] ) rows.append( [ InlineKeyboardButton( text="📱 Доп. устройства", callback_data="devices:list", ) ] ) rows.append([InlineKeyboardButton(text="🛒 Тарифы", callback_data="shop:list")]) rows.append([InlineKeyboardButton(text="🏠 Меню", callback_data="menu:home")]) return InlineKeyboardMarkup(inline_keyboard=rows) def device_packs_inline(*, excess: int = 0) -> InlineKeyboardMarkup: from services.devices import DEVICE_PACKS, EXTRA_DEVICE_PRICE_RUB rows: list[list[InlineKeyboardButton]] = [] if excess > 0: price = int(excess * EXTRA_DEVICE_PRICE_RUB) rows.append( [ InlineKeyboardButton( text=f"⚠️ Оплатить превышение +{excess} · {price} ₽", callback_data=f"devices:confirm:{excess}", ) ] ) for pack in DEVICE_PACKS: rows.append( [ InlineKeyboardButton( text=f"{pack.title} · {pack.format_price()}", callback_data=f"devices:buy:{pack.slots}", ) ] ) rows.append([InlineKeyboardButton(text="⬅️ К подписке", callback_data="sub:mine")]) return InlineKeyboardMarkup(inline_keyboard=rows) def device_confirm_inline(slots: int) -> InlineKeyboardMarkup: return InlineKeyboardMarkup( inline_keyboard=[ [ InlineKeyboardButton( text="✅ Купить с баланса", callback_data=f"devices:confirm:{slots}", ) ], [InlineKeyboardButton(text="⬅️ Назад", callback_data="devices:list")], ] ) def traffic_packs_inline() -> InlineKeyboardMarkup: from services.traffic import TRAFFIC_PACKS rows: list[list[InlineKeyboardButton]] = [] for pack in TRAFFIC_PACKS: rows.append( [ InlineKeyboardButton( text=f"{pack.title} · {pack.format_price()}", callback_data=f"traffic:buy:{pack.packs}", ) ] ) rows.append([InlineKeyboardButton(text="⬅️ К подписке", callback_data="sub:mine")]) return InlineKeyboardMarkup(inline_keyboard=rows) def traffic_confirm_inline(packs: int) -> InlineKeyboardMarkup: return InlineKeyboardMarkup( inline_keyboard=[ [ InlineKeyboardButton( text="✅ Купить с баланса", callback_data=f"traffic:confirm:{packs}", ) ], [InlineKeyboardButton(text="⬅️ Назад", callback_data="traffic:list")], ] ) def tariff_pay_methods_inline( tariff_id: str, *, crypto_enabled: bool, heleket_enabled: bool = False, lava_enabled: bool = False, anypay_enabled: bool = False, ) -> InlineKeyboardMarkup: rows: list[list[InlineKeyboardButton]] = [ [ InlineKeyboardButton( text="💰 С баланса", callback_data=f"shop:buy_balance:{tariff_id}", ) ] ] if crypto_enabled: rows.append( [ InlineKeyboardButton( text="🪙 Crypto Bot", callback_data=f"shop:buy_crypto:{tariff_id}", ) ] ) if heleket_enabled: rows.append( [ InlineKeyboardButton( text="🪙 Heleket", callback_data=f"shop:buy_heleket:{tariff_id}", ) ] ) if lava_enabled: rows.append( [ InlineKeyboardButton( text="💳 Lava (карта / СБП)", callback_data=f"shop:buy_lava:{tariff_id}", ) ] ) if anypay_enabled: rows.append( [ InlineKeyboardButton( text="💳 AnyPay", callback_data=f"shop:buy_anypay:{tariff_id}", ) ] ) rows.append([InlineKeyboardButton(text="⬅️ Назад", callback_data="shop:list")]) return InlineKeyboardMarkup(inline_keyboard=rows) def invoice_inline( invoice_id: int, pay_url: str, *, provider: str = "cryptobot", ) -> InlineKeyboardMarkup: if provider == "anypay": return InlineKeyboardMarkup( inline_keyboard=[ [InlineKeyboardButton(text="💳 Оплатить AnyPay", url=pay_url)], [InlineKeyboardButton(text="🏠 Меню", callback_data="menu:home")], ] ) if provider == "heleket": check_cb = f"pay:check_hk:{invoice_id}" elif provider == "lava": check_cb = f"pay:check_lava:{invoice_id}" else: check_cb = f"pay:check:{invoice_id}" return InlineKeyboardMarkup( inline_keyboard=[ [InlineKeyboardButton(text="🪙 Оплатить", url=pay_url)], [ InlineKeyboardButton( text="✅ Я оплатил — проверить", callback_data=check_cb, ) ], [InlineKeyboardButton(text="🏠 Меню", callback_data="menu:home")], ] ) def admin_inline() -> InlineKeyboardMarkup: return InlineKeyboardMarkup( inline_keyboard=[ [ InlineKeyboardButton(text="📡 Серверы", callback_data="admin:servers"), InlineKeyboardButton(text="💚 Health API", callback_data="admin:health"), ], [ InlineKeyboardButton(text="👥 Пользователи", callback_data="admin:users"), InlineKeyboardButton(text="📊 Статистика", callback_data="admin:stats"), ], [ InlineKeyboardButton( text="🎫 Тикеты", callback_data="admin:tickets", ) ], [ InlineKeyboardButton( text="💰 Пополнить пользователю", callback_data="admin:topup", ) ], [ InlineKeyboardButton(text="🔄 Обновить панель", callback_data="admin:home"), InlineKeyboardButton(text="🏠 В меню", callback_data="menu:home"), ], ] ) def tickets_root_inline(*, has_open: bool = False) -> InlineKeyboardMarkup: rows: list[list[InlineKeyboardButton]] = [ [InlineKeyboardButton(text="✍️ Новый тикет", callback_data="tix:new")], [InlineKeyboardButton(text="📋 Мои тикеты", callback_data="tix:list")], ] if has_open: rows.insert( 1, [InlineKeyboardButton(text="🟢 Открытые", callback_data="tix:list:open")], ) rows.append([InlineKeyboardButton(text="🏠 Меню", callback_data="menu:home")]) return InlineKeyboardMarkup(inline_keyboard=rows) def tickets_list_inline( tickets: list, *, prefix: str = "tix:view", back: str = "tix:root", ) -> InlineKeyboardMarkup: from services.tickets import status_label rows: list[list[InlineKeyboardButton]] = [] for t in tickets[:20]: tid = int(t["id"]) st = status_label(str(t.get("status") or ""), short=True) subj = str(t.get("subject") or "")[:28] rows.append( [ InlineKeyboardButton( text=f"#{tid} · {st} · {subj}", callback_data=f"{prefix}:{tid}", ) ] ) rows.append([InlineKeyboardButton(text="⬅️ Назад", callback_data=back)]) return InlineKeyboardMarkup(inline_keyboard=rows) def ticket_view_inline(ticket_id: int, *, status: str, is_admin: bool = False) -> InlineKeyboardMarkup: rows: list[list[InlineKeyboardButton]] = [] if status != "closed": reply_cb = f"admin:tix:reply:{ticket_id}" if is_admin else f"tix:reply:{ticket_id}" close_cb = f"admin:tix:close:{ticket_id}" if is_admin else f"tix:close:{ticket_id}" rows.append([InlineKeyboardButton(text="💬 Ответить", callback_data=reply_cb)]) rows.append([InlineKeyboardButton(text="🔒 Закрыть", callback_data=close_cb)]) elif is_admin: rows.append( [ InlineKeyboardButton( text="🔓 Открыть снова", callback_data=f"admin:tix:reopen:{ticket_id}", ) ] ) back = "admin:tickets" if is_admin else "tix:list" rows.append([InlineKeyboardButton(text="⬅️ К списку", callback_data=back)]) return InlineKeyboardMarkup(inline_keyboard=rows) def ticket_cancel_inline(*, back: str = "tix:root") -> InlineKeyboardMarkup: return InlineKeyboardMarkup( inline_keyboard=[ [InlineKeyboardButton(text="❌ Отмена", callback_data=back)], ] ) def ticket_notify_inline(ticket_id: int) -> InlineKeyboardMarkup: return InlineKeyboardMarkup( inline_keyboard=[ [ InlineKeyboardButton( text="Открыть тикет", callback_data=f"tix:view:{ticket_id}", ) ] ] ) def admin_ticket_notify_inline(ticket_id: int) -> InlineKeyboardMarkup: return InlineKeyboardMarkup( inline_keyboard=[ [ InlineKeyboardButton( text="Открыть тикет", callback_data=f"admin:tix:view:{ticket_id}", ) ] ] ) def admin_tickets_root_inline(stats: dict | None = None) -> InlineKeyboardMarkup: open_n = int((stats or {}).get("open") or 0) ans_n = int((stats or {}).get("answered") or 0) rows = [ [ InlineKeyboardButton( text=f"🟢 Открытые ({open_n})", callback_data="admin:tickets:open", ) ], [ InlineKeyboardButton( text=f"💬 С ответом ({ans_n})", callback_data="admin:tickets:answered", ) ], [InlineKeyboardButton(text="📋 Все", callback_data="admin:tickets:all")], [InlineKeyboardButton(text="⬅️ Админка", callback_data="admin:home")], ] return InlineKeyboardMarkup(inline_keyboard=rows) def admin_back_inline() -> InlineKeyboardMarkup: return InlineKeyboardMarkup( inline_keyboard=[ [ InlineKeyboardButton(text="🔄 Обновить", callback_data="admin:servers"), InlineKeyboardButton(text="⬅️ Назад", callback_data="admin:home"), ] ] ) def admin_topup_cancel() -> InlineKeyboardMarkup: return InlineKeyboardMarkup( inline_keyboard=[ [InlineKeyboardButton(text="❌ Отмена", callback_data="admin:topup_cancel")] ] ) def admin_topup_amounts(telegram_id: int) -> InlineKeyboardMarkup: amounts = [100, 200, 500, 1000, 2000, 5000] rows: list[list[InlineKeyboardButton]] = [] row: list[InlineKeyboardButton] = [] for amount in amounts: row.append( InlineKeyboardButton( text=f"+{amount} ₽", callback_data=f"admin:topup_do:{telegram_id}:{amount}", ) ) if len(row) == 3: rows.append(row) row = [] if row: rows.append(row) rows.append( [ InlineKeyboardButton( text="✏️ Своя сумма", callback_data=f"admin:topup_custom:{telegram_id}", ) ] ) rows.append([InlineKeyboardButton(text="❌ Отмена", callback_data="admin:topup_cancel")]) return InlineKeyboardMarkup(inline_keyboard=rows)