From 8454d438be922fad20ab4d15c4f45fda1dbef6b2 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 7 May 2018 14:30:55 +0200 Subject: [PATCH] Refactor the project by using Mixin classes This will cut client.py down from ~4k to ~1k SLOC and also makes the whole project tidier and more organized. --- examples/callback_query_handler.py | 18 + examples/echo_bot.py | 18 + examples/get_history.py | 18 + examples/get_participants.py | 18 + examples/get_participants2.py | 18 + examples/hello_world.py | 18 + examples/query_inline_bots.py | 18 + examples/raw_update_handler.py | 18 + examples/send_bot_keyboards.py | 18 + examples/welcome_bot.py | 18 + pyrogram/client/__init__.py | 4 +- pyrogram/client/client.py | 2566 +---------------- pyrogram/client/dispatcher/dispatcher.py | 2 +- pyrogram/client/ext/__init__.py | 23 + pyrogram/client/ext/base_client.py | 95 + pyrogram/client/{ => ext}/chat_action.py | 0 pyrogram/client/{ => ext}/emoji.py | 0 pyrogram/client/{ => ext}/parse_mode.py | 0 pyrogram/client/{ => ext}/syncer.py | 0 pyrogram/client/{ => ext}/utils.py | 4 +- pyrogram/client/methods/__init__.py | 37 + pyrogram/client/methods/bots/__init__.py | 27 + .../methods/bots/callback_query/__init__.py | 25 + .../callback_query/answer_callback_query.py | 62 + .../client/methods/bots/inline/__init__.py | 27 + .../bots/inline/get_inline_bot_results.py | 74 + .../bots/inline/send_inline_bot_results.py | 68 + pyrogram/client/methods/chats/__init__.py | 31 + .../methods/chats/export_chat_invite_link.py | 85 + pyrogram/client/methods/chats/get_chat.py | 35 + pyrogram/client/methods/chats/join_chat.py | 59 + pyrogram/client/methods/chats/leave_chat.py | 62 + pyrogram/client/methods/contacts/__init__.py | 29 + .../client/methods/contacts/add_contacts.py | 43 + .../methods/contacts/delete_contacts.py | 54 + .../client/methods/contacts/get_contacts.py | 42 + .../client/methods/decorators/__init__.py | 25 + .../methods/decorators/on_callback_query.py | 42 + .../client/methods/decorators/on_message.py | 42 + .../methods/decorators/on_raw_update.py | 38 + pyrogram/client/methods/messages/__init__.py | 33 + .../methods/messages/action/__init__.py | 25 + .../messages/action/send_chat_action.py | 70 + .../client/methods/messages/get_messages.py | 97 + .../client/methods/messages/media/__init__.py | 43 + .../methods/messages/media/send_audio.py | 182 ++ .../methods/messages/media/send_contact.py | 88 + .../methods/messages/media/send_document.py | 163 ++ .../methods/messages/media/send_location.py | 85 + .../messages/media/send_media_group.py | 170 ++ .../methods/messages/media/send_photo.py | 167 ++ .../methods/messages/media/send_sticker.py | 151 + .../methods/messages/media/send_venue.py | 102 + .../methods/messages/media/send_video.py | 193 ++ .../methods/messages/media/send_video_note.py | 161 ++ .../methods/messages/media/send_voice.py | 170 ++ .../client/methods/messages/text/__init__.py | 27 + .../methods/messages/text/forward_messages.py | 85 + .../methods/messages/text/send_message.py | 97 + .../methods/messages/update/__init__.py | 31 + .../messages/update/delete_messages.py | 77 + .../messages/update/edit_message_caption.py | 75 + .../update/edit_message_reply_markup.py | 64 + .../messages/update/edit_message_text.py | 80 + pyrogram/client/methods/password/__init__.py | 29 + .../methods/password/change_cloud_password.py | 65 + .../methods/password/enable_cloud_password.py | 66 + .../methods/password/remove_cloud_password.py | 55 + pyrogram/client/methods/users/__init__.py | 29 + pyrogram/client/methods/users/get_me.py | 37 + .../methods/users/get_user_profile_photos.py | 57 + pyrogram/client/methods/users/get_users.py | 57 + .../reply_markup/reply_keyboard_remove.py | 1 - 73 files changed, 4086 insertions(+), 2527 deletions(-) create mode 100644 pyrogram/client/ext/__init__.py create mode 100644 pyrogram/client/ext/base_client.py rename pyrogram/client/{ => ext}/chat_action.py (100%) rename pyrogram/client/{ => ext}/emoji.py (100%) rename pyrogram/client/{ => ext}/parse_mode.py (100%) rename pyrogram/client/{ => ext}/syncer.py (100%) rename pyrogram/client/{ => ext}/utils.py (99%) create mode 100644 pyrogram/client/methods/__init__.py create mode 100644 pyrogram/client/methods/bots/__init__.py create mode 100644 pyrogram/client/methods/bots/callback_query/__init__.py create mode 100644 pyrogram/client/methods/bots/callback_query/answer_callback_query.py create mode 100644 pyrogram/client/methods/bots/inline/__init__.py create mode 100644 pyrogram/client/methods/bots/inline/get_inline_bot_results.py create mode 100644 pyrogram/client/methods/bots/inline/send_inline_bot_results.py create mode 100644 pyrogram/client/methods/chats/__init__.py create mode 100644 pyrogram/client/methods/chats/export_chat_invite_link.py create mode 100644 pyrogram/client/methods/chats/get_chat.py create mode 100644 pyrogram/client/methods/chats/join_chat.py create mode 100644 pyrogram/client/methods/chats/leave_chat.py create mode 100644 pyrogram/client/methods/contacts/__init__.py create mode 100644 pyrogram/client/methods/contacts/add_contacts.py create mode 100644 pyrogram/client/methods/contacts/delete_contacts.py create mode 100644 pyrogram/client/methods/contacts/get_contacts.py create mode 100644 pyrogram/client/methods/decorators/__init__.py create mode 100644 pyrogram/client/methods/decorators/on_callback_query.py create mode 100644 pyrogram/client/methods/decorators/on_message.py create mode 100644 pyrogram/client/methods/decorators/on_raw_update.py create mode 100644 pyrogram/client/methods/messages/__init__.py create mode 100644 pyrogram/client/methods/messages/action/__init__.py create mode 100644 pyrogram/client/methods/messages/action/send_chat_action.py create mode 100644 pyrogram/client/methods/messages/get_messages.py create mode 100644 pyrogram/client/methods/messages/media/__init__.py create mode 100644 pyrogram/client/methods/messages/media/send_audio.py create mode 100644 pyrogram/client/methods/messages/media/send_contact.py create mode 100644 pyrogram/client/methods/messages/media/send_document.py create mode 100644 pyrogram/client/methods/messages/media/send_location.py create mode 100644 pyrogram/client/methods/messages/media/send_media_group.py create mode 100644 pyrogram/client/methods/messages/media/send_photo.py create mode 100644 pyrogram/client/methods/messages/media/send_sticker.py create mode 100644 pyrogram/client/methods/messages/media/send_venue.py create mode 100644 pyrogram/client/methods/messages/media/send_video.py create mode 100644 pyrogram/client/methods/messages/media/send_video_note.py create mode 100644 pyrogram/client/methods/messages/media/send_voice.py create mode 100644 pyrogram/client/methods/messages/text/__init__.py create mode 100644 pyrogram/client/methods/messages/text/forward_messages.py create mode 100644 pyrogram/client/methods/messages/text/send_message.py create mode 100644 pyrogram/client/methods/messages/update/__init__.py create mode 100644 pyrogram/client/methods/messages/update/delete_messages.py create mode 100644 pyrogram/client/methods/messages/update/edit_message_caption.py create mode 100644 pyrogram/client/methods/messages/update/edit_message_reply_markup.py create mode 100644 pyrogram/client/methods/messages/update/edit_message_text.py create mode 100644 pyrogram/client/methods/password/__init__.py create mode 100644 pyrogram/client/methods/password/change_cloud_password.py create mode 100644 pyrogram/client/methods/password/enable_cloud_password.py create mode 100644 pyrogram/client/methods/password/remove_cloud_password.py create mode 100644 pyrogram/client/methods/users/__init__.py create mode 100644 pyrogram/client/methods/users/get_me.py create mode 100644 pyrogram/client/methods/users/get_user_profile_photos.py create mode 100644 pyrogram/client/methods/users/get_users.py 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 0ff6c28c..72259d8f 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,257 +1259,6 @@ 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 = "", @@ -3504,329 +1356,3 @@ class Client: 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) diff --git a/pyrogram/client/dispatcher/dispatcher.py b/pyrogram/client/dispatcher/dispatcher.py index a9439d6e..ce8a23c1 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__) 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..1cb7ec30 --- /dev/null +++ b/pyrogram/client/ext/base_client.py @@ -0,0 +1,95 @@ +# 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 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/utils.py b/pyrogram/client/ext/utils.py similarity index 99% rename from pyrogram/client/utils.py rename to pyrogram/client/ext/utils.py index e117316b..09513fcb 100644 --- a/pyrogram/client/utils.py +++ b/pyrogram/client/ext/utils.py @@ -21,8 +21,8 @@ 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 +from ...api import types, functions +from ...api.errors import StickersetInvalid # TODO: Organize the code better? diff --git a/pyrogram/client/methods/__init__.py b/pyrogram/client/methods/__init__.py new file mode 100644 index 00000000..ea249e3f --- /dev/null +++ b/pyrogram/client/methods/__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 .bots import Bots +from .chats import Chats +from .contacts import Contacts +from .decorators import Decorators +from .messages import Messages +from .password import Password +from .users import Users + + +class Methods( + Bots, + Contacts, + Password, + Chats, + Users, + 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..6e9ccc9b --- /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_results import SendInlineBotResults + + +class Inline( + SendInlineBotResults, + 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..68e2a8cc --- /dev/null +++ b/pyrogram/client/methods/bots/inline/get_inline_bot_results.py @@ -0,0 +1,74 @@ +# 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 = "", + 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 diff --git a/pyrogram/client/methods/bots/inline/send_inline_bot_results.py b/pyrogram/client/methods/bots/inline/send_inline_bot_results.py new file mode 100644 index 00000000..9ae0c1b4 --- /dev/null +++ b/pyrogram/client/methods/bots/inline/send_inline_bot_results.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 SendInlineBotResults(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..d69edd6b --- /dev/null +++ b/pyrogram/client/methods/chats/export_chat_invite_link.py @@ -0,0 +1,85 @@ +# 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 ChatAdminRequired +from ...ext import BaseClient + + +class ExportChatInviteLink(BaseClient): + 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 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/messages/__init__.py b/pyrogram/client/methods/messages/__init__.py new file mode 100644 index 00000000..a1d3506c --- /dev/null +++ b/pyrogram/client/methods/messages/__init__.py @@ -0,0 +1,33 @@ +# 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 .get_messages import GetMessages +from .media import Media +from .text import Text +from .update import Update + + +class Messages( + GetMessages, + Action, + Media, + Update, + Text +): + 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..71e1d9f0 --- /dev/null +++ b/pyrogram/client/methods/messages/action/send_chat_action.py @@ -0,0 +1,70 @@ +# 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 (``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 + ) + ) diff --git a/pyrogram/client/methods/messages/get_messages.py b/pyrogram/client/methods/messages/get_messages.py new file mode 100644 index 00000000..3fcb0daa --- /dev/null +++ b/pyrogram/client/methods/messages/get_messages.py @@ -0,0 +1,97 @@ +# 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 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] diff --git a/pyrogram/client/methods/messages/media/__init__.py b/pyrogram/client/methods/messages/media/__init__.py new file mode 100644 index 00000000..d24383e8 --- /dev/null +++ b/pyrogram/client/methods/messages/media/__init__.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 .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_venue import SendVenue +from .send_video import SendVideo +from .send_video_note import SendVideoNote +from .send_voice import SendVoice + + +class Media( + SendContact, + SendVenue, + 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..655b0d8c --- /dev/null +++ b/pyrogram/client/methods/messages/media/send_audio.py @@ -0,0 +1,182 @@ +# 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``): + 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``): + 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 " +# +# 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``): + 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) 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..09d1c5a9 --- /dev/null +++ b/pyrogram/client/methods/messages/media/send_document.py @@ -0,0 +1,163 @@ +# 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``): + 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``): + 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 " +# +# 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)): + 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) 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``): + 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 " +# +# 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``): + 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 + + 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)): + 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) 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..1998149e --- /dev/null +++ b/pyrogram/client/methods/messages/media/send_video.py @@ -0,0 +1,193 @@ +# 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``): + 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``): + 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 " +# +# 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``): + 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 + + 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``): + 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``): + 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 " +# +# 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 .forward_messages import ForwardMessages +from .send_message import SendMessage + + +class Text( + ForwardMessages, + SendMessage +): + pass diff --git a/pyrogram/client/methods/messages/text/forward_messages.py b/pyrogram/client/methods/messages/text/forward_messages.py new file mode 100644 index 00000000..4cd22f9d --- /dev/null +++ b/pyrogram/client/methods/messages/text/forward_messages.py @@ -0,0 +1,85 @@ +# 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_message(self, i.message, users, chats) + ) + + return messages if is_iterable else messages[0] diff --git a/pyrogram/client/methods/messages/text/send_message.py b/pyrogram/client/methods/messages/text/send_message.py new file mode 100644 index 00000000..3016b432 --- /dev/null +++ b/pyrogram/client/methods/messages/text/send_message.py @@ -0,0 +1,97 @@ +# 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.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``): + 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) 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..7833ed91 --- /dev/null +++ b/pyrogram/client/methods/messages/update/edit_message_caption.py @@ -0,0 +1,75 @@ +# 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``): + 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) 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..7e16c284 --- /dev/null +++ b/pyrogram/client/methods/messages/update/edit_message_reply_markup.py @@ -0,0 +1,64 @@ +# 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`): + 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) 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..3ece21af --- /dev/null +++ b/pyrogram/client/methods/messages/update/edit_message_text.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 ....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``): + 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) 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..b0f36f85 --- /dev/null +++ b/pyrogram/client/methods/users/get_me.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 pyrogram.api import functions, types +from ...ext import BaseClient + + +class GetMe(BaseClient): + 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() + ) + ) 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..aa16749d --- /dev/null +++ b/pyrogram/client/methods/users/get_user_profile_photos.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 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 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 + ) + ) + ) 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