Fix text dialog routing and club card registration recovery
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
@@ -1,6 +1,10 @@
|
||||
"""
|
||||
Расширенная админ-панель для управления розыгрышами
|
||||
"""
|
||||
from src.utils.input_validation import lottery_field, numeric_id
|
||||
from src.utils.text_output import answer_plain_pages
|
||||
from html import escape
|
||||
from src.filters.dialog_input import NotCommand
|
||||
from src.utils.errors import public_error
|
||||
from src.core.access import is_admin
|
||||
from src.core.access import is_admin as check_admin_access
|
||||
@@ -180,11 +184,12 @@ def get_winner_management_keyboard() -> InlineKeyboardMarkup:
|
||||
|
||||
|
||||
@admin_router.callback_query(F.data == "admin_panel")
|
||||
async def show_admin_panel(callback: CallbackQuery):
|
||||
async def show_admin_panel(callback: CallbackQuery, state: FSMContext):
|
||||
"""Показать админ-панель"""
|
||||
if not await check_admin_access(callback.from_user.id):
|
||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||
return
|
||||
await state.clear()
|
||||
|
||||
async with async_session_maker() as session:
|
||||
# Быстрая статистика
|
||||
@@ -215,11 +220,12 @@ async def show_admin_panel(callback: CallbackQuery):
|
||||
# ======================
|
||||
|
||||
@admin_router.callback_query(F.data.in_({"admin_lotteries", "lottery_management"}))
|
||||
async def show_lottery_management(callback: CallbackQuery):
|
||||
async def show_lottery_management(callback: CallbackQuery, state: FSMContext):
|
||||
"""Управление розыгрышами"""
|
||||
if not await check_admin_access(callback.from_user.id):
|
||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||
return
|
||||
await state.clear()
|
||||
|
||||
text = "🎲 Управление розыгрышами\n\n"
|
||||
text += "Здесь вы можете создавать, редактировать и управлять розыгрышами.\n\n"
|
||||
@@ -243,6 +249,7 @@ async def start_create_lottery(callback: CallbackQuery, state: FSMContext):
|
||||
|
||||
logging.info(f"✅ Админ {callback.from_user.id} начинает создание розыгрыша")
|
||||
|
||||
await state.clear()
|
||||
text = "📝 Создание нового розыгрыша\n\n"
|
||||
text += "Шаг 1 из 4\n\n"
|
||||
text += "Введите название розыгрыша:"
|
||||
@@ -261,13 +268,19 @@ async def start_create_lottery(callback: CallbackQuery, state: FSMContext):
|
||||
await callback.message.answer(f"❌ Ошибка: {public_error(e)}")
|
||||
|
||||
|
||||
@admin_router.message(StateFilter(AdminStates.lottery_title))
|
||||
@admin_router.message(StateFilter(AdminStates.lottery_title), F.text, NotCommand())
|
||||
async def process_lottery_title(message: Message, state: FSMContext):
|
||||
"""Обработка названия розыгрыша (создание или редактирование)"""
|
||||
if not await check_admin_access(message.from_user.id):
|
||||
await message.answer("❌ Недостаточно прав")
|
||||
return
|
||||
|
||||
try:
|
||||
value = lottery_field("title", message.text)
|
||||
except ValueError as error:
|
||||
await message.answer(str(error))
|
||||
return
|
||||
|
||||
data = await state.get_data()
|
||||
edit_lottery_id = data.get('edit_lottery_id')
|
||||
|
||||
@@ -277,51 +290,49 @@ async def process_lottery_title(message: Message, state: FSMContext):
|
||||
success = await LotteryService.update_lottery(
|
||||
session,
|
||||
edit_lottery_id,
|
||||
title=message.text
|
||||
title=value
|
||||
)
|
||||
|
||||
if success:
|
||||
await message.answer(f"✅ Название изменено на: {message.text}")
|
||||
await message.answer(f"✅ Название изменено на: {value}")
|
||||
await state.clear()
|
||||
# Возвращаемся к выбору полей
|
||||
from aiogram.types import CallbackQuery
|
||||
fake_callback = CallbackQuery(
|
||||
id="fake",
|
||||
from_user=message.from_user,
|
||||
chat_instance="fake",
|
||||
data=f"admin_edit_lottery_select_{edit_lottery_id}",
|
||||
message=message
|
||||
)
|
||||
await choose_edit_field(fake_callback, state)
|
||||
await show_edit_fields(message, edit_lottery_id)
|
||||
else:
|
||||
await message.answer("❌ Ошибка при изменении названия")
|
||||
return
|
||||
|
||||
# Если это создание нового розыгрыша
|
||||
await state.update_data(title=message.text)
|
||||
await state.update_data(title=value)
|
||||
|
||||
text = f"📝 Создание нового розыгрыша\n\n"
|
||||
text += f"Шаг 2 из 4\n\n"
|
||||
text += f"✅ Название: {message.text}\n\n"
|
||||
text += f"✅ Название: {value}\n\n"
|
||||
text += f"Введите описание розыгрыша (или '-' для пропуска):"
|
||||
|
||||
await message.answer(text)
|
||||
await answer_plain_pages(message, text)
|
||||
await state.set_state(AdminStates.lottery_description)
|
||||
|
||||
|
||||
@admin_router.message(StateFilter(AdminStates.lottery_description))
|
||||
@admin_router.message(StateFilter(AdminStates.lottery_description), F.text, NotCommand())
|
||||
async def process_lottery_description(message: Message, state: FSMContext):
|
||||
"""Обработка описания розыгрыша (создание или редактирование)"""
|
||||
if not await check_admin_access(message.from_user.id):
|
||||
await message.answer("❌ Недостаточно прав")
|
||||
return
|
||||
|
||||
try:
|
||||
value = lottery_field("description", message.text)
|
||||
except ValueError as error:
|
||||
await message.answer(str(error))
|
||||
return
|
||||
|
||||
data = await state.get_data()
|
||||
edit_lottery_id = data.get('edit_lottery_id')
|
||||
|
||||
# Если это редактирование существующего розыгрыша
|
||||
if edit_lottery_id:
|
||||
description = None if message.text == "-" else message.text
|
||||
description = value
|
||||
async with async_session_maker() as session:
|
||||
success = await LotteryService.update_lottery(
|
||||
session,
|
||||
@@ -333,21 +344,13 @@ async def process_lottery_description(message: Message, state: FSMContext):
|
||||
await message.answer(f"✅ Описание изменено")
|
||||
await state.clear()
|
||||
# Возвращаемся к выбору полей
|
||||
from aiogram.types import CallbackQuery
|
||||
fake_callback = CallbackQuery(
|
||||
id="fake",
|
||||
from_user=message.from_user,
|
||||
chat_instance="fake",
|
||||
data=f"admin_edit_lottery_select_{edit_lottery_id}",
|
||||
message=message
|
||||
)
|
||||
await choose_edit_field(fake_callback, state)
|
||||
await show_edit_fields(message, edit_lottery_id)
|
||||
else:
|
||||
await message.answer("❌ Ошибка при изменении описания")
|
||||
return
|
||||
|
||||
# Если это создание нового розыгрыша
|
||||
description = None if message.text == "-" else message.text
|
||||
description = value
|
||||
await state.update_data(description=description)
|
||||
|
||||
data = await state.get_data()
|
||||
@@ -363,21 +366,27 @@ async def process_lottery_description(message: Message, state: FSMContext):
|
||||
text += f"🥉 AirPods Pro\n"
|
||||
text += f"🏆 10,000 рублей"
|
||||
|
||||
await message.answer(text)
|
||||
await answer_plain_pages(message, text)
|
||||
await state.set_state(AdminStates.lottery_prizes)
|
||||
|
||||
|
||||
@admin_router.message(StateFilter(AdminStates.lottery_prizes))
|
||||
@admin_router.message(StateFilter(AdminStates.lottery_prizes), F.text, NotCommand())
|
||||
async def process_lottery_prizes(message: Message, state: FSMContext):
|
||||
"""Обработка призов розыгрыша (создание или редактирование)"""
|
||||
if not await check_admin_access(message.from_user.id):
|
||||
await message.answer("❌ Недостаточно прав")
|
||||
return
|
||||
|
||||
try:
|
||||
value = lottery_field("prizes", message.text)
|
||||
except ValueError as error:
|
||||
await message.answer(str(error))
|
||||
return
|
||||
|
||||
data = await state.get_data()
|
||||
edit_lottery_id = data.get('edit_lottery_id')
|
||||
|
||||
prizes = [prize.strip() for prize in message.text.split('\n') if prize.strip()]
|
||||
prizes = value
|
||||
|
||||
# Если это редактирование существующего розыгрыша
|
||||
if edit_lottery_id:
|
||||
@@ -392,15 +401,7 @@ async def process_lottery_prizes(message: Message, state: FSMContext):
|
||||
await message.answer(f"✅ Призы изменены")
|
||||
await state.clear()
|
||||
# Возвращаемся к выбору полей
|
||||
from aiogram.types import CallbackQuery
|
||||
fake_callback = CallbackQuery(
|
||||
id="fake",
|
||||
from_user=message.from_user,
|
||||
chat_instance="fake",
|
||||
data=f"admin_edit_lottery_select_{edit_lottery_id}",
|
||||
message=message
|
||||
)
|
||||
await choose_edit_field(fake_callback, state)
|
||||
await show_edit_fields(message, edit_lottery_id)
|
||||
else:
|
||||
await message.answer("❌ Ошибка при изменении призов")
|
||||
return
|
||||
@@ -420,7 +421,7 @@ async def process_lottery_prizes(message: Message, state: FSMContext):
|
||||
|
||||
text += f"\n✅ Подтвердите создание розыгрыша:"
|
||||
|
||||
await message.answer(
|
||||
await answer_plain_pages(message,
|
||||
text,
|
||||
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text="✅ Создать", callback_data="confirm_create_lottery")],
|
||||
@@ -765,7 +766,7 @@ async def choose_user_to_add(callback: CallbackQuery, state: FSMContext):
|
||||
await state.set_state(AdminStates.add_participant_user)
|
||||
|
||||
|
||||
@admin_router.message(StateFilter(AdminStates.add_participant_user), F.text)
|
||||
@admin_router.message(StateFilter(AdminStates.add_participant_user), F.text, NotCommand())
|
||||
async def process_add_participant(message: Message, state: FSMContext):
|
||||
"""Обработка добавления участника"""
|
||||
if not await check_admin_access(message.from_user.id):
|
||||
@@ -886,7 +887,7 @@ async def remove_participant_select_lottery(callback: CallbackQuery, state: FSMC
|
||||
)
|
||||
|
||||
|
||||
@admin_router.message(StateFilter(AdminStates.remove_participant_user), F.text)
|
||||
@admin_router.message(StateFilter(AdminStates.remove_participant_user), F.text, NotCommand())
|
||||
async def process_remove_participant(message: Message, state: FSMContext):
|
||||
"""Обработка удаления участника"""
|
||||
if not await check_admin_access(message.from_user.id):
|
||||
@@ -1161,7 +1162,7 @@ async def start_search_participants(callback: CallbackQuery, state: FSMContext):
|
||||
await state.set_state(AdminStates.participant_search)
|
||||
|
||||
|
||||
@admin_router.message(StateFilter(AdminStates.participant_search), F.text)
|
||||
@admin_router.message(StateFilter(AdminStates.participant_search), F.text, NotCommand())
|
||||
async def process_search_participants(message: Message, state: FSMContext):
|
||||
"""Обработка поиска участников"""
|
||||
if not await check_admin_access(message.from_user.id):
|
||||
@@ -1169,6 +1170,9 @@ async def process_search_participants(message: Message, state: FSMContext):
|
||||
return
|
||||
|
||||
search_term = message.text.strip()
|
||||
if not 1 <= len(search_term) <= 100:
|
||||
await message.answer("Поисковый запрос должен содержать от 1 до 100 символов. /cancel — отмена.")
|
||||
return
|
||||
|
||||
async with async_session_maker() as session:
|
||||
users = await UserService.search_users(session, search_term)
|
||||
@@ -1279,7 +1283,7 @@ async def choose_users_bulk_add(callback: CallbackQuery, state: FSMContext):
|
||||
await state.set_state(AdminStates.add_participant_bulk)
|
||||
|
||||
|
||||
@admin_router.message(StateFilter(AdminStates.add_participant_bulk))
|
||||
@admin_router.message(StateFilter(AdminStates.add_participant_bulk), F.text, NotCommand())
|
||||
async def process_bulk_add_participant(message: Message, state: FSMContext):
|
||||
"""Обработка массового добавления участников"""
|
||||
if not await check_admin_access(message.from_user.id):
|
||||
@@ -1290,7 +1294,10 @@ async def process_bulk_add_participant(message: Message, state: FSMContext):
|
||||
lottery_id = data['bulk_add_lottery_id']
|
||||
|
||||
# Парсим входные данные
|
||||
user_inputs = [x.strip() for x in message.text.split(',') if x.strip()]
|
||||
user_inputs = [x.strip() for x in message.text.replace('\n', ',').split(',') if x.strip()]
|
||||
if not user_inputs:
|
||||
await message.answer('Введите хотя бы один Telegram ID или @username, либо /cancel.')
|
||||
return
|
||||
telegram_ids = []
|
||||
|
||||
async with async_session_maker() as session:
|
||||
@@ -1302,11 +1309,14 @@ async def process_bulk_add_participant(message: Message, state: FSMContext):
|
||||
if user:
|
||||
telegram_ids.append(user.telegram_id)
|
||||
elif user_input.isdigit():
|
||||
telegram_ids.append(int(user_input))
|
||||
telegram_ids.append(numeric_id(user_input))
|
||||
except:
|
||||
continue
|
||||
|
||||
# Массовое добавление
|
||||
if not telegram_ids or len(telegram_ids) != len(user_inputs):
|
||||
await message.answer("Часть ID или username неверна либо не найдена. Исправьте список и повторите ввод. Изменения не внесены.")
|
||||
return
|
||||
results = await ParticipationService.add_participants_bulk(session, lottery_id, telegram_ids)
|
||||
lottery = await LotteryService.get_lottery(session, lottery_id)
|
||||
|
||||
@@ -1410,7 +1420,7 @@ async def choose_users_bulk_remove(callback: CallbackQuery, state: FSMContext):
|
||||
await state.set_state(AdminStates.remove_participant_bulk)
|
||||
|
||||
|
||||
@admin_router.message(StateFilter(AdminStates.remove_participant_bulk))
|
||||
@admin_router.message(StateFilter(AdminStates.remove_participant_bulk), F.text, NotCommand())
|
||||
async def process_bulk_remove_participant(message: Message, state: FSMContext):
|
||||
"""Обработка массового удаления участников"""
|
||||
if not await check_admin_access(message.from_user.id):
|
||||
@@ -1421,7 +1431,10 @@ async def process_bulk_remove_participant(message: Message, state: FSMContext):
|
||||
lottery_id = data['bulk_remove_lottery_id']
|
||||
|
||||
# Парсим входные данные
|
||||
user_inputs = [x.strip() for x in message.text.split(',') if x.strip()]
|
||||
user_inputs = [x.strip() for x in message.text.replace('\n', ',').split(',') if x.strip()]
|
||||
if not user_inputs:
|
||||
await message.answer('Введите хотя бы один Telegram ID или @username, либо /cancel.')
|
||||
return
|
||||
telegram_ids = []
|
||||
|
||||
async with async_session_maker() as session:
|
||||
@@ -1433,11 +1446,14 @@ async def process_bulk_remove_participant(message: Message, state: FSMContext):
|
||||
if user:
|
||||
telegram_ids.append(user.telegram_id)
|
||||
elif user_input.isdigit():
|
||||
telegram_ids.append(int(user_input))
|
||||
telegram_ids.append(numeric_id(user_input))
|
||||
except:
|
||||
continue
|
||||
|
||||
# Массовое удаление
|
||||
if not telegram_ids or len(telegram_ids) != len(user_inputs):
|
||||
await message.answer("Часть ID или username неверна либо не найдена. Исправьте список и повторите ввод. Изменения не внесены.")
|
||||
return
|
||||
results = await ParticipationService.remove_participants_bulk(session, lottery_id, telegram_ids)
|
||||
lottery = await LotteryService.get_lottery(session, lottery_id)
|
||||
|
||||
@@ -1501,7 +1517,7 @@ async def add_participant_to_lottery(callback: CallbackQuery, state: FSMContext)
|
||||
await state.set_state(AdminStates.add_to_lottery_user)
|
||||
|
||||
|
||||
@admin_router.message(StateFilter(AdminStates.add_to_lottery_user), F.text)
|
||||
@admin_router.message(StateFilter(AdminStates.add_to_lottery_user), F.text, NotCommand())
|
||||
async def process_add_to_lottery(message: Message, state: FSMContext):
|
||||
"""Обработка добавления участника в конкретный розыгрыш"""
|
||||
if not await is_admin(message.from_user.id):
|
||||
@@ -1637,7 +1653,7 @@ async def remove_participant_from_lottery(callback: CallbackQuery, state: FSMCon
|
||||
await state.set_state(AdminStates.remove_from_lottery_user)
|
||||
|
||||
|
||||
@admin_router.message(StateFilter(AdminStates.remove_from_lottery_user), F.text)
|
||||
@admin_router.message(StateFilter(AdminStates.remove_from_lottery_user), F.text, NotCommand())
|
||||
async def process_remove_from_lottery(message: Message, state: FSMContext):
|
||||
"""Обработка удаления участника из конкретного розыгрыша"""
|
||||
if not await is_admin(message.from_user.id):
|
||||
@@ -2002,7 +2018,7 @@ async def choose_accounts_bulk_add(callback: CallbackQuery, state: FSMContext):
|
||||
await state.set_state(AdminStates.add_participant_bulk_accounts)
|
||||
|
||||
|
||||
@admin_router.message(StateFilter(AdminStates.add_participant_bulk_accounts))
|
||||
@admin_router.message(StateFilter(AdminStates.add_participant_bulk_accounts), F.text, NotCommand())
|
||||
async def process_bulk_add_accounts(message: Message, state: FSMContext):
|
||||
"""Обработка массового добавления участников по номерам счетов"""
|
||||
if not await check_admin_access(message.from_user.id):
|
||||
@@ -2015,6 +2031,9 @@ async def process_bulk_add_accounts(message: Message, state: FSMContext):
|
||||
# Используем функцию парсинга из account_utils для корректной обработки формата "КАРТА СЧЕТ"
|
||||
from ..utils.account_utils import parse_accounts_from_message
|
||||
account_inputs = parse_accounts_from_message(message.text)
|
||||
if not account_inputs:
|
||||
await message.answer("Счета не распознаны. Используйте формат 11-22-33-44-55-66-77 или /cancel.")
|
||||
return
|
||||
|
||||
async with async_session_maker() as session:
|
||||
# Массовое добавление по номерам счетов
|
||||
@@ -2127,7 +2146,7 @@ async def choose_accounts_bulk_remove(callback: CallbackQuery, state: FSMContext
|
||||
await state.set_state(AdminStates.remove_participant_bulk_accounts)
|
||||
|
||||
|
||||
@admin_router.message(StateFilter(AdminStates.remove_participant_bulk_accounts))
|
||||
@admin_router.message(StateFilter(AdminStates.remove_participant_bulk_accounts), F.text, NotCommand())
|
||||
async def process_bulk_remove_accounts(message: Message, state: FSMContext):
|
||||
"""Обработка массового удаления участников по номерам счетов"""
|
||||
if not await check_admin_access(message.from_user.id):
|
||||
@@ -2140,6 +2159,9 @@ async def process_bulk_remove_accounts(message: Message, state: FSMContext):
|
||||
# Используем функцию парсинга из account_utils для корректной обработки формата "КАРТА СЧЕТ"
|
||||
from ..utils.account_utils import parse_accounts_from_message
|
||||
account_inputs = parse_accounts_from_message(message.text)
|
||||
if not account_inputs:
|
||||
await message.answer("Счета не распознаны. Используйте формат 11-22-33-44-55-66-77 или /cancel.")
|
||||
return
|
||||
|
||||
async with async_session_maker() as session:
|
||||
# Массовое удаление по номерам счетов
|
||||
@@ -2408,13 +2430,23 @@ async def choose_edit_field(callback: CallbackQuery, state: FSMContext):
|
||||
lottery_id = int(callback.data.split("_")[-1])
|
||||
await state.update_data(edit_lottery_id=lottery_id)
|
||||
|
||||
await state.set_state(None)
|
||||
await callback.answer()
|
||||
await show_edit_fields(callback.message, lottery_id, edit=True)
|
||||
|
||||
|
||||
async def show_edit_fields(message: Message, lottery_id: int, edit=False):
|
||||
async with async_session_maker() as session:
|
||||
lottery = await LotteryService.get_lottery(session, lottery_id)
|
||||
|
||||
if lottery is None:
|
||||
await message.answer("Розыгрыш не найден. Откройте /admin.")
|
||||
return
|
||||
|
||||
text = f"📝 Редактирование: {lottery.title}\n\n"
|
||||
text += "Выберите, что хотите изменить:\n\n"
|
||||
text += f"📝 Название: {lottery.title}\n"
|
||||
text += f"📄 Описание: {lottery.description[:50]}{'...' if len(lottery.description) > 50 else ''}\n"
|
||||
text += f"📄 Описание: {(lottery.description or "")[:50]}{'...' if len(lottery.description or "") > 50 else ''}\n"
|
||||
text += f"🎁 Призы: {len(getattr(lottery, 'prizes', []))} шт.\n"
|
||||
text += f"🎭 Отображение: {getattr(lottery, 'winner_display_type', 'username')}\n"
|
||||
text += f"🟢 Активен: {'Да' if getattr(lottery, 'is_active', True) else 'Нет'}"
|
||||
@@ -2431,7 +2463,7 @@ async def choose_edit_field(callback: CallbackQuery, state: FSMContext):
|
||||
[InlineKeyboardButton(text="◀️ Назад", callback_data="admin_edit_lottery")]
|
||||
]
|
||||
|
||||
await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons))
|
||||
await (message.edit_text if edit else message.answer)(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons))
|
||||
|
||||
|
||||
@admin_router.callback_query(F.data.startswith("admin_toggle_active_"))
|
||||
@@ -2776,7 +2808,7 @@ async def choose_winner_place(callback: CallbackQuery, state: FSMContext):
|
||||
await state.set_state(AdminStates.set_winner_place)
|
||||
|
||||
|
||||
@admin_router.message(StateFilter(AdminStates.set_winner_place))
|
||||
@admin_router.message(StateFilter(AdminStates.set_winner_place), F.text, NotCommand())
|
||||
async def process_winner_place(message: Message, state: FSMContext):
|
||||
"""Обработка места победителя"""
|
||||
if not await check_admin_access(message.from_user.id):
|
||||
@@ -2784,7 +2816,7 @@ async def process_winner_place(message: Message, state: FSMContext):
|
||||
return
|
||||
|
||||
try:
|
||||
place = int(message.text)
|
||||
place = numeric_id(message.text, maximum=2**31 - 1)
|
||||
if place < 1:
|
||||
raise ValueError
|
||||
except ValueError:
|
||||
@@ -2798,6 +2830,9 @@ async def process_winner_place(message: Message, state: FSMContext):
|
||||
async with async_session_maker() as session:
|
||||
lottery = await LotteryService.get_lottery(session, lottery_id)
|
||||
|
||||
if not lottery or place > len(lottery.prizes or []):
|
||||
await message.answer("Номер места должен соответствовать одному из призов розыгрыша.")
|
||||
return
|
||||
if lottery.manual_winners and str(place) in lottery.manual_winners:
|
||||
existing_id = lottery.manual_winners[str(place)]
|
||||
existing_user = await UserService.get_user_by_telegram_id(session, existing_id)
|
||||
@@ -2824,7 +2859,7 @@ async def process_winner_place(message: Message, state: FSMContext):
|
||||
await state.set_state(AdminStates.set_winner_user)
|
||||
|
||||
|
||||
@admin_router.message(StateFilter(AdminStates.set_winner_user), F.text)
|
||||
@admin_router.message(StateFilter(AdminStates.set_winner_user), F.text, NotCommand())
|
||||
async def process_winner_user(message: Message, state: FSMContext):
|
||||
"""Обработка пользователя-победителя (по ID, username или номеру счета)"""
|
||||
if not await check_admin_access(message.from_user.id):
|
||||
@@ -4397,7 +4432,7 @@ async def admin_import_users_start(callback: CallbackQuery, state: FSMContext):
|
||||
await state.set_state(AdminStates.import_users_json)
|
||||
|
||||
|
||||
@admin_router.message(StateFilter(AdminStates.import_users_json), F.document)
|
||||
@admin_router.message(StateFilter(AdminStates.import_users_json), F.document, NotCommand())
|
||||
async def admin_import_users_process(message: Message, state: FSMContext):
|
||||
if not await check_admin_access(message.from_user.id):
|
||||
return
|
||||
@@ -4644,7 +4679,7 @@ BROADCAST_FAILURE_TEXT = "❌ Рассылка прервана. Часть со
|
||||
BROADCAST_BUSY_TEXT = "⏳ Ваша предыдущая рассылка ещё выполняется или заняты оба места отправки. Повторите запуск позже через /admin → Рассылки."
|
||||
|
||||
|
||||
@admin_router.message(StateFilter(AdminStates.broadcast_message), F.text | F.photo | F.video | F.document | F.animation | F.audio | F.voice | F.sticker)
|
||||
@admin_router.message(StateFilter(AdminStates.broadcast_message), F.text | F.photo | F.video | F.document | F.animation | F.audio | F.voice | F.sticker, NotCommand())
|
||||
async def admin_broadcast_send(message: Message, state: FSMContext):
|
||||
"""Обработка и отправка рассылки"""
|
||||
if not await check_admin_access(message.from_user.id):
|
||||
@@ -4876,14 +4911,14 @@ async def admin_broadcast_add_channel_start(callback: CallbackQuery, state: FSMC
|
||||
await state.set_state(AdminStates.broadcast_add_channel_id)
|
||||
|
||||
|
||||
@admin_router.message(StateFilter(AdminStates.broadcast_add_channel_id), F.text)
|
||||
@admin_router.message(StateFilter(AdminStates.broadcast_add_channel_id), F.text, NotCommand())
|
||||
async def admin_broadcast_add_channel_id(message: Message, state: FSMContext):
|
||||
"""Обработка ID канала"""
|
||||
if not await check_admin_access(message.from_user.id):
|
||||
return
|
||||
|
||||
try:
|
||||
chat_id = int(message.text.strip())
|
||||
chat_id = numeric_id(message.text, signed=True)
|
||||
except ValueError:
|
||||
await message.answer(
|
||||
"❌ Неверный формат ID. Отправьте число, например: -1001234567890"
|
||||
@@ -4903,7 +4938,6 @@ async def admin_broadcast_add_channel_id(message: Message, state: FSMContext):
|
||||
await message.answer(
|
||||
"❌ Неверный тип чата. Поддерживаются только каналы и группы."
|
||||
)
|
||||
await state.clear()
|
||||
return
|
||||
|
||||
# Сохраняем данные
|
||||
@@ -4917,7 +4951,7 @@ async def admin_broadcast_add_channel_id(message: Message, state: FSMContext):
|
||||
# Запрашиваем описание
|
||||
text = (
|
||||
f"✅ <b>Канал найден!</b>\n\n"
|
||||
f"📱 <b>Название:</b> {chat.title}\n"
|
||||
f"📱 <b>Название:</b> {escape(chat.title or "")}\n"
|
||||
f"🆔 <b>ID:</b> <code>{chat_id}</code>\n"
|
||||
f"📝 <b>Тип:</b> {'Канал' if chat_type == 'channel' else 'Группа'}\n"
|
||||
)
|
||||
@@ -4939,10 +4973,9 @@ async def admin_broadcast_add_channel_id(message: Message, state: FSMContext):
|
||||
f"Детали: {public_error(e)}",
|
||||
parse_mode="HTML"
|
||||
)
|
||||
await state.clear()
|
||||
|
||||
|
||||
@admin_router.message(StateFilter(AdminStates.broadcast_add_channel_title), F.text)
|
||||
@admin_router.message(StateFilter(AdminStates.broadcast_add_channel_title), F.text, NotCommand("skip"))
|
||||
async def admin_broadcast_add_channel_description(message: Message, state: FSMContext):
|
||||
"""Обработка описания канала"""
|
||||
if not await check_admin_access(message.from_user.id):
|
||||
@@ -4950,7 +4983,7 @@ async def admin_broadcast_add_channel_description(message: Message, state: FSMCo
|
||||
|
||||
data = await state.get_data()
|
||||
|
||||
description = None if message.text.strip() == '/skip' else message.text.strip()
|
||||
description = None if message.text.strip().split('@', 1)[0].lower() == '/skip' else message.text.strip()
|
||||
|
||||
# Сохраняем в БД
|
||||
async with async_session_maker() as session:
|
||||
@@ -4977,10 +5010,11 @@ async def admin_broadcast_add_channel_description(message: Message, state: FSMCo
|
||||
existing.description = description
|
||||
existing.chat_type = data['chat_type']
|
||||
await session.commit()
|
||||
await state.clear()
|
||||
|
||||
await message.answer(
|
||||
"✅ <b>Канал обновлен!</b>\n\n"
|
||||
f"📱 {data['title']}",
|
||||
f"📱 {escape(data['title'])}",
|
||||
parse_mode="HTML"
|
||||
)
|
||||
else:
|
||||
@@ -4995,10 +5029,11 @@ async def admin_broadcast_add_channel_description(message: Message, state: FSMCo
|
||||
)
|
||||
session.add(channel)
|
||||
await session.commit()
|
||||
await state.clear()
|
||||
|
||||
await message.answer(
|
||||
"✅ <b>Канал добавлен!</b>\n\n"
|
||||
f"📱 {data['title']}\n"
|
||||
f"📱 {escape(data['title'])}\n"
|
||||
f"Теперь вы можете использовать его для рассылок.",
|
||||
parse_mode="HTML"
|
||||
)
|
||||
@@ -5209,13 +5244,16 @@ async def admin_users_search_prompt(callback: CallbackQuery, state: FSMContext):
|
||||
await state.set_state(AdminStates.user_management_search)
|
||||
|
||||
|
||||
@admin_router.message(AdminStates.user_management_search, F.text)
|
||||
@admin_router.message(AdminStates.user_management_search, F.text, NotCommand())
|
||||
async def admin_users_search_process(message: Message, state: FSMContext):
|
||||
"""Обработка поискового запроса"""
|
||||
if not await check_admin_access(message.from_user.id):
|
||||
return
|
||||
|
||||
query = message.text.strip()
|
||||
if not 1 <= len(query) <= 100:
|
||||
await message.answer("Поисковый запрос должен содержать от 1 до 100 символов. /cancel — отмена.")
|
||||
return
|
||||
|
||||
if query == "/cancel":
|
||||
await message.answer("❌ Поиск отменен")
|
||||
@@ -5233,12 +5271,12 @@ async def admin_users_search_process(message: Message, state: FSMContext):
|
||||
)
|
||||
|
||||
if not users:
|
||||
text = f"❌ По запросу «{query}» ничего не найдено"
|
||||
text = f"❌ По запросу «{escape(query)}» ничего не найдено"
|
||||
buttons = [
|
||||
[InlineKeyboardButton(text="◀️ В управление пользователями", callback_data="admin_users")]
|
||||
]
|
||||
else:
|
||||
text = f"🔍 <b>Результаты поиска:</b> «{query}»\n"
|
||||
text = f"🔍 <b>Результаты поиска:</b> «{escape(query)}»\n"
|
||||
text += f"Найдено: {total} пользователей\n\n"
|
||||
|
||||
buttons = []
|
||||
@@ -5262,7 +5300,7 @@ async def admin_users_search_process(message: Message, state: FSMContext):
|
||||
if total > 15:
|
||||
nav_buttons.append(InlineKeyboardButton(
|
||||
text="➡️ Далее",
|
||||
callback_data=f"admin_users_search_page:{query}:2"
|
||||
callback_data=f"admin_users_search_page:{escape(query)}:2"
|
||||
))
|
||||
buttons.append(nav_buttons)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user