Merge develop -> asyncio

This commit is contained in:
Dan 2019-06-20 04:17:24 +02:00
commit a8dfe61f7e
93 changed files with 2162 additions and 1084 deletions

View File

@ -4,7 +4,7 @@ about: Create a bug report affecting the library
labels: "bug"
---
<!-- WARNING: Ignoring this template will lead to the issue being closed as incomplete -->
<!-- WARNING: Ignoring this template could lead to the issue being closed as incomplete -->
## Checklist
- [ ] I am sure the error is coming from Pyrogram's code and not elsewhere.
@ -15,7 +15,7 @@ labels: "bug"
A clear and concise description of the problem.
## Steps to Reproduce
[A minimal, complete and verifiable example](https://stackoverflow.com/help/mcve).
[A minimal, reproducible example](https://stackoverflow.com/help/minimal-reproducible-example).
## Traceback
The full traceback (if applicable).

View File

@ -4,7 +4,7 @@ about: Suggest ideas, new features or enhancements
labels: "enhancement"
---
<!-- WARNING: Ignoring this template will lead to the issue being closed as incomplete -->
<!-- WARNING: Ignoring this template could lead to the issue being closed as incomplete -->
## Checklist
- [ ] I believe the idea is awesome and would benefit the library.

View File

@ -5,11 +5,11 @@ title: For Q&A purposes, please read this template body
labels: "question"
---
<!-- WARNING: Ignoring this template will lead to the issue being closed as incomplete -->
<!-- WARNING: Ignoring this template could lead to the issue being closed as incomplete -->
# Important
This place is for issues about Pyrogram, it's **not a forum**.
If you'd like to post a question, please move to https://stackoverflow.com or join the Telegram community by following the description in https://t.me/pyrogram.
If you'd like to post a question, please move to https://stackoverflow.com or join the Telegram community at https://t.me/pyrogram. Useful information on how to ask good questions can be found here: https://stackoverflow.com/help/how-to-ask.
Thanks.
Thanks.

View File

@ -1,7 +1,7 @@
## Include
include README.md COPYING COPYING.lesser NOTICE requirements.txt
recursive-include compiler *.py *.tl *.tsv *.txt
recursive-include pyrogram mime.types
recursive-include pyrogram mime.types schema.sql
## Exclude
prune pyrogram/api/errors/exceptions

View File

@ -99,4 +99,10 @@ RESULT_ID_DUPLICATE The result contains items with duplicated identifiers
ACCESS_TOKEN_INVALID The bot access token is invalid
INVITE_HASH_EXPIRED The chat invite link is no longer valid
USER_BANNED_IN_CHANNEL You are limited, check @SpamBot for details
MESSAGE_EDIT_TIME_EXPIRED You can no longer edit this message
MESSAGE_EDIT_TIME_EXPIRED You can no longer edit this message
FOLDER_ID_INVALID The folder id is invalid
MEGAGROUP_PREHISTORY_HIDDEN The action failed because the supergroup has the pre-history hidden
CHAT_LINK_EXISTS The action failed because the supergroup is linked to a channel
LINK_NOT_MODIFIED The chat link was not modified because you tried to link to the same target
BROADCAST_ID_INVALID The channel is invalid
MEGAGROUP_ID_INVALID The supergroup is invalid
1 id message
99 ACCESS_TOKEN_INVALID The bot access token is invalid
100 INVITE_HASH_EXPIRED The chat invite link is no longer valid
101 USER_BANNED_IN_CHANNEL You are limited, check @SpamBot for details
102 MESSAGE_EDIT_TIME_EXPIRED You can no longer edit this message
103 FOLDER_ID_INVALID The folder id is invalid
104 MEGAGROUP_PREHISTORY_HIDDEN The action failed because the supergroup has the pre-history hidden
105 CHAT_LINK_EXISTS The action failed because the supergroup is linked to a channel
106 LINK_NOT_MODIFIED The chat link was not modified because you tried to link to the same target
107 BROADCAST_ID_INVALID The channel is invalid
108 MEGAGROUP_ID_INVALID The supergroup is invalid

View File

@ -7,5 +7,6 @@ HISTORY_GET_FAILED Telegram is having internal problems. Please try again later
REG_ID_GENERATE_FAILED Telegram is having internal problems. Please try again later
RANDOM_ID_DUPLICATE Telegram is having internal problems. Please try again later
WORKER_BUSY_TOO_LONG_RETRY Telegram is having internal problems. Please try again later
INTERDC_X_CALL_ERROR Telegram is having internal problems. Please try again later
INTERDC_X_CALL_RICH_ERROR Telegram is having internal problems. Please try again later
INTERDC_X_CALL_ERROR Telegram is having internal problems at DC{x}. Please try again later
INTERDC_X_CALL_RICH_ERROR Telegram is having internal problems at DC{x}. Please try again later
FOLDER_DEAC_AUTOFIX_ALL Telegram is having internal problems. Please try again later
1 id message
7 REG_ID_GENERATE_FAILED Telegram is having internal problems. Please try again later
8 RANDOM_ID_DUPLICATE Telegram is having internal problems. Please try again later
9 WORKER_BUSY_TOO_LONG_RETRY Telegram is having internal problems. Please try again later
10 INTERDC_X_CALL_ERROR Telegram is having internal problems. Please try again later Telegram is having internal problems at DC{x}. Please try again later
11 INTERDC_X_CALL_RICH_ERROR Telegram is having internal problems. Please try again later Telegram is having internal problems at DC{x}. Please try again later
12 FOLDER_DEAC_AUTOFIX_ALL Telegram is having internal problems. Please try again later

View File

@ -35,8 +35,7 @@ backwards-incompatible changes made in that version.
When upgrading to a new version of Pyrogram, you will need to check all the breaking changes in order to find
incompatible code in your application, but also to take advantage of new features and improvements.
Releases
--------
**Contents**
""".lstrip("\n")

View File

@ -1,3 +1,8 @@
User-agent: *
Allow: /
Disallow: /dev/*
Disallow: /old/*
Sitemap: https://docs.pyrogram.org/sitemap.xml

View File

@ -34,13 +34,13 @@ Message
- :meth:`~Message.click`
- :meth:`~Message.delete`
- :meth:`~Message.download`
- :meth:`~Message.edit`
- :meth:`~Message.forward`
- :meth:`~Message.pin`
- :meth:`~Message.edit_text`
- :meth:`~Message.edit_caption`
- :meth:`~Message.edit_media`
- :meth:`~Message.edit_reply_markup`
- :meth:`~Message.forward`
- :meth:`~Message.pin`
- :meth:`~Message.reply`
- :meth:`~Message.reply_text`
- :meth:`~Message.reply_animation`
- :meth:`~Message.reply_audio`
- :meth:`~Message.reply_cached_media`
@ -59,13 +59,35 @@ Message
- :meth:`~Message.reply_video_note`
- :meth:`~Message.reply_voice`
CallbackQuery
^^^^^^^^^^^^^
Chat
^^^^
.. hlist::
:columns: 2
- :meth:`~Chat.archive`
- :meth:`~Chat.unarchive`
User
^^^^
.. hlist::
:columns: 2
- :meth:`~User.archive`
- :meth:`~User.unarchive`
CallbackQuery
^^^^^^^^^^^^^
.. hlist::
:columns: 4
- :meth:`~CallbackQuery.answer`
- :meth:`~CallbackQuery.edit_text`
- :meth:`~CallbackQuery.edit_caption`
- :meth:`~CallbackQuery.edit_media`
- :meth:`~CallbackQuery.edit_reply_markup`
InlineQuery
^^^^^^^^^^^
@ -84,13 +106,13 @@ Details
.. automethod:: Message.click()
.. automethod:: Message.delete()
.. automethod:: Message.download()
.. automethod:: Message.edit()
.. automethod:: Message.forward()
.. automethod:: Message.pin()
.. automethod:: Message.edit_text()
.. automethod:: Message.edit_caption()
.. automethod:: Message.edit_media()
.. automethod:: Message.edit_reply_markup()
.. automethod:: Message.forward()
.. automethod:: Message.pin()
.. automethod:: Message.reply()
.. automethod:: Message.reply_text()
.. automethod:: Message.reply_animation()
.. automethod:: Message.reply_audio()
.. automethod:: Message.reply_cached_media()
@ -109,8 +131,20 @@ Details
.. automethod:: Message.reply_video_note()
.. automethod:: Message.reply_voice()
.. Chat
.. automethod:: Chat.archive()
.. automethod:: Chat.unarchive()
.. User
.. automethod:: User.archive()
.. automethod:: User.unarchive()
.. CallbackQuery
.. automethod:: CallbackQuery.answer()
.. automethod:: CallbackQuery.edit_text()
.. automethod:: CallbackQuery.edit_caption()
.. automethod:: CallbackQuery.edit_media()
.. automethod:: CallbackQuery.edit_reply_markup()
.. InlineQuery
.. automethod:: InlineQuery.answer()

View File

@ -13,4 +13,7 @@ This is the Client class. It exposes high-level methods for an easy access to th
with app:
app.send_message("me", "Hi!")
Details
-------
.. autoclass:: pyrogram.Client()

View File

@ -1,5 +1,8 @@
Update Filters
==============
Details
-------
.. autoclass:: pyrogram.Filters
:members:

View File

@ -31,10 +31,8 @@ Utilities
- :meth:`~Client.run`
- :meth:`~Client.add_handler`
- :meth:`~Client.remove_handler`
- :meth:`~Client.send`
- :meth:`~Client.resolve_peer`
- :meth:`~Client.save_file`
- :meth:`~Client.stop_transmission`
- :meth:`~Client.export_session_string`
Messages
^^^^^^^^
@ -58,11 +56,15 @@ Messages
- :meth:`~Client.send_venue`
- :meth:`~Client.send_contact`
- :meth:`~Client.send_cached_media`
- :meth:`~Client.send_chat_action`
- :meth:`~Client.edit_message_text`
- :meth:`~Client.edit_message_caption`
- :meth:`~Client.edit_message_reply_markup`
- :meth:`~Client.edit_message_media`
- :meth:`~Client.edit_message_reply_markup`
- :meth:`~Client.edit_inline_text`
- :meth:`~Client.edit_inline_caption`
- :meth:`~Client.edit_inline_media`
- :meth:`~Client.edit_inline_reply_markup`
- :meth:`~Client.send_chat_action`
- :meth:`~Client.delete_messages`
- :meth:`~Client.get_messages`
- :meth:`~Client.get_history`
@ -104,6 +106,8 @@ Chats
- :meth:`~Client.get_dialogs_count`
- :meth:`~Client.restrict_chat`
- :meth:`~Client.update_chat_username`
- :meth:`~Client.archive_chats`
- :meth:`~Client.unarchive_chats`
Users
^^^^^
@ -157,6 +161,18 @@ Bots
- :meth:`~Client.set_game_score`
- :meth:`~Client.get_game_high_scores`
Advanced Usage (Raw API)
^^^^^^^^^^^^^^^^^^^^^^^^
Learn more about these methods at :doc:`Advanced Usage <../topics/advanced-usage>`.
.. hlist::
:columns: 4
- :meth:`~Client.send`
- :meth:`~Client.resolve_peer`
- :meth:`~Client.save_file`
-----
Details
@ -170,10 +186,8 @@ Details
.. automethod:: Client.run()
.. automethod:: Client.add_handler()
.. automethod:: Client.remove_handler()
.. automethod:: Client.send()
.. automethod:: Client.resolve_peer()
.. automethod:: Client.save_file()
.. automethod:: Client.stop_transmission()
.. automethod:: Client.export_session_string()
.. Messages
.. automethod:: Client.send_message()
@ -195,8 +209,12 @@ Details
.. automethod:: Client.send_chat_action()
.. automethod:: Client.edit_message_text()
.. automethod:: Client.edit_message_caption()
.. automethod:: Client.edit_message_reply_markup()
.. automethod:: Client.edit_message_media()
.. automethod:: Client.edit_message_reply_markup()
.. automethod:: Client.edit_inline_text()
.. automethod:: Client.edit_inline_caption()
.. automethod:: Client.edit_inline_media()
.. automethod:: Client.edit_inline_reply_markup()
.. automethod:: Client.delete_messages()
.. automethod:: Client.get_messages()
.. automethod:: Client.get_history()
@ -233,6 +251,8 @@ Details
.. automethod:: Client.get_dialogs_count()
.. automethod:: Client.restrict_chat()
.. automethod:: Client.update_chat_username()
.. automethod:: Client.archive_chats()
.. automethod:: Client.unarchive_chats()
.. Users
.. automethod:: Client.get_me()
@ -265,3 +285,8 @@ Details
.. automethod:: Client.send_game()
.. automethod:: Client.set_game_score()
.. automethod:: Client.get_game_high_scores()
.. Advanced Usage
.. automethod:: Client.send()
.. automethod:: Client.resolve_peer()
.. automethod:: Client.save_file()

View File

@ -30,10 +30,8 @@ Users & Chats
- :class:`ChatPreview`
- :class:`ChatPhoto`
- :class:`ChatMember`
- :class:`ChatMembers`
- :class:`ChatPermissions`
- :class:`Dialog`
- :class:`Dialogs`
Messages & Media
^^^^^^^^^^^^^^^^
@ -42,10 +40,8 @@ Messages & Media
:columns: 5
- :class:`Message`
- :class:`Messages`
- :class:`MessageEntity`
- :class:`Photo`
- :class:`ProfilePhotos`
- :class:`Thumbnail`
- :class:`Audio`
- :class:`Document`
@ -61,8 +57,8 @@ Messages & Media
- :class:`Poll`
- :class:`PollOption`
Keyboards
^^^^^^^^^
Bots & Keyboards
^^^^^^^^^^^^^^^^
.. hlist::
:columns: 4
@ -75,7 +71,6 @@ Keyboards
- :class:`ForceReply`
- :class:`CallbackQuery`
- :class:`GameHighScore`
- :class:`GameHighScores`
- :class:`CallbackGame`
Input Media
@ -123,17 +118,13 @@ Details
.. autoclass:: ChatPreview()
.. autoclass:: ChatPhoto()
.. autoclass:: ChatMember()
.. autoclass:: ChatMembers()
.. autoclass:: ChatPermissions()
.. autoclass:: Dialog()
.. autoclass:: Dialogs()
.. Messages & Media
.. autoclass:: Message()
.. autoclass:: Messages()
.. autoclass:: MessageEntity()
.. autoclass:: Photo()
.. autoclass:: ProfilePhotos()
.. autoclass:: Thumbnail()
.. autoclass:: Audio()
.. autoclass:: Document()
@ -149,7 +140,7 @@ Details
.. autoclass:: Poll()
.. autoclass:: PollOption()
.. Keyboards
.. Bots & Keyboards
.. autoclass:: ReplyKeyboardMarkup()
.. autoclass:: KeyboardButton()
.. autoclass:: ReplyKeyboardRemove()
@ -158,7 +149,6 @@ Details
.. autoclass:: ForceReply()
.. autoclass:: CallbackQuery()
.. autoclass:: GameHighScore()
.. autoclass:: GameHighScores()
.. autoclass:: CallbackGame()
.. Input Media

View File

@ -53,6 +53,22 @@ Why Pyrogram?
.. _TgCrypto: https://github.com/pyrogram/tgcrypto
How stable and reliable is Pyrogram?
------------------------------------
So far, since its first public release, Pyrogram has always shown itself to be quite reliable in handling client-server
interconnections and just as stable when keeping long running applications online. The only annoying issues faced are
actually coming from Telegram servers internal errors and down times, from which Pyrogram is able to recover itself
automatically.
To challenge the framework, the creator is constantly keeping a public
`welcome bot <https://github.com/pyrogram/pyrogram/blob/develop/examples/welcomebot.py>`_ online 24/7 on his own,
relatively-busy account for well over a year now.
In addition to that, about six months ago, one of the most popular Telegram bot has been rewritten
:doc:`using Pyrogram <powered-by>` and is serving more than 200,000 Monthly Active Users since
then, uninterruptedly and without any need for restarting it.
What can MTProto do more than the Bot API?
------------------------------------------
@ -134,20 +150,60 @@ in a bunch of seconds:
import logging
logging.basicConfig(level=logging.INFO)
Another way to confirm you aren't able to connect to Telegram is by pinging these IP addresses and see whether ping
fails or not:
Another way to confirm you aren't able to connect to Telegram is by pinging the IP addresses below and see whether ping
fails or not.
- DC1: ``149.154.175.50``
- DC2: ``149.154.167.51``
- DC3: ``149.154.175.100``
- DC4: ``149.154.167.91``
- DC5: ``91.108.56.149``
What are the IP addresses of Telegram Data Centers?
---------------------------------------------------
The Telegram cloud is currently composed by a decentralized, multi-DC infrastructure (each of which can work
independently) spread in 5 different locations. However, some of the less busy DCs have been lately dismissed and their
IP addresses are now kept as aliases.
.. csv-table:: Production Environment
:header: ID, Location, IPv4, IPv6
:widths: auto
:align: center
DC1, "MIA, Miami FL, USA", ``149.154.175.50``, ``2001:b28:f23d:f001::a``
DC2, "AMS, Amsterdam, NL", ``149.154.167.51``, ``2001:67c:4e8:f002::a``
DC3*, "MIA, Miami FL, USA", ``149.154.175.100``, ``2001:b28:f23d:f003::a``
DC4, "AMS, Amsterdam, NL", ``149.154.167.91``, ``2001:67c:4e8:f004::a``
DC5, "SIN, Singapore, SG", ``91.108.56.149``, ``2001:b28:f23f:f005::a``
.. csv-table:: Test Environment
:header: ID, Location, IPv4, IPv6
:widths: auto
:align: center
DC1, "MIA, Miami FL, USA", ``149.154.175.10``, ``2001:b28:f23d:f001::e``
DC2, "AMS, Amsterdam, NL", ``149.154.167.40``, ``2001:67c:4e8:f002::e``
DC3*, "MIA, Miami FL, USA", ``149.154.175.117``, ``2001:b28:f23d:f003::e``
***** Alias DC
More info about the Test Environment can be found :doc:`here <topics/test-servers>`.
I want to migrate my account from DCX to DCY.
---------------------------------------------
This question is often asked by people who find their account(s) always being connected to DC1 - USA (for example), but
are connecting from a place far away (e.g DC4 - Europe), thus resulting in slower interactions when using the API
because of the great physical distance between the user and its associated DC.
When registering an account for the first time, is up to Telegram to decide which DC the new user is going to be created
in, based on the phone number origin.
Even though Telegram `documentations <https://core.telegram.org/api/datacenter#user-migration>`_ state the server might
decide to automatically migrate a user in case of prolonged usages from a distant, unusual location and albeit this
mechanism is also `confirmed <https://twitter.com/telegram/status/427131446655197184>`_ to exist by Telegram itself,
it's currently not possible to have your account migrated, in any way, simply because the feature was once planned but
not yet implemented.
I keep getting PEER_ID_INVALID error!
-------------------------------------------
-------------------------------------
The error in question is ``[400 PEER_ID_INVALID]``, and could mean several
things:
The error in question is ``[400 PEER_ID_INVALID]``, and could mean several things:
- The chat id you tried to use is simply wrong, double check it.
- The chat id refers to a group or channel you are not a member of.

View File

@ -58,7 +58,7 @@ Terms
Pyrogram --- to automate some behaviours, like sending messages or reacting to text commands or any other event.
Session
Also known as *login session*, is a strictly personal piece of information created and held by both parties
Also known as *login session*, is a strictly personal piece of data created and held by both parties
(client and server) which is used to grant permission into a single account without having to start a new
authorization process from scratch.

View File

@ -130,6 +130,7 @@ Meta
topics/auto-auth
topics/session-settings
topics/tgcrypto
topics/storage-engines
topics/text-formatting
topics/serialize
topics/proxy

View File

@ -0,0 +1,95 @@
Storage Engines
===============
Every time you login to Telegram, some personal piece of data are created and held by both parties (the client, Pyrogram
and the server, Telegram). This session data is uniquely bound to your own account, indefinitely (until you logout or
decide to manually terminate it) and is used to authorize a client to execute API calls on behalf of your identity.
Persisting Sessions
-------------------
In order to make a client reconnect successfully between restarts, that is, without having to start a new
authorization process from scratch each time, Pyrogram needs to store the generated session data somewhere.
Other useful data being stored is peers' cache. In short, peers are all those entities you can chat with, such as users
or bots, basic groups, but also channels and supergroups. Because of how Telegram works, a unique pair of **id** and
**access_hash** is needed to contact a peer. This, plus other useful info such as the peer type, is what is stored
inside a session storage.
So, if you ever wondered how is Pyrogram able to contact peers just by asking for their ids, it's because of this very
reason: the peer *id* is looked up in the internal database and the available *access_hash* is retrieved, which is then
used to correctly invoke API methods.
Different Storage Engines
-------------------------
Let's now talk about how Pyrogram actually stores all the relevant data. Pyrogram offers two different types of storage
engines: a **File Storage** and a **Memory Storage**. These engines are well integrated in the library and require a
minimal effort to set up. Here's how they work:
File Storage
^^^^^^^^^^^^
This is the most common storage engine. It is implemented by using **SQLite**, which will store the session and peers
details. The database will be saved to disk as a single portable file and is designed to efficiently save and retrieve
peers whenever they are needed.
To use this type of engine, simply pass any name of your choice to the ``session_name`` parameter of the
:obj:`~pyrogram.Client` constructor, as usual:
.. code-block:: python
from pyrogram import Client
with Client("my_account") as app:
print(app.get_me())
Once you successfully log in (either with a user or a bot identity), a session file will be created and saved to disk as
``my_account.session``. Any subsequent client restart will make Pyrogram search for a file named that way and the
session database will be automatically loaded.
Memory Storage
^^^^^^^^^^^^^^
In case you don't want to have any session file saved on disk, you can use an in-memory storage by passing the special
session name "**:memory:**" to the ``session_name`` parameter of the :obj:`~pyrogram.Client` constructor:
.. code-block:: python
from pyrogram import Client
with Client(":memory:") as app:
print(app.get_me())
This database is still backed by SQLite, but exists purely in memory. However, once you stop a client, the entire
database is discarded and the session details used for logging in again will be lost forever.
Session Strings
---------------
Session strings are useful when you want to run authorized Pyrogram clients on platforms like
`Heroku <https://www.heroku.com/>`_, where their ephemeral filesystems makes it much harder for a file-based storage
engine to properly work as intended.
In case you want to use an in-memory storage, but also want to keep access to the session you created, call
:meth:`~pyrogram.Client.export_session_string` anytime before stopping the client...
.. code-block:: python
from pyrogram import Client
with Client(":memory:") as app:
print(app.export_session_string())
...and save the resulting string somewhere. You can use this string as session name the next time you want to login
using the same session; the storage used will still be completely in-memory:
.. code-block:: python
from pyrogram import Client
session_string = "...ZnUIFD8jsjXTb8g_vpxx48k1zkov9sapD-tzjz-S4WZv70M..."
with Client(session_string) as app:
print(app.get_me())

View File

@ -24,7 +24,7 @@ if sys.version_info[:3] in [(3, 5, 0), (3, 5, 1), (3, 5, 2)]:
# Monkey patch the standard "typing" module because Python versions from 3.5.0 to 3.5.2 have a broken one.
sys.modules["typing"] = typing
__version__ = "0.14.1-asyncio"
__version__ = "0.15.0-asyncio"
__license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
__copyright__ = "Copyright (C) 2017-2019 Dan <https://github.com/delivrance>"

View File

@ -29,7 +29,6 @@ import shutil
import tempfile
import time
from configparser import ConfigParser
from datetime import datetime
from hashlib import sha256, md5
from importlib import import_module
from pathlib import Path
@ -54,6 +53,7 @@ from pyrogram.session import Auth, Session
from .ext import utils, Syncer, BaseClient, Dispatcher
from .ext.utils import ainput
from .methods import Methods
from .storage import Storage, FileStorage, MemoryStorage
log = logging.getLogger(__name__)
@ -63,8 +63,13 @@ class Client(Methods, BaseClient):
Parameters:
session_name (``str``):
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.
Pass a string of your choice to give a name to the client session, e.g.: "*my_account*". This name will be
used to save a file on disk that stores details needed to reconnect without asking again for credentials.
Alternatively, if you don't want a file to be saved on disk, pass the special name "**:memory:**" to start
an in-memory session that will be discarded as soon as you stop the Client. In order to reconnect again
using a memory storage without having to login again, you can use
:meth:`~pyrogram.Client.export_session_string` before stopping the client to get a session string you can
pass here as argument.
api_id (``int``, *optional*):
The *api_id* part of your Telegram API Key, as integer. E.g.: 12345
@ -178,7 +183,7 @@ class Client(Methods, BaseClient):
def __init__(
self,
session_name: str,
session_name: Union[str, Storage],
api_id: Union[int, str] = None,
api_hash: str = None,
app_version: str = None,
@ -225,12 +230,23 @@ class Client(Methods, BaseClient):
self.first_name = first_name
self.last_name = last_name
self.workers = workers
self.workdir = workdir
self.config_file = config_file
self.workdir = Path(workdir)
self.config_file = Path(config_file)
self.plugins = plugins
self.no_updates = no_updates
self.takeout = takeout
if isinstance(session_name, str):
if session_name == ":memory:" or len(session_name) >= MemoryStorage.SESSION_STRING_SIZE:
session_name = re.sub(r"[\n\s]+", "", session_name)
self.storage = MemoryStorage(session_name)
else:
self.storage = FileStorage(session_name, self.workdir)
elif isinstance(session_name, Storage):
self.storage = session_name
else:
raise ValueError("Unknown storage engine")
self.dispatcher = Dispatcher(self, workers)
def __enter__(self):
@ -271,50 +287,32 @@ class Client(Methods, BaseClient):
if self.is_started:
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]
log.warning('\nWARNING: You are using a bot token as session name!\n'
'This usage will be deprecated soon. Please use a session file name to load '
'an existing session and the bot_token argument to create new sessions.\n'
'More info: https://docs.pyrogram.org/intro/auth#bot-authorization\n')
self.load_config()
await self.load_session()
self.load_plugins()
self.session = Session(
self,
self.dc_id,
self.auth_key
)
self.session = Session(self, self.storage.dc_id, self.storage.auth_key)
await self.session.start()
self.is_started = True
try:
if self.user_id is None:
if self.storage.user_id is None:
if self.bot_token is None:
self.is_bot = False
self.storage.is_bot = False
await self.authorize_user()
else:
self.is_bot = True
self.storage.is_bot = True
await self.authorize_bot()
self.save_session()
if not self.is_bot:
if not self.storage.is_bot:
if self.takeout:
self.takeout_id = (await self.send(functions.account.InitTakeoutSession())).id
log.warning("Takeout session {} initiated".format(self.takeout_id))
now = time.time()
if abs(now - self.date) > Client.OFFLINE_SLEEP:
self.peers_by_username = {}
self.peers_by_phone = {}
if abs(now - self.storage.date) > Client.OFFLINE_SLEEP:
await self.get_initial_dialogs()
await self.get_contacts()
else:
@ -516,19 +514,14 @@ class Client(Methods, BaseClient):
except UserMigrate as e:
await self.session.stop()
self.dc_id = e.x
self.auth_key = await Auth(self.dc_id, self.test_mode, self.ipv6, self._proxy).create()
self.session = Session(
self,
self.dc_id,
self.auth_key
)
self.storage.dc_id = e.x
self.storage.auth_key = await Auth(self, self.storage.dc_id).create()
self.session = Session(self, self.storage.dc_id, self.storage.auth_key)
await self.session.start()
await self.authorize_bot()
else:
self.user_id = r.user.id
self.storage.user_id = r.user.id
print("Logged in successfully as @{}".format(r.user.username))
@ -569,20 +562,10 @@ class Client(Methods, BaseClient):
except (PhoneMigrate, NetworkMigrate) as e:
await self.session.stop()
self.dc_id = e.x
self.storage.dc_id = e.x
self.storage.auth_key = await Auth(self, self.storage.dc_id).create()
self.auth_key = await Auth(
self.dc_id,
self.test_mode,
self.ipv6,
self._proxy
).create()
self.session = Session(
self,
self.dc_id,
self.auth_key
)
self.session = Session(self, self.storage.dc_id, self.storage.auth_key)
await self.session.start()
except (PhoneNumberInvalid, PhoneNumberBanned) as e:
@ -762,13 +745,13 @@ class Client(Methods, BaseClient):
)
self.password = None
self.user_id = r.user.id
self.storage.user_id = r.user.id
print("Logged in successfully as {}".format(r.user.first_name))
def fetch_peers(
self,
entities: List[
peers: List[
Union[
types.User,
types.Chat, types.ChatForbidden,
@ -777,64 +760,57 @@ class Client(Methods, BaseClient):
]
) -> bool:
is_min = False
parsed_peers = []
for entity in entities:
if isinstance(entity, types.User):
user_id = entity.id
for peer in peers:
username = None
phone_number = None
access_hash = entity.access_hash
if isinstance(peer, types.User):
peer_id = peer.id
access_hash = peer.access_hash
username = peer.username
phone_number = peer.phone
if peer.bot:
peer_type = "bot"
else:
peer_type = "user"
if access_hash is None:
is_min = True
continue
username = entity.username
phone = entity.phone
input_peer = types.InputPeerUser(
user_id=user_id,
access_hash=access_hash
)
self.peers_by_id[user_id] = input_peer
if username is not None:
self.peers_by_username[username.lower()] = input_peer
username = username.lower()
elif isinstance(peer, (types.Chat, types.ChatForbidden)):
peer_id = -peer.id
access_hash = 0
peer_type = "group"
elif isinstance(peer, (types.Channel, types.ChannelForbidden)):
peer_id = int("-100" + str(peer.id))
access_hash = peer.access_hash
if phone is not None:
self.peers_by_phone[phone] = input_peer
username = getattr(peer, "username", None)
if isinstance(entity, (types.Chat, types.ChatForbidden)):
chat_id = entity.id
peer_id = -chat_id
input_peer = types.InputPeerChat(
chat_id=chat_id
)
self.peers_by_id[peer_id] = input_peer
if isinstance(entity, (types.Channel, types.ChannelForbidden)):
channel_id = entity.id
peer_id = int("-100" + str(channel_id))
access_hash = entity.access_hash
if peer.broadcast:
peer_type = "channel"
else:
peer_type = "supergroup"
if access_hash is None:
is_min = True
continue
username = getattr(entity, "username", None)
input_peer = types.InputPeerChannel(
channel_id=channel_id,
access_hash=access_hash
)
self.peers_by_id[peer_id] = input_peer
if username is not None:
self.peers_by_username[username.lower()] = input_peer
username = username.lower()
else:
continue
parsed_peers.append((peer_id, access_hash, peer_type, username, phone_number))
self.storage.update_peers(parsed_peers)
return is_min
@ -849,37 +825,7 @@ class Client(Methods, BaseClient):
final_file_path = ""
try:
data, file_name, done, progress, progress_args, path = packet
directory, file_name = os.path.split(file_name)
directory = directory or "downloads"
media_type_str = Client.MEDIA_TYPE_ID[data.media_type]
if not data.file_name:
guessed_extension = self.guess_extension(data.mime_type)
if data.media_type in (0, 1, 2, 14):
extension = ".jpg"
elif data.media_type == 3:
extension = guessed_extension or ".ogg"
elif data.media_type in (4, 10, 13):
extension = guessed_extension or ".mp4"
elif data.media_type == 5:
extension = guessed_extension or ".zip"
elif data.media_type == 8:
extension = guessed_extension or ".webp"
elif data.media_type == 9:
extension = guessed_extension or ".mp3"
else:
continue
file_name = "{}_{}_{}{}".format(
media_type_str,
datetime.fromtimestamp(data.date or time.time()).strftime("%Y-%m-%d_%H-%M-%S"),
self.rnd_id(),
extension
)
data, directory, file_name, done, progress, progress_args, path = packet
temp_file_path = await self.get_file(
media_type=data.media_type,
@ -1092,71 +1038,75 @@ class Client(Methods, BaseClient):
self._proxy["password"] = parser.get("proxy", "password", fallback=None) or None
if self.plugins:
self.plugins["enabled"] = bool(self.plugins.get("enabled", True))
self.plugins["include"] = "\n".join(self.plugins.get("include", [])) or None
self.plugins["exclude"] = "\n".join(self.plugins.get("exclude", [])) or None
self.plugins = {
"enabled": bool(self.plugins.get("enabled", True)),
"root": self.plugins.get("root", None),
"include": self.plugins.get("include", []),
"exclude": self.plugins.get("exclude", [])
}
else:
try:
section = parser["plugins"]
self.plugins = {
"enabled": section.getboolean("enabled", True),
"root": section.get("root"),
"include": section.get("include") or None,
"exclude": section.get("exclude") or None
"root": section.get("root", None),
"include": section.get("include", []),
"exclude": section.get("exclude", [])
}
except KeyError:
self.plugins = {}
if self.plugins:
for option in ["include", "exclude"]:
if self.plugins[option] is not None:
self.plugins[option] = [
(i.split()[0], i.split()[1:] or None)
for i in self.plugins[option].strip().split("\n")
]
include = self.plugins["include"]
exclude = self.plugins["exclude"]
if include:
self.plugins["include"] = include.strip().split("\n")
if exclude:
self.plugins["exclude"] = exclude.strip().split("\n")
except KeyError:
self.plugins = None
async def load_session(self):
try:
with open(os.path.join(self.workdir, "{}.session".format(self.session_name)), encoding="utf-8") as f:
s = json.load(f)
except FileNotFoundError:
self.dc_id = 1
self.date = 0
self.auth_key = await Auth(self.dc_id, self.test_mode, self.ipv6, self._proxy).create()
else:
self.dc_id = s["dc_id"]
self.test_mode = s["test_mode"]
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)
self.storage.open()
for k, v in s.get("peers_by_id", {}).items():
self.peers_by_id[int(k)] = utils.get_input_peer(int(k), v)
session_empty = any([
self.storage.test_mode is None,
self.storage.auth_key is None,
self.storage.user_id is None,
self.storage.is_bot is None
])
for k, v in s.get("peers_by_username", {}).items():
peer = self.peers_by_id.get(v, None)
if session_empty:
self.storage.dc_id = 1
self.storage.date = 0
if peer:
self.peers_by_username[k] = peer
for k, v in s.get("peers_by_phone", {}).items():
peer = self.peers_by_id.get(v, None)
if peer:
self.peers_by_phone[k] = peer
self.storage.test_mode = self.test_mode
self.storage.auth_key = await Auth(self, self.storage.dc_id).create()
self.storage.user_id = None
self.storage.is_bot = None
def load_plugins(self):
if self.plugins.get("enabled", False):
root = self.plugins["root"]
include = self.plugins["include"]
exclude = self.plugins["exclude"]
if self.plugins:
plugins = self.plugins.copy()
for option in ["include", "exclude"]:
if plugins[option]:
plugins[option] = [
(i.split()[0], i.split()[1:] or None)
for i in self.plugins[option]
]
else:
return
if plugins.get("enabled", False):
root = plugins["root"]
include = plugins["include"]
exclude = plugins["exclude"]
count = 0
if include is None:
if not include:
for path in sorted(Path(root).rglob("*.py")):
module_path = '.'.join(path.parent.parts + (path.stem,))
module = import_module(module_path)
@ -1213,7 +1163,7 @@ class Client(Methods, BaseClient):
log.warning('[{}] [LOAD] Ignoring non-existent function "{}" from "{}"'.format(
self.session_name, name, module_path))
if exclude is not None:
if exclude:
for path, handlers in exclude:
module_path = root + "." + path
warn_non_existent_functions = True
@ -1258,28 +1208,7 @@ class Client(Methods, BaseClient):
log.warning('[{}] No plugin loaded from "{}"'.format(
self.session_name, root))
def save_session(self):
auth_key = base64.b64encode(self.auth_key).decode()
auth_key = [auth_key[i: i + 43] for i in range(0, len(auth_key), 43)]
os.makedirs(self.workdir, exist_ok=True)
with open(os.path.join(self.workdir, "{}.session".format(self.session_name)), "w", encoding="utf-8") as f:
json.dump(
dict(
dc_id=self.dc_id,
test_mode=self.test_mode,
auth_key=auth_key,
user_id=self.user_id,
date=self.date,
is_bot=self.is_bot,
),
f,
indent=4
)
async def get_initial_dialogs_chunk(self,
offset_date: int = 0):
async def get_initial_dialogs_chunk(self, offset_date: int = 0):
while True:
try:
r = await self.send(
@ -1296,7 +1225,7 @@ class Client(Methods, BaseClient):
log.warning("get_dialogs flood: waiting {} seconds".format(e.x))
await asyncio.sleep(e.x)
else:
log.info("Total peers: {}".format(len(self.peers_by_id)))
log.info("Total peers: {}".format(self.storage.peers_count))
return r
async def get_initial_dialogs(self):
@ -1335,7 +1264,7 @@ class Client(Methods, BaseClient):
KeyError: In case the peer doesn't exist in the internal database.
"""
try:
return self.peers_by_id[peer_id]
return self.storage.get_peer_by_id(peer_id)
except KeyError:
if type(peer_id) is str:
if peer_id in ("self", "me"):
@ -1346,15 +1275,17 @@ class Client(Methods, BaseClient):
try:
int(peer_id)
except ValueError:
if peer_id not in self.peers_by_username:
try:
return self.storage.get_peer_by_username(peer_id)
except KeyError:
await self.send(functions.contacts.ResolveUsername(username=peer_id
)
)
return self.peers_by_username[peer_id]
return self.storage.get_peer_by_username(peer_id)
else:
try:
return self.peers_by_phone[peer_id]
return self.storage.get_peer_by_phone_number(peer_id)
except KeyError:
raise PeerIdInvalid
@ -1362,7 +1293,10 @@ class Client(Methods, BaseClient):
self.fetch_peers(
await self.send(
functions.users.GetUsers(
id=[types.InputUser(user_id=peer_id, access_hash=0)]
id=[types.InputUser(
user_id=peer_id,
access_hash=0
)]
)
)
)
@ -1370,7 +1304,10 @@ class Client(Methods, BaseClient):
if str(peer_id).startswith("-100"):
await self.send(
functions.channels.GetChannels(
id=[types.InputChannel(channel_id=int(str(peer_id)[4:]), access_hash=0)]
id=[types.InputChannel(
channel_id=int(str(peer_id)[4:]),
access_hash=0
)]
)
)
else:
@ -1381,7 +1318,7 @@ class Client(Methods, BaseClient):
)
try:
return self.peers_by_id[peer_id]
return self.storage.get_peer_by_id(peer_id)
except KeyError:
raise PeerIdInvalid
@ -1469,7 +1406,7 @@ class Client(Methods, BaseClient):
is_missing_part = file_id is not None
file_id = file_id or self.rnd_id()
md5_sum = md5() if not is_big and not is_missing_part else None
pool = [Session(self, self.dc_id, self.auth_key, is_media=True) for _ in range(pool_size)]
pool = [Session(self, self.storage.dc_id, self.storage.auth_key, is_media=True) for _ in range(pool_size)]
workers = [asyncio.ensure_future(worker(session)) for session in pool for _ in range(workers_count)]
queue = asyncio.Queue(16)
@ -1559,7 +1496,7 @@ class Client(Methods, BaseClient):
session = self.media_sessions.get(dc_id, None)
if session is None:
if dc_id != self.dc_id:
if dc_id != self.storage.dc_id:
exported_auth = await self.send(
functions.auth.ExportAuthorization(
dc_id=dc_id
@ -1569,9 +1506,7 @@ class Client(Methods, BaseClient):
session = Session(
self,
dc_id,
await Auth(dc_id, self.test_mode, self.ipv6, self._proxy).create(),
is_media=True
)
await Auth(self, dc_id).create(), is_media=True)
await session.start()
@ -1584,12 +1519,7 @@ class Client(Methods, BaseClient):
)
)
else:
session = Session(
self,
dc_id,
self.auth_key,
is_media=True
)
session = Session(self, dc_id, self.storage.auth_key, is_media=True)
await session.start()
@ -1677,10 +1607,7 @@ class Client(Methods, BaseClient):
cdn_session = Session(
self,
r.dc_id,
await Auth(r.dc_id, self.test_mode, self.ipv6, self._proxy).create(),
is_media=True,
is_cdn=True
)
await Auth(self, r.dc_id).create(), is_media=True, is_cdn=True)
await cdn_session.start()
@ -1776,3 +1703,11 @@ class Client(Methods, BaseClient):
if extensions:
return extensions.split(" ")[0]
def export_session_string(self):
"""Export the current session as serialized string.
Returns:
``str``: The session serialized into a printable, url-safe string.
"""
return self.storage.export_session_string()

View File

@ -19,6 +19,5 @@
from .base_client import BaseClient
from .dispatcher import Dispatcher
from .emoji import Emoji
from .syncer import Syncer
from .file_data import FileData
from .syncer import Syncer

View File

@ -20,8 +20,11 @@ import asyncio
import os
import platform
import re
import sys
from pathlib import Path
from pyrogram import __version__
from ..style import Markdown, HTML
from ...session.internals import MsgId
@ -44,6 +47,8 @@ class BaseClient:
LANG_CODE = "en"
PARENT_DIR = Path(sys.argv[0]).parent
INVITE_LINK_RE = re.compile(r"^(?:https?://)?(?:www\.)?(?:t(?:elegram)?\.(?:org|me|dog)/joinchat/)([\w-]+)$")
BOT_TOKEN_RE = re.compile(r"^\d+:[\w-]+$")
DIALOGS_AT_ONCE = 100
@ -51,8 +56,8 @@ class BaseClient:
DOWNLOAD_WORKERS = 4
OFFLINE_SLEEP = 900
WORKERS = 4
WORKDIR = "."
CONFIG_FILE = "./config.ini"
WORKDIR = PARENT_DIR
CONFIG_FILE = PARENT_DIR / "config.ini"
MEDIA_TYPE_ID = {
0: "photo_thumbnail",
@ -83,18 +88,10 @@ class BaseClient:
mime_types_to_extensions[mime_type] = " ".join(extensions)
def __init__(self):
self.is_bot = None
self.dc_id = None
self.auth_key = None
self.user_id = None
self.date = None
self.storage = None
self.rnd_id = MsgId
self.peers_by_id = {}
self.peers_by_username = {}
self.peers_by_phone = {}
self.markdown = Markdown(self)
self.html = HTML(self)
@ -155,3 +152,21 @@ class BaseClient:
def get_profile_photos(self, *args, **kwargs):
pass
def edit_message_text(self, *args, **kwargs):
pass
def edit_inline_text(self, *args, **kwargs):
pass
def edit_message_media(self, *args, **kwargs):
pass
def edit_inline_media(self, *args, **kwargs):
pass
def edit_message_reply_markup(self, *args, **kwargs):
pass
def edit_inline_reply_markup(self, *args, **kwargs):
pass

View File

@ -22,6 +22,8 @@ from collections import OrderedDict
import pyrogram
from pyrogram.api import types
from . import utils
from ..handlers import (
CallbackQueryHandler, MessageHandler, DeletedMessagesHandler,
UserStatusHandler, RawUpdateHandler, InlineQueryHandler, PollHandler
@ -65,7 +67,7 @@ class Dispatcher:
return await pyrogram.Message._parse(self.client, update.message, users, chats), MessageHandler
async def deleted_messages_parser(update, users, chats):
return pyrogram.Messages._parse_deleted(self.client, update), DeletedMessagesHandler
return utils.parse_deleted_messages(self.client, update), DeletedMessagesHandler
async def callback_query_parser(update, users, chats):
return await pyrogram.CallbackQuery._parse(self.client, update, users), CallbackQueryHandler
@ -106,6 +108,7 @@ class Dispatcher:
await i
self.update_worker_tasks.clear()
self.groups.clear()
log.info("Stopped {} UpdateWorkerTasks".format(self.workers))

View File

@ -17,15 +17,9 @@
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
import asyncio
import base64
import json
import logging
import os
import shutil
import time
from . import utils
log = logging.getLogger(__name__)
@ -85,48 +79,13 @@ class Syncer:
@classmethod
def sync(cls, client):
temporary = os.path.join(client.workdir, "{}.sync".format(client.session_name))
persistent = os.path.join(client.workdir, "{}.session".format(client.session_name))
try:
auth_key = base64.b64encode(client.auth_key).decode()
auth_key = [auth_key[i: i + 43] for i in range(0, len(auth_key), 43)]
data = dict(
dc_id=client.dc_id,
test_mode=client.test_mode,
auth_key=auth_key,
user_id=client.user_id,
date=int(time.time()),
is_bot=bool(client.is_bot),
peers_by_id={
k: getattr(v, "access_hash", None)
for k, v in client.peers_by_id.copy().items()
},
peers_by_username={
k: utils.get_peer_id(v)
for k, v in client.peers_by_username.copy().items()
},
peers_by_phone={
k: utils.get_peer_id(v)
for k, v in client.peers_by_phone.copy().items()
}
)
os.makedirs(client.workdir, exist_ok=True)
with open(temporary, "w", encoding="utf-8") as f:
json.dump(data, f, indent=4)
f.flush()
os.fsync(f.fileno())
start = time.time()
client.storage.save()
except Exception as e:
log.critical(e, exc_info=True)
else:
shutil.move(temporary, persistent)
log.info("Synced {}".format(client.session_name))
finally:
try:
os.remove(temporary)
except OSError:
pass
log.info('Synced "{}" in {:.6} ms'.format(
client.storage.name,
(time.time() - start) * 1000
))

View File

@ -17,18 +17,20 @@
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
import asyncio
import base64
import struct
import sys
from base64 import b64decode, b64encode
from concurrent.futures.thread import ThreadPoolExecutor
from typing import Union
from typing import Union, List
import pyrogram
from . import BaseClient
from ...api import types
def decode(s: str) -> bytes:
s = b64decode(s + "=" * (-len(s) % 4), "-_")
s = base64.urlsafe_b64decode(s + "=" * (-len(s) % 4))
r = b""
assert s[-1] == 2
@ -60,7 +62,7 @@ def encode(s: bytes) -> str:
r += bytes([i])
return b64encode(r, b"-_").decode().rstrip("=")
return base64.urlsafe_b64encode(r).decode().rstrip("=")
async def ainput(prompt: str = ""):
@ -147,3 +149,69 @@ def get_input_media_from_file_id(
)
raise ValueError("Unknown media type: {}".format(file_id_str))
async def parse_messages(client, messages: types.messages.Messages, replies: int = 1) -> List["pyrogram.Message"]:
users = {i.id: i for i in messages.users}
chats = {i.id: i for i in messages.chats}
if not messages.messages:
return pyrogram.List()
parsed_messages = []
for message in messages.messages:
parsed_messages.append(await pyrogram.Message._parse(client, message, users, chats, replies=0))
if replies:
messages_with_replies = {i.id: getattr(i, "reply_to_msg_id", None) for i in messages.messages}
reply_message_ids = [i[0] for i in filter(lambda x: x[1] is not None, messages_with_replies.items())]
if reply_message_ids:
reply_messages = await client.get_messages(
parsed_messages[0].chat.id,
reply_to_message_ids=reply_message_ids,
replies=replies - 1
)
for message in parsed_messages:
reply_id = messages_with_replies[message.message_id]
for reply in reply_messages:
if reply.message_id == reply_id:
message.reply_to_message = reply
return pyrogram.List(parsed_messages)
def parse_deleted_messages(client, update) -> List["pyrogram.Message"]:
messages = update.messages
channel_id = getattr(update, "channel_id", None)
parsed_messages = []
for message in messages:
parsed_messages.append(
pyrogram.Message(
message_id=message,
chat=pyrogram.Chat(
id=int("-100" + str(channel_id)),
type="channel",
client=client
) if channel_id is not None else None,
client=client
)
)
return pyrogram.List(parsed_messages)
def unpack_inline_message_id(inline_message_id: str) -> types.InputBotInlineMessageID:
r = inline_message_id + "=" * (-len(inline_message_id) % 4)
r = struct.unpack("<iqq", base64.b64decode(r, altchars="-_"))
return types.InputBotInlineMessageID(
dc_id=r[0],
id=r[1],
access_hash=r[2]
)

View File

@ -19,7 +19,7 @@
import re
from .filter import Filter
from ..types.keyboards import InlineKeyboardMarkup, ReplyKeyboardMarkup
from ..types.bots_and_keyboards import InlineKeyboardMarkup, ReplyKeyboardMarkup
def create(name: str, func: callable, **kwargs) -> type:

View File

@ -20,16 +20,15 @@ from .handler import Handler
class DeletedMessagesHandler(Handler):
"""The deleted Messages handler class. Used to handle deleted messages coming from any chat
(private, group, channel). It is intended to be used with
:meth:`~Client.add_handler`
"""The deleted messages handler class. Used to handle deleted messages coming from any chat
(private, group, channel). It is intended to be used with :meth:`~Client.add_handler`
For a nicer way to register this handler, have a look at the
:meth:`~Client.on_deleted_messages` decorator.
Parameters:
callback (``callable``):
Pass a function that will be called when one or more Messages have been deleted.
Pass a function that will be called when one or more messages have been deleted.
It takes *(client, messages)* as positional arguments (look at the section below for a detailed description).
filters (:obj:`Filters`):
@ -40,12 +39,12 @@ class DeletedMessagesHandler(Handler):
client (:obj:`Client`):
The Client itself, useful when you want to call other API methods inside the message handler.
messages (:obj:`Messages`):
The deleted messages.
messages (List of :obj:`Message`):
The deleted messages, as list.
"""
def __init__(self, callback: callable, filters=None):
super().__init__(callback, filters)
def check(self, messages):
return super().check(messages.messages[0])
return super().check(messages[0])

View File

@ -16,7 +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 typing import Union
from typing import Union, List
import pyrogram
from pyrogram.api import functions
@ -29,7 +29,7 @@ class GetGameHighScores(BaseClient):
user_id: Union[int, str],
chat_id: Union[int, str],
message_id: int = None
) -> "pyrogram.GameHighScores":
) -> List["pyrogram.GameHighScore"]:
"""Get data for high score tables.
Parameters:
@ -49,20 +49,19 @@ class GetGameHighScores(BaseClient):
Required if inline_message_id is not specified.
Returns:
:obj:`GameHighScores`: On success.
List of :obj:`GameHighScore`: On success.
Raises:
RPCError: In case of a Telegram RPC error.
"""
# TODO: inline_message_id
return pyrogram.GameHighScores._parse(
self,
await self.send(
functions.messages.GetGameHighScores(
peer=await self.resolve_peer(chat_id),
id=message_id,
user_id=await self.resolve_peer(user_id)
)
r = await self.send(
functions.messages.GetGameHighScores(
peer=await self.resolve_peer(chat_id),
id=message_id,
user_id=await self.resolve_peer(user_id)
)
)
return pyrogram.List(pyrogram.GameHighScore._parse(self, score, r.users) for score in r.scores)

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/>.
from .archive_chats import ArchiveChats
from .delete_chat_photo import DeleteChatPhoto
from .export_chat_invite_link import ExportChatInviteLink
from .get_chat import GetChat
@ -36,6 +37,7 @@ from .restrict_chat_member import RestrictChatMember
from .set_chat_description import SetChatDescription
from .set_chat_photo import SetChatPhoto
from .set_chat_title import SetChatTitle
from .unarchive_chats import UnarchiveChats
from .unban_chat_member import UnbanChatMember
from .unpin_chat_message import UnpinChatMessage
from .update_chat_username import UpdateChatUsername
@ -64,6 +66,8 @@ class Chats(
IterChatMembers,
UpdateChatUsername,
RestrictChat,
GetDialogsCount
GetDialogsCount,
ArchiveChats,
UnarchiveChats
):
pass

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, List
from pyrogram.api import functions, types
from ...ext import BaseClient
class ArchiveChats(BaseClient):
async def archive_chats(
self,
chat_ids: Union[int, str, List[Union[int, str]]],
) -> bool:
"""Archive one or more chats.
Parameters:
chat_ids (``int`` | ``str`` | List[``int``, ``str``]):
Unique identifier (int) or username (str) of the target chat.
You can also pass a list of ids (int) or usernames (str).
Returns:
``bool``: On success, True is returned.
Raises:
RPCError: In case of a Telegram RPC error.
"""
if not isinstance(chat_ids, list):
chat_ids = [chat_ids]
await self.send(
functions.folders.EditPeerFolders(
folder_peers=[
types.InputFolderPeer(
peer=await self.resolve_peer(chat),
folder_id=1
) for chat in chat_ids
]
)
)
return True

View File

@ -21,6 +21,7 @@ from typing import Union
import pyrogram
from pyrogram.api import functions, types
from pyrogram.errors import UserNotParticipant
from ...ext import BaseClient
@ -51,13 +52,18 @@ class GetChatMember(BaseClient):
user = await self.resolve_peer(user_id)
if isinstance(chat, types.InputPeerChat):
full_chat = await self.send(
r = await self.send(
functions.messages.GetFullChat(
chat_id=chat.chat_id
)
)
for member in pyrogram.ChatMembers._parse(self, full_chat).chat_members:
members = r.full_chat.participants.participants
users = {i.id: i for i in r.users}
for member in members:
member = pyrogram.ChatMember._parse(self, member, users)
if isinstance(user, types.InputPeerSelf):
if member.user.is_self:
return member

View File

@ -18,11 +18,12 @@
import asyncio
import logging
from typing import Union
from typing import Union, List
import pyrogram
from pyrogram.api import functions, types
from pyrogram.errors import FloodWait
from ...ext import BaseClient
log = logging.getLogger(__name__)
@ -45,7 +46,7 @@ class GetChatMembers(BaseClient):
limit: int = 200,
query: str = "",
filter: str = Filters.ALL
) -> "pyrogram.ChatMembers":
) -> List["pyrogram.ChatMember"]:
"""Get a chunk of the members list of a chat.
You can get up to 200 chat members at once.
@ -59,15 +60,16 @@ class GetChatMembers(BaseClient):
offset (``int``, *optional*):
Sequential number of the first member to be returned.
Defaults to 0 [1]_.
Only applicable to supergroups and channels. Defaults to 0 [1]_.
limit (``int``, *optional*):
Limits the number of members to be retrieved.
Only applicable to supergroups and channels.
Defaults to 200, which is also the maximum server limit allowed per method call.
query (``str``, *optional*):
Query string to filter members based on their display names and usernames.
Defaults to "" (empty string) [2]_.
Only applicable to supergroups and channels. Defaults to "" (empty string) [2]_.
filter (``str``, *optional*):
Filter used to select the kind of members you want to retrieve. Only applicable for supergroups
@ -78,6 +80,7 @@ class GetChatMembers(BaseClient):
*"bots"* - bots only,
*"recent"* - recent members only,
*"administrators"* - chat administrators only.
Only applicable to supergroups and channels.
Defaults to *"all"*.
.. [1] Server limit: on supergroups, you can get up to 10,000 members for a single query and up to 200 members
@ -86,7 +89,7 @@ class GetChatMembers(BaseClient):
.. [2] A query string is applicable only for *"all"*, *"kicked"* and *"restricted"* filters only.
Returns:
:obj:`ChatMembers`: On success, an object containing a list of chat members is returned.
List of :obj:`ChatMember`: On success, a list of chat members is returned.
Raises:
RPCError: In case of a Telegram RPC error.
@ -95,14 +98,16 @@ class GetChatMembers(BaseClient):
peer = await self.resolve_peer(chat_id)
if isinstance(peer, types.InputPeerChat):
return pyrogram.ChatMembers._parse(
self,
await self.send(
functions.messages.GetFullChat(
chat_id=peer.chat_id
)
r = await self.send(
functions.messages.GetFullChat(
chat_id=peer.chat_id
)
)
members = r.full_chat.participants.participants
users = {i.id: i for i in r.users}
return pyrogram.List(pyrogram.ChatMember._parse(self, member, users) for member in members)
elif isinstance(peer, types.InputPeerChannel):
filter = filter.lower()
@ -123,18 +128,20 @@ class GetChatMembers(BaseClient):
while True:
try:
return pyrogram.ChatMembers._parse(
self,
await self.send(
functions.channels.GetParticipants(
channel=peer,
filter=filter,
offset=offset,
limit=limit,
hash=0
)
r = await self.send(
functions.channels.GetParticipants(
channel=peer,
filter=filter,
offset=offset,
limit=limit,
hash=0
)
)
members = r.participants
users = {i.id: i for i in r.users}
return pyrogram.List(pyrogram.ChatMember._parse(self, member, users) for member in members)
except FloodWait as e:
log.warning("Sleeping for {}s".format(e.x))
await asyncio.sleep(e.x)

View File

@ -18,10 +18,12 @@
import asyncio
import logging
from typing import List
import pyrogram
from pyrogram.api import functions, types
from pyrogram.errors import FloodWait
from ...ext import BaseClient
log = logging.getLogger(__name__)
@ -33,7 +35,7 @@ class GetDialogs(BaseClient):
offset_date: int = 0,
limit: int = 100,
pinned_only: bool = False
) -> "pyrogram.Dialogs":
) -> List["pyrogram.Dialog"]:
"""Get a chunk of the user's dialogs.
You can get up to 100 dialogs at once.
@ -53,7 +55,7 @@ class GetDialogs(BaseClient):
Defaults to False.
Returns:
:obj:`Dialogs`: On success, an object containing a list of dialogs is returned.
List of :obj:`Dialog`: On success, a list of dialogs is returned.
Raises:
RPCError: In case of a Telegram RPC error.
@ -80,4 +82,32 @@ class GetDialogs(BaseClient):
else:
break
return await pyrogram.Dialogs._parse(self, r)
users = {i.id: i for i in r.users}
chats = {i.id: i for i in r.chats}
messages = {}
for message in r.messages:
to_id = message.to_id
if isinstance(to_id, types.PeerUser):
if message.out:
chat_id = to_id.user_id
else:
chat_id = message.from_id
elif isinstance(to_id, types.PeerChat):
chat_id = -to_id.chat_id
else:
chat_id = int("-100" + str(to_id.channel_id))
messages[chat_id] = await pyrogram.Message._parse(self, message, users, chats)
parsed_dialogs = []
for dialog in r.dialogs:
if not isinstance(dialog, types.Dialog):
continue
parsed_dialogs.append(pyrogram.Dialog._parse(self, dialog, messages, users, chats))
return pyrogram.List(parsed_dialogs)

View File

@ -19,10 +19,10 @@
from string import ascii_lowercase
from typing import Union, AsyncGenerator, Optional
from async_generator import async_generator, yield_
import pyrogram
from async_generator import async_generator, yield_
from pyrogram.api import types
from ...ext import BaseClient
@ -103,13 +103,13 @@ class IterChatMembers(BaseClient):
offset = 0
while True:
chat_members = (await self.get_chat_members(
chat_members = await self.get_chat_members(
chat_id=chat_id,
offset=offset,
limit=limit,
query=q,
filter=filter
)).chat_members
)
if not chat_members:
break

View File

@ -18,9 +18,9 @@
from typing import AsyncGenerator, Optional
import pyrogram
from async_generator import async_generator, yield_
import pyrogram
from ...ext import BaseClient
@ -56,9 +56,9 @@ class IterDialogs(BaseClient):
total = limit or (1 << 31) - 1
limit = min(100, total)
pinned_dialogs = (await self.get_dialogs(
pinned_dialogs = await self.get_dialogs(
pinned_only=True
)).dialogs
)
for dialog in pinned_dialogs:
await yield_(dialog)
@ -69,10 +69,10 @@ class IterDialogs(BaseClient):
return
while True:
dialogs = (await self.get_dialogs(
dialogs = await self.get_dialogs(
offset_date=offset_date,
limit=limit
)).dialogs
)
if not dialogs:
return

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, List
from pyrogram.api import functions, types
from ...ext import BaseClient
class UnarchiveChats(BaseClient):
async def unarchive_chats(
self,
chat_ids: Union[int, str, List[Union[int, str]]],
) -> bool:
"""Unarchive one or more chats.
Parameters:
chat_ids (``int`` | ``str`` | List[``int``, ``str``]):
Unique identifier (int) or username (str) of the target chat.
You can also pass a list of ids (int) or usernames (str).
Returns:
``bool``: On success, True is returned.
Raises:
RPCError: In case of a Telegram RPC error.
"""
if not isinstance(chat_ids, list):
chat_ids = [chat_ids]
await self.send(
functions.folders.EditPeerFolders(
folder_peers=[
types.InputFolderPeer(
peer=await self.resolve_peer(chat),
folder_id=0
) for chat in chat_ids
]
)
)
return True

View File

@ -34,6 +34,9 @@ class AddContacts(BaseClient):
contacts (List of :obj:`InputPhoneContact`):
The contact list to be added
Returns:
:obj:`types.contacts.ImportedContacts`
Raises:
RPCError: In case of a Telegram RPC error.
"""

View File

@ -46,5 +46,4 @@ class GetContacts(BaseClient):
log.warning("get_contacts flood: waiting {} seconds".format(e.x))
await asyncio.sleep(e.x)
else:
log.info("Total contacts: {}".format(len(self.peers_by_phone)))
return [pyrogram.User._parse(self, user) for user in contacts.users]
return pyrogram.List(pyrogram.User._parse(self, user) for user in contacts.users)

View File

@ -18,6 +18,10 @@
from .delete_messages import DeleteMessages
from .download_media import DownloadMedia
from .edit_inline_caption import EditInlineCaption
from .edit_inline_media import EditInlineMedia
from .edit_inline_reply_markup import EditInlineReplyMarkup
from .edit_inline_text import EditInlineText
from .edit_message_caption import EditMessageCaption
from .edit_message_media import EditMessageMedia
from .edit_message_reply_markup import EditMessageReplyMarkup
@ -82,6 +86,10 @@ class Messages(
SendCachedMedia,
GetHistoryCount,
SendAnimatedSticker,
ReadHistory
ReadHistory,
EditInlineText,
EditInlineCaption,
EditInlineMedia,
EditInlineReplyMarkup
):
pass

View File

@ -18,19 +18,25 @@
import asyncio
import binascii
import os
import struct
import time
from datetime import datetime
from threading import Event
from typing import Union
import pyrogram
from pyrogram.client.ext import BaseClient, FileData, utils
from pyrogram.errors import FileIdInvalid
DEFAULT_DOWNLOAD_DIR = "downloads/"
class DownloadMedia(BaseClient):
async def download_media(
self,
message: Union["pyrogram.Message", str],
file_name: str = "",
file_name: str = DEFAULT_DOWNLOAD_DIR,
block: bool = True,
progress: callable = None,
progress_args: tuple = ()
@ -86,6 +92,7 @@ class DownloadMedia(BaseClient):
error_message = "This message doesn't contain any downloadable media"
available_media = ("audio", "document", "photo", "sticker", "animation", "video", "voice", "video_note")
media_file_name = None
file_size = None
mime_type = None
date = None
@ -105,13 +112,13 @@ class DownloadMedia(BaseClient):
file_id_str = media
else:
file_id_str = media.file_id
file_name = getattr(media, "file_name", "")
media_file_name = getattr(media, "file_name", "")
file_size = getattr(media, "file_size", None)
mime_type = getattr(media, "mime_type", None)
date = getattr(media, "date", None)
data = FileData(
file_name=file_name,
file_name=media_file_name,
file_size=file_size,
mime_type=mime_type,
date=date
@ -168,7 +175,40 @@ class DownloadMedia(BaseClient):
done = asyncio.Event()
path = [None]
self.download_queue.put_nowait((data, file_name, done, progress, progress_args, path))
directory, file_name = os.path.split(file_name)
file_name = file_name or data.file_name or ""
if not os.path.isabs(file_name):
directory = self.PARENT_DIR / (directory or DEFAULT_DOWNLOAD_DIR)
media_type_str = self.MEDIA_TYPE_ID[data.media_type]
if not file_name:
guessed_extension = self.guess_extension(data.mime_type)
if data.media_type in (0, 1, 2, 14):
extension = ".jpg"
elif data.media_type == 3:
extension = guessed_extension or ".ogg"
elif data.media_type in (4, 10, 13):
extension = guessed_extension or ".mp4"
elif data.media_type == 5:
extension = guessed_extension or ".zip"
elif data.media_type == 8:
extension = guessed_extension or ".webp"
elif data.media_type == 9:
extension = guessed_extension or ".mp3"
else:
extension = ".unknown"
file_name = "{}_{}_{}{}".format(
media_type_str,
datetime.fromtimestamp(data.date or time.time()).strftime("%Y-%m-%d_%H-%M-%S"),
self.rnd_id(),
extension
)
self.download_queue.put_nowait((data, directory, file_name, done, progress, progress_args, path))
if block:
await done.wait()

View File

@ -0,0 +1,58 @@
# 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.client.ext import BaseClient
class EditInlineCaption(BaseClient):
async def edit_inline_caption(
self,
inline_message_id: str,
caption: str,
parse_mode: str = "",
reply_markup: "pyrogram.InlineKeyboardMarkup" = None
) -> bool:
"""Edit the caption of **inline** media messages.
Parameters:
inline_message_id (``str``):
Identifier of the inline message.
caption (``str``):
New caption of the media message.
parse_mode (``str``, *optional*):
Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline
URLs in your message. Defaults to "markdown".
reply_markup (:obj:`InlineKeyboardMarkup`, *optional*):
An InlineKeyboardMarkup object.
Returns:
``bool``: On success, True is returned.
Raises:
RPCError: In case of a Telegram RPC error.
"""
return await self.edit_inline_text(
inline_message_id=inline_message_id,
text=caption,
parse_mode=parse_mode,
reply_markup=reply_markup
)

View File

@ -0,0 +1,104 @@
# 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 pyrogram.client.ext import BaseClient, utils
from pyrogram.client.types import (
InputMediaPhoto, InputMediaVideo, InputMediaAudio,
InputMediaAnimation, InputMediaDocument
)
from pyrogram.client.types.input_media import InputMedia
class EditInlineMedia(BaseClient):
async def edit_inline_media(
self,
inline_message_id: str,
media: InputMedia,
reply_markup: "pyrogram.InlineKeyboardMarkup" = None
) -> bool:
"""Edit **inline** animation, audio, document, photo or video messages.
When the inline message is edited, a new file can't be uploaded. Use a previously uploaded file via its file_id
or specify a URL.
Parameters:
inline_message_id (``str``):
Required if *chat_id* and *message_id* are not specified.
Identifier of the inline message.
media (:obj:`InputMedia`):
One of the InputMedia objects describing an animation, audio, document, photo or video.
reply_markup (:obj:`InlineKeyboardMarkup`, *optional*):
An InlineKeyboardMarkup object.
Returns:
``bool``: On success, True is returned.
Raises:
RPCError: In case of a Telegram RPC error.
"""
style = self.html if media.parse_mode.lower() == "html" else self.markdown
caption = media.caption
if isinstance(media, InputMediaPhoto):
if media.media.startswith("http"):
media = types.InputMediaPhotoExternal(
url=media.media
)
else:
media = utils.get_input_media_from_file_id(media.media, 2)
elif isinstance(media, InputMediaVideo):
if media.media.startswith("http"):
media = types.InputMediaDocumentExternal(
url=media.media
)
else:
media = utils.get_input_media_from_file_id(media.media, 4)
elif isinstance(media, InputMediaAudio):
if media.media.startswith("http"):
media = types.InputMediaDocumentExternal(
url=media.media
)
else:
media = utils.get_input_media_from_file_id(media.media, 9)
elif isinstance(media, InputMediaAnimation):
if media.media.startswith("http"):
media = types.InputMediaDocumentExternal(
url=media.media
)
else:
media = utils.get_input_media_from_file_id(media.media, 10)
elif isinstance(media, InputMediaDocument):
if media.media.startswith("http"):
media = types.InputMediaDocumentExternal(
url=media.media
)
else:
media = utils.get_input_media_from_file_id(media.media, 5)
return await self.send(
functions.messages.EditInlineBotMessage(
id=utils.unpack_inline_message_id(inline_message_id),
media=media,
reply_markup=reply_markup.write() if reply_markup else None,
**style.parse(caption)
)
)

View File

@ -0,0 +1,50 @@
# 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
from pyrogram.client.ext import BaseClient, utils
class EditInlineReplyMarkup(BaseClient):
async def edit_inline_reply_markup(
self,
inline_message_id: str,
reply_markup: "pyrogram.InlineKeyboardMarkup" = None
) -> bool:
"""Edit only the reply markup of **inline** messages sent via the bot (for inline bots).
Parameters:
inline_message_id (``str``):
Identifier of the inline message.
reply_markup (:obj:`InlineKeyboardMarkup`, *optional*):
An InlineKeyboardMarkup object.
Returns:
``bool``: On success, True is returned.
Raises:
RPCError: In case of a Telegram RPC error.
"""
return await self.send(
functions.messages.EditInlineBotMessage(
id=utils.unpack_inline_message_id(inline_message_id),
reply_markup=reply_markup.write() if reply_markup else None,
)
)

View File

@ -0,0 +1,67 @@
# 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
from pyrogram.client.ext import BaseClient, utils
class EditInlineText(BaseClient):
async def edit_inline_text(
self,
inline_message_id: str,
text: str,
parse_mode: str = "",
disable_web_page_preview: bool = None,
reply_markup: "pyrogram.InlineKeyboardMarkup" = None
) -> bool:
"""Edit the text of **inline** messages.
Parameters:
inline_message_id (``str``):
Identifier of the inline message.
text (``str``):
New text of the message.
parse_mode (``str``, *optional*):
Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline
URLs in your message. Defaults to "markdown".
disable_web_page_preview (``bool``, *optional*):
Disables link previews for links in this message.
reply_markup (:obj:`InlineKeyboardMarkup`, *optional*):
An InlineKeyboardMarkup object.
Returns:
``bool``: On success, True is returned.
Raises:
RPCError: In case of a Telegram RPC error.
"""
style = self.html if parse_mode.lower() == "html" else self.markdown
return await self.send(
functions.messages.EditInlineBotMessage(
id=utils.unpack_inline_message_id(inline_message_id),
no_webpage=disable_web_page_preview or None,
reply_markup=reply_markup.write() if reply_markup else None,
**style.parse(text)
)
)

View File

@ -19,7 +19,6 @@
from typing import Union
import pyrogram
from pyrogram.api import functions, types
from pyrogram.client.ext import BaseClient
@ -32,7 +31,7 @@ class EditMessageCaption(BaseClient):
parse_mode: str = "",
reply_markup: "pyrogram.InlineKeyboardMarkup" = None
) -> "pyrogram.Message":
"""Edit captions of messages.
"""Edit the caption of media messages.
Parameters:
chat_id (``int`` | ``str``):
@ -44,11 +43,11 @@ class EditMessageCaption(BaseClient):
Message identifier in the chat specified in chat_id.
caption (``str``):
New caption of the message.
New caption of the media message.
parse_mode (``str``, *optional*):
Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline
URLs in your caption. Defaults to "markdown".
URLs in your message. Defaults to "markdown".
reply_markup (:obj:`InlineKeyboardMarkup`, *optional*):
An InlineKeyboardMarkup object.
@ -59,21 +58,10 @@ class EditMessageCaption(BaseClient):
Raises:
RPCError: In case of a Telegram RPC error.
"""
style = self.html if parse_mode.lower() == "html" else self.markdown
r = await self.send(
functions.messages.EditMessage(
peer=await self.resolve_peer(chat_id),
id=message_id,
reply_markup=reply_markup.write() if reply_markup else None,
**await style.parse(caption)
)
return await self.edit_message_text(
chat_id=chat_id,
message_id=message_id,
text=caption,
parse_mode=parse_mode,
reply_markup=reply_markup
)
for i in r.updates:
if isinstance(i, (types.UpdateEditMessage, types.UpdateEditChannelMessage)):
return await pyrogram.Message._parse(
self, i.message,
{i.id: i for i in r.users},
{i.id: i for i in r.chats}
)

View File

@ -37,12 +37,10 @@ class EditMessageMedia(BaseClient):
media: InputMedia,
reply_markup: "pyrogram.InlineKeyboardMarkup" = None
) -> "pyrogram.Message":
"""Edit audio, document, photo, or video messages.
"""Edit animation, audio, document, photo or video messages.
If a message is a part of a message album, then it can be edited only to a photo or a video. Otherwise,
message type can be changed arbitrarily. When inline message is edited, new file can't be uploaded.
Use previously uploaded file via its file_id or specify a URL. On success, if the edited message was sent
by the bot, the edited Message is returned, otherwise True is returned.
If a message is a part of a message album, then it can be edited only to a photo or a video. Otherwise, the
message type can be changed arbitrarily.
Parameters:
chat_id (``int`` | ``str``):
@ -53,7 +51,7 @@ class EditMessageMedia(BaseClient):
message_id (``int``):
Message identifier in the chat specified in chat_id.
media (:obj:`InputMedia`)
media (:obj:`InputMedia`):
One of the InputMedia objects describing an animation, audio, document, photo or video.
reply_markup (:obj:`InlineKeyboardMarkup`, *optional*):
@ -92,8 +90,7 @@ class EditMessageMedia(BaseClient):
)
else:
media = utils.get_input_media_from_file_id(media.media, 2)
if isinstance(media, InputMediaVideo):
elif isinstance(media, InputMediaVideo):
if os.path.exists(media.media):
media = await self.send(
functions.messages.UploadMedia(
@ -130,8 +127,7 @@ class EditMessageMedia(BaseClient):
)
else:
media = utils.get_input_media_from_file_id(media.media, 4)
if isinstance(media, InputMediaAudio):
elif isinstance(media, InputMediaAudio):
if os.path.exists(media.media):
media = await self.send(
functions.messages.UploadMedia(
@ -167,8 +163,7 @@ class EditMessageMedia(BaseClient):
)
else:
media = utils.get_input_media_from_file_id(media.media, 9)
if isinstance(media, InputMediaAnimation):
elif isinstance(media, InputMediaAnimation):
if os.path.exists(media.media):
media = await self.send(
functions.messages.UploadMedia(
@ -206,8 +201,7 @@ class EditMessageMedia(BaseClient):
)
else:
media = utils.get_input_media_from_file_id(media.media, 10)
if isinstance(media, InputMediaDocument):
elif isinstance(media, InputMediaDocument):
if os.path.exists(media.media):
media = await self.send(
functions.messages.UploadMedia(
@ -243,8 +237,8 @@ class EditMessageMedia(BaseClient):
functions.messages.EditMessage(
peer=await self.resolve_peer(chat_id),
id=message_id,
reply_markup=reply_markup.write() if reply_markup else None,
media=media,
reply_markup=reply_markup.write() if reply_markup else None,
**await style.parse(caption)
)
)

View File

@ -28,9 +28,9 @@ class EditMessageReplyMarkup(BaseClient):
self,
chat_id: Union[int, str],
message_id: int,
reply_markup: "pyrogram.InlineKeyboardMarkup" = None
reply_markup: "pyrogram.InlineKeyboardMarkup" = None,
) -> "pyrogram.Message":
"""Edit only the reply markup of messages sent by the bot or via the bot (for inline bots).
"""Edit only the reply markup of messages sent by the bot.
Parameters:
chat_id (``int`` | ``str``):
@ -45,18 +45,16 @@ class EditMessageReplyMarkup(BaseClient):
An InlineKeyboardMarkup object.
Returns:
:obj:`Message` | ``bool``: In case the edited message is sent by the bot, the edited message is returned,
otherwise, True is returned in case the edited message is send by the user.
:obj:`Message`: On success, the edited message is returned.
Raises:
RPCError: In case of a Telegram RPC error.
"""
r = await self.send(
functions.messages.EditMessage(
peer=await self.resolve_peer(chat_id),
id=message_id,
reply_markup=reply_markup.write() if reply_markup else None
reply_markup=reply_markup.write() if reply_markup else None,
)
)

View File

@ -33,7 +33,7 @@ class EditMessageText(BaseClient):
disable_web_page_preview: bool = None,
reply_markup: "pyrogram.InlineKeyboardMarkup" = None
) -> "pyrogram.Message":
"""Edit text messages.
"""Edit the text of messages.
Parameters:
chat_id (``int`` | ``str``):

View File

@ -16,10 +16,11 @@
# 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, Iterable
from typing import Union, Iterable, List
import pyrogram
from pyrogram.api import functions, types
from ...ext import BaseClient
@ -32,7 +33,7 @@ class ForwardMessages(BaseClient):
disable_notification: bool = None,
as_copy: bool = False,
remove_caption: bool = False
) -> "pyrogram.Messages":
) -> List["pyrogram.Message"]:
"""Forward messages of any kind.
Parameters:
@ -64,9 +65,9 @@ class ForwardMessages(BaseClient):
Defaults to False.
Returns:
:obj:`Message` | :obj:`Messages`: In case *message_ids* was an integer, the single forwarded message is
returned, otherwise, in case *message_ids* was an iterable, the returned value will be an object containing
a list of messages, even if such iterable contained just a single element.
:obj:`Message` | List of :obj:`Message`: In case *message_ids* was an integer, the single forwarded message
is returned, otherwise, in case *message_ids* was an iterable, the returned value will be a list of
messages, even if such iterable contained just a single element.
Raises:
RPCError: In case of a Telegram RPC error.
@ -79,9 +80,9 @@ class ForwardMessages(BaseClient):
forwarded_messages = []
for chunk in [message_ids[i:i + 200] for i in range(0, len(message_ids), 200)]:
messages = await self.get_messages(chat_id=from_chat_id, message_ids=chunk) # type: pyrogram.Messages
messages = await self.get_messages(chat_id=from_chat_id, message_ids=chunk)
for message in messages.messages:
for message in messages:
forwarded_messages.append(
await message.forward(
chat_id,
@ -91,11 +92,7 @@ class ForwardMessages(BaseClient):
)
)
return pyrogram.Messages(
client=self,
total_count=len(forwarded_messages),
messages=forwarded_messages
) if is_iterable else forwarded_messages[0]
return pyrogram.List(forwarded_messages) if is_iterable else forwarded_messages[0]
else:
r = await self.send(
functions.messages.ForwardMessages(
@ -116,13 +113,9 @@ class ForwardMessages(BaseClient):
if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)):
forwarded_messages.append(
await pyrogram.Message._parse(
self, i.message,
users, chats
self, i.message,
users, chats
)
)
)
return pyrogram.Messages(
client=self,
total_count=len(forwarded_messages),
messages=forwarded_messages
) if is_iterable else forwarded_messages[0]
return pyrogram.List(forwarded_messages) if is_iterable else forwarded_messages[0]

View File

@ -18,11 +18,13 @@
import asyncio
import logging
from typing import Union
from typing import Union, List
import pyrogram
from pyrogram.api import functions
from pyrogram.client.ext import utils
from pyrogram.errors import FloodWait
from ...ext import BaseClient
log = logging.getLogger(__name__)
@ -37,7 +39,7 @@ class GetHistory(BaseClient):
offset_id: int = 0,
offset_date: int = 0,
reverse: bool = False
) -> "pyrogram.Messages":
) -> List["pyrogram.Message"]:
"""Retrieve a chunk of the history of a chat.
You can get up to 100 messages at once.
@ -67,15 +69,17 @@ class GetHistory(BaseClient):
Pass True to retrieve the messages in reversed order (from older to most recent).
Returns:
:obj:`Messages` - On success, an object containing a list of the retrieved messages.
List of :obj:`Message` - On success, a list of the retrieved messages is returned.
Raises:
RPCError: In case of a Telegram RPC error.
"""
offset_id = offset_id or (1 if reverse else 0)
while True:
try:
messages = await pyrogram.Messages._parse(
messages = await utils.parse_messages(
self,
await self.send(
functions.messages.GetHistory(
@ -97,6 +101,6 @@ class GetHistory(BaseClient):
break
if reverse:
messages.messages.reverse()
messages.reverse()
return messages

View File

@ -17,12 +17,10 @@
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
import logging
import time
from typing import Union
from pyrogram.api import types, functions
from pyrogram.client.ext import BaseClient
from pyrogram.errors import FloodWait
log = logging.getLogger(__name__)
@ -51,34 +49,20 @@ class GetHistoryCount(BaseClient):
RPCError: In case of a Telegram RPC error.
"""
peer = await self.resolve_peer(chat_id)
r = await self.send(
functions.messages.GetHistory(
peer=await self.resolve_peer(chat_id),
offset_id=0,
offset_date=0,
add_offset=0,
limit=1,
max_id=0,
min_id=0,
hash=0
)
)
if not isinstance(peer, types.InputPeerChannel):
offset = 0
limit = 100
while True:
try:
r = await self.send(
functions.messages.GetHistory(
peer=peer,
offset_id=1,
offset_date=0,
add_offset=-offset - limit,
limit=limit,
max_id=0,
min_id=0,
hash=0
)
)
except FloodWait as e:
log.warning("Sleeping for {}s".format(e.x))
time.sleep(e.x)
continue
if not r.messages:
return offset
offset += len(r.messages)
return (await self.get_history(chat_id=chat_id, limit=1)).total_count
if isinstance(r, types.messages.Messages):
return len(r.messages)
else:
return r.count

View File

@ -18,12 +18,13 @@
import asyncio
import logging
from typing import Union, Iterable
from typing import Union, Iterable, List
import pyrogram
from pyrogram.api import functions, types
from pyrogram.errors import FloodWait
from ...ext import BaseClient
from ...ext import BaseClient, utils
log = logging.getLogger(__name__)
@ -35,7 +36,7 @@ class GetMessages(BaseClient):
message_ids: Union[int, Iterable[int]] = None,
reply_to_message_ids: Union[int, Iterable[int]] = None,
replies: int = 1
) -> Union["pyrogram.Message", "pyrogram.Messages"]:
) -> Union["pyrogram.Message", List["pyrogram.Message"]]:
"""Get one or more messages that belong to a specific chat.
You can retrieve up to 200 messages at once.
@ -60,9 +61,9 @@ class GetMessages(BaseClient):
Defaults to 1.
Returns:
:obj:`Message` | :obj:`Messages`: In case *message_ids* was an integer, the single requested message is
returned, otherwise, in case *message_ids* was an iterable, the returned value will be an object containing
a list of messages, even if such iterable contained just a single element.
:obj:`Message` | List of :obj:`Message`: In case *message_ids* was an integer, the single requested message is
returned, otherwise, in case *message_ids* was an iterable, the returned value will be a list of messages,
even if such iterable contained just a single element.
Raises:
RPCError: In case of a Telegram RPC error.
@ -99,6 +100,6 @@ class GetMessages(BaseClient):
else:
break
messages = await pyrogram.Messages._parse(self, r, replies)
messages = await utils.parse_messages(self, r, replies=replies)
return messages if is_iterable else messages.messages[0]
return messages if is_iterable else messages[0]

View File

@ -18,9 +18,9 @@
from typing import Union, Optional, AsyncGenerator
import pyrogram
from async_generator import async_generator, yield_
import pyrogram
from ...ext import BaseClient
@ -76,14 +76,14 @@ class IterHistory(BaseClient):
limit = min(100, total)
while True:
messages = (await self.get_history(
messages = await self.get_history(
chat_id=chat_id,
limit=limit,
offset=offset,
offset_id=offset_id,
offset_date=offset_date,
reverse=reverse
)).messages
)
if not messages:
return

View File

@ -38,7 +38,7 @@ class SendMediaGroup(BaseClient):
media: List[Union["pyrogram.InputMediaPhoto", "pyrogram.InputMediaVideo"]],
disable_notification: bool = None,
reply_to_message_id: int = None
):
) -> List["pyrogram.Message"]:
"""Send a group of photos or videos as an album.
Parameters:
@ -58,7 +58,7 @@ class SendMediaGroup(BaseClient):
If the message is a reply, ID of the original message.
Returns:
:obj:`Messages`: On success, an object is returned containing all the single messages sent.
List of :obj:`Message`: On success, a list of the sent messages is returned.
Raises:
RPCError: In case of a Telegram RPC error.
@ -158,7 +158,7 @@ class SendMediaGroup(BaseClient):
else:
break
return await pyrogram.Messages._parse(
return await utils.parse_messages(
self,
types.messages.Messages(
messages=[m.message for m in filter(

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/>.
from .block_user import BlockUser
from .delete_profile_photos import DeleteProfilePhotos
from .get_me import GetMe
from .get_profile_photos import GetProfilePhotos
@ -24,10 +25,12 @@ from .get_user_dc import GetUserDC
from .get_users import GetUsers
from .iter_profile_photos import IterProfilePhotos
from .set_profile_photo import SetProfilePhoto
from .unblock_user import UnblockUser
from .update_username import UpdateUsername
class Users(
BlockUser,
GetProfilePhotos,
SetProfilePhoto,
DeleteProfilePhotos,
@ -36,6 +39,7 @@ class Users(
UpdateUsername,
GetProfilePhotosCount,
GetUserDC,
IterProfilePhotos
IterProfilePhotos,
UnblockUser
):
pass

View File

@ -0,0 +1,45 @@
# 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 BlockUser(BaseClient):
async def block_user(
self,
user_id: Union[int, str]
) -> bool:
"""Block a user.
Returns:
``bool``: True on success
Raises:
RPCError: In case of Telegram RPC Error.
"""
return bool(
await self.send(
functions.contact.Block(
id=await self.resolve_peer(user_id)
)
)
)

View File

@ -16,10 +16,12 @@
# 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 typing import Union, List
import pyrogram
from pyrogram.api import functions, types
from pyrogram.client.ext import utils
from ...ext import BaseClient
@ -29,7 +31,7 @@ class GetProfilePhotos(BaseClient):
chat_id: Union[int, str],
offset: int = 0,
limit: int = 100
) -> "pyrogram.ProfilePhotos":
) -> List["pyrogram.Photo"]:
"""Get a list of profile pictures for a user or a chat.
Parameters:
@ -47,27 +49,15 @@ class GetProfilePhotos(BaseClient):
Values between 1100 are accepted. Defaults to 100.
Returns:
:obj:`ProfilePhotos`: On success, an object containing a list of the profile photos is returned.
List of :obj:`Photo`: On success, a list of profile photos is returned.
Raises:
RPCError: In case of a Telegram RPC error.
"""
peer_id = await self.resolve_peer(chat_id)
if isinstance(peer_id, types.InputPeerUser):
return pyrogram.ProfilePhotos._parse(
self,
await self.send(
functions.photos.GetUserPhotos(
user_id=peer_id,
offset=offset,
max_id=0,
limit=limit
)
)
)
else:
new_chat_photos = pyrogram.Messages._parse(
if isinstance(peer_id, types.InputPeerChannel):
r = await utils.parse_messages(
self,
await self.send(
functions.messages.Search(
@ -86,7 +76,15 @@ class GetProfilePhotos(BaseClient):
)
)
return pyrogram.ProfilePhotos(
total_count=new_chat_photos.total_count,
profile_photos=[m.new_chat_photo for m in new_chat_photos.messages][:limit]
return pyrogram.List([message.new_chat_photo for message in r][:limit])
else:
r = await self.send(
functions.photos.GetUserPhotos(
user_id=peer_id,
offset=offset,
max_id=0,
limit=limit
)
)
return pyrogram.List(pyrogram.Photo._parse(self, photo) for photo in r.photos)

View File

@ -18,6 +18,8 @@
from typing import Union
from pyrogram.api import functions, types
from ...ext import BaseClient
@ -38,4 +40,28 @@ class GetProfilePhotosCount(BaseClient):
RPCError: In case of a Telegram RPC error.
"""
return await self.get_profile_photos(chat_id, limit=1).total_count
peer_id = await self.resolve_peer(chat_id)
if isinstance(peer_id, types.InputPeerChannel):
r = await self.send(
functions.messages.GetSearchCounters(
peer=peer_id,
filters=[types.InputMessagesFilterChatPhotos()],
)
)
return r[0].count
else:
r = await self.send(
functions.photos.GetUserPhotos(
user_id=peer_id,
offset=0,
max_id=0,
limit=1
)
)
if isinstance(r, types.photos.Photos):
return len(r.photos)
else:
return r.count

View File

@ -24,7 +24,13 @@ from ...ext import BaseClient
class GetUserDC(BaseClient):
async def get_user_dc(self, user_id: Union[int, str]) -> Union[int, None]:
"""Get the assigned data center (DC) of a user.
"""Get the assigned DC (data center) of a user.
.. note::
This information is approximate: it is based on where Telegram stores a user profile pictures and does not
by any means tell you the user location (i.e. a user might travel far away, but will still connect to its
assigned DC). More info at `FAQs <../faq#what-are-the-ip-addresses-of-telegram-data-centers>`_.
Parameters:
user_id (``int`` | ``str``):
@ -33,7 +39,8 @@ class GetUserDC(BaseClient):
For a contact that exists in your Telegram address book you can use his phone number (str).
Returns:
``int`` | ``None``: The DC identifier as integer, or None in case it wasn't possible to get it.
``int`` | ``None``: The DC identifier as integer, or None in case it wasn't possible to get it (i.e. the
user has no profile picture or has the privacy setting enabled).
Raises:
RPCError: In case of a Telegram RPC error.

View File

@ -57,7 +57,7 @@ class GetUsers(BaseClient):
)
)
users = []
users = pyrogram.List()
for i in r:
users.append(pyrogram.User._parse(self, i))

View File

@ -16,11 +16,11 @@
# 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, Generator
from async_generator import async_generator, yield_
from typing import Union, AsyncGenerator, Optional
import pyrogram
from async_generator import async_generator, yield_
from ...ext import BaseClient
@ -31,7 +31,7 @@ class IterProfilePhotos(BaseClient):
chat_id: Union[int, str],
offset: int = 0,
limit: int = 0,
) -> Generator["pyrogram.Photo", None, None]:
) -> Optional[AsyncGenerator["pyrogram.Message", None]]:
"""Iterate through a chat or a user profile photos sequentially.
This convenience method does the same as repeatedly calling :meth:`~Client.get_profile_photos` in a loop, thus
@ -62,11 +62,11 @@ class IterProfilePhotos(BaseClient):
limit = min(100, total)
while True:
photos = self.get_profile_photos(
photos = await self.get_profile_photos(
chat_id=chat_id,
offset=offset,
limit=limit
).photos
)
if not photos:
return

View File

@ -0,0 +1,45 @@
# 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 UnblockUser(BaseClient):
async def unblock_user(
self,
user_id: Union[int, str]
) -> bool:
"""Unblock a user.
Returns:
``bool``: True on success
Raises:
RPCError: In case of Telegram RPC Error.
"""
return bool(
await self.send(
functions.contact.Unblock(
id=await self.resolve_peer(user_id)
)
)
)

View File

@ -0,0 +1,21 @@
# 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 .memory_storage import MemoryStorage
from .file_storage import FileStorage
from .storage import Storage

View File

@ -0,0 +1,110 @@
# 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 base64
import json
import logging
import sqlite3
from pathlib import Path
from threading import Lock
from .memory_storage import MemoryStorage
log = logging.getLogger(__name__)
class FileStorage(MemoryStorage):
FILE_EXTENSION = ".session"
def __init__(self, name: str, workdir: Path):
super().__init__(name)
self.workdir = workdir
self.database = workdir / (self.name + self.FILE_EXTENSION)
self.conn = None # type: sqlite3.Connection
self.lock = Lock()
# noinspection PyAttributeOutsideInit
def migrate_from_json(self, session_json: dict):
self.open()
self.dc_id = session_json["dc_id"]
self.test_mode = session_json["test_mode"]
self.auth_key = base64.b64decode("".join(session_json["auth_key"]))
self.user_id = session_json["user_id"]
self.date = session_json.get("date", 0)
self.is_bot = session_json.get("is_bot", False)
peers_by_id = session_json.get("peers_by_id", {})
peers_by_phone = session_json.get("peers_by_phone", {})
peers = {}
for k, v in peers_by_id.items():
if v is None:
type_ = "group"
elif k.startswith("-100"):
type_ = "channel"
else:
type_ = "user"
peers[int(k)] = [int(k), int(v) if v is not None else None, type_, None, None]
for k, v in peers_by_phone.items():
peers[v][4] = k
# noinspection PyTypeChecker
self.update_peers(peers.values())
def open(self):
path = self.database
file_exists = path.is_file()
if file_exists:
try:
with open(path, encoding="utf-8") as f:
session_json = json.load(f)
except ValueError:
pass
else:
log.warning("JSON session storage detected! Converting it into an SQLite session storage...")
path.rename(path.name + ".OLD")
log.warning('The old session file has been renamed to "{}.OLD"'.format(path.name))
self.migrate_from_json(session_json)
log.warning("Done! The session has been successfully converted from JSON to SQLite storage")
return
if Path(path.name + ".OLD").is_file():
log.warning('Old session file detected: "{}.OLD". You can remove this file now'.format(path.name))
self.conn = sqlite3.connect(
path,
timeout=1,
check_same_thread=False
)
if not file_exists:
self.create()
with self.conn:
self.conn.execute("VACUUM")

View File

@ -0,0 +1,241 @@
# 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 base64
import inspect
import logging
import sqlite3
import struct
import time
from pathlib import Path
from threading import Lock
from typing import List, Tuple
from pyrogram.api import types
from pyrogram.client.storage.storage import Storage
log = logging.getLogger(__name__)
class MemoryStorage(Storage):
SCHEMA_VERSION = 1
USERNAME_TTL = 8 * 60 * 60
SESSION_STRING_FMT = ">B?256sI?"
SESSION_STRING_SIZE = 351
def __init__(self, name: str):
super().__init__(name)
self.conn = None # type: sqlite3.Connection
self.lock = Lock()
def create(self):
with self.lock, self.conn:
with open(Path(__file__).parent / "schema.sql", "r") as schema:
self.conn.executescript(schema.read())
self.conn.execute(
"INSERT INTO version VALUES (?)",
(self.SCHEMA_VERSION,)
)
self.conn.execute(
"INSERT INTO sessions VALUES (?, ?, ?, ?, ?, ?)",
(1, None, None, 0, None, None)
)
def _import_session_string(self, string_session: str):
decoded = base64.urlsafe_b64decode(string_session + "=" * (-len(string_session) % 4))
return struct.unpack(self.SESSION_STRING_FMT, decoded)
def export_session_string(self):
packed = struct.pack(
self.SESSION_STRING_FMT,
self.dc_id,
self.test_mode,
self.auth_key,
self.user_id,
self.is_bot
)
return base64.urlsafe_b64encode(packed).decode().rstrip("=")
# noinspection PyAttributeOutsideInit
def open(self):
self.conn = sqlite3.connect(":memory:", check_same_thread=False)
self.create()
if self.name != ":memory:":
imported_session_string = self._import_session_string(self.name)
self.dc_id, self.test_mode, self.auth_key, self.user_id, self.is_bot = imported_session_string
self.date = 0
self.name = ":memory:" + str(self.user_id or "<unknown>")
# noinspection PyAttributeOutsideInit
def save(self):
self.date = int(time.time())
with self.lock:
self.conn.commit()
def close(self):
with self.lock:
self.conn.close()
def update_peers(self, peers: List[Tuple[int, int, str, str, str]]):
with self.lock:
self.conn.executemany(
"REPLACE INTO peers (id, access_hash, type, username, phone_number)"
"VALUES (?, ?, ?, ?, ?)",
peers
)
def clear_peers(self):
with self.lock, self.conn:
self.conn.execute(
"DELETE FROM peers"
)
@staticmethod
def _get_input_peer(peer_id: int, access_hash: int, peer_type: str):
if peer_type in ["user", "bot"]:
return types.InputPeerUser(
user_id=peer_id,
access_hash=access_hash
)
if peer_type == "group":
return types.InputPeerChat(
chat_id=-peer_id
)
if peer_type in ["channel", "supergroup"]:
return types.InputPeerChannel(
channel_id=int(str(peer_id)[4:]),
access_hash=access_hash
)
raise ValueError("Invalid peer type")
def get_peer_by_id(self, peer_id: int):
r = self.conn.execute(
"SELECT id, access_hash, type FROM peers WHERE id = ?",
(peer_id,)
).fetchone()
if r is None:
raise KeyError("ID not found")
return self._get_input_peer(*r)
def get_peer_by_username(self, username: str):
r = self.conn.execute(
"SELECT id, access_hash, type, last_update_on FROM peers WHERE username = ?",
(username,)
).fetchone()
if r is None:
raise KeyError("Username not found")
if abs(time.time() - r[3]) > self.USERNAME_TTL:
raise KeyError("Username expired")
return self._get_input_peer(*r[:3])
def get_peer_by_phone_number(self, phone_number: str):
r = self.conn.execute(
"SELECT id, access_hash, type FROM peers WHERE phone_number = ?",
(phone_number,)
).fetchone()
if r is None:
raise KeyError("Phone number not found")
return self._get_input_peer(*r)
@property
def peers_count(self):
return self.conn.execute(
"SELECT COUNT(*) FROM peers"
).fetchone()[0]
def _get(self):
attr = inspect.stack()[1].function
return self.conn.execute(
"SELECT {} FROM sessions".format(attr)
).fetchone()[0]
def _set(self, value):
attr = inspect.stack()[1].function
with self.lock, self.conn:
self.conn.execute(
"UPDATE sessions SET {} = ?".format(attr),
(value,)
)
@property
def dc_id(self):
return self._get()
@dc_id.setter
def dc_id(self, value):
self._set(value)
@property
def test_mode(self):
return self._get()
@test_mode.setter
def test_mode(self, value):
self._set(value)
@property
def auth_key(self):
return self._get()
@auth_key.setter
def auth_key(self, value):
self._set(value)
@property
def date(self):
return self._get()
@date.setter
def date(self, value):
self._set(value)
@property
def user_id(self):
return self._get()
@user_id.setter
def user_id(self, value):
self._set(value)
@property
def is_bot(self):
return self._get()
@is_bot.setter
def is_bot(self, value):
self._set(value)

View File

@ -0,0 +1,34 @@
CREATE TABLE sessions (
dc_id INTEGER PRIMARY KEY,
test_mode INTEGER,
auth_key BLOB,
date INTEGER NOT NULL,
user_id INTEGER,
is_bot INTEGER
);
CREATE TABLE peers (
id INTEGER PRIMARY KEY,
access_hash INTEGER,
type INTEGER NOT NULL,
username TEXT,
phone_number TEXT,
last_update_on INTEGER NOT NULL DEFAULT (CAST(STRFTIME('%s', 'now') AS INTEGER))
);
CREATE TABLE version (
number INTEGER PRIMARY KEY
);
CREATE INDEX idx_peers_id ON peers (id);
CREATE INDEX idx_peers_username ON peers (username);
CREATE INDEX idx_peers_phone_number ON peers (phone_number);
CREATE TRIGGER trg_peers_last_update_on
AFTER UPDATE
ON peers
BEGIN
UPDATE peers
SET last_update_on = CAST(STRFTIME('%s', 'now') AS INTEGER)
WHERE id = NEW.id;
END;

View File

@ -0,0 +1,98 @@
# 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/>.
class Storage:
def __init__(self, name: str):
self.name = name
def open(self):
raise NotImplementedError
def save(self):
raise NotImplementedError
def close(self):
raise NotImplementedError
def update_peers(self, peers):
raise NotImplementedError
def get_peer_by_id(self, peer_id):
raise NotImplementedError
def get_peer_by_username(self, username):
raise NotImplementedError
def get_peer_by_phone_number(self, phone_number):
raise NotImplementedError
def export_session_string(self):
raise NotImplementedError
@property
def peers_count(self):
raise NotImplementedError
@property
def dc_id(self):
raise NotImplementedError
@dc_id.setter
def dc_id(self, value):
raise NotImplementedError
@property
def test_mode(self):
raise NotImplementedError
@test_mode.setter
def test_mode(self, value):
raise NotImplementedError
@property
def auth_key(self):
raise NotImplementedError
@auth_key.setter
def auth_key(self, value):
raise NotImplementedError
@property
def date(self):
raise NotImplementedError
@date.setter
def date(self, value):
raise NotImplementedError
@property
def user_id(self):
raise NotImplementedError
@user_id.setter
def user_id(self, value):
raise NotImplementedError
@property
def is_bot(self):
raise NotImplementedError
@is_bot.setter
def is_bot(self, value):
raise NotImplementedError

View File

@ -16,10 +16,12 @@
# 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 .keyboards import *
from .bots_and_keyboards import *
from .inline_mode import *
from .input_media import *
from .input_message_content import *
from .list import List
from .messages_and_media import *
from .object import Object
from .update import *
from .user_and_chats import *

View File

@ -20,7 +20,6 @@ from .callback_game import CallbackGame
from .callback_query import CallbackQuery
from .force_reply import ForceReply
from .game_high_score import GameHighScore
from .game_high_scores import GameHighScores
from .inline_keyboard_button import InlineKeyboardButton
from .inline_keyboard_markup import InlineKeyboardMarkup
from .keyboard_button import KeyboardButton
@ -28,6 +27,6 @@ from .reply_keyboard_markup import ReplyKeyboardMarkup
from .reply_keyboard_remove import ReplyKeyboardRemove
__all__ = [
"CallbackGame", "CallbackQuery", "ForceReply", "GameHighScore", "GameHighScores", "InlineKeyboardButton",
"InlineKeyboardMarkup", "KeyboardButton", "ReplyKeyboardMarkup", "ReplyKeyboardRemove"
"CallbackGame", "CallbackQuery", "ForceReply", "GameHighScore", "InlineKeyboardButton", "InlineKeyboardMarkup",
"KeyboardButton", "ReplyKeyboardMarkup", "ReplyKeyboardRemove"
]

View File

@ -172,3 +172,151 @@ class CallbackQuery(Object, Update):
url=url,
cache_time=cache_time
)
def edit_text(
self,
text: str,
parse_mode: str = "",
disable_web_page_preview: bool = None,
reply_markup: "pyrogram.InlineKeyboardMarkup" = None
) -> Union["pyrogram.Message", bool]:
"""Edit the text of messages attached to this callback query.
Bound method *edit_message_text* of :obj:`CallbackQuery`.
Parameters:
text (``str``):
New text of the message.
parse_mode (``str``, *optional*):
Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline
URLs in your message. Defaults to "markdown".
disable_web_page_preview (``bool``, *optional*):
Disables link previews for links in this message.
reply_markup (:obj:`InlineKeyboardMarkup`, *optional*):
An InlineKeyboardMarkup object.
Returns:
:obj:`Message` | ``bool``: On success, if the edited message was sent by the bot, the edited message is
returned, otherwise True is returned (message sent via the bot, as inline query result).
Raises:
RPCError: In case of a Telegram RPC error.
"""
if self.inline_message_id is None:
return self._client.edit_message_text(
chat_id=self.message.chat.id,
message_id=self.message.message_id,
text=text,
parse_mode=parse_mode,
disable_web_page_preview=disable_web_page_preview,
reply_markup=reply_markup
)
else:
return self._client.edit_inline_text(
inline_message_id=self.inline_message_id,
text=text,
parse_mode=parse_mode,
disable_web_page_preview=disable_web_page_preview,
reply_markup=reply_markup
)
def edit_caption(
self,
caption: str,
parse_mode: str = "",
reply_markup: "pyrogram.InlineKeyboardMarkup" = None
) -> Union["pyrogram.Message", bool]:
"""Edit the caption of media messages attached to this callback query.
Bound method *edit_message_caption* of :obj:`CallbackQuery`.
Parameters:
caption (``str``):
New caption of the message.
parse_mode (``str``, *optional*):
Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline
URLs in your message. Defaults to "markdown".
reply_markup (:obj:`InlineKeyboardMarkup`, *optional*):
An InlineKeyboardMarkup object.
Returns:
:obj:`Message` | ``bool``: On success, if the edited message was sent by the bot, the edited message is
returned, otherwise True is returned (message sent via the bot, as inline query result).
Raises:
RPCError: In case of a Telegram RPC error.
"""
return self.edit_text(caption, parse_mode, reply_markup)
def edit_media(
self,
media: "pyrogram.InputMedia",
reply_markup: "pyrogram.InlineKeyboardMarkup" = None
) -> Union["pyrogram.Message", bool]:
"""Edit animation, audio, document, photo or video messages attached to this callback query.
Bound method *edit_message_media* of :obj:`CallbackQuery`.
Parameters:
media (:obj:`InputMedia`):
One of the InputMedia objects describing an animation, audio, document, photo or video.
reply_markup (:obj:`InlineKeyboardMarkup`, *optional*):
An InlineKeyboardMarkup object.
Returns:
:obj:`Message` | ``bool``: On success, if the edited message was sent by the bot, the edited message is
returned, otherwise True is returned (message sent via the bot, as inline query result).
Raises:
RPCError: In case of a Telegram RPC error.
"""
if self.inline_message_id is None:
return self._client.edit_message_media(
chat_id=self.message.chat.id,
message_id=self.message.message_id,
media=media,
reply_markup=reply_markup
)
else:
return self._client.edit_inline_media(
inline_message_id=self.inline_message_id,
media=media,
reply_markup=reply_markup
)
def edit_reply_markup(
self,
reply_markup: "pyrogram.InlineKeyboardMarkup" = None
) -> Union["pyrogram.Message", bool]:
"""Edit only the reply markup of messages attached to this callback query.
Bound method *edit_message_reply_markup* of :obj:`CallbackQuery`.
Parameters:
reply_markup (:obj:`InlineKeyboardMarkup`):
An InlineKeyboardMarkup object.
Returns:
:obj:`Message` | ``bool``: On success, if the edited message was sent by the bot, the edited message is
returned, otherwise True is returned (message sent via the bot, as inline query result).
Raises:
RPCError: In case of a Telegram RPC error.
"""
if self.inline_message_id is None:
return self._client.edit_message_reply_markup(
chat_id=self.message.chat.id,
message_id=self.message.message_id,
reply_markup=reply_markup
)
else:
return self._client.edit_inline_reply_markup(
inline_message_id=self.inline_message_id,
reply_markup=reply_markup
)

View File

@ -24,7 +24,7 @@ from ..object import Object
class InputPhoneContact(Object):
"""A Phone Contact to be added in your Telegram address book.
It is intended to be used with :meth:`~Client.add_contacts() <pyrogram.Client.add_contacts>`
It is intended to be used with :meth:`~pyrogram.Client.add_contacts()`
Parameters:
phone (``str``):

View File

@ -1,60 +0,0 @@
# 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 pyrogram.client.types.object import Object
from .game_high_score import GameHighScore
class GameHighScores(Object):
"""The high scores table for a game.
Parameters:
total_count (``int``):
Total number of scores the target game has.
game_high_scores (List of :obj:`GameHighScore`):
Game scores.
"""
__slots__ = ["total_count", "game_high_scores"]
def __init__(
self,
*,
client: "pyrogram.BaseClient" = None,
total_count: int,
game_high_scores: List[GameHighScore]
):
super().__init__(client)
self.total_count = total_count
self.game_high_scores = game_high_scores
@staticmethod
def _parse(client, game_high_scores: types.messages.HighScores) -> "GameHighScores":
return GameHighScores(
total_count=len(game_high_scores.scores),
game_high_scores=[
GameHighScore._parse(client, score, game_high_scores.users)
for score in game_high_scores.scores],
client=client
)

View File

@ -24,11 +24,9 @@ from .game import Game
from .location import Location
from .message import Message
from .message_entity import MessageEntity
from .messages import Messages
from .photo import Photo
from .poll import Poll
from .poll_option import PollOption
from .profile_photos import ProfilePhotos
from .sticker import Sticker
from .stripped_thumbnail import StrippedThumbnail
from .thumbnail import Thumbnail
@ -38,7 +36,6 @@ from .video_note import VideoNote
from .voice import Voice
__all__ = [
"Animation", "Audio", "Contact", "Document", "Game", "Location", "Message", "MessageEntity", "Messages", "Photo",
"Thumbnail", "StrippedThumbnail", "Poll", "PollOption", "Sticker", "ProfilePhotos", "Venue", "Video", "VideoNote",
"Voice"
"Animation", "Audio", "Contact", "Document", "Game", "Location", "Message", "MessageEntity", "Photo", "Thumbnail",
"StrippedThumbnail", "Poll", "PollOption", "Sticker", "Venue", "Video", "VideoNote", "Voice"
]

View File

@ -651,7 +651,7 @@ class Message(Object, Update):
return parsed_message
async def reply(
async def reply_text(
self,
text: str,
quote: bool = None,
@ -661,7 +661,7 @@ class Message(Object, Update):
reply_to_message_id: int = None,
reply_markup=None
) -> "Message":
"""Bound method *reply* :obj:`Message <pyrogram.Message>`.
"""Bound method *reply_text* of :obj:`Message`.
Use as a shortcut for:
@ -676,7 +676,7 @@ class Message(Object, Update):
Example:
.. code-block:: python
message.reply("hello", quote=True)
message.reply_text("hello", quote=True)
Parameters:
text (``str``):
@ -748,7 +748,7 @@ class Message(Object, Update):
progress: callable = None,
progress_args: tuple = ()
) -> "Message":
"""Bound method *reply_animation* :obj:`Message <pyrogram.Message>`.
"""Bound method *reply_animation* :obj:`Message`.
Use as a shortcut for:
@ -882,7 +882,7 @@ class Message(Object, Update):
progress: callable = None,
progress_args: tuple = ()
) -> "Message":
"""Bound method *reply_audio* :obj:`Message <pyrogram.Message>`.
"""Bound method *reply_audio* of :obj:`Message`.
Use as a shortcut for:
@ -1010,7 +1010,7 @@ class Message(Object, Update):
"pyrogram.ForceReply"
] = None
) -> "Message":
"""Bound method *reply_cached_media* :obj:`Message <pyrogram.Message>`.
"""Bound method *reply_cached_media* of :obj:`Message`.
Use as a shortcut for:
@ -1077,7 +1077,7 @@ class Message(Object, Update):
)
async def reply_chat_action(self, action: str) -> bool:
"""Bound method *reply_chat_action* :obj:`Message <pyrogram.Message>`.
"""Bound method *reply_chat_action* of :obj:`Message`.
Use as a shortcut for:
@ -1130,7 +1130,7 @@ class Message(Object, Update):
"pyrogram.ForceReply"
] = None
) -> "Message":
"""Bound method *reply_contact* :obj:`Message <pyrogram.Message>`.
"""Bound method *reply_contact* of :obj:`Message`.
Use as a shortcut for:
@ -1217,7 +1217,7 @@ class Message(Object, Update):
progress: callable = None,
progress_args: tuple = ()
) -> "Message":
"""Bound method *reply_document* :obj:`Message <pyrogram.Message>`.
"""Bound method *reply_document* of :obj:`Message`.
Use as a shortcut for:
@ -1331,7 +1331,7 @@ class Message(Object, Update):
"pyrogram.ForceReply"
] = None
) -> "Message":
"""Bound method *reply_game* :obj:`Message <pyrogram.Message>`.
"""Bound method *reply_game* of :obj:`Message`.
Use as a shortcut for:
@ -1396,7 +1396,7 @@ class Message(Object, Update):
reply_to_message_id: int = None,
hide_via: bool = None
) -> "Message":
"""Bound method *reply_inline_bot_result* :obj:`Message <pyrogram.Message>`.
"""Bound method *reply_inline_bot_result* of :obj:`Message`.
Use as a shortcut for:
@ -1470,7 +1470,7 @@ class Message(Object, Update):
"pyrogram.ForceReply"
] = None
) -> "Message":
"""Bound method *reply_location* :obj:`Message <pyrogram.Message>`.
"""Bound method *reply_location* of :obj:`Message`.
Use as a shortcut for:
@ -1538,7 +1538,7 @@ class Message(Object, Update):
disable_notification: bool = None,
reply_to_message_id: int = None
) -> "Message":
"""Bound method *reply_media_group* :obj:`Message <pyrogram.Message>`.
"""Bound method *reply_media_group* of :obj:`Message`.
Use as a shortcut for:
@ -1610,7 +1610,7 @@ class Message(Object, Update):
progress: callable = None,
progress_args: tuple = ()
) -> "Message":
"""Bound method *reply_photo* :obj:`Message <pyrogram.Message>`.
"""Bound method *reply_photo* of :obj:`Message`.
Use as a shortcut for:
@ -1724,7 +1724,7 @@ class Message(Object, Update):
"pyrogram.ForceReply"
] = None
) -> "Message":
"""Bound method *reply_poll* :obj:`Message <pyrogram.Message>`.
"""Bound method *reply_poll* of :obj:`Message`.
Use as a shortcut for:
@ -1800,7 +1800,7 @@ class Message(Object, Update):
progress: callable = None,
progress_args: tuple = ()
) -> "Message":
"""Bound method *reply_sticker* :obj:`Message <pyrogram.Message>`.
"""Bound method *reply_sticker* of :obj:`Message`.
Use as a shortcut for:
@ -1903,7 +1903,7 @@ class Message(Object, Update):
"pyrogram.ForceReply"
] = None
) -> "Message":
"""Bound method *reply_venue* :obj:`Message <pyrogram.Message>`.
"""Bound method *reply_venue* of :obj:`Message`.
Use as a shortcut for:
@ -2005,7 +2005,7 @@ class Message(Object, Update):
progress: callable = None,
progress_args: tuple = ()
) -> "Message":
"""Bound method *reply_video* :obj:`Message <pyrogram.Message>`.
"""Bound method *reply_video* of :obj:`Message`.
Use as a shortcut for:
@ -2140,7 +2140,7 @@ class Message(Object, Update):
progress: callable = None,
progress_args: tuple = ()
) -> "Message":
"""Bound method *reply_video_note* :obj:`Message <pyrogram.Message>`.
"""Bound method *reply_video_note* of :obj:`Message`.
Use as a shortcut for:
@ -2258,7 +2258,7 @@ class Message(Object, Update):
progress: callable = None,
progress_args: tuple = ()
) -> "Message":
"""Bound method *reply_voice* :obj:`Message <pyrogram.Message>`.
"""Bound method *reply_voice* of :obj:`Message`.
Use as a shortcut for:
@ -2356,19 +2356,14 @@ class Message(Object, Update):
progress_args=progress_args
)
async def edit(
async def edit_text(
self,
text: str,
parse_mode: str = "",
disable_web_page_preview: bool = None,
reply_markup: Union[
"pyrogram.InlineKeyboardMarkup",
"pyrogram.ReplyKeyboardMarkup",
"pyrogram.ReplyKeyboardRemove",
"pyrogram.ForceReply"
] = None
reply_markup: "pyrogram.InlineKeyboardMarkup" = None
) -> "Message":
"""Bound method *edit* :obj:`Message <pyrogram.Message>`.
"""Bound method *edit_text* of :obj:`Message`.
Use as a shortcut for:
@ -2383,7 +2378,7 @@ class Message(Object, Update):
Example:
.. code-block:: python
message.edit("hello")
message.edit_text("hello")
Parameters:
text (``str``):
@ -2418,14 +2413,9 @@ class Message(Object, Update):
self,
caption: str,
parse_mode: str = "",
reply_markup: Union[
"pyrogram.InlineKeyboardMarkup",
"pyrogram.ReplyKeyboardMarkup",
"pyrogram.ReplyKeyboardRemove",
"pyrogram.ForceReply"
] = None
reply_markup: "pyrogram.InlineKeyboardMarkup" = None
) -> "Message":
"""Bound method *edit_caption* :obj:`Message <pyrogram.Message>`.
"""Bound method *edit_caption* of :obj:`Message`.
Use as a shortcut for:
@ -2468,7 +2458,7 @@ class Message(Object, Update):
)
async def edit_media(self, media: InputMedia, reply_markup: "pyrogram.InlineKeyboardMarkup" = None) -> "Message":
"""Bound method *edit_media* :obj:`Message <pyrogram.Message>`.
"""Bound method *edit_media* of :obj:`Message`.
Use as a shortcut for:
@ -2486,7 +2476,7 @@ class Message(Object, Update):
message.edit_media(media)
Parameters:
media (:obj:`InputMediaAnimation` | :obj:`InputMediaAudio` | :obj:`InputMediaDocument` | :obj:`InputMediaPhoto` | :obj:`InputMediaVideo`)
media (:obj:`InputMedia`):
One of the InputMedia objects describing an animation, audio, document, photo or video.
reply_markup (:obj:`InlineKeyboardMarkup`, *optional*):
@ -2506,7 +2496,7 @@ class Message(Object, Update):
)
async def edit_reply_markup(self, reply_markup: "pyrogram.InlineKeyboardMarkup" = None) -> "Message":
"""Bound method *edit_reply_markup* :obj:`Message <pyrogram.Message>`.
"""Bound method *edit_reply_markup* of :obj:`Message`.
Use as a shortcut for:
@ -2547,7 +2537,7 @@ class Message(Object, Update):
as_copy: bool = False,
remove_caption: bool = False
) -> "Message":
"""Bound method *forward* :obj:`Message <pyrogram.Message>`.
"""Bound method *forward* of :obj:`Message`.
Use as a shortcut for:
@ -2617,7 +2607,7 @@ class Message(Object, Update):
)
if self.photo:
file_id = self.photo.sizes[-1].file_id
file_id = self.photo.file_id
elif self.audio:
file_id = self.audio.file_id
elif self.document:
@ -2690,7 +2680,7 @@ class Message(Object, Update):
)
async def delete(self, revoke: bool = True):
"""Bound method *delete* :obj:`Message <pyrogram.Message>`.
"""Bound method *delete* of :obj:`Message`.
Use as a shortcut for:
@ -2726,7 +2716,7 @@ class Message(Object, Update):
)
async def click(self, x: int or str, y: int = 0, quote: bool = None, timeout: int = 10):
"""Bound method *click* :obj:`Message <pyrogram.Message>`.
"""Bound method *click* of :obj:`Message`.
Use as a shortcut for clicking a button attached to the message instead of:
@ -2853,7 +2843,7 @@ class Message(Object, Update):
progress: callable = None,
progress_args: tuple = ()
) -> str:
"""Bound method *download* :obj:`Message <pyrogram.Message>`.
"""Bound method *download* of :obj:`Message`.
Use as a shortcut for:
@ -2902,7 +2892,7 @@ class Message(Object, Update):
)
async def pin(self, disable_notification: bool = None) -> "Message":
"""Bound method *pin* :obj:`Message <pyrogram.Message>`.
"""Bound method *pin* of :obj:`Message`.
Use as a shortcut for:

View File

@ -31,8 +31,8 @@ class MessageEntity(Object):
type (``str``):
Type of the entity.
Can be "mention" (@username), "hashtag", "cashtag", "bot_command", "url", "email", "phone_number", "bold"
(bold text), italic (italic text), "code" (monowidth string), "pre" (monowidth block), "text_link"
(for clickable text URLs), "text_mention" (for users without usernames).
(bold text), "italic" (italic text), "code" (monowidth string), "pre" (monowidth block), "text_link"
(for clickable text URLs), "text_mention" (for custom text mentions based on users' identifiers).
offset (``int``):
Offset in UTF-16 code units to the start of the entity.

View File

@ -1,174 +0,0 @@
# 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, Union
import pyrogram
from pyrogram.api import types
from .message import Message
from ..object import Object
from ..update import Update
from ..user_and_chats import Chat
class Messages(Object, Update):
"""Contains a chat's messages.
Parameters:
total_count (``int``):
Total number of messages the target chat has.
messages (List of :obj:`Message`):
Requested messages.
"""
__slots__ = ["total_count", "messages"]
def __init__(
self,
*,
client: "pyrogram.BaseClient" = None,
total_count: int,
messages: List[Message]
):
super().__init__(client)
self.total_count = total_count
self.messages = messages
@staticmethod
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}
total_count = getattr(messages, "count", len(messages.messages))
if not messages.messages:
return Messages(
total_count=total_count,
messages=[],
client=client
)
# TODO: WTF! Py 3.5 doesn't support await inside comprehensions
parsed_messages = []
for message in messages.messages:
parsed_messages.append(await Message._parse(client, message, users, chats, replies=0))
if replies:
messages_with_replies = {i.id: getattr(i, "reply_to_msg_id", None) for i in messages.messages}
reply_message_ids = [i[0] for i in filter(lambda x: x[1] is not None, messages_with_replies.items())]
if reply_message_ids:
reply_messages = (await client.get_messages(
parsed_messages[0].chat.id,
reply_to_message_ids=reply_message_ids,
replies=replies - 1
)).messages
for message in parsed_messages:
reply_id = messages_with_replies[message.message_id]
for reply in reply_messages:
if reply.message_id == reply_id:
message.reply_to_message = reply
return Messages(
total_count=total_count,
messages=parsed_messages,
client=client
)
@staticmethod
def _parse_deleted(client, update) -> "Messages":
messages = update.messages
channel_id = getattr(update, "channel_id", None)
parsed_messages = []
for message in messages:
parsed_messages.append(
Message(
message_id=message,
chat=Chat(
id=int("-100" + str(channel_id)),
type="channel",
client=client
) if channel_id is not None else None,
client=client
)
)
return Messages(
total_count=len(parsed_messages),
messages=parsed_messages,
client=client
)
def forward(
self,
chat_id: Union[int, str],
disable_notification: bool = None,
as_copy: bool = False,
remove_caption: bool = False
):
"""Bound method *forward* of :obj:`Message`.
Parameters:
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).
disable_notification (``bool``, *optional*):
Sends messages silently.
Users will receive a notification with no sound.
as_copy (``bool``, *optional*):
Pass True to forward messages without the forward header (i.e.: send a copy of the message content).
Defaults to False.
remove_caption (``bool``, *optional*):
If set to True and *as_copy* is enabled as well, media captions are not preserved when copying the
message. Has no effect if *as_copy* is not enabled.
Defaults to False.
Returns:
On success, a :obj:`Messages` containing forwarded messages is returned.
Raises:
RPCError: In case of a Telegram RPC error.
"""
forwarded_messages = []
for message in self.messages:
forwarded_messages.append(
message.forward(
chat_id=chat_id,
as_copy=as_copy,
disable_notification=disable_notification,
remove_caption=remove_caption
)
)
return Messages(
total_count=len(forwarded_messages),
messages=forwarded_messages,
client=self._client
)

View File

@ -1,57 +0,0 @@
# 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 .photo import Photo
from ..object import Object
class ProfilePhotos(Object):
"""Contains a user's profile pictures.
Parameters:
total_count (``int``):
Total number of profile pictures the target user has.
profile_photos (List of :obj:`Photo`):
Requested profile pictures.
"""
__slots__ = ["total_count", "profile_photos"]
def __init__(
self,
*,
client: "pyrogram.BaseClient" = None,
total_count: int,
profile_photos: List[Photo]
):
super().__init__(client)
self.total_count = total_count
self.profile_photos = profile_photos
@staticmethod
def _parse(client, photos) -> "ProfilePhotos":
return ProfilePhotos(
total_count=getattr(photos, "count", len(photos.photos)),
profile_photos=[Photo._parse(client, photo) for photo in photos.photos],
client=client
)

View File

@ -95,7 +95,7 @@ class Sticker(Object):
self.width = width
self.height = height
self.emoji = emoji
self.set_name = set_name,
self.set_name = set_name
self.thumbs = thumbs
# self.mask_position = mask_position

View File

@ -18,16 +18,13 @@
from .chat import Chat
from .chat_member import ChatMember
from .chat_members import ChatMembers
from .chat_permissions import ChatPermissions
from .chat_photo import ChatPhoto
from .chat_preview import ChatPreview
from .dialog import Dialog
from .dialogs import Dialogs
from .user import User
from .user_status import UserStatus
__all__ = [
"Chat", "ChatMember", "ChatMembers", "ChatPermissions", "ChatPhoto", "ChatPreview", "Dialog", "Dialogs", "User",
"UserStatus"
"Chat", "ChatMember", "ChatPermissions", "ChatPhoto", "ChatPreview", "Dialog", "User", "UserStatus"
]

View File

@ -20,6 +20,7 @@ from typing import Union
import pyrogram
from pyrogram.api import types
from .chat_permissions import ChatPermissions
from .chat_photo import ChatPhoto
from ..object import Object
@ -36,14 +37,17 @@ class Chat(Object):
Type of chat, can be either "private", "bot", "group", "supergroup" or "channel".
is_verified (``bool``, *optional*):
True, if this chat has been verified by Telegram. Supergroups and channels only.
True, if this chat has been verified by Telegram. Supergroups, channels and bots only.
is_restricted (``bool``, *optional*):
True, if this chat has been restricted. Supergroups and channels only.
True, if this chat has been restricted. Supergroups, channels and bots only.
See *restriction_reason* for details.
is_scam (``bool``, *optional*):
True, if this chat has been flagged for scam. Supergroups and channels only.
True, if this chat has been flagged for scam. Supergroups, channels and bots only.
is_support (``bool``):
True, if this chat is part of the Telegram support team. Users and bots only.
title (``str``, *optional*):
Title, for supergroups, channels and basic group chats.
@ -92,8 +96,8 @@ class Chat(Object):
"""
__slots__ = [
"id", "type", "is_verified", "is_restricted", "is_scam", "title", "username", "first_name", "last_name",
"photo", "description", "invite_link", "pinned_message", "sticker_set_name", "can_set_sticker_set",
"id", "type", "is_verified", "is_restricted", "is_scam", "is_support", "title", "username", "first_name",
"last_name", "photo", "description", "invite_link", "pinned_message", "sticker_set_name", "can_set_sticker_set",
"members_count", "restriction_reason", "permissions"
]
@ -106,6 +110,7 @@ class Chat(Object):
is_verified: bool = None,
is_restricted: bool = None,
is_scam: bool = None,
is_support: bool = None,
title: str = None,
username: str = None,
first_name: str = None,
@ -127,6 +132,7 @@ class Chat(Object):
self.is_verified = is_verified
self.is_restricted = is_restricted
self.is_scam = is_scam
self.is_support = is_support
self.title = title
self.username = username
self.first_name = first_name
@ -148,6 +154,10 @@ class Chat(Object):
return Chat(
id=peer_id,
type="bot" if user.bot else "private",
is_verified=getattr(user, "verified", None),
is_restricted=getattr(user, "restricted", None),
is_scam=getattr(user, "scam", None),
is_support=getattr(user, "support", None),
username=user.username,
first_name=user.first_name,
last_name=user.last_name,
@ -257,3 +267,49 @@ class Chat(Object):
return Chat._parse_user_chat(client, chat)
else:
return Chat._parse_channel_chat(client, chat)
async def archive(self):
"""Bound method *archive* of :obj:`Chat`.
Use as a shortcut for:
.. code-block:: python
client.archive_chats(-100123456789)
Example:
.. code-block:: python
chat.archive()
Returns:
True on success.
Raises:
RPCError: In case of a Telegram RPC error.
"""
return await self._client.archive_chats(self.id)
async def unarchive(self):
"""Bound method *unarchive* of :obj:`Chat`.
Use as a shortcut for:
.. code-block:: python
client.unarchive_chats(-100123456789)
Example:
.. code-block:: python
chat.unarchive()
Returns:
True on success.
Raises:
RPCError: In case of a Telegram RPC error.
"""
return await self._client.unarchive_chats(self.id)

View File

@ -1,71 +0,0 @@
# 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_member import ChatMember
from ..object import Object
class ChatMembers(Object):
"""Contains information about the members list of a chat.
Parameters:
total_count (``int``):
Total number of members the chat has.
chat_members (List of :obj:`ChatMember <pyrogram.ChatMember>`):
Requested chat members.
"""
__slots__ = ["total_count", "chat_members"]
def __init__(
self,
*,
client: "pyrogram.BaseClient" = None,
total_count: int,
chat_members: List[ChatMember]
):
super().__init__(client)
self.total_count = total_count
self.chat_members = chat_members
@staticmethod
def _parse(client, members):
users = {i.id: i for i in members.users}
chat_members = []
if isinstance(members, types.channels.ChannelParticipants):
total_count = members.count
members = members.participants
else:
members = members.full_chat.participants.participants
total_count = len(members)
for member in members:
chat_members.append(ChatMember._parse(client, member, users))
return ChatMembers(
total_count=total_count,
chat_members=chat_members,
client=client
)

View File

@ -1,87 +0,0 @@
# 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 .dialog import Dialog
from ..messages_and_media import Message
from ..object import Object
class Dialogs(Object):
"""Contains a user's dialogs chunk.
Parameters:
total_count (``int``):
Total number of dialogs the user has.
dialogs (List of :obj:`Dialog`):
Requested dialogs.
"""
__slots__ = ["total_count", "dialogs"]
def __init__(
self,
*,
client: "pyrogram.BaseClient" = None,
total_count: int,
dialogs: List[Dialog]
):
super().__init__(client)
self.total_count = total_count
self.dialogs = dialogs
@staticmethod
async def _parse(client, dialogs: types.messages.Dialogs) -> "Dialogs":
users = {i.id: i for i in dialogs.users}
chats = {i.id: i for i in dialogs.chats}
messages = {}
for message in dialogs.messages:
to_id = message.to_id
if isinstance(to_id, types.PeerUser):
if message.out:
chat_id = to_id.user_id
else:
chat_id = message.from_id
elif isinstance(to_id, types.PeerChat):
chat_id = -to_id.chat_id
else:
chat_id = int("-100" + str(to_id.channel_id))
messages[chat_id] = await Message._parse(client, message, users, chats)
parsed_dialogs = []
for dialog in dialogs.dialogs:
if not isinstance(dialog, types.Dialog):
continue
parsed_dialogs.append(Dialog._parse(client, dialog, messages, users, chats))
return Dialogs(
total_count=getattr(dialogs, "count", len(dialogs.dialogs)),
dialogs=parsed_dialogs,
client=client
)

View File

@ -18,6 +18,7 @@
import pyrogram
from pyrogram.api import types
from .chat_photo import ChatPhoto
from .user_status import UserStatus
from ..object import Object
@ -52,12 +53,12 @@ class User(Object):
True, if this user has been restricted. Bots only.
See *restriction_reason* for details.
is_support (``bool``):
True, if this user is part of the Telegram support team.
is_scam (``bool``):
True, if this user has been flagged for scam.
is_support (``bool``):
True, if this user is part of the Telegram support team.
first_name (``str``):
User's or bot's first name.
@ -86,7 +87,7 @@ class User(Object):
__slots__ = [
"id", "is_self", "is_contact", "is_mutual_contact", "is_deleted", "is_bot", "is_verified", "is_restricted",
"is_support", "is_scam", "first_name", "last_name", "status", "username", "language_code", "phone_number",
"is_scam", "is_support", "first_name", "last_name", "status", "username", "language_code", "phone_number",
"photo", "restriction_reason"
]
@ -102,8 +103,8 @@ class User(Object):
is_bot: bool,
is_verified: bool,
is_restricted: bool,
is_support: bool,
is_scam: bool,
is_support: bool,
first_name: str,
last_name: str = None,
status: UserStatus = None,
@ -123,8 +124,8 @@ class User(Object):
self.is_bot = is_bot
self.is_verified = is_verified
self.is_restricted = is_restricted
self.is_support = is_support
self.is_scam = is_scam
self.is_support = is_support
self.first_name = first_name
self.last_name = last_name
self.status = status
@ -148,8 +149,8 @@ class User(Object):
is_bot=user.bot,
is_verified=user.verified,
is_restricted=user.restricted,
is_support=user.support,
is_scam=user.scam,
is_support=user.support,
first_name=user.first_name,
last_name=user.last_name,
status=UserStatus._parse(client, user.status, user.id, user.bot),
@ -160,3 +161,49 @@ class User(Object):
restriction_reason=user.restriction_reason,
client=client
)
async def archive(self):
"""Bound method *archive* of :obj:`User`.
Use as a shortcut for:
.. code-block:: python
client.archive_chats(123456789)
Example:
.. code-block:: python
user.archive()
Returns:
True on success.
Raises:
RPCError: In case of a Telegram RPC error.
"""
return await self._client.archive_chats(self.id)
async def unarchive(self):
"""Bound method *unarchive* of :obj:`User`.
Use as a shortcut for:
.. code-block:: python
client.unarchive_chats(123456789)
Example:
.. code-block:: python
user.unarchive()
Returns:
True on success.
Raises:
RPCError: In case of a Telegram RPC error.
"""
return await self._client.unarchive_chats(self.id)

View File

@ -23,10 +23,12 @@ from hashlib import sha1
from io import BytesIO
from os import urandom
import pyrogram
from pyrogram.api import functions, types
from pyrogram.api.core import TLObject, Long, Int
from pyrogram.connection import Connection
from pyrogram.crypto import AES, RSA, Prime
from .internals import MsgId
log = logging.getLogger(__name__)
@ -35,11 +37,11 @@ log = logging.getLogger(__name__)
class Auth:
MAX_RETRIES = 5
def __init__(self, dc_id: int, test_mode: bool, ipv6: bool, proxy: dict):
def __init__(self, client: "pyrogram.Client", dc_id: int):
self.dc_id = dc_id
self.test_mode = test_mode
self.ipv6 = ipv6
self.proxy = proxy
self.test_mode = client.storage.test_mode
self.ipv6 = client.ipv6
self.proxy = client.proxy
self.connection = None

View File

@ -30,6 +30,7 @@ from pyrogram.api.core import TLObject, MsgContainer, Int, Long, FutureSalt, Fut
from pyrogram.connection import Connection
from pyrogram.crypto import MTProto
from pyrogram.errors import RPCError, InternalServerError, AuthKeyDuplicated
from .internals import MsgId, MsgFactory
log = logging.getLogger(__name__)
@ -66,12 +67,14 @@ class Session:
64: "[64] invalid container"
}
def __init__(self,
client: pyrogram,
dc_id: int,
auth_key: bytes,
is_media: bool = False,
is_cdn: bool = False):
def __init__(
self,
client: pyrogram,
dc_id: int,
auth_key: bytes,
is_media: bool = False,
is_cdn: bool = False
):
if not Session.notice_displayed:
print("Pyrogram v{}, {}".format(__version__, __copyright__))
print("Licensed under the terms of the " + __license__, end="\n\n")
@ -110,7 +113,12 @@ class Session:
async def start(self):
while True:
self.connection = Connection(self.dc_id, self.client.test_mode, self.client.ipv6, self.client.proxy)
self.connection = Connection(
self.dc_id,
self.client.storage.test_mode,
self.client.ipv6,
self.client.proxy
)
try:
await self.connection.connect()

View File

@ -168,12 +168,13 @@ setup(
python_requires="~=3.4",
packages=find_packages(exclude=["compiler*"]),
package_data={
"pyrogram.client.ext": ["mime.types"]
"pyrogram.client.ext": ["mime.types"],
"pyrogram.client.storage": ["schema.sql"]
},
zip_safe=False,
install_requires=requires,
extras_require={
"fast": ["tgcrypto==1.1.1"]
"fast": ["tgcrypto==1.2.0"]
},
cmdclass={
"clean": Clean,