add heleket

This commit is contained in:
Yuriy Yuriev
2026-05-15 00:12:14 +07:00
parent ae5f7967f2
commit 7d28ba794b
4 changed files with 508 additions and 89 deletions
+340 -83
View File
@@ -26,7 +26,8 @@ from services.browser_service import BrowserService
from services.visit_service import VisitScheduler
from utils.helpers import parse_range, format_range, extract_domain
from utils.telegram import send_message_safe, send_visit_result
from managers.storage import TaskStorage, ChatStorage, BalanceStorage
from managers.storage import TaskStorage, ChatStorage, BalanceStorage, RubleBalanceStorage, PaymentStorage
from services.payment_service import HelketPayment
from auth.manager import AuthManager
from auth.storage import AuthStorage
from auth import ADMIN_PASSWORD
@@ -54,8 +55,12 @@ class BotInterface:
self.chat_storage = chat_storage
self.auth_manager = AuthManager()
self.auth_storage = AuthStorage()
self.balance_storage = BalanceStorage()
self.balance_storage = BalanceStorage() # переходы
self.rub_storage = RubleBalanceStorage() # рубли
self.payment_storage = PaymentStorage()
self.heleket = HelketPayment()
self._user_input_state: Dict[int, dict] = {}
self._payment_state: Dict[int, str] = {} # user_id -> "topup" | "buy_clicks"
self._user_task_state: Dict[int, dict] = {} # шаги создания задачи пользователем
self._admin_balance_state: Dict[int, int] = {} # admin_id -> target_user_id
self._menu_msg: Dict[int, int] = {} # user_id -> inline keyboard message_id
@@ -90,13 +95,11 @@ class BotInterface:
exists = await interface.auth_storage.user_exists(user_id)
if exists:
sent = await message.answer("👋 С возвращением!\n\nВведите пароль для входа:")
sent = await message.answer("С возвращением! Введите пароль:")
interface._waiting_password[user_id] = "login"
else:
sent = await message.answer(
"👋 Добро пожаловать!\n\n"
"Вы здесь впервые. Придумайте пароль для регистрации\n"
"_(минимум 4 символа):_"
"Добро пожаловать!\n\nПридумайте пароль для входа (мин. 4 символа):"
)
interface._waiting_password[user_id] = "register"
interface._menu_msg[user_id] = sent.message_id
@@ -167,10 +170,7 @@ class BotInterface:
async def require_auth(event):
user_id = event.from_user.id
if not interface.auth_manager.is_authenticated(user_id):
await event.answer(
"🔒 Требуется авторизация!\n\n"
"Напишите администратору для получения доступа."
)
await event.answer("Требуется авторизация")
return False
return True
@@ -178,18 +178,10 @@ class BotInterface:
async def require_admin(event):
user_id = event.from_user.id
if not interface.auth_manager.is_authenticated(user_id):
await event.answer(
"🔒 Требуется авторизация!\n\n"
"/register <пароль> — зарегистрироваться\n"
"/login <пароль> — войти"
)
await event.answer("Требуется авторизация")
return False
if not interface.auth_manager.is_admin(user_id):
await event.answer(
"⛔ **Нет доступа!**\n\n"
"Эта функция доступна только администраторам.\n"
"Войдите как администратор."
)
await event.answer("Нет доступа — войдите как администратор")
return False
return True
@@ -278,13 +270,13 @@ class BotInterface:
if not await require_admin(message):
return
await message.answer(
"🚀 **БЫСТРЫЙ СТАРТ**\n\n"
"**Twitch мониторинг:**\n"
"🚀 БЫСТРЫЙ СТАРТ\n\n"
"Twitch мониторинг:\n"
"`<канал> <username> [посещений] [минут] [задержка] [чтение] [домены]`\n\n"
"**Пример:** `streamer user123 5 30 10-30 10-30`\n\n"
"**Посещение URL:**\n"
"Пример: `streamer user123 5 30 10-30 10-30`\n\n"
"Посещение URL:\n"
"`<url> [задержка] [повторов] [чтение]`\n\n"
"**Пример:** `https://example.com 10-30 100 15-30`"
"Пример: `https://example.com 10-30 100 15-30`"
)
# === Управление задачей ===
@@ -449,21 +441,117 @@ class BotInterface:
# === Кабинет пользователя ===
@dp.callback_query(F.data == "user_menu")
async def cb_user_menu(callback: CallbackQuery):
await interface._show_user_menu(callback.message, edit=True)
await interface._show_user_menu(
callback.message, edit=True, user_id=callback.from_user.id
)
await callback.answer()
@dp.callback_query(F.data == "my_balance")
async def cb_my_balance(callback: CallbackQuery):
uid = callback.from_user.id
balance = await interface.balance_storage.get_balance(uid)
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_menu"))
await callback.message.edit_text(
f"💰 **Ваш баланс**\n\n**{balance}** переходов",
reply_markup=builder.as_markup()
await interface._show_user_menu(
callback.message, edit=True, user_id=callback.from_user.id
)
await callback.answer()
# ── Пополнение рублей (крипта → рубли) ──────────────────────────────
@dp.callback_query(F.data == "topup_main")
async def cb_topup_main(callback: CallbackQuery):
uid = callback.from_user.id
existing = await interface.payment_storage.get_by_user(uid)
if existing:
await callback.answer("⚠️ Уже есть незакрытый счёт", show_alert=True)
await interface._show_invoice(callback, existing)
return
interface._payment_state[uid] = "topup"
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="❌ Отмена", callback_data="cancel_pay_input"))
await callback.message.edit_text(
f"💳 ПОПОЛНЕНИЕ БАЛАНСА\n\n"
f"Введите сумму пополнения в рублях:\n"
f"(от {settings.TOPUP_MIN_RUB} до {settings.TOPUP_MAX_RUB} ₽)",
reply_markup=builder.as_markup()
)
interface._menu_msg[uid] = callback.message.message_id
await callback.answer()
@dp.callback_query(F.data == "check_pay")
async def cb_check_payment(callback: CallbackQuery):
uid = callback.from_user.id
payment = await interface.payment_storage.get_by_user(uid)
if not payment:
await callback.answer("❌ Платёж не найден", show_alert=True)
await interface._show_user_menu(callback.message, edit=True, user_id=callback.from_user.id)
return
await callback.answer("⏳ Проверяем...")
status = await interface.heleket.check_status(payment["payment_id"])
if status == "paid":
await interface.payment_storage.delete(payment["payment_id"])
new_rub = await interface.rub_storage.add_balance(uid, payment["rub"])
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="🛒 Купить переходы", callback_data="visit_shop"))
builder.row(InlineKeyboardButton(text="🔙 В кабинет", callback_data="my_balance"))
await callback.message.edit_text(
f"✅ Оплата подтверждена!\n\n"
f"Начислено: +{payment['rub']}\n"
f"Баланс: {new_rub}",
reply_markup=builder.as_markup()
)
elif status == "expired":
await interface.payment_storage.delete(payment["payment_id"])
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="💳 Пополнить снова", callback_data="topup_main"))
builder.row(InlineKeyboardButton(text="🔙 В кабинет", callback_data="my_balance"))
await callback.message.edit_text(
"Счёт истёк. Создайте новый.",
reply_markup=builder.as_markup()
)
else:
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="🔄 Проверить снова", callback_data="check_pay"))
builder.row(InlineKeyboardButton(text="❌ Отменить", callback_data="cancel_pay"))
await callback.message.edit_text(
"⏳ Платёж ещё не поступил\n\nОбычно перевод занимает 1–5 минут.",
reply_markup=builder.as_markup()
)
@dp.callback_query(F.data == "cancel_pay")
async def cb_cancel_payment(callback: CallbackQuery):
uid = callback.from_user.id
payment = await interface.payment_storage.get_by_user(uid)
if payment:
await interface.payment_storage.delete(payment["payment_id"])
await callback.answer("Счёт отменён")
await interface._show_user_menu(callback.message, edit=True, user_id=callback.from_user.id)
# ── Покупка переходов (рубли → переходы) ────────────────────────────
@dp.callback_query(F.data == "visit_shop")
async def cb_visit_shop(callback: CallbackQuery):
uid = callback.from_user.id
rub_bal = await interface.rub_storage.get_balance(uid)
price = settings.CLICK_PRICE_RUB
interface._payment_state[uid] = "buy_clicks"
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="❌ Отмена", callback_data="cancel_pay_input"))
await callback.message.edit_text(
f"🛒 Купить переходы\n\n"
f"Баланс: {rub_bal}\n"
f"Цена: {price:.0f} ₽ за 1 переход (мин. {settings.CLICKS_MIN})\n\n"
f"Введите количество:",
reply_markup=builder.as_markup()
)
interface._menu_msg[uid] = callback.message.message_id
await callback.answer()
@dp.callback_query(F.data == "cancel_pay_input")
async def cb_cancel_pay_input(callback: CallbackQuery):
uid = callback.from_user.id
interface._payment_state.pop(uid, None)
await interface._show_user_menu(callback.message, edit=True, user_id=callback.from_user.id)
@dp.callback_query(F.data == "new_task")
async def cb_new_task(callback: CallbackQuery):
uid = callback.from_user.id
@@ -483,9 +571,9 @@ class BotInterface:
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="❌ Отмена", callback_data="cancel_new_task"))
await callback.message.edit_text(
f"📝 **НОВАЯ ЗАДАЧА**\n\n"
f"💰 Баланс: {balance} переходов\n\n"
"Шаг 1/2: Введите название вашего Twitch канала:",
f"📝 Новая задача\n\n"
f"Переходов: {balance}\n\n"
"Шаг 1/2 — введите название Twitch канала:",
reply_markup=builder.as_markup()
)
interface._menu_msg[uid] = callback.message.message_id
@@ -503,6 +591,7 @@ class BotInterface:
builder.row(InlineKeyboardButton(text="❌ Отмена", callback_data="user_menu"))
await callback.message.edit_text(
"🔑 Введите пароль администратора:",
reply_markup=builder.as_markup()
)
await callback.answer()
@@ -514,13 +603,14 @@ class BotInterface:
await callback.answer("❌ Требуется авторизация", show_alert=True)
return
interface.auth_manager.login(uid, is_admin=False)
await interface._show_user_menu(callback.message)
await interface._show_user_menu(callback.message, user_id=uid)
await callback.answer("👤 Переключились в режим пользователя")
@dp.callback_query(F.data == "cancel_new_task")
async def cb_cancel_new_task(callback: CallbackQuery):
interface._user_task_state.pop(callback.from_user.id, None)
await interface._show_user_menu(callback.message)
uid = callback.from_user.id
interface._user_task_state.pop(uid, None)
await interface._show_user_menu(callback.message, user_id=uid)
await callback.answer("Отменено")
@dp.callback_query(F.data == "my_tasks")
@@ -583,8 +673,8 @@ class BotInterface:
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="❌ Отмена", callback_data="my_tasks"))
await callback.message.edit_text(
f"🌐 **Изменить домены**\n\n"
f"Текущие: `{current}`\n\n"
f"🌐 Изменить домены\n\n"
f"Текущие: {current}\n\n"
"Введите новые домены через запятую:",
reply_markup=builder.as_markup()
)
@@ -651,7 +741,7 @@ class BotInterface:
if mode == "register":
if len(text) < 4:
sent = await message.answer("Пароль слишком короткий (минимум 4 символа). Попробуйте ещё раз:")
sent = await message.answer("Пароль слишком короткий, мин. 4 символа:")
interface._menu_msg[user_id] = sent.message_id
interface._waiting_password[user_id] = "register"
return
@@ -668,10 +758,10 @@ class BotInterface:
elif mode == "login":
user_data = await interface.auth_storage.get_user(user_id)
if not user_data:
await interface._send_temp(message, "Вы не зарегистрированы.\nНапишите администратору.")
await interface._send_temp(message, "Вы не зарегистрированы")
return
if interface.auth_manager.is_locked_out(user_id):
await interface._send_temp(message, "🔒 Слишком много попыток. Попробуйте позже.")
await interface._send_temp(message, "Слишком много попыток, попробуйте позже")
return
if text == ADMIN_PASSWORD:
interface.auth_manager.login(user_id, is_admin=True)
@@ -684,7 +774,7 @@ class BotInterface:
await interface._show_user_menu(message)
else:
interface.auth_manager.record_failed_attempt(user_id)
sent = await message.answer("Неверный пароль. Попробуйте ещё раз:")
sent = await message.answer("Неверный пароль, попробуйте ещё раз:")
interface._menu_msg[user_id] = sent.message_id
interface._waiting_password[user_id] = "login"
@@ -694,7 +784,7 @@ class BotInterface:
await interface._update_commands()
await interface._show_main_menu(message)
else:
sent = await message.answer("Неверный пароль администратора. Попробуйте ещё раз:")
sent = await message.answer("Неверный пароль администратора:")
interface._menu_msg[user_id] = sent.message_id
interface._waiting_password[user_id] = "admin_upgrade"
return
@@ -704,11 +794,13 @@ class BotInterface:
await interface._process_user_task_input(message)
return
# Ввод суммы пополнения или покупки переходов
if user_id in interface._payment_state:
await interface._process_payment_input(message)
return
if not interface.auth_manager.is_authenticated(user_id):
await interface._send_temp(
message,
"🔒 Требуется авторизация!\n\n/register <пароль> — зарегистрироваться\n/login <пароль> — войти"
)
await interface._send_temp(message, "Требуется авторизация")
return
if not interface.auth_manager.is_admin(user_id):
@@ -806,7 +898,7 @@ class BotInterface:
await self.chat_storage.add_streamer(message.chat.id, channel)
domains_str = ", ".join(domains) if domains else "все"
text = (
f"**МОНИТОРИНГ ЗАПУЩЕН**\n\n"
f"Мониторинг запущен\n\n"
f"📺 Канал: `{channel}`\n"
f"👤 Отслеживание: все пользователи\n"
f"🔗 На ссылку: {vl} визитов\n"
@@ -878,7 +970,7 @@ class BotInterface:
if success:
await message.answer(
f"**ПОСЕЩЕНИЕ ЗАПУЩЕНО**\n\n"
f"Посещение запущено\n\n"
f"🌐 `{url}`\n"
f"⏱️ Задержка: {format_range(min_delay, max_delay)}с\n"
f"📖 Чтение: {format_range(min_reading, max_reading)}с\n"
@@ -934,10 +1026,10 @@ class BotInterface:
stats = await self.task_manager.get_stats()
text = (
"👋 **ГЛАВНОЕ МЕНЮ**\n\n"
f"📺 Twitch: **{stats['twitch']}**\n"
f"🌐 Визитов: **{stats['visit']}**\n"
f"⏸️ Пауза: **{stats['paused']}**\n\n"
"👋 Главное меню\n\n"
f"📺 Twitch: {stats['twitch']}\n"
f"🌐 Визитов: {stats['visit']}\n"
f"⏸️ Пауза: {stats['paused']}\n\n"
"Выберите раздел:"
)
@@ -970,21 +1062,21 @@ class BotInterface:
twitch_done = {tid: p for tid, p in completed.items() if p.task_type == "twitch_irc"}
if not twitch_active and not twitch_done:
text = "📺 **СТРИМЕРЫ**\n\nНет задач.\nОтправьте `<канал>` для добавления."
text = "📺 Стримеры\n\nЗадач нет. Отправьте название канала для добавления."
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="🔙 В меню", callback_data="menu_main"))
else:
text = "📺 **СТРИМЕРЫ**\n\n"
text = "📺 Стримеры\n\n"
builder = InlineKeyboardBuilder()
if twitch_active:
text += "**🔄 Активные:**\n"
text += "🔄 Активные:\n"
for tid, p in list(twitch_active.items())[:10]:
emoji = p.get_status_emoji()
domains = ", ".join(p.allowed_domains) if p.allowed_domains else "все"
text += (
f"{emoji} **{p.channel}**\n"
f"{emoji} {p.channel}\n"
f" 🔗{p.links_found} | 📊{p.total_visits} | 🌐{domains}\n"
)
builder.row(InlineKeyboardButton(
@@ -993,13 +1085,13 @@ class BotInterface:
))
if twitch_done:
text += "\n**📁 Завершенные:**\n"
text += "\n📁 Завершенные:\n"
for tid, p in list(twitch_done.items())[:5]:
emoji = p.get_status_emoji()
domains = ", ".join(p.allowed_domains) if p.allowed_domains else "все"
text += (
f"{emoji} **{p.channel}**\n"
f"{emoji} {p.channel}\n"
f" 🔗{p.links_found} | 📊{p.total_visits} | 🌐{domains}\n"
)
builder.row(InlineKeyboardButton(
@@ -1024,11 +1116,11 @@ class BotInterface:
stats = await self.task_manager.get_stats()
proxy_stats = self.proxy_manager.get_stats()
text = (
"📈 **СТАТУС**\n\n"
f"📺 Twitch: **{stats['twitch']}**\n"
f"🌐 Визиты: **{stats['visit']}**\n"
f"⏸️ Пауза: **{stats['paused']}**\n"
f"🔄 Активно: **{stats['active']}**\n\n"
"📈 Статус\n\n"
f"📺 Twitch: {stats['twitch']}\n"
f"🌐 Визиты: {stats['visit']}\n"
f"⏸️ Пауза: {stats['paused']}\n"
f"🔄 Активно: {stats['active']}\n\n"
f"🔌 Прокси: {proxy_stats['total']} всего, {proxy_stats['available']} доступно"
)
builder = InlineKeyboardBuilder()
@@ -1046,7 +1138,7 @@ class BotInterface:
await callback.answer("❌ Задача не найдена")
return
text = f"📋 **УПРАВЛЕНИЕ**\n\n{params.get_stats_text()}"
text = f"📋 Управление\n\n{params.get_stats_text()}"
builder = InlineKeyboardBuilder()
@@ -1250,31 +1342,48 @@ class BotInterface:
# КАБИНЕТ ПОЛЬЗОВАТЕЛЯ
# =========================================================================
async def _show_user_menu(self, message: Message, edit: bool = False):
user_id = message.from_user.id
async def _show_user_menu(self, message: Message, edit: bool = False, user_id: int = None):
if user_id is None:
user_id = message.from_user.id if message.from_user else None
if not user_id:
return
# Сбрасываем все незавершённые состояния при возврате в меню
self._waiting_password.pop(user_id, None)
self._user_task_state.pop(user_id, None)
balance = await self.balance_storage.get_balance(user_id)
self._payment_state.pop(user_id, None)
visits = await self.balance_storage.get_balance(user_id)
rubles = await self.rub_storage.get_balance(user_id)
all_tasks = await self.task_manager.get_all_tasks()
active = sum(
1 for p in all_tasks.values()
if p.user_id == user_id and not p.completed and not p.stopped
)
pending = await self.payment_storage.get_by_user(user_id)
text = (
"👤 **ЛИЧНЫЙ КАБИНЕТ**\n\n"
f"💰 Баланс: **{balance}** переходов\n"
f"📋 Активных задач: **{active}**\n\n"
"Выберите действие:"
"👤 Личный кабинет\n\n"
f"🪙 Рублей: {rubles}\n"
f"🔢 Переходов: {visits}\n"
f"📋 Активных задач: {active}"
)
if pending:
text += "\n\n⏳ Есть незакрытый счёт"
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(text="📝 Новая задача", callback_data="new_task"),
InlineKeyboardButton(text="📋 Мои задачи", callback_data="my_tasks"),
)
builder.row(InlineKeyboardButton(text="💰 Баланс", callback_data="my_balance"))
if pending:
builder.row(InlineKeyboardButton(text="⏳ Проверить оплату", callback_data="check_pay"))
else:
builder.row(
InlineKeyboardButton(text="💳 Пополнить", callback_data="topup_main"),
InlineKeyboardButton(text="🛒 Купить переходы", callback_data="visit_shop"),
)
builder.row(InlineKeyboardButton(text="🔑 Режим администратора", callback_data="enter_admin_mode"))
await self._edit_or_send(message, text, builder.as_markup(), edit)
await self._edit_or_send(message, text, builder.as_markup(), edit, user_id=user_id)
async def _show_user_tasks(
self, message: Message, user_id: int,
@@ -1284,9 +1393,9 @@ class BotInterface:
user_tasks = {tid: p for tid, p in all_tasks.items() if p.user_id == user_id}
if not user_tasks:
text = f"📋 **ЗАДАЧИ** {'пользователя `' + str(user_id) + '`' if for_admin else ''}\n\nЗадач нет."
text = f"📋 Задачи {str(user_id) if for_admin else ''}\n\nЗадач нет."
else:
header = f"📋 **ЗАДАЧИ** `{user_id}`\n\n" if for_admin else "📋 **МОИ ЗАДАЧИ**\n\n"
header = f"📋 Задачи пользователя {user_id}\n\n" if for_admin else "📋 Мои задачи\n\n"
text = header
for tid, p in list(user_tasks.items())[:10]:
domains = ", ".join(p.allowed_domains) if p.allowed_domains else "все"
@@ -1355,7 +1464,7 @@ class BotInterface:
sent = await message.answer(
f"📺 Канал: `{channel}`\n\n"
"Шаг 2/2: Введите домены через запятую:\n"
"_Пример: `twitch.tv, youtube.com`_",
"Пример: twitch.tv, youtube.com",
reply_markup=builder.as_markup()
)
self._menu_msg[user_id] = sent.message_id
@@ -1437,11 +1546,159 @@ class BotInterface:
# ПАНЕЛЬ ПОЛЬЗОВАТЕЛЕЙ (ADMIN)
# =========================================================================
# =========================================================================
# КАБИНЕТ / МАГАЗИН
# =========================================================================
async def _process_payment_input(self, message: Message):
user_id = message.from_user.id
action = self._payment_state.pop(user_id)
await self._safe_delete(message.bot, message.chat.id, message.message_id)
text = message.text.strip() if message.text else ""
prompt_id = self._menu_msg.pop(user_id, None)
async def _edit_prompt(new_text: str, markup=None):
if prompt_id:
try:
await message.bot.edit_message_text(
chat_id=message.chat.id, message_id=prompt_id,
text=new_text, reply_markup=markup
)
return
except Exception:
pass
await message.answer(new_text, reply_markup=markup)
try:
value = int(text)
except (ValueError, TypeError):
await self._send_temp(message, "❌ Введите целое число")
self._payment_state[user_id] = action
if prompt_id:
self._menu_msg[user_id] = prompt_id
return
if action == "topup":
if value < settings.TOPUP_MIN_RUB:
await self._send_temp(message, f"❌ Минимум {settings.TOPUP_MIN_RUB}")
self._payment_state[user_id] = "topup"
if prompt_id:
self._menu_msg[user_id] = prompt_id
return
if value > settings.TOPUP_MAX_RUB:
await self._send_temp(message, f"❌ Максимум {settings.TOPUP_MAX_RUB}")
self._payment_state[user_id] = "topup"
if prompt_id:
self._menu_msg[user_id] = prompt_id
return
order_id = f"{user_id}_topup_{int(datetime.now().timestamp())}"
invoice = await self.heleket.create_invoice(value, order_id)
if not invoice:
await self._send_temp(message, "❌ Ошибка создания счёта. Попробуйте позже.")
return
payment_data = {
"user_id": user_id,
"payment_id": invoice["payment_id"],
"rub": value,
"address": invoice.get("address"),
"url": invoice.get("url"),
"mock": invoice.get("mock", False),
"created_at": datetime.now().isoformat(),
}
await self.payment_storage.save(invoice["payment_id"], payment_data)
builder = InlineKeyboardBuilder()
if invoice.get("url"):
builder.row(InlineKeyboardButton(text="💳 Оплатить через Heleket", url=invoice["url"]))
builder.row(InlineKeyboardButton(text="✅ Я оплатил", callback_data="check_pay"))
builder.row(InlineKeyboardButton(text="❌ Отменить", callback_data="cancel_pay"))
if payment_data["mock"]:
body = "⚠️ Тестовый режим — добавьте HELEKET_API_KEY в .env"
elif invoice.get("url"):
body = "Нажмите кнопку ниже для оплаты"
else:
body = f"Адрес:\n`{invoice['address']}`"
await _edit_prompt(
f"💳 Счёт на оплату\n\n"
f"💰 Сумма: {value}\n\n"
f"{body}\n\n"
f"⏱ Счёт действителен 60 минут",
builder.as_markup()
)
elif action == "buy_clicks":
if value < settings.CLICKS_MIN:
await self._send_temp(message, f"❌ Минимум {settings.CLICKS_MIN} переходов")
self._payment_state[user_id] = "buy_clicks"
if prompt_id:
self._menu_msg[user_id] = prompt_id
return
cost = int(value * settings.CLICK_PRICE_RUB)
rub_bal = await self.rub_storage.get_balance(user_id)
if rub_bal < cost:
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="💳 Пополнить рубли", callback_data="topup_main"))
builder.row(InlineKeyboardButton(text="🔙 В кабинет", callback_data="my_balance"))
await _edit_prompt(
f"Недостаточно рублей\n\nНужно: {cost} ₽, у вас: {rub_bal}",
builder.as_markup()
)
return
new_rub = await self.rub_storage.deduct(user_id, cost)
new_visits = await self.balance_storage.add_balance(user_id, value)
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="🛒 Купить ещё", callback_data="visit_shop"))
builder.row(InlineKeyboardButton(text="🔙 В кабинет", callback_data="my_balance"))
await _edit_prompt(
f"✅ Переходы начислены\n\n"
f"+{value} переходов за {cost}\n"
f"Остаток: {new_rub} ₽ | Переходов: {new_visits}",
builder.as_markup()
)
async def _show_invoice(self, callback: CallbackQuery, payment: dict):
"""Отображает счёт на оплату."""
builder = InlineKeyboardBuilder()
if payment.get("url"):
builder.row(InlineKeyboardButton(text="💳 Оплатить через Heleket", url=payment["url"]))
builder.row(InlineKeyboardButton(text="✅ Я оплатил", callback_data="check_pay"))
builder.row(InlineKeyboardButton(text="❌ Отменить", callback_data="cancel_pay"))
if payment.get("mock"):
body = "⚠️ Тестовый режим — добавьте HELEKET_API_KEY и HELEKET_SHOP_ID в .env"
elif payment.get("url"):
body = f"Нажмите кнопку ниже для оплаты через Heleket"
else:
body = f"Адрес для оплаты:\n`{payment['address']}`"
await callback.message.edit_text(
f"💳 Счёт на оплату\n\n"
f"💰 Сумма: {payment['rub']}\n\n"
f"{body}\n\n"
f"⏱ Счёт действителен 60 минут",
reply_markup=builder.as_markup()
)
# =========================================================================
# ПАНЕЛЬ ПОЛЬЗОВАТЕЛЕЙ (ADMIN)
# =========================================================================
async def _show_users_list(self, message: Message, edit: bool = False):
balances = await self.balance_storage.get_all()
all_tasks = await self.task_manager.get_all_tasks()
text = "👥 **ПОЛЬЗОВАТЕЛИ**\n\n"
text = "👥 Пользователи\n\n"
builder = InlineKeyboardBuilder()
if not balances:
@@ -1469,8 +1726,8 @@ class BotInterface:
active = sum(1 for p in user_tasks.values() if not p.completed and not p.stopped)
text = (
f"👤 **Пользователь** `{target_user_id}`\n\n"
f"💰 Баланс: **{balance}** переходов\n"
f"👤 Пользователь {target_user_id}\n\n"
f"🔢 Переходов: {balance}\n"
f"📋 Задач: {len(user_tasks)} (активных: {active})\n\n"
"Пополнить баланс:"
)