refactor(bot): modularize bot code

This commit is contained in:
2026-07-22 18:44:44 +05:00
parent 9b8d388c3c
commit 04f17ff8e2
25 changed files with 1433 additions and 983 deletions
+1
View File
@@ -0,0 +1 @@
+57
View File
@@ -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
+98
View File
@@ -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)
+125
View File
@@ -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 "")
+41
View File
@@ -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)