Merge branch 'develop' into asyncio

# Conflicts:
#	pyrogram/client/client.py
#	pyrogram/client/methods/chats/join_chat.py
#	pyrogram/client/methods/messages/edit_message_media.py
This commit is contained in:
Dan 2019-03-02 16:36:44 +01:00
commit 02a1dde399
22 changed files with 187 additions and 35 deletions

View File

@ -20,7 +20,9 @@ Pyrogram
**Pyrogram** is an elegant, easy-to-use Telegram_ client library and framework written from the ground up in Python and C.
It enables you to easily create custom apps using both user and bot identities (bot API alternative) via the `MTProto API`_.
`A fully-asynchronous variant is also available » <https://github.com/pyrogram/pyrogram/issues/181>`_
`Pyrogram in fully-asynchronous mode is also available » <https://github.com/pyrogram/pyrogram/issues/181>`_
`Working PoC of Telegram voice calls using Pyrogram » <https://github.com/bakatrouble/pytgvoip>`_
Features
--------

View File

@ -88,5 +88,6 @@ MEDIA_INVALID The media is invalid
BOT_SCORE_NOT_MODIFIED The bot score was not modified
USER_BOT_REQUIRED The method can be used by bots only
IMAGE_PROCESS_FAILED The server failed to process your image
USERNAME_NOT_MODIFIED The username was not modified
CALL_ALREADY_ACCEPTED The call is already accepted
CALL_ALREADY_DECLINED The call is already declined

1 id message
88 BOT_SCORE_NOT_MODIFIED The bot score was not modified
89 USER_BOT_REQUIRED The method can be used by bots only
90 IMAGE_PROCESS_FAILED The server failed to process your image
91 USERNAME_NOT_MODIFIED The username was not modified
92 CALL_ALREADY_ACCEPTED The call is already accepted
93 CALL_ALREADY_DECLINED The call is already declined

View File

@ -13,4 +13,4 @@ with app:
app.send_location("me", 51.500729, -0.124583)
# Send a sticker
app.send_sticker("me", "CAADBAADhw4AAvLQYAHICbZ5SUs_jwI")
app.send_sticker("me", "CAADBAADyg4AAvLQYAEYD4F7vcZ43AI")

View File

@ -30,6 +30,7 @@ import shutil
import struct
import tempfile
import time
import warnings
from configparser import ConfigParser
from datetime import datetime
from hashlib import sha256, md5
@ -68,10 +69,10 @@ class Client(Methods, BaseClient):
Args:
session_name (``str``):
Name to uniquely identify a session of either a User or a Bot.
For Users: pass a string of your choice, e.g.: "my_main_account".
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.
Name to uniquely identify a session of either a User or a Bot, e.g.: "my_account". This name will be used
to save a file to disk that stores details needed for reconnecting without asking again for credentials.
Note for bots: You can pass a bot token here, but this usage will be deprecated in next releases.
Use *bot_token* instead.
api_id (``int``, *optional*):
The *api_id* part of your Telegram API Key, as integer. E.g.: 12345
@ -145,6 +146,10 @@ class Client(Methods, BaseClient):
a new Telegram account in case the phone number you passed is not registered yet.
Only applicable for new sessions.
bot_token (``str``, *optional*):
Pass your Bot API token to create a bot session, e.g.: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"
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: "". Only applicable for new sessions.
@ -193,6 +198,7 @@ class Client(Methods, BaseClient):
password: str = None,
recovery_code: callable = None,
force_sms: bool = False,
bot_token: str = None,
first_name: str = None,
last_name: str = None,
workers: int = BaseClient.WORKERS,
@ -219,6 +225,7 @@ class Client(Methods, BaseClient):
self.password = password
self.recovery_code = recovery_code
self.force_sms = force_sms
self.bot_token = bot_token
self.first_name = first_name
self.last_name = last_name
self.workers = workers
@ -264,8 +271,13 @@ class Client(Methods, BaseClient):
raise ConnectionError("Client has already been started")
if self.BOT_TOKEN_RE.match(self.session_name):
self.is_bot = True
self.bot_token = self.session_name
self.session_name = self.session_name.split(":")[0]
warnings.warn('\nYou are using a bot token as session name.\n'
'It will be deprecated in next update, please use session file name to load '
'existing sessions and bot_token argument to create new sessions.',
DeprecationWarning, stacklevel=2)
self.load_config()
await self.load_session()
@ -283,13 +295,15 @@ class Client(Methods, BaseClient):
try:
if self.user_id is None:
if self.bot_token is None:
self.is_bot = False
await self.authorize_user()
else:
self.is_bot = True
await self.authorize_bot()
self.save_session()
if self.bot_token is None:
if not self.is_bot:
if self.takeout:
self.takeout_id = (await self.send(functions.account.InitTakeoutSession())).id
log.warning("Takeout session {} initiated".format(self.takeout_id))
@ -1112,6 +1126,8 @@ class Client(Methods, BaseClient):
self.auth_key = base64.b64decode("".join(s["auth_key"]))
self.user_id = s["user_id"]
self.date = s.get("date", 0)
# TODO: replace default with False once token session name will be deprecated
self.is_bot = s.get("is_bot", self.is_bot)
for k, v in s.get("peers_by_id", {}).items():
self.peers_by_id[int(k)] = utils.get_input_peer(int(k), v)
@ -1138,7 +1154,7 @@ class Client(Methods, BaseClient):
if include is None:
for path in sorted(Path(root).rglob("*.py")):
module_path = os.path.splitext(str(path))[0].replace("/", ".")
module_path = '.'.join(path.parent.parts + (path.stem,))
module = import_module(module_path)
for name in vars(module).keys():
@ -1245,7 +1261,8 @@ class Client(Methods, BaseClient):
test_mode=self.test_mode,
auth_key=auth_key,
user_id=self.user_id,
date=self.date
date=self.date,
is_bot=self.is_bot,
),
f,
indent=4

View File

@ -67,7 +67,7 @@ class BaseClient:
}
def __init__(self):
self.bot_token = None
self.is_bot = None
self.dc_id = None
self.auth_key = None
self.user_id = None

View File

@ -92,6 +92,7 @@ class Syncer:
auth_key=auth_key,
user_id=client.user_id,
date=int(time.time()),
is_bot=client.is_bot,
peers_by_id={
k: getattr(v, "access_hash", None)
for k, v in client.peers_by_id.copy().items()

View File

@ -37,6 +37,7 @@ from .set_chat_photo import SetChatPhoto
from .set_chat_title import SetChatTitle
from .unban_chat_member import UnbanChatMember
from .unpin_chat_message import UnpinChatMessage
from .update_chat_username import UpdateChatUsername
class Chats(
@ -60,6 +61,7 @@ class Chats(
GetChatMembersCount,
GetChatPreview,
IterDialogs,
IterChatMembers
IterChatMembers,
UpdateChatUsername
):
pass

View File

@ -16,6 +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/>.
import pyrogram
from pyrogram.api import functions, types
from ...ext import BaseClient
@ -30,17 +31,24 @@ class JoinChat(BaseClient):
Unique identifier for the target chat in form of a *t.me/joinchat/* link or username of the target
channel/supergroup (in the format @username).
Returns:
On success, a :obj:`Chat <pyrogram.Chat>` object is returned.
Raises:
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
"""
match = self.INVITE_LINK_RE.match(chat_id)
if match:
return await self.send(
chat = await self.send(
functions.messages.ImportChatInvite(
hash=match.group(1)
)
)
if isinstance(chat.chats[0], types.Chat):
return pyrogram.Chat._parse_chat_chat(self, chat.chats[0])
elif isinstance(chat.chats[0], types.Channel):
return pyrogram.Chat._parse_channel_chat(self, chat.chats[0])
else:
resolved_peer = await self.send(
functions.contacts.ResolveUsername(
@ -53,8 +61,10 @@ class JoinChat(BaseClient):
access_hash=resolved_peer.chats[0].access_hash
)
return await self.send(
chat = await self.send(
functions.channels.JoinChannel(
channel=channel
)
)
return pyrogram.Chat._parse_channel_chat(self, chat.chats[0])

View File

@ -0,0 +1,59 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2019 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, types
from ...ext import BaseClient
class UpdateChatUsername(BaseClient):
def update_chat_username(self,
chat_id: Union[int, str],
username: Union[str, None]) -> bool:
"""Use this method to update a channel or a supergroup username.
To update your own username (for users only, not bots) you can use :meth:`update_username`.
Args:
chat_id (``int`` | ``str``)
Unique identifier (int) or username (str) of the target chat.
username (``str`` | ``None``):
Username to set. Pass "" (empty string) or None to remove the username.
Returns:
True on success.
Raises:
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
``ValueError`` if a chat_id belongs to a user or chat.
"""
peer = self.resolve_peer(chat_id)
if isinstance(peer, types.InputPeerChannel):
return bool(
self.send(
functions.channels.UpdateUsername(
channel=peer,
username=username or ""
)
)
)
else:
raise ValueError("The chat_id \"{}\" belongs to a user or chat".format(chat_id))

View File

@ -17,7 +17,6 @@
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
import binascii
import mimetypes
import os
import struct
from typing import Union
@ -122,7 +121,8 @@ class EditMessageMedia(BaseClient):
functions.messages.UploadMedia(
peer=await self.resolve_peer(chat_id),
media=types.InputMediaUploadedDocument(
mime_type=mimetypes.types_map[".mp4"],
mime_type="video/mp4",
thumb=None if media.thumb is None else self.save_file(media.thumb),
file=await self.save_file(media.media),
attributes=[
types.DocumentAttributeVideo(
@ -178,7 +178,8 @@ class EditMessageMedia(BaseClient):
functions.messages.UploadMedia(
peer=await self.resolve_peer(chat_id),
media=types.InputMediaUploadedDocument(
mime_type=mimetypes.types_map.get("." + media.media.split(".")[-1], "audio/mpeg"),
mime_type="audio/mpeg",
thumb=None if media.thumb is None else self.save_file(media.thumb),
file=await self.save_file(media.media),
attributes=[
types.DocumentAttributeAudio(
@ -233,7 +234,8 @@ class EditMessageMedia(BaseClient):
functions.messages.UploadMedia(
peer=await self.resolve_peer(chat_id),
media=types.InputMediaUploadedDocument(
mime_type=mimetypes.types_map[".mp4"],
mime_type="video/mp4",
thumb=None if media.thumb is None else self.save_file(media.thumb),
file=await self.save_file(media.media),
attributes=[
types.DocumentAttributeVideo(
@ -290,7 +292,8 @@ class EditMessageMedia(BaseClient):
functions.messages.UploadMedia(
peer=await self.resolve_peer(chat_id),
media=types.InputMediaUploadedDocument(
mime_type=mimetypes.types_map.get("." + media.media.split(".")[-1], "text/plain"),
mime_type="application/zip",
thumb=None if media.thumb is None else self.save_file(media.thumb),
file=await self.save_file(media.media),
attributes=[
types.DocumentAttributeFilename(os.path.basename(media.media))

View File

@ -17,7 +17,6 @@
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
import binascii
import mimetypes
import os
import struct
from typing import Union
@ -132,7 +131,7 @@ class SendAnimation(BaseClient):
thumb = None if thumb is None else await self.save_file(thumb)
file = await self.save_file(animation, progress=progress, progress_args=progress_args)
media = types.InputMediaUploadedDocument(
mime_type=mimetypes.types_map[".mp4"],
mime_type="video/mp4",
file=file,
thumb=thumb,
attributes=[

View File

@ -17,7 +17,6 @@
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
import binascii
import mimetypes
import os
import struct
from typing import Union
@ -133,7 +132,7 @@ class SendAudio(BaseClient):
thumb = None if thumb is None else await self.save_file(thumb)
file = await self.save_file(audio, progress=progress, progress_args=progress_args)
media = types.InputMediaUploadedDocument(
mime_type=mimetypes.types_map.get("." + audio.split(".")[-1], "audio/mpeg"),
mime_type="audio/mpeg",
file=file,
thumb=thumb,
attributes=[

View File

@ -17,7 +17,6 @@
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
import binascii
import mimetypes
import os
import struct
from typing import Union
@ -119,7 +118,7 @@ class SendDocument(BaseClient):
thumb = None if thumb is None else await self.save_file(thumb)
file = await self.save_file(document, progress=progress, progress_args=progress_args)
media = types.InputMediaUploadedDocument(
mime_type=mimetypes.types_map.get("." + document.split(".")[-1], "text/plain"),
mime_type="application/zip",
file=file,
thumb=thumb,
attributes=[

View File

@ -18,7 +18,6 @@
import binascii
import logging
import mimetypes
import os
import struct
from typing import Union, List
@ -130,7 +129,7 @@ class SendMediaGroup(BaseClient):
media=types.InputMediaUploadedDocument(
file=await self.save_file(i.media),
thumb=None if i.thumb is None else self.save_file(i.thumb),
mime_type=mimetypes.types_map[".mp4"],
mime_type="video/mp4",
attributes=[
types.DocumentAttributeVideo(
supports_streaming=i.supports_streaming or None,

View File

@ -17,7 +17,6 @@
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
import binascii
import mimetypes
import os
import struct
from typing import Union
@ -136,7 +135,7 @@ class SendVideo(BaseClient):
thumb = None if thumb is None else await self.save_file(thumb)
file = await self.save_file(video, progress=progress, progress_args=progress_args)
media = types.InputMediaUploadedDocument(
mime_type=mimetypes.types_map[".mp4"],
mime_type="video/mp4",
file=file,
thumb=thumb,
attributes=[

View File

@ -17,7 +17,6 @@
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
import binascii
import mimetypes
import os
import struct
from typing import Union
@ -116,7 +115,7 @@ class SendVideoNote(BaseClient):
thumb = None if thumb is None else await self.save_file(thumb)
file = await self.save_file(video_note, progress=progress, progress_args=progress_args)
media = types.InputMediaUploadedDocument(
mime_type=mimetypes.types_map[".mp4"],
mime_type="video/mp4",
file=file,
thumb=thumb,
attributes=[

View File

@ -17,7 +17,6 @@
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
import binascii
import mimetypes
import os
import struct
from typing import Union
@ -116,7 +115,7 @@ class SendVoice(BaseClient):
if os.path.exists(voice):
file = await self.save_file(voice, progress=progress, progress_args=progress_args)
media = types.InputMediaUploadedDocument(
mime_type=mimetypes.types_map.get("." + voice.split(".")[-1], "audio/mpeg"),
mime_type="audio/mpeg",
file=file,
attributes=[
types.DocumentAttributeAudio(

View File

@ -21,6 +21,7 @@ from .get_me import GetMe
from .get_user_profile_photos import GetUserProfilePhotos
from .get_users import GetUsers
from .set_user_profile_photo import SetUserProfilePhoto
from .update_username import UpdateUsername
class Users(
@ -28,6 +29,7 @@ class Users(
SetUserProfilePhoto,
DeleteUserProfilePhotos,
GetUsers,
GetMe
GetMe,
UpdateUsername
):
pass

View File

@ -0,0 +1,51 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2019 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 ...ext import BaseClient
class UpdateUsername(BaseClient):
def update_username(self,
username: Union[str, None]) -> bool:
"""Use this method to update your own username.
This method only works for users, not bots. Bot usernames must be changed via Bot Support or by recreating
them from scratch using BotFather. To update a channel or supergroup username you can use
:meth:`update_chat_username`.
Args:
username (``str`` | ``None``):
Username to set. "" (empty string) or None to remove the username.
Returns:
True on success.
Raises:
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
"""
return bool(
self.send(
functions.account.UpdateUsername(
username=username or ""
)
)
)

View File

@ -63,7 +63,7 @@ class InlineKeyboardButton(PyrogramType):
callback_game: CallbackGame = None):
super().__init__(None)
self.text = text
self.text = str(text)
self.url = url
self.callback_data = callback_data
self.switch_inline_query = switch_inline_query

View File

@ -46,7 +46,7 @@ class KeyboardButton(PyrogramType):
request_location: bool = None):
super().__init__(None)
self.text = text
self.text = str(text)
self.request_contact = request_contact
self.request_location = request_location

View File

@ -209,3 +209,14 @@ class Chat(PyrogramType):
parsed_chat.invite_link = full_chat.exported_invite.link
return parsed_chat
@staticmethod
def _parse_chat(client, chat):
# A wrapper around each entity parser: User, Chat and Channel.
# Currently unused, might become useful in future.
if isinstance(chat, types.Chat):
return Chat._parse_chat_chat(client, chat)
elif isinstance(chat, types.User):
return Chat._parse_user_chat(client, chat)
else:
return Chat._parse_channel_chat(client, chat)