diff --git a/.gitignore b/.gitignore index 0b1a0699..ce3407dd 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,9 @@ pyrogram/api/all.py # PyCharm stuff .idea/ +# VS Code +.vscode/ + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] @@ -78,6 +81,7 @@ instance/ # Sphinx documentation docs/_build/ +docs/source/_build # PyBuilder target/ diff --git a/compiler/api/compiler.py b/compiler/api/compiler.py index 3995fd5f..255884db 100644 --- a/compiler/api/compiler.py +++ b/compiler/api/compiler.py @@ -478,7 +478,6 @@ def start(): f.write("\n 0xbc799737: \"pyrogram.api.core.BoolFalse\",") f.write("\n 0x997275b5: \"pyrogram.api.core.BoolTrue\",") - f.write("\n 0x56730bcc: \"pyrogram.api.core.Null\",") f.write("\n 0x1cb5c415: \"pyrogram.api.core.Vector\",") f.write("\n 0x73f1f8dc: \"pyrogram.api.core.MsgContainer\",") f.write("\n 0xae500895: \"pyrogram.api.core.FutureSalts\",") diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index e9d099d1..fa2c7af8 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -1364,7 +1364,4 @@ langpack.getLanguage#6a596502 lang_pack:string lang_code:string = LangPackLangua folders.editPeerFolders#6847d0ab folder_peers:Vector = Updates; folders.deleteFolder#1c295881 folder_id:int = Updates; -// LAYER 103 - -// Ports -channels.exportInvite#c7560885 channel:InputChannel = ExportedChatInvite; \ No newline at end of file +// LAYER 103 \ No newline at end of file diff --git a/compiler/api/source/sys_msgs.tl b/compiler/api/source/sys_msgs.tl index 067ab91e..6a3f6325 100644 --- a/compiler/api/source/sys_msgs.tl +++ b/compiler/api/source/sys_msgs.tl @@ -53,6 +53,15 @@ ipPortSecret#37982646 ipv4:int port:int secret:bytes = IpPort; accessPointRule#4679b65f phone_prefix_rules:string dc_id:int ips:vector = AccessPointRule; help.configSimple#5a592a6c date:int expires:int rules:vector = help.ConfigSimple; +// tlsClientHello blocks:vector = TlsClientHello; +// +// tlsBlockString data:string = TlsBlock; +// tlsBlockRandom length:int = TlsBlock; +// tlsBlockZero length:int = TlsBlock; +// tlsBlockDomain = TlsBlock; +// tlsBlockGrease seed:int = TlsBlock; +// tlsBlockScope entries:Vector = TlsBlock; + ---functions--- rpc_drop_answer#58e4a740 req_msg_id:long = RpcDropAnswer; diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index d678b370..e515dc77 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -136,6 +136,7 @@ def pyrogram_api(): remove_handler stop_transmission export_session_string + set_parse_mode """, messages=""" Messages @@ -145,7 +146,6 @@ def pyrogram_api(): send_audio send_document send_sticker - send_animated_sticker send_video send_animation send_voice @@ -189,6 +189,7 @@ def pyrogram_api(): delete_chat_photo set_chat_title set_chat_description + set_chat_permissions pin_chat_message unpin_chat_message get_chat @@ -199,10 +200,15 @@ def pyrogram_api(): get_dialogs iter_dialogs get_dialogs_count - restrict_chat update_chat_username archive_chats unarchive_chats + add_chat_members + create_channel + create_group + create_supergroup + delete_channel + delete_supergroup """, users=""" Users @@ -334,6 +340,8 @@ def pyrogram_api(): InlineQuery InlineQueryResult InlineQueryResultArticle + InlineQueryResultPhoto + InlineQueryResultAnimation """, input_message_content=""" InputMessageContent @@ -412,11 +420,15 @@ def pyrogram_api(): Chat.unban_member Chat.restrict_member Chat.promote_member + Chat.join + Chat.leave """, user=""" User User.archive User.unarchive + User.block + User.unblock """, callback_query=""" Callback Query diff --git a/compiler/error/source/400_BAD_REQUEST.tsv b/compiler/error/source/400_BAD_REQUEST.tsv index d7942e4b..d9a85cf0 100644 --- a/compiler/error/source/400_BAD_REQUEST.tsv +++ b/compiler/error/source/400_BAD_REQUEST.tsv @@ -113,4 +113,13 @@ FILE_PART_TOO_BIG The size limit (512 KB) for the content of the file part has b FILE_PART_EMPTY The file part sent is empty FILE_PART_SIZE_INVALID 512 KB cannot be evenly divided by part_size FILE_PART_SIZE_CHANGED The part size is different from the size of one of the previous parts in the same file -FILE_MIGRATE_X The file is in Data Center No. {x} \ No newline at end of file +FILE_MIGRATE_X The file is in Data Center No. {x} +RESULT_TYPE_INVALID The result type is invalid +PHOTO_THUMB_URL_EMPTY The photo thumb URL is empty +PHOTO_THUMB_URL_INVALID The photo thumb URL is invalid +PHOTO_CONTENT_URL_EMPTY The photo content URL is empty +PHOTO_CONTENT_TYPE_INVALID The photo content type is invalid +WEBDOCUMENT_INVALID The web document is invalid +WEBDOCUMENT_URL_EMPTY The web document URL is empty +WEBDOCUMENT_URL_INVALID The web document URL is invalid +WEBDOCUMENT_MIME_INVALID The web document mime type is invalid \ No newline at end of file diff --git a/docs/source/faq.rst b/docs/source/faq.rst index 147eb4fa..a05ff39c 100644 --- a/docs/source/faq.rst +++ b/docs/source/faq.rst @@ -144,7 +144,8 @@ I started a client and nothing happens! --------------------------------------- If you are connecting from Russia, China or Iran :doc:`you need a proxy `, because Telegram could be -partially or totally blocked in those countries. +partially or totally blocked in those countries. More information about this block can be found at +`Wikipedia `_. Another possible cause might be network issues, either yours or Telegram's. To confirm this, add the following code on the top of your script and run it again. You should see some error mentioning a socket timeout or an unreachable network @@ -161,9 +162,9 @@ fails or not. What are the IP addresses of Telegram Data Centers? --------------------------------------------------- -The Telegram cloud is currently composed by a decentralized, multi-DC infrastructure (each of which can work -independently) spread in 5 different locations. However, some of the less busy DCs have been lately dismissed and their -IP addresses are now kept as aliases. +The Telegram cloud is currently composed by a decentralized, multi-DC infrastructure (currently 5 DCs, each of which can +work independently) spread in different locations worldwide. However, some of the less busy DCs have been lately +dismissed and their IP addresses are now kept as aliases to the nearest one. .. csv-table:: Production Environment :header: ID, Location, IPv4, IPv6 @@ -191,7 +192,6 @@ IP addresses are now kept as aliases. Thanks to `@FrayxRulez `_ for telling about alias DCs. - I want to migrate my account from DCX to DCY. --------------------------------------------- @@ -245,9 +245,13 @@ The error in question is ``[400 PEER_ID_INVALID]``, and could mean several thing - The chat id you tried to use is simply wrong, double check it. - The chat id refers to a group or channel you are not a member of. -- The chat id refers to a user you have't seen yet (from contacts, groups in common, forwarded messages or private - chats). - The chat id argument you passed is in form of a string; you have to convert it into an integer with ``int(chat_id)``. +- The chat id refers to a user your current session haven't met yet. + +About the last point: in order for you to meet a user and thus communicate with them, you should ask yourself how to +contact people using official apps. The answer is the same for Pyrogram too and involves normal usages such as searching +for usernames, meet them in a common group, have their phone contacts saved, getting a message mentioning them (either a +forward or a mention in the message text). UnicodeEncodeError: '' codec can't encode … ----------------------------------------------------- @@ -257,6 +261,14 @@ shows up when you try to print something and has very little to do with Pyrogram your own terminal. To fix it, either find a way to change the encoding settings of your terminal to UTF-8 or switch to a better terminal altogether. +Uploading with URLs gives error WEBPAGE_CURL_FAILED +--------------------------------------------------- + +When uploading media files using an URL, the server automatically tries to download the media and uploads it to the +Telegram cloud. This error usually happens in case the provided URL is not publicly accessible by Telegram itself or the +media exceeds 20 MB in size. In such cases, your only option is to download the media yourself and upload from your +local machine. + My verification code expires immediately! ----------------------------------------- diff --git a/docs/source/index.rst b/docs/source/index.rst index 5cb9bb2e..f6961bc6 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -45,8 +45,9 @@ Welcome to Pyrogram topics/tgcrypto topics/storage-engines topics/text-formatting - topics/serialize + topics/serializing topics/proxy + topics/scheduling topics/bots-interaction topics/mtproto-vs-botapi topics/debugging diff --git a/docs/source/topics/scheduling.rst b/docs/source/topics/scheduling.rst new file mode 100644 index 00000000..3cb95ec7 --- /dev/null +++ b/docs/source/topics/scheduling.rst @@ -0,0 +1,87 @@ +Scheduling Tasks +================ + +Scheduling tasks means executing one or more functions periodically at pre-defined intervals or after a delay. This is +useful, for example, to send recurring messages to specific chats or users. + +Since there's no built-in task scheduler in Pyrogram, this page will only show examples on how to integrate Pyrogram +with the main Python schedule libraries such as ``schedule`` and ``apscheduler``. For more detailed information, you can +visit and learn from each library documentation. + +Using ``schedule`` +------------------ + +- Install with ``pip3 install schedule`` +- Documentation: https://schedule.readthedocs.io + +.. code-block:: python + + import time + + import schedule + + from pyrogram import Client + + app = Client("my_account") + + + def job(): + app.send_message("me", "Hi!") + + + schedule.every(3).seconds.do(job) + + with app: + while True: + schedule.run_pending() + time.sleep(1) + + + +Using ``apscheduler`` +--------------------- + +- Install with ``pip3 install apscheduler`` +- Documentation: https://apscheduler.readthedocs.io + +.. code-block:: python + + from apscheduler.schedulers.background import BackgroundScheduler + + from pyrogram import Client + + app = Client("my_account") + + + def job(): + app.send_message("me", "Hi!") + + + scheduler = BackgroundScheduler() + scheduler.add_job(job, "interval", seconds=3) + + scheduler.start() + app.run() + +``apscheduler`` does also support async code, here's an example with +`Pyrogram Asyncio `_: + +.. code-block:: python + + from apscheduler.schedulers.asyncio import AsyncIOScheduler + + from pyrogram import Client + + app = Client("my_account") + + + async def job(): + await app.send_message("me", "Hi!") + + + scheduler = AsyncIOScheduler() + scheduler.add_job(job, "interval", seconds=3) + + scheduler.start() + app.run() + diff --git a/docs/source/topics/serialize.rst b/docs/source/topics/serializing.rst similarity index 100% rename from docs/source/topics/serialize.rst rename to docs/source/topics/serializing.rst diff --git a/docs/source/topics/smart-plugins.rst b/docs/source/topics/smart-plugins.rst index 8e59b971..5131f27b 100644 --- a/docs/source/topics/smart-plugins.rst +++ b/docs/source/topics/smart-plugins.rst @@ -316,9 +316,9 @@ attribute. Here's an example: Unloading ^^^^^^^^^ -In order to unload a plugin, or any other handler, all you need to do is obtain a reference to it by importing the -relevant module and call :meth:`~pyrogram.Client.remove_handler` Client's method with your function -name preceded by the star ``*`` operator as argument. Example: +In order to unload a plugin, all you need to do is obtain a reference to it by importing the relevant module and call +:meth:`~pyrogram.Client.remove_handler` Client's method with your function's *handler* special attribute preceded by the +star ``*`` operator as argument. Example: - ``main.py`` @@ -328,14 +328,14 @@ name preceded by the star ``*`` operator as argument. Example: ... - app.remove_handler(*echo) + app.remove_handler(*echo.handler) The star ``*`` operator is used to unpack the tuple into positional arguments so that *remove_handler* will receive exactly what is needed. The same could have been achieved with: .. code-block:: python - handler, group = echo + handler, group = echo.handler app.remove_handler(handler, group) Loading @@ -352,4 +352,4 @@ using :meth:`~pyrogram.Client.add_handler` instead. Example: ... - app.add_handler(*echo) \ No newline at end of file + app.add_handler(*echo.handler) \ No newline at end of file diff --git a/pyrogram/api/__init__.py b/pyrogram/api/__init__.py index 8d7831ff..78f1a579 100644 --- a/pyrogram/api/__init__.py +++ b/pyrogram/api/__init__.py @@ -19,8 +19,7 @@ from importlib import import_module from .all import objects -from .core.tl_object import TLObject for k, v in objects.items(): path, name = v.rsplit(".", 1) - TLObject.all[k] = getattr(import_module(path), name) + objects[k] = getattr(import_module(path), name) diff --git a/pyrogram/api/core/__init__.py b/pyrogram/api/core/__init__.py index aaf5a324..ff4fc9c5 100644 --- a/pyrogram/api/core/__init__.py +++ b/pyrogram/api/core/__init__.py @@ -22,8 +22,5 @@ from .gzip_packed import GzipPacked from .list import List from .message import Message from .msg_container import MsgContainer -from .primitives import ( - Bool, BoolTrue, BoolFalse, Bytes, Double, - Int, Long, Int128, Int256, Null, String, Vector -) +from .primitives import * from .tl_object import TLObject diff --git a/pyrogram/api/core/primitives/__init__.py b/pyrogram/api/core/primitives/__init__.py index 8885878b..f86e3cab 100644 --- a/pyrogram/api/core/primitives/__init__.py +++ b/pyrogram/api/core/primitives/__init__.py @@ -16,10 +16,11 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from .bool import Bool, BoolTrue, BoolFalse +from .bool import Bool, BoolFalse, BoolTrue from .bytes import Bytes from .double import Double from .int import Int, Long, Int128, Int256 -from .null import Null from .string import String from .vector import Vector + +__all__ = ["Bool", "BoolFalse", "BoolTrue", "Bytes", "Double", "Int", "Long", "Int128", "Int256", "String", "Vector"] diff --git a/pyrogram/api/core/tl_object.py b/pyrogram/api/core/tl_object.py index 4b951404..d39a8ae2 100644 --- a/pyrogram/api/core/tl_object.py +++ b/pyrogram/api/core/tl_object.py @@ -20,17 +20,17 @@ from collections import OrderedDict from io import BytesIO from json import dumps +from ..all import objects + class TLObject: - all = {} - __slots__ = [] QUALNAME = "Base" @staticmethod def read(b: BytesIO, *args): # TODO: Rename b -> data - return TLObject.all[int.from_bytes(b.read(4), "little")].read(b, *args) + return objects[int.from_bytes(b.read(4), "little")].read(b, *args) def write(self, *args) -> bytes: pass diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index de21cce3..8c29e3a2 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -174,6 +174,17 @@ class Client(Methods, BaseClient): download_media, ...) are less prone to throw FloodWait exceptions. Only available for users, bots will ignore this parameter. Defaults to False (normal session). + + Example: + .. code-block:: python + + from pyrogram import Client + + app = Client("my_account") + + with app: + app.send_message("me", "Hi!") + """ terms_of_service_displayed = False @@ -619,11 +630,28 @@ class Client(Methods, BaseClient): return self def start(self): - """Start the Client. + """Start the client. + + This method connects the client to Telegram and, in case of new sessions, automatically manages the full login + process using an interactive prompt (by default). + + Has no parameters. Raises: - RPCError: In case of a Telegram RPC error. - ConnectionError: In case you try to start an already started Client. + ConnectionError: In case you try to start an already started client. + + Example: + .. code-block:: python + :emphasize-lines: 4 + + from pyrogram import Client + + app = Client("my_account") + app.start() + + ... # Call API methods + + app.stop() """ if self.is_started: raise ConnectionError("Client has already been started") @@ -696,8 +724,25 @@ class Client(Methods, BaseClient): def stop(self): """Stop the Client. + This method disconnects the client from Telegram and stops the underlying tasks. + + Has no parameters. + Raises: - ConnectionError: In case you try to stop an already stopped Client. + ConnectionError: In case you try to stop an already stopped client. + + Example: + .. code-block:: python + :emphasize-lines: 8 + + from pyrogram import Client + + app = Client("my_account") + app.start() + + ... # Call API methods + + app.stop() """ if not self.is_started: raise ConnectionError("Client is already stopped") @@ -738,64 +783,125 @@ class Client(Methods, BaseClient): def restart(self): """Restart the Client. + This method will first call :meth:`~Client.stop` and then :meth:`~Client.start` in a row in order to restart + a client using a single method. + + Has no parameters. + Raises: ConnectionError: In case you try to restart a stopped Client. + + Example: + .. code-block:: python + :emphasize-lines: 8 + + from pyrogram import Client + + app = Client("my_account") + app.start() + + ... # Call API methods + + app.restart() + + ... # Call other API methods + + app.stop() """ self.stop() self.start() - def idle(self, stop_signals: tuple = (SIGINT, SIGTERM, SIGABRT)): - """Block the main script execution until a signal (e.g.: from CTRL+C) is received. - Once the signal is received, the client will automatically stop and the main script will continue its execution. + @staticmethod + def idle(stop_signals: tuple = (SIGINT, SIGTERM, SIGABRT)): + """Block the main script execution until a signal is received. - This is used after starting one or more clients and is useful for event-driven applications only, that are, - applications which react upon incoming Telegram updates through handlers, rather than executing a set of methods - sequentially. + This static method will run an infinite loop in order to block the main script execution and prevent it from + exiting while having client(s) that are still running in the background. - The way Pyrogram works, will keep your handlers in a pool of workers, which are executed concurrently outside - the main script; calling idle() will ensure the client(s) will be kept alive by not letting the main script to - end, until you decide to quit. + It is useful for event-driven application only, that are, applications which react upon incoming Telegram + updates through handlers, rather than executing a set of methods sequentially. + + The way Pyrogram works, it will keep your handlers in a pool of worker threads, which are executed concurrently + outside the main thread; calling idle() will ensure the client(s) will be kept alive by not letting the main + script to end, until you decide to quit. + + Once a signal is received (e.g.: from CTRL+C) the inner infinite loop will break and your main script will + continue. Don't forget to call :meth:`~Client.stop` for each running client before the script ends. Parameters: stop_signals (``tuple``, *optional*): Iterable containing signals the signal handler will listen to. - Defaults to (SIGINT, SIGTERM, SIGABRT). + Defaults to *(SIGINT, SIGTERM, SIGABRT)*. + + Example: + .. code-block:: python + :emphasize-lines: 13 + + from pyrogram import Client + + app1 = Client("account1") + app2 = Client("account2") + app3 = Client("account3") + + ... # Set handlers up + + app1.start() + app2.start() + app3.start() + + Client.idle() + + app1.stop() + app2.stop() + app3.stop() """ - # TODO: Maybe make this method static and don't automatically stop - def signal_handler(*args): - self.is_idle = False + Client.is_idling = False for s in stop_signals: signal(s, signal_handler) - self.is_idle = True + Client.is_idling = True - while self.is_idle: + while Client.is_idling: time.sleep(1) - self.stop() - def run(self): - """Start the Client and automatically idle the main script. + """Start the client, idle the main script and finally stop the client. - This is a convenience method that literally just calls :meth:`~Client.start` and :meth:`~Client.idle`. It makes - running a client less verbose, but is not suitable in case you want to run more than one client in a single main - script, since :meth:`~Client.idle` will block. + This is a convenience method that calls :meth:`~Client.start`, :meth:`~Client.idle` and :meth:`~Client.stop` in + sequence. It makes running a client less verbose, but is not suitable in case you want to run more than one + client in a single main script, since idle() will block after starting the own client. + + Has no parameters. Raises: - RPCError: In case of a Telegram RPC error. + ConnectionError: In case you try to run an already started client. + + Example: + .. code-block:: python + :emphasize-lines: 7 + + from pyrogram import Client + + app = Client("my_account") + + ... # Set handlers up + + app.run() """ self.start() - self.idle() + Client.idle() + self.stop() def add_handler(self, handler: Handler, group: int = 0): """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). + 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). This mechanism is explained in greater details at + :doc:`More on Updates <../../topics/more-on-updates>`. Parameters: handler (``Handler``): @@ -805,7 +911,22 @@ class Client(Methods, BaseClient): The group identifier, defaults to 0. Returns: - ``tuple``: A tuple consisting of (handler, group). + ``tuple``: A tuple consisting of *(handler, group)*. + + Example: + .. code-block:: python + :emphasize-lines: 8 + + from pyrogram import Client, MessageHandler + + def dump(client, message): + print(message) + + app = Client("my_account") + + app.add_handler(MessageHandler(dump)) + + app.run() """ if isinstance(handler, DisconnectHandler): self.disconnect_handler = handler.callback @@ -817,9 +938,8 @@ class Client(Methods, BaseClient): def remove_handler(self, handler: Handler, group: int = 0): """Remove a previously-registered update handler. - Make sure to provide the right group that the handler was added in. You can use - the return value of the :meth:`~Client.add_handler` method, a tuple of (handler, group), and - pass it directly. + Make sure to provide the right group where the handler was added in. You can use the return value of the + :meth:`~Client.add_handler` method, a tuple of *(handler, group)*, and pass it directly. Parameters: handler (``Handler``): @@ -827,6 +947,24 @@ class Client(Methods, BaseClient): group (``int``, *optional*): The group identifier, defaults to 0. + + Example: + .. code-block:: python + :emphasize-lines: 11 + + from pyrogram import Client, MessageHandler + + def dump(client, message): + print(message) + + app = Client("my_account") + + handler = app.add_handler(MessageHandler(dump)) + + # Starred expression to unpack (handler, group) + app.remove_handler(*handler) + + app.run() """ if isinstance(handler, DisconnectHandler): self.disconnect_handler = None @@ -835,10 +973,109 @@ class Client(Methods, BaseClient): def stop_transmission(self): """Stop downloading or uploading a file. - Must be called inside a progress callback function. + + This method must be called inside a progress callback function in order to stop the transmission at the + desired time. The progress callback is called every time a file chunk is uploaded/downloaded. + + Has no parameters. + + Example: + .. code-block:: python + :emphasize-lines: 9 + + from pyrogram import Client + + app = Client("my_account") + + # Example to stop transmission once the upload progress reaches 50% + # Useless in practice, but shows how to stop on command + def progress(client, current, total): + if (current * 100 / total) > 50: + client.stop_transmission() + + with app: + app.send_document("me", "files.zip", progress=progress) """ raise Client.StopTransmission + def export_session_string(self): + """Export the current authorized session as a serialized string. + + Session strings are useful for storing in-memory authorized sessions in a portable, serialized string. + More detailed information about session strings can be found at the dedicated page of + :doc:`Storage Engines <../../topics/storage-engines>`. + + Has no parameters. + + Returns: + ``str``: The session serialized into a printable, url-safe string. + + Example: + .. code-block:: python + :emphasize-lines: 6 + + from pyrogram import Client + + app = Client("my_account") + + with app: + print(app.export_session_string()) + """ + return self.storage.export_session_string() + + def set_parse_mode(self, parse_mode: Union[str, None] = "combined"): + """Set the parse mode to be used globally by the client. + + When setting the parse mode with this method, all other methods having a *parse_mode* parameter will follow the + global value by default. The default value *"combined"* enables both Markdown and HTML styles to be used and + combined together. + + Parameters: + parse_mode (``str``): + The new parse mode, can be any of: *"combined"*, for the default combined mode. *"markdown"* or *"md"* + to force Markdown-only styles. *"html"* to force HTML-only styles. *None* to disable the parser + completely. + + Raises: + ValueError: In case the provided *parse_mode* is not a valid parse mode. + + Example: + .. code-block:: python + :emphasize-lines: 10,14,18,22 + + from pyrogram import Client + + app = Client("my_account") + + with app: + # Default combined mode: Markdown + HTML + app.send_message("haskell", "1. **markdown** and html") + + # Force Markdown-only, HTML is disabled + app.set_parse_mode("markdown") + app.send_message("haskell", "2. **markdown** and html") + + # Force HTML-only, Markdown is disabled + app.set_parse_mode("html") + app.send_message("haskell", "3. **markdown** and html") + + # Disable the parser completely + app.set_parse_mode(None) + app.send_message("haskell", "4. **markdown** and html") + + # Bring back the default combined mode + app.set_parse_mode() + app.send_message("haskell", "5. **markdown** and html") + """ + + if parse_mode not in self.PARSE_MODES: + raise ValueError('parse_mode must be one of {} or None. Not "{}"'.format( + ", ".join('"{}"'.format(m) for m in self.PARSE_MODES[:-1]), + parse_mode + )) + + self.parse_mode = parse_mode + def authorize_bot(self): try: r = self.send( @@ -1128,7 +1365,7 @@ class Client(Methods, BaseClient): access_hash = 0 peer_type = "group" elif isinstance(peer, (types.Channel, types.ChannelForbidden)): - peer_id = int("-100" + str(peer.id)) + peer_id = utils.get_channel_id(peer.id) access_hash = peer.access_hash username = getattr(peer, "username", None) @@ -1244,7 +1481,7 @@ class Client(Methods, BaseClient): try: diff = self.send( functions.updates.GetChannelDifference( - channel=self.resolve_peer(int("-100" + str(channel_id))), + channel=self.resolve_peer(utils.get_channel_id(channel_id)), filter=types.ChannelMessagesFilter( ranges=[types.MessageRange( min_id=update.message.id, @@ -1420,7 +1657,7 @@ class Client(Methods, BaseClient): ]) if session_empty: - self.storage.dc_id = 1 + self.storage.dc_id = 4 self.storage.date = 0 self.storage.test_mode = self.test_mode @@ -1456,7 +1693,7 @@ class Client(Methods, BaseClient): for name in vars(module).keys(): # noinspection PyBroadException try: - handler, group = getattr(module, name).pyrogram_plugin + handler, group = getattr(module, name).handler if isinstance(handler, Handler) and isinstance(group, int): self.add_handler(handler, group) @@ -1491,7 +1728,7 @@ class Client(Methods, BaseClient): for name in handlers: # noinspection PyBroadException try: - handler, group = getattr(module, name).pyrogram_plugin + handler, group = getattr(module, name).handler if isinstance(handler, Handler) and isinstance(group, int): self.add_handler(handler, group) @@ -1529,7 +1766,7 @@ class Client(Methods, BaseClient): for name in handlers: # noinspection PyBroadException try: - handler, group = getattr(module, name).pyrogram_plugin + handler, group = getattr(module, name).handler if isinstance(handler, Handler) and isinstance(group, int): self.remove_handler(handler, group) @@ -1632,33 +1869,38 @@ class Client(Methods, BaseClient): except KeyError: raise PeerIdInvalid - if peer_id > 0: + peer_type = utils.get_type(peer_id) + + if peer_type == "user": self.fetch_peers( self.send( functions.users.GetUsers( - id=[types.InputUser( - user_id=peer_id, - access_hash=0 - )] + id=[ + types.InputUser( + user_id=peer_id, + access_hash=0 + ) + ] ) ) ) + elif peer_type == "chat": + self.send( + functions.messages.GetChats( + id=[-peer_id] + ) + ) else: - if str(peer_id).startswith("-100"): - self.send( - functions.channels.GetChannels( - id=[types.InputChannel( - channel_id=int(str(peer_id)[4:]), + self.send( + functions.channels.GetChannels( + id=[ + types.InputChannel( + channel_id=utils.get_channel_id(peer_id), access_hash=0 - )] - ) - ) - else: - self.send( - functions.messages.GetChats( - id=[-peer_id] - ) + ) + ] ) + ) try: return self.storage.get_peer_by_id(peer_id) @@ -1693,23 +1935,22 @@ class Client(Methods, BaseClient): In case a file part expired, pass the file_id and the file_part to retry uploading that specific chunk. progress (``callable``, *optional*): - Pass a callback function to view the upload progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). + Pass a callback function to view the file transmission progress. + The function must take *(current, total)* as positional arguments (look at Other Parameters below for a + detailed description) and will be called back each time a new file chunk has been successfully + transmitted. progress_args (``tuple``, *optional*): - Extra custom arguments for the progress callback function. Useful, for example, if you want to pass - a chat_id and a message_id in order to edit a message with the updated progress. + Extra custom arguments for the progress callback function. + You can pass anything you need to be available in the progress callback scope; for example, a Message + object or a Client instance in order to edit the message with the updated progress status. 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. + The amount of bytes transmitted so far. total (``int``): - The size of the file. + The total size of the file. *args (``tuple``, *optional*): Extra custom arguments as defined in the *progress_args* parameter. @@ -2023,11 +2264,3 @@ class Client(Methods, BaseClient): if extensions: return extensions.split(" ")[0] - - def export_session_string(self): - """Export the current session as serialized string. - - Returns: - ``str``: The session serialized into a printable, url-safe string. - """ - return self.storage.export_session_string() diff --git a/pyrogram/client/ext/base_client.py b/pyrogram/client/ext/base_client.py index b5be089b..ce736e87 100644 --- a/pyrogram/client/ext/base_client.py +++ b/pyrogram/client/ext/base_client.py @@ -59,6 +59,8 @@ class BaseClient: WORKDIR = PARENT_DIR CONFIG_FILE = PARENT_DIR / "config.ini" + PARSE_MODES = ["combined", "markdown", "md", "html", None] + MEDIA_TYPE_ID = { 0: "photo_thumbnail", 1: "chat_photo", @@ -87,19 +89,21 @@ class BaseClient: mime_types_to_extensions[mime_type] = " ".join(extensions) + is_idling = False + def __init__(self): self.storage = None self.rnd_id = MsgId self.parser = Parser(self) + self.parse_mode = "combined" self.session = None self.media_sessions = {} self.media_sessions_lock = Lock() self.is_started = None - self.is_idle = None self.takeout_id = None diff --git a/pyrogram/client/ext/dispatcher.py b/pyrogram/client/ext/dispatcher.py index 55a31452..5b6bccd2 100644 --- a/pyrogram/client/ext/dispatcher.py +++ b/pyrogram/client/ext/dispatcher.py @@ -166,8 +166,13 @@ class Dispatcher: args = None if isinstance(handler, handler_type): - if handler.check(parsed_update): - args = (parsed_update,) + try: + if handler.check(parsed_update): + args = (parsed_update,) + except Exception as e: + log.error(e, exc_info=True) + continue + elif isinstance(handler, RawUpdateHandler): args = (update, users, chats) diff --git a/pyrogram/client/ext/utils.py b/pyrogram/client/ext/utils.py index e0a797e2..cdc0684c 100644 --- a/pyrogram/client/ext/utils.py +++ b/pyrogram/client/ext/utils.py @@ -18,10 +18,11 @@ import base64 import struct -from typing import Union, List +from typing import List +from typing import Union import pyrogram - +from pyrogram.api.types import PeerUser, PeerChat, PeerChannel from . import BaseClient from ...api import types @@ -30,10 +31,17 @@ def decode(s: str) -> bytes: s = base64.urlsafe_b64decode(s + "=" * (-len(s) % 4)) r = b"" - assert s[-1] == 2 + try: + assert s[-1] == 2 + skip = 1 + except AssertionError: + assert s[-2] == 22 + assert s[-1] == 4 + skip = 2 i = 0 - while i < len(s) - 1: + + while i < len(s) - skip: if s[i] != 0: r += bytes([s[i]]) else: @@ -49,7 +57,7 @@ def encode(s: bytes) -> str: r = b"" n = 0 - for i in s + bytes([2]): + for i in s + bytes([22]) + bytes([4]): if i == 0: n += 1 else: @@ -62,23 +70,6 @@ def encode(s: bytes) -> str: return base64.urlsafe_b64encode(r).decode().rstrip("=") -def get_peer_id(input_peer) -> int: - return ( - input_peer.user_id if isinstance(input_peer, types.InputPeerUser) - else -input_peer.chat_id if isinstance(input_peer, types.InputPeerChat) - else int("-100" + str(input_peer.channel_id)) - ) - - -def get_input_peer(peer_id: int, access_hash: int): - return ( - types.InputPeerUser(user_id=peer_id, access_hash=access_hash) if peer_id > 0 - else types.InputPeerChannel(channel_id=int(str(peer_id)[4:]), access_hash=access_hash) - if (str(peer_id).startswith("-100") and access_hash) - else types.InputPeerChat(chat_id=-peer_id) - ) - - def get_offset_date(dialogs): for m in reversed(dialogs.messages): if isinstance(m, types.MessageEmpty): @@ -183,7 +174,7 @@ def parse_deleted_messages(client, update) -> List["pyrogram.Message"]: pyrogram.Message( message_id=message, chat=pyrogram.Chat( - id=int("-100" + str(channel_id)), + id=get_channel_id(channel_id), type="channel", client=client ) if channel_id is not None else None, @@ -203,3 +194,39 @@ def unpack_inline_message_id(inline_message_id: str) -> types.InputBotInlineMess id=r[1], access_hash=r[2] ) + + +MIN_CHANNEL_ID = -1002147483647 +MAX_CHANNEL_ID = -1000000000000 +MIN_CHAT_ID = -2147483647 +MAX_USER_ID = 2147483647 + + +def get_peer_id(peer: Union[PeerUser, PeerChat, PeerChannel]) -> int: + if isinstance(peer, PeerUser): + return peer.user_id + + if isinstance(peer, PeerChat): + return -peer.chat_id + + if isinstance(peer, PeerChannel): + return MAX_CHANNEL_ID - peer.channel_id + + raise ValueError("Peer type invalid: {}".format(peer)) + + +def get_type(peer_id: int) -> str: + if peer_id < 0: + if MIN_CHAT_ID <= peer_id: + return "chat" + + if MIN_CHANNEL_ID <= peer_id < MAX_CHANNEL_ID: + return "channel" + elif 0 < peer_id <= MAX_USER_ID: + return "user" + + raise ValueError("Peer id invalid: {}".format(peer_id)) + + +def get_channel_id(peer_id: int) -> int: + return MAX_CHANNEL_ID - peer_id diff --git a/pyrogram/client/filters/filters.py b/pyrogram/client/filters/filters.py index d8768b3b..f80127c2 100644 --- a/pyrogram/client/filters/filters.py +++ b/pyrogram/client/filters/filters.py @@ -136,7 +136,7 @@ class Filters: poll = create(lambda _, m: m.poll, "PollFilter") """Filter messages that contain :obj:`Poll` objects.""" - private = create(lambda _, m: bool(m.chat and m.chat.type == "private"), "PrivateFilter") + private = create(lambda _, m: bool(m.chat and m.chat.type in {"private", "bot"}), "PrivateFilter") """Filter messages sent in private chats.""" group = create(lambda _, m: bool(m.chat and m.chat.type in {"group", "supergroup"}), "GroupFilter") @@ -240,6 +240,7 @@ class Filters: def func(flt, message): text = message.text or message.caption + message.command = None if text: for p in flt.p: @@ -272,11 +273,15 @@ class Filters: RegEx flags. """ - def f(_, m): - m.matches = [i for i in _.p.finditer(m.text or m.caption or "")] - return bool(m.matches) + def func(flt, message): + text = message.text or message.caption - return create(f, "RegexFilter", p=re.compile(pattern, flags)) + if text: + message.matches = list(flt.p.finditer(text)) or None + + return bool(message.matches) + + return create(func, "RegexFilter", p=re.compile(pattern, flags)) # noinspection PyPep8Naming class user(Filter, set): diff --git a/pyrogram/client/methods/bots/answer_callback_query.py b/pyrogram/client/methods/bots/answer_callback_query.py index 010c29ea..dec3bef0 100644 --- a/pyrogram/client/methods/bots/answer_callback_query.py +++ b/pyrogram/client/methods/bots/answer_callback_query.py @@ -56,8 +56,14 @@ class AnswerCallbackQuery(BaseClient): Returns: ``bool``: True, on success. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Answer without alert + app.answer_callback_query(query_id, text=text) + + # Answer with alert + app.answer_callback_query(query_id, text=text, show_alert=True) """ return self.send( functions.messages.SetBotCallbackAnswer( diff --git a/pyrogram/client/methods/bots/answer_inline_query.py b/pyrogram/client/methods/bots/answer_inline_query.py index 38ed99c3..da801c62 100644 --- a/pyrogram/client/methods/bots/answer_inline_query.py +++ b/pyrogram/client/methods/bots/answer_inline_query.py @@ -29,28 +29,34 @@ class AnswerInlineQuery(BaseClient): inline_query_id: str, results: List[InlineQueryResult], cache_time: int = 300, - is_personal: bool = None, + is_gallery: bool = False, + is_personal: bool = False, next_offset: str = "", switch_pm_text: str = "", switch_pm_parameter: str = "" ): """Send answers to an inline query. - No more than 50 results per query are allowed. + + A maximum of 50 results per query is allowed. Parameters: inline_query_id (``str``): Unique identifier for the answered query. - results (List of :obj:`InlineQueryResult `): + results (List of :obj:`InlineQueryResult`): A list of results for the inline query. cache_time (``int``, *optional*): The maximum amount of time in seconds that the result of the inline query may be cached on the server. Defaults to 300. + is_gallery (``bool``, *optional*): + Pass True, if results should be displayed in gallery mode instead of list mode. + Defaults to False. + is_personal (``bool``, *optional*): Pass True, if results may be cached on the server side only for the user that sent the query. - By default, results may be returned to any user who sends the same query. + By default (False), results may be returned to any user who sends the same query. next_offset (``str``, *optional*): Pass the offset that a client should send in the next query with the same text to receive more results. @@ -75,15 +81,24 @@ class AnswerInlineQuery(BaseClient): Returns: ``bool``: True, on success. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + from pyrogram import InlineQueryResultArticle, InputTextMessageContent + + app.answer_inline_query( + inline_query_id, + results=[ + InlineQueryResultArticle( + "Title", + InputTextMessageContent("Message content"))]) """ return self.send( functions.messages.SetInlineBotResults( query_id=int(inline_query_id), results=[r.write() for r in results], cache_time=cache_time, - gallery=None, + gallery=is_gallery or None, private=is_personal or None, next_offset=next_offset or None, switch_pm=types.InlineBotSwitchPM( diff --git a/pyrogram/client/methods/bots/get_game_high_scores.py b/pyrogram/client/methods/bots/get_game_high_scores.py index e6459bac..595e4e1a 100644 --- a/pyrogram/client/methods/bots/get_game_high_scores.py +++ b/pyrogram/client/methods/bots/get_game_high_scores.py @@ -51,8 +51,11 @@ class GetGameHighScores(BaseClient): Returns: List of :obj:`GameHighScore`: On success. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + scores = app.get_game_high_scores(user_id, chat_id, message_id) + print(scores) """ # TODO: inline_message_id diff --git a/pyrogram/client/methods/bots/get_inline_bot_results.py b/pyrogram/client/methods/bots/get_inline_bot_results.py index cc0fc1b1..99f05c95 100644 --- a/pyrogram/client/methods/bots/get_inline_bot_results.py +++ b/pyrogram/client/methods/bots/get_inline_bot_results.py @@ -27,7 +27,7 @@ class GetInlineBotResults(BaseClient): def get_inline_bot_results( self, bot: Union[int, str], - query: str, + query: str = "", offset: str = "", latitude: float = None, longitude: float = None @@ -40,8 +40,9 @@ class GetInlineBotResults(BaseClient): 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``): + query (``str``, *optional*): Text of the query (up to 512 characters). + Defaults to "" (empty string). offset (``str``, *optional*): Offset of the results to be returned. @@ -58,8 +59,13 @@ class GetInlineBotResults(BaseClient): :obj:`BotResults `: On Success. Raises: - RPCError: In case of a Telegram RPC error. TimeoutError: In case the bot fails to answer within 10 seconds. + + Example: + .. code-block:: python + + results = app.get_inline_bot_results("pyrogrambot") + print(results) """ # TODO: Don't return the raw type diff --git a/pyrogram/client/methods/bots/request_callback_answer.py b/pyrogram/client/methods/bots/request_callback_answer.py index 97d8d42b..01879bbb 100644 --- a/pyrogram/client/methods/bots/request_callback_answer.py +++ b/pyrogram/client/methods/bots/request_callback_answer.py @@ -53,8 +53,12 @@ class RequestCallbackAnswer(BaseClient): or as an alert. Raises: - RPCError: In case of a Telegram RPC error. TimeoutError: In case the bot fails to answer within 10 seconds. + + Example: + .. code-block:: python + + app.request_callback_answer(chat_id, message_id, "callback_data") """ # Telegram only wants bytes, but we are allowed to pass strings too. diff --git a/pyrogram/client/methods/bots/send_game.py b/pyrogram/client/methods/bots/send_game.py index c10d328a..1a6a772a 100644 --- a/pyrogram/client/methods/bots/send_game.py +++ b/pyrogram/client/methods/bots/send_game.py @@ -62,8 +62,10 @@ class SendGame(BaseClient): Returns: :obj:`Message`: On success, the sent game message is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + app.send_game(chat_id, "gamename") """ r = self.send( functions.messages.SendMedia( diff --git a/pyrogram/client/methods/bots/send_inline_bot_result.py b/pyrogram/client/methods/bots/send_inline_bot_result.py index 411ab462..059185db 100644 --- a/pyrogram/client/methods/bots/send_inline_bot_result.py +++ b/pyrogram/client/methods/bots/send_inline_bot_result.py @@ -60,8 +60,10 @@ class SendInlineBotResult(BaseClient): Returns: :obj:`Message`: On success, the sent inline result message is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + app.send_inline_bot_result(chat_id, query_id, result_id) """ return self.send( functions.messages.SendInlineBotResult( diff --git a/pyrogram/client/methods/bots/set_game_score.py b/pyrogram/client/methods/bots/set_game_score.py index f9115b74..ba2e74fa 100644 --- a/pyrogram/client/methods/bots/set_game_score.py +++ b/pyrogram/client/methods/bots/set_game_score.py @@ -66,8 +66,14 @@ class SetGameScore(BaseClient): :obj:`Message` | ``bool``: On success, if the message was sent by the bot, the edited message is returned, True otherwise. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Set new score + app.set_game_score(user_id, 1000) + + # Force set new score + app.set_game_score(user_id, 25, force=True) """ r = self.send( functions.messages.SetGameScore( diff --git a/pyrogram/client/methods/chats/__init__.py b/pyrogram/client/methods/chats/__init__.py index 969628ee..fddb48ce 100644 --- a/pyrogram/client/methods/chats/__init__.py +++ b/pyrogram/client/methods/chats/__init__.py @@ -16,8 +16,14 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from .add_chat_members import AddChatMembers from .archive_chats import ArchiveChats +from .create_channel import CreateChannel +from .create_group import CreateGroup +from .create_supergroup import CreateSupergroup +from .delete_channel import DeleteChannel from .delete_chat_photo import DeleteChatPhoto +from .delete_supergroup import DeleteSupergroup from .export_chat_invite_link import ExportChatInviteLink from .get_chat import GetChat from .get_chat_member import GetChatMember @@ -32,9 +38,9 @@ from .kick_chat_member import KickChatMember from .leave_chat import LeaveChat from .pin_chat_message import PinChatMessage from .promote_chat_member import PromoteChatMember -from .restrict_chat import RestrictChat from .restrict_chat_member import RestrictChatMember from .set_chat_description import SetChatDescription +from .set_chat_permissions import SetChatPermissions from .set_chat_photo import SetChatPhoto from .set_chat_title import SetChatTitle from .unarchive_chats import UnarchiveChats @@ -65,9 +71,15 @@ class Chats( IterDialogs, IterChatMembers, UpdateChatUsername, - RestrictChat, + SetChatPermissions, GetDialogsCount, ArchiveChats, - UnarchiveChats + UnarchiveChats, + CreateGroup, + CreateSupergroup, + CreateChannel, + AddChatMembers, + DeleteChannel, + DeleteSupergroup ): pass diff --git a/pyrogram/client/methods/chats/add_chat_members.py b/pyrogram/client/methods/chats/add_chat_members.py new file mode 100644 index 00000000..8dbad1a3 --- /dev/null +++ b/pyrogram/client/methods/chats/add_chat_members.py @@ -0,0 +1,88 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 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 typing import Union, List + +from pyrogram.api import functions, types +from ...ext import BaseClient + + +class AddChatMembers(BaseClient): + def add_chat_members( + self, + chat_id: Union[int, str], + user_ids: Union[Union[int, str], List[Union[int, str]]], + forward_limit: int = 100 + ) -> bool: + """Add new chat members to a group, supergroup or channel + + Parameters: + chat_id (``int`` | ``str``): + The group, supergroup or channel id + + user_ids (``int`` | ``str`` | List of ``int`` or ``str``): + Users to add in the chat + You can pass an ID (int), username (str) or phone number (str). + Multiple users can be added by passing a list of IDs, usernames or phone numbers. + + forward_limit (``int``, *optional*): + How many of the latest messages you want to forward to the new members. Pass 0 to forward none of them. + Only applicable to basic groups (the argument is ignored for supergroups or channels). + Defaults to 100 (max amount). + + Returns: + ``bool``: On success, True is returned. + + Example: + .. code-block:: python + + # Add one member to a group or channel + app.add_chat_members(chat_id, user_id) + + # Add multiple members to a group or channel + app.add_chat_members(chat_id, [user_id1, user_id2, user_id3]) + + # Change forward_limit (for basic groups only) + app.add_chat_members(chat_id, user_id, forward_limit=25) + """ + peer = self.resolve_peer(chat_id) + + if not isinstance(user_ids, list): + user_ids = [user_ids] + + if isinstance(peer, types.InputPeerChat): + for user_id in user_ids: + self.send( + functions.messages.AddChatUser( + chat_id=peer.chat_id, + user_id=self.resolve_peer(user_id), + fwd_limit=forward_limit + ) + ) + else: + self.send( + functions.channels.InviteToChannel( + channel=peer, + users=[ + self.resolve_peer(user_id) + for user_id in user_ids + ] + ) + ) + + return True diff --git a/pyrogram/client/methods/chats/archive_chats.py b/pyrogram/client/methods/chats/archive_chats.py index 3c929983..14375a92 100644 --- a/pyrogram/client/methods/chats/archive_chats.py +++ b/pyrogram/client/methods/chats/archive_chats.py @@ -37,8 +37,14 @@ class ArchiveChats(BaseClient): Returns: ``bool``: On success, True is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Archive chat + app.archive_chats(chat_id) + + # Archive multiple chats at once + app.archive_chats([chat_id1, chat_id2, chat_id3]) """ if not isinstance(chat_ids, list): diff --git a/pyrogram/client/methods/chats/create_channel.py b/pyrogram/client/methods/chats/create_channel.py new file mode 100644 index 00000000..9520ceef --- /dev/null +++ b/pyrogram/client/methods/chats/create_channel.py @@ -0,0 +1,55 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 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 pyrogram.api import functions +from ...ext import BaseClient + + +class CreateChannel(BaseClient): + def create_channel( + self, + title: str, + description: str = "" + ) -> "pyrogram.Chat": + """Create a new broadcast channel. + + Parameters: + title (``title``): + The channel title. + + description (``str``, *optional*): + The channel description. + + Returns: + :obj:`Chat`: On success, a chat object is returned. + + Example: + .. code-block:: python + + app.create_channel("Channel Title", "Channel Description") + """ + r = self.send( + functions.channels.CreateChannel( + title=title, + about=description, + broadcast=True + ) + ) + + return pyrogram.Chat._parse_chat(self, r.chats[0]) diff --git a/pyrogram/client/methods/chats/create_group.py b/pyrogram/client/methods/chats/create_group.py new file mode 100644 index 00000000..4e1d63bd --- /dev/null +++ b/pyrogram/client/methods/chats/create_group.py @@ -0,0 +1,65 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 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 typing import Union, List + +import pyrogram +from pyrogram.api import functions +from ...ext import BaseClient + + +class CreateGroup(BaseClient): + def create_group( + self, + title: str, + users: Union[Union[int, str], List[Union[int, str]]] + ) -> "pyrogram.Chat": + """Create a new basic group. + + .. note:: + + If you want to create a new supergroup, use :meth:`~pyrogram.Client.create_supergroup` instead. + + Parameters: + title (``title``): + The group title. + + users (``int`` | ``str`` | List of ``int`` or ``str``): + Users to create a chat with. + You must pass at least one user using their IDs (int), usernames (str) or phone numbers (str). + Multiple users can be invited by passing a list of IDs, usernames or phone numbers. + + Returns: + :obj:`Chat`: On success, a chat object is returned. + + Example: + .. code-block:: python + + app.create_group("Group Title", user_id) + """ + if not isinstance(users, list): + users = [users] + + r = self.send( + functions.messages.CreateChat( + title=title, + users=[self.resolve_peer(u) for u in users] + ) + ) + + return pyrogram.Chat._parse_chat(self, r.chats[0]) diff --git a/pyrogram/client/methods/chats/create_supergroup.py b/pyrogram/client/methods/chats/create_supergroup.py new file mode 100644 index 00000000..0ad14d06 --- /dev/null +++ b/pyrogram/client/methods/chats/create_supergroup.py @@ -0,0 +1,59 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 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 pyrogram.api import functions +from ...ext import BaseClient + + +class CreateSupergroup(BaseClient): + def create_supergroup( + self, + title: str, + description: str = "" + ) -> "pyrogram.Chat": + """Create a new supergroup. + + .. note:: + + If you want to create a new basic group, use :meth:`~pyrogram.Client.create_group` instead. + + Parameters: + title (``title``): + The supergroup title. + + description (``str``, *optional*): + The supergroup description. + + Returns: + :obj:`Chat`: On success, a chat object is returned. + + Example: + .. code-block:: python + + app.create_supergroup("Supergroup Title", "Supergroup Description") + """ + r = self.send( + functions.channels.CreateChannel( + title=title, + about=description, + megagroup=True + ) + ) + + return pyrogram.Chat._parse_chat(self, r.chats[0]) diff --git a/pyrogram/api/core/primitives/null.py b/pyrogram/client/methods/chats/delete_channel.py similarity index 54% rename from pyrogram/api/core/primitives/null.py rename to pyrogram/client/methods/chats/delete_channel.py index ffddea94..74fbea13 100644 --- a/pyrogram/api/core/primitives/null.py +++ b/pyrogram/client/methods/chats/delete_channel.py @@ -16,17 +16,33 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from io import BytesIO -from ..tl_object import TLObject +from typing import Union + +from pyrogram.api import functions +from ...ext import BaseClient -class Null(TLObject): - ID = 0x56730bcc +class DeleteChannel(BaseClient): + def delete_channel(self, chat_id: Union[int, str]) -> bool: + """Delete a channel. - @staticmethod - def read(b: BytesIO, *args) -> None: - return None + Parameters: + chat_id (``int`` | ``str``): + The id of the channel to be deleted. - def __new__(cls) -> bytes: - return cls.ID.to_bytes(4, "little") + Returns: + ``bool``: On success, True is returned. + + Example: + .. code-block:: python + + app.delete_channel(channel_id) + """ + self.send( + functions.channels.DeleteChannel( + channel=self.resolve_peer(chat_id) + ) + ) + + return True diff --git a/pyrogram/client/methods/chats/delete_chat_photo.py b/pyrogram/client/methods/chats/delete_chat_photo.py index 88d97506..89f869bf 100644 --- a/pyrogram/client/methods/chats/delete_chat_photo.py +++ b/pyrogram/client/methods/chats/delete_chat_photo.py @@ -28,12 +28,8 @@ class DeleteChatPhoto(BaseClient): chat_id: Union[int, str] ) -> bool: """Delete a chat photo. - Photos can't be changed for private chats. - You must be an administrator in the chat for this to work and must have the appropriate admin rights. - Note: - In regular groups (non-supergroups), this method will only work if the "All Members Are Admins" - setting is off. + You must be an administrator in the chat for this to work and must have the appropriate admin rights. Parameters: chat_id (``int`` | ``str``): @@ -43,8 +39,12 @@ class DeleteChatPhoto(BaseClient): ``bool``: True on success. Raises: - RPCError: In case of a Telegram RPC error. - ``ValueError`` if a chat_id belongs to user. + ValueError: if a chat_id belongs to user. + + Example: + .. code-block:: python + + app.delete_chat_photo(chat_id) """ peer = self.resolve_peer(chat_id) diff --git a/pyrogram/client/methods/chats/delete_supergroup.py b/pyrogram/client/methods/chats/delete_supergroup.py new file mode 100644 index 00000000..a1eb198d --- /dev/null +++ b/pyrogram/client/methods/chats/delete_supergroup.py @@ -0,0 +1,48 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 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 typing import Union + +from pyrogram.api import functions +from ...ext import BaseClient + + +class DeleteSupergroup(BaseClient): + def delete_supergroup(self, chat_id: Union[int, str]) -> bool: + """Delete a supergroup. + + Parameters: + chat_id (``int`` | ``str``): + The id of the supergroup to be deleted. + + Returns: + ``bool``: On success, True is returned. + + Example: + .. code-block:: python + + app.delete_supergroup(supergroup_id) + """ + self.send( + functions.channels.DeleteChannel( + channel=self.resolve_peer(chat_id) + ) + ) + + return True diff --git a/pyrogram/client/methods/chats/export_chat_invite_link.py b/pyrogram/client/methods/chats/export_chat_invite_link.py index ca75cac6..46886469 100644 --- a/pyrogram/client/methods/chats/export_chat_invite_link.py +++ b/pyrogram/client/methods/chats/export_chat_invite_link.py @@ -47,22 +47,21 @@ class ExportChatInviteLink(BaseClient): ``str``: On success, the exported invite link is returned. Raises: - RPCError: In case of a Telegram RPC error. ValueError: In case the chat_id belongs to a user. + + Example: + .. code-block:: python + + link = app.export_chat_invite_link(chat_id) + print(link) """ peer = self.resolve_peer(chat_id) - if isinstance(peer, types.InputPeerChat): + if isinstance(peer, (types.InputPeerChat, types.InputPeerChannel)): return self.send( functions.messages.ExportChatInvite( peer=peer ) ).link - elif isinstance(peer, types.InputPeerChannel): - return self.send( - functions.channels.ExportInvite( - channel=peer - ) - ).link else: raise ValueError('The chat_id "{}" belongs to a user'.format(chat_id)) diff --git a/pyrogram/client/methods/chats/get_chat.py b/pyrogram/client/methods/chats/get_chat.py index 4f71c3b3..0773ce6c 100644 --- a/pyrogram/client/methods/chats/get_chat.py +++ b/pyrogram/client/methods/chats/get_chat.py @@ -20,7 +20,7 @@ from typing import Union import pyrogram from pyrogram.api import functions, types -from ...ext import BaseClient +from ...ext import BaseClient, utils class GetChat(BaseClient): @@ -44,8 +44,13 @@ class GetChat(BaseClient): otherwise, a chat preview object is returned. Raises: - RPCError: In case of a Telegram RPC error. ValueError: In case the chat invite link points to a chat you haven't joined yet. + + Example: + .. code-block:: python + + chat = app.get_chat("pyrogram") + print(chat) """ match = self.INVITE_LINK_RE.match(str(chat_id)) @@ -65,7 +70,7 @@ class GetChat(BaseClient): chat_id = -r.chat.id if isinstance(r.chat, types.Channel): - chat_id = int("-100" + str(r.chat.id)) + chat_id = utils.get_channel_id(r.chat.id) peer = self.resolve_peer(chat_id) diff --git a/pyrogram/client/methods/chats/get_chat_member.py b/pyrogram/client/methods/chats/get_chat_member.py index b0d0641a..20d9c624 100644 --- a/pyrogram/client/methods/chats/get_chat_member.py +++ b/pyrogram/client/methods/chats/get_chat_member.py @@ -44,8 +44,11 @@ class GetChatMember(BaseClient): Returns: :obj:`ChatMember`: On success, a chat member is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + dan = app.get_chat_member("pyrogramchat", "haskell") + print(dan) """ chat = self.resolve_peer(chat_id) user = self.resolve_peer(user_id) diff --git a/pyrogram/client/methods/chats/get_chat_members.py b/pyrogram/client/methods/chats/get_chat_members.py index 0b4613d8..19b5971e 100644 --- a/pyrogram/client/methods/chats/get_chat_members.py +++ b/pyrogram/client/methods/chats/get_chat_members.py @@ -91,8 +91,19 @@ class GetChatMembers(BaseClient): List of :obj:`ChatMember`: On success, a list of chat members is returned. Raises: - RPCError: In case of a Telegram RPC error. ValueError: In case you used an invalid filter or a chat id that belongs to a user. + + Example: + .. code-block:: python + + # Get first 200 recent members + app.get_chat_members("pyrogramchat") + + # Get all administrators + app.get_chat_members("pyrogramchat", filter="administrators") + + # Get all bots + app.get_chat_members("pyrogramchat", filter="bots") """ peer = self.resolve_peer(chat_id) diff --git a/pyrogram/client/methods/chats/get_chat_members_count.py b/pyrogram/client/methods/chats/get_chat_members_count.py index 4c7ab747..74b6cda2 100644 --- a/pyrogram/client/methods/chats/get_chat_members_count.py +++ b/pyrogram/client/methods/chats/get_chat_members_count.py @@ -37,8 +37,13 @@ class GetChatMembersCount(BaseClient): ``int``: On success, the chat members count is returned. Raises: - RPCError: In case of a Telegram RPC error. ValueError: In case a chat id belongs to user. + + Example: + .. code-block:: python + + count = app.get_chat_members_count("pyrogramchat") + print(count) """ peer = self.resolve_peer(chat_id) diff --git a/pyrogram/client/methods/chats/get_dialogs.py b/pyrogram/client/methods/chats/get_dialogs.py index 8c374a44..30078d57 100644 --- a/pyrogram/client/methods/chats/get_dialogs.py +++ b/pyrogram/client/methods/chats/get_dialogs.py @@ -23,7 +23,7 @@ from typing import List import pyrogram from pyrogram.api import functions, types from pyrogram.errors import FloodWait -from ...ext import BaseClient +from ...ext import BaseClient, utils log = logging.getLogger(__name__) @@ -56,8 +56,14 @@ class GetDialogs(BaseClient): Returns: List of :obj:`Dialog`: On success, a list of dialogs is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Get first 100 dialogs + app.get_dialogs() + + # Get pinned dialogs + app.get_dialogs(pinned_only=True) """ while True: @@ -94,10 +100,8 @@ class GetDialogs(BaseClient): chat_id = to_id.user_id else: chat_id = message.from_id - elif isinstance(to_id, types.PeerChat): - chat_id = -to_id.chat_id else: - chat_id = int("-100" + str(to_id.channel_id)) + chat_id = utils.get_peer_id(to_id) messages[chat_id] = pyrogram.Message._parse(self, message, users, chats) diff --git a/pyrogram/client/methods/chats/get_dialogs_count.py b/pyrogram/client/methods/chats/get_dialogs_count.py index c804709d..128b4364 100644 --- a/pyrogram/client/methods/chats/get_dialogs_count.py +++ b/pyrogram/client/methods/chats/get_dialogs_count.py @@ -31,8 +31,11 @@ class GetDialogsCount(BaseClient): Returns: ``int``: On success, the dialogs count is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + count = app.get_dialogs_count() + print(count) """ if pinned_only: diff --git a/pyrogram/client/methods/chats/iter_chat_members.py b/pyrogram/client/methods/chats/iter_chat_members.py index fe117694..297b8ff3 100644 --- a/pyrogram/client/methods/chats/iter_chat_members.py +++ b/pyrogram/client/methods/chats/iter_chat_members.py @@ -77,8 +77,20 @@ class IterChatMembers(BaseClient): Returns: ``Generator``: A generator yielding :obj:`ChatMember` objects. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Iterate though all chat members + for member in app.iter_chat_members("pyrogramchat"): + print(member.user.first_name) + + # Iterate though all administrators + for member in app.iter_chat_members("pyrogramchat", filter="administrators"): + print(member.user.first_name) + + # Iterate though all bots + for member in app.iter_chat_members("pyrogramchat", filter="bots"): + print(member.user.first_name) """ current = 0 yielded = set() diff --git a/pyrogram/client/methods/chats/iter_dialogs.py b/pyrogram/client/methods/chats/iter_dialogs.py index fce9fb99..55de2a74 100644 --- a/pyrogram/client/methods/chats/iter_dialogs.py +++ b/pyrogram/client/methods/chats/iter_dialogs.py @@ -46,8 +46,12 @@ class IterDialogs(BaseClient): Returns: ``Generator``: A generator yielding :obj:`Dialog` objects. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Iterate through all dialogs + for dialog in app.iter_dialogs(): + print(dialog.chat.first_name or dialog.chat.title) """ current = 0 total = limit or (1 << 31) - 1 diff --git a/pyrogram/client/methods/chats/join_chat.py b/pyrogram/client/methods/chats/join_chat.py index ed6c69ce..c1dd923a 100644 --- a/pyrogram/client/methods/chats/join_chat.py +++ b/pyrogram/client/methods/chats/join_chat.py @@ -36,8 +36,14 @@ class JoinChat(BaseClient): Returns: :obj:`Chat`: On success, a chat object is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Join chat via username + app.join_chat("pyrogram") + + # Join chat via invite link + app.join_chat("https://t.me/joinchat/AAAAAE0QmSW3IUmm3UFR7A") """ match = self.INVITE_LINK_RE.match(chat_id) diff --git a/pyrogram/client/methods/chats/kick_chat_member.py b/pyrogram/client/methods/chats/kick_chat_member.py index 9686e754..20f26c50 100644 --- a/pyrogram/client/methods/chats/kick_chat_member.py +++ b/pyrogram/client/methods/chats/kick_chat_member.py @@ -57,8 +57,16 @@ class KickChatMember(BaseClient): :obj:`Message` | ``bool``: On success, a service message will be returned (when applicable), otherwise, in case a message object couldn't be returned, True is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + from time import time + + # Ban chat member forever + app.kick_chat_member(chat_id, user_id) + + # Kick chat member and automatically unban after 24h + app.kick_chat_member(chat_id, user_id, int(time.time() + 86400)) """ chat_peer = self.resolve_peer(chat_id) user_peer = self.resolve_peer(user_id) diff --git a/pyrogram/client/methods/chats/leave_chat.py b/pyrogram/client/methods/chats/leave_chat.py index 3ed6f10f..0a8aec0e 100644 --- a/pyrogram/client/methods/chats/leave_chat.py +++ b/pyrogram/client/methods/chats/leave_chat.py @@ -37,9 +37,16 @@ class LeaveChat(BaseClient): delete (``bool``, *optional*): Deletes the group chat dialog after leaving (for simple group chats, not supergroups). + Defaults to False. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Leave chat or channel + app.leave_chat(chat_id) + + # Leave basic chat and also delete the dialog + app.leave_chat(chat_id, delete=True) """ peer = self.resolve_peer(chat_id) diff --git a/pyrogram/client/methods/chats/pin_chat_message.py b/pyrogram/client/methods/chats/pin_chat_message.py index efb41e67..fcdb31fd 100644 --- a/pyrogram/client/methods/chats/pin_chat_message.py +++ b/pyrogram/client/methods/chats/pin_chat_message.py @@ -47,8 +47,14 @@ class PinChatMessage(BaseClient): Returns: ``bool``: True on success. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Pin with notification + app.pin_chat_message(chat_id, message_id) + + # Pin without notification + app.pin_chat_message(chat_id, message_id, disable_notification=True) """ self.send( functions.messages.UpdatePinnedMessage( diff --git a/pyrogram/client/methods/chats/promote_chat_member.py b/pyrogram/client/methods/chats/promote_chat_member.py index 700b3a68..9394841b 100644 --- a/pyrogram/client/methods/chats/promote_chat_member.py +++ b/pyrogram/client/methods/chats/promote_chat_member.py @@ -78,8 +78,11 @@ class PromoteChatMember(BaseClient): Returns: ``bool``: True on success. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Promote chat member to supergroup admin + app.promote_chat_member(chat_id, user_id) """ self.send( functions.channels.EditAdmin( diff --git a/pyrogram/client/methods/chats/restrict_chat_member.py b/pyrogram/client/methods/chats/restrict_chat_member.py index 30574022..f20eb348 100644 --- a/pyrogram/client/methods/chats/restrict_chat_member.py +++ b/pyrogram/client/methods/chats/restrict_chat_member.py @@ -20,7 +20,7 @@ from typing import Union from pyrogram.api import functions, types from ...ext import BaseClient -from ...types.user_and_chats import Chat +from ...types.user_and_chats import Chat, ChatPermissions class RestrictChatMember(BaseClient): @@ -28,20 +28,13 @@ class RestrictChatMember(BaseClient): self, chat_id: Union[int, str], user_id: Union[int, str], - until_date: int = 0, - can_send_messages: bool = False, - can_send_media_messages: bool = False, - can_send_other_messages: bool = False, - can_add_web_page_previews: bool = False, - can_send_polls: bool = False, - can_change_info: bool = False, - can_invite_users: bool = False, - can_pin_messages: bool = False + permissions: ChatPermissions, + until_date: int = 0 ) -> Chat: """Restrict a user in a supergroup. - The bot must be an administrator in the supergroup for this to work and must have the appropriate admin rights. - Pass True for all boolean parameters to lift restrictions from a user. + You must be an administrator in the supergroup for this to work and must have the appropriate admin rights. + Pass True for all permissions to lift restrictions from a user. Parameters: chat_id (``int`` | ``str``): @@ -51,42 +44,32 @@ class RestrictChatMember(BaseClient): Unique identifier (int) or username (str) of the target user. For a contact that exists in your Telegram address book you can use his phone number (str). + permissions (:obj:`ChatPermissions`): + New user permissions. + until_date (``int``, *optional*): Date when the user will be unbanned, unix time. If user is banned for more than 366 days or less than 30 seconds from the current time they are considered to be banned forever. Defaults to 0 (ban forever). - can_send_messages (``bool``, *optional*): - Pass True, if the user can send text messages, contacts, locations and venues. - - can_send_media_messages (``bool``, *optional*): - Pass True, if the user can send audios, documents, photos, videos, video notes and voice notes, - implies can_send_messages. - - can_send_other_messages (``bool``, *optional*): - Pass True, if the user can send animations, games, stickers and use inline bots, - implies can_send_messages. - - can_add_web_page_previews (``bool``, *optional*): - Pass True, if the user may add web page previews to their messages, implies can_send_messages. - - can_send_polls (``bool``, *optional*): - Pass True, if the user can send polls, implies can_send_messages. - - can_change_info (``bool``, *optional*): - Pass True, if the user can change the chat title, photo and other settings. - - can_invite_users (``bool``, *optional*): - Pass True, if the user can invite new users to the chat. - - can_pin_messages (``bool``, *optional*): - Pass True, if the user can pin messages. - Returns: :obj:`Chat`: On success, a chat object is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + from time import time + + from pyrogram import ChatPermissions + + # Completely restrict chat member (mute) forever + app.restrict_chat_member(chat_id, user_id, ChatPermissions()) + + # Chat member muted for 24h + app.restrict_chat_member(chat_id, user_id, ChatPermissions(), int(time.time() + 86400)) + + # Chat member can only send text messages + app.restrict_chat_member(chat_id, user_id, ChatPermissions(can_send_messages=True)) """ send_messages = True send_media = True @@ -100,35 +83,35 @@ class RestrictChatMember(BaseClient): invite_users = True pin_messages = True - if can_send_messages: + if permissions.can_send_messages: send_messages = None - if can_send_media_messages: + if permissions.can_send_media_messages: send_messages = None send_media = None - if can_send_other_messages: + if permissions.can_send_other_messages: send_messages = None send_stickers = None send_gifs = None send_games = None send_inline = None - if can_add_web_page_previews: + if permissions.can_add_web_page_previews: send_messages = None embed_links = None - if can_send_polls: + if permissions.can_send_polls: send_messages = None send_polls = None - if can_change_info: + if permissions.can_change_info: change_info = None - if can_invite_users: + if permissions.can_invite_users: invite_users = None - if can_pin_messages: + if permissions.can_pin_messages: pin_messages = None r = self.send( diff --git a/pyrogram/client/methods/chats/set_chat_description.py b/pyrogram/client/methods/chats/set_chat_description.py index 68bf9fa2..8d0f0669 100644 --- a/pyrogram/client/methods/chats/set_chat_description.py +++ b/pyrogram/client/methods/chats/set_chat_description.py @@ -42,8 +42,12 @@ class SetChatDescription(BaseClient): ``bool``: True on success. Raises: - RPCError: In case of a Telegram RPC error. - ``ValueError`` if a chat_id doesn't belong to a supergroup or a channel. + ValueError: if a chat_id doesn't belong to a supergroup or a channel. + + Example: + .. code-block:: python + + app.set_chat_description(chat_id, "New Description") """ peer = self.resolve_peer(chat_id) diff --git a/pyrogram/client/methods/chats/restrict_chat.py b/pyrogram/client/methods/chats/set_chat_permissions.py similarity index 56% rename from pyrogram/client/methods/chats/restrict_chat.py rename to pyrogram/client/methods/chats/set_chat_permissions.py index dc0f96a1..f1ea61c7 100644 --- a/pyrogram/client/methods/chats/restrict_chat.py +++ b/pyrogram/client/methods/chats/set_chat_permissions.py @@ -20,60 +20,47 @@ from typing import Union from pyrogram.api import functions, types from ...ext import BaseClient -from ...types.user_and_chats import Chat +from ...types.user_and_chats import Chat, ChatPermissions -class RestrictChat(BaseClient): - def restrict_chat( +class SetChatPermissions(BaseClient): + def set_chat_permissions( self, chat_id: Union[int, str], - can_send_messages: bool = False, - can_send_media_messages: bool = False, - can_send_other_messages: bool = False, - can_add_web_page_previews: bool = False, - can_send_polls: bool = False, - can_change_info: bool = False, - can_invite_users: bool = False, - can_pin_messages: bool = False + permissions: ChatPermissions, ) -> Chat: - """Restrict a chat. - Pass True for all boolean parameters to lift restrictions from a chat. + """Set default chat permissions for all members. + + You must be an administrator in the group or a supergroup for this to work and must have the + *can_restrict_members* admin rights. Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. - can_send_messages (``bool``, *optional*): - Pass True, if the user can send text messages, contacts, locations and venues. - - can_send_media_messages (``bool``, *optional*): - Pass True, if the user can send audios, documents, photos, videos, video notes and voice notes, - implies can_send_messages. - - can_send_other_messages (``bool``, *optional*): - Pass True, if the user can send animations, games, stickers and use inline bots, - implies can_send_messages. - - can_add_web_page_previews (``bool``, *optional*): - Pass True, if the user may add web page previews to their messages, implies can_send_messages. - - can_send_polls (``bool``, *optional*): - Pass True, if the user can send polls, implies can_send_messages. - - can_change_info (``bool``, *optional*): - Pass True, if the user can change the chat title, photo and other settings. - - can_invite_users (``bool``, *optional*): - Pass True, if the user can invite new users to the chat. - - can_pin_messages (``bool``, *optional*): - Pass True, if the user can pin messages. + permissions (:obj:`ChatPermissions`): + New default chat permissions. Returns: :obj:`Chat`: On success, a chat object is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + from pyrogram import ChatPermissions + + # Completely restrict chat + app.set_chat_permissions(chat_id, ChatPermissions()) + + # Chat members can only send text messages, media, stickers and GIFs + app.set_chat_permissions( + chat_id, + ChatPermissions( + can_send_messages=True, + can_send_media_messages=True, + can_send_other_messages=True + ) + ) """ send_messages = True send_media = True @@ -87,35 +74,35 @@ class RestrictChat(BaseClient): invite_users = True pin_messages = True - if can_send_messages: + if permissions.can_send_messages: send_messages = None - if can_send_media_messages: + if permissions.can_send_media_messages: send_messages = None send_media = None - if can_send_other_messages: + if permissions.can_send_other_messages: send_messages = None send_stickers = None send_gifs = None send_games = None send_inline = None - if can_add_web_page_previews: + if permissions.can_add_web_page_previews: send_messages = None embed_links = None - if can_send_polls: + if permissions.can_send_polls: send_messages = None send_polls = None - if can_change_info: + if permissions.can_change_info: change_info = None - if can_invite_users: + if permissions.can_invite_users: invite_users = None - if can_pin_messages: + if permissions.can_pin_messages: pin_messages = None r = self.send( diff --git a/pyrogram/client/methods/chats/set_chat_photo.py b/pyrogram/client/methods/chats/set_chat_photo.py index 2baa29fe..71cd6590 100644 --- a/pyrogram/client/methods/chats/set_chat_photo.py +++ b/pyrogram/client/methods/chats/set_chat_photo.py @@ -17,12 +17,11 @@ # along with Pyrogram. If not, see . import os -from base64 import b64decode from struct import unpack from typing import Union from pyrogram.api import functions, types -from ...ext import BaseClient +from ...ext import BaseClient, utils class SetChatPhoto(BaseClient): @@ -32,38 +31,43 @@ class SetChatPhoto(BaseClient): photo: str ) -> bool: """Set a new profile photo for the chat. - Photos can't be changed for private chats. - You must be an administrator in the chat for this to work and must have the appropriate admin rights. - Note: - In regular groups (non-supergroups), this method will only work if the "All Members Are Admins" - setting is off. + You must be an administrator in the chat for this to work and must have the appropriate admin rights. Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. photo (``str``): - New chat photo. You can pass a :obj:`Photo` id or a file path to upload a new photo. + New chat photo. You can pass a :obj:`Photo` file_id or a file path to upload a new photo from your local + machine. Returns: ``bool``: True on success. Raises: - RPCError: In case of a Telegram RPC error. ValueError: if a chat_id belongs to user. + + Example: + .. code-block:: python + + # Set chat photo using a local file + app.set_chat_photo(chat_id, "photo.jpg") + + # Set chat photo using an exiting Photo file_id + app.set_chat_photo(chat_id, photo.file_id) """ peer = self.resolve_peer(chat_id) if os.path.exists(photo): photo = types.InputChatUploadedPhoto(file=self.save_file(photo)) else: - s = unpack(" List["pyrogram.User"]: - # TODO: Create a Users object and return that """Get contacts from your Telegram address book. Returns: List of :obj:`User`: On success, a list of users is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + contacts = app.get_contacts() + print(contacts) """ while True: try: diff --git a/pyrogram/client/methods/contacts/get_contacts_count.py b/pyrogram/client/methods/contacts/get_contacts_count.py index dddfe8c4..8e23d698 100644 --- a/pyrogram/client/methods/contacts/get_contacts_count.py +++ b/pyrogram/client/methods/contacts/get_contacts_count.py @@ -27,8 +27,11 @@ class GetContactsCount(BaseClient): Returns: ``int``: On success, the contacts count is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + count = app.get_contacts_count() + print(count) """ return len(self.send(functions.contacts.GetContacts(hash=0)).contacts) diff --git a/pyrogram/client/methods/decorators/on_callback_query.py b/pyrogram/client/methods/decorators/on_callback_query.py index 1706d71a..1b7e2bcb 100644 --- a/pyrogram/client/methods/decorators/on_callback_query.py +++ b/pyrogram/client/methods/decorators/on_callback_query.py @@ -47,7 +47,7 @@ class OnCallbackQuery(BaseClient): if isinstance(self, pyrogram.Client): self.add_handler(pyrogram.CallbackQueryHandler(func, filters), group) elif isinstance(self, Filter) or self is None: - func.pyrogram_plugin = ( + func.handler = ( pyrogram.CallbackQueryHandler(func, self), group if filters is None else filters ) diff --git a/pyrogram/client/methods/decorators/on_deleted_messages.py b/pyrogram/client/methods/decorators/on_deleted_messages.py index 86dda587..cf31ac87 100644 --- a/pyrogram/client/methods/decorators/on_deleted_messages.py +++ b/pyrogram/client/methods/decorators/on_deleted_messages.py @@ -47,7 +47,7 @@ class OnDeletedMessages(BaseClient): if isinstance(self, pyrogram.Client): self.add_handler(pyrogram.DeletedMessagesHandler(func, filters), group) elif isinstance(self, Filter) or self is None: - func.pyrogram_plugin = ( + func.handler = ( pyrogram.DeletedMessagesHandler(func, self), group if filters is None else filters ) diff --git a/pyrogram/client/methods/decorators/on_inline_query.py b/pyrogram/client/methods/decorators/on_inline_query.py index d0f2925b..a84b7ca9 100644 --- a/pyrogram/client/methods/decorators/on_inline_query.py +++ b/pyrogram/client/methods/decorators/on_inline_query.py @@ -46,7 +46,7 @@ class OnInlineQuery(BaseClient): if isinstance(self, pyrogram.Client): self.add_handler(pyrogram.InlineQueryHandler(func, filters), group) elif isinstance(self, Filter) or self is None: - func.pyrogram_plugin = ( + func.handler = ( pyrogram.InlineQueryHandler(func, self), group if filters is None else filters ) diff --git a/pyrogram/client/methods/decorators/on_message.py b/pyrogram/client/methods/decorators/on_message.py index 5640f22c..0166541c 100644 --- a/pyrogram/client/methods/decorators/on_message.py +++ b/pyrogram/client/methods/decorators/on_message.py @@ -46,7 +46,7 @@ class OnMessage(BaseClient): if isinstance(self, pyrogram.Client): self.add_handler(pyrogram.MessageHandler(func, filters), group) elif isinstance(self, Filter) or self is None: - func.pyrogram_plugin = ( + func.handler = ( pyrogram.MessageHandler(func, self), group if filters is None else filters ) diff --git a/pyrogram/client/methods/decorators/on_poll.py b/pyrogram/client/methods/decorators/on_poll.py index 24282f28..c797c8c6 100644 --- a/pyrogram/client/methods/decorators/on_poll.py +++ b/pyrogram/client/methods/decorators/on_poll.py @@ -46,7 +46,7 @@ class OnPoll(BaseClient): if isinstance(self, pyrogram.Client): self.add_handler(pyrogram.PollHandler(func, filters), group) elif isinstance(self, Filter) or self is None: - func.pyrogram_plugin = ( + func.handler = ( pyrogram.PollHandler(func, self), group if filters is None else filters ) diff --git a/pyrogram/client/methods/decorators/on_raw_update.py b/pyrogram/client/methods/decorators/on_raw_update.py index bbf40c8b..f56de6f9 100644 --- a/pyrogram/client/methods/decorators/on_raw_update.py +++ b/pyrogram/client/methods/decorators/on_raw_update.py @@ -40,7 +40,7 @@ class OnRawUpdate(BaseClient): if isinstance(self, pyrogram.Client): self.add_handler(pyrogram.RawUpdateHandler(func), group) else: - func.pyrogram_plugin = ( + func.handler = ( pyrogram.RawUpdateHandler(func), group if self is None else group ) diff --git a/pyrogram/client/methods/decorators/on_user_status.py b/pyrogram/client/methods/decorators/on_user_status.py index 81a83d02..02ed9e7b 100644 --- a/pyrogram/client/methods/decorators/on_user_status.py +++ b/pyrogram/client/methods/decorators/on_user_status.py @@ -44,7 +44,7 @@ class OnUserStatus(BaseClient): if isinstance(self, pyrogram.Client): self.add_handler(pyrogram.UserStatusHandler(func, filters), group) elif isinstance(self, Filter) or self is None: - func.pyrogram_plugin = ( + func.handler = ( pyrogram.UserStatusHandler(func, self), group if filters is None else filters ) diff --git a/pyrogram/client/methods/messages/__init__.py b/pyrogram/client/methods/messages/__init__.py index aa0b0c94..6237b47c 100644 --- a/pyrogram/client/methods/messages/__init__.py +++ b/pyrogram/client/methods/messages/__init__.py @@ -33,7 +33,6 @@ from .get_messages import GetMessages from .iter_history import IterHistory from .read_history import ReadHistory from .retract_vote import RetractVote -from .send_animated_sticker import SendAnimatedSticker from .send_animation import SendAnimation from .send_audio import SendAudio from .send_cached_media import SendCachedMedia @@ -85,7 +84,6 @@ class Messages( IterHistory, SendCachedMedia, GetHistoryCount, - SendAnimatedSticker, ReadHistory, EditInlineText, EditInlineCaption, diff --git a/pyrogram/client/methods/messages/delete_messages.py b/pyrogram/client/methods/messages/delete_messages.py index 3667c8ee..f0c4d991 100644 --- a/pyrogram/client/methods/messages/delete_messages.py +++ b/pyrogram/client/methods/messages/delete_messages.py @@ -50,8 +50,17 @@ class DeleteMessages(BaseClient): Returns: ``bool``: True on success, False otherwise. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Delete one message + app.delete_messages(chat_id, message_id) + + # Delete multiple messages at once + app.delete_messages(chat_id, list_of_message_ids) + + # Delete messages only on your side (without revoking) + app.delete_messages(chat_id, message_id, revoke=False) """ peer = self.resolve_peer(chat_id) message_ids = list(message_ids) if not isinstance(message_ids, int) else [message_ids] diff --git a/pyrogram/client/methods/messages/download_media.py b/pyrogram/client/methods/messages/download_media.py index cc0e54d2..b00b7c72 100644 --- a/pyrogram/client/methods/messages/download_media.py +++ b/pyrogram/client/methods/messages/download_media.py @@ -57,24 +57,23 @@ class DownloadMedia(BaseClient): Blocks the code execution until the file has been downloaded. Defaults to True. - progress (``callable``): - Pass a callback function to view the download progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). + progress (``callable``, *optional*): + Pass a callback function to view the file transmission progress. + The function must take *(current, total)* as positional arguments (look at Other Parameters below for a + detailed description) and will be called back each time a new file chunk has been successfully + transmitted. - 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. + progress_args (``tuple``, *optional*): + Extra custom arguments for the progress callback function. + You can pass anything you need to be available in the progress callback scope; for example, a Message + object or a Client instance in order to edit the message with the updated progress status. Other Parameters: - client (:obj:`Client`): - The Client itself, useful when you want to call other API methods inside the callback function. - current (``int``): - The amount of bytes downloaded so far. + The amount of bytes transmitted so far. total (``int``): - The size of the file. + The total size of the file. *args (``tuple``, *optional*): Extra custom arguments as defined in the *progress_args* parameter. @@ -85,8 +84,16 @@ class DownloadMedia(BaseClient): the download failed or was deliberately stopped with :meth:`~Client.stop_transmission`, None is returned. Raises: - RPCError: In case of a Telegram RPC error. ValueError: if the message doesn't contain any downloadable media + + Example: + .. code-block:: python + + # Download from Message + app.download_media(message) + + # Download from file id + app.download_media("CAADBAADyg4AAvLQYAEYD4F7vcZ43AI") """ error_message = "This message doesn't contain any downloadable media" available_media = ("audio", "document", "photo", "sticker", "animation", "video", "voice", "video_note") @@ -207,7 +214,8 @@ class DownloadMedia(BaseClient): extension ) - self.download_queue.put((data, directory, file_name, done, progress, progress_args, path)) + # Cast to string because Path objects aren't supported by Python 3.5 + self.download_queue.put((data, str(directory), str(file_name), done, progress, progress_args, path)) if block: done.wait() diff --git a/pyrogram/client/methods/messages/edit_inline_caption.py b/pyrogram/client/methods/messages/edit_inline_caption.py index 2d904198..57a0ac75 100644 --- a/pyrogram/client/methods/messages/edit_inline_caption.py +++ b/pyrogram/client/methods/messages/edit_inline_caption.py @@ -16,6 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Union + import pyrogram from pyrogram.client.ext import BaseClient @@ -25,10 +27,10 @@ class EditInlineCaption(BaseClient): self, inline_message_id: str, caption: str, - parse_mode: str = "", + parse_mode: Union[str, None] = object, reply_markup: "pyrogram.InlineKeyboardMarkup" = None ) -> bool: - """Edit the caption of **inline** media messages. + """Edit the caption of inline media messages. Parameters: inline_message_id (``str``): @@ -50,8 +52,11 @@ class EditInlineCaption(BaseClient): Returns: ``bool``: On success, True is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Bots only + app.edit_inline_caption(inline_message_id, "new media caption") """ return self.edit_inline_text( inline_message_id=inline_message_id, diff --git a/pyrogram/client/methods/messages/edit_inline_media.py b/pyrogram/client/methods/messages/edit_inline_media.py index 0ed89d17..7a82f3a8 100644 --- a/pyrogram/client/methods/messages/edit_inline_media.py +++ b/pyrogram/client/methods/messages/edit_inline_media.py @@ -33,7 +33,7 @@ class EditInlineMedia(BaseClient): media: InputMedia, reply_markup: "pyrogram.InlineKeyboardMarkup" = None ) -> bool: - """Edit **inline** animation, audio, document, photo or video messages. + """Edit inline animation, audio, document, photo or video messages. When the inline message is edited, a new file can't be uploaded. Use a previously uploaded file via its file_id or specify a URL. @@ -52,8 +52,21 @@ class EditInlineMedia(BaseClient): Returns: ``bool``: On success, True is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + from pyrogram import InputMediaPhoto, InputMediaVideo, InputMediaAudio + + # Bots only + + # Replace the current media with a local photo + app.edit_inline_media(inline_message_id, InputMediaPhoto("new_photo.jpg")) + + # Replace the current media with a local video + app.edit_inline_media(inline_message_id, InputMediaVideo("new_video.mp4")) + + # Replace the current media with a local audio + app.edit_inline_media(inline_message_id, InputMediaAudio("new_audio.mp3")) """ caption = media.caption parse_mode = media.parse_mode diff --git a/pyrogram/client/methods/messages/edit_inline_reply_markup.py b/pyrogram/client/methods/messages/edit_inline_reply_markup.py index 0326ed72..aae64898 100644 --- a/pyrogram/client/methods/messages/edit_inline_reply_markup.py +++ b/pyrogram/client/methods/messages/edit_inline_reply_markup.py @@ -27,7 +27,7 @@ class EditInlineReplyMarkup(BaseClient): inline_message_id: str, reply_markup: "pyrogram.InlineKeyboardMarkup" = None ) -> bool: - """Edit only the reply markup of **inline** messages sent via the bot (for inline bots). + """Edit only the reply markup of inline messages sent via the bot (for inline bots). Parameters: inline_message_id (``str``): @@ -39,8 +39,16 @@ class EditInlineReplyMarkup(BaseClient): Returns: ``bool``: On success, True is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + from pyrogram import InlineKeyboardMarkup, InlineKeyboardButton + + # Bots only + app.edit_inline_reply_markup( + inline_message_id, + InlineKeyboardMarkup([[ + InlineKeyboardButton("New button", callback_data="new_data")]])) """ return self.send( functions.messages.EditInlineBotMessage( diff --git a/pyrogram/client/methods/messages/edit_inline_text.py b/pyrogram/client/methods/messages/edit_inline_text.py index 0d17b4a4..c92e13a1 100644 --- a/pyrogram/client/methods/messages/edit_inline_text.py +++ b/pyrogram/client/methods/messages/edit_inline_text.py @@ -28,11 +28,11 @@ class EditInlineText(BaseClient): self, inline_message_id: str, text: str, - parse_mode: Union[str, None] = "", + parse_mode: Union[str, None] = object, disable_web_page_preview: bool = None, reply_markup: "pyrogram.InlineKeyboardMarkup" = None ) -> bool: - """Edit the text of **inline** messages. + """Edit the text of inline messages. Parameters: inline_message_id (``str``): @@ -57,8 +57,18 @@ class EditInlineText(BaseClient): Returns: ``bool``: On success, True is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Bots only + + # Simple edit text + app.edit_inline_text(inline_message_id, "new text") + + # Take the same text message, remove the web page preview only + app.edit_inline_text( + inline_message_id, message.text, + disable_web_page_preview=True) """ return self.send( diff --git a/pyrogram/client/methods/messages/edit_message_caption.py b/pyrogram/client/methods/messages/edit_message_caption.py index 6fefe0b2..eae59c62 100644 --- a/pyrogram/client/methods/messages/edit_message_caption.py +++ b/pyrogram/client/methods/messages/edit_message_caption.py @@ -28,7 +28,7 @@ class EditMessageCaption(BaseClient): chat_id: Union[int, str], message_id: int, caption: str, - parse_mode: str = "", + parse_mode: Union[str, None] = object, reply_markup: "pyrogram.InlineKeyboardMarkup" = None ) -> "pyrogram.Message": """Edit the caption of media messages. @@ -58,8 +58,10 @@ class EditMessageCaption(BaseClient): Returns: :obj:`Message`: On success, the edited message is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + app.edit_message_caption(chat_id, message_id, "new media caption") """ return self.edit_message_text( chat_id=chat_id, diff --git a/pyrogram/client/methods/messages/edit_message_media.py b/pyrogram/client/methods/messages/edit_message_media.py index 72077710..f543af2b 100644 --- a/pyrogram/client/methods/messages/edit_message_media.py +++ b/pyrogram/client/methods/messages/edit_message_media.py @@ -60,8 +60,19 @@ class EditMessageMedia(BaseClient): Returns: :obj:`Message`: On success, the edited message is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + from pyrogram import InputMediaPhoto, InputMediaVideo, InputMediaAudio + + # Replace the current media with a local photo + app.edit_message_media(chat_id, message_id, InputMediaPhoto("new_photo.jpg")) + + # Replace the current media with a local video + app.edit_message_media(chat_id, message_id, InputMediaVideo("new_video.mp4")) + + # Replace the current media with a local audio + app.edit_message_media(chat_id, message_id, InputMediaAudio("new_audio.mp3")) """ caption = media.caption parse_mode = media.parse_mode diff --git a/pyrogram/client/methods/messages/edit_message_reply_markup.py b/pyrogram/client/methods/messages/edit_message_reply_markup.py index 51b77a6a..737fc23b 100644 --- a/pyrogram/client/methods/messages/edit_message_reply_markup.py +++ b/pyrogram/client/methods/messages/edit_message_reply_markup.py @@ -47,8 +47,16 @@ class EditMessageReplyMarkup(BaseClient): Returns: :obj:`Message`: On success, the edited message is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + from pyrogram import InlineKeyboardMarkup, InlineKeyboardButton + + # Bots only + app.edit_message_reply_markup( + chat_id, message_id, + InlineKeyboardMarkup([[ + InlineKeyboardButton("New button", callback_data="new_data")]])) """ r = self.send( functions.messages.EditMessage( diff --git a/pyrogram/client/methods/messages/edit_message_text.py b/pyrogram/client/methods/messages/edit_message_text.py index c81139af..31022c0e 100644 --- a/pyrogram/client/methods/messages/edit_message_text.py +++ b/pyrogram/client/methods/messages/edit_message_text.py @@ -29,7 +29,7 @@ class EditMessageText(BaseClient): chat_id: Union[int, str], message_id: int, text: str, - parse_mode: Union[str, None] = "", + parse_mode: Union[str, None] = object, disable_web_page_preview: bool = None, reply_markup: "pyrogram.InlineKeyboardMarkup" = None ) -> "pyrogram.Message": @@ -63,8 +63,16 @@ class EditMessageText(BaseClient): Returns: :obj:`Message`: On success, the edited message is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Simple edit text + app.edit_message_text(chat_id, message_id, "new text") + + # Take the same text message, remove the web page preview only + app.edit_message_text( + chat_id, message_id, message.text, + disable_web_page_preview=True) """ r = self.send( diff --git a/pyrogram/client/methods/messages/forward_messages.py b/pyrogram/client/methods/messages/forward_messages.py index c69df608..ba74e373 100644 --- a/pyrogram/client/methods/messages/forward_messages.py +++ b/pyrogram/client/methods/messages/forward_messages.py @@ -55,7 +55,8 @@ class ForwardMessages(BaseClient): Users will receive a notification with no sound. as_copy (``bool``, *optional*): - Pass True to forward messages without the forward header (i.e.: send a copy of the message content). + Pass True to forward messages without the forward header (i.e.: send a copy of the message content so + that it appears as originally sent by you). Defaults to False. remove_caption (``bool``, *optional*): @@ -68,8 +69,18 @@ class ForwardMessages(BaseClient): is returned, otherwise, in case *message_ids* was an iterable, the returned value will be a list of messages, even if such iterable contained just a single element. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + :emphasize-lines: 2,5,8 + + # Forward a single message + app.forward_messages("me", "pyrogram", 20) + + # Forward multiple messages at once + app.forward_messages("me", "pyrogram", [3, 20, 27]) + + # Forward messages as copy + app.forward_messages("me", "pyrogram", 20, as_copy=True) """ is_iterable = not isinstance(message_ids, int) diff --git a/pyrogram/client/methods/messages/get_history.py b/pyrogram/client/methods/messages/get_history.py index 8adafe22..e471c6fd 100644 --- a/pyrogram/client/methods/messages/get_history.py +++ b/pyrogram/client/methods/messages/get_history.py @@ -70,8 +70,17 @@ class GetHistory(BaseClient): Returns: List of :obj:`Message` - On success, a list of the retrieved messages is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Get the last 100 messages of a chat + app.get_history("pyrogramchat") + + # Get the last 3 messages of a chat + app.get_history("pyrogramchat", limit=3) + + # Get 3 messages after skipping the first 5 + app.get_history("pyrogramchat", offset=5, limit=3) """ offset_id = offset_id or (1 if reverse else 0) diff --git a/pyrogram/client/methods/messages/get_history_count.py b/pyrogram/client/methods/messages/get_history_count.py index 9f3e2637..8ceba0ed 100644 --- a/pyrogram/client/methods/messages/get_history_count.py +++ b/pyrogram/client/methods/messages/get_history_count.py @@ -45,8 +45,10 @@ class GetHistoryCount(BaseClient): Returns: ``int``: On success, the chat history count is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + app.get_history_count("pyrogramchat") """ r = self.send( diff --git a/pyrogram/client/methods/messages/get_messages.py b/pyrogram/client/methods/messages/get_messages.py index 0f901174..8f547227 100644 --- a/pyrogram/client/methods/messages/get_messages.py +++ b/pyrogram/client/methods/messages/get_messages.py @@ -28,6 +28,9 @@ from ...ext import BaseClient, utils log = logging.getLogger(__name__) +# TODO: Rewrite using a flag for replied messages and have message_ids non-optional + + class GetMessages(BaseClient): def get_messages( self, @@ -36,7 +39,8 @@ class GetMessages(BaseClient): reply_to_message_ids: Union[int, Iterable[int]] = None, replies: int = 1 ) -> Union["pyrogram.Message", List["pyrogram.Message"]]: - """Get one or more messages that belong to a specific chat. + """Get one or more messages from a chat by using message identifiers. + You can retrieve up to 200 messages at once. Parameters: @@ -64,8 +68,26 @@ class GetMessages(BaseClient): returned, otherwise, in case *message_ids* was an iterable, the returned value will be a list of messages, even if such iterable contained just a single element. + Example: + .. code-block:: python + + # Get one message + app.get_messages("pyrogramchat", 51110) + + # Get more than one message (list of messages) + app.get_messages("pyrogramchat", [44625, 51110]) + + # Get message by ignoring any replied-to message + app.get_messages(chat_id, message_id, replies=0) + + # Get message with all chained replied-to messages + app.get_messages(chat_id, message_id, replies=-1) + + # Get the replied-to message of a message + app.get_messages(chat_id, reply_to_message_ids=message_id) + Raises: - RPCError: In case of a Telegram RPC error. + ValueError: In case of invalid arguments. """ ids, ids_type = ( (message_ids, types.InputMessageID) if message_ids @@ -74,7 +96,7 @@ class GetMessages(BaseClient): ) if ids is None: - raise ValueError("No argument supplied") + raise ValueError("No argument supplied. Either pass message_ids or reply_to_message_ids") peer = self.resolve_peer(chat_id) diff --git a/pyrogram/client/methods/messages/iter_history.py b/pyrogram/client/methods/messages/iter_history.py index 15c48c95..735ed162 100644 --- a/pyrogram/client/methods/messages/iter_history.py +++ b/pyrogram/client/methods/messages/iter_history.py @@ -64,8 +64,11 @@ class IterHistory(BaseClient): Returns: ``Generator``: A generator yielding :obj:`Message` objects. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + for message in app.iter_history("pyrogram"): + print(message.text) """ offset_id = offset_id or (1 if reverse else 0) current = 0 diff --git a/pyrogram/client/methods/messages/read_history.py b/pyrogram/client/methods/messages/read_history.py index f0278e91..f5dc8630 100644 --- a/pyrogram/client/methods/messages/read_history.py +++ b/pyrogram/client/methods/messages/read_history.py @@ -43,8 +43,14 @@ class ReadHistory(BaseClient): Returns: ``bool`` - On success, True is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Mark the whole chat as read + app.read_history("pyrogramlounge") + + # Mark messages as read only up to the given message id + app.read_history("pyrogramlounge", 123456) """ peer = self.resolve_peer(chat_id) diff --git a/pyrogram/client/methods/messages/retract_vote.py b/pyrogram/client/methods/messages/retract_vote.py index b52181a6..a273ad7b 100644 --- a/pyrogram/client/methods/messages/retract_vote.py +++ b/pyrogram/client/methods/messages/retract_vote.py @@ -43,8 +43,10 @@ class RetractVote(BaseClient): Returns: :obj:`Poll`: On success, the poll with the retracted vote is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + app.retract_vote(chat_id, message_id) """ r = self.send( functions.messages.SendVote( diff --git a/pyrogram/client/methods/messages/send_animated_sticker.py b/pyrogram/client/methods/messages/send_animated_sticker.py deleted file mode 100644 index 6fd0c647..00000000 --- a/pyrogram/client/methods/messages/send_animated_sticker.py +++ /dev/null @@ -1,141 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2019 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 typing import Union - -import pyrogram -from pyrogram.api import functions, types -from pyrogram.client.ext import BaseClient, utils -from pyrogram.errors import FilePartMissing - - -class SendAnimatedSticker(BaseClient): - def send_animated_sticker( - self, - chat_id: Union[int, str], - animated_sticker: str, - disable_notification: bool = None, - reply_to_message_id: int = None, - reply_markup: Union[ - "pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply" - ] = None, - progress: callable = None, - progress_args: tuple = () - ) -> Union["pyrogram.Message", None]: - """Send .tgs animated stickers. - - Parameters: - 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). - - animated_sticker (``str``): - Animated sticker to send. - Pass a file_id as string to send a animated sticker that exists on the Telegram servers, - pass an HTTP URL as a string for Telegram to get a .webp animated sticker file from the Internet, or - pass a file path as string to upload a new animated sticker that exists on your local machine. - - disable_notification (``bool``, *optional*): - Sends the message silently. - Users will receive a notification with no sound. - - reply_to_message_id (``int``, *optional*): - If the message is a reply, ID of the original message. - - reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): - Additional interface options. An object for an inline keyboard, custom reply keyboard, - instructions to remove reply keyboard or to force a reply from the user. - - progress (``callable``, *optional*): - Pass a callback function to view the upload progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). - - progress_args (``tuple``, *optional*): - Extra custom arguments for the progress callback function. Useful, for example, if you want to pass - a chat_id and a message_id in order to edit a message with the updated progress. - - Other Parameters: - client (:obj:`Client`): - The Client itself, useful when you want to call other API methods inside the callback function. - - current (``int``): - The amount of bytes uploaded so far. - - total (``int``): - The size of the file. - - *args (``tuple``, *optional*): - 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: - :obj:`Message` | ``None``: On success, the sent animated sticker message is returned, otherwise, in case the - upload is deliberately stopped with :meth:`~Client.stop_transmission`, None is returned. - Raises: - RPCError: In case of a Telegram RPC error. - """ - file = None - - try: - if os.path.exists(animated_sticker): - file = self.save_file(animated_sticker, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(animated_sticker) or "application/x-tgsticker", - file=file, - attributes=[ - types.DocumentAttributeFilename(file_name=os.path.basename(animated_sticker)) - ] - ) - elif animated_sticker.startswith("http"): - media = types.InputMediaDocumentExternal( - url=animated_sticker - ) - else: - media = utils.get_input_media_from_file_id(animated_sticker, 5) - - while True: - try: - r = self.send( - functions.messages.SendMedia( - peer=self.resolve_peer(chat_id), - media=media, - 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, - message="" - ) - ) - except FilePartMissing as e: - self.save_file(animated_sticker, file_id=file.id, file_part=e.x) - else: - for i in r.updates: - if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return pyrogram.Message._parse( - self, i.message, - {i.id: i for i in r.users}, - {i.id: i for i in r.chats} - ) - except BaseClient.StopTransmission: - return None diff --git a/pyrogram/client/methods/messages/send_animation.py b/pyrogram/client/methods/messages/send_animation.py index 34389149..5d345010 100644 --- a/pyrogram/client/methods/messages/send_animation.py +++ b/pyrogram/client/methods/messages/send_animation.py @@ -32,7 +32,7 @@ class SendAnimation(BaseClient): animation: str, caption: str = "", unsave: bool = False, - parse_mode: Union[str, None] = "", + parse_mode: Union[str, None] = object, duration: int = 0, width: int = 0, height: int = 0, @@ -66,7 +66,7 @@ class SendAnimation(BaseClient): Animation caption, 0-1024 characters. unsave (``bool``, *optional*): - By default, the server will save into your own collection any new animation GIF you send. + By default, the server will save into your own collection any new animation you send. Pass True to automatically unsave the sent animation. Defaults to False. parse_mode (``str``, *optional*): @@ -103,23 +103,22 @@ class SendAnimation(BaseClient): instructions to remove reply keyboard or to force a reply from the user. progress (``callable``, *optional*): - Pass a callback function to view the upload progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). + Pass a callback function to view the file transmission progress. + The function must take *(current, total)* as positional arguments (look at Other Parameters below for a + detailed description) and will be called back each time a new file chunk has been successfully + transmitted. progress_args (``tuple``, *optional*): - Extra custom arguments for the progress callback function. Useful, for example, if you want to pass - a chat_id and a message_id in order to edit a message with the updated progress. + Extra custom arguments for the progress callback function. + You can pass anything you need to be available in the progress callback scope; for example, a Message + object or a Client instance in order to edit the message with the updated progress status. 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. + The amount of bytes transmitted so far. total (``int``): - The size of the file. + The total size of the file. *args (``tuple``, *optional*): Extra custom arguments as defined in the *progress_args* parameter. @@ -129,8 +128,23 @@ class SendAnimation(BaseClient): :obj:`Message` | ``None``: On success, the sent animation message is returned, otherwise, in case the upload is deliberately stopped with :meth:`~Client.stop_transmission`, None is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Send animation by uploading from local file + app.send_animation("me", "animation.gif") + + # Add caption to the animation + app.send_animation("me", "animation.gif", caption="cat") + + # Unsave the animation once is sent + app.send_animation("me", "animation.gif", unsave=True) + + # Keep track of the progress while uploading + def progress(current, total): + print("{:.1f}%".format(current * 100 / total)) + + app.send_animation("me", "animation.gif", progress=progress) """ file = None diff --git a/pyrogram/client/methods/messages/send_audio.py b/pyrogram/client/methods/messages/send_audio.py index 43c5a63e..7395718b 100644 --- a/pyrogram/client/methods/messages/send_audio.py +++ b/pyrogram/client/methods/messages/send_audio.py @@ -31,7 +31,7 @@ class SendAudio(BaseClient): chat_id: Union[int, str], audio: str, caption: str = "", - parse_mode: Union[str, None] = "", + parse_mode: Union[str, None] = object, duration: int = 0, performer: str = None, title: str = None, @@ -100,23 +100,22 @@ class SendAudio(BaseClient): instructions to remove reply keyboard or to force a reply from the user. progress (``callable``, *optional*): - Pass a callback function to view the upload progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). + Pass a callback function to view the file transmission progress. + The function must take *(current, total)* as positional arguments (look at Other Parameters below for a + detailed description) and will be called back each time a new file chunk has been successfully + transmitted. progress_args (``tuple``, *optional*): - Extra custom arguments for the progress callback function. Useful, for example, if you want to pass - a chat_id and a message_id in order to edit a message with the updated progress. + Extra custom arguments for the progress callback function. + You can pass anything you need to be available in the progress callback scope; for example, a Message + object or a Client instance in order to edit the message with the updated progress status. 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. + The amount of bytes transmitted so far. total (``int``): - The size of the file. + The total size of the file. *args (``tuple``, *optional*): Extra custom arguments as defined in the *progress_args* parameter. @@ -126,8 +125,26 @@ class SendAudio(BaseClient): :obj:`Message` | ``None``: On success, the sent audio message is returned, otherwise, in case the upload is deliberately stopped with :meth:`~Client.stop_transmission`, None is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + :emphasize-lines: 2,5,8-10,13-16 + + # Send audio file by uploading from file + app.send_audio("me", "audio.mp3") + + # Add caption to the audio + app.send_audio("me", "audio.mp3", caption="shoegaze") + + # Set audio metadata + app.send_audio( + "me", "audio.mp3", + title="Printemps émeraude", performer="Alcest", duration=440) + + # Keep track of the progress while uploading + def progress(current, total): + print("{:.1f}%".format(current * 100 / total)) + + app.send_audio("me", "audio.mp3", progress=progress) """ file = None diff --git a/pyrogram/client/methods/messages/send_cached_media.py b/pyrogram/client/methods/messages/send_cached_media.py index 0f2e1389..9b4fbafa 100644 --- a/pyrogram/client/methods/messages/send_cached_media.py +++ b/pyrogram/client/methods/messages/send_cached_media.py @@ -29,7 +29,7 @@ class SendCachedMedia(BaseClient): chat_id: Union[int, str], file_id: str, caption: str = "", - parse_mode: Union[str, None] = "", + parse_mode: Union[str, None] = object, disable_notification: bool = None, reply_to_message_id: int = None, reply_markup: Union[ @@ -79,8 +79,10 @@ class SendCachedMedia(BaseClient): Returns: :obj:`Message`: On success, the sent media message is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + app.send_cached_media("me", "CAADBAADyg4AAvLQYAEYD4F7vcZ43AI") """ r = self.send( diff --git a/pyrogram/client/methods/messages/send_chat_action.py b/pyrogram/client/methods/messages/send_chat_action.py index da974c97..7488fb16 100644 --- a/pyrogram/client/methods/messages/send_chat_action.py +++ b/pyrogram/client/methods/messages/send_chat_action.py @@ -64,8 +64,22 @@ class SendChatAction(BaseClient): ``bool``: On success, True is returned. Raises: - RPCError: In case of a Telegram RPC error. - ValueError: In case the provided string is not a valid ChatAction. + ValueError: In case the provided string is not a valid chat action. + + Example: + .. code-block:: python + + # Send "typing" chat action + app.send_chat_action(chat_id, "typing") + + # Send "upload_video" chat action + app.send_chat_action(chat_id, "upload_video") + + # Send "playing" chat action + app.send_chat_action(chat_id, "playing") + + # Cancel any current chat action + app.send_chat_action(chat_id, "cancel") """ try: diff --git a/pyrogram/client/methods/messages/send_contact.py b/pyrogram/client/methods/messages/send_contact.py index d0b6fb58..c32ca25d 100644 --- a/pyrogram/client/methods/messages/send_contact.py +++ b/pyrogram/client/methods/messages/send_contact.py @@ -74,8 +74,10 @@ class SendContact(BaseClient): Returns: :obj:`Message`: On success, the sent contact message is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + app.send_contact("me", "+39 123 456 7890", "Dan") """ r = self.send( functions.messages.SendMedia( diff --git a/pyrogram/client/methods/messages/send_document.py b/pyrogram/client/methods/messages/send_document.py index fcaf5f51..567bc561 100644 --- a/pyrogram/client/methods/messages/send_document.py +++ b/pyrogram/client/methods/messages/send_document.py @@ -32,7 +32,7 @@ class SendDocument(BaseClient): document: str, thumb: str = None, caption: str = "", - parse_mode: Union[str, None] = "", + parse_mode: Union[str, None] = object, disable_notification: bool = None, reply_to_message_id: int = None, reply_markup: Union[ @@ -86,23 +86,22 @@ class SendDocument(BaseClient): instructions to remove reply keyboard or to force a reply from the user. progress (``callable``, *optional*): - Pass a callback function to view the upload progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). + Pass a callback function to view the file transmission progress. + The function must take *(current, total)* as positional arguments (look at Other Parameters below for a + detailed description) and will be called back each time a new file chunk has been successfully + transmitted. progress_args (``tuple``, *optional*): - Extra custom arguments for the progress callback function. Useful, for example, if you want to pass - a chat_id and a message_id in order to edit a message with the updated progress. + Extra custom arguments for the progress callback function. + You can pass anything you need to be available in the progress callback scope; for example, a Message + object or a Client instance in order to edit the message with the updated progress status. 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. + The amount of bytes transmitted so far. total (``int``): - The size of the file. + The total size of the file. *args (``tuple``, *optional*): Extra custom arguments as defined in the *progress_args* parameter. @@ -112,8 +111,20 @@ class SendDocument(BaseClient): :obj:`Message` | ``None``: On success, the sent document message is returned, otherwise, in case the upload is deliberately stopped with :meth:`~Client.stop_transmission`, None is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Send document by uploading from local file + app.send_document("me", "document.zip") + + # Add caption to the document file + app.send_document("me", "document.zip", caption="archive") + + # Keep track of the progress while uploading + def progress(current, total): + print("{:.1f}%".format(current * 100 / total)) + + app.send_document("me", "document.zip", progress=progress) """ file = None diff --git a/pyrogram/client/methods/messages/send_location.py b/pyrogram/client/methods/messages/send_location.py index 2e3681e6..245f61f2 100644 --- a/pyrogram/client/methods/messages/send_location.py +++ b/pyrogram/client/methods/messages/send_location.py @@ -66,8 +66,10 @@ class SendLocation(BaseClient): Returns: :obj:`Message`: On success, the sent location message is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + app.send_location("me", 51.500729, -0.124583) """ r = self.send( functions.messages.SendMedia( diff --git a/pyrogram/client/methods/messages/send_media_group.py b/pyrogram/client/methods/messages/send_media_group.py index 681e1850..ac38c0d6 100644 --- a/pyrogram/client/methods/messages/send_media_group.py +++ b/pyrogram/client/methods/messages/send_media_group.py @@ -31,7 +31,6 @@ log = logging.getLogger(__name__) class SendMediaGroup(BaseClient): # TODO: Add progress parameter - # TODO: Figure out how to send albums using URLs def send_media_group( self, chat_id: Union[int, str], @@ -60,8 +59,19 @@ class SendMediaGroup(BaseClient): Returns: List of :obj:`Message`: On success, a list of the sent messages is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + from pyrogram import InputMediaPhoto, InputMediaVideo + + app.send_media_group( + "me", + [ + InputMediaPhoto("photo1.jpg"), + InputMediaPhoto("photo2.jpg", caption="photo caption"), + InputMediaVideo("video.mp4", caption="a video") + ] + ) """ multi_media = [] @@ -88,7 +98,24 @@ class SendMediaGroup(BaseClient): id=types.InputPhoto( id=media.photo.id, access_hash=media.photo.access_hash, - file_reference=b"" + file_reference=media.photo.file_reference + ) + ) + elif i.media.startswith("http"): + media = self.send( + functions.messages.UploadMedia( + peer=self.resolve_peer(chat_id), + media=types.InputMediaPhotoExternal( + url=i.media + ) + ) + ) + + media = types.InputMediaPhoto( + id=types.InputPhoto( + id=media.photo.id, + access_hash=media.photo.access_hash, + file_reference=media.photo.file_reference ) ) else: @@ -126,7 +153,24 @@ class SendMediaGroup(BaseClient): id=types.InputDocument( id=media.document.id, access_hash=media.document.access_hash, - file_reference=b"" + file_reference=media.document.file_reference + ) + ) + elif i.media.startswith("http"): + media = self.send( + functions.messages.UploadMedia( + peer=self.resolve_peer(chat_id), + media=types.InputMediaDocumentExternal( + url=i.media + ) + ) + ) + + media = types.InputMediaDocument( + id=types.InputDocument( + id=media.document.id, + access_hash=media.document.access_hash, + file_reference=media.document.file_reference ) ) else: diff --git a/pyrogram/client/methods/messages/send_message.py b/pyrogram/client/methods/messages/send_message.py index 15b5f25c..c15b3a84 100644 --- a/pyrogram/client/methods/messages/send_message.py +++ b/pyrogram/client/methods/messages/send_message.py @@ -28,7 +28,7 @@ class SendMessage(BaseClient): self, chat_id: Union[int, str], text: str, - parse_mode: Union[str, None] = "", + parse_mode: Union[str, None] = object, disable_web_page_preview: bool = None, disable_notification: bool = None, reply_to_message_id: int = None, @@ -74,9 +74,44 @@ class SendMessage(BaseClient): Returns: :obj:`Message`: On success, the sent text message is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + :emphasize-lines: 2,5,8,11,21-23,26-33 + + # Simple example + app.send_message("haskell", "Thanks for creating **Pyrogram**!") + + # Disable web page previews + app.send_message("me", "https://docs.pyrogram.org", disable_web_page_preview=True) + + # Reply to a message using its id + app.send_message("me", "this is a reply", reply_to_message_id=12345) + + # Force HTML-only styles for this request only + app.send_message("me", "**not bold**, italic", parse_mode="html") + + ## + # For bots only, send messages with keyboards attached + ## + + from pyrogram import ( + ReplyKeyboardMarkup, InlineKeyboardMarkup, InlineKeyboardButton) + + # Send a normal keyboard + app.send_message( + chat_id, "Look at that button!", + reply_markup=ReplyKeyboardMarkup([["Nice!"]])) + + # Send an inline keyboard + app.send_message( + chat_id, "These are inline buttons", + reply_markup=InlineKeyboardMarkup( + [ + [InlineKeyboardButton("Data", callback_data="hidden_callback_data")], + [InlineKeyboardButton("Docs", url="https://docs.pyrogram.org")] + ])) """ + message, entities = self.parser.parse(text, parse_mode).values() r = self.send( diff --git a/pyrogram/client/methods/messages/send_photo.py b/pyrogram/client/methods/messages/send_photo.py index c43c0139..0c82ebfc 100644 --- a/pyrogram/client/methods/messages/send_photo.py +++ b/pyrogram/client/methods/messages/send_photo.py @@ -31,7 +31,7 @@ class SendPhoto(BaseClient): chat_id: Union[int, str], photo: str, caption: str = "", - parse_mode: Union[str, None] = "", + parse_mode: Union[str, None] = object, ttl_seconds: int = None, disable_notification: bool = None, reply_to_message_id: int = None, @@ -85,23 +85,22 @@ class SendPhoto(BaseClient): instructions to remove reply keyboard or to force a reply from the user. progress (``callable``, *optional*): - Pass a callback function to view the upload progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). + Pass a callback function to view the file transmission progress. + The function must take *(current, total)* as positional arguments (look at Other Parameters below for a + detailed description) and will be called back each time a new file chunk has been successfully + transmitted. progress_args (``tuple``, *optional*): - Extra custom arguments for the progress callback function. Useful, for example, if you want to pass - a chat_id and a message_id in order to edit a message with the updated progress. + Extra custom arguments for the progress callback function. + You can pass anything you need to be available in the progress callback scope; for example, a Message + object or a Client instance in order to edit the message with the updated progress status. 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. + The amount of bytes transmitted so far. total (``int``): - The size of the file. + The total size of the file. *args (``tuple``, *optional*): Extra custom arguments as defined in the *progress_args* parameter. @@ -111,8 +110,20 @@ class SendPhoto(BaseClient): :obj:`Message` | ``None``: On success, the sent photo message is returned, otherwise, in case the upload is deliberately stopped with :meth:`~Client.stop_transmission`, None is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Send photo by uploading from local file + app.send_photo("me", "photo.jpg") + + # Send photo by uploading from URL + app.send_photo("me", "https://i.imgur.com/BQBTP7d.png") + + # Add caption to a photo + app.send_photo("me", "photo.jpg", caption="Holidays!") + + # Send self-destructing photo + app.send_photo("me", "photo.jpg", ttl_seconds=10) """ file = None diff --git a/pyrogram/client/methods/messages/send_poll.py b/pyrogram/client/methods/messages/send_poll.py index 4dae53b2..2fa008ab 100644 --- a/pyrogram/client/methods/messages/send_poll.py +++ b/pyrogram/client/methods/messages/send_poll.py @@ -66,8 +66,10 @@ class SendPoll(BaseClient): Returns: :obj:`Message`: On success, the sent poll message is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + app.send_poll(chat_id, "Is this a poll question?", ["Yes", "No", "Maybe"]) """ r = self.send( functions.messages.SendMedia( diff --git a/pyrogram/client/methods/messages/send_sticker.py b/pyrogram/client/methods/messages/send_sticker.py index 4f7a99ff..ae5e8551 100644 --- a/pyrogram/client/methods/messages/send_sticker.py +++ b/pyrogram/client/methods/messages/send_sticker.py @@ -41,7 +41,7 @@ class SendSticker(BaseClient): progress: callable = None, progress_args: tuple = () ) -> Union["pyrogram.Message", None]: - """Send .webp stickers. + """Send static .webp or animated .tgs stickers. Parameters: chat_id (``int`` | ``str``): @@ -67,23 +67,22 @@ class SendSticker(BaseClient): instructions to remove reply keyboard or to force a reply from the user. progress (``callable``, *optional*): - Pass a callback function to view the upload progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). + Pass a callback function to view the file transmission progress. + The function must take *(current, total)* as positional arguments (look at Other Parameters below for a + detailed description) and will be called back each time a new file chunk has been successfully + transmitted. progress_args (``tuple``, *optional*): - Extra custom arguments for the progress callback function. Useful, for example, if you want to pass - a chat_id and a message_id in order to edit a message with the updated progress. + Extra custom arguments for the progress callback function. + You can pass anything you need to be available in the progress callback scope; for example, a Message + object or a Client instance in order to edit the message with the updated progress status. 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. + The amount of bytes transmitted so far. total (``int``): - The size of the file. + The total size of the file. *args (``tuple``, *optional*): Extra custom arguments as defined in the *progress_args* parameter. @@ -92,8 +91,15 @@ class SendSticker(BaseClient): Returns: :obj:`Message` | ``None``: On success, the sent sticker message is returned, otherwise, in case the upload is deliberately stopped with :meth:`~Client.stop_transmission`, None is returned. - Raises: - RPCError: In case of a Telegram RPC error. + + Example: + .. code-block:: python + + # Send sticker by uploading from local file + app.send_sticker("me", "sticker.webp") + + # Send sticker using file_id + app.send_sticker("me", "CAADBAADyg4AAvLQYAEYD4F7vcZ43AI") """ file = None diff --git a/pyrogram/client/methods/messages/send_venue.py b/pyrogram/client/methods/messages/send_venue.py index 35545c9b..ab630936 100644 --- a/pyrogram/client/methods/messages/send_venue.py +++ b/pyrogram/client/methods/messages/send_venue.py @@ -83,8 +83,12 @@ class SendVenue(BaseClient): Returns: :obj:`Message`: On success, the sent venue message is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + app.send_venue( + "me", 51.500729, -0.124583, + "Elizabeth Tower", "Westminster, London SW1A 0AA, UK") """ r = self.send( functions.messages.SendMedia( diff --git a/pyrogram/client/methods/messages/send_video.py b/pyrogram/client/methods/messages/send_video.py index ba69aafb..ca6f0519 100644 --- a/pyrogram/client/methods/messages/send_video.py +++ b/pyrogram/client/methods/messages/send_video.py @@ -31,7 +31,7 @@ class SendVideo(BaseClient): chat_id: Union[int, str], video: str, caption: str = "", - parse_mode: Union[str, None] = "", + parse_mode: Union[str, None] = object, duration: int = 0, width: int = 0, height: int = 0, @@ -103,23 +103,22 @@ class SendVideo(BaseClient): instructions to remove reply keyboard or to force a reply from the user. progress (``callable``, *optional*): - Pass a callback function to view the upload progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). + Pass a callback function to view the file transmission progress. + The function must take *(current, total)* as positional arguments (look at Other Parameters below for a + detailed description) and will be called back each time a new file chunk has been successfully + transmitted. progress_args (``tuple``, *optional*): - Extra custom arguments for the progress callback function. Useful, for example, if you want to pass - a chat_id and a message_id in order to edit a message with the updated progress. + Extra custom arguments for the progress callback function. + You can pass anything you need to be available in the progress callback scope; for example, a Message + object or a Client instance in order to edit the message with the updated progress status. 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. + The amount of bytes transmitted so far. total (``int``): - The size of the file. + The total size of the file. *args (``tuple``, *optional*): Extra custom arguments as defined in the *progress_args* parameter. @@ -129,8 +128,20 @@ class SendVideo(BaseClient): :obj:`Message` | ``None``: On success, the sent video message is returned, otherwise, in case the upload is deliberately stopped with :meth:`~Client.stop_transmission`, None is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Send video by uploading from local file + app.send_video("me", "video.mp4") + + # Add caption to the video + app.send_video("me", "video.mp4", caption="recording") + + # Keep track of the progress while uploading + def progress(current, total): + print("{:.1f}%".format(current * 100 / total)) + + app.send_video("me", "video.mp4", progress=progress) """ file = None diff --git a/pyrogram/client/methods/messages/send_video_note.py b/pyrogram/client/methods/messages/send_video_note.py index da8d53c2..65988b36 100644 --- a/pyrogram/client/methods/messages/send_video_note.py +++ b/pyrogram/client/methods/messages/send_video_note.py @@ -82,23 +82,22 @@ class SendVideoNote(BaseClient): instructions to remove reply keyboard or to force a reply from the user. progress (``callable``, *optional*): - Pass a callback function to view the upload progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). + Pass a callback function to view the file transmission progress. + The function must take *(current, total)* as positional arguments (look at Other Parameters below for a + detailed description) and will be called back each time a new file chunk has been successfully + transmitted. progress_args (``tuple``, *optional*): - Extra custom arguments for the progress callback function. Useful, for example, if you want to pass - a chat_id and a message_id in order to edit a message with the updated progress. + Extra custom arguments for the progress callback function. + You can pass anything you need to be available in the progress callback scope; for example, a Message + object or a Client instance in order to edit the message with the updated progress status. 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. + The amount of bytes transmitted so far. total (``int``): - The size of the file. + The total size of the file. *args (``tuple``, *optional*): Extra custom arguments as defined in the *progress_args* parameter. @@ -108,8 +107,14 @@ class SendVideoNote(BaseClient): :obj:`Message` | ``None``: On success, the sent video note message is returned, otherwise, in case the upload is deliberately stopped with :meth:`~Client.stop_transmission`, None is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Send video note by uploading from local file + app.send_video_note("me", "video_note.mp4") + + # Set video note length + app.send_video_note("me", "video_note.mp4", length=25) """ file = None diff --git a/pyrogram/client/methods/messages/send_voice.py b/pyrogram/client/methods/messages/send_voice.py index 854385d8..8d9f6c5f 100644 --- a/pyrogram/client/methods/messages/send_voice.py +++ b/pyrogram/client/methods/messages/send_voice.py @@ -31,7 +31,7 @@ class SendVoice(BaseClient): chat_id: Union[int, str], voice: str, caption: str = "", - parse_mode: Union[str, None] = "", + parse_mode: Union[str, None] = object, duration: int = 0, disable_notification: bool = None, reply_to_message_id: int = None, @@ -83,23 +83,22 @@ class SendVoice(BaseClient): instructions to remove reply keyboard or to force a reply from the user. progress (``callable``, *optional*): - Pass a callback function to view the upload progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). + Pass a callback function to view the file transmission progress. + The function must take *(current, total)* as positional arguments (look at Other Parameters below for a + detailed description) and will be called back each time a new file chunk has been successfully + transmitted. progress_args (``tuple``, *optional*): - Extra custom arguments for the progress callback function. Useful, for example, if you want to pass - a chat_id and a message_id in order to edit a message with the updated progress. + Extra custom arguments for the progress callback function. + You can pass anything you need to be available in the progress callback scope; for example, a Message + object or a Client instance in order to edit the message with the updated progress status. 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. + The amount of bytes transmitted so far. total (``int``): - The size of the file. + The total size of the file. *args (``tuple``, *optional*): Extra custom arguments as defined in the *progress_args* parameter. @@ -109,8 +108,17 @@ class SendVoice(BaseClient): :obj:`Message` | ``None``: On success, the sent voice message is returned, otherwise, in case the upload is deliberately stopped with :meth:`~Client.stop_transmission`, None is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Send voice note by uploading from local file + app.send_voice("me", "voice.ogg") + + # Add caption to the voice note + app.send_voice("me", "voice.ogg", caption="voice note") + + # Set voice note duration + app.send_voice("me", "voice.ogg", duration=20) """ file = None diff --git a/pyrogram/client/methods/messages/stop_poll.py b/pyrogram/client/methods/messages/stop_poll.py index 6abe6791..308bf587 100644 --- a/pyrogram/client/methods/messages/stop_poll.py +++ b/pyrogram/client/methods/messages/stop_poll.py @@ -49,8 +49,10 @@ class StopPoll(BaseClient): Returns: :obj:`Poll`: On success, the stopped poll with the final results is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + app.stop_poll(chat_id, message_id) """ poll = self.get_messages(chat_id, message_id).poll diff --git a/pyrogram/client/methods/messages/vote_poll.py b/pyrogram/client/methods/messages/vote_poll.py index a5d77d86..7c976cd8 100644 --- a/pyrogram/client/methods/messages/vote_poll.py +++ b/pyrogram/client/methods/messages/vote_poll.py @@ -47,8 +47,10 @@ class VotePoll(BaseClient): Returns: :obj:`Poll` - On success, the poll with the chosen option is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + app.vote_poll(chat_id, message_id, 6) """ poll = self.get_messages(chat_id, message_id).poll diff --git a/pyrogram/client/methods/password/change_cloud_password.py b/pyrogram/client/methods/password/change_cloud_password.py index a33b83c7..67e1254f 100644 --- a/pyrogram/client/methods/password/change_cloud_password.py +++ b/pyrogram/client/methods/password/change_cloud_password.py @@ -46,8 +46,16 @@ class ChangeCloudPassword(BaseClient): ``bool``: True on success. Raises: - RPCError: In case of a Telegram RPC error. ValueError: In case there is no cloud password to change. + + Example: + .. code-block:: python + + # Change password only + app.change_cloud_password("current_password", "new_password") + + # Change password and hint + app.change_cloud_password("current_password", "new_password", new_hint="hint") """ r = self.send(functions.account.GetPassword()) diff --git a/pyrogram/client/methods/password/enable_cloud_password.py b/pyrogram/client/methods/password/enable_cloud_password.py index 23ee1608..19683ffc 100644 --- a/pyrogram/client/methods/password/enable_cloud_password.py +++ b/pyrogram/client/methods/password/enable_cloud_password.py @@ -48,8 +48,19 @@ class EnableCloudPassword(BaseClient): ``bool``: True on success. Raises: - RPCError: In case of a Telegram RPC error. ValueError: In case there is already a cloud password enabled. + + Example: + .. code-block:: python + + # Enable password without hint and email + app.enable_cloud_password("password") + + # Enable password with hint and without email + app.enable_cloud_password("password", hint="hint") + + # Enable password with hint and email + app.enable_cloud_password("password", hint="hint", email="user@email.com") """ r = self.send(functions.account.GetPassword()) diff --git a/pyrogram/client/methods/password/remove_cloud_password.py b/pyrogram/client/methods/password/remove_cloud_password.py index 9dcbb005..6b68bd5e 100644 --- a/pyrogram/client/methods/password/remove_cloud_password.py +++ b/pyrogram/client/methods/password/remove_cloud_password.py @@ -36,8 +36,12 @@ class RemoveCloudPassword(BaseClient): ``bool``: True on success. Raises: - RPCError: In case of a Telegram RPC error. ValueError: In case there is no cloud password to remove. + + Example: + .. code-block:: python + + app.remove_cloud_password("password") """ r = self.send(functions.account.GetPassword()) diff --git a/pyrogram/client/methods/users/block_user.py b/pyrogram/client/methods/users/block_user.py index ef3cad85..ff29089c 100644 --- a/pyrogram/client/methods/users/block_user.py +++ b/pyrogram/client/methods/users/block_user.py @@ -18,8 +18,7 @@ from typing import Union -import pyrogram -from pyrogram.api import functions, types +from pyrogram.api import functions from ...ext import BaseClient @@ -30,11 +29,19 @@ class BlockUser(BaseClient): ) -> bool: """Block a user. + Parameters: + user_id (``int`` | ``str``):: + Unique identifier (int) or username (str) of the target user. + For you yourself you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + Returns: ``bool``: True on success - Raises: - RPCError: In case of Telegram RPC Error. + Example: + .. code-block:: python + + app.block_user(user_id) """ return bool( self.send( diff --git a/pyrogram/client/methods/users/delete_profile_photos.py b/pyrogram/client/methods/users/delete_profile_photos.py index a165f7d1..5c3b26e8 100644 --- a/pyrogram/client/methods/users/delete_profile_photos.py +++ b/pyrogram/client/methods/users/delete_profile_photos.py @@ -21,7 +21,6 @@ from typing import List, Union from pyrogram.api import functions, types from pyrogram.client.ext import utils - from ...ext import BaseClient @@ -40,8 +39,17 @@ class DeleteProfilePhotos(BaseClient): Returns: ``bool``: True on success. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Get the photos to be deleted + photos = app.get_profile_photos("me") + + # Delete one photo + app.delete_profile_photos(photos[0].file_id) + + # Delete the rest of the photos + app.delete_profile_photos([p.file_id for p in photos[1:]]) """ photo_ids = photo_ids if isinstance(photo_ids, list) else [photo_ids] input_photos = [] diff --git a/pyrogram/client/methods/users/get_me.py b/pyrogram/client/methods/users/get_me.py index 44f16af3..b399187f 100644 --- a/pyrogram/client/methods/users/get_me.py +++ b/pyrogram/client/methods/users/get_me.py @@ -26,10 +26,13 @@ class GetMe(BaseClient): """Get your own user identity. Returns: - :obj:`User`: Basic information about the user or bot. + :obj:`User`: Information about the own logged in user/bot. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + me = app.get_me() + print(me) """ return pyrogram.User._parse( self, diff --git a/pyrogram/client/methods/users/get_profile_photos.py b/pyrogram/client/methods/users/get_profile_photos.py index 3ffeae39..2723a36c 100644 --- a/pyrogram/client/methods/users/get_profile_photos.py +++ b/pyrogram/client/methods/users/get_profile_photos.py @@ -21,7 +21,6 @@ from typing import Union, List import pyrogram from pyrogram.api import functions, types from pyrogram.client.ext import utils - from ...ext import BaseClient @@ -51,8 +50,17 @@ class GetProfilePhotos(BaseClient): Returns: List of :obj:`Photo`: On success, a list of profile photos is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Get the first 100 profile photos of a user + app.get_profile_photos("haskell") + + # Get only the first profile photo of a user + app.get_profile_photos("haskell", limit=1) + + # Get 3 profile photos of a user, skip the first 5 + app.get_profile_photos("haskell", limit=3, offset=5) """ peer_id = self.resolve_peer(chat_id) diff --git a/pyrogram/client/methods/users/get_profile_photos_count.py b/pyrogram/client/methods/users/get_profile_photos_count.py index bf00a10b..51a4091e 100644 --- a/pyrogram/client/methods/users/get_profile_photos_count.py +++ b/pyrogram/client/methods/users/get_profile_photos_count.py @@ -19,7 +19,6 @@ from typing import Union from pyrogram.api import functions, types - from ...ext import BaseClient @@ -36,8 +35,11 @@ class GetProfilePhotosCount(BaseClient): Returns: ``int``: On success, the user profile photos count is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + count = app.get_profile_photos_count("haskell") + print(count) """ peer_id = self.resolve_peer(chat_id) diff --git a/pyrogram/client/methods/users/get_users.py b/pyrogram/client/methods/users/get_users.py index f76e6802..67e58615 100644 --- a/pyrogram/client/methods/users/get_users.py +++ b/pyrogram/client/methods/users/get_users.py @@ -24,7 +24,6 @@ from ...ext import BaseClient class GetUsers(BaseClient): - # TODO: Add Users type and use that def get_users( self, user_ids: Union[Iterable[Union[int, str]], int, str] @@ -43,8 +42,14 @@ class GetUsers(BaseClient): returned, otherwise, in case *user_ids* was an iterable a list of users is returned, even if the iterable contained one item only. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Get information about one user + app.get_users("haskell") + + # Get information about multiple users at once + app.get_users([user1, user2, user3]) """ is_iterable = not isinstance(user_ids, (int, str)) user_ids = list(user_ids) if is_iterable else [user_ids] diff --git a/pyrogram/client/methods/users/iter_profile_photos.py b/pyrogram/client/methods/users/iter_profile_photos.py index 49317f87..f812a856 100644 --- a/pyrogram/client/methods/users/iter_profile_photos.py +++ b/pyrogram/client/methods/users/iter_profile_photos.py @@ -51,8 +51,11 @@ class IterProfilePhotos(BaseClient): Returns: ``Generator``: A generator yielding :obj:`Photo` objects. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + for photo in app.iter_profile_photos("haskell"): + print(photo.file_id) """ current = 0 total = limit or (1 << 31) diff --git a/pyrogram/client/methods/users/set_profile_photo.py b/pyrogram/client/methods/users/set_profile_photo.py index a713fd34..975a2ced 100644 --- a/pyrogram/client/methods/users/set_profile_photo.py +++ b/pyrogram/client/methods/users/set_profile_photo.py @@ -38,8 +38,10 @@ class SetProfilePhoto(BaseClient): Returns: ``bool``: True on success. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + app.set_profile_photo("new_photo.jpg") """ return bool( diff --git a/pyrogram/client/methods/users/unblock_user.py b/pyrogram/client/methods/users/unblock_user.py index c06533cd..e42fbd24 100644 --- a/pyrogram/client/methods/users/unblock_user.py +++ b/pyrogram/client/methods/users/unblock_user.py @@ -18,8 +18,7 @@ from typing import Union -import pyrogram -from pyrogram.api import functions, types +from pyrogram.api import functions from ...ext import BaseClient @@ -30,11 +29,19 @@ class UnblockUser(BaseClient): ) -> bool: """Unblock a user. + Parameters: + user_id (``int`` | ``str``):: + Unique identifier (int) or username (str) of the target user. + For you yourself you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + Returns: ``bool``: True on success - Raises: - RPCError: In case of Telegram RPC Error. + Example: + .. code-block:: python + + app.unblock_user(user_id) """ return bool( self.send( diff --git a/pyrogram/client/methods/users/update_username.py b/pyrogram/client/methods/users/update_username.py index 002dbf75..07bd62bb 100644 --- a/pyrogram/client/methods/users/update_username.py +++ b/pyrogram/client/methods/users/update_username.py @@ -40,8 +40,10 @@ class UpdateUsername(BaseClient): Returns: ``bool``: True on success. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + app.update_username("new_username") """ return bool( diff --git a/pyrogram/client/parser/__init__.py b/pyrogram/client/parser/__init__.py index 4769038d..53806619 100644 --- a/pyrogram/client/parser/__init__.py +++ b/pyrogram/client/parser/__init__.py @@ -16,4 +16,4 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from .parser import Parser \ No newline at end of file +from .parser import Parser diff --git a/pyrogram/client/parser/html.py b/pyrogram/client/parser/html.py index 9aff757f..41efe3b3 100644 --- a/pyrogram/client/parser/html.py +++ b/pyrogram/client/parser/html.py @@ -86,7 +86,8 @@ class Parser(HTMLParser): for entities in self.tag_entities.values(): for entity in entities: - entity.length += len(data) + entity.offset += len(data) - len(data.lstrip()) # Ignore left whitespaces for offsets + entity.length += len(data.strip()) # Ignore all whitespaces (left + right) for lengths self.text += data diff --git a/pyrogram/client/parser/parser.py b/pyrogram/client/parser/parser.py index 8fde46bd..371c4791 100644 --- a/pyrogram/client/parser/parser.py +++ b/pyrogram/client/parser/parser.py @@ -19,7 +19,6 @@ from collections import OrderedDict from typing import Union - import pyrogram from .html import HTML from .markdown import Markdown @@ -27,11 +26,18 @@ from .markdown import Markdown class Parser: def __init__(self, client: Union["pyrogram.BaseClient", None]): + self.client = client self.html = HTML(client) self.markdown = Markdown(client) - def parse(self, text: str, mode: str = ""): - text = str(text or "").strip() + def parse(self, text: str, mode: Union[str, None] = object): + text = str(text).strip() + + if mode == object: + if self.client: + mode = self.client.parse_mode + else: + mode = "combined" if mode is None: return OrderedDict([ @@ -41,7 +47,7 @@ class Parser: mode = mode.lower() - if mode == "": + if mode == "combined": return self.markdown.parse(text) if mode in ["markdown", "md"]: @@ -50,6 +56,11 @@ class Parser: if mode == "html": return self.html.parse(text) + raise ValueError('parse_mode must be one of {} or None. Not "{}"'.format( + ", ".join('"{}"'.format(m) for m in pyrogram.Client.PARSE_MODES[:-1]), + mode + )) + @staticmethod def unparse(text: str, entities: list, is_html: bool): if is_html: diff --git a/pyrogram/client/storage/__init__.py b/pyrogram/client/storage/__init__.py index 00d2f144..657c06eb 100644 --- a/pyrogram/client/storage/__init__.py +++ b/pyrogram/client/storage/__init__.py @@ -16,6 +16,6 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from .memory_storage import MemoryStorage from .file_storage import FileStorage +from .memory_storage import MemoryStorage from .storage import Storage diff --git a/pyrogram/client/types/bots_and_keyboards/callback_game.py b/pyrogram/client/types/bots_and_keyboards/callback_game.py index acf6df60..338cfb06 100644 --- a/pyrogram/client/types/bots_and_keyboards/callback_game.py +++ b/pyrogram/client/types/bots_and_keyboards/callback_game.py @@ -25,7 +25,5 @@ class CallbackGame(Object): Use BotFather to set up your game. """ - __slots__ = [] - def __init__(self): super().__init__() diff --git a/pyrogram/client/types/bots_and_keyboards/callback_query.py b/pyrogram/client/types/bots_and_keyboards/callback_query.py index b09e5440..9a5674ae 100644 --- a/pyrogram/client/types/bots_and_keyboards/callback_query.py +++ b/pyrogram/client/types/bots_and_keyboards/callback_query.py @@ -25,6 +25,7 @@ from pyrogram.api import types from ..object import Object from ..update import Update from ..user_and_chats import User +from ...ext import utils class CallbackQuery(Object, Update): @@ -60,8 +61,6 @@ class CallbackQuery(Object, Update): """ - __slots__ = ["id", "from_user", "chat_instance", "message", "inline_message_id", "data", "game_short_name"] - def __init__( self, *, @@ -90,16 +89,7 @@ class CallbackQuery(Object, Update): inline_message_id = None if isinstance(callback_query, types.UpdateBotCallbackQuery): - peer = callback_query.peer - - if isinstance(peer, types.PeerUser): - peer_id = peer.user_id - elif isinstance(peer, types.PeerChat): - peer_id = -peer.chat_id - else: - peer_id = int("-100" + str(peer.channel_id)) - - message = client.get_messages(peer_id, callback_query.msg_id) + message = client.get_messages(utils.get_peer_id(callback_query.peer), callback_query.msg_id) elif isinstance(callback_query, types.UpdateInlineBotCallbackQuery): inline_message_id = b64encode( pack( @@ -176,7 +166,7 @@ class CallbackQuery(Object, Update): def edit_message_text( self, text: str, - parse_mode: str = "", + parse_mode: Union[str, None] = object, disable_web_page_preview: bool = None, reply_markup: "pyrogram.InlineKeyboardMarkup" = None ) -> Union["pyrogram.Message", bool]: @@ -229,7 +219,7 @@ class CallbackQuery(Object, Update): def edit_message_caption( self, caption: str, - parse_mode: str = "", + parse_mode: Union[str, None] = object, reply_markup: "pyrogram.InlineKeyboardMarkup" = None ) -> Union["pyrogram.Message", bool]: """Edit the caption of media messages attached to callback queries. diff --git a/pyrogram/client/types/bots_and_keyboards/force_reply.py b/pyrogram/client/types/bots_and_keyboards/force_reply.py index 6c542aa8..ef5c0ccb 100644 --- a/pyrogram/client/types/bots_and_keyboards/force_reply.py +++ b/pyrogram/client/types/bots_and_keyboards/force_reply.py @@ -37,8 +37,6 @@ class ForceReply(Object): 2) if the bot's message is a reply (has reply_to_message_id), sender of the original message. """ - __slots__ = ["selective"] - def __init__( self, selective: bool = None diff --git a/pyrogram/client/types/bots_and_keyboards/game_high_score.py b/pyrogram/client/types/bots_and_keyboards/game_high_score.py index 5d576ad4..38e2242a 100644 --- a/pyrogram/client/types/bots_and_keyboards/game_high_score.py +++ b/pyrogram/client/types/bots_and_keyboards/game_high_score.py @@ -37,8 +37,6 @@ class GameHighScore(Object): Position in high score table for the game. """ - __slots__ = ["user", "score", "position"] - def __init__( self, *, diff --git a/pyrogram/client/types/bots_and_keyboards/inline_keyboard_button.py b/pyrogram/client/types/bots_and_keyboards/inline_keyboard_button.py index 54aa7802..678be614 100644 --- a/pyrogram/client/types/bots_and_keyboards/inline_keyboard_button.py +++ b/pyrogram/client/types/bots_and_keyboards/inline_keyboard_button.py @@ -22,6 +22,7 @@ from pyrogram.api.types import ( KeyboardButtonUrl, KeyboardButtonCallback, KeyboardButtonSwitchInline, KeyboardButtonGame ) + from .callback_game import CallbackGame from ..object import Object @@ -58,10 +59,6 @@ class InlineKeyboardButton(Object): # TODO: Add callback_game and pay fields - __slots__ = [ - "text", "url", "callback_data", "switch_inline_query", "switch_inline_query_current_chat", "callback_game" - ] - def __init__( self, text: str, diff --git a/pyrogram/client/types/bots_and_keyboards/inline_keyboard_markup.py b/pyrogram/client/types/bots_and_keyboards/inline_keyboard_markup.py index 7b811f88..811c4365 100644 --- a/pyrogram/client/types/bots_and_keyboards/inline_keyboard_markup.py +++ b/pyrogram/client/types/bots_and_keyboards/inline_keyboard_markup.py @@ -19,6 +19,7 @@ from typing import List from pyrogram.api.types import ReplyInlineMarkup, KeyboardButtonRow + from . import InlineKeyboardButton from ..object import Object @@ -31,8 +32,6 @@ class InlineKeyboardMarkup(Object): List of button rows, each represented by a List of InlineKeyboardButton objects. """ - __slots__ = ["inline_keyboard"] - def __init__( self, inline_keyboard: List[List[InlineKeyboardButton]] diff --git a/pyrogram/client/types/bots_and_keyboards/keyboard_button.py b/pyrogram/client/types/bots_and_keyboards/keyboard_button.py index 8374db1b..21c03613 100644 --- a/pyrogram/client/types/bots_and_keyboards/keyboard_button.py +++ b/pyrogram/client/types/bots_and_keyboards/keyboard_button.py @@ -41,8 +41,6 @@ class KeyboardButton(Object): Available in private chats only. """ - __slots__ = ["text", "request_contact", "request_location"] - def __init__( self, text: str, diff --git a/pyrogram/client/types/bots_and_keyboards/reply_keyboard_markup.py b/pyrogram/client/types/bots_and_keyboards/reply_keyboard_markup.py index 4e666d1f..12799bd7 100644 --- a/pyrogram/client/types/bots_and_keyboards/reply_keyboard_markup.py +++ b/pyrogram/client/types/bots_and_keyboards/reply_keyboard_markup.py @@ -20,6 +20,7 @@ from typing import List, Union from pyrogram.api.types import KeyboardButtonRow from pyrogram.api.types import ReplyKeyboardMarkup as RawReplyKeyboardMarkup + from . import KeyboardButton from ..object import Object @@ -49,8 +50,6 @@ class ReplyKeyboardMarkup(Object): select the new language. Other users in the group don't see the keyboard. """ - __slots__ = ["keyboard", "resize_keyboard", "one_time_keyboard", "selective"] - def __init__( self, keyboard: List[List[Union[KeyboardButton, str]]], diff --git a/pyrogram/client/types/bots_and_keyboards/reply_keyboard_remove.py b/pyrogram/client/types/bots_and_keyboards/reply_keyboard_remove.py index d451a8e8..1623c9bd 100644 --- a/pyrogram/client/types/bots_and_keyboards/reply_keyboard_remove.py +++ b/pyrogram/client/types/bots_and_keyboards/reply_keyboard_remove.py @@ -38,8 +38,6 @@ class ReplyKeyboardRemove(Object): keyboard for that user, while still showing the keyboard with poll options to users who haven't voted yet. """ - __slots__ = ["selective"] - def __init__( self, selective: bool = None diff --git a/pyrogram/client/types/inline_mode/__init__.py b/pyrogram/client/types/inline_mode/__init__.py index 7a3b3023..4768ecae 100644 --- a/pyrogram/client/types/inline_mode/__init__.py +++ b/pyrogram/client/types/inline_mode/__init__.py @@ -18,8 +18,11 @@ from .inline_query import InlineQuery from .inline_query_result import InlineQueryResult +from .inline_query_result_animation import InlineQueryResultAnimation from .inline_query_result_article import InlineQueryResultArticle +from .inline_query_result_photo import InlineQueryResultPhoto __all__ = [ - "InlineQuery", "InlineQueryResult", "InlineQueryResultArticle" + "InlineQuery", "InlineQueryResult", "InlineQueryResultArticle", "InlineQueryResultPhoto", + "InlineQueryResultAnimation" ] diff --git a/pyrogram/client/types/inline_mode/inline_query.py b/pyrogram/client/types/inline_mode/inline_query.py index 6bfc58c3..27b73ff4 100644 --- a/pyrogram/client/types/inline_mode/inline_query.py +++ b/pyrogram/client/types/inline_mode/inline_query.py @@ -48,7 +48,6 @@ class InlineQuery(Object, Update): location (:obj:`Location`. *optional*): Sender location, only for bots that request user location. """ - __slots__ = ["id", "from_user", "query", "offset", "location"] def __init__( self, @@ -87,7 +86,8 @@ class InlineQuery(Object, Update): self, results: List[InlineQueryResult], cache_time: int = 300, - is_personal: bool = None, + is_gallery: bool = False, + is_personal: bool = False, next_offset: str = "", switch_pm_text: str = "", switch_pm_parameter: str = "" @@ -116,9 +116,13 @@ class InlineQuery(Object, Update): The maximum amount of time in seconds that the result of the inline query may be cached on the server. Defaults to 300. + is_gallery (``bool``, *optional*): + Pass True, if results should be displayed in gallery mode instead of list mode. + Defaults to False. + is_personal (``bool``, *optional*): Pass True, if results may be cached on the server side only for the user that sent the query. - By default, results may be returned to any user who sends the same query. + By default (False), results may be returned to any user who sends the same query. next_offset (``str``, *optional*): Pass the offset that a client should send in the next query with the same text to receive more results. @@ -145,6 +149,7 @@ class InlineQuery(Object, Update): inline_query_id=self.id, results=results, cache_time=cache_time, + is_gallery=is_gallery, is_personal=is_personal, next_offset=next_offset, switch_pm_text=switch_pm_text, diff --git a/pyrogram/client/types/inline_mode/inline_query_result.py b/pyrogram/client/types/inline_mode/inline_query_result.py index 3fc70885..ef26eacc 100644 --- a/pyrogram/client/types/inline_mode/inline_query_result.py +++ b/pyrogram/client/types/inline_mode/inline_query_result.py @@ -16,6 +16,10 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from uuid import uuid4 + +from ..bots_and_keyboards import InlineKeyboardMarkup +from ..input_message_content import InputMessageContent from ..object import Object """- :obj:`InlineQueryResultCachedAudio` @@ -45,15 +49,23 @@ class InlineQueryResult(Object): Pyrogram currently supports results of the following types: - :obj:`InlineQueryResultArticle` + - :obj:`InlineQueryResultPhoto` + - :obj:`InlineQueryResultAnimation` """ - __slots__ = ["type", "id"] - - def __init__(self, type: str, id: str): + def __init__( + self, + type: str, + id: str, + input_message_content: InputMessageContent, + reply_markup: InlineKeyboardMarkup + ): super().__init__() self.type = type - self.id = id + self.id = str(uuid4()) if id is None else id + self.input_message_content = input_message_content + self.reply_markup = reply_markup def write(self): pass diff --git a/pyrogram/client/types/inline_mode/inline_query_result_animation.py b/pyrogram/client/types/inline_mode/inline_query_result_animation.py new file mode 100644 index 00000000..c4cb26e4 --- /dev/null +++ b/pyrogram/client/types/inline_mode/inline_query_result_animation.py @@ -0,0 +1,127 @@ +# 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 typing import Union + +from pyrogram.api import types +from .inline_query_result import InlineQueryResult +from ..bots_and_keyboards import InlineKeyboardMarkup +from ..input_message_content import InputMessageContent +from ...parser import Parser + + +class InlineQueryResultAnimation(InlineQueryResult): + """Link to an animated GIF file. + + By default, this animated GIF file will be sent by the user with optional caption. + Alternatively, you can use *input_message_content* to send a message with the specified content instead of the + animation. + + Parameters: + animation_url (``str``): + A valid URL for the animated GIF file. + File size must not exceed 1 MB. + + thumb_url (``str``, *optional*): + URL of the static thumbnail for the result (jpeg or gif) + Defaults to the value passed in *animation_url*. + + id (``str``, *optional*): + Unique identifier for this result, 1-64 bytes. + Defaults to a randomly generated UUID4. + + title (``str``, *optional*): + Title for the result. + + description (``str``, *optional*): + Short description of the result. + + caption (``str``, *optional*): + Caption of the photo to be sent, 0-1024 characters. + + parse_mode (``str``, *optional*): + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" or "md" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. + + reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + An InlineKeyboardMarkup object. + + input_message_content (:obj:`InputMessageContent`): + Content of the message to be sent instead of the photo. + """ + + def __init__( + self, + animation_url: str, + thumb_url: str = None, + id: str = None, + title: str = None, + description: str = None, + caption: str = None, + parse_mode: Union[str, None] = object, + reply_markup: InlineKeyboardMarkup = None, + input_message_content: InputMessageContent = None + ): + super().__init__("gif", id, input_message_content, reply_markup) + + self.animation_url = animation_url + self.thumb_url = thumb_url + self.title = title + self.description = description + self.caption = caption + self.parse_mode = parse_mode + self.reply_markup = reply_markup + self.input_message_content = input_message_content + + def write(self): + animation = types.InputWebDocument( + url=self.animation_url, + size=0, + mime_type="image/gif", + attributes=[] + ) + + if self.thumb_url is None: + thumb = animation + else: + thumb = types.InputWebDocument( + url=self.thumb_url, + size=0, + mime_type="image/gif", + attributes=[] + ) + + return types.InputBotInlineResult( + id=self.id, + type=self.type, + title=self.title, + description=self.description, + thumb=thumb, + content=animation, + send_message=( + self.input_message_content.write(self.reply_markup) + if self.input_message_content + else types.InputBotInlineMessageMediaAuto( + reply_markup=self.reply_markup.write() if self.reply_markup else None, + **(Parser(None)).parse(self.caption, self.parse_mode) + ) + ) + ) diff --git a/pyrogram/client/types/inline_mode/inline_query_result_article.py b/pyrogram/client/types/inline_mode/inline_query_result_article.py index ad0be9e4..735a1e02 100644 --- a/pyrogram/client/types/inline_mode/inline_query_result_article.py +++ b/pyrogram/client/types/inline_mode/inline_query_result_article.py @@ -16,29 +16,25 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Any - from pyrogram.api import types from .inline_query_result import InlineQueryResult +from ..bots_and_keyboards import InlineKeyboardMarkup +from ..input_message_content import InputMessageContent class InlineQueryResultArticle(InlineQueryResult): """Link to an article or web page. - TODO: Hide url? - Parameters: - id (``str``): - Unique identifier for this result, 1-64 bytes. - title (``str``): Title for the result. input_message_content (:obj:`InputMessageContent`): Content of the message to be sent. - reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): - Inline keyboard attached to the message. + id (``str``, *optional*): + Unique identifier for this result, 1-64 bytes. + Defaults to a randomly generated UUID4. url (``str``, *optional*): URL of the result. @@ -47,46 +43,32 @@ class InlineQueryResultArticle(InlineQueryResult): Short description of the result. thumb_url (``str``, *optional*): - Url of the thumbnail for the result. + URL of the thumbnail for the result. - thumb_width (``int``, *optional*): - Thumbnail width. - - thumb_height (``int``, *optional*): - Thumbnail height. + reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + Inline keyboard attached to the message. """ - __slots__ = [ - "title", "input_message_content", "reply_markup", "url", "description", "thumb_url", "thumb_width", - "thumb_height" - ] - def __init__( self, - id: Any, title: str, - input_message_content, - reply_markup=None, + input_message_content: InputMessageContent, + id: str = None, + reply_markup: InlineKeyboardMarkup = None, url: str = None, description: str = None, - thumb_url: str = None, - thumb_width: int = 0, - thumb_height: int = 0 + thumb_url: str = None ): - super().__init__("article", id) + super().__init__("article", id, input_message_content, reply_markup) self.title = title - self.input_message_content = input_message_content - self.reply_markup = reply_markup self.url = url self.description = description self.thumb_url = thumb_url - self.thumb_width = thumb_width - self.thumb_height = thumb_height def write(self): return types.InputBotInlineResult( - id=str(self.id), + id=self.id, type=self.type, send_message=self.input_message_content.write(self.reply_markup), title=self.title, @@ -96,11 +78,6 @@ class InlineQueryResultArticle(InlineQueryResult): url=self.thumb_url, size=0, mime_type="image/jpeg", - attributes=[ - types.DocumentAttributeImageSize( - w=self.thumb_width, - h=self.thumb_height - ) - ] + attributes=[] ) if self.thumb_url else None ) diff --git a/pyrogram/client/types/inline_mode/inline_query_result_photo.py b/pyrogram/client/types/inline_mode/inline_query_result_photo.py new file mode 100644 index 00000000..ffcc21c0 --- /dev/null +++ b/pyrogram/client/types/inline_mode/inline_query_result_photo.py @@ -0,0 +1,127 @@ +# 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 typing import Union + +from pyrogram.api import types +from .inline_query_result import InlineQueryResult +from ..bots_and_keyboards import InlineKeyboardMarkup +from ..input_message_content import InputMessageContent +from ...parser import Parser + + +class InlineQueryResultPhoto(InlineQueryResult): + """Link to a photo. + + By default, this photo will be sent by the user with optional caption. + Alternatively, you can use *input_message_content* to send a message with the specified content instead of the + photo. + + Parameters: + photo_url (``str``): + A valid URL of the photo. + Photo must be in jpeg format an must not exceed 5 MB. + + thumb_url (``str``, *optional*): + URL of the thumbnail for the photo. + Defaults to the value passed in *photo_url*. + + id (``str``, *optional*): + Unique identifier for this result, 1-64 bytes. + Defaults to a randomly generated UUID4. + + title (``str``, *optional*): + Title for the result. + + description (``str``, *optional*): + Short description of the result. + + caption (``str``, *optional*): + Caption of the photo to be sent, 0-1024 characters. + + parse_mode (``str``, *optional*): + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" or "md" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. + + reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + An InlineKeyboardMarkup object. + + input_message_content (:obj:`InputMessageContent`): + Content of the message to be sent instead of the photo. + """ + + def __init__( + self, + photo_url: str, + thumb_url: str = None, + id: str = None, + title: str = None, + description: str = None, + caption: str = None, + parse_mode: Union[str, None] = object, + reply_markup: InlineKeyboardMarkup = None, + input_message_content: InputMessageContent = None + ): + super().__init__("photo", id, input_message_content, reply_markup) + + self.photo_url = photo_url + self.thumb_url = thumb_url + self.title = title + self.description = description + self.caption = caption + self.parse_mode = parse_mode + self.reply_markup = reply_markup + self.input_message_content = input_message_content + + def write(self): + photo = types.InputWebDocument( + url=self.photo_url, + size=0, + mime_type="image/jpeg", + attributes=[] + ) + + if self.thumb_url is None: + thumb = photo + else: + thumb = types.InputWebDocument( + url=self.thumb_url, + size=0, + mime_type="image/jpeg", + attributes=[] + ) + + return types.InputBotInlineResult( + id=self.id, + type=self.type, + title=self.title, + description=self.description, + thumb=thumb, + content=photo, + send_message=( + self.input_message_content.write(self.reply_markup) + if self.input_message_content + else types.InputBotInlineMessageMediaAuto( + reply_markup=self.reply_markup.write() if self.reply_markup else None, + **(Parser(None)).parse(self.caption, self.parse_mode) + ) + ) + ) diff --git a/pyrogram/client/types/input_media/input_media.py b/pyrogram/client/types/input_media/input_media.py index 2b5d7f0f..9b89fe12 100644 --- a/pyrogram/client/types/input_media/input_media.py +++ b/pyrogram/client/types/input_media/input_media.py @@ -30,7 +30,6 @@ class InputMedia(Object): - :obj:`InputMediaPhoto` - :obj:`InputMediaVideo` """ - __slots__ = ["media", "caption", "parse_mode"] def __init__(self, media: str, caption: str, parse_mode: str): super().__init__() diff --git a/pyrogram/client/types/input_media/input_media_animation.py b/pyrogram/client/types/input_media/input_media_animation.py index e157993b..dc70cbec 100644 --- a/pyrogram/client/types/input_media/input_media_animation.py +++ b/pyrogram/client/types/input_media/input_media_animation.py @@ -56,14 +56,12 @@ class InputMediaAnimation(InputMedia): Animation duration. """ - __slots__ = ["thumb", "width", "height", "duration"] - def __init__( self, media: str, thumb: str = None, caption: str = "", - parse_mode: Union[str, None] = "", + parse_mode: Union[str, None] = object, width: int = 0, height: int = 0, duration: int = 0 diff --git a/pyrogram/client/types/input_media/input_media_audio.py b/pyrogram/client/types/input_media/input_media_audio.py index 3eb3ea65..5ed670a6 100644 --- a/pyrogram/client/types/input_media/input_media_audio.py +++ b/pyrogram/client/types/input_media/input_media_audio.py @@ -58,14 +58,12 @@ class InputMediaAudio(InputMedia): Title of the audio """ - __slots__ = ["thumb", "duration", "performer", "title"] - def __init__( self, media: str, thumb: str = None, caption: str = "", - parse_mode: Union[str, None] = "", + parse_mode: Union[str, None] = object, duration: int = 0, performer: int = "", title: str = "" diff --git a/pyrogram/client/types/input_media/input_media_document.py b/pyrogram/client/types/input_media/input_media_document.py index 7aca9a31..14756e02 100644 --- a/pyrogram/client/types/input_media/input_media_document.py +++ b/pyrogram/client/types/input_media/input_media_document.py @@ -47,14 +47,12 @@ class InputMediaDocument(InputMedia): Pass None to completely disable style parsing. """ - __slots__ = ["thumb"] - def __init__( self, media: str, thumb: str = None, caption: str = "", - parse_mode: Union[str, None] = "" + parse_mode: Union[str, None] = object ): super().__init__(media, caption, parse_mode) diff --git a/pyrogram/client/types/input_media/input_media_photo.py b/pyrogram/client/types/input_media/input_media_photo.py index d2f26a88..5e18cdd6 100644 --- a/pyrogram/client/types/input_media/input_media_photo.py +++ b/pyrogram/client/types/input_media/input_media_photo.py @@ -43,12 +43,10 @@ class InputMediaPhoto(InputMedia): Pass None to completely disable style parsing. """ - __slots__ = [] - def __init__( self, media: str, caption: str = "", - parse_mode: Union[str, None] = "" + parse_mode: Union[str, None] = object ): super().__init__(media, caption, parse_mode) diff --git a/pyrogram/client/types/input_media/input_media_video.py b/pyrogram/client/types/input_media/input_media_video.py index d2ee851d..6b64caa8 100644 --- a/pyrogram/client/types/input_media/input_media_video.py +++ b/pyrogram/client/types/input_media/input_media_video.py @@ -61,14 +61,12 @@ class InputMediaVideo(InputMedia): Pass True, if the uploaded video is suitable for streaming. """ - __slots__ = ["thumb", "width", "height", "duration", "supports_streaming"] - def __init__( self, media: str, thumb: str = None, caption: str = "", - parse_mode: Union[str, None] = "", + parse_mode: Union[str, None] = object, width: int = 0, height: int = 0, duration: int = 0, diff --git a/pyrogram/client/types/input_media/input_phone_contact.py b/pyrogram/client/types/input_media/input_phone_contact.py index 9c03694d..7498768d 100644 --- a/pyrogram/client/types/input_media/input_phone_contact.py +++ b/pyrogram/client/types/input_media/input_phone_contact.py @@ -37,8 +37,6 @@ class InputPhoneContact(Object): Contact's last name """ - __slots__ = [] - def __init__(self, phone: str, first_name: str, last_name: str = ""): super().__init__(None) diff --git a/pyrogram/client/types/input_message_content/input_message_content.py b/pyrogram/client/types/input_message_content/input_message_content.py index fe11ef7a..6561b5a8 100644 --- a/pyrogram/client/types/input_message_content/input_message_content.py +++ b/pyrogram/client/types/input_message_content/input_message_content.py @@ -31,7 +31,8 @@ class InputMessageContent(Object): - :obj:`InputTextMessageContent` """ - __slots__ = [] - def __init__(self): super().__init__() + + def write(self, reply_markup): + raise NotImplementedError diff --git a/pyrogram/client/types/input_message_content/input_text_message_content.py b/pyrogram/client/types/input_message_content/input_text_message_content.py index f4b9aefc..3ab67d96 100644 --- a/pyrogram/client/types/input_message_content/input_text_message_content.py +++ b/pyrogram/client/types/input_message_content/input_text_message_content.py @@ -41,9 +41,7 @@ class InputTextMessageContent(InputMessageContent): Disables link previews for links in this message. """ - __slots__ = ["message_text", "parse_mode", "disable_web_page_preview"] - - def __init__(self, message_text: str, parse_mode: Union[str, None] = "", disable_web_page_preview: bool = None): + def __init__(self, message_text: str, parse_mode: Union[str, None] = object, disable_web_page_preview: bool = None): super().__init__() self.message_text = message_text diff --git a/pyrogram/client/types/messages_and_media/animation.py b/pyrogram/client/types/messages_and_media/animation.py index 5441a114..ba6744ce 100644 --- a/pyrogram/client/types/messages_and_media/animation.py +++ b/pyrogram/client/types/messages_and_media/animation.py @@ -58,8 +58,6 @@ class Animation(Object): Animation thumbnails. """ - __slots__ = ["file_id", "file_name", "mime_type", "file_size", "date", "width", "height", "duration", "thumbs"] - def __init__( self, *, diff --git a/pyrogram/client/types/messages_and_media/audio.py b/pyrogram/client/types/messages_and_media/audio.py index 3d9cf8a6..6d8a12e9 100644 --- a/pyrogram/client/types/messages_and_media/audio.py +++ b/pyrogram/client/types/messages_and_media/audio.py @@ -58,10 +58,6 @@ class Audio(Object): Thumbnails of the music file album cover. """ - __slots__ = [ - "file_id", "file_name", "mime_type", "file_size", "date", "duration", "performer", "title", "thumbs" - ] - def __init__( self, *, diff --git a/pyrogram/client/types/messages_and_media/contact.py b/pyrogram/client/types/messages_and_media/contact.py index d18f5e18..ad263397 100644 --- a/pyrogram/client/types/messages_and_media/contact.py +++ b/pyrogram/client/types/messages_and_media/contact.py @@ -42,8 +42,6 @@ class Contact(Object): Additional data about the contact in the form of a vCard. """ - __slots__ = ["phone_number", "first_name", "last_name", "user_id", "vcard"] - def __init__( self, *, diff --git a/pyrogram/client/types/messages_and_media/document.py b/pyrogram/client/types/messages_and_media/document.py index 45994e16..4bb40980 100644 --- a/pyrogram/client/types/messages_and_media/document.py +++ b/pyrogram/client/types/messages_and_media/document.py @@ -49,8 +49,6 @@ class Document(Object): Document thumbnails as defined by sender. """ - __slots__ = ["file_id", "file_name", "mime_type", "file_size", "date", "thumbs"] - def __init__( self, *, diff --git a/pyrogram/client/types/messages_and_media/game.py b/pyrogram/client/types/messages_and_media/game.py index 2b400e65..38c00fdf 100644 --- a/pyrogram/client/types/messages_and_media/game.py +++ b/pyrogram/client/types/messages_and_media/game.py @@ -48,8 +48,6 @@ class Game(Object): Upload via BotFather. """ - __slots__ = ["id", "title", "short_name", "description", "photo", "animation"] - def __init__( self, *, diff --git a/pyrogram/client/types/messages_and_media/location.py b/pyrogram/client/types/messages_and_media/location.py index 5af55f0f..4dec0277 100644 --- a/pyrogram/client/types/messages_and_media/location.py +++ b/pyrogram/client/types/messages_and_media/location.py @@ -33,8 +33,6 @@ class Location(Object): Latitude as defined by sender. """ - __slots__ = ["longitude", "latitude"] - def __init__( self, *, diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 144f04a4..7616a6ec 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -31,7 +31,8 @@ from ..object import Object from ..update import Update from ..user_and_chats.chat import Chat from ..user_and_chats.user import User -from ...parser import utils, Parser +from ...ext import utils +from ...parser import utils as parser_utils, Parser class Str(str): @@ -54,7 +55,7 @@ class Str(str): return Parser.unparse(self, self.entities, True) def __getitem__(self, item): - return utils.remove_surrogates(utils.add_surrogates(self)[item]) + return parser_utils.remove_surrogates(parser_utils.add_surrogates(self)[item]) class Message(Object, Update): @@ -263,17 +264,6 @@ class Message(Object, Update): # TODO: Add game missing field. Also invoice, successful_payment, connected_website - __slots__ = [ - "message_id", "date", "chat", "from_user", "forward_from", "forward_sender_name", "forward_from_chat", - "forward_from_message_id", "forward_signature", "forward_date", "reply_to_message", "mentioned", "empty", - "service", "media", "edit_date", "media_group_id", "author_signature", "text", "entities", "caption_entities", - "audio", "document", "photo", "sticker", "animation", "game", "video", "voice", "video_note", "caption", - "contact", "location", "venue", "web_page", "poll", "new_chat_members", "left_chat_member", "new_chat_title", - "new_chat_photo", "delete_chat_photo", "group_chat_created", "supergroup_chat_created", "channel_chat_created", - "migrate_to_chat_id", "migrate_from_chat_id", "pinned_message", "game_high_score", "views", "via_bot", - "outgoing", "matches", "command", "reply_markup" - ] - def __init__( self, *, @@ -446,7 +436,7 @@ class Message(Object, Update): new_chat_title=new_chat_title, new_chat_photo=new_chat_photo, delete_chat_photo=delete_chat_photo, - migrate_to_chat_id=int("-100" + str(migrate_to_chat_id)) if migrate_to_chat_id else None, + migrate_to_chat_id=utils.get_channel_id(migrate_to_chat_id) if migrate_to_chat_id else None, migrate_from_chat_id=-migrate_from_chat_id if migrate_from_chat_id else None, group_chat_created=group_chat_created, channel_chat_created=channel_chat_created, @@ -602,10 +592,26 @@ class Message(Object, Update): date=message.date, chat=Chat._parse(client, message, users, chats), from_user=User._parse(client, users.get(message.from_id, None)), - text=Str(message.message).init(entities) or None if media is None else None, - caption=Str(message.message).init(entities) or None if media is not None else None, - entities=entities or None if media is None else None, - caption_entities=entities or None if media is not None else None, + text=( + Str(message.message).init(entities) or None + if media is None or web_page is not None + else None + ), + caption=( + Str(message.message).init(entities) or None + if media is not None and web_page is None + else None + ), + entities=( + entities or None + if media is None or web_page is not None + else None + ), + caption_entities=( + entities or None + if media is not None and web_page is None + else None + ), author_signature=message.post_author, forward_from=forward_from, forward_sender_name=forward_sender_name, @@ -654,7 +660,7 @@ class Message(Object, Update): self, text: str, quote: bool = None, - parse_mode: str = "", + parse_mode: Union[str, None] = object, disable_web_page_preview: bool = None, disable_notification: bool = None, reply_to_message_id: int = None, @@ -736,7 +742,7 @@ class Message(Object, Update): animation: str, quote: bool = None, caption: str = "", - parse_mode: str = "", + parse_mode: Union[str, None] = object, duration: int = 0, width: int = 0, height: int = 0, @@ -817,23 +823,22 @@ class Message(Object, Update): instructions to remove reply keyboard or to force a reply from the user. progress (``callable``, *optional*): - Pass a callback function to view the upload progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). + Pass a callback function to view the file transmission progress. + The function must take *(current, total)* as positional arguments (look at Other Parameters below for a + detailed description) and will be called back each time a new file chunk has been successfully + transmitted. progress_args (``tuple``, *optional*): - Extra custom arguments for the progress callback function. Useful, for example, if you want to pass - a chat_id and a message_id in order to edit a message with the updated progress. + Extra custom arguments for the progress callback function. + You can pass anything you need to be available in the progress callback scope; for example, a Message + object or a Client instance in order to edit the message with the updated progress status. 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. + The amount of bytes transmitted so far. total (``int``): - The size of the file. + The total size of the file. *args (``tuple``, *optional*): Extra custom arguments as defined in the *progress_args* parameter. @@ -873,7 +878,7 @@ class Message(Object, Update): audio: str, quote: bool = None, caption: str = "", - parse_mode: str = "", + parse_mode: Union[str, None] = object, duration: int = 0, performer: str = None, title: str = None, @@ -954,23 +959,22 @@ class Message(Object, Update): instructions to remove reply keyboard or to force a reply from the user. progress (``callable``, *optional*): - Pass a callback function to view the upload progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). + Pass a callback function to view the file transmission progress. + The function must take *(current, total)* as positional arguments (look at Other Parameters below for a + detailed description) and will be called back each time a new file chunk has been successfully + transmitted. progress_args (``tuple``, *optional*): - Extra custom arguments for the progress callback function. Useful, for example, if you want to pass - a chat_id and a message_id in order to edit a message with the updated progress. + Extra custom arguments for the progress callback function. + You can pass anything you need to be available in the progress callback scope; for example, a Message + object or a Client instance in order to edit the message with the updated progress status. 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. + The amount of bytes transmitted so far. total (``int``): - The size of the file. + The total size of the file. *args (``tuple``, *optional*): Extra custom arguments as defined in the *progress_args* parameter. @@ -1010,7 +1014,7 @@ class Message(Object, Update): file_id: str, quote: bool = None, caption: str = "", - parse_mode: str = "", + parse_mode: Union[str, None] = object, disable_notification: bool = None, reply_to_message_id: int = None, reply_markup: Union[ @@ -1218,7 +1222,7 @@ class Message(Object, Update): quote: bool = None, thumb: str = None, caption: str = "", - parse_mode: str = "", + parse_mode: Union[str, None] = object, disable_notification: bool = None, reply_to_message_id: int = None, reply_markup: Union[ @@ -1286,23 +1290,22 @@ class Message(Object, Update): instructions to remove reply keyboard or to force a reply from the user. progress (``callable``, *optional*): - Pass a callback function to view the upload progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). + Pass a callback function to view the file transmission progress. + The function must take *(current, total)* as positional arguments (look at Other Parameters below for a + detailed description) and will be called back each time a new file chunk has been successfully + transmitted. progress_args (``tuple``, *optional*): - Extra custom arguments for the progress callback function. Useful, for example, if you want to pass - a chat_id and a message_id in order to edit a message with the updated progress. + Extra custom arguments for the progress callback function. + You can pass anything you need to be available in the progress callback scope; for example, a Message + object or a Client instance in order to edit the message with the updated progress status. 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. + The amount of bytes transmitted so far. total (``int``): - The size of the file. + The total size of the file. *args (``tuple``, *optional*): Extra custom arguments as defined in the *progress_args* parameter. @@ -1613,7 +1616,7 @@ class Message(Object, Update): photo: str, quote: bool = None, caption: str = "", - parse_mode: str = "", + parse_mode: Union[str, None] = object, ttl_seconds: int = None, disable_notification: bool = None, reply_to_message_id: int = None, @@ -1681,23 +1684,22 @@ class Message(Object, Update): instructions to remove reply keyboard or to force a reply from the user. progress (``callable``, *optional*): - Pass a callback function to view the upload progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). + Pass a callback function to view the file transmission progress. + The function must take *(current, total)* as positional arguments (look at Other Parameters below for a + detailed description) and will be called back each time a new file chunk has been successfully + transmitted. progress_args (``tuple``, *optional*): - Extra custom arguments for the progress callback function. Useful, for example, if you want to pass - a chat_id and a message_id in order to edit a message with the updated progress. + Extra custom arguments for the progress callback function. + You can pass anything you need to be available in the progress callback scope; for example, a Message + object or a Client instance in order to edit the message with the updated progress status. 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. + The amount of bytes transmitted so far. total (``int``): - The size of the file. + The total size of the file. *args (``tuple``, *optional*): Extra custom arguments as defined in the *progress_args* parameter. @@ -1859,23 +1861,22 @@ class Message(Object, Update): instructions to remove reply keyboard or to force a reply from the user. progress (``callable``, *optional*): - Pass a callback function to view the upload progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). + Pass a callback function to view the file transmission progress. + The function must take *(current, total)* as positional arguments (look at Other Parameters below for a + detailed description) and will be called back each time a new file chunk has been successfully + transmitted. progress_args (``tuple``, *optional*): - Extra custom arguments for the progress callback function. Useful, for example, if you want to pass - a chat_id and a message_id in order to edit a message with the updated progress. + Extra custom arguments for the progress callback function. + You can pass anything you need to be available in the progress callback scope; for example, a Message + object or a Client instance in order to edit the message with the updated progress status. 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. + The amount of bytes transmitted so far. total (``int``): - The size of the file. + The total size of the file. *args (``tuple``, *optional*): Extra custom arguments as defined in the *progress_args* parameter. @@ -2007,7 +2008,7 @@ class Message(Object, Update): video: str, quote: bool = None, caption: str = "", - parse_mode: str = "", + parse_mode: Union[str, None] = object, duration: int = 0, width: int = 0, height: int = 0, @@ -2092,23 +2093,22 @@ class Message(Object, Update): instructions to remove reply keyboard or to force a reply from the user. progress (``callable``, *optional*): - Pass a callback function to view the upload progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). + Pass a callback function to view the file transmission progress. + The function must take *(current, total)* as positional arguments (look at Other Parameters below for a + detailed description) and will be called back each time a new file chunk has been successfully + transmitted. progress_args (``tuple``, *optional*): - Extra custom arguments for the progress callback function. Useful, for example, if you want to pass - a chat_id and a message_id in order to edit a message with the updated progress. + Extra custom arguments for the progress callback function. + You can pass anything you need to be available in the progress callback scope; for example, a Message + object or a Client instance in order to edit the message with the updated progress status. 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. + The amount of bytes transmitted so far. total (``int``): - The size of the file. + The total size of the file. *args (``tuple``, *optional*): Extra custom arguments as defined in the *progress_args* parameter. @@ -2214,23 +2214,22 @@ class Message(Object, Update): instructions to remove reply keyboard or to force a reply from the user. progress (``callable``, *optional*): - Pass a callback function to view the upload progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). + Pass a callback function to view the file transmission progress. + The function must take *(current, total)* as positional arguments (look at Other Parameters below for a + detailed description) and will be called back each time a new file chunk has been successfully + transmitted. progress_args (``tuple``, *optional*): - Extra custom arguments for the progress callback function. Useful, for example, if you want to pass - a chat_id and a message_id in order to edit a message with the updated progress. + Extra custom arguments for the progress callback function. + You can pass anything you need to be available in the progress callback scope; for example, a Message + object or a Client instance in order to edit the message with the updated progress status. 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. + The amount of bytes transmitted so far. total (``int``): - The size of the file. + The total size of the file. *args (``tuple``, *optional*): Extra custom arguments as defined in the *progress_args* parameter. @@ -2267,7 +2266,7 @@ class Message(Object, Update): voice: str, quote: bool = None, caption: str = "", - parse_mode: str = "", + parse_mode: Union[str, None] = object, duration: int = 0, disable_notification: bool = None, reply_to_message_id: int = None, @@ -2333,23 +2332,22 @@ class Message(Object, Update): instructions to remove reply keyboard or to force a reply from the user. progress (``callable``, *optional*): - Pass a callback function to view the upload progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). + Pass a callback function to view the file transmission progress. + The function must take *(current, total)* as positional arguments (look at Other Parameters below for a + detailed description) and will be called back each time a new file chunk has been successfully + transmitted. progress_args (``tuple``, *optional*): - Extra custom arguments for the progress callback function. Useful, for example, if you want to pass - a chat_id and a message_id in order to edit a message with the updated progress. + Extra custom arguments for the progress callback function. + You can pass anything you need to be available in the progress callback scope; for example, a Message + object or a Client instance in order to edit the message with the updated progress status. 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. + The amount of bytes transmitted so far. total (``int``): - The size of the file. + The total size of the file. *args (``tuple``, *optional*): Extra custom arguments as defined in the *progress_args* parameter. @@ -2384,7 +2382,7 @@ class Message(Object, Update): def edit_text( self, text: str, - parse_mode: str = "", + parse_mode: Union[str, None] = object, disable_web_page_preview: bool = None, reply_markup: "pyrogram.InlineKeyboardMarkup" = None ) -> "Message": @@ -2442,7 +2440,7 @@ class Message(Object, Update): def edit_caption( self, caption: str, - parse_mode: str = "", + parse_mode: Union[str, None] = object, reply_markup: "pyrogram.InlineKeyboardMarkup" = None ) -> "Message": """Bound method *edit_caption* of :obj:`Message`. @@ -2619,9 +2617,6 @@ class Message(Object, Update): if self.game and not self._client.is_bot: raise ValueError("Users cannot send messages with Game media type") - # TODO: Improve markdown parser. Currently html appears to be more stable, thus we use it here because users - # can"t choose. - if self.text: return self._client.send_message( chat_id, @@ -2631,7 +2626,7 @@ class Message(Object, Update): disable_notification=disable_notification ) elif self.media: - caption = self.caption.html if self.caption and not remove_caption else None + caption = self.caption.html if self.caption and not remove_caption else "" send_media = partial( self._client.send_cached_media, @@ -2869,6 +2864,37 @@ class Message(Object, Update): else: self.reply(button, quote=quote) + def retract_vote( + self, + ) -> "pyrogram.Poll": + """Bound method *retract_vote* of :obj:`Message`. + + Use as a shortcut for: + + .. code-block:: python + + client.retract_vote( + chat_id=message.chat.id, + message_id=message_id, + ) + + Example: + .. code-block:: python + + message.retract_vote() + + Returns: + :obj:`Poll`: On success, the poll with the retracted vote is returned. + + Raises: + RPCError: In case of a Telegram RPC error. + """ + + return self._client.retract_vote( + chat_id=self.chat.id, + message_id=self.message_id + ) + def download( self, file_name: str = "", @@ -2900,14 +2926,27 @@ class Message(Object, Update): Blocks the code execution until the file has been downloaded. Defaults to True. - progress (``callable``): - Pass a callback function to view the download progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). + progress (``callable``, *optional*): + Pass a callback function to view the file transmission progress. + The function must take *(current, total)* as positional arguments (look at Other Parameters below for a + detailed description) and will be called back each time a new file chunk has been successfully + transmitted. - 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. + progress_args (``tuple``, *optional*): + Extra custom arguments for the progress callback function. + You can pass anything you need to be available in the progress callback scope; for example, a Message + object or a Client instance in order to edit the message with the updated progress status. + + Other Parameters: + current (``int``): + The amount of bytes transmitted so far. + + total (``int``): + The total size of the file. + + *args (``tuple``, *optional*): + Extra custom arguments as defined in the *progress_args* parameter. + You can either keep *\*args* or add every single extra argument in your function signature. Returns: On success, the absolute path of the downloaded file as string is returned, None otherwise. @@ -2924,6 +2963,44 @@ class Message(Object, Update): progress_args=progress_args, ) + def vote( + self, + option: int, + ) -> "pyrogram.Poll": + """Bound method *vote* of :obj:`Message`. + + Use as a shortcut for: + + .. code-block:: python + + client.vote_poll( + chat_id=message.chat.id, + message_id=message.message_id, + option=1 + ) + + Example: + .. code-block:: python + + message.vote(6) + + Parameters: + option (``int``): + Index of the poll option you want to vote for (0 to 9). + + Returns: + :obj:`Poll`: On success, the poll with the chosen option is returned. + + Raises: + RPCError: In case of a Telegram RPC error. + """ + + return self._client.vote_poll( + chat_id=self.chat.id, + message_id=self.message_id, + option=option + ) + def pin(self, disable_notification: bool = None) -> "Message": """Bound method *pin* of :obj:`Message`. diff --git a/pyrogram/client/types/messages_and_media/message_entity.py b/pyrogram/client/types/messages_and_media/message_entity.py index 1c3076a2..63aeb447 100644 --- a/pyrogram/client/types/messages_and_media/message_entity.py +++ b/pyrogram/client/types/messages_and_media/message_entity.py @@ -47,8 +47,6 @@ class MessageEntity(Object): For "text_mention" only, the mentioned user. """ - __slots__ = ["type", "offset", "length", "url", "user"] - ENTITIES = { types.MessageEntityMention.ID: "mention", types.MessageEntityHashtag.ID: "hashtag", diff --git a/pyrogram/client/types/messages_and_media/photo.py b/pyrogram/client/types/messages_and_media/photo.py index 653fe4c0..8ccaaf19 100644 --- a/pyrogram/client/types/messages_and_media/photo.py +++ b/pyrogram/client/types/messages_and_media/photo.py @@ -49,8 +49,6 @@ class Photo(Object): Available thumbnails of this photo. """ - __slots__ = ["file_id", "width", "height", "file_size", "date", "thumbs"] - def __init__( self, *, diff --git a/pyrogram/client/types/messages_and_media/poll.py b/pyrogram/client/types/messages_and_media/poll.py index 2570fdf1..fecc5f7d 100644 --- a/pyrogram/client/types/messages_and_media/poll.py +++ b/pyrogram/client/types/messages_and_media/poll.py @@ -48,8 +48,6 @@ class Poll(Object, Update): Index of your chosen option (0-9), None in case you haven't voted yet. """ - __slots__ = ["id", "question", "options", "is_closed", "total_voters", "chosen_option"] - def __init__( self, *, diff --git a/pyrogram/client/types/messages_and_media/poll_option.py b/pyrogram/client/types/messages_and_media/poll_option.py index 35f6b071..2882860a 100644 --- a/pyrogram/client/types/messages_and_media/poll_option.py +++ b/pyrogram/client/types/messages_and_media/poll_option.py @@ -35,8 +35,6 @@ class PollOption(Object): The data this poll option is holding. """ - __slots__ = ["text", "voter_count", "data"] - def __init__( self, *, diff --git a/pyrogram/client/types/messages_and_media/sticker.py b/pyrogram/client/types/messages_and_media/sticker.py index 78fdda38..cb5c34b2 100644 --- a/pyrogram/client/types/messages_and_media/sticker.py +++ b/pyrogram/client/types/messages_and_media/sticker.py @@ -41,6 +41,9 @@ class Sticker(Object): height (``int``): Sticker height. + is_animated (``bool``): + True, if the sticker is animated + file_name (``str``, *optional*): Sticker file name. @@ -65,10 +68,6 @@ class Sticker(Object): # TODO: Add mask position - __slots__ = [ - "file_id", "file_name", "mime_type", "file_size", "date", "width", "height", "emoji", "set_name", "thumbs" - ] - def __init__( self, *, @@ -76,6 +75,7 @@ class Sticker(Object): file_id: str, width: int, height: int, + is_animated: bool, file_name: str = None, mime_type: str = None, file_size: int = None, @@ -93,6 +93,7 @@ class Sticker(Object): self.date = date self.width = width self.height = height + self.is_animated = is_animated self.emoji = emoji self.set_name = set_name self.thumbs = thumbs @@ -134,8 +135,9 @@ class Sticker(Object): sticker.access_hash ) ), - width=image_size_attributes.w if image_size_attributes else 0, - height=image_size_attributes.h if image_size_attributes else 0, + width=image_size_attributes.w if image_size_attributes else 512, + height=image_size_attributes.h if image_size_attributes else 512, + is_animated=sticker.mime_type == "application/x-tgsticker", # TODO: mask_position set_name=set_name, emoji=sticker_attributes.alt or None, diff --git a/pyrogram/client/types/messages_and_media/stripped_thumbnail.py b/pyrogram/client/types/messages_and_media/stripped_thumbnail.py index 1c967042..ea24e071 100644 --- a/pyrogram/client/types/messages_and_media/stripped_thumbnail.py +++ b/pyrogram/client/types/messages_and_media/stripped_thumbnail.py @@ -29,8 +29,6 @@ class StrippedThumbnail(Object): Thumbnail data """ - __slots__ = ["data"] - def __init__( self, *, diff --git a/pyrogram/client/types/messages_and_media/thumbnail.py b/pyrogram/client/types/messages_and_media/thumbnail.py index ee173b1c..936241c6 100644 --- a/pyrogram/client/types/messages_and_media/thumbnail.py +++ b/pyrogram/client/types/messages_and_media/thumbnail.py @@ -43,8 +43,6 @@ class Thumbnail(Object): File size. """ - __slots__ = ["file_id", "width", "height", "file_size"] - def __init__( self, *, diff --git a/pyrogram/client/types/messages_and_media/venue.py b/pyrogram/client/types/messages_and_media/venue.py index 45d9368f..419af318 100644 --- a/pyrogram/client/types/messages_and_media/venue.py +++ b/pyrogram/client/types/messages_and_media/venue.py @@ -44,8 +44,6 @@ class Venue(Object): """ - __slots__ = ["location", "title", "address", "foursquare_id", "foursquare_type"] - def __init__( self, *, diff --git a/pyrogram/client/types/messages_and_media/video.py b/pyrogram/client/types/messages_and_media/video.py index 0a7f47cd..d9c2c37f 100644 --- a/pyrogram/client/types/messages_and_media/video.py +++ b/pyrogram/client/types/messages_and_media/video.py @@ -61,11 +61,6 @@ class Video(Object): Video thumbnails. """ - __slots__ = [ - "file_id", "width", "height", "duration", "file_name", "mime_type", "supports_streaming", "file_size", "date", - "thumbs" - ] - def __init__( self, *, diff --git a/pyrogram/client/types/messages_and_media/video_note.py b/pyrogram/client/types/messages_and_media/video_note.py index 54c9ec8d..e419d692 100644 --- a/pyrogram/client/types/messages_and_media/video_note.py +++ b/pyrogram/client/types/messages_and_media/video_note.py @@ -52,8 +52,6 @@ class VideoNote(Object): Video thumbnails. """ - __slots__ = ["file_id", "mime_type", "file_size", "date", "length", "duration", "thumbs"] - def __init__( self, *, diff --git a/pyrogram/client/types/messages_and_media/voice.py b/pyrogram/client/types/messages_and_media/voice.py index e4256197..0f480ad5 100644 --- a/pyrogram/client/types/messages_and_media/voice.py +++ b/pyrogram/client/types/messages_and_media/voice.py @@ -47,8 +47,6 @@ class Voice(Object): Date the voice was sent in Unix time. """ - __slots__ = ["file_id", "duration", "waveform", "mime_type", "file_size", "date"] - def __init__( self, *, diff --git a/pyrogram/client/types/messages_and_media/webpage.py b/pyrogram/client/types/messages_and_media/webpage.py index 9ddc7c6c..d65d34d8 100644 --- a/pyrogram/client/types/messages_and_media/webpage.py +++ b/pyrogram/client/types/messages_and_media/webpage.py @@ -83,12 +83,6 @@ class WebPage(Object): Author of the webpage, eg the Twitter user for a tweet, or the author in an article. """ - __slots__ = [ - "id", "url", "display_url", "type", "site_name", "title", "description", - "audio", "document", "photo", "animation", "video", - "embed_url", "embed_type", "embed_width", "embed_height", "duration", "author" - ] - def __init__( self, *, diff --git a/pyrogram/client/types/object.py b/pyrogram/client/types/object.py index f7fc413f..5978203f 100644 --- a/pyrogram/client/types/object.py +++ b/pyrogram/client/types/object.py @@ -29,8 +29,6 @@ class Meta(type, metaclass=type("", (type,), {"__str__": lambda _: "~hi"})): class Object(metaclass=Meta): - __slots__ = ["_client"] - def __init__(self, client: "pyrogram.BaseClient" = None): self._client = client @@ -50,7 +48,7 @@ class Object(metaclass=Meta): else (attr, str(datetime.fromtimestamp(getattr(obj, attr)))) if attr.endswith("date") else (attr, getattr(obj, attr)) - for attr in getattr(obj, "__slots__", []) + for attr in filter(lambda x: not x.startswith("_"), obj.__dict__) if getattr(obj, attr) is not None ] ) @@ -63,13 +61,13 @@ class Object(metaclass=Meta): self.__class__.__name__, ", ".join( "{}={}".format(attr, repr(getattr(self, attr))) - for attr in self.__slots__ + for attr in filter(lambda x: not x.startswith("_"), self.__dict__) if getattr(self, attr) is not None ) ) def __eq__(self, other: "Object") -> bool: - for attr in self.__slots__: + for attr in self.__dict__: try: if getattr(self, attr) != getattr(other, attr): return False diff --git a/pyrogram/client/types/update.py b/pyrogram/client/types/update.py index 48179ac0..2ec22f5a 100644 --- a/pyrogram/client/types/update.py +++ b/pyrogram/client/types/update.py @@ -26,8 +26,6 @@ class ContinuePropagation(StopIteration): class Update: - __slots__ = [] - def stop_propagation(self): raise StopPropagation diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py index cac5d0c7..546485f6 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -23,6 +23,7 @@ from pyrogram.api import types from .chat_permissions import ChatPermissions from .chat_photo import ChatPhoto from ..object import Object +from ...ext import utils class Chat(Object): @@ -91,15 +92,9 @@ class Chat(Object): This field is available only in case *is_restricted* is True. permissions (:obj:`ChatPermissions` *optional*): - Information about the chat default permissions, for groups and supergroups. + Default chat member permissions, for groups and supergroups. """ - __slots__ = [ - "id", "type", "is_verified", "is_restricted", "is_scam", "is_support", "title", "username", "first_name", - "last_name", "photo", "description", "invite_link", "pinned_message", "sticker_set_name", "can_set_sticker_set", - "members_count", "restriction_reason", "permissions" - ] - def __init__( self, *, @@ -180,7 +175,7 @@ class Chat(Object): @staticmethod def _parse_channel_chat(client, channel: types.Channel) -> "Chat": - peer_id = int("-100" + str(channel.id)) + peer_id = utils.get_channel_id(channel.id) return Chat( id=peer_id, @@ -236,6 +231,7 @@ class Chat(Object): if isinstance(full_chat, types.ChatFull): parsed_chat = Chat._parse_chat_chat(client, chat) + parsed_chat.description = full_chat.about or None if isinstance(full_chat.participants, types.ChatParticipants): parsed_chat.members_count = len(full_chat.participants.participants) @@ -672,7 +668,7 @@ class Chat(Object): can_pin_messages=can_pin_messages, can_promote_members=can_promote_members ) - + def join(self): """Bound method *join* of :obj:`Chat`. diff --git a/pyrogram/client/types/user_and_chats/chat_member.py b/pyrogram/client/types/user_and_chats/chat_member.py index 7451012c..812a3204 100644 --- a/pyrogram/client/types/user_and_chats/chat_member.py +++ b/pyrogram/client/types/user_and_chats/chat_member.py @@ -33,11 +33,13 @@ class ChatMember(Object): The member's status in the chat. Can be "creator", "administrator", "member", "restricted", "left" or "kicked". - date (``int``, *optional*): - Date when the user joined, unix time. Not available for creator. + until_date (``int``, *optional*): + Restricted and kicked only. + Date when restrictions will be lifted for this user; unix time. - is_member (``bool``, *optional*): - Restricted only. True, if the user is a member of the chat at the moment of the request. + joined_date (``int``, *optional*): + Date when the user joined, unix time. + Not available for creator. invited_by (:obj:`User`, *optional*): Administrators and self member only. Information about the user who invited this member. @@ -49,12 +51,67 @@ class ChatMember(Object): restricted_by (:obj:`User`, *optional*): Restricted and kicked only. Information about the user who restricted or kicked this member. - permissions (:obj:`ChatPermissions` *optional*): - Administrators, restricted and kicked members only. - Information about the member permissions. - """ + is_member (``bool``, *optional*): + Restricted only. True, if the user is a member of the chat at the moment of the request. - __slots__ = ["user", "status", "date", "is_member", "invited_by", "promoted_by", "restricted_by", "permissions"] + can_be_edited (``bool``, *optional*): + Administrators only. + True, if you are allowed to edit administrator privileges of the user. + + can_post_messages (``bool``, *optional*): + Administrators only. Channels only. + True, if the administrator can post messages in the channel. + + can_edit_messages (``bool``, *optional*): + Administrators only. Channels only. + True, if the administrator can edit messages of other users and can pin messages. + + can_delete_messages (``bool``, *optional*): + Administrators only. + True, if the administrator can delete messages of other users. + + can_restrict_members (``bool``, *optional*): + Administrators only. + True, if the administrator can restrict, ban or unban chat members. + + can_promote_members (``bool``, *optional*): + Administrators only. + True, if the administrator can add new administrators with a subset of his own privileges or demote + administrators that he has promoted, directly or indirectly (promoted by administrators that were appointed + by the user). + + can_change_info (``bool``, *optional*): + Administrators and restricted only. + True, if the user is allowed to change the chat title, photo and other settings. + + can_invite_users (``bool``, *optional*): + Administrators and restricted only. + True, if the user is allowed to invite new users to the chat. + + can_pin_messages (``bool``, *optional*): + Administrators and restricted only. Groups and supergroups only. + True, if the user is allowed to pin messages. + + can_send_messages (``bool``, *optional*): + Restricted only. + True, if the user is allowed to send text messages, contacts, locations and venues. + + can_send_media_messages (``bool``, *optional*): + Restricted only. + True, if the user is allowed to send audios, documents, photos, videos, video notes and voice notes. + + can_send_other_messages (``bool``, *optional*): + Restricted only. + True, if the user is allowed to send animations, games, stickers and use inline bots. + + can_add_web_page_previews (``bool``, *optional*): + Restricted only. + True, if the user is allowed to add web page previews to their messages. + + can_send_polls (``bool``, *optional*): + Restricted only. + True, if the user is allowed to send polls. + """ def __init__( self, @@ -62,23 +119,57 @@ class ChatMember(Object): client: "pyrogram.BaseClient" = None, user: "pyrogram.User", status: str, - date: int = None, - is_member: bool = None, + until_date: int = None, + joined_date: int = None, invited_by: "pyrogram.User" = None, promoted_by: "pyrogram.User" = None, restricted_by: "pyrogram.User" = None, - permissions: "pyrogram.ChatPermissions" = None + is_member: bool = None, + + # Admin permissions + can_be_edited: bool = None, + can_post_messages: bool = None, # Channels only + can_edit_messages: bool = None, # Channels only + can_delete_messages: bool = None, + can_restrict_members: bool = None, + can_promote_members: bool = None, + can_change_info: bool = None, + can_invite_users: bool = None, + can_pin_messages: bool = None, # Groups and supergroups only + + # Restricted user permissions + can_send_messages: bool = None, # Text, contacts, locations and venues + can_send_media_messages: bool = None, # Audios, documents, photos, videos, video notes and voice notes + can_send_other_messages: bool = None, # Animations (GIFs), games, stickers, inline bot results + can_add_web_page_previews: bool = None, + can_send_polls: bool = None ): super().__init__(client) self.user = user self.status = status - self.date = date - self.is_member = is_member + self.until_date = until_date + self.joined_date = joined_date self.invited_by = invited_by self.promoted_by = promoted_by self.restricted_by = restricted_by - self.permissions = permissions + self.is_member = is_member + + self.can_be_edited = can_be_edited + self.can_post_messages = can_post_messages + self.can_edit_messages = can_edit_messages + self.can_delete_messages = can_delete_messages + self.can_restrict_members = can_restrict_members + self.can_promote_members = can_promote_members + self.can_change_info = can_change_info + self.can_invite_users = can_invite_users + self.can_pin_messages = can_pin_messages + + self.can_send_messages = can_send_messages + self.can_send_media_messages = can_send_media_messages + self.can_send_other_messages = can_send_other_messages + self.can_add_web_page_previews = can_add_web_page_previews + self.can_send_polls = can_send_polls @staticmethod def _parse(client, member, users) -> "ChatMember": @@ -93,7 +184,7 @@ class ChatMember(Object): return ChatMember( user=user, status="member", - date=member.date, + joined_date=member.date, invited_by=invited_by, client=client ) @@ -109,29 +200,52 @@ class ChatMember(Object): return ChatMember( user=user, status="administrator", - date=member.date, + joined_date=member.date, invited_by=invited_by, client=client ) if isinstance(member, types.ChannelParticipantAdmin): + permissions = member.admin_rights + return ChatMember( user=user, status="administrator", - date=member.date, + joined_date=member.date, invited_by=invited_by, promoted_by=pyrogram.User._parse(client, users[member.promoted_by]), - permissions=pyrogram.ChatPermissions._parse(member), + can_be_edited=member.can_edit, + can_change_info=permissions.change_info, + can_post_messages=permissions.post_messages, + can_edit_messages=permissions.edit_messages, + can_delete_messages=permissions.delete_messages, + can_restrict_members=permissions.ban_users, + can_invite_users=permissions.invite_users, + can_pin_messages=permissions.pin_messages, + can_promote_members=permissions.add_admins, client=client ) if isinstance(member, types.ChannelParticipantBanned): + denied_permissions = member.banned_rights + return ChatMember( user=user, status="kicked" if member.banned_rights.view_messages else "restricted", - date=member.date, + until_date=denied_permissions.until_date, + joined_date=member.date, is_member=not member.left, restricted_by=pyrogram.User._parse(client, users[member.kicked_by]), - permissions=pyrogram.ChatPermissions._parse(member), + can_send_messages=not denied_permissions.send_messages, + can_send_media_messages=not denied_permissions.send_media, + can_send_other_messages=( + not denied_permissions.send_stickers or not denied_permissions.send_gifs or + not denied_permissions.send_games or not denied_permissions.send_inline + ), + can_add_web_page_previews=not denied_permissions.embed_links, + can_send_polls=not denied_permissions.send_polls, + can_change_info=not denied_permissions.change_info, + can_invite_users=not denied_permissions.invite_users, + can_pin_messages=not denied_permissions.pin_messages, client=client ) diff --git a/pyrogram/client/types/user_and_chats/chat_permissions.py b/pyrogram/client/types/user_and_chats/chat_permissions.py index 84099955..09c33089 100644 --- a/pyrogram/client/types/user_and_chats/chat_permissions.py +++ b/pyrogram/client/types/user_and_chats/chat_permissions.py @@ -16,8 +16,6 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union - from pyrogram.api import types from ..object import Object @@ -29,152 +27,62 @@ class ChatPermissions(Object): administrators in groups or channels. Parameters: - until_date (``int``, *optional*): - Applicable to restricted and kicked members only. - Date when user restrictions will be lifted, unix time. - 0 means the restrictions will never be lifted (user restricted forever). - - can_be_edited (``bool``, *optional*): - Applicable to administrators only. - True, if you are allowed to edit administrator privileges of the user. - - can_change_info (``bool``, *optional*): - Applicable to default chat permissions in private groups and administrators in public groups only. - True, if the chat title, photo and other settings can be changed. - - can_post_messages (``bool``, *optional*): - Applicable to channel administrators only. - True, if the administrator can post messages in the channel, channels only. - - can_edit_messages (``bool``, *optional*): - Applicable to channel administrators only. - True, if the administrator can edit messages of other users and can pin messages, channels only. - - can_delete_messages (``bool``, *optional*): - Applicable to administrators only. - True, if the administrator can delete messages of other users. - - can_restrict_members (``bool``, *optional*): - Applicable to administrators only. - True, if the administrator can restrict, ban or unban chat members. - - can_invite_users (``bool``, *optional*): - Applicable to default chat permissions and administrators only. - True, if new users can be invited to the chat. - - can_pin_messages (``bool``, *optional*): - Applicable to default chat permissions in private groups and administrators in public groups only. - True, if messages can be pinned, supergroups only. - - can_promote_members (``bool``, *optional*): - Applicable to administrators only. - True, if the administrator can add new administrators with a subset of his own privileges or demote - administrators that he has promoted, directly or indirectly (promoted by administrators that were appointed - by the user). - can_send_messages (``bool``, *optional*): - Applicable to default chat permissions and restricted members only. - True, if text messages, contacts, locations and venues can be sent. + True, if the user is allowed to send text messages, contacts, locations and venues. can_send_media_messages (``bool``, *optional*): - Applicable to default chat permissions and restricted members only. - True, if audios, documents, photos, videos, video notes and voice notes can be sent, implies + True, if the user is allowed to send audios, documents, photos, videos, video notes and voice notes, implies can_send_messages. can_send_other_messages (``bool``, *optional*): - Applicable to default chat permissions and restricted members only. - True, if animations, games, stickers and inline bot results can be sent, implies can_send_media_messages. + True, if the user is allowed to send animations, games, stickers and use inline bots, implies + can_send_media_messages can_add_web_page_previews (``bool``, *optional*): - Applicable to default chat permissions and restricted members only. - True, if web page previews can be attached to text messages, implies can_send_media_messages. + True, if the user is allowed to add web page previews to their messages, implies can_send_media_messages. can_send_polls (``bool``, *optional*): - Applicable to default chat permissions and restricted members only. - True, if polls can be sent, implies can_send_media_messages. - """ + True, if the user is allowed to send polls, implies can_send_messages. - __slots__ = [ - "until_date", "can_be_edited", "can_change_info", "can_post_messages", "can_edit_messages", - "can_delete_messages", "can_restrict_members", "can_invite_users", "can_pin_messages", "can_promote_members", - "can_send_messages", "can_send_media_messages", "can_send_other_messages", "can_add_web_page_previews", - "can_send_polls" - ] + can_change_info (``bool``, *optional*): + True, if the user is allowed to change the chat title, photo and other settings. + Ignored in public supergroups. + + can_invite_users (``bool``, *optional*): + True, if the user is allowed to invite new users to the chat. + + can_pin_messages (``bool``, *optional*): + True, if the user is allowed to pin messages. + Ignored in public supergroups. + """ def __init__( self, *, - until_date: int = None, - - # Admin permissions - can_be_edited: bool = None, - can_change_info: bool = None, - can_post_messages: bool = None, # Channels only - can_edit_messages: bool = None, # Channels only - can_delete_messages: bool = None, - can_restrict_members: bool = None, - can_invite_users: bool = None, - can_pin_messages: bool = None, # Supergroups only - can_promote_members: bool = None, - - # Restricted user permissions can_send_messages: bool = None, # Text, contacts, locations and venues can_send_media_messages: bool = None, # Audios, documents, photos, videos, video notes and voice notes can_send_other_messages: bool = None, # Animations (GIFs), games, stickers, inline bot results can_add_web_page_previews: bool = None, - can_send_polls: bool = None + can_send_polls: bool = None, + can_change_info: bool = None, + can_invite_users: bool = None, + can_pin_messages: bool = None ): super().__init__(None) - self.until_date = until_date - self.can_be_edited = can_be_edited - - self.can_change_info = can_change_info - self.can_post_messages = can_post_messages - self.can_edit_messages = can_edit_messages - self.can_delete_messages = can_delete_messages - self.can_restrict_members = can_restrict_members - self.can_invite_users = can_invite_users - self.can_pin_messages = can_pin_messages - self.can_promote_members = can_promote_members - self.can_send_messages = can_send_messages self.can_send_media_messages = can_send_media_messages self.can_send_other_messages = can_send_other_messages self.can_add_web_page_previews = can_add_web_page_previews self.can_send_polls = can_send_polls + self.can_change_info = can_change_info + self.can_invite_users = can_invite_users + self.can_pin_messages = can_pin_messages @staticmethod - def _parse( - entity: Union[ - types.ChannelParticipantAdmin, - types.ChannelParticipantBanned, - types.ChatBannedRights - ] - ) -> "ChatPermissions": - if isinstance(entity, types.ChannelParticipantAdmin): - permissions = entity.admin_rights - + def _parse(denied_permissions: types.ChatBannedRights) -> "ChatPermissions": + if isinstance(denied_permissions, types.ChatBannedRights): return ChatPermissions( - can_be_edited=entity.can_edit, - can_change_info=permissions.change_info, - can_post_messages=permissions.post_messages, - can_edit_messages=permissions.edit_messages, - can_delete_messages=permissions.delete_messages, - can_restrict_members=permissions.ban_users, - can_invite_users=permissions.invite_users, - can_pin_messages=permissions.pin_messages, - can_promote_members=permissions.add_admins - ) - - if isinstance(entity, (types.ChannelParticipantBanned, types.ChatBannedRights)): - if isinstance(entity, types.ChannelParticipantBanned): - denied_permissions = entity.banned_rights # type: types.ChatBannedRights - else: - denied_permissions = entity - - return ChatPermissions( - until_date=0 if denied_permissions.until_date == (1 << 31) - 1 else denied_permissions.until_date, can_send_messages=not denied_permissions.send_messages, can_send_media_messages=not denied_permissions.send_media, can_send_other_messages=( diff --git a/pyrogram/client/types/user_and_chats/chat_photo.py b/pyrogram/client/types/user_and_chats/chat_photo.py index 1584a286..623aaca8 100644 --- a/pyrogram/client/types/user_and_chats/chat_photo.py +++ b/pyrogram/client/types/user_and_chats/chat_photo.py @@ -29,14 +29,14 @@ class ChatPhoto(Object): Parameters: small_file_id (``str``): - Unique file identifier of small (160x160) chat photo. This file_id can be used only for photo download. + File identifier of small (160x160) chat photo. + This file_id can be used only for photo download and only for as long as the photo is not changed. big_file_id (``str``): - Unique file identifier of big (640x640) chat photo. This file_id can be used only for photo download. + File identifier of big (640x640) chat photo. + This file_id can be used only for photo download and only for as long as the photo is not changed. """ - __slots__ = ["small_file_id", "big_file_id"] - def __init__( self, *, diff --git a/pyrogram/client/types/user_and_chats/chat_preview.py b/pyrogram/client/types/user_and_chats/chat_preview.py index 312bdfe6..10754170 100644 --- a/pyrogram/client/types/user_and_chats/chat_preview.py +++ b/pyrogram/client/types/user_and_chats/chat_preview.py @@ -20,7 +20,7 @@ from typing import List import pyrogram from pyrogram.api import types -from .chat_photo import ChatPhoto +from ..messages_and_media import Photo from ..object import Object from ..user_and_chats.user import User @@ -32,48 +32,46 @@ class ChatPreview(Object): title (``str``): Title of the chat. - photo (:obj:`ChatPhoto`, *optional*): - Chat photo. Suitable for downloads only. - type (``str``): Type of chat, can be either, "group", "supergroup" or "channel". members_count (``int``): Chat members count. + photo (:obj:`Photo`, *optional*): + Chat photo. + members (List of :obj:`User`, *optional*): Preview of some of the chat members. """ - __slots__ = ["title", "photo", "type", "members_count", "members"] - def __init__( self, *, client: "pyrogram.BaseClient" = None, title: str, - photo: ChatPhoto = None, type: str, members_count: int, + photo: Photo = None, members: List[User] = None ): super().__init__(client) self.title = title - self.photo = photo self.type = type self.members_count = members_count + self.photo = photo self.members = members @staticmethod def _parse(client, chat_invite: types.ChatInvite) -> "ChatPreview": return ChatPreview( title=chat_invite.title, - photo=ChatPhoto._parse(client, chat_invite.photo), type=("group" if not chat_invite.channel else "channel" if chat_invite.broadcast else "supergroup"), members_count=chat_invite.participants_count, + photo=Photo._parse(client, chat_invite.photo), members=[User._parse(client, user) for user in chat_invite.participants] or None, client=client ) diff --git a/pyrogram/client/types/user_and_chats/dialog.py b/pyrogram/client/types/user_and_chats/dialog.py index 4ea82184..471c4319 100644 --- a/pyrogram/client/types/user_and_chats/dialog.py +++ b/pyrogram/client/types/user_and_chats/dialog.py @@ -21,6 +21,7 @@ import pyrogram from pyrogram.api import types from ..object import Object from ..user_and_chats import Chat +from ...ext import utils class Dialog(Object): @@ -46,8 +47,6 @@ class Dialog(Object): True, if the dialog is pinned. """ - __slots__ = ["chat", "top_message", "unread_messages_count", "unread_mentions_count", "unread_mark", "is_pinned"] - def __init__( self, *, @@ -70,18 +69,9 @@ class Dialog(Object): @staticmethod def _parse(client, dialog: types.Dialog, messages, users, chats) -> "Dialog": - chat_id = dialog.peer - - if isinstance(chat_id, types.PeerUser): - chat_id = chat_id.user_id - elif isinstance(chat_id, types.PeerChat): - chat_id = -chat_id.chat_id - else: - chat_id = int("-100" + str(chat_id.channel_id)) - return Dialog( chat=Chat._parse_dialog(client, dialog.peer, users, chats), - top_message=messages.get(chat_id), + top_message=messages.get(utils.get_peer_id(dialog.peer)), unread_messages_count=dialog.unread_count, unread_mentions_count=dialog.unread_mentions_count, unread_mark=dialog.unread_mark, diff --git a/pyrogram/client/types/user_and_chats/user.py b/pyrogram/client/types/user_and_chats/user.py index 43baca25..783c0566 100644 --- a/pyrogram/client/types/user_and_chats/user.py +++ b/pyrogram/client/types/user_and_chats/user.py @@ -106,12 +106,6 @@ class User(Object, Update): This field is available only in case *is_restricted* is True. """ - __slots__ = [ - "id", "is_self", "is_contact", "is_mutual_contact", "is_deleted", "is_bot", "is_verified", "is_restricted", - "is_scam", "is_support", "first_name", "last_name", "status", "last_online_date", "next_offline_date", - "username", "language_code", "dc_id", "phone_number", "photo", "restriction_reason" - ] - def __init__( self, *, diff --git a/pyrogram/errors/rpc_error.py b/pyrogram/errors/rpc_error.py index 806b5373..9969d3fa 100644 --- a/pyrogram/errors/rpc_error.py +++ b/pyrogram/errors/rpc_error.py @@ -21,8 +21,9 @@ from datetime import datetime from importlib import import_module from typing import Type -from pyrogram.api.core import TLObject from pyrogram.api.types import RpcError as RawRPCError + +from pyrogram.api.core import TLObject from .exceptions.all import exceptions diff --git a/pyrogram/session/auth.py b/pyrogram/session/auth.py index b05b2855..f6d137fa 100644 --- a/pyrogram/session/auth.py +++ b/pyrogram/session/auth.py @@ -27,7 +27,6 @@ from pyrogram.api import functions, types from pyrogram.api.core import TLObject, Long, Int from pyrogram.connection import Connection from pyrogram.crypto import AES, RSA, Prime - from .internals import MsgId log = logging.getLogger(__name__) diff --git a/pyrogram/session/internals/msg_factory.py b/pyrogram/session/internals/msg_factory.py index 2b833ce8..453eefc1 100644 --- a/pyrogram/session/internals/msg_factory.py +++ b/pyrogram/session/internals/msg_factory.py @@ -16,9 +16,10 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from pyrogram.api.core import Message, MsgContainer, TLObject from pyrogram.api.functions import Ping from pyrogram.api.types import MsgsAck, HttpWait + +from pyrogram.api.core import Message, MsgContainer, TLObject from .msg_id import MsgId from .seq_no import SeqNo diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 5947fc0f..689fe584 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -26,15 +26,15 @@ from os import urandom from queue import Queue from threading import Event, Thread +from pyrogram.api.all import layer + import pyrogram from pyrogram import __copyright__, __license__, __version__ from pyrogram.api import functions, types, core -from pyrogram.api.all import layer from pyrogram.api.core import Message, TLObject, MsgContainer, Long, FutureSalt, Int from pyrogram.connection import Connection from pyrogram.crypto import AES, KDF from pyrogram.errors import RPCError, InternalServerError, AuthKeyDuplicated - from .internals import MsgId, MsgFactory log = logging.getLogger(__name__) @@ -439,9 +439,9 @@ class Session: if retries == 0: raise e from None - (log.warning if retries < 3 else log.info)( + (log.warning if retries < 2 else log.info)( "{}: {} Retrying {}".format( - Session.MAX_RETRIES - retries, + Session.MAX_RETRIES - retries + 1, datetime.now(), type(data))) time.sleep(0.5)