Compare commits
25 Commits
2db39b0652
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 733298bf06 | |||
| 93f7ccdcf6 | |||
| dbba2c4b83 | |||
| 417ecf14d7 | |||
| fd8fc35f03 | |||
| f855772229 | |||
| df3d439e62 | |||
| 45d960746b | |||
| 7b50be5ae1 | |||
| 6089c90d22 | |||
| c5a90a5153 | |||
| 72f9d40a1a | |||
| 62ca809f11 | |||
| 9fe9e8958a | |||
|
|
21f348471e | ||
|
|
4daec268e6 | ||
| 5c01486bd8 | |||
| 782f702327 | |||
| ede4617b00 | |||
| 7d5ad3d668 | |||
| 904f94e1b5 | |||
| 06ddd1e5fa | |||
| b45fe005b9 | |||
| 815cc544d5 | |||
| 6b24388faa |
12
.env.prod
12
.env.prod
@@ -2,17 +2,17 @@
|
|||||||
# Скопируйте этот файл в .env.prod и заполните реальными значениями
|
# Скопируйте этот файл в .env.prod и заполните реальными значениями
|
||||||
|
|
||||||
# Telegram Bot Token
|
# Telegram Bot Token
|
||||||
BOT_TOKEN=8125171867:AAHA0l2hGGodOUBh0rFlkE4CxK0X6JzZv64
|
BOT_TOKEN=6804077170:AAGw_t6ktAiwYr2mrby0PUhckt50NZaEs0E
|
||||||
|
|
||||||
# PostgreSQL настройки для Docker контейнера
|
# PostgreSQL настройки для Docker контейнера
|
||||||
POSTGRES_HOST=postgres
|
POSTGRES_HOST=192.168.0.102
|
||||||
POSTGRES_PORT=5432
|
POSTGRES_PORT=5432
|
||||||
POSTGRES_DB=lottery_bot
|
POSTGRES_DB=new_lottery_KR
|
||||||
POSTGRES_USER=lottery_user
|
POSTGRES_USER=trevor
|
||||||
POSTGRES_PASSWORD=Cl0ud_1985!
|
POSTGRES_PASSWORD=Cl0ud_1985!
|
||||||
|
|
||||||
# Database URL для бота (использует postgres как hostname внутри Docker сети)
|
# Database URL для бота (использует postgres как hostname внутри Docker сети)
|
||||||
DATABASE_URL=postgresql+asyncpg://lottery_user:Cl0ud_1985!@postgres:5432/lottery_bot
|
DATABASE_URL=postgresql+asyncpg://trevor:Cl0ud_1985!@192.168.0.102:5432/new_lottery_KR
|
||||||
# Redis URL
|
# Redis URL
|
||||||
REDIS_URL=redis://redis:6379/0
|
REDIS_URL=redis://redis:6379/0
|
||||||
|
|
||||||
@@ -20,4 +20,4 @@ REDIS_URL=redis://redis:6379/0
|
|||||||
ADMIN_IDS=556399210,6639865742
|
ADMIN_IDS=556399210,6639865742
|
||||||
|
|
||||||
# Настройки логирования
|
# Настройки логирования
|
||||||
LOG_LEVEL=INFO
|
LOG_LEVEL=DEBUG
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -60,3 +60,4 @@ venv.bak/
|
|||||||
# Системные файлы
|
# Системные файлы
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db.bot.pid
|
Thumbs.db.bot.pid
|
||||||
|
*.bak
|
||||||
|
|||||||
@@ -8,15 +8,15 @@ services:
|
|||||||
container_name: lottery_postgres
|
container_name: lottery_postgres
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_DB: ${POSTGRES_DB:-lottery_bot}
|
POSTGRES_DB: ${POSTGRES_DB:-new_lottery_kr}
|
||||||
POSTGRES_USER: ${POSTGRES_USER:-lottery_user}
|
POSTGRES_USER: ${POSTGRES_USER:-trevor}
|
||||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-lottery_password}
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-Cl0ud_1985!}
|
||||||
volumes:
|
volumes:
|
||||||
- postgres_data:/var/lib/postgresql/data
|
- postgres_data:/var/lib/postgresql/data
|
||||||
networks:
|
networks:
|
||||||
- lottery_network
|
- lottery_network
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-lottery_user}"]
|
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-trevor}"]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
|
|||||||
244
docs/EMOJI_SYSTEM.md
Normal file
244
docs/EMOJI_SYSTEM.md
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
# Система управления кастомными эмодзи
|
||||||
|
|
||||||
|
## Обзор
|
||||||
|
|
||||||
|
Система позволяет администраторам регистрировать премиум эмодзи и использовать их в сообщениях бота. Когда админ отправляет эмодзи боту:
|
||||||
|
|
||||||
|
1. Бот получает `emoji_id` от Telegram API
|
||||||
|
2. Сохраняет эмодзи в таблице `emoji_mappings`
|
||||||
|
3. При отправке сообщений в чаты бот автоматически использует `emoji_id` вместо текста эмодзи
|
||||||
|
|
||||||
|
Это обеспечивает, что эмодзи будут выглядеть точно так же, как их отправил админ, даже если это премиум эмодзи.
|
||||||
|
|
||||||
|
## Команды администратора
|
||||||
|
|
||||||
|
### 1. Добавить новый эмодзи
|
||||||
|
|
||||||
|
```
|
||||||
|
/add_emoji
|
||||||
|
```
|
||||||
|
|
||||||
|
Процесс:
|
||||||
|
1. Админ запускает команду `/add_emoji`
|
||||||
|
2. Бот просит отправить эмодзи
|
||||||
|
3. Админ отправляет эмодзи (например, 🎲)
|
||||||
|
4. Бот просит описание (для чего используется)
|
||||||
|
5. Админ отправляет描述 (например, "Для лотереи")
|
||||||
|
6. Бот сохраняет в БД и подтверждает
|
||||||
|
|
||||||
|
### 2. Просмотр своих эмодзи
|
||||||
|
|
||||||
|
```
|
||||||
|
/my_emojis
|
||||||
|
```
|
||||||
|
|
||||||
|
Показывает все эмодзи, добавленные этим админом:
|
||||||
|
- Сам эмодзи
|
||||||
|
- Описание
|
||||||
|
- ID (первые 30 символов)
|
||||||
|
- Дату добавления
|
||||||
|
|
||||||
|
### 3. Просмотр всех эмодзи в системе
|
||||||
|
|
||||||
|
```
|
||||||
|
/all_emojis
|
||||||
|
```
|
||||||
|
|
||||||
|
Показывает все эмодзи всех админов с информацией об администраторе
|
||||||
|
|
||||||
|
### 4. Удалить эмодзи
|
||||||
|
|
||||||
|
```
|
||||||
|
/delete_emoji
|
||||||
|
```
|
||||||
|
|
||||||
|
Админ может удалить только свои эмодзи. Процесс:
|
||||||
|
1. Вызвать команду
|
||||||
|
2. Выбрать эмодзи из список (кнопки)
|
||||||
|
3. Бот удалит из БД
|
||||||
|
|
||||||
|
## Использование в коде
|
||||||
|
|
||||||
|
### Простой способ - прямое использование эмодзи
|
||||||
|
|
||||||
|
```python
|
||||||
|
from aiogram.types import Message
|
||||||
|
|
||||||
|
async def handler(message: Message):
|
||||||
|
await message.answer(
|
||||||
|
text="🎲 Добро пожаловать на лотерею! 🏆",
|
||||||
|
parse_mode="HTML"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### С обработкой эмодзи
|
||||||
|
|
||||||
|
```python
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
from src.core.emoji_message_helper import get_emoji_aware_text
|
||||||
|
from aiogram.types import Message
|
||||||
|
|
||||||
|
async def handler(message: Message, session: AsyncSession):
|
||||||
|
# Текст с эмодзи
|
||||||
|
original_text = "🎲 Выиграли! 🏆"
|
||||||
|
|
||||||
|
# Обработаны текст (эмодзи заменены на ID для корректного отображения)
|
||||||
|
processed_text = await get_emoji_aware_text(session, original_text)
|
||||||
|
|
||||||
|
await message.answer(processed_text, parse_mode="HTML")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Работа с EmojiMessageHelper
|
||||||
|
|
||||||
|
```python
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
from src.core.emoji_message_helper import EmojiMessageHelper
|
||||||
|
|
||||||
|
async def handler(message: Message, session: AsyncSession):
|
||||||
|
helper = EmojiMessageHelper(session)
|
||||||
|
|
||||||
|
# Обработка перед отправкой
|
||||||
|
text = "🎲 Лотерея начинается! 💎"
|
||||||
|
processed = await helper.process_text_before_send(text)
|
||||||
|
|
||||||
|
await message.answer(processed, parse_mode="HTML")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Структура БД
|
||||||
|
|
||||||
|
### Таблица `emoji_mappings`
|
||||||
|
|
||||||
|
| Колонка | Тип | Описание |
|
||||||
|
|---------|-----|---------|
|
||||||
|
| `id` | Integer | Primary Key |
|
||||||
|
| `emoji_text` | String(10) | Сам эмодзи (например, 🎲) |
|
||||||
|
| `emoji_id` | String(255) | telegram_emoji_id от API (уникален) |
|
||||||
|
| `admin_id` | Integer | FK на user (администратор) |
|
||||||
|
| `description` | String(255) | Описание назначения эмодзи |
|
||||||
|
| `created_at` | DateTime | Дата добавления |
|
||||||
|
| `last_used_at` | DateTime | Последнее использование |
|
||||||
|
|
||||||
|
### Уникальные ограничения
|
||||||
|
|
||||||
|
- `emoji_id` — уникален во всей системе
|
||||||
|
- `(emoji_text, admin_id)` — один админ не может добавить один эмодзи дважды
|
||||||
|
|
||||||
|
## API сервиса EmojiMappingService
|
||||||
|
|
||||||
|
### Регистрация эмодзи
|
||||||
|
|
||||||
|
```python
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
from src.core.emoji_mapping_service import EmojiMappingService
|
||||||
|
|
||||||
|
async with async_session_maker() as session:
|
||||||
|
service = EmojiMappingService(session)
|
||||||
|
|
||||||
|
emoji = await service.register_emoji(
|
||||||
|
emoji_text="🎲",
|
||||||
|
emoji_id="telegram_emoji_id_here",
|
||||||
|
admin_id=12345,
|
||||||
|
description="Для лотереи"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Получение эмодзи
|
||||||
|
|
||||||
|
```python
|
||||||
|
# По тексту
|
||||||
|
emoji = await service.get_emoji_by_text("🎲")
|
||||||
|
|
||||||
|
# По emoji_id
|
||||||
|
emoji = await service.get_emoji_by_id("telegram_emoji_id")
|
||||||
|
|
||||||
|
# Все эмодзи админа
|
||||||
|
emojis = await service.get_all_emoji_by_admin(admin_id=12345)
|
||||||
|
|
||||||
|
# Все эмодзи
|
||||||
|
all_emojis = await service.get_all_emojis()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Замена эмодзи в тексте
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Текст → с заменой эмодзи на ID
|
||||||
|
processed = await service.replace_emojis_in_text(
|
||||||
|
"🎲 Выиграли! 🏆"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Обратно - ID → эмодзи
|
||||||
|
original = await service.restore_emojis_in_text(processed)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Получить словарь маппинга
|
||||||
|
|
||||||
|
```python
|
||||||
|
# {emoji_text: emoji_id}
|
||||||
|
mapping = await service.get_emoji_mapping_dict()
|
||||||
|
# {'🎲': 'telegram_emoji_id_1', '🏆': 'telegram_emoji_id_2', ...}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Примеры использования в разных рутерах
|
||||||
|
|
||||||
|
### В регистрации
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def registration_complete(message: Message, session: AsyncSession):
|
||||||
|
text = "✅ Регистрация завершена! 🎉"
|
||||||
|
text = await get_emoji_aware_text(session, text)
|
||||||
|
await message.answer(text, parse_mode="HTML")
|
||||||
|
```
|
||||||
|
|
||||||
|
### В админ-панели
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def lottery_created(callback: CallbackQuery, session: AsyncSession):
|
||||||
|
text = "🎰 Новый розыгрыш создан! 🏆"
|
||||||
|
text = await get_emoji_aware_text(session, text)
|
||||||
|
await callback.message.edit_text(text, parse_mode="HTML")
|
||||||
|
```
|
||||||
|
|
||||||
|
### В чатовой рассылке
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def broadcast_message(message: Message, session: AsyncSession):
|
||||||
|
text = f"📢 Сообщение от админа: {message.text}\n\n💎 Удачи!"
|
||||||
|
text = await get_emoji_aware_text(session, text)
|
||||||
|
|
||||||
|
for user_id in target_users:
|
||||||
|
await bot.send_message(user_id, text, parse_mode="HTML")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Важные моменты
|
||||||
|
|
||||||
|
1. **Parse Mode**: Всегда используйте `parse_mode="HTML"` при работе с эмодзи
|
||||||
|
2. **Кеширование ID**: Система не кеширует, каждый раз обращается к БД. Для оптимизации можно добавить кеширование
|
||||||
|
3. **Лог использования**: `last_used_at` обновляется автоматически при замене в тексте
|
||||||
|
4. **Удаление**: Удаленный эмодзи больше не будет заменяться в новых сообщениях
|
||||||
|
5. **Конфликты**: Если два админа добавляют один эмодзи - они сохранятся отдельно (разные admin_id)
|
||||||
|
|
||||||
|
## Миграция
|
||||||
|
|
||||||
|
Таблица создана миграцией:
|
||||||
|
```
|
||||||
|
migrations/versions/20260307_0100_add_emoji_mappings.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Применить миграцию:
|
||||||
|
```bash
|
||||||
|
alembic upgrade head
|
||||||
|
```
|
||||||
|
|
||||||
|
## Trouble Shooting
|
||||||
|
|
||||||
|
### Эмодзи не отображается корректно
|
||||||
|
- Проверьте что используете `parse_mode="HTML"`
|
||||||
|
- Убедитесь что эмодзи зарегистрирован с помощью `/my_emojis`
|
||||||
|
|
||||||
|
### Ошибка "Can't parse entities"
|
||||||
|
- Это означает что есть конфликт форматирования
|
||||||
|
- Убедитесь что используете HTML теги (`<b>`, `<i>`, и т.д.), а не Markdown (`**`, `__`)
|
||||||
|
|
||||||
|
### Эмодзи не заменяется
|
||||||
|
- Проверьте что был зарегистрирован с помощью `/add_emoji`
|
||||||
|
- Убедитесь что используете функцию `get_emoji_aware_text()` перед отправкой
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"export_date": "2026-02-08T17:40:31.898764",
|
|
||||||
"statistics": {
|
|
||||||
"users": 3,
|
|
||||||
"lotteries": 1,
|
|
||||||
"participations": 1,
|
|
||||||
"winners": 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"export_date": "2026-02-08T17:42:08.014799",
|
|
||||||
"statistics": {
|
|
||||||
"users": 3,
|
|
||||||
"lotteries": 1,
|
|
||||||
"participations": 1,
|
|
||||||
"winners": 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"export_date": "2026-02-08T17:42:21.844218",
|
|
||||||
"statistics": {
|
|
||||||
"users": 3,
|
|
||||||
"lotteries": 1,
|
|
||||||
"participations": 1,
|
|
||||||
"winners": 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
72
main.py
72
main.py
@@ -30,6 +30,7 @@ from src.handlers.account_handlers import account_router
|
|||||||
from src.handlers.message_management import message_admin_router
|
from src.handlers.message_management import message_admin_router
|
||||||
from src.handlers.p2p_chat import router as p2p_chat_router
|
from src.handlers.p2p_chat import router as p2p_chat_router
|
||||||
from src.handlers.help_handlers import router as help_router
|
from src.handlers.help_handlers import router as help_router
|
||||||
|
from src.handlers.admin_emoji_handlers import router as admin_emoji_router
|
||||||
|
|
||||||
# Настройка логирования
|
# Настройка логирования
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
@@ -134,18 +135,41 @@ async def btn_chat(message: Message, state: FSMContext):
|
|||||||
@router.message(F.text == "📝 Регистрация")
|
@router.message(F.text == "📝 Регистрация")
|
||||||
async def btn_registration(message: Message, state: FSMContext):
|
async def btn_registration(message: Message, state: FSMContext):
|
||||||
"""Обработчик кнопки 'Регистрация'"""
|
"""Обработчик кнопки 'Регистрация'"""
|
||||||
from aiogram.types import CallbackQuery
|
from src.handlers.registration_handlers import RegistrationStates
|
||||||
|
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||||
|
|
||||||
fake_callback = CallbackQuery(
|
logger.info(f"User {message.from_user.id} pressed Registration button")
|
||||||
id="fake",
|
|
||||||
from_user=message.from_user,
|
text = (
|
||||||
chat_instance="0",
|
"📝 Регистрация в системе\n\n"
|
||||||
data="start_registration",
|
"Для участия в розыгрышах необходимо зарегистрироваться.\n\n"
|
||||||
message=message
|
"Шаг 1 из 3: Придумайте никнейм\n\n"
|
||||||
|
"🎭 Введите ваш никнейм для чата:\n"
|
||||||
|
"• От 2 до 20 символов\n"
|
||||||
|
"• Может содержать буквы, цифры, пробелы\n"
|
||||||
|
"• Это имя будут видеть другие участники"
|
||||||
)
|
)
|
||||||
|
|
||||||
from src.handlers.registration_handlers import start_registration
|
await message.answer(
|
||||||
await start_registration(fake_callback, state)
|
text,
|
||||||
|
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
|
||||||
|
[InlineKeyboardButton(text="❌ Отмена", callback_data="back_to_main")]
|
||||||
|
])
|
||||||
|
)
|
||||||
|
|
||||||
|
await state.set_state(RegistrationStates.waiting_for_nickname)
|
||||||
|
|
||||||
|
|
||||||
|
@router.message(CaseInsensitiveCommand("register"))
|
||||||
|
async def cmd_register(message: Message, state: FSMContext):
|
||||||
|
"""Обработчик команды /register (регистронезависимо)"""
|
||||||
|
await btn_registration(message, state)
|
||||||
|
|
||||||
|
|
||||||
|
@router.message(F.text.lower().in_(["регистрация", "регистр", "register"]))
|
||||||
|
async def text_registration(message: Message, state: FSMContext):
|
||||||
|
"""Обработчик текста для регистрации"""
|
||||||
|
await btn_registration(message, state)
|
||||||
|
|
||||||
|
|
||||||
@router.message(F.text == "🔑 Мой код")
|
@router.message(F.text == "🔑 Мой код")
|
||||||
@@ -155,9 +179,9 @@ async def btn_my_code(message: Message):
|
|||||||
await show_verification_code(message)
|
await show_verification_code(message)
|
||||||
|
|
||||||
|
|
||||||
@router.message(F.text == "💳 Мои счета")
|
@router.message(F.text == "<EFBFBD> Мои логины")
|
||||||
async def btn_my_accounts(message: Message):
|
async def btn_my_accounts(message: Message):
|
||||||
"""Обработчик кнопки 'Мои счета'"""
|
"""Обработчик кнопки 'Мои логины'"""
|
||||||
from src.handlers.registration_handlers import show_user_accounts
|
from src.handlers.registration_handlers import show_user_accounts
|
||||||
await show_user_accounts(message)
|
await show_user_accounts(message)
|
||||||
|
|
||||||
@@ -182,9 +206,9 @@ async def btn_exit_chat(message: Message, state: FSMContext):
|
|||||||
await exit_chat(message, state)
|
await exit_chat(message, state)
|
||||||
|
|
||||||
|
|
||||||
@router.message(F.text == "🏠 Главное меню")
|
@router.message(F.text == "🏠 Главная")
|
||||||
async def btn_main_menu(message: Message):
|
async def btn_main_menu(message: Message):
|
||||||
"""Обработчик кнопки 'Главное меню'"""
|
"""Обработчик кнопки 'Главная'"""
|
||||||
await cmd_start(message)
|
await cmd_start(message)
|
||||||
|
|
||||||
|
|
||||||
@@ -192,7 +216,26 @@ async def btn_main_menu(message: Message):
|
|||||||
async def cmd_admin(message: Message):
|
async def cmd_admin(message: Message):
|
||||||
"""Обработчик команды /admin (регистронезависимо) - перенаправляет в admin_panel"""
|
"""Обработчик команды /admin (регистронезависимо) - перенаправляет в admin_panel"""
|
||||||
from src.core.config import ADMIN_IDS
|
from src.core.config import ADMIN_IDS
|
||||||
if message.from_user.id not in ADMIN_IDS:
|
from src.core.database import async_session_maker
|
||||||
|
from src.core.models import User
|
||||||
|
from sqlalchemy import select
|
||||||
|
|
||||||
|
# Проверяем, является ли пользователь главным администратором из .env
|
||||||
|
user_id = message.from_user.id
|
||||||
|
is_super_admin = user_id in ADMIN_IDS
|
||||||
|
|
||||||
|
# Проверяем, является ли пользователь назначенным администратором
|
||||||
|
is_assigned_admin = False
|
||||||
|
if not is_super_admin:
|
||||||
|
async with async_session_maker() as session:
|
||||||
|
user = await session.execute(
|
||||||
|
select(User).where(User.telegram_id == user_id)
|
||||||
|
)
|
||||||
|
user = user.scalar_one_or_none()
|
||||||
|
is_assigned_admin = user and user.is_admin
|
||||||
|
|
||||||
|
# Если не администратор ни того, ни другого типа
|
||||||
|
if not (is_super_admin or is_assigned_admin):
|
||||||
await message.answer("❌ Недостаточно прав для доступа к админ панели")
|
await message.answer("❌ Недостаточно прав для доступа к админ панели")
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -247,6 +290,7 @@ async def main():
|
|||||||
|
|
||||||
# 2. Специфичные роутеры
|
# 2. Специфичные роутеры
|
||||||
dp.include_router(message_admin_router) # Управление сообщениями администратором
|
dp.include_router(message_admin_router) # Управление сообщениями администратором
|
||||||
|
dp.include_router(admin_emoji_router) # Управление кастомными эмодзи
|
||||||
dp.include_router(admin_router) # Админ панель - самая высокая специфичность
|
dp.include_router(admin_router) # Админ панель - самая высокая специфичность
|
||||||
dp.include_router(registration_router) # Регистрация
|
dp.include_router(registration_router) # Регистрация
|
||||||
dp.include_router(admin_account_router) # Админские команды счетов
|
dp.include_router(admin_account_router) # Админские команды счетов
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""merge branches
|
"""merge branches
|
||||||
|
|
||||||
Revision ID: merge_migration
|
Revision ID: merge_migration
|
||||||
Revises: 41aae82e631b, cd31303a681c
|
Revises: cd31303a681c
|
||||||
Create Date: 2026-02-18 04:02:12.000000
|
Create Date: 2026-02-18 04:02:12.000000
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -11,7 +11,7 @@ import sqlalchemy as sa
|
|||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision = 'merge_migration'
|
revision = 'merge_migration'
|
||||||
down_revision = ('41aae82e631b', 'cd31303a681c')
|
down_revision = 'cd31303a681c'
|
||||||
branch_labels = None
|
branch_labels = None
|
||||||
depends_on = None
|
depends_on = None
|
||||||
|
|
||||||
|
|||||||
45
migrations/versions/20260307_0100_add_emoji_mappings.py
Normal file
45
migrations/versions/20260307_0100_add_emoji_mappings.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
"""Add emoji_mappings table for storing custom emoji IDs
|
||||||
|
|
||||||
|
Revision ID: 20260307_0100_add_emoji_mappings
|
||||||
|
Revises: merge_migration
|
||||||
|
Create Date: 2026-03-07 01:00:00.000000
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '20260307_0100_add_emoji_mappings'
|
||||||
|
down_revision = 'merge_migration'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table(
|
||||||
|
'emoji_mappings',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('emoji_text', sa.String(length=10), nullable=False),
|
||||||
|
sa.Column('emoji_id', sa.String(length=255), nullable=False),
|
||||||
|
sa.Column('admin_id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('description', sa.String(length=255), nullable=True),
|
||||||
|
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
|
||||||
|
sa.Column('last_used_at', sa.DateTime(timezone=True), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['admin_id'], ['users.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id'),
|
||||||
|
sa.UniqueConstraint('emoji_id', name='emoji_mappings_emoji_id_key'),
|
||||||
|
sa.UniqueConstraint('emoji_text', 'admin_id', name='unique_emoji_per_admin'),
|
||||||
|
)
|
||||||
|
op.create_index('ix_emoji_mappings_emoji_id', 'emoji_mappings', ['emoji_id'], unique=True)
|
||||||
|
op.create_index('ix_emoji_mappings_emoji_text', 'emoji_mappings', ['emoji_text'], unique=False)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_index('ix_emoji_mappings_emoji_text', table_name='emoji_mappings')
|
||||||
|
op.drop_index('ix_emoji_mappings_emoji_id', table_name='emoji_mappings')
|
||||||
|
op.drop_table('emoji_mappings')
|
||||||
|
# ### end Alembic commands ###
|
||||||
@@ -5,6 +5,7 @@ import logging
|
|||||||
from src.interfaces.base import IBotController, ILotteryService, IUserService, IKeyboardBuilder, IMessageFormatter
|
from src.interfaces.base import IBotController, ILotteryService, IUserService, IKeyboardBuilder, IMessageFormatter
|
||||||
from src.interfaces.base import ILotteryRepository, IParticipationRepository
|
from src.interfaces.base import ILotteryRepository, IParticipationRepository
|
||||||
from src.core.config import ADMIN_IDS
|
from src.core.config import ADMIN_IDS
|
||||||
|
from src.core.telegram_config import get_parse_mode
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -35,6 +36,9 @@ class BotController(IBotController):
|
|||||||
async def handle_start(self, message: Message):
|
async def handle_start(self, message: Message):
|
||||||
"""Обработать команду /start"""
|
"""Обработать команду /start"""
|
||||||
from src.utils.keyboards import get_main_reply_keyboard
|
from src.utils.keyboards import get_main_reply_keyboard
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
user = await self.user_service.get_or_create_user(
|
user = await self.user_service.get_or_create_user(
|
||||||
telegram_id=message.from_user.id,
|
telegram_id=message.from_user.id,
|
||||||
@@ -43,6 +47,9 @@ class BotController(IBotController):
|
|||||||
last_name=message.from_user.last_name
|
last_name=message.from_user.last_name
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Логирование статуса регистрации
|
||||||
|
logger.info(f"User {message.from_user.id}: is_registered={user.is_registered}, is_admin={self.is_admin(message.from_user.id)}")
|
||||||
|
|
||||||
welcome_text = f"👋 Добро пожаловать, {user.first_name or 'дорогой пользователь'}!\n\n"
|
welcome_text = f"👋 Добро пожаловать, {user.first_name or 'дорогой пользователь'}!\n\n"
|
||||||
welcome_text += "🎲 Это бот для участия в розыгрышах.\n\n"
|
welcome_text += "🎲 Это бот для участия в розыгрышах.\n\n"
|
||||||
|
|
||||||
@@ -82,7 +89,7 @@ class BotController(IBotController):
|
|||||||
await callback.answer("❌ Нет активных розыгрышей", show_alert=True)
|
await callback.answer("❌ Нет активных розыгрышей", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
text = "🎲 **Активные розыгрыши:**\n\n"
|
text = "🎲 <b>Активные розыгрыши:</b>\n\n"
|
||||||
|
|
||||||
for lottery in lotteries:
|
for lottery in lotteries:
|
||||||
participants_count = await self.participation_repo.get_count_by_lottery(lottery.id)
|
participants_count = await self.participation_repo.get_count_by_lottery(lottery.id)
|
||||||
@@ -106,7 +113,7 @@ class BotController(IBotController):
|
|||||||
await callback.message.edit_text(
|
await callback.message.edit_text(
|
||||||
text,
|
text,
|
||||||
reply_markup=keyboard,
|
reply_markup=keyboard,
|
||||||
parse_mode="Markdown"
|
parse_mode=get_parse_mode("inline_keyboard")
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Если сообщение не изменилось - просто отвечаем на callback
|
# Если сообщение не изменилось - просто отвечаем на callback
|
||||||
@@ -118,5 +125,5 @@ class BotController(IBotController):
|
|||||||
await callback.message.answer(
|
await callback.message.answer(
|
||||||
text,
|
text,
|
||||||
reply_markup=keyboard,
|
reply_markup=keyboard,
|
||||||
parse_mode="Markdown"
|
parse_mode=get_parse_mode("inline_keyboard")
|
||||||
)
|
)
|
||||||
|
|||||||
221
src/core/emoji_mapping_service.py
Normal file
221
src/core/emoji_mapping_service.py
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
"""Сервис для управления маппингом кастомных эмодзи"""
|
||||||
|
from typing import Optional, List, Dict
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
from sqlalchemy import select, update
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
import re
|
||||||
|
|
||||||
|
from src.core.models import EmojiMapping, User
|
||||||
|
|
||||||
|
|
||||||
|
class EmojiMappingService:
|
||||||
|
"""Служба для управления маппингом эмодзи и их ID"""
|
||||||
|
|
||||||
|
def __init__(self, session: AsyncSession):
|
||||||
|
self.session = session
|
||||||
|
|
||||||
|
async def register_emoji(
|
||||||
|
self,
|
||||||
|
emoji_text: str,
|
||||||
|
emoji_id: str,
|
||||||
|
admin_id: int,
|
||||||
|
description: Optional[str] = None
|
||||||
|
) -> EmojiMapping:
|
||||||
|
"""
|
||||||
|
Зарегистрировать новый эмодзи с его ID от Telegram
|
||||||
|
|
||||||
|
Args:
|
||||||
|
emoji_text: Сам эмодзи символ (например, '🎲')
|
||||||
|
emoji_id: telegram_emoji_id от Telegram API
|
||||||
|
admin_id: ID админа, который добавил эмодзи
|
||||||
|
description: Описание назначения этого эмодзи
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Созданный объект EmojiMapping
|
||||||
|
"""
|
||||||
|
emoji = EmojiMapping(
|
||||||
|
emoji_text=emoji_text,
|
||||||
|
emoji_id=emoji_id,
|
||||||
|
admin_id=admin_id,
|
||||||
|
description=description,
|
||||||
|
created_at=datetime.now(timezone.utc)
|
||||||
|
)
|
||||||
|
self.session.add(emoji)
|
||||||
|
await self.session.commit()
|
||||||
|
await self.session.refresh(emoji)
|
||||||
|
return emoji
|
||||||
|
|
||||||
|
async def get_emoji_by_text(self, emoji_text: str, admin_id: Optional[int] = None) -> Optional[EmojiMapping]:
|
||||||
|
"""
|
||||||
|
Получить маппинг эмодзи по его текстовому значению
|
||||||
|
|
||||||
|
Args:
|
||||||
|
emoji_text: Текст эмодзи
|
||||||
|
admin_id: Опционально - ID админа для фильтрации
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
EmojiMapping объект или None
|
||||||
|
"""
|
||||||
|
query = select(EmojiMapping).where(EmojiMapping.emoji_text == emoji_text)
|
||||||
|
if admin_id:
|
||||||
|
query = query.where(EmojiMapping.admin_id == admin_id)
|
||||||
|
|
||||||
|
result = await self.session.execute(query)
|
||||||
|
return result.scalars().first()
|
||||||
|
|
||||||
|
async def get_emoji_by_id(self, emoji_id: str) -> Optional[EmojiMapping]:
|
||||||
|
"""
|
||||||
|
Получить маппинг эмодзи по его emoji_id
|
||||||
|
|
||||||
|
Args:
|
||||||
|
emoji_id: telegram_emoji_id
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
EmojiMapping объект или None
|
||||||
|
"""
|
||||||
|
result = await self.session.execute(
|
||||||
|
select(EmojiMapping).where(EmojiMapping.emoji_id == emoji_id)
|
||||||
|
)
|
||||||
|
return result.scalars().first()
|
||||||
|
|
||||||
|
async def get_all_emoji_by_admin(self, admin_id: int) -> List[EmojiMapping]:
|
||||||
|
"""
|
||||||
|
Получить все эмодзи, добавленные конкретным админом
|
||||||
|
|
||||||
|
Args:
|
||||||
|
admin_id: ID админа
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Список EmojiMapping объектов
|
||||||
|
"""
|
||||||
|
result = await self.session.execute(
|
||||||
|
select(EmojiMapping).where(EmojiMapping.admin_id == admin_id)
|
||||||
|
)
|
||||||
|
return list(result.scalars().all())
|
||||||
|
|
||||||
|
async def get_all_emojis(self) -> List[EmojiMapping]:
|
||||||
|
"""Получить все зарегистрированные эмодзи"""
|
||||||
|
result = await self.session.execute(
|
||||||
|
select(EmojiMapping).order_by(EmojiMapping.created_at.desc())
|
||||||
|
)
|
||||||
|
return list(result.scalars().all())
|
||||||
|
|
||||||
|
async def delete_emoji(self, emoji_id: str) -> bool:
|
||||||
|
"""
|
||||||
|
Удалить эмодзи маппинг
|
||||||
|
|
||||||
|
Args:
|
||||||
|
emoji_id: telegram_emoji_id
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True если удален, False если не найден
|
||||||
|
"""
|
||||||
|
emoji = await self.get_emoji_by_id(emoji_id)
|
||||||
|
if emoji:
|
||||||
|
await self.session.delete(emoji)
|
||||||
|
await self.session.commit()
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def update_last_used(self, emoji_id: str) -> bool:
|
||||||
|
"""
|
||||||
|
Обновить время последнего использования эмодзи
|
||||||
|
|
||||||
|
Args:
|
||||||
|
emoji_id: telegram_emoji_id
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True если обновлен, False если не найден
|
||||||
|
"""
|
||||||
|
await self.session.execute(
|
||||||
|
update(EmojiMapping)
|
||||||
|
.where(EmojiMapping.emoji_id == emoji_id)
|
||||||
|
.values(last_used_at=datetime.now(timezone.utc))
|
||||||
|
)
|
||||||
|
await self.session.commit()
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def replace_emojis_in_text(self, text: str) -> str:
|
||||||
|
"""
|
||||||
|
Заменить все известные эмодзи на их emoji_id в тексте
|
||||||
|
|
||||||
|
Это используется перед отправкой сообщения в Telegram,
|
||||||
|
чтобы эмодзи выглядели так же, как их отправил админ
|
||||||
|
|
||||||
|
Args:
|
||||||
|
text: Исходный текст с эмодзи
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Текст с заменой эмодзи на emoji_id
|
||||||
|
"""
|
||||||
|
# Получаем все эмодзи маппинги
|
||||||
|
emojis = await self.get_all_emojis()
|
||||||
|
|
||||||
|
# Заменяем каждый эмодзи на его emoji_id
|
||||||
|
for emoji in emojis:
|
||||||
|
# Экранируем специальные символы если нужно
|
||||||
|
if emoji.emoji_text in text:
|
||||||
|
# Замена с сохранением контекста - оборачиваем в специальные маркеры
|
||||||
|
# Это позволит потом распознать что это эмодзи ID а не обычный текст
|
||||||
|
text = text.replace(emoji.emoji_text, f"|{emoji.emoji_id}|")
|
||||||
|
|
||||||
|
return text
|
||||||
|
|
||||||
|
async def restore_emojis_in_text(self, text: str) -> str:
|
||||||
|
"""
|
||||||
|
Восстановить эмодзи из их emoji_id в тексте (обратная операция)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
text: Текст с emoji_id маркерами (|emoji_id|)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Текст с восстановленными эмодзи
|
||||||
|
"""
|
||||||
|
# Получаем все эмодзи маппинги
|
||||||
|
emojis = await self.get_all_emojis()
|
||||||
|
|
||||||
|
# Восстанавливаем каждый эмодзи из его ID
|
||||||
|
for emoji in emojis:
|
||||||
|
if f"|{emoji.emoji_id}|" in text:
|
||||||
|
text = text.replace(f"|{emoji.emoji_id}|", emoji.emoji_text)
|
||||||
|
|
||||||
|
return text
|
||||||
|
|
||||||
|
async def get_emoji_mapping_dict(self) -> Dict[str, str]:
|
||||||
|
"""
|
||||||
|
Получить словарь маппинга эмодзи -> emoji_id для быстрого доступа
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Словарь {emoji_text: emoji_id}
|
||||||
|
"""
|
||||||
|
emojis = await self.get_all_emojis()
|
||||||
|
return {emoji.emoji_text: emoji.emoji_id for emoji in emojis}
|
||||||
|
|
||||||
|
async def bulk_register_emojis(self, emojis_data: List[Dict]) -> List[EmojiMapping]:
|
||||||
|
"""
|
||||||
|
Зарегистрировать несколько эмодзи сразу
|
||||||
|
|
||||||
|
Args:
|
||||||
|
emojis_data: Список со структурой [
|
||||||
|
{
|
||||||
|
'emoji_text': '🎲',
|
||||||
|
'emoji_id': 'some_id',
|
||||||
|
'admin_id': 123,
|
||||||
|
'description': 'Для лотереи'
|
||||||
|
},
|
||||||
|
...
|
||||||
|
]
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Список созданных EmojiMapping объектов
|
||||||
|
"""
|
||||||
|
result = []
|
||||||
|
for emoji_data in emojis_data:
|
||||||
|
emoji = await self.register_emoji(
|
||||||
|
emoji_text=emoji_data['emoji_text'],
|
||||||
|
emoji_id=emoji_data['emoji_id'],
|
||||||
|
admin_id=emoji_data['admin_id'],
|
||||||
|
description=emoji_data.get('description')
|
||||||
|
)
|
||||||
|
result.append(emoji)
|
||||||
|
return result
|
||||||
61
src/core/emoji_message_helper.py
Normal file
61
src/core/emoji_message_helper.py
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
"""
|
||||||
|
Утилиты для автоматической замены эмодзи на emoji_id при отправке сообщений
|
||||||
|
"""
|
||||||
|
from typing import Optional
|
||||||
|
from aiogram.types import Message, CallbackQuery
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
from .emoji_mapping_service import EmojiMappingService
|
||||||
|
|
||||||
|
|
||||||
|
class EmojiMessageHelper:
|
||||||
|
"""Помощник для работы с эмодзи в сообщениях"""
|
||||||
|
|
||||||
|
def __init__(self, session: AsyncSession):
|
||||||
|
self.service = EmojiMappingService(session)
|
||||||
|
|
||||||
|
async def process_text_before_send(self, text: str) -> str:
|
||||||
|
"""
|
||||||
|
Обработать текст перед отправкой - заменить эмодзи на их ID
|
||||||
|
|
||||||
|
Args:
|
||||||
|
text: Текст сообщения
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Обработанный текст с заменой эмодзи на ID
|
||||||
|
"""
|
||||||
|
return await self.service.replace_emojis_in_text(text)
|
||||||
|
|
||||||
|
async def process_text_after_receive(self, text: str) -> str:
|
||||||
|
"""
|
||||||
|
Обработать текст после получения - восстановить эмодзи из ID
|
||||||
|
|
||||||
|
Args:
|
||||||
|
text: Текст с ID эмодзи
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Текст с восстановленными эмодзи
|
||||||
|
"""
|
||||||
|
return await self.service.restore_emojis_in_text(text)
|
||||||
|
|
||||||
|
|
||||||
|
async def get_emoji_aware_text(session: AsyncSession, text: str) -> str:
|
||||||
|
"""
|
||||||
|
Удобная функция для получения эмодзи-оптимизированного текста
|
||||||
|
|
||||||
|
Заменяет все известные эмодзи на их telegram_emoji_id для правильного отображения
|
||||||
|
|
||||||
|
Args:
|
||||||
|
session: Сессия БД
|
||||||
|
text: Исходный текст
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Текст с замененными эмодзи на их ID
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> text = "🎲 Выиграли! 🏆"
|
||||||
|
>>> processed = await get_emoji_aware_text(session, text)
|
||||||
|
>>> await message.answer(processed, parse_mode="HTML")
|
||||||
|
"""
|
||||||
|
helper = EmojiMessageHelper(session)
|
||||||
|
return await helper.process_text_before_send(text)
|
||||||
@@ -313,3 +313,25 @@ class BroadcastLog(Base):
|
|||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<BroadcastLog(id={self.id}, type={self.broadcast_type}, status={self.status})>"
|
return f"<BroadcastLog(id={self.id}, type={self.broadcast_type}, status={self.status})>"
|
||||||
|
|
||||||
|
|
||||||
|
class EmojiMapping(Base):
|
||||||
|
"""Маппинг эмодзи на их telegram_emoji_id для безопасной передачи в чат"""
|
||||||
|
__tablename__ = "emoji_mappings"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True)
|
||||||
|
emoji_text = Column(String(10), nullable=False, index=True) # Сам эмодзи (например, 🎲)
|
||||||
|
emoji_id = Column(String(255), nullable=False, unique=True, index=True) # telegram_emoji_id из API
|
||||||
|
admin_id = Column(Integer, ForeignKey("users.id"), nullable=False) # Кто добавил
|
||||||
|
description = Column(String(255), nullable=True) # Описание назначения эмодзи
|
||||||
|
created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))
|
||||||
|
last_used_at = Column(DateTime(timezone=True), nullable=True) # Последнее использование
|
||||||
|
|
||||||
|
# Связи
|
||||||
|
admin = relationship("User")
|
||||||
|
|
||||||
|
# Уникальность: один эмодзи от админа не может быть добавлен дважды
|
||||||
|
__table_args__ = (UniqueConstraint('emoji_text', 'admin_id', name='unique_emoji_per_admin'),)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<EmojiMapping(emoji={self.emoji_text}, emoji_id={self.emoji_id[:20]}...)>"
|
||||||
|
|||||||
93
src/core/premium_emoji.py
Normal file
93
src/core/premium_emoji.py
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
"""
|
||||||
|
Поддержка премиум эмодзи для ботов, созданных с премиум аккаунтов
|
||||||
|
Telegram Bot API поддерживает премиум эмодзи начиная с версии 7.0
|
||||||
|
|
||||||
|
Для использования премиум эмодзи:
|
||||||
|
1. Бот должен быть создан с премиум аккаунта
|
||||||
|
2. Использовать эмодзи напрямую в тексте сообщений
|
||||||
|
3. Использовать parse_mode="HTML" или parse_mode="Markdown"
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
|
from aiogram.types import MessageEntity, TextQuote
|
||||||
|
from aiogram.enums import MessageEntityType
|
||||||
|
|
||||||
|
|
||||||
|
class PremiumEmojiConfig:
|
||||||
|
"""Конфигурация поддержки премиум эмодзи"""
|
||||||
|
|
||||||
|
# Флаг, что бот может использовать премиум эмодзи
|
||||||
|
SUPPORTS_PREMIUM_EMOJI = True
|
||||||
|
|
||||||
|
# Стандартные parse_mode для автоматической поддержки эмодзи
|
||||||
|
DEFAULT_PARSE_MODE = "HTML" # Поддерживает эмодзи лучше чем Markdown
|
||||||
|
|
||||||
|
# Премиум эмодзи которые используются в приложении
|
||||||
|
PREMIUM_EMOJIS = {
|
||||||
|
# Розыгрыши
|
||||||
|
"🎲_premium": "🎲", # Если есть премиум версия
|
||||||
|
"🏆_premium": "🏆",
|
||||||
|
"🎯_premium": "🎯",
|
||||||
|
|
||||||
|
# Логины
|
||||||
|
"📱_premium": "📱",
|
||||||
|
"🔐_premium": "🔐",
|
||||||
|
|
||||||
|
# Статусы
|
||||||
|
"✅_premium": "✅",
|
||||||
|
"❌_premium": "❌",
|
||||||
|
"⏸_premium": "⏸️",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def supports_premium_emoji() -> bool:
|
||||||
|
"""Проверить поддерживает ли бот премиум эмодзи"""
|
||||||
|
return PremiumEmojiConfig.SUPPORTS_PREMIUM_EMOJI
|
||||||
|
|
||||||
|
|
||||||
|
def get_parse_mode() -> str:
|
||||||
|
"""Получить оптимальный parse_mode для поддержки эмодзи"""
|
||||||
|
return PremiumEmojiConfig.DEFAULT_PARSE_MODE
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_emoji_support(text: str) -> str:
|
||||||
|
"""
|
||||||
|
Убедиться что текст может быть отправлен с эмодзи
|
||||||
|
|
||||||
|
Args:
|
||||||
|
text: Текст сообщения
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Обработанный текст с поддержкой эмодзи
|
||||||
|
"""
|
||||||
|
# В Aiogram 3.16+ эмодзи автоматически поддерживаются при правильном parse_mode
|
||||||
|
# Эта функция может быть расширена для дополнительной обработки если нужно
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
async def send_message_with_emoji(
|
||||||
|
send_func,
|
||||||
|
text: str,
|
||||||
|
parse_mode: Optional[str] = None,
|
||||||
|
**kwargs
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Отправить сообщение с поддержкой премиум эмодзи
|
||||||
|
|
||||||
|
Args:
|
||||||
|
send_func: Функция отправки (message.answer, callback.message.edit_text и т.д.)
|
||||||
|
text: Текст сообщения
|
||||||
|
parse_mode: Parse mode (если None, использует default)
|
||||||
|
**kwargs: Дополнительные параметры
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Результат отправки сообщения
|
||||||
|
"""
|
||||||
|
if parse_mode is None:
|
||||||
|
parse_mode = get_parse_mode()
|
||||||
|
|
||||||
|
# Убедиться что текст может содержать эмодзи
|
||||||
|
text = ensure_emoji_support(text)
|
||||||
|
|
||||||
|
# Отправить сообщение
|
||||||
|
return await send_func(text, parse_mode=parse_mode, **kwargs)
|
||||||
42
src/core/telegram_config.py
Normal file
42
src/core/telegram_config.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
"""
|
||||||
|
Глобальная конфигурация для Telegram Bot API параметров
|
||||||
|
Включая поддержку премиум эмодзи
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Parse mode для всех сообщений
|
||||||
|
# HTML поддерживает премиум эмодзи лучше чем Markdown
|
||||||
|
GLOBAL_PARSE_MODE = "HTML"
|
||||||
|
|
||||||
|
# Доступные parse modes
|
||||||
|
PARSE_MODES = {
|
||||||
|
"HTML": "HTML",
|
||||||
|
"MARKDOWN": "Markdown",
|
||||||
|
"NONE": None
|
||||||
|
}
|
||||||
|
|
||||||
|
# Какой parse mode использовать для разных типов сообщений
|
||||||
|
MESSAGE_PARSE_MODES = {
|
||||||
|
"text_message": "HTML", # Обычные текстовые сообщения
|
||||||
|
"inline_keyboard": "HTML", # С inline клавиатурой
|
||||||
|
"reply_keyboard": "HTML", # С reply клавиатуре
|
||||||
|
"edit_message": "HTML", # Редактирование сообщения
|
||||||
|
"broadcast": "HTML", # Массовые рассылки
|
||||||
|
"admin_broadcast": "HTML", # Административные рассылки
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_parse_mode(message_type: str = "text_message") -> str:
|
||||||
|
"""
|
||||||
|
Получить parse_mode для типа сообщения
|
||||||
|
|
||||||
|
Args:
|
||||||
|
message_type: Тип сообщения (см. MESSAGE_PARSE_MODES)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Parse mode строка ("HTML", "Markdown", None)
|
||||||
|
"""
|
||||||
|
return MESSAGE_PARSE_MODES.get(message_type, GLOBAL_PARSE_MODE)
|
||||||
|
|
||||||
|
|
||||||
|
def get_global_parse_mode() -> str:
|
||||||
|
"""Получить глобальный parse mode"""
|
||||||
|
return GLOBAL_PARSE_MODE
|
||||||
273
src/handlers/admin_emoji_handlers.py
Normal file
273
src/handlers/admin_emoji_handlers.py
Normal file
@@ -0,0 +1,273 @@
|
|||||||
|
"""
|
||||||
|
Хендлеры для управления кастомными эмодзи админом
|
||||||
|
Админ отправляет эмодзи боту, бот сохраняет emoji_id и использует его в сообщениях в чатах
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
from aiogram import Router, F
|
||||||
|
from aiogram.types import Message, CallbackQuery, InlineKeyboardMarkup, InlineKeyboardButton
|
||||||
|
from aiogram.filters import Command, StateFilter
|
||||||
|
from aiogram.fsm.context import FSMContext
|
||||||
|
from aiogram.fsm.state import State, StatesGroup
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
from ..core.database import async_session_maker
|
||||||
|
from ..core.config import ADMIN_IDS
|
||||||
|
from ..core.emoji_mapping_service import EmojiMappingService
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
router = Router()
|
||||||
|
|
||||||
|
|
||||||
|
class EmojiStates(StatesGroup):
|
||||||
|
waiting_for_emoji = State()
|
||||||
|
waiting_for_description = State()
|
||||||
|
|
||||||
|
|
||||||
|
@router.message(Command("add_emoji"), StateFilter(None))
|
||||||
|
async def add_emoji_start(message: Message, state: FSMContext):
|
||||||
|
"""Начать процесс добавления нового эмодзи"""
|
||||||
|
if message.from_user.id not in ADMIN_IDS:
|
||||||
|
await message.answer("❌ Эта команда доступна только администраторам")
|
||||||
|
return
|
||||||
|
|
||||||
|
await message.answer(
|
||||||
|
"🎨 Отправьте эмодзи, который хотите зарегистрировать.\n\n"
|
||||||
|
"Бот получит его <code>emoji_id</code> и будет использовать этот ID "
|
||||||
|
"при отправке сообщений в чаты, чтобы эмодзи выглядел точно так же.",
|
||||||
|
parse_mode="HTML"
|
||||||
|
)
|
||||||
|
await state.set_state(EmojiStates.waiting_for_emoji)
|
||||||
|
|
||||||
|
|
||||||
|
@router.message(EmojiStates.waiting_for_emoji)
|
||||||
|
async def receive_emoji(message: Message, state: FSMContext):
|
||||||
|
"""Получить эмодзи от админа и сохранить его emoji_id"""
|
||||||
|
# Проверяем что это именно тект сообщение с эмодзи
|
||||||
|
if not message.text or len(message.text) > 10:
|
||||||
|
await message.answer(
|
||||||
|
"❌ Пожалуйста, отправьте просто эмодзи или маленький текст с эмодзи"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
emoji_text = message.text.strip()
|
||||||
|
|
||||||
|
# Проверяем что хотя бы один символ это эмодзи
|
||||||
|
has_emoji = any(ord(c) > 127 for c in emoji_text)
|
||||||
|
if not has_emoji:
|
||||||
|
await message.answer(
|
||||||
|
"❌ Текст не содержит эмодзи. Пожалуйста, отправьте эмодзи"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Извлекаем emoji_id из entities если это есть
|
||||||
|
emoji_id = None
|
||||||
|
|
||||||
|
# Проверяем есть ли entities в сообщении (custom emoji имеют свой entitytype)
|
||||||
|
if message.entities:
|
||||||
|
for entity in message.entities:
|
||||||
|
if entity.type == "custom_emoji":
|
||||||
|
# Получаем text с этим entity
|
||||||
|
emoji_id = entity.custom_emoji_id
|
||||||
|
break
|
||||||
|
|
||||||
|
# Если нет custom_emoji entity, пробуем другой способ
|
||||||
|
if not emoji_id:
|
||||||
|
# Используем встроенный способ Telegram - отправляем тестовое сообщение с этим эмодзи
|
||||||
|
# и смотрим entities
|
||||||
|
try:
|
||||||
|
# Отправляем сообщение с эмодзи обратно
|
||||||
|
test_msg = await message.answer(
|
||||||
|
f"Тестирую эмодзи: {emoji_text}",
|
||||||
|
parse_mode="HTML"
|
||||||
|
)
|
||||||
|
# Пытаемся получить emoji_id из реакции
|
||||||
|
# В Telegram для premium emoji нужно обращаться к API
|
||||||
|
# Но мы можем просто использовать сам emoji как ID - он уникален
|
||||||
|
emoji_id = emoji_text
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error testing emoji: {e}")
|
||||||
|
emoji_id = emoji_text
|
||||||
|
|
||||||
|
# Сохраняем в состояние
|
||||||
|
await state.update_data(emoji_text=emoji_text, emoji_id=emoji_id if emoji_id else emoji_text)
|
||||||
|
|
||||||
|
await message.answer(
|
||||||
|
f"✅ Получил эмодзи: <code>{emoji_text}</code>\n\n"
|
||||||
|
f"Теперь отправьте описание этого эмодзи (для чего его использовать?)\n"
|
||||||
|
f"Например: <code>Для лотереи</code>, <code>Для победителей</code> и т.д.",
|
||||||
|
parse_mode="HTML"
|
||||||
|
)
|
||||||
|
await state.set_state(EmojiStates.waiting_for_description)
|
||||||
|
|
||||||
|
|
||||||
|
@router.message(EmojiStates.waiting_for_description)
|
||||||
|
async def receive_emoji_description(message: Message, state: FSMContext):
|
||||||
|
"""Получить описание эмодзи и сохранить в БД"""
|
||||||
|
if not message.text:
|
||||||
|
await message.answer("❌ Пожалуйста, отправьте текстовое описание")
|
||||||
|
return
|
||||||
|
|
||||||
|
description = message.text.strip()
|
||||||
|
data = await state.get_data()
|
||||||
|
emoji_text = data.get("emoji_text")
|
||||||
|
emoji_id = data.get("emoji_id")
|
||||||
|
|
||||||
|
# Сохраняем в БД
|
||||||
|
async with async_session_maker() as session:
|
||||||
|
emoji_service = EmojiMappingService(session)
|
||||||
|
|
||||||
|
# Проверяем не существует ли уже такой эмодзи
|
||||||
|
existing = await emoji_service.get_emoji_by_text(emoji_text, message.from_user.id)
|
||||||
|
if existing:
|
||||||
|
await message.answer(
|
||||||
|
f"⚠️ Вы уже зарегистрировали этот эмодзи: {emoji_text}\n"
|
||||||
|
f"Описание: <code>{existing.description}</code>",
|
||||||
|
parse_mode="HTML"
|
||||||
|
)
|
||||||
|
await state.clear()
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
emoji_mapping = await emoji_service.register_emoji(
|
||||||
|
emoji_text=emoji_text,
|
||||||
|
emoji_id=emoji_id,
|
||||||
|
admin_id=message.from_user.id,
|
||||||
|
description=description
|
||||||
|
)
|
||||||
|
|
||||||
|
await message.answer(
|
||||||
|
f"✅ <b>Эмодзи успешно зарегистрировано!</b>\n\n"
|
||||||
|
f"Эмодзи: <code>{emoji_text}</code>\n"
|
||||||
|
f"Описание: <code>{description}</code>\n"
|
||||||
|
f"ID: <code>{emoji_id[:50]}</code>...\n\n"
|
||||||
|
f"Теперь это эмодзи будет автоматически использоваться в сообщениях бота.",
|
||||||
|
parse_mode="HTML"
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error registering emoji: {e}")
|
||||||
|
await message.answer(
|
||||||
|
f"❌ Ошибка при сохранении эмодзи: {str(e)}",
|
||||||
|
parse_mode="HTML"
|
||||||
|
)
|
||||||
|
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
|
|
||||||
|
@router.message(Command("my_emojis"))
|
||||||
|
async def list_my_emojis(message: Message):
|
||||||
|
"""Показать все эмодзи, добавленные этим админом"""
|
||||||
|
if message.from_user.id not in ADMIN_IDS:
|
||||||
|
await message.answer("❌ Эта команда доступна только администраторам")
|
||||||
|
return
|
||||||
|
|
||||||
|
async with async_session_maker() as session:
|
||||||
|
emoji_service = EmojiMappingService(session)
|
||||||
|
emojis = await emoji_service.get_all_emoji_by_admin(message.from_user.id)
|
||||||
|
|
||||||
|
if not emojis:
|
||||||
|
await message.answer(
|
||||||
|
"📭 Вы еще не добавили ни один эмодзи.\n\n"
|
||||||
|
"Используйте /add_emoji чтобы добавить новый эмодзи"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
text = "🎨 <b>Ваши зарегистрированные эмодзи:</b>\n\n"
|
||||||
|
for emoji in emojis:
|
||||||
|
text += (
|
||||||
|
f"<code>{emoji.emoji_text}</code> — {emoji.description}\n"
|
||||||
|
f" ID: <code>{emoji.emoji_id[:30]}</code>...\n"
|
||||||
|
f" Добавлено: <code>{emoji.created_at.strftime('%d.%m.%Y %H:%M')}</code>\n\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
await message.answer(text, parse_mode="HTML")
|
||||||
|
|
||||||
|
|
||||||
|
@router.message(Command("all_emojis"))
|
||||||
|
async def list_all_emojis(message: Message):
|
||||||
|
"""Показать все зарегистрированные эмодзи (для всех админов)"""
|
||||||
|
if message.from_user.id not in ADMIN_IDS:
|
||||||
|
await message.answer("❌ Эта команда доступна только администраторам")
|
||||||
|
return
|
||||||
|
|
||||||
|
async with async_session_maker() as session:
|
||||||
|
emoji_service = EmojiMappingService(session)
|
||||||
|
emojis = await emoji_service.get_all_emojis()
|
||||||
|
|
||||||
|
if not emojis:
|
||||||
|
await message.answer(
|
||||||
|
"📭 Нет зарегистрированных эмодзи в системе"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
text = "🎨 <b>Все зарегистрированные эмодзи в системе:</b>\n\n"
|
||||||
|
for emoji in emojis:
|
||||||
|
text += (
|
||||||
|
f"<code>{emoji.emoji_text}</code> — {emoji.description}\n"
|
||||||
|
f" Админ: <code>{emoji.admin.first_name or 'Unknown'}</code> "
|
||||||
|
f"(ID: {emoji.admin_id})\n"
|
||||||
|
f" Добавлено: <code>{emoji.created_at.strftime('%d.%m.%Y %H:%M')}</code>\n\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
await message.answer(text, parse_mode="HTML")
|
||||||
|
|
||||||
|
|
||||||
|
@router.message(Command("delete_emoji"))
|
||||||
|
async def delete_emoji_start(message: Message, state: FSMContext):
|
||||||
|
"""Удалить эмодзи"""
|
||||||
|
if message.from_user.id not in ADMIN_IDS:
|
||||||
|
await message.answer("❌ Эта команда доступна только администраторам")
|
||||||
|
return
|
||||||
|
|
||||||
|
async with async_session_maker() as session:
|
||||||
|
emoji_service = EmojiMappingService(session)
|
||||||
|
emojis = await emoji_service.get_all_emoji_by_admin(message.from_user.id)
|
||||||
|
|
||||||
|
if not emojis:
|
||||||
|
await message.answer(
|
||||||
|
"📭 У вас нет зарегистрированных эмодзи"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Создаем клавиатуру для выбора эмодзи
|
||||||
|
buttons = []
|
||||||
|
for emoji in emojis:
|
||||||
|
buttons.append([
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text=f"{emoji.emoji_text} ({emoji.description})",
|
||||||
|
callback_data=f"delete_emoji_{emoji.emoji_id}"
|
||||||
|
)
|
||||||
|
])
|
||||||
|
|
||||||
|
kb = InlineKeyboardMarkup(inline_keyboard=buttons)
|
||||||
|
await message.answer(
|
||||||
|
"🗑️ Выберите эмодзи для удаления:",
|
||||||
|
reply_markup=kb
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(F.data.startswith("delete_emoji_"))
|
||||||
|
async def delete_emoji_confirm(callback: CallbackQuery):
|
||||||
|
"""Подтвердить удаление эмодзи"""
|
||||||
|
emoji_id = callback.data.replace("delete_emoji_", "")
|
||||||
|
|
||||||
|
async with async_session_maker() as session:
|
||||||
|
emoji_service = EmojiMappingService(session)
|
||||||
|
emoji = await emoji_service.get_emoji_by_id(emoji_id)
|
||||||
|
|
||||||
|
if not emoji:
|
||||||
|
await callback.answer("❌ Эмодзи не найден", show_alert=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
if emoji.admin_id != callback.from_user.id and callback.from_user.id not in ADMIN_IDS:
|
||||||
|
await callback.answer("❌ Вы не можете удалить эмодзи другого админа", show_alert=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
success = await emoji_service.delete_emoji(emoji_id)
|
||||||
|
if success:
|
||||||
|
await callback.answer(
|
||||||
|
f"✅ Эмодзи <code>{emoji.emoji_text}</code> удалено",
|
||||||
|
show_alert=True
|
||||||
|
)
|
||||||
|
await callback.message.delete()
|
||||||
|
else:
|
||||||
|
await callback.answer("❌ Ошибка при удалении эмодзи", show_alert=True)
|
||||||
@@ -113,7 +113,7 @@ admin_router = Router()
|
|||||||
|
|
||||||
|
|
||||||
def is_admin(user_id: int) -> bool:
|
def is_admin(user_id: int) -> bool:
|
||||||
"""Проверка прав администратора"""
|
"""Проверка прав администратора (быстрая проверка только .env)"""
|
||||||
return user_id in ADMIN_IDS
|
return user_id in ADMIN_IDS
|
||||||
|
|
||||||
|
|
||||||
@@ -122,6 +122,25 @@ def is_super_admin(user_id: int) -> bool:
|
|||||||
return user_id in ADMIN_IDS
|
return user_id in ADMIN_IDS
|
||||||
|
|
||||||
|
|
||||||
|
async def check_admin_access(user_id: int) -> bool:
|
||||||
|
"""
|
||||||
|
Асинхронная проверка доступа администратора.
|
||||||
|
Проверяет как главных администраторов (.env), так и назначенных (БД)
|
||||||
|
"""
|
||||||
|
# Сначала проверяем главных администраторов
|
||||||
|
if user_id in ADMIN_IDS:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Затем проверяем назначенных администраторов в БД
|
||||||
|
async with async_session_maker() as session:
|
||||||
|
from sqlalchemy import select
|
||||||
|
result = await session.execute(
|
||||||
|
select(User).where(User.telegram_id == user_id, User.is_admin == True)
|
||||||
|
)
|
||||||
|
user = result.scalar_one_or_none()
|
||||||
|
return user is not None
|
||||||
|
|
||||||
|
|
||||||
def get_admin_main_keyboard() -> InlineKeyboardMarkup:
|
def get_admin_main_keyboard() -> InlineKeyboardMarkup:
|
||||||
"""Главная админ-панель"""
|
"""Главная админ-панель"""
|
||||||
buttons = [
|
buttons = [
|
||||||
@@ -182,7 +201,7 @@ def get_winner_management_keyboard() -> InlineKeyboardMarkup:
|
|||||||
@admin_router.callback_query(F.data == "admin_panel")
|
@admin_router.callback_query(F.data == "admin_panel")
|
||||||
async def show_admin_panel(callback: CallbackQuery):
|
async def show_admin_panel(callback: CallbackQuery):
|
||||||
"""Показать админ-панель"""
|
"""Показать админ-панель"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -217,7 +236,7 @@ async def show_admin_panel(callback: CallbackQuery):
|
|||||||
@admin_router.callback_query(F.data == "admin_lotteries")
|
@admin_router.callback_query(F.data == "admin_lotteries")
|
||||||
async def show_lottery_management(callback: CallbackQuery):
|
async def show_lottery_management(callback: CallbackQuery):
|
||||||
"""Управление розыгрышами"""
|
"""Управление розыгрышами"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -236,7 +255,7 @@ async def start_create_lottery(callback: CallbackQuery, state: FSMContext):
|
|||||||
# Сразу отвечаем на callback
|
# Сразу отвечаем на callback
|
||||||
await callback.answer()
|
await callback.answer()
|
||||||
|
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
logging.warning(f"⚠️ Пользователь {callback.from_user.id} не является админом")
|
logging.warning(f"⚠️ Пользователь {callback.from_user.id} не является админом")
|
||||||
await callback.message.answer("❌ Недостаточно прав")
|
await callback.message.answer("❌ Недостаточно прав")
|
||||||
return
|
return
|
||||||
@@ -264,7 +283,7 @@ async def start_create_lottery(callback: CallbackQuery, state: FSMContext):
|
|||||||
@admin_router.message(StateFilter(AdminStates.lottery_title))
|
@admin_router.message(StateFilter(AdminStates.lottery_title))
|
||||||
async def process_lottery_title(message: Message, state: FSMContext):
|
async def process_lottery_title(message: Message, state: FSMContext):
|
||||||
"""Обработка названия розыгрыша (создание или редактирование)"""
|
"""Обработка названия розыгрыша (создание или редактирование)"""
|
||||||
if not is_admin(message.from_user.id):
|
if not await check_admin_access(message.from_user.id):
|
||||||
await message.answer("❌ Недостаточно прав")
|
await message.answer("❌ Недостаточно прав")
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -312,7 +331,7 @@ async def process_lottery_title(message: Message, state: FSMContext):
|
|||||||
@admin_router.message(StateFilter(AdminStates.lottery_description))
|
@admin_router.message(StateFilter(AdminStates.lottery_description))
|
||||||
async def process_lottery_description(message: Message, state: FSMContext):
|
async def process_lottery_description(message: Message, state: FSMContext):
|
||||||
"""Обработка описания розыгрыша (создание или редактирование)"""
|
"""Обработка описания розыгрыша (создание или редактирование)"""
|
||||||
if not is_admin(message.from_user.id):
|
if not await check_admin_access(message.from_user.id):
|
||||||
await message.answer("❌ Недостаточно прав")
|
await message.answer("❌ Недостаточно прав")
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -370,7 +389,7 @@ async def process_lottery_description(message: Message, state: FSMContext):
|
|||||||
@admin_router.message(StateFilter(AdminStates.lottery_prizes))
|
@admin_router.message(StateFilter(AdminStates.lottery_prizes))
|
||||||
async def process_lottery_prizes(message: Message, state: FSMContext):
|
async def process_lottery_prizes(message: Message, state: FSMContext):
|
||||||
"""Обработка призов розыгрыша (создание или редактирование)"""
|
"""Обработка призов розыгрыша (создание или редактирование)"""
|
||||||
if not is_admin(message.from_user.id):
|
if not await check_admin_access(message.from_user.id):
|
||||||
await message.answer("❌ Недостаточно прав")
|
await message.answer("❌ Недостаточно прав")
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -433,7 +452,7 @@ async def process_lottery_prizes(message: Message, state: FSMContext):
|
|||||||
@admin_router.callback_query(F.data == "confirm_create_lottery", StateFilter(AdminStates.lottery_confirm))
|
@admin_router.callback_query(F.data == "confirm_create_lottery", StateFilter(AdminStates.lottery_confirm))
|
||||||
async def confirm_create_lottery(callback: CallbackQuery, state: FSMContext):
|
async def confirm_create_lottery(callback: CallbackQuery, state: FSMContext):
|
||||||
"""Подтверждение создания розыгрыша"""
|
"""Подтверждение создания розыгрыша"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -468,7 +487,7 @@ async def confirm_create_lottery(callback: CallbackQuery, state: FSMContext):
|
|||||||
text,
|
text,
|
||||||
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
|
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
|
||||||
[InlineKeyboardButton(text="🎰 К управлению розыгрышами", callback_data="admin_lotteries")],
|
[InlineKeyboardButton(text="🎰 К управлению розыгрышами", callback_data="admin_lotteries")],
|
||||||
[InlineKeyboardButton(text="🏠 В главное меню", callback_data="back_to_main")]
|
[InlineKeyboardButton(text="🏠 Главная", callback_data="back_to_main")]
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -476,7 +495,7 @@ async def confirm_create_lottery(callback: CallbackQuery, state: FSMContext):
|
|||||||
@admin_router.callback_query(F.data == "admin_list_all_lotteries")
|
@admin_router.callback_query(F.data == "admin_list_all_lotteries")
|
||||||
async def list_all_lotteries(callback: CallbackQuery):
|
async def list_all_lotteries(callback: CallbackQuery):
|
||||||
"""Список всех розыгрышей"""
|
"""Список всех розыгрышей"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -526,7 +545,7 @@ async def list_all_lotteries(callback: CallbackQuery):
|
|||||||
@admin_router.callback_query(F.data.startswith("admin_lottery_detail_"))
|
@admin_router.callback_query(F.data.startswith("admin_lottery_detail_"))
|
||||||
async def show_lottery_detail(callback: CallbackQuery):
|
async def show_lottery_detail(callback: CallbackQuery):
|
||||||
"""Детальная информация о розыгрыше"""
|
"""Детальная информация о розыгрыше"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -609,7 +628,7 @@ async def show_lottery_detail(callback: CallbackQuery):
|
|||||||
@admin_router.callback_query(F.data == "admin_participants")
|
@admin_router.callback_query(F.data == "admin_participants")
|
||||||
async def show_participant_management(callback: CallbackQuery):
|
async def show_participant_management(callback: CallbackQuery):
|
||||||
"""Управление участниками"""
|
"""Управление участниками"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -623,7 +642,7 @@ async def show_participant_management(callback: CallbackQuery):
|
|||||||
@admin_router.callback_query(F.data.startswith("admin_participants_"))
|
@admin_router.callback_query(F.data.startswith("admin_participants_"))
|
||||||
async def show_lottery_participants(callback: CallbackQuery):
|
async def show_lottery_participants(callback: CallbackQuery):
|
||||||
"""Показать участников конкретного розыгрыша"""
|
"""Показать участников конкретного розыгрыша"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -675,7 +694,7 @@ async def show_lottery_participants(callback: CallbackQuery):
|
|||||||
@admin_router.callback_query(F.data == "admin_bulk_operations")
|
@admin_router.callback_query(F.data == "admin_bulk_operations")
|
||||||
async def show_bulk_operations_menu(callback: CallbackQuery):
|
async def show_bulk_operations_menu(callback: CallbackQuery):
|
||||||
"""Подменю массовых операций"""
|
"""Подменю массовых операций"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -702,7 +721,7 @@ async def show_bulk_operations_menu(callback: CallbackQuery):
|
|||||||
@admin_router.callback_query(F.data == "admin_add_participant")
|
@admin_router.callback_query(F.data == "admin_add_participant")
|
||||||
async def start_add_participant(callback: CallbackQuery, state: FSMContext):
|
async def start_add_participant(callback: CallbackQuery, state: FSMContext):
|
||||||
"""Начать добавление участника"""
|
"""Начать добавление участника"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -740,7 +759,7 @@ async def start_add_participant(callback: CallbackQuery, state: FSMContext):
|
|||||||
@admin_router.callback_query(F.data.startswith("admin_add_part_to_"))
|
@admin_router.callback_query(F.data.startswith("admin_add_part_to_"))
|
||||||
async def choose_user_to_add(callback: CallbackQuery, state: FSMContext):
|
async def choose_user_to_add(callback: CallbackQuery, state: FSMContext):
|
||||||
"""Выбор пользователя для добавления"""
|
"""Выбор пользователя для добавления"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -768,7 +787,7 @@ async def choose_user_to_add(callback: CallbackQuery, state: FSMContext):
|
|||||||
@admin_router.message(StateFilter(AdminStates.add_participant_user))
|
@admin_router.message(StateFilter(AdminStates.add_participant_user))
|
||||||
async def process_add_participant(message: Message, state: FSMContext):
|
async def process_add_participant(message: Message, state: FSMContext):
|
||||||
"""Обработка добавления участника"""
|
"""Обработка добавления участника"""
|
||||||
if not is_admin(message.from_user.id):
|
if not await check_admin_access(message.from_user.id):
|
||||||
await message.answer("❌ Недостаточно прав")
|
await message.answer("❌ Недостаточно прав")
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -823,7 +842,7 @@ async def process_add_participant(message: Message, state: FSMContext):
|
|||||||
@admin_router.callback_query(F.data == "admin_remove_participant")
|
@admin_router.callback_query(F.data == "admin_remove_participant")
|
||||||
async def remove_participant_start(callback: CallbackQuery):
|
async def remove_participant_start(callback: CallbackQuery):
|
||||||
"""Начало процесса удаления участника"""
|
"""Начало процесса удаления участника"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -858,7 +877,7 @@ async def remove_participant_start(callback: CallbackQuery):
|
|||||||
@admin_router.callback_query(F.data.startswith("admin_remove_part_from_"))
|
@admin_router.callback_query(F.data.startswith("admin_remove_part_from_"))
|
||||||
async def remove_participant_select_lottery(callback: CallbackQuery, state: FSMContext):
|
async def remove_participant_select_lottery(callback: CallbackQuery, state: FSMContext):
|
||||||
"""Выбор розыгрыша для удаления участника"""
|
"""Выбор розыгрыша для удаления участника"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -889,7 +908,7 @@ async def remove_participant_select_lottery(callback: CallbackQuery, state: FSMC
|
|||||||
@admin_router.message(StateFilter(AdminStates.remove_participant_user))
|
@admin_router.message(StateFilter(AdminStates.remove_participant_user))
|
||||||
async def process_remove_participant(message: Message, state: FSMContext):
|
async def process_remove_participant(message: Message, state: FSMContext):
|
||||||
"""Обработка удаления участника"""
|
"""Обработка удаления участника"""
|
||||||
if not is_admin(message.from_user.id):
|
if not await check_admin_access(message.from_user.id):
|
||||||
await message.answer("❌ Недостаточно прав")
|
await message.answer("❌ Недостаточно прав")
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -954,7 +973,7 @@ async def process_remove_participant(message: Message, state: FSMContext):
|
|||||||
@admin_router.callback_query(F.data == "admin_list_all_participants")
|
@admin_router.callback_query(F.data == "admin_list_all_participants")
|
||||||
async def list_all_participants(callback: CallbackQuery):
|
async def list_all_participants(callback: CallbackQuery):
|
||||||
"""Список всех участников"""
|
"""Список всех участников"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -1003,7 +1022,7 @@ async def list_all_participants(callback: CallbackQuery):
|
|||||||
@admin_router.callback_query(F.data == "admin_participants_report")
|
@admin_router.callback_query(F.data == "admin_participants_report")
|
||||||
async def generate_participants_report(callback: CallbackQuery):
|
async def generate_participants_report(callback: CallbackQuery):
|
||||||
"""Генерация отчета по участникам"""
|
"""Генерация отчета по участникам"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -1088,7 +1107,7 @@ async def generate_participants_report(callback: CallbackQuery):
|
|||||||
@admin_router.callback_query(F.data == "admin_export_participants")
|
@admin_router.callback_query(F.data == "admin_export_participants")
|
||||||
async def export_participants_data(callback: CallbackQuery):
|
async def export_participants_data(callback: CallbackQuery):
|
||||||
"""Экспорт данных участников"""
|
"""Экспорт данных участников"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -1141,7 +1160,7 @@ async def export_participants_data(callback: CallbackQuery):
|
|||||||
@admin_router.callback_query(F.data == "admin_search_participants")
|
@admin_router.callback_query(F.data == "admin_search_participants")
|
||||||
async def start_search_participants(callback: CallbackQuery, state: FSMContext):
|
async def start_search_participants(callback: CallbackQuery, state: FSMContext):
|
||||||
"""Начать поиск участников"""
|
"""Начать поиск участников"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -1164,7 +1183,7 @@ async def start_search_participants(callback: CallbackQuery, state: FSMContext):
|
|||||||
@admin_router.message(StateFilter(AdminStates.participant_search))
|
@admin_router.message(StateFilter(AdminStates.participant_search))
|
||||||
async def process_search_participants(message: Message, state: FSMContext):
|
async def process_search_participants(message: Message, state: FSMContext):
|
||||||
"""Обработка поиска участников"""
|
"""Обработка поиска участников"""
|
||||||
if not is_admin(message.from_user.id):
|
if not await check_admin_access(message.from_user.id):
|
||||||
await message.answer("❌ Недостаточно прав")
|
await message.answer("❌ Недостаточно прав")
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -1215,7 +1234,7 @@ async def process_search_participants(message: Message, state: FSMContext):
|
|||||||
@admin_router.callback_query(F.data == "admin_bulk_add_participant")
|
@admin_router.callback_query(F.data == "admin_bulk_add_participant")
|
||||||
async def start_bulk_add_participant(callback: CallbackQuery, state: FSMContext):
|
async def start_bulk_add_participant(callback: CallbackQuery, state: FSMContext):
|
||||||
"""Начать массовое добавление участников"""
|
"""Начать массовое добавление участников"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -1253,7 +1272,7 @@ async def start_bulk_add_participant(callback: CallbackQuery, state: FSMContext)
|
|||||||
@admin_router.callback_query(F.data.startswith("admin_bulk_add_to_"))
|
@admin_router.callback_query(F.data.startswith("admin_bulk_add_to_"))
|
||||||
async def choose_users_bulk_add(callback: CallbackQuery, state: FSMContext):
|
async def choose_users_bulk_add(callback: CallbackQuery, state: FSMContext):
|
||||||
"""Выбор пользователей для массового добавления"""
|
"""Выбор пользователей для массового добавления"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -1282,7 +1301,7 @@ async def choose_users_bulk_add(callback: CallbackQuery, state: FSMContext):
|
|||||||
@admin_router.message(StateFilter(AdminStates.add_participant_bulk))
|
@admin_router.message(StateFilter(AdminStates.add_participant_bulk))
|
||||||
async def process_bulk_add_participant(message: Message, state: FSMContext):
|
async def process_bulk_add_participant(message: Message, state: FSMContext):
|
||||||
"""Обработка массового добавления участников"""
|
"""Обработка массового добавления участников"""
|
||||||
if not is_admin(message.from_user.id):
|
if not await check_admin_access(message.from_user.id):
|
||||||
await message.answer("❌ Недостаточно прав")
|
await message.answer("❌ Недостаточно прав")
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -1336,7 +1355,7 @@ async def process_bulk_add_participant(message: Message, state: FSMContext):
|
|||||||
@admin_router.callback_query(F.data == "admin_bulk_remove_participant")
|
@admin_router.callback_query(F.data == "admin_bulk_remove_participant")
|
||||||
async def start_bulk_remove_participant(callback: CallbackQuery, state: FSMContext):
|
async def start_bulk_remove_participant(callback: CallbackQuery, state: FSMContext):
|
||||||
"""Начать массовое удаление участников"""
|
"""Начать массовое удаление участников"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -1384,7 +1403,7 @@ async def start_bulk_remove_participant(callback: CallbackQuery, state: FSMConte
|
|||||||
@admin_router.callback_query(F.data.startswith("admin_bulk_remove_from_"))
|
@admin_router.callback_query(F.data.startswith("admin_bulk_remove_from_"))
|
||||||
async def choose_users_bulk_remove(callback: CallbackQuery, state: FSMContext):
|
async def choose_users_bulk_remove(callback: CallbackQuery, state: FSMContext):
|
||||||
"""Выбор пользователей для массового удаления"""
|
"""Выбор пользователей для массового удаления"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -1413,7 +1432,7 @@ async def choose_users_bulk_remove(callback: CallbackQuery, state: FSMContext):
|
|||||||
@admin_router.message(StateFilter(AdminStates.remove_participant_bulk))
|
@admin_router.message(StateFilter(AdminStates.remove_participant_bulk))
|
||||||
async def process_bulk_remove_participant(message: Message, state: FSMContext):
|
async def process_bulk_remove_participant(message: Message, state: FSMContext):
|
||||||
"""Обработка массового удаления участников"""
|
"""Обработка массового удаления участников"""
|
||||||
if not is_admin(message.from_user.id):
|
if not await check_admin_access(message.from_user.id):
|
||||||
await message.answer("❌ Недостаточно прав")
|
await message.answer("❌ Недостаточно прав")
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -2044,7 +2063,7 @@ async def delete_lottery_execute(callback: CallbackQuery):
|
|||||||
@admin_router.callback_query(F.data == "admin_bulk_add_accounts")
|
@admin_router.callback_query(F.data == "admin_bulk_add_accounts")
|
||||||
async def start_bulk_add_accounts(callback: CallbackQuery, state: FSMContext):
|
async def start_bulk_add_accounts(callback: CallbackQuery, state: FSMContext):
|
||||||
"""Начать массовое добавление участников по номерам счетов"""
|
"""Начать массовое добавление участников по номерам счетов"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -2082,7 +2101,7 @@ async def start_bulk_add_accounts(callback: CallbackQuery, state: FSMContext):
|
|||||||
@admin_router.callback_query(F.data.startswith("admin_bulk_add_accounts_to_"))
|
@admin_router.callback_query(F.data.startswith("admin_bulk_add_accounts_to_"))
|
||||||
async def choose_accounts_bulk_add(callback: CallbackQuery, state: FSMContext):
|
async def choose_accounts_bulk_add(callback: CallbackQuery, state: FSMContext):
|
||||||
"""Выбор номеров счетов для массового добавления"""
|
"""Выбор номеров счетов для массового добавления"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -2113,7 +2132,7 @@ async def choose_accounts_bulk_add(callback: CallbackQuery, state: FSMContext):
|
|||||||
@admin_router.message(StateFilter(AdminStates.add_participant_bulk_accounts))
|
@admin_router.message(StateFilter(AdminStates.add_participant_bulk_accounts))
|
||||||
async def process_bulk_add_accounts(message: Message, state: FSMContext):
|
async def process_bulk_add_accounts(message: Message, state: FSMContext):
|
||||||
"""Обработка массового добавления участников по номерам счетов"""
|
"""Обработка массового добавления участников по номерам счетов"""
|
||||||
if not is_admin(message.from_user.id):
|
if not await check_admin_access(message.from_user.id):
|
||||||
await message.answer("❌ Недостаточно прав")
|
await message.answer("❌ Недостаточно прав")
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -2170,7 +2189,7 @@ async def process_bulk_add_accounts(message: Message, state: FSMContext):
|
|||||||
@admin_router.callback_query(F.data == "admin_bulk_remove_accounts")
|
@admin_router.callback_query(F.data == "admin_bulk_remove_accounts")
|
||||||
async def start_bulk_remove_accounts(callback: CallbackQuery, state: FSMContext):
|
async def start_bulk_remove_accounts(callback: CallbackQuery, state: FSMContext):
|
||||||
"""Начать массовое удаление участников по номерам счетов"""
|
"""Начать массовое удаление участников по номерам счетов"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -2208,7 +2227,7 @@ async def start_bulk_remove_accounts(callback: CallbackQuery, state: FSMContext)
|
|||||||
@admin_router.callback_query(F.data.startswith("admin_bulk_remove_accounts_from_"))
|
@admin_router.callback_query(F.data.startswith("admin_bulk_remove_accounts_from_"))
|
||||||
async def choose_accounts_bulk_remove(callback: CallbackQuery, state: FSMContext):
|
async def choose_accounts_bulk_remove(callback: CallbackQuery, state: FSMContext):
|
||||||
"""Выбор номеров счетов для массового удаления"""
|
"""Выбор номеров счетов для массового удаления"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -2238,7 +2257,7 @@ async def choose_accounts_bulk_remove(callback: CallbackQuery, state: FSMContext
|
|||||||
@admin_router.message(StateFilter(AdminStates.remove_participant_bulk_accounts))
|
@admin_router.message(StateFilter(AdminStates.remove_participant_bulk_accounts))
|
||||||
async def process_bulk_remove_accounts(message: Message, state: FSMContext):
|
async def process_bulk_remove_accounts(message: Message, state: FSMContext):
|
||||||
"""Обработка массового удаления участников по номерам счетов"""
|
"""Обработка массового удаления участников по номерам счетов"""
|
||||||
if not is_admin(message.from_user.id):
|
if not await check_admin_access(message.from_user.id):
|
||||||
await message.answer("❌ Недостаточно прав")
|
await message.answer("❌ Недостаточно прав")
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -2299,7 +2318,7 @@ async def process_bulk_remove_accounts(message: Message, state: FSMContext):
|
|||||||
@admin_router.callback_query(F.data == "admin_participants_by_lottery")
|
@admin_router.callback_query(F.data == "admin_participants_by_lottery")
|
||||||
async def show_participants_by_lottery(callback: CallbackQuery):
|
async def show_participants_by_lottery(callback: CallbackQuery):
|
||||||
"""Показать участников по розыгрышам"""
|
"""Показать участников по розыгрышам"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -2343,7 +2362,7 @@ async def show_participants_by_lottery(callback: CallbackQuery):
|
|||||||
@admin_router.callback_query(F.data == "admin_participants_report")
|
@admin_router.callback_query(F.data == "admin_participants_report")
|
||||||
async def show_participants_report(callback: CallbackQuery):
|
async def show_participants_report(callback: CallbackQuery):
|
||||||
"""Отчет по участникам"""
|
"""Отчет по участникам"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -2410,7 +2429,7 @@ async def show_participants_report(callback: CallbackQuery):
|
|||||||
@admin_router.callback_query(F.data == "admin_edit_lottery")
|
@admin_router.callback_query(F.data == "admin_edit_lottery")
|
||||||
async def start_edit_lottery(callback: CallbackQuery, state: FSMContext):
|
async def start_edit_lottery(callback: CallbackQuery, state: FSMContext):
|
||||||
"""Начать редактирование розыгрыша"""
|
"""Начать редактирование розыгрыша"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -2447,7 +2466,7 @@ async def start_edit_lottery(callback: CallbackQuery, state: FSMContext):
|
|||||||
@admin_router.callback_query(F.data.startswith("admin_edit_field_"))
|
@admin_router.callback_query(F.data.startswith("admin_edit_field_"))
|
||||||
async def handle_edit_field(callback: CallbackQuery, state: FSMContext):
|
async def handle_edit_field(callback: CallbackQuery, state: FSMContext):
|
||||||
"""Обработка выбора поля для редактирования"""
|
"""Обработка выбора поля для редактирования"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -2488,7 +2507,7 @@ async def handle_edit_field(callback: CallbackQuery, state: FSMContext):
|
|||||||
@admin_router.callback_query(F.data.startswith("admin_edit_"))
|
@admin_router.callback_query(F.data.startswith("admin_edit_"))
|
||||||
async def redirect_to_edit_lottery(callback: CallbackQuery, state: FSMContext):
|
async def redirect_to_edit_lottery(callback: CallbackQuery, state: FSMContext):
|
||||||
"""Редирект на редактирование розыгрыша из детального просмотра"""
|
"""Редирект на редактирование розыгрыша из детального просмотра"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -2509,7 +2528,7 @@ async def redirect_to_edit_lottery(callback: CallbackQuery, state: FSMContext):
|
|||||||
@admin_router.callback_query(F.data.startswith("admin_edit_lottery_select_"))
|
@admin_router.callback_query(F.data.startswith("admin_edit_lottery_select_"))
|
||||||
async def choose_edit_field(callback: CallbackQuery, state: FSMContext):
|
async def choose_edit_field(callback: CallbackQuery, state: FSMContext):
|
||||||
"""Выбор поля для редактирования"""
|
"""Выбор поля для редактирования"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -2545,7 +2564,7 @@ async def choose_edit_field(callback: CallbackQuery, state: FSMContext):
|
|||||||
@admin_router.callback_query(F.data.startswith("admin_toggle_active_"))
|
@admin_router.callback_query(F.data.startswith("admin_toggle_active_"))
|
||||||
async def toggle_lottery_active(callback: CallbackQuery, state: FSMContext):
|
async def toggle_lottery_active(callback: CallbackQuery, state: FSMContext):
|
||||||
"""Переключить активность розыгрыша"""
|
"""Переключить активность розыгрыша"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -2571,7 +2590,7 @@ async def toggle_lottery_active(callback: CallbackQuery, state: FSMContext):
|
|||||||
@admin_router.callback_query(F.data == "admin_finish_lottery")
|
@admin_router.callback_query(F.data == "admin_finish_lottery")
|
||||||
async def start_finish_lottery(callback: CallbackQuery):
|
async def start_finish_lottery(callback: CallbackQuery):
|
||||||
"""Завершить розыгрыш"""
|
"""Завершить розыгрыш"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -2609,7 +2628,7 @@ async def start_finish_lottery(callback: CallbackQuery):
|
|||||||
@admin_router.callback_query(F.data.startswith("admin_confirm_finish_"))
|
@admin_router.callback_query(F.data.startswith("admin_confirm_finish_"))
|
||||||
async def confirm_finish_lottery(callback: CallbackQuery):
|
async def confirm_finish_lottery(callback: CallbackQuery):
|
||||||
"""Подтвердить завершение розыгрыша"""
|
"""Подтвердить завершение розыгрыша"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -2638,7 +2657,7 @@ async def confirm_finish_lottery(callback: CallbackQuery):
|
|||||||
@admin_router.callback_query(F.data.startswith("admin_do_finish_"))
|
@admin_router.callback_query(F.data.startswith("admin_do_finish_"))
|
||||||
async def do_finish_lottery(callback: CallbackQuery):
|
async def do_finish_lottery(callback: CallbackQuery):
|
||||||
"""Выполнить завершение розыгрыша"""
|
"""Выполнить завершение розыгрыша"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -2669,7 +2688,7 @@ async def do_finish_lottery(callback: CallbackQuery):
|
|||||||
@admin_router.callback_query(F.data == "admin_delete_lottery")
|
@admin_router.callback_query(F.data == "admin_delete_lottery")
|
||||||
async def start_delete_lottery(callback: CallbackQuery):
|
async def start_delete_lottery(callback: CallbackQuery):
|
||||||
"""Удаление розыгрыша"""
|
"""Удаление розыгрыша"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -2709,7 +2728,7 @@ async def start_delete_lottery(callback: CallbackQuery):
|
|||||||
@admin_router.callback_query(F.data.startswith("admin_confirm_delete_"))
|
@admin_router.callback_query(F.data.startswith("admin_confirm_delete_"))
|
||||||
async def confirm_delete_lottery(callback: CallbackQuery):
|
async def confirm_delete_lottery(callback: CallbackQuery):
|
||||||
"""Подтвердить удаление розыгрыша"""
|
"""Подтвердить удаление розыгрыша"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -2741,7 +2760,7 @@ async def confirm_delete_lottery(callback: CallbackQuery):
|
|||||||
@admin_router.callback_query(F.data.startswith("admin_do_delete_"))
|
@admin_router.callback_query(F.data.startswith("admin_do_delete_"))
|
||||||
async def do_delete_lottery(callback: CallbackQuery):
|
async def do_delete_lottery(callback: CallbackQuery):
|
||||||
"""Выполнить удаление розыгрыша"""
|
"""Выполнить удаление розыгрыша"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -2778,7 +2797,7 @@ async def do_delete_lottery(callback: CallbackQuery):
|
|||||||
@admin_router.callback_query(F.data == "admin_winners")
|
@admin_router.callback_query(F.data == "admin_winners")
|
||||||
async def show_winner_management(callback: CallbackQuery):
|
async def show_winner_management(callback: CallbackQuery):
|
||||||
"""Управление победителями"""
|
"""Управление победителями"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -2792,7 +2811,7 @@ async def show_winner_management(callback: CallbackQuery):
|
|||||||
@admin_router.callback_query(F.data == "admin_set_manual_winner")
|
@admin_router.callback_query(F.data == "admin_set_manual_winner")
|
||||||
async def start_set_manual_winner(callback: CallbackQuery, state: FSMContext):
|
async def start_set_manual_winner(callback: CallbackQuery, state: FSMContext):
|
||||||
"""Начать установку ручного победителя"""
|
"""Начать установку ручного победителя"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -2836,7 +2855,7 @@ async def start_set_manual_winner(callback: CallbackQuery, state: FSMContext):
|
|||||||
@admin_router.callback_query(F.data.startswith("admin_set_winner_"))
|
@admin_router.callback_query(F.data.startswith("admin_set_winner_"))
|
||||||
async def handle_set_winner_from_lottery(callback: CallbackQuery, state: FSMContext):
|
async def handle_set_winner_from_lottery(callback: CallbackQuery, state: FSMContext):
|
||||||
"""Обработчик для кнопки 'Установить победителя' из карточки розыгрыша"""
|
"""Обработчик для кнопки 'Установить победителя' из карточки розыгрыша"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -2850,7 +2869,7 @@ async def handle_set_winner_from_lottery(callback: CallbackQuery, state: FSMCont
|
|||||||
@admin_router.callback_query(F.data.startswith("admin_choose_winner_lottery_"))
|
@admin_router.callback_query(F.data.startswith("admin_choose_winner_lottery_"))
|
||||||
async def choose_winner_place(callback: CallbackQuery, state: FSMContext):
|
async def choose_winner_place(callback: CallbackQuery, state: FSMContext):
|
||||||
"""Выбор места для победителя"""
|
"""Выбор места для победителя"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -2887,7 +2906,7 @@ async def choose_winner_place(callback: CallbackQuery, state: FSMContext):
|
|||||||
@admin_router.message(StateFilter(AdminStates.set_winner_place))
|
@admin_router.message(StateFilter(AdminStates.set_winner_place))
|
||||||
async def process_winner_place(message: Message, state: FSMContext):
|
async def process_winner_place(message: Message, state: FSMContext):
|
||||||
"""Обработка места победителя"""
|
"""Обработка места победителя"""
|
||||||
if not is_admin(message.from_user.id):
|
if not await check_admin_access(message.from_user.id):
|
||||||
await message.answer("❌ Недостаточно прав")
|
await message.answer("❌ Недостаточно прав")
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -2935,7 +2954,7 @@ async def process_winner_place(message: Message, state: FSMContext):
|
|||||||
@admin_router.message(StateFilter(AdminStates.set_winner_user))
|
@admin_router.message(StateFilter(AdminStates.set_winner_user))
|
||||||
async def process_winner_user(message: Message, state: FSMContext):
|
async def process_winner_user(message: Message, state: FSMContext):
|
||||||
"""Обработка пользователя-победителя (по ID, username или номеру счета)"""
|
"""Обработка пользователя-победителя (по ID, username или номеру счета)"""
|
||||||
if not is_admin(message.from_user.id):
|
if not await check_admin_access(message.from_user.id):
|
||||||
await message.answer("❌ Недостаточно прав")
|
await message.answer("❌ Недостаточно прав")
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -3035,7 +3054,7 @@ async def process_winner_user(message: Message, state: FSMContext):
|
|||||||
@admin_router.callback_query(F.data == "admin_list_winners")
|
@admin_router.callback_query(F.data == "admin_list_winners")
|
||||||
async def list_all_winners(callback: CallbackQuery):
|
async def list_all_winners(callback: CallbackQuery):
|
||||||
"""Список всех победителей"""
|
"""Список всех победителей"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -3102,7 +3121,7 @@ async def list_all_winners(callback: CallbackQuery):
|
|||||||
@admin_router.callback_query(F.data == "admin_edit_winner")
|
@admin_router.callback_query(F.data == "admin_edit_winner")
|
||||||
async def edit_winner_start(callback: CallbackQuery):
|
async def edit_winner_start(callback: CallbackQuery):
|
||||||
"""Начало редактирования победителя"""
|
"""Начало редактирования победителя"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -3148,7 +3167,7 @@ async def edit_winner_start(callback: CallbackQuery):
|
|||||||
@admin_router.callback_query(F.data.startswith("admin_edit_winner_lottery_"))
|
@admin_router.callback_query(F.data.startswith("admin_edit_winner_lottery_"))
|
||||||
async def edit_winner_select_place(callback: CallbackQuery, state: FSMContext):
|
async def edit_winner_select_place(callback: CallbackQuery, state: FSMContext):
|
||||||
"""Выбор места победителя для редактирования"""
|
"""Выбор места победителя для редактирования"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -3182,7 +3201,7 @@ async def edit_winner_select_place(callback: CallbackQuery, state: FSMContext):
|
|||||||
@admin_router.callback_query(F.data.startswith("admin_edit_winner_id_"))
|
@admin_router.callback_query(F.data.startswith("admin_edit_winner_id_"))
|
||||||
async def edit_winner_details(callback: CallbackQuery):
|
async def edit_winner_details(callback: CallbackQuery):
|
||||||
"""Показать детали победителя (пока просто информационное сообщение)"""
|
"""Показать детали победителя (пока просто информационное сообщение)"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -3225,7 +3244,7 @@ async def edit_winner_details(callback: CallbackQuery):
|
|||||||
@admin_router.callback_query(F.data == "admin_remove_winner")
|
@admin_router.callback_query(F.data == "admin_remove_winner")
|
||||||
async def remove_winner_start(callback: CallbackQuery):
|
async def remove_winner_start(callback: CallbackQuery):
|
||||||
"""Начало удаления победителя"""
|
"""Начало удаления победителя"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -3271,7 +3290,7 @@ async def remove_winner_start(callback: CallbackQuery):
|
|||||||
@admin_router.callback_query(F.data.startswith("admin_remove_winner_lottery_"))
|
@admin_router.callback_query(F.data.startswith("admin_remove_winner_lottery_"))
|
||||||
async def remove_winner_select_place(callback: CallbackQuery):
|
async def remove_winner_select_place(callback: CallbackQuery):
|
||||||
"""Выбор победителя для удаления"""
|
"""Выбор победителя для удаления"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -3305,7 +3324,7 @@ async def remove_winner_select_place(callback: CallbackQuery):
|
|||||||
@admin_router.callback_query(F.data.startswith("admin_confirm_remove_winner_"))
|
@admin_router.callback_query(F.data.startswith("admin_confirm_remove_winner_"))
|
||||||
async def confirm_remove_winner(callback: CallbackQuery):
|
async def confirm_remove_winner(callback: CallbackQuery):
|
||||||
"""Подтверждение удаления победителя"""
|
"""Подтверждение удаления победителя"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -3347,7 +3366,7 @@ async def confirm_remove_winner(callback: CallbackQuery):
|
|||||||
@admin_router.callback_query(F.data.startswith("admin_do_remove_winner_"))
|
@admin_router.callback_query(F.data.startswith("admin_do_remove_winner_"))
|
||||||
async def do_remove_winner(callback: CallbackQuery):
|
async def do_remove_winner(callback: CallbackQuery):
|
||||||
"""Выполнение удаления победителя"""
|
"""Выполнение удаления победителя"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -3393,7 +3412,7 @@ async def do_remove_winner(callback: CallbackQuery):
|
|||||||
@admin_router.callback_query(F.data == "admin_conduct_draw")
|
@admin_router.callback_query(F.data == "admin_conduct_draw")
|
||||||
async def choose_lottery_for_draw(callback: CallbackQuery):
|
async def choose_lottery_for_draw(callback: CallbackQuery):
|
||||||
"""Выбор розыгрыша для проведения"""
|
"""Выбор розыгрыша для проведения"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -3432,7 +3451,7 @@ async def choose_lottery_for_draw(callback: CallbackQuery):
|
|||||||
@admin_router.callback_query(F.data.regexp(r"^admin_conduct_\d+$"))
|
@admin_router.callback_query(F.data.regexp(r"^admin_conduct_\d+$"))
|
||||||
async def conduct_lottery_draw_confirm(callback: CallbackQuery):
|
async def conduct_lottery_draw_confirm(callback: CallbackQuery):
|
||||||
"""Запрос подтверждения проведения розыгрыша"""
|
"""Запрос подтверждения проведения розыгрыша"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -3490,7 +3509,7 @@ async def conduct_lottery_draw(callback: CallbackQuery):
|
|||||||
logger.info(f"🎯 conduct_lottery_draw HANDLER TRIGGERED! data={callback.data}, user={callback.from_user.id}")
|
logger.info(f"🎯 conduct_lottery_draw HANDLER TRIGGERED! data={callback.data}, user={callback.from_user.id}")
|
||||||
logger.info(f"conduct_lottery_draw вызван: callback.data={callback.data}, user_id={callback.from_user.id}")
|
logger.info(f"conduct_lottery_draw вызван: callback.data={callback.data}, user_id={callback.from_user.id}")
|
||||||
|
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -3582,7 +3601,7 @@ async def conduct_lottery_draw(callback: CallbackQuery):
|
|||||||
@admin_router.callback_query(F.data == "admin_stats")
|
@admin_router.callback_query(F.data == "admin_stats")
|
||||||
async def show_detailed_stats(callback: CallbackQuery):
|
async def show_detailed_stats(callback: CallbackQuery):
|
||||||
"""Подробная статистика"""
|
"""Подробная статистика"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -3658,7 +3677,7 @@ async def show_detailed_stats(callback: CallbackQuery):
|
|||||||
@admin_router.callback_query(F.data == "admin_settings")
|
@admin_router.callback_query(F.data == "admin_settings")
|
||||||
async def show_admin_settings(callback: CallbackQuery):
|
async def show_admin_settings(callback: CallbackQuery):
|
||||||
"""Настройки админ-панели"""
|
"""Настройки админ-панели"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -3689,7 +3708,7 @@ async def show_admin_settings(callback: CallbackQuery):
|
|||||||
@admin_router.callback_query(F.data == "admin_export_data")
|
@admin_router.callback_query(F.data == "admin_export_data")
|
||||||
async def export_data(callback: CallbackQuery):
|
async def export_data(callback: CallbackQuery):
|
||||||
"""Экспорт данных из системы"""
|
"""Экспорт данных из системы"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -3742,7 +3761,7 @@ async def export_data(callback: CallbackQuery):
|
|||||||
@admin_router.callback_query(F.data == "admin_cleanup")
|
@admin_router.callback_query(F.data == "admin_cleanup")
|
||||||
async def cleanup_old_data(callback: CallbackQuery):
|
async def cleanup_old_data(callback: CallbackQuery):
|
||||||
"""Очистка старых данных"""
|
"""Очистка старых данных"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -3763,7 +3782,7 @@ async def cleanup_old_data(callback: CallbackQuery):
|
|||||||
@admin_router.callback_query(F.data == "admin_cleanup_old_lotteries")
|
@admin_router.callback_query(F.data == "admin_cleanup_old_lotteries")
|
||||||
async def cleanup_old_lotteries(callback: CallbackQuery):
|
async def cleanup_old_lotteries(callback: CallbackQuery):
|
||||||
"""Очистка старых завершённых розыгрышей"""
|
"""Очистка старых завершённых розыгрышей"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -3811,7 +3830,7 @@ async def cleanup_old_lotteries(callback: CallbackQuery):
|
|||||||
@admin_router.callback_query(F.data == "admin_cleanup_inactive_users")
|
@admin_router.callback_query(F.data == "admin_cleanup_inactive_users")
|
||||||
async def cleanup_inactive_users(callback: CallbackQuery):
|
async def cleanup_inactive_users(callback: CallbackQuery):
|
||||||
"""Очистка неактивных пользователей"""
|
"""Очистка неактивных пользователей"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -3872,7 +3891,7 @@ async def cleanup_inactive_users(callback: CallbackQuery):
|
|||||||
@admin_router.callback_query(F.data == "admin_cleanup_old_participations")
|
@admin_router.callback_query(F.data == "admin_cleanup_old_participations")
|
||||||
async def cleanup_old_participations(callback: CallbackQuery):
|
async def cleanup_old_participations(callback: CallbackQuery):
|
||||||
"""Очистка старых участий"""
|
"""Очистка старых участий"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -3921,7 +3940,7 @@ async def cleanup_old_participations(callback: CallbackQuery):
|
|||||||
@admin_router.callback_query(F.data == "admin_system_info")
|
@admin_router.callback_query(F.data == "admin_system_info")
|
||||||
async def show_system_info(callback: CallbackQuery):
|
async def show_system_info(callback: CallbackQuery):
|
||||||
"""Системная информация"""
|
"""Системная информация"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -3951,7 +3970,7 @@ async def show_system_info(callback: CallbackQuery):
|
|||||||
@admin_router.callback_query(F.data == "admin_winner_display_settings")
|
@admin_router.callback_query(F.data == "admin_winner_display_settings")
|
||||||
async def show_winner_display_settings(callback: CallbackQuery, state: FSMContext):
|
async def show_winner_display_settings(callback: CallbackQuery, state: FSMContext):
|
||||||
"""Настройка отображения победителей для розыгрышей"""
|
"""Настройка отображения победителей для розыгрышей"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -3993,7 +4012,7 @@ async def show_winner_display_settings(callback: CallbackQuery, state: FSMContex
|
|||||||
@admin_router.callback_query(F.data.startswith("admin_set_display_"))
|
@admin_router.callback_query(F.data.startswith("admin_set_display_"))
|
||||||
async def choose_display_type(callback: CallbackQuery, state: FSMContext):
|
async def choose_display_type(callback: CallbackQuery, state: FSMContext):
|
||||||
"""Выбор типа отображения для конкретного розыгрыша"""
|
"""Выбор типа отображения для конкретного розыгрыша"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -4032,7 +4051,7 @@ async def apply_display_type(callback: CallbackQuery, state: FSMContext):
|
|||||||
|
|
||||||
logger.info(f"🎭 Попытка смены типа отображения. Callback data: {callback.data}")
|
logger.info(f"🎭 Попытка смены типа отображения. Callback data: {callback.data}")
|
||||||
|
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
logger.warning(f"🚫 Отказ в доступе пользователю {callback.from_user.id}")
|
logger.warning(f"🚫 Отказ в доступе пользователю {callback.from_user.id}")
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
@@ -4096,7 +4115,7 @@ async def apply_display_type(callback: CallbackQuery, state: FSMContext):
|
|||||||
@admin_router.callback_query(F.data == "admin_messages")
|
@admin_router.callback_query(F.data == "admin_messages")
|
||||||
async def show_messages_menu(callback: CallbackQuery):
|
async def show_messages_menu(callback: CallbackQuery):
|
||||||
"""Показать меню управления сообщениями"""
|
"""Показать меню управления сообщениями"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -4119,7 +4138,7 @@ async def show_messages_menu(callback: CallbackQuery):
|
|||||||
@admin_router.callback_query(F.data == "admin_messages_recent")
|
@admin_router.callback_query(F.data == "admin_messages_recent")
|
||||||
async def show_recent_messages(callback: CallbackQuery, page: int = 0):
|
async def show_recent_messages(callback: CallbackQuery, page: int = 0):
|
||||||
"""Показать последние сообщения"""
|
"""Показать последние сообщения"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -4168,7 +4187,7 @@ async def show_recent_messages(callback: CallbackQuery, page: int = 0):
|
|||||||
@admin_router.callback_query(F.data.startswith("admin_message_view_"))
|
@admin_router.callback_query(F.data.startswith("admin_message_view_"))
|
||||||
async def view_message(callback: CallbackQuery):
|
async def view_message(callback: CallbackQuery):
|
||||||
"""Просмотр конкретного сообщения"""
|
"""Просмотр конкретного сообщения"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -4254,7 +4273,7 @@ async def view_message(callback: CallbackQuery):
|
|||||||
@admin_router.callback_query(F.data.startswith("admin_message_delete_"))
|
@admin_router.callback_query(F.data.startswith("admin_message_delete_"))
|
||||||
async def delete_message(callback: CallbackQuery):
|
async def delete_message(callback: CallbackQuery):
|
||||||
"""Удалить сообщение пользователя"""
|
"""Удалить сообщение пользователя"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -4320,7 +4339,7 @@ async def delete_message(callback: CallbackQuery):
|
|||||||
@admin_router.callback_query(F.data.startswith("admin_messages_user_"))
|
@admin_router.callback_query(F.data.startswith("admin_messages_user_"))
|
||||||
async def show_user_messages(callback: CallbackQuery):
|
async def show_user_messages(callback: CallbackQuery):
|
||||||
"""Показать все сообщения конкретного пользователя"""
|
"""Показать все сообщения конкретного пользователя"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -4470,7 +4489,7 @@ async def _notify_all_participants_about_results(bot, session: AsyncSession, lot
|
|||||||
@admin_router.callback_query(F.data == "admin_export_users")
|
@admin_router.callback_query(F.data == "admin_export_users")
|
||||||
async def admin_export_users(callback: CallbackQuery):
|
async def admin_export_users(callback: CallbackQuery):
|
||||||
"""Экспорт всех пользователей в XLSX"""
|
"""Экспорт всех пользователей в XLSX"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Доступ запрещен", show_alert=True)
|
await callback.answer("❌ Доступ запрещен", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -4568,7 +4587,7 @@ async def admin_export_users(callback: CallbackQuery):
|
|||||||
@admin_router.callback_query(F.data == "admin_import_users")
|
@admin_router.callback_query(F.data == "admin_import_users")
|
||||||
async def admin_import_users_start(callback: CallbackQuery, state: FSMContext):
|
async def admin_import_users_start(callback: CallbackQuery, state: FSMContext):
|
||||||
"""Начать импорт пользователей"""
|
"""Начать импорт пользователей"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Доступ запрещен", show_alert=True)
|
await callback.answer("❌ Доступ запрещен", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -4601,7 +4620,7 @@ async def admin_import_users_start(callback: CallbackQuery, state: FSMContext):
|
|||||||
@admin_router.message(StateFilter(AdminStates.import_users_json), F.document)
|
@admin_router.message(StateFilter(AdminStates.import_users_json), F.document)
|
||||||
async def admin_import_users_process(message: Message, state: FSMContext):
|
async def admin_import_users_process(message: Message, state: FSMContext):
|
||||||
"""Обработка импорта пользователей из XLSX"""
|
"""Обработка импорта пользователей из XLSX"""
|
||||||
if not is_admin(message.from_user.id):
|
if not await check_admin_access(message.from_user.id):
|
||||||
return
|
return
|
||||||
|
|
||||||
# Проверяем формат файла
|
# Проверяем формат файла
|
||||||
@@ -4775,7 +4794,7 @@ async def admin_import_users_process(message: Message, state: FSMContext):
|
|||||||
@admin_router.callback_query(F.data == "admin_broadcast")
|
@admin_router.callback_query(F.data == "admin_broadcast")
|
||||||
async def admin_broadcast_menu(callback: CallbackQuery, state: FSMContext):
|
async def admin_broadcast_menu(callback: CallbackQuery, state: FSMContext):
|
||||||
"""Меню массовой рассылки"""
|
"""Меню массовой рассылки"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Доступ запрещен", show_alert=True)
|
await callback.answer("❌ Доступ запрещен", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -4822,7 +4841,7 @@ async def admin_broadcast_menu(callback: CallbackQuery, state: FSMContext):
|
|||||||
@admin_router.callback_query(F.data == "admin_broadcast_start")
|
@admin_router.callback_query(F.data == "admin_broadcast_start")
|
||||||
async def admin_broadcast_start(callback: CallbackQuery, state: FSMContext):
|
async def admin_broadcast_start(callback: CallbackQuery, state: FSMContext):
|
||||||
"""Выбор типа рассылки"""
|
"""Выбор типа рассылки"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Доступ запрещен", show_alert=True)
|
await callback.answer("❌ Доступ запрещен", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -4851,7 +4870,7 @@ async def admin_broadcast_start(callback: CallbackQuery, state: FSMContext):
|
|||||||
@admin_router.callback_query(F.data == "broadcast_type_direct")
|
@admin_router.callback_query(F.data == "broadcast_type_direct")
|
||||||
async def broadcast_type_direct(callback: CallbackQuery, state: FSMContext):
|
async def broadcast_type_direct(callback: CallbackQuery, state: FSMContext):
|
||||||
"""Рассылка в ЛС - запрос сообщения"""
|
"""Рассылка в ЛС - запрос сообщения"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Доступ запрещен", show_alert=True)
|
await callback.answer("❌ Доступ запрещен", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -4885,7 +4904,7 @@ async def broadcast_type_direct(callback: CallbackQuery, state: FSMContext):
|
|||||||
@admin_router.callback_query(F.data.startswith("broadcast_type_"))
|
@admin_router.callback_query(F.data.startswith("broadcast_type_"))
|
||||||
async def broadcast_type_channel_or_group(callback: CallbackQuery, state: FSMContext):
|
async def broadcast_type_channel_or_group(callback: CallbackQuery, state: FSMContext):
|
||||||
"""Выбор канала или группы для рассылки"""
|
"""Выбор канала или группы для рассылки"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Доступ запрещен", show_alert=True)
|
await callback.answer("❌ Доступ запрещен", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -4939,7 +4958,7 @@ async def broadcast_type_channel_or_group(callback: CallbackQuery, state: FSMCon
|
|||||||
@admin_router.callback_query(F.data.startswith("broadcast_select_channel_"))
|
@admin_router.callback_query(F.data.startswith("broadcast_select_channel_"))
|
||||||
async def broadcast_select_channel(callback: CallbackQuery, state: FSMContext):
|
async def broadcast_select_channel(callback: CallbackQuery, state: FSMContext):
|
||||||
"""Выбран канал/группа - запрос сообщения"""
|
"""Выбран канал/группа - запрос сообщения"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Доступ запрещен", show_alert=True)
|
await callback.answer("❌ Доступ запрещен", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -4984,7 +5003,7 @@ async def broadcast_select_channel(callback: CallbackQuery, state: FSMContext):
|
|||||||
@admin_router.message(StateFilter(AdminStates.broadcast_message), F.text | F.photo | F.video | F.document)
|
@admin_router.message(StateFilter(AdminStates.broadcast_message), F.text | F.photo | F.video | F.document)
|
||||||
async def admin_broadcast_send(message: Message, state: FSMContext):
|
async def admin_broadcast_send(message: Message, state: FSMContext):
|
||||||
"""Обработка и отправка рассылки"""
|
"""Обработка и отправка рассылки"""
|
||||||
if not is_admin(message.from_user.id):
|
if not await check_admin_access(message.from_user.id):
|
||||||
return
|
return
|
||||||
|
|
||||||
data = await state.get_data()
|
data = await state.get_data()
|
||||||
@@ -5127,7 +5146,7 @@ async def _broadcast_channel(message: Message, state: FSMContext, data: dict):
|
|||||||
@admin_router.callback_query(F.data == "admin_broadcast_channels")
|
@admin_router.callback_query(F.data == "admin_broadcast_channels")
|
||||||
async def admin_broadcast_channels_menu(callback: CallbackQuery, state: FSMContext):
|
async def admin_broadcast_channels_menu(callback: CallbackQuery, state: FSMContext):
|
||||||
"""Меню управления каналами"""
|
"""Меню управления каналами"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Доступ запрещен", show_alert=True)
|
await callback.answer("❌ Доступ запрещен", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -5171,7 +5190,7 @@ async def admin_broadcast_channels_menu(callback: CallbackQuery, state: FSMConte
|
|||||||
@admin_router.callback_query(F.data == "admin_broadcast_add_channel")
|
@admin_router.callback_query(F.data == "admin_broadcast_add_channel")
|
||||||
async def admin_broadcast_add_channel_start(callback: CallbackQuery, state: FSMContext):
|
async def admin_broadcast_add_channel_start(callback: CallbackQuery, state: FSMContext):
|
||||||
"""Начать добавление канала"""
|
"""Начать добавление канала"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Доступ запрещен", show_alert=True)
|
await callback.answer("❌ Доступ запрещен", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -5202,7 +5221,7 @@ async def admin_broadcast_add_channel_start(callback: CallbackQuery, state: FSMC
|
|||||||
@admin_router.message(StateFilter(AdminStates.broadcast_add_channel_id), F.text)
|
@admin_router.message(StateFilter(AdminStates.broadcast_add_channel_id), F.text)
|
||||||
async def admin_broadcast_add_channel_id(message: Message, state: FSMContext):
|
async def admin_broadcast_add_channel_id(message: Message, state: FSMContext):
|
||||||
"""Обработка ID канала"""
|
"""Обработка ID канала"""
|
||||||
if not is_admin(message.from_user.id):
|
if not await check_admin_access(message.from_user.id):
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -5268,7 +5287,7 @@ async def admin_broadcast_add_channel_id(message: Message, state: FSMContext):
|
|||||||
@admin_router.message(StateFilter(AdminStates.broadcast_add_channel_title), F.text)
|
@admin_router.message(StateFilter(AdminStates.broadcast_add_channel_title), F.text)
|
||||||
async def admin_broadcast_add_channel_description(message: Message, state: FSMContext):
|
async def admin_broadcast_add_channel_description(message: Message, state: FSMContext):
|
||||||
"""Обработка описания канала"""
|
"""Обработка описания канала"""
|
||||||
if not is_admin(message.from_user.id):
|
if not await check_admin_access(message.from_user.id):
|
||||||
return
|
return
|
||||||
|
|
||||||
data = await state.get_data()
|
data = await state.get_data()
|
||||||
@@ -5332,7 +5351,7 @@ async def admin_broadcast_add_channel_description(message: Message, state: FSMCo
|
|||||||
@admin_router.callback_query(F.data == "admin_broadcast_stats")
|
@admin_router.callback_query(F.data == "admin_broadcast_stats")
|
||||||
async def admin_broadcast_stats(callback: CallbackQuery, state: FSMContext):
|
async def admin_broadcast_stats(callback: CallbackQuery, state: FSMContext):
|
||||||
"""Статистика рассылок"""
|
"""Статистика рассылок"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Доступ запрещен", show_alert=True)
|
await callback.answer("❌ Доступ запрещен", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -5386,7 +5405,7 @@ async def admin_broadcast_stats(callback: CallbackQuery, state: FSMContext):
|
|||||||
@admin_router.callback_query(F.data == "admin_broadcast_inactive")
|
@admin_router.callback_query(F.data == "admin_broadcast_inactive")
|
||||||
async def admin_broadcast_inactive(callback: CallbackQuery, state: FSMContext):
|
async def admin_broadcast_inactive(callback: CallbackQuery, state: FSMContext):
|
||||||
"""Статистика по неактивным пользователям"""
|
"""Статистика по неактивным пользователям"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Доступ запрещен", show_alert=True)
|
await callback.answer("❌ Доступ запрещен", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -5434,7 +5453,7 @@ async def admin_broadcast_inactive(callback: CallbackQuery, state: FSMContext):
|
|||||||
@admin_router.callback_query(F.data == "admin_check_inactive_now")
|
@admin_router.callback_query(F.data == "admin_check_inactive_now")
|
||||||
async def admin_check_inactive_now(callback: CallbackQuery, state: FSMContext):
|
async def admin_check_inactive_now(callback: CallbackQuery, state: FSMContext):
|
||||||
"""Запустить проверку неактивных пользователей вручную"""
|
"""Запустить проверку неактивных пользователей вручную"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Доступ запрещен", show_alert=True)
|
await callback.answer("❌ Доступ запрещен", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -5467,7 +5486,7 @@ async def admin_check_inactive_now(callback: CallbackQuery, state: FSMContext):
|
|||||||
@admin_router.callback_query(F.data == "admin_users")
|
@admin_router.callback_query(F.data == "admin_users")
|
||||||
async def admin_users_menu(callback: CallbackQuery, state: FSMContext):
|
async def admin_users_menu(callback: CallbackQuery, state: FSMContext):
|
||||||
"""Меню управления пользователями"""
|
"""Меню управления пользователями"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Доступ запрещен", show_alert=True)
|
await callback.answer("❌ Доступ запрещен", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -5504,7 +5523,7 @@ async def admin_users_menu(callback: CallbackQuery, state: FSMContext):
|
|||||||
@admin_router.callback_query(F.data == "admin_users_search")
|
@admin_router.callback_query(F.data == "admin_users_search")
|
||||||
async def admin_users_search_prompt(callback: CallbackQuery, state: FSMContext):
|
async def admin_users_search_prompt(callback: CallbackQuery, state: FSMContext):
|
||||||
"""Запрос поискового запроса"""
|
"""Запрос поискового запроса"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Доступ запрещен", show_alert=True)
|
await callback.answer("❌ Доступ запрещен", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -5535,7 +5554,7 @@ async def admin_users_search_prompt(callback: CallbackQuery, state: FSMContext):
|
|||||||
@admin_router.message(AdminStates.user_management_search)
|
@admin_router.message(AdminStates.user_management_search)
|
||||||
async def admin_users_search_process(message: Message, state: FSMContext):
|
async def admin_users_search_process(message: Message, state: FSMContext):
|
||||||
"""Обработка поискового запроса"""
|
"""Обработка поискового запроса"""
|
||||||
if not is_admin(message.from_user.id):
|
if not await check_admin_access(message.from_user.id):
|
||||||
return
|
return
|
||||||
|
|
||||||
query = message.text.strip()
|
query = message.text.strip()
|
||||||
@@ -5603,7 +5622,7 @@ async def admin_users_search_process(message: Message, state: FSMContext):
|
|||||||
@admin_router.callback_query(F.data.startswith("admin_users_list:"))
|
@admin_router.callback_query(F.data.startswith("admin_users_list:"))
|
||||||
async def admin_users_list(callback: CallbackQuery, state: FSMContext):
|
async def admin_users_list(callback: CallbackQuery, state: FSMContext):
|
||||||
"""Список всех пользователей с пагинацией"""
|
"""Список всех пользователей с пагинацией"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Доступ запрещен", show_alert=True)
|
await callback.answer("❌ Доступ запрещен", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -5662,7 +5681,7 @@ async def admin_users_list(callback: CallbackQuery, state: FSMContext):
|
|||||||
@admin_router.callback_query(F.data.startswith("admin_users_banned:"))
|
@admin_router.callback_query(F.data.startswith("admin_users_banned:"))
|
||||||
async def admin_users_banned_list(callback: CallbackQuery, state: FSMContext):
|
async def admin_users_banned_list(callback: CallbackQuery, state: FSMContext):
|
||||||
"""Список заблокированных пользователей"""
|
"""Список заблокированных пользователей"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Доступ запрещен", show_alert=True)
|
await callback.answer("❌ Доступ запрещен", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -5726,7 +5745,7 @@ async def admin_users_banned_list(callback: CallbackQuery, state: FSMContext):
|
|||||||
@admin_router.callback_query(F.data.startswith("admin_user_view:"))
|
@admin_router.callback_query(F.data.startswith("admin_user_view:"))
|
||||||
async def admin_user_view(callback: CallbackQuery, state: FSMContext):
|
async def admin_user_view(callback: CallbackQuery, state: FSMContext):
|
||||||
"""Просмотр информации о пользователе"""
|
"""Просмотр информации о пользователе"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Доступ запрещен", show_alert=True)
|
await callback.answer("❌ Доступ запрещен", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -5770,7 +5789,7 @@ async def admin_user_view(callback: CallbackQuery, state: FSMContext):
|
|||||||
@admin_router.callback_query(F.data.startswith("admin_user_ban:"))
|
@admin_router.callback_query(F.data.startswith("admin_user_ban:"))
|
||||||
async def admin_user_ban(callback: CallbackQuery, state: FSMContext):
|
async def admin_user_ban(callback: CallbackQuery, state: FSMContext):
|
||||||
"""Заблокировать пользователя в чате"""
|
"""Заблокировать пользователя в чате"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Доступ запрещен", show_alert=True)
|
await callback.answer("❌ Доступ запрещен", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -5792,7 +5811,7 @@ async def admin_user_ban(callback: CallbackQuery, state: FSMContext):
|
|||||||
@admin_router.callback_query(F.data.startswith("admin_user_unban:"))
|
@admin_router.callback_query(F.data.startswith("admin_user_unban:"))
|
||||||
async def admin_user_unban(callback: CallbackQuery, state: FSMContext):
|
async def admin_user_unban(callback: CallbackQuery, state: FSMContext):
|
||||||
"""Разблокировать пользователя в чате"""
|
"""Разблокировать пользователя в чате"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not await check_admin_access(callback.from_user.id):
|
||||||
await callback.answer("❌ Доступ запрещен", show_alert=True)
|
await callback.answer("❌ Доступ запрещен", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ async def enter_chat(message: Message, state: FSMContext):
|
|||||||
|
|
||||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||||
[InlineKeyboardButton(text="🚪 Выйти из чата", callback_data="exit_chat")],
|
[InlineKeyboardButton(text="🚪 Выйти из чата", callback_data="exit_chat")],
|
||||||
[InlineKeyboardButton(text="🏠 В главное меню", callback_data="back_to_main")]
|
[InlineKeyboardButton(text="🏠 Главная", callback_data="back_to_main")]
|
||||||
])
|
])
|
||||||
|
|
||||||
# Обычная клавиатура для чата
|
# Обычная клавиатура для чата
|
||||||
@@ -118,7 +118,7 @@ async def exit_chat(message: Message, state: FSMContext):
|
|||||||
|
|
||||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||||
[InlineKeyboardButton(text="💬 Войти в чат", callback_data="enter_chat")],
|
[InlineKeyboardButton(text="💬 Войти в чат", callback_data="enter_chat")],
|
||||||
[InlineKeyboardButton(text="🏠 В главное меню", callback_data="back_to_main")]
|
[InlineKeyboardButton(text="🏠 Главная", callback_data="back_to_main")]
|
||||||
])
|
])
|
||||||
|
|
||||||
# Обычная клавиатура
|
# Обычная клавиатура
|
||||||
|
|||||||
@@ -20,9 +20,10 @@ def get_help_menu_keyboard() -> InlineKeyboardMarkup:
|
|||||||
buttons = [
|
buttons = [
|
||||||
[InlineKeyboardButton(text="📝 Регистрация", callback_data="help_registration")],
|
[InlineKeyboardButton(text="📝 Регистрация", callback_data="help_registration")],
|
||||||
[InlineKeyboardButton(text="🎰 Участие в розыгрышах", callback_data="help_lottery")],
|
[InlineKeyboardButton(text="🎰 Участие в розыгрышах", callback_data="help_lottery")],
|
||||||
|
[InlineKeyboardButton(text="📱 Мои логины", callback_data="help_logins")],
|
||||||
[InlineKeyboardButton(text="💬 Чат", callback_data="help_chat")],
|
[InlineKeyboardButton(text="💬 Чат", callback_data="help_chat")],
|
||||||
[InlineKeyboardButton(text="⚙️ Команды", callback_data="help_commands")],
|
[InlineKeyboardButton(text="⚙️ Команды", callback_data="help_commands")],
|
||||||
[InlineKeyboardButton(text="🏠 В главное меню", callback_data="back_to_main")]
|
[InlineKeyboardButton(text="🏠 Главная", callback_data="back_to_main")]
|
||||||
]
|
]
|
||||||
return InlineKeyboardMarkup(inline_keyboard=buttons)
|
return InlineKeyboardMarkup(inline_keyboard=buttons)
|
||||||
|
|
||||||
@@ -31,7 +32,7 @@ def get_back_to_help_keyboard() -> InlineKeyboardMarkup:
|
|||||||
"""Клавиатура возврата к справке"""
|
"""Клавиатура возврата к справке"""
|
||||||
buttons = [
|
buttons = [
|
||||||
[InlineKeyboardButton(text="◀️ Назад к справке", callback_data="help_main")],
|
[InlineKeyboardButton(text="◀️ Назад к справке", callback_data="help_main")],
|
||||||
[InlineKeyboardButton(text="🏠 В главное меню", callback_data="back_to_main")]
|
[InlineKeyboardButton(text="🏠 Главная", callback_data="back_to_main")]
|
||||||
]
|
]
|
||||||
return InlineKeyboardMarkup(inline_keyboard=buttons)
|
return InlineKeyboardMarkup(inline_keyboard=buttons)
|
||||||
|
|
||||||
@@ -56,6 +57,7 @@ async def show_help_main(message: Message, edit: bool = False):
|
|||||||
"Выберите интересующий вас раздел:\n\n"
|
"Выберите интересующий вас раздел:\n\n"
|
||||||
"📝 <b>Регистрация</b> - как зарегистрироваться в системе\n"
|
"📝 <b>Регистрация</b> - как зарегистрироваться в системе\n"
|
||||||
"🎰 <b>Участие в розыгрышах</b> - как участвовать и выигрывать\n"
|
"🎰 <b>Участие в розыгрышах</b> - как участвовать и выигрывать\n"
|
||||||
|
"📱 <b>Мои логины</b> - информация о ваших добавленных логинах\n"
|
||||||
"💬 <b>Чат</b> - общение с другими участниками\n"
|
"💬 <b>Чат</b> - общение с другими участниками\n"
|
||||||
"⚙️ <b>Команды</b> - список доступных команд"
|
"⚙️ <b>Команды</b> - список доступных команд"
|
||||||
)
|
)
|
||||||
@@ -137,6 +139,61 @@ async def help_lottery(callback: CallbackQuery):
|
|||||||
await callback.message.answer(text, reply_markup=keyboard, parse_mode="HTML")
|
await callback.message.answer(text, reply_markup=keyboard, parse_mode="HTML")
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(F.data == "help_logins")
|
||||||
|
async def help_logins(callback: CallbackQuery):
|
||||||
|
"""Справка по логинам пользователей"""
|
||||||
|
await callback.answer()
|
||||||
|
|
||||||
|
text = (
|
||||||
|
"📱 <b>Мои логины</b>\n\n"
|
||||||
|
"<b>Что это такое?</b>\n\n"
|
||||||
|
"В этом разделе вы всегда сможете найти свои добавленные логины в розыгрыши, "
|
||||||
|
"которые администратор указал для вас в системе.\n\n"
|
||||||
|
|
||||||
|
"<b>Какая информация показывается?</b>\n\n"
|
||||||
|
"Для каждого логина вы сможете увидеть:\n"
|
||||||
|
"🎲 <b>Активные розыгрыши</b> - розыгрыши в которых сейчас участвует логин\n"
|
||||||
|
"🏁 <b>Завершенные розыгрыши</b> - прошедшие розыгрыши:\n"
|
||||||
|
" 🏆 ВЫИГРАЛ - если логин победил (указано место)\n"
|
||||||
|
" ✗ Не выиграл - если логин не получил приз\n\n"
|
||||||
|
|
||||||
|
"⚠️ <b>Важное уточнение о статусе логинов:</b>\n\n"
|
||||||
|
"✅ <b>Зеленый (активный)</b> - логин участвует в новых розыгрышах\n"
|
||||||
|
"⏸️ <b>Серый (неактивный)</b> - логин не участвует в новых розыгрышах\n\n"
|
||||||
|
|
||||||
|
"Имейте в виду, что логины, которые участвовали в закрытых розыгрышах, "
|
||||||
|
"<b>не добавляются в новые розыгрыши</b>. В списке отображаются только те логины, "
|
||||||
|
"которые активны и соответствуют условиям текущих розыгрышей.\n\n"
|
||||||
|
|
||||||
|
"<b>Как это работает:</b>\n\n"
|
||||||
|
"1️⃣ Если у вас есть 100 логинов\n"
|
||||||
|
"2️⃣ 60 из них участвовали в прошедших/закрытых розыгрышах\n"
|
||||||
|
"3️⃣ Статус этих 60 логинов будет ⏸️ (неактивны)\n"
|
||||||
|
"4️⃣ Они не добавляются в новые розыгрыши\n"
|
||||||
|
"5️⃣ В новых розыгрышах участвуют только оставшиеся активные логины\n\n"
|
||||||
|
|
||||||
|
"<b>Как использовать:</b>\n\n"
|
||||||
|
"1️⃣ Откройте главное меню\n"
|
||||||
|
"2️⃣ Нажмите кнопку <i>\"Мои логины\"</i>\n"
|
||||||
|
"3️⃣ Вы увидите полный список всех ваших логинов с информацией\n\n"
|
||||||
|
|
||||||
|
"💡 <b>Совет:</b>\n"
|
||||||
|
"Если вы не видите ожидаемый логин в списке активных розыгрышей, "
|
||||||
|
"это может означать, что он уже участвовал в закрытых розыгрышах и помечен как неактивный. "
|
||||||
|
"Свяжитесь с администратором для уточнения.\n\n"
|
||||||
|
|
||||||
|
"🔄 <b>Обновление информации:</b>\n"
|
||||||
|
"Список обновляется автоматически при каждом открытии раздела."
|
||||||
|
)
|
||||||
|
|
||||||
|
keyboard = get_back_to_help_keyboard()
|
||||||
|
|
||||||
|
try:
|
||||||
|
await callback.message.edit_text(text, reply_markup=keyboard, parse_mode="HTML")
|
||||||
|
except:
|
||||||
|
await callback.message.answer(text, reply_markup=keyboard, parse_mode="HTML")
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(F.data == "help_chat")
|
@router.callback_query(F.data == "help_chat")
|
||||||
async def help_chat(callback: CallbackQuery):
|
async def help_chat(callback: CallbackQuery):
|
||||||
"""Справка по чату"""
|
"""Справка по чату"""
|
||||||
|
|||||||
@@ -30,6 +30,35 @@ def is_admin(user_id: int) -> bool:
|
|||||||
return user_id in ADMIN_IDS
|
return user_id in ADMIN_IDS
|
||||||
|
|
||||||
|
|
||||||
|
def format_sender_name(user: User, is_current_user: bool = False, current_user_is_admin: bool = False) -> str:
|
||||||
|
"""
|
||||||
|
Форматирует имя отправителя для отображения в чате
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user: Объект пользователя
|
||||||
|
is_current_user: Текущий ли это пользователь
|
||||||
|
current_user_is_admin: Админ ли текущий пользователь
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Отформатированное имя
|
||||||
|
"""
|
||||||
|
if is_current_user:
|
||||||
|
return "🔵 Вы"
|
||||||
|
|
||||||
|
# Если это администратор и текущий пользователь не админ - показываем "Админ"
|
||||||
|
if user.is_admin and not current_user_is_admin:
|
||||||
|
return "🔵 Админ"
|
||||||
|
|
||||||
|
# Формируем базовое имя (используем nickname из профиля)
|
||||||
|
name = user.nickname or user.first_name or f"@{user.username}" or "Unknown"
|
||||||
|
|
||||||
|
# Добавляем информацию о карте если пользователь админ и текущий юзер админ
|
||||||
|
if current_user_is_admin and user.club_card_number:
|
||||||
|
name += f" (карта: {user.club_card_number})"
|
||||||
|
|
||||||
|
return f"🔵 {name}"
|
||||||
|
|
||||||
|
|
||||||
@router.message(CaseInsensitiveCommand("chat"))
|
@router.message(CaseInsensitiveCommand("chat"))
|
||||||
async def show_chat_menu(message: Message, state: FSMContext):
|
async def show_chat_menu(message: Message, state: FSMContext):
|
||||||
"""
|
"""
|
||||||
@@ -106,7 +135,7 @@ async def select_recipient(callback: CallbackQuery, state: FSMContext):
|
|||||||
# Создаём кнопки с пользователями (по 1 на строку)
|
# Создаём кнопки с пользователями (по 1 на строку)
|
||||||
buttons = []
|
buttons = []
|
||||||
for user in users[:20]: # Ограничение 20 пользователей на странице
|
for user in users[:20]: # Ограничение 20 пользователей на странице
|
||||||
display_name = f"@{user.username}" if user.username else user.first_name
|
display_name = user.nickname or f"@{user.username}" or user.first_name or "Unknown"
|
||||||
if user.club_card_number:
|
if user.club_card_number:
|
||||||
display_name += f" (карта: {user.club_card_number})"
|
display_name += f" (карта: {user.club_card_number})"
|
||||||
|
|
||||||
@@ -162,14 +191,18 @@ async def start_conversation(callback: CallbackQuery, state: FSMContext):
|
|||||||
await state.update_data(recipient_id=recipient.id, recipient_telegram_id=recipient.telegram_id)
|
await state.update_data(recipient_id=recipient.id, recipient_telegram_id=recipient.telegram_id)
|
||||||
await state.set_state(P2PChatStates.chatting)
|
await state.set_state(P2PChatStates.chatting)
|
||||||
|
|
||||||
recipient_name = f"@{recipient.username}" if recipient.username else recipient.first_name
|
recipient_name = recipient.nickname or f"@{recipient.username}" or recipient.first_name or "Unknown"
|
||||||
|
|
||||||
text = f"💬 <b>Диалог с {recipient_name}</b>\n\n"
|
text = f"💬 <b>Диалог с {recipient_name}</b>\n\n"
|
||||||
|
|
||||||
if messages:
|
if messages:
|
||||||
text += "📝 <b>Последние сообщения:</b>\n\n"
|
text += "📝 <b>Последние сообщения:</b>\n\n"
|
||||||
for msg in reversed(messages[-5:]): # Последние 5 сообщений
|
for msg in reversed(messages[-5:]): # Последние 5 сообщений
|
||||||
sender_name = "Вы" if msg.sender_id == sender.id else recipient_name
|
# Определяем имя отправителя
|
||||||
|
is_current = msg.sender_id == sender.id
|
||||||
|
user_for_display = sender if is_current else recipient
|
||||||
|
sender_name = format_sender_name(user_for_display, is_current, is_admin(sender.telegram_id))
|
||||||
|
|
||||||
msg_text = msg.text[:50] + "..." if msg.text and len(msg.text) > 50 else (msg.text or f"[{msg.message_type}]")
|
msg_text = msg.text[:50] + "..." if msg.text and len(msg.text) > 50 else (msg.text or f"[{msg.message_type}]")
|
||||||
text += f"• {sender_name}: {msg_text}\n"
|
text += f"• {sender_name}: {msg_text}\n"
|
||||||
text += "\n"
|
text += "\n"
|
||||||
@@ -204,7 +237,7 @@ async def show_conversations(callback: CallbackQuery):
|
|||||||
last_name=callback.from_user.last_name
|
last_name=callback.from_user.last_name
|
||||||
)
|
)
|
||||||
|
|
||||||
conversations = await P2PMessageService.get_recent_conversations(session, user.id, limit=10)
|
conversations = await P2PMessageService.get_recent_conversations(session, sender.id, limit=10)
|
||||||
|
|
||||||
if not conversations:
|
if not conversations:
|
||||||
await callback.message.edit_text(
|
await callback.message.edit_text(
|
||||||
@@ -217,7 +250,7 @@ async def show_conversations(callback: CallbackQuery):
|
|||||||
|
|
||||||
buttons = []
|
buttons = []
|
||||||
for peer, last_msg, unread in conversations:
|
for peer, last_msg, unread in conversations:
|
||||||
peer_name = f"@{peer.username}" if peer.username else peer.first_name
|
peer_name = peer.nickname or f"@{peer.username}" or peer.first_name or "Unknown"
|
||||||
|
|
||||||
# Иконка в зависимости от непрочитанных
|
# Иконка в зависимости от непрочитанных
|
||||||
icon = "🔴" if unread > 0 else "💬"
|
icon = "🔴" if unread > 0 else "💬"
|
||||||
@@ -234,7 +267,11 @@ async def show_conversations(callback: CallbackQuery):
|
|||||||
callback_data=f"p2p:user:{peer.id}"
|
callback_data=f"p2p:user:{peer.id}"
|
||||||
)])
|
)])
|
||||||
|
|
||||||
text += f"{icon} <b>{peer_name}</b>\n"
|
text += f"{icon} <b>{peer_name}</b>"
|
||||||
|
# Показываем номер карты если есть
|
||||||
|
if peer.club_card_number:
|
||||||
|
text += f" (карта: {peer.club_card_number})"
|
||||||
|
text += "\n"
|
||||||
text += f" {preview}\n"
|
text += f" {preview}\n"
|
||||||
if unread > 0:
|
if unread > 0:
|
||||||
text += f" 📨 Непрочитанных: {unread}\n"
|
text += f" 📨 Непрочитанных: {unread}\n"
|
||||||
@@ -268,12 +305,53 @@ async def end_conversation(callback: CallbackQuery, state: FSMContext):
|
|||||||
async def back_to_menu(callback: CallbackQuery, state: FSMContext):
|
async def back_to_menu(callback: CallbackQuery, state: FSMContext):
|
||||||
"""Вернуться в главное меню"""
|
"""Вернуться в главное меню"""
|
||||||
await callback.answer()
|
await callback.answer()
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
# Имитируем команду /chat
|
async with async_session_maker() as session:
|
||||||
fake_message = callback.message
|
user = await UserService.get_or_create_user(
|
||||||
fake_message.from_user = callback.from_user
|
session,
|
||||||
|
callback.from_user.id,
|
||||||
|
username=callback.from_user.username,
|
||||||
|
first_name=callback.from_user.first_name,
|
||||||
|
last_name=callback.from_user.last_name
|
||||||
|
)
|
||||||
|
|
||||||
|
if not user:
|
||||||
|
await callback.message.edit_text("❌ Вы не зарегистрированы. Используйте /start")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Получаем количество непрочитанных сообщений
|
||||||
|
unread_count = await P2PMessageService.get_unread_count(session, user.id)
|
||||||
|
|
||||||
|
# Получаем последние диалоги
|
||||||
|
recent = await P2PMessageService.get_recent_conversations(session, user.id, limit=5)
|
||||||
|
|
||||||
await show_chat_menu(fake_message, state)
|
text = "💬 <b>Чат</b>\n\n"
|
||||||
|
|
||||||
|
if unread_count > 0:
|
||||||
|
text += f"📨 У вас <b>{unread_count}</b> непрочитанных сообщений\n\n"
|
||||||
|
|
||||||
|
text += "Выберите действие:"
|
||||||
|
|
||||||
|
buttons = [
|
||||||
|
[InlineKeyboardButton(
|
||||||
|
text="✉️ Написать пользователю",
|
||||||
|
callback_data="p2p:select_user"
|
||||||
|
)],
|
||||||
|
[InlineKeyboardButton(
|
||||||
|
text="📋 Мои диалоги",
|
||||||
|
callback_data="p2p:my_conversations"
|
||||||
|
)]
|
||||||
|
]
|
||||||
|
|
||||||
|
if recent:
|
||||||
|
text += "\n\n<b>Последние диалоги:</b>\n"
|
||||||
|
for peer, last_msg, unread in recent:
|
||||||
|
unread_badge = f" ({unread})" if unread > 0 else ""
|
||||||
|
text += f" • @{peer.username or peer.first_name}{unread_badge}\n"
|
||||||
|
|
||||||
|
kb = InlineKeyboardMarkup(inline_keyboard=buttons)
|
||||||
|
await callback.message.edit_text(text, reply_markup=kb, parse_mode="HTML")
|
||||||
|
|
||||||
|
|
||||||
# Обработчик сообщений в состоянии chatting
|
# Обработчик сообщений в состоянии chatting
|
||||||
@@ -301,7 +379,19 @@ async def handle_p2p_message(message: Message, state: FSMContext):
|
|||||||
first_name=message.from_user.first_name,
|
first_name=message.from_user.first_name,
|
||||||
last_name=message.from_user.last_name
|
last_name=message.from_user.last_name
|
||||||
)
|
)
|
||||||
sender_name = f"@{sender.username}" if sender.username else sender.first_name
|
|
||||||
|
# Получаем информацию о получателе для определения как подписать сообщение
|
||||||
|
recipient = await UserService.get_user_by_telegram_id(session, recipient_telegram_id)
|
||||||
|
|
||||||
|
# Формируем подпись сообщения для получателя
|
||||||
|
if sender.is_admin:
|
||||||
|
sender_name = "АДМИН"
|
||||||
|
else:
|
||||||
|
sender_name = sender.nickname or f"@{sender.username}" or sender.first_name or "Unknown"
|
||||||
|
|
||||||
|
# Добавляем карту если получатель админ
|
||||||
|
if recipient and recipient.is_admin and sender.club_card_number:
|
||||||
|
sender_name += f" (карта: {sender.club_card_number})"
|
||||||
|
|
||||||
# Определяем тип сообщения
|
# Определяем тип сообщения
|
||||||
message_type = "text"
|
message_type = "text"
|
||||||
@@ -326,28 +416,28 @@ async def handle_p2p_message(message: Message, state: FSMContext):
|
|||||||
if message_type == "text":
|
if message_type == "text":
|
||||||
sent = await message.bot.send_message(
|
sent = await message.bot.send_message(
|
||||||
recipient_telegram_id,
|
recipient_telegram_id,
|
||||||
f"💬 <b>Сообщение от {sender_name}:</b>\n\n{text}",
|
f"<b>{sender_name}</b>\n\n{text}",
|
||||||
parse_mode="HTML"
|
parse_mode="HTML"
|
||||||
)
|
)
|
||||||
elif message_type == "photo":
|
elif message_type == "photo":
|
||||||
sent = await message.bot.send_photo(
|
sent = await message.bot.send_photo(
|
||||||
recipient_telegram_id,
|
recipient_telegram_id,
|
||||||
photo=file_id,
|
photo=file_id,
|
||||||
caption=f"💬 <b>Фото от {sender_name}</b>\n\n{text or ''}" ,
|
caption=f"<b>{sender_name}</b>\n\n{text or ''}" ,
|
||||||
parse_mode="HTML"
|
parse_mode="HTML"
|
||||||
)
|
)
|
||||||
elif message_type == "video":
|
elif message_type == "video":
|
||||||
sent = await message.bot.send_video(
|
sent = await message.bot.send_video(
|
||||||
recipient_telegram_id,
|
recipient_telegram_id,
|
||||||
video=file_id,
|
video=file_id,
|
||||||
caption=f"💬 <b>Видео от {sender_name}</b>\n\n{text or ''}",
|
caption=f"<b>{sender_name}</b>\n\n{text or ''}",
|
||||||
parse_mode="HTML"
|
parse_mode="HTML"
|
||||||
)
|
)
|
||||||
elif message_type == "document":
|
elif message_type == "document":
|
||||||
sent = await message.bot.send_document(
|
sent = await message.bot.send_document(
|
||||||
recipient_telegram_id,
|
recipient_telegram_id,
|
||||||
document=file_id,
|
document=file_id,
|
||||||
caption=f"💬 <b>Документ от {sender_name}</b>\n\n{text or ''}",
|
caption=f"<b>{sender_name}</b>\n\n{text or ''}",
|
||||||
parse_mode="HTML"
|
parse_mode="HTML"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
from aiogram import Router, F
|
from aiogram import Router, F
|
||||||
from aiogram.types import Message, CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup
|
from aiogram.types import Message, CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup
|
||||||
from aiogram.filters import Command, StateFilter
|
from aiogram.filters import Command, StateFilter
|
||||||
|
from sqlalchemy import select
|
||||||
|
from sqlalchemy.orm import selectinload
|
||||||
|
|
||||||
from src.filters.case_insensitive import CaseInsensitiveCommand
|
from src.filters.case_insensitive import CaseInsensitiveCommand
|
||||||
from aiogram.fsm.context import FSMContext
|
from aiogram.fsm.context import FSMContext
|
||||||
@@ -11,6 +13,7 @@ import logging
|
|||||||
from src.core.database import async_session_maker
|
from src.core.database import async_session_maker
|
||||||
from src.core.registration_services import RegistrationService, AccountService
|
from src.core.registration_services import RegistrationService, AccountService
|
||||||
from src.core.services import UserService
|
from src.core.services import UserService
|
||||||
|
from src.core.models import Participation, Winner, Lottery
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
router = Router()
|
router = Router()
|
||||||
@@ -143,7 +146,20 @@ async def process_club_card(message: Message, state: FSMContext):
|
|||||||
@router.message(StateFilter(RegistrationStates.waiting_for_phone))
|
@router.message(StateFilter(RegistrationStates.waiting_for_phone))
|
||||||
async def process_phone(message: Message, state: FSMContext):
|
async def process_phone(message: Message, state: FSMContext):
|
||||||
"""Обработка номера телефона"""
|
"""Обработка номера телефона"""
|
||||||
phone = None if message.text.strip() == "-" else message.text.strip()
|
phone_input = message.text.strip()
|
||||||
|
|
||||||
|
# Проверяем, не отправил ли пользователь просто "-"
|
||||||
|
if phone_input == "-":
|
||||||
|
phone = None
|
||||||
|
else:
|
||||||
|
# Валидируем телефон: не должно быть пустых или некорректных значений
|
||||||
|
if not phone_input:
|
||||||
|
await message.answer(
|
||||||
|
"❌ Неверный номер телефона.\n\n"
|
||||||
|
"Пожалуйста, введите корректный номер или отправьте '-' чтобы пропустить."
|
||||||
|
)
|
||||||
|
return
|
||||||
|
phone = phone_input
|
||||||
|
|
||||||
data = await state.get_data()
|
data = await state.get_data()
|
||||||
club_card_number = data['club_card_number']
|
club_card_number = data['club_card_number']
|
||||||
@@ -168,12 +184,12 @@ async def process_phone(message: Message, state: FSMContext):
|
|||||||
"✅ Регистрация завершена!\n\n"
|
"✅ Регистрация завершена!\n\n"
|
||||||
f"🎭 Никнейм: {user.nickname}\n"
|
f"🎭 Никнейм: {user.nickname}\n"
|
||||||
f"🎫 Клубная карта: {user.club_card_number}\n"
|
f"🎫 Клубная карта: {user.club_card_number}\n"
|
||||||
f"🔑 Ваш код верификации: **{user.verification_code}**\n\n"
|
f"🔑 Ваш код верификации: <b>{user.verification_code}</b>\n\n"
|
||||||
"⚠️ Сохраните этот код! Он понадобится для подтверждения выигрыша.\n\n"
|
"⚠️ Сохраните этот код! Он понадобится для подтверждения выигрыша.\n\n"
|
||||||
"Теперь вы можете участвовать в розыгрышах!"
|
"Теперь вы можете участвовать в розыгрышах!"
|
||||||
)
|
)
|
||||||
|
|
||||||
await message.answer(text, parse_mode="Markdown")
|
await message.answer(text, parse_mode="HTML")
|
||||||
await state.clear()
|
await state.clear()
|
||||||
|
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
@@ -199,17 +215,17 @@ async def show_verification_code(message: Message):
|
|||||||
|
|
||||||
text = (
|
text = (
|
||||||
"🔑 Ваш код верификации:\n\n"
|
"🔑 Ваш код верификации:\n\n"
|
||||||
f"**{user.verification_code}**\n\n"
|
f"<code>{user.verification_code}</code>\n\n"
|
||||||
"Этот код используется для подтверждения выигрыша.\n"
|
"Этот код используется для подтверждения выигрыша.\n"
|
||||||
"Сообщите его администратору при получении приза."
|
"Сообщите его администратору при получении приза."
|
||||||
)
|
)
|
||||||
|
|
||||||
await message.answer(text, parse_mode="Markdown")
|
await message.answer(text, parse_mode="HTML")
|
||||||
|
|
||||||
|
|
||||||
@router.message(Command("my_accounts"))
|
@router.message(Command("my_accounts"))
|
||||||
async def show_user_accounts(message: Message):
|
async def show_user_accounts(message: Message):
|
||||||
"""Показать счета пользователя"""
|
"""Показать логины пользователя с информацией о розыгрышах"""
|
||||||
async with async_session_maker() as session:
|
async with async_session_maker() as session:
|
||||||
user = await UserService.get_user_by_telegram_id(session, message.from_user.id)
|
user = await UserService.get_user_by_telegram_id(session, message.from_user.id)
|
||||||
|
|
||||||
@@ -221,15 +237,69 @@ async def show_user_accounts(message: Message):
|
|||||||
|
|
||||||
if not accounts:
|
if not accounts:
|
||||||
await message.answer(
|
await message.answer(
|
||||||
"У вас пока нет привязанных счетов.\n\n"
|
"У вас пока нет привязанных логинов.\n\n"
|
||||||
"Счета добавляются администратором."
|
"Логины добавляются администратором."
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
text = f"💳 Ваши счета (Клубная карта: {user.club_card_number}):\n\n"
|
text = f"📱 <b>Ваши логины</b> (Клубная карта: {user.club_card_number})\n\n"
|
||||||
|
|
||||||
for i, account in enumerate(accounts, 1):
|
for i, account in enumerate(accounts, 1):
|
||||||
status = "✅" if account.is_active else "❌"
|
# Получаем participations для этого account с загруженными данными о lottery
|
||||||
text += f"{i}. {status} {account.account_number}\n"
|
participations = await session.execute(
|
||||||
|
select(Participation)
|
||||||
|
.where(Participation.account_id == account.id)
|
||||||
|
.options(selectinload(Participation.lottery))
|
||||||
|
)
|
||||||
|
participations = participations.scalars().all()
|
||||||
|
|
||||||
|
# Определяем статус логина
|
||||||
|
active_participations = [p for p in participations if not p.lottery.is_completed]
|
||||||
|
closed_participations = [p for p in participations if p.lottery.is_completed]
|
||||||
|
|
||||||
|
# Основная информация о логине
|
||||||
|
status_icon = "✅" if account.is_active and active_participations else "⏸️"
|
||||||
|
text += f"{i}. {status_icon} <b>{account.account_number}</b>\n"
|
||||||
|
|
||||||
|
if active_participations:
|
||||||
|
text += " 🎲 <b>Активные розыгрыши:</b>\n"
|
||||||
|
for p in active_participations[:5]: # Показываем не более 5
|
||||||
|
status = "🟢"
|
||||||
|
text += f" {status} {p.lottery.title}\n"
|
||||||
|
if len(active_participations) > 5:
|
||||||
|
text += f" ... и еще {len(active_participations) - 5}\n"
|
||||||
|
|
||||||
|
if closed_participations:
|
||||||
|
text += " 🏁 <b>Завершенные розыгрыши:</b>\n"
|
||||||
|
for p in closed_participations[:3]: # Показываем не более 3
|
||||||
|
# Проверяем, выиграл ли в этом розыгрыше
|
||||||
|
winner_result = await session.execute(
|
||||||
|
select(Winner)
|
||||||
|
.where(
|
||||||
|
(Winner.lottery_id == p.lottery_id) &
|
||||||
|
(Winner.account_number == account.account_number)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
winner = winner_result.scalar_one_or_none()
|
||||||
|
|
||||||
|
if winner:
|
||||||
|
text += f" 🏆 {p.lottery.title} - <b>ВЫИГРАЛ!</b> ({winner.place} место)\n"
|
||||||
|
else:
|
||||||
|
text += f" ✗ {p.lottery.title}\n"
|
||||||
|
|
||||||
|
if len(closed_participations) > 3:
|
||||||
|
text += f" ... и еще {len(closed_participations) - 3} закрытых\n"
|
||||||
|
|
||||||
|
if not participations:
|
||||||
|
text += " ℹ️ В розыгрышах не участвовал\n"
|
||||||
|
|
||||||
|
text += "\n"
|
||||||
|
|
||||||
await message.answer(text)
|
# Добавляем примечание о неактивных логинах
|
||||||
|
if any(not acc.is_active for acc in accounts):
|
||||||
|
text += "⏸️ - Логин не участвует в новых розыгрышах\n"
|
||||||
|
text += "✅ - Логин активен и может участвовать\n\n"
|
||||||
|
|
||||||
|
text += "💡 <b>Заметка:</b> Логины, участвовавшие в закрытых розыгрышах, не добавляются в новые розыгрыши."
|
||||||
|
|
||||||
|
await message.answer(text, parse_mode="HTML")
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ def get_main_reply_keyboard(is_admin: bool = False, is_registered: bool = False)
|
|||||||
|
|
||||||
# Первая строка - основные команды
|
# Первая строка - основные команды
|
||||||
row1 = [
|
row1 = [
|
||||||
KeyboardButton(text="🎰 Розыгрыши"),
|
KeyboardButton(text="💬 Чат"),
|
||||||
KeyboardButton(text="💬 Чат")
|
KeyboardButton(text="❓ Справка")
|
||||||
]
|
]
|
||||||
keyboard.append(row1)
|
keyboard.append(row1)
|
||||||
|
|
||||||
@@ -29,13 +29,13 @@ def get_main_reply_keyboard(is_admin: bool = False, is_registered: bool = False)
|
|||||||
|
|
||||||
if is_registered or is_admin:
|
if is_registered or is_admin:
|
||||||
row2.append(KeyboardButton(text="🔑 Мой код"))
|
row2.append(KeyboardButton(text="🔑 Мой код"))
|
||||||
row2.append(KeyboardButton(text="💳 Мои счета"))
|
row2.append(KeyboardButton(text="📱 Мои логины"))
|
||||||
|
|
||||||
if row2:
|
if row2:
|
||||||
keyboard.append(row2)
|
keyboard.append(row2)
|
||||||
|
|
||||||
# Третья строка - справка
|
# Третья строка - главная
|
||||||
row3 = [KeyboardButton(text="❓ Справка")]
|
row3 = [KeyboardButton(text="🏠 Главная")]
|
||||||
|
|
||||||
# Админские команды
|
# Админские команды
|
||||||
if is_admin:
|
if is_admin:
|
||||||
@@ -59,7 +59,7 @@ def get_chat_reply_keyboard() -> ReplyKeyboardMarkup:
|
|||||||
"""
|
"""
|
||||||
keyboard = [
|
keyboard = [
|
||||||
[KeyboardButton(text="🚪 Выйти из чата")],
|
[KeyboardButton(text="🚪 Выйти из чата")],
|
||||||
[KeyboardButton(text="🏠 Главное меню")]
|
[KeyboardButton(text="🏠 Главная")]
|
||||||
]
|
]
|
||||||
|
|
||||||
return ReplyKeyboardMarkup(
|
return ReplyKeyboardMarkup(
|
||||||
|
|||||||
Reference in New Issue
Block a user