Files

219 lines
7.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from __future__ import annotations
from collections.abc import Iterable
from aiogram import F, Router
from aiogram.enums import MessageEntityType
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.types import CallbackQuery, Message, MessageEntity
from aiogram.utils.keyboard import InlineKeyboardBuilder
from filters.admin import IsAdminFilter
from handlers.buttons import BACK
from ..panel.keyboard import AdminPanelCallback, build_admin_back_kb
from filters.admin import HasPermission
from filters.permissions import PERM_EMOJI
class AdminEmojiState(StatesGroup):
waiting_for_custom_emoji = State()
router = Router()
router.callback_query.filter(HasPermission(PERM_EMOJI))
router.message.filter(HasPermission(PERM_EMOJI))
def _build_back_to_emoji_menu() -> InlineKeyboardBuilder:
builder = InlineKeyboardBuilder()
builder.button(text=BACK, callback_data=AdminPanelCallback(action="emoji").pack())
builder.adjust(1)
return builder
def _extract_custom_emoji_ids(entities: Iterable[MessageEntity]) -> list[str]:
ids: list[str] = []
for ent in entities:
if ent.type == MessageEntityType.CUSTOM_EMOJI and ent.custom_emoji_id:
ids.append(ent.custom_emoji_id)
return ids
def _utf16_len(text: str) -> int:
return len(text.encode("utf-16-le")) // 2
@router.callback_query(AdminPanelCallback.filter(F.action == "emoji"), IsAdminFilter())
async def show_emoji_menu(callback_query: CallbackQuery, state: FSMContext):
await state.set_state(AdminEmojiState.waiting_for_custom_emoji)
example_id = "5201769509345588200"
marker = f"{{{{emoji:{example_id}}}}}"
preview_placeholder = "😀"
text = (
"Отправьте любое кастомное эмоджи — я верну его ID и покажу пример для текстов.\n\n"
"Пример:\n"
f"{preview_placeholder}"
)
entities: list[MessageEntity] = []
start = 0
while True:
marker_pos = text.find(marker, start)
if marker_pos == -1:
break
entities.append(
MessageEntity(
type=MessageEntityType.CODE,
offset=_utf16_len(text[:marker_pos]),
length=_utf16_len(marker),
)
)
start = marker_pos + len(marker)
preview_pos = text.find(preview_placeholder)
if preview_pos != -1:
entities.append(
MessageEntity(
type=MessageEntityType.CUSTOM_EMOJI,
offset=_utf16_len(text[:preview_pos]),
length=_utf16_len(preview_placeholder),
custom_emoji_id=example_id,
)
)
if hasattr(callback_query.message, "_original_edit_text"):
await callback_query.message._original_edit_text(
text=text,
entities=entities,
reply_markup=build_admin_back_kb("admin"),
parse_mode=None,
)
else:
await callback_query.message.edit_text(
text=text,
entities=entities,
reply_markup=build_admin_back_kb("admin"),
parse_mode=None,
)
@router.message(AdminEmojiState.waiting_for_custom_emoji, IsAdminFilter())
async def handle_custom_emoji_id(message: Message, state: FSMContext):
entities = list(message.entities or []) + list(message.caption_entities or [])
emoji_ids = _extract_custom_emoji_ids(entities)
if not emoji_ids:
await message.answer(
"❌ Не вижу кастомных эмоджи. Отправьте именно <b>кастомный эмоджи</b> из набора.",
reply_markup=_build_back_to_emoji_menu().as_markup(),
)
return
unique_ids: list[str] = []
for emoji_id in emoji_ids:
if emoji_id not in unique_ids:
unique_ids.append(emoji_id)
placeholder = "😀"
back_builder = _build_back_to_emoji_menu()
if len(unique_ids) == 1:
emoji_id = unique_ids[0]
marker = f"{{{{emoji:{emoji_id}}}}}"
example_send = f"Ты отправил: Привет, {marker} !"
example_recv = f"А получил: Привет, {placeholder} !"
instruction_text = (
"✅ ID кастомного эмоджи\n"
f"{emoji_id}\n\n"
"Вставляйте в файл текстов так:\n"
f"{marker}\n\n"
"Пример:\n"
f"{example_send}\n"
f"{example_recv}\n\n"
"⚠️ Условие: отображение кастомных эмоджи работает, если у владельца бота есть Telegram Premium."
)
preview_text = example_recv
preview_ids = [emoji_id]
code_markers = [marker]
else:
ids_text = "\n".join(f"{emoji_id}" for emoji_id in unique_ids)
markers_text = "\n".join(f"{{{{emoji:{emoji_id}}}}}" for emoji_id in unique_ids)
markers_inline = " ".join(f"{{{{emoji:{emoji_id}}}}}" for emoji_id in unique_ids)
example_send = f"Ты отправил: Привет, {markers_inline} !"
example_recv = "А получил: Привет, " + " ".join(placeholder for _ in unique_ids) + " !"
instruction_text = (
"✅ ID кастомных эмоджи\n"
f"{ids_text}\n\n"
"Вставляйте в файл текстов так:\n"
f"{markers_text}\n\n"
"Пример:\n"
f"{example_send}\n"
f"{example_recv}\n\n"
"⚠️ Условие: отображение кастомных эмоджи работает, если у владельца бота есть Telegram Premium."
)
preview_text = example_recv
preview_ids = unique_ids
code_markers = [f"{{{{emoji:{emoji_id}}}}}" for emoji_id in unique_ids]
full_text = instruction_text
entities: list[MessageEntity] = []
for marker in code_markers:
start = 0
while True:
pos = full_text.find(marker, start)
if pos == -1:
break
entities.append(
MessageEntity(
type=MessageEntityType.CODE,
offset=_utf16_len(full_text[:pos]),
length=_utf16_len(marker),
)
)
start = pos + len(marker)
for emoji_id in unique_ids:
start = 0
while True:
pos = full_text.find(emoji_id, start)
if pos == -1:
break
entities.append(
MessageEntity(
type=MessageEntityType.CODE,
offset=_utf16_len(full_text[:pos]),
length=_utf16_len(emoji_id),
)
)
start = pos + len(emoji_id)
preview_offset_base = _utf16_len(full_text[: full_text.index(preview_text)])
running_utf16 = 0
idx = 0
for ch in preview_text:
if ch == placeholder and idx < len(preview_ids):
entities.append(
MessageEntity(
type=MessageEntityType.CUSTOM_EMOJI,
offset=preview_offset_base + running_utf16,
length=_utf16_len(placeholder),
custom_emoji_id=str(preview_ids[idx]),
)
)
idx += 1
running_utf16 += _utf16_len(ch)
if hasattr(message, "_original_answer"):
await message._original_answer(
text=full_text,
entities=entities,
reply_markup=back_builder.as_markup(),
parse_mode=None,
)
else:
await message.answer(text=full_text, reply_markup=back_builder.as_markup(), parse_mode=None)
await state.clear()