feat(bot): add async DB and FSM state handling

This commit is contained in:
2026-07-23 22:58:52 +05:00
parent b1c8f6d9f8
commit 87385228ea
28 changed files with 514 additions and 213 deletions
+47 -16
View File
@@ -1,10 +1,13 @@
import random
import asyncio
from aiogram import Router
from aiogram.types import CallbackQuery
from bot.api.asf import get_all_bots
from bot.filters import AdminCallbackFilter
from bot.logging_utils import describe_user, get_logger
from bot.services.bots import is_idle_enabled, update_idle_game
from bot.services.bots import is_bot_reloading, 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
@@ -31,7 +34,8 @@ async def bots_handler(callback: CallbackQuery) -> None:
"Пользователь %s открыл список ботов",
describe_user(callback.from_user),
)
await stop_twofa_task(callback.message.message_id) # type: ignore[union-attr]
await stop_twofa_task(callback.message.message_id)
await callback.answer()
try:
response = get_all_bots()
@@ -41,16 +45,18 @@ async def bots_handler(callback: CallbackQuery) -> None:
describe_user(callback.from_user),
response.status_code,
)
await callback.message.edit_text("Ошибка API") # type: ignore[union-attr]
await callback.message.edit_text("Ошибка API")
return
data = response.json()
if not data.get("Success"):
logger.warning(
"ASF вернул неуспешный ответ при загрузке списка ботов для %s",
describe_user(callback.from_user),
)
await callback.message.edit_text("ASF вернул ошибку") # type: ignore[union-attr]
await callback.message.edit_text("ASF вернул ошибку")
return
bots = data.get("Result", {})
logger.info(
"Список ботов для %s загружен: %s бот(ов)",
@@ -58,33 +64,50 @@ async def bots_handler(callback: CallbackQuery) -> None:
len(bots),
)
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]
await callback.message.edit_text(text, reply_markup=bots_keyboard(bots))
except Exception as error:
logger.exception(
"Ошибка при открытии списка ботов для %s",
describe_user(callback.from_user),
)
await callback.message.edit_text(f"Ошибка: {error}") # type: ignore[union-attr]
await callback.message.edit_text(f"Ошибка: {error}")
@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()
await stop_twofa_task(callback.message.message_id)
if not callback.data:
logger.debug(
"Колбэк выбора бота пришел без data от %s",
describe_user(callback.from_user),
)
await callback.answer()
return
bot_name = callback.data.replace("bot_", "")
logger.info(
"Пользователь %s выбрал бота %s",
describe_user(callback.from_user),
bot_name,
)
if is_bot_reloading(bot_name):
logger.info(
"Бот %s перезагружается, карточка временно не отображается для %s",
bot_name,
describe_user(callback.from_user),
)
await callback.answer(
"Аккаунт перезагружается. Попробуйте через несколько секунд.",
show_alert=True,
)
return
try:
data = get_all_bots().json()
bots = data.get("Result", {})
@@ -105,20 +128,22 @@ async def bot_selected(callback: CallbackQuery) -> None:
bot_name,
describe_user(callback.from_user),
)
await callback.message.edit_text( # type: ignore[union-attr]
await callback.answer()
await callback.message.edit_text(
format_bot_ui(bot),
disable_web_page_preview=True,
reply_markup=bot_details_keyboard(
bot_name, bool(bot.get("HasMobileAuthenticator"))
),
)
except Exception as error:
logger.exception(
"Ошибка при открытии карточки бота %s для %s",
bot_name,
describe_user(callback.from_user),
)
await callback.message.edit_text(f"Ошибка: {error}") # type: ignore[union-attr]
await callback.message.edit_text(f"Ошибка: {error}")
@router.callback_query(
@@ -132,6 +157,7 @@ async def games(callback: CallbackQuery) -> None:
describe_user(callback.from_user),
)
return
bot_name = callback.data.replace("games_", "")
idle_enabled = is_idle_enabled(bot_name, 730)
logger.info(
@@ -140,7 +166,8 @@ async def games(callback: CallbackQuery) -> None:
bot_name,
idle_enabled,
)
await callback.message.edit_text( # type: ignore[union-attr]
await callback.message.edit_text(
"⌚ Выберите игру для накрутки часов",
reply_markup=games_keyboard(bot_name, idle_enabled),
)
@@ -157,6 +184,7 @@ async def farm_game(callback: CallbackQuery) -> None:
describe_user(callback.from_user),
)
return
_, bot_name, app_id_value = callback.data.split("|")
logger.info(
"Пользователь %s меняет игру для накрутки на боте %s: app_id=%s",
@@ -164,6 +192,7 @@ async def farm_game(callback: CallbackQuery) -> None:
bot_name,
app_id_value,
)
success, text, enabled = await update_idle_game(bot_name, int(app_id_value))
if not success:
logger.warning(
@@ -174,20 +203,22 @@ async def farm_game(callback: CallbackQuery) -> None:
)
await callback.answer(text, show_alert=True)
return
logger.info(
"Игра для накрутки на боте %s обновлена для %s. CS2=%s",
bot_name,
describe_user(callback.from_user),
enabled,
)
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)
await callback.message.edit_text(
text=f"⌚ Выберите игру для накрутки часов",
reply_markup=games_keyboard(bot_name, enabled),
)
except Exception:
logger.debug(
"Не удалось обновить клавиатуру игр для бота %s",
bot_name,
exc_info=True,
"Не удалось обновить клавиатуру игр для бота %s", bot_name, exc_info=True
)
+16 -10
View File
@@ -1,10 +1,11 @@
from aiogram import Router
from aiogram.types import CallbackQuery
from aiogram.fsm.context import FSMContext
from bot.filters import AdminCallbackFilter
from bot.logging_utils import describe_user, get_logger
from bot.state import console_users
from bot.states import ConsoleFlow
from bot.ui.formatters import get_asf_status_text
from bot.logging_utils import describe_user, get_logger
from bot.ui.keyboards import console_keyboard, main_keyboard
router = Router()
@@ -12,21 +13,26 @@ logger = get_logger(__name__)
@router.callback_query(lambda c: c.data == "console", AdminCallbackFilter())
async def console_enter(callback: CallbackQuery) -> None:
async def console_enter(callback: CallbackQuery, state: FSMContext) -> None:
user = describe_user(callback.from_user.id)
logger.info("Пользователь вошел в режим консоли ASF: %s", user)
await state.set_state(ConsoleFlow.waiting_command)
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(),
await callback.message.edit_text(
"💻 Консоль ASF\n\nОтправь команду (без !)", reply_markup=console_keyboard()
)
@router.callback_query(lambda c: c.data == "console_exit", AdminCallbackFilter())
async def console_exit(callback: CallbackQuery) -> None:
async def console_exit(callback: CallbackQuery, state: FSMContext) -> None:
user = describe_user(callback.from_user.id)
logger.info("Пользователь вышел из режима консоли ASF: %s", user)
await state.clear()
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]
await callback.message.edit_text(
get_asf_status_text(), reply_markup=main_keyboard()
)
+26 -15
View File
@@ -15,15 +15,18 @@ logger = get_logger(__name__)
)
async def inventory_menu(callback: CallbackQuery) -> None:
user = describe_user(callback.from_user.id)
bot_name = callback.data.replace("inventory_", "") # type: ignore[union-attr]
bot_name = callback.data.replace("inventory_", "")
logger.info("Открыто меню инвентаря: %s bot=%s", user, bot_name)
await callback.answer()
await callback.message.edit_text("Проверка inventory...") # type: ignore[union-attr]
await callback.message.edit_text("Проверка инвентаря...")
text, keyboard = await build_inventory_menu(bot_name)
if "пуст" in text.lower():
logger.info("Показано пустое меню инвентаря: %s bot=%s", user, bot_name)
keyboard = back_keyboard(f"bot_{bot_name}")
await callback.message.edit_text(text, reply_markup=keyboard) # type: ignore[union-attr]
await callback.message.edit_text(text, reply_markup=keyboard)
@router.callback_query(
@@ -31,16 +34,19 @@ async def inventory_menu(callback: CallbackQuery) -> None:
)
async def cs2_inventory(callback: CallbackQuery) -> None:
user = describe_user(callback.from_user.id)
bot_name = callback.data.replace("inv_cs2_", "") # type: ignore[union-attr]
bot_name = callback.data.replace("inv_cs2_", "")
logger.info("Открыт CS2-инвентарь: %s bot=%s", user, bot_name)
await callback.answer()
await callback.message.edit_text("Загрузка inventory...") # type: ignore[union-attr]
await callback.message.edit_text("Загрузка инвентаря...")
text, keyboard = await render_inventory_page(bot_name, "cs2", 730, 2, 0)
if text is None:
logger.warning("Не удалось загрузить CS2-инвентарь: %s bot=%s", user, bot_name)
await callback.message.edit_text("Не удалось загрузить inventory") # type: ignore[union-attr]
await callback.message.edit_text("Не удалось загрузить инвентарь")
return
await callback.message.edit_text(text, reply_markup=keyboard) # type: ignore[union-attr]
await callback.message.edit_text(text, reply_markup=keyboard)
@router.callback_query(
@@ -48,16 +54,21 @@ async def cs2_inventory(callback: CallbackQuery) -> None:
)
async def steam_inventory(callback: CallbackQuery) -> None:
user = describe_user(callback.from_user.id)
bot_name = callback.data.replace("inv_steam_", "") # type: ignore[union-attr]
bot_name = callback.data.replace("inv_steam_", "")
logger.info("Открыт Steam-инвентарь: %s bot=%s", user, bot_name)
await callback.answer()
await callback.message.edit_text("Загрузка inventory...") # type: ignore[union-attr]
await callback.message.edit_text("Загрузка инвентаря...")
text, keyboard = await render_inventory_page(bot_name, "steam", 753, 6, 0)
if text is None:
logger.warning("Не удалось загрузить Steam-инвентарь: %s bot=%s", user, bot_name)
await callback.message.edit_text("Не удалось загрузить inventory") # type: ignore[union-attr]
logger.warning(
"Не удалось загрузить Steam-инвентарь: %s bot=%s", user, bot_name
)
await callback.message.edit_text("Не удалось загрузить инвентарь")
return
await callback.message.edit_text(text, reply_markup=keyboard) # type: ignore[union-attr]
await callback.message.edit_text(text, reply_markup=keyboard)
@router.callback_query(
@@ -65,7 +76,7 @@ async def steam_inventory(callback: CallbackQuery) -> None:
)
async def inventory_page(callback: CallbackQuery) -> None:
user = describe_user(callback.from_user.id)
_, inv_type, bot_name, page_value = callback.data.split("|") # type: ignore[union-attr]
_, inv_type, bot_name, page_value = callback.data.split("|")
logger.info(
"Открыта страница инвентаря: %s bot=%s type=%s page=%s",
user,
@@ -86,6 +97,6 @@ async def inventory_page(callback: CallbackQuery) -> None:
inv_type,
page_value,
)
await callback.message.edit_text("Не удалось загрузить inventory") # type: ignore[union-attr]
await callback.message.edit_text("Не удалось загрузить инвентарь")
return
await callback.message.edit_text(text, reply_markup=keyboard) # type: ignore[union-attr]
await callback.message.edit_text(text, reply_markup=keyboard)
+100 -48
View File
@@ -2,50 +2,57 @@ import html
from aiogram import Router
from aiogram.types import Message
from aiogram.fsm.context import FSMContext
from bot.api.asf import get_all_bots, send_command
from bot.filters import AdminFilter
from bot.api.asf import get_all_bots, send_command
from bot.logging_utils import describe_user, get_logger
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.states import ConsoleFlow, RedeemFlow
from bot.ui.formatters import format_bot_ui, get_asf_status_text
from bot.ui.keyboards import (
main_keyboard,
bot_details_keyboard,
delete_message_keyboard,
main_keyboard,
)
router = Router()
logger = get_logger(__name__)
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]
@router.message(ConsoleFlow.waiting_command, AdminFilter())
async def handle_console_message(message: Message, state: FSMContext):
if not message.text:
await message.answer("Отправьте текстовую команду.")
return
command = message.text.strip()
if not command:
await message.answer("Команда не может быть пустой.")
return
logger.info(
"Пользователь %s отправил консольную команду: %s",
describe_user(message.from_user),
command,
)
try:
await message.delete()
except Exception:
logger.debug(
"Не удалось удалить сообщение с консольной командой от %s",
describe_user(message.from_user),
exc_info=True,
)
try:
response = send_command(f"!{command}")
if response.status_code != 200:
@@ -55,9 +62,12 @@ async def handle_console_message(message: Message) -> bool:
command,
describe_user(message.from_user),
)
text = f"Ошибка HTTP {response.status_code}"
else:
data = response.json()
if not data.get("Success"):
logger.warning(
"ASF вернул неуспешный ответ на консольную команду '%s' от %s",
@@ -65,6 +75,7 @@ async def handle_console_message(message: Message) -> bool:
describe_user(message.from_user),
)
text = "Ошибка ASF"
else:
logger.info(
"Консольная команда '%s' от %s выполнена успешно",
@@ -73,39 +84,55 @@ async def handle_console_message(message: Message) -> bool:
)
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:
logger.exception(
"Ошибка при выполнении консольной команды '%s' от %s",
command,
describe_user(message.from_user),
)
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]
@router.message(RedeemFlow.waiting_bot_keys, AdminFilter())
async def handle_single_bot_redeem(message: Message, state: FSMContext):
state_data = await state.get_data()
bot_name = state_data.get("bot_name")
if not isinstance(bot_name, str) or not bot_name:
await message.answer("Контекст потерян, попробуйте еще раз.")
await state.clear()
return
chat_id = message.chat.id
menu_message_id = state_data.get("menu_message_id")
logger.info(
"Пользователь %s отправил ключи для бота %s",
describe_user(message.from_user),
bot_name,
)
if menu_message:
if menu_message_id:
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,
await message.bot.edit_message_text(
text=format_bot_ui(bot_data),
reply_markup=bot_details_keyboard(
bot_name, bool(bot_data.get("HasMobileAuthenticator"))
),
disable_web_page_preview=True,
chat_id=chat_id,
message_id=menu_message_id,
)
except Exception:
logger.debug(
"Не удалось восстановить меню бота %s после ввода ключей от %s",
@@ -113,10 +140,12 @@ async def handle_single_bot_redeem(message: Message) -> bool:
describe_user(message.from_user),
exc_info=True,
)
try:
await message.bot.delete_message(
chat_id=message.chat.id, message_id=message.message_id
)
except Exception:
logger.debug(
"Не удалось удалить сообщение с ключами для бота %s от %s",
@@ -124,6 +153,7 @@ async def handle_single_bot_redeem(message: Message) -> bool:
describe_user(message.from_user),
exc_info=True,
)
keys = extract_keys(message.text or "")
if not keys:
logger.info(
@@ -132,7 +162,10 @@ async def handle_single_bot_redeem(message: Message) -> bool:
bot_name,
)
await message.answer("Ключи не найдены")
return True
await state.clear()
return
logger.info(
"Для бота %s получено %s ключ(ей) от %s",
bot_name,
@@ -142,40 +175,66 @@ async def handle_single_bot_redeem(message: Message) -> bool:
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
try:
await checking.edit_text(await redeem_single_bot_keys(bot_name, keys))
except Exception as error:
logger.exception(
"Не удалось активировать ключи для бота %s от %s",
bot_name,
describe_user(message.from_user),
)
try:
await checking.edit_text(f"Ошибка: {error}")
except Exception:
await message.answer(f"Ошибка: {error}")
finally:
await state.clear()
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]
@router.message(RedeemFlow.waiting_all_keys, AdminFilter())
async def handle_all_bots_redeem(message: Message, state: FSMContext):
state_data = await state.get_data()
chat_id = message.chat.id
menu_message_id = state_data.get("menu_message_id")
logger.info(
"Пользователь %s отправил ключи для активации на всех ботах",
describe_user(message.from_user),
)
if menu_message:
if menu_message_id:
try:
await menu_message.edit_text(
get_asf_status_text(), reply_markup=main_keyboard()
await message.bot.edit_message_text(
text=get_asf_status_text(),
reply_markup=main_keyboard(),
chat_id=chat_id,
message_id=menu_message_id,
)
except Exception:
logger.debug(
"Не удалось восстановить главное меню после массовой активации для %s",
describe_user(message.from_user),
exc_info=True,
)
try:
await message.bot.delete_message(
chat_id=message.chat.id, message_id=message.message_id
)
except Exception:
logger.debug(
"Не удалось удалить сообщение с массовой активацией от %s",
describe_user(message.from_user),
exc_info=True,
)
keys = extract_keys(message.text or "")
if not keys:
logger.info(
@@ -183,7 +242,10 @@ async def handle_all_bots_redeem(message: Message) -> bool:
describe_user(message.from_user),
)
await message.answer("Ключи не найдены")
return True
await state.clear()
return
logger.info(
"Для массовой активации получено %s ключ(ей) от %s",
len(keys),
@@ -192,25 +254,15 @@ async def handle_all_bots_redeem(message: Message) -> bool:
checking = await message.answer(
f"Найдено ключей: {len(keys)}\nНачинаю активацию..."
)
try:
await checking.edit_text(await redeem_all_bots_keys(keys))
except Exception as error:
logger.exception(
"Ошибка при массовой активации ключей от %s",
describe_user(message.from_user),
)
await checking.edit_text(f"Ошибка: {error}")
return True
@router.message(AdminFilter())
async def text_router(message: Message) -> None:
logger.debug(
"Получено текстовое сообщение от %s для маршрутизации",
describe_user(message.from_user),
)
if await handle_console_message(message):
return
if await handle_single_bot_redeem(message):
return
await handle_all_bots_redeem(message)
await state.clear()
+41 -35
View File
@@ -2,18 +2,15 @@ import asyncio
from aiogram import Router
from aiogram.types import CallbackQuery
from aiogram.fsm.context import FSMContext
from bot.api.asf import get_plugins, restart_asf as restart_asf_request
from bot.filters import AdminCallbackFilter
from bot.logging_utils import describe_user, get_logger
from bot.api.asf import get_plugins, restart_asf as restart_asf_request
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, get_asf_status
from bot.ui.keyboards import back_keyboard, confirm_action_keyboard, main_keyboard
@@ -21,28 +18,24 @@ router = Router()
logger = get_logger(__name__)
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)
logger.info("Состояние пользователя очищено: user_id=%s", user_id)
@router.callback_query(lambda c: c.data == "back", AdminCallbackFilter())
async def back_button(callback: CallbackQuery) -> None:
async def back_button(callback: CallbackQuery, state: FSMContext) -> None:
user = describe_user(callback.from_user.id)
logger.info("Нажата кнопка возврата: %s", user)
clear_user_state(callback.from_user.id)
await stop_twofa_task(callback.message.message_id) # type: ignore[union-attr]
await state.clear()
await stop_twofa_task(callback.message.message_id)
await callback.answer()
try:
await callback.message.edit_text(get_asf_status_text(), reply_markup=main_keyboard()) # type: ignore[union-attr]
await callback.message.edit_text(
get_asf_status_text(), reply_markup=main_keyboard()
)
logger.info("Пользователь возвращен в главное меню: %s", user)
except Exception as error:
logger.exception("Не удалось вернуть пользователя в главное меню: %s", user)
await callback.message.edit_text(f"Ошибка: {error}") # type: ignore[union-attr]
await callback.message.edit_text(f"Ошибка: {error}")
@router.callback_query(lambda c: c.data == "refresh", AdminCallbackFilter())
@@ -50,22 +43,28 @@ async def refresh_handler(callback: CallbackQuery) -> None:
user = describe_user(callback.from_user.id)
logger.info("Запрошено обновление главного экрана: %s", user)
await callback.answer()
try:
await callback.message.edit_text(get_asf_status_text(), reply_markup=main_keyboard()) # type: ignore[union-attr]
await callback.message.edit_text(
get_asf_status_text(), reply_markup=main_keyboard()
)
logger.info("Главный экран обновлен: %s", user)
except Exception as error:
logger.exception("Не удалось обновить главный экран: %s", user)
await callback.message.edit_text(f"Ошибка: {error}") # type: ignore[union-attr]
await callback.message.edit_text(f"Ошибка: {error}")
@router.callback_query(lambda c: c.data == "delete_msg", AdminCallbackFilter())
async def delete_message(callback: CallbackQuery) -> None:
user = describe_user(callback.from_user.id)
logger.info("Запрошено удаление сообщения: %s", user)
await callback.answer()
try:
await callback.message.delete() # type: ignore[union-attr]
await callback.message.delete()
logger.info("Сообщение удалено: %s", user)
except Exception:
logger.exception("Не удалось удалить сообщение: %s", user)
@@ -74,8 +73,9 @@ async def delete_message(callback: CallbackQuery) -> None:
async def restart_asf_confirm(callback: CallbackQuery) -> None:
user = describe_user(callback.from_user.id)
logger.info("Открыто подтверждение перезапуска ASF: %s", user)
await callback.answer()
await callback.message.edit_text( # type: ignore[union-attr]
await callback.message.edit_text(
"♻ Точно перезапустить ASF?\n\nВо время перезапуска IPC будет недоступен несколько секунд.",
reply_markup=confirm_action_keyboard("restart_asf", "back"),
)
@@ -85,9 +85,10 @@ async def restart_asf_confirm(callback: CallbackQuery) -> None:
async def restart_asf(callback: CallbackQuery) -> None:
user = describe_user(callback.from_user.id)
logger.info("Подтвержден перезапуск ASF: %s", user)
await callback.answer()
try:
await callback.message.edit_text("♻ ASF перезапускается...") # type: ignore[union-attr]
await callback.message.edit_text("♻ ASF перезапускается...")
response = restart_asf_request()
if response.status_code != 200:
@@ -96,7 +97,7 @@ async def restart_asf(callback: CallbackQuery) -> None:
user,
response.status_code,
)
await callback.message.edit_text( # type: ignore[union-attr]
await callback.message.edit_text(
f"Ошибка HTTP {response.status_code}",
reply_markup=main_keyboard(),
)
@@ -113,7 +114,7 @@ async def restart_asf(callback: CallbackQuery) -> None:
status = get_asf_status()
await asyncio.sleep(1)
await callback.message.edit_text( # type: ignore[union-attr]
await callback.message.edit_text(
f"ASF успешно перезапущен\n\n{get_asf_status_text()}",
reply_markup=main_keyboard(),
)
@@ -123,7 +124,7 @@ async def restart_asf(callback: CallbackQuery) -> None:
except Exception as error:
logger.exception("Не удалось перезапустить ASF: %s", user)
await callback.message.edit_text( # type: ignore[union-attr]
await callback.message.edit_text(
f"Ошибка: {error}",
reply_markup=main_keyboard(),
)
@@ -133,7 +134,8 @@ async def restart_asf(callback: CallbackQuery) -> None:
async def plugins_handler(callback: CallbackQuery) -> None:
user = describe_user(callback.from_user.id)
logger.info("Открыт список плагинов ASF: %s", user)
await stop_twofa_task(callback.message.message_id) # type: ignore[union-attr]
await stop_twofa_task(callback.message.message_id)
await callback.answer()
try:
response = get_plugins()
@@ -143,13 +145,15 @@ async def plugins_handler(callback: CallbackQuery) -> None:
user,
response.status_code,
)
await callback.message.edit_text("Ошибка API") # type: ignore[union-attr]
await callback.message.edit_text("Ошибка API")
return
data = response.json()
if not data.get("Success"):
logger.warning("ASF вернул ошибку при получении плагинов: %s", user)
await callback.message.edit_text("ASF вернул ошибку") # type: ignore[union-attr]
await callback.message.edit_text("ASF вернул ошибку")
return
plugins = data.get("Result", [])
if not plugins:
logger.info("Плагины ASF не установлены: %s", user)
@@ -161,7 +165,9 @@ async def plugins_handler(callback: CallbackQuery) -> None:
text += (
f"{plugin.get('Name', '???')} ({plugin.get('Version', '???')})\n"
)
await callback.message.edit_text(text.strip(), reply_markup=back_keyboard()) # type: ignore[union-attr]
await callback.message.edit_text(text.strip(), reply_markup=back_keyboard())
except Exception as error:
logger.exception("Не удалось отобразить список плагинов: %s", user)
await callback.message.edit_text(f"Ошибка: {error}") # type: ignore[union-attr]
await callback.message.edit_text(f"Ошибка: {error}")
+21 -16
View File
@@ -1,14 +1,12 @@
from aiogram import Router
from aiogram.types import CallbackQuery
from aiogram.fsm.context import FSMContext
from bot.filters import AdminCallbackFilter
from bot.logging_utils import describe_user, get_logger
from bot.state import (
bot_redeem_messages,
bot_redeem_users,
redeem_messages,
redeem_users,
)
from bot.states import RedeemFlow
from bot.ui.keyboards import back_keyboard
router = Router()
@@ -16,33 +14,40 @@ logger = get_logger(__name__)
@router.callback_query(lambda c: c.data == "redeem_keys", AdminCallbackFilter())
async def redeem_keys_menu(callback: CallbackQuery) -> None:
async def redeem_keys_menu(callback: CallbackQuery, state: FSMContext) -> None:
user = describe_user(callback.from_user.id)
logger.info("Открыт режим активации ключей по всем ботам: %s", user)
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]
await callback.message.edit_text(
"Отправьте ключ Steam для активации\n\n"
"Можно отправить сразу несколько ключей.\n\n"
"Пример:\n"
"<code>AAAAA-BBBBB-CCCCC\nDDDDD-EEEEE-FFFFF</code>",
reply_markup=back_keyboard(),
)
await state.set_state(RedeemFlow.waiting_all_keys)
await state.update_data(menu_message_id=callback.message.message_id)
@router.callback_query(
lambda c: c.data and c.data.startswith("redeem_bot_"), AdminCallbackFilter()
)
async def redeem_bot_menu(callback: CallbackQuery) -> None:
async def redeem_bot_menu(callback: CallbackQuery, state: FSMContext) -> None:
user = describe_user(callback.from_user.id)
bot_name = callback.data.replace("redeem_bot_", "") # type: ignore[union-attr]
logger.info("Открыт режим активации ключей для одного бота: %s bot=%s", user, bot_name)
bot_name = callback.data.replace("redeem_bot_", "")
logger.info(
"Открыт режим активации ключей для одного бота: %s bot=%s", user, bot_name
)
await callback.answer()
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]
await callback.message.edit_text(
f"Отправьте ключ Steam для активации на аккаунте:\n<code>{bot_name}</code>\n\n"
"Можно отправить сразу несколько ключей.",
reply_markup=back_keyboard(f"bot_{bot_name}"),
)
await state.set_state(RedeemFlow.waiting_bot_keys)
await state.update_data(
bot_name=bot_name, menu_message_id=callback.message.message_id
)
+11 -3
View File
@@ -1,11 +1,14 @@
from aiogram import Router
from aiogram.filters import Command
from aiogram.types import Message
from aiogram.filters import Command
from bot.filters import AdminFilter
from bot.logging_utils import describe_user, get_logger
from bot.ui.formatters import get_asf_status_text
from bot.services.users import upsert_user_from_telegram
from bot.ui.keyboards import main_keyboard
from bot.ui.formatters import get_asf_status_text
from bot.logging_utils import describe_user, get_logger
router = Router()
logger = get_logger(__name__)
@@ -14,15 +17,20 @@ logger = get_logger(__name__)
@router.message(Command("start"), AdminFilter())
async def start_handler(message: Message) -> None:
user = describe_user(message.from_user.id if message.from_user else None)
await upsert_user_from_telegram(message.from_user)
logger.info("Получена команда /start: %s", user)
try:
await message.delete()
logger.info("Сообщение /start удалено: %s", user)
except Exception:
logger.exception("Не удалось удалить сообщение /start: %s", user)
try:
await message.answer(get_asf_status_text(), reply_markup=main_keyboard())
logger.info("Главное меню отправлено: %s", user)
except Exception as error:
logger.exception("Не удалось отправить главное меню: %s", user)
await message.answer(f"Ошибка: {error}")
+24 -17
View File
@@ -3,11 +3,11 @@ import asyncio
from aiogram import Router
from aiogram.types import CallbackQuery
from bot.api.asf import accept_confirmations, get_confirmations
from bot.states import twofa_tasks
from bot.filters import AdminCallbackFilter
from bot.logging_utils import describe_user, get_logger
from bot.services.twofa import auto_update_2fa, stop_twofa_task
from bot.state import twofa_tasks
from bot.api.asf import accept_confirmations, get_confirmations
from bot.ui.keyboards import back_keyboard, confirmations_keyboard
router = Router()
@@ -29,14 +29,14 @@ async def twofa_handler(callback: CallbackQuery) -> None:
)
return
bot_name = callback.data.replace("2fa_", "")
message_id = callback.message.message_id # type: ignore[union-attr]
message_id = callback.message.message_id
logger.info(
"Пользователь %s открыл 2FA для бота %s",
describe_user(callback.from_user),
bot_name,
)
await stop_twofa_task(message_id)
await callback.message.edit_text( # type: ignore[union-attr]
await callback.message.edit_text(
f"🔐 {bot_name}\n\nЗагрузка 2FA...",
reply_markup=back_keyboard(f"bot_{bot_name}"),
)
@@ -58,7 +58,7 @@ async def twofa_handler(callback: CallbackQuery) -> None:
)
async def confirm_trades(callback: CallbackQuery) -> None:
await callback.answer()
bot_name = callback.data.replace("confirm_", "") # type: ignore[union-attr]
bot_name = callback.data.replace("confirm_", "")
logger.info(
"Пользователь %s подтвердил сделки для бота %s",
describe_user(callback.from_user),
@@ -82,14 +82,14 @@ async def confirm_trades(callback: CallbackQuery) -> None:
"Ошибка при подтверждении сделок для бота %s",
bot_name,
)
await callback.message.edit_text(f"Ошибка: {error}") # type: ignore[union-attr]
await callback.message.edit_text(f"Ошибка: {error}")
@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 stop_twofa_task(callback.message.message_id)
await callback.answer()
if not callback.data:
logger.debug(
@@ -117,20 +117,23 @@ async def confirm_list(callback: CallbackQuery) -> None:
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.message.edit_text(
text, reply_markup=confirmations_keyboard(bot_name)
)
except Exception as error:
logger.exception(
"Ошибка при загрузке списка подтверждений для бота %s",
bot_name,
)
await callback.message.edit_text(f"Ошибка: {error}") # type: ignore[union-attr]
await callback.message.edit_text(f"Ошибка: {error}")
@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 stop_twofa_task(callback.message.message_id)
await callback.answer()
if not callback.data:
logger.debug(
@@ -138,12 +141,14 @@ async def confirm_all(callback: CallbackQuery) -> None:
describe_user(callback.from_user),
)
return
bot_name = callback.data.replace("confirm_all_", "")
logger.info(
"Пользователь %s запустил подтверждение всех сделок для бота %s",
describe_user(callback.from_user),
bot_name,
)
try:
status = await accept_confirmations(bot_name)
if status != 200:
@@ -154,6 +159,7 @@ async def confirm_all(callback: CallbackQuery) -> None:
)
await callback.answer("Ошибка HTTP", show_alert=True)
return
confirmations = await get_confirmations(bot_name)
if not confirmations:
logger.info("После подтверждения у бота %s не осталось сделок", bot_name)
@@ -167,11 +173,12 @@ async def confirm_all(callback: CallbackQuery) -> None:
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:
logger.exception(
"Ошибка при подтверждении всех сделок для бота %s",
bot_name,
await callback.message.edit_text(
text, reply_markup=confirmations_keyboard(bot_name)
)
await callback.message.edit_text(f"Ошибка: {error}") # type: ignore[union-attr]
await callback.answer("Все подтверждено", show_alert=True)
except Exception as error:
logger.exception("Ошибка при подтверждении всех сделок для бота %s", bot_name)
await callback.message.edit_text(f"Ошибка: {error}")