diff --git a/aiogram/utils/markdown.py b/aiogram/utils/markdown.py
index 7b217b4f..5daae546 100644
--- a/aiogram/utils/markdown.py
+++ b/aiogram/utils/markdown.py
@@ -1,22 +1,5 @@
from .text_decorations import html_decoration, markdown_decoration
-LIST_MD_SYMBOLS = "*_`["
-
-MD_SYMBOLS = (
- (LIST_MD_SYMBOLS[0], LIST_MD_SYMBOLS[0]),
- (LIST_MD_SYMBOLS[1], LIST_MD_SYMBOLS[1]),
- (LIST_MD_SYMBOLS[2], LIST_MD_SYMBOLS[2]),
- (LIST_MD_SYMBOLS[2] * 3 + "\n", "\n" + LIST_MD_SYMBOLS[2] * 3),
- ("", ""),
- ("", ""),
- ("", ""),
- ("
", ""), -) - -HTML_QUOTES_MAP = {"<": "<", ">": ">", "&": "&", '"': """} - -_HQS = HTML_QUOTES_MAP.keys() # HQS for HTML QUOTES SYMBOLS - def _join(*content, sep=" "): return sep.join(map(str, content)) @@ -41,7 +24,7 @@ def bold(*content, sep=" "): :param sep: :return: """ - return markdown_decoration.bold.format(value=html_decoration.quote(_join(*content, sep=sep))) + return markdown_decoration.bold(value=html_decoration.quote(_join(*content, sep=sep))) def hbold(*content, sep=" "): @@ -52,7 +35,7 @@ def hbold(*content, sep=" "): :param sep: :return: """ - return html_decoration.bold.format(value=html_decoration.quote(_join(*content, sep=sep))) + return html_decoration.bold(value=html_decoration.quote(_join(*content, sep=sep))) def italic(*content, sep=" "): @@ -63,7 +46,7 @@ def italic(*content, sep=" "): :param sep: :return: """ - return markdown_decoration.italic.format(value=html_decoration.quote(_join(*content, sep=sep))) + return markdown_decoration.italic(value=html_decoration.quote(_join(*content, sep=sep))) def hitalic(*content, sep=" "): @@ -74,7 +57,7 @@ def hitalic(*content, sep=" "): :param sep: :return: """ - return html_decoration.italic.format(value=html_decoration.quote(_join(*content, sep=sep))) + return html_decoration.italic(value=html_decoration.quote(_join(*content, sep=sep))) def code(*content, sep=" "): @@ -85,7 +68,7 @@ def code(*content, sep=" "): :param sep: :return: """ - return markdown_decoration.code.format(value=html_decoration.quote(_join(*content, sep=sep))) + return markdown_decoration.code(value=html_decoration.quote(_join(*content, sep=sep))) def hcode(*content, sep=" "): @@ -96,7 +79,7 @@ def hcode(*content, sep=" "): :param sep: :return: """ - return html_decoration.code.format(value=html_decoration.quote(_join(*content, sep=sep))) + return html_decoration.code(value=html_decoration.quote(_join(*content, sep=sep))) def pre(*content, sep="\n"): @@ -107,7 +90,7 @@ def pre(*content, sep="\n"): :param sep: :return: """ - return markdown_decoration.pre.format(value=html_decoration.quote(_join(*content, sep=sep))) + return markdown_decoration.pre(value=html_decoration.quote(_join(*content, sep=sep))) def hpre(*content, sep="\n"): @@ -118,7 +101,7 @@ def hpre(*content, sep="\n"): :param sep: :return: """ - return html_decoration.pre.format(value=html_decoration.quote(_join(*content, sep=sep))) + return html_decoration.pre(value=html_decoration.quote(_join(*content, sep=sep))) def underline(*content, sep=" "): @@ -129,9 +112,7 @@ def underline(*content, sep=" "): :param sep: :return: """ - return markdown_decoration.underline.format( - value=markdown_decoration.quote(_join(*content, sep=sep)) - ) + return markdown_decoration.underline(value=markdown_decoration.quote(_join(*content, sep=sep))) def hunderline(*content, sep=" "): @@ -142,7 +123,7 @@ def hunderline(*content, sep=" "): :param sep: :return: """ - return html_decoration.underline.format(value=html_decoration.quote(_join(*content, sep=sep))) + return html_decoration.underline(value=html_decoration.quote(_join(*content, sep=sep))) def strikethrough(*content, sep=" "): @@ -153,7 +134,7 @@ def strikethrough(*content, sep=" "): :param sep: :return: """ - return markdown_decoration.strikethrough.format( + return markdown_decoration.strikethrough( value=markdown_decoration.quote(_join(*content, sep=sep)) ) @@ -166,9 +147,7 @@ def hstrikethrough(*content, sep=" "): :param sep: :return: """ - return html_decoration.strikethrough.format( - value=html_decoration.quote(_join(*content, sep=sep)) - ) + return html_decoration.strikethrough(value=html_decoration.quote(_join(*content, sep=sep))) def link(title: str, url: str) -> str: @@ -179,7 +158,7 @@ def link(title: str, url: str) -> str: :param url: :return: """ - return markdown_decoration.link.format(value=html_decoration.quote(title), link=url) + return markdown_decoration.link(value=html_decoration.quote(title), link=url) def hlink(title: str, url: str) -> str: @@ -190,7 +169,7 @@ def hlink(title: str, url: str) -> str: :param url: :return: """ - return html_decoration.link.format(value=html_decoration.quote(title), link=url) + return html_decoration.link(value=html_decoration.quote(title), link=url) def hide_link(url: str) -> str: diff --git a/aiogram/utils/text_decorations.py b/aiogram/utils/text_decorations.py index 125547e8..4e01bc8b 100644 --- a/aiogram/utils/text_decorations.py +++ b/aiogram/utils/text_decorations.py @@ -1,32 +1,23 @@ +from __future__ import annotations + import html import re -import struct -from dataclasses import dataclass -from typing import AnyStr, Callable, Generator, Iterable, List, Optional +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING, Generator, List, Optional, Pattern, cast -from aiogram.api.types import MessageEntity +if TYPE_CHECKING: # pragma: no cover + from aiogram.api.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 - pre_language: 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,20 +27,27 @@ class TextDecoration: :return: """ if entity.type in ("bold", "italic", "code", "underline", "strikethrough"): - return getattr(self, entity.type).format(value=text) + return cast(str, getattr(self, entity.type)(value=text)) if entity.type == "pre": - return (self.pre_language if entity.language else self.pre).format( - value=text, language=entity.language + return ( + self.pre_language(value=text, language=entity.language) + if entity.language + else self.pre(value=text) ) elif entity.type == "text_mention": - return self.link.format(value=text, link=f"tg://user?id={entity.user.id}") + from aiogram.api.types import User + + user = cast(User, entity.user) + return self.link(value=text, link=f"tg://user?id={user.id}") + elif entity.type == "mention": + return text elif entity.type == "text_link": - return self.link.format(value=text, link=entity.url) + return self.link(value=text, link=cast(str, entity.url)) elif entity.type == "url": return text 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 @@ -57,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): @@ -83,7 +81,7 @@ class TextDecoration: start = entity.offset offset = entity.offset + entity.length - sub_entities = list(filter(lambda e: e.offset < offset, entities[index + 1 :])) + sub_entities = list(filter(lambda e: e.offset < (offset or 0), entities[index + 1 :])) yield self.apply_entity( entity, "".join(self._unparse_entities(text, sub_entities, offset=start, length=offset)), @@ -92,42 +90,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}",
- pre_language='{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"