forked from FOSS/ASF-Control-Bot
refactor(bot): modularize bot code
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
+118
@@ -0,0 +1,118 @@
|
|||||||
|
import aiohttp
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from bot.config import ASF_PASSWORD, ASF_URL
|
||||||
|
|
||||||
|
IPC_BASE_URL = "http://127.0.0.1:1242"
|
||||||
|
|
||||||
|
|
||||||
|
def _headers() -> dict[str, str | None]:
|
||||||
|
return {"Authentication": ASF_PASSWORD}
|
||||||
|
|
||||||
|
|
||||||
|
def asf_get(path: str) -> requests.Response:
|
||||||
|
return requests.get(f"{IPC_BASE_URL}{path}", headers=_headers())
|
||||||
|
|
||||||
|
|
||||||
|
def asf_post(path: str, payload: dict | None = None) -> requests.Response:
|
||||||
|
return requests.post(f"{IPC_BASE_URL}{path}", headers=_headers(), json=payload)
|
||||||
|
|
||||||
|
|
||||||
|
def get_asf_status() -> requests.Response:
|
||||||
|
return requests.get(ASF_URL, headers=_headers()) # type: ignore[arg-type]
|
||||||
|
|
||||||
|
|
||||||
|
def get_all_bots() -> requests.Response:
|
||||||
|
return asf_get("/Api/Bot/ASF")
|
||||||
|
|
||||||
|
|
||||||
|
def get_bot(bot_name: str) -> requests.Response:
|
||||||
|
return asf_get(f"/Api/Bot/{bot_name}")
|
||||||
|
|
||||||
|
|
||||||
|
def get_plugins() -> requests.Response:
|
||||||
|
return asf_get("/Api/Plugins")
|
||||||
|
|
||||||
|
|
||||||
|
def send_command(command: str) -> requests.Response:
|
||||||
|
return asf_post("/Api/Command", {"Command": command})
|
||||||
|
|
||||||
|
|
||||||
|
def restart_asf() -> requests.Response:
|
||||||
|
return send_command("!restart")
|
||||||
|
|
||||||
|
|
||||||
|
async def get_bot_inventory(bot_name: str, appid: int, contextid: int) -> dict | None:
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.get(
|
||||||
|
f"{IPC_BASE_URL}/Api/Bot/{bot_name}/Inventory/{appid}/{contextid}",
|
||||||
|
headers=_headers(),
|
||||||
|
) as response:
|
||||||
|
if response.status != 200:
|
||||||
|
return None
|
||||||
|
data = await response.json()
|
||||||
|
if not data.get("Success"):
|
||||||
|
return None
|
||||||
|
return data.get("Result")
|
||||||
|
|
||||||
|
|
||||||
|
async def get_2fa_token(bot_name: str) -> str:
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.get(
|
||||||
|
f"{IPC_BASE_URL}/Api/Bot/{bot_name}/TwoFactorAuthentication/Token",
|
||||||
|
headers=_headers(),
|
||||||
|
) as response:
|
||||||
|
if response.status != 200:
|
||||||
|
return f"Ошибка HTTP {response.status}"
|
||||||
|
data = await response.json()
|
||||||
|
if not data.get("Success"):
|
||||||
|
return "Ошибка ASF"
|
||||||
|
try:
|
||||||
|
return data["Result"][bot_name]["Result"]
|
||||||
|
except Exception:
|
||||||
|
return "Не удалось получить код"
|
||||||
|
|
||||||
|
|
||||||
|
async def get_confirmations(bot_name: str) -> list:
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.get(
|
||||||
|
f"{IPC_BASE_URL}/Api/Bot/{bot_name}/TwoFactorAuthentication/Confirmations",
|
||||||
|
headers=_headers(),
|
||||||
|
) as response:
|
||||||
|
if response.status != 200:
|
||||||
|
return []
|
||||||
|
data = await response.json()
|
||||||
|
if not data.get("Success"):
|
||||||
|
return []
|
||||||
|
try:
|
||||||
|
return data["Result"][bot_name]["Result"]
|
||||||
|
except Exception:
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
async def accept_confirmations(bot_name: str) -> int:
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.post(
|
||||||
|
f"{IPC_BASE_URL}/Api/Bot/{bot_name}/TwoFactorAuthentication/Confirmations/Accept",
|
||||||
|
headers=_headers(),
|
||||||
|
) as response:
|
||||||
|
return response.status
|
||||||
|
|
||||||
|
|
||||||
|
def save_bot_config(bot_name: str, config: dict) -> requests.Response:
|
||||||
|
return asf_post(f"/Api/Bot/{bot_name}", {"BotConfig": config})
|
||||||
|
|
||||||
|
|
||||||
|
async def redeem_key(bot_name: str, key: str) -> tuple[bool, str]:
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.post(
|
||||||
|
f"{IPC_BASE_URL}/Api/Command",
|
||||||
|
headers=_headers(),
|
||||||
|
json={"Command": f"!redeem {bot_name} {key}"},
|
||||||
|
) as response:
|
||||||
|
if response.status != 200:
|
||||||
|
return False, f"HTTP_ERROR_{response.status}"
|
||||||
|
data = await response.json()
|
||||||
|
if not data.get("Success"):
|
||||||
|
return False, "ASF_ERROR"
|
||||||
|
return True, str(data.get("Result", ""))
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
API_TOKEN = os.getenv("API_TOKEN")
|
||||||
|
ADMIN_ID = int(os.getenv("ADMIN_ID")) # type: ignore[arg-type]
|
||||||
|
ASF_URL = os.getenv("ASF_URL")
|
||||||
|
ASF_PASSWORD = os.getenv("ASF_PASSWORD")
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
PAGE_SIZE = 40
|
||||||
|
GAMES = {730: "CS2", 440: "TF2", 570: "Dota 2"}
|
||||||
|
|
||||||
|
appid = 753
|
||||||
|
contextid = 6
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
from aiogram.filters import BaseFilter
|
||||||
|
from aiogram.types import CallbackQuery, Message
|
||||||
|
|
||||||
|
from bot.config import ADMIN_ID
|
||||||
|
|
||||||
|
|
||||||
|
class AdminFilter(BaseFilter):
|
||||||
|
async def __call__(self, message: Message) -> bool:
|
||||||
|
return message.from_user.id == ADMIN_ID # type: ignore[union-attr]
|
||||||
|
|
||||||
|
|
||||||
|
class AdminCallbackFilter(BaseFilter):
|
||||||
|
async def __call__(self, callback: CallbackQuery) -> bool:
|
||||||
|
return callback.from_user.id == ADMIN_ID
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -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
|
||||||
@@ -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]
|
||||||
@@ -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]
|
||||||
@@ -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)
|
||||||
@@ -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]
|
||||||
@@ -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}"),
|
||||||
|
)
|
||||||
@@ -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}")
|
||||||
@@ -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]
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import asyncio
|
||||||
|
|
||||||
|
from bot.api.asf import get_all_bots, get_bot, save_bot_config, send_command
|
||||||
|
|
||||||
|
|
||||||
|
def is_bot_playing(bot_name: str) -> bool:
|
||||||
|
response = get_all_bots()
|
||||||
|
if response.status_code != 200:
|
||||||
|
return False
|
||||||
|
data = response.json()
|
||||||
|
bot = data.get("Result", {}).get(bot_name, {})
|
||||||
|
return bool(bot.get("PlayingBlocked", False)) or bool(bot.get("CurrentGamesPlayed", []))
|
||||||
|
|
||||||
|
|
||||||
|
def is_idle_enabled(bot_name: str, app_id: int) -> bool:
|
||||||
|
response = get_bot(bot_name)
|
||||||
|
if response.status_code != 200:
|
||||||
|
return False
|
||||||
|
data = response.json()
|
||||||
|
bot = data.get("Result", {}).get(bot_name, {})
|
||||||
|
config = bot.get("BotConfig", {})
|
||||||
|
return app_id in config.get("GamesPlayedWhileIdle", [])
|
||||||
|
|
||||||
|
|
||||||
|
def toggle_idle_game(config: dict, app_id: int) -> tuple[dict, bool]:
|
||||||
|
idle_games = config.get("GamesPlayedWhileIdle", [])
|
||||||
|
if app_id in idle_games:
|
||||||
|
idle_games.remove(app_id)
|
||||||
|
enabled = False
|
||||||
|
else:
|
||||||
|
idle_games.append(app_id)
|
||||||
|
enabled = True
|
||||||
|
config["GamesPlayedWhileIdle"] = idle_games
|
||||||
|
return config, enabled
|
||||||
|
|
||||||
|
|
||||||
|
async def reload_bot(bot_name: str) -> None:
|
||||||
|
try:
|
||||||
|
send_command(f"!reload {bot_name}")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
async def update_idle_game(bot_name: str, app_id: int) -> tuple[bool, str, bool]:
|
||||||
|
response = get_bot(bot_name)
|
||||||
|
if response.status_code != 200:
|
||||||
|
return False, "Ошибка API", False
|
||||||
|
data = response.json()
|
||||||
|
bot_data = data.get("Result", {}).get(bot_name, {})
|
||||||
|
config = bot_data.get("BotConfig", {})
|
||||||
|
config, enabled = toggle_idle_game(config, app_id)
|
||||||
|
save = save_bot_config(bot_name, config)
|
||||||
|
if save.status_code != 200:
|
||||||
|
return False, "Ошибка сохранения", enabled
|
||||||
|
asyncio.create_task(reload_bot(bot_name))
|
||||||
|
action = "Idle включен" if enabled else "Idle выключен"
|
||||||
|
return True, action, enabled
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
from bot.api.asf import get_bot_inventory
|
||||||
|
from bot.constants import PAGE_SIZE
|
||||||
|
from bot.ui.formatters import get_inventory_icon
|
||||||
|
from bot.ui.keyboards import inventory_menu_keyboard, inventory_page_keyboard
|
||||||
|
|
||||||
|
|
||||||
|
async def build_inventory_menu(bot_name: str) -> tuple[str, object]:
|
||||||
|
cs2_inventory = await get_bot_inventory(bot_name, 730, 2)
|
||||||
|
steam_inventory = await get_bot_inventory(bot_name, 753, 6)
|
||||||
|
cs2_assets = []
|
||||||
|
steam_assets = []
|
||||||
|
try:
|
||||||
|
cs2_assets = cs2_inventory.get(bot_name, {}).get("Assets", []) # type: ignore[union-attr]
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
steam_assets = steam_inventory.get(bot_name, {}).get("Assets", []) # type: ignore[union-attr]
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
if not cs2_assets and not steam_assets:
|
||||||
|
return f"📦 Инвентарь {bot_name} пуст", inventory_menu_keyboard(bot_name, 0, 0)
|
||||||
|
text = (
|
||||||
|
f"📦 Инвентарь {bot_name}\n\n"
|
||||||
|
"🔄 — можно трейдить\n"
|
||||||
|
"💰 — можно продавать\n"
|
||||||
|
"🔒 — нельзя продавать\n"
|
||||||
|
"❌ — нельзя трейдить"
|
||||||
|
)
|
||||||
|
return text, inventory_menu_keyboard(bot_name, len(cs2_assets), len(steam_assets))
|
||||||
|
|
||||||
|
|
||||||
|
async def render_inventory_page(
|
||||||
|
bot_name: str, inventory_type: str, appid: int, contextid: int, page: int
|
||||||
|
) -> tuple[str, object] | tuple[None, None]:
|
||||||
|
inventory = await get_bot_inventory(bot_name, appid, contextid)
|
||||||
|
if not inventory:
|
||||||
|
return None, None
|
||||||
|
bot_inventory = inventory.get(bot_name, {})
|
||||||
|
assets = bot_inventory.get("Assets", [])
|
||||||
|
descriptions = bot_inventory.get("Descriptions", [])
|
||||||
|
if not assets:
|
||||||
|
return f"Инвентарь {bot_name} пуст", inventory_menu_keyboard(bot_name, 0, 0)
|
||||||
|
desc_map = {}
|
||||||
|
for desc in descriptions:
|
||||||
|
key = (str(desc.get("classid")), str(desc.get("instanceid")))
|
||||||
|
desc_map[key] = desc
|
||||||
|
total_pages = (len(assets) + PAGE_SIZE - 1) // PAGE_SIZE
|
||||||
|
start = page * PAGE_SIZE
|
||||||
|
end = start + PAGE_SIZE
|
||||||
|
page_assets = assets[start:end]
|
||||||
|
lines = []
|
||||||
|
marketable_count = 0
|
||||||
|
tradable_count = 0
|
||||||
|
cards_count = 0
|
||||||
|
emotes_count = 0
|
||||||
|
backgrounds_count = 0
|
||||||
|
gems_count = 0
|
||||||
|
for asset in page_assets:
|
||||||
|
key = (str(asset.get("classid")), str(asset.get("instanceid")))
|
||||||
|
desc = desc_map.get(key, {})
|
||||||
|
name = desc.get("market_name", "Unknown")
|
||||||
|
amount = asset.get("amount", 1)
|
||||||
|
tradable = "🔄" if desc.get("tradable") else "❌"
|
||||||
|
marketable = "💰" if desc.get("marketable") else "🔒"
|
||||||
|
icon = get_inventory_icon(desc)
|
||||||
|
if desc.get("marketable"):
|
||||||
|
marketable_count += 1
|
||||||
|
if desc.get("tradable"):
|
||||||
|
tradable_count += 1
|
||||||
|
if icon == "🃏":
|
||||||
|
cards_count += amount
|
||||||
|
elif icon == "😀":
|
||||||
|
emotes_count += amount
|
||||||
|
elif icon == "🖼":
|
||||||
|
backgrounds_count += amount
|
||||||
|
elif icon == "💎":
|
||||||
|
gems_count += amount
|
||||||
|
lines.append(f"{icon} {tradable}{marketable} {name} x{amount}")
|
||||||
|
inv_name = "CS2" if appid == 730 else "Steam"
|
||||||
|
stats = (
|
||||||
|
f"📦 Items: {len(assets)}\n"
|
||||||
|
f"💰 Marketable: {marketable_count}\n"
|
||||||
|
f"🔄 Tradable: {tradable_count}"
|
||||||
|
)
|
||||||
|
if appid == 753:
|
||||||
|
stats += (
|
||||||
|
f"\n🃏 Cards: {cards_count}"
|
||||||
|
f"\n😀 Emotes: {emotes_count}"
|
||||||
|
f"\n🖼 Backgrounds: {backgrounds_count}"
|
||||||
|
f"\n💎 Gems: {gems_count}"
|
||||||
|
)
|
||||||
|
text = (
|
||||||
|
f"📦 {inv_name} Inventory {bot_name}\n"
|
||||||
|
f"📄 Страница {page + 1}/{total_pages}\n\n"
|
||||||
|
f"{stats}\n\n"
|
||||||
|
+ "\n".join(lines)
|
||||||
|
)
|
||||||
|
return text[:4000], inventory_page_keyboard(inventory_type, bot_name, page, total_pages)
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
import asyncio
|
||||||
|
import re
|
||||||
|
|
||||||
|
from bot.api.asf import get_all_bots, redeem_key
|
||||||
|
|
||||||
|
KEY_PATTERN = r"[A-Z0-9]{5}(?:-[A-Z0-9]{5}){2}"
|
||||||
|
|
||||||
|
|
||||||
|
def extract_keys(text: str) -> list[str]:
|
||||||
|
keys = re.findall(KEY_PATTERN, text.upper())
|
||||||
|
return list(dict.fromkeys(keys))
|
||||||
|
|
||||||
|
|
||||||
|
def parse_redeem_result(result: str, bot_name: str | None = None) -> str:
|
||||||
|
result_lower = result.lower()
|
||||||
|
if bot_name:
|
||||||
|
for line in result.splitlines():
|
||||||
|
if f"<{bot_name.lower()}>" in line.lower():
|
||||||
|
result_lower = line.lower()
|
||||||
|
break
|
||||||
|
if "ok/nodetail" in result_lower or "ok/" in result_lower:
|
||||||
|
return "success"
|
||||||
|
if "ratelimited" in result_lower:
|
||||||
|
return "rate_limited"
|
||||||
|
if "alreadypurchased" in result_lower or "already purchased" in result_lower:
|
||||||
|
return "already_owned"
|
||||||
|
if "duplicateactivationcode" in result_lower:
|
||||||
|
return "duplicate"
|
||||||
|
if "regionlocked" in result_lower:
|
||||||
|
return "region_locked"
|
||||||
|
if "invalidactivationcode" in result_lower or "invalid" in result_lower:
|
||||||
|
return "invalid"
|
||||||
|
return "unknown"
|
||||||
|
|
||||||
|
|
||||||
|
async def redeem_single_bot_keys(bot_name: str, keys: list[str]) -> str:
|
||||||
|
success_count = 0
|
||||||
|
failed_count = 0
|
||||||
|
results = []
|
||||||
|
for key in keys:
|
||||||
|
success, result = await redeem_key(bot_name, key)
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
if not success or not result:
|
||||||
|
failed_count += 1
|
||||||
|
results.append(f"❌ {key}: ASF ERROR")
|
||||||
|
continue
|
||||||
|
status = parse_redeem_result(result)
|
||||||
|
if status == "success":
|
||||||
|
success_count += 1
|
||||||
|
results.append(f"✅ {key}: активировано")
|
||||||
|
elif status == "rate_limited":
|
||||||
|
failed_count += 1
|
||||||
|
results.append(f"⏳ {key}: rate limit")
|
||||||
|
elif status == "already_owned":
|
||||||
|
failed_count += 1
|
||||||
|
results.append(f"⚠️ {key}: игра уже есть")
|
||||||
|
elif status == "duplicate":
|
||||||
|
failed_count += 1
|
||||||
|
results.append(f"❌ {key}: ключ уже использован")
|
||||||
|
elif status == "region_locked":
|
||||||
|
failed_count += 1
|
||||||
|
results.append(f"🌍 {key}: регион лок")
|
||||||
|
elif status == "invalid":
|
||||||
|
failed_count += 1
|
||||||
|
results.append(f"❌ {key}: неверный ключ")
|
||||||
|
else:
|
||||||
|
failed_count += 1
|
||||||
|
results.append(f"❔ {key}: неизвестный ответ")
|
||||||
|
text = (
|
||||||
|
f"Результат активации для <code>{bot_name}</code>\n\n"
|
||||||
|
f"✅ Успешно: {success_count}\n"
|
||||||
|
f"❌ Ошибок: {failed_count}\n\n"
|
||||||
|
+ "\n".join(results[:50])
|
||||||
|
)
|
||||||
|
return text[:4000] + ("\n\n...обрезано" if len(text) > 4000 else "")
|
||||||
|
|
||||||
|
|
||||||
|
async def redeem_all_bots_keys(keys: list[str]) -> str:
|
||||||
|
response = get_all_bots()
|
||||||
|
data = response.json()
|
||||||
|
bots = list(data.get("Result", {}).keys())
|
||||||
|
success_count = 0
|
||||||
|
failed_count = 0
|
||||||
|
results = []
|
||||||
|
for key in keys:
|
||||||
|
activated = False
|
||||||
|
key_report = []
|
||||||
|
for bot_name in bots:
|
||||||
|
success, result = await redeem_key(bot_name, key)
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
if not success or not result:
|
||||||
|
key_report.append(f"❌ {bot_name}: ASF ERROR")
|
||||||
|
continue
|
||||||
|
status = parse_redeem_result(result, bot_name)
|
||||||
|
if status == "success":
|
||||||
|
key_report.append(f"✅ {bot_name}: активировано")
|
||||||
|
success_count += 1
|
||||||
|
activated = True
|
||||||
|
break
|
||||||
|
if status == "rate_limited":
|
||||||
|
key_report.append(f"⏳ {bot_name}: rate limit")
|
||||||
|
continue
|
||||||
|
if status == "already_owned":
|
||||||
|
key_report.append(f"⚠️ {bot_name}: игра уже есть")
|
||||||
|
continue
|
||||||
|
if status == "duplicate":
|
||||||
|
key_report.append(f"❌ {bot_name}: ключ уже использован")
|
||||||
|
break
|
||||||
|
if status == "region_locked":
|
||||||
|
key_report.append(f"🌍 {bot_name}: регион лок")
|
||||||
|
continue
|
||||||
|
if status == "invalid":
|
||||||
|
key_report.append(f"❌ {bot_name}: неверный ключ")
|
||||||
|
break
|
||||||
|
key_report.append(f"❔ {bot_name}: неизвестный ответ")
|
||||||
|
if not activated:
|
||||||
|
failed_count += 1
|
||||||
|
results.append(f"\n🔑 {key}\n" + "\n".join(key_report))
|
||||||
|
text = (
|
||||||
|
"Результат активации\n\n"
|
||||||
|
f"✅ Успешно: {success_count}\n"
|
||||||
|
f"❌ Ошибок: {failed_count}\n\n"
|
||||||
|
+ "\n".join(results[:50])
|
||||||
|
)
|
||||||
|
return text[:4000] + ("\n\n...обрезано" if len(text) > 4000 else "")
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import asyncio
|
||||||
|
|
||||||
|
from bot.api.asf import get_2fa_token, get_confirmations
|
||||||
|
from bot.state import twofa_tasks
|
||||||
|
from bot.ui.keyboards import twofa_keyboard
|
||||||
|
|
||||||
|
|
||||||
|
async def stop_twofa_task(message_id: int) -> None:
|
||||||
|
task = twofa_tasks.pop(message_id, None)
|
||||||
|
if task:
|
||||||
|
task.cancel()
|
||||||
|
try:
|
||||||
|
await task
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
async def auto_update_2fa(message, bot_name: str) -> None:
|
||||||
|
last_code = None
|
||||||
|
message_id = message.message_id
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
code = await get_2fa_token(bot_name)
|
||||||
|
if code != last_code:
|
||||||
|
confirmations = await get_confirmations(bot_name)
|
||||||
|
text = f"🔐 {bot_name}\n\n<code>{code}</code>"
|
||||||
|
try:
|
||||||
|
await message.edit_text(
|
||||||
|
text,
|
||||||
|
reply_markup=twofa_keyboard(bot_name, bool(confirmations)),
|
||||||
|
)
|
||||||
|
last_code = code
|
||||||
|
except Exception:
|
||||||
|
break
|
||||||
|
await asyncio.sleep(15)
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
pass
|
||||||
|
except Exception as error:
|
||||||
|
print(f"2FA updater error: {error}")
|
||||||
|
finally:
|
||||||
|
twofa_tasks.pop(message_id, None)
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
console_users = set()
|
||||||
|
redeem_users = set()
|
||||||
|
twofa_tasks = {}
|
||||||
|
inventory_cache = {}
|
||||||
|
redeem_messages = {}
|
||||||
|
bot_redeem_users = {}
|
||||||
|
bot_redeem_messages = {}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
import html
|
||||||
|
import json
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
|
from bot.api.asf import get_asf_status
|
||||||
|
from bot.constants import GAMES
|
||||||
|
|
||||||
|
|
||||||
|
def get_currency_name(currency_id: int) -> str:
|
||||||
|
if currency_id == 1:
|
||||||
|
return "USD"
|
||||||
|
if currency_id == 3:
|
||||||
|
return "EUR"
|
||||||
|
if currency_id == 5:
|
||||||
|
return "RUB"
|
||||||
|
if currency_id == 18:
|
||||||
|
return "UAH"
|
||||||
|
if currency_id == 6:
|
||||||
|
return "PLN"
|
||||||
|
if currency_id == 0:
|
||||||
|
return ""
|
||||||
|
return "???"
|
||||||
|
|
||||||
|
|
||||||
|
def get_inventory_icon(desc: dict) -> str:
|
||||||
|
tags = desc.get("tags", [])
|
||||||
|
for tag in tags:
|
||||||
|
category = tag.get("category", "")
|
||||||
|
name = tag.get("localized_tag_name", "")
|
||||||
|
if category == "item_class":
|
||||||
|
if "Trading Card" in name:
|
||||||
|
return "🃏"
|
||||||
|
if "Emoticon" in name:
|
||||||
|
return "😀"
|
||||||
|
if "Profile Background" in name:
|
||||||
|
return "🖼"
|
||||||
|
if "Booster Pack" in name:
|
||||||
|
return "📦"
|
||||||
|
if "Steam Gems" in name:
|
||||||
|
return "💎"
|
||||||
|
if "Gift" in name:
|
||||||
|
return "🎁"
|
||||||
|
item_type = desc.get("type", "").lower()
|
||||||
|
if "knife" in item_type:
|
||||||
|
return "🔪"
|
||||||
|
if "pistol" in item_type:
|
||||||
|
return "🔫"
|
||||||
|
if "rifle" in item_type:
|
||||||
|
return "🎯"
|
||||||
|
if "graffiti" in item_type:
|
||||||
|
return "🎨"
|
||||||
|
if "music kit" in item_type:
|
||||||
|
return "🎵"
|
||||||
|
return "📦"
|
||||||
|
|
||||||
|
|
||||||
|
def get_uptime(start_time_str: str) -> str:
|
||||||
|
start_time_str = start_time_str[:26] + "Z"
|
||||||
|
start_time = datetime.fromisoformat(start_time_str.replace("Z", "+00:00"))
|
||||||
|
now = datetime.now(timezone.utc)
|
||||||
|
delta = now - start_time
|
||||||
|
days = delta.days
|
||||||
|
hours, remainder = divmod(delta.seconds, 3600)
|
||||||
|
minutes, seconds = divmod(remainder, 60)
|
||||||
|
return f"{days}д {hours}ч {minutes}м {seconds}с"
|
||||||
|
|
||||||
|
|
||||||
|
def format_asf(data: dict) -> str:
|
||||||
|
result = data.get("Result", {})
|
||||||
|
current_version = result.get("Version")
|
||||||
|
latest_version = result.get("LatestVersion", current_version)
|
||||||
|
lines = [f"Версия ASF: {current_version}"]
|
||||||
|
if current_version != latest_version:
|
||||||
|
lines.append(f"Доступно обновление: {latest_version}")
|
||||||
|
memory_kb = result.get("MemoryUsage", 0)
|
||||||
|
memory_mb = memory_kb / 1024
|
||||||
|
lines.append(f"Используется памяти: {memory_mb:.2f} MB")
|
||||||
|
lines.append(f"Работает: {get_uptime(result.get('ProcessStartTime'))}")
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def get_bot_status_icon(bot: dict) -> str:
|
||||||
|
online = bot.get("IsConnectedAndLoggedOn", False)
|
||||||
|
farming = bot.get("CardsFarmer", {}).get("NowFarming", False)
|
||||||
|
idle_games = bot.get("BotConfig", {}).get("GamesPlayedWhileIdle", [])
|
||||||
|
loaded = bool(bot.get("BotName")) and bool(bot.get("SteamID"))
|
||||||
|
if not loaded:
|
||||||
|
return "🔄"
|
||||||
|
if farming:
|
||||||
|
return "🎴"
|
||||||
|
if idle_games and online:
|
||||||
|
return "⌚"
|
||||||
|
if online:
|
||||||
|
return "🟢"
|
||||||
|
return "🔴"
|
||||||
|
|
||||||
|
|
||||||
|
def format_bot_ui(bot: dict) -> str:
|
||||||
|
online = get_bot_status_icon(bot)
|
||||||
|
now_farming = bot.get("CardsFarmer", {}).get("NowFarming")
|
||||||
|
status_line = "🃏 Идет фарм карточек" if now_farming else "🃏 Карточки не фармится"
|
||||||
|
time = bot.get("CardsFarmer", {}).get("TimeRemaining", "00:00:00")
|
||||||
|
games_to_farm = len(bot.get("CardsFarmer", {}).get("GamesToFarm", []))
|
||||||
|
twofa = "есть" if bot.get("HasMobileAuthenticator") else "нет"
|
||||||
|
balance = bot.get("WalletBalance", 0) / 100
|
||||||
|
currency = get_currency_name(bot.get("WalletCurrency", 0))
|
||||||
|
redeem = bot.get("GamesToRedeemInBackgroundCount", 0)
|
||||||
|
steam_id = bot.get("SteamID")
|
||||||
|
nickname = html.escape(str(bot.get("Nickname") or ""))
|
||||||
|
profile_url = f"https://steamcommunity.com/profiles/{steam_id}"
|
||||||
|
text = (
|
||||||
|
f"{online} {bot.get('BotName') or 'Загрузка...'}"
|
||||||
|
f'(<a href="{profile_url}">{nickname}</a>)\n'
|
||||||
|
f"{status_line}\n"
|
||||||
|
)
|
||||||
|
idle_games = bot.get("BotConfig", {}).get("GamesPlayedWhileIdle", [])
|
||||||
|
if idle_games:
|
||||||
|
names = [GAMES.get(game, str(game)) for game in idle_games]
|
||||||
|
if bot.get("IsConnectedAndLoggedOn"):
|
||||||
|
idle_text = f"⌚ Накрутка часов: включена ({', '.join(names)})"
|
||||||
|
else:
|
||||||
|
idle_text = (
|
||||||
|
f"⌚ Накрутка часов: включена ({', '.join(names)}) "
|
||||||
|
"применится после запуска"
|
||||||
|
)
|
||||||
|
text += f"{idle_text}\n"
|
||||||
|
else:
|
||||||
|
text += "⌚ Накрутка часов: выключена\n"
|
||||||
|
text += (
|
||||||
|
f"🆔 : <code>{steam_id}</code>\n"
|
||||||
|
f"🔐 2FA: {twofa}\n\n"
|
||||||
|
f"💰 Баланс: {balance:.2f} {currency}\n"
|
||||||
|
)
|
||||||
|
if redeem > 0:
|
||||||
|
text += f"\nКлючей в очереди: {redeem}\n"
|
||||||
|
if now_farming:
|
||||||
|
text += f"\nОсталось: {time}\n"
|
||||||
|
if games_to_farm > 0:
|
||||||
|
text += f"В очереди игр: {games_to_farm}\n"
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
def get_asf_status_text() -> str:
|
||||||
|
response = get_asf_status()
|
||||||
|
if response.status_code != 200:
|
||||||
|
return f"Ошибка API: {response.status_code}"
|
||||||
|
data = response.json()
|
||||||
|
if not data.get("Success"):
|
||||||
|
return "ASF вернул ошибку"
|
||||||
|
return f"<b>💻 Панель управления ASF</b>\n\n{format_asf(data)}"
|
||||||
|
|
||||||
|
|
||||||
|
def get_farm_summary(bots: dict) -> str:
|
||||||
|
total_games = 0
|
||||||
|
total_time_seconds = 0
|
||||||
|
for bot in bots.values():
|
||||||
|
farmer = bot.get("CardsFarmer", {})
|
||||||
|
if farmer.get("NowFarming"):
|
||||||
|
games = farmer.get("CurrentGamesFarming", [])
|
||||||
|
total_games += len(games)
|
||||||
|
time_str = farmer.get("TimeRemaining", "00:00:00")
|
||||||
|
hours, minutes, seconds = map(int, time_str.split(":"))
|
||||||
|
total_time_seconds += hours * 3600 + minutes * 60 + seconds
|
||||||
|
if total_games == 0:
|
||||||
|
return "На аккаунтах нечего не фармиться"
|
||||||
|
hours = total_time_seconds // 3600
|
||||||
|
minutes = (total_time_seconds % 3600) // 60
|
||||||
|
return (
|
||||||
|
f"Игр фармится: {total_games}\n"
|
||||||
|
f"Осталось времени: {hours}ч {minutes}м\n"
|
||||||
|
f"Карт осталось: ~{total_games * 3}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def format_bot(bot_data: dict) -> str:
|
||||||
|
return json.dumps(bot_data, indent=2, ensure_ascii=False)
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
|
||||||
|
|
||||||
|
from bot.ui.formatters import get_bot_status_icon
|
||||||
|
|
||||||
|
|
||||||
|
def main_keyboard() -> InlineKeyboardMarkup:
|
||||||
|
return InlineKeyboardMarkup(
|
||||||
|
inline_keyboard=[
|
||||||
|
[InlineKeyboardButton(text="🆙 Обновить", callback_data="refresh")],
|
||||||
|
[InlineKeyboardButton(text="🤖 Боты", callback_data="bots")],
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text="🔑 Активация ключей на всех аккаунтах",
|
||||||
|
callback_data="redeem_keys",
|
||||||
|
)
|
||||||
|
],
|
||||||
|
[InlineKeyboardButton(text="📁 Плагины", callback_data="plugins")],
|
||||||
|
[InlineKeyboardButton(text="💻 Консоль", callback_data="console")],
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text="♻ Перезапустить ASF",
|
||||||
|
callback_data="restart_asf_confirm",
|
||||||
|
)
|
||||||
|
],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def back_keyboard(callback_data: str = "back") -> InlineKeyboardMarkup:
|
||||||
|
return InlineKeyboardMarkup(
|
||||||
|
inline_keyboard=[
|
||||||
|
[InlineKeyboardButton(text="🔙 Назад", callback_data=callback_data)]
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def games_keyboard(bot_name: str, is_enabled: bool) -> InlineKeyboardMarkup:
|
||||||
|
text = "Остановить накрутку CS2" if is_enabled else "⌚ Накрутить часы CS2"
|
||||||
|
return InlineKeyboardMarkup(
|
||||||
|
inline_keyboard=[
|
||||||
|
[InlineKeyboardButton(text=text, callback_data=f"farm|{bot_name}|730")],
|
||||||
|
[InlineKeyboardButton(text="🔙 Назад", callback_data=f"bot_{bot_name}")],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def bots_keyboard(bots: dict) -> InlineKeyboardMarkup:
|
||||||
|
keyboard = []
|
||||||
|
for name, bot in bots.items():
|
||||||
|
status = get_bot_status_icon(bot)
|
||||||
|
loaded = bool(bot.get("BotName")) and bool(bot.get("SteamID"))
|
||||||
|
callback_data = f"bot_{name}" if loaded else "bot_loading"
|
||||||
|
keyboard.append(
|
||||||
|
[InlineKeyboardButton(text=f"{status} {name}", callback_data=callback_data)]
|
||||||
|
)
|
||||||
|
keyboard.append([InlineKeyboardButton(text="🔙 Назад", callback_data="back")])
|
||||||
|
return InlineKeyboardMarkup(inline_keyboard=keyboard)
|
||||||
|
|
||||||
|
|
||||||
|
def bot_details_keyboard(
|
||||||
|
bot_name: str, has_mobile_authenticator: bool
|
||||||
|
) -> InlineKeyboardMarkup:
|
||||||
|
keyboard = []
|
||||||
|
if has_mobile_authenticator:
|
||||||
|
keyboard.append(
|
||||||
|
[InlineKeyboardButton(text="🔐 2FA", callback_data=f"2fa_{bot_name}")]
|
||||||
|
)
|
||||||
|
keyboard.append(
|
||||||
|
[InlineKeyboardButton(text="⌚ Накрутка часов", callback_data=f"games_{bot_name}")]
|
||||||
|
)
|
||||||
|
keyboard.append(
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text="🔑 Активировать ключ",
|
||||||
|
callback_data=f"redeem_bot_{bot_name}",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
keyboard.append(
|
||||||
|
[InlineKeyboardButton(text="📦 Инвентарь", callback_data=f"inventory_{bot_name}")]
|
||||||
|
)
|
||||||
|
keyboard.append([InlineKeyboardButton(text="🔙 Назад", callback_data="bots")])
|
||||||
|
return InlineKeyboardMarkup(inline_keyboard=keyboard)
|
||||||
|
|
||||||
|
|
||||||
|
def twofa_keyboard(bot_name: str, has_confirmations: bool) -> InlineKeyboardMarkup:
|
||||||
|
keyboard = []
|
||||||
|
if has_confirmations:
|
||||||
|
keyboard.append(
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text="Подтверждения",
|
||||||
|
callback_data=f"confirm_list_{bot_name}",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
keyboard.append(
|
||||||
|
[InlineKeyboardButton(text="🔙 Назад", callback_data=f"bot_{bot_name}")]
|
||||||
|
)
|
||||||
|
return InlineKeyboardMarkup(inline_keyboard=keyboard)
|
||||||
|
|
||||||
|
|
||||||
|
def confirmations_keyboard(bot_name: str) -> InlineKeyboardMarkup:
|
||||||
|
return InlineKeyboardMarkup(
|
||||||
|
inline_keyboard=[
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text="Подтвердить всё",
|
||||||
|
callback_data=f"confirm_all_{bot_name}",
|
||||||
|
)
|
||||||
|
],
|
||||||
|
[InlineKeyboardButton(text="🔙 Назад", callback_data=f"2fa_{bot_name}")],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def confirm_action_keyboard(confirm_data: str, cancel_data: str) -> InlineKeyboardMarkup:
|
||||||
|
return InlineKeyboardMarkup(
|
||||||
|
inline_keyboard=[
|
||||||
|
[InlineKeyboardButton(text="Да", callback_data=confirm_data)],
|
||||||
|
[InlineKeyboardButton(text="Нет", callback_data=cancel_data)],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def inventory_menu_keyboard(
|
||||||
|
bot_name: str, cs2_count: int, steam_count: int
|
||||||
|
) -> InlineKeyboardMarkup:
|
||||||
|
keyboard = []
|
||||||
|
if cs2_count:
|
||||||
|
keyboard.append(
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text=f"CS2 ({cs2_count})",
|
||||||
|
callback_data=f"inv_cs2_{bot_name}",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
if steam_count:
|
||||||
|
keyboard.append(
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text=f"Steam ({steam_count})",
|
||||||
|
callback_data=f"inv_steam_{bot_name}",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
keyboard.append(
|
||||||
|
[InlineKeyboardButton(text="🔙 Назад", callback_data=f"bot_{bot_name}")]
|
||||||
|
)
|
||||||
|
return InlineKeyboardMarkup(inline_keyboard=keyboard)
|
||||||
|
|
||||||
|
|
||||||
|
def inventory_page_keyboard(
|
||||||
|
inventory_type: str, bot_name: str, page: int, total_pages: int
|
||||||
|
) -> InlineKeyboardMarkup:
|
||||||
|
nav_buttons = []
|
||||||
|
if page > 0:
|
||||||
|
nav_buttons.append(
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text="◀",
|
||||||
|
callback_data=f"invpage|{inventory_type}|{bot_name}|{page - 1}",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if page < total_pages - 1:
|
||||||
|
nav_buttons.append(
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text="▶",
|
||||||
|
callback_data=f"invpage|{inventory_type}|{bot_name}|{page + 1}",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
keyboard = []
|
||||||
|
if nav_buttons:
|
||||||
|
keyboard.append(nav_buttons)
|
||||||
|
keyboard.append(
|
||||||
|
[InlineKeyboardButton(text="🔙 Назад", callback_data=f"inventory_{bot_name}")]
|
||||||
|
)
|
||||||
|
return InlineKeyboardMarkup(inline_keyboard=keyboard)
|
||||||
|
|
||||||
|
|
||||||
|
def console_keyboard() -> InlineKeyboardMarkup:
|
||||||
|
return InlineKeyboardMarkup(
|
||||||
|
inline_keyboard=[
|
||||||
|
[InlineKeyboardButton(text="🔙 Назад", callback_data="console_exit")]
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def delete_message_keyboard() -> InlineKeyboardMarkup:
|
||||||
|
return InlineKeyboardMarkup(
|
||||||
|
inline_keyboard=[[InlineKeyboardButton(text="Удалить", callback_data="delete_msg")]]
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user