diff --git a/docs/source/pyrogram/Client.rst b/docs/source/pyrogram/Client.rst index d2c35501..e879db71 100644 --- a/docs/source/pyrogram/Client.rst +++ b/docs/source/pyrogram/Client.rst @@ -4,6 +4,7 @@ Client .. currentmodule:::: pyrogram.Client .. autoclass:: pyrogram.Client + :inherited-members: :members: **Available methods** @@ -49,9 +50,12 @@ Client change_cloud_password remove_cloud_password add_contacts + get_contacts delete_contacts get_inline_bot_results send_inline_bot_result + answer_callback_query get_users + get_chat get_messages - answer_callback_query \ No newline at end of file + get_history \ No newline at end of file diff --git a/examples/callback_query_handler.py b/examples/callback_query_handler.py index 3aa5b11c..6b76edd8 100644 --- a/examples/callback_query_handler.py +++ b/examples/callback_query_handler.py @@ -1,3 +1,21 @@ +# 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 . + from pyrogram import Client """This example shows how to handle callback queries, i.e.: queries coming from inline button presses. diff --git a/examples/echo_bot.py b/examples/echo_bot.py index dcf178b5..ed2d4df5 100644 --- a/examples/echo_bot.py +++ b/examples/echo_bot.py @@ -1,3 +1,21 @@ +# 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 . + from pyrogram import Client, Filters """This simple echo bot replies to every private text message. diff --git a/examples/get_history.py b/examples/get_history.py index b608668b..2afef42f 100644 --- a/examples/get_history.py +++ b/examples/get_history.py @@ -1,3 +1,21 @@ +# 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 time from pyrogram import Client diff --git a/examples/get_participants.py b/examples/get_participants.py index 810a0ee2..d10710ba 100644 --- a/examples/get_participants.py +++ b/examples/get_participants.py @@ -1,3 +1,21 @@ +# 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 time from pyrogram import Client diff --git a/examples/get_participants2.py b/examples/get_participants2.py index b27a8f75..dfad315b 100644 --- a/examples/get_participants2.py +++ b/examples/get_participants2.py @@ -1,3 +1,21 @@ +# 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 time from string import ascii_lowercase diff --git a/examples/hello_world.py b/examples/hello_world.py index c4ff23d0..8dba8417 100644 --- a/examples/hello_world.py +++ b/examples/hello_world.py @@ -1,3 +1,21 @@ +# 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 . + from pyrogram import Client """This example demonstrates a basic API usage""" diff --git a/examples/query_inline_bots.py b/examples/query_inline_bots.py index 0d75b9ff..0d5f7560 100644 --- a/examples/query_inline_bots.py +++ b/examples/query_inline_bots.py @@ -1,3 +1,21 @@ +# 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 . + from pyrogram import Client """This example shows how to query an inline bot""" diff --git a/examples/raw_update_handler.py b/examples/raw_update_handler.py index 2590c64b..eb417c24 100644 --- a/examples/raw_update_handler.py +++ b/examples/raw_update_handler.py @@ -1,3 +1,21 @@ +# 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 . + from pyrogram import Client """This example shows how to handle raw updates""" diff --git a/examples/send_bot_keyboards.py b/examples/send_bot_keyboards.py index 21c1a60e..517b0696 100644 --- a/examples/send_bot_keyboards.py +++ b/examples/send_bot_keyboards.py @@ -1,3 +1,21 @@ +# 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 . + from pyrogram import Client, ReplyKeyboardMarkup, InlineKeyboardMarkup, InlineKeyboardButton """This example will show you how to send normal and inline keyboards. diff --git a/examples/welcome_bot.py b/examples/welcome_bot.py index 10cf5518..8e087728 100644 --- a/examples/welcome_bot.py +++ b/examples/welcome_bot.py @@ -1,3 +1,21 @@ +# 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 . + from pyrogram import Client, Emoji, Filters """This is the Welcome Bot in @PyrogramChat. diff --git a/pyrogram/client/__init__.py b/pyrogram/client/__init__.py index b7819bd8..aac1b9e0 100644 --- a/pyrogram/client/__init__.py +++ b/pyrogram/client/__init__.py @@ -16,9 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from .chat_action import ChatAction from .client import Client -from .emoji import Emoji +from .ext import BaseClient, ChatAction, Emoji, ParseMode from .filters import Filters from .handlers import MessageHandler, CallbackQueryHandler, RawUpdateHandler -from .parse_mode import ParseMode diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index a8989427..561b70ec 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -32,28 +32,23 @@ import time from configparser import ConfigParser from datetime import datetime from hashlib import sha256, md5 -from queue import Queue from signal import signal, SIGINT, SIGTERM, SIGABRT from threading import Event, Thread -import pyrogram from pyrogram.api import functions, types from pyrogram.api.core import Object from pyrogram.api.errors import ( PhoneMigrate, NetworkMigrate, PhoneNumberInvalid, PhoneNumberUnoccupied, PhoneCodeInvalid, PhoneCodeHashEmpty, PhoneCodeExpired, PhoneCodeEmpty, SessionPasswordNeeded, - PasswordHashInvalid, FloodWait, PeerIdInvalid, FilePartMissing, - ChatAdminRequired, FirstnameInvalid, PhoneNumberBanned, - VolumeLocNotFound, UserMigrate, FileIdInvalid, UnknownError) + PasswordHashInvalid, FloodWait, PeerIdInvalid, FirstnameInvalid, PhoneNumberBanned, + VolumeLocNotFound, UserMigrate, FileIdInvalid) from pyrogram.crypto import AES from pyrogram.session import Auth, Session -from pyrogram.session.internals import MsgId from . import types as pyrogram_types -from . import utils, ChatAction from .dispatcher import Dispatcher -from .style import Markdown, HTML -from .syncer import Syncer +from .ext import utils, Syncer, BaseClient +from .methods import Methods # Custom format for nice looking log lines LOG_FORMAT = "[%(asctime)s.%(msecs)03d] %(filename)s:%(lineno)s %(levelname)s: %(message)s" @@ -61,7 +56,7 @@ LOG_FORMAT = "[%(asctime)s.%(msecs)03d] %(filename)s:%(lineno)s %(levelname)s: % log = logging.getLogger(__name__) -class Client: +class Client(Methods, BaseClient): """This class represents a Client, the main mean for interacting with Telegram. It exposes bot-like methods for an easy access to the API as well as a simple way to invoke every single Telegram API method available. @@ -126,26 +121,6 @@ class Client: where Pyrogram will store your session files. Defaults to "." (current directory). """ - INVITE_LINK_RE = re.compile(r"^(?:https?://)?(?:t\.me/joinchat/)([\w-]+)$") - BOT_TOKEN_RE = re.compile(r"^\d+:[\w-]+$") - DIALOGS_AT_ONCE = 100 - UPDATES_WORKERS = 1 - DOWNLOAD_WORKERS = 1 - OFFLINE_SLEEP = 300 - - MEDIA_TYPE_ID = { - 0: "thumbnail", - 1: "thumbnail", - 2: "photo", - 3: "voice", - 4: "video", - 5: "document", - 8: "sticker", - 9: "audio", - 10: "gif", - 13: "video_note" - } - def __init__(self, session_name: str, api_id: int or str = None, @@ -160,147 +135,24 @@ class Client: last_name: str = None, workers: int = 4, workdir: str = "."): + super().__init__() + self.session_name = session_name self.api_id = int(api_id) if api_id else None self.api_hash = api_hash self.proxy = proxy self.test_mode = test_mode - self.phone_number = phone_number - self.password = password self.phone_code = phone_code + self.password = password + self.force_sms = force_sms self.first_name = first_name self.last_name = last_name - self.force_sms = force_sms - self.workers = workers self.workdir = workdir - self.token = None - - self.dc_id = None - self.auth_key = None - self.user_id = None - self.date = None - - self.rnd_id = MsgId - - self.peers_by_id = {} - self.peers_by_username = {} - self.peers_by_phone = {} - - self.channels_pts = {} - - self.markdown = Markdown(self.peers_by_id) - self.html = HTML(self.peers_by_id) - - self.session = None - self.media_sessions = {} - self.media_sessions_lock = threading.Lock() - - self.is_started = None - self.is_idle = None - - self.updates_queue = Queue() - self.updates_workers_list = [] - self.download_queue = Queue() - self.download_workers_list = [] - self.dispatcher = Dispatcher(self, workers) - def on_message(self, filters=None, group: int = 0): - """Use this decorator to automatically register a function for handling - messages. This does the same thing as :meth:`add_handler` using the - MessageHandler. - - Args: - filters (:obj:`Filters `): - Pass one or more filters to allow only a subset of messages to be passed - in your function. - - group (``int``, *optional*): - The group identifier, defaults to 0. - """ - - def decorator(func): - self.add_handler(pyrogram.MessageHandler(func, filters), group) - return func - - return decorator - - def on_callback_query(self, filters=None, group: int = 0): - """Use this decorator to automatically register a function for handling - callback queries. This does the same thing as :meth:`add_handler` using the - CallbackQueryHandler. - - Args: - filters (:obj:`Filters `): - Pass one or more filters to allow only a subset of callback queries to be passed - in your function. - - group (``int``, *optional*): - The group identifier, defaults to 0. - """ - - def decorator(func): - self.add_handler(pyrogram.CallbackQueryHandler(func, filters), group) - return func - - return decorator - - def on_raw_update(self, group: int = 0): - """Use this decorator to automatically register a function for handling - raw updates. This does the same thing as :meth:`add_handler` using the - RawUpdateHandler. - - Args: - group (``int``, *optional*): - The group identifier, defaults to 0. - """ - - def decorator(func): - self.add_handler(pyrogram.RawUpdateHandler(func), group) - return func - - return decorator - - def add_handler(self, handler, group: int = 0): - """Use this method to register an update handler. - - You can register multiple handlers, but at most one handler within a group - will be used for a single update. To handle the same update more than once, register - your handler using a different group id (lower group id == higher priority). - - Args: - handler (``Handler``): - The handler to be registered. - - group (``int``, *optional*): - The group identifier, defaults to 0. - - Returns: - A tuple of (handler, group) - """ - self.dispatcher.add_handler(handler, group) - - return handler, group - - def remove_handler(self, handler, group: int = 0): - """Removes a previously-added update handler. - - Make sure to provide the right group that the handler was added in. You can use - the return value of the :meth:`add_handler` method, a tuple of (handler, group), and - pass it directly. - - Args: - handler (``Handler``): - The handler to be removed. - - group (``int``, *optional*): - The group identifier, defaults to 0. - """ - self.dispatcher.remove_handler(handler, group) - def start(self, debug: bool = False): """Use this method to start the Client after creating it. Requires no parameters. @@ -417,6 +269,43 @@ class Client: self.is_started = False self.session.stop() + def add_handler(self, handler, group: int = 0): + """Use this method to register an update handler. + + You can register multiple handlers, but at most one handler within a group + will be used for a single update. To handle the same update more than once, register + your handler using a different group id (lower group id == higher priority). + + Args: + handler (``Handler``): + The handler to be registered. + + group (``int``, *optional*): + The group identifier, defaults to 0. + + Returns: + A tuple of (handler, group) + """ + self.dispatcher.add_handler(handler, group) + + return handler, group + + def remove_handler(self, handler, group: int = 0): + """Removes a previously-added update handler. + + Make sure to provide the right group that the handler was added in. You can use + the return value of the :meth:`add_handler` method, a tuple of (handler, group), and + pass it directly. + + Args: + handler (``Handler``): + The handler to be removed. + + group (``int``, *optional*): + The group identifier, defaults to 0. + """ + self.dispatcher.remove_handler(handler, group) + def authorize_bot(self): try: r = self.send( @@ -1090,1792 +979,6 @@ class Client: except (KeyError, ValueError): raise PeerIdInvalid - def get_me(self): - """A simple method for testing the user authorization. Requires no parameters. - - Returns: - Full information about the user in form of a :obj:`UserFull ` object. - - Raises: - :class:`Error ` - """ - return self.send( - functions.users.GetFullUser( - types.InputPeerSelf() - ) - ) - - 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_message_id: int = None, - reply_markup=None): - """Use this method to send text messages. - - Args: - chat_id (``int`` | ``str``): - Unique identifier (int) or username (str) of the target chat. - For your personal cloud (Saved Messages) you can simply use "me" or "self". - For a contact that exists in your Telegram address book you can use his phone number (str). - For a private channel/supergroup you can use its *t.me/joinchat/* link. - - text (``str``): - Text of the message to be sent. - - parse_mode (``str``): - Use :obj:`MARKDOWN ` or :obj:`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 (``bool``, *optional*): - Disables link previews for links in this message. - - disable_notification (``bool``, *optional*): - Sends the message silently. - Users will receive a notification with no sound. - - reply_to_message_id (``bool``, *optional*): - If the message is a reply, ID of the original message. - - reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): - Additional interface options. An object for an inline keyboard, custom reply keyboard, - instructions to remove reply keyboard or to force a reply from the user. - - Returns: - On success, the sent Message is returned. - - Raises: - :class:`Error ` - """ - style = self.html if parse_mode.lower() == "html" else self.markdown - - r = self.send( - functions.messages.SendMessage( - peer=self.resolve_peer(chat_id), - no_webpage=disable_web_page_preview or None, - silent=disable_notification or None, - reply_to_msg_id=reply_to_message_id, - random_id=self.rnd_id(), - reply_markup=reply_markup.write() if reply_markup else None, - **style.parse(text) - ) - ) - - if isinstance(r, types.UpdateShortSentMessage): - return pyrogram_types.Message( - message_id=r.id, - date=r.date, - outgoing=r.out, - entities=utils.parse_entities(r.entities, {}) or None - ) - - for i in r.updates: - if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - users = {i.id: i for i in r.users} - chats = {i.id: i for i in r.chats} - - return utils.parse_message(self, i.message, users, chats) - - def forward_messages(self, - chat_id: int or str, - from_chat_id: int or str, - message_ids, - disable_notification: bool = None): - """Use this method to forward messages of any kind. - - Args: - chat_id (``int`` | ``str``): - Unique identifier (int) or username (str) of the target chat. - For your personal cloud (Saved Messages) you can simply use "me" or "self". - For a contact that exists in your Telegram address book you can use his phone number (str). - For a private channel/supergroup you can use its *t.me/joinchat/* link. - - from_chat_id (``int`` | ``str``): - Unique identifier (int) or username (str) of the source chat where the original message was sent. - For your personal cloud (Saved Messages) you can simply use "me" or "self". - For a contact that exists in your Telegram address book you can use his phone number (str). - For a private channel/supergroup you can use its *t.me/joinchat/* link. - - message_ids (``iterable``): - A list of Message identifiers in the chat specified in *from_chat_id* or a single message id. - Iterators and Generators are also accepted. - - disable_notification (``bool``, *optional*): - Sends the message silently. - Users will receive a notification with no sound. - - Returns: - On success and in case *message_ids* was a list, the returned value will be a list of the forwarded - :obj:`Messages ` even if a list contains just one element, otherwise if - *message_ids* was an integer, the single forwarded :obj:`Message ` - is returned. - - Raises: - :class:`Error ` - """ - is_iterable = not isinstance(message_ids, int) - message_ids = list(message_ids) if is_iterable else [message_ids] - - r = self.send( - functions.messages.ForwardMessages( - to_peer=self.resolve_peer(chat_id), - from_peer=self.resolve_peer(from_chat_id), - id=message_ids, - silent=disable_notification or None, - random_id=[self.rnd_id() for _ in message_ids] - ) - ) - - messages = [] - - users = {i.id: i for i in r.users} - chats = {i.id: i for i in r.chats} - - for i in r.updates: - if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - messages.append( - utils.parse_message(self, i.message, users, chats) - ) - - return messages if is_iterable else messages[0] - - def send_photo(self, - 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, - reply_markup=None, - progress: callable = None, - progress_args: tuple = ()): - """Use this method to send photos. - - Args: - chat_id (``int`` | ``str``): - Unique identifier (int) or username (str) of the target chat. - For your personal cloud (Saved Messages) you can simply use "me" or "self". - For a contact that exists in your Telegram address book you can use his phone number (str). - For a private channel/supergroup you can use its *t.me/joinchat/* link. - - photo (``str``): - Photo to send. - Pass a file_id as string to send a photo that exists on the Telegram servers, - pass an HTTP URL as a string for Telegram to get a photo from the Internet, or - pass a file path as string to upload a new photo that exists on your local machine. - - caption (``bool``, *optional*): - Photo caption, 0-200 characters. - - parse_mode (``str``): - Use :obj:`MARKDOWN ` or :obj:`HTML ` - if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your caption. - Defaults to Markdown. - - ttl_seconds (``int``, *optional*): - Self-Destruct Timer. - If you set a timer, the photo will self-destruct in *ttl_seconds* - seconds after it was viewed. - - disable_notification (``bool``, *optional*): - Sends the message silently. - Users will receive a notification with no sound. - - reply_to_message_id (``int``, *optional*): - If the message is a reply, ID of the original message. - - reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): - Additional interface options. An object for an inline keyboard, custom reply keyboard, - instructions to remove reply keyboard or to force a reply from the user. - - progress (``callable``): - Pass a callback function to view the upload progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). - - progress_args (``tuple``): - Extra custom arguments for the progress callback function. Useful, for example, if you want to pass - a chat_id and a message_id in order to edit a message with the updated progress. - - Other Parameters: - client (:obj:`Client `): - The Client itself, useful when you want to call other API methods inside the callback function. - - current (``int``): - The amount of bytes uploaded so far. - - total (``int``): - The size of the file. - - *args (``tuple``): - Extra custom arguments as defined in the *progress_args* parameter. - You can either keep *\*args* or add every single extra argument in your function signature. - - Returns: - On success, the sent :obj:`Message ` is returned. - - Raises: - :class:`Error ` - """ - file = None - style = self.html if parse_mode.lower() == "html" else self.markdown - - if os.path.exists(photo): - file = self.save_file(photo, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedPhoto( - file=file, - ttl_seconds=ttl_seconds - ) - elif photo.startswith("http"): - media = types.InputMediaPhotoExternal( - url=photo, - ttl_seconds=ttl_seconds - ) - else: - try: - decoded = utils.decode(photo) - fmt = " 24 else "` or :obj:`HTML ` - if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your caption. - Defaults to Markdown. - - duration (``int``, *optional*): - Duration of the audio in seconds. - - performer (``str``, *optional*): - Performer. - - title (``str``, *optional*): - Track name. - - disable_notification (``bool``, *optional*): - Sends the message silently. - Users will receive a notification with no sound. - - reply_to_message_id (``int``, *optional*): - If the message is a reply, ID of the original message. - - reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): - Additional interface options. An object for an inline keyboard, custom reply keyboard, - instructions to remove reply keyboard or to force a reply from the user. - - progress (``callable``): - Pass a callback function to view the upload progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). - - progress_args (``tuple``): - Extra custom arguments for the progress callback function. Useful, for example, if you want to pass - a chat_id and a message_id in order to edit a message with the updated progress. - - Other Parameters: - client (:obj:`Client `): - The Client itself, useful when you want to call other API methods inside the callback function. - - current (``int``): - The amount of bytes uploaded so far. - - total (``int``): - The size of the file. - - *args (``tuple``): - Extra custom arguments as defined in the *progress_args* parameter. - You can either keep *\*args* or add every single extra argument in your function signature. - - Returns: - On success, the sent :obj:`Message ` is returned. - - Raises: - :class:`Error ` - """ - file = None - style = self.html if parse_mode.lower() == "html" else self.markdown - - if os.path.exists(audio): - file = self.save_file(audio, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedDocument( - mime_type=mimetypes.types_map.get("." + audio.split(".")[-1], "audio/mpeg"), - file=file, - attributes=[ - types.DocumentAttributeAudio( - duration=duration, - performer=performer, - title=title - ), - types.DocumentAttributeFilename(os.path.basename(audio)) - ] - ) - elif audio.startswith("http"): - media = types.InputMediaDocumentExternal( - url=audio - ) - else: - try: - decoded = utils.decode(audio) - fmt = " 24 else "` or :obj:`HTML ` - if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your caption. - Defaults to Markdown. - - disable_notification (``bool``, *optional*): - Sends the message silently. - Users will receive a notification with no sound. - - reply_to_message_id (``int``, *optional*): - If the message is a reply, ID of the original message. - - reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): - Additional interface options. An object for an inline keyboard, custom reply keyboard, - instructions to remove reply keyboard or to force a reply from the user. - - progress (``callable``): - Pass a callback function to view the upload progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). - - progress_args (``tuple``): - Extra custom arguments for the progress callback function. Useful, for example, if you want to pass - a chat_id and a message_id in order to edit a message with the updated progress. - - Other Parameters: - client (:obj:`Client `): - The Client itself, useful when you want to call other API methods inside the callback function. - - current (``int``): - The amount of bytes uploaded so far. - - total (``int``): - The size of the file. - - *args (``tuple``): - Extra custom arguments as defined in the *progress_args* parameter. - You can either keep *\*args* or add every single extra argument in your function signature. - - Returns: - On success, the sent :obj:`Message ` is returned. - - Raises: - :class:`Error ` - """ - file = None - style = self.html if parse_mode.lower() == "html" else self.markdown - - if os.path.exists(document): - file = self.save_file(document, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedDocument( - mime_type=mimetypes.types_map.get("." + document.split(".")[-1], "text/plain"), - file=file, - attributes=[ - types.DocumentAttributeFilename(os.path.basename(document)) - ] - ) - elif document.startswith("http"): - media = types.InputMediaDocumentExternal( - url=document - ) - else: - try: - decoded = utils.decode(document) - fmt = " 24 else "`): - The Client itself, useful when you want to call other API methods inside the callback function. - - current (``int``): - The amount of bytes uploaded so far. - - total (``int``): - The size of the file. - - *args (``tuple``): - Extra custom arguments as defined in the *progress_args* parameter. - You can either keep *\*args* or add every single extra argument in your function signature. - - Returns: - On success, the sent :obj:`Message ` is returned. - - Raises: - :class:`Error ` - """ - file = None - - if os.path.exists(sticker): - file = self.save_file(sticker, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedDocument( - mime_type="image/webp", - file=file, - attributes=[ - types.DocumentAttributeFilename(os.path.basename(sticker)) - ] - ) - elif sticker.startswith("http"): - media = types.InputMediaDocumentExternal( - url=sticker - ) - else: - try: - decoded = utils.decode(sticker) - fmt = " 24 else "` or :obj:`HTML ` - if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your caption. - Defaults to Markdown. - - duration (``int``, *optional*): - Duration of sent video in seconds. - - width (``int``, *optional*): - Video width. - - height (``int``, *optional*): - Video height. - - thumb (``str``, *optional*): - Video thumbnail. - Pass a file path as string to send an image that exists on your local machine. - Thumbnail should have 90 or less pixels of width and 90 or less pixels of height. - - supports_streaming (``bool``, *optional*): - Pass True, if the uploaded video is suitable for streaming. - - disable_notification (``bool``, *optional*): - Sends the message silently. - Users will receive a notification with no sound. - - reply_to_message_id (``int``, *optional*): - If the message is a reply, ID of the original message. - - reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): - Additional interface options. An object for an inline keyboard, custom reply keyboard, - instructions to remove reply keyboard or to force a reply from the user. - - progress (``callable``): - Pass a callback function to view the upload progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). - - progress_args (``tuple``): - Extra custom arguments for the progress callback function. Useful, for example, if you want to pass - a chat_id and a message_id in order to edit a message with the updated progress. - - Other Parameters: - client (:obj:`Client `): - The Client itself, useful when you want to call other API methods inside the callback function. - - current (``int``): - The amount of bytes uploaded so far. - - total (``int``): - The size of the file. - - *args (``tuple``): - Extra custom arguments as defined in the *progress_args* parameter. - You can either keep *\*args* or add every single extra argument in your function signature. - - Returns: - On success, the sent :obj:`Message ` is returned. - - Raises: - :class:`Error ` - """ - file = None - style = self.html if parse_mode.lower() == "html" else self.markdown - - if os.path.exists(video): - thumb = None if thumb is None else self.save_file(thumb) - file = self.save_file(video, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedDocument( - mime_type=mimetypes.types_map[".mp4"], - file=file, - thumb=thumb, - attributes=[ - types.DocumentAttributeVideo( - supports_streaming=supports_streaming or None, - duration=duration, - w=width, - h=height - ), - types.DocumentAttributeFilename(os.path.basename(video)) - ] - ) - elif video.startswith("http"): - media = types.InputMediaDocumentExternal( - url=video - ) - else: - try: - decoded = utils.decode(video) - fmt = " 24 else "` or :obj:`HTML ` - if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your caption. - Defaults to Markdown. - - duration (``int``, *optional*): - Duration of the voice message in seconds. - - disable_notification (``bool``, *optional*): - Sends the message silently. - Users will receive a notification with no sound. - - reply_to_message_id (``int``, *optional*): - If the message is a reply, ID of the original message - - reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): - Additional interface options. An object for an inline keyboard, custom reply keyboard, - instructions to remove reply keyboard or to force a reply from the user. - - progress (``callable``): - Pass a callback function to view the upload progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). - - progress_args (``tuple``): - Extra custom arguments for the progress callback function. Useful, for example, if you want to pass - a chat_id and a message_id in order to edit a message with the updated progress. - - Other Parameters: - client (:obj:`Client `): - The Client itself, useful when you want to call other API methods inside the callback function. - - current (``int``): - The amount of bytes uploaded so far. - - total (``int``): - The size of the file. - - *args (``tuple``): - Extra custom arguments as defined in the *progress_args* parameter. - You can either keep *\*args* or add every single extra argument in your function signature. - - Returns: - On success, the sent :obj:`Message ` is returned. - - Raises: - :class:`Error ` - """ - file = None - style = self.html if parse_mode.lower() == "html" else self.markdown - - if os.path.exists(voice): - file = self.save_file(voice, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedDocument( - mime_type=mimetypes.types_map.get("." + voice.split(".")[-1], "audio/mpeg"), - file=file, - attributes=[ - types.DocumentAttributeAudio( - voice=True, - duration=duration - ) - ] - ) - elif voice.startswith("http"): - media = types.InputMediaDocumentExternal( - url=voice - ) - else: - try: - decoded = utils.decode(voice) - fmt = " 24 else "`): - The Client itself, useful when you want to call other API methods inside the callback function. - - current (``int``): - The amount of bytes uploaded so far. - - total (``int``): - The size of the file. - - *args (``tuple``): - Extra custom arguments as defined in the *progress_args* parameter. - You can either keep *\*args* or add every single extra argument in your function signature. - - Returns: - On success, the sent :obj:`Message ` is returned. - - Raises: - :class:`Error ` - """ - file = None - - if os.path.exists(video_note): - file = self.save_file(video_note, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedDocument( - mime_type=mimetypes.types_map[".mp4"], - file=file, - attributes=[ - types.DocumentAttributeVideo( - round_message=True, - duration=duration, - w=length, - h=length - ) - ] - ) - else: - try: - decoded = utils.decode(video_note) - fmt = " 24 else "` or - :obj:`InputMediaVideo ` objects - describing photos and videos to be sent, must include 2–10 items. - - disable_notification (``bool``, *optional*): - Sends the message silently. - Users will receive a notification with no sound. - - reply_to_message_id (``int``, *optional*): - If the message is a reply, ID of the original message. - """ - multi_media = [] - - for i in media: - style = self.html if i.parse_mode.lower() == "html" else self.markdown - - if isinstance(i, pyrogram_types.InputMediaPhoto): - if os.path.exists(i.media): - media = self.send( - functions.messages.UploadMedia( - peer=self.resolve_peer(chat_id), - media=types.InputMediaUploadedPhoto( - file=self.save_file(i.media) - ) - ) - ) - - media = types.InputMediaPhoto( - id=types.InputPhoto( - id=media.photo.id, - access_hash=media.photo.access_hash - ) - ) - else: - try: - decoded = utils.decode(i.media) - fmt = " 24 else " 24 else "` is returned. - - Raises: - :class:`Error ` - """ - r = self.send( - functions.messages.SendMedia( - peer=self.resolve_peer(chat_id), - media=types.InputMediaGeoPoint( - types.InputGeoPoint( - latitude, - longitude - ) - ), - message="", - silent=disable_notification or None, - reply_to_msg_id=reply_to_message_id, - random_id=self.rnd_id(), - reply_markup=reply_markup.write() if reply_markup else None - ) - ) - - for i in r.updates: - if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - users = {i.id: i for i in r.users} - chats = {i.id: i for i in r.chats} - - return utils.parse_message(self, i.message, users, chats) - - def send_venue(self, - chat_id: int or str, - latitude: float, - longitude: float, - title: str, - address: str, - foursquare_id: str = "", - disable_notification: bool = None, - reply_to_message_id: int = None, - reply_markup=None): - """Use this method to send information about a venue. - - Args: - chat_id (``int`` | ``str``): - Unique identifier (int) or username (str) of the target chat. - For your personal cloud (Saved Messages) you can simply use "me" or "self". - For a contact that exists in your Telegram address book you can use his phone number (str). - For a private channel/supergroup you can use its *t.me/joinchat/* link. - - latitude (``float``): - Latitude of the venue. - - longitude (``float``): - Longitude of the venue. - - title (``str``): - Name of the venue. - - address (``str``): - Address of the venue. - - foursquare_id (``str``, *optional*): - Foursquare identifier of the venue. - - disable_notification (``bool``, *optional*): - Sends the message silently. - Users will receive a notification with no sound. - - reply_to_message_id (``int``, *optional*): - If the message is a reply, ID of the original message - - reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): - Additional interface options. An object for an inline keyboard, custom reply keyboard, - instructions to remove reply keyboard or to force a reply from the user. - - Returns: - On success, the sent :obj:`Message ` is returned. - - Raises: - :class:`Error ` - """ - r = self.send( - functions.messages.SendMedia( - peer=self.resolve_peer(chat_id), - media=types.InputMediaVenue( - geo_point=types.InputGeoPoint( - lat=latitude, - long=longitude - ), - title=title, - address=address, - provider="", - venue_id=foursquare_id, - venue_type="" - ), - message="", - silent=disable_notification or None, - reply_to_msg_id=reply_to_message_id, - random_id=self.rnd_id(), - reply_markup=reply_markup.write() if reply_markup else None - ) - ) - - for i in r.updates: - if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - users = {i.id: i for i in r.users} - chats = {i.id: i for i in r.chats} - - return utils.parse_message(self, i.message, users, chats) - - def send_contact(self, - chat_id: int or str, - phone_number: str, - first_name: str, - last_name: str = "", - disable_notification: bool = None, - reply_to_message_id: int = None, - reply_markup=None): - """Use this method to send phone contacts. - - Args: - chat_id (``int`` | ``str``): - Unique identifier (int) or username (str) of the target chat. - For your personal cloud (Saved Messages) you can simply use "me" or "self". - For a contact that exists in your Telegram address book you can use his phone number (str). - For a private channel/supergroup you can use its *t.me/joinchat/* link. - - phone_number (``str``): - Contact's phone number. - - first_name (``str``): - Contact's first name. - - last_name (``str``): - Contact's last name. - - disable_notification (``bool``, *optional*): - Sends the message silently. - Users will receive a notification with no sound. - - reply_to_message_id (``int``, *optional*): - If the message is a reply, ID of the original message. - - reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): - Additional interface options. An object for an inline keyboard, custom reply keyboard, - instructions to remove reply keyboard or to force a reply from the user. - - Returns: - On success, the sent :obj:`Message ` is returned. - - Raises: - :class:`Error ` - """ - r = self.send( - functions.messages.SendMedia( - peer=self.resolve_peer(chat_id), - media=types.InputMediaContact( - phone_number, - first_name, - last_name - ), - message="", - silent=disable_notification or None, - reply_to_msg_id=reply_to_message_id, - random_id=self.rnd_id(), - reply_markup=reply_markup.write() if reply_markup else None - ) - ) - - for i in r.updates: - if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - users = {i.id: i for i in r.users} - chats = {i.id: i for i in r.chats} - - return utils.parse_message(self, i.message, users, chats) - - def send_chat_action(self, - chat_id: int or str, - action: ChatAction or str, - progress: int = 0): - """Use this method when you need to tell the other party that something is happening on your side. - - Args: - chat_id (``int`` | ``str``): - Unique identifier (int) or username (str) of the target chat. - For your personal cloud (Saved Messages) you can simply use "me" or "self". - For a contact that exists in your Telegram address book you can use his phone number (str). - For a private channel/supergroup you can use its *t.me/joinchat/* link. - - action (``ChatAction`` | ``str``): - Type of action to broadcast. - Choose one from the :class:`ChatAction ` enumeration, - depending on what the user is about to receive. - You can also provide a string (e.g. "typing", "upload_photo", "record_audio", ...). - - progress (``int``, *optional*): - Progress of the upload process. - - Returns: - On success, True is returned. - - Raises: - :class:`Error ` - :class:`ValueError`: If the provided string is not a valid ChatAction - """ - - # Resolve Enum type - if isinstance(action, str): - action = ChatAction.from_string(action).value - elif isinstance(action, ChatAction): - action = action.value - - if "Upload" in action.__name__: - action = action(progress) - else: - action = action() - - return self.send( - functions.messages.SetTyping( - peer=self.resolve_peer(chat_id), - action=action - ) - ) - - def get_user_profile_photos(self, - user_id: int or str, - offset: int = 0, - limit: int = 100): - """Use this method to get a list of profile pictures for a user. - - Args: - user_id (``int`` | ``str``): - Unique identifier of the target user. - - offset (``int``, *optional*): - Sequential number of the first photo to be returned. - By default, all photos are returned. - - limit (``int``, *optional*): - Limits the number of photos to be retrieved. - Values between 1—100 are accepted. Defaults to 100. - - Returns: - On success, a :obj:`UserProfilePhotos` object is returned. - - Raises: - :class:`Error ` - """ - return utils.parse_photos( - self.send( - functions.photos.GetUserPhotos( - user_id=self.resolve_peer(user_id), - offset=offset, - max_id=0, - limit=limit - ) - ) - ) - - def edit_message_text(self, - chat_id: int or str, - message_id: int, - text: str, - parse_mode: str = "", - disable_web_page_preview: bool = None, - reply_markup=None): - """Use this method to edit text messages. - - Args: - chat_id (``int`` | ``str``): - Unique identifier (int) or username (str) of the target chat. - For your personal cloud (Saved Messages) you can simply use "me" or "self". - For a contact that exists in your Telegram address book you can use his phone number (str). - For a private channel/supergroup you can use its *t.me/joinchat/* link. - - message_id (``int``): - Message identifier in the chat specified in chat_id. - - text (``str``): - New text of the message. - - parse_mode (``str``): - Use :obj:`MARKDOWN ` or :obj:`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 (``bool``, *optional*): - Disables link previews for links in this message. - - reply_markup (:obj:`InlineKeyboardMarkup`): - An InlineKeyboardMarkup object. - - Returns: - On success, the edited :obj:`Message ` is returned. - - Raises: - :class:`Error ` - """ - style = self.html if parse_mode.lower() == "html" else self.markdown - - r = self.send( - functions.messages.EditMessage( - peer=self.resolve_peer(chat_id), - id=message_id, - no_webpage=disable_web_page_preview or None, - reply_markup=reply_markup.write() if reply_markup else None, - **style.parse(text) - ) - ) - - for i in r.updates: - if isinstance(i, (types.UpdateEditMessage, types.UpdateEditChannelMessage)): - users = {i.id: i for i in r.users} - chats = {i.id: i for i in r.chats} - - return utils.parse_message(self, i.message, users, chats) - - def edit_message_caption(self, - chat_id: int or str, - message_id: int, - caption: str, - parse_mode: str = "", - reply_markup=None): - """Use this method to edit captions of messages. - - Args: - chat_id (``int`` | ``str``): - Unique identifier (int) or username (str) of the target chat. - For your personal cloud (Saved Messages) you can simply use "me" or "self". - For a contact that exists in your Telegram address book you can use his phone number (str). - For a private channel/supergroup you can use its *t.me/joinchat/* link. - - message_id (``int``): - Message identifier in the chat specified in chat_id. - - caption (``str``): - New caption of the message. - - parse_mode (``str``): - Use :obj:`MARKDOWN ` or :obj:`HTML ` - if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your caption. - Defaults to Markdown. - - reply_markup (:obj:`InlineKeyboardMarkup`): - An InlineKeyboardMarkup object. - - Returns: - On success, the edited :obj:`Message ` is returned. - - Raises: - :class:`Error ` - """ - style = self.html if parse_mode.lower() == "html" else self.markdown - - r = self.send( - functions.messages.EditMessage( - peer=self.resolve_peer(chat_id), - id=message_id, - reply_markup=reply_markup.write() if reply_markup else None, - **style.parse(caption) - ) - ) - - for i in r.updates: - if isinstance(i, (types.UpdateEditMessage, types.UpdateEditChannelMessage)): - users = {i.id: i for i in r.users} - chats = {i.id: i for i in r.chats} - - return utils.parse_message(self, i.message, users, chats) - - def edit_message_reply_markup(self, - chat_id: int or str, - message_id: int, - reply_markup=None): - """Use this method to edit only the reply markup of messages sent by the bot or via the bot (for inline bots). - - Args: - chat_id (``int`` | ``str``): - Unique identifier (int) or username (str) of the target chat. - For your personal cloud (Saved Messages) you can simply use "me" or "self". - For a contact that exists in your Telegram address book you can use his phone number (str). - For a private channel/supergroup you can use its *t.me/joinchat/* link. - - message_id (``int``): - Message identifier in the chat specified in chat_id. - - reply_markup (:obj:`InlineKeyboardMarkup`): - An InlineKeyboardMarkup object. - - Returns: - On success, if edited message is sent by the bot, the edited - :obj:`Message ` is returned, otherwise True is returned. - - Raises: - :class:`Error ` - """ - - r = self.send( - functions.messages.EditMessage( - peer=self.resolve_peer(chat_id), - id=message_id, - reply_markup=reply_markup.write() if reply_markup else None - ) - ) - - for i in r.updates: - if isinstance(i, (types.UpdateEditMessage, types.UpdateEditChannelMessage)): - users = {i.id: i for i in r.users} - chats = {i.id: i for i in r.chats} - - return utils.parse_message(self, i.message, users, chats) - - def delete_messages(self, - chat_id: int or str, - message_ids, - revoke: bool = True): - """Use this method to delete messages, including service messages, with the following limitations: - - - A message can only be deleted if it was sent less than 48 hours ago. - - Users can delete outgoing messages in groups and supergroups. - - Users granted *can_post_messages* permissions can delete outgoing messages in channels. - - If the user is an administrator of a group, it can delete any message there. - - If the user has *can_delete_messages* permission in a supergroup or a channel, it can delete any message there. - - Args: - chat_id (``int`` | ``str``): - Unique identifier (int) or username (str) of the target chat. - For your personal cloud (Saved Messages) you can simply use "me" or "self". - For a contact that exists in your Telegram address book you can use his phone number (str). - For a private channel/supergroup you can use its *t.me/joinchat/* link. - - message_ids (``iterable``): - A list of Message identifiers to delete or a single message id. - Iterators and Generators are also accepted. - - revoke (``bool``, *optional*): - Deletes messages on both parts. - This is only for private cloud chats and normal groups, messages on - channels and supergroups are always revoked (i.e.: deleted for everyone). - Defaults to True. - - Returns: - True on success. - - Raises: - :class:`Error ` - """ - peer = self.resolve_peer(chat_id) - message_ids = list(message_ids) if not isinstance(message_ids, int) else [message_ids] - - if isinstance(peer, types.InputPeerChannel): - self.send( - functions.channels.DeleteMessages( - channel=peer, - id=message_ids - ) - ) - else: - self.send( - functions.messages.DeleteMessages( - id=message_ids, - revoke=revoke or None - ) - ) - - return True - # TODO: Improvements for the new API def save_file(self, path: str, @@ -3156,727 +1259,3 @@ class Client: pass # Don't stop sessions, they are now cached and kept online # session.stop() TODO: Remove this branch - def join_chat(self, chat_id: str): - """Use this method to join a group chat or channel. - - Args: - chat_id (``str``): - Unique identifier for the target chat in form of *t.me/joinchat/* links or username of the target - channel/supergroup (in the format @username). - - Raises: - :class:`Error ` - """ - match = self.INVITE_LINK_RE.match(chat_id) - - if match: - return self.send( - functions.messages.ImportChatInvite( - hash=match.group(1) - ) - ) - else: - resolved_peer = self.send( - functions.contacts.ResolveUsername( - username=chat_id.lower().strip("@") - ) - ) - - channel = types.InputPeerChannel( - channel_id=resolved_peer.chats[0].id, - access_hash=resolved_peer.chats[0].access_hash - ) - - return self.send( - functions.channels.JoinChannel( - channel=channel - ) - ) - - def leave_chat(self, chat_id: int or str, delete: bool = False): - """Use this method to leave a group chat or channel. - - Args: - chat_id (``int`` | ``str``): - Unique identifier for the target chat or username of the target channel/supergroup - (in the format @username). - - delete (``bool``, *optional*): - Deletes the group chat dialog after leaving (for simple group chats, not supergroups). - - Raises: - :class:`Error ` - """ - peer = self.resolve_peer(chat_id) - - if isinstance(peer, types.InputPeerChannel): - return self.send( - functions.channels.LeaveChannel( - channel=self.resolve_peer(chat_id) - ) - ) - elif isinstance(peer, types.InputPeerChat): - r = self.send( - functions.messages.DeleteChatUser( - chat_id=peer.chat_id, - user_id=types.InputPeerSelf() - ) - ) - - if delete: - self.send( - functions.messages.DeleteHistory( - peer=peer, - max_id=0 - ) - ) - - return r - - def export_chat_invite_link(self, chat_id: int or str, new: bool = False): - """Use this method to export an invite link to a supergroup or a channel. - - The user must be an administrator in the chat for this to work and must have the appropriate admin rights. - - Args: - chat_id (``int`` | ``str``): - Unique identifier for the target chat or username of the target channel/supergroup - (in the format @username). - - new (``bool``): - The previous link will be deactivated and a new link will be generated. - This is also used to create the invite link in case it doesn't exist yet. - - Returns: - On success, the exported invite link as string is returned. - - Raises: - :class:`Error ` - - Note: - If the returned link is a new one it may take a while for it to be activated. - """ - peer = self.resolve_peer(chat_id) - - if isinstance(peer, types.InputPeerChat): - if new: - return self.send( - functions.messages.ExportChatInvite( - chat_id=peer.chat_id - ) - ).link - else: - chat_full = self.send( - functions.messages.GetFullChat( - chat_id=peer.chat_id - ) - ).full_chat # type: types.ChatFull - - if isinstance(chat_full.exported_invite, types.ChatInviteExported): - return chat_full.exported_invite.link - else: - raise ChatAdminRequired - elif isinstance(peer, types.InputPeerChannel): - if new: - return self.send( - functions.channels.ExportInvite( - channel=peer - ) - ).link - else: - channel_full = self.send( - functions.channels.GetFullChannel( - channel=peer - ) - ).full_chat # type: types.ChannelFull - - if isinstance(channel_full.exported_invite, types.ChatInviteExported): - return channel_full.exported_invite.link - else: - raise ChatAdminRequired - - def enable_cloud_password(self, password: str, hint: str = "", email: str = ""): - """Use this method to enable the Two-Step Verification security feature (Cloud Password) on your account. - - This password will be asked when you log in on a new device in addition to the SMS code. - - Args: - password (``str``): - Your password. - - hint (``str``, *optional*): - A password hint. - - email (``str``, *optional*): - Recovery e-mail. - - Returns: - True on success, False otherwise. - - Raises: - :class:`Error ` - """ - r = self.send(functions.account.GetPassword()) - - if isinstance(r, types.account.NoPassword): - salt = r.new_salt + os.urandom(8) - password_hash = sha256(salt + password.encode() + salt).digest() - - return self.send( - functions.account.UpdatePasswordSettings( - current_password_hash=salt, - new_settings=types.account.PasswordInputSettings( - new_salt=salt, - new_password_hash=password_hash, - hint=hint, - email=email - ) - ) - ) - else: - return False - - def change_cloud_password(self, current_password: str, new_password: str, new_hint: str = ""): - """Use this method to change your Two-Step Verification password (Cloud Password) with a new one. - - Args: - current_password (``str``): - Your current password. - - new_password (``str``): - Your new password. - - new_hint (``str``, *optional*): - A new password hint. - - Returns: - True on success, False otherwise. - - Raises: - :class:`Error ` - """ - r = self.send(functions.account.GetPassword()) - - if isinstance(r, types.account.Password): - current_password_hash = sha256(r.current_salt + current_password.encode() + r.current_salt).digest() - - new_salt = r.new_salt + os.urandom(8) - new_password_hash = sha256(new_salt + new_password.encode() + new_salt).digest() - - return self.send( - functions.account.UpdatePasswordSettings( - current_password_hash=current_password_hash, - new_settings=types.account.PasswordInputSettings( - new_salt=new_salt, - new_password_hash=new_password_hash, - hint=new_hint - ) - ) - ) - else: - return False - - def remove_cloud_password(self, password: str): - """Use this method to turn off the Two-Step Verification security feature (Cloud Password) on your account. - - Args: - password (``str``): - Your current password. - - Returns: - True on success, False otherwise. - - Raises: - :class:`Error ` - """ - r = self.send(functions.account.GetPassword()) - - if isinstance(r, types.account.Password): - password_hash = sha256(r.current_salt + password.encode() + r.current_salt).digest() - - return self.send( - functions.account.UpdatePasswordSettings( - current_password_hash=password_hash, - new_settings=types.account.PasswordInputSettings( - new_salt=b"", - new_password_hash=b"", - hint="" - ) - ) - ) - else: - return False - - def download_media(self, - message: pyrogram_types.Message or str, - file_name: str = "", - block: bool = True, - progress: callable = None, - progress_args: tuple = None): - """Use this method to download the media from a Message. - - Args: - message (:obj:`Message ` | ``str``): - Pass a Message containing the media, the media itself (message.audio, message.video, ...) or - the file id as string. - - file_name (``str``, *optional*): - A custom *file_name* to be used instead of the one provided by Telegram. - By default, all files are downloaded in the *downloads* folder in your working directory. - You can also specify a path for downloading files in a custom location: paths that end with "/" - are considered directories. All non-existent folders will be created automatically. - - block (``bool``, *optional*): - Blocks the code execution until the file has been downloaded. - Defaults to True. - - progress (``callable``): - Pass a callback function to view the download progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). - - progress_args (``tuple``): - Extra custom arguments for the progress callback function. Useful, for example, if you want to pass - a chat_id and a message_id in order to edit a message with the updated progress. - - Other Parameters: - client (:obj:`Client `): - The Client itself, useful when you want to call other API methods inside the callback function. - - current (``int``): - The amount of bytes downloaded so far. - - total (``int``): - The size of the file. - - *args (``tuple``): - Extra custom arguments as defined in the *progress_args* parameter. - You can either keep *\*args* or add every single extra argument in your function signature. - - Returns: - On success, the absolute path of the downloaded file as string is returned, None otherwise. - - Raises: - :class:`Error ` - """ - if isinstance(message, pyrogram_types.Message): - if message.photo: - media = message.photo[-1] - elif message.audio: - media = message.audio - elif message.document: - media = message.document - elif message.video: - media = message.video - elif message.voice: - media = message.voice - elif message.video_note: - media = message.video_note - elif message.sticker: - media = message.sticker - else: - return - elif isinstance(message, ( - pyrogram_types.PhotoSize, - pyrogram_types.Audio, - pyrogram_types.Document, - pyrogram_types.Video, - pyrogram_types.Voice, - pyrogram_types.VideoNote, - pyrogram_types.Sticker - )): - media = message - elif isinstance(message, str): - media = pyrogram_types.Document( - file_id=message, - file_size=0, - mime_type="" - ) - else: - return - - done = Event() - path = [None] - - self.download_queue.put((media, file_name, done, progress, progress_args, path)) - - if block: - done.wait() - - return path[0] - - def add_contacts(self, contacts: list): - """Use this method to add contacts to your Telegram address book. - - Args: - contacts (``list``): - A list of :obj:`InputPhoneContact ` - - Returns: - On success, the added contacts are returned. - - Raises: - :class:`Error ` - """ - imported_contacts = self.send( - functions.contacts.ImportContacts( - contacts=contacts - ) - ) - - return imported_contacts - - def delete_contacts(self, ids: list): - """Use this method to delete contacts from your Telegram address book - - Args: - ids (``list``): - A list of unique identifiers for the target users. - Can be an ID (int), a username (string) or phone number (string). - - Returns: - True on success. - - Raises: - :class:`Error ` - """ - contacts = [] - - for i in ids: - try: - input_user = self.resolve_peer(i) - except PeerIdInvalid: - continue - else: - if isinstance(input_user, types.InputPeerUser): - contacts.append(input_user) - - return self.send( - functions.contacts.DeleteContacts( - id=contacts - ) - ) - - def get_contacts(self, _hash: int = 0): - while True: - try: - contacts = self.send(functions.contacts.GetContacts(_hash)) - except FloodWait as e: - log.warning("get_contacts flood: waiting {} seconds".format(e.x)) - time.sleep(e.x) - continue - else: - if isinstance(contacts, types.contacts.Contacts): - log.info("Total contacts: {}".format(len(self.peers_by_phone))) - - return contacts - - def get_inline_bot_results(self, - bot: int or str, - query: str, - offset: str = "", - location: tuple = None): - """Use this method to get bot results via inline queries. - You can then send a result using :obj:`send_inline_bot_result ` - - Args: - bot (``int`` | ``str``): - Unique identifier of the inline bot you want to get results from. You can specify - a @username (str) or a bot ID (int). - - query (``str``): - Text of the query (up to 512 characters). - - offset (``str``): - Offset of the results to be returned. - - location (``tuple``, *optional*): - Your location in tuple format (latitude, longitude), e.g.: (51.500729, -0.124583). - Useful for location-based results only. - - Returns: - On Success, :obj:`BotResults ` is returned. - - Raises: - :class:`Error ` - """ - # TODO: Split location parameter into lat and long - - try: - return self.send( - functions.messages.GetInlineBotResults( - bot=self.resolve_peer(bot), - peer=types.InputPeerSelf(), - query=query, - offset=offset, - geo_point=types.InputGeoPoint( - lat=location[0], - long=location[1] - ) if location else None - ) - ) - except UnknownError as e: - # TODO: Add this -503 Timeout error into the Error DB - if e.x.error_code == -503 and e.x.error_message == "Timeout": - raise TimeoutError("The inline bot didn't answer in time") from None - else: - raise e - - def send_inline_bot_result(self, - chat_id: int or str, - query_id: int, - result_id: str, - disable_notification: bool = None, - reply_to_message_id: int = None): - """Use this method to send an inline bot result. - Bot results can be retrieved using :obj:`get_inline_bot_results ` - - Args: - chat_id (``int`` | ``str``): - Unique identifier (int) or username (str) of the target chat. - For your personal cloud (Saved Messages) you can simply use "me" or "self". - For a contact that exists in your Telegram address book you can use his phone number (str). - For a private channel/supergroup you can use its *t.me/joinchat/* link. - - query_id (``int``): - Unique identifier for the answered query. - - result_id (``str``): - Unique identifier for the result that was chosen. - - disable_notification (``bool``, *optional*): - Sends the message silently. - Users will receive a notification with no sound. - - reply_to_message_id (``bool``, *optional*): - If the message is a reply, ID of the original message. - - Returns: - On success, the sent Message is returned. - - Raises: - :class:`Error ` - """ - return self.send( - functions.messages.SendInlineBotResult( - peer=self.resolve_peer(chat_id), - query_id=query_id, - id=result_id, - random_id=self.rnd_id(), - silent=disable_notification or None, - reply_to_msg_id=reply_to_message_id - ) - ) - - def get_users(self, user_ids): - """Use this method to get information about a user. - You can retrieve up to 200 users at once. - - Args: - user_ids (``iterable``): - A list of User identifiers (id or username) or a single user id/username. - For a contact that exists in your Telegram address book you can use his phone number (str). - Iterators and Generators are also accepted. - - Returns: - On success and in case *user_ids* was a list, the returned value will be a list of the requested - :obj:`Users ` even if a list contains just one element, otherwise if - *user_ids* was an integer, the single requested :obj:`User` is returned. - - Raises: - :class:`Error ` - """ - is_iterable = not isinstance(user_ids, (int, str)) - user_ids = list(user_ids) if is_iterable else [user_ids] - user_ids = [self.resolve_peer(i) for i in user_ids] - - r = self.send( - functions.users.GetUsers( - id=user_ids - ) - ) - - users = [] - - for i in r: - users.append(utils.parse_user(i)) - - return users if is_iterable else users[0] - - def get_messages(self, - chat_id: int or str, - message_ids, - replies: int = 1): - """Use this method to get messages that belong to a specific chat. - You can retrieve up to 200 messages at once. - - Args: - chat_id (``int`` | ``str``): - Unique identifier (int) or username (str) of the target chat. - For your personal cloud (Saved Messages) you can simply use "me" or "self". - For a contact that exists in your Telegram address book you can use his phone number (str). - For a private channel/supergroup you can use its *t.me/joinchat/* link. - - message_ids (``iterable``): - A list of Message identifiers in the chat specified in *chat_id* or a single message id, as integer. - Iterators and Generators are also accepted. - - replies (``int``, *optional*): - The number of replies to get for each message. Defaults to 1. - - Returns: - On success and in case *message_ids* was a list, the returned value will be a list of the requested - :obj:`Messages ` even if a list contains just one element, otherwise if - *message_ids* was an integer, the single requested :obj:`Message ` - is returned. - - Raises: - :class:`Error ` - """ - peer = self.resolve_peer(chat_id) - is_iterable = not isinstance(message_ids, int) - message_ids = list(message_ids) if is_iterable else [message_ids] - message_ids = [types.InputMessageID(i) for i in message_ids] - - if isinstance(peer, types.InputPeerChannel): - rpc = functions.channels.GetMessages( - channel=peer, - id=message_ids - ) - else: - rpc = functions.messages.GetMessages( - id=message_ids - ) - - r = self.send(rpc) - - users = {i.id: i for i in r.users} - chats = {i.id: i for i in r.chats} - - messages = [] - - for i in r.messages: - if isinstance(i, types.Message): - messages.append( - utils.parse_message( - self, i, users, chats, - replies=replies - ) - ) - elif isinstance(i, types.MessageService): - messages.append( - utils.parse_message_service( - self, i, users, chats - ) - ) - else: - messages.append( - utils.parse_message_empty( - self, i - ) - ) - - return messages if is_iterable else messages[0] - - def answer_callback_query(self, - callback_query_id: str, - text: str = None, - show_alert: bool = None, - url: str = None, - cache_time: int = 0): - """Use this method to send answers to callback queries sent from inline keyboards. - The answer will be displayed to the user as a notification at the top of the chat screen or as an alert. - - Args: - callback_query_id (``str``): - Unique identifier for the query to be answered. - - text (``str``): - Text of the notification. If not specified, nothing will be shown to the user, 0-200 characters. - - show_alert (``bool``): - If true, an alert will be shown by the client instead of a notification at the top of the chat screen. - Defaults to False. - - url (``str``): - URL that will be opened by the user's client. - If you have created a Game and accepted the conditions via @Botfather, specify the URL that opens your - game – note that this will only work if the query comes from a callback_game button. - Otherwise, you may use links like t.me/your_bot?start=XXXX that open your bot with a parameter. - - cache_time (``int``): - The maximum amount of time in seconds that the result of the callback query may be cached client-side. - Telegram apps will support caching starting in version 3.14. Defaults to 0. - """ - return self.send( - functions.messages.SetBotCallbackAnswer( - query_id=int(callback_query_id), - cache_time=cache_time, - alert=show_alert or None, - message=text, - url=url - ) - ) - - def get_chat(self, chat_id: int or str): - # TODO: Add docstrings - peer = self.resolve_peer(chat_id) - - if isinstance(peer, types.InputPeerChannel): - r = self.send(functions.channels.GetFullChannel(peer)) - elif isinstance(peer, (types.InputPeerUser, types.InputPeerSelf)): - r = self.send(functions.users.GetFullUser(peer)) - else: - r = self.send(functions.messages.GetFullChat(peer.chat_id)) - - return utils.parse_chat_full(self, r) - - def get_history(self, - chat_id: int or str, - offset: int, - limit: int, - offset_id: int = 0, - offset_date: int = 0, - max_id: int = 0, - min_id: int = 0): - # TODO: Documentation - - r = self.send( - functions.messages.GetHistory( - peer=self.resolve_peer(chat_id), - offset_id=offset_id, - offset_date=offset_date, - add_offset=offset, - limit=limit, - max_id=max_id, - min_id=min_id, - hash=0 - ) - ) - - users = {i.id: i for i in r.users} - chats = {i.id: i for i in r.chats} - - messages = [] - - for i in r.messages: - if isinstance(i, types.Message): - messages.append( - utils.parse_message( - self, i, users, chats - ) - ) - elif isinstance(i, types.MessageService): - messages.append( - utils.parse_message_service( - self, i, users, chats - ) - ) - else: - messages.append( - utils.parse_message_empty( - self, i - ) - ) - - return messages diff --git a/pyrogram/client/dispatcher/dispatcher.py b/pyrogram/client/dispatcher/dispatcher.py index a9439d6e..892a387d 100644 --- a/pyrogram/client/dispatcher/dispatcher.py +++ b/pyrogram/client/dispatcher/dispatcher.py @@ -24,7 +24,7 @@ from threading import Thread import pyrogram from pyrogram.api import types -from .. import utils +from ..ext import utils from ..handlers import RawUpdateHandler, CallbackQueryHandler, MessageHandler log = logging.getLogger(__name__) @@ -138,14 +138,7 @@ class Dispatcher: self.dispatch(update, users=users, chats=chats, is_raw=True) if isinstance(update, Dispatcher.MESSAGE_UPDATES): - if isinstance(update.message, types.Message): - parser = utils.parse_message - elif isinstance(update.message, types.MessageService): - parser = utils.parse_message_service - else: - continue - - message = parser( + message = utils.parse_messages( self.client, update.message, users, diff --git a/pyrogram/client/ext/__init__.py b/pyrogram/client/ext/__init__.py new file mode 100644 index 00000000..38eaf089 --- /dev/null +++ b/pyrogram/client/ext/__init__.py @@ -0,0 +1,23 @@ +# 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 . + +from .base_client import BaseClient +from .chat_action import ChatAction +from .emoji import Emoji +from .parse_mode import ParseMode +from .syncer import Syncer diff --git a/pyrogram/client/ext/base_client.py b/pyrogram/client/ext/base_client.py new file mode 100644 index 00000000..04862e88 --- /dev/null +++ b/pyrogram/client/ext/base_client.py @@ -0,0 +1,103 @@ +# 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 queue import Queue +from threading import Lock + +from ..style import Markdown, HTML +from ...api.core import Object +from ...session.internals import MsgId + + +class BaseClient: + INVITE_LINK_RE = re.compile(r"^(?:https?://)?(?:t\.me/joinchat/)([\w-]+)$") + BOT_TOKEN_RE = re.compile(r"^\d+:[\w-]+$") + DIALOGS_AT_ONCE = 100 + UPDATES_WORKERS = 1 + DOWNLOAD_WORKERS = 1 + OFFLINE_SLEEP = 300 + + MEDIA_TYPE_ID = { + 0: "thumbnail", + 1: "thumbnail", + 2: "photo", + 3: "voice", + 4: "video", + 5: "document", + 8: "sticker", + 9: "audio", + 10: "gif", + 13: "video_note" + } + + def __init__(self): + self.token = None + self.dc_id = None + self.auth_key = None + self.user_id = None + self.date = None + + self.rnd_id = MsgId + self.channels_pts = {} + + self.peers_by_id = {} + self.peers_by_username = {} + self.peers_by_phone = {} + + self.markdown = Markdown(self.peers_by_id) + self.html = HTML(self.peers_by_id) + + self.session = None + self.media_sessions = {} + self.media_sessions_lock = Lock() + + self.is_started = None + self.is_idle = None + + self.updates_queue = Queue() + self.updates_workers_list = [] + self.download_queue = Queue() + self.download_workers_list = [] + + def send(self, data: Object): + pass + + def resolve_peer(self, peer_id: int or str): + pass + + def add_handler(self, handler, group: int = 0) -> tuple: + pass + + def save_file( + self, + path: str, + file_id: int = None, + file_part: int = 0, + progress: callable = None, + progress_args: tuple = () + ): + pass + + def get_messages( + self, + chat_id: int or str, + message_ids, + replies: int = 1 + ): + pass diff --git a/pyrogram/client/chat_action.py b/pyrogram/client/ext/chat_action.py similarity index 100% rename from pyrogram/client/chat_action.py rename to pyrogram/client/ext/chat_action.py diff --git a/pyrogram/client/emoji.py b/pyrogram/client/ext/emoji.py similarity index 100% rename from pyrogram/client/emoji.py rename to pyrogram/client/ext/emoji.py diff --git a/pyrogram/client/parse_mode.py b/pyrogram/client/ext/parse_mode.py similarity index 100% rename from pyrogram/client/parse_mode.py rename to pyrogram/client/ext/parse_mode.py diff --git a/pyrogram/client/syncer.py b/pyrogram/client/ext/syncer.py similarity index 100% rename from pyrogram/client/syncer.py rename to pyrogram/client/ext/syncer.py diff --git a/pyrogram/client/ext/utils.py b/pyrogram/client/ext/utils.py new file mode 100644 index 00000000..94dcbe9c --- /dev/null +++ b/pyrogram/client/ext/utils.py @@ -0,0 +1,808 @@ +# 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 logging +import time +from base64 import b64decode, b64encode +from struct import pack +from weakref import proxy + +from pyrogram.api.errors import FloodWait +from pyrogram.client import types as pyrogram_types +from ...api import types, functions +from ...api.errors import StickersetInvalid + +log = logging.getLogger(__name__) + +# TODO: Organize the code better? + +ENTITIES = { + types.MessageEntityMention.ID: "mention", + types.MessageEntityHashtag.ID: "hashtag", + types.MessageEntityBotCommand.ID: "bot_command", + types.MessageEntityUrl.ID: "url", + types.MessageEntityEmail.ID: "email", + types.MessageEntityBold.ID: "bold", + types.MessageEntityItalic.ID: "italic", + types.MessageEntityCode.ID: "code", + types.MessageEntityPre.ID: "pre", + types.MessageEntityTextUrl.ID: "text_link", + types.MessageEntityMentionName.ID: "text_mention" +} + + +def parse_entities(entities: list, users: dict) -> list: + output_entities = [] + + for entity in entities: + entity_type = ENTITIES.get(entity.ID, None) + + if entity_type: + output_entities.append(pyrogram_types.MessageEntity( + type=entity_type, + offset=entity.offset, + length=entity.length, + url=getattr(entity, "url", None), + user=parse_user( + users.get( + getattr(entity, "user_id", None), + None + ) + ) + )) + + return output_entities + + +def parse_chat_photo(photo): + if not isinstance(photo, (types.UserProfilePhoto, types.ChatPhoto)): + return None + + if not isinstance(photo.photo_small, types.FileLocation): + return None + + if not isinstance(photo.photo_big, types.FileLocation): + return None + + photo_id = getattr(photo, "photo_id", 0) + loc_small = photo.photo_small + loc_big = photo.photo_big + + return pyrogram_types.ChatPhoto( + small_file_id=encode( + pack( + " pyrogram_types.User or None: + return pyrogram_types.User( + id=user.id, + is_bot=user.bot, + first_name=user.first_name, + last_name=user.last_name, + username=user.username, + language_code=user.lang_code, + phone_number=user.phone, + photo=parse_chat_photo(user.photo) + ) if user else None + + +def parse_chat(message: types.Message, users: dict, chats: dict) -> pyrogram_types.Chat: + if isinstance(message.to_id, types.PeerUser): + return parse_user_chat(users[message.to_id.user_id if message.out else message.from_id]) + elif isinstance(message.to_id, types.PeerChat): + return parse_chat_chat(chats[message.to_id.chat_id]) + else: + return parse_channel_chat(chats[message.to_id.channel_id]) + + +def parse_user_chat(user: types.User) -> pyrogram_types.Chat: + return pyrogram_types.Chat( + id=user.id, + type="private", + username=user.username, + first_name=user.first_name, + last_name=user.last_name, + photo=parse_chat_photo(user.photo) + ) + + +def parse_chat_chat(chat: types.Chat) -> pyrogram_types.Chat: + admins_enabled = getattr(chat, "admins_enabled", None) + + if admins_enabled is not None: + admins_enabled = not admins_enabled + + return pyrogram_types.Chat( + id=-chat.id, + type="group", + title=chat.title, + all_members_are_administrators=admins_enabled, + photo=parse_chat_photo(getattr(chat, "photo", None)) + ) + + +def parse_channel_chat(channel: types.Channel) -> pyrogram_types.Chat: + return pyrogram_types.Chat( + id=int("-100" + str(channel.id)), + type="supergroup" if channel.megagroup else "channel", + title=channel.title, + username=getattr(channel, "username", None), + photo=parse_chat_photo(getattr(channel, "photo", None)) + ) + + +def parse_thumb(thumb: types.PhotoSize or types.PhotoCachedSize) -> pyrogram_types.PhotoSize or None: + if isinstance(thumb, (types.PhotoSize, types.PhotoCachedSize)): + loc = thumb.location + + if isinstance(thumb, types.PhotoSize): + file_size = thumb.size + else: + file_size = len(thumb.bytes) + + if isinstance(loc, types.FileLocation): + return pyrogram_types.PhotoSize( + file_id=encode( + pack( + " bytes: + s = b64decode(s + "=" * (-len(s) % 4), "-_") + r = b"" + + assert s[-1] == 2 + + i = 0 + while i < len(s) - 1: + if s[i] != 0: + r += bytes([s[i]]) + else: + r += b"\x00" * s[i + 1] + i += 1 + + i += 1 + + return r + + +def encode(s: bytes) -> str: + r = b"" + n = 0 + + for i in s + bytes([2]): + if i == 0: + n += 1 + else: + if n: + r += b"\x00" + bytes([n]) + n = 0 + + r += bytes([i]) + + return b64encode(r, b"-_").decode().rstrip("=") + + +# TODO: Reorganize code, maybe split parts as well +def parse_messages( + client, + messages: list or types.Message or types.MessageService or types.MessageEmpty, + users: dict, + chats: dict, + replies: int = 1 +) -> pyrogram_types.Message: + is_list = isinstance(messages, list) + messages = messages if is_list else [messages] + parsed_messages = [] + + for message in messages: + if isinstance(message, types.Message): + entities = parse_entities(message.entities, users) + + forward_from = None + forward_from_chat = None + forward_from_message_id = None + forward_signature = None + forward_date = None + + forward_header = message.fwd_from # type: types.MessageFwdHeader + + if forward_header: + forward_date = forward_header.date + + if forward_header.from_id: + forward_from = parse_user(users[forward_header.from_id]) + else: + forward_from_chat = parse_channel_chat(chats[forward_header.channel_id]) + forward_from_message_id = forward_header.channel_post + forward_signature = forward_header.post_author + + photo = None + location = None + contact = None + venue = None + audio = None + voice = None + video = None + video_note = None + sticker = None + document = None + + media = message.media + + if media: + if isinstance(media, types.MessageMediaPhoto): + photo = media.photo + + if isinstance(photo, types.Photo): + sizes = photo.sizes + photo_sizes = [] + + for size in sizes: + if isinstance(size, (types.PhotoSize, types.PhotoCachedSize)): + loc = size.location + + if isinstance(size, types.PhotoSize): + file_size = size.size + else: + file_size = len(size.bytes) + + if isinstance(loc, types.FileLocation): + photo_size = pyrogram_types.PhotoSize( + file_id=encode( + pack( + " int: + return ( + input_peer.user_id if isinstance(input_peer, types.InputPeerUser) + else -input_peer.chat_id if isinstance(input_peer, types.InputPeerChat) + else int("-100" + str(input_peer.channel_id)) + ) + + +def get_input_peer(peer_id: int, access_hash: int): + return ( + types.InputPeerUser(peer_id, access_hash) if peer_id > 0 + else types.InputPeerChannel(int(str(peer_id)[4:]), access_hash) + if (str(peer_id).startswith("-100") and access_hash) + else types.InputPeerChat(-peer_id) + ) + + +def get_offset_date(dialogs): + for m in reversed(dialogs.messages): + if isinstance(m, types.MessageEmpty): + continue + else: + return m.date + else: + return 0 + + +def parse_photos(photos): + if isinstance(photos, types.photos.Photos): + total_count = len(photos.photos) + else: + total_count = photos.count + + user_profile_photos = [] + + for photo in photos.photos: + if isinstance(photo, types.Photo): + sizes = photo.sizes + photo_sizes = [] + + for size in sizes: + if isinstance(size, (types.PhotoSize, types.PhotoCachedSize)): + loc = size.location + + if isinstance(size, types.PhotoSize): + file_size = size.size + else: + file_size = len(size.bytes) + + if isinstance(loc, types.FileLocation): + photo_size = pyrogram_types.PhotoSize( + file_id=encode( + pack( + " pyrogram_types.Chat: + if isinstance(chat_full, types.UserFull): + chat = parse_user_chat(chat_full.user) + chat.description = chat_full.about + else: + full_chat = chat_full.full_chat + chat = None + + for i in chat_full.chats: + if full_chat.id == i.id: + chat = i + + if isinstance(full_chat, types.ChatFull): + chat = parse_chat_chat(chat) + else: + chat = parse_channel_chat(chat) + chat.description = full_chat.about or None + # TODO: Add StickerSet type + chat.can_set_sticker_set = full_chat.can_set_stickers + chat.sticker_set_name = full_chat.stickerset + + if full_chat.pinned_msg_id: + chat.pinned_message = client.get_messages( + int("-100" + str(full_chat.id)), + full_chat.pinned_msg_id + ) + + if isinstance(full_chat.exported_invite, types.ChatInviteExported): + chat.invite_link = full_chat.exported_invite.link + + return chat diff --git a/pyrogram/client/filters/filters.py b/pyrogram/client/filters/filters.py index de59d083..41bf2da3 100644 --- a/pyrogram/client/filters/filters.py +++ b/pyrogram/client/filters/filters.py @@ -32,6 +32,9 @@ class Filters: """This class provides access to all Filters available in Pyrogram. Filters are intended to be used with the :obj:`MessageHandler `.""" + bot = build("Bot", lambda _, m: bool(m.from_user and m.from_user.is_bot)) + """Filter messages coming from bots""" + incoming = build("Incoming", lambda _, m: not m.outgoing) """Filter incoming messages.""" diff --git a/pyrogram/client/methods/__init__.py b/pyrogram/client/methods/__init__.py new file mode 100644 index 00000000..396fd3c2 --- /dev/null +++ b/pyrogram/client/methods/__init__.py @@ -0,0 +1,39 @@ +# 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 . + +from .bots import Bots +from .chats import Chats +from .contacts import Contacts +from .decorators import Decorators +from .download_media import DownloadMedia +from .messages import Messages +from .password import Password +from .users import Users + + +class Methods( + Bots, + Contacts, + Password, + Chats, + Users, + DownloadMedia, + Messages, + Decorators +): + pass diff --git a/pyrogram/client/methods/bots/__init__.py b/pyrogram/client/methods/bots/__init__.py new file mode 100644 index 00000000..62e0eb61 --- /dev/null +++ b/pyrogram/client/methods/bots/__init__.py @@ -0,0 +1,27 @@ +# 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 . + +from .callback_query import CallbackQuery +from .inline import Inline + + +class Bots( + CallbackQuery, + Inline +): + pass diff --git a/pyrogram/client/methods/bots/callback_query/__init__.py b/pyrogram/client/methods/bots/callback_query/__init__.py new file mode 100644 index 00000000..76575724 --- /dev/null +++ b/pyrogram/client/methods/bots/callback_query/__init__.py @@ -0,0 +1,25 @@ +# 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 . + +from .answer_callback_query import AnswerCallbackQuery + + +class CallbackQuery( + AnswerCallbackQuery +): + pass diff --git a/pyrogram/client/methods/bots/callback_query/answer_callback_query.py b/pyrogram/client/methods/bots/callback_query/answer_callback_query.py new file mode 100644 index 00000000..a4baa166 --- /dev/null +++ b/pyrogram/client/methods/bots/callback_query/answer_callback_query.py @@ -0,0 +1,62 @@ +# 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 . + +from pyrogram.api import functions +from ....ext import BaseClient + + +class AnswerCallbackQuery(BaseClient): + def answer_callback_query(self, + callback_query_id: str, + text: str = None, + show_alert: bool = None, + url: str = None, + cache_time: int = 0): + """Use this method to send answers to callback queries sent from inline keyboards. + The answer will be displayed to the user as a notification at the top of the chat screen or as an alert. + + Args: + callback_query_id (``str``): + Unique identifier for the query to be answered. + + text (``str``): + Text of the notification. If not specified, nothing will be shown to the user, 0-200 characters. + + show_alert (``bool``): + If true, an alert will be shown by the client instead of a notification at the top of the chat screen. + Defaults to False. + + url (``str``): + URL that will be opened by the user's client. + If you have created a Game and accepted the conditions via @Botfather, specify the URL that opens your + game – note that this will only work if the query comes from a callback_game button. + Otherwise, you may use links like t.me/your_bot?start=XXXX that open your bot with a parameter. + + cache_time (``int``): + The maximum amount of time in seconds that the result of the callback query may be cached client-side. + Telegram apps will support caching starting in version 3.14. Defaults to 0. + """ + return self.send( + functions.messages.SetBotCallbackAnswer( + query_id=int(callback_query_id), + cache_time=cache_time, + alert=show_alert or None, + message=text, + url=url + ) + ) diff --git a/pyrogram/client/methods/bots/inline/__init__.py b/pyrogram/client/methods/bots/inline/__init__.py new file mode 100644 index 00000000..af88c57e --- /dev/null +++ b/pyrogram/client/methods/bots/inline/__init__.py @@ -0,0 +1,27 @@ +# 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 . + +from .get_inline_bot_results import GetInlineBotResults +from .send_inline_bot_result import SendInlineBotResult + + +class Inline( + SendInlineBotResult, + GetInlineBotResults +): + pass diff --git a/pyrogram/client/methods/bots/inline/get_inline_bot_results.py b/pyrogram/client/methods/bots/inline/get_inline_bot_results.py new file mode 100644 index 00000000..52c3b005 --- /dev/null +++ b/pyrogram/client/methods/bots/inline/get_inline_bot_results.py @@ -0,0 +1,80 @@ +# 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 . + +from pyrogram.api import functions, types +from pyrogram.api.errors import UnknownError +from ....ext import BaseClient + + +class GetInlineBotResults(BaseClient): + def get_inline_bot_results(self, + bot: int or str, + query: str, + offset: str = "", + latitude: float = None, + longitude: float = None): + """Use this method to get bot results via inline queries. + You can then send a result using :obj:`send_inline_bot_result ` + + Args: + bot (``int`` | ``str``): + Unique identifier of the inline bot you want to get results from. You can specify + a @username (str) or a bot ID (int). + + query (``str``): + Text of the query (up to 512 characters). + + offset (``str``, *optional*): + Offset of the results to be returned. + + latitude (``float``, *optional*): + Latitude of the location. + Useful for location-based results only. + + longitude (``float``, *optional*): + Longitude of the location. + Useful for location-based results only. + + Returns: + On Success, :obj:`BotResults ` is returned. + + Raises: + :class:`Error ` + ``TimeoutError``: If the bot fails to answer within 10 seconds + """ + # TODO: Don't return the raw type + + try: + return self.send( + functions.messages.GetInlineBotResults( + bot=self.resolve_peer(bot), + peer=types.InputPeerSelf(), + query=query, + offset=offset, + geo_point=types.InputGeoPoint( + lat=latitude, + long=longitude + ) if (latitude is not None and longitude is not None) else None + ) + ) + except UnknownError as e: + # TODO: Add this -503 Timeout error into the Error DB + if e.x.error_code == -503 and e.x.error_message == "Timeout": + raise TimeoutError("The inline bot didn't answer in time") from None + else: + raise e diff --git a/pyrogram/client/methods/bots/inline/send_inline_bot_result.py b/pyrogram/client/methods/bots/inline/send_inline_bot_result.py new file mode 100644 index 00000000..947433cd --- /dev/null +++ b/pyrogram/client/methods/bots/inline/send_inline_bot_result.py @@ -0,0 +1,68 @@ +# 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 . + +from pyrogram.api import functions +from ....ext import BaseClient + + +class SendInlineBotResult(BaseClient): + def send_inline_bot_result(self, + chat_id: int or str, + query_id: int, + result_id: str, + disable_notification: bool = None, + reply_to_message_id: int = None): + """Use this method to send an inline bot result. + Bot results can be retrieved using :obj:`get_inline_bot_results ` + + Args: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + For a private channel/supergroup you can use its *t.me/joinchat/* link. + + query_id (``int``): + Unique identifier for the answered query. + + result_id (``str``): + Unique identifier for the result that was chosen. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``bool``, *optional*): + If the message is a reply, ID of the original message. + + Returns: + On success, the sent Message is returned. + + Raises: + :class:`Error ` + """ + return self.send( + functions.messages.SendInlineBotResult( + peer=self.resolve_peer(chat_id), + query_id=query_id, + id=result_id, + random_id=self.rnd_id(), + silent=disable_notification or None, + reply_to_msg_id=reply_to_message_id + ) + ) diff --git a/pyrogram/client/methods/chats/__init__.py b/pyrogram/client/methods/chats/__init__.py new file mode 100644 index 00000000..f32e474e --- /dev/null +++ b/pyrogram/client/methods/chats/__init__.py @@ -0,0 +1,31 @@ +# 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 . + +from .export_chat_invite_link import ExportChatInviteLink +from .get_chat import GetChat +from .join_chat import JoinChat +from .leave_chat import LeaveChat + + +class Chats( + GetChat, + ExportChatInviteLink, + LeaveChat, + JoinChat +): + pass diff --git a/pyrogram/client/methods/chats/export_chat_invite_link.py b/pyrogram/client/methods/chats/export_chat_invite_link.py new file mode 100644 index 00000000..dc289af3 --- /dev/null +++ b/pyrogram/client/methods/chats/export_chat_invite_link.py @@ -0,0 +1,53 @@ +# 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 . + +from pyrogram.api import functions, types +from ...ext import BaseClient + + +class ExportChatInviteLink(BaseClient): + def export_chat_invite_link(self, chat_id: int or str): + """Use this method to generate a new invite link for a chat; any previously generated link is revoked. + + You must be an administrator in the chat for this to work and have the appropriate admin rights. + + Args: + chat_id (``int`` | ``str``): + Unique identifier for the target chat or username of the target channel/supergroup + (in the format @username). + + Returns: + On success, the exported invite link as string is returned. + + Raises: + :class:`Error ` + """ + peer = self.resolve_peer(chat_id) + + if isinstance(peer, types.InputPeerChat): + return self.send( + functions.messages.ExportChatInvite( + chat_id=peer.chat_id + ) + ).link + elif isinstance(peer, types.InputPeerChannel): + return self.send( + functions.channels.ExportInvite( + channel=peer + ) + ).link diff --git a/pyrogram/client/methods/chats/get_chat.py b/pyrogram/client/methods/chats/get_chat.py new file mode 100644 index 00000000..7861c551 --- /dev/null +++ b/pyrogram/client/methods/chats/get_chat.py @@ -0,0 +1,35 @@ +# 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 . + +from pyrogram.api import functions, types +from ...ext import BaseClient, utils + + +class GetChat(BaseClient): + def get_chat(self, chat_id: int or str): + # TODO: Add docstrings + peer = self.resolve_peer(chat_id) + + if isinstance(peer, types.InputPeerChannel): + r = self.send(functions.channels.GetFullChannel(peer)) + elif isinstance(peer, (types.InputPeerUser, types.InputPeerSelf)): + r = self.send(functions.users.GetFullUser(peer)) + else: + r = self.send(functions.messages.GetFullChat(peer.chat_id)) + + return utils.parse_chat_full(self, r) diff --git a/pyrogram/client/methods/chats/join_chat.py b/pyrogram/client/methods/chats/join_chat.py new file mode 100644 index 00000000..b7b8d42c --- /dev/null +++ b/pyrogram/client/methods/chats/join_chat.py @@ -0,0 +1,59 @@ +# 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 . + +from pyrogram.api import functions, types +from ...ext import BaseClient + + +class JoinChat(BaseClient): + def join_chat(self, chat_id: str): + """Use this method to join a group chat or channel. + + Args: + chat_id (``str``): + Unique identifier for the target chat in form of *t.me/joinchat/* links or username of the target + channel/supergroup (in the format @username). + + Raises: + :class:`Error ` + """ + match = self.INVITE_LINK_RE.match(chat_id) + + if match: + return self.send( + functions.messages.ImportChatInvite( + hash=match.group(1) + ) + ) + else: + resolved_peer = self.send( + functions.contacts.ResolveUsername( + username=chat_id.lower().strip("@") + ) + ) + + channel = types.InputPeerChannel( + channel_id=resolved_peer.chats[0].id, + access_hash=resolved_peer.chats[0].access_hash + ) + + return self.send( + functions.channels.JoinChannel( + channel=channel + ) + ) diff --git a/pyrogram/client/methods/chats/leave_chat.py b/pyrogram/client/methods/chats/leave_chat.py new file mode 100644 index 00000000..55d6ef21 --- /dev/null +++ b/pyrogram/client/methods/chats/leave_chat.py @@ -0,0 +1,62 @@ +# 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 . + +from pyrogram.api import functions, types +from ...ext import BaseClient + + +class LeaveChat(BaseClient): + def leave_chat(self, chat_id: int or str, delete: bool = False): + """Use this method to leave a group chat or channel. + + Args: + chat_id (``int`` | ``str``): + Unique identifier for the target chat or username of the target channel/supergroup + (in the format @username). + + delete (``bool``, *optional*): + Deletes the group chat dialog after leaving (for simple group chats, not supergroups). + + Raises: + :class:`Error ` + """ + peer = self.resolve_peer(chat_id) + + if isinstance(peer, types.InputPeerChannel): + return self.send( + functions.channels.LeaveChannel( + channel=self.resolve_peer(chat_id) + ) + ) + elif isinstance(peer, types.InputPeerChat): + r = self.send( + functions.messages.DeleteChatUser( + chat_id=peer.chat_id, + user_id=types.InputPeerSelf() + ) + ) + + if delete: + self.send( + functions.messages.DeleteHistory( + peer=peer, + max_id=0 + ) + ) + + return r diff --git a/pyrogram/client/methods/contacts/__init__.py b/pyrogram/client/methods/contacts/__init__.py new file mode 100644 index 00000000..e0fe52fb --- /dev/null +++ b/pyrogram/client/methods/contacts/__init__.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 . + +from .add_contacts import AddContacts +from .delete_contacts import DeleteContacts +from .get_contacts import GetContacts + + +class Contacts( + GetContacts, + DeleteContacts, + AddContacts +): + pass diff --git a/pyrogram/client/methods/contacts/add_contacts.py b/pyrogram/client/methods/contacts/add_contacts.py new file mode 100644 index 00000000..10b5e415 --- /dev/null +++ b/pyrogram/client/methods/contacts/add_contacts.py @@ -0,0 +1,43 @@ +# 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 . + +from pyrogram.api import functions +from ...ext import BaseClient + + +class AddContacts(BaseClient): + def add_contacts(self, contacts: list): + """Use this method to add contacts to your Telegram address book. + + Args: + contacts (``list``): + A list of :obj:`InputPhoneContact ` + + Returns: + On success, the added contacts are returned. + + Raises: + :class:`Error ` + """ + imported_contacts = self.send( + functions.contacts.ImportContacts( + contacts=contacts + ) + ) + + return imported_contacts diff --git a/pyrogram/client/methods/contacts/delete_contacts.py b/pyrogram/client/methods/contacts/delete_contacts.py new file mode 100644 index 00000000..ed3d67f9 --- /dev/null +++ b/pyrogram/client/methods/contacts/delete_contacts.py @@ -0,0 +1,54 @@ +# 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 . + +from pyrogram.api import functions, types +from pyrogram.api.errors import PeerIdInvalid +from ...ext import BaseClient + + +class DeleteContacts(BaseClient): + def delete_contacts(self, ids: list): + """Use this method to delete contacts from your Telegram address book + + Args: + ids (``list``): + A list of unique identifiers for the target users. + Can be an ID (int), a username (string) or phone number (string). + + Returns: + True on success. + + Raises: + :class:`Error ` + """ + contacts = [] + + for i in ids: + try: + input_user = self.resolve_peer(i) + except PeerIdInvalid: + continue + else: + if isinstance(input_user, types.InputPeerUser): + contacts.append(input_user) + + return self.send( + functions.contacts.DeleteContacts( + id=contacts + ) + ) diff --git a/pyrogram/client/methods/contacts/get_contacts.py b/pyrogram/client/methods/contacts/get_contacts.py new file mode 100644 index 00000000..6fd01a1f --- /dev/null +++ b/pyrogram/client/methods/contacts/get_contacts.py @@ -0,0 +1,42 @@ +# 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 logging +import time + +from pyrogram.api import functions, types +from pyrogram.api.errors import FloodWait +from ...ext import BaseClient + +log = logging.getLogger(__name__) + + +class GetContacts(BaseClient): + def get_contacts(self, _hash: int = 0): + while True: + try: + contacts = self.send(functions.contacts.GetContacts(_hash)) + except FloodWait as e: + log.warning("get_contacts flood: waiting {} seconds".format(e.x)) + time.sleep(e.x) + continue + else: + if isinstance(contacts, types.contacts.Contacts): + log.info("Total contacts: {}".format(len(self.peers_by_phone))) + + return contacts diff --git a/pyrogram/client/methods/decorators/__init__.py b/pyrogram/client/methods/decorators/__init__.py new file mode 100644 index 00000000..89645906 --- /dev/null +++ b/pyrogram/client/methods/decorators/__init__.py @@ -0,0 +1,25 @@ +# 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 . + +from .on_callback_query import OnCallbackQuery +from .on_message import OnMessage +from .on_raw_update import OnRawUpdate + + +class Decorators(OnMessage, OnCallbackQuery, OnRawUpdate): + pass diff --git a/pyrogram/client/methods/decorators/on_callback_query.py b/pyrogram/client/methods/decorators/on_callback_query.py new file mode 100644 index 00000000..3bafc94d --- /dev/null +++ b/pyrogram/client/methods/decorators/on_callback_query.py @@ -0,0 +1,42 @@ +# 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 pyrogram +from ...ext import BaseClient + + +class OnCallbackQuery(BaseClient): + def on_callback_query(self, filters=None, group: int = 0): + """Use this decorator to automatically register a function for handling + callback queries. This does the same thing as :meth:`add_handler` using the + CallbackQueryHandler. + + Args: + filters (:obj:`Filters `): + Pass one or more filters to allow only a subset of callback queries to be passed + in your function. + + group (``int``, *optional*): + The group identifier, defaults to 0. + """ + + def decorator(func): + self.add_handler(pyrogram.CallbackQueryHandler(func, filters), group) + return func + + return decorator diff --git a/pyrogram/client/methods/decorators/on_message.py b/pyrogram/client/methods/decorators/on_message.py new file mode 100644 index 00000000..43c6f9f8 --- /dev/null +++ b/pyrogram/client/methods/decorators/on_message.py @@ -0,0 +1,42 @@ +# 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 pyrogram +from ...ext import BaseClient + + +class OnMessage(BaseClient): + def on_message(self, filters=None, group: int = 0): + """Use this decorator to automatically register a function for handling + messages. This does the same thing as :meth:`add_handler` using the + MessageHandler. + + Args: + filters (:obj:`Filters `): + Pass one or more filters to allow only a subset of messages to be passed + in your function. + + group (``int``, *optional*): + The group identifier, defaults to 0. + """ + + def decorator(func): + self.add_handler(pyrogram.MessageHandler(func, filters), group) + return func + + return decorator diff --git a/pyrogram/client/methods/decorators/on_raw_update.py b/pyrogram/client/methods/decorators/on_raw_update.py new file mode 100644 index 00000000..ca1c9d9b --- /dev/null +++ b/pyrogram/client/methods/decorators/on_raw_update.py @@ -0,0 +1,38 @@ +# 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 pyrogram +from ...ext import BaseClient + + +class OnRawUpdate(BaseClient): + def on_raw_update(self, group: int = 0): + """Use this decorator to automatically register a function for handling + raw updates. This does the same thing as :meth:`add_handler` using the + RawUpdateHandler. + + Args: + group (``int``, *optional*): + The group identifier, defaults to 0. + """ + + def decorator(func): + self.add_handler(pyrogram.RawUpdateHandler(func), group) + return func + + return decorator diff --git a/pyrogram/client/methods/download_media.py b/pyrogram/client/methods/download_media.py new file mode 100644 index 00000000..d878cc62 --- /dev/null +++ b/pyrogram/client/methods/download_media.py @@ -0,0 +1,122 @@ +# 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 . + +from threading import Event + +from pyrogram.client import types as pyrogram_types +from ..ext import BaseClient + + +class DownloadMedia(BaseClient): + def download_media(self, + message: pyrogram_types.Message or str, + file_name: str = "", + block: bool = True, + progress: callable = None, + progress_args: tuple = None): + """Use this method to download the media from a Message. + + Args: + message (:obj:`Message ` | ``str``): + Pass a Message containing the media, the media itself (message.audio, message.video, ...) or + the file id as string. + + file_name (``str``, *optional*): + A custom *file_name* to be used instead of the one provided by Telegram. + By default, all files are downloaded in the *downloads* folder in your working directory. + You can also specify a path for downloading files in a custom location: paths that end with "/" + are considered directories. All non-existent folders will be created automatically. + + block (``bool``, *optional*): + Blocks the code execution until the file has been downloaded. + Defaults to True. + + progress (``callable``): + Pass a callback function to view the download progress. + The function must take *(client, current, total, \*args)* as positional arguments (look at the section + below for a detailed description). + + progress_args (``tuple``): + Extra custom arguments for the progress callback function. Useful, for example, if you want to pass + a chat_id and a message_id in order to edit a message with the updated progress. + + Other Parameters: + client (:obj:`Client `): + The Client itself, useful when you want to call other API methods inside the callback function. + + current (``int``): + The amount of bytes downloaded so far. + + total (``int``): + The size of the file. + + *args (``tuple``): + Extra custom arguments as defined in the *progress_args* parameter. + You can either keep *\*args* or add every single extra argument in your function signature. + + Returns: + On success, the absolute path of the downloaded file as string is returned, None otherwise. + + Raises: + :class:`Error ` + """ + if isinstance(message, pyrogram_types.Message): + if message.photo: + media = message.photo[-1] + elif message.audio: + media = message.audio + elif message.document: + media = message.document + elif message.video: + media = message.video + elif message.voice: + media = message.voice + elif message.video_note: + media = message.video_note + elif message.sticker: + media = message.sticker + else: + return + elif isinstance(message, ( + pyrogram_types.PhotoSize, + pyrogram_types.Audio, + pyrogram_types.Document, + pyrogram_types.Video, + pyrogram_types.Voice, + pyrogram_types.VideoNote, + pyrogram_types.Sticker + )): + media = message + elif isinstance(message, str): + media = pyrogram_types.Document( + file_id=message, + file_size=0, + mime_type="" + ) + else: + return + + done = Event() + path = [None] + + self.download_queue.put((media, file_name, done, progress, progress_args, path)) + + if block: + done.wait() + + return path[0] diff --git a/pyrogram/client/methods/messages/__init__.py b/pyrogram/client/methods/messages/__init__.py new file mode 100644 index 00000000..c2ff2400 --- /dev/null +++ b/pyrogram/client/methods/messages/__init__.py @@ -0,0 +1,37 @@ +# 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 . + +from .action import Action +from .forward_messages import ForwardMessages +from .get_history import GetHistory +from .get_messages import GetMessages +from .media import Media +from .send_message import SendMessage +from .update import Update + + +class Messages( + GetHistory, + GetMessages, + Action, + Media, + Update, + ForwardMessages, + SendMessage +): + pass diff --git a/pyrogram/client/methods/messages/action/__init__.py b/pyrogram/client/methods/messages/action/__init__.py new file mode 100644 index 00000000..639405f2 --- /dev/null +++ b/pyrogram/client/methods/messages/action/__init__.py @@ -0,0 +1,25 @@ +# 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 . + +from .send_chat_action import SendChatAction + + +class Action( + SendChatAction +): + pass diff --git a/pyrogram/client/methods/messages/action/send_chat_action.py b/pyrogram/client/methods/messages/action/send_chat_action.py new file mode 100644 index 00000000..4b34dd40 --- /dev/null +++ b/pyrogram/client/methods/messages/action/send_chat_action.py @@ -0,0 +1,71 @@ +# 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 . + +from pyrogram.api import functions +from ....ext import BaseClient, ChatAction + + +class SendChatAction(BaseClient): + def send_chat_action(self, + chat_id: int or str, + action: ChatAction or str, + progress: int = 0): + """Use this method when you need to tell the other party that something is happening on your side. + + Args: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + For a private channel/supergroup you can use its *t.me/joinchat/* link. + + action (:obj:`ChatAction ` | ``str``): + Type of action to broadcast. + Choose one from the :class:`ChatAction ` enumeration, + depending on what the user is about to receive. + You can also provide a string (e.g. "typing", "upload_photo", "record_audio", ...). + + progress (``int``, *optional*): + Progress of the upload process. + Currently useless because official clients don't seem to be handling this. + + Returns: + On success, True is returned. + + Raises: + :class:`Error ` + ``ValueError``: If the provided string is not a valid ChatAction + """ + + # Resolve Enum type + if isinstance(action, str): + action = ChatAction.from_string(action).value + elif isinstance(action, ChatAction): + action = action.value + + if "Upload" in action.__name__: + action = action(progress) + else: + action = action() + + return self.send( + functions.messages.SetTyping( + peer=self.resolve_peer(chat_id), + action=action + ) + ) diff --git a/pyrogram/client/methods/messages/forward_messages.py b/pyrogram/client/methods/messages/forward_messages.py new file mode 100644 index 00000000..606e54b5 --- /dev/null +++ b/pyrogram/client/methods/messages/forward_messages.py @@ -0,0 +1,88 @@ +# 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 . + +from pyrogram.api import functions, types +from ...ext import BaseClient, utils + + +class ForwardMessages(BaseClient): + def forward_messages(self, + chat_id: int or str, + from_chat_id: int or str, + message_ids, + disable_notification: bool = None): + """Use this method to forward messages of any kind. + + Args: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + For a private channel/supergroup you can use its *t.me/joinchat/* link. + + from_chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the source chat where the original message was sent. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + For a private channel/supergroup you can use its *t.me/joinchat/* link. + + message_ids (``iterable``): + A list of Message identifiers in the chat specified in *from_chat_id* or a single message id. + Iterators and Generators are also accepted. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + Returns: + On success and in case *message_ids* was a list, the returned value will be a list of the forwarded + :obj:`Messages ` even if a list contains just one element, otherwise if + *message_ids* was an integer, the single forwarded :obj:`Message ` + is returned. + + Raises: + :class:`Error ` + """ + is_iterable = not isinstance(message_ids, int) + message_ids = list(message_ids) if is_iterable else [message_ids] + + r = self.send( + functions.messages.ForwardMessages( + to_peer=self.resolve_peer(chat_id), + from_peer=self.resolve_peer(from_chat_id), + id=message_ids, + silent=disable_notification or None, + random_id=[self.rnd_id() for _ in message_ids] + ) + ) + + messages = [] + + users = {i.id: i for i in r.users} + chats = {i.id: i for i in r.chats} + + for i in r.updates: + if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): + messages.append( + utils.parse_messages( + self, i.message, + users, chats + ) + ) + + return messages if is_iterable else messages[0] diff --git a/pyrogram/client/methods/messages/get_history.py b/pyrogram/client/methods/messages/get_history.py new file mode 100644 index 00000000..c2acb423 --- /dev/null +++ b/pyrogram/client/methods/messages/get_history.py @@ -0,0 +1,101 @@ +# 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 . + +from pyrogram.api import functions +from ...ext import BaseClient, utils + + +class GetHistory(BaseClient): + def get_history(self, + chat_id: int or str, + offset: int = 0, + limit: int = 100, + offset_id: int = 0, + offset_date: int = 0): + """Use this method to retrieve the history of a chat. + + You can get up to 100 messages at once. + + Args: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + For a private channel/supergroup you can use its *t.me/joinchat/* link. + + offset (``int``, *optional*) + Sequential number of the first message to be returned. + Defaults to 0 (most recent message). + + limit (``int``, *optional*): + Limits the number of messages to be retrieved. + By default, the first 100 messages are returned. + + offset_id (``int``, *optional*): + Pass a message identifier as offset to retrieve only older messages starting from that message. + + offset_date (``int``, *optional*): + Pass a date in Unix time as offset to retrieve only older messages starting from that date. + """ + + r = self.send( + functions.messages.GetHistory( + peer=self.resolve_peer(chat_id), + offset_id=offset_id, + offset_date=offset_date, + add_offset=offset, + limit=limit, + max_id=0, + min_id=0, + hash=0 + ) + ) + + users = {i.id: i for i in r.users} + chats = {i.id: i for i in r.chats} + + reply_to_messages = { + i.reply_to_msg_id: None + for i in r.messages + if i.reply_to_msg_id + } + + if reply_to_messages: + temp = self.get_messages( + chat_id, reply_to_messages, + replies=0 + ) + + assert len(temp) == len(reply_to_messages) + + for i in range(len(temp)): + reply_to_messages[temp[i].message_id] = temp[i] + + messages = utils.parse_messages( + self, r.messages, + users, chats, + replies=0 + ) + + assert len(messages) == len(r.messages) + + for i in range(len(messages)): + if r.messages[i].reply_to_msg_id: + messages[i].reply_to_message = reply_to_messages[r.messages[i].reply_to_msg_id] + + return messages diff --git a/pyrogram/client/methods/messages/get_messages.py b/pyrogram/client/methods/messages/get_messages.py new file mode 100644 index 00000000..49535a40 --- /dev/null +++ b/pyrogram/client/methods/messages/get_messages.py @@ -0,0 +1,78 @@ +# 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 . + +from pyrogram.api import functions, types +from ...ext import BaseClient, utils + + +class GetMessages(BaseClient): + def get_messages(self, + chat_id: int or str, + message_ids, + replies: int = 1): + """Use this method to get messages that belong to a specific chat. + You can retrieve up to 200 messages at once. + + Args: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + For a private channel/supergroup you can use its *t.me/joinchat/* link. + + message_ids (``iterable``): + A list of Message identifiers in the chat specified in *chat_id* or a single message id, as integer. + Iterators and Generators are also accepted. + + replies (``int``, *optional*): + The number of subsequent replies to get for each message. Defaults to 1. + + Returns: + On success and in case *message_ids* was a list, the returned value will be a list of the requested + :obj:`Messages ` even if a list contains just one element, otherwise if + *message_ids* was an integer, the single requested :obj:`Message ` + is returned. + + Raises: + :class:`Error ` + """ + peer = self.resolve_peer(chat_id) + is_iterable = not isinstance(message_ids, int) + message_ids = list(message_ids) if is_iterable else [message_ids] + message_ids = [types.InputMessageID(i) for i in message_ids] + + if isinstance(peer, types.InputPeerChannel): + rpc = functions.channels.GetMessages( + channel=peer, + id=message_ids + ) + else: + rpc = functions.messages.GetMessages( + id=message_ids + ) + + r = self.send(rpc) + + messages = utils.parse_messages( + self, r.messages, + {i.id: i for i in r.users}, + {i.id: i for i in r.chats}, + replies=replies + ) + + return messages if is_iterable else messages[0] diff --git a/pyrogram/client/methods/messages/media/__init__.py b/pyrogram/client/methods/messages/media/__init__.py new file mode 100644 index 00000000..564efa6f --- /dev/null +++ b/pyrogram/client/methods/messages/media/__init__.py @@ -0,0 +1,45 @@ +# 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 . + +from .send_audio import SendAudio +from .send_contact import SendContact +from .send_document import SendDocument +from .send_media_group import SendMediaGroup +from .send_photo import SendPhoto +from .send_sticker import SendSticker +from .send_location import SendLocation +from .send_venue import SendVenue +from .send_video import SendVideo +from .send_video_note import SendVideoNote +from .send_voice import SendVoice + + +class Media( + SendContact, + SendVenue, + SendLocation, + SendMediaGroup, + SendVideoNote, + SendVoice, + SendVideo, + SendSticker, + SendDocument, + SendAudio, + SendPhoto +): + pass diff --git a/pyrogram/client/methods/messages/media/send_audio.py b/pyrogram/client/methods/messages/media/send_audio.py new file mode 100644 index 00000000..0d08211e --- /dev/null +++ b/pyrogram/client/methods/messages/media/send_audio.py @@ -0,0 +1,183 @@ +# 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 binascii +import mimetypes +import os +import struct + +from pyrogram.api import functions, types +from pyrogram.api.errors import FileIdInvalid, FilePartMissing +from ....ext import BaseClient, utils + + +class SendAudio(BaseClient): + def send_audio(self, + chat_id: int or str, + audio: str, + caption: str = "", + parse_mode: str = "", + duration: int = 0, + performer: str = None, + title: str = None, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup=None, + progress: callable = None, + progress_args: tuple = ()): + """Use this method to send audio files. + + For sending voice messages, use the :obj:`send_voice()` method instead. + + Args: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + For a private channel/supergroup you can use its *t.me/joinchat/* link. + + audio (``str``): + Audio file to send. + Pass a file_id as string to send an audio file that exists on the Telegram servers, + pass an HTTP URL as a string for Telegram to get an audio file from the Internet, or + pass a file path as string to upload a new audio file that exists on your local machine. + + caption (``str``, *optional*): + Audio caption, 0-200 characters. + + parse_mode (``str``, *optional*): + Use :obj:`MARKDOWN ` or :obj:`HTML ` + if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your caption. + Defaults to Markdown. + + duration (``int``, *optional*): + Duration of the audio in seconds. + + performer (``str``, *optional*): + Performer. + + title (``str``, *optional*): + Track name. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message. + + reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + Additional interface options. An object for an inline keyboard, custom reply keyboard, + instructions to remove reply keyboard or to force a reply from the user. + + progress (``callable``, *optional*): + Pass a callback function to view the upload progress. + The function must take *(client, current, total, \*args)* as positional arguments (look at the section + below for a detailed description). + + progress_args (``tuple``, *optional*): + Extra custom arguments for the progress callback function. Useful, for example, if you want to pass + a chat_id and a message_id in order to edit a message with the updated progress. + + Other Parameters: + client (:obj:`Client `): + The Client itself, useful when you want to call other API methods inside the callback function. + + current (``int``): + The amount of bytes uploaded so far. + + total (``int``): + The size of the file. + + *args (``tuple``): + Extra custom arguments as defined in the *progress_args* parameter. + You can either keep *\*args* or add every single extra argument in your function signature. + + Returns: + On success, the sent :obj:`Message ` is returned. + + Raises: + :class:`Error ` + """ + file = None + style = self.html if parse_mode.lower() == "html" else self.markdown + + if os.path.exists(audio): + file = self.save_file(audio, progress=progress, progress_args=progress_args) + media = types.InputMediaUploadedDocument( + mime_type=mimetypes.types_map.get("." + audio.split(".")[-1], "audio/mpeg"), + file=file, + attributes=[ + types.DocumentAttributeAudio( + duration=duration, + performer=performer, + title=title + ), + types.DocumentAttributeFilename(os.path.basename(audio)) + ] + ) + elif audio.startswith("http"): + media = types.InputMediaDocumentExternal( + url=audio + ) + else: + try: + decoded = utils.decode(audio) + fmt = " 24 else " +# +# 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 . + +from pyrogram.api import functions, types +from ....ext import BaseClient, utils + + +class SendContact(BaseClient): + def send_contact(self, + chat_id: int or str, + phone_number: str, + first_name: str, + last_name: str = "", + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup=None): + """Use this method to send phone contacts. + + Args: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + For a private channel/supergroup you can use its *t.me/joinchat/* link. + + phone_number (``str``): + Contact's phone number. + + first_name (``str``): + Contact's first name. + + last_name (``str``, *optional*): + Contact's last name. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message. + + reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + Additional interface options. An object for an inline keyboard, custom reply keyboard, + instructions to remove reply keyboard or to force a reply from the user. + + Returns: + On success, the sent :obj:`Message ` is returned. + + Raises: + :class:`Error ` + """ + r = self.send( + functions.messages.SendMedia( + peer=self.resolve_peer(chat_id), + media=types.InputMediaContact( + phone_number, + first_name, + last_name + ), + message="", + silent=disable_notification or None, + reply_to_msg_id=reply_to_message_id, + random_id=self.rnd_id(), + reply_markup=reply_markup.write() if reply_markup else None + ) + ) + + for i in r.updates: + if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): + return utils.parse_messages( + self, i.message, + {i.id: i for i in r.users}, + {i.id: i for i in r.chats} + ) diff --git a/pyrogram/client/methods/messages/media/send_document.py b/pyrogram/client/methods/messages/media/send_document.py new file mode 100644 index 00000000..5cc5900d --- /dev/null +++ b/pyrogram/client/methods/messages/media/send_document.py @@ -0,0 +1,164 @@ +# 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 binascii +import mimetypes +import os +import struct + +from pyrogram.api import functions, types +from pyrogram.api.errors import FileIdInvalid, FilePartMissing +from ....ext import BaseClient, utils + + +class SendDocument(BaseClient): + def send_document(self, + chat_id: int or str, + document: str, + caption: str = "", + parse_mode: str = "", + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup=None, + progress: callable = None, + progress_args: tuple = ()): + """Use this method to send general files. + + Args: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + For a private channel/supergroup you can use its *t.me/joinchat/* link. + + document (``str``): + File to send. + Pass a file_id as string to send a file that exists on the Telegram servers, + pass an HTTP URL as a string for Telegram to get a file from the Internet, or + pass a file path as string to upload a new file that exists on your local machine. + + caption (``str``, *optional*): + Document caption, 0-200 characters. + + parse_mode (``str``, *optional*): + Use :obj:`MARKDOWN ` or :obj:`HTML ` + if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your caption. + Defaults to Markdown. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message. + + reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + Additional interface options. An object for an inline keyboard, custom reply keyboard, + instructions to remove reply keyboard or to force a reply from the user. + + progress (``callable``, *optional*): + Pass a callback function to view the upload progress. + The function must take *(client, current, total, \*args)* as positional arguments (look at the section + below for a detailed description). + + progress_args (``tuple``, *optional*): + Extra custom arguments for the progress callback function. Useful, for example, if you want to pass + a chat_id and a message_id in order to edit a message with the updated progress. + + Other Parameters: + client (:obj:`Client `): + The Client itself, useful when you want to call other API methods inside the callback function. + + current (``int``): + The amount of bytes uploaded so far. + + total (``int``): + The size of the file. + + *args (``tuple``): + Extra custom arguments as defined in the *progress_args* parameter. + You can either keep *\*args* or add every single extra argument in your function signature. + + Returns: + On success, the sent :obj:`Message ` is returned. + + Raises: + :class:`Error ` + """ + file = None + style = self.html if parse_mode.lower() == "html" else self.markdown + + if os.path.exists(document): + file = self.save_file(document, progress=progress, progress_args=progress_args) + media = types.InputMediaUploadedDocument( + mime_type=mimetypes.types_map.get("." + document.split(".")[-1], "text/plain"), + file=file, + attributes=[ + types.DocumentAttributeFilename(os.path.basename(document)) + ] + ) + elif document.startswith("http"): + media = types.InputMediaDocumentExternal( + url=document + ) + else: + try: + decoded = utils.decode(document) + fmt = " 24 else " +# +# 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 . + +from pyrogram.api import functions, types +from ....ext import BaseClient, utils + + +class SendLocation(BaseClient): + def send_location(self, + chat_id: int or str, + latitude: float, + longitude: float, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup=None): + """Use this method to send points on the map. + + Args: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + For a private channel/supergroup you can use its *t.me/joinchat/* link. + + latitude (``float``): + Latitude of the location. + + longitude (``float``): + Longitude of the location. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message + + reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + Additional interface options. An object for an inline keyboard, custom reply keyboard, + instructions to remove reply keyboard or to force a reply from the user. + + Returns: + On success, the sent :obj:`Message ` is returned. + + Raises: + :class:`Error ` + """ + r = self.send( + functions.messages.SendMedia( + peer=self.resolve_peer(chat_id), + media=types.InputMediaGeoPoint( + types.InputGeoPoint( + latitude, + longitude + ) + ), + message="", + silent=disable_notification or None, + reply_to_msg_id=reply_to_message_id, + random_id=self.rnd_id(), + reply_markup=reply_markup.write() if reply_markup else None + ) + ) + + for i in r.updates: + if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): + return utils.parse_messages( + self, i.message, + {i.id: i for i in r.users}, + {i.id: i for i in r.chats} + ) diff --git a/pyrogram/client/methods/messages/media/send_media_group.py b/pyrogram/client/methods/messages/media/send_media_group.py new file mode 100644 index 00000000..6d004d9f --- /dev/null +++ b/pyrogram/client/methods/messages/media/send_media_group.py @@ -0,0 +1,170 @@ +# 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 binascii +import mimetypes +import os +import struct + +from pyrogram.api import functions, types +from pyrogram.api.errors import FileIdInvalid +from pyrogram.client import types as pyrogram_types +from ....ext import BaseClient, utils + + +class SendMediaGroup(BaseClient): + # TODO: Add progress parameter + # TODO: Return new Message object + # TODO: Figure out how to send albums using URLs + def send_media_group(self, + chat_id: int or str, + media: list, + disable_notification: bool = None, + reply_to_message_id: int = None): + """Use this method to send a group of photos or videos as an album. + On success, an Update containing the sent Messages is returned. + + Args: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + For a private channel/supergroup you can use its *t.me/joinchat/* link. + + media (``list``): + A list containing either :obj:`InputMediaPhoto ` or + :obj:`InputMediaVideo ` objects + describing photos and videos to be sent, must include 2–10 items. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message. + """ + multi_media = [] + + for i in media: + style = self.html if i.parse_mode.lower() == "html" else self.markdown + + if isinstance(i, pyrogram_types.InputMediaPhoto): + if os.path.exists(i.media): + media = self.send( + functions.messages.UploadMedia( + peer=self.resolve_peer(chat_id), + media=types.InputMediaUploadedPhoto( + file=self.save_file(i.media) + ) + ) + ) + + media = types.InputMediaPhoto( + id=types.InputPhoto( + id=media.photo.id, + access_hash=media.photo.access_hash + ) + ) + else: + try: + decoded = utils.decode(i.media) + fmt = " 24 else " 24 else " +# +# 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 binascii +import os +import struct + +from pyrogram.api import functions, types +from pyrogram.api.errors import FileIdInvalid, FilePartMissing +from ....ext import BaseClient, utils + + +class SendPhoto(BaseClient): + def send_photo(self, + 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, + reply_markup=None, + progress: callable = None, + progress_args: tuple = ()): + """Use this method to send photos. + + Args: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + For a private channel/supergroup you can use its *t.me/joinchat/* link. + + photo (``str``): + Photo to send. + Pass a file_id as string to send a photo that exists on the Telegram servers, + pass an HTTP URL as a string for Telegram to get a photo from the Internet, or + pass a file path as string to upload a new photo that exists on your local machine. + + caption (``bool``, *optional*): + Photo caption, 0-200 characters. + + parse_mode (``str``, *optional*): + Use :obj:`MARKDOWN ` or :obj:`HTML ` + if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your caption. + Defaults to Markdown. + + ttl_seconds (``int``, *optional*): + Self-Destruct Timer. + If you set a timer, the photo will self-destruct in *ttl_seconds* + seconds after it was viewed. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message. + + reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + Additional interface options. An object for an inline keyboard, custom reply keyboard, + instructions to remove reply keyboard or to force a reply from the user. + + progress (``callable``, *optional*): + Pass a callback function to view the upload progress. + The function must take *(client, current, total, \*args)* as positional arguments (look at the section + below for a detailed description). + + progress_args (``tuple``, *optional*): + Extra custom arguments for the progress callback function. Useful, for example, if you want to pass + a chat_id and a message_id in order to edit a message with the updated progress. + + Other Parameters: + client (:obj:`Client `): + The Client itself, useful when you want to call other API methods inside the callback function. + + current (``int``): + The amount of bytes uploaded so far. + + total (``int``): + The size of the file. + + *args (``tuple``): + Extra custom arguments as defined in the *progress_args* parameter. + You can either keep *\*args* or add every single extra argument in your function signature. + + Returns: + On success, the sent :obj:`Message ` is returned. + + Raises: + :class:`Error ` + """ + file = None + style = self.html if parse_mode.lower() == "html" else self.markdown + + if os.path.exists(photo): + file = self.save_file(photo, progress=progress, progress_args=progress_args) + media = types.InputMediaUploadedPhoto( + file=file, + ttl_seconds=ttl_seconds + ) + elif photo.startswith("http"): + media = types.InputMediaPhotoExternal( + url=photo, + ttl_seconds=ttl_seconds + ) + else: + try: + decoded = utils.decode(photo) + fmt = " 24 else " +# +# 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 binascii +import os +import struct + +from pyrogram.api import functions, types +from pyrogram.api.errors import FileIdInvalid, FilePartMissing +from ....ext import BaseClient, utils + + +class SendSticker(BaseClient): + def send_sticker(self, + chat_id: int or str, + sticker: str, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup=None, + progress: callable = None, + progress_args: tuple = ()): + """Use this method to send .webp stickers. + + Args: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + For a private channel/supergroup you can use its *t.me/joinchat/* link. + + sticker (``str``): + Sticker to send. + Pass a file_id as string to send a sticker that exists on the Telegram servers, + pass an HTTP URL as a string for Telegram to get a .webp sticker file from the Internet, or + pass a file path as string to upload a new sticker that exists on your local machine. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message. + + reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + Additional interface options. An object for an inline keyboard, custom reply keyboard, + instructions to remove reply keyboard or to force a reply from the user. + + progress (``callable``, *optional*): + Pass a callback function to view the upload progress. + The function must take *(client, current, total, \*args)* as positional arguments (look at the section + below for a detailed description). + + progress_args (``tuple``, *optional*): + Extra custom arguments for the progress callback function. Useful, for example, if you want to pass + a chat_id and a message_id in order to edit a message with the updated progress. + + Other Parameters: + client (:obj:`Client `): + The Client itself, useful when you want to call other API methods inside the callback function. + + current (``int``): + The amount of bytes uploaded so far. + + total (``int``): + The size of the file. + + *args (``tuple``): + Extra custom arguments as defined in the *progress_args* parameter. + You can either keep *\*args* or add every single extra argument in your function signature. + + Returns: + On success, the sent :obj:`Message ` is returned. + + Raises: + :class:`Error ` + """ + file = None + + if os.path.exists(sticker): + file = self.save_file(sticker, progress=progress, progress_args=progress_args) + media = types.InputMediaUploadedDocument( + mime_type="image/webp", + file=file, + attributes=[ + types.DocumentAttributeFilename(os.path.basename(sticker)) + ] + ) + elif sticker.startswith("http"): + media = types.InputMediaDocumentExternal( + url=sticker + ) + else: + try: + decoded = utils.decode(sticker) + fmt = " 24 else " +# +# 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 . + +from pyrogram.api import functions, types +from ....ext import BaseClient, utils + + +class SendVenue(BaseClient): + def send_venue(self, + chat_id: int or str, + latitude: float, + longitude: float, + title: str, + address: str, + foursquare_id: str = "", + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup=None): + """Use this method to send information about a venue. + + Args: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + For a private channel/supergroup you can use its *t.me/joinchat/* link. + + latitude (``float``): + Latitude of the venue. + + longitude (``float``): + Longitude of the venue. + + title (``str``): + Name of the venue. + + address (``str``): + Address of the venue. + + foursquare_id (``str``, *optional*): + Foursquare identifier of the venue. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message + + reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + Additional interface options. An object for an inline keyboard, custom reply keyboard, + instructions to remove reply keyboard or to force a reply from the user. + + Returns: + On success, the sent :obj:`Message ` is returned. + + Raises: + :class:`Error ` + """ + r = self.send( + functions.messages.SendMedia( + peer=self.resolve_peer(chat_id), + media=types.InputMediaVenue( + geo_point=types.InputGeoPoint( + lat=latitude, + long=longitude + ), + title=title, + address=address, + provider="", + venue_id=foursquare_id, + venue_type="" + ), + message="", + silent=disable_notification or None, + reply_to_msg_id=reply_to_message_id, + random_id=self.rnd_id(), + reply_markup=reply_markup.write() if reply_markup else None + ) + ) + + for i in r.updates: + if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): + return utils.parse_messages( + self, i.message, + {i.id: i for i in r.users}, + {i.id: i for i in r.chats} + ) diff --git a/pyrogram/client/methods/messages/media/send_video.py b/pyrogram/client/methods/messages/media/send_video.py new file mode 100644 index 00000000..c0eb4eec --- /dev/null +++ b/pyrogram/client/methods/messages/media/send_video.py @@ -0,0 +1,194 @@ +# 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 binascii +import mimetypes +import os +import struct + +from pyrogram.api import functions, types +from pyrogram.api.errors import FileIdInvalid, FilePartMissing +from ....ext import BaseClient, utils + + +class SendVideo(BaseClient): + 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, + thumb: str = None, + supports_streaming: bool = True, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup=None, + progress: callable = None, + progress_args: tuple = ()): + """Use this method to send video files. + + Args: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + For a private channel/supergroup you can use its *t.me/joinchat/* link. + + video (``str``): + Video to send. + Pass a file_id as string to send a video that exists on the Telegram servers, + pass an HTTP URL as a string for Telegram to get a video from the Internet, or + pass a file path as string to upload a new video that exists on your local machine. + + caption (``str``, *optional*): + Video caption, 0-200 characters. + + parse_mode (``str``, *optional*): + Use :obj:`MARKDOWN ` or :obj:`HTML ` + if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your caption. + Defaults to Markdown. + + duration (``int``, *optional*): + Duration of sent video in seconds. + + width (``int``, *optional*): + Video width. + + height (``int``, *optional*): + Video height. + + thumb (``str``, *optional*): + Video thumbnail. + Pass a file path as string to send an image that exists on your local machine. + Thumbnail should have 90 or less pixels of width and 90 or less pixels of height. + + supports_streaming (``bool``, *optional*): + Pass True, if the uploaded video is suitable for streaming. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message. + + reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + Additional interface options. An object for an inline keyboard, custom reply keyboard, + instructions to remove reply keyboard or to force a reply from the user. + + progress (``callable``, *optional*): + Pass a callback function to view the upload progress. + The function must take *(client, current, total, \*args)* as positional arguments (look at the section + below for a detailed description). + + progress_args (``tuple``, *optional*): + Extra custom arguments for the progress callback function. Useful, for example, if you want to pass + a chat_id and a message_id in order to edit a message with the updated progress. + + Other Parameters: + client (:obj:`Client `): + The Client itself, useful when you want to call other API methods inside the callback function. + + current (``int``): + The amount of bytes uploaded so far. + + total (``int``): + The size of the file. + + *args (``tuple``): + Extra custom arguments as defined in the *progress_args* parameter. + You can either keep *\*args* or add every single extra argument in your function signature. + + Returns: + On success, the sent :obj:`Message ` is returned. + + Raises: + :class:`Error ` + """ + file = None + style = self.html if parse_mode.lower() == "html" else self.markdown + + if os.path.exists(video): + thumb = None if thumb is None else self.save_file(thumb) + file = self.save_file(video, progress=progress, progress_args=progress_args) + media = types.InputMediaUploadedDocument( + mime_type=mimetypes.types_map[".mp4"], + file=file, + thumb=thumb, + attributes=[ + types.DocumentAttributeVideo( + supports_streaming=supports_streaming or None, + duration=duration, + w=width, + h=height + ), + types.DocumentAttributeFilename(os.path.basename(video)) + ] + ) + elif video.startswith("http"): + media = types.InputMediaDocumentExternal( + url=video + ) + else: + try: + decoded = utils.decode(video) + fmt = " 24 else " +# +# 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 binascii +import mimetypes +import os +import struct + +from pyrogram.api import functions, types +from pyrogram.api.errors import FileIdInvalid, FilePartMissing +from ....ext import BaseClient, utils + + +class SendVideoNote(BaseClient): + def send_video_note(self, + chat_id: int or str, + video_note: str, + duration: int = 0, + length: int = 1, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup=None, + progress: callable = None, + progress_args: tuple = ()): + """Use this method to send video messages. + + Args: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + For a private channel/supergroup you can use its *t.me/joinchat/* link. + + video_note (``str``): + Video note to send. + Pass a file_id as string to send a video note that exists on the Telegram servers, or + pass a file path as string to upload a new video note that exists on your local machine. + Sending video notes by a URL is currently unsupported. + + duration (``int``, *optional*): + Duration of sent video in seconds. + + length (``int``, *optional*): + Video width and height. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message + + reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + Additional interface options. An object for an inline keyboard, custom reply keyboard, + instructions to remove reply keyboard or to force a reply from the user. + + progress (``callable``, *optional*): + Pass a callback function to view the upload progress. + The function must take *(client, current, total, \*args)* as positional arguments (look at the section + below for a detailed description). + + progress_args (``tuple``, *optional*): + Extra custom arguments for the progress callback function. Useful, for example, if you want to pass + a chat_id and a message_id in order to edit a message with the updated progress. + + Other Parameters: + client (:obj:`Client `): + The Client itself, useful when you want to call other API methods inside the callback function. + + current (``int``): + The amount of bytes uploaded so far. + + total (``int``): + The size of the file. + + *args (``tuple``): + Extra custom arguments as defined in the *progress_args* parameter. + You can either keep *\*args* or add every single extra argument in your function signature. + + Returns: + On success, the sent :obj:`Message ` is returned. + + Raises: + :class:`Error ` + """ + file = None + + if os.path.exists(video_note): + file = self.save_file(video_note, progress=progress, progress_args=progress_args) + media = types.InputMediaUploadedDocument( + mime_type=mimetypes.types_map[".mp4"], + file=file, + attributes=[ + types.DocumentAttributeVideo( + round_message=True, + duration=duration, + w=length, + h=length + ) + ] + ) + else: + try: + decoded = utils.decode(video_note) + fmt = " 24 else " +# +# 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 binascii +import mimetypes +import os +import struct + +from pyrogram.api import functions, types +from pyrogram.api.errors import FileIdInvalid, FilePartMissing +from ....ext import BaseClient, utils + + +class SendVoice(BaseClient): + def send_voice(self, + 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, + reply_markup=None, + progress: callable = None, + progress_args: tuple = ()): + """Use this method to send audio files. + + Args: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + For a private channel/supergroup you can use its *t.me/joinchat/* link. + + voice (``str``): + Audio file to send. + Pass a file_id as string to send an audio that exists on the Telegram servers, + pass an HTTP URL as a string for Telegram to get an audio from the Internet, or + pass a file path as string to upload a new audio that exists on your local machine. + + caption (``str``, *optional*): + Voice message caption, 0-200 characters. + + parse_mode (``str``, *optional*): + Use :obj:`MARKDOWN ` or :obj:`HTML ` + if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your caption. + Defaults to Markdown. + + duration (``int``, *optional*): + Duration of the voice message in seconds. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message + + reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + Additional interface options. An object for an inline keyboard, custom reply keyboard, + instructions to remove reply keyboard or to force a reply from the user. + + progress (``callable``, *optional*): + Pass a callback function to view the upload progress. + The function must take *(client, current, total, \*args)* as positional arguments (look at the section + below for a detailed description). + + progress_args (``tuple``, *optional*): + Extra custom arguments for the progress callback function. Useful, for example, if you want to pass + a chat_id and a message_id in order to edit a message with the updated progress. + + Other Parameters: + client (:obj:`Client `): + The Client itself, useful when you want to call other API methods inside the callback function. + + current (``int``): + The amount of bytes uploaded so far. + + total (``int``): + The size of the file. + + *args (``tuple``): + Extra custom arguments as defined in the *progress_args* parameter. + You can either keep *\*args* or add every single extra argument in your function signature. + + Returns: + On success, the sent :obj:`Message ` is returned. + + Raises: + :class:`Error ` + """ + file = None + style = self.html if parse_mode.lower() == "html" else self.markdown + + if os.path.exists(voice): + file = self.save_file(voice, progress=progress, progress_args=progress_args) + media = types.InputMediaUploadedDocument( + mime_type=mimetypes.types_map.get("." + voice.split(".")[-1], "audio/mpeg"), + file=file, + attributes=[ + types.DocumentAttributeAudio( + voice=True, + duration=duration + ) + ] + ) + elif voice.startswith("http"): + media = types.InputMediaDocumentExternal( + url=voice + ) + else: + try: + decoded = utils.decode(voice) + fmt = " 24 else " +# +# 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 . + +from pyrogram.api import functions, types +from pyrogram.client import types as pyrogram_types +from ...ext import utils, BaseClient + + +class SendMessage(BaseClient): + 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_message_id: int = None, + reply_markup=None): + """Use this method to send text messages. + + Args: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + For a private channel/supergroup you can use its *t.me/joinchat/* link. + + text (``str``): + Text of the message to be sent. + + parse_mode (``str``, *optional*): + Use :obj:`MARKDOWN ` or :obj:`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 (``bool``, *optional*): + Disables link previews for links in this message. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message. + + reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + Additional interface options. An object for an inline keyboard, custom reply keyboard, + instructions to remove reply keyboard or to force a reply from the user. + + Returns: + On success, the sent Message is returned. + + Raises: + :class:`Error ` + """ + style = self.html if parse_mode.lower() == "html" else self.markdown + + r = self.send( + functions.messages.SendMessage( + peer=self.resolve_peer(chat_id), + no_webpage=disable_web_page_preview or None, + silent=disable_notification or None, + reply_to_msg_id=reply_to_message_id, + random_id=self.rnd_id(), + reply_markup=reply_markup.write() if reply_markup else None, + **style.parse(text) + ) + ) + + if isinstance(r, types.UpdateShortSentMessage): + return pyrogram_types.Message( + message_id=r.id, + date=r.date, + outgoing=r.out, + entities=utils.parse_entities(r.entities, {}) or None + ) + + for i in r.updates: + if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): + return utils.parse_messages( + self, i.message, + {i.id: i for i in r.users}, + {i.id: i for i in r.chats} + ) diff --git a/pyrogram/client/methods/messages/update/__init__.py b/pyrogram/client/methods/messages/update/__init__.py new file mode 100644 index 00000000..cc913e23 --- /dev/null +++ b/pyrogram/client/methods/messages/update/__init__.py @@ -0,0 +1,31 @@ +# 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 . + +from .delete_messages import DeleteMessages +from .edit_message_caption import EditMessageCaption +from .edit_message_reply_markup import EditMessageReplyMarkup +from .edit_message_text import EditMessageText + + +class Update( + DeleteMessages, + EditMessageReplyMarkup, + EditMessageCaption, + EditMessageText +): + pass diff --git a/pyrogram/client/methods/messages/update/delete_messages.py b/pyrogram/client/methods/messages/update/delete_messages.py new file mode 100644 index 00000000..3d29bf55 --- /dev/null +++ b/pyrogram/client/methods/messages/update/delete_messages.py @@ -0,0 +1,77 @@ +# 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 . + +from pyrogram.api import functions, types +from ....ext import BaseClient + + +class DeleteMessages(BaseClient): + def delete_messages(self, + chat_id: int or str, + message_ids, + revoke: bool = True): + """Use this method to delete messages, including service messages, with the following limitations: + + - A message can only be deleted if it was sent less than 48 hours ago. + - Users can delete outgoing messages in groups and supergroups. + - Users granted *can_post_messages* permissions can delete outgoing messages in channels. + - If the user is an administrator of a group, it can delete any message there. + - If the user has *can_delete_messages* permission in a supergroup or a channel, it can delete any message there. + + Args: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + For a private channel/supergroup you can use its *t.me/joinchat/* link. + + message_ids (``iterable``): + A list of Message identifiers to delete or a single message id. + Iterators and Generators are also accepted. + + revoke (``bool``, *optional*): + Deletes messages on both parts. + This is only for private cloud chats and normal groups, messages on + channels and supergroups are always revoked (i.e.: deleted for everyone). + Defaults to True. + + Returns: + True on success. + + Raises: + :class:`Error ` + """ + peer = self.resolve_peer(chat_id) + message_ids = list(message_ids) if not isinstance(message_ids, int) else [message_ids] + + if isinstance(peer, types.InputPeerChannel): + self.send( + functions.channels.DeleteMessages( + channel=peer, + id=message_ids + ) + ) + else: + self.send( + functions.messages.DeleteMessages( + id=message_ids, + revoke=revoke or None + ) + ) + + return True diff --git a/pyrogram/client/methods/messages/update/edit_message_caption.py b/pyrogram/client/methods/messages/update/edit_message_caption.py new file mode 100644 index 00000000..90bf26f7 --- /dev/null +++ b/pyrogram/client/methods/messages/update/edit_message_caption.py @@ -0,0 +1,76 @@ +# 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 . + +from pyrogram.api import functions, types +from ....ext import BaseClient, utils + + +class EditMessageCaption(BaseClient): + def edit_message_caption(self, + chat_id: int or str, + message_id: int, + caption: str, + parse_mode: str = "", + reply_markup=None): + """Use this method to edit captions of messages. + + Args: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + For a private channel/supergroup you can use its *t.me/joinchat/* link. + + message_id (``int``): + Message identifier in the chat specified in chat_id. + + caption (``str``): + New caption of the message. + + parse_mode (``str``, *optional*): + Use :obj:`MARKDOWN ` or :obj:`HTML ` + if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your caption. + Defaults to Markdown. + + reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + An InlineKeyboardMarkup object. + + Returns: + On success, the edited :obj:`Message ` is returned. + + Raises: + :class:`Error ` + """ + style = self.html if parse_mode.lower() == "html" else self.markdown + + r = self.send( + functions.messages.EditMessage( + peer=self.resolve_peer(chat_id), + id=message_id, + reply_markup=reply_markup.write() if reply_markup else None, + **style.parse(caption) + ) + ) + + for i in r.updates: + if isinstance(i, (types.UpdateEditMessage, types.UpdateEditChannelMessage)): + return utils.parse_messages( + self, i.message, + {i.id: i for i in r.users}, + {i.id: i for i in r.chats} + ) diff --git a/pyrogram/client/methods/messages/update/edit_message_reply_markup.py b/pyrogram/client/methods/messages/update/edit_message_reply_markup.py new file mode 100644 index 00000000..295eb258 --- /dev/null +++ b/pyrogram/client/methods/messages/update/edit_message_reply_markup.py @@ -0,0 +1,65 @@ +# 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 . + +from pyrogram.api import functions, types +from ....ext import BaseClient, utils + + +class EditMessageReplyMarkup(BaseClient): + def edit_message_reply_markup(self, + chat_id: int or str, + message_id: int, + reply_markup=None): + """Use this method to edit only the reply markup of messages sent by the bot or via the bot (for inline bots). + + Args: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + For a private channel/supergroup you can use its *t.me/joinchat/* link. + + message_id (``int``): + Message identifier in the chat specified in chat_id. + + reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + An InlineKeyboardMarkup object. + + Returns: + On success, if edited message is sent by the bot, the edited + :obj:`Message ` is returned, otherwise True is returned. + + Raises: + :class:`Error ` + """ + + r = self.send( + functions.messages.EditMessage( + peer=self.resolve_peer(chat_id), + id=message_id, + reply_markup=reply_markup.write() if reply_markup else None + ) + ) + + for i in r.updates: + if isinstance(i, (types.UpdateEditMessage, types.UpdateEditChannelMessage)): + return utils.parse_messages( + self, i.message, + {i.id: i for i in r.users}, + {i.id: i for i in r.chats} + ) diff --git a/pyrogram/client/methods/messages/update/edit_message_text.py b/pyrogram/client/methods/messages/update/edit_message_text.py new file mode 100644 index 00000000..be7b380c --- /dev/null +++ b/pyrogram/client/methods/messages/update/edit_message_text.py @@ -0,0 +1,81 @@ +# 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 . + +from pyrogram.api import functions, types +from ....ext import BaseClient, utils + + +class EditMessageText(BaseClient): + def edit_message_text(self, + chat_id: int or str, + message_id: int, + text: str, + parse_mode: str = "", + disable_web_page_preview: bool = None, + reply_markup=None): + """Use this method to edit text messages. + + Args: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + For a private channel/supergroup you can use its *t.me/joinchat/* link. + + message_id (``int``): + Message identifier in the chat specified in chat_id. + + text (``str``): + New text of the message. + + parse_mode (``str``, *optional*): + Use :obj:`MARKDOWN ` or :obj:`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 (``bool``, *optional*): + Disables link previews for links in this message. + + reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + An InlineKeyboardMarkup object. + + Returns: + On success, the edited :obj:`Message ` is returned. + + Raises: + :class:`Error ` + """ + style = self.html if parse_mode.lower() == "html" else self.markdown + + r = self.send( + functions.messages.EditMessage( + peer=self.resolve_peer(chat_id), + id=message_id, + no_webpage=disable_web_page_preview or None, + reply_markup=reply_markup.write() if reply_markup else None, + **style.parse(text) + ) + ) + + for i in r.updates: + if isinstance(i, (types.UpdateEditMessage, types.UpdateEditChannelMessage)): + return utils.parse_messages( + self, i.message, + {i.id: i for i in r.users}, + {i.id: i for i in r.chats} + ) diff --git a/pyrogram/client/methods/password/__init__.py b/pyrogram/client/methods/password/__init__.py new file mode 100644 index 00000000..07d8dd7d --- /dev/null +++ b/pyrogram/client/methods/password/__init__.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 . + +from .change_cloud_password import ChangeCloudPassword +from .enable_cloud_password import EnableCloudPassword +from .remove_cloud_password import RemoveCloudPassword + + +class Password( + RemoveCloudPassword, + ChangeCloudPassword, + EnableCloudPassword +): + pass diff --git a/pyrogram/client/methods/password/change_cloud_password.py b/pyrogram/client/methods/password/change_cloud_password.py new file mode 100644 index 00000000..045a0cc9 --- /dev/null +++ b/pyrogram/client/methods/password/change_cloud_password.py @@ -0,0 +1,65 @@ +# 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 os +from hashlib import sha256 + +from pyrogram.api import functions, types +from ...ext import BaseClient + + +class ChangeCloudPassword(BaseClient): + def change_cloud_password(self, current_password: str, new_password: str, new_hint: str = ""): + """Use this method to change your Two-Step Verification password (Cloud Password) with a new one. + + Args: + current_password (``str``): + Your current password. + + new_password (``str``): + Your new password. + + new_hint (``str``, *optional*): + A new password hint. + + Returns: + True on success, False otherwise. + + Raises: + :class:`Error ` + """ + r = self.send(functions.account.GetPassword()) + + if isinstance(r, types.account.Password): + current_password_hash = sha256(r.current_salt + current_password.encode() + r.current_salt).digest() + + new_salt = r.new_salt + os.urandom(8) + new_password_hash = sha256(new_salt + new_password.encode() + new_salt).digest() + + return self.send( + functions.account.UpdatePasswordSettings( + current_password_hash=current_password_hash, + new_settings=types.account.PasswordInputSettings( + new_salt=new_salt, + new_password_hash=new_password_hash, + hint=new_hint + ) + ) + ) + else: + return False diff --git a/pyrogram/client/methods/password/enable_cloud_password.py b/pyrogram/client/methods/password/enable_cloud_password.py new file mode 100644 index 00000000..639879cb --- /dev/null +++ b/pyrogram/client/methods/password/enable_cloud_password.py @@ -0,0 +1,66 @@ +# 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 os +from hashlib import sha256 + +from pyrogram.api import functions, types +from ...ext import BaseClient + + +class EnableCloudPassword(BaseClient): + def enable_cloud_password(self, password: str, hint: str = "", email: str = ""): + """Use this method to enable the Two-Step Verification security feature (Cloud Password) on your account. + + This password will be asked when you log in on a new device in addition to the SMS code. + + Args: + password (``str``): + Your password. + + hint (``str``, *optional*): + A password hint. + + email (``str``, *optional*): + Recovery e-mail. + + Returns: + True on success, False otherwise. + + Raises: + :class:`Error ` + """ + r = self.send(functions.account.GetPassword()) + + if isinstance(r, types.account.NoPassword): + salt = r.new_salt + os.urandom(8) + password_hash = sha256(salt + password.encode() + salt).digest() + + return self.send( + functions.account.UpdatePasswordSettings( + current_password_hash=salt, + new_settings=types.account.PasswordInputSettings( + new_salt=salt, + new_password_hash=password_hash, + hint=hint, + email=email + ) + ) + ) + else: + return False diff --git a/pyrogram/client/methods/password/remove_cloud_password.py b/pyrogram/client/methods/password/remove_cloud_password.py new file mode 100644 index 00000000..bfbb2c8b --- /dev/null +++ b/pyrogram/client/methods/password/remove_cloud_password.py @@ -0,0 +1,55 @@ +# 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 . + +from hashlib import sha256 + +from pyrogram.api import functions, types +from ...ext import BaseClient + + +class RemoveCloudPassword(BaseClient): + def remove_cloud_password(self, password: str): + """Use this method to turn off the Two-Step Verification security feature (Cloud Password) on your account. + + Args: + password (``str``): + Your current password. + + Returns: + True on success, False otherwise. + + Raises: + :class:`Error ` + """ + r = self.send(functions.account.GetPassword()) + + if isinstance(r, types.account.Password): + password_hash = sha256(r.current_salt + password.encode() + r.current_salt).digest() + + return self.send( + functions.account.UpdatePasswordSettings( + current_password_hash=password_hash, + new_settings=types.account.PasswordInputSettings( + new_salt=b"", + new_password_hash=b"", + hint="" + ) + ) + ) + else: + return False diff --git a/pyrogram/client/methods/users/__init__.py b/pyrogram/client/methods/users/__init__.py new file mode 100644 index 00000000..7a82667d --- /dev/null +++ b/pyrogram/client/methods/users/__init__.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 . + +from .get_me import GetMe +from .get_user_profile_photos import GetUserProfilePhotos +from .get_users import GetUsers + + +class Users( + GetUserProfilePhotos, + GetUsers, + GetMe +): + pass diff --git a/pyrogram/client/methods/users/get_me.py b/pyrogram/client/methods/users/get_me.py new file mode 100644 index 00000000..80ee65e9 --- /dev/null +++ b/pyrogram/client/methods/users/get_me.py @@ -0,0 +1,39 @@ +# 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 . + +from pyrogram.api import functions, types +from ...ext import BaseClient, utils + + +class GetMe(BaseClient): + def get_me(self): + """A simple method for testing your authorization. Requires no parameters. + + Returns: + Basic information about the user or bot in form of a :obj:`User` object + + Raises: + :class:`Error ` + """ + return utils.parse_user( + self.send( + functions.users.GetFullUser( + types.InputPeerSelf() + ) + ).user + ) diff --git a/pyrogram/client/methods/users/get_user_profile_photos.py b/pyrogram/client/methods/users/get_user_profile_photos.py new file mode 100644 index 00000000..42fb84bb --- /dev/null +++ b/pyrogram/client/methods/users/get_user_profile_photos.py @@ -0,0 +1,60 @@ +# 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 . + +from pyrogram.api import functions +from ...ext import BaseClient, utils + + +class GetUserProfilePhotos(BaseClient): + def get_user_profile_photos(self, + user_id: int or str, + offset: int = 0, + limit: int = 100): + """Use this method to get a list of profile pictures for a user. + + Args: + user_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + For a private channel/supergroup you can use its *t.me/joinchat/* link. + + offset (``int``, *optional*): + Sequential number of the first photo to be returned. + By default, all photos are returned. + + limit (``int``, *optional*): + Limits the number of photos to be retrieved. + Values between 1—100 are accepted. Defaults to 100. + + Returns: + On success, a :obj:`UserProfilePhotos` object is returned. + + Raises: + :class:`Error ` + """ + return utils.parse_photos( + self.send( + functions.photos.GetUserPhotos( + user_id=self.resolve_peer(user_id), + offset=offset, + max_id=0, + limit=limit + ) + ) + ) diff --git a/pyrogram/client/methods/users/get_users.py b/pyrogram/client/methods/users/get_users.py new file mode 100644 index 00000000..400e35a1 --- /dev/null +++ b/pyrogram/client/methods/users/get_users.py @@ -0,0 +1,57 @@ +# 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 . + +from pyrogram.api import functions +from ...ext import BaseClient, utils + + +class GetUsers(BaseClient): + def get_users(self, user_ids): + """Use this method to get information about a user. + You can retrieve up to 200 users at once. + + Args: + user_ids (``iterable``): + A list of User identifiers (id or username) or a single user id/username. + For a contact that exists in your Telegram address book you can use his phone number (str). + Iterators and Generators are also accepted. + + Returns: + On success and in case *user_ids* was a list, the returned value will be a list of the requested + :obj:`Users ` even if a list contains just one element, otherwise if + *user_ids* was an integer, the single requested :obj:`User` is returned. + + Raises: + :class:`Error ` + """ + is_iterable = not isinstance(user_ids, (int, str)) + user_ids = list(user_ids) if is_iterable else [user_ids] + user_ids = [self.resolve_peer(i) for i in user_ids] + + r = self.send( + functions.users.GetUsers( + id=user_ids + ) + ) + + users = [] + + for i in r: + users.append(utils.parse_user(i)) + + return users if is_iterable else users[0] diff --git a/pyrogram/client/types/reply_markup/reply_keyboard_remove.py b/pyrogram/client/types/reply_markup/reply_keyboard_remove.py index 84f71a06..b73b5894 100644 --- a/pyrogram/client/types/reply_markup/reply_keyboard_remove.py +++ b/pyrogram/client/types/reply_markup/reply_keyboard_remove.py @@ -16,7 +16,6 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . - from pyrogram.api.core import Object from pyrogram.api.types import ReplyKeyboardHide diff --git a/pyrogram/client/utils.py b/pyrogram/client/utils.py deleted file mode 100644 index e117316b..00000000 --- a/pyrogram/client/utils.py +++ /dev/null @@ -1,787 +0,0 @@ -# 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 . - -from base64 import b64decode, b64encode -from struct import pack -from weakref import proxy - -from pyrogram.client import types as pyrogram_types -from ..api import types, functions -from ..api.errors import StickersetInvalid - -# TODO: Organize the code better? - -ENTITIES = { - types.MessageEntityMention.ID: "mention", - types.MessageEntityHashtag.ID: "hashtag", - types.MessageEntityBotCommand.ID: "bot_command", - types.MessageEntityUrl.ID: "url", - types.MessageEntityEmail.ID: "email", - types.MessageEntityBold.ID: "bold", - types.MessageEntityItalic.ID: "italic", - types.MessageEntityCode.ID: "code", - types.MessageEntityPre.ID: "pre", - types.MessageEntityTextUrl.ID: "text_link", - types.MessageEntityMentionName.ID: "text_mention" -} - - -def parse_entities(entities: list, users: dict) -> list: - output_entities = [] - - for entity in entities: - entity_type = ENTITIES.get(entity.ID, None) - - if entity_type: - output_entities.append(pyrogram_types.MessageEntity( - type=entity_type, - offset=entity.offset, - length=entity.length, - url=getattr(entity, "url", None), - user=parse_user( - users.get( - getattr(entity, "user_id", None), - None - ) - ) - )) - - return output_entities - - -def parse_chat_photo(photo): - if not isinstance(photo, (types.UserProfilePhoto, types.ChatPhoto)): - return None - - if not isinstance(photo.photo_small, types.FileLocation): - return None - - if not isinstance(photo.photo_big, types.FileLocation): - return None - - photo_id = getattr(photo, "photo_id", 0) - loc_small = photo.photo_small - loc_big = photo.photo_big - - return pyrogram_types.ChatPhoto( - small_file_id=encode( - pack( - " pyrogram_types.User or None: - return pyrogram_types.User( - id=user.id, - is_bot=user.bot, - first_name=user.first_name, - last_name=user.last_name, - username=user.username, - language_code=user.lang_code, - phone_number=user.phone, - photo=parse_chat_photo(user.photo) - ) if user else None - - -def parse_chat(message: types.Message, users: dict, chats: dict) -> pyrogram_types.Chat: - if isinstance(message.to_id, types.PeerUser): - return parse_user_chat(users[message.to_id.user_id if message.out else message.from_id]) - elif isinstance(message.to_id, types.PeerChat): - return parse_chat_chat(chats[message.to_id.chat_id]) - else: - return parse_channel_chat(chats[message.to_id.channel_id]) - - -def parse_user_chat(user: types.User) -> pyrogram_types.Chat: - return pyrogram_types.Chat( - id=user.id, - type="private", - username=user.username, - first_name=user.first_name, - last_name=user.last_name, - photo=parse_chat_photo(user.photo) - ) - - -def parse_chat_chat(chat: types.Chat) -> pyrogram_types.Chat: - admins_enabled = getattr(chat, "admins_enabled", None) - - if admins_enabled is not None: - admins_enabled = not admins_enabled - - return pyrogram_types.Chat( - id=-chat.id, - type="group", - title=chat.title, - all_members_are_administrators=admins_enabled, - photo=parse_chat_photo(getattr(chat, "photo", None)) - ) - - -def parse_channel_chat(channel: types.Channel) -> pyrogram_types.Chat: - return pyrogram_types.Chat( - id=int("-100" + str(channel.id)), - type="supergroup" if channel.megagroup else "channel", - title=channel.title, - username=getattr(channel, "username", None), - photo=parse_chat_photo(getattr(channel, "photo", None)) - ) - - -def parse_thumb(thumb: types.PhotoSize or types.PhotoCachedSize) -> pyrogram_types.PhotoSize or None: - if isinstance(thumb, (types.PhotoSize, types.PhotoCachedSize)): - loc = thumb.location - - if isinstance(thumb, types.PhotoSize): - file_size = thumb.size - else: - file_size = len(thumb.bytes) - - if isinstance(loc, types.FileLocation): - return pyrogram_types.PhotoSize( - file_id=encode( - pack( - " bytes: - s = b64decode(s + "=" * (-len(s) % 4), "-_") - r = b"" - - assert s[-1] == 2 - - i = 0 - while i < len(s) - 1: - if s[i] != 0: - r += bytes([s[i]]) - else: - r += b"\x00" * s[i + 1] - i += 1 - - i += 1 - - return r - - -def encode(s: bytes) -> str: - r = b"" - n = 0 - - for i in s + bytes([2]): - if i == 0: - n += 1 - else: - if n: - r += b"\x00" + bytes([n]) - n = 0 - - r += bytes([i]) - - return b64encode(r, b"-_").decode().rstrip("=") - - -# TODO: Reorganize code, maybe split parts as well -def parse_message( - client, - message: types.Message, - users: dict, - chats: dict, - replies: int = 1 -) -> pyrogram_types.Message: - entities = parse_entities(message.entities, users) - - forward_from = None - forward_from_chat = None - forward_from_message_id = None - forward_signature = None - forward_date = None - - forward_header = message.fwd_from # type: types.MessageFwdHeader - - if forward_header: - forward_date = forward_header.date - - if forward_header.from_id: - forward_from = parse_user(users[forward_header.from_id]) - else: - forward_from_chat = parse_channel_chat(chats[forward_header.channel_id]) - forward_from_message_id = forward_header.channel_post - forward_signature = forward_header.post_author - - photo = None - location = None - contact = None - venue = None - audio = None - voice = None - video = None - video_note = None - sticker = None - document = None - - media = message.media - - if media: - if isinstance(media, types.MessageMediaPhoto): - photo = media.photo - - if isinstance(photo, types.Photo): - sizes = photo.sizes - photo_sizes = [] - - for size in sizes: - if isinstance(size, (types.PhotoSize, types.PhotoCachedSize)): - loc = size.location - - if isinstance(size, types.PhotoSize): - file_size = size.size - else: - file_size = len(size.bytes) - - if isinstance(loc, types.FileLocation): - photo_size = pyrogram_types.PhotoSize( - file_id=encode( - pack( - " pyrogram_types.Message: - action = message.action - - new_chat_members = None - left_chat_member = None - new_chat_title = None - delete_chat_photo = None - migrate_to_chat_id = None - migrate_from_chat_id = None - group_chat_created = None - channel_chat_created = None - new_chat_photo = None - - if isinstance(action, types.MessageActionChatAddUser): - new_chat_members = [parse_user(users[i]) for i in action.users] - elif isinstance(action, types.MessageActionChatJoinedByLink): - new_chat_members = [parse_user(users[message.from_id])] - elif isinstance(action, types.MessageActionChatDeleteUser): - left_chat_member = parse_user(users[action.user_id]) - elif isinstance(action, types.MessageActionChatEditTitle): - new_chat_title = action.title - elif isinstance(action, types.MessageActionChatDeletePhoto): - delete_chat_photo = True - elif isinstance(action, types.MessageActionChatMigrateTo): - migrate_to_chat_id = action.channel_id - elif isinstance(action, types.MessageActionChannelMigrateFrom): - migrate_from_chat_id = action.chat_id - elif isinstance(action, types.MessageActionChatCreate): - group_chat_created = True - elif isinstance(action, types.MessageActionChannelCreate): - channel_chat_created = True - elif isinstance(action, types.MessageActionChatEditPhoto): - photo = action.photo - - if isinstance(photo, types.Photo): - sizes = photo.sizes - photo_sizes = [] - - for size in sizes: - if isinstance(size, (types.PhotoSize, types.PhotoCachedSize)): - loc = size.location - - if isinstance(size, types.PhotoSize): - file_size = size.size - else: - file_size = len(size.bytes) - - if isinstance(loc, types.FileLocation): - photo_size = pyrogram_types.PhotoSize( - file_id=encode( - pack( - " pyrogram_types.Message: - return pyrogram_types.Message(message_id=message.id, client=proxy(client)) - - -def get_peer_id(input_peer) -> int: - return ( - input_peer.user_id if isinstance(input_peer, types.InputPeerUser) - else -input_peer.chat_id if isinstance(input_peer, types.InputPeerChat) - else int("-100" + str(input_peer.channel_id)) - ) - - -def get_input_peer(peer_id: int, access_hash: int): - return ( - types.InputPeerUser(peer_id, access_hash) if peer_id > 0 - else types.InputPeerChannel(int(str(peer_id)[4:]), access_hash) - if (str(peer_id).startswith("-100") and access_hash) - else types.InputPeerChat(-peer_id) - ) - - -def get_offset_date(dialogs): - for m in reversed(dialogs.messages): - if isinstance(m, types.MessageEmpty): - continue - else: - return m.date - else: - return 0 - - -def parse_photos(photos): - if isinstance(photos, types.photos.Photos): - total_count = len(photos.photos) - else: - total_count = photos.count - - user_profile_photos = [] - - for photo in photos.photos: - if isinstance(photo, types.Photo): - sizes = photo.sizes - photo_sizes = [] - - for size in sizes: - if isinstance(size, (types.PhotoSize, types.PhotoCachedSize)): - loc = size.location - - if isinstance(size, types.PhotoSize): - file_size = size.size - else: - file_size = len(size.bytes) - - if isinstance(loc, types.FileLocation): - photo_size = pyrogram_types.PhotoSize( - file_id=encode( - pack( - " pyrogram_types.Chat: - if isinstance(chat_full, types.UserFull): - chat = parse_user_chat(chat_full.user) - chat.description = chat_full.about - else: - full_chat = chat_full.full_chat - chat = None - - for i in chat_full.chats: - if full_chat.id == i.id: - chat = i - - if isinstance(full_chat, types.ChatFull): - chat = parse_chat_chat(chat) - else: - chat = parse_channel_chat(chat) - chat.description = full_chat.about or None - # TODO: Add StickerSet type - chat.can_set_sticker_set = full_chat.can_set_stickers - chat.sticker_set_name = full_chat.stickerset - - if full_chat.pinned_msg_id: - chat.pinned_message = client.get_messages( - int("-100" + str(full_chat.id)), - full_chat.pinned_msg_id - ) - - if isinstance(full_chat.exported_invite, types.ChatInviteExported): - chat.invite_link = full_chat.exported_invite.link - - return chat