mirror of
https://github.com/pyrogram/pyrogram
synced 2025-09-05 00:35:10 +00:00
Merge branch 'master' into new-api
This commit is contained in:
@@ -23,10 +23,12 @@ __copyright__ = "Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance
|
||||
"e" if sys.getfilesystemencoding() == "ascii" else "\xe8"
|
||||
)
|
||||
__license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
|
||||
__version__ = "0.5.0"
|
||||
__version__ = "0.6.2"
|
||||
|
||||
from .api.errors import Error
|
||||
from .client import ChatAction
|
||||
from .client import Client
|
||||
from .client import ParseMode
|
||||
from .client.input_media import InputMedia
|
||||
from .client.input_phone_contact import InputPhoneContact
|
||||
from .client import Emoji
|
||||
|
@@ -19,3 +19,4 @@
|
||||
from .chat_action import ChatAction
|
||||
from .client import Client
|
||||
from .parse_mode import ParseMode
|
||||
from .emoji import Emoji
|
||||
|
File diff suppressed because it is too large
Load Diff
7829
pyrogram/client/emoji.py
Normal file
7829
pyrogram/client/emoji.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -20,10 +20,11 @@
|
||||
class InputMedia:
|
||||
class Photo:
|
||||
"""This object represents a photo to be sent inside an album.
|
||||
It is intended to be used with :obj:`pyrogram.Client.send_media_group`.
|
||||
|
||||
Args:
|
||||
media (:obj:`str`):
|
||||
File to send.
|
||||
Photo file to send.
|
||||
Pass a file path as string to send a photo that exists on your local machine.
|
||||
|
||||
caption (:obj:`str`):
|
||||
@@ -45,19 +46,32 @@ class InputMedia:
|
||||
|
||||
class Video:
|
||||
"""This object represents a video to be sent inside an album.
|
||||
It is intended to be used with :obj:`pyrogram.Client.send_media_group`.
|
||||
|
||||
Args:
|
||||
media (:obj:`str`):
|
||||
File to send.
|
||||
Video file to send.
|
||||
Pass a file path as string to send a video that exists on your local machine.
|
||||
|
||||
caption (:obj:`str`):
|
||||
caption (:obj:`str`, optional):
|
||||
Caption of the video to be sent, 0-200 characters
|
||||
|
||||
parse_mode (:obj:`str`):
|
||||
parse_mode (:obj:`str`, optional):
|
||||
Use :obj:`pyrogram.ParseMode.MARKDOWN` or :obj:`pyrogram.ParseMode.HTML` if you want Telegram apps
|
||||
to show bold, italic, fixed-width text or inline URLs in your caption.
|
||||
Defaults to Markdown.
|
||||
|
||||
width (:obj:`int`, optional):
|
||||
Video width.
|
||||
|
||||
height (:obj:`int`, optional):
|
||||
Video height
|
||||
|
||||
duration (:obj:`int`, optional):
|
||||
Video duration.
|
||||
|
||||
supports_streaming (:obj:`bool`, optional):
|
||||
Pass True, if the uploaded video is suitable for streaming.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
@@ -66,10 +80,12 @@ class InputMedia:
|
||||
parse_mode: str = "",
|
||||
width: int = 0,
|
||||
height: int = 0,
|
||||
duration: int = 0):
|
||||
duration: int = 0,
|
||||
supports_streaming: bool = None):
|
||||
self.media = media
|
||||
self.caption = caption
|
||||
self.parse_mode = parse_mode
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.duration = duration
|
||||
self.supports_streaming = supports_streaming
|
||||
|
@@ -16,20 +16,29 @@
|
||||
# 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:
|
||||
from pyaes import AESModeOfOperationCTR
|
||||
except ImportError:
|
||||
pass
|
||||
from pyrogram.api.types import InputPhoneContact as RawInputPhoneContact
|
||||
from pyrogram.session.internals import MsgId
|
||||
|
||||
|
||||
class CTR:
|
||||
def __init__(self, key: bytes, iv: bytes):
|
||||
self.ctr = AESModeOfOperationCTR(key)
|
||||
self.iv = iv
|
||||
class InputPhoneContact:
|
||||
"""This object represents a Phone Contact to be added in your Telegram address book.
|
||||
It is intended to be used with :obj:`pyrogram.Client.add_contacts`
|
||||
|
||||
def decrypt(self, data: bytes, offset: int) -> bytes:
|
||||
replace = int.to_bytes(offset // 16, byteorder="big", length=4)
|
||||
iv = self.iv[:-4] + replace
|
||||
self.ctr._counter._counter = list(iv)
|
||||
Args:
|
||||
phone (:obj:`str`):
|
||||
Contact's phone number
|
||||
|
||||
return self.ctr.decrypt(data)
|
||||
first_name (:obj:`str`):
|
||||
Contact's first name
|
||||
|
||||
last_name (:obj:`str`, optional):
|
||||
Contact's last name
|
||||
"""
|
||||
|
||||
def __new__(cls, phone: str, first_name: str, last_name: str = ""):
|
||||
return RawInputPhoneContact(
|
||||
client_id=MsgId(),
|
||||
phone="+" + phone.strip("+"),
|
||||
first_name=first_name,
|
||||
last_name=last_name
|
||||
)
|
@@ -31,7 +31,7 @@ from . import utils
|
||||
|
||||
|
||||
class HTML:
|
||||
HTML_RE = re.compile(r"<(\w+)(?: href=([\"'])(.*)\2)?>(.*)</\1>")
|
||||
HTML_RE = re.compile(r"<(\w+)(?: href=([\"'])([^<]+)\2)?>([^>]+)</\1>")
|
||||
MENTION_RE = re.compile(r"tg://user\?id=(\d+)")
|
||||
|
||||
def __init__(self, peers_by_id):
|
||||
@@ -44,7 +44,7 @@ class HTML:
|
||||
|
||||
for match in self.HTML_RE.finditer(text):
|
||||
start = match.start() - offset
|
||||
style, url, body = match.groups()
|
||||
style, url, body = match.group(1, 3, 4)
|
||||
|
||||
if url:
|
||||
mention = self.MENTION_RE.match(url)
|
||||
|
@@ -24,95 +24,84 @@ from pyrogram.api.types import (
|
||||
MessageEntityCode as Code,
|
||||
MessageEntityTextUrl as Url,
|
||||
MessageEntityPre as Pre,
|
||||
MessageEntityMentionName as MentionInvalid,
|
||||
InputMessageEntityMentionName as Mention
|
||||
)
|
||||
from . import utils
|
||||
|
||||
|
||||
class Markdown:
|
||||
INLINE_DELIMITERS = {
|
||||
"**": Bold,
|
||||
"__": Italic,
|
||||
"`": Code
|
||||
}
|
||||
BOLD_DELIMITER = "**"
|
||||
ITALIC_DELIMITER = "__"
|
||||
CODE_DELIMITER = "`"
|
||||
PRE_DELIMITER = "```"
|
||||
|
||||
# ``` python
|
||||
# for i in range(10):
|
||||
# print(i)
|
||||
# ```
|
||||
PRE_RE = r"(?P<pre>```(?P<lang>.*)\n(?P<code>(.|\n)*)\n```)"
|
||||
|
||||
# [url](github.com)
|
||||
URL_RE = r"(?P<url>(\[(?P<url_text>.+?)\]\((?P<url_path>.+?)\)))"
|
||||
|
||||
# [name](tg://user?id=123456789)
|
||||
MENTION_RE = r"(?P<mention>(\[(?P<mention_text>.+?)\]\(tg:\/\/user\?id=(?P<user_id>\d+?)\)))"
|
||||
|
||||
# **bold**
|
||||
# __italic__
|
||||
# `code`
|
||||
INLINE_RE = r"(?P<inline>(?P<start_delimiter>{d})(?P<body>.+?)(?P<end_delimiter>{d}))".format(
|
||||
MARKDOWN_RE = re.compile(r"```([\w ]*)\n([\w\W]*)(?:\n|)```|\[([^[(]+)\]\(([^])]+)\)|({d})(.+?)\5".format(
|
||||
d="|".join(
|
||||
["".join(i) for i in [
|
||||
["\{}".format(j) for j in i]
|
||||
for i in sorted( # Sort delimiters by length
|
||||
INLINE_DELIMITERS.keys(),
|
||||
key=lambda k: len(k), # Or: key=len
|
||||
reverse=True
|
||||
)
|
||||
for i in [
|
||||
PRE_DELIMITER,
|
||||
CODE_DELIMITER,
|
||||
ITALIC_DELIMITER,
|
||||
BOLD_DELIMITER
|
||||
]
|
||||
]]
|
||||
)
|
||||
)
|
||||
))
|
||||
MENTION_RE = re.compile(r"tg://user\?id=(\d+)")
|
||||
|
||||
MARKDOWN_RE = re.compile("|".join([PRE_RE, MENTION_RE, URL_RE, INLINE_RE]))
|
||||
|
||||
def __init__(self, peers_by_id):
|
||||
def __init__(self, peers_by_id: dict):
|
||||
self.peers_by_id = peers_by_id
|
||||
|
||||
def parse(self, text):
|
||||
def parse(self, message: str):
|
||||
entities = []
|
||||
text = utils.add_surrogates(text)
|
||||
message = utils.add_surrogates(message).strip()
|
||||
offset = 0
|
||||
|
||||
for match in self.MARKDOWN_RE.finditer(text):
|
||||
for match in self.MARKDOWN_RE.finditer(message):
|
||||
start = match.start() - offset
|
||||
lang, pre, text, url, style, body = match.groups()
|
||||
|
||||
if match.group("pre"):
|
||||
pattern = match.group("pre")
|
||||
lang = match.group("lang")
|
||||
replace = match.group("code")
|
||||
entity = Pre(start, len(replace), lang.strip())
|
||||
offset += len(lang) + 8
|
||||
elif match.group("url"):
|
||||
pattern = match.group("url")
|
||||
replace = match.group("url_text")
|
||||
path = match.group("url_path")
|
||||
entity = Url(start, len(replace), path)
|
||||
offset += len(path) + 4
|
||||
elif match.group("mention"):
|
||||
pattern = match.group("mention")
|
||||
replace = match.group("mention_text")
|
||||
user_id = match.group("user_id")
|
||||
entity = Mention(start, len(replace), self.peers_by_id[int(user_id)])
|
||||
offset += len(user_id) + 17
|
||||
elif match.group("inline"):
|
||||
pattern = match.group("inline")
|
||||
replace = match.group("body")
|
||||
start_delimiter = match.group("start_delimiter")
|
||||
end_delimiter = match.group("end_delimiter")
|
||||
if pre:
|
||||
body = pre = pre.strip()
|
||||
entity = Pre(start, len(pre), lang.strip() or "")
|
||||
offset += len(lang) + len(self.PRE_DELIMITER) * 2
|
||||
elif url:
|
||||
mention = self.MENTION_RE.match(url)
|
||||
|
||||
if start_delimiter != end_delimiter:
|
||||
if mention:
|
||||
user_id = int(mention.group(1))
|
||||
input_user = self.peers_by_id.get(user_id, None)
|
||||
|
||||
entity = (
|
||||
Mention(start, len(text), input_user)
|
||||
if input_user
|
||||
else MentionInvalid(start, len(text), user_id)
|
||||
)
|
||||
else:
|
||||
entity = Url(start, len(text), url)
|
||||
|
||||
body = text
|
||||
offset += len(url) + 4
|
||||
else:
|
||||
if style == self.BOLD_DELIMITER:
|
||||
entity = Bold(start, len(body))
|
||||
elif style == self.ITALIC_DELIMITER:
|
||||
entity = Italic(start, len(body))
|
||||
elif style == self.CODE_DELIMITER:
|
||||
entity = Code(start, len(body))
|
||||
elif style == self.PRE_DELIMITER:
|
||||
entity = Pre(start, len(body), "")
|
||||
else:
|
||||
continue
|
||||
|
||||
entity = self.INLINE_DELIMITERS[start_delimiter](start, len(replace))
|
||||
offset += len(start_delimiter) * 2
|
||||
else:
|
||||
continue
|
||||
offset += len(style) * 2
|
||||
|
||||
entities.append(entity)
|
||||
text = text.replace(pattern, replace)
|
||||
message = message.replace(match.group(), body)
|
||||
|
||||
return dict(
|
||||
message=utils.remove_surrogates(text),
|
||||
message=utils.remove_surrogates(message),
|
||||
entities=entities
|
||||
)
|
||||
|
@@ -30,6 +30,7 @@ Proxy = namedtuple("Proxy", ["enabled", "hostname", "port", "username", "passwor
|
||||
class TCP(socks.socksocket):
|
||||
def __init__(self, proxy: Proxy):
|
||||
super().__init__()
|
||||
self.settimeout(10)
|
||||
self.proxy_enabled = False
|
||||
|
||||
if proxy and proxy.enabled:
|
||||
@@ -43,6 +44,11 @@ class TCP(socks.socksocket):
|
||||
password=proxy.password
|
||||
)
|
||||
|
||||
log.info("Using proxy {}:{}".format(
|
||||
proxy.hostname,
|
||||
proxy.port
|
||||
))
|
||||
|
||||
def close(self):
|
||||
try:
|
||||
self.shutdown(socket.SHUT_RDWR)
|
||||
|
@@ -16,8 +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 .ctr import CTR
|
||||
from .ige import IGE
|
||||
from .aes import AES
|
||||
from .kdf import KDF
|
||||
from .prime import Prime
|
||||
from .rsa import RSA
|
||||
|
@@ -16,21 +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/>.
|
||||
|
||||
from pyaes import AES
|
||||
import logging
|
||||
|
||||
BLOCK_SIZE = 16
|
||||
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: Performance optimization
|
||||
|
||||
class IGE:
|
||||
# TODO: Ugly IFs
|
||||
class AES:
|
||||
@classmethod
|
||||
def encrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes:
|
||||
return cls.ige(data, key, iv, True)
|
||||
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 decrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes:
|
||||
return cls.ige(data, key, iv, False)
|
||||
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, byteorder="big", length=4)
|
||||
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:
|
||||
@@ -42,12 +73,12 @@ class IGE:
|
||||
|
||||
@classmethod
|
||||
def ige(cls, data: bytes, key: bytes, iv: bytes, encrypt: bool) -> bytes:
|
||||
cipher = AES(key)
|
||||
cipher = pyaes.AES(key)
|
||||
|
||||
iv_1 = iv[:BLOCK_SIZE]
|
||||
iv_2 = iv[BLOCK_SIZE:]
|
||||
iv_1 = iv[:16]
|
||||
iv_2 = iv[16:]
|
||||
|
||||
data = [data[i: i + BLOCK_SIZE] for i in range(0, len(data), BLOCK_SIZE)]
|
||||
data = [data[i: i + 16] for i in range(0, len(data), 16)]
|
||||
|
||||
if encrypt:
|
||||
for i, chunk in enumerate(data):
|
@@ -23,10 +23,16 @@ PublicKey = namedtuple("PublicKey", ["m", "e"])
|
||||
|
||||
class RSA:
|
||||
# To get modulus and exponent:
|
||||
#
|
||||
# [RSA PUBLIC KEY]:
|
||||
# grep -v -- - public.key | tr -d \\n | base64 -d | openssl asn1parse -inform DER -i
|
||||
#
|
||||
# [PUBLIC KEY]:
|
||||
# openssl rsa -pubin -in key -text -noout
|
||||
|
||||
server_public_keys = {
|
||||
0xc3b42b026ce86b21 - (1 << 64): PublicKey( # Telegram servers
|
||||
# -4344800451088585951
|
||||
0xc3b42b026ce86b21 - (1 << 64): PublicKey( # Telegram servers #1
|
||||
# -----BEGIN RSA PUBLIC KEY-----
|
||||
# MIIBCgKCAQEAwVACPi9w23mF3tBkdZz+zwrzKOaaQdr01vAbU4E1pvkfj4sqDsm6
|
||||
# lyDONS789sVoD/xCS9Y0hkkC3gtL1tSfTlgCMOOul9lcixlEKzwKENj1Yz/s7daS
|
||||
@@ -48,6 +54,108 @@ class RSA:
|
||||
), # Modulus
|
||||
int("010001", 16) # Exponent
|
||||
),
|
||||
|
||||
# 847625836280919973
|
||||
0x10bc35f3509f7b7a5 - (1 << 64): PublicKey( # Telegram servers #2
|
||||
# -----BEGIN PUBLIC KEY-----
|
||||
# MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAruw2yP/BCcsJliRoW5eB
|
||||
# VBVle9dtjJw+OYED160Wybum9SXtBBLXriwt4rROd9csv0t0OHCaTmRqBcQ0J8fx
|
||||
# hN6/cpR1GWgOZRUAiQxoMnlt0R93LCX/j1dnVa/gVbCjdSxpbrfY2g2L4frzjJvd
|
||||
# l84Kd9ORYjDEAyFnEA7dD556OptgLQQ2e2iVNq8NZLYTzLp5YpOdO1doK+ttrltg
|
||||
# gTCy5SrKeLoCPPbOgGsdxJxyz5KKcZnSLj16yE5HvJQn0CNpRdENvRUXe6tBP78O
|
||||
# 39oJ8BTHp9oIjd6XWXAsp2CvK45Ol8wFXGF710w9lwCGNbmNxNYhtIkdqfsEcwR5
|
||||
# JwIDAQAB
|
||||
# -----END PUBLIC KEY-----
|
||||
int(
|
||||
"AEEC36C8FFC109CB099624685B97815415657BD76D8C9C3E398103D7AD16C9BB"
|
||||
"A6F525ED0412D7AE2C2DE2B44E77D72CBF4B7438709A4E646A05C43427C7F184"
|
||||
"DEBF72947519680E651500890C6832796DD11F772C25FF8F576755AFE055B0A3"
|
||||
"752C696EB7D8DA0D8BE1FAF38C9BDD97CE0A77D3916230C4032167100EDD0F9E"
|
||||
"7A3A9B602D04367B689536AF0D64B613CCBA7962939D3B57682BEB6DAE5B6081"
|
||||
"30B2E52ACA78BA023CF6CE806B1DC49C72CF928A7199D22E3D7AC84E47BC9427"
|
||||
"D0236945D10DBD15177BAB413FBF0EDFDA09F014C7A7DA088DDE9759702CA760"
|
||||
"AF2B8E4E97CC055C617BD74C3D97008635B98DC4D621B4891DA9FB0473047927",
|
||||
16
|
||||
), # Modulus
|
||||
int("010001", 16) # Exponent
|
||||
),
|
||||
|
||||
# 1562291298945373506
|
||||
0x115ae5fa8b5529542 - (1 << 64): PublicKey( # Telegram servers #3
|
||||
# -----BEGIN PUBLIC KEY-----
|
||||
# MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvfLHfYH2r9R70w8prHbl
|
||||
# Wt/nDkh+XkgpflqQVcnAfSuTtO05lNPspQmL8Y2XjVT4t8cT6xAkdgfmmvnvRPOO
|
||||
# KPi0OfJXoRVylFzAQG/j83u5K3kRLbae7fLccVhKZhY46lvsueI1hQdLgNV9n1cQ
|
||||
# 3TDS2pQOCtovG4eDl9wacrXOJTG2990VjgnIKNA0UMoP+KF03qzryqIt3oTvZq03
|
||||
# DyWdGK+AZjgBLaDKSnC6qD2cFY81UryRWOab8zKkWAnhw2kFpcqhI0jdV5QaSCEx
|
||||
# vnsjVaX0Y1N0870931/5Jb9ICe4nweZ9kSDF/gip3kWLG0o8XQpChDfyvsqB9OLV
|
||||
# /wIDAQAB
|
||||
# -----END PUBLIC KEY-----
|
||||
int(
|
||||
"BDF2C77D81F6AFD47BD30F29AC76E55ADFE70E487E5E48297E5A9055C9C07D2B"
|
||||
"93B4ED3994D3ECA5098BF18D978D54F8B7C713EB10247607E69AF9EF44F38E28"
|
||||
"F8B439F257A11572945CC0406FE3F37BB92B79112DB69EEDF2DC71584A661638"
|
||||
"EA5BECB9E23585074B80D57D9F5710DD30D2DA940E0ADA2F1B878397DC1A72B5"
|
||||
"CE2531B6F7DD158E09C828D03450CA0FF8A174DEACEBCAA22DDE84EF66AD370F"
|
||||
"259D18AF806638012DA0CA4A70BAA83D9C158F3552BC9158E69BF332A45809E1"
|
||||
"C36905A5CAA12348DD57941A482131BE7B2355A5F4635374F3BD3DDF5FF925BF"
|
||||
"4809EE27C1E67D9120C5FE08A9DE458B1B4A3C5D0A428437F2BECA81F4E2D5FF",
|
||||
16
|
||||
), # Modulus
|
||||
int("010001", 16) # Exponent
|
||||
),
|
||||
|
||||
# -5859577972006586033
|
||||
0xaeae98e13cd7f94f - (1 << 64): PublicKey( # Telegram servers #4
|
||||
# -----BEGIN PUBLIC KEY-----
|
||||
# MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs/ditzm+mPND6xkhzwFI
|
||||
# z6J/968CtkcSE/7Z2qAJiXbmZ3UDJPGrzqTDHkO30R8VeRM/Kz2f4nR05GIFiITl
|
||||
# 4bEjvpy7xqRDspJcCFIOcyXm8abVDhF+th6knSU0yLtNKuQVP6voMrnt9MV1X92L
|
||||
# GZQLgdHZbPQz0Z5qIpaKhdyA8DEvWWvSUwwc+yi1/gGaybwlzZwqXYoPOhwMebzK
|
||||
# Uk0xW14htcJrRrq+PXXQbRzTMynseCoPIoke0dtCodbA3qQxQovE16q9zz4Otv2k
|
||||
# 4j63cz53J+mhkVWAeWxVGI0lltJmWtEYK6er8VqqWot3nqmWMXogrgRLggv/Nbbo
|
||||
# oQIDAQAB
|
||||
# -----END PUBLIC KEY-----
|
||||
int(
|
||||
"B3F762B739BE98F343EB1921CF0148CFA27FF7AF02B6471213FED9DAA0098976"
|
||||
"E667750324F1ABCEA4C31E43B7D11F1579133F2B3D9FE27474E462058884E5E1"
|
||||
"B123BE9CBBC6A443B2925C08520E7325E6F1A6D50E117EB61EA49D2534C8BB4D"
|
||||
"2AE4153FABE832B9EDF4C5755FDD8B19940B81D1D96CF433D19E6A22968A85DC"
|
||||
"80F0312F596BD2530C1CFB28B5FE019AC9BC25CD9C2A5D8A0F3A1C0C79BCCA52"
|
||||
"4D315B5E21B5C26B46BABE3D75D06D1CD33329EC782A0F22891ED1DB42A1D6C0"
|
||||
"DEA431428BC4D7AABDCF3E0EB6FDA4E23EB7733E7727E9A1915580796C55188D"
|
||||
"2596D2665AD1182BA7ABF15AAA5A8B779EA996317A20AE044B820BFF35B6E8A1",
|
||||
16
|
||||
), # Modulus
|
||||
int("010001", 16) # Exponent
|
||||
),
|
||||
|
||||
# 6491968696586960280
|
||||
0x15a181b2235057d98 - (1 << 64): PublicKey( # Telegram servers #5
|
||||
# -----BEGIN PUBLIC KEY-----
|
||||
# MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvmpxVY7ld/8DAjz6F6q0
|
||||
# 5shjg8/4p6047bn6/m8yPy1RBsvIyvuDuGnP/RzPEhzXQ9UJ5Ynmh2XJZgHoE9xb
|
||||
# nfxL5BXHplJhMtADXKM9bWB11PU1Eioc3+AXBB8QiNFBn2XI5UkO5hPhbb9mJpjA
|
||||
# 9Uhw8EdfqJP8QetVsI/xrCEbwEXe0xvifRLJbY08/Gp66KpQvy7g8w7VB8wlgePe
|
||||
# xW3pT13Ap6vuC+mQuJPyiHvSxjEKHgqePji9NP3tJUFQjcECqcm0yV7/2d0t/pbC
|
||||
# m+ZH1sadZspQCEPPrtbkQBlvHb4OLiIWPGHKSMeRFvp3IWcmdJqXahxLCUS1Eh6M
|
||||
# AQIDAQAB
|
||||
# -----END PUBLIC KEY-----
|
||||
int(
|
||||
"BE6A71558EE577FF03023CFA17AAB4E6C86383CFF8A7AD38EDB9FAFE6F323F2D"
|
||||
"5106CBC8CAFB83B869CFFD1CCF121CD743D509E589E68765C96601E813DC5B9D"
|
||||
"FC4BE415C7A6526132D0035CA33D6D6075D4F535122A1CDFE017041F1088D141"
|
||||
"9F65C8E5490EE613E16DBF662698C0F54870F0475FA893FC41EB55B08FF1AC21"
|
||||
"1BC045DED31BE27D12C96D8D3CFC6A7AE8AA50BF2EE0F30ED507CC2581E3DEC5"
|
||||
"6DE94F5DC0A7ABEE0BE990B893F2887BD2C6310A1E0A9E3E38BD34FDED254150"
|
||||
"8DC102A9C9B4C95EFFD9DD2DFE96C29BE647D6C69D66CA500843CFAED6E44019"
|
||||
"6F1DBE0E2E22163C61CA48C79116FA77216726749A976A1C4B0944B5121E8C01",
|
||||
16
|
||||
), # Modulus
|
||||
int("010001", 16) # Exponent
|
||||
),
|
||||
|
||||
# 6427105915145367799
|
||||
0x15931aac70e0d30f7 - (1 << 64): PublicKey( # CDN DC-121
|
||||
# -----BEGIN RSA PUBLIC KEY-----
|
||||
# MIIBCgKCAQEA+Lf3PvgE1yxbJUCMaEAkV0QySTVpnaDjiednB5RbtNWjCeqSVakY
|
||||
@@ -70,6 +178,8 @@ class RSA:
|
||||
), # Modulus
|
||||
int("010001", 16) # Exponent
|
||||
),
|
||||
|
||||
# 2685959930972952888
|
||||
0x1254672538e935938 - (1 << 64): PublicKey( # CDN DC-140
|
||||
# -----BEGIN RSA PUBLIC KEY-----
|
||||
# MIIBCgKCAQEAzuHVC7sE50Kho/yDVZtWnlmA5Bf/aM8KZY3WzS16w6w1sBqipj8o
|
||||
|
@@ -25,7 +25,7 @@ from os import urandom
|
||||
from pyrogram.api import functions, types
|
||||
from pyrogram.api.core import Object, Long, Int
|
||||
from pyrogram.connection import Connection
|
||||
from pyrogram.crypto import IGE, RSA, Prime
|
||||
from pyrogram.crypto import AES, RSA, Prime
|
||||
from .internals import MsgId, DataCenter
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -51,12 +51,12 @@ class Auth:
|
||||
self.test_mode = test_mode
|
||||
|
||||
self.connection = Connection(DataCenter(dc_id, test_mode), proxy)
|
||||
self.msg_id = MsgId()
|
||||
|
||||
def pack(self, data: Object) -> bytes:
|
||||
@staticmethod
|
||||
def pack(data: Object) -> bytes:
|
||||
return (
|
||||
bytes(8)
|
||||
+ Long(self.msg_id())
|
||||
+ Long(MsgId())
|
||||
+ Int(len(data.write()))
|
||||
+ data.write()
|
||||
)
|
||||
@@ -91,8 +91,19 @@ class Auth:
|
||||
# Step 1; Step 2
|
||||
nonce = int.from_bytes(urandom(16), "little", signed=True)
|
||||
log.debug("Send req_pq: {}".format(nonce))
|
||||
res_pq = self.send(functions.ReqPq(nonce))
|
||||
res_pq = self.send(functions.ReqPqMulti(nonce))
|
||||
log.debug("Got ResPq: {}".format(res_pq.server_nonce))
|
||||
log.debug("Server public key fingerprints: {}".format(res_pq.server_public_key_fingerprints))
|
||||
|
||||
for i in res_pq.server_public_key_fingerprints:
|
||||
if i in RSA.server_public_keys:
|
||||
log.debug("Using fingerprint: {}".format(i))
|
||||
public_key_fingerprint = i
|
||||
break
|
||||
else:
|
||||
log.debug("Fingerprint unknown: {}".format(i))
|
||||
else:
|
||||
raise Exception("Public key not found")
|
||||
|
||||
# Step 3
|
||||
pq = int.from_bytes(res_pq.pq, "big")
|
||||
@@ -118,7 +129,7 @@ class Auth:
|
||||
sha = sha1(data).digest()
|
||||
padding = urandom(- (len(data) + len(sha)) % 255)
|
||||
data_with_hash = sha + data + padding
|
||||
encrypted_data = RSA.encrypt(data_with_hash, res_pq.server_public_key_fingerprints[0])
|
||||
encrypted_data = RSA.encrypt(data_with_hash, public_key_fingerprint)
|
||||
|
||||
log.debug("Done encrypt data with RSA")
|
||||
|
||||
@@ -130,7 +141,7 @@ class Auth:
|
||||
server_nonce,
|
||||
int.to_bytes(p, 4, "big"),
|
||||
int.to_bytes(q, 4, "big"),
|
||||
res_pq.server_public_key_fingerprints[0],
|
||||
public_key_fingerprint,
|
||||
encrypted_data
|
||||
)
|
||||
)
|
||||
@@ -152,7 +163,7 @@ class Auth:
|
||||
|
||||
server_nonce = int.from_bytes(server_nonce, "little", signed=True)
|
||||
|
||||
answer_with_hash = IGE.decrypt(encrypted_answer, tmp_aes_key, tmp_aes_iv)
|
||||
answer_with_hash = AES.ige_decrypt(encrypted_answer, tmp_aes_key, tmp_aes_iv)
|
||||
answer = answer_with_hash[20:]
|
||||
|
||||
server_dh_inner_data = Object.read(BytesIO(answer))
|
||||
@@ -181,7 +192,7 @@ class Auth:
|
||||
sha = sha1(data).digest()
|
||||
padding = urandom(- (len(data) + len(sha)) % 16)
|
||||
data_with_hash = sha + data + padding
|
||||
encrypted_data = IGE.encrypt(data_with_hash, tmp_aes_key, tmp_aes_iv)
|
||||
encrypted_data = AES.ige_encrypt(data_with_hash, tmp_aes_key, tmp_aes_iv)
|
||||
|
||||
log.debug("Send set_client_DH_params")
|
||||
set_client_dh_params_answer = self.send(
|
||||
@@ -236,7 +247,7 @@ class Auth:
|
||||
log.debug("Nonce fields check: OK")
|
||||
|
||||
# Step 9
|
||||
server_salt = IGE.xor(new_nonce[:8], server_nonce[:8])
|
||||
server_salt = AES.xor(new_nonce[:8], server_nonce[:8])
|
||||
|
||||
log.debug("Server salt: {}".format(int.from_bytes(server_salt, "little")))
|
||||
|
||||
|
@@ -26,14 +26,13 @@ not_content_related = [Ping, HttpWait, MsgsAck, MsgContainer]
|
||||
|
||||
|
||||
class MsgFactory:
|
||||
def __init__(self, msg_id: MsgId):
|
||||
self.msg_id = msg_id
|
||||
def __init__(self):
|
||||
self.seq_no = SeqNo()
|
||||
|
||||
def __call__(self, body: Object) -> Message:
|
||||
return Message(
|
||||
body,
|
||||
self.msg_id(),
|
||||
MsgId(),
|
||||
self.seq_no(type(body) not in not_content_related),
|
||||
len(body)
|
||||
)
|
||||
|
@@ -16,19 +16,20 @@
|
||||
# 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 threading import Lock
|
||||
from time import time
|
||||
|
||||
|
||||
class MsgId:
|
||||
def __init__(self, delta_time: float = 0.0):
|
||||
self.delta_time = delta_time
|
||||
self.last_time = 0
|
||||
self.offset = 0
|
||||
last_time = 0
|
||||
offset = 0
|
||||
lock = Lock()
|
||||
|
||||
def __call__(self) -> int:
|
||||
now = time()
|
||||
self.offset = self.offset + 4 if now == self.last_time else 0
|
||||
msg_id = int((now + self.delta_time) * 2 ** 32) + self.offset
|
||||
self.last_time = now
|
||||
def __new__(cls) -> int:
|
||||
with cls.lock:
|
||||
now = time()
|
||||
cls.offset = cls.offset + 4 if now == cls.last_time else 0
|
||||
msg_id = int(now * 2 ** 32) + cls.offset
|
||||
cls.last_time = now
|
||||
|
||||
return msg_id
|
||||
return msg_id
|
||||
|
@@ -16,15 +16,19 @@
|
||||
# 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 threading import Lock
|
||||
|
||||
|
||||
class SeqNo:
|
||||
def __init__(self):
|
||||
self.content_related_messages_sent = 0
|
||||
self.lock = Lock()
|
||||
|
||||
def __call__(self, is_content_related: bool) -> int:
|
||||
seq_no = (self.content_related_messages_sent * 2) + (1 if is_content_related else 0)
|
||||
with self.lock:
|
||||
seq_no = (self.content_related_messages_sent * 2) + (1 if is_content_related else 0)
|
||||
|
||||
if is_content_related:
|
||||
self.content_related_messages_sent += 1
|
||||
if is_content_related:
|
||||
self.content_related_messages_sent += 1
|
||||
|
||||
return seq_no
|
||||
return seq_no
|
||||
|
@@ -33,7 +33,7 @@ from pyrogram.api.all import layer
|
||||
from pyrogram.api.core import Message, Object, MsgContainer, Long, FutureSalt, Int
|
||||
from pyrogram.api.errors import Error
|
||||
from pyrogram.connection import Connection
|
||||
from pyrogram.crypto import IGE, KDF
|
||||
from pyrogram.crypto import AES, KDF
|
||||
from .internals import MsgId, MsgFactory, DataCenter
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -60,7 +60,7 @@ class Session:
|
||||
)
|
||||
|
||||
INITIAL_SALT = 0x616e67656c696361
|
||||
NET_WORKERS = 2
|
||||
NET_WORKERS = 1
|
||||
WAIT_TIMEOUT = 10
|
||||
MAX_RETRIES = 5
|
||||
ACKS_THRESHOLD = 8
|
||||
@@ -68,6 +68,20 @@ class Session:
|
||||
|
||||
notice_displayed = False
|
||||
|
||||
BAD_MSG_DESCRIPTION = {
|
||||
16: "[16] msg_id too low, the client time has to be synchronized",
|
||||
17: "[17] msg_id too high, the client time has to be synchronized",
|
||||
18: "[18] incorrect two lower order msg_id bits, the server expects client message msg_id to be divisible by 4",
|
||||
19: "[19] container msg_id is the same as msg_id of a previously received message",
|
||||
20: "[20] message too old, it cannot be verified by the server",
|
||||
32: "[32] msg_seqno too low",
|
||||
33: "[33] msg_seqno too high",
|
||||
34: "[34] an even msg_seqno expected, but odd received",
|
||||
35: "[35] odd msg_seqno expected, but even received",
|
||||
48: "[48] incorrect server salt",
|
||||
64: "[64] invalid container"
|
||||
}
|
||||
|
||||
def __init__(self,
|
||||
dc_id: int,
|
||||
test_mode: bool,
|
||||
@@ -89,9 +103,8 @@ class Session:
|
||||
self.auth_key = auth_key
|
||||
self.auth_key_id = sha1(auth_key).digest()[-8:]
|
||||
|
||||
self.msg_id = MsgId()
|
||||
self.session_id = Long(self.msg_id())
|
||||
self.msg_factory = MsgFactory(self.msg_id)
|
||||
self.session_id = Long(MsgId())
|
||||
self.msg_factory = MsgFactory()
|
||||
|
||||
self.current_salt = None
|
||||
|
||||
@@ -146,7 +159,7 @@ class Session:
|
||||
self.ping_thread.start()
|
||||
|
||||
log.info("Connection inited: Layer {}".format(layer))
|
||||
except (OSError, TimeoutError):
|
||||
except (OSError, TimeoutError, Error):
|
||||
self.stop()
|
||||
else:
|
||||
break
|
||||
@@ -192,14 +205,14 @@ class Session:
|
||||
msg_key = msg_key_large[8:24]
|
||||
aes_key, aes_iv = KDF(self.auth_key, msg_key, True)
|
||||
|
||||
return self.auth_key_id + msg_key + IGE.encrypt(data + padding, aes_key, aes_iv)
|
||||
return self.auth_key_id + msg_key + AES.ige_encrypt(data + padding, aes_key, aes_iv)
|
||||
|
||||
def unpack(self, b: BytesIO) -> Message:
|
||||
assert b.read(8) == self.auth_key_id, b.getvalue()
|
||||
|
||||
msg_key = b.read(16)
|
||||
aes_key, aes_iv = KDF(self.auth_key, msg_key, False)
|
||||
data = BytesIO(IGE.decrypt(b.read(), aes_key, aes_iv))
|
||||
data = BytesIO(AES.ige_decrypt(b.read(), aes_key, aes_iv))
|
||||
data.read(8)
|
||||
|
||||
# https://core.telegram.org/mtproto/security_guidelines#checking-session-id
|
||||
@@ -270,7 +283,7 @@ class Session:
|
||||
msg_id = msg.body.msg_id
|
||||
else:
|
||||
if self.client is not None:
|
||||
self.client.update_queue.put(msg.body)
|
||||
self.client.updates_queue.put(msg.body)
|
||||
|
||||
if msg_id in self.results:
|
||||
self.results[msg_id].value = getattr(msg.body, "result", msg.body)
|
||||
@@ -296,7 +309,7 @@ class Session:
|
||||
break
|
||||
|
||||
try:
|
||||
self._send(functions.Ping(0), False)
|
||||
self._send(functions.PingDelayDisconnect(0, self.PING_INTERVAL + 15), False)
|
||||
except (OSError, TimeoutError):
|
||||
pass
|
||||
|
||||
@@ -338,7 +351,10 @@ class Session:
|
||||
while True:
|
||||
packet = self.connection.recv()
|
||||
|
||||
if packet is None or (len(packet) == 4 and Int.read(BytesIO(packet)) == -404):
|
||||
if packet is None or len(packet) == 4:
|
||||
if packet:
|
||||
log.warning("Server sent \"{}\"".format(Int.read(BytesIO(packet))))
|
||||
|
||||
if self.is_connected.is_set():
|
||||
Thread(target=self.restart, name="RestartThread").start()
|
||||
break
|
||||
@@ -370,6 +386,11 @@ class Session:
|
||||
raise TimeoutError
|
||||
elif isinstance(result, types.RpcError):
|
||||
Error.raise_it(result, type(data))
|
||||
elif isinstance(result, types.BadMsgNotification):
|
||||
raise Exception(self.BAD_MSG_DESCRIPTION.get(
|
||||
result.error_code,
|
||||
"Error code {}".format(result.error_code)
|
||||
))
|
||||
else:
|
||||
return result
|
||||
|
||||
|
Reference in New Issue
Block a user