diff --git a/.env.example b/.env.example index 545f7a5..cafe81f 100644 --- a/.env.example +++ b/.env.example @@ -4,14 +4,15 @@ ADMIN_ID= CRYPTO_PAY_TOKEN= CRYPTO_PAY_NETWORK= +CRYPTO_PAY_ASSET=USDT # Бот для логгинга (логи будут в этом боте, можно указать один и тот же бот на главный и логгинга) LOG_BOT_TOKEN= # Данные шопа SHOP_NAME=KILL UNONY MOM -SHOP_CHANNEL=https://t.me/username -BOT_USERNAME=username +SHOP_CHANNEL= +BOT_USERNAME= # Директории и файлы DATABASE_DIR=Users diff --git a/config.py b/config.py index 3f24315..d91914d 100644 --- a/config.py +++ b/config.py @@ -5,8 +5,8 @@ from dotenv import load_dotenv from concurrent.futures import ThreadPoolExecutor from pathlib import Path -from models.config import BotConfig from models.stats import BotStats +from models.config import BotConfig BASE_DIR = Path(__file__).resolve().parent @@ -22,6 +22,7 @@ def _env_int(name: str, default: int) -> int: raw = os.getenv(name) if raw is None or raw == "": return default + return int(raw) @@ -29,6 +30,7 @@ def _env_float(name: str, default: float) -> float: raw = os.getenv(name) if raw is None or raw == "": return default + return float(raw) @@ -36,21 +38,23 @@ def _env_bool(name: str, default: bool) -> bool: raw = os.getenv(name) if raw is None or raw == "": return default + return raw.strip().lower() in {"1", "true", "yes", "on"} load_dotenv() BOT_TOKEN = os.getenv("BOT_TOKEN", "") -LOG_BOT_TOKEN = os.getenv("LOG_BOT_TOKEN", "") or None - +LOG_BOT_TOKEN = os.getenv("LOG_BOT_TOKEN", BOT_TOKEN) or None ADMIN_ID = _env_int("ADMIN_ID", 0) + CRYPTO_PAY_TOKEN = os.getenv("CRYPTO_PAY_TOKEN", "") CRYPTO_PAY_NETWORK = os.getenv("CRYPTO_PAY_NETWORK", "MAINNET") +CRYPTO_PAY_ASSET = os.getenv("CRYPTO_PAY_ASSET", "USDT") SHOP_NAME = os.getenv("SHOP_NAME", "KILL UNONY MOM") -SHOP_CHANNEL = os.getenv("SHOP_CHANNEL", "https://t.me/bot399_start_bot") -BOT_USERNAME = os.getenv("BOT_USERNAME", "bot399_start_bot") +SHOP_CHANNEL = os.getenv("SHOP_CHANNEL", "") +BOT_USERNAME = os.getenv("BOT_USERNAME", "") DATABASE_DIR = _resolve_path("DATABASE_DIR", "Users") COOKIE_FILES_DIR = _resolve_path("COOKIE_FILES_DIR", "Cookies") diff --git a/handlers/admin.py b/handlers/admin.py index 77b8b3a..70fa8f7 100644 --- a/handlers/admin.py +++ b/handlers/admin.py @@ -11,7 +11,7 @@ from aiogram.types import ( Message, ) -from config import ADMIN_ID, ROBSEC_FILE +from config import ADMIN_ID, CRYPTO_PAY_ASSET, ROBSEC_FILE from db.storage import load_config from db.users import get from keyboards.admin import admin_menu_kb @@ -37,7 +37,10 @@ from states.admin import Form from runtime import get_bot from utils.logging import safe_edit +from runtime import get_cp + router = Router(name="admin") +cp = get_cp() def _admin(user_id: int) -> bool: @@ -78,7 +81,9 @@ def _user_card(uid: int) -> tuple[str, InlineKeyboardMarkup] | None: ], [ InlineKeyboardButton( - text="👑 Убрать админку" if profile.is_admin else "👑 Выдать админку", + text=( + "👑 Убрать админку" if profile.is_admin else "👑 Выдать админку" + ), callback_data=f"u_admin_{uid}", ) ], @@ -184,7 +189,9 @@ async def admin_robsec_clear_yes(callback: CallbackQuery): if not _admin(callback.from_user.id): return clear_robsec() - await safe_edit(callback, "✅ robsec.txt очищен", reply_markup=admin_menu_kb()) + await safe_edit( + callback, "✅ robsec.txt очищен", reply_markup=admin_menu_kb() + ) await callback.answer("Очищено") @@ -315,7 +322,8 @@ async def admin_treasury_amount(message: Message, state: FSMContext): "❌ Не удалось создать счет CryptoBot.", reply_markup=admin_menu_kb() ) return - pay_url, invoice_id, amount_usdt = invoice + pay_url, invoice_id, amount_asset = invoice + keyboard = InlineKeyboardMarkup( inline_keyboard=[ [InlineKeyboardButton(text="💳 Оплатить", url=pay_url)], @@ -328,7 +336,7 @@ async def admin_treasury_amount(message: Message, state: FSMContext): ] ) await message.answer( - f"🏦 Счет на пополнение казны\n\n💰 Сумма: {amount:.2f} ₽ (~{amount_usdt} USDT)\n\nПосле оплаты нажмите «Проверить оплату».", + f"🏦 Счет на пополнение казны\n\n💰 Сумма: {amount:.2f} ₽ (~{amount_asset} {CRYPTO_PAY_ASSET})\n\nПосле оплаты нажмите «Проверить оплату».", parse_mode="HTML", reply_markup=keyboard, ) @@ -338,16 +346,21 @@ async def admin_treasury_amount(message: Message, state: FSMContext): async def treasury_check(callback: CallbackQuery): if not _admin(callback.from_user.id): return + _, _, invoice_id, amount = callback.data.split("_", 3) + try: paid = await check_treasury_invoice(int(invoice_id)) amount_rub = float(amount) + except (ValueError, TypeError): paid = False amount_rub = 0.0 + if not paid: await callback.answer("Оплата еще не поступила", show_alert=True) return + balance = add_treasury(amount_rub) await safe_edit( callback, @@ -392,7 +405,9 @@ async def admin_users(callback: CallbackQuery, state: FSMContext): return await state.set_state(Form.admin_find_user) await safe_edit( - callback, "Отправьте ID пользователя или @username.", reply_markup=back_kb("admin_back") + callback, + "Отправьте ID пользователя или @username.", + reply_markup=back_kb("admin_back"), ) @@ -489,7 +504,9 @@ async def u_ban(callback: CallbackQuery, state: FSMContext): await state.update_data(target_uid=uid) await state.set_state(Form.admin_user_ban_reason) await safe_edit( - callback, "Отправьте причину блокировки:", reply_markup=back_kb("admin_back") + callback, + "Отправьте причину блокировки:", + reply_markup=back_kb("admin_back"), ) return card = _user_card(uid) diff --git a/handlers/upload.py b/handlers/upload.py index e85df63..787f44c 100644 --- a/handlers/upload.py +++ b/handlers/upload.py @@ -36,7 +36,9 @@ async def _blocked(message: Message) -> bool: 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 '—'}") + 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: @@ -51,7 +53,9 @@ async def _blocked(message: Message) -> bool: @router.message(StateFilter(None), F.document) async def handle_document(message: Message): if is_admin(message.from_user.id): - await message.answer("👑 Администраторы не могут продавать cookie. Используйте /admin.") + await message.answer( + "👑 Администраторы не могут продавать cookie. Используйте /admin." + ) return if await _blocked(message): return @@ -60,7 +64,10 @@ async def handle_document(message: Message): await message.answer("❌ Отправьте файл формата .txt.") return - path = COOKIE_FILES_DIR / f"{message.from_user.id}_{random.randint(100000, 999999)}.txt" + 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") @@ -72,14 +79,22 @@ async def handle_document(message: Message): @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.") + 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()) + await message.answer( + "👋 Используйте меню ниже или отправьте текст с cookie.", + reply_markup=main_menu_kb(), + ) return + await process_cookies(message, text) @@ -99,7 +114,9 @@ async def process_cookies(message: Message, raw_text: str): valid = [] async with aiohttp.ClientSession() as session: - tasks = [check_cookie_with_retry(session, cookie, proxies) for cookie in cookies] + 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 @@ -117,15 +134,22 @@ async def process_cookies(message: Message, raw_text: str): pass if not valid: - await progress.edit_text(f"❌ Валидные cookie не найдены. Дубликатов: {duplicates}") + await progress.edit_text( + f"❌ Валидные cookie не найдены. Дубликатов: {duplicates}" + ) return - await progress.edit_text(f"⏳ Проверяю донаты\nВалидных cookie: {len(valid)}", parse_mode="HTML") + await progress.edit_text( + f"⏳ Проверяю донаты\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) + 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, @@ -152,14 +176,16 @@ async def process_cookies(message: Message, raw_text: str): fresh_ok = [] fresh_failed = 0 for offset in range(0, len(priced), FRESHER_BATCH_SIZE): - batch = priced[offset:offset + 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)): + 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: @@ -184,8 +210,13 @@ async def process_cookies(message: Message, raw_text: str): 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") + 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) @@ -202,7 +233,9 @@ async def process_cookies(message: Message, raw_text: str): 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) + await apply_referral_payout( + bot, profile.user_id, profile.username, payout, cfg.referral_percent + ) method = "📊 AVG-система" if avg_used else "📋 Поштучный" report = ( diff --git a/handlers/user.py b/handlers/user.py index 45addbb..6af340b 100644 --- a/handlers/user.py +++ b/handlers/user.py @@ -1,11 +1,12 @@ from aiogram import F, Router from aiogram.fsm.context import FSMContext -from aiogram.types import CallbackQuery +from aiogram.types import LinkPreviewOptions, CallbackQuery, Message +from aiogram.fsm.state import State, StatesGroup from config import ADMIN_ID, BOT_USERNAME -from db.storage import load_config, load_stats +from db.storage import load_config, load_stats, save_config from db.users import get, register, save -from keyboards.user import back_kb +from keyboards.user import back_kb, activate_kb from runtime import get_bot, get_log_bot from services.withdrawals import create_crypto_check from utils.logging import log_admin, safe_edit @@ -13,6 +14,10 @@ from utils.logging import log_admin, safe_edit router = Router(name="user") +class WithdrawState(StatesGroup): + waiting_for_amount = State() + + def _get_profile(callback: CallbackQuery): profile = get(callback.from_user.id) if profile is None: @@ -77,6 +82,7 @@ async def cb_withdraw(callback: CallbackQuery, state: FSMContext): await state.clear() profile = _get_profile(callback) cfg = load_config() + if profile.balance < cfg.min_withdraw: await safe_edit( callback, @@ -87,37 +93,76 @@ async def cb_withdraw(callback: CallbackQuery, state: FSMContext): ) await callback.answer() return - if profile.balance > cfg.treasury: - await safe_edit( - callback, - f"⚠️ Недостаточно средств в казне: {cfg.treasury:.2f} ₽.", - reply_markup=back_kb(), - ) - await callback.answer() - return - await callback.answer("⏳ Создаю чек...") - await safe_edit(callback, "⏳ Создаю CryptoBot-чек, подождите...") - amount = profile.balance - check_url = await create_crypto_check(amount) - if not check_url: - await safe_edit(callback, "❌ Не удалось создать чек. Попробуйте позже.", reply_markup=back_kb()) - return - - profile.balance = 0.0 - save(profile.user_id, profile) - cfg.treasury = max(0.0, cfg.treasury - amount) - from db.storage import save_config - - save_config(cfg) await safe_edit( callback, - f"✅ Чек создан на сумму {amount:.2f} ₽.\n" - f"Активировать чек", + f"💳 Вывод средств\n\n" + f"Ваш баланс: {profile.balance:.2f} ₽\n" + f"Минимум: {cfg.min_withdraw:.2f} ₽\n\n" + f"Введите сумму для вывода:", reply_markup=back_kb(), - disable_web_page_preview=True, ) - bot = get_bot() or callback.message.bot + await state.set_state(WithdrawState.waiting_for_amount) + await callback.answer() + + +@router.message(WithdrawState.waiting_for_amount) +async def process_withdraw_amount(message: Message, state: FSMContext): + profile = _get_profile(message) + cfg = load_config() + + try: + amount = float(message.text) + except ValueError: + await message.answer("❌ Введите корректную сумму числом.") + return + + if amount <= 0: + await message.answer("❌ Сумма должна быть больше нуля.") + return + + if amount < cfg.min_withdraw: + await message.answer(f"❌ Минимум для вывода: {cfg.min_withdraw:.2f} ₽.") + return + + if amount > profile.balance: + await message.answer( + f"❌ Недостаточно средств. Ваш баланс: {profile.balance:.2f} ₽." + ) + return + + if amount > cfg.treasury: + await message.answer( + f"❌ Недостаточно средств в казне: {cfg.treasury:.2f} ₽." + ) + return + + await state.clear() + await message.answer("⏳ Создаю CryptoBot-чек, подождите...") + + check_url, check_image_url = await create_crypto_check(amount) + if not check_url: + await message.answer( + "❌ Не удалось создать чек. Попробуйте позже.", + reply_markup=back_kb(), + ) + return + + profile.balance -= amount + save(profile.user_id, profile) + cfg.treasury = max(0.0, cfg.treasury - amount) + + save_config(cfg) + + await message.answer( + text=f"🦋 Чек на {amount:.2f} ₽.", + reply_markup=activate_kb(url=check_url, amount=amount), + link_preview_options=LinkPreviewOptions( + url=check_image_url, show_above_text=True + ), + ) + + bot = get_bot() or message.bot await log_admin( bot, ADMIN_ID, diff --git a/keyboards/user.py b/keyboards/user.py index e2cce62..fd4aedb 100644 --- a/keyboards/user.py +++ b/keyboards/user.py @@ -19,6 +19,17 @@ def main_menu_kb() -> InlineKeyboardMarkup: ) +def activate_kb(url: str, amount: float) -> InlineKeyboardMarkup: + if not url: + return back_kb() + + return InlineKeyboardMarkup( + inline_keyboard=[ + [InlineKeyboardButton(text=f"Получить {amount:.2f} ₽", url=url)] + ] + ) + + def back_kb(callback_data: str = "back_main") -> InlineKeyboardMarkup: return InlineKeyboardMarkup( inline_keyboard=[ diff --git a/main.py b/main.py index f963a7a..3696e2e 100644 --- a/main.py +++ b/main.py @@ -4,22 +4,23 @@ from datetime import datetime from aiosend import CryptoPay, TESTNET, MAINNET + from aiogram import Bot, Dispatcher from aiogram.enums import ParseMode from aiogram.fsm.storage.memory import MemoryStorage from aiogram.client.default import DefaultBotProperties from config import ( - ADMIN_ID, BOT_TOKEN, - CRYPTO_PAY_NETWORK, - CRYPTO_PAY_TOKEN, LOG_BOT_TOKEN, + ADMIN_ID, SHOP_NAME, + CRYPTO_PAY_TOKEN, + CRYPTO_PAY_NETWORK, ) from db.storage import load_config, load_stats from handlers import admin, start, upload, user -from runtime import set_bots +from runtime import set_bots, set_cp from utils.logging import log_admin @@ -28,11 +29,13 @@ async def main() -> None: bot = Bot(token=BOT_TOKEN, default=defaults) log_bot = Bot(token=LOG_BOT_TOKEN, default=defaults) if LOG_BOT_TOKEN else None - set_bots(bot, log_bot) cp_net = TESTNET if CRYPTO_PAY_NETWORK.lower() == "testnet" else MAINNET - cp = CryptoPay(token=CRYPTO_PAY_TOKEN, network=cp_net) + + set_cp(cp) + set_bots(bot, log_bot) + dp = Dispatcher(storage=MemoryStorage()) dp.include_router(start.router) @@ -52,13 +55,11 @@ async def main() -> None: try: await dp.start_polling(bot) - await cp.start_polling(bot) finally: if log_bot: await log_bot.session.close() - await cp.session.close() await bot.session.close() diff --git a/runtime.py b/runtime.py index 766997b..09b92ee 100644 --- a/runtime.py +++ b/runtime.py @@ -1,9 +1,11 @@ from __future__ import annotations from aiogram import Bot +from aiosend import CryptoPay _bot: Bot | None = None _log_bot: Bot | None = None +_cp: CryptoPay | None = None def set_bots(bot: Bot, log_bot: Bot | None = None) -> None: @@ -18,3 +20,12 @@ def get_bot() -> Bot | None: def get_log_bot() -> Bot | None: return _log_bot + + +def set_cp(cp: CryptoPay | None = None) -> None: + global _cp + _cp = cp + + +def get_cp() -> CryptoPay | None: + return _cp diff --git a/services/admin.py b/services/admin.py index 4089e54..133e4a7 100644 --- a/services/admin.py +++ b/services/admin.py @@ -1,23 +1,22 @@ from __future__ import annotations -import asyncio import os +import asyncio from aiogram.enums import ParseMode -from aiogram.types import FSInputFile, InlineKeyboardButton, InlineKeyboardMarkup +from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup -from config import ADMIN_ID, BOT_USERNAME, ROBSEC_FILE -from db.storage import load_config, save_config, load_stats, save_stats +from config import ADMIN_ID, ROBSEC_FILE + +from utils.logging import safe_edit +from db.storage import load_config, save_config from db.users import all_users, get, save, find_by_username -from keyboards.admin import admin_menu_kb -from models.user import UserProfile -from runtime import get_log_bot -from utils.logging import log_admin, log_admin_document, safe_edit def is_admin(user_id: int) -> bool: if user_id == ADMIN_ID: return True + profile = get(user_id) return bool(profile and profile.is_admin) @@ -25,6 +24,7 @@ def is_admin(user_id: int) -> bool: def toggle_shop() -> bool: cfg = load_config() cfg.shop_enabled = not cfg.shop_enabled + save_config(cfg) return cfg.shop_enabled @@ -32,6 +32,7 @@ def toggle_shop() -> bool: def toggle_bot() -> bool: cfg = load_config() cfg.bot_enabled = not cfg.bot_enabled + save_config(cfg) return cfg.bot_enabled @@ -52,6 +53,7 @@ def update_min_withdraw(value: float) -> None: def add_treasury(amount_rub: float) -> float: cfg = load_config() cfg.treasury += amount_rub + save_config(cfg) return cfg.treasury @@ -59,6 +61,7 @@ def add_treasury(amount_rub: float) -> float: def subtract_treasury(amount_rub: float) -> float: cfg = load_config() cfg.treasury = max(0.0, cfg.treasury - amount_rub) + save_config(cfg) return cfg.treasury @@ -99,8 +102,12 @@ async def send_user_card(target, uid: int) -> None: kb = InlineKeyboardMarkup( inline_keyboard=[ [ - InlineKeyboardButton(text="➕ Баланс", callback_data=f"u_bal_add_{uid}"), - InlineKeyboardButton(text="➖ Баланс", callback_data=f"u_bal_sub_{uid}"), + InlineKeyboardButton( + text="➕ Баланс", callback_data=f"u_bal_add_{uid}" + ), + InlineKeyboardButton( + text="➖ Баланс", callback_data=f"u_bal_sub_{uid}" + ), ], [ InlineKeyboardButton( @@ -110,7 +117,9 @@ async def send_user_card(target, uid: int) -> None: ], [ InlineKeyboardButton( - text="👑 Убрать админку" if profile.is_admin else "👑 Выдать админку", + text=( + "👑 Убрать админку" if profile.is_admin else "👑 Выдать админку" + ), callback_data=f"u_admin_{uid}", ) ], @@ -123,15 +132,24 @@ async def send_user_card(target, uid: int) -> None: await safe_edit(target, text, reply_markup=kb) -async def broadcast(bot, text: str | None = None, photo_id: str | None = None, caption: str | None = None) -> tuple[int, int]: +async def broadcast( + bot, + text: str | None = None, + photo_id: str | None = None, + caption: str | None = None, +) -> tuple[int, int]: ok = 0 fail = 0 for uid in all_users(): try: if photo_id: - await bot.send_photo(uid, photo_id, caption=caption or "", parse_mode=ParseMode.HTML) + await bot.send_photo( + uid, photo_id, caption=caption or "", parse_mode=ParseMode.HTML + ) else: - await bot.send_message(uid, text or caption or "-", parse_mode=ParseMode.HTML) + await bot.send_message( + uid, text or caption or "-", parse_mode=ParseMode.HTML + ) ok += 1 except Exception: fail += 1 @@ -143,6 +161,7 @@ def find_user(query: str) -> int | None: if query.isdigit(): uid = int(query) return uid if get(uid) else None + return find_by_username(query) @@ -150,6 +169,7 @@ def set_balance(uid: int, value: float) -> None: profile = get(uid) if not profile: return + profile.balance = value save(uid, profile) @@ -158,6 +178,7 @@ def add_balance(uid: int, amount: float) -> None: profile = get(uid) if not profile: return + profile.balance += amount save(uid, profile) @@ -166,6 +187,7 @@ def sub_balance(uid: int, amount: float) -> None: profile = get(uid) if not profile: return + profile.balance = max(0.0, profile.balance - amount) save(uid, profile) @@ -174,6 +196,7 @@ def toggle_user_admin(uid: int) -> bool: profile = get(uid) if not profile: return False + profile.is_admin = not profile.is_admin save(uid, profile) return profile.is_admin @@ -183,6 +206,7 @@ def ban_user(uid: int, reason: str) -> None: profile = get(uid) if not profile: return + profile.is_banned = True profile.ban_reason = reason save(uid, profile) @@ -192,6 +216,7 @@ def unban_user(uid: int) -> None: profile = get(uid) if not profile: return + profile.is_banned = False profile.ban_reason = "" save(uid, profile) diff --git a/services/withdrawals.py b/services/withdrawals.py index 341ceb1..ef191d1 100644 --- a/services/withdrawals.py +++ b/services/withdrawals.py @@ -1,82 +1,63 @@ from __future__ import annotations -import aiohttp import logging +from runtime import get_cp -from config import CRYPTO_PAY_TOKEN - - -async def _get_usdt_rub_rate(session: aiohttp.ClientSession) -> float: - rate = 90.0 - async with session.get( - "https://testnet-pay.crypt.bot/api/getExchangeRates", - headers={"Crypto-Pay-API-Token": CRYPTO_PAY_TOKEN}, - ) as response: - data = await response.json() - if data.get("ok"): - for item in data.get("result", []): - if item.get("source") == "USDT" and item.get("target") == "RUB": - rate = float(item["rate"]) - break - return rate +from config import CRYPTO_PAY_ASSET async def create_crypto_check(amount_rub: float) -> str | None: try: - headers = {"Crypto-Pay-API-Token": CRYPTO_PAY_TOKEN} - async with aiohttp.ClientSession() as session: - usdt_rub = await _get_usdt_rub_rate(session) - amount_usdt = round(amount_rub / usdt_rub, 4) - async with session.post( - "https://testnet-pay.crypt.bot/api/createCheck", - headers=headers, - json={"asset": "USDT", "amount": str(amount_usdt)}, - ) as response: - data = await response.json() - if data.get("ok"): - return data["result"]["bot_check_url"] + cp = get_cp() + + amount_asset = await cp.exchange( + amount=amount_rub, source="RUB", target=CRYPTO_PAY_ASSET + ) + + createdCheck = await cp.create_check( + amount=amount_asset, asset=CRYPTO_PAY_ASSET + ) + check_image_url = await createdCheck.get_image(fiat="RUB") + + return createdCheck.bot_check_url, check_image_url + except Exception as exc: logging.error("crypto check: %s", exc) + return None async def create_treasury_invoice(amount_rub: float) -> tuple[str, int, float] | None: try: - headers = {"Crypto-Pay-API-Token": CRYPTO_PAY_TOKEN} - async with aiohttp.ClientSession() as session: - usdt_rub = await _get_usdt_rub_rate(session) - amount_usdt = round(amount_rub / usdt_rub, 4) - async with session.post( - "https://testnet-pay.crypt.bot/api/createInvoice", - headers=headers, - json={ - "asset": "USDT", - "amount": str(amount_usdt), - "description": f"Treasury +{amount_rub} RUB", - "payload": f"treasury_{amount_rub}", - }, - ) as response: - data = await response.json() - if data.get("ok"): - result = data["result"] - return result["pay_url"], int(result["invoice_id"]), amount_usdt + cp = get_cp() + + createdInvoice = await cp.create_invoice( + amount=amount_rub, + currency_type="fiat", + accepted_assets=[CRYPTO_PAY_ASSET], + fiat="RUB", + ) + + amount_asset = round( + await cp.exchange(amount=amount_rub, source="RUB", target=CRYPTO_PAY_ASSET), + 2, + ) + + return (createdInvoice.pay_url, int(createdInvoice.invoice_id), amount_asset) + except Exception as exc: logging.error("create_treasury_invoice: %s", exc) + return None async def check_treasury_invoice(invoice_id: int) -> bool: try: - headers = {"Crypto-Pay-API-Token": CRYPTO_PAY_TOKEN} - async with aiohttp.ClientSession() as session: - async with session.get( - f"https://testnet-pay.crypt.bot/api/getInvoices?invoice_ids={invoice_id}", - headers=headers, - ) as response: - data = await response.json() - if data.get("ok"): - items = data.get("result", {}).get("items", []) - return bool(items and items[0].get("status") == "paid") + cp = get_cp() + + paidInvoice = await cp.get_invoice(invoice_id) + return paidInvoice.status == "paid" + except Exception as exc: logging.error("check_treasury_invoice: %s", exc) diff --git a/utils/logging.py b/utils/logging.py index a7e129b..3a116c4 100644 --- a/utils/logging.py +++ b/utils/logging.py @@ -8,11 +8,14 @@ async def log_admin(bot, admin_id: int, text: str, log_bot=None) -> None: try: target = log_bot or bot await target.send_message(admin_id, text, parse_mode=ParseMode.HTML) + except Exception as exc: logging.error("log_admin: %s", exc) -async def log_admin_document(bot, admin_id: int, path: str, caption: str | None = None, log_bot=None) -> None: +async def log_admin_document( + bot, admin_id: int, path: str, caption: str | None = None, log_bot=None +) -> None: try: target = log_bot or bot await target.send_document(