2
0
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:
Dan 2018-04-11 03:19:33 +02:00
commit 144a0103e4
8 changed files with 139 additions and 69 deletions

View File

@ -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

View File

@ -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):

View File

@ -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:

View File

@ -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)

View File

@ -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:

View File

@ -1,2 +1,2 @@
pysocks
tgcrypto
pyaes>=1.6.1
pysocks>=1.6.8

1
requirements_extras.txt Normal file
View File

@ -0,0 +1 @@
tgcrypto>=1.0.4

View File

@ -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")}
)