diff --git a/.env.example b/.env.example
index f857d3a..97353bb 100644
--- a/.env.example
+++ b/.env.example
@@ -1,9 +1,9 @@
#Токен бота Телеграм
-API_TOKEN = ""
+BOT_TOKEN=
# Ваш Тг Ид
-ADMIN_ID = 0
+ADMIN_ID=
# конфиг ASF
-ASF_URL = "http://127.0.0.1:1242/Api/ASF"
-ASF_PASSWORD = "0000"
+ASF_URL="http://127.0.0.1:1242/Api/ASF"
+ASF_PASSWORD="0000"
diff --git a/.gitignore b/.gitignore
index d7f14ff..7c7d8de 100644
--- a/.gitignore
+++ b/.gitignore
@@ -58,6 +58,7 @@ cover/
# Django stuff:
*.log
+bot.log
local_settings.py
db.sqlite3
db.sqlite3-journal
diff --git a/bot/api/asf.py b/bot/api/asf.py
index ba38e50..deefc1c 100644
--- a/bot/api/asf.py
+++ b/bot/api/asf.py
@@ -2,8 +2,10 @@ import aiohttp
import requests
from bot.config import ASF_PASSWORD, ASF_URL
+from bot.logging_utils import get_logger
IPC_BASE_URL = "http://127.0.0.1:1242"
+logger = get_logger(__name__)
def _headers() -> dict[str, str | None]:
@@ -11,15 +13,36 @@ def _headers() -> dict[str, str | None]:
def asf_get(path: str) -> requests.Response:
- return requests.get(f"{IPC_BASE_URL}{path}", headers=_headers())
+ logger.info("Отправка GET-запроса в ASF: path=%s", path)
+ response = requests.get(f"{IPC_BASE_URL}{path}", headers=_headers())
+ logger.info(
+ "Получен ответ ASF на GET-запрос: path=%s status=%s",
+ path,
+ response.status_code,
+ )
+ return response
def asf_post(path: str, payload: dict | None = None) -> requests.Response:
- return requests.post(f"{IPC_BASE_URL}{path}", headers=_headers(), json=payload)
+ logger.info(
+ "Отправка POST-запроса в ASF: path=%s payload_keys=%s",
+ path,
+ sorted((payload or {}).keys()),
+ )
+ response = requests.post(f"{IPC_BASE_URL}{path}", headers=_headers(), json=payload)
+ logger.info(
+ "Получен ответ ASF на POST-запрос: path=%s status=%s",
+ path,
+ response.status_code,
+ )
+ return response
def get_asf_status() -> requests.Response:
- return requests.get(ASF_URL, headers=_headers()) # type: ignore[arg-type]
+ logger.info("Запрос статуса ASF")
+ response = requests.get(ASF_URL, headers=_headers()) # type: ignore[arg-type]
+ logger.info("Получен статус ASF: status=%s", response.status_code)
+ return response
def get_all_bots() -> requests.Response:
@@ -43,67 +66,129 @@ def restart_asf() -> requests.Response:
async def get_bot_inventory(bot_name: str, appid: int, contextid: int) -> dict | None:
+ logger.info(
+ "Запрос инвентаря бота: bot=%s appid=%s contextid=%s",
+ bot_name,
+ appid,
+ contextid,
+ )
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:
+ logger.warning(
+ "Не удалось получить инвентарь бота: bot=%s appid=%s contextid=%s status=%s",
+ bot_name,
+ appid,
+ contextid,
+ response.status,
+ )
return None
data = await response.json()
if not data.get("Success"):
+ logger.warning(
+ "ASF вернул ошибку при получении инвентаря: bot=%s appid=%s contextid=%s",
+ bot_name,
+ appid,
+ contextid,
+ )
return None
+ logger.info(
+ "Инвентарь бота получен: bot=%s appid=%s contextid=%s",
+ bot_name,
+ appid,
+ contextid,
+ )
return data.get("Result")
async def get_2fa_token(bot_name: str) -> str:
+ logger.info("Запрос 2FA-кода: bot=%s", bot_name)
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:
+ logger.warning(
+ "Не удалось получить 2FA-код: bot=%s status=%s",
+ bot_name,
+ response.status,
+ )
return f"Ошибка HTTP {response.status}"
data = await response.json()
if not data.get("Success"):
+ logger.warning("ASF вернул ошибку при запросе 2FA-кода: bot=%s", bot_name)
return "Ошибка ASF"
try:
+ logger.info("2FA-код успешно получен: bot=%s", bot_name)
return data["Result"][bot_name]["Result"]
except Exception:
+ logger.exception("Не удалось извлечь 2FA-код из ответа ASF: bot=%s", bot_name)
return "Не удалось получить код"
async def get_confirmations(bot_name: str) -> list:
+ logger.info("Запрос подтверждений 2FA: bot=%s", bot_name)
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:
+ logger.warning(
+ "Не удалось получить подтверждения 2FA: bot=%s status=%s",
+ bot_name,
+ response.status,
+ )
return []
data = await response.json()
if not data.get("Success"):
+ logger.warning(
+ "ASF вернул ошибку при запросе подтверждений 2FA: bot=%s",
+ bot_name,
+ )
return []
try:
- return data["Result"][bot_name]["Result"]
+ result = data["Result"][bot_name]["Result"]
+ logger.info(
+ "Подтверждения 2FA получены: bot=%s count=%s",
+ bot_name,
+ len(result),
+ )
+ return result
except Exception:
+ logger.exception(
+ "Не удалось извлечь подтверждения 2FA из ответа ASF: bot=%s",
+ bot_name,
+ )
return []
async def accept_confirmations(bot_name: str) -> int:
+ logger.info("Отправка запроса на подтверждение всех 2FA-операций: bot=%s", bot_name)
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:
+ logger.info(
+ "Получен ответ на подтверждение 2FA-операций: bot=%s status=%s",
+ bot_name,
+ response.status,
+ )
return response.status
def save_bot_config(bot_name: str, config: dict) -> requests.Response:
+ logger.info("Сохранение конфигурации бота: bot=%s", bot_name)
return asf_post(f"/Api/Bot/{bot_name}", {"BotConfig": config})
async def redeem_key(bot_name: str, key: str) -> tuple[bool, str]:
+ logger.info("Запрос активации ключа: bot=%s", bot_name)
async with aiohttp.ClientSession() as session:
async with session.post(
f"{IPC_BASE_URL}/Api/Command",
@@ -111,8 +196,15 @@ async def redeem_key(bot_name: str, key: str) -> tuple[bool, str]:
json={"Command": f"!redeem {bot_name} {key}"},
) as response:
if response.status != 200:
+ logger.warning(
+ "Не удалось активировать ключ: bot=%s status=%s",
+ bot_name,
+ response.status,
+ )
return False, f"HTTP_ERROR_{response.status}"
data = await response.json()
if not data.get("Success"):
+ logger.warning("ASF вернул ошибку при активации ключа: bot=%s", bot_name)
return False, "ASF_ERROR"
+ logger.info("ASF вернул результат активации ключа: bot=%s", bot_name)
return True, str(data.get("Result", ""))
diff --git a/bot/config.py b/bot/config.py
index 6483519..eabab43 100644
--- a/bot/config.py
+++ b/bot/config.py
@@ -4,7 +4,8 @@ from dotenv import load_dotenv
load_dotenv()
-API_TOKEN = os.getenv("API_TOKEN")
+BOT_TOKEN = os.getenv("BOT_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/filters.py b/bot/filters.py
index 107ad07..15bf796 100644
--- a/bot/filters.py
+++ b/bot/filters.py
@@ -2,13 +2,29 @@ from aiogram.filters import BaseFilter
from aiogram.types import CallbackQuery, Message
from bot.config import ADMIN_ID
+from bot.logging_utils import get_logger
+
+logger = get_logger(__name__)
class AdminFilter(BaseFilter):
async def __call__(self, message: Message) -> bool:
- return message.from_user.id == ADMIN_ID # type: ignore[union-attr]
+ allowed = message.from_user.id == ADMIN_ID # type: ignore[union-attr]
+ if not allowed:
+ logger.warning(
+ "Отклонено сообщение от неавторизованного пользователя: user_id=%s",
+ message.from_user.id if message.from_user else None,
+ )
+ return allowed
class AdminCallbackFilter(BaseFilter):
async def __call__(self, callback: CallbackQuery) -> bool:
- return callback.from_user.id == ADMIN_ID
+ allowed = callback.from_user.id == ADMIN_ID
+ if not allowed:
+ logger.warning(
+ "Отклонен callback от неавторизованного пользователя: user_id=%s data=%s",
+ callback.from_user.id,
+ callback.data,
+ )
+ return allowed
diff --git a/bot/handlers/bots.py b/bot/handlers/bots.py
index b4b6aed..63b0a55 100644
--- a/bot/handlers/bots.py
+++ b/bot/handlers/bots.py
@@ -3,39 +3,67 @@ 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.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()
+logger = get_logger(__name__)
@router.callback_query(lambda c: c.data == "bot_loading", AdminCallbackFilter())
async def bot_loading_handler(callback: CallbackQuery) -> None:
+ logger.info(
+ "Пользователь %s нажал на еще не загруженного бота",
+ describe_user(callback.from_user),
+ )
await callback.answer(
- "Аккаунт ещё загружается. Попробуйте через несколько секунд.",
+ "Аккаунт еще загружается. Попробуйте через несколько секунд.",
show_alert=True,
)
@router.callback_query(lambda c: c.data == "bots", AdminCallbackFilter())
async def bots_handler(callback: CallbackQuery) -> None:
+ logger.info(
+ "Пользователь %s открыл список ботов",
+ describe_user(callback.from_user),
+ )
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:
+ logger.warning(
+ "Не удалось загрузить список ботов для %s: HTTP %s",
+ describe_user(callback.from_user),
+ response.status_code,
+ )
await callback.message.edit_text("Ошибка API") # type: ignore[union-attr]
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]
return
bots = data.get("Result", {})
+ logger.info(
+ "Список ботов для %s загружен: %s бот(ов)",
+ describe_user(callback.from_user),
+ len(bots),
+ )
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:
+ logger.exception(
+ "Ошибка при открытии списка ботов для %s",
+ describe_user(callback.from_user),
+ )
await callback.message.edit_text(f"Ошибка: {error}") # type: ignore[union-attr]
@@ -46,18 +74,37 @@ 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:
+ logger.debug(
+ "Колбэк выбора бота пришел без data от %s",
+ describe_user(callback.from_user),
+ )
return
bot_name = callback.data.replace("bot_", "")
+ logger.info(
+ "Пользователь %s выбрал бота %s",
+ describe_user(callback.from_user),
+ bot_name,
+ )
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"):
+ logger.info(
+ "Бот %s еще не готов к отображению для %s",
+ bot_name,
+ describe_user(callback.from_user),
+ )
await callback.answer(
- "Аккаунт ещё загружается. Попробуйте через несколько секунд.",
+ "Аккаунт еще загружается. Попробуйте через несколько секунд.",
show_alert=True,
)
return
+ logger.info(
+ "Открыта карточка бота %s для %s",
+ bot_name,
+ describe_user(callback.from_user),
+ )
await callback.message.edit_text( # type: ignore[union-attr]
format_bot_ui(bot),
disable_web_page_preview=True,
@@ -66,6 +113,11 @@ async def bot_selected(callback: CallbackQuery) -> None:
),
)
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]
@@ -75,9 +127,19 @@ async def bot_selected(callback: CallbackQuery) -> None:
async def games(callback: CallbackQuery) -> None:
await callback.answer()
if not callback.data:
+ logger.debug(
+ "Колбэк выбора игры пришел без data от %s",
+ describe_user(callback.from_user),
+ )
return
bot_name = callback.data.replace("games_", "")
idle_enabled = is_idle_enabled(bot_name, 730)
+ logger.info(
+ "Пользователь %s открыл меню накрутки часов для бота %s. CS2=%s",
+ describe_user(callback.from_user),
+ bot_name,
+ idle_enabled,
+ )
await callback.message.edit_text( # type: ignore[union-attr]
"⌚ Выберите игру для накрутки часов",
reply_markup=games_keyboard(bot_name, idle_enabled),
@@ -90,16 +152,42 @@ async def games(callback: CallbackQuery) -> None:
async def farm_game(callback: CallbackQuery) -> None:
await callback.answer()
if not callback.data:
+ logger.debug(
+ "Колбэк изменения фарма пришел без data от %s",
+ describe_user(callback.from_user),
+ )
return
_, bot_name, app_id_value = callback.data.split("|")
+ logger.info(
+ "Пользователь %s меняет игру для накрутки на боте %s: app_id=%s",
+ describe_user(callback.from_user),
+ bot_name,
+ app_id_value,
+ )
success, text, enabled = await update_idle_game(bot_name, int(app_id_value))
if not success:
+ logger.warning(
+ "Не удалось изменить игру для накрутки на боте %s для %s: %s",
+ bot_name,
+ describe_user(callback.from_user),
+ text,
+ )
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)
)
except Exception:
- pass
+ logger.debug(
+ "Не удалось обновить клавиатуру игр для бота %s",
+ bot_name,
+ exc_info=True,
+ )
diff --git a/bot/handlers/console.py b/bot/handlers/console.py
index 70d0ec6..b897ebf 100644
--- a/bot/handlers/console.py
+++ b/bot/handlers/console.py
@@ -2,15 +2,19 @@ from aiogram import Router
from aiogram.types import CallbackQuery
from bot.filters import AdminCallbackFilter
+from bot.logging_utils import describe_user, get_logger
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()
+logger = get_logger(__name__)
@router.callback_query(lambda c: c.data == "console", AdminCallbackFilter())
async def console_enter(callback: CallbackQuery) -> None:
+ user = describe_user(callback.from_user.id)
+ logger.info("Пользователь вошел в режим консоли ASF: %s", user)
await callback.answer()
console_users.add(callback.from_user.id)
await callback.message.edit_text( # type: ignore[union-attr]
@@ -21,6 +25,8 @@ async def console_enter(callback: CallbackQuery) -> None:
@router.callback_query(lambda c: c.data == "console_exit", AdminCallbackFilter())
async def console_exit(callback: CallbackQuery) -> None:
+ user = describe_user(callback.from_user.id)
+ logger.info("Пользователь вышел из режима консоли ASF: %s", user)
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
index fd743e5..09ab9a4 100644
--- a/bot/handlers/inventory.py
+++ b/bot/handlers/inventory.py
@@ -2,21 +2,26 @@ from aiogram import Router
from aiogram.types import CallbackQuery
from bot.filters import AdminCallbackFilter
+from bot.logging_utils import describe_user, get_logger
from bot.services.inventory import build_inventory_menu, render_inventory_page
from bot.ui.keyboards import back_keyboard
router = Router()
+logger = get_logger(__name__)
@router.callback_query(
lambda c: c.data and c.data.startswith("inventory_"), AdminCallbackFilter()
)
async def inventory_menu(callback: CallbackQuery) -> None:
- await callback.answer()
+ user = describe_user(callback.from_user.id)
bot_name = callback.data.replace("inventory_", "") # type: ignore[union-attr]
+ logger.info("Открыто меню инвентаря: %s bot=%s", user, bot_name)
+ await callback.answer()
await callback.message.edit_text("Проверка inventory...") # type: ignore[union-attr]
text, keyboard = await build_inventory_menu(bot_name)
- if "пуст" in text:
+ 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]
@@ -25,11 +30,14 @@ async def inventory_menu(callback: CallbackQuery) -> None:
lambda c: c.data and c.data.startswith("inv_cs2_"), AdminCallbackFilter()
)
async def cs2_inventory(callback: CallbackQuery) -> None:
- await callback.answer()
+ user = describe_user(callback.from_user.id)
bot_name = callback.data.replace("inv_cs2_", "") # type: ignore[union-attr]
+ logger.info("Открыт CS2-инвентарь: %s bot=%s", user, bot_name)
+ await callback.answer()
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:
+ logger.warning("Не удалось загрузить CS2-инвентарь: %s bot=%s", user, bot_name)
await callback.message.edit_text("Не удалось загрузить inventory") # type: ignore[union-attr]
return
await callback.message.edit_text(text, reply_markup=keyboard) # type: ignore[union-attr]
@@ -39,11 +47,14 @@ async def cs2_inventory(callback: CallbackQuery) -> None:
lambda c: c.data and c.data.startswith("inv_steam_"), AdminCallbackFilter()
)
async def steam_inventory(callback: CallbackQuery) -> None:
- await callback.answer()
+ user = describe_user(callback.from_user.id)
bot_name = callback.data.replace("inv_steam_", "") # type: ignore[union-attr]
+ logger.info("Открыт Steam-инвентарь: %s bot=%s", user, bot_name)
+ await callback.answer()
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:
+ logger.warning("Не удалось загрузить Steam-инвентарь: %s bot=%s", user, bot_name)
await callback.message.edit_text("Не удалось загрузить inventory") # type: ignore[union-attr]
return
await callback.message.edit_text(text, reply_markup=keyboard) # type: ignore[union-attr]
@@ -53,13 +64,28 @@ async def steam_inventory(callback: CallbackQuery) -> None:
lambda c: c.data and c.data.startswith("invpage|"), AdminCallbackFilter()
)
async def inventory_page(callback: CallbackQuery) -> None:
- await callback.answer()
+ user = describe_user(callback.from_user.id)
_, inv_type, bot_name, page_value = callback.data.split("|") # type: ignore[union-attr]
+ logger.info(
+ "Открыта страница инвентаря: %s bot=%s type=%s page=%s",
+ user,
+ bot_name,
+ inv_type,
+ page_value,
+ )
+ await callback.answer()
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:
+ logger.warning(
+ "Не удалось загрузить страницу инвентаря: %s bot=%s type=%s page=%s",
+ user,
+ bot_name,
+ inv_type,
+ page_value,
+ )
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
index 9f743fd..e0b7b5a 100644
--- a/bot/handlers/messages.py
+++ b/bot/handlers/messages.py
@@ -5,6 +5,7 @@ from aiogram.types import Message
from bot.api.asf import get_all_bots, send_command
from bot.filters import AdminFilter
+from bot.logging_utils import describe_user, get_logger
from bot.services.redeem import (
extract_keys,
redeem_all_bots_keys,
@@ -25,29 +26,60 @@ from bot.ui.keyboards import (
)
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]
+ logger.info(
+ "Пользователь %s отправил консольную команду: %s",
+ describe_user(message.from_user),
+ command,
+ )
try:
await message.delete()
except Exception:
- pass
+ logger.debug(
+ "Не удалось удалить сообщение с консольной командой от %s",
+ describe_user(message.from_user),
+ exc_info=True,
+ )
try:
response = send_command(f"!{command}")
if response.status_code != 200:
+ logger.warning(
+ "ASF вернул HTTP %s на консольную команду '%s' от %s",
+ response.status_code,
+ 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",
+ command,
+ describe_user(message.from_user),
+ )
text = "Ошибка ASF"
else:
+ logger.info(
+ "Консольная команда '%s' от %s выполнена успешно",
+ command,
+ describe_user(message.from_user),
+ )
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:
+ logger.exception(
+ "Ошибка при выполнении консольной команды '%s' от %s",
+ command,
+ describe_user(message.from_user),
+ )
await message.answer(f"Ошибка: {error}")
return True
@@ -57,6 +89,11 @@ async def handle_single_bot_redeem(message: Message) -> bool:
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]
+ logger.info(
+ "Пользователь %s отправил ключи для бота %s",
+ describe_user(message.from_user),
+ bot_name,
+ )
if menu_message:
try:
response = get_all_bots()
@@ -70,17 +107,38 @@ async def handle_single_bot_redeem(message: Message) -> bool:
),
)
except Exception:
- pass
+ logger.debug(
+ "Не удалось восстановить меню бота %s после ввода ключей от %s",
+ bot_name,
+ 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:
- pass
+ logger.debug(
+ "Не удалось удалить сообщение с ключами для бота %s от %s",
+ bot_name,
+ describe_user(message.from_user),
+ exc_info=True,
+ )
keys = extract_keys(message.text or "")
if not keys:
+ logger.info(
+ "В сообщении от %s для бота %s не найдено ключей",
+ describe_user(message.from_user),
+ bot_name,
+ )
await message.answer("Ключи не найдены")
return True
+ logger.info(
+ "Для бота %s получено %s ключ(ей) от %s",
+ bot_name,
+ len(keys),
+ describe_user(message.from_user),
+ )
checking = await message.answer(
f"Аккаунт: {bot_name}\nНайдено ключей: {len(keys)}\nНачинаю активацию..."
)
@@ -93,35 +151,64 @@ async def handle_all_bots_redeem(message: Message) -> bool:
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]
+ logger.info(
+ "Пользователь %s отправил ключи для активации на всех ботах",
+ describe_user(message.from_user),
+ )
if menu_message:
try:
await menu_message.edit_text(
get_asf_status_text(), reply_markup=main_keyboard()
)
except Exception:
- pass
+ 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:
- pass
+ logger.debug(
+ "Не удалось удалить сообщение с массовой активацией от %s",
+ describe_user(message.from_user),
+ exc_info=True,
+ )
keys = extract_keys(message.text or "")
if not keys:
+ logger.info(
+ "В сообщении от %s для массовой активации не найдено ключей",
+ describe_user(message.from_user),
+ )
await message.answer("Ключи не найдены")
return True
+ logger.info(
+ "Для массовой активации получено %s ключ(ей) от %s",
+ len(keys),
+ describe_user(message.from_user),
+ )
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):
diff --git a/bot/handlers/navigation.py b/bot/handlers/navigation.py
index 7960eee..4974af5 100644
--- a/bot/handlers/navigation.py
+++ b/bot/handlers/navigation.py
@@ -5,6 +5,7 @@ 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.logging_utils import describe_user, get_logger
from bot.services.twofa import stop_twofa_task
from bot.state import (
bot_redeem_messages,
@@ -17,6 +18,7 @@ from bot.ui.formatters import get_asf_status_text
from bot.ui.keyboards import back_keyboard, confirm_action_keyboard, main_keyboard
router = Router()
+logger = get_logger(__name__)
def clear_user_state(user_id: int) -> None:
@@ -25,39 +27,53 @@ def clear_user_state(user_id: int) -> 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:
+ 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 callback.answer()
try:
await callback.message.edit_text(get_asf_status_text(), reply_markup=main_keyboard()) # type: ignore[union-attr]
+ logger.info("Пользователь возвращен в главное меню: %s", user)
except Exception as error:
+ logger.exception("Не удалось вернуть пользователя в главное меню: %s", user)
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:
+ 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]
+ logger.info("Главный экран обновлен: %s", user)
except Exception as error:
+ logger.exception("Не удалось обновить главный экран: %s", user)
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:
+ user = describe_user(callback.from_user.id)
+ logger.info("Запрошено удаление сообщения: %s", user)
await callback.answer()
try:
await callback.message.delete() # type: ignore[union-attr]
+ logger.info("Сообщение удалено: %s", user)
except Exception:
- pass
+ logger.exception("Не удалось удалить сообщение: %s", user)
@router.callback_query(lambda c: c.data == "restart_asf_confirm", AdminCallbackFilter())
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]
"♻ Точно перезапустить ASF?\n\nВо время перезапуска IPC будет недоступен несколько секунд.",
@@ -67,11 +83,18 @@ async def restart_asf_confirm(callback: CallbackQuery) -> None:
@router.callback_query(lambda c: c.data == "restart_asf", AdminCallbackFilter())
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]
response = restart_asf_request()
if response.status_code != 200:
+ logger.warning(
+ "Перезапуск ASF завершился ошибкой HTTP: %s status=%s",
+ user,
+ response.status_code,
+ )
await callback.message.edit_text( # type: ignore[union-attr]
f"Ошибка HTTP {response.status_code}",
reply_markup=main_keyboard(),
@@ -82,7 +105,9 @@ async def restart_asf(callback: CallbackQuery) -> None:
f"ASF успешно перезапущен\n\n{get_asf_status_text()}",
reply_markup=main_keyboard(),
)
+ logger.info("ASF успешно перезапущен: %s", user)
except Exception as error:
+ logger.exception("Не удалось перезапустить ASF: %s", user)
await callback.message.edit_text( # type: ignore[union-attr]
f"Ошибка: {error}",
reply_markup=main_keyboard(),
@@ -91,21 +116,27 @@ async def restart_asf(callback: CallbackQuery) -> None:
@router.callback_query(lambda c: c.data == "plugins", AdminCallbackFilter())
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 callback.answer()
try:
response = get_plugins()
if response.status_code != 200:
+ logger.warning("Не удалось получить список плагинов: %s status=%s", user, response.status_code)
await callback.message.edit_text("Ошибка API") # type: ignore[union-attr]
return
data = response.json()
if not data.get("Success"):
+ logger.warning("ASF вернул ошибку при получении плагинов: %s", user)
await callback.message.edit_text("ASF вернул ошибку") # type: ignore[union-attr]
return
plugins = data.get("Result", [])
if not plugins:
+ logger.info("Плагины ASF не установлены: %s", user)
text = "Плагины не установлены"
else:
+ logger.info("Получен список плагинов ASF: %s count=%s", user, len(plugins))
text = "📁 Плагины ASF:\n\n"
for plugin in plugins:
text += (
@@ -113,4 +144,5 @@ async def plugins_handler(callback: CallbackQuery) -> None:
)
await callback.message.edit_text(text.strip(), reply_markup=back_keyboard()) # type: ignore[union-attr]
except Exception as error:
+ logger.exception("Не удалось отобразить список плагинов: %s", user)
await callback.message.edit_text(f"Ошибка: {error}") # type: ignore[union-attr]
diff --git a/bot/handlers/redeem.py b/bot/handlers/redeem.py
index e75e73c..4592c81 100644
--- a/bot/handlers/redeem.py
+++ b/bot/handlers/redeem.py
@@ -2,6 +2,7 @@ from aiogram import Router
from aiogram.types import CallbackQuery
from bot.filters import AdminCallbackFilter
+from bot.logging_utils import describe_user, get_logger
from bot.state import (
bot_redeem_messages,
bot_redeem_users,
@@ -11,10 +12,13 @@ from bot.state import (
from bot.ui.keyboards import back_keyboard
router = Router()
+logger = get_logger(__name__)
@router.callback_query(lambda c: c.data == "redeem_keys", AdminCallbackFilter())
async def redeem_keys_menu(callback: CallbackQuery) -> 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
@@ -31,8 +35,10 @@ async def redeem_keys_menu(callback: CallbackQuery) -> None:
lambda c: c.data and c.data.startswith("redeem_bot_"), AdminCallbackFilter()
)
async def redeem_bot_menu(callback: CallbackQuery) -> None:
- await callback.answer()
+ 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)
+ 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]
diff --git a/bot/handlers/start.py b/bot/handlers/start.py
index bf0cb9a..e3bb509 100644
--- a/bot/handlers/start.py
+++ b/bot/handlers/start.py
@@ -3,19 +3,26 @@ from aiogram.filters import Command
from aiogram.types import Message
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.ui.keyboards import main_keyboard
router = Router()
+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)
+ logger.info("Получена команда /start: %s", user)
try:
await message.delete()
+ logger.info("Сообщение /start удалено: %s", user)
except Exception:
- pass
+ 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}")
diff --git a/bot/handlers/twofa.py b/bot/handlers/twofa.py
index c8361ad..ce2f1b5 100644
--- a/bot/handlers/twofa.py
+++ b/bot/handlers/twofa.py
@@ -5,11 +5,13 @@ from aiogram.types import CallbackQuery
from bot.api.asf import accept_confirmations, get_confirmations
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.ui.keyboards import back_keyboard, confirmations_keyboard
router = Router()
+logger = get_logger(__name__)
@router.callback_query(
@@ -21,9 +23,18 @@ router = Router()
async def twofa_handler(callback: CallbackQuery) -> None:
await callback.answer()
if not callback.data:
+ logger.debug(
+ "Колбэк открытия 2FA пришел без data от %s",
+ describe_user(callback.from_user),
+ )
return
bot_name = callback.data.replace("2fa_", "")
message_id = callback.message.message_id # type: ignore[union-attr]
+ 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]
f"🔐 {bot_name}\n\nЗагрузка 2FA...",
@@ -31,6 +42,11 @@ async def twofa_handler(callback: CallbackQuery) -> None:
)
task = asyncio.create_task(auto_update_2fa(callback.message, bot_name))
twofa_tasks[message_id] = task
+ logger.info(
+ "Запущено фоновое обновление 2FA для бота %s в сообщении %s",
+ bot_name,
+ message_id,
+ )
@router.callback_query(
@@ -43,14 +59,29 @@ 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]
+ logger.info(
+ "Пользователь %s подтвердил сделки для бота %s",
+ describe_user(callback.from_user),
+ bot_name,
+ )
try:
status = await accept_confirmations(bot_name)
if status != 200:
+ logger.warning(
+ "Не удалось подтвердить сделки для бота %s: HTTP %s",
+ bot_name,
+ status,
+ )
await callback.answer("Ошибка HTTP", show_alert=True)
return
+ logger.info("Сделки для бота %s подтверждены", bot_name)
await callback.answer("Подтверждено", show_alert=True)
await twofa_handler(callback)
except Exception as error:
+ logger.exception(
+ "Ошибка при подтверждении сделок для бота %s",
+ bot_name,
+ )
await callback.message.edit_text(f"Ошибка: {error}") # type: ignore[union-attr]
@@ -61,18 +92,37 @@ 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:
+ logger.debug(
+ "Колбэк списка подтверждений пришел без data от %s",
+ describe_user(callback.from_user),
+ )
return
bot_name = callback.data.replace("confirm_list_", "")
+ logger.info(
+ "Пользователь %s открыл список подтверждений для бота %s",
+ describe_user(callback.from_user),
+ bot_name,
+ )
try:
confirmations = await get_confirmations(bot_name)
if not confirmations:
+ logger.info("Для бота %s подтверждений не найдено", bot_name)
text = "Подтверждений нет"
else:
+ logger.info(
+ "Для бота %s найдено %s подтверждени(й)",
+ bot_name,
+ len(confirmations),
+ )
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:
+ logger.exception(
+ "Ошибка при загрузке списка подтверждений для бота %s",
+ bot_name,
+ )
await callback.message.edit_text(f"Ошибка: {error}") # type: ignore[union-attr]
@@ -83,21 +133,45 @@ 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:
+ logger.debug(
+ "Колбэк подтверждения всех сделок пришел без data от %s",
+ 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:
+ logger.warning(
+ "Не удалось подтвердить все сделки для бота %s: HTTP %s",
+ bot_name,
+ status,
+ )
await callback.answer("Ошибка HTTP", show_alert=True)
return
confirmations = await get_confirmations(bot_name)
if not confirmations:
+ logger.info("После подтверждения у бота %s не осталось сделок", bot_name)
text = f"🔐 {bot_name}\n\nПодтверждений больше нет"
else:
+ logger.info(
+ "После подтверждения у бота %s осталось %s подтверждени(й)",
+ bot_name,
+ len(confirmations),
+ )
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:
+ logger.exception(
+ "Ошибка при подтверждении всех сделок для бота %s",
+ bot_name,
+ )
await callback.message.edit_text(f"Ошибка: {error}") # type: ignore[union-attr]
diff --git a/bot/logging_utils.py b/bot/logging_utils.py
new file mode 100644
index 0000000..0edc394
--- /dev/null
+++ b/bot/logging_utils.py
@@ -0,0 +1,20 @@
+import logging
+from typing import Any
+
+
+def get_logger(name: str) -> logging.Logger:
+ return logging.getLogger(name)
+
+
+def describe_user(user: Any) -> str:
+ if user is None:
+ return "пользователь="
+
+ user_id = getattr(user, "id", user)
+ return f"пользователь={user_id}"
+
+
+def mask_key(key: str) -> str:
+ if len(key) < 9:
+ return "***"
+ return f"{key[:5]}-*****-*****"
diff --git a/bot/services/bots.py b/bot/services/bots.py
index d3ea260..a174522 100644
--- a/bot/services/bots.py
+++ b/bot/services/bots.py
@@ -1,27 +1,53 @@
import asyncio
from bot.api.asf import get_all_bots, get_bot, save_bot_config, send_command
+from bot.logging_utils import get_logger
+
+logger = get_logger(__name__)
def is_bot_playing(bot_name: str) -> bool:
+ logger.info("Проверка, играет ли бот: bot=%s", bot_name)
response = get_all_bots()
if response.status_code != 200:
+ logger.warning(
+ "Не удалось проверить состояние игры бота: bot=%s status=%s",
+ bot_name,
+ response.status_code,
+ )
return False
+
data = response.json()
bot = data.get("Result", {}).get(bot_name, {})
- return bool(bot.get("PlayingBlocked", False)) or bool(
+ playing = bool(bot.get("PlayingBlocked", False)) or bool(
bot.get("CurrentGamesPlayed", [])
)
+ logger.info("Состояние игры бота определено: bot=%s playing=%s", bot_name, playing)
+ return playing
def is_idle_enabled(bot_name: str, app_id: int) -> bool:
+ logger.info("Проверка статуса idle-игры: bot=%s app_id=%s", bot_name, app_id)
response = get_bot(bot_name)
if response.status_code != 200:
+ logger.warning(
+ "Не удалось получить конфиг бота для проверки idle: bot=%s status=%s",
+ bot_name,
+ response.status_code,
+ )
return False
+
data = response.json()
bot = data.get("Result", {}).get(bot_name, {})
config = bot.get("BotConfig", {})
- return app_id in config.get("GamesPlayedWhileIdle", [])
+ enabled = app_id in config.get("GamesPlayedWhileIdle", [])
+ logger.info(
+ "Статус idle-игры определен: bot=%s app_id=%s enabled=%s",
+ bot_name,
+ app_id,
+ enabled,
+ )
+ return enabled
def toggle_idle_game(config: dict, app_id: int) -> tuple[dict, bool]:
@@ -38,22 +64,45 @@ def toggle_idle_game(config: dict, app_id: int) -> tuple[dict, bool]:
async def reload_bot(bot_name: str) -> None:
try:
+ logger.info("Отправка команды перезагрузки бота: bot=%s", bot_name)
send_command(f"!reload {bot_name}")
except Exception:
- pass
+ logger.exception(
+ "Не удалось отправить команду перезагрузки бота: bot=%s", bot_name
+ )
async def update_idle_game(bot_name: str, app_id: int) -> tuple[bool, str, bool]:
+ logger.info("Изменение настройки idle-игры: bot=%s app_id=%s", bot_name, app_id)
response = get_bot(bot_name)
if response.status_code != 200:
+ logger.warning(
+ "Не удалось загрузить конфиг бота для idle: bot=%s status=%s",
+ bot_name,
+ response.status_code,
+ )
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:
+ logger.warning(
+ "Не удалось сохранить конфиг idle-игры: bot=%s app_id=%s status=%s",
+ bot_name,
+ app_id,
+ save.status_code,
+ )
return False, "Ошибка сохранения", enabled
+
asyncio.create_task(reload_bot(bot_name))
action = "Idle включен" if enabled else "Idle выключен"
+ logger.info(
+ "Настройка idle-игры изменена: bot=%s app_id=%s enabled=%s",
+ bot_name,
+ app_id,
+ enabled,
+ )
return True, action, enabled
diff --git a/bot/services/inventory.py b/bot/services/inventory.py
index 2f1bee8..bb81057 100644
--- a/bot/services/inventory.py
+++ b/bot/services/inventory.py
@@ -1,10 +1,14 @@
from bot.api.asf import get_bot_inventory
from bot.constants import PAGE_SIZE
+from bot.logging_utils import get_logger
from bot.ui.formatters import get_inventory_icon
from bot.ui.keyboards import inventory_menu_keyboard, inventory_page_keyboard
+logger = get_logger(__name__)
+
async def build_inventory_menu(bot_name: str) -> tuple[str, object]:
+ logger.info("Формирование меню инвентаря: bot=%s", bot_name)
cs2_inventory = await get_bot_inventory(bot_name, 730, 2)
steam_inventory = await get_bot_inventory(bot_name, 753, 6)
cs2_assets = []
@@ -12,13 +16,23 @@ async def build_inventory_menu(bot_name: str) -> tuple[str, object]:
try:
cs2_assets = cs2_inventory.get(bot_name, {}).get("Assets", []) # type: ignore[union-attr]
except Exception:
- pass
+ logger.exception("Ошибка чтения CS2-инвентаря: bot=%s", bot_name)
try:
steam_assets = steam_inventory.get(bot_name, {}).get("Assets", []) # type: ignore[union-attr]
except Exception:
- pass
+ logger.exception("Ошибка чтения Steam-инвентаря: bot=%s", bot_name)
if not cs2_assets and not steam_assets:
- return f"📦 Инвентарь {bot_name} пуст", inventory_menu_keyboard(bot_name, 0, 0)
+ logger.info("Инвентарь пуст: bot=%s", bot_name)
+ return (
+ f"📦 Инвентарь {bot_name} пуст",
+ inventory_menu_keyboard(bot_name, 0, 0),
+ )
+ logger.info(
+ "Меню инвентаря сформировано: bot=%s cs2_items=%s steam_items=%s",
+ bot_name,
+ len(cs2_assets),
+ len(steam_assets),
+ )
text = (
f"📦 Инвентарь {bot_name}\n\n"
"🔄 — можно трейдить\n"
@@ -32,13 +46,28 @@ async def build_inventory_menu(bot_name: str) -> tuple[str, object]:
async def render_inventory_page(
bot_name: str, inventory_type: str, appid: int, contextid: int, page: int
) -> tuple[str, object] | tuple[None, None]:
+ logger.info(
+ "Формирование страницы инвентаря: bot=%s type=%s appid=%s contextid=%s page=%s",
+ bot_name,
+ inventory_type,
+ appid,
+ contextid,
+ page,
+ )
inventory = await get_bot_inventory(bot_name, appid, contextid)
if not inventory:
+ logger.warning(
+ "Не удалось получить данные для страницы инвентаря: bot=%s type=%s page=%s",
+ bot_name,
+ inventory_type,
+ page,
+ )
return None, None
bot_inventory = inventory.get(bot_name, {})
assets = bot_inventory.get("Assets", [])
descriptions = bot_inventory.get("Descriptions", [])
if not assets:
+ logger.info("Инвентарь выбранного типа пуст: bot=%s type=%s", bot_name, inventory_type)
return f"Инвентарь {bot_name} пуст", inventory_menu_keyboard(bot_name, 0, 0)
desc_map = {}
for desc in descriptions:
@@ -67,7 +96,7 @@ async def render_inventory_page(
marketable_count += 1
if desc.get("tradable"):
tradable_count += 1
- if icon == "🃏":
+ if icon == "🎏":
cards_count += amount
elif icon == "😀":
emotes_count += amount
@@ -78,22 +107,30 @@ async def render_inventory_page(
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}"
+ f"📦 Предметов: {len(assets)}\n"
+ f"💰 Можно продать: {marketable_count}\n"
+ f"🔄 Можно трейдить: {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}"
+ f"\n🎏 Карточек: {cards_count}"
+ f"\n😀 Смайлов: {emotes_count}"
+ f"\n🖼 Фонов: {backgrounds_count}"
+ f"\n💎 Самоцветов: {gems_count}"
)
text = (
f"📦 {inv_name} Inventory {bot_name}\n"
f"📄 Страница {page + 1}/{total_pages}\n\n"
f"{stats}\n\n" + "\n".join(lines)
)
+ logger.info(
+ "Страница инвентаря сформирована: bot=%s type=%s page=%s total_pages=%s items_on_page=%s",
+ bot_name,
+ inventory_type,
+ page,
+ total_pages,
+ len(page_assets),
+ )
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
index eae94f4..08cac8b 100644
--- a/bot/services/redeem.py
+++ b/bot/services/redeem.py
@@ -2,13 +2,17 @@ import asyncio
import re
from bot.api.asf import get_all_bots, redeem_key
+from bot.logging_utils import get_logger, mask_key
KEY_PATTERN = r"[A-Z0-9]{5}(?:-[A-Z0-9]{5}){2}"
+logger = get_logger(__name__)
def extract_keys(text: str) -> list[str]:
keys = re.findall(KEY_PATTERN, text.upper())
- return list(dict.fromkeys(keys))
+ unique_keys = list(dict.fromkeys(keys))
+ logger.info("Из текста извлечены ключи: count=%s", len(unique_keys))
+ return unique_keys
def parse_redeem_result(result: str, bot_name: str | None = None) -> str:
@@ -18,6 +22,7 @@ def parse_redeem_result(result: str, bot_name: str | None = None) -> str:
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:
@@ -34,17 +39,42 @@ def parse_redeem_result(result: str, bot_name: str | None = None) -> str:
async def redeem_single_bot_keys(bot_name: str, keys: list[str]) -> str:
+ logger.info(
+ "Начата активация ключей для одного бота: bot=%s count=%s",
+ bot_name,
+ len(keys),
+ )
success_count = 0
failed_count = 0
results = []
+
for key in keys:
+ logger.info(
+ "Попытка активации ключа для одного бота: bot=%s key=%s",
+ bot_name,
+ mask_key(key),
+ )
success, result = await redeem_key(bot_name, key)
await asyncio.sleep(2)
+
if not success or not result:
failed_count += 1
+ logger.warning(
+ "Активация ключа завершилась ошибкой ASF/HTTP: bot=%s key=%s",
+ bot_name,
+ mask_key(key),
+ )
results.append(f"❌ {key}: ASF ERROR")
continue
+
status = parse_redeem_result(result)
+ logger.info(
+ "Получен результат активации ключа: bot=%s key=%s status=%s",
+ bot_name,
+ mask_key(key),
+ status,
+ )
+
if status == "success":
success_count += 1
results.append(f"✅ {key}: активировано")
@@ -66,31 +96,62 @@ async def redeem_single_bot_keys(bot_name: str, keys: list[str]) -> str:
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])
)
+ logger.info(
+ "Завершена активация ключей для одного бота: bot=%s success=%s failed=%s",
+ bot_name,
+ success_count,
+ failed_count,
+ )
return text[:4000] + ("\n\n...обрезано" if len(text) > 4000 else "")
async def redeem_all_bots_keys(keys: list[str]) -> str:
+ logger.info("Начата активация ключей по всем ботам: count=%s", len(keys))
response = get_all_bots()
data = response.json()
bots = list(data.get("Result", {}).keys())
+ logger.info("Для массовой активации найдено ботов: count=%s", len(bots))
success_count = 0
failed_count = 0
results = []
+
for key in keys:
activated = False
key_report = []
+ logger.info("Начата обработка ключа по всем ботам: key=%s", mask_key(key))
+
for bot_name in bots:
+ logger.info(
+ "Попытка активации ключа на боте: bot=%s key=%s",
+ bot_name,
+ mask_key(key),
+ )
success, result = await redeem_key(bot_name, key)
await asyncio.sleep(2)
+
if not success or not result:
+ logger.warning(
+ "Активация ключа завершилась ошибкой ASF/HTTP: bot=%s key=%s",
+ bot_name,
+ mask_key(key),
+ )
key_report.append(f"❌ {bot_name}: ASF ERROR")
continue
+
status = parse_redeem_result(result, bot_name)
+ logger.info(
+ "Получен результат активации ключа: bot=%s key=%s status=%s",
+ bot_name,
+ mask_key(key),
+ status,
+ )
+
if status == "success":
key_report.append(f"✅ {bot_name}: активировано")
success_count += 1
@@ -112,12 +173,24 @@ async def redeem_all_bots_keys(keys: list[str]) -> str:
key_report.append(f"❌ {bot_name}: неверный ключ")
break
key_report.append(f"❔ {bot_name}: неизвестный ответ")
+
if not activated:
failed_count += 1
+ logger.warning(
+ "Ключ не удалось активировать ни на одном боте: key=%s",
+ mask_key(key),
+ )
+
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])
)
+ logger.info(
+ "Завершена массовая активация ключей: success=%s failed=%s",
+ success_count,
+ failed_count,
+ )
return text[:4000] + ("\n\n...обрезано" if len(text) > 4000 else "")
diff --git a/bot/services/twofa.py b/bot/services/twofa.py
index b98d08f..f35efe2 100644
--- a/bot/services/twofa.py
+++ b/bot/services/twofa.py
@@ -1,23 +1,37 @@
import asyncio
from bot.api.asf import get_2fa_token, get_confirmations
+from bot.logging_utils import get_logger
from bot.state import twofa_tasks
from bot.ui.keyboards import twofa_keyboard
+logger = get_logger(__name__)
+
async def stop_twofa_task(message_id: int) -> None:
task = twofa_tasks.pop(message_id, None)
if task:
+ logger.info(
+ "Остановка фоновой задачи обновления 2FA: message_id=%s", message_id
+ )
task.cancel()
try:
await task
except Exception:
- pass
+ logger.exception(
+ "Ошибка при завершении фоновой задачи обновления 2FA: message_id=%s",
+ message_id,
+ )
async def auto_update_2fa(message, bot_name: str) -> None:
last_code = None
message_id = message.message_id
+ logger.info(
+ "Запуск фонового обновления 2FA: bot=%s message_id=%s",
+ bot_name,
+ message_id,
+ )
try:
while True:
code = await get_2fa_token(bot_name)
@@ -30,12 +44,37 @@ async def auto_update_2fa(message, bot_name: str) -> None:
reply_markup=twofa_keyboard(bot_name, bool(confirmations)),
)
last_code = code
+ logger.info(
+ "Сообщение 2FA обновлено: bot=%s message_id=%s confirmations=%s",
+ bot_name,
+ message_id,
+ len(confirmations),
+ )
except Exception:
+ logger.exception(
+ "Не удалось обновить сообщение 2FA: bot=%s message_id=%s",
+ bot_name,
+ message_id,
+ )
break
await asyncio.sleep(15)
except asyncio.CancelledError:
- pass
+ logger.info(
+ "Фоновое обновление 2FA отменено: bot=%s message_id=%s",
+ bot_name,
+ message_id,
+ )
except Exception as error:
- print(f"2FA updater error: {error}")
+ logger.exception(
+ "Фоновое обновление 2FA завершилось ошибкой: bot=%s message_id=%s error=%s",
+ bot_name,
+ message_id,
+ error,
+ )
finally:
twofa_tasks.pop(message_id, None)
+ logger.info(
+ "Фоновая задача обновления 2FA завершена: bot=%s message_id=%s",
+ bot_name,
+ message_id,
+ )
diff --git a/bot/ui/formatters.py b/bot/ui/formatters.py
index 434a9b9..0dfe095 100644
--- a/bot/ui/formatters.py
+++ b/bot/ui/formatters.py
@@ -29,7 +29,7 @@ def get_inventory_icon(desc: dict) -> str:
name = tag.get("localized_tag_name", "")
if category == "item_class":
if "Trading Card" in name:
- return "🃏"
+ return "🎏"
if "Emoticon" in name:
return "😀"
if "Profile Background" in name:
@@ -40,6 +40,7 @@ def get_inventory_icon(desc: dict) -> str:
return "💎"
if "Gift" in name:
return "🎁"
+
item_type = desc.get("type", "").lower()
if "knife" in item_type:
return "🔪"
@@ -98,7 +99,11 @@ def get_bot_status_icon(bot: dict) -> str:
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 "🃏 Карточки не фармится"
+ 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 "нет"
@@ -110,9 +115,10 @@ def format_bot_ui(bot: dict) -> str:
profile_url = f"https://steamcommunity.com/profiles/{steam_id}"
text = (
f"{online} {bot.get('BotName') or 'Загрузка...'}"
- f'({nickname})\n'
+ 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]
@@ -126,8 +132,9 @@ def format_bot_ui(bot: dict) -> str:
text += f"{idle_text}\n"
else:
text += "⌚ Накрутка часов: выключена\n"
+
text += (
- f"🆔 : {steam_id}\n"
+ f"🆔: {steam_id}\n"
f"🔐 2FA: {twofa}\n\n"
f"💰 Баланс: {balance:.2f} {currency}\n"
)
@@ -161,8 +168,10 @@ def get_farm_summary(bots: dict) -> str:
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 "На аккаунтах нечего не фармиться"
+ return "На аккаунтах нечего фармить"
+
hours = total_time_seconds // 3600
minutes = (total_time_seconds % 3600) // 60
return (
diff --git a/bot/ui/keyboards.py b/bot/ui/keyboards.py
index 3a3d59f..58325eb 100644
--- a/bot/ui/keyboards.py
+++ b/bot/ui/keyboards.py
@@ -35,7 +35,11 @@ def back_keyboard(callback_data: str = "back") -> InlineKeyboardMarkup:
def games_keyboard(bot_name: str, is_enabled: bool) -> InlineKeyboardMarkup:
- text = "Остановить накрутку CS2" if is_enabled else "⌚ Накрутить часы CS2"
+ text = (
+ "Остановить накрутку CS2"
+ if is_enabled
+ else "⌚ Накрутить часы CS2"
+ )
return InlineKeyboardMarkup(
inline_keyboard=[
[InlineKeyboardButton(text=text, callback_data=f"farm|{bot_name}|730")],
@@ -179,6 +183,7 @@ def inventory_page_keyboard(
callback_data=f"invpage|{inventory_type}|{bot_name}|{page + 1}",
)
)
+
keyboard = []
if nav_buttons:
keyboard.append(nav_buttons)
diff --git a/main.py b/main.py
index 228265a..c11adc6 100644
--- a/main.py
+++ b/main.py
@@ -1,9 +1,11 @@
import asyncio
+import logging
from aiogram import Bot, Dispatcher
from aiogram.client.default import DefaultBotProperties
-from bot.config import API_TOKEN
+from bot.config import BOT_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
@@ -13,7 +15,9 @@ 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
-bot = Bot(token=API_TOKEN, default=DefaultBotProperties(parse_mode="HTML")) # type: ignore[arg-type]
+logger = logging.getLogger(__name__)
+
+bot = Bot(token=BOT_TOKEN, default=DefaultBotProperties(parse_mode="HTML")) # type: ignore[arg-type]
dp = Dispatcher()
dp.include_router(start_router)
@@ -27,8 +31,23 @@ dp.include_router(messages_router)
async def main() -> None:
+ logger.info("Запуск Telegram-бота ASF Control Bot")
await dp.start_polling(bot)
if __name__ == "__main__":
- asyncio.run(main())
+ logging.basicConfig(
+ level=logging.INFO,
+ format="%(asctime)s | %(levelname)s | %(name)s | %(message)s",
+ handlers=[
+ logging.FileHandler("bot.log", encoding="utf-8"),
+ logging.StreamHandler(),
+ ],
+ )
+ try:
+ asyncio.run(main())
+ except KeyboardInterrupt:
+ logger.info("Получен сигнал остановки, бот завершает работу")
+ except Exception:
+ logger.exception("Бот завершился из-за необработанной ошибки")
+ raise