mirror of
https://github.com/pyrogram/pyrogram
synced 2025-09-03 07:45:14 +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:
@@ -24,11 +24,9 @@ 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.12.0"
|
||||
__version__ = "0.15.0-develop"
|
||||
__license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
|
||||
__copyright__ = "Copyright (C) 2017-2019 Dan Tès <https://github.com/delivrance>".replace(
|
||||
"\xe8", "e" if sys.getfilesystemencoding() != "utf-8" else "\xe8"
|
||||
)
|
||||
__copyright__ = "Copyright (C) 2017-2019 Dan <https://github.com/delivrance>"
|
||||
|
||||
from .errors import RPCError
|
||||
from .client import *
|
||||
|
@@ -19,8 +19,8 @@
|
||||
from importlib import import_module
|
||||
|
||||
from .all import objects
|
||||
from .core.object import Object
|
||||
from .core.tl_object import TLObject
|
||||
|
||||
for k, v in objects.items():
|
||||
path, name = v.rsplit(".", 1)
|
||||
Object.all[k] = getattr(import_module(path), name)
|
||||
TLObject.all[k] = getattr(import_module(path), name)
|
||||
|
@@ -19,10 +19,11 @@
|
||||
from .future_salt import FutureSalt
|
||||
from .future_salts import FutureSalts
|
||||
from .gzip_packed import GzipPacked
|
||||
from .list import List
|
||||
from .message import Message
|
||||
from .msg_container import MsgContainer
|
||||
from .object import Object
|
||||
from .primitives import (
|
||||
Bool, BoolTrue, BoolFalse, Bytes, Double,
|
||||
Int, Long, Int128, Int256, Null, String, Vector
|
||||
)
|
||||
from .tl_object import TLObject
|
||||
|
@@ -16,29 +16,28 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from datetime import datetime
|
||||
from io import BytesIO
|
||||
|
||||
from .object import Object
|
||||
from .primitives import Int, Long
|
||||
from .tl_object import TLObject
|
||||
|
||||
|
||||
class FutureSalt(Object):
|
||||
class FutureSalt(TLObject):
|
||||
ID = 0x0949d9dc
|
||||
|
||||
__slots__ = ["valid_since", "valid_until", "salt"]
|
||||
|
||||
QUALNAME = "FutureSalt"
|
||||
|
||||
def __init__(self, valid_since: int or datetime, valid_until: int or datetime, salt: int):
|
||||
def __init__(self, valid_since: int, valid_until: int, salt: int):
|
||||
self.valid_since = valid_since
|
||||
self.valid_until = valid_until
|
||||
self.salt = salt
|
||||
|
||||
@staticmethod
|
||||
def read(b: BytesIO, *args) -> "FutureSalt":
|
||||
valid_since = datetime.fromtimestamp(Int.read(b))
|
||||
valid_until = datetime.fromtimestamp(Int.read(b))
|
||||
valid_since = Int.read(b)
|
||||
valid_until = Int.read(b)
|
||||
salt = Long.read(b)
|
||||
|
||||
return FutureSalt(valid_since, valid_until, salt)
|
||||
|
@@ -16,22 +16,21 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from datetime import datetime
|
||||
from io import BytesIO
|
||||
|
||||
from . import FutureSalt
|
||||
from .object import Object
|
||||
from .primitives import Int, Long
|
||||
from .tl_object import TLObject
|
||||
|
||||
|
||||
class FutureSalts(Object):
|
||||
class FutureSalts(TLObject):
|
||||
ID = 0xae500895
|
||||
|
||||
__slots__ = ["req_msg_id", "now", "salts"]
|
||||
|
||||
QUALNAME = "FutureSalts"
|
||||
|
||||
def __init__(self, req_msg_id: int, now: int or datetime, salts: list):
|
||||
def __init__(self, req_msg_id: int, now: int, salts: list):
|
||||
self.req_msg_id = req_msg_id
|
||||
self.now = now
|
||||
self.salts = salts
|
||||
@@ -39,7 +38,7 @@ class FutureSalts(Object):
|
||||
@staticmethod
|
||||
def read(b: BytesIO, *args) -> "FutureSalts":
|
||||
req_msg_id = Long.read(b)
|
||||
now = datetime.fromtimestamp(Int.read(b))
|
||||
now = Int.read(b)
|
||||
|
||||
count = Int.read(b)
|
||||
salts = [FutureSalt.read(b) for _ in range(count)]
|
||||
|
@@ -19,24 +19,24 @@
|
||||
from gzip import compress, decompress
|
||||
from io import BytesIO
|
||||
|
||||
from .object import Object
|
||||
from .primitives import Int, Bytes
|
||||
from .tl_object import TLObject
|
||||
|
||||
|
||||
class GzipPacked(Object):
|
||||
class GzipPacked(TLObject):
|
||||
ID = 0x3072cfa1
|
||||
|
||||
__slots__ = ["packed_data"]
|
||||
|
||||
QUALNAME = "GzipPacked"
|
||||
|
||||
def __init__(self, packed_data: Object):
|
||||
def __init__(self, packed_data: TLObject):
|
||||
self.packed_data = packed_data
|
||||
|
||||
@staticmethod
|
||||
def read(b: BytesIO, *args) -> "GzipPacked":
|
||||
# Return the Object itself instead of a GzipPacked wrapping it
|
||||
return Object.read(
|
||||
return TLObject.read(
|
||||
BytesIO(
|
||||
decompress(
|
||||
Bytes.read(b)
|
||||
|
@@ -16,14 +16,13 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from .tl_object import TLObject
|
||||
|
||||
class ParseMode:
|
||||
"""This class provides a convenient access to Parse Modes.
|
||||
Parse Modes are intended to be used with any method that accepts the optional argument **parse_mode**.
|
||||
"""
|
||||
|
||||
HTML = "html"
|
||||
"""Set the parse mode to HTML style"""
|
||||
class List(list, TLObject):
|
||||
__slots__ = []
|
||||
|
||||
MARKDOWN = "markdown"
|
||||
"""Set the parse mode to Markdown style"""
|
||||
def __repr__(self):
|
||||
return "pyrogram.api.core.List([{}])".format(
|
||||
",".join(TLObject.__repr__(i) for i in self)
|
||||
)
|
@@ -18,18 +18,18 @@
|
||||
|
||||
from io import BytesIO
|
||||
|
||||
from .object import Object
|
||||
from .primitives import Int, Long
|
||||
from .tl_object import TLObject
|
||||
|
||||
|
||||
class Message(Object):
|
||||
class Message(TLObject):
|
||||
ID = 0x5bb8e511 # hex(crc32(b"message msg_id:long seqno:int bytes:int body:Object = Message"))
|
||||
|
||||
__slots__ = ["msg_id", "seq_no", "length", "body"]
|
||||
|
||||
QUALNAME = "Message"
|
||||
|
||||
def __init__(self, body: Object, msg_id: int, seq_no: int, length: int):
|
||||
def __init__(self, body: TLObject, msg_id: int, seq_no: int, length: int):
|
||||
self.msg_id = msg_id
|
||||
self.seq_no = seq_no
|
||||
self.length = length
|
||||
@@ -42,7 +42,7 @@ class Message(Object):
|
||||
length = Int.read(b)
|
||||
body = b.read(length)
|
||||
|
||||
return Message(Object.read(BytesIO(body)), msg_id, seq_no, length)
|
||||
return Message(TLObject.read(BytesIO(body)), msg_id, seq_no, length)
|
||||
|
||||
def write(self) -> bytes:
|
||||
b = BytesIO()
|
||||
|
@@ -19,11 +19,11 @@
|
||||
from io import BytesIO
|
||||
|
||||
from .message import Message
|
||||
from .object import Object
|
||||
from .primitives import Int
|
||||
from .tl_object import TLObject
|
||||
|
||||
|
||||
class MsgContainer(Object):
|
||||
class MsgContainer(TLObject):
|
||||
ID = 0x73f1f8dc
|
||||
|
||||
__slots__ = ["messages"]
|
||||
|
@@ -1,72 +0,0 @@
|
||||
# Pyrogram - Telegram MTProto API Client Library for Python
|
||||
# Copyright (C) 2017-2019 Dan Tès <https://github.com/delivrance>
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
from collections import OrderedDict
|
||||
from datetime import datetime
|
||||
from io import BytesIO
|
||||
from json import dumps
|
||||
|
||||
|
||||
class Object:
|
||||
all = {}
|
||||
|
||||
__slots__ = []
|
||||
|
||||
QUALNAME = "Base"
|
||||
|
||||
@staticmethod
|
||||
def read(b: BytesIO, *args):
|
||||
return Object.all[int.from_bytes(b.read(4), "little")].read(b, *args)
|
||||
|
||||
def write(self, *args) -> bytes:
|
||||
pass
|
||||
|
||||
def __str__(self) -> str:
|
||||
return dumps(self, indent=4, default=default, ensure_ascii=False)
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self.write())
|
||||
|
||||
def __getitem__(self, item):
|
||||
return getattr(self, item)
|
||||
|
||||
|
||||
def remove_none(obj):
|
||||
if isinstance(obj, (list, tuple, set)):
|
||||
return type(obj)(remove_none(x) for x in obj if x is not None)
|
||||
elif isinstance(obj, dict):
|
||||
return type(obj)((remove_none(k), remove_none(v)) for k, v in obj.items() if k is not None and v is not None)
|
||||
else:
|
||||
return obj
|
||||
|
||||
|
||||
def default(o: "Object"):
|
||||
try:
|
||||
content = {i: getattr(o, i) for i in o.__slots__}
|
||||
|
||||
return remove_none(
|
||||
OrderedDict(
|
||||
[("_", o.QUALNAME)]
|
||||
+ [i for i in content.items()]
|
||||
)
|
||||
)
|
||||
except AttributeError:
|
||||
if isinstance(o, datetime):
|
||||
return o.strftime("%d-%b-%Y %H:%M:%S")
|
||||
else:
|
||||
return repr(o)
|
@@ -18,10 +18,10 @@
|
||||
|
||||
from io import BytesIO
|
||||
|
||||
from ..object import Object
|
||||
from ..tl_object import TLObject
|
||||
|
||||
|
||||
class BoolFalse(Object):
|
||||
class BoolFalse(TLObject):
|
||||
ID = 0xbc799737
|
||||
value = False
|
||||
|
||||
@@ -38,7 +38,7 @@ class BoolTrue(BoolFalse):
|
||||
value = True
|
||||
|
||||
|
||||
class Bool(Object):
|
||||
class Bool(TLObject):
|
||||
@classmethod
|
||||
def read(cls, b: BytesIO) -> bool:
|
||||
return int.from_bytes(b.read(4), "little") == BoolTrue.ID
|
||||
|
@@ -18,10 +18,10 @@
|
||||
|
||||
from io import BytesIO
|
||||
|
||||
from ..object import Object
|
||||
from ..tl_object import TLObject
|
||||
|
||||
|
||||
class Bytes(Object):
|
||||
class Bytes(TLObject):
|
||||
@staticmethod
|
||||
def read(b: BytesIO, *args) -> bytes:
|
||||
length = int.from_bytes(b.read(1), "little")
|
||||
|
@@ -19,10 +19,10 @@
|
||||
from io import BytesIO
|
||||
from struct import unpack, pack
|
||||
|
||||
from ..object import Object
|
||||
from ..tl_object import TLObject
|
||||
|
||||
|
||||
class Double(Object):
|
||||
class Double(TLObject):
|
||||
@staticmethod
|
||||
def read(b: BytesIO, *args) -> float:
|
||||
return unpack("d", b.read(8))[0]
|
||||
|
@@ -18,10 +18,10 @@
|
||||
|
||||
from io import BytesIO
|
||||
|
||||
from ..object import Object
|
||||
from ..tl_object import TLObject
|
||||
|
||||
|
||||
class Int(Object):
|
||||
class Int(TLObject):
|
||||
SIZE = 4
|
||||
|
||||
@classmethod
|
||||
|
@@ -18,10 +18,10 @@
|
||||
|
||||
from io import BytesIO
|
||||
|
||||
from ..object import Object
|
||||
from ..tl_object import TLObject
|
||||
|
||||
|
||||
class Null(Object):
|
||||
class Null(TLObject):
|
||||
ID = 0x56730bcc
|
||||
|
||||
@staticmethod
|
||||
|
@@ -19,31 +19,32 @@
|
||||
from io import BytesIO
|
||||
|
||||
from . import Int
|
||||
from ..object import Object
|
||||
from ..list import List
|
||||
from ..tl_object import TLObject
|
||||
|
||||
|
||||
class Vector(Object):
|
||||
class Vector(TLObject):
|
||||
ID = 0x1cb5c415
|
||||
|
||||
# Method added to handle the special case when a query returns a bare Vector (of Ints);
|
||||
# i.e., RpcResult body starts with 0x1cb5c415 (Vector Id) - e.g., messages.GetMessagesViews.
|
||||
@staticmethod
|
||||
def _read(b: BytesIO) -> Object or int:
|
||||
def _read(b: BytesIO) -> TLObject or int:
|
||||
try:
|
||||
return Object.read(b)
|
||||
return TLObject.read(b)
|
||||
except KeyError:
|
||||
b.seek(-4, 1)
|
||||
return Int.read(b)
|
||||
|
||||
@staticmethod
|
||||
def read(b: BytesIO, t: Object = None) -> list:
|
||||
return [
|
||||
def read(b: BytesIO, t: TLObject = None) -> list:
|
||||
return List(
|
||||
t.read(b) if t
|
||||
else Vector._read(b)
|
||||
for _ in range(Int.read(b))
|
||||
]
|
||||
)
|
||||
|
||||
def __new__(cls, value: list, t: Object = None) -> bytes:
|
||||
def __new__(cls, value: list, t: TLObject = None) -> bytes:
|
||||
return b"".join(
|
||||
[Int(cls.ID, False), Int(len(value))]
|
||||
+ [
|
||||
|
82
pyrogram/api/core/tl_object.py
Normal file
82
pyrogram/api/core/tl_object.py
Normal file
@@ -0,0 +1,82 @@
|
||||
# Pyrogram - Telegram MTProto API Client Library for Python
|
||||
# Copyright (C) 2017-2019 Dan Tès <https://github.com/delivrance>
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
from collections import OrderedDict
|
||||
from io import BytesIO
|
||||
from json import dumps
|
||||
|
||||
|
||||
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)
|
||||
|
||||
def write(self, *args) -> bytes:
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def default(obj: "TLObject"):
|
||||
if isinstance(obj, bytes):
|
||||
return repr(obj)
|
||||
|
||||
return OrderedDict(
|
||||
[("_", obj.QUALNAME)]
|
||||
+ [
|
||||
(attr, getattr(obj, attr))
|
||||
for attr in obj.__slots__
|
||||
if getattr(obj, attr) is not None
|
||||
]
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return dumps(self, indent=4, default=TLObject.default, ensure_ascii=False)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "pyrogram.api.{}({})".format(
|
||||
self.QUALNAME,
|
||||
", ".join(
|
||||
"{}={}".format(attr, repr(getattr(self, attr)))
|
||||
for attr in self.__slots__
|
||||
if getattr(self, attr) is not None
|
||||
)
|
||||
)
|
||||
|
||||
def __eq__(self, other: "TLObject") -> bool:
|
||||
for attr in self.__slots__:
|
||||
try:
|
||||
if getattr(self, attr) != getattr(other, attr):
|
||||
return False
|
||||
except AttributeError:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self.write())
|
||||
|
||||
def __getitem__(self, item):
|
||||
return getattr(self, item)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
setattr(self, key, value)
|
@@ -17,9 +17,9 @@
|
||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from .client import Client
|
||||
from .ext import BaseClient, ChatAction, Emoji, ParseMode
|
||||
from .ext import BaseClient, Emoji
|
||||
from .filters import Filters
|
||||
|
||||
__all__ = [
|
||||
"Client", "BaseClient", "ChatAction", "Emoji", "ParseMode", "Filters",
|
||||
"Client", "BaseClient", "Emoji", "Filters",
|
||||
]
|
||||
|
@@ -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]
|
||||
|
@@ -17,8 +17,7 @@
|
||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from .base_client import BaseClient
|
||||
from .chat_action import ChatAction
|
||||
from .dispatcher import Dispatcher
|
||||
from .emoji import Emoji
|
||||
from .parse_mode import ParseMode
|
||||
from .file_data import FileData
|
||||
from .syncer import Syncer
|
||||
|
@@ -16,8 +16,11 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from queue import Queue
|
||||
from threading import Lock
|
||||
|
||||
@@ -31,7 +34,7 @@ class BaseClient:
|
||||
class StopTransmission(StopIteration):
|
||||
pass
|
||||
|
||||
APP_VERSION = "Pyrogram \U0001f525 {}".format(__version__)
|
||||
APP_VERSION = "Pyrogram {}".format(__version__)
|
||||
|
||||
DEVICE_MODEL = "{} {}".format(
|
||||
platform.python_implementation(),
|
||||
@@ -45,18 +48,20 @@ 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
|
||||
UPDATES_WORKERS = 1
|
||||
DOWNLOAD_WORKERS = 1
|
||||
OFFLINE_SLEEP = 300
|
||||
OFFLINE_SLEEP = 900
|
||||
WORKERS = 4
|
||||
WORKDIR = "."
|
||||
CONFIG_FILE = "./config.ini"
|
||||
WORKDIR = PARENT_DIR
|
||||
CONFIG_FILE = PARENT_DIR / "config.ini"
|
||||
|
||||
MEDIA_TYPE_ID = {
|
||||
0: "thumbnail",
|
||||
0: "photo_thumbnail",
|
||||
1: "chat_photo",
|
||||
2: "photo",
|
||||
3: "voice",
|
||||
@@ -65,14 +70,28 @@ class BaseClient:
|
||||
8: "sticker",
|
||||
9: "audio",
|
||||
10: "animation",
|
||||
13: "video_note"
|
||||
13: "video_note",
|
||||
14: "document_thumbnail"
|
||||
}
|
||||
|
||||
mime_types_to_extensions = {}
|
||||
extensions_to_mime_types = {}
|
||||
|
||||
with open("{}/mime.types".format(os.path.dirname(__file__)), "r", encoding="UTF-8") as f:
|
||||
for match in re.finditer(r"^([^#\s]+)\s+(.+)$", f.read(), flags=re.M):
|
||||
mime_type, extensions = match.groups()
|
||||
|
||||
extensions = [".{}".format(ext) for ext in extensions.split(" ")]
|
||||
|
||||
for ext in extensions:
|
||||
extensions_to_mime_types[ext] = mime_type
|
||||
|
||||
mime_types_to_extensions[mime_type] = " ".join(extensions)
|
||||
|
||||
def __init__(self, session_storage: SessionStorage):
|
||||
self.session_storage = session_storage
|
||||
|
||||
self.rnd_id = MsgId
|
||||
self.channels_pts = {}
|
||||
|
||||
self.markdown = Markdown(self.session_storage, self)
|
||||
self.html = HTML(self.session_storage, self)
|
||||
@@ -125,3 +144,30 @@ class BaseClient:
|
||||
|
||||
def answer_inline_query(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def guess_mime_type(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def guess_extension(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def get_profile_photos(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
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
|
||||
|
@@ -1,77 +0,0 @@
|
||||
# Pyrogram - Telegram MTProto API Client Library for Python
|
||||
# Copyright (C) 2017-2019 Dan Tès <https://github.com/delivrance>
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
from enum import Enum
|
||||
|
||||
from pyrogram.api import types
|
||||
|
||||
|
||||
class ChatAction(Enum):
|
||||
"""This enumeration provides a convenient access to all Chat Actions available.
|
||||
Chat Actions are intended to be used with
|
||||
:meth:`send_chat_action() <pyrogram.Client.send_chat_action>`.
|
||||
"""
|
||||
|
||||
CANCEL = types.SendMessageCancelAction
|
||||
"""Cancels any chat action currently displayed."""
|
||||
|
||||
TYPING = types.SendMessageTypingAction
|
||||
"""User is typing a text message."""
|
||||
|
||||
PLAYING = types.SendMessageGamePlayAction
|
||||
"""User is playing a game."""
|
||||
|
||||
CHOOSE_CONTACT = types.SendMessageChooseContactAction
|
||||
"""User is choosing a contact to share."""
|
||||
|
||||
UPLOAD_PHOTO = types.SendMessageUploadPhotoAction
|
||||
"""User is uploading a photo."""
|
||||
|
||||
RECORD_VIDEO = types.SendMessageRecordVideoAction
|
||||
"""User is recording a video."""
|
||||
|
||||
UPLOAD_VIDEO = types.SendMessageUploadVideoAction
|
||||
"""User is uploading a video."""
|
||||
|
||||
RECORD_AUDIO = types.SendMessageRecordAudioAction
|
||||
"""User is recording an audio message."""
|
||||
|
||||
UPLOAD_AUDIO = types.SendMessageUploadAudioAction
|
||||
"""User is uploading an audio message."""
|
||||
|
||||
UPLOAD_DOCUMENT = types.SendMessageUploadDocumentAction
|
||||
"""User is uploading a generic document."""
|
||||
|
||||
FIND_LOCATION = types.SendMessageGeoLocationAction
|
||||
"""User is searching for a location on the map."""
|
||||
|
||||
RECORD_VIDEO_NOTE = types.SendMessageRecordRoundAction
|
||||
"""User is recording a round video note."""
|
||||
|
||||
UPLOAD_VIDEO_NOTE = types.SendMessageUploadRoundAction
|
||||
"""User is uploading a round video note."""
|
||||
|
||||
@staticmethod
|
||||
def from_string(action: str) -> "ChatAction":
|
||||
for a in ChatAction:
|
||||
if a.name.lower() == action.lower():
|
||||
return a
|
||||
|
||||
raise ValueError("Invalid ChatAction: '{}'. Possible types are {}".format(
|
||||
action, [x.name.lower() for x in ChatAction]
|
||||
))
|
@@ -24,9 +24,10 @@ from threading import Thread
|
||||
|
||||
import pyrogram
|
||||
from pyrogram.api import types
|
||||
from . import utils
|
||||
from ..handlers import (
|
||||
CallbackQueryHandler, MessageHandler, DeletedMessagesHandler,
|
||||
UserStatusHandler, RawUpdateHandler, InlineQueryHandler
|
||||
UserStatusHandler, RawUpdateHandler, InlineQueryHandler, PollHandler
|
||||
)
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -68,7 +69,7 @@ class Dispatcher:
|
||||
lambda upd, usr, cht: (pyrogram.Message._parse(self.client, upd.message, usr, cht), MessageHandler),
|
||||
|
||||
Dispatcher.DELETE_MESSAGES_UPDATES:
|
||||
lambda upd, usr, cht: (pyrogram.Messages._parse_deleted(self.client, upd), DeletedMessagesHandler),
|
||||
lambda upd, usr, cht: (utils.parse_deleted_messages(self.client, upd), DeletedMessagesHandler),
|
||||
|
||||
Dispatcher.CALLBACK_QUERY_UPDATES:
|
||||
lambda upd, usr, cht: (pyrogram.CallbackQuery._parse(self.client, upd, usr), CallbackQueryHandler),
|
||||
@@ -79,7 +80,10 @@ class Dispatcher:
|
||||
),
|
||||
|
||||
(types.UpdateBotInlineQuery,):
|
||||
lambda upd, usr, cht: (pyrogram.InlineQuery._parse(self.client, upd, usr), InlineQueryHandler)
|
||||
lambda upd, usr, cht: (pyrogram.InlineQuery._parse(self.client, upd, usr), InlineQueryHandler),
|
||||
|
||||
(types.UpdateMessagePoll,):
|
||||
lambda upd, usr, cht: (pyrogram.Poll._parse_update(self.client, upd), PollHandler)
|
||||
}
|
||||
|
||||
self.update_parsers = {key: value for key_tuple, value in self.update_parsers.items() for key in key_tuple}
|
||||
@@ -103,6 +107,7 @@ class Dispatcher:
|
||||
worker.join()
|
||||
|
||||
self.workers_list.clear()
|
||||
self.groups.clear()
|
||||
|
||||
def add_handler(self, handler, group: int):
|
||||
if group not in self.groups:
|
||||
@@ -122,16 +127,13 @@ class Dispatcher:
|
||||
log.debug("{} started".format(name))
|
||||
|
||||
while True:
|
||||
update = self.updates_queue.get()
|
||||
packet = self.updates_queue.get()
|
||||
|
||||
if update is None:
|
||||
if packet is None:
|
||||
break
|
||||
|
||||
try:
|
||||
users = {i.id: i for i in update[1]}
|
||||
chats = {i.id: i for i in update[2]}
|
||||
update = update[0]
|
||||
|
||||
update, users, chats = packet
|
||||
parser = self.update_parsers.get(type(update), None)
|
||||
|
||||
parsed_update, handler_type = (
|
||||
|
38
pyrogram/client/ext/file_data.py
Normal file
38
pyrogram/client/ext/file_data.py
Normal file
@@ -0,0 +1,38 @@
|
||||
# Pyrogram - Telegram MTProto API Client Library for Python
|
||||
# Copyright (C) 2017-2019 Dan Tès <https://github.com/delivrance>
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
class FileData:
|
||||
def __init__(
|
||||
self, *, media_type: int = None, dc_id: int = None, document_id: int = None, access_hash: int = None,
|
||||
thumb_size: str = None, peer_id: int = None, volume_id: int = None, local_id: int = None, is_big: bool = None,
|
||||
file_size: int = None, mime_type: str = None, file_name: str = None, date: int = None
|
||||
):
|
||||
self.media_type = media_type
|
||||
self.dc_id = dc_id
|
||||
self.document_id = document_id
|
||||
self.access_hash = access_hash
|
||||
self.thumb_size = thumb_size
|
||||
self.peer_id = peer_id
|
||||
self.volume_id = volume_id
|
||||
self.local_id = local_id
|
||||
self.is_big = is_big
|
||||
self.file_size = file_size
|
||||
self.mime_type = mime_type
|
||||
self.file_name = file_name
|
||||
self.date = date
|
1858
pyrogram/client/ext/mime.types
Normal file
1858
pyrogram/client/ext/mime.types
Normal file
File diff suppressed because it is too large
Load Diff
@@ -16,8 +16,13 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import base64
|
||||
import struct
|
||||
from base64 import b64decode, b64encode
|
||||
from typing import Union, List
|
||||
|
||||
import pyrogram
|
||||
from . import BaseClient
|
||||
from ...api import types
|
||||
|
||||
|
||||
@@ -82,3 +87,119 @@ def get_offset_date(dialogs):
|
||||
return m.date
|
||||
else:
|
||||
return 0
|
||||
|
||||
|
||||
def get_input_media_from_file_id(
|
||||
file_id_str: str,
|
||||
expected_media_type: int = None
|
||||
) -> Union[types.InputMediaPhoto, types.InputMediaDocument]:
|
||||
try:
|
||||
decoded = decode(file_id_str)
|
||||
except Exception:
|
||||
raise ValueError("Failed to decode file_id: {}".format(file_id_str))
|
||||
else:
|
||||
media_type = decoded[0]
|
||||
|
||||
if expected_media_type is not None:
|
||||
if media_type != expected_media_type:
|
||||
media_type_str = BaseClient.MEDIA_TYPE_ID.get(media_type, None)
|
||||
expected_media_type_str = BaseClient.MEDIA_TYPE_ID.get(expected_media_type, None)
|
||||
|
||||
raise ValueError(
|
||||
'Expected: "{}", got "{}" file_id instead'.format(expected_media_type_str, media_type_str)
|
||||
)
|
||||
|
||||
if media_type in (0, 1, 14):
|
||||
raise ValueError("This file_id can only be used for download: {}".format(file_id_str))
|
||||
|
||||
if media_type == 2:
|
||||
unpacked = struct.unpack("<iiqqc", decoded)
|
||||
dc_id, file_id, access_hash, thumb_size = unpacked[1:]
|
||||
|
||||
return types.InputMediaPhoto(
|
||||
id=types.InputPhoto(
|
||||
id=file_id,
|
||||
access_hash=access_hash,
|
||||
file_reference=b""
|
||||
)
|
||||
)
|
||||
|
||||
if media_type in (3, 4, 5, 8, 9, 10, 13):
|
||||
unpacked = struct.unpack("<iiqq", decoded)
|
||||
dc_id, file_id, access_hash = unpacked[1:]
|
||||
|
||||
return types.InputMediaDocument(
|
||||
id=types.InputDocument(
|
||||
id=file_id,
|
||||
access_hash=access_hash,
|
||||
file_reference=b""
|
||||
)
|
||||
)
|
||||
|
||||
raise ValueError("Unknown media type: {}".format(file_id_str))
|
||||
|
||||
|
||||
def parse_messages(client, messages: types.messages.Messages, replies: int = 1) -> List["pyrogram.Message"]:
|
||||
users = {i.id: i for i in messages.users}
|
||||
chats = {i.id: i for i in messages.chats}
|
||||
|
||||
if not messages.messages:
|
||||
return pyrogram.List()
|
||||
|
||||
parsed_messages = [
|
||||
pyrogram.Message._parse(client, message, users, chats, replies=0)
|
||||
for message in messages.messages
|
||||
]
|
||||
|
||||
if replies:
|
||||
messages_with_replies = {i.id: getattr(i, "reply_to_msg_id", None) for i in messages.messages}
|
||||
reply_message_ids = [i[0] for i in filter(lambda x: x[1] is not None, messages_with_replies.items())]
|
||||
|
||||
if reply_message_ids:
|
||||
reply_messages = client.get_messages(
|
||||
parsed_messages[0].chat.id,
|
||||
reply_to_message_ids=reply_message_ids,
|
||||
replies=replies - 1
|
||||
)
|
||||
|
||||
for message in parsed_messages:
|
||||
reply_id = messages_with_replies[message.message_id]
|
||||
|
||||
for reply in reply_messages:
|
||||
if reply.message_id == reply_id:
|
||||
message.reply_to_message = reply
|
||||
|
||||
return pyrogram.List(parsed_messages)
|
||||
|
||||
|
||||
def parse_deleted_messages(client, update) -> List["pyrogram.Message"]:
|
||||
messages = update.messages
|
||||
channel_id = getattr(update, "channel_id", None)
|
||||
|
||||
parsed_messages = []
|
||||
|
||||
for message in messages:
|
||||
parsed_messages.append(
|
||||
pyrogram.Message(
|
||||
message_id=message,
|
||||
chat=pyrogram.Chat(
|
||||
id=int("-100" + str(channel_id)),
|
||||
type="channel",
|
||||
client=client
|
||||
) if channel_id is not None else None,
|
||||
client=client
|
||||
)
|
||||
)
|
||||
|
||||
return pyrogram.List(parsed_messages)
|
||||
|
||||
|
||||
def unpack_inline_message_id(inline_message_id: str) -> types.InputBotInlineMessageID:
|
||||
r = inline_message_id + "=" * (-len(inline_message_id) % 4)
|
||||
r = struct.unpack("<iqq", base64.b64decode(r, altchars="-_"))
|
||||
|
||||
return types.InputBotInlineMessageID(
|
||||
dc_id=r[0],
|
||||
id=r[1],
|
||||
access_hash=r[2]
|
||||
)
|
||||
|
@@ -19,15 +19,15 @@
|
||||
import re
|
||||
|
||||
from .filter import Filter
|
||||
from ..types.bots import InlineKeyboardMarkup, ReplyKeyboardMarkup
|
||||
from ..types.bots_and_keyboards import InlineKeyboardMarkup, ReplyKeyboardMarkup
|
||||
|
||||
|
||||
def create(name: str, func: callable, **kwargs) -> type:
|
||||
"""Use this method to create a Filter.
|
||||
"""Create a Filter.
|
||||
|
||||
Custom filters give you extra control over which updates are allowed or not to be processed by your handlers.
|
||||
|
||||
Args:
|
||||
Parameters:
|
||||
name (``str``):
|
||||
Your filter's name. Can be anything you like.
|
||||
|
||||
@@ -35,14 +35,14 @@ def create(name: str, func: callable, **kwargs) -> type:
|
||||
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 <Handlers.html>`_ is coming from.
|
||||
For example, in a :obj:`MessageHandler <pyrogram.MessageHandler>` the update type will be
|
||||
a :obj:`Message <pyrogram.Message>`; in a :obj:`CallbackQueryHandler <pyrogram.CallbackQueryHandler>` the
|
||||
update type will be a :obj:`CallbackQuery <pyrogram.CallbackQuery>`. Your function body can then access the
|
||||
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.
|
||||
|
||||
**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`).
|
||||
:meth:`~Filters.command`, :meth:`~Filters.regex`).
|
||||
"""
|
||||
# TODO: unpack kwargs using **kwargs into the dict itself. For Python 3.5+ only
|
||||
d = {"__call__": func}
|
||||
@@ -54,9 +54,9 @@ def create(name: str, func: callable, **kwargs) -> type:
|
||||
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 <pyrogram.MessageHandler>` only.
|
||||
The Filters listed here are intended to be used with the :obj:`MessageHandler` only.
|
||||
At the moment, if you want to filter updates coming from different `Handlers <Handlers.html>`_ you have to create
|
||||
your own filters with :meth:`Filters.create` and use them in the same way.
|
||||
your own filters with :meth:`~Filters.create` and use them in the same way.
|
||||
"""
|
||||
|
||||
create = create
|
||||
@@ -89,49 +89,49 @@ class Filters:
|
||||
"""Filter edited messages."""
|
||||
|
||||
audio = create("Audio", lambda _, m: bool(m.audio))
|
||||
"""Filter messages that contain :obj:`Audio <pyrogram.Audio>` objects."""
|
||||
"""Filter messages that contain :obj:`Audio` objects."""
|
||||
|
||||
document = create("Document", lambda _, m: bool(m.document))
|
||||
"""Filter messages that contain :obj:`Document <pyrogram.Document>` objects."""
|
||||
"""Filter messages that contain :obj:`Document` objects."""
|
||||
|
||||
photo = create("Photo", lambda _, m: bool(m.photo))
|
||||
"""Filter messages that contain :obj:`Photo <pyrogram.PhotoSize>` objects."""
|
||||
"""Filter messages that contain :obj:`Photo` objects."""
|
||||
|
||||
sticker = create("Sticker", lambda _, m: bool(m.sticker))
|
||||
"""Filter messages that contain :obj:`Sticker <pyrogram.Sticker>` objects."""
|
||||
"""Filter messages that contain :obj:`Sticker` objects."""
|
||||
|
||||
animation = create("Animation", lambda _, m: bool(m.animation))
|
||||
"""Filter messages that contain :obj:`Animation <pyrogram.Animation>` objects."""
|
||||
"""Filter messages that contain :obj:`Animation` objects."""
|
||||
|
||||
game = create("Game", lambda _, m: bool(m.game))
|
||||
"""Filter messages that contain :obj:`Game <pyrogram.Game>` objects."""
|
||||
"""Filter messages that contain :obj:`Game` objects."""
|
||||
|
||||
video = create("Video", lambda _, m: bool(m.video))
|
||||
"""Filter messages that contain :obj:`Video <pyrogram.Video>` objects."""
|
||||
"""Filter messages that contain :obj:`Video` objects."""
|
||||
|
||||
media_group = create("MediaGroup", lambda _, m: bool(m.media_group_id))
|
||||
"""Filter messages containing photos or videos being part of an album."""
|
||||
|
||||
voice = create("Voice", lambda _, m: bool(m.voice))
|
||||
"""Filter messages that contain :obj:`Voice <pyrogram.Voice>` note objects."""
|
||||
"""Filter messages that contain :obj:`Voice` note objects."""
|
||||
|
||||
video_note = create("VideoNote", lambda _, m: bool(m.video_note))
|
||||
"""Filter messages that contain :obj:`VideoNote <pyrogram.VideoNote>` objects."""
|
||||
"""Filter messages that contain :obj:`VideoNote` objects."""
|
||||
|
||||
contact = create("Contact", lambda _, m: bool(m.contact))
|
||||
"""Filter messages that contain :obj:`Contact <pyrogram.Contact>` objects."""
|
||||
"""Filter messages that contain :obj:`Contact` objects."""
|
||||
|
||||
location = create("Location", lambda _, m: bool(m.location))
|
||||
"""Filter messages that contain :obj:`Location <pyrogram.Location>` objects."""
|
||||
"""Filter messages that contain :obj:`Location` objects."""
|
||||
|
||||
venue = create("Venue", lambda _, m: bool(m.venue))
|
||||
"""Filter messages that contain :obj:`Venue <pyrogram.Venue>` objects."""
|
||||
"""Filter messages that contain :obj:`Venue` objects."""
|
||||
|
||||
web_page = create("WebPage", lambda _, m: m.web_page)
|
||||
"""Filter messages sent with a webpage preview."""
|
||||
|
||||
poll = create("Poll", lambda _, m: m.poll)
|
||||
"""Filter messages that contain :obj:`Poll <pyrogram.Poll>` objects."""
|
||||
"""Filter messages that contain :obj:`Poll` objects."""
|
||||
|
||||
private = create("Private", lambda _, m: bool(m.chat and m.chat.type == "private"))
|
||||
"""Filter messages sent in private chats."""
|
||||
@@ -191,35 +191,19 @@ class Filters:
|
||||
"""Filter messages sent via inline bots"""
|
||||
|
||||
service = create("Service", lambda _, m: bool(m.service))
|
||||
"""Filter service messages. A service message contains any of the following fields set
|
||||
"""Filter service messages.
|
||||
|
||||
- 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"""
|
||||
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))
|
||||
"""Filter media messages. A media message contains any of the following fields set
|
||||
"""Filter media messages.
|
||||
|
||||
- audio
|
||||
- document
|
||||
- photo
|
||||
- sticker
|
||||
- video
|
||||
- animation
|
||||
- voice
|
||||
- video_note
|
||||
- contact
|
||||
- location
|
||||
- venue
|
||||
- poll"""
|
||||
A media message contains any of the following fields set: *audio*, *document*, *photo*, *sticker*, *video*,
|
||||
*animation*, *voice*, *video_note*, *contact*, *location*, *venue*, *poll*.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def command(
|
||||
@@ -230,12 +214,12 @@ class Filters:
|
||||
):
|
||||
"""Filter commands, i.e.: text messages starting with "/" or any other custom prefix.
|
||||
|
||||
Args:
|
||||
Parameters:
|
||||
commands (``str`` | ``list``):
|
||||
The command or list of commands as string the filter should look for.
|
||||
Examples: "start", ["start", "help", "settings"]. When a message text containing
|
||||
a command arrives, the command itself and its arguments will be stored in the *command*
|
||||
field of the :class:`Message <pyrogram.Message>`.
|
||||
field of the :obj:`Message`.
|
||||
|
||||
prefix (``str`` | ``list``, *optional*):
|
||||
A prefix or a list of prefixes as string the filter should look for.
|
||||
@@ -275,11 +259,11 @@ class Filters:
|
||||
def regex(pattern, flags: int = 0):
|
||||
"""Filter messages that match a given RegEx pattern.
|
||||
|
||||
Args:
|
||||
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 <https://docs.python.org/3/library/re.html#match-objects>`_
|
||||
are stored in the *matches* field of the :class:`Message <pyrogram.Message>` itself.
|
||||
are stored in the *matches* field of the :obj:`Message` itself.
|
||||
|
||||
flags (``int``, *optional*):
|
||||
RegEx flags.
|
||||
@@ -298,7 +282,7 @@ class Filters:
|
||||
You can use `set bound methods <https://docs.python.org/3/library/stdtypes.html#set>`_ to manipulate the
|
||||
users container.
|
||||
|
||||
Args:
|
||||
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.
|
||||
@@ -329,7 +313,7 @@ class Filters:
|
||||
You can use `set bound methods <https://docs.python.org/3/library/stdtypes.html#set>`_ to manipulate the
|
||||
chats container.
|
||||
|
||||
Args:
|
||||
Parameters:
|
||||
chats (``int`` | ``str`` | ``list``):
|
||||
Pass one or more chat ids/usernames to filter chats.
|
||||
For your personal cloud (Saved Messages) you can simply use "me" or "self".
|
||||
@@ -355,4 +339,15 @@ class Filters:
|
||||
and message.from_user.is_self
|
||||
and not message.outgoing)))
|
||||
|
||||
@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)
|
||||
|
||||
dan = create("Dan", lambda _, m: bool(m.from_user and m.from_user.id == 23122162))
|
||||
|
@@ -21,10 +21,11 @@ from .deleted_messages_handler import DeletedMessagesHandler
|
||||
from .disconnect_handler import DisconnectHandler
|
||||
from .inline_query_handler import InlineQueryHandler
|
||||
from .message_handler import MessageHandler
|
||||
from .poll_handler import PollHandler
|
||||
from .raw_update_handler import RawUpdateHandler
|
||||
from .user_status_handler import UserStatusHandler
|
||||
|
||||
__all__ = [
|
||||
"MessageHandler", "DeletedMessagesHandler", "CallbackQueryHandler", "RawUpdateHandler", "DisconnectHandler",
|
||||
"UserStatusHandler", "InlineQueryHandler"
|
||||
"UserStatusHandler", "InlineQueryHandler", "PollHandler"
|
||||
]
|
||||
|
@@ -21,22 +21,22 @@ from .handler import Handler
|
||||
|
||||
class CallbackQueryHandler(Handler):
|
||||
"""The CallbackQuery handler class. Used to handle callback queries coming from inline buttons.
|
||||
It is intended to be used with :meth:`add_handler() <pyrogram.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:`on_callback_query() <pyrogram.Client.on_callback_query>` decorator.
|
||||
:meth:`~Client.on_callback_query` decorator.
|
||||
|
||||
Args:
|
||||
Parameters:
|
||||
callback (``callable``):
|
||||
Pass a function that will be called when a new CallbackQuery arrives. It takes *(client, callback_query)*
|
||||
as positional arguments (look at the section below for a detailed description).
|
||||
|
||||
filters (:obj:`Filters <pyrogram.Filters>`):
|
||||
filters (:obj:`Filters`):
|
||||
Pass one or more filters to allow only a subset of callback queries to be passed
|
||||
in your callback function.
|
||||
|
||||
Other parameters:
|
||||
client (:obj:`Client <pyrogram.Client>`):
|
||||
client (:obj:`Client`):
|
||||
The Client itself, useful when you want to call other API methods inside the message handler.
|
||||
|
||||
callback_query (:obj:`CallbackQuery <pyrogram.CallbackQuery>`):
|
||||
|
@@ -20,32 +20,31 @@ from .handler import Handler
|
||||
|
||||
|
||||
class DeletedMessagesHandler(Handler):
|
||||
"""The deleted Messages handler class. Used to handle deleted messages coming from any chat
|
||||
(private, group, channel). It is intended to be used with
|
||||
:meth:`add_handler() <pyrogram.Client.add_handler>`
|
||||
"""The deleted messages handler class. Used to handle deleted messages coming from any chat
|
||||
(private, group, channel). 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:`on_deleted_messages() <pyrogram.Client.on_deleted_messages>` decorator.
|
||||
:meth:`~Client.on_deleted_messages` decorator.
|
||||
|
||||
Args:
|
||||
Parameters:
|
||||
callback (``callable``):
|
||||
Pass a function that will be called when one or more Messages have been deleted.
|
||||
Pass a function that will be called when one or more messages have been deleted.
|
||||
It takes *(client, messages)* as positional arguments (look at the section below for a detailed description).
|
||||
|
||||
filters (:obj:`Filters <pyrogram.Filters>`):
|
||||
filters (:obj:`Filters`):
|
||||
Pass one or more filters to allow only a subset of messages to be passed
|
||||
in your callback function.
|
||||
|
||||
Other parameters:
|
||||
client (:obj:`Client <pyrogram.Client>`):
|
||||
client (:obj:`Client`):
|
||||
The Client itself, useful when you want to call other API methods inside the message handler.
|
||||
|
||||
messages (:obj:`Messages <pyrogram.Messages>`):
|
||||
The deleted messages.
|
||||
messages (List of :obj:`Message`):
|
||||
The deleted messages, as list.
|
||||
"""
|
||||
|
||||
def __init__(self, callback: callable, filters=None):
|
||||
super().__init__(callback, filters)
|
||||
|
||||
def check(self, messages):
|
||||
return super().check(messages.messages[0])
|
||||
return super().check(messages[0])
|
||||
|
@@ -21,18 +21,18 @@ from .handler import Handler
|
||||
|
||||
class DisconnectHandler(Handler):
|
||||
"""The Disconnect handler class. Used to handle disconnections. It is intended to be used with
|
||||
:meth:`add_handler() <pyrogram.Client.add_handler>`
|
||||
:meth:~Client.add_handler`
|
||||
|
||||
For a nicer way to register this handler, have a look at the
|
||||
:meth:`on_disconnect() <pyrogram.Client.on_disconnect>` decorator.
|
||||
:meth:`~Client.on_disconnect` decorator.
|
||||
|
||||
Args:
|
||||
Parameters:
|
||||
callback (``callable``):
|
||||
Pass a function that will be called when a disconnection occurs. It takes *(client)*
|
||||
as positional argument (look at the section below for a detailed description).
|
||||
|
||||
Other parameters:
|
||||
client (:obj:`Client <pyrogram.Client>`):
|
||||
client (:obj:`Client`):
|
||||
The Client itself. Useful, for example, when you want to change the proxy before a new connection
|
||||
is established.
|
||||
"""
|
||||
|
@@ -21,34 +21,27 @@ from .handler import Handler
|
||||
|
||||
class InlineQueryHandler(Handler):
|
||||
"""The InlineQuery handler class. Used to handle inline queries.
|
||||
It is intended to be used with :meth:`add_handler() <pyrogram.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:`on_inline_query() <pyrogram.Client.on_inline_query>` decorator.
|
||||
:meth:`~Client.on_inline_query` decorator.
|
||||
|
||||
Args:
|
||||
Parameters:
|
||||
callback (``callable``):
|
||||
Pass a function that will be called when a new InlineQuery arrives. It takes *(client, inline_query)*
|
||||
as positional arguments (look at the section below for a detailed description).
|
||||
|
||||
filters (:obj:`Filters <pyrogram.Filters>`):
|
||||
filters (:obj:`Filters`):
|
||||
Pass one or more filters to allow only a subset of inline queries to be passed
|
||||
in your callback function.
|
||||
|
||||
Other parameters:
|
||||
client (:obj:`Client <pyrogram.Client>`):
|
||||
client (:obj:`Client`):
|
||||
The Client itself, useful when you want to call other API methods inside the inline query handler.
|
||||
|
||||
inline_query (:obj:`InlineQuery <pyrogram.InlineQuery>`):
|
||||
inline_query (:obj:`InlineQuery`):
|
||||
The received inline query.
|
||||
"""
|
||||
|
||||
def __init__(self, callback: callable, filters=None):
|
||||
super().__init__(callback, filters)
|
||||
|
||||
def check(self, callback_query):
|
||||
return (
|
||||
self.filters(callback_query)
|
||||
if callable(self.filters)
|
||||
else True
|
||||
)
|
||||
|
@@ -21,26 +21,25 @@ from .handler import Handler
|
||||
|
||||
class MessageHandler(Handler):
|
||||
"""The Message handler class. Used to handle text, media and service messages coming from
|
||||
any chat (private, group, channel). It is intended to be used with
|
||||
:meth:`add_handler() <pyrogram.Client.add_handler>`
|
||||
any chat (private, group, channel). 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:`on_message() <pyrogram.Client.on_message>` decorator.
|
||||
:meth:`~Client.on_message` decorator.
|
||||
|
||||
Args:
|
||||
Parameters:
|
||||
callback (``callable``):
|
||||
Pass a function that will be called when a new Message arrives. It takes *(client, message)*
|
||||
as positional arguments (look at the section below for a detailed description).
|
||||
|
||||
filters (:obj:`Filters <pyrogram.Filters>`):
|
||||
filters (:obj:`Filters`):
|
||||
Pass one or more filters to allow only a subset of messages to be passed
|
||||
in your callback function.
|
||||
|
||||
Other parameters:
|
||||
client (:obj:`Client <pyrogram.Client>`):
|
||||
client (:obj:`Client`):
|
||||
The Client itself, useful when you want to call other API methods inside the message handler.
|
||||
|
||||
message (:obj:`Message <pyrogram.Message>`):
|
||||
message (:obj:`Message`):
|
||||
The received message.
|
||||
"""
|
||||
|
||||
|
48
pyrogram/client/handlers/poll_handler.py
Normal file
48
pyrogram/client/handlers/poll_handler.py
Normal file
@@ -0,0 +1,48 @@
|
||||
# Pyrogram - Telegram MTProto API Client Library for Python
|
||||
# Copyright (C) 2017-2019 Dan Tès <https://github.com/delivrance>
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
from .handler import Handler
|
||||
|
||||
|
||||
class PollHandler(Handler):
|
||||
"""The Poll handler class. Used to handle polls updates.
|
||||
|
||||
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_poll` decorator.
|
||||
|
||||
Parameters:
|
||||
callback (``callable``):
|
||||
Pass a function that will be called when a new poll update arrives. It takes *(client, poll)*
|
||||
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 polls 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 poll handler.
|
||||
|
||||
poll (:obj:`Poll`):
|
||||
The received poll.
|
||||
"""
|
||||
|
||||
def __init__(self, callback: callable, filters=None):
|
||||
super().__init__(callback, filters)
|
@@ -21,19 +21,19 @@ from .handler import Handler
|
||||
|
||||
class RawUpdateHandler(Handler):
|
||||
"""The Raw Update handler class. Used to handle raw updates. It is intended to be used with
|
||||
:meth:`add_handler() <pyrogram.Client.add_handler>`
|
||||
:meth:`~Client.add_handler`
|
||||
|
||||
For a nicer way to register this handler, have a look at the
|
||||
:meth:`on_raw_update() <pyrogram.Client.on_raw_update>` decorator.
|
||||
:meth:`~Client.on_raw_update` decorator.
|
||||
|
||||
Args:
|
||||
Parameters:
|
||||
callback (``callable``):
|
||||
A function that will be called when a new update is received from the server. It takes
|
||||
*(client, update, users, chats)* as positional arguments (look at the section below for
|
||||
a detailed description).
|
||||
|
||||
Other Parameters:
|
||||
client (:class:`Client <pyrogram.Client>`):
|
||||
client (:obj:`Client`):
|
||||
The Client itself, useful when you want to call other API methods inside the update handler.
|
||||
|
||||
update (``Update``):
|
||||
|
@@ -21,25 +21,25 @@ 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:`add_handler() <pyrogram.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:`on_user_status() <pyrogram.Client.on_user_status>` decorator.
|
||||
:meth:`~Client.on_user_status` decorator.
|
||||
|
||||
Args:
|
||||
Parameters:
|
||||
callback (``callable``):
|
||||
Pass a function that will be called when a new UserStatus update arrives. It takes *(client, user_status)*
|
||||
as positional arguments (look at the section below for a detailed description).
|
||||
|
||||
filters (:obj:`Filters <pyrogram.Filters>`):
|
||||
filters (:obj:`Filters`):
|
||||
Pass one or more filters to allow only a subset of messages to be passed
|
||||
in your callback function.
|
||||
|
||||
Other parameters:
|
||||
client (:obj:`Client <pyrogram.Client>`):
|
||||
client (:obj:`Client`):
|
||||
The Client itself, useful when you want to call other API methods inside the user status handler.
|
||||
|
||||
user_status (:obj:`UserStatus <pyrogram.UserStatus>`):
|
||||
user_status (:obj:`UserStatus`):
|
||||
The received UserStatus update.
|
||||
"""
|
||||
|
||||
|
@@ -29,10 +29,10 @@ class AnswerCallbackQuery(BaseClient):
|
||||
url: str = None,
|
||||
cache_time: int = 0
|
||||
):
|
||||
"""Use this method to send answers to callback queries sent from inline keyboards.
|
||||
"""Send answers to callback queries sent from inline keyboards.
|
||||
The answer will be displayed to the user as a notification at the top of the chat screen or as an alert.
|
||||
|
||||
Args:
|
||||
Parameters:
|
||||
callback_query_id (``str``):
|
||||
Unique identifier for the query to be answered.
|
||||
|
||||
@@ -54,10 +54,10 @@ class AnswerCallbackQuery(BaseClient):
|
||||
Telegram apps will support caching starting in version 3.14. Defaults to 0.
|
||||
|
||||
Returns:
|
||||
True, on success.
|
||||
``bool``: True, on success.
|
||||
|
||||
Raises:
|
||||
:class:`RPCError <pyrogram.RPCError>` in case of a Telegram RPC error.
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
"""
|
||||
return self.send(
|
||||
functions.messages.SetBotCallbackAnswer(
|
||||
|
@@ -34,10 +34,10 @@ class AnswerInlineQuery(BaseClient):
|
||||
switch_pm_text: str = "",
|
||||
switch_pm_parameter: str = ""
|
||||
):
|
||||
"""Use this method to send answers to an inline query.
|
||||
"""Send answers to an inline query.
|
||||
No more than 50 results per query are allowed.
|
||||
|
||||
Args:
|
||||
Parameters:
|
||||
inline_query_id (``str``):
|
||||
Unique identifier for the answered query.
|
||||
|
||||
@@ -73,7 +73,10 @@ class AnswerInlineQuery(BaseClient):
|
||||
where they wanted to use the bot's inline capabilities.
|
||||
|
||||
Returns:
|
||||
On success, True is returned.
|
||||
``bool``: True, on success.
|
||||
|
||||
Raises:
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
"""
|
||||
return self.send(
|
||||
functions.messages.SetInlineBotResults(
|
||||
|
@@ -16,7 +16,7 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from typing import Union
|
||||
from typing import Union, List
|
||||
|
||||
import pyrogram
|
||||
from pyrogram.api import functions
|
||||
@@ -29,10 +29,10 @@ class GetGameHighScores(BaseClient):
|
||||
user_id: Union[int, str],
|
||||
chat_id: Union[int, str],
|
||||
message_id: int = None
|
||||
):
|
||||
"""Use this method to get data for high score tables.
|
||||
) -> List["pyrogram.GameHighScore"]:
|
||||
"""Get data for high score tables.
|
||||
|
||||
Args:
|
||||
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".
|
||||
@@ -49,20 +49,19 @@ class GetGameHighScores(BaseClient):
|
||||
Required if inline_message_id is not specified.
|
||||
|
||||
Returns:
|
||||
On success, a :obj:`GameHighScores <pyrogram.GameHighScores>` object is returned.
|
||||
List of :obj:`GameHighScore`: On success.
|
||||
|
||||
Raises:
|
||||
:class:`RPCError <pyrogram.RPCError>` in case of a Telegram RPC error.
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
"""
|
||||
# TODO: inline_message_id
|
||||
|
||||
return pyrogram.GameHighScores._parse(
|
||||
self,
|
||||
self.send(
|
||||
functions.messages.GetGameHighScores(
|
||||
peer=self.resolve_peer(chat_id),
|
||||
id=message_id,
|
||||
user_id=self.resolve_peer(user_id)
|
||||
)
|
||||
r = self.send(
|
||||
functions.messages.GetGameHighScores(
|
||||
peer=self.resolve_peer(chat_id),
|
||||
id=message_id,
|
||||
user_id=self.resolve_peer(user_id)
|
||||
)
|
||||
)
|
||||
|
||||
return pyrogram.List(pyrogram.GameHighScore._parse(self, score, r.users) for score in r.scores)
|
||||
|
@@ -19,8 +19,8 @@
|
||||
from typing import Union
|
||||
|
||||
from pyrogram.api import functions, types
|
||||
from pyrogram.errors import UnknownError
|
||||
from pyrogram.client.ext import BaseClient
|
||||
from pyrogram.errors import UnknownError
|
||||
|
||||
|
||||
class GetInlineBotResults(BaseClient):
|
||||
@@ -32,10 +32,10 @@ class GetInlineBotResults(BaseClient):
|
||||
latitude: float = None,
|
||||
longitude: float = None
|
||||
):
|
||||
"""Use this method to get bot results via inline queries.
|
||||
"""Get bot results via inline queries.
|
||||
You can then send a result using :obj:`send_inline_bot_result <pyrogram.Client.send_inline_bot_result>`
|
||||
|
||||
Args:
|
||||
Parameters:
|
||||
bot (``int`` | ``str``):
|
||||
Unique identifier of the inline bot you want to get results from. You can specify
|
||||
a @username (str) or a bot ID (int).
|
||||
@@ -55,11 +55,11 @@ class GetInlineBotResults(BaseClient):
|
||||
Useful for location-based results only.
|
||||
|
||||
Returns:
|
||||
On Success, :obj:`BotResults <pyrogram.api.types.messages.BotResults>` is returned.
|
||||
:obj:`BotResults <pyrogram.api.types.messages.BotResults>`: On Success.
|
||||
|
||||
Raises:
|
||||
:class:`RPCError <pyrogram.RPCError>` in case of a Telegram RPC error.
|
||||
``TimeoutError`` if the bot fails to answer within 10 seconds
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
TimeoutError: In case the bot fails to answer within 10 seconds.
|
||||
"""
|
||||
# TODO: Don't return the raw type
|
||||
|
||||
|
@@ -27,12 +27,13 @@ class RequestCallbackAnswer(BaseClient):
|
||||
self,
|
||||
chat_id: Union[int, str],
|
||||
message_id: int,
|
||||
callback_data: bytes
|
||||
callback_data: Union[str, bytes],
|
||||
timeout: int = 10
|
||||
):
|
||||
"""Use this method to request a callback answer from bots.
|
||||
"""Request a callback answer from bots.
|
||||
This is the equivalent of clicking an inline button containing callback data.
|
||||
|
||||
Args:
|
||||
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".
|
||||
@@ -41,23 +42,30 @@ class RequestCallbackAnswer(BaseClient):
|
||||
message_id (``int``):
|
||||
The message id the inline keyboard is attached on.
|
||||
|
||||
callback_data (``bytes``):
|
||||
callback_data (``str`` | ``bytes``):
|
||||
Callback data associated with the inline button you want to get the answer from.
|
||||
|
||||
timeout (``int``, *optional*):
|
||||
Timeout in seconds.
|
||||
|
||||
Returns:
|
||||
The answer containing info useful for clients to display a notification at the top of the chat screen
|
||||
or as an alert.
|
||||
|
||||
Raises:
|
||||
:class:`RPCError <pyrogram.RPCError>` in case of a Telegram RPC error.
|
||||
``TimeoutError`` if the bot fails to answer within 10 seconds.
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
TimeoutError: In case the bot fails to answer within 10 seconds.
|
||||
"""
|
||||
|
||||
# Telegram only wants bytes, but we are allowed to pass strings too.
|
||||
data = bytes(callback_data, "utf-8") if isinstance(callback_data, str) else callback_data
|
||||
|
||||
return self.send(
|
||||
functions.messages.GetBotCallbackAnswer(
|
||||
peer=self.resolve_peer(chat_id),
|
||||
msg_id=message_id,
|
||||
data=callback_data
|
||||
data=data
|
||||
),
|
||||
retries=0,
|
||||
timeout=10
|
||||
timeout=timeout
|
||||
)
|
||||
|
@@ -37,9 +37,9 @@ class SendGame(BaseClient):
|
||||
"pyrogram.ForceReply"
|
||||
] = None
|
||||
) -> "pyrogram.Message":
|
||||
"""Use this method to send a game.
|
||||
"""Send a game.
|
||||
|
||||
Args:
|
||||
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".
|
||||
@@ -60,10 +60,10 @@ class SendGame(BaseClient):
|
||||
If not empty, the first button must launch the game.
|
||||
|
||||
Returns:
|
||||
On success, the sent :obj:`Message` is returned.
|
||||
:obj:`Message`: On success, the sent game message is returned.
|
||||
|
||||
Raises:
|
||||
:class:`RPCError <pyrogram.RPCError>` in case of a Telegram RPC error.
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
"""
|
||||
r = self.send(
|
||||
functions.messages.SendMedia(
|
||||
|
@@ -32,10 +32,10 @@ class SendInlineBotResult(BaseClient):
|
||||
reply_to_message_id: int = None,
|
||||
hide_via: bool = None
|
||||
):
|
||||
"""Use this method to send an inline bot result.
|
||||
"""Send an inline bot result.
|
||||
Bot results can be retrieved using :obj:`get_inline_bot_results <pyrogram.Client.get_inline_bot_results>`
|
||||
|
||||
Args:
|
||||
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".
|
||||
@@ -58,10 +58,10 @@ class SendInlineBotResult(BaseClient):
|
||||
Sends the message with *via @bot* hidden.
|
||||
|
||||
Returns:
|
||||
On success, the sent Message is returned.
|
||||
:obj:`Message`: On success, the sent inline result message is returned.
|
||||
|
||||
Raises:
|
||||
:class:`RPCError <pyrogram.RPCError>` in case of a Telegram RPC error.
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
"""
|
||||
return self.send(
|
||||
functions.messages.SendInlineBotResult(
|
||||
|
@@ -32,11 +32,11 @@ class SetGameScore(BaseClient):
|
||||
disable_edit_message: bool = None,
|
||||
chat_id: Union[int, str] = None,
|
||||
message_id: int = None
|
||||
):
|
||||
) -> Union["pyrogram.Message", bool]:
|
||||
# inline_message_id: str = None): TODO Add inline_message_id
|
||||
"""Use this method to set the score of the specified user in a game.
|
||||
"""Set the score of the specified user in a game.
|
||||
|
||||
Args:
|
||||
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".
|
||||
@@ -63,12 +63,11 @@ class SetGameScore(BaseClient):
|
||||
Required if inline_message_id is not specified.
|
||||
|
||||
Returns:
|
||||
On success, if the message was sent by the bot, returns the edited :obj:`Message <pyrogram.Message>`,
|
||||
otherwise returns True.
|
||||
:obj:`Message` | ``bool``: On success, if the message was sent by the bot, the edited message is returned,
|
||||
True otherwise.
|
||||
|
||||
Raises:
|
||||
:class:`RPCError <pyrogram.RPCError>` in case of a Telegram RPC error.
|
||||
:class:`BotScoreNotModified` if the new score is not greater than the user's current score in the chat and force is False.
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
"""
|
||||
r = self.send(
|
||||
functions.messages.SetGameScore(
|
||||
|
@@ -16,14 +16,15 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from .archive_chats import ArchiveChats
|
||||
from .delete_chat_photo import DeleteChatPhoto
|
||||
from .export_chat_invite_link import ExportChatInviteLink
|
||||
from .get_chat import GetChat
|
||||
from .get_chat_member import GetChatMember
|
||||
from .get_chat_members import GetChatMembers
|
||||
from .get_chat_members_count import GetChatMembersCount
|
||||
from .get_chat_preview import GetChatPreview
|
||||
from .get_dialogs import GetDialogs
|
||||
from .get_dialogs_count import GetDialogsCount
|
||||
from .iter_chat_members import IterChatMembers
|
||||
from .iter_dialogs import IterDialogs
|
||||
from .join_chat import JoinChat
|
||||
@@ -36,6 +37,7 @@ from .restrict_chat_member import RestrictChatMember
|
||||
from .set_chat_description import SetChatDescription
|
||||
from .set_chat_photo import SetChatPhoto
|
||||
from .set_chat_title import SetChatTitle
|
||||
from .unarchive_chats import UnarchiveChats
|
||||
from .unban_chat_member import UnbanChatMember
|
||||
from .unpin_chat_message import UnpinChatMessage
|
||||
from .update_chat_username import UpdateChatUsername
|
||||
@@ -60,10 +62,12 @@ class Chats(
|
||||
UnpinChatMessage,
|
||||
GetDialogs,
|
||||
GetChatMembersCount,
|
||||
GetChatPreview,
|
||||
IterDialogs,
|
||||
IterChatMembers,
|
||||
UpdateChatUsername,
|
||||
RestrictChat
|
||||
RestrictChat,
|
||||
GetDialogsCount,
|
||||
ArchiveChats,
|
||||
UnarchiveChats
|
||||
):
|
||||
pass
|
||||
|
58
pyrogram/client/methods/chats/archive_chats.py
Normal file
58
pyrogram/client/methods/chats/archive_chats.py
Normal file
@@ -0,0 +1,58 @@
|
||||
# Pyrogram - Telegram MTProto API Client Library for Python
|
||||
# Copyright (C) 2017-2019 Dan Tès <https://github.com/delivrance>
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
from typing import Union, List
|
||||
|
||||
from pyrogram.api import functions, types
|
||||
from ...ext import BaseClient
|
||||
|
||||
|
||||
class ArchiveChats(BaseClient):
|
||||
def archive_chats(
|
||||
self,
|
||||
chat_ids: Union[int, str, List[Union[int, str]]],
|
||||
) -> bool:
|
||||
"""Archive one or more chats.
|
||||
|
||||
Parameters:
|
||||
chat_ids (``int`` | ``str`` | List[``int``, ``str``]):
|
||||
Unique identifier (int) or username (str) of the target chat.
|
||||
You can also pass a list of ids (int) or usernames (str).
|
||||
|
||||
Returns:
|
||||
``bool``: On success, True is returned.
|
||||
|
||||
Raises:
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
"""
|
||||
|
||||
if not isinstance(chat_ids, list):
|
||||
chat_ids = [chat_ids]
|
||||
|
||||
self.send(
|
||||
functions.folders.EditPeerFolders(
|
||||
folder_peers=[
|
||||
types.InputFolderPeer(
|
||||
peer=self.resolve_peer(chat),
|
||||
folder_id=1
|
||||
) for chat in chat_ids
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
return True
|
@@ -27,7 +27,7 @@ class DeleteChatPhoto(BaseClient):
|
||||
self,
|
||||
chat_id: Union[int, str]
|
||||
) -> bool:
|
||||
"""Use this method to delete a chat photo.
|
||||
"""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.
|
||||
|
||||
@@ -35,15 +35,15 @@ class DeleteChatPhoto(BaseClient):
|
||||
In regular groups (non-supergroups), this method will only work if the "All Members Are Admins"
|
||||
setting is off.
|
||||
|
||||
Args:
|
||||
Parameters:
|
||||
chat_id (``int`` | ``str``):
|
||||
Unique identifier (int) or username (str) of the target chat.
|
||||
|
||||
Returns:
|
||||
True on success.
|
||||
``bool``: True on success.
|
||||
|
||||
Raises:
|
||||
:class:`RPCError <pyrogram.RPCError>` in case of a Telegram RPC error.
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
``ValueError`` if a chat_id belongs to user.
|
||||
"""
|
||||
peer = self.resolve_peer(chat_id)
|
||||
|
@@ -27,27 +27,34 @@ class ExportChatInviteLink(BaseClient):
|
||||
self,
|
||||
chat_id: Union[int, str]
|
||||
) -> str:
|
||||
"""Use this method to generate a new invite link for a chat; any previously generated link is revoked.
|
||||
"""Generate a new invite link for a chat; any previously generated link is revoked.
|
||||
|
||||
You must be an administrator in the chat for this to work and have the appropriate admin rights.
|
||||
|
||||
Args:
|
||||
.. note ::
|
||||
|
||||
Each administrator in a chat generates their own invite links. Bots can't use invite links generated by
|
||||
other administrators. If you want your bot to work with invite links, it will need to generate its own link
|
||||
using this method – after this the link will become available to the bot via the :meth:`~Client.get_chat`
|
||||
method. If your bot needs to generate a new invite link replacing its previous one, use this method again.
|
||||
|
||||
Parameters:
|
||||
chat_id (``int`` | ``str``):
|
||||
Unique identifier for the target chat or username of the target channel/supergroup
|
||||
(in the format @username).
|
||||
|
||||
Returns:
|
||||
On success, the exported invite link as string is returned.
|
||||
``str``: On success, the exported invite link is returned.
|
||||
|
||||
Raises:
|
||||
:class:`RPCError <pyrogram.RPCError>` in case of a Telegram RPC error.
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
"""
|
||||
peer = self.resolve_peer(chat_id)
|
||||
|
||||
if isinstance(peer, types.InputPeerChat):
|
||||
return self.send(
|
||||
functions.messages.ExportChatInvite(
|
||||
peer=peer.chat_id
|
||||
peer=peer
|
||||
)
|
||||
).link
|
||||
elif isinstance(peer, types.InputPeerChannel):
|
||||
|
@@ -27,37 +27,37 @@ class GetChat(BaseClient):
|
||||
def get_chat(
|
||||
self,
|
||||
chat_id: Union[int, str]
|
||||
) -> "pyrogram.Chat":
|
||||
"""Use this method to get up to date information about the chat.
|
||||
) -> Union["pyrogram.Chat", "pyrogram.ChatPreview"]:
|
||||
"""Get up to date information about a chat.
|
||||
|
||||
Information include current name of the user for one-on-one conversations, current username of a user, group or
|
||||
channel, etc.
|
||||
|
||||
Args:
|
||||
Parameters:
|
||||
chat_id (``int`` | ``str``):
|
||||
Unique identifier (int) or username (str) of the target chat.
|
||||
Unique identifier for the target chat in form of a *t.me/joinchat/* link, identifier (int) or username
|
||||
of the target channel/supergroup (in the format @username).
|
||||
|
||||
Returns:
|
||||
On success, a :obj:`Chat <pyrogram.Chat>` object is returned.
|
||||
:obj:`Chat` | :obj:`ChatPreview`: On success, if you've already joined the chat, a chat object is returned,
|
||||
otherwise, a chat preview object is returned.
|
||||
|
||||
Raises:
|
||||
:class:`RPCError <pyrogram.RPCError>` in case of a Telegram RPC error.
|
||||
``ValueError`` in case the chat invite link refers to a chat you haven't joined yet.
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
ValueError: In case the chat invite link points to a chat you haven't joined yet.
|
||||
"""
|
||||
match = self.INVITE_LINK_RE.match(str(chat_id))
|
||||
|
||||
if match:
|
||||
h = match.group(1)
|
||||
|
||||
r = self.send(
|
||||
functions.messages.CheckChatInvite(
|
||||
hash=h
|
||||
hash=match.group(1)
|
||||
)
|
||||
)
|
||||
|
||||
if isinstance(r, types.ChatInvite):
|
||||
raise ValueError("You haven't joined \"t.me/joinchat/{}\" yet".format(h))
|
||||
return pyrogram.ChatPreview._parse(self, r)
|
||||
|
||||
self.fetch_peers([r.chat])
|
||||
|
||||
|
@@ -30,43 +30,52 @@ class GetChatMember(BaseClient):
|
||||
chat_id: Union[int, str],
|
||||
user_id: Union[int, str]
|
||||
) -> "pyrogram.ChatMember":
|
||||
"""Use this method to get information about one member of a chat.
|
||||
"""Get information about one member of a chat.
|
||||
|
||||
Args:
|
||||
Parameters:
|
||||
chat_id (``int`` | ``str``):
|
||||
Unique identifier (int) or username (str) of the target chat.
|
||||
|
||||
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".
|
||||
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:
|
||||
On success, a :obj:`ChatMember <pyrogram.ChatMember>` object is returned.
|
||||
:obj:`ChatMember`: On success, a chat member is returned.
|
||||
|
||||
Raises:
|
||||
:class:`RPCError <pyrogram.RPCError>` in case of a Telegram RPC error.
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
"""
|
||||
chat_id = self.resolve_peer(chat_id)
|
||||
user_id = self.resolve_peer(user_id)
|
||||
chat = self.resolve_peer(chat_id)
|
||||
user = self.resolve_peer(user_id)
|
||||
|
||||
if isinstance(chat_id, types.InputPeerChat):
|
||||
full_chat = self.send(
|
||||
if isinstance(chat, types.InputPeerChat):
|
||||
r = self.send(
|
||||
functions.messages.GetFullChat(
|
||||
chat_id=chat_id.chat_id
|
||||
chat_id=chat.chat_id
|
||||
)
|
||||
)
|
||||
|
||||
for member in pyrogram.ChatMembers._parse(self, full_chat).chat_members:
|
||||
if member.user.is_self:
|
||||
return member
|
||||
members = r.full_chat.participants.participants
|
||||
users = {i.id: i for i in r.users}
|
||||
|
||||
for member in members:
|
||||
member = pyrogram.ChatMember._parse(self, member, users)
|
||||
|
||||
if isinstance(user, types.InputPeerSelf):
|
||||
if member.user.is_self:
|
||||
return member
|
||||
else:
|
||||
if member.user.id == user.user_id:
|
||||
return member
|
||||
else:
|
||||
raise UserNotParticipant
|
||||
elif isinstance(chat_id, types.InputPeerChannel):
|
||||
elif isinstance(chat, types.InputPeerChannel):
|
||||
r = self.send(
|
||||
functions.channels.GetParticipant(
|
||||
channel=chat_id,
|
||||
user_id=user_id
|
||||
channel=chat,
|
||||
user_id=user
|
||||
)
|
||||
)
|
||||
|
||||
|
@@ -18,7 +18,7 @@
|
||||
|
||||
import logging
|
||||
import time
|
||||
from typing import Union
|
||||
from typing import Union, List
|
||||
|
||||
import pyrogram
|
||||
from pyrogram.api import functions, types
|
||||
@@ -45,29 +45,30 @@ class GetChatMembers(BaseClient):
|
||||
limit: int = 200,
|
||||
query: str = "",
|
||||
filter: str = Filters.ALL
|
||||
) -> "pyrogram.ChatMembers":
|
||||
"""Use this method to get a chunk of the members list of a chat.
|
||||
) -> List["pyrogram.ChatMember"]:
|
||||
"""Get a chunk of the members list of a chat.
|
||||
|
||||
You can get up to 200 chat members at once.
|
||||
A chat can be either a basic group, a supergroup or a channel.
|
||||
You must be admin to retrieve the members list of a channel (also known as "subscribers").
|
||||
For a more convenient way of getting chat members see :meth:`iter_chat_members`.
|
||||
For a more convenient way of getting chat members see :meth:`~Client.iter_chat_members`.
|
||||
|
||||
Args:
|
||||
Parameters:
|
||||
chat_id (``int`` | ``str``):
|
||||
Unique identifier (int) or username (str) of the target chat.
|
||||
|
||||
offset (``int``, *optional*):
|
||||
Sequential number of the first member to be returned.
|
||||
Defaults to 0 [1]_.
|
||||
Only applicable to supergroups and channels. Defaults to 0 [1]_.
|
||||
|
||||
limit (``int``, *optional*):
|
||||
Limits the number of members to be retrieved.
|
||||
Only applicable to supergroups and channels.
|
||||
Defaults to 200, which is also the maximum server limit allowed per method call.
|
||||
|
||||
query (``str``, *optional*):
|
||||
Query string to filter members based on their display names and usernames.
|
||||
Defaults to "" (empty string) [2]_.
|
||||
Only applicable to supergroups and channels. Defaults to "" (empty string) [2]_.
|
||||
|
||||
filter (``str``, *optional*):
|
||||
Filter used to select the kind of members you want to retrieve. Only applicable for supergroups
|
||||
@@ -78,6 +79,7 @@ class GetChatMembers(BaseClient):
|
||||
*"bots"* - bots only,
|
||||
*"recent"* - recent members only,
|
||||
*"administrators"* - chat administrators only.
|
||||
Only applicable to supergroups and channels.
|
||||
Defaults to *"all"*.
|
||||
|
||||
.. [1] Server limit: on supergroups, you can get up to 10,000 members for a single query and up to 200 members
|
||||
@@ -86,23 +88,25 @@ class GetChatMembers(BaseClient):
|
||||
.. [2] A query string is applicable only for *"all"*, *"kicked"* and *"restricted"* filters only.
|
||||
|
||||
Returns:
|
||||
On success, a :obj:`ChatMembers` object is returned.
|
||||
List of :obj:`ChatMember`: On success, a list of chat members is returned.
|
||||
|
||||
Raises:
|
||||
:class:`RPCError <pyrogram.RPCError>` in case of a Telegram RPC error.
|
||||
``ValueError`` if you used an invalid filter or a chat_id that belongs to a user.
|
||||
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.
|
||||
"""
|
||||
peer = self.resolve_peer(chat_id)
|
||||
|
||||
if isinstance(peer, types.InputPeerChat):
|
||||
return pyrogram.ChatMembers._parse(
|
||||
self,
|
||||
self.send(
|
||||
functions.messages.GetFullChat(
|
||||
chat_id=peer.chat_id
|
||||
)
|
||||
r = self.send(
|
||||
functions.messages.GetFullChat(
|
||||
chat_id=peer.chat_id
|
||||
)
|
||||
)
|
||||
|
||||
members = r.full_chat.participants.participants
|
||||
users = {i.id: i for i in r.users}
|
||||
|
||||
return pyrogram.List(pyrogram.ChatMember._parse(self, member, users) for member in members)
|
||||
elif isinstance(peer, types.InputPeerChannel):
|
||||
filter = filter.lower()
|
||||
|
||||
@@ -123,18 +127,20 @@ class GetChatMembers(BaseClient):
|
||||
|
||||
while True:
|
||||
try:
|
||||
return pyrogram.ChatMembers._parse(
|
||||
self,
|
||||
self.send(
|
||||
functions.channels.GetParticipants(
|
||||
channel=peer,
|
||||
filter=filter,
|
||||
offset=offset,
|
||||
limit=limit,
|
||||
hash=0
|
||||
)
|
||||
r = self.send(
|
||||
functions.channels.GetParticipants(
|
||||
channel=peer,
|
||||
filter=filter,
|
||||
offset=offset,
|
||||
limit=limit,
|
||||
hash=0
|
||||
)
|
||||
)
|
||||
|
||||
members = r.participants
|
||||
users = {i.id: i for i in r.users}
|
||||
|
||||
return pyrogram.List(pyrogram.ChatMember._parse(self, member, users) for member in members)
|
||||
except FloodWait as e:
|
||||
log.warning("Sleeping for {}s".format(e.x))
|
||||
time.sleep(e.x)
|
||||
|
@@ -27,18 +27,18 @@ class GetChatMembersCount(BaseClient):
|
||||
self,
|
||||
chat_id: Union[int, str]
|
||||
) -> int:
|
||||
"""Use this method to get the number of members in a chat.
|
||||
"""Get the number of members in a chat.
|
||||
|
||||
Args:
|
||||
Parameters:
|
||||
chat_id (``int`` | ``str``):
|
||||
Unique identifier (int) or username (str) of the target chat.
|
||||
|
||||
Returns:
|
||||
On success, an integer is returned.
|
||||
``int``: On success, the chat members count is returned.
|
||||
|
||||
Raises:
|
||||
:class:`RPCError <pyrogram.RPCError>` in case of a Telegram RPC error.
|
||||
``ValueError`` if a chat_id belongs to user.
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
ValueError: In case a chat id belongs to user.
|
||||
"""
|
||||
peer = self.resolve_peer(chat_id)
|
||||
|
||||
|
@@ -1,65 +0,0 @@
|
||||
# Pyrogram - Telegram MTProto API Client Library for Python
|
||||
# Copyright (C) 2017-2019 Dan Tès <https://github.com/delivrance>
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
import pyrogram
|
||||
from pyrogram.api import functions, types
|
||||
from ...ext import BaseClient
|
||||
|
||||
|
||||
class GetChatPreview(BaseClient):
|
||||
def get_chat_preview(
|
||||
self,
|
||||
invite_link: str
|
||||
):
|
||||
"""Use this method to get the preview of a chat using the invite link.
|
||||
|
||||
This method only returns a chat preview, if you want to join a chat use :meth:`join_chat`
|
||||
|
||||
Args:
|
||||
invite_link (``str``):
|
||||
Unique identifier for the target chat in form of *t.me/joinchat/* links.
|
||||
|
||||
Returns:
|
||||
Either :obj:`Chat` or :obj:`ChatPreview`, depending on whether you already joined the chat or not.
|
||||
|
||||
Raises:
|
||||
:class:`RPCError <pyrogram.RPCError>` in case of a Telegram RPC error.
|
||||
``ValueError`` in case of an invalid invite_link.
|
||||
"""
|
||||
match = self.INVITE_LINK_RE.match(invite_link)
|
||||
|
||||
if match:
|
||||
r = self.send(
|
||||
functions.messages.CheckChatInvite(
|
||||
hash=match.group(1)
|
||||
)
|
||||
)
|
||||
|
||||
if isinstance(r, types.ChatInvite):
|
||||
return pyrogram.ChatPreview._parse(self, r)
|
||||
|
||||
if isinstance(r, types.ChatInviteAlready):
|
||||
chat = r.chat
|
||||
|
||||
if isinstance(chat, types.Chat):
|
||||
return pyrogram.Chat._parse_chat_chat(self, chat)
|
||||
|
||||
if isinstance(chat, types.Channel):
|
||||
return pyrogram.Chat._parse_channel_chat(self, chat)
|
||||
else:
|
||||
raise ValueError("The invite_link is invalid")
|
@@ -18,6 +18,7 @@
|
||||
|
||||
import logging
|
||||
import time
|
||||
from typing import List
|
||||
|
||||
import pyrogram
|
||||
from pyrogram.api import functions, types
|
||||
@@ -33,13 +34,13 @@ class GetDialogs(BaseClient):
|
||||
offset_date: int = 0,
|
||||
limit: int = 100,
|
||||
pinned_only: bool = False
|
||||
) -> "pyrogram.Dialogs":
|
||||
"""Use this method to get a chunk of the user's dialogs.
|
||||
) -> List["pyrogram.Dialog"]:
|
||||
"""Get a chunk of the user's dialogs.
|
||||
|
||||
You can get up to 100 dialogs at once.
|
||||
For a more convenient way of getting a user's dialogs see :meth:`iter_dialogs`.
|
||||
For a more convenient way of getting a user's dialogs see :meth:`~Client.iter_dialogs`.
|
||||
|
||||
Args:
|
||||
Parameters:
|
||||
offset_date (``int``):
|
||||
The offset date in Unix time taken from the top message of a :obj:`Dialog`.
|
||||
Defaults to 0. Valid for non-pinned dialogs only.
|
||||
@@ -53,16 +54,16 @@ class GetDialogs(BaseClient):
|
||||
Defaults to False.
|
||||
|
||||
Returns:
|
||||
On success, a :obj:`Dialogs` object is returned.
|
||||
List of :obj:`Dialog`: On success, a list of dialogs is returned.
|
||||
|
||||
Raises:
|
||||
:class:`RPCError <pyrogram.RPCError>` in case of a Telegram RPC error.
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
"""
|
||||
|
||||
while True:
|
||||
try:
|
||||
if pinned_only:
|
||||
r = self.send(functions.messages.GetPinnedDialogs())
|
||||
r = self.send(functions.messages.GetPinnedDialogs(folder_id=0))
|
||||
else:
|
||||
r = self.send(
|
||||
functions.messages.GetDialogs(
|
||||
@@ -80,4 +81,32 @@ class GetDialogs(BaseClient):
|
||||
else:
|
||||
break
|
||||
|
||||
return pyrogram.Dialogs._parse(self, r)
|
||||
users = {i.id: i for i in r.users}
|
||||
chats = {i.id: i for i in r.chats}
|
||||
|
||||
messages = {}
|
||||
|
||||
for message in r.messages:
|
||||
to_id = message.to_id
|
||||
|
||||
if isinstance(to_id, types.PeerUser):
|
||||
if message.out:
|
||||
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))
|
||||
|
||||
messages[chat_id] = pyrogram.Message._parse(self, message, users, chats)
|
||||
|
||||
parsed_dialogs = []
|
||||
|
||||
for dialog in r.dialogs:
|
||||
if not isinstance(dialog, types.Dialog):
|
||||
continue
|
||||
|
||||
parsed_dialogs.append(pyrogram.Dialog._parse(self, dialog, messages, users, chats))
|
||||
|
||||
return pyrogram.List(parsed_dialogs)
|
||||
|
54
pyrogram/client/methods/chats/get_dialogs_count.py
Normal file
54
pyrogram/client/methods/chats/get_dialogs_count.py
Normal file
@@ -0,0 +1,54 @@
|
||||
# Pyrogram - Telegram MTProto API Client Library for Python
|
||||
# Copyright (C) 2017-2019 Dan Tès <https://github.com/delivrance>
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
from pyrogram.api import functions, types
|
||||
from ...ext import BaseClient
|
||||
|
||||
|
||||
class GetDialogsCount(BaseClient):
|
||||
def get_dialogs_count(self, pinned_only: bool = False) -> int:
|
||||
"""Get the total count of your dialogs.
|
||||
|
||||
pinned_only (``bool``, *optional*):
|
||||
Pass True if you want to count only pinned dialogs.
|
||||
Defaults to False.
|
||||
|
||||
Returns:
|
||||
``int``: On success, the dialogs count is returned.
|
||||
|
||||
Raises:
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
"""
|
||||
|
||||
if pinned_only:
|
||||
return len(self.send(functions.messages.GetPinnedDialogs(folder_id=0)).dialogs)
|
||||
else:
|
||||
r = self.send(
|
||||
functions.messages.GetDialogs(
|
||||
offset_date=0,
|
||||
offset_id=0,
|
||||
offset_peer=types.InputPeerEmpty(),
|
||||
limit=1,
|
||||
hash=0
|
||||
)
|
||||
)
|
||||
|
||||
if isinstance(r, types.messages.Dialogs):
|
||||
return len(r.dialogs)
|
||||
else:
|
||||
return r.count
|
@@ -45,13 +45,13 @@ class IterChatMembers(BaseClient):
|
||||
query: str = "",
|
||||
filter: str = Filters.ALL
|
||||
) -> Generator["pyrogram.ChatMember", None, None]:
|
||||
"""Use this method to iterate through the members of a chat sequentially.
|
||||
"""Iterate through the members of a chat sequentially.
|
||||
|
||||
This convenience method does the same as repeatedly calling :meth:`get_chat_members` in a loop, thus saving you
|
||||
This convenience method does the same as repeatedly calling :meth:`~Client.get_chat_members` in a loop, thus saving you
|
||||
from the hassle of setting up boilerplate code. It is useful for getting the whole members list of a chat with
|
||||
a single call.
|
||||
|
||||
Args:
|
||||
Parameters:
|
||||
chat_id (``int`` | ``str``):
|
||||
Unique identifier (int) or username (str) of the target chat.
|
||||
|
||||
@@ -75,10 +75,10 @@ class IterChatMembers(BaseClient):
|
||||
Defaults to *"all"*.
|
||||
|
||||
Returns:
|
||||
A generator yielding :obj:`ChatMember <pyrogram.ChatMember>` objects.
|
||||
``Generator``: A generator yielding :obj:`ChatMember` objects.
|
||||
|
||||
Raises:
|
||||
:class:`RPCError <pyrogram.RPCError>` in case of a Telegram RPC error.
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
"""
|
||||
current = 0
|
||||
yielded = set()
|
||||
@@ -106,7 +106,7 @@ class IterChatMembers(BaseClient):
|
||||
limit=limit,
|
||||
query=q,
|
||||
filter=filter
|
||||
).chat_members
|
||||
)
|
||||
|
||||
if not chat_members:
|
||||
break
|
||||
|
@@ -26,14 +26,15 @@ class IterDialogs(BaseClient):
|
||||
def iter_dialogs(
|
||||
self,
|
||||
offset_date: int = 0,
|
||||
limit: int = 0
|
||||
limit: int = None
|
||||
) -> Generator["pyrogram.Dialog", None, None]:
|
||||
"""Use this method to iterate through a user's dialogs sequentially.
|
||||
"""Iterate through a user's dialogs sequentially.
|
||||
|
||||
This convenience method does the same as repeatedly calling :meth:`get_dialogs` in a loop, thus saving you from
|
||||
the hassle of setting up boilerplate code. It is useful for getting the whole dialogs list with a single call.
|
||||
This convenience method does the same as repeatedly calling :meth:`~Client.get_dialogs` in a loop, thus saving
|
||||
you from the hassle of setting up boilerplate code. It is useful for getting the whole dialogs list with a
|
||||
single call.
|
||||
|
||||
Args:
|
||||
Parameters:
|
||||
offset_date (``int``):
|
||||
The offset date in Unix time taken from the top message of a :obj:`Dialog`.
|
||||
Defaults to 0 (most recent dialog).
|
||||
@@ -43,10 +44,10 @@ class IterDialogs(BaseClient):
|
||||
By default, no limit is applied and all dialogs are returned.
|
||||
|
||||
Returns:
|
||||
A generator yielding :obj:`Dialog <pyrogram.Dialog>` objects.
|
||||
``Generator``: A generator yielding :obj:`Dialog` objects.
|
||||
|
||||
Raises:
|
||||
:class:`RPCError <pyrogram.RPCError>` in case of a Telegram RPC error.
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
"""
|
||||
current = 0
|
||||
total = limit or (1 << 31) - 1
|
||||
@@ -54,7 +55,7 @@ class IterDialogs(BaseClient):
|
||||
|
||||
pinned_dialogs = self.get_dialogs(
|
||||
pinned_only=True
|
||||
).dialogs
|
||||
)
|
||||
|
||||
for dialog in pinned_dialogs:
|
||||
yield dialog
|
||||
@@ -68,7 +69,7 @@ class IterDialogs(BaseClient):
|
||||
dialogs = self.get_dialogs(
|
||||
offset_date=offset_date,
|
||||
limit=limit
|
||||
).dialogs
|
||||
)
|
||||
|
||||
if not dialogs:
|
||||
return
|
||||
|
@@ -26,18 +26,18 @@ class JoinChat(BaseClient):
|
||||
self,
|
||||
chat_id: str
|
||||
):
|
||||
"""Use this method to join a group chat or channel.
|
||||
"""Join a group chat or channel.
|
||||
|
||||
Args:
|
||||
Parameters:
|
||||
chat_id (``str``):
|
||||
Unique identifier for the target chat in form of a *t.me/joinchat/* link or username of the target
|
||||
channel/supergroup (in the format @username).
|
||||
|
||||
Returns:
|
||||
On success, a :obj:`Chat <pyrogram.Chat>` object is returned.
|
||||
:obj:`Chat`: On success, a chat object is returned.
|
||||
|
||||
Raises:
|
||||
:class:`RPCError <pyrogram.RPCError>` in case of a Telegram RPC error.
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
"""
|
||||
match = self.INVITE_LINK_RE.match(chat_id)
|
||||
|
||||
|
@@ -30,7 +30,7 @@ class KickChatMember(BaseClient):
|
||||
user_id: Union[int, str],
|
||||
until_date: int = 0
|
||||
) -> Union["pyrogram.Message", bool]:
|
||||
"""Use this method to kick a user from a group, a supergroup or a channel.
|
||||
"""Kick a user from a group, a supergroup or a channel.
|
||||
In the case of supergroups and channels, the user will not be able to return to the group on their own using
|
||||
invite links, etc., unless unbanned first. You must be an administrator in the chat for this to work and must
|
||||
have the appropriate admin rights.
|
||||
@@ -40,7 +40,7 @@ class KickChatMember(BaseClient):
|
||||
off in the target group. Otherwise members may only be removed by the group's creator or by the member
|
||||
that added them.
|
||||
|
||||
Args:
|
||||
Parameters:
|
||||
chat_id (``int`` | ``str``):
|
||||
Unique identifier (int) or username (str) of the target chat.
|
||||
|
||||
@@ -54,10 +54,11 @@ class KickChatMember(BaseClient):
|
||||
considered to be banned forever. Defaults to 0 (ban forever).
|
||||
|
||||
Returns:
|
||||
On success, either True or a service :obj:`Message <pyrogram.Message>` will be returned (when applicable).
|
||||
: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:
|
||||
:class:`RPCError <pyrogram.RPCError>` in case of a Telegram RPC error.
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
"""
|
||||
chat_peer = self.resolve_peer(chat_id)
|
||||
user_peer = self.resolve_peer(user_id)
|
||||
|
@@ -28,9 +28,9 @@ class LeaveChat(BaseClient):
|
||||
chat_id: Union[int, str],
|
||||
delete: bool = False
|
||||
):
|
||||
"""Use this method to leave a group chat or channel.
|
||||
"""Leave a group chat or channel.
|
||||
|
||||
Args:
|
||||
Parameters:
|
||||
chat_id (``int`` | ``str``):
|
||||
Unique identifier for the target chat or username of the target channel/supergroup
|
||||
(in the format @username).
|
||||
@@ -39,7 +39,7 @@ class LeaveChat(BaseClient):
|
||||
Deletes the group chat dialog after leaving (for simple group chats, not supergroups).
|
||||
|
||||
Raises:
|
||||
:class:`RPCError <pyrogram.RPCError>` in case of a Telegram RPC error.
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
"""
|
||||
peer = self.resolve_peer(chat_id)
|
||||
|
||||
|
@@ -29,11 +29,11 @@ class PinChatMessage(BaseClient):
|
||||
message_id: int,
|
||||
disable_notification: bool = None
|
||||
) -> bool:
|
||||
"""Use this method to pin a message in a group, channel or your own chat.
|
||||
"""Pin a message in a group, channel or your own chat.
|
||||
You must be an administrator in the chat for this to work and must have the "can_pin_messages" admin right in
|
||||
the supergroup or "can_edit_messages" admin right in the channel.
|
||||
|
||||
Args:
|
||||
Parameters:
|
||||
chat_id (``int`` | ``str``):
|
||||
Unique identifier (int) or username (str) of the target chat.
|
||||
|
||||
@@ -45,10 +45,10 @@ class PinChatMessage(BaseClient):
|
||||
message. Notifications are always disabled in channels.
|
||||
|
||||
Returns:
|
||||
True on success.
|
||||
``bool``: True on success.
|
||||
|
||||
Raises:
|
||||
:class:`RPCError <pyrogram.RPCError>` in case of a Telegram RPC error.
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
"""
|
||||
self.send(
|
||||
functions.messages.UpdatePinnedMessage(
|
||||
|
@@ -36,12 +36,12 @@ class PromoteChatMember(BaseClient):
|
||||
can_pin_messages: bool = False,
|
||||
can_promote_members: bool = False
|
||||
) -> bool:
|
||||
"""Use this method to promote or demote a user in a supergroup or a channel.
|
||||
"""Promote or demote a user in a supergroup or a channel.
|
||||
|
||||
You must be an administrator in the chat for this to work and must have the appropriate admin rights.
|
||||
Pass False for all boolean parameters to demote a user.
|
||||
|
||||
Args:
|
||||
Parameters:
|
||||
chat_id (``int`` | ``str``):
|
||||
Unique identifier (int) or username (str) of the target chat.
|
||||
|
||||
@@ -76,10 +76,10 @@ class PromoteChatMember(BaseClient):
|
||||
were appointed by him).
|
||||
|
||||
Returns:
|
||||
True on success.
|
||||
``bool``: True on success.
|
||||
|
||||
Raises:
|
||||
:class:`RPCError <pyrogram.RPCError>` in case of a Telegram RPC error.
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
"""
|
||||
self.send(
|
||||
functions.channels.EditAdmin(
|
||||
|
@@ -36,10 +36,10 @@ class RestrictChat(BaseClient):
|
||||
can_invite_users: bool = False,
|
||||
can_pin_messages: bool = False
|
||||
) -> Chat:
|
||||
"""Use this method to restrict a chat.
|
||||
"""Restrict a chat.
|
||||
Pass True for all boolean parameters to lift restrictions from a chat.
|
||||
|
||||
Args:
|
||||
Parameters:
|
||||
chat_id (``int`` | ``str``):
|
||||
Unique identifier (int) or username (str) of the target chat.
|
||||
|
||||
@@ -70,10 +70,10 @@ class RestrictChat(BaseClient):
|
||||
Pass True, if the user can pin messages.
|
||||
|
||||
Returns:
|
||||
On success, a :obj:`Chat <pyrogram.Chat>` object is returned.
|
||||
:obj:`Chat`: On success, a chat object is returned.
|
||||
|
||||
Raises:
|
||||
:class:`RPCError <pyrogram.RPCError>` in case of a Telegram RPC error.
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
"""
|
||||
send_messages = True
|
||||
send_media = True
|
||||
|
@@ -38,12 +38,12 @@ class RestrictChatMember(BaseClient):
|
||||
can_invite_users: bool = False,
|
||||
can_pin_messages: bool = False
|
||||
) -> Chat:
|
||||
"""Use this method to restrict a user in a supergroup.
|
||||
"""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.
|
||||
|
||||
Args:
|
||||
Parameters:
|
||||
chat_id (``int`` | ``str``):
|
||||
Unique identifier (int) or username (str) of the target chat.
|
||||
|
||||
@@ -83,10 +83,10 @@ class RestrictChatMember(BaseClient):
|
||||
Pass True, if the user can pin messages.
|
||||
|
||||
Returns:
|
||||
On success, a :obj:`Chat <pyrogram.Chat>` object is returned.
|
||||
:obj:`Chat`: On success, a chat object is returned.
|
||||
|
||||
Raises:
|
||||
:class:`RPCError <pyrogram.RPCError>` in case of a Telegram RPC error.
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
"""
|
||||
send_messages = True
|
||||
send_media = True
|
||||
|
@@ -28,10 +28,10 @@ class SetChatDescription(BaseClient):
|
||||
chat_id: Union[int, str],
|
||||
description: str
|
||||
) -> bool:
|
||||
"""Use this method to change the description of a supergroup or a channel.
|
||||
"""Change the description of a supergroup or a channel.
|
||||
You must be an administrator in the chat for this to work and must have the appropriate admin rights.
|
||||
|
||||
Args:
|
||||
Parameters:
|
||||
chat_id (``int`` | ``str``):
|
||||
Unique identifier (int) or username (str) of the target chat.
|
||||
|
||||
@@ -39,10 +39,10 @@ class SetChatDescription(BaseClient):
|
||||
New chat description, 0-255 characters.
|
||||
|
||||
Returns:
|
||||
True on success.
|
||||
``bool``: True on success.
|
||||
|
||||
Raises:
|
||||
:class:`RPCError <pyrogram.RPCError>` in case of a Telegram RPC error.
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
``ValueError`` if a chat_id doesn't belong to a supergroup or a channel.
|
||||
"""
|
||||
peer = self.resolve_peer(chat_id)
|
||||
|
@@ -31,7 +31,7 @@ class SetChatPhoto(BaseClient):
|
||||
chat_id: Union[int, str],
|
||||
photo: str
|
||||
) -> bool:
|
||||
"""Use this method to set a new profile photo for the chat.
|
||||
"""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.
|
||||
|
||||
@@ -39,19 +39,19 @@ class SetChatPhoto(BaseClient):
|
||||
In regular groups (non-supergroups), this method will only work if the "All Members Are Admins"
|
||||
setting is off.
|
||||
|
||||
Args:
|
||||
Parameters:
|
||||
chat_id (``int`` | ``str``):
|
||||
Unique identifier (int) or username (str) of the target chat.
|
||||
|
||||
photo (``str``):
|
||||
New chat photo. You can pass a :class:`Photo` id or a file path to upload a new photo.
|
||||
New chat photo. You can pass a :obj:`Photo` id or a file path to upload a new photo.
|
||||
|
||||
Returns:
|
||||
True on success.
|
||||
``bool``: True on success.
|
||||
|
||||
Raises:
|
||||
:class:`RPCError <pyrogram.RPCError>` in case of a Telegram RPC error.
|
||||
``ValueError`` if a chat_id belongs to user.
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
ValueError: if a chat_id belongs to user.
|
||||
"""
|
||||
peer = self.resolve_peer(chat_id)
|
||||
|
||||
|
@@ -28,7 +28,7 @@ class SetChatTitle(BaseClient):
|
||||
chat_id: Union[int, str],
|
||||
title: str
|
||||
) -> bool:
|
||||
"""Use this method to change the title of a chat.
|
||||
"""Change the title of a chat.
|
||||
Titles 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.
|
||||
|
||||
@@ -36,7 +36,7 @@ class SetChatTitle(BaseClient):
|
||||
In regular groups (non-supergroups), this method will only work if the "All Members Are Admins"
|
||||
setting is off.
|
||||
|
||||
Args:
|
||||
Parameters:
|
||||
chat_id (``int`` | ``str``):
|
||||
Unique identifier (int) or username (str) of the target chat.
|
||||
|
||||
@@ -44,11 +44,11 @@ class SetChatTitle(BaseClient):
|
||||
New chat title, 1-255 characters.
|
||||
|
||||
Returns:
|
||||
True on success.
|
||||
``bool``: True on success.
|
||||
|
||||
Raises:
|
||||
:class:`RPCError <pyrogram.RPCError>` in case of a Telegram RPC error.
|
||||
``ValueError`` if a chat_id belongs to user.
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
ValueError: In case a chat id belongs to user.
|
||||
"""
|
||||
peer = self.resolve_peer(chat_id)
|
||||
|
||||
|
58
pyrogram/client/methods/chats/unarchive_chats.py
Normal file
58
pyrogram/client/methods/chats/unarchive_chats.py
Normal file
@@ -0,0 +1,58 @@
|
||||
# Pyrogram - Telegram MTProto API Client Library for Python
|
||||
# Copyright (C) 2017-2019 Dan Tès <https://github.com/delivrance>
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
from typing import Union, List
|
||||
|
||||
from pyrogram.api import functions, types
|
||||
from ...ext import BaseClient
|
||||
|
||||
|
||||
class UnarchiveChats(BaseClient):
|
||||
def unarchive_chats(
|
||||
self,
|
||||
chat_ids: Union[int, str, List[Union[int, str]]],
|
||||
) -> bool:
|
||||
"""Unarchive one or more chats.
|
||||
|
||||
Parameters:
|
||||
chat_ids (``int`` | ``str`` | List[``int``, ``str``]):
|
||||
Unique identifier (int) or username (str) of the target chat.
|
||||
You can also pass a list of ids (int) or usernames (str).
|
||||
|
||||
Returns:
|
||||
``bool``: On success, True is returned.
|
||||
|
||||
Raises:
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
"""
|
||||
|
||||
if not isinstance(chat_ids, list):
|
||||
chat_ids = [chat_ids]
|
||||
|
||||
self.send(
|
||||
functions.folders.EditPeerFolders(
|
||||
folder_peers=[
|
||||
types.InputFolderPeer(
|
||||
peer=self.resolve_peer(chat),
|
||||
folder_id=0
|
||||
) for chat in chat_ids
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
return True
|
@@ -28,11 +28,11 @@ class UnbanChatMember(BaseClient):
|
||||
chat_id: Union[int, str],
|
||||
user_id: Union[int, str]
|
||||
) -> bool:
|
||||
"""Use this method to unban a previously kicked user in a supergroup or channel.
|
||||
"""Unban a previously kicked user in a supergroup or channel.
|
||||
The user will **not** return to the group or channel automatically, but will be able to join via link, etc.
|
||||
You must be an administrator for this to work.
|
||||
|
||||
Args:
|
||||
Parameters:
|
||||
chat_id (``int`` | ``str``):
|
||||
Unique identifier (int) or username (str) of the target chat.
|
||||
|
||||
@@ -41,10 +41,10 @@ class UnbanChatMember(BaseClient):
|
||||
For a contact that exists in your Telegram address book you can use his phone number (str).
|
||||
|
||||
Returns:
|
||||
True on success.
|
||||
``bool``: True on success.
|
||||
|
||||
Raises:
|
||||
:class:`RPCError <pyrogram.RPCError>` in case of a Telegram RPC error.
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
"""
|
||||
self.send(
|
||||
functions.channels.EditBanned(
|
||||
|
@@ -27,19 +27,19 @@ class UnpinChatMessage(BaseClient):
|
||||
self,
|
||||
chat_id: Union[int, str]
|
||||
) -> bool:
|
||||
"""Use this method to unpin a message in a group, channel or your own chat.
|
||||
"""Unpin a message in a group, channel or your own chat.
|
||||
You must be an administrator in the chat for this to work and must have the "can_pin_messages" admin
|
||||
right in the supergroup or "can_edit_messages" admin right in the channel.
|
||||
|
||||
Args:
|
||||
Parameters:
|
||||
chat_id (``int`` | ``str``):
|
||||
Unique identifier (int) or username (str) of the target chat.
|
||||
|
||||
Returns:
|
||||
True on success.
|
||||
``bool``: True on success.
|
||||
|
||||
Raises:
|
||||
:class:`RPCError <pyrogram.RPCError>` in case of a Telegram RPC error.
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
"""
|
||||
self.send(
|
||||
functions.messages.UpdatePinnedMessage(
|
||||
|
@@ -28,22 +28,22 @@ class UpdateChatUsername(BaseClient):
|
||||
chat_id: Union[int, str],
|
||||
username: Union[str, None]
|
||||
) -> bool:
|
||||
"""Use this method to update a channel or a supergroup username.
|
||||
"""Update a channel or a supergroup username.
|
||||
|
||||
To update your own username (for users only, not bots) you can use :meth:`update_username`.
|
||||
To update your own username (for users only, not bots) you can use :meth:`~Client.update_username`.
|
||||
|
||||
Args:
|
||||
Parameters:
|
||||
chat_id (``int`` | ``str``)
|
||||
Unique identifier (int) or username (str) of the target chat.
|
||||
username (``str`` | ``None``):
|
||||
Username to set. Pass "" (empty string) or None to remove the username.
|
||||
|
||||
Returns:
|
||||
True on success.
|
||||
``bool``: True on success.
|
||||
|
||||
Raises:
|
||||
:class:`RPCError <pyrogram.RPCError>` in case of a Telegram RPC error.
|
||||
``ValueError`` if a chat_id belongs to a user or chat.
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
ValueError: In case a chat id belongs to a user or chat.
|
||||
"""
|
||||
|
||||
peer = self.resolve_peer(chat_id)
|
||||
|
@@ -19,11 +19,13 @@
|
||||
from .add_contacts import AddContacts
|
||||
from .delete_contacts import DeleteContacts
|
||||
from .get_contacts import GetContacts
|
||||
from .get_contacts_count import GetContactsCount
|
||||
|
||||
|
||||
class Contacts(
|
||||
GetContacts,
|
||||
DeleteContacts,
|
||||
AddContacts
|
||||
AddContacts,
|
||||
GetContactsCount
|
||||
):
|
||||
pass
|
||||
|
@@ -28,17 +28,14 @@ class AddContacts(BaseClient):
|
||||
self,
|
||||
contacts: List["pyrogram.InputPhoneContact"]
|
||||
):
|
||||
"""Use this method to add contacts to your Telegram address book.
|
||||
"""Add contacts to your Telegram address book.
|
||||
|
||||
Args:
|
||||
contacts (List of :obj:`InputPhoneContact <pyrogram.InputPhoneContact>`):
|
||||
Parameters:
|
||||
contacts (List of :obj:`InputPhoneContact`):
|
||||
The contact list to be added
|
||||
|
||||
Returns:
|
||||
On success, the added contacts are returned.
|
||||
|
||||
Raises:
|
||||
:class:`RPCError <pyrogram.RPCError>` in case of a Telegram RPC error.
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
"""
|
||||
imported_contacts = self.send(
|
||||
functions.contacts.ImportContacts(
|
||||
|
@@ -28,18 +28,18 @@ class DeleteContacts(BaseClient):
|
||||
self,
|
||||
ids: List[int]
|
||||
):
|
||||
"""Use this method to delete contacts from your Telegram address book.
|
||||
"""Delete contacts from your Telegram address book.
|
||||
|
||||
Args:
|
||||
Parameters:
|
||||
ids (List of ``int``):
|
||||
A list of unique identifiers for the target users.
|
||||
Can be an ID (int), a username (string) or phone number (string).
|
||||
|
||||
Returns:
|
||||
True on success.
|
||||
``bool``: True on success.
|
||||
|
||||
Raises:
|
||||
:class:`RPCError <pyrogram.RPCError>` in case of a Telegram RPC error.
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
"""
|
||||
contacts = []
|
||||
|
||||
|
@@ -18,6 +18,7 @@
|
||||
|
||||
import logging
|
||||
import time
|
||||
from typing import List
|
||||
|
||||
import pyrogram
|
||||
from pyrogram.api import functions
|
||||
@@ -28,14 +29,15 @@ log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GetContacts(BaseClient):
|
||||
def get_contacts(self):
|
||||
"""Use this method to get contacts from your Telegram address book.
|
||||
def get_contacts(self) -> List["pyrogram.User"]:
|
||||
# TODO: Create a Users object and return that
|
||||
"""Get contacts from your Telegram address book.
|
||||
|
||||
Returns:
|
||||
On success, a list of :obj:`User` objects is returned.
|
||||
List of :obj:`User`: On success, a list of users is returned.
|
||||
|
||||
Raises:
|
||||
:class:`RPCError <pyrogram.RPCError>` in case of a Telegram RPC error.
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
"""
|
||||
while True:
|
||||
try:
|
||||
@@ -45,4 +47,4 @@ class GetContacts(BaseClient):
|
||||
time.sleep(e.x)
|
||||
else:
|
||||
log.info("Total contacts: {}".format(self.session_storage.contacts_count()))
|
||||
return [pyrogram.User._parse(self, user) for user in contacts.users]
|
||||
return pyrogram.List(pyrogram.User._parse(self, user) for user in contacts.users)
|
||||
|
34
pyrogram/client/methods/contacts/get_contacts_count.py
Normal file
34
pyrogram/client/methods/contacts/get_contacts_count.py
Normal file
@@ -0,0 +1,34 @@
|
||||
# Pyrogram - Telegram MTProto API Client Library for Python
|
||||
# Copyright (C) 2017-2019 Dan Tès <https://github.com/delivrance>
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
from pyrogram.api import functions
|
||||
from ...ext import BaseClient
|
||||
|
||||
|
||||
class GetContactsCount(BaseClient):
|
||||
def get_contacts_count(self) -> int:
|
||||
"""Get the total count of contacts from your Telegram address book.
|
||||
|
||||
Returns:
|
||||
``int``: On success, the contacts count is returned.
|
||||
|
||||
Raises:
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
"""
|
||||
|
||||
return len(self.send(functions.contacts.GetContacts(hash=0)).contacts)
|
@@ -21,6 +21,7 @@ from .on_deleted_messages import OnDeletedMessages
|
||||
from .on_disconnect import OnDisconnect
|
||||
from .on_inline_query import OnInlineQuery
|
||||
from .on_message import OnMessage
|
||||
from .on_poll import OnPoll
|
||||
from .on_raw_update import OnRawUpdate
|
||||
from .on_user_status import OnUserStatus
|
||||
|
||||
@@ -32,6 +33,7 @@ class Decorators(
|
||||
OnRawUpdate,
|
||||
OnDisconnect,
|
||||
OnUserStatus,
|
||||
OnInlineQuery
|
||||
OnInlineQuery,
|
||||
OnPoll
|
||||
):
|
||||
pass
|
||||
|
@@ -30,11 +30,13 @@ class OnCallbackQuery(BaseClient):
|
||||
filters=None,
|
||||
group: int = 0
|
||||
) -> callable:
|
||||
"""Use this decorator to automatically register a function for handling callback queries.
|
||||
This does the same thing as :meth:`add_handler` using the :class:`CallbackQueryHandler`.
|
||||
"""Decorator for handling callback queries.
|
||||
|
||||
Args:
|
||||
filters (:obj:`Filters <pyrogram.Filters>`):
|
||||
This does the same thing as :meth:`~pyrogram.Client.add_handler` using the
|
||||
:obj:`~pyrogram.CallbackQueryHandler`.
|
||||
|
||||
Parameters:
|
||||
filters (:obj:`~pyrogram.Filters`, *optional*):
|
||||
Pass one or more filters to allow only a subset of callback queries to be passed
|
||||
in your function.
|
||||
|
||||
|
@@ -30,11 +30,13 @@ class OnDeletedMessages(BaseClient):
|
||||
filters=None,
|
||||
group: int = 0
|
||||
) -> callable:
|
||||
"""Use this decorator to automatically register a function for handling deleted messages.
|
||||
This does the same thing as :meth:`add_handler` using the :class:`DeletedMessagesHandler`.
|
||||
"""Decorator for handling deleted messages.
|
||||
|
||||
Args:
|
||||
filters (:obj:`Filters <pyrogram.Filters>`):
|
||||
This does the same thing as :meth:`~pyrogram.Client.add_handler` using the
|
||||
:obj:`~pyrogram.DeletedMessagesHandler`.
|
||||
|
||||
Parameters:
|
||||
filters (:obj:`~pyrogram.Filters`, *optional*):
|
||||
Pass one or more filters to allow only a subset of messages to be passed
|
||||
in your function.
|
||||
|
||||
|
@@ -23,8 +23,9 @@ from ...ext import BaseClient
|
||||
|
||||
class OnDisconnect(BaseClient):
|
||||
def on_disconnect(self=None) -> callable:
|
||||
"""Use this decorator to automatically register a function for handling disconnections.
|
||||
This does the same thing as :meth:`add_handler` using the :class:`DisconnectHandler`.
|
||||
"""Decorator for handling disconnections.
|
||||
|
||||
This does the same thing as :meth:`~pyrogram.Client.add_handler` using the :obj:`~pyrogram.DisconnectHandler`.
|
||||
"""
|
||||
|
||||
def decorator(func: callable) -> Handler:
|
||||
|
@@ -30,11 +30,12 @@ class OnInlineQuery(BaseClient):
|
||||
filters=None,
|
||||
group: int = 0
|
||||
) -> callable:
|
||||
"""Use this decorator to automatically register a function for handling inline queries.
|
||||
This does the same thing as :meth:`add_handler` using the :class:`InlineQueryHandler`.
|
||||
"""Decorator for handling inline queries.
|
||||
|
||||
Args:
|
||||
filters (:obj:`Filters <pyrogram.Filters>`):
|
||||
This does the same thing as :meth:`~pyrogram.Client.add_handler` using the :obj:`~pyrogram.InlineQueryHandler`.
|
||||
|
||||
Parameters:
|
||||
filters (:obj:`~pyrogram.Filters`, *optional*):
|
||||
Pass one or more filters to allow only a subset of inline queries to be passed
|
||||
in your function.
|
||||
|
||||
|
@@ -30,11 +30,12 @@ class OnMessage(BaseClient):
|
||||
filters=None,
|
||||
group: int = 0
|
||||
) -> callable:
|
||||
"""Use this decorator to automatically register a function for handling messages.
|
||||
This does the same thing as :meth:`add_handler` using the :class:`MessageHandler`.
|
||||
"""Decorator for handling messages.
|
||||
|
||||
Args:
|
||||
filters (:obj:`Filters <pyrogram.Filters>`):
|
||||
This does the same thing as :meth:`~pyrogram.Client.add_handler` using the :obj:`~pyrogram.MessageHandler`.
|
||||
|
||||
Parameters:
|
||||
filters (:obj:`~pyrogram.Filters`, *optional*):
|
||||
Pass one or more filters to allow only a subset of messages to be passed
|
||||
in your function.
|
||||
|
||||
|
60
pyrogram/client/methods/decorators/on_poll.py
Normal file
60
pyrogram/client/methods/decorators/on_poll.py
Normal file
@@ -0,0 +1,60 @@
|
||||
# Pyrogram - Telegram MTProto API Client Library for Python
|
||||
# Copyright (C) 2017-2019 Dan Tès <https://github.com/delivrance>
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
from typing import Tuple
|
||||
|
||||
import pyrogram
|
||||
from pyrogram.client.filters.filter import Filter
|
||||
from pyrogram.client.handlers.handler import Handler
|
||||
from ...ext import BaseClient
|
||||
|
||||
|
||||
class OnPoll(BaseClient):
|
||||
def on_poll(
|
||||
self=None,
|
||||
filters=None,
|
||||
group: int = 0
|
||||
) -> callable:
|
||||
"""Decorator for handling poll updates.
|
||||
|
||||
This does the same thing as :meth:`~pyrogram.Client.add_handler` using the :obj:`~pyrogram.PollHandler`.
|
||||
|
||||
Parameters:
|
||||
filters (:obj:`~pyrogram.Filters`, *optional*):
|
||||
Pass one or more filters to allow only a subset of polls to be passed
|
||||
in your function.
|
||||
|
||||
group (``int``, *optional*):
|
||||
The group identifier, defaults to 0.
|
||||
"""
|
||||
|
||||
def decorator(func: callable) -> Tuple[Handler, int]:
|
||||
if isinstance(func, tuple):
|
||||
func = func[0].callback
|
||||
|
||||
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 decorator
|
@@ -28,10 +28,11 @@ class OnRawUpdate(BaseClient):
|
||||
self=None,
|
||||
group: int = 0
|
||||
) -> callable:
|
||||
"""Use this decorator to automatically register a function for handling raw updates.
|
||||
This does the same thing as :meth:`add_handler` using the :class:`RawUpdateHandler`.
|
||||
"""Decorator for handling raw updates.
|
||||
|
||||
Args:
|
||||
This does the same thing as :meth:`~pyrogram.Client.add_handler` using the :obj:`~pyrogram.RawUpdateHandler`.
|
||||
|
||||
Parameters:
|
||||
group (``int``, *optional*):
|
||||
The group identifier, defaults to 0.
|
||||
"""
|
||||
|
@@ -30,11 +30,11 @@ class OnUserStatus(BaseClient):
|
||||
filters=None,
|
||||
group: int = 0
|
||||
) -> callable:
|
||||
"""Use this decorator to automatically register a function for handling user status updates.
|
||||
This does the same thing as :meth:`add_handler` using the :class:`UserStatusHandler`.
|
||||
"""Decorator for handling user status updates.
|
||||
This does the same thing as :meth:`~pyrogram.Client.add_handler` using the :obj:`~pyrogram.UserStatusHandler`.
|
||||
|
||||
Args:
|
||||
filters (:obj:`Filters <pyrogram.Filters>`):
|
||||
Parameters:
|
||||
filters (:obj:`~pyrogram.Filters`, *optional*):
|
||||
Pass one or more filters to allow only a subset of UserStatus updated to be passed in your function.
|
||||
|
||||
group (``int``, *optional*):
|
||||
|
@@ -16,18 +16,24 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from .close_poll import ClosePoll
|
||||
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
|
||||
from .edit_message_text import EditMessageText
|
||||
from .forward_messages import ForwardMessages
|
||||
from .get_history import GetHistory
|
||||
from .get_history_count import GetHistoryCount
|
||||
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
|
||||
@@ -44,6 +50,7 @@ from .send_venue import SendVenue
|
||||
from .send_video import SendVideo
|
||||
from .send_video_note import SendVideoNote
|
||||
from .send_voice import SendVoice
|
||||
from .stop_poll import StopPoll
|
||||
from .vote_poll import VotePoll
|
||||
|
||||
|
||||
@@ -72,10 +79,17 @@ class Messages(
|
||||
SendVoice,
|
||||
SendPoll,
|
||||
VotePoll,
|
||||
ClosePoll,
|
||||
StopPoll,
|
||||
RetractVote,
|
||||
DownloadMedia,
|
||||
IterHistory,
|
||||
SendCachedMedia
|
||||
SendCachedMedia,
|
||||
GetHistoryCount,
|
||||
SendAnimatedSticker,
|
||||
ReadHistory,
|
||||
EditInlineText,
|
||||
EditInlineCaption,
|
||||
EditInlineMedia,
|
||||
EditInlineReplyMarkup
|
||||
):
|
||||
pass
|
||||
|
@@ -29,9 +29,9 @@ class DeleteMessages(BaseClient):
|
||||
message_ids: Iterable[int],
|
||||
revoke: bool = True
|
||||
) -> bool:
|
||||
"""Use this method to delete messages, including service messages.
|
||||
"""Delete messages, including service messages.
|
||||
|
||||
Args:
|
||||
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".
|
||||
@@ -48,27 +48,29 @@ class DeleteMessages(BaseClient):
|
||||
Defaults to True.
|
||||
|
||||
Returns:
|
||||
True on success.
|
||||
``bool``: True on success, False otherwise.
|
||||
|
||||
Raises:
|
||||
:class:`RPCError <pyrogram.RPCError>` in case of a Telegram RPC error.
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
"""
|
||||
peer = self.resolve_peer(chat_id)
|
||||
message_ids = list(message_ids) if not isinstance(message_ids, int) else [message_ids]
|
||||
|
||||
if isinstance(peer, types.InputPeerChannel):
|
||||
self.send(
|
||||
r = self.send(
|
||||
functions.channels.DeleteMessages(
|
||||
channel=peer,
|
||||
id=message_ids
|
||||
)
|
||||
)
|
||||
else:
|
||||
self.send(
|
||||
r = self.send(
|
||||
functions.messages.DeleteMessages(
|
||||
id=message_ids,
|
||||
revoke=revoke or None
|
||||
)
|
||||
)
|
||||
|
||||
return True
|
||||
# Deleting messages you don't have right onto, won't raise any error.
|
||||
# Check for pts_count, which is 0 in case deletes fail.
|
||||
return bool(r.pts_count)
|
||||
|
@@ -16,26 +16,34 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import binascii
|
||||
import os
|
||||
import struct
|
||||
import time
|
||||
from datetime import datetime
|
||||
from threading import Event
|
||||
from typing import Union
|
||||
|
||||
import pyrogram
|
||||
from pyrogram.client.ext import BaseClient
|
||||
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 = ()
|
||||
) -> Union[str, None]:
|
||||
"""Use this method to download the media from a message.
|
||||
"""Download the media from a message.
|
||||
|
||||
Args:
|
||||
message (:obj:`Message <pyrogram.Message>` | ``str``):
|
||||
Parameters:
|
||||
message (:obj:`Message` | ``str``):
|
||||
Pass a Message containing the media, the media itself (message.audio, message.video, ...) or
|
||||
the file id as string.
|
||||
|
||||
@@ -59,7 +67,7 @@ class DownloadMedia(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``):
|
||||
@@ -73,75 +81,133 @@ class DownloadMedia(BaseClient):
|
||||
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.
|
||||
In case the download is deliberately stopped with :meth:`stop_transmission`, None is returned as well.
|
||||
``str`` | ``None``: On success, the absolute path of the downloaded file is returned, otherwise, in case
|
||||
the download failed or was deliberately stopped with :meth:`~Client.stop_transmission`, None is returned.
|
||||
|
||||
Raises:
|
||||
:class:`RPCError <pyrogram.RPCError>` in case of a Telegram RPC error.
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
``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")
|
||||
|
||||
media_file_name = None
|
||||
file_size = None
|
||||
mime_type = None
|
||||
date = None
|
||||
|
||||
if isinstance(message, pyrogram.Message):
|
||||
if message.photo:
|
||||
media = pyrogram.Document(
|
||||
file_id=message.photo.sizes[-1].file_id,
|
||||
file_size=message.photo.sizes[-1].file_size,
|
||||
mime_type="",
|
||||
date=message.photo.date,
|
||||
client=self
|
||||
)
|
||||
elif message.audio:
|
||||
media = message.audio
|
||||
elif message.document:
|
||||
media = message.document
|
||||
elif message.video:
|
||||
media = message.video
|
||||
elif message.voice:
|
||||
media = message.voice
|
||||
elif message.video_note:
|
||||
media = message.video_note
|
||||
elif message.sticker:
|
||||
media = message.sticker
|
||||
elif message.animation:
|
||||
media = message.animation
|
||||
for kind in available_media:
|
||||
media = getattr(message, kind, None)
|
||||
|
||||
if media is not None:
|
||||
break
|
||||
else:
|
||||
raise ValueError(error_message)
|
||||
elif isinstance(message, (
|
||||
pyrogram.Photo,
|
||||
pyrogram.PhotoSize,
|
||||
pyrogram.Audio,
|
||||
pyrogram.Document,
|
||||
pyrogram.Video,
|
||||
pyrogram.Voice,
|
||||
pyrogram.VideoNote,
|
||||
pyrogram.Sticker,
|
||||
pyrogram.Animation
|
||||
)):
|
||||
if isinstance(message, pyrogram.Photo):
|
||||
media = pyrogram.Document(
|
||||
file_id=message.sizes[-1].file_id,
|
||||
file_size=message.sizes[-1].file_size,
|
||||
mime_type="",
|
||||
date=message.date,
|
||||
client=self
|
||||
else:
|
||||
media = message
|
||||
|
||||
if isinstance(media, str):
|
||||
file_id_str = media
|
||||
else:
|
||||
file_id_str = media.file_id
|
||||
media_file_name = getattr(media, "file_name", "")
|
||||
file_size = getattr(media, "file_size", None)
|
||||
mime_type = getattr(media, "mime_type", None)
|
||||
date = getattr(media, "date", None)
|
||||
|
||||
data = FileData(
|
||||
file_name=media_file_name,
|
||||
file_size=file_size,
|
||||
mime_type=mime_type,
|
||||
date=date
|
||||
)
|
||||
|
||||
def get_existing_attributes() -> dict:
|
||||
return dict(filter(lambda x: x[1] is not None, data.__dict__.items()))
|
||||
|
||||
try:
|
||||
decoded = utils.decode(file_id_str)
|
||||
media_type = decoded[0]
|
||||
|
||||
if media_type == 1:
|
||||
unpacked = struct.unpack("<iiqqib", decoded)
|
||||
dc_id, peer_id, volume_id, local_id, is_big = unpacked[1:]
|
||||
|
||||
data = FileData(
|
||||
**get_existing_attributes(),
|
||||
media_type=media_type,
|
||||
dc_id=dc_id,
|
||||
peer_id=peer_id,
|
||||
volume_id=volume_id,
|
||||
local_id=local_id,
|
||||
is_big=bool(is_big)
|
||||
)
|
||||
elif media_type in (0, 2, 14):
|
||||
unpacked = struct.unpack("<iiqqc", decoded)
|
||||
dc_id, document_id, access_hash, thumb_size = unpacked[1:]
|
||||
|
||||
data = FileData(
|
||||
**get_existing_attributes(),
|
||||
media_type=media_type,
|
||||
dc_id=dc_id,
|
||||
document_id=document_id,
|
||||
access_hash=access_hash,
|
||||
thumb_size=thumb_size.decode()
|
||||
)
|
||||
elif media_type in (3, 4, 5, 8, 9, 10, 13):
|
||||
unpacked = struct.unpack("<iiqq", decoded)
|
||||
dc_id, document_id, access_hash = unpacked[1:]
|
||||
|
||||
data = FileData(
|
||||
**get_existing_attributes(),
|
||||
media_type=media_type,
|
||||
dc_id=dc_id,
|
||||
document_id=document_id,
|
||||
access_hash=access_hash
|
||||
)
|
||||
else:
|
||||
media = message
|
||||
elif isinstance(message, str):
|
||||
media = pyrogram.Document(
|
||||
file_id=message,
|
||||
file_size=0,
|
||||
mime_type="",
|
||||
client=self
|
||||
)
|
||||
else:
|
||||
raise ValueError(error_message)
|
||||
raise ValueError("Unknown media type: {}".format(file_id_str))
|
||||
except (AssertionError, binascii.Error, struct.error):
|
||||
raise FileIdInvalid from None
|
||||
|
||||
done = Event()
|
||||
path = [None]
|
||||
|
||||
self.download_queue.put((media, 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()
|
||||
|
58
pyrogram/client/methods/messages/edit_inline_caption.py
Normal file
58
pyrogram/client/methods/messages/edit_inline_caption.py
Normal file
@@ -0,0 +1,58 @@
|
||||
# Pyrogram - Telegram MTProto API Client Library for Python
|
||||
# Copyright (C) 2017-2019 Dan Tès <https://github.com/delivrance>
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
)
|
104
pyrogram/client/methods/messages/edit_inline_media.py
Normal file
104
pyrogram/client/methods/messages/edit_inline_media.py
Normal file
@@ -0,0 +1,104 @@
|
||||
# Pyrogram - Telegram MTProto API Client Library for Python
|
||||
# Copyright (C) 2017-2019 Dan Tès <https://github.com/delivrance>
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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)
|
||||
)
|
||||
)
|
50
pyrogram/client/methods/messages/edit_inline_reply_markup.py
Normal file
50
pyrogram/client/methods/messages/edit_inline_reply_markup.py
Normal file
@@ -0,0 +1,50 @@
|
||||
# Pyrogram - Telegram MTProto API Client Library for Python
|
||||
# Copyright (C) 2017-2019 Dan Tès <https://github.com/delivrance>
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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,
|
||||
)
|
||||
)
|
67
pyrogram/client/methods/messages/edit_inline_text.py
Normal file
67
pyrogram/client/methods/messages/edit_inline_text.py
Normal file
@@ -0,0 +1,67 @@
|
||||
# Pyrogram - Telegram MTProto API Client Library for Python
|
||||
# Copyright (C) 2017-2019 Dan Tès <https://github.com/delivrance>
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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)
|
||||
)
|
||||
)
|
@@ -19,7 +19,6 @@
|
||||
from typing import Union
|
||||
|
||||
import pyrogram
|
||||
from pyrogram.api import functions, types
|
||||
from pyrogram.client.ext import BaseClient
|
||||
|
||||
|
||||
@@ -32,9 +31,9 @@ class EditMessageCaption(BaseClient):
|
||||
parse_mode: str = "",
|
||||
reply_markup: "pyrogram.InlineKeyboardMarkup" = None
|
||||
) -> "pyrogram.Message":
|
||||
"""Use this method to edit captions of messages.
|
||||
"""Edit the caption of media messages.
|
||||
|
||||
Args:
|
||||
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".
|
||||
@@ -44,37 +43,25 @@ class EditMessageCaption(BaseClient):
|
||||
Message identifier in the chat specified in chat_id.
|
||||
|
||||
caption (``str``):
|
||||
New caption of the message.
|
||||
New caption of the media message.
|
||||
|
||||
parse_mode (``str``, *optional*):
|
||||
Use :obj:`MARKDOWN <pyrogram.ParseMode.MARKDOWN>` or :obj:`HTML <pyrogram.ParseMode.HTML>`
|
||||
if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your caption.
|
||||
Defaults to Markdown.
|
||||
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:
|
||||
On success, the edited :obj:`Message <pyrogram.Message>` is returned.
|
||||
:obj:`Message`: On success, the edited message is returned.
|
||||
|
||||
Raises:
|
||||
:class:`RPCError <pyrogram.RPCError>` in case of a Telegram RPC error.
|
||||
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(
|
||||
peer=self.resolve_peer(chat_id),
|
||||
id=message_id,
|
||||
reply_markup=reply_markup.write() if reply_markup else None,
|
||||
**style.parse(caption)
|
||||
)
|
||||
return self.edit_message_text(
|
||||
chat_id=chat_id,
|
||||
message_id=message_id,
|
||||
text=caption,
|
||||
parse_mode=parse_mode,
|
||||
reply_markup=reply_markup
|
||||
)
|
||||
|
||||
for i in r.updates:
|
||||
if isinstance(i, (types.UpdateEditMessage, types.UpdateEditChannelMessage)):
|
||||
return pyrogram.Message._parse(
|
||||
self, i.message,
|
||||
{i.id: i for i in r.users},
|
||||
{i.id: i for i in r.chats}
|
||||
)
|
||||
|
@@ -16,14 +16,11 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import binascii
|
||||
import os
|
||||
import struct
|
||||
from typing import Union
|
||||
|
||||
import pyrogram
|
||||
from pyrogram.api import functions, types
|
||||
from pyrogram.errors import FileIdInvalid
|
||||
from pyrogram.client.ext import BaseClient, utils
|
||||
from pyrogram.client.types import (
|
||||
InputMediaPhoto, InputMediaVideo, InputMediaAudio,
|
||||
@@ -40,14 +37,12 @@ class EditMessageMedia(BaseClient):
|
||||
media: InputMedia,
|
||||
reply_markup: "pyrogram.InlineKeyboardMarkup" = None
|
||||
) -> "pyrogram.Message":
|
||||
"""Use this method to edit audio, document, photo, or video messages.
|
||||
"""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. On success, if the edited message was sent
|
||||
by the bot, the edited Message is returned, otherwise True is returned.
|
||||
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.
|
||||
|
||||
Args:
|
||||
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".
|
||||
@@ -56,17 +51,17 @@ class EditMessageMedia(BaseClient):
|
||||
message_id (``int``):
|
||||
Message identifier in the chat specified in chat_id.
|
||||
|
||||
media (:obj:`InputMedia`)
|
||||
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:
|
||||
On success, the edited :obj:`Message <pyrogram.Message>` is returned.
|
||||
:obj:`Message`: On success, the edited message is returned.
|
||||
|
||||
Raises:
|
||||
:class:`RPCError <pyrogram.RPCError>` in case of a Telegram RPC error.
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
"""
|
||||
style = self.html if media.parse_mode.lower() == "html" else self.markdown
|
||||
caption = media.caption
|
||||
@@ -94,36 +89,14 @@ class EditMessageMedia(BaseClient):
|
||||
url=media.media
|
||||
)
|
||||
else:
|
||||
try:
|
||||
decoded = utils.decode(media.media)
|
||||
fmt = "<iiqqqqi" if len(decoded) > 24 else "<iiqq"
|
||||
unpacked = struct.unpack(fmt, decoded)
|
||||
except (AssertionError, binascii.Error, struct.error):
|
||||
raise FileIdInvalid from None
|
||||
else:
|
||||
if unpacked[0] != 2:
|
||||
media_type = BaseClient.MEDIA_TYPE_ID.get(unpacked[0], None)
|
||||
|
||||
if media_type:
|
||||
raise FileIdInvalid("The file_id belongs to a {}".format(media_type))
|
||||
else:
|
||||
raise FileIdInvalid("Unknown media type: {}".format(unpacked[0]))
|
||||
|
||||
media = types.InputMediaPhoto(
|
||||
id=types.InputPhoto(
|
||||
id=unpacked[2],
|
||||
access_hash=unpacked[3],
|
||||
file_reference=b""
|
||||
)
|
||||
)
|
||||
|
||||
if isinstance(media, InputMediaVideo):
|
||||
media = utils.get_input_media_from_file_id(media.media, 2)
|
||||
elif isinstance(media, InputMediaVideo):
|
||||
if os.path.exists(media.media):
|
||||
media = self.send(
|
||||
functions.messages.UploadMedia(
|
||||
peer=self.resolve_peer(chat_id),
|
||||
media=types.InputMediaUploadedDocument(
|
||||
mime_type="video/mp4",
|
||||
mime_type=self.guess_mime_type(media.media) or "video/mp4",
|
||||
thumb=None if media.thumb is None else self.save_file(media.thumb),
|
||||
file=self.save_file(media.media),
|
||||
attributes=[
|
||||
@@ -153,36 +126,14 @@ class EditMessageMedia(BaseClient):
|
||||
url=media.media
|
||||
)
|
||||
else:
|
||||
try:
|
||||
decoded = utils.decode(media.media)
|
||||
fmt = "<iiqqqqi" if len(decoded) > 24 else "<iiqq"
|
||||
unpacked = struct.unpack(fmt, decoded)
|
||||
except (AssertionError, binascii.Error, struct.error):
|
||||
raise FileIdInvalid from None
|
||||
else:
|
||||
if unpacked[0] != 4:
|
||||
media_type = BaseClient.MEDIA_TYPE_ID.get(unpacked[0], None)
|
||||
|
||||
if media_type:
|
||||
raise FileIdInvalid("The file_id belongs to a {}".format(media_type))
|
||||
else:
|
||||
raise FileIdInvalid("Unknown media type: {}".format(unpacked[0]))
|
||||
|
||||
media = types.InputMediaDocument(
|
||||
id=types.InputDocument(
|
||||
id=unpacked[2],
|
||||
access_hash=unpacked[3],
|
||||
file_reference=b""
|
||||
)
|
||||
)
|
||||
|
||||
if isinstance(media, InputMediaAudio):
|
||||
media = utils.get_input_media_from_file_id(media.media, 4)
|
||||
elif isinstance(media, InputMediaAudio):
|
||||
if os.path.exists(media.media):
|
||||
media = self.send(
|
||||
functions.messages.UploadMedia(
|
||||
peer=self.resolve_peer(chat_id),
|
||||
media=types.InputMediaUploadedDocument(
|
||||
mime_type="audio/mpeg",
|
||||
mime_type=self.guess_mime_type(media.media) or "audio/mpeg",
|
||||
thumb=None if media.thumb is None else self.save_file(media.thumb),
|
||||
file=self.save_file(media.media),
|
||||
attributes=[
|
||||
@@ -211,36 +162,14 @@ class EditMessageMedia(BaseClient):
|
||||
url=media.media
|
||||
)
|
||||
else:
|
||||
try:
|
||||
decoded = utils.decode(media.media)
|
||||
fmt = "<iiqqqqi" if len(decoded) > 24 else "<iiqq"
|
||||
unpacked = struct.unpack(fmt, decoded)
|
||||
except (AssertionError, binascii.Error, struct.error):
|
||||
raise FileIdInvalid from None
|
||||
else:
|
||||
if unpacked[0] != 9:
|
||||
media_type = BaseClient.MEDIA_TYPE_ID.get(unpacked[0], None)
|
||||
|
||||
if media_type:
|
||||
raise FileIdInvalid("The file_id belongs to a {}".format(media_type))
|
||||
else:
|
||||
raise FileIdInvalid("Unknown media type: {}".format(unpacked[0]))
|
||||
|
||||
media = types.InputMediaDocument(
|
||||
id=types.InputDocument(
|
||||
id=unpacked[2],
|
||||
access_hash=unpacked[3],
|
||||
file_reference=b""
|
||||
)
|
||||
)
|
||||
|
||||
if isinstance(media, InputMediaAnimation):
|
||||
media = utils.get_input_media_from_file_id(media.media, 9)
|
||||
elif isinstance(media, InputMediaAnimation):
|
||||
if os.path.exists(media.media):
|
||||
media = self.send(
|
||||
functions.messages.UploadMedia(
|
||||
peer=self.resolve_peer(chat_id),
|
||||
media=types.InputMediaUploadedDocument(
|
||||
mime_type="video/mp4",
|
||||
mime_type=self.guess_mime_type(media.media) or "video/mp4",
|
||||
thumb=None if media.thumb is None else self.save_file(media.thumb),
|
||||
file=self.save_file(media.media),
|
||||
attributes=[
|
||||
@@ -271,36 +200,14 @@ class EditMessageMedia(BaseClient):
|
||||
url=media.media
|
||||
)
|
||||
else:
|
||||
try:
|
||||
decoded = utils.decode(media.media)
|
||||
fmt = "<iiqqqqi" if len(decoded) > 24 else "<iiqq"
|
||||
unpacked = struct.unpack(fmt, decoded)
|
||||
except (AssertionError, binascii.Error, struct.error):
|
||||
raise FileIdInvalid from None
|
||||
else:
|
||||
if unpacked[0] != 10:
|
||||
media_type = BaseClient.MEDIA_TYPE_ID.get(unpacked[0], None)
|
||||
|
||||
if media_type:
|
||||
raise FileIdInvalid("The file_id belongs to a {}".format(media_type))
|
||||
else:
|
||||
raise FileIdInvalid("Unknown media type: {}".format(unpacked[0]))
|
||||
|
||||
media = types.InputMediaDocument(
|
||||
id=types.InputDocument(
|
||||
id=unpacked[2],
|
||||
access_hash=unpacked[3],
|
||||
file_reference=b""
|
||||
)
|
||||
)
|
||||
|
||||
if isinstance(media, InputMediaDocument):
|
||||
media = utils.get_input_media_from_file_id(media.media, 10)
|
||||
elif isinstance(media, InputMediaDocument):
|
||||
if os.path.exists(media.media):
|
||||
media = self.send(
|
||||
functions.messages.UploadMedia(
|
||||
peer=self.resolve_peer(chat_id),
|
||||
media=types.InputMediaUploadedDocument(
|
||||
mime_type="application/zip",
|
||||
mime_type=self.guess_mime_type(media.media) or "application/zip",
|
||||
thumb=None if media.thumb is None else self.save_file(media.thumb),
|
||||
file=self.save_file(media.media),
|
||||
attributes=[
|
||||
@@ -324,35 +231,14 @@ class EditMessageMedia(BaseClient):
|
||||
url=media.media
|
||||
)
|
||||
else:
|
||||
try:
|
||||
decoded = utils.decode(media.media)
|
||||
fmt = "<iiqqqqi" if len(decoded) > 24 else "<iiqq"
|
||||
unpacked = struct.unpack(fmt, decoded)
|
||||
except (AssertionError, binascii.Error, struct.error):
|
||||
raise FileIdInvalid from None
|
||||
else:
|
||||
if unpacked[0] not in (5, 10):
|
||||
media_type = BaseClient.MEDIA_TYPE_ID.get(unpacked[0], None)
|
||||
|
||||
if media_type:
|
||||
raise FileIdInvalid("The file_id belongs to a {}".format(media_type))
|
||||
else:
|
||||
raise FileIdInvalid("Unknown media type: {}".format(unpacked[0]))
|
||||
|
||||
media = types.InputMediaDocument(
|
||||
id=types.InputDocument(
|
||||
id=unpacked[2],
|
||||
access_hash=unpacked[3],
|
||||
file_reference=b""
|
||||
)
|
||||
)
|
||||
media = utils.get_input_media_from_file_id(media.media, 5)
|
||||
|
||||
r = self.send(
|
||||
functions.messages.EditMessage(
|
||||
peer=self.resolve_peer(chat_id),
|
||||
id=message_id,
|
||||
reply_markup=reply_markup.write() if reply_markup else None,
|
||||
media=media,
|
||||
reply_markup=reply_markup.write() if reply_markup else None,
|
||||
**style.parse(caption)
|
||||
)
|
||||
)
|
||||
|
@@ -28,11 +28,11 @@ class EditMessageReplyMarkup(BaseClient):
|
||||
self,
|
||||
chat_id: Union[int, str],
|
||||
message_id: int,
|
||||
reply_markup: "pyrogram.InlineKeyboardMarkup" = None
|
||||
reply_markup: "pyrogram.InlineKeyboardMarkup" = None,
|
||||
) -> "pyrogram.Message":
|
||||
"""Use this method to 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.
|
||||
|
||||
Args:
|
||||
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".
|
||||
@@ -45,18 +45,16 @@ class EditMessageReplyMarkup(BaseClient):
|
||||
An InlineKeyboardMarkup object.
|
||||
|
||||
Returns:
|
||||
On success, if edited message is sent by the bot, the edited
|
||||
:obj:`Message <pyrogram.Message>` is returned, otherwise True is returned.
|
||||
:obj:`Message`: On success, the edited message is returned.
|
||||
|
||||
Raises:
|
||||
:class:`RPCError <pyrogram.RPCError>` in case of a Telegram RPC error.
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
"""
|
||||
|
||||
r = self.send(
|
||||
functions.messages.EditMessage(
|
||||
peer=self.resolve_peer(chat_id),
|
||||
id=message_id,
|
||||
reply_markup=reply_markup.write() if reply_markup else None
|
||||
reply_markup=reply_markup.write() if reply_markup else None,
|
||||
)
|
||||
)
|
||||
|
||||
|
@@ -33,9 +33,9 @@ class EditMessageText(BaseClient):
|
||||
disable_web_page_preview: bool = None,
|
||||
reply_markup: "pyrogram.InlineKeyboardMarkup" = None
|
||||
) -> "pyrogram.Message":
|
||||
"""Use this method to edit text messages.
|
||||
"""Edit the text of messages.
|
||||
|
||||
Args:
|
||||
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".
|
||||
@@ -48,9 +48,8 @@ class EditMessageText(BaseClient):
|
||||
New text of the message.
|
||||
|
||||
parse_mode (``str``, *optional*):
|
||||
Use :obj:`MARKDOWN <pyrogram.ParseMode.MARKDOWN>` or :obj:`HTML <pyrogram.ParseMode.HTML>`
|
||||
if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your message.
|
||||
Defaults to Markdown.
|
||||
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.
|
||||
@@ -59,10 +58,10 @@ class EditMessageText(BaseClient):
|
||||
An InlineKeyboardMarkup object.
|
||||
|
||||
Returns:
|
||||
On success, the edited :obj:`Message <pyrogram.Message>` is returned.
|
||||
:obj:`Message`: On success, the edited message is returned.
|
||||
|
||||
Raises:
|
||||
:class:`RPCError <pyrogram.RPCError>` in case of a Telegram RPC error.
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
"""
|
||||
style = self.html if parse_mode.lower() == "html" else self.markdown
|
||||
|
||||
|
@@ -16,7 +16,7 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from typing import Union, Iterable
|
||||
from typing import Union, Iterable, List
|
||||
|
||||
import pyrogram
|
||||
from pyrogram.api import functions, types
|
||||
@@ -28,14 +28,14 @@ class ForwardMessages(BaseClient):
|
||||
self,
|
||||
chat_id: Union[int, str],
|
||||
from_chat_id: Union[int, str],
|
||||
message_ids: Iterable[int],
|
||||
message_ids: Union[int, Iterable[int]],
|
||||
disable_notification: bool = None,
|
||||
as_copy: bool = False,
|
||||
remove_caption: bool = False
|
||||
) -> "pyrogram.Messages":
|
||||
"""Use this method to forward messages of any kind.
|
||||
) -> List["pyrogram.Message"]:
|
||||
"""Forward messages of any kind.
|
||||
|
||||
Args:
|
||||
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".
|
||||
@@ -64,13 +64,12 @@ class ForwardMessages(BaseClient):
|
||||
Defaults to False.
|
||||
|
||||
Returns:
|
||||
On success and in case *message_ids* was an iterable, the returned value will be a list of the forwarded
|
||||
:obj:`Messages <pyrogram.Message>` even if a list contains just one element, otherwise if
|
||||
*message_ids* was an integer, the single forwarded :obj:`Message <pyrogram.Message>`
|
||||
is returned.
|
||||
:obj:`Message` | List of :obj:`Message`: In case *message_ids* was an integer, the single forwarded message
|
||||
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:
|
||||
:class:`RPCError <pyrogram.RPCError>` in case of a Telegram RPC error.
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
"""
|
||||
|
||||
is_iterable = not isinstance(message_ids, int)
|
||||
@@ -80,9 +79,9 @@ class ForwardMessages(BaseClient):
|
||||
forwarded_messages = []
|
||||
|
||||
for chunk in [message_ids[i:i + 200] for i in range(0, len(message_ids), 200)]:
|
||||
messages = self.get_messages(chat_id=from_chat_id, message_ids=chunk) # type: pyrogram.Messages
|
||||
messages = self.get_messages(chat_id=from_chat_id, message_ids=chunk)
|
||||
|
||||
for message in messages.messages:
|
||||
for message in messages:
|
||||
forwarded_messages.append(
|
||||
message.forward(
|
||||
chat_id,
|
||||
@@ -92,11 +91,7 @@ class ForwardMessages(BaseClient):
|
||||
)
|
||||
)
|
||||
|
||||
return pyrogram.Messages(
|
||||
client=self,
|
||||
total_count=len(forwarded_messages),
|
||||
messages=forwarded_messages
|
||||
) if is_iterable else forwarded_messages[0]
|
||||
return pyrogram.List(forwarded_messages) if is_iterable else forwarded_messages[0]
|
||||
else:
|
||||
r = self.send(
|
||||
functions.messages.ForwardMessages(
|
||||
@@ -122,8 +117,4 @@ class ForwardMessages(BaseClient):
|
||||
)
|
||||
)
|
||||
|
||||
return pyrogram.Messages(
|
||||
client=self,
|
||||
total_count=len(forwarded_messages),
|
||||
messages=forwarded_messages
|
||||
) if is_iterable else forwarded_messages[0]
|
||||
return pyrogram.List(forwarded_messages) if is_iterable else forwarded_messages[0]
|
||||
|
@@ -18,10 +18,11 @@
|
||||
|
||||
import logging
|
||||
import time
|
||||
from typing import Union
|
||||
from typing import Union, List
|
||||
|
||||
import pyrogram
|
||||
from pyrogram.api import functions
|
||||
from pyrogram.client.ext import utils
|
||||
from pyrogram.errors import FloodWait
|
||||
from ...ext import BaseClient
|
||||
|
||||
@@ -37,13 +38,13 @@ class GetHistory(BaseClient):
|
||||
offset_id: int = 0,
|
||||
offset_date: int = 0,
|
||||
reverse: bool = False
|
||||
):
|
||||
"""Use this method to retrieve a chunk of the history of a chat.
|
||||
) -> List["pyrogram.Message"]:
|
||||
"""Retrieve a chunk of the history of a chat.
|
||||
|
||||
You can get up to 100 messages at once.
|
||||
For a more convenient way of getting a chat history see :meth:`iter_history`.
|
||||
For a more convenient way of getting a chat history see :meth:`~Client.iter_history`.
|
||||
|
||||
Args:
|
||||
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".
|
||||
@@ -67,15 +68,17 @@ class GetHistory(BaseClient):
|
||||
Pass True to retrieve the messages in reversed order (from older to most recent).
|
||||
|
||||
Returns:
|
||||
On success, a :obj:`Messages <pyrogram.Messages>` object is returned.
|
||||
List of :obj:`Message` - On success, a list of the retrieved messages is returned.
|
||||
|
||||
Raises:
|
||||
:class:`RPCError <pyrogram.RPCError>` in case of a Telegram RPC error.
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
"""
|
||||
|
||||
offset_id = offset_id or (1 if reverse else 0)
|
||||
|
||||
while True:
|
||||
try:
|
||||
messages = pyrogram.Messages._parse(
|
||||
messages = utils.parse_messages(
|
||||
self,
|
||||
self.send(
|
||||
functions.messages.GetHistory(
|
||||
@@ -97,6 +100,6 @@ class GetHistory(BaseClient):
|
||||
break
|
||||
|
||||
if reverse:
|
||||
messages.messages.reverse()
|
||||
messages.reverse()
|
||||
|
||||
return messages
|
||||
|
68
pyrogram/client/methods/messages/get_history_count.py
Normal file
68
pyrogram/client/methods/messages/get_history_count.py
Normal file
@@ -0,0 +1,68 @@
|
||||
# Pyrogram - Telegram MTProto API Client Library for Python
|
||||
# Copyright (C) 2017-2019 Dan Tès <https://github.com/delivrance>
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
import logging
|
||||
from typing import Union
|
||||
|
||||
from pyrogram.api import types, functions
|
||||
from pyrogram.client.ext import BaseClient
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GetHistoryCount(BaseClient):
|
||||
def get_history_count(
|
||||
self,
|
||||
chat_id: Union[int, str]
|
||||
) -> int:
|
||||
"""Get the total count of messages in a chat.
|
||||
|
||||
.. note::
|
||||
|
||||
Due to Telegram latest internal changes, the server can't reliably find anymore the total count of messages
|
||||
a **private** or a **basic group** chat has with a single method call. To overcome this limitation, Pyrogram
|
||||
has to iterate over all the messages. Channels and supergroups are not affected by this limitation.
|
||||
|
||||
Parameters:
|
||||
chat_id (``int`` | ``str``):
|
||||
Unique identifier (int) or username (str) of the target chat.
|
||||
|
||||
Returns:
|
||||
``int``: On success, the chat history count is returned.
|
||||
|
||||
Raises:
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
"""
|
||||
|
||||
r = self.send(
|
||||
functions.messages.GetHistory(
|
||||
peer=self.resolve_peer(chat_id),
|
||||
offset_id=0,
|
||||
offset_date=0,
|
||||
add_offset=0,
|
||||
limit=1,
|
||||
max_id=0,
|
||||
min_id=0,
|
||||
hash=0
|
||||
)
|
||||
)
|
||||
|
||||
if isinstance(r, types.messages.Messages):
|
||||
return len(r.messages)
|
||||
else:
|
||||
return r.count
|
@@ -18,12 +18,12 @@
|
||||
|
||||
import logging
|
||||
import time
|
||||
from typing import Union, Iterable
|
||||
from typing import Union, Iterable, 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__)
|
||||
|
||||
@@ -35,11 +35,11 @@ class GetMessages(BaseClient):
|
||||
message_ids: Union[int, Iterable[int]] = None,
|
||||
reply_to_message_ids: Union[int, Iterable[int]] = None,
|
||||
replies: int = 1
|
||||
) -> Union["pyrogram.Message", "pyrogram.Messages"]:
|
||||
"""Use this method to get one or more messages that belong to a specific chat.
|
||||
) -> Union["pyrogram.Message", List["pyrogram.Message"]]:
|
||||
"""Get one or more messages that belong to a specific chat.
|
||||
You can retrieve up to 200 messages at once.
|
||||
|
||||
Args:
|
||||
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".
|
||||
@@ -55,15 +55,17 @@ class GetMessages(BaseClient):
|
||||
If *message_ids* is set, this argument will be ignored.
|
||||
|
||||
replies (``int``, *optional*):
|
||||
The number of subsequent replies to get for each message. Defaults to 1.
|
||||
The number of subsequent replies to get for each message.
|
||||
Pass 0 for no reply at all or -1 for unlimited replies.
|
||||
Defaults to 1.
|
||||
|
||||
Returns:
|
||||
On success and in case *message_ids* or *reply_to_message_ids* was an iterable, the returned value will be a
|
||||
:obj:`Messages <pyrogram.Messages>` even if a list contains just one element. Otherwise, if *message_ids* or
|
||||
*reply_to_message_ids* was an integer, the single requested :obj:`Message <pyrogram.Message>` is returned.
|
||||
:obj:`Message` | List of :obj:`Message`: In case *message_ids* was an integer, the single requested message 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:
|
||||
:class:`RPCError <pyrogram.RPCError>` in case of a Telegram RPC error.
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
"""
|
||||
ids, ids_type = (
|
||||
(message_ids, types.InputMessageID) if message_ids
|
||||
@@ -80,6 +82,9 @@ class GetMessages(BaseClient):
|
||||
ids = list(ids) if is_iterable else [ids]
|
||||
ids = [ids_type(id=i) for i in ids]
|
||||
|
||||
if replies < 0:
|
||||
replies = (1 << 31) - 1
|
||||
|
||||
if isinstance(peer, types.InputPeerChannel):
|
||||
rpc = functions.channels.GetMessages(channel=peer, id=ids)
|
||||
else:
|
||||
@@ -94,6 +99,6 @@ class GetMessages(BaseClient):
|
||||
else:
|
||||
break
|
||||
|
||||
messages = pyrogram.Messages._parse(self, r, replies=replies)
|
||||
messages = utils.parse_messages(self, r, replies=replies)
|
||||
|
||||
return messages if is_iterable else messages.messages[0]
|
||||
return messages if is_iterable else messages[0]
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user