diff --git a/aiogram/types/message.py b/aiogram/types/message.py index 459091ee..9f57f483 100644 --- a/aiogram/types/message.py +++ b/aiogram/types/message.py @@ -1,5 +1,6 @@ import datetime import functools +import sys import typing from . import base @@ -21,6 +22,7 @@ from .video import Video from .video_note import VideoNote from .voice import Voice from ..utils import helper +from ..utils import markdown as md class Message(base.TelegramObject): @@ -157,6 +159,49 @@ class Message(base.TelegramObject): if command: return command[1].strip() + def parse_entities(self, as_html=True): + """ + Text or caption formatted as HTML. + + :return: str + """ + + text = self.text or self.caption + if text is None: + raise TypeError("This message doesn't have any text.") + + quote_fn = md.quote_html if as_html else md.escape_md + + if not self.entities: + return quote_fn(text) + + if not sys.maxunicode == 0xffff: + text = text.encode('utf-16-le') + + result = '' + offset = 0 + + for entity in sorted(self.entities, key=lambda item: item.offset): + entity_text = entity.parse(text, as_html=as_html) + + if sys.maxunicode == 0xffff: + part = text[offset:entity.offset] + result += quote_fn(part) + entity_text + else: + part = text[offset * 2:entity.offset * 2] + result += quote_fn(part.decode('utf-16-le')) + entity_text + + offset = entity.offset + entity.length + + if sys.maxunicode == 0xffff: + part = text[offset:] + result += quote_fn(part) + else: + part = text[offset * 2:] + result += quote_fn(part.decode('utf-16-le')) + + return result + @property def md_text(self) -> str: """ @@ -164,28 +209,16 @@ class Message(base.TelegramObject): :return: str """ - text = self.caption if self.caption else self.text - - if self.text and self.entities: - for entity in reversed(self.entities): - text = entity.apply_md(text) - - return text + return self.parse_entities(False) @property def html_text(self) -> str: """ - Text or caption formatted as HTML. + Text or caption formatted as HTML :return: str """ - text = self.caption if self.caption else self.text - - if self.text and self.entities: - for entity in reversed(self.entities): - text = entity.apply_html(text) - - return text + return self.parse_entities(True) async def reply(self, text, parse_mode=None, disable_web_page_preview=None, disable_notification=None, reply_markup=None, reply=True) -> 'Message': diff --git a/aiogram/types/message_entity.py b/aiogram/types/message_entity.py index 24e6da5f..6ca81518 100644 --- a/aiogram/types/message_entity.py +++ b/aiogram/types/message_entity.py @@ -1,3 +1,5 @@ +import sys + from . import base from . import fields from .user import User @@ -16,56 +18,63 @@ class MessageEntity(base.TelegramObject): url: base.String = fields.Field() user: User = fields.Field(base=User) - def _apply(self, text, func): - return text[:self.offset] + \ - func(text[self.offset:self.offset + self.length]) + \ - text[self.offset + self.length:] - - def apply_md(self, text): + def get_text(self, text): """ - Apply entity for text as Markdown + Get value of entity - :param text: - :return: + :param text: full text + :return: part of text """ + if sys.maxunicode == 0xffff: + return text[self.offset:self.offset + self.length] + + if not isinstance(text, bytes): + entity_text = text.encode('utf-16-le') + else: + entity_text = text + + entity_text = entity_text[self.offset * 2:(self.offset + self.length) * 2] + return entity_text.decode('utf-16-le') + + def parse(self, text, as_html=True): + """ + Get entity value with markup + + :param text: original text + :param as_html: as html? + :return: entity text with markup + """ + if not text: + return text + entity_text = self.get_text(text) + if self.type == MessageEntityType.BOLD: - return self._apply(text, markdown.bold) + if as_html: + return markdown.hbold(entity_text) + return markdown.bold(entity_text) elif self.type == MessageEntityType.ITALIC: - return self._apply(text, markdown.italic) + if as_html: + return markdown.hitalic(entity_text) + return markdown.italic(entity_text) elif self.type == MessageEntityType.PRE: - return self._apply(text, markdown.pre) + if as_html: + return markdown.hpre(entity_text) + return markdown.pre(entity_text) elif self.type == MessageEntityType.CODE: - return self._apply(text, markdown.code) + if as_html: + return markdown.hcode(entity_text) + return markdown.code(entity_text) elif self.type == MessageEntityType.URL: - return self._apply(text, lambda url: markdown.link(url, url)) + if as_html: + return markdown.hlink(entity_text, entity_text) + return markdown.link(entity_text, entity_text) elif self.type == MessageEntityType.TEXT_LINK: - return self._apply(text, lambda url: markdown.link(url, self.url)) - if self.type == MessageEntityType.TEXT_MENTION and self.user: - return self._apply(text, lambda name: self.user.get_mention(name, as_html=False)) - return text - - def apply_html(self, text): - """ - Apply entity for text as HTML - - :param text: - :return: - """ - if self.type == MessageEntityType.BOLD: - return self._apply(text, markdown.hbold) - elif self.type == MessageEntityType.ITALIC: - return self._apply(text, markdown.hitalic) - elif self.type == MessageEntityType.PRE: - return self._apply(text, markdown.hpre) - elif self.type == MessageEntityType.CODE: - return self._apply(text, markdown.hcode) - elif self.type == MessageEntityType.URL: - return self._apply(text, lambda url: markdown.hlink(url, url)) - elif self.type == MessageEntityType.TEXT_LINK: - return self._apply(text, lambda url: markdown.hlink(url, self.url)) - if self.type == MessageEntityType.TEXT_MENTION and self.user: - return self._apply(text, lambda name: self.user.get_mention(name, as_html=True)) - return text + if as_html: + return markdown.hlink(entity_text, self.url) + return markdown.link(entity_text, self.url) + elif self.type == MessageEntityType.TEXT_MENTION and self.user: + return self.user.get_mention(entity_text) + return entity_text class MessageEntityType(helper.Helper):