diff --git a/main.py b/main.py index 1981962..4e5498c 100644 --- a/main.py +++ b/main.py @@ -1,30 +1,42 @@ -import asyncio -import requests -from io import BytesIO +import asyncio, aiohttp, json, html, requests, re, os from datetime import datetime, timezone - from aiogram import Bot, Dispatcher -from aiogram.types import Message from aiogram.filters import Command, BaseFilter -from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton -from aiogram.types import CallbackQuery -import json +from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery, Message +from dotenv import load_dotenv +load_dotenv() -# Settings -API_TOKEN = "" -ADMIN_ID = 0 -ASF_URL = "http://127.0.0.1:1242/Api/ASF" -ASF_PASSWORD = "0" +API_TOKEN = os.getenv("API_TOKEN") +ADMIN_ID = int(os.getenv("ADMIN_ID")) +ASF_URL = os.getenv("ASF_URL") +ASF_PASSWORD = os.getenv("ASF_PASSWORD") + +PAGE_SIZE = 40 +GAMES = { + 730: "CS2", + 440: "TF2", + 570: "Dota 2"} +appid = 753 +contextid = 6 +console_users = set() +redeem_users = set() +bot_redeem_users = {} +bot_redeem_messages = {} +redeem_messages = {} +twofa_tasks = {} +inventory_cache = {} bot = Bot(token=API_TOKEN) dp = Dispatcher() -# Classes class AdminFilter(BaseFilter): async def __call__(self, message: Message) -> bool: return message.from_user.id == ADMIN_ID -# Functions +class AdminCallbackFilter(BaseFilter): + async def __call__(self, callback: CallbackQuery) -> bool: + return callback.from_user.id == ADMIN_ID + def get_currency_name(currency_id: int) -> str: if currency_id == 1: return "USD" @@ -40,377 +52,968 @@ def get_currency_name(currency_id: int) -> str: return "" else: return "???" + +def get_inventory_icon(desc: dict) -> str: + tags = desc.get("tags", []) + for tag in tags: + category = tag.get("category", "") + name = tag.get("localized_tag_name", "") + if category == "item_class": + if "Trading Card" in name: + return "🃏" + if "Emoticon" in name: + return "😀" + if "Profile Background" in name: + return "🖼" + if "Booster Pack" in name: + return "📦" + if "Steam Gems" in name: + return "💎" + if "Gift" in name: + return "🎁" + item_type = desc.get("type", "").lower() + if "knife" in item_type: + return "🔪" + if "pistol" in item_type: + return "🔫" + if "rifle" in item_type: + return "🎯" + if "graffiti" in item_type: + return "🎨" + if "music kit" in item_type: + return "🎵" + return "📦" def format_asf(data: dict) -> str: result = data.get("Result", {}) config = result.get("GlobalConfig", {}) - - lines = [] - - lines.append(f"Версия ASF: {result.get('Version')}") - can_update = result.get("CanUpdate") - if result.get("CanUpdate"): - lines.append("🔄 Доступно обновление") + current_version = result.get("Version") + latest_version = result.get("LatestVersion", current_version) + lines = [f"Версия ASF: {current_version}"] + if current_version != latest_version: + lines.append(f"Доступно обновление: {latest_version}") memory_kb = result.get("MemoryUsage", 0) memory_mb = memory_kb / 1024 lines.append(f"Используется памяти: {memory_mb:.2f} MB") lines.append(f"Работает: {get_uptime(result.get('ProcessStartTime'))}") - return "\n".join(lines) def get_uptime(start_time_str: str) -> str: - # убираем лишнюю точность (Python не любит 7 знаков) start_time_str = start_time_str[:26] + "Z" - start_time = datetime.fromisoformat(start_time_str.replace("Z", "+00:00")) now = datetime.now(timezone.utc) - delta = now - start_time - days = delta.days hours, remainder = divmod(delta.seconds, 3600) minutes, seconds = divmod(remainder, 60) - return f"{days}д {hours}ч {minutes}м {seconds}с" +def get_bot_status_icon(bot: dict) -> str: + online = bot.get("IsConnectedAndLoggedOn", False) + farming = bot.get("CardsFarmer", {}).get("NowFarming", False) + idle_games = bot.get("BotConfig", {}).get("GamesPlayedWhileIdle", []) + loaded = bool(bot.get("BotName")) and bool(bot.get("SteamID")) + if not loaded: + return "🔄" + if farming: + return "🎴" + if idle_games and online: + return "🎮" + if online: + return "🟢" + return "🔴" + def format_bot_ui(bot: dict) -> str: - # основные статусы - online = "🟢 Онлайн" if bot.get("IsConnectedAndLoggedOn") else "❌ Оффлайн" + online = get_bot_status_icon(bot) now_farming = bot.get("CardsFarmer", {}).get("NowFarming") - if now_farming: - status_line = "🎮 Фармит: играть нельзя!" + status_line = "Идет фарм карточек" else: - status_line = "🎮 Не фармит: можно играть!" - + status_line = "Карточки не фармится" time = bot.get("CardsFarmer", {}).get("TimeRemaining", "00:00:00") games_to_farm = len(bot.get("CardsFarmer", {}).get("GamesToFarm", [])) - twofa = "есть" if bot.get("HasMobileAuthenticator") else "нет" - - # баланс - balance_raw = bot.get("WalletBalance", 0) - balance = balance_raw / 100 - - currency_id = bot.get("WalletCurrency", 0) - currency = get_currency_name(currency_id) - - # ключи + balance = bot.get("WalletBalance", 0) / 100 + currency = get_currency_name(bot.get("WalletCurrency", 0)) redeem = bot.get("GamesToRedeemInBackgroundCount", 0) - steam_id = bot.get("SteamID") - steam_trade_token = bot.get("BotConfig", {}).get("SteamTradeToken") - + nickname = html.escape(str(bot.get("Nickname") or "")) + profile_url = f"https://steamcommunity.com/profiles/{steam_id}" text = ( - f"🤖 {bot.get('BotName')} ({bot.get('Nickname')})\n\n" - - f"{online}\n" - f"{status_line}\n" + f"{online}" + f" {bot.get('BotName') or 'Загрузка...'}" + f"({nickname})\n" + f"{status_line}\n") + idle_games = bot.get("BotConfig", {}).get("GamesPlayedWhileIdle", []) + if idle_games: + names = [GAMES.get(g, str(g)) for g in idle_games] + if bot.get("IsConnectedAndLoggedOn"): + idle_text = f"Idle: включен ({', '.join(names)})" + else: + idle_text = f"Idle: включен ({', '.join(names)}) применится после запуска" + text += f"{idle_text}\n" + else: + text += "Idle: выключен\n" + text += ( f"Steam id: {steam_id}\n" - f"Trade Token: {steam_trade_token}\n" - f"🔐 2FA: {twofa}\n\n" - - f"💰 Баланс: {balance:.2f} {currency}\n" - ) - - # ключи (только если есть) + f"2FA: {twofa}\n\n" + f"Баланс: {balance:.2f} {currency}\n") if redeem > 0: - text += f"📦 Ключей в очереди: {redeem}\n" - - # если фармит → показываем время и очередь игр + text += f"\nКлючей в очереди: {redeem}\n" if now_farming: - text += f"\n⏱ Осталось: {time}\n" - + text += f"\nОсталось: {time}\n" if games_to_farm > 0: - text += f"📚 В очереди игр: {games_to_farm}\n" - + text += f"В очереди игр: {games_to_farm}\n" return text def get_asf_status_text(): - response = requests.get( - ASF_URL, - headers={"Authentication": ASF_PASSWORD} - ) - + response = requests.get(ASF_URL, headers={"Authentication": ASF_PASSWORD}) if response.status_code != 200: return f"Ошибка API: {response.status_code}" - data = response.json() - if not data.get("Success"): return "ASF вернул ошибку" - status_text = format_asf(data) return f"Панель управления ASF\n\n{status_text}" -def get_2fa_code(bot_name: str) -> str: - response = requests.get( - f"http://127.0.0.1:1242/Api/Bot/{bot_name}/TwoFactorAuthentication/Token", - headers={"Authentication": ASF_PASSWORD} - ) - - if response.status_code != 200: - return f"Ошибка HTTP {response.status_code}" - - data = response.json() - - if not data.get("Success"): - return "Ошибка ASF" - - try: - return data["Result"][bot_name]["Result"] - except: - return "Не удалось получить код" +async def get_2fa_code(bot_name: str) -> str: + async with aiohttp.ClientSession() as session: + async with session.get(f"http://127.0.0.1:1242/Api/Bot/{bot_name}/TwoFactorAuthentication/Token", headers={"Authentication": ASF_PASSWORD}) as response: + if response.status != 200: + return f"Ошибка HTTP {response.status}" + data = await response.json() + if not data.get("Success"): + return "Ошибка ASF" + try: + return data["Result"][bot_name]["Result"] + except: + return "Не удалось получить код" def get_farm_summary(bots: dict) -> str: total_games = 0 total_time_seconds = 0 - for bot in bots.values(): farmer = bot.get("CardsFarmer", {}) - - # если бот фармит if farmer.get("NowFarming"): games = farmer.get("CurrentGamesFarming", []) total_games += len(games) - - # время time_str = farmer.get("TimeRemaining", "00:00:00") h, m, s = map(int, time_str.split(":")) total_time_seconds += h * 3600 + m * 60 + s - - # если ничего не фармится if total_games == 0: - return "❌ На аккаунтах нечего не фармиться" - - # перевод времени обратно + return "На аккаунтах нечего не фармиться" hours = total_time_seconds // 3600 minutes = (total_time_seconds % 3600) // 60 - return ( - f"🎮 Игр фармится: {total_games}\n" - f"⏱ Осталось времени: {hours}ч {minutes}м\n" - f"🃏 Карт осталось: ~{total_games * 3}" # грубая оценка - ) + f"Игр фармится: {total_games}\n" + f"Осталось времени: {hours}ч {minutes}м\n" + f"Карт осталось: ~{total_games * 3}") + +async def get_confirmations(bot_name: str): + async with aiohttp.ClientSession() as session: + async with session.get(f"http://127.0.0.1:1242/Api/Bot/{bot_name}/TwoFactorAuthentication/Confirmations", headers={"Authentication": ASF_PASSWORD}) as response: + if response.status != 200: + return [] + data = await response.json() + if not data.get("Success"): + return [] + try: + return data["Result"][bot_name]["Result"] + except: + return [] + +async def get_inventory(bot_name: str, appid: int, contextid: int = 2): + url = (f"http://127.0.0.1:1242/Api/Bot/{bot_name}/Inventory/{appid}/{contextid}") + async with aiohttp.ClientSession() as session: + async with session.get(url, headers={"Authentication": ASF_PASSWORD}) as response: + if response.status != 200: + return None + data = await response.json() + if not data.get("Success"): + return None + try: + return data["Result"] + except: + return None + +async def render_inventory_page(callback: CallbackQuery, bot_name: str, inventory_type: str, appid: int, contextid: int, page: int): + await callback.message.edit_text("Загрузка inventory...") + inventory = await get_inventory(bot_name, appid, contextid) + if not inventory: + await callback.message.edit_text("Не удалось загрузить inventory") + return + bot_inventory = inventory.get(bot_name, {}) + assets = bot_inventory.get("Assets", []) + descriptions = bot_inventory.get("Descriptions", []) + if not assets: + await callback.message.edit_text("Инвентарь пуст", reply_markup=InlineKeyboardMarkup(inline_keyboard=[[InlineKeyboardButton(text="Назад", callback_data=f"inventory_{bot_name}")]])) + return + desc_map = {} + for desc in descriptions: + key = (str(desc.get("classid")), str(desc.get("instanceid"))) + desc_map[key] = desc + total_pages = (len(assets) + PAGE_SIZE - 1) // PAGE_SIZE + start = page * PAGE_SIZE + end = start + PAGE_SIZE + page_assets = assets[start:end] + lines = [] + marketable_count = 0 + tradable_count = 0 + cards_count = 0 + emotes_count = 0 + backgrounds_count = 0 + gems_count = 0 + for asset in page_assets: + key = (str(asset.get("classid")), str(asset.get("instanceid"))) + desc = desc_map.get(key, {}) + name = desc.get("market_name", "Unknown") + amount = asset.get("amount", 1) + tradable = ("🔄" if desc.get("tradable") else "❌") + marketable = ("💰" if desc.get("marketable") else "🔒") + icon = get_inventory_icon(desc) + if desc.get("marketable"): + marketable_count += 1 + if desc.get("tradable"): + tradable_count += 1 + if icon == "🃏": + cards_count += amount + elif icon == "😀": + emotes_count += amount + elif icon == "🖼": + backgrounds_count += amount + elif icon == "💎": + gems_count += amount + lines.append(f"{icon} {tradable}{marketable} {name} x{amount}") + inv_name = ("CS2" if appid == 730 else "Steam") + stats = ( + f"📦 Items: {len(assets)}\n" + f"💰 Marketable: {marketable_count}\n" + f"🔄 Tradable: {tradable_count}") + if appid == 753: + stats += ( + f"\n🃏 Cards: {cards_count}" + f"\n😀 Emotes: {emotes_count}" + f"\n🖼 Backgrounds: {backgrounds_count}" + f"\n💎 Gems: {gems_count}") + text = ( + f"📦 {inv_name} Inventory {bot_name}\n" + f"📄 Страница {page + 1}/{total_pages}\n\n" + f"{stats}\n\n" + + "\n".join(lines)) + nav_buttons = [] + if page > 0: + nav_buttons.append(InlineKeyboardButton(text="◀", callback_data=f"invpage|{inventory_type}|{bot_name}|{page - 1}")) + if page < total_pages - 1: + nav_buttons.append(InlineKeyboardButton(text="▶", callback_data=f"invpage|{inventory_type}|{bot_name}|{page + 1}")) + keyboard = [] + if nav_buttons: + keyboard.append(nav_buttons) + keyboard.append([InlineKeyboardButton(text="Назад", callback_data=f"inventory_{bot_name}")]) + await callback.message.edit_text(text[:4000], reply_markup=InlineKeyboardMarkup(inline_keyboard=keyboard)) def format_bot(bot_data: dict) -> str: return json.dumps(bot_data, indent=2, ensure_ascii=False) def main_keyboard(): return InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text="🔄 Обновить", callback_data="refresh")], - [InlineKeyboardButton(text="🤖 Боты", callback_data="bots")] + [InlineKeyboardButton(text="Обновить", callback_data="refresh")], + [InlineKeyboardButton(text="Боты", callback_data="bots")], + [InlineKeyboardButton(text="Активация ключей на всех аккаунтах", callback_data="redeem_keys")], + [InlineKeyboardButton(text="Плагины", callback_data="plugins")], + [InlineKeyboardButton(text="Консоль", callback_data="console")], + [InlineKeyboardButton(text="♻ Restart ASF", callback_data="restart_asf_confirm")], ]) + def back_keyboard(): + return InlineKeyboardMarkup(inline_keyboard=[[InlineKeyboardButton(text="Назад", callback_data="back")]]) + +def games_keyboard(bot_name, is_enabled): + if is_enabled: + text = "Остановить накрутку CS2" + else: + text = "Накрутить часы CS2" return InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text="⬅️ Назад", callback_data="back")] - ]) + [InlineKeyboardButton(text=text, callback_data=f"farm|{bot_name}|730")], + [InlineKeyboardButton(text="Назад", callback_data=f"bot_{bot_name}")]]) + def bots_keyboard(bots: dict): keyboard = [] - - for name in bots.keys(): - keyboard.append([ - InlineKeyboardButton( - text=name, - callback_data=f"bot_{name}" - ) - ]) - - # кнопка назад - keyboard.append([ - InlineKeyboardButton(text="⬅️ Назад", callback_data="back") - ]) - + for name, bot in bots.items(): + status = get_bot_status_icon(bot) + loaded = (bool(bot.get("BotName")) and bool(bot.get("SteamID"))) + if not loaded: + callback_data = "bot_loading" + else: + callback_data = f"bot_{name}" + keyboard.append([InlineKeyboardButton(text=f"{status} {name}", callback_data=callback_data)]) + keyboard.append([InlineKeyboardButton(text="Назад", callback_data="back")]) return InlineKeyboardMarkup(inline_keyboard=keyboard) -# Handlers +@dp.callback_query(lambda c: c.data == "bot_loading", AdminCallbackFilter()) +async def bot_loading_handler(callback: CallbackQuery): + await callback.answer("Аккаунт ещё загружается. Попробуйте через несколько секунд.", show_alert=True) + +async def auto_update_2fa(message, bot_name): + last_code = None + message_id = message.message_id + try: + while True: + code = await get_2fa_code(bot_name) + if code != last_code: + confirmations = await get_confirmations(bot_name) + keyboard = [] + if confirmations: + keyboard.append([InlineKeyboardButton(text="Подтверждения", callback_data=f"confirm_list_{bot_name}")]) + keyboard.append([InlineKeyboardButton(text="Назад", callback_data=f"bot_{bot_name}")]) + text = ( + f"🔐 {bot_name}\n\n" + f"{code}") + try: + await message.edit_text(text, parse_mode="HTML", reply_markup=InlineKeyboardMarkup(inline_keyboard=keyboard)) + last_code = code + except: + break + await asyncio.sleep(15) + except asyncio.CancelledError: + pass + except Exception as e: + print(f"2FA updater error: {e}") + finally: + twofa_tasks.pop(message_id, None) + +async def stop_twofa_task(message_id: int): + task = twofa_tasks.pop(message_id, None) + if task: + task.cancel() + try: + await task + except: + pass + @dp.message(Command("start"), AdminFilter()) async def start_handler(message: Message): try: await message.delete() except: pass - try: text = get_asf_status_text() - - await message.answer( - text, - reply_markup=main_keyboard() - ) - + await message.answer(text, reply_markup=main_keyboard()) except Exception as e: await message.answer(f"Ошибка: {e}") -@dp.callback_query(lambda c: c.data == "bots") -async def bots_handler(callback: CallbackQuery): +@dp.callback_query(lambda c: c.data == "console", AdminCallbackFilter()) +async def console_enter(callback: CallbackQuery): await callback.answer() + console_users.add(callback.from_user.id) + await callback.message.edit_text("Консоль ASF\n\nОтправь команду (без !)", reply_markup=InlineKeyboardMarkup(inline_keyboard=[[InlineKeyboardButton(text="Назад", callback_data="console_exit")]])) +@dp.callback_query(lambda c: c.data == "console_exit", AdminCallbackFilter()) +async def console_exit(callback: CallbackQuery): + await callback.answer() + console_users.discard(callback.from_user.id) + text = get_asf_status_text() + await callback.message.edit_text(text, reply_markup=main_keyboard()) + +@dp.callback_query(lambda c: c.data == "delete_msg", AdminCallbackFilter()) +async def delete_message(callback: CallbackQuery): + await callback.answer() + try: + await callback.message.delete() + except: + pass + +@dp.callback_query(lambda c: c.data == "bots", AdminCallbackFilter()) +async def bots_handler(callback: CallbackQuery): + await stop_twofa_task(callback.message.message_id) + await callback.answer() try: response = requests.get( "http://127.0.0.1:1242/Api/Bot/ASF", headers={"Authentication": ASF_PASSWORD} ) - if response.status_code != 200: await callback.message.edit_text("Ошибка API") return - data = response.json() - if not data.get("Success"): await callback.message.edit_text("ASF вернул ошибку") return - bots = data.get("Result", {}) - count = len(bots) - summary = get_farm_summary(bots) - text = ( - f"🤖 Ботов всего: {count}\n\n" + f"Ботов всего: {count}\n\n" f"{summary}\n\n" f"Выбери бота:" ) - - await callback.message.edit_text( - text, - reply_markup=bots_keyboard(bots) - ) - + await callback.message.edit_text(text, reply_markup=bots_keyboard(bots)) except Exception as e: await callback.message.edit_text(f"Ошибка: {e}") -@dp.callback_query(lambda c: c.data.startswith("bot_")) +@dp.callback_query(lambda c: c.data and c.data.startswith("bot_"), AdminCallbackFilter()) async def bot_selected(callback: CallbackQuery): + await stop_twofa_task(callback.message.message_id) await callback.answer() - + if not callback.data: + return bot_name = callback.data.replace("bot_", "") - try: - response = requests.get( - "http://127.0.0.1:1242/Api/Bot/ASF", - headers={"Authentication": ASF_PASSWORD} - ) - + response = requests.get("http://127.0.0.1:1242/Api/Bot/ASF", headers={"Authentication": ASF_PASSWORD}) data = response.json() bots = data.get("Result", {}) - bot = bots.get(bot_name) - + if not bot or not bot.get("BotName") or not bot.get("SteamID"): + await callback.answer("Аккаунт ещё загружается. Попробуйте через несколько секунд.", show_alert=True) + return if not bot: await callback.message.edit_text("Бот не найден") return - text = format_bot_ui(bot) + keyboard = [] + if bot.get("HasMobileAuthenticator"): + keyboard.append([InlineKeyboardButton(text="2FA", callback_data=f"2fa_{bot_name}")]) + keyboard.append([InlineKeyboardButton(text="Игры", callback_data=f"games_{bot_name}")]) + keyboard.append([InlineKeyboardButton(text="Активировать ключ", callback_data=f"redeem_bot_{bot_name}")]) + keyboard.append([InlineKeyboardButton(text="Инвентарь", callback_data=f"inventory_{bot_name}")]) + keyboard.append([InlineKeyboardButton(text="Назад", callback_data="bots")]) - await callback.message.edit_text( - text, - parse_mode="HTML", - reply_markup=InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text="🔐 2FA", callback_data=f"2fa_{bot_name}")], - [InlineKeyboardButton(text="⬅️ Назад", callback_data="bots")] - ]) -) - + await callback.message.edit_text(text, parse_mode="HTML", disable_web_page_preview=True, reply_markup=InlineKeyboardMarkup(inline_keyboard=keyboard)) except Exception as e: await callback.message.edit_text(f"Ошибка: {e}") -@dp.callback_query(lambda c: c.data == "back") -async def back_button(callback: CallbackQuery): +@dp.callback_query(lambda c: c.data and c.data.startswith("games_"), AdminCallbackFilter()) +async def games(callback: CallbackQuery): await callback.answer() + if not callback.data: + return + bot_name = callback.data.replace("games_", "") + idle_enabled = is_idle_enabled(bot_name, 730) + await callback.message.edit_text("Выберите игру для накрутки часов", reply_markup=games_keyboard(bot_name, idle_enabled)) +@dp.callback_query(lambda c: c.data == "back", AdminCallbackFilter()) +async def back_button(callback: CallbackQuery): + redeem_users.discard(callback.from_user.id) + redeem_messages.pop(callback.from_user.id, None) + console_users.discard(callback.from_user.id) + bot_redeem_users.pop(callback.from_user.id, None) + bot_redeem_messages.pop(callback.from_user.id, None) + await stop_twofa_task(callback.message.message_id) + await callback.answer() try: text = get_asf_status_text() - - await callback.message.edit_text( - text, - reply_markup=main_keyboard() - ) - + await callback.message.edit_text(text, reply_markup=main_keyboard()) except Exception as e: await callback.message.edit_text(f"Ошибка: {e}") -@dp.callback_query(lambda c: c.data == "refresh") +@dp.callback_query(lambda c: c.data == "refresh", AdminCallbackFilter()) async def refresh_handler(callback: CallbackQuery): await callback.answer() - try: text = get_asf_status_text() - - await callback.message.edit_text( - text, - reply_markup=main_keyboard() - ) - + await callback.message.edit_text(text, reply_markup=main_keyboard()) except Exception as e: await callback.message.edit_text(f"Ошибка: {e}") -@dp.callback_query(lambda c: c.data.startswith("2fa_") and not c.data.startswith("2fa_refresh_")) +@dp.callback_query(lambda c: c.data == "restart_asf_confirm", AdminCallbackFilter()) +async def restart_asf_confirm(callback: CallbackQuery): + await callback.answer() + await callback.message.edit_text( + "♻ Точно перезапустить ASF?\n\n" + "Во время перезапуска IPC будет недоступен несколько секунд.", + reply_markup=InlineKeyboardMarkup( + inline_keyboard=[ + [InlineKeyboardButton(text="Да", callback_data="restart_asf")], + [InlineKeyboardButton(text="Нет", callback_data="back")]])) + +@dp.callback_query(lambda c: c.data == "restart_asf", AdminCallbackFilter()) +async def restart_asf(callback: CallbackQuery): + await callback.answer() + try: + await callback.message.edit_text("♻ ASF перезапускается...") + response = requests.post("http://127.0.0.1:1242/Api/Command", headers={"Authentication": ASF_PASSWORD}, json={"Command": "!restart"}) + if response.status_code != 200: + await callback.message.edit_text(f"Ошибка HTTP {response.status_code}", reply_markup=main_keyboard()) + return + await asyncio.sleep(6) + text = get_asf_status_text() + await callback.message.edit_text(f"ASF успешно перезапущен\n\n{text}", reply_markup=main_keyboard()) + except Exception as e: + await callback.message.edit_text(f"Ошибка: {e}", reply_markup=main_keyboard()) + +@dp.callback_query(lambda c: c.data and c.data.startswith("2fa_") and not c.data.startswith("2fa_refresh_"), AdminCallbackFilter()) async def twofa_handler(callback: CallbackQuery): await callback.answer() - + if not callback.data: + return bot_name = callback.data.replace("2fa_", "") + message_id = callback.message.message_id + # убиваем старую task + old_task = twofa_tasks.pop(message_id, None) + if old_task: + old_task.cancel() + try: + await old_task + except: + pass + # мгновенный экран загрузки + await callback.message.edit_text( + f"🔐 {bot_name}\n\nЗагрузка 2FA...", + reply_markup=InlineKeyboardMarkup(inline_keyboard=[[InlineKeyboardButton(text="Назад", callback_data=f"bot_{bot_name}")]])) + # запускаем updater + task = asyncio.create_task(auto_update_2fa(callback.message, bot_name)) + twofa_tasks[message_id] = task +@dp.callback_query(lambda c: c.data and c.data.startswith("confirm_") and not c.data.startswith("confirm_list_") and not c.data.startswith("confirm_all_"), AdminCallbackFilter()) +async def confirm_trades(callback: CallbackQuery): + await callback.answer() + bot_name = callback.data.replace("confirm_", "") try: - code = get_2fa_code(bot_name) + response = requests.post(f"http://127.0.0.1:1242/Api/Bot/{bot_name}/TwoFactorAuthentication/Confirmations/Accept", headers={"Authentication": ASF_PASSWORD}) + if response.status_code != 200: + await callback.answer("Ошибка HTTP", show_alert=True) + return + await callback.answer("Подтверждено", show_alert=True) + # обновляем экран + await twofa_handler(callback) + except Exception as e: + await callback.message.edit_text(f"Ошибка: {e}") - text = ( - f"🤖 {bot_name}\n\n" - f"{code}" - ) +@dp.callback_query(lambda c: c.data == "plugins", AdminCallbackFilter()) +async def plugins_handler(callback: CallbackQuery): + await stop_twofa_task(callback.message.message_id) + await callback.answer() + try: + response = requests.get("http://127.0.0.1:1242/Api/Plugins", headers={"Authentication": ASF_PASSWORD}) + if response.status_code != 200: + await callback.message.edit_text("Ошибка API") + return + data = response.json() + if not data.get("Success"): + await callback.message.edit_text("ASF вернул ошибку") + return + plugins = data.get("Result", []) + if not plugins: + text = "Плагины не установлены" + else: + text = "Плагины ASF:\n\n" + for plugin in plugins: + name = plugin.get("Name", "???") + version = plugin.get("Version", "???") + text += f"{name} ({version})\n" + await callback.message.edit_text( + text.strip(), + reply_markup=InlineKeyboardMarkup(inline_keyboard=[[InlineKeyboardButton(text="Назад", callback_data="back")]])) + except Exception as e: + await callback.message.edit_text(f"Ошибка: {e}") +@dp.callback_query(lambda c: c.data and c.data.startswith("confirm_list_"), AdminCallbackFilter()) +async def confirm_list(callback: CallbackQuery): + await stop_twofa_task(callback.message.message_id) + await callback.answer() + if not callback.data: + return + bot_name = callback.data.replace("confirm_list_", "") + try: + confirmations = await get_confirmations(bot_name) + if not confirmations: + text = "Подтверждений нет" + else: + text = f" {bot_name}\n\nПодтверждения:\n\n" + for conf in confirmations: + text += ( + f"- {conf['type_name']}\n" + f"ID: {conf['id']}\n\n" + ) + await callback.message.edit_text(text, parse_mode="HTML", + reply_markup=InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text="Подтвердить всё", callback_data=f"confirm_all_{bot_name}")], + [InlineKeyboardButton(text="Назад", callback_data=f"2fa_{bot_name}")]])) + except Exception as e: + await callback.message.edit_text(f"Ошибка: {e}") + +@dp.callback_query(lambda c: c.data and c.data.startswith("confirm_all_"), AdminCallbackFilter()) +async def confirm_all(callback: CallbackQuery): + await stop_twofa_task(callback.message.message_id) + await callback.answer() + if not callback.data: + return + bot_name = callback.data.replace("confirm_all_", "") + try: + response = requests.post(f"http://127.0.0.1:1242/Api/Bot/{bot_name}/TwoFactorAuthentication/Confirmations/Accept", headers={"Authentication": ASF_PASSWORD}) + if response.status_code != 200: + await callback.answer("Ошибка HTTP", show_alert=True) + return + # заново получаем список + confirmations = await get_confirmations(bot_name) + if not confirmations: + text = f" {bot_name}\n\nПодтверждений больше нет" + else: + text = f" {bot_name}\n\nПодтверждения:\n\n" + for conf in confirmations: + text += ( + f"- {conf['type_name']}\n" + f"ID: {conf['id']}\n\n" + ) await callback.message.edit_text( text, parse_mode="HTML", reply_markup=InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text="🔄 Обновить код", callback_data=f"2fa_refresh_{bot_name}")], - [InlineKeyboardButton( - text="⬅️ Назад", - callback_data=f"bot_{bot_name}" -)] - ]) - ) - + [InlineKeyboardButton(text="Подтвердить всё", callback_data=f"confirm_all_{bot_name}")], + [InlineKeyboardButton(text="Назад", callback_data=f"2fa_{bot_name}")]])) + await callback.answer(" Все подтверждено", show_alert=True) except Exception as e: await callback.message.edit_text(f"Ошибка: {e}") -@dp.callback_query(lambda c: c.data.startswith("2fa_refresh_")) -async def twofa_refresh(callback: CallbackQuery): - await callback.answer() +def is_bot_playing(bot_name: str) -> bool: + response = requests.get("http://127.0.0.1:1242/Api/Bot/ASF", headers={"Authentication": ASF_PASSWORD}) + if response.status_code != 200: + return False + data = response.json() + bot = data.get("Result", {}).get(bot_name, {}) + # правильный способ + return bool(bot.get("PlayingBlocked", False)) or bool(bot.get("CurrentGamesPlayed", [])) - bot_name = callback.data.replace("2fa_refresh_", "") +def is_idle_enabled(bot_name: str, app_id: int) -> bool: + response = requests.get(f"http://127.0.0.1:1242/Api/Bot/{bot_name}", headers={"Authentication": ASF_PASSWORD}) + if response.status_code != 200: + return False + data = response.json() + bot = data.get("Result", {}).get(bot_name, {}) + config = bot.get("BotConfig", {}) + return app_id in config.get("GamesPlayedWhileIdle", []) +async def reload_bot(bot_name: str): try: - code = get_2fa_code(bot_name) + requests.post("http://127.0.0.1:1242/Api/Command", headers={"Authentication": ASF_PASSWORD}, json={"Command": f"!reload {bot_name}"}) + except: + pass - # 🔥 проверяем есть ли уже этот код в тексте - if callback.message.text and code in callback.message.text: - await callback.answer("Код ещё не обновился") +@dp.callback_query(lambda c: c.data and c.data.startswith("farm|"), AdminCallbackFilter()) +async def farm_game(callback: CallbackQuery): + await callback.answer() + if not callback.data: + return + _, bot_name, app_id = callback.data.split("|") + app_id = int(app_id) + try: + # получаем текущий конфиг + response = requests.get(f"http://127.0.0.1:1242/Api/Bot/{bot_name}", headers={"Authentication": ASF_PASSWORD}) + data = response.json() + bot_data = data.get("Result", {}).get(bot_name, {}) + config = bot_data.get("BotConfig", {}) + idle_games = config.get("GamesPlayedWhileIdle", []) + # переключение + if app_id in idle_games: + idle_games.remove(app_id) + action = "Idle выключен" + else: + idle_games.append(app_id) + action = "Idle включен" + # обновляем конфиг + config["GamesPlayedWhileIdle"] = idle_games + save = requests.post( + f"http://127.0.0.1:1242/Api/Bot/{bot_name}", headers={"Authentication": ASF_PASSWORD}, json={"BotConfig": config}) + if save.status_code != 200: + await callback.answer("Ошибка сохранения", show_alert=True) return - - new_text = ( - f"🤖 {bot_name}\n\n" - f"{code}" - ) - - await callback.message.edit_text( - new_text, - parse_mode="HTML", - reply_markup=InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text="🔄 Обновить код", callback_data=f"2fa_refresh_{bot_name}")], - [InlineKeyboardButton( - text="⬅️ Назад", - callback_data=f"bot_{bot_name}" -)] - ]) - ) - + asyncio.create_task(reload_bot(bot_name)) + await callback.answer(action, show_alert=True) + try: + await callback.message.edit_reply_markup(reply_markup=games_keyboard(bot_name, app_id in idle_games)) + except: + pass except Exception as e: - await callback.message.edit_text(f"Ошибка: {e}") + await callback.answer(f"Ошибка: {e}", show_alert=True) + +@dp.callback_query(lambda c: c.data and c.data.startswith("inventory_"), AdminCallbackFilter()) +async def inventory_menu(callback: CallbackQuery): + await callback.answer() + bot_name = callback.data.replace("inventory_", "") + await callback.message.edit_text("Проверка inventory...") + cs2_inventory = await get_inventory(bot_name, 730, 2) + steam_inventory = await get_inventory(bot_name, 753, 6) + cs2_assets = [] + steam_assets = [] + try: + cs2_assets = (cs2_inventory.get(bot_name, {}).get("Assets", [])) + except: + pass + try: + steam_assets = (steam_inventory.get(bot_name, {}).get("Assets", [])) + except: + pass + if not cs2_assets and not steam_assets: + await callback.message.edit_text(f"📦 Инвентарь {bot_name} пуст", reply_markup=InlineKeyboardMarkup(inline_keyboard=[[InlineKeyboardButton(text="Назад", callback_data=f"bot_{bot_name}")]])) + return + keyboard = [] + if cs2_assets: + keyboard.append([InlineKeyboardButton(text=f"CS2 ({len(cs2_assets)})", callback_data=f"inv_cs2_{bot_name}")]) + if steam_assets: + keyboard.append([InlineKeyboardButton(text=f"Steam ({len(steam_assets)})", callback_data=f"inv_steam_{bot_name}")]) + keyboard.append([InlineKeyboardButton(text="Назад", callback_data=f"bot_{bot_name}")]) + await callback.message.edit_text( + ( + f"📦 Инвентарь {bot_name}\n\n" + "🔄 — можно трейдить\n" + "💰 — можно продавать\n" + "🔒 — нельзя продавать\n" + "❌ — нельзя трейдить" + ), + reply_markup=InlineKeyboardMarkup(inline_keyboard=keyboard) + ) + +@dp.callback_query(lambda c: c.data and c.data.startswith("inv_cs2_"), AdminCallbackFilter()) +async def cs2_inventory(callback: CallbackQuery): + await callback.answer() + bot_name = callback.data.replace("inv_cs2_", "") + await render_inventory_page(callback, bot_name, "cs2", 730, 2, 0) + +@dp.callback_query(lambda c: c.data and c.data.startswith("inv_steam_"), AdminCallbackFilter()) +async def steam_inventory(callback: CallbackQuery): + await callback.answer() + bot_name = callback.data.replace("inv_steam_", "") + await render_inventory_page(callback, bot_name, "steam", 753, 6, 0) + +@dp.callback_query(lambda c: c.data and c.data.startswith("invpage|"), AdminCallbackFilter()) +async def inventory_page(callback: CallbackQuery): + await callback.answer() + _, inv_type, bot_name, page = callback.data.split("|") + page = int(page) + if inv_type == "cs2": + await render_inventory_page(callback, bot_name, "cs2", 730, 2, page) + else: + await render_inventory_page(callback, bot_name, "steam", 753, 6, page) + +async def redeem_key(bot_name: str, key: str): + async with aiohttp.ClientSession() as session: + async with session.post("http://127.0.0.1:1242/Api/Command", headers={"Authentication": ASF_PASSWORD}, json={"Command": f"!redeem {bot_name} {key}"}) as response: + if response.status != 200: + return False, f"HTTP_ERROR_{response.status}" + data = await response.json() + if not data.get("Success"): + return False, "ASF_ERROR" + return True, str(data.get("Result", "")) + +@dp.callback_query(lambda c: c.data == "redeem_keys", AdminCallbackFilter()) +async def redeem_keys_menu(callback: CallbackQuery): + await callback.answer() + redeem_users.add(callback.from_user.id) + redeem_messages[callback.from_user.id] = callback.message + await callback.message.edit_text( + "Отправьте ключ Steam для активации\n\n" + "Можно отправить сразу несколько ключей.\n\n" + "Пример:\n" + "AAAAA-BBBBB-CCCCC\nDDDDD-EEEEE-FFFFF", + parse_mode="HTML", + reply_markup=InlineKeyboardMarkup(inline_keyboard=[[InlineKeyboardButton(text="Назад", callback_data="back")]])) + +@dp.callback_query(lambda c: c.data and c.data.startswith("redeem_bot_"), AdminCallbackFilter()) +async def redeem_bot_menu(callback: CallbackQuery): + await callback.answer() + bot_name = callback.data.replace("redeem_bot_", "") + bot_redeem_users[callback.from_user.id] = bot_name + bot_redeem_messages[callback.from_user.id] = callback.message + await callback.message.edit_text( + f"Отправьте ключ Steam для активации на аккаунте:\n" + f"{bot_name}\n\n" + f"Можно отправить сразу несколько ключей.", + parse_mode="HTML", + reply_markup=InlineKeyboardMarkup(inline_keyboard=[[InlineKeyboardButton(text="Назад", callback_data=f"bot_{bot_name}")]])) + +@dp.message(AdminFilter()) +async def text_router(message: Message): + # console mode + if message.from_user.id in console_users: + command = message.text.strip() + try: + await message.delete() + except: + pass + try: + response = requests.post("http://127.0.0.1:1242/Api/Command", headers={"Authentication": ASF_PASSWORD}, json={"Command": f"!{command}"}) + if response.status_code != 200: + text = f"Ошибка HTTP {response.status_code}" + else: + data = response.json() + if not data.get("Success"): + text = "Ошибка ASF" + else: + result = html.escape(str(data.get("Result", "Нет ответа"))) + text = ( + f"Команда: {command}\n\n" + f"Ответ:\n{result}" + ) + await message.answer(text, parse_mode="HTML", reply_markup=InlineKeyboardMarkup(inline_keyboard=[[InlineKeyboardButton(text="Удалить", callback_data="delete_msg")]])) + except Exception as e: + await message.answer(f"Ошибка: {e}") + return + # single bot redeem mode + if message.from_user.id in bot_redeem_users: + bot_name = bot_redeem_users.pop(message.from_user.id) + menu_message = bot_redeem_messages.pop(message.from_user.id, None) + if menu_message: + try: + response = requests.get("http://127.0.0.1:1242/Api/Bot/ASF",headers={"Authentication": ASF_PASSWORD}) + data = response.json() + bot_data = data.get("Result", {}).get(bot_name) + if bot_data: + await menu_message.edit_text( + format_bot_ui(bot_data), + parse_mode="HTML", + disable_web_page_preview=True, + reply_markup=InlineKeyboardMarkup( + inline_keyboard=[ + [InlineKeyboardButton(text="2FA", callback_data=f"2fa_{bot_name}")] + if bot_data.get("HasMobileAuthenticator") else [], + [InlineKeyboardButton(text="Игры", callback_data=f"games_{bot_name}")], + [InlineKeyboardButton(text="Активировать ключ", callback_data=f"redeem_bot_{bot_name}")], + [InlineKeyboardButton(text="Инвентарь", callback_data=f"inventory_{bot_name}")], + [InlineKeyboardButton(text="Назад", callback_data="bots")]])) + except: + pass + try: + await bot.delete_message(chat_id=message.chat.id, message_id=message.message_id) + except: + pass + raw_text = message.text.upper() + keys = re.findall(r"[A-Z0-9]{5}(?:-[A-Z0-9]{5}){2}", raw_text) + keys = list(dict.fromkeys(keys)) + if not keys: + await message.answer("Ключи не найдены") + return + checking = await message.answer( + f"Аккаунт: {bot_name}\n" + f"Найдено ключей: {len(keys)}\n" + f"Начинаю активацию...", + parse_mode="HTML") + success_count = 0 + failed_count = 0 + results = [] + for key in keys: + success, result = await redeem_key(bot_name, key) + await asyncio.sleep(2) + if not success or not result: + failed_count += 1 + results.append(f"❌ {key}: ASF ERROR") + continue + result_lower = result.lower() + if "ok/nodetail" in result_lower or "ok/" in result_lower: + success_count += 1 + results.append(f"✅ {key}: активировано") + elif "ratelimited" in result_lower: + failed_count += 1 + results.append(f"⏳ {key}: rate limit") + elif "alreadypurchased" in result_lower: + failed_count += 1 + results.append(f"⚠️ {key}: игра уже есть") + elif "duplicateactivationcode" in result_lower: + failed_count += 1 + results.append(f"❌ {key}: ключ уже использован") + elif "regionlocked" in result_lower: + failed_count += 1 + results.append(f"🌍 {key}: регион лок") + elif "invalidactivationcode" in result_lower or "invalid" in result_lower: + failed_count += 1 + results.append(f"❌ {key}: неверный ключ") + else: + failed_count += 1 + results.append(f"❔ {key}: неизвестный ответ") + text = ( + f"Результат активации для {bot_name}\n\n" + f"✅ Успешно: {success_count}\n" + f"❌ Ошибок: {failed_count}\n\n" + + "\n".join(results[:50])) + if len(text) > 4000: + text = text[:4000] + "\n\n...обрезано" + await checking.edit_text(text, parse_mode="HTML") + return + # redeem mode + if message.from_user.id in redeem_users: + redeem_users.discard(message.from_user.id) + menu_message = redeem_messages.pop(message.from_user.id, None) + if menu_message: + try: + await menu_message.edit_text(get_asf_status_text(), reply_markup=main_keyboard()) + except: + pass + try: + await bot.delete_message(chat_id=message.chat.id, message_id=message.message_id) + except: + pass + raw_text = message.text.upper() + keys = re.findall(r"[A-Z0-9]{5}(?:-[A-Z0-9]{5}){2}", raw_text) + keys = list(dict.fromkeys(keys)) + if not keys: + await message.answer("Ключи не найдены") + return + checking = await message.answer(f"Найдено ключей: {len(keys)}\nНачинаю активацию...") + try: + response = requests.get("http://127.0.0.1:1242/Api/Bot/ASF", headers={"Authentication": ASF_PASSWORD}) + data = response.json() + bots = list(data.get("Result", {}).keys()) + success_count = 0 + failed_count = 0 + results = [] + for key in keys: + activated = False + key_report = [] + for bot_name in bots: + success, result = await redeem_key(bot_name, key) + await asyncio.sleep(2) + if not success or not result: + key_report.append(f"❌ {bot_name}: ASF ERROR") + continue + print(result) + result_lower = result.lower() + bot_line = "" + for line in result.splitlines(): + if f"<{bot_name.lower()}>" in line.lower(): + bot_line = line + break + if bot_line: + parsed = bot_line.lower() + else: + parsed = result_lower + if "ok/nodetail" in parsed or "ok/" in parsed: + key_report.append(f"✅ {bot_name}: активировано") + success_count += 1 + activated = True + break + elif "ratelimited" in parsed: + key_report.append(f"⏳ {bot_name}: rate limit") + continue + elif "alreadypurchased" in parsed or "already purchased" in parsed: + key_report.append(f"⚠️ {bot_name}: игра уже есть") + continue + elif "duplicateactivationcode" in parsed: + key_report.append(f"❌ {bot_name}: ключ уже использован") + break + elif "regionlocked" in parsed: + key_report.append(f"🌍 {bot_name}: регион лок") + continue + elif "invalidactivationcode" in parsed or "invalid" in parsed: + key_report.append(f"❌ {bot_name}: неверный ключ") + break + else: + key_report.append(f"❔ {bot_name}: неизвестный ответ") + continue + if not activated: + failed_count += 1 + results.append(f"\n🔑 {key}\n" + "\n".join(key_report)) + text = ( + f"Результат активации\n\n" + f"✅ Успешно: {success_count}\n" + f"❌ Ошибок: {failed_count}\n\n" + + "\n".join(results[:50])) + if len(text) > 4000: + text = text[:4000] + "\n\n...обрезано" + await checking.edit_text(text) + + except Exception as e: + await checking.edit_text(f"Ошибка: {e}") + return -# Start async def main(): await dp.start_polling(bot)