Merge branch 'develop' into asyncio

# Conflicts:
#	pyrogram/client/client.py
#	pyrogram/client/methods/users/delete_user_profile_photos.py
This commit is contained in:
Dan 2018-10-16 12:20:46 +02:00
commit f45e3377a9
34 changed files with 613 additions and 98 deletions

View File

@ -506,6 +506,7 @@ def start():
f.write("\n 0xb0700028: \"pyrogram.client.types.Dialog\",") f.write("\n 0xb0700028: \"pyrogram.client.types.Dialog\",")
f.write("\n 0xb0700029: \"pyrogram.client.types.Dialogs\",") f.write("\n 0xb0700029: \"pyrogram.client.types.Dialogs\",")
f.write("\n 0xb0700030: \"pyrogram.client.types.ChatMembers\",") f.write("\n 0xb0700030: \"pyrogram.client.types.ChatMembers\",")
f.write("\n 0xb0700031: \"pyrogram.client.types.UserStatus\"")
f.write("\n}\n") f.write("\n}\n")

View File

@ -23,13 +23,13 @@ Welcome to Pyrogram
<a href="https://t.me/PyrogramChat"> <a href="https://t.me/PyrogramChat">
Community Community
</a> </a>
<br><br> <br>
<a href="https://github.com/pyrogram/pyrogram/blob/master/compiler/api/source/main_api.tl"> <a href="https://github.com/pyrogram/pyrogram/blob/master/compiler/api/source/main_api.tl">
<img src="https://img.shields.io/badge/SCHEME-LAYER%2082-eda738.svg?longCache=true&style=for-the-badge&colorA=262b30" <img src="https://img.shields.io/badge/schema-layer%2082-eda738.svg?longCache=true&colorA=262b30"
alt="Scheme Layer"> alt="Scheme Layer">
</a> </a>
<a href="https://github.com/pyrogram/tgcrypto"> <a href="https://github.com/pyrogram/tgcrypto">
<img src="https://img.shields.io/badge/TGCRYPTO-V1.1.1-eda738.svg?longCache=true&style=for-the-badge&colorA=262b30" <img src="https://img.shields.io/badge/tgcrypto-v1.1.1-eda738.svg?longCache=true&colorA=262b30"
alt="TgCrypto"> alt="TgCrypto">
</a> </a>
</p> </p>
@ -49,14 +49,14 @@ Welcome to Pyrogram
app.run() app.run()
Welcome to Pyrogram's Documentation! Here you can find resources for learning how to use the library. Welcome to Pyrogram's Documentation! Here you can find resources for learning how to use the library.
Contents are organized by topic and can be accessed from the sidebar, or by following them one by one using the Next Contents are organized into self-contained topics and can be accessed from the sidebar, or by following them in order
button at the end of each page. But first, here's a brief overview of what is this all about. using the Next button at the end of each page. But first, here's a brief overview of what is this all about.
About About
----- -----
**Pyrogram** is a brand new Telegram_ Client Library written from the ground up in Python and C. It can be used for building **Pyrogram** is a brand new Telegram_ Client Library written from the ground up in Python and C. It can be used for
custom Telegram applications that interact with the MTProto API as both User and Bot. building custom Telegram applications that interact with the MTProto API as both User and Bot.
Features Features
-------- --------
@ -64,7 +64,7 @@ Features
- **Easy to use**: You can easily install Pyrogram using pip and start building your app right away. - **Easy to use**: You can easily install Pyrogram using pip and start building your app right away.
- **High-level**: The low-level details of MTProto are abstracted and automatically handled. - **High-level**: The low-level details of MTProto are abstracted and automatically handled.
- **Fast**: Crypto parts are boosted up by TgCrypto_, a high-performance library written in pure C. - **Fast**: Crypto parts are boosted up by TgCrypto_, a high-performance library written in pure C.
- **Updated** to the latest Telegram API version, currently Layer 81 on top of MTProto 2.0. - **Updated** to the latest Telegram API version, currently Layer 82 on top of MTProto 2.0.
- **Documented**: The Pyrogram API is well documented and resembles the Telegram Bot API. - **Documented**: The Pyrogram API is well documented and resembles the Telegram Bot API.
- **Full API**, allowing to execute any advanced action an official client is able to do, and more. - **Full API**, allowing to execute any advanced action an official client is able to do, and more.
@ -84,6 +84,7 @@ To get started, press the Next button.
resources/UpdateHandling resources/UpdateHandling
resources/UsingFilters resources/UsingFilters
resources/Plugins
resources/AutoAuthorization resources/AutoAuthorization
resources/CustomizeSessions resources/CustomizeSessions
resources/TgCrypto resources/TgCrypto

View File

@ -30,6 +30,7 @@ Decorators
on_message on_message
on_callback_query on_callback_query
on_deleted_messages on_deleted_messages
on_user_status
on_disconnect on_disconnect
on_raw_update on_raw_update
@ -96,7 +97,8 @@ Users
get_me get_me
get_users get_users
get_user_profile_photos get_user_profile_photos
delete_profile_photos set_user_profile_photo
delete_user_profile_photos
Contacts Contacts
-------- --------

View File

@ -9,6 +9,7 @@ Handlers
MessageHandler MessageHandler
DeletedMessagesHandler DeletedMessagesHandler
CallbackQueryHandler CallbackQueryHandler
UserStatusHandler
DisconnectHandler DisconnectHandler
RawUpdateHandler RawUpdateHandler
@ -21,6 +22,9 @@ Handlers
.. autoclass:: CallbackQueryHandler .. autoclass:: CallbackQueryHandler
:members: :members:
.. autoclass:: UserStatusHandler
:members:
.. autoclass:: DisconnectHandler .. autoclass:: DisconnectHandler
:members: :members:

View File

@ -10,6 +10,7 @@ Users & Chats
:nosignatures: :nosignatures:
User User
UserStatus
Chat Chat
ChatPhoto ChatPhoto
ChatMember ChatMember
@ -73,6 +74,9 @@ Input Media
.. autoclass:: User .. autoclass:: User
:members: :members:
.. autoclass:: UserStatus
:members:
.. autoclass:: Chat .. autoclass:: Chat
:members: :members:

View File

@ -0,0 +1,116 @@
Plugins
=======
Pyrogram embeds an **automatic** and lightweight plugin system that is meant to greatly simplify the organization of
large projects and to provide a way for creating pluggable components that can be **easily shared** across different
Pyrogram applications with **minimal boilerplate code**.
Introduction
------------
Prior to the plugin system, pluggable handlers were already possible. For instance, if you wanted to modularize your
applications, you had to do something like this...
.. note:: This is an example application that replies in private chats with two messages: one containing the same
text message you sent and the other containing the reversed text message (e.g.: "pyrogram" -> "pyrogram" and
"margoryp"):
.. code-block:: text
myproject/
config.ini
handlers.py
main.py
- ``handlers.py``
.. code-block:: python
def echo(client, message):
message.reply(message.text)
def echo_reversed(client, message):
message.reply(message.text[::-1])
- ``main.py``
.. code-block:: python
from pyrogram import Client, MessageHandler, Filters
from handlers import echo, echo_reversed
app = Client("my_account")
app.add_handler(
MessageHandler(
echo,
Filters.text & Filters.private))
app.add_handler(
MessageHandler(
echo_reversed,
Filters.text & Filters.private),
group=1)
app.run()
...which is already nice and doesn't add *too much* boilerplate code, but things can get boring still; you have to
manually ``import``, manually :meth:`add_handler <pyrogram.Client.add_handler>` and manually instantiate each
:obj:`MessageHandler <pyrogram.MessageHandler>` object because **you can't use those cool decorators** for your
functions. So... What if you could?
Creating Plugins
----------------
Setting up your Pyrogram project to accommodate plugins is as easy as creating a folder and putting your files full of
handlers inside.
.. note:: This is the same example application `as shown above <#introduction>`_, written using the plugin system.
.. code-block:: text
:emphasize-lines: 2, 3
myproject/
plugins/
handlers.py
config.ini
main.py
- ``plugins/handlers.py``
.. code-block:: python
:emphasize-lines: 4, 9
from pyrogram import Client, Filters
@Client.on_message(Filters.text & Filters.private)
def echo(client, message):
message.reply(message.text)
@Client.on_message(Filters.text & Filters.private, group=1)
def echo_reversed(client, message):
message.reply(message.text[::-1])
- ``main.py``
.. code-block:: python
from pyrogram import Client
Client("my_account").run()
The first important thing to note is the ``plugins`` folder, whose name is default and can be changed easily by setting
the ``plugins_dir`` parameter when creating a :obj:`Client <pyrogram.Client>`; you can put any python file in the
plugins folder and each file can contain any decorated function (handlers). Your Pyrogram Client instance (in the
``main.py`` file) will **automatically** scan the folder upon creation to search for valid handlers and register them
for you.
Then you'll notice you can now use decorators. That's right, you can apply the usual decorators to your callback
functions in a static way, i.e. **without having the Client instance around**: simply use ``@Client`` (Client class)
instead of the usual ``@app`` (Client instance) namespace and things will work just the same.
The ``main.py`` script is now at its bare minimum and cleanest state.

View File

@ -2,15 +2,15 @@ Update Handling
=============== ===============
Updates are events that happen in your Telegram account (incoming messages, new channel posts, new members join, ...) Updates are events that happen in your Telegram account (incoming messages, new channel posts, new members join, ...)
and can be handled by registering one or more callback functions in your app by using an `Handler <../pyrogram/Handlers.html>`_. and can be handled by registering one or more callback functions in your app by using `Handlers <../pyrogram/Handlers.html>`_.
To put it simply, whenever an update is received from Telegram it will be dispatched and your previously defined callback To put it simply, whenever an update is received from Telegram it will be dispatched and your previously defined callback
function(s) will be called back with the update itself as argument. function(s) matching it will be called back with the update itself as argument.
Registering an Handler Registering an Handler
---------------------- ----------------------
To explain how `Handlers <../pyrogram/Handlers.html>`_ work let's have a look at the most used one, the To explain how handlers work let's have a look at the most used one, the
:obj:`MessageHandler <pyrogram.MessageHandler>`, which will be in charge for handling :obj:`Message <pyrogram.Message>` :obj:`MessageHandler <pyrogram.MessageHandler>`, which will be in charge for handling :obj:`Message <pyrogram.Message>`
updates coming from all around your chats. Every other handler shares the same setup logic; you should not have troubles updates coming from all around your chats. Every other handler shares the same setup logic; you should not have troubles
settings them up once you learn from this section. settings them up once you learn from this section.

View File

@ -4,11 +4,12 @@ Installation
Being a Python library, Pyrogram requires Python to be installed in your system. Being a Python library, Pyrogram requires Python to be installed in your system.
We recommend using the latest version of Python 3 and pip. We recommend using the latest version of Python 3 and pip.
Get Python 3 from https://www.python.org/downloads/ or with your package manager and pip Get Python 3 from https://www.python.org/downloads/ (or with your package manager) and pip
by following the instructions at https://pip.pypa.io/en/latest/installing/. by following the instructions at https://pip.pypa.io/en/latest/installing/.
.. note:: .. important::
Pyrogram supports Python 3 only, starting from version 3.4 and PyPy.
Pyrogram supports **Python 3** only, starting from version 3.4. **PyPy** is supported too.
Install Pyrogram Install Pyrogram
---------------- ----------------
@ -29,7 +30,7 @@ Bleeding Edge
------------- -------------
If you want the latest development version of Pyrogram, you can install it straight from the develop_ If you want the latest development version of Pyrogram, you can install it straight from the develop_
branch using this command: branch using this command (you might need to install **git** first):
.. code-block:: bash .. code-block:: bash

View File

@ -53,6 +53,7 @@ fits better for you:
) )
.. note:: .. note::
The examples below assume you have created a ``config.ini`` file, thus they won't show the *api_id* The examples below assume you have created a ``config.ini`` file, thus they won't show the *api_id*
and *api_hash* parameters usage. and *api_hash* parameters usage.
@ -74,7 +75,7 @@ the :class:`Client <pyrogram.Client>` class by passing to it a ``session_name``
This starts an interactive shell asking you to input your **phone number** (including your `Country Code`_) This starts an interactive shell asking you to input your **phone number** (including your `Country Code`_)
and the **phone code** you will receive: and the **phone code** you will receive:
.. code:: .. code-block:: text
Enter phone number: +39********** Enter phone number: +39**********
Is "+39**********" correct? (y/n): y Is "+39**********" correct? (y/n): y
@ -84,7 +85,9 @@ After successfully authorizing yourself, a new file called ``my_account.session`
Pyrogram executing API calls with your identity. This file will be loaded again when you restart your app, Pyrogram executing API calls with your identity. This file will be loaded again when you restart your app,
and as long as you keep the session alive, Pyrogram won't ask you again to enter your phone number. and as long as you keep the session alive, Pyrogram won't ask you again to enter your phone number.
.. important:: Your ``*.session`` files are personal and must be kept secret. .. important::
Your ``*.session`` files are personal and must be kept secret.
Bot Authorization Bot Authorization
----------------- -----------------

View File

@ -10,25 +10,38 @@ High-level API
The easiest and recommended way to interact with Telegram is via the high-level Pyrogram methods_ and types_, which are The easiest and recommended way to interact with Telegram is via the high-level Pyrogram methods_ and types_, which are
named after the `Telegram Bot API`_. named after the `Telegram Bot API`_.
Examples (more on `GitHub <https://github.com/pyrogram/pyrogram/tree/develop/examples>`_): Here's a simple example:
- Get information about the authorized user:
.. code-block:: python .. code-block:: python
from pyrogram import Client
app = Client("my_account")
app.start()
print(app.get_me()) print(app.get_me())
app.send_message("me", "Hi there! I'm using **Pyrogram**")
app.send_location("me", 51.500729, -0.124583)
- Send a message to yourself (Saved Messages): app.stop()
You can also use Pyrogram in a context manager with the ``with`` statement. The Client will automatically
:meth:`start <pyrogram.Client.start>` and :meth:`stop <pyrogram.Client.stop>` gracefully, even in case of unhandled
exceptions in your code:
.. code-block:: python .. code-block:: python
app.send_message("me", "Hi there! I'm using Pyrogram") from pyrogram import Client
- Upload a new photo (with caption): app = Client("my_account")
.. code-block:: python with app:
print(app.get_me())
app.send_message("me", "Hi there! I'm using **Pyrogram**")
app.send_location("me", 51.500729, -0.124583)
app.send_photo("me", "/home/dan/perla.jpg", "Cute!") More examples on `GitHub <https://github.com/pyrogram/pyrogram/tree/develop/examples>`_.
Raw Functions Raw Functions
------------- -------------
@ -38,7 +51,9 @@ you have to use the raw :mod:`functions <pyrogram.api.functions>` and :mod:`type
``pyrogram.api`` package and call any Telegram API method you wish using the :meth:`send() <pyrogram.Client.send>` ``pyrogram.api`` package and call any Telegram API method you wish using the :meth:`send() <pyrogram.Client.send>`
method provided by the Client class. method provided by the Client class.
.. hint:: Every high-level method mentioned in the section above is built on top of these raw functions. .. hint::
Every high-level method mentioned in the section above is built on top of these raw functions.
Nothing stops you from using the raw functions only, but they are rather complex and `plenty of them`_ are already Nothing stops you from using the raw functions only, but they are rather complex and `plenty of them`_ are already
re-implemented by providing a much simpler and cleaner interface which is very similar to the Bot API. re-implemented by providing a much simpler and cleaner interface which is very similar to the Bot API.
@ -54,17 +69,13 @@ Examples (more on `GitHub <https://github.com/pyrogram/pyrogram/tree/develop/exa
from pyrogram import Client from pyrogram import Client
from pyrogram.api import functions from pyrogram.api import functions
app = Client("my_account") with Client("my_account") as app:
app.start() app.send(
functions.account.UpdateProfile(
app.send( first_name="Dan", last_name="Tès",
functions.account.UpdateProfile( about="Bio written from Pyrogram"
first_name="Dan", last_name="Tès", )
about="Bio written from Pyrogram"
) )
)
app.stop()
- Share your Last Seen time only with your contacts: - Share your Last Seen time only with your contacts:
@ -73,17 +84,13 @@ Examples (more on `GitHub <https://github.com/pyrogram/pyrogram/tree/develop/exa
from pyrogram import Client from pyrogram import Client
from pyrogram.api import functions, types from pyrogram.api import functions, types
app = Client("my_account") with Client("my_account") as app:
app.start() app.send(
functions.account.SetPrivacy(
app.send( key=types.InputPrivacyKeyStatusTimestamp(),
functions.account.SetPrivacy( rules=[types.InputPrivacyValueAllowContacts()]
key=types.InputPrivacyKeyStatusTimestamp(), )
rules=[types.InputPrivacyValueAllowContacts()]
) )
)
app.stop()
- Invite users to your channel/supergroup: - Invite users to your channel/supergroup:
@ -92,21 +99,17 @@ Examples (more on `GitHub <https://github.com/pyrogram/pyrogram/tree/develop/exa
from pyrogram import Client from pyrogram import Client
from pyrogram.api import functions, types from pyrogram.api import functions, types
app = Client("my_account") with Client("my_account") as app:
app.start() app.send(
functions.channels.InviteToChannel(
app.send( channel=app.resolve_peer(123456789), # ID or Username
functions.channels.InviteToChannel( users=[ # The users you want to invite
channel=app.resolve_peer(123456789), # ID or Username app.resolve_peer(23456789), # By ID
users=[ # The users you want to invite app.resolve_peer("username"), # By username
app.resolve_peer(23456789), # By ID app.resolve_peer("393281234567"), # By phone number
app.resolve_peer("username"), # By username ]
app.resolve_peer("393281234567"), # By phone number )
]
) )
)
app.stop()
.. _methods: ../pyrogram/Client.html#messages .. _methods: ../pyrogram/Client.html#messages
.. _plenty of them: ../pyrogram/Client.html#messages .. _plenty of them: ../pyrogram/Client.html#messages

View File

@ -37,12 +37,12 @@ from .api.errors import Error
from .client.types import ( from .client.types import (
Audio, Chat, ChatMember, ChatMembers, ChatPhoto, Contact, Document, InputMediaPhoto, Audio, Chat, ChatMember, ChatMembers, ChatPhoto, Contact, Document, InputMediaPhoto,
InputMediaVideo, InputMediaDocument, InputMediaAudio, InputMediaAnimation, InputPhoneContact, InputMediaVideo, InputMediaDocument, InputMediaAudio, InputMediaAnimation, InputPhoneContact,
Location, Message, MessageEntity, Dialog, Dialogs, Photo, PhotoSize, Sticker, Update, User, Location, Message, MessageEntity, Dialog, Dialogs, Photo, PhotoSize, Sticker, Update, User, UserStatus,
UserProfilePhotos, Venue, Animation, Video, VideoNote, Voice, CallbackQuery, Messages, ForceReply, UserProfilePhotos, Venue, Animation, Video, VideoNote, Voice, CallbackQuery, Messages, ForceReply,
InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove
) )
from .client import ( from .client import (
Client, ChatAction, ParseMode, Emoji, Client, ChatAction, ParseMode, Emoji,
MessageHandler, DeletedMessagesHandler, CallbackQueryHandler, MessageHandler, DeletedMessagesHandler, CallbackQueryHandler,
RawUpdateHandler, DisconnectHandler, Filters RawUpdateHandler, DisconnectHandler, UserStatusHandler, Filters
) )

View File

@ -22,5 +22,5 @@ from .filters import Filters
from .handlers import ( from .handlers import (
MessageHandler, DeletedMessagesHandler, MessageHandler, DeletedMessagesHandler,
CallbackQueryHandler, RawUpdateHandler, CallbackQueryHandler, RawUpdateHandler,
DisconnectHandler DisconnectHandler, UserStatusHandler
) )

View File

@ -33,6 +33,7 @@ import time
from configparser import ConfigParser from configparser import ConfigParser
from datetime import datetime from datetime import datetime
from hashlib import sha256, md5 from hashlib import sha256, md5
from importlib import import_module
from signal import signal, SIGINT, SIGTERM, SIGABRT from signal import signal, SIGINT, SIGTERM, SIGABRT
from pyrogram.api import functions, types from pyrogram.api import functions, types
@ -43,6 +44,8 @@ from pyrogram.api.errors import (
PhoneCodeExpired, PhoneCodeEmpty, SessionPasswordNeeded, PhoneCodeExpired, PhoneCodeEmpty, SessionPasswordNeeded,
PasswordHashInvalid, FloodWait, PeerIdInvalid, FirstnameInvalid, PhoneNumberBanned, PasswordHashInvalid, FloodWait, PeerIdInvalid, FirstnameInvalid, PhoneNumberBanned,
VolumeLocNotFound, UserMigrate, FileIdInvalid, ChannelPrivate) VolumeLocNotFound, UserMigrate, FileIdInvalid, ChannelPrivate)
from pyrogram.client.handlers import DisconnectHandler
from pyrogram.client.handlers.handler import Handler
from pyrogram.crypto import AES from pyrogram.crypto import AES
from pyrogram.session import Auth, Session from pyrogram.session import Auth, Session
from .dispatcher import Dispatcher from .dispatcher import Dispatcher
@ -140,6 +143,11 @@ class Client(Methods, BaseClient):
config_file (``str``, *optional*): config_file (``str``, *optional*):
Path of the configuration file. Defaults to ./config.ini Path of the configuration file. Defaults to ./config.ini
plugins_dir (``str``, *optional*):
Define a custom directory for your plugins. The plugins directory is the location in your
filesystem where Pyrogram will automatically load your update handlers.
Defaults to "./plugins". Set to None to completely disable plugins.
""" """
def __init__(self, def __init__(self,
@ -159,9 +167,10 @@ class Client(Methods, BaseClient):
force_sms: bool = False, force_sms: bool = False,
first_name: str = None, first_name: str = None,
last_name: str = None, last_name: str = None,
workers: int = 4, workers: int = BaseClient.WORKERS,
workdir: str = ".", workdir: str = BaseClient.WORKDIR,
config_file: str = "./config.ini"): config_file: str = BaseClient.CONFIG_FILE,
plugins_dir: str or None = BaseClient.PLUGINS_DIR):
super().__init__() super().__init__()
self.session_name = session_name self.session_name = session_name
@ -184,6 +193,7 @@ class Client(Methods, BaseClient):
self.workers = workers self.workers = workers
self.workdir = workdir self.workdir = workdir
self.config_file = config_file self.config_file = config_file
self.plugins_dir = plugins_dir
self.dispatcher = Dispatcher(self, workers) self.dispatcher = Dispatcher(self, workers)
@ -219,6 +229,7 @@ class Client(Methods, BaseClient):
self.load_config() self.load_config()
await self.load_session() await self.load_session()
self.load_plugins()
self.session = Session( self.session = Session(
self, self,
@ -966,6 +977,44 @@ class Client(Methods, BaseClient):
if peer: if peer:
self.peers_by_phone[k] = peer self.peers_by_phone[k] = peer
def load_plugins(self):
if self.plugins_dir is not None:
try:
dirs = os.listdir(self.plugins_dir)
except FileNotFoundError:
if self.plugins_dir == Client.PLUGINS_DIR:
log.info("No plugin loaded: default directory is missing")
else:
log.warning('No plugin loaded: "{}" directory is missing'.format(self.plugins_dir))
else:
plugins_dir = self.plugins_dir.lstrip("./").replace("/", ".")
plugins_count = 0
for i in dirs:
module = import_module("{}.{}".format(plugins_dir, i.split(".")[0]))
for j in dir(module):
# noinspection PyBroadException
try:
handler, group = getattr(module, j)
if isinstance(handler, Handler) and isinstance(group, int):
self.add_handler(handler, group)
log.info('{}("{}") from "{}/{}" loaded in group {}'.format(
type(handler).__name__, j, self.plugins_dir, i, group)
)
plugins_count += 1
except Exception:
pass
log.warning('Successfully loaded {} plugin{} from "{}"'.format(
plugins_count,
"s" if plugins_count > 1 else "",
self.plugins_dir
))
def save_session(self): def save_session(self):
auth_key = base64.b64encode(self.auth_key).decode() auth_key = base64.b64encode(self.auth_key).decode()
auth_key = [auth_key[i: i + 43] for i in range(0, len(auth_key), 43)] auth_key = [auth_key[i: i + 43] for i in range(0, len(auth_key), 43)]

View File

@ -23,7 +23,7 @@ from collections import OrderedDict
import pyrogram import pyrogram
from pyrogram.api import types from pyrogram.api import types
from ..ext import utils from ..ext import utils
from ..handlers import RawUpdateHandler, CallbackQueryHandler, MessageHandler, DeletedMessagesHandler from ..handlers import RawUpdateHandler, CallbackQueryHandler, MessageHandler, DeletedMessagesHandler, UserStatusHandler
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -108,6 +108,8 @@ class Dispatcher:
callback_query = update.callback_query callback_query = update.callback_query
user_status = update.user_status
if message and isinstance(handler, MessageHandler): if message and isinstance(handler, MessageHandler):
if not handler.check(message): if not handler.check(message):
continue continue
@ -123,6 +125,11 @@ class Dispatcher:
continue continue
args = (self.client, callback_query) args = (self.client, callback_query)
elif user_status and isinstance(handler, UserStatusHandler):
if not handler.check(user_status):
continue
args = (self.client, user_status)
else: else:
continue continue
@ -207,6 +214,14 @@ class Dispatcher:
) )
) )
) )
elif isinstance(update, types.UpdateUserStatus):
self.dispatch(
pyrogram.Update(
user_status=utils.parse_user_status(
update.status, update.user_id
)
)
)
else: else:
continue continue
except Exception as e: except Exception as e:

View File

@ -48,6 +48,10 @@ class BaseClient:
UPDATES_WORKERS = 1 UPDATES_WORKERS = 1
DOWNLOAD_WORKERS = 4 DOWNLOAD_WORKERS = 4
OFFLINE_SLEEP = 300 OFFLINE_SLEEP = 300
WORKERS = 4
WORKDIR = "."
CONFIG_FILE = "./config.ini"
PLUGINS_DIR = "./plugins"
MEDIA_TYPE_ID = { MEDIA_TYPE_ID = {
0: "thumbnail", 0: "thumbnail",

View File

@ -141,6 +141,30 @@ def parse_chat_photo(photo):
) )
def parse_user_status(user_status, user_id: int = None, is_bot: bool = False) -> pyrogram_types.UserStatus or None:
if is_bot:
return None
status = pyrogram_types.UserStatus(user_id)
if isinstance(user_status, types.UserStatusOnline):
status.online = True
status.date = user_status.expires
elif isinstance(user_status, types.UserStatusOffline):
status.offline = True
status.date = user_status.was_online
elif isinstance(user_status, types.UserStatusRecently):
status.recently = True
elif isinstance(user_status, types.UserStatusLastWeek):
status.within_week = True
elif isinstance(user_status, types.UserStatusLastMonth):
status.within_month = True
else:
status.long_time_ago = True
return status
def parse_user(user: types.User) -> pyrogram_types.User or None: def parse_user(user: types.User) -> pyrogram_types.User or None:
return pyrogram_types.User( return pyrogram_types.User(
id=user.id, id=user.id,
@ -154,7 +178,8 @@ def parse_user(user: types.User) -> pyrogram_types.User or None:
username=user.username, username=user.username,
language_code=user.lang_code, language_code=user.lang_code,
phone_number=user.phone, phone_number=user.phone,
photo=parse_chat_photo(user.photo) photo=parse_chat_photo(user.photo),
status=parse_user_status(user.status, is_bot=user.bot),
) if user else None ) if user else None

View File

@ -168,7 +168,7 @@ class Filters:
@staticmethod @staticmethod
def command(command: str or list, def command(command: str or list,
prefix: str = "/", prefix: str or list = "/",
separator: str = " ", separator: str = " ",
case_sensitive: bool = False): case_sensitive: bool = False):
"""Filter commands, i.e.: text messages starting with "/" or any other custom prefix. """Filter commands, i.e.: text messages starting with "/" or any other custom prefix.
@ -180,9 +180,9 @@ class Filters:
a command arrives, the command itself and its arguments will be stored in the *command* a command arrives, the command itself and its arguments will be stored in the *command*
field of the :class:`Message <pyrogram.Message>`. field of the :class:`Message <pyrogram.Message>`.
prefix (``str``, *optional*): prefix (``str`` | ``list``, *optional*):
The command prefix. Defaults to "/" (slash). A prefix or a list of prefixes as string the filter should look for.
Examples: /start, .help, !settings. Defaults to "/" (slash). Examples: ".", "!", ["/", "!", "."].
separator (``str``, *optional*): separator (``str``, *optional*):
The command arguments separator. Defaults to " " (white space). The command arguments separator. Defaults to " " (white space).
@ -194,11 +194,14 @@ class Filters:
""" """
def f(_, m): def f(_, m):
if m.text and m.text.startswith(_.p): if m.text:
t = m.text.split(_.s) for i in _.p:
c, a = t[0][len(_.p):], t[1:] if m.text.startswith(i):
c = c if _.cs else c.lower() t = m.text.split(_.s)
m.command = ([c] + a) if c in _.c else None c, a = t[0][len(i):], t[1:]
c = c if _.cs else c.lower()
m.command = ([c] + a) if c in _.c else None
break
return bool(m.command) return bool(m.command)
@ -211,7 +214,7 @@ class Filters:
else {c if case_sensitive else {c if case_sensitive
else c.lower() else c.lower()
for c in command}, for c in command},
p=prefix, p=set(prefix),
s=separator, s=separator,
cs=case_sensitive cs=case_sensitive
) )

View File

@ -21,3 +21,4 @@ from .deleted_messages_handler import DeletedMessagesHandler
from .disconnect_handler import DisconnectHandler from .disconnect_handler import DisconnectHandler
from .message_handler import MessageHandler from .message_handler import MessageHandler
from .raw_update_handler import RawUpdateHandler from .raw_update_handler import RawUpdateHandler
from .user_status_handler import UserStatusHandler

View File

@ -0,0 +1,54 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
#
# This file is part of Pyrogram.
#
# Pyrogram is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyrogram is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
from .handler import Handler
class UserStatusHandler(Handler):
"""The UserStatus handler class. Used to handle user status updates (user going online or offline).
It is intended to be used with :meth:`add_handler() <pyrogram.Client.add_handler>`
For a nicer way to register this handler, have a look at the
:meth:`on_user_status() <pyrogram.Client.on_user_status>` decorator.
Args:
callback (``callable``):
Pass a function that will be called when a new UserStatus update arrives. It takes *(client, user_status)*
as positional arguments (look at the section below for a detailed description).
filters (:obj:`Filters <pyrogram.Filters>`):
Pass one or more filters to allow only a subset of messages to be passed
in your callback function.
Other parameters:
client (:obj:`Client <pyrogram.Client>`):
The Client itself, useful when you want to call other API methods inside the user status handler.
user_status (:obj:`UserStatus <pyrogram.UserStatus>`):
The received UserStatus update.
"""
def __init__(self, callback: callable, filters=None):
super().__init__(callback, filters)
def check(self, user_status):
return (
self.filters(user_status)
if callable(self.filters)
else True
)

View File

@ -21,7 +21,15 @@ from .on_deleted_messages import OnDeletedMessages
from .on_disconnect import OnDisconnect from .on_disconnect import OnDisconnect
from .on_message import OnMessage from .on_message import OnMessage
from .on_raw_update import OnRawUpdate from .on_raw_update import OnRawUpdate
from .on_user_status import OnUserStatus
class Decorators(OnMessage, OnDeletedMessages, OnCallbackQuery, OnRawUpdate, OnDisconnect): class Decorators(
OnMessage,
OnDeletedMessages,
OnCallbackQuery,
OnRawUpdate,
OnDisconnect,
OnUserStatus
):
pass pass

View File

@ -17,6 +17,7 @@
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>. # along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
import pyrogram import pyrogram
from pyrogram.client.filters.filter import Filter
from ...ext import BaseClient from ...ext import BaseClient
@ -36,7 +37,14 @@ class OnCallbackQuery(BaseClient):
""" """
def decorator(func): def decorator(func):
self.add_handler(pyrogram.CallbackQueryHandler(func, filters), group) handler = pyrogram.CallbackQueryHandler(func, filters)
return func
if isinstance(self, Filter):
return pyrogram.CallbackQueryHandler(func, self), group if filters is None else filters
if self is not None:
self.add_handler(handler, group)
return handler, group
return decorator return decorator

View File

@ -17,6 +17,7 @@
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>. # along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
import pyrogram import pyrogram
from pyrogram.client.filters.filter import Filter
from ...ext import BaseClient from ...ext import BaseClient
@ -36,7 +37,14 @@ class OnDeletedMessages(BaseClient):
""" """
def decorator(func): def decorator(func):
self.add_handler(pyrogram.DeletedMessagesHandler(func, filters), group) handler = pyrogram.DeletedMessagesHandler(func, filters)
return func
if isinstance(self, Filter):
return pyrogram.DeletedMessagesHandler(func, self), group if filters is None else filters
if self is not None:
self.add_handler(handler, group)
return handler, group
return decorator return decorator

View File

@ -28,7 +28,11 @@ class OnDisconnect(BaseClient):
""" """
def decorator(func): def decorator(func):
self.add_handler(pyrogram.DisconnectHandler(func)) handler = pyrogram.DisconnectHandler(func)
return func
if self is not None:
self.add_handler(handler)
return handler
return decorator return decorator

View File

@ -17,11 +17,12 @@
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>. # along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
import pyrogram import pyrogram
from pyrogram.client.filters.filter import Filter
from ...ext import BaseClient from ...ext import BaseClient
class OnMessage(BaseClient): class OnMessage(BaseClient):
def on_message(self, filters=None, group: int = 0): def on_message(self=None, filters=None, group: int = 0):
"""Use this decorator to automatically register a function for handling """Use this decorator to automatically register a function for handling
messages. This does the same thing as :meth:`add_handler` using the messages. This does the same thing as :meth:`add_handler` using the
:class:`MessageHandler`. :class:`MessageHandler`.
@ -36,7 +37,14 @@ class OnMessage(BaseClient):
""" """
def decorator(func): def decorator(func):
self.add_handler(pyrogram.MessageHandler(func, filters), group) handler = pyrogram.MessageHandler(func, filters)
return func
if isinstance(self, Filter):
return pyrogram.MessageHandler(func, self), group if filters is None else filters
if self is not None:
self.add_handler(handler, group)
return handler, group
return decorator return decorator

View File

@ -21,7 +21,7 @@ from ...ext import BaseClient
class OnRawUpdate(BaseClient): class OnRawUpdate(BaseClient):
def on_raw_update(self, group: int = 0): def on_raw_update(self=None, group: int = 0):
"""Use this decorator to automatically register a function for handling """Use this decorator to automatically register a function for handling
raw updates. This does the same thing as :meth:`add_handler` using the raw updates. This does the same thing as :meth:`add_handler` using the
:class:`RawUpdateHandler`. :class:`RawUpdateHandler`.
@ -32,7 +32,14 @@ class OnRawUpdate(BaseClient):
""" """
def decorator(func): def decorator(func):
self.add_handler(pyrogram.RawUpdateHandler(func), group) handler = pyrogram.RawUpdateHandler(func)
return func
if isinstance(self, int):
return handler, group if self is None else group
if self is not None:
self.add_handler(handler, group)
return handler, group
return decorator return decorator

View File

@ -0,0 +1,41 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
#
# This file is part of Pyrogram.
#
# Pyrogram is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyrogram is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
import pyrogram
from ...ext import BaseClient
class OnUserStatus(BaseClient):
def on_user_status(self, filters=None, group: int = 0):
"""Use this decorator to automatically register a function for handling
user status updates. This does the same thing as :meth:`add_handler` using the
:class:`UserStatusHandler`.
Args:
filters (:obj:`Filters <pyrogram.Filters>`):
Pass one or more filters to allow only a subset of UserStatus updated to be passed in your function.
group (``int``, *optional*):
The group identifier, defaults to 0.
"""
def decorator(func):
self.add_handler(pyrogram.UserStatusHandler(func, filters), group)
return func
return decorator

View File

@ -16,15 +16,17 @@
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>. # along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
from .delete_profile_photos import DeleteProfilePhotos from .delete_user_profile_photos import DeleteUserProfilePhotos
from .get_me import GetMe from .get_me import GetMe
from .get_user_profile_photos import GetUserProfilePhotos from .get_user_profile_photos import GetUserProfilePhotos
from .get_users import GetUsers from .get_users import GetUsers
from .set_user_profile_photo import SetUserProfilePhoto
class Users( class Users(
GetUserProfilePhotos, GetUserProfilePhotos,
DeleteProfilePhotos, SetUserProfilePhoto,
DeleteUserProfilePhotos,
GetUsers, GetUsers,
GetMe GetMe
): ):

View File

@ -23,8 +23,8 @@ from pyrogram.api import functions, types
from ...ext import BaseClient from ...ext import BaseClient
class DeleteProfilePhotos(BaseClient): class DeleteUserProfilePhotos(BaseClient):
async def delete_profile_photos(self, id: str or list): async def delete_user_profile_photos(self, id: str or list):
"""Use this method to delete your own profile photos """Use this method to delete your own profile photos
Args: Args:

View File

@ -0,0 +1,48 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
#
# This file is part of Pyrogram.
#
# Pyrogram is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyrogram is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
from pyrogram.api import functions
from ...ext import BaseClient
class SetUserProfilePhoto(BaseClient):
def set_user_profile_photo(self, photo: str):
"""Use this method to set a new profile photo.
This method only works for Users.
Bots profile photos must be set using BotFather.
Args:
photo (``str``):
Profile photo to set.
Pass a file path as string to upload a new photo that exists on your local machine.
Returns:
True on success.
Raises:
:class:`Error <pyrogram.Error>`
"""
return bool(
self.send(
functions.photos.UploadProfilePhoto(
self.save_file(photo)
)
)
)

View File

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

View File

@ -58,6 +58,9 @@ class Update(Object):
pre_checkout_query (:obj:`PreCheckoutQuery <pyrogram.PreCheckoutQuery>`, *optional*): pre_checkout_query (:obj:`PreCheckoutQuery <pyrogram.PreCheckoutQuery>`, *optional*):
New incoming pre-checkout query. Contains full information about checkout. New incoming pre-checkout query. Contains full information about checkout.
user_status (:obj:`UserStatus <pyrogram.UserStatus>`, *optional*):
User status (last seen date) update.
""" """
ID = 0xb0700000 ID = 0xb0700000
@ -74,7 +77,8 @@ class Update(Object):
chosen_inline_result=None, chosen_inline_result=None,
callback_query=None, callback_query=None,
shipping_query=None, shipping_query=None,
pre_checkout_query=None pre_checkout_query=None,
user_status=None
): ):
self.message = message self.message = message
self.edited_message = edited_message self.edited_message = edited_message
@ -87,3 +91,4 @@ class Update(Object):
self.callback_query = callback_query self.callback_query = callback_query
self.shipping_query = shipping_query self.shipping_query = shipping_query
self.pre_checkout_query = pre_checkout_query self.pre_checkout_query = pre_checkout_query
self.user_status = user_status

View File

@ -22,4 +22,5 @@ from .chat_members import ChatMembers
from .chat_photo import ChatPhoto from .chat_photo import ChatPhoto
from .dialog import Dialog from .dialog import Dialog
from .dialogs import Dialogs from .dialogs import Dialogs
from .user_status import UserStatus
from .user import User from .user import User

View File

@ -41,6 +41,9 @@ class User(Object):
is_bot (``bool``): is_bot (``bool``):
True, if this user is a bot. True, if this user is a bot.
status (:obj:`UserStatus <pyrogram.UserStatus>`):
User's Last Seen status. Empty for bots.
first_name (``str``): first_name (``str``):
User's or bot's first name. User's or bot's first name.
@ -70,6 +73,7 @@ class User(Object):
is_mutual_contact: bool, is_mutual_contact: bool,
is_deleted: bool, is_deleted: bool,
is_bot: bool, is_bot: bool,
status,
first_name: str, first_name: str,
last_name: str = None, last_name: str = None,
username: str = None, username: str = None,
@ -83,6 +87,7 @@ class User(Object):
self.is_mutual_contact = is_mutual_contact self.is_mutual_contact = is_mutual_contact
self.is_deleted = is_deleted self.is_deleted = is_deleted
self.is_bot = is_bot self.is_bot = is_bot
self.status = status
self.first_name = first_name self.first_name = first_name
self.last_name = last_name self.last_name = last_name
self.username = username self.username = username

View File

@ -0,0 +1,84 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
#
# This file is part of Pyrogram.
#
# Pyrogram is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyrogram is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
from pyrogram.api.core import Object
class UserStatus(Object):
"""This object represents a User status (Last Seen privacy)
.. note::
You won't see exact last seen timestamps for people with whom you don't share your own. Instead, you get
"recently", "within_week", "within_month" or "long_time_ago" fields set.
Args:
user_id (``int``):
User's id. Only available for UserStatus updates.
online (``bool``):
True if the user is online in this moment, None otherwise.
If True, the "date" field will be also set containing the online expiration date (i.e.: the date when a
user will automatically go offline in case of no action by his client).
offline (``bool``):
True if the user is offline and has the Last Seen privacy setting visible for everybody, None otherwise.
If True, the "date" field will be also set containing the last seen date (i.e.: the date when a user
was online the last time).
date (``int``):
Exact date in unix time. Available only in case "online" or "offline" equals to True.
recently (``bool``):
True for users with hidden Last Seen privacy that have been online between 1 second and 2-3 days ago,
None otherwise.
within_week (``bool``):
True for users with hidden Last Seen privacy that have been online between 2-3 and seven days ago,
None otherwise.
within_month (``bool``):
True for users with hidden Last Seen privacy that have been online between 6-7 days and a month ago,
None otherwise.
long_time_ago (``bool``):
True for users with hidden Last Seen privacy that have been online more than a month ago (this is also
always shown to blocked users), None otherwise.
"""
ID = 0xb0700031
def __init__(
self,
user_id: int = None,
online: bool = None,
offline: bool = None,
date: int = None,
recently: bool = None,
within_week: bool = None,
within_month: bool = None,
long_time_ago: bool = None
):
self.user_id = user_id
self.online = online
self.offline = offline
self.date = date
self.recently = recently
self.within_week = within_week
self.within_month = within_month
self.long_time_ago = long_time_ago