2
0
mirror of https://github.com/pyrogram/pyrogram synced 2025-09-04 08:15:08 +00:00

Merge branch 'develop' into asyncio

# Conflicts:
#	pyrogram/__init__.py
#	pyrogram/client/client.py
#	pyrogram/client/methods/bots/send_inline_bot_result.py
#	pyrogram/client/methods/chats/pin_chat_message.py
#	pyrogram/client/methods/chats/unpin_chat_message.py
#	pyrogram/client/methods/password/change_cloud_password.py
#	pyrogram/client/methods/password/enable_cloud_password.py
#	pyrogram/client/methods/password/remove_cloud_password.py
This commit is contained in:
Dan
2018-12-25 23:07:45 +01:00
39 changed files with 837 additions and 192 deletions

View File

@@ -39,7 +39,8 @@ from .client.types import (
InputMediaVideo, InputMediaDocument, InputMediaAudio, InputMediaAnimation, InputPhoneContact,
Location, Message, MessageEntity, Dialog, Dialogs, Photo, PhotoSize, Sticker, User, UserStatus,
UserProfilePhotos, Venue, Animation, Video, VideoNote, Voice, CallbackQuery, Messages, ForceReply,
InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove
InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove,
Poll, PollOption
)
from .client import (
Client, ChatAction, ParseMode, Emoji,

View File

@@ -45,8 +45,12 @@ from pyrogram.api.errors import (
PhoneNumberUnoccupied, PhoneCodeInvalid, PhoneCodeHashEmpty,
PhoneCodeExpired, PhoneCodeEmpty, SessionPasswordNeeded,
PasswordHashInvalid, FloodWait, PeerIdInvalid, FirstnameInvalid, PhoneNumberBanned,
VolumeLocNotFound, UserMigrate, FileIdInvalid, ChannelPrivate, PhoneNumberOccupied)
VolumeLocNotFound, UserMigrate, FileIdInvalid, ChannelPrivate, PhoneNumberOccupied,
PasswordRecoveryNa, PasswordEmpty
)
from pyrogram.client.handlers import DisconnectHandler
from pyrogram.client.handlers.handler import Handler
from pyrogram.client.methods.password.utils import compute_check
from pyrogram.crypto import AES
from pyrogram.session import Auth, Session
from .dispatcher import Dispatcher
@@ -578,21 +582,47 @@ class Client(Methods, BaseClient):
self.first_name = None
except SessionPasswordNeeded as e:
print(e.MESSAGE)
r = await self.send(functions.account.GetPassword())
while True:
try:
r = await self.send(functions.account.GetPassword())
if self.password is None:
print("Hint: {}".format(r.hint))
self.password = await ainput("Enter password: ")
if type(self.password) is str:
self.password = r.current_salt + self.password.encode() + r.current_salt
self.password = await ainput("Enter password (empty to recover): ")
password_hash = sha256(self.password).digest()
if self.password == "":
r = await self.send(functions.auth.RequestPasswordRecovery())
r = await self.send(functions.auth.CheckPassword(password_hash))
print("An e-mail containing the recovery code has been sent to {}".format(
r.email_pattern
))
r = await self.send(
functions.auth.RecoverPassword(
code=await ainput("Enter password recovery code: ")
)
)
else:
r = await self.send(
functions.auth.CheckPassword(
password=compute_check(r, self.password)
)
)
except PasswordEmpty as e:
if password_hash_invalid_raises:
raise
else:
print(e.MESSAGE)
self.password = None
except PasswordRecoveryNa as e:
if password_hash_invalid_raises:
raise
else:
print(e.MESSAGE)
self.password = None
except PasswordHashInvalid as e:
if password_hash_invalid_raises:
raise
@@ -605,6 +635,7 @@ class Client(Methods, BaseClient):
else:
print(e.MESSAGE.format(x=e.x))
time.sleep(e.x)
self.password = None
except Exception as e:
log.error(e, exc_info=True)
else:
@@ -1263,7 +1294,7 @@ class Client(Methods, BaseClient):
volume_id: int = None,
local_id: int = None,
secret: int = None,
version: int = 0,
size: int = None,
progress: callable = None,
progress_args: tuple = ()) -> str:
@@ -1311,13 +1342,14 @@ class Client(Methods, BaseClient):
location = types.InputFileLocation(
volume_id=volume_id,
local_id=local_id,
secret=secret
secret=secret,
file_reference=b""
)
else: # Any other file can be more easily accessed by id and access_hash
location = types.InputDocumentFileLocation(
id=id,
access_hash=access_hash,
version=version
file_reference=b""
)
limit = 1024 * 1024

View File

@@ -121,6 +121,9 @@ class Filters:
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."""
private = create("Private", lambda _, m: bool(m.chat and m.chat.type == "private"))
"""Filter messages sent in private chats."""

View File

@@ -28,7 +28,8 @@ class SendInlineBotResult(BaseClient):
query_id: int,
result_id: str,
disable_notification: bool = None,
reply_to_message_id: int = None):
reply_to_message_id: int = None,
hide_via: bool = None):
"""Use this method to send an inline bot result.
Bot results can be retrieved using :obj:`get_inline_bot_results <pyrogram.Client.get_inline_bot_results>`
@@ -51,6 +52,9 @@ class SendInlineBotResult(BaseClient):
reply_to_message_id (``bool``, *optional*):
If the message is a reply, ID of the original message.
hide_via (``bool``):
Sends the message with *via @bot* hidden.
Returns:
On success, the sent Message is returned.
@@ -64,6 +68,7 @@ class SendInlineBotResult(BaseClient):
id=result_id,
random_id=self.rnd_id(),
silent=disable_notification or None,
reply_to_msg_id=reply_to_message_id
reply_to_msg_id=reply_to_message_id,
hide_via=hide_via or None
)
)

View File

@@ -18,7 +18,7 @@
from typing import Union
from pyrogram.api import functions, types
from pyrogram.api import functions
from ...ext import BaseClient
@@ -27,7 +27,7 @@ class PinChatMessage(BaseClient):
chat_id: Union[int, str],
message_id: int,
disable_notification: bool = None) -> bool:
"""Use this method to pin a message in a supergroup or a channel.
"""Use this method to 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.
@@ -49,19 +49,10 @@ class PinChatMessage(BaseClient):
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
``ValueError`` if a chat_id doesn't belong to a supergroup or a channel.
"""
peer = await self.resolve_peer(chat_id)
if isinstance(peer, types.InputPeerChannel):
await self.send(
functions.channels.UpdatePinnedMessage(
channel=peer,
id=message_id,
silent=disable_notification or None
)
await self.send(
functions.messages.UpdatePinnedMessage(
peer=await self.resolve_peer(chat_id),
id=message_id,
silent=disable_notification or None
)
elif isinstance(peer, types.InputPeerChat):
raise ValueError("The chat_id \"{}\" belongs to a basic group".format(chat_id))
else:
raise ValueError("The chat_id \"{}\" belongs to a user".format(chat_id))
return True
)

View File

@@ -61,7 +61,8 @@ class SetChatPhoto(BaseClient):
photo = types.InputChatPhoto(
id=types.InputPhoto(
id=s[0],
access_hash=s[1]
access_hash=s[1],
file_reference=b""
)
)

View File

@@ -18,14 +18,14 @@
from typing import Union
from pyrogram.api import functions, types
from pyrogram.api import functions
from ...ext import BaseClient
class UnpinChatMessage(BaseClient):
async def unpin_chat_message(self,
chat_id: Union[int, str]) -> bool:
"""Use this method to unpin a message in a supergroup or a channel.
"""Use this method to 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.
@@ -40,18 +40,11 @@ class UnpinChatMessage(BaseClient):
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
``ValueError`` if a chat_id doesn't belong to a supergroup or a channel.
"""
peer = await self.resolve_peer(chat_id)
if isinstance(peer, types.InputPeerChannel):
await self.send(
functions.channels.UpdatePinnedMessage(
channel=peer,
id=0
)
await self.send(
functions.messages.UpdatePinnedMessage(
peer=await self.resolve_peer(chat_id),
id=0
)
elif isinstance(peer, types.InputPeerChat):
raise ValueError("The chat_id \"{}\" belongs to a basic group".format(chat_id))
else:
raise ValueError("The chat_id \"{}\" belongs to a user".format(chat_id))
)
return True

View File

@@ -24,6 +24,7 @@ from .edit_message_text import EditMessageText
from .forward_messages import ForwardMessages
from .get_history import GetHistory
from .get_messages import GetMessages
from .retract_vote import RetractVote
from .send_animation import SendAnimation
from .send_audio import SendAudio
from .send_chat_action import SendChatAction
@@ -33,11 +34,13 @@ from .send_location import SendLocation
from .send_media_group import SendMediaGroup
from .send_message import SendMessage
from .send_photo import SendPhoto
from .send_poll import SendPoll
from .send_sticker import SendSticker
from .send_venue import SendVenue
from .send_video import SendVideo
from .send_video_note import SendVideoNote
from .send_voice import SendVoice
from .vote_poll import VotePoll
class Messages(
@@ -62,6 +65,9 @@ class Messages(
SendVenue,
SendVideo,
SendVideoNote,
SendVoice
SendVoice,
SendPoll,
VotePoll,
RetractVote
):
pass

View File

@@ -84,7 +84,8 @@ class EditMessageMedia(BaseClient):
media = types.InputMediaPhoto(
id=types.InputPhoto(
id=media.photo.id,
access_hash=media.photo.access_hash
access_hash=media.photo.access_hash,
file_reference=b""
)
)
elif media.media.startswith("http"):
@@ -110,7 +111,8 @@ class EditMessageMedia(BaseClient):
media = types.InputMediaPhoto(
id=types.InputPhoto(
id=unpacked[2],
access_hash=unpacked[3]
access_hash=unpacked[3],
file_reference=b""
)
)
@@ -138,7 +140,8 @@ class EditMessageMedia(BaseClient):
media = types.InputMediaDocument(
id=types.InputDocument(
id=media.document.id,
access_hash=media.document.access_hash
access_hash=media.document.access_hash,
file_reference=b""
)
)
elif media.media.startswith("http"):
@@ -164,7 +167,8 @@ class EditMessageMedia(BaseClient):
media = types.InputMediaDocument(
id=types.InputDocument(
id=unpacked[2],
access_hash=unpacked[3]
access_hash=unpacked[3],
file_reference=b""
)
)
@@ -191,7 +195,8 @@ class EditMessageMedia(BaseClient):
media = types.InputMediaDocument(
id=types.InputDocument(
id=media.document.id,
access_hash=media.document.access_hash
access_hash=media.document.access_hash,
file_reference=b""
)
)
elif media.media.startswith("http"):
@@ -217,7 +222,8 @@ class EditMessageMedia(BaseClient):
media = types.InputMediaDocument(
id=types.InputDocument(
id=unpacked[2],
access_hash=unpacked[3]
access_hash=unpacked[3],
file_reference=b""
)
)
@@ -246,7 +252,8 @@ class EditMessageMedia(BaseClient):
media = types.InputMediaDocument(
id=types.InputDocument(
id=media.document.id,
access_hash=media.document.access_hash
access_hash=media.document.access_hash,
file_reference=b""
)
)
elif media.media.startswith("http"):
@@ -272,7 +279,8 @@ class EditMessageMedia(BaseClient):
media = types.InputMediaDocument(
id=types.InputDocument(
id=unpacked[2],
access_hash=unpacked[3]
access_hash=unpacked[3],
file_reference=b""
)
)
@@ -294,7 +302,8 @@ class EditMessageMedia(BaseClient):
media = types.InputMediaDocument(
id=types.InputDocument(
id=media.document.id,
access_hash=media.document.access_hash
access_hash=media.document.access_hash,
file_reference=b""
)
)
elif media.media.startswith("http"):
@@ -320,7 +329,8 @@ class EditMessageMedia(BaseClient):
media = types.InputMediaDocument(
id=types.InputDocument(
id=unpacked[2],
access_hash=unpacked[3]
access_hash=unpacked[3],
file_reference=b""
)
)

View File

@@ -0,0 +1,54 @@
# 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/>.
from typing import Union
from pyrogram.api import functions
from pyrogram.client.ext import BaseClient
class RetractVote(BaseClient):
def retract_vote(self,
chat_id: Union[int, str],
message_id: id) -> bool:
"""Use this method to retract your vote in a poll.
Args:
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".
For a contact that exists in your Telegram address book you can use his phone number (str).
message_id (``int``):
Unique poll message identifier inside this chat.
Returns:
On success, True is returned.
Raises:
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
"""
self.send(
functions.messages.SendVote(
peer=self.resolve_peer(chat_id),
msg_id=message_id,
options=[]
)
)
return True

View File

@@ -167,7 +167,8 @@ class SendAnimation(BaseClient):
media = types.InputMediaDocument(
id=types.InputDocument(
id=unpacked[2],
access_hash=unpacked[3]
access_hash=unpacked[3],
file_reference=b""
)
)

View File

@@ -166,7 +166,8 @@ class SendAudio(BaseClient):
media = types.InputMediaDocument(
id=types.InputDocument(
id=unpacked[2],
access_hash=unpacked[3]
access_hash=unpacked[3],
file_reference=b""
)
)

View File

@@ -147,7 +147,8 @@ class SendDocument(BaseClient):
media = types.InputMediaDocument(
id=types.InputDocument(
id=unpacked[2],
access_hash=unpacked[3]
access_hash=unpacked[3],
file_reference=b""
)
)

View File

@@ -77,7 +77,8 @@ class SendMediaGroup(BaseClient):
media = types.InputMediaPhoto(
id=types.InputPhoto(
id=media.photo.id,
access_hash=media.photo.access_hash
access_hash=media.photo.access_hash,
file_reference=b""
)
)
else:
@@ -99,7 +100,8 @@ class SendMediaGroup(BaseClient):
media = types.InputMediaPhoto(
id=types.InputPhoto(
id=unpacked[2],
access_hash=unpacked[3]
access_hash=unpacked[3],
file_reference=b""
)
)
elif isinstance(i, pyrogram.InputMediaVideo):
@@ -127,7 +129,8 @@ class SendMediaGroup(BaseClient):
media = types.InputMediaDocument(
id=types.InputDocument(
id=media.document.id,
access_hash=media.document.access_hash
access_hash=media.document.access_hash,
file_reference=b""
)
)
else:
@@ -149,7 +152,8 @@ class SendMediaGroup(BaseClient):
media = types.InputMediaDocument(
id=types.InputDocument(
id=unpacked[2],
access_hash=unpacked[3]
access_hash=unpacked[3],
file_reference=b""
)
)

View File

@@ -142,7 +142,8 @@ class SendPhoto(BaseClient):
media = types.InputMediaPhoto(
id=types.InputPhoto(
id=unpacked[2],
access_hash=unpacked[3]
access_hash=unpacked[3],
file_reference=b""
),
ttl_seconds=ttl_seconds
)

View File

@@ -0,0 +1,95 @@
# 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/>.
from typing import Union, List
import pyrogram
from pyrogram.api import functions, types
from pyrogram.client.ext import BaseClient
class SendPoll(BaseClient):
def send_poll(self,
chat_id: Union[int, str],
question: str,
options: List[str],
disable_notification: bool = None,
reply_to_message_id: int = None,
reply_markup: Union["pyrogram.InlineKeyboardMarkup",
"pyrogram.ReplyKeyboardMarkup",
"pyrogram.ReplyKeyboardRemove",
"pyrogram.ForceReply"] = None) -> "pyrogram.Message":
"""Use this method to send a new poll.
Args:
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".
For a contact that exists in your Telegram address book you can use his phone number (str).
question (``str``):
The poll question, as string.
options (List of ``str``):
The poll options, as list of strings (2 to 10 options are allowed).
disable_notification (``bool``, *optional*):
Sends the message silently.
Users will receive a notification with no sound.
reply_to_message_id (``int``, *optional*):
If the message is a reply, ID of the original message.
reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*):
Additional interface options. An object for an inline keyboard, custom reply keyboard,
instructions to remove reply keyboard or to force a reply from the user.
Returns:
On success, the sent :obj:`Message <pyrogram.Message>` is returned.
Raises:
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
"""
r = self.send(
functions.messages.SendMedia(
peer=self.resolve_peer(chat_id),
media=types.InputMediaPoll(
poll=types.Poll(
id=0,
question=question,
answers=[
types.PollAnswer(text=o, option=bytes([i]))
for i, o in enumerate(options)
]
)
),
message="",
silent=disable_notification or None,
reply_to_msg_id=reply_to_message_id,
random_id=self.rnd_id(),
reply_markup=reply_markup.write() if reply_markup else None
)
)
for i in r.updates:
if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)):
return pyrogram.Message._parse(
self, i.message,
{i.id: i for i in r.users},
{i.id: i for i in r.chats}
)

View File

@@ -127,7 +127,8 @@ class SendSticker(BaseClient):
media = types.InputMediaDocument(
id=types.InputDocument(
id=unpacked[2],
access_hash=unpacked[3]
access_hash=unpacked[3],
file_reference=b""
)
)

View File

@@ -170,7 +170,8 @@ class SendVideo(BaseClient):
media = types.InputMediaDocument(
id=types.InputDocument(
id=unpacked[2],
access_hash=unpacked[3]
access_hash=unpacked[3],
file_reference=b""
)
)

View File

@@ -145,7 +145,8 @@ class SendVideoNote(BaseClient):
media = types.InputMediaDocument(
id=types.InputDocument(
id=unpacked[2],
access_hash=unpacked[3]
access_hash=unpacked[3],
file_reference=b""
)
)

View File

@@ -146,7 +146,8 @@ class SendVoice(BaseClient):
media = types.InputMediaDocument(
id=types.InputDocument(
id=unpacked[2],
access_hash=unpacked[3]
access_hash=unpacked[3],
file_reference=b""
)
)

View File

@@ -0,0 +1,60 @@
# 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/>.
from typing import Union
from pyrogram.api import functions
from pyrogram.client.ext import BaseClient
class VotePoll(BaseClient):
def vote_poll(self,
chat_id: Union[int, str],
message_id: id,
option: int) -> bool:
"""Use this method to vote a poll.
Args:
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".
For a contact that exists in your Telegram address book you can use his phone number (str).
message_id (``int``):
Unique poll message identifier inside this chat.
option (``int``):
Index of the poll option you want to vote for (0 to 9).
Returns:
On success, True is returned.
Raises:
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
"""
poll = self.get_messages(chat_id, message_id).poll
self.send(
functions.messages.SendVote(
peer=self.resolve_peer(chat_id),
msg_id=message_id,
options=[poll.options[option].data]
)
)
return True

View File

@@ -17,17 +17,17 @@
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
import os
from hashlib import sha256
from pyrogram.api import functions, types
from .utils import compute_hash, compute_check, btoi, itob
from ...ext import BaseClient
class ChangeCloudPassword(BaseClient):
async def change_cloud_password(self,
current_password: str,
new_password: str,
new_hint: str = "") -> bool:
current_password: str,
new_password: str,
new_hint: str = "") -> bool:
"""Use this method to change your Two-Step Verification password (Cloud Password) with a new one.
Args:
@@ -41,28 +41,30 @@ class ChangeCloudPassword(BaseClient):
A new password hint.
Returns:
True on success, False otherwise.
True on success.
Raises:
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
``ValueError`` in case there is no cloud password to change.
"""
r = await self.send(functions.account.GetPassword())
if isinstance(r, types.account.Password):
current_password_hash = sha256(r.current_salt + current_password.encode() + r.current_salt).digest()
if not r.has_password:
raise ValueError("There is no cloud password to change")
new_salt = r.new_salt + os.urandom(8)
new_password_hash = sha256(new_salt + new_password.encode() + new_salt).digest()
r.new_algo.salt1 += os.urandom(32)
new_hash = btoi(compute_hash(r.new_algo, new_password))
new_hash = itob(pow(r.new_algo.g, new_hash, btoi(r.new_algo.p)))
return await self.send(
functions.account.UpdatePasswordSettings(
current_password_hash=current_password_hash,
new_settings=types.account.PasswordInputSettings(
new_salt=new_salt,
new_password_hash=new_password_hash,
hint=new_hint
)
await self.send(
functions.account.UpdatePasswordSettings(
password=compute_check(r, current_password),
new_settings=types.account.PasswordInputSettings(
new_algo=r.new_algo,
new_password_hash=new_hash,
hint=new_hint
)
)
else:
return False
)
return True

View File

@@ -17,9 +17,9 @@
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
import os
from hashlib import sha256
from pyrogram.api import functions, types
from .utils import compute_hash, btoi, itob
from ...ext import BaseClient
@@ -27,10 +27,10 @@ class EnableCloudPassword(BaseClient):
async def enable_cloud_password(self,
password: str,
hint: str = "",
email: str = "") -> bool:
email: str = None) -> bool:
"""Use this method to enable the Two-Step Verification security feature (Cloud Password) on your account.
This password will be asked when you log in on a new device in addition to the SMS code.
This password will be asked when you log-in on a new device in addition to the SMS code.
Args:
password (``str``):
@@ -43,27 +43,31 @@ class EnableCloudPassword(BaseClient):
Recovery e-mail.
Returns:
True on success, False otherwise.
True on success.
Raises:
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
``ValueError`` in case there is already a cloud password enabled.
"""
r = await self.send(functions.account.GetPassword())
if isinstance(r, types.account.NoPassword):
salt = r.new_salt + os.urandom(8)
password_hash = sha256(salt + password.encode() + salt).digest()
if r.has_password:
raise ValueError("There is already a cloud password enabled")
return await self.send(
functions.account.UpdatePasswordSettings(
current_password_hash=salt,
new_settings=types.account.PasswordInputSettings(
new_salt=salt,
new_password_hash=password_hash,
hint=hint,
email=email
)
r.new_algo.salt1 += os.urandom(32)
new_hash = btoi(compute_hash(r.new_algo, password))
new_hash = itob(pow(r.new_algo.g, new_hash, btoi(r.new_algo.p)))
await self.send(
functions.account.UpdatePasswordSettings(
password=types.InputCheckPasswordEmpty(),
new_settings=types.account.PasswordInputSettings(
new_algo=r.new_algo,
new_password_hash=new_hash,
hint=hint,
email=email
)
)
else:
return False
)
return True

View File

@@ -16,9 +16,8 @@
# 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 hashlib import sha256
from pyrogram.api import functions, types
from .utils import compute_check
from ...ext import BaseClient
@@ -32,25 +31,26 @@ class RemoveCloudPassword(BaseClient):
Your current password.
Returns:
True on success, False otherwise.
True on success.
Raises:
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
``ValueError`` in case there is no cloud password to remove.
"""
r = await self.send(functions.account.GetPassword())
if isinstance(r, types.account.Password):
password_hash = sha256(r.current_salt + password.encode() + r.current_salt).digest()
if not r.has_password:
raise ValueError("There is no cloud password to remove")
return await self.send(
functions.account.UpdatePasswordSettings(
current_password_hash=password_hash,
new_settings=types.account.PasswordInputSettings(
new_salt=b"",
new_password_hash=b"",
hint=""
)
await self.send(
functions.account.UpdatePasswordSettings(
password=compute_check(r, password),
new_settings=types.account.PasswordInputSettings(
new_algo=types.PasswordKdfAlgoUnknown(),
new_password_hash=b"",
hint=""
)
)
else:
return False
)
return True

View File

@@ -0,0 +1,104 @@
# 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 hashlib
import os
from pyrogram.api import types
def btoi(b: bytes) -> int:
return int.from_bytes(b, "big")
def itob(i: int) -> bytes:
return i.to_bytes(256, "big")
def sha256(data: bytes) -> bytes:
return hashlib.sha256(data).digest()
def xor(a: bytes, b: bytes) -> bytes:
return bytes(i ^ j for i, j in zip(a, b))
def compute_hash(algo: types.PasswordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow, password: str) -> bytes:
hash1 = sha256(algo.salt1 + password.encode() + algo.salt1)
hash2 = sha256(algo.salt2 + hash1 + algo.salt2)
hash3 = hashlib.pbkdf2_hmac("sha512", hash2, algo.salt1, 100000)
return sha256(algo.salt2 + hash3 + algo.salt2)
# noinspection PyPep8Naming
def compute_check(r: types.account.Password, password: str) -> types.InputCheckPasswordSRP:
algo = r.current_algo
p_bytes = algo.p
p = btoi(algo.p)
g_bytes = itob(algo.g)
g = algo.g
B_bytes = r.srp_B
B = btoi(B_bytes)
srp_id = r.srp_id
x_bytes = compute_hash(algo, password)
x = btoi(x_bytes)
g_x = pow(g, x, p)
k_bytes = sha256(p_bytes + g_bytes)
k = btoi(k_bytes)
kg_x = (k * g_x) % p
while True:
a_bytes = os.urandom(256)
a = btoi(a_bytes)
A = pow(g, a, p)
A_bytes = itob(A)
u = btoi(sha256(A_bytes + B_bytes))
if u > 0:
break
g_b = (B - kg_x) % p
ux = u * x
a_ux = a + ux
S = pow(g_b, a_ux, p)
S_bytes = itob(S)
K_bytes = sha256(S_bytes)
M1_bytes = sha256(
xor(sha256(p_bytes), sha256(g_bytes))
+ sha256(algo.salt1)
+ sha256(algo.salt2)
+ A_bytes
+ B_bytes
+ K_bytes
)
return types.InputCheckPasswordSRP(srp_id, A_bytes, M1_bytes)

View File

@@ -49,7 +49,8 @@ class DeleteUserProfilePhotos(BaseClient):
input_photos.append(
types.InputPhoto(
id=s[0],
access_hash=s[1]
access_hash=s[1],
file_reference=b""
)
)

View File

@@ -31,7 +31,7 @@ from .input_media import (
from .messages_and_media import (
Audio, Contact, Document, Animation, Location, Photo, PhotoSize,
Sticker, Venue, Video, VideoNote, Voice, UserProfilePhotos,
Message, Messages, MessageEntity
Message, Messages, MessageEntity, Poll, PollOption
)
from .user_and_chats import (
Chat, ChatMember, ChatMembers, ChatPhoto,

View File

@@ -26,6 +26,8 @@ from .message_entity import MessageEntity
from .messages import Messages
from .photo import Photo
from .photo_size import PhotoSize
from .poll import Poll
from .poll_option import PollOption
from .sticker import Sticker
from .user_profile_photos import UserProfilePhotos
from .venue import Venue

View File

@@ -262,6 +262,7 @@ class Message(PyrogramType):
location: "pyrogram.Location" = None,
venue: "pyrogram.Venue" = None,
web_page: bool = None,
poll: "pyrogram.Poll" = None,
new_chat_members: List[User] = None,
left_chat_member: User = None,
new_chat_title: str = None,
@@ -317,6 +318,7 @@ class Message(PyrogramType):
self.location = location
self.venue = venue
self.web_page = web_page
self.poll = poll
self.new_chat_members = new_chat_members
self.left_chat_member = left_chat_member
self.new_chat_title = new_chat_title
@@ -440,6 +442,7 @@ class Message(PyrogramType):
sticker = None
document = None
web_page = None
poll = None
media = message.media
@@ -494,6 +497,8 @@ class Message(PyrogramType):
elif isinstance(media, types.MessageMediaWebPage):
web_page = True
media = None
elif isinstance(media, types.MessageMediaPoll):
poll = pyrogram.Poll._parse(client, media)
else:
media = None
@@ -542,6 +547,7 @@ class Message(PyrogramType):
sticker=sticker,
document=document,
web_page=web_page,
poll=poll,
views=message.views,
via_bot=User._parse(client, users.get(message.via_bot_id, None)),
outgoing=message.out,

View File

@@ -0,0 +1,102 @@
# 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/>.
from typing import List
import pyrogram
from pyrogram.api import types
from .poll_option import PollOption
from ..pyrogram_type import PyrogramType
class Poll(PyrogramType):
"""This object represents a Poll.
Args:
id (``int``):
The poll id in this chat.
closed (``bool``):
Whether the poll is closed or not.
question (``str``):
Poll question.
options (List of :obj:`PollOption`):
The available poll options.
total_voters (``int``):
Total amount of voters for this poll.
option_chosen (``int``, *optional*):
The index of your chosen option (in case you voted already), None otherwise.
"""
def __init__(self,
*,
client: "pyrogram.client.ext.BaseClient",
id: int,
closed: bool,
question: str,
options: List[PollOption],
total_voters: int,
option_chosen: int = None):
super().__init__(client)
self.id = id
self.closed = closed
self.question = question
self.options = options
self.total_voters = total_voters
self.option_chosen = option_chosen
@staticmethod
def _parse(client, media_poll: types.MessageMediaPoll) -> "Poll":
poll = media_poll.poll
results = media_poll.results.results
total_voters = media_poll.results.total_voters
option_chosen = None
options = []
for i, answer in enumerate(poll.answers):
voters = 0
if results:
result = results[i]
voters = result.voters
if result.chosen:
option_chosen = i
options.append(PollOption(
text=answer.text,
voters=voters,
data=answer.option,
client=client
))
return Poll(
id=poll.id,
closed=poll.closed,
question=poll.question,
options=options,
total_voters=total_voters,
option_chosen=option_chosen,
client=client
)

View File

@@ -0,0 +1,47 @@
# 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 pyrogram
from ..pyrogram_type import PyrogramType
class PollOption(PyrogramType):
"""This object represents a Poll Option.
Args:
text (``str``):
Text of the poll option.
voters (``int``):
The number of users who voted this option.
data (``bytes``):
Unique data that identifies this option among all the other options in a poll.
"""
def __init__(self,
*,
client: "pyrogram.client.ext.BaseClient",
text: str,
voters: int,
data: bytes):
super().__init__(client)
self.text = text
self.voters = voters
self.data = data

View File

@@ -199,11 +199,11 @@ class Chat(PyrogramType):
parsed_chat.can_set_sticker_set = full_chat.can_set_stickers
parsed_chat.sticker_set_name = full_chat.stickerset
if full_chat.pinned_msg_id:
parsed_chat.pinned_message = client.get_messages(
parsed_chat.id,
message_ids=full_chat.pinned_msg_id
)
if full_chat.pinned_msg_id:
parsed_chat.pinned_message = client.get_messages(
parsed_chat.id,
message_ids=full_chat.pinned_msg_id
)
if isinstance(full_chat.exported_invite, types.ChatInviteExported):
parsed_chat.invite_link = full_chat.exported_invite.link