mirror of
https://github.com/TeamPGM/pyrogram.git
synced 2024-11-17 13:21:52 +00:00
Merge develop -> asyncio
This commit is contained in:
commit
a8dfe61f7e
4
.github/ISSUE_TEMPLATE/bug_report.md
vendored
4
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -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).
|
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -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.
|
||||
|
6
.github/ISSUE_TEMPLATE/question.md
vendored
6
.github/ISSUE_TEMPLATE/question.md
vendored
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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
|
|
@ -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
|
|
@ -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")
|
||||
|
||||
|
@ -1,3 +1,8 @@
|
||||
User-agent: *
|
||||
|
||||
Allow: /
|
||||
|
||||
Disallow: /dev/*
|
||||
Disallow: /old/*
|
||||
|
||||
Sitemap: https://docs.pyrogram.org/sitemap.xml
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -1,5 +1,8 @@
|
||||
Update Filters
|
||||
==============
|
||||
|
||||
Details
|
||||
-------
|
||||
|
||||
.. autoclass:: pyrogram.Filters
|
||||
:members:
|
||||
|
@ -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()
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
||||
|
@ -130,6 +130,7 @@ Meta
|
||||
topics/auto-auth
|
||||
topics/session-settings
|
||||
topics/tgcrypto
|
||||
topics/storage-engines
|
||||
topics/text-formatting
|
||||
topics/serialize
|
||||
topics/proxy
|
||||
|
95
docs/source/topics/storage-engines.rst
Normal file
95
docs/source/topics/storage-engines.rst
Normal 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())
|
||||
|
@ -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>"
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
|
||||
|
@ -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
|
||||
))
|
||||
|
@ -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]
|
||||
)
|
||||
|
@ -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:
|
||||
|
@ -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])
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
59
pyrogram/client/methods/chats/archive_chats.py
Normal file
59
pyrogram/client/methods/chats/archive_chats.py
Normal 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
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
59
pyrogram/client/methods/chats/unarchive_chats.py
Normal file
59
pyrogram/client/methods/chats/unarchive_chats.py
Normal 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
|
@ -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.
|
||||
"""
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
58
pyrogram/client/methods/messages/edit_inline_caption.py
Normal file
58
pyrogram/client/methods/messages/edit_inline_caption.py
Normal 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
|
||||
)
|
104
pyrogram/client/methods/messages/edit_inline_media.py
Normal file
104
pyrogram/client/methods/messages/edit_inline_media.py
Normal 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)
|
||||
)
|
||||
)
|
50
pyrogram/client/methods/messages/edit_inline_reply_markup.py
Normal file
50
pyrogram/client/methods/messages/edit_inline_reply_markup.py
Normal 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,
|
||||
)
|
||||
)
|
67
pyrogram/client/methods/messages/edit_inline_text.py
Normal file
67
pyrogram/client/methods/messages/edit_inline_text.py
Normal 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)
|
||||
)
|
||||
)
|
@ -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}
|
||||
)
|
||||
|
@ -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)
|
||||
)
|
||||
)
|
||||
|
@ -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,
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -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``):
|
||||
|
@ -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]
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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]
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
45
pyrogram/client/methods/users/block_user.py
Normal file
45
pyrogram/client/methods/users/block_user.py
Normal 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)
|
||||
)
|
||||
)
|
||||
)
|
@ -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 1—100 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)
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -57,7 +57,7 @@ class GetUsers(BaseClient):
|
||||
)
|
||||
)
|
||||
|
||||
users = []
|
||||
users = pyrogram.List()
|
||||
|
||||
for i in r:
|
||||
users.append(pyrogram.User._parse(self, i))
|
||||
|
@ -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
|
||||
|
45
pyrogram/client/methods/users/unblock_user.py
Normal file
45
pyrogram/client/methods/users/unblock_user.py
Normal 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)
|
||||
)
|
||||
)
|
||||
)
|
21
pyrogram/client/storage/__init__.py
Normal file
21
pyrogram/client/storage/__init__.py
Normal 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
|
110
pyrogram/client/storage/file_storage.py
Normal file
110
pyrogram/client/storage/file_storage.py
Normal 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")
|
241
pyrogram/client/storage/memory_storage.py
Normal file
241
pyrogram/client/storage/memory_storage.py
Normal 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)
|
34
pyrogram/client/storage/schema.sql
Normal file
34
pyrogram/client/storage/schema.sql
Normal 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;
|
98
pyrogram/client/storage/storage.py
Normal file
98
pyrogram/client/storage/storage.py
Normal 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
|
@ -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 *
|
||||
|
@ -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"
|
||||
]
|
@ -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
|
||||
)
|
@ -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``):
|
||||
|
@ -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
|
||||
)
|
@ -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"
|
||||
]
|
||||
|
@ -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:
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
)
|
@ -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
|
||||
)
|
@ -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
|
||||
|
||||
|
@ -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"
|
||||
]
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
)
|
@ -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
|
||||
)
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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()
|
||||
|
5
setup.py
5
setup.py
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user