Merge pull request 'Add custom emoji mapping system for premium emoji support' (#11) from v2_functions into master
All checks were successful
continuous-integration/drone/push Build is passing

Reviewed-on: #11
This commit is contained in:
2026-03-07 01:47:02 +00:00
8 changed files with 870 additions and 2 deletions

View 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)