2
0
mirror of https://github.com/pyrogram/pyrogram synced 2025-09-18 13:55:15 +00:00

Merge branch 'develop' into session_storage

# Conflicts:
#	pyrogram/client/client.py
#	pyrogram/client/ext/base_client.py
#	pyrogram/client/ext/syncer.py
#	pyrogram/client/methods/contacts/get_contacts.py
This commit is contained in:
Dan
2019-06-15 23:52:34 +02:00
313 changed files with 9183 additions and 4943 deletions

View File

@@ -23,13 +23,11 @@ import mimetypes
import os
import re
import shutil
import struct
import tempfile
import threading
import time
import warnings
from configparser import ConfigParser
from datetime import datetime
from hashlib import sha256, md5
from importlib import import_module
from pathlib import Path
@@ -38,7 +36,7 @@ from threading import Thread
from typing import Union, List, Type
from pyrogram.api import functions, types
from pyrogram.api.core import Object
from pyrogram.api.core import TLObject
from pyrogram.client.handlers import DisconnectHandler
from pyrogram.client.handlers.handler import Handler
from pyrogram.client.methods.password.utils import compute_check
@@ -48,7 +46,7 @@ from pyrogram.errors import (
PhoneNumberUnoccupied, PhoneCodeInvalid, PhoneCodeHashEmpty,
PhoneCodeExpired, PhoneCodeEmpty, SessionPasswordNeeded,
PasswordHashInvalid, FloodWait, PeerIdInvalid, FirstnameInvalid, PhoneNumberBanned,
VolumeLocNotFound, UserMigrate, FileIdInvalid, ChannelPrivate, PhoneNumberOccupied,
VolumeLocNotFound, UserMigrate, ChannelPrivate, PhoneNumberOccupied,
PasswordRecoveryNa, PasswordEmpty
)
from pyrogram.session import Auth, Session
@@ -63,27 +61,23 @@ log = logging.getLogger(__name__)
class Client(Methods, BaseClient):
"""This class represents a Client, the main mean for interacting with Telegram.
It exposes bot-like methods for an easy access to the API as well as a simple way to
invoke every single Telegram API method available.
"""Pyrogram Client, the main means for interacting with Telegram.
Args:
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.
Note for bots: You can pass a bot token here, but this usage will be deprecated in next releases.
Use *bot_token* instead.
api_id (``int``, *optional*):
The *api_id* part of your Telegram API Key, as integer. E.g.: 12345
This is an alternative way to pass it if you don't want to use the *config.ini* file.
api_hash (``str``, *optional*):
The *api_hash* part of your Telegram API Key, as string. E.g.: "0123456789abcdef0123456789abcdef"
The *api_hash* part of your Telegram API Key, as string. E.g.: "0123456789abcdef0123456789abcdef".
This is an alternative way to pass it if you don't want to use the *config.ini* file.
app_version (``str``, *optional*):
Application version. Defaults to "Pyrogram \U0001f525 vX.Y.Z"
Application version. Defaults to "Pyrogram X.Y.Z"
This is an alternative way to set it if you don't want to use the *config.ini* file.
device_model (``str``, *optional*):
@@ -109,10 +103,14 @@ class Client(Methods, BaseClient):
This is an alternative way to setup a proxy if you don't want to use the *config.ini* file.
test_mode (``bool``, *optional*):
Enable or disable log-in to testing servers. Defaults to False.
Enable or disable login to the test servers. Defaults to False.
Only applicable for new sessions and will be ignored in case previously
created sessions are loaded.
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.
phone_number (``str`` | ``callable``, *optional*):
Pass your phone number as string (with your Country Code prefix included) to avoid entering it manually.
Or pass a callback function which accepts no arguments and must return the correct phone number as string
@@ -146,10 +144,6 @@ 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.
@@ -175,7 +169,7 @@ class Client(Methods, BaseClient):
Defaults to False (updates enabled and always received).
takeout (``bool``, *optional*):
Pass True to let the client use a takeout session instead of a normal one, implies no_updates.
Pass True to let the client use a takeout session instead of a normal one, implies *no_updates=True*.
Useful for exporting your Telegram data. Methods invoked inside a takeout session (such as get_history,
download_media, ...) are less prone to throw FloodWait exceptions.
Only available for users, bots will ignore this parameter.
@@ -196,12 +190,12 @@ class Client(Methods, BaseClient):
ipv6: bool = False,
proxy: dict = None,
test_mode: bool = False,
bot_token: str = None,
phone_number: str = None,
phone_code: Union[str, callable] = None,
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,
@@ -239,12 +233,12 @@ class Client(Methods, BaseClient):
# TODO: Make code consistent, use underscore for private/protected fields
self._proxy = proxy
self.session_storage.test_mode = test_mode
self.bot_token = bot_token
self.phone_number = phone_number
self.phone_code = phone_code
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
@@ -279,12 +273,11 @@ class Client(Methods, BaseClient):
self._proxy.update(value)
def start(self):
"""Use this method to start the Client after creating it.
Requires no parameters.
"""Start the Client.
Raises:
:class:`RPCError <pyrogram.RPCError>` in case of a Telegram RPC error.
``ConnectionError`` in case you try to start an already started Client.
RPCError: In case of a Telegram RPC error.
ConnectionError: In case you try to start an already started Client.
"""
if self.is_started:
raise ConnectionError("Client has already been started")
@@ -297,7 +290,7 @@ class Client(Methods, BaseClient):
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.ml/start/Setup#bot-authorization\n')
'More info: https://docs.pyrogram.org/intro/auth#bot-authorization\n')
self.load_config()
self.load_session()
@@ -336,7 +329,7 @@ class Client(Methods, BaseClient):
self.get_initial_dialogs()
self.get_contacts()
else:
self.send(functions.messages.GetPinnedDialogs())
self.send(functions.messages.GetPinnedDialogs(folder_id=0))
self.get_initial_dialogs_chunk()
else:
self.send(functions.updates.GetState())
@@ -373,11 +366,10 @@ class Client(Methods, BaseClient):
return self
def stop(self):
"""Use this method to manually stop the Client.
Requires no parameters.
"""Stop the Client.
Raises:
``ConnectionError`` in case you try to stop an already stopped Client.
ConnectionError: In case you try to stop an already stopped Client.
"""
if not self.is_started:
raise ConnectionError("Client is already stopped")
@@ -416,25 +408,34 @@ class Client(Methods, BaseClient):
return self
def restart(self):
"""Use this method to restart the Client.
Requires no parameters.
"""Restart the Client.
Raises:
``ConnectionError`` in case you try to restart a stopped Client.
ConnectionError: In case you try to restart a stopped Client.
"""
self.stop()
self.start()
def idle(self, stop_signals: tuple = (SIGINT, SIGTERM, SIGABRT)):
"""Blocks the program execution until one of the signals are received,
then gently stop the Client by closing the underlying connection.
"""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.
Args:
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.
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.
Parameters:
stop_signals (``tuple``, *optional*):
Iterable containing signals the signal handler will listen to.
Defaults to (SIGINT, SIGTERM, SIGABRT).
"""
# TODO: Maybe make this method static and don't automatically stop
def signal_handler(*args):
self.is_idle = False
@@ -449,23 +450,26 @@ class Client(Methods, BaseClient):
self.stop()
def run(self):
"""Use this method to automatically start and idle a Client.
Requires no parameters.
"""Start the Client and automatically idle the main script.
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.
Raises:
:class:`RPCError <pyrogram.RPCError>` in case of a Telegram RPC error.
RPCError: In case of a Telegram RPC error.
"""
self.start()
self.idle()
def add_handler(self, handler: Handler, group: int = 0):
"""Use this method to register an update handler.
"""Register an update handler.
You can register multiple handlers, but at most one handler within a group
will be used for a single update. To handle the same update more than once, register
your handler using a different group id (lower group id == higher priority).
Args:
Parameters:
handler (``Handler``):
The handler to be registered.
@@ -473,7 +477,7 @@ class Client(Methods, BaseClient):
The group identifier, defaults to 0.
Returns:
A tuple of (handler, group)
``tuple``: A tuple consisting of (handler, group).
"""
if isinstance(handler, DisconnectHandler):
self.disconnect_handler = handler.callback
@@ -483,13 +487,13 @@ class Client(Methods, BaseClient):
return handler, group
def remove_handler(self, handler: Handler, group: int = 0):
"""Removes a previously-added update handler.
"""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:`add_handler` method, a tuple of (handler, group), and
the return value of the :meth:`~Client.add_handler` method, a tuple of (handler, group), and
pass it directly.
Args:
Parameters:
handler (``Handler``):
The handler to be removed.
@@ -502,7 +506,7 @@ class Client(Methods, BaseClient):
self.dispatcher.remove_handler(handler, group)
def stop_transmission(self):
"""Use this method to stop downloading or uploading a file.
"""Stop downloading or uploading a file.
Must be called inside a progress callback function.
"""
raise Client.StopTransmission
@@ -771,96 +775,52 @@ class Client(Methods, BaseClient):
print("Logged in successfully as {}".format(r.user.first_name))
def fetch_peers(self, entities: List[Union[types.User,
types.Chat, types.ChatForbidden,
types.Channel, types.ChannelForbidden]]):
def fetch_peers(
self,
entities: List[
Union[
types.User,
types.Chat, types.ChatForbidden,
types.Channel, types.ChannelForbidden
]
]
) -> bool:
is_min = False
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)
return is_min
def download_worker(self):
name = threading.current_thread().name
log.debug("{} started".format(name))
while True:
media = self.download_queue.get()
packet = self.download_queue.get()
if media is None:
if packet is None:
break
temp_file_path = ""
final_file_path = ""
try:
media, file_name, done, progress, progress_args, path = media
file_id = media.file_id
size = media.file_size
directory, file_name = os.path.split(file_name)
directory = directory or "downloads"
try:
decoded = utils.decode(file_id)
fmt = "<iiqqqqi" if len(decoded) > 24 else "<iiqq"
unpacked = struct.unpack(fmt, decoded)
except (AssertionError, binascii.Error, struct.error):
raise FileIdInvalid from None
else:
media_type = unpacked[0]
dc_id = unpacked[1]
id = unpacked[2]
access_hash = unpacked[3]
volume_id = None
secret = None
local_id = None
if len(decoded) > 24:
volume_id = unpacked[4]
secret = unpacked[5]
local_id = unpacked[6]
media_type_str = Client.MEDIA_TYPE_ID.get(media_type, None)
if media_type_str is None:
raise FileIdInvalid("Unknown media type: {}".format(unpacked[0]))
file_name = file_name or getattr(media, "file_name", None)
if not file_name:
if media_type == 3:
extension = ".ogg"
elif media_type in (4, 10, 13):
extension = mimetypes.guess_extension(media.mime_type) or ".mp4"
elif media_type == 5:
extension = mimetypes.guess_extension(media.mime_type) or ".unknown"
elif media_type == 8:
extension = ".webp"
elif media_type == 9:
extension = mimetypes.guess_extension(media.mime_type) or ".mp3"
elif media_type in (0, 1, 2):
extension = ".jpg"
else:
continue
file_name = "{}_{}_{}{}".format(
media_type_str,
datetime.fromtimestamp(
getattr(media, "date", None) 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(
dc_id=dc_id,
id=id,
access_hash=access_hash,
volume_id=volume_id,
local_id=local_id,
secret=secret,
size=size,
media_type=data.media_type,
dc_id=data.dc_id,
document_id=data.document_id,
access_hash=data.access_hash,
thumb_size=data.thumb_size,
peer_id=data.peer_id,
volume_id=data.volume_id,
local_id=data.local_id,
file_size=data.file_size,
is_big=data.is_big,
progress=progress,
progress_args=progress_args
)
@@ -898,8 +858,10 @@ class Client(Methods, BaseClient):
try:
if isinstance(updates, (types.Update, types.UpdatesCombined)):
self.fetch_peers(updates.users)
self.fetch_peers(updates.chats)
is_min = self.fetch_peers(updates.users) or self.fetch_peers(updates.chats)
users = {u.id: u for u in updates.users}
chats = {c.id: c for c in updates.chats}
for update in updates.updates:
channel_id = getattr(
@@ -916,7 +878,7 @@ class Client(Methods, BaseClient):
if isinstance(update, types.UpdateChannelTooLong):
log.warning(update)
if isinstance(update, types.UpdateNewChannelMessage):
if isinstance(update, types.UpdateNewChannelMessage) and is_min:
message = update.message
if not isinstance(message, types.MessageEmpty):
@@ -938,22 +900,10 @@ class Client(Methods, BaseClient):
pass
else:
if not isinstance(diff, types.updates.ChannelDifferenceEmpty):
updates.users += diff.users
updates.chats += diff.chats
users.update({u.id: u for u in diff.users})
chats.update({c.id: c for c in diff.chats})
if channel_id and pts:
if channel_id not in self.channels_pts:
self.channels_pts[channel_id] = []
if pts in self.channels_pts[channel_id]:
continue
self.channels_pts[channel_id].append(pts)
if len(self.channels_pts[channel_id]) > 50:
self.channels_pts[channel_id] = self.channels_pts[channel_id][25:]
self.dispatcher.updates_queue.put((update, updates.users, updates.chats))
self.dispatcher.updates_queue.put((update, users, chats))
elif isinstance(updates, (types.UpdateShortMessage, types.UpdateShortChatMessage)):
diff = self.send(
functions.updates.GetDifference(
@@ -970,13 +920,13 @@ class Client(Methods, BaseClient):
pts=updates.pts,
pts_count=updates.pts_count
),
diff.users,
diff.chats
{u.id: u for u in diff.users},
{c.id: c for c in diff.chats}
))
else:
self.dispatcher.updates_queue.put((diff.other_updates[0], [], []))
self.dispatcher.updates_queue.put((diff.other_updates[0], {}, {}))
elif isinstance(updates, types.UpdateShort):
self.dispatcher.updates_queue.put((updates.update, [], []))
self.dispatcher.updates_queue.put((updates.update, {}, {}))
elif isinstance(updates, types.UpdatesTooLong):
log.warning(updates)
except Exception as e:
@@ -984,18 +934,21 @@ class Client(Methods, BaseClient):
log.debug("{} stopped".format(name))
def send(self,
data: Object,
retries: int = Session.MAX_RETRIES,
timeout: float = Session.WAIT_TIMEOUT):
"""Use this method to send Raw Function queries.
def send(self, data: TLObject, retries: int = Session.MAX_RETRIES, timeout: float = Session.WAIT_TIMEOUT):
"""Send raw Telegram queries.
This method makes possible to manually call every single Telegram API method in a low-level manner.
This method makes it possible to manually call every single Telegram API method in a low-level manner.
Available functions are listed in the :obj:`functions <pyrogram.api.functions>` package and may accept compound
data types from :obj:`types <pyrogram.api.types>` as well as bare types such as ``int``, ``str``, etc...
Args:
data (``Object``):
.. note::
This is a utility method intended to be used **only** when working with raw
:obj:`functions <pyrogram.api.functions>` (i.e: a Telegram API method you wish to use which is not
available yet in the Client class as an easy-to-use method).
Parameters:
data (``RawFunction``):
The API Schema function filled with proper arguments.
retries (``int``):
@@ -1004,8 +957,11 @@ class Client(Methods, BaseClient):
timeout (``float``):
Timeout in seconds.
Returns:
``RawType``: The raw type response generated by the query.
Raises:
:class:`RPCError <pyrogram.RPCError>` in case of a Telegram RPC error.
RPCError: In case of a Telegram RPC error.
"""
if not self.is_started:
raise ConnectionError("Client has not been started")
@@ -1036,7 +992,7 @@ class Client(Methods, BaseClient):
else:
raise AttributeError(
"No API Key found. "
"More info: https://docs.pyrogram.ml/start/ProjectSetup#configuration"
"More info: https://docs.pyrogram.org/intro/setup#configuration"
)
for option in ["app_version", "device_model", "system_version", "lang_code"]:
@@ -1065,29 +1021,34 @@ class Client(Methods, BaseClient):
self._proxy["password"] = parser.get("proxy", "password", fallback=None) or None
if self.plugins:
self.plugins["enabled"] = bool(self.plugins.get("enabled", True))
self.plugins["include"] = "\n".join(self.plugins.get("include", [])) or None
self.plugins["exclude"] = "\n".join(self.plugins.get("exclude", [])) or None
self.plugins = {
"enabled": bool(self.plugins.get("enabled", True)),
"root": self.plugins.get("root", None),
"include": self.plugins.get("include", []),
"exclude": self.plugins.get("exclude", [])
}
else:
try:
section = parser["plugins"]
self.plugins = {
"enabled": section.getboolean("enabled", True),
"root": section.get("root"),
"include": section.get("include") or None,
"exclude": section.get("exclude") or None
"root": section.get("root", None),
"include": section.get("include", []),
"exclude": section.get("exclude", [])
}
except KeyError:
self.plugins = {}
if self.plugins:
for option in ["include", "exclude"]:
if self.plugins[option] is not None:
self.plugins[option] = [
(i.split()[0], i.split()[1:] or None)
for i in self.plugins[option].strip().split("\n")
]
include = self.plugins["include"]
exclude = self.plugins["exclude"]
if include:
self.plugins["include"] = include.strip().split("\n")
if exclude:
self.plugins["exclude"] = exclude.strip().split("\n")
except KeyError:
self.plugins = None
def load_session(self):
try:
@@ -1098,14 +1059,26 @@ class Client(Methods, BaseClient):
self.ipv6, self._proxy).create()
def load_plugins(self):
if self.plugins.get("enabled", False):
root = self.plugins["root"]
include = self.plugins["include"]
exclude = self.plugins["exclude"]
if self.plugins:
plugins = self.plugins.copy()
for option in ["include", "exclude"]:
if plugins[option]:
plugins[option] = [
(i.split()[0], i.split()[1:] or None)
for i in self.plugins[option]
]
else:
return
if plugins.get("enabled", False):
root = plugins["root"]
include = plugins["include"]
exclude = plugins["exclude"]
count = 0
if include is None:
if not include:
for path in sorted(Path(root).rglob("*.py")):
module_path = '.'.join(path.parent.parts + (path.stem,))
module = import_module(module_path)
@@ -1118,8 +1091,8 @@ class Client(Methods, BaseClient):
if isinstance(handler, Handler) and isinstance(group, int):
self.add_handler(handler, group)
log.info('[LOAD] {}("{}") in group {} from "{}"'.format(
type(handler).__name__, name, group, module_path))
log.info('[{}] [LOAD] {}("{}") in group {} from "{}"'.format(
self.session_name, type(handler).__name__, name, group, module_path))
count += 1
except Exception:
@@ -1132,11 +1105,13 @@ class Client(Methods, BaseClient):
try:
module = import_module(module_path)
except ImportError:
log.warning('[LOAD] Ignoring non-existent module "{}"'.format(module_path))
log.warning('[{}] [LOAD] Ignoring non-existent module "{}"'.format(
self.session_name, module_path))
continue
if "__path__" in dir(module):
log.warning('[LOAD] Ignoring namespace "{}"'.format(module_path))
log.warning('[{}] [LOAD] Ignoring namespace "{}"'.format(
self.session_name, module_path))
continue
if handlers is None:
@@ -1151,16 +1126,16 @@ class Client(Methods, BaseClient):
if isinstance(handler, Handler) and isinstance(group, int):
self.add_handler(handler, group)
log.info('[LOAD] {}("{}") in group {} from "{}"'.format(
type(handler).__name__, name, group, module_path))
log.info('[{}] [LOAD] {}("{}") in group {} from "{}"'.format(
self.session_name, type(handler).__name__, name, group, module_path))
count += 1
except Exception:
if warn_non_existent_functions:
log.warning('[LOAD] Ignoring non-existent function "{}" from "{}"'.format(
name, module_path))
log.warning('[{}] [LOAD] Ignoring non-existent function "{}" from "{}"'.format(
self.session_name, name, module_path))
if exclude is not None:
if exclude:
for path, handlers in exclude:
module_path = root + "." + path
warn_non_existent_functions = True
@@ -1168,11 +1143,13 @@ class Client(Methods, BaseClient):
try:
module = import_module(module_path)
except ImportError:
log.warning('[UNLOAD] Ignoring non-existent module "{}"'.format(module_path))
log.warning('[{}] [UNLOAD] Ignoring non-existent module "{}"'.format(
self.session_name, module_path))
continue
if "__path__" in dir(module):
log.warning('[UNLOAD] Ignoring namespace "{}"'.format(module_path))
log.warning('[{}] [UNLOAD] Ignoring namespace "{}"'.format(
self.session_name, module_path))
continue
if handlers is None:
@@ -1187,25 +1164,26 @@ class Client(Methods, BaseClient):
if isinstance(handler, Handler) and isinstance(group, int):
self.remove_handler(handler, group)
log.info('[UNLOAD] {}("{}") from group {} in "{}"'.format(
type(handler).__name__, name, group, module_path))
log.info('[{}] [UNLOAD] {}("{}") from group {} in "{}"'.format(
self.session_name, type(handler).__name__, name, group, module_path))
count -= 1
except Exception:
if warn_non_existent_functions:
log.warning('[UNLOAD] Ignoring non-existent function "{}" from "{}"'.format(
name, module_path))
log.warning('[{}] [UNLOAD] Ignoring non-existent function "{}" from "{}"'.format(
self.session_name, name, module_path))
if count > 0:
log.warning('Successfully loaded {} plugin{} from "{}"'.format(count, "s" if count > 1 else "", root))
log.warning('[{}] Successfully loaded {} plugin{} from "{}"'.format(
self.session_name, count, "s" if count > 1 else "", root))
else:
log.warning('No plugin loaded from "{}"'.format(root))
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):
def get_initial_dialogs_chunk(self, offset_date: int = 0):
while True:
try:
r = self.send(
@@ -1226,7 +1204,7 @@ class Client(Methods, BaseClient):
return r
def get_initial_dialogs(self):
self.send(functions.messages.GetPinnedDialogs())
self.send(functions.messages.GetPinnedDialogs(folder_id=0))
dialogs = self.get_initial_dialogs_chunk()
offset_date = utils.get_offset_date(dialogs)
@@ -1237,25 +1215,27 @@ class Client(Methods, BaseClient):
self.get_initial_dialogs_chunk()
def resolve_peer(self,
peer_id: Union[int, str]):
"""Use this method to get the InputPeer of a known peer_id.
def resolve_peer(self, peer_id: Union[int, str]):
"""Get the InputPeer of a known peer id.
Useful whenever an InputPeer type is required.
This is a utility method intended to be used **only** when working with Raw Functions (i.e: a Telegram API
method you wish to use which is not available yet in the Client class as an easy-to-use method), whenever an
InputPeer type is required.
.. note::
Args:
This is a utility method intended to be used **only** when working with raw
:obj:`functions <pyrogram.api.functions>` (i.e: a Telegram API method you wish to use which is not
available yet in the Client class as an easy-to-use method).
Parameters:
peer_id (``int`` | ``str``):
The peer id you want to extract the InputPeer from.
Can be a direct id (int), a username (str) or a phone number (str).
Returns:
On success, the resolved peer id is returned in form of an InputPeer object.
``InputPeer``: On success, the resolved peer id is returned in form of an InputPeer object.
Raises:
:class:`RPCError <pyrogram.RPCError>` in case of a Telegram RPC error.
``KeyError`` in case the peer doesn't exist in the internal database.
RPCError: In case of a Telegram RPC error.
KeyError: In case the peer doesn't exist in the internal database.
"""
try:
return self.session_storage.get_peer_by_id(peer_id)
@@ -1312,19 +1292,24 @@ class Client(Methods, BaseClient):
except KeyError:
raise PeerIdInvalid
def save_file(self,
path: str,
file_id: int = None,
file_part: int = 0,
progress: callable = None,
progress_args: tuple = ()):
"""Use this method to upload a file onto Telegram servers, without actually sending the message to anyone.
def save_file(
self,
path: str,
file_id: int = None,
file_part: int = 0,
progress: callable = None,
progress_args: tuple = ()
):
"""Upload a file onto Telegram servers, without actually sending the message to anyone.
Useful whenever an InputFile type is required.
This is a utility method intended to be used **only** when working with Raw Functions (i.e: a Telegram API
method you wish to use which is not available yet in the Client class as an easy-to-use method), whenever an
InputFile type is required.
.. note::
Args:
This is a utility method intended to be used **only** when working with raw
:obj:`functions <pyrogram.api.functions>` (i.e: a Telegram API method you wish to use which is not
available yet in the Client class as an easy-to-use method).
Parameters:
path (``str``):
The path of the file you want to upload that exists on your local machine.
@@ -1344,7 +1329,7 @@ class Client(Methods, BaseClient):
a chat_id and a message_id in order to edit a message with the updated progress.
Other Parameters:
client (:obj:`Client <pyrogram.Client>`):
client (:obj:`Client`):
The Client itself, useful when you want to call other API methods inside the callback function.
current (``int``):
@@ -1358,10 +1343,10 @@ class Client(Methods, BaseClient):
You can either keep *\*args* or add every single extra argument in your function signature.
Returns:
On success, the uploaded file is returned in form of an InputFile object.
``InputFile``: On success, the uploaded file is returned in form of an InputFile object.
Raises:
:class:`RPCError <pyrogram.RPCError>` in case of a Telegram RPC error.
RPCError: In case of a Telegram RPC error.
"""
part_size = 512 * 1024
file_size = os.path.getsize(path)
@@ -1445,16 +1430,21 @@ class Client(Methods, BaseClient):
finally:
session.stop()
def get_file(self,
dc_id: int,
id: int = None,
access_hash: int = None,
volume_id: int = None,
local_id: int = None,
secret: int = None,
size: int = None,
progress: callable = None,
progress_args: tuple = ()) -> str:
def get_file(
self,
media_type: int,
dc_id: int,
document_id: int,
access_hash: int,
thumb_size: str,
peer_id: int,
volume_id: int,
local_id: int,
file_size: int,
is_big: bool,
progress: callable,
progress_args: tuple = ()
) -> str:
with self.media_sessions_lock:
session = self.media_sessions.get(dc_id, None)
@@ -1495,18 +1485,33 @@ class Client(Methods, BaseClient):
self.media_sessions[dc_id] = session
if volume_id: # Photos are accessed by volume_id, local_id, secret
location = types.InputFileLocation(
if media_type == 1:
location = types.InputPeerPhotoFileLocation(
peer=self.resolve_peer(peer_id),
volume_id=volume_id,
local_id=local_id,
secret=secret,
file_reference=b""
big=is_big or None
)
else: # Any other file can be more easily accessed by id and access_hash
location = types.InputDocumentFileLocation(
id=id,
elif media_type in (0, 2):
location = types.InputPhotoFileLocation(
id=document_id,
access_hash=access_hash,
file_reference=b""
file_reference=b"",
thumb_size=thumb_size
)
elif media_type == 14:
location = types.InputDocumentFileLocation(
id=document_id,
access_hash=access_hash,
file_reference=b"",
thumb_size=thumb_size
)
else:
location = types.InputDocumentFileLocation(
id=document_id,
access_hash=access_hash,
file_reference=b"",
thumb_size=""
)
limit = 1024 * 1024
@@ -1537,7 +1542,14 @@ class Client(Methods, BaseClient):
offset += limit
if progress:
progress(self, min(offset, size) if size != 0 else offset, size, *progress_args)
progress(
self,
min(offset, file_size)
if file_size != 0
else offset,
file_size,
*progress_args
)
r = session.send(
functions.upload.GetFile(
@@ -1619,7 +1631,14 @@ class Client(Methods, BaseClient):
offset += limit
if progress:
progress(self, min(offset, size) if size != 0 else offset, size, *progress_args)
progress(
self,
min(offset, file_size)
if file_size != 0
else offset,
file_size,
*progress_args
)
if len(chunk) < limit:
break
@@ -1637,3 +1656,13 @@ class Client(Methods, BaseClient):
return ""
else:
return file_name
def guess_mime_type(self, filename: str):
extension = os.path.splitext(filename)[1]
return self.extensions_to_mime_types.get(extension)
def guess_extension(self, mime_type: str):
extensions = self.mime_types_to_extensions.get(mime_type)
if extensions:
return extensions.split(" ")[0]