mirror of
https://github.com/pyrogram/pyrogram
synced 2025-08-29 05:18:10 +00:00
Merge branch 'master' into docs
This commit is contained in:
commit
144a0103e4
@ -1,5 +1,5 @@
|
||||
## Include
|
||||
include COPYING COPYING.lesser NOTICE requirements.txt
|
||||
include COPYING COPYING.lesser NOTICE requirements.txt requirements_extras.txt
|
||||
recursive-include compiler *.py *.tl *.tsv *.txt
|
||||
|
||||
## Exclude
|
||||
|
@ -33,7 +33,11 @@ class Error(Exception):
|
||||
MESSAGE = None
|
||||
|
||||
def __init__(self, x: int or RpcError = None, query_type: type = None):
|
||||
super().__init__("[{} {}]: {}".format(self.CODE, self.ID or self.NAME, self.MESSAGE.format(x=x)))
|
||||
super().__init__("[{} {}]: {}".format(
|
||||
self.CODE,
|
||||
self.ID or self.NAME,
|
||||
str(self) or self.MESSAGE.format(x=x)
|
||||
))
|
||||
|
||||
try:
|
||||
self.x = int(x)
|
||||
@ -50,13 +54,13 @@ class Error(Exception):
|
||||
code = rpc_error.error_code
|
||||
|
||||
if code not in exceptions:
|
||||
raise UnknownError(rpc_error, query_type)
|
||||
raise UnknownError(x=rpc_error, query_type=query_type)
|
||||
|
||||
message = rpc_error.error_message
|
||||
id = re.sub(r"_\d+", "_X", message)
|
||||
|
||||
if id not in exceptions[code]:
|
||||
raise UnknownError(rpc_error, query_type)
|
||||
raise UnknownError(x=rpc_error, query_type=query_type)
|
||||
|
||||
x = re.search(r"_(\d+)", message)
|
||||
x = x.group(1) if x is not None else x
|
||||
@ -64,7 +68,7 @@ class Error(Exception):
|
||||
raise getattr(
|
||||
import_module("pyrogram.api.errors"),
|
||||
exceptions[code][id]
|
||||
)(x)
|
||||
)(x=x)
|
||||
|
||||
|
||||
class UnknownError(Error):
|
||||
|
@ -54,12 +54,6 @@ from .style import Markdown, HTML
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class APIKey:
|
||||
def __init__(self, api_id: int, api_hash: str):
|
||||
self.api_id = api_id
|
||||
self.api_hash = api_hash
|
||||
|
||||
|
||||
class Proxy:
|
||||
def __init__(self, enabled: bool, hostname: str, port: int, username: str, password: str):
|
||||
self.enabled = enabled
|
||||
@ -81,10 +75,13 @@ class Client:
|
||||
For Bots: pass your Bot API token, e.g.: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"
|
||||
Note: as long as a valid User session file exists, Pyrogram won't ask you again to input your phone number.
|
||||
|
||||
api_key (``tuple``, optional):
|
||||
Your Telegram API Key as tuple: *(api_id, api_hash)*.
|
||||
E.g.: *(12345, "0123456789abcdef0123456789abcdef")*. This is an alternative way to pass it if you
|
||||
don't want to use the *config.ini* file.
|
||||
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"
|
||||
This is an alternative way to pass it if you don't want to use the *config.ini* file.
|
||||
|
||||
proxy (``dict``, optional):
|
||||
Your SOCKS5 Proxy settings as dict,
|
||||
@ -110,13 +107,18 @@ class Client:
|
||||
Pass your Two-Step Verification password (if you have one) to avoid entering it
|
||||
manually. Only applicable for new sessions.
|
||||
|
||||
force_sms (``str``, optional):
|
||||
Pass True to force Telegram sending the authorization code via SMS.
|
||||
Only applicable for new sessions.
|
||||
|
||||
first_name (``str``, optional):
|
||||
Pass a First Name to avoid entering it manually. It will be used to automatically
|
||||
create a new Telegram account in case the phone number you passed is not registered yet.
|
||||
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: ""
|
||||
be an empty string: "". Only applicable for new sessions.
|
||||
|
||||
workers (``int``, optional):
|
||||
Thread pool size for handling incoming updates. Defaults to 4.
|
||||
@ -130,17 +132,20 @@ class Client:
|
||||
|
||||
def __init__(self,
|
||||
session_name: str,
|
||||
api_key: tuple or APIKey = None,
|
||||
api_id: int or str = None,
|
||||
api_hash: str = None,
|
||||
proxy: dict or Proxy = None,
|
||||
test_mode: bool = False,
|
||||
phone_number: str = None,
|
||||
phone_code: str or callable = None,
|
||||
password: str = None,
|
||||
force_sms: bool = False,
|
||||
first_name: str = None,
|
||||
last_name: str = None,
|
||||
workers: int = 4):
|
||||
self.session_name = session_name
|
||||
self.api_key = api_key
|
||||
self.api_id = int(api_id) if api_id else None
|
||||
self.api_hash = api_hash
|
||||
self.proxy = proxy
|
||||
self.test_mode = test_mode
|
||||
|
||||
@ -149,6 +154,7 @@ class Client:
|
||||
self.phone_code = phone_code
|
||||
self.first_name = first_name
|
||||
self.last_name = last_name
|
||||
self.force_sms = force_sms
|
||||
|
||||
self.workers = workers
|
||||
|
||||
@ -187,6 +193,9 @@ class Client:
|
||||
Raises:
|
||||
:class:`Error <pyrogram.Error>`
|
||||
"""
|
||||
if self.is_started:
|
||||
raise ConnectionError("Client has already been started")
|
||||
|
||||
if self.BOT_TOKEN_RE.match(self.session_name):
|
||||
self.token = self.session_name
|
||||
self.session_name = self.session_name.split(":")[0]
|
||||
@ -199,7 +208,7 @@ class Client:
|
||||
self.test_mode,
|
||||
self.proxy,
|
||||
self.auth_key,
|
||||
self.api_key.api_id,
|
||||
self.api_id,
|
||||
client=self
|
||||
)
|
||||
|
||||
@ -235,6 +244,9 @@ class Client:
|
||||
"""Use this method to manually stop the Client.
|
||||
Requires no parameters.
|
||||
"""
|
||||
if not self.is_started:
|
||||
raise ConnectionError("Client is already stopped")
|
||||
|
||||
self.is_started = False
|
||||
self.session.stop()
|
||||
|
||||
@ -252,8 +264,8 @@ class Client:
|
||||
r = self.send(
|
||||
functions.auth.ImportBotAuthorization(
|
||||
flags=0,
|
||||
api_id=self.api_key.api_id,
|
||||
api_hash=self.api_key.api_hash,
|
||||
api_id=self.api_id,
|
||||
api_hash=self.api_hash,
|
||||
bot_auth_token=self.token
|
||||
)
|
||||
)
|
||||
@ -268,7 +280,7 @@ class Client:
|
||||
self.test_mode,
|
||||
self.proxy,
|
||||
self.auth_key,
|
||||
self.api_key.api_id,
|
||||
self.api_id,
|
||||
client=self
|
||||
)
|
||||
|
||||
@ -301,8 +313,8 @@ class Client:
|
||||
r = self.send(
|
||||
functions.auth.SendCode(
|
||||
self.phone_number,
|
||||
self.api_key.api_id,
|
||||
self.api_key.api_hash
|
||||
self.api_id,
|
||||
self.api_hash
|
||||
)
|
||||
)
|
||||
except (PhoneMigrate, NetworkMigrate) as e:
|
||||
@ -316,7 +328,7 @@ class Client:
|
||||
self.test_mode,
|
||||
self.proxy,
|
||||
self.auth_key,
|
||||
self.api_key.api_id,
|
||||
self.api_id,
|
||||
client=self
|
||||
)
|
||||
self.session.start()
|
||||
@ -324,8 +336,8 @@ class Client:
|
||||
r = self.send(
|
||||
functions.auth.SendCode(
|
||||
self.phone_number,
|
||||
self.api_key.api_id,
|
||||
self.api_key.api_hash
|
||||
self.api_id,
|
||||
self.api_hash
|
||||
)
|
||||
)
|
||||
break
|
||||
@ -346,11 +358,19 @@ class Client:
|
||||
phone_registered = r.phone_registered
|
||||
phone_code_hash = r.phone_code_hash
|
||||
|
||||
if self.force_sms:
|
||||
self.send(
|
||||
functions.auth.ResendCode(
|
||||
phone_number=self.phone_number,
|
||||
phone_code_hash=phone_code_hash
|
||||
)
|
||||
)
|
||||
|
||||
while True:
|
||||
self.phone_code = (
|
||||
input("Enter phone code: ") if self.phone_code is None
|
||||
else self.phone_code if type(self.phone_code) is str
|
||||
else self.phone_code()
|
||||
else str(self.phone_code())
|
||||
)
|
||||
|
||||
try:
|
||||
@ -634,7 +654,7 @@ class Client:
|
||||
if not isinstance(message, types.MessageEmpty):
|
||||
diff = self.send(
|
||||
functions.updates.GetChannelDifference(
|
||||
channel=self.resolve_peer(update.message.to_id.channel_id),
|
||||
channel=self.resolve_peer(int("-100" + str(update.message.to_id.channel_id))),
|
||||
filter=types.ChannelMessagesFilter(
|
||||
ranges=[types.MessageRange(
|
||||
min_id=update.message.id,
|
||||
@ -787,32 +807,28 @@ class Client:
|
||||
Raises:
|
||||
:class:`Error <pyrogram.Error>`
|
||||
"""
|
||||
if self.is_started:
|
||||
r = self.session.send(data)
|
||||
if not self.is_started:
|
||||
raise ConnectionError("Client has not been started")
|
||||
|
||||
self.fetch_peers(getattr(r, "users", []))
|
||||
self.fetch_peers(getattr(r, "chats", []))
|
||||
r = self.session.send(data)
|
||||
|
||||
return r
|
||||
else:
|
||||
raise ConnectionError("client '{}' is not started".format(self.session_name))
|
||||
self.fetch_peers(getattr(r, "users", []))
|
||||
self.fetch_peers(getattr(r, "chats", []))
|
||||
|
||||
return r
|
||||
|
||||
def load_config(self):
|
||||
parser = ConfigParser()
|
||||
parser.read("config.ini")
|
||||
|
||||
if self.api_key is not None:
|
||||
self.api_key = APIKey(
|
||||
api_id=int(self.api_key[0]),
|
||||
api_hash=self.api_key[1]
|
||||
)
|
||||
elif parser.has_section("pyrogram"):
|
||||
self.api_key = APIKey(
|
||||
api_id=parser.getint("pyrogram", "api_id"),
|
||||
api_hash=parser.get("pyrogram", "api_hash")
|
||||
)
|
||||
if self.api_id and self.api_hash:
|
||||
pass
|
||||
else:
|
||||
raise AttributeError("No API Key found")
|
||||
if parser.has_section("pyrogram"):
|
||||
self.api_id = parser.getint("pyrogram", "api_id")
|
||||
self.api_hash = parser.get("pyrogram", "api_hash")
|
||||
else:
|
||||
raise AttributeError("No API Key found")
|
||||
|
||||
if self.proxy is not None:
|
||||
self.proxy = Proxy(
|
||||
@ -899,6 +915,15 @@ class Client:
|
||||
offset_date = parse_dialogs(dialogs)
|
||||
log.info("Entities count: {}".format(len(self.peers_by_id)))
|
||||
|
||||
self.send(
|
||||
functions.messages.GetDialogs(
|
||||
0, 0, types.InputPeerEmpty(),
|
||||
self.DIALOGS_AT_ONCE, True
|
||||
)
|
||||
)
|
||||
|
||||
log.info("Entities count: {}".format(len(self.peers_by_id)))
|
||||
|
||||
def resolve_peer(self, peer_id: int or str):
|
||||
"""Use this method to get the *InputPeer* of a known *peer_id*.
|
||||
|
||||
@ -931,7 +956,7 @@ class Client:
|
||||
except (AttributeError, binascii.Error, struct.error):
|
||||
pass
|
||||
|
||||
peer_id = peer_id.lower().strip("@+")
|
||||
peer_id = re.sub(r"[@+\s]", "", peer_id.lower())
|
||||
|
||||
try:
|
||||
int(peer_id)
|
||||
@ -2161,7 +2186,7 @@ class Client:
|
||||
file_id = file_id or self.rnd_id()
|
||||
md5_sum = md5() if not is_big and not is_missing_part else None
|
||||
|
||||
session = Session(self.dc_id, self.test_mode, self.proxy, self.auth_key, self.api_key.api_id)
|
||||
session = Session(self.dc_id, self.test_mode, self.proxy, self.auth_key, self.api_id)
|
||||
session.start()
|
||||
|
||||
try:
|
||||
@ -2244,7 +2269,7 @@ class Client:
|
||||
self.test_mode,
|
||||
self.proxy,
|
||||
Auth(dc_id, self.test_mode, self.proxy).create(),
|
||||
self.api_key.api_id
|
||||
self.api_id
|
||||
)
|
||||
|
||||
session.start()
|
||||
@ -2261,7 +2286,7 @@ class Client:
|
||||
self.test_mode,
|
||||
self.proxy,
|
||||
self.auth_key,
|
||||
self.api_key.api_id
|
||||
self.api_id
|
||||
)
|
||||
|
||||
session.start()
|
||||
@ -2293,7 +2318,7 @@ class Client:
|
||||
)
|
||||
|
||||
if isinstance(r, types.upload.File):
|
||||
with tempfile.NamedTemporaryFile('wb', delete=False) as f:
|
||||
with tempfile.NamedTemporaryFile("wb", delete=False) as f:
|
||||
file_name = f.name
|
||||
|
||||
while True:
|
||||
@ -2325,14 +2350,14 @@ class Client:
|
||||
self.test_mode,
|
||||
self.proxy,
|
||||
Auth(r.dc_id, self.test_mode, self.proxy).create(),
|
||||
self.api_key.api_id,
|
||||
self.api_id,
|
||||
is_cdn=True
|
||||
)
|
||||
|
||||
cdn_session.start()
|
||||
|
||||
try:
|
||||
with tempfile.NamedTemporaryFile('wb', delete=False) as f:
|
||||
with tempfile.NamedTemporaryFile("wb", delete=False) as f:
|
||||
file_name = f.name
|
||||
|
||||
while True:
|
||||
|
@ -16,33 +16,52 @@
|
||||
# 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
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
import tgcrypto
|
||||
except ImportError as e:
|
||||
e.msg = (
|
||||
"TgCrypto is missing and Pyrogram can't run without. "
|
||||
"Please install it using \"pip3 install tgcrypto\". "
|
||||
except ImportError:
|
||||
log.warning(
|
||||
"TgCrypto is missing! "
|
||||
"Pyrogram will work the same, but at a much slower speed. "
|
||||
"More info: https://docs.pyrogram.ml/resources/TgCrypto"
|
||||
)
|
||||
|
||||
raise e
|
||||
is_fast = False
|
||||
import pyaes
|
||||
else:
|
||||
log.info("Using TgCrypto")
|
||||
is_fast = True
|
||||
|
||||
|
||||
# TODO: Ugly IFs
|
||||
class AES:
|
||||
@classmethod
|
||||
def ige_encrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes:
|
||||
return tgcrypto.ige_encrypt(data, key, iv)
|
||||
if is_fast:
|
||||
return tgcrypto.ige_encrypt(data, key, iv)
|
||||
else:
|
||||
return cls.ige(data, key, iv, True)
|
||||
|
||||
@classmethod
|
||||
def ige_decrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes:
|
||||
return tgcrypto.ige_decrypt(data, key, iv)
|
||||
if is_fast:
|
||||
return tgcrypto.ige_decrypt(data, key, iv)
|
||||
else:
|
||||
return cls.ige(data, key, iv, False)
|
||||
|
||||
@staticmethod
|
||||
def ctr_decrypt(data: bytes, key: bytes, iv: bytes, offset: int) -> bytes:
|
||||
replace = int.to_bytes(offset // 16, 4, "big")
|
||||
iv = iv[:-4] + replace
|
||||
|
||||
return tgcrypto.ctr_decrypt(data, key, iv)
|
||||
if is_fast:
|
||||
return tgcrypto.ctr_decrypt(data, key, iv)
|
||||
else:
|
||||
ctr = pyaes.AESModeOfOperationCTR(key)
|
||||
ctr._counter._counter = list(iv)
|
||||
return ctr.decrypt(data)
|
||||
|
||||
@staticmethod
|
||||
def xor(a: bytes, b: bytes) -> bytes:
|
||||
@ -51,3 +70,23 @@ class AES:
|
||||
len(a),
|
||||
"big",
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def ige(cls, data: bytes, key: bytes, iv: bytes, encrypt: bool) -> bytes:
|
||||
cipher = pyaes.AES(key)
|
||||
|
||||
iv_1 = iv[:16]
|
||||
iv_2 = iv[16:]
|
||||
|
||||
data = [data[i: i + 16] for i in range(0, len(data), 16)]
|
||||
|
||||
if encrypt:
|
||||
for i, chunk in enumerate(data):
|
||||
iv_1 = data[i] = cls.xor(cipher.encrypt(cls.xor(chunk, iv_1)), iv_2)
|
||||
iv_2 = chunk
|
||||
else:
|
||||
for i, chunk in enumerate(data):
|
||||
iv_2 = data[i] = cls.xor(cipher.decrypt(cls.xor(chunk, iv_2)), iv_1)
|
||||
iv_1 = chunk
|
||||
|
||||
return b"".join(data)
|
||||
|
@ -87,7 +87,7 @@ class Session:
|
||||
test_mode: bool,
|
||||
proxy: type,
|
||||
auth_key: bytes,
|
||||
api_id: str,
|
||||
api_id: int,
|
||||
is_cdn: bool = False,
|
||||
client: pyrogram = None):
|
||||
if not Session.notice_displayed:
|
||||
|
@ -1,2 +1,2 @@
|
||||
pysocks
|
||||
tgcrypto
|
||||
pyaes>=1.6.1
|
||||
pysocks>=1.6.8
|
1
requirements_extras.txt
Normal file
1
requirements_extras.txt
Normal file
@ -0,0 +1 @@
|
||||
tgcrypto>=1.0.4
|
7
setup.py
7
setup.py
@ -25,8 +25,8 @@ from compiler.api import compiler as api_compiler
|
||||
from compiler.error import compiler as error_compiler
|
||||
|
||||
|
||||
def requirements():
|
||||
with open("requirements.txt", encoding="utf-8") as r:
|
||||
def read(file: str) -> list:
|
||||
with open(file, encoding="utf-8") as r:
|
||||
return [i.strip() for i in r]
|
||||
|
||||
|
||||
@ -82,5 +82,6 @@ setup(
|
||||
python_requires="~=3.4",
|
||||
packages=find_packages(exclude=["compiler*"]),
|
||||
zip_safe=False,
|
||||
install_requires=requirements()
|
||||
install_requires=read("requirements.txt"),
|
||||
extras_require={"tgcrypto": read("requirements_extras.txt")}
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user