Files
Solo_bot/utils/custom_emojis.py
T

911 lines
33 KiB
Python

from __future__ import annotations
import re
from typing import Any, Iterable
from aiogram import Bot
from aiogram.enums import MessageEntityType
from aiogram.types import MessageEntity
from logger import logger
_PLACEHOLDER_CACHE: dict[str, str] = {}
_BOT: Bot | None = None
_MARKER_RE = re.compile(r"\{emoji:(\d+)\}|\[emoji:(\d+)\]")
_CODE_BLOCK_RE = re.compile(r"<code[^>]*>(.*?)</code>", re.IGNORECASE | re.DOTALL)
_PRE_BLOCK_RE = re.compile(r"<pre[^>]*>(.*?)</pre>", re.IGNORECASE | re.DOTALL)
def _get_protected_ranges(text: str) -> list[tuple[int, int]]:
"""Return ranges inside <code> and <pre> tags to skip replacements."""
ranges: list[tuple[int, int]] = []
for match in _CODE_BLOCK_RE.finditer(text):
ranges.append((match.start(1), match.end(1)))
for match in _PRE_BLOCK_RE.finditer(text):
ranges.append((match.start(1), match.end(1)))
return ranges
def _is_in_ranges(pos: int, ranges: list[tuple[int, int]]) -> bool:
for start, end in ranges:
if start <= pos < end:
return True
return False
def _utf16_len(text: str) -> int:
"""Length of string in UTF-16 code units."""
return len(text.encode("utf-16-le")) // 2
async def _fetch_placeholder(emoji_id: str) -> str:
"""Resolve a custom emoji id to a visible placeholder emoji."""
if emoji_id in _PLACEHOLDER_CACHE:
return _PLACEHOLDER_CACHE[emoji_id]
if _BOT is None:
return "😀"
try:
stickers = await _BOT.get_custom_emoji_stickers(custom_emoji_ids=[emoji_id])
if stickers:
sticker = stickers[0]
placeholder = None
if getattr(sticker, "emoji", None):
placeholder = sticker.emoji
elif getattr(sticker, "alt", None):
placeholder = sticker.alt
if placeholder:
_PLACEHOLDER_CACHE[emoji_id] = placeholder
return placeholder
except Exception:
pass
return "😀"
async def _replace_markers(text: str) -> tuple[str, list[MessageEntity]]:
"""Replace markers with placeholders and build custom emoji entities."""
if not text:
return text, []
entities: list[MessageEntity] = []
result = text
protected_ranges = _get_protected_ranges(text)
matches = [m for m in _MARKER_RE.finditer(text) if not _is_in_ranges(m.start(), protected_ranges)]
for match in reversed(matches):
emoji_id = match.group(1) or match.group(2)
marker = match.group(0)
marker_pos = match.start()
placeholder = await _fetch_placeholder(emoji_id)
result = result[:marker_pos] + placeholder + result[marker_pos + len(marker) :]
offset = _utf16_len(result[:marker_pos])
length = _utf16_len(placeholder)
entities.insert(
0,
MessageEntity(
type=MessageEntityType.CUSTOM_EMOJI,
offset=offset,
length=length,
custom_emoji_id=str(emoji_id),
),
)
return result, entities
def _parse_html_entities(text: str) -> list[MessageEntity]:
"""Parse simple HTML tags to entities (bold/italic/etc, links)."""
entities: list[MessageEntity] = []
link_open = re.compile(r'<a href="([^"]+)"\s*>|<a href=\'([^\']+)\'\s*>')
link_close = re.compile(r"</a>")
link_stack: list[dict[str, Any]] = []
for match in link_open.finditer(text):
url = match.group(1) or match.group(2)
link_stack.append({"open_end": match.end(), "url": url})
for close in link_close.finditer(text):
if not link_stack:
continue
open_tag = link_stack.pop()
open_pos = open_tag["open_end"]
close_pos = close.start()
content = text[open_pos:close_pos]
entities.append(
MessageEntity(
type=MessageEntityType.TEXT_LINK,
offset=_utf16_len(text[:open_pos]),
length=_utf16_len(content),
url=open_tag["url"],
)
)
tag_map: list[tuple[str, str, MessageEntityType]] = [
(r"<b\s*>", r"</b>", MessageEntityType.BOLD),
(r"<strong\s*>", r"</strong>", MessageEntityType.BOLD),
(r"<i\s*>", r"</i>", MessageEntityType.ITALIC),
(r"<em\s*>", r"</em>", MessageEntityType.ITALIC),
(r"<u\s*>", r"</u>", MessageEntityType.UNDERLINE),
(r"<ins\s*>", r"</ins>", MessageEntityType.UNDERLINE),
(r"<s\s*>", r"</s>", MessageEntityType.STRIKETHROUGH),
(r"<strike\s*>", r"</strike>", MessageEntityType.STRIKETHROUGH),
(r"<del\s*>", r"</del>", MessageEntityType.STRIKETHROUGH),
(r"<code\s*>", r"</code>", MessageEntityType.CODE),
(r"<pre\s*>", r"</pre>", MessageEntityType.PRE),
(r"<blockquote\s*>", r"</blockquote>", MessageEntityType.BLOCKQUOTE),
]
for open_pat, close_pat, entity_type in tag_map:
for open in re.finditer(open_pat, text):
close = re.search(close_pat, text[open.end() :])
if not close:
continue
close_pos = open.end() + close.start()
content = text[open.end() : close_pos]
entities.append(
MessageEntity(
type=entity_type,
offset=_utf16_len(text[: open.end()]),
length=_utf16_len(content),
)
)
return entities
async def _process_text(
text: str, entities: list[MessageEntity] | None = None
) -> tuple[str, list[MessageEntity] | None]:
"""Apply custom emoji markers and merge entities."""
processed, custom_entities = await _replace_markers(text)
if not custom_entities:
return text, entities
html_entities: list[MessageEntity] = []
if "<" in processed and ">" in processed:
html_entities = _parse_html_entities(processed)
if not html_entities and not custom_entities and not entities:
return text, None
if html_entities:
plain = []
html_pos = 0
utf16_map: dict[int, int] = {}
plain_utf16 = 0
html_utf16 = 0
while html_pos < len(processed):
ch = processed[html_pos]
if ch == "<":
while html_pos < len(processed) and processed[html_pos] != ">":
ch = processed[html_pos]
ch_len = _utf16_len(ch)
for i in range(ch_len):
utf16_map[html_utf16 + i] = plain_utf16
html_utf16 += ch_len
html_pos += 1
if html_pos < len(processed):
ch = processed[html_pos]
ch_len = _utf16_len(ch)
for i in range(ch_len):
utf16_map[html_utf16 + i] = plain_utf16
html_utf16 += ch_len
html_pos += 1
continue
plain.append(ch)
ch_len = _utf16_len(ch)
for i in range(ch_len):
utf16_map[html_utf16 + i] = plain_utf16 + i
plain_utf16 += ch_len
html_utf16 += ch_len
html_pos += 1
def remap(offset: int) -> int:
if offset in utf16_map:
return utf16_map[offset]
keys = sorted(utf16_map.keys())
best = None
for key in keys:
if key <= offset:
best = key
else:
break
return utf16_map[best] if best is not None else offset
remapped_html: list[MessageEntity] = []
for ent in html_entities:
new_offset = remap(ent.offset)
end_offset = remap(ent.offset + ent.length)
data = ent.model_dump()
data["offset"] = new_offset
data["length"] = end_offset - new_offset
remapped_html.append(MessageEntity(**data))
remapped_custom: list[MessageEntity] = []
for ent in custom_entities:
data = ent.model_dump()
data["offset"] = remap(ent.offset)
remapped_custom.append(MessageEntity(**data))
merged: list[MessageEntity] = []
merged.extend(remapped_html)
merged.extend(remapped_custom)
if entities:
merged.extend(entities)
merged.sort(key=lambda e: e.offset)
return "".join(plain), merged
merged: list[MessageEntity] = []
merged.extend(custom_entities)
if entities:
merged.extend(entities)
merged.sort(key=lambda e: e.offset)
return processed, merged
def _set_parse_mode_none(kwargs: dict[str, Any]) -> None:
if kwargs.get("parse_mode") is not None:
kwargs["parse_mode"] = None
def patch_bot_methods() -> bool:
"""Patch Message methods to auto-handle custom emojis."""
global _BOT
try:
from aiogram.types import Message
from bot import bot
_BOT = bot
if not hasattr(Message, "_custom_emojis_patched"):
Message._custom_emojis_patched = True
Message._original_answer = Message.answer
Message._original_edit_text = Message.edit_text
Message._original_edit_caption = Message.edit_caption
Message._original_answer_photo = Message.answer_photo
Message._original_answer_video = Message.answer_video
Message._original_answer_animation = Message.answer_animation
Message._original_edit_media = Message.edit_media
async def patched_answer(self, text: str, entities: list[MessageEntity] | None = None, **kwargs):
processed, merged = await _process_text(text, entities)
if merged:
_set_parse_mode_none(kwargs)
return await self._original_answer(text=processed, entities=merged, **kwargs)
async def patched_edit_text(
self, text: str, entities: list[MessageEntity] | None = None, **kwargs
):
processed, merged = await _process_text(text, entities)
if merged:
_set_parse_mode_none(kwargs)
return await self._original_edit_text(text=processed, entities=merged, **kwargs)
async def patched_edit_caption(
self,
caption: str | None = None,
caption_entities: list[MessageEntity] | None = None,
**kwargs,
):
if not caption:
return await self._original_edit_caption(
caption=caption, caption_entities=caption_entities, **kwargs
)
processed, merged = await _process_text(caption, caption_entities)
if merged:
_set_parse_mode_none(kwargs)
return await self._original_edit_caption(
caption=processed, caption_entities=merged, **kwargs
)
async def patched_answer_photo(
self,
photo: Any,
caption: str | None = None,
caption_entities: list[MessageEntity] | None = None,
**kwargs,
):
if not caption:
return await self._original_answer_photo(
photo=photo, caption=caption, caption_entities=caption_entities, **kwargs
)
processed, merged = await _process_text(caption, caption_entities)
if merged:
_set_parse_mode_none(kwargs)
return await self._original_answer_photo(
photo=photo, caption=processed, caption_entities=merged, **kwargs
)
async def patched_answer_video(
self,
video: Any,
caption: str | None = None,
caption_entities: list[MessageEntity] | None = None,
**kwargs,
):
if not caption:
return await self._original_answer_video(
video=video, caption=caption, caption_entities=caption_entities, **kwargs
)
processed, merged = await _process_text(caption, caption_entities)
if merged:
_set_parse_mode_none(kwargs)
return await self._original_answer_video(
video=video, caption=processed, caption_entities=merged, **kwargs
)
async def patched_answer_animation(
self,
animation: Any,
caption: str | None = None,
caption_entities: list[MessageEntity] | None = None,
**kwargs,
):
if not caption:
return await self._original_answer_animation(
animation=animation,
caption=caption,
caption_entities=caption_entities,
**kwargs,
)
processed, merged = await _process_text(caption, caption_entities)
if merged:
_set_parse_mode_none(kwargs)
return await self._original_answer_animation(
animation=animation,
caption=processed,
caption_entities=merged,
**kwargs,
)
async def patched_edit_media(self, media: Any, **kwargs):
if hasattr(media, "caption") and media.caption:
processed, merged = await _process_text(
media.caption, getattr(media, "caption_entities", None)
)
media.caption = processed
if merged:
if hasattr(media, "parse_mode"):
media.parse_mode = None
kwargs["parse_mode"] = None
media.caption_entities = merged
return await self._original_edit_media(media=media, **kwargs)
Message.answer = patched_answer
Message.edit_text = patched_edit_text
Message.edit_caption = patched_edit_caption
Message.answer_photo = patched_answer_photo
Message.answer_video = patched_answer_video
Message.answer_animation = patched_answer_animation
Message.edit_media = patched_edit_media
return True
except Exception as e:
logger.error(f"[CustomEmojis] Patch failed: {e}", exc_info=True)
return False
def initialize_custom_emojis() -> bool:
"""Initialize custom emoji support."""
try:
return patch_bot_methods()
except Exception as e:
logger.error(f"[CustomEmojis] Init failed: {e}", exc_info=True)
return False
"""
Support custom Telegram emojis in bot texts.
Allows writing custom emoji IDs in texts via a special syntax.
"""
import re
from typing import Any
from aiogram import Bot
from aiogram.enums import MessageEntityType
from aiogram.types import MessageEntity
from logger import logger
_emoji_placeholder_cache: dict[str, str] = {}
_bot_instance: Bot | None = None
async def _get_emoji_placeholder(emoji_id: str) -> str:
"""Fetch placeholder emoji for custom_emoji_id via API, with cache."""
if emoji_id in _emoji_placeholder_cache:
return _emoji_placeholder_cache[emoji_id]
if _bot_instance is None:
return "😀"
try:
stickers = await _bot_instance.get_custom_emoji_stickers(custom_emoji_ids=[emoji_id])
if stickers:
sticker = stickers[0]
placeholder = None
if hasattr(sticker, "emoji") and sticker.emoji:
placeholder = sticker.emoji
elif hasattr(sticker, "alt") and sticker.alt:
placeholder = sticker.alt
if placeholder:
_emoji_placeholder_cache[emoji_id] = placeholder
return placeholder
except Exception:
pass
return "😀"
def _get_utf16_length(text: str) -> int:
"""Calculate length in UTF-16 code units."""
return len(text.encode("utf-16-le")) // 2
async def parse_custom_emoji_markers(text: str) -> tuple[str, list[MessageEntity]]:
"""Parse text with custom emoji markers to placeholders and entities."""
if not text:
return text, []
entities: list[MessageEntity] = []
result_text = text
pattern = r"\{emoji:(\d+)\}|\[emoji:(\d+)\]"
matches = list(re.finditer(pattern, text))
for match in reversed(matches):
emoji_id = match.group(1) or match.group(2)
marker_text = match.group(0)
marker_pos_in_result = match.start()
emoji_placeholder = await _get_emoji_placeholder(emoji_id)
result_text = (
result_text[:marker_pos_in_result]
+ emoji_placeholder
+ result_text[marker_pos_in_result + len(marker_text) :]
)
text_before = result_text[:marker_pos_in_result]
offset_utf16 = _get_utf16_length(text_before)
placeholder_utf16_length = _get_utf16_length(emoji_placeholder)
entity = MessageEntity(
type=MessageEntityType.CUSTOM_EMOJI,
offset=offset_utf16,
length=placeholder_utf16_length,
custom_emoji_id=str(emoji_id),
)
entities.insert(0, entity)
return result_text, entities
def _parse_html_entities(text: str) -> list[MessageEntity]:
"""Parse HTML markup and build entities manually."""
entities: list[MessageEntity] = []
link_patterns = [
(r'<a href="([^"]+)"\s*>', r"</a>"),
(r"<a href='([^']+)'\s*>", r"</a>"),
]
link_tags = []
for open_pattern, close_pattern in link_patterns:
for open_match in re.finditer(open_pattern, text):
url = open_match.group(1)
link_tags.append(
{
"type": "open",
"url": url,
"open_start": open_match.start(),
"open_end": open_match.end(),
"close_pattern": close_pattern,
}
)
for close_match in re.finditer(r"</a>", text):
link_tags.append({"type": "close", "pos": close_match.start()})
link_tags.sort(key=lambda x: x.get("open_start", x.get("pos", 0)))
link_stacks = []
tag_positions = []
for tag in link_tags:
if tag["type"] == "open":
link_stacks.append(tag)
else:
if link_stacks:
matching_open = link_stacks.pop()
open_pos = matching_open["open_end"]
close_pos = tag["pos"]
content = text[open_pos:close_pos]
tag_positions.append(
{
"open_pos": open_pos,
"close_pos": close_pos,
"content": content,
"entity_type": "text_link",
"url": matching_open["url"],
}
)
other_patterns = [
(r"<b\s*>", r"</b>", "bold"),
(r"<strong\s*>", r"</strong>", "bold"),
(r"<i\s*>", r"</i>", "italic"),
(r"<em\s*>", r"</em>", "italic"),
(r"<u\s*>", r"</u>", "underline"),
(r"<ins\s*>", r"</ins>", "underline"),
(r"<s\s*>", r"</s>", "strikethrough"),
(r"<strike\s*>", r"</strike>", "strikethrough"),
(r"<del\s*>", r"</del>", "strikethrough"),
(r"<code\s*>", r"</code>", "code"),
(r"<pre\s*>", r"</pre>", "pre"),
(r"<blockquote\s*>", r"</blockquote>", "blockquote"),
]
other_tags = []
for open_pattern, close_pattern, entity_type in other_patterns:
for open_match in re.finditer(open_pattern, text):
other_tags.append(
{
"type": "open",
"entity_type": entity_type,
"open_end": open_match.end(),
"close_pattern": close_pattern,
}
)
for close_match in re.finditer(close_pattern, text):
other_tags.append(
{"type": "close", "entity_type": entity_type, "pos": close_match.start()}
)
other_tags.sort(key=lambda x: x.get("open_end", x.get("pos", 0)))
other_stacks: dict[str, list[dict[str, Any]]] = {}
for tag in other_tags:
entity_type = tag["entity_type"]
if tag["type"] == "open":
if entity_type not in other_stacks:
other_stacks[entity_type] = []
other_stacks[entity_type].append(tag)
else:
if entity_type in other_stacks and other_stacks[entity_type]:
matching_open = other_stacks[entity_type].pop()
open_pos = matching_open["open_end"]
close_pos = tag["pos"]
content = text[open_pos:close_pos]
tag_positions.append(
{
"open_pos": open_pos,
"close_pos": close_pos,
"content": content,
"entity_type": entity_type,
"url": None,
}
)
tag_positions.sort(key=lambda x: x["open_pos"])
for tag_info in tag_positions:
content = tag_info["content"]
open_pos = tag_info["open_pos"]
entity_type = tag_info["entity_type"]
url = tag_info["url"]
text_before_content = text[:open_pos]
offset_utf16 = _get_utf16_length(text_before_content)
length_utf16 = _get_utf16_length(content)
entity_dict = {
"type": MessageEntityType(entity_type),
"offset": offset_utf16,
"length": length_utf16,
}
if url:
entity_dict["url"] = url
entity = MessageEntity(**entity_dict)
entities.append(entity)
return entities
async def process_text_with_custom_emojis(text: str) -> tuple[str, list[MessageEntity] | None]:
"""Process text with custom emoji markers."""
if not text or not isinstance(text, str):
return text, None
processed_text, entities = await parse_custom_emoji_markers(text)
return (processed_text, entities) if entities else (text, None)
def patch_bot_methods() -> bool:
"""Patch Message methods to handle custom emojis."""
global _bot_instance
try:
from aiogram.types import Message
from bot import bot
_bot_instance = bot
if not hasattr(Message, "_original_answer"):
Message._original_answer = Message.answer
Message._original_edit_text = Message.edit_text
Message._original_edit_caption = Message.edit_caption
Message._original_answer_photo = Message.answer_photo
Message._original_answer_video = Message.answer_video
Message._original_answer_animation = Message.answer_animation
Message._original_edit_media = Message.edit_media
async def process_text_and_entities(
text: str,
entities: list[MessageEntity] | None = None,
parse_mode: str | None = None,
):
"""Process text and entities for custom emojis."""
if not text:
return text, entities
processed_text_with_html, custom_entities = await process_text_with_custom_emojis(text)
html_entities_with_html = []
if custom_entities and "<" in processed_text_with_html and ">" in processed_text_with_html:
html_entities_with_html = _parse_html_entities(processed_text_with_html)
if html_entities_with_html or custom_entities:
text_without_html = ""
utf16_offset_map: dict[int, int] = {}
html_pos = 0
plain_utf16 = 0
html_utf16 = 0
while html_pos < len(processed_text_with_html):
if processed_text_with_html[html_pos] == "<":
while (
html_pos < len(processed_text_with_html)
and processed_text_with_html[html_pos] != ">"
):
char = processed_text_with_html[html_pos]
char_utf16_len = _get_utf16_length(char)
for i in range(char_utf16_len):
utf16_offset_map[html_utf16 + i] = plain_utf16
html_utf16 += char_utf16_len
html_pos += 1
if html_pos < len(processed_text_with_html):
char = processed_text_with_html[html_pos]
char_utf16_len = _get_utf16_length(char)
for i in range(char_utf16_len):
utf16_offset_map[html_utf16 + i] = plain_utf16
html_utf16 += char_utf16_len
html_pos += 1
else:
char = processed_text_with_html[html_pos]
text_without_html += char
char_utf16_len = _get_utf16_length(char)
for i in range(char_utf16_len):
utf16_offset_map[html_utf16 + i] = plain_utf16 + i
plain_utf16 += char_utf16_len
html_utf16 += char_utf16_len
html_pos += 1
def recalculate_offset(entity_offset_utf16: int) -> int:
"""Recalculate UTF-16 offset from HTML to plain text."""
if entity_offset_utf16 in utf16_offset_map:
return utf16_offset_map[entity_offset_utf16]
sorted_keys = sorted(utf16_offset_map.keys())
best_match = None
for key in sorted_keys:
if key <= entity_offset_utf16:
best_match = key
else:
break
return (
utf16_offset_map[best_match]
if best_match is not None
else entity_offset_utf16
)
html_entities = []
for entity in html_entities_with_html:
new_offset = recalculate_offset(entity.offset)
entity_end_offset = entity.offset + entity.length
new_end_offset = recalculate_offset(entity_end_offset)
new_length = new_end_offset - new_offset
entity_dict = entity.model_dump()
entity_dict["offset"] = new_offset
entity_dict["length"] = new_length
html_entities.append(MessageEntity(**entity_dict))
corrected_custom_entities = []
for entity in custom_entities:
new_offset = recalculate_offset(entity.offset)
entity_dict = entity.model_dump()
entity_dict["offset"] = new_offset
corrected_custom_entities.append(MessageEntity(**entity_dict))
final_entities: list[MessageEntity] = []
if html_entities:
final_entities.extend(html_entities)
if corrected_custom_entities:
final_entities.extend(corrected_custom_entities)
if entities:
final_entities.extend(entities)
if final_entities:
final_entities = sorted(final_entities, key=lambda e: e.offset)
return text_without_html, final_entities if final_entities else None
return text, entities
async def patched_message_answer(
self, text: str, entities: list[MessageEntity] | None = None, **kwargs
):
"""Patched Message.answer."""
processed_text, final_entities = await process_text_and_entities(
text, entities, kwargs.get("parse_mode")
)
if final_entities:
kwargs["parse_mode"] = None
return await self._original_answer(
text=processed_text, entities=final_entities if final_entities else None, **kwargs
)
async def patched_message_edit_text(
self, text: str, entities: list[MessageEntity] | None = None, **kwargs
):
"""Patched Message.edit_text."""
processed_text, final_entities = await process_text_and_entities(
text, entities, kwargs.get("parse_mode")
)
if final_entities:
kwargs["parse_mode"] = None
return await self._original_edit_text(
text=processed_text, entities=final_entities, **kwargs
)
async def patched_message_edit_caption(
self,
caption: str | None = None,
caption_entities: list[MessageEntity] | None = None,
**kwargs,
):
"""Patched Message.edit_caption."""
if not caption:
return await self._original_edit_caption(
caption=caption, caption_entities=caption_entities, **kwargs
)
processed_caption, final_entities = await process_text_and_entities(
caption, caption_entities, kwargs.get("parse_mode")
)
if final_entities:
kwargs["parse_mode"] = None
return await self._original_edit_caption(
caption=processed_caption, caption_entities=final_entities, **kwargs
)
async def patched_message_answer_photo(
self,
photo: Any,
caption: str | None = None,
caption_entities: list[MessageEntity] | None = None,
**kwargs,
):
"""Patched Message.answer_photo."""
if not caption:
return await self._original_answer_photo(
photo=photo, caption=caption, caption_entities=caption_entities, **kwargs
)
processed_caption, final_entities = await process_text_and_entities(
caption, caption_entities, kwargs.get("parse_mode")
)
if final_entities:
kwargs["parse_mode"] = None
return await self._original_answer_photo(
photo=photo, caption=processed_caption, caption_entities=final_entities, **kwargs
)
async def patched_message_answer_video(
self,
video: Any,
caption: str | None = None,
caption_entities: list[MessageEntity] | None = None,
**kwargs,
):
"""Patched Message.answer_video."""
if not caption:
return await self._original_answer_video(
video=video, caption=caption, caption_entities=caption_entities, **kwargs
)
processed_caption, final_entities = await process_text_and_entities(
caption, caption_entities, kwargs.get("parse_mode")
)
if final_entities:
kwargs["parse_mode"] = None
return await self._original_answer_video(
video=video, caption=processed_caption, caption_entities=final_entities, **kwargs
)
async def patched_message_answer_animation(
self,
animation: Any,
caption: str | None = None,
caption_entities: list[MessageEntity] | None = None,
**kwargs,
):
"""Patched Message.answer_animation."""
if not caption:
return await self._original_answer_animation(
animation=animation,
caption=caption,
caption_entities=caption_entities,
**kwargs,
)
processed_caption, final_entities = await process_text_and_entities(
caption, caption_entities, kwargs.get("parse_mode")
)
if final_entities:
kwargs["parse_mode"] = None
return await self._original_answer_animation(
animation=animation,
caption=processed_caption,
caption_entities=final_entities,
**kwargs,
)
async def patched_message_edit_media(self, media: Any, **kwargs):
"""Patched Message.edit_media."""
if hasattr(media, "caption") and media.caption:
processed_caption, final_entities = await process_text_and_entities(
media.caption, getattr(media, "caption_entities", None), kwargs.get("parse_mode")
)
media.caption = processed_caption
if final_entities:
if hasattr(media, "parse_mode"):
media.parse_mode = None
kwargs["parse_mode"] = None
media.caption_entities = final_entities
return await self._original_edit_media(media=media, **kwargs)
Message.answer = patched_message_answer
Message.edit_text = patched_message_edit_text
Message.edit_caption = patched_message_edit_caption
Message.answer_photo = patched_message_answer_photo
Message.answer_video = patched_message_answer_video
Message.answer_animation = patched_message_answer_animation
Message.edit_media = patched_message_edit_media
return True
except Exception as e:
logger.error(f"[CustomEmojis] Error while patching bot methods: {e}", exc_info=True)
return False
def initialize_custom_emojis() -> bool:
"""Initialize custom emoji support."""
try:
return patch_bot_methods()
except Exception as e:
logger.error(f"[CustomEmojis] Error during initialization: {e}", exc_info=True)
return False