Roblox API и команды
बनाया गया: 29 जनवरी 2025
बनाया गया: 29 जनवरी 2025
Альтернативно можно смотреть новые Roblox-эндпоинты, но многие требуют авторизации или куки. сделай это и добавь логирование запросов, добавь команду /post "сообщение" (только для админа) которая бы отправляло это сообщение всем кто пользуется ботом: import asyncio
import time
import urllib.parse
import re
import aiohttp
from pyrogram import Client, filters, idle, enums
from pyrogram.types import (
Message, InlineKeyboardButton, InlineKeyboardMarkup,
ReplyKeyboardMarkup, ReplyKeyboardRemove, CallbackQuery
)
import aiosqlite
def parse_gamepass_id(link: str) -> int:
"""
Пробуем вытащить gamepass ID из ссылки вида:
https://www.roblox.com/game-pass/123456?someparams=...
или просто https://www.roblox.com/game-pass/123456
Вернём -1, если ID найти не смогли.
"""
match = re.search(r'game-pass/(\d+)', link)
if match:
return int(match.group(1))
return -1
async def get_gamepass_info(gamepass_id: int) -> dict:
"""
Делаем запрос к старому API roblox.com, возвращаем dict со всей информацией
или None, если не удалось получить данные.
textПример ответа (возможно, структура поменялась): { "Name": "My pass", "Description": "desc", "PriceInRobux": 15, "Created": "2022-01-01T00:00:00Z", "Updated": "2022-01-02T00:00:00Z", "Sales": 0 } """ url = f"https://api.roblox.com/marketplace/game-pass-product-info?gamePassId={gamepass_id}" async with aiohttp.ClientSession() as session: try: async with session.get(url) as resp: if resp.status == 200: data = await resp.json() return data else: return None except: return None
async def main():
# ======================
# НАСТРОЙКИ БОТА
# ======================
API_ID = '24142370'
API_HASH = '5499eef960820d34a871c67024d89819'
BOT_TOKEN = "7380283669:AAH2Do7p-bxOjnVn4c3Y2tnHh4DdXEw4egw"
text# Канал, где проверяем подписку (в формате -100xxxxxxxxxx) MAIN_CHANNEL_ID = -1002380592722 BOT_USERNAME = "RobuxTapBot" START_ENERGY = 12 ENERGY_REFILL_HOURS = 14 # Минимальный вывод (только для Robux) MIN_WITHDRAW_BALANCE = 4 # Курс конверсии Robux -> TTD GEMS (1 Robux = 70 GEMS) ROBUX_TO_TTD_RATE = 70 # Предположим, ID админа, куда будем слать уведомления ADMIN_CHAT_ID = 8085926304 # Каждые 4 часа бот напоминает, если есть энергия и пользователь не тапал REMINDER_INTERVAL_HOURS = 4 # Главное меню (Reply-кнопки) main_menu = ReplyKeyboardMarkup( [ ["ℹ Мой Профиль"], ["💸 Заработать Робуксы", "🗣 Друзья"], ["🦈 Вывести", "🌴 Пруфы"], ["📊 Статистика", "❓ FAQ"] ], resize_keyboard=True ) # Инлайн-кнопка «Пруфы» proofs_inline = InlineKeyboardMarkup( [ [ InlineKeyboardButton( "Канал с пруфами и отзывами", url="https://t.me/proofs_vivods" ) ] ] ) # Список команд/кнопок в меню (используем для сброса состояний) MENU_COMMANDS = [ "ℹ Мой Профиль", "💸 Заработать Робуксы", "🗣 Друзья", "🦈 Вывести", "🌴 Пруфы", "📊 Статистика", "❓ FAQ", "/start" ] # Создаём объект бота app = Client("robux_tap_bot", api_id=API_ID, api_hash=API_HASH, bot_token=BOT_TOKEN) # ========================= # Вспомогательные функции # ========================= async def init_db(): """Создание таблиц (если не существуют) + добавление недостающих колонок.""" async with aiosqlite.connect("database.db") as db: # Таблица пользователей await db.execute(""" CREATE TABLE IF NOT EXISTS users ( user_id INTEGER PRIMARY KEY, username TEXT, balance INTEGER DEFAULT 0, energy INTEGER DEFAULT 10, energy_refill_ts REAL DEFAULT 0, total_withdrawn INTEGER DEFAULT 0, withdraw_count INTEGER DEFAULT 0, referred_by INTEGER DEFAULT 0, taps_count INTEGER DEFAULT 0, referral_commission REAL DEFAULT 0, total_ref_bonus REAL DEFAULT 0 ) """) # Попытка добавить столбцы (если уже есть — будет ошибка, игнорируем) try: await db.execute("ALTER TABLE users ADD COLUMN last_tap_ts REAL DEFAULT 0") except: pass try: await db.execute("ALTER TABLE users ADD COLUMN last_reminder_ts REAL DEFAULT 0") except: pass # Таблица состояний (FSM) await db.execute(""" CREATE TABLE IF NOT EXISTS states ( user_id INTEGER PRIMARY KEY, state TEXT DEFAULT '', temp_data TEXT DEFAULT '' ) """) # Глобальная статистика await db.execute(""" CREATE TABLE IF NOT EXISTS global_stats ( id INTEGER PRIMARY KEY, total_users INTEGER DEFAULT 0, total_tapped INTEGER DEFAULT 0, total_withdrawn INTEGER DEFAULT 0 ) """) # Инициализируем одну строку в глобальной статистике await db.execute(""" INSERT OR IGNORE INTO global_stats (id, total_users, total_tapped, total_withdrawn) VALUES (1, 0, 0, 0) """) # Таблица рефералов await db.execute(""" CREATE TABLE IF NOT EXISTS referrals ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER, ref_user_id INTEGER, ref_username TEXT, ref_taps INTEGER DEFAULT 0 ) """) # Заявки на вывод await db.execute(""" CREATE TABLE IF NOT EXISTS pending_withdrawals ( withdraw_id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER, roblox_name TEXT, gamepass_link TEXT, status TEXT DEFAULT 'pending', currency TEXT DEFAULT 'robux', amount INTEGER DEFAULT 0, ttd_nick TEXT DEFAULT '' ) """) # История выводов await db.execute(""" CREATE TABLE IF NOT EXISTS withdraw_history ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER, amount INTEGER, proof_message_id INTEGER DEFAULT 0, review TEXT DEFAULT '' ) """) await db.commit() async def get_user(user_id: int): """Получить запись пользователя из БД (или None).""" async with aiosqlite.connect("database.db") as db: db.row_factory = aiosqlite.Row cur = await db.execute("SELECT * FROM users WHERE user_id = ?", (user_id,)) return await cur.fetchone() async def add_user(user_id: int, username: str, referred_by=0): """Добавить пользователя, если его ещё нет в базе.""" async with aiosqlite.connect("database.db") as db: cur = await db.execute("SELECT user_id FROM users WHERE user_id = ?", (user_id,)) existing = await cur.fetchone() if existing: return # Увеличим общий счётчик пользователей await db.execute("UPDATE global_stats SET total_users = total_users + 1 WHERE id = 1") # Создаём запись await db.execute(""" INSERT INTO users (user_id, username, balance, energy, energy_refill_ts, referred_by, last_tap_ts, last_reminder_ts) VALUES (?,?,?,?,?,?,?,?) """, (user_id, username, 0, START_ENERGY, 0, referred_by, 0, 0)) # Если есть "пригласивший" (реферер) if referred_by != 0: await db.execute(""" INSERT INTO referrals (user_id, ref_user_id, ref_username) VALUES (?,?,?) """, (referred_by, user_id, username)) # Рефереру +3 робукса (пример) await db.execute("UPDATE users SET balance = balance + 3 WHERE user_id = ?", (referred_by,)) # Рефералу +2 энергии (пример) await db.execute("UPDATE users SET energy = energy + 2 WHERE user_id = ?", (user_id,)) await db.commit() async def update_username(user_id: int, username: str): """Обновить username в БД (если изменился).""" async with aiosqlite.connect("database.db") as db: await db.execute("UPDATE users SET username=? WHERE user_id=?", (username, user_id)) await db.commit() async def is_subscribed(client: Client, user_id: int) -> bool: """Проверяем подписку на канал MAIN_CHANNEL_ID.""" try: member = await client.get_chat_member(MAIN_CHANNEL_ID, user_id) return (member.status in ( enums.ChatMemberStatus.OWNER, enums.ChatMemberStatus.ADMINISTRATOR, enums.ChatMemberStatus.MEMBER )) except: return False async def set_state(user_id: int, state: str, temp_data=""): """Записать пользователю новое состояние state + дополнительные данные.""" async with aiosqlite.connect("database.db") as db: await db.execute(""" INSERT INTO states (user_id, state, temp_data) VALUES (?,?,?) ON CONFLICT(user_id) DO UPDATE SET state=excluded.state, temp_data=excluded.temp_data """, (user_id, state, temp_data)) await db.commit() async def get_state(user_id: int): """Получить текущее состояние (state, temp_data) пользователя.""" async with aiosqlite.connect("database.db") as db: cur = await db.execute("SELECT state, temp_data FROM states WHERE user_id=?", (user_id,)) row = await cur.fetchone() if row: return row[0], row[1] # (state, temp_data) else: return "", "" async def clear_state(user_id: int): """Сбросить состояние FSM для пользователя.""" await set_state(user_id, "", "") async def ensure_subscribed(client: Client, user_id: int, chat_id: int) -> bool: """ Проверяем подписку и, если пользователь не подписан, отправляем кнопку «Проверить». Возвращаем True/False (подписан ли). """ if await is_subscribed(client, user_id): return True else: text = ( "⚠ <b>Для использования бота</b> необходимо подписаться на наш канал!\n\n" "👉 <a href='https://t.me/RobuxTapNews'>Перейти в канал</a>\n\n" "После подписки нажмите кнопку «Проверить»." ) check_sub_kb = InlineKeyboardMarkup( [[InlineKeyboardButton("✅ Проверить подписку", callback_data="check_sub_again")]] ) await client.send_message( chat_id=chat_id, text=text, parse_mode=enums.ParseMode.HTML, disable_web_page_preview=True, reply_markup=check_sub_kb ) return False # ========================= # Хендлеры # ========================= @app.on_message(filters.command("start") & ~filters.private) async def start_in_group(_, message: Message): await message.reply("👋 Я работаю только в ЛС. Напишите мне в личку!") @app.on_message(filters.command("start") & filters.private) async def start_private(client: Client, message: Message): user_id = message.from_user.id username = message.from_user.username or f"user_{user_id}" # Проверим, нет ли реферального ID ref_id = 0 if len(message.command) > 1: try: ref_id = int(message.command[1]) except: ref_id = 0 # Добавляем пользователя (если нет в БД) await add_user(user_id, username, ref_id) # Проверяем подписку if await is_subscribed(client, user_id): row = await get_user(user_id) balance = row["balance"] if row else 0 text = ( f"👋<b>Привет, {username}!</b>\n\n" f"На твоём балансе: <b>{balance}</b> робуксов.\n\n" f"💡Чтобы быстрее заработать, приглашай друзей!\n" f"Получай +5 робуксов за каждого.\n\n" f"Твоя реф. ссылка:\n" f"<a href='https://t.me/{BOT_USERNAME}?start={user_id}'>https://t.me/{BOT_USERNAME}?start={user_id}</a>" ) await message.reply( text, parse_mode=enums.ParseMode.HTML, reply_markup=main_menu, disable_web_page_preview=True ) else: text = ( "🔒 <b>Чтобы пользоваться ботом, подпишись на наш канал</b>\n\n" "👉 <a href='https://t.me/RobuxTapNews'>Перейти в канал</a>\n\n" "После подписки нажми кнопку ниже, чтобы бот проверил." ) sub_kb = InlineKeyboardMarkup( [[InlineKeyboardButton("✅ Проверить подписку", callback_data="check_sub_again")]] ) await message.reply( text, parse_mode=enums.ParseMode.HTML, disable_web_page_preview=True, reply_markup=sub_kb ) @app.on_callback_query(filters.regex("^check_sub_again")) async def callback_check_sub_again(client: Client, cq: CallbackQuery): user_id = cq.from_user.id if await is_subscribed(client, user_id): await cq.answer("✅ Подписка подтверждена!", show_alert=True) row = await get_user(user_id) if row: balance = row["balance"] username = row["username"] else: balance = 0 username = cq.from_user.username or str(user_id) text = ( f"👋<b>Привет, {username}!</b>\n\n" f"Баланс: <b>{balance}</b> робуксов.\n\n" f"Приглашай друзей и зарабатывай ещё!\n\n" f"Реф. ссылка:\n" f"<a href='https://t.me/{BOT_USERNAME}?start={user_id}'>https://t.me/{BOT_USERNAME}?start={user_id}</a>" ) await client.send_message( chat_id=user_id, text=text, parse_mode=enums.ParseMode.HTML, reply_markup=main_menu, disable_web_page_preview=True ) else: await cq.answer("❌ Вы всё ещё не подписаны!", show_alert=True) # ---------------------- # Обработка всех сообщений в ЛС # ---------------------- @app.on_message(filters.private & filters.text) async def menu_handler(client: Client, message: Message): user_id = message.from_user.id text_in = message.text.strip() # Проверяем, что пользователь есть в БД row = await get_user(user_id) if not row: username = message.from_user.username or f"user_{user_id}" await add_user(user_id, username, 0) # Проверяем подписку if not await ensure_subscribed(client, user_id, message.chat.id): return # Актуализируем username user_info = await get_user(user_id) if not user_info: await message.reply("Ошибка в профиле. Нажмите /start.") return current_name = message.from_user.username or f"user_{user_id}" if current_name != user_info["username"]: await update_username(user_id, current_name) # Получаем текущее состояние state, temp_data = await get_state(user_id) # Если пользователь в одном из состояний, но нажал кнопку из меню — сбрасываем состояние if state in [ "waiting_for_review", "awaiting_gamepass_link", "awaiting_robux_amount", "awaiting_ttd_nick" ] and text_in in MENU_COMMANDS: await clear_state(user_id) state, temp_data = await get_state(user_id) # =================================== # 1. Ожидание отзыва (waiting_for_review) # =================================== if state == "waiting_for_review": if not temp_data: await message.reply("Ошибка: нет ID вывода (temp_data пустое).") await clear_state(user_id) return try: withdraw_id = int(temp_data) except ValueError: await message.reply("Ошибка: temp_data не является корректным числом!") await clear_state(user_id) return # Принимаем отзыв review_text = message.text or "" async with aiosqlite.connect("database.db") as db: # Запишем отзыв await db.execute( "UPDATE withdraw_history SET review=? WHERE id=?", (review_text, withdraw_id) ) # Получаем данные для пересылки cur = await db.execute(""" SELECT proof_message_id, user_id, amount FROM withdraw_history WHERE id=? """, (withdraw_id,)) row_w = await cur.fetchone() if not row_w: await message.reply("Не найдена запись о выводе (withdraw_history).") await clear_state(user_id) return proof_msg_id, target_user_id, w_amount = row_w # Узнаём username целевого пользователя cur_u = await db.execute("SELECT username FROM users WHERE user_id=?", (target_user_id,)) user_row = await cur_u.fetchone() username_db = user_row[0] if (user_row and user_row[0]) else str(target_user_id) # Начислим +1 робукс за отзыв (просто пример) await db.execute("UPDATE users SET balance = balance + 1 WHERE user_id=?", (target_user_id,)) await db.commit() # Допустим, пересылаем отзыв и пруф в канал для отзывов channel_for_reviews = -1002434602961 # Ваш канал для отзывов # Пересылаем отзыв try: await client.forward_messages( chat_id=channel_for_reviews, from_chat_id=message.chat.id, message_ids=message.id ) except Exception as e: print("Ошибка при пересылке отзыва пользователя в канал:", e) # Копируем фото/видео пруфа (если было) try: await client.copy_message( chat_id=channel_for_reviews, from_chat_id=target_user_id, message_id=proof_msg_id ) except Exception as e: print("Ошибка при копировании пруфа:", e) # Доп. сообщение в канал await client.send_message( chat_id=channel_for_reviews, text=( f"❇ <b>Отзыв от @{username_db}</b>\n" f"🔗 UserID: {target_user_id}\n" f"💰 Сумма вывода: {w_amount}\n\n" f"✅ За отзыв начислен +1 Robux!\n" ), parse_mode=enums.ParseMode.HTML ) # Сообщаем пользователю await message.reply( "🎉 Спасибо за отзыв! Тебе начислен <b>+1 Robux</b> на баланс.", parse_mode=enums.ParseMode.HTML, reply_markup=main_menu ) await clear_state(user_id) return # =================================== # 2. Ожидание суммы вывода Robux (awaiting_robux_amount) # =================================== if state == "awaiting_robux_amount": try: amount = int(text_in) if amount <= 0: await message.reply("⚠ Введите положительное число!") return except ValueError: await message.reply("⚠ Введите целое число (количество робуксов).") return user_balance = user_info["balance"] if amount > user_balance: await message.reply("❌ Недостаточно средств на балансе!") return if amount < MIN_WITHDRAW_BALANCE: await message.reply(f"❌ Минимальная сумма для вывода: {MIN_WITHDRAW_BALANCE}.") return # Сохраняем сумму во временных данных await set_state(user_id, "awaiting_gamepass_link", str(amount)) # Сообщаем пользователю, чтобы создал геймпасс await message.reply( f"Создайте Game Pass ровно на <b>{amount}</b> робуксов.\n\n" "После этого пришлите сюда ссылку на геймпасс, чтобы бот проверил цену.", parse_mode=enums.ParseMode.HTML, reply_markup=ReplyKeyboardRemove() ) return # =================================== # 3. Ожидание ссылки на Game Pass (awaiting_gamepass_link) # =================================== if state == "awaiting_gamepass_link": amount_str = temp_data # сумма, которую пользователь ввёл try: expected_amount = int(amount_str) except ValueError: await message.reply("Ошибка: некорректная сохранённая сумма. Начните заново /start") await clear_state(user_id) return gamepass_link = text_in.strip() # 1) Парсим gamepass_id gp_id = parse_gamepass_id(gamepass_link) if gp_id < 0: await message.reply( "⚠ Не удалось определить ID геймпаса из ссылки.\n" "Убедитесь, что формат ссылки типа:\n" "https://www.roblox.com/game-pass/123456" ) return # 2) Запрашиваем реальную инфу о геймпассе gp_info = await get_gamepass_info(gp_id) if not gp_info: await message.reply("❌ Не удалось получить информацию о геймпассе. Попробуйте заново.") return real_price = gp_info.get("PriceInRobux", 0) # 3) Сравниваем цену if real_price != expected_amount: await message.reply( f"❌ Цена геймпаса = {real_price} robux, а ты вводил {expected_amount}!\n" "Проверьте правильность, затем заново укажите сумму вывода." ) # Возвращаем бота к состоянию "спросить сумму заново" await set_state(user_id, "awaiting_robux_amount", "") return # Если всё ок — создаём заявку async with aiosqlite.connect("database.db") as db: await db.execute(""" INSERT INTO pending_withdrawals (user_id, roblox_name, gamepass_link, status, currency, amount) VALUES (?,?,?,?,?,?) """, (user_id, "", gamepass_link, 'pending', 'robux', expected_amount)) # Списываем робуксы await db.execute("UPDATE users SET balance=balance-? WHERE user_id=?", (expected_amount, user_id)) # Увеличим кол-во выводов await db.execute("UPDATE users SET withdraw_count = withdraw_count + 1 WHERE user_id=?", (user_id,)) # Запись в историю await db.execute(""" INSERT INTO withdraw_history (user_id, amount) VALUES (?,?) """, (user_id, expected_amount)) await db.commit() # Определяем ID заявки new_withdraw_id = 0 async with aiosqlite.connect("database.db") as db: cur = await db.execute("SELECT MAX(withdraw_id) FROM pending_withdrawals WHERE user_id=?", (user_id,)) row2 = await cur.fetchone() new_withdraw_id = row2[0] if (row2 and row2[0]) else 0 # Уведомляем админа inline_kb = InlineKeyboardMarkup( [ [ InlineKeyboardButton( "Выполнить", callback_data=f"complete_withdraw:{new_withdraw_id}" ) ] ] ) await client.send_message( chat_id=ADMIN_CHAT_ID, text=( f"🔔 <b>Новая заявка на вывод [ROBLOX]</b>\n\n" f"👤 <b>Telegram ID:</b> <code>{user_id}</code>\n" f"🔗 <b>GamePass:</b> {gamepass_link}\n" f"💸 <b>Сумма вывода:</b> {expected_amount} Robux\n\n" f"Нажмите «Выполнить» после покупки геймпаса." ), reply_markup=inline_kb, parse_mode=enums.ParseMode.HTML ) await message.reply( f"✅ Заявка на вывод <b>{expected_amount}</b> Robux отправлена админу!\n" "Ожидай подтверждения и пруфов в личке.", parse_mode=enums.ParseMode.HTML, reply_markup=main_menu ) await clear_state(user_id) return # =================================== # 4. Ожидание ника для TTD (awaiting_ttd_nick) # =================================== # (Остальной код — не меняем, если у вас всё уже настроено для TTD) # ... # =================================== # Обработка кнопок меню (нет активного состояния) # =================================== if text_in == "ℹ Мой Профиль": balance = user_info["balance"] wcount = user_info["withdraw_count"] wsum = user_info["total_withdrawn"] uname = user_info["username"] await message.reply( f"📋 <b>Твой профиль:</b>\n\n" f"👤<b>Ник:</b> @{uname}\n" f"🆔<b>ID:</b> <code>{user_id}</code>\n" f"💰<b>Баланс (Robux):</b> {balance}\n" f"🔄<b>Выводов сделано:</b> {wcount}\n" f"💸<b>Всего выведено (Robux):</b> {wsum}", parse_mode=enums.ParseMode.HTML ) elif text_in == "💸 Заработать Робуксы": # Пример логики «тапов» energy = user_info["energy"] energy_refill_ts = user_info["energy_refill_ts"] last_tap_ts = user_info["last_tap_ts"] or 0 current_time = time.time() if energy <= 0: # Проверим, не наступило ли время восстановления if current_time >= energy_refill_ts and energy_refill_ts != 0: energy = START_ENERGY energy_refill_ts = 0 else: sec_left = int(energy_refill_ts - current_time) hours = sec_left // 3600 mins = (sec_left % 3600) // 60 await message.reply( f"😴 У тебя закончилась энергия.\n" f"⏳ Восстановится через <b>{hours} ч {mins} мин</b>.", parse_mode=enums.ParseMode.HTML ) return new_balance = user_info["balance"] + 1 energy -= 1 taps_count = user_info["taps_count"] + 1 referred_by = user_info["referred_by"] if referred_by != 0: async with aiosqlite.connect("database.db") as db: cur_ref = await db.execute("SELECT referral_commission FROM users WHERE user_id=?", (referred_by,)) ref_info = await cur_ref.fetchone() if ref_info: old_comm = float(ref_info[0]) new_comm = old_comm + 0.1 await db.execute( "UPDATE users SET referral_commission=? WHERE user_id=?", (new_comm, referred_by) ) await db.execute( "UPDATE referrals SET ref_taps=ref_taps+1 WHERE ref_user_id=?", (user_id,) ) # Если энергия стала 0, запускаем таймер восстановления if energy <= 0: energy_refill_ts = current_time + ENERGY_REFILL_HOURS * 3600 # Обновляем данные в БД async with aiosqlite.connect("database.db") as db: await db.execute(""" UPDATE users SET balance=?, energy=?, energy_refill_ts=?, taps_count=?, last_tap_ts=? WHERE user_id=? """, (new_balance, energy, energy_refill_ts, taps_count, current_time, user_id)) await db.execute(""" UPDATE global_stats SET total_tapped=total_tapped+1 WHERE id=1 """) await db.commit() await message.reply( f"🪄 Ты получил <b>+1</b> робакс!\n" f"⚡ Осталось энергии: <b>{energy}</b> из {START_ENERGY}.\n\n" f"Когда энергия станет 0, она восстановится через {ENERGY_REFILL_HOURS} ч.", parse_mode=enums.ParseMode.HTML ) elif text_in == "🗣 Друзья": ref_link = f"https://t.me/{BOT_USERNAME}?start={user_id}" async with aiosqlite.connect("database.db") as db: cur_f = await db.execute(""" SELECT ref_user_id, ref_username, ref_taps FROM referrals WHERE user_id=? """, (user_id,)) friends_data = await cur_f.fetchall() cur_comm = await db.execute( "SELECT referral_commission FROM users WHERE user_id=?", (user_id,) ) row_u = await cur_comm.fetchone() ref_comm = row_u[0] if row_u else 0 text_friends = "👥 <b>Твои рефералы:</b>\n\n" if friends_data: for f in friends_data: rid, rname, rtaps = f text_friends += ( f"• @{rname} (ID: {rid}), " f"натапал {rtaps} робуксов.\n" ) else: text_friends += "Пока нет рефералов.\n" text_friends += "\n" text_friends += f"💸 С рефералов накопилось: <b>{ref_comm:.2f}</b> робуксов (10%)." # -- Кнопка "Вывести 10%" withdraw_button = InlineKeyboardButton("Вывести 10%", callback_data="withdraw_ref_commission") # -- Кнопка "Поделиться" share_text = f"😎Зарабатывай робуксы с помощью бота: {ref_link}" encoded_share_text = urllib.parse.quote_plus(share_text) share_url = f"https://t.me/share/url?url={urllib.parse.quote_plus(ref_link)}&text={encoded_share_text}" share_button = InlineKeyboardButton("Поделиться", url=share_url) inline_kb = InlineKeyboardMarkup( [ [withdraw_button], [share_button] ] ) await message.reply( f"🔗 Твоя реф. ссылка:\n{ref_link}\n\n{text_friends}", reply_markup=inline_kb, parse_mode=enums.ParseMode.HTML, disable_web_page_preview=True ) elif text_in == "🦈 Вывести": # Предлагаем выбрать Robux или TTD currency_kb = InlineKeyboardMarkup( [ [InlineKeyboardButton("Robux", callback_data="choose_currency:robux")], [InlineKeyboardButton("💎 TTD GEMS", callback_data="choose_currency:ttd")] ] ) await message.reply( "Выбери, что хочешь вывести:", reply_markup=currency_kb ) elif text_in == "🌴 Пруфы": await message.reply( "📸 Посмотреть все пруфы можно тут:", reply_markup=proofs_inline ) elif text_in == "📊 Статистика": async with aiosqlite.connect("database.db") as db: cur_st = await db.execute("SELECT total_users, total_tapped, total_withdrawn FROM global_stats WHERE id=1") stats = await cur_st.fetchone() total_users = stats[0] total_tapped = stats[1] total_wd = stats[2] await message.reply( f"📊 <b>Статистика бота:</b>\n\n" f"👥 Пользователей: <b>{total_users}</b>\n" f"👆 Всего натапано: <b>{total_tapped}</b>\n" f"💸 Всего выведено (Robux): <b>{total_wd}</b>", parse_mode=enums.ParseMode.HTML ) elif text_in == "❓ FAQ": faq_text = ( "❓ <b>Частые вопросы:</b>\n\n" "1. <b>Мой Профиль</b> — посмотреть баланс и статистику.\n" "2. <b>💸 Заработать Робуксы</b> — получить +1 робакс (тратится 1 энергия).\n" "3. <b>🗣 Друзья</b> — реф.ссылка и доход с рефералов.\n" "4. <b>🦈 Вывести</b> — подать заявку на вывод (Robux или TTD).\n" "5. <b>🌴 Пруфы</b> — канал с выплатами.\n" "6. <b>📊 Статистика</b> — общая статистика бота.\n" "7. <b>❓ FAQ</b> — это меню." ) await message.reply(faq_text, parse_mode=enums.ParseMode.HTML) else: await message.reply("Выберите команду из меню или введите /start.") # --------------------------- # Callback: выбор валюты # --------------------------- @app.on_callback_query(filters.regex(r"^choose_currency:(\w+)")) async def choose_currency_handler(client: Client, callback_query: CallbackQuery): user_id = callback_query.from_user.id currency = callback_query.matches[0].group(1) # 'robux' или 'ttd' await callback_query.answer() if not await is_subscribed(client, user_id): await callback_query.answer("Сначала подпишитесь на канал!", show_alert=True) return user_info = await get_user(user_id) if not user_info: await callback_query.message.reply("Ошибка профиля, нажмите /start.") return user_balance = user_info["balance"] if currency == "robux": if user_balance < MIN_WITHDRAW_BALANCE: await callback_query.message.reply( f"Минимальная сумма для вывода: {MIN_WITHDRAW_BALANCE} робуксов!", parse_mode=enums.ParseMode.HTML ) return # Запрашиваем сумму await set_state(user_id, "awaiting_robux_amount", "") await callback_query.message.reply( "💰 Введи количество робуксов, которое хочешь вывести:\n(Не больше твоего баланса!)", parse_mode=enums.ParseMode.HTML, reply_markup=ReplyKeyboardRemove() ) elif currency == "ttd": # Проверяем условие для TTD if user_balance < 100: await callback_query.message.reply( "Для вывода TTD нужно как минимум 100 Robux на балансе!", parse_mode=enums.ParseMode.HTML ) return # Конвертируем весь баланс: 1 Robux = 70 GEMS ttd_amount = user_balance * ROBUX_TO_TTD_RATE temp_data = f"{ttd_amount}|{user_balance}" await set_state(user_id, "awaiting_ttd_nick", temp_data) await callback_query.message.reply( f"У тебя <b>{user_balance}</b> Robux.\n" f"По курсу <b>1 R$ = {ROBUX_TO_TTD_RATE} GEMS</b>\n" f"Ты получишь <b>{ttd_amount} TTD GEMS</b>.\n\n" "💎 Введи <b>свой ник в TTD</b>:", parse_mode=enums.ParseMode.HTML, reply_markup=ReplyKeyboardRemove() ) # --------------------------- # Callback: вывод реф. комиссии # --------------------------- @app.on_callback_query(filters.regex(r"^withdraw_ref_commission")) async def withdraw_ref_commission_handler(client: Client, cq: CallbackQuery): user_id = cq.from_user.id if not await is_subscribed(client, user_id): await cq.answer("Вы не подписаны на канал!", show_alert=True) return async with aiosqlite.connect("database.db") as db: cur = await db.execute("SELECT referral_commission FROM users WHERE user_id=?", (user_id,)) row = await cur.fetchone() if not row: await cq.answer("Ошибка: нет данных о пользователе.", show_alert=True) return commission = float(row[0]) if commission <= 0: await cq.answer("У вас нет накопленной комиссии.", show_alert=True) return # Переводим на баланс await db.execute(""" UPDATE users SET balance=balance+?, referral_commission=0 WHERE user_id=? """, (commission, user_id)) await db.commit() await cq.answer("Реферальная комиссия выведена на баланс!") await client.send_message( user_id, f"Вы успешно перевели <b>{commission:.2f}</b> робуксов из реф. комиссии на основной баланс.", parse_mode=enums.ParseMode.HTML ) # --------------------------- # Callback: админ нажимает «Выполнить» # --------------------------- @app.on_callback_query(filters.regex(r"^complete_withdraw:(\d+)")) async def complete_withdraw_handler(client: Client, callback_query: CallbackQuery): withdraw_id = int(callback_query.matches[0].group(1)) admin_id = callback_query.from_user.id # Ставим админу состояние «ждём пруфы» await set_state(admin_id, "admin_waiting_proofs", str(withdraw_id)) # Обновим статус заявки async with aiosqlite.connect("database.db") as db: await db.execute( "UPDATE pending_withdrawals SET status='processing' WHERE withdraw_id=?", (withdraw_id,) ) await db.commit() await callback_query.answer("Отправьте пруфы (фото/видео) боту в ЛС.") # --------------------------- # Приём пруфов от админа (фото / видео) # --------------------------- @app.on_message(filters.private & (filters.photo | filters.video)) async def admin_send_proof_handler(client: Client, message: Message): user_id = message.from_user.id state, temp_data = await get_state(user_id) if state == "admin_waiting_proofs": try: withdraw_id = int(temp_data) except ValueError: await message.reply("Ошибка: некорректный ID вывода.") await clear_state(user_id) return # Узнаём, чей вывод и какой amount async with aiosqlite.connect("database.db") as db: cur = await db.execute(""" SELECT user_id, amount FROM pending_withdrawals WHERE withdraw_id=? AND status IN ('pending','processing') """, (withdraw_id,)) row = await cur.fetchone() if not row: await message.reply("Ошибка: заявка не найдена (или уже завершена)!") await clear_state(user_id) return target_user_id, amount = row # Отправим пруф пользователю if message.photo: sent_msg = await client.send_photo( chat_id=target_user_id, photo=message.photo.file_id, caption="🎉 Твой вывод выполнен! Пришли, пожалуйста, отзыв.", parse_mode=enums.ParseMode.HTML ) else: sent_msg = await client.send_video( chat_id=target_user_id, video=message.video.file_id, caption="🎉 Твой вывод выполнен! Пришли, пожалуйста, отзыв.", parse_mode=enums.ParseMode.HTML ) # Помечаем вывод как завершён и сохраняем ID сообщения с пруфом async with aiosqlite.connect("database.db") as db: # Запишем ID сообщения в withdraw_history await db.execute(""" UPDATE withdraw_history SET proof_message_id=? WHERE id=? """, (sent_msg.id, withdraw_id)) # Меняем статус заявки await db.execute(""" UPDATE pending_withdrawals SET status='completed' WHERE withdraw_id=? """, (withdraw_id,)) # Увеличим счётчик total_withdrawn у пользователя await db.execute(""" UPDATE users SET total_withdrawn = total_withdrawn + ? WHERE user_id=? """, (amount, target_user_id)) # И в глобальной статистике await db.execute(""" UPDATE global_stats SET total_withdrawn = total_withdrawn + ? WHERE id=1 """, (amount,)) await db.commit() # Ставим пользователю состояние ожидания отзыва await set_state(target_user_id, "waiting_for_review", str(withdraw_id)) # Сообщаем админу await message.reply("Пруф отправлен пользователю! Ожидаем от него отзыв.") await clear_state(user_id) # =========================== # Фоновая задача напоминаний # =========================== async def reminder_loop(): """Каждый час проверяем, кто не тапал N часов, но имеет энергию > 0, и шлём напоминание.""" while True: try: now_ts = time.time() interval_sec = REMINDER_INTERVAL_HOURS * 3600 threshold_ts = now_ts - interval_sec async with aiosqlite.connect("database.db") as db: db.row_factory = aiosqlite.Row cur = await db.execute(""" SELECT user_id FROM users WHERE energy > 0 AND last_tap_ts < ? AND last_reminder_ts < ? """, (threshold_ts, threshold_ts)) rows = await cur.fetchall() for row in rows: uid = row["user_id"] try: await app.send_message( chat_id=uid, text=( "Привет!\n" "У тебя есть энергия для тапов, но ты давно не заходил!\n" "Не упускай возможность заработать робуксы 😎" ) ) except Exception as e: print(f"Не смог отправить напоминание пользователю {uid}: {e}") # Обновляем время последнего напоминания await db.execute( "UPDATE users SET last_reminder_ts=? WHERE user_id=?", (now_ts, uid) ) await db.commit() except Exception as e: print("Ошибка в reminder_loop:", e) # Спим 1 час await asyncio.sleep(3600) # =========================== # ИНИЦИАЛИЗАЦИЯ И ЗАПУСК # =========================== await init_db() await app.start() print("Bot started...") # Запускаем фоновую задачу напоминаний asyncio.create_task(reminder_loop()) await idle() await app.stop()
if name == "main":
asyncio.run(main())
Ниже приведён пример того, как можно встроить в ваш код:
/post "сообщение"
(только для админа), рассылающую сообщение всем пользователям бота.Основная идея в том, что мы:
fetch_roblox_data
, которая делает GET
-запрос к нужному эндпоинту Roblox, указывая авторизационные заголовки (Cookie и т.п.). В этой же функции ведём логирование (можно в консоль или в файл, или в таблицу в БД — выбирайте по вкусу).@app.on_message(filters.command("post"))
, который проверяет, что сообщение пришло от админа, и рассылает текст всем пользователям из таблицы users
.Ниже — общий укрупнённый вариант вашего кода с новыми вставками (помечено комментариями # NEW
), чтобы было понятно, куда их примерно можно внедрить.
pythonimport asyncio import time import urllib.parse import re import aiohttp import logging # NEW: для логирования from pyrogram import Client, filters, idle, enums from pyrogram.types import ( Message, InlineKeyboardButton, InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, CallbackQuery ) import aiosqlite # ============================= # Пример настройки логирования (по желанию) # ============================= logging.basicConfig( level=logging.INFO, format='[%(asctime)s] %(levelname)s: %(message)s', datefmt='%H:%M:%S' ) # ============================= # ФУНКЦИИ ДЛЯ РАБОТЫ С ГЕЙМПАССАМИ # ============================= def parse_gamepass_id(link: str) -> int: match = re.search(r'game-pass/(\d+)', link) if match: return int(match.group(1)) return -1 async def get_gamepass_info(gamepass_id: int) -> dict: """ Старый API для Game Pass (без авторизации). """ url = f"https://api.roblox.com/marketplace/game-pass-product-info?gamePassId={gamepass_id}" async with aiohttp.ClientSession() as session: try: async with session.get(url) as resp: if resp.status == 200: data = await resp.json() return data else: return None except: return None # ============================= # NEW: Функция для запроса к новым эндпоинтам Roblox, # где требуется куки или авторизация. # ============================= # Пример: Если нужно использовать cookie .ROBLOSECURITY, # укажите его здесь (или загрузите из переменных окружения). ROBLOX_COOKIE = "YOUR_ROBLOSECURITY_COOKIE_ЗДЕСЬ" async def fetch_roblox_data(endpoint: str, params: dict=None) -> dict: """ Пример запроса к новому Roblox-эндпоинту, который требует авторизации (cookie). Заодно логируем запрос/ответ в консоль (или файл). endpoint: например, "/v1/users/authenticated" или полный URL params: dict с query-параметрами """ # Если endpoint не полный, формируем URL (пример): if endpoint.startswith("https://"): url = endpoint else: # допустим, у нас есть общий домен url = f"https://roblox.com{endpoint}" headers = { "Cookie": ROBLOX_COOKIE, "User-Agent": "Mozilla/5.0", # При необходимости добавляем другие заголовки # "X-CSRF-TOKEN": ... } # Логируем попытку запроса logging.info(f"[REQUEST] GET {url}, params={params}, headers={headers}") async with aiohttp.ClientSession() as session: try: async with session.get(url, headers=headers, params=params) as resp: # Читаем ответ data = await resp.json() # Логируем статус и часть ответа logging.info(f"[RESPONSE] status={resp.status}, data={str(data)[:200]}...") return data except Exception as e: logging.error(f"[ERROR] При запросе к {url}: {e}") return {} # ============================= # Основная логика бота # ============================= async def main(): # ====================== # НАСТРОЙКИ БОТА # ====================== API_ID = '24142370' API_HASH = '5499eef960820d34a871c67024d89819' BOT_TOKEN = "7380283669:AAH2Do7p-bxOjnVn4c3Y2tnHh4DdXEw4egw" MAIN_CHANNEL_ID = -1002380592722 BOT_USERNAME = "RobuxTapBot" START_ENERGY = 12 ENERGY_REFILL_HOURS = 14 MIN_WITHDRAW_BALANCE = 4 ROBUX_TO_TTD_RATE = 70 # ID админа (кто сможет пользоваться /post) ADMIN_CHAT_ID = 8085926304 REMINDER_INTERVAL_HOURS = 4 main_menu = ReplyKeyboardMarkup( [ ["ℹ Мой Профиль"], ["💸 Заработать Робуксы", "🗣 Друзья"], ["🦈 Вывести", "🌴 Пруфы"], ["📊 Статистика", "❓ FAQ"] ], resize_keyboard=True ) proofs_inline = InlineKeyboardMarkup( [ [ InlineKeyboardButton( "Канал с пруфами и отзывами", url="https://t.me/proofs_vivods" ) ] ] ) MENU_COMMANDS = [ "ℹ Мой Профиль", "💸 Заработать Робуксы", "🗣 Друзья", "🦈 Вывести", "🌴 Пруфы", "📊 Статистика", "❓ FAQ", "/start" ] app = Client("robux_tap_bot", api_id=API_ID, api_hash=API_HASH, bot_token=BOT_TOKEN) # ========================= # Вспомогательные функции # ========================= async def init_db(): async with aiosqlite.connect("database.db") as db: await db.execute(""" CREATE TABLE IF NOT EXISTS users ( user_id INTEGER PRIMARY KEY, username TEXT, balance INTEGER DEFAULT 0, energy INTEGER DEFAULT 10, energy_refill_ts REAL DEFAULT 0, total_withdrawn INTEGER DEFAULT 0, withdraw_count INTEGER DEFAULT 0, referred_by INTEGER DEFAULT 0, taps_count INTEGER DEFAULT 0, referral_commission REAL DEFAULT 0, total_ref_bonus REAL DEFAULT 0 ) """) try: await db.execute("ALTER TABLE users ADD COLUMN last_tap_ts REAL DEFAULT 0") except: pass try: await db.execute("ALTER TABLE users ADD COLUMN last_reminder_ts REAL DEFAULT 0") except: pass await db.execute(""" CREATE TABLE IF NOT EXISTS states ( user_id INTEGER PRIMARY KEY, state TEXT DEFAULT '', temp_data TEXT DEFAULT '' ) """) await db.execute(""" CREATE TABLE IF NOT EXISTS global_stats ( id INTEGER PRIMARY KEY, total_users INTEGER DEFAULT 0, total_tapped INTEGER DEFAULT 0, total_withdrawn INTEGER DEFAULT 0 ) """) await db.execute(""" INSERT OR IGNORE INTO global_stats (id, total_users, total_tapped, total_withdrawn) VALUES (1, 0, 0, 0) """) await db.execute(""" CREATE TABLE IF NOT EXISTS referrals ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER, ref_user_id INTEGER, ref_username TEXT, ref_taps INTEGER DEFAULT 0 ) """) await db.execute(""" CREATE TABLE IF NOT EXISTS pending_withdrawals ( withdraw_id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER, roblox_name TEXT, gamepass_link TEXT, status TEXT DEFAULT 'pending', currency TEXT DEFAULT 'robux', amount INTEGER DEFAULT 0, ttd_nick TEXT DEFAULT '' ) """) await db.execute(""" CREATE TABLE IF NOT EXISTS withdraw_history ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER, amount INTEGER, proof_message_id INTEGER DEFAULT 0, review TEXT DEFAULT '' ) """) await db.commit() async def get_user(user_id: int): async with aiosqlite.connect("database.db") as db: db.row_factory = aiosqlite.Row cur = await db.execute("SELECT * FROM users WHERE user_id = ?", (user_id,)) return await cur.fetchone() async def add_user(user_id: int, username: str, referred_by=0): async with aiosqlite.connect("database.db") as db: cur = await db.execute("SELECT user_id FROM users WHERE user_id = ?", (user_id,)) existing = await cur.fetchone() if existing: return await db.execute("UPDATE global_stats SET total_users = total_users + 1 WHERE id = 1") await db.execute(""" INSERT INTO users (user_id, username, balance, energy, energy_refill_ts, referred_by, last_tap_ts, last_reminder_ts) VALUES (?,?,?,?,?,?,?,?) """, (user_id, username, 0, START_ENERGY, 0, referred_by, 0, 0)) if referred_by != 0: await db.execute(""" INSERT INTO referrals (user_id, ref_user_id, ref_username) VALUES (?,?,?) """, (referred_by, user_id, username)) # пример бонусов за реферала: await db.execute("UPDATE users SET balance = balance + 3 WHERE user_id = ?", (referred_by,)) await db.execute("UPDATE users SET energy = energy + 2 WHERE user_id = ?", (user_id,)) await db.commit() async def update_username(user_id: int, username: str): async with aiosqlite.connect("database.db") as db: await db.execute("UPDATE users SET username=? WHERE user_id=?", (username, user_id)) await db.commit() async def is_subscribed(client: Client, user_id: int) -> bool: try: member = await client.get_chat_member(MAIN_CHANNEL_ID, user_id) return (member.status in ( enums.ChatMemberStatus.OWNER, enums.ChatMemberStatus.ADMINISTRATOR, enums.ChatMemberStatus.MEMBER )) except: return False async def set_state(user_id: int, state: str, temp_data=""): async with aiosqlite.connect("database.db") as db: await db.execute(""" INSERT INTO states (user_id, state, temp_data) VALUES (?,?,?) ON CONFLICT(user_id) DO UPDATE SET state=excluded.state, temp_data=excluded.temp_data """, (user_id, state, temp_data)) await db.commit() async def get_state(user_id: int): async with aiosqlite.connect("database.db") as db: cur = await db.execute("SELECT state, temp_data FROM states WHERE user_id=?", (user_id,)) row = await cur.fetchone() if row: return row[0], row[1] else: return "", "" async def clear_state(user_id: int): await set_state(user_id, "", "") async def ensure_subscribed(client: Client, user_id: int, chat_id: int) -> bool: if await is_subscribed(client, user_id): return True else: text = ( "⚠ <b>Для использования бота</b> необходимо подписаться на наш канал!\n\n" "👉 <a href='https://t.me/RobuxTapNews'>Перейти в канал</a>\n\n" "После подписки нажмите кнопку «Проверить»." ) check_sub_kb = InlineKeyboardMarkup( [[InlineKeyboardButton("✅ Проверить подписку", callback_data="check_sub_again")]] ) await client.send_message( chat_id=chat_id, text=text, parse_mode=enums.ParseMode.HTML, disable_web_page_preview=True, reply_markup=check_sub_kb ) return False # ========================= # Хендлеры # ========================= @app.on_message(filters.command("start") & ~filters.private) async def start_in_group(_, message: Message): await message.reply("👋 Я работаю только в ЛС. Напишите мне в личку!") @app.on_message(filters.command("start") & filters.private) async def start_private(client: Client, message: Message): user_id = message.from_user.id username = message.from_user.username or f"user_{user_id}" ref_id = 0 if len(message.command) > 1: try: ref_id = int(message.command[1]) except: ref_id = 0 await add_user(user_id, username, ref_id) if await is_subscribed(client, user_id): row = await get_user(user_id) balance = row["balance"] if row else 0 text = ( f"👋<b>Привет, {username}!</b>\n\n" f"На твоём балансе: <b>{balance}</b> робуксов.\n\n" f"💡Чтобы быстрее заработать, приглашай друзей!\n" f"Получай +5 робуксов за каждого.\n\n" f"Твоя реф. ссылка:\n" f"<a href='https://t.me/{BOT_USERNAME}?start={user_id}'>https://t.me/{BOT_USERNAME}?start={user_id}</a>" ) await message.reply( text, parse_mode=enums.ParseMode.HTML, reply_markup=main_menu, disable_web_page_preview=True ) else: text = ( "🔒 <b>Чтобы пользоваться ботом, подпишись на наш канал</b>\n\n" "👉 <a href='https://t.me/RobuxTapNews'>Перейти в канал</a>\n\n" "После подписки нажми кнопку ниже, чтобы бот проверил." ) sub_kb = InlineKeyboardMarkup( [[InlineKeyboardButton("✅ Проверить подписку", callback_data="check_sub_again")]] ) await message.reply( text, parse_mode=enums.ParseMode.HTML, disable_web_page_preview=True, reply_markup=sub_kb ) @app.on_callback_query(filters.regex("^check_sub_again")) async def callback_check_sub_again(client: Client, cq: CallbackQuery): user_id = cq.from_user.id if await is_subscribed(client, user_id): await cq.answer("✅ Подписка подтверждена!", show_alert=True) row = await get_user(user_id) if row: balance = row["balance"] username = row["username"] else: balance = 0 username = cq.from_user.username or str(user_id) text = ( f"👋<b>Привет, {username}!</b>\n\n" f"Баланс: <b>{balance}</b> робуксов.\n\n" f"Приглашай друзей и зарабатывай ещё!\n\n" f"Реф. ссылка:\n" f"<a href='https://t.me/{BOT_USERNAME}?start={user_id}'>https://t.me/{BOT_USERNAME}?start={user_id}</a>" ) await client.send_message( chat_id=user_id, text=text, parse_mode=enums.ParseMode.HTML, reply_markup=main_menu, disable_web_page_preview=True ) else: await cq.answer("❌ Вы всё ещё не подписаны!", show_alert=True) @app.on_message(filters.private & filters.text) async def menu_handler(client: Client, message: Message): user_id = message.from_user.id text_in = message.text.strip() row = await get_user(user_id) if not row: username = message.from_user.username or f"user_{user_id}" await add_user(user_id, username, 0) if not await ensure_subscribed(client, user_id, message.chat.id): return user_info = await get_user(user_id) if not user_info: await message.reply("Ошибка в профиле. Нажмите /start.") return current_name = message.from_user.username or f"user_{user_id}" if current_name != user_info["username"]: await update_username(user_id, current_name) state, temp_data = await get_state(user_id) if state in [ "waiting_for_review", "awaiting_gamepass_link", "awaiting_robux_amount", "awaiting_ttd_nick" ] and text_in in MENU_COMMANDS: await clear_state(user_id) state, temp_data = await get_state(user_id) # ------------------------------------------------ # 1) Состояние "waiting_for_review" # ------------------------------------------------ if state == "waiting_for_review": if not temp_data: await message.reply("Ошибка: нет ID вывода (temp_data пустое).") await clear_state(user_id) return try: withdraw_id = int(temp_data) except ValueError: await message.reply("Ошибка: temp_data не является корректным числом!") await clear_state(user_id) return review_text = message.text or "" async with aiosqlite.connect("database.db") as db: await db.execute( "UPDATE withdraw_history SET review=? WHERE id=?", (review_text, withdraw_id) ) cur = await db.execute(""" SELECT proof_message_id, user_id, amount FROM withdraw_history WHERE id=? """, (withdraw_id,)) row_w = await cur.fetchone() if not row_w: await message.reply("Не найдена запись о выводе (withdraw_history).") await clear_state(user_id) return proof_msg_id, target_user_id, w_amount = row_w cur_u = await db.execute("SELECT username FROM users WHERE user_id=?", (target_user_id,)) user_row = await cur_u.fetchone() username_db = user_row[0] if (user_row and user_row[0]) else str(target_user_id) # Начислим +1 робукс за отзыв await db.execute("UPDATE users SET balance = balance + 1 WHERE user_id=?", (target_user_id,)) await db.commit() channel_for_reviews = -1002434602961 # ваш канал для отзывов # пересылаем отзыв (текст) try: await client.forward_messages( chat_id=channel_for_reviews, from_chat_id=message.chat.id, message_ids=message.id ) except Exception as e: print("Ошибка при пересылке отзыва пользователя в канал:", e) # копируем фото/видео пруфа try: await client.copy_message( chat_id=channel_for_reviews, from_chat_id=target_user_id, message_id=proof_msg_id ) except Exception as e: print("Ошибка при копировании пруфа:", e) await client.send_message( chat_id=channel_for_reviews, text=( f"❇ <b>Отзыв от @{username_db}</b>\n" f"🔗 UserID: {target_user_id}\n" f"💰 Сумма вывода: {w_amount}\n\n" f"✅ За отзыв начислен +1 Robux!\n" ), parse_mode=enums.ParseMode.HTML ) await message.reply( "🎉 Спасибо за отзыв! Тебе начислен <b>+1 Robux</b> на баланс.", parse_mode=enums.ParseMode.HTML, reply_markup=main_menu ) await clear_state(user_id) return # ------------------------------------------------ # 2) Состояние "awaiting_robux_amount" # ------------------------------------------------ if state == "awaiting_robux_amount": try: amount = int(text_in) if amount <= 0: await message.reply("⚠ Введите положительное число!") return except ValueError: await message.reply("⚠ Введите целое число (количество робуксов).") return user_balance = user_info["balance"] if amount > user_balance: await message.reply("❌ Недостаточно средств на балансе!") return if amount < MIN_WITHDRAW_BALANCE: await message.reply(f"❌ Минимальная сумма для вывода: {MIN_WITHDRAW_BALANCE}.") return await set_state(user_id, "awaiting_gamepass_link", str(amount)) await message.reply( f"Создайте Game Pass ровно на <b>{amount}</b> робуксов.\n\n" "После этого пришлите сюда ссылку на геймпасс, чтобы бот проверил цену.", parse_mode=enums.ParseMode.HTML, reply_markup=ReplyKeyboardRemove() ) return # ------------------------------------------------ # 3) Состояние "awaiting_gamepass_link" # ------------------------------------------------ if state == "awaiting_gamepass_link": amount_str = temp_data try: expected_amount = int(amount_str) except ValueError: await message.reply("Ошибка: некорректная сохранённая сумма. Начните заново /start") await clear_state(user_id) return gamepass_link = text_in.strip() gp_id = parse_gamepass_id(gamepass_link) if gp_id < 0: await message.reply( "⚠ Не удалось определить ID геймпаса из ссылки.\n" "Убедитесь, что формат ссылки типа:\n" "https://www.roblox.com/game-pass/123456" ) return gp_info = await get_gamepass_info(gp_id) if not gp_info: await message.reply("❌ Не удалось получить информацию о геймпассе. Попробуйте заново.") return real_price = gp_info.get("PriceInRobux", 0) if real_price != expected_amount: await message.reply( f"❌ Цена геймпаса = {real_price} robux, а ты вводил {expected_amount}!\n" "Проверь правильность, затем заново укажи сумму вывода." ) await set_state(user_id, "awaiting_robux_amount", "") return # создаём заявку async with aiosqlite.connect("database.db") as db: await db.execute(""" INSERT INTO pending_withdrawals (user_id, roblox_name, gamepass_link, status, currency, amount) VALUES (?,?,?,?,?,?) """, (user_id, "", gamepass_link, 'pending', 'robux', expected_amount)) await db.execute("UPDATE users SET balance=balance-? WHERE user_id=?", (expected_amount, user_id)) await db.execute("UPDATE users SET withdraw_count = withdraw_count + 1 WHERE user_id=?", (user_id,)) await db.execute(""" INSERT INTO withdraw_history (user_id, amount) VALUES (?,?) """, (user_id, expected_amount)) await db.commit() new_withdraw_id = 0 async with aiosqlite.connect("database.db") as db: cur = await db.execute("SELECT MAX(withdraw_id) FROM pending_withdrawals WHERE user_id=?", (user_id,)) row2 = await cur.fetchone() new_withdraw_id = row2[0] if (row2 and row2[0]) else 0 inline_kb = InlineKeyboardMarkup( [ [ InlineKeyboardButton( "Выполнить", callback_data=f"complete_withdraw:{new_withdraw_id}" ) ] ] ) await client.send_message( chat_id=ADMIN_CHAT_ID, text=( f"🔔 <b>Новая заявка на вывод [ROBLOX]</b>\n\n" f"👤 <b>Telegram ID:</b> <code>{user_id}</code>\n" f"🔗 <b>GamePass:</b> {gamepass_link}\n" f"💸 <b>Сумма вывода:</b> {expected_amount} Robux\n\n" f"Нажмите «Выполнить» после покупки геймпаса." ), reply_markup=inline_kb, parse_mode=enums.ParseMode.HTML ) await message.reply( f"✅ Заявка на вывод <b>{expected_amount}</b> Robux отправлена админу!\n" "Ожидай подтверждения и пруфов в личке.", parse_mode=enums.ParseMode.HTML, reply_markup=main_menu ) await clear_state(user_id) return # ------------------------------------------------ # 4) Состояние "awaiting_ttd_nick" (пример для TTD) # ------------------------------------------------ # ... (остальная логика) # ------------------------------------------------ # Обработка кнопок меню # ------------------------------------------------ if text_in == "ℹ Мой Профиль": balance = user_info["balance"] wcount = user_info["withdraw_count"] wsum = user_info["total_withdrawn"] uname = user_info["username"] await message.reply( f"📋 <b>Твой профиль:</b>\n\n" f"👤<b>Ник:</b> @{uname}\n" f"🆔<b>ID:</b> <code>{user_id}</code>\n" f"💰<b>Баланс (Robux):</b> {balance}\n" f"🔄<b>Выводов сделано:</b> {wcount}\n" f"💸<b>Всего выведено (Robux):</b> {wsum}", parse_mode=enums.ParseMode.HTML ) elif text_in == "💸 Заработать Робуксы": energy = user_info["energy"] energy_refill_ts = user_info["energy_refill_ts"] last_tap_ts = user_info["last_tap_ts"] or 0 current_time = time.time() if energy <= 0: if current_time >= energy_refill_ts and energy_refill_ts != 0: energy = START_ENERGY energy_refill_ts = 0 else: sec_left = int(energy_refill_ts - current_time) hours = sec_left // 3600 mins = (sec_left % 3600) // 60 await message.reply( f"😴 У тебя закончилась энергия.\n" f"⏳ Восстановится через <b>{hours} ч {mins} мин</b>.", parse_mode=enums.ParseMode.HTML ) return new_balance = user_info["balance"] + 1 energy -= 1 taps_count = user_info["taps_count"] + 1 referred_by = user_info["referred_by"] if referred_by != 0: async with aiosqlite.connect("database.db") as db: cur_ref = await db.execute("SELECT referral_commission FROM users WHERE user_id=?", (referred_by,)) ref_info = await cur_ref.fetchone() if ref_info: old_comm = float(ref_info[0]) new_comm = old_comm + 0.1 await db.execute( "UPDATE users SET referral_commission=? WHERE user_id=?", (new_comm, referred_by) ) await db.execute( "UPDATE referrals SET ref_taps=ref_taps+1 WHERE ref_user_id=?", (user_id,) ) if energy <= 0: energy_refill_ts = current_time + ENERGY_REFILL_HOURS * 3600 async with aiosqlite.connect("database.db") as db: await db.execute(""" UPDATE users SET balance=?, energy=?, energy_refill_ts=?, taps_count=?, last_tap_ts=? WHERE user_id=? """, (new_balance, energy, energy_refill_ts, taps_count, current_time, user_id)) await db.execute(""" UPDATE global_stats SET total_tapped=total_tapped+1 WHERE id=1 """) await db.commit() await message.reply( f"🪄 Ты получил <b>+1</b> робакс!\n" f"⚡ Осталось энергии: <b>{energy}</b> из {START_ENERGY}.\n\n" f"Когда энергия станет 0, она восстановится через {ENERGY_REFILL_HOURS} ч.", parse_mode=enums.ParseMode.HTML ) elif text_in == "🗣 Друзья": ref_link = f"https://t.me/{BOT_USERNAME}?start={user_id}" async with aiosqlite.connect("database.db") as db: cur_f = await db.execute(""" SELECT ref_user_id, ref_username, ref_taps FROM referrals WHERE user_id=? """, (user_id,)) friends_data = await cur_f.fetchall() cur_comm = await db.execute( "SELECT referral_commission FROM users WHERE user_id=?", (user_id,) ) row_u = await cur_comm.fetchone() ref_comm = row_u[0] if row_u else 0 text_friends = "👥 <b>Твои рефералы:</b>\n\n" if friends_data: for f in friends_data: rid, rname, rtaps = f text_friends += f"• @{rname} (ID: {rid}), натапал {rtaps} робуксов.\n" else: text_friends += "Пока нет рефералов.\n" text_friends += "\n" text_friends += f"💸 С рефералов накопилось: <b>{ref_comm:.2f}</b> робуксов (10%)." withdraw_button = InlineKeyboardButton("Вывести 10%", callback_data="withdraw_ref_commission") share_text = f"😎Зарабатывай робуксы с помощью бота: {ref_link}" encoded_share_text = urllib.parse.quote_plus(share_text) share_url = f"https://t.me/share/url?url={urllib.parse.quote_plus(ref_link)}&text={encoded_share_text}" share_button = InlineKeyboardButton("Поделиться", url=share_url) inline_kb = InlineKeyboardMarkup( [ [withdraw_button], [share_button] ] ) await message.reply( f"🔗 Твоя реф. ссылка:\n{ref_link}\n\n{text_friends}", reply_markup=inline_kb, parse_mode=enums.ParseMode.HTML, disable_web_page_preview=True ) elif text_in == "🦈 Вывести": currency_kb = InlineKeyboardMarkup( [ [InlineKeyboardButton("Robux", callback_data="choose_currency:robux")], [InlineKeyboardButton("💎 TTD GEMS", callback_data="choose_currency:ttd")] ] ) await message.reply( "Выбери, что хочешь вывести:", reply_markup=currency_kb ) elif text_in == "🌴 Пруфы": await message.reply( "📸 Посмотреть все пруфы можно тут:", reply_markup=proofs_inline ) elif text_in == "📊 Статистика": async with aiosqlite.connect("database.db") as db: cur_st = await db.execute("SELECT total_users, total_tapped, total_withdrawn FROM global_stats WHERE id=1") stats = await cur_st.fetchone() total_users = stats[0] total_tapped = stats[1] total_wd = stats[2] await message.reply( f"📊 <b>Статистика бота:</b>\n\n" f"👥 Пользователей: <b>{total_users}</b>\n" f"👆 Всего натапано: <b>{total_tapped}</b>\n" f"💸 Всего выведено (Robux): <b>{total_wd}</b>", parse_mode=enums.ParseMode.HTML ) elif text_in == "❓ FAQ": faq_text = ( "❓ <b>Частые вопросы:</b>\n\n" "1. <b>Мой Профиль</b> — посмотреть баланс и статистику.\n" "2. <b>💸 Заработать Робуксы</b> — получить +1 робакс (тратится 1 энергия).\n" "3. <b>🗣 Друзья</b> — реф.ссылка и доход с рефералов.\n" "4. <b>🦈 Вывести</b> — подать заявку на вывод (Robux или TTD).\n" "5. <b>🌴 Пруфы</b> — канал с выплатами.\n" "6. <b>📊 Статистика</b> — общая статистика бота.\n" "7. <b>❓ FAQ</b> — это меню." ) await message.reply(faq_text, parse_mode=enums.ParseMode.HTML) else: await message.reply("Выберите команду из меню или введите /start.") @app.on_callback_query(filters.regex(r"^choose_currency:(\w+)")) async def choose_currency_handler(client: Client, callback_query: CallbackQuery): user_id = callback_query.from_user.id currency = callback_query.matches[0].group(1) await callback_query.answer() if not await is_subscribed(client, user_id): await callback_query.answer("Сначала подпишитесь на канал!", show_alert=True) return user_info = await get_user(user_id) if not user_info: await callback_query.message.reply("Ошибка профиля, нажмите /start.") return user_balance = user_info["balance"] if currency == "robux": if user_balance < MIN_WITHDRAW_BALANCE: await callback_query.message.reply( f"Минимальная сумма для вывода: {MIN_WITHDRAW_BALANCE} робуксов!", parse_mode=enums.ParseMode.HTML ) return await set_state(user_id, "awaiting_robux_amount", "") await callback_query.message.reply( "💰 Введи количество робуксов, которое хочешь вывести:\n(Не больше твоего баланса!)", parse_mode=enums.ParseMode.HTML, reply_markup=ReplyKeyboardRemove() ) elif currency == "ttd": # ... логика вывода TTD pass @app.on_callback_query(filters.regex(r"^withdraw_ref_commission")) async def withdraw_ref_commission_handler(client: Client, cq: CallbackQuery): user_id = cq.from_user.id if not await is_subscribed(client, user_id): await cq.answer("Вы не подписаны на канал!", show_alert=True) return async with aiosqlite.connect("database.db") as db: cur = await db.execute("SELECT referral_commission FROM users WHERE user_id=?", (user_id,)) row = await cur.fetchone() if not row: await cq.answer("Ошибка: нет данных о пользователе.", show_alert=True) return commission = float(row[0]) if commission <= 0: await cq.answer("У вас нет накопленной комиссии.", show_alert=True) return await db.execute(""" UPDATE users SET balance=balance+?, referral_commission=0 WHERE user_id=? """, (commission, user_id)) await db.commit() await cq.answer("Реферальная комиссия выведена на баланс!") await client.send_message( user_id, f"Вы успешно перевели <b>{commission:.2f}</b> робуксов из реф. комиссии на основной баланс.", parse_mode=enums.ParseMode.HTML ) @app.on_callback_query(filters.regex(r"^complete_withdraw:(\d+)")) async def complete_withdraw_handler(client: Client, callback_query: CallbackQuery): withdraw_id = int(callback_query.matches[0].group(1)) admin_id = callback_query.from_user.id await set_state(admin_id, "admin_waiting_proofs", str(withdraw_id)) async with aiosqlite.connect("database.db") as db: await db.execute( "UPDATE pending_withdrawals SET status='processing' WHERE withdraw_id=?", (withdraw_id,) ) await db.commit() await callback_query.answer("Отправьте пруфы (фото/видео) боту в ЛС.") @app.on_message(filters.private & (filters.photo | filters.video)) async def admin_send_proof_handler(client: Client, message: Message): user_id = message.from_user.id state, temp_data = await get_state(user_id) if state == "admin_waiting_proofs": try: withdraw_id = int(temp_data) except ValueError: await message.reply("Ошибка: некорректный ID вывода.") await clear_state(user_id) return async with aiosqlite.connect("database.db") as db: cur = await db.execute(""" SELECT user_id, amount FROM pending_withdrawals WHERE withdraw_id=? AND status IN ('pending','processing') """, (withdraw_id,)) row = await cur.fetchone() if not row: await message.reply("Ошибка: заявка не найдена (или уже завершена)!") await clear_state(user_id) return target_user_id, amount = row # отправляем пруф пользователю if message.photo: sent_msg = await client.send_photo( chat_id=target_user_id, photo=message.photo.file_id, caption="🎉 Твой вывод выполнен! Пришли, пожалуйста, отзыв.", parse_mode=enums.ParseMode.HTML ) else: sent_msg = await client.send_video( chat_id=target_user_id, video=message.video.file_id, caption="🎉 Твой вывод выполнен! Пришли, пожалуйста, отзыв.", parse_mode=enums.ParseMode.HTML ) async with aiosqlite.connect("database.db") as db: await db.execute(""" UPDATE withdraw_history SET proof_message_id=? WHERE id=? """, (sent_msg.id, withdraw_id)) await db.execute(""" UPDATE pending_withdrawals SET status='completed' WHERE withdraw_id=? """, (withdraw_id,)) await db.execute(""" UPDATE users SET total_withdrawn = total_withdrawn + ? WHERE user_id=? """, (amount, target_user_id)) await db.execute(""" UPDATE global_stats SET total_withdrawn = total_withdrawn + ? WHERE id=1 """, (amount,)) await db.commit() # ставим пользователю состояние "ждём отзыв" await set_state(target_user_id, "waiting_for_review", str(withdraw_id)) # сообщаем админу await message.reply("Пруф отправлен пользователю! Ожидаем от него отзыв.") await clear_state(user_id) # ========================= # NEW: Команда /post — рассылка от админа # ========================= @app.on_message(filters.command("post") & filters.private) async def admin_broadcast_handler(client: Client, message: Message): # Проверяем, что отправитель — админ if message.from_user.id != ADMIN_CHAT_ID: await message.reply("❌ У вас нет прав для использования этой команды.") return # Извлекаем текст, который идёт после /post # Формат: /post Какой-то текст if len(message.command) < 2: await message.reply("Используйте формат: /post ТЕКСТ_СООБЩЕНИЯ") return broadcast_text = message.text.split(" ", 1)[1].strip() if not broadcast_text: await message.reply("Сообщение пустое, укажите текст после /post.") return # Получаем всех пользователей async with aiosqlite.connect("database.db") as db: cur = await db.execute("SELECT user_id FROM users") all_users = await cur.fetchall() # Рассылаем sent_count = 0 for row_u in all_users: uid = row_u[0] try: await client.send_message(uid, broadcast_text) sent_count += 1 # Если пользователей очень много, можно сделать небольшую паузу: # await asyncio.sleep(0.05) except Exception as e: logging.warning(f"Не удалось отправить сообщение user_id={uid}: {e}") await message.reply(f"✅ Сообщение отправлено {sent_count} пользователям.") # =========================== # Фоновая задача напоминаний # =========================== async def reminder_loop(): while True: try: now_ts = time.time() interval_sec = REMINDER_INTERVAL_HOURS * 3600 threshold_ts = now_ts - interval_sec async with aiosqlite.connect("database.db") as db: db.row_factory = aiosqlite.Row cur = await db.execute(""" SELECT user_id FROM users WHERE energy > 0 AND last_tap_ts < ? AND last_reminder_ts < ? """, (threshold_ts, threshold_ts)) rows = await cur.fetchall() for row in rows: uid = row["user_id"] try: await app.send_message( chat_id=uid, text=( "Привет!\n" "У тебя есть энергия для тапов, но ты давно не заходил!\n" "Не упускай возможность заработать робуксы 😎" ) ) except Exception as e: print(f"Не смог отправить напоминание пользователю {uid}: {e}") await db.execute( "UPDATE users SET last_reminder_ts=? WHERE user_id=?", (now_ts, uid) ) await db.commit() except Exception as e: print("Ошибка в reminder_loop:", e) await asyncio.sleep(3600) # =========================== # ИНИЦИАЛИЗАЦИЯ И ЗАПУСК # =========================== await init_db() await app.start() print("Bot started...") asyncio.create_task(reminder_loop()) await idle() await app.stop() if __name__ == "__main__": asyncio.run(main())
Новая функция fetch_roblox_data(endpoint, params=None)
— пример того, как можно ходить на эндпоинты Roblox с куки. Внутри неё есть простое логирование (через logging
), выводящее запрос и короткий ответ в консоль. По желанию можно усложнить (сохранять логи в БД или писать в файл).
Логика вызова fetch_roblox_data
не встроена в существующие хендлеры, потому что неясно, какие именно новые данные вы хотите получать. Вы можете самостоятельно вызвать её в любом месте кода, например:
pythondata = await fetch_roblox_data("/v1/users/authenticated") if data: # что-то делаем
Команда /post "сообщение"
:
admin_broadcast_handler
(см. комментарий # NEW: Команда /post
).ADMIN_CHAT_ID
, бот отвечает «нет прав».users
.Так вы сможете быстро «рассылать объявления» всем, кто когда-либо запустил бота.
❌ Не удалось получить информацию о геймпассе. Попробуйте заново., в логах ничего нету, попробуй использовать ROBLOX_COOKIE = "Ваш_куки_в_формате__ROBLOSECURITY=..."
Начиная с 2023 года старый эндпоинт https://api.roblox.com/marketplace/game-pass-product-info
часто отказывает в выдаче данных без авторизации (или вовсе перестал корректно работать). Чтобы получать данные об игре или геймпассе, Roblox требует .ROBLOSECURITY
-куки и иногда ещё CSRF-токен.
Ниже — два основных способа, как можно в вашем боте (на aiohttp
) корректно загрузить информацию о Game Pass, используя куки:
get_gamepass_info
(старый endpoint), добавив CookieЕсли вы хотите оставить тот же самый старый URL .../game-pass-product-info?gamePassId=...
, но добавить к нему авторизацию через .ROBLOSECURITY
, попробуйте следующее:
pythonROBLOX_COOKIE = ".ROBLOSECURITY=ВАШ_КУКИ_ТУТ" # укажите правильное значение! async def get_gamepass_info(gamepass_id: int) -> dict: """ Запрос к старому API (https://api.roblox.com/marketplace/game-pass-product-info), но с установкой .ROBLOSECURITY-куки. """ url = f"https://api.roblox.com/marketplace/game-pass-product-info?gamePassId={gamepass_id}" # Заголовки: можно добавить User-Agent, куки, при необходимости X-CSRF-TOKEN headers = { "Cookie": ROBLOX_COOKIE, "User-Agent": "Mozilla/5.0" } async with aiohttp.ClientSession() as session: try: async with session.get(url, headers=headers) as resp: status = resp.status data = await resp.text() # Для отладки можно посмотреть "сырые" данные print(f"[get_gamepass_info] status={status}, response={data[:200]}...") if status == 200: # Парсим JSON return await resp.json() else: # Если вдруг 403, 401, 400 и т.п. — возвращаем None return None except Exception as e: print(f"[get_gamepass_info] Ошибка: {e}") return None
".ROBLOSECURITY="
(без лишних пробелов), и, разумеется, должен быть активным.X-CSRF-TOKEN
, а потом повторить запрос уже с этим заголовком. Если у вас всё время status=403
, придётся реализовать логику «поймать 403 → перезапросить X-CSRF → повторить».apis.roblox.com
)Официальная документация Roblox рекомендует для различных объектов (game-pass, badges и т.д.) использовать apis.roblox.com
или economy.roblox.com
. Например, сейчас для Game Pass могут работать такие пути:
GET https://apis.roblox.com/game-passes/v1/game-passes/{gamePassId}
Либо можно пробовать economy.roblox.com/v2/assets
, но там нужен assetId, а у GamePass assetId часто не совпадает напрямую с gamePassId.
Ниже — пример функции, где мы используем более «современный» эндпоинт и ту же куки:
pythonROBLOX_COOKIE = ".ROBLOSECURITY=ВАШ_КУКИ_ТУТ" async def get_gamepass_info_new(gamepass_id: int) -> dict: """ Пример запроса к новому эндпоинту для GamePass. (Путь может меняться; здесь "https://apis.roblox.com/game-passes/v1/game-passes/<id>") """ url = f"https://apis.roblox.com/game-passes/v1/game-passes/{gamepass_id}" headers = { "Cookie": ROBLOX_COOKIE, "User-Agent": "Mozilla/5.0" } async with aiohttp.ClientSession() as session: try: async with session.get(url, headers=headers) as resp: txt = await resp.text() print(f"[get_gamepass_info_new] status={resp.status}, resp={txt[:200]}...") if resp.status == 200: return await resp.json() else: return None except Exception as e: print(f"[get_gamepass_info_new] Ошибка: {e}") return None
Потом, в коде бота, вы заменяете все вызовы get_gamepass_info(...)
на get_gamepass_info_new(...)
. В результате, если у вас есть доступ и куки верные, PriceInRobux
или аналогичную информацию можно получить из вернувшегося JSON.
Учтите, что Roblox может возвращать 403 Forbidden, если ваш аккаунт/куки не имеют достаточно прав для просмотра этого объекта. Иногда Game Pass скрыт, или вы не владелец, или Cookie «слишком свежая». Тогда нужно либо авторизовываться под аккаунтом, который владеет конкретными GamePass, либо парсить HTML-страницу (нежелательно), либо находить подходящий публичный API-метод.
Логируйте resp.text()
или хотя бы первые 200–300 символов. Если в ответе есть упоминание «Captcha», «Access Denied» или «Token Validation Failed», значит нужно донастроить заголовки / токены.
Если часто вылетает 403 — придётся добавлять механику получения X-CSRF-TOKEN
.
session.post()
или session.get()
без токена,X-CSRF-TOKEN: <что-то>
,headers["X-CSRF-TOKEN"] = полученный_токен
.Если в логе «ничего нет», значит, возможно, у вас не вызывается ваша функция или у вас где-то стоит try/except
без вывода ошибок. Убедитесь, что ваш print(...)
или logging.info(...)
действительно вызывается, и что функция get_gamepass_info(...)
/get_gamepass_info_new(...)
не прерывается раньше времени.
Roblox может блокировать аккаунты «бесконечными запросами». Если вы слишком часто стучитесь в API, включите кэширование или задержки, чтобы не вызвать подозрений.
PriceInRobux
).apis.roblox.com
), где также нужна Cookie (а иногда и CSRF).делай это Более надёжно — перейти на актуальный эндпоинт (apis.roblox.com), где также нужна Cookie (а иногда и CSRF).
Ниже приведён пример того, как перейти на более новый эндпоинт для Game Pass и использовать apis.roblox.com
с .ROBLOSECURITY
-куки и получением X-CSRF-TOKEN
.
Данный пример можно взять за основу и встроить в ваш бот вместо старой функции get_gamepass_info
. Логика такая:
X-CSRF-TOKEN
.
403 Forbidden
и передаст в заголовке новый X-CSRF-TOKEN
, мы его сохраняем.X-CSRF-TOKEN
в заголовки.pythonimport aiohttp import logging ROBLOX_COOKIE = ".ROBLOSECURITY=ВАШ_КУКИ_ТУТ" # Обязательно укажите нужное значение! # Пример эндпоинта (актуален на момент 2023-2025, но может меняться): GAMEPASS_URL_TEMPLATE = "https://apis.roblox.com/game-passes/v1/game-passes/{gamepass_id}" async def get_gamepass_info_new(gamepass_id: int) -> dict: """ Обращается к новому эндпоинту: GET https://apis.roblox.com/game-passes/v1/game-passes/<gamepass_id> Возвращает словарь с данными о GamePass, если удалось получить. Если не удалось (403, 404 и т.п.), вернёт None. """ url = GAMEPASS_URL_TEMPLATE.format(gamepass_id=gamepass_id) headers = { "Cookie": ROBLOX_COOKIE, "User-Agent": "Mozilla/5.0" # X-CSRF-TOKEN добавим позже при необходимости } async with aiohttp.ClientSession() as session: # 1) Пробуем без X-CSRF-TOKEN try: resp_data, new_csrf = await _roblox_get(session, url, headers) if resp_data is not None: # Если уже получилось (или 200, или хоть какой-то JSON), возвращаем return resp_data if new_csrf: # 2) Повторяем запрос, если есть токен headers["X-CSRF-TOKEN"] = new_csrf resp_data, _ = await _roblox_get(session, url, headers) return resp_data except Exception as e: logging.error(f"[get_gamepass_info_new] Ошибка: {e}") return None return None async def _roblox_get(session: aiohttp.ClientSession, url: str, headers: dict) -> tuple: """ Вспомогательная функция: делает GET-запрос к Roblox API, возвращает (data, new_csrf_token). - `data` = dict или None - `new_csrf_token` = строка или None """ new_csrf_token = None resp_data = None async with session.get(url, headers=headers) as resp: status = resp.status text_body = await resp.text() logging.info(f"GET {url} => status={status}, text[:200]={text_body[:200]!r}") # Поищем заголовок X-CSRF-TOKEN (Roblox присылает его при 403) # Иногда может прийти и при 200, 401, 403, проверять нужно всегда new_csrf_token = resp.headers.get("x-csrf-token", None) if status == 200: # Пытаемся распарсить JSON try: resp_data = await resp.json() except Exception as e: logging.warning(f"Не удалось распарсить JSON: {e}") resp_data = None else: # Если 403, 404 и т.п., тоже попробуем json try: resp_data_json = await resp.json() resp_data = resp_data_json except: resp_data = None return resp_data, new_csrf_token
После того как вы добавите этот код в ваш проект, замените старые вызовы вроде:
pythongp_info = await get_gamepass_info(gamepass_id) if not gp_info: ...
на:
pythongp_info = await get_gamepass_info_new(gamepass_id) if not gp_info: # Обработка ошибки ...
В возвращаемом JSON от apis.roblox.com/game-passes/...
вы сможете найти нужные поля, например, "price"
, "name"
, "description"
и т.д. (зависит от актуальной структуры ответа).
Учтите, что у нового эндпоинта поле цены может называться иначе (вместо
PriceInRobux
из старогоapi.roblox.com
). Смотрите, что реально возвращается вresp_data
. Лог пишется черезlogging.info
, в консоли вы увидите первые 200 символов ответа.
status=404
и в resp_data
нет нормальных полей, возможно, геймпасса с таким ID не существует (или вам не дают доступ к нему).status=403
и даже после повторной попытки с X-CSRF-TOKEN
всё так же, значит:
.ROBLOSECURITY
неактуальны / просрочены,resp_data
не содержит price
(или аналогичного поля), посмотрите, какие конкретно поля приходят. Возможно, Roblox не возвращает публично цену (нужно быть владельцем или иметь специальный доступ).X-CSRF-TOKEN
(который вы получаете впервые) и потом использовать его во всех будущих запросах, а не заново запрашивать в каждом вызове. Иначе Roblox может вас временно заблокировать за «слишком часто получаете CSRF-токен».В итоге, данный способ «более надёжен», чем старый api.roblox.com/marketplace/game-pass-product-info
, но требует аккуратной настройки авторизации и, возможно, прав на просмотр.
напиши весь скрипт
Ниже приведён полный пример скрипта на Pyrogram с учётом:
https://apis.roblox.com/game-passes/v1/game-passes/<gamePassId>
для проверки GamePass.X-CSRF-TOKEN
(если Roblox попросит).logging
).Обратите внимание, что Roblox может не возвращать цену (price) для любых GamePass, доступ к которым у аккаунта (куки) отсутствует. Если у вас всегда возвращается 403/404, значит куки просрочены/неподходят, или GamePass скрыт.
Сохраните это в файле, скажем, main.py
, вставьте ваш реальный куки .ROBLOSECURITY=...
в переменную ROBLOX_COOKIE
, установите зависимости (pip install pyrogram aiohttp aiosqlite
) и запустите.
pythonimport asyncio import time import urllib.parse import re import aiohttp import logging from pyrogram import Client, filters, idle, enums from pyrogram.types import ( Message, InlineKeyboardButton, InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, CallbackQuery ) import aiosqlite # ============================= # НАСТРОЙКИ ЛОГИРОВАНИЯ # ============================= logging.basicConfig( level=logging.INFO, format='[%(asctime)s] %(levelname)s: %(message)s', datefmt='%H:%M:%S' ) # ============================= # ПАРАМЕТРЫ БОТА И НАСТРОЙКИ # ============================= API_ID = '24142370' API_HASH = '5499eef960820d34a871c67024d89819' BOT_TOKEN = "7380283669:AAH2Do7p-bxOjnVn4c3Y2tnHh4DdXEw4egw" # Канал, где проверяем подписку (в формате -100xxxxxxxxxx) MAIN_CHANNEL_ID = -1002380592722 BOT_USERNAME = "RobuxTapBot" START_ENERGY = 12 ENERGY_REFILL_HOURS = 14 # Минимальный вывод (только для Robux) MIN_WITHDRAW_BALANCE = 4 # Курс конверсии Robux -> TTD GEMS (1 Robux = 70 GEMS) ROBUX_TO_TTD_RATE = 70 # Предположим, ID админа ADMIN_CHAT_ID = 8085926304 # Каждые 4 часа бот напоминает, если у пользователя есть энергия и он давно не заходил REMINDER_INTERVAL_HOURS = 4 # Ваша кука .ROBLOSECURITY (обязательно укажите правильное значение) ROBLOX_COOKIE = ".ROBLOSECURITY=ВАШ_КУКИ_ТУТ" # Шаблон URL для GamePass в новом API GAMEPASS_URL_TEMPLATE = "https://apis.roblox.com/game-passes/v1/game-passes/{gamepass_id}" # ============================= # КНОПКИ/МЕНЮ # ============================= main_menu = ReplyKeyboardMarkup( [ ["ℹ Мой Профиль"], ["💸 Заработать Робуксы", "🗣 Друзья"], ["🦈 Вывести", "🌴 Пруфы"], ["📊 Статистика", "❓ FAQ"] ], resize_keyboard=True ) proofs_inline = InlineKeyboardMarkup( [ [ InlineKeyboardButton( "Канал с пруфами и отзывами", url="https://t.me/proofs_vivods" ) ] ] ) MENU_COMMANDS = [ "ℹ Мой Профиль", "💸 Заработать Робуксы", "🗣 Друзья", "🦈 Вывести", "🌴 Пруфы", "📊 Статистика", "❓ FAQ", "/start" ] # ============================= # ФУНКЦИИ ДЛЯ РАБОТЫ С GAME PASS # ============================= def parse_gamepass_id(link: str) -> int: """ Ищем gamePassId в ссылке вида https://www.roblox.com/game-pass/123456... Вернём -1, если не нашли. """ match = re.search(r'game-pass/(\d+)', link) if match: return int(match.group(1)) return -1 async def get_gamepass_info(gamepass_id: int) -> dict: """ Используем новый эндпоинт: GET https://apis.roblox.com/game-passes/v1/game-passes/<gamePassId>. Для доступа нужен .ROBLOSECURITY-куки (ROBLOX_COOKIE). Также при первом 403 ловим X-CSRF-TOKEN из заголовков и повторяем запрос. Возвращаем dict с информацией о GamePass (например, {"id":..., "price":..., ...}), или None, если не удалось (403, 404, и т.д.). """ url = GAMEPASS_URL_TEMPLATE.format(gamepass_id=gamepass_id) # Первичная "голова" без X-CSRF headers = { "Cookie": ROBLOX_COOKIE, "User-Agent": "Mozilla/5.0" } async with aiohttp.ClientSession() as session: # 1) Пробуем сходу data, csrf_token = await roblox_get_request(session, url, headers) if data is not None: # Если уже вернулось что-то (даже если status=2xx), возвращаем return data if csrf_token: # 2) Пробуем повторно с X-CSRF-TOKEN headers["X-CSRF-TOKEN"] = csrf_token data2, _ = await roblox_get_request(session, url, headers) return data2 return None async def roblox_get_request(session: aiohttp.ClientSession, url: str, headers: dict) -> (dict, str): """ Вспомогательная функция: - Выполняет GET-запрос - Пытается вернуть (data, new_csrf_token) где data = dict или None, если не смогли распарсить / статус не 2xx. new_csrf_token = строка или None (если Roblox вернул в заголовках) """ new_csrf_token = None resp_data = None try: async with session.get(url, headers=headers) as resp: status = resp.status text_body = await resp.text() # Логируем logging.info(f"[roblox_get_request] GET {url}, status={status}, body[:200]={text_body[:200]!r}") # Смотрим, не прислал ли Roblox X-CSRF-TOKEN new_csrf_token = resp.headers.get("x-csrf-token", None) # Если 2xx — пробуем распарсить if 200 <= status < 300: try: resp_data = await resp.json() except Exception as e: logging.warning(f"Не удалось распарсить JSON при 2xx: {e}") resp_data = None else: # При 403/404/401 и т.п. тоже иногда есть json # Если хотим посмотреть, что внутри - пробуем json: try: resp_data_json = await resp.json() resp_data = resp_data_json except: resp_data = None except Exception as e: logging.error(f"[roblox_get_request] Ошибка запроса: {e}") resp_data = None # Если статус был не OK (напр. 403/404), resp_data может оказаться {'errors': [...]} — # проверяйте при использовании. # Если нужно только при 200, придётся дополнительно проверять "если status == 200: ..." return resp_data, new_csrf_token # ============================= # ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ/БД/БОТ # ============================= async def init_db(): async with aiosqlite.connect("database.db") as db: # Таблица пользователей await db.execute(""" CREATE TABLE IF NOT EXISTS users ( user_id INTEGER PRIMARY KEY, username TEXT, balance INTEGER DEFAULT 0, energy INTEGER DEFAULT 10, energy_refill_ts REAL DEFAULT 0, total_withdrawn INTEGER DEFAULT 0, withdraw_count INTEGER DEFAULT 0, referred_by INTEGER DEFAULT 0, taps_count INTEGER DEFAULT 0, referral_commission REAL DEFAULT 0, total_ref_bonus REAL DEFAULT 0 ) """) try: await db.execute("ALTER TABLE users ADD COLUMN last_tap_ts REAL DEFAULT 0") except: pass try: await db.execute("ALTER TABLE users ADD COLUMN last_reminder_ts REAL DEFAULT 0") except: pass # Таблица состояний (FSM) await db.execute(""" CREATE TABLE IF NOT EXISTS states ( user_id INTEGER PRIMARY KEY, state TEXT DEFAULT '', temp_data TEXT DEFAULT '' ) """) # Глобальная статистика await db.execute(""" CREATE TABLE IF NOT EXISTS global_stats ( id INTEGER PRIMARY KEY, total_users INTEGER DEFAULT 0, total_tapped INTEGER DEFAULT 0, total_withdrawn INTEGER DEFAULT 0 ) """) await db.execute(""" INSERT OR IGNORE INTO global_stats (id, total_users, total_tapped, total_withdrawn) VALUES (1, 0, 0, 0) """) # Таблица рефералов await db.execute(""" CREATE TABLE IF NOT EXISTS referrals ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER, ref_user_id INTEGER, ref_username TEXT, ref_taps INTEGER DEFAULT 0 ) """) # Заявки на вывод await db.execute(""" CREATE TABLE IF NOT EXISTS pending_withdrawals ( withdraw_id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER, roblox_name TEXT, gamepass_link TEXT, status TEXT DEFAULT 'pending', currency TEXT DEFAULT 'robux', amount INTEGER DEFAULT 0, ttd_nick TEXT DEFAULT '' ) """) # История выводов await db.execute(""" CREATE TABLE IF NOT EXISTS withdraw_history ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER, amount INTEGER, proof_message_id INTEGER DEFAULT 0, review TEXT DEFAULT '' ) """) await db.commit() async def get_user(user_id: int): async with aiosqlite.connect("database.db") as db: db.row_factory = aiosqlite.Row cur = await db.execute("SELECT * FROM users WHERE user_id = ?", (user_id,)) return await cur.fetchone() async def add_user(user_id: int, username: str, referred_by=0): async with aiosqlite.connect("database.db") as db: cur = await db.execute("SELECT user_id FROM users WHERE user_id = ?", (user_id,)) existing = await cur.fetchone() if existing: return await db.execute("UPDATE global_stats SET total_users = total_users + 1 WHERE id = 1") await db.execute(""" INSERT INTO users (user_id, username, balance, energy, energy_refill_ts, referred_by, last_tap_ts, last_reminder_ts) VALUES (?,?,?,?,?,?,?,?) """, (user_id, username, 0, START_ENERGY, 0, referred_by, 0, 0)) if referred_by != 0: # Добавляем запись о реферале await db.execute(""" INSERT INTO referrals (user_id, ref_user_id, ref_username) VALUES (?,?,?) """, (referred_by, user_id, username)) # Пример бонуса рефереру await db.execute("UPDATE users SET balance = balance + 3 WHERE user_id = ?", (referred_by,)) # Пример бонуса рефералу await db.execute("UPDATE users SET energy = energy + 2 WHERE user_id = ?", (user_id,)) await db.commit() async def update_username(user_id: int, username: str): async with aiosqlite.connect("database.db") as db: await db.execute("UPDATE users SET username=? WHERE user_id=?", (username, user_id)) await db.commit() async def is_subscribed(client: Client, user_id: int) -> bool: try: member = await client.get_chat_member(MAIN_CHANNEL_ID, user_id) return (member.status in ( enums.ChatMemberStatus.OWNER, enums.ChatMemberStatus.ADMINISTRATOR, enums.ChatMemberStatus.MEMBER )) except: return False async def set_state(user_id: int, state: str, temp_data=""): async with aiosqlite.connect("database.db") as db: await db.execute(""" INSERT INTO states (user_id, state, temp_data) VALUES (?,?,?) ON CONFLICT(user_id) DO UPDATE SET state=excluded.state, temp_data=excluded.temp_data """, (user_id, state, temp_data)) await db.commit() async def get_state(user_id: int): async with aiosqlite.connect("database.db") as db: cur = await db.execute("SELECT state, temp_data FROM states WHERE user_id=?", (user_id,)) row = await cur.fetchone() if row: return row[0], row[1] else: return "", "" async def clear_state(user_id: int): await set_state(user_id, "", "") async def ensure_subscribed(client: Client, user_id: int, chat_id: int) -> bool: if await is_subscribed(client, user_id): return True else: text = ( "⚠ <b>Для использования бота</b> необходимо подписаться на наш канал!\n\n" "👉 <a href='https://t.me/RobuxTapNews'>Перейти в канал</a>\n\n" "После подписки нажмите кнопку «Проверить»." ) check_sub_kb = InlineKeyboardMarkup( [[InlineKeyboardButton("✅ Проверить подписку", callback_data="check_sub_again")]] ) await client.send_message( chat_id=chat_id, text=text, parse_mode=enums.ParseMode.HTML, disable_web_page_preview=True, reply_markup=check_sub_kb ) return False # ============================= # ОСНОВНОЙ КОД БОТА # ============================= async def main(): app = Client("robux_tap_bot", api_id=API_ID, api_hash=API_HASH, bot_token=BOT_TOKEN) # Инициализация БД await init_db() # --------------------------- # Хендлер /start (в ЛС) # --------------------------- @app.on_message(filters.command("start") & filters.private) async def start_private(client: Client, message: Message): user_id = message.from_user.id username = message.from_user.username or f"user_{user_id}" # Проверяем рефералку ref_id = 0 if len(message.command) > 1: try: ref_id = int(message.command[1]) except: ref_id = 0 # Добавляем в БД, если нет await add_user(user_id, username, ref_id) # Проверяем подписку if await is_subscribed(client, user_id): row = await get_user(user_id) balance = row["balance"] if row else 0 text = ( f"👋<b>Привет, {username}!</b>\n\n" f"На твоём балансе: <b>{balance}</b> робуксов.\n\n" f"💡Чтобы быстрее заработать, приглашай друзей!\n" f"Получай +5 робуксов за каждого.\n\n" f"Твоя реф. ссылка:\n" f"<a href='https://t.me/{BOT_USERNAME}?start={user_id}'>https://t.me/{BOT_USERNAME}?start={user_id}</a>" ) await message.reply( text, parse_mode=enums.ParseMode.HTML, reply_markup=main_menu, disable_web_page_preview=True ) else: text = ( "🔒 <b>Чтобы пользоваться ботом, подпишись на наш канал</b>\n\n" "👉 <a href='https://t.me/RobuxTapNews'>Перейти в канал</a>\n\n" "После подписки нажми кнопку ниже, чтобы бот проверил." ) sub_kb = InlineKeyboardMarkup( [[InlineKeyboardButton("✅ Проверить подписку", callback_data="check_sub_again")]] ) await message.reply( text, parse_mode=enums.ParseMode.HTML, disable_web_page_preview=True, reply_markup=sub_kb ) # --------------------------- # Хендлер /start в группе # --------------------------- @app.on_message(filters.command("start") & ~filters.private) async def start_in_group(_, message: Message): await message.reply("👋 Я работаю только в ЛС. Напишите мне в личку!") # --------------------------- # Кнопка «Проверить подписку» # --------------------------- @app.on_callback_query(filters.regex("^check_sub_again")) async def callback_check_sub_again(client: Client, cq: CallbackQuery): user_id = cq.from_user.id if await is_subscribed(client, user_id): await cq.answer("✅ Подписка подтверждена!", show_alert=True) row = await get_user(user_id) if row: balance = row["balance"] username = row["username"] else: balance = 0 username = cq.from_user.username or str(user_id) text = ( f"👋<b>Привет, {username}!</b>\n\n" f"Баланс: <b>{balance}</b> робуксов.\n\n" f"Приглашай друзей и зарабатывай ещё!\n\n" f"Реф. ссылка:\n" f"<a href='https://t.me/{BOT_USERNAME}?start={user_id}'>https://t.me/{BOT_USERNAME}?start={user_id}</a>" ) await client.send_message( chat_id=user_id, text=text, parse_mode=enums.ParseMode.HTML, reply_markup=main_menu, disable_web_page_preview=True ) else: await cq.answer("❌ Вы всё ещё не подписаны!", show_alert=True) # --------------------------- # Обработка сообщений в ЛС (FSM) # --------------------------- @app.on_message(filters.private & filters.text) async def menu_handler(client: Client, message: Message): user_id = message.from_user.id text_in = message.text.strip() # Убеждаемся, что пользователь в БД row = await get_user(user_id) if not row: username = message.from_user.username or f"user_{user_id}" await add_user(user_id, username, 0) # Проверяем подписку if not await ensure_subscribed(client, user_id, message.chat.id): return user_info = await get_user(user_id) if not user_info: await message.reply("Ошибка в профиле. Нажмите /start.") return current_name = message.from_user.username or f"user_{user_id}" if current_name != user_info["username"]: await update_username(user_id, current_name) state, temp_data = await get_state(user_id) # Если пользователь в одном из состояний, но нажал кнопку из меню — сброс if state in [ "waiting_for_review", "awaiting_gamepass_link", "awaiting_robux_amount", "awaiting_ttd_nick" ] and text_in in MENU_COMMANDS: await clear_state(user_id) state, temp_data = await get_state(user_id) # ======================= # 1) waiting_for_review # ======================= if state == "waiting_for_review": if not temp_data: await message.reply("Ошибка: нет ID вывода (temp_data пустое).") await clear_state(user_id) return try: withdraw_id = int(temp_data) except ValueError: await message.reply("Ошибка: temp_data не является корректным числом!") await clear_state(user_id) return review_text = message.text or "" async with aiosqlite.connect("database.db") as db: await db.execute( "UPDATE withdraw_history SET review=? WHERE id=?", (review_text, withdraw_id) ) cur = await db.execute(""" SELECT proof_message_id, user_id, amount FROM withdraw_history WHERE id=? """, (withdraw_id,)) row_w = await cur.fetchone() if not row_w: await message.reply("Не найдена запись о выводе (withdraw_history).") await clear_state(user_id) return proof_msg_id, target_user_id, w_amount = row_w # Узнаём username того, кому вывод cur_u = await db.execute("SELECT username FROM users WHERE user_id=?", (target_user_id,)) user_row = await cur_u.fetchone() username_db = user_row[0] if (user_row and user_row[0]) else str(target_user_id) # Начисляем +1 робукс за отзыв await db.execute("UPDATE users SET balance = balance + 1 WHERE user_id=?", (target_user_id,)) await db.commit() # Отправляем отзыв в канал (прим. канал для отзывов) channel_for_reviews = -1002434602961 # Пересылаем сам отзыв (текст) try: await client.forward_messages( chat_id=channel_for_reviews, from_chat_id=message.chat.id, message_ids=message.id ) except Exception as e: logging.warning(f"Ошибка при пересылке отзыва: {e}") # Копируем фото/видео пруфа try: await client.copy_message( chat_id=channel_for_reviews, from_chat_id=target_user_id, message_id=proof_msg_id ) except Exception as e: logging.warning(f"Ошибка при копировании пруфа: {e}") # Доп сообщение await client.send_message( chat_id=channel_for_reviews, text=( f"❇ <b>Отзыв от @{username_db}</b>\n" f"🔗 UserID: {target_user_id}\n" f"💰 Сумма вывода: {w_amount}\n\n" f"✅ За отзыв начислен +1 Robux!\n" ), parse_mode=enums.ParseMode.HTML ) await message.reply( "🎉 Спасибо за отзыв! Тебе начислен <b>+1 Robux</b> на баланс.", parse_mode=enums.ParseMode.HTML, reply_markup=main_menu ) await clear_state(user_id) return # ======================= # 2) awaiting_robux_amount # ======================= if state == "awaiting_robux_amount": try: amount = int(text_in) if amount <= 0: await message.reply("⚠ Введите положительное число!") return except ValueError: await message.reply("⚠ Введите целое число (количество робуксов).") return user_balance = user_info["balance"] if amount > user_balance: await message.reply("❌ Недостаточно средств на балансе!") return if amount < MIN_WITHDRAW_BALANCE: await message.reply(f"❌ Минимальная сумма для вывода: {MIN_WITHDRAW_BALANCE}.") return await set_state(user_id, "awaiting_gamepass_link", str(amount)) await message.reply( f"Создайте Game Pass ровно на <b>{amount}</b> робуксов.\n\n" "После этого пришлите сюда ссылку на геймпасс, чтобы бот проверил цену.", parse_mode=enums.ParseMode.HTML, reply_markup=ReplyKeyboardRemove() ) return # ======================= # 3) awaiting_gamepass_link # ======================= if state == "awaiting_gamepass_link": amount_str = temp_data try: expected_amount = int(amount_str) except ValueError: await message.reply("Ошибка: некорректная сохранённая сумма. Начните заново /start") await clear_state(user_id) return gamepass_link = text_in.strip() gp_id = parse_gamepass_id(gamepass_link) if gp_id < 0: await message.reply( "⚠ Не удалось определить ID геймпаса из ссылки.\n" "Убедитесь, что формат:\n" "https://www.roblox.com/game-pass/123456" ) return # Запрашиваем через новый эндпоинт gp_info = await get_gamepass_info(gp_id) if not gp_info: await message.reply( "❌ Не удалось получить информацию о геймпассе (403 или 404). Попробуйте заново." ) return # Посмотрите в gp_info, какое поле отвечает за цену # Пример: gp_info.get("price") или gp_info["price"] real_price = gp_info.get("price", 0) # Если вдруг в ответе нет price - возможно у аккаунта нет прав. if real_price != expected_amount: await message.reply( f"❌ Цена геймпаса = {real_price} robux, а вы ввели {expected_amount}!\n" "Проверьте правильность, затем заново укажите сумму вывода." ) await set_state(user_id, "awaiting_robux_amount", "") return # Если всё ок — создаём заявку async with aiosqlite.connect("database.db") as db: await db.execute(""" INSERT INTO pending_withdrawals (user_id, roblox_name, gamepass_link, status, currency, amount) VALUES (?,?,?,?,?,?) """, (user_id, "", gamepass_link, 'pending', 'robux', expected_amount)) await db.execute("UPDATE users SET balance=balance-? WHERE user_id=?", (expected_amount, user_id)) await db.execute("UPDATE users SET withdraw_count = withdraw_count + 1 WHERE user_id=?", (user_id,)) await db.execute(""" INSERT INTO withdraw_history (user_id, amount) VALUES (?,?) """, (user_id, expected_amount)) await db.commit() # Узнаём ID заявки new_withdraw_id = 0 async with aiosqlite.connect("database.db") as db: cur = await db.execute("SELECT MAX(withdraw_id) FROM pending_withdrawals WHERE user_id=?", (user_id,)) row2 = await cur.fetchone() new_withdraw_id = row2[0] if (row2 and row2[0]) else 0 inline_kb = InlineKeyboardMarkup( [ [ InlineKeyboardButton( "Выполнить", callback_data=f"complete_withdraw:{new_withdraw_id}" ) ] ] ) await client.send_message( chat_id=ADMIN_CHAT_ID, text=( f"🔔 <b>Новая заявка на вывод [ROBLOX]</b>\n\n" f"👤 <b>Telegram ID:</b> <code>{user_id}</code>\n" f"🔗 <b>GamePass:</b> {gamepass_link}\n" f"💸 <b>Сумма вывода:</b> {expected_amount} Robux\n\n" f"Нажмите «Выполнить» после покупки геймпаса." ), reply_markup=inline_kb, parse_mode=enums.ParseMode.HTML ) await message.reply( f"✅ Заявка на вывод <b>{expected_amount}</b> Robux отправлена админу!\n" "Ожидайте подтверждения и пруфов в личке.", parse_mode=enums.ParseMode.HTML, reply_markup=main_menu ) await clear_state(user_id) return # ======================= # 4) awaiting_ttd_nick (пример, если нужно) # ======================= # (Здесь логика для TTD, если используете) # ======================= # Обработка основных кнопок меню # ======================= if text_in == "ℹ Мой Профиль": balance = user_info["balance"] wcount = user_info["withdraw_count"] wsum = user_info["total_withdrawn"] uname = user_info["username"] await message.reply( f"📋 <b>Твой профиль:</b>\n\n" f"👤<b>Ник:</b> @{uname}\n" f"🆔<b>ID:</b> <code>{user_id}</code>\n" f"💰<b>Баланс (Robux):</b> {balance}\n" f"🔄<b>Выводов сделано:</b> {wcount}\n" f"💸<b>Всего выведено (Robux):</b> {wsum}", parse_mode=enums.ParseMode.HTML ) elif text_in == "💸 Заработать Робуксы": energy = user_info["energy"] energy_refill_ts = user_info["energy_refill_ts"] last_tap_ts = user_info["last_tap_ts"] or 0 current_time = time.time() if energy <= 0: if current_time >= energy_refill_ts and energy_refill_ts != 0: energy = START_ENERGY energy_refill_ts = 0 else: sec_left = int(energy_refill_ts - current_time) hours = sec_left // 3600 mins = (sec_left % 3600) // 60 await message.reply( f"😴 У тебя закончилась энергия.\n" f"⏳ Восстановится через <b>{hours} ч {mins} мин</b>.", parse_mode=enums.ParseMode.HTML ) return new_balance = user_info["balance"] + 1 energy -= 1 taps_count = user_info["taps_count"] + 1 referred_by = user_info["referred_by"] if referred_by != 0: async with aiosqlite.connect("database.db") as db: cur_ref = await db.execute("SELECT referral_commission FROM users WHERE user_id=?", (referred_by,)) ref_info = await cur_ref.fetchone() if ref_info: old_comm = float(ref_info[0]) new_comm = old_comm + 0.1 await db.execute( "UPDATE users SET referral_commission=? WHERE user_id=?", (new_comm, referred_by) ) await db.execute( "UPDATE referrals SET ref_taps=ref_taps+1 WHERE ref_user_id=?", (user_id,) ) if energy <= 0: energy_refill_ts = current_time + ENERGY_REFILL_HOURS * 3600 async with aiosqlite.connect("database.db") as db: await db.execute(""" UPDATE users SET balance=?, energy=?, energy_refill_ts=?, taps_count=?, last_tap_ts=? WHERE user_id=? """, (new_balance, energy, energy_refill_ts, taps_count, current_time, user_id)) await db.execute(""" UPDATE global_stats SET total_tapped=total_tapped+1 WHERE id=1 """) await db.commit() await message.reply( f"🪄 Ты получил <b>+1</b> робакс!\n" f"⚡ Осталось энергии: <b>{energy}</b> из {START_ENERGY}.\n\n" f"Когда энергия станет 0, она восстановится через {ENERGY_REFILL_HOURS} ч.", parse_mode=enums.ParseMode.HTML ) elif text_in == "🗣 Друзья": ref_link = f"https://t.me/{BOT_USERNAME}?start={user_id}" async with aiosqlite.connect("database.db") as db: cur_f = await db.execute(""" SELECT ref_user_id, ref_username, ref_taps FROM referrals WHERE user_id=? """, (user_id,)) friends_data = await cur_f.fetchall() cur_comm = await db.execute( "SELECT referral_commission FROM users WHERE user_id=?", (user_id,) ) row_u = await cur_comm.fetchone() ref_comm = row_u[0] if row_u else 0 text_friends = "👥 <b>Твои рефералы:</b>\n\n" if friends_data: for f in friends_data: rid, rname, rtaps = f text_friends += ( f"• @{rname} (ID: {rid}), " f"натапал {rtaps} робуксов.\n" ) else: text_friends += "Пока нет рефералов.\n" text_friends += "\n" text_friends += f"💸 С рефералов накопилось: <b>{ref_comm:.2f}</b> робуксов (10%)." withdraw_button = InlineKeyboardButton("Вывести 10%", callback_data="withdraw_ref_commission") share_text = f"😎Зарабатывай робуксы с помощью бота: {ref_link}" encoded_share_text = urllib.parse.quote_plus(share_text) share_url = f"https://t.me/share/url?url={urllib.parse.quote_plus(ref_link)}&text={encoded_share_text}" share_button = InlineKeyboardButton("Поделиться", url=share_url) inline_kb = InlineKeyboardMarkup( [ [withdraw_button], [share_button] ] ) await message.reply( f"🔗 Твоя реф. ссылка:\n{ref_link}\n\n{text_friends}", reply_markup=inline_kb, parse_mode=enums.ParseMode.HTML, disable_web_page_preview=True ) elif text_in == "🦈 Вывести": currency_kb = InlineKeyboardMarkup( [ [InlineKeyboardButton("Robux", callback_data="choose_currency:robux")], [InlineKeyboardButton("💎 TTD GEMS", callback_data="choose_currency:ttd")] ] ) await message.reply( "Выбери, что хочешь вывести:", reply_markup=currency_kb ) elif text_in == "🌴 Пруфы": await message.reply( "📸 Посмотреть все пруфы можно тут:", reply_markup=proofs_inline ) elif text_in == "📊 Статистика": async with aiosqlite.connect("database.db") as db: cur_st = await db.execute("SELECT total_users, total_tapped, total_withdrawn FROM global_stats WHERE id=1") stats = await cur_st.fetchone() total_users = stats[0] total_tapped = stats[1] total_wd = stats[2] await message.reply( f"📊 <b>Статистика бота:</b>\n\n" f"👥 Пользователей: <b>{total_users}</b>\n" f"👆 Всего натапано: <b>{total_tapped}</b>\n" f"💸 Всего выведено (Robux): <b>{total_wd}</b>", parse_mode=enums.ParseMode.HTML ) elif text_in == "❓ FAQ": faq_text = ( "❓ <b>Частые вопросы:</b>\n\n" "1. <b>Мой Профиль</b> — посмотреть баланс и статистику.\n" "2. <b>💸 Заработать Робуксы</b> — получить +1 робакс (тратится 1 энергия).\n" "3. <b>🗣 Друзья</b> — реф.ссылка и доход с рефералов.\n" "4. <b>🦈 Вывести</b> — подать заявку на вывод (Robux или TTD).\n" "5. <b>🌴 Пруфы</b> — канал с выплатами.\n" "6. <b>📊 Статистика</b> — общая статистика бота.\n" "7. <b>❓ FAQ</b> — это меню." ) await message.reply(faq_text, parse_mode=enums.ParseMode.HTML) else: await message.reply("Выберите команду из меню или введите /start.") # --------------------------- # Хендлер колбэка "choose_currency:..." # --------------------------- @app.on_callback_query(filters.regex(r"^choose_currency:(\w+)")) async def choose_currency_handler(client: Client, callback_query: CallbackQuery): user_id = callback_query.from_user.id currency = callback_query.matches[0].group(1) await callback_query.answer() if not await is_subscribed(client, user_id): await callback_query.answer("Сначала подпишитесь на канал!", show_alert=True) return user_info = await get_user(user_id) if not user_info: await callback_query.message.reply("Ошибка профиля, нажмите /start.") return user_balance = user_info["balance"] if currency == "robux": if user_balance < MIN_WITHDRAW_BALANCE: await callback_query.message.reply( f"Минимальная сумма для вывода: {MIN_WITHDRAW_BALANCE} робуксов!", parse_mode=enums.ParseMode.HTML ) return await set_state(user_id, "awaiting_robux_amount", "") await callback_query.message.reply( "💰 Введи количество робуксов, которое хочешь вывести:\n(Не больше твоего баланса!)", parse_mode=enums.ParseMode.HTML, reply_markup=ReplyKeyboardRemove() ) elif currency == "ttd": # Пример вывода TTD (опционально) pass # --------------------------- # Вывод реф. комиссии # --------------------------- @app.on_callback_query(filters.regex(r"^withdraw_ref_commission")) async def withdraw_ref_commission_handler(client: Client, cq: CallbackQuery): user_id = cq.from_user.id if not await is_subscribed(client, user_id): await cq.answer("Вы не подписаны на канал!", show_alert=True) return async with aiosqlite.connect("database.db") as db: cur = await db.execute("SELECT referral_commission FROM users WHERE user_id=?", (user_id,)) row = await cur.fetchone() if not row: await cq.answer("Ошибка: нет данных о пользователе.", show_alert=True) return commission = float(row[0]) if commission <= 0: await cq.answer("У вас нет накопленной комиссии.", show_alert=True) return # Переводим на баланс await db.execute(""" UPDATE users SET balance=balance+?, referral_commission=0 WHERE user_id=? """, (commission, user_id)) await db.commit() await cq.answer("Реферальная комиссия выведена на баланс!") await client.send_message( user_id, f"Вы успешно перевели <b>{commission:.2f}</b> робуксов из реф. комиссии на основной баланс.", parse_mode=enums.ParseMode.HTML ) # --------------------------- # Админ: Выполнить заявку # --------------------------- @app.on_callback_query(filters.regex(r"^complete_withdraw:(\d+)")) async def complete_withdraw_handler(client: Client, callback_query: CallbackQuery): withdraw_id = int(callback_query.matches[0].group(1)) admin_id = callback_query.from_user.id # Ставим админу состояние, ждём пруфы await set_state(admin_id, "admin_waiting_proofs", str(withdraw_id)) async with aiosqlite.connect("database.db") as db: await db.execute( "UPDATE pending_withdrawals SET status='processing' WHERE withdraw_id=?", (withdraw_id,) ) await db.commit() await callback_query.answer("Отправьте пруфы (фото/видео) боту в ЛС.") # --------------------------- # Приём фото/видео от админа (пруф) # --------------------------- @app.on_message(filters.private & (filters.photo | filters.video)) async def admin_send_proof_handler(client: Client, message: Message): user_id = message.from_user.id state, temp_data = await get_state(user_id) if state == "admin_waiting_proofs": try: withdraw_id = int(temp_data) except ValueError: await message.reply("Ошибка: некорректный ID вывода.") await clear_state(user_id) return async with aiosqlite.connect("database.db") as db: cur = await db.execute(""" SELECT user_id, amount FROM pending_withdrawals WHERE withdraw_id=? AND status IN ('pending','processing') """, (withdraw_id,)) row = await cur.fetchone() if not row: await message.reply("Ошибка: заявка не найдена (или уже завершена)!") await clear_state(user_id) return target_user_id, amount = row # Отправляем пруф пользователю if message.photo: sent_msg = await client.send_photo( chat_id=target_user_id, photo=message.photo.file_id, caption="🎉 Твой вывод выполнен! Пришли, пожалуйста, отзыв.", parse_mode=enums.ParseMode.HTML ) else: sent_msg = await client.send_video( chat_id=target_user_id, video=message.video.file_id, caption="🎉 Твой вывод выполнен! Пришли, пожалуйста, отзыв.", parse_mode=enums.ParseMode.HTML ) # Ставим вывод "completed" в БД async with aiosqlite.connect("database.db") as db: await db.execute(""" UPDATE withdraw_history SET proof_message_id=? WHERE id=? """, (sent_msg.id, withdraw_id)) await db.execute(""" UPDATE pending_withdrawals SET status='completed' WHERE withdraw_id=? """, (withdraw_id,)) await db.execute(""" UPDATE users SET total_withdrawn = total_withdrawn + ? WHERE user_id=? """, (amount, target_user_id)) await db.execute(""" UPDATE global_stats SET total_withdrawn = total_withdrawn + ? WHERE id=1 """, (amount,)) await db.commit() # Ставим пользователю состояние "waiting_for_review" await set_state(target_user_id, "waiting_for_review", str(withdraw_id)) # Админу сообщаем, что всё ок await message.reply("Пруф отправлен пользователю! Ожидаем от него отзыв.") await clear_state(user_id) # --------------------------- # NEW: Команда /post (рассылка всем) # --------------------------- @app.on_message(filters.command("post") & filters.private) async def admin_broadcast_handler(client: Client, message: Message): if message.from_user.id != ADMIN_CHAT_ID: await message.reply("❌ У вас нет прав для использования этой команды.") return if len(message.command) < 2: await message.reply("Формат: /post ТЕКСТ_СООБЩЕНИЯ") return broadcast_text = message.text.split(" ", 1)[1].strip() if not broadcast_text: await message.reply("Сообщение пустое!") return # Собираем всех юзеров async with aiosqlite.connect("database.db") as db: cur = await db.execute("SELECT user_id FROM users") all_users = await cur.fetchall() sent_count = 0 for row_u in all_users: uid = row_u[0] try: await client.send_message(uid, broadcast_text) sent_count += 1 # Можно сделать задержку, если пользователей много: # await asyncio.sleep(0.05) except Exception as e: logging.warning(f"Не удалось отправить user_id={uid}: {e}") await message.reply(f"✅ Рассылка отправлена {sent_count} пользователям.") # --------------------------- # Фоновая задача: напоминания # --------------------------- async def reminder_loop(): while True: try: now_ts = time.time() interval_sec = REMINDER_INTERVAL_HOURS * 3600 threshold_ts = now_ts - interval_sec async with aiosqlite.connect("database.db") as db: db.row_factory = aiosqlite.Row cur = await db.execute(""" SELECT user_id FROM users WHERE energy > 0 AND last_tap_ts < ? AND last_reminder_ts < ? """, (threshold_ts, threshold_ts)) rows = await cur.fetchall() for row in rows: uid = row["user_id"] try: await app.send_message( chat_id=uid, text=( "Привет!\n" "У тебя есть энергия для тапов, но ты давно не заходил!\n" "Не упускай возможность заработать робуксы 😎" ) ) except Exception as e: logging.warning(f"Ошибка при отправке напоминания {uid}: {e}") await db.execute( "UPDATE users SET last_reminder_ts=? WHERE user_id=?", (now_ts, uid) ) await db.commit() except Exception as e: logging.error(f"Ошибка в reminder_loop: {e}") # Спим 1 час await asyncio.sleep(3600) # Запуск бота await app.start() logging.info("Bot started...") # Запускаем фоновую задачу asyncio.create_task(reminder_loop()) await idle() await app.stop() if __name__ == "__main__": asyncio.run(main())
Функция get_gamepass_info(gamepass_id)
:
https://apis.roblox.com/game-passes/v1/game-passes/<ID>
.ROBLOX_COOKIE
.403
и Roblox пришлёт заголовок x-csrf-token
, функция попробует повторить запрос, уже указав X-CSRF-TOKEN
.None
.Парсинг цены:
price
(часто), а не PriceInRobux
.403
или 404
, и в итоге получите None
.Логирование:
logging
выводит статусы в консоль. Если надо писать в файл, настроите logging.FileHandler(...)
.Команда /post ТЕКСТ
— рассылает сообщение всем пользователям (только админ может это делать).
Остальные части (обработка FSM, вывод, рефералы) идентичны предыдущим вашим вариантам.
Не забудьте заменить в коде значения (
BOT_TOKEN
,API_ID
,API_HASH
,ADMIN_CHAT_ID
,ROBLOX_COOKIE
) на ваши реальные.
каждый раз пишет что цена 0 робуксов, лог [14:19:18] INFO: [roblox_get_request] GET https://apis.roblox.com/game-passes/v1/game-passes/6028662, status=404, body[:200]='{"errors":[{"code":0,"message":""}]}\n'
Ошибка status=404
означает, что Roblox API не смог найти этот GamePass или у вашего аккаунта (куки .ROBLOSECURITY
) нет прав на просмотр.
https://www.roblox.com/game-pass/6028662
apis.roblox.com/game-passes/v1/game-passes/{id}
часто не показывает информацию о GamePass, если он не принадлежит аккаунту, который используется для API-запроса.404
.economy.roblox.com
).ROBLOSECURITY
).economy.roblox.com
)Попробуйте вместо apis.roblox.com
использовать экономический API:
pythonasync def get_gamepass_info(gamepass_id: int) -> dict: """ Новый метод через economy.roblox.com/v2/assets/{gamepass_id}/details. Работает даже для GamePass, которые не принадлежат вашему аккаунту. """ url = f"https://economy.roblox.com/v2/assets/{gamepass_id}/details" headers = { "Cookie": ROBLOX_COOKIE, "User-Agent": "Mozilla/5.0" } async with aiohttp.ClientSession() as session: try: async with session.get(url, headers=headers) as resp: status = resp.status data = await resp.json() logging.info(f"GET {url} => status={status}, data={str(data)[:200]}") if status == 200: return data else: return None except Exception as e: logging.error(f"Ошибка запроса к Roblox API: {e}") return None
Вместо старого вызова:
pythongp_info = await get_gamepass_info_old(gp_id)
Используйте новый метод:
pythongp_info = await get_gamepass_info(gp_id) if gp_info: price = gp_info.get("price", 0) # Цена в Robux name = gp_info.get("name", "Неизвестно") # Название геймпасса
https://economy.roblox.com/v2/assets/{gamepass_id}/details
, который не требует владения GamePass.json{ "id": 6028662, "name": "VIP Pass", "description": "Это VIP доступ!", "price": 100, // Цена в Robux "creator": { "id": 123456789, "name": "SuperDev" } }
404
или 403
ROBLOX_COOKIE
: возможно, она устарела.https://www.roblox.com/game-pass/{id}
).https://economy.roblox.com/v2/assets/{gamepass_id}/details
(если API-ключ недоступен, аккаунт может быть ограничен).Попробуйте и отпишитесь, получилось ли у вас.
приходит такой ответ [14:24:05] INFO: GET https://economy.roblox.com/v2/assets/158343189/details => status=200, data={'TargetId': 0, 'ProductType': None, 'AssetId': 158343189, 'ProductId': 0, 'Name': "ShockingHello123's Place", 'Description': '', 'AssetTypeId': 9, 'Creator': {'Id': 61452837, 'Name': 'ShockingHello12, и бот пишет что цена 0 хотя это не так
Проблема в том, что Game Pass ID (число, которое вы берёте из ссылки вида https://www.roblox.com/game-pass/123456
) не всегда совпадает с Asset ID, который нужен некоторым эндпоинтам economy.roblox.com
для вывода цены.
Из-за этого при запросе к economy.roblox.com/v2/assets/<ID>/details
вы получаете:
{"AssetTypeId": 9, "Name": "... Place ...", "Description": "...", "Price": null ...}
404/{"errors":[{"code":0,"message":""}]}
...потому что Roblox видит не тот тип объекта (например, Place вместо Game Pass) или вообще не находит соответствующий Asset.
/game-pass/
в URL (часто используется в старом api.roblox.com/marketplace/game-pass-product-info?gamePassId=...
).Вам нужно либо:
Иногда проще всего оставить старый эндпоинт (если он у вас ещё работает) и просто добавить туда авторизацию .ROBLOSECURITY
:
pythonasync def get_gamepass_info(gamepass_id: int) -> dict: """Старый эндпоинт, но с куками.""" url = f"https://api.roblox.com/marketplace/game-pass-product-info?gamePassId={gamepass_id}" headers = { "Cookie": ".ROBLOSECURITY=ВАШ_КУКИ", "User-Agent": "Mozilla/5.0" } async with aiohttp.ClientSession() as session: async with session.get(url, headers=headers) as resp: if resp.status == 200: return await resp.json() else: return None
json{ "Name": "VIP Access", "Description": "...", "PriceInRobux": 25, "Created": "...", "Updated": "...", "Sales": 123 }
games.roblox.com/v1/game-passes/{gamePassId}
Есть вариант через games.roblox.com
, который принимает именно Game Pass ID (а не Asset ID). Пример:
pythonasync def get_gamepass_info_new(gamepass_id: int) -> dict: """ Пример эндпоинта: GET https://games.roblox.com/v1/game-passes/<gamePassId> Может вернуть: { "id": 6028662, "name": "...", "displayName": "...", "productId": 123456789, "price": 50, ... } """ url = f"https://games.roblox.com/v1/game-passes/{gamepass_id}" headers = { "Cookie": ".ROBLOSECURITY=ВАШ_КУКИ", "User-Agent": "Mozilla/5.0" } async with aiohttp.ClientSession() as session: async with session.get(url, headers=headers) as resp: if resp.status == 200: return await resp.json() else: return None
значит, вы сможете взятьjson{ "id": 6028662, "productId": 987654321, "price": 15, ... }
price
напрямую.economy.roblox.com/v2/assets/<ID>/details
цена = 0?Потому что:
<ID>
= GamePass ID, а Economy API ждёт Asset ID (другой номер).AssetTypeId = 9
говорит, что это Place, а не GamePass (Roblox пытается «найти» объект с таким ID, находит Place, где цена нет).К сожалению, все официальные способы либо устарели, либо требуют владения. Обычно делают так:
games.roblox.com/v1/game-passes/{gamePassId}
→ если вернёт JSON, в нём может быть поле "assetId"
(или "productId"
).economy.roblox.com/v2/assets/<assetId>/details
.Но если games.roblox.com
сразу возвращает 404/403, придётся или:
https://www.roblox.com/game-pass/{gamePassId}
, выуживая из неё нужную инфу (небезопасно и ненадёжно),.ROBLOSECURITY
от самого пользователя, создавшего этот геймпасс), если хотите получать точную цену через games.roblox.com
.api.roblox.com/marketplace/game-pass-product-info?gamePassId=...
— при условии, что у вас правильная авторизация и Roblox его не выключил.games.roblox.com/v1/game-passes/{gamePassId}
возвращает нужные поля (price, productId), используйте его: это самый логичный вариант для Game Pass по ID.Итог:
.ROBLOSECURITY
вы пользуетесь. Проверяйте через games.roblox.com/v1/game-passes/{ID}
.