mirror of
https://github.com/Mayuri-Chan/pyrofork.git
synced 2025-12-29 12:04:51 +00:00
PyroFork: Bring back docs
Signed-off-by: wulan17 <wulan17@nusantararom.org>
This commit is contained in:
parent
7a1684f074
commit
261214ec1c
88 changed files with 3796 additions and 1 deletions
4
Makefile
4
Makefile
|
|
@ -14,6 +14,10 @@ venv:
|
||||||
$(PYTHON) -m pip install -U -r requirements.txt -r dev-requirements.txt
|
$(PYTHON) -m pip install -U -r requirements.txt -r dev-requirements.txt
|
||||||
@echo "Created venv with $$($(PYTHON) --version)"
|
@echo "Created venv with $$($(PYTHON) --version)"
|
||||||
|
|
||||||
|
clean-docs:
|
||||||
|
$(RM) docs/build
|
||||||
|
$(RM) docs/source/api/bound-methods docs/source/api/methods docs/source/api/types docs/source/telegram
|
||||||
|
|
||||||
clean-build:
|
clean-build:
|
||||||
$(RM) *.egg-info build dist
|
$(RM) *.egg-info build dist
|
||||||
|
|
||||||
|
|
|
||||||
20
build-docs.sh
Normal file
20
build-docs.sh
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
#!/bin/bash
|
||||||
|
export DOCS_KEY
|
||||||
|
export VENV=$(pwd)/venv
|
||||||
|
|
||||||
|
make clean
|
||||||
|
make clean-docs
|
||||||
|
make venv
|
||||||
|
make api
|
||||||
|
"$VENV"/bin/pip install -r docs/requirements.txt
|
||||||
|
cd compiler/docs && "$VENV"/bin/python compiler.py
|
||||||
|
cd ../..
|
||||||
|
"$VENV"/bin/sphinx-build -b html "docs/source" "docs/build/html" -j auto
|
||||||
|
git clone https://wulan17:"$DOCS_KEY"@github.com/Mayuri-Chan/pyrofork-docs.git
|
||||||
|
cp -r docs/build/html/* pyrofork-docs
|
||||||
|
cd pyrofork-docs
|
||||||
|
git config --local user.name "Mayuri-Chan"
|
||||||
|
git config --local user.email "mayuri@mayuri.my.id"
|
||||||
|
git add --all
|
||||||
|
git commit -a -m "docs: Update docs $(date '+%Y-%m-%d | %H:%m:%S %p %Z')" --signoff
|
||||||
|
git push -u origin --all
|
||||||
6
docs/requirements.txt
Normal file
6
docs/requirements.txt
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
pyston_lite-autoload
|
||||||
|
sphinx
|
||||||
|
sphinx_rtd_theme==1.2.2
|
||||||
|
sphinx_copybutton
|
||||||
|
sphinx-autobuild
|
||||||
|
tgcrypto
|
||||||
24
docs/source/api/client.rst
Normal file
24
docs/source/api/client.rst
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
Pyrofork Client
|
||||||
|
===============
|
||||||
|
|
||||||
|
You have entered the API Reference section where you can find detailed information about Pyrofork's API. The main Client
|
||||||
|
class, all available methods and types, filters, handlers, decorators and bound-methods detailed descriptions can be
|
||||||
|
found starting from this page.
|
||||||
|
|
||||||
|
This page is about the Client class, which exposes high-level methods for an easy access to the API.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import Client
|
||||||
|
|
||||||
|
app = Client("my_account")
|
||||||
|
|
||||||
|
with app:
|
||||||
|
app.send_message("me", "Hi!")
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
Details
|
||||||
|
-------
|
||||||
|
|
||||||
|
.. autoclass:: pyrogram.Client()
|
||||||
68
docs/source/api/decorators.rst
Normal file
68
docs/source/api/decorators.rst
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
Decorators
|
||||||
|
==========
|
||||||
|
|
||||||
|
Decorators are able to register callback functions for handling updates in a much easier and cleaner way compared to
|
||||||
|
:doc:`Handlers <handlers>`; they do so by instantiating the correct handler and calling
|
||||||
|
:meth:`~pyrogram.Client.add_handler` automatically. All you need to do is adding the decorators on top of your
|
||||||
|
functions.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import Client
|
||||||
|
|
||||||
|
app = Client("my_account")
|
||||||
|
|
||||||
|
|
||||||
|
@app.on_message()
|
||||||
|
def log(client, message):
|
||||||
|
print(message)
|
||||||
|
|
||||||
|
|
||||||
|
app.run()
|
||||||
|
|
||||||
|
.. contents:: Contents
|
||||||
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
|
:local:
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
.. currentmodule:: pyrogram
|
||||||
|
|
||||||
|
Index
|
||||||
|
-----
|
||||||
|
|
||||||
|
.. hlist::
|
||||||
|
:columns: 3
|
||||||
|
|
||||||
|
- :meth:`~Client.on_message`
|
||||||
|
- :meth:`~Client.on_edited_message`
|
||||||
|
- :meth:`~Client.on_callback_query`
|
||||||
|
- :meth:`~Client.on_inline_query`
|
||||||
|
- :meth:`~Client.on_chosen_inline_result`
|
||||||
|
- :meth:`~Client.on_chat_member_updated`
|
||||||
|
- :meth:`~Client.on_chat_join_request`
|
||||||
|
- :meth:`~Client.on_deleted_messages`
|
||||||
|
- :meth:`~Client.on_user_status`
|
||||||
|
- :meth:`~Client.on_poll`
|
||||||
|
- :meth:`~Client.on_disconnect`
|
||||||
|
- :meth:`~Client.on_raw_update`
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
Details
|
||||||
|
-------
|
||||||
|
|
||||||
|
.. Decorators
|
||||||
|
.. autodecorator:: pyrogram.Client.on_message()
|
||||||
|
.. autodecorator:: pyrogram.Client.on_edited_message()
|
||||||
|
.. autodecorator:: pyrogram.Client.on_callback_query()
|
||||||
|
.. autodecorator:: pyrogram.Client.on_inline_query()
|
||||||
|
.. autodecorator:: pyrogram.Client.on_chosen_inline_result()
|
||||||
|
.. autodecorator:: pyrogram.Client.on_chat_member_updated()
|
||||||
|
.. autodecorator:: pyrogram.Client.on_chat_join_request()
|
||||||
|
.. autodecorator:: pyrogram.Client.on_deleted_messages()
|
||||||
|
.. autodecorator:: pyrogram.Client.on_user_status()
|
||||||
|
.. autodecorator:: pyrogram.Client.on_poll()
|
||||||
|
.. autodecorator:: pyrogram.Client.on_disconnect()
|
||||||
|
.. autodecorator:: pyrogram.Client.on_raw_update()
|
||||||
8
docs/source/api/enums/ChatAction.rst
Normal file
8
docs/source/api/enums/ChatAction.rst
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
ChatAction
|
||||||
|
==========
|
||||||
|
|
||||||
|
.. autoclass:: pyrogram.enums.ChatAction()
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
:file: ./cleanup.html
|
||||||
8
docs/source/api/enums/ChatEventAction.rst
Normal file
8
docs/source/api/enums/ChatEventAction.rst
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
ChatEventAction
|
||||||
|
===============
|
||||||
|
|
||||||
|
.. autoclass:: pyrogram.enums.ChatEventAction()
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
:file: ./cleanup.html
|
||||||
8
docs/source/api/enums/ChatMemberStatus.rst
Normal file
8
docs/source/api/enums/ChatMemberStatus.rst
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
ChatMemberStatus
|
||||||
|
================
|
||||||
|
|
||||||
|
.. autoclass:: pyrogram.enums.ChatMemberStatus()
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
:file: ./cleanup.html
|
||||||
8
docs/source/api/enums/ChatMembersFilter.rst
Normal file
8
docs/source/api/enums/ChatMembersFilter.rst
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
ChatMembersFilter
|
||||||
|
=================
|
||||||
|
|
||||||
|
.. autoclass:: pyrogram.enums.ChatMembersFilter()
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
:file: ./cleanup.html
|
||||||
8
docs/source/api/enums/ChatType.rst
Normal file
8
docs/source/api/enums/ChatType.rst
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
ChatType
|
||||||
|
========
|
||||||
|
|
||||||
|
.. autoclass:: pyrogram.enums.ChatType()
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
:file: ./cleanup.html
|
||||||
8
docs/source/api/enums/MessageEntityType.rst
Normal file
8
docs/source/api/enums/MessageEntityType.rst
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
MessageEntityType
|
||||||
|
=================
|
||||||
|
|
||||||
|
.. autoclass:: pyrogram.enums.MessageEntityType()
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
:file: ./cleanup.html
|
||||||
8
docs/source/api/enums/MessageMediaType.rst
Normal file
8
docs/source/api/enums/MessageMediaType.rst
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
MessageMediaType
|
||||||
|
================
|
||||||
|
|
||||||
|
.. autoclass:: pyrogram.enums.MessageMediaType()
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
:file: ./cleanup.html
|
||||||
8
docs/source/api/enums/MessageServiceType.rst
Normal file
8
docs/source/api/enums/MessageServiceType.rst
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
MessageServiceType
|
||||||
|
==================
|
||||||
|
|
||||||
|
.. autoclass:: pyrogram.enums.MessageServiceType()
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
:file: ./cleanup.html
|
||||||
8
docs/source/api/enums/MessagesFilter.rst
Normal file
8
docs/source/api/enums/MessagesFilter.rst
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
MessagesFilter
|
||||||
|
==============
|
||||||
|
|
||||||
|
.. autoclass:: pyrogram.enums.MessagesFilter()
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
:file: ./cleanup.html
|
||||||
8
docs/source/api/enums/NextCodeType.rst
Normal file
8
docs/source/api/enums/NextCodeType.rst
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
NextCodeType
|
||||||
|
============
|
||||||
|
|
||||||
|
.. autoclass:: pyrogram.enums.NextCodeType()
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
:file: ./cleanup.html
|
||||||
8
docs/source/api/enums/ParseMode.rst
Normal file
8
docs/source/api/enums/ParseMode.rst
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
ParseMode
|
||||||
|
=========
|
||||||
|
|
||||||
|
.. autoclass:: pyrogram.enums.ParseMode()
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
:file: ./cleanup.html
|
||||||
8
docs/source/api/enums/PollType.rst
Normal file
8
docs/source/api/enums/PollType.rst
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
PollType
|
||||||
|
========
|
||||||
|
|
||||||
|
.. autoclass:: pyrogram.enums.PollType()
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
:file: ./cleanup.html
|
||||||
8
docs/source/api/enums/SentCodeType.rst
Normal file
8
docs/source/api/enums/SentCodeType.rst
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
SentCodeType
|
||||||
|
============
|
||||||
|
|
||||||
|
.. autoclass:: pyrogram.enums.SentCodeType()
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
:file: ./cleanup.html
|
||||||
8
docs/source/api/enums/UserStatus.rst
Normal file
8
docs/source/api/enums/UserStatus.rst
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
UserStatus
|
||||||
|
==========
|
||||||
|
|
||||||
|
.. autoclass:: pyrogram.enums.UserStatus()
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
:file: ./cleanup.html
|
||||||
9
docs/source/api/enums/cleanup.html
Normal file
9
docs/source/api/enums/cleanup.html
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
<script>
|
||||||
|
document
|
||||||
|
.querySelectorAll("em.property")
|
||||||
|
.forEach((elem, i) => i !== 0 ? elem.remove() : true)
|
||||||
|
|
||||||
|
document
|
||||||
|
.querySelectorAll("a.headerlink")
|
||||||
|
.forEach((elem, i) => [0, 1].includes(i) ? true : elem.remove())
|
||||||
|
</script>
|
||||||
47
docs/source/api/enums/index.rst
Normal file
47
docs/source/api/enums/index.rst
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
Enumerations
|
||||||
|
============
|
||||||
|
|
||||||
|
This page is about Pyrofork enumerations.
|
||||||
|
Enumerations are types that hold a group of related values to be used whenever a constant value is required.
|
||||||
|
They will help you deal with those values in a type-safe way and also enable code completion so that you can be sure
|
||||||
|
to apply only a valid value among the expected ones.
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
.. currentmodule:: pyrogram.enums
|
||||||
|
|
||||||
|
.. autosummary::
|
||||||
|
:nosignatures:
|
||||||
|
|
||||||
|
ChatAction
|
||||||
|
ChatEventAction
|
||||||
|
ChatMemberStatus
|
||||||
|
ChatMembersFilter
|
||||||
|
ChatType
|
||||||
|
MessageEntityType
|
||||||
|
MessageMediaType
|
||||||
|
MessageServiceType
|
||||||
|
MessagesFilter
|
||||||
|
ParseMode
|
||||||
|
PollType
|
||||||
|
SentCodeType
|
||||||
|
NextCodeType
|
||||||
|
UserStatus
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:hidden:
|
||||||
|
|
||||||
|
ChatAction
|
||||||
|
ChatEventAction
|
||||||
|
ChatMemberStatus
|
||||||
|
ChatMembersFilter
|
||||||
|
ChatType
|
||||||
|
MessageEntityType
|
||||||
|
MessageMediaType
|
||||||
|
MessageServiceType
|
||||||
|
MessagesFilter
|
||||||
|
ParseMode
|
||||||
|
PollType
|
||||||
|
SentCodeType
|
||||||
|
NextCodeType
|
||||||
|
UserStatus
|
||||||
7
docs/source/api/errors/bad-request.rst
Normal file
7
docs/source/api/errors/bad-request.rst
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
400 - BadRequest
|
||||||
|
----------------
|
||||||
|
|
||||||
|
.. csv-table::
|
||||||
|
:file: ../../../../compiler/errors/source/400_BAD_REQUEST.tsv
|
||||||
|
:delim: tab
|
||||||
|
:header-rows: 1
|
||||||
7
docs/source/api/errors/flood.rst
Normal file
7
docs/source/api/errors/flood.rst
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
420 - Flood
|
||||||
|
-----------
|
||||||
|
|
||||||
|
.. csv-table::
|
||||||
|
:file: ../../../../compiler/errors/source/420_FLOOD.tsv
|
||||||
|
:delim: tab
|
||||||
|
:header-rows: 1
|
||||||
7
docs/source/api/errors/forbidden.rst
Normal file
7
docs/source/api/errors/forbidden.rst
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
403 - Forbidden
|
||||||
|
---------------
|
||||||
|
|
||||||
|
.. csv-table::
|
||||||
|
:file: ../../../../compiler/errors/source/403_FORBIDDEN.tsv
|
||||||
|
:delim: tab
|
||||||
|
:header-rows: 1
|
||||||
37
docs/source/api/errors/index.rst
Normal file
37
docs/source/api/errors/index.rst
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
RPC Errors
|
||||||
|
==========
|
||||||
|
|
||||||
|
All Pyrofork API errors live inside the ``errors`` sub-package: ``pyrogram.errors``.
|
||||||
|
The errors ids listed here are shown as *UPPER_SNAKE_CASE*, but the actual exception names to import from Pyrofork
|
||||||
|
follow the usual *PascalCase* convention.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram.errors import FloodWait
|
||||||
|
|
||||||
|
try:
|
||||||
|
...
|
||||||
|
except FloodWait as e:
|
||||||
|
...
|
||||||
|
|
||||||
|
.. hlist::
|
||||||
|
:columns: 1
|
||||||
|
|
||||||
|
- :doc:`see-other`
|
||||||
|
- :doc:`bad-request`
|
||||||
|
- :doc:`unauthorized`
|
||||||
|
- :doc:`forbidden`
|
||||||
|
- :doc:`not-acceptable`
|
||||||
|
- :doc:`flood`
|
||||||
|
- :doc:`internal-server-error`
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:hidden:
|
||||||
|
|
||||||
|
see-other
|
||||||
|
bad-request
|
||||||
|
unauthorized
|
||||||
|
forbidden
|
||||||
|
not-acceptable
|
||||||
|
flood
|
||||||
|
internal-server-error
|
||||||
7
docs/source/api/errors/internal-server-error.rst
Normal file
7
docs/source/api/errors/internal-server-error.rst
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
500 - InternalServerError
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
.. csv-table::
|
||||||
|
:file: ../../../../compiler/errors/source/500_INTERNAL_SERVER_ERROR.tsv
|
||||||
|
:delim: tab
|
||||||
|
:header-rows: 1
|
||||||
7
docs/source/api/errors/not-acceptable.rst
Normal file
7
docs/source/api/errors/not-acceptable.rst
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
406 - NotAcceptable
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
.. csv-table::
|
||||||
|
:file: ../../../../compiler/errors/source/406_NOT_ACCEPTABLE.tsv
|
||||||
|
:delim: tab
|
||||||
|
:header-rows: 1
|
||||||
7
docs/source/api/errors/see-other.rst
Normal file
7
docs/source/api/errors/see-other.rst
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
303 - SeeOther
|
||||||
|
--------------
|
||||||
|
|
||||||
|
.. csv-table::
|
||||||
|
:file: ../../../../compiler/errors/source/303_SEE_OTHER.tsv
|
||||||
|
:delim: tab
|
||||||
|
:header-rows: 1
|
||||||
7
docs/source/api/errors/unauthorized.rst
Normal file
7
docs/source/api/errors/unauthorized.rst
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
401 - Unauthorized
|
||||||
|
------------------
|
||||||
|
|
||||||
|
.. csv-table::
|
||||||
|
:file: ../../../../compiler/errors/source/401_UNAUTHORIZED.tsv
|
||||||
|
:delim: tab
|
||||||
|
:header-rows: 1
|
||||||
11
docs/source/api/filters.rst
Normal file
11
docs/source/api/filters.rst
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
Update Filters
|
||||||
|
==============
|
||||||
|
|
||||||
|
Filters are objects that can be used to filter the content of incoming updates.
|
||||||
|
:doc:`Read more about how filters work <../topics/use-filters>`.
|
||||||
|
|
||||||
|
Details
|
||||||
|
-------
|
||||||
|
|
||||||
|
.. automodule:: pyrogram.filters
|
||||||
|
:members:
|
||||||
66
docs/source/api/handlers.rst
Normal file
66
docs/source/api/handlers.rst
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
Update Handlers
|
||||||
|
===============
|
||||||
|
|
||||||
|
Handlers are used to instruct Pyrofork about which kind of updates you'd like to handle with your callback functions.
|
||||||
|
For a much more convenient way of registering callback functions have a look at :doc:`Decorators <decorators>` instead.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import Client
|
||||||
|
from pyrogram.handlers import MessageHandler
|
||||||
|
|
||||||
|
app = Client("my_account")
|
||||||
|
|
||||||
|
|
||||||
|
def dump(client, message):
|
||||||
|
print(message)
|
||||||
|
|
||||||
|
|
||||||
|
app.add_handler(MessageHandler(dump))
|
||||||
|
|
||||||
|
app.run()
|
||||||
|
|
||||||
|
.. contents:: Contents
|
||||||
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
|
:local:
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
.. currentmodule:: pyrogram.handlers
|
||||||
|
|
||||||
|
Index
|
||||||
|
-----
|
||||||
|
|
||||||
|
.. hlist::
|
||||||
|
:columns: 3
|
||||||
|
|
||||||
|
- :class:`MessageHandler`
|
||||||
|
- :class:`EditedMessageHandler`
|
||||||
|
- :class:`DeletedMessagesHandler`
|
||||||
|
- :class:`CallbackQueryHandler`
|
||||||
|
- :class:`InlineQueryHandler`
|
||||||
|
- :class:`ChosenInlineResultHandler`
|
||||||
|
- :class:`ChatMemberUpdatedHandler`
|
||||||
|
- :class:`UserStatusHandler`
|
||||||
|
- :class:`PollHandler`
|
||||||
|
- :class:`DisconnectHandler`
|
||||||
|
- :class:`RawUpdateHandler`
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
Details
|
||||||
|
-------
|
||||||
|
|
||||||
|
.. Handlers
|
||||||
|
.. autoclass:: MessageHandler()
|
||||||
|
.. autoclass:: EditedMessageHandler()
|
||||||
|
.. autoclass:: DeletedMessagesHandler()
|
||||||
|
.. autoclass:: CallbackQueryHandler()
|
||||||
|
.. autoclass:: InlineQueryHandler()
|
||||||
|
.. autoclass:: ChosenInlineResultHandler()
|
||||||
|
.. autoclass:: ChatMemberUpdatedHandler()
|
||||||
|
.. autoclass:: UserStatusHandler()
|
||||||
|
.. autoclass:: PollHandler()
|
||||||
|
.. autoclass:: DisconnectHandler()
|
||||||
|
.. autoclass:: RawUpdateHandler()
|
||||||
92
docs/source/conf.py
Normal file
92
docs/source/conf.py
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
# Pyrofork - Telegram MTProto API Client Library for Python
|
||||||
|
# Copyright (C) 2017-present Dan <https://github.com/delivrance>
|
||||||
|
# Copyright (C) 2022-present Mayuri-Chan <https://github.com/Mayuri-Chan>
|
||||||
|
#
|
||||||
|
# This file is part of Pyrofork.
|
||||||
|
#
|
||||||
|
# Pyrofork 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.
|
||||||
|
#
|
||||||
|
# Pyrofork 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 Pyrofork. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.abspath("../.."))
|
||||||
|
|
||||||
|
from pyrogram import __version__
|
||||||
|
|
||||||
|
from pygments.styles.friendly import FriendlyStyle
|
||||||
|
|
||||||
|
FriendlyStyle.background_color = "#f3f2f1"
|
||||||
|
|
||||||
|
project = "Pyrofork"
|
||||||
|
copyright = f"2022-present, Mayuri-Chan"
|
||||||
|
author = "Mayuri-Chan"
|
||||||
|
|
||||||
|
version = ".".join(__version__.split(".")[:-1])
|
||||||
|
|
||||||
|
extensions = [
|
||||||
|
"sphinx.ext.autodoc",
|
||||||
|
"sphinx.ext.napoleon",
|
||||||
|
"sphinx.ext.autosummary",
|
||||||
|
"sphinx.ext.intersphinx",
|
||||||
|
"sphinx_copybutton"
|
||||||
|
]
|
||||||
|
|
||||||
|
intersphinx_mapping = {
|
||||||
|
"python": ("https://docs.python.org/3", None)
|
||||||
|
}
|
||||||
|
|
||||||
|
master_doc = "index"
|
||||||
|
source_suffix = ".rst"
|
||||||
|
autodoc_member_order = "bysource"
|
||||||
|
|
||||||
|
templates_path = ["../resources/templates"]
|
||||||
|
html_copy_source = False
|
||||||
|
|
||||||
|
napoleon_use_rtype = False
|
||||||
|
napoleon_use_param = False
|
||||||
|
|
||||||
|
pygments_style = "friendly"
|
||||||
|
|
||||||
|
copybutton_prompt_text = "$ "
|
||||||
|
|
||||||
|
suppress_warnings = ["image.not_readable"]
|
||||||
|
|
||||||
|
html_title = "Pyrofork Documentation"
|
||||||
|
html_theme = "sphinx_rtd_theme"
|
||||||
|
html_static_path = ["../resources/static"]
|
||||||
|
html_show_sourcelink = True
|
||||||
|
html_show_copyright = False
|
||||||
|
html_theme_options = {
|
||||||
|
"canonical_url": "https://pyrofork.mayuri.my.id/",
|
||||||
|
"collapse_navigation": True,
|
||||||
|
"sticky_navigation": False,
|
||||||
|
"logo_only": True,
|
||||||
|
"display_version": False,
|
||||||
|
"style_external_links": True
|
||||||
|
}
|
||||||
|
|
||||||
|
html_logo = "../resources/static/img/pyrogram.png"
|
||||||
|
html_favicon = "../resources/static/img/favicon.ico"
|
||||||
|
|
||||||
|
latex_engine = "xelatex"
|
||||||
|
latex_logo = "../resources/static/img/pyrogram.png"
|
||||||
|
|
||||||
|
latex_elements = {
|
||||||
|
"pointsize": "12pt",
|
||||||
|
"fontpkg": r"""
|
||||||
|
\setmainfont{Open Sans}
|
||||||
|
\setsansfont{Bitter}
|
||||||
|
\setmonofont{Ubuntu Mono}
|
||||||
|
"""
|
||||||
|
}
|
||||||
11
docs/source/faq/client-started-but-nothing-happens.rst
Normal file
11
docs/source/faq/client-started-but-nothing-happens.rst
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
Client started, but nothing happens
|
||||||
|
===================================
|
||||||
|
|
||||||
|
A possible cause might be network issues, either yours or Telegram's. To check this, add the following code at
|
||||||
|
the top of your script and run it again. You should see some error mentioning a socket timeout or an unreachable
|
||||||
|
network:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
import logging
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
Code hangs when calling stop, restart, add/remove_handler
|
||||||
|
=========================================================
|
||||||
|
|
||||||
|
You tried to ``.stop()``, ``.restart()``, ``.add_handler()`` or ``.remove_handler()`` inside a running handler, but
|
||||||
|
that can't be done because the way Pyrofork deals with handlers would make it hang.
|
||||||
|
|
||||||
|
When calling one of the methods above inside an event handler, Pyrofork needs to wait for all running handlers to finish
|
||||||
|
in order to continue. Since your handler is blocking the execution by waiting for the called method to finish
|
||||||
|
and since Pyrofork needs to wait for your handler to finish, you are left with a deadlock.
|
||||||
|
|
||||||
|
The solution to this problem is to pass ``block=False`` to such methods so that they return immediately and the actual
|
||||||
|
code called asynchronously.
|
||||||
23
docs/source/faq/how-to-avoid-flood-waits.rst
Normal file
23
docs/source/faq/how-to-avoid-flood-waits.rst
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
How to avoid Flood Waits?
|
||||||
|
=========================
|
||||||
|
|
||||||
|
Slow things down and make less requests. Moreover, exact limits are unknown and can change anytime based on normal
|
||||||
|
usages.
|
||||||
|
|
||||||
|
When a flood wait happens the server will tell you how much time to wait before continuing.
|
||||||
|
The following shows how to catch the exception in your code and wait the required seconds.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
from pyrogram.errors import FloodWait
|
||||||
|
|
||||||
|
...
|
||||||
|
try:
|
||||||
|
... # Your code
|
||||||
|
except FloodWait as e:
|
||||||
|
await asyncio.sleep(e.value) # Wait "value" seconds before continuing
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
More info about error handling can be found :doc:`here <../start/errors>`.
|
||||||
9
docs/source/faq/how-to-use-webhooks.rst
Normal file
9
docs/source/faq/how-to-use-webhooks.rst
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
How to use webhooks?
|
||||||
|
====================
|
||||||
|
|
||||||
|
There is no webhook in Pyrofork, simply because there is no HTTP involved. However, a similar technique is
|
||||||
|
being used to make receiving updates efficient.
|
||||||
|
|
||||||
|
Pyrofork uses persistent connections via TCP sockets to interact with the server and instead of actively asking for
|
||||||
|
updates every time (polling), Pyrofork will sit down and wait for the server to send updates by itself the very moment
|
||||||
|
they are available (server push).
|
||||||
45
docs/source/faq/index.rst
Normal file
45
docs/source/faq/index.rst
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
Frequently Asked Questions
|
||||||
|
==========================
|
||||||
|
|
||||||
|
This FAQ page provides answers to common questions about Pyrofork and, to some extent, Telegram in general.
|
||||||
|
|
||||||
|
**Contents**
|
||||||
|
|
||||||
|
- :doc:`why-is-the-api-key-needed-for-bots`
|
||||||
|
- :doc:`how-to-use-webhooks`
|
||||||
|
- :doc:`using-the-same-file-id-across-different-accounts`
|
||||||
|
- :doc:`using-multiple-clients-at-once-on-the-same-account`
|
||||||
|
- :doc:`client-started-but-nothing-happens`
|
||||||
|
- :doc:`what-are-the-ip-addresses-of-telegram-data-centers`
|
||||||
|
- :doc:`migrating-the-account-to-another-data-center`
|
||||||
|
- :doc:`why-is-the-client-reacting-slowly-in-supergroups-channels`
|
||||||
|
- :doc:`peer-id-invalid-error`
|
||||||
|
- :doc:`code-hangs-when-calling-stop-restart-add-remove-handler`
|
||||||
|
- :doc:`unicodeencodeerror-codec-cant-encode`
|
||||||
|
- :doc:`uploading-with-urls-gives-error-webpage-curl-failed`
|
||||||
|
- :doc:`sqlite3-operationalerror-database-is-locked`
|
||||||
|
- :doc:`sqlite3-interfaceerror-error-binding-parameter`
|
||||||
|
- :doc:`socket-send-oserror-timeouterror-connection-lost-reset`
|
||||||
|
- :doc:`how-to-avoid-flood-waits`
|
||||||
|
- :doc:`the-account-has-been-limited-deactivated`
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:hidden:
|
||||||
|
|
||||||
|
why-is-the-api-key-needed-for-bots
|
||||||
|
how-to-use-webhooks
|
||||||
|
using-the-same-file-id-across-different-accounts
|
||||||
|
using-multiple-clients-at-once-on-the-same-account
|
||||||
|
client-started-but-nothing-happens
|
||||||
|
what-are-the-ip-addresses-of-telegram-data-centers
|
||||||
|
migrating-the-account-to-another-data-center
|
||||||
|
why-is-the-client-reacting-slowly-in-supergroups-channels
|
||||||
|
peer-id-invalid-error
|
||||||
|
code-hangs-when-calling-stop-restart-add-remove-handler
|
||||||
|
unicodeencodeerror-codec-cant-encode
|
||||||
|
uploading-with-urls-gives-error-webpage-curl-failed
|
||||||
|
sqlite3-operationalerror-database-is-locked
|
||||||
|
sqlite3-interfaceerror-error-binding-parameter
|
||||||
|
socket-send-oserror-timeouterror-connection-lost-reset
|
||||||
|
how-to-avoid-flood-waits
|
||||||
|
the-account-has-been-limited-deactivated
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
Migrating the account to another data center
|
||||||
|
============================================
|
||||||
|
|
||||||
|
This question is asked by people who find their account always being connected to one DC (data center), but are
|
||||||
|
connecting from a place far away, thus resulting in slower interactions when using the API because of the greater
|
||||||
|
physical distance between the user and the 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. It's also up to the server to decide whether to automatically migrate a user in case of prolonged usages
|
||||||
|
from a distant location.
|
||||||
14
docs/source/faq/peer-id-invalid-error.rst
Normal file
14
docs/source/faq/peer-id-invalid-error.rst
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
PEER_ID_INVALID error
|
||||||
|
=====================
|
||||||
|
|
||||||
|
This error could mean several things:
|
||||||
|
|
||||||
|
- The chat id you tried to use is simply wrong, check it again.
|
||||||
|
- The chat id refers to a group or channel you are not a member of.
|
||||||
|
- The chat id argument you passed is in form of a string; you have to convert it into an integer with ``int(chat_id)``.
|
||||||
|
- The chat id refers to a user or chat your current session hasn't met yet.
|
||||||
|
|
||||||
|
About the last point: in order for you to meet a user and thus communicate with them, you should ask yourself how to
|
||||||
|
contact people using official apps. The answer is the same for Pyrofork too and involves normal usages such as searching
|
||||||
|
for usernames, meeting them in a common group, having their phone contacts saved, getting a message mentioning them
|
||||||
|
or obtaining the dialogs list.
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
socket.send(), OSError(), TimeoutError(), Connection lost/reset
|
||||||
|
===============================================================
|
||||||
|
|
||||||
|
If you get any of these errors chances are you ended up with a slow or inconsistent network connection.
|
||||||
|
Another reason could be because you are blocking the event loop for too long.
|
||||||
|
|
||||||
|
You can consider the following:
|
||||||
|
|
||||||
|
- Use Pyrofork asynchronously in its intended way.
|
||||||
|
- Use shorter non-asynchronous processing loops.
|
||||||
|
- Use ``asyncio.sleep()`` instead of ``time.sleep()``.
|
||||||
|
- Use a stable network connection.
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
sqlite3.InterfaceError: Error binding parameter
|
||||||
|
===============================================
|
||||||
|
|
||||||
|
This error occurs when you pass a chat id value of the wrong type when trying to call a method. Most likely, you
|
||||||
|
accidentally passed the whole user or chat object instead of the id or username.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
# Wrong. You passed the whole Chat instance
|
||||||
|
app.send_message(chat, "text")
|
||||||
|
|
||||||
|
# Correct
|
||||||
|
app.send_message(chat.id, "text")
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
sqlite3.OperationalError: database is locked
|
||||||
|
============================================
|
||||||
|
|
||||||
|
This error occurs when more than one process is using the same session file, that is, when you run two or more clients
|
||||||
|
at the same time using the same session name or in case another program has accessed the file.
|
||||||
|
|
||||||
|
For example, it could occur when a background script is still running and you forgot about it. In this case, you either
|
||||||
|
restart your system or find and kill the process that is locking the database. On Unix based systems, you can try the
|
||||||
|
following:
|
||||||
|
|
||||||
|
#. ``cd`` into your session file directory.
|
||||||
|
#. ``fuser my_account.session`` to find the process id.
|
||||||
|
#. ``kill 1234`` to gracefully stop the process.
|
||||||
|
#. If the last command doesn't help, use ``kill -9 1234`` instead.
|
||||||
|
|
||||||
|
If you want to run multiple clients on the same account, you must authorize your account (either user or bot)
|
||||||
|
from the beginning every time, and use different session names for each parallel client you are going to use.
|
||||||
16
docs/source/faq/the-account-has-been-limited-deactivated.rst
Normal file
16
docs/source/faq/the-account-has-been-limited-deactivated.rst
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
The account has been limited/deactivated
|
||||||
|
========================================
|
||||||
|
|
||||||
|
Pyrofork is a framework that interfaces with Telegram; it is at your commands, meaning it only does what you tell it to
|
||||||
|
do, the rest is up to you and Telegram (see `Telegram's ToS`_).
|
||||||
|
|
||||||
|
If you found your account being limited/deactivated, it could be due spam/flood/abuse of the API or the usage of certain
|
||||||
|
virtual/VoIP numbers.
|
||||||
|
|
||||||
|
If you think your account was limited/deactivated by mistake, you can write to recover@telegram.org, contact
|
||||||
|
`@SpamBot`_ or use `this form`_.
|
||||||
|
|
||||||
|
.. _@SpamBot: https://t.me/spambot
|
||||||
|
.. _this form: https://telegram.org/support
|
||||||
|
.. _Telegram's ToS: https://telegram.org/tos
|
||||||
|
|
||||||
7
docs/source/faq/unicodeencodeerror-codec-cant-encode.rst
Normal file
7
docs/source/faq/unicodeencodeerror-codec-cant-encode.rst
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
UnicodeEncodeError: '...' codec can't encode ...
|
||||||
|
================================================
|
||||||
|
|
||||||
|
Where ``<encoding>`` might be *ascii*, *cp932*, *charmap* or anything else other than *utf-8*. This error usually
|
||||||
|
shows up when you try to print something and has very little to do with Pyrofork itself as it is strictly related to
|
||||||
|
your own terminal. To fix it, either find a way to change the encoding settings of your terminal to UTF-8 or switch to
|
||||||
|
another terminal altogether.
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
Uploading with URLs gives error WEBPAGE_CURL_FAILED
|
||||||
|
===================================================
|
||||||
|
|
||||||
|
When uploading media files using an URL, the server automatically tries to download the media and uploads it to the
|
||||||
|
Telegram cloud. This error usually happens in case the provided URL is not publicly accessible by Telegram itself or the
|
||||||
|
media file is too large. In such cases, your only option is to download the media yourself and upload it from your
|
||||||
|
local machine.
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
Using multiple clients at once on the same account
|
||||||
|
==================================================
|
||||||
|
|
||||||
|
Both user and bot accounts are able to run multiple sessions in parallel. However, you must not use the same session
|
||||||
|
in more than one client at the same time. The correct way to run multiple clients on the same account is by authorizing
|
||||||
|
your account (either user or bot) from the beginning each time, and use one separate session for each parallel client.
|
||||||
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
Using the same file_id across different accounts
|
||||||
|
================================================
|
||||||
|
|
||||||
|
Telegram file_id strings are bound to the account which generated them. An attempt in using a foreign file id will
|
||||||
|
result in errors such as ``[400 MEDIA_EMPTY]``. The only exception are stickers' file ids; you can use them across
|
||||||
|
different accounts without any problem.
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
What are the IP addresses of Telegram Data Centers?
|
||||||
|
===================================================
|
||||||
|
|
||||||
|
Telegram is currently composed by a decentralized, multi-DC infrastructure (currently 5 DCs, each of which can
|
||||||
|
work independently) spread across different locations worldwide. However, some of the less busy DCs have been lately
|
||||||
|
dismissed and their IP addresses are now kept as aliases to the nearest one.
|
||||||
|
|
||||||
|
.. csv-table:: Production Environment
|
||||||
|
:header: ID, Location, IPv4, IPv6
|
||||||
|
:widths: auto
|
||||||
|
:align: center
|
||||||
|
|
||||||
|
DC1, "MIA, Miami FL, USA", ``149.154.175.53``, ``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.130``, ``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``
|
||||||
|
|
||||||
|
.. centered:: More info about the Test Environment can be found :doc:`here <../topics/test-servers>`.
|
||||||
|
|
||||||
|
***** Alias DC
|
||||||
12
docs/source/faq/why-is-the-api-key-needed-for-bots.rst
Normal file
12
docs/source/faq/why-is-the-api-key-needed-for-bots.rst
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
Why is the API key needed for bots?
|
||||||
|
===================================
|
||||||
|
|
||||||
|
Requests against the official bot API endpoints are made via JSON/HTTP and are handled by an intermediate server
|
||||||
|
application that implements the MTProto protocol and uses its own API key to communicate with the MTProto servers.
|
||||||
|
|
||||||
|
.. figure:: //_static/img/mtproto-vs-bot-api.png
|
||||||
|
:align: center
|
||||||
|
|
||||||
|
Using MTProto is the only way to communicate with the actual Telegram servers, and the main API requires developers to
|
||||||
|
identify applications by means of a unique key; the bot token identifies a bot as a user and replaces the user's phone
|
||||||
|
number only.
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
Why is the client reacting slowly in supergroups/channels?
|
||||||
|
==========================================================
|
||||||
|
|
||||||
|
Because of how Telegram works internally, every message you receive and send must pass through the creator's DC, and in
|
||||||
|
the worst case where you, the creator and another member all belong to three different DCs, the other member messages
|
||||||
|
have to go through from their DC to the creator's DC and finally to your DC. This is applied to each message and member
|
||||||
|
of a supergroup/channel and the process will inevitably take its time.
|
||||||
|
|
||||||
|
Another reason that makes responses come slowly is that messages are dispatched by priority. Depending on the kind
|
||||||
|
of member, some users receive messages faster than others and for big and busy supergroups the delay might become
|
||||||
|
noticeable, especially if you are among the lower end of the priority list:
|
||||||
|
|
||||||
|
1. Creator.
|
||||||
|
2. Administrators.
|
||||||
|
3. Bots.
|
||||||
|
4. Mentioned users.
|
||||||
|
5. Recent online users.
|
||||||
|
6. Everyone else.
|
||||||
158
docs/source/index.rst
Normal file
158
docs/source/index.rst
Normal file
|
|
@ -0,0 +1,158 @@
|
||||||
|
Welcome to Pyrofork
|
||||||
|
===================
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<a href="/">
|
||||||
|
<div class="pyrogram-logo-index"><img src="_static/pyrogram.png" alt="Pyrofork"></div>
|
||||||
|
<div class="pyrogram-text pyrogram-text-index">Pyrofork</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<b>Telegram MTProto API Framework for Python</b>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
<a href="https://github.com/Mayuri-Chan/pyrofork">
|
||||||
|
Development
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import Client, filters
|
||||||
|
|
||||||
|
app = Client("my_account")
|
||||||
|
|
||||||
|
|
||||||
|
@app.on_message(filters.private)
|
||||||
|
async def hello(client, message):
|
||||||
|
await message.reply("Hello from Pyrofork!")
|
||||||
|
|
||||||
|
|
||||||
|
app.run()
|
||||||
|
|
||||||
|
**Pyrofork** is a modern, elegant and asynchronous :doc:`MTProto API <topics/mtproto-vs-botapi>` framework.
|
||||||
|
It enables you to easily interact with the main Telegram API through a user account (custom client) or a bot identity
|
||||||
|
(bot API alternative) using Python.
|
||||||
|
|
||||||
|
Support
|
||||||
|
-------
|
||||||
|
|
||||||
|
If you'd like to support Pyrofork, you can consider:
|
||||||
|
|
||||||
|
- `Become a GitHub sponsor <https://github.com/sponsors/Mayuri-Chan>`_.
|
||||||
|
|
||||||
|
How the Documentation is Organized
|
||||||
|
----------------------------------
|
||||||
|
|
||||||
|
Contents are organized into sections composed of self-contained topics which can be all accessed from the sidebar, or by
|
||||||
|
following them in order using the :guilabel:`Next` button at the end of each page.
|
||||||
|
You can also switch to Dark or Light theme or leave on Auto (follows system preferences) by using the dedicated button
|
||||||
|
in the top left corner.
|
||||||
|
|
||||||
|
Here below you can, instead, find a list of the most relevant pages for a quick access.
|
||||||
|
|
||||||
|
First Steps
|
||||||
|
^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. hlist::
|
||||||
|
:columns: 1
|
||||||
|
|
||||||
|
- :doc:`Quick Start <intro/quickstart>`: Overview to get you started quickly.
|
||||||
|
- :doc:`Invoking Methods <start/invoking>`: How to call Pyrofork's methods.
|
||||||
|
- :doc:`Handling Updates <start/updates>`: How to handle Telegram updates.
|
||||||
|
- :doc:`Error Handling <start/errors>`: How to handle API errors correctly.
|
||||||
|
|
||||||
|
API Reference
|
||||||
|
^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. hlist::
|
||||||
|
:columns: 1
|
||||||
|
|
||||||
|
- :doc:`Pyrofork Client <api/client>`: Reference details about the Client class.
|
||||||
|
- :doc:`Available Methods <api/methods/index>`: List of available high-level methods.
|
||||||
|
- :doc:`Available Types <api/types/index>`: List of available high-level types.
|
||||||
|
- :doc:`Enumerations <api/enums/index>`: List of available enumerations.
|
||||||
|
- :doc:`Bound Methods <api/bound-methods/index>`: List of convenient bound methods.
|
||||||
|
|
||||||
|
Meta
|
||||||
|
^^^^
|
||||||
|
|
||||||
|
.. hlist::
|
||||||
|
:columns: 1
|
||||||
|
|
||||||
|
- :doc:`Pyrofork FAQ <faq/index>`: Answers to common Pyrofork questions.
|
||||||
|
- :doc:`Support Pyrofork <support>`: Ways to show your appreciation.
|
||||||
|
- :doc:`Release Notes <releases/index>`: Release notes for Pyrofork releases.
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:hidden:
|
||||||
|
:caption: Introduction
|
||||||
|
|
||||||
|
intro/quickstart
|
||||||
|
intro/install
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:hidden:
|
||||||
|
:caption: Getting Started
|
||||||
|
|
||||||
|
start/setup
|
||||||
|
start/auth
|
||||||
|
start/invoking
|
||||||
|
start/updates
|
||||||
|
start/errors
|
||||||
|
start/examples/index
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:hidden:
|
||||||
|
:caption: API Reference
|
||||||
|
|
||||||
|
api/client
|
||||||
|
api/methods/index
|
||||||
|
api/types/index
|
||||||
|
api/bound-methods/index
|
||||||
|
api/enums/index
|
||||||
|
api/handlers
|
||||||
|
api/decorators
|
||||||
|
api/errors/index
|
||||||
|
api/filters
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:hidden:
|
||||||
|
:caption: Topic Guides
|
||||||
|
|
||||||
|
topics/use-filters
|
||||||
|
topics/create-filters
|
||||||
|
topics/more-on-updates
|
||||||
|
topics/client-settings
|
||||||
|
topics/speedups
|
||||||
|
topics/text-formatting
|
||||||
|
topics/synchronous
|
||||||
|
topics/smart-plugins
|
||||||
|
topics/storage-engines
|
||||||
|
topics/serializing
|
||||||
|
topics/proxy
|
||||||
|
topics/scheduling
|
||||||
|
topics/mtproto-vs-botapi
|
||||||
|
topics/debugging
|
||||||
|
topics/test-servers
|
||||||
|
topics/advanced-usage
|
||||||
|
topics/voice-calls
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:hidden:
|
||||||
|
:caption: Meta
|
||||||
|
|
||||||
|
faq/index
|
||||||
|
support
|
||||||
|
releases/index
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:hidden:
|
||||||
|
:caption: Telegram Raw API
|
||||||
|
|
||||||
|
telegram/functions/index
|
||||||
|
telegram/types/index
|
||||||
|
telegram/base/index
|
||||||
50
docs/source/intro/install.rst
Normal file
50
docs/source/intro/install.rst
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
Install Guide
|
||||||
|
=============
|
||||||
|
|
||||||
|
Being a modern Python framework, Pyrofork requires an up to date version of Python to be installed in your system.
|
||||||
|
We recommend using the latest versions of both Python 3 and pip.
|
||||||
|
|
||||||
|
.. contents:: Contents
|
||||||
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
|
:local:
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
Install Pyrofork
|
||||||
|
----------------
|
||||||
|
|
||||||
|
- The easiest way to install and upgrade Pyrofork to its latest stable version is by using **pip**:
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
$ pip3 install -U pyrofork
|
||||||
|
|
||||||
|
- or, with :doc:`TgCrypto <../topics/speedups>` as extra requirement (recommended):
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
$ pip3 install -U pyrofork tgcrypto
|
||||||
|
|
||||||
|
Bleeding Edge
|
||||||
|
-------------
|
||||||
|
|
||||||
|
You can install the development version from the git ``master`` branch using this command:
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
$ pip3 install -U https://github.com/Mayuri-Chan/pyrofork/archive/master.zip
|
||||||
|
|
||||||
|
Verifying
|
||||||
|
---------
|
||||||
|
|
||||||
|
To verify that Pyrofork is correctly installed, open a Python shell and import it.
|
||||||
|
If no error shows up you are good to go.
|
||||||
|
|
||||||
|
.. parsed-literal::
|
||||||
|
|
||||||
|
>>> import pyrogram
|
||||||
|
>>> pyrogram.__version__
|
||||||
|
'x.y.z'
|
||||||
|
|
||||||
|
.. _`Github repo`: http://github.com/Mayuri-Chan/pyrofork
|
||||||
56
docs/source/intro/quickstart.rst
Normal file
56
docs/source/intro/quickstart.rst
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
Quick Start
|
||||||
|
===========
|
||||||
|
|
||||||
|
The next few steps serve as a quick start to see Pyrofork in action as fast as possible.
|
||||||
|
|
||||||
|
Get Pyrofork Real Fast
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
.. admonition :: Cloud Credits
|
||||||
|
:class: tip
|
||||||
|
|
||||||
|
If you need a cloud server to host your applications, try Hetzner Cloud. You can sign up with
|
||||||
|
`this link <https://hetzner.cloud/?ref=9CyT92gZEINU>`_ to get €20 in cloud credits.
|
||||||
|
|
||||||
|
1. Install Pyrofork with ``pip3 install -U pyrogram``.
|
||||||
|
|
||||||
|
2. Get your own Telegram API key from https://my.telegram.org/apps.
|
||||||
|
|
||||||
|
3. Open the text editor of your choice and paste the following:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
from pyrogram import Client
|
||||||
|
|
||||||
|
api_id = 12345
|
||||||
|
api_hash = "0123456789abcdef0123456789abcdef"
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
async with Client("my_account", api_id, api_hash) as app:
|
||||||
|
await app.send_message("me", "Greetings from **Pyrofork**!")
|
||||||
|
|
||||||
|
|
||||||
|
asyncio.run(main())
|
||||||
|
|
||||||
|
4. Replace *api_id* and *api_hash* values with your own.
|
||||||
|
|
||||||
|
5. Save the file as ``hello.py``.
|
||||||
|
|
||||||
|
6. Run the script with ``python3 hello.py``
|
||||||
|
|
||||||
|
7. Follow the instructions on your terminal to login.
|
||||||
|
|
||||||
|
8. Watch Pyrofork send a message to yourself.
|
||||||
|
|
||||||
|
Enjoy the API
|
||||||
|
-------------
|
||||||
|
|
||||||
|
That was just a quick overview. In the next few pages of the introduction, we'll take a much more in-depth look of what
|
||||||
|
we have just done above.
|
||||||
|
|
||||||
|
If you are feeling eager to continue you can take a shortcut to :doc:`../start/invoking` and come back
|
||||||
|
later to learn some more details.
|
||||||
|
|
||||||
|
.. _community: https://t.me/Pyrofork
|
||||||
93
docs/source/start/auth.rst
Normal file
93
docs/source/start/auth.rst
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
Authorization
|
||||||
|
=============
|
||||||
|
|
||||||
|
Once a :doc:`project is set up <setup>`, you will still have to follow a few steps before you can actually use Pyrofork to make
|
||||||
|
API calls. This section provides all the information you need in order to authorize yourself as user or bot.
|
||||||
|
|
||||||
|
.. contents:: Contents
|
||||||
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
|
:local:
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
User Authorization
|
||||||
|
------------------
|
||||||
|
|
||||||
|
In order to use the API, Telegram requires that users be authorized via their phone numbers.
|
||||||
|
Pyrofork automatically manages this process, all you need to do is create an instance of the
|
||||||
|
:class:`~pyrogram.Client` class by passing to it a ``name`` of your choice (e.g.: "my_account") and call
|
||||||
|
the :meth:`~pyrogram.Client.run` method:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import Client
|
||||||
|
|
||||||
|
api_id = 12345
|
||||||
|
api_hash = "0123456789abcdef0123456789abcdef"
|
||||||
|
|
||||||
|
app = Client("my_account", api_id=api_id, api_hash=api_hash)
|
||||||
|
|
||||||
|
app.run()
|
||||||
|
|
||||||
|
This starts an interactive shell asking you to input your **phone number**, including your `Country Code`_ (the plus
|
||||||
|
``+`` and minus ``-`` symbols can be omitted) and the **phone code** you will receive in your devices that are already
|
||||||
|
authorized or via SMS:
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
Enter phone number: +1-123-456-7890
|
||||||
|
Is "+1-123-456-7890" correct? (y/n): y
|
||||||
|
Enter phone code: 12345
|
||||||
|
Logged in successfully
|
||||||
|
|
||||||
|
After successfully authorizing yourself, a new file called ``my_account.session`` will be created allowing Pyrofork to
|
||||||
|
execute API calls with your identity. This file is personal and will be loaded again when you restart your app.
|
||||||
|
You can now remove the api_id and api_hash values from the code as they are not needed anymore.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The code above does nothing except asking for credentials and keeping the client online, hit :guilabel:`CTRL+C` now
|
||||||
|
to stop your application and keep reading.
|
||||||
|
|
||||||
|
Bot Authorization
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
Bots are a special kind of users that are authorized via their tokens (instead of phone numbers), which are created by
|
||||||
|
the `Bot Father`_. Bot tokens replace the users' phone numbers only — you still need to
|
||||||
|
:doc:`configure a Telegram API key <../start/setup>` with Pyrofork, even when using bots.
|
||||||
|
|
||||||
|
The authorization process is automatically managed. All you need to do is choose a ``name`` (can be anything,
|
||||||
|
usually your bot username) and pass your bot token using the ``bot_token`` parameter. The session file will be named
|
||||||
|
after the session name, which will be ``my_bot.session`` for the example below.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import Client
|
||||||
|
|
||||||
|
api_id = 12345
|
||||||
|
api_hash = "0123456789abcdef0123456789abcdef"
|
||||||
|
bot_token = "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"
|
||||||
|
|
||||||
|
app = Client(
|
||||||
|
"my_bot",
|
||||||
|
api_id=api_id, api_hash=api_hash,
|
||||||
|
bot_token=bot_token
|
||||||
|
)
|
||||||
|
|
||||||
|
app.run()
|
||||||
|
|
||||||
|
.. _Country Code: https://en.wikipedia.org/wiki/List_of_country_calling_codes
|
||||||
|
.. _Bot Father: https://t.me/botfather
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The API key (api_id and api_hash) and the bot_token are not required anymore after a successful authorization.
|
||||||
|
This means you can now simply use the following:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import Client
|
||||||
|
|
||||||
|
app = Client("my_account")
|
||||||
|
app.run()
|
||||||
101
docs/source/start/errors.rst
Normal file
101
docs/source/start/errors.rst
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
Error Handling
|
||||||
|
==============
|
||||||
|
|
||||||
|
Errors can be correctly handled with ``try...except`` blocks in order to control the behaviour of your application.
|
||||||
|
Pyrofork errors all live inside the ``errors`` package:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import errors
|
||||||
|
|
||||||
|
.. contents:: Contents
|
||||||
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
|
:local:
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
RPCError
|
||||||
|
--------
|
||||||
|
|
||||||
|
The father of all errors is named ``RPCError`` and is able to catch all Telegram API related errors.
|
||||||
|
This error is raised every time a method call against Telegram's API was unsuccessful.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram.errors import RPCError
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
Avoid catching this error everywhere, especially when no feedback is given (i.e. by logging/printing the full error
|
||||||
|
traceback), because it makes it impossible to understand what went wrong.
|
||||||
|
|
||||||
|
Error Categories
|
||||||
|
----------------
|
||||||
|
|
||||||
|
The ``RPCError`` packs together all the possible errors Telegram could raise, but to make things tidier, Pyrofork
|
||||||
|
provides categories of errors, which are named after the common HTTP errors and are subclass-ed from the ``RPCError``:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram.errors import BadRequest, Forbidden, ...
|
||||||
|
|
||||||
|
- :doc:`303 - SeeOther <../api/errors/see-other>`
|
||||||
|
- :doc:`400 - BadRequest <../api/errors/bad-request>`
|
||||||
|
- :doc:`401 - Unauthorized <../api/errors/unauthorized>`
|
||||||
|
- :doc:`403 - Forbidden <../api/errors/forbidden>`
|
||||||
|
- :doc:`406 - NotAcceptable <../api/errors/not-acceptable>`
|
||||||
|
- :doc:`420 - Flood <../api/errors/flood>`
|
||||||
|
- :doc:`500 - InternalServerError <../api/errors/internal-server-error>`
|
||||||
|
|
||||||
|
Single Errors
|
||||||
|
-------------
|
||||||
|
|
||||||
|
For a fine-grained control over every single error, Pyrofork does also expose errors that deal each with a specific
|
||||||
|
issue. For example:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram.errors import FloodWait
|
||||||
|
|
||||||
|
These errors subclass directly from the category of errors they belong to, which in turn subclass from the father
|
||||||
|
``RPCError``, thus building a class of error hierarchy such as this:
|
||||||
|
|
||||||
|
- RPCError
|
||||||
|
- BadRequest
|
||||||
|
- ``MessageEmpty``
|
||||||
|
- ``UsernameOccupied``
|
||||||
|
- ``...``
|
||||||
|
- InternalServerError
|
||||||
|
- ``RpcCallFail``
|
||||||
|
- ``InterDcCallError``
|
||||||
|
- ``...``
|
||||||
|
- ``...``
|
||||||
|
|
||||||
|
.. _Errors: api/errors
|
||||||
|
|
||||||
|
Unknown Errors
|
||||||
|
--------------
|
||||||
|
|
||||||
|
In case Pyrofork does not know anything about a specific error yet, it raises a generic error from its known category,
|
||||||
|
for example, an unknown error with error code ``400``, will be raised as a ``BadRequest``. This way you can catch the
|
||||||
|
whole category of errors and be sure to also handle these unknown errors.
|
||||||
|
|
||||||
|
Errors with Values
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Exception objects may also contain some informative values. For example, ``FloodWait`` holds the amount of seconds you
|
||||||
|
have to wait before you can try again, some other errors contain the DC number on which the request must be repeated on.
|
||||||
|
The value is stored in the ``value`` attribute of the exception object:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
from pyrogram.errors import FloodWait
|
||||||
|
|
||||||
|
...
|
||||||
|
try:
|
||||||
|
... # Your code
|
||||||
|
except FloodWait as e:
|
||||||
|
await asyncio.sleep(e.value) # Wait N seconds before continuing
|
||||||
|
...
|
||||||
68
docs/source/start/examples/bot_keyboards.rst
Normal file
68
docs/source/start/examples/bot_keyboards.rst
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
bot_keyboards
|
||||||
|
=============
|
||||||
|
|
||||||
|
This example will show you how to send normal and inline keyboards (as bot).
|
||||||
|
|
||||||
|
You must log-in as a regular bot in order to send keyboards (use the token from @BotFather).
|
||||||
|
Any attempt in sending keyboards with a user account will be simply ignored by the server.
|
||||||
|
|
||||||
|
send_message() is used as example, but a keyboard can be sent with any other send_* methods,
|
||||||
|
like send_audio(), send_document(), send_location(), etc...
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import Client
|
||||||
|
from pyrogram.types import (ReplyKeyboardMarkup, InlineKeyboardMarkup,
|
||||||
|
InlineKeyboardButton)
|
||||||
|
|
||||||
|
# Create a client using your bot token
|
||||||
|
app = Client("my_bot", bot_token="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11")
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
async with app:
|
||||||
|
await app.send_message(
|
||||||
|
"me", # Edit this
|
||||||
|
"This is a ReplyKeyboardMarkup example",
|
||||||
|
reply_markup=ReplyKeyboardMarkup(
|
||||||
|
[
|
||||||
|
["A", "B", "C", "D"], # First row
|
||||||
|
["E", "F", "G"], # Second row
|
||||||
|
["H", "I"], # Third row
|
||||||
|
["J"] # Fourth row
|
||||||
|
],
|
||||||
|
resize_keyboard=True # Make the keyboard smaller
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
await app.send_message(
|
||||||
|
"me", # Edit this
|
||||||
|
"This is a InlineKeyboardMarkup example",
|
||||||
|
reply_markup=InlineKeyboardMarkup(
|
||||||
|
[
|
||||||
|
[ # First row
|
||||||
|
InlineKeyboardButton( # Generates a callback query when pressed
|
||||||
|
"Button",
|
||||||
|
callback_data="data"
|
||||||
|
),
|
||||||
|
InlineKeyboardButton( # Opens a web URL
|
||||||
|
"URL",
|
||||||
|
url="https://pyrofork.mayuri.my.id"
|
||||||
|
),
|
||||||
|
],
|
||||||
|
[ # Second row
|
||||||
|
InlineKeyboardButton( # Opens the inline interface
|
||||||
|
"Choose chat",
|
||||||
|
switch_inline_query="pyrogram"
|
||||||
|
),
|
||||||
|
InlineKeyboardButton( # Opens the inline interface in the current chat
|
||||||
|
"Inline here",
|
||||||
|
switch_inline_query_current_chat="pyrogram"
|
||||||
|
)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
app.run(main())
|
||||||
21
docs/source/start/examples/callback_queries.rst
Normal file
21
docs/source/start/examples/callback_queries.rst
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
callback_queries
|
||||||
|
================
|
||||||
|
|
||||||
|
This example shows how to handle callback queries, i.e.: queries coming from inline button presses.
|
||||||
|
It uses the @on_callback_query decorator to register a CallbackQueryHandler.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import Client
|
||||||
|
|
||||||
|
app = Client("my_bot", bot_token="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11")
|
||||||
|
|
||||||
|
|
||||||
|
@app.on_callback_query()
|
||||||
|
async def answer(client, callback_query):
|
||||||
|
await callback_query.answer(
|
||||||
|
f"Button contains: '{callback_query.data}'",
|
||||||
|
show_alert=True)
|
||||||
|
|
||||||
|
|
||||||
|
app.run() # Automatically start() and idle()
|
||||||
21
docs/source/start/examples/echo_bot.rst
Normal file
21
docs/source/start/examples/echo_bot.rst
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
echo_bot
|
||||||
|
========
|
||||||
|
|
||||||
|
This simple echo bot replies to every private text message.
|
||||||
|
|
||||||
|
It uses the ``@on_message`` decorator to register a ``MessageHandler`` and applies two filters on it:
|
||||||
|
``filters.text`` and ``filters.private`` to make sure it will reply to private text messages only.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import Client, filters
|
||||||
|
|
||||||
|
app = Client("my_account")
|
||||||
|
|
||||||
|
|
||||||
|
@app.on_message(filters.text & filters.private)
|
||||||
|
async def echo(client, message):
|
||||||
|
await message.reply(message.text)
|
||||||
|
|
||||||
|
|
||||||
|
app.run() # Automatically start() and idle()
|
||||||
20
docs/source/start/examples/get_chat_history.rst
Normal file
20
docs/source/start/examples/get_chat_history.rst
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
get_history
|
||||||
|
===========
|
||||||
|
|
||||||
|
This example shows how to get the full message history of a chat, starting from the latest message.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import Client
|
||||||
|
|
||||||
|
app = Client("my_account")
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
async with app:
|
||||||
|
# "me" refers to your own chat (Saved Messages)
|
||||||
|
async for message in app.get_chat_history("me"):
|
||||||
|
print(message)
|
||||||
|
|
||||||
|
|
||||||
|
app.run(main())
|
||||||
22
docs/source/start/examples/get_chat_members.rst
Normal file
22
docs/source/start/examples/get_chat_members.rst
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
get_chat_members
|
||||||
|
================
|
||||||
|
|
||||||
|
This example shows how to get all the members of a chat.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import Client
|
||||||
|
|
||||||
|
# Target channel/supergroup
|
||||||
|
TARGET = -100123456789
|
||||||
|
|
||||||
|
app = Client("my_account")
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
async with app:
|
||||||
|
async for member in app.get_chat_members(TARGET):
|
||||||
|
print(member)
|
||||||
|
|
||||||
|
|
||||||
|
app.run(main())
|
||||||
19
docs/source/start/examples/get_dialogs.rst
Normal file
19
docs/source/start/examples/get_dialogs.rst
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
get_dialogs
|
||||||
|
===========
|
||||||
|
|
||||||
|
This example shows how to get the full dialogs list (as user).
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import Client
|
||||||
|
|
||||||
|
app = Client("my_account")
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
async with app:
|
||||||
|
async for dialog in app.get_dialogs():
|
||||||
|
print(dialog.chat.title or dialog.chat.first_name)
|
||||||
|
|
||||||
|
|
||||||
|
app.run(main())
|
||||||
20
docs/source/start/examples/hello_world.rst
Normal file
20
docs/source/start/examples/hello_world.rst
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
hello_world
|
||||||
|
===========
|
||||||
|
|
||||||
|
This example demonstrates a basic API usage
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import Client
|
||||||
|
|
||||||
|
# Create a new Client instance
|
||||||
|
app = Client("my_account")
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
async with app:
|
||||||
|
# Send a message, Markdown is enabled by default
|
||||||
|
await app.send_message("me", "Hi there! I'm using **Pyrofork**")
|
||||||
|
|
||||||
|
|
||||||
|
app.run(main())
|
||||||
46
docs/source/start/examples/index.rst
Normal file
46
docs/source/start/examples/index.rst
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
Examples
|
||||||
|
========
|
||||||
|
|
||||||
|
This page contains example scripts to show you how Pyrofork looks like.
|
||||||
|
|
||||||
|
Every script is working right away (provided you correctly set up your credentials), meaning you can simply copy-paste
|
||||||
|
and run. The only things you have to change are session names and target chats, where applicable.
|
||||||
|
|
||||||
|
The examples listed below can be treated as building blocks for your own applications and are meant to be simple enough
|
||||||
|
to give you a basic idea.
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
.. csv-table::
|
||||||
|
:header: Example, Description
|
||||||
|
:widths: auto
|
||||||
|
:align: center
|
||||||
|
|
||||||
|
:doc:`hello_world`, "Demonstration of basic API usage"
|
||||||
|
:doc:`echo_bot`, "Echo every private text message"
|
||||||
|
:doc:`welcome_bot`, "The Welcome Bot in @PyroforkChat"
|
||||||
|
:doc:`get_chat_history`, "Get the full message history of a chat"
|
||||||
|
:doc:`get_chat_members`, "Get all the members of a chat"
|
||||||
|
:doc:`get_dialogs`, "Get all of your dialog chats"
|
||||||
|
:doc:`callback_queries`, "Handle callback queries (as bot) coming from inline button presses"
|
||||||
|
:doc:`inline_queries`, "Handle inline queries (as bot) and answer with results"
|
||||||
|
:doc:`use_inline_bots`, "Query an inline bot (as user) and send a result to a chat"
|
||||||
|
:doc:`bot_keyboards`, "Send normal and inline keyboards using regular bots"
|
||||||
|
:doc:`raw_updates`, "Handle raw updates (old, should be avoided)"
|
||||||
|
|
||||||
|
For more advanced examples, see https://snippets.pyrogram.org.
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:hidden:
|
||||||
|
|
||||||
|
hello_world
|
||||||
|
echo_bot
|
||||||
|
welcome_bot
|
||||||
|
get_chat_history
|
||||||
|
get_chat_members
|
||||||
|
get_dialogs
|
||||||
|
callback_queries
|
||||||
|
inline_queries
|
||||||
|
use_inline_bots
|
||||||
|
bot_keyboards
|
||||||
|
raw_updates
|
||||||
59
docs/source/start/examples/inline_queries.rst
Normal file
59
docs/source/start/examples/inline_queries.rst
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
inline_queries
|
||||||
|
==============
|
||||||
|
|
||||||
|
This example shows how to handle inline queries.
|
||||||
|
|
||||||
|
Two results are generated when users invoke the bot inline mode, e.g.: @pyrogrambot hi.
|
||||||
|
It uses the @on_inline_query decorator to register an InlineQueryHandler.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import Client
|
||||||
|
from pyrogram.types import (InlineQueryResultArticle, InputTextMessageContent,
|
||||||
|
InlineKeyboardMarkup, InlineKeyboardButton)
|
||||||
|
|
||||||
|
app = Client("my_bot", bot_token="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11")
|
||||||
|
|
||||||
|
|
||||||
|
@app.on_inline_query()
|
||||||
|
async def answer(client, inline_query):
|
||||||
|
await inline_query.answer(
|
||||||
|
results=[
|
||||||
|
InlineQueryResultArticle(
|
||||||
|
title="Installation",
|
||||||
|
input_message_content=InputTextMessageContent(
|
||||||
|
"Here's how to install **Pyrofork**"
|
||||||
|
),
|
||||||
|
url="https://pyrofork.mayuri.my.id/intro/install",
|
||||||
|
description="How to install Pyrofork",
|
||||||
|
reply_markup=InlineKeyboardMarkup(
|
||||||
|
[
|
||||||
|
[InlineKeyboardButton(
|
||||||
|
"Open website",
|
||||||
|
url="https://pyrofork.mayuri.my.id/intro/install"
|
||||||
|
)]
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
InlineQueryResultArticle(
|
||||||
|
title="Usage",
|
||||||
|
input_message_content=InputTextMessageContent(
|
||||||
|
"Here's how to use **Pyrofork**"
|
||||||
|
),
|
||||||
|
url="https://pyrofork.mayuri.my.id/start/invoking",
|
||||||
|
description="How to use Pyrofork",
|
||||||
|
reply_markup=InlineKeyboardMarkup(
|
||||||
|
[
|
||||||
|
[InlineKeyboardButton(
|
||||||
|
"Open website",
|
||||||
|
url="https://pyrofork.mayuri.my.id/start/invoking"
|
||||||
|
)]
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
cache_time=1
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
app.run() # Automatically start() and idle()
|
||||||
18
docs/source/start/examples/raw_updates.rst
Normal file
18
docs/source/start/examples/raw_updates.rst
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
raw_updates
|
||||||
|
===========
|
||||||
|
|
||||||
|
This example shows how to handle raw updates.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import Client
|
||||||
|
|
||||||
|
app = Client("my_account")
|
||||||
|
|
||||||
|
|
||||||
|
@app.on_raw_update()
|
||||||
|
async def raw(client, update, users, chats):
|
||||||
|
print(update)
|
||||||
|
|
||||||
|
|
||||||
|
app.run() # Automatically start() and idle()
|
||||||
25
docs/source/start/examples/use_inline_bots.rst
Normal file
25
docs/source/start/examples/use_inline_bots.rst
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
use_inline_bots
|
||||||
|
===============
|
||||||
|
|
||||||
|
This example shows how to query an inline bot (as user).
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import Client
|
||||||
|
|
||||||
|
# Create a new Client
|
||||||
|
app = Client("my_account")
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
async with app:
|
||||||
|
# Get bot results for "hello" from the inline bot @vid
|
||||||
|
bot_results = await app.get_inline_bot_results("vid", "hello")
|
||||||
|
|
||||||
|
# Send the first result to your own chat (Saved Messages)
|
||||||
|
await app.send_inline_bot_result(
|
||||||
|
"me", bot_results.query_id,
|
||||||
|
bot_results.results[0].id)
|
||||||
|
|
||||||
|
|
||||||
|
app.run(main())
|
||||||
30
docs/source/start/examples/welcome_bot.rst
Normal file
30
docs/source/start/examples/welcome_bot.rst
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
welcome_bot
|
||||||
|
===========
|
||||||
|
|
||||||
|
This example uses the ``emoji`` module to easily add emoji in your text messages and ``filters``
|
||||||
|
to make it only work for specific messages in a specific chat.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import Client, emoji, filters
|
||||||
|
|
||||||
|
# Target chat. Can also be a list of multiple chat ids/usernames
|
||||||
|
TARGET = -100123456789
|
||||||
|
# Welcome message template
|
||||||
|
MESSAGE = "{} Welcome to [Pyrofork](https://pyrofork.mayuri.my.id/)'s group chat {}!"
|
||||||
|
|
||||||
|
app = Client("my_account")
|
||||||
|
|
||||||
|
|
||||||
|
# Filter in only new_chat_members updates generated in TARGET chat
|
||||||
|
@app.on_message(filters.chat(TARGET) & filters.new_chat_members)
|
||||||
|
async def welcome(client, message):
|
||||||
|
# Build the new members list (with mentions) by using their first_name
|
||||||
|
new_members = [u.mention for u in message.new_chat_members]
|
||||||
|
# Build the welcome message by using an emoji and the list we built above
|
||||||
|
text = MESSAGE.format(emoji.SPARKLES, ", ".join(new_members))
|
||||||
|
# Send the welcome message, without the web page preview
|
||||||
|
await message.reply_text(text, disable_web_page_preview=True)
|
||||||
|
|
||||||
|
|
||||||
|
app.run() # Automatically start() and idle()
|
||||||
110
docs/source/start/invoking.rst
Normal file
110
docs/source/start/invoking.rst
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
Invoking Methods
|
||||||
|
================
|
||||||
|
|
||||||
|
At this point, we have successfully :doc:`installed Pyrofork <../intro/install>` and :doc:`authorized <auth>` our
|
||||||
|
account; we are now aiming towards the core of the framework.
|
||||||
|
|
||||||
|
.. contents:: Contents
|
||||||
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
|
:local:
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
Basic Usage
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Making API calls with Pyrofork is very simple. Here's a basic example we are going to examine step by step:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import Client
|
||||||
|
|
||||||
|
app = Client("my_account")
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
async with app:
|
||||||
|
await app.send_message("me", "Hi!")
|
||||||
|
|
||||||
|
|
||||||
|
app.run(main())
|
||||||
|
|
||||||
|
Step-by-step
|
||||||
|
^^^^^^^^^^^^
|
||||||
|
|
||||||
|
#. Let's begin by importing the Client class.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import Client
|
||||||
|
|
||||||
|
#. Now instantiate a new Client object, "my_account" is a session name of your choice.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
app = Client("my_account")
|
||||||
|
|
||||||
|
#. Async methods must be invoked within an async context.
|
||||||
|
Here we define an async function and put our code inside. Also notice the ``await`` keyword in front of the method
|
||||||
|
call; this is required for all asynchronous methods.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
async with app:
|
||||||
|
await app.send_message("me", "Hi!")
|
||||||
|
|
||||||
|
#. Finally, we tell Python to schedule our ``main()`` async function by using Pyrofork's :meth:`~pyrogram.Client.run`
|
||||||
|
method.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
app.run(main())
|
||||||
|
|
||||||
|
Context Manager
|
||||||
|
---------------
|
||||||
|
|
||||||
|
The ``async with`` statement starts a context manager, which is used as a shortcut for starting, executing and stopping
|
||||||
|
the Client, asynchronously. It does so by automatically calling :meth:`~pyrogram.Client.start` and
|
||||||
|
:meth:`~pyrogram.Client.stop` in a more convenient way which also gracefully stops the client, even in case of
|
||||||
|
unhandled exceptions in your code.
|
||||||
|
|
||||||
|
Below there's the same example as above, but without the use of the context manager:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import Client
|
||||||
|
|
||||||
|
app = Client("my_account")
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
await app.start()
|
||||||
|
await app.send_message("me", "Hi!")
|
||||||
|
await app.stop()
|
||||||
|
|
||||||
|
|
||||||
|
app.run(main())
|
||||||
|
|
||||||
|
Using asyncio.run()
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
Alternatively to the :meth:`~pyrogram.Client.run` method, you can use Python's ``asyncio.run()`` to execute the main
|
||||||
|
function, with one little caveat: the Client instance (and possibly other asyncio resources you are going to use) must
|
||||||
|
be instantiated inside the main function.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
from pyrogram import Client
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
app = Client("my_account")
|
||||||
|
|
||||||
|
async with app:
|
||||||
|
await app.send_message("me", "Hi!")
|
||||||
|
|
||||||
|
|
||||||
|
asyncio.run(main())
|
||||||
40
docs/source/start/setup.rst
Normal file
40
docs/source/start/setup.rst
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
Project Setup
|
||||||
|
=============
|
||||||
|
|
||||||
|
We have just :doc:`installed Pyrofork <../intro/install>`. In this page we'll discuss what you need to do in order to set up a
|
||||||
|
project with the framework.
|
||||||
|
|
||||||
|
.. contents:: Contents
|
||||||
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
|
:local:
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
API Key
|
||||||
|
-------
|
||||||
|
|
||||||
|
The first step requires you to obtain a valid Telegram API key (api_id and api_hash pair):
|
||||||
|
|
||||||
|
#. Visit https://my.telegram.org/apps and log in with your Telegram account.
|
||||||
|
#. Fill out the form with your details and register a new Telegram application.
|
||||||
|
#. Done. The API key consists of two parts: **api_id** and **api_hash**. Keep it secret.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The API key defines a token for a Telegram *application* you are going to build.
|
||||||
|
This means that you are able to authorize multiple users or bots with a single API key.
|
||||||
|
|
||||||
|
Configuration
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Having the API key from the previous step in handy, we can now begin to configure a Pyrofork project: pass your API key to Pyrofork by using the *api_id* and *api_hash* parameters of the Client class:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import Client
|
||||||
|
|
||||||
|
api_id = 12345
|
||||||
|
api_hash = "0123456789abcdef0123456789abcdef"
|
||||||
|
|
||||||
|
app = Client("my_account", api_id=api_id, api_hash=api_hash)
|
||||||
78
docs/source/start/updates.rst
Normal file
78
docs/source/start/updates.rst
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
Handling Updates
|
||||||
|
================
|
||||||
|
|
||||||
|
:doc:`Invoking API methods <invoking>` sequentially is one way to use Pyrofork. This page deals with Telegram updates
|
||||||
|
and how to handle new incoming messages or other events in Pyrofork.
|
||||||
|
|
||||||
|
.. contents:: Contents
|
||||||
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
|
:local:
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
Defining Updates
|
||||||
|
----------------
|
||||||
|
|
||||||
|
Updates are events that happen in your Telegram account (incoming messages, new members join,
|
||||||
|
bot button presses, etc.), which are meant to notify you about a new specific state that has changed. These updates are
|
||||||
|
handled by registering one or more callback functions in your app using :doc:`Handlers <../api/handlers>`.
|
||||||
|
|
||||||
|
Each handler deals with a specific event and once a matching update arrives from Telegram, your registered callback
|
||||||
|
function will be called back by the framework and its body executed.
|
||||||
|
|
||||||
|
Registering a Handler
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
To explain how handlers work let's examine the one which will be in charge for handling :class:`~pyrogram.types.Message`
|
||||||
|
updates coming from all around your chats. Every other kind of handler shares the same setup logic and you should not
|
||||||
|
have troubles settings them up once you learn from this section.
|
||||||
|
|
||||||
|
Using Decorators
|
||||||
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
The most elegant way to register a message handler is by using the :meth:`~pyrogram.Client.on_message` decorator:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import Client
|
||||||
|
|
||||||
|
app = Client("my_account")
|
||||||
|
|
||||||
|
|
||||||
|
@app.on_message()
|
||||||
|
async def my_handler(client, message):
|
||||||
|
await message.forward("me")
|
||||||
|
|
||||||
|
|
||||||
|
app.run()
|
||||||
|
|
||||||
|
The defined function ``my_handler``, which accepts the two arguments *(client, message)*, will be the function that gets
|
||||||
|
executed every time a new message arrives.
|
||||||
|
|
||||||
|
In the last line we see again the :meth:`~pyrogram.Client.run` method, this time used without any argument.
|
||||||
|
Its purpose here is simply to automatically :meth:`~pyrogram.Client.start`, keep the Client online so that it can listen
|
||||||
|
for updates and :meth:`~pyrogram.Client.stop` it once you hit ``CTRL+C``.
|
||||||
|
|
||||||
|
Using add_handler()
|
||||||
|
^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
The :meth:`~pyrogram.Client.add_handler` method takes any handler instance that wraps around your defined callback
|
||||||
|
function and registers it in your Client. It is useful in case you want to programmatically add handlers.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import Client
|
||||||
|
from pyrogram.handlers import MessageHandler
|
||||||
|
|
||||||
|
|
||||||
|
async def my_function(client, message):
|
||||||
|
await message.forward("me")
|
||||||
|
|
||||||
|
|
||||||
|
app = Client("my_account")
|
||||||
|
|
||||||
|
my_handler = MessageHandler(my_function)
|
||||||
|
app.add_handler(my_handler)
|
||||||
|
|
||||||
|
app.run()
|
||||||
43
docs/source/support.rst
Normal file
43
docs/source/support.rst
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
Support Pyrofork
|
||||||
|
================
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
<script async defer src="https://buttons.github.io/buttons.js"></script>
|
||||||
|
|
||||||
|
<div style="float: right; margin-bottom: 10px">
|
||||||
|
<a class="github-button"
|
||||||
|
href="https://github.com/Mayuri-Chan/pyrofork"
|
||||||
|
data-color-scheme="no-preference: light; light: light; dark: dark;"
|
||||||
|
data-icon="octicon-star" data-size="large" data-show-count="true"
|
||||||
|
aria-label="Star Mayuri-Chan/pyrofork on GitHub">Star</a>
|
||||||
|
|
||||||
|
<a class="github-button"
|
||||||
|
href="https://github.com/Mayuri-Chan/pyrofork/fork"
|
||||||
|
data-color-scheme="no-preference: light; light: light; dark: dark;"
|
||||||
|
data-icon="octicon-repo-forked" data-size="large"
|
||||||
|
data-show-count="true" aria-label="Fork Mayuri-Chan/pyrofok on GitHub">Fork</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br style="clear: both"/>
|
||||||
|
|
||||||
|
Pyrofork is a free and open source project.
|
||||||
|
If you enjoy Pyrofork and would like to show your appreciation, consider donating or becoming
|
||||||
|
a sponsor of the project. You can support Pyrofork via the ways shown below:
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
GitHub Sponsor
|
||||||
|
--------------
|
||||||
|
|
||||||
|
`Become a GitHub sponsor <https://github.com/sponsors/Mayuri-Chan>`_.
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
<a class="github-button"
|
||||||
|
href="https://github.com/sponsors/Mayuri-Chan"
|
||||||
|
data-color-scheme="no-preference: light; light: light; dark: dark;"
|
||||||
|
data-icon="octicon-heart" data-size="large"
|
||||||
|
aria-label="Sponsor @Mayuri-Chan on GitHub">Sponsor</a>
|
||||||
|
|
||||||
|
-----
|
||||||
124
docs/source/topics/advanced-usage.rst
Normal file
124
docs/source/topics/advanced-usage.rst
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
Advanced Usage
|
||||||
|
==============
|
||||||
|
|
||||||
|
Pyrofork's API -- which consists of well documented :doc:`methods <../api/methods/index>` and
|
||||||
|
:doc:`types <../api/types/index>` -- exists to provide an easier interface to the more complex Telegram API.
|
||||||
|
|
||||||
|
In this section, you'll be shown the alternative way of communicating with Telegram using Pyrofork: the main "raw"
|
||||||
|
Telegram API with its functions and types.
|
||||||
|
|
||||||
|
.. contents:: Contents
|
||||||
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
|
:local:
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
Telegram Raw API
|
||||||
|
----------------
|
||||||
|
|
||||||
|
If you can't find a high-level method for your needs or if you want complete, low-level access to the whole
|
||||||
|
Telegram API, you have to use the raw :mod:`~pyrogram.raw.functions` and :mod:`~pyrogram.raw.types`.
|
||||||
|
|
||||||
|
As already hinted, raw functions and types can be less convenient. This section will therefore explain some pitfalls to
|
||||||
|
take into consideration when working with the raw API.
|
||||||
|
|
||||||
|
.. tip::
|
||||||
|
|
||||||
|
Every available high-level method in Pyrofork is built on top of these raw functions.
|
||||||
|
|
||||||
|
Invoking Functions
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Unlike the :doc:`methods <../api/methods/index>` found in Pyrofork's API, which can be called in the usual simple way,
|
||||||
|
functions to be invoked from the raw Telegram API have a different way of usage.
|
||||||
|
|
||||||
|
First of all, both :doc:`raw functions <../telegram/functions/index>` and :doc:`raw types <../telegram/types/index>`
|
||||||
|
live in their respective packages (and sub-packages): ``pyrogram.raw.functions``, ``pyrogram.raw.types``. They all exist
|
||||||
|
as Python classes, meaning you need to create an instance of each every time you need them and fill them in with the
|
||||||
|
correct values using named arguments.
|
||||||
|
|
||||||
|
Next, to actually invoke the raw function you have to use the :meth:`~pyrogram.Client.invoke` method provided by the
|
||||||
|
Client class and pass the function object you created.
|
||||||
|
|
||||||
|
Here's some examples:
|
||||||
|
|
||||||
|
- Update first name, last name and bio:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import Client
|
||||||
|
from pyrogram.raw import functions
|
||||||
|
|
||||||
|
async with Client("my_account") as app:
|
||||||
|
await app.invoke(
|
||||||
|
functions.account.UpdateProfile(
|
||||||
|
first_name="First Name", last_name="Last Name",
|
||||||
|
about="New bio text"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
- Set online/offline status:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import Client
|
||||||
|
from pyrogram.raw import functions, types
|
||||||
|
|
||||||
|
async with Client("my_account") as app:
|
||||||
|
# Set online status
|
||||||
|
await app.invoke(functions.account.UpdateStatus(offline=False))
|
||||||
|
|
||||||
|
# Set offline status
|
||||||
|
await app.invoke(functions.account.UpdateStatus(offline=True))
|
||||||
|
|
||||||
|
- Get chat info:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import Client
|
||||||
|
from pyrogram.raw import functions, types
|
||||||
|
|
||||||
|
async with Client("my_account") as app:
|
||||||
|
r = await app.invoke(
|
||||||
|
functions.channels.GetFullChannel(
|
||||||
|
channel=app.resolve_peer("username")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
print(r)
|
||||||
|
|
||||||
|
Chat IDs
|
||||||
|
--------
|
||||||
|
|
||||||
|
The way Telegram works makes it not possible to directly send a message to a user or a chat by using their IDs only.
|
||||||
|
Instead, a pair of ``id`` and ``access_hash`` wrapped in a so called ``InputPeer`` is always needed. Pyrofork allows
|
||||||
|
sending messages with IDs only thanks to cached access hashes.
|
||||||
|
|
||||||
|
There are three different InputPeer types, one for each kind of Telegram entity.
|
||||||
|
Whenever an InputPeer is needed you must pass one of these:
|
||||||
|
|
||||||
|
- :class:`~pyrogram.raw.types.InputPeerUser` - Users
|
||||||
|
- :class:`~pyrogram.raw.types.InputPeerChat` - Basic Chats
|
||||||
|
- :class:`~pyrogram.raw.types.InputPeerChannel` - Channels & Supergroups
|
||||||
|
|
||||||
|
But you don't necessarily have to manually instantiate each object because Pyrofork already provides
|
||||||
|
:meth:`~pyrogram.Client.resolve_peer` as a convenience utility method that returns the correct InputPeer
|
||||||
|
by accepting a peer ID only.
|
||||||
|
|
||||||
|
Another thing to take into consideration about chat IDs is the way they are represented: they are all integers and
|
||||||
|
all positive within their respective raw types.
|
||||||
|
|
||||||
|
Things are different when working with Pyrofork's API because having them in the same space could lead to
|
||||||
|
collisions, and that's why Pyrofork uses a slightly different representation for each kind of ID.
|
||||||
|
|
||||||
|
For example, given the ID *123456789*, here's how Pyrofork can tell entities apart:
|
||||||
|
|
||||||
|
- ``+ID`` User: *123456789*
|
||||||
|
- ``-ID`` Chat: *-123456789*
|
||||||
|
- ``-100ID`` Channel or Supergroup: *-100123456789*
|
||||||
|
|
||||||
|
So, every time you take a raw ID, make sure to translate it into the correct ID when you want to use it with an
|
||||||
|
high-level method.
|
||||||
|
|
||||||
|
.. _Community: https://t.me/Pyrofork
|
||||||
46
docs/source/topics/client-settings.rst
Normal file
46
docs/source/topics/client-settings.rst
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
Client Settings
|
||||||
|
===============
|
||||||
|
|
||||||
|
You can control the way your client appears in the Active Sessions menu of an official client by changing some client
|
||||||
|
settings. By default you will see something like the following:
|
||||||
|
|
||||||
|
- Device Model: ``CPython x.y.z``
|
||||||
|
- Application: ``Pyrofork x.y.z``
|
||||||
|
- System Version: ``Linux x.y.z``
|
||||||
|
|
||||||
|
.. contents:: Contents
|
||||||
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
|
:local:
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
Set Custom Values
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
To set custom values, you can pass the arguments directly in the Client's constructor.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
app = Client(
|
||||||
|
"my_account",
|
||||||
|
app_version="1.2.3",
|
||||||
|
device_model="PC",
|
||||||
|
system_version="Linux"
|
||||||
|
)
|
||||||
|
|
||||||
|
Set Custom Languages
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
To tell Telegram in which language should speak to you (terms of service, bots, service messages, ...) you can
|
||||||
|
set ``lang_code`` in `ISO 639-1 <https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes>`_ standard (defaults to "en",
|
||||||
|
English).
|
||||||
|
|
||||||
|
With the following code we make Telegram know we want it to speak in Italian (it):
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
app = Client(
|
||||||
|
"my_account",
|
||||||
|
lang_code="it",
|
||||||
|
)
|
||||||
109
docs/source/topics/create-filters.rst
Normal file
109
docs/source/topics/create-filters.rst
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
Creating Filters
|
||||||
|
================
|
||||||
|
|
||||||
|
Pyrofork already provides lots of built-in :class:`~pyrogram.filters` to work with, but in case you can't find a
|
||||||
|
specific one for your needs or want to build a custom filter by yourself you can use
|
||||||
|
:meth:`filters.create() <pyrogram.filters.create>`.
|
||||||
|
|
||||||
|
.. contents:: Contents
|
||||||
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
|
:local:
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
Custom Filters
|
||||||
|
--------------
|
||||||
|
|
||||||
|
An example to demonstrate how custom filters work is to show how to create and use one for the
|
||||||
|
:class:`~pyrogram.handlers.CallbackQueryHandler`. Note that callback queries updates are only received by bots as result
|
||||||
|
of a user pressing an inline button attached to the bot's message; create and :doc:`authorize your bot <../start/auth>`,
|
||||||
|
then send a message with an inline keyboard to yourself. This allows you to test your filter by pressing the inline
|
||||||
|
button:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||||
|
|
||||||
|
await app.send_message(
|
||||||
|
"username", # Change this to your username or id
|
||||||
|
"Pyrofork custom filter test",
|
||||||
|
reply_markup=InlineKeyboardMarkup(
|
||||||
|
[[InlineKeyboardButton("Press me", "pyrogram")]]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
Basic Filters
|
||||||
|
-------------
|
||||||
|
|
||||||
|
For this basic filter we will be using only the first parameter of :meth:`~pyrogram.filters.create`.
|
||||||
|
|
||||||
|
The heart of a filter is its callback function, which accepts three arguments *(self, client, update)* and returns
|
||||||
|
either ``True``, in case you want the update to pass the filter or ``False`` otherwise.
|
||||||
|
|
||||||
|
In this example we are matching the query data to "pyrogram", which means that the filter will only allow callback
|
||||||
|
queries containing "pyrogram" as data:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import filters
|
||||||
|
|
||||||
|
async def func(_, __, query):
|
||||||
|
return query.data == "pyrogram"
|
||||||
|
|
||||||
|
static_data_filter = filters.create(func)
|
||||||
|
|
||||||
|
|
||||||
|
The first two arguments of the callback function are unused here and because of this we named them using underscores.
|
||||||
|
|
||||||
|
Finally, the filter usage remains the same:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
@app.on_callback_query(static_data_filter)
|
||||||
|
async def pyrogram_data(_, query):
|
||||||
|
query.answer("it works!")
|
||||||
|
|
||||||
|
Filters with Arguments
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
A more flexible filter would be one that accepts "pyrogram" or any other string as argument at usage time.
|
||||||
|
A dynamic filter like this will make use of named arguments for the :meth:`~pyrogram.filters.create` method and the
|
||||||
|
first argument of the callback function, which is a reference to the filter object itself holding the extra data passed
|
||||||
|
via named arguments.
|
||||||
|
|
||||||
|
This is how a dynamic custom filter looks like:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import filters
|
||||||
|
|
||||||
|
def dynamic_data_filter(data):
|
||||||
|
async def func(flt, _, query):
|
||||||
|
return flt.data == query.data
|
||||||
|
|
||||||
|
# "data" kwarg is accessed with "flt.data" above
|
||||||
|
return filters.create(func, data=data)
|
||||||
|
|
||||||
|
And finally its usage:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
@app.on_callback_query(dynamic_data_filter("pyrogram"))
|
||||||
|
async def pyrogram_data(_, query):
|
||||||
|
query.answer("it works!")
|
||||||
|
|
||||||
|
|
||||||
|
Method Calls Inside Filters
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
The missing piece we haven't covered yet is the second argument of a filter callback function, namely, the ``client``
|
||||||
|
argument. This is a reference to the :obj:`~pyrogram.Client` instance that is running the filter and it is useful in
|
||||||
|
case you would like to make some API calls before deciding whether the filter should allow the update or not:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
async def func(_, client, query):
|
||||||
|
# r = await client.some_api_method()
|
||||||
|
# check response "r" and decide to return True or False
|
||||||
|
...
|
||||||
122
docs/source/topics/debugging.rst
Normal file
122
docs/source/topics/debugging.rst
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
Debugging
|
||||||
|
=========
|
||||||
|
|
||||||
|
When working with the API, chances are you'll stumble upon bugs, get stuck and start wondering how to continue. Nothing
|
||||||
|
to actually worry about since Pyrofork provides some commodities to help you in this.
|
||||||
|
|
||||||
|
.. contents:: Contents
|
||||||
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
|
:local:
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
Caveman Debugging
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
*The most effective debugging tool is still careful thought, coupled with judiciously placed print statements.*
|
||||||
|
|
||||||
|
-- Brian Kernighan, "Unix for Beginners" (1979)
|
||||||
|
|
||||||
|
Adding ``print()`` statements in crucial parts of your code is by far the most ancient, yet efficient technique for
|
||||||
|
debugging programs, especially considering the concurrent nature of the framework itself. Pyrofork goodness in this
|
||||||
|
respect comes with the fact that any object can be nicely printed just by calling ``print(obj)``, thus giving to you
|
||||||
|
an insight of all its inner details.
|
||||||
|
|
||||||
|
Consider the following code:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
me = await app.get_users("me")
|
||||||
|
print(me) # User
|
||||||
|
|
||||||
|
This will show a JSON representation of the object returned by :meth:`~pyrogram.Client.get_users`, which is a
|
||||||
|
:class:`~pyrogram.types.User` instance, in this case. The output on your terminal will be something similar to this:
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
{
|
||||||
|
"_": "User",
|
||||||
|
"id": 123456789,
|
||||||
|
"is_self": true,
|
||||||
|
"is_contact": false,
|
||||||
|
"is_mutual_contact": false,
|
||||||
|
"is_deleted": false,
|
||||||
|
"is_bot": false,
|
||||||
|
"is_verified": false,
|
||||||
|
"is_restricted": false,
|
||||||
|
"is_support": false,
|
||||||
|
"first_name": "Pyrofork",
|
||||||
|
"photo": {
|
||||||
|
"_": "ChatPhoto",
|
||||||
|
"small_file_id": "AbCdE...EdCbA",
|
||||||
|
"small_photo_unique_id": "VwXyZ...ZyXwV",
|
||||||
|
"big_file_id": "AbCdE...EdCbA",
|
||||||
|
"big_photo_unique_id": "VwXyZ...ZyXwV"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
As you've probably guessed already, Pyrofork objects can be nested. That's how compound data are built, and nesting
|
||||||
|
keeps going until we are left with base data types only, such as ``str``, ``int``, ``bool``, etc.
|
||||||
|
|
||||||
|
Accessing Attributes
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
Even though you see a JSON output, it doesn't mean we are dealing with dictionaries; in fact, all Pyrofork types are
|
||||||
|
fully-fledged Python objects and the correct way to access any attribute of them is by using the dot notation ``.``:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
photo = me.photo
|
||||||
|
print(photo) # ChatPhoto
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
{
|
||||||
|
"_": "ChatPhoto",
|
||||||
|
"small_file_id": "AbCdE...EdCbA",
|
||||||
|
"small_photo_unique_id": "VwXyZ...ZyXwV",
|
||||||
|
"big_file_id": "AbCdE...EdCbA",
|
||||||
|
"big_photo_unique_id": "VwXyZ...ZyXwV"
|
||||||
|
}
|
||||||
|
|
||||||
|
Checking an Object's Type
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
Another thing worth talking about is how to tell and check for an object's type.
|
||||||
|
|
||||||
|
As you noticed already, when printing an object you'll see the special attribute ``"_"``. This is just a visual thing
|
||||||
|
useful to show humans the object type, but doesn't really exist anywhere; any attempt in accessing it will lead to an
|
||||||
|
error. The correct way to get the object type is by using the built-in function ``type()``:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
status = me.status
|
||||||
|
print(type(status))
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
<class 'pyrogram.types.UserStatus'>
|
||||||
|
|
||||||
|
And to check if an object is an instance of a given class, you use the built-in function ``isinstance()``:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
:name: this-py
|
||||||
|
|
||||||
|
from pyrogram.types import UserStatus
|
||||||
|
|
||||||
|
status = me.status
|
||||||
|
print(isinstance(status, UserStatus))
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
True
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var e = document.querySelector("blockquote p.attribution");
|
||||||
|
var s = e.innerHTML;
|
||||||
|
|
||||||
|
e.innerHTML = s[0] + " " + s.slice(1);
|
||||||
|
</script>
|
||||||
226
docs/source/topics/more-on-updates.rst
Normal file
226
docs/source/topics/more-on-updates.rst
Normal file
|
|
@ -0,0 +1,226 @@
|
||||||
|
More on Updates
|
||||||
|
===============
|
||||||
|
|
||||||
|
Here we'll show some advanced usages when working with :doc:`update handlers <../start/updates>` and
|
||||||
|
:doc:`filters <use-filters>`.
|
||||||
|
|
||||||
|
.. contents:: Contents
|
||||||
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
|
:local:
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
Handler Groups
|
||||||
|
--------------
|
||||||
|
|
||||||
|
If you register handlers with overlapping (conflicting) filters, only the first one is executed and any other handler
|
||||||
|
will be ignored. This is intended by design.
|
||||||
|
|
||||||
|
In order to handle the very same update more than once, you have to register your handler in a different dispatching
|
||||||
|
group. Dispatching groups hold one or more handlers and are processed sequentially, they are identified by a number
|
||||||
|
(number 0 being the default) and sorted, that is, a lower group number has a higher priority:
|
||||||
|
|
||||||
|
For example, take these two handlers:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
@app.on_message(filters.text | filters.sticker)
|
||||||
|
async def text_or_sticker(client, message):
|
||||||
|
print("Text or Sticker")
|
||||||
|
|
||||||
|
|
||||||
|
@app.on_message(filters.text)
|
||||||
|
async def just_text(client, message):
|
||||||
|
print("Just Text")
|
||||||
|
|
||||||
|
Here, ``just_text`` is never executed because ``text_or_sticker``, which has been registered first, already handles
|
||||||
|
texts (``filters.text`` is shared and conflicting). To enable it, register the handler using a different group:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
@app.on_message(filters.text, group=1)
|
||||||
|
async def just_text(client, message):
|
||||||
|
print("Just Text")
|
||||||
|
|
||||||
|
Or, if you want ``just_text`` to be executed *before* ``text_or_sticker`` (note ``-1``, which is less than ``0``):
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
@app.on_message(filters.text, group=-1)
|
||||||
|
async def just_text(client, message):
|
||||||
|
print("Just Text")
|
||||||
|
|
||||||
|
With :meth:`~pyrogram.Client.add_handler` (without decorators) the same can be achieved with:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
app.add_handler(MessageHandler(just_text, filters.text), -1)
|
||||||
|
|
||||||
|
Update propagation
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Registering multiple handlers, each in a different group, becomes useful when you want to handle the same update more
|
||||||
|
than once. Any incoming update will be sequentially processed by all of your registered functions by respecting the
|
||||||
|
groups priority policy described above. Even in case any handler raises an unhandled exception, Pyrofork will still
|
||||||
|
continue to propagate the same update to the next groups until all the handlers are done. Example:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
@app.on_message(filters.private)
|
||||||
|
async def _(client, message):
|
||||||
|
print(0)
|
||||||
|
|
||||||
|
|
||||||
|
@app.on_message(filters.private, group=1)
|
||||||
|
async def _(client, message):
|
||||||
|
raise Exception("Unhandled exception!") # Simulate an unhandled exception
|
||||||
|
|
||||||
|
|
||||||
|
@app.on_message(filters.private, group=2)
|
||||||
|
async def _(client, message):
|
||||||
|
print(2)
|
||||||
|
|
||||||
|
All these handlers will handle the same kind of messages, that are, messages sent or received in private chats.
|
||||||
|
The output for each incoming update will therefore be:
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
0
|
||||||
|
Exception: Unhandled exception!
|
||||||
|
2
|
||||||
|
|
||||||
|
Stop Propagation
|
||||||
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
In order to prevent further propagation of an update in the dispatching phase, you can do *one* of the following:
|
||||||
|
|
||||||
|
- Call the update's bound-method ``.stop_propagation()`` (preferred way).
|
||||||
|
- Manually ``raise StopPropagation`` exception (more suitable for raw updates only).
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Internally, the propagation is stopped by handling a custom exception. ``.stop_propagation()`` is just an elegant
|
||||||
|
and intuitive way to ``raise StopPropagation``; this also means that any code coming *after* calling the method
|
||||||
|
won't be executed as your function just raised an exception to signal the dispatcher not to propagate the
|
||||||
|
update anymore.
|
||||||
|
|
||||||
|
Example with ``stop_propagation()``:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
@app.on_message(filters.private)
|
||||||
|
async def _(client, message):
|
||||||
|
print(0)
|
||||||
|
|
||||||
|
|
||||||
|
@app.on_message(filters.private, group=1)
|
||||||
|
async def _(client, message):
|
||||||
|
print(1)
|
||||||
|
message.stop_propagation()
|
||||||
|
|
||||||
|
|
||||||
|
@app.on_message(filters.private, group=2)
|
||||||
|
async def _(client, message):
|
||||||
|
print(2)
|
||||||
|
|
||||||
|
Example with ``raise StopPropagation``:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import StopPropagation
|
||||||
|
|
||||||
|
@app.on_message(filters.private)
|
||||||
|
async def _(client, message):
|
||||||
|
print(0)
|
||||||
|
|
||||||
|
|
||||||
|
@app.on_message(filters.private, group=1)
|
||||||
|
async ef _(client, message):
|
||||||
|
print(1)
|
||||||
|
raise StopPropagation
|
||||||
|
|
||||||
|
|
||||||
|
@app.on_message(filters.private, group=2)
|
||||||
|
async def _(client, message):
|
||||||
|
print(2)
|
||||||
|
|
||||||
|
Each handler is registered in a different group, but the handler in group number 2 will never be executed because the
|
||||||
|
propagation was stopped earlier. The output of both (equivalent) examples will be:
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
0
|
||||||
|
1
|
||||||
|
|
||||||
|
Continue Propagation
|
||||||
|
^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
As opposed to `stopping the update propagation <#stop-propagation>`_ and also as an alternative to the
|
||||||
|
`handler groups <#handler-groups>`_, you can signal the internal dispatcher to continue the update propagation within
|
||||||
|
**the same group** despite having conflicting filters in the next registered handler. This allows you to register
|
||||||
|
multiple handlers with overlapping filters in the same group; to let the dispatcher process the next handler you can do
|
||||||
|
*one* of the following in each handler you want to grant permission to continue:
|
||||||
|
|
||||||
|
- Call the update's bound-method ``.continue_propagation()`` (preferred way).
|
||||||
|
- Manually ``raise ContinuePropagation`` exception (more suitable for raw updates only).
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Internally, the propagation is continued by handling a custom exception. ``.continue_propagation()`` is just an
|
||||||
|
elegant and intuitive way to ``raise ContinuePropagation``; this also means that any code coming *after* calling the
|
||||||
|
method won't be executed as your function just raised an exception to signal the dispatcher to continue with the
|
||||||
|
next available handler.
|
||||||
|
|
||||||
|
|
||||||
|
Example with ``continue_propagation()``:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
@app.on_message(filters.private)
|
||||||
|
async def _(client, message):
|
||||||
|
print(0)
|
||||||
|
message.continue_propagation()
|
||||||
|
|
||||||
|
|
||||||
|
@app.on_message(filters.private)
|
||||||
|
async def _(client, message):
|
||||||
|
print(1)
|
||||||
|
message.continue_propagation()
|
||||||
|
|
||||||
|
|
||||||
|
@app.on_message(filters.private)
|
||||||
|
async def _(client, message):
|
||||||
|
print(2)
|
||||||
|
|
||||||
|
Example with ``raise ContinuePropagation``:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import ContinuePropagation
|
||||||
|
|
||||||
|
@app.on_message(filters.private)
|
||||||
|
async def _(client, message):
|
||||||
|
print(0)
|
||||||
|
raise ContinuePropagation
|
||||||
|
|
||||||
|
|
||||||
|
@app.on_message(filters.private)
|
||||||
|
async def _(client, message):
|
||||||
|
print(1)
|
||||||
|
raise ContinuePropagation
|
||||||
|
|
||||||
|
|
||||||
|
@app.on_message(filters.private)
|
||||||
|
async def _(client, message):
|
||||||
|
print(2)
|
||||||
|
|
||||||
|
Three handlers are registered in the same group, and all of them will be executed because the propagation was continued
|
||||||
|
in each handler (except in the last one, where is useless to do so since there is no more handlers after).
|
||||||
|
The output of both (equivalent) examples will be:
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
0
|
||||||
|
1
|
||||||
|
2
|
||||||
112
docs/source/topics/mtproto-vs-botapi.rst
Normal file
112
docs/source/topics/mtproto-vs-botapi.rst
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
MTProto vs. Bot API
|
||||||
|
===================
|
||||||
|
|
||||||
|
Pyrofork is a framework written from the ground up that acts as a fully-fledged Telegram client based on the MTProto
|
||||||
|
API. This means that Pyrofork is able to execute any official client and bot API action and more. This page will
|
||||||
|
therefore show you why Pyrofork might be a better choice for your project by comparing the two APIs, but first, let's
|
||||||
|
make it clear what actually is the MTProto and the Bot API.
|
||||||
|
|
||||||
|
.. contents:: Contents
|
||||||
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
|
:local:
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
What is the MTProto API?
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
`MTProto`_, took alone, is the name of the custom-made, open and encrypted communication protocol created by Telegram
|
||||||
|
itself --- it's the only protocol used to exchange information between a client and the actual Telegram servers.
|
||||||
|
|
||||||
|
The MTProto API on the other hand, is what people for convenience call the main Telegram API in order to distinguish it
|
||||||
|
from the Bot API. The main Telegram API is able to authorize both users and bots and is built on top of the MTProto
|
||||||
|
encryption protocol by means of `binary data serialized`_ in a specific way, as described by the `TL language`_, and
|
||||||
|
delivered using UDP, TCP or even HTTP as transport-layer protocol. Clients that make use of Telegram's main API, such as
|
||||||
|
Pyrofork, implement all these details.
|
||||||
|
|
||||||
|
.. _MTProto: https://core.telegram.org/mtproto
|
||||||
|
.. _binary data serialized: https://core.telegram.org/mtproto/serialize
|
||||||
|
.. _TL language: https://core.telegram.org/mtproto/TL
|
||||||
|
|
||||||
|
What is the Bot API?
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
The `Bot API`_ is an HTTP(S) interface for building normal bots using a sub-set of the main Telegram API. Bots are
|
||||||
|
special accounts that are authorized via tokens instead of phone numbers. The Bot API is built yet again on top of the
|
||||||
|
main Telegram API, but runs on an intermediate server application that in turn communicates with the actual Telegram
|
||||||
|
servers using MTProto.
|
||||||
|
|
||||||
|
.. figure:: //_static/img/mtproto-vs-bot-api.png
|
||||||
|
:align: center
|
||||||
|
|
||||||
|
.. _Bot API: https://core.telegram.org/bots/api
|
||||||
|
|
||||||
|
Advantages of the MTProto API
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
Here is a non-exhaustive list of all the advantages in using MTProto-based libraries -- such as Pyrofork -- instead of
|
||||||
|
the official HTTP Bot API. Using Pyrofork you can:
|
||||||
|
|
||||||
|
.. hlist::
|
||||||
|
:columns: 1
|
||||||
|
|
||||||
|
- :guilabel:`+` **Authorize both user and bot identities**
|
||||||
|
- :guilabel:`--` The Bot API only allows bot accounts
|
||||||
|
|
||||||
|
.. hlist::
|
||||||
|
:columns: 1
|
||||||
|
|
||||||
|
- :guilabel:`+` **Upload & download any file, up to 2000 MiB each (~2 GB)**
|
||||||
|
- :guilabel:`--` The Bot API allows uploads and downloads of files only up to 50 MB / 20 MB in size (respectively).
|
||||||
|
|
||||||
|
.. hlist::
|
||||||
|
:columns: 1
|
||||||
|
|
||||||
|
- :guilabel:`+` **Has less overhead due to direct connections to Telegram**
|
||||||
|
- :guilabel:`--` The Bot API uses an intermediate server to handle HTTP requests before they are sent to the actual
|
||||||
|
Telegram servers.
|
||||||
|
|
||||||
|
.. hlist::
|
||||||
|
:columns: 1
|
||||||
|
|
||||||
|
- :guilabel:`+` **Run multiple sessions at once (for both user and bot identities)**
|
||||||
|
- :guilabel:`--` The Bot API intermediate server will terminate any other session in case you try to use the same
|
||||||
|
bot again in a parallel connection.
|
||||||
|
|
||||||
|
.. hlist::
|
||||||
|
:columns: 1
|
||||||
|
|
||||||
|
- :guilabel:`+` **Has much more detailed types and powerful methods**
|
||||||
|
- :guilabel:`--` The Bot API types often miss some useful information about Telegram entities and some of the
|
||||||
|
methods are limited as well.
|
||||||
|
|
||||||
|
.. hlist::
|
||||||
|
:columns: 1
|
||||||
|
|
||||||
|
- :guilabel:`+` **Obtain information about any message existing in a chat using their ids**
|
||||||
|
- :guilabel:`--` The Bot API simply doesn't support this
|
||||||
|
|
||||||
|
.. hlist::
|
||||||
|
:columns: 1
|
||||||
|
|
||||||
|
- :guilabel:`+` **Retrieve the whole chat members list of either public or private chats**
|
||||||
|
- :guilabel:`--` The Bot API simply doesn't support this
|
||||||
|
|
||||||
|
.. hlist::
|
||||||
|
:columns: 1
|
||||||
|
|
||||||
|
- :guilabel:`+` **Receive extra updates, such as the one about a user name change**
|
||||||
|
- :guilabel:`--` The Bot API simply doesn't support this
|
||||||
|
|
||||||
|
.. hlist::
|
||||||
|
:columns: 1
|
||||||
|
|
||||||
|
- :guilabel:`+` **Has more meaningful errors in case something went wrong**
|
||||||
|
- :guilabel:`--` The Bot API reports less detailed errors
|
||||||
|
|
||||||
|
.. hlist::
|
||||||
|
:columns: 1
|
||||||
|
|
||||||
|
- :guilabel:`+` **Get API version updates, and thus new features, sooner**
|
||||||
|
- :guilabel:`--` The Bot API is simply slower in implementing new features
|
||||||
34
docs/source/topics/proxy.rst
Normal file
34
docs/source/topics/proxy.rst
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
Proxy Settings
|
||||||
|
==============
|
||||||
|
|
||||||
|
Pyrofork supports proxies with and without authentication. This feature allows Pyrofork to exchange data with Telegram
|
||||||
|
through an intermediate SOCKS 4/5 or HTTP (CONNECT) proxy server.
|
||||||
|
|
||||||
|
.. contents:: Contents
|
||||||
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
|
:local:
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
|
||||||
|
To use Pyrofork with a proxy, use the *proxy* parameter in the Client class. If your proxy doesn't require authorization
|
||||||
|
you can omit ``username`` and ``password``.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import Client
|
||||||
|
|
||||||
|
proxy = {
|
||||||
|
"scheme": "socks5", # "socks4", "socks5" and "http" are supported
|
||||||
|
"hostname": "11.22.33.44",
|
||||||
|
"port": 1234,
|
||||||
|
"username": "username",
|
||||||
|
"password": "password"
|
||||||
|
}
|
||||||
|
|
||||||
|
app = Client("my_account", proxy=proxy)
|
||||||
|
|
||||||
|
app.run()
|
||||||
65
docs/source/topics/scheduling.rst
Normal file
65
docs/source/topics/scheduling.rst
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
Scheduling Tasks
|
||||||
|
================
|
||||||
|
|
||||||
|
Scheduling tasks means executing one or more functions periodically at pre-defined intervals or after a delay. This is
|
||||||
|
useful, for example, to send recurring messages to specific chats or users.
|
||||||
|
|
||||||
|
This page will show examples on how to integrate Pyrofork with ``apscheduler`` in both asynchronous and
|
||||||
|
non-asynchronous contexts. For more detailed information, you can visit and learn from the library documentation.
|
||||||
|
|
||||||
|
.. contents:: Contents
|
||||||
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
|
:local:
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
Using apscheduler
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
- Install with ``pip3 install apscheduler``
|
||||||
|
- Documentation: https://apscheduler.readthedocs.io
|
||||||
|
|
||||||
|
Asynchronously
|
||||||
|
^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||||||
|
|
||||||
|
from pyrogram import Client
|
||||||
|
|
||||||
|
app = Client("my_account")
|
||||||
|
|
||||||
|
|
||||||
|
async def job():
|
||||||
|
await app.send_message("me", "Hi!")
|
||||||
|
|
||||||
|
|
||||||
|
scheduler = AsyncIOScheduler()
|
||||||
|
scheduler.add_job(job, "interval", seconds=3)
|
||||||
|
|
||||||
|
scheduler.start()
|
||||||
|
app.run()
|
||||||
|
|
||||||
|
Non-Asynchronously
|
||||||
|
^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from apscheduler.schedulers.background import BackgroundScheduler
|
||||||
|
|
||||||
|
from pyrogram import Client
|
||||||
|
|
||||||
|
app = Client("my_account")
|
||||||
|
|
||||||
|
|
||||||
|
def job():
|
||||||
|
app.send_message("me", "Hi!")
|
||||||
|
|
||||||
|
|
||||||
|
scheduler = BackgroundScheduler()
|
||||||
|
scheduler.add_job(job, "interval", seconds=3)
|
||||||
|
|
||||||
|
scheduler.start()
|
||||||
|
app.run()
|
||||||
56
docs/source/topics/serializing.rst
Normal file
56
docs/source/topics/serializing.rst
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
Object Serialization
|
||||||
|
====================
|
||||||
|
|
||||||
|
Serializing means converting a Pyrofork object, which exists as Python class instance, to a text string that can be
|
||||||
|
easily shared and stored anywhere. Pyrofork provides two formats for serializing its objects: one good looking for
|
||||||
|
humans and another more compact for machines that is able to recover the original structures.
|
||||||
|
|
||||||
|
.. contents:: Contents
|
||||||
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
|
:local:
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
For Humans - str(obj)
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
If you want a nicely formatted, human readable JSON representation of any object in the API you can use ``str(obj)``.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
async with app:
|
||||||
|
r = await app.get_chat("me")
|
||||||
|
print(str(r))
|
||||||
|
|
||||||
|
.. tip::
|
||||||
|
|
||||||
|
When using ``print()`` you don't actually need to use ``str()`` on the object because it is called automatically, we
|
||||||
|
have done that above just to show you how to explicitly convert a Pyrofork object to JSON.
|
||||||
|
|
||||||
|
For Machines - repr(obj)
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
If you want to share or store objects for future references in a more compact way, you can use ``repr(obj)``. While
|
||||||
|
still pretty much readable, this format is not intended for humans. The advantage of this format is that once you
|
||||||
|
serialize your object, you can use ``eval()`` to get back the original structure; just make sure to ``import pyrogram``,
|
||||||
|
as the process requires the package to be in scope.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
import pyrogram
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
async with app:
|
||||||
|
r = await app.get_chat("me")
|
||||||
|
|
||||||
|
print(repr(r))
|
||||||
|
print(eval(repr(r)) == r) # True
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Type definitions are subject to changes between versions. You should make sure to store and load objects using the
|
||||||
|
same Pyrofork version.
|
||||||
306
docs/source/topics/smart-plugins.rst
Normal file
306
docs/source/topics/smart-plugins.rst
Normal file
|
|
@ -0,0 +1,306 @@
|
||||||
|
Smart Plugins
|
||||||
|
=============
|
||||||
|
|
||||||
|
Pyrofork embeds a smart, lightweight yet powerful plugin system that is meant to further simplify the organization
|
||||||
|
of large projects and to provide a way for creating pluggable (modular) components that can be easily shared across
|
||||||
|
different Pyrofork applications with minimal boilerplate code.
|
||||||
|
|
||||||
|
.. tip::
|
||||||
|
|
||||||
|
Smart Plugins are completely optional and disabled by default.
|
||||||
|
|
||||||
|
.. contents:: Contents
|
||||||
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
|
:local:
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
Introduction
|
||||||
|
------------
|
||||||
|
|
||||||
|
Prior to the Smart Plugin system, pluggable handlers were already possible. For example, if you wanted to modularize
|
||||||
|
your applications, you had to put your function definitions in separate files and register them inside your main script
|
||||||
|
after importing your modules, 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.
|
||||||
|
|
||||||
|
Example: *"Pyrofork"* replies with *"Pyrofork"* and *"margoryP"*
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
myproject/
|
||||||
|
handlers.py
|
||||||
|
main.py
|
||||||
|
|
||||||
|
- ``handlers.py``
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
async def echo(client, message):
|
||||||
|
await message.reply(message.text)
|
||||||
|
|
||||||
|
|
||||||
|
async def echo_reversed(client, message):
|
||||||
|
await message.reply(message.text[::-1])
|
||||||
|
|
||||||
|
- ``main.py``
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import Client, filters
|
||||||
|
from pyrogram.handlers import MessageHandler
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
This is already nice and doesn't add *too much* boilerplate code, but things can get boring still; you have to
|
||||||
|
manually ``import``, manually :meth:`~pyrogram.Client.add_handler` and manually instantiate each
|
||||||
|
:class:`~pyrogram.handlers.MessageHandler` object because you can't use decorators for your functions.
|
||||||
|
So, what if you could? Smart Plugins solve this issue by taking care of handlers registration automatically.
|
||||||
|
|
||||||
|
Using Smart Plugins
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
Setting up your Pyrofork project to accommodate Smart Plugins is pretty straightforward:
|
||||||
|
|
||||||
|
#. Create a new folder to store all the plugins (e.g.: "plugins", "handlers", ...).
|
||||||
|
#. Put your python files full of plugins inside. Organize them as you wish.
|
||||||
|
#. Enable plugins in your Client.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
This is the same example application as shown above, written using the Smart Plugin system.
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
myproject/
|
||||||
|
plugins/
|
||||||
|
handlers.py
|
||||||
|
main.py
|
||||||
|
|
||||||
|
- ``plugins/handlers.py``
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import Client, filters
|
||||||
|
|
||||||
|
|
||||||
|
@Client.on_message(filters.text & filters.private)
|
||||||
|
async def echo(client, message):
|
||||||
|
await message.reply(message.text)
|
||||||
|
|
||||||
|
|
||||||
|
@Client.on_message(filters.text & filters.private, group=1)
|
||||||
|
async def echo_reversed(client, message):
|
||||||
|
await message.reply(message.text[::-1])
|
||||||
|
|
||||||
|
- ``main.py``
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import Client
|
||||||
|
|
||||||
|
plugins = dict(root="plugins")
|
||||||
|
|
||||||
|
Client("my_account", plugins=plugins).run()
|
||||||
|
|
||||||
|
|
||||||
|
The first important thing to note is the new ``plugins`` folder. You can put *any python file* in *any subfolder* and
|
||||||
|
each file can contain *any decorated function* (handlers) with one limitation: within a single module (file) you must
|
||||||
|
use different names for each decorated function.
|
||||||
|
|
||||||
|
The second thing is telling Pyrofork where to look for your plugins: you can use the Client parameter "plugins";
|
||||||
|
the *root* value must match the name of your plugins root folder. Your Pyrofork Client instance will **automatically**
|
||||||
|
scan the folder upon starting 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) and things will work just the same.
|
||||||
|
|
||||||
|
Specifying the Plugins to include
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
By default, if you don't explicitly supply a list of plugins, every valid one found inside your plugins root folder will
|
||||||
|
be included by following the alphabetical order of the directory structure (files and subfolders); the single handlers
|
||||||
|
found inside each module will be, instead, loaded in the order they are defined, from top to bottom.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Remember: there can be at most one handler, within a group, dealing with a specific update. Plugins with overlapping
|
||||||
|
filters included a second time will not work, by design. Learn more at :doc:`More on Updates <more-on-updates>`.
|
||||||
|
|
||||||
|
This default loading behaviour is usually enough, but sometimes you want to have more control on what to include (or
|
||||||
|
exclude) and in which exact order to load plugins. The way to do this is to make use of ``include`` and ``exclude``
|
||||||
|
directives in the dictionary passed as Client argument. Here's how they work:
|
||||||
|
|
||||||
|
- If both ``include`` and ``exclude`` are omitted, all plugins are loaded as described above.
|
||||||
|
- If ``include`` is given, only the specified plugins will be loaded, in the order they are passed.
|
||||||
|
- If ``exclude`` is given, the plugins specified here will be unloaded.
|
||||||
|
|
||||||
|
The ``include`` and ``exclude`` value is a **list of strings**. Each string containing the path of the module relative
|
||||||
|
to the plugins root folder, in Python notation (dots instead of slashes).
|
||||||
|
|
||||||
|
E.g.: ``subfolder.module`` refers to ``plugins/subfolder/module.py``, with ``root="plugins"``.
|
||||||
|
|
||||||
|
You can also choose the order in which the single handlers inside a module are loaded, thus overriding the default
|
||||||
|
top-to-bottom loading policy. You can do this by appending the name of the functions to the module path, each one
|
||||||
|
separated by a blank space.
|
||||||
|
|
||||||
|
E.g.: ``subfolder.module fn2 fn1 fn3`` will load *fn2*, *fn1* and *fn3* from *subfolder.module*, in this order.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
^^^^^^^^
|
||||||
|
|
||||||
|
Given this plugins folder structure with three modules, each containing their own handlers (fn1, fn2, etc...), which are
|
||||||
|
also organized in subfolders:
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
myproject/
|
||||||
|
plugins/
|
||||||
|
subfolder1/
|
||||||
|
plugins1.py
|
||||||
|
- fn1
|
||||||
|
- fn2
|
||||||
|
- fn3
|
||||||
|
subfolder2/
|
||||||
|
plugins2.py
|
||||||
|
...
|
||||||
|
plugins0.py
|
||||||
|
...
|
||||||
|
...
|
||||||
|
|
||||||
|
- Load every handler from every module, namely *plugins0.py*, *plugins1.py* and *plugins2.py* in alphabetical order
|
||||||
|
(files) and definition order (handlers inside files):
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
plugins = dict(root="plugins")
|
||||||
|
|
||||||
|
Client("my_account", plugins=plugins).run()
|
||||||
|
|
||||||
|
- Load only handlers defined inside *plugins2.py* and *plugins0.py*, in this order:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
plugins = dict(
|
||||||
|
root="plugins",
|
||||||
|
include=[
|
||||||
|
"subfolder2.plugins2",
|
||||||
|
"plugins0"
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
Client("my_account", plugins=plugins).run()
|
||||||
|
|
||||||
|
- Load everything except the handlers inside *plugins2.py*:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
plugins = dict(
|
||||||
|
root="plugins",
|
||||||
|
exclude=["subfolder2.plugins2"]
|
||||||
|
)
|
||||||
|
|
||||||
|
Client("my_account", plugins=plugins).run()
|
||||||
|
|
||||||
|
- Load only *fn3*, *fn1* and *fn2* (in this order) from *plugins1.py*:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
plugins = dict(
|
||||||
|
root="plugins",
|
||||||
|
include=["subfolder1.plugins1 fn3 fn1 fn2"]
|
||||||
|
)
|
||||||
|
|
||||||
|
Client("my_account", plugins=plugins).run()
|
||||||
|
|
||||||
|
Load/Unload Plugins at Runtime
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
In the previous section we've explained how to specify which plugins to load and which to ignore before your Client
|
||||||
|
starts. Here we'll show, instead, how to unload and load again a previously registered plugin at runtime.
|
||||||
|
|
||||||
|
Each function decorated with the usual ``on_message`` decorator (or any other decorator that deals with Telegram
|
||||||
|
updates) will be modified in such a way that a special ``handlers`` attribute pointing to a list of tuples of
|
||||||
|
*(handler: Handler, group: int)* is attached to the function object itself.
|
||||||
|
|
||||||
|
- ``plugins/handlers.py``
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
@Client.on_message(filters.text & filters.private)
|
||||||
|
async def echo(client, message):
|
||||||
|
await message.reply(message.text)
|
||||||
|
|
||||||
|
print(echo)
|
||||||
|
print(echo.handlers)
|
||||||
|
|
||||||
|
- Printing ``echo`` will show something like ``<function echo at 0x10e3b6598>``.
|
||||||
|
|
||||||
|
- Printing ``echo.handlers`` will reveal the handlers, that is, a list of tuples containing the actual handlers and
|
||||||
|
the groups they were registered on ``[(<MessageHandler object at 0x10e3abc50>, 0)]``.
|
||||||
|
|
||||||
|
Unloading
|
||||||
|
^^^^^^^^^
|
||||||
|
|
||||||
|
In order to unload a plugin, all you need to do is obtain a reference to it by importing the relevant module and call
|
||||||
|
:meth:`~pyrogram.Client.remove_handler` Client's method with your function's *handler* instance:
|
||||||
|
|
||||||
|
- ``main.py``
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from plugins.handlers import echo
|
||||||
|
|
||||||
|
handlers = echo.handlers
|
||||||
|
|
||||||
|
for h in handlers:
|
||||||
|
app.remove_handler(*h)
|
||||||
|
|
||||||
|
The star ``*`` operator is used to unpack the tuple into positional arguments so that *remove_handler* will receive
|
||||||
|
exactly what is needed. The same could have been achieved with:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
handlers = echo.handlers
|
||||||
|
handler, group = handlers[0]
|
||||||
|
|
||||||
|
app.remove_handler(handler, group)
|
||||||
|
|
||||||
|
Loading
|
||||||
|
^^^^^^^
|
||||||
|
|
||||||
|
Similarly to the unloading process, in order to load again a previously unloaded plugin you do the same, but this time
|
||||||
|
using :meth:`~pyrogram.Client.add_handler` instead. Example:
|
||||||
|
|
||||||
|
- ``main.py``
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from plugins.handlers import echo
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
handlers = echo.handlers
|
||||||
|
|
||||||
|
for h in handlers:
|
||||||
|
app.add_handler(*h)
|
||||||
88
docs/source/topics/speedups.rst
Normal file
88
docs/source/topics/speedups.rst
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
Speedups
|
||||||
|
========
|
||||||
|
|
||||||
|
Pyrofork's speed can be boosted up by using TgCrypto and uvloop.
|
||||||
|
|
||||||
|
.. contents:: Contents
|
||||||
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
|
:local:
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
TgCrypto
|
||||||
|
--------
|
||||||
|
|
||||||
|
TgCrypto_ is a high-performance, easy-to-install cryptography library specifically written in C for Pyrofork as a Python
|
||||||
|
extension. It is a replacement for a slower Python-only alternative and implements the cryptographic algorithms Telegram
|
||||||
|
requires, namely: AES-256-IGE, AES-256-CTR and AES-256-CBC.
|
||||||
|
|
||||||
|
Installation
|
||||||
|
^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ pip3 install -U tgcrypto
|
||||||
|
|
||||||
|
Usage
|
||||||
|
^^^^^
|
||||||
|
|
||||||
|
Pyrofork will automatically make use of TgCrypto when detected, all you need to do is to install it.
|
||||||
|
|
||||||
|
uvloop
|
||||||
|
------
|
||||||
|
|
||||||
|
uvloop_ is a fast, drop-in replacement of the built-in asyncio event loop. uvloop is implemented in Cython and uses
|
||||||
|
libuv under the hood. It makes asyncio 2-4x faster.
|
||||||
|
|
||||||
|
Installation
|
||||||
|
^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ pip3 install -U uvloop
|
||||||
|
|
||||||
|
Usage
|
||||||
|
^^^^^
|
||||||
|
|
||||||
|
Call ``uvloop.install()`` before calling ``asyncio.run()`` or ``app.run()``.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import uvloop
|
||||||
|
|
||||||
|
from pyrogram import Client
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
app = Client("my_account")
|
||||||
|
|
||||||
|
async with app:
|
||||||
|
print(await app.get_me())
|
||||||
|
|
||||||
|
|
||||||
|
uvloop.install()
|
||||||
|
asyncio.run(main())
|
||||||
|
|
||||||
|
The ``uvloop.install()`` call also needs to be placed before creating a Client instance.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
import uvloop
|
||||||
|
from pyrogram import Client
|
||||||
|
|
||||||
|
uvloop.install()
|
||||||
|
|
||||||
|
app = Client("my_account")
|
||||||
|
|
||||||
|
|
||||||
|
@app.on_message()
|
||||||
|
async def hello(client, message):
|
||||||
|
print(await client.get_me())
|
||||||
|
|
||||||
|
|
||||||
|
app.run()
|
||||||
|
|
||||||
|
.. _TgCrypto: https://github.com/pyrogram/tgcrypto
|
||||||
|
.. _uvloop: https://github.com/MagicStack/uvloop
|
||||||
90
docs/source/topics/storage-engines.rst
Normal file
90
docs/source/topics/storage-engines.rst
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
Storage Engines
|
||||||
|
===============
|
||||||
|
|
||||||
|
Every time you login to Telegram, some personal piece of data are created and held by both parties (the client, Pyrofork
|
||||||
|
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.
|
||||||
|
|
||||||
|
.. contents:: Contents
|
||||||
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
|
:local:
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
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, Pyrofork needs to store the generated session data somewhere.
|
||||||
|
|
||||||
|
Different Storage Engines
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
Pyrofork offers two different types of storage engines: a **File Storage** and a **Memory Storage**.
|
||||||
|
These engines are well integrated in the framework 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 details.
|
||||||
|
The database will be saved to disk as a single portable file and is designed to efficiently store and retrieve
|
||||||
|
data whenever they are needed.
|
||||||
|
|
||||||
|
To use this type of engine, simply pass any name of your choice to the ``name`` parameter of the
|
||||||
|
:obj:`~pyrogram.Client` constructor, as usual:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import Client
|
||||||
|
|
||||||
|
async with Client("my_account") as app:
|
||||||
|
print(await 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 Pyrofork 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 to disk, you can use an in-memory storage by passing True to the
|
||||||
|
``in_memory`` parameter of the :obj:`~pyrogram.Client` constructor:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import Client
|
||||||
|
|
||||||
|
async with Client("my_account", in_memory=True) as app:
|
||||||
|
print(await app.get_me())
|
||||||
|
|
||||||
|
This storage engine is still backed by SQLite, but the database exists purely in memory. This means that, 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
|
||||||
|
---------------
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
async with Client("my_account", in_memory=True) as app:
|
||||||
|
print(await app.export_session_string())
|
||||||
|
|
||||||
|
...and save the resulting string. You can use this string by passing it as Client argument the next time you want to
|
||||||
|
login using the same session; the storage used will still be in-memory:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import Client
|
||||||
|
|
||||||
|
session_string = "...ZnUIFD8jsjXTb8g_vpxx48k1zkov9sapD-tzjz-S4WZv70M..."
|
||||||
|
|
||||||
|
async with Client("my_account", session_string=session_string) as app:
|
||||||
|
print(await app.get_me())
|
||||||
|
|
||||||
|
Session strings are useful when you want to run authorized Pyrofork clients on platforms where their ephemeral
|
||||||
|
filesystems makes it harder for a file-based storage engine to properly work as intended.
|
||||||
88
docs/source/topics/synchronous.rst
Normal file
88
docs/source/topics/synchronous.rst
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
Synchronous Usage
|
||||||
|
=================
|
||||||
|
|
||||||
|
Pyrofork is an asynchronous framework and as such is subject to the asynchronous rules. It can, however, run in
|
||||||
|
synchronous mode (also known as non-asynchronous or sync/non-async for short). This mode exists mainly as a convenience
|
||||||
|
way for invoking methods without the need of ``async``/``await`` keywords and the extra boilerplate, but **it's not the
|
||||||
|
intended way to use the framework**.
|
||||||
|
|
||||||
|
You can use Pyrofork in this synchronous mode when you want to write something short and contained without the
|
||||||
|
async boilerplate or in case you want to combine Pyrofork with other libraries that are not async.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
You have to be very careful when using the framework in its synchronous, non-native form, especially when combined
|
||||||
|
with other non-async libraries because thread blocking operations that clog the asynchronous event loop underneath
|
||||||
|
will make the program run erratically.
|
||||||
|
|
||||||
|
.. contents:: Contents
|
||||||
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
|
:local:
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
Synchronous Invocations
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
The following is a standard example of running asynchronous functions with Python's asyncio.
|
||||||
|
Pyrofork is being used inside the main function with its asynchronous interface.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
from pyrogram import Client
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
app = Client("my_account")
|
||||||
|
|
||||||
|
async with app:
|
||||||
|
await app.send_message("me", "Hi!")
|
||||||
|
|
||||||
|
|
||||||
|
asyncio.run(main())
|
||||||
|
|
||||||
|
To run Pyrofork synchronously, use the non-async context manager as shown in the following example.
|
||||||
|
As you can see, the non-async example becomes less cluttered.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import Client
|
||||||
|
|
||||||
|
app = Client("my_account")
|
||||||
|
|
||||||
|
with app:
|
||||||
|
app.send_message("me", "Hi!")
|
||||||
|
|
||||||
|
Synchronous handlers
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
You can also have synchronous handlers; you only need to define the callback function without using ``async def`` and
|
||||||
|
invoke API methods by not placing ``await`` in front of them. Mixing ``def`` and ``async def`` handlers together is also
|
||||||
|
possible.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
@app.on_message()
|
||||||
|
async def handler1(client, message):
|
||||||
|
await message.forward("me")
|
||||||
|
|
||||||
|
@app.on_edited_message()
|
||||||
|
def handler2(client, message):
|
||||||
|
message.forward("me")
|
||||||
|
|
||||||
|
uvloop usage
|
||||||
|
------------
|
||||||
|
|
||||||
|
When using Pyrofork in its synchronous mode combined with uvloop, you need to call ``uvloop.install()`` before importing
|
||||||
|
Pyrofork.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
import uvloop
|
||||||
|
uvloop.install()
|
||||||
|
|
||||||
|
from pyrogram import Client
|
||||||
|
|
||||||
|
...
|
||||||
41
docs/source/topics/test-servers.rst
Normal file
41
docs/source/topics/test-servers.rst
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
Test Servers
|
||||||
|
============
|
||||||
|
|
||||||
|
If you wish to test your application in a separate environment, Pyrofork is able to authorize your account into
|
||||||
|
Telegram's test servers without hassle. All you need to do is start a new session (e.g.: "my_account_test") using
|
||||||
|
``test_mode=True``:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import Client
|
||||||
|
|
||||||
|
async with Client("my_account_test", test_mode=True) as app:
|
||||||
|
print(await app.get_me())
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
If this is the first time you login into test servers, you will be asked to register your account first.
|
||||||
|
Accounts registered on test servers reside in a different, parallel instance of a Telegram server.
|
||||||
|
|
||||||
|
.. contents:: Contents
|
||||||
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
|
:local:
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
Test Mode in Official Apps
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
You can also login yourself into test servers using official desktop apps, such as Telegram Web and Telegram Desktop:
|
||||||
|
|
||||||
|
- **Telegram Web**: Login here: https://web.telegram.org/?test=1
|
||||||
|
- **Telegram Desktop**: Hold ``Alt+Shift`` and right click on "Add account", then choose "Test server".
|
||||||
|
|
||||||
|
Test Numbers
|
||||||
|
------------
|
||||||
|
|
||||||
|
Beside normal numbers, the test environment allows you to login with reserved test numbers.
|
||||||
|
Valid phone numbers follow the pattern ``99966XYYYY``, where ``X`` is the DC number (1 to 3) and ``YYYY`` are random
|
||||||
|
numbers. Users with such numbers always get ``XXXXX`` or ``XXXXXX`` as the confirmation code (the DC number, repeated
|
||||||
|
five or six times).
|
||||||
243
docs/source/topics/text-formatting.rst
Normal file
243
docs/source/topics/text-formatting.rst
Normal file
|
|
@ -0,0 +1,243 @@
|
||||||
|
Text Formatting
|
||||||
|
===============
|
||||||
|
|
||||||
|
.. role:: strike
|
||||||
|
:class: strike
|
||||||
|
|
||||||
|
.. role:: underline
|
||||||
|
:class: underline
|
||||||
|
|
||||||
|
.. role:: bold-underline
|
||||||
|
:class: bold-underline
|
||||||
|
|
||||||
|
.. role:: strike-italic
|
||||||
|
:class: strike-italic
|
||||||
|
|
||||||
|
Pyrofork uses a custom Markdown dialect for text formatting which adds some unique features that make writing styled
|
||||||
|
texts easier in both Markdown and HTML. You can send sophisticated text messages and media captions using a
|
||||||
|
variety of decorations that can also be nested in order to combine multiple styles together.
|
||||||
|
|
||||||
|
.. contents:: Contents
|
||||||
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
|
:local:
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
Basic Styles
|
||||||
|
------------
|
||||||
|
|
||||||
|
When formatting your messages, you can choose between Markdown-style, HTML-style or both (default). The following is a
|
||||||
|
list of the basic styles currently supported by Pyrofork.
|
||||||
|
|
||||||
|
- **bold**
|
||||||
|
- *italic*
|
||||||
|
- :strike:`strike`
|
||||||
|
- :underline:`underline`
|
||||||
|
- spoiler
|
||||||
|
- `text URL <https://pyrogram.org>`_
|
||||||
|
- `user text mention <tg://user?id=123456789>`_
|
||||||
|
- ``inline fixed-width code``
|
||||||
|
- .. code-block:: text
|
||||||
|
|
||||||
|
pre-formatted
|
||||||
|
fixed-width
|
||||||
|
code block
|
||||||
|
|
||||||
|
Markdown Style
|
||||||
|
--------------
|
||||||
|
|
||||||
|
To strictly use this mode, pass :obj:`~pyrogram.enums.ParseMode.MARKDOWN` to the *parse_mode* parameter when using
|
||||||
|
:meth:`~pyrogram.Client.send_message`. Use the following syntax in your message:
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
**bold**
|
||||||
|
|
||||||
|
__italic__
|
||||||
|
|
||||||
|
--underline--
|
||||||
|
|
||||||
|
~~strike~~
|
||||||
|
|
||||||
|
||spoiler||
|
||||||
|
|
||||||
|
[text URL](https://pyrogram.org/)
|
||||||
|
|
||||||
|
[text user mention](tg://user?id=123456789)
|
||||||
|
|
||||||
|
`inline fixed-width code`
|
||||||
|
|
||||||
|
```
|
||||||
|
pre-formatted
|
||||||
|
fixed-width
|
||||||
|
code block
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example**:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import enums
|
||||||
|
|
||||||
|
await app.send_message(
|
||||||
|
"me",
|
||||||
|
(
|
||||||
|
"**bold**, "
|
||||||
|
"__italic__, "
|
||||||
|
"--underline--, "
|
||||||
|
"~~strike~~, "
|
||||||
|
"||spoiler||, "
|
||||||
|
"[URL](https://pyrogram.org), "
|
||||||
|
"`code`, "
|
||||||
|
"```"
|
||||||
|
"for i in range(10):\n"
|
||||||
|
" print(i)"
|
||||||
|
"```"
|
||||||
|
),
|
||||||
|
parse_mode=enums.ParseMode.MARKDOWN
|
||||||
|
)
|
||||||
|
|
||||||
|
HTML Style
|
||||||
|
----------
|
||||||
|
|
||||||
|
To strictly use this mode, pass :obj:`~pyrogram.enums.HTML` to the *parse_mode* parameter when using
|
||||||
|
:meth:`~pyrogram.Client.send_message`. The following tags are currently supported:
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
<b>bold</b>, <strong>bold</strong>
|
||||||
|
|
||||||
|
<i>italic</i>, <em>italic</em>
|
||||||
|
|
||||||
|
<u>underline</u>
|
||||||
|
|
||||||
|
<s>strike</s>, <del>strike</del>, <strike>strike</strike>
|
||||||
|
|
||||||
|
<spoiler>spoiler</spoiler>
|
||||||
|
|
||||||
|
<a href="https://pyrogram.org/">text URL</a>
|
||||||
|
|
||||||
|
<a href="tg://user?id=123456789">inline mention</a>
|
||||||
|
|
||||||
|
<code>inline fixed-width code</code>
|
||||||
|
|
||||||
|
<emoji id="12345678901234567890">🔥</emoji>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
pre-formatted
|
||||||
|
fixed-width
|
||||||
|
code block
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
**Example**:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import enums
|
||||||
|
|
||||||
|
await app.send_message(
|
||||||
|
"me",
|
||||||
|
(
|
||||||
|
"<b>bold</b>, "
|
||||||
|
"<i>italic</i>, "
|
||||||
|
"<u>underline</u>, "
|
||||||
|
"<s>strike</s>, "
|
||||||
|
"<spoiler>spoiler</spoiler>, "
|
||||||
|
"<a href=\"https://pyrogram.org/\">URL</a>, "
|
||||||
|
"<code>code</code>\n\n"
|
||||||
|
"<pre>"
|
||||||
|
"for i in range(10):\n"
|
||||||
|
" print(i)"
|
||||||
|
"</pre>"
|
||||||
|
),
|
||||||
|
parse_mode=enums.ParseMode.HTML
|
||||||
|
)
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
All ``<``, ``>`` and ``&`` symbols that are not a part of a tag or an HTML entity must be replaced with the
|
||||||
|
corresponding HTML entities (``<`` with ``<``, ``>`` with ``>`` and ``&`` with ``&``). You can use this
|
||||||
|
snippet to quickly escape those characters:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
import html
|
||||||
|
|
||||||
|
text = "<my text>"
|
||||||
|
text = html.escape(text)
|
||||||
|
|
||||||
|
print(text)
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
<my text>
|
||||||
|
|
||||||
|
Different Styles
|
||||||
|
----------------
|
||||||
|
|
||||||
|
By default, when ignoring the *parse_mode* parameter, both Markdown and HTML styles are enabled together.
|
||||||
|
This means you can combine together both syntaxes in the same text:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
await app.send_message("me", "**bold**, <i>italic</i>")
|
||||||
|
|
||||||
|
Result:
|
||||||
|
|
||||||
|
**bold**, *italic*
|
||||||
|
|
||||||
|
If you don't like this behaviour you can always choose to only enable either Markdown or HTML in strict mode by passing
|
||||||
|
:obj:`~pyrogram.enums.MARKDOWN` or :obj:`~pyrogram.enums.HTML` as argument to the *parse_mode* parameter.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import enums
|
||||||
|
|
||||||
|
await app.send_message("me", "**bold**, <i>italic</i>", parse_mode=enums.ParseMode.MARKDOWN)
|
||||||
|
await app.send_message("me", "**bold**, <i>italic</i>", parse_mode=enums.ParseMode.HTML)
|
||||||
|
|
||||||
|
Result:
|
||||||
|
|
||||||
|
**bold**, <i>italic</i>
|
||||||
|
|
||||||
|
\*\*bold**, *italic*
|
||||||
|
|
||||||
|
In case you want to completely turn off the style parser, simply pass :obj:`~pyrogram.enums.DISABLED` to *parse_mode*.
|
||||||
|
The text will be sent as-is.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import enums
|
||||||
|
|
||||||
|
await app.send_message("me", "**bold**, <i>italic</i>", parse_mode=enums.ParseMode.DISABLED)
|
||||||
|
|
||||||
|
Result:
|
||||||
|
|
||||||
|
\*\*bold**, <i>italic</i>
|
||||||
|
|
||||||
|
Nested and Overlapping Entities
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
You can also style texts with more than one decoration at once by nesting entities together. For example, you can send
|
||||||
|
a text message with both :bold-underline:`bold and underline` styles, or a text that has both :strike-italic:`italic and
|
||||||
|
strike` styles, and you can still combine both Markdown and HTML together.
|
||||||
|
|
||||||
|
Here there are some example texts you can try sending:
|
||||||
|
|
||||||
|
**Markdown**:
|
||||||
|
|
||||||
|
- ``**bold, --underline--**``
|
||||||
|
- ``**bold __italic --underline ~~strike~~--__**``
|
||||||
|
- ``**bold __and** italic__``
|
||||||
|
|
||||||
|
**HTML**:
|
||||||
|
|
||||||
|
- ``<b>bold, <u>underline</u></b>``
|
||||||
|
- ``<b>bold <i>italic <u>underline <s>strike</s></u></i></b>``
|
||||||
|
- ``<b>bold <i>and</b> italic</i>``
|
||||||
|
|
||||||
|
**Combined**:
|
||||||
|
|
||||||
|
- ``--you can combine <i>HTML</i> with **Markdown**--``
|
||||||
|
- ``**and also <i>overlap** --entities</i> this way--``
|
||||||
114
docs/source/topics/use-filters.rst
Normal file
114
docs/source/topics/use-filters.rst
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
Using Filters
|
||||||
|
=============
|
||||||
|
|
||||||
|
So far we've seen :doc:`how to register a callback function <../start/updates>` that executes every time an update comes
|
||||||
|
from the server, but there's much more than that to come.
|
||||||
|
|
||||||
|
Here we'll discuss about :obj:`~pyrogram.filters`. Filters enable a fine-grain control over what kind of
|
||||||
|
updates are allowed or not to be passed in your callback functions, based on their inner details.
|
||||||
|
|
||||||
|
.. contents:: Contents
|
||||||
|
:backlinks: none
|
||||||
|
:depth: 1
|
||||||
|
:local:
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
Single Filters
|
||||||
|
--------------
|
||||||
|
|
||||||
|
Let's start right away with a simple example:
|
||||||
|
|
||||||
|
- This example will show you how to **only** handle messages containing a :class:`~pyrogram.types.Sticker` object and
|
||||||
|
ignore any other message. Filters are passed as the first argument of the decorator:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import filters
|
||||||
|
|
||||||
|
|
||||||
|
@app.on_message(filters.sticker)
|
||||||
|
async def my_handler(client, message):
|
||||||
|
print(message)
|
||||||
|
|
||||||
|
- or, without decorators. Here filters are passed as the second argument of the handler constructor; the first is the
|
||||||
|
callback function itself:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import filters
|
||||||
|
from pyrogram.handlers import MessageHandler
|
||||||
|
|
||||||
|
|
||||||
|
async def my_handler(client, message):
|
||||||
|
print(message)
|
||||||
|
|
||||||
|
|
||||||
|
app.add_handler(MessageHandler(my_handler, filters.sticker))
|
||||||
|
|
||||||
|
Combining Filters
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
Filters can be used in a more advanced way by inverting and combining more filters together using bitwise
|
||||||
|
operators ``~``, ``&`` and ``|``:
|
||||||
|
|
||||||
|
- Use ``~`` to invert a filter (behaves like the ``not`` operator).
|
||||||
|
- Use ``&`` and ``|`` to merge two filters (behave like ``and``, ``or`` operators respectively).
|
||||||
|
|
||||||
|
Here are some examples:
|
||||||
|
|
||||||
|
- Message is a **text** message **or** a **photo**.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
@app.on_message(filters.text | filters.photo)
|
||||||
|
async def my_handler(client, message):
|
||||||
|
print(message)
|
||||||
|
|
||||||
|
- Message is a **sticker** **and** is coming from a **channel or** a **private** chat.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
@app.on_message(filters.sticker & (filters.channel | filters.private))
|
||||||
|
async def my_handler(client, message):
|
||||||
|
print(message)
|
||||||
|
|
||||||
|
Advanced Filters
|
||||||
|
----------------
|
||||||
|
|
||||||
|
Some filters, like :meth:`~pyrogram.filters.command` or :meth:`~pyrogram.filters.regex`
|
||||||
|
can also accept arguments:
|
||||||
|
|
||||||
|
- Message is either a */start* or */help* **command**.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
@app.on_message(filters.command(["start", "help"]))
|
||||||
|
async def my_handler(client, message):
|
||||||
|
print(message)
|
||||||
|
|
||||||
|
- Message is a **text** message or a media **caption** matching the given **regex** pattern.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
@app.on_message(filters.regex("pyrogram"))
|
||||||
|
async def my_handler(client, message):
|
||||||
|
print(message)
|
||||||
|
|
||||||
|
More handlers using different filters can also live together.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
@app.on_message(filters.command("start"))
|
||||||
|
async def start_command(client, message):
|
||||||
|
print("This is the /start command")
|
||||||
|
|
||||||
|
|
||||||
|
@app.on_message(filters.command("help"))
|
||||||
|
async def help_command(client, message):
|
||||||
|
print("This is the /help command")
|
||||||
|
|
||||||
|
|
||||||
|
@app.on_message(filters.chat("PyroforkChat"))
|
||||||
|
async def from_pyrogramchat(client, message):
|
||||||
|
print("New message in @PyroforkChat")
|
||||||
19
docs/source/topics/voice-calls.rst
Normal file
19
docs/source/topics/voice-calls.rst
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
Voice Calls
|
||||||
|
===========
|
||||||
|
|
||||||
|
Both private voice calls and group voice calls are currently supported by third-party, external libraries that integrate
|
||||||
|
with Pyrofork.
|
||||||
|
|
||||||
|
Libraries
|
||||||
|
---------
|
||||||
|
|
||||||
|
There are currently two main libraries (with very similar names) you can use:
|
||||||
|
|
||||||
|
1. https://github.com/pytgcalls/pytgcalls
|
||||||
|
2. https://github.com/MarshalX/tgcalls
|
||||||
|
|
||||||
|
Older implementations
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
An older implementation of Telegram voice calls can be found at https://github.com/bakatrouble/pylibtgvoip (currently
|
||||||
|
outdated due to the deprecation of the Telegram VoIP library used underneath).
|
||||||
Loading…
Reference in a new issue