refactor(bot): modularize bot code

This commit is contained in:
2026-07-22 18:44:44 +05:00
parent 9b8d388c3c
commit 04f17ff8e2
25 changed files with 1433 additions and 983 deletions
+1
View File
@@ -0,0 +1 @@
+97
View File
@@ -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"<b>🤖 Ботов всего:</b> {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
+26
View File
@@ -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]
+55
View File
@@ -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]
+113
View File
@@ -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"<b>Команда:</b> <code>{command}</code>\n\n<b>Ответ:</b>\n<code>{result}</code>"
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"Аккаунт: <code>{bot_name}</code>\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)
+114
View File
@@ -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 = "<b>📁 Плагины ASF:</b>\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]
+40
View File
@@ -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"
"<code>AAAAA-BBBBB-CCCCC\nDDDDD-EEEEE-FFFFF</code>",
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<code>{bot_name}</code>\n\n"
"Можно отправить сразу несколько ключей.",
reply_markup=back_keyboard(f"bot_{bot_name}"),
)
+21
View File
@@ -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}")
+97
View File
@@ -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: <code>{conf['id']}</code>\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: <code>{conf['id']}</code>\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]