forked from FOSS/ASF-Control-Bot
feat(bot): add bot account ownership and balance
This commit is contained in:
@@ -189,3 +189,5 @@ pyvenv.cfg
|
|||||||
.venv
|
.venv
|
||||||
pip-selfcheck.json
|
pip-selfcheck.json
|
||||||
|
|
||||||
|
bot.db
|
||||||
|
v1.json
|
||||||
|
|||||||
+31
-2
@@ -1,7 +1,7 @@
|
|||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
from sqlalchemy import BigInteger, Boolean, DateTime, String
|
from sqlalchemy import BigInteger, Boolean, DateTime, ForeignKey, String, Text
|
||||||
from sqlalchemy.orm import Mapped, mapped_column
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||||
|
|
||||||
from bot.db.base import Base
|
from bot.db.base import Base
|
||||||
|
|
||||||
@@ -19,9 +19,38 @@ class User(Base):
|
|||||||
first_name: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
first_name: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
||||||
last_name: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
last_name: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
||||||
is_admin: Mapped[bool] = mapped_column(Boolean, default=False)
|
is_admin: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||||
|
balance_credits: Mapped[int] = mapped_column(BigInteger, default=0, nullable=False)
|
||||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)
|
||||||
updated_at: Mapped[datetime] = mapped_column(
|
updated_at: Mapped[datetime] = mapped_column(
|
||||||
DateTime(timezone=True),
|
DateTime(timezone=True),
|
||||||
default=utc_now,
|
default=utc_now,
|
||||||
onupdate=utc_now,
|
onupdate=utc_now,
|
||||||
)
|
)
|
||||||
|
bots: Mapped[list["BotAccount"]] = relationship(back_populates="owner", cascade="all, delete-orphan")
|
||||||
|
|
||||||
|
|
||||||
|
class AppSetting(Base):
|
||||||
|
__tablename__ = "app_settings"
|
||||||
|
|
||||||
|
key: Mapped[str] = mapped_column(String(128), primary_key=True)
|
||||||
|
integer_value: Mapped[int] = mapped_column(BigInteger, nullable=False)
|
||||||
|
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now, onupdate=utc_now)
|
||||||
|
updated_by: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
|
||||||
|
|
||||||
|
|
||||||
|
class BotAccount(Base):
|
||||||
|
__tablename__ = "bot_accounts"
|
||||||
|
|
||||||
|
id: Mapped[int] = mapped_column(primary_key=True)
|
||||||
|
owner_id: Mapped[int] = mapped_column(ForeignKey("users.id"), index=True)
|
||||||
|
bot_name: Mapped[str] = mapped_column(String(255), unique=True, index=True)
|
||||||
|
steam_id: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
||||||
|
source_filename: Mapped[str] = mapped_column(String(255))
|
||||||
|
config_sha256: Mapped[str] = mapped_column(String(64), index=True)
|
||||||
|
upload_state: Mapped[str] = mapped_column(String(32), default="attached")
|
||||||
|
last_error: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||||
|
is_attached: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||||
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)
|
||||||
|
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now, onupdate=utc_now)
|
||||||
|
attached_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||||
|
owner: Mapped[User] = relationship(back_populates="bots")
|
||||||
|
|||||||
@@ -0,0 +1,177 @@
|
|||||||
|
from aiogram import Router
|
||||||
|
from aiogram.filters import Command
|
||||||
|
from aiogram.fsm.context import FSMContext
|
||||||
|
from aiogram.types import CallbackQuery, Message
|
||||||
|
|
||||||
|
from bot.config import ADMIN_ID
|
||||||
|
from bot.states import AdminFlow
|
||||||
|
from bot.filters import AdminCallbackFilter, AdminFilter
|
||||||
|
from bot.ui.keyboards import admin_keyboard, back_keyboard, admin_profile_keyboard
|
||||||
|
from bot.services.balance import (
|
||||||
|
BalanceError,
|
||||||
|
change_user_balance,
|
||||||
|
get_bot_slot_price,
|
||||||
|
get_user_profile,
|
||||||
|
set_bot_slot_price,
|
||||||
|
)
|
||||||
|
|
||||||
|
router = Router()
|
||||||
|
|
||||||
|
|
||||||
|
@router.message(Command("admin"), AdminFilter())
|
||||||
|
async def admin_menu(message: Message, state: FSMContext) -> None:
|
||||||
|
await state.clear()
|
||||||
|
await message.answer("🤵 <b>Админ-панель</b>", reply_markup=admin_keyboard())
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(lambda c: c.data == "admin_panel", AdminCallbackFilter())
|
||||||
|
async def admin_panel(callback: CallbackQuery, state: FSMContext) -> None:
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
|
await callback.answer()
|
||||||
|
await callback.message.edit_text(
|
||||||
|
"🤵 <b>Админ-панель</b>", reply_markup=admin_keyboard()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(
|
||||||
|
AdminCallbackFilter(), lambda c: c.data.startswith("admin_lookup")
|
||||||
|
)
|
||||||
|
async def admin_lookup_prompt(callback: CallbackQuery, state: FSMContext) -> None:
|
||||||
|
user_id = None
|
||||||
|
try:
|
||||||
|
user_id = int(callback.data.split(":")[1])
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if user_id:
|
||||||
|
try:
|
||||||
|
if user_id <= 0:
|
||||||
|
raise ValueError
|
||||||
|
_, balance, bot_count = await get_user_profile(user_id)
|
||||||
|
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
await callback.message.edit_text("❌ Некорректный Telegram ID")
|
||||||
|
return
|
||||||
|
|
||||||
|
await callback.message.edit_text(
|
||||||
|
f"👤 <b>Профиль</b>\n\n"
|
||||||
|
f"🆔: <code>{user_id}</code>\n"
|
||||||
|
f"💰 Баланс: <code>{balance}</code> ₽\n"
|
||||||
|
f"🤖 Ботов: <code>{bot_count}</code>",
|
||||||
|
reply_markup=admin_profile_keyboard(user_id=user_id),
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
await callback.answer()
|
||||||
|
await callback.message.edit_text(
|
||||||
|
"🆔 Отправьте ID пользователя",
|
||||||
|
reply_markup=back_keyboard(callback_data="admin_panel"),
|
||||||
|
)
|
||||||
|
|
||||||
|
await state.set_state(AdminFlow.waiting_user_id)
|
||||||
|
await state.update_data(action="lookup")
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(
|
||||||
|
AdminCallbackFilter(), lambda c: c.data.startswith("admin_balance:")
|
||||||
|
)
|
||||||
|
async def admin_balance_prompt(callback: CallbackQuery, state: FSMContext) -> None:
|
||||||
|
user_id = int(callback.data.split(":")[1])
|
||||||
|
|
||||||
|
await callback.answer()
|
||||||
|
await callback.message.edit_text(
|
||||||
|
"💲 Отправьте сумму для изменения баланса пользователя",
|
||||||
|
reply_markup=back_keyboard(callback_data=f"admin_lookup:{user_id}"),
|
||||||
|
)
|
||||||
|
|
||||||
|
await state.set_state(AdminFlow.waiting_amount)
|
||||||
|
await state.update_data(action="balance", user_id=user_id)
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(AdminCallbackFilter(), lambda c: c.data == "admin_price")
|
||||||
|
async def admin_price_prompt(callback: CallbackQuery, state: FSMContext) -> None:
|
||||||
|
await callback.answer()
|
||||||
|
await callback.message.edit_text(
|
||||||
|
f"💲 Текущая цена слота: <code>{await get_bot_slot_price()}</code> ₽\n"
|
||||||
|
"Отправьте новую положительную цену.",
|
||||||
|
reply_markup=back_keyboard(callback_data="admin_panel"),
|
||||||
|
)
|
||||||
|
|
||||||
|
await state.set_state(AdminFlow.waiting_amount)
|
||||||
|
await state.update_data(action="price")
|
||||||
|
|
||||||
|
|
||||||
|
@router.message(AdminFlow.waiting_user_id, AdminFilter())
|
||||||
|
async def admin_lookup(message: Message, state: FSMContext) -> None:
|
||||||
|
try:
|
||||||
|
user_id = int((message.text or "").strip())
|
||||||
|
if user_id <= 0:
|
||||||
|
raise ValueError
|
||||||
|
_, balance, bot_count = await get_user_profile(user_id)
|
||||||
|
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
await message.answer("❌ Некорректный Telegram ID")
|
||||||
|
return
|
||||||
|
|
||||||
|
await message.answer(
|
||||||
|
f"👤 <b>Профиль</b>\n\n"
|
||||||
|
f"🆔: <code>{user_id}</code>\n"
|
||||||
|
f"💰 Баланс: <code>{balance}</code> ₽\n"
|
||||||
|
f"🤖 Ботов: <code>{bot_count}</code>",
|
||||||
|
reply_markup=admin_profile_keyboard(user_id=user_id),
|
||||||
|
)
|
||||||
|
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
|
|
||||||
|
@router.message(AdminFlow.waiting_amount, AdminFilter())
|
||||||
|
async def admin_amount(message: Message, state: FSMContext) -> None:
|
||||||
|
data = await state.get_data()
|
||||||
|
text = (message.text or "").strip()
|
||||||
|
|
||||||
|
try:
|
||||||
|
if data.get("action") == "price":
|
||||||
|
price = int(text)
|
||||||
|
await set_bot_slot_price(price, updated_by=ADMIN_ID)
|
||||||
|
response = f"✅ Цена слота обновлена: <code>{price}</code> ₽"
|
||||||
|
|
||||||
|
else:
|
||||||
|
amount_text = text
|
||||||
|
user_text = data.get("user_id", None)
|
||||||
|
|
||||||
|
print(amount_text, user_text)
|
||||||
|
|
||||||
|
user_id, signed_amount = int(user_text), int(amount_text)
|
||||||
|
|
||||||
|
if user_id <= 0 or signed_amount == 0:
|
||||||
|
raise ValueError
|
||||||
|
|
||||||
|
balance = await change_user_balance(
|
||||||
|
telegram_id=user_id,
|
||||||
|
amount=signed_amount,
|
||||||
|
actor_telegram_id=message.from_user.id,
|
||||||
|
)
|
||||||
|
|
||||||
|
response = f"✅ Баланс пользователя обновлён: <code>{balance}</code> ₽"
|
||||||
|
|
||||||
|
except (ValueError, TypeError, BalanceError):
|
||||||
|
response = "❌ Некорректные данные: нужны положительные целые числа"
|
||||||
|
|
||||||
|
await message.answer(response)
|
||||||
|
|
||||||
|
if data.get("action") == "price":
|
||||||
|
return await admin_menu(message, state)
|
||||||
|
|
||||||
|
_, balance, bot_count = await get_user_profile(user_id)
|
||||||
|
|
||||||
|
await message.answer(
|
||||||
|
f"👤 <b>Профиль</b>\n\n"
|
||||||
|
f"🆔: <code>{user_id}</code>\n"
|
||||||
|
f"💰 Баланс: <code>{balance}</code> ₽\n"
|
||||||
|
f"🤖 Ботов: <code>{bot_count}</code>",
|
||||||
|
reply_markup=admin_profile_keyboard(user_id=user_id),
|
||||||
|
)
|
||||||
|
|
||||||
|
await state.clear()
|
||||||
+68
-167
@@ -1,224 +1,125 @@
|
|||||||
import random
|
|
||||||
import asyncio
|
|
||||||
|
|
||||||
from aiogram import Router
|
from aiogram import Router
|
||||||
from aiogram.types import CallbackQuery
|
from aiogram.types import CallbackQuery
|
||||||
|
|
||||||
from bot.api.asf import get_all_bots
|
from bot.api.asf import get_all_bots
|
||||||
from bot.filters import AdminCallbackFilter
|
from bot.logging_utils import get_logger
|
||||||
from bot.logging_utils import describe_user, get_logger
|
from bot.services.bot_accounts import (
|
||||||
from bot.services.bots import is_bot_reloading, is_idle_enabled, update_idle_game
|
ensure_admin_compatibility,
|
||||||
|
get_owned_bot,
|
||||||
|
list_user_bot_accounts,
|
||||||
|
)
|
||||||
|
from bot.config import ADMIN_ID
|
||||||
from bot.services.twofa import stop_twofa_task
|
from bot.services.twofa import stop_twofa_task
|
||||||
from bot.ui.formatters import format_bot_ui, get_farm_summary
|
from bot.ui.formatters import format_bot_ui, get_farm_summary
|
||||||
from bot.ui.keyboards import bot_details_keyboard, bots_keyboard, games_keyboard
|
from bot.ui.keyboards import bot_details_keyboard, bots_keyboard, games_keyboard
|
||||||
|
from bot.services.bots import is_bot_reloading, is_idle_enabled, update_idle_game
|
||||||
|
|
||||||
router = Router()
|
router = Router()
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(lambda c: c.data == "bot_loading", AdminCallbackFilter())
|
async def owned_callback_bot(callback: CallbackQuery, prefix: str):
|
||||||
|
try:
|
||||||
|
account_id = int((callback.data or "").removeprefix(prefix))
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return await get_owned_bot(callback.from_user.id, account_id)
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(lambda c: c.data == "bot_loading")
|
||||||
async def bot_loading_handler(callback: CallbackQuery) -> None:
|
async def bot_loading_handler(callback: CallbackQuery) -> None:
|
||||||
logger.info(
|
|
||||||
"Пользователь %s нажал на еще не загруженного бота",
|
|
||||||
describe_user(callback.from_user),
|
|
||||||
)
|
|
||||||
await callback.answer(
|
await callback.answer(
|
||||||
"Аккаунт еще загружается. Попробуйте через несколько секунд.",
|
"Аккаунт ещё загружается. Попробуйте через несколько секунд.", show_alert=True
|
||||||
show_alert=True,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(lambda c: c.data == "bots", AdminCallbackFilter())
|
@router.callback_query(lambda c: c.data == "bots")
|
||||||
async def bots_handler(callback: CallbackQuery) -> None:
|
async def bots_handler(callback: CallbackQuery) -> None:
|
||||||
logger.info(
|
|
||||||
"Пользователь %s открыл список ботов",
|
|
||||||
describe_user(callback.from_user),
|
|
||||||
)
|
|
||||||
await stop_twofa_task(callback.message.message_id)
|
await stop_twofa_task(callback.message.message_id)
|
||||||
|
|
||||||
await callback.answer()
|
|
||||||
try:
|
|
||||||
response = get_all_bots()
|
response = get_all_bots()
|
||||||
if response.status_code != 200:
|
await callback.answer()
|
||||||
logger.warning(
|
|
||||||
"Не удалось загрузить список ботов для %s: HTTP %s",
|
if response.status_code != 200 or not response.json().get("Success"):
|
||||||
describe_user(callback.from_user),
|
await callback.message.edit_text("Не удалось загрузить список ботов")
|
||||||
response.status_code,
|
|
||||||
)
|
|
||||||
await callback.message.edit_text("Ошибка API")
|
|
||||||
return
|
return
|
||||||
|
|
||||||
data = response.json()
|
asf_bots = response.json().get("Result", {})
|
||||||
if not data.get("Success"):
|
if callback.from_user.id == ADMIN_ID:
|
||||||
logger.warning(
|
await ensure_admin_compatibility(list(asf_bots))
|
||||||
"ASF вернул неуспешный ответ при загрузке списка ботов для %s",
|
|
||||||
describe_user(callback.from_user),
|
accounts = await list_user_bot_accounts(callback.from_user.id)
|
||||||
|
rows = [
|
||||||
|
(account.id, account.bot_name, asf_bots.get(account.bot_name, {}))
|
||||||
|
for account in accounts
|
||||||
|
]
|
||||||
|
|
||||||
|
await callback.message.edit_text(
|
||||||
|
f"<b>🤖 Ваших ботов:</b> {len(rows)}\n\n{get_farm_summary({name: bot for _, name, bot in rows})}",
|
||||||
|
reply_markup=bots_keyboard(rows),
|
||||||
)
|
)
|
||||||
await callback.message.edit_text("ASF вернул ошибку")
|
|
||||||
return
|
|
||||||
|
|
||||||
bots = data.get("Result", {})
|
|
||||||
logger.info(
|
|
||||||
"Список ботов для %s загружен: %s бот(ов)",
|
|
||||||
describe_user(callback.from_user),
|
|
||||||
len(bots),
|
|
||||||
)
|
|
||||||
text = f"<b>🤖 Ботов всего:</b> {len(bots)}\n\n{get_farm_summary(bots)}"
|
|
||||||
|
|
||||||
await callback.message.edit_text(text, reply_markup=bots_keyboard(bots))
|
|
||||||
|
|
||||||
except Exception as error:
|
|
||||||
logger.exception(
|
|
||||||
"Ошибка при открытии списка ботов для %s",
|
|
||||||
describe_user(callback.from_user),
|
|
||||||
)
|
|
||||||
|
|
||||||
await callback.message.edit_text(f"Ошибка: {error}")
|
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(
|
@router.callback_query(
|
||||||
lambda c: c.data and c.data.startswith("bot_"), AdminCallbackFilter()
|
lambda c: c.data and c.data.startswith("bot_") and c.data != "bot_loading"
|
||||||
)
|
)
|
||||||
async def bot_selected(callback: CallbackQuery) -> None:
|
async def bot_selected(callback: CallbackQuery) -> None:
|
||||||
|
account = await owned_callback_bot(callback, "bot_")
|
||||||
|
if account is None:
|
||||||
|
await callback.answer("Бот не найден или вам не принадлежит", show_alert=True)
|
||||||
|
return
|
||||||
await stop_twofa_task(callback.message.message_id)
|
await stop_twofa_task(callback.message.message_id)
|
||||||
|
if is_bot_reloading(account.bot_name):
|
||||||
if not callback.data:
|
|
||||||
logger.debug(
|
|
||||||
"Колбэк выбора бота пришел без data от %s",
|
|
||||||
describe_user(callback.from_user),
|
|
||||||
)
|
|
||||||
await callback.answer()
|
|
||||||
return
|
|
||||||
|
|
||||||
bot_name = callback.data.replace("bot_", "")
|
|
||||||
logger.info(
|
|
||||||
"Пользователь %s выбрал бота %s",
|
|
||||||
describe_user(callback.from_user),
|
|
||||||
bot_name,
|
|
||||||
)
|
|
||||||
if is_bot_reloading(bot_name):
|
|
||||||
logger.info(
|
|
||||||
"Бот %s перезагружается, карточка временно не отображается для %s",
|
|
||||||
bot_name,
|
|
||||||
describe_user(callback.from_user),
|
|
||||||
)
|
|
||||||
await callback.answer(
|
await callback.answer(
|
||||||
"Аккаунт перезагружается. Попробуйте через несколько секунд.",
|
"Аккаунт перезагружается. Попробуйте позже.", show_alert=True
|
||||||
show_alert=True,
|
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
bot = get_all_bots().json().get("Result", {}).get(account.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"):
|
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(
|
await callback.answer(
|
||||||
"Аккаунт еще загружается. Попробуйте через несколько секунд.",
|
"Аккаунт ещё загружается. Попробуйте позже.", show_alert=True
|
||||||
show_alert=True,
|
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
logger.info(
|
|
||||||
"Открыта карточка бота %s для %s",
|
|
||||||
bot_name,
|
|
||||||
describe_user(callback.from_user),
|
|
||||||
)
|
|
||||||
await callback.answer()
|
await callback.answer()
|
||||||
await callback.message.edit_text(
|
await callback.message.edit_text(
|
||||||
format_bot_ui(bot),
|
format_bot_ui(bot),
|
||||||
disable_web_page_preview=True,
|
disable_web_page_preview=True,
|
||||||
reply_markup=bot_details_keyboard(
|
reply_markup=bot_details_keyboard(
|
||||||
bot_name, bool(bot.get("HasMobileAuthenticator"))
|
account.id, bool(bot.get("HasMobileAuthenticator"))
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as error:
|
|
||||||
logger.exception(
|
|
||||||
"Ошибка при открытии карточки бота %s для %s",
|
|
||||||
bot_name,
|
|
||||||
describe_user(callback.from_user),
|
|
||||||
)
|
|
||||||
await callback.message.edit_text(f"Ошибка: {error}")
|
|
||||||
|
|
||||||
|
@router.callback_query(lambda c: c.data and c.data.startswith("games_"))
|
||||||
@router.callback_query(
|
|
||||||
lambda c: c.data and c.data.startswith("games_"), AdminCallbackFilter()
|
|
||||||
)
|
|
||||||
async def games(callback: CallbackQuery) -> None:
|
async def games(callback: CallbackQuery) -> None:
|
||||||
await callback.answer()
|
account = await owned_callback_bot(callback, "games_")
|
||||||
if not callback.data:
|
if account is None:
|
||||||
logger.debug(
|
await callback.answer("Бот не найден или вам не принадлежит", show_alert=True)
|
||||||
"Колбэк выбора игры пришел без data от %s",
|
|
||||||
describe_user(callback.from_user),
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
|
await callback.answer()
|
||||||
bot_name = callback.data.replace("games_", "")
|
enabled = is_idle_enabled(account.bot_name, 730)
|
||||||
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(
|
await callback.message.edit_text(
|
||||||
"⌚ Выберите игру для накрутки часов",
|
"⌚ Выберите игру для накрутки часов",
|
||||||
reply_markup=games_keyboard(bot_name, idle_enabled),
|
reply_markup=games_keyboard(account.id, enabled),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(
|
@router.callback_query(lambda c: c.data and c.data.startswith("farm|"))
|
||||||
lambda c: c.data and c.data.startswith("farm|"), AdminCallbackFilter()
|
|
||||||
)
|
|
||||||
async def farm_game(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:
|
try:
|
||||||
|
_, account_id, app_id = (callback.data or "").split("|")
|
||||||
|
account = await get_owned_bot(callback.from_user.id, int(account_id))
|
||||||
|
app_id = int(app_id)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
account = None
|
||||||
|
if account is None:
|
||||||
|
await callback.answer("Бот не найден или вам не принадлежит", show_alert=True)
|
||||||
|
return
|
||||||
|
success, text, enabled = await update_idle_game(account.bot_name, app_id)
|
||||||
|
await callback.answer(text, show_alert=True)
|
||||||
|
if success:
|
||||||
await callback.message.edit_text(
|
await callback.message.edit_text(
|
||||||
text=f"⌚ Выберите игру для накрутки часов",
|
"⌚ Выберите игру для накрутки часов",
|
||||||
reply_markup=games_keyboard(bot_name, enabled),
|
reply_markup=games_keyboard(account.id, enabled),
|
||||||
)
|
|
||||||
|
|
||||||
except Exception:
|
|
||||||
logger.debug(
|
|
||||||
"Не удалось обновить клавиатуру игр для бота %s", bot_name, exc_info=True
|
|
||||||
)
|
)
|
||||||
|
|||||||
+2
-15
@@ -2,8 +2,8 @@ from aiogram import Router
|
|||||||
from aiogram.types import CallbackQuery
|
from aiogram.types import CallbackQuery
|
||||||
from aiogram.fsm.context import FSMContext
|
from aiogram.fsm.context import FSMContext
|
||||||
|
|
||||||
from bot.filters import AdminCallbackFilter
|
|
||||||
from bot.states import ConsoleFlow
|
from bot.states import ConsoleFlow
|
||||||
|
from bot.filters import AdminFilter
|
||||||
from bot.ui.formatters import get_asf_status_text
|
from bot.ui.formatters import get_asf_status_text
|
||||||
from bot.logging_utils import describe_user, get_logger
|
from bot.logging_utils import describe_user, get_logger
|
||||||
from bot.ui.keyboards import console_keyboard, main_keyboard
|
from bot.ui.keyboards import console_keyboard, main_keyboard
|
||||||
@@ -12,7 +12,7 @@ router = Router()
|
|||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(lambda c: c.data == "console", AdminCallbackFilter())
|
@router.callback_query(lambda c: c.data == "console", AdminFilter())
|
||||||
async def console_enter(callback: CallbackQuery, state: FSMContext) -> None:
|
async def console_enter(callback: CallbackQuery, state: FSMContext) -> None:
|
||||||
user = describe_user(callback.from_user.id)
|
user = describe_user(callback.from_user.id)
|
||||||
logger.info("Пользователь вошел в режим консоли ASF: %s", user)
|
logger.info("Пользователь вошел в режим консоли ASF: %s", user)
|
||||||
@@ -23,16 +23,3 @@ async def console_enter(callback: CallbackQuery, state: FSMContext) -> None:
|
|||||||
await callback.message.edit_text(
|
await callback.message.edit_text(
|
||||||
"💻 Консоль ASF\n\nОтправь команду (без !)", reply_markup=console_keyboard()
|
"💻 Консоль ASF\n\nОтправь команду (без !)", reply_markup=console_keyboard()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(lambda c: c.data == "console_exit", AdminCallbackFilter())
|
|
||||||
async def console_exit(callback: CallbackQuery, state: FSMContext) -> None:
|
|
||||||
user = describe_user(callback.from_user.id)
|
|
||||||
logger.info("Пользователь вышел из режима консоли ASF: %s", user)
|
|
||||||
|
|
||||||
await state.clear()
|
|
||||||
|
|
||||||
await callback.answer()
|
|
||||||
await callback.message.edit_text(
|
|
||||||
get_asf_status_text(), reply_markup=main_keyboard()
|
|
||||||
)
|
|
||||||
|
|||||||
+36
-77
@@ -1,102 +1,61 @@
|
|||||||
from aiogram import Router
|
from aiogram import Router
|
||||||
from aiogram.types import CallbackQuery
|
from aiogram.types import CallbackQuery
|
||||||
|
|
||||||
from bot.filters import AdminCallbackFilter
|
from bot.services.bot_accounts import get_owned_bot
|
||||||
from bot.logging_utils import describe_user, get_logger
|
|
||||||
from bot.services.inventory import build_inventory_menu, render_inventory_page
|
from bot.services.inventory import build_inventory_menu, render_inventory_page
|
||||||
from bot.ui.keyboards import back_keyboard
|
from bot.ui.keyboards import back_keyboard
|
||||||
|
|
||||||
router = Router()
|
router = Router()
|
||||||
logger = get_logger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(
|
async def account_from_data(callback: CallbackQuery, prefix: str):
|
||||||
lambda c: c.data and c.data.startswith("inventory_"), AdminCallbackFilter()
|
try:
|
||||||
)
|
return await get_owned_bot(callback.from_user.id, int((callback.data or "").removeprefix(prefix)))
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(lambda c: c.data and c.data.startswith("inventory_"))
|
||||||
async def inventory_menu(callback: CallbackQuery) -> None:
|
async def inventory_menu(callback: CallbackQuery) -> None:
|
||||||
user = describe_user(callback.from_user.id)
|
account = await account_from_data(callback, "inventory_")
|
||||||
bot_name = callback.data.replace("inventory_", "")
|
if account is None:
|
||||||
logger.info("Открыто меню инвентаря: %s bot=%s", user, bot_name)
|
await callback.answer("Бот не найден или вам не принадлежит", show_alert=True); return
|
||||||
|
|
||||||
await callback.answer()
|
await callback.answer()
|
||||||
await callback.message.edit_text("Проверка инвентаря...")
|
text, keyboard = await build_inventory_menu(account.bot_name, account.id)
|
||||||
|
|
||||||
text, keyboard = await build_inventory_menu(bot_name)
|
|
||||||
if "пуст" in text.lower():
|
|
||||||
logger.info("Показано пустое меню инвентаря: %s bot=%s", user, bot_name)
|
|
||||||
keyboard = back_keyboard(f"bot_{bot_name}")
|
|
||||||
|
|
||||||
await callback.message.edit_text(text, reply_markup=keyboard)
|
await callback.message.edit_text(text, reply_markup=keyboard)
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(
|
@router.callback_query(lambda c: c.data and c.data.startswith("inv_cs2_"))
|
||||||
lambda c: c.data and c.data.startswith("inv_cs2_"), AdminCallbackFilter()
|
async def inventory_cs2(callback: CallbackQuery) -> None:
|
||||||
)
|
account = await account_from_data(callback, "inv_cs2_")
|
||||||
async def cs2_inventory(callback: CallbackQuery) -> None:
|
if account is None:
|
||||||
user = describe_user(callback.from_user.id)
|
await callback.answer("Бот не найден или вам не принадлежит", show_alert=True); return
|
||||||
bot_name = callback.data.replace("inv_cs2_", "")
|
|
||||||
logger.info("Открыт CS2-инвентарь: %s bot=%s", user, bot_name)
|
|
||||||
|
|
||||||
await callback.answer()
|
await callback.answer()
|
||||||
await callback.message.edit_text("Загрузка инвентаря...")
|
text, keyboard = await render_inventory_page(account.bot_name, "cs2", 730, 2, 0, account.id)
|
||||||
|
|
||||||
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("Не удалось загрузить инвентарь")
|
|
||||||
return
|
|
||||||
|
|
||||||
await callback.message.edit_text(text, reply_markup=keyboard)
|
await callback.message.edit_text(text, reply_markup=keyboard)
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(
|
@router.callback_query(lambda c: c.data and c.data.startswith("inv_steam_"))
|
||||||
lambda c: c.data and c.data.startswith("inv_steam_"), AdminCallbackFilter()
|
async def inventory_steam(callback: CallbackQuery) -> None:
|
||||||
)
|
account = await account_from_data(callback, "inv_steam_")
|
||||||
async def steam_inventory(callback: CallbackQuery) -> None:
|
if account is None:
|
||||||
user = describe_user(callback.from_user.id)
|
await callback.answer("Бот не найден или вам не принадлежит", show_alert=True); return
|
||||||
bot_name = callback.data.replace("inv_steam_", "")
|
|
||||||
logger.info("Открыт Steam-инвентарь: %s bot=%s", user, bot_name)
|
|
||||||
|
|
||||||
await callback.answer()
|
await callback.answer()
|
||||||
await callback.message.edit_text("Загрузка инвентаря...")
|
text, keyboard = await render_inventory_page(account.bot_name, "steam", 753, 6, 0, account.id)
|
||||||
|
|
||||||
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("Не удалось загрузить инвентарь")
|
|
||||||
return
|
|
||||||
|
|
||||||
await callback.message.edit_text(text, reply_markup=keyboard)
|
await callback.message.edit_text(text, reply_markup=keyboard)
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(
|
@router.callback_query(lambda c: c.data and c.data.startswith("invpage|"))
|
||||||
lambda c: c.data and c.data.startswith("invpage|"), AdminCallbackFilter()
|
|
||||||
)
|
|
||||||
async def inventory_page(callback: CallbackQuery) -> None:
|
async def inventory_page(callback: CallbackQuery) -> None:
|
||||||
user = describe_user(callback.from_user.id)
|
try:
|
||||||
_, inv_type, bot_name, page_value = callback.data.split("|")
|
_, inv_type, account_id, page = (callback.data or "").split("|")
|
||||||
logger.info(
|
account = await get_owned_bot(callback.from_user.id, int(account_id))
|
||||||
"Открыта страница инвентаря: %s bot=%s type=%s page=%s",
|
page = int(page)
|
||||||
user,
|
except (ValueError, TypeError):
|
||||||
bot_name,
|
account = None
|
||||||
inv_type,
|
if account is None or inv_type not in {"cs2", "steam"}:
|
||||||
page_value,
|
await callback.answer("Бот не найден или вам не принадлежит", show_alert=True); return
|
||||||
)
|
appid, contextid = (730, 2) if inv_type == "cs2" else (753, 6)
|
||||||
await callback.answer()
|
await callback.answer()
|
||||||
app_id, context_id = (730, 2) if inv_type == "cs2" else (753, 6)
|
text, keyboard = await render_inventory_page(account.bot_name, inv_type, appid, contextid, page, account.id)
|
||||||
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("Не удалось загрузить инвентарь")
|
|
||||||
return
|
|
||||||
await callback.message.edit_text(text, reply_markup=keyboard)
|
await callback.message.edit_text(text, reply_markup=keyboard)
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ from aiogram import Router
|
|||||||
from aiogram.types import Message
|
from aiogram.types import Message
|
||||||
from aiogram.fsm.context import FSMContext
|
from aiogram.fsm.context import FSMContext
|
||||||
|
|
||||||
from bot.filters import AdminFilter
|
|
||||||
from bot.api.asf import get_all_bots, send_command
|
from bot.api.asf import get_all_bots, send_command
|
||||||
from bot.logging_utils import describe_user, get_logger
|
from bot.logging_utils import describe_user, get_logger
|
||||||
from bot.services.redeem import (
|
from bot.services.redeem import (
|
||||||
@@ -12,6 +11,7 @@ from bot.services.redeem import (
|
|||||||
redeem_all_bots_keys,
|
redeem_all_bots_keys,
|
||||||
redeem_single_bot_keys,
|
redeem_single_bot_keys,
|
||||||
)
|
)
|
||||||
|
from bot.services.bot_accounts import list_user_bot_accounts
|
||||||
|
|
||||||
from bot.states import ConsoleFlow, RedeemFlow
|
from bot.states import ConsoleFlow, RedeemFlow
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@ router = Router()
|
|||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@router.message(ConsoleFlow.waiting_command, AdminFilter())
|
@router.message(ConsoleFlow.waiting_command)
|
||||||
async def handle_console_message(message: Message, state: FSMContext):
|
async def handle_console_message(message: Message, state: FSMContext):
|
||||||
if not message.text:
|
if not message.text:
|
||||||
await message.answer("Отправьте текстовую команду.")
|
await message.answer("Отправьте текстовую команду.")
|
||||||
@@ -97,11 +97,12 @@ async def handle_console_message(message: Message, state: FSMContext):
|
|||||||
await message.answer(f"Ошибка: {error}")
|
await message.answer(f"Ошибка: {error}")
|
||||||
|
|
||||||
|
|
||||||
@router.message(RedeemFlow.waiting_bot_keys, AdminFilter())
|
@router.message(RedeemFlow.waiting_bot_keys)
|
||||||
async def handle_single_bot_redeem(message: Message, state: FSMContext):
|
async def handle_single_bot_redeem(message: Message, state: FSMContext):
|
||||||
state_data = await state.get_data()
|
state_data = await state.get_data()
|
||||||
|
|
||||||
bot_name = state_data.get("bot_name")
|
bot_name = state_data.get("bot_name")
|
||||||
|
account_id = state_data.get("account_id")
|
||||||
if not isinstance(bot_name, str) or not bot_name:
|
if not isinstance(bot_name, str) or not bot_name:
|
||||||
await message.answer("Контекст потерян, попробуйте еще раз.")
|
await message.answer("Контекст потерян, попробуйте еще раз.")
|
||||||
await state.clear()
|
await state.clear()
|
||||||
@@ -126,7 +127,7 @@ async def handle_single_bot_redeem(message: Message, state: FSMContext):
|
|||||||
await message.bot.edit_message_text(
|
await message.bot.edit_message_text(
|
||||||
text=format_bot_ui(bot_data),
|
text=format_bot_ui(bot_data),
|
||||||
reply_markup=bot_details_keyboard(
|
reply_markup=bot_details_keyboard(
|
||||||
bot_name, bool(bot_data.get("HasMobileAuthenticator"))
|
account_id, bool(bot_data.get("HasMobileAuthenticator"))
|
||||||
),
|
),
|
||||||
disable_web_page_preview=True,
|
disable_web_page_preview=True,
|
||||||
chat_id=chat_id,
|
chat_id=chat_id,
|
||||||
@@ -195,7 +196,7 @@ async def handle_single_bot_redeem(message: Message, state: FSMContext):
|
|||||||
await state.clear()
|
await state.clear()
|
||||||
|
|
||||||
|
|
||||||
@router.message(RedeemFlow.waiting_all_keys, AdminFilter())
|
@router.message(RedeemFlow.waiting_all_keys)
|
||||||
async def handle_all_bots_redeem(message: Message, state: FSMContext):
|
async def handle_all_bots_redeem(message: Message, state: FSMContext):
|
||||||
state_data = await state.get_data()
|
state_data = await state.get_data()
|
||||||
|
|
||||||
@@ -210,8 +211,8 @@ async def handle_all_bots_redeem(message: Message, state: FSMContext):
|
|||||||
if menu_message_id:
|
if menu_message_id:
|
||||||
try:
|
try:
|
||||||
await message.bot.edit_message_text(
|
await message.bot.edit_message_text(
|
||||||
text=get_asf_status_text(),
|
text=get_asf_status_text(message.from_user.id),
|
||||||
reply_markup=main_keyboard(),
|
reply_markup=main_keyboard(message.from_user.id),
|
||||||
chat_id=chat_id,
|
chat_id=chat_id,
|
||||||
message_id=menu_message_id,
|
message_id=menu_message_id,
|
||||||
)
|
)
|
||||||
@@ -256,7 +257,10 @@ async def handle_all_bots_redeem(message: Message, state: FSMContext):
|
|||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await checking.edit_text(await redeem_all_bots_keys(keys))
|
accounts = await list_user_bot_accounts(message.from_user.id)
|
||||||
|
await checking.edit_text(
|
||||||
|
await redeem_all_bots_keys(keys, [account.bot_name for account in accounts])
|
||||||
|
)
|
||||||
|
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
logger.exception(
|
logger.exception(
|
||||||
|
|||||||
+12
-10
@@ -4,7 +4,7 @@ from aiogram import Router
|
|||||||
from aiogram.types import CallbackQuery
|
from aiogram.types import CallbackQuery
|
||||||
from aiogram.fsm.context import FSMContext
|
from aiogram.fsm.context import FSMContext
|
||||||
|
|
||||||
from bot.filters import AdminCallbackFilter
|
from bot.filters import AdminCallbackFilter, AdminFilter
|
||||||
from bot.logging_utils import describe_user, get_logger
|
from bot.logging_utils import describe_user, get_logger
|
||||||
|
|
||||||
from bot.api.asf import get_plugins, restart_asf as restart_asf_request
|
from bot.api.asf import get_plugins, restart_asf as restart_asf_request
|
||||||
@@ -18,7 +18,7 @@ router = Router()
|
|||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(lambda c: c.data == "back", AdminCallbackFilter())
|
@router.callback_query(lambda c: c.data == "back")
|
||||||
async def back_button(callback: CallbackQuery, state: FSMContext) -> None:
|
async def back_button(callback: CallbackQuery, state: FSMContext) -> None:
|
||||||
user = describe_user(callback.from_user.id)
|
user = describe_user(callback.from_user.id)
|
||||||
logger.info("Нажата кнопка возврата: %s", user)
|
logger.info("Нажата кнопка возврата: %s", user)
|
||||||
@@ -29,7 +29,8 @@ async def back_button(callback: CallbackQuery, state: FSMContext) -> None:
|
|||||||
await callback.answer()
|
await callback.answer()
|
||||||
try:
|
try:
|
||||||
await callback.message.edit_text(
|
await callback.message.edit_text(
|
||||||
get_asf_status_text(), reply_markup=main_keyboard()
|
get_asf_status_text(callback.from_user.id),
|
||||||
|
reply_markup=main_keyboard(callback.from_user.id),
|
||||||
)
|
)
|
||||||
logger.info("Пользователь возвращен в главное меню: %s", user)
|
logger.info("Пользователь возвращен в главное меню: %s", user)
|
||||||
|
|
||||||
@@ -46,7 +47,8 @@ async def refresh_handler(callback: CallbackQuery) -> None:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
await callback.message.edit_text(
|
await callback.message.edit_text(
|
||||||
get_asf_status_text(), reply_markup=main_keyboard()
|
get_asf_status_text(callback.from_user.id),
|
||||||
|
reply_markup=main_keyboard(callback.from_user.id),
|
||||||
)
|
)
|
||||||
logger.info("Главный экран обновлен: %s", user)
|
logger.info("Главный экран обновлен: %s", user)
|
||||||
|
|
||||||
@@ -55,7 +57,7 @@ async def refresh_handler(callback: CallbackQuery) -> None:
|
|||||||
await callback.message.edit_text(f"Ошибка: {error}")
|
await callback.message.edit_text(f"Ошибка: {error}")
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(lambda c: c.data == "delete_msg", AdminCallbackFilter())
|
@router.callback_query(lambda c: c.data == "delete_msg")
|
||||||
async def delete_message(callback: CallbackQuery) -> None:
|
async def delete_message(callback: CallbackQuery) -> None:
|
||||||
user = describe_user(callback.from_user.id)
|
user = describe_user(callback.from_user.id)
|
||||||
logger.info("Запрошено удаление сообщения: %s", user)
|
logger.info("Запрошено удаление сообщения: %s", user)
|
||||||
@@ -77,7 +79,7 @@ async def restart_asf_confirm(callback: CallbackQuery) -> None:
|
|||||||
await callback.answer()
|
await callback.answer()
|
||||||
await callback.message.edit_text(
|
await callback.message.edit_text(
|
||||||
"♻ Точно перезапустить ASF?\n\nВо время перезапуска IPC будет недоступен несколько секунд.",
|
"♻ Точно перезапустить ASF?\n\nВо время перезапуска IPC будет недоступен несколько секунд.",
|
||||||
reply_markup=confirm_action_keyboard("restart_asf", "back"),
|
reply_markup=confirm_action_keyboard("restart_asf", "admin_panel"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -99,7 +101,7 @@ async def restart_asf(callback: CallbackQuery) -> None:
|
|||||||
)
|
)
|
||||||
await callback.message.edit_text(
|
await callback.message.edit_text(
|
||||||
f"Ошибка HTTP {response.status_code}",
|
f"Ошибка HTTP {response.status_code}",
|
||||||
reply_markup=main_keyboard(),
|
reply_markup=main_keyboard(callback.from_user.id),
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -115,8 +117,8 @@ async def restart_asf(callback: CallbackQuery) -> None:
|
|||||||
await asyncio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
await callback.message.edit_text(
|
await callback.message.edit_text(
|
||||||
f"ASF успешно перезапущен\n\n{get_asf_status_text()}",
|
f"ASF успешно перезапущен\n\n{get_asf_status_text(callback.from_user.id)}",
|
||||||
reply_markup=main_keyboard(),
|
reply_markup=main_keyboard(callback.from_user.id),
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info("ASF успешно перезапущен: %s", user)
|
logger.info("ASF успешно перезапущен: %s", user)
|
||||||
@@ -126,7 +128,7 @@ async def restart_asf(callback: CallbackQuery) -> None:
|
|||||||
|
|
||||||
await callback.message.edit_text(
|
await callback.message.edit_text(
|
||||||
f"Ошибка: {error}",
|
f"Ошибка: {error}",
|
||||||
reply_markup=main_keyboard(),
|
reply_markup=main_keyboard(callback.from_user.id),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
from aiogram import Router
|
||||||
|
from aiogram.types import CallbackQuery
|
||||||
|
|
||||||
|
from bot.services.balance import get_user_profile
|
||||||
|
from bot.ui.keyboards import back_keyboard
|
||||||
|
|
||||||
|
router = Router()
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(lambda c: c.data == "profile")
|
||||||
|
async def profile_handler(callback: CallbackQuery) -> None:
|
||||||
|
telegram_id, balance, bot_count = await get_user_profile(callback.from_user.id)
|
||||||
|
|
||||||
|
await callback.answer()
|
||||||
|
await callback.message.edit_text(
|
||||||
|
f"👤 <b>Профиль</b>\n\n"
|
||||||
|
f"🆔: <code>{telegram_id}</code>\n"
|
||||||
|
f"💰 Баланс: <code>{balance}</code> ₽\n"
|
||||||
|
f"🤖 Ботов: <code>{bot_count}</code>",
|
||||||
|
reply_markup=back_keyboard(),
|
||||||
|
)
|
||||||
+13
-8
@@ -2,8 +2,8 @@ from aiogram import Router
|
|||||||
from aiogram.types import CallbackQuery
|
from aiogram.types import CallbackQuery
|
||||||
from aiogram.fsm.context import FSMContext
|
from aiogram.fsm.context import FSMContext
|
||||||
|
|
||||||
from bot.filters import AdminCallbackFilter
|
|
||||||
from bot.logging_utils import describe_user, get_logger
|
from bot.logging_utils import describe_user, get_logger
|
||||||
|
from bot.services.bot_accounts import get_owned_bot
|
||||||
|
|
||||||
|
|
||||||
from bot.states import RedeemFlow
|
from bot.states import RedeemFlow
|
||||||
@@ -13,7 +13,7 @@ router = Router()
|
|||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(lambda c: c.data == "redeem_keys", AdminCallbackFilter())
|
@router.callback_query(lambda c: c.data == "redeem_keys")
|
||||||
async def redeem_keys_menu(callback: CallbackQuery, state: FSMContext) -> None:
|
async def redeem_keys_menu(callback: CallbackQuery, state: FSMContext) -> None:
|
||||||
user = describe_user(callback.from_user.id)
|
user = describe_user(callback.from_user.id)
|
||||||
logger.info("Открыт режим активации ключей по всем ботам: %s", user)
|
logger.info("Открыт режим активации ключей по всем ботам: %s", user)
|
||||||
@@ -30,13 +30,18 @@ async def redeem_keys_menu(callback: CallbackQuery, state: FSMContext) -> None:
|
|||||||
await state.update_data(menu_message_id=callback.message.message_id)
|
await state.update_data(menu_message_id=callback.message.message_id)
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(
|
@router.callback_query(lambda c: c.data and c.data.startswith("redeem_bot_"))
|
||||||
lambda c: c.data and c.data.startswith("redeem_bot_"), AdminCallbackFilter()
|
|
||||||
)
|
|
||||||
async def redeem_bot_menu(callback: CallbackQuery, state: FSMContext) -> None:
|
async def redeem_bot_menu(callback: CallbackQuery, state: FSMContext) -> None:
|
||||||
user = describe_user(callback.from_user.id)
|
user = describe_user(callback.from_user.id)
|
||||||
|
|
||||||
bot_name = callback.data.replace("redeem_bot_", "")
|
try:
|
||||||
|
account = await get_owned_bot(callback.from_user.id, int(callback.data.replace("redeem_bot_", "")))
|
||||||
|
except ValueError:
|
||||||
|
account = None
|
||||||
|
if account is None:
|
||||||
|
await callback.answer("Бот не найден или вам не принадлежит", show_alert=True)
|
||||||
|
return
|
||||||
|
bot_name = account.bot_name
|
||||||
logger.info(
|
logger.info(
|
||||||
"Открыт режим активации ключей для одного бота: %s bot=%s", user, bot_name
|
"Открыт режим активации ключей для одного бота: %s bot=%s", user, bot_name
|
||||||
)
|
)
|
||||||
@@ -45,9 +50,9 @@ async def redeem_bot_menu(callback: CallbackQuery, state: FSMContext) -> None:
|
|||||||
await callback.message.edit_text(
|
await callback.message.edit_text(
|
||||||
f"Отправьте ключ Steam для активации на аккаунте:\n<code>{bot_name}</code>\n\n"
|
f"Отправьте ключ Steam для активации на аккаунте:\n<code>{bot_name}</code>\n\n"
|
||||||
"Можно отправить сразу несколько ключей.",
|
"Можно отправить сразу несколько ключей.",
|
||||||
reply_markup=back_keyboard(f"bot_{bot_name}"),
|
reply_markup=back_keyboard(f"bot_{account.id}"),
|
||||||
)
|
)
|
||||||
await state.set_state(RedeemFlow.waiting_bot_keys)
|
await state.set_state(RedeemFlow.waiting_bot_keys)
|
||||||
await state.update_data(
|
await state.update_data(
|
||||||
bot_name=bot_name, menu_message_id=callback.message.message_id
|
bot_name=bot_name, account_id=account.id, menu_message_id=callback.message.message_id
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ from aiogram import Router
|
|||||||
from aiogram.types import Message
|
from aiogram.types import Message
|
||||||
from aiogram.filters import Command
|
from aiogram.filters import Command
|
||||||
|
|
||||||
from bot.filters import AdminFilter
|
|
||||||
from bot.services.users import upsert_user_from_telegram
|
from bot.services.users import upsert_user_from_telegram
|
||||||
|
|
||||||
from bot.ui.keyboards import main_keyboard
|
from bot.ui.keyboards import main_keyboard
|
||||||
@@ -14,7 +13,7 @@ router = Router()
|
|||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@router.message(Command("start"), AdminFilter())
|
@router.message(Command("start"))
|
||||||
async def start_handler(message: Message) -> None:
|
async def start_handler(message: Message) -> None:
|
||||||
user = describe_user(message.from_user.id if message.from_user else None)
|
user = describe_user(message.from_user.id if message.from_user else None)
|
||||||
await upsert_user_from_telegram(message.from_user)
|
await upsert_user_from_telegram(message.from_user)
|
||||||
@@ -28,7 +27,10 @@ async def start_handler(message: Message) -> None:
|
|||||||
logger.exception("Не удалось удалить сообщение /start: %s", user)
|
logger.exception("Не удалось удалить сообщение /start: %s", user)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await message.answer(get_asf_status_text(), reply_markup=main_keyboard())
|
await message.answer(
|
||||||
|
get_asf_status_text(message.from_user.id),
|
||||||
|
reply_markup=main_keyboard(message.from_user.id),
|
||||||
|
)
|
||||||
logger.info("Главное меню отправлено: %s", user)
|
logger.info("Главное меню отправлено: %s", user)
|
||||||
|
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
|
|||||||
+36
-13
@@ -4,7 +4,7 @@ from aiogram import Router
|
|||||||
from aiogram.types import CallbackQuery
|
from aiogram.types import CallbackQuery
|
||||||
|
|
||||||
from bot.states import twofa_tasks
|
from bot.states import twofa_tasks
|
||||||
from bot.filters import AdminCallbackFilter
|
from bot.services.bot_accounts import get_owned_bot
|
||||||
from bot.logging_utils import describe_user, get_logger
|
from bot.logging_utils import describe_user, get_logger
|
||||||
from bot.services.twofa import auto_update_2fa, stop_twofa_task
|
from bot.services.twofa import auto_update_2fa, stop_twofa_task
|
||||||
from bot.api.asf import accept_confirmations, get_confirmations
|
from bot.api.asf import accept_confirmations, get_confirmations
|
||||||
@@ -18,7 +18,6 @@ logger = get_logger(__name__)
|
|||||||
lambda c: c.data
|
lambda c: c.data
|
||||||
and c.data.startswith("2fa_")
|
and c.data.startswith("2fa_")
|
||||||
and not c.data.startswith("2fa_refresh_"),
|
and not c.data.startswith("2fa_refresh_"),
|
||||||
AdminCallbackFilter(),
|
|
||||||
)
|
)
|
||||||
async def twofa_handler(callback: CallbackQuery) -> None:
|
async def twofa_handler(callback: CallbackQuery) -> None:
|
||||||
await callback.answer()
|
await callback.answer()
|
||||||
@@ -28,7 +27,13 @@ async def twofa_handler(callback: CallbackQuery) -> None:
|
|||||||
describe_user(callback.from_user),
|
describe_user(callback.from_user),
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
bot_name = callback.data.replace("2fa_", "")
|
try:
|
||||||
|
account = await get_owned_bot(callback.from_user.id, int(callback.data.replace("2fa_", "")))
|
||||||
|
except ValueError:
|
||||||
|
account = None
|
||||||
|
if account is None:
|
||||||
|
await callback.answer("Бот не найден или вам не принадлежит", show_alert=True); return
|
||||||
|
bot_name = account.bot_name
|
||||||
message_id = callback.message.message_id
|
message_id = callback.message.message_id
|
||||||
logger.info(
|
logger.info(
|
||||||
"Пользователь %s открыл 2FA для бота %s",
|
"Пользователь %s открыл 2FA для бота %s",
|
||||||
@@ -38,9 +43,9 @@ async def twofa_handler(callback: CallbackQuery) -> None:
|
|||||||
await stop_twofa_task(message_id)
|
await stop_twofa_task(message_id)
|
||||||
await callback.message.edit_text(
|
await callback.message.edit_text(
|
||||||
f"🔐 {bot_name}\n\nЗагрузка 2FA...",
|
f"🔐 {bot_name}\n\nЗагрузка 2FA...",
|
||||||
reply_markup=back_keyboard(f"bot_{bot_name}"),
|
reply_markup=back_keyboard(f"bot_{account.id}"),
|
||||||
)
|
)
|
||||||
task = asyncio.create_task(auto_update_2fa(callback.message, bot_name))
|
task = asyncio.create_task(auto_update_2fa(callback.message, bot_name, account.id))
|
||||||
twofa_tasks[message_id] = task
|
twofa_tasks[message_id] = task
|
||||||
logger.info(
|
logger.info(
|
||||||
"Запущено фоновое обновление 2FA для бота %s в сообщении %s",
|
"Запущено фоновое обновление 2FA для бота %s в сообщении %s",
|
||||||
@@ -52,13 +57,19 @@ async def twofa_handler(callback: CallbackQuery) -> None:
|
|||||||
@router.callback_query(
|
@router.callback_query(
|
||||||
lambda c: c.data
|
lambda c: c.data
|
||||||
and c.data.startswith("confirm_")
|
and c.data.startswith("confirm_")
|
||||||
|
and c.data.removeprefix("confirm_").isdigit()
|
||||||
and not c.data.startswith("confirm_list_")
|
and not c.data.startswith("confirm_list_")
|
||||||
and not c.data.startswith("confirm_all_"),
|
and not c.data.startswith("confirm_all_"),
|
||||||
AdminCallbackFilter(),
|
|
||||||
)
|
)
|
||||||
async def confirm_trades(callback: CallbackQuery) -> None:
|
async def confirm_trades(callback: CallbackQuery) -> None:
|
||||||
await callback.answer()
|
await callback.answer()
|
||||||
bot_name = callback.data.replace("confirm_", "")
|
try:
|
||||||
|
account = await get_owned_bot(callback.from_user.id, int(callback.data.replace("confirm_", "")))
|
||||||
|
except ValueError:
|
||||||
|
account = None
|
||||||
|
if account is None:
|
||||||
|
await callback.answer("Бот не найден или вам не принадлежит", show_alert=True); return
|
||||||
|
bot_name = account.bot_name
|
||||||
logger.info(
|
logger.info(
|
||||||
"Пользователь %s подтвердил сделки для бота %s",
|
"Пользователь %s подтвердил сделки для бота %s",
|
||||||
describe_user(callback.from_user),
|
describe_user(callback.from_user),
|
||||||
@@ -86,7 +97,7 @@ async def confirm_trades(callback: CallbackQuery) -> None:
|
|||||||
|
|
||||||
|
|
||||||
@router.callback_query(
|
@router.callback_query(
|
||||||
lambda c: c.data and c.data.startswith("confirm_list_"), AdminCallbackFilter()
|
lambda c: c.data and c.data.startswith("confirm_list_")
|
||||||
)
|
)
|
||||||
async def confirm_list(callback: CallbackQuery) -> None:
|
async def confirm_list(callback: CallbackQuery) -> None:
|
||||||
await stop_twofa_task(callback.message.message_id)
|
await stop_twofa_task(callback.message.message_id)
|
||||||
@@ -97,7 +108,13 @@ async def confirm_list(callback: CallbackQuery) -> None:
|
|||||||
describe_user(callback.from_user),
|
describe_user(callback.from_user),
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
bot_name = callback.data.replace("confirm_list_", "")
|
try:
|
||||||
|
account = await get_owned_bot(callback.from_user.id, int(callback.data.replace("confirm_list_", "")))
|
||||||
|
except ValueError:
|
||||||
|
account = None
|
||||||
|
if account is None:
|
||||||
|
await callback.answer("Бот не найден или вам не принадлежит", show_alert=True); return
|
||||||
|
bot_name = account.bot_name
|
||||||
logger.info(
|
logger.info(
|
||||||
"Пользователь %s открыл список подтверждений для бота %s",
|
"Пользователь %s открыл список подтверждений для бота %s",
|
||||||
describe_user(callback.from_user),
|
describe_user(callback.from_user),
|
||||||
@@ -118,7 +135,7 @@ async def confirm_list(callback: CallbackQuery) -> None:
|
|||||||
for conf in confirmations:
|
for conf in confirmations:
|
||||||
text += f"- {conf['type_name']}\nID: <code>{conf['id']}</code>\n\n"
|
text += f"- {conf['type_name']}\nID: <code>{conf['id']}</code>\n\n"
|
||||||
await callback.message.edit_text(
|
await callback.message.edit_text(
|
||||||
text, reply_markup=confirmations_keyboard(bot_name)
|
text, reply_markup=confirmations_keyboard(account.id)
|
||||||
)
|
)
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
logger.exception(
|
logger.exception(
|
||||||
@@ -129,7 +146,7 @@ async def confirm_list(callback: CallbackQuery) -> None:
|
|||||||
|
|
||||||
|
|
||||||
@router.callback_query(
|
@router.callback_query(
|
||||||
lambda c: c.data and c.data.startswith("confirm_all_"), AdminCallbackFilter()
|
lambda c: c.data and c.data.startswith("confirm_all_")
|
||||||
)
|
)
|
||||||
async def confirm_all(callback: CallbackQuery) -> None:
|
async def confirm_all(callback: CallbackQuery) -> None:
|
||||||
await stop_twofa_task(callback.message.message_id)
|
await stop_twofa_task(callback.message.message_id)
|
||||||
@@ -142,7 +159,13 @@ async def confirm_all(callback: CallbackQuery) -> None:
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
bot_name = callback.data.replace("confirm_all_", "")
|
try:
|
||||||
|
account = await get_owned_bot(callback.from_user.id, int(callback.data.replace("confirm_all_", "")))
|
||||||
|
except ValueError:
|
||||||
|
account = None
|
||||||
|
if account is None:
|
||||||
|
await callback.answer("Бот не найден или вам не принадлежит", show_alert=True); return
|
||||||
|
bot_name = account.bot_name
|
||||||
logger.info(
|
logger.info(
|
||||||
"Пользователь %s запустил подтверждение всех сделок для бота %s",
|
"Пользователь %s запустил подтверждение всех сделок для бота %s",
|
||||||
describe_user(callback.from_user),
|
describe_user(callback.from_user),
|
||||||
@@ -175,7 +198,7 @@ async def confirm_all(callback: CallbackQuery) -> None:
|
|||||||
text += f"- {conf['type_name']}\nID: <code>{conf['id']}</code>\n\n"
|
text += f"- {conf['type_name']}\nID: <code>{conf['id']}</code>\n\n"
|
||||||
|
|
||||||
await callback.message.edit_text(
|
await callback.message.edit_text(
|
||||||
text, reply_markup=confirmations_keyboard(bot_name)
|
text, reply_markup=confirmations_keyboard(account.id)
|
||||||
)
|
)
|
||||||
await callback.answer("Все подтверждено", show_alert=True)
|
await callback.answer("Все подтверждено", show_alert=True)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,190 @@
|
|||||||
|
import asyncio
|
||||||
|
import hashlib
|
||||||
|
import io
|
||||||
|
import json
|
||||||
|
import posixpath
|
||||||
|
import re
|
||||||
|
import zipfile
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from pathlib import PurePosixPath
|
||||||
|
|
||||||
|
from aiogram import Router
|
||||||
|
from aiogram.fsm.context import FSMContext
|
||||||
|
from aiogram.types import CallbackQuery, Message
|
||||||
|
|
||||||
|
from bot.api.asf import get_bot, save_bot_config
|
||||||
|
from bot.services.bot_accounts import (
|
||||||
|
bot_name_exists,
|
||||||
|
create_bot_account,
|
||||||
|
user_hash_exists,
|
||||||
|
)
|
||||||
|
from bot.services.balance import (
|
||||||
|
InsufficientBalance,
|
||||||
|
credit_user,
|
||||||
|
debit_user,
|
||||||
|
get_bot_slot_price,
|
||||||
|
)
|
||||||
|
from bot.states import UploadFlow
|
||||||
|
from bot.ui.keyboards import back_keyboard
|
||||||
|
|
||||||
|
router = Router()
|
||||||
|
_groups: dict[str, list[Message]] = {}
|
||||||
|
_group_tasks: dict[str, asyncio.Task] = {}
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(lambda c: c.data == "upload")
|
||||||
|
async def upload_menu(callback: CallbackQuery, state: FSMContext) -> None:
|
||||||
|
await callback.answer()
|
||||||
|
await state.set_state(UploadFlow.waiting_upload)
|
||||||
|
await callback.message.edit_text(
|
||||||
|
"Отправьте JSON-конфиг, несколько JSON одним альбомом или ZIP-архив с JSON-файлами.",
|
||||||
|
reply_markup=back_keyboard(callback_data="bots"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _decode_config(raw: bytes) -> dict:
|
||||||
|
value = json.loads(raw.decode("utf-8-sig"))
|
||||||
|
if not isinstance(value, dict):
|
||||||
|
raise ValueError("JSON должен содержать объект конфигурации")
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def _zip_entries(raw: bytes) -> list[tuple[str, bytes]]:
|
||||||
|
entries = []
|
||||||
|
with zipfile.ZipFile(io.BytesIO(raw)) as archive:
|
||||||
|
for item in archive.infolist():
|
||||||
|
name = item.filename.replace("\\", "/")
|
||||||
|
path = PurePosixPath(name)
|
||||||
|
if path.is_absolute() or ".." in path.parts:
|
||||||
|
raise ValueError("ZIP содержит небезопасный путь")
|
||||||
|
if item.is_dir() or not name.lower().endswith(".json"):
|
||||||
|
continue
|
||||||
|
entries.append((name, archive.read(item)))
|
||||||
|
return entries
|
||||||
|
|
||||||
|
|
||||||
|
async def _download(message: Message) -> bytes:
|
||||||
|
buffer = io.BytesIO()
|
||||||
|
await message.bot.download(message.document.file_id, destination=buffer)
|
||||||
|
return buffer.getvalue()
|
||||||
|
|
||||||
|
|
||||||
|
async def _process(user_id: int, files: list[tuple[str, bytes]]) -> str:
|
||||||
|
results = []
|
||||||
|
seen_hashes: set[str] = set()
|
||||||
|
seen_names: set[str] = set()
|
||||||
|
|
||||||
|
for filename, raw in files:
|
||||||
|
try:
|
||||||
|
config = _decode_config(raw)
|
||||||
|
digest = hashlib.sha256(raw).hexdigest()
|
||||||
|
name = config.get("SteamLogin")
|
||||||
|
|
||||||
|
if name in seen_names or await bot_name_exists(name):
|
||||||
|
raise ValueError("бот с таким именем уже существует")
|
||||||
|
|
||||||
|
if digest in seen_hashes or await user_hash_exists(user_id, digest):
|
||||||
|
raise ValueError("такой конфиг уже загружался")
|
||||||
|
|
||||||
|
existing = get_bot(name)
|
||||||
|
if existing.status_code == 200 and existing.json().get("Result"):
|
||||||
|
raise ValueError("бот с таким именем уже существует в ASF")
|
||||||
|
|
||||||
|
slot_price = await get_bot_slot_price()
|
||||||
|
await debit_user(user_id, slot_price)
|
||||||
|
try:
|
||||||
|
response = save_bot_config(name, config)
|
||||||
|
if response.status_code != 200 or not response.json().get("Success"):
|
||||||
|
raise ValueError(
|
||||||
|
f"ASF отклонил конфиг (HTTP {response.status_code})"
|
||||||
|
)
|
||||||
|
|
||||||
|
await create_bot_account(
|
||||||
|
user_id,
|
||||||
|
bot_name=name,
|
||||||
|
steam_id=(
|
||||||
|
str(config.get("SteamID")) if config.get("SteamID") else None
|
||||||
|
),
|
||||||
|
source_filename=filename[:255],
|
||||||
|
config_sha256=digest,
|
||||||
|
upload_state="attached",
|
||||||
|
is_attached=True,
|
||||||
|
attached_at=datetime.now(timezone.utc),
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
await credit_user(user_id, slot_price)
|
||||||
|
raise
|
||||||
|
|
||||||
|
seen_names.add(name)
|
||||||
|
seen_hashes.add(digest)
|
||||||
|
results.append(f"✅ {filename}")
|
||||||
|
|
||||||
|
except (
|
||||||
|
InsufficientBalance,
|
||||||
|
ValueError,
|
||||||
|
json.JSONDecodeError,
|
||||||
|
UnicodeDecodeError,
|
||||||
|
zipfile.BadZipFile,
|
||||||
|
) as error:
|
||||||
|
results.append(f"❌ {filename}: {error}")
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
results.append(f"❌ {filename}: внутренняя ошибка")
|
||||||
|
|
||||||
|
return f"Обработано файлов: {len(files)}"
|
||||||
|
|
||||||
|
|
||||||
|
async def _finish_group(key: str, state: FSMContext) -> None:
|
||||||
|
await asyncio.sleep(0.8)
|
||||||
|
messages = _groups.pop(key, [])
|
||||||
|
_group_tasks.pop(key, None)
|
||||||
|
if not messages:
|
||||||
|
return
|
||||||
|
files = []
|
||||||
|
for message in messages:
|
||||||
|
try:
|
||||||
|
files.append(
|
||||||
|
(message.document.file_name or "config.json", await _download(message))
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
files.append((message.document.file_name or "config.json", b""))
|
||||||
|
|
||||||
|
await messages[0].answer(await _process(messages[0].from_user.id, files))
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
|
|
||||||
|
@router.message(UploadFlow.waiting_upload, lambda message: bool(message.document))
|
||||||
|
async def upload_document(message: Message, state: FSMContext) -> None:
|
||||||
|
document = message.document
|
||||||
|
filename = document.file_name or "config.json"
|
||||||
|
|
||||||
|
if message.media_group_id:
|
||||||
|
key = f"{message.chat.id}:{message.media_group_id}"
|
||||||
|
_groups.setdefault(key, []).append(message)
|
||||||
|
if key not in _group_tasks:
|
||||||
|
_group_tasks[key] = asyncio.create_task(_finish_group(key, state))
|
||||||
|
return
|
||||||
|
|
||||||
|
raw = await _download(message)
|
||||||
|
if filename.lower().endswith(".zip"):
|
||||||
|
try:
|
||||||
|
files = _zip_entries(raw)
|
||||||
|
|
||||||
|
except Exception as error:
|
||||||
|
await message.answer(f"❌ {filename}: {error}")
|
||||||
|
await state.clear()
|
||||||
|
return
|
||||||
|
|
||||||
|
if not files:
|
||||||
|
await message.answer("❌ В архиве нет JSON-файлов")
|
||||||
|
else:
|
||||||
|
await message.answer(await _process(message.from_user.id, files))
|
||||||
|
|
||||||
|
elif filename.lower().endswith(".json"):
|
||||||
|
await message.answer(await _process(message.from_user.id, [(filename, raw)]))
|
||||||
|
else:
|
||||||
|
await message.answer("❌ Поддерживаются только .json и .zip")
|
||||||
|
|
||||||
|
await state.clear()
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
from sqlalchemy import func, select, update
|
||||||
|
|
||||||
|
from bot.config import ADMIN_ID
|
||||||
|
from bot.db.models import AppSetting, BotAccount, User
|
||||||
|
from bot.db.session import async_session
|
||||||
|
|
||||||
|
BOT_SLOT_PRICE_KEY = "bot_slot_price"
|
||||||
|
DEFAULT_BOT_SLOT_PRICE = 1
|
||||||
|
|
||||||
|
|
||||||
|
class BalanceError(ValueError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class InsufficientBalance(BalanceError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _amount(value: int) -> int:
|
||||||
|
if isinstance(value, bool) or not isinstance(value, int) or value <= 0:
|
||||||
|
raise BalanceError("Amount must be a positive integer")
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
async def _get_or_create_user(session, telegram_id: int) -> User:
|
||||||
|
user = await session.scalar(select(User).where(User.telegram_id == telegram_id))
|
||||||
|
if user is None:
|
||||||
|
user = User(telegram_id=telegram_id, balance_credits=0)
|
||||||
|
session.add(user)
|
||||||
|
await session.flush()
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
async def get_user_balance(telegram_id: int) -> int:
|
||||||
|
async with async_session() as session:
|
||||||
|
user = await session.scalar(select(User).where(User.telegram_id == telegram_id))
|
||||||
|
return int(user.balance_credits) if user else 0
|
||||||
|
|
||||||
|
|
||||||
|
async def change_user_balance(
|
||||||
|
telegram_id: int, amount: int, *, actor_telegram_id: int | None = None
|
||||||
|
) -> int:
|
||||||
|
amount = _amount(amount)
|
||||||
|
if (
|
||||||
|
actor_telegram_id is not None
|
||||||
|
and telegram_id != actor_telegram_id
|
||||||
|
and actor_telegram_id != ADMIN_ID
|
||||||
|
):
|
||||||
|
raise PermissionError("Only an administrator can adjust another user")
|
||||||
|
|
||||||
|
async with async_session() as session:
|
||||||
|
user = await _get_or_create_user(session, telegram_id)
|
||||||
|
await session.execute(
|
||||||
|
update(User).where(User.id == user.id).values(balance_credits=amount)
|
||||||
|
)
|
||||||
|
|
||||||
|
await session.commit()
|
||||||
|
return int(
|
||||||
|
(
|
||||||
|
await session.scalar(
|
||||||
|
select(User.balance_credits).where(User.id == user.id)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
or 0
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def credit_user(
|
||||||
|
telegram_id: int, amount: int, *, actor_telegram_id: int | None = None
|
||||||
|
) -> int:
|
||||||
|
amount = _amount(amount)
|
||||||
|
if (
|
||||||
|
actor_telegram_id is not None
|
||||||
|
and telegram_id != actor_telegram_id
|
||||||
|
and actor_telegram_id != ADMIN_ID
|
||||||
|
):
|
||||||
|
raise PermissionError("Only an administrator can adjust another user")
|
||||||
|
|
||||||
|
async with async_session() as session:
|
||||||
|
user = await _get_or_create_user(session, telegram_id)
|
||||||
|
await session.execute(
|
||||||
|
update(User)
|
||||||
|
.where(User.id == user.id)
|
||||||
|
.values(balance_credits=User.balance_credits + amount)
|
||||||
|
)
|
||||||
|
await session.commit()
|
||||||
|
return int(
|
||||||
|
(
|
||||||
|
await session.scalar(
|
||||||
|
select(User.balance_credits).where(User.id == user.id)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
or 0
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def debit_user(
|
||||||
|
telegram_id: int, amount: int, *, actor_telegram_id: int | None = None
|
||||||
|
) -> int:
|
||||||
|
amount = _amount(amount)
|
||||||
|
if (
|
||||||
|
actor_telegram_id is not None
|
||||||
|
and telegram_id != actor_telegram_id
|
||||||
|
and actor_telegram_id != ADMIN_ID
|
||||||
|
):
|
||||||
|
raise PermissionError("Only an administrator can adjust another user")
|
||||||
|
|
||||||
|
async with async_session() as session:
|
||||||
|
user = await _get_or_create_user(session, telegram_id)
|
||||||
|
result = await session.execute(
|
||||||
|
update(User)
|
||||||
|
.where(User.id == user.id, User.balance_credits >= amount)
|
||||||
|
.values(balance_credits=User.balance_credits - amount)
|
||||||
|
)
|
||||||
|
if result.rowcount != 1:
|
||||||
|
await session.rollback()
|
||||||
|
raise InsufficientBalance("Insufficient balance")
|
||||||
|
await session.commit()
|
||||||
|
return int(
|
||||||
|
(
|
||||||
|
await session.scalar(
|
||||||
|
select(User.balance_credits).where(User.id == user.id)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
or 0
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def get_bot_slot_price() -> int:
|
||||||
|
async with async_session() as session:
|
||||||
|
setting = await session.get(AppSetting, BOT_SLOT_PRICE_KEY)
|
||||||
|
if setting is None:
|
||||||
|
setting = AppSetting(
|
||||||
|
key=BOT_SLOT_PRICE_KEY, integer_value=DEFAULT_BOT_SLOT_PRICE
|
||||||
|
)
|
||||||
|
session.add(setting)
|
||||||
|
await session.commit()
|
||||||
|
return DEFAULT_BOT_SLOT_PRICE
|
||||||
|
if setting.integer_value <= 0:
|
||||||
|
return DEFAULT_BOT_SLOT_PRICE
|
||||||
|
return int(setting.integer_value)
|
||||||
|
|
||||||
|
|
||||||
|
async def set_bot_slot_price(price: int, *, updated_by: int) -> int:
|
||||||
|
price = _amount(price)
|
||||||
|
async with async_session() as session:
|
||||||
|
setting = await session.get(AppSetting, BOT_SLOT_PRICE_KEY)
|
||||||
|
if setting is None:
|
||||||
|
setting = AppSetting(
|
||||||
|
key=BOT_SLOT_PRICE_KEY, integer_value=price, updated_by=updated_by
|
||||||
|
)
|
||||||
|
session.add(setting)
|
||||||
|
else:
|
||||||
|
setting.integer_value = price
|
||||||
|
setting.updated_by = updated_by
|
||||||
|
await session.commit()
|
||||||
|
return price
|
||||||
|
|
||||||
|
|
||||||
|
async def get_user_profile(telegram_id: int) -> tuple[int, int, int]:
|
||||||
|
async with async_session() as session:
|
||||||
|
user = await session.scalar(select(User).where(User.telegram_id == telegram_id))
|
||||||
|
if user is None:
|
||||||
|
return telegram_id, 0, 0
|
||||||
|
|
||||||
|
count = await session.scalar(
|
||||||
|
select(func.count(BotAccount.id)).where(BotAccount.owner_id == user.id)
|
||||||
|
)
|
||||||
|
return telegram_id, int(user.balance_credits), int(count or 0)
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
from sqlalchemy import select
|
||||||
|
|
||||||
|
from bot.db.models import BotAccount, User
|
||||||
|
from bot.db.session import async_session
|
||||||
|
from bot.config import ADMIN_ID
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
|
||||||
|
async def list_user_bot_accounts(telegram_id: int) -> list[BotAccount]:
|
||||||
|
async with async_session() as session:
|
||||||
|
result = await session.execute(
|
||||||
|
select(BotAccount)
|
||||||
|
.join(User)
|
||||||
|
.where(User.telegram_id == telegram_id)
|
||||||
|
.order_by(BotAccount.bot_name)
|
||||||
|
)
|
||||||
|
return list(result.scalars())
|
||||||
|
|
||||||
|
|
||||||
|
async def get_owned_bot(telegram_id: int, account_id: int) -> BotAccount | None:
|
||||||
|
async with async_session() as session:
|
||||||
|
result = await session.execute(
|
||||||
|
select(BotAccount)
|
||||||
|
.join(User)
|
||||||
|
.where(BotAccount.id == account_id, User.telegram_id == telegram_id)
|
||||||
|
)
|
||||||
|
return result.scalar_one_or_none()
|
||||||
|
|
||||||
|
|
||||||
|
async def create_bot_account(telegram_id: int, **values) -> BotAccount:
|
||||||
|
async with async_session() as session:
|
||||||
|
user = await session.scalar(select(User).where(User.telegram_id == telegram_id))
|
||||||
|
if user is None:
|
||||||
|
user = User(telegram_id=telegram_id)
|
||||||
|
session.add(user)
|
||||||
|
await session.flush()
|
||||||
|
account = BotAccount(owner_id=user.id, **values)
|
||||||
|
session.add(account)
|
||||||
|
await session.commit()
|
||||||
|
await session.refresh(account)
|
||||||
|
return account
|
||||||
|
|
||||||
|
|
||||||
|
async def bot_name_exists(bot_name: str) -> bool:
|
||||||
|
async with async_session() as session:
|
||||||
|
return (
|
||||||
|
await session.scalar(
|
||||||
|
select(BotAccount.id).where(BotAccount.bot_name == bot_name)
|
||||||
|
)
|
||||||
|
is not None
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def user_hash_exists(telegram_id: int, config_sha256: str) -> bool:
|
||||||
|
async with async_session() as session:
|
||||||
|
return (
|
||||||
|
await session.scalar(
|
||||||
|
select(BotAccount.id)
|
||||||
|
.join(User)
|
||||||
|
.where(
|
||||||
|
User.telegram_id == telegram_id,
|
||||||
|
BotAccount.config_sha256 == config_sha256,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
is not None
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def ensure_admin_compatibility(bot_names: list[str]) -> None:
|
||||||
|
"""Keep ASF bots created before ownership was introduced visible to the admin."""
|
||||||
|
for bot_name in bot_names:
|
||||||
|
if await bot_name_exists(bot_name):
|
||||||
|
continue
|
||||||
|
|
||||||
|
await create_bot_account(
|
||||||
|
ADMIN_ID,
|
||||||
|
bot_name=bot_name,
|
||||||
|
source_filename="existing-asf",
|
||||||
|
config_sha256=hashlib.sha256(bot_name.encode()).hexdigest(),
|
||||||
|
upload_state="attached",
|
||||||
|
is_attached=True,
|
||||||
|
)
|
||||||
@@ -7,7 +7,7 @@ from bot.ui.keyboards import inventory_menu_keyboard, inventory_page_keyboard
|
|||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
async def build_inventory_menu(bot_name: str) -> tuple[str, object]:
|
async def build_inventory_menu(bot_name: str, account_id: int) -> tuple[str, object]:
|
||||||
logger.info("Формирование меню инвентаря: bot=%s", bot_name)
|
logger.info("Формирование меню инвентаря: bot=%s", bot_name)
|
||||||
cs2_inventory = await get_bot_inventory(bot_name, 730, 2)
|
cs2_inventory = await get_bot_inventory(bot_name, 730, 2)
|
||||||
steam_inventory = await get_bot_inventory(bot_name, 753, 6)
|
steam_inventory = await get_bot_inventory(bot_name, 753, 6)
|
||||||
@@ -25,7 +25,7 @@ async def build_inventory_menu(bot_name: str) -> tuple[str, object]:
|
|||||||
logger.info("Инвентарь пуст: bot=%s", bot_name)
|
logger.info("Инвентарь пуст: bot=%s", bot_name)
|
||||||
return (
|
return (
|
||||||
f"📦 Инвентарь {bot_name} пуст",
|
f"📦 Инвентарь {bot_name} пуст",
|
||||||
inventory_menu_keyboard(bot_name, 0, 0),
|
inventory_menu_keyboard(account_id, 0, 0),
|
||||||
)
|
)
|
||||||
logger.info(
|
logger.info(
|
||||||
"Меню инвентаря сформировано: bot=%s cs2_items=%s steam_items=%s",
|
"Меню инвентаря сформировано: bot=%s cs2_items=%s steam_items=%s",
|
||||||
@@ -40,11 +40,11 @@ async def build_inventory_menu(bot_name: str) -> tuple[str, object]:
|
|||||||
"🔒 — нельзя продавать\n"
|
"🔒 — нельзя продавать\n"
|
||||||
"❌ — нельзя трейдить"
|
"❌ — нельзя трейдить"
|
||||||
)
|
)
|
||||||
return text, inventory_menu_keyboard(bot_name, len(cs2_assets), len(steam_assets))
|
return text, inventory_menu_keyboard(account_id, len(cs2_assets), len(steam_assets))
|
||||||
|
|
||||||
|
|
||||||
async def render_inventory_page(
|
async def render_inventory_page(
|
||||||
bot_name: str, inventory_type: str, appid: int, contextid: int, page: int
|
bot_name: str, inventory_type: str, appid: int, contextid: int, page: int, account_id: int
|
||||||
) -> tuple[str, object] | tuple[None, None]:
|
) -> tuple[str, object] | tuple[None, None]:
|
||||||
logger.info(
|
logger.info(
|
||||||
"Формирование страницы инвентаря: bot=%s type=%s appid=%s contextid=%s page=%s",
|
"Формирование страницы инвентаря: bot=%s type=%s appid=%s contextid=%s page=%s",
|
||||||
@@ -70,7 +70,7 @@ async def render_inventory_page(
|
|||||||
logger.info(
|
logger.info(
|
||||||
"Инвентарь выбранного типа пуст: bot=%s type=%s", bot_name, inventory_type
|
"Инвентарь выбранного типа пуст: bot=%s type=%s", bot_name, inventory_type
|
||||||
)
|
)
|
||||||
return f"Инвентарь {bot_name} пуст", inventory_menu_keyboard(bot_name, 0, 0)
|
return f"Инвентарь {bot_name} пуст", inventory_menu_keyboard(account_id, 0, 0)
|
||||||
desc_map = {}
|
desc_map = {}
|
||||||
for desc in descriptions:
|
for desc in descriptions:
|
||||||
key = (str(desc.get("classid")), str(desc.get("instanceid")))
|
key = (str(desc.get("classid")), str(desc.get("instanceid")))
|
||||||
@@ -139,5 +139,5 @@ async def render_inventory_page(
|
|||||||
)
|
)
|
||||||
|
|
||||||
return text[:4000], inventory_page_keyboard(
|
return text[:4000], inventory_page_keyboard(
|
||||||
inventory_type, bot_name, page, total_pages
|
inventory_type, account_id, page, total_pages
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -111,11 +111,12 @@ async def redeem_single_bot_keys(bot_name: str, keys: list[str]) -> str:
|
|||||||
return text[:4000] + ("\n\n...обрезано" if len(text) > 4000 else "")
|
return text[:4000] + ("\n\n...обрезано" if len(text) > 4000 else "")
|
||||||
|
|
||||||
|
|
||||||
async def redeem_all_bots_keys(keys: list[str]) -> str:
|
async def redeem_all_bots_keys(keys: list[str], allowed_bots: list[str] | None = None) -> str:
|
||||||
logger.info("Начата активация ключей по всем ботам: count=%s", len(keys))
|
logger.info("Начата активация ключей по всем ботам: count=%s", len(keys))
|
||||||
response = get_all_bots()
|
response = get_all_bots()
|
||||||
data = response.json()
|
data = response.json()
|
||||||
bots = list(data.get("Result", {}).keys())
|
available = list(data.get("Result", {}).keys())
|
||||||
|
bots = [name for name in available if allowed_bots is None or name in allowed_bots]
|
||||||
logger.info("Для массовой активации найдено ботов: count=%s", len(bots))
|
logger.info("Для массовой активации найдено ботов: count=%s", len(bots))
|
||||||
success_count = 0
|
success_count = 0
|
||||||
failed_count = 0
|
failed_count = 0
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ async def stop_twofa_task(message_id: int) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def auto_update_2fa(message, bot_name: str) -> None:
|
async def auto_update_2fa(message, bot_name: str, account_id: int) -> None:
|
||||||
last_code = None
|
last_code = None
|
||||||
message_id = message.message_id
|
message_id = message.message_id
|
||||||
logger.info(
|
logger.info(
|
||||||
@@ -45,7 +45,7 @@ async def auto_update_2fa(message, bot_name: str) -> None:
|
|||||||
try:
|
try:
|
||||||
await message.edit_text(
|
await message.edit_text(
|
||||||
text,
|
text,
|
||||||
reply_markup=twofa_keyboard(bot_name, bool(confirmations)),
|
reply_markup=twofa_keyboard(account_id, bool(confirmations)),
|
||||||
)
|
)
|
||||||
|
|
||||||
last_code = code
|
last_code = code
|
||||||
|
|||||||
@@ -10,4 +10,13 @@ class RedeemFlow(StatesGroup):
|
|||||||
waiting_bot_keys = State()
|
waiting_bot_keys = State()
|
||||||
|
|
||||||
|
|
||||||
|
class UploadFlow(StatesGroup):
|
||||||
|
waiting_upload = State()
|
||||||
|
|
||||||
|
|
||||||
|
class AdminFlow(StatesGroup):
|
||||||
|
waiting_user_id = State()
|
||||||
|
waiting_amount = State()
|
||||||
|
|
||||||
|
|
||||||
twofa_tasks = {}
|
twofa_tasks = {}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import json
|
|||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
from bot.api.asf import get_asf_status
|
from bot.api.asf import get_asf_status
|
||||||
|
from bot.config import ADMIN_ID
|
||||||
from bot.constants import GAMES
|
from bot.constants import GAMES
|
||||||
|
|
||||||
|
|
||||||
@@ -78,13 +79,15 @@ def get_uptime(start_time_str: str) -> str:
|
|||||||
return f"{days_measure}{hours_measure}{minutes_measure}{seconds_measure}"
|
return f"{days_measure}{hours_measure}{minutes_measure}{seconds_measure}"
|
||||||
|
|
||||||
|
|
||||||
def format_asf(data: dict) -> str:
|
def format_asf(data: dict, user_id: int) -> str:
|
||||||
result = data.get("Result", {})
|
result = data.get("Result", {})
|
||||||
|
|
||||||
current_version = result.get("Version")
|
current_version = result.get("Version")
|
||||||
latest_version = result.get("LatestVersion", current_version)
|
latest_version = result.get("LatestVersion", current_version)
|
||||||
|
|
||||||
lines = [f"Версия ASF: {current_version}"]
|
lines = [f"Версия ASF: {current_version}"]
|
||||||
|
|
||||||
|
if user_id == ADMIN_ID:
|
||||||
if current_version != latest_version:
|
if current_version != latest_version:
|
||||||
lines.append(f"Доступно обновление: {latest_version}")
|
lines.append(f"Доступно обновление: {latest_version}")
|
||||||
|
|
||||||
@@ -172,7 +175,7 @@ def format_bot_ui(bot: dict) -> str:
|
|||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
def get_asf_status_text() -> str:
|
def get_asf_status_text(user_id: int) -> str:
|
||||||
response = get_asf_status()
|
response = get_asf_status()
|
||||||
|
|
||||||
if response.status_code != 200:
|
if response.status_code != 200:
|
||||||
@@ -182,7 +185,7 @@ def get_asf_status_text() -> str:
|
|||||||
if not data.get("Success"):
|
if not data.get("Success"):
|
||||||
return "ASF вернул ошибку"
|
return "ASF вернул ошибку"
|
||||||
|
|
||||||
return f"<b>💻 Панель управления ASF</b>\n\n{format_asf(data)}"
|
return f"<b>💻 Панель управления ASF</b>\n\n{format_asf(data, user_id)}"
|
||||||
|
|
||||||
|
|
||||||
def get_farm_summary(bots: dict) -> str:
|
def get_farm_summary(bots: dict) -> str:
|
||||||
|
|||||||
+99
-56
@@ -1,30 +1,40 @@
|
|||||||
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
|
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
|
||||||
|
|
||||||
from bot.ui.formatters import get_bot_status_icon
|
from bot.ui.formatters import get_bot_status_icon
|
||||||
|
from bot.config import ADMIN_ID
|
||||||
|
|
||||||
|
|
||||||
def main_keyboard() -> InlineKeyboardMarkup:
|
def main_keyboard(user_id: int | None = None) -> InlineKeyboardMarkup:
|
||||||
return InlineKeyboardMarkup(
|
keyboard = [
|
||||||
inline_keyboard=[
|
|
||||||
[InlineKeyboardButton(text="🆙 Обновить", callback_data="refresh")],
|
|
||||||
[InlineKeyboardButton(text="🤖 Боты", callback_data="bots")],
|
[InlineKeyboardButton(text="🤖 Боты", callback_data="bots")],
|
||||||
[
|
[InlineKeyboardButton(text="🔑 Активация ключей", callback_data="redeem_keys")],
|
||||||
InlineKeyboardButton(
|
|
||||||
text="🔑 Активация ключей",
|
|
||||||
callback_data="redeem_keys",
|
|
||||||
)
|
|
||||||
],
|
|
||||||
[InlineKeyboardButton(text="📁 Плагины", callback_data="plugins")],
|
|
||||||
[InlineKeyboardButton(text="💻 Консоль", callback_data="console")],
|
|
||||||
[
|
|
||||||
InlineKeyboardButton(
|
|
||||||
text="♻ Перезапустить ASF",
|
|
||||||
callback_data="restart_asf_confirm",
|
|
||||||
)
|
|
||||||
],
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
profile_index = -1 if user_id == ADMIN_ID else 0
|
||||||
|
|
||||||
|
if user_id == ADMIN_ID:
|
||||||
|
keyboard.insert(
|
||||||
|
0,
|
||||||
|
[InlineKeyboardButton(text="🆙 Обновить", callback_data="refresh")],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
keyboard.extend(
|
||||||
|
[
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text="🤵 Админ-панель", callback_data="admin_panel"
|
||||||
|
)
|
||||||
|
],
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
keyboard.insert(
|
||||||
|
profile_index,
|
||||||
|
[InlineKeyboardButton(text="👤 Профиль", callback_data="profile")],
|
||||||
|
)
|
||||||
|
|
||||||
|
return InlineKeyboardMarkup(inline_keyboard=keyboard)
|
||||||
|
|
||||||
|
|
||||||
def back_keyboard(callback_data: str = "back") -> InlineKeyboardMarkup:
|
def back_keyboard(callback_data: str = "back") -> InlineKeyboardMarkup:
|
||||||
return InlineKeyboardMarkup(
|
return InlineKeyboardMarkup(
|
||||||
@@ -34,57 +44,96 @@ def back_keyboard(callback_data: str = "back") -> InlineKeyboardMarkup:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def games_keyboard(bot_name: str, is_enabled: bool) -> InlineKeyboardMarkup:
|
def admin_keyboard() -> InlineKeyboardMarkup:
|
||||||
|
return InlineKeyboardMarkup(
|
||||||
|
inline_keyboard=[
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text="🔍 Найти пользователя", callback_data="admin_lookup"
|
||||||
|
)
|
||||||
|
],
|
||||||
|
[InlineKeyboardButton(text="💲 Цена слота", callback_data="admin_price")],
|
||||||
|
[InlineKeyboardButton(text="📁 Плагины", callback_data="plugins")],
|
||||||
|
[InlineKeyboardButton(text="💻 Консоль", callback_data="console")],
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text="♻ Перезапустить ASF", callback_data="restart_asf_confirm"
|
||||||
|
)
|
||||||
|
],
|
||||||
|
[InlineKeyboardButton(text="🔙 Назад", callback_data="back")],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def admin_profile_keyboard(user_id: int) -> InlineKeyboardMarkup:
|
||||||
|
return InlineKeyboardMarkup(
|
||||||
|
inline_keyboard=[
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text="💲 Изменить баланс", callback_data=f"admin_balance:{user_id}"
|
||||||
|
)
|
||||||
|
],
|
||||||
|
[InlineKeyboardButton(text="🔙 Назад", callback_data="admin_panel")],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def games_keyboard(account_id: int, is_enabled: bool) -> InlineKeyboardMarkup:
|
||||||
text = "Остановить накрутку CS2" if is_enabled else "⌚ Накрутить часы CS2"
|
text = "Остановить накрутку CS2" if is_enabled else "⌚ Накрутить часы CS2"
|
||||||
|
|
||||||
return InlineKeyboardMarkup(
|
return InlineKeyboardMarkup(
|
||||||
inline_keyboard=[
|
inline_keyboard=[
|
||||||
[InlineKeyboardButton(text=text, callback_data=f"farm|{bot_name}|730")],
|
[InlineKeyboardButton(text=text, callback_data=f"farm|{account_id}|730")],
|
||||||
[InlineKeyboardButton(text="🔙 Назад", callback_data=f"bot_{bot_name}")],
|
[InlineKeyboardButton(text="🔙 Назад", callback_data=f"bot_{account_id}")],
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def bots_keyboard(bots: dict) -> InlineKeyboardMarkup:
|
def bots_keyboard(bots: list[tuple[int, str, dict]]) -> InlineKeyboardMarkup:
|
||||||
keyboard = []
|
keyboard = []
|
||||||
for name, bot in bots.items():
|
for account_id, name, bot in bots:
|
||||||
status = get_bot_status_icon(bot)
|
status = get_bot_status_icon(bot)
|
||||||
loaded = bool(bot.get("BotName")) and bool(bot.get("SteamID"))
|
loaded = bool(bot.get("BotName")) and bool(bot.get("SteamID"))
|
||||||
callback_data = f"bot_{name}" if loaded else "bot_loading"
|
callback_data = f"bot_{account_id}" if loaded else "bot_loading"
|
||||||
keyboard.append(
|
keyboard.append(
|
||||||
[InlineKeyboardButton(text=f"{status} {name}", callback_data=callback_data)]
|
[InlineKeyboardButton(text=f"{status} {name}", callback_data=callback_data)]
|
||||||
)
|
)
|
||||||
keyboard.append([InlineKeyboardButton(text="🔙 Назад", callback_data="back")])
|
|
||||||
|
keyboard.extend(
|
||||||
|
[
|
||||||
|
[InlineKeyboardButton(text="⬆️ Загрузить конфиг", callback_data="upload")],
|
||||||
|
[InlineKeyboardButton(text="🔙 Назад", callback_data="admin_panel")],
|
||||||
|
]
|
||||||
|
)
|
||||||
return InlineKeyboardMarkup(inline_keyboard=keyboard)
|
return InlineKeyboardMarkup(inline_keyboard=keyboard)
|
||||||
|
|
||||||
|
|
||||||
def bot_details_keyboard(
|
def bot_details_keyboard(
|
||||||
bot_name: str, has_mobile_authenticator: bool
|
account_id: int, has_mobile_authenticator: bool
|
||||||
) -> InlineKeyboardMarkup:
|
) -> InlineKeyboardMarkup:
|
||||||
keyboard = []
|
keyboard = []
|
||||||
if has_mobile_authenticator:
|
if has_mobile_authenticator:
|
||||||
keyboard.append(
|
keyboard.append(
|
||||||
[InlineKeyboardButton(text="🔐 2FA", callback_data=f"2fa_{bot_name}")]
|
[InlineKeyboardButton(text="🔐 2FA", callback_data=f"2fa_{account_id}")]
|
||||||
)
|
)
|
||||||
keyboard.append(
|
keyboard.append(
|
||||||
[
|
[
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
text="⌚ Накрутка часов", callback_data=f"games_{bot_name}"
|
text="⌚ Накрутка часов", callback_data=f"games_{account_id}"
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
keyboard.append(
|
keyboard.append(
|
||||||
[
|
[
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
text="🔑 Активировать ключ",
|
text="🔑 Активировать ключ", callback_data=f"redeem_bot_{account_id}"
|
||||||
callback_data=f"redeem_bot_{bot_name}",
|
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
keyboard.append(
|
keyboard.append(
|
||||||
[
|
[
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
text="📦 Инвентарь", callback_data=f"inventory_{bot_name}"
|
text="📦 Инвентарь", callback_data=f"inventory_{account_id}"
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@@ -92,33 +141,31 @@ def bot_details_keyboard(
|
|||||||
return InlineKeyboardMarkup(inline_keyboard=keyboard)
|
return InlineKeyboardMarkup(inline_keyboard=keyboard)
|
||||||
|
|
||||||
|
|
||||||
def twofa_keyboard(bot_name: str, has_confirmations: bool) -> InlineKeyboardMarkup:
|
def twofa_keyboard(account_id: int, has_confirmations: bool) -> InlineKeyboardMarkup:
|
||||||
keyboard = []
|
keyboard = []
|
||||||
if has_confirmations:
|
if has_confirmations:
|
||||||
keyboard.append(
|
keyboard.append(
|
||||||
[
|
[
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
text="Подтверждения",
|
text="Подтверждения", callback_data=f"confirm_list_{account_id}"
|
||||||
callback_data=f"confirm_list_{bot_name}",
|
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
keyboard.append(
|
keyboard.append(
|
||||||
[InlineKeyboardButton(text="🔙 Назад", callback_data=f"bot_{bot_name}")]
|
[InlineKeyboardButton(text="🔙 Назад", callback_data=f"bot_{account_id}")]
|
||||||
)
|
)
|
||||||
return InlineKeyboardMarkup(inline_keyboard=keyboard)
|
return InlineKeyboardMarkup(inline_keyboard=keyboard)
|
||||||
|
|
||||||
|
|
||||||
def confirmations_keyboard(bot_name: str) -> InlineKeyboardMarkup:
|
def confirmations_keyboard(account_id: int) -> InlineKeyboardMarkup:
|
||||||
return InlineKeyboardMarkup(
|
return InlineKeyboardMarkup(
|
||||||
inline_keyboard=[
|
inline_keyboard=[
|
||||||
[
|
[
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
text="Подтвердить всё",
|
text="Подтвердить всё", callback_data=f"confirm_all_{account_id}"
|
||||||
callback_data=f"confirm_all_{bot_name}",
|
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
[InlineKeyboardButton(text="🔙 Назад", callback_data=f"2fa_{bot_name}")],
|
[InlineKeyboardButton(text="🔙 Назад", callback_data=f"2fa_{account_id}")],
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -135,15 +182,14 @@ def confirm_action_keyboard(
|
|||||||
|
|
||||||
|
|
||||||
def inventory_menu_keyboard(
|
def inventory_menu_keyboard(
|
||||||
bot_name: str, cs2_count: int, steam_count: int
|
account_id: int, cs2_count: int, steam_count: int
|
||||||
) -> InlineKeyboardMarkup:
|
) -> InlineKeyboardMarkup:
|
||||||
keyboard = []
|
keyboard = []
|
||||||
if cs2_count:
|
if cs2_count:
|
||||||
keyboard.append(
|
keyboard.append(
|
||||||
[
|
[
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
text=f"CS2 ({cs2_count})",
|
text=f"CS2 ({cs2_count})", callback_data=f"inv_cs2_{account_id}"
|
||||||
callback_data=f"inv_cs2_{bot_name}",
|
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@@ -152,40 +198,37 @@ def inventory_menu_keyboard(
|
|||||||
[
|
[
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
text=f"Steam ({steam_count})",
|
text=f"Steam ({steam_count})",
|
||||||
callback_data=f"inv_steam_{bot_name}",
|
callback_data=f"inv_steam_{account_id}",
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
keyboard.append(
|
keyboard.append(
|
||||||
[InlineKeyboardButton(text="🔙 Назад", callback_data=f"bot_{bot_name}")]
|
[InlineKeyboardButton(text="🔙 Назад", callback_data=f"bot_{account_id}")]
|
||||||
)
|
)
|
||||||
return InlineKeyboardMarkup(inline_keyboard=keyboard)
|
return InlineKeyboardMarkup(inline_keyboard=keyboard)
|
||||||
|
|
||||||
|
|
||||||
def inventory_page_keyboard(
|
def inventory_page_keyboard(
|
||||||
inventory_type: str, bot_name: str, page: int, total_pages: int
|
inventory_type: str, account_id: int, page: int, total_pages: int
|
||||||
) -> InlineKeyboardMarkup:
|
) -> InlineKeyboardMarkup:
|
||||||
nav_buttons = []
|
nav = []
|
||||||
if page > 0:
|
if page > 0:
|
||||||
nav_buttons.append(
|
nav.append(
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
text="◀",
|
text="◀",
|
||||||
callback_data=f"invpage|{inventory_type}|{bot_name}|{page - 1}",
|
callback_data=f"invpage|{inventory_type}|{account_id}|{page - 1}",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if page < total_pages - 1:
|
if page < total_pages - 1:
|
||||||
nav_buttons.append(
|
nav.append(
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
text="▶",
|
text="▶",
|
||||||
callback_data=f"invpage|{inventory_type}|{bot_name}|{page + 1}",
|
callback_data=f"invpage|{inventory_type}|{account_id}|{page + 1}",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
keyboard = [nav] if nav else []
|
||||||
keyboard = []
|
|
||||||
if nav_buttons:
|
|
||||||
keyboard.append(nav_buttons)
|
|
||||||
keyboard.append(
|
keyboard.append(
|
||||||
[InlineKeyboardButton(text="🔙 Назад", callback_data=f"inventory_{bot_name}")]
|
[InlineKeyboardButton(text="🔙 Назад", callback_data=f"inventory_{account_id}")]
|
||||||
)
|
)
|
||||||
return InlineKeyboardMarkup(inline_keyboard=keyboard)
|
return InlineKeyboardMarkup(inline_keyboard=keyboard)
|
||||||
|
|
||||||
@@ -193,7 +236,7 @@ def inventory_page_keyboard(
|
|||||||
def console_keyboard() -> InlineKeyboardMarkup:
|
def console_keyboard() -> InlineKeyboardMarkup:
|
||||||
return InlineKeyboardMarkup(
|
return InlineKeyboardMarkup(
|
||||||
inline_keyboard=[
|
inline_keyboard=[
|
||||||
[InlineKeyboardButton(text="🔙 Назад", callback_data="console_exit")]
|
[InlineKeyboardButton(text="🔙 Назад", callback_data="admin_panel")]
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ from bot.handlers.navigation import router as navigation_router
|
|||||||
from bot.handlers.redeem import router as redeem_router
|
from bot.handlers.redeem import router as redeem_router
|
||||||
from bot.handlers.start import router as start_router
|
from bot.handlers.start import router as start_router
|
||||||
from bot.handlers.twofa import router as twofa_router
|
from bot.handlers.twofa import router as twofa_router
|
||||||
|
from bot.handlers.uploads import router as uploads_router
|
||||||
|
from bot.handlers.profile import router as profile_router
|
||||||
|
from bot.handlers.admin import router as admin_router
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -30,6 +33,9 @@ dp.include_router(twofa_router)
|
|||||||
dp.include_router(inventory_router)
|
dp.include_router(inventory_router)
|
||||||
dp.include_router(redeem_router)
|
dp.include_router(redeem_router)
|
||||||
dp.include_router(messages_router)
|
dp.include_router(messages_router)
|
||||||
|
dp.include_router(uploads_router)
|
||||||
|
dp.include_router(profile_router)
|
||||||
|
dp.include_router(admin_router)
|
||||||
|
|
||||||
|
|
||||||
async def main() -> None:
|
async def main() -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user