first commit
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
import html
|
||||
import re
|
||||
from html.parser import HTMLParser
|
||||
|
||||
|
||||
def to_html(obj):
|
||||
return str(obj).replace("<", "<").replace(">", ">")
|
||||
|
||||
|
||||
class TelegramHTMLSanitizer(HTMLParser):
|
||||
ALLOWED_TAGS = {"b", "i", "u", "s", "code", "pre", "a"}
|
||||
TAG_ALIASES = {"strong": "b", "em": "i"}
|
||||
ALLOWED_HREF_PREFIXES = ("http://", "https://", "tg://", "mailto:")
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(convert_charrefs=False)
|
||||
self.parts: list[str] = []
|
||||
self.tag_stack: list[str] = []
|
||||
|
||||
def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None:
|
||||
normalized_tag = self.TAG_ALIASES.get(tag, tag)
|
||||
if normalized_tag not in self.ALLOWED_TAGS:
|
||||
return
|
||||
|
||||
if normalized_tag == "a":
|
||||
href = next((value for key, value in attrs if key == "href" and value), None)
|
||||
if not href or not href.startswith(self.ALLOWED_HREF_PREFIXES):
|
||||
return
|
||||
safe_href = html.escape(href, quote=True)
|
||||
self.parts.append(f'<a href="{safe_href}">')
|
||||
self.tag_stack.append(normalized_tag)
|
||||
return
|
||||
|
||||
self.parts.append(f"<{normalized_tag}>")
|
||||
self.tag_stack.append(normalized_tag)
|
||||
|
||||
def handle_endtag(self, tag: str) -> None:
|
||||
normalized_tag = self.TAG_ALIASES.get(tag, tag)
|
||||
if normalized_tag not in self.ALLOWED_TAGS:
|
||||
return
|
||||
|
||||
for index in range(len(self.tag_stack) - 1, -1, -1):
|
||||
if self.tag_stack[index] == normalized_tag:
|
||||
del self.tag_stack[index]
|
||||
self.parts.append(f"</{normalized_tag}>")
|
||||
break
|
||||
|
||||
def handle_data(self, data: str) -> None:
|
||||
self.parts.append(html.escape(data, quote=False))
|
||||
|
||||
def handle_entityref(self, name: str) -> None:
|
||||
self.parts.append(f"&{name};")
|
||||
|
||||
def handle_charref(self, name: str) -> None:
|
||||
self.parts.append(f"&#{name};")
|
||||
|
||||
def get_html(self) -> str:
|
||||
while self.tag_stack:
|
||||
self.parts.append(f"</{self.tag_stack.pop()}>")
|
||||
return "".join(self.parts)
|
||||
|
||||
|
||||
def markdown_to_telegram_html(text: str) -> str:
|
||||
prepared = text.replace("\r\n", "\n").strip()
|
||||
prepared = re.sub(
|
||||
r"\[([^\]]+)\]\((https?://[^\s)]+)\)",
|
||||
r'<a href="\2">\1</a>',
|
||||
prepared,
|
||||
)
|
||||
prepared = re.sub(r"\*\*(.+?)\*\*", r"<b>\1</b>", prepared, flags=re.DOTALL)
|
||||
prepared = re.sub(r"__(.+?)__", r"<b>\1</b>", prepared, flags=re.DOTALL)
|
||||
prepared = re.sub(r"(?m)^[ \t]*[*-]\s+", "• ", prepared)
|
||||
return prepared
|
||||
|
||||
|
||||
def format_telegram_html(text: str) -> str:
|
||||
prepared = markdown_to_telegram_html(str(text))
|
||||
sanitizer = TelegramHTMLSanitizer()
|
||||
sanitizer.feed(prepared)
|
||||
sanitizer.close()
|
||||
return sanitizer.get_html()
|
||||
|
||||
|
||||
def parse_links_to_inline_markup(message: str) -> list:
|
||||
"""
|
||||
Парсит сообщение с форматированными ссылками и возвращает список рядов кнопок.
|
||||
|
||||
Формат входного сообщения:
|
||||
- [Текст кнопки + Ссылка] для одной кнопки.
|
||||
- [Кнопка1 + Ссылка1][Кнопка2 + Ссылка2] для нескольких кнопок в одном ряду.
|
||||
- Каждая строка представляет отдельный ряд кнопок.
|
||||
|
||||
Пример:
|
||||
[Кнопка1 + https://example.com]
|
||||
[Кнопка2 + https://example.org][Кнопка3 + https://example.net]
|
||||
|
||||
:param message: Строка с отформатированными ссылками.
|
||||
:return: Список рядов кнопок, где каждый ряд — это список кортежей (Текст, Ссылка).
|
||||
"""
|
||||
# Исправленное регулярное выражение для поиска [Текст + Ссылка]
|
||||
pattern = re.compile(r"\[([^\[\]+]+)\s*\+\s*(https?://[^\[\]]+)\]")
|
||||
|
||||
# Инициализируем список рядов кнопок
|
||||
keyboard_rows = []
|
||||
|
||||
# Разбиваем сообщение на строки
|
||||
lines = message.strip().split("\n")
|
||||
|
||||
for line in lines:
|
||||
# Находим все совпадения в строке
|
||||
matches = pattern.findall(line)
|
||||
if matches:
|
||||
row = []
|
||||
for text, url in matches:
|
||||
button = (text.strip(), url.strip())
|
||||
row.append(button)
|
||||
keyboard_rows.append(row)
|
||||
|
||||
return keyboard_rows
|
||||
Reference in New Issue
Block a user