diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index d6005bd8..a290ca0f 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -23,3 +23,4 @@ __version__ = "0.4.2" from .api.errors import Error from .client import ChatAction from .client import Client +from .client import ParseMode diff --git a/pyrogram/client/__init__.py b/pyrogram/client/__init__.py index 99c4337f..380f0cb4 100644 --- a/pyrogram/client/__init__.py +++ b/pyrogram/client/__init__.py @@ -18,3 +18,4 @@ from .chat_action import ChatAction from .client import Client +from .parse_mode import ParseMode diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 131ce037..860b18d1 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -47,8 +47,8 @@ from pyrogram.api.types import ( InputPeerUser, InputPeerChat, InputPeerChannel ) from pyrogram.crypto import CTR -from pyrogram.extensions import Markdown from pyrogram.session import Auth, Session +from .style import Markdown, HTML log = logging.getLogger(__name__) @@ -89,6 +89,7 @@ class Client: self.peers_by_username = {} self.markdown = Markdown(self.peers_by_id) + self.html = HTML(self.peers_by_id) self.config = None self.proxy = None @@ -471,6 +472,7 @@ class Client: def send_message(self, chat_id: int or str, text: str, + parse_mode: str = "", disable_web_page_preview: bool = None, disable_notification: bool = None, reply_to_msg_id: int = None): @@ -485,6 +487,11 @@ class Client: text (:obj:`str`): Text of the message to be sent. + parse_mode (:obj:`str`): + Use :obj:`pyrogram.ParseMode.MARKDOWN` or :obj:`pyrogram.ParseMode.HTML` if you want Telegram apps + to show bold, italic, fixed-width text or inline URLs in your message. + Defaults to MARKDOWN. + disable_web_page_preview (:obj:`bool`, optional): Disables link previews for links in this message. @@ -501,6 +508,8 @@ class Client: Raises: :class:`pyrogram.Error` """ + style = self.html if parse_mode.lower() == "html" else self.markdown + return self.send( functions.messages.SendMessage( peer=self.resolve_peer(chat_id), @@ -508,7 +517,7 @@ class Client: silent=disable_notification or None, reply_to_msg_id=reply_to_msg_id, random_id=self.rnd_id(), - **self.markdown.parse(text) + **style.parse(text) ) ) @@ -557,6 +566,7 @@ class Client: chat_id: int or str, photo: str, caption: str = "", + parse_mode: str = "", ttl_seconds: int = None, disable_notification: bool = None, reply_to_message_id: int = None): @@ -575,6 +585,11 @@ class Client: caption (:obj:`bool`, optional): Photo caption, 0-200 characters. + parse_mode (:obj:`str`): + Use :obj:`pyrogram.ParseMode.MARKDOWN` or :obj:`pyrogram.ParseMode.HTML` if you want Telegram apps + to show bold, italic, fixed-width text or inline URLs in your caption. + Defaults to MARKDOWN. + ttl_seconds (:obj:`int`, optional): Self-Destruct Timer. If you set a timer, the photo will self-destruct in :obj:`ttl_seconds` @@ -593,6 +608,7 @@ class Client: Raises: :class:`pyrogram.Error` """ + style = self.html if parse_mode.lower() == "html" else self.markdown file = self.save_file(photo) while True: @@ -607,7 +623,7 @@ class Client: silent=disable_notification or None, reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), - **self.markdown.parse(caption) + **style.parse(caption) ) ) except FilePartMissing as e: @@ -619,6 +635,7 @@ class Client: chat_id: int or str, audio: str, caption: str = "", + parse_mode: str = "", duration: int = 0, performer: str = None, title: str = None, @@ -641,6 +658,11 @@ class Client: caption (:obj:`str`, optional): Audio caption, 0-200 characters. + parse_mode (:obj:`str`): + Use :obj:`pyrogram.ParseMode.MARKDOWN` or :obj:`pyrogram.ParseMode.HTML` if you want Telegram apps + to show bold, italic, fixed-width text or inline URLs in your caption. + Defaults to MARKDOWN. + duration (:obj:`int`, optional): Duration of the audio in seconds. @@ -663,6 +685,7 @@ class Client: Raises: :class:`pyrogram.Error` """ + style = self.html if parse_mode.lower() == "html" else self.markdown file = self.save_file(audio) while True: @@ -685,7 +708,7 @@ class Client: silent=disable_notification or None, reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), - **self.markdown.parse(caption) + **style.parse(caption) ) ) except FilePartMissing as e: @@ -697,6 +720,7 @@ class Client: chat_id: int or str, document: str, caption: str = "", + parse_mode: str = "", disable_notification: bool = None, reply_to_message_id: int = None): """Use this method to send general files. @@ -714,6 +738,11 @@ class Client: caption (:obj:`str`, optional): Document caption, 0-200 characters. + parse_mode (:obj:`str`): + Use :obj:`pyrogram.ParseMode.MARKDOWN` or :obj:`pyrogram.ParseMode.HTML` if you want Telegram apps + to show bold, italic, fixed-width text or inline URLs in your caption. + Defaults to MARKDOWN. + disable_notification (:obj:`bool`, optional): Sends the message silently. Users will receive a notification with no sound. @@ -727,6 +756,7 @@ class Client: Raises: :class:`pyrogram.Error` """ + style = self.html if parse_mode.lower() == "html" else self.markdown file = self.save_file(document) while True: @@ -744,7 +774,7 @@ class Client: silent=disable_notification or None, reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), - **self.markdown.parse(caption) + **style.parse(caption) ) ) except FilePartMissing as e: @@ -755,10 +785,11 @@ class Client: def send_video(self, chat_id: int or str, video: str, + caption: str = "", + parse_mode: str = "", duration: int = 0, width: int = 0, height: int = 0, - caption: str = "", disable_notification: bool = None, reply_to_message_id: int = None): """Use this method to send video files. @@ -773,6 +804,14 @@ class Client: Video to send. Pass a file path as string to send a video that exists on your local machine. + caption (:obj:`str`, optional): + Video caption, 0-200 characters. + + parse_mode (:obj:`str`): + Use :obj:`pyrogram.ParseMode.MARKDOWN` or :obj:`pyrogram.ParseMode.HTML` if you want Telegram apps + to show bold, italic, fixed-width text or inline URLs in your caption. + Defaults to MARKDOWN. + duration (:obj:`int`, optional): Duration of sent video in seconds. @@ -782,9 +821,6 @@ class Client: height (:obj:`int`, optional): Video height. - caption (:obj:`str`, optional): - Video caption, 0-200 characters. - disable_notification (:obj:`bool`, optional): Sends the message silently. Users will receive a notification with no sound. @@ -798,6 +834,7 @@ class Client: Raises: :class:`pyrogram.Error` """ + style = self.html if parse_mode.lower() == "html" else self.markdown file = self.save_file(video) while True: @@ -819,7 +856,7 @@ class Client: silent=disable_notification or None, reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), - **self.markdown.parse(caption) + **style.parse(caption) ) ) except FilePartMissing as e: @@ -831,6 +868,7 @@ class Client: chat_id: int or str, voice: str, caption: str = "", + parse_mode: str = "", duration: int = 0, disable_notification: bool = None, reply_to_message_id: int = None): @@ -849,6 +887,11 @@ class Client: caption (:obj:`str`, optional): Voice message caption, 0-200 characters. + parse_mode (:obj:`str`): + Use :obj:`pyrogram.ParseMode.MARKDOWN` or :obj:`pyrogram.ParseMode.HTML` if you want Telegram apps + to show bold, italic, fixed-width text or inline URLs in your caption. + Defaults to MARKDOWN. + duration (:obj:`int`, optional): Duration of the voice message in seconds. @@ -865,6 +908,7 @@ class Client: Raises: :class:`pyrogram.Error` """ + style = self.html if parse_mode.lower() == "html" else self.markdown file = self.save_file(voice) while True: @@ -885,7 +929,7 @@ class Client: silent=disable_notification or None, reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), - **self.markdown.parse(caption) + **style.parse(caption) ) ) except FilePartMissing as e: @@ -1192,6 +1236,7 @@ class Client: chat_id: int or str, message_id: int, text: str, + parse_mode: str = "", disable_web_page_preview: bool = None): """Use this method to edit text messages. @@ -1207,25 +1252,33 @@ class Client: text (:obj:`str`): New text of the message. + parse_mode (:obj:`str`): + Use :obj:`pyrogram.ParseMode.MARKDOWN` or :obj:`pyrogram.ParseMode.HTML` if you want Telegram apps + to show bold, italic, fixed-width text or inline URLs in your message. + Defaults to MARKDOWN. + disable_web_page_preview (:obj:`bool`, optional): Disables link previews for links in this message. Raises: :class:`pyrogram.Error` """ + style = self.html if parse_mode.lower() == "html" else self.markdown + return self.send( functions.messages.EditMessage( peer=self.resolve_peer(chat_id), id=message_id, no_webpage=disable_web_page_preview or None, - **self.markdown.parse(text) + **style.parse(text) ) ) def edit_message_caption(self, chat_id: int or str, message_id: int, - caption: str): + caption: str, + parse_mode: str = ""): """Use this method to edit captions of messages. Args: @@ -1240,14 +1293,21 @@ class Client: caption (:obj:`str`): New caption of the message. + parse_mode (:obj:`str`): + Use :obj:`pyrogram.ParseMode.MARKDOWN` or :obj:`pyrogram.ParseMode.HTML` if you want Telegram apps + to show bold, italic, fixed-width text or inline URLs in your caption. + Defaults to MARKDOWN. + Raises: :class:`pyrogram.Error` """ + style = self.html if parse_mode.lower() == "html" else self.markdown + return self.send( functions.messages.EditMessage( peer=self.resolve_peer(chat_id), id=message_id, - **self.markdown.parse(caption) + **style.parse(caption) ) ) diff --git a/pyrogram/client/parse_mode.py b/pyrogram/client/parse_mode.py new file mode 100644 index 00000000..668bb9c8 --- /dev/null +++ b/pyrogram/client/parse_mode.py @@ -0,0 +1,29 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2018 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + + +class ParseMode: + """This class provides a convenient access to parse modes. + It is intended to be used with any method that accepts the optional argument **parse_mode** + """ + + HTML = "html" + """Set the parse mode to HTML style""" + + MARKDOWN = "markdown" + """Set the parse mode to Markdown style""" diff --git a/pyrogram/extensions/__init__.py b/pyrogram/client/style/__init__.py similarity index 97% rename from pyrogram/extensions/__init__.py rename to pyrogram/client/style/__init__.py index cd918a09..e60b4da1 100644 --- a/pyrogram/extensions/__init__.py +++ b/pyrogram/client/style/__init__.py @@ -16,4 +16,5 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from .html import HTML from .markdown import Markdown diff --git a/pyrogram/client/style/html.py b/pyrogram/client/style/html.py new file mode 100644 index 00000000..28091aa0 --- /dev/null +++ b/pyrogram/client/style/html.py @@ -0,0 +1,82 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2018 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +import re + +from pyrogram.api.types import ( + MessageEntityBold as Bold, + MessageEntityItalic as Italic, + MessageEntityCode as Code, + MessageEntityTextUrl as Url, + MessageEntityPre as Pre, + MessageEntityMentionName as MentionInvalid, + InputMessageEntityMentionName as Mention, +) +from . import utils + + +class HTML: + SMP_RE = re.compile(r"[\U00010000-\U0010FFFF]") + HTML_RE = re.compile(r"<(\w+)(?: href=\"(.*)\")?>(.*)") + MENTION_RE = re.compile(r"tg://user\?id=(\d+)") + + def __init__(self, peers_by_id): + self.peers_by_id = peers_by_id + + def parse(self, text): + entities = [] + text = utils.add_surrogates(text) + offset = 0 + + for match in self.HTML_RE.finditer(text): + start = match.start() - offset + style, url, body = match.groups() + + if url: + mention = self.MENTION_RE.match(url) + + if mention: + user_id = int(mention.group(1)) + input_user = self.peers_by_id.get(user_id, None) + + entity = ( + Mention(start, len(body), input_user) + if input_user else MentionInvalid(start, len(body), user_id) + ) + else: + entity = Url(start, len(body), url) + else: + if style == "b" or style == "strong": + entity = Bold(start, len(body)) + elif style == "i" or style == "em": + entity = Italic(start, len(body)) + elif style == "code": + entity = Code(start, len(body)) + elif style == "pre": + entity = Pre(start, len(body), "") + else: + continue + + entities.append(entity) + text = text.replace(match.group(), body) + offset += len(style) * 2 + 5 + (len(url) + 8 if url else 0) + + return dict( + message=utils.remove_surrogates(text), + entities=entities + ) diff --git a/pyrogram/extensions/markdown.py b/pyrogram/client/style/markdown.py similarity index 83% rename from pyrogram/extensions/markdown.py rename to pyrogram/client/style/markdown.py index b1b467f4..8e0c9462 100644 --- a/pyrogram/extensions/markdown.py +++ b/pyrogram/client/style/markdown.py @@ -17,7 +17,6 @@ # along with Pyrogram. If not, see . import re -from struct import unpack from pyrogram.api.types import ( MessageEntityBold as Bold, @@ -27,6 +26,7 @@ from pyrogram.api.types import ( MessageEntityPre as Pre, InputMessageEntityMentionName as Mention ) +from . import utils class Markdown: @@ -36,9 +36,6 @@ class Markdown: "`": Code } - # SMP = Supplementary Multilingual Plane: https://en.wikipedia.org/wiki/Plane_(Unicode)#Overview - SMP_RE = re.compile(r"[\U00010000-\U0010FFFF]") - # ``` python # for i in range(10): # print(i) @@ -69,26 +66,12 @@ class Markdown: MARKDOWN_RE = re.compile("|".join([PRE_RE, MENTION_RE, URL_RE, INLINE_RE])) - @classmethod - def add_surrogates(cls, text): - # Replace each SMP code point with a surrogate pair - return cls.SMP_RE.sub( - lambda match: # Split SMP in two surrogates - "".join(chr(i) for i in unpack(" +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +import re +from struct import unpack + +# SMP = Supplementary Multilingual Plane: https://en.wikipedia.org/wiki/Plane_(Unicode)#Overview +SMP_RE = re.compile(r"[\U00010000-\U0010FFFF]") + + +def add_surrogates(text): + # Replace each SMP code point with a surrogate pair + return SMP_RE.sub( + lambda match: # Split SMP in two surrogates + "".join(chr(i) for i in unpack("