diff --git a/aiogram/utils/text_decorations.py b/aiogram/utils/text_decorations.py
index ad52c9d7..77dc0ff3 100644
--- a/aiogram/utils/text_decorations.py
+++ b/aiogram/utils/text_decorations.py
@@ -1,33 +1,23 @@
from __future__ import annotations
+
import html
import re
-import struct
-from dataclasses import dataclass
-from typing import TYPE_CHECKING, AnyStr, Callable, Generator, Iterable, List, Optional
+from abc import ABC, abstractmethod
+from typing import TYPE_CHECKING, Generator, List, Optional, Pattern, cast
-if TYPE_CHECKING:
+if TYPE_CHECKING: # pragma: no cover
from aiogram.types import MessageEntity
__all__ = (
"TextDecoration",
+ "HtmlDecoration",
+ "MarkdownDecoration",
"html_decoration",
"markdown_decoration",
- "add_surrogate",
- "remove_surrogate",
)
-@dataclass
-class TextDecoration:
- link: str
- bold: str
- italic: str
- code: str
- pre: str
- underline: str
- strikethrough: str
- quote: Callable[[AnyStr], AnyStr]
-
+class TextDecoration(ABC):
def apply_entity(self, entity: MessageEntity, text: str) -> str:
"""
Apply single entity to text
@@ -36,24 +26,28 @@ class TextDecoration:
:param text:
:return:
"""
- if entity.type in (
- "bold",
- "italic",
- "code",
- "pre",
- "underline",
- "strikethrough",
- ):
- return getattr(self, entity.type).format(value=text)
- elif entity.type == "text_mention":
- return self.link.format(value=text, link=f"tg://user?id={entity.user.id}")
- elif entity.type == "text_link":
- return self.link.format(value=text, link=entity.url)
- elif entity.type == "url":
+ if entity.type in {"bot_command", "url", "mention", "phone_number"}:
+ # This entities should not be changed
return text
+ if entity.type in {"bold", "italic", "code", "underline", "strikethrough"}:
+ return cast(str, getattr(self, entity.type)(value=text))
+ if entity.type == "pre":
+ return (
+ self.pre_language(value=text, language=entity.language)
+ if entity.language
+ else self.pre(value=text)
+ )
+ if entity.type == "text_mention":
+ from aiogram.types import User
+
+ user = cast(User, entity.user)
+ return self.link(value=text, link=f"tg://user?id={user.id}")
+ if entity.type == "text_link":
+ return self.link(value=text, link=cast(str, entity.url))
+
return self.quote(text)
- def unparse(self, text, entities: Optional[List[MessageEntity]] = None) -> str:
+ def unparse(self, text: str, entities: Optional[List[MessageEntity]] = None) -> str:
"""
Unparse message entities
@@ -61,22 +55,22 @@ class TextDecoration:
:param entities: Array of MessageEntities
:return:
"""
- text = add_surrogate(text)
result = "".join(
self._unparse_entities(
text, sorted(entities, key=lambda item: item.offset) if entities else []
)
)
- return remove_surrogate(result)
+ return result
def _unparse_entities(
self,
text: str,
- entities: Iterable[MessageEntity],
+ entities: List[MessageEntity],
offset: Optional[int] = None,
length: Optional[int] = None,
) -> Generator[str, None, None]:
- offset = offset or 0
+ if offset is None:
+ offset = 0
length = length or len(text)
for index, entity in enumerate(entities):
@@ -88,7 +82,7 @@ class TextDecoration:
offset = entity.offset + entity.length
sub_entities = list(
- filter(lambda e: e.offset < offset, entities[index + 1 :])
+ filter(lambda e: e.offset < (offset or 0), entities[index + 1 :])
)
yield self.apply_entity(
entity,
@@ -102,42 +96,102 @@ class TextDecoration:
if offset < length:
yield self.quote(text[offset:length])
+ @abstractmethod
+ def link(self, value: str, link: str) -> str: # pragma: no cover
+ pass
-html_decoration = TextDecoration(
- link='{value}',
- bold="{value}",
- italic="{value}",
- code="{value}",
- pre="
{value}",
- underline="{value}",
- strikethrough="{value}"
+
+ def pre(self, value: str) -> str:
+ return f"{value}"
+
+ def pre_language(self, value: str, language: str) -> str:
+ return f'{value}'
+
+ def underline(self, value: str) -> str:
+ return f"{value}"
+
+ def strikethrough(self, value: str) -> str:
+ return f"