diff --git a/aiogram/utils/markdown.py b/aiogram/utils/markdown.py index ba8356e7..e934fe14 100644 --- a/aiogram/utils/markdown.py +++ b/aiogram/utils/markdown.py @@ -1,3 +1,5 @@ +from .text_decorations import html, markdown + LIST_MD_SYMBOLS = "*_`[" MD_SYMBOLS = ( @@ -20,34 +22,6 @@ def _join(*content, sep=" "): return sep.join(map(str, content)) -def _escape(s, symbols=LIST_MD_SYMBOLS): - for symbol in symbols: - s = s.replace(symbol, "\\" + symbol) - return s - - -def _md(string, symbols=("", "")): - start, end = symbols - return start + string + end - - -def quote_html(content): - """ - Quote HTML symbols - - All <, >, & and " symbols that are not a part of a tag or - an HTML entity must be replaced with the corresponding HTML entities - (< with < > with > & with & and " with "). - - :param content: str - :return: str - """ - new_content = "" - for symbol in content: - new_content += HTML_QUOTES_MAP[symbol] if symbol in _HQS else symbol - return new_content - - def text(*content, sep=" "): """ Join all elements with a separator @@ -67,7 +41,7 @@ def bold(*content, sep=" "): :param sep: :return: """ - return _md(_join(*content, sep=sep), symbols=MD_SYMBOLS[0]) + return markdown.bold.format(value=html.quote(_join(*content, sep=sep))) def hbold(*content, sep=" "): @@ -78,7 +52,7 @@ def hbold(*content, sep=" "): :param sep: :return: """ - return _md(quote_html(_join(*content, sep=sep)), symbols=MD_SYMBOLS[4]) + return html.bold.format(value=html.quote(_join(*content, sep=sep))) def italic(*content, sep=" "): @@ -89,7 +63,7 @@ def italic(*content, sep=" "): :param sep: :return: """ - return _md(_join(*content, sep=sep), symbols=MD_SYMBOLS[1]) + return markdown.italic.format(value=html.quote(_join(*content, sep=sep))) def hitalic(*content, sep=" "): @@ -100,7 +74,7 @@ def hitalic(*content, sep=" "): :param sep: :return: """ - return _md(quote_html(_join(*content, sep=sep)), symbols=MD_SYMBOLS[5]) + return html.italic.format(value=html.quote(_join(*content, sep=sep))) def code(*content, sep=" "): @@ -111,7 +85,7 @@ def code(*content, sep=" "): :param sep: :return: """ - return _md(_join(*content, sep=sep), symbols=MD_SYMBOLS[2]) + return markdown.code.format(value=html.quote(_join(*content, sep=sep))) def hcode(*content, sep=" "): @@ -122,7 +96,7 @@ def hcode(*content, sep=" "): :param sep: :return: """ - return _md(quote_html(_join(*content, sep=sep)), symbols=MD_SYMBOLS[6]) + return html.code.format(value=html.quote(_join(*content, sep=sep))) def pre(*content, sep="\n"): @@ -133,7 +107,7 @@ def pre(*content, sep="\n"): :param sep: :return: """ - return _md(_join(*content, sep=sep), symbols=MD_SYMBOLS[3]) + return markdown.pre.format(value=html.quote(_join(*content, sep=sep))) def hpre(*content, sep="\n"): @@ -144,10 +118,54 @@ def hpre(*content, sep="\n"): :param sep: :return: """ - return _md(quote_html(_join(*content, sep=sep)), symbols=MD_SYMBOLS[7]) + return html.pre.format(value=html.quote(_join(*content, sep=sep))) -def link(title, url): +def underline(*content, sep=" "): + """ + Make underlined text (Markdown) + + :param content: + :param sep: + :return: + """ + return markdown.underline.format(value=markdown.quote(_join(*content, sep=sep))) + + +def hunderline(*content, sep=" "): + """ + Make underlined text (HTML) + + :param content: + :param sep: + :return: + """ + return html.underline.format(value=html.quote(_join(*content, sep=sep))) + + +def strikethrough(*content, sep=" "): + """ + Make strikethrough text (Markdown) + + :param content: + :param sep: + :return: + """ + return markdown.strikethrough.format(value=markdown.quote(_join(*content, sep=sep))) + + +def hstrikethrough(*content, sep=" "): + """ + Make strikethrough text (HTML) + + :param content: + :param sep: + :return: + """ + return html.strikethrough.format(value=html.quote(_join(*content, sep=sep))) + + +def link(title: str, url: str) -> str: """ Format URL (Markdown) @@ -155,10 +173,10 @@ def link(title, url): :param url: :return: """ - return "[{0}]({1})".format(title, url) + return markdown.link.format(value=html.quote(title), link=url) -def hlink(title, url): +def hlink(title: str, url: str) -> str: """ Format URL (HTML) @@ -166,23 +184,10 @@ def hlink(title, url): :param url: :return: """ - return '{1}'.format(url, quote_html(title)) + return html.link.format(value=html.quote(title), link=url) -def escape_md(*content, sep=" "): - """ - Escape markdown text - - E.g. for usernames - - :param content: - :param sep: - :return: - """ - return _escape(_join(*content, sep=sep)) - - -def hide_link(url): +def hide_link(url: str) -> str: """ Hide URL (HTML only) Can be used for adding an image to a text message diff --git a/tests/test_utils/test_markdown.py b/tests/test_utils/test_markdown.py new file mode 100644 index 00000000..79b22c97 --- /dev/null +++ b/tests/test_utils/test_markdown.py @@ -0,0 +1,45 @@ +from typing import Any, Callable, Optional, Tuple + +import pytest + +from aiogram.utils import markdown + + +class TestMarkdown: + @pytest.mark.parametrize( + "func,args,sep,result", + [ + [markdown.text, ("test", "test"), " ", "test test"], + [markdown.text, ("test", "test"), "\n", "test\ntest"], + [markdown.text, ("test", "test"), None, "test test"], + [markdown.bold, ("test", "test"), " ", "*test test*"], + [markdown.hbold, ("test", "test"), " ", "test test"], + [markdown.italic, ("test", "test"), " ", "_test test_"], + [markdown.hitalic, ("test", "test"), " ", "test test"], + [markdown.code, ("test", "test"), " ", "`test test`"], + [markdown.hcode, ("test", "test"), " ", "test test"], + [markdown.pre, ("test", "test"), " ", "```test test```"], + [markdown.hpre, ("test", "test"), " ", "
test test
"], + [markdown.underline, ("test", "test"), " ", "--test test--"], + [markdown.hunderline, ("test", "test"), " ", "test test"], + [markdown.strikethrough, ("test", "test"), " ", "~~test test~~"], + [markdown.hstrikethrough, ("test", "test"), " ", "test test"], + [markdown.link, ("test", "https://aiogram.dev"), None, "[test](https://aiogram.dev)"], + [ + markdown.hlink, + ("test", "https://aiogram.dev"), + None, + 'test', + ], + [ + markdown.hide_link, + ("https://aiogram.dev",), + None, + '', + ], + ], + ) + def test_formatter( + self, func: Callable[[Any], Any], args: Tuple[str], sep: Optional[str], result: str + ): + assert func(*args, **({"sep": sep} if sep is not None else {})) == result # type: ignore