mirror of
https://github.com/TeamPGM/pyrogram.git
synced 2024-11-16 20:59:29 +00:00
Merge branch 'master' into new-api
# Conflicts: # pyrogram/crypto/aes.py
This commit is contained in:
commit
38e895ed82
@ -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
|
||||
|
@ -56,12 +56,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
|
||||
@ -83,10 +77,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,
|
||||
@ -112,13 +109,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.
|
||||
@ -132,17 +134,20 @@ class Client:
|
||||
|
||||
def __init__(self,
|
||||
session_name: str,
|
||||
api_key: tuple or APIKey = None,
|
||||
api_id: int = 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 = api_id
|
||||
self.api_hash = api_hash
|
||||
self.proxy = proxy
|
||||
self.test_mode = test_mode
|
||||
|
||||
@ -151,6 +156,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
|
||||
|
||||
@ -189,6 +195,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]
|
||||
@ -201,7 +210,7 @@ class Client:
|
||||
self.test_mode,
|
||||
self.proxy,
|
||||
self.auth_key,
|
||||
self.api_key.api_id,
|
||||
self.api_id,
|
||||
client=self
|
||||
)
|
||||
|
||||
@ -237,6 +246,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()
|
||||
|
||||
@ -254,8 +266,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
|
||||
)
|
||||
)
|
||||
@ -270,7 +282,7 @@ class Client:
|
||||
self.test_mode,
|
||||
self.proxy,
|
||||
self.auth_key,
|
||||
self.api_key.api_id,
|
||||
self.api_id,
|
||||
client=self
|
||||
)
|
||||
|
||||
@ -303,8 +315,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:
|
||||
@ -318,7 +330,7 @@ class Client:
|
||||
self.test_mode,
|
||||
self.proxy,
|
||||
self.auth_key,
|
||||
self.api_key.api_id,
|
||||
self.api_id,
|
||||
client=self
|
||||
)
|
||||
self.session.start()
|
||||
@ -326,8 +338,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
|
||||
@ -348,6 +360,14 @@ 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
|
||||
@ -636,7 +656,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,
|
||||
@ -813,32 +833,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(
|
||||
@ -925,6 +941,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*.
|
||||
|
||||
@ -957,7 +982,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)
|
||||
@ -2187,7 +2212,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:
|
||||
@ -2270,7 +2295,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()
|
||||
@ -2287,7 +2312,7 @@ class Client:
|
||||
self.test_mode,
|
||||
self.proxy,
|
||||
self.auth_key,
|
||||
self.api_key.api_id
|
||||
self.api_id
|
||||
)
|
||||
|
||||
session.start()
|
||||
@ -2319,7 +2344,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:
|
||||
@ -2351,14 +2376,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:
|
||||
|
@ -1 +1,92 @@
|
||||
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2018 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/>.
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\". "
"More info: https://docs.pyrogram.ml/resources/TgCrypto"
)
raise e
class AES:
@classmethod
def ige_encrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes:
return tgcrypto.ige_encrypt(data, key, iv)
@classmethod
def ige_decrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes:
return tgcrypto.ige_decrypt(data, key, iv)
@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)
@staticmethod
def xor(a: bytes, b: bytes) -> bytes:
return int.to_bytes(
int.from_bytes(a, "big") ^ int.from_bytes(b, "big"),
len(a),
"big",
)
|
||||
# Pyrogram - Telegram MTProto API Client Library for Python
|
||||
# Copyright (C) 2017-2018 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
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
import 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"
|
||||
)
|
||||
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:
|
||||
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:
|
||||
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
|
||||
|
||||
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:
|
||||
return int.to_bytes(
|
||||
int.from_bytes(a, "big") ^ int.from_bytes(b, "big"),
|
||||
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…
Reference in New Issue
Block a user