diff --git a/bot/__init__.py b/bot/__init__.py
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/bot/__init__.py
@@ -0,0 +1 @@
+
diff --git a/bot/api/__init__.py b/bot/api/__init__.py
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/bot/api/__init__.py
@@ -0,0 +1 @@
+
diff --git a/bot/api/asf.py b/bot/api/asf.py
new file mode 100644
index 0000000..ba38e50
--- /dev/null
+++ b/bot/api/asf.py
@@ -0,0 +1,118 @@
+import aiohttp
+import requests
+
+from bot.config import ASF_PASSWORD, ASF_URL
+
+IPC_BASE_URL = "http://127.0.0.1:1242"
+
+
+def _headers() -> dict[str, str | None]:
+ return {"Authentication": ASF_PASSWORD}
+
+
+def asf_get(path: str) -> requests.Response:
+ return requests.get(f"{IPC_BASE_URL}{path}", headers=_headers())
+
+
+def asf_post(path: str, payload: dict | None = None) -> requests.Response:
+ return requests.post(f"{IPC_BASE_URL}{path}", headers=_headers(), json=payload)
+
+
+def get_asf_status() -> requests.Response:
+ return requests.get(ASF_URL, headers=_headers()) # type: ignore[arg-type]
+
+
+def get_all_bots() -> requests.Response:
+ return asf_get("/Api/Bot/ASF")
+
+
+def get_bot(bot_name: str) -> requests.Response:
+ return asf_get(f"/Api/Bot/{bot_name}")
+
+
+def get_plugins() -> requests.Response:
+ return asf_get("/Api/Plugins")
+
+
+def send_command(command: str) -> requests.Response:
+ return asf_post("/Api/Command", {"Command": command})
+
+
+def restart_asf() -> requests.Response:
+ return send_command("!restart")
+
+
+async def get_bot_inventory(bot_name: str, appid: int, contextid: int) -> dict | None:
+ async with aiohttp.ClientSession() as session:
+ async with session.get(
+ f"{IPC_BASE_URL}/Api/Bot/{bot_name}/Inventory/{appid}/{contextid}",
+ headers=_headers(),
+ ) as response:
+ if response.status != 200:
+ return None
+ data = await response.json()
+ if not data.get("Success"):
+ return None
+ return data.get("Result")
+
+
+async def get_2fa_token(bot_name: str) -> str:
+ async with aiohttp.ClientSession() as session:
+ async with session.get(
+ f"{IPC_BASE_URL}/Api/Bot/{bot_name}/TwoFactorAuthentication/Token",
+ headers=_headers(),
+ ) 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 Exception:
+ return "Не удалось получить код"
+
+
+async def get_confirmations(bot_name: str) -> list:
+ async with aiohttp.ClientSession() as session:
+ async with session.get(
+ f"{IPC_BASE_URL}/Api/Bot/{bot_name}/TwoFactorAuthentication/Confirmations",
+ headers=_headers(),
+ ) 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 Exception:
+ return []
+
+
+async def accept_confirmations(bot_name: str) -> int:
+ async with aiohttp.ClientSession() as session:
+ async with session.post(
+ f"{IPC_BASE_URL}/Api/Bot/{bot_name}/TwoFactorAuthentication/Confirmations/Accept",
+ headers=_headers(),
+ ) as response:
+ return response.status
+
+
+def save_bot_config(bot_name: str, config: dict) -> requests.Response:
+ return asf_post(f"/Api/Bot/{bot_name}", {"BotConfig": config})
+
+
+async def redeem_key(bot_name: str, key: str) -> tuple[bool, str]:
+ async with aiohttp.ClientSession() as session:
+ async with session.post(
+ f"{IPC_BASE_URL}/Api/Command",
+ headers=_headers(),
+ 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", ""))
diff --git a/bot/config.py b/bot/config.py
new file mode 100644
index 0000000..6483519
--- /dev/null
+++ b/bot/config.py
@@ -0,0 +1,10 @@
+import os
+
+from dotenv import load_dotenv
+
+load_dotenv()
+
+API_TOKEN = os.getenv("API_TOKEN")
+ADMIN_ID = int(os.getenv("ADMIN_ID")) # type: ignore[arg-type]
+ASF_URL = os.getenv("ASF_URL")
+ASF_PASSWORD = os.getenv("ASF_PASSWORD")
diff --git a/bot/constants.py b/bot/constants.py
new file mode 100644
index 0000000..0c416ed
--- /dev/null
+++ b/bot/constants.py
@@ -0,0 +1,5 @@
+PAGE_SIZE = 40
+GAMES = {730: "CS2", 440: "TF2", 570: "Dota 2"}
+
+appid = 753
+contextid = 6
diff --git a/bot/filters.py b/bot/filters.py
new file mode 100644
index 0000000..107ad07
--- /dev/null
+++ b/bot/filters.py
@@ -0,0 +1,14 @@
+from aiogram.filters import BaseFilter
+from aiogram.types import CallbackQuery, Message
+
+from bot.config import ADMIN_ID
+
+
+class AdminFilter(BaseFilter):
+ async def __call__(self, message: Message) -> bool:
+ return message.from_user.id == ADMIN_ID # type: ignore[union-attr]
+
+
+class AdminCallbackFilter(BaseFilter):
+ async def __call__(self, callback: CallbackQuery) -> bool:
+ return callback.from_user.id == ADMIN_ID
diff --git a/bot/handlers/__init__.py b/bot/handlers/__init__.py
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/bot/handlers/__init__.py
@@ -0,0 +1 @@
+
diff --git a/bot/handlers/bots.py b/bot/handlers/bots.py
new file mode 100644
index 0000000..d324a64
--- /dev/null
+++ b/bot/handlers/bots.py
@@ -0,0 +1,97 @@
+from aiogram import Router
+from aiogram.types import CallbackQuery
+
+from bot.api.asf import get_all_bots
+from bot.filters import AdminCallbackFilter
+from bot.services.bots import is_idle_enabled, update_idle_game
+from bot.services.twofa import stop_twofa_task
+from bot.ui.formatters import format_bot_ui, get_farm_summary
+from bot.ui.keyboards import bot_details_keyboard, bots_keyboard, games_keyboard
+
+router = Router()
+
+
+@router.callback_query(lambda c: c.data == "bot_loading", AdminCallbackFilter())
+async def bot_loading_handler(callback: CallbackQuery) -> None:
+ await callback.answer(
+ "Аккаунт ещё загружается. Попробуйте через несколько секунд.",
+ show_alert=True,
+ )
+
+
+@router.callback_query(lambda c: c.data == "bots", AdminCallbackFilter())
+async def bots_handler(callback: CallbackQuery) -> None:
+ await stop_twofa_task(callback.message.message_id) # type: ignore[union-attr]
+ await callback.answer()
+ try:
+ response = get_all_bots()
+ if response.status_code != 200:
+ await callback.message.edit_text("Ошибка API") # type: ignore[union-attr]
+ return
+ data = response.json()
+ if not data.get("Success"):
+ await callback.message.edit_text("ASF вернул ошибку") # type: ignore[union-attr]
+ return
+ bots = data.get("Result", {})
+ text = f"🤖 Ботов всего: {len(bots)}\n\n{get_farm_summary(bots)}"
+ await callback.message.edit_text(text, reply_markup=bots_keyboard(bots)) # type: ignore[union-attr]
+ except Exception as error:
+ await callback.message.edit_text(f"Ошибка: {error}") # type: ignore[union-attr]
+
+
+@router.callback_query(lambda c: c.data and c.data.startswith("bot_"), AdminCallbackFilter())
+async def bot_selected(callback: CallbackQuery) -> None:
+ await stop_twofa_task(callback.message.message_id) # type: ignore[union-attr]
+ await callback.answer()
+ if not callback.data:
+ return
+ bot_name = callback.data.replace("bot_", "")
+ try:
+ data = get_all_bots().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
+ await callback.message.edit_text( # type: ignore[union-attr]
+ format_bot_ui(bot),
+ disable_web_page_preview=True,
+ reply_markup=bot_details_keyboard(bot_name, bool(bot.get("HasMobileAuthenticator"))),
+ )
+ except Exception as error:
+ await callback.message.edit_text(f"Ошибка: {error}") # type: ignore[union-attr]
+
+
+@router.callback_query(lambda c: c.data and c.data.startswith("games_"), AdminCallbackFilter())
+async def games(callback: CallbackQuery) -> None:
+ 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( # type: ignore[union-attr]
+ "⌚ Выберите игру для накрутки часов",
+ reply_markup=games_keyboard(bot_name, idle_enabled),
+ )
+
+
+@router.callback_query(lambda c: c.data and c.data.startswith("farm|"), AdminCallbackFilter())
+async def farm_game(callback: CallbackQuery) -> None:
+ await callback.answer()
+ if not callback.data:
+ return
+ _, bot_name, app_id_value = callback.data.split("|")
+ success, text, enabled = await update_idle_game(bot_name, int(app_id_value))
+ if not success:
+ await callback.answer(text, show_alert=True)
+ return
+ await callback.answer(text, show_alert=True)
+ try:
+ await callback.message.edit_reply_markup( # type: ignore[union-attr]
+ reply_markup=games_keyboard(bot_name, enabled)
+ )
+ except Exception:
+ pass
diff --git a/bot/handlers/console.py b/bot/handlers/console.py
new file mode 100644
index 0000000..70d0ec6
--- /dev/null
+++ b/bot/handlers/console.py
@@ -0,0 +1,26 @@
+from aiogram import Router
+from aiogram.types import CallbackQuery
+
+from bot.filters import AdminCallbackFilter
+from bot.state import console_users
+from bot.ui.formatters import get_asf_status_text
+from bot.ui.keyboards import console_keyboard, main_keyboard
+
+router = Router()
+
+
+@router.callback_query(lambda c: c.data == "console", AdminCallbackFilter())
+async def console_enter(callback: CallbackQuery) -> None:
+ await callback.answer()
+ console_users.add(callback.from_user.id)
+ await callback.message.edit_text( # type: ignore[union-attr]
+ "💻 Консоль ASF\n\nОтправь команду (без !)",
+ reply_markup=console_keyboard(),
+ )
+
+
+@router.callback_query(lambda c: c.data == "console_exit", AdminCallbackFilter())
+async def console_exit(callback: CallbackQuery) -> None:
+ await callback.answer()
+ console_users.discard(callback.from_user.id)
+ await callback.message.edit_text(get_asf_status_text(), reply_markup=main_keyboard()) # type: ignore[union-attr]
diff --git a/bot/handlers/inventory.py b/bot/handlers/inventory.py
new file mode 100644
index 0000000..9321a8c
--- /dev/null
+++ b/bot/handlers/inventory.py
@@ -0,0 +1,55 @@
+from aiogram import Router
+from aiogram.types import CallbackQuery
+
+from bot.filters import AdminCallbackFilter
+from bot.services.inventory import build_inventory_menu, render_inventory_page
+from bot.ui.keyboards import back_keyboard
+
+router = Router()
+
+
+@router.callback_query(lambda c: c.data and c.data.startswith("inventory_"), AdminCallbackFilter())
+async def inventory_menu(callback: CallbackQuery) -> None:
+ await callback.answer()
+ bot_name = callback.data.replace("inventory_", "") # type: ignore[union-attr]
+ await callback.message.edit_text("Проверка inventory...") # type: ignore[union-attr]
+ text, keyboard = await build_inventory_menu(bot_name)
+ if "пуст" in text:
+ keyboard = back_keyboard(f"bot_{bot_name}")
+ await callback.message.edit_text(text, reply_markup=keyboard) # type: ignore[union-attr]
+
+
+@router.callback_query(lambda c: c.data and c.data.startswith("inv_cs2_"), AdminCallbackFilter())
+async def cs2_inventory(callback: CallbackQuery) -> None:
+ await callback.answer()
+ bot_name = callback.data.replace("inv_cs2_", "") # type: ignore[union-attr]
+ await callback.message.edit_text("Загрузка inventory...") # type: ignore[union-attr]
+ text, keyboard = await render_inventory_page(bot_name, "cs2", 730, 2, 0)
+ if text is None:
+ await callback.message.edit_text("Не удалось загрузить inventory") # type: ignore[union-attr]
+ return
+ await callback.message.edit_text(text, reply_markup=keyboard) # type: ignore[union-attr]
+
+
+@router.callback_query(lambda c: c.data and c.data.startswith("inv_steam_"), AdminCallbackFilter())
+async def steam_inventory(callback: CallbackQuery) -> None:
+ await callback.answer()
+ bot_name = callback.data.replace("inv_steam_", "") # type: ignore[union-attr]
+ await callback.message.edit_text("Загрузка inventory...") # type: ignore[union-attr]
+ text, keyboard = await render_inventory_page(bot_name, "steam", 753, 6, 0)
+ if text is None:
+ await callback.message.edit_text("Не удалось загрузить inventory") # type: ignore[union-attr]
+ return
+ await callback.message.edit_text(text, reply_markup=keyboard) # type: ignore[union-attr]
+
+
+@router.callback_query(lambda c: c.data and c.data.startswith("invpage|"), AdminCallbackFilter())
+async def inventory_page(callback: CallbackQuery) -> None:
+ await callback.answer()
+ _, inv_type, bot_name, page_value = callback.data.split("|") # type: ignore[union-attr]
+ app_id, context_id = (730, 2) if inv_type == "cs2" else (753, 6)
+ text, keyboard = await render_inventory_page(bot_name, inv_type, app_id, context_id, int(page_value))
+ if text is None:
+ await callback.message.edit_text("Не удалось загрузить inventory") # type: ignore[union-attr]
+ return
+ await callback.message.edit_text(text, reply_markup=keyboard) # type: ignore[union-attr]
diff --git a/bot/handlers/messages.py b/bot/handlers/messages.py
new file mode 100644
index 0000000..2cd773a
--- /dev/null
+++ b/bot/handlers/messages.py
@@ -0,0 +1,113 @@
+import html
+
+from aiogram import Router
+from aiogram.types import Message
+
+from bot.api.asf import get_all_bots, send_command
+from bot.filters import AdminFilter
+from bot.services.redeem import extract_keys, redeem_all_bots_keys, redeem_single_bot_keys
+from bot.state import (
+ bot_redeem_messages,
+ bot_redeem_users,
+ console_users,
+ redeem_messages,
+ redeem_users,
+)
+from bot.ui.formatters import format_bot_ui, get_asf_status_text
+from bot.ui.keyboards import bot_details_keyboard, delete_message_keyboard, main_keyboard
+
+router = Router()
+
+
+async def handle_console_message(message: Message) -> bool:
+ if message.from_user.id not in console_users: # type: ignore[union-attr]
+ return False
+ command = message.text.strip() # type: ignore[union-attr]
+ try:
+ await message.delete()
+ except Exception:
+ pass
+ try:
+ response = send_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Ответ:\n{result}"
+ await message.answer(text, reply_markup=delete_message_keyboard())
+ except Exception as error:
+ await message.answer(f"Ошибка: {error}")
+ return True
+
+
+async def handle_single_bot_redeem(message: Message) -> bool:
+ if message.from_user.id not in bot_redeem_users: # type: ignore[union-attr]
+ return False
+ bot_name = bot_redeem_users.pop(message.from_user.id) # type: ignore[union-attr]
+ menu_message = bot_redeem_messages.pop(message.from_user.id, None) # type: ignore[union-attr]
+ if menu_message:
+ try:
+ response = get_all_bots()
+ bot_data = response.json().get("Result", {}).get(bot_name)
+ if bot_data:
+ await menu_message.edit_text(
+ format_bot_ui(bot_data),
+ disable_web_page_preview=True,
+ reply_markup=bot_details_keyboard(
+ bot_name, bool(bot_data.get("HasMobileAuthenticator"))
+ ),
+ )
+ except Exception:
+ pass
+ try:
+ await message.bot.delete_message(chat_id=message.chat.id, message_id=message.message_id)
+ except Exception:
+ pass
+ keys = extract_keys(message.text or "")
+ if not keys:
+ await message.answer("Ключи не найдены")
+ return True
+ checking = await message.answer(
+ f"Аккаунт: {bot_name}\nНайдено ключей: {len(keys)}\nНачинаю активацию..."
+ )
+ await checking.edit_text(await redeem_single_bot_keys(bot_name, keys))
+ return True
+
+
+async def handle_all_bots_redeem(message: Message) -> bool:
+ if message.from_user.id not in redeem_users: # type: ignore[union-attr]
+ return False
+ redeem_users.discard(message.from_user.id) # type: ignore[union-attr]
+ menu_message = redeem_messages.pop(message.from_user.id, None) # type: ignore[union-attr]
+ if menu_message:
+ try:
+ await menu_message.edit_text(get_asf_status_text(), reply_markup=main_keyboard())
+ except Exception:
+ pass
+ try:
+ await message.bot.delete_message(chat_id=message.chat.id, message_id=message.message_id)
+ except Exception:
+ pass
+ keys = extract_keys(message.text or "")
+ if not keys:
+ await message.answer("Ключи не найдены")
+ return True
+ checking = await message.answer(f"Найдено ключей: {len(keys)}\nНачинаю активацию...")
+ try:
+ await checking.edit_text(await redeem_all_bots_keys(keys))
+ except Exception as error:
+ await checking.edit_text(f"Ошибка: {error}")
+ return True
+
+
+@router.message(AdminFilter())
+async def text_router(message: Message) -> None:
+ if await handle_console_message(message):
+ return
+ if await handle_single_bot_redeem(message):
+ return
+ await handle_all_bots_redeem(message)
diff --git a/bot/handlers/navigation.py b/bot/handlers/navigation.py
new file mode 100644
index 0000000..b6aa1cb
--- /dev/null
+++ b/bot/handlers/navigation.py
@@ -0,0 +1,114 @@
+import asyncio
+
+from aiogram import Router
+from aiogram.types import CallbackQuery
+
+from bot.api.asf import get_plugins, restart_asf as restart_asf_request
+from bot.filters import AdminCallbackFilter
+from bot.services.twofa import stop_twofa_task
+from bot.state import (
+ bot_redeem_messages,
+ bot_redeem_users,
+ console_users,
+ redeem_messages,
+ redeem_users,
+)
+from bot.ui.formatters import get_asf_status_text
+from bot.ui.keyboards import back_keyboard, confirm_action_keyboard, main_keyboard
+
+router = Router()
+
+
+def clear_user_state(user_id: int) -> None:
+ redeem_users.discard(user_id)
+ redeem_messages.pop(user_id, None)
+ console_users.discard(user_id)
+ bot_redeem_users.pop(user_id, None)
+ bot_redeem_messages.pop(user_id, None)
+
+
+@router.callback_query(lambda c: c.data == "back", AdminCallbackFilter())
+async def back_button(callback: CallbackQuery) -> None:
+ clear_user_state(callback.from_user.id)
+ await stop_twofa_task(callback.message.message_id) # type: ignore[union-attr]
+ await callback.answer()
+ try:
+ await callback.message.edit_text(get_asf_status_text(), reply_markup=main_keyboard()) # type: ignore[union-attr]
+ except Exception as error:
+ await callback.message.edit_text(f"Ошибка: {error}") # type: ignore[union-attr]
+
+
+@router.callback_query(lambda c: c.data == "refresh", AdminCallbackFilter())
+async def refresh_handler(callback: CallbackQuery) -> None:
+ await callback.answer()
+ try:
+ await callback.message.edit_text(get_asf_status_text(), reply_markup=main_keyboard()) # type: ignore[union-attr]
+ except Exception as error:
+ await callback.message.edit_text(f"Ошибка: {error}") # type: ignore[union-attr]
+
+
+@router.callback_query(lambda c: c.data == "delete_msg", AdminCallbackFilter())
+async def delete_message(callback: CallbackQuery) -> None:
+ await callback.answer()
+ try:
+ await callback.message.delete() # type: ignore[union-attr]
+ except Exception:
+ pass
+
+
+@router.callback_query(lambda c: c.data == "restart_asf_confirm", AdminCallbackFilter())
+async def restart_asf_confirm(callback: CallbackQuery) -> None:
+ await callback.answer()
+ await callback.message.edit_text( # type: ignore[union-attr]
+ "♻ Точно перезапустить ASF?\n\nВо время перезапуска IPC будет недоступен несколько секунд.",
+ reply_markup=confirm_action_keyboard("restart_asf", "back"),
+ )
+
+
+@router.callback_query(lambda c: c.data == "restart_asf", AdminCallbackFilter())
+async def restart_asf(callback: CallbackQuery) -> None:
+ await callback.answer()
+ try:
+ await callback.message.edit_text("♻ ASF перезапускается...") # type: ignore[union-attr]
+ response = restart_asf_request()
+ if response.status_code != 200:
+ await callback.message.edit_text( # type: ignore[union-attr]
+ f"Ошибка HTTP {response.status_code}",
+ reply_markup=main_keyboard(),
+ )
+ return
+ await asyncio.sleep(6)
+ await callback.message.edit_text( # type: ignore[union-attr]
+ f"ASF успешно перезапущен\n\n{get_asf_status_text()}",
+ reply_markup=main_keyboard(),
+ )
+ except Exception as error:
+ await callback.message.edit_text( # type: ignore[union-attr]
+ f"Ошибка: {error}",
+ reply_markup=main_keyboard(),
+ )
+
+
+@router.callback_query(lambda c: c.data == "plugins", AdminCallbackFilter())
+async def plugins_handler(callback: CallbackQuery) -> None:
+ await stop_twofa_task(callback.message.message_id) # type: ignore[union-attr]
+ await callback.answer()
+ try:
+ response = get_plugins()
+ if response.status_code != 200:
+ await callback.message.edit_text("Ошибка API") # type: ignore[union-attr]
+ return
+ data = response.json()
+ if not data.get("Success"):
+ await callback.message.edit_text("ASF вернул ошибку") # type: ignore[union-attr]
+ return
+ plugins = data.get("Result", [])
+ if not plugins:
+ text = "Плагины не установлены"
+ else:
+ text = "📁 Плагины ASF:\n\n"
+ for plugin in plugins:
+ text += f"{plugin.get('Name', '???')} ({plugin.get('Version', '???')})\n"
+ await callback.message.edit_text(text.strip(), reply_markup=back_keyboard()) # type: ignore[union-attr]
+ except Exception as error:
+ await callback.message.edit_text(f"Ошибка: {error}") # type: ignore[union-attr]
diff --git a/bot/handlers/redeem.py b/bot/handlers/redeem.py
new file mode 100644
index 0000000..15e3a93
--- /dev/null
+++ b/bot/handlers/redeem.py
@@ -0,0 +1,40 @@
+from aiogram import Router
+from aiogram.types import CallbackQuery
+
+from bot.filters import AdminCallbackFilter
+from bot.state import (
+ bot_redeem_messages,
+ bot_redeem_users,
+ redeem_messages,
+ redeem_users,
+)
+from bot.ui.keyboards import back_keyboard
+
+router = Router()
+
+
+@router.callback_query(lambda c: c.data == "redeem_keys", AdminCallbackFilter())
+async def redeem_keys_menu(callback: CallbackQuery) -> None:
+ await callback.answer()
+ redeem_users.add(callback.from_user.id)
+ redeem_messages[callback.from_user.id] = callback.message
+ await callback.message.edit_text( # type: ignore[union-attr]
+ "Отправьте ключ Steam для активации\n\n"
+ "Можно отправить сразу несколько ключей.\n\n"
+ "Пример:\n"
+ "AAAAA-BBBBB-CCCCC\nDDDDD-EEEEE-FFFFF",
+ reply_markup=back_keyboard(),
+ )
+
+
+@router.callback_query(lambda c: c.data and c.data.startswith("redeem_bot_"), AdminCallbackFilter())
+async def redeem_bot_menu(callback: CallbackQuery) -> None:
+ await callback.answer()
+ bot_name = callback.data.replace("redeem_bot_", "") # type: ignore[union-attr]
+ bot_redeem_users[callback.from_user.id] = bot_name
+ bot_redeem_messages[callback.from_user.id] = callback.message
+ await callback.message.edit_text( # type: ignore[union-attr]
+ f"Отправьте ключ Steam для активации на аккаунте:\n{bot_name}\n\n"
+ "Можно отправить сразу несколько ключей.",
+ reply_markup=back_keyboard(f"bot_{bot_name}"),
+ )
diff --git a/bot/handlers/start.py b/bot/handlers/start.py
new file mode 100644
index 0000000..bf0cb9a
--- /dev/null
+++ b/bot/handlers/start.py
@@ -0,0 +1,21 @@
+from aiogram import Router
+from aiogram.filters import Command
+from aiogram.types import Message
+
+from bot.filters import AdminFilter
+from bot.ui.formatters import get_asf_status_text
+from bot.ui.keyboards import main_keyboard
+
+router = Router()
+
+
+@router.message(Command("start"), AdminFilter())
+async def start_handler(message: Message) -> None:
+ try:
+ await message.delete()
+ except Exception:
+ pass
+ try:
+ await message.answer(get_asf_status_text(), reply_markup=main_keyboard())
+ except Exception as error:
+ await message.answer(f"Ошибка: {error}")
diff --git a/bot/handlers/twofa.py b/bot/handlers/twofa.py
new file mode 100644
index 0000000..eb21d6d
--- /dev/null
+++ b/bot/handlers/twofa.py
@@ -0,0 +1,97 @@
+import asyncio
+
+from aiogram import Router
+from aiogram.types import CallbackQuery
+
+from bot.api.asf import accept_confirmations, get_confirmations
+from bot.filters import AdminCallbackFilter
+from bot.services.twofa import auto_update_2fa, stop_twofa_task
+from bot.state import twofa_tasks
+from bot.ui.keyboards import back_keyboard, confirmations_keyboard
+
+router = Router()
+
+
+@router.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) -> None:
+ await callback.answer()
+ if not callback.data:
+ return
+ bot_name = callback.data.replace("2fa_", "")
+ message_id = callback.message.message_id # type: ignore[union-attr]
+ await stop_twofa_task(message_id)
+ await callback.message.edit_text( # type: ignore[union-attr]
+ f"🔐 {bot_name}\n\nЗагрузка 2FA...",
+ reply_markup=back_keyboard(f"bot_{bot_name}"),
+ )
+ task = asyncio.create_task(auto_update_2fa(callback.message, bot_name))
+ twofa_tasks[message_id] = task
+
+
+@router.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) -> None:
+ await callback.answer()
+ bot_name = callback.data.replace("confirm_", "") # type: ignore[union-attr]
+ try:
+ status = await accept_confirmations(bot_name)
+ if status != 200:
+ await callback.answer("Ошибка HTTP", show_alert=True)
+ return
+ await callback.answer("Подтверждено", show_alert=True)
+ await twofa_handler(callback)
+ except Exception as error:
+ await callback.message.edit_text(f"Ошибка: {error}") # type: ignore[union-attr]
+
+
+@router.callback_query(lambda c: c.data and c.data.startswith("confirm_list_"), AdminCallbackFilter())
+async def confirm_list(callback: CallbackQuery) -> None:
+ await stop_twofa_task(callback.message.message_id) # type: ignore[union-attr]
+ 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']}\nID: {conf['id']}\n\n"
+ await callback.message.edit_text(text, reply_markup=confirmations_keyboard(bot_name)) # type: ignore[union-attr]
+ except Exception as error:
+ await callback.message.edit_text(f"Ошибка: {error}") # type: ignore[union-attr]
+
+
+@router.callback_query(lambda c: c.data and c.data.startswith("confirm_all_"), AdminCallbackFilter())
+async def confirm_all(callback: CallbackQuery) -> None:
+ await stop_twofa_task(callback.message.message_id) # type: ignore[union-attr]
+ await callback.answer()
+ if not callback.data:
+ return
+ bot_name = callback.data.replace("confirm_all_", "")
+ try:
+ status = await accept_confirmations(bot_name)
+ if status != 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']}\nID: {conf['id']}\n\n"
+ await callback.message.edit_text(text, reply_markup=confirmations_keyboard(bot_name)) # type: ignore[union-attr]
+ await callback.answer("Все подтверждено", show_alert=True)
+ except Exception as error:
+ await callback.message.edit_text(f"Ошибка: {error}") # type: ignore[union-attr]
diff --git a/bot/services/__init__.py b/bot/services/__init__.py
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/bot/services/__init__.py
@@ -0,0 +1 @@
+
diff --git a/bot/services/bots.py b/bot/services/bots.py
new file mode 100644
index 0000000..b731fc2
--- /dev/null
+++ b/bot/services/bots.py
@@ -0,0 +1,57 @@
+import asyncio
+
+from bot.api.asf import get_all_bots, get_bot, save_bot_config, send_command
+
+
+def is_bot_playing(bot_name: str) -> bool:
+ response = get_all_bots()
+ 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", []))
+
+
+def is_idle_enabled(bot_name: str, app_id: int) -> bool:
+ response = get_bot(bot_name)
+ 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", [])
+
+
+def toggle_idle_game(config: dict, app_id: int) -> tuple[dict, bool]:
+ idle_games = config.get("GamesPlayedWhileIdle", [])
+ if app_id in idle_games:
+ idle_games.remove(app_id)
+ enabled = False
+ else:
+ idle_games.append(app_id)
+ enabled = True
+ config["GamesPlayedWhileIdle"] = idle_games
+ return config, enabled
+
+
+async def reload_bot(bot_name: str) -> None:
+ try:
+ send_command(f"!reload {bot_name}")
+ except Exception:
+ pass
+
+
+async def update_idle_game(bot_name: str, app_id: int) -> tuple[bool, str, bool]:
+ response = get_bot(bot_name)
+ if response.status_code != 200:
+ return False, "Ошибка API", False
+ data = response.json()
+ bot_data = data.get("Result", {}).get(bot_name, {})
+ config = bot_data.get("BotConfig", {})
+ config, enabled = toggle_idle_game(config, app_id)
+ save = save_bot_config(bot_name, config)
+ if save.status_code != 200:
+ return False, "Ошибка сохранения", enabled
+ asyncio.create_task(reload_bot(bot_name))
+ action = "Idle включен" if enabled else "Idle выключен"
+ return True, action, enabled
diff --git a/bot/services/inventory.py b/bot/services/inventory.py
new file mode 100644
index 0000000..122e10a
--- /dev/null
+++ b/bot/services/inventory.py
@@ -0,0 +1,98 @@
+from bot.api.asf import get_bot_inventory
+from bot.constants import PAGE_SIZE
+from bot.ui.formatters import get_inventory_icon
+from bot.ui.keyboards import inventory_menu_keyboard, inventory_page_keyboard
+
+
+async def build_inventory_menu(bot_name: str) -> tuple[str, object]:
+ cs2_inventory = await get_bot_inventory(bot_name, 730, 2)
+ steam_inventory = await get_bot_inventory(bot_name, 753, 6)
+ cs2_assets = []
+ steam_assets = []
+ try:
+ cs2_assets = cs2_inventory.get(bot_name, {}).get("Assets", []) # type: ignore[union-attr]
+ except Exception:
+ pass
+ try:
+ steam_assets = steam_inventory.get(bot_name, {}).get("Assets", []) # type: ignore[union-attr]
+ except Exception:
+ pass
+ if not cs2_assets and not steam_assets:
+ return f"📦 Инвентарь {bot_name} пуст", inventory_menu_keyboard(bot_name, 0, 0)
+ text = (
+ f"📦 Инвентарь {bot_name}\n\n"
+ "🔄 — можно трейдить\n"
+ "💰 — можно продавать\n"
+ "🔒 — нельзя продавать\n"
+ "❌ — нельзя трейдить"
+ )
+ return text, inventory_menu_keyboard(bot_name, len(cs2_assets), len(steam_assets))
+
+
+async def render_inventory_page(
+ bot_name: str, inventory_type: str, appid: int, contextid: int, page: int
+) -> tuple[str, object] | tuple[None, None]:
+ inventory = await get_bot_inventory(bot_name, appid, contextid)
+ if not inventory:
+ return None, None
+ bot_inventory = inventory.get(bot_name, {})
+ assets = bot_inventory.get("Assets", [])
+ descriptions = bot_inventory.get("Descriptions", [])
+ if not assets:
+ return f"Инвентарь {bot_name} пуст", inventory_menu_keyboard(bot_name, 0, 0)
+ 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)
+ )
+ return text[:4000], inventory_page_keyboard(inventory_type, bot_name, page, total_pages)
diff --git a/bot/services/redeem.py b/bot/services/redeem.py
new file mode 100644
index 0000000..ab583ca
--- /dev/null
+++ b/bot/services/redeem.py
@@ -0,0 +1,125 @@
+import asyncio
+import re
+
+from bot.api.asf import get_all_bots, redeem_key
+
+KEY_PATTERN = r"[A-Z0-9]{5}(?:-[A-Z0-9]{5}){2}"
+
+
+def extract_keys(text: str) -> list[str]:
+ keys = re.findall(KEY_PATTERN, text.upper())
+ return list(dict.fromkeys(keys))
+
+
+def parse_redeem_result(result: str, bot_name: str | None = None) -> str:
+ result_lower = result.lower()
+ if bot_name:
+ for line in result.splitlines():
+ if f"<{bot_name.lower()}>" in line.lower():
+ result_lower = line.lower()
+ break
+ if "ok/nodetail" in result_lower or "ok/" in result_lower:
+ return "success"
+ if "ratelimited" in result_lower:
+ return "rate_limited"
+ if "alreadypurchased" in result_lower or "already purchased" in result_lower:
+ return "already_owned"
+ if "duplicateactivationcode" in result_lower:
+ return "duplicate"
+ if "regionlocked" in result_lower:
+ return "region_locked"
+ if "invalidactivationcode" in result_lower or "invalid" in result_lower:
+ return "invalid"
+ return "unknown"
+
+
+async def redeem_single_bot_keys(bot_name: str, keys: list[str]) -> str:
+ 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
+ status = parse_redeem_result(result)
+ if status == "success":
+ success_count += 1
+ results.append(f"✅ {key}: активировано")
+ elif status == "rate_limited":
+ failed_count += 1
+ results.append(f"⏳ {key}: rate limit")
+ elif status == "already_owned":
+ failed_count += 1
+ results.append(f"⚠️ {key}: игра уже есть")
+ elif status == "duplicate":
+ failed_count += 1
+ results.append(f"❌ {key}: ключ уже использован")
+ elif status == "region_locked":
+ failed_count += 1
+ results.append(f"🌍 {key}: регион лок")
+ elif status == "invalid":
+ 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])
+ )
+ return text[:4000] + ("\n\n...обрезано" if len(text) > 4000 else "")
+
+
+async def redeem_all_bots_keys(keys: list[str]) -> str:
+ response = get_all_bots()
+ 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
+ status = parse_redeem_result(result, bot_name)
+ if status == "success":
+ key_report.append(f"✅ {bot_name}: активировано")
+ success_count += 1
+ activated = True
+ break
+ if status == "rate_limited":
+ key_report.append(f"⏳ {bot_name}: rate limit")
+ continue
+ if status == "already_owned":
+ key_report.append(f"⚠️ {bot_name}: игра уже есть")
+ continue
+ if status == "duplicate":
+ key_report.append(f"❌ {bot_name}: ключ уже использован")
+ break
+ if status == "region_locked":
+ key_report.append(f"🌍 {bot_name}: регион лок")
+ continue
+ if status == "invalid":
+ key_report.append(f"❌ {bot_name}: неверный ключ")
+ break
+ key_report.append(f"❔ {bot_name}: неизвестный ответ")
+ if not activated:
+ failed_count += 1
+ results.append(f"\n🔑 {key}\n" + "\n".join(key_report))
+ text = (
+ "Результат активации\n\n"
+ f"✅ Успешно: {success_count}\n"
+ f"❌ Ошибок: {failed_count}\n\n"
+ + "\n".join(results[:50])
+ )
+ return text[:4000] + ("\n\n...обрезано" if len(text) > 4000 else "")
diff --git a/bot/services/twofa.py b/bot/services/twofa.py
new file mode 100644
index 0000000..b98d08f
--- /dev/null
+++ b/bot/services/twofa.py
@@ -0,0 +1,41 @@
+import asyncio
+
+from bot.api.asf import get_2fa_token, get_confirmations
+from bot.state import twofa_tasks
+from bot.ui.keyboards import twofa_keyboard
+
+
+async def stop_twofa_task(message_id: int) -> None:
+ task = twofa_tasks.pop(message_id, None)
+ if task:
+ task.cancel()
+ try:
+ await task
+ except Exception:
+ pass
+
+
+async def auto_update_2fa(message, bot_name: str) -> None:
+ last_code = None
+ message_id = message.message_id
+ try:
+ while True:
+ code = await get_2fa_token(bot_name)
+ if code != last_code:
+ confirmations = await get_confirmations(bot_name)
+ text = f"🔐 {bot_name}\n\n{code}"
+ try:
+ await message.edit_text(
+ text,
+ reply_markup=twofa_keyboard(bot_name, bool(confirmations)),
+ )
+ last_code = code
+ except Exception:
+ break
+ await asyncio.sleep(15)
+ except asyncio.CancelledError:
+ pass
+ except Exception as error:
+ print(f"2FA updater error: {error}")
+ finally:
+ twofa_tasks.pop(message_id, None)
diff --git a/bot/state.py b/bot/state.py
new file mode 100644
index 0000000..1560a08
--- /dev/null
+++ b/bot/state.py
@@ -0,0 +1,7 @@
+console_users = set()
+redeem_users = set()
+twofa_tasks = {}
+inventory_cache = {}
+redeem_messages = {}
+bot_redeem_users = {}
+bot_redeem_messages = {}
diff --git a/bot/ui/__init__.py b/bot/ui/__init__.py
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/bot/ui/__init__.py
@@ -0,0 +1 @@
+
diff --git a/bot/ui/formatters.py b/bot/ui/formatters.py
new file mode 100644
index 0000000..434a9b9
--- /dev/null
+++ b/bot/ui/formatters.py
@@ -0,0 +1,176 @@
+import html
+import json
+from datetime import datetime, timezone
+
+from bot.api.asf import get_asf_status
+from bot.constants import GAMES
+
+
+def get_currency_name(currency_id: int) -> str:
+ if currency_id == 1:
+ return "USD"
+ if currency_id == 3:
+ return "EUR"
+ if currency_id == 5:
+ return "RUB"
+ if currency_id == 18:
+ return "UAH"
+ if currency_id == 6:
+ return "PLN"
+ if currency_id == 0:
+ return ""
+ 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 get_uptime(start_time_str: str) -> str:
+ 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 format_asf(data: dict) -> str:
+ result = data.get("Result", {})
+ 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_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 = get_bot_status_icon(bot)
+ now_farming = bot.get("CardsFarmer", {}).get("NowFarming")
+ status_line = "🃏 Идет фарм карточек" if now_farming else "🃏 Карточки не фармится"
+ 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 = bot.get("WalletBalance", 0) / 100
+ currency = get_currency_name(bot.get("WalletCurrency", 0))
+ redeem = bot.get("GamesToRedeemInBackgroundCount", 0)
+ steam_id = bot.get("SteamID")
+ nickname = html.escape(str(bot.get("Nickname") or ""))
+ profile_url = f"https://steamcommunity.com/profiles/{steam_id}"
+ text = (
+ f"{online} {bot.get('BotName') or 'Загрузка...'}"
+ f'({nickname})\n'
+ f"{status_line}\n"
+ )
+ idle_games = bot.get("BotConfig", {}).get("GamesPlayedWhileIdle", [])
+ if idle_games:
+ names = [GAMES.get(game, str(game)) for game in idle_games]
+ if bot.get("IsConnectedAndLoggedOn"):
+ idle_text = f"⌚ Накрутка часов: включена ({', '.join(names)})"
+ else:
+ idle_text = (
+ f"⌚ Накрутка часов: включена ({', '.join(names)}) "
+ "применится после запуска"
+ )
+ text += f"{idle_text}\n"
+ else:
+ text += "⌚ Накрутка часов: выключена\n"
+ text += (
+ f"🆔 : {steam_id}\n"
+ f"🔐 2FA: {twofa}\n\n"
+ f"💰 Баланс: {balance:.2f} {currency}\n"
+ )
+ if redeem > 0:
+ text += f"\nКлючей в очереди: {redeem}\n"
+ if now_farming:
+ text += f"\nОсталось: {time}\n"
+ if games_to_farm > 0:
+ text += f"В очереди игр: {games_to_farm}\n"
+ return text
+
+
+def get_asf_status_text() -> str:
+ response = get_asf_status()
+ if response.status_code != 200:
+ return f"Ошибка API: {response.status_code}"
+ data = response.json()
+ if not data.get("Success"):
+ return "ASF вернул ошибку"
+ return f"💻 Панель управления ASF\n\n{format_asf(data)}"
+
+
+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")
+ hours, minutes, seconds = map(int, time_str.split(":"))
+ total_time_seconds += hours * 3600 + minutes * 60 + seconds
+ if total_games == 0:
+ 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}"
+ )
+
+
+def format_bot(bot_data: dict) -> str:
+ return json.dumps(bot_data, indent=2, ensure_ascii=False)
diff --git a/bot/ui/keyboards.py b/bot/ui/keyboards.py
new file mode 100644
index 0000000..482018b
--- /dev/null
+++ b/bot/ui/keyboards.py
@@ -0,0 +1,192 @@
+from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
+
+from bot.ui.formatters import get_bot_status_icon
+
+
+def main_keyboard() -> InlineKeyboardMarkup:
+ return InlineKeyboardMarkup(
+ inline_keyboard=[
+ [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="♻ Перезапустить ASF",
+ callback_data="restart_asf_confirm",
+ )
+ ],
+ ]
+ )
+
+
+def back_keyboard(callback_data: str = "back") -> InlineKeyboardMarkup:
+ return InlineKeyboardMarkup(
+ inline_keyboard=[
+ [InlineKeyboardButton(text="🔙 Назад", callback_data=callback_data)]
+ ]
+ )
+
+
+def games_keyboard(bot_name: str, is_enabled: bool) -> InlineKeyboardMarkup:
+ text = "Остановить накрутку CS2" if is_enabled else "⌚ Накрутить часы CS2"
+ return InlineKeyboardMarkup(
+ inline_keyboard=[
+ [InlineKeyboardButton(text=text, callback_data=f"farm|{bot_name}|730")],
+ [InlineKeyboardButton(text="🔙 Назад", callback_data=f"bot_{bot_name}")],
+ ]
+ )
+
+
+def bots_keyboard(bots: dict) -> InlineKeyboardMarkup:
+ keyboard = []
+ for name, bot in bots.items():
+ status = get_bot_status_icon(bot)
+ loaded = bool(bot.get("BotName")) and bool(bot.get("SteamID"))
+ callback_data = f"bot_{name}" if loaded else "bot_loading"
+ keyboard.append(
+ [InlineKeyboardButton(text=f"{status} {name}", callback_data=callback_data)]
+ )
+ keyboard.append([InlineKeyboardButton(text="🔙 Назад", callback_data="back")])
+ return InlineKeyboardMarkup(inline_keyboard=keyboard)
+
+
+def bot_details_keyboard(
+ bot_name: str, has_mobile_authenticator: bool
+) -> InlineKeyboardMarkup:
+ keyboard = []
+ if has_mobile_authenticator:
+ 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")])
+ return InlineKeyboardMarkup(inline_keyboard=keyboard)
+
+
+def twofa_keyboard(bot_name: str, has_confirmations: bool) -> InlineKeyboardMarkup:
+ keyboard = []
+ if has_confirmations:
+ keyboard.append(
+ [
+ InlineKeyboardButton(
+ text="Подтверждения",
+ callback_data=f"confirm_list_{bot_name}",
+ )
+ ]
+ )
+ keyboard.append(
+ [InlineKeyboardButton(text="🔙 Назад", callback_data=f"bot_{bot_name}")]
+ )
+ return InlineKeyboardMarkup(inline_keyboard=keyboard)
+
+
+def confirmations_keyboard(bot_name: str) -> InlineKeyboardMarkup:
+ return InlineKeyboardMarkup(
+ inline_keyboard=[
+ [
+ InlineKeyboardButton(
+ text="Подтвердить всё",
+ callback_data=f"confirm_all_{bot_name}",
+ )
+ ],
+ [InlineKeyboardButton(text="🔙 Назад", callback_data=f"2fa_{bot_name}")],
+ ]
+ )
+
+
+def confirm_action_keyboard(confirm_data: str, cancel_data: str) -> InlineKeyboardMarkup:
+ return InlineKeyboardMarkup(
+ inline_keyboard=[
+ [InlineKeyboardButton(text="Да", callback_data=confirm_data)],
+ [InlineKeyboardButton(text="Нет", callback_data=cancel_data)],
+ ]
+ )
+
+
+def inventory_menu_keyboard(
+ bot_name: str, cs2_count: int, steam_count: int
+) -> InlineKeyboardMarkup:
+ keyboard = []
+ if cs2_count:
+ keyboard.append(
+ [
+ InlineKeyboardButton(
+ text=f"CS2 ({cs2_count})",
+ callback_data=f"inv_cs2_{bot_name}",
+ )
+ ]
+ )
+ if steam_count:
+ keyboard.append(
+ [
+ InlineKeyboardButton(
+ text=f"Steam ({steam_count})",
+ callback_data=f"inv_steam_{bot_name}",
+ )
+ ]
+ )
+ keyboard.append(
+ [InlineKeyboardButton(text="🔙 Назад", callback_data=f"bot_{bot_name}")]
+ )
+ return InlineKeyboardMarkup(inline_keyboard=keyboard)
+
+
+def inventory_page_keyboard(
+ inventory_type: str, bot_name: str, page: int, total_pages: int
+) -> InlineKeyboardMarkup:
+ 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}")]
+ )
+ return InlineKeyboardMarkup(inline_keyboard=keyboard)
+
+
+def console_keyboard() -> InlineKeyboardMarkup:
+ return InlineKeyboardMarkup(
+ inline_keyboard=[
+ [InlineKeyboardButton(text="🔙 Назад", callback_data="console_exit")]
+ ]
+ )
+
+
+def delete_message_keyboard() -> InlineKeyboardMarkup:
+ return InlineKeyboardMarkup(
+ inline_keyboard=[[InlineKeyboardButton(text="Удалить", callback_data="delete_msg")]]
+ )
diff --git a/main.py b/main.py
index cacbb86..228265a 100644
--- a/main.py
+++ b/main.py
@@ -1,995 +1,34 @@
-import asyncio, aiohttp, json, html, requests, re, os
-from datetime import datetime, timezone
-from typing import cast
+import asyncio
+
from aiogram import Bot, Dispatcher
-from aiogram.filters import Command, BaseFilter
-from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery, Message
from aiogram.client.default import DefaultBotProperties
-from dotenv import load_dotenv
-load_dotenv()
-API_TOKEN = os.getenv("API_TOKEN")
-ADMIN_ID = int(os.getenv("ADMIN_ID")) # type: ignore
-ASF_URL = os.getenv("ASF_URL")
-ASF_PASSWORD = os.getenv("ASF_PASSWORD")
+from bot.config import API_TOKEN
+from bot.handlers.bots import router as bots_router
+from bot.handlers.console import router as console_router
+from bot.handlers.inventory import router as inventory_router
+from bot.handlers.messages import router as messages_router
+from bot.handlers.navigation import router as navigation_router
+from bot.handlers.redeem import router as redeem_router
+from bot.handlers.start import router as start_router
+from bot.handlers.twofa import router as twofa_router
-PAGE_SIZE = 40
-GAMES = {730: "CS2", 440: "TF2", 570: "Dota 2"}
-appid = 753
-contextid = 6
-console_users, redeem_users = set(), set()
-twofa_tasks, inventory_cache, redeem_messages, bot_redeem_users, bot_redeem_messages = {}, {}, {}, {}, {}
-
-bot = Bot(token=API_TOKEN, default=DefaultBotProperties(parse_mode="HTML")) # type: ignore
+bot = Bot(token=API_TOKEN, default=DefaultBotProperties(parse_mode="HTML")) # type: ignore[arg-type]
dp = Dispatcher()
-class AdminFilter(BaseFilter):
- async def __call__(self, message: Message) -> bool:
- return message.from_user.id == ADMIN_ID # type: ignore
+dp.include_router(start_router)
+dp.include_router(navigation_router)
+dp.include_router(console_router)
+dp.include_router(bots_router)
+dp.include_router(twofa_router)
+dp.include_router(inventory_router)
+dp.include_router(redeem_router)
+dp.include_router(messages_router)
-class AdminCallbackFilter(BaseFilter):
- async def __call__(self, callback: CallbackQuery) -> bool:
- return callback.from_user.id == ADMIN_ID
-
-def asf_get(path: str):
- return requests.get(f"http://127.0.0.1:1242{path}", headers={"Authentication": ASF_PASSWORD})
-def get_currency_name(currency_id: int) -> str:
- if currency_id == 1:
- return "USD"
- elif currency_id == 3:
- return "EUR"
- elif currency_id == 5:
- return "RUB"
- elif currency_id == 18:
- return "UAH"
- elif currency_id == 6:
- return "PLN"
- elif currency_id == 0:
- 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", {})
- 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:
- 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 = get_bot_status_icon(bot)
- now_farming = bot.get("CardsFarmer", {}).get("NowFarming")
- if now_farming:
- status_line = "🃏 Идет фарм карточек"
- else:
- 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 = bot.get("WalletBalance", 0) / 100
- currency = get_currency_name(bot.get("WalletCurrency", 0))
- redeem = bot.get("GamesToRedeemInBackgroundCount", 0)
- steam_id = bot.get("SteamID")
- nickname = html.escape(str(bot.get("Nickname") or ""))
- profile_url = f"https://steamcommunity.com/profiles/{steam_id}"
- text = (
- 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"⌚ Накрутка часов: включена ({', '.join(names)})"
- else:
- idle_text = f"⌚ Накрутка часов: включена ({', '.join(names)}) применится после запуска"
- text += f"{idle_text}\n"
- else:
- text += "⌚ Накрутка часов: выключена\n"
- text += (
- f"🆔 : {steam_id}\n"
- f"🔐 2FA: {twofa}\n\n"
- f"💰 Баланс: {balance:.2f} {currency}\n")
- if redeem > 0:
- text += f"\nКлючей в очереди: {redeem}\n"
- if now_farming:
- text += f"\nОсталось: {time}\n"
- if games_to_farm > 0:
- text += f"В очереди игр: {games_to_farm}\n"
- return text
-
-def get_asf_status_text():
- response = requests.get(ASF_URL, headers={"Authentication": ASF_PASSWORD}) # type: ignore
- 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_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 "На аккаунтах нечего не фармиться"
- hours = total_time_seconds // 3600
- minutes = (total_time_seconds % 3600) // 60
- return (
- f"Игр фармится: {total_games}\n"
- f"Осталось времени: {hours}ч {minutes}м\n"
- f"Карт осталось: ~{total_games * 3}")
-
-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="redeem_keys")],
- [InlineKeyboardButton(text="📁 Плагины", callback_data="plugins")],
- [InlineKeyboardButton(text="💻 Консоль", callback_data="console")],
- [InlineKeyboardButton(text="♻ Перезапустить 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=text, callback_data=f"farm|{bot_name}|730")],
- [InlineKeyboardButton(text="🔙 Назад", callback_data=f"bot_{bot_name}")]])
-
-def bots_keyboard(bots: dict):
- keyboard = []
- 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)
-
-def is_bot_playing(bot_name: str) -> bool:
- response = asf_get("/Api/Bot/ASF")
- 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", []))
-
-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 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: # type: ignore
- 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 "Не удалось получить код"
-
-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: # type: ignore
- 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: # type: ignore
- 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...") # type: ignore
- inventory = await get_inventory(bot_name, appid, contextid)
- if not inventory:
- await callback.message.edit_text("Не удалось загрузить inventory") # type: ignore
- 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}")]])) # type: ignore
- 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)) # type: ignore
-
-async def reload_bot(bot_name: str):
- try:
- requests.post("http://127.0.0.1:1242/Api/Command", headers={"Authentication": ASF_PASSWORD}, json={"Command": f"!reload {bot_name}"})
- except:
- pass
-
-async def stop_twofa_task(message_id: int):
- task = twofa_tasks.pop(message_id, None)
- if task:
- task.cancel()
- try:
- await task
- except:
- pass
-
-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: # type: ignore
- 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 == "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, 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)
-
-@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())
- except Exception as e:
- await message.answer(f"Ошибка: {e}")
-
-@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")]])) # type: ignore
-
-@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()) # type: ignore
-
-@dp.callback_query(lambda c: c.data == "delete_msg", AdminCallbackFilter())
-async def delete_message(callback: CallbackQuery):
- await callback.answer()
- try:
- await callback.message.delete() # type: ignore
- 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) # type: ignore
- 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") # type: ignore
- return
- data = response.json()
- if not data.get("Success"):
- await callback.message.edit_text("ASF вернул ошибку") # type: ignore
- return
- bots = data.get("Result", {})
- count = len(bots)
- summary = get_farm_summary(bots)
- text = (
- f"🤖 Ботов всего: {count}\n\n"
- f"{summary}")
- await callback.message.edit_text(text, reply_markup=bots_keyboard(bots)) # type: ignore
- except Exception as e:
- await callback.message.edit_text(f"Ошибка: {e}") # type: ignore
-
-@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) # type: ignore
- await callback.answer()
- if not callback.data:
- return
- bot_name = callback.data.replace("bot_", "")
- try:
- response = asf_get("/Api/Bot/ASF")
- 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("Бот не найден") # type: ignore
- 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, disable_web_page_preview=True, reply_markup=InlineKeyboardMarkup(inline_keyboard=keyboard)) # type: ignore
- except Exception as e:
- await callback.message.edit_text(f"Ошибка: {e}") # type: ignore
-
-@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)) # type: ignore
-
-@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) # type: ignore
- await callback.answer()
- try:
- text = get_asf_status_text()
- await callback.message.edit_text(text, reply_markup=main_keyboard()) # type: ignore
- except Exception as e:
- await callback.message.edit_text(f"Ошибка: {e}") # type: ignore
-
-@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()) # type: ignore
- except Exception as e:
- await callback.message.edit_text(f"Ошибка: {e}") # type: ignore
-
-@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( # type: ignore
- "♻ Точно перезапустить 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 перезапускается...") # type: ignore
- 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()) # type: ignore
- return
- await asyncio.sleep(6)
- text = get_asf_status_text()
- await callback.message.edit_text(f"ASF успешно перезапущен\n\n{text}", reply_markup=main_keyboard()) # type: ignore
- except Exception as e:
- await callback.message.edit_text(f"Ошибка: {e}", reply_markup=main_keyboard()) # type: ignore
-
-@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 # type: ignore
- 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( # type: ignore
- f"🔐 {bot_name}\n\nЗагрузка 2FA...",
- reply_markup=InlineKeyboardMarkup(inline_keyboard=[[InlineKeyboardButton(text="🔙 Назад", callback_data=f"bot_{bot_name}")]]))
- 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_", "") # type: ignore
- 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
- await callback.answer("Подтверждено", show_alert=True)
- await twofa_handler(callback)
- except Exception as e:
- await callback.message.edit_text(f"Ошибка: {e}") # type: ignore
-
-@dp.callback_query(lambda c: c.data == "plugins", AdminCallbackFilter())
-async def plugins_handler(callback: CallbackQuery):
- await stop_twofa_task(callback.message.message_id) # type: ignore
- 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") # type: ignore
- return
- data = response.json()
- if not data.get("Success"):
- await callback.message.edit_text("ASF вернул ошибку") # type: ignore
- 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")]])) # type: ignore
- except Exception as e:
- await callback.message.edit_text(f"Ошибка: {e}") # type: ignore
-
-@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) # type: ignore
- 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, # type: ignore
- 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}") # type: ignore
-
-@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) # type: ignore
- 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( # type: ignore
- text,
- reply_markup=InlineKeyboardMarkup(inline_keyboard=[
- [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}") # type: ignore
-
-@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
- 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)) # type: ignore
- except:
- pass
- except Exception as 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_", "") # type: ignore
- await callback.message.edit_text("Проверка inventory...") # type: ignore
- 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", [])) # type: ignore
- except:
- pass
- try:
- steam_assets = (steam_inventory.get(bot_name, {}).get("Assets", [])) # type: ignore
- 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}")]])) # type: ignore
- 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( # type: ignore
- (
- 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_", "") # type: ignore
- 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_", "") # type: ignore
- 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("|") # type: ignore
- 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)
-
-@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( # type: ignore
- "Отправьте ключ Steam для активации\n\n"
- "Можно отправить сразу несколько ключей.\n\n"
- "Пример:\n"
- "AAAAA-BBBBB-CCCCC\nDDDDD-EEEEE-FFFFF",
- 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_", "") # type: ignore
- bot_redeem_users[callback.from_user.id] = bot_name
- bot_redeem_messages[callback.from_user.id] = callback.message
- await callback.message.edit_text( # type: ignore
- f"Отправьте ключ Steam для активации на аккаунте:\n"
- f"{bot_name}\n\n"
- f"Можно отправить сразу несколько ключей.",
- 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: # type: ignore
- command = message.text.strip() # type: ignore
- 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, 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: # type: ignore
- bot_name = bot_redeem_users.pop(message.from_user.id) # type: ignore
- menu_message = bot_redeem_messages.pop(message.from_user.id, None) # type: ignore
- 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),
- 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() # type: ignore
- 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"Начинаю активацию...")
- 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)
- return
- if message.from_user.id in redeem_users: # type: ignore
- redeem_users.discard(message.from_user.id) # type: ignore
- menu_message = redeem_messages.pop(message.from_user.id, None) # type: ignore
- 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() # type: ignore
- 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 = asf_get("/Api/Bot/ASF")
- 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
-
-async def main():
+async def main() -> None:
await dp.start_polling(bot)
+
if __name__ == "__main__":
asyncio.run(main())