From 9d32b28f94e4c0f263e80e758fa530a3471cf91b Mon Sep 17 00:00:00 2001 From: bakatrouble Date: Thu, 21 Feb 2019 20:12:11 +0300 Subject: [PATCH 001/202] Implement extendable session storage and JSON session storage --- pyrogram/client/client.py | 59 ++------- pyrogram/client/ext/base_client.py | 14 +-- pyrogram/client/ext/syncer.py | 41 +------ pyrogram/client/session_storage/__init__.py | 21 ++++ .../session_storage/base_session_storage.py | 50 ++++++++ .../session_storage/json_session_storage.py | 116 ++++++++++++++++++ .../session_storage/session_storage_mixin.py | 73 +++++++++++ 7 files changed, 278 insertions(+), 96 deletions(-) create mode 100644 pyrogram/client/session_storage/__init__.py create mode 100644 pyrogram/client/session_storage/base_session_storage.py create mode 100644 pyrogram/client/session_storage/json_session_storage.py create mode 100644 pyrogram/client/session_storage/session_storage_mixin.py diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index f62c046c..9a9f8482 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -36,7 +36,7 @@ from importlib import import_module from pathlib import Path from signal import signal, SIGINT, SIGTERM, SIGABRT from threading import Thread -from typing import Union, List +from typing import Union, List, Type from pyrogram.api import functions, types from pyrogram.api.core import Object @@ -56,6 +56,7 @@ from pyrogram.session import Auth, Session from .dispatcher import Dispatcher from .ext import utils, Syncer, BaseClient from .methods import Methods +from .session_storage import BaseSessionStorage, JsonSessionStorage, SessionDoesNotExist log = logging.getLogger(__name__) @@ -199,8 +200,9 @@ class Client(Methods, BaseClient): config_file: str = BaseClient.CONFIG_FILE, plugins: dict = None, no_updates: bool = None, - takeout: bool = None): - super().__init__() + takeout: bool = None, + session_storage_cls: Type[BaseSessionStorage] = JsonSessionStorage): + super().__init__(session_storage_cls(self)) self.session_name = session_name self.api_id = int(api_id) if api_id else None @@ -296,8 +298,8 @@ class Client(Methods, BaseClient): now = time.time() if abs(now - self.date) > Client.OFFLINE_SLEEP: - self.peers_by_username = {} - self.peers_by_phone = {} + self.peers_by_username.clear() + self.peers_by_phone.clear() self.get_initial_dialogs() self.get_contacts() @@ -1101,33 +1103,10 @@ class Client(Methods, BaseClient): def load_session(self): try: - with open(os.path.join(self.workdir, "{}.session".format(self.session_name)), encoding="utf-8") as f: - s = json.load(f) - except FileNotFoundError: - self.dc_id = 1 - self.date = 0 + self.session_storage.load_session(self.session_name) + except SessionDoesNotExist: + log.info('Session {} was not found, initializing new one') self.auth_key = Auth(self.dc_id, self.test_mode, self.ipv6, self._proxy).create() - else: - self.dc_id = s["dc_id"] - self.test_mode = s["test_mode"] - self.auth_key = base64.b64decode("".join(s["auth_key"])) - self.user_id = s["user_id"] - self.date = s.get("date", 0) - - for k, v in s.get("peers_by_id", {}).items(): - self.peers_by_id[int(k)] = utils.get_input_peer(int(k), v) - - for k, v in s.get("peers_by_username", {}).items(): - peer = self.peers_by_id.get(v, None) - - if peer: - self.peers_by_username[k] = peer - - for k, v in s.get("peers_by_phone", {}).items(): - peer = self.peers_by_id.get(v, None) - - if peer: - self.peers_by_phone[k] = peer def load_plugins(self): if self.plugins.get("enabled", False): @@ -1234,23 +1213,7 @@ class Client(Methods, BaseClient): log.warning('No plugin loaded from "{}"'.format(root)) def save_session(self): - auth_key = base64.b64encode(self.auth_key).decode() - auth_key = [auth_key[i: i + 43] for i in range(0, len(auth_key), 43)] - - os.makedirs(self.workdir, exist_ok=True) - - with open(os.path.join(self.workdir, "{}.session".format(self.session_name)), "w", encoding="utf-8") as f: - json.dump( - dict( - dc_id=self.dc_id, - test_mode=self.test_mode, - auth_key=auth_key, - user_id=self.user_id, - date=self.date - ), - f, - indent=4 - ) + self.session_storage.save_session(self.session_name) def get_initial_dialogs_chunk(self, offset_date: int = 0): diff --git a/pyrogram/client/ext/base_client.py b/pyrogram/client/ext/base_client.py index d2c348a8..87f11e23 100644 --- a/pyrogram/client/ext/base_client.py +++ b/pyrogram/client/ext/base_client.py @@ -24,9 +24,10 @@ from threading import Lock from pyrogram import __version__ from ..style import Markdown, HTML from ...session.internals import MsgId +from ..session_storage import SessionStorageMixin, BaseSessionStorage -class BaseClient: +class BaseClient(SessionStorageMixin): class StopTransmission(StopIteration): pass @@ -67,20 +68,13 @@ class BaseClient: 13: "video_note" } - def __init__(self): + def __init__(self, session_storage: BaseSessionStorage): + self.session_storage = session_storage self.bot_token = None - self.dc_id = None - self.auth_key = None - self.user_id = None - self.date = None self.rnd_id = MsgId self.channels_pts = {} - self.peers_by_id = {} - self.peers_by_username = {} - self.peers_by_phone = {} - self.markdown = Markdown(self.peers_by_id) self.html = HTML(self.peers_by_id) diff --git a/pyrogram/client/ext/syncer.py b/pyrogram/client/ext/syncer.py index e169d2a3..8930b13e 100644 --- a/pyrogram/client/ext/syncer.py +++ b/pyrogram/client/ext/syncer.py @@ -81,47 +81,12 @@ class Syncer: @classmethod def sync(cls, client): - temporary = os.path.join(client.workdir, "{}.sync".format(client.session_name)) - persistent = os.path.join(client.workdir, "{}.session".format(client.session_name)) - + client.date = int(time.time()) try: - auth_key = base64.b64encode(client.auth_key).decode() - auth_key = [auth_key[i: i + 43] for i in range(0, len(auth_key), 43)] - - data = dict( - dc_id=client.dc_id, - test_mode=client.test_mode, - auth_key=auth_key, - user_id=client.user_id, - date=int(time.time()), - peers_by_id={ - k: getattr(v, "access_hash", None) - for k, v in client.peers_by_id.copy().items() - }, - peers_by_username={ - k: utils.get_peer_id(v) - for k, v in client.peers_by_username.copy().items() - }, - peers_by_phone={ - k: utils.get_peer_id(v) - for k, v in client.peers_by_phone.copy().items() - } - ) - - os.makedirs(client.workdir, exist_ok=True) - - with open(temporary, "w", encoding="utf-8") as f: - json.dump(data, f, indent=4) - - f.flush() - os.fsync(f.fileno()) + client.session_storage.save_session(client.session_name, sync=True) except Exception as e: log.critical(e, exc_info=True) else: - shutil.move(temporary, persistent) log.info("Synced {}".format(client.session_name)) finally: - try: - os.remove(temporary) - except OSError: - pass + client.session_storage.sync_cleanup(client.session_name) diff --git a/pyrogram/client/session_storage/__init__.py b/pyrogram/client/session_storage/__init__.py new file mode 100644 index 00000000..6ee92ebc --- /dev/null +++ b/pyrogram/client/session_storage/__init__.py @@ -0,0 +1,21 @@ +# 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 .session_storage_mixin import SessionStorageMixin +from .base_session_storage import BaseSessionStorage, SessionDoesNotExist +from .json_session_storage import JsonSessionStorage diff --git a/pyrogram/client/session_storage/base_session_storage.py b/pyrogram/client/session_storage/base_session_storage.py new file mode 100644 index 00000000..75e416b4 --- /dev/null +++ b/pyrogram/client/session_storage/base_session_storage.py @@ -0,0 +1,50 @@ +# 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 abc + +import pyrogram + + +class SessionDoesNotExist(Exception): + pass + + +class BaseSessionStorage(abc.ABC): + def __init__(self, client: 'pyrogram.client.BaseClient'): + self.client = client + self.dc_id = 1 + self.test_mode = None + self.auth_key = None + self.user_id = None + self.date = 0 + self.peers_by_id = {} + self.peers_by_username = {} + self.peers_by_phone = {} + + @abc.abstractmethod + def load_session(self, name: str): + ... + + @abc.abstractmethod + def save_session(self, name: str, sync=False): + ... + + @abc.abstractmethod + def sync_cleanup(self, name: str): + ... diff --git a/pyrogram/client/session_storage/json_session_storage.py b/pyrogram/client/session_storage/json_session_storage.py new file mode 100644 index 00000000..679a21f3 --- /dev/null +++ b/pyrogram/client/session_storage/json_session_storage.py @@ -0,0 +1,116 @@ +# 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 base64 +import json +import logging +import os +import shutil + +from ..ext import utils +from . import BaseSessionStorage, SessionDoesNotExist + + +log = logging.getLogger(__name__) + + +class JsonSessionStorage(BaseSessionStorage): + def _get_file_name(self, name: str): + if not name.endswith('.session'): + name += '.session' + return os.path.join(self.client.workdir, name) + + def load_session(self, name: str): + file_path = self._get_file_name(name) + log.info('Loading JSON session from {}'.format(file_path)) + + try: + with open(file_path, encoding='utf-8') as f: + s = json.load(f) + except FileNotFoundError: + raise SessionDoesNotExist() + + self.dc_id = s["dc_id"] + self.test_mode = s["test_mode"] + self.auth_key = base64.b64decode("".join(s["auth_key"])) # join split key + self.user_id = s["user_id"] + self.date = s.get("date", 0) + + for k, v in s.get("peers_by_id", {}).items(): + self.peers_by_id[int(k)] = utils.get_input_peer(int(k), v) + + for k, v in s.get("peers_by_username", {}).items(): + peer = self.peers_by_id.get(v, None) + + if peer: + self.peers_by_username[k] = peer + + for k, v in s.get("peers_by_phone", {}).items(): + peer = self.peers_by_id.get(v, None) + + if peer: + self.peers_by_phone[k] = peer + + def save_session(self, name: str, sync=False): + file_path = self._get_file_name(name) + + if sync: + file_path += '.tmp' + + log.info('Saving JSON session to {}, sync={}'.format(file_path, sync)) + + auth_key = base64.b64encode(self.auth_key).decode() + auth_key = [auth_key[i: i + 43] for i in range(0, len(auth_key), 43)] # split key in lines of 43 chars + + os.makedirs(self.client.workdir, exist_ok=True) + + data = { + 'dc_id': self.dc_id, + 'test_mode': self.test_mode, + 'auth_key': auth_key, + 'user_id': self.user_id, + 'date': self.date, + 'peers_by_id': { + k: getattr(v, "access_hash", None) + for k, v in self.peers_by_id.copy().items() + }, + 'peers_by_username': { + k: utils.get_peer_id(v) + for k, v in self.peers_by_username.copy().items() + }, + 'peers_by_phone': { + k: utils.get_peer_id(v) + for k, v in self.peers_by_phone.copy().items() + } + } + + with open(file_path, "w", encoding="utf-8") as f: + json.dump(data, f, indent=4) + + f.flush() + os.fsync(f.fileno()) + + # execution won't be here if an error has occurred earlier + if sync: + shutil.move(file_path, self._get_file_name(name)) + + def sync_cleanup(self, name: str): + try: + os.remove(self._get_file_name(name) + '.tmp') + except OSError: + pass diff --git a/pyrogram/client/session_storage/session_storage_mixin.py b/pyrogram/client/session_storage/session_storage_mixin.py new file mode 100644 index 00000000..bfe9a590 --- /dev/null +++ b/pyrogram/client/session_storage/session_storage_mixin.py @@ -0,0 +1,73 @@ +# 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 Dict + + +class SessionStorageMixin: + @property + def dc_id(self) -> int: + return self.session_storage.dc_id + + @dc_id.setter + def dc_id(self, val): + self.session_storage.dc_id = val + + @property + def test_mode(self) -> bool: + return self.session_storage.test_mode + + @test_mode.setter + def test_mode(self, val): + self.session_storage.test_mode = val + + @property + def auth_key(self) -> bytes: + return self.session_storage.auth_key + + @auth_key.setter + def auth_key(self, val): + self.session_storage.auth_key = val + + @property + def user_id(self): + return self.session_storage.user_id + + @user_id.setter + def user_id(self, val) -> int: + self.session_storage.user_id = val + + @property + def date(self) -> int: + return self.session_storage.date + + @date.setter + def date(self, val): + self.session_storage.date = val + + @property + def peers_by_id(self) -> Dict[str, int]: + return self.session_storage.peers_by_id + + @property + def peers_by_username(self) -> Dict[str, int]: + return self.session_storage.peers_by_username + + @property + def peers_by_phone(self) -> Dict[str, int]: + return self.session_storage.peers_by_phone From 431a983d5b66522604f0685ef078d40735cea64c Mon Sep 17 00:00:00 2001 From: bakatrouble Date: Thu, 21 Feb 2019 21:18:53 +0300 Subject: [PATCH 002/202] Fix logging and cleanup imports in client.py --- pyrogram/client/client.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 9a9f8482..0e8d5554 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -16,9 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import base64 import binascii -import json import logging import math import mimetypes @@ -1105,7 +1103,10 @@ class Client(Methods, BaseClient): try: self.session_storage.load_session(self.session_name) except SessionDoesNotExist: - log.info('Session {} was not found, initializing new one') + session_name = self.session_name[:32] + if session_name != self.session_name: + session_name += '...' + log.info('Could not load session "{}", initializing new one'.format(self.session_name)) self.auth_key = Auth(self.dc_id, self.test_mode, self.ipv6, self._proxy).create() def load_plugins(self): From b04cf9ec9297ce0884943879b1fac07fa7e2933f Mon Sep 17 00:00:00 2001 From: bakatrouble Date: Thu, 21 Feb 2019 21:43:57 +0300 Subject: [PATCH 003/202] Add string session storage --- pyrogram/client/session_storage/__init__.py | 1 + .../session_storage/string_session_storage.py | 38 +++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 pyrogram/client/session_storage/string_session_storage.py diff --git a/pyrogram/client/session_storage/__init__.py b/pyrogram/client/session_storage/__init__.py index 6ee92ebc..ced103ce 100644 --- a/pyrogram/client/session_storage/__init__.py +++ b/pyrogram/client/session_storage/__init__.py @@ -19,3 +19,4 @@ from .session_storage_mixin import SessionStorageMixin from .base_session_storage import BaseSessionStorage, SessionDoesNotExist from .json_session_storage import JsonSessionStorage +from .string_session_storage import StringSessionStorage diff --git a/pyrogram/client/session_storage/string_session_storage.py b/pyrogram/client/session_storage/string_session_storage.py new file mode 100644 index 00000000..9b6ebf0e --- /dev/null +++ b/pyrogram/client/session_storage/string_session_storage.py @@ -0,0 +1,38 @@ +import base64 +import binascii +import struct + +from . import BaseSessionStorage, SessionDoesNotExist + + +def StringSessionStorage(print_session: bool = False): + class StringSessionStorageClass(BaseSessionStorage): + """ + Packs session data as following (forcing little-endian byte order): + Char dc_id (1 byte, unsigned) + Boolean test_mode (1 byte) + Long long user_id (8 bytes, signed) + Bytes auth_key (256 bytes) + + Uses Base64 encoding for printable representation + """ + PACK_FORMAT = ' Date: Fri, 22 Feb 2019 00:03:58 +0300 Subject: [PATCH 004/202] Refactor session storages: use session_name arg to detect storage type --- pyrogram/client/client.py | 34 +++++++----- pyrogram/client/ext/syncer.py | 4 +- pyrogram/client/session_storage/__init__.py | 4 +- .../session_storage/base_session_storage.py | 17 ++++-- .../session_storage/json_session_storage.py | 14 ++--- .../session_storage/string_session_storage.py | 53 +++++++++---------- 6 files changed, 71 insertions(+), 55 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 0e8d5554..f17a054b 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -49,12 +49,15 @@ from pyrogram.api.errors import ( from pyrogram.client.handlers import DisconnectHandler from pyrogram.client.handlers.handler import Handler from pyrogram.client.methods.password.utils import compute_check +from pyrogram.client.session_storage import BaseSessionConfig from pyrogram.crypto import AES from pyrogram.session import Auth, Session from .dispatcher import Dispatcher from .ext import utils, Syncer, BaseClient from .methods import Methods -from .session_storage import BaseSessionStorage, JsonSessionStorage, SessionDoesNotExist +from .session_storage import SessionDoesNotExist +from .session_storage.json_session_storage import JsonSessionStorage +from .session_storage.string_session_storage import StringSessionStorage log = logging.getLogger(__name__) @@ -176,7 +179,7 @@ class Client(Methods, BaseClient): """ def __init__(self, - session_name: str, + session_name: Union[str, BaseSessionConfig], api_id: Union[int, str] = None, api_hash: str = None, app_version: str = None, @@ -198,11 +201,21 @@ class Client(Methods, BaseClient): config_file: str = BaseClient.CONFIG_FILE, plugins: dict = None, no_updates: bool = None, - takeout: bool = None, - session_storage_cls: Type[BaseSessionStorage] = JsonSessionStorage): - super().__init__(session_storage_cls(self)) + takeout: bool = None): - self.session_name = session_name + if isinstance(session_name, str): + if session_name.startswith(':'): + session_storage = StringSessionStorage(self, session_name) + else: + session_storage = JsonSessionStorage(self, session_name) + elif isinstance(session_name, BaseSessionConfig): + session_storage = session_name.session_storage_cls(self, session_name) + else: + raise RuntimeError('Wrong session_name passed, expected str or BaseSessionConfig subclass') + + super().__init__(session_storage) + + self.session_name = str(session_name) # TODO: build correct session name self.api_id = int(api_id) if api_id else None self.api_hash = api_hash self.app_version = app_version @@ -1101,12 +1114,9 @@ class Client(Methods, BaseClient): def load_session(self): try: - self.session_storage.load_session(self.session_name) + self.session_storage.load_session() except SessionDoesNotExist: - session_name = self.session_name[:32] - if session_name != self.session_name: - session_name += '...' - log.info('Could not load session "{}", initializing new one'.format(self.session_name)) + log.info('Could not load session "{}", initiate new one'.format(self.session_name)) self.auth_key = Auth(self.dc_id, self.test_mode, self.ipv6, self._proxy).create() def load_plugins(self): @@ -1214,7 +1224,7 @@ class Client(Methods, BaseClient): log.warning('No plugin loaded from "{}"'.format(root)) def save_session(self): - self.session_storage.save_session(self.session_name) + self.session_storage.save_session() def get_initial_dialogs_chunk(self, offset_date: int = 0): diff --git a/pyrogram/client/ext/syncer.py b/pyrogram/client/ext/syncer.py index 8930b13e..70955624 100644 --- a/pyrogram/client/ext/syncer.py +++ b/pyrogram/client/ext/syncer.py @@ -83,10 +83,10 @@ class Syncer: def sync(cls, client): client.date = int(time.time()) try: - client.session_storage.save_session(client.session_name, sync=True) + client.session_storage.save_session(sync=True) except Exception as e: log.critical(e, exc_info=True) else: log.info("Synced {}".format(client.session_name)) finally: - client.session_storage.sync_cleanup(client.session_name) + client.session_storage.sync_cleanup() diff --git a/pyrogram/client/session_storage/__init__.py b/pyrogram/client/session_storage/__init__.py index ced103ce..611ec9b7 100644 --- a/pyrogram/client/session_storage/__init__.py +++ b/pyrogram/client/session_storage/__init__.py @@ -17,6 +17,4 @@ # along with Pyrogram. If not, see . from .session_storage_mixin import SessionStorageMixin -from .base_session_storage import BaseSessionStorage, SessionDoesNotExist -from .json_session_storage import JsonSessionStorage -from .string_session_storage import StringSessionStorage +from .base_session_storage import BaseSessionStorage, BaseSessionConfig, SessionDoesNotExist diff --git a/pyrogram/client/session_storage/base_session_storage.py b/pyrogram/client/session_storage/base_session_storage.py index 75e416b4..a5c879f1 100644 --- a/pyrogram/client/session_storage/base_session_storage.py +++ b/pyrogram/client/session_storage/base_session_storage.py @@ -17,6 +17,7 @@ # along with Pyrogram. If not, see . import abc +from typing import Type import pyrogram @@ -26,8 +27,9 @@ class SessionDoesNotExist(Exception): class BaseSessionStorage(abc.ABC): - def __init__(self, client: 'pyrogram.client.BaseClient'): + def __init__(self, client: 'pyrogram.client.BaseClient', session_data): self.client = client + self.session_data = session_data self.dc_id = 1 self.test_mode = None self.auth_key = None @@ -38,13 +40,20 @@ class BaseSessionStorage(abc.ABC): self.peers_by_phone = {} @abc.abstractmethod - def load_session(self, name: str): + def load_session(self): ... @abc.abstractmethod - def save_session(self, name: str, sync=False): + def save_session(self, sync=False): ... @abc.abstractmethod - def sync_cleanup(self, name: str): + def sync_cleanup(self): + ... + + +class BaseSessionConfig(abc.ABC): + @property + @abc.abstractmethod + def session_storage_cls(self) -> Type[BaseSessionStorage]: ... diff --git a/pyrogram/client/session_storage/json_session_storage.py b/pyrogram/client/session_storage/json_session_storage.py index 679a21f3..f41091af 100644 --- a/pyrogram/client/session_storage/json_session_storage.py +++ b/pyrogram/client/session_storage/json_session_storage.py @@ -35,8 +35,8 @@ class JsonSessionStorage(BaseSessionStorage): name += '.session' return os.path.join(self.client.workdir, name) - def load_session(self, name: str): - file_path = self._get_file_name(name) + def load_session(self): + file_path = self._get_file_name(self.session_data) log.info('Loading JSON session from {}'.format(file_path)) try: @@ -66,8 +66,8 @@ class JsonSessionStorage(BaseSessionStorage): if peer: self.peers_by_phone[k] = peer - def save_session(self, name: str, sync=False): - file_path = self._get_file_name(name) + def save_session(self, sync=False): + file_path = self._get_file_name(self.session_data) if sync: file_path += '.tmp' @@ -107,10 +107,10 @@ class JsonSessionStorage(BaseSessionStorage): # execution won't be here if an error has occurred earlier if sync: - shutil.move(file_path, self._get_file_name(name)) + shutil.move(file_path, self._get_file_name(self.session_data)) - def sync_cleanup(self, name: str): + def sync_cleanup(self): try: - os.remove(self._get_file_name(name) + '.tmp') + os.remove(self._get_file_name(self.session_data) + '.tmp') except OSError: pass diff --git a/pyrogram/client/session_storage/string_session_storage.py b/pyrogram/client/session_storage/string_session_storage.py index 9b6ebf0e..c01a2b35 100644 --- a/pyrogram/client/session_storage/string_session_storage.py +++ b/pyrogram/client/session_storage/string_session_storage.py @@ -5,34 +5,33 @@ import struct from . import BaseSessionStorage, SessionDoesNotExist -def StringSessionStorage(print_session: bool = False): - class StringSessionStorageClass(BaseSessionStorage): - """ - Packs session data as following (forcing little-endian byte order): - Char dc_id (1 byte, unsigned) - Boolean test_mode (1 byte) - Long long user_id (8 bytes, signed) - Bytes auth_key (256 bytes) +class StringSessionStorage(BaseSessionStorage): + """ + Packs session data as following (forcing little-endian byte order): + Char dc_id (1 byte, unsigned) + Boolean test_mode (1 byte) + Long long user_id (8 bytes, signed) + Bytes auth_key (256 bytes) - Uses Base64 encoding for printable representation - """ - PACK_FORMAT = ' Date: Fri, 22 Feb 2019 01:34:08 +0300 Subject: [PATCH 005/202] Add bot_token argument (closes #123) --- pyrogram/client/client.py | 25 ++++++++++++++++++++----- pyrogram/client/ext/base_client.py | 2 +- pyrogram/client/ext/syncer.py | 1 + 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index f62c046c..da2ddc5b 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -29,6 +29,7 @@ import struct import tempfile import threading import time +import warnings from configparser import ConfigParser from datetime import datetime from hashlib import sha256, md5 @@ -67,9 +68,8 @@ class Client(Methods, BaseClient): Args: session_name (``str``): - Name to uniquely identify a session of either a User or a Bot. - For Users: pass a string of your choice, e.g.: "my_main_account". - For Bots: pass your Bot API token, e.g.: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11" + Name to uniquely identify a session of either a User or a Bot, e.g.: "my_main_account". + You still can use bot token here, but it will be deprecated in next release. Note: as long as a valid User session file exists, Pyrogram won't ask you again to input your phone number. api_id (``int``, *optional*): @@ -144,6 +144,10 @@ class Client(Methods, BaseClient): a new Telegram account in case the phone number you passed is not registered yet. Only applicable for new sessions. + bot_token (``str``, *optional*): + Pass your Bot API token to create a bot session, e.g.: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11" + Only applicable for new sessions. + last_name (``str``, *optional*): Same purpose as *first_name*; pass a Last Name to avoid entering it manually. It can be an empty string: "". Only applicable for new sessions. @@ -192,6 +196,7 @@ class Client(Methods, BaseClient): password: str = None, recovery_code: callable = None, force_sms: bool = False, + bot_token: str = None, first_name: str = None, last_name: str = None, workers: int = BaseClient.WORKERS, @@ -218,6 +223,7 @@ class Client(Methods, BaseClient): self.password = password self.recovery_code = recovery_code self.force_sms = force_sms + self.bot_token = bot_token self.first_name = first_name self.last_name = last_name self.workers = workers @@ -263,8 +269,13 @@ class Client(Methods, BaseClient): raise ConnectionError("Client has already been started") if self.BOT_TOKEN_RE.match(self.session_name): + self.is_bot = True self.bot_token = self.session_name self.session_name = self.session_name.split(":")[0] + warnings.warn('\nYou are using a bot token as session name.\n' + 'It will be deprecated in next update, please use session file name to load ' + 'existing sessions and bot_token argument to create new sessions.', + DeprecationWarning, stacklevel=2) self.load_config() self.load_session() @@ -284,11 +295,12 @@ class Client(Methods, BaseClient): if self.bot_token is None: self.authorize_user() else: + self.is_bot = True self.authorize_bot() self.save_session() - if self.bot_token is None: + if not self.is_bot: if self.takeout: self.takeout_id = self.send(functions.account.InitTakeoutSession()).id log.warning("Takeout session {} initiated".format(self.takeout_id)) @@ -1113,6 +1125,8 @@ class Client(Methods, BaseClient): self.auth_key = base64.b64decode("".join(s["auth_key"])) self.user_id = s["user_id"] self.date = s.get("date", 0) + # TODO: replace default with False once token session name will be deprecated + self.is_bot = s.get("is_bot", self.is_bot) for k, v in s.get("peers_by_id", {}).items(): self.peers_by_id[int(k)] = utils.get_input_peer(int(k), v) @@ -1246,7 +1260,8 @@ class Client(Methods, BaseClient): test_mode=self.test_mode, auth_key=auth_key, user_id=self.user_id, - date=self.date + date=self.date, + is_bot=self.is_bot, ), f, indent=4 diff --git a/pyrogram/client/ext/base_client.py b/pyrogram/client/ext/base_client.py index d2c348a8..8ca784aa 100644 --- a/pyrogram/client/ext/base_client.py +++ b/pyrogram/client/ext/base_client.py @@ -68,7 +68,7 @@ class BaseClient: } def __init__(self): - self.bot_token = None + self.is_bot = False self.dc_id = None self.auth_key = None self.user_id = None diff --git a/pyrogram/client/ext/syncer.py b/pyrogram/client/ext/syncer.py index e169d2a3..71dc3f35 100644 --- a/pyrogram/client/ext/syncer.py +++ b/pyrogram/client/ext/syncer.py @@ -94,6 +94,7 @@ class Syncer: auth_key=auth_key, user_id=client.user_id, date=int(time.time()), + is_bot=client.is_bot, peers_by_id={ k: getattr(v, "access_hash", None) for k, v in client.peers_by_id.copy().items() From 9c4e9e166e528d2ef990bcb3f2093a877d65b642 Mon Sep 17 00:00:00 2001 From: bakatrouble Date: Fri, 22 Feb 2019 02:13:51 +0300 Subject: [PATCH 006/202] Merge #221, string sessions now work for bots too --- pyrogram/client/client.py | 17 +++++++++-------- pyrogram/client/ext/base_client.py | 1 - .../session_storage/base_session_storage.py | 1 + .../session_storage/json_session_storage.py | 2 ++ .../session_storage/session_storage_mixin.py | 8 ++++++++ .../session_storage/string_session_storage.py | 13 ++++++++++--- 6 files changed, 30 insertions(+), 12 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 42a2566a..429abab3 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -281,14 +281,15 @@ class Client(Methods, BaseClient): if self.is_started: raise ConnectionError("Client has already been started") - if self.BOT_TOKEN_RE.match(self.session_name): - self.is_bot = True - self.bot_token = self.session_name - self.session_name = self.session_name.split(":")[0] - warnings.warn('\nYou are using a bot token as session name.\n' - 'It will be deprecated in next update, please use session file name to load ' - 'existing sessions and bot_token argument to create new sessions.', - DeprecationWarning, stacklevel=2) + if isinstance(self.session_storage, JsonSessionStorage): + if self.BOT_TOKEN_RE.match(self.session_storage.session_data): + self.is_bot = True + self.bot_token = self.session_storage.session_data + self.session_storage.session_data = self.session_storage.session_data.split(":")[0] + warnings.warn('\nYou are using a bot token as session name.\n' + 'It will be deprecated in next update, please use session file name to load ' + 'existing sessions and bot_token argument to create new sessions.', + DeprecationWarning, stacklevel=2) self.load_config() self.load_session() diff --git a/pyrogram/client/ext/base_client.py b/pyrogram/client/ext/base_client.py index a354ba76..3f40865f 100644 --- a/pyrogram/client/ext/base_client.py +++ b/pyrogram/client/ext/base_client.py @@ -70,7 +70,6 @@ class BaseClient(SessionStorageMixin): def __init__(self, session_storage: BaseSessionStorage): self.session_storage = session_storage - self.is_bot = False self.rnd_id = MsgId self.channels_pts = {} diff --git a/pyrogram/client/session_storage/base_session_storage.py b/pyrogram/client/session_storage/base_session_storage.py index a5c879f1..92473956 100644 --- a/pyrogram/client/session_storage/base_session_storage.py +++ b/pyrogram/client/session_storage/base_session_storage.py @@ -35,6 +35,7 @@ class BaseSessionStorage(abc.ABC): self.auth_key = None self.user_id = None self.date = 0 + self.is_bot = False self.peers_by_id = {} self.peers_by_username = {} self.peers_by_phone = {} diff --git a/pyrogram/client/session_storage/json_session_storage.py b/pyrogram/client/session_storage/json_session_storage.py index f41091af..1e1e0ca4 100644 --- a/pyrogram/client/session_storage/json_session_storage.py +++ b/pyrogram/client/session_storage/json_session_storage.py @@ -50,6 +50,7 @@ class JsonSessionStorage(BaseSessionStorage): self.auth_key = base64.b64decode("".join(s["auth_key"])) # join split key self.user_id = s["user_id"] self.date = s.get("date", 0) + self.is_bot = s.get('is_bot', self.client.is_bot) for k, v in s.get("peers_by_id", {}).items(): self.peers_by_id[int(k)] = utils.get_input_peer(int(k), v) @@ -85,6 +86,7 @@ class JsonSessionStorage(BaseSessionStorage): 'auth_key': auth_key, 'user_id': self.user_id, 'date': self.date, + 'is_bot': self.is_bot, 'peers_by_id': { k: getattr(v, "access_hash", None) for k, v in self.peers_by_id.copy().items() diff --git a/pyrogram/client/session_storage/session_storage_mixin.py b/pyrogram/client/session_storage/session_storage_mixin.py index bfe9a590..7d783ca7 100644 --- a/pyrogram/client/session_storage/session_storage_mixin.py +++ b/pyrogram/client/session_storage/session_storage_mixin.py @@ -60,6 +60,14 @@ class SessionStorageMixin: def date(self, val): self.session_storage.date = val + @property + def is_bot(self): + return self.session_storage.is_bot + + @is_bot.setter + def is_bot(self, val) -> int: + self.session_storage.is_bot = val + @property def peers_by_id(self) -> Dict[str, int]: return self.session_storage.peers_by_id diff --git a/pyrogram/client/session_storage/string_session_storage.py b/pyrogram/client/session_storage/string_session_storage.py index c01a2b35..5b1a8cc1 100644 --- a/pyrogram/client/session_storage/string_session_storage.py +++ b/pyrogram/client/session_storage/string_session_storage.py @@ -11,24 +11,31 @@ class StringSessionStorage(BaseSessionStorage): Char dc_id (1 byte, unsigned) Boolean test_mode (1 byte) Long long user_id (8 bytes, signed) + Boolean is_bot (1 byte) Bytes auth_key (256 bytes) Uses Base64 encoding for printable representation """ - PACK_FORMAT = ' Date: Fri, 22 Feb 2019 03:37:19 +0300 Subject: [PATCH 007/202] add in-memory session storage, refactor session storages, remove mixin --- pyrogram/client/client.py | 112 +++++++++--------- pyrogram/client/ext/base_client.py | 10 +- pyrogram/client/ext/syncer.py | 4 +- .../client/methods/contacts/get_contacts.py | 2 +- pyrogram/client/session_storage/__init__.py | 6 +- .../{session_storage_mixin.py => abstract.py} | 89 ++++++++++---- .../session_storage/base_session_storage.py | 60 ---------- .../{json_session_storage.py => json.py} | 65 +++++----- pyrogram/client/session_storage/memory.py | 85 +++++++++++++ .../{string_session_storage.py => string.py} | 19 +-- pyrogram/session/session.py | 3 +- 11 files changed, 267 insertions(+), 188 deletions(-) rename pyrogram/client/session_storage/{session_storage_mixin.py => abstract.py} (50%) delete mode 100644 pyrogram/client/session_storage/base_session_storage.py rename pyrogram/client/session_storage/{json_session_storage.py => json.py} (58%) create mode 100644 pyrogram/client/session_storage/memory.py rename pyrogram/client/session_storage/{string_session_storage.py => string.py} (62%) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 429abab3..42bd73d6 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -50,15 +50,15 @@ from pyrogram.api.errors import ( from pyrogram.client.handlers import DisconnectHandler from pyrogram.client.handlers.handler import Handler from pyrogram.client.methods.password.utils import compute_check -from pyrogram.client.session_storage import BaseSessionConfig from pyrogram.crypto import AES from pyrogram.session import Auth, Session from .dispatcher import Dispatcher from .ext import utils, Syncer, BaseClient from .methods import Methods -from .session_storage import SessionDoesNotExist -from .session_storage.json_session_storage import JsonSessionStorage -from .session_storage.string_session_storage import StringSessionStorage +from .session_storage import ( + SessionDoesNotExist, SessionStorage, MemorySessionStorage, JsonSessionStorage, + StringSessionStorage +) log = logging.getLogger(__name__) @@ -183,7 +183,7 @@ class Client(Methods, BaseClient): """ def __init__(self, - session_name: Union[str, BaseSessionConfig], + session_name: Union[str, SessionStorage], api_id: Union[int, str] = None, api_hash: str = None, app_version: str = None, @@ -209,14 +209,16 @@ class Client(Methods, BaseClient): takeout: bool = None): if isinstance(session_name, str): - if session_name.startswith(':'): + if session_name == ':memory:': + session_storage = MemorySessionStorage(self) + elif session_name.startswith(':'): session_storage = StringSessionStorage(self, session_name) else: session_storage = JsonSessionStorage(self, session_name) - elif isinstance(session_name, BaseSessionConfig): - session_storage = session_name.session_storage_cls(self, session_name) + elif isinstance(session_name, SessionStorage): + session_storage = session_name else: - raise RuntimeError('Wrong session_name passed, expected str or BaseSessionConfig subclass') + raise RuntimeError('Wrong session_name passed, expected str or SessionConfig subclass') super().__init__(session_storage) @@ -230,7 +232,7 @@ class Client(Methods, BaseClient): self.ipv6 = ipv6 # TODO: Make code consistent, use underscore for private/protected fields self._proxy = proxy - self.test_mode = test_mode + self.session_storage.test_mode = test_mode self.phone_number = phone_number self.phone_code = phone_code self.password = password @@ -282,10 +284,10 @@ class Client(Methods, BaseClient): raise ConnectionError("Client has already been started") if isinstance(self.session_storage, JsonSessionStorage): - if self.BOT_TOKEN_RE.match(self.session_storage.session_data): - self.is_bot = True - self.bot_token = self.session_storage.session_data - self.session_storage.session_data = self.session_storage.session_data.split(":")[0] + if self.BOT_TOKEN_RE.match(self.session_storage._session_name): + self.session_storage.is_bot = True + self.bot_token = self.session_storage._session_name + self.session_storage._session_name = self.session_storage._session_name.split(":")[0] warnings.warn('\nYou are using a bot token as session name.\n' 'It will be deprecated in next update, please use session file name to load ' 'existing sessions and bot_token argument to create new sessions.', @@ -297,33 +299,33 @@ class Client(Methods, BaseClient): self.session = Session( self, - self.dc_id, - self.auth_key + self.session_storage.dc_id, + self.session_storage.auth_key ) self.session.start() self.is_started = True try: - if self.user_id is None: + if self.session_storage.user_id is None: if self.bot_token is None: self.authorize_user() else: - self.is_bot = True + self.session_storage.is_bot = True self.authorize_bot() self.save_session() - if not self.is_bot: + if not self.session_storage.is_bot: if self.takeout: self.takeout_id = self.send(functions.account.InitTakeoutSession()).id log.warning("Takeout session {} initiated".format(self.takeout_id)) now = time.time() - if abs(now - self.date) > Client.OFFLINE_SLEEP: - self.peers_by_username.clear() - self.peers_by_phone.clear() + if abs(now - self.session_storage.date) > Client.OFFLINE_SLEEP: + self.session_storage.peers_by_username.clear() + self.session_storage.peers_by_phone.clear() self.get_initial_dialogs() self.get_contacts() @@ -512,19 +514,20 @@ class Client(Methods, BaseClient): except UserMigrate as e: self.session.stop() - self.dc_id = e.x - self.auth_key = Auth(self.dc_id, self.test_mode, self.ipv6, self._proxy).create() + self.session_storage.dc_id = e.x + self.session_storage.auth_key = Auth(self.session_storage.dc_id, self.session_storage.test_mode, + self.ipv6, self._proxy).create() self.session = Session( self, - self.dc_id, - self.auth_key + self.session_storage.dc_id, + self.session_storage.auth_key ) self.session.start() self.authorize_bot() else: - self.user_id = r.user.id + self.session_storage.user_id = r.user.id print("Logged in successfully as @{}".format(r.user.username)) @@ -564,19 +567,19 @@ class Client(Methods, BaseClient): except (PhoneMigrate, NetworkMigrate) as e: self.session.stop() - self.dc_id = e.x + self.session_storage.dc_id = e.x - self.auth_key = Auth( - self.dc_id, - self.test_mode, + self.session_storage.auth_key = Auth( + self.session_storage.dc_id, + self.session_storage.test_mode, self.ipv6, self._proxy ).create() self.session = Session( self, - self.dc_id, - self.auth_key + self.session_storage.dc_id, + self.session_storage.auth_key ) self.session.start() @@ -752,7 +755,7 @@ class Client(Methods, BaseClient): assert self.send(functions.help.AcceptTermsOfService(terms_of_service.id)) self.password = None - self.user_id = r.user.id + self.session_storage.user_id = r.user.id print("Logged in successfully as {}".format(r.user.first_name)) @@ -776,13 +779,13 @@ class Client(Methods, BaseClient): access_hash=access_hash ) - self.peers_by_id[user_id] = input_peer + self.session_storage.peers_by_id[user_id] = input_peer if username is not None: - self.peers_by_username[username.lower()] = input_peer + self.session_storage.peers_by_username[username.lower()] = input_peer if phone is not None: - self.peers_by_phone[phone] = input_peer + self.session_storage.peers_by_phone[phone] = input_peer if isinstance(entity, (types.Chat, types.ChatForbidden)): chat_id = entity.id @@ -792,7 +795,7 @@ class Client(Methods, BaseClient): chat_id=chat_id ) - self.peers_by_id[peer_id] = input_peer + self.session_storage.peers_by_id[peer_id] = input_peer if isinstance(entity, (types.Channel, types.ChannelForbidden)): channel_id = entity.id @@ -810,10 +813,10 @@ class Client(Methods, BaseClient): access_hash=access_hash ) - self.peers_by_id[peer_id] = input_peer + self.session_storage.peers_by_id[peer_id] = input_peer if username is not None: - self.peers_by_username[username.lower()] = input_peer + self.session_storage.peers_by_username[username.lower()] = input_peer def download_worker(self): name = threading.current_thread().name @@ -1127,10 +1130,11 @@ class Client(Methods, BaseClient): def load_session(self): try: - self.session_storage.load_session() + self.session_storage.load() except SessionDoesNotExist: log.info('Could not load session "{}", initiate new one'.format(self.session_name)) - self.auth_key = Auth(self.dc_id, self.test_mode, self.ipv6, self._proxy).create() + self.session_storage.auth_key = Auth(self.session_storage.dc_id, self.session_storage.test_mode, + self.ipv6, self._proxy).create() def load_plugins(self): if self.plugins.get("enabled", False): @@ -1237,7 +1241,7 @@ class Client(Methods, BaseClient): log.warning('No plugin loaded from "{}"'.format(root)) def save_session(self): - self.session_storage.save_session() + self.session_storage.save() def get_initial_dialogs_chunk(self, offset_date: int = 0): @@ -1257,7 +1261,7 @@ class Client(Methods, BaseClient): log.warning("get_dialogs flood: waiting {} seconds".format(e.x)) time.sleep(e.x) else: - log.info("Total peers: {}".format(len(self.peers_by_id))) + log.info("Total peers: {}".format(len(self.session_storage.peers_by_id))) return r def get_initial_dialogs(self): @@ -1293,7 +1297,7 @@ class Client(Methods, BaseClient): ``KeyError`` in case the peer doesn't exist in the internal database. """ try: - return self.peers_by_id[peer_id] + return self.session_storage.peers_by_id[peer_id] except KeyError: if type(peer_id) is str: if peer_id in ("self", "me"): @@ -1304,17 +1308,17 @@ class Client(Methods, BaseClient): try: int(peer_id) except ValueError: - if peer_id not in self.peers_by_username: + if peer_id not in self.session_storage.peers_by_username: self.send( functions.contacts.ResolveUsername( username=peer_id ) ) - return self.peers_by_username[peer_id] + return self.session_storage.peers_by_username[peer_id] else: try: - return self.peers_by_phone[peer_id] + return self.session_storage.peers_by_phone[peer_id] except KeyError: raise PeerIdInvalid @@ -1341,7 +1345,7 @@ class Client(Methods, BaseClient): ) try: - return self.peers_by_id[peer_id] + return self.session_storage.peers_by_id[peer_id] except KeyError: raise PeerIdInvalid @@ -1411,7 +1415,7 @@ class Client(Methods, BaseClient): file_id = file_id or self.rnd_id() md5_sum = md5() if not is_big and not is_missing_part else None - session = Session(self, self.dc_id, self.auth_key, is_media=True) + session = Session(self, self.session_storage.dc_id, self.session_storage.auth_key, is_media=True) session.start() try: @@ -1492,7 +1496,7 @@ class Client(Methods, BaseClient): session = self.media_sessions.get(dc_id, None) if session is None: - if dc_id != self.dc_id: + if dc_id != self.session_storage.dc_id: exported_auth = self.send( functions.auth.ExportAuthorization( dc_id=dc_id @@ -1502,7 +1506,7 @@ class Client(Methods, BaseClient): session = Session( self, dc_id, - Auth(dc_id, self.test_mode, self.ipv6, self._proxy).create(), + Auth(dc_id, self.session_storage.test_mode, self.ipv6, self._proxy).create(), is_media=True ) @@ -1520,7 +1524,7 @@ class Client(Methods, BaseClient): session = Session( self, dc_id, - self.auth_key, + self.session_storage.auth_key, is_media=True ) @@ -1588,7 +1592,7 @@ class Client(Methods, BaseClient): cdn_session = Session( self, r.dc_id, - Auth(r.dc_id, self.test_mode, self.ipv6, self._proxy).create(), + Auth(r.dc_id, self.session_storage.test_mode, self.ipv6, self._proxy).create(), is_media=True, is_cdn=True ) diff --git a/pyrogram/client/ext/base_client.py b/pyrogram/client/ext/base_client.py index 3f40865f..732a600f 100644 --- a/pyrogram/client/ext/base_client.py +++ b/pyrogram/client/ext/base_client.py @@ -24,10 +24,10 @@ from threading import Lock from pyrogram import __version__ from ..style import Markdown, HTML from ...session.internals import MsgId -from ..session_storage import SessionStorageMixin, BaseSessionStorage +from ..session_storage import SessionStorage -class BaseClient(SessionStorageMixin): +class BaseClient: class StopTransmission(StopIteration): pass @@ -68,14 +68,14 @@ class BaseClient(SessionStorageMixin): 13: "video_note" } - def __init__(self, session_storage: BaseSessionStorage): + def __init__(self, session_storage: SessionStorage): self.session_storage = session_storage self.rnd_id = MsgId self.channels_pts = {} - self.markdown = Markdown(self.peers_by_id) - self.html = HTML(self.peers_by_id) + self.markdown = Markdown(self.session_storage.peers_by_id) + self.html = HTML(self.session_storage.peers_by_id) self.session = None self.media_sessions = {} diff --git a/pyrogram/client/ext/syncer.py b/pyrogram/client/ext/syncer.py index 70955624..e13212be 100644 --- a/pyrogram/client/ext/syncer.py +++ b/pyrogram/client/ext/syncer.py @@ -81,9 +81,9 @@ class Syncer: @classmethod def sync(cls, client): - client.date = int(time.time()) + client.session_storage.date = int(time.time()) try: - client.session_storage.save_session(sync=True) + client.session_storage.save(sync=True) except Exception as e: log.critical(e, exc_info=True) else: diff --git a/pyrogram/client/methods/contacts/get_contacts.py b/pyrogram/client/methods/contacts/get_contacts.py index 29b7e176..35b24592 100644 --- a/pyrogram/client/methods/contacts/get_contacts.py +++ b/pyrogram/client/methods/contacts/get_contacts.py @@ -44,5 +44,5 @@ class GetContacts(BaseClient): log.warning("get_contacts flood: waiting {} seconds".format(e.x)) time.sleep(e.x) else: - log.info("Total contacts: {}".format(len(self.peers_by_phone))) + log.info("Total contacts: {}".format(len(self.session_storage.peers_by_phone))) return [pyrogram.User._parse(self, user) for user in contacts.users] diff --git a/pyrogram/client/session_storage/__init__.py b/pyrogram/client/session_storage/__init__.py index 611ec9b7..ad2d8900 100644 --- a/pyrogram/client/session_storage/__init__.py +++ b/pyrogram/client/session_storage/__init__.py @@ -16,5 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from .session_storage_mixin import SessionStorageMixin -from .base_session_storage import BaseSessionStorage, BaseSessionConfig, SessionDoesNotExist +from .abstract import SessionStorage, SessionDoesNotExist +from .memory import MemorySessionStorage +from .json import JsonSessionStorage +from .string import StringSessionStorage diff --git a/pyrogram/client/session_storage/session_storage_mixin.py b/pyrogram/client/session_storage/abstract.py similarity index 50% rename from pyrogram/client/session_storage/session_storage_mixin.py rename to pyrogram/client/session_storage/abstract.py index 7d783ca7..e8f4441e 100644 --- a/pyrogram/client/session_storage/session_storage_mixin.py +++ b/pyrogram/client/session_storage/abstract.py @@ -16,66 +16,103 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Dict +import abc +from typing import Type + +import pyrogram -class SessionStorageMixin: +class SessionDoesNotExist(Exception): + pass + + +class SessionStorage(abc.ABC): + def __init__(self, client: 'pyrogram.client.BaseClient'): + self._client = client + + @abc.abstractmethod + def load(self): + ... + + @abc.abstractmethod + def save(self, sync=False): + ... + + @abc.abstractmethod + def sync_cleanup(self): + ... + @property - def dc_id(self) -> int: - return self.session_storage.dc_id + @abc.abstractmethod + def dc_id(self): + ... @dc_id.setter + @abc.abstractmethod def dc_id(self, val): - self.session_storage.dc_id = val + ... @property - def test_mode(self) -> bool: - return self.session_storage.test_mode + @abc.abstractmethod + def test_mode(self): + ... @test_mode.setter + @abc.abstractmethod def test_mode(self, val): - self.session_storage.test_mode = val + ... @property - def auth_key(self) -> bytes: - return self.session_storage.auth_key + @abc.abstractmethod + def auth_key(self): + ... @auth_key.setter + @abc.abstractmethod def auth_key(self, val): - self.session_storage.auth_key = val + ... @property + @abc.abstractmethod def user_id(self): - return self.session_storage.user_id + ... @user_id.setter - def user_id(self, val) -> int: - self.session_storage.user_id = val + @abc.abstractmethod + def user_id(self, val): + ... @property - def date(self) -> int: - return self.session_storage.date + @abc.abstractmethod + def date(self): + ... @date.setter + @abc.abstractmethod def date(self, val): - self.session_storage.date = val + ... @property + @abc.abstractmethod def is_bot(self): - return self.session_storage.is_bot + ... @is_bot.setter - def is_bot(self, val) -> int: - self.session_storage.is_bot = val + @abc.abstractmethod + def is_bot(self, val): + ... @property - def peers_by_id(self) -> Dict[str, int]: - return self.session_storage.peers_by_id + @abc.abstractmethod + def peers_by_id(self): + ... @property - def peers_by_username(self) -> Dict[str, int]: - return self.session_storage.peers_by_username + @abc.abstractmethod + def peers_by_username(self): + ... @property - def peers_by_phone(self) -> Dict[str, int]: - return self.session_storage.peers_by_phone + @abc.abstractmethod + def peers_by_phone(self): + ... diff --git a/pyrogram/client/session_storage/base_session_storage.py b/pyrogram/client/session_storage/base_session_storage.py deleted file mode 100644 index 92473956..00000000 --- a/pyrogram/client/session_storage/base_session_storage.py +++ /dev/null @@ -1,60 +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 abc -from typing import Type - -import pyrogram - - -class SessionDoesNotExist(Exception): - pass - - -class BaseSessionStorage(abc.ABC): - def __init__(self, client: 'pyrogram.client.BaseClient', session_data): - self.client = client - self.session_data = session_data - self.dc_id = 1 - self.test_mode = None - self.auth_key = None - self.user_id = None - self.date = 0 - self.is_bot = False - self.peers_by_id = {} - self.peers_by_username = {} - self.peers_by_phone = {} - - @abc.abstractmethod - def load_session(self): - ... - - @abc.abstractmethod - def save_session(self, sync=False): - ... - - @abc.abstractmethod - def sync_cleanup(self): - ... - - -class BaseSessionConfig(abc.ABC): - @property - @abc.abstractmethod - def session_storage_cls(self) -> Type[BaseSessionStorage]: - ... diff --git a/pyrogram/client/session_storage/json_session_storage.py b/pyrogram/client/session_storage/json.py similarity index 58% rename from pyrogram/client/session_storage/json_session_storage.py rename to pyrogram/client/session_storage/json.py index 1e1e0ca4..170089a4 100644 --- a/pyrogram/client/session_storage/json_session_storage.py +++ b/pyrogram/client/session_storage/json.py @@ -22,21 +22,26 @@ import logging import os import shutil +import pyrogram from ..ext import utils -from . import BaseSessionStorage, SessionDoesNotExist +from . import MemorySessionStorage, SessionDoesNotExist log = logging.getLogger(__name__) -class JsonSessionStorage(BaseSessionStorage): +class JsonSessionStorage(MemorySessionStorage): + def __init__(self, client: 'pyrogram.client.ext.BaseClient', session_name: str): + super(JsonSessionStorage, self).__init__(client) + self._session_name = session_name + def _get_file_name(self, name: str): if not name.endswith('.session'): name += '.session' - return os.path.join(self.client.workdir, name) + return os.path.join(self._client.workdir, name) - def load_session(self): - file_path = self._get_file_name(self.session_data) + def load(self): + file_path = self._get_file_name(self._session_name) log.info('Loading JSON session from {}'.format(file_path)) try: @@ -45,59 +50,59 @@ class JsonSessionStorage(BaseSessionStorage): except FileNotFoundError: raise SessionDoesNotExist() - self.dc_id = s["dc_id"] - self.test_mode = s["test_mode"] - self.auth_key = base64.b64decode("".join(s["auth_key"])) # join split key - self.user_id = s["user_id"] - self.date = s.get("date", 0) - self.is_bot = s.get('is_bot', self.client.is_bot) + self._dc_id = s["dc_id"] + self._test_mode = s["test_mode"] + self._auth_key = base64.b64decode("".join(s["auth_key"])) # join split key + self._user_id = s["user_id"] + self._date = s.get("date", 0) + self._is_bot = s.get('is_bot', self._is_bot) for k, v in s.get("peers_by_id", {}).items(): - self.peers_by_id[int(k)] = utils.get_input_peer(int(k), v) + self._peers_by_id[int(k)] = utils.get_input_peer(int(k), v) for k, v in s.get("peers_by_username", {}).items(): - peer = self.peers_by_id.get(v, None) + peer = self._peers_by_id.get(v, None) if peer: - self.peers_by_username[k] = peer + self._peers_by_username[k] = peer for k, v in s.get("peers_by_phone", {}).items(): - peer = self.peers_by_id.get(v, None) + peer = self._peers_by_id.get(v, None) if peer: - self.peers_by_phone[k] = peer + self._peers_by_phone[k] = peer - def save_session(self, sync=False): - file_path = self._get_file_name(self.session_data) + def save(self, sync=False): + file_path = self._get_file_name(self._session_name) if sync: file_path += '.tmp' log.info('Saving JSON session to {}, sync={}'.format(file_path, sync)) - auth_key = base64.b64encode(self.auth_key).decode() + auth_key = base64.b64encode(self._auth_key).decode() auth_key = [auth_key[i: i + 43] for i in range(0, len(auth_key), 43)] # split key in lines of 43 chars - os.makedirs(self.client.workdir, exist_ok=True) + os.makedirs(self._client.workdir, exist_ok=True) data = { - 'dc_id': self.dc_id, - 'test_mode': self.test_mode, + 'dc_id': self._dc_id, + 'test_mode': self._test_mode, 'auth_key': auth_key, - 'user_id': self.user_id, - 'date': self.date, - 'is_bot': self.is_bot, + 'user_id': self._user_id, + 'date': self._date, + 'is_bot': self._is_bot, 'peers_by_id': { k: getattr(v, "access_hash", None) - for k, v in self.peers_by_id.copy().items() + for k, v in self._peers_by_id.copy().items() }, 'peers_by_username': { k: utils.get_peer_id(v) - for k, v in self.peers_by_username.copy().items() + for k, v in self._peers_by_username.copy().items() }, 'peers_by_phone': { k: utils.get_peer_id(v) - for k, v in self.peers_by_phone.copy().items() + for k, v in self._peers_by_phone.copy().items() } } @@ -109,10 +114,10 @@ class JsonSessionStorage(BaseSessionStorage): # execution won't be here if an error has occurred earlier if sync: - shutil.move(file_path, self._get_file_name(self.session_data)) + shutil.move(file_path, self._get_file_name(self._session_name)) def sync_cleanup(self): try: - os.remove(self._get_file_name(self.session_data) + '.tmp') + os.remove(self._get_file_name(self._session_name) + '.tmp') except OSError: pass diff --git a/pyrogram/client/session_storage/memory.py b/pyrogram/client/session_storage/memory.py new file mode 100644 index 00000000..f456f8eb --- /dev/null +++ b/pyrogram/client/session_storage/memory.py @@ -0,0 +1,85 @@ +import pyrogram +from . import SessionStorage, SessionDoesNotExist + + +class MemorySessionStorage(SessionStorage): + def __init__(self, client: 'pyrogram.client.ext.BaseClient'): + super(MemorySessionStorage, self).__init__(client) + self._dc_id = 1 + self._test_mode = None + self._auth_key = None + self._user_id = None + self._date = 0 + self._is_bot = False + self._peers_by_id = {} + self._peers_by_username = {} + self._peers_by_phone = {} + + def load(self): + raise SessionDoesNotExist() + + def save(self, sync=False): + pass + + def sync_cleanup(self): + pass + + @property + def dc_id(self): + return self._dc_id + + @dc_id.setter + def dc_id(self, val): + self._dc_id = val + + @property + def test_mode(self): + return self._test_mode + + @test_mode.setter + def test_mode(self, val): + self._test_mode = val + + @property + def auth_key(self): + return self._auth_key + + @auth_key.setter + def auth_key(self, val): + self._auth_key = val + + @property + def user_id(self): + return self._user_id + + @user_id.setter + def user_id(self, val): + self._user_id = val + + @property + def date(self): + return self._date + + @date.setter + def date(self, val): + self._date = val + + @property + def is_bot(self): + return self._is_bot + + @is_bot.setter + def is_bot(self, val): + self._is_bot = val + + @property + def peers_by_id(self): + return self._peers_by_id + + @property + def peers_by_username(self): + return self._peers_by_username + + @property + def peers_by_phone(self): + return self._peers_by_phone diff --git a/pyrogram/client/session_storage/string_session_storage.py b/pyrogram/client/session_storage/string.py similarity index 62% rename from pyrogram/client/session_storage/string_session_storage.py rename to pyrogram/client/session_storage/string.py index 5b1a8cc1..f8ec740a 100644 --- a/pyrogram/client/session_storage/string_session_storage.py +++ b/pyrogram/client/session_storage/string.py @@ -2,10 +2,11 @@ import base64 import binascii import struct -from . import BaseSessionStorage, SessionDoesNotExist +import pyrogram +from . import MemorySessionStorage, SessionDoesNotExist -class StringSessionStorage(BaseSessionStorage): +class StringSessionStorage(MemorySessionStorage): """ Packs session data as following (forcing little-endian byte order): Char dc_id (1 byte, unsigned) @@ -18,22 +19,26 @@ class StringSessionStorage(BaseSessionStorage): """ PACK_FORMAT = '. import abc -from typing import Type +from typing import Type, Union import pyrogram +from pyrogram.api import types class SessionDoesNotExist(Exception): @@ -102,17 +103,41 @@ class SessionStorage(abc.ABC): def is_bot(self, val): ... - @property @abc.abstractmethod - def peers_by_id(self): + def clear_cache(self): ... - @property @abc.abstractmethod - def peers_by_username(self): + def cache_peer(self, entity: Union[types.User, + types.Chat, types.ChatForbidden, + types.Channel, types.ChannelForbidden]): ... - @property @abc.abstractmethod - def peers_by_phone(self): + def get_peer_by_id(self, val: int): + ... + + @abc.abstractmethod + def get_peer_by_username(self, val: str): + ... + + @abc.abstractmethod + def get_peer_by_phone(self, val: str): + ... + + def get_peer(self, peer_id: Union[int, str]): + if isinstance(peer_id, int): + return self.get_peer_by_id(peer_id) + else: + peer_id = peer_id.lstrip('+@') + if peer_id.isdigit(): + return self.get_peer_by_phone(peer_id) + return self.get_peer_by_username(peer_id) + + @abc.abstractmethod + def peers_count(self): + ... + + @abc.abstractmethod + def contacts_count(self): ... diff --git a/pyrogram/client/session_storage/json.py b/pyrogram/client/session_storage/json.py index 170089a4..aaa6b96f 100644 --- a/pyrogram/client/session_storage/json.py +++ b/pyrogram/client/session_storage/json.py @@ -58,19 +58,19 @@ class JsonSessionStorage(MemorySessionStorage): self._is_bot = s.get('is_bot', self._is_bot) for k, v in s.get("peers_by_id", {}).items(): - self._peers_by_id[int(k)] = utils.get_input_peer(int(k), v) + self._peers_cache['i' + k] = utils.get_input_peer(int(k), v) for k, v in s.get("peers_by_username", {}).items(): - peer = self._peers_by_id.get(v, None) - - if peer: - self._peers_by_username[k] = peer + try: + self._peers_cache['u' + k] = self.get_peer_by_id(v) + except KeyError: + pass for k, v in s.get("peers_by_phone", {}).items(): - peer = self._peers_by_id.get(v, None) - - if peer: - self._peers_by_phone[k] = peer + try: + self._peers_cache['p' + k] = self.get_peer_by_id(v) + except KeyError: + pass def save(self, sync=False): file_path = self._get_file_name(self._session_name) @@ -93,16 +93,19 @@ class JsonSessionStorage(MemorySessionStorage): 'date': self._date, 'is_bot': self._is_bot, 'peers_by_id': { - k: getattr(v, "access_hash", None) - for k, v in self._peers_by_id.copy().items() + k[1:]: getattr(v, "access_hash", None) + for k, v in self._peers_cache.copy().items() + if k[0] == 'i' }, 'peers_by_username': { - k: utils.get_peer_id(v) - for k, v in self._peers_by_username.copy().items() + k[1:]: utils.get_peer_id(v) + for k, v in self._peers_cache.copy().items() + if k[0] == 'u' }, 'peers_by_phone': { - k: utils.get_peer_id(v) - for k, v in self._peers_by_phone.copy().items() + k[1:]: utils.get_peer_id(v) + for k, v in self._peers_cache.copy().items() + if k[0] == 'p' } } diff --git a/pyrogram/client/session_storage/memory.py b/pyrogram/client/session_storage/memory.py index f456f8eb..d5f92f0d 100644 --- a/pyrogram/client/session_storage/memory.py +++ b/pyrogram/client/session_storage/memory.py @@ -1,4 +1,5 @@ import pyrogram +from pyrogram.api import types from . import SessionStorage, SessionDoesNotExist @@ -11,9 +12,7 @@ class MemorySessionStorage(SessionStorage): self._user_id = None self._date = 0 self._is_bot = False - self._peers_by_id = {} - self._peers_by_username = {} - self._peers_by_phone = {} + self._peers_cache = {} def load(self): raise SessionDoesNotExist() @@ -72,14 +71,48 @@ class MemorySessionStorage(SessionStorage): def is_bot(self, val): self._is_bot = val - @property - def peers_by_id(self): - return self._peers_by_id + def clear_cache(self): + keys = list(filter(lambda k: k[0] in 'up', self._peers_cache.keys())) + for key in keys: + try: + del self._peers_cache[key] + except KeyError: + pass - @property - def peers_by_username(self): - return self._peers_by_username + def cache_peer(self, entity): + if isinstance(entity, types.User): + input_peer = types.InputPeerUser( + user_id=entity.id, + access_hash=entity.access_hash + ) + self._peers_cache['i' + str(entity.id)] = input_peer + if entity.username: + self._peers_cache['u' + entity.username.lower()] = input_peer + if entity.phone: + self._peers_cache['p' + entity.phone] = input_peer + elif isinstance(entity, (types.Chat, types.ChatForbidden)): + self._peers_cache['i-' + str(entity.id)] = types.InputPeerChat(chat_id=entity.id) + elif isinstance(entity, (types.Channel, types.ChannelForbidden)): + input_peer = types.InputPeerChannel( + channel_id=entity.id, + access_hash=entity.access_hash + ) + self._peers_cache['i-100' + str(entity.id)] = input_peer + username = getattr(entity, "username", None) + if username: + self._peers_cache['u' + username.lower()] = input_peer - @property - def peers_by_phone(self): - return self._peers_by_phone + def get_peer_by_id(self, val): + return self._peers_cache['i' + str(val)] + + def get_peer_by_username(self, val): + return self._peers_cache['u' + val.lower()] + + def get_peer_by_phone(self, val): + return self._peers_cache['p' + val] + + def peers_count(self): + return len(list(filter(lambda k: k[0] == 'i', self._peers_cache.keys()))) + + def contacts_count(self): + return len(list(filter(lambda k: k[0] == 'p', self._peers_cache.keys()))) diff --git a/pyrogram/client/style/html.py b/pyrogram/client/style/html.py index 9a72a565..88e317cd 100644 --- a/pyrogram/client/style/html.py +++ b/pyrogram/client/style/html.py @@ -29,14 +29,15 @@ from pyrogram.api.types import ( InputMessageEntityMentionName as Mention, ) from . import utils +from ..session_storage import SessionStorage class HTML: HTML_RE = re.compile(r"<(\w+)(?: href=([\"'])([^<]+)\2)?>([^>]+)") MENTION_RE = re.compile(r"tg://user\?id=(\d+)") - def __init__(self, peers_by_id): - self.peers_by_id = peers_by_id + def __init__(self, session_storage: SessionStorage): + self.session_storage = session_storage def parse(self, message: str): entities = [] @@ -52,7 +53,10 @@ class HTML: if mention: user_id = int(mention.group(1)) - input_user = self.peers_by_id.get(user_id, None) + try: + input_user = self.session_storage.get_peer_by_id(user_id) + except KeyError: + input_user = None entity = ( Mention(start, len(body), input_user) diff --git a/pyrogram/client/style/markdown.py b/pyrogram/client/style/markdown.py index 05a11a25..6793b643 100644 --- a/pyrogram/client/style/markdown.py +++ b/pyrogram/client/style/markdown.py @@ -29,6 +29,7 @@ from pyrogram.api.types import ( InputMessageEntityMentionName as Mention ) from . import utils +from ..session_storage import SessionStorage class Markdown: @@ -52,8 +53,8 @@ class Markdown: )) MENTION_RE = re.compile(r"tg://user\?id=(\d+)") - def __init__(self, peers_by_id: dict): - self.peers_by_id = peers_by_id + def __init__(self, session_storage: SessionStorage): + self.session_storage = session_storage def parse(self, message: str): message = utils.add_surrogates(str(message)).strip() @@ -69,7 +70,10 @@ class Markdown: if mention: user_id = int(mention.group(1)) - input_user = self.peers_by_id.get(user_id, None) + try: + input_user = self.session_storage.get_peer_by_id(user_id) + except KeyError: + input_user = None entity = ( Mention(start, len(text), input_user) From 03b92b3302a9d316d4e693efa8b9d87b0b991fd0 Mon Sep 17 00:00:00 2001 From: bakatrouble Date: Tue, 26 Feb 2019 21:06:30 +0300 Subject: [PATCH 009/202] Implement SQLite session storage --- pyrogram/client/client.py | 2 +- pyrogram/client/session_storage/__init__.py | 1 + pyrogram/client/session_storage/json.py | 6 +- .../client/session_storage/sqlite/0001.sql | 21 +++ .../client/session_storage/sqlite/__init__.py | 132 ++++++++++++++++++ 5 files changed, 159 insertions(+), 3 deletions(-) create mode 100644 pyrogram/client/session_storage/sqlite/0001.sql create mode 100644 pyrogram/client/session_storage/sqlite/__init__.py diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index ad755977..5fc805c0 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -57,7 +57,7 @@ from .ext import utils, Syncer, BaseClient from .methods import Methods from .session_storage import ( SessionDoesNotExist, SessionStorage, MemorySessionStorage, JsonSessionStorage, - StringSessionStorage + StringSessionStorage, SQLiteSessionStorage ) log = logging.getLogger(__name__) diff --git a/pyrogram/client/session_storage/__init__.py b/pyrogram/client/session_storage/__init__.py index ad2d8900..adfcf813 100644 --- a/pyrogram/client/session_storage/__init__.py +++ b/pyrogram/client/session_storage/__init__.py @@ -20,3 +20,4 @@ from .abstract import SessionStorage, SessionDoesNotExist from .memory import MemorySessionStorage from .json import JsonSessionStorage from .string import StringSessionStorage +from .sqlite import SQLiteSessionStorage diff --git a/pyrogram/client/session_storage/json.py b/pyrogram/client/session_storage/json.py index aaa6b96f..570e1525 100644 --- a/pyrogram/client/session_storage/json.py +++ b/pyrogram/client/session_storage/json.py @@ -29,6 +29,8 @@ from . import MemorySessionStorage, SessionDoesNotExist log = logging.getLogger(__name__) +EXTENSION = '.session' + class JsonSessionStorage(MemorySessionStorage): def __init__(self, client: 'pyrogram.client.ext.BaseClient', session_name: str): @@ -36,8 +38,8 @@ class JsonSessionStorage(MemorySessionStorage): self._session_name = session_name def _get_file_name(self, name: str): - if not name.endswith('.session'): - name += '.session' + if not name.endswith(EXTENSION): + name += EXTENSION return os.path.join(self._client.workdir, name) def load(self): diff --git a/pyrogram/client/session_storage/sqlite/0001.sql b/pyrogram/client/session_storage/sqlite/0001.sql new file mode 100644 index 00000000..d81e9554 --- /dev/null +++ b/pyrogram/client/session_storage/sqlite/0001.sql @@ -0,0 +1,21 @@ +create table sessions ( + dc_id integer primary key, + test_mode integer, + auth_key blob, + user_id integer, + date integer, + is_bot integer +); + +create table peers_cache ( + id integer primary key, + hash integer, + username text, + phone integer +); + +create table migrations ( + name text primary key +); + +insert into migrations (name) values ('0001'); diff --git a/pyrogram/client/session_storage/sqlite/__init__.py b/pyrogram/client/session_storage/sqlite/__init__.py new file mode 100644 index 00000000..75931109 --- /dev/null +++ b/pyrogram/client/session_storage/sqlite/__init__.py @@ -0,0 +1,132 @@ +# 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 logging +import os +import sqlite3 + +import pyrogram +from ....api import types +from ...ext import utils +from .. import MemorySessionStorage, SessionDoesNotExist + + +log = logging.getLogger(__name__) + +EXTENSION = '.session.sqlite3' +MIGRATIONS = ['0001'] + + +class SQLiteSessionStorage(MemorySessionStorage): + def __init__(self, client: 'pyrogram.client.ext.BaseClient', session_name: str): + super(SQLiteSessionStorage, self).__init__(client) + self._session_name = session_name + self._conn = None # type: sqlite3.Connection + + def _get_file_name(self, name: str): + if not name.endswith(EXTENSION): + name += EXTENSION + return os.path.join(self._client.workdir, name) + + def _apply_migrations(self, new_db=False): + migrations = MIGRATIONS.copy() + if not new_db: + cursor = self._conn.cursor() + cursor.execute('select name from migrations') + for row in cursor.fetchone(): + migrations.remove(row) + for name in migrations: + with open(os.path.join(os.path.dirname(__file__), '{}.sql'.format(name))) as script: + self._conn.executescript(script.read()) + + def load(self): + file_path = self._get_file_name(self._session_name) + log.info('Loading SQLite session from {}'.format(file_path)) + + if os.path.isfile(file_path): + self._conn = sqlite3.connect(file_path) + self._apply_migrations() + else: + self._conn = sqlite3.connect(file_path) + self._apply_migrations(new_db=True) + + cursor = self._conn.cursor() + cursor.execute('select dc_id, test_mode, auth_key, user_id, "date", is_bot from sessions') + row = cursor.fetchone() + if not row: + raise SessionDoesNotExist() + + self._dc_id = row[0] + self._test_mode = bool(row[1]) + self._auth_key = row[2] + self._user_id = row[3] + self._date = row[4] + self._is_bot = bool(row[5]) + + def cache_peer(self, entity): + peer_id = username = phone = access_hash = None + + if isinstance(entity, types.User): + peer_id = entity.id + username = entity.username.lower() if entity.username else None + phone = entity.phone or None + access_hash = entity.access_hash + elif isinstance(entity, (types.Chat, types.ChatForbidden)): + peer_id = -entity.id + # input_peer = types.InputPeerChat(chat_id=entity.id) + elif isinstance(entity, (types.Channel, types.ChannelForbidden)): + peer_id = int('-100' + str(entity.id)) + username = entity.username.lower() if hasattr(entity, 'username') and entity.username else None + access_hash = entity.access_hash + + self._conn.execute('insert or replace into peers_cache values (?, ?, ?, ?)', + (peer_id, access_hash, username, phone)) + + def get_peer_by_id(self, val): + cursor = self._conn.cursor() + cursor.execute('select id, hash from peers_cache where id = ?', (val,)) + row = cursor.fetchone() + if not row: + raise KeyError(val) + return utils.get_input_peer(row[0], row[1]) + + def get_peer_by_username(self, val): + cursor = self._conn.cursor() + cursor.execute('select id, hash from peers_cache where username = ?', (val,)) + row = cursor.fetchone() + if not row: + raise KeyError(val) + return utils.get_input_peer(row[0], row[1]) + + def get_peer_by_phone(self, val): + cursor = self._conn.cursor() + cursor.execute('select id, hash from peers_cache where phone = ?', (val,)) + row = cursor.fetchone() + if not row: + raise KeyError(val) + return utils.get_input_peer(row[0], row[1]) + + def save(self, sync=False): + log.info('Committing SQLite session') + self._conn.execute('delete from sessions') + self._conn.execute('insert into sessions values (?, ?, ?, ?, ?, ?)', + (self._dc_id, self._test_mode, self._auth_key, self._user_id, self._date, self._is_bot)) + self._conn.commit() + + def sync_cleanup(self): + pass From 10fc340efff40dc54e35bc687af5966b3ad077f5 Mon Sep 17 00:00:00 2001 From: bakatrouble Date: Tue, 26 Feb 2019 21:43:23 +0300 Subject: [PATCH 010/202] Add session migrating from json; add some indexes to sqlite sessions --- pyrogram/client/client.py | 2 +- .../client/session_storage/sqlite/0001.sql | 3 ++ .../client/session_storage/sqlite/__init__.py | 29 +++++++++++++++---- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 5fc805c0..d2bc3ee4 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -214,7 +214,7 @@ class Client(Methods, BaseClient): elif session_name.startswith(':'): session_storage = StringSessionStorage(self, session_name) else: - session_storage = JsonSessionStorage(self, session_name) + session_storage = SQLiteSessionStorage(self, session_name) elif isinstance(session_name, SessionStorage): session_storage = session_name else: diff --git a/pyrogram/client/session_storage/sqlite/0001.sql b/pyrogram/client/session_storage/sqlite/0001.sql index d81e9554..c6c51d24 100644 --- a/pyrogram/client/session_storage/sqlite/0001.sql +++ b/pyrogram/client/session_storage/sqlite/0001.sql @@ -18,4 +18,7 @@ create table migrations ( name text primary key ); +create index username_idx on peers_cache(username); +create index phone_idx on peers_cache(phone); + insert into migrations (name) values ('0001'); diff --git a/pyrogram/client/session_storage/sqlite/__init__.py b/pyrogram/client/session_storage/sqlite/__init__.py index 75931109..4fc7ff64 100644 --- a/pyrogram/client/session_storage/sqlite/__init__.py +++ b/pyrogram/client/session_storage/sqlite/__init__.py @@ -18,17 +18,18 @@ import logging import os +import shutil import sqlite3 import pyrogram from ....api import types from ...ext import utils -from .. import MemorySessionStorage, SessionDoesNotExist +from .. import MemorySessionStorage, SessionDoesNotExist, JsonSessionStorage log = logging.getLogger(__name__) -EXTENSION = '.session.sqlite3' +EXTENSION = '.session' MIGRATIONS = ['0001'] @@ -54,13 +55,32 @@ class SQLiteSessionStorage(MemorySessionStorage): with open(os.path.join(os.path.dirname(__file__), '{}.sql'.format(name))) as script: self._conn.executescript(script.read()) + def _migrate_from_json(self): + jss = JsonSessionStorage(self._client, self._session_name) + jss.load() + file_path = self._get_file_name(self._session_name) + self._conn = sqlite3.connect(file_path + '.tmp') + self._apply_migrations(new_db=True) + self._dc_id, self._test_mode, self._auth_key, self._user_id, self._date, self._is_bot = \ + jss.dc_id, jss.test_mode, jss.auth_key, jss.user_id, jss.date, jss.is_bot + self.save() + self._conn.close() + shutil.move(file_path + '.tmp', file_path) + log.warning('Session was migrated from JSON, loading...') + self.load() + def load(self): file_path = self._get_file_name(self._session_name) log.info('Loading SQLite session from {}'.format(file_path)) if os.path.isfile(file_path): - self._conn = sqlite3.connect(file_path) - self._apply_migrations() + try: + self._conn = sqlite3.connect(file_path) + self._apply_migrations() + except sqlite3.DatabaseError: + log.warning('Trying to migrate session from JSON...') + self._migrate_from_json() + return else: self._conn = sqlite3.connect(file_path) self._apply_migrations(new_db=True) @@ -88,7 +108,6 @@ class SQLiteSessionStorage(MemorySessionStorage): access_hash = entity.access_hash elif isinstance(entity, (types.Chat, types.ChatForbidden)): peer_id = -entity.id - # input_peer = types.InputPeerChat(chat_id=entity.id) elif isinstance(entity, (types.Channel, types.ChannelForbidden)): peer_id = int('-100' + str(entity.id)) username = entity.username.lower() if hasattr(entity, 'username') and entity.username else None From 033622cfb85efbd8a09abf8b0949f9ccc0495b90 Mon Sep 17 00:00:00 2001 From: bakatrouble Date: Wed, 27 Feb 2019 22:49:23 +0300 Subject: [PATCH 011/202] Cleanup json session storage specific code as it is used only for migrations --- pyrogram/client/ext/syncer.py | 2 - pyrogram/client/session_storage/abstract.py | 4 -- pyrogram/client/session_storage/json.py | 67 +------------------ pyrogram/client/session_storage/memory.py | 3 - .../client/session_storage/sqlite/__init__.py | 3 - pyrogram/client/session_storage/string.py | 3 - 6 files changed, 1 insertion(+), 81 deletions(-) diff --git a/pyrogram/client/ext/syncer.py b/pyrogram/client/ext/syncer.py index e13212be..9e7d2303 100644 --- a/pyrogram/client/ext/syncer.py +++ b/pyrogram/client/ext/syncer.py @@ -88,5 +88,3 @@ class Syncer: log.critical(e, exc_info=True) else: log.info("Synced {}".format(client.session_name)) - finally: - client.session_storage.sync_cleanup() diff --git a/pyrogram/client/session_storage/abstract.py b/pyrogram/client/session_storage/abstract.py index 39517a01..134d5c8c 100644 --- a/pyrogram/client/session_storage/abstract.py +++ b/pyrogram/client/session_storage/abstract.py @@ -38,10 +38,6 @@ class SessionStorage(abc.ABC): @abc.abstractmethod def save(self, sync=False): ... - - @abc.abstractmethod - def sync_cleanup(self): - ... @property @abc.abstractmethod diff --git a/pyrogram/client/session_storage/json.py b/pyrogram/client/session_storage/json.py index 570e1525..4a48d3c1 100644 --- a/pyrogram/client/session_storage/json.py +++ b/pyrogram/client/session_storage/json.py @@ -59,70 +59,5 @@ class JsonSessionStorage(MemorySessionStorage): self._date = s.get("date", 0) self._is_bot = s.get('is_bot', self._is_bot) - for k, v in s.get("peers_by_id", {}).items(): - self._peers_cache['i' + k] = utils.get_input_peer(int(k), v) - - for k, v in s.get("peers_by_username", {}).items(): - try: - self._peers_cache['u' + k] = self.get_peer_by_id(v) - except KeyError: - pass - - for k, v in s.get("peers_by_phone", {}).items(): - try: - self._peers_cache['p' + k] = self.get_peer_by_id(v) - except KeyError: - pass - def save(self, sync=False): - file_path = self._get_file_name(self._session_name) - - if sync: - file_path += '.tmp' - - log.info('Saving JSON session to {}, sync={}'.format(file_path, sync)) - - auth_key = base64.b64encode(self._auth_key).decode() - auth_key = [auth_key[i: i + 43] for i in range(0, len(auth_key), 43)] # split key in lines of 43 chars - - os.makedirs(self._client.workdir, exist_ok=True) - - data = { - 'dc_id': self._dc_id, - 'test_mode': self._test_mode, - 'auth_key': auth_key, - 'user_id': self._user_id, - 'date': self._date, - 'is_bot': self._is_bot, - 'peers_by_id': { - k[1:]: getattr(v, "access_hash", None) - for k, v in self._peers_cache.copy().items() - if k[0] == 'i' - }, - 'peers_by_username': { - k[1:]: utils.get_peer_id(v) - for k, v in self._peers_cache.copy().items() - if k[0] == 'u' - }, - 'peers_by_phone': { - k[1:]: utils.get_peer_id(v) - for k, v in self._peers_cache.copy().items() - if k[0] == 'p' - } - } - - with open(file_path, "w", encoding="utf-8") as f: - json.dump(data, f, indent=4) - - f.flush() - os.fsync(f.fileno()) - - # execution won't be here if an error has occurred earlier - if sync: - shutil.move(file_path, self._get_file_name(self._session_name)) - - def sync_cleanup(self): - try: - os.remove(self._get_file_name(self._session_name) + '.tmp') - except OSError: - pass + pass diff --git a/pyrogram/client/session_storage/memory.py b/pyrogram/client/session_storage/memory.py index d5f92f0d..c0610e70 100644 --- a/pyrogram/client/session_storage/memory.py +++ b/pyrogram/client/session_storage/memory.py @@ -20,9 +20,6 @@ class MemorySessionStorage(SessionStorage): def save(self, sync=False): pass - def sync_cleanup(self): - pass - @property def dc_id(self): return self._dc_id diff --git a/pyrogram/client/session_storage/sqlite/__init__.py b/pyrogram/client/session_storage/sqlite/__init__.py index 4fc7ff64..0308a4dc 100644 --- a/pyrogram/client/session_storage/sqlite/__init__.py +++ b/pyrogram/client/session_storage/sqlite/__init__.py @@ -146,6 +146,3 @@ class SQLiteSessionStorage(MemorySessionStorage): self._conn.execute('insert into sessions values (?, ?, ?, ?, ?, ?)', (self._dc_id, self._test_mode, self._auth_key, self._user_id, self._date, self._is_bot)) self._conn.commit() - - def sync_cleanup(self): - pass diff --git a/pyrogram/client/session_storage/string.py b/pyrogram/client/session_storage/string.py index f8ec740a..11051323 100644 --- a/pyrogram/client/session_storage/string.py +++ b/pyrogram/client/session_storage/string.py @@ -44,6 +44,3 @@ class StringSessionStorage(MemorySessionStorage): encoded = ':' + base64.b64encode(packed, b'-_').decode('latin-1').rstrip('=') split = '\n'.join(['"{}"'.format(encoded[i: i + 50]) for i in range(0, len(encoded), 50)]) print('Created session string:\n{}'.format(split)) - - def sync_cleanup(self): - pass From 8cc61f00ed74fc8290b4d75cc1503275a42d5136 Mon Sep 17 00:00:00 2001 From: bakatrouble Date: Fri, 1 Mar 2019 21:23:01 +0300 Subject: [PATCH 012/202] Fix threading with sqlite storage --- .../client/session_storage/sqlite/__init__.py | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/pyrogram/client/session_storage/sqlite/__init__.py b/pyrogram/client/session_storage/sqlite/__init__.py index 0308a4dc..a16e75e8 100644 --- a/pyrogram/client/session_storage/sqlite/__init__.py +++ b/pyrogram/client/session_storage/sqlite/__init__.py @@ -20,6 +20,7 @@ import logging import os import shutil import sqlite3 +from threading import Lock import pyrogram from ....api import types @@ -38,6 +39,7 @@ class SQLiteSessionStorage(MemorySessionStorage): super(SQLiteSessionStorage, self).__init__(client) self._session_name = session_name self._conn = None # type: sqlite3.Connection + self._lock = Lock() def _get_file_name(self, name: str): if not name.endswith(EXTENSION): @@ -45,6 +47,7 @@ class SQLiteSessionStorage(MemorySessionStorage): return os.path.join(self._client.workdir, name) def _apply_migrations(self, new_db=False): + self._conn.execute('PRAGMA read_uncommitted = true') migrations = MIGRATIONS.copy() if not new_db: cursor = self._conn.cursor() @@ -75,14 +78,14 @@ class SQLiteSessionStorage(MemorySessionStorage): if os.path.isfile(file_path): try: - self._conn = sqlite3.connect(file_path) + self._conn = sqlite3.connect(file_path, isolation_level='EXCLUSIVE', check_same_thread=False) self._apply_migrations() except sqlite3.DatabaseError: log.warning('Trying to migrate session from JSON...') self._migrate_from_json() return else: - self._conn = sqlite3.connect(file_path) + self._conn = sqlite3.connect(file_path, isolation_level='EXCLUSIVE', check_same_thread=False) self._apply_migrations(new_db=True) cursor = self._conn.cursor() @@ -113,8 +116,9 @@ class SQLiteSessionStorage(MemorySessionStorage): username = entity.username.lower() if hasattr(entity, 'username') and entity.username else None access_hash = entity.access_hash - self._conn.execute('insert or replace into peers_cache values (?, ?, ?, ?)', - (peer_id, access_hash, username, phone)) + with self._lock: + self._conn.execute('insert or replace into peers_cache values (?, ?, ?, ?)', + (peer_id, access_hash, username, phone)) def get_peer_by_id(self, val): cursor = self._conn.cursor() @@ -142,7 +146,8 @@ class SQLiteSessionStorage(MemorySessionStorage): def save(self, sync=False): log.info('Committing SQLite session') - self._conn.execute('delete from sessions') - self._conn.execute('insert into sessions values (?, ?, ?, ?, ?, ?)', - (self._dc_id, self._test_mode, self._auth_key, self._user_id, self._date, self._is_bot)) - self._conn.commit() + with self._lock: + self._conn.execute('delete from sessions') + self._conn.execute('insert into sessions values (?, ?, ?, ?, ?, ?)', + (self._dc_id, self._test_mode, self._auth_key, self._user_id, self._date, self._is_bot)) + self._conn.commit() From 85700b0ffc191458677b6d29452cff121a5d4d13 Mon Sep 17 00:00:00 2001 From: bakatrouble Date: Fri, 1 Mar 2019 21:23:53 +0300 Subject: [PATCH 013/202] Do not cache entities without access_hash --- pyrogram/client/client.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 2bcf294f..33b3f137 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -762,6 +762,8 @@ class Client(Methods, BaseClient): types.Chat, types.ChatForbidden, types.Channel, types.ChannelForbidden]]): for entity in entities: + if isinstance(entity, (types.User, types.Channel, types.ChannelForbidden)) and not entity.access_hash: + continue self.session_storage.cache_peer(entity) def download_worker(self): From da4ff268a41073e0767373532f69c58ac8394333 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 14 Jun 2019 02:46:27 +0200 Subject: [PATCH 014/202] Add edit, edit_caption, edit_media and edit_reply_markup bound methods to the CallbackQuery type --- docs/source/api/bound-methods.rst | 10 +- pyrogram/client/ext/base_client.py | 6 + .../bots_and_keyboards/callback_query.py | 172 ++++++++++++++++++ 3 files changed, 187 insertions(+), 1 deletion(-) diff --git a/docs/source/api/bound-methods.rst b/docs/source/api/bound-methods.rst index d01a4383..38389ae6 100644 --- a/docs/source/api/bound-methods.rst +++ b/docs/source/api/bound-methods.rst @@ -81,9 +81,13 @@ CallbackQuery ^^^^^^^^^^^^^ .. hlist:: - :columns: 2 + :columns: 5 - :meth:`~CallbackQuery.answer` + - :meth:`~CallbackQuery.edit` + - :meth:`~CallbackQuery.edit_caption` + - :meth:`~CallbackQuery.edit_media` + - :meth:`~CallbackQuery.edit_reply_markup` InlineQuery ^^^^^^^^^^^ @@ -137,6 +141,10 @@ Details .. CallbackQuery .. automethod:: CallbackQuery.answer() +.. automethod:: CallbackQuery.edit() +.. automethod:: CallbackQuery.edit_caption() +.. automethod:: CallbackQuery.edit_media() +.. automethod:: CallbackQuery.edit_reply_markup() .. InlineQuery .. automethod:: InlineQuery.answer() diff --git a/pyrogram/client/ext/base_client.py b/pyrogram/client/ext/base_client.py index b4c16666..e584c743 100644 --- a/pyrogram/client/ext/base_client.py +++ b/pyrogram/client/ext/base_client.py @@ -159,3 +159,9 @@ class BaseClient: def edit_message_text(self, *args, **kwargs): pass + + def edit_message_media(self, *args, **kwargs): + pass + + def edit_message_reply_markup(self, *args, **kwargs): + pass diff --git a/pyrogram/client/types/bots_and_keyboards/callback_query.py b/pyrogram/client/types/bots_and_keyboards/callback_query.py index fa0d8be2..df0f6d33 100644 --- a/pyrogram/client/types/bots_and_keyboards/callback_query.py +++ b/pyrogram/client/types/bots_and_keyboards/callback_query.py @@ -172,3 +172,175 @@ class CallbackQuery(Object, Update): url=url, cache_time=cache_time ) + + def edit( + self, + text: str, + parse_mode: str = "", + disable_web_page_preview: bool = None, + reply_markup: "pyrogram.InlineKeyboardMarkup" = None + ) -> Union["pyrogram.Message", bool]: + """Bound method *edit* of :obj:`CallbackQuery`. + + Parameters: + text (``str``): + New text of the message. + + parse_mode (``str``, *optional*): + Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline + URLs in your message. Defaults to "markdown". + + disable_web_page_preview (``bool``, *optional*): + Disables link previews for links in this message. + + reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + An InlineKeyboardMarkup object. + + Returns: + :obj:`Message` | ``bool``: On success, if the edited message was sent by the bot, the edited message is + returned, otherwise True is returned (message sent via the bot, as inline query result). + + Raises: + RPCError: In case of a Telegram RPC error. + """ + chat_id = None + message_id = None + inline_message_id = None + + if self.message is not None: + chat_id = self.message.chat.id + message_id = self.message.message_id + + if self.inline_message_id is not None: + inline_message_id = self.inline_message_id + + return self._client.edit_message_text( + text=text, + chat_id=chat_id, + message_id=message_id, + inline_message_id=inline_message_id, + parse_mode=parse_mode, + disable_web_page_preview=disable_web_page_preview, + reply_markup=reply_markup + ) + + def edit_caption( + self, + caption: str, + parse_mode: str = "", + reply_markup: "pyrogram.InlineKeyboardMarkup" = None + ) -> Union["pyrogram.Message", bool]: + """Bound method *edit_caption* of :obj:`Message`. + + Parameters: + caption (``str``): + New caption of the message. + + parse_mode (``str``, *optional*): + Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline + URLs in your message. Defaults to "markdown". + + reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + An InlineKeyboardMarkup object. + + Returns: + :obj:`Message` | ``bool``: On success, if the edited message was sent by the bot, the edited message is + returned, otherwise True is returned (message sent via the bot, as inline query result). + + Raises: + RPCError: In case of a Telegram RPC error. + """ + chat_id = None + message_id = None + inline_message_id = None + + if self.message is not None: + chat_id = self.message.chat.id + message_id = self.message.message_id + + if self.inline_message_id is not None: + inline_message_id = self.inline_message_id + + return self._client.edit_message_text( + text=caption, + chat_id=chat_id, + message_id=message_id, + inline_message_id=inline_message_id, + parse_mode=parse_mode, + reply_markup=reply_markup + ) + + def edit_media( + self, + media: "pyrogram.InputMedia", + reply_markup: "pyrogram.InlineKeyboardMarkup" = None + ) -> Union["pyrogram.Message", bool]: + """Bound method *edit_media* of :obj:`Message`. + + Parameters: + media (:obj:`InputMedia`): + One of the InputMedia objects describing an animation, audio, document, photo or video. + + reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + An InlineKeyboardMarkup object. + + Returns: + :obj:`Message` | ``bool``: On success, if the edited message was sent by the bot, the edited message is + returned, otherwise True is returned (message sent via the bot, as inline query result). + + Raises: + RPCError: In case of a Telegram RPC error. + """ + chat_id = None + message_id = None + inline_message_id = None + + if self.message is not None: + chat_id = self.message.chat.id + message_id = self.message.message_id + + if self.inline_message_id is not None: + inline_message_id = self.inline_message_id + + return self._client.edit_message_media( + media=media, + chat_id=chat_id, + message_id=message_id, + inline_message_id=inline_message_id, + reply_markup=reply_markup + ) + + def edit_reply_markup( + self, + reply_markup: "pyrogram.InlineKeyboardMarkup" = None + ) -> Union["pyrogram.Message", bool]: + """Bound method *edit_reply_markup* of :obj:`Message`. + + Parameters: + reply_markup (:obj:`InlineKeyboardMarkup`): + An InlineKeyboardMarkup object. + + Returns: + :obj:`Message` | ``bool``: On success, if the edited message was sent by the bot, the edited message is + returned, otherwise True is returned (message sent via the bot, as inline query result). + + Raises: + RPCError: In case of a Telegram RPC error. + """ + chat_id = None + message_id = None + inline_message_id = None + + if self.message is not None: + chat_id = self.message.chat.id + message_id = self.message.message_id + + if self.inline_message_id is not None: + inline_message_id = self.inline_message_id + + return self._client.edit_message_reply_markup( + reply_markup=reply_markup, + chat_id=chat_id, + message_id=message_id, + inline_message_id=inline_message_id + ) From c485715db1b3ea9578d334624113b8a7f563912f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 14 Jun 2019 02:47:17 +0200 Subject: [PATCH 015/202] Small docstrings fixup --- .../methods/messages/edit_message_media.py | 2 +- .../client/types/messages_and_media/message.py | 16 +++------------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/pyrogram/client/methods/messages/edit_message_media.py b/pyrogram/client/methods/messages/edit_message_media.py index 2b3ca5d5..d74e2b56 100644 --- a/pyrogram/client/methods/messages/edit_message_media.py +++ b/pyrogram/client/methods/messages/edit_message_media.py @@ -45,7 +45,7 @@ class EditMessageMedia(BaseClient): Use previously uploaded file via its file_id or specify a URL. Parameters: - media (:obj:`InputMedia`) + media (:obj:`InputMedia`): One of the InputMedia objects describing an animation, audio, document, photo or video. chat_id (``int`` | ``str``, *optional*): diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index c6f3cc77..2a1ba2c8 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -2361,12 +2361,7 @@ class Message(Object, Update): text: str, parse_mode: str = "", disable_web_page_preview: bool = None, - reply_markup: Union[ - "pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply" - ] = None + reply_markup: "pyrogram.InlineKeyboardMarkup" = None ) -> "Message": """Bound method *edit* of :obj:`Message`. @@ -2418,12 +2413,7 @@ class Message(Object, Update): self, caption: str, parse_mode: str = "", - reply_markup: Union[ - "pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply" - ] = None + reply_markup: "pyrogram.InlineKeyboardMarkup" = None ) -> "Message": """Bound method *edit_caption* of :obj:`Message`. @@ -2486,7 +2476,7 @@ class Message(Object, Update): message.edit_media(media) Parameters: - media (:obj:`InputMediaAnimation` | :obj:`InputMediaAudio` | :obj:`InputMediaDocument` | :obj:`InputMediaPhoto` | :obj:`InputMediaVideo`) + media (:obj:`InputMedia`): One of the InputMedia objects describing an animation, audio, document, photo or video. reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): From 3ae77d55c758f91346ad8a122d0da2a3d65fd545 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 14 Jun 2019 02:52:01 +0200 Subject: [PATCH 016/202] Rename edit -> edit_text and reply -> reply_text bound methods --- docs/source/api/bound-methods.rst | 20 +++++++++---------- .../bots_and_keyboards/callback_query.py | 4 ++-- .../types/messages_and_media/message.py | 12 +++++------ 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/docs/source/api/bound-methods.rst b/docs/source/api/bound-methods.rst index 38389ae6..2f2200ab 100644 --- a/docs/source/api/bound-methods.rst +++ b/docs/source/api/bound-methods.rst @@ -34,13 +34,13 @@ Message - :meth:`~Message.click` - :meth:`~Message.delete` - :meth:`~Message.download` - - :meth:`~Message.edit` + - :meth:`~Message.forward` + - :meth:`~Message.pin` + - :meth:`~Message.edit_text` - :meth:`~Message.edit_caption` - :meth:`~Message.edit_media` - :meth:`~Message.edit_reply_markup` - - :meth:`~Message.forward` - - :meth:`~Message.pin` - - :meth:`~Message.reply` + - :meth:`~Message.reply_text` - :meth:`~Message.reply_animation` - :meth:`~Message.reply_audio` - :meth:`~Message.reply_cached_media` @@ -84,7 +84,7 @@ CallbackQuery :columns: 5 - :meth:`~CallbackQuery.answer` - - :meth:`~CallbackQuery.edit` + - :meth:`~CallbackQuery.edit_text` - :meth:`~CallbackQuery.edit_caption` - :meth:`~CallbackQuery.edit_media` - :meth:`~CallbackQuery.edit_reply_markup` @@ -106,13 +106,13 @@ Details .. automethod:: Message.click() .. automethod:: Message.delete() .. automethod:: Message.download() -.. automethod:: Message.edit() +.. automethod:: Message.forward() +.. automethod:: Message.pin() +.. automethod:: Message.edit_text() .. automethod:: Message.edit_caption() .. automethod:: Message.edit_media() .. automethod:: Message.edit_reply_markup() -.. automethod:: Message.forward() -.. automethod:: Message.pin() -.. automethod:: Message.reply() +.. automethod:: Message.reply_text() .. automethod:: Message.reply_animation() .. automethod:: Message.reply_audio() .. automethod:: Message.reply_cached_media() @@ -141,7 +141,7 @@ Details .. CallbackQuery .. automethod:: CallbackQuery.answer() -.. automethod:: CallbackQuery.edit() +.. automethod:: CallbackQuery.edit_text() .. automethod:: CallbackQuery.edit_caption() .. automethod:: CallbackQuery.edit_media() .. automethod:: CallbackQuery.edit_reply_markup() diff --git a/pyrogram/client/types/bots_and_keyboards/callback_query.py b/pyrogram/client/types/bots_and_keyboards/callback_query.py index df0f6d33..4bb4a7e4 100644 --- a/pyrogram/client/types/bots_and_keyboards/callback_query.py +++ b/pyrogram/client/types/bots_and_keyboards/callback_query.py @@ -173,14 +173,14 @@ class CallbackQuery(Object, Update): cache_time=cache_time ) - def edit( + def edit_text( self, text: str, parse_mode: str = "", disable_web_page_preview: bool = None, reply_markup: "pyrogram.InlineKeyboardMarkup" = None ) -> Union["pyrogram.Message", bool]: - """Bound method *edit* of :obj:`CallbackQuery`. + """Bound method *edit_text* of :obj:`CallbackQuery`. Parameters: text (``str``): diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 2a1ba2c8..cd59b5eb 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -651,7 +651,7 @@ class Message(Object, Update): return parsed_message - def reply( + def reply_text( self, text: str, quote: bool = None, @@ -661,7 +661,7 @@ class Message(Object, Update): reply_to_message_id: int = None, reply_markup=None ) -> "Message": - """Bound method *reply* of :obj:`Message`. + """Bound method *reply_text* of :obj:`Message`. Use as a shortcut for: @@ -676,7 +676,7 @@ class Message(Object, Update): Example: .. code-block:: python - message.reply("hello", quote=True) + message.reply_text("hello", quote=True) Parameters: text (``str``): @@ -2356,14 +2356,14 @@ class Message(Object, Update): progress_args=progress_args ) - def edit( + def edit_text( self, text: str, parse_mode: str = "", disable_web_page_preview: bool = None, reply_markup: "pyrogram.InlineKeyboardMarkup" = None ) -> "Message": - """Bound method *edit* of :obj:`Message`. + """Bound method *edit_text* of :obj:`Message`. Use as a shortcut for: @@ -2378,7 +2378,7 @@ class Message(Object, Update): Example: .. code-block:: python - message.edit("hello") + message.edit_text("hello") Parameters: text (``str``): From 3ed1bb0d86e604d75a809468f3395cd401d060a6 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 14 Jun 2019 03:57:12 +0200 Subject: [PATCH 017/202] Rename CallbackQuery edit_* bound methods to edit_message_* We are editing the message the callback query comes from, not the callback query itself. --- docs/source/api/bound-methods.rst | 18 +++++++++--------- .../types/bots_and_keyboards/callback_query.py | 16 ++++++++-------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/docs/source/api/bound-methods.rst b/docs/source/api/bound-methods.rst index 2f2200ab..83b3dbbe 100644 --- a/docs/source/api/bound-methods.rst +++ b/docs/source/api/bound-methods.rst @@ -81,13 +81,13 @@ CallbackQuery ^^^^^^^^^^^^^ .. hlist:: - :columns: 5 + :columns: 3 - :meth:`~CallbackQuery.answer` - - :meth:`~CallbackQuery.edit_text` - - :meth:`~CallbackQuery.edit_caption` - - :meth:`~CallbackQuery.edit_media` - - :meth:`~CallbackQuery.edit_reply_markup` + - :meth:`~CallbackQuery.edit_message_text` + - :meth:`~CallbackQuery.edit_message_caption` + - :meth:`~CallbackQuery.edit_message_media` + - :meth:`~CallbackQuery.edit_message_reply_markup` InlineQuery ^^^^^^^^^^^ @@ -141,10 +141,10 @@ Details .. CallbackQuery .. automethod:: CallbackQuery.answer() -.. automethod:: CallbackQuery.edit_text() -.. automethod:: CallbackQuery.edit_caption() -.. automethod:: CallbackQuery.edit_media() -.. automethod:: CallbackQuery.edit_reply_markup() +.. automethod:: CallbackQuery.edit_message_text() +.. automethod:: CallbackQuery.edit_message_caption() +.. automethod:: CallbackQuery.edit_message_media() +.. automethod:: CallbackQuery.edit_message_reply_markup() .. InlineQuery .. automethod:: InlineQuery.answer() diff --git a/pyrogram/client/types/bots_and_keyboards/callback_query.py b/pyrogram/client/types/bots_and_keyboards/callback_query.py index 4bb4a7e4..6872d65b 100644 --- a/pyrogram/client/types/bots_and_keyboards/callback_query.py +++ b/pyrogram/client/types/bots_and_keyboards/callback_query.py @@ -173,14 +173,14 @@ class CallbackQuery(Object, Update): cache_time=cache_time ) - def edit_text( + def edit_message_text( self, text: str, parse_mode: str = "", disable_web_page_preview: bool = None, reply_markup: "pyrogram.InlineKeyboardMarkup" = None ) -> Union["pyrogram.Message", bool]: - """Bound method *edit_text* of :obj:`CallbackQuery`. + """Bound method *edit_message_text* of :obj:`CallbackQuery`. Parameters: text (``str``): @@ -224,13 +224,13 @@ class CallbackQuery(Object, Update): reply_markup=reply_markup ) - def edit_caption( + def edit_message_caption( self, caption: str, parse_mode: str = "", reply_markup: "pyrogram.InlineKeyboardMarkup" = None ) -> Union["pyrogram.Message", bool]: - """Bound method *edit_caption* of :obj:`Message`. + """Bound method *edit_message_caption* of :obj:`CallbackQuery`. Parameters: caption (``str``): @@ -270,12 +270,12 @@ class CallbackQuery(Object, Update): reply_markup=reply_markup ) - def edit_media( + def edit_message_media( self, media: "pyrogram.InputMedia", reply_markup: "pyrogram.InlineKeyboardMarkup" = None ) -> Union["pyrogram.Message", bool]: - """Bound method *edit_media* of :obj:`Message`. + """Bound method *edit_message_media* of :obj:`CallbackQuery`. Parameters: media (:obj:`InputMedia`): @@ -310,11 +310,11 @@ class CallbackQuery(Object, Update): reply_markup=reply_markup ) - def edit_reply_markup( + def edit_message_reply_markup( self, reply_markup: "pyrogram.InlineKeyboardMarkup" = None ) -> Union["pyrogram.Message", bool]: - """Bound method *edit_reply_markup* of :obj:`Message`. + """Bound method *edit_message_reply_markup* of :obj:`CallbackQuery`. Parameters: reply_markup (:obj:`InlineKeyboardMarkup`): From 61ed44ff5f1c2dee91da309ce24f6b2b5884be13 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 14 Jun 2019 04:52:05 +0200 Subject: [PATCH 018/202] Add edit_inline_* methods to deal with inline messages only --- docs/source/api/methods.rst | 14 ++- pyrogram/client/ext/base_client.py | 9 ++ pyrogram/client/methods/messages/__init__.py | 10 +- .../methods/messages/edit_inline_caption.py | 58 ++++++++++ .../methods/messages/edit_inline_media.py | 104 ++++++++++++++++++ .../messages/edit_inline_reply_markup.py | 50 +++++++++ .../methods/messages/edit_inline_text.py | 67 +++++++++++ .../methods/messages/edit_message_caption.py | 27 ++--- .../methods/messages/edit_message_media.py | 37 ++----- .../messages/edit_message_reply_markup.py | 34 ++---- .../methods/messages/edit_message_text.py | 36 ++---- 11 files changed, 344 insertions(+), 102 deletions(-) create mode 100644 pyrogram/client/methods/messages/edit_inline_caption.py create mode 100644 pyrogram/client/methods/messages/edit_inline_media.py create mode 100644 pyrogram/client/methods/messages/edit_inline_reply_markup.py create mode 100644 pyrogram/client/methods/messages/edit_inline_text.py diff --git a/docs/source/api/methods.rst b/docs/source/api/methods.rst index ac515f6e..4a3eefd8 100644 --- a/docs/source/api/methods.rst +++ b/docs/source/api/methods.rst @@ -55,11 +55,15 @@ Messages - :meth:`~Client.send_venue` - :meth:`~Client.send_contact` - :meth:`~Client.send_cached_media` - - :meth:`~Client.send_chat_action` - :meth:`~Client.edit_message_text` - :meth:`~Client.edit_message_caption` - - :meth:`~Client.edit_message_reply_markup` - :meth:`~Client.edit_message_media` + - :meth:`~Client.edit_message_reply_markup` + - :meth:`~Client.edit_inline_text` + - :meth:`~Client.edit_inline_caption` + - :meth:`~Client.edit_inline_media` + - :meth:`~Client.edit_inline_reply_markup` + - :meth:`~Client.send_chat_action` - :meth:`~Client.delete_messages` - :meth:`~Client.get_messages` - :meth:`~Client.get_history` @@ -203,8 +207,12 @@ Details .. automethod:: Client.send_chat_action() .. automethod:: Client.edit_message_text() .. automethod:: Client.edit_message_caption() -.. automethod:: Client.edit_message_reply_markup() .. automethod:: Client.edit_message_media() +.. automethod:: Client.edit_message_reply_markup() +.. automethod:: Client.edit_inline_text() +.. automethod:: Client.edit_inline_caption() +.. automethod:: Client.edit_inline_media() +.. automethod:: Client.edit_inline_reply_markup() .. automethod:: Client.delete_messages() .. automethod:: Client.get_messages() .. automethod:: Client.get_history() diff --git a/pyrogram/client/ext/base_client.py b/pyrogram/client/ext/base_client.py index e584c743..c8d1beab 100644 --- a/pyrogram/client/ext/base_client.py +++ b/pyrogram/client/ext/base_client.py @@ -160,8 +160,17 @@ class BaseClient: def edit_message_text(self, *args, **kwargs): pass + def edit_inline_text(self, *args, **kwargs): + pass + def edit_message_media(self, *args, **kwargs): pass + def edit_inline_media(self, *args, **kwargs): + pass + def edit_message_reply_markup(self, *args, **kwargs): pass + + def edit_inline_reply_markup(self, *args, **kwargs): + pass diff --git a/pyrogram/client/methods/messages/__init__.py b/pyrogram/client/methods/messages/__init__.py index 07df7a64..aa0b0c94 100644 --- a/pyrogram/client/methods/messages/__init__.py +++ b/pyrogram/client/methods/messages/__init__.py @@ -18,6 +18,10 @@ from .delete_messages import DeleteMessages from .download_media import DownloadMedia +from .edit_inline_caption import EditInlineCaption +from .edit_inline_media import EditInlineMedia +from .edit_inline_reply_markup import EditInlineReplyMarkup +from .edit_inline_text import EditInlineText from .edit_message_caption import EditMessageCaption from .edit_message_media import EditMessageMedia from .edit_message_reply_markup import EditMessageReplyMarkup @@ -82,6 +86,10 @@ class Messages( SendCachedMedia, GetHistoryCount, SendAnimatedSticker, - ReadHistory + ReadHistory, + EditInlineText, + EditInlineCaption, + EditInlineMedia, + EditInlineReplyMarkup ): pass diff --git a/pyrogram/client/methods/messages/edit_inline_caption.py b/pyrogram/client/methods/messages/edit_inline_caption.py new file mode 100644 index 00000000..a9bbc551 --- /dev/null +++ b/pyrogram/client/methods/messages/edit_inline_caption.py @@ -0,0 +1,58 @@ +# 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.client.ext import BaseClient + + +class EditInlineCaption(BaseClient): + def edit_inline_caption( + self, + inline_message_id: str, + caption: str, + parse_mode: str = "", + reply_markup: "pyrogram.InlineKeyboardMarkup" = None + ) -> bool: + """Edit the caption of **inline** media messages. + + Parameters: + inline_message_id (``str``): + Identifier of the inline message. + + caption (``str``): + New caption of the media message. + + parse_mode (``str``, *optional*): + Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline + URLs in your message. Defaults to "markdown". + + reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + An InlineKeyboardMarkup object. + + Returns: + ``bool``: On success, True is returned. + + Raises: + RPCError: In case of a Telegram RPC error. + """ + return self.edit_inline_text( + inline_message_id=inline_message_id, + text=caption, + parse_mode=parse_mode, + reply_markup=reply_markup + ) diff --git a/pyrogram/client/methods/messages/edit_inline_media.py b/pyrogram/client/methods/messages/edit_inline_media.py new file mode 100644 index 00000000..87e692fd --- /dev/null +++ b/pyrogram/client/methods/messages/edit_inline_media.py @@ -0,0 +1,104 @@ +# 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, types +from pyrogram.client.ext import BaseClient, utils +from pyrogram.client.types import ( + InputMediaPhoto, InputMediaVideo, InputMediaAudio, + InputMediaAnimation, InputMediaDocument +) +from pyrogram.client.types.input_media import InputMedia + + +class EditInlineMedia(BaseClient): + def edit_inline_media( + self, + inline_message_id: str, + media: InputMedia, + reply_markup: "pyrogram.InlineKeyboardMarkup" = None + ) -> bool: + """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. + + Parameters: + inline_message_id (``str``): + Required if *chat_id* and *message_id* are not specified. + Identifier of the inline message. + + media (:obj:`InputMedia`): + One of the InputMedia objects describing an animation, audio, document, photo or video. + + reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + An InlineKeyboardMarkup object. + + Returns: + ``bool``: On success, True is returned. + + Raises: + RPCError: In case of a Telegram RPC error. + """ + style = self.html if media.parse_mode.lower() == "html" else self.markdown + caption = media.caption + + if isinstance(media, InputMediaPhoto): + if media.media.startswith("http"): + media = types.InputMediaPhotoExternal( + url=media.media + ) + else: + media = utils.get_input_media_from_file_id(media.media, 2) + elif isinstance(media, InputMediaVideo): + if media.media.startswith("http"): + media = types.InputMediaDocumentExternal( + url=media.media + ) + else: + media = utils.get_input_media_from_file_id(media.media, 4) + elif isinstance(media, InputMediaAudio): + if media.media.startswith("http"): + media = types.InputMediaDocumentExternal( + url=media.media + ) + else: + media = utils.get_input_media_from_file_id(media.media, 9) + elif isinstance(media, InputMediaAnimation): + if media.media.startswith("http"): + media = types.InputMediaDocumentExternal( + url=media.media + ) + else: + media = utils.get_input_media_from_file_id(media.media, 10) + elif isinstance(media, InputMediaDocument): + if media.media.startswith("http"): + media = types.InputMediaDocumentExternal( + url=media.media + ) + else: + media = utils.get_input_media_from_file_id(media.media, 5) + + return self.send( + functions.messages.EditInlineBotMessage( + id=utils.unpack_inline_message_id(inline_message_id), + media=media, + reply_markup=reply_markup.write() if reply_markup else None, + **style.parse(caption) + ) + ) diff --git a/pyrogram/client/methods/messages/edit_inline_reply_markup.py b/pyrogram/client/methods/messages/edit_inline_reply_markup.py new file mode 100644 index 00000000..0326ed72 --- /dev/null +++ b/pyrogram/client/methods/messages/edit_inline_reply_markup.py @@ -0,0 +1,50 @@ +# 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 pyrogram.client.ext import BaseClient, utils + + +class EditInlineReplyMarkup(BaseClient): + def edit_inline_reply_markup( + self, + 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). + + Parameters: + inline_message_id (``str``): + Identifier of the inline message. + + reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + An InlineKeyboardMarkup object. + + Returns: + ``bool``: On success, True is returned. + + Raises: + RPCError: In case of a Telegram RPC error. + """ + return self.send( + functions.messages.EditInlineBotMessage( + id=utils.unpack_inline_message_id(inline_message_id), + reply_markup=reply_markup.write() if reply_markup else None, + ) + ) diff --git a/pyrogram/client/methods/messages/edit_inline_text.py b/pyrogram/client/methods/messages/edit_inline_text.py new file mode 100644 index 00000000..927fd80f --- /dev/null +++ b/pyrogram/client/methods/messages/edit_inline_text.py @@ -0,0 +1,67 @@ +# 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 pyrogram.client.ext import BaseClient, utils + + +class EditInlineText(BaseClient): + def edit_inline_text( + self, + inline_message_id: str, + text: str, + parse_mode: str = "", + disable_web_page_preview: bool = None, + reply_markup: "pyrogram.InlineKeyboardMarkup" = None + ) -> bool: + """Edit the text of **inline** messages. + + Parameters: + inline_message_id (``str``): + Identifier of the inline message. + + text (``str``): + New text of the message. + + parse_mode (``str``, *optional*): + Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline + URLs in your message. Defaults to "markdown". + + disable_web_page_preview (``bool``, *optional*): + Disables link previews for links in this message. + + reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + An InlineKeyboardMarkup object. + + Returns: + ``bool``: On success, True is returned. + + Raises: + RPCError: In case of a Telegram RPC error. + """ + style = self.html if parse_mode.lower() == "html" else self.markdown + + return self.send( + functions.messages.EditInlineBotMessage( + id=utils.unpack_inline_message_id(inline_message_id), + no_webpage=disable_web_page_preview or None, + reply_markup=reply_markup.write() if reply_markup else None, + **style.parse(text) + ) + ) diff --git a/pyrogram/client/methods/messages/edit_message_caption.py b/pyrogram/client/methods/messages/edit_message_caption.py index e9866573..52c22726 100644 --- a/pyrogram/client/methods/messages/edit_message_caption.py +++ b/pyrogram/client/methods/messages/edit_message_caption.py @@ -25,32 +25,25 @@ from pyrogram.client.ext import BaseClient class EditMessageCaption(BaseClient): def edit_message_caption( self, + chat_id: Union[int, str], + message_id: int, caption: str, - chat_id: Union[int, str] = None, - message_id: int = None, - inline_message_id: str = None, parse_mode: str = "", reply_markup: "pyrogram.InlineKeyboardMarkup" = None ) -> "pyrogram.Message": - """Edit caption of media messages. + """Edit the caption of media messages. Parameters: - caption (``str``): - New caption of the media message. - - chat_id (``int`` | ``str``, *optional*): - Required if *inline_message_id* is not specified. + 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). - message_id (``int``, *optional*): - Required if *inline_message_id* is not specified. + message_id (``int``): Message identifier in the chat specified in chat_id. - inline_message_id (``str``, *optional*): - Required if *chat_id* and *message_id* are not specified. - Identifier of the inline message. + caption (``str``): + New caption of the media message. parse_mode (``str``, *optional*): Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline @@ -60,17 +53,15 @@ class EditMessageCaption(BaseClient): An InlineKeyboardMarkup object. Returns: - :obj:`Message` | ``bool``: On success, if the edited message was sent by the bot, the edited message is - returned, otherwise True is returned (message sent via the bot, as inline query result). + :obj:`Message`: On success, the edited message is returned. Raises: RPCError: In case of a Telegram RPC error. """ return self.edit_message_text( - text=caption, chat_id=chat_id, message_id=message_id, - inline_message_id=inline_message_id, + text=caption, parse_mode=parse_mode, reply_markup=reply_markup ) diff --git a/pyrogram/client/methods/messages/edit_message_media.py b/pyrogram/client/methods/messages/edit_message_media.py index d74e2b56..b65804fd 100644 --- a/pyrogram/client/methods/messages/edit_message_media.py +++ b/pyrogram/client/methods/messages/edit_message_media.py @@ -32,42 +32,33 @@ from pyrogram.client.types.input_media import InputMedia class EditMessageMedia(BaseClient): def edit_message_media( self, + chat_id: Union[int, str], + message_id: int, media: InputMedia, - chat_id: Union[int, str] = None, - message_id: int = None, - inline_message_id: str = None, reply_markup: "pyrogram.InlineKeyboardMarkup" = None ) -> "pyrogram.Message": """Edit animation, audio, document, photo or video messages. - If a message is a part of a message album, then it can be edited only to a photo or a video. Otherwise, - message type can be changed arbitrarily. When inline message is edited, new file can't be uploaded. - Use previously uploaded file via its file_id or specify a URL. + If a message is a part of a message album, then it can be edited only to a photo or a video. Otherwise, the + message type can be changed arbitrarily. Parameters: - media (:obj:`InputMedia`): - One of the InputMedia objects describing an animation, audio, document, photo or video. - - chat_id (``int`` | ``str``, *optional*): - Required if *inline_message_id* is not specified. + 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). - message_id (``int``, *optional*): - Required if *inline_message_id* is not specified. + message_id (``int``): Message identifier in the chat specified in chat_id. - inline_message_id (``str``, *optional*): - Required if *chat_id* and *message_id* are not specified. - Identifier of the inline message. + media (:obj:`InputMedia`): + One of the InputMedia objects describing an animation, audio, document, photo or video. reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): An InlineKeyboardMarkup object. Returns: - :obj:`Message` | ``bool``: On success, if the edited message was sent by the bot, the edited message is - returned, otherwise True is returned (message sent via the bot, as inline query result). + :obj:`Message`: On success, the edited message is returned. Raises: RPCError: In case of a Telegram RPC error. @@ -242,16 +233,6 @@ class EditMessageMedia(BaseClient): else: media = utils.get_input_media_from_file_id(media.media, 5) - if inline_message_id is not None: - return self.send( - functions.messages.EditInlineBotMessage( - id=utils.unpack_inline_message_id(inline_message_id), - media=media, - reply_markup=reply_markup.write() if reply_markup else None, - **style.parse(caption) - ) - ) - r = self.send( functions.messages.EditMessage( peer=self.resolve_peer(chat_id), diff --git a/pyrogram/client/methods/messages/edit_message_reply_markup.py b/pyrogram/client/methods/messages/edit_message_reply_markup.py index 516d7b9c..51b77a6a 100644 --- a/pyrogram/client/methods/messages/edit_message_reply_markup.py +++ b/pyrogram/client/methods/messages/edit_message_reply_markup.py @@ -20,52 +20,36 @@ from typing import Union import pyrogram from pyrogram.api import functions, types -from pyrogram.client.ext import BaseClient, utils +from pyrogram.client.ext import BaseClient class EditMessageReplyMarkup(BaseClient): def edit_message_reply_markup( self, + chat_id: Union[int, str], + message_id: int, reply_markup: "pyrogram.InlineKeyboardMarkup" = None, - chat_id: Union[int, str] = None, - message_id: int = None, - inline_message_id: str = None ) -> "pyrogram.Message": - """Edit only the reply markup of messages sent by the bot or via the bot (for inline bots). + """Edit only the reply markup of messages sent by the bot. Parameters: - reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): - An InlineKeyboardMarkup object. - - chat_id (``int`` | ``str``, *optional*): - Required if *inline_message_id* is not specified. + 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). - message_id (``int``, *optional*): - Required if *inline_message_id* is not specified. + message_id (``int``): Message identifier in the chat specified in chat_id. - inline_message_id (``str``, *optional*): - Required if *chat_id* and *message_id* are not specified. - Identifier of the inline message. + reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + An InlineKeyboardMarkup object. Returns: - :obj:`Message` | ``bool``: On success, if the edited message was sent by the bot, the edited message is - returned, otherwise True is returned (message sent via the bot, as inline query result). + :obj:`Message`: On success, the edited message is returned. Raises: RPCError: In case of a Telegram RPC error. """ - if inline_message_id is not None: - return self.send( - functions.messages.EditInlineBotMessage( - id=utils.unpack_inline_message_id(inline_message_id), - reply_markup=reply_markup.write() if reply_markup else None, - ) - ) - r = self.send( functions.messages.EditMessage( peer=self.resolve_peer(chat_id), diff --git a/pyrogram/client/methods/messages/edit_message_text.py b/pyrogram/client/methods/messages/edit_message_text.py index 919e5dc1..7e4345c6 100644 --- a/pyrogram/client/methods/messages/edit_message_text.py +++ b/pyrogram/client/methods/messages/edit_message_text.py @@ -20,39 +20,32 @@ from typing import Union import pyrogram from pyrogram.api import functions, types -from pyrogram.client.ext import BaseClient, utils +from pyrogram.client.ext import BaseClient class EditMessageText(BaseClient): def edit_message_text( self, + chat_id: Union[int, str], + message_id: int, text: str, - chat_id: Union[int, str] = None, - message_id: int = None, - inline_message_id: str = None, parse_mode: str = "", disable_web_page_preview: bool = None, reply_markup: "pyrogram.InlineKeyboardMarkup" = None ) -> "pyrogram.Message": - """Edit text messages. + """Edit the text of messages. Parameters: - text (``str``): - New text of the message. - - chat_id (``int`` | ``str``, *optional*): - Required if *inline_message_id* is not specified. + 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). - message_id (``int``, *optional*): - Required if *inline_message_id* is not specified. + message_id (``int``): Message identifier in the chat specified in chat_id. - inline_message_id (``str``, *optional*): - Required if *chat_id* and *message_id* are not specified. - Identifier of the inline message. + text (``str``): + New text of the message. parse_mode (``str``, *optional*): Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline @@ -65,24 +58,13 @@ class EditMessageText(BaseClient): An InlineKeyboardMarkup object. Returns: - :obj:`Message` | ``bool``: On success, if the edited message was sent by the bot, the edited message is - returned, otherwise True is returned (message sent via the bot, as inline query result). + :obj:`Message`: On success, the edited message is returned. Raises: RPCError: In case of a Telegram RPC error. """ style = self.html if parse_mode.lower() == "html" else self.markdown - if inline_message_id is not None: - return self.send( - functions.messages.EditInlineBotMessage( - id=utils.unpack_inline_message_id(inline_message_id), - no_webpage=disable_web_page_preview or None, - reply_markup=reply_markup.write() if reply_markup else None, - **style.parse(text) - ) - ) - r = self.send( functions.messages.EditMessage( peer=self.resolve_peer(chat_id), From ef8f3bd6e15b2d7da57d20939cf3bba03642edc5 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 14 Jun 2019 04:53:04 +0200 Subject: [PATCH 019/202] Revert: CallbackQuery edit_* bound methods renamed to edit_message_* --- docs/source/api/bound-methods.rst | 18 +-- .../bots_and_keyboards/callback_query.py | 140 ++++++++---------- 2 files changed, 67 insertions(+), 91 deletions(-) diff --git a/docs/source/api/bound-methods.rst b/docs/source/api/bound-methods.rst index 83b3dbbe..e6729da6 100644 --- a/docs/source/api/bound-methods.rst +++ b/docs/source/api/bound-methods.rst @@ -81,13 +81,13 @@ CallbackQuery ^^^^^^^^^^^^^ .. hlist:: - :columns: 3 + :columns: 4 - :meth:`~CallbackQuery.answer` - - :meth:`~CallbackQuery.edit_message_text` - - :meth:`~CallbackQuery.edit_message_caption` - - :meth:`~CallbackQuery.edit_message_media` - - :meth:`~CallbackQuery.edit_message_reply_markup` + - :meth:`~CallbackQuery.edit_text` + - :meth:`~CallbackQuery.edit_caption` + - :meth:`~CallbackQuery.edit_media` + - :meth:`~CallbackQuery.edit_reply_markup` InlineQuery ^^^^^^^^^^^ @@ -141,10 +141,10 @@ Details .. CallbackQuery .. automethod:: CallbackQuery.answer() -.. automethod:: CallbackQuery.edit_message_text() -.. automethod:: CallbackQuery.edit_message_caption() -.. automethod:: CallbackQuery.edit_message_media() -.. automethod:: CallbackQuery.edit_message_reply_markup() +.. automethod:: CallbackQuery.edit_text() +.. automethod:: CallbackQuery.edit_caption() +.. automethod:: CallbackQuery.edit_media() +.. automethod:: CallbackQuery.edit_reply_markup() .. InlineQuery .. automethod:: InlineQuery.answer() diff --git a/pyrogram/client/types/bots_and_keyboards/callback_query.py b/pyrogram/client/types/bots_and_keyboards/callback_query.py index 6872d65b..fcc90e57 100644 --- a/pyrogram/client/types/bots_and_keyboards/callback_query.py +++ b/pyrogram/client/types/bots_and_keyboards/callback_query.py @@ -173,14 +173,16 @@ class CallbackQuery(Object, Update): cache_time=cache_time ) - def edit_message_text( + def edit_text( self, text: str, parse_mode: str = "", disable_web_page_preview: bool = None, reply_markup: "pyrogram.InlineKeyboardMarkup" = None ) -> Union["pyrogram.Message", bool]: - """Bound method *edit_message_text* of :obj:`CallbackQuery`. + """Edit the text of messages attached to this callback query. + + Bound method *edit_message_text* of :obj:`CallbackQuery`. Parameters: text (``str``): @@ -203,34 +205,33 @@ class CallbackQuery(Object, Update): Raises: RPCError: In case of a Telegram RPC error. """ - chat_id = None - message_id = None - inline_message_id = None + if self.inline_message_id is None: + return self._client.edit_message_text( + chat_id=self.message.chat.id, + message_id=self.message.message_id, + text=text, + parse_mode=parse_mode, + disable_web_page_preview=disable_web_page_preview, + reply_markup=reply_markup + ) + else: + return self._client.edit_inline_text( + inline_message_id=self.inline_message_id, + text=text, + parse_mode=parse_mode, + disable_web_page_preview=disable_web_page_preview, + reply_markup=reply_markup + ) - if self.message is not None: - chat_id = self.message.chat.id - message_id = self.message.message_id - - if self.inline_message_id is not None: - inline_message_id = self.inline_message_id - - return self._client.edit_message_text( - text=text, - chat_id=chat_id, - message_id=message_id, - inline_message_id=inline_message_id, - parse_mode=parse_mode, - disable_web_page_preview=disable_web_page_preview, - reply_markup=reply_markup - ) - - def edit_message_caption( + def edit_caption( self, caption: str, parse_mode: str = "", reply_markup: "pyrogram.InlineKeyboardMarkup" = None ) -> Union["pyrogram.Message", bool]: - """Bound method *edit_message_caption* of :obj:`CallbackQuery`. + """Edit the caption of media messages attached to this callback query. + + Bound method *edit_message_caption* of :obj:`CallbackQuery`. Parameters: caption (``str``): @@ -250,32 +251,16 @@ class CallbackQuery(Object, Update): Raises: RPCError: In case of a Telegram RPC error. """ - chat_id = None - message_id = None - inline_message_id = None + return self.edit_text(caption, parse_mode, reply_markup) - if self.message is not None: - chat_id = self.message.chat.id - message_id = self.message.message_id - - if self.inline_message_id is not None: - inline_message_id = self.inline_message_id - - return self._client.edit_message_text( - text=caption, - chat_id=chat_id, - message_id=message_id, - inline_message_id=inline_message_id, - parse_mode=parse_mode, - reply_markup=reply_markup - ) - - def edit_message_media( + def edit_media( self, media: "pyrogram.InputMedia", reply_markup: "pyrogram.InlineKeyboardMarkup" = None ) -> Union["pyrogram.Message", bool]: - """Bound method *edit_message_media* of :obj:`CallbackQuery`. + """Edit animation, audio, document, photo or video messages attached to this callback query. + + Bound method *edit_message_media* of :obj:`CallbackQuery`. Parameters: media (:obj:`InputMedia`): @@ -291,30 +276,27 @@ class CallbackQuery(Object, Update): Raises: RPCError: In case of a Telegram RPC error. """ - chat_id = None - message_id = None - inline_message_id = None + if self.inline_message_id is None: + return self._client.edit_message_media( + chat_id=self.message.chat.id, + message_id=self.message.message_id, + media=media, + reply_markup=reply_markup + ) + else: + return self._client.edit_inline_media( + inline_message_id=self.inline_message_id, + media=media, + reply_markup=reply_markup + ) - if self.message is not None: - chat_id = self.message.chat.id - message_id = self.message.message_id - - if self.inline_message_id is not None: - inline_message_id = self.inline_message_id - - return self._client.edit_message_media( - media=media, - chat_id=chat_id, - message_id=message_id, - inline_message_id=inline_message_id, - reply_markup=reply_markup - ) - - def edit_message_reply_markup( + def edit_reply_markup( self, reply_markup: "pyrogram.InlineKeyboardMarkup" = None ) -> Union["pyrogram.Message", bool]: - """Bound method *edit_message_reply_markup* of :obj:`CallbackQuery`. + """Edit only the reply markup of messages attached to this callback query. + + Bound method *edit_message_reply_markup* of :obj:`CallbackQuery`. Parameters: reply_markup (:obj:`InlineKeyboardMarkup`): @@ -327,20 +309,14 @@ class CallbackQuery(Object, Update): Raises: RPCError: In case of a Telegram RPC error. """ - chat_id = None - message_id = None - inline_message_id = None - - if self.message is not None: - chat_id = self.message.chat.id - message_id = self.message.message_id - - if self.inline_message_id is not None: - inline_message_id = self.inline_message_id - - return self._client.edit_message_reply_markup( - reply_markup=reply_markup, - chat_id=chat_id, - message_id=message_id, - inline_message_id=inline_message_id - ) + if self.inline_message_id is None: + return self._client.edit_message_reply_markup( + chat_id=self.message.chat.id, + message_id=self.message.message_id, + reply_markup=reply_markup + ) + else: + return self._client.edit_inline_reply_markup( + inline_message_id=self.inline_message_id, + reply_markup=reply_markup + ) From a1aef9cf251dd3083cfdd2a9eae70b846eeaea78 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 15 Jun 2019 15:57:00 +0200 Subject: [PATCH 020/202] Hint about how to ask good questions --- .github/ISSUE_TEMPLATE/question.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md index 88d91ecd..05f342bc 100644 --- a/.github/ISSUE_TEMPLATE/question.md +++ b/.github/ISSUE_TEMPLATE/question.md @@ -10,6 +10,6 @@ labels: "question" # Important This place is for issues about Pyrogram, it's **not a forum**. -If you'd like to post a question, please move to https://stackoverflow.com or join the Telegram community at https://t.me/pyrogram. +If you'd like to post a question, please move to https://stackoverflow.com or join the Telegram community at https://t.me/pyrogram. Useful information on how to ask good questions can be found here: https://stackoverflow.com/help/how-to-ask. -Thanks. \ No newline at end of file +Thanks. From abc0e992cf43ace3b32c6a6e87b90c59d1a89de2 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 15 Jun 2019 17:59:28 +0200 Subject: [PATCH 021/202] Fix Sticker.set_name being treated as tuple/list-like when should in fact be a string Yes, that little comma messed things up (again) --- pyrogram/client/types/messages_and_media/sticker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/types/messages_and_media/sticker.py b/pyrogram/client/types/messages_and_media/sticker.py index 3c171543..78fdda38 100644 --- a/pyrogram/client/types/messages_and_media/sticker.py +++ b/pyrogram/client/types/messages_and_media/sticker.py @@ -94,7 +94,7 @@ class Sticker(Object): self.width = width self.height = height self.emoji = emoji - self.set_name = set_name, + self.set_name = set_name self.thumbs = thumbs # self.mask_position = mask_position From 80d8443be4c11dd44d89184022599891d4f078b4 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 15 Jun 2019 23:02:31 +0200 Subject: [PATCH 022/202] Fix script executions not working outside the current directory Fixes #41 --- pyrogram/client/client.py | 35 +--------------- pyrogram/client/ext/base_client.py | 8 +++- .../client/methods/messages/download_media.py | 42 ++++++++++++++++++- 3 files changed, 47 insertions(+), 38 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 1106a416..9001a37e 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -28,7 +28,6 @@ import tempfile import threading import time from configparser import ConfigParser -from datetime import datetime from hashlib import sha256, md5 from importlib import import_module from pathlib import Path @@ -842,39 +841,7 @@ class Client(Methods, BaseClient): final_file_path = "" try: - data, file_name, done, progress, progress_args, path = packet - - directory, file_name = os.path.split(file_name) - directory = directory or "downloads" - - media_type_str = Client.MEDIA_TYPE_ID[data.media_type] - - file_name = file_name or data.file_name - - if not file_name: - guessed_extension = self.guess_extension(data.mime_type) - - if data.media_type in (0, 1, 2, 14): - extension = ".jpg" - elif data.media_type == 3: - extension = guessed_extension or ".ogg" - elif data.media_type in (4, 10, 13): - extension = guessed_extension or ".mp4" - elif data.media_type == 5: - extension = guessed_extension or ".zip" - elif data.media_type == 8: - extension = guessed_extension or ".webp" - elif data.media_type == 9: - extension = guessed_extension or ".mp3" - else: - continue - - file_name = "{}_{}_{}{}".format( - media_type_str, - datetime.fromtimestamp(data.date or time.time()).strftime("%Y-%m-%d_%H-%M-%S"), - self.rnd_id(), - extension - ) + data, directory, file_name, done, progress, progress_args, path = packet temp_file_path = self.get_file( media_type=data.media_type, diff --git a/pyrogram/client/ext/base_client.py b/pyrogram/client/ext/base_client.py index c8d1beab..def290e6 100644 --- a/pyrogram/client/ext/base_client.py +++ b/pyrogram/client/ext/base_client.py @@ -19,6 +19,8 @@ import os import platform import re +import sys +from pathlib import Path from queue import Queue from threading import Lock @@ -45,6 +47,8 @@ class BaseClient: LANG_CODE = "en" + PARENT_DIR = Path(sys.argv[0]).parent + INVITE_LINK_RE = re.compile(r"^(?:https?://)?(?:www\.)?(?:t(?:elegram)?\.(?:org|me|dog)/joinchat/)([\w-]+)$") BOT_TOKEN_RE = re.compile(r"^\d+:[\w-]+$") DIALOGS_AT_ONCE = 100 @@ -52,8 +56,8 @@ class BaseClient: DOWNLOAD_WORKERS = 1 OFFLINE_SLEEP = 900 WORKERS = 4 - WORKDIR = "." - CONFIG_FILE = "./config.ini" + WORKDIR = PARENT_DIR + CONFIG_FILE = PARENT_DIR / "config.ini" MEDIA_TYPE_ID = { 0: "photo_thumbnail", diff --git a/pyrogram/client/methods/messages/download_media.py b/pyrogram/client/methods/messages/download_media.py index bd8de2d6..143349f7 100644 --- a/pyrogram/client/methods/messages/download_media.py +++ b/pyrogram/client/methods/messages/download_media.py @@ -17,7 +17,10 @@ # along with Pyrogram. If not, see . import binascii +import os import struct +import time +from datetime import datetime from threading import Event from typing import Union @@ -25,12 +28,14 @@ import pyrogram from pyrogram.client.ext import BaseClient, FileData, utils from pyrogram.errors import FileIdInvalid +DEFAULT_DOWNLOAD_DIR = "downloads/" + class DownloadMedia(BaseClient): def download_media( self, message: Union["pyrogram.Message", str], - file_name: str = "", + file_name: str = DEFAULT_DOWNLOAD_DIR, block: bool = True, progress: callable = None, progress_args: tuple = () @@ -169,7 +174,40 @@ class DownloadMedia(BaseClient): done = Event() path = [None] - self.download_queue.put((data, file_name, done, progress, progress_args, path)) + directory, file_name = os.path.split(file_name) + file_name = file_name or data.file_name or "" + + if not os.path.isabs(file_name): + directory = self.PARENT_DIR / (directory or DEFAULT_DOWNLOAD_DIR) + + media_type_str = self.MEDIA_TYPE_ID[data.media_type] + + if not file_name: + guessed_extension = self.guess_extension(data.mime_type) + + if data.media_type in (0, 1, 2, 14): + extension = ".jpg" + elif data.media_type == 3: + extension = guessed_extension or ".ogg" + elif data.media_type in (4, 10, 13): + extension = guessed_extension or ".mp4" + elif data.media_type == 5: + extension = guessed_extension or ".zip" + elif data.media_type == 8: + extension = guessed_extension or ".webp" + elif data.media_type == 9: + extension = guessed_extension or ".mp3" + else: + extension = ".unknown" + + file_name = "{}_{}_{}{}".format( + media_type_str, + datetime.fromtimestamp(data.date or time.time()).strftime("%Y-%m-%d_%H-%M-%S"), + self.rnd_id(), + extension + ) + + self.download_queue.put((data, directory, file_name, done, progress, progress_args, path)) if block: done.wait() From 682591ea8fdb3f33c2970690b0aafd67c8823c29 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 19 Jun 2019 16:01:23 +0200 Subject: [PATCH 023/202] Update Auth and Session to accommodate Storage Engines --- pyrogram/client/client.py | 24 +-- pyrogram/client/ext/base_client.py | 1 - pyrogram/client/session_storage/abstract.py | 139 ---------------- pyrogram/client/session_storage/json.py | 63 -------- pyrogram/client/session_storage/memory.py | 115 ------------- .../client/session_storage/sqlite/0001.sql | 24 --- .../client/session_storage/sqlite/__init__.py | 153 ------------------ pyrogram/client/session_storage/string.py | 46 ------ .../{session_storage => storage}/__init__.py | 8 +- pyrogram/client/style/html.py | 9 +- pyrogram/client/style/markdown.py | 9 +- pyrogram/session/auth.py | 10 +- pyrogram/session/session.py | 23 ++- 13 files changed, 36 insertions(+), 588 deletions(-) delete mode 100644 pyrogram/client/session_storage/abstract.py delete mode 100644 pyrogram/client/session_storage/json.py delete mode 100644 pyrogram/client/session_storage/memory.py delete mode 100644 pyrogram/client/session_storage/sqlite/0001.sql delete mode 100644 pyrogram/client/session_storage/sqlite/__init__.py delete mode 100644 pyrogram/client/session_storage/string.py rename pyrogram/client/{session_storage => storage}/__init__.py (78%) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 1aa436b5..885c3334 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -26,14 +26,13 @@ import shutil import tempfile import threading import time -import warnings from configparser import ConfigParser from hashlib import sha256, md5 from importlib import import_module from pathlib import Path from signal import signal, SIGINT, SIGTERM, SIGABRT from threading import Thread -from typing import Union, List, Type +from typing import Union, List from pyrogram.api import functions, types from pyrogram.api.core import TLObject @@ -205,24 +204,9 @@ class Client(Methods, BaseClient): no_updates: bool = None, takeout: bool = None ): + super().__init__() - if isinstance(session_name, str): - if session_name == ':memory:': - session_storage = MemorySessionStorage(self) - elif session_name.startswith(':'): - session_storage = StringSessionStorage(self, session_name) - else: - session_storage = SQLiteSessionStorage(self, session_name) - elif isinstance(session_name, SessionStorage): - session_storage = session_name - else: - raise RuntimeError('Wrong session_name passed, expected str or SessionConfig subclass') - - super().__init__(session_storage) - - super().__init__(session_storage) - - self.session_name = str(session_name) # TODO: build correct session name + self.session_name = session_name self.api_id = int(api_id) if api_id else None self.api_hash = api_hash self.app_version = app_version @@ -232,7 +216,7 @@ class Client(Methods, BaseClient): self.ipv6 = ipv6 # TODO: Make code consistent, use underscore for private/protected fields self._proxy = proxy - self.session_storage.test_mode = test_mode + self.test_mode = test_mode self.bot_token = bot_token self.phone_number = phone_number self.phone_code = phone_code diff --git a/pyrogram/client/ext/base_client.py b/pyrogram/client/ext/base_client.py index aaf87823..9276b0eb 100644 --- a/pyrogram/client/ext/base_client.py +++ b/pyrogram/client/ext/base_client.py @@ -27,7 +27,6 @@ from threading import Lock from pyrogram import __version__ from ..style import Markdown, HTML from ...session.internals import MsgId -from ..session_storage import SessionStorage class BaseClient: diff --git a/pyrogram/client/session_storage/abstract.py b/pyrogram/client/session_storage/abstract.py deleted file mode 100644 index 134d5c8c..00000000 --- a/pyrogram/client/session_storage/abstract.py +++ /dev/null @@ -1,139 +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 abc -from typing import Type, Union - -import pyrogram -from pyrogram.api import types - - -class SessionDoesNotExist(Exception): - pass - - -class SessionStorage(abc.ABC): - def __init__(self, client: 'pyrogram.client.BaseClient'): - self._client = client - - @abc.abstractmethod - def load(self): - ... - - @abc.abstractmethod - def save(self, sync=False): - ... - - @property - @abc.abstractmethod - def dc_id(self): - ... - - @dc_id.setter - @abc.abstractmethod - def dc_id(self, val): - ... - - @property - @abc.abstractmethod - def test_mode(self): - ... - - @test_mode.setter - @abc.abstractmethod - def test_mode(self, val): - ... - - @property - @abc.abstractmethod - def auth_key(self): - ... - - @auth_key.setter - @abc.abstractmethod - def auth_key(self, val): - ... - - @property - @abc.abstractmethod - def user_id(self): - ... - - @user_id.setter - @abc.abstractmethod - def user_id(self, val): - ... - - @property - @abc.abstractmethod - def date(self): - ... - - @date.setter - @abc.abstractmethod - def date(self, val): - ... - - @property - @abc.abstractmethod - def is_bot(self): - ... - - @is_bot.setter - @abc.abstractmethod - def is_bot(self, val): - ... - - @abc.abstractmethod - def clear_cache(self): - ... - - @abc.abstractmethod - def cache_peer(self, entity: Union[types.User, - types.Chat, types.ChatForbidden, - types.Channel, types.ChannelForbidden]): - ... - - @abc.abstractmethod - def get_peer_by_id(self, val: int): - ... - - @abc.abstractmethod - def get_peer_by_username(self, val: str): - ... - - @abc.abstractmethod - def get_peer_by_phone(self, val: str): - ... - - def get_peer(self, peer_id: Union[int, str]): - if isinstance(peer_id, int): - return self.get_peer_by_id(peer_id) - else: - peer_id = peer_id.lstrip('+@') - if peer_id.isdigit(): - return self.get_peer_by_phone(peer_id) - return self.get_peer_by_username(peer_id) - - @abc.abstractmethod - def peers_count(self): - ... - - @abc.abstractmethod - def contacts_count(self): - ... diff --git a/pyrogram/client/session_storage/json.py b/pyrogram/client/session_storage/json.py deleted file mode 100644 index 4a48d3c1..00000000 --- a/pyrogram/client/session_storage/json.py +++ /dev/null @@ -1,63 +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 base64 -import json -import logging -import os -import shutil - -import pyrogram -from ..ext import utils -from . import MemorySessionStorage, SessionDoesNotExist - - -log = logging.getLogger(__name__) - -EXTENSION = '.session' - - -class JsonSessionStorage(MemorySessionStorage): - def __init__(self, client: 'pyrogram.client.ext.BaseClient', session_name: str): - super(JsonSessionStorage, self).__init__(client) - self._session_name = session_name - - def _get_file_name(self, name: str): - if not name.endswith(EXTENSION): - name += EXTENSION - return os.path.join(self._client.workdir, name) - - def load(self): - file_path = self._get_file_name(self._session_name) - log.info('Loading JSON session from {}'.format(file_path)) - - try: - with open(file_path, encoding='utf-8') as f: - s = json.load(f) - except FileNotFoundError: - raise SessionDoesNotExist() - - self._dc_id = s["dc_id"] - self._test_mode = s["test_mode"] - self._auth_key = base64.b64decode("".join(s["auth_key"])) # join split key - self._user_id = s["user_id"] - self._date = s.get("date", 0) - self._is_bot = s.get('is_bot', self._is_bot) - - def save(self, sync=False): - pass diff --git a/pyrogram/client/session_storage/memory.py b/pyrogram/client/session_storage/memory.py deleted file mode 100644 index c0610e70..00000000 --- a/pyrogram/client/session_storage/memory.py +++ /dev/null @@ -1,115 +0,0 @@ -import pyrogram -from pyrogram.api import types -from . import SessionStorage, SessionDoesNotExist - - -class MemorySessionStorage(SessionStorage): - def __init__(self, client: 'pyrogram.client.ext.BaseClient'): - super(MemorySessionStorage, self).__init__(client) - self._dc_id = 1 - self._test_mode = None - self._auth_key = None - self._user_id = None - self._date = 0 - self._is_bot = False - self._peers_cache = {} - - def load(self): - raise SessionDoesNotExist() - - def save(self, sync=False): - pass - - @property - def dc_id(self): - return self._dc_id - - @dc_id.setter - def dc_id(self, val): - self._dc_id = val - - @property - def test_mode(self): - return self._test_mode - - @test_mode.setter - def test_mode(self, val): - self._test_mode = val - - @property - def auth_key(self): - return self._auth_key - - @auth_key.setter - def auth_key(self, val): - self._auth_key = val - - @property - def user_id(self): - return self._user_id - - @user_id.setter - def user_id(self, val): - self._user_id = val - - @property - def date(self): - return self._date - - @date.setter - def date(self, val): - self._date = val - - @property - def is_bot(self): - return self._is_bot - - @is_bot.setter - def is_bot(self, val): - self._is_bot = val - - def clear_cache(self): - keys = list(filter(lambda k: k[0] in 'up', self._peers_cache.keys())) - for key in keys: - try: - del self._peers_cache[key] - except KeyError: - pass - - def cache_peer(self, entity): - if isinstance(entity, types.User): - input_peer = types.InputPeerUser( - user_id=entity.id, - access_hash=entity.access_hash - ) - self._peers_cache['i' + str(entity.id)] = input_peer - if entity.username: - self._peers_cache['u' + entity.username.lower()] = input_peer - if entity.phone: - self._peers_cache['p' + entity.phone] = input_peer - elif isinstance(entity, (types.Chat, types.ChatForbidden)): - self._peers_cache['i-' + str(entity.id)] = types.InputPeerChat(chat_id=entity.id) - elif isinstance(entity, (types.Channel, types.ChannelForbidden)): - input_peer = types.InputPeerChannel( - channel_id=entity.id, - access_hash=entity.access_hash - ) - self._peers_cache['i-100' + str(entity.id)] = input_peer - username = getattr(entity, "username", None) - if username: - self._peers_cache['u' + username.lower()] = input_peer - - def get_peer_by_id(self, val): - return self._peers_cache['i' + str(val)] - - def get_peer_by_username(self, val): - return self._peers_cache['u' + val.lower()] - - def get_peer_by_phone(self, val): - return self._peers_cache['p' + val] - - def peers_count(self): - return len(list(filter(lambda k: k[0] == 'i', self._peers_cache.keys()))) - - def contacts_count(self): - return len(list(filter(lambda k: k[0] == 'p', self._peers_cache.keys()))) diff --git a/pyrogram/client/session_storage/sqlite/0001.sql b/pyrogram/client/session_storage/sqlite/0001.sql deleted file mode 100644 index c6c51d24..00000000 --- a/pyrogram/client/session_storage/sqlite/0001.sql +++ /dev/null @@ -1,24 +0,0 @@ -create table sessions ( - dc_id integer primary key, - test_mode integer, - auth_key blob, - user_id integer, - date integer, - is_bot integer -); - -create table peers_cache ( - id integer primary key, - hash integer, - username text, - phone integer -); - -create table migrations ( - name text primary key -); - -create index username_idx on peers_cache(username); -create index phone_idx on peers_cache(phone); - -insert into migrations (name) values ('0001'); diff --git a/pyrogram/client/session_storage/sqlite/__init__.py b/pyrogram/client/session_storage/sqlite/__init__.py deleted file mode 100644 index a16e75e8..00000000 --- a/pyrogram/client/session_storage/sqlite/__init__.py +++ /dev/null @@ -1,153 +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 logging -import os -import shutil -import sqlite3 -from threading import Lock - -import pyrogram -from ....api import types -from ...ext import utils -from .. import MemorySessionStorage, SessionDoesNotExist, JsonSessionStorage - - -log = logging.getLogger(__name__) - -EXTENSION = '.session' -MIGRATIONS = ['0001'] - - -class SQLiteSessionStorage(MemorySessionStorage): - def __init__(self, client: 'pyrogram.client.ext.BaseClient', session_name: str): - super(SQLiteSessionStorage, self).__init__(client) - self._session_name = session_name - self._conn = None # type: sqlite3.Connection - self._lock = Lock() - - def _get_file_name(self, name: str): - if not name.endswith(EXTENSION): - name += EXTENSION - return os.path.join(self._client.workdir, name) - - def _apply_migrations(self, new_db=False): - self._conn.execute('PRAGMA read_uncommitted = true') - migrations = MIGRATIONS.copy() - if not new_db: - cursor = self._conn.cursor() - cursor.execute('select name from migrations') - for row in cursor.fetchone(): - migrations.remove(row) - for name in migrations: - with open(os.path.join(os.path.dirname(__file__), '{}.sql'.format(name))) as script: - self._conn.executescript(script.read()) - - def _migrate_from_json(self): - jss = JsonSessionStorage(self._client, self._session_name) - jss.load() - file_path = self._get_file_name(self._session_name) - self._conn = sqlite3.connect(file_path + '.tmp') - self._apply_migrations(new_db=True) - self._dc_id, self._test_mode, self._auth_key, self._user_id, self._date, self._is_bot = \ - jss.dc_id, jss.test_mode, jss.auth_key, jss.user_id, jss.date, jss.is_bot - self.save() - self._conn.close() - shutil.move(file_path + '.tmp', file_path) - log.warning('Session was migrated from JSON, loading...') - self.load() - - def load(self): - file_path = self._get_file_name(self._session_name) - log.info('Loading SQLite session from {}'.format(file_path)) - - if os.path.isfile(file_path): - try: - self._conn = sqlite3.connect(file_path, isolation_level='EXCLUSIVE', check_same_thread=False) - self._apply_migrations() - except sqlite3.DatabaseError: - log.warning('Trying to migrate session from JSON...') - self._migrate_from_json() - return - else: - self._conn = sqlite3.connect(file_path, isolation_level='EXCLUSIVE', check_same_thread=False) - self._apply_migrations(new_db=True) - - cursor = self._conn.cursor() - cursor.execute('select dc_id, test_mode, auth_key, user_id, "date", is_bot from sessions') - row = cursor.fetchone() - if not row: - raise SessionDoesNotExist() - - self._dc_id = row[0] - self._test_mode = bool(row[1]) - self._auth_key = row[2] - self._user_id = row[3] - self._date = row[4] - self._is_bot = bool(row[5]) - - def cache_peer(self, entity): - peer_id = username = phone = access_hash = None - - if isinstance(entity, types.User): - peer_id = entity.id - username = entity.username.lower() if entity.username else None - phone = entity.phone or None - access_hash = entity.access_hash - elif isinstance(entity, (types.Chat, types.ChatForbidden)): - peer_id = -entity.id - elif isinstance(entity, (types.Channel, types.ChannelForbidden)): - peer_id = int('-100' + str(entity.id)) - username = entity.username.lower() if hasattr(entity, 'username') and entity.username else None - access_hash = entity.access_hash - - with self._lock: - self._conn.execute('insert or replace into peers_cache values (?, ?, ?, ?)', - (peer_id, access_hash, username, phone)) - - def get_peer_by_id(self, val): - cursor = self._conn.cursor() - cursor.execute('select id, hash from peers_cache where id = ?', (val,)) - row = cursor.fetchone() - if not row: - raise KeyError(val) - return utils.get_input_peer(row[0], row[1]) - - def get_peer_by_username(self, val): - cursor = self._conn.cursor() - cursor.execute('select id, hash from peers_cache where username = ?', (val,)) - row = cursor.fetchone() - if not row: - raise KeyError(val) - return utils.get_input_peer(row[0], row[1]) - - def get_peer_by_phone(self, val): - cursor = self._conn.cursor() - cursor.execute('select id, hash from peers_cache where phone = ?', (val,)) - row = cursor.fetchone() - if not row: - raise KeyError(val) - return utils.get_input_peer(row[0], row[1]) - - def save(self, sync=False): - log.info('Committing SQLite session') - with self._lock: - self._conn.execute('delete from sessions') - self._conn.execute('insert into sessions values (?, ?, ?, ?, ?, ?)', - (self._dc_id, self._test_mode, self._auth_key, self._user_id, self._date, self._is_bot)) - self._conn.commit() diff --git a/pyrogram/client/session_storage/string.py b/pyrogram/client/session_storage/string.py deleted file mode 100644 index 11051323..00000000 --- a/pyrogram/client/session_storage/string.py +++ /dev/null @@ -1,46 +0,0 @@ -import base64 -import binascii -import struct - -import pyrogram -from . import MemorySessionStorage, SessionDoesNotExist - - -class StringSessionStorage(MemorySessionStorage): - """ - Packs session data as following (forcing little-endian byte order): - Char dc_id (1 byte, unsigned) - Boolean test_mode (1 byte) - Long long user_id (8 bytes, signed) - Boolean is_bot (1 byte) - Bytes auth_key (256 bytes) - - Uses Base64 encoding for printable representation - """ - PACK_FORMAT = '. -from .abstract import SessionStorage, SessionDoesNotExist -from .memory import MemorySessionStorage -from .json import JsonSessionStorage -from .string import StringSessionStorage -from .sqlite import SQLiteSessionStorage +from .memory_storage import MemoryStorage +from .file_storage import FileStorage +from .storage import Storage diff --git a/pyrogram/client/style/html.py b/pyrogram/client/style/html.py index 894dbd6c..9c0a372c 100644 --- a/pyrogram/client/style/html.py +++ b/pyrogram/client/style/html.py @@ -31,16 +31,14 @@ from pyrogram.api.types import ( ) from pyrogram.errors import PeerIdInvalid from . import utils -from ..session_storage import SessionStorage class HTML: HTML_RE = re.compile(r"<(\w+)(?: href=([\"'])([^<]+)\2)?>([^>]+)") MENTION_RE = re.compile(r"tg://user\?id=(\d+)") - def __init__(self, session_storage: SessionStorage, client: "pyrogram.BaseClient" = None): + def __init__(self, client: "pyrogram.BaseClient" = None): self.client = client - self.session_storage = session_storage def parse(self, message: str): entities = [] @@ -56,9 +54,10 @@ class HTML: if mention: user_id = int(mention.group(1)) + try: - input_user = self.session_storage.get_peer_by_id(user_id) - except KeyError: + input_user = self.client.resolve_peer(user_id) + except PeerIdInvalid: input_user = None entity = ( diff --git a/pyrogram/client/style/markdown.py b/pyrogram/client/style/markdown.py index 68b54bbb..adb86e94 100644 --- a/pyrogram/client/style/markdown.py +++ b/pyrogram/client/style/markdown.py @@ -31,7 +31,6 @@ from pyrogram.api.types import ( ) from pyrogram.errors import PeerIdInvalid from . import utils -from ..session_storage import SessionStorage class Markdown: @@ -55,9 +54,8 @@ class Markdown: )) MENTION_RE = re.compile(r"tg://user\?id=(\d+)") - def __init__(self, session_storage: SessionStorage, client: "pyrogram.BaseClient" = None): + def __init__(self, client: "pyrogram.BaseClient" = None): self.client = client - self.session_storage = session_storage def parse(self, message: str): message = utils.add_surrogates(str(message or "")).strip() @@ -73,9 +71,10 @@ class Markdown: if mention: user_id = int(mention.group(1)) + try: - input_user = self.session_storage.get_peer_by_id(user_id) - except KeyError: + input_user = self.client.resolve_peer(user_id) + except PeerIdInvalid: input_user = None entity = ( diff --git a/pyrogram/session/auth.py b/pyrogram/session/auth.py index fb6e7ca3..b05b2855 100644 --- a/pyrogram/session/auth.py +++ b/pyrogram/session/auth.py @@ -22,10 +22,12 @@ from hashlib import sha1 from io import BytesIO from os import urandom +import pyrogram 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__) @@ -34,11 +36,11 @@ log = logging.getLogger(__name__) class Auth: MAX_RETRIES = 5 - def __init__(self, dc_id: int, test_mode: bool, ipv6: bool, proxy: dict): + def __init__(self, client: "pyrogram.Client", dc_id: int): self.dc_id = dc_id - self.test_mode = test_mode - self.ipv6 = ipv6 - self.proxy = proxy + self.test_mode = client.storage.test_mode + self.ipv6 = client.ipv6 + self.proxy = client.proxy self.connection = None diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index bd7f0f26..5947fc0f 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -34,6 +34,7 @@ from pyrogram.api.core import Message, TLObject, MsgContainer, Long, FutureSalt, 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__) @@ -70,12 +71,14 @@ class Session: 64: "[64] invalid container" } - def __init__(self, - client: pyrogram, - dc_id: int, - auth_key: bytes, - is_media: bool = False, - is_cdn: bool = False): + def __init__( + self, + client: pyrogram, + dc_id: int, + auth_key: bytes, + is_media: bool = False, + is_cdn: bool = False + ): if not Session.notice_displayed: print("Pyrogram v{}, {}".format(__version__, __copyright__)) print("Licensed under the terms of the " + __license__, end="\n\n") @@ -113,8 +116,12 @@ class Session: def start(self): while True: - self.connection = Connection(self.dc_id, self.client.session_storage.test_mode, - self.client.ipv6, self.client.proxy) + self.connection = Connection( + self.dc_id, + self.client.storage.test_mode, + self.client.ipv6, + self.client.proxy + ) try: self.connection.connect() From 6177abbfa4d09b63eee17907a3d2462ac2e14a32 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 19 Jun 2019 16:04:06 +0200 Subject: [PATCH 024/202] Add Storage abstract class --- pyrogram/client/storage/storage.py | 98 ++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 pyrogram/client/storage/storage.py diff --git a/pyrogram/client/storage/storage.py b/pyrogram/client/storage/storage.py new file mode 100644 index 00000000..e0810645 --- /dev/null +++ b/pyrogram/client/storage/storage.py @@ -0,0 +1,98 @@ +# 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 . + + +class Storage: + def __init__(self, name: str): + self.name = name + + def open(self): + raise NotImplementedError + + def save(self): + raise NotImplementedError + + def close(self): + raise NotImplementedError + + def update_peers(self, peers): + raise NotImplementedError + + def get_peer_by_id(self, peer_id): + raise NotImplementedError + + def get_peer_by_username(self, username): + raise NotImplementedError + + def get_peer_by_phone_number(self, phone_number): + raise NotImplementedError + + def export_session_string(self): + raise NotImplementedError + + @property + def peers_count(self): + raise NotImplementedError + + @property + def dc_id(self): + raise NotImplementedError + + @dc_id.setter + def dc_id(self, value): + raise NotImplementedError + + @property + def test_mode(self): + raise NotImplementedError + + @test_mode.setter + def test_mode(self, value): + raise NotImplementedError + + @property + def auth_key(self): + raise NotImplementedError + + @auth_key.setter + def auth_key(self, value): + raise NotImplementedError + + @property + def date(self): + raise NotImplementedError + + @date.setter + def date(self, value): + raise NotImplementedError + + @property + def user_id(self): + raise NotImplementedError + + @user_id.setter + def user_id(self, value): + raise NotImplementedError + + @property + def is_bot(self): + raise NotImplementedError + + @is_bot.setter + def is_bot(self, value): + raise NotImplementedError From 6cc9688e4921a15c82667264eea624844cb48be9 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 19 Jun 2019 16:04:35 +0200 Subject: [PATCH 025/202] Implement FileStorage and MemoryStorage engines --- pyrogram/client/storage/file_storage.py | 102 +++++++++ pyrogram/client/storage/memory_storage.py | 241 ++++++++++++++++++++++ pyrogram/client/storage/schema.sql | 34 +++ 3 files changed, 377 insertions(+) create mode 100644 pyrogram/client/storage/file_storage.py create mode 100644 pyrogram/client/storage/memory_storage.py create mode 100644 pyrogram/client/storage/schema.sql diff --git a/pyrogram/client/storage/file_storage.py b/pyrogram/client/storage/file_storage.py new file mode 100644 index 00000000..ee5000c5 --- /dev/null +++ b/pyrogram/client/storage/file_storage.py @@ -0,0 +1,102 @@ +# 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 base64 +import json +import logging +import os +import sqlite3 +from pathlib import Path +from sqlite3 import DatabaseError +from threading import Lock +from typing import Union + +from .memory_storage import MemoryStorage + +log = logging.getLogger(__name__) + + +class FileStorage(MemoryStorage): + FILE_EXTENSION = ".session" + + def __init__(self, name: str, workdir: Path): + super().__init__(name) + + self.workdir = workdir + self.database = workdir / (self.name + self.FILE_EXTENSION) + self.conn = None # type: sqlite3.Connection + self.lock = Lock() + + # noinspection PyAttributeOutsideInit + def migrate_from_json(self, path: Union[str, Path]): + log.warning("JSON session storage detected! Pyrogram will now convert it into an SQLite session storage...") + + with open(path, encoding="utf-8") as f: + json_session = json.load(f) + + os.remove(path) + + self.open() + + self.dc_id = json_session["dc_id"] + self.test_mode = json_session["test_mode"] + self.auth_key = base64.b64decode("".join(json_session["auth_key"])) + self.user_id = json_session["user_id"] + self.date = json_session.get("date", 0) + self.is_bot = json_session.get("is_bot", False) + + peers_by_id = json_session.get("peers_by_id", {}) + peers_by_phone = json_session.get("peers_by_phone", {}) + + peers = {} + + for k, v in peers_by_id.items(): + if v is None: + type_ = "group" + elif k.startswith("-100"): + type_ = "channel" + else: + type_ = "user" + + peers[int(k)] = [int(k), int(v) if v is not None else None, type_, None, None] + + for k, v in peers_by_phone.items(): + peers[v][4] = k + + # noinspection PyTypeChecker + self.update_peers(peers.values()) + + log.warning("Done! The session has been successfully converted from JSON to SQLite storage") + + def open(self): + database_exists = os.path.isfile(self.database) + + self.conn = sqlite3.connect( + str(self.database), + timeout=1, + check_same_thread=False + ) + + try: + if not database_exists: + self.create() + + with self.conn: + self.conn.execute("VACUUM") + except DatabaseError: + self.migrate_from_json(self.database) diff --git a/pyrogram/client/storage/memory_storage.py b/pyrogram/client/storage/memory_storage.py new file mode 100644 index 00000000..7eb3a7d0 --- /dev/null +++ b/pyrogram/client/storage/memory_storage.py @@ -0,0 +1,241 @@ +# 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 base64 +import inspect +import logging +import sqlite3 +import struct +import time +from pathlib import Path +from threading import Lock +from typing import List, Tuple + +from pyrogram.api import types +from pyrogram.client.storage.storage import Storage + +log = logging.getLogger(__name__) + + +class MemoryStorage(Storage): + SCHEMA_VERSION = 1 + USERNAME_TTL = 8 * 60 * 60 + SESSION_STRING_FMT = ">B?256sI?" + SESSION_STRING_SIZE = 351 + + def __init__(self, name: str): + super().__init__(name) + + self.conn = None # type: sqlite3.Connection + self.lock = Lock() + + def create(self): + with self.lock, self.conn: + with open(Path(__file__).parent / "schema.sql", "r") as schema: + self.conn.executescript(schema.read()) + + self.conn.execute( + "INSERT INTO version VALUES (?)", + (self.SCHEMA_VERSION,) + ) + + self.conn.execute( + "INSERT INTO sessions VALUES (?, ?, ?, ?, ?, ?)", + (1, None, None, 0, None, None) + ) + + def _import_session_string(self, string_session: str): + decoded = base64.urlsafe_b64decode(string_session + "=" * (-len(string_session) % 4)) + return struct.unpack(self.SESSION_STRING_FMT, decoded) + + def export_session_string(self): + packed = struct.pack( + self.SESSION_STRING_FMT, + self.dc_id, + self.test_mode, + self.auth_key, + self.user_id, + self.is_bot + ) + + return base64.urlsafe_b64encode(packed).decode().rstrip("=") + + # noinspection PyAttributeOutsideInit + def open(self): + self.conn = sqlite3.connect(":memory:", check_same_thread=False) + self.create() + + if self.name != ":memory:": + imported_session_string = self._import_session_string(self.name) + + self.dc_id, self.test_mode, self.auth_key, self.user_id, self.is_bot = imported_session_string + self.date = 0 + + self.name = ":memory:" + str(self.user_id or "") + + # noinspection PyAttributeOutsideInit + def save(self): + self.date = int(time.time()) + + with self.lock: + self.conn.commit() + + def close(self): + with self.lock: + self.conn.close() + + def update_peers(self, peers: List[Tuple[int, int, str, str, str]]): + with self.lock: + self.conn.executemany( + "REPLACE INTO peers (id, access_hash, type, username, phone_number)" + "VALUES (?, ?, ?, ?, ?)", + peers + ) + + def clear_peers(self): + with self.lock, self.conn: + self.conn.execute( + "DELETE FROM peers" + ) + + @staticmethod + def _get_input_peer(peer_id: int, access_hash: int, peer_type: str): + if peer_type in ["user", "bot"]: + return types.InputPeerUser( + user_id=peer_id, + access_hash=access_hash + ) + + if peer_type == "group": + return types.InputPeerChat( + chat_id=-peer_id + ) + + if peer_type in ["channel", "supergroup"]: + return types.InputPeerChannel( + channel_id=int(str(peer_id)[4:]), + access_hash=access_hash + ) + + raise ValueError("Invalid peer type") + + def get_peer_by_id(self, peer_id: int): + r = self.conn.execute( + "SELECT id, access_hash, type FROM peers WHERE id = ?", + (peer_id,) + ).fetchone() + + if r is None: + raise KeyError("ID not found") + + return self._get_input_peer(*r) + + def get_peer_by_username(self, username: str): + r = self.conn.execute( + "SELECT id, access_hash, type, last_update_on FROM peers WHERE username = ?", + (username,) + ).fetchone() + + if r is None: + raise KeyError("Username not found") + + if abs(time.time() - r[3]) > self.USERNAME_TTL: + raise KeyError("Username expired") + + return self._get_input_peer(*r[:3]) + + def get_peer_by_phone_number(self, phone_number: str): + r = self.conn.execute( + "SELECT id, access_hash, type FROM peers WHERE phone_number = ?", + (phone_number,) + ).fetchone() + + if r is None: + raise KeyError("Phone number not found") + + return self._get_input_peer(*r) + + @property + def peers_count(self): + return self.conn.execute( + "SELECT COUNT(*) FROM peers" + ).fetchone()[0] + + def _get(self): + attr = inspect.stack()[1].function + + return self.conn.execute( + "SELECT {} FROM sessions".format(attr) + ).fetchone()[0] + + def _set(self, value): + attr = inspect.stack()[1].function + + with self.lock, self.conn: + self.conn.execute( + "UPDATE sessions SET {} = ?".format(attr), + (value,) + ) + + @property + def dc_id(self): + return self._get() + + @dc_id.setter + def dc_id(self, value): + self._set(value) + + @property + def test_mode(self): + return self._get() + + @test_mode.setter + def test_mode(self, value): + self._set(value) + + @property + def auth_key(self): + return self._get() + + @auth_key.setter + def auth_key(self, value): + self._set(value) + + @property + def date(self): + return self._get() + + @date.setter + def date(self, value): + self._set(value) + + @property + def user_id(self): + return self._get() + + @user_id.setter + def user_id(self, value): + self._set(value) + + @property + def is_bot(self): + return self._get() + + @is_bot.setter + def is_bot(self, value): + self._set(value) diff --git a/pyrogram/client/storage/schema.sql b/pyrogram/client/storage/schema.sql new file mode 100644 index 00000000..1f5af6d2 --- /dev/null +++ b/pyrogram/client/storage/schema.sql @@ -0,0 +1,34 @@ +CREATE TABLE sessions ( + dc_id INTEGER PRIMARY KEY, + test_mode INTEGER, + auth_key BLOB, + date INTEGER NOT NULL, + user_id INTEGER, + is_bot INTEGER +); + +CREATE TABLE peers ( + id INTEGER PRIMARY KEY, + access_hash INTEGER, + type INTEGER NOT NULL, + username TEXT, + phone_number TEXT, + last_update_on INTEGER NOT NULL DEFAULT (CAST(STRFTIME('%s', 'now') AS INTEGER)) +); + +CREATE TABLE version ( + number INTEGER PRIMARY KEY +); + +CREATE INDEX idx_peers_id ON peers (id); +CREATE INDEX idx_peers_username ON peers (username); +CREATE INDEX idx_peers_phone_number ON peers (phone_number); + +CREATE TRIGGER trg_peers_last_update_on + AFTER UPDATE + ON peers + BEGIN + UPDATE peers + SET last_update_on = CAST(STRFTIME('%s', 'now') AS INTEGER) + WHERE id = NEW.id; + END; \ No newline at end of file From 8465c4a97798de7d09bab1048d4154c470d59278 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 19 Jun 2019 16:06:37 +0200 Subject: [PATCH 026/202] Instruct Python to add schema.sql file to the package --- MANIFEST.in | 2 +- setup.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 97d04588..79c547f6 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,7 +1,7 @@ ## Include include README.md COPYING COPYING.lesser NOTICE requirements.txt recursive-include compiler *.py *.tl *.tsv *.txt -recursive-include pyrogram mime.types +recursive-include pyrogram mime.types schema.sql ## Exclude prune pyrogram/api/errors/exceptions diff --git a/setup.py b/setup.py index 146dae9e..d4255e03 100644 --- a/setup.py +++ b/setup.py @@ -168,7 +168,8 @@ setup( python_requires="~=3.4", packages=find_packages(exclude=["compiler*"]), package_data={ - "pyrogram.client.ext": ["mime.types"] + "pyrogram.client.ext": ["mime.types"], + "pyrogram.client.storage": ["schema.sql"] }, zip_safe=False, install_requires=requires, From edaced35a758a84dff17328cb0095cfb525b5449 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 19 Jun 2019 16:07:22 +0200 Subject: [PATCH 027/202] Use base64.urlsafe_b64encode/decode instead of manually passing altchars --- pyrogram/client/ext/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyrogram/client/ext/utils.py b/pyrogram/client/ext/utils.py index fa107fab..e0a797e2 100644 --- a/pyrogram/client/ext/utils.py +++ b/pyrogram/client/ext/utils.py @@ -18,16 +18,16 @@ import base64 import struct -from base64 import b64decode, b64encode from typing import Union, List import pyrogram + from . import BaseClient from ...api import types def decode(s: str) -> bytes: - s = b64decode(s + "=" * (-len(s) % 4), "-_") + s = base64.urlsafe_b64decode(s + "=" * (-len(s) % 4)) r = b"" assert s[-1] == 2 @@ -59,7 +59,7 @@ def encode(s: bytes) -> str: r += bytes([i]) - return b64encode(r, b"-_").decode().rstrip("=") + return base64.urlsafe_b64encode(r).decode().rstrip("=") def get_peer_id(input_peer) -> int: From 30192de1ad493acd896448e727443d7b03e65761 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 19 Jun 2019 16:10:37 +0200 Subject: [PATCH 028/202] Update pyrogram/client to accommodate Storage Engines --- pyrogram/client/client.py | 231 ++++++++++-------- pyrogram/client/ext/base_client.py | 8 +- pyrogram/client/ext/syncer.py | 15 +- .../client/methods/contacts/get_contacts.py | 1 - 4 files changed, 141 insertions(+), 114 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 885c3334..2d18d178 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -16,7 +16,6 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import binascii import logging import math import mimetypes @@ -51,10 +50,7 @@ from pyrogram.errors import ( from pyrogram.session import Auth, Session from .ext import utils, Syncer, BaseClient, Dispatcher from .methods import Methods -from .session_storage import ( - SessionDoesNotExist, SessionStorage, MemorySessionStorage, JsonSessionStorage, - StringSessionStorage, SQLiteSessionStorage -) +from .storage import Storage, FileStorage, MemoryStorage log = logging.getLogger(__name__) @@ -64,8 +60,13 @@ class Client(Methods, BaseClient): Parameters: session_name (``str``): - Name to uniquely identify a session of either a User or a Bot, e.g.: "my_account". This name will be used - to save a file to disk that stores details needed for reconnecting without asking again for credentials. + Pass a string of your choice to give a name to the client session, e.g.: "*my_account*". This name will be + used to save a file on disk that stores details needed to reconnect without asking again for credentials. + Alternatively, if you don't want a file to be saved on disk, pass the special name "**:memory:**" to start + an in-memory session that will be discarded as soon as you stop the Client. In order to reconnect again + using a memory storage without having to login again, you can use + :meth:`~pyrogram.Client.export_session_string` before stopping the client to get a session string you can + pass here as argument. api_id (``int``, *optional*): The *api_id* part of your Telegram API Key, as integer. E.g.: 12345 @@ -179,7 +180,7 @@ class Client(Methods, BaseClient): def __init__( self, - session_name: str, + session_name: Union[str, Storage], api_id: Union[int, str] = None, api_hash: str = None, app_version: str = None, @@ -226,12 +227,23 @@ class Client(Methods, BaseClient): self.first_name = first_name self.last_name = last_name self.workers = workers - self.workdir = workdir - self.config_file = config_file + self.workdir = Path(workdir) + self.config_file = Path(config_file) self.plugins = plugins self.no_updates = no_updates self.takeout = takeout + if isinstance(session_name, str): + if session_name == ":memory:" or len(session_name) >= MemoryStorage.SESSION_STRING_SIZE: + session_name = re.sub(r"[\n\s]+", "", session_name) + self.storage = MemoryStorage(session_name) + else: + self.storage = FileStorage(session_name, self.workdir) + elif isinstance(session_name, Storage): + self.storage = session_name + else: + raise ValueError("Unknown storage engine") + self.dispatcher = Dispatcher(self, workers) def __enter__(self): @@ -266,50 +278,32 @@ class Client(Methods, BaseClient): if self.is_started: raise ConnectionError("Client has already been started") - if isinstance(self.session_storage, JsonSessionStorage): - if self.BOT_TOKEN_RE.match(self.session_storage._session_name): - self.session_storage.is_bot = True - self.bot_token = self.session_storage._session_name - self.session_storage._session_name = self.session_storage._session_name.split(":")[0] - warnings.warn('\nWARNING: You are using a bot token as session name!\n' - 'This usage will be deprecated soon. Please use a session file name to load ' - 'an existing session and the bot_token argument to create new sessions.\n' - 'More info: https://docs.pyrogram.org/intro/auth#bot-authorization\n') - self.load_config() self.load_session() self.load_plugins() - self.session = Session( - self, - self.session_storage.dc_id, - self.session_storage.auth_key - ) + self.session = Session(self, self.storage.dc_id, self.storage.auth_key) self.session.start() self.is_started = True try: - if self.session_storage.user_id is None: + if self.storage.user_id is None: if self.bot_token is None: - self.is_bot = False + self.storage.is_bot = False self.authorize_user() else: - self.session_storage.is_bot = True + self.storage.is_bot = True self.authorize_bot() - self.save_session() - - if not self.session_storage.is_bot: + if not self.storage.is_bot: if self.takeout: self.takeout_id = self.send(functions.account.InitTakeoutSession()).id log.warning("Takeout session {} initiated".format(self.takeout_id)) now = time.time() - if abs(now - self.session_storage.date) > Client.OFFLINE_SLEEP: - self.session_storage.clear_cache() - + if abs(now - self.storage.date) > Client.OFFLINE_SLEEP: self.get_initial_dialogs() self.get_contacts() else: @@ -508,20 +502,15 @@ class Client(Methods, BaseClient): except UserMigrate as e: self.session.stop() - self.session_storage.dc_id = e.x - self.session_storage.auth_key = Auth(self.session_storage.dc_id, self.session_storage.test_mode, - self.ipv6, self._proxy).create() - - self.session = Session( - self, - self.session_storage.dc_id, - self.session_storage.auth_key - ) + self.storage.dc_id = e.x + self.storage.auth_key = Auth(self, self.storage.dc_id).create() + self.session = Session(self, self.storage.dc_id, self.storage.auth_key) self.session.start() + self.authorize_bot() else: - self.session_storage.user_id = r.user.id + self.storage.user_id = r.user.id print("Logged in successfully as @{}".format(r.user.username)) @@ -562,20 +551,10 @@ class Client(Methods, BaseClient): except (PhoneMigrate, NetworkMigrate) as e: self.session.stop() - self.session_storage.dc_id = e.x + self.storage.dc_id = e.x + self.storage.auth_key = Auth(self, self.storage.dc_id).create() - self.session_storage.auth_key = Auth( - self.session_storage.dc_id, - self.session_storage.test_mode, - self.ipv6, - self._proxy - ).create() - - self.session = Session( - self, - self.session_storage.dc_id, - self.session_storage.auth_key - ) + self.session = Session(self, self.storage.dc_id, self.storage.auth_key) self.session.start() except (PhoneNumberInvalid, PhoneNumberBanned) as e: @@ -755,13 +734,13 @@ class Client(Methods, BaseClient): ) self.password = None - self.session_storage.user_id = r.user.id + self.storage.user_id = r.user.id print("Logged in successfully as {}".format(r.user.first_name)) def fetch_peers( self, - entities: List[ + peers: List[ Union[ types.User, types.Chat, types.ChatForbidden, @@ -770,11 +749,57 @@ class Client(Methods, BaseClient): ] ) -> bool: is_min = False + parsed_peers = [] - for entity in entities: - if isinstance(entity, (types.User, types.Channel, types.ChannelForbidden)) and not entity.access_hash: + for peer in peers: + username = None + phone_number = None + + if isinstance(peer, types.User): + peer_id = peer.id + access_hash = peer.access_hash + + username = peer.username + phone_number = peer.phone + + if peer.bot: + peer_type = "bot" + else: + peer_type = "user" + + if access_hash is None: + is_min = True + continue + + if username is not None: + username = username.lower() + elif isinstance(peer, (types.Chat, types.ChatForbidden)): + peer_id = -peer.id + access_hash = 0 + peer_type = "group" + elif isinstance(peer, (types.Channel, types.ChannelForbidden)): + peer_id = int("-100" + str(peer.id)) + access_hash = peer.access_hash + + username = getattr(peer, "username", None) + + if peer.broadcast: + peer_type = "channel" + else: + peer_type = "supergroup" + + if access_hash is None: + is_min = True + continue + + if username is not None: + username = username.lower() + else: continue - self.session_storage.cache_peer(entity) + + parsed_peers.append((peer_id, access_hash, peer_type, username, phone_number)) + + self.storage.update_peers(parsed_peers) return is_min @@ -1035,12 +1060,23 @@ class Client(Methods, BaseClient): self.plugins = None def load_session(self): - try: - self.session_storage.load() - except SessionDoesNotExist: - log.info('Could not load session "{}", initiate new one'.format(self.session_name)) - self.session_storage.auth_key = Auth(self.session_storage.dc_id, self.session_storage.test_mode, - self.ipv6, self._proxy).create() + self.storage.open() + + session_empty = any([ + self.storage.test_mode is None, + self.storage.auth_key is None, + self.storage.user_id is None, + self.storage.is_bot is None + ]) + + if session_empty: + self.storage.dc_id = 1 + self.storage.date = 0 + + self.storage.test_mode = self.test_mode + self.storage.auth_key = Auth(self, self.storage.dc_id).create() + self.storage.user_id = None + self.storage.is_bot = None def load_plugins(self): if self.plugins: @@ -1164,9 +1200,6 @@ class Client(Methods, BaseClient): log.warning('[{}] No plugin loaded from "{}"'.format( self.session_name, root)) - def save_session(self): - self.session_storage.save() - def get_initial_dialogs_chunk(self, offset_date: int = 0): while True: try: @@ -1184,7 +1217,7 @@ class Client(Methods, BaseClient): log.warning("get_dialogs flood: waiting {} seconds".format(e.x)) time.sleep(e.x) else: - log.info("Total peers: {}".format(self.session_storage.peers_count())) + log.info("Total peers: {}".format(self.storage.peers_count)) return r def get_initial_dialogs(self): @@ -1222,7 +1255,7 @@ class Client(Methods, BaseClient): KeyError: In case the peer doesn't exist in the internal database. """ try: - return self.session_storage.get_peer_by_id(peer_id) + return self.storage.get_peer_by_id(peer_id) except KeyError: if type(peer_id) is str: if peer_id in ("self", "me"): @@ -1234,7 +1267,7 @@ class Client(Methods, BaseClient): int(peer_id) except ValueError: try: - self.session_storage.get_peer_by_username(peer_id) + return self.storage.get_peer_by_username(peer_id) except KeyError: self.send( functions.contacts.ResolveUsername( @@ -1242,10 +1275,10 @@ class Client(Methods, BaseClient): ) ) - return self.session_storage.get_peer_by_username(peer_id) + return self.storage.get_peer_by_username(peer_id) else: try: - return self.session_storage.get_peer_by_phone(peer_id) + return self.storage.get_peer_by_phone_number(peer_id) except KeyError: raise PeerIdInvalid @@ -1253,7 +1286,10 @@ class Client(Methods, BaseClient): 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 + )] ) ) ) @@ -1261,7 +1297,10 @@ class Client(Methods, BaseClient): if str(peer_id).startswith("-100"): self.send( functions.channels.GetChannels( - id=[types.InputChannel(channel_id=int(str(peer_id)[4:]), access_hash=0)] + id=[types.InputChannel( + channel_id=int(str(peer_id)[4:]), + access_hash=0 + )] ) ) else: @@ -1272,7 +1311,7 @@ class Client(Methods, BaseClient): ) try: - return self.session_storage.get_peer_by_id(peer_id) + return self.storage.get_peer_by_id(peer_id) except KeyError: raise PeerIdInvalid @@ -1347,7 +1386,7 @@ class Client(Methods, BaseClient): file_id = file_id or self.rnd_id() md5_sum = md5() if not is_big and not is_missing_part else None - session = Session(self, self.session_storage.dc_id, self.session_storage.auth_key, is_media=True) + session = Session(self, self.storage.dc_id, self.storage.auth_key, is_media=True) session.start() try: @@ -1433,19 +1472,14 @@ class Client(Methods, BaseClient): session = self.media_sessions.get(dc_id, None) if session is None: - if dc_id != self.session_storage.dc_id: + if dc_id != self.storage.dc_id: exported_auth = self.send( functions.auth.ExportAuthorization( dc_id=dc_id ) ) - session = Session( - self, - dc_id, - Auth(dc_id, self.session_storage.test_mode, self.ipv6, self._proxy).create(), - is_media=True - ) + session = Session(self, dc_id, Auth(self, dc_id).create(), is_media=True) session.start() @@ -1458,12 +1492,7 @@ class Client(Methods, BaseClient): ) ) else: - session = Session( - self, - dc_id, - self.session_storage.auth_key, - is_media=True - ) + session = Session(self, dc_id, self.storage.auth_key, is_media=True) session.start() @@ -1548,13 +1577,7 @@ class Client(Methods, BaseClient): cdn_session = self.media_sessions.get(r.dc_id, None) if cdn_session is None: - cdn_session = Session( - self, - r.dc_id, - Auth(r.dc_id, self.session_storage.test_mode, self.ipv6, self._proxy).create(), - is_media=True, - is_cdn=True - ) + cdn_session = Session(self, r.dc_id, Auth(self, r.dc_id).create(), is_media=True, is_cdn=True) cdn_session.start() @@ -1650,3 +1673,11 @@ 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 9276b0eb..88623f4a 100644 --- a/pyrogram/client/ext/base_client.py +++ b/pyrogram/client/ext/base_client.py @@ -87,13 +87,13 @@ class BaseClient: mime_types_to_extensions[mime_type] = " ".join(extensions) - def __init__(self, session_storage: SessionStorage): - self.session_storage = session_storage + def __init__(self): + self.storage = None self.rnd_id = MsgId - self.markdown = Markdown(self.session_storage, self) - self.html = HTML(self.session_storage, self) + self.markdown = Markdown(self) + self.html = HTML(self) self.session = None self.media_sessions = {} diff --git a/pyrogram/client/ext/syncer.py b/pyrogram/client/ext/syncer.py index 9e7d2303..42e1f95a 100644 --- a/pyrogram/client/ext/syncer.py +++ b/pyrogram/client/ext/syncer.py @@ -16,16 +16,10 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import base64 -import json import logging -import os -import shutil import time from threading import Thread, Event, Lock -from . import utils - log = logging.getLogger(__name__) @@ -81,10 +75,13 @@ class Syncer: @classmethod def sync(cls, client): - client.session_storage.date = int(time.time()) try: - client.session_storage.save(sync=True) + start = time.time() + client.storage.save() except Exception as e: log.critical(e, exc_info=True) else: - log.info("Synced {}".format(client.session_name)) + log.info('Synced "{}" in {:.6} ms'.format( + client.storage.name, + (time.time() - start) * 1000 + )) diff --git a/pyrogram/client/methods/contacts/get_contacts.py b/pyrogram/client/methods/contacts/get_contacts.py index 79677563..40cb344e 100644 --- a/pyrogram/client/methods/contacts/get_contacts.py +++ b/pyrogram/client/methods/contacts/get_contacts.py @@ -46,5 +46,4 @@ class GetContacts(BaseClient): log.warning("get_contacts flood: waiting {} seconds".format(e.x)) time.sleep(e.x) else: - log.info("Total contacts: {}".format(self.session_storage.contacts_count())) return pyrogram.List(pyrogram.User._parse(self, user) for user in contacts.users) From 0be0e2da5615cd9a403889ce487d3e8878451949 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 19 Jun 2019 16:11:25 +0200 Subject: [PATCH 029/202] Add export_session_string method to docs --- docs/source/api/methods.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/source/api/methods.rst b/docs/source/api/methods.rst index 4a3eefd8..2a08b37f 100644 --- a/docs/source/api/methods.rst +++ b/docs/source/api/methods.rst @@ -32,6 +32,7 @@ Utilities - :meth:`~Client.add_handler` - :meth:`~Client.remove_handler` - :meth:`~Client.stop_transmission` + - :meth:`~Client.export_session_string` Messages ^^^^^^^^ @@ -186,6 +187,7 @@ Details .. automethod:: Client.add_handler() .. automethod:: Client.remove_handler() .. automethod:: Client.stop_transmission() +.. automethod:: Client.export_session_string() .. Messages .. automethod:: Client.send_message() From 1f04ce38fc4f62ec786efb9c8080993bdfe127f2 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 19 Jun 2019 16:11:53 +0200 Subject: [PATCH 030/202] Fix glossary term --- docs/source/glossary.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/glossary.rst b/docs/source/glossary.rst index bcb1193c..d5a1bffd 100644 --- a/docs/source/glossary.rst +++ b/docs/source/glossary.rst @@ -58,7 +58,7 @@ Terms Pyrogram --- to automate some behaviours, like sending messages or reacting to text commands or any other event. Session - Also known as *login session*, is a strictly personal piece of information created and held by both parties + Also known as *login session*, is a strictly personal piece of data created and held by both parties (client and server) which is used to grant permission into a single account without having to start a new authorization process from scratch. From d1cd21916a7d20f71afa78767ca660f9f3fa8c8d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 19 Jun 2019 16:12:06 +0200 Subject: [PATCH 031/202] Add storage-engines.rst page to docs --- docs/source/index.rst | 1 + docs/source/topics/storage-engines.rst | 95 ++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 docs/source/topics/storage-engines.rst diff --git a/docs/source/index.rst b/docs/source/index.rst index 0bc175ee..b9682827 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -130,6 +130,7 @@ Meta topics/auto-auth topics/session-settings topics/tgcrypto + topics/storage-engines topics/text-formatting topics/serialize topics/proxy diff --git a/docs/source/topics/storage-engines.rst b/docs/source/topics/storage-engines.rst new file mode 100644 index 00000000..933a21b3 --- /dev/null +++ b/docs/source/topics/storage-engines.rst @@ -0,0 +1,95 @@ +Storage Engines +=============== + +Every time you login to Telegram, some personal piece of data are created and held by both parties (the client, Pyrogram +and the server, Telegram). This session data is uniquely bound to your own account, indefinitely (until you logout or +decide to manually terminate it) and is used to authorize a client to execute API calls on behalf of your identity. + +Persisting Sessions +------------------- + +In order to make a client reconnect successfully between restarts, that is, without having to start a new +authorization process from scratch each time, Pyrogram needs to store the generated session data somewhere. + +Other useful data being stored is peers' cache. In short, peers are all those entities you can chat with, such as users +or bots, basic groups, but also channels and supergroups. Because of how Telegram works, a unique pair of **id** and +**access_hash** is needed to contact a peer. This, plus other useful info such as the peer type, is what is stored +inside a session storage. + +So, if you ever wondered how is Pyrogram able to contact peers just by asking for their ids, it's because of this very +reason: the peer *id* is looked up in the internal database and the available *access_hash* is retrieved, which is then +used to correctly invoke API methods. + +Different Storage Engines +------------------------- + +Let's now talk about how Pyrogram actually stores all the relevant data. Pyrogram offers two different types of storage +engines: a **File Storage** and a **Memory Storage**. These engines are well integrated in the library and require a +minimal effort to set up. Here's how they work: + +File Storage +^^^^^^^^^^^^ + +This is the most common storage engine. It is implemented by using **SQLite**, which will store the session and peers +details. The database will be saved to disk as a single portable file and is designed to efficiently save and retrieve +peers whenever they are needed. + +To use this type of engine, simply pass any name of your choice to the ``session_name`` parameter of the +:obj:`~pyrogram.Client` constructor, as usual: + +.. code-block:: python + + from pyrogram import Client + + with Client("my_account") as app: + print(app.get_me()) + +Once you successfully log in (either with a user or a bot identity), a session file will be created and saved to disk as +``my_account.session``. Any subsequent client restart will make Pyrogram search for a file named that way and the +session database will be automatically loaded. + +Memory Storage +^^^^^^^^^^^^^^ + +In case you don't want to have any session file saved on disk, you can use an in-memory storage by passing the special +session name "**:memory:**" to the ``session_name`` parameter of the :obj:`~pyrogram.Client` constructor: + +.. code-block:: python + + from pyrogram import Client + + with Client(":memory:") as app: + print(app.get_me()) + +This database is still backed by SQLite, but exists purely in memory. However, once you stop a client, the entire +database is discarded and the session details used for logging in again will be lost forever. + +Session Strings +--------------- + +Session strings are useful when you want to run authorized Pyrogram clients on platforms like +`Heroku `_, where their ephemeral filesystems makes it much harder for a file-based storage +engine to properly work as intended. + +In case you want to use an in-memory storage, but also want to keep access to the session you created, call +:meth:`~pyrogram.Client.export_session_string` anytime before stopping the client... + +.. code-block:: python + + from pyrogram import Client + + with Client(":memory:") as app: + print(app.export_session_string()) + +...and save the resulting string somewhere. You can use this string as session name the next time you want to login +using the same session; the storage used will still be completely in-memory: + +.. code-block:: python + + from pyrogram import Client + + session_string = "...ZnUIFD8jsjXTb8g_vpxx48k1zkov9sapD-tzjz-S4WZv70M..." + + with Client(session_string) as app: + print(app.get_me()) + From fa1976c8a0e73c3acd0cf35ee3f831db5c945e42 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 19 Jun 2019 17:09:07 +0200 Subject: [PATCH 032/202] Update TgCrypto required version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d4255e03..45c2871b 100644 --- a/setup.py +++ b/setup.py @@ -174,7 +174,7 @@ setup( zip_safe=False, install_requires=requires, extras_require={ - "fast": ["tgcrypto==1.1.1"] + "fast": ["tgcrypto==1.2.0"] }, cmdclass={ "clean": Clean, From 8c96e5f46aaca4ca2a1593a6c57a24cc60a766ea Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 20 Jun 2019 03:31:37 +0200 Subject: [PATCH 033/202] Smarter session migration --- pyrogram/client/storage/file_storage.py | 68 ++++++++++++++----------- 1 file changed, 38 insertions(+), 30 deletions(-) diff --git a/pyrogram/client/storage/file_storage.py b/pyrogram/client/storage/file_storage.py index ee5000c5..f52a03a9 100644 --- a/pyrogram/client/storage/file_storage.py +++ b/pyrogram/client/storage/file_storage.py @@ -19,12 +19,9 @@ import base64 import json import logging -import os import sqlite3 from pathlib import Path -from sqlite3 import DatabaseError from threading import Lock -from typing import Union from .memory_storage import MemoryStorage @@ -43,25 +40,18 @@ class FileStorage(MemoryStorage): self.lock = Lock() # noinspection PyAttributeOutsideInit - def migrate_from_json(self, path: Union[str, Path]): - log.warning("JSON session storage detected! Pyrogram will now convert it into an SQLite session storage...") - - with open(path, encoding="utf-8") as f: - json_session = json.load(f) - - os.remove(path) - + def migrate_from_json(self, session_json: dict): self.open() - self.dc_id = json_session["dc_id"] - self.test_mode = json_session["test_mode"] - self.auth_key = base64.b64decode("".join(json_session["auth_key"])) - self.user_id = json_session["user_id"] - self.date = json_session.get("date", 0) - self.is_bot = json_session.get("is_bot", False) + self.dc_id = session_json["dc_id"] + self.test_mode = session_json["test_mode"] + self.auth_key = base64.b64decode("".join(session_json["auth_key"])) + self.user_id = session_json["user_id"] + self.date = session_json.get("date", 0) + self.is_bot = session_json.get("is_bot", False) - peers_by_id = json_session.get("peers_by_id", {}) - peers_by_phone = json_session.get("peers_by_phone", {}) + peers_by_id = session_json.get("peers_by_id", {}) + peers_by_phone = session_json.get("peers_by_phone", {}) peers = {} @@ -81,22 +71,40 @@ class FileStorage(MemoryStorage): # noinspection PyTypeChecker self.update_peers(peers.values()) - log.warning("Done! The session has been successfully converted from JSON to SQLite storage") - def open(self): - database_exists = os.path.isfile(self.database) + path = self.database + file_exists = path.is_file() + + if file_exists: + try: + with open(path, encoding="utf-8") as f: + session_json = json.load(f) + except ValueError: + pass + else: + log.warning("JSON session storage detected! Converting it into an SQLite session storage...") + + path.rename(path.name + ".OLD") + + log.warning('The old session file has been renamed to "{}.OLD"'.format(path.name)) + + self.migrate_from_json(session_json) + + log.warning("Done! The session has been successfully converted from JSON to SQLite storage") + + return + + if Path(path.name + ".OLD").is_file(): + log.warning('Old session file detected: "{}.OLD". You can remove this file now'.format(path.name)) self.conn = sqlite3.connect( - str(self.database), + path, timeout=1, check_same_thread=False ) - try: - if not database_exists: - self.create() + if not file_exists: + self.create() - with self.conn: - self.conn.execute("VACUUM") - except DatabaseError: - self.migrate_from_json(self.database) + with self.conn: + self.conn.execute("VACUUM") From f16ca8b9ea78c8af503fc80f3f7a2ca6cd9495c8 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 20 Jun 2019 03:39:12 +0200 Subject: [PATCH 034/202] Small documentation fixes --- docs/releases.py | 3 +-- docs/source/license.rst | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/releases.py b/docs/releases.py index 0c284f0b..0b566ca7 100644 --- a/docs/releases.py +++ b/docs/releases.py @@ -35,8 +35,7 @@ backwards-incompatible changes made in that version. When upgrading to a new version of Pyrogram, you will need to check all the breaking changes in order to find incompatible code in your application, but also to take advantage of new features and improvements. -Releases --------- +**Contents** """.lstrip("\n") diff --git a/docs/source/license.rst b/docs/source/license.rst index 38302bdc..43f59d73 100644 --- a/docs/source/license.rst +++ b/docs/source/license.rst @@ -2,7 +2,7 @@ About the License ================= .. image:: https://www.gnu.org/graphics/lgplv3-with-text-154x68.png - :align: right + :align: left Pyrogram is free software and is currently licensed under the terms of the `GNU Lesser General Public License v3 or later (LGPLv3+)`_. In short: you may use, redistribute and/or modify it From ad2d45bca1c0f8f7df82848bcd31a0a65cdd2bcf Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 20 Jun 2019 13:54:46 +0200 Subject: [PATCH 035/202] Add BUTTON_DATA_INVALID error --- compiler/error/source/400_BAD_REQUEST.tsv | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/error/source/400_BAD_REQUEST.tsv b/compiler/error/source/400_BAD_REQUEST.tsv index 8e82c9f6..389f99ab 100644 --- a/compiler/error/source/400_BAD_REQUEST.tsv +++ b/compiler/error/source/400_BAD_REQUEST.tsv @@ -105,4 +105,5 @@ MEGAGROUP_PREHISTORY_HIDDEN The action failed because the supergroup has the pre CHAT_LINK_EXISTS The action failed because the supergroup is linked to a channel LINK_NOT_MODIFIED The chat link was not modified because you tried to link to the same target BROADCAST_ID_INVALID The channel is invalid -MEGAGROUP_ID_INVALID The supergroup is invalid \ No newline at end of file +MEGAGROUP_ID_INVALID The supergroup is invalid +BUTTON_DATA_INVALID The button callback data contains invalid data or exceeds 64 bytes \ No newline at end of file From 36757c125cc4f67f4d48a3538b50ff4e7a22a5e8 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 20 Jun 2019 13:55:05 +0200 Subject: [PATCH 036/202] Tiny documentation fix --- docs/source/glossary.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/glossary.rst b/docs/source/glossary.rst index d5a1bffd..80ae1ecc 100644 --- a/docs/source/glossary.rst +++ b/docs/source/glossary.rst @@ -29,7 +29,7 @@ Terms achieve high quality and availability for services. RPC - Acronym for Remote Procedure call, that is, a function which gets executed at some remote place (i.e. Telegram + Acronym for Remote Procedure Call, that is, a function which gets executed at some remote place (i.e. Telegram server) and not in your local machine. RPCError From 48197cb2228f0eb38c58fa72e66659a87dbdbe5e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 20 Jun 2019 13:56:23 +0200 Subject: [PATCH 037/202] Update FAQ: Starting from v0.14.1 photo-like file id formats changed --- docs/source/faq.rst | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/source/faq.rst b/docs/source/faq.rst index 449076af..05fa0bb5 100644 --- a/docs/source/faq.rst +++ b/docs/source/faq.rst @@ -1,6 +1,9 @@ Pyrogram FAQ ============ +.. role:: strike + :class: strike + This FAQ page provides answers to common questions about Pyrogram and, to some extent, Telegram in general. .. tip:: @@ -102,9 +105,11 @@ one: ``CAADBAADyg4AAvLQYAEYD4F7vcZ43AI``. Can I use Bot API's file_ids in Pyrogram? ----------------------------------------- -Definitely! All file ids you might have taken from the Bot API are 100% compatible and re-usable in Pyrogram... +:strike:`Definitely! All file ids you might have taken from the Bot API are 100% compatible and re-usable in Pyrogram.` -...at least for now. +Starting from :doc:`Pyrogram v0.14.1 (Layer 100) `, the file_id format of all photo-like objects has +changed. Types affected are: :obj:`~pyrogram.Thumbnail`, :obj:`~pyrogram.ChatPhoto` and :obj:`~pyrogram.Photo`. Any +other file id remains compatible with the Bot API. Telegram is slowly changing some server's internals and it's doing it in such a way that file ids are going to break inevitably. Not only this, but it seems that the new, hypothetical, file ids could also possibly expire at anytime, thus From 9ebf2983fe444770f180a110278e45071477b094 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 20 Jun 2019 14:15:02 +0200 Subject: [PATCH 038/202] Cast Paths to string: pathlib for older pythons doesn't properly work --- pyrogram/client/client.py | 2 +- pyrogram/client/storage/file_storage.py | 4 ++-- pyrogram/client/storage/memory_storage.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 2d18d178..e1bebeaf 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -990,7 +990,7 @@ class Client(Methods, BaseClient): def load_config(self): parser = ConfigParser() - parser.read(self.config_file) + parser.read(str(self.config_file)) if self.api_id and self.api_hash: pass diff --git a/pyrogram/client/storage/file_storage.py b/pyrogram/client/storage/file_storage.py index f52a03a9..e6ba8420 100644 --- a/pyrogram/client/storage/file_storage.py +++ b/pyrogram/client/storage/file_storage.py @@ -77,7 +77,7 @@ class FileStorage(MemoryStorage): if file_exists: try: - with open(path, encoding="utf-8") as f: + with open(str(path), encoding="utf-8") as f: session_json = json.load(f) except ValueError: pass @@ -98,7 +98,7 @@ class FileStorage(MemoryStorage): log.warning('Old session file detected: "{}.OLD". You can remove this file now'.format(path.name)) self.conn = sqlite3.connect( - path, + str(path), timeout=1, check_same_thread=False ) diff --git a/pyrogram/client/storage/memory_storage.py b/pyrogram/client/storage/memory_storage.py index 7eb3a7d0..bf000f35 100644 --- a/pyrogram/client/storage/memory_storage.py +++ b/pyrogram/client/storage/memory_storage.py @@ -46,7 +46,7 @@ class MemoryStorage(Storage): def create(self): with self.lock, self.conn: - with open(Path(__file__).parent / "schema.sql", "r") as schema: + with open(str(Path(__file__).parent / "schema.sql"), "r") as schema: self.conn.executescript(schema.read()) self.conn.execute( From 81e7c1b4eba9389e8bda64d49fab05419c090fef Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 20 Jun 2019 14:15:53 +0200 Subject: [PATCH 039/202] Add START_PARAM_INVALID error --- compiler/error/source/400_BAD_REQUEST.tsv | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/error/source/400_BAD_REQUEST.tsv b/compiler/error/source/400_BAD_REQUEST.tsv index 389f99ab..43bade44 100644 --- a/compiler/error/source/400_BAD_REQUEST.tsv +++ b/compiler/error/source/400_BAD_REQUEST.tsv @@ -106,4 +106,5 @@ CHAT_LINK_EXISTS The action failed because the supergroup is linked to a channel LINK_NOT_MODIFIED The chat link was not modified because you tried to link to the same target BROADCAST_ID_INVALID The channel is invalid MEGAGROUP_ID_INVALID The supergroup is invalid -BUTTON_DATA_INVALID The button callback data contains invalid data or exceeds 64 bytes \ No newline at end of file +BUTTON_DATA_INVALID The button callback data contains invalid data or exceeds 64 bytes +START_PARAM_INVALID The start parameter is invalid \ No newline at end of file From d06289c88d15d9db51f55bd5b11516b96528deb7 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 20 Jun 2019 15:31:02 +0200 Subject: [PATCH 040/202] Add FAQ about why responses might be slow --- docs/source/faq.rst | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/docs/source/faq.rst b/docs/source/faq.rst index 05fa0bb5..4c737291 100644 --- a/docs/source/faq.rst +++ b/docs/source/faq.rst @@ -205,6 +205,34 @@ mechanism is also `confirmed `_ for this list. + I keep getting PEER_ID_INVALID error! ------------------------------------- From a928981b1f8b3f520dc8c4d827cbce2e4e23949c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 20 Jun 2019 16:07:57 +0200 Subject: [PATCH 041/202] Link back to the source of information, where applicable --- docs/source/faq.rst | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/source/faq.rst b/docs/source/faq.rst index 4c737291..288c5502 100644 --- a/docs/source/faq.rst +++ b/docs/source/faq.rst @@ -185,9 +185,12 @@ IP addresses are now kept as aliases. DC2, "AMS, Amsterdam, NL", ``149.154.167.40``, ``2001:67c:4e8:f002::e`` DC3*, "MIA, Miami FL, USA", ``149.154.175.117``, ``2001:b28:f23d:f003::e`` +.. centered:: More info about the Test Environment can be found :doc:`here `. + ***** Alias DC -More info about the Test Environment can be found :doc:`here `. +Thanks to `@FrayxRulez `_ for telling about alias DCs. + I want to migrate my account from DCX to DCY. --------------------------------------------- @@ -205,6 +208,8 @@ mechanism is also `confirmed `_ for confirming the feature was not implemented yet. + Why is my client reacting slowly in supergroups? ------------------------------------------------ @@ -231,7 +236,7 @@ noticeable, especially if you are among the lower end of the priority list: 5. Recent online users. 6. Everyone else. -Thanks `@Manuel15 `_ for this list. +Thanks to `@Manuel15 `_ for the priority list. I keep getting PEER_ID_INVALID error! ------------------------------------- @@ -276,7 +281,7 @@ Having said that, here's a list of what Telegram definitely doesn't like: - Spam, sending unsolicited messages or adding people to unwanted groups and channels. - Virtual/VoIP and cheap real numbers, because they are relatively easy to get and likely used for spam/flood. -And here's a good explanation of how, probably, the system works: +And thanks to `@koteeq `_, here's a good explanation of how, probably, the system works: .. raw:: html @@ -285,8 +290,7 @@ And here's a good explanation of how, probably, the system works: data-telegram-post="PyrogramChat/69424" data-width="100%"> - -.. centered:: Join the discussion at `@Pyrogram `_ +

However, you might be right, and your account was deactivated/limited without any good reason. This could happen because of mistakes by either the automatic systems or a moderator. In such cases you can kindly email Telegram at From 83900f398f7516e0f50850037dc09178a6d1749b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 20 Jun 2019 16:10:18 +0200 Subject: [PATCH 042/202] Specify that 3.5.3 or higher is required for asyncio --- docs/source/intro/install.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/intro/install.rst b/docs/source/intro/install.rst index 82ab4c0b..b60671f0 100644 --- a/docs/source/intro/install.rst +++ b/docs/source/intro/install.rst @@ -47,7 +47,7 @@ Pyrogram heavily depends on IO-bound network code (it's a cloud-based messaging where asyncio shines the most by providing extra performance and efficiency while running on a single OS-level thread only. -**A fully asynchronous variant of Pyrogram is therefore available** (Python 3.5.3+ required). +**A fully asynchronous variant of Pyrogram is therefore available** (Python 3.5.3 or higher is required). Use this command to install (note "asyncio.zip" in the link): .. code-block:: text From 11b4b947ac08ffd7d3049caaa371b055db6bc8e4 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 20 Jun 2019 16:12:14 +0200 Subject: [PATCH 043/202] Small rewords --- docs/source/faq.rst | 2 +- docs/source/topics/storage-engines.rst | 24 ++++++++++++++---------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/docs/source/faq.rst b/docs/source/faq.rst index 288c5502..335f4bc6 100644 --- a/docs/source/faq.rst +++ b/docs/source/faq.rst @@ -255,7 +255,7 @@ UnicodeEncodeError: '' codec can't encode … Where ```` might be *ascii*, *cp932*, *charmap* or anything else other than **utf-8**. This error usually shows up when you try to print something and has very little to do with Pyrogram itself as it is strictly related to 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 one. +better terminal altogether. My verification code expires immediately! ----------------------------------------- diff --git a/docs/source/topics/storage-engines.rst b/docs/source/topics/storage-engines.rst index 933a21b3..a161e50f 100644 --- a/docs/source/topics/storage-engines.rst +++ b/docs/source/topics/storage-engines.rst @@ -31,7 +31,7 @@ File Storage ^^^^^^^^^^^^ This is the most common storage engine. It is implemented by using **SQLite**, which will store the session and peers -details. The database will be saved to disk as a single portable file and is designed to efficiently save and retrieve +details. The database will be saved to disk as a single portable file and is designed to efficiently store and retrieve peers whenever they are needed. To use this type of engine, simply pass any name of your choice to the ``session_name`` parameter of the @@ -51,7 +51,7 @@ session database will be automatically loaded. Memory Storage ^^^^^^^^^^^^^^ -In case you don't want to have any session file saved on disk, you can use an in-memory storage by passing the special +In case you don't want to have any session file saved to disk, you can use an in-memory storage by passing the special session name "**:memory:**" to the ``session_name`` parameter of the :obj:`~pyrogram.Client` constructor: .. code-block:: python @@ -61,16 +61,12 @@ session name "**:memory:**" to the ``session_name`` parameter of the :obj:`~pyro with Client(":memory:") as app: print(app.get_me()) -This database is still backed by SQLite, but exists purely in memory. However, once you stop a client, the entire -database is discarded and the session details used for logging in again will be lost forever. +This storage engine is still backed by SQLite, but the database exists purely in memory. This means that, once you stop a +client, the entire database is discarded and the session details used for logging in again will be lost forever. Session Strings --------------- -Session strings are useful when you want to run authorized Pyrogram clients on platforms like -`Heroku `_, where their ephemeral filesystems makes it much harder for a file-based storage -engine to properly work as intended. - In case you want to use an in-memory storage, but also want to keep access to the session you created, call :meth:`~pyrogram.Client.export_session_string` anytime before stopping the client... @@ -81,8 +77,8 @@ In case you want to use an in-memory storage, but also want to keep access to th with Client(":memory:") as app: print(app.export_session_string()) -...and save the resulting string somewhere. You can use this string as session name the next time you want to login -using the same session; the storage used will still be completely in-memory: +...and save the resulting (quite long) string somewhere. You can use this string as session name the next time you want +to login using the same session; the storage used will still be completely in-memory: .. code-block:: python @@ -93,3 +89,11 @@ using the same session; the storage used will still be completely in-memory: with Client(session_string) as app: print(app.get_me()) +Session strings are useful when you want to run authorized Pyrogram clients on platforms like +`Heroku `_, where their ephemeral filesystems makes it much harder for a file-based storage +engine to properly work as intended. + +But, why is the session string so long? Can't it be shorter? No, it can't. The session string already packs the bare +minimum data Pyrogram needs to successfully reconnect to an authorized session, and the 2048-bits auth key is the major +contributor to the overall length. Needless to repeat that this string, as well as any other session storage, represent +strictly personal data. Keep them safe. From 0699bd31e5e75addab28d90397e9811548fcc00e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 20 Jun 2019 19:23:33 +0200 Subject: [PATCH 044/202] Bring Message .reply() and .edit() back for now --- pyrogram/client/types/messages_and_media/message.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index cd59b5eb..52e8f473 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -727,6 +727,8 @@ class Message(Object, Update): reply_markup=reply_markup ) + reply = reply_text + def reply_animation( self, animation: str, @@ -2409,6 +2411,8 @@ class Message(Object, Update): reply_markup=reply_markup ) + edit = edit_text + def edit_caption( self, caption: str, From 8d0e161b5602775dc4697ac44b264c81a58c70b9 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 21 Jun 2019 01:53:17 +0200 Subject: [PATCH 045/202] Lock dispatcher groups. Fixes #255 --- pyrogram/client/ext/dispatcher.py | 59 +++++++++++++++++-------------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/pyrogram/client/ext/dispatcher.py b/pyrogram/client/ext/dispatcher.py index 56cdead6..2224bda9 100644 --- a/pyrogram/client/ext/dispatcher.py +++ b/pyrogram/client/ext/dispatcher.py @@ -20,7 +20,7 @@ import logging import threading from collections import OrderedDict from queue import Queue -from threading import Thread +from threading import Thread, Lock import pyrogram from pyrogram.api import types @@ -64,6 +64,8 @@ class Dispatcher: self.updates_queue = Queue() self.groups = OrderedDict() + self.lock = Lock() + self.update_parsers = { Dispatcher.MESSAGE_UPDATES: lambda upd, usr, cht: (pyrogram.Message._parse(self.client, upd.message, usr, cht), MessageHandler), @@ -110,17 +112,19 @@ class Dispatcher: self.groups.clear() def add_handler(self, handler, group: int): - if group not in self.groups: - self.groups[group] = [] - self.groups = OrderedDict(sorted(self.groups.items())) + with self.lock: + if group not in self.groups: + self.groups[group] = [] + self.groups = OrderedDict(sorted(self.groups.items())) - self.groups[group].append(handler) + self.groups[group].append(handler) def remove_handler(self, handler, group: int): - if group not in self.groups: - raise ValueError("Group {} does not exist. Handler was not removed.".format(group)) + with self.lock: + if group not in self.groups: + raise ValueError("Group {} does not exist. Handler was not removed.".format(group)) - self.groups[group].remove(handler) + self.groups[group].remove(handler) def update_worker(self): name = threading.current_thread().name @@ -142,29 +146,30 @@ class Dispatcher: else (None, type(None)) ) - for group in self.groups.values(): - for handler in group: - args = None + with self.lock: + for group in self.groups.values(): + for handler in group: + args = None - if isinstance(handler, handler_type): - if handler.check(parsed_update): - args = (parsed_update,) - elif isinstance(handler, RawUpdateHandler): - args = (update, users, chats) + if isinstance(handler, handler_type): + if handler.check(parsed_update): + args = (parsed_update,) + elif isinstance(handler, RawUpdateHandler): + args = (update, users, chats) - if args is None: - continue + if args is None: + continue - try: - handler.callback(self.client, *args) - except pyrogram.StopPropagation: - raise - except pyrogram.ContinuePropagation: - continue - except Exception as e: - log.error(e, exc_info=True) + try: + handler.callback(self.client, *args) + except pyrogram.StopPropagation: + raise + except pyrogram.ContinuePropagation: + continue + except Exception as e: + log.error(e, exc_info=True) - break + break except pyrogram.StopPropagation: pass except Exception as e: From a398bc5fc72c050ff90d8f82015755b2eccad3bd Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 21 Jun 2019 02:00:29 +0200 Subject: [PATCH 046/202] Rename CallbackQuery's bound-methods: edit_* -> edit_message_* --- docs/source/api/bound-methods.rst | 18 +++++++++--------- .../types/bots_and_keyboards/callback_query.py | 18 +++++++++--------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/docs/source/api/bound-methods.rst b/docs/source/api/bound-methods.rst index e6729da6..83b3dbbe 100644 --- a/docs/source/api/bound-methods.rst +++ b/docs/source/api/bound-methods.rst @@ -81,13 +81,13 @@ CallbackQuery ^^^^^^^^^^^^^ .. hlist:: - :columns: 4 + :columns: 3 - :meth:`~CallbackQuery.answer` - - :meth:`~CallbackQuery.edit_text` - - :meth:`~CallbackQuery.edit_caption` - - :meth:`~CallbackQuery.edit_media` - - :meth:`~CallbackQuery.edit_reply_markup` + - :meth:`~CallbackQuery.edit_message_text` + - :meth:`~CallbackQuery.edit_message_caption` + - :meth:`~CallbackQuery.edit_message_media` + - :meth:`~CallbackQuery.edit_message_reply_markup` InlineQuery ^^^^^^^^^^^ @@ -141,10 +141,10 @@ Details .. CallbackQuery .. automethod:: CallbackQuery.answer() -.. automethod:: CallbackQuery.edit_text() -.. automethod:: CallbackQuery.edit_caption() -.. automethod:: CallbackQuery.edit_media() -.. automethod:: CallbackQuery.edit_reply_markup() +.. automethod:: CallbackQuery.edit_message_text() +.. automethod:: CallbackQuery.edit_message_caption() +.. automethod:: CallbackQuery.edit_message_media() +.. automethod:: CallbackQuery.edit_message_reply_markup() .. InlineQuery .. automethod:: InlineQuery.answer() diff --git a/pyrogram/client/types/bots_and_keyboards/callback_query.py b/pyrogram/client/types/bots_and_keyboards/callback_query.py index fcc90e57..6a717489 100644 --- a/pyrogram/client/types/bots_and_keyboards/callback_query.py +++ b/pyrogram/client/types/bots_and_keyboards/callback_query.py @@ -173,14 +173,14 @@ class CallbackQuery(Object, Update): cache_time=cache_time ) - def edit_text( + def edit_message_text( self, text: str, parse_mode: str = "", disable_web_page_preview: bool = None, reply_markup: "pyrogram.InlineKeyboardMarkup" = None ) -> Union["pyrogram.Message", bool]: - """Edit the text of messages attached to this callback query. + """Edit the text of messages attached to callback queries. Bound method *edit_message_text* of :obj:`CallbackQuery`. @@ -223,13 +223,13 @@ class CallbackQuery(Object, Update): reply_markup=reply_markup ) - def edit_caption( + def edit_message_caption( self, caption: str, parse_mode: str = "", reply_markup: "pyrogram.InlineKeyboardMarkup" = None ) -> Union["pyrogram.Message", bool]: - """Edit the caption of media messages attached to this callback query. + """Edit the caption of media messages attached to callback queries. Bound method *edit_message_caption* of :obj:`CallbackQuery`. @@ -251,14 +251,14 @@ class CallbackQuery(Object, Update): Raises: RPCError: In case of a Telegram RPC error. """ - return self.edit_text(caption, parse_mode, reply_markup) + return self.edit_message_text(caption, parse_mode, reply_markup) - def edit_media( + def edit_message_media( self, media: "pyrogram.InputMedia", reply_markup: "pyrogram.InlineKeyboardMarkup" = None ) -> Union["pyrogram.Message", bool]: - """Edit animation, audio, document, photo or video messages attached to this callback query. + """Edit animation, audio, document, photo or video messages attached to callback queries. Bound method *edit_message_media* of :obj:`CallbackQuery`. @@ -290,11 +290,11 @@ class CallbackQuery(Object, Update): reply_markup=reply_markup ) - def edit_reply_markup( + def edit_message_reply_markup( self, reply_markup: "pyrogram.InlineKeyboardMarkup" = None ) -> Union["pyrogram.Message", bool]: - """Edit only the reply markup of messages attached to this callback query. + """Edit only the reply markup of messages attached to callback queries. Bound method *edit_message_reply_markup* of :obj:`CallbackQuery`. From b439e440155f1745d05463b6b1634f921817afcb Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 21 Jun 2019 03:43:43 +0200 Subject: [PATCH 047/202] Fix tiny typo --- pyrogram/client/methods/messages/send_video_note.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/methods/messages/send_video_note.py b/pyrogram/client/methods/messages/send_video_note.py index 7bb8803b..da8d53c2 100644 --- a/pyrogram/client/methods/messages/send_video_note.py +++ b/pyrogram/client/methods/messages/send_video_note.py @@ -106,7 +106,7 @@ class SendVideoNote(BaseClient): Returns: :obj:`Message` | ``None``: On success, the sent video note message is returned, otherwise, in case the - pload is deliberately stopped with :meth:`~Client.stop_transmission`, None is returned. + upload is deliberately stopped with :meth:`~Client.stop_transmission`, None is returned. Raises: RPCError: In case of a Telegram RPC error. From e7fffd2f7690d1dc6337f70ce1da09c6ee3b4617 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 22 Jun 2019 00:45:49 +0200 Subject: [PATCH 048/202] Fix workers not running concurrently anymore after using a shared Lock --- pyrogram/client/ext/dispatcher.py | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/pyrogram/client/ext/dispatcher.py b/pyrogram/client/ext/dispatcher.py index 2224bda9..c931c8f7 100644 --- a/pyrogram/client/ext/dispatcher.py +++ b/pyrogram/client/ext/dispatcher.py @@ -61,11 +61,11 @@ class Dispatcher: self.workers = workers self.workers_list = [] + self.locks_list = [] + self.updates_queue = Queue() self.groups = OrderedDict() - self.lock = Lock() - self.update_parsers = { Dispatcher.MESSAGE_UPDATES: lambda upd, usr, cht: (pyrogram.Message._parse(self.client, upd.message, usr, cht), MessageHandler), @@ -92,10 +92,13 @@ class Dispatcher: def start(self): for i in range(self.workers): + self.locks_list.append(Lock()) + self.workers_list.append( Thread( target=self.update_worker, - name="UpdateWorker#{}".format(i + 1) + name="UpdateWorker#{}".format(i + 1), + args=(self.locks_list[-1],) ) ) @@ -109,24 +112,37 @@ class Dispatcher: worker.join() self.workers_list.clear() + self.locks_list.clear() self.groups.clear() def add_handler(self, handler, group: int): - with self.lock: + for lock in self.locks_list: + lock.acquire() + + try: if group not in self.groups: self.groups[group] = [] self.groups = OrderedDict(sorted(self.groups.items())) self.groups[group].append(handler) + finally: + for lock in self.locks_list: + lock.release() def remove_handler(self, handler, group: int): - with self.lock: + for lock in self.locks_list: + lock.acquire() + + try: if group not in self.groups: raise ValueError("Group {} does not exist. Handler was not removed.".format(group)) self.groups[group].remove(handler) + finally: + for lock in self.locks_list: + lock.release() - def update_worker(self): + def update_worker(self, lock): name = threading.current_thread().name log.debug("{} started".format(name)) @@ -146,7 +162,7 @@ class Dispatcher: else (None, type(None)) ) - with self.lock: + with lock: for group in self.groups.values(): for handler in group: args = None From 91e377aacc92cde4fd33ed1f490105f6c8751447 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 23 Jun 2019 01:30:38 +0200 Subject: [PATCH 049/202] Add ARTICLE_TITLE_EMPTY error --- compiler/error/source/400_BAD_REQUEST.tsv | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/error/source/400_BAD_REQUEST.tsv b/compiler/error/source/400_BAD_REQUEST.tsv index 43bade44..f44f5cb2 100644 --- a/compiler/error/source/400_BAD_REQUEST.tsv +++ b/compiler/error/source/400_BAD_REQUEST.tsv @@ -107,4 +107,5 @@ LINK_NOT_MODIFIED The chat link was not modified because you tried to link to th BROADCAST_ID_INVALID The channel is invalid MEGAGROUP_ID_INVALID The supergroup is invalid BUTTON_DATA_INVALID The button callback data contains invalid data or exceeds 64 bytes -START_PARAM_INVALID The start parameter is invalid \ No newline at end of file +START_PARAM_INVALID The start parameter is invalid +ARTICLE_TITLE_EMPTY The article title is empty \ No newline at end of file From 267797051c42a9f75eec8b56bc2dcc8509e594f4 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 23 Jun 2019 01:30:50 +0200 Subject: [PATCH 050/202] Fix broken link --- docs/source/intro/quickstart.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/intro/quickstart.rst b/docs/source/intro/quickstart.rst index a7a7e377..13f646d1 100644 --- a/docs/source/intro/quickstart.rst +++ b/docs/source/intro/quickstart.rst @@ -46,4 +46,4 @@ In the next few pages of the introduction, we'll take a much more in-depth look Feeling eager to continue? You can take a shortcut to :doc:`Calling Methods <../start/invoking>` and come back later to learn some more details. -.. _community: //t.me/Pyrogram +.. _community: https://t.me/Pyrogram From bf71989bd047c68fade76360e4ead0e3c77cf725 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 23 Jun 2019 01:31:18 +0200 Subject: [PATCH 051/202] Enhance PDF building --- docs/source/conf.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/source/conf.py b/docs/source/conf.py index 01fbe6de..b0313901 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -66,3 +66,15 @@ html_theme_options = { html_logo = "_images/pyrogram.png" html_favicon = "_images/favicon.ico" + +latex_engine = "xelatex" +latex_logo = "_images/pyrogram.png" + +latex_elements = { + "pointsize": "12pt", + "fontpkg": r""" + \setmainfont{Noto Sans} + \setsansfont{Roboto Slab} + \setmonofont{Ubuntu Mono} + """ +} From b4f0f411bd5f7b2c491faab59ad5f9d2e999debc Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 23 Jun 2019 01:32:04 +0200 Subject: [PATCH 052/202] Small documentation fixes --- docs/source/faq.rst | 6 +- docs/source/index.rst | 174 +++++++++++++++++++++--------------------- 2 files changed, 90 insertions(+), 90 deletions(-) diff --git a/docs/source/faq.rst b/docs/source/faq.rst index 335f4bc6..f76f1790 100644 --- a/docs/source/faq.rst +++ b/docs/source/faq.rst @@ -68,7 +68,7 @@ To challenge the framework, the creator is constantly keeping a public `welcome bot `_ online 24/7 on his own, relatively-busy account for well over a year now. -In addition to that, about six months ago, one of the most popular Telegram bot has been rewritten +In addition to that, about six months ago, one of the most popular Telegram bot has been rewritten from scratch :doc:`using Pyrogram ` and is serving more than 200,000 Monthly Active Users since then, uninterruptedly and without any need for restarting it. @@ -134,8 +134,8 @@ If you -- even accidentally -- fail to do so, all the previous session copies wi and eventually the server will start throwing the error ``[406 AUTH_KEY_DUPLICATED]``, inviting you to login again. Why is that so? Because the server has recognized two identical sessions are running in two different locations, and -concludes it could possibly be due to a cloned/stolen device. Having the session ended in such occasions will protect -the user's privacy. +concludes it could possibly be due to a cloned/stolen device. Having the session terminated in such occasions will +protect the user's privacy. So, the only correct way to run multiple clients on the same account is authorizing your account (either user or bot) from the beginning every time, and use one separate session for each parallel client you are going to use. diff --git a/docs/source/index.rst b/docs/source/index.rst index b9682827..66722690 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,93 +1,6 @@ Welcome to Pyrogram =================== -.. raw:: html - -
- -
Pyrogram Logo
-
-
- -

- Telegram MTProto API Framework for Python - -
- - Source Code - - • - - Releases - - • - - Community - -

- -.. code-block:: python - - from pyrogram import Client, Filters - - app = Client("my_account") - - - @app.on_message(Filters.private) - def hello(client, message): - message.reply("Hello {}".format(message.from_user.first_name)) - - - app.run() - -**Pyrogram** is an elegant, easy-to-use Telegram_ client library and framework written from the ground up in Python and -C. It enables you to easily create custom apps for both user and bot identities (bot API alternative) via the -:doc:`MTProto API `. - -.. _Telegram: https://telegram.org - -How the Documentation is Organized ----------------------------------- - -Contents are organized into self-contained topics and can be all accessed from the sidebar, or by following them in -order using the :guilabel:`Next` button at the end of each page. Here below you can, instead, find a list of the most -relevant pages for a quick access. - -First Steps ------------ - -.. hlist:: - :columns: 2 - - - :doc:`Quick Start `: Overview to get you started quickly. - - :doc:`Calling Methods `: How to call Pyrogram's methods. - - :doc:`Handling Updates `: How to handle Telegram updates. - - :doc:`Error Handling `: How to handle API errors correctly. - -API Reference -------------- - -.. hlist:: - :columns: 2 - - - :doc:`Pyrogram Client `: Reference details about the Client class. - - :doc:`Available Methods `: List of available high-level methods. - - :doc:`Available Types `: List of available high-level types. - - :doc:`Bound Methods `: List of convenient bound methods. - -Meta ----- - -.. hlist:: - :columns: 2 - - - :doc:`Pyrogram FAQ `: Answers to common Pyrogram questions. - - :doc:`Pyrogram Glossary `: List of words with brief explanations. - - :doc:`Powered by Pyrogram `: Collection of Pyrogram Projects. - - :doc:`Support Pyrogram `: Ways to show your appreciation. - - :doc:`About the License `: Information about the Project license. - - :doc:`Release Notes `: Release notes for Pyrogram releases. - .. toctree:: :hidden: :caption: Introduction @@ -159,4 +72,91 @@ Meta telegram/functions/index telegram/types/index +.. raw:: html + +
+ +
Pyrogram Logo
+
+
+ +

+ Telegram MTProto API Framework for Python + +
+ + Source Code + + • + + Releases + + • + + Community + +

+ +.. code-block:: python + + from pyrogram import Client, Filters + + app = Client("my_account") + + + @app.on_message(Filters.private) + def hello(client, message): + message.reply("Hello {}".format(message.from_user.first_name)) + + + app.run() + +**Pyrogram** is an elegant, easy-to-use Telegram_ client library and framework written from the ground up in Python and +C. It enables you to easily create custom apps for both user and bot identities (bot API alternative) via the +:doc:`MTProto API `. + +.. _Telegram: https://telegram.org + +How the Documentation is Organized +---------------------------------- + +Contents are organized into self-contained topics and can be all accessed from the sidebar, or by following them in +order using the :guilabel:`Next` button at the end of each page. Here below you can, instead, find a list of the most +relevant pages for a quick access. + +First Steps +^^^^^^^^^^^ + +.. hlist:: + :columns: 2 + + - :doc:`Quick Start `: Overview to get you started quickly. + - :doc:`Calling Methods `: How to call Pyrogram's methods. + - :doc:`Handling Updates `: How to handle Telegram updates. + - :doc:`Error Handling `: How to handle API errors correctly. + +API Reference +^^^^^^^^^^^^^ + +.. hlist:: + :columns: 2 + + - :doc:`Pyrogram Client `: Reference details about the Client class. + - :doc:`Available Methods `: List of available high-level methods. + - :doc:`Available Types `: List of available high-level types. + - :doc:`Bound Methods `: List of convenient bound methods. + +Meta +^^^^ + +.. hlist:: + :columns: 2 + + - :doc:`Pyrogram FAQ `: Answers to common Pyrogram questions. + - :doc:`Pyrogram Glossary `: List of words with brief explanations. + - :doc:`Powered by Pyrogram `: Collection of Pyrogram Projects. + - :doc:`Support Pyrogram `: Ways to show your appreciation. + - :doc:`About the License `: Information about the Project license. + - :doc:`Release Notes `: Release notes for Pyrogram releases. + Last updated on |today| \ No newline at end of file From 31f39a00ab602c9714c827833e93730b9466bcb2 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 23 Jun 2019 01:33:46 +0200 Subject: [PATCH 053/202] Make plugin callback functions return the function itself when decorated --- pyrogram/client/client.py | 6 ++--- .../methods/decorators/on_callback_query.py | 24 ++++++++----------- .../methods/decorators/on_deleted_messages.py | 24 ++++++++----------- .../methods/decorators/on_disconnect.py | 13 +++++----- .../methods/decorators/on_inline_query.py | 24 ++++++++----------- .../client/methods/decorators/on_message.py | 24 ++++++++----------- pyrogram/client/methods/decorators/on_poll.py | 24 ++++++++----------- .../methods/decorators/on_raw_update.py | 24 ++++++++----------- .../methods/decorators/on_user_status.py | 24 ++++++++----------- 9 files changed, 79 insertions(+), 108 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index e1bebeaf..fdfc1e68 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -1106,7 +1106,7 @@ class Client(Methods, BaseClient): for name in vars(module).keys(): # noinspection PyBroadException try: - handler, group = getattr(module, name) + handler, group = getattr(module, name).pyrogram_plugin if isinstance(handler, Handler) and isinstance(group, int): self.add_handler(handler, group) @@ -1141,7 +1141,7 @@ class Client(Methods, BaseClient): for name in handlers: # noinspection PyBroadException try: - handler, group = getattr(module, name) + handler, group = getattr(module, name).pyrogram_plugin if isinstance(handler, Handler) and isinstance(group, int): self.add_handler(handler, group) @@ -1179,7 +1179,7 @@ class Client(Methods, BaseClient): for name in handlers: # noinspection PyBroadException try: - handler, group = getattr(module, name) + handler, group = getattr(module, name).pyrogram_plugin if isinstance(handler, Handler) and isinstance(group, int): self.remove_handler(handler, group) diff --git a/pyrogram/client/methods/decorators/on_callback_query.py b/pyrogram/client/methods/decorators/on_callback_query.py index 1552bae7..1706d71a 100644 --- a/pyrogram/client/methods/decorators/on_callback_query.py +++ b/pyrogram/client/methods/decorators/on_callback_query.py @@ -16,11 +16,10 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Tuple +from typing import Callable import pyrogram from pyrogram.client.filters.filter import Filter -from pyrogram.client.handlers.handler import Handler from ...ext import BaseClient @@ -44,18 +43,15 @@ class OnCallbackQuery(BaseClient): The group identifier, defaults to 0. """ - def decorator(func: callable) -> Tuple[Handler, int]: - if isinstance(func, tuple): - func = func[0].callback + def decorator(func: Callable) -> Callable: + if isinstance(self, pyrogram.Client): + self.add_handler(pyrogram.CallbackQueryHandler(func, filters), group) + elif isinstance(self, Filter) or self is None: + func.pyrogram_plugin = ( + pyrogram.CallbackQueryHandler(func, self), + group if filters is None else filters + ) - handler = pyrogram.CallbackQueryHandler(func, filters) - - if isinstance(self, Filter): - return pyrogram.CallbackQueryHandler(func, self), group if filters is None else filters - - if self is not None: - self.add_handler(handler, group) - - return handler, group + return func return decorator diff --git a/pyrogram/client/methods/decorators/on_deleted_messages.py b/pyrogram/client/methods/decorators/on_deleted_messages.py index 0d87ba5a..86dda587 100644 --- a/pyrogram/client/methods/decorators/on_deleted_messages.py +++ b/pyrogram/client/methods/decorators/on_deleted_messages.py @@ -16,11 +16,10 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Tuple +from typing import Callable import pyrogram from pyrogram.client.filters.filter import Filter -from pyrogram.client.handlers.handler import Handler from ...ext import BaseClient @@ -44,18 +43,15 @@ class OnDeletedMessages(BaseClient): The group identifier, defaults to 0. """ - def decorator(func: callable) -> Tuple[Handler, int]: - if isinstance(func, tuple): - func = func[0].callback + def decorator(func: Callable) -> Callable: + if isinstance(self, pyrogram.Client): + self.add_handler(pyrogram.DeletedMessagesHandler(func, filters), group) + elif isinstance(self, Filter) or self is None: + func.pyrogram_plugin = ( + pyrogram.DeletedMessagesHandler(func, self), + group if filters is None else filters + ) - handler = pyrogram.DeletedMessagesHandler(func, filters) - - if isinstance(self, Filter): - return pyrogram.DeletedMessagesHandler(func, self), group if filters is None else filters - - if self is not None: - self.add_handler(handler, group) - - return handler, group + return func return decorator diff --git a/pyrogram/client/methods/decorators/on_disconnect.py b/pyrogram/client/methods/decorators/on_disconnect.py index 4a514a41..012abd38 100644 --- a/pyrogram/client/methods/decorators/on_disconnect.py +++ b/pyrogram/client/methods/decorators/on_disconnect.py @@ -16,8 +16,9 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Callable + import pyrogram -from pyrogram.client.handlers.handler import Handler from ...ext import BaseClient @@ -28,12 +29,10 @@ class OnDisconnect(BaseClient): This does the same thing as :meth:`~pyrogram.Client.add_handler` using the :obj:`~pyrogram.DisconnectHandler`. """ - def decorator(func: callable) -> Handler: - handler = pyrogram.DisconnectHandler(func) + def decorator(func: Callable) -> Callable: + if isinstance(self, pyrogram.Client): + self.add_handler(pyrogram.DisconnectHandler(func)) - if self is not None: - self.add_handler(handler) - - return handler + return func return decorator diff --git a/pyrogram/client/methods/decorators/on_inline_query.py b/pyrogram/client/methods/decorators/on_inline_query.py index adc65d25..d0f2925b 100644 --- a/pyrogram/client/methods/decorators/on_inline_query.py +++ b/pyrogram/client/methods/decorators/on_inline_query.py @@ -16,11 +16,10 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Tuple +from typing import Callable import pyrogram from pyrogram.client.filters.filter import Filter -from pyrogram.client.handlers.handler import Handler from ...ext import BaseClient @@ -43,18 +42,15 @@ class OnInlineQuery(BaseClient): The group identifier, defaults to 0. """ - def decorator(func: callable) -> Tuple[Handler, int]: - if isinstance(func, tuple): - func = func[0].callback + def decorator(func: Callable) -> Callable: + if isinstance(self, pyrogram.Client): + self.add_handler(pyrogram.InlineQueryHandler(func, filters), group) + elif isinstance(self, Filter) or self is None: + func.pyrogram_plugin = ( + pyrogram.InlineQueryHandler(func, self), + group if filters is None else filters + ) - handler = pyrogram.InlineQueryHandler(func, filters) - - if isinstance(self, Filter): - return pyrogram.InlineQueryHandler(func, self), group if filters is None else filters - - if self is not None: - self.add_handler(handler, group) - - return handler, group + return func return decorator diff --git a/pyrogram/client/methods/decorators/on_message.py b/pyrogram/client/methods/decorators/on_message.py index 758a6831..5640f22c 100644 --- a/pyrogram/client/methods/decorators/on_message.py +++ b/pyrogram/client/methods/decorators/on_message.py @@ -16,11 +16,10 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Tuple +from typing import Callable import pyrogram from pyrogram.client.filters.filter import Filter -from pyrogram.client.handlers.handler import Handler from ...ext import BaseClient @@ -43,18 +42,15 @@ class OnMessage(BaseClient): The group identifier, defaults to 0. """ - def decorator(func: callable) -> Tuple[Handler, int]: - if isinstance(func, tuple): - func = func[0].callback + def decorator(func: Callable) -> Callable: + if isinstance(self, pyrogram.Client): + self.add_handler(pyrogram.MessageHandler(func, filters), group) + elif isinstance(self, Filter) or self is None: + func.pyrogram_plugin = ( + pyrogram.MessageHandler(func, self), + group if filters is None else filters + ) - handler = pyrogram.MessageHandler(func, filters) - - if isinstance(self, Filter): - return pyrogram.MessageHandler(func, self), group if filters is None else filters - - if self is not None: - self.add_handler(handler, group) - - return handler, group + return func return decorator diff --git a/pyrogram/client/methods/decorators/on_poll.py b/pyrogram/client/methods/decorators/on_poll.py index 0ade42c0..24282f28 100644 --- a/pyrogram/client/methods/decorators/on_poll.py +++ b/pyrogram/client/methods/decorators/on_poll.py @@ -16,11 +16,10 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Tuple +from typing import Callable import pyrogram from pyrogram.client.filters.filter import Filter -from pyrogram.client.handlers.handler import Handler from ...ext import BaseClient @@ -43,18 +42,15 @@ class OnPoll(BaseClient): The group identifier, defaults to 0. """ - def decorator(func: callable) -> Tuple[Handler, int]: - if isinstance(func, tuple): - func = func[0].callback + def decorator(func: Callable) -> Callable: + if isinstance(self, pyrogram.Client): + self.add_handler(pyrogram.PollHandler(func, filters), group) + elif isinstance(self, Filter) or self is None: + func.pyrogram_plugin = ( + pyrogram.PollHandler(func, self), + group if filters is None else filters + ) - handler = pyrogram.PollHandler(func, filters) - - if isinstance(self, Filter): - return pyrogram.PollHandler(func, self), group if filters is None else filters - - if self is not None: - self.add_handler(handler, group) - - return handler, group + return func return decorator diff --git a/pyrogram/client/methods/decorators/on_raw_update.py b/pyrogram/client/methods/decorators/on_raw_update.py index 7dff75fa..bbf40c8b 100644 --- a/pyrogram/client/methods/decorators/on_raw_update.py +++ b/pyrogram/client/methods/decorators/on_raw_update.py @@ -16,10 +16,9 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Tuple +from typing import Callable import pyrogram -from pyrogram.client.handlers.handler import Handler from ...ext import BaseClient @@ -37,18 +36,15 @@ class OnRawUpdate(BaseClient): The group identifier, defaults to 0. """ - def decorator(func: callable) -> Tuple[Handler, int]: - if isinstance(func, tuple): - func = func[0].callback + def decorator(func: Callable) -> Callable: + if isinstance(self, pyrogram.Client): + self.add_handler(pyrogram.RawUpdateHandler(func), group) + else: + func.pyrogram_plugin = ( + pyrogram.RawUpdateHandler(func), + group if self is None else group + ) - handler = pyrogram.RawUpdateHandler(func) - - if isinstance(self, int): - return handler, group if self is None else group - - if self is not None: - self.add_handler(handler, group) - - return handler, group + return func return decorator diff --git a/pyrogram/client/methods/decorators/on_user_status.py b/pyrogram/client/methods/decorators/on_user_status.py index 09e037f7..81a83d02 100644 --- a/pyrogram/client/methods/decorators/on_user_status.py +++ b/pyrogram/client/methods/decorators/on_user_status.py @@ -16,11 +16,10 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Tuple +from typing import Callable import pyrogram from pyrogram.client.filters.filter import Filter -from pyrogram.client.handlers.handler import Handler from ...ext import BaseClient @@ -41,18 +40,15 @@ class OnUserStatus(BaseClient): The group identifier, defaults to 0. """ - def decorator(func: callable) -> Tuple[Handler, int]: - if isinstance(func, tuple): - func = func[0].callback + def decorator(func: Callable) -> Callable: + if isinstance(self, pyrogram.Client): + self.add_handler(pyrogram.UserStatusHandler(func, filters), group) + elif isinstance(self, Filter) or self is None: + func.pyrogram_plugin = ( + pyrogram.UserStatusHandler(func, self), + group if filters is None else filters + ) - handler = pyrogram.UserStatusHandler(func, filters) - - if isinstance(self, Filter): - return pyrogram.UserStatusHandler(func, self), group if filters is None else filters - - if self is not None: - self.add_handler(handler, group) - - return handler, group + return func return decorator From 5f2d76ceca82e06e43d7f0d995729f82f7bccbc4 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 23 Jun 2019 01:34:27 +0200 Subject: [PATCH 054/202] Remove outdated note --- docs/source/start/updates.rst | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/docs/source/start/updates.rst b/docs/source/start/updates.rst index 9ac428b3..9b0a5a32 100644 --- a/docs/source/start/updates.rst +++ b/docs/source/start/updates.rst @@ -96,14 +96,3 @@ to do so is by decorating your callback function with the :meth:`~pyrogram.Clien app.run() - - -.. note:: - - Due to how these decorators work in Pyrogram, they will wrap your defined callback function in a tuple consisting of - ``(handler, group)``; this will be the value held by your function identifier (e.g.: *my_function* from the example - above). - - In case, for some reason, you want to get your own function back after it has been decorated, you need to access - ``my_function[0].callback``, that is, the *callback* field of the *handler* object which is the first element in the - tuple, accessed by bracket notation *[0]*. From 745d29dfac686d5cc4f148ec8d441f909ff84ee5 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 23 Jun 2019 02:52:12 +0200 Subject: [PATCH 055/202] Add (and update) a bunch of up/download related errors --- compiler/error/source/400_BAD_REQUEST.tsv | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/compiler/error/source/400_BAD_REQUEST.tsv b/compiler/error/source/400_BAD_REQUEST.tsv index f44f5cb2..d7942e4b 100644 --- a/compiler/error/source/400_BAD_REQUEST.tsv +++ b/compiler/error/source/400_BAD_REQUEST.tsv @@ -12,10 +12,10 @@ PHONE_NUMBER_UNOCCUPIED The phone number is not yet being used USERS_TOO_FEW Not enough users (to create a chat, for example) USERS_TOO_MUCH The maximum number of users has been exceeded (to create a chat, for example) TYPE_CONSTRUCTOR_INVALID The type constructor is invalid -FILE_PART_INVALID The file part number is invalid -FILE_PARTS_INVALID The number of file parts is invalid +FILE_PART_INVALID The file part number is invalid. The value is not between 0 and 2999 +FILE_PARTS_INVALID Invalid number of parts. The value is not between 1 and 3000 FILE_PART_X_MISSING Part {x} of the file is missing from storage -MD5_CHECKSUM_INVALID The MD5 checksums do not match +MD5_CHECKSUM_INVALID The file's checksum did not match the md5_checksum parameter PHOTO_INVALID_DIMENSIONS The photo dimensions are invalid FIELD_NAME_INVALID The field with the name FIELD_NAME is invalid FIELD_NAME_EMPTY The field with the name FIELD_NAME is missing @@ -41,7 +41,7 @@ PERSISTENT_TIMESTAMP_EMPTY The pts is empty CDN_METHOD_INVALID The method can't be used on CDN DCs VOLUME_LOC_NOT_FOUND The volume location can't be found FILE_ID_INVALID The file id is invalid -LOCATION_INVALID The file location is invalid +LOCATION_INVALID The file address is invalid CHAT_ADMIN_REQUIRED The method requires chat admin privileges PHONE_NUMBER_BANNED The phone number is banned ABOUT_TOO_LONG The about text is too long @@ -108,4 +108,9 @@ BROADCAST_ID_INVALID The channel is invalid MEGAGROUP_ID_INVALID The supergroup is invalid BUTTON_DATA_INVALID The button callback data contains invalid data or exceeds 64 bytes START_PARAM_INVALID The start parameter is invalid -ARTICLE_TITLE_EMPTY The article title is empty \ No newline at end of file +ARTICLE_TITLE_EMPTY The article title is empty +FILE_PART_TOO_BIG The size limit (512 KB) for the content of the file part has been exceeded +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 From 4dbdfc85d7c69dd6f16b8a9f54679287516d0e0b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 23 Jun 2019 20:45:19 +0200 Subject: [PATCH 056/202] Update API schema to Layer 102 --- compiler/api/source/main_api.tl | 49 ++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index 16a93420..ff295130 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -105,7 +105,7 @@ channel#4df30834 flags:# creator:flags.0?true left:flags.2?true broadcast:flags. channelForbidden#289da732 flags:# broadcast:flags.5?true megagroup:flags.8?true id:int access_hash:long title:string until_date:flags.16?int = Chat; chatFull#1b7c9db3 flags:# can_set_username:flags.7?true id:int about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int = ChatFull; -channelFull#9882e516 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_view_stats:flags.12?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.13?int pts:int = ChatFull; +channelFull#10916653 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_view_stats:flags.12?true can_set_location:flags.16?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?int location:flags.15?ChannelLocation pts:int = ChatFull; chatParticipant#c8d7493e user_id:int inviter_id:int date:int = ChatParticipant; chatParticipantCreator#da13538a user_id:int = ChatParticipant; @@ -189,7 +189,7 @@ inputPeerNotifySettings#9c3d198e flags:# show_previews:flags.0?Bool silent:flags peerNotifySettings#af509d20 flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int sound:flags.3?string = PeerNotifySettings; -peerSettings#818426cd flags:# report_spam:flags.0?true = PeerSettings; +peerSettings#818426cd flags:# report_spam:flags.0?true add_contact:flags.1?true block_contact:flags.2?true share_contact:flags.3?true need_contacts_exception:flags.4?true report_geo:flags.5?true = PeerSettings; wallPaper#a437c3ed id:long flags:# creator:flags.0?true default:flags.1?true pattern:flags.3?true dark:flags.4?true access_hash:long slug:string document:Document settings:flags.2?WallPaperSettings = WallPaper; @@ -199,8 +199,9 @@ inputReportReasonPornography#2e59d922 = ReportReason; inputReportReasonChildAbuse#adf44ee3 = ReportReason; inputReportReasonOther#e1746d0a text:string = ReportReason; inputReportReasonCopyright#9b89f93a = ReportReason; +inputReportReasonGeoIrrelevant#dbd4feed = ReportReason; -userFull#745559cc flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true user:User about:flags.1?string link:contacts.Link profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int = UserFull; +userFull#edf17c12 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true user:User about:flags.1?string settings:PeerSettings profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int = UserFull; contact#f911c994 user_id:int mutual:Bool = Contact; @@ -210,8 +211,6 @@ contactBlocked#561bc879 user_id:int date:int = ContactBlocked; contactStatus#d3680c61 user_id:int status:UserStatus = ContactStatus; -contacts.link#3ace484c my_link:ContactLink foreign_link:ContactLink user:User = contacts.Link; - contacts.contactsNotModified#b74ba9d2 = contacts.Contacts; contacts.contacts#eae87e42 contacts:Vector saved_count:int users:Vector = contacts.Contacts; @@ -262,7 +261,6 @@ updateChatParticipants#7761198 participants:ChatParticipants = Update; updateUserStatus#1bfbd823 user_id:int status:UserStatus = Update; updateUserName#a7332b73 user_id:int first_name:string last_name:string username:string = Update; updateUserPhoto#95313b0c user_id:int date:int photo:UserProfilePhoto previous:Bool = Update; -updateContactLink#9d2e67c5 user_id:int my_link:ContactLink foreign_link:ContactLink = Update; updateNewEncryptedMessage#12bcbd9a message:EncryptedMessage qts:int = Update; updateEncryptedChatTyping#1710f156 chat_id:int = Update; updateEncryption#b4a2e88d chat:EncryptedChat date:int = Update; @@ -323,6 +321,8 @@ updateChatPinnedMessage#e10db349 chat_id:int id:int version:int = Update; updateMessagePoll#aca1657b flags:# poll_id:long poll:flags.0?Poll results:PollResults = Update; updateChatDefaultBannedRights#54c01850 peer:Peer default_banned_rights:ChatBannedRights version:int = Update; updateFolderPeers#19360dc0 folder_peers:Vector pts:int pts_count:int = Update; +updatePeerSettings#6a7e7366 peer:Peer settings:PeerSettings = Update; +updatePeerLocated#b4afcfb0 peers:Vector = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -468,10 +468,6 @@ messages.allStickers#edfd405f hash:int sets:Vector = messages.AllSti messages.affectedMessages#84d19185 pts:int pts_count:int = messages.AffectedMessages; -contactLinkUnknown#5f4f9247 = ContactLink; -contactLinkNone#feedd3ad = ContactLink; -contactLinkContact#d502c2d0 = ContactLink; - webPageEmpty#eb1477e8 id:long = WebPage; webPagePending#c586da1c id:long date:int = WebPage; webPage#5f07b4bc flags:# id:long url:string display_url:string hash:int type:flags.0?string site_name:flags.1?string title:flags.2?string description:flags.3?string photo:flags.4?Photo embed_url:flags.5?string embed_type:flags.5?string embed_width:flags.6?int embed_height:flags.6?int duration:flags.7?int author:flags.8?string document:flags.9?Document cached_page:flags.10?Page = WebPage; @@ -501,7 +497,7 @@ inputStickerSetEmpty#ffb62b95 = InputStickerSet; inputStickerSetID#9de7a269 id:long access_hash:long = InputStickerSet; inputStickerSetShortName#861cc8a0 short_name:string = InputStickerSet; -stickerSet#eeb46f27 flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumb:flags.4?PhotoSize thumb_dc_id:flags.4?int count:int hash:int = StickerSet; +stickerSet#eeb46f27 flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumb:flags.4?PhotoSize thumb_dc_id:flags.4?int count:int hash:int = StickerSet; messages.stickerSet#b60a24a6 set:StickerSet packs:Vector documents:Vector = messages.StickerSet; @@ -542,6 +538,9 @@ messageEntityMentionName#352dca58 offset:int length:int user_id:int = MessageEnt inputMessageEntityMentionName#208e68c9 offset:int length:int user_id:InputUser = MessageEntity; messageEntityPhone#9b69e34b offset:int length:int = MessageEntity; messageEntityCashtag#4c4e743f offset:int length:int = MessageEntity; +messageEntityUnderline#9c4e7e8b offset:int length:int = MessageEntity; +messageEntityStrike#bf0693d4 offset:int length:int = MessageEntity; +messageEntityBlockquote#20df5d0 offset:int length:int = MessageEntity; inputChannelEmpty#ee8c1e86 = InputChannel; inputChannel#afeb712e channel_id:int access_hash:long = InputChannel; @@ -828,6 +827,7 @@ channelAdminLogEventActionTogglePreHistoryHidden#5f5c95f1 new_value:Bool = Chann channelAdminLogEventActionDefaultBannedRights#2df5fc0a prev_banned_rights:ChatBannedRights new_banned_rights:ChatBannedRights = ChannelAdminLogEventAction; channelAdminLogEventActionStopPoll#8f079643 message:Message = ChannelAdminLogEventAction; channelAdminLogEventActionChangeLinkedChat#a26f881b prev_value:int new_value:int = ChannelAdminLogEventAction; +channelAdminLogEventActionChangeLocation#e6b76ae prev_value:ChannelLocation new_value:ChannelLocation = ChannelAdminLogEventAction; channelAdminLogEvent#3b5a3e40 id:long date:int user_id:int action:ChannelAdminLogEventAction = ChannelAdminLogEvent; @@ -1034,6 +1034,11 @@ urlAuthResultRequest#92d33a0e flags:# request_write_access:flags.0?true bot:User urlAuthResultAccepted#8f8c0e4e url:string = UrlAuthResult; urlAuthResultDefault#a9d6db1f = UrlAuthResult; +channelLocationEmpty#bfb5ad8b = ChannelLocation; +channelLocation#209b82db geo_point:GeoPoint address:string = ChannelLocation; + +peerLocated#ca461b5d peer:Peer expires:int distance:int = PeerLocated; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -1124,8 +1129,7 @@ contacts.getContactIDs#2caa4a42 hash:int = Vector; contacts.getStatuses#c4a353ee = Vector; contacts.getContacts#c023849f hash:int = contacts.Contacts; contacts.importContacts#2c800be5 contacts:Vector = contacts.ImportedContacts; -contacts.deleteContact#8e953744 id:InputUser = contacts.Link; -contacts.deleteContacts#59ab389e id:Vector = Bool; +contacts.deleteContacts#96a0e00 id:Vector = Updates; contacts.deleteByPhones#1013fd9e phones:Vector = Bool; contacts.block#332b49fc id:InputUser = Bool; contacts.unblock#e54100bd id:InputUser = Bool; @@ -1137,6 +1141,9 @@ contacts.resetTopPeerRating#1ae373ac category:TopPeerCategory peer:InputPeer = B contacts.resetSaved#879537f1 = Bool; contacts.getSaved#82f1e39f = Vector; contacts.toggleTopPeers#8514bdda enabled:Bool = Bool; +contacts.addContact#e8f463d0 flags:# add_phone_privacy_exception:flags.0?true id:InputUser first_name:string last_name:string phone:string = Updates; +contacts.acceptContact#f831a20f id:InputUser = Updates; +contacts.getLocated#a356056 geo_point:InputGeoPoint = Updates; messages.getMessages#63c66506 id:Vector = messages.Messages; messages.getDialogs#a0ee3b73 flags:# exclude_pinned:flags.0?true folder_id:flags.1?int offset_date:int offset_id:int offset_peer:InputPeer limit:int hash:int = messages.Dialogs; @@ -1151,7 +1158,6 @@ messages.sendMessage#fa88427a flags:# no_webpage:flags.1?true silent:flags.5?tru messages.sendMedia#b8d1262b flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector = Updates; messages.forwardMessages#708e0195 flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true grouped:flags.9?true from_peer:InputPeer id:Vector random_id:Vector to_peer:InputPeer = Updates; messages.reportSpam#cf1592db peer:InputPeer = Bool; -messages.hideReportSpam#a8f1709b peer:InputPeer = Bool; messages.getPeerSettings#3672e09c peer:InputPeer = PeerSettings; messages.report#bd82b658 peer:InputPeer id:Vector reason:ReportReason = Bool; messages.getChats#3c6aa187 id:Vector = messages.Chats; @@ -1186,7 +1192,7 @@ messages.startBot#e6df7378 bot:InputUser peer:InputPeer random_id:long start_par messages.getMessagesViews#c4c8a55d peer:InputPeer id:Vector increment:Bool = Vector; messages.editChatAdmin#a9e69f2e chat_id:int user_id:InputUser is_admin:Bool = Bool; messages.migrateChat#15a3b8e3 chat_id:int = Updates; -messages.searchGlobal#f79c611 q:string offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; +messages.searchGlobal#bf7225a4 flags:# folder_id:flags.0?int q:string offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; messages.reorderStickerSets#78337739 flags:# masks:flags.0?true order:Vector = Bool; messages.getDocumentByHash#338e2464 sha256:bytes size:int mime_type:string = Document; messages.searchGifs#bf9a776b q:string offset:int = messages.FoundGifs; @@ -1251,6 +1257,7 @@ messages.getEmojiURL#d5b10c26 lang_code:string = EmojiURL; messages.getSearchCounters#732eef00 peer:InputPeer filters:Vector = Vector; messages.requestUrlAuth#e33f5613 peer:InputPeer msg_id:int button_id:int = UrlAuthResult; messages.acceptUrlAuth#f729ea98 flags:# write_allowed:flags.0?true peer:InputPeer msg_id:int button_id:int = UrlAuthResult; +messages.hidePeerSettingsBar#4facb138 peer:InputPeer = Bool; updates.getState#edd4882a = updates.State; updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference; @@ -1299,7 +1306,7 @@ channels.getParticipants#123e05e9 channel:InputChannel filter:ChannelParticipant channels.getParticipant#546dd7a6 channel:InputChannel user_id:InputUser = channels.ChannelParticipant; channels.getChannels#a7f6bbb id:Vector = messages.Chats; channels.getFullChannel#8736a09 channel:InputChannel = messages.ChatFull; -channels.createChannel#f4893d7f flags:# broadcast:flags.0?true megagroup:flags.1?true title:string about:string = Updates; +channels.createChannel#3d5fb10f flags:# broadcast:flags.0?true megagroup:flags.1?true title:string about:string geo_point:flags.2?InputGeoPoint address:flags.2?string = Updates; channels.editAdmin#70f893ba channel:InputChannel user_id:InputUser admin_rights:ChatAdminRights = Updates; channels.editTitle#566decd0 channel:InputChannel title:string = Updates; channels.editPhoto#f12e57c9 channel:InputChannel photo:InputChatPhoto = Updates; @@ -1311,7 +1318,7 @@ channels.inviteToChannel#199f3a6c channel:InputChannel users:Vector = channels.deleteChannel#c0111fe3 channel:InputChannel = Updates; channels.exportMessageLink#ceb77163 channel:InputChannel id:int grouped:Bool = ExportedMessageLink; channels.toggleSignatures#1f69b606 channel:InputChannel enabled:Bool = Updates; -channels.getAdminedPublicChannels#8d8d82d7 = messages.Chats; +channels.getAdminedPublicChannels#f8b036af flags:# by_location:flags.0?true check_limit:flags.1?true = messages.Chats; channels.editBanned#72796912 channel:InputChannel user_id:InputUser banned_rights:ChatBannedRights = Updates; channels.getAdminLog#33ddf480 flags:# channel:InputChannel q:string events_filter:flags.0?ChannelAdminLogEventsFilter admins:flags.1?Vector max_id:long min_id:long limit:int = channels.AdminLogResults; channels.setStickers#ea8ca4f9 channel:InputChannel stickerset:InputStickerSet = Bool; @@ -1320,8 +1327,9 @@ channels.deleteHistory#af369d42 channel:InputChannel max_id:int = Bool; channels.togglePreHistoryHidden#eabbb94c channel:InputChannel enabled:Bool = Updates; channels.getLeftChannels#8341ecc0 offset:int = messages.Chats; channels.getGroupsForDiscussion#f5dad378 = messages.Chats; -channels.getBroadcastsForDiscussion#1a87f304 = messages.Chats; channels.setDiscussionGroup#40582bb2 broadcast:InputChannel group:InputChannel = Bool; +channels.editCreator#8f38cd1f channel:InputChannel user_id:InputUser password:InputCheckPasswordSRP = Updates; +channels.editLocation#58e63f6d channel:InputChannel geo_point:InputGeoPoint address:string = Bool; bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON; bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool; @@ -1356,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 100 - -// Ports -channels.exportInvite#c7560885 channel:InputChannel = ExportedChatInvite; \ No newline at end of file +// LAYER 102 \ No newline at end of file From 0985d7ad9054b7369a00ab5ffa79c527aaaaf82a Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 23 Jun 2019 21:40:52 +0200 Subject: [PATCH 057/202] Enable parsing underline, strikethrough and blockquote text for HTML --- pyrogram/client/style/html.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/pyrogram/client/style/html.py b/pyrogram/client/style/html.py index 9c0a372c..b42114a8 100644 --- a/pyrogram/client/style/html.py +++ b/pyrogram/client/style/html.py @@ -26,6 +26,9 @@ from pyrogram.api.types import ( MessageEntityCode as Code, MessageEntityTextUrl as Url, MessageEntityPre as Pre, + MessageEntityUnderline as Underline, + MessageEntityStrike as Strike, + MessageEntityBlockquote as Blockquote, MessageEntityMentionName as MentionInvalid, InputMessageEntityMentionName as Mention, ) @@ -75,6 +78,12 @@ class HTML: entity = Code(offset=start, length=len(body)) elif style == "pre": entity = Pre(offset=start, length=len(body), language="") + elif style == "u": + entity = Underline(offset=start, length=len(body)) + elif style in ["strike", "s", "del"]: + entity = Strike(offset=start, length=len(body)) + elif style == "blockquote": + entity = Blockquote(offset=start, length=len(body)) else: continue @@ -107,6 +116,12 @@ class HTML: style = "code" elif type == "pre": style = "pre" + elif type == "underline": + style = "u" + elif type == "strike": + style = "s" + elif type == "blockquote": + style = "blockquote" elif type == "text_link": offset += 15 + len(url) message = message[:start] + message[start:].replace( From 978ee4e6a62bff12732298f64695875de7abbf65 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 23 Jun 2019 22:32:45 +0200 Subject: [PATCH 058/202] Add new message entity types: "underline", "strike" and "blockquote" --- pyrogram/client/types/messages_and_media/message_entity.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyrogram/client/types/messages_and_media/message_entity.py b/pyrogram/client/types/messages_and_media/message_entity.py index 420bd914..1c3076a2 100644 --- a/pyrogram/client/types/messages_and_media/message_entity.py +++ b/pyrogram/client/types/messages_and_media/message_entity.py @@ -60,6 +60,9 @@ class MessageEntity(Object): types.MessageEntityItalic.ID: "italic", types.MessageEntityCode.ID: "code", types.MessageEntityPre.ID: "pre", + types.MessageEntityUnderline.ID: "underline", + types.MessageEntityStrike.ID: "strike", + types.MessageEntityBlockquote.ID: "blockquote", types.MessageEntityTextUrl.ID: "text_link", types.MessageEntityMentionName.ID: "text_mention", types.MessageEntityPhone.ID: "phone_number" From 648f37cf6daad9923845369ef8d207d469486f47 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 23 Jun 2019 22:43:11 +0200 Subject: [PATCH 059/202] Add support for underline and strikethrough text via Markdown New delimiters: - ~~strikethrough~~ - --underline-- --- pyrogram/client/style/markdown.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pyrogram/client/style/markdown.py b/pyrogram/client/style/markdown.py index adb86e94..9dded1f3 100644 --- a/pyrogram/client/style/markdown.py +++ b/pyrogram/client/style/markdown.py @@ -26,6 +26,8 @@ from pyrogram.api.types import ( MessageEntityCode as Code, MessageEntityTextUrl as Url, MessageEntityPre as Pre, + MessageEntityUnderline as Underline, + MessageEntityStrike as Strike, MessageEntityMentionName as MentionInvalid, InputMessageEntityMentionName as Mention ) @@ -36,6 +38,8 @@ from . import utils class Markdown: BOLD_DELIMITER = "**" ITALIC_DELIMITER = "__" + UNDERLINE_DELIMITER = "--" + STRIKE_DELIMITER = "~~" CODE_DELIMITER = "`" PRE_DELIMITER = "```" @@ -46,6 +50,8 @@ class Markdown: for i in [ PRE_DELIMITER, CODE_DELIMITER, + STRIKE_DELIMITER, + UNDERLINE_DELIMITER, ITALIC_DELIMITER, BOLD_DELIMITER ] @@ -91,6 +97,10 @@ class Markdown: entity = Bold(offset=start, length=len(body)) elif style == self.ITALIC_DELIMITER: entity = Italic(offset=start, length=len(body)) + elif style == self.UNDERLINE_DELIMITER: + entity = Underline(offset=start, length=len(body)) + elif style == self.STRIKE_DELIMITER: + entity = Strike(offset=start, length=len(body)) elif style == self.CODE_DELIMITER: entity = Code(offset=start, length=len(body)) elif style == self.PRE_DELIMITER: @@ -124,6 +134,10 @@ class Markdown: style = self.BOLD_DELIMITER elif type == "italic": style = self.ITALIC_DELIMITER + elif type == "underline": + style = self.UNDERLINE_DELIMITER + elif type == "strike": + style = self.STRIKE_DELIMITER elif type == "code": style = self.CODE_DELIMITER elif type == "pre": From 8102a7fe82323988f45aaaf32fdb75db1169dfd5 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 24 Jun 2019 01:46:20 +0200 Subject: [PATCH 060/202] Update FUNDING.yml --- .github/FUNDING.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index f34f615a..3437aeae 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,2 +1,2 @@ -github: delivrance +# github: delivrance custom: https://docs.pyrogram.org/support-pyrogram From e7c49c6a1b98148a792beecdafff79e9030a277e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 24 Jun 2019 10:07:28 +0200 Subject: [PATCH 061/202] Revamp HTML and Markdown parsers to allow multiple nested entities --- pyrogram/client/style/html.py | 197 ++++++++++++++---------------- pyrogram/client/style/markdown.py | 158 +++++++----------------- 2 files changed, 137 insertions(+), 218 deletions(-) diff --git a/pyrogram/client/style/html.py b/pyrogram/client/style/html.py index b42114a8..82921f4c 100644 --- a/pyrogram/client/style/html.py +++ b/pyrogram/client/style/html.py @@ -16,127 +16,110 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import html import re from collections import OrderedDict +from html.parser import HTMLParser import pyrogram -from pyrogram.api.types import ( - MessageEntityBold as Bold, - MessageEntityItalic as Italic, - MessageEntityCode as Code, - MessageEntityTextUrl as Url, - MessageEntityPre as Pre, - MessageEntityUnderline as Underline, - MessageEntityStrike as Strike, - MessageEntityBlockquote as Blockquote, - MessageEntityMentionName as MentionInvalid, - InputMessageEntityMentionName as Mention, -) +from pyrogram.api import types from pyrogram.errors import PeerIdInvalid from . import utils -class HTML: - HTML_RE = re.compile(r"<(\w+)(?: href=([\"'])([^<]+)\2)?>([^>]+)") +class Parser(HTMLParser): MENTION_RE = re.compile(r"tg://user\?id=(\d+)") + def __init__(self, client: "pyrogram.BaseClient"): + super().__init__() + + self.client = client + + self.text = "" + self.entities = [] + self.temp_entities = [] + self.tags = [] + + def handle_starttag(self, tag, attrs): + attrs = dict(attrs) + extra = {} + + if tag in ["b", "strong"]: + entity = types.MessageEntityBold + elif tag in ["i", "em"]: + entity = types.MessageEntityItalic + elif tag == "u": + entity = types.MessageEntityUnderline + elif tag in ["s", "del", "strike"]: + entity = types.MessageEntityStrike + elif tag == "blockquote": + entity = types.MessageEntityBlockquote + elif tag == "code": + entity = types.MessageEntityCode + elif tag == "pre": + entity = types.MessageEntityPre + extra["language"] = "" + elif tag == "a": + url = attrs.get("href", "") + + mention = Parser.MENTION_RE.match(url) + + if mention: + user_id = int(mention.group(1)) + + try: + user = self.client.resolve_peer(user_id) + except PeerIdInvalid: + entity = types.MessageEntityMentionName + extra["user_id"] = user_id + else: + entity = types.InputMessageEntityMentionName + extra["user_id"] = user + else: + entity = types.MessageEntityTextUrl + extra["url"] = url + else: + return + + self.tags.append(tag) + self.temp_entities.append(entity(offset=len(self.text), length=0, **extra)) + + def handle_data(self, data): + data = html.unescape(data) + + for entity in self.temp_entities: + entity.length += len(data) + + self.text += data + + def handle_endtag(self, tag): + start_tag = self.tags.pop() + + if start_tag != tag: + line, offset = self.getpos() + offset += 1 + + raise ValueError("Expected end tag , but found at {}:{}".format(start_tag, tag, line, offset)) + + self.entities.append(self.temp_entities.pop()) + + def error(self, message): + pass + + +class HTML: def __init__(self, client: "pyrogram.BaseClient" = None): self.client = client - def parse(self, message: str): - entities = [] - message = utils.add_surrogates(str(message or "")) - offset = 0 + def parse(self, text: str): + text = utils.add_surrogates(str(text or "").strip()) - for match in self.HTML_RE.finditer(message): - start = match.start() - offset - style, url, body = match.group(1, 3, 4) + parser = Parser(self.client) + parser.feed(text) + print(parser.entities) - if url: - mention = self.MENTION_RE.match(url) - - if mention: - user_id = int(mention.group(1)) - - try: - input_user = self.client.resolve_peer(user_id) - except PeerIdInvalid: - input_user = None - - entity = ( - Mention(offset=start, length=len(body), user_id=input_user) - if input_user else MentionInvalid(offset=start, length=len(body), user_id=user_id) - ) - else: - entity = Url(offset=start, length=len(body), url=url) - else: - if style == "b" or style == "strong": - entity = Bold(offset=start, length=len(body)) - elif style == "i" or style == "em": - entity = Italic(offset=start, length=len(body)) - elif style == "code": - entity = Code(offset=start, length=len(body)) - elif style == "pre": - entity = Pre(offset=start, length=len(body), language="") - elif style == "u": - entity = Underline(offset=start, length=len(body)) - elif style in ["strike", "s", "del"]: - entity = Strike(offset=start, length=len(body)) - elif style == "blockquote": - entity = Blockquote(offset=start, length=len(body)) - else: - continue - - entities.append(entity) - message = message.replace(match.group(), body) - offset += len(style) * 2 + 5 + (len(url) + 8 if url else 0) - - # TODO: OrderedDict to be removed in Python3.6 + # TODO: OrderedDict to be removed in Python 3.6 return OrderedDict([ - ("message", utils.remove_surrogates(message)), - ("entities", entities) + ("message", utils.remove_surrogates(parser.text)), + ("entities", parser.entities) ]) - - def unparse(self, message: str, entities: list): - message = utils.add_surrogates(message).strip() - offset = 0 - - for entity in entities: - start = entity.offset + offset - type = entity.type - url = entity.url - user = entity.user - sub = message[start: start + entity.length] - - if type == "bold": - style = "b" - elif type == "italic": - style = "i" - elif type == "code": - style = "code" - elif type == "pre": - style = "pre" - elif type == "underline": - style = "u" - elif type == "strike": - style = "s" - elif type == "blockquote": - style = "blockquote" - elif type == "text_link": - offset += 15 + len(url) - message = message[:start] + message[start:].replace( - sub, "{}".format(url, sub), 1) - continue - elif type == "text_mention": - offset += 28 + len(str(user.id)) - message = message[:start] + message[start:].replace( - sub, "{}".format(user.id, sub), 1) - continue - else: - continue - - offset += len(style) * 2 + 5 - message = message[:start] + message[start:].replace( - sub, "<{0}>{1}".format(style, sub), 1) - - return utils.remove_surrogates(message) diff --git a/pyrogram/client/style/markdown.py b/pyrogram/client/style/markdown.py index 9dded1f3..26effe5c 100644 --- a/pyrogram/client/style/markdown.py +++ b/pyrogram/client/style/markdown.py @@ -17,22 +17,9 @@ # along with Pyrogram. If not, see . import re -from collections import OrderedDict import pyrogram -from pyrogram.api.types import ( - MessageEntityBold as Bold, - MessageEntityItalic as Italic, - MessageEntityCode as Code, - MessageEntityTextUrl as Url, - MessageEntityPre as Pre, - MessageEntityUnderline as Underline, - MessageEntityStrike as Strike, - MessageEntityMentionName as MentionInvalid, - InputMessageEntityMentionName as Mention -) -from pyrogram.errors import PeerIdInvalid -from . import utils +from .html import HTML class Markdown: @@ -43,10 +30,10 @@ class Markdown: CODE_DELIMITER = "`" PRE_DELIMITER = "```" - MARKDOWN_RE = re.compile(r"({d})([\w\W]*?)\1|\[([^[]+?)\]\(([^(]+?)\)".format( + MARKDOWN_RE = re.compile(r"({d})".format( d="|".join( ["".join(i) for i in [ - ["\{}".format(j) for j in i] + [r"\{}".format(j) for j in i] for i in [ PRE_DELIMITER, CODE_DELIMITER, @@ -56,107 +43,56 @@ class Markdown: BOLD_DELIMITER ] ]] - ) - )) - MENTION_RE = re.compile(r"tg://user\?id=(\d+)") + ))) - def __init__(self, client: "pyrogram.BaseClient" = None): - self.client = client + URL_RE = re.compile(r"\[([^[]+)]\(([^(]+)\)") - def parse(self, message: str): - message = utils.add_surrogates(str(message or "")).strip() - entities = [] + def __init__(self, client: "pyrogram.BaseClient"): + self.html = HTML(client) + + def parse(self, text: str): offset = 0 + delimiters = set() - for match in self.MARKDOWN_RE.finditer(message): - start = match.start() - offset - style, body, text, url = match.groups() + for i, match in enumerate(re.finditer(Markdown.MARKDOWN_RE, text)): + start, stop = match.span() + delimiter = match.group(1) - if url: - mention = self.MENTION_RE.match(url) - - if mention: - user_id = int(mention.group(1)) - - try: - input_user = self.client.resolve_peer(user_id) - except PeerIdInvalid: - input_user = None - - entity = ( - Mention(offset=start, length=len(text), user_id=input_user) - if input_user else MentionInvalid(offset=start, length=len(text), user_id=user_id) - ) - else: - entity = Url(offset=start, length=len(text), url=url) - - body = text - offset += len(url) + 4 - else: - if style == self.BOLD_DELIMITER: - entity = Bold(offset=start, length=len(body)) - elif style == self.ITALIC_DELIMITER: - entity = Italic(offset=start, length=len(body)) - elif style == self.UNDERLINE_DELIMITER: - entity = Underline(offset=start, length=len(body)) - elif style == self.STRIKE_DELIMITER: - entity = Strike(offset=start, length=len(body)) - elif style == self.CODE_DELIMITER: - entity = Code(offset=start, length=len(body)) - elif style == self.PRE_DELIMITER: - entity = Pre(offset=start, length=len(body), language="") - else: - continue - - offset += len(style) * 2 - - entities.append(entity) - message = message.replace(match.group(), body) - - # TODO: OrderedDict to be removed in Python3.6 - return OrderedDict([ - ("message", utils.remove_surrogates(message)), - ("entities", entities) - ]) - - def unparse(self, message: str, entities: list): - message = utils.add_surrogates(message).strip() - offset = 0 - - for entity in entities: - start = entity.offset + offset - type = entity.type - url = entity.url - user = entity.user - sub = message[start: start + entity.length] - - if type == "bold": - style = self.BOLD_DELIMITER - elif type == "italic": - style = self.ITALIC_DELIMITER - elif type == "underline": - style = self.UNDERLINE_DELIMITER - elif type == "strike": - style = self.STRIKE_DELIMITER - elif type == "code": - style = self.CODE_DELIMITER - elif type == "pre": - style = self.PRE_DELIMITER - elif type == "text_link": - offset += 4 + len(url) - message = message[:start] + message[start:].replace( - sub, "[{}]({})".format(sub, url), 1) - continue - elif type == "text_mention": - offset += 17 + len(str(user.id)) - message = message[:start] + message[start:].replace( - sub, "[{}](tg://user?id={})".format(sub, user.id), 1) - continue + if delimiter == Markdown.BOLD_DELIMITER: + tag = "b" + elif delimiter == Markdown.ITALIC_DELIMITER: + tag = "i" + elif delimiter == Markdown.UNDERLINE_DELIMITER: + tag = "u" + elif delimiter == Markdown.STRIKE_DELIMITER: + tag = "s" + elif delimiter == Markdown.CODE_DELIMITER: + tag = "code" + elif delimiter == Markdown.PRE_DELIMITER: + tag = "pre" else: continue - offset += len(style) * 2 - message = message[:start] + message[start:].replace( - sub, "{0}{1}{0}".format(style, sub), 1) + if delimiter not in delimiters: + delimiters.add(delimiter) + tag = "<{}>".format(tag) + else: + delimiters.remove(delimiter) + tag = "".format(tag) - return utils.remove_surrogates(message) + text = text[:start + offset] + tag + text[stop + offset:] + + offset += len(tag) - len(delimiter) + + offset = 0 + + for match in re.finditer(Markdown.URL_RE, text): + start, stop = match.span() + full = match.group(0) + body, url = match.groups() + replace = '{}'.format(url, body) + + text = text[:start + offset] + replace + text[stop + offset:] + offset += len(replace) - len(full) + + return self.html.parse(text) From d6900cde9f5adca32fb80624ceb88331af1c0b85 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 24 Jun 2019 10:11:21 +0200 Subject: [PATCH 062/202] Remove debug print() --- pyrogram/client/style/html.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyrogram/client/style/html.py b/pyrogram/client/style/html.py index 82921f4c..81d7ce9b 100644 --- a/pyrogram/client/style/html.py +++ b/pyrogram/client/style/html.py @@ -116,7 +116,6 @@ class HTML: parser = Parser(self.client) parser.feed(text) - print(parser.entities) # TODO: OrderedDict to be removed in Python 3.6 return OrderedDict([ From f12cee5d94f42c4b5d7e669e33e1aa85de9ea0ce Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 24 Jun 2019 10:54:58 +0200 Subject: [PATCH 063/202] Automatically escape URL bodies when using markdown --- pyrogram/client/style/markdown.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pyrogram/client/style/markdown.py b/pyrogram/client/style/markdown.py index 26effe5c..001fc60f 100644 --- a/pyrogram/client/style/markdown.py +++ b/pyrogram/client/style/markdown.py @@ -16,6 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import html import re import pyrogram @@ -89,10 +90,14 @@ class Markdown: for match in re.finditer(Markdown.URL_RE, text): start, stop = match.span() full = match.group(0) + body, url = match.groups() + body = html.escape(body) + replace = '{}'.format(url, body) text = text[:start + offset] + replace + text[stop + offset:] + offset += len(replace) - len(full) return self.html.parse(text) From 8e0182633f8036d23afe6de98e3f391f80a2074c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 24 Jun 2019 13:35:58 +0200 Subject: [PATCH 064/202] Ignore any other style when inside a fixed-width style --- pyrogram/client/style/markdown.py | 66 +++++++++++++++++-------------- 1 file changed, 37 insertions(+), 29 deletions(-) diff --git a/pyrogram/client/style/markdown.py b/pyrogram/client/style/markdown.py index 001fc60f..520008a8 100644 --- a/pyrogram/client/style/markdown.py +++ b/pyrogram/client/style/markdown.py @@ -24,66 +24,76 @@ from .html import HTML class Markdown: - BOLD_DELIMITER = "**" - ITALIC_DELIMITER = "__" - UNDERLINE_DELIMITER = "--" - STRIKE_DELIMITER = "~~" - CODE_DELIMITER = "`" - PRE_DELIMITER = "```" + BOLD_DELIM = "**" + ITALIC_DELIM = "__" + UNDERLINE_DELIM = "--" + STRIKE_DELIM = "~~" + CODE_DELIM = "`" + PRE_DELIM = "```" MARKDOWN_RE = re.compile(r"({d})".format( d="|".join( ["".join(i) for i in [ [r"\{}".format(j) for j in i] for i in [ - PRE_DELIMITER, - CODE_DELIMITER, - STRIKE_DELIMITER, - UNDERLINE_DELIMITER, - ITALIC_DELIMITER, - BOLD_DELIMITER + PRE_DELIM, + CODE_DELIM, + STRIKE_DELIM, + UNDERLINE_DELIM, + ITALIC_DELIM, + BOLD_DELIM ] ]] ))) URL_RE = re.compile(r"\[([^[]+)]\(([^(]+)\)") + OPENING_TAG = "<{}>" + CLOSING_TAG = "" + URL_MARKUP = '{}' + FIXED_WIDTH_DELIMS = [CODE_DELIM, PRE_DELIM] + def __init__(self, client: "pyrogram.BaseClient"): self.html = HTML(client) def parse(self, text: str): + text = html.escape(text) + offset = 0 - delimiters = set() + delims = set() for i, match in enumerate(re.finditer(Markdown.MARKDOWN_RE, text)): start, stop = match.span() - delimiter = match.group(1) + delim = match.group(1) - if delimiter == Markdown.BOLD_DELIMITER: + if delim == Markdown.BOLD_DELIM: tag = "b" - elif delimiter == Markdown.ITALIC_DELIMITER: + elif delim == Markdown.ITALIC_DELIM: tag = "i" - elif delimiter == Markdown.UNDERLINE_DELIMITER: + elif delim == Markdown.UNDERLINE_DELIM: tag = "u" - elif delimiter == Markdown.STRIKE_DELIMITER: + elif delim == Markdown.STRIKE_DELIM: tag = "s" - elif delimiter == Markdown.CODE_DELIMITER: + elif delim == Markdown.CODE_DELIM: tag = "code" - elif delimiter == Markdown.PRE_DELIMITER: + elif delim == Markdown.PRE_DELIM: tag = "pre" else: continue - if delimiter not in delimiters: - delimiters.add(delimiter) - tag = "<{}>".format(tag) + if delim not in Markdown.FIXED_WIDTH_DELIMS and any(x in delims for x in Markdown.FIXED_WIDTH_DELIMS): + continue + + if delim not in delims: + delims.add(delim) + tag = Markdown.OPENING_TAG.format(tag) else: - delimiters.remove(delimiter) - tag = "".format(tag) + delims.remove(delim) + tag = Markdown.CLOSING_TAG.format(tag) text = text[:start + offset] + tag + text[stop + offset:] - offset += len(tag) - len(delimiter) + offset += len(tag) - len(delim) offset = 0 @@ -92,9 +102,7 @@ class Markdown: full = match.group(0) body, url = match.groups() - body = html.escape(body) - - replace = '{}'.format(url, body) + replace = Markdown.URL_MARKUP.format(url, body) text = text[:start + offset] + replace + text[stop + offset:] From cac0bcabf915ff3e7a06a7d7be2a21da257050e5 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 24 Jun 2019 13:36:27 +0200 Subject: [PATCH 065/202] Fix HTML parsing breaking with no tags --- pyrogram/client/style/html.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pyrogram/client/style/html.py b/pyrogram/client/style/html.py index 81d7ce9b..df7c64ff 100644 --- a/pyrogram/client/style/html.py +++ b/pyrogram/client/style/html.py @@ -93,7 +93,10 @@ class Parser(HTMLParser): self.text += data def handle_endtag(self, tag): - start_tag = self.tags.pop() + try: + start_tag = self.tags.pop() + except IndexError: + return if start_tag != tag: line, offset = self.getpos() @@ -113,6 +116,7 @@ class HTML: def parse(self, text: str): text = utils.add_surrogates(str(text or "").strip()) + text = "

{}

".format(text) parser = Parser(self.client) parser.feed(text) From a27dc575e413968f8814a7fb365bf7f2ada9aaa6 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 24 Jun 2019 14:17:46 +0200 Subject: [PATCH 066/202] Actually fix the HTML Parser feeding by calling .close() when done --- pyrogram/client/style/html.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/style/html.py b/pyrogram/client/style/html.py index df7c64ff..1748fa58 100644 --- a/pyrogram/client/style/html.py +++ b/pyrogram/client/style/html.py @@ -116,10 +116,10 @@ class HTML: def parse(self, text: str): text = utils.add_surrogates(str(text or "").strip()) - text = "

{}

".format(text) parser = Parser(self.client) parser.feed(text) + parser.close() # TODO: OrderedDict to be removed in Python 3.6 return OrderedDict([ From e7457de947524a11c9690f98bddd7b2295d08e0d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 24 Jun 2019 14:25:09 +0200 Subject: [PATCH 067/202] Add MSGID_DECREASE_RETRY 5xx-class error --- compiler/error/source/500_INTERNAL_SERVER_ERROR.tsv | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/error/source/500_INTERNAL_SERVER_ERROR.tsv b/compiler/error/source/500_INTERNAL_SERVER_ERROR.tsv index 446fe908..4bbea8ea 100644 --- a/compiler/error/source/500_INTERNAL_SERVER_ERROR.tsv +++ b/compiler/error/source/500_INTERNAL_SERVER_ERROR.tsv @@ -9,4 +9,5 @@ RANDOM_ID_DUPLICATE Telegram is having internal problems. Please try again later WORKER_BUSY_TOO_LONG_RETRY Telegram is having internal problems. Please try again later INTERDC_X_CALL_ERROR Telegram is having internal problems at DC{x}. Please try again later INTERDC_X_CALL_RICH_ERROR Telegram is having internal problems at DC{x}. Please try again later -FOLDER_DEAC_AUTOFIX_ALL Telegram is having internal problems. Please try again later \ No newline at end of file +FOLDER_DEAC_AUTOFIX_ALL Telegram is having internal problems. Please try again later +MSGID_DECREASE_RETRY Telegram is having internal problems. Please try again later \ No newline at end of file From cd1e41b130297d3517c4e3cf0d14f2f81b2801a7 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 24 Jun 2019 14:33:17 +0200 Subject: [PATCH 068/202] Delete style utils.py and move its content inside html.py The HTML parser is now the only one that makes use of those util methods --- pyrogram/client/style/html.py | 23 ++++++++++++++++++--- pyrogram/client/style/utils.py | 37 ---------------------------------- 2 files changed, 20 insertions(+), 40 deletions(-) delete mode 100644 pyrogram/client/style/utils.py diff --git a/pyrogram/client/style/html.py b/pyrogram/client/style/html.py index 1748fa58..9376f793 100644 --- a/pyrogram/client/style/html.py +++ b/pyrogram/client/style/html.py @@ -20,11 +20,11 @@ import html import re from collections import OrderedDict from html.parser import HTMLParser +from struct import unpack import pyrogram from pyrogram.api import types from pyrogram.errors import PeerIdInvalid -from . import utils class Parser(HTMLParser): @@ -111,11 +111,28 @@ class Parser(HTMLParser): class HTML: + # SMP = Supplementary Multilingual Plane: https://en.wikipedia.org/wiki/Plane_(Unicode)#Overview + SMP_RE = re.compile(r"[\U00010000-\U0010FFFF]") + def __init__(self, client: "pyrogram.BaseClient" = None): self.client = client + @staticmethod + def add_surrogates(text): + # Replace each SMP code point with a surrogate pair + return HTML.SMP_RE.sub( + lambda match: # Split SMP in two surrogates + "".join(chr(i) for i in unpack(" -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -import re -from struct import unpack - -# SMP = Supplementary Multilingual Plane: https://en.wikipedia.org/wiki/Plane_(Unicode)#Overview -SMP_RE = re.compile(r"[\U00010000-\U0010FFFF]") - - -def add_surrogates(text): - # Replace each SMP code point with a surrogate pair - return SMP_RE.sub( - lambda match: # Split SMP in two surrogates - "".join(chr(i) for i in unpack(" Date: Tue, 25 Jun 2019 05:47:57 +0200 Subject: [PATCH 069/202] Revert "Delete style utils.py and move its content inside html.py The HTML parser is now the only one that makes use of those util methods" This reverts commit cd1e41b1 --- pyrogram/client/style/html.py | 23 +++------------------ pyrogram/client/style/utils.py | 37 ++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 20 deletions(-) create mode 100644 pyrogram/client/style/utils.py diff --git a/pyrogram/client/style/html.py b/pyrogram/client/style/html.py index 9376f793..1748fa58 100644 --- a/pyrogram/client/style/html.py +++ b/pyrogram/client/style/html.py @@ -20,11 +20,11 @@ import html import re from collections import OrderedDict from html.parser import HTMLParser -from struct import unpack import pyrogram from pyrogram.api import types from pyrogram.errors import PeerIdInvalid +from . import utils class Parser(HTMLParser): @@ -111,28 +111,11 @@ class Parser(HTMLParser): class HTML: - # SMP = Supplementary Multilingual Plane: https://en.wikipedia.org/wiki/Plane_(Unicode)#Overview - SMP_RE = re.compile(r"[\U00010000-\U0010FFFF]") - def __init__(self, client: "pyrogram.BaseClient" = None): self.client = client - @staticmethod - def add_surrogates(text): - # Replace each SMP code point with a surrogate pair - return HTML.SMP_RE.sub( - lambda match: # Split SMP in two surrogates - "".join(chr(i) for i in unpack(" +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +import re +from struct import unpack + +# SMP = Supplementary Multilingual Plane: https://en.wikipedia.org/wiki/Plane_(Unicode)#Overview +SMP_RE = re.compile(r"[\U00010000-\U0010FFFF]") + + +def add_surrogates(text): + # Replace each SMP code point with a surrogate pair + return SMP_RE.sub( + lambda match: # Split SMP in two surrogates + "".join(chr(i) for i in unpack(" Date: Tue, 25 Jun 2019 05:53:41 +0200 Subject: [PATCH 070/202] Make slicing text messages & captions work properly with entity offsets --- pyrogram/client/types/messages_and_media/message.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 52e8f473..34080d7f 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -31,6 +31,7 @@ from ..object import Object from ..update import Update from ..user_and_chats.chat import Chat from ..user_and_chats.user import User +from ...style import utils class Str(str): @@ -58,6 +59,9 @@ class Str(str): def html(self): return self._client.html.unparse(self, self._entities) + def __getitem__(self, item): + return utils.remove_surrogates(utils.add_surrogates(self)[item]) + class Message(Object, Update): """A message. From 07bc7e39df8f9e66514bc78b3b6896e8a0d70d8d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 25 Jun 2019 07:08:38 +0200 Subject: [PATCH 071/202] Allow entities to overlap, like: bold and italic --- pyrogram/client/style/html.py | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/pyrogram/client/style/html.py b/pyrogram/client/style/html.py index 1748fa58..f861412b 100644 --- a/pyrogram/client/style/html.py +++ b/pyrogram/client/style/html.py @@ -37,8 +37,7 @@ class Parser(HTMLParser): self.text = "" self.entities = [] - self.temp_entities = [] - self.tags = [] + self.tag_entities = {} def handle_starttag(self, tag, attrs): attrs = dict(attrs) @@ -81,30 +80,22 @@ class Parser(HTMLParser): else: return - self.tags.append(tag) - self.temp_entities.append(entity(offset=len(self.text), length=0, **extra)) + if tag not in self.tag_entities: + self.tag_entities[tag] = [] + + self.tag_entities[tag].append(entity(offset=len(self.text), length=0, **extra)) def handle_data(self, data): data = html.unescape(data) - for entity in self.temp_entities: - entity.length += len(data) + for entities in self.tag_entities.values(): + for entity in entities: + entity.length += len(data) self.text += data def handle_endtag(self, tag): - try: - start_tag = self.tags.pop() - except IndexError: - return - - if start_tag != tag: - line, offset = self.getpos() - offset += 1 - - raise ValueError("Expected end tag , but found at {}:{}".format(start_tag, tag, line, offset)) - - self.entities.append(self.temp_entities.pop()) + self.entities.append(self.tag_entities[tag].pop()) def error(self, message): pass From a086964e85851b25ae351cca73ac26c496799642 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 25 Jun 2019 07:41:48 +0200 Subject: [PATCH 072/202] Make the HTML parser more sound --- pyrogram/client/style/html.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/pyrogram/client/style/html.py b/pyrogram/client/style/html.py index f861412b..579ed7ec 100644 --- a/pyrogram/client/style/html.py +++ b/pyrogram/client/style/html.py @@ -95,7 +95,16 @@ class Parser(HTMLParser): self.text += data def handle_endtag(self, tag): - self.entities.append(self.tag_entities[tag].pop()) + try: + self.entities.append(self.tag_entities[tag].pop()) + except (KeyError, IndexError): + line, offset = self.getpos() + offset += 1 + + raise ValueError("Unmatched closing tag at line {}:{}".format(tag, line, offset)) + else: + if not self.tag_entities[tag]: + self.tag_entities.pop(tag) def error(self, message): pass @@ -112,6 +121,14 @@ class HTML: parser.feed(text) parser.close() + if parser.tag_entities: + unclosed_tags = [] + + for tag, entities in parser.tag_entities.items(): + unclosed_tags.append("<{}> (x{})".format(tag, len(entities))) + + raise ValueError("Unclosed tags: {}".format(", ".join(unclosed_tags))) + # TODO: OrderedDict to be removed in Python 3.6 return OrderedDict([ ("message", utils.remove_surrogates(parser.text)), From 168fce09da2d5b26f24187970a425ae875a49527 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 25 Jun 2019 10:24:19 +0200 Subject: [PATCH 073/202] Implement HTML.unparse and Markdown.unparse --- pyrogram/client/style/html.py | 44 +++++++++++++++++++ pyrogram/client/style/markdown.py | 72 +++++++++++++++++++++++++------ 2 files changed, 103 insertions(+), 13 deletions(-) diff --git a/pyrogram/client/style/html.py b/pyrogram/client/style/html.py index 579ed7ec..17a5daa6 100644 --- a/pyrogram/client/style/html.py +++ b/pyrogram/client/style/html.py @@ -134,3 +134,47 @@ class HTML: ("message", utils.remove_surrogates(parser.text)), ("entities", parser.entities) ]) + + @staticmethod + def unparse(text: str, entities: list): + text = utils.add_surrogates(text) + copy = text + + for entity in entities: + start = entity.offset + end = start + entity.length + + type = entity.type + + url = entity.url + user = entity.user + + sub = copy[start:end] + + if type == "bold": + style = "b" + elif type == "italic": + style = "i" + elif type == "underline": + style = "u" + elif type == "strike": + style = "s" + elif type == "code": + style = "code" + elif type == "pre": + style = "pre" + elif type == "blockquote": + style = "blockquote" + elif type == "text_link": + text = text[:start] + text[start:].replace(sub, '{}'.format(url, sub), 1) + continue + elif type == "text_mention": + text = text[:start] + text[start:].replace( + sub, '{}'.format(user.id, sub), 1) + continue + else: + continue + + text = text[:start] + text[start:].replace(sub, "<{0}>{1}".format(style, sub), 1) + + return utils.remove_surrogates(text) diff --git a/pyrogram/client/style/markdown.py b/pyrogram/client/style/markdown.py index 520008a8..93d8fc9a 100644 --- a/pyrogram/client/style/markdown.py +++ b/pyrogram/client/style/markdown.py @@ -20,17 +20,18 @@ import html import re import pyrogram +from . import utils from .html import HTML +BOLD_DELIM = "**" +ITALIC_DELIM = "__" +UNDERLINE_DELIM = "--" +STRIKE_DELIM = "~~" +CODE_DELIM = "`" +PRE_DELIM = "```" + class Markdown: - BOLD_DELIM = "**" - ITALIC_DELIM = "__" - UNDERLINE_DELIM = "--" - STRIKE_DELIM = "~~" - CODE_DELIM = "`" - PRE_DELIM = "```" - MARKDOWN_RE = re.compile(r"({d})".format( d="|".join( ["".join(i) for i in [ @@ -66,17 +67,17 @@ class Markdown: start, stop = match.span() delim = match.group(1) - if delim == Markdown.BOLD_DELIM: + if delim == BOLD_DELIM: tag = "b" - elif delim == Markdown.ITALIC_DELIM: + elif delim == ITALIC_DELIM: tag = "i" - elif delim == Markdown.UNDERLINE_DELIM: + elif delim == UNDERLINE_DELIM: tag = "u" - elif delim == Markdown.STRIKE_DELIM: + elif delim == STRIKE_DELIM: tag = "s" - elif delim == Markdown.CODE_DELIM: + elif delim == CODE_DELIM: tag = "code" - elif delim == Markdown.PRE_DELIM: + elif delim == PRE_DELIM: tag = "pre" else: continue @@ -109,3 +110,48 @@ class Markdown: offset += len(replace) - len(full) return self.html.parse(text) + + @staticmethod + def unparse(text: str, entities: list): + text = utils.add_surrogates(text) + copy = text + + for entity in entities: + start = entity.offset + end = start + entity.length + + type = entity.type + + url = entity.url + user = entity.user + + sub = copy[start:end] + + if type == "bold": + style = BOLD_DELIM + elif type == "italic": + style = ITALIC_DELIM + elif type == "underline": + style = UNDERLINE_DELIM + elif type == "strike": + style = STRIKE_DELIM + elif type == "code": + style = CODE_DELIM + elif type == "pre": + style = PRE_DELIM + # TODO: Blockquote for MD + # elif type == "blockquote": + # style = ... + elif type == "text_link": + text = text[:start] + text[start:].replace(sub, '[{1}]({0})'.format(url, sub), 1) + continue + elif type == "text_mention": + text = text[:start] + text[start:].replace( + sub, '[{1}](tg://user?id={0})'.format(user.id, sub), 1) + continue + else: + continue + + text = text[:start] + text[start:].replace(sub, "{0}{1}{0}".format(style, sub), 1) + + return utils.remove_surrogates(text) From 32ca805f6be0295f53ff9d4e20c817ad7183e949 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 25 Jun 2019 10:25:21 +0200 Subject: [PATCH 074/202] Update message.py --- .../types/messages_and_media/message.py | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 34080d7f..16726a47 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -31,33 +31,27 @@ from ..object import Object from ..update import Update from ..user_and_chats.chat import Chat from ..user_and_chats.user import User -from ...style import utils +from ...style import utils, Markdown, HTML class Str(str): def __init__(self, *args): super().__init__() - self._client = None - self._entities = None + self.entities = None - def init(self, client, entities): - self._client = client - self._entities = entities + def init(self, entities): + self.entities = entities return self - @property - def text(self): - return self - @property def markdown(self): - return self._client.markdown.unparse(self, self._entities) + return Markdown.unparse(self, self.entities) @property def html(self): - return self._client.html.unparse(self, self._entities) + return HTML.unparse(self, self.entities) def __getitem__(self, item): return utils.remove_surrogates(utils.add_surrogates(self)[item]) @@ -490,7 +484,7 @@ class Message(Object, Update): if isinstance(message, types.Message): entities = [MessageEntity._parse(client, entity, users) for entity in message.entities] - entities = list(filter(lambda x: x is not None, entities)) + entities = pyrogram.List(filter(lambda x: x is not None, entities)) forward_from = None forward_sender_name = None @@ -607,8 +601,8 @@ 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(client, entities) or None if media is None else None, - caption=Str(message.message).init(client, entities) or None if media is not None else 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, author_signature=message.post_author, From 7490f6cfa3b8b401c00316bdd5a17389a1b6ee01 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 25 Jun 2019 11:47:45 +0200 Subject: [PATCH 075/202] Update the HTML parser: make it easy for asyncio to deal with mentions We can't await coroutines inside HTMLParser overridden methods, such as handle_starttag, because they can't be async. This commit moves the resolve_peer call into the parse method of the HTML class, which can be defined async. --- pyrogram/client/style/html.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/pyrogram/client/style/html.py b/pyrogram/client/style/html.py index 17a5daa6..5617cb54 100644 --- a/pyrogram/client/style/html.py +++ b/pyrogram/client/style/html.py @@ -64,16 +64,8 @@ class Parser(HTMLParser): mention = Parser.MENTION_RE.match(url) if mention: - user_id = int(mention.group(1)) - - try: - user = self.client.resolve_peer(user_id) - except PeerIdInvalid: - entity = types.MessageEntityMentionName - extra["user_id"] = user_id - else: - entity = types.InputMessageEntityMentionName - extra["user_id"] = user + entity = types.InputMessageEntityMentionName + extra["user_id"] = int(mention.group(1)) else: entity = types.MessageEntityTextUrl extra["url"] = url @@ -129,10 +121,21 @@ class HTML: raise ValueError("Unclosed tags: {}".format(", ".join(unclosed_tags))) + entities = [] + + for entity in parser.entities: + if isinstance(entity, types.InputMessageEntityMentionName): + try: + entity.user_id = self.client.resolve_peer(entity.user_id) + except PeerIdInvalid: + continue + + entities.append(entity) + # TODO: OrderedDict to be removed in Python 3.6 return OrderedDict([ ("message", utils.remove_surrogates(parser.text)), - ("entities", parser.entities) + ("entities", entities) ]) @staticmethod From e4a6d16cf3f0f5af28608a8e3032bbc2086a0ee9 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 26 Jun 2019 15:56:08 +0200 Subject: [PATCH 076/202] Remove generated inline query result types --- .../todo/inline_query_result_audio.py | 72 ---------- .../todo/inline_query_result_cached_audio.py | 103 -------------- .../inline_query_result_cached_document.py | 69 ---------- .../todo/inline_query_result_cached_gif.py | 65 --------- .../inline_query_result_cached_mpeg4_gif.py | 65 --------- .../todo/inline_query_result_cached_photo.py | 69 ---------- .../inline_query_result_cached_sticker.py | 52 -------- .../todo/inline_query_result_cached_video.py | 69 ---------- .../todo/inline_query_result_cached_voice.py | 65 --------- .../todo/inline_query_result_contact.py | 78 ----------- .../todo/inline_query_result_document.py | 86 ------------ .../todo/inline_query_result_game.py | 48 ------- .../todo/inline_query_result_gif.py | 82 ------------ .../todo/inline_query_result_location.py | 78 ----------- .../todo/inline_query_result_mpeg4_gif.py | 82 ------------ .../todo/inline_query_result_photo.py | 126 ------------------ .../todo/inline_query_result_venue.py | 86 ------------ .../todo/inline_query_result_video.py | 90 ------------- .../todo/inline_query_result_voice.py | 69 ---------- 19 files changed, 1454 deletions(-) delete mode 100644 pyrogram/client/types/inline_mode/todo/inline_query_result_audio.py delete mode 100644 pyrogram/client/types/inline_mode/todo/inline_query_result_cached_audio.py delete mode 100644 pyrogram/client/types/inline_mode/todo/inline_query_result_cached_document.py delete mode 100644 pyrogram/client/types/inline_mode/todo/inline_query_result_cached_gif.py delete mode 100644 pyrogram/client/types/inline_mode/todo/inline_query_result_cached_mpeg4_gif.py delete mode 100644 pyrogram/client/types/inline_mode/todo/inline_query_result_cached_photo.py delete mode 100644 pyrogram/client/types/inline_mode/todo/inline_query_result_cached_sticker.py delete mode 100644 pyrogram/client/types/inline_mode/todo/inline_query_result_cached_video.py delete mode 100644 pyrogram/client/types/inline_mode/todo/inline_query_result_cached_voice.py delete mode 100644 pyrogram/client/types/inline_mode/todo/inline_query_result_contact.py delete mode 100644 pyrogram/client/types/inline_mode/todo/inline_query_result_document.py delete mode 100644 pyrogram/client/types/inline_mode/todo/inline_query_result_game.py delete mode 100644 pyrogram/client/types/inline_mode/todo/inline_query_result_gif.py delete mode 100644 pyrogram/client/types/inline_mode/todo/inline_query_result_location.py delete mode 100644 pyrogram/client/types/inline_mode/todo/inline_query_result_mpeg4_gif.py delete mode 100644 pyrogram/client/types/inline_mode/todo/inline_query_result_photo.py delete mode 100644 pyrogram/client/types/inline_mode/todo/inline_query_result_venue.py delete mode 100644 pyrogram/client/types/inline_mode/todo/inline_query_result_video.py delete mode 100644 pyrogram/client/types/inline_mode/todo/inline_query_result_voice.py diff --git a/pyrogram/client/types/inline_mode/todo/inline_query_result_audio.py b/pyrogram/client/types/inline_mode/todo/inline_query_result_audio.py deleted file mode 100644 index d5fb954a..00000000 --- a/pyrogram/client/types/inline_mode/todo/inline_query_result_audio.py +++ /dev/null @@ -1,72 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -from pyrogram.client.types.object import Object - - -class InlineQueryResultAudio(Object): - """Represents a link to an mp3 audio file. By default, this audio file will be sent by the user. Alternatively, you can use input_message_content to send a message with the specified content instead of the audio. - - Attributes: - ID: ``0xb0700004`` - - Parameters: - type (``str``): - Type of the result, must be audio. - - id (``str``): - Unique identifier for this result, 1-64 bytes. - - audio_url (``str``): - A valid URL for the audio file. - - title (``str``): - Title. - - caption (``str``, optional): - Caption, 0-200 characters. - - parse_mode (``str``, optional): - Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in the media caption. - - performer (``str``, optional): - Performer. - - audio_duration (``int`` ``32-bit``, optional): - Audio duration in seconds. - - reply_markup (:obj:`InlineKeyboardMarkup`, optional): - Inline keyboard attached to the message. - - input_message_content (:obj:`InputMessageContent`, optional): - Content of the message to be sent instead of the audio. - - """ - - def __init__(self, type: str, id: str, audio_url: str, title: str, caption: str = None, parse_mode: str = None, - performer: str = None, audio_duration: int = None, reply_markup=None, input_message_content=None): - self.type = type # string - self.id = id # string - self.audio_url = audio_url # string - self.title = title # string - self.caption = caption # flags.0?string - self.parse_mode = parse_mode # flags.1?string - self.performer = performer # flags.2?string - self.audio_duration = audio_duration # flags.3?int - self.reply_markup = reply_markup # flags.4?InlineKeyboardMarkup - self.input_message_content = input_message_content # flags.5?InputMessageContent diff --git a/pyrogram/client/types/inline_mode/todo/inline_query_result_cached_audio.py b/pyrogram/client/types/inline_mode/todo/inline_query_result_cached_audio.py deleted file mode 100644 index 47b9bbe2..00000000 --- a/pyrogram/client/types/inline_mode/todo/inline_query_result_cached_audio.py +++ /dev/null @@ -1,103 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -import binascii -import struct - -from pyrogram.api import types -from pyrogram.client.ext import utils, BaseClient -from pyrogram.client.style import HTML, Markdown -from pyrogram.client.types.object import Object -from pyrogram.errors import FileIdInvalid - - -class InlineQueryResultCachedAudio(Object): - """Represents a link to an audio file stored on the Telegram servers. - By default, this audio file will be sent by the user. Alternatively, you can use *input_message_content* to send a - message with the specified content instead of the audio. - - Parameters: - id (``str``): - Unique identifier for this result, 1-64 bytes. - - audio_file_id (``str``): - A valid file identifier for the audio file. - - caption (``str``, *optional*): - Caption, 0-200 characters. - - parse_mode (``str``, *optional*): - Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in - the media caption. - - reply_markup (:obj:`InlineKeyboardMarkup `, *optional*): - Inline keyboard attached to the message. - - input_message_content (:obj:`InputMessageContent `, *optional*): - Content of the message to be sent instead of the audio. - - """ - - def __init__( - self, - id: str, - audio_file_id: str, - caption: str = "", - parse_mode: str = "", - reply_markup=None, - input_message_content=None - ): - self.id = id - self.audio_file_id = audio_file_id - self.caption = caption - self.parse_mode = parse_mode - self.reply_markup = reply_markup - self.input_message_content = input_message_content - - self.style = HTML() if parse_mode.lower() == "html" else Markdown() - - def write(self): - try: - decoded = utils.decode(self.audio_file_id) - fmt = " 24 else " -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -from pyrogram.client.types.object import Object - - -class InlineQueryResultCachedDocument(Object): - """Represents a link to a file stored on the Telegram servers. By default, this file will be sent by the user with an optional caption. Alternatively, you can use input_message_content to send a message with the specified content instead of the file. - - Attributes: - ID: ``0xb0700015`` - - Parameters: - type (``str``): - Type of the result, must be document. - - id (``str``): - Unique identifier for this result, 1-64 bytes. - - title (``str``): - Title for the result. - - document_file_id (``str``): - A valid file identifier for the file. - - description (``str``, optional): - Short description of the result. - - caption (``str``, optional): - Caption of the document to be sent, 0-200 characters. - - parse_mode (``str``, optional): - Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in the media caption. - - reply_markup (:obj:`InlineKeyboardMarkup`, optional): - Inline keyboard attached to the message. - - input_message_content (:obj:`InputMessageContent`, optional): - Content of the message to be sent instead of the file. - - """ - ID = 0xb0700015 - - def __init__(self, type: str, id: str, title: str, document_file_id: str, description: str = None, - caption: str = None, parse_mode: str = None, reply_markup=None, input_message_content=None): - self.type = type # string - self.id = id # string - self.title = title # string - self.document_file_id = document_file_id # string - self.description = description # flags.0?string - self.caption = caption # flags.1?string - self.parse_mode = parse_mode # flags.2?string - self.reply_markup = reply_markup # flags.3?InlineKeyboardMarkup - self.input_message_content = input_message_content # flags.4?InputMessageContent diff --git a/pyrogram/client/types/inline_mode/todo/inline_query_result_cached_gif.py b/pyrogram/client/types/inline_mode/todo/inline_query_result_cached_gif.py deleted file mode 100644 index 28a3595b..00000000 --- a/pyrogram/client/types/inline_mode/todo/inline_query_result_cached_gif.py +++ /dev/null @@ -1,65 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -from pyrogram.client.types.object import Object - - -class InlineQueryResultCachedGif(Object): - """Represents a link to an animated GIF file stored on the Telegram servers. By default, this animated GIF file will be sent by the user with an optional caption. Alternatively, you can use input_message_content to send a message with specified content instead of the animation. - - Attributes: - ID: ``0xb0700012`` - - Parameters: - type (``str``): - Type of the result, must be gif. - - id (``str``): - Unique identifier for this result, 1-64 bytes. - - gif_file_id (``str``): - A valid file identifier for the GIF file. - - title (``str``, optional): - Title for the result. - - caption (``str``, optional): - Caption of the GIF file to be sent, 0-200 characters. - - parse_mode (``str``, optional): - Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in the media caption. - - reply_markup (:obj:`InlineKeyboardMarkup`, optional): - Inline keyboard attached to the message. - - input_message_content (:obj:`InputMessageContent`, optional): - Content of the message to be sent instead of the GIF animation. - - """ - ID = 0xb0700012 - - def __init__(self, type: str, id: str, gif_file_id: str, title: str = None, caption: str = None, - parse_mode: str = None, reply_markup=None, input_message_content=None): - self.type = type # string - self.id = id # string - self.gif_file_id = gif_file_id # string - self.title = title # flags.0?string - self.caption = caption # flags.1?string - self.parse_mode = parse_mode # flags.2?string - self.reply_markup = reply_markup # flags.3?InlineKeyboardMarkup - self.input_message_content = input_message_content # flags.4?InputMessageContent diff --git a/pyrogram/client/types/inline_mode/todo/inline_query_result_cached_mpeg4_gif.py b/pyrogram/client/types/inline_mode/todo/inline_query_result_cached_mpeg4_gif.py deleted file mode 100644 index 95ab03a0..00000000 --- a/pyrogram/client/types/inline_mode/todo/inline_query_result_cached_mpeg4_gif.py +++ /dev/null @@ -1,65 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -from pyrogram.client.types.object import Object - - -class InlineQueryResultCachedMpeg4Gif(Object): - """Represents a link to a video animation (H.264/MPEG-4 AVC video without sound) stored on the Telegram servers. By default, this animated MPEG-4 file will be sent by the user with an optional caption. Alternatively, you can use input_message_content to send a message with the specified content instead of the animation. - - Attributes: - ID: ``0xb0700013`` - - Parameters: - type (``str``): - Type of the result, must be mpeg4_gif. - - id (``str``): - Unique identifier for this result, 1-64 bytes. - - mpeg4_file_id (``str``): - A valid file identifier for the MP4 file. - - title (``str``, optional): - Title for the result. - - caption (``str``, optional): - Caption of the MPEG-4 file to be sent, 0-200 characters. - - parse_mode (``str``, optional): - Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in the media caption. - - reply_markup (:obj:`InlineKeyboardMarkup `, optional): - Inline keyboard attached to the message. - - input_message_content (:obj:`InputMessageContent`, optional): - Content of the message to be sent instead of the video animation. - - """ - ID = 0xb0700013 - - def __init__(self, type: str, id: str, mpeg4_file_id: str, title: str = None, caption: str = None, - parse_mode: str = None, reply_markup=None, input_message_content=None): - self.type = type # string - self.id = id # string - self.mpeg4_file_id = mpeg4_file_id # string - self.title = title # flags.0?string - self.caption = caption # flags.1?string - self.parse_mode = parse_mode # flags.2?string - self.reply_markup = reply_markup # flags.3?InlineKeyboardMarkup - self.input_message_content = input_message_content # flags.4?InputMessageContent diff --git a/pyrogram/client/types/inline_mode/todo/inline_query_result_cached_photo.py b/pyrogram/client/types/inline_mode/todo/inline_query_result_cached_photo.py deleted file mode 100644 index 22793cef..00000000 --- a/pyrogram/client/types/inline_mode/todo/inline_query_result_cached_photo.py +++ /dev/null @@ -1,69 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -from pyrogram.client.types.object import Object - - -class InlineQueryResultCachedPhoto(Object): - """Represents a link to a photo stored on the Telegram servers. By default, this photo will be sent by the user with an optional caption. Alternatively, you can use input_message_content to send a message with the specified content instead of the photo. - - Attributes: - ID: ``0xb0700011`` - - Parameters: - type (``str``): - Type of the result, must be photo. - - id (``str``): - Unique identifier for this result, 1-64 bytes. - - photo_file_id (``str``): - A valid file identifier of the photo. - - 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-200 characters. - - parse_mode (``str``, optional): - Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in the media caption. - - reply_markup (:obj:`InlineKeyboardMarkup `, optional): - Inline keyboard attached to the message. - - input_message_content (:obj:`InputMessageContent `, optional): - Content of the message to be sent instead of the photo. - - """ - ID = 0xb0700011 - - def __init__(self, type: str, id: str, photo_file_id: str, title: str = None, description: str = None, - caption: str = None, parse_mode: str = None, reply_markup=None, input_message_content=None): - self.type = type # string - self.id = id # string - self.photo_file_id = photo_file_id # string - self.title = title # flags.0?string - self.description = description # flags.1?string - self.caption = caption # flags.2?string - self.parse_mode = parse_mode # flags.3?string - self.reply_markup = reply_markup # flags.4?InlineKeyboardMarkup - self.input_message_content = input_message_content # flags.5?InputMessageContent diff --git a/pyrogram/client/types/inline_mode/todo/inline_query_result_cached_sticker.py b/pyrogram/client/types/inline_mode/todo/inline_query_result_cached_sticker.py deleted file mode 100644 index 6b2b37c9..00000000 --- a/pyrogram/client/types/inline_mode/todo/inline_query_result_cached_sticker.py +++ /dev/null @@ -1,52 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -from pyrogram.client.types.object import Object - - -class InlineQueryResultCachedSticker(Object): - """Represents a link to a sticker stored on the Telegram servers. By default, this sticker will be sent by the user. Alternatively, you can use input_message_content to send a message with the specified content instead of the sticker. - - Attributes: - ID: ``0xb0700014`` - - Parameters: - type (``str``): - Type of the result, must be sticker. - - id (``str``): - Unique identifier for this result, 1-64 bytes. - - sticker_file_id (``str``): - A valid file identifier of the sticker. - - reply_markup (:obj:`InlineKeyboardMarkup`, optional): - Inline keyboard attached to the message. - - input_message_content (:obj:`InputMessageContent`, optional): - Content of the message to be sent instead of the sticker. - - """ - ID = 0xb0700014 - - def __init__(self, type: str, id: str, sticker_file_id: str, reply_markup=None, input_message_content=None): - self.type = type # string - self.id = id # string - self.sticker_file_id = sticker_file_id # string - self.reply_markup = reply_markup # flags.0?InlineKeyboardMarkup - self.input_message_content = input_message_content # flags.1?InputMessageContent diff --git a/pyrogram/client/types/inline_mode/todo/inline_query_result_cached_video.py b/pyrogram/client/types/inline_mode/todo/inline_query_result_cached_video.py deleted file mode 100644 index 77dcd6dd..00000000 --- a/pyrogram/client/types/inline_mode/todo/inline_query_result_cached_video.py +++ /dev/null @@ -1,69 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -from pyrogram.client.types.object import Object - - -class InlineQueryResultCachedVideo(Object): - """Represents a link to a video file stored on the Telegram servers. By default, this video file will be sent by the user with an optional caption. Alternatively, you can use input_message_content to send a message with the specified content instead of the video. - - Attributes: - ID: ``0xb0700016`` - - Parameters: - type (``str``): - Type of the result, must be video. - - id (``str``): - Unique identifier for this result, 1-64 bytes. - - video_file_id (``str``): - A valid file identifier for the video file. - - title (``str``): - Title for the result. - - description (``str``, optional): - Short description of the result. - - caption (``str``, optional): - Caption of the video to be sent, 0-200 characters. - - parse_mode (``str``, optional): - Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in the media caption. - - reply_markup (:obj:`InlineKeyboardMarkup `, optional): - Inline keyboard attached to the message. - - input_message_content (:obj:`InputMessageContent `, optional): - Content of the message to be sent instead of the video. - - """ - ID = 0xb0700016 - - def __init__(self, type: str, id: str, video_file_id: str, title: str, description: str = None, caption: str = None, - parse_mode: str = None, reply_markup=None, input_message_content=None): - self.type = type # string - self.id = id # string - self.video_file_id = video_file_id # string - self.title = title # string - self.description = description # flags.0?string - self.caption = caption # flags.1?string - self.parse_mode = parse_mode # flags.2?string - self.reply_markup = reply_markup # flags.3?InlineKeyboardMarkup - self.input_message_content = input_message_content # flags.4?InputMessageContent diff --git a/pyrogram/client/types/inline_mode/todo/inline_query_result_cached_voice.py b/pyrogram/client/types/inline_mode/todo/inline_query_result_cached_voice.py deleted file mode 100644 index a80d5a20..00000000 --- a/pyrogram/client/types/inline_mode/todo/inline_query_result_cached_voice.py +++ /dev/null @@ -1,65 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -from pyrogram.client.types.object import Object - - -class InlineQueryResultCachedVoice(Object): - """Represents a link to a voice message stored on the Telegram servers. By default, this voice message will be sent by the user. Alternatively, you can use input_message_content to send a message with the specified content instead of the voice message. - - Attributes: - ID: ``0xb0700017`` - - Parameters: - type (``str``): - Type of the result, must be voice. - - id (``str``): - Unique identifier for this result, 1-64 bytes. - - voice_file_id (``str``): - A valid file identifier for the voice message. - - title (``str``): - Voice message title. - - caption (``str``, optional): - Caption, 0-200 characters. - - parse_mode (``str``, optional): - Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in the media caption. - - reply_markup (:obj:`InlineKeyboardMarkup`, optional): - Inline keyboard attached to the message. - - input_message_content (:obj:`InputMessageContent`, optional): - Content of the message to be sent instead of the voice message. - - """ - ID = 0xb0700017 - - def __init__(self, type: str, id: str, voice_file_id: str, title: str, caption: str = None, parse_mode: str = None, - reply_markup=None, input_message_content=None): - self.type = type # string - self.id = id # string - self.voice_file_id = voice_file_id # string - self.title = title # string - self.caption = caption # flags.0?string - self.parse_mode = parse_mode # flags.1?string - self.reply_markup = reply_markup # flags.2?InlineKeyboardMarkup - self.input_message_content = input_message_content # flags.3?InputMessageContent diff --git a/pyrogram/client/types/inline_mode/todo/inline_query_result_contact.py b/pyrogram/client/types/inline_mode/todo/inline_query_result_contact.py deleted file mode 100644 index afddb9ec..00000000 --- a/pyrogram/client/types/inline_mode/todo/inline_query_result_contact.py +++ /dev/null @@ -1,78 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -from pyrogram.client.types.object import Object - - -class InlineQueryResultContact(Object): - """Represents a contact with a phone number. By default, this contact will be sent by the user. Alternatively, you can use input_message_content to send a message with the specified content instead of the contact. - - Attributes: - ID: ``0xb0700009`` - - Parameters: - type (``str``): - Type of the result, must be contact. - - id (``str``): - Unique identifier for this result, 1-64 Bytes. - - phone_number (``str``): - Contact's phone number. - - first_name (``str``): - Contact's first name. - - last_name (``str``, optional): - Contact's last name. - - vcard (``str``, optional): - Additional data about the contact in the form of a vCard, 0-2048 bytes. - - reply_markup (:obj:`InlineKeyboardMarkup `, optional): - Inline keyboard attached to the message. - - input_message_content (:obj:`InputMessageContent `, optional): - Content of the message to be sent instead of the contact. - - thumb_url (``str``, optional): - Url of the thumbnail for the result. - - thumb_width (``int`` ``32-bit``, optional): - Thumbnail width. - - thumb_height (``int`` ``32-bit``, optional): - Thumbnail height. - - """ - ID = 0xb0700009 - - def __init__(self, type: str, id: str, phone_number: str, first_name: str, last_name: str = None, vcard: str = None, - reply_markup=None, input_message_content=None, thumb_url: str = None, thumb_width: int = None, - thumb_height: int = None): - self.type = type # string - self.id = id # string - self.phone_number = phone_number # string - self.first_name = first_name # string - self.last_name = last_name # flags.0?string - self.vcard = vcard # flags.1?string - self.reply_markup = reply_markup # flags.2?InlineKeyboardMarkup - self.input_message_content = input_message_content # flags.3?InputMessageContent - self.thumb_url = thumb_url # flags.4?string - self.thumb_width = thumb_width # flags.5?int - self.thumb_height = thumb_height # flags.6?int diff --git a/pyrogram/client/types/inline_mode/todo/inline_query_result_document.py b/pyrogram/client/types/inline_mode/todo/inline_query_result_document.py deleted file mode 100644 index 370dc3c6..00000000 --- a/pyrogram/client/types/inline_mode/todo/inline_query_result_document.py +++ /dev/null @@ -1,86 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -from pyrogram.client.types.object import Object - - -class InlineQueryResultDocument(Object): - """Represents a link to a file. By default, this file will be sent by the user with an optional caption. Alternatively, you can use input_message_content to send a message with the specified content instead of the file. Currently, only .PDF and .ZIP files can be sent using this method. - - Attributes: - ID: ``0xb0700006`` - - Parameters: - type (``str``): - Type of the result, must be document. - - id (``str``): - Unique identifier for this result, 1-64 bytes. - - title (``str``): - Title for the result. - - document_url (``str``, optional): - Caption of the document to be sent, 0-200 characters. - - mime_type (``str``, optional): - Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in the media caption. - - caption (``str``): - A valid URL for the file. - - parse_mode (``str``): - Mime type of the content of the file, either "application/pdf" or "application/zip". - - description (``str``, optional): - Short description of the result. - - reply_markup (:obj:`InlineKeyboardMarkup`, optional): - Inline keyboard attached to the message. - - input_message_content (:obj:`InputMessageContent`, optional): - Content of the message to be sent instead of the file. - - thumb_url (``str``, optional): - URL of the thumbnail (jpeg only) for the file. - - thumb_width (``int`` ``32-bit``, optional): - Thumbnail width. - - thumb_height (``int`` ``32-bit``, optional): - Thumbnail height. - - """ - ID = 0xb0700006 - - def __init__(self, type: str, id: str, title: str, document_url: str, mime_type: str, caption: str = None, - parse_mode: str = None, description: str = None, reply_markup=None, input_message_content=None, - thumb_url: str = None, thumb_width: int = None, thumb_height: int = None): - self.type = type # string - self.id = id # string - self.title = title # string - self.caption = caption # flags.0?string - self.parse_mode = parse_mode # flags.1?string - self.document_url = document_url # string - self.mime_type = mime_type # string - self.description = description # flags.2?string - self.reply_markup = reply_markup # flags.3?InlineKeyboardMarkup - self.input_message_content = input_message_content # flags.4?InputMessageContent - self.thumb_url = thumb_url # flags.5?string - self.thumb_width = thumb_width # flags.6?int - self.thumb_height = thumb_height # flags.7?int diff --git a/pyrogram/client/types/inline_mode/todo/inline_query_result_game.py b/pyrogram/client/types/inline_mode/todo/inline_query_result_game.py deleted file mode 100644 index bd6f25d2..00000000 --- a/pyrogram/client/types/inline_mode/todo/inline_query_result_game.py +++ /dev/null @@ -1,48 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -from pyrogram.client.types.object import Object - - -class InlineQueryResultGame(Object): - """Represents a Game. - - Attributes: - ID: ``0xb0700010`` - - Parameters: - type (``str``): - Type of the result, must be game. - - id (``str``): - Unique identifier for this result, 1-64 bytes. - - game_short_name (``str``): - Short name of the game. - - reply_markup (:obj:`InlineKeyboardMarkup`, optional): - Inline keyboard attached to the message. - - """ - ID = 0xb0700010 - - def __init__(self, type: str, id: str, game_short_name: str, reply_markup=None): - self.type = type # string - self.id = id # string - self.game_short_name = game_short_name # string - self.reply_markup = reply_markup # flags.0?InlineKeyboardMarkup diff --git a/pyrogram/client/types/inline_mode/todo/inline_query_result_gif.py b/pyrogram/client/types/inline_mode/todo/inline_query_result_gif.py deleted file mode 100644 index 56817d76..00000000 --- a/pyrogram/client/types/inline_mode/todo/inline_query_result_gif.py +++ /dev/null @@ -1,82 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -from pyrogram.client.types.object import Object - - -class InlineQueryResultGif(Object): - """Represents a 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. - - Attributes: - ID: ``0xb0700001`` - - Parameters: - type (``str``): - Type of the result, must be gif. - - id (``str``): - Unique identifier for this result, 1-64 bytes. - - gif_url (``str``): - A valid URL for the GIF file. File size must not exceed 1MB. - - thumb_url (``str``, optional): - Width of the GIF. - - gif_width (``int`` ``32-bit``, optional): - Height of the GIF. - - gif_height (``int`` ``32-bit``, optional): - Duration of the GIF. - - gif_duration (``int`` ``32-bit``): - URL of the static thumbnail for the result (jpeg or gif). - - title (``str``, optional): - Title for the result. - - caption (``str``, optional): - Caption of the GIF file to be sent, 0-200 characters. - - parse_mode (``str``, optional): - Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in the media caption. - - reply_markup (:obj:`InlineKeyboardMarkup `, optional): - Inline keyboard attached to the message. - - input_message_content (:obj:`InputMessageContent `, optional): - Content of the message to be sent instead of the GIF animation. - - """ - ID = 0xb0700001 - - def __init__(self, type: str, id: str, gif_url: str, thumb_url: str, gif_width: int = None, gif_height: int = None, - gif_duration: int = None, title: str = None, caption: str = None, parse_mode: str = None, - reply_markup=None, input_message_content=None): - self.type = type # string - self.id = id # string - self.gif_url = gif_url # string - self.gif_width = gif_width # flags.0?int - self.gif_height = gif_height # flags.1?int - self.gif_duration = gif_duration # flags.2?int - self.thumb_url = thumb_url # string - self.title = title # flags.3?string - self.caption = caption # flags.4?string - self.parse_mode = parse_mode # flags.5?string - self.reply_markup = reply_markup # flags.6?InlineKeyboardMarkup - self.input_message_content = input_message_content # flags.7?InputMessageContent diff --git a/pyrogram/client/types/inline_mode/todo/inline_query_result_location.py b/pyrogram/client/types/inline_mode/todo/inline_query_result_location.py deleted file mode 100644 index 74c63ede..00000000 --- a/pyrogram/client/types/inline_mode/todo/inline_query_result_location.py +++ /dev/null @@ -1,78 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -from pyrogram.client.types.object import Object - - -class InlineQueryResultLocation(Object): - """Represents a location on a map. By default, the location will be sent by the user. Alternatively, you can use input_message_content to send a message with the specified content instead of the location. - - Attributes: - ID: ``0xb0700007`` - - Parameters: - type (``str``): - Type of the result, must be location. - - id (``str``): - Unique identifier for this result, 1-64 Bytes. - - latitude (``float`` ``64-bit``): - Location latitude in degrees. - - longitude (``float`` ``64-bit``): - Location longitude in degrees. - - title (``str``): - Location title. - - live_period (``int`` ``32-bit``, optional): - Period in seconds for which the location can be updated, should be between 60 and 86400. - - reply_markup (:obj:`InlineKeyboardMarkup `, optional): - Inline keyboard attached to the message. - - input_message_content (:obj:`InputMessageContent `, optional): - Content of the message to be sent instead of the location. - - thumb_url (``str``, optional): - Url of the thumbnail for the result. - - thumb_width (``int`` ``32-bit``, optional): - Thumbnail width. - - thumb_height (``int`` ``32-bit``, optional): - Thumbnail height. - - """ - ID = 0xb0700007 - - def __init__(self, type: str, id: str, latitude: float, longitude: float, title: str, live_period: int = None, - reply_markup=None, input_message_content=None, thumb_url: str = None, thumb_width: int = None, - thumb_height: int = None): - self.type = type # string - self.id = id # string - self.latitude = latitude # double - self.longitude = longitude # double - self.title = title # string - self.live_period = live_period # flags.0?int - self.reply_markup = reply_markup # flags.1?InlineKeyboardMarkup - self.input_message_content = input_message_content # flags.2?InputMessageContent - self.thumb_url = thumb_url # flags.3?string - self.thumb_width = thumb_width # flags.4?int - self.thumb_height = thumb_height # flags.5?int diff --git a/pyrogram/client/types/inline_mode/todo/inline_query_result_mpeg4_gif.py b/pyrogram/client/types/inline_mode/todo/inline_query_result_mpeg4_gif.py deleted file mode 100644 index e4da6b89..00000000 --- a/pyrogram/client/types/inline_mode/todo/inline_query_result_mpeg4_gif.py +++ /dev/null @@ -1,82 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -from pyrogram.client.types.object import Object - - -class InlineQueryResultMpeg4Gif(Object): - """Represents a link to a video animation (H.264/MPEG-4 AVC video without sound). By default, this animated MPEG-4 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. - - Attributes: - ID: ``0xb0700002`` - - Parameters: - type (``str``): - Type of the result, must be mpeg4_gif. - - id (``str``): - Unique identifier for this result, 1-64 bytes. - - mpeg4_url (``str``): - A valid URL for the MP4 file. File size must not exceed 1MB. - - thumb_url (``str``, optional): - Video width. - - mpeg4_width (``int`` ``32-bit``, optional): - Video height. - - mpeg4_height (``int`` ``32-bit``, optional): - Video duration. - - mpeg4_duration (``int`` ``32-bit``): - URL of the static thumbnail (jpeg or gif) for the result. - - title (``str``, optional): - Title for the result. - - caption (``str``, optional): - Caption of the MPEG-4 file to be sent, 0-200 characters. - - parse_mode (``str``, optional): - Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in the media caption. - - reply_markup (:obj:`InlineKeyboardMarkup `, optional): - Inline keyboard attached to the message. - - input_message_content (:obj:`InputMessageContent `, optional): - Content of the message to be sent instead of the video animation. - - """ - ID = 0xb0700002 - - def __init__(self, type: str, id: str, mpeg4_url: str, thumb_url: str, mpeg4_width: int = None, - mpeg4_height: int = None, mpeg4_duration: int = None, title: str = None, caption: str = None, - parse_mode: str = None, reply_markup=None, input_message_content=None): - self.type = type # string - self.id = id # string - self.mpeg4_url = mpeg4_url # string - self.mpeg4_width = mpeg4_width # flags.0?int - self.mpeg4_height = mpeg4_height # flags.1?int - self.mpeg4_duration = mpeg4_duration # flags.2?int - self.thumb_url = thumb_url # string - self.title = title # flags.3?string - self.caption = caption # flags.4?string - self.parse_mode = parse_mode # flags.5?string - self.reply_markup = reply_markup # flags.6?InlineKeyboardMarkup - self.input_message_content = input_message_content # flags.7?InputMessageContent diff --git a/pyrogram/client/types/inline_mode/todo/inline_query_result_photo.py b/pyrogram/client/types/inline_mode/todo/inline_query_result_photo.py deleted file mode 100644 index 570bd55d..00000000 --- a/pyrogram/client/types/inline_mode/todo/inline_query_result_photo.py +++ /dev/null @@ -1,126 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -from pyrogram.api import types -from pyrogram.client.style import HTML, Markdown -from pyrogram.client.types.object import Object - - -class InlineQueryResultPhoto(Object): - """Represents a 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: - id (``str``): - Unique identifier for this result, 1-64 bytes. - - photo_url (``str``): - A valid URL of the photo. Photo must be in jpeg format. Photo size must not exceed 5MB. - - thumb_url (``str``): - URL of the thumbnail for the photo. - - photo_width (``int``, *optional*): - Width of the photo. - - photo_height (``int``, *optional*): - Height of the photo. - - 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-200 characters. - - parse_mode (``str``, *optional*): - Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in - the media caption. - - reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): - Inline keyboard attached to the message. - - input_message_content (:obj:`InputMessageContent`, *optional*): - Content of the message to be sent instead of the photo. - - """ - - def __init__( - self, - id: str, - photo_url: str, - thumb_url: str, - photo_width: int = 0, - photo_height: int = 0, - title: str = None, - description: str = None, - caption: str = "", - parse_mode: str = "", - reply_markup=None, - input_message_content=None - ): - self.id = id # string - self.photo_url = photo_url # string - self.thumb_url = thumb_url # string - self.photo_width = photo_width # flags.0?int - self.photo_height = photo_height # flags.1?int - self.title = title # flags.2?string - self.description = description # flags.3?string - self.caption = caption # flags.4?string - self.parse_mode = parse_mode # flags.5?string - self.reply_markup = reply_markup # flags.6?InlineKeyboardMarkup - self.input_message_content = input_message_content # flags.7?InputMessageContent - - self.style = HTML() if parse_mode.lower() == "html" else Markdown() - - def write(self): - return types.InputBotInlineResult( - id=self.id, - type="photo", - send_message=types.InputBotInlineMessageMediaAuto( - reply_markup=self.reply_markup.write() if self.reply_markup else None, - **self.style.parse(self.caption) - ), - title=self.title, - description=self.description, - url=self.photo_url, - thumb=types.InputWebDocument( - url=self.thumb_url, - size=0, - mime_type="image/jpeg", - attributes=[ - types.DocumentAttributeImageSize( - w=0, - h=0 - ) - ] - ), - content=types.InputWebDocument( - url=self.thumb_url, - size=0, - mime_type="image/jpeg", - attributes=[ - types.DocumentAttributeImageSize( - w=self.photo_width, - h=self.photo_height - ) - ] - ) - ) diff --git a/pyrogram/client/types/inline_mode/todo/inline_query_result_venue.py b/pyrogram/client/types/inline_mode/todo/inline_query_result_venue.py deleted file mode 100644 index 29eb86a6..00000000 --- a/pyrogram/client/types/inline_mode/todo/inline_query_result_venue.py +++ /dev/null @@ -1,86 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -from pyrogram.client.types.object import Object - - -class InlineQueryResultVenue(Object): - """Represents a venue. By default, the venue will be sent by the user. Alternatively, you can use input_message_content to send a message with the specified content instead of the venue. - - Attributes: - ID: ``0xb0700008`` - - Parameters: - type (``str``): - Type of the result, must be venue. - - id (``str``): - Unique identifier for this result, 1-64 Bytes. - - latitude (``float`` ``64-bit``): - Latitude of the venue location in degrees. - - longitude (``float`` ``64-bit``): - Longitude of the venue location in degrees. - - title (``str``): - Title of the venue. - - address (``str``): - Address of the venue. - - foursquare_id (``str``, optional): - Foursquare identifier of the venue if known. - - foursquare_type (``str``, optional): - Foursquare type of the venue, if known. (For example, "arts_entertainment/default", "arts_entertainment/aquarium" or "food/icecream".). - - reply_markup (:obj:`InlineKeyboardMarkup`, optional): - Inline keyboard attached to the message. - - input_message_content (:obj:`InputMessageContent`, optional): - Content of the message to be sent instead of the venue. - - thumb_url (``str``, optional): - Url of the thumbnail for the result. - - thumb_width (``int`` ``32-bit``, optional): - Thumbnail width. - - thumb_height (``int`` ``32-bit``, optional): - Thumbnail height. - - """ - ID = 0xb0700008 - - def __init__(self, type: str, id: str, latitude: float, longitude: float, title: str, address: str, - foursquare_id: str = None, foursquare_type: str = None, reply_markup=None, input_message_content=None, - thumb_url: str = None, thumb_width: int = None, thumb_height: int = None): - self.type = type # string - self.id = id # string - self.latitude = latitude # double - self.longitude = longitude # double - self.title = title # string - self.address = address # string - self.foursquare_id = foursquare_id # flags.0?string - self.foursquare_type = foursquare_type # flags.1?string - self.reply_markup = reply_markup # flags.2?InlineKeyboardMarkup - self.input_message_content = input_message_content # flags.3?InputMessageContent - self.thumb_url = thumb_url # flags.4?string - self.thumb_width = thumb_width # flags.5?int - self.thumb_height = thumb_height # flags.6?int diff --git a/pyrogram/client/types/inline_mode/todo/inline_query_result_video.py b/pyrogram/client/types/inline_mode/todo/inline_query_result_video.py deleted file mode 100644 index 61984d48..00000000 --- a/pyrogram/client/types/inline_mode/todo/inline_query_result_video.py +++ /dev/null @@ -1,90 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -from pyrogram.client.types.object import Object - - -class InlineQueryResultVideo(Object): - """Represents a link to a page containing an embedded video player or a video file. By default, this video file will be sent by the user with an optional caption. Alternatively, you can use input_message_content to send a message with the specified content instead of the video. - - Attributes: - ID: ``0xb0700003`` - - Parameters: - type (``str``): - Type of the result, must be video. - - id (``str``): - Unique identifier for this result, 1-64 bytes. - - video_url (``str``): - A valid URL for the embedded video player or video file. - - mime_type (``str``): - Mime type of the content of video url, "text/html" or "video/mp4". - - thumb_url (``str``): - URL of the thumbnail (jpeg only) for the video. - - title (``str``): - Title for the result. - - caption (``str``, optional): - Caption of the video to be sent, 0-200 characters. - - parse_mode (``str``, optional): - Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in the media caption. - - video_width (``int`` ``32-bit``, optional): - Video width. - - video_height (``int`` ``32-bit``, optional): - Video height. - - video_duration (``int`` ``32-bit``, optional): - Video duration in seconds. - - description (``str``, optional): - Short description of the result. - - reply_markup (:obj:`InlineKeyboardMarkup`, optional): - Inline keyboard attached to the message. - - input_message_content (:obj:`InputMessageContent`, optional): - Content of the message to be sent instead of the video. This field is required if InlineQueryResultVideo is used to send an HTML-page as a result (e.g., a YouTube video). - - """ - ID = 0xb0700003 - - def __init__(self, type: str, id: str, video_url: str, mime_type: str, thumb_url: str, title: str, - caption: str = None, parse_mode: str = None, video_width: int = None, video_height: int = None, - video_duration: int = None, description: str = None, reply_markup=None, input_message_content=None): - self.type = type # string - self.id = id # string - self.video_url = video_url # string - self.mime_type = mime_type # string - self.thumb_url = thumb_url # string - self.title = title # string - self.caption = caption # flags.0?string - self.parse_mode = parse_mode # flags.1?string - self.video_width = video_width # flags.2?int - self.video_height = video_height # flags.3?int - self.video_duration = video_duration # flags.4?int - self.description = description # flags.5?string - self.reply_markup = reply_markup # flags.6?InlineKeyboardMarkup - self.input_message_content = input_message_content # flags.7?InputMessageContent diff --git a/pyrogram/client/types/inline_mode/todo/inline_query_result_voice.py b/pyrogram/client/types/inline_mode/todo/inline_query_result_voice.py deleted file mode 100644 index 7a5f3cd1..00000000 --- a/pyrogram/client/types/inline_mode/todo/inline_query_result_voice.py +++ /dev/null @@ -1,69 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -from pyrogram.client.types.object import Object - - -class InlineQueryResultVoice(Object): - """Represents a link to a voice recording in an .ogg container encoded with OPUS. By default, this voice recording will be sent by the user. Alternatively, you can use input_message_content to send a message with the specified content instead of the the voice message. - - Attributes: - ID: ``0xb0700005`` - - Parameters: - type (``str``): - Type of the result, must be voice. - - id (``str``): - Unique identifier for this result, 1-64 bytes. - - voice_url (``str``): - A valid URL for the voice recording. - - title (``str``): - Recording title. - - caption (``str``, optional): - Caption, 0-200 characters. - - parse_mode (``str``, optional): - Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in the media caption. - - voice_duration (``int`` ``32-bit``, optional): - Recording duration in seconds. - - reply_markup (:obj:`InlineKeyboardMarkup`, optional): - Inline keyboard attached to the message. - - input_message_content (:obj:`InputMessageContent`, optional): - Content of the message to be sent instead of the voice recording. - - """ - ID = 0xb0700005 - - def __init__(self, type: str, id: str, voice_url: str, title: str, caption: str = None, parse_mode: str = None, - voice_duration: int = None, reply_markup=None, input_message_content=None): - self.type = type # string - self.id = id # string - self.voice_url = voice_url # string - self.title = title # string - self.caption = caption # flags.0?string - self.parse_mode = parse_mode # flags.1?string - self.voice_duration = voice_duration # flags.2?int - self.reply_markup = reply_markup # flags.3?InlineKeyboardMarkup - self.input_message_content = input_message_content # flags.4?InputMessageContent From 8d852cb47ee10863de67033a87648cb1daa82aeb Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 26 Jun 2019 16:05:09 +0200 Subject: [PATCH 077/202] Implement strict and loose markdown parsing This is enabled by default: - strict: only markdown syntax is parsed - loose: both markdown and html syntax are parsed --- pyrogram/client/{style => parser}/markdown.py | 93 +++++++++---------- 1 file changed, 43 insertions(+), 50 deletions(-) rename pyrogram/client/{style => parser}/markdown.py (66%) diff --git a/pyrogram/client/style/markdown.py b/pyrogram/client/parser/markdown.py similarity index 66% rename from pyrogram/client/style/markdown.py rename to pyrogram/client/parser/markdown.py index 93d8fc9a..74d06e97 100644 --- a/pyrogram/client/style/markdown.py +++ b/pyrogram/client/parser/markdown.py @@ -18,6 +18,7 @@ import html import re +from typing import Union import pyrogram from . import utils @@ -30,42 +31,52 @@ STRIKE_DELIM = "~~" CODE_DELIM = "`" PRE_DELIM = "```" +MARKDOWN_RE = re.compile(r"({d})|\[(.+?)\]\((.+?)\)".format( + d="|".join( + ["".join(i) for i in [ + [r"\{}".format(j) for j in i] + for i in [ + PRE_DELIM, + CODE_DELIM, + STRIKE_DELIM, + UNDERLINE_DELIM, + ITALIC_DELIM, + BOLD_DELIM + ] + ]] + ))) + +OPENING_TAG = "<{}>" +CLOSING_TAG = "" +URL_MARKUP = '{}' +FIXED_WIDTH_DELIMS = [CODE_DELIM, PRE_DELIM] + class Markdown: - MARKDOWN_RE = re.compile(r"({d})".format( - d="|".join( - ["".join(i) for i in [ - [r"\{}".format(j) for j in i] - for i in [ - PRE_DELIM, - CODE_DELIM, - STRIKE_DELIM, - UNDERLINE_DELIM, - ITALIC_DELIM, - BOLD_DELIM - ] - ]] - ))) - - URL_RE = re.compile(r"\[([^[]+)]\(([^(]+)\)") - - OPENING_TAG = "<{}>" - CLOSING_TAG = "" - URL_MARKUP = '{}' - FIXED_WIDTH_DELIMS = [CODE_DELIM, PRE_DELIM] - - def __init__(self, client: "pyrogram.BaseClient"): + def __init__(self, client: Union["pyrogram.BaseClient", None]): self.html = HTML(client) - def parse(self, text: str): - text = html.escape(text) + def parse(self, text: str, strict: bool = False): + if strict: + text = html.escape(text) - offset = 0 delims = set() + is_fixed_width = False - for i, match in enumerate(re.finditer(Markdown.MARKDOWN_RE, text)): - start, stop = match.span() - delim = match.group(1) + for i, match in enumerate(re.finditer(MARKDOWN_RE, text)): + start, _ = match.span() + delim, text_url, url = match.groups() + full = match.group(0) + + if delim in FIXED_WIDTH_DELIMS: + is_fixed_width = not is_fixed_width + + if is_fixed_width and delim not in FIXED_WIDTH_DELIMS: + continue + + if text_url: + text = utils.replace_once(text, full, URL_MARKUP.format(url, text_url), start) + continue if delim == BOLD_DELIM: tag = "b" @@ -82,32 +93,14 @@ class Markdown: else: continue - if delim not in Markdown.FIXED_WIDTH_DELIMS and any(x in delims for x in Markdown.FIXED_WIDTH_DELIMS): - continue - if delim not in delims: delims.add(delim) - tag = Markdown.OPENING_TAG.format(tag) + tag = OPENING_TAG.format(tag) else: delims.remove(delim) - tag = Markdown.CLOSING_TAG.format(tag) + tag = CLOSING_TAG.format(tag) - text = text[:start + offset] + tag + text[stop + offset:] - - offset += len(tag) - len(delim) - - offset = 0 - - for match in re.finditer(Markdown.URL_RE, text): - start, stop = match.span() - full = match.group(0) - - body, url = match.groups() - replace = Markdown.URL_MARKUP.format(url, body) - - text = text[:start + offset] + replace + text[stop + offset:] - - offset += len(replace) - len(full) + text = utils.replace_once(text, delim, tag, start) return self.html.parse(text) From be5f0c95298fdf6b19c51f301a1adf856ee5743e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 26 Jun 2019 16:06:50 +0200 Subject: [PATCH 078/202] Parser's client can be None In that case, check if is None and don't parse user mentions. This happens only in text content for inline results --- pyrogram/client/{style => parser}/html.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) rename pyrogram/client/{style => parser}/html.py (95%) diff --git a/pyrogram/client/style/html.py b/pyrogram/client/parser/html.py similarity index 95% rename from pyrogram/client/style/html.py rename to pyrogram/client/parser/html.py index 5617cb54..16c5922a 100644 --- a/pyrogram/client/style/html.py +++ b/pyrogram/client/parser/html.py @@ -20,6 +20,7 @@ import html import re from collections import OrderedDict from html.parser import HTMLParser +from typing import Union import pyrogram from pyrogram.api import types @@ -103,7 +104,7 @@ class Parser(HTMLParser): class HTML: - def __init__(self, client: "pyrogram.BaseClient" = None): + def __init__(self, client: Union["pyrogram.BaseClient", None]): self.client = client def parse(self, text: str): @@ -126,7 +127,8 @@ class HTML: for entity in parser.entities: if isinstance(entity, types.InputMessageEntityMentionName): try: - entity.user_id = self.client.resolve_peer(entity.user_id) + if self.client is not None: + entity.user_id = self.client.resolve_peer(entity.user_id) except PeerIdInvalid: continue @@ -135,7 +137,7 @@ class HTML: # TODO: OrderedDict to be removed in Python 3.6 return OrderedDict([ ("message", utils.remove_surrogates(parser.text)), - ("entities", entities) + ("entities", sorted(entities, key=lambda e: e.offset)) ]) @staticmethod From e61bf9262740996ccce04c90e49bab633ef59060 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 26 Jun 2019 16:07:27 +0200 Subject: [PATCH 079/202] Add Parser package Revamped from HTML/Markdown --- pyrogram/client/{style => parser}/__init__.py | 3 +- pyrogram/client/parser/parser.py | 56 +++++++++++++++++++ pyrogram/client/{style => parser}/utils.py | 4 ++ 3 files changed, 61 insertions(+), 2 deletions(-) rename pyrogram/client/{style => parser}/__init__.py (93%) create mode 100644 pyrogram/client/parser/parser.py rename pyrogram/client/{style => parser}/utils.py (91%) diff --git a/pyrogram/client/style/__init__.py b/pyrogram/client/parser/__init__.py similarity index 93% rename from pyrogram/client/style/__init__.py rename to pyrogram/client/parser/__init__.py index 768cee7b..4769038d 100644 --- a/pyrogram/client/style/__init__.py +++ b/pyrogram/client/parser/__init__.py @@ -16,5 +16,4 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from .html import HTML -from .markdown import Markdown +from .parser import Parser \ No newline at end of file diff --git a/pyrogram/client/parser/parser.py b/pyrogram/client/parser/parser.py new file mode 100644 index 00000000..afb7ecaa --- /dev/null +++ b/pyrogram/client/parser/parser.py @@ -0,0 +1,56 @@ +# 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 collections import OrderedDict +from typing import Union + + +import pyrogram +from .html import HTML +from .markdown import Markdown + + +class Parser: + def __init__(self, client: Union["pyrogram.BaseClient", None]): + self.html = HTML(client) + self.markdown = Markdown(client) + + def parse(self, text: str, mode: str = ""): + if mode is None: + return OrderedDict([ + ("message", text), + ("entities", []) + ]) + + mode = mode.lower() + + if mode == "": + return self.markdown.parse(text) + + if mode in ["markdown", "md"]: + return self.markdown.parse(text, True) + + if mode == "html": + return self.html.parse(text) + + @staticmethod + def unparse(text: str, entities: list, is_html: bool): + if is_html: + return HTML.unparse(text, entities) + else: + return Markdown.unparse(text, entities) diff --git a/pyrogram/client/style/utils.py b/pyrogram/client/parser/utils.py similarity index 91% rename from pyrogram/client/style/utils.py rename to pyrogram/client/parser/utils.py index b001f1cf..1fce419f 100644 --- a/pyrogram/client/style/utils.py +++ b/pyrogram/client/parser/utils.py @@ -35,3 +35,7 @@ def add_surrogates(text): def remove_surrogates(text): # Replace each surrogate pair with a SMP code point return text.encode("utf-16", "surrogatepass").decode("utf-16") + + +def replace_once(source: str, old: str, new: str, start: int): + return source[:start] + source[start:].replace(old, new, 1) From f05e79e0f4a7f6ebcba8be9158f7ecd868293926 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 26 Jun 2019 16:08:24 +0200 Subject: [PATCH 080/202] Update usages of Parser all around the library --- pyrogram/client/ext/base_client.py | 5 ++--- pyrogram/client/methods/messages/edit_inline_text.py | 7 ++++--- pyrogram/client/methods/messages/edit_message_text.py | 5 ++--- pyrogram/client/methods/messages/send_animation.py | 5 ++--- pyrogram/client/methods/messages/send_audio.py | 5 ++--- pyrogram/client/methods/messages/send_cached_media.py | 5 ++--- pyrogram/client/methods/messages/send_document.py | 5 ++--- pyrogram/client/methods/messages/send_message.py | 5 ++--- pyrogram/client/methods/messages/send_photo.py | 5 ++--- pyrogram/client/methods/messages/send_video.py | 5 ++--- pyrogram/client/methods/messages/send_voice.py | 5 ++--- .../input_message_content/input_text_message_content.py | 8 +++++--- pyrogram/client/types/messages_and_media/message.py | 6 +++--- 13 files changed, 32 insertions(+), 39 deletions(-) diff --git a/pyrogram/client/ext/base_client.py b/pyrogram/client/ext/base_client.py index 88623f4a..b5be089b 100644 --- a/pyrogram/client/ext/base_client.py +++ b/pyrogram/client/ext/base_client.py @@ -25,7 +25,7 @@ from queue import Queue from threading import Lock from pyrogram import __version__ -from ..style import Markdown, HTML +from ..parser import Parser from ...session.internals import MsgId @@ -92,8 +92,7 @@ class BaseClient: self.rnd_id = MsgId - self.markdown = Markdown(self) - self.html = HTML(self) + self.parser = Parser(self) self.session = None self.media_sessions = {} diff --git a/pyrogram/client/methods/messages/edit_inline_text.py b/pyrogram/client/methods/messages/edit_inline_text.py index 927fd80f..349e3e35 100644 --- a/pyrogram/client/methods/messages/edit_inline_text.py +++ b/pyrogram/client/methods/messages/edit_inline_text.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.api import functions from pyrogram.client.ext import BaseClient, utils @@ -26,7 +28,7 @@ class EditInlineText(BaseClient): self, inline_message_id: str, text: str, - parse_mode: str = "", + parse_mode: Union[str, None] = "", disable_web_page_preview: bool = None, reply_markup: "pyrogram.InlineKeyboardMarkup" = None ) -> bool: @@ -55,13 +57,12 @@ class EditInlineText(BaseClient): Raises: RPCError: In case of a Telegram RPC error. """ - style = self.html if parse_mode.lower() == "html" else self.markdown return self.send( functions.messages.EditInlineBotMessage( id=utils.unpack_inline_message_id(inline_message_id), no_webpage=disable_web_page_preview or None, reply_markup=reply_markup.write() if reply_markup else None, - **style.parse(text) + **self.parser.parse(text, parse_mode) ) ) diff --git a/pyrogram/client/methods/messages/edit_message_text.py b/pyrogram/client/methods/messages/edit_message_text.py index 7e4345c6..e30afdfc 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: str = "", + parse_mode: Union[str, None] = "", disable_web_page_preview: bool = None, reply_markup: "pyrogram.InlineKeyboardMarkup" = None ) -> "pyrogram.Message": @@ -63,7 +63,6 @@ class EditMessageText(BaseClient): Raises: RPCError: In case of a Telegram RPC error. """ - style = self.html if parse_mode.lower() == "html" else self.markdown r = self.send( functions.messages.EditMessage( @@ -71,7 +70,7 @@ class EditMessageText(BaseClient): id=message_id, no_webpage=disable_web_page_preview or None, reply_markup=reply_markup.write() if reply_markup else None, - **style.parse(text) + **self.parser.parse(text, parse_mode) ) ) diff --git a/pyrogram/client/methods/messages/send_animation.py b/pyrogram/client/methods/messages/send_animation.py index 0c4649dd..cff80af5 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: str = "", + parse_mode: Union[str, None] = "", duration: int = 0, width: int = 0, height: int = 0, @@ -130,7 +130,6 @@ class SendAnimation(BaseClient): RPCError: In case of a Telegram RPC error. """ file = None - style = self.html if parse_mode.lower() == "html" else self.markdown try: if os.path.exists(animation): @@ -168,7 +167,7 @@ class SendAnimation(BaseClient): reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), reply_markup=reply_markup.write() if reply_markup else None, - **style.parse(caption) + **self.parser.parse(caption, parse_mode) ) ) except FilePartMissing as e: diff --git a/pyrogram/client/methods/messages/send_audio.py b/pyrogram/client/methods/messages/send_audio.py index 7b218a66..5cd9fc8d 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: str = "", + parse_mode: Union[str, None] = "", duration: int = 0, performer: str = None, title: str = None, @@ -127,7 +127,6 @@ class SendAudio(BaseClient): RPCError: In case of a Telegram RPC error. """ file = None - style = self.html if parse_mode.lower() == "html" else self.markdown try: if os.path.exists(audio): @@ -163,7 +162,7 @@ class SendAudio(BaseClient): reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), reply_markup=reply_markup.write() if reply_markup else None, - **style.parse(caption) + **self.parser.parse(caption, parse_mode) ) ) except FilePartMissing as e: diff --git a/pyrogram/client/methods/messages/send_cached_media.py b/pyrogram/client/methods/messages/send_cached_media.py index 99961ca1..1c5aad7b 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: str = "", + parse_mode: Union[str, None] = "", disable_notification: bool = None, reply_to_message_id: int = None, reply_markup: Union[ @@ -79,7 +79,6 @@ class SendCachedMedia(BaseClient): Raises: RPCError: In case of a Telegram RPC error. """ - style = self.html if parse_mode.lower() == "html" else self.markdown r = self.send( functions.messages.SendMedia( @@ -89,7 +88,7 @@ class SendCachedMedia(BaseClient): reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), reply_markup=reply_markup.write() if reply_markup else None, - **style.parse(caption) + **self.parser.parse(caption, parse_mode) ) ) diff --git a/pyrogram/client/methods/messages/send_document.py b/pyrogram/client/methods/messages/send_document.py index da012b2c..55ab53af 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: str = "", + parse_mode: Union[str, None] = "", disable_notification: bool = None, reply_to_message_id: int = None, reply_markup: Union[ @@ -113,7 +113,6 @@ class SendDocument(BaseClient): RPCError: In case of a Telegram RPC error. """ file = None - style = self.html if parse_mode.lower() == "html" else self.markdown try: if os.path.exists(document): @@ -144,7 +143,7 @@ class SendDocument(BaseClient): reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), reply_markup=reply_markup.write() if reply_markup else None, - **style.parse(caption) + **self.parser(caption, parse_mode) ) ) except FilePartMissing as e: diff --git a/pyrogram/client/methods/messages/send_message.py b/pyrogram/client/methods/messages/send_message.py index f8caa081..959cf9b9 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: str = "", + parse_mode: Union[str, None] = "", disable_web_page_preview: bool = None, disable_notification: bool = None, reply_to_message_id: int = None, @@ -74,8 +74,7 @@ class SendMessage(BaseClient): Raises: RPCError: In case of a Telegram RPC error. """ - style = self.html if parse_mode.lower() == "html" else self.markdown - message, entities = style.parse(text).values() + message, entities = self.parser.parse(text, parse_mode).values() r = self.send( functions.messages.SendMessage( diff --git a/pyrogram/client/methods/messages/send_photo.py b/pyrogram/client/methods/messages/send_photo.py index c1fd33d8..8ebb2761 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: str = "", + parse_mode: Union[str, None] = "", ttl_seconds: int = None, disable_notification: bool = None, reply_to_message_id: int = None, @@ -112,7 +112,6 @@ class SendPhoto(BaseClient): RPCError: In case of a Telegram RPC error. """ file = None - style = self.html if parse_mode.lower() == "html" else self.markdown try: if os.path.exists(photo): @@ -139,7 +138,7 @@ class SendPhoto(BaseClient): reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), reply_markup=reply_markup.write() if reply_markup else None, - **style.parse(caption) + **self.parser.parse(caption, parse_mode) ) ) except FilePartMissing as e: diff --git a/pyrogram/client/methods/messages/send_video.py b/pyrogram/client/methods/messages/send_video.py index 4e1201fc..0785056d 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: str = "", + parse_mode: Union[str, None] = "", duration: int = 0, width: int = 0, height: int = 0, @@ -129,7 +129,6 @@ class SendVideo(BaseClient): RPCError: In case of a Telegram RPC error. """ file = None - style = self.html if parse_mode.lower() == "html" else self.markdown try: if os.path.exists(video): @@ -166,7 +165,7 @@ class SendVideo(BaseClient): reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), reply_markup=reply_markup.write() if reply_markup else None, - **style.parse(caption) + **self.parser.parse(caption, parse_mode) ) ) except FilePartMissing as e: diff --git a/pyrogram/client/methods/messages/send_voice.py b/pyrogram/client/methods/messages/send_voice.py index 9dace1e0..45dfa45b 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: str = "", + parse_mode: Union[str, None] = "", duration: int = 0, disable_notification: bool = None, reply_to_message_id: int = None, @@ -110,7 +110,6 @@ class SendVoice(BaseClient): RPCError: In case of a Telegram RPC error. """ file = None - style = self.html if parse_mode.lower() == "html" else self.markdown try: if os.path.exists(voice): @@ -142,7 +141,7 @@ class SendVoice(BaseClient): reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), reply_markup=reply_markup.write() if reply_markup else None, - **style.parse(caption) + **self.parser.parse(caption, parse_mode) ) ) except FilePartMissing as e: 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 4b294aab..7db2069d 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 @@ -16,9 +16,11 @@ # 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 .input_message_content import InputMessageContent -from ...style import HTML, Markdown +from ...parser import Parser class InputTextMessageContent(InputMessageContent): @@ -38,7 +40,7 @@ class InputTextMessageContent(InputMessageContent): __slots__ = ["message_text", "parse_mode", "disable_web_page_preview"] - def __init__(self, message_text: str, parse_mode: str = "", disable_web_page_preview: bool = None): + def __init__(self, message_text: str, parse_mode: Union[str, None] = "", disable_web_page_preview: bool = None): super().__init__() self.message_text = message_text @@ -49,5 +51,5 @@ class InputTextMessageContent(InputMessageContent): return types.InputBotInlineMessageText( no_webpage=self.disable_web_page_preview or None, reply_markup=reply_markup.write() if reply_markup else None, - **(HTML() if self.parse_mode.lower() == "html" else Markdown()).parse(self.message_text) + **(Parser(None)).parse(self.message_text, self.parse_mode) ) diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 16726a47..549d6f09 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -31,7 +31,7 @@ from ..object import Object from ..update import Update from ..user_and_chats.chat import Chat from ..user_and_chats.user import User -from ...style import utils, Markdown, HTML +from ...parser import utils, Parser class Str(str): @@ -47,11 +47,11 @@ class Str(str): @property def markdown(self): - return Markdown.unparse(self, self.entities) + return Parser.unparse(self, self.entities, False) @property def html(self): - return HTML.unparse(self, self.entities) + return Parser.unparse(self, self.entities, True) def __getitem__(self, item): return utils.remove_surrogates(utils.add_surrogates(self)[item]) From 197cf5506ce7cbeb03db10b2b979e29040cf9119 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 26 Jun 2019 16:36:00 +0200 Subject: [PATCH 081/202] Only allow either "markdown" or "html" as text style parse mode --- pyrogram/client/parser/parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/parser/parser.py b/pyrogram/client/parser/parser.py index afb7ecaa..f685c942 100644 --- a/pyrogram/client/parser/parser.py +++ b/pyrogram/client/parser/parser.py @@ -42,7 +42,7 @@ class Parser: if mode == "": return self.markdown.parse(text) - if mode in ["markdown", "md"]: + if mode in "markdown": return self.markdown.parse(text, True) if mode == "html": From 39e25147bd66c1b82f5654b8595f020eb5a7c719 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 26 Jun 2019 16:36:24 +0200 Subject: [PATCH 082/202] Update parse_mode parameter docstrings --- .../methods/messages/edit_inline_caption.py | 7 +- .../methods/messages/edit_inline_text.py | 7 +- .../methods/messages/edit_message_caption.py | 7 +- .../methods/messages/edit_message_text.py | 7 +- .../client/methods/messages/send_animation.py | 7 +- .../client/methods/messages/send_audio.py | 7 +- .../methods/messages/send_cached_media.py | 7 +- .../client/methods/messages/send_document.py | 7 +- .../client/methods/messages/send_message.py | 7 +- .../client/methods/messages/send_photo.py | 7 +- .../client/methods/messages/send_video.py | 7 +- .../client/methods/messages/send_voice.py | 7 +- .../bots_and_keyboards/callback_query.py | 14 ++-- .../input_media/input_media_animation.py | 7 +- .../types/input_media/input_media_audio.py | 7 +- .../types/input_media/input_media_document.py | 7 +- .../types/input_media/input_media_photo.py | 7 +- .../types/input_media/input_media_video.py | 7 +- .../input_text_message_content.py | 7 +- .../types/messages_and_media/message.py | 70 +++++++++++++------ 20 files changed, 150 insertions(+), 60 deletions(-) diff --git a/pyrogram/client/methods/messages/edit_inline_caption.py b/pyrogram/client/methods/messages/edit_inline_caption.py index a9bbc551..aa5661dd 100644 --- a/pyrogram/client/methods/messages/edit_inline_caption.py +++ b/pyrogram/client/methods/messages/edit_inline_caption.py @@ -38,8 +38,11 @@ class EditInlineCaption(BaseClient): New caption of the media message. parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline - URLs in your message. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" 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. diff --git a/pyrogram/client/methods/messages/edit_inline_text.py b/pyrogram/client/methods/messages/edit_inline_text.py index 349e3e35..3c153c3c 100644 --- a/pyrogram/client/methods/messages/edit_inline_text.py +++ b/pyrogram/client/methods/messages/edit_inline_text.py @@ -42,8 +42,11 @@ class EditInlineText(BaseClient): New text of the message. parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline - URLs in your message. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. disable_web_page_preview (``bool``, *optional*): Disables link previews for links in this message. diff --git a/pyrogram/client/methods/messages/edit_message_caption.py b/pyrogram/client/methods/messages/edit_message_caption.py index 52c22726..3c2cd138 100644 --- a/pyrogram/client/methods/messages/edit_message_caption.py +++ b/pyrogram/client/methods/messages/edit_message_caption.py @@ -46,8 +46,11 @@ class EditMessageCaption(BaseClient): New caption of the media message. parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline - URLs in your message. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" 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. diff --git a/pyrogram/client/methods/messages/edit_message_text.py b/pyrogram/client/methods/messages/edit_message_text.py index e30afdfc..9a7a1a09 100644 --- a/pyrogram/client/methods/messages/edit_message_text.py +++ b/pyrogram/client/methods/messages/edit_message_text.py @@ -48,8 +48,11 @@ class EditMessageText(BaseClient): New text of the message. parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline - URLs in your message. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. disable_web_page_preview (``bool``, *optional*): Disables link previews for links in this message. diff --git a/pyrogram/client/methods/messages/send_animation.py b/pyrogram/client/methods/messages/send_animation.py index cff80af5..c0c57059 100644 --- a/pyrogram/client/methods/messages/send_animation.py +++ b/pyrogram/client/methods/messages/send_animation.py @@ -70,8 +70,11 @@ class SendAnimation(BaseClient): Pass True to automatically unsave the sent animation. Defaults to False. parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline - URLs in your caption. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. duration (``int``, *optional*): Duration of sent animation in seconds. diff --git a/pyrogram/client/methods/messages/send_audio.py b/pyrogram/client/methods/messages/send_audio.py index 5cd9fc8d..7759a6e9 100644 --- a/pyrogram/client/methods/messages/send_audio.py +++ b/pyrogram/client/methods/messages/send_audio.py @@ -67,8 +67,11 @@ class SendAudio(BaseClient): Audio caption, 0-1024 characters. parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline - URLs in your caption. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. duration (``int``, *optional*): Duration of the audio in seconds. diff --git a/pyrogram/client/methods/messages/send_cached_media.py b/pyrogram/client/methods/messages/send_cached_media.py index 1c5aad7b..6c947d96 100644 --- a/pyrogram/client/methods/messages/send_cached_media.py +++ b/pyrogram/client/methods/messages/send_cached_media.py @@ -59,8 +59,11 @@ class SendCachedMedia(BaseClient): Media caption, 0-1024 characters. parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline - URLs in your caption. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. disable_notification (``bool``, *optional*): Sends the message silently. diff --git a/pyrogram/client/methods/messages/send_document.py b/pyrogram/client/methods/messages/send_document.py index 55ab53af..06132688 100644 --- a/pyrogram/client/methods/messages/send_document.py +++ b/pyrogram/client/methods/messages/send_document.py @@ -68,8 +68,11 @@ class SendDocument(BaseClient): Document caption, 0-1024 characters. parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline - URLs in your caption. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. disable_notification (``bool``, *optional*): Sends the message silently. diff --git a/pyrogram/client/methods/messages/send_message.py b/pyrogram/client/methods/messages/send_message.py index 959cf9b9..67861f3f 100644 --- a/pyrogram/client/methods/messages/send_message.py +++ b/pyrogram/client/methods/messages/send_message.py @@ -51,8 +51,11 @@ class SendMessage(BaseClient): Text of the message to be sent. parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline - URLs in your message. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. disable_web_page_preview (``bool``, *optional*): Disables link previews for links in this message. diff --git a/pyrogram/client/methods/messages/send_photo.py b/pyrogram/client/methods/messages/send_photo.py index 8ebb2761..93178f26 100644 --- a/pyrogram/client/methods/messages/send_photo.py +++ b/pyrogram/client/methods/messages/send_photo.py @@ -62,8 +62,11 @@ class SendPhoto(BaseClient): Photo caption, 0-1024 characters. parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline - URLs in your caption. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. ttl_seconds (``int``, *optional*): Self-Destruct Timer. diff --git a/pyrogram/client/methods/messages/send_video.py b/pyrogram/client/methods/messages/send_video.py index 0785056d..feacf5c7 100644 --- a/pyrogram/client/methods/messages/send_video.py +++ b/pyrogram/client/methods/messages/send_video.py @@ -66,8 +66,11 @@ class SendVideo(BaseClient): Video caption, 0-1024 characters. parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline - URLs in your caption. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. duration (``int``, *optional*): Duration of sent video in seconds. diff --git a/pyrogram/client/methods/messages/send_voice.py b/pyrogram/client/methods/messages/send_voice.py index 45dfa45b..ffed409e 100644 --- a/pyrogram/client/methods/messages/send_voice.py +++ b/pyrogram/client/methods/messages/send_voice.py @@ -62,8 +62,11 @@ class SendVoice(BaseClient): Voice message caption, 0-1024 characters. parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline - URLs in your caption. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. duration (``int``, *optional*): Duration of the voice message in seconds. diff --git a/pyrogram/client/types/bots_and_keyboards/callback_query.py b/pyrogram/client/types/bots_and_keyboards/callback_query.py index 6a717489..f631db45 100644 --- a/pyrogram/client/types/bots_and_keyboards/callback_query.py +++ b/pyrogram/client/types/bots_and_keyboards/callback_query.py @@ -189,8 +189,11 @@ class CallbackQuery(Object, Update): New text of the message. parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline - URLs in your message. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. disable_web_page_preview (``bool``, *optional*): Disables link previews for links in this message. @@ -238,8 +241,11 @@ class CallbackQuery(Object, Update): New caption of the message. parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline - URLs in your message. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" 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. diff --git a/pyrogram/client/types/input_media/input_media_animation.py b/pyrogram/client/types/input_media/input_media_animation.py index 23fcb967..e5b8edb4 100644 --- a/pyrogram/client/types/input_media/input_media_animation.py +++ b/pyrogram/client/types/input_media/input_media_animation.py @@ -38,8 +38,11 @@ class InputMediaAnimation(InputMedia): Caption of the animation to be sent, 0-1024 characters parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline URLs - in your caption. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. width (``int``, *optional*): Animation width. diff --git a/pyrogram/client/types/input_media/input_media_audio.py b/pyrogram/client/types/input_media/input_media_audio.py index 3fb45d8f..02299a12 100644 --- a/pyrogram/client/types/input_media/input_media_audio.py +++ b/pyrogram/client/types/input_media/input_media_audio.py @@ -40,8 +40,11 @@ class InputMediaAudio(InputMedia): Caption of the audio to be sent, 0-1024 characters parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline URLs - in your caption. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. duration (``int``, *optional*): Duration of the audio in seconds diff --git a/pyrogram/client/types/input_media/input_media_document.py b/pyrogram/client/types/input_media/input_media_document.py index 0de8dedf..46a5b446 100644 --- a/pyrogram/client/types/input_media/input_media_document.py +++ b/pyrogram/client/types/input_media/input_media_document.py @@ -38,8 +38,11 @@ class InputMediaDocument(InputMedia): Caption of the document to be sent, 0-1024 characters parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline URLs - in your caption. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. """ __slots__ = ["thumb"] diff --git a/pyrogram/client/types/input_media/input_media_photo.py b/pyrogram/client/types/input_media/input_media_photo.py index ce134af2..064065e3 100644 --- a/pyrogram/client/types/input_media/input_media_photo.py +++ b/pyrogram/client/types/input_media/input_media_photo.py @@ -34,8 +34,11 @@ class InputMediaPhoto(InputMedia): Caption of the photo to be sent, 0-1024 characters parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline URLs - in your caption. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. """ __slots__ = [] diff --git a/pyrogram/client/types/input_media/input_media_video.py b/pyrogram/client/types/input_media/input_media_video.py index 9764dd1a..4584ffbe 100644 --- a/pyrogram/client/types/input_media/input_media_video.py +++ b/pyrogram/client/types/input_media/input_media_video.py @@ -40,8 +40,11 @@ class InputMediaVideo(InputMedia): Caption of the video to be sent, 0-1024 characters parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline URLs - in your caption. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. width (``int``, *optional*): Video width. 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 7db2069d..f1c24631 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 @@ -31,8 +31,11 @@ class InputTextMessageContent(InputMessageContent): Text of the message to be sent, 1-4096 characters. parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline URLs - in your message. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. disable_web_page_preview (``bool``, *optional*): Disables link previews for links in this message. diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 549d6f09..39fd24fa 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -686,8 +686,11 @@ class Message(Object, Update): Defaults to ``True`` in group chats and ``False`` in private chats. parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline - URLs in your message. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. disable_web_page_preview (``bool``, *optional*): Disables link previews for links in this message. @@ -780,8 +783,11 @@ class Message(Object, Update): Animation caption, 0-1024 characters. parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline - URLs in your caption. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. duration (``int``, *optional*): Duration of sent animation in seconds. @@ -914,8 +920,11 @@ class Message(Object, Update): Audio caption, 0-1024 characters. parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline - URLs in your caption. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. duration (``int``, *optional*): Duration of the audio in seconds. @@ -1040,8 +1049,11 @@ class Message(Object, Update): Media caption, 0-1024 characters. parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline - URLs in your caption. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. disable_notification (``bool``, *optional*): Sends the message silently. @@ -1255,8 +1267,11 @@ class Message(Object, Update): Document caption, 0-1024 characters. parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline - URLs in your caption. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. disable_notification (``bool``, *optional*): Sends the message silently. @@ -1642,8 +1657,11 @@ class Message(Object, Update): Photo caption, 0-1024 characters. parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline - URLs in your caption. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. ttl_seconds (``int``, *optional*): Self-Destruct Timer. @@ -2037,8 +2055,11 @@ class Message(Object, Update): Video caption, 0-1024 characters. parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline - URLs in your caption. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. duration (``int``, *optional*): Duration of sent video in seconds. @@ -2290,8 +2311,11 @@ class Message(Object, Update): Voice message caption, 0-1024 characters. parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline - URLs in your caption. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. duration (``int``, *optional*): Duration of the voice message in seconds. @@ -2385,8 +2409,11 @@ class Message(Object, Update): New text of the message. parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline - URLs in your message. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. disable_web_page_preview (``bool``, *optional*): Disables link previews for links in this message. @@ -2439,8 +2466,11 @@ class Message(Object, Update): New caption of the message. parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline - URLs in your message. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" 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. From 40bcd4e59d8b96b2dddd70ce810006988672101d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 26 Jun 2019 21:43:08 +0200 Subject: [PATCH 083/202] Fix delete_profile_photos. Closes #259 --- .../methods/users/delete_profile_photos.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/pyrogram/client/methods/users/delete_profile_photos.py b/pyrogram/client/methods/users/delete_profile_photos.py index 1b46382c..a165f7d1 100644 --- a/pyrogram/client/methods/users/delete_profile_photos.py +++ b/pyrogram/client/methods/users/delete_profile_photos.py @@ -16,23 +16,24 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from base64 import b64decode from struct import unpack from typing import List, Union from pyrogram.api import functions, types +from pyrogram.client.ext import utils + from ...ext import BaseClient class DeleteProfilePhotos(BaseClient): def delete_profile_photos( self, - id: Union[str, List[str]] + photo_ids: Union[str, List[str]] ) -> bool: """Delete your own profile photos. Parameters: - id (``str`` | ``list``): + photo_ids (``str`` | List of ``str``): A single :obj:`Photo` id as string or multiple ids as list of strings for deleting more than one photos at once. @@ -42,16 +43,16 @@ class DeleteProfilePhotos(BaseClient): Raises: RPCError: In case of a Telegram RPC error. """ - id = id if isinstance(id, list) else [id] + photo_ids = photo_ids if isinstance(photo_ids, list) else [photo_ids] input_photos = [] - for i in id: - s = unpack(" Date: Thu, 27 Jun 2019 11:59:44 +0200 Subject: [PATCH 084/202] Make unknown errors with known error codes inherit from base categories --- compiler/error/compiler.py | 10 +++--- pyrogram/errors/rpc_error.py | 63 +++++++++++++++++++----------------- 2 files changed, 39 insertions(+), 34 deletions(-) diff --git a/compiler/error/compiler.py b/compiler/error/compiler.py index 996c4981..80826396 100644 --- a/compiler/error/compiler.py +++ b/compiler/error/compiler.py @@ -81,6 +81,8 @@ def start(): sub_classes = [] + f_all.write(" \"_\": \"{}\",\n".format(super_class)) + for j, row in enumerate(reader): if j == 0: continue @@ -90,13 +92,13 @@ def start(): if not row: # Row is empty (blank line) continue - id, message = row + error_id, error_message = row - sub_class = caml(re.sub(r"_X", "_", id)) + sub_class = caml(re.sub(r"_X", "_", error_id)) - f_all.write(" \"{}\": \"{}\",\n".format(id, sub_class)) + f_all.write(" \"{}\": \"{}\",\n".format(error_id, sub_class)) - sub_classes.append((sub_class, id, message)) + sub_classes.append((sub_class, error_id, error_message)) with open("{}/template/class.txt".format(HOME), "r", encoding="utf-8") as f_class_template: class_template = f_class_template.read() diff --git a/pyrogram/errors/rpc_error.py b/pyrogram/errors/rpc_error.py index c1799f50..fb3a717d 100644 --- a/pyrogram/errors/rpc_error.py +++ b/pyrogram/errors/rpc_error.py @@ -17,65 +17,68 @@ # along with Pyrogram. If not, see . import re +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 .exceptions.all import exceptions class RPCError(Exception): - """This is the base exception class for all Telegram API related errors. - For a finer grained control, see the specific errors below. - """ ID = None CODE = None NAME = None - MESSAGE = None + MESSAGE = "{x}" - def __init__(self, x: int or RawRPCError = None, query_type: type = None): - super().__init__("[{} {}]: {}".format( + def __init__(self, x: int or RawRPCError, rpc_name: str, is_unknown: bool): + super().__init__("[{} {}]: {} ({})".format( self.CODE, self.ID or self.NAME, - str(self) or self.MESSAGE.format(x=x) + self.MESSAGE.format(x=x), + 'caused by "{}"'.format(rpc_name) )) - try: - self.x = int(x) - except (ValueError, TypeError): - self.x = x - - # TODO: Proper log unknown errors - if self.CODE == 520: + if is_unknown: with open("unknown_errors.txt", "a", encoding="utf-8") as f: - f.write("{}\t{}\t{}\n".format(x.error_code, x.error_message, query_type)) + f.write("{}\t{}\t{}\n".format(datetime.now(), x, rpc_name)) @staticmethod - def raise_it(rpc_error: RawRPCError, query_type: type): - code = rpc_error.error_code + def raise_it(rpc_error: RawRPCError, rpc_type: Type[TLObject]): + error_code = rpc_error.error_code + error_message = rpc_error.error_message + rpc_name = ".".join(rpc_type.QUALNAME.split(".")[1:]) - if code not in exceptions: - raise UnknownError(x=rpc_error, query_type=query_type) + if error_code not in exceptions: + raise UnknownError( + x="[{} {}]".format(error_code, error_message), + rpc_name=rpc_name, + is_unknown=True + ) - message = rpc_error.error_message - id = re.sub(r"_\d+", "_X", message) + error_id = re.sub(r"_\d+", "_X", error_message) - if id not in exceptions[code]: - raise UnknownError(x=rpc_error, query_type=query_type) + if error_id not in exceptions[error_code]: + raise getattr( + import_module("pyrogram.errors"), + exceptions[error_code]["_"] + )(x="[{} {}]".format(error_code, error_message), + rpc_name=rpc_name, + is_unknown=True) - x = re.search(r"_(\d+)", message) + x = re.search(r"_(\d+)", error_message) x = x.group(1) if x is not None else x raise getattr( import_module("pyrogram.errors"), - exceptions[code][id] - )(x=x) + exceptions[error_code][error_id] + )(x=x, + rpc_name=rpc_name, + is_unknown=False) class UnknownError(RPCError): - """This object represents an Unknown Error, that is, an error which - Pyrogram does not know anything about, yet. - """ CODE = 520 """:obj:`int`: Error code""" NAME = "Unknown error" - MESSAGE = "{x}" From a44c996302077b718a2d3c174c1edf9e06b2fd36 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 27 Jun 2019 23:15:12 +0200 Subject: [PATCH 085/202] Revamp text-formatting page, add info about the new styles --- docs/source/topics/text-formatting.rst | 228 +++++++++++++++++++------ 1 file changed, 178 insertions(+), 50 deletions(-) diff --git a/docs/source/topics/text-formatting.rst b/docs/source/topics/text-formatting.rst index bc74d562..03b50a8e 100644 --- a/docs/source/topics/text-formatting.rst +++ b/docs/source/topics/text-formatting.rst @@ -1,40 +1,99 @@ Text Formatting =============== -Pyrogram, just like the `Telegram Bot API`_, natively supports basic Markdown and HTML formatting styles for text -messages and media captions. +.. role:: strike + :class: strike -Markdown style uses the same syntax as Telegram Desktop's and is enabled by default. +.. role:: underline + :class: underline -Beside bold, italic, and pre-formatted code, **Pyrogram does also support inline URLs and inline mentions of users**. +.. role:: bold-underline + :class: bold-underline + +.. role:: strike-italic + :class: strike-italic + +Pyrogram uses a custom Markdown dialect for text formatting which adds some unique features that make writing styled +texts easier in both Markdown and HTML. You can send sophisticated text messages and media captions using a great +variety of decorations that can also be nested in order to combine multiple styles together. + +Basic Styles +------------ + +When formatting your messages, you can choose between Markdown-style, HTML-style or both (default). The following is a +list of the basic styles currently supported by Pyrogram. + +- **bold** +- *italic* +- :strike:`strike` +- :underline:`underline` +- `text URL `_ +- `user text mention `_ +- ``inline fixed-width code`` +- .. code-block:: text + + pre-formatted + fixed-width + code block + +.. note:: + + User text mentions are only guaranteed to work if you have already met the user (in groups or private chats). Markdown Style -------------- -To use this mode, pass "markdown" in the *parse_mode* field when using +To strictly use this mode, pass "markdown" to the *parse_mode* parameter when using :meth:`~pyrogram.Client.send_message`. Use the following syntax in your message: .. code-block:: text - **bold text** + **bold** - __italic text__ + __italic__ - [inline URL](https://docs.pyrogram.org/) + --underline-- - [inline mention of a user](tg://user?id=23122162) + ~~strike~~ + + [text URL](https://docs.pyrogram.org/) + + [text user mention](tg://user?id=23122162) `inline fixed-width code` - ```block_language - pre-formatted fixed-width code block + ``` + pre-formatted + fixed-width + code block ``` +**Example**: + +.. code-block:: python + + app.send_message( + "haskell", + ( + "**bold**, " + "__italic__, " + "--underline--, " + "~~strikethrough~~, " + "[mention](tg://user?id=23122162), " + "[URL](https://pyrogram.org), " + "`code`, " + "```" + "for i in range(10):\n" + " print(i)" + "```" + ), + parse_mode="markdown" + ) HTML Style ---------- -To use this mode, pass "html" in the *parse_mode* field when using :meth:`~pyrogram.Client.send_message`. +To strictly use this mode, pass "html" to the *parse_mode* parameter when using :meth:`~pyrogram.Client.send_message`. The following tags are currently supported: .. code-block:: text @@ -43,55 +102,124 @@ The following tags are currently supported: italic, italic - inline URL + underline - inline mention of a user + strike, strike, strike + + text URL + + inline mention inline fixed-width code -
pre-formatted fixed-width code block
+
+    pre-formatted
+      fixed-width
+        code block
+    
-.. note:: Mentions are only guaranteed to work if you have already met the user (in groups or private chats). +**Example**: -Examples --------- +.. code-block:: python -- Markdown: + app.send_message( + "haskell", + ( + "bold, " + "italic, " + "underline, " + "strikethrough, " + "mention, " + "URL, " + "code\n\n" + "
"
+            "for i in range(10):\n"
+            "    print(i)"
+            "
" + ), + parse_mode="html" + ) + +.. note:: + + All ``<``, ``>`` and ``&`` symbols that are not a part of a tag or an HTML entity must be replaced with the + corresponding HTML entities (``<`` with ``<``, ``>`` with ``>`` and ``&`` with ``&``). You can use this + snippet to quickly escape those characters: .. code-block:: python - app.send_message( - chat_id="haskell", - text=( - "**bold**, " - "__italic__, " - "[mention](tg://user?id=23122162), " - "[URL](https://docs.pyrogram.org), " - "`code`, " - "```" - "for i in range(10):\n" - " print(i)```" - ) - ) + import html -- HTML: + text = "" + text = html.escape(text) - .. code-block:: python + print(text) - app.send_message( - chat_id="haskell", - text=( - "bold, " - "italic, " - "mention, " - "URL, " - "code, " - "
"
-                "for i in range(10):\n"
-                "    print(i)"
-                "
" - ), - parse_mode="html" - ) + .. code-block:: text -.. _Telegram Bot API: https://core.telegram.org/bots/api#formatting-options \ No newline at end of file + <my text> + +Different Styles +---------------- + +By default, when ignoring the *parse_mode* parameter, both Markdown and HTML styles are enabled together. +This means you can combine together both syntaxes in the same text: + +.. code-block:: python + + app.send_message("haskell", "**bold**, italic") + +Result: + + **bold**, *italic* + +If you don't like this behaviour you can always choose to only enable either Markdown or HTML in strict mode by passing +"markdown" or "html" as argument to the *parse_mode* parameter. + +.. code-block:: + + app.send_message("haskell", "**bold**, italic", parse_mode="markdown") + app.send_message("haskell", "**bold**, italic", parse_mode="html") + +Result: + + **bold**, italic + + \*\*bold**, *italic* + +In case you want to completely turn off the style parser, simply pass ``None`` to *parse_mode*. The text will be sent +as-is. + +.. code-block:: python + + app.send_message("haskell", "**bold**, italic", parse_mode=None) + +Result: + + \*\*bold**, italic + +Nested and Overlapping Entities +------------------------------- + +You can also style texts with more than one decoration at once by nesting entities together. For example, you can send +a text message with both :bold-underline:`bold and underline` styles, or a text that has both :italic-strike:`italic and +strike` styles, and you can still combine both Markdown and HTML together. + +Here there are some example texts you can try sending: + +**Markdown**: + +- ``**bold, --underline--**`` +- ``**bold __italic --underline ~~striked~~--__**`` +- ``**bold __and** italic__`` + +**HTML**: + +- ``bold, underline`` +- ``bold italic underline striked`` +- ``bold and italic`` + +**Combined**: + +- ``--you can combine HTML with **Markdown**--`` +- ``**and also overlap** --entities this way--`` From 9f231bb880d99e7bef196f7914bc6e92a7cc4a89 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 27 Jun 2019 23:15:46 +0200 Subject: [PATCH 086/202] Update errors documentation page --- docs/source/start/errors.rst | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/docs/source/start/errors.rst b/docs/source/start/errors.rst index cf329947..bd82bf73 100644 --- a/docs/source/start/errors.rst +++ b/docs/source/start/errors.rst @@ -28,7 +28,7 @@ Error Categories ---------------- The ``RPCError`` packs together all the possible errors Telegram could raise, but to make things tidier, Pyrogram -provides categories of errors, which are named after the common HTTP errors and subclass-ed from the RPCError: +provides categories of errors, which are named after the common HTTP errors and are subclass-ed from the RPCError: .. code-block:: python @@ -71,14 +71,22 @@ RPCError, thus building a class of error hierarchy such as this: Unknown Errors -------------- -In case Pyrogram does not know anything yet about a specific error, it raises a special ``520 - UnknownError`` exception -and logs it in the ``unknown_errors.txt`` file. Users are invited to report these unknown errors. +In case Pyrogram does not know anything about a specific error yet, it raises a generic error from its known category, +for example, an unknown error with error code ``400``, will be raised as a ``BadRequest``. This way you can catch the +whole category of errors and be sure to also handle these unknown errors. + +In case a whole class of errors is unknown (that is, an error code that is unknown), Pyrogram will raise a special +``520 UnknownError`` exception. + +In both cases, Pyrogram will log them in the ``unknown_errors.txt`` file. Users are invited to report +these unknown errors in the `discussion group `_. Errors with Values ------------------ Exception objects may also contain some informative values. For example, ``FloodWait`` holds the amount of seconds you -have to wait before you can try again. The value is always stored in the ``x`` field of the returned exception object: +have to wait before you can try again, some other errors contain the DC number on which the request must be repeated on. +The value is stored in the ``x`` attribute of the exception object: .. code-block:: python @@ -88,4 +96,4 @@ have to wait before you can try again. The value is always stored in the ``x`` f try: ... except FloodWait as e: - time.sleep(e.x) # Wait before trying again + time.sleep(e.x) # Wait "x" seconds before continuing From b6f508711aff07b7a35f9f271514c4385bb4c2f4 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 27 Jun 2019 23:16:21 +0200 Subject: [PATCH 087/202] Many minor documentation enhancements --- docs/source/api/client.rst | 6 +++++- docs/source/api/decorators.rst | 2 +- docs/source/api/handlers.rst | 3 --- docs/source/api/methods.rst | 2 +- docs/source/api/types.rst | 2 +- docs/source/index.rst | 2 +- docs/source/start/updates.rst | 12 +++++++----- docs/source/topics/storage-engines.rst | 2 +- docs/source/topics/use-filters.rst | 4 ++-- 9 files changed, 19 insertions(+), 16 deletions(-) diff --git a/docs/source/api/client.rst b/docs/source/api/client.rst index d1b8c4b0..d28b7f61 100644 --- a/docs/source/api/client.rst +++ b/docs/source/api/client.rst @@ -1,7 +1,11 @@ Pyrogram Client =============== -This is the Client class. It exposes high-level methods for an easy access to the API. +You have entered the API Reference section where you can find detailed information about Pyrogram's API. The main Client +class, all available methods and types, filters, handlers, decorators and bound-methods detailed descriptions can be +found starting from this page. + +This page is about the Client class, which exposes high-level methods for an easy access to the API. .. code-block:: python :emphasize-lines: 1-3 diff --git a/docs/source/api/decorators.rst b/docs/source/api/decorators.rst index ff31cb27..fd397cc4 100644 --- a/docs/source/api/decorators.rst +++ b/docs/source/api/decorators.rst @@ -6,7 +6,7 @@ deserve a dedicated page. Decorators are able to register callback functions for handling updates in a much easier and cleaner way compared to :doc:`Handlers `; they do so by instantiating the correct handler and calling -:meth:`~pyrogram.Client.add_handler`, automatically. All you need to do is adding the decorators on top of your +:meth:`~pyrogram.Client.add_handler` automatically. All you need to do is adding the decorators on top of your functions. .. code-block:: python diff --git a/docs/source/api/handlers.rst b/docs/source/api/handlers.rst index f91dd3d5..1ae0961b 100644 --- a/docs/source/api/handlers.rst +++ b/docs/source/api/handlers.rst @@ -2,10 +2,7 @@ Update Handlers =============== Handlers are used to instruct Pyrogram about which kind of updates you'd like to handle with your callback functions. - For a much more convenient way of registering callback functions have a look at :doc:`Decorators ` instead. -In case you decided to manually create a handler, use :class:`~pyrogram.Client.add_handler` to register -it. .. code-block:: python :emphasize-lines: 1, 10 diff --git a/docs/source/api/methods.rst b/docs/source/api/methods.rst index 2a08b37f..0cd7700f 100644 --- a/docs/source/api/methods.rst +++ b/docs/source/api/methods.rst @@ -1,7 +1,7 @@ Available Methods ================= -All Pyrogram methods listed here are bound to a :class:`~pyrogram.Client` instance. +This page is about Pyrogram methods. All the methods listed here are bound to a :class:`~pyrogram.Client` instance. .. code-block:: python :emphasize-lines: 6 diff --git a/docs/source/api/types.rst b/docs/source/api/types.rst index 644f8bb2..f91e4f58 100644 --- a/docs/source/api/types.rst +++ b/docs/source/api/types.rst @@ -1,7 +1,7 @@ Available Types =============== -All Pyrogram types listed here are accessible through the main package directly. +This page is about Pyrogram types. All types listed here are accessible through the main package directly. .. code-block:: python :emphasize-lines: 1 diff --git a/docs/source/index.rst b/docs/source/index.rst index 66722690..682c883c 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -28,8 +28,8 @@ Welcome to Pyrogram api/bound-methods api/handlers api/decorators - api/filters api/errors + api/filters .. toctree:: :hidden: diff --git a/docs/source/start/updates.rst b/docs/source/start/updates.rst index 9b0a5a32..056fcb3d 100644 --- a/docs/source/start/updates.rst +++ b/docs/source/start/updates.rst @@ -45,7 +45,9 @@ arrives: app.run() -#. Let's examine these four new pieces. First one: a callback function we defined which accepts two arguments - +Let's examine these four new pieces. + +#. A callback function we defined which accepts two arguments - *(client, message)*. This will be the function that gets executed every time a new message arrives and Pyrogram will call that function by passing the client instance and the new message instance as argument. @@ -54,14 +56,14 @@ arrives: def my_function(client, message): print(message) -#. Second one: the :class:`~pyrogram.MessageHandler`. This object tells Pyrogram the function we defined above must - only handle updates that are in form of a :class:`~pyrogram.Message`: +#. The :class:`~pyrogram.MessageHandler`. This object tells Pyrogram the function we defined above must only handle + updates that are in form of a :class:`~pyrogram.Message`: .. code-block:: python my_handler = MessageHandler(my_function) -#. Third: the method :meth:`~pyrogram.Client.add_handler`. This method is used to actually register the handler and let +#. The method :meth:`~pyrogram.Client.add_handler`. This method is used to actually register the handler and let Pyrogram know it needs to be taken into consideration when new updates arrive and the internal dispatching phase begins. @@ -69,7 +71,7 @@ arrives: app.add_handler(my_handler) -#. Last one, the :meth:`~pyrogram.Client.run` method. What this does is simply call :meth:`~pyrogram.Client.start` and +#. The :meth:`~pyrogram.Client.run` method. What this does is simply call :meth:`~pyrogram.Client.start` and a special method :meth:`~pyrogram.Client.idle` that keeps your main scripts alive until you press ``CTRL+C``; the client will be automatically stopped after that. diff --git a/docs/source/topics/storage-engines.rst b/docs/source/topics/storage-engines.rst index a161e50f..44b4afa6 100644 --- a/docs/source/topics/storage-engines.rst +++ b/docs/source/topics/storage-engines.rst @@ -95,5 +95,5 @@ engine to properly work as intended. But, why is the session string so long? Can't it be shorter? No, it can't. The session string already packs the bare minimum data Pyrogram needs to successfully reconnect to an authorized session, and the 2048-bits auth key is the major -contributor to the overall length. Needless to repeat that this string, as well as any other session storage, represent +contributor to the overall length. Needless to say that this string, as well as any other session storage, represent strictly personal data. Keep them safe. diff --git a/docs/source/topics/use-filters.rst b/docs/source/topics/use-filters.rst index d481b393..de7a35a8 100644 --- a/docs/source/topics/use-filters.rst +++ b/docs/source/topics/use-filters.rst @@ -1,8 +1,8 @@ Using Filters ============= -So far we've seen how to register a callback function that executes every time a specific update comes from the server, -but there's much more than that to come. +So far we've seen :doc:`how to register a callback function <../start/updates>` that executes every time a specific update +comes from the server, but there's much more than that to come. Here we'll discuss about :class:`~pyrogram.Filters`. Filters enable a fine-grain control over what kind of updates are allowed or not to be passed in your callback functions, based on their inner details. From 506253e506a8c22175346e92d7a017cd6118013b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 28 Jun 2019 10:41:57 +0200 Subject: [PATCH 088/202] Fix objects failing to print in case there's no __slots__ attribute --- pyrogram/client/types/object.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/types/object.py b/pyrogram/client/types/object.py index 4d482e63..f7fc413f 100644 --- a/pyrogram/client/types/object.py +++ b/pyrogram/client/types/object.py @@ -50,7 +50,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 obj.__slots__ + for attr in getattr(obj, "__slots__", []) if getattr(obj, attr) is not None ] ) From 155580649ac7781029393ccdbab084a48d3ba853 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 28 Jun 2019 11:11:59 +0200 Subject: [PATCH 089/202] Update filters: Make the name argument optional --- docs/source/topics/create-filters.rst | 39 +++---- pyrogram/client/filters/filters.py | 149 +++++++++++++------------- 2 files changed, 92 insertions(+), 96 deletions(-) diff --git a/docs/source/topics/create-filters.rst b/docs/source/topics/create-filters.rst index 6cb33a50..6ae6e98c 100644 --- a/docs/source/topics/create-filters.rst +++ b/docs/source/topics/create-filters.rst @@ -24,7 +24,7 @@ button: app.send_message( "username", # Change this to your username or id - "Pyrogram's custom filter test", + "Pyrogram custom filter test", reply_markup=InlineKeyboardMarkup( [[InlineKeyboardButton("Press me", "pyrogram")]] ) @@ -33,61 +33,54 @@ button: Basic Filters ------------- -For this basic filter we will be using only the first two parameters of :meth:`~pyrogram.Filters.create`. +For this basic filter we will be using only the first parameter of :meth:`~pyrogram.Filters.create`. The code below creates a simple filter for hardcoded, static callback data. This filter will only allow callback queries -containing "Pyrogram" as data, that is, the function *func* you pass returns True in case the callback query data -equals to ``"Pyrogram"``. +containing "pyrogram" as data, that is, the function *func* you pass returns True in case the callback query data +equals to ``"pyrogram"``. .. code-block:: python - static_data = Filters.create( - name="StaticdData", - func=lambda flt, query: query.data == "Pyrogram" - ) + static_data_filter = Filters.create(lambda _, query: query.data == "pyrogram") The ``lambda`` operator in python is used to create small anonymous functions and is perfect for this example, the same -could be achieved with a normal function, but we don't really need it as it makes sense only inside the filter's scope: +could be achieved with a normal function, but we don't really need it as it makes sense only inside the filter scope: .. code-block:: python - def func(flt, query): - return query.data == "Pyrogram" + def func(_, query): + return query.data == "pyrogram" - static_data = Filters.create( - name="StaticData", - func=func - ) + static_data_filter = Filters.create(func) The filter usage remains the same: .. code-block:: python - @app.on_callback_query(static_data) + @app.on_callback_query(static_data_filter) def pyrogram_data(_, query): query.answer("it works!") Filters with Arguments ---------------------- -A much cooler filter would be one that accepts "Pyrogram" or any other data as argument at usage time. -A dynamic filter like this will make use of the third parameter of :meth:`~pyrogram.Filters.create`. +A much cooler filter would be one that accepts "pyrogram" or any other data as argument at usage time. +A dynamic filter like this will make use of named arguments for the :meth:`~pyrogram.Filters.create` method. This is how a dynamic custom filter looks like: .. code-block:: python - def dynamic_data(data): + def dynamic_data_filter(data): return Filters.create( - name="DynamicData", - func=lambda flt, query: flt.data == query.data, - data=data # "data" kwarg is accessed with "flt.data" + lambda flt, query: flt.data == query.data, + data=data # "data" kwarg is accessed with "flt.data" above ) And its usage: .. code-block:: python - @app.on_callback_query(dynamic_data("Pyrogram")) + @app.on_callback_query(dynamic_data_filter("pyrogram")) def pyrogram_data(_, query): query.answer("it works!") diff --git a/pyrogram/client/filters/filters.py b/pyrogram/client/filters/filters.py index fb0a3615..d8768b3b 100644 --- a/pyrogram/client/filters/filters.py +++ b/pyrogram/client/filters/filters.py @@ -17,190 +17,193 @@ # along with Pyrogram. If not, see . import re +from typing import Callable from .filter import Filter from ..types.bots_and_keyboards import InlineKeyboardMarkup, ReplyKeyboardMarkup +CUSTOM_FILTER_NAME = "CustomFilter" -def create(name: str, func: callable, **kwargs) -> type: - """Create a Filter. + +def create(func: Callable, name: str = None, **kwargs) -> Filter: + """Easily create a custom filter. Custom filters give you extra control over which updates are allowed or not to be processed by your handlers. Parameters: - name (``str``): - Your filter's name. Can be anything you like. - func (``callable``): - A function that accepts two arguments *(filter, update)* and returns a Boolean: True if the update should be - handled, False otherwise. - The "update" argument type will vary depending on which `Handler `_ is coming from. - For example, in a :obj:`MessageHandler` the update type will be - a :obj:`Message`; in a :obj:`CallbackQueryHandler` the - update type will be a :obj:`CallbackQuery`. Your function body can then access the - incoming update and decide whether to allow it or not. + A function that accepts two positional arguments *(filter, update)* and returns a boolean: True if the + update should be handled, False otherwise. The *filter* argument refers to the filter itself and can be used + to access keyword arguments (read below). The *update* argument type will vary depending on which + `Handler `_ is coming from. For example, in a :obj:`MessageHandler` the *update* argument will be + a :obj:`Message`; in a :obj:`CallbackQueryHandler` the *update* will be a :obj:`CallbackQuery`. Your + function body can then access the incoming update attributes and decide whether to allow it or not. + + name (``str``, *optional*): + Your filter's name. Can be anything you like. + Defaults to "CustomFilter". **kwargs (``any``, *optional*): - Any keyword argument you would like to pass. Useful for custom filters that accept parameters (e.g.: - :meth:`~Filters.command`, :meth:`~Filters.regex`). + Any keyword argument you would like to pass. Useful when creating parameterized custom filters, such as + :meth:`~Filters.command` or :meth:`~Filters.regex`. """ # TODO: unpack kwargs using **kwargs into the dict itself. For Python 3.5+ only d = {"__call__": func} d.update(kwargs) - return type(name, (Filter,), d)() + return type(name or CUSTOM_FILTER_NAME, (Filter,), d)() class Filters: """This class provides access to all library-defined Filters available in Pyrogram. - The Filters listed here are intended to be used with the :obj:`MessageHandler` only. + The Filters listed here are currently intended to be used with the :obj:`MessageHandler` only. At the moment, if you want to filter updates coming from different `Handlers `_ you have to create your own filters with :meth:`~Filters.create` and use them in the same way. """ create = create - me = create("Me", lambda _, m: bool(m.from_user and m.from_user.is_self)) + me = create(lambda _, m: bool(m.from_user and m.from_user.is_self), "MeFilter") """Filter messages generated by you yourself.""" - bot = create("Bot", lambda _, m: bool(m.from_user and m.from_user.is_bot)) + bot = create(lambda _, m: bool(m.from_user and m.from_user.is_bot), "BotFilter") """Filter messages coming from bots.""" - incoming = create("Incoming", lambda _, m: not m.outgoing) + incoming = create(lambda _, m: not m.outgoing, "IncomingFilter") """Filter incoming messages. Messages sent to your own chat (Saved Messages) are also recognised as incoming.""" - outgoing = create("Outgoing", lambda _, m: m.outgoing) + outgoing = create(lambda _, m: m.outgoing, "OutgoingFilter") """Filter outgoing messages. Messages sent to your own chat (Saved Messages) are not recognized as outgoing.""" - text = create("Text", lambda _, m: bool(m.text)) + text = create(lambda _, m: bool(m.text), "TextFilter") """Filter text messages.""" - reply = create("Reply", lambda _, m: bool(m.reply_to_message)) + reply = create(lambda _, m: bool(m.reply_to_message), "ReplyFilter") """Filter messages that are replies to other messages.""" - forwarded = create("Forwarded", lambda _, m: bool(m.forward_date)) + forwarded = create(lambda _, m: bool(m.forward_date), "ForwardedFilter") """Filter messages that are forwarded.""" - caption = create("Caption", lambda _, m: bool(m.caption)) + caption = create(lambda _, m: bool(m.caption), "CaptionFilter") """Filter media messages that contain captions.""" - edited = create("Edited", lambda _, m: bool(m.edit_date)) + edited = create(lambda _, m: bool(m.edit_date), "EditedFilter") """Filter edited messages.""" - audio = create("Audio", lambda _, m: bool(m.audio)) + audio = create(lambda _, m: bool(m.audio), "AudioFilter") """Filter messages that contain :obj:`Audio` objects.""" - document = create("Document", lambda _, m: bool(m.document)) + document = create(lambda _, m: bool(m.document), "DocumentFilter") """Filter messages that contain :obj:`Document` objects.""" - photo = create("Photo", lambda _, m: bool(m.photo)) + photo = create(lambda _, m: bool(m.photo), "PhotoFilter") """Filter messages that contain :obj:`Photo` objects.""" - sticker = create("Sticker", lambda _, m: bool(m.sticker)) + sticker = create(lambda _, m: bool(m.sticker), "StickerFilter") """Filter messages that contain :obj:`Sticker` objects.""" - animation = create("Animation", lambda _, m: bool(m.animation)) + animation = create(lambda _, m: bool(m.animation), "AnimationFilter") """Filter messages that contain :obj:`Animation` objects.""" - game = create("Game", lambda _, m: bool(m.game)) + game = create(lambda _, m: bool(m.game), "GameFilter") """Filter messages that contain :obj:`Game` objects.""" - video = create("Video", lambda _, m: bool(m.video)) + video = create(lambda _, m: bool(m.video), "VideoFilter") """Filter messages that contain :obj:`Video` objects.""" - media_group = create("MediaGroup", lambda _, m: bool(m.media_group_id)) + media_group = create(lambda _, m: bool(m.media_group_id), "MediaGroupFilter") """Filter messages containing photos or videos being part of an album.""" - voice = create("Voice", lambda _, m: bool(m.voice)) + voice = create(lambda _, m: bool(m.voice), "VoiceFilter") """Filter messages that contain :obj:`Voice` note objects.""" - video_note = create("VideoNote", lambda _, m: bool(m.video_note)) + video_note = create(lambda _, m: bool(m.video_note), "VideoNoteFilter") """Filter messages that contain :obj:`VideoNote` objects.""" - contact = create("Contact", lambda _, m: bool(m.contact)) + contact = create(lambda _, m: bool(m.contact), "ContactFilter") """Filter messages that contain :obj:`Contact` objects.""" - location = create("Location", lambda _, m: bool(m.location)) + location = create(lambda _, m: bool(m.location), "LocationFilter") """Filter messages that contain :obj:`Location` objects.""" - venue = create("Venue", lambda _, m: bool(m.venue)) + venue = create(lambda _, m: bool(m.venue), "VenueFilter") """Filter messages that contain :obj:`Venue` objects.""" - web_page = create("WebPage", lambda _, m: m.web_page) + web_page = create(lambda _, m: m.web_page, "WebPageFilter") """Filter messages sent with a webpage preview.""" - poll = create("Poll", lambda _, m: m.poll) + poll = create(lambda _, m: m.poll, "PollFilter") """Filter messages that contain :obj:`Poll` objects.""" - private = create("Private", lambda _, m: bool(m.chat and m.chat.type == "private")) + private = create(lambda _, m: bool(m.chat and m.chat.type == "private"), "PrivateFilter") """Filter messages sent in private chats.""" - group = create("Group", lambda _, m: bool(m.chat and m.chat.type in {"group", "supergroup"})) + group = create(lambda _, m: bool(m.chat and m.chat.type in {"group", "supergroup"}), "GroupFilter") """Filter messages sent in group or supergroup chats.""" - channel = create("Channel", lambda _, m: bool(m.chat and m.chat.type == "channel")) + channel = create(lambda _, m: bool(m.chat and m.chat.type == "channel"), "ChannelFilter") """Filter messages sent in channels.""" - new_chat_members = create("NewChatMembers", lambda _, m: bool(m.new_chat_members)) + new_chat_members = create(lambda _, m: bool(m.new_chat_members), "NewChatMembersFilter") """Filter service messages for new chat members.""" - left_chat_member = create("LeftChatMember", lambda _, m: bool(m.left_chat_member)) + left_chat_member = create(lambda _, m: bool(m.left_chat_member), "LeftChatMemberFilter") """Filter service messages for members that left the chat.""" - new_chat_title = create("NewChatTitle", lambda _, m: bool(m.new_chat_title)) + new_chat_title = create(lambda _, m: bool(m.new_chat_title), "NewChatTitleFilter") """Filter service messages for new chat titles.""" - new_chat_photo = create("NewChatPhoto", lambda _, m: bool(m.new_chat_photo)) + new_chat_photo = create(lambda _, m: bool(m.new_chat_photo), "NewChatPhotoFilter") """Filter service messages for new chat photos.""" - delete_chat_photo = create("DeleteChatPhoto", lambda _, m: bool(m.delete_chat_photo)) + delete_chat_photo = create(lambda _, m: bool(m.delete_chat_photo), "DeleteChatPhotoFilter") """Filter service messages for deleted photos.""" - group_chat_created = create("GroupChatCreated", lambda _, m: bool(m.group_chat_created)) + group_chat_created = create(lambda _, m: bool(m.group_chat_created), "GroupChatCreatedFilter") """Filter service messages for group chat creations.""" - supergroup_chat_created = create("SupergroupChatCreated", lambda _, m: bool(m.supergroup_chat_created)) + supergroup_chat_created = create(lambda _, m: bool(m.supergroup_chat_created), "SupergroupChatCreatedFilter") """Filter service messages for supergroup chat creations.""" - channel_chat_created = create("ChannelChatCreated", lambda _, m: bool(m.channel_chat_created)) + channel_chat_created = create(lambda _, m: bool(m.channel_chat_created), "ChannelChatCreatedFilter") """Filter service messages for channel chat creations.""" - migrate_to_chat_id = create("MigrateToChatId", lambda _, m: bool(m.migrate_to_chat_id)) + migrate_to_chat_id = create(lambda _, m: bool(m.migrate_to_chat_id), "MigrateToChatIdFilter") """Filter service messages that contain migrate_to_chat_id.""" - migrate_from_chat_id = create("MigrateFromChatId", lambda _, m: bool(m.migrate_from_chat_id)) + migrate_from_chat_id = create(lambda _, m: bool(m.migrate_from_chat_id), "MigrateFromChatIdFilter") """Filter service messages that contain migrate_from_chat_id.""" - pinned_message = create("PinnedMessage", lambda _, m: bool(m.pinned_message)) + pinned_message = create(lambda _, m: bool(m.pinned_message), "PinnedMessageFilter") """Filter service messages for pinned messages.""" - game_high_score = create("GameHighScore", lambda _, m: bool(m.game_high_score)) + game_high_score = create(lambda _, m: bool(m.game_high_score), "GameHighScoreFilter") """Filter service messages for game high scores.""" - reply_keyboard = create("ReplyKeyboard", lambda _, m: isinstance(m.reply_markup, ReplyKeyboardMarkup)) + reply_keyboard = create(lambda _, m: isinstance(m.reply_markup, ReplyKeyboardMarkup), "ReplyKeyboardFilter") """Filter messages containing reply keyboard markups""" - inline_keyboard = create("InlineKeyboard", lambda _, m: isinstance(m.reply_markup, InlineKeyboardMarkup)) + inline_keyboard = create(lambda _, m: isinstance(m.reply_markup, InlineKeyboardMarkup), "InlineKeyboardFilter") """Filter messages containing inline keyboard markups""" - mentioned = create("Mentioned", lambda _, m: bool(m.mentioned)) + mentioned = create(lambda _, m: bool(m.mentioned), "MentionedFilter") """Filter messages containing mentions""" - via_bot = create("ViaBot", lambda _, m: bool(m.via_bot)) + via_bot = create(lambda _, m: bool(m.via_bot), "ViaBotFilter") """Filter messages sent via inline bots""" - service = create("Service", lambda _, m: bool(m.service)) + service = create(lambda _, m: bool(m.service), "ServiceFilter") """Filter service messages. - + A service message contains any of the following fields set: *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_score*. """ - media = create("Media", lambda _, m: bool(m.media)) + media = create(lambda _, m: bool(m.media), "MediaFilter") """Filter media messages. - + A media message contains any of the following fields set: *audio*, *document*, *photo*, *sticker*, *video*, *animation*, *voice*, *video_note*, *contact*, *location*, *venue*, *poll*. """ @@ -253,17 +256,17 @@ class Filters: commands = {c if case_sensitive else c.lower() for c in commands} prefixes = set(prefix) if prefix else {""} - return create("Command", func=func, c=commands, p=prefixes, s=separator, cs=case_sensitive) + return create(func, "CommandFilter", c=commands, p=prefixes, s=separator, cs=case_sensitive) @staticmethod def regex(pattern, flags: int = 0): - """Filter messages that match a given RegEx pattern. + """Filter message texts or captions that match a given regular expression pattern. Parameters: pattern (``str``): - The RegEx pattern as string, it will be applied to the text of a message. When a pattern matches, - all the `Match Objects `_ - are stored in the *matches* field of the :obj:`Message` itself. + The RegEx pattern as string, it will be applied to the text or the caption of a message. When a pattern + matches, all the `Match Objects `_ are stored + in the *matches* field of the :obj:`Message` itself. flags (``int``, *optional*): RegEx flags. @@ -273,7 +276,7 @@ class Filters: m.matches = [i for i in _.p.finditer(m.text or m.caption or "")] return bool(m.matches) - return create("Regex", f, p=re.compile(pattern, flags)) + return create(f, "RegexFilter", p=re.compile(pattern, flags)) # noinspection PyPep8Naming class user(Filter, set): @@ -285,7 +288,7 @@ class Filters: Parameters: users (``int`` | ``str`` | ``list``): Pass one or more user ids/usernames to filter users. - For you yourself, "me" or "self" can be used as well. + For you yourself, "me" or "self" can be used as well. Defaults to None (no users). """ @@ -342,12 +345,12 @@ class Filters: @staticmethod def callback_data(data: str or bytes): """Filter callback queries for their data. - + Parameters: data (``str`` | ``bytes``): Pass the data you want to filter for. """ - return create("CallbackData", lambda flt, cb: cb.data == flt.data, data=data) + return create(lambda flt, cb: cb.data == flt.data, "CallbackDataFilter", data=data) - dan = create("Dan", lambda _, m: bool(m.from_user and m.from_user.id == 23122162)) + dan = create(lambda _, m: bool(m.from_user and m.from_user.id == 23122162), "DanFilter") From 88632ae265329915830527451883ba1e0b698148 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 28 Jun 2019 16:29:16 +0200 Subject: [PATCH 090/202] Add block_user and unblock_user to docs --- docs/source/api/methods.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/source/api/methods.rst b/docs/source/api/methods.rst index 0cd7700f..9b2fc6c4 100644 --- a/docs/source/api/methods.rst +++ b/docs/source/api/methods.rst @@ -124,6 +124,8 @@ Users - :meth:`~Client.delete_profile_photos` - :meth:`~Client.update_username` - :meth:`~Client.get_user_dc` + - :meth:`~Client.block_user` + - :meth:`~Client.unblock_user` Contacts ^^^^^^^^ @@ -264,6 +266,8 @@ Details .. automethod:: Client.delete_profile_photos() .. automethod:: Client.update_username() .. automethod:: Client.get_user_dc() +.. automethod:: Client.block_user() +.. automethod:: Client.unblock_user() .. Contacts .. automethod:: Client.add_contacts() From 46bf3824800a5f372d87e63768873a7ce7058c39 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 28 Jun 2019 16:31:32 +0200 Subject: [PATCH 091/202] Fix self.parser not calling .parse for send_document --- pyrogram/client/methods/messages/send_document.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/methods/messages/send_document.py b/pyrogram/client/methods/messages/send_document.py index 06132688..e496b604 100644 --- a/pyrogram/client/methods/messages/send_document.py +++ b/pyrogram/client/methods/messages/send_document.py @@ -146,7 +146,7 @@ class SendDocument(BaseClient): reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), reply_markup=reply_markup.write() if reply_markup else None, - **self.parser(caption, parse_mode) + **self.parser.parse(caption, parse_mode) ) ) except FilePartMissing as e: From 59b43af02e79b80b1f3831e8157f2e128d61d263 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 28 Jun 2019 20:43:06 +0200 Subject: [PATCH 092/202] Fix RPCError not setting the x attribute --- pyrogram/errors/rpc_error.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pyrogram/errors/rpc_error.py b/pyrogram/errors/rpc_error.py index fb3a717d..e0acdc21 100644 --- a/pyrogram/errors/rpc_error.py +++ b/pyrogram/errors/rpc_error.py @@ -40,6 +40,11 @@ class RPCError(Exception): 'caused by "{}"'.format(rpc_name) )) + try: + self.x = int(x) + except (ValueError, TypeError): + self.x = x + if is_unknown: with open("unknown_errors.txt", "a", encoding="utf-8") as f: f.write("{}\t{}\t{}\n".format(datetime.now(), x, rpc_name)) From 54c8e24f483ea905d97161a6994c83aadfd0ac43 Mon Sep 17 00:00:00 2001 From: Mendel E Date: Fri, 28 Jun 2019 13:45:03 -0400 Subject: [PATCH 093/202] Add .set_ bound methods to Chat --- docs/source/api/bound-methods.rst | 6 ++ pyrogram/client/types/user_and_chats/chat.py | 107 +++++++++++++++++++ 2 files changed, 113 insertions(+) diff --git a/docs/source/api/bound-methods.rst b/docs/source/api/bound-methods.rst index 83b3dbbe..d129ad16 100644 --- a/docs/source/api/bound-methods.rst +++ b/docs/source/api/bound-methods.rst @@ -67,6 +67,9 @@ Chat - :meth:`~Chat.archive` - :meth:`~Chat.unarchive` + - :meth:`~Chat.set_title` + - :meth:`~Chat.set_description` + - :meth:`~Chat.set_photo` User ^^^^ @@ -134,6 +137,9 @@ Details .. Chat .. automethod:: Chat.archive() .. automethod:: Chat.unarchive() +.. automethod:: Chat.set_title() +.. automethod:: Chat.set_description) +.. automethod:: Chat.set_photo() .. User .. automethod:: User.archive() diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py index 2d88d3ed..e99bccea 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -312,3 +312,110 @@ class Chat(Object): """ return self._client.unarchive_chats(self.id) + + def set_title(self, title: str) -> bool: + """Bound method *set_title* of :obj:`Chat`. + + Use as a shortcut for: + + .. code-block:: python + + client.set_chat_title( + chat_id=chat_id, + title=title + ) + + Example: + .. code-block:: python + + chat.set_title("Lounge") + + Note: + In regular groups (non-supergroups), this method will only work if the "All Members Are Admins" + setting is off. + + Parameters: + title (``str``): + New chat title, 1-255 characters. + + Returns: + ``bool``: True on success. + + Raises: + RPCError: In case of Telegram RPC error. + ValueError: In case a chat_id belongs to user. + """ + + return self._client.set_chat_title( + chat_id=self.id, + title=title + ) + + def set_description(self, description: str) -> bool: + """Bound method *set_description* of :obj:`Chat`. + + Use as a shortcut for: + + .. code-block:: python + + client.set_chat_description( + chat_id=chat_id, + description=description + ) + + Example: + .. code-block:: python + + chat.set_chat_description("Don't spam!") + + Parameters: + description (``str``): + New chat description, 0-255 characters. + + Returns: + ``bool``: True on success. + + Raises: + RPCError: In case of Telegram RPC error. + ValueError If a chat_id doesn't belong to a supergroup or a channel. + """ + + return self._client.set_chat_description( + chat_id=self.id, + description=description + ) + + + def set_photo(self, photo: str) -> bool: + """Bound method *set_photo* of :obj:`Chat`. + + Use as a shortcut for: + + .. code-block:: python + + client.set_chat_photo( + chat_id=chat_id, + photo=photo + ) + + Example: + .. code-block:: python + + chat.set_photo("photo.png") + + Parameters: + photo (``str``): + New chat photo. You can pass a :obj:`Photo` id or a file path to upload a new photo. + + Returns: + ``bool``: True on success. + + Raises: + RPCError: In case of a Telegram RPC error. + ValueError: if a chat_id belongs to user. + """ + + return self._client.set_chat_photo( + chat_id=self.id, + photo=photo + ) From 2c1834b1b2191f066e26db38275632c294f39d18 Mon Sep 17 00:00:00 2001 From: Mendel E Date: Fri, 28 Jun 2019 20:22:20 -0400 Subject: [PATCH 094/202] Add .(kick|unban|restrict|promote)_member bound methods to Chat --- docs/source/api/bound-methods.rst | 8 + pyrogram/client/types/user_and_chats/chat.py | 255 +++++++++++++++++++ 2 files changed, 263 insertions(+) diff --git a/docs/source/api/bound-methods.rst b/docs/source/api/bound-methods.rst index d129ad16..6a1cf1c3 100644 --- a/docs/source/api/bound-methods.rst +++ b/docs/source/api/bound-methods.rst @@ -70,6 +70,10 @@ Chat - :meth:`~Chat.set_title` - :meth:`~Chat.set_description` - :meth:`~Chat.set_photo` + - :meth:`~Chat.kick_member` + - :meth:`~Chat.unban_member` + - :meth:`~Chat.restrict_member` + - :meth:`~Chat.promote_member` User ^^^^ @@ -140,6 +144,10 @@ Details .. automethod:: Chat.set_title() .. automethod:: Chat.set_description) .. automethod:: Chat.set_photo() +.. automethod:: Chat.kick_member() +.. automethod:: Chat.unban_member() +.. automethod:: Chat.restrict_member() +.. automethod:: Chat.promote_member() .. User .. automethod:: User.archive() diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py index e99bccea..556672d2 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -419,3 +419,258 @@ class Chat(Object): chat_id=self.id, photo=photo ) + + def kick_member( + self, + user_id: Union[int, str], + until_date: int = 0 + ) -> Union["pyrogram.Message", bool]: + """Bound method *kick_member* of :obj:`Chat`. + + Use as a shortcut for: + + .. code-block:: python + + client.kick_chat_member( + chat_id=chat_id, + user_id=user_id + ) + + Example: + .. code-block:: python + + chat.kick_member(123456789) + + Note: + In regular groups (non-supergroups), this method will only work if the "All Members Are Admins" setting is + off in the target group. Otherwise members may only be removed by the group's creator or by the member + that added them. + + Parameters: + user_id (``int`` | ``str``): + 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). + + 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). + + Returns: + :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. + """ + + return self._client.kick_chat_member( + chat_id=self.id, + user_id=user_id, + until_date=until_date + ) + + def unban_member( + self, + user_id: Union[int, str] + ) -> bool: + """Bound method *unban_member* of :obj:`Chat`. + + Use as a shortcut for: + + .. code-block:: python + + client.unban_chat_member( + chat_id=chat_id, + user_id=user_id + ) + + Example: + .. code-block:: python + + chat.unban_member(123456789) + + Parameters: + user_id (``int`` | ``str``): + 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). + + Returns: + ``bool``: True on success. + + Raises: + RPCError: In case of a Telegram RPC error. + """ + + return self._client.unban_chat_member( + chat_id=self.id, + user_id=user_id, + ) + + def restrict_member( + 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 + ) -> "pyrogram.Chat": + """Bound method *unban_member* of :obj:`Chat`. + + Use as a shortcut for: + + .. code-block:: python + + client.restrict_chat_member( + chat_id=chat_id, + user_id=user_id + ) + + Example: + .. code-block:: python + + chat.restrict_member(123456789) + + Parameters: + user_id (``int`` | ``str``): + 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). + + 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_media_messages. + + can_add_web_page_previews (``bool``, *optional*): + Pass True, if the user may add web page previews to their messages, implies can_send_media_messages. + + can_send_polls (``bool``, *optional*): + Pass True, if the user can send polls, implies can_send_media_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. + """ + + return self._client.restrict_chat_member( + self, + chat_id=self.id, + user_id=user_id, + until_date=until_date, + can_send_messages=can_send_messages, + can_send_media_messages=can_send_media_messages, + can_send_other_messages=can_send_other_messages, + can_add_web_page_previews=can_add_web_page_previews, + can_send_polls=can_send_polls, + can_change_info=can_change_info, + can_invite_users=can_invite_users, + can_pin_messages=can_pin_messages + ) + + def promote_member( + chat_id: Union[int, str], + user_id: Union[int, str], + can_change_info: bool = True, + can_post_messages: bool = False, + can_edit_messages: bool = False, + can_delete_messages: bool = True, + can_restrict_members: bool = True, + can_invite_users: bool = True, + can_pin_messages: bool = False, + can_promote_members: bool = False + ) -> bool: + """Bound method *promote_member* of :obj:`Chat`. + + Use as a shortcut for: + + .. code-block:: python + + client.promote_chat_member( + chat_id=chat_id, + user_id=user_id + ) + + Example: + + .. code-block:: python + + chat.promote_member(123456789) + + Parameters: + user_id (``int`` | ``str``): + 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). + + can_change_info (``bool``, *optional*): + Pass True, if the administrator can change chat title, photo and other settings. + + can_post_messages (``bool``, *optional*): + Pass True, if the administrator can create channel posts, channels only. + + can_edit_messages (``bool``, *optional*): + Pass True, if the administrator can edit messages of other users and can pin messages, channels only. + + can_delete_messages (``bool``, *optional*): + Pass True, if the administrator can delete messages of other users. + + can_restrict_members (``bool``, *optional*): + Pass True, if the administrator can restrict, ban or unban chat members. + + can_invite_users (``bool``, *optional*): + Pass True, if the administrator can invite new users to the chat. + + can_pin_messages (``bool``, *optional*): + Pass True, if the administrator can pin messages, supergroups only. + + can_promote_members (``bool``, *optional*): + Pass 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 him). + + Returns: + ``bool``: True on success. + + Raises: + RPCError: In case of a Telegram RPC error. + """ + + return self._client.promote_chat_member( + chat_id=self.id, + user_id=user_id, + can_change_info=can_change_info, + can_post_messages=can_post_messages, + can_edit_messages=can_edit_messages, + can_delete_messages=can_delete_messages, + can_restrict_members=can_restrict_members, + can_invite_users=can_invite_users, + can_pin_messages=can_pin_messages, + can_promote_members=can_promote_members + ) From a02cd271c94540944977eb304e90c5d6415e924b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 30 Jun 2019 10:01:39 +0200 Subject: [PATCH 095/202] Update chat.py --- pyrogram/client/types/user_and_chats/chat.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py index 556672d2..0e53ae35 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -313,6 +313,7 @@ class Chat(Object): return self._client.unarchive_chats(self.id) + # TODO: Remove notes about "All Members Are Admins" for basic groups, the attribute doesn't exist anymore def set_title(self, title: str) -> bool: """Bound method *set_title* of :obj:`Chat`. From 80d7a8cbbd4fd1e4b04da8a5209e8e01cc78783b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 30 Jun 2019 10:03:45 +0200 Subject: [PATCH 096/202] Add missing colon --- pyrogram/client/types/user_and_chats/chat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py index 0e53ae35..90205fbf 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -378,7 +378,7 @@ class Chat(Object): Raises: RPCError: In case of 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. """ return self._client.set_chat_description( From 09e1ac5eb49b3024eef7c1a0bfaccae192aac3a1 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 30 Jun 2019 10:48:19 +0200 Subject: [PATCH 097/202] Fix bad params when defining and calling methods --- pyrogram/client/types/user_and_chats/chat.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py index 90205fbf..586b49fe 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -510,7 +510,6 @@ class Chat(Object): def restrict_member( self, - chat_id: Union[int, str], user_id: Union[int, str], until_date: int = 0, can_send_messages: bool = False, @@ -582,7 +581,6 @@ class Chat(Object): """ return self._client.restrict_chat_member( - self, chat_id=self.id, user_id=user_id, until_date=until_date, @@ -597,7 +595,7 @@ class Chat(Object): ) def promote_member( - chat_id: Union[int, str], + self, user_id: Union[int, str], can_change_info: bool = True, can_post_messages: bool = False, From 580c684cb3a60d73a3c0a1aa90012a47f9dcc466 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 30 Jun 2019 16:13:10 +0200 Subject: [PATCH 098/202] Fix export_chat_invite_link not working correctly (channels/supergroups) --- .../client/methods/chats/export_chat_invite_link.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/pyrogram/client/methods/chats/export_chat_invite_link.py b/pyrogram/client/methods/chats/export_chat_invite_link.py index 9266183d..6e39dd84 100644 --- a/pyrogram/client/methods/chats/export_chat_invite_link.py +++ b/pyrogram/client/methods/chats/export_chat_invite_link.py @@ -48,18 +48,15 @@ class ExportChatInviteLink(BaseClient): Raises: RPCError: In case of a Telegram RPC error. + ValueError: In case the chat_id belongs to a user. """ peer = self.resolve_peer(chat_id) - if isinstance(peer, types.InputPeerChat): + if isinstance(peer, (types.InputPeerChannel, types.InputPeerChat)): 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)) From c76a62964f166801286d435f5b8db8b67e317d95 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 30 Jun 2019 18:51:04 +0200 Subject: [PATCH 099/202] Update: consistent examples --- docs/source/topics/text-formatting.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/source/topics/text-formatting.rst b/docs/source/topics/text-formatting.rst index 03b50a8e..fc8b952c 100644 --- a/docs/source/topics/text-formatting.rst +++ b/docs/source/topics/text-formatting.rst @@ -78,7 +78,7 @@ To strictly use this mode, pass "markdown" to the *parse_mode* parameter when us "**bold**, " "__italic__, " "--underline--, " - "~~strikethrough~~, " + "~~strike~~, " "[mention](tg://user?id=23122162), " "[URL](https://pyrogram.org), " "`code`, " @@ -128,7 +128,7 @@ The following tags are currently supported: "bold, " "italic, " "underline, " - "strikethrough, " + "strike, " "mention, " "URL, " "code\n\n" @@ -210,13 +210,13 @@ Here there are some example texts you can try sending: **Markdown**: - ``**bold, --underline--**`` -- ``**bold __italic --underline ~~striked~~--__**`` +- ``**bold __italic --underline ~~strike~~--__**`` - ``**bold __and** italic__`` **HTML**: - ``bold, underline`` -- ``bold italic underline striked`` +- ``bold italic underline strike`` - ``bold and italic`` **Combined**: From a05ac9d8a3993595c3a28719f02dd7c1b1951178 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 30 Jun 2019 19:22:19 +0200 Subject: [PATCH 100/202] Update errors prune path --- MANIFEST.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index 79c547f6..9f4d328c 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,7 +4,7 @@ recursive-include compiler *.py *.tl *.tsv *.txt recursive-include pyrogram mime.types schema.sql ## Exclude -prune pyrogram/api/errors/exceptions +prune pyrogram/errors/exceptions prune pyrogram/api/functions prune pyrogram/api/types exclude pyrogram/api/all.py \ No newline at end of file From 83c386cbecfc57884560623063deb8483d99371c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 30 Jun 2019 20:53:05 +0200 Subject: [PATCH 101/202] Use consistent naming --- pyrogram/client/storage/memory_storage.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/storage/memory_storage.py b/pyrogram/client/storage/memory_storage.py index bf000f35..1c32b3f3 100644 --- a/pyrogram/client/storage/memory_storage.py +++ b/pyrogram/client/storage/memory_storage.py @@ -59,8 +59,8 @@ class MemoryStorage(Storage): (1, None, None, 0, None, None) ) - def _import_session_string(self, string_session: str): - decoded = base64.urlsafe_b64decode(string_session + "=" * (-len(string_session) % 4)) + def _import_session_string(self, session_string: str): + decoded = base64.urlsafe_b64decode(session_string + "=" * (-len(session_string) % 4)) return struct.unpack(self.SESSION_STRING_FMT, decoded) def export_session_string(self): From 40b0c57b54d634eb0a83f172100e184d5ab20e14 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 30 Jun 2019 20:53:35 +0200 Subject: [PATCH 102/202] Don't rename the in-memory sessions --- pyrogram/client/storage/memory_storage.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pyrogram/client/storage/memory_storage.py b/pyrogram/client/storage/memory_storage.py index 1c32b3f3..ff4cb540 100644 --- a/pyrogram/client/storage/memory_storage.py +++ b/pyrogram/client/storage/memory_storage.py @@ -86,8 +86,6 @@ class MemoryStorage(Storage): self.dc_id, self.test_mode, self.auth_key, self.user_id, self.is_bot = imported_session_string self.date = 0 - self.name = ":memory:" + str(self.user_id or "") - # noinspection PyAttributeOutsideInit def save(self): self.date = int(time.time()) From a790431274f65a19392fa905a726c5b0a302c063 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 30 Jun 2019 21:08:50 +0200 Subject: [PATCH 103/202] Do string conversion and striping in the Parser --- pyrogram/client/parser/html.py | 2 +- pyrogram/client/parser/parser.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pyrogram/client/parser/html.py b/pyrogram/client/parser/html.py index 16c5922a..04f4ad30 100644 --- a/pyrogram/client/parser/html.py +++ b/pyrogram/client/parser/html.py @@ -108,7 +108,7 @@ class HTML: self.client = client def parse(self, text: str): - text = utils.add_surrogates(str(text or "").strip()) + text = utils.add_surrogates(text) parser = Parser(self.client) parser.feed(text) diff --git a/pyrogram/client/parser/parser.py b/pyrogram/client/parser/parser.py index f685c942..cde26f0b 100644 --- a/pyrogram/client/parser/parser.py +++ b/pyrogram/client/parser/parser.py @@ -31,6 +31,8 @@ class Parser: self.markdown = Markdown(client) def parse(self, text: str, mode: str = ""): + text = str(text or "").strip() + if mode is None: return OrderedDict([ ("message", text), From 6c80064f2c239309c50b9e44db0ee1d6fa08be51 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 30 Jun 2019 21:10:28 +0200 Subject: [PATCH 104/202] Enable custom format for User mentions Examples: - format(user, "mention") - "{:mention}".format(user) - f"{user:mention}" --- pyrogram/client/types/user_and_chats/user.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pyrogram/client/types/user_and_chats/user.py b/pyrogram/client/types/user_and_chats/user.py index f47e8c42..248c4c0a 100644 --- a/pyrogram/client/types/user_and_chats/user.py +++ b/pyrogram/client/types/user_and_chats/user.py @@ -16,6 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import html + import pyrogram from pyrogram.api import types from .chat_photo import ChatPhoto @@ -134,6 +136,12 @@ class User(Object): self.photo = photo self.restriction_reason = restriction_reason + def __format__(self, format_spec): + if format_spec == "mention": + return '{1}'.format(self.id, html.escape(self.first_name)) + + return html.escape(str(self)) + @staticmethod def _parse(client, user: types.User) -> "User" or None: if user is None: From 46a03a20009f625fa5b2ebbb4f05caf6b3b6f114 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 1 Jul 2019 13:17:16 +0200 Subject: [PATCH 105/202] Log the invalid values when raising errors --- pyrogram/client/storage/memory_storage.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pyrogram/client/storage/memory_storage.py b/pyrogram/client/storage/memory_storage.py index ff4cb540..e69d247f 100644 --- a/pyrogram/client/storage/memory_storage.py +++ b/pyrogram/client/storage/memory_storage.py @@ -130,7 +130,7 @@ class MemoryStorage(Storage): access_hash=access_hash ) - raise ValueError("Invalid peer type") + raise ValueError("Invalid peer type: {}".format(peer_type)) def get_peer_by_id(self, peer_id: int): r = self.conn.execute( @@ -139,7 +139,7 @@ class MemoryStorage(Storage): ).fetchone() if r is None: - raise KeyError("ID not found") + raise KeyError("ID not found: {}".format(peer_id)) return self._get_input_peer(*r) @@ -150,10 +150,10 @@ class MemoryStorage(Storage): ).fetchone() if r is None: - raise KeyError("Username not found") + raise KeyError("Username not found: {}".format(username)) if abs(time.time() - r[3]) > self.USERNAME_TTL: - raise KeyError("Username expired") + raise KeyError("Username expired: {}".format(username)) return self._get_input_peer(*r[:3]) @@ -164,7 +164,7 @@ class MemoryStorage(Storage): ).fetchone() if r is None: - raise KeyError("Phone number not found") + raise KeyError("Phone number not found: {}".format(phone_number)) return self._get_input_peer(*r) From 414e42a3cca544c5e4628e319465743289b8881e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 1 Jul 2019 13:32:14 +0200 Subject: [PATCH 106/202] Update README.md example Use .reply_text() instead of .reply() --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4e758b5a..a4294175 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ app = Client("my_account") @app.on_message(Filters.private) def hello(client, message): - message.reply("Hello {}".format(message.from_user.first_name)) + message.reply_text("Hello {}".format(message.from_user.first_name)) app.run() From 992ef7bf527d59011593526dfec894c6ec21eaa5 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 1 Jul 2019 13:37:12 +0200 Subject: [PATCH 107/202] Fix sphinx warnings --- docs/source/api/bound-methods.rst | 2 +- docs/source/topics/text-formatting.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/api/bound-methods.rst b/docs/source/api/bound-methods.rst index 6a1cf1c3..679e1f8a 100644 --- a/docs/source/api/bound-methods.rst +++ b/docs/source/api/bound-methods.rst @@ -142,7 +142,7 @@ Details .. automethod:: Chat.archive() .. automethod:: Chat.unarchive() .. automethod:: Chat.set_title() -.. automethod:: Chat.set_description) +.. automethod:: Chat.set_description() .. automethod:: Chat.set_photo() .. automethod:: Chat.kick_member() .. automethod:: Chat.unban_member() diff --git a/docs/source/topics/text-formatting.rst b/docs/source/topics/text-formatting.rst index fc8b952c..0194dc58 100644 --- a/docs/source/topics/text-formatting.rst +++ b/docs/source/topics/text-formatting.rst @@ -202,7 +202,7 @@ Nested and Overlapping Entities ------------------------------- You can also style texts with more than one decoration at once by nesting entities together. For example, you can send -a text message with both :bold-underline:`bold and underline` styles, or a text that has both :italic-strike:`italic and +a text message with both :bold-underline:`bold and underline` styles, or a text that has both :strike-italic:`italic and strike` styles, and you can still combine both Markdown and HTML together. Here there are some example texts you can try sending: From 544cddfd1c0b03f1b19d07c074e5ac3d80d58990 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 1 Jul 2019 13:44:16 +0200 Subject: [PATCH 108/202] Update Pyrogram to v0.15.0 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index ac184844..3e5940a8 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -24,7 +24,7 @@ if sys.version_info[:3] in [(3, 5, 0), (3, 5, 1), (3, 5, 2)]: # Monkey patch the standard "typing" module because Python versions from 3.5.0 to 3.5.2 have a broken one. sys.modules["typing"] = typing -__version__ = "0.15.0-develop" +__version__ = "0.15.0" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2019 Dan " From 7a1d6002a9b63157f1cb541424cc3cb2f42ed0c8 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 1 Jul 2019 14:29:02 +0200 Subject: [PATCH 109/202] Update robots.txt --- docs/robots.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/robots.txt b/docs/robots.txt index 1b9e8da6..e7799fdd 100644 --- a/docs/robots.txt +++ b/docs/robots.txt @@ -2,7 +2,7 @@ User-agent: * Allow: / -Disallow: /dev/* -Disallow: /old/* +Disallow: /dev* +Disallow: /v0* Sitemap: https://docs.pyrogram.org/sitemap.xml \ No newline at end of file From 3f2f40af02346ef7d646bb4a3d2235bb54903185 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 1 Jul 2019 14:46:33 +0200 Subject: [PATCH 110/202] Update docs index example --- docs/source/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index 682c883c..b37bf2dc 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -106,7 +106,7 @@ Welcome to Pyrogram @app.on_message(Filters.private) def hello(client, message): - message.reply("Hello {}".format(message.from_user.first_name)) + message.reply_text("Hello {}".format(message.from_user.first_name)) app.run() From be3947e20bf837904ab36c9abac2135c35cb1976 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 2 Jul 2019 00:00:59 +0200 Subject: [PATCH 111/202] Fix some methods not working correctly with the new Text Parser --- pyrogram/client/methods/messages/edit_inline_media.py | 4 ++-- pyrogram/client/methods/messages/edit_message_media.py | 4 ++-- pyrogram/client/methods/messages/send_media_group.py | 4 +--- pyrogram/client/types/input_media/input_media_animation.py | 4 +++- pyrogram/client/types/input_media/input_media_audio.py | 4 +++- pyrogram/client/types/input_media/input_media_document.py | 4 +++- pyrogram/client/types/input_media/input_media_photo.py | 4 +++- pyrogram/client/types/input_media/input_media_video.py | 4 +++- 8 files changed, 20 insertions(+), 12 deletions(-) diff --git a/pyrogram/client/methods/messages/edit_inline_media.py b/pyrogram/client/methods/messages/edit_inline_media.py index 87e692fd..0ed89d17 100644 --- a/pyrogram/client/methods/messages/edit_inline_media.py +++ b/pyrogram/client/methods/messages/edit_inline_media.py @@ -55,8 +55,8 @@ class EditInlineMedia(BaseClient): Raises: RPCError: In case of a Telegram RPC error. """ - style = self.html if media.parse_mode.lower() == "html" else self.markdown caption = media.caption + parse_mode = media.parse_mode if isinstance(media, InputMediaPhoto): if media.media.startswith("http"): @@ -99,6 +99,6 @@ class EditInlineMedia(BaseClient): id=utils.unpack_inline_message_id(inline_message_id), media=media, reply_markup=reply_markup.write() if reply_markup else None, - **style.parse(caption) + **self.parser.parse(caption, parse_mode) ) ) diff --git a/pyrogram/client/methods/messages/edit_message_media.py b/pyrogram/client/methods/messages/edit_message_media.py index b65804fd..72077710 100644 --- a/pyrogram/client/methods/messages/edit_message_media.py +++ b/pyrogram/client/methods/messages/edit_message_media.py @@ -63,8 +63,8 @@ class EditMessageMedia(BaseClient): Raises: RPCError: In case of a Telegram RPC error. """ - style = self.html if media.parse_mode.lower() == "html" else self.markdown caption = media.caption + parse_mode = media.parse_mode if isinstance(media, InputMediaPhoto): if os.path.exists(media.media): @@ -239,7 +239,7 @@ class EditMessageMedia(BaseClient): id=message_id, media=media, reply_markup=reply_markup.write() if reply_markup else None, - **style.parse(caption) + **self.parser.parse(caption, parse_mode) ) ) diff --git a/pyrogram/client/methods/messages/send_media_group.py b/pyrogram/client/methods/messages/send_media_group.py index 194a2202..681e1850 100644 --- a/pyrogram/client/methods/messages/send_media_group.py +++ b/pyrogram/client/methods/messages/send_media_group.py @@ -66,8 +66,6 @@ class SendMediaGroup(BaseClient): multi_media = [] for i in media: - style = self.html if i.parse_mode.lower() == "html" else self.markdown - if isinstance(i, pyrogram.InputMediaPhoto): if os.path.exists(i.media): while True: @@ -138,7 +136,7 @@ class SendMediaGroup(BaseClient): types.InputSingleMedia( media=media, random_id=self.rnd_id(), - **style.parse(i.caption) + **self.parser.parse(i.caption, i.parse_mode) ) ) diff --git a/pyrogram/client/types/input_media/input_media_animation.py b/pyrogram/client/types/input_media/input_media_animation.py index e5b8edb4..14920723 100644 --- a/pyrogram/client/types/input_media/input_media_animation.py +++ b/pyrogram/client/types/input_media/input_media_animation.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 + from . import InputMedia @@ -61,7 +63,7 @@ class InputMediaAnimation(InputMedia): media: str, thumb: str = None, caption: str = "", - parse_mode: str = "", + parse_mode: Union[str, None] = "", 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 02299a12..f360d3c6 100644 --- a/pyrogram/client/types/input_media/input_media_audio.py +++ b/pyrogram/client/types/input_media/input_media_audio.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 + from . import InputMedia @@ -63,7 +65,7 @@ class InputMediaAudio(InputMedia): media: str, thumb: str = None, caption: str = "", - parse_mode: str = "", + parse_mode: Union[str, None] = "", 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 46a5b446..629f7469 100644 --- a/pyrogram/client/types/input_media/input_media_document.py +++ b/pyrogram/client/types/input_media/input_media_document.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 + from . import InputMedia @@ -52,7 +54,7 @@ class InputMediaDocument(InputMedia): media: str, thumb: str = None, caption: str = "", - parse_mode: str = "" + parse_mode: Union[str, None] = "" ): 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 064065e3..97077d1d 100644 --- a/pyrogram/client/types/input_media/input_media_photo.py +++ b/pyrogram/client/types/input_media/input_media_photo.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 + from . import InputMedia @@ -47,6 +49,6 @@ class InputMediaPhoto(InputMedia): self, media: str, caption: str = "", - parse_mode: str = "" + parse_mode: Union[str, None] = "" ): 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 4584ffbe..50d70004 100644 --- a/pyrogram/client/types/input_media/input_media_video.py +++ b/pyrogram/client/types/input_media/input_media_video.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 + from . import InputMedia @@ -66,7 +68,7 @@ class InputMediaVideo(InputMedia): media: str, thumb: str = None, caption: str = "", - parse_mode: str = "", + parse_mode: Union[str, None] = "", width: int = 0, height: int = 0, duration: int = 0, From 18b581fb4547c0eee9edfa6901c1c35e5a904b1f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 2 Jul 2019 00:03:14 +0200 Subject: [PATCH 112/202] Update Pyrogram to v0.15.1 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 3e5940a8..edfc756d 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -24,7 +24,7 @@ if sys.version_info[:3] in [(3, 5, 0), (3, 5, 1), (3, 5, 2)]: # Monkey patch the standard "typing" module because Python versions from 3.5.0 to 3.5.2 have a broken one. sys.modules["typing"] = typing -__version__ = "0.15.0" +__version__ = "0.15.1" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2019 Dan " From 17a25898912a484ade13e227d46c2037421caf66 Mon Sep 17 00:00:00 2001 From: Mendel E Date: Mon, 1 Jul 2019 01:28:29 -0400 Subject: [PATCH 113/202] Add message.web_page attributes --- .../types/messages_and_media/__init__.py | 3 +- .../types/messages_and_media/message.py | 6 +- .../types/messages_and_media/webpage.py | 191 ++++++++++++++++++ 3 files changed, 197 insertions(+), 3 deletions(-) create mode 100644 pyrogram/client/types/messages_and_media/webpage.py diff --git a/pyrogram/client/types/messages_and_media/__init__.py b/pyrogram/client/types/messages_and_media/__init__.py index b9bcb460..f5db82e5 100644 --- a/pyrogram/client/types/messages_and_media/__init__.py +++ b/pyrogram/client/types/messages_and_media/__init__.py @@ -34,8 +34,9 @@ from .venue import Venue from .video import Video from .video_note import VideoNote from .voice import Voice +from .webpage import WebPage __all__ = [ "Animation", "Audio", "Contact", "Document", "Game", "Location", "Message", "MessageEntity", "Photo", "Thumbnail", - "StrippedThumbnail", "Poll", "PollOption", "Sticker", "Venue", "Video", "VideoNote", "Voice" + "StrippedThumbnail", "Poll", "PollOption", "Sticker", "Venue", "Video", "VideoNote", "Voice", "WebPage" ] diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 39fd24fa..37b41082 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -575,10 +575,12 @@ class Message(Object, Update): else: document = pyrogram.Document._parse(client, doc, file_name) elif isinstance(media, types.MessageMediaWebPage): - web_page = True - media = None + if isinstance(media.webpage, types.WebPage): + web_page = pyrogram.WebPage._parse(client, media.webpage) + elif isinstance(media, types.MessageMediaPoll): poll = pyrogram.Poll._parse(client, media) + else: media = None diff --git a/pyrogram/client/types/messages_and_media/webpage.py b/pyrogram/client/types/messages_and_media/webpage.py new file mode 100644 index 00000000..c8a14b04 --- /dev/null +++ b/pyrogram/client/types/messages_and_media/webpage.py @@ -0,0 +1,191 @@ +# 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 types +from .photo import Photo +from ..object import Object + + +class WebPage(Object): + #TODO: hash, cached_page + """A webpage preview + + Parameters: + id (``str``): + Unique identifier for this webpage. + + url (``str``): + Full URL for this webpage. + + display_url (``str``): + Display URL for this webpage. + + type (``str``, *optional*): + Type of webpage, can be `article`, `photo`, `gif`, `video` or `document` afaik. #TODO + + site_name (``str``, *optional*): + Site name. + + title (``str``, *optional*): + Title of this webpage. + + description (``str``, *optional*): + Description of this webpage. + + audio (:obj:`Audio`, *optional*): + Webpage is an audio file, information about the file. + + document (:obj:`Document`, *optional*): + Webpage is a general file, information about the file. + + photo (:obj:`Photo`, *optional*): + Webpage is a photo, information about the photo. + + animation (:obj:`Animation`, *optional*): + Webpage is an animation, information about the animation. + + video (:obj:`Video`, *optional*): + Webpage is a video, information about the video.abs + + embed_url (``str``, *optional*): + Embedded content URL. + + embed_type (``str``, *optional*): + Embedded content type, can be `iframe` #TODO + + embed_width (``int``, *optional*): + Embedded content width. + + embed_height (``int``, *optional*): + Embedded content height. + + duration (``int``, *optional*): + :shrug: + + author (``str``, *optional*): + Author of the webpage, eg the Twitter user. + """ + + __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, + *, + client: "pyrogram.BaseClient" = None, + id: str, + url: str, + display_url: str, + type: str = None, + site_name: str = None, + title: str = None, + description: str = None, + audio: "pyrogram.Audio" = None, + document: "pyrogram.Document" = None, + photo: "pyrogram.Photo" = None, + animation: "pyrogram.Animation" = None, + video: "pyrogram.Video" = None, + embed_url: str = None, + embed_type: str = None, + embed_width: int = None, + embed_height: int = None, + duration: int = None, + author: str = None + ) -> "pyrogram.WebPage" : + super().__init__(client) + + self.id = id + self.url = url + self.display_url = display_url + self.type = type + self.site_name = site_name + self.title = title + self.description = description + self.audio = audio + self.document = document + self.photo = photo + self.animation = animation + self.video = video + self.embed_url = embed_url + self.embed_type = embed_type + self.embed_width = embed_width + self.embed_height = embed_height + self.duration = duration + self.author = author + + @staticmethod + def _parse(client, webpage: types.WebPage) -> "WebPage": + audio = None + document = None + photo = None + animation = None + video = None + + if isinstance(webpage.photo, types.Photo): + photo = pyrogram.Photo._parse(client, webpage.photo) + + doc = webpage.document + + if isinstance(doc, types.Document): + attributes = {type(i): i for i in doc.attributes} + + file_name = getattr( + attributes.get( + types.DocumentAttributeFilename, None + ), "file_name", None + ) + + if types.DocumentAttributeAudio in attributes: + audio_attributes = attributes[types.DocumentAttributeAudio] + audio = pyrogram.Audio._parse(client, doc, audio_attributes, file_name) + + elif types.DocumentAttributeAnimated in attributes: + video_attributes = attributes.get(types.DocumentAttributeVideo, None) + animation = pyrogram.Animation._parse(client, doc, video_attributes, file_name) + + elif types.DocumentAttributeVideo in attributes: + video_attributes = attributes[types.DocumentAttributeVideo] + video = pyrogram.Video._parse(client, doc, video_attributes, file_name) + + else: + document = pyrogram.Document._parse(client, doc, file_name) + + return WebPage( + id=str(webpage.id), + url=webpage.url, + display_url=webpage.display_url, + type=webpage.type, + site_name=webpage.site_name, + title=webpage.title, + description=webpage.description, + audio=audio, + document=document, + photo=photo, + animation=animation, + video=video, + embed_url=webpage.embed_url, + embed_type=webpage.embed_type, + embed_width=webpage.embed_width, + embed_height=webpage.embed_height, + duration=webpage.duration, + author=webpage.author + ) From 9687ef2820248951087482acd7be465ec35f1f80 Mon Sep 17 00:00:00 2001 From: Mendel E Date: Tue, 2 Jul 2019 10:00:03 -0400 Subject: [PATCH 114/202] flake8 errors --- pyrogram/client/types/messages_and_media/webpage.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/pyrogram/client/types/messages_and_media/webpage.py b/pyrogram/client/types/messages_and_media/webpage.py index c8a14b04..df19e7c8 100644 --- a/pyrogram/client/types/messages_and_media/webpage.py +++ b/pyrogram/client/types/messages_and_media/webpage.py @@ -18,12 +18,11 @@ import pyrogram from pyrogram.api import types -from .photo import Photo from ..object import Object class WebPage(Object): - #TODO: hash, cached_page + # TODO: hash, cached_page """A webpage preview Parameters: @@ -81,7 +80,7 @@ class WebPage(Object): author (``str``, *optional*): Author of the webpage, eg the Twitter user. """ - + __slots__ = [ "id", "url", "display_url", "type", "site_name", "title", "description", "audio", "document", "photo", "animation", "video", @@ -110,9 +109,9 @@ class WebPage(Object): embed_height: int = None, duration: int = None, author: str = None - ) -> "pyrogram.WebPage" : + ) -> "pyrogram.WebPage": super().__init__(client) - + self.id = id self.url = url self.display_url = display_url @@ -131,7 +130,7 @@ class WebPage(Object): self.embed_height = embed_height self.duration = duration self.author = author - + @staticmethod def _parse(client, webpage: types.WebPage) -> "WebPage": audio = None From d363a18e8450f0a7fbcaf6588d48bc949043066c Mon Sep 17 00:00:00 2001 From: Mendel E Date: Tue, 2 Jul 2019 10:46:08 -0400 Subject: [PATCH 115/202] Initial docs for WebPage --- docs/source/api/types.rst | 2 ++ .../types/messages_and_media/message.py | 5 +---- .../types/messages_and_media/webpage.py | 20 +++++++++---------- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/docs/source/api/types.rst b/docs/source/api/types.rst index f91e4f58..2147996d 100644 --- a/docs/source/api/types.rst +++ b/docs/source/api/types.rst @@ -54,6 +54,7 @@ Messages & Media - :class:`Venue` - :class:`Sticker` - :class:`Game` + - :class:`WebPage` - :class:`Poll` - :class:`PollOption` @@ -137,6 +138,7 @@ Details .. autoclass:: Venue() .. autoclass:: Sticker() .. autoclass:: Game() +.. autoclass:: WebPage() .. autoclass:: Poll() .. autoclass:: PollOption() diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 37b41082..323693c5 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -177,11 +177,8 @@ class Message(Object, Update): venue (:obj:`Venue`, *optional*): Message is a venue, information about the venue. - web_page (``bool``, *optional*): + web_page (:obj:`WebPage`, *optional*): Message was sent with a webpage preview. - **Note:** Support for web pages is still basic; a simple boolean is set in case the message contains a - web page preview. In future versions this property could turn into a full web page object that contains - more details. poll (:obj:`Poll`, *optional*): Message is a native poll, information about the poll. diff --git a/pyrogram/client/types/messages_and_media/webpage.py b/pyrogram/client/types/messages_and_media/webpage.py index df19e7c8..7785e754 100644 --- a/pyrogram/client/types/messages_and_media/webpage.py +++ b/pyrogram/client/types/messages_and_media/webpage.py @@ -36,10 +36,10 @@ class WebPage(Object): Display URL for this webpage. type (``str``, *optional*): - Type of webpage, can be `article`, `photo`, `gif`, `video` or `document` afaik. #TODO + Type of webpage preview, known types (at the time of writing) are `article`, `photo`, `gif`, `video` and `document`. site_name (``str``, *optional*): - Site name. + Webpage site name. title (``str``, *optional*): Title of this webpage. @@ -48,25 +48,25 @@ class WebPage(Object): Description of this webpage. audio (:obj:`Audio`, *optional*): - Webpage is an audio file, information about the file. + Webpage preview is an audio file, information about the file. document (:obj:`Document`, *optional*): - Webpage is a general file, information about the file. + Webpage preview is a general file, information about the file. photo (:obj:`Photo`, *optional*): - Webpage is a photo, information about the photo. + Webpage preview is a photo, information about the photo. animation (:obj:`Animation`, *optional*): - Webpage is an animation, information about the animation. + Webpage preview is an animation, information about the animation. video (:obj:`Video`, *optional*): - Webpage is a video, information about the video.abs + Webpage preview is a video, information about the video. embed_url (``str``, *optional*): Embedded content URL. embed_type (``str``, *optional*): - Embedded content type, can be `iframe` #TODO + Embedded content type, like `iframe` embed_width (``int``, *optional*): Embedded content width. @@ -75,10 +75,10 @@ class WebPage(Object): Embedded content height. duration (``int``, *optional*): - :shrug: + Uknown at the time of writing. author (``str``, *optional*): - Author of the webpage, eg the Twitter user. + Author of the webpage, eg the Twitter user for a tweet, or the author in an article. """ __slots__ = [ From dfffa9510a52cf9c114e7956586e49e6eb733580 Mon Sep 17 00:00:00 2001 From: Mendel E Date: Tue, 2 Jul 2019 21:04:08 -0400 Subject: [PATCH 116/202] Add more types in WebPage docs --- pyrogram/client/types/messages_and_media/webpage.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyrogram/client/types/messages_and_media/webpage.py b/pyrogram/client/types/messages_and_media/webpage.py index 7785e754..9baf9de5 100644 --- a/pyrogram/client/types/messages_and_media/webpage.py +++ b/pyrogram/client/types/messages_and_media/webpage.py @@ -36,7 +36,9 @@ class WebPage(Object): Display URL for this webpage. type (``str``, *optional*): - Type of webpage preview, known types (at the time of writing) are `article`, `photo`, `gif`, `video` and `document`. + Type of webpage preview, known types (at the time of writing) are: + `article`, `photo`, `gif`, `video` and `document`, + `telegram_user`, `telegram_bot`, `telegram_channel`, `telegram_megagroup`. site_name (``str``, *optional*): Webpage site name. From f7bfd5597ec97fc0cddfbcb9c86c062e53481c07 Mon Sep 17 00:00:00 2001 From: Mendel E Date: Wed, 3 Jul 2019 09:27:06 -0400 Subject: [PATCH 117/202] Make optional values italics --- pyrogram/client/types/messages_and_media/webpage.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/types/messages_and_media/webpage.py b/pyrogram/client/types/messages_and_media/webpage.py index 9baf9de5..9ddc7c6c 100644 --- a/pyrogram/client/types/messages_and_media/webpage.py +++ b/pyrogram/client/types/messages_and_media/webpage.py @@ -37,8 +37,8 @@ class WebPage(Object): type (``str``, *optional*): Type of webpage preview, known types (at the time of writing) are: - `article`, `photo`, `gif`, `video` and `document`, - `telegram_user`, `telegram_bot`, `telegram_channel`, `telegram_megagroup`. + *"article"*, *"photo"*, *"gif"*, *"video"* and *"document"*, + *"telegram_user"*, *"telegram_bot"*, *"telegram_channel"*, *"telegram_megagroup"*. site_name (``str``, *optional*): Webpage site name. From b4a8763452d9073baa09731f2778bd6a0c38cb70 Mon Sep 17 00:00:00 2001 From: Mendel E Date: Wed, 3 Jul 2019 10:59:11 -0400 Subject: [PATCH 118/202] Message: media = None if WebPage is empty --- pyrogram/client/types/messages_and_media/message.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 323693c5..a1d42b79 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -574,6 +574,8 @@ class Message(Object, Update): elif isinstance(media, types.MessageMediaWebPage): if isinstance(media.webpage, types.WebPage): web_page = pyrogram.WebPage._parse(client, media.webpage) + else: + media = None elif isinstance(media, types.MessageMediaPoll): poll = pyrogram.Poll._parse(client, media) From bdd554575a34fcd849e25442a1a8a4bd95015f75 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 4 Jul 2019 21:00:22 +0200 Subject: [PATCH 119/202] Fix export_chat_invite_link not working for bots on channels/supergroups Telegram still hasn't enabled this for bots... Closes --- compiler/api/source/main_api.tl | 3 +++ pyrogram/client/methods/chats/export_chat_invite_link.py | 8 +++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index ff295130..f465d865 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -1364,4 +1364,7 @@ langpack.getLanguage#6a596502 lang_pack:string lang_code:string = LangPackLangua folders.editPeerFolders#6847d0ab folder_peers:Vector = Updates; folders.deleteFolder#1c295881 folder_id:int = Updates; +// Ports +channels.exportInvite#c7560885 channel:InputChannel = ExportedChatInvite; + // LAYER 102 \ No newline at end of file diff --git a/pyrogram/client/methods/chats/export_chat_invite_link.py b/pyrogram/client/methods/chats/export_chat_invite_link.py index 6e39dd84..ca75cac6 100644 --- a/pyrogram/client/methods/chats/export_chat_invite_link.py +++ b/pyrogram/client/methods/chats/export_chat_invite_link.py @@ -52,11 +52,17 @@ class ExportChatInviteLink(BaseClient): """ peer = self.resolve_peer(chat_id) - if isinstance(peer, (types.InputPeerChannel, types.InputPeerChat)): + if isinstance(peer, types.InputPeerChat): 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)) From 485877daa397e2f5de484c241e6efaf8d3552d43 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 5 Jul 2019 12:06:25 +0200 Subject: [PATCH 120/202] Update API schema to Layer 103 --- compiler/api/source/main_api.tl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index f465d865..e9d099d1 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -101,7 +101,7 @@ userStatusLastMonth#77ebc742 = UserStatus; chatEmpty#9ba2d800 id:int = Chat; chat#3bda1bde flags:# creator:flags.0?true kicked:flags.1?true left:flags.2?true deactivated:flags.5?true id:int title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel admin_rights:flags.14?ChatAdminRights default_banned_rights:flags.18?ChatBannedRights = Chat; chatForbidden#7328bdb id:int title:string = Chat; -channel#4df30834 flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true id:int access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int version:int restriction_reason:flags.9?string admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat; +channel#4df30834 flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true id:int access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int version:int restriction_reason:flags.9?string admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat; channelForbidden#289da732 flags:# broadcast:flags.5?true megagroup:flags.8?true id:int access_hash:long title:string until_date:flags.16?int = Chat; chatFull#1b7c9db3 flags:# can_set_username:flags.7?true id:int about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int = ChatFull; @@ -1065,7 +1065,7 @@ auth.resendCode#3ef1a9bf phone_number:string phone_code_hash:string = auth.SentC auth.cancelCode#1f040578 phone_number:string phone_code_hash:string = Bool; auth.dropTempAuthKeys#8e48a188 except_auth_keys:Vector = Bool; -account.registerDevice#5cbea590 token_type:int token:string app_sandbox:Bool secret:bytes other_uids:Vector = Bool; +account.registerDevice#68976c6f flags:# no_muted:flags.0?true token_type:int token:string app_sandbox:Bool secret:bytes other_uids:Vector = Bool; account.unregisterDevice#3076c4bf token_type:int token:string other_uids:Vector = Bool; account.updateNotifySettings#84be5b93 peer:InputNotifyPeer settings:InputPeerNotifySettings = Bool; account.getNotifySettings#12b3ad31 peer:InputNotifyPeer = PeerNotifySettings; @@ -1364,7 +1364,7 @@ langpack.getLanguage#6a596502 lang_pack:string lang_code:string = LangPackLangua folders.editPeerFolders#6847d0ab folder_peers:Vector = Updates; folders.deleteFolder#1c295881 folder_id:int = Updates; -// Ports -channels.exportInvite#c7560885 channel:InputChannel = ExportedChatInvite; +// LAYER 103 -// LAYER 102 \ No newline at end of file +// Ports +channels.exportInvite#c7560885 channel:InputChannel = ExportedChatInvite; \ No newline at end of file From 13d6ce14956a33c508e07fc453e8866bc0f30d58 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 9 Jul 2019 19:02:54 +0200 Subject: [PATCH 121/202] Update DC IPs --- docs/source/faq.rst | 4 ++-- pyrogram/session/internals/data_center.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/source/faq.rst b/docs/source/faq.rst index f76f1790..147eb4fa 100644 --- a/docs/source/faq.rst +++ b/docs/source/faq.rst @@ -170,11 +170,11 @@ IP addresses are now kept as aliases. :widths: auto :align: center - DC1, "MIA, Miami FL, USA", ``149.154.175.50``, ``2001:b28:f23d:f001::a`` + DC1, "MIA, Miami FL, USA", ``149.154.175.53``, ``2001:b28:f23d:f001::a`` DC2, "AMS, Amsterdam, NL", ``149.154.167.51``, ``2001:67c:4e8:f002::a`` DC3*, "MIA, Miami FL, USA", ``149.154.175.100``, ``2001:b28:f23d:f003::a`` DC4, "AMS, Amsterdam, NL", ``149.154.167.91``, ``2001:67c:4e8:f004::a`` - DC5, "SIN, Singapore, SG", ``91.108.56.149``, ``2001:b28:f23f:f005::a`` + DC5, "SIN, Singapore, SG", ``91.108.56.130``, ``2001:b28:f23f:f005::a`` .. csv-table:: Test Environment :header: ID, Location, IPv4, IPv6 diff --git a/pyrogram/session/internals/data_center.py b/pyrogram/session/internals/data_center.py index fd51932a..acff723b 100644 --- a/pyrogram/session/internals/data_center.py +++ b/pyrogram/session/internals/data_center.py @@ -26,11 +26,11 @@ class DataCenter: } PROD = { - 1: "149.154.175.50", + 1: "149.154.175.53", 2: "149.154.167.51", 3: "149.154.175.100", 4: "149.154.167.91", - 5: "91.108.56.149", + 5: "91.108.56.130", 121: "95.213.217.195" } From 82efc9a154bf21b28acde9595d201efc75e21d21 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 9 Jul 2019 19:03:15 +0200 Subject: [PATCH 122/202] Small documentation fix --- pyrogram/client/methods/messages/download_media.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/methods/messages/download_media.py b/pyrogram/client/methods/messages/download_media.py index 143349f7..cc0e54d2 100644 --- a/pyrogram/client/methods/messages/download_media.py +++ b/pyrogram/client/methods/messages/download_media.py @@ -86,7 +86,7 @@ class DownloadMedia(BaseClient): Raises: RPCError: In case of a Telegram RPC error. - ``ValueError`` if the message doesn't contain any downloadable media + ValueError: if the message doesn't contain any downloadable media """ error_message = "This message doesn't contain any downloadable media" available_media = ("audio", "document", "photo", "sticker", "animation", "video", "voice", "video_note") From ee2d5b1315633629974275101c9c2b56f8d80c18 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 9 Jul 2019 19:03:37 +0200 Subject: [PATCH 123/202] Fix RPCError raising an error itself because of non-optional args --- pyrogram/errors/rpc_error.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyrogram/errors/rpc_error.py b/pyrogram/errors/rpc_error.py index e0acdc21..806b5373 100644 --- a/pyrogram/errors/rpc_error.py +++ b/pyrogram/errors/rpc_error.py @@ -32,12 +32,12 @@ class RPCError(Exception): NAME = None MESSAGE = "{x}" - def __init__(self, x: int or RawRPCError, rpc_name: str, is_unknown: bool): - super().__init__("[{} {}]: {} ({})".format( + def __init__(self, x: int or RawRPCError = None, rpc_name: str = None, is_unknown: bool = False): + super().__init__("[{} {}]: {} {}".format( self.CODE, self.ID or self.NAME, self.MESSAGE.format(x=x), - 'caused by "{}"'.format(rpc_name) + '(caused by "{}")'.format(rpc_name) if rpc_name else "" )) try: From efcf7d5503751565b9df27a1073d7c9fb9d2e410 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 9 Jul 2019 19:03:46 +0200 Subject: [PATCH 124/202] Move single methods, types and bound-methods to separated pages The resulting pages were huge and were also taking a while to load This will improve docs navigation --- compiler/docs/compiler.py | 349 +++++++++++++++++++++++ compiler/docs/template/bound-methods.rst | 88 ++++++ compiler/docs/template/methods.rst | 122 ++++++++ compiler/docs/template/types.rst | 95 ++++++ docs/source/api/bound-methods.rst | 164 ----------- docs/source/api/methods.rst | 296 ------------------- docs/source/api/types.rst | 172 ----------- docs/source/index.rst | 6 +- setup.py | 7 +- 9 files changed, 662 insertions(+), 637 deletions(-) create mode 100644 compiler/docs/template/bound-methods.rst create mode 100644 compiler/docs/template/methods.rst create mode 100644 compiler/docs/template/types.rst delete mode 100644 docs/source/api/bound-methods.rst delete mode 100644 docs/source/api/methods.rst delete mode 100644 docs/source/api/types.rst diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index b167fa57..b9e13dd1 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -23,6 +23,7 @@ import shutil HOME = "compiler/docs" DESTINATION = "docs/source/telegram" +PYROGRAM_API_DEST = "docs/source/api" FUNCTIONS_PATH = "pyrogram/api/functions" TYPES_PATH = "pyrogram/api/types" @@ -117,6 +118,352 @@ def generate(source_path, base): f.write("\n") +def pyrogram_api(): + def get_title_list(s: str) -> list: + return [i.strip() for i in [j.strip() for j in s.split("\n") if j] if i] + + # Methods + + categories = dict( + utilities=""" + Utilities + start + stop + restart + idle + run + add_handler + remove_handler + stop_transmission + export_session_string + """, + messages=""" + Messages + send_message + forward_messages + send_photo + send_audio + send_document + send_sticker + send_animated_sticker + send_video + send_animation + send_voice + send_video_note + send_media_group + send_location + send_venue + send_contact + send_cached_media + edit_message_text + edit_message_caption + edit_message_media + edit_message_reply_markup + edit_inline_text + edit_inline_caption + edit_inline_media + edit_inline_reply_markup + send_chat_action + delete_messages + get_messages + get_history + get_history_count + read_history + iter_history + send_poll + vote_poll + stop_poll + retract_vote + download_media + """, + chats=""" + Chats + join_chat + leave_chat + kick_chat_member + unban_chat_member + restrict_chat_member + promote_chat_member + export_chat_invite_link + set_chat_photo + delete_chat_photo + set_chat_title + set_chat_description + pin_chat_message + unpin_chat_message + get_chat + get_chat_member + get_chat_members + get_chat_members_count + iter_chat_members + get_dialogs + iter_dialogs + get_dialogs_count + restrict_chat + update_chat_username + archive_chats + unarchive_chats + """, + users=""" + Users + get_me + get_users + get_profile_photos + get_profile_photos_count + iter_profile_photos + set_profile_photo + delete_profile_photos + update_username + get_user_dc + block_user + unblock_user + """, + contacts=""" + Contacts + add_contacts + get_contacts + get_contacts_count + delete_contacts + """, + password=""" + Pssword + enable_cloud_password + change_cloud_password + remove_cloud_password + """, + bots=""" + Bots + get_inline_bot_results + send_inline_bot_result + answer_callback_query + answer_inline_query + request_callback_answer + send_game + set_game_score + get_game_high_scores + """, + advanced=""" + Advanced + send + resolve_peer + save_file + """ + ) + + root = PYROGRAM_API_DEST + "/methods" + + shutil.rmtree(root, ignore_errors=True) + os.mkdir(root) + + with open("template/methods.rst") as f: + template = f.read() + + with open(root + "/index.rst", "w") as f: + fmt_keys = {} + + for k, v in categories.items(): + name, *methods = get_title_list(v) + fmt_keys.update({k: "\n ".join("{0} <{0}>".format(m) for m in methods)}) + + for method in methods: + with open(root + "/{}.rst".format(method), "w") as f2: + title = "{}()".format(method) + + f2.write(title + "\n" + "=" * len(title) + "\n\n") + f2.write(".. automethod:: pyrogram.Client.{}()".format(method)) + + f.write(template.format(**fmt_keys)) + + # Types + + categories = dict( + users_chats=""" + Users & Chats + User + UserStatus + Chat + ChatPreview + ChatPhoto + ChatMember + ChatPermissions + Dialog + """, + messages_media=""" + Messages & Media + Message + MessageEntity + Photo + Thumbnail + Audio + Document + Animation + Video + Voice + VideoNote + Contact + Location + Venue + Sticker + Game + WebPage + Poll + PollOption + """, + bots_keyboard=""" + Bots & Keyboards + ReplyKeyboardMarkup + KeyboardButton + ReplyKeyboardRemove + InlineKeyboardMarkup + InlineKeyboardButton + ForceReply + CallbackQuery + GameHighScore + CallbackGame + """, + input_media=""" + Input Media + InputMedia + InputMediaPhoto + InputMediaVideo + InputMediaAudio + InputMediaAnimation + InputMediaDocument + InputPhoneContact + """, + inline_mode=""" + Inline Mode + InlineQuery + InlineQueryResult + InlineQueryResultArticle + """, + input_message_content=""" + InputMessageContent + InputMessageContent + InputTextMessageContent + """ + ) + + root = PYROGRAM_API_DEST + "/types" + + shutil.rmtree(root, ignore_errors=True) + os.mkdir(root) + + with open("template/types.rst") as f: + template = f.read() + + with open(root + "/index.rst", "w") as f: + fmt_keys = {} + + for k, v in categories.items(): + name, *types = get_title_list(v) + + fmt_keys.update({k: "\n ".join(types)}) + + # noinspection PyShadowingBuiltins + for type in types: + with open(root + "/{}.rst".format(type), "w") as f2: + title = "{}".format(type) + + f2.write(title + "\n" + "=" * len(title) + "\n\n") + f2.write(".. autoclass:: pyrogram.{}()".format(type)) + + f.write(template.format(**fmt_keys)) + + # Bound Methods + + categories = dict( + message=""" + Message + Message.click + Message.delete + Message.download + Message.forward + Message.pin + Message.edit_text + Message.edit_caption + Message.edit_media + Message.edit_reply_markup + Message.reply_text + Message.reply_animation + Message.reply_audio + Message.reply_cached_media + Message.reply_chat_action + Message.reply_contact + Message.reply_document + Message.reply_game + Message.reply_inline_bot_result + Message.reply_location + Message.reply_media_group + Message.reply_photo + Message.reply_poll + Message.reply_sticker + Message.reply_venue + Message.reply_video + Message.reply_video_note + Message.reply_voice + """, + chat=""" + Chat + Chat.archive + Chat.unarchive + Chat.set_title + Chat.set_description + Chat.set_photo + Chat.kick_member + Chat.unban_member + Chat.restrict_member + Chat.promote_member + """, + user=""" + User + User.archive + User.unarchive + """, + callback_query=""" + Callback Query + CallbackQuery.answer + CallbackQuery.edit_message_text + CallbackQuery.edit_message_caption + CallbackQuery.edit_message_media + CallbackQuery.edit_message_reply_markup + """, + inline_query=""" + InlineQuery + InlineQuery.answer + """ + ) + + root = PYROGRAM_API_DEST + "/bound-methods" + + shutil.rmtree(root, ignore_errors=True) + os.mkdir(root) + + with open("template/bound-methods.rst") as f: + template = f.read() + + with open(root + "/index.rst", "w") as f: + fmt_keys = {} + + for k, v in categories.items(): + name, *bound_methods = get_title_list(v) + + fmt_keys.update({"{}_hlist".format(k): "\n ".join("- :meth:`~{}`".format(bm) for bm in bound_methods)}) + + fmt_keys.update( + {"{}_toctree".format(k): "\n ".join("{} <{}>".format(bm.split(".")[1], bm) for bm in bound_methods)}) + + # noinspection PyShadowingBuiltins + for bm in bound_methods: + with open(root + "/{}.rst".format(bm), "w") as f2: + title = "{}()".format(bm) + + f2.write(title + "\n" + "=" * len(title) + "\n\n") + f2.write(".. automethod:: pyrogram.{}()".format(bm)) + + f.write(template.format(**fmt_keys)) + + def start(): global page_template global toctree @@ -131,6 +478,7 @@ def start(): generate(TYPES_PATH, TYPES_BASE) generate(FUNCTIONS_PATH, FUNCTIONS_BASE) + pyrogram_api() if "__main__" == __name__: @@ -138,5 +486,6 @@ if "__main__" == __name__: TYPES_PATH = "../../pyrogram/api/types" HOME = "." DESTINATION = "../../docs/source/telegram" + PYROGRAM_API_DEST = "../../docs/source/api" start() diff --git a/compiler/docs/template/bound-methods.rst b/compiler/docs/template/bound-methods.rst new file mode 100644 index 00000000..0057e071 --- /dev/null +++ b/compiler/docs/template/bound-methods.rst @@ -0,0 +1,88 @@ +Bound Methods +============= + +Some Pyrogram types define what are called bound methods. Bound methods are functions attached to a class which are +accessed via an instance of that class. They make it even easier to call specific methods by automatically inferring +some of the required arguments. + +.. code-block:: python + :emphasize-lines: 8 + + from pyrogram import Client + + app = Client("my_account") + + + @app.on_message() + def hello(client, message) + message.reply("hi") + + + app.run() + +.. currentmodule:: pyrogram + +Message +------- + +.. hlist:: + :columns: 3 + + {message_hlist} + +.. toctree:: + :hidden: + + {message_toctree} + +Chat +---- + +.. hlist:: + :columns: 4 + + {chat_hlist} + +.. toctree:: + :hidden: + + {chat_toctree} + +User +---- + +.. hlist:: + :columns: 2 + + {user_hlist} + +.. toctree:: + :hidden: + + {user_toctree} + +CallbackQuery +------------- + +.. hlist:: + :columns: 3 + + {callback_query_hlist} + +.. toctree:: + :hidden: + + {callback_query_toctree} + +InlineQuery +----------- + +.. hlist:: + :columns: 2 + + {inline_query_hlist} + +.. toctree:: + :hidden: + + {inline_query_toctree} diff --git a/compiler/docs/template/methods.rst b/compiler/docs/template/methods.rst new file mode 100644 index 00000000..0de7ee87 --- /dev/null +++ b/compiler/docs/template/methods.rst @@ -0,0 +1,122 @@ +Available Methods +================= + +This page is about Pyrogram methods. All the methods listed here are bound to a :class:`~pyrogram.Client` instance. + +.. code-block:: python + :emphasize-lines: 6 + + from pyrogram import Client + + app = Client("my_account") + + with app: + app.send_message("haskell", "hi") + +.. currentmodule:: pyrogram.Client + +Utilities +--------- + +.. autosummary:: + :nosignatures: + + {utilities} + +.. toctree:: + :hidden: + + {utilities} + +Messages +-------- + +.. autosummary:: + :nosignatures: + + {messages} + +.. toctree:: + :hidden: + + {messages} + +Chats +----- + +.. autosummary:: + :nosignatures: + + {chats} + +.. toctree:: + :hidden: + + {chats} + +Users +----- + +.. autosummary:: + :nosignatures: + + {users} + +.. toctree:: + :hidden: + + {users} + +Contacts +-------- + +.. autosummary:: + :nosignatures: + + {contacts} + +.. toctree:: + :hidden: + + {contacts} + +Password +-------- + +.. autosummary:: + :nosignatures: + + {password} + +.. toctree:: + :hidden: + + {password} + +Bots +---- + +.. autosummary:: + :nosignatures: + + {bots} + +.. toctree:: + :hidden: + + {bots} + +Advanced +-------- + +Learn more about these methods at :doc:`Advanced Usage <../../topics/advanced-usage>`. + +.. autosummary:: + :nosignatures: + + {advanced} + +.. toctree:: + :hidden: + + {advanced} \ No newline at end of file diff --git a/compiler/docs/template/types.rst b/compiler/docs/template/types.rst new file mode 100644 index 00000000..635a81d3 --- /dev/null +++ b/compiler/docs/template/types.rst @@ -0,0 +1,95 @@ +Available Types +=============== + +This page is about Pyrogram types. All types listed here are accessible through the main package directly. + +.. code-block:: python + :emphasize-lines: 1 + + from pyrogram import User, Message, ... + +.. note:: + + **Optional** fields may not exist when irrelevant -- i.e.: they will contain the value of ``None`` and aren't shown + when, for example, using ``print()``. + +.. currentmodule:: pyrogram + + +Users & Chats +------------- + +.. autosummary:: + :nosignatures: + + {users_chats} + +.. toctree:: + :hidden: + + {users_chats} + +Messages & Media +---------------- + +.. autosummary:: + :nosignatures: + + {messages_media} + +.. toctree:: + :hidden: + + {messages_media} + +Bots & Keyboards +---------------- + +.. autosummary:: + :nosignatures: + + {bots_keyboard} + +.. toctree:: + :hidden: + + {bots_keyboard} + +Input Media +----------- + +.. autosummary:: + :nosignatures: + + {input_media} + +.. toctree:: + :hidden: + + {input_media} + +Inline Mode +----------- + +.. autosummary:: + :nosignatures: + + {inline_mode} + +.. toctree:: + :hidden: + + {inline_mode} + +InputMessageContent +------------------- + +.. autosummary:: + :nosignatures: + + {input_message_content} + +.. toctree:: + :hidden: + + {input_message_content} \ No newline at end of file diff --git a/docs/source/api/bound-methods.rst b/docs/source/api/bound-methods.rst deleted file mode 100644 index 679e1f8a..00000000 --- a/docs/source/api/bound-methods.rst +++ /dev/null @@ -1,164 +0,0 @@ -Bound Methods -============= - -Some Pyrogram types define what are called bound methods. Bound methods are functions attached to a class which are -accessed via an instance of that class. They make it even easier to call specific methods by automatically inferring -some of the required arguments. - -.. code-block:: python - :emphasize-lines: 8 - - from pyrogram import Client - - app = Client("my_account") - - - @app.on_message() - def hello(client, message) - message.reply("hi") - - - app.run() - -.. currentmodule:: pyrogram - -Index ------ - -Message -^^^^^^^ - -.. hlist:: - :columns: 3 - - - :meth:`~Message.click` - - :meth:`~Message.delete` - - :meth:`~Message.download` - - :meth:`~Message.forward` - - :meth:`~Message.pin` - - :meth:`~Message.edit_text` - - :meth:`~Message.edit_caption` - - :meth:`~Message.edit_media` - - :meth:`~Message.edit_reply_markup` - - :meth:`~Message.reply_text` - - :meth:`~Message.reply_animation` - - :meth:`~Message.reply_audio` - - :meth:`~Message.reply_cached_media` - - :meth:`~Message.reply_chat_action` - - :meth:`~Message.reply_contact` - - :meth:`~Message.reply_document` - - :meth:`~Message.reply_game` - - :meth:`~Message.reply_inline_bot_result` - - :meth:`~Message.reply_location` - - :meth:`~Message.reply_media_group` - - :meth:`~Message.reply_photo` - - :meth:`~Message.reply_poll` - - :meth:`~Message.reply_sticker` - - :meth:`~Message.reply_venue` - - :meth:`~Message.reply_video` - - :meth:`~Message.reply_video_note` - - :meth:`~Message.reply_voice` - -Chat -^^^^ - -.. hlist:: - :columns: 2 - - - :meth:`~Chat.archive` - - :meth:`~Chat.unarchive` - - :meth:`~Chat.set_title` - - :meth:`~Chat.set_description` - - :meth:`~Chat.set_photo` - - :meth:`~Chat.kick_member` - - :meth:`~Chat.unban_member` - - :meth:`~Chat.restrict_member` - - :meth:`~Chat.promote_member` - -User -^^^^ - -.. hlist:: - :columns: 2 - - - :meth:`~User.archive` - - :meth:`~User.unarchive` - -CallbackQuery -^^^^^^^^^^^^^ - -.. hlist:: - :columns: 3 - - - :meth:`~CallbackQuery.answer` - - :meth:`~CallbackQuery.edit_message_text` - - :meth:`~CallbackQuery.edit_message_caption` - - :meth:`~CallbackQuery.edit_message_media` - - :meth:`~CallbackQuery.edit_message_reply_markup` - -InlineQuery -^^^^^^^^^^^ - -.. hlist:: - :columns: 2 - - - :meth:`~InlineQuery.answer` - ------ - -Details -------- - -.. Message -.. automethod:: Message.click() -.. automethod:: Message.delete() -.. automethod:: Message.download() -.. automethod:: Message.forward() -.. automethod:: Message.pin() -.. automethod:: Message.edit_text() -.. automethod:: Message.edit_caption() -.. automethod:: Message.edit_media() -.. automethod:: Message.edit_reply_markup() -.. automethod:: Message.reply_text() -.. automethod:: Message.reply_animation() -.. automethod:: Message.reply_audio() -.. automethod:: Message.reply_cached_media() -.. automethod:: Message.reply_chat_action() -.. automethod:: Message.reply_contact() -.. automethod:: Message.reply_document() -.. automethod:: Message.reply_game() -.. automethod:: Message.reply_inline_bot_result() -.. automethod:: Message.reply_location() -.. automethod:: Message.reply_media_group() -.. automethod:: Message.reply_photo() -.. automethod:: Message.reply_poll() -.. automethod:: Message.reply_sticker() -.. automethod:: Message.reply_venue() -.. automethod:: Message.reply_video() -.. automethod:: Message.reply_video_note() -.. automethod:: Message.reply_voice() - -.. Chat -.. automethod:: Chat.archive() -.. automethod:: Chat.unarchive() -.. automethod:: Chat.set_title() -.. automethod:: Chat.set_description() -.. automethod:: Chat.set_photo() -.. automethod:: Chat.kick_member() -.. automethod:: Chat.unban_member() -.. automethod:: Chat.restrict_member() -.. automethod:: Chat.promote_member() - -.. User -.. automethod:: User.archive() -.. automethod:: User.unarchive() - -.. CallbackQuery -.. automethod:: CallbackQuery.answer() -.. automethod:: CallbackQuery.edit_message_text() -.. automethod:: CallbackQuery.edit_message_caption() -.. automethod:: CallbackQuery.edit_message_media() -.. automethod:: CallbackQuery.edit_message_reply_markup() - -.. InlineQuery -.. automethod:: InlineQuery.answer() diff --git a/docs/source/api/methods.rst b/docs/source/api/methods.rst deleted file mode 100644 index 9b2fc6c4..00000000 --- a/docs/source/api/methods.rst +++ /dev/null @@ -1,296 +0,0 @@ -Available Methods -================= - -This page is about Pyrogram methods. All the methods listed here are bound to a :class:`~pyrogram.Client` instance. - -.. code-block:: python - :emphasize-lines: 6 - - from pyrogram import Client - - app = Client("my_account") - - with app: - app.send_message("haskell", "hi") - -.. currentmodule:: pyrogram - -Index ------ - -Utilities -^^^^^^^^^ - -.. hlist:: - :columns: 4 - - - :meth:`~Client.start` - - :meth:`~Client.stop` - - :meth:`~Client.restart` - - :meth:`~Client.idle` - - :meth:`~Client.run` - - :meth:`~Client.add_handler` - - :meth:`~Client.remove_handler` - - :meth:`~Client.stop_transmission` - - :meth:`~Client.export_session_string` - -Messages -^^^^^^^^ - -.. hlist:: - :columns: 3 - - - :meth:`~Client.send_message` - - :meth:`~Client.forward_messages` - - :meth:`~Client.send_photo` - - :meth:`~Client.send_audio` - - :meth:`~Client.send_document` - - :meth:`~Client.send_sticker` - - :meth:`~Client.send_animated_sticker` - - :meth:`~Client.send_video` - - :meth:`~Client.send_animation` - - :meth:`~Client.send_voice` - - :meth:`~Client.send_video_note` - - :meth:`~Client.send_media_group` - - :meth:`~Client.send_location` - - :meth:`~Client.send_venue` - - :meth:`~Client.send_contact` - - :meth:`~Client.send_cached_media` - - :meth:`~Client.edit_message_text` - - :meth:`~Client.edit_message_caption` - - :meth:`~Client.edit_message_media` - - :meth:`~Client.edit_message_reply_markup` - - :meth:`~Client.edit_inline_text` - - :meth:`~Client.edit_inline_caption` - - :meth:`~Client.edit_inline_media` - - :meth:`~Client.edit_inline_reply_markup` - - :meth:`~Client.send_chat_action` - - :meth:`~Client.delete_messages` - - :meth:`~Client.get_messages` - - :meth:`~Client.get_history` - - :meth:`~Client.get_history_count` - - :meth:`~Client.read_history` - - :meth:`~Client.iter_history` - - :meth:`~Client.send_poll` - - :meth:`~Client.vote_poll` - - :meth:`~Client.stop_poll` - - :meth:`~Client.retract_vote` - - :meth:`~Client.download_media` - -Chats -^^^^^ - -.. hlist:: - :columns: 3 - - - :meth:`~Client.join_chat` - - :meth:`~Client.leave_chat` - - :meth:`~Client.kick_chat_member` - - :meth:`~Client.unban_chat_member` - - :meth:`~Client.restrict_chat_member` - - :meth:`~Client.promote_chat_member` - - :meth:`~Client.export_chat_invite_link` - - :meth:`~Client.set_chat_photo` - - :meth:`~Client.delete_chat_photo` - - :meth:`~Client.set_chat_title` - - :meth:`~Client.set_chat_description` - - :meth:`~Client.pin_chat_message` - - :meth:`~Client.unpin_chat_message` - - :meth:`~Client.get_chat` - - :meth:`~Client.get_chat_member` - - :meth:`~Client.get_chat_members` - - :meth:`~Client.get_chat_members_count` - - :meth:`~Client.iter_chat_members` - - :meth:`~Client.get_dialogs` - - :meth:`~Client.iter_dialogs` - - :meth:`~Client.get_dialogs_count` - - :meth:`~Client.restrict_chat` - - :meth:`~Client.update_chat_username` - - :meth:`~Client.archive_chats` - - :meth:`~Client.unarchive_chats` - -Users -^^^^^ - -.. hlist:: - :columns: 3 - - - :meth:`~Client.get_me` - - :meth:`~Client.get_users` - - :meth:`~Client.get_profile_photos` - - :meth:`~Client.get_profile_photos_count` - - :meth:`~Client.iter_profile_photos` - - :meth:`~Client.set_profile_photo` - - :meth:`~Client.delete_profile_photos` - - :meth:`~Client.update_username` - - :meth:`~Client.get_user_dc` - - :meth:`~Client.block_user` - - :meth:`~Client.unblock_user` - -Contacts -^^^^^^^^ - -.. hlist:: - :columns: 3 - - - :meth:`~Client.add_contacts` - - :meth:`~Client.get_contacts` - - :meth:`~Client.get_contacts_count` - - :meth:`~Client.delete_contacts` - -Password -^^^^^^^^ - -.. hlist:: - :columns: 3 - - - :meth:`~Client.enable_cloud_password` - - :meth:`~Client.change_cloud_password` - - :meth:`~Client.remove_cloud_password` - -Bots -^^^^ - -.. hlist:: - :columns: 3 - - - :meth:`~Client.get_inline_bot_results` - - :meth:`~Client.send_inline_bot_result` - - :meth:`~Client.answer_callback_query` - - :meth:`~Client.answer_inline_query` - - :meth:`~Client.request_callback_answer` - - :meth:`~Client.send_game` - - :meth:`~Client.set_game_score` - - :meth:`~Client.get_game_high_scores` - -Advanced Usage (Raw API) -^^^^^^^^^^^^^^^^^^^^^^^^ - -Learn more about these methods at :doc:`Advanced Usage <../topics/advanced-usage>`. - -.. hlist:: - :columns: 4 - - - :meth:`~Client.send` - - :meth:`~Client.resolve_peer` - - :meth:`~Client.save_file` - ------ - -Details -------- - -.. Utilities -.. automethod:: Client.start() -.. automethod:: Client.stop() -.. automethod:: Client.restart() -.. automethod:: Client.idle() -.. automethod:: Client.run() -.. automethod:: Client.add_handler() -.. automethod:: Client.remove_handler() -.. automethod:: Client.stop_transmission() -.. automethod:: Client.export_session_string() - -.. Messages -.. automethod:: Client.send_message() -.. automethod:: Client.forward_messages() -.. automethod:: Client.send_photo() -.. automethod:: Client.send_audio() -.. automethod:: Client.send_document() -.. automethod:: Client.send_sticker() -.. automethod:: Client.send_animated_sticker() -.. automethod:: Client.send_video() -.. automethod:: Client.send_animation() -.. automethod:: Client.send_voice() -.. automethod:: Client.send_video_note() -.. automethod:: Client.send_media_group() -.. automethod:: Client.send_location() -.. automethod:: Client.send_venue() -.. automethod:: Client.send_contact() -.. automethod:: Client.send_cached_media() -.. automethod:: Client.send_chat_action() -.. automethod:: Client.edit_message_text() -.. automethod:: Client.edit_message_caption() -.. automethod:: Client.edit_message_media() -.. automethod:: Client.edit_message_reply_markup() -.. automethod:: Client.edit_inline_text() -.. automethod:: Client.edit_inline_caption() -.. automethod:: Client.edit_inline_media() -.. automethod:: Client.edit_inline_reply_markup() -.. automethod:: Client.delete_messages() -.. automethod:: Client.get_messages() -.. automethod:: Client.get_history() -.. automethod:: Client.get_history_count() -.. automethod:: Client.read_history() -.. automethod:: Client.iter_history() -.. automethod:: Client.send_poll() -.. automethod:: Client.vote_poll() -.. automethod:: Client.stop_poll() -.. automethod:: Client.retract_vote() -.. automethod:: Client.download_media() - -.. Chats -.. automethod:: Client.join_chat() -.. automethod:: Client.leave_chat() -.. automethod:: Client.kick_chat_member() -.. automethod:: Client.unban_chat_member() -.. automethod:: Client.restrict_chat_member() -.. automethod:: Client.promote_chat_member() -.. automethod:: Client.export_chat_invite_link() -.. automethod:: Client.set_chat_photo() -.. automethod:: Client.delete_chat_photo() -.. automethod:: Client.set_chat_title() -.. automethod:: Client.set_chat_description() -.. automethod:: Client.pin_chat_message() -.. automethod:: Client.unpin_chat_message() -.. automethod:: Client.get_chat() -.. automethod:: Client.get_chat_member() -.. automethod:: Client.get_chat_members() -.. automethod:: Client.get_chat_members_count() -.. automethod:: Client.iter_chat_members() -.. automethod:: Client.get_dialogs() -.. automethod:: Client.iter_dialogs() -.. automethod:: Client.get_dialogs_count() -.. automethod:: Client.restrict_chat() -.. automethod:: Client.update_chat_username() -.. automethod:: Client.archive_chats() -.. automethod:: Client.unarchive_chats() - -.. Users -.. automethod:: Client.get_me() -.. automethod:: Client.get_users() -.. automethod:: Client.get_profile_photos() -.. automethod:: Client.get_profile_photos_count() -.. automethod:: Client.iter_profile_photos() -.. automethod:: Client.set_profile_photo() -.. automethod:: Client.delete_profile_photos() -.. automethod:: Client.update_username() -.. automethod:: Client.get_user_dc() -.. automethod:: Client.block_user() -.. automethod:: Client.unblock_user() - -.. Contacts -.. automethod:: Client.add_contacts() -.. automethod:: Client.get_contacts() -.. automethod:: Client.get_contacts_count() -.. automethod:: Client.delete_contacts() - -.. Password -.. automethod:: Client.enable_cloud_password() -.. automethod:: Client.change_cloud_password() -.. automethod:: Client.remove_cloud_password() - -.. Bots -.. automethod:: Client.get_inline_bot_results() -.. automethod:: Client.send_inline_bot_result() -.. automethod:: Client.answer_callback_query() -.. automethod:: Client.answer_inline_query() -.. automethod:: Client.request_callback_answer() -.. automethod:: Client.send_game() -.. automethod:: Client.set_game_score() -.. automethod:: Client.get_game_high_scores() - -.. Advanced Usage -.. automethod:: Client.send() -.. automethod:: Client.resolve_peer() -.. automethod:: Client.save_file() \ No newline at end of file diff --git a/docs/source/api/types.rst b/docs/source/api/types.rst deleted file mode 100644 index 2147996d..00000000 --- a/docs/source/api/types.rst +++ /dev/null @@ -1,172 +0,0 @@ -Available Types -=============== - -This page is about Pyrogram types. All types listed here are accessible through the main package directly. - -.. code-block:: python - :emphasize-lines: 1 - - from pyrogram import User, Message, ... - -.. note:: - - **Optional** fields may not exist when irrelevant -- i.e.: they will contain the value of ``None`` and aren't shown - when, for example, using ``print()``. - -.. currentmodule:: pyrogram - -Index ------ - -Users & Chats -^^^^^^^^^^^^^ - -.. hlist:: - :columns: 5 - - - :class:`User` - - :class:`UserStatus` - - :class:`Chat` - - :class:`ChatPreview` - - :class:`ChatPhoto` - - :class:`ChatMember` - - :class:`ChatPermissions` - - :class:`Dialog` - -Messages & Media -^^^^^^^^^^^^^^^^ - -.. hlist:: - :columns: 5 - - - :class:`Message` - - :class:`MessageEntity` - - :class:`Photo` - - :class:`Thumbnail` - - :class:`Audio` - - :class:`Document` - - :class:`Animation` - - :class:`Video` - - :class:`Voice` - - :class:`VideoNote` - - :class:`Contact` - - :class:`Location` - - :class:`Venue` - - :class:`Sticker` - - :class:`Game` - - :class:`WebPage` - - :class:`Poll` - - :class:`PollOption` - -Bots & Keyboards -^^^^^^^^^^^^^^^^ - -.. hlist:: - :columns: 4 - - - :class:`ReplyKeyboardMarkup` - - :class:`KeyboardButton` - - :class:`ReplyKeyboardRemove` - - :class:`InlineKeyboardMarkup` - - :class:`InlineKeyboardButton` - - :class:`ForceReply` - - :class:`CallbackQuery` - - :class:`GameHighScore` - - :class:`CallbackGame` - -Input Media -^^^^^^^^^^^ - -.. hlist:: - :columns: 4 - - - :class:`InputMedia` - - :class:`InputMediaPhoto` - - :class:`InputMediaVideo` - - :class:`InputMediaAudio` - - :class:`InputMediaAnimation` - - :class:`InputMediaDocument` - - :class:`InputPhoneContact` - -Inline Mode -^^^^^^^^^^^ - -.. hlist:: - :columns: 3 - - - :class:`InlineQuery` - - :class:`InlineQueryResult` - - :class:`InlineQueryResultArticle` - -InputMessageContent -^^^^^^^^^^^^^^^^^^^ - -.. hlist:: - :columns: 3 - - - :class:`InputMessageContent` - - :class:`InputTextMessageContent` - ------ - -Details -------- - -.. User & Chats -.. autoclass:: User() -.. autoclass:: UserStatus() -.. autoclass:: Chat() -.. autoclass:: ChatPreview() -.. autoclass:: ChatPhoto() -.. autoclass:: ChatMember() -.. autoclass:: ChatPermissions() -.. autoclass:: Dialog() - -.. Messages & Media -.. autoclass:: Message() -.. autoclass:: MessageEntity() -.. autoclass:: Photo() -.. autoclass:: Thumbnail() -.. autoclass:: Audio() -.. autoclass:: Document() -.. autoclass:: Animation() -.. autoclass:: Video() -.. autoclass:: Voice() -.. autoclass:: VideoNote() -.. autoclass:: Contact() -.. autoclass:: Location() -.. autoclass:: Venue() -.. autoclass:: Sticker() -.. autoclass:: Game() -.. autoclass:: WebPage() -.. autoclass:: Poll() -.. autoclass:: PollOption() - -.. Bots & Keyboards -.. autoclass:: ReplyKeyboardMarkup() -.. autoclass:: KeyboardButton() -.. autoclass:: ReplyKeyboardRemove() -.. autoclass:: InlineKeyboardMarkup() -.. autoclass:: InlineKeyboardButton() -.. autoclass:: ForceReply() -.. autoclass:: CallbackQuery() -.. autoclass:: GameHighScore() -.. autoclass:: CallbackGame() - -.. Input Media -.. autoclass:: InputMedia() -.. autoclass:: InputMediaPhoto() -.. autoclass:: InputMediaVideo() -.. autoclass:: InputMediaAudio() -.. autoclass:: InputMediaAnimation() -.. autoclass:: InputMediaDocument() -.. autoclass:: InputPhoneContact() - -.. Inline Mode -.. autoclass:: InlineQuery() -.. autoclass:: InlineQueryResult() -.. autoclass:: InlineQueryResultArticle() - -.. InputMessageContent -.. autoclass:: InputMessageContent() -.. autoclass:: InputTextMessageContent() diff --git a/docs/source/index.rst b/docs/source/index.rst index b37bf2dc..3743cf30 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -23,9 +23,9 @@ Welcome to Pyrogram :caption: API Reference api/client - api/methods - api/types - api/bound-methods + api/methods/index + api/types/index + api/bound-methods/index api/handlers api/decorators api/errors diff --git a/setup.py b/setup.py index 45c2871b..f24020ab 100644 --- a/setup.py +++ b/setup.py @@ -40,7 +40,11 @@ with open("README.md", encoding="utf-8") as f: class Clean(Command): DIST = ["./build", "./dist", "./Pyrogram.egg-info"] API = ["pyrogram/api/errors/exceptions", "pyrogram/api/functions", "pyrogram/api/types", "pyrogram/api/all.py"] - DOCS = ["docs/source/telegram", "docs/build"] + DOCS = [ + "docs/source/telegram", "docs/build", "docs/source/api/methods", "docs/source/api/types", + "docs/source/api/bound-methods" + ] + ALL = DIST + API + DOCS description = "Clean generated files" @@ -122,7 +126,6 @@ class Generate(Command): if len(argv) > 1 and argv[1] in ["bdist_wheel", "install", "develop"]: api_compiler.start() error_compiler.start() - docs_compiler.start() setup( name="Pyrogram", From ed2e7dfb6695ca98474ca0f5ecc785b2ff82219a Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 9 Jul 2019 19:09:35 +0200 Subject: [PATCH 125/202] Fix cleaning generated exceptions --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f24020ab..508bca90 100644 --- a/setup.py +++ b/setup.py @@ -39,7 +39,7 @@ with open("README.md", encoding="utf-8") as f: class Clean(Command): DIST = ["./build", "./dist", "./Pyrogram.egg-info"] - API = ["pyrogram/api/errors/exceptions", "pyrogram/api/functions", "pyrogram/api/types", "pyrogram/api/all.py"] + API = ["pyrogram/errors/exceptions", "pyrogram/api/functions", "pyrogram/api/types", "pyrogram/api/all.py"] DOCS = [ "docs/source/telegram", "docs/build", "docs/source/api/methods", "docs/source/api/types", "docs/source/api/bound-methods" From b352ef334eac1d3fff853564da051514ae4417d4 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 9 Jul 2019 19:10:11 +0200 Subject: [PATCH 126/202] Fix Pyrogram's API template path --- compiler/docs/compiler.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index b9e13dd1..6864f9de 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -255,7 +255,7 @@ def pyrogram_api(): shutil.rmtree(root, ignore_errors=True) os.mkdir(root) - with open("template/methods.rst") as f: + with open(HOME + "/template/methods.rst") as f: template = f.read() with open(root + "/index.rst", "w") as f: @@ -349,7 +349,7 @@ def pyrogram_api(): shutil.rmtree(root, ignore_errors=True) os.mkdir(root) - with open("template/types.rst") as f: + with open(HOME + "/template/types.rst") as f: template = f.read() with open(root + "/index.rst", "w") as f: @@ -439,7 +439,7 @@ def pyrogram_api(): shutil.rmtree(root, ignore_errors=True) os.mkdir(root) - with open("template/bound-methods.rst") as f: + with open(HOME + "/template/bound-methods.rst") as f: template = f.read() with open(root + "/index.rst", "w") as f: From 448a65bb6d65fc8eb5af006dd9e34444fbc81fb5 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 9 Jul 2019 20:02:51 +0200 Subject: [PATCH 127/202] Fix some cross references --- docs/source/index.rst | 6 +++--- docs/source/topics/advanced-usage.rst | 20 ++++++++++---------- docs/source/topics/serialize.rst | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index 3743cf30..5cb9bb2e 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -142,9 +142,9 @@ API Reference :columns: 2 - :doc:`Pyrogram Client `: Reference details about the Client class. - - :doc:`Available Methods `: List of available high-level methods. - - :doc:`Available Types `: List of available high-level types. - - :doc:`Bound Methods `: List of convenient bound methods. + - :doc:`Available Methods `: List of available high-level methods. + - :doc:`Available Types `: List of available high-level types. + - :doc:`Bound Methods `: List of convenient bound methods. Meta ^^^^ diff --git a/docs/source/topics/advanced-usage.rst b/docs/source/topics/advanced-usage.rst index 9c794be0..1460a3d8 100644 --- a/docs/source/topics/advanced-usage.rst +++ b/docs/source/topics/advanced-usage.rst @@ -1,9 +1,9 @@ Advanced Usage ============== -Pyrogram's API, which consists of well documented convenience :doc:`methods <../api/methods>` and facade -:doc:`types <../api/types>`, exists to provide a much easier interface to the undocumented and often confusing Telegram -API. +Pyrogram's API, which consists of well documented convenience :doc:`methods <../api/methods/index>` and facade +:doc:`types <../api/types/index>`, exists to provide a much easier interface to the undocumented and often confusing +Telegram API. In this section, you'll be shown the alternative way of communicating with Telegram using Pyrogram: the main "raw" Telegram API with its functions and types. @@ -23,21 +23,21 @@ some pitfalls to take into consideration when working with the raw API. Every available high-level methods in Pyrogram is built on top of these raw functions. Nothing stops you from using the raw functions only, but they are rather complex and - :doc:`plenty of them <../api/methods>` are already re-implemented by providing a much simpler and cleaner interface - which is very similar to the Bot API (yet much more powerful). + :doc:`plenty of them <../api/methods/index>` are already re-implemented by providing a much simpler and cleaner + interface which is very similar to the Bot API (yet much more powerful). If you think a raw function should be wrapped and added as a high-level method, feel free to ask in our Community_! Invoking Functions ^^^^^^^^^^^^^^^^^^ -Unlike the :doc:`methods <../api/methods>` found in Pyrogram's API, which can be called in the usual simple way, +Unlike the :doc:`methods <../api/methods/index>` found in Pyrogram's API, which can be called in the usual simple way, functions to be invoked from the raw Telegram API have a different way of usage and are more complex. -First of all, both :doc:`raw functions <../telegram/functions/index>` and :doc:`raw types <../telegram/types/index>` live in their -respective packages (and sub-packages): ``pyrogram.api.functions``, ``pyrogram.api.types``. They all exist as Python -classes, meaning you need to create an instance of each every time you need them and fill them in with the correct -values using named arguments. +First of all, both :doc:`raw functions <../telegram/functions/index>` and :doc:`raw types <../telegram/types/index>` +live in their respective packages (and sub-packages): ``pyrogram.api.functions``, ``pyrogram.api.types``. They all exist +as Python classes, meaning you need to create an instance of each every time you need them and fill them in with the +correct values using named arguments. Next, to actually invoke the raw function you have to use the :meth:`~pyrogram.Client.send` method provided by the Client class and pass the function object you created. diff --git a/docs/source/topics/serialize.rst b/docs/source/topics/serialize.rst index a238f8dc..4c0b2327 100644 --- a/docs/source/topics/serialize.rst +++ b/docs/source/topics/serialize.rst @@ -9,7 +9,7 @@ For Humans - str(obj) --------------------- If you want a nicely formatted, human readable JSON representation of any object in the API -- namely, any object from -:doc:`Pyrogram types <../api/types>`, :doc:`raw functions <../telegram/functions/index>` and +:doc:`Pyrogram types <../api/types/index>`, :doc:`raw functions <../telegram/functions/index>` and :doc:`raw types <../telegram/types/index>` -- you can use use ``str(obj)``. .. code-block:: python From e039c1fb801232ceba855f8025e458ca0c4008a8 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 9 Jul 2019 20:03:03 +0200 Subject: [PATCH 128/202] Update Client.idle() docstrings to make summary clearer --- pyrogram/client/client.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index fdfc1e68..9c19e49a 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -395,8 +395,10 @@ class Client(Methods, BaseClient): 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. + """Block the main script execution until a signal is received. + + Once the signal is received (e.g.: from CTRL+C), the client will automatically stop and the main script will + continue its execution. 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 From 674e1996ca2029a9972a9cf6fe1b8116013c353d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 9 Jul 2019 20:06:19 +0200 Subject: [PATCH 129/202] Add STICKERSET_INVALID error --- compiler/error/source/406_NOT_ACCEPTABLE.tsv | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/error/source/406_NOT_ACCEPTABLE.tsv b/compiler/error/source/406_NOT_ACCEPTABLE.tsv index e94706ed..1c8e5647 100644 --- a/compiler/error/source/406_NOT_ACCEPTABLE.tsv +++ b/compiler/error/source/406_NOT_ACCEPTABLE.tsv @@ -1,3 +1,4 @@ id message AUTH_KEY_DUPLICATED Authorization error - you must delete your session file and log in again with your phone number -FILEREF_UPGRADE_NEEDED The file reference has expired - you must obtain the original media message \ No newline at end of file +FILEREF_UPGRADE_NEEDED The file reference has expired - you must obtain the original media message +STICKERSET_INVALID The sticker set is invalid \ No newline at end of file From 856870c195e72ab57b9d3f1248a35e03b2704bfa Mon Sep 17 00:00:00 2001 From: kalmengr <46006289+kalmengr@users.noreply.github.com> Date: Tue, 9 Jul 2019 17:33:41 -0400 Subject: [PATCH 130/202] Add bound methods block and unblock to User object --- pyrogram/client/types/user_and_chats/user.py | 35 ++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/pyrogram/client/types/user_and_chats/user.py b/pyrogram/client/types/user_and_chats/user.py index 248c4c0a..c8041cb9 100644 --- a/pyrogram/client/types/user_and_chats/user.py +++ b/pyrogram/client/types/user_and_chats/user.py @@ -214,3 +214,38 @@ class User(Object): """ return self._client.unarchive_chats(self.id) + + + def block(self): + """Bound method *block* of :obj:`User`. + Use as a shortcut for: + .. code-block:: python + client.block_user(123456789) + Example: + .. code-block:: python + user.block() + Returns: + True on success. + Raises: + RPCError: In case of a Telegram RPC error. + """ + + return self._client.block_user(self.id) + + + def unblock(self): + """Bound method *unblock* of :obj:`User`. + Use as a shortcut for: + .. code-block:: python + client.unblock_user(123456789) + Example: + .. code-block:: python + user.unblock() + Returns: + True on success. + Raises: + RPCError: In case of a Telegram RPC error. + """ + + return self._client.unblock_user(self.id) + From 92d47f17f3142f003397822fdd338e92ee1f1e5e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 9 Jul 2019 23:39:48 +0200 Subject: [PATCH 131/202] Add new blank lines for clarity and for fixing rst --- pyrogram/client/types/user_and_chats/user.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/pyrogram/client/types/user_and_chats/user.py b/pyrogram/client/types/user_and_chats/user.py index c8041cb9..9cdd9760 100644 --- a/pyrogram/client/types/user_and_chats/user.py +++ b/pyrogram/client/types/user_and_chats/user.py @@ -215,17 +215,23 @@ class User(Object): return self._client.unarchive_chats(self.id) - def block(self): """Bound method *block* of :obj:`User`. + Use as a shortcut for: + .. code-block:: python + client.block_user(123456789) + Example: .. code-block:: python + user.block() + Returns: True on success. + Raises: RPCError: In case of a Telegram RPC error. """ @@ -235,14 +241,21 @@ class User(Object): def unblock(self): """Bound method *unblock* of :obj:`User`. + Use as a shortcut for: + .. code-block:: python + client.unblock_user(123456789) + Example: .. code-block:: python + user.unblock() + Returns: True on success. + Raises: RPCError: In case of a Telegram RPC error. """ From 6a62d1b8c4e45ad3a5ca71139ffc21dd8e3ebed6 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 9 Jul 2019 23:51:43 +0200 Subject: [PATCH 132/202] Fix tiny typo: contact -> contacts --- pyrogram/client/methods/users/block_user.py | 2 +- pyrogram/client/methods/users/unblock_user.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/methods/users/block_user.py b/pyrogram/client/methods/users/block_user.py index 1b4cc31a..ef3cad85 100644 --- a/pyrogram/client/methods/users/block_user.py +++ b/pyrogram/client/methods/users/block_user.py @@ -38,7 +38,7 @@ class BlockUser(BaseClient): """ return bool( self.send( - functions.contact.Block( + functions.contacts.Block( id=self.resolve_peer(user_id) ) ) diff --git a/pyrogram/client/methods/users/unblock_user.py b/pyrogram/client/methods/users/unblock_user.py index b2212762..c06533cd 100644 --- a/pyrogram/client/methods/users/unblock_user.py +++ b/pyrogram/client/methods/users/unblock_user.py @@ -38,7 +38,7 @@ class UnblockUser(BaseClient): """ return bool( self.send( - functions.contact.Unblock( + functions.contacts.Unblock( id=self.resolve_peer(user_id) ) ) From be8d1068c053a476e2264a79ae92f2a98b01b82f Mon Sep 17 00:00:00 2001 From: kalmengr <46006289+kalmengr@users.noreply.github.com> Date: Wed, 10 Jul 2019 10:39:07 -0400 Subject: [PATCH 133/202] Add join and leave methods to Chat object --- pyrogram/client/types/user_and_chats/chat.py | 48 ++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py index 586b49fe..304eb8d2 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -673,3 +673,51 @@ class Chat(Object): can_pin_messages=can_pin_messages, can_promote_members=can_promote_members ) + + + + + def join(self): + """Bound method *join* of :obj:`Chat`. + + Use as a shortcut for: + + .. code-block:: python + + client.join_chat(123456789) + + Example: + .. code-block:: python + + chat.join() + + Returns: + :obj:`Chat`: On success, a chat object is returned. + + Raises: + RPCError: In case of a Telegram RPC error. + """ + + return self._client.join_chat(self.id) + + + def leave(self): + """Bound method *leave* of :obj:`Chat`. + + Use as a shortcut for: + + .. code-block:: python + + client.leave_chat(123456789) + + Example: + .. code-block:: python + + chat.leave() + + Raises: + RPCError: In case of a Telegram RPC error. + """ + + return self._client.leave_chat(self.id) + From e41d21ba295bfc748bddb04861f5c14f939e0db5 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 10 Jul 2019 20:03:10 +0200 Subject: [PATCH 134/202] Add "md" to possible parse modes for Markdown style --- .../methods/messages/edit_inline_caption.py | 2 +- .../methods/messages/edit_inline_text.py | 2 +- .../methods/messages/edit_message_caption.py | 2 +- .../methods/messages/edit_message_text.py | 2 +- .../client/methods/messages/send_animation.py | 2 +- .../client/methods/messages/send_audio.py | 2 +- .../methods/messages/send_cached_media.py | 2 +- .../client/methods/messages/send_document.py | 2 +- .../client/methods/messages/send_message.py | 2 +- .../client/methods/messages/send_photo.py | 2 +- .../client/methods/messages/send_video.py | 2 +- .../client/methods/messages/send_voice.py | 2 +- pyrogram/client/parser/parser.py | 2 +- .../bots_and_keyboards/callback_query.py | 4 ++-- .../input_media/input_media_animation.py | 2 +- .../types/input_media/input_media_audio.py | 2 +- .../types/input_media/input_media_document.py | 2 +- .../types/input_media/input_media_photo.py | 2 +- .../types/input_media/input_media_video.py | 2 +- .../input_text_message_content.py | 2 +- .../types/messages_and_media/message.py | 20 +++++++++---------- 21 files changed, 31 insertions(+), 31 deletions(-) diff --git a/pyrogram/client/methods/messages/edit_inline_caption.py b/pyrogram/client/methods/messages/edit_inline_caption.py index aa5661dd..2d904198 100644 --- a/pyrogram/client/methods/messages/edit_inline_caption.py +++ b/pyrogram/client/methods/messages/edit_inline_caption.py @@ -40,7 +40,7 @@ class EditInlineCaption(BaseClient): parse_mode (``str``, *optional*): By default, texts are parsed using both Markdown and HTML styles. You can combine both syntaxes together. - Pass "markdown" to enable Markdown-style parsing only. + 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. diff --git a/pyrogram/client/methods/messages/edit_inline_text.py b/pyrogram/client/methods/messages/edit_inline_text.py index 3c153c3c..0d17b4a4 100644 --- a/pyrogram/client/methods/messages/edit_inline_text.py +++ b/pyrogram/client/methods/messages/edit_inline_text.py @@ -44,7 +44,7 @@ class EditInlineText(BaseClient): parse_mode (``str``, *optional*): By default, texts are parsed using both Markdown and HTML styles. You can combine both syntaxes together. - Pass "markdown" to enable Markdown-style parsing only. + 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. diff --git a/pyrogram/client/methods/messages/edit_message_caption.py b/pyrogram/client/methods/messages/edit_message_caption.py index 3c2cd138..6fefe0b2 100644 --- a/pyrogram/client/methods/messages/edit_message_caption.py +++ b/pyrogram/client/methods/messages/edit_message_caption.py @@ -48,7 +48,7 @@ class EditMessageCaption(BaseClient): parse_mode (``str``, *optional*): By default, texts are parsed using both Markdown and HTML styles. You can combine both syntaxes together. - Pass "markdown" to enable Markdown-style parsing only. + 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. diff --git a/pyrogram/client/methods/messages/edit_message_text.py b/pyrogram/client/methods/messages/edit_message_text.py index 9a7a1a09..c81139af 100644 --- a/pyrogram/client/methods/messages/edit_message_text.py +++ b/pyrogram/client/methods/messages/edit_message_text.py @@ -50,7 +50,7 @@ class EditMessageText(BaseClient): parse_mode (``str``, *optional*): By default, texts are parsed using both Markdown and HTML styles. You can combine both syntaxes together. - Pass "markdown" to enable Markdown-style parsing only. + 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. diff --git a/pyrogram/client/methods/messages/send_animation.py b/pyrogram/client/methods/messages/send_animation.py index c0c57059..34389149 100644 --- a/pyrogram/client/methods/messages/send_animation.py +++ b/pyrogram/client/methods/messages/send_animation.py @@ -72,7 +72,7 @@ class SendAnimation(BaseClient): parse_mode (``str``, *optional*): By default, texts are parsed using both Markdown and HTML styles. You can combine both syntaxes together. - Pass "markdown" to enable Markdown-style parsing only. + 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. diff --git a/pyrogram/client/methods/messages/send_audio.py b/pyrogram/client/methods/messages/send_audio.py index 7759a6e9..43c5a63e 100644 --- a/pyrogram/client/methods/messages/send_audio.py +++ b/pyrogram/client/methods/messages/send_audio.py @@ -69,7 +69,7 @@ class SendAudio(BaseClient): parse_mode (``str``, *optional*): By default, texts are parsed using both Markdown and HTML styles. You can combine both syntaxes together. - Pass "markdown" to enable Markdown-style parsing only. + 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. diff --git a/pyrogram/client/methods/messages/send_cached_media.py b/pyrogram/client/methods/messages/send_cached_media.py index 6c947d96..0f2e1389 100644 --- a/pyrogram/client/methods/messages/send_cached_media.py +++ b/pyrogram/client/methods/messages/send_cached_media.py @@ -61,7 +61,7 @@ class SendCachedMedia(BaseClient): parse_mode (``str``, *optional*): By default, texts are parsed using both Markdown and HTML styles. You can combine both syntaxes together. - Pass "markdown" to enable Markdown-style parsing only. + 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. diff --git a/pyrogram/client/methods/messages/send_document.py b/pyrogram/client/methods/messages/send_document.py index e496b604..fcaf5f51 100644 --- a/pyrogram/client/methods/messages/send_document.py +++ b/pyrogram/client/methods/messages/send_document.py @@ -70,7 +70,7 @@ class SendDocument(BaseClient): parse_mode (``str``, *optional*): By default, texts are parsed using both Markdown and HTML styles. You can combine both syntaxes together. - Pass "markdown" to enable Markdown-style parsing only. + 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. diff --git a/pyrogram/client/methods/messages/send_message.py b/pyrogram/client/methods/messages/send_message.py index 67861f3f..15b5f25c 100644 --- a/pyrogram/client/methods/messages/send_message.py +++ b/pyrogram/client/methods/messages/send_message.py @@ -53,7 +53,7 @@ class SendMessage(BaseClient): parse_mode (``str``, *optional*): By default, texts are parsed using both Markdown and HTML styles. You can combine both syntaxes together. - Pass "markdown" to enable Markdown-style parsing only. + 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. diff --git a/pyrogram/client/methods/messages/send_photo.py b/pyrogram/client/methods/messages/send_photo.py index 93178f26..c43c0139 100644 --- a/pyrogram/client/methods/messages/send_photo.py +++ b/pyrogram/client/methods/messages/send_photo.py @@ -64,7 +64,7 @@ class SendPhoto(BaseClient): parse_mode (``str``, *optional*): By default, texts are parsed using both Markdown and HTML styles. You can combine both syntaxes together. - Pass "markdown" to enable Markdown-style parsing only. + 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. diff --git a/pyrogram/client/methods/messages/send_video.py b/pyrogram/client/methods/messages/send_video.py index feacf5c7..09005775 100644 --- a/pyrogram/client/methods/messages/send_video.py +++ b/pyrogram/client/methods/messages/send_video.py @@ -68,7 +68,7 @@ class SendVideo(BaseClient): parse_mode (``str``, *optional*): By default, texts are parsed using both Markdown and HTML styles. You can combine both syntaxes together. - Pass "markdown" to enable Markdown-style parsing only. + 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. diff --git a/pyrogram/client/methods/messages/send_voice.py b/pyrogram/client/methods/messages/send_voice.py index ffed409e..854385d8 100644 --- a/pyrogram/client/methods/messages/send_voice.py +++ b/pyrogram/client/methods/messages/send_voice.py @@ -64,7 +64,7 @@ class SendVoice(BaseClient): parse_mode (``str``, *optional*): By default, texts are parsed using both Markdown and HTML styles. You can combine both syntaxes together. - Pass "markdown" to enable Markdown-style parsing only. + 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. diff --git a/pyrogram/client/parser/parser.py b/pyrogram/client/parser/parser.py index cde26f0b..8fde46bd 100644 --- a/pyrogram/client/parser/parser.py +++ b/pyrogram/client/parser/parser.py @@ -44,7 +44,7 @@ class Parser: if mode == "": return self.markdown.parse(text) - if mode in "markdown": + if mode in ["markdown", "md"]: return self.markdown.parse(text, True) if mode == "html": diff --git a/pyrogram/client/types/bots_and_keyboards/callback_query.py b/pyrogram/client/types/bots_and_keyboards/callback_query.py index f631db45..b09e5440 100644 --- a/pyrogram/client/types/bots_and_keyboards/callback_query.py +++ b/pyrogram/client/types/bots_and_keyboards/callback_query.py @@ -191,7 +191,7 @@ class CallbackQuery(Object, Update): parse_mode (``str``, *optional*): By default, texts are parsed using both Markdown and HTML styles. You can combine both syntaxes together. - Pass "markdown" to enable Markdown-style parsing only. + 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. @@ -243,7 +243,7 @@ class CallbackQuery(Object, Update): parse_mode (``str``, *optional*): By default, texts are parsed using both Markdown and HTML styles. You can combine both syntaxes together. - Pass "markdown" to enable Markdown-style parsing only. + 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. diff --git a/pyrogram/client/types/input_media/input_media_animation.py b/pyrogram/client/types/input_media/input_media_animation.py index 14920723..e157993b 100644 --- a/pyrogram/client/types/input_media/input_media_animation.py +++ b/pyrogram/client/types/input_media/input_media_animation.py @@ -42,7 +42,7 @@ class InputMediaAnimation(InputMedia): parse_mode (``str``, *optional*): By default, texts are parsed using both Markdown and HTML styles. You can combine both syntaxes together. - Pass "markdown" to enable Markdown-style parsing only. + 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. diff --git a/pyrogram/client/types/input_media/input_media_audio.py b/pyrogram/client/types/input_media/input_media_audio.py index f360d3c6..3eb3ea65 100644 --- a/pyrogram/client/types/input_media/input_media_audio.py +++ b/pyrogram/client/types/input_media/input_media_audio.py @@ -44,7 +44,7 @@ class InputMediaAudio(InputMedia): parse_mode (``str``, *optional*): By default, texts are parsed using both Markdown and HTML styles. You can combine both syntaxes together. - Pass "markdown" to enable Markdown-style parsing only. + 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. diff --git a/pyrogram/client/types/input_media/input_media_document.py b/pyrogram/client/types/input_media/input_media_document.py index 629f7469..7aca9a31 100644 --- a/pyrogram/client/types/input_media/input_media_document.py +++ b/pyrogram/client/types/input_media/input_media_document.py @@ -42,7 +42,7 @@ class InputMediaDocument(InputMedia): parse_mode (``str``, *optional*): By default, texts are parsed using both Markdown and HTML styles. You can combine both syntaxes together. - Pass "markdown" to enable Markdown-style parsing only. + 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. """ diff --git a/pyrogram/client/types/input_media/input_media_photo.py b/pyrogram/client/types/input_media/input_media_photo.py index 97077d1d..d2f26a88 100644 --- a/pyrogram/client/types/input_media/input_media_photo.py +++ b/pyrogram/client/types/input_media/input_media_photo.py @@ -38,7 +38,7 @@ class InputMediaPhoto(InputMedia): parse_mode (``str``, *optional*): By default, texts are parsed using both Markdown and HTML styles. You can combine both syntaxes together. - Pass "markdown" to enable Markdown-style parsing only. + 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. """ diff --git a/pyrogram/client/types/input_media/input_media_video.py b/pyrogram/client/types/input_media/input_media_video.py index 50d70004..d2ee851d 100644 --- a/pyrogram/client/types/input_media/input_media_video.py +++ b/pyrogram/client/types/input_media/input_media_video.py @@ -44,7 +44,7 @@ class InputMediaVideo(InputMedia): parse_mode (``str``, *optional*): By default, texts are parsed using both Markdown and HTML styles. You can combine both syntaxes together. - Pass "markdown" to enable Markdown-style parsing only. + 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. 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 f1c24631..f4b9aefc 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 @@ -33,7 +33,7 @@ class InputTextMessageContent(InputMessageContent): parse_mode (``str``, *optional*): By default, texts are parsed using both Markdown and HTML styles. You can combine both syntaxes together. - Pass "markdown" to enable Markdown-style parsing only. + 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. diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index a1d42b79..144f04a4 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -689,7 +689,7 @@ class Message(Object, Update): parse_mode (``str``, *optional*): By default, texts are parsed using both Markdown and HTML styles. You can combine both syntaxes together. - Pass "markdown" to enable Markdown-style parsing only. + 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. @@ -786,7 +786,7 @@ class Message(Object, Update): parse_mode (``str``, *optional*): By default, texts are parsed using both Markdown and HTML styles. You can combine both syntaxes together. - Pass "markdown" to enable Markdown-style parsing only. + 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. @@ -923,7 +923,7 @@ class Message(Object, Update): parse_mode (``str``, *optional*): By default, texts are parsed using both Markdown and HTML styles. You can combine both syntaxes together. - Pass "markdown" to enable Markdown-style parsing only. + 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. @@ -1052,7 +1052,7 @@ class Message(Object, Update): parse_mode (``str``, *optional*): By default, texts are parsed using both Markdown and HTML styles. You can combine both syntaxes together. - Pass "markdown" to enable Markdown-style parsing only. + 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. @@ -1270,7 +1270,7 @@ class Message(Object, Update): parse_mode (``str``, *optional*): By default, texts are parsed using both Markdown and HTML styles. You can combine both syntaxes together. - Pass "markdown" to enable Markdown-style parsing only. + 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. @@ -1660,7 +1660,7 @@ class Message(Object, Update): parse_mode (``str``, *optional*): By default, texts are parsed using both Markdown and HTML styles. You can combine both syntaxes together. - Pass "markdown" to enable Markdown-style parsing only. + 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. @@ -2058,7 +2058,7 @@ class Message(Object, Update): parse_mode (``str``, *optional*): By default, texts are parsed using both Markdown and HTML styles. You can combine both syntaxes together. - Pass "markdown" to enable Markdown-style parsing only. + 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. @@ -2314,7 +2314,7 @@ class Message(Object, Update): parse_mode (``str``, *optional*): By default, texts are parsed using both Markdown and HTML styles. You can combine both syntaxes together. - Pass "markdown" to enable Markdown-style parsing only. + 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. @@ -2412,7 +2412,7 @@ class Message(Object, Update): parse_mode (``str``, *optional*): By default, texts are parsed using both Markdown and HTML styles. You can combine both syntaxes together. - Pass "markdown" to enable Markdown-style parsing only. + 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. @@ -2469,7 +2469,7 @@ class Message(Object, Update): parse_mode (``str``, *optional*): By default, texts are parsed using both Markdown and HTML styles. You can combine both syntaxes together. - Pass "markdown" to enable Markdown-style parsing only. + 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. From d1199982126e0955b07c6006c3faaab8c2c7dcc5 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 10 Jul 2019 20:20:22 +0200 Subject: [PATCH 135/202] Remove get_user_dc method, add dc_id attribute to User --- compiler/docs/compiler.py | 1 - pyrogram/client/methods/users/__init__.py | 2 - pyrogram/client/methods/users/get_user_dc.py | 58 -------------------- pyrogram/client/types/user_and_chats/user.py | 17 ++++-- 4 files changed, 12 insertions(+), 66 deletions(-) delete mode 100644 pyrogram/client/methods/users/get_user_dc.py diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index 6864f9de..36368bf9 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -214,7 +214,6 @@ def pyrogram_api(): set_profile_photo delete_profile_photos update_username - get_user_dc block_user unblock_user """, diff --git a/pyrogram/client/methods/users/__init__.py b/pyrogram/client/methods/users/__init__.py index f30245d7..775ccbb2 100644 --- a/pyrogram/client/methods/users/__init__.py +++ b/pyrogram/client/methods/users/__init__.py @@ -21,7 +21,6 @@ from .delete_profile_photos import DeleteProfilePhotos from .get_me import GetMe from .get_profile_photos import GetProfilePhotos from .get_profile_photos_count import GetProfilePhotosCount -from .get_user_dc import GetUserDC from .get_users import GetUsers from .iter_profile_photos import IterProfilePhotos from .set_profile_photo import SetProfilePhoto @@ -38,7 +37,6 @@ class Users( GetMe, UpdateUsername, GetProfilePhotosCount, - GetUserDC, IterProfilePhotos, UnblockUser ): diff --git a/pyrogram/client/methods/users/get_user_dc.py b/pyrogram/client/methods/users/get_user_dc.py deleted file mode 100644 index 75587884..00000000 --- a/pyrogram/client/methods/users/get_user_dc.py +++ /dev/null @@ -1,58 +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 . - -from typing import Union - -from pyrogram.api import functions, types -from ...ext import BaseClient - - -class GetUserDC(BaseClient): - def get_user_dc(self, user_id: Union[int, str]) -> Union[int, None]: - """Get the assigned DC (data center) of a user. - - .. note:: - - This information is approximate: it is based on where Telegram stores a user profile pictures and does not - by any means tell you the user location (i.e. a user might travel far away, but will still connect to its - assigned DC). More info at `FAQs <../faq#what-are-the-ip-addresses-of-telegram-data-centers>`_. - - Parameters: - user_id (``int`` | ``str``): - Unique identifier (int) or username (str) of the target chat. - For your personal cloud (Saved Messages) you can simply use "me" or "self". - For a contact that exists in your Telegram address book you can use his phone number (str). - - Returns: - ``int`` | ``None``: The DC identifier as integer, or None in case it wasn't possible to get it (i.e. the - user has no profile picture or has the privacy setting enabled). - - Raises: - RPCError: In case of a Telegram RPC error. - """ - - r = self.send(functions.users.GetUsers(id=[self.resolve_peer(user_id)])) - - if r: - r = r[0] - - if r.photo: - if isinstance(r.photo, types.UserProfilePhoto): - return r.photo.dc_id - - return None diff --git a/pyrogram/client/types/user_and_chats/user.py b/pyrogram/client/types/user_and_chats/user.py index 9cdd9760..75d8fa5f 100644 --- a/pyrogram/client/types/user_and_chats/user.py +++ b/pyrogram/client/types/user_and_chats/user.py @@ -75,6 +75,12 @@ class User(Object): language_code (``str``, *optional*): IETF language tag of the user's language. + dc_id (``int``, *optional*): + User's or bot's assigned DC (data center). Available only in case the user has set a public profile photo. + Note that this information is approximate: it is based on where Telegram stores a user profile pictures and + does not by any means tell you the user location (i.e. a user might travel far away, but will still connect + to its assigned DC). More info at `FAQs `_. + phone_number (``str``, *optional*): User's phone number. @@ -88,8 +94,8 @@ class User(Object): __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", "username", "language_code", "phone_number", - "photo", "restriction_reason" + "is_scam", "is_support", "first_name", "last_name", "status", "username", "language_code", "dc_id", + "phone_number", "photo", "restriction_reason" ] def __init__( @@ -111,6 +117,7 @@ class User(Object): status: UserStatus = None, username: str = None, language_code: str = None, + dc_id: int = None, phone_number: str = None, photo: ChatPhoto = None, restriction_reason: str = None @@ -132,6 +139,7 @@ class User(Object): self.status = status self.username = username self.language_code = language_code + self.dc_id = dc_id self.phone_number = phone_number self.photo = photo self.restriction_reason = restriction_reason @@ -163,6 +171,7 @@ class User(Object): status=UserStatus._parse(client, user.status, user.id, user.bot), username=user.username, language_code=user.lang_code, + dc_id=getattr(user.photo, "dc_id", None), phone_number=user.phone, photo=ChatPhoto._parse(client, user.photo, user.id), restriction_reason=user.restriction_reason, @@ -214,7 +223,7 @@ class User(Object): """ return self._client.unarchive_chats(self.id) - + def block(self): """Bound method *block* of :obj:`User`. @@ -238,7 +247,6 @@ class User(Object): return self._client.block_user(self.id) - def unblock(self): """Bound method *unblock* of :obj:`User`. @@ -261,4 +269,3 @@ class User(Object): """ return self._client.unblock_user(self.id) - From 52b0988af41297986746ffd1bc6eb46d7842f584 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 10 Jul 2019 22:04:57 +0200 Subject: [PATCH 136/202] Clarify default value of supports_streaming parameter (send_video) --- pyrogram/client/methods/messages/send_video.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyrogram/client/methods/messages/send_video.py b/pyrogram/client/methods/messages/send_video.py index 09005775..ba69aafb 100644 --- a/pyrogram/client/methods/messages/send_video.py +++ b/pyrogram/client/methods/messages/send_video.py @@ -89,6 +89,7 @@ class SendVideo(BaseClient): supports_streaming (``bool``, *optional*): Pass True, if the uploaded video is suitable for streaming. + Defaults to True. disable_notification (``bool``, *optional*): Sends the message silently. From 997a3c1626c6b9d79c959feab1a0fb6a0e19fc1a Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 11 Jul 2019 00:12:27 +0200 Subject: [PATCH 137/202] Remove UserStatus (user_status.py) --- compiler/docs/compiler.py | 1 - .../types/user_and_chats/user_status.py | 116 ------------------ 2 files changed, 117 deletions(-) delete mode 100644 pyrogram/client/types/user_and_chats/user_status.py diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index 36368bf9..d678b370 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -279,7 +279,6 @@ def pyrogram_api(): users_chats=""" Users & Chats User - UserStatus Chat ChatPreview ChatPhoto diff --git a/pyrogram/client/types/user_and_chats/user_status.py b/pyrogram/client/types/user_and_chats/user_status.py deleted file mode 100644 index 4d12afc1..00000000 --- a/pyrogram/client/types/user_and_chats/user_status.py +++ /dev/null @@ -1,116 +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 pyrogram - -from pyrogram.api import types -from ..object import Object -from ..update import Update - - -class UserStatus(Object, Update): - """A User status (Last Seen privacy). - - .. note:: - - You won't see exact last seen timestamps for people with whom you don't share your own. Instead, you get - "recently", "within_week", "within_month" or "long_time_ago" fields set. - - Parameters: - user_id (``int``): - User's id. - - online (``bool``, *optional*): - True if the user is online in this very moment, None otherwise. - If True, the "date" field will be also set containing the online expiration date (i.e.: the date when a - user will automatically go offline in case of no action by his client). - - offline (``bool``, *optional*): - True if the user is offline in this moment and has the Last Seen privacy setting public, None otherwise. - If True, the "date" field will be also set containing the last seen date (i.e.: the date when a user - was online the last time). - - date (``int``, *optional*): - Exact date in unix time. Available only in case "online" or "offline" equals to True. - - recently (``bool``, *optional*): - True for users with hidden Last Seen privacy that have been online between 1 second and 2-3 days ago, - None otherwise. - - within_week (``bool``, *optional*): - True for users with hidden Last Seen privacy that have been online between 2-3 and seven days ago, - None otherwise. - - within_month (``bool``, *optional*): - True for users with hidden Last Seen privacy that have been online between 6-7 days and a month ago, - None otherwise. - - long_time_ago (``bool``, *optional*): - True for users with hidden Last Seen privacy that have been online more than a month ago (this is also - always shown to blocked users), None otherwise. - """ - - __slots__ = ["user_id", "online", "offline", "date", "recently", "within_week", "within_month", "long_time_ago"] - - def __init__( - self, - *, - client: "pyrogram.BaseClient" = None, - user_id: int, - online: bool = None, - offline: bool = None, - date: int = None, - recently: bool = None, - within_week: bool = None, - within_month: bool = None, - long_time_ago: bool = None - ): - super().__init__(client) - - self.user_id = user_id - self.online = online - self.offline = offline - self.date = date - self.recently = recently - self.within_week = within_week - self.within_month = within_month - self.long_time_ago = long_time_ago - - @staticmethod - def _parse(client, user_status, user_id: int, is_bot: bool = False): - if is_bot: - return None - - status = UserStatus(user_id=user_id, client=client) - - if isinstance(user_status, types.UserStatusOnline): - status.online = True - status.date = user_status.expires - elif isinstance(user_status, types.UserStatusOffline): - status.offline = True - status.date = user_status.was_online - elif isinstance(user_status, types.UserStatusRecently): - status.recently = True - elif isinstance(user_status, types.UserStatusLastWeek): - status.within_week = True - elif isinstance(user_status, types.UserStatusLastMonth): - status.within_month = True - else: - status.long_time_ago = True - - return status From f9ea45f98726e883ccc02f722fcd6dff44951227 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 11 Jul 2019 01:32:18 +0200 Subject: [PATCH 138/202] Fix restrict_chat and restrict_chat_member combined permissions --- pyrogram/client/methods/chats/restrict_chat.py | 8 +++----- pyrogram/client/methods/chats/restrict_chat_member.py | 8 +++----- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/pyrogram/client/methods/chats/restrict_chat.py b/pyrogram/client/methods/chats/restrict_chat.py index 8e63a9b2..dc0f96a1 100644 --- a/pyrogram/client/methods/chats/restrict_chat.py +++ b/pyrogram/client/methods/chats/restrict_chat.py @@ -52,13 +52,13 @@ class RestrictChat(BaseClient): can_send_other_messages (``bool``, *optional*): Pass True, if the user can send animations, games, stickers and use inline bots, - implies can_send_media_messages. + 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_media_messages. + 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_media_messages. + 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. @@ -96,7 +96,6 @@ class RestrictChat(BaseClient): if can_send_other_messages: send_messages = None - send_media = None send_stickers = None send_gifs = None send_games = None @@ -104,7 +103,6 @@ class RestrictChat(BaseClient): if can_add_web_page_previews: send_messages = None - send_media = None embed_links = None if can_send_polls: diff --git a/pyrogram/client/methods/chats/restrict_chat_member.py b/pyrogram/client/methods/chats/restrict_chat_member.py index 96e07d18..30574022 100644 --- a/pyrogram/client/methods/chats/restrict_chat_member.py +++ b/pyrogram/client/methods/chats/restrict_chat_member.py @@ -65,13 +65,13 @@ class RestrictChatMember(BaseClient): can_send_other_messages (``bool``, *optional*): Pass True, if the user can send animations, games, stickers and use inline bots, - implies can_send_media_messages. + 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_media_messages. + 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_media_messages. + 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. @@ -109,7 +109,6 @@ class RestrictChatMember(BaseClient): if can_send_other_messages: send_messages = None - send_media = None send_stickers = None send_gifs = None send_games = None @@ -117,7 +116,6 @@ class RestrictChatMember(BaseClient): if can_add_web_page_previews: send_messages = None - send_media = None embed_links = None if can_send_polls: From 2f07e7abc400144c47f1e17d09a45df24730f71d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 11 Jul 2019 01:35:02 +0200 Subject: [PATCH 139/202] Rework user.status, add last_online_date and next_offline_date --- pyrogram/client/ext/dispatcher.py | 4 +- .../client/handlers/user_status_handler.py | 14 +-- .../client/types/user_and_chats/__init__.py | 3 +- pyrogram/client/types/user_and_chats/user.py | 119 +++++++++++++----- 4 files changed, 97 insertions(+), 43 deletions(-) diff --git a/pyrogram/client/ext/dispatcher.py b/pyrogram/client/ext/dispatcher.py index c931c8f7..55a31452 100644 --- a/pyrogram/client/ext/dispatcher.py +++ b/pyrogram/client/ext/dispatcher.py @@ -77,9 +77,7 @@ class Dispatcher: lambda upd, usr, cht: (pyrogram.CallbackQuery._parse(self.client, upd, usr), CallbackQueryHandler), (types.UpdateUserStatus,): - lambda upd, usr, cht: ( - pyrogram.UserStatus._parse(self.client, upd.status, upd.user_id), UserStatusHandler - ), + lambda upd, usr, cht: (pyrogram.User._parse_user_status(self.client, upd), UserStatusHandler), (types.UpdateBotInlineQuery,): lambda upd, usr, cht: (pyrogram.InlineQuery._parse(self.client, upd, usr), InlineQueryHandler), diff --git a/pyrogram/client/handlers/user_status_handler.py b/pyrogram/client/handlers/user_status_handler.py index 9b39aab6..1f84d77f 100644 --- a/pyrogram/client/handlers/user_status_handler.py +++ b/pyrogram/client/handlers/user_status_handler.py @@ -21,26 +21,24 @@ from .handler import Handler class UserStatusHandler(Handler): """The UserStatus handler class. Used to handle user status updates (user going online or offline). - It is intended to be used with :meth:`~Client.add_handler` + It is intended to be used with :meth:`~Client.add_handler`. - For a nicer way to register this handler, have a look at the - :meth:`~Client.on_user_status` decorator. + For a nicer way to register this handler, have a look at the :meth:`~Client.on_user_status` decorator. Parameters: callback (``callable``): - Pass a function that will be called when a new UserStatus update arrives. It takes *(client, user_status)* + Pass a function that will be called when a new user status update arrives. It takes *(client, user)* as positional arguments (look at the section below for a detailed description). filters (:obj:`Filters`): - Pass one or more filters to allow only a subset of messages to be passed - in your callback function. + Pass one or more filters to allow only a subset of users to be passed in your callback function. Other parameters: client (:obj:`Client`): The Client itself, useful when you want to call other API methods inside the user status handler. - user_status (:obj:`UserStatus`): - The received UserStatus update. + user (:obj:`User`): + The user containing the updated status. """ def __init__(self, callback: callable, filters=None): diff --git a/pyrogram/client/types/user_and_chats/__init__.py b/pyrogram/client/types/user_and_chats/__init__.py index 922ac86a..29c45e09 100644 --- a/pyrogram/client/types/user_and_chats/__init__.py +++ b/pyrogram/client/types/user_and_chats/__init__.py @@ -23,8 +23,7 @@ from .chat_photo import ChatPhoto from .chat_preview import ChatPreview from .dialog import Dialog from .user import User -from .user_status import UserStatus __all__ = [ - "Chat", "ChatMember", "ChatPermissions", "ChatPhoto", "ChatPreview", "Dialog", "User", "UserStatus" + "Chat", "ChatMember", "ChatPermissions", "ChatPhoto", "ChatPreview", "Dialog", "User" ] diff --git a/pyrogram/client/types/user_and_chats/user.py b/pyrogram/client/types/user_and_chats/user.py index 75d8fa5f..43baca25 100644 --- a/pyrogram/client/types/user_and_chats/user.py +++ b/pyrogram/client/types/user_and_chats/user.py @@ -21,54 +21,68 @@ import html import pyrogram from pyrogram.api import types from .chat_photo import ChatPhoto -from .user_status import UserStatus from ..object import Object +from ..update import Update -class User(Object): +class User(Object, Update): """A Telegram user or bot. Parameters: id (``int``): Unique identifier for this user or bot. - is_self(``bool``): + is_self(``bool``, *optional*): True, if this user is you yourself. - is_contact(``bool``): + is_contact(``bool``, *optional*): True, if this user is in your contacts. - is_mutual_contact(``bool``): + is_mutual_contact(``bool``, *optional*): True, if you both have each other's contact. - is_deleted(``bool``): + is_deleted(``bool``, *optional*): True, if this user is deleted. - is_bot (``bool``): + is_bot (``bool``, *optional*): True, if this user is a bot. - is_verified (``bool``): + is_verified (``bool``, *optional*): True, if this user has been verified by Telegram. - is_restricted (``bool``): + is_restricted (``bool``, *optional*): True, if this user has been restricted. Bots only. See *restriction_reason* for details. - is_scam (``bool``): + is_scam (``bool``, *optional*): True, if this user has been flagged for scam. - is_support (``bool``): + is_support (``bool``, *optional*): True, if this user is part of the Telegram support team. - first_name (``str``): + first_name (``str``, *optional*): User's or bot's first name. - status (:obj:`UserStatus `, *optional*): - User's Last Seen status. Empty for bots. - last_name (``str``, *optional*): User's or bot's last name. + status (``str``, *optional*): + User's Last Seen & Online status. + Can be one of the following: + "*online*", user is online right now. + "*offline*", user is currently offline. + "*recently*", user with hidden last seen time who was online between 1 second and 2-3 days ago. + "*within_week*", user with hidden last seen time who was online between 2-3 and seven days ago. + "*within_month*", user with hidden last seen time who was online between 6-7 days and a month ago. + "*long_time_ago*", blocked user or user with hidden last seen time who was online more than a month ago. + *None*, for bots. + + last_online_date (``int``, *optional*): + Last online date of a user. Only available in case status is "*offline*". + + next_offline_date (``int``, *optional*): + Date when a user will automatically go offline. Only available in case status is "*online*". + username (``str``, *optional*): User's or bot's username. @@ -77,7 +91,7 @@ class User(Object): dc_id (``int``, *optional*): User's or bot's assigned DC (data center). Available only in case the user has set a public profile photo. - Note that this information is approximate: it is based on where Telegram stores a user profile pictures and + Note that this information is approximate; it is based on where Telegram stores a user profile pictures and does not by any means tell you the user location (i.e. a user might travel far away, but will still connect to its assigned DC). More info at `FAQs `_. @@ -94,8 +108,8 @@ class User(Object): __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", "username", "language_code", "dc_id", - "phone_number", "photo", "restriction_reason" + "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__( @@ -103,18 +117,20 @@ class User(Object): *, client: "pyrogram.BaseClient" = None, id: int, - is_self: bool, - is_contact: bool, - is_mutual_contact: bool, - is_deleted: bool, - is_bot: bool, - is_verified: bool, - is_restricted: bool, - is_scam: bool, - is_support: bool, - first_name: str, + is_self: bool = None, + is_contact: bool = None, + is_mutual_contact: bool = None, + is_deleted: bool = None, + is_bot: bool = None, + is_verified: bool = None, + is_restricted: bool = None, + is_scam: bool = None, + is_support: bool = None, + first_name: str = None, last_name: str = None, - status: UserStatus = None, + status: str = None, + last_online_date: int = None, + next_offline_date: int = None, username: str = None, language_code: str = None, dc_id: int = None, @@ -137,6 +153,8 @@ class User(Object): self.first_name = first_name self.last_name = last_name self.status = status + self.last_online_date = last_online_date + self.next_offline_date = next_offline_date self.username = username self.language_code = language_code self.dc_id = dc_id @@ -168,7 +186,7 @@ class User(Object): is_support=user.support, first_name=user.first_name, last_name=user.last_name, - status=UserStatus._parse(client, user.status, user.id, user.bot), + **User._parse_status(user.status, user.bot), username=user.username, language_code=user.lang_code, dc_id=getattr(user.photo, "dc_id", None), @@ -178,6 +196,47 @@ class User(Object): client=client ) + @staticmethod + def _parse_status(user_status: types.UpdateUserStatus, is_bot: bool = False): + if isinstance(user_status, types.UserStatusOnline): + status, date = "online", user_status.expires + elif isinstance(user_status, types.UserStatusOffline): + status, date = "offline", user_status.was_online + elif isinstance(user_status, types.UserStatusRecently): + status, date = "recently", None + elif isinstance(user_status, types.UserStatusLastWeek): + status, date = "within_week", None + elif isinstance(user_status, types.UserStatusLastMonth): + status, date = "within_month", None + else: + status, date = "long_time_ago", None + + last_online_date = None + next_offline_date = None + + if is_bot: + status = None + + if status == "online": + next_offline_date = date + + if status == "offline": + last_online_date = date + + return { + "status": status, + "last_online_date": last_online_date, + "next_offline_date": next_offline_date + } + + @staticmethod + def _parse_user_status(client, user_status: types.UpdateUserStatus): + return User( + id=user_status.user_id, + **User._parse_status(user_status.status), + client=client + ) + def archive(self): """Bound method *archive* of :obj:`User`. From 7c704bbb6ac11553b556f6dc01f20d4b0b4e2d37 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 11 Jul 2019 04:14:14 +0200 Subject: [PATCH 140/202] Make the text parser log warnings instead of raising exceptions --- pyrogram/client/parser/html.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/parser/html.py b/pyrogram/client/parser/html.py index 04f4ad30..9aff757f 100644 --- a/pyrogram/client/parser/html.py +++ b/pyrogram/client/parser/html.py @@ -17,6 +17,7 @@ # along with Pyrogram. If not, see . import html +import logging import re from collections import OrderedDict from html.parser import HTMLParser @@ -27,6 +28,8 @@ from pyrogram.api import types from pyrogram.errors import PeerIdInvalid from . import utils +log = logging.getLogger(__name__) + class Parser(HTMLParser): MENTION_RE = re.compile(r"tg://user\?id=(\d+)") @@ -94,7 +97,7 @@ class Parser(HTMLParser): line, offset = self.getpos() offset += 1 - raise ValueError("Unmatched closing tag at line {}:{}".format(tag, line, offset)) + log.warning("Unmatched closing tag at line {}:{}".format(tag, line, offset)) else: if not self.tag_entities[tag]: self.tag_entities.pop(tag) @@ -120,7 +123,7 @@ class HTML: for tag, entities in parser.tag_entities.items(): unclosed_tags.append("<{}> (x{})".format(tag, len(entities))) - raise ValueError("Unclosed tags: {}".format(", ".join(unclosed_tags))) + log.warning("Unclosed tags: {}".format(", ".join(unclosed_tags))) entities = [] From f7ba7bfd3acd07203558b79fc9ed58e9f93523f4 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 11 Jul 2019 04:21:02 +0200 Subject: [PATCH 141/202] Update dev version --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index edfc756d..bc4a0545 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -24,7 +24,7 @@ if sys.version_info[:3] in [(3, 5, 0), (3, 5, 1), (3, 5, 2)]: # Monkey patch the standard "typing" module because Python versions from 3.5.0 to 3.5.2 have a broken one. sys.modules["typing"] = typing -__version__ = "0.15.1" +__version__ = "0.16.0.dev" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2019 Dan " From efe564064bd5cdff5de09e55eb0746e94c03ebd3 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 11 Jul 2019 14:29:41 +0200 Subject: [PATCH 142/202] Update chat.py --- pyrogram/client/types/user_and_chats/chat.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py index 304eb8d2..ee8aacb8 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -674,9 +674,6 @@ class Chat(Object): can_promote_members=can_promote_members ) - - - def join(self): """Bound method *join* of :obj:`Chat`. @@ -700,7 +697,6 @@ class Chat(Object): return self._client.join_chat(self.id) - def leave(self): """Bound method *leave* of :obj:`Chat`. From 5599182fd192e3c29494c3778dc3f95c9f019548 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 11 Jul 2019 14:42:18 +0200 Subject: [PATCH 143/202] Fix Chat.join The bound method will only be able to make users join public chats that have set a username. --- pyrogram/client/types/user_and_chats/chat.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py index ee8aacb8..cac5d0c7 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -386,7 +386,6 @@ class Chat(Object): description=description ) - def set_photo(self, photo: str) -> bool: """Bound method *set_photo* of :obj:`Chat`. @@ -688,6 +687,9 @@ class Chat(Object): chat.join() + Note: + This only works for public groups and channels that have set a username. + Returns: :obj:`Chat`: On success, a chat object is returned. @@ -695,7 +697,7 @@ class Chat(Object): RPCError: In case of a Telegram RPC error. """ - return self._client.join_chat(self.id) + return self._client.join_chat(self.username) def leave(self): """Bound method *leave* of :obj:`Chat`. @@ -716,4 +718,3 @@ class Chat(Object): """ return self._client.leave_chat(self.id) - From 8b4c326365182498fd2963d825b29d93006c1512 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 11 Jul 2019 17:13:20 +0200 Subject: [PATCH 144/202] Add missing bound methods to docs --- compiler/docs/compiler.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index d678b370..d261623d 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -412,11 +412,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 From 5fe8fba3dfa6bb84c81614c0b9271d79f80d71c3 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 11 Jul 2019 17:13:43 +0200 Subject: [PATCH 145/202] Fix smart plugins load/unload documentation --- docs/source/topics/smart-plugins.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) 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 From e1197e066eadb4673bd70866eef2b22e311fb56c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 11 Jul 2019 17:14:38 +0200 Subject: [PATCH 146/202] Use a better name for the special plugin attribute when decorating funcs --- pyrogram/client/client.py | 6 +++--- pyrogram/client/methods/decorators/on_callback_query.py | 2 +- pyrogram/client/methods/decorators/on_deleted_messages.py | 2 +- pyrogram/client/methods/decorators/on_inline_query.py | 2 +- pyrogram/client/methods/decorators/on_message.py | 2 +- pyrogram/client/methods/decorators/on_poll.py | 2 +- pyrogram/client/methods/decorators/on_raw_update.py | 2 +- pyrogram/client/methods/decorators/on_user_status.py | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 9c19e49a..c660ef21 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -1108,7 +1108,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) @@ -1143,7 +1143,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) @@ -1181,7 +1181,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) 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 ) From fed8cbf87e8f5921fad437c603f569b7c83d6012 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 11 Jul 2019 19:28:33 +0200 Subject: [PATCH 147/202] Add new set_parse_mode utility method --- compiler/docs/compiler.py | 1 + pyrogram/client/client.py | 78 +++++++++++++++++++ pyrogram/client/ext/base_client.py | 3 + .../methods/messages/edit_inline_text.py | 2 +- .../methods/messages/edit_message_text.py | 2 +- .../client/methods/messages/send_animation.py | 2 +- .../client/methods/messages/send_audio.py | 2 +- .../methods/messages/send_cached_media.py | 2 +- .../client/methods/messages/send_document.py | 2 +- .../client/methods/messages/send_message.py | 2 +- .../client/methods/messages/send_photo.py | 2 +- .../client/methods/messages/send_video.py | 2 +- .../client/methods/messages/send_voice.py | 2 +- pyrogram/client/parser/parser.py | 14 +++- .../input_media/input_media_animation.py | 2 +- .../types/input_media/input_media_audio.py | 2 +- .../types/input_media/input_media_document.py | 2 +- .../types/input_media/input_media_photo.py | 2 +- .../types/input_media/input_media_video.py | 2 +- .../input_text_message_content.py | 2 +- 20 files changed, 109 insertions(+), 19 deletions(-) diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index d261623d..c4a0f8a8 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 diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index c660ef21..81eef671 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -491,6 +491,84 @@ class Client(Methods, BaseClient): """ 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 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( diff --git a/pyrogram/client/ext/base_client.py b/pyrogram/client/ext/base_client.py index b5be089b..bb024250 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", @@ -93,6 +95,7 @@ class BaseClient: self.rnd_id = MsgId self.parser = Parser(self) + self.parse_mode = "combined" self.session = None self.media_sessions = {} diff --git a/pyrogram/client/methods/messages/edit_inline_text.py b/pyrogram/client/methods/messages/edit_inline_text.py index 0d17b4a4..9b0b34d3 100644 --- a/pyrogram/client/methods/messages/edit_inline_text.py +++ b/pyrogram/client/methods/messages/edit_inline_text.py @@ -28,7 +28,7 @@ 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: diff --git a/pyrogram/client/methods/messages/edit_message_text.py b/pyrogram/client/methods/messages/edit_message_text.py index c81139af..063c8c72 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": diff --git a/pyrogram/client/methods/messages/send_animation.py b/pyrogram/client/methods/messages/send_animation.py index 34389149..a68984fc 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, diff --git a/pyrogram/client/methods/messages/send_audio.py b/pyrogram/client/methods/messages/send_audio.py index 43c5a63e..c8ce7368 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, diff --git a/pyrogram/client/methods/messages/send_cached_media.py b/pyrogram/client/methods/messages/send_cached_media.py index 0f2e1389..59a24171 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[ diff --git a/pyrogram/client/methods/messages/send_document.py b/pyrogram/client/methods/messages/send_document.py index fcaf5f51..a3cec395 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[ diff --git a/pyrogram/client/methods/messages/send_message.py b/pyrogram/client/methods/messages/send_message.py index 15b5f25c..f652f3d9 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, diff --git a/pyrogram/client/methods/messages/send_photo.py b/pyrogram/client/methods/messages/send_photo.py index c43c0139..981b0045 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, diff --git a/pyrogram/client/methods/messages/send_video.py b/pyrogram/client/methods/messages/send_video.py index ba69aafb..602e3b01 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, diff --git a/pyrogram/client/methods/messages/send_voice.py b/pyrogram/client/methods/messages/send_voice.py index 854385d8..9c0b8514 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, diff --git a/pyrogram/client/parser/parser.py b/pyrogram/client/parser/parser.py index 8fde46bd..e6681b32 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,12 +26,16 @@ 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 = ""): + def parse(self, text: str, mode: Union[str, None] = object): text = str(text or "").strip() + if mode == object: + mode = self.client.parse_mode + if mode is None: return OrderedDict([ ("message", text), @@ -41,7 +44,7 @@ class Parser: mode = mode.lower() - if mode == "": + if mode == "combined": return self.markdown.parse(text) if mode in ["markdown", "md"]: @@ -50,6 +53,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 self.client.PARSE_MODES[:-1]), + mode + )) + @staticmethod def unparse(text: str, entities: list, is_html: bool): if is_html: diff --git a/pyrogram/client/types/input_media/input_media_animation.py b/pyrogram/client/types/input_media/input_media_animation.py index e157993b..d6c67d56 100644 --- a/pyrogram/client/types/input_media/input_media_animation.py +++ b/pyrogram/client/types/input_media/input_media_animation.py @@ -63,7 +63,7 @@ class InputMediaAnimation(InputMedia): 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..f01444a8 100644 --- a/pyrogram/client/types/input_media/input_media_audio.py +++ b/pyrogram/client/types/input_media/input_media_audio.py @@ -65,7 +65,7 @@ class InputMediaAudio(InputMedia): 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..af549c81 100644 --- a/pyrogram/client/types/input_media/input_media_document.py +++ b/pyrogram/client/types/input_media/input_media_document.py @@ -54,7 +54,7 @@ class InputMediaDocument(InputMedia): 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..30c53777 100644 --- a/pyrogram/client/types/input_media/input_media_photo.py +++ b/pyrogram/client/types/input_media/input_media_photo.py @@ -49,6 +49,6 @@ class InputMediaPhoto(InputMedia): 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..3500ff55 100644 --- a/pyrogram/client/types/input_media/input_media_video.py +++ b/pyrogram/client/types/input_media/input_media_video.py @@ -68,7 +68,7 @@ class InputMediaVideo(InputMedia): 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_message_content/input_text_message_content.py b/pyrogram/client/types/input_message_content/input_text_message_content.py index f4b9aefc..f90b7096 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 @@ -43,7 +43,7 @@ class InputTextMessageContent(InputMessageContent): __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 From 385ab22b68169fdfe43ce33126d3a8d839ea0f5e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 11 Jul 2019 19:59:56 +0200 Subject: [PATCH 148/202] Rework Client.idle() idle() is now static and doesn't stop the client anymore --- pyrogram/client/client.py | 53 +++++++++++++++++++++--------- pyrogram/client/ext/base_client.py | 3 +- 2 files changed, 40 insertions(+), 16 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 81eef671..08ea3b39 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -394,37 +394,60 @@ class Client(Methods, BaseClient): self.stop() self.start() - def idle(self, stop_signals: tuple = (SIGINT, SIGTERM, SIGABRT)): + @staticmethod + def idle(stop_signals: tuple = (SIGINT, SIGTERM, SIGABRT)): """Block the main script execution until a signal is received. - Once the signal is received (e.g.: from CTRL+C), the client will automatically stop and the main script will - continue its execution. + 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. - 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. + 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, 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. + 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() diff --git a/pyrogram/client/ext/base_client.py b/pyrogram/client/ext/base_client.py index bb024250..ce736e87 100644 --- a/pyrogram/client/ext/base_client.py +++ b/pyrogram/client/ext/base_client.py @@ -89,6 +89,8 @@ class BaseClient: mime_types_to_extensions[mime_type] = " ".join(extensions) + is_idling = False + def __init__(self): self.storage = None @@ -102,7 +104,6 @@ class BaseClient: self.media_sessions_lock = Lock() self.is_started = None - self.is_idle = None self.takeout_id = None From 2095f9fb5e3cf4dc39b7e5f2f51e9313a1526adb Mon Sep 17 00:00:00 2001 From: kalmengr <46006289+kalmengr@users.noreply.github.com> Date: Fri, 12 Jul 2019 12:05:19 -0400 Subject: [PATCH 149/202] Add bound method vote to Message --- .../types/messages_and_media/message.py | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 144f04a4..68678210 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -2923,6 +2923,43 @@ class Message(Object, Update): progress=progress, progress_args=progress_args, ) + def vote( + self, + option: int, + ) -> "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: + 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`. From ba4748814249dc003341f64b286d56449087a2b9 Mon Sep 17 00:00:00 2001 From: kalmengr <46006289+kalmengr@users.noreply.github.com> Date: Fri, 12 Jul 2019 12:13:14 -0400 Subject: [PATCH 150/202] Update message.py --- pyrogram/client/types/messages_and_media/message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 68678210..8b02839f 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -2957,7 +2957,7 @@ class Message(Object, Update): return self._client.vote_poll( chat_id=self.chat.id, - message_id= self.message_id, + message_id=self.message_id, option=option ) From 449f318e6d06dd1aeed5cd18d1533b6ef3da1aec Mon Sep 17 00:00:00 2001 From: kalmengr <46006289+kalmengr@users.noreply.github.com> Date: Fri, 12 Jul 2019 18:29:35 -0400 Subject: [PATCH 151/202] Add retract_vote bound method to Message --- .../types/messages_and_media/message.py | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 144f04a4..80597e33 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -2868,6 +2868,38 @@ class Message(Object, Update): raise ValueError("This button is not supported yet") else: self.reply(button, quote=quote) + + + def retract_vote( + self, + ) -> "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, From bb1470e57f90a114f0334588055c772debfde48d Mon Sep 17 00:00:00 2001 From: MrNaif2018 Date: Mon, 15 Jul 2019 15:37:18 +0300 Subject: [PATCH 152/202] Add section to docs about scheduling --- docs/source/index.rst | 1 + docs/source/topics/scheduling.rst | 73 +++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 docs/source/topics/scheduling.rst diff --git a/docs/source/index.rst b/docs/source/index.rst index 5cb9bb2e..b8927657 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -40,6 +40,7 @@ Welcome to Pyrogram topics/more-on-updates topics/config-file topics/smart-plugins + topics/scheduling topics/auto-auth topics/session-settings topics/tgcrypto diff --git a/docs/source/topics/scheduling.rst b/docs/source/topics/scheduling.rst new file mode 100644 index 00000000..70f70215 --- /dev/null +++ b/docs/source/topics/scheduling.rst @@ -0,0 +1,73 @@ +Scheduling tasks +================ + +Pyrogram itself as Telegram MTProto API Framework contains only stuff +related to Telegram. Scheduling is out of it's scope. + +But it is easy to integrate pyrogram with your favourite scheduler. + +schedule +-------- + +Note that schedule is not suitable for async version of pyrogram. + +.. code-block:: python + + import time + import schedule + + + def job(): + app.send_message("me", "Hi!") + + + schedule.every(10).minutes.do(job) + schedule.every().hour.do(job) + schedule.every().day.at("10:30").do(job) + schedule.every(5).to(10).minutes.do(job) + schedule.every().monday.do(job) + schedule.every().wednesday.at("13:15").do(job) + schedule.every().minute.at(":17").do(job) + + with app: + while True: + schedule.run_pending() + time.sleep(1) + + +apscheduler +----------- + +.. code-block:: python + + import time + from apscheduler.schedulers.background import BackgroundScheduler + + + def job(): + app.send_message("me", "Hi!") + + + scheduler = BackgroundScheduler() + scheduler.add_job(job, 'interval', seconds=3) + + scheduler.start() + app.run() + +Apscheduler supports async version of pyrogram too, here is async example: + +.. code-block:: python + + from apscheduler.schedulers.asyncio import AsyncIOScheduler + + + async def job(): + await app.send_message("me", "Hi!") + + + scheduler = AsyncIOScheduler() + scheduler.add_job(job, 'interval', seconds=3) + + scheduler.start() + app.run() + From 85c21308757d21fbb55b610ab7f59b8a8ba1f12a Mon Sep 17 00:00:00 2001 From: MrNaif2018 Date: Mon, 15 Jul 2019 16:56:59 +0300 Subject: [PATCH 153/202] Update .gitignore with vscode and alternate docs build dir --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) 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/ From f2b3db47a9e4de2d0ccf52b3887fc65975360340 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 16 Jul 2019 07:41:11 +0200 Subject: [PATCH 154/202] Add "bot" chat type into Filters.private. Bots are still 1-to-1 private chats --- pyrogram/client/filters/filters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/filters/filters.py b/pyrogram/client/filters/filters.py index d8768b3b..01d6bbd4 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") From 62a39521d92b6e33132a58ca3e3be228d812d27d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 19 Jul 2019 13:40:12 +0200 Subject: [PATCH 155/202] Allow send_media_group send media from URLs --- .../methods/messages/send_media_group.py | 39 +++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/pyrogram/client/methods/messages/send_media_group.py b/pyrogram/client/methods/messages/send_media_group.py index 681e1850..1f0aa3dd 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], @@ -88,7 +87,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 +142,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: From 6459ce0a07a6105b435ba9642d099d7d9cb5ad06 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 21 Jul 2019 01:03:19 +0200 Subject: [PATCH 156/202] Fix lots of bound methods breaking due to latest changes on parse_mode Addresses #287 --- .../methods/messages/edit_inline_caption.py | 4 +++- .../methods/messages/edit_message_caption.py | 2 +- pyrogram/client/parser/parser.py | 7 +++++-- .../bots_and_keyboards/callback_query.py | 4 ++-- .../types/messages_and_media/message.py | 20 +++++++++---------- 5 files changed, 21 insertions(+), 16 deletions(-) diff --git a/pyrogram/client/methods/messages/edit_inline_caption.py b/pyrogram/client/methods/messages/edit_inline_caption.py index 2d904198..298e3ef4 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,7 +27,7 @@ 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. diff --git a/pyrogram/client/methods/messages/edit_message_caption.py b/pyrogram/client/methods/messages/edit_message_caption.py index 6fefe0b2..c760c675 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. diff --git a/pyrogram/client/parser/parser.py b/pyrogram/client/parser/parser.py index e6681b32..edf9553d 100644 --- a/pyrogram/client/parser/parser.py +++ b/pyrogram/client/parser/parser.py @@ -34,7 +34,10 @@ class Parser: text = str(text or "").strip() if mode == object: - mode = self.client.parse_mode + if self.client: + mode = self.client.parse_mode + else: + mode = "combined" if mode is None: return OrderedDict([ @@ -54,7 +57,7 @@ class Parser: return self.html.parse(text) raise ValueError('parse_mode must be one of {} or None. Not "{}"'.format( - ", ".join('"{}"'.format(m) for m in self.client.PARSE_MODES[:-1]), + ", ".join('"{}"'.format(m) for m in pyrogram.Client.PARSE_MODES[:-1]), mode )) diff --git a/pyrogram/client/types/bots_and_keyboards/callback_query.py b/pyrogram/client/types/bots_and_keyboards/callback_query.py index b09e5440..d58865b2 100644 --- a/pyrogram/client/types/bots_and_keyboards/callback_query.py +++ b/pyrogram/client/types/bots_and_keyboards/callback_query.py @@ -176,7 +176,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 +229,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/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 144f04a4..2f1d5928 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -654,7 +654,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 +736,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, @@ -873,7 +873,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, @@ -1010,7 +1010,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 +1218,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[ @@ -1613,7 +1613,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, @@ -2007,7 +2007,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, @@ -2267,7 +2267,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, @@ -2384,7 +2384,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 +2442,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`. From 184f851625d0526c94d55cc6c47184faf601895b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 21 Jul 2019 02:22:46 +0200 Subject: [PATCH 157/202] Fix idle() and run() breaking after latest changes --- pyrogram/client/client.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 08ea3b39..19ff1b5e 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -450,8 +450,6 @@ class Client(Methods, BaseClient): while Client.is_idling: time.sleep(1) - self.stop() - def run(self): """Start the Client and automatically idle the main script. @@ -464,6 +462,7 @@ class Client(Methods, BaseClient): """ self.start() self.idle() + self.stop() def add_handler(self, handler: Handler, group: int = 0): """Register an update handler. From 036a73997a46c4e0151b6eff304ead5c441b386b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 21 Jul 2019 23:08:30 +0200 Subject: [PATCH 158/202] Add new methods: add_chat_members, create_* and delete_* chats - add_chat_members - create_group - create_channel - create_supergroup - delete_channel - delete_supergroup --- compiler/docs/compiler.py | 6 ++ pyrogram/client/methods/chats/__init__.py | 14 +++- .../client/methods/chats/add_chat_members.py | 76 +++++++++++++++++++ .../client/methods/chats/create_channel.py | 50 ++++++++++++ pyrogram/client/methods/chats/create_group.py | 60 +++++++++++++++ .../client/methods/chats/create_supergroup.py | 54 +++++++++++++ .../client/methods/chats/delete_channel.py | 43 +++++++++++ .../client/methods/chats/delete_supergroup.py | 43 +++++++++++ 8 files changed, 345 insertions(+), 1 deletion(-) create mode 100644 pyrogram/client/methods/chats/add_chat_members.py create mode 100644 pyrogram/client/methods/chats/create_channel.py create mode 100644 pyrogram/client/methods/chats/create_group.py create mode 100644 pyrogram/client/methods/chats/create_supergroup.py create mode 100644 pyrogram/client/methods/chats/delete_channel.py create mode 100644 pyrogram/client/methods/chats/delete_supergroup.py diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index c4a0f8a8..98c3c591 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -204,6 +204,12 @@ def pyrogram_api(): update_chat_username archive_chats unarchive_chats + add_chat_members + create_channel + create_group + create_supergroup + delete_channel + delete_supergroup """, users=""" Users diff --git a/pyrogram/client/methods/chats/__init__.py b/pyrogram/client/methods/chats/__init__.py index 969628ee..a7fc2792 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 @@ -68,6 +74,12 @@ class Chats( RestrictChat, 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..ce5b0cce --- /dev/null +++ b/pyrogram/client/methods/chats/add_chat_members.py @@ -0,0 +1,76 @@ +# 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. + """ + 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/create_channel.py b/pyrogram/client/methods/chats/create_channel.py new file mode 100644 index 00000000..c9b804f1 --- /dev/null +++ b/pyrogram/client/methods/chats/create_channel.py @@ -0,0 +1,50 @@ +# 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. + """ + 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..cbf71bb3 --- /dev/null +++ b/pyrogram/client/methods/chats/create_group.py @@ -0,0 +1,60 @@ +# 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. + """ + 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..163eae93 --- /dev/null +++ b/pyrogram/client/methods/chats/create_supergroup.py @@ -0,0 +1,54 @@ +# 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. + """ + 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/client/methods/chats/delete_channel.py b/pyrogram/client/methods/chats/delete_channel.py new file mode 100644 index 00000000..47a29b76 --- /dev/null +++ b/pyrogram/client/methods/chats/delete_channel.py @@ -0,0 +1,43 @@ +# 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 DeleteChannel(BaseClient): + def delete_channel(self, chat_id: Union[int, str]) -> bool: + """Delete a channel. + + Parameters: + chat_id (``int`` | ``str``): + The id of the channel to be deleted. + + Returns: + ``bool``: On success, True is returned. + """ + self.send( + functions.channels.DeleteChannel( + channel=self.resolve_peer(chat_id) + ) + ) + + return True diff --git a/pyrogram/client/methods/chats/delete_supergroup.py b/pyrogram/client/methods/chats/delete_supergroup.py new file mode 100644 index 00000000..f4ec5e2f --- /dev/null +++ b/pyrogram/client/methods/chats/delete_supergroup.py @@ -0,0 +1,43 @@ +# 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. + """ + self.send( + functions.channels.DeleteChannel( + channel=self.resolve_peer(chat_id) + ) + ) + + return True From 090675434e640cfa96dcd36c50a42646448e5473 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 21 Jul 2019 23:09:02 +0200 Subject: [PATCH 159/202] Add new RPCErrors --- compiler/error/source/400_BAD_REQUEST.tsv | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) 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 From c0e9b98cab1883d5aff7e637672b0d4675f875e8 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 21 Jul 2019 23:10:23 +0200 Subject: [PATCH 160/202] Add is_gallery parameter to answer_inline_query and InlineQuery.answer --- .../client/methods/bots/answer_inline_query.py | 16 +++++++++++----- .../client/types/inline_mode/inline_query.py | 10 ++++++++-- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/pyrogram/client/methods/bots/answer_inline_query.py b/pyrogram/client/methods/bots/answer_inline_query.py index 38ed99c3..c2777256 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. @@ -83,7 +89,7 @@ class AnswerInlineQuery(BaseClient): 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/types/inline_mode/inline_query.py b/pyrogram/client/types/inline_mode/inline_query.py index 6bfc58c3..065c4492 100644 --- a/pyrogram/client/types/inline_mode/inline_query.py +++ b/pyrogram/client/types/inline_mode/inline_query.py @@ -87,7 +87,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 +117,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 +150,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, From 4274ef9639e0bdea57ae5919723ea90029ff0f77 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 21 Jul 2019 23:10:57 +0200 Subject: [PATCH 161/202] Add abstract method InputMessageContent.write --- .../types/input_message_content/input_message_content.py | 3 +++ 1 file changed, 3 insertions(+) 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..b02c6b38 100644 --- a/pyrogram/client/types/input_message_content/input_message_content.py +++ b/pyrogram/client/types/input_message_content/input_message_content.py @@ -35,3 +35,6 @@ class InputMessageContent(Object): def __init__(self): super().__init__() + + def write(self, reply_markup): + raise NotImplementedError From f0c1cb00cab978f57eaeb901db5654502457c16f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 21 Jul 2019 23:18:38 +0200 Subject: [PATCH 162/202] Rework InlineQueryResultArticle. Also add *Photo and *Animation types --- compiler/docs/compiler.py | 2 + pyrogram/client/types/inline_mode/__init__.py | 5 +- .../types/inline_mode/inline_query_result.py | 20 ++- .../inline_query_result_animation.py | 132 ++++++++++++++++++ .../inline_query_result_article.py | 53 +++---- .../inline_mode/inline_query_result_photo.py | 132 ++++++++++++++++++ 6 files changed, 303 insertions(+), 41 deletions(-) create mode 100644 pyrogram/client/types/inline_mode/inline_query_result_animation.py create mode 100644 pyrogram/client/types/inline_mode/inline_query_result_photo.py diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index 98c3c591..cb3ad5a2 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -341,6 +341,8 @@ def pyrogram_api(): InlineQuery InlineQueryResult InlineQueryResultArticle + InlineQueryResultPhoto + InlineQueryResultAnimation """, input_message_content=""" InputMessageContent 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_result.py b/pyrogram/client/types/inline_mode/inline_query_result.py index 3fc70885..1ff3e5e1 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,25 @@ class InlineQueryResult(Object): Pyrogram currently supports results of the following types: - :obj:`InlineQueryResultArticle` + - :obj:`InlineQueryResultPhoto` + - :obj:`InlineQueryResultAnimation` """ - __slots__ = ["type", "id"] + __slots__ = ["type", "id", "input_message_content", "reply_markup"] - 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..4d2d5596 --- /dev/null +++ b/pyrogram/client/types/inline_mode/inline_query_result_animation.py @@ -0,0 +1,132 @@ +# 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. + """ + + __slots__ = [ + "animation_url", "thumb_url", "title", "description", "caption", "parse_mode", "reply_markup", + "input_message_content" + ] + + 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..c21416f5 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,34 @@ 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" - ] + __slots__ = ["title", "url", "description", "thumb_url"] 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 +80,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..3442764e --- /dev/null +++ b/pyrogram/client/types/inline_mode/inline_query_result_photo.py @@ -0,0 +1,132 @@ +# 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. + """ + + __slots__ = [ + "photo_url", "thumb_url", "title", "description", "caption", "parse_mode", "reply_markup", + "input_message_content" + ] + + 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) + ) + ) + ) From af1bb3b0a7d0db6b5bcc34d6387870a397b4448b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 22 Jul 2019 13:31:03 +0200 Subject: [PATCH 163/202] Fix command filter relying on a previous command state --- pyrogram/client/filters/filters.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyrogram/client/filters/filters.py b/pyrogram/client/filters/filters.py index 01d6bbd4..0a7346ef 100644 --- a/pyrogram/client/filters/filters.py +++ b/pyrogram/client/filters/filters.py @@ -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: From 776557f60bfc08d6feb1cf69fc37a8550698bd81 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 22 Jul 2019 13:31:50 +0200 Subject: [PATCH 164/202] Refactor regex filter --- pyrogram/client/filters/filters.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/pyrogram/client/filters/filters.py b/pyrogram/client/filters/filters.py index 0a7346ef..f80127c2 100644 --- a/pyrogram/client/filters/filters.py +++ b/pyrogram/client/filters/filters.py @@ -273,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): From 948bba7a08d4aaf14e4144d576f64ccc78069249 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 22 Jul 2019 14:16:43 +0200 Subject: [PATCH 165/202] Add missing return type --- pyrogram/client/types/messages_and_media/message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 8b02839f..ae0e257a 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -2949,7 +2949,7 @@ class Message(Object, Update): Index of the poll option you want to vote for (0 to 9). Returns: - On success, the poll with the chosen option is returned. + :obj:`Poll`: On success, the poll with the chosen option is returned. Raises: RPCError: In case of a Telegram RPC error. From a320088fee36cd5f7be49bef1e9ad05bf78eaf84 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 22 Jul 2019 14:21:42 +0200 Subject: [PATCH 166/202] Fix Message.retract_vote style --- pyrogram/client/types/messages_and_media/message.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 80597e33..a534aae1 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -2871,8 +2871,8 @@ class Message(Object, Update): def retract_vote( - self, - ) -> "Poll": + self, + ) -> "Poll": """Bound method *retract_vote* of :obj:`Message`. Use as a shortcut for: @@ -2883,14 +2883,14 @@ class Message(Object, Update): 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. + :obj:`Poll`: On success, the poll with the retracted vote is returned. Raises: RPCError: In case of a Telegram RPC error. From b2886c21ca6faced3d3e9bed1fb73946cf60e396 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 25 Jul 2019 10:48:36 +0200 Subject: [PATCH 167/202] Clarify get_messages error messages --- pyrogram/client/methods/messages/get_messages.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyrogram/client/methods/messages/get_messages.py b/pyrogram/client/methods/messages/get_messages.py index 0f901174..0e3a2ea3 100644 --- a/pyrogram/client/methods/messages/get_messages.py +++ b/pyrogram/client/methods/messages/get_messages.py @@ -66,6 +66,7 @@ class GetMessages(BaseClient): 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 +75,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) From 0302a27b7e0dde1bf4b02ecf7839a1f03cbc558f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 25 Jul 2019 11:15:14 +0200 Subject: [PATCH 168/202] Update FAQs --- docs/source/faq.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/source/faq.rst b/docs/source/faq.rst index 147eb4fa..2dde12f2 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 From 72b7a53ff5b6a12a74bf18859231e3c94c9279fd Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 25 Jul 2019 11:15:56 +0200 Subject: [PATCH 169/202] Log to warning only in the last attempt --- pyrogram/session/session.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 5947fc0f..21e2ba10 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -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) From 8f56610c2ca0d216b88331a1d3e5551e8d5bde28 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 25 Jul 2019 11:17:28 +0200 Subject: [PATCH 170/202] Add examples to utility methods --- pyrogram/client/client.py | 206 ++++++++++++++++++++++++++++++-------- 1 file changed, 166 insertions(+), 40 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 19ff1b5e..64595a91 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 @@ -269,11 +280,28 @@ class Client(Methods, BaseClient): self._proxy.update(value) 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") @@ -346,8 +374,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") @@ -388,8 +433,30 @@ 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() @@ -451,25 +518,40 @@ class Client(Methods, BaseClient): time.sleep(1) 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``): @@ -479,7 +561,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 @@ -491,9 +588,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``): @@ -501,6 +597,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 @@ -509,7 +623,28 @@ 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 @@ -541,9 +676,9 @@ class Client(Methods, BaseClient): 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 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. + 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``): @@ -1172,7 +1307,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 @@ -1445,23 +1580,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. @@ -1775,11 +1909,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() From fe2ccc6036e020087cc68734042dd9c7644b8af1 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 25 Jul 2019 11:18:11 +0200 Subject: [PATCH 171/202] Clean up Message docstrings --- .../types/messages_and_media/message.py | 182 +++++++++--------- 1 file changed, 92 insertions(+), 90 deletions(-) diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 2f1d5928..88b05e43 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -817,23 +817,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. @@ -954,23 +953,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. @@ -1286,23 +1284,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. @@ -1681,23 +1678,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 +1855,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. @@ -2092,23 +2087,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 +2208,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. @@ -2333,23 +2326,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. @@ -2619,9 +2611,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, @@ -2900,14 +2889,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. From 2dec2442e5506ed96400b75b5ba9446022ff180f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 25 Jul 2019 11:22:14 +0200 Subject: [PATCH 172/202] Add examples to all available methods --- .../methods/bots/answer_callback_query.py | 10 ++++- .../methods/bots/answer_inline_query.py | 13 +++++- .../methods/bots/get_game_high_scores.py | 7 +++- .../methods/bots/get_inline_bot_results.py | 12 ++++-- .../methods/bots/request_callback_answer.py | 6 ++- pyrogram/client/methods/bots/send_game.py | 6 ++- .../methods/bots/send_inline_bot_result.py | 6 ++- .../client/methods/bots/set_game_score.py | 10 ++++- .../client/methods/chats/add_chat_members.py | 12 ++++++ .../client/methods/chats/archive_chats.py | 10 ++++- .../client/methods/chats/create_channel.py | 5 +++ pyrogram/client/methods/chats/create_group.py | 5 +++ .../client/methods/chats/create_supergroup.py | 5 +++ .../client/methods/chats/delete_channel.py | 5 +++ .../client/methods/chats/delete_chat_photo.py | 14 +++---- .../client/methods/chats/delete_supergroup.py | 5 +++ .../methods/chats/export_chat_invite_link.py | 7 +++- pyrogram/client/methods/chats/get_chat.py | 7 +++- .../client/methods/chats/get_chat_member.py | 7 +++- .../client/methods/chats/get_chat_members.py | 13 +++++- .../methods/chats/get_chat_members_count.py | 7 +++- pyrogram/client/methods/chats/get_dialogs.py | 10 ++++- .../client/methods/chats/get_dialogs_count.py | 7 +++- .../client/methods/chats/iter_chat_members.py | 16 +++++++- pyrogram/client/methods/chats/iter_dialogs.py | 8 +++- pyrogram/client/methods/chats/join_chat.py | 10 ++++- .../client/methods/chats/kick_chat_member.py | 12 +++++- pyrogram/client/methods/chats/leave_chat.py | 11 ++++- .../client/methods/chats/pin_chat_message.py | 10 ++++- .../methods/chats/promote_chat_member.py | 7 +++- .../client/methods/chats/restrict_chat.py | 10 ++++- .../methods/chats/restrict_chat_member.py | 15 ++++++- .../methods/chats/set_chat_description.py | 8 +++- .../client/methods/chats/set_chat_photo.py | 28 +++++++------ .../client/methods/chats/set_chat_title.py | 6 ++- .../client/methods/chats/unarchive_chats.py | 10 ++++- .../client/methods/chats/unban_chat_member.py | 7 +++- .../methods/chats/unpin_chat_message.py | 6 ++- .../methods/chats/update_chat_username.py | 6 ++- .../client/methods/contacts/add_contacts.py | 11 ++++- .../methods/contacts/delete_contacts.py | 6 ++- .../client/methods/contacts/get_contacts.py | 8 ++-- .../methods/contacts/get_contacts_count.py | 7 +++- .../methods/messages/delete_messages.py | 13 +++++- .../client/methods/messages/download_media.py | 33 +++++++++------ .../methods/messages/edit_inline_caption.py | 9 ++-- .../methods/messages/edit_inline_media.py | 19 +++++++-- .../messages/edit_inline_reply_markup.py | 14 +++++-- .../methods/messages/edit_inline_text.py | 16 ++++++-- .../methods/messages/edit_message_caption.py | 6 ++- .../methods/messages/edit_message_media.py | 15 ++++++- .../messages/edit_message_reply_markup.py | 12 +++++- .../methods/messages/edit_message_text.py | 12 +++++- .../methods/messages/forward_messages.py | 17 ++++++-- .../client/methods/messages/get_history.py | 13 +++++- .../methods/messages/get_history_count.py | 6 ++- .../client/methods/messages/get_messages.py | 19 ++++++++- .../client/methods/messages/iter_history.py | 7 +++- .../client/methods/messages/read_history.py | 10 ++++- .../client/methods/messages/retract_vote.py | 6 ++- .../methods/messages/send_animated_sticker.py | 27 ++++++------ .../client/methods/messages/send_animation.py | 40 ++++++++++++------ .../client/methods/messages/send_audio.py | 41 +++++++++++++------ .../methods/messages/send_cached_media.py | 6 ++- .../methods/messages/send_chat_action.py | 18 +++++++- .../client/methods/messages/send_contact.py | 6 ++- .../client/methods/messages/send_document.py | 35 ++++++++++------ .../client/methods/messages/send_location.py | 6 ++- .../methods/messages/send_media_group.py | 15 ++++++- .../client/methods/messages/send_message.py | 39 +++++++++++++++++- .../client/methods/messages/send_photo.py | 35 ++++++++++------ pyrogram/client/methods/messages/send_poll.py | 6 ++- .../client/methods/messages/send_sticker.py | 30 ++++++++------ .../client/methods/messages/send_venue.py | 8 +++- .../client/methods/messages/send_video.py | 35 ++++++++++------ .../methods/messages/send_video_note.py | 29 +++++++------ .../client/methods/messages/send_voice.py | 32 +++++++++------ pyrogram/client/methods/messages/stop_poll.py | 6 ++- pyrogram/client/methods/messages/vote_poll.py | 6 ++- .../methods/password/change_cloud_password.py | 10 ++++- .../methods/password/enable_cloud_password.py | 13 +++++- .../methods/password/remove_cloud_password.py | 6 ++- pyrogram/client/methods/users/block_user.py | 12 +++++- .../methods/users/delete_profile_photos.py | 13 +++++- pyrogram/client/methods/users/get_me.py | 9 ++-- .../methods/users/get_profile_photos.py | 13 +++++- .../methods/users/get_profile_photos_count.py | 7 +++- pyrogram/client/methods/users/get_users.py | 11 +++-- .../methods/users/iter_profile_photos.py | 7 +++- .../client/methods/users/set_profile_photo.py | 6 ++- pyrogram/client/methods/users/unblock_user.py | 12 +++++- .../client/methods/users/update_username.py | 6 ++- 92 files changed, 876 insertions(+), 287 deletions(-) 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 c2777256..da801c62 100644 --- a/pyrogram/client/methods/bots/answer_inline_query.py +++ b/pyrogram/client/methods/bots/answer_inline_query.py @@ -81,8 +81,17 @@ 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( 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/add_chat_members.py b/pyrogram/client/methods/chats/add_chat_members.py index ce5b0cce..8dbad1a3 100644 --- a/pyrogram/client/methods/chats/add_chat_members.py +++ b/pyrogram/client/methods/chats/add_chat_members.py @@ -47,6 +47,18 @@ class AddChatMembers(BaseClient): 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) 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 index c9b804f1..9520ceef 100644 --- a/pyrogram/client/methods/chats/create_channel.py +++ b/pyrogram/client/methods/chats/create_channel.py @@ -38,6 +38,11 @@ class CreateChannel(BaseClient): 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( diff --git a/pyrogram/client/methods/chats/create_group.py b/pyrogram/client/methods/chats/create_group.py index cbf71bb3..4e1d63bd 100644 --- a/pyrogram/client/methods/chats/create_group.py +++ b/pyrogram/client/methods/chats/create_group.py @@ -46,6 +46,11 @@ class CreateGroup(BaseClient): 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] diff --git a/pyrogram/client/methods/chats/create_supergroup.py b/pyrogram/client/methods/chats/create_supergroup.py index 163eae93..0ad14d06 100644 --- a/pyrogram/client/methods/chats/create_supergroup.py +++ b/pyrogram/client/methods/chats/create_supergroup.py @@ -42,6 +42,11 @@ class CreateSupergroup(BaseClient): 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( diff --git a/pyrogram/client/methods/chats/delete_channel.py b/pyrogram/client/methods/chats/delete_channel.py index 47a29b76..74fbea13 100644 --- a/pyrogram/client/methods/chats/delete_channel.py +++ b/pyrogram/client/methods/chats/delete_channel.py @@ -33,6 +33,11 @@ class DeleteChannel(BaseClient): Returns: ``bool``: On success, True is returned. + + Example: + .. code-block:: python + + app.delete_channel(channel_id) """ self.send( functions.channels.DeleteChannel( 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 index f4ec5e2f..a1eb198d 100644 --- a/pyrogram/client/methods/chats/delete_supergroup.py +++ b/pyrogram/client/methods/chats/delete_supergroup.py @@ -33,6 +33,11 @@ class DeleteSupergroup(BaseClient): Returns: ``bool``: On success, True is returned. + + Example: + .. code-block:: python + + app.delete_supergroup(supergroup_id) """ self.send( functions.channels.DeleteChannel( diff --git a/pyrogram/client/methods/chats/export_chat_invite_link.py b/pyrogram/client/methods/chats/export_chat_invite_link.py index ca75cac6..bf5d3a38 100644 --- a/pyrogram/client/methods/chats/export_chat_invite_link.py +++ b/pyrogram/client/methods/chats/export_chat_invite_link.py @@ -47,8 +47,13 @@ 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) diff --git a/pyrogram/client/methods/chats/get_chat.py b/pyrogram/client/methods/chats/get_chat.py index 4f71c3b3..48c5cc22 100644 --- a/pyrogram/client/methods/chats/get_chat.py +++ b/pyrogram/client/methods/chats/get_chat.py @@ -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)) 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..f77ad30a 100644 --- a/pyrogram/client/methods/chats/get_dialogs.py +++ b/pyrogram/client/methods/chats/get_dialogs.py @@ -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: 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.py b/pyrogram/client/methods/chats/restrict_chat.py index dc0f96a1..20acd5e1 100644 --- a/pyrogram/client/methods/chats/restrict_chat.py +++ b/pyrogram/client/methods/chats/restrict_chat.py @@ -72,8 +72,14 @@ class RestrictChat(BaseClient): Returns: :obj:`Chat`: On success, a chat object is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Completely restrict chat + app.restrict_chat(chat_id) + + # All chat members can only send text messages + app.restrict_chat(chat_id, can_send_messages=True) """ send_messages = True send_media = True diff --git a/pyrogram/client/methods/chats/restrict_chat_member.py b/pyrogram/client/methods/chats/restrict_chat_member.py index 30574022..60787b32 100644 --- a/pyrogram/client/methods/chats/restrict_chat_member.py +++ b/pyrogram/client/methods/chats/restrict_chat_member.py @@ -85,8 +85,19 @@ class RestrictChatMember(BaseClient): 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 + + # Completely restrict chat member forever + app.restrict_chat_member(chat_id, user_id) + + # Chat member can't send messages for 24h + app.restrict_chat_member(chat_id, user_id, int(time.time() + 86400)) + + # Chat member can only send text messages + app.restrict_chat_member(chat_id, user_id, can_send_messages=True) """ send_messages = True send_media = True 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/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/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..46709ced 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") diff --git a/pyrogram/client/methods/messages/edit_inline_caption.py b/pyrogram/client/methods/messages/edit_inline_caption.py index 298e3ef4..57a0ac75 100644 --- a/pyrogram/client/methods/messages/edit_inline_caption.py +++ b/pyrogram/client/methods/messages/edit_inline_caption.py @@ -30,7 +30,7 @@ class EditInlineCaption(BaseClient): 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``): @@ -52,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 9b0b34d3..c92e13a1 100644 --- a/pyrogram/client/methods/messages/edit_inline_text.py +++ b/pyrogram/client/methods/messages/edit_inline_text.py @@ -32,7 +32,7 @@ class EditInlineText(BaseClient): 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 c760c675..eae59c62 100644 --- a/pyrogram/client/methods/messages/edit_message_caption.py +++ b/pyrogram/client/methods/messages/edit_message_caption.py @@ -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 063c8c72..31022c0e 100644 --- a/pyrogram/client/methods/messages/edit_message_text.py +++ b/pyrogram/client/methods/messages/edit_message_text.py @@ -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 0e3a2ea3..e9615b43 100644 --- a/pyrogram/client/methods/messages/get_messages.py +++ b/pyrogram/client/methods/messages/get_messages.py @@ -64,8 +64,25 @@ 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 = ( 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 index 6fd0c647..8e57c527 100644 --- a/pyrogram/client/methods/messages/send_animated_sticker.py +++ b/pyrogram/client/methods/messages/send_animated_sticker.py @@ -67,23 +67,22 @@ class SendAnimatedSticker(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,12 @@ class SendAnimatedSticker(BaseClient): 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. + + Example: + .. code-block:: python + + # Send animated sticker by uploading from local file + app.send_animated_sticker("me", "animated_sticker.tgs") """ file = None diff --git a/pyrogram/client/methods/messages/send_animation.py b/pyrogram/client/methods/messages/send_animation.py index a68984fc..5d345010 100644 --- a/pyrogram/client/methods/messages/send_animation.py +++ b/pyrogram/client/methods/messages/send_animation.py @@ -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 c8ce7368..7395718b 100644 --- a/pyrogram/client/methods/messages/send_audio.py +++ b/pyrogram/client/methods/messages/send_audio.py @@ -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 59a24171..9b4fbafa 100644 --- a/pyrogram/client/methods/messages/send_cached_media.py +++ b/pyrogram/client/methods/messages/send_cached_media.py @@ -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 a3cec395..567bc561 100644 --- a/pyrogram/client/methods/messages/send_document.py +++ b/pyrogram/client/methods/messages/send_document.py @@ -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 1f0aa3dd..ac38c0d6 100644 --- a/pyrogram/client/methods/messages/send_media_group.py +++ b/pyrogram/client/methods/messages/send_media_group.py @@ -59,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 = [] diff --git a/pyrogram/client/methods/messages/send_message.py b/pyrogram/client/methods/messages/send_message.py index f652f3d9..c15b3a84 100644 --- a/pyrogram/client/methods/messages/send_message.py +++ b/pyrogram/client/methods/messages/send_message.py @@ -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 981b0045..0c82ebfc 100644 --- a/pyrogram/client/methods/messages/send_photo.py +++ b/pyrogram/client/methods/messages/send_photo.py @@ -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..a5fc7b26 100644 --- a/pyrogram/client/methods/messages/send_sticker.py +++ b/pyrogram/client/methods/messages/send_sticker.py @@ -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 602e3b01..ca6f0519 100644 --- a/pyrogram/client/methods/messages/send_video.py +++ b/pyrogram/client/methods/messages/send_video.py @@ -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 9c0b8514..8d9f6c5f 100644 --- a/pyrogram/client/methods/messages/send_voice.py +++ b/pyrogram/client/methods/messages/send_voice.py @@ -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..120a4aaf 100644 --- a/pyrogram/client/methods/users/block_user.py +++ b/pyrogram/client/methods/users/block_user.py @@ -30,11 +30,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..c155ede8 100644 --- a/pyrogram/client/methods/users/delete_profile_photos.py +++ b/pyrogram/client/methods/users/delete_profile_photos.py @@ -40,8 +40,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..dece6b5d 100644 --- a/pyrogram/client/methods/users/get_profile_photos.py +++ b/pyrogram/client/methods/users/get_profile_photos.py @@ -51,8 +51,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..4069f85b 100644 --- a/pyrogram/client/methods/users/get_profile_photos_count.py +++ b/pyrogram/client/methods/users/get_profile_photos_count.py @@ -36,8 +36,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..8b87cd7e 100644 --- a/pyrogram/client/methods/users/unblock_user.py +++ b/pyrogram/client/methods/users/unblock_user.py @@ -30,11 +30,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( From 7b9a38a2bae77c33ea12ec6887d4789239b6ccf6 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 28 Jul 2019 08:16:41 +0200 Subject: [PATCH 173/202] Fix indentation and docstrings due to bad PR --- pyrogram/client/types/messages_and_media/message.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 4f5043e4..2994ec56 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -2857,11 +2857,10 @@ class Message(Object, Update): raise ValueError("This button is not supported yet") else: self.reply(button, quote=quote) - - - def retract_vote( - self, - ) -> "Poll": + + def retract_vote( + self, + ) -> "pyrogram.Poll": """Bound method *retract_vote* of :obj:`Message`. Use as a shortcut for: @@ -2957,10 +2956,11 @@ class Message(Object, Update): progress=progress, progress_args=progress_args, ) + def vote( self, option: int, - ) -> "Poll": + ) -> "pyrogram.Poll": """Bound method *vote* of :obj:`Message`. Use as a shortcut for: From 87bbd764b9751b8e9e231f0d3ba75efe39bf3cd0 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 28 Jul 2019 08:54:18 +0200 Subject: [PATCH 174/202] Add better summary for get_messages --- pyrogram/client/methods/messages/get_messages.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pyrogram/client/methods/messages/get_messages.py b/pyrogram/client/methods/messages/get_messages.py index e9615b43..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: From 11ea15aa08fc423154596011ce61944aef368f90 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 28 Jul 2019 09:32:35 +0200 Subject: [PATCH 175/202] Fix yet another compatibility issue with Path objects and Python 3.5 --- pyrogram/client/methods/messages/download_media.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyrogram/client/methods/messages/download_media.py b/pyrogram/client/methods/messages/download_media.py index 46709ced..b00b7c72 100644 --- a/pyrogram/client/methods/messages/download_media.py +++ b/pyrogram/client/methods/messages/download_media.py @@ -214,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() From bed13de413a4b0fad4e1662bb0e4e0ddb82d29f2 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 28 Jul 2019 09:47:11 +0200 Subject: [PATCH 176/202] Fix ChatPreview objects failing to parse This happened because Telegram changed the preview photo type from ChatPhoto to Photo. The reason behind this change was due to ChatPhoto requiring now a peer id to be downloaded, which is not available in case of chat previews. --- .../client/types/user_and_chats/chat_preview.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pyrogram/client/types/user_and_chats/chat_preview.py b/pyrogram/client/types/user_and_chats/chat_preview.py index 312bdfe6..781f85c8 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,48 @@ 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"] + __slots__ = ["title", "type", "members_count", "photo", "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 ) From e1c6e6ecc142da31a14608c9b0381ee69b2b804e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 28 Jul 2019 10:13:38 +0200 Subject: [PATCH 177/202] Better handling of non-string message texts Now everything will be allowed and automatically casted to string. This means that send_message(id, True) would send "True", literally. --- pyrogram/client/parser/parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/parser/parser.py b/pyrogram/client/parser/parser.py index edf9553d..371c4791 100644 --- a/pyrogram/client/parser/parser.py +++ b/pyrogram/client/parser/parser.py @@ -31,7 +31,7 @@ class Parser: self.markdown = Markdown(client) def parse(self, text: str, mode: Union[str, None] = object): - text = str(text or "").strip() + text = str(text).strip() if mode == object: if self.client: From 8cdcf90b1044a07b5618d99e13111aed27e28b7d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 28 Jul 2019 15:11:18 +0200 Subject: [PATCH 178/202] Enhance Parser when dealing with leading and trailing whitespaces --- pyrogram/client/parser/html.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 From d875298937636a2fde245f8f05ee08882dec19fc Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 29 Jul 2019 12:16:00 +0200 Subject: [PATCH 179/202] Fix forward copies having "None" as caption in case of no caption at all --- pyrogram/client/types/messages_and_media/message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 2994ec56..4b98c290 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -2620,7 +2620,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, From 02451ffeb56bed9918ef548b4744afe069143495 Mon Sep 17 00:00:00 2001 From: Mendel E Date: Mon, 29 Jul 2019 07:31:07 -0400 Subject: [PATCH 180/202] Try/except handler.check() Previously, when handler.check raised errors, it would be excepted by the try on line 153, and would fail to loop through the rest of the groups/handlers. --- pyrogram/client/ext/dispatcher.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) 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) From 47e5b9a7cbeb225ea7a0213f69d653b3bb07ec59 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 1 Aug 2019 00:53:17 +0200 Subject: [PATCH 181/202] Set Message.text instead of Message.caption in case of web_page previews --- .../types/messages_and_media/message.py | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 4b98c290..a5105888 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -602,10 +602,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, From 93a2fed8e662a1cd581065b0c2c50f4778c8b065 Mon Sep 17 00:00:00 2001 From: MrNaif2018 <39452697+MrNaif2018@users.noreply.github.com> Date: Thu, 1 Aug 2019 10:11:29 +0300 Subject: [PATCH 182/202] Improved examples, added links to docs --- docs/source/topics/scheduling.rst | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/docs/source/topics/scheduling.rst b/docs/source/topics/scheduling.rst index 70f70215..1f4e1e01 100644 --- a/docs/source/topics/scheduling.rst +++ b/docs/source/topics/scheduling.rst @@ -9,8 +9,6 @@ But it is easy to integrate pyrogram with your favourite scheduler. schedule -------- -Note that schedule is not suitable for async version of pyrogram. - .. code-block:: python import time @@ -20,20 +18,15 @@ Note that schedule is not suitable for async version of pyrogram. def job(): app.send_message("me", "Hi!") - - schedule.every(10).minutes.do(job) - schedule.every().hour.do(job) - schedule.every().day.at("10:30").do(job) - schedule.every(5).to(10).minutes.do(job) - schedule.every().monday.do(job) - schedule.every().wednesday.at("13:15").do(job) - schedule.every().minute.at(":17").do(job) + schedule.every(3).seconds.do(job) with app: while True: schedule.run_pending() time.sleep(1) +Note that schedule is not suitable for async version of pyrogram. +For more information read `library `_ docs. apscheduler ----------- @@ -71,3 +64,4 @@ Apscheduler supports async version of pyrogram too, here is async example: scheduler.start() app.run() +For more information read `library `_ docs. From 6973f584886bc5d65e6aa5d88a5f98abeb8a287e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 1 Aug 2019 12:37:22 +0200 Subject: [PATCH 183/202] Update scheduling.rst --- docs/source/topics/scheduling.rst | 50 +++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/docs/source/topics/scheduling.rst b/docs/source/topics/scheduling.rst index 1f4e1e01..3cb95ec7 100644 --- a/docs/source/topics/scheduling.rst +++ b/docs/source/topics/scheduling.rst @@ -1,23 +1,34 @@ -Scheduling tasks +Scheduling Tasks ================ -Pyrogram itself as Telegram MTProto API Framework contains only stuff -related to Telegram. Scheduling is out of it's scope. +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. -But it is easy to integrate pyrogram with your favourite scheduler. +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. -schedule --------- +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: @@ -25,43 +36,52 @@ schedule schedule.run_pending() time.sleep(1) -Note that schedule is not suitable for async version of pyrogram. -For more information read `library `_ docs. -apscheduler ------------ + +Using ``apscheduler`` +--------------------- + +- Install with ``pip3 install apscheduler`` +- Documentation: https://apscheduler.readthedocs.io .. code-block:: python - import time 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.add_job(job, "interval", seconds=3) scheduler.start() app.run() -Apscheduler supports async version of pyrogram too, here is async example: +``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.add_job(job, "interval", seconds=3) scheduler.start() app.run() -For more information read `library `_ docs. From f8f2ad5a60025eaf7a611b7680faa5bec50c1bd7 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 1 Aug 2019 12:44:34 +0200 Subject: [PATCH 184/202] Tidy up docs --- docs/source/index.rst | 4 ++-- docs/source/topics/{serialize.rst => serializing.rst} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename docs/source/topics/{serialize.rst => serializing.rst} (100%) diff --git a/docs/source/index.rst b/docs/source/index.rst index b8927657..f6961bc6 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -40,14 +40,14 @@ Welcome to Pyrogram topics/more-on-updates topics/config-file topics/smart-plugins - topics/scheduling topics/auto-auth topics/session-settings 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/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 From 3fedae8d820cd455aab69cc8a8a9627bd05af833 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 1 Aug 2019 17:26:59 +0200 Subject: [PATCH 185/202] Update faq.rst --- docs/source/faq.rst | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/docs/source/faq.rst b/docs/source/faq.rst index 2dde12f2..a05ff39c 100644 --- a/docs/source/faq.rst +++ b/docs/source/faq.rst @@ -192,7 +192,6 @@ dismissed and their IP addresses are now kept as aliases to the nearest one. Thanks to `@FrayxRulez `_ for telling about alias DCs. - I want to migrate my account from DCX to DCY. --------------------------------------------- @@ -246,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 … ----------------------------------------------------- @@ -258,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! ----------------------------------------- From d727754ad6256eaaa8a2b49850e99e5862a88d30 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 1 Aug 2019 18:41:04 +0200 Subject: [PATCH 186/202] Remove Null primitive It's unused --- compiler/api/compiler.py | 1 - pyrogram/api/core/primitives/__init__.py | 5 ++-- pyrogram/api/core/primitives/null.py | 32 ------------------------ 3 files changed, 3 insertions(+), 35 deletions(-) delete mode 100644 pyrogram/api/core/primitives/null.py 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/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/primitives/null.py b/pyrogram/api/core/primitives/null.py deleted file mode 100644 index ffddea94..00000000 --- a/pyrogram/api/core/primitives/null.py +++ /dev/null @@ -1,32 +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 . - -from io import BytesIO - -from ..tl_object import TLObject - - -class Null(TLObject): - ID = 0x56730bcc - - @staticmethod - def read(b: BytesIO, *args) -> None: - return None - - def __new__(cls) -> bytes: - return cls.ID.to_bytes(4, "little") From 9ad5e62dea5fa5d9786770097c2b0de8db10b6f4 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 1 Aug 2019 18:44:20 +0200 Subject: [PATCH 187/202] Move all imported schema objects outside TLObject --- pyrogram/api/__init__.py | 3 +-- pyrogram/api/core/__init__.py | 5 +---- pyrogram/api/core/tl_object.py | 6 +++--- 3 files changed, 5 insertions(+), 9 deletions(-) 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/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 From ad0f8284f686b2501460a51943b2149deb9331ce Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 1 Aug 2019 19:07:08 +0200 Subject: [PATCH 188/202] Use the correct way to parse peer identifiers --- pyrogram/client/client.py | 45 +++++++------- pyrogram/client/ext/utils.py | 60 ++++++++++++------- pyrogram/client/methods/chats/get_chat.py | 4 +- pyrogram/client/methods/chats/get_dialogs.py | 6 +- .../bots_and_keyboards/callback_query.py | 12 +--- .../types/messages_and_media/message.py | 7 ++- pyrogram/client/types/user_and_chats/chat.py | 5 +- .../client/types/user_and_chats/dialog.py | 12 +--- 8 files changed, 80 insertions(+), 71 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 64595a91..511dbf2a 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -1015,7 +1015,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) @@ -1131,7 +1131,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, @@ -1519,33 +1519,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) diff --git a/pyrogram/client/ext/utils.py b/pyrogram/client/ext/utils.py index e0a797e2..d89f83bb 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 @@ -62,23 +63,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 +167,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 +187,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/methods/chats/get_chat.py b/pyrogram/client/methods/chats/get_chat.py index 48c5cc22..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): @@ -70,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_dialogs.py b/pyrogram/client/methods/chats/get_dialogs.py index f77ad30a..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__) @@ -100,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/types/bots_and_keyboards/callback_query.py b/pyrogram/client/types/bots_and_keyboards/callback_query.py index d58865b2..9ba1804b 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): @@ -90,16 +91,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( diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index a5105888..ed0088cd 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): @@ -446,7 +447,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, diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py index cac5d0c7..396831cc 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): @@ -180,7 +181,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, @@ -672,7 +673,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/dialog.py b/pyrogram/client/types/user_and_chats/dialog.py index 4ea82184..a78e501b 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): @@ -70,18 +71,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, From aa135ea457880a051531b7624c65f8d1dc6b45da Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 1 Aug 2019 19:11:43 +0200 Subject: [PATCH 189/202] Reformat project --- pyrogram/client/ext/utils.py | 3 ++- pyrogram/client/methods/users/block_user.py | 3 +-- pyrogram/client/methods/users/delete_profile_photos.py | 1 - pyrogram/client/methods/users/get_profile_photos.py | 1 - pyrogram/client/methods/users/get_profile_photos_count.py | 1 - pyrogram/client/methods/users/unblock_user.py | 3 +-- pyrogram/client/parser/__init__.py | 2 +- pyrogram/client/storage/__init__.py | 2 +- .../client/types/bots_and_keyboards/inline_keyboard_button.py | 1 + .../client/types/bots_and_keyboards/inline_keyboard_markup.py | 1 + .../client/types/bots_and_keyboards/reply_keyboard_markup.py | 1 + pyrogram/errors/rpc_error.py | 3 ++- pyrogram/session/auth.py | 1 - pyrogram/session/internals/msg_factory.py | 3 ++- pyrogram/session/session.py | 4 ++-- 15 files changed, 15 insertions(+), 15 deletions(-) diff --git a/pyrogram/client/ext/utils.py b/pyrogram/client/ext/utils.py index d89f83bb..fbee9ea8 100644 --- a/pyrogram/client/ext/utils.py +++ b/pyrogram/client/ext/utils.py @@ -21,8 +21,9 @@ import struct from typing import List from typing import Union -import pyrogram from pyrogram.api.types import PeerUser, PeerChat, PeerChannel + +import pyrogram from . import BaseClient from ...api import types diff --git a/pyrogram/client/methods/users/block_user.py b/pyrogram/client/methods/users/block_user.py index 120a4aaf..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 diff --git a/pyrogram/client/methods/users/delete_profile_photos.py b/pyrogram/client/methods/users/delete_profile_photos.py index c155ede8..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 diff --git a/pyrogram/client/methods/users/get_profile_photos.py b/pyrogram/client/methods/users/get_profile_photos.py index dece6b5d..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 diff --git a/pyrogram/client/methods/users/get_profile_photos_count.py b/pyrogram/client/methods/users/get_profile_photos_count.py index 4069f85b..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 diff --git a/pyrogram/client/methods/users/unblock_user.py b/pyrogram/client/methods/users/unblock_user.py index 8b87cd7e..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 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/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/inline_keyboard_button.py b/pyrogram/client/types/bots_and_keyboards/inline_keyboard_button.py index 54aa7802..514b0303 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 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..bdca85d1 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 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..f958051b 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 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 21e2ba10..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__) From 67112a34e92c2265f9aed555b9299fb594cd0869 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 2 Aug 2019 00:33:48 +0200 Subject: [PATCH 190/202] Remove __slots__ from Pyrogram types --- .../client/types/bots_and_keyboards/callback_game.py | 2 -- .../client/types/bots_and_keyboards/callback_query.py | 2 -- .../client/types/bots_and_keyboards/force_reply.py | 2 -- .../types/bots_and_keyboards/game_high_score.py | 2 -- .../bots_and_keyboards/inline_keyboard_button.py | 4 ---- .../bots_and_keyboards/inline_keyboard_markup.py | 2 -- .../types/bots_and_keyboards/keyboard_button.py | 2 -- .../types/bots_and_keyboards/reply_keyboard_markup.py | 2 -- .../types/bots_and_keyboards/reply_keyboard_remove.py | 2 -- pyrogram/client/types/inline_mode/inline_query.py | 1 - .../client/types/inline_mode/inline_query_result.py | 2 -- .../inline_mode/inline_query_result_animation.py | 5 ----- .../types/inline_mode/inline_query_result_article.py | 2 -- .../types/inline_mode/inline_query_result_photo.py | 5 ----- pyrogram/client/types/input_media/input_media.py | 1 - .../client/types/input_media/input_media_animation.py | 2 -- .../client/types/input_media/input_media_audio.py | 2 -- .../client/types/input_media/input_media_document.py | 2 -- .../client/types/input_media/input_media_photo.py | 2 -- .../client/types/input_media/input_media_video.py | 2 -- .../client/types/input_media/input_phone_contact.py | 2 -- .../input_message_content/input_message_content.py | 2 -- .../input_text_message_content.py | 2 -- pyrogram/client/types/messages_and_media/animation.py | 2 -- pyrogram/client/types/messages_and_media/audio.py | 4 ---- pyrogram/client/types/messages_and_media/contact.py | 2 -- pyrogram/client/types/messages_and_media/document.py | 2 -- pyrogram/client/types/messages_and_media/game.py | 2 -- pyrogram/client/types/messages_and_media/location.py | 2 -- pyrogram/client/types/messages_and_media/message.py | 11 ----------- .../client/types/messages_and_media/message_entity.py | 2 -- pyrogram/client/types/messages_and_media/photo.py | 2 -- pyrogram/client/types/messages_and_media/poll.py | 2 -- .../client/types/messages_and_media/poll_option.py | 2 -- pyrogram/client/types/messages_and_media/sticker.py | 4 ---- .../types/messages_and_media/stripped_thumbnail.py | 2 -- pyrogram/client/types/messages_and_media/thumbnail.py | 2 -- pyrogram/client/types/messages_and_media/venue.py | 2 -- pyrogram/client/types/messages_and_media/video.py | 5 ----- .../client/types/messages_and_media/video_note.py | 2 -- pyrogram/client/types/messages_and_media/voice.py | 2 -- pyrogram/client/types/messages_and_media/webpage.py | 6 ------ pyrogram/client/types/object.py | 8 +++----- pyrogram/client/types/update.py | 2 -- pyrogram/client/types/user_and_chats/chat.py | 6 ------ pyrogram/client/types/user_and_chats/chat_member.py | 2 -- .../client/types/user_and_chats/chat_permissions.py | 7 ------- pyrogram/client/types/user_and_chats/chat_photo.py | 2 -- pyrogram/client/types/user_and_chats/chat_preview.py | 2 -- pyrogram/client/types/user_and_chats/dialog.py | 2 -- pyrogram/client/types/user_and_chats/user.py | 6 ------ 51 files changed, 3 insertions(+), 144 deletions(-) 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 9ba1804b..9a5674ae 100644 --- a/pyrogram/client/types/bots_and_keyboards/callback_query.py +++ b/pyrogram/client/types/bots_and_keyboards/callback_query.py @@ -61,8 +61,6 @@ class CallbackQuery(Object, Update): """ - __slots__ = ["id", "from_user", "chat_instance", "message", "inline_message_id", "data", "game_short_name"] - def __init__( self, *, 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 514b0303..678be614 100644 --- a/pyrogram/client/types/bots_and_keyboards/inline_keyboard_button.py +++ b/pyrogram/client/types/bots_and_keyboards/inline_keyboard_button.py @@ -59,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 bdca85d1..811c4365 100644 --- a/pyrogram/client/types/bots_and_keyboards/inline_keyboard_markup.py +++ b/pyrogram/client/types/bots_and_keyboards/inline_keyboard_markup.py @@ -32,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 f958051b..12799bd7 100644 --- a/pyrogram/client/types/bots_and_keyboards/reply_keyboard_markup.py +++ b/pyrogram/client/types/bots_and_keyboards/reply_keyboard_markup.py @@ -50,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/inline_query.py b/pyrogram/client/types/inline_mode/inline_query.py index 065c4492..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, diff --git a/pyrogram/client/types/inline_mode/inline_query_result.py b/pyrogram/client/types/inline_mode/inline_query_result.py index 1ff3e5e1..ef26eacc 100644 --- a/pyrogram/client/types/inline_mode/inline_query_result.py +++ b/pyrogram/client/types/inline_mode/inline_query_result.py @@ -53,8 +53,6 @@ class InlineQueryResult(Object): - :obj:`InlineQueryResultAnimation` """ - __slots__ = ["type", "id", "input_message_content", "reply_markup"] - def __init__( self, type: str, diff --git a/pyrogram/client/types/inline_mode/inline_query_result_animation.py b/pyrogram/client/types/inline_mode/inline_query_result_animation.py index 4d2d5596..c4cb26e4 100644 --- a/pyrogram/client/types/inline_mode/inline_query_result_animation.py +++ b/pyrogram/client/types/inline_mode/inline_query_result_animation.py @@ -68,11 +68,6 @@ class InlineQueryResultAnimation(InlineQueryResult): Content of the message to be sent instead of the photo. """ - __slots__ = [ - "animation_url", "thumb_url", "title", "description", "caption", "parse_mode", "reply_markup", - "input_message_content" - ] - def __init__( self, animation_url: str, 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 c21416f5..735a1e02 100644 --- a/pyrogram/client/types/inline_mode/inline_query_result_article.py +++ b/pyrogram/client/types/inline_mode/inline_query_result_article.py @@ -49,8 +49,6 @@ class InlineQueryResultArticle(InlineQueryResult): Inline keyboard attached to the message. """ - __slots__ = ["title", "url", "description", "thumb_url"] - def __init__( self, title: str, diff --git a/pyrogram/client/types/inline_mode/inline_query_result_photo.py b/pyrogram/client/types/inline_mode/inline_query_result_photo.py index 3442764e..ffcc21c0 100644 --- a/pyrogram/client/types/inline_mode/inline_query_result_photo.py +++ b/pyrogram/client/types/inline_mode/inline_query_result_photo.py @@ -68,11 +68,6 @@ class InlineQueryResultPhoto(InlineQueryResult): Content of the message to be sent instead of the photo. """ - __slots__ = [ - "photo_url", "thumb_url", "title", "description", "caption", "parse_mode", "reply_markup", - "input_message_content" - ] - def __init__( self, photo_url: str, 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 d6c67d56..dc70cbec 100644 --- a/pyrogram/client/types/input_media/input_media_animation.py +++ b/pyrogram/client/types/input_media/input_media_animation.py @@ -56,8 +56,6 @@ class InputMediaAnimation(InputMedia): Animation duration. """ - __slots__ = ["thumb", "width", "height", "duration"] - def __init__( self, media: str, diff --git a/pyrogram/client/types/input_media/input_media_audio.py b/pyrogram/client/types/input_media/input_media_audio.py index f01444a8..5ed670a6 100644 --- a/pyrogram/client/types/input_media/input_media_audio.py +++ b/pyrogram/client/types/input_media/input_media_audio.py @@ -58,8 +58,6 @@ class InputMediaAudio(InputMedia): Title of the audio """ - __slots__ = ["thumb", "duration", "performer", "title"] - def __init__( self, media: str, diff --git a/pyrogram/client/types/input_media/input_media_document.py b/pyrogram/client/types/input_media/input_media_document.py index af549c81..14756e02 100644 --- a/pyrogram/client/types/input_media/input_media_document.py +++ b/pyrogram/client/types/input_media/input_media_document.py @@ -47,8 +47,6 @@ class InputMediaDocument(InputMedia): Pass None to completely disable style parsing. """ - __slots__ = ["thumb"] - def __init__( self, media: str, diff --git a/pyrogram/client/types/input_media/input_media_photo.py b/pyrogram/client/types/input_media/input_media_photo.py index 30c53777..5e18cdd6 100644 --- a/pyrogram/client/types/input_media/input_media_photo.py +++ b/pyrogram/client/types/input_media/input_media_photo.py @@ -43,8 +43,6 @@ class InputMediaPhoto(InputMedia): Pass None to completely disable style parsing. """ - __slots__ = [] - def __init__( self, media: str, diff --git a/pyrogram/client/types/input_media/input_media_video.py b/pyrogram/client/types/input_media/input_media_video.py index 3500ff55..6b64caa8 100644 --- a/pyrogram/client/types/input_media/input_media_video.py +++ b/pyrogram/client/types/input_media/input_media_video.py @@ -61,8 +61,6 @@ 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, 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 b02c6b38..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,8 +31,6 @@ class InputMessageContent(Object): - :obj:`InputTextMessageContent` """ - __slots__ = [] - def __init__(self): super().__init__() 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 f90b7096..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,8 +41,6 @@ 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] = object, disable_web_page_preview: bool = None): super().__init__() 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 ed0088cd..7616a6ec 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -264,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, *, 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..2fc5caa1 100644 --- a/pyrogram/client/types/messages_and_media/sticker.py +++ b/pyrogram/client/types/messages_and_media/sticker.py @@ -65,10 +65,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, *, 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 396831cc..cfed441f 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -95,12 +95,6 @@ class Chat(Object): Information about the chat default 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, *, diff --git a/pyrogram/client/types/user_and_chats/chat_member.py b/pyrogram/client/types/user_and_chats/chat_member.py index 7451012c..42eb08f3 100644 --- a/pyrogram/client/types/user_and_chats/chat_member.py +++ b/pyrogram/client/types/user_and_chats/chat_member.py @@ -54,8 +54,6 @@ class ChatMember(Object): Information about the member permissions. """ - __slots__ = ["user", "status", "date", "is_member", "invited_by", "promoted_by", "restricted_by", "permissions"] - def __init__( self, *, diff --git a/pyrogram/client/types/user_and_chats/chat_permissions.py b/pyrogram/client/types/user_and_chats/chat_permissions.py index 84099955..551dc667 100644 --- a/pyrogram/client/types/user_and_chats/chat_permissions.py +++ b/pyrogram/client/types/user_and_chats/chat_permissions.py @@ -94,13 +94,6 @@ class ChatPermissions(Object): True, if polls can be sent, implies can_send_media_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" - ] - def __init__( self, *, diff --git a/pyrogram/client/types/user_and_chats/chat_photo.py b/pyrogram/client/types/user_and_chats/chat_photo.py index 1584a286..70e114af 100644 --- a/pyrogram/client/types/user_and_chats/chat_photo.py +++ b/pyrogram/client/types/user_and_chats/chat_photo.py @@ -35,8 +35,6 @@ class ChatPhoto(Object): Unique file identifier of big (640x640) chat photo. This file_id can be used only for photo download. """ - __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 781f85c8..10754170 100644 --- a/pyrogram/client/types/user_and_chats/chat_preview.py +++ b/pyrogram/client/types/user_and_chats/chat_preview.py @@ -45,8 +45,6 @@ class ChatPreview(Object): Preview of some of the chat members. """ - __slots__ = ["title", "type", "members_count", "photo", "members"] - def __init__( self, *, diff --git a/pyrogram/client/types/user_and_chats/dialog.py b/pyrogram/client/types/user_and_chats/dialog.py index a78e501b..471c4319 100644 --- a/pyrogram/client/types/user_and_chats/dialog.py +++ b/pyrogram/client/types/user_and_chats/dialog.py @@ -47,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, *, 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, *, From 64939e52892a860173c83fea7b28ec81833518b3 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 2 Aug 2019 01:15:01 +0200 Subject: [PATCH 191/202] Update Document file ids to make them compatible again with the Bot API Telegram changed something server side on 29 July, 2019 starting exactly at 04:00 AM UTC+1 (DST), logs say. Looks like Document file ids, just like Photo-like ids, are going to change as well after all, if we want to keep them compatible with the Bot API --- pyrogram/client/ext/utils.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/pyrogram/client/ext/utils.py b/pyrogram/client/ext/utils.py index fbee9ea8..cdc0684c 100644 --- a/pyrogram/client/ext/utils.py +++ b/pyrogram/client/ext/utils.py @@ -21,9 +21,8 @@ import struct from typing import List from typing import Union -from pyrogram.api.types import PeerUser, PeerChat, PeerChannel - import pyrogram +from pyrogram.api.types import PeerUser, PeerChat, PeerChannel from . import BaseClient from ...api import types @@ -32,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: @@ -51,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: From 091552e5d9db4161c48d19523c68c94725c7ee69 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 3 Aug 2019 18:05:57 +0200 Subject: [PATCH 192/202] [Bot API 4.4] Update stickers - Add is_animated field to Sticker - Remove send_animated_sticker (use send_sticker instead) - Default to 512x512 in case size is unknown (instead of 0x0) --- compiler/docs/compiler.py | 1 - pyrogram/client/methods/messages/__init__.py | 2 - .../methods/messages/send_animated_sticker.py | 144 ------------------ .../client/methods/messages/send_sticker.py | 2 +- .../types/messages_and_media/sticker.py | 10 +- 5 files changed, 9 insertions(+), 150 deletions(-) delete mode 100644 pyrogram/client/methods/messages/send_animated_sticker.py diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index cb3ad5a2..32674887 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -146,7 +146,6 @@ def pyrogram_api(): send_audio send_document send_sticker - send_animated_sticker send_video send_animation send_voice 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/send_animated_sticker.py b/pyrogram/client/methods/messages/send_animated_sticker.py deleted file mode 100644 index 8e57c527..00000000 --- a/pyrogram/client/methods/messages/send_animated_sticker.py +++ /dev/null @@ -1,144 +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 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. - 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: - :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. - - Example: - .. code-block:: python - - # Send animated sticker by uploading from local file - app.send_animated_sticker("me", "animated_sticker.tgs") - """ - 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_sticker.py b/pyrogram/client/methods/messages/send_sticker.py index a5fc7b26..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``): diff --git a/pyrogram/client/types/messages_and_media/sticker.py b/pyrogram/client/types/messages_and_media/sticker.py index 2fc5caa1..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. @@ -72,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, @@ -89,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 @@ -130,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, From c6f346f83dcc91e4f88ff1ac73104609aab639ab Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 3 Aug 2019 19:09:42 +0200 Subject: [PATCH 193/202] [Bot API 4.4] Update chat permissions - Move can_* permissions back to ChatMember objects - Rename restrict_chat to set_chat_permissions - Update restrict_chat_member to accept a single ChatPermissions arg. - Update ChatPermissions to be the same as the one on the Bot API --- compiler/docs/compiler.py | 2 +- pyrogram/client/methods/chats/__init__.py | 4 +- .../methods/chats/restrict_chat_member.py | 74 +++----- ...strict_chat.py => set_chat_permissions.py} | 81 ++++----- pyrogram/client/types/user_and_chats/chat.py | 2 +- .../types/user_and_chats/chat_member.py | 154 +++++++++++++++-- .../types/user_and_chats/chat_permissions.py | 162 ++++-------------- 7 files changed, 231 insertions(+), 248 deletions(-) rename pyrogram/client/methods/chats/{restrict_chat.py => set_chat_permissions.py} (56%) diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index 32674887..e515dc77 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -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,7 +200,6 @@ def pyrogram_api(): get_dialogs iter_dialogs get_dialogs_count - restrict_chat update_chat_username archive_chats unarchive_chats diff --git a/pyrogram/client/methods/chats/__init__.py b/pyrogram/client/methods/chats/__init__.py index a7fc2792..fddb48ce 100644 --- a/pyrogram/client/methods/chats/__init__.py +++ b/pyrogram/client/methods/chats/__init__.py @@ -38,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 @@ -71,7 +71,7 @@ class Chats( IterDialogs, IterChatMembers, UpdateChatUsername, - RestrictChat, + SetChatPermissions, GetDialogsCount, ArchiveChats, UnarchiveChats, diff --git a/pyrogram/client/methods/chats/restrict_chat_member.py b/pyrogram/client/methods/chats/restrict_chat_member.py index 60787b32..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,37 +44,14 @@ 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. @@ -90,14 +60,16 @@ class RestrictChatMember(BaseClient): from time import time - # Completely restrict chat member forever - app.restrict_chat_member(chat_id, user_id) + from pyrogram import ChatPermissions - # Chat member can't send messages for 24h - app.restrict_chat_member(chat_id, user_id, int(time.time() + 86400)) + # 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, can_send_messages=True) + app.restrict_chat_member(chat_id, user_id, ChatPermissions(can_send_messages=True)) """ send_messages = True send_media = True @@ -111,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/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 20acd5e1..f1ea61c7 100644 --- a/pyrogram/client/methods/chats/restrict_chat.py +++ b/pyrogram/client/methods/chats/set_chat_permissions.py @@ -20,54 +20,26 @@ 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. @@ -75,11 +47,20 @@ class RestrictChat(BaseClient): Example: .. code-block:: python - # Completely restrict chat - app.restrict_chat(chat_id) + from pyrogram import ChatPermissions - # All chat members can only send text messages - app.restrict_chat(chat_id, can_send_messages=True) + # 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 @@ -93,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/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py index cfed441f..4a032a26 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -92,7 +92,7 @@ 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. """ def __init__( diff --git a/pyrogram/client/types/user_and_chats/chat_member.py b/pyrogram/client/types/user_and_chats/chat_member.py index 42eb08f3..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,9 +51,66 @@ 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. + + 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__( @@ -60,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": @@ -91,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 ) @@ -107,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 551dc667..e4b0b0d0 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,154 +27,70 @@ 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. + + 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 - - 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=( - 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 - ) + def _parse(denied_permissions: types.ChatBannedRights) -> "ChatPermissions": + return ChatPermissions( + 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 + ) From 360cfaa9aa5ee4732a8390129e905d2e567bf65d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 3 Aug 2019 19:30:58 +0200 Subject: [PATCH 194/202] [Bot API 4.4] Update chat photos - Update ChatPhoto fields descriptions --- pyrogram/client/types/user_and_chats/chat_photo.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/types/user_and_chats/chat_photo.py b/pyrogram/client/types/user_and_chats/chat_photo.py index 70e114af..623aaca8 100644 --- a/pyrogram/client/types/user_and_chats/chat_photo.py +++ b/pyrogram/client/types/user_and_chats/chat_photo.py @@ -29,10 +29,12 @@ 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. """ def __init__( From c8c93b9ce617d46c90e12f9b86c3a3975485e49f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 3 Aug 2019 19:36:15 +0200 Subject: [PATCH 195/202] Update Main API and System Messages schemas --- compiler/api/source/main_api.tl | 5 +---- compiler/api/source/sys_msgs.tl | 9 +++++++++ 2 files changed, 10 insertions(+), 4 deletions(-) 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; From 937987a361179491161fc75fc0b8e56efc237c48 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 3 Aug 2019 19:40:45 +0200 Subject: [PATCH 196/202] Finally remove ports from older schemas and fix export_chat_invite_link --- pyrogram/client/methods/chats/export_chat_invite_link.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/pyrogram/client/methods/chats/export_chat_invite_link.py b/pyrogram/client/methods/chats/export_chat_invite_link.py index bf5d3a38..46886469 100644 --- a/pyrogram/client/methods/chats/export_chat_invite_link.py +++ b/pyrogram/client/methods/chats/export_chat_invite_link.py @@ -57,17 +57,11 @@ class ExportChatInviteLink(BaseClient): """ 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)) From 8a99f996ab95d99404e937f4f3c0370b76d267df Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 3 Aug 2019 19:50:12 +0200 Subject: [PATCH 197/202] Handle cases where denied_permissions might be None --- .../types/user_and_chats/chat_permissions.py | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/pyrogram/client/types/user_and_chats/chat_permissions.py b/pyrogram/client/types/user_and_chats/chat_permissions.py index e4b0b0d0..09c33089 100644 --- a/pyrogram/client/types/user_and_chats/chat_permissions.py +++ b/pyrogram/client/types/user_and_chats/chat_permissions.py @@ -81,16 +81,17 @@ class ChatPermissions(Object): @staticmethod def _parse(denied_permissions: types.ChatBannedRights) -> "ChatPermissions": - return ChatPermissions( - 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 - ) + if isinstance(denied_permissions, types.ChatBannedRights): + return ChatPermissions( + 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 + ) From 3dc2a81d7242e1ae40c2c25beaf93e87a1ea70dd Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 3 Aug 2019 19:54:14 +0200 Subject: [PATCH 198/202] Add Chat.description for basic chats --- pyrogram/client/types/user_and_chats/chat.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py index 4a032a26..546485f6 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -231,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) From eeda40002dd58abb541934610a0776883b3ad090 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 6 Aug 2019 00:08:31 +0200 Subject: [PATCH 199/202] Better explanation of workdir Client parameter --- docs/source/intro/setup.rst | 6 +++--- pyrogram/client/client.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/source/intro/setup.rst b/docs/source/intro/setup.rst index 6273b2b2..b3aa1836 100644 --- a/docs/source/intro/setup.rst +++ b/docs/source/intro/setup.rst @@ -29,9 +29,9 @@ Configuration Having the API key from the previous step in handy, we can now begin to configure a Pyrogram project. There are two ways to do so, and you can choose what fits better for you: -- First option (recommended): create a new ``config.ini`` file at the root of your working directory, copy-paste the - following and replace the **api_id** and **api_hash** values with your own. This is the preferred method because - allows you to keep your credentials out of your code without having to deal with how to load them: +- First option (recommended): create a new ``config.ini`` file next to your main script, copy-paste the following and + replace the **api_id** and **api_hash** values with your own. This is the preferred method because allows you to + keep your credentials out of your code without having to deal with how to load them: .. code-block:: ini diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 511dbf2a..7ac155c2 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -153,7 +153,7 @@ class Client(Methods, BaseClient): workdir (``str``, *optional*): Define a custom working directory. The working directory is the location in your filesystem - where Pyrogram will store your session files. Defaults to "." (current directory). + where Pyrogram will store your session files. Defaults to the parent directory of the main script. config_file (``str``, *optional*): Path of the configuration file. Defaults to ./config.ini From b1c63c18d67c9f5d0bec9a332a24968cb7f6e7ca Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 6 Aug 2019 00:11:51 +0200 Subject: [PATCH 200/202] Small document fix --- docs/source/faq.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/faq.rst b/docs/source/faq.rst index a05ff39c..5d4823c8 100644 --- a/docs/source/faq.rst +++ b/docs/source/faq.rst @@ -250,8 +250,8 @@ The error in question is ``[400 PEER_ID_INVALID]``, and could mean several thing 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). +for usernames, meeting them in a common group, have their phone contacts saved or getting a message mentioning them, +either a forward or a mention in the message text. UnicodeEncodeError: '' codec can't encode … ----------------------------------------------------- From 82e0087def2a06a2ccf3f84e69640ea6e885419c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 6 Aug 2019 01:02:41 +0200 Subject: [PATCH 201/202] Always cast inline query ids to string --- pyrogram/client/types/inline_mode/inline_query_result.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/types/inline_mode/inline_query_result.py b/pyrogram/client/types/inline_mode/inline_query_result.py index ef26eacc..d44a5ee2 100644 --- a/pyrogram/client/types/inline_mode/inline_query_result.py +++ b/pyrogram/client/types/inline_mode/inline_query_result.py @@ -63,7 +63,7 @@ class InlineQueryResult(Object): super().__init__() self.type = type - self.id = str(uuid4()) if id is None else id + self.id = str(uuid4()) if id is None else str(id) self.input_message_content = input_message_content self.reply_markup = reply_markup From 2aefbfd531d05ab70b5dd48faa18e69233648bff Mon Sep 17 00:00:00 2001 From: Mario A Date: Wed, 7 Aug 2019 13:48:21 +0200 Subject: [PATCH 202/202] Add better support for nested entities (both for HTML and Markdown) (#297) * Added better support for nested entities, both for HTML and Markdown * Tiny style fix * Make use of pre-defined constants --- pyrogram/client/parser/html.py | 55 ++++++++++++-------------- pyrogram/client/parser/markdown.py | 62 +++++++++++++++--------------- 2 files changed, 56 insertions(+), 61 deletions(-) diff --git a/pyrogram/client/parser/html.py b/pyrogram/client/parser/html.py index 41efe3b3..82499cb3 100644 --- a/pyrogram/client/parser/html.py +++ b/pyrogram/client/parser/html.py @@ -147,43 +147,38 @@ class HTML: @staticmethod def unparse(text: str, entities: list): text = utils.add_surrogates(text) - copy = text + + entities_offsets = [] for entity in entities: + entity_type = entity.type start = entity.offset end = start + entity.length - type = entity.type - - url = entity.url - user = entity.user - - sub = copy[start:end] - - if type == "bold": - style = "b" - elif type == "italic": - style = "i" - elif type == "underline": - style = "u" - elif type == "strike": - style = "s" - elif type == "code": - style = "code" - elif type == "pre": - style = "pre" - elif type == "blockquote": - style = "blockquote" - elif type == "text_link": - text = text[:start] + text[start:].replace(sub, '{}'.format(url, sub), 1) - continue - elif type == "text_mention": - text = text[:start] + text[start:].replace( - sub, '{}'.format(user.id, sub), 1) - continue + if entity_type in ("bold", "italic", "underline", "strike"): + start_tag = "<{}>".format(entity_type[0]) + end_tag = "".format(entity_type[0]) + elif entity_type in ("code", "pre", "blockquote"): + start_tag = "<{}>".format(entity_type) + end_tag = "".format(entity_type) + elif entity_type == "text_link": + url = entity.url + start_tag = ''.format(url) + end_tag = "" + elif entity_type == "text_mention": + user = entity.user + start_tag = ''.format(user.id) + end_tag = "" else: continue - text = text[:start] + text[start:].replace(sub, "<{0}>{1}".format(style, sub), 1) + entities_offsets.append((start_tag, start,)) + entities_offsets.append((end_tag, end,)) + + # sorting by offset (desc) + entities_offsets.sort(key=lambda x: -x[1]) + + for entity, offset in entities_offsets: + text = text[:offset] + entity + text[offset:] return utils.remove_surrogates(text) diff --git a/pyrogram/client/parser/markdown.py b/pyrogram/client/parser/markdown.py index 74d06e97..1319f6df 100644 --- a/pyrogram/client/parser/markdown.py +++ b/pyrogram/client/parser/markdown.py @@ -107,44 +107,44 @@ class Markdown: @staticmethod def unparse(text: str, entities: list): text = utils.add_surrogates(text) - copy = text + + entities_offsets = [] for entity in entities: + entity_type = entity.type start = entity.offset end = start + entity.length - type = entity.type - - url = entity.url - user = entity.user - - sub = copy[start:end] - - if type == "bold": - style = BOLD_DELIM - elif type == "italic": - style = ITALIC_DELIM - elif type == "underline": - style = UNDERLINE_DELIM - elif type == "strike": - style = STRIKE_DELIM - elif type == "code": - style = CODE_DELIM - elif type == "pre": - style = PRE_DELIM - # TODO: Blockquote for MD - # elif type == "blockquote": - # style = ... - elif type == "text_link": - text = text[:start] + text[start:].replace(sub, '[{1}]({0})'.format(url, sub), 1) - continue - elif type == "text_mention": - text = text[:start] + text[start:].replace( - sub, '[{1}](tg://user?id={0})'.format(user.id, sub), 1) - continue + if entity_type == "bold": + start_tag = end_tag = BOLD_DELIM + elif entity_type == "italic": + start_tag = end_tag = ITALIC_DELIM + elif entity_type == "underline": + start_tag = end_tag = UNDERLINE_DELIM + elif entity_type == "strike": + start_tag = end_tag = STRIKE_DELIM + elif entity_type == "code": + start_tag = end_tag = CODE_DELIM + elif entity_type in ("pre", "blockquote"): + start_tag = end_tag = PRE_DELIM + elif entity_type == "text_link": + url = entity.url + start_tag = "[" + end_tag = "]({})".format(url) + elif entity_type == "text_mention": + user = entity.user + start_tag = "[" + end_tag = "](tg://user?id={})".format(user.id) else: continue - text = text[:start] + text[start:].replace(sub, "{0}{1}{0}".format(style, sub), 1) + entities_offsets.append((start_tag, start,)) + entities_offsets.append((end_tag, end,)) + + # sorting by offset (desc) + entities_offsets.sort(key=lambda x: -x[1]) + + for entity, offset in entities_offsets: + text = text[:offset] + entity + text[offset:] return utils.remove_surrogates(text)