2
0
mirror of https://github.com/pyrogram/pyrogram synced 2025-09-02 23:35:17 +00:00

Merge branch 'develop' into asyncio

# Conflicts:
#	pyrogram/client/client.py
#	pyrogram/client/methods/chats/get_chat.py
#	pyrogram/client/methods/messages/get_messages.py
#	pyrogram/client/types/messages_and_media/messages.py
This commit is contained in:
Dan
2018-12-31 12:06:15 +01:00
29 changed files with 390 additions and 146 deletions

View File

@@ -1,5 +1,5 @@
Bad Request
===========
400 - Bad Request
=================
.. module:: pyrogram.api.errors.BadRequest

View File

@@ -1,5 +1,5 @@
Flood
=====
420 - Flood
===========
.. module:: pyrogram.api.errors.Flood

View File

@@ -1,5 +1,5 @@
Forbidden
=========
403 - Forbidden
===============
.. module:: pyrogram.api.errors.Forbidden

View File

@@ -1,5 +1,5 @@
Internal Server Error
=====================
500 - Internal Server Error
===========================
.. module:: pyrogram.api.errors.InternalServerError

View File

@@ -1,5 +1,5 @@
Not Acceptable
==============
406 - Not Acceptable
====================
.. module:: pyrogram.api.errors.NotAcceptable

View File

@@ -1,5 +1,5 @@
See Other
=========
303 - See Other
===============
.. module:: pyrogram.api.errors.SeeOther

View File

@@ -1,5 +1,5 @@
Unauthorized
============
401 - Unauthorized
==================
.. module:: pyrogram.api.errors.Unauthorized

View File

@@ -1,5 +1,5 @@
Unknown Error
=============
520 - Unknown Error
===================
.. module:: pyrogram.api.errors.UnknownError

View File

@@ -93,6 +93,7 @@ To get started, press the Next button.
resources/BotsInteraction
resources/ErrorHandling
resources/TestServers
resources/AdvancedUsage
resources/Changelog
.. toctree::

View File

@@ -19,7 +19,7 @@ Utilities
remove_handler
send
resolve_peer
download_media
save_file
Decorators
----------
@@ -65,6 +65,7 @@ Messages
send_poll
vote_poll
retract_vote
download_media
Chats
-----
@@ -86,6 +87,7 @@ Chats
pin_chat_message
unpin_chat_message
get_chat
get_chat_preview
get_chat_member
get_chat_members
get_chat_members_count

View File

@@ -12,6 +12,7 @@ Users & Chats
User
UserStatus
Chat
ChatPreview
ChatPhoto
ChatMember
ChatMembers
@@ -82,6 +83,9 @@ Input Media
.. autoclass:: Chat
:members:
.. autoclass:: ChatPreview
:members:
.. autoclass:: ChatPhoto
:members:

View File

@@ -0,0 +1,121 @@
Advanced Usage
==============
In this section, you'll be shown the alternative way of communicating with Telegram using Pyrogram: the main Telegram
API with its raw functions and types.
Telegram Raw API
----------------
If you can't find a high-level method for your needs or if you want complete, low-level access to the whole
Telegram API, you have to use the raw :mod:`functions <pyrogram.api.functions>` and :mod:`types <pyrogram.api.types>`
exposed by the ``pyrogram.api`` package and call any Telegram API method you wish using the
:meth:`send() <pyrogram.Client.send>` method provided by the Client class.
.. hint::
Every available high-level method mentioned in the previous page is built on top of these raw functions.
Nothing stops you from using the raw functions only, but they are rather complex and `plenty of them`_ are already
re-implemented by providing a much simpler and cleaner interface which is very similar to the Bot API.
If you think a raw function should be wrapped and added as a high-level method, feel free to ask in our Community_!
Caveats
-------
As hinted before, raw functions and types can be confusing, mainly because people don't realize they must accept
*exactly* the right values, but also because most of them don't have enough Python experience to fully grasp how things
work.
This section will therefore explain some pitfalls to take into consideration when working with the raw API.
Chat IDs
^^^^^^^^
The way Telegram works makes it impossible to directly send a message to a user or a chat by using their IDs only.
Instead, a pair of ``id`` and ``access_hash`` wrapped in a so called ``InputPeer`` is always needed.
There are three different InputPeer types, one for each kind of Telegram entity.
Whenever an InputPeer is needed you must pass one of these:
- `InputPeerUser <https://docs.pyrogram.ml/types/InputPeerUser>`_ - Users
- `InputPeerChat <https://docs.pyrogram.ml/types/InputPeerChat>`_ - Basic Chats
- `InputPeerChannel <https://docs.pyrogram.ml/types/InputPeerChannel>`_ - Either Channels or Supergroups
But you don't necessarily have to manually instantiate each object because, luckily for you, Pyrogram already provides
:meth:`resolve_peer() <pyrogram.Client.resolve_peer>` as a convenience utility method that returns the correct InputPeer
by accepting a peer ID only.
Another thing to take into consideration about chat IDs is the way they are represented: they are all integers and
all positive within their respective raw types.
Things are different when working with Pyrogram's API because having them in the same space can theoretically lead to
collisions, and that's why Pyrogram (as well as the official Bot API) uses a slightly different representation for each
kind of ID.
For example, given the ID *123456789*, here's how Pyrogram can tell entities apart:
- ``+ID`` - User: *123456789*
- ``-ID`` - Chat: *-123456789*
- ``-100ID`` - Channel (and Supergroup): *-100123456789*
So, every time you take a raw ID, make sure to translate it into the correct ID when you want to use it with an
high-level method.
Examples
--------
- Update first name, last name and bio:
.. code-block:: python
from pyrogram import Client
from pyrogram.api import functions
with Client("my_account") as app:
app.send(
functions.account.UpdateProfile(
first_name="Dan", last_name="Tès",
about="Bio written from Pyrogram"
)
)
- Share your Last Seen time only with your contacts:
.. code-block:: python
from pyrogram import Client
from pyrogram.api import functions, types
with Client("my_account") as app:
app.send(
functions.account.SetPrivacy(
key=types.InputPrivacyKeyStatusTimestamp(),
rules=[types.InputPrivacyValueAllowContacts()]
)
)
- Invite users to your channel/supergroup:
.. code-block:: python
from pyrogram import Client
from pyrogram.api import functions, types
with Client("my_account") as app:
app.send(
functions.channels.InviteToChannel(
channel=app.resolve_peer(123456789), # ID or Username
users=[ # The users you want to invite
app.resolve_peer(23456789), # By ID
app.resolve_peer("username"), # By username
app.resolve_peer("393281234567"), # By phone number
]
)
)
.. _plenty of them: ../pyrogram/Client.html#messages
.. _Raw Functions: Usage.html#using-raw-functions
.. _Community: https://t.me/PyrogramChat

View File

@@ -6,13 +6,13 @@ Errors are inevitable when working with the API, and they must be correctly hand
There are many errors that Telegram could return, but they all fall in one of these categories
(which are in turn children of the :obj:`pyrogram.Error` superclass)
- :obj:`303 See Other <pyrogram.api.errors.SeeOther>`
- :obj:`400 Bad Request <pyrogram.api.errors.BadRequest>`
- :obj:`401 Unauthorized <pyrogram.api.errors.Unauthorized>`
- :obj:`403 Forbidden <pyrogram.api.errors.Forbidden>`
- :obj:`406 Not Acceptable <pyrogram.api.errors.NotAcceptable>`
- :obj:`420 Flood <pyrogram.api.errors.Flood>`
- :obj:`500 Internal Server Error <pyrogram.api.errors.InternalServerError>`
- :obj:`303 - See Other <pyrogram.api.errors.SeeOther>`
- :obj:`400 - Bad Request <pyrogram.api.errors.BadRequest>`
- :obj:`401 - Unauthorized <pyrogram.api.errors.Unauthorized>`
- :obj:`403 - Forbidden <pyrogram.api.errors.Forbidden>`
- :obj:`406 - Not Acceptable <pyrogram.api.errors.NotAcceptable>`
- :obj:`420 - Flood <pyrogram.api.errors.Flood>`
- :obj:`500 - Internal Server Error <pyrogram.api.errors.InternalServerError>`
As stated above, there are really many (too many) errors, and in case Pyrogram does not know anything yet about a
specific one, it raises a special :obj:`520 Unknown Error <pyrogram.api.errors.UnknownError>` exception and logs it

View File

@@ -1,8 +1,7 @@
Usage
=====
Having your `project set up`_ and your account authorized_, it's time to play with the API.
In this section, you'll be shown two ways of communicating with Telegram using Pyrogram. Let's start!
Having your `project set up`_ and your account authorized_, it's time to play with the API. Let's start!
High-level API
--------------
@@ -43,79 +42,8 @@ exceptions in your code:
More examples on `GitHub <https://github.com/pyrogram/pyrogram/tree/develop/examples>`_.
Raw Functions
-------------
If you can't find a high-level method for your needs or if you want complete, low-level access to the whole Telegram API,
you have to use the raw :mod:`functions <pyrogram.api.functions>` and :mod:`types <pyrogram.api.types>` exposed by the
``pyrogram.api`` package and call any Telegram API method you wish using the :meth:`send() <pyrogram.Client.send>`
method provided by the Client class.
.. hint::
Every high-level method mentioned in the section above is built on top of these raw functions.
Nothing stops you from using the raw functions only, but they are rather complex and `plenty of them`_ are already
re-implemented by providing a much simpler and cleaner interface which is very similar to the Bot API.
If you think a raw function should be wrapped and added as a high-level method, feel free to ask in our Community_!
Examples (more on `GitHub <https://github.com/pyrogram/pyrogram/tree/develop/examples>`_):
- Update first name, last name and bio:
.. code-block:: python
from pyrogram import Client
from pyrogram.api import functions
with Client("my_account") as app:
app.send(
functions.account.UpdateProfile(
first_name="Dan", last_name="Tès",
about="Bio written from Pyrogram"
)
)
- Share your Last Seen time only with your contacts:
.. code-block:: python
from pyrogram import Client
from pyrogram.api import functions, types
with Client("my_account") as app:
app.send(
functions.account.SetPrivacy(
key=types.InputPrivacyKeyStatusTimestamp(),
rules=[types.InputPrivacyValueAllowContacts()]
)
)
- Invite users to your channel/supergroup:
.. code-block:: python
from pyrogram import Client
from pyrogram.api import functions, types
with Client("my_account") as app:
app.send(
functions.channels.InviteToChannel(
channel=app.resolve_peer(123456789), # ID or Username
users=[ # The users you want to invite
app.resolve_peer(23456789), # By ID
app.resolve_peer("username"), # By username
app.resolve_peer("393281234567"), # By phone number
]
)
)
.. _methods: ../pyrogram/Client.html#messages
.. _plenty of them: ../pyrogram/Client.html#messages
.. _types: ../pyrogram/Types.html
.. _Raw Functions: Usage.html#using-raw-functions
.. _Community: https://t.me/PyrogramChat
.. _project set up: Setup.html
.. _authorized: Setup.html#user-authorization
.. _Telegram Bot API: https://core.telegram.org/bots/api
.. _Telegram Bot API: https://core.telegram.org/bots/api
.. _methods: ../pyrogram/Client.html#messages
.. _types: ../pyrogram/Types.html

View File

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

View File

@@ -919,9 +919,9 @@ class Client(Methods, BaseClient):
log.info("UpdatesWorkerTask stopped")
async def send(self,
data: Object,
retries: int = Session.MAX_RETRIES,
timeout: float = Session.WAIT_TIMEOUT):
data: Object,
retries: int = Session.MAX_RETRIES,
timeout: float = Session.WAIT_TIMEOUT):
"""Use this method to send Raw Function queries.
This method makes possible to manually call every single Telegram API method in a low-level manner.
@@ -1081,7 +1081,7 @@ class Client(Methods, BaseClient):
)
async def get_initial_dialogs_chunk(self,
offset_date: int = 0):
offset_date: int = 0):
while True:
try:
r = await self.send(
@@ -1114,12 +1114,12 @@ class Client(Methods, BaseClient):
await self.get_initial_dialogs_chunk()
async def resolve_peer(self,
peer_id: Union[int, str]):
peer_id: Union[int, str]):
"""Use this method to get the InputPeer of a known peer_id.
This is a utility method intended to be used only when working with Raw Functions (i.e: a Telegram API method
you wish to use which is not available yet in the Client class as an easy-to-use method), whenever an InputPeer
type is required.
This is a utility method intended to be used **only** when working with Raw Functions (i.e: a Telegram API
method you wish to use which is not available yet in the Client class as an easy-to-use method), whenever an
InputPeer type is required.
Args:
peer_id (``int`` | ``str``):
@@ -1147,8 +1147,8 @@ class Client(Methods, BaseClient):
except ValueError:
if peer_id not in self.peers_by_username:
await self.send(functions.contacts.ResolveUsername(username=peer_id
)
)
)
)
return self.peers_by_username[peer_id]
else:
@@ -1190,6 +1190,52 @@ class Client(Methods, BaseClient):
file_part: int = 0,
progress: callable = None,
progress_args: tuple = ()):
"""Use this method to upload a file onto Telegram servers, without actually sending the message to anyone.
This is a utility method intended to be used **only** when working with Raw Functions (i.e: a Telegram API
method you wish to use which is not available yet in the Client class as an easy-to-use method), whenever an
InputFile type is required.
Args:
path (``str``):
The path of the file you want to upload that exists on your local machine.
file_id (``int``, *optional*):
In case a file part expired, pass the file_id and the file_part to retry uploading that specific chunk.
file_part (``int``, *optional*):
In case a file part expired, pass the file_id and the file_part to retry uploading that specific chunk.
progress (``callable``, *optional*):
Pass a callback function to view the upload progress.
The function must take *(client, current, total, \*args)* as positional arguments (look at the section
below for a detailed description).
progress_args (``tuple``, *optional*):
Extra custom arguments for the progress callback function. Useful, for example, if you want to pass
a chat_id and a message_id in order to edit a message with the updated progress.
Other Parameters:
client (:obj:`Client <pyrogram.Client>`):
The Client itself, useful when you want to call other API methods inside the callback function.
current (``int``):
The amount of bytes uploaded so far.
total (``int``):
The size of the file.
*args (``tuple``, *optional*):
Extra custom arguments as defined in the *progress_args* parameter.
You can either keep *\*args* or add every single extra argument in your function signature.
Returns:
On success, the uploaded file is returned in form of an InputFile object.
Raises:
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
"""
async def worker(session):
while True:
data = await queue.get()

View File

@@ -23,7 +23,6 @@ from .decorators import Decorators
from .messages import Messages
from .password import Password
from .users import Users
from .utilities import Utilities
class Methods(
@@ -32,7 +31,6 @@ class Methods(
Password,
Chats,
Users,
Utilities,
Messages,
Decorators
):

View File

@@ -22,6 +22,7 @@ from .get_chat import GetChat
from .get_chat_member import GetChatMember
from .get_chat_members import GetChatMembers
from .get_chat_members_count import GetChatMembersCount
from .get_chat_preview import GetChatPreview
from .get_dialogs import GetDialogs
from .join_chat import JoinChat
from .kick_chat_member import KickChatMember
@@ -54,6 +55,7 @@ class Chats(
PinChatMessage,
UnpinChatMessage,
GetDialogs,
GetChatMembersCount
GetChatMembersCount,
GetChatPreview
):
pass

View File

@@ -32,13 +32,36 @@ class GetChat(BaseClient):
Args:
chat_id (``int`` | ``str``):
Unique identifier (int) or username (str) of the target chat.
Unique identifier for the target chat in form of a *t.me/joinchat/* link, identifier (int) 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.
``ValueError`` in case the chat invite link refers to a chat you haven't joined yet.
"""
match = self.INVITE_LINK_RE.match(str(chat_id))
if match:
h = match.group(1)
r = await self.send(
functions.messages.CheckChatInvite(
hash=h
)
)
if isinstance(r, types.ChatInvite):
raise ValueError("You haven't joined \"t.me/joinchat/{}\" yet".format(h))
if isinstance(r.chat, types.Chat):
chat_id = -r.chat.id
if isinstance(r.chat, types.Channel):
chat_id = int("-100" + str(r.chat.id))
peer = await self.resolve_peer(chat_id)
if isinstance(peer, types.InputPeerChannel):

View File

@@ -0,0 +1,63 @@
# 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/>.
import pyrogram
from pyrogram.api import functions, types
from ...ext import BaseClient
class GetChatPreview(BaseClient):
def get_chat_preview(self,
invite_link: str):
"""Use this method to get the preview of a chat using the invite link.
This method only returns a chat preview, if you want to join a chat use :meth:`join_chat`
Args:
invite_link (``str``):
Unique identifier for the target chat in form of *t.me/joinchat/* links.
Returns:
Either :obj:`Chat` or :obj:`ChatPreview`, depending on whether you already joined the chat or not.
Raises:
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
``ValueError`` in case of an invalid invite_link.
"""
match = self.INVITE_LINK_RE.match(invite_link)
if match:
r = self.send(
functions.messages.CheckChatInvite(
hash=match.group(1)
)
)
if isinstance(r, types.ChatInvite):
return pyrogram.ChatPreview._parse(self, r)
if isinstance(r, types.ChatInviteAlready):
chat = r.chat
if isinstance(chat, types.Chat):
return pyrogram.Chat._parse_chat_chat(self, chat)
if isinstance(chat, types.Channel):
return pyrogram.Chat._parse_channel_chat(self, chat)
else:
raise ValueError("The invite_link is invalid")

View File

@@ -27,7 +27,7 @@ class JoinChat(BaseClient):
Args:
chat_id (``str``):
Unique identifier for the target chat in form of *t.me/joinchat/* links or username of the target
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).
Raises:

View File

@@ -17,6 +17,7 @@
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
from .delete_messages import DeleteMessages
from .download_media import DownloadMedia
from .edit_message_caption import EditMessageCaption
from .edit_message_media import EditMessageMedia
from .edit_message_reply_markup import EditMessageReplyMarkup
@@ -68,6 +69,7 @@ class Messages(
SendVoice,
SendPoll,
VotePoll,
RetractVote
RetractVote,
DownloadMedia
):
pass

View File

@@ -20,7 +20,7 @@ import asyncio
from typing import Union
import pyrogram
from ...ext import BaseClient
from pyrogram.client.ext import BaseClient
class DownloadMedia(BaseClient):

View File

@@ -78,6 +78,6 @@ class GetMessages(BaseClient):
else:
rpc = functions.messages.GetMessages(id=ids)
messages = await pyrogram.Messages._parse(self, await self.send(rpc))
messages = await pyrogram.Messages._parse(self, await self.send(rpc), replies)
return messages if is_iterable else messages.messages[0]

View File

@@ -1,25 +0,0 @@
# 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 .download_media import DownloadMedia
class Utilities(
DownloadMedia
):
pass

View File

@@ -35,5 +35,5 @@ from .messages_and_media import (
)
from .user_and_chats import (
Chat, ChatMember, ChatMembers, ChatPhoto,
Dialog, Dialogs, User, UserStatus
Dialog, Dialogs, User, UserStatus, ChatPreview
)

View File

@@ -47,7 +47,7 @@ class Messages(PyrogramType):
self.messages = messages
@staticmethod
async def _parse(client, messages: types.messages.Messages) -> "Messages":
async def _parse(client, messages: types.messages.Messages, replies: int = 1) -> "Messages":
users = {i.id: i for i in messages.users}
chats = {i.id: i for i in messages.chats}
@@ -55,7 +55,7 @@ class Messages(PyrogramType):
parsed_messages = []
for message in messages.messages:
parsed_messages.append(await Message._parse(client, message, users, chats))
parsed_messages.append(await Message._parse(client, message, users, chats, replies))
return Messages(
total_count=getattr(messages, "count", len(messages.messages)),

View File

@@ -20,6 +20,7 @@ from .chat import Chat
from .chat_member import ChatMember
from .chat_members import ChatMembers
from .chat_photo import ChatPhoto
from .chat_preview import ChatPreview
from .dialog import Dialog
from .dialogs import Dialogs
from .user import User

View File

@@ -0,0 +1,78 @@
# 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 List
import pyrogram
from pyrogram.api import types
from .chat_photo import ChatPhoto
from ..pyrogram_type import PyrogramType
from ..user_and_chats.user import User
class ChatPreview(PyrogramType):
"""This object represents a chat preview.
Args:
title (``str``):
Title of the chat.
photo (:obj:`ChatPhoto`):
Chat photo. Suitable for downloads only.
type (``str``):
Type of chat, can be either, "group", "supergroup" or "channel".
members_count (``int``):
Chat members count.
members (List of :obj:`User`, *optional*):
Preview of some of the chat members.
"""
def __init__(self,
*,
client: "pyrogram.client.ext.BaseClient",
title: str,
photo: ChatPhoto,
type: str,
members_count: int,
members: List[User] = None):
super().__init__(client)
self.title = title
self.photo = photo
self.type = type
self.members_count = members_count
self.members = members
@staticmethod
def _parse(client, chat_invite: types.ChatInvite) -> "ChatPreview":
return ChatPreview(
title=chat_invite.title,
photo=ChatPhoto._parse(client, chat_invite.photo),
type=("group" if not chat_invite.channel else
"channel" if chat_invite.broadcast else
"supergroup"),
members_count=chat_invite.participants_count,
members=[User._parse(client, user) for user in chat_invite.participants] or None,
client=client
)
# TODO: Maybe just merge this object into Chat itself by adding the "members" field.
# get_chat can be used as well instead of get_chat_preview