mirror of
https://github.com/pyrogram/pyrogram
synced 2025-08-29 13:27:47 +00:00
Merge branch 'develop'
This commit is contained in:
commit
89ffa6b196
52
README.rst
52
README.rst
@ -17,18 +17,22 @@ Pyrogram
|
|||||||
|
|
||||||
app.run()
|
app.run()
|
||||||
|
|
||||||
**Pyrogram** is a brand new Telegram_ Client Library written from the ground up in Python and C. It can be used for
|
**Pyrogram** is an elegant, easy-to-use Telegram_ client library and framework written from the ground up in Python and C.
|
||||||
building custom Telegram applications that interact with the MTProto API as both User and Bot.
|
It enables you to easily create custom apps using both user and bot identities (bot API alternative) via the `MTProto API`_.
|
||||||
|
|
||||||
|
`A fully-asynchronous variant is also available » <https://github.com/pyrogram/pyrogram/issues/181>`_
|
||||||
|
|
||||||
Features
|
Features
|
||||||
--------
|
--------
|
||||||
|
|
||||||
- **Easy to use**: You can easily install Pyrogram using pip and start building your app right away.
|
- **Easy**: You can install Pyrogram with pip and start building your applications right away.
|
||||||
- **High-level**: The low-level details of MTProto are abstracted and automatically handled.
|
- **Elegant**: Low-level details are abstracted and re-presented in a much nicer and easier way.
|
||||||
- **Fast**: Crypto parts are boosted up by TgCrypto_, a high-performance library written in pure C.
|
- **Fast**: Crypto parts are boosted up by TgCrypto_, a high-performance library written in pure C.
|
||||||
- **Updated** to the latest Telegram API version, currently Layer 91 on top of MTProto 2.0.
|
- **Documented**: Pyrogram API methods, types and public interfaces are well documented.
|
||||||
- **Documented**: The Pyrogram API is well documented and resembles the Telegram Bot API.
|
- **Type-hinted**: Exposed Pyrogram types and method parameters are all type-hinted.
|
||||||
- **Full API**, allowing to execute any advanced action an official client is able to do, and more.
|
- **Updated**, to the latest Telegram API version, currently Layer 91 on top of `MTProto 2.0`_.
|
||||||
|
- **Pluggable**: The Smart Plugin system allows to write components with minimal boilerplate code.
|
||||||
|
- **Comprehensive**: Execute any advanced action an official client is able to do, and even more.
|
||||||
|
|
||||||
Requirements
|
Requirements
|
||||||
------------
|
------------
|
||||||
@ -43,11 +47,11 @@ Installing
|
|||||||
|
|
||||||
pip3 install pyrogram
|
pip3 install pyrogram
|
||||||
|
|
||||||
Getting Started
|
Resources
|
||||||
---------------
|
---------
|
||||||
|
|
||||||
- The Docs contain lots of resources to help you getting started with Pyrogram: https://docs.pyrogram.ml.
|
- The Docs contain lots of resources to help you getting started with Pyrogram: https://docs.pyrogram.ml.
|
||||||
- Reading Examples_ in this repository is also a good way for learning how things work.
|
- Reading `Examples in this repository`_ is also a good way for learning how Pyrogram works.
|
||||||
- Seeking extra help? Don't be shy, come join and ask our Community_!
|
- Seeking extra help? Don't be shy, come join and ask our Community_!
|
||||||
- For other requests you can send an Email_ or a Message_.
|
- For other requests you can send an Email_ or a Message_.
|
||||||
|
|
||||||
@ -61,17 +65,19 @@ and documentation. Any help is appreciated!
|
|||||||
Copyright & License
|
Copyright & License
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
- Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
|
- Copyright (C) 2017-2019 Dan Tès <https://github.com/delivrance>
|
||||||
- Licensed under the terms of the `GNU Lesser General Public License v3 or later (LGPLv3+)`_
|
- Licensed under the terms of the `GNU Lesser General Public License v3 or later (LGPLv3+)`_
|
||||||
|
|
||||||
.. _`Telegram`: https://telegram.org/
|
.. _`Telegram`: https://telegram.org/
|
||||||
|
.. _`MTProto API`: https://core.telegram.org/api#telegram-api
|
||||||
.. _`Telegram API key`: https://docs.pyrogram.ml/start/ProjectSetup#api-keys
|
.. _`Telegram API key`: https://docs.pyrogram.ml/start/ProjectSetup#api-keys
|
||||||
.. _`Community`: https://t.me/PyrogramChat
|
.. _`Community`: https://t.me/PyrogramChat
|
||||||
.. _`Examples`: https://github.com/pyrogram/pyrogram/tree/master/examples
|
.. _`Examples in this repository`: https://github.com/pyrogram/pyrogram/tree/master/examples
|
||||||
.. _`GitHub`: https://github.com/pyrogram/pyrogram/issues
|
.. _`GitHub`: https://github.com/pyrogram/pyrogram/issues
|
||||||
.. _`Email`: admin@pyrogram.ml
|
.. _`Email`: admin@pyrogram.ml
|
||||||
.. _`Message`: https://t.me/haskell
|
.. _`Message`: https://t.me/haskell
|
||||||
.. _TgCrypto: https://github.com/pyrogram/tgcrypto
|
.. _TgCrypto: https://github.com/pyrogram/tgcrypto
|
||||||
|
.. _`MTProto 2.0`: https://core.telegram.org/mtproto
|
||||||
.. _`GNU Lesser General Public License v3 or later (LGPLv3+)`: COPYING.lesser
|
.. _`GNU Lesser General Public License v3 or later (LGPLv3+)`: COPYING.lesser
|
||||||
|
|
||||||
.. |header| raw:: html
|
.. |header| raw:: html
|
||||||
@ -83,17 +89,17 @@ Copyright & License
|
|||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<b>Telegram MTProto API Client Library for Python</b>
|
<b>Telegram MTProto API Framework for Python</b>
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
<a href="https://github.com/pyrogram/pyrogram/releases/latest">
|
|
||||||
Download
|
|
||||||
</a>
|
|
||||||
•
|
|
||||||
<a href="https://docs.pyrogram.ml">
|
<a href="https://docs.pyrogram.ml">
|
||||||
Documentation
|
Documentation
|
||||||
</a>
|
</a>
|
||||||
•
|
•
|
||||||
|
<a href="https://github.com/pyrogram/pyrogram/releases">
|
||||||
|
Changelog
|
||||||
|
</a>
|
||||||
|
•
|
||||||
<a href="https://t.me/PyrogramChat">
|
<a href="https://t.me/PyrogramChat">
|
||||||
Community
|
Community
|
||||||
</a>
|
</a>
|
||||||
@ -104,7 +110,7 @@ Copyright & License
|
|||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/pyrogram/tgcrypto">
|
<a href="https://github.com/pyrogram/tgcrypto">
|
||||||
<img src="https://img.shields.io/badge/tgcrypto-v1.1.1-eda738.svg?longCache=true&colorA=262b30"
|
<img src="https://img.shields.io/badge/tgcrypto-v1.1.1-eda738.svg?longCache=true&colorA=262b30"
|
||||||
alt="TgCrypto">
|
alt="TgCrypto Version">
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@ -112,12 +118,12 @@ Copyright & License
|
|||||||
:target: https://pyrogram.ml
|
:target: https://pyrogram.ml
|
||||||
:alt: Pyrogram
|
:alt: Pyrogram
|
||||||
|
|
||||||
.. |description| replace:: **Telegram MTProto API Client Library for Python**
|
.. |description| replace:: **Telegram MTProto API Framework for Python**
|
||||||
|
|
||||||
.. |scheme| image:: "https://img.shields.io/badge/schema-layer%2091-eda738.svg?longCache=true&colorA=262b30"
|
.. |schema| image:: https://img.shields.io/badge/schema-layer%2091-eda738.svg?longCache=true&colorA=262b30
|
||||||
:target: compiler/api/source/main_api.tl
|
:target: compiler/api/source/main_api.tl
|
||||||
:alt: Scheme Layer
|
:alt: Schema Layer
|
||||||
|
|
||||||
.. |tgcrypto| image:: "https://img.shields.io/badge/tgcrypto-v1.1.1-eda738.svg?longCache=true&colorA=262b30"
|
.. |tgcrypto| image:: https://img.shields.io/badge/tgcrypto-v1.1.1-eda738.svg?longCache=true&colorA=262b30
|
||||||
:target: https://github.com/pyrogram/tgcrypto
|
:target: https://github.com/pyrogram/tgcrypto
|
||||||
:alt: TgCrypto
|
:alt: TgCrypto Version
|
||||||
|
@ -26,7 +26,7 @@ NOTICE_PATH = "NOTICE"
|
|||||||
SECTION_RE = re.compile(r"---(\w+)---")
|
SECTION_RE = re.compile(r"---(\w+)---")
|
||||||
LAYER_RE = re.compile(r"//\sLAYER\s(\d+)")
|
LAYER_RE = re.compile(r"//\sLAYER\s(\d+)")
|
||||||
COMBINATOR_RE = re.compile(r"^([\w.]+)#([0-9a-f]+)\s(?:.*)=\s([\w<>.]+);(?: // Docs: (.+))?$", re.MULTILINE)
|
COMBINATOR_RE = re.compile(r"^([\w.]+)#([0-9a-f]+)\s(?:.*)=\s([\w<>.]+);(?: // Docs: (.+))?$", re.MULTILINE)
|
||||||
ARGS_RE = re.compile("[^{](\w+):([\w?!.<>]+)")
|
ARGS_RE = re.compile("[^{](\w+):([\w?!.<>#]+)")
|
||||||
FLAGS_RE = re.compile(r"flags\.(\d+)\?")
|
FLAGS_RE = re.compile(r"flags\.(\d+)\?")
|
||||||
FLAGS_RE_2 = re.compile(r"flags\.(\d+)\?([\w<>.]+)")
|
FLAGS_RE_2 = re.compile(r"flags\.(\d+)\?([\w<>.]+)")
|
||||||
FLAGS_RE_3 = re.compile(r"flags:#")
|
FLAGS_RE_3 = re.compile(r"flags:#")
|
||||||
@ -288,17 +288,20 @@ def start():
|
|||||||
sorted_args = sort_args(c.args)
|
sorted_args = sort_args(c.args)
|
||||||
|
|
||||||
arguments = ", " + ", ".join(
|
arguments = ", " + ", ".join(
|
||||||
[get_argument_type(i) for i in sorted_args]
|
[get_argument_type(i) for i in sorted_args if i != ("flags", "#")]
|
||||||
) if c.args else ""
|
) if c.args else ""
|
||||||
|
|
||||||
fields = "\n ".join(
|
fields = "\n ".join(
|
||||||
["self.{0} = {0} # {1}".format(i[0], i[1]) for i in c.args]
|
["self.{0} = {0} # {1}".format(i[0], i[1]) for i in c.args if i != ("flags", "#")]
|
||||||
) if c.args else "pass"
|
) if c.args else "pass"
|
||||||
|
|
||||||
docstring_args = []
|
docstring_args = []
|
||||||
docs = c.docs.split("|")[1:] if c.docs else None
|
docs = c.docs.split("|")[1:] if c.docs else None
|
||||||
|
|
||||||
for i, arg in enumerate(sorted_args):
|
for i, arg in enumerate(sorted_args):
|
||||||
|
if arg == ("flags", "#"):
|
||||||
|
continue
|
||||||
|
|
||||||
arg_name, arg_type = arg
|
arg_name, arg_type = arg
|
||||||
is_optional = FLAGS_RE.match(arg_type)
|
is_optional = FLAGS_RE.match(arg_type)
|
||||||
flag_number = is_optional.group(1) if is_optional else -1
|
flag_number = is_optional.group(1) if is_optional else -1
|
||||||
@ -338,27 +341,30 @@ def start():
|
|||||||
if references:
|
if references:
|
||||||
docstring_args += "\n\n See Also:\n This object can be returned by " + references + "."
|
docstring_args += "\n\n See Also:\n This object can be returned by " + references + "."
|
||||||
|
|
||||||
if c.has_flags:
|
write_types = read_types = "" if c.has_flags else "# No flags\n "
|
||||||
|
|
||||||
|
for arg_name, arg_type in c.args:
|
||||||
|
flag = FLAGS_RE_2.findall(arg_type)
|
||||||
|
|
||||||
|
if arg_name == "flags" and arg_type == "#":
|
||||||
write_flags = []
|
write_flags = []
|
||||||
|
|
||||||
for i in c.args:
|
for i in c.args:
|
||||||
flag = FLAGS_RE.match(i[1])
|
flag = FLAGS_RE.match(i[1])
|
||||||
if flag:
|
if flag:
|
||||||
write_flags.append("flags |= (1 << {}) if self.{} is not None else 0".format(flag.group(1), i[0]))
|
write_flags.append(
|
||||||
|
"flags |= (1 << {}) if self.{} is not None else 0".format(flag.group(1), i[0]))
|
||||||
|
|
||||||
write_flags = "\n ".join([
|
write_flags = "\n ".join([
|
||||||
"flags = 0",
|
"flags = 0",
|
||||||
"\n ".join(write_flags),
|
"\n ".join(write_flags),
|
||||||
"b.write(Int(flags))"
|
"b.write(Int(flags))\n "
|
||||||
])
|
])
|
||||||
else:
|
|
||||||
write_flags = "# No flags"
|
|
||||||
|
|
||||||
read_flags = "flags = Int.read(b)" if c.has_flags else "# No flags"
|
write_types += write_flags
|
||||||
|
read_types += "flags = Int.read(b)\n "
|
||||||
|
|
||||||
write_types = read_types = ""
|
continue
|
||||||
|
|
||||||
for arg_name, arg_type in c.args:
|
|
||||||
flag = FLAGS_RE_2.findall(arg_type)
|
|
||||||
|
|
||||||
if flag:
|
if flag:
|
||||||
index, flag_type = flag[0]
|
index, flag_type = flag[0]
|
||||||
@ -448,11 +454,9 @@ def start():
|
|||||||
object_id=c.id,
|
object_id=c.id,
|
||||||
arguments=arguments,
|
arguments=arguments,
|
||||||
fields=fields,
|
fields=fields,
|
||||||
read_flags=read_flags,
|
|
||||||
read_types=read_types,
|
read_types=read_types,
|
||||||
write_flags=write_flags,
|
|
||||||
write_types=write_types,
|
write_types=write_types,
|
||||||
return_arguments=", ".join([i[0] for i in sorted_args])
|
return_arguments=", ".join([i[0] for i in sorted_args if i != ("flags", "#")])
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -16,7 +16,6 @@ class {class_name}(Object):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def read(b: BytesIO, *args) -> "{class_name}":
|
def read(b: BytesIO, *args) -> "{class_name}":
|
||||||
{read_flags}
|
|
||||||
{read_types}
|
{read_types}
|
||||||
return {class_name}({return_arguments})
|
return {class_name}({return_arguments})
|
||||||
|
|
||||||
@ -24,6 +23,5 @@ class {class_name}(Object):
|
|||||||
b = BytesIO()
|
b = BytesIO()
|
||||||
b.write(Int(self.ID, False))
|
b.write(Int(self.ID, False))
|
||||||
|
|
||||||
{write_flags}
|
|
||||||
{write_types}
|
{write_types}
|
||||||
return b.getvalue()
|
return b.getvalue()
|
||||||
|
@ -62,7 +62,7 @@ USER_IS_BOT A bot cannot send messages to other bots or to itself
|
|||||||
WEBPAGE_CURL_FAILED Telegram server could not fetch the provided URL
|
WEBPAGE_CURL_FAILED Telegram server could not fetch the provided URL
|
||||||
STICKERSET_INVALID The requested sticker set is invalid
|
STICKERSET_INVALID The requested sticker set is invalid
|
||||||
PEER_FLOOD The method can't be used because your account is limited
|
PEER_FLOOD The method can't be used because your account is limited
|
||||||
MEDIA_CAPTION_TOO_LONG The media caption is longer than 200 characters
|
MEDIA_CAPTION_TOO_LONG The media caption is longer than 1024 characters
|
||||||
USER_NOT_MUTUAL_CONTACT The user is not a mutual contact
|
USER_NOT_MUTUAL_CONTACT The user is not a mutual contact
|
||||||
USER_CHANNELS_TOO_MUCH The user is already in too many channels or supergroups
|
USER_CHANNELS_TOO_MUCH The user is already in too many channels or supergroups
|
||||||
API_ID_PUBLISHED_FLOOD You are using an API key that is limited on the server side
|
API_ID_PUBLISHED_FLOOD You are using an API key that is limited on the server side
|
||||||
@ -81,3 +81,10 @@ INPUT_USER_DEACTIVATED The target user has been deactivated
|
|||||||
PASSWORD_RECOVERY_NA The password recovery e-mail is not available
|
PASSWORD_RECOVERY_NA The password recovery e-mail is not available
|
||||||
PASSWORD_EMPTY The password entered is empty
|
PASSWORD_EMPTY The password entered is empty
|
||||||
PHONE_NUMBER_FLOOD This number has tried to login too many times
|
PHONE_NUMBER_FLOOD This number has tried to login too many times
|
||||||
|
TAKEOUT_INVALID The takeout id is invalid
|
||||||
|
TAKEOUT_REQUIRED The method must be invoked inside a takeout session
|
||||||
|
MESSAGE_POLL_CLOSED You can't interact with a closed poll
|
||||||
|
MEDIA_INVALID The media is invalid
|
||||||
|
BOT_SCORE_NOT_MODIFIED The bot score was not modified
|
||||||
|
USER_BOT_REQUIRED The method can be used by bots only
|
||||||
|
IMAGE_PROCESS_FAILED The server failed to process your image
|
|
@ -1,2 +1,3 @@
|
|||||||
id message
|
id message
|
||||||
FLOOD_WAIT_X A wait of {x} seconds is required
|
FLOOD_WAIT_X A wait of {x} seconds is required
|
||||||
|
TAKEOUT_INIT_DELAY_X You have to confirm the data export request using one of your mobile devices or wait {x} seconds
|
||||||
|
|
@ -10,27 +10,28 @@ Welcome to Pyrogram
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<b>Telegram MTProto API Client Library for Python</b>
|
<b>Telegram MTProto API Framework for Python</b>
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
<a href="https://github.com/pyrogram/pyrogram/releases/latest">
|
<a href="https://docs.pyrogram.ml">
|
||||||
Download
|
Documentation
|
||||||
</a>
|
</a>
|
||||||
•
|
•
|
||||||
<a href="https://github.com/pyrogram/pyrogram">
|
<a href="https://github.com/pyrogram/pyrogram/releases">
|
||||||
Source code
|
Changelog
|
||||||
</a>
|
</a>
|
||||||
•
|
•
|
||||||
<a href="https://t.me/PyrogramChat">
|
<a href="https://t.me/PyrogramChat">
|
||||||
Community
|
Community
|
||||||
</a>
|
</a>
|
||||||
<br>
|
<br>
|
||||||
<a href="https://github.com/pyrogram/pyrogram/blob/master/compiler/api/source/main_api.tl">
|
<a href="compiler/api/source/main_api.tl">
|
||||||
<img src="https://img.shields.io/badge/schema-layer%2091-eda738.svg?longCache=true&colorA=262b30"
|
<img src="https://img.shields.io/badge/schema-layer%2091-eda738.svg?longCache=true&colorA=262b30"
|
||||||
alt="Scheme Layer">
|
alt="Schema Layer">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/pyrogram/tgcrypto">
|
<a href="https://github.com/pyrogram/tgcrypto">
|
||||||
<img src="https://img.shields.io/badge/tgcrypto-v1.1.1-eda738.svg?longCache=true&colorA=262b30"
|
<img src="https://img.shields.io/badge/tgcrypto-v1.1.1-eda738.svg?longCache=true&colorA=262b30"
|
||||||
alt="TgCrypto">
|
alt="TgCrypto Version">
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@ -48,25 +49,27 @@ Welcome to Pyrogram
|
|||||||
|
|
||||||
app.run()
|
app.run()
|
||||||
|
|
||||||
Welcome to Pyrogram's Documentation! Here you can find resources for learning how to use the library.
|
Welcome to Pyrogram's Documentation! Here you can find resources for learning how to use the framework.
|
||||||
Contents are organized into self-contained topics and can be accessed from the sidebar, or by following them in order
|
Contents are organized into self-contained topics and can be accessed from the sidebar, or by following them in order
|
||||||
using the Next button at the end of each page. But first, here's a brief overview of what is this all about.
|
using the Next button at the end of each page. But first, here's a brief overview of what is this all about.
|
||||||
|
|
||||||
About
|
About
|
||||||
-----
|
-----
|
||||||
|
|
||||||
**Pyrogram** is a brand new Telegram_ Client Library written from the ground up in Python and C. It can be used for
|
**Pyrogram** is an elegant, easy-to-use Telegram_ client library and framework written from the ground up in Python and C.
|
||||||
building custom Telegram applications that interact with the MTProto API as both User and Bot.
|
It enables you to easily create custom apps using both user and bot identities (bot API alternative) via the `MTProto API`_.
|
||||||
|
|
||||||
Features
|
Features
|
||||||
--------
|
--------
|
||||||
|
|
||||||
- **Easy to use**: You can easily install Pyrogram using pip and start building your app right away.
|
- **Easy**: You can install Pyrogram with pip and start building your applications right away.
|
||||||
- **High-level**: The low-level details of MTProto are abstracted and automatically handled.
|
- **Elegant**: Low-level details are abstracted and re-presented in a much nicer and easier way.
|
||||||
- **Fast**: Crypto parts are boosted up by TgCrypto_, a high-performance library written in pure C.
|
- **Fast**: Crypto parts are boosted up by TgCrypto_, a high-performance library written in pure C.
|
||||||
- **Updated** to the latest Telegram API version, currently Layer 91 on top of MTProto 2.0.
|
- **Documented**: Pyrogram API methods, types and public interfaces are well documented.
|
||||||
- **Documented**: The Pyrogram API is well documented and resembles the Telegram Bot API.
|
- **Type-hinted**: Exposed Pyrogram types and method parameters are all type-hinted.
|
||||||
- **Full API**, allowing to execute any advanced action an official client is able to do, and more.
|
- **Updated**, to the latest Telegram API version, currently Layer 91 on top of `MTProto 2.0`_.
|
||||||
|
- **Pluggable**: The Smart Plugin system allows to write components with minimal boilerplate code.
|
||||||
|
- **Comprehensive**: Execute any advanced action an official client is able to do, and even more.
|
||||||
|
|
||||||
To get started, press the Next button.
|
To get started, press the Next button.
|
||||||
|
|
||||||
@ -85,6 +88,7 @@ To get started, press the Next button.
|
|||||||
resources/UpdateHandling
|
resources/UpdateHandling
|
||||||
resources/UsingFilters
|
resources/UsingFilters
|
||||||
resources/MoreOnUpdates
|
resources/MoreOnUpdates
|
||||||
|
resources/ConfigurationFile
|
||||||
resources/SmartPlugins
|
resources/SmartPlugins
|
||||||
resources/AutoAuthorization
|
resources/AutoAuthorization
|
||||||
resources/CustomizeSessions
|
resources/CustomizeSessions
|
||||||
@ -95,6 +99,7 @@ To get started, press the Next button.
|
|||||||
resources/ErrorHandling
|
resources/ErrorHandling
|
||||||
resources/TestServers
|
resources/TestServers
|
||||||
resources/AdvancedUsage
|
resources/AdvancedUsage
|
||||||
|
resources/VoiceCalls
|
||||||
resources/Changelog
|
resources/Changelog
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
@ -112,3 +117,5 @@ To get started, press the Next button.
|
|||||||
|
|
||||||
.. _`Telegram`: https://telegram.org/
|
.. _`Telegram`: https://telegram.org/
|
||||||
.. _TgCrypto: https://docs.pyrogram.ml/resources/TgCrypto/
|
.. _TgCrypto: https://docs.pyrogram.ml/resources/TgCrypto/
|
||||||
|
.. _`MTProto API`: https://core.telegram.org/api#telegram-api
|
||||||
|
.. _`MTProto 2.0`: https://core.telegram.org/mtproto
|
@ -13,6 +13,7 @@ Utilities
|
|||||||
|
|
||||||
start
|
start
|
||||||
stop
|
stop
|
||||||
|
restart
|
||||||
idle
|
idle
|
||||||
run
|
run
|
||||||
add_handler
|
add_handler
|
||||||
@ -20,6 +21,7 @@ Utilities
|
|||||||
send
|
send
|
||||||
resolve_peer
|
resolve_peer
|
||||||
save_file
|
save_file
|
||||||
|
stop_transmission
|
||||||
|
|
||||||
Decorators
|
Decorators
|
||||||
----------
|
----------
|
||||||
@ -62,6 +64,7 @@ Messages
|
|||||||
delete_messages
|
delete_messages
|
||||||
get_messages
|
get_messages
|
||||||
get_history
|
get_history
|
||||||
|
iter_history
|
||||||
send_poll
|
send_poll
|
||||||
vote_poll
|
vote_poll
|
||||||
retract_vote
|
retract_vote
|
||||||
@ -91,7 +94,9 @@ Chats
|
|||||||
get_chat_member
|
get_chat_member
|
||||||
get_chat_members
|
get_chat_members
|
||||||
get_chat_members_count
|
get_chat_members_count
|
||||||
|
iter_chat_members
|
||||||
get_dialogs
|
get_dialogs
|
||||||
|
iter_dialogs
|
||||||
|
|
||||||
Users
|
Users
|
||||||
-----
|
-----
|
||||||
@ -135,6 +140,9 @@ Bots
|
|||||||
send_inline_bot_result
|
send_inline_bot_result
|
||||||
answer_callback_query
|
answer_callback_query
|
||||||
request_callback_answer
|
request_callback_answer
|
||||||
|
send_game
|
||||||
|
set_game_score
|
||||||
|
get_game_high_scores
|
||||||
|
|
||||||
|
|
||||||
.. autoclass:: pyrogram.Client
|
.. autoclass:: pyrogram.Client
|
||||||
|
@ -57,6 +57,7 @@ Bots
|
|||||||
InlineKeyboardButton
|
InlineKeyboardButton
|
||||||
ForceReply
|
ForceReply
|
||||||
CallbackQuery
|
CallbackQuery
|
||||||
|
Game
|
||||||
|
|
||||||
Input Media
|
Input Media
|
||||||
-----------
|
-----------
|
||||||
@ -182,6 +183,15 @@ Input Media
|
|||||||
.. autoclass:: CallbackQuery
|
.. autoclass:: CallbackQuery
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: Game
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: GameHighScore
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: GameHighScores
|
||||||
|
:members:
|
||||||
|
|
||||||
.. Input Media
|
.. Input Media
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
|
90
docs/source/resources/ConfigurationFile.rst
Normal file
90
docs/source/resources/ConfigurationFile.rst
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
Configuration File
|
||||||
|
==================
|
||||||
|
|
||||||
|
As already mentioned in previous sections, Pyrogram can also be configured by the use of an INI file.
|
||||||
|
This page explains how this file is structured in Pyrogram, how to use it and why.
|
||||||
|
|
||||||
|
Introduction
|
||||||
|
------------
|
||||||
|
|
||||||
|
The idea behind using a configuration file is to help keeping your code free of settings (private) information such as
|
||||||
|
the API Key and Proxy without having you to even deal with how to load such settings. The configuration file, usually
|
||||||
|
referred as ``config.ini`` file, is automatically loaded from the root of your working directory; all you need to do is
|
||||||
|
fill in the necessary parts.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The configuration file is optional, but recommended. If, for any reason, you prefer not to use it, there's always an
|
||||||
|
alternative way to configure Pyrogram via Client's parameters. Doing so, you can have full control on how to store
|
||||||
|
and load your settings (e.g.: from environment variables).
|
||||||
|
|
||||||
|
Settings specified via Client's parameter have higher priority and will override any setting stored in the
|
||||||
|
configuration file.
|
||||||
|
|
||||||
|
|
||||||
|
The config.ini File
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
By default, Pyrogram will look for a file named ``config.ini`` placed at the root of your working directory, that is,
|
||||||
|
the same folder of your running script. You can change the name or location of your configuration file by specifying it
|
||||||
|
in your Client's parameter *config_file*.
|
||||||
|
|
||||||
|
- Replace the default *config.ini* file with *my_configuration.ini*:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import Client
|
||||||
|
|
||||||
|
app = Client("my_account", config_file="my_configuration.ini")
|
||||||
|
|
||||||
|
|
||||||
|
Configuration Sections
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
These are all the sections Pyrogram uses in its configuration file:
|
||||||
|
|
||||||
|
Pyrogram
|
||||||
|
^^^^^^^^
|
||||||
|
|
||||||
|
The ``[pyrogram]`` section contains your Telegram API credentials *api_id* and *api_hash*.
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
[pyrogram]
|
||||||
|
api_id = 12345
|
||||||
|
api_hash = 0123456789abcdef0123456789abcdef
|
||||||
|
|
||||||
|
`More info about API Key. <../start/Setup.html#configuration>`_
|
||||||
|
|
||||||
|
Proxy
|
||||||
|
^^^^^
|
||||||
|
|
||||||
|
The ``[proxy]`` section contains settings about your SOCKS5 proxy.
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
[proxy]
|
||||||
|
enabled = True
|
||||||
|
hostname = 11.22.33.44
|
||||||
|
port = 1080
|
||||||
|
username = <your_username>
|
||||||
|
password = <your_password>
|
||||||
|
|
||||||
|
`More info about SOCKS5 Proxy. <SOCKS5Proxy.html>`_
|
||||||
|
|
||||||
|
Plugins
|
||||||
|
^^^^^^^
|
||||||
|
|
||||||
|
The ``[plugins]`` section contains settings about Smart Plugins.
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
[plugins]
|
||||||
|
root = plugins
|
||||||
|
include =
|
||||||
|
module
|
||||||
|
folder.module
|
||||||
|
exclude =
|
||||||
|
module fn2
|
||||||
|
|
||||||
|
`More info about Smart Plugins. <SmartPlugins.html>`_
|
@ -91,13 +91,14 @@ Stop Propagation
|
|||||||
In order to prevent further propagation of an update in the dispatching phase, you can do *one* of the following:
|
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).
|
- Call the update's bound-method ``.stop_propagation()`` (preferred way).
|
||||||
- Manually ``raise StopPropagation`` error (more suitable for raw updates only).
|
- Manually ``raise StopPropagation`` exception (more suitable for raw updates only).
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
Note that ``.stop_propagation()`` is just an elegant and intuitive way to raise a ``StopPropagation`` error;
|
Internally, the propagation is stopped by handling a custom exception. ``.stop_propagation()`` is just an elegant
|
||||||
this means that any code coming *after* calling it won't be executed as your function just raised a custom exception
|
and intuitive way to ``raise StopPropagation``; this also means that any code coming *after* calling the method
|
||||||
to signal the dispatcher not to propagate the update anymore.
|
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()``:
|
Example with ``stop_propagation()``:
|
||||||
|
|
||||||
@ -139,10 +140,82 @@ Example with ``raise StopPropagation``:
|
|||||||
def _(client, message):
|
def _(client, message):
|
||||||
print(2)
|
print(2)
|
||||||
|
|
||||||
The handler in group number 2 will never be executed because the propagation was stopped before. The output of both
|
Each handler is registered in a different group, but the handler in group number 2 will never be executed because the
|
||||||
examples will be:
|
propagation was stopped earlier. The output of both (equivalent) examples will be:
|
||||||
|
|
||||||
.. code-block:: text
|
.. code-block:: text
|
||||||
|
|
||||||
0
|
0
|
||||||
1
|
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 group regardless of the next handler's filters. 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)
|
||||||
|
def _(client, message):
|
||||||
|
print(0)
|
||||||
|
message.continue_propagation()
|
||||||
|
|
||||||
|
|
||||||
|
@app.on_message(Filters.private)
|
||||||
|
def _(client, message):
|
||||||
|
print(1)
|
||||||
|
message.continue_propagation()
|
||||||
|
|
||||||
|
|
||||||
|
@app.on_message(Filters.private)
|
||||||
|
def _(client, message):
|
||||||
|
print(2)
|
||||||
|
|
||||||
|
Example with ``raise ContinuePropagation``:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import ContinuePropagation
|
||||||
|
|
||||||
|
@app.on_message(Filters.private)
|
||||||
|
def _(client, message):
|
||||||
|
print(0)
|
||||||
|
raise ContinuePropagation
|
||||||
|
|
||||||
|
|
||||||
|
@app.on_message(Filters.private)
|
||||||
|
def _(client, message):
|
||||||
|
print(1)
|
||||||
|
raise ContinuePropagation
|
||||||
|
|
||||||
|
|
||||||
|
@app.on_message(Filters.private)
|
||||||
|
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
|
@ -1,9 +1,9 @@
|
|||||||
Smart Plugins
|
Smart Plugins
|
||||||
=============
|
=============
|
||||||
|
|
||||||
Pyrogram embeds a **smart** (automatic) and lightweight plugin system that is meant to further simplify the organization
|
Pyrogram 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 components that can be **easily shared** across different
|
of large projects and to provide a way for creating pluggable (modular) components that can be **easily shared** across
|
||||||
Pyrogram applications with **minimal boilerplate code**.
|
different Pyrogram applications with **minimal boilerplate code**.
|
||||||
|
|
||||||
.. tip::
|
.. tip::
|
||||||
|
|
||||||
@ -13,7 +13,8 @@ Introduction
|
|||||||
------------
|
------------
|
||||||
|
|
||||||
Prior to the Smart Plugin system, pluggable handlers were already possible. For example, if you wanted to modularize
|
Prior to the Smart Plugin system, pluggable handlers were already possible. For example, if you wanted to modularize
|
||||||
your applications, you had to do something like this...
|
your applications, you had to put your function definitions in separate files and register them inside your main script,
|
||||||
|
like this:
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
@ -63,19 +64,19 @@ your applications, you had to do something like this...
|
|||||||
|
|
||||||
app.run()
|
app.run()
|
||||||
|
|
||||||
...which is already nice and doesn't add *too much* boilerplate code, but things can get boring still; you have to
|
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:`add_handler <pyrogram.Client.add_handler>` and manually instantiate each
|
manually ``import``, manually :meth:`add_handler <pyrogram.Client.add_handler>` and manually instantiate each
|
||||||
:obj:`MessageHandler <pyrogram.MessageHandler>` object because **you can't use those cool decorators** for your
|
:obj:`MessageHandler <pyrogram.MessageHandler>` object because **you can't use those cool decorators** for your
|
||||||
functions. So... What if you could?
|
functions. So, what if you could? Smart Plugins solve this issue by taking care of handlers registration automatically.
|
||||||
|
|
||||||
Using Smart Plugins
|
Using Smart Plugins
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
Setting up your Pyrogram project to accommodate Smart Plugins is pretty straightforward:
|
Setting up your Pyrogram project to accommodate Smart Plugins is straightforward:
|
||||||
|
|
||||||
#. Create a new folder to store all the plugins (e.g.: "plugins").
|
#. Create a new folder to store all the plugins (e.g.: "plugins", "handlers", ...).
|
||||||
#. Put your files full of plugins inside.
|
#. Put your python files full of plugins inside. Organize them as you wish.
|
||||||
#. Enable plugins in your Client.
|
#. Enable plugins in your Client or via the *config.ini* file.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
@ -107,20 +108,252 @@ Setting up your Pyrogram project to accommodate Smart Plugins is pretty straight
|
|||||||
def echo_reversed(client, message):
|
def echo_reversed(client, message):
|
||||||
message.reply(message.text[::-1])
|
message.reply(message.text[::-1])
|
||||||
|
|
||||||
|
- ``config.ini``
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
[plugins]
|
||||||
|
root = plugins
|
||||||
|
|
||||||
- ``main.py``
|
- ``main.py``
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
from pyrogram import Client
|
from pyrogram import Client
|
||||||
|
|
||||||
Client("my_account", plugins_dir="plugins").run()
|
Client("my_account").run()
|
||||||
|
|
||||||
The first important thing to note is the new ``plugins`` folder, whose name is passed to the the ``plugins_dir``
|
Alternatively, without using the *config.ini* file:
|
||||||
parameter when creating a :obj:`Client <pyrogram.Client>` in the ``main.py`` file — you can put *any python file* in
|
|
||||||
there and each file can contain *any decorated function* (handlers) with only one limitation: within a single plugin
|
.. code-block:: python
|
||||||
file you must use different names for each decorated function. Your Pyrogram Client instance will **automatically**
|
|
||||||
scan the folder upon creation to search for valid handlers and register them for you.
|
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 Pyrogram where to look for your plugins: you can either use the *config.ini* file or
|
||||||
|
the Client parameter "plugins"; the *root* value must match the name of your plugins folder. Your Pyrogram 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
|
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)
|
functions in a static way, i.e. **without having the Client instance around**: simply use ``@Client`` (Client class)
|
||||||
instead of the usual ``@app`` (Client instance) namespace and things will work just the same.
|
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. Learn more at `More on Updates <MoreOnUpdates.html>`_.
|
||||||
|
|
||||||
|
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``
|
||||||
|
keys, either in the *config.ini* file or 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):
|
||||||
|
|
||||||
|
Using *config.ini* file:
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
[plugins]
|
||||||
|
root = plugins
|
||||||
|
|
||||||
|
Using *Client*'s parameter:
|
||||||
|
|
||||||
|
.. 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:
|
||||||
|
|
||||||
|
Using *config.ini* file:
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
[plugins]
|
||||||
|
root = plugins
|
||||||
|
include =
|
||||||
|
subfolder2.plugins2
|
||||||
|
plugins0
|
||||||
|
|
||||||
|
Using *Client*'s parameter:
|
||||||
|
|
||||||
|
.. 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*:
|
||||||
|
|
||||||
|
Using *config.ini* file:
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
[plugins]
|
||||||
|
root = plugins
|
||||||
|
exclude = subfolder2.plugins2
|
||||||
|
|
||||||
|
Using *Client*'s parameter:
|
||||||
|
|
||||||
|
.. 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*:
|
||||||
|
|
||||||
|
Using *config.ini* file:
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
[plugins]
|
||||||
|
root = plugins
|
||||||
|
include = subfolder1.plugins1 fn3 fn1 fn2
|
||||||
|
|
||||||
|
Using *Client*'s parameter:
|
||||||
|
|
||||||
|
.. 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 <#specifying-the-plugins-to-include>`_ 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 plugins 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, when you reference them later on, they will be actually pointing to a tuple of
|
||||||
|
*(handler: Handler, group: int)*. The actual callback function is therefore stored inside the handler's *callback*
|
||||||
|
attribute. Here's an example:
|
||||||
|
|
||||||
|
- ``plugins/handlers.py``
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
:emphasize-lines: 5, 6
|
||||||
|
|
||||||
|
@Client.on_message(Filters.text & Filters.private)
|
||||||
|
def echo(client, message):
|
||||||
|
message.reply(message.text)
|
||||||
|
|
||||||
|
print(echo)
|
||||||
|
print(echo[0].callback)
|
||||||
|
|
||||||
|
- Printing ``echo`` will show something like ``(<MessageHandler object at 0x10e3abc50>, 0)``.
|
||||||
|
|
||||||
|
- Printing ``echo[0].callback``, that is, the *callback* attribute of the first eleent of the tuple, which is an
|
||||||
|
Handler, will reveal the actual callback ``<function echo at 0x10e3b6598>``.
|
||||||
|
|
||||||
|
Unloading
|
||||||
|
^^^^^^^^^
|
||||||
|
|
||||||
|
In order to unload a plugin, or any other handler, all you need to do is obtain a reference to it (by importing the
|
||||||
|
relevant module) and call :meth:`remove_handler <pyrogram.Client.remove_handler>` Client's method with your function
|
||||||
|
name preceded by the star ``*`` operator as argument. Example:
|
||||||
|
|
||||||
|
- ``main.py``
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from plugins.handlers import echo
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
app.remove_handler(*echo)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
handler, group = echo
|
||||||
|
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:`add_handler <pyrogram.Client.add_handler>` instead. Example:
|
||||||
|
|
||||||
|
- ``main.py``
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from plugins.handlers import echo
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
app.add_handler(*echo)
|
@ -1,5 +1,5 @@
|
|||||||
TgCrypto
|
Fast Crypto
|
||||||
========
|
===========
|
||||||
|
|
||||||
Pyrogram's speed can be *dramatically* boosted up by TgCrypto_, a high-performance, easy-to-install Telegram Crypto
|
Pyrogram's speed can be *dramatically* boosted up by TgCrypto_, a high-performance, easy-to-install Telegram Crypto
|
||||||
Library specifically written in C for Pyrogram [#f1]_ as a Python extension.
|
Library specifically written in C for Pyrogram [#f1]_ as a Python extension.
|
||||||
|
10
docs/source/resources/VoiceCalls.rst
Normal file
10
docs/source/resources/VoiceCalls.rst
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
Voice Calls
|
||||||
|
===========
|
||||||
|
|
||||||
|
A working proof-of-concept of Telegram voice calls using Pyrogram can be found here:
|
||||||
|
https://github.com/bakatrouble/pylibtgvoip. Thanks to `@bakatrouble <https://t.me/bakatrouble>`_.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
This page will be updated with more information once voice calls become eventually more usable and more integrated
|
||||||
|
in Pyrogram itself.
|
@ -2,21 +2,21 @@
|
|||||||
|
|
||||||
This folder contains example scripts to show you how **Pyrogram** looks like.
|
This folder contains example scripts to show you how **Pyrogram** looks like.
|
||||||
|
|
||||||
Every script is working right away (provided you correctly set up your credentials), meaning
|
Every script is working right away (provided you correctly set up your credentials), meaning you can simply copy-paste
|
||||||
you can simply copy-paste and run. The only things you have to change are session names and target chats.
|
and run. The only things you have to change are session names and target chats.
|
||||||
|
|
||||||
All the examples listed in this directory are licensed under the terms of the [CC0 1.0 Universal](LICENSE) license and
|
All the examples listed in this directory are licensed under the terms of the [CC0 1.0 Universal](LICENSE) license and
|
||||||
can be freely used as basic building blocks for your own applications without worrying about copyrights.
|
can be freely used as basic building blocks for your own applications without worrying about copyrights.
|
||||||
|
|
||||||
Example | Description
|
Example | Description
|
||||||
---: | :---
|
---: | :---
|
||||||
[**hello_world**](hello_world.py) | Demonstration of basic API usages
|
[**hello**](hello.py) | Demonstration of basic API usage
|
||||||
[**echo_bot**](echo_bot.py) | Echo bot that replies to every private text message
|
[**echo**](echo.py) | Reply to every private text message
|
||||||
[**welcome_bot**](welcome_bot.py) | The Welcome Bot source code in [@PyrogramChat](https://t.me/pyrogramchat)
|
[**welcome**](welcome.py) | The Welcome Bot in [@PyrogramChat](https://t.me/pyrogramchat)
|
||||||
[**get_history**](get_history.py) | How to retrieve the full message history of a chat
|
[**history**](history.py) | Get the full message history of a chat
|
||||||
[**get_chat_members**](get_chat_members.py) | How to get the first 10.000 members of a supergroup/channel
|
[**chat_members**](chat_members.py) | Get all the members of a chat
|
||||||
[**get_chat_members2**](get_chat_members2.py) | Improved version to get more than 10.000 members
|
[**dialogs**](dialogs.py) | Get all of your dialog chats
|
||||||
[**query_inline_bots**](query_inline_bots.py) | How to query an inline bot and send a result to a chat
|
[**inline_bots**](inline_bots.py) | Query an inline bot and send a result to a chat
|
||||||
[**send_bot_keyboards**](send_bot_keyboards.py) | How to send normal and inline keyboards using regular bots
|
[**keyboards**](keyboards.py) | Send normal and inline keyboards using regular bots
|
||||||
[**callback_query_handler**](callback_query_handler.py) | How to handle queries coming from inline button presses
|
[**callback_queries**](callback_queries.py) | Handle queries coming from inline button presses
|
||||||
[**raw_update_handler**](raw_update_handler.py) | How to handle raw updates (old, should be avoided)
|
[**raw_updates**](raw_updates.py) | Handle raw updates (old, should be avoided)
|
||||||
|
10
examples/chat_members.py
Normal file
10
examples/chat_members.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
"""This example shows how to get all the members of a chat."""
|
||||||
|
|
||||||
|
from pyrogram import Client
|
||||||
|
|
||||||
|
app = Client("my_count")
|
||||||
|
target = "pyrogramchat" # Target channel/supergroup
|
||||||
|
|
||||||
|
with app:
|
||||||
|
for member in app.iter_chat_members(target):
|
||||||
|
print(member.user.first_name)
|
9
examples/dialogs.py
Normal file
9
examples/dialogs.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
"""This example shows how to get the full dialogs list of a user."""
|
||||||
|
|
||||||
|
from pyrogram import Client
|
||||||
|
|
||||||
|
app = Client("my_account")
|
||||||
|
|
||||||
|
with app:
|
||||||
|
for dialog in app.iter_dialogs():
|
||||||
|
print(dialog.chat.title or dialog.chat.first_name)
|
@ -11,7 +11,7 @@ app = Client("my_account")
|
|||||||
|
|
||||||
@app.on_message(Filters.text & Filters.private)
|
@app.on_message(Filters.text & Filters.private)
|
||||||
def echo(client, message):
|
def echo(client, message):
|
||||||
message.reply(message.text, quote=True)
|
message.reply(message.text)
|
||||||
|
|
||||||
|
|
||||||
app.run() # Automatically start() and idle()
|
app.run() # Automatically start() and idle()
|
@ -1,31 +0,0 @@
|
|||||||
"""This example shows you how to get the first 10.000 members of a chat.
|
|
||||||
Refer to get_chat_members2.py for more than 10.000 members.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import time
|
|
||||||
|
|
||||||
from pyrogram import Client
|
|
||||||
from pyrogram.api.errors import FloodWait
|
|
||||||
|
|
||||||
app = Client("my_account")
|
|
||||||
|
|
||||||
target = "pyrogramchat" # Target channel/supergroup
|
|
||||||
members = [] # List that will contain all the members of the target chat
|
|
||||||
offset = 0 # Offset starts at 0
|
|
||||||
limit = 200 # Amount of users to retrieve for each API call (max 200)
|
|
||||||
|
|
||||||
with app:
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
chunk = app.get_chat_members(target, offset)
|
|
||||||
except FloodWait as e: # Very large chats could trigger FloodWait
|
|
||||||
time.sleep(e.x) # When it happens, wait X seconds and try again
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not chunk.chat_members:
|
|
||||||
break # No more members left
|
|
||||||
|
|
||||||
members.extend(chunk.chat_members)
|
|
||||||
offset += len(chunk.chat_members)
|
|
||||||
|
|
||||||
# Now the "members" list contains all the members of the target chat
|
|
@ -1,50 +0,0 @@
|
|||||||
"""This is an improved version of get_chat_members.py
|
|
||||||
|
|
||||||
Since Telegram will return at most 10.000 members for a single query, this script
|
|
||||||
repeats the search using numbers ("0" to "9") and all the available ascii letters ("a" to "z").
|
|
||||||
|
|
||||||
This can be further improved by also searching for non-ascii characters (e.g.: Japanese script),
|
|
||||||
as some user names may not contain ascii letters at all.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import time
|
|
||||||
from string import ascii_lowercase
|
|
||||||
|
|
||||||
from pyrogram import Client
|
|
||||||
from pyrogram.api.errors import FloodWait
|
|
||||||
|
|
||||||
app = Client("my_account")
|
|
||||||
|
|
||||||
target = "pyrogramchat" # Target channel/supergroup
|
|
||||||
members = {} # List that will contain all the members of the target chat
|
|
||||||
limit = 200 # Amount of users to retrieve for each API call (max 200)
|
|
||||||
|
|
||||||
# "" + "0123456789" + "abcdefghijklmnopqrstuvwxyz" (as list)
|
|
||||||
queries = [""] + [str(i) for i in range(10)] + list(ascii_lowercase)
|
|
||||||
|
|
||||||
with app:
|
|
||||||
for q in queries:
|
|
||||||
print('Searching for "{}"'.format(q))
|
|
||||||
offset = 0 # For each query, offset restarts from 0
|
|
||||||
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
chunk = app.get_chat_members(target, offset, query=q)
|
|
||||||
except FloodWait as e: # Very large chats could trigger FloodWait
|
|
||||||
print("Flood wait: {} seconds".format(e.x))
|
|
||||||
time.sleep(e.x) # When it happens, wait X seconds and try again
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not chunk.chat_members:
|
|
||||||
print('Done searching for "{}"'.format(q))
|
|
||||||
print()
|
|
||||||
break # No more members left
|
|
||||||
|
|
||||||
members.update({i.user.id: i for i in chunk.chat_members})
|
|
||||||
offset += len(chunk.chat_members)
|
|
||||||
|
|
||||||
print("Total members: {}".format(len(members)))
|
|
||||||
|
|
||||||
print("Grand total: {}".format(len(members)))
|
|
||||||
|
|
||||||
# Now the "members" list contains all the members of the target chat
|
|
@ -1,31 +0,0 @@
|
|||||||
"""This example shows how to retrieve the full message history of a chat"""
|
|
||||||
|
|
||||||
import time
|
|
||||||
|
|
||||||
from pyrogram import Client
|
|
||||||
from pyrogram.api.errors import FloodWait
|
|
||||||
|
|
||||||
app = Client("my_account")
|
|
||||||
target = "me" # "me" refers to your own chat (Saved Messages)
|
|
||||||
messages = [] # List that will contain all the messages of the target chat
|
|
||||||
offset_id = 0 # ID of the last message of the chunk
|
|
||||||
|
|
||||||
with app:
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
m = app.get_history(target, offset_id=offset_id)
|
|
||||||
except FloodWait as e: # For very large chats the method call can raise a FloodWait
|
|
||||||
print("waiting {}".format(e.x))
|
|
||||||
time.sleep(e.x) # Sleep X seconds before continuing
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not m.messages:
|
|
||||||
break
|
|
||||||
|
|
||||||
messages += m.messages
|
|
||||||
offset_id = m.messages[-1].message_id
|
|
||||||
|
|
||||||
print("Messages: {}".format(len(messages)))
|
|
||||||
|
|
||||||
# Now the "messages" list contains all the messages sorted by date in
|
|
||||||
# descending order (from the most recent to the oldest one)
|
|
16
examples/hello.py
Normal file
16
examples/hello.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
"""This example demonstrates a basic API usage"""
|
||||||
|
|
||||||
|
from pyrogram import Client
|
||||||
|
|
||||||
|
# Create a new Client instance
|
||||||
|
app = Client("my_account")
|
||||||
|
|
||||||
|
with app:
|
||||||
|
# Send a message, Markdown is enabled by default
|
||||||
|
app.send_message("me", "Hi there! I'm using **Pyrogram**")
|
||||||
|
|
||||||
|
# Send a location
|
||||||
|
app.send_location("me", 51.500729, -0.124583)
|
||||||
|
|
||||||
|
# Send a sticker
|
||||||
|
app.send_sticker("me", "CAADBAADhw4AAvLQYAHICbZ5SUs_jwI")
|
@ -1,18 +0,0 @@
|
|||||||
"""This example demonstrates a basic API usage"""
|
|
||||||
|
|
||||||
from pyrogram import Client
|
|
||||||
|
|
||||||
# Create a new Client instance
|
|
||||||
app = Client("my_account")
|
|
||||||
|
|
||||||
# Start the Client before calling any API method
|
|
||||||
app.start()
|
|
||||||
|
|
||||||
# Send a message to yourself, Markdown is enabled by default
|
|
||||||
app.send_message("me", "Hi there! I'm using **Pyrogram**")
|
|
||||||
|
|
||||||
# Send a location to yourself
|
|
||||||
app.send_location("me", 51.500729, -0.124583)
|
|
||||||
|
|
||||||
# Stop the client when you're done
|
|
||||||
app.stop()
|
|
10
examples/history.py
Normal file
10
examples/history.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
"""This example shows how to get the full message history of a chat, starting from the latest message"""
|
||||||
|
|
||||||
|
from pyrogram import Client
|
||||||
|
|
||||||
|
app = Client("my_account")
|
||||||
|
target = "me" # "me" refers to your own chat (Saved Messages)
|
||||||
|
|
||||||
|
with app:
|
||||||
|
for message in app.iter_history(target):
|
||||||
|
print(message.text)
|
59
examples/keyboards.py
Normal file
59
examples/keyboards.py
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
"""This example will show you how to send normal and inline keyboards.
|
||||||
|
|
||||||
|
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...
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pyrogram import Client, ReplyKeyboardMarkup, InlineKeyboardMarkup, InlineKeyboardButton
|
||||||
|
|
||||||
|
# Create a client using your bot token
|
||||||
|
app = Client("123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11")
|
||||||
|
|
||||||
|
with app:
|
||||||
|
app.send_message(
|
||||||
|
"haskell", # 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
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
app.send_message(
|
||||||
|
"haskell", # Edit this
|
||||||
|
"This is a InlineKeyboardMarkup example",
|
||||||
|
reply_markup=InlineKeyboardMarkup(
|
||||||
|
[
|
||||||
|
[ # First row
|
||||||
|
|
||||||
|
InlineKeyboardButton( # Generates a callback query when pressed
|
||||||
|
"Button",
|
||||||
|
callback_data=b"data"
|
||||||
|
), # Note how callback_data must be bytes
|
||||||
|
InlineKeyboardButton( # Opens a web URL
|
||||||
|
"URL",
|
||||||
|
url="https://docs.pyrogram.ml"
|
||||||
|
),
|
||||||
|
],
|
||||||
|
[ # Second row
|
||||||
|
# Opens the inline interface
|
||||||
|
InlineKeyboardButton(
|
||||||
|
"Choose chat",
|
||||||
|
switch_inline_query="pyrogram"
|
||||||
|
),
|
||||||
|
InlineKeyboardButton( # Opens the inline interface in the current chat
|
||||||
|
"Inline here",
|
||||||
|
switch_inline_query_current_chat="pyrogram"
|
||||||
|
)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
@ -1,51 +0,0 @@
|
|||||||
"""This example will show you how to send normal and inline keyboards.
|
|
||||||
|
|
||||||
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...
|
|
||||||
"""
|
|
||||||
|
|
||||||
from pyrogram import Client, ReplyKeyboardMarkup, InlineKeyboardMarkup, InlineKeyboardButton
|
|
||||||
|
|
||||||
# Create a client using your bot token
|
|
||||||
app = Client("123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11")
|
|
||||||
app.start()
|
|
||||||
|
|
||||||
app.send_message(
|
|
||||||
"haskell", # 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
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
app.send_message(
|
|
||||||
"haskell", # Edit this
|
|
||||||
"This is a InlineKeyboardMarkup example",
|
|
||||||
reply_markup=InlineKeyboardMarkup(
|
|
||||||
[
|
|
||||||
[ # First row
|
|
||||||
# Generates a callback query when pressed
|
|
||||||
InlineKeyboardButton("Button", callback_data="data"),
|
|
||||||
# Opens a web URL
|
|
||||||
InlineKeyboardButton("URL", url="https://docs.pyrogram.ml"),
|
|
||||||
],
|
|
||||||
[ # Second row
|
|
||||||
# Opens the inline interface of a bot in another chat with a pre-defined query
|
|
||||||
InlineKeyboardButton("Choose chat", switch_inline_query="pyrogram"),
|
|
||||||
# Same as the button above, but the inline interface is opened in the current chat
|
|
||||||
InlineKeyboardButton("Inline here", switch_inline_query_current_chat="pyrogram"),
|
|
||||||
]
|
|
||||||
]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
app.stop()
|
|
29
examples/welcome.py
Normal file
29
examples/welcome.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
"""This is the Welcome Bot in @PyrogramChat.
|
||||||
|
|
||||||
|
It uses the Emoji module to easily add emojis in your text messages and Filters
|
||||||
|
to make it only work for specific messages in a specific chat.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pyrogram import Client, Emoji, Filters
|
||||||
|
|
||||||
|
TARGET = "PyrogramChat" # Target chat. Can also be a list of multiple chat ids/usernames
|
||||||
|
MENTION = "[{}](tg://user?id={})" # User mention markup
|
||||||
|
MESSAGE = "{} Welcome to [Pyrogram](https://docs.pyrogram.ml/)'s group chat {}!" # Welcome message
|
||||||
|
|
||||||
|
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)
|
||||||
|
def welcome(client, message):
|
||||||
|
# Build the new members list (with mentions) by using their first_name
|
||||||
|
new_members = [MENTION.format(i.first_name, i.id) for i 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
|
||||||
|
message.reply(text, disable_web_page_preview=True)
|
||||||
|
|
||||||
|
|
||||||
|
app.run() # Automatically start() and idle()
|
@ -1,45 +0,0 @@
|
|||||||
"""This is the Welcome Bot in @PyrogramChat.
|
|
||||||
|
|
||||||
It uses the Emoji module to easily add emojis in your text messages and Filters
|
|
||||||
to make it only work for specific messages in a specific chat.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from pyrogram import Client, Emoji, Filters
|
|
||||||
|
|
||||||
USER = "**{}**"
|
|
||||||
MESSAGE = "{} Welcome to [Pyrogram](https://docs.pyrogram.ml/)'s group chat {{}}!".format(Emoji.SPARKLES)
|
|
||||||
|
|
||||||
enabled_groups = Filters.chat("PyrogramChat")
|
|
||||||
last_welcomes = {}
|
|
||||||
|
|
||||||
app = Client("my_account")
|
|
||||||
|
|
||||||
|
|
||||||
@app.on_message(enabled_groups & Filters.new_chat_members)
|
|
||||||
def welcome(client, message):
|
|
||||||
chat_id = message.chat.id
|
|
||||||
|
|
||||||
# Get the previous welcome message and members, if any
|
|
||||||
previous_welcome, previous_members = last_welcomes.pop(chat_id, (None, []))
|
|
||||||
|
|
||||||
# Delete the previous message, if exists
|
|
||||||
if previous_welcome:
|
|
||||||
previous_welcome.delete()
|
|
||||||
|
|
||||||
# Build the new members list by using their first_name. Also append the previous members, if any
|
|
||||||
new_members = [USER.format(i.first_name) for i in message.new_chat_members] + previous_members
|
|
||||||
|
|
||||||
# Build the welcome message by using an emoji and the list we created above
|
|
||||||
text = MESSAGE.format(", ".join(new_members))
|
|
||||||
|
|
||||||
# Actually send the welcome and save the new message and the new members list
|
|
||||||
last_welcomes[message.chat.id] = message.reply(text, disable_web_page_preview=True), new_members
|
|
||||||
|
|
||||||
|
|
||||||
@app.on_message(enabled_groups)
|
|
||||||
def reset(client, message):
|
|
||||||
# Don't make the bot delete the previous welcome in case someone talks in the middle
|
|
||||||
last_welcomes.pop(message.chat.id, None)
|
|
||||||
|
|
||||||
|
|
||||||
app.run() # Automatically start() and idle()
|
|
@ -18,12 +18,12 @@
|
|||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
__copyright__ = "Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>".replace(
|
__copyright__ = "Copyright (C) 2017-2019 Dan Tès <https://github.com/delivrance>".replace(
|
||||||
"\xe8",
|
"\xe8",
|
||||||
"e" if sys.getfilesystemencoding() != "utf-8" else "\xe8"
|
"e" if sys.getfilesystemencoding() != "utf-8" else "\xe8"
|
||||||
)
|
)
|
||||||
__license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
|
__license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
|
||||||
__version__ = "0.10.3"
|
__version__ = "0.11.0"
|
||||||
|
|
||||||
from .api.errors import Error
|
from .api.errors import Error
|
||||||
from .client.types import (
|
from .client.types import (
|
||||||
@ -32,7 +32,8 @@ from .client.types import (
|
|||||||
Location, Message, MessageEntity, Dialog, Dialogs, Photo, PhotoSize, Sticker, User, UserStatus,
|
Location, Message, MessageEntity, Dialog, Dialogs, Photo, PhotoSize, Sticker, User, UserStatus,
|
||||||
UserProfilePhotos, Venue, Animation, Video, VideoNote, Voice, CallbackQuery, Messages, ForceReply,
|
UserProfilePhotos, Venue, Animation, Video, VideoNote, Voice, CallbackQuery, Messages, ForceReply,
|
||||||
InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove,
|
InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove,
|
||||||
Poll, PollOption, ChatPreview, StopPropagation
|
Poll, PollOption, ChatPreview, StopPropagation, ContinuePropagation, Game, CallbackGame, GameHighScore,
|
||||||
|
GameHighScores
|
||||||
)
|
)
|
||||||
from .client import (
|
from .client import (
|
||||||
Client, ChatAction, ParseMode, Emoji,
|
Client, ChatAction, ParseMode, Emoji,
|
||||||
|
@ -111,26 +111,37 @@ class Client(Methods, BaseClient):
|
|||||||
Only applicable for new sessions and will be ignored in case previously
|
Only applicable for new sessions and will be ignored in case previously
|
||||||
created sessions are loaded.
|
created sessions are loaded.
|
||||||
|
|
||||||
phone_number (``str``, *optional*):
|
phone_number (``str`` | ``callable``, *optional*):
|
||||||
Pass your phone number (with your Country Code prefix included) to avoid
|
Pass your phone number as string (with your Country Code prefix included) to avoid entering it manually.
|
||||||
entering it manually. Only applicable for new sessions.
|
Or pass a callback function which accepts no arguments and must return the correct phone number as string
|
||||||
|
(e.g., "391234567890").
|
||||||
|
Only applicable for new sessions.
|
||||||
|
|
||||||
phone_code (``str`` | ``callable``, *optional*):
|
phone_code (``str`` | ``callable``, *optional*):
|
||||||
Pass the phone code as string (for test numbers only), or pass a callback function which accepts
|
Pass the phone code as string (for test numbers only) to avoid entering it manually. Or pass a callback
|
||||||
a single positional argument *(phone_number)* and must return the correct phone code (e.g., "12345").
|
function which accepts a single positional argument *(phone_number)* and must return the correct phone code
|
||||||
|
as string (e.g., "12345").
|
||||||
Only applicable for new sessions.
|
Only applicable for new sessions.
|
||||||
|
|
||||||
password (``str``, *optional*):
|
password (``str``, *optional*):
|
||||||
Pass your Two-Step Verification password (if you have one) to avoid entering it
|
Pass your Two-Step Verification password as string (if you have one) to avoid entering it manually.
|
||||||
manually. Only applicable for new sessions.
|
Or pass a callback function which accepts a single positional argument *(password_hint)* and must return
|
||||||
|
the correct password as string (e.g., "password").
|
||||||
|
Only applicable for new sessions.
|
||||||
|
|
||||||
|
recovery_code (``callable``, *optional*):
|
||||||
|
Pass a callback function which accepts a single positional argument *(email_pattern)* and must return the
|
||||||
|
correct password recovery code as string (e.g., "987654").
|
||||||
|
Only applicable for new sessions.
|
||||||
|
|
||||||
force_sms (``str``, *optional*):
|
force_sms (``str``, *optional*):
|
||||||
Pass True to force Telegram sending the authorization code via SMS.
|
Pass True to force Telegram sending the authorization code via SMS.
|
||||||
Only applicable for new sessions.
|
Only applicable for new sessions.
|
||||||
|
|
||||||
first_name (``str``, *optional*):
|
first_name (``str``, *optional*):
|
||||||
Pass a First Name to avoid entering it manually. It will be used to automatically
|
Pass a First Name as string to avoid entering it manually. Or pass a callback function which accepts no
|
||||||
create a new Telegram account in case the phone number you passed is not registered yet.
|
arguments and must return the correct name as string (e.g., "Dan"). It will be used to automatically create
|
||||||
|
a new Telegram account in case the phone number you passed is not registered yet.
|
||||||
Only applicable for new sessions.
|
Only applicable for new sessions.
|
||||||
|
|
||||||
last_name (``str``, *optional*):
|
last_name (``str``, *optional*):
|
||||||
@ -147,10 +158,22 @@ class Client(Methods, BaseClient):
|
|||||||
config_file (``str``, *optional*):
|
config_file (``str``, *optional*):
|
||||||
Path of the configuration file. Defaults to ./config.ini
|
Path of the configuration file. Defaults to ./config.ini
|
||||||
|
|
||||||
plugins_dir (``str``, *optional*):
|
plugins (``dict``, *optional*):
|
||||||
Define a custom directory for your plugins. The plugins directory is the location in your
|
Your Smart Plugins settings as dict, e.g.: *dict(root="plugins")*.
|
||||||
filesystem where Pyrogram will automatically load your update handlers.
|
This is an alternative way to setup plugins if you don't want to use the *config.ini* file.
|
||||||
Defaults to None (plugins disabled).
|
|
||||||
|
no_updates (``bool``, *optional*):
|
||||||
|
Pass True to completely disable incoming updates for the current session.
|
||||||
|
When updates are disabled your client can't receive any new message.
|
||||||
|
Useful for batch programs that don't need to deal with updates.
|
||||||
|
Defaults to False (updates enabled and always received).
|
||||||
|
|
||||||
|
takeout (``bool``, *optional*):
|
||||||
|
Pass True to let the client use a takeout session instead of a normal one, implies no_updates.
|
||||||
|
Useful for exporting your Telegram data. Methods invoked inside a takeout session (such as get_history,
|
||||||
|
download_media, ...) are less prone to throw FloodWait exceptions.
|
||||||
|
Only available for users, bots will ignore this parameter.
|
||||||
|
Defaults to False (normal session).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
@ -167,13 +190,16 @@ class Client(Methods, BaseClient):
|
|||||||
phone_number: str = None,
|
phone_number: str = None,
|
||||||
phone_code: Union[str, callable] = None,
|
phone_code: Union[str, callable] = None,
|
||||||
password: str = None,
|
password: str = None,
|
||||||
|
recovery_code: callable = None,
|
||||||
force_sms: bool = False,
|
force_sms: bool = False,
|
||||||
first_name: str = None,
|
first_name: str = None,
|
||||||
last_name: str = None,
|
last_name: str = None,
|
||||||
workers: int = BaseClient.WORKERS,
|
workers: int = BaseClient.WORKERS,
|
||||||
workdir: str = BaseClient.WORKDIR,
|
workdir: str = BaseClient.WORKDIR,
|
||||||
config_file: str = BaseClient.CONFIG_FILE,
|
config_file: str = BaseClient.CONFIG_FILE,
|
||||||
plugins_dir: str = None):
|
plugins: dict = None,
|
||||||
|
no_updates: bool = None,
|
||||||
|
takeout: bool = None):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
self.session_name = session_name
|
self.session_name = session_name
|
||||||
@ -190,13 +216,16 @@ class Client(Methods, BaseClient):
|
|||||||
self.phone_number = phone_number
|
self.phone_number = phone_number
|
||||||
self.phone_code = phone_code
|
self.phone_code = phone_code
|
||||||
self.password = password
|
self.password = password
|
||||||
|
self.recovery_code = recovery_code
|
||||||
self.force_sms = force_sms
|
self.force_sms = force_sms
|
||||||
self.first_name = first_name
|
self.first_name = first_name
|
||||||
self.last_name = last_name
|
self.last_name = last_name
|
||||||
self.workers = workers
|
self.workers = workers
|
||||||
self.workdir = workdir
|
self.workdir = workdir
|
||||||
self.config_file = config_file
|
self.config_file = config_file
|
||||||
self.plugins_dir = plugins_dir
|
self.plugins = plugins
|
||||||
|
self.no_updates = no_updates
|
||||||
|
self.takeout = takeout
|
||||||
|
|
||||||
self.dispatcher = Dispatcher(self, workers)
|
self.dispatcher = Dispatcher(self, workers)
|
||||||
|
|
||||||
@ -212,7 +241,14 @@ class Client(Methods, BaseClient):
|
|||||||
|
|
||||||
@proxy.setter
|
@proxy.setter
|
||||||
def proxy(self, value):
|
def proxy(self, value):
|
||||||
self._proxy["enabled"] = True
|
if value is None:
|
||||||
|
self._proxy = None
|
||||||
|
return
|
||||||
|
|
||||||
|
if self._proxy is None:
|
||||||
|
self._proxy = {}
|
||||||
|
|
||||||
|
self._proxy["enabled"] = bool(value.get("enabled", True))
|
||||||
self._proxy.update(value)
|
self._proxy.update(value)
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
@ -253,6 +289,10 @@ class Client(Methods, BaseClient):
|
|||||||
self.save_session()
|
self.save_session()
|
||||||
|
|
||||||
if self.bot_token is None:
|
if self.bot_token is None:
|
||||||
|
if self.takeout:
|
||||||
|
self.takeout_id = self.send(functions.account.InitTakeoutSession()).id
|
||||||
|
log.warning("Takeout session {} initiated".format(self.takeout_id))
|
||||||
|
|
||||||
now = time.time()
|
now = time.time()
|
||||||
|
|
||||||
if abs(now - self.date) > Client.OFFLINE_SLEEP:
|
if abs(now - self.date) > Client.OFFLINE_SLEEP:
|
||||||
@ -308,6 +348,10 @@ class Client(Methods, BaseClient):
|
|||||||
if not self.is_started:
|
if not self.is_started:
|
||||||
raise ConnectionError("Client is already stopped")
|
raise ConnectionError("Client is already stopped")
|
||||||
|
|
||||||
|
if self.takeout_id:
|
||||||
|
self.send(functions.account.FinishTakeoutSession())
|
||||||
|
log.warning("Takeout session {} finished".format(self.takeout_id))
|
||||||
|
|
||||||
Syncer.remove(self)
|
Syncer.remove(self)
|
||||||
self.dispatcher.stop()
|
self.dispatcher.stop()
|
||||||
|
|
||||||
@ -337,6 +381,16 @@ class Client(Methods, BaseClient):
|
|||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
def restart(self):
|
||||||
|
"""Use this method to restart the Client.
|
||||||
|
Requires no parameters.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
``ConnectionError`` in case you try to restart a stopped Client.
|
||||||
|
"""
|
||||||
|
self.stop()
|
||||||
|
self.start()
|
||||||
|
|
||||||
def idle(self, stop_signals: tuple = (SIGINT, SIGTERM, SIGABRT)):
|
def idle(self, stop_signals: tuple = (SIGINT, SIGTERM, SIGABRT)):
|
||||||
"""Blocks the program execution until one of the signals are received,
|
"""Blocks the program execution until one of the signals are received,
|
||||||
then gently stop the Client by closing the underlying connection.
|
then gently stop the Client by closing the underlying connection.
|
||||||
@ -413,6 +467,12 @@ class Client(Methods, BaseClient):
|
|||||||
else:
|
else:
|
||||||
self.dispatcher.remove_handler(handler, group)
|
self.dispatcher.remove_handler(handler, group)
|
||||||
|
|
||||||
|
def stop_transmission(self):
|
||||||
|
"""Use this method to stop downloading or uploading a file.
|
||||||
|
Must be called inside a progress callback function.
|
||||||
|
"""
|
||||||
|
raise Client.StopTransmission
|
||||||
|
|
||||||
def authorize_bot(self):
|
def authorize_bot(self):
|
||||||
try:
|
try:
|
||||||
r = self.send(
|
r = self.send(
|
||||||
@ -445,20 +505,25 @@ class Client(Methods, BaseClient):
|
|||||||
def authorize_user(self):
|
def authorize_user(self):
|
||||||
phone_number_invalid_raises = self.phone_number is not None
|
phone_number_invalid_raises = self.phone_number is not None
|
||||||
phone_code_invalid_raises = self.phone_code is not None
|
phone_code_invalid_raises = self.phone_code is not None
|
||||||
password_hash_invalid_raises = self.password is not None
|
password_invalid_raises = self.password is not None
|
||||||
first_name_invalid_raises = self.first_name is not None
|
first_name_invalid_raises = self.first_name is not None
|
||||||
|
|
||||||
|
def default_phone_number_callback():
|
||||||
while True:
|
while True:
|
||||||
if self.phone_number is None:
|
phone_number = input("Enter phone number: ")
|
||||||
self.phone_number = input("Enter phone number: ")
|
confirm = input("Is \"{}\" correct? (y/n): ".format(phone_number))
|
||||||
|
|
||||||
while True:
|
|
||||||
confirm = input("Is \"{}\" correct? (y/n): ".format(self.phone_number))
|
|
||||||
|
|
||||||
if confirm in ("y", "1"):
|
if confirm in ("y", "1"):
|
||||||
break
|
return phone_number
|
||||||
elif confirm in ("n", "2"):
|
elif confirm in ("n", "2"):
|
||||||
self.phone_number = input("Enter phone number: ")
|
continue
|
||||||
|
|
||||||
|
while True:
|
||||||
|
self.phone_number = (
|
||||||
|
default_phone_number_callback() if self.phone_number is None
|
||||||
|
else str(self.phone_number()) if callable(self.phone_number)
|
||||||
|
else str(self.phone_number)
|
||||||
|
)
|
||||||
|
|
||||||
self.phone_number = self.phone_number.strip("+")
|
self.phone_number = self.phone_number.strip("+")
|
||||||
|
|
||||||
@ -474,23 +539,21 @@ class Client(Methods, BaseClient):
|
|||||||
self.session.stop()
|
self.session.stop()
|
||||||
|
|
||||||
self.dc_id = e.x
|
self.dc_id = e.x
|
||||||
self.auth_key = Auth(self.dc_id, self.test_mode, self.ipv6, self._proxy).create()
|
|
||||||
|
self.auth_key = Auth(
|
||||||
|
self.dc_id,
|
||||||
|
self.test_mode,
|
||||||
|
self.ipv6,
|
||||||
|
self._proxy
|
||||||
|
).create()
|
||||||
|
|
||||||
self.session = Session(
|
self.session = Session(
|
||||||
self,
|
self,
|
||||||
self.dc_id,
|
self.dc_id,
|
||||||
self.auth_key
|
self.auth_key
|
||||||
)
|
)
|
||||||
self.session.start()
|
|
||||||
|
|
||||||
r = self.send(
|
self.session.start()
|
||||||
functions.auth.SendCode(
|
|
||||||
self.phone_number,
|
|
||||||
self.api_id,
|
|
||||||
self.api_hash
|
|
||||||
)
|
|
||||||
)
|
|
||||||
break
|
|
||||||
except (PhoneNumberInvalid, PhoneNumberBanned) as e:
|
except (PhoneNumberInvalid, PhoneNumberBanned) as e:
|
||||||
if phone_number_invalid_raises:
|
if phone_number_invalid_raises:
|
||||||
raise
|
raise
|
||||||
@ -505,6 +568,7 @@ class Client(Methods, BaseClient):
|
|||||||
time.sleep(e.x)
|
time.sleep(e.x)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error(e, exc_info=True)
|
log.error(e, exc_info=True)
|
||||||
|
raise
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
|
|
||||||
@ -524,10 +588,23 @@ class Client(Methods, BaseClient):
|
|||||||
)
|
)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
|
if not phone_registered:
|
||||||
|
self.first_name = (
|
||||||
|
input("First name: ") if self.first_name is None
|
||||||
|
else str(self.first_name()) if callable(self.first_name)
|
||||||
|
else str(self.first_name)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.last_name = (
|
||||||
|
input("Last name: ") if self.last_name is None
|
||||||
|
else str(self.last_name()) if callable(self.last_name)
|
||||||
|
else str(self.last_name)
|
||||||
|
)
|
||||||
|
|
||||||
self.phone_code = (
|
self.phone_code = (
|
||||||
input("Enter phone code: ") if self.phone_code is None
|
input("Enter phone code: ") if self.phone_code is None
|
||||||
else self.phone_code if type(self.phone_code) is str
|
else str(self.phone_code(self.phone_number)) if callable(self.phone_code)
|
||||||
else str(self.phone_code(self.phone_number))
|
else str(self.phone_code)
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -545,9 +622,6 @@ class Client(Methods, BaseClient):
|
|||||||
phone_registered = False
|
phone_registered = False
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
self.first_name = self.first_name if self.first_name is not None else input("First name: ")
|
|
||||||
self.last_name = self.last_name if self.last_name is not None else input("Last name: ")
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
r = self.send(
|
r = self.send(
|
||||||
functions.auth.SignUp(
|
functions.auth.SignUp(
|
||||||
@ -577,25 +651,36 @@ class Client(Methods, BaseClient):
|
|||||||
except SessionPasswordNeeded as e:
|
except SessionPasswordNeeded as e:
|
||||||
print(e.MESSAGE)
|
print(e.MESSAGE)
|
||||||
|
|
||||||
|
def default_password_callback(password_hint: str) -> str:
|
||||||
|
print("Hint: {}".format(password_hint))
|
||||||
|
return input("Enter password (empty to recover): ")
|
||||||
|
|
||||||
|
def default_recovery_callback(email_pattern: str) -> str:
|
||||||
|
print("An e-mail containing the recovery code has been sent to {}".format(email_pattern))
|
||||||
|
return input("Enter password recovery code: ")
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
r = self.send(functions.account.GetPassword())
|
r = self.send(functions.account.GetPassword())
|
||||||
|
|
||||||
if self.password is None:
|
self.password = (
|
||||||
print("Hint: {}".format(r.hint))
|
default_password_callback(r.hint) if self.password is None
|
||||||
|
else str(self.password(r.hint) or "") if callable(self.password)
|
||||||
self.password = input("Enter password (empty to recover): ")
|
else str(self.password)
|
||||||
|
)
|
||||||
|
|
||||||
if self.password == "":
|
if self.password == "":
|
||||||
r = self.send(functions.auth.RequestPasswordRecovery())
|
r = self.send(functions.auth.RequestPasswordRecovery())
|
||||||
|
|
||||||
print("An e-mail containing the recovery code has been sent to {}".format(
|
self.recovery_code = (
|
||||||
r.email_pattern
|
default_recovery_callback(r.email_pattern) if self.recovery_code is None
|
||||||
))
|
else str(self.recovery_code(r.email_pattern)) if callable(self.recovery_code)
|
||||||
|
else str(self.recovery_code)
|
||||||
|
)
|
||||||
|
|
||||||
r = self.send(
|
r = self.send(
|
||||||
functions.auth.RecoverPassword(
|
functions.auth.RecoverPassword(
|
||||||
code=input("Enter password recovery code: ")
|
code=self.recovery_code
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@ -604,33 +689,24 @@ class Client(Methods, BaseClient):
|
|||||||
password=compute_check(r, self.password)
|
password=compute_check(r, self.password)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
except PasswordEmpty as e:
|
except (PasswordEmpty, PasswordRecoveryNa, PasswordHashInvalid) as e:
|
||||||
if password_hash_invalid_raises:
|
if password_invalid_raises:
|
||||||
raise
|
|
||||||
else:
|
|
||||||
print(e.MESSAGE)
|
|
||||||
self.password = None
|
|
||||||
except PasswordRecoveryNa as e:
|
|
||||||
if password_hash_invalid_raises:
|
|
||||||
raise
|
|
||||||
else:
|
|
||||||
print(e.MESSAGE)
|
|
||||||
self.password = None
|
|
||||||
except PasswordHashInvalid as e:
|
|
||||||
if password_hash_invalid_raises:
|
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
print(e.MESSAGE)
|
print(e.MESSAGE)
|
||||||
self.password = None
|
self.password = None
|
||||||
|
self.recovery_code = None
|
||||||
except FloodWait as e:
|
except FloodWait as e:
|
||||||
if password_hash_invalid_raises:
|
if password_invalid_raises:
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
print(e.MESSAGE.format(x=e.x))
|
print(e.MESSAGE.format(x=e.x))
|
||||||
time.sleep(e.x)
|
time.sleep(e.x)
|
||||||
self.password = None
|
self.password = None
|
||||||
|
self.recovery_code = None
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error(e, exc_info=True)
|
log.error(e, exc_info=True)
|
||||||
|
raise
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
break
|
break
|
||||||
@ -642,6 +718,7 @@ class Client(Methods, BaseClient):
|
|||||||
time.sleep(e.x)
|
time.sleep(e.x)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error(e, exc_info=True)
|
log.error(e, exc_info=True)
|
||||||
|
raise
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
|
|
||||||
@ -943,6 +1020,12 @@ class Client(Methods, BaseClient):
|
|||||||
if not self.is_started:
|
if not self.is_started:
|
||||||
raise ConnectionError("Client has not been started")
|
raise ConnectionError("Client has not been started")
|
||||||
|
|
||||||
|
if self.no_updates:
|
||||||
|
data = functions.InvokeWithoutUpdates(data)
|
||||||
|
|
||||||
|
if self.takeout_id:
|
||||||
|
data = functions.InvokeWithTakeout(self.takeout_id, data)
|
||||||
|
|
||||||
r = self.session.send(data, retries, timeout)
|
r = self.session.send(data, retries, timeout)
|
||||||
|
|
||||||
self.fetch_peers(getattr(r, "users", []))
|
self.fetch_peers(getattr(r, "users", []))
|
||||||
@ -980,17 +1063,42 @@ class Client(Methods, BaseClient):
|
|||||||
setattr(self, option, getattr(Client, option.upper()))
|
setattr(self, option, getattr(Client, option.upper()))
|
||||||
|
|
||||||
if self._proxy:
|
if self._proxy:
|
||||||
self._proxy["enabled"] = True
|
self._proxy["enabled"] = bool(self._proxy.get("enabled", True))
|
||||||
else:
|
else:
|
||||||
self._proxy = {}
|
self._proxy = {}
|
||||||
|
|
||||||
if parser.has_section("proxy"):
|
if parser.has_section("proxy"):
|
||||||
self._proxy["enabled"] = parser.getboolean("proxy", "enabled")
|
self._proxy["enabled"] = parser.getboolean("proxy", "enabled", fallback=True)
|
||||||
self._proxy["hostname"] = parser.get("proxy", "hostname")
|
self._proxy["hostname"] = parser.get("proxy", "hostname")
|
||||||
self._proxy["port"] = parser.getint("proxy", "port")
|
self._proxy["port"] = parser.getint("proxy", "port")
|
||||||
self._proxy["username"] = parser.get("proxy", "username", fallback=None) or None
|
self._proxy["username"] = parser.get("proxy", "username", fallback=None) or None
|
||||||
self._proxy["password"] = parser.get("proxy", "password", fallback=None) or None
|
self._proxy["password"] = parser.get("proxy", "password", fallback=None) or None
|
||||||
|
|
||||||
|
if self.plugins:
|
||||||
|
self.plugins["enabled"] = bool(self.plugins.get("enabled", True))
|
||||||
|
self.plugins["include"] = "\n".join(self.plugins.get("include", [])) or None
|
||||||
|
self.plugins["exclude"] = "\n".join(self.plugins.get("exclude", [])) or None
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
section = parser["plugins"]
|
||||||
|
|
||||||
|
self.plugins = {
|
||||||
|
"enabled": section.getboolean("enabled", True),
|
||||||
|
"root": section.get("root"),
|
||||||
|
"include": section.get("include") or None,
|
||||||
|
"exclude": section.get("exclude") or None
|
||||||
|
}
|
||||||
|
except KeyError:
|
||||||
|
self.plugins = {}
|
||||||
|
|
||||||
|
if self.plugins:
|
||||||
|
for option in ["include", "exclude"]:
|
||||||
|
if self.plugins[option] is not None:
|
||||||
|
self.plugins[option] = [
|
||||||
|
(i.split()[0], i.split()[1:] or None)
|
||||||
|
for i in self.plugins[option].strip().split("\n")
|
||||||
|
]
|
||||||
|
|
||||||
def load_session(self):
|
def load_session(self):
|
||||||
try:
|
try:
|
||||||
with open(os.path.join(self.workdir, "{}.session".format(self.session_name)), encoding="utf-8") as f:
|
with open(os.path.join(self.workdir, "{}.session".format(self.session_name)), encoding="utf-8") as f:
|
||||||
@ -1022,21 +1130,19 @@ class Client(Methods, BaseClient):
|
|||||||
self.peers_by_phone[k] = peer
|
self.peers_by_phone[k] = peer
|
||||||
|
|
||||||
def load_plugins(self):
|
def load_plugins(self):
|
||||||
if self.plugins_dir is not None:
|
if self.plugins.get("enabled", False):
|
||||||
plugins_count = 0
|
root = self.plugins["root"]
|
||||||
|
include = self.plugins["include"]
|
||||||
|
exclude = self.plugins["exclude"]
|
||||||
|
|
||||||
for path in Path(self.plugins_dir).rglob("*.py"):
|
count = 0
|
||||||
file_path = os.path.splitext(str(path))[0]
|
|
||||||
import_path = []
|
|
||||||
|
|
||||||
while file_path:
|
if include is None:
|
||||||
file_path, tail = os.path.split(file_path)
|
for path in sorted(Path(root).rglob("*.py")):
|
||||||
import_path.insert(0, tail)
|
module_path = os.path.splitext(str(path))[0].replace("/", ".")
|
||||||
|
module = import_module(module_path)
|
||||||
|
|
||||||
import_path = ".".join(import_path)
|
for name in vars(module).keys():
|
||||||
module = import_module(import_path)
|
|
||||||
|
|
||||||
for name in dir(module):
|
|
||||||
# noinspection PyBroadException
|
# noinspection PyBroadException
|
||||||
try:
|
try:
|
||||||
handler, group = getattr(module, name)
|
handler, group = getattr(module, name)
|
||||||
@ -1044,21 +1150,88 @@ class Client(Methods, BaseClient):
|
|||||||
if isinstance(handler, Handler) and isinstance(group, int):
|
if isinstance(handler, Handler) and isinstance(group, int):
|
||||||
self.add_handler(handler, group)
|
self.add_handler(handler, group)
|
||||||
|
|
||||||
log.info('{}("{}") from "{}" loaded in group {}'.format(
|
log.info('[LOAD] {}("{}") in group {} from "{}"'.format(
|
||||||
type(handler).__name__, name, import_path, group))
|
type(handler).__name__, name, group, module_path))
|
||||||
|
|
||||||
plugins_count += 1
|
count += 1
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if plugins_count > 0:
|
|
||||||
log.warning('Successfully loaded {} plugin{} from "{}"'.format(
|
|
||||||
plugins_count,
|
|
||||||
"s" if plugins_count > 1 else "",
|
|
||||||
self.plugins_dir
|
|
||||||
))
|
|
||||||
else:
|
else:
|
||||||
log.warning('No plugin loaded: "{}" doesn\'t contain any valid plugin'.format(self.plugins_dir))
|
for path, handlers in include:
|
||||||
|
module_path = root + "." + path
|
||||||
|
warn_non_existent_functions = True
|
||||||
|
|
||||||
|
try:
|
||||||
|
module = import_module(module_path)
|
||||||
|
except ModuleNotFoundError:
|
||||||
|
log.warning('[LOAD] Ignoring non-existent module "{}"'.format(module_path))
|
||||||
|
continue
|
||||||
|
|
||||||
|
if "__path__" in dir(module):
|
||||||
|
log.warning('[LOAD] Ignoring namespace "{}"'.format(module_path))
|
||||||
|
continue
|
||||||
|
|
||||||
|
if handlers is None:
|
||||||
|
handlers = vars(module).keys()
|
||||||
|
warn_non_existent_functions = False
|
||||||
|
|
||||||
|
for name in handlers:
|
||||||
|
# noinspection PyBroadException
|
||||||
|
try:
|
||||||
|
handler, group = getattr(module, name)
|
||||||
|
|
||||||
|
if isinstance(handler, Handler) and isinstance(group, int):
|
||||||
|
self.add_handler(handler, group)
|
||||||
|
|
||||||
|
log.info('[LOAD] {}("{}") in group {} from "{}"'.format(
|
||||||
|
type(handler).__name__, name, group, module_path))
|
||||||
|
|
||||||
|
count += 1
|
||||||
|
except Exception:
|
||||||
|
if warn_non_existent_functions:
|
||||||
|
log.warning('[LOAD] Ignoring non-existent function "{}" from "{}"'.format(
|
||||||
|
name, module_path))
|
||||||
|
|
||||||
|
if exclude is not None:
|
||||||
|
for path, handlers in exclude:
|
||||||
|
module_path = root + "." + path
|
||||||
|
warn_non_existent_functions = True
|
||||||
|
|
||||||
|
try:
|
||||||
|
module = import_module(module_path)
|
||||||
|
except ModuleNotFoundError:
|
||||||
|
log.warning('[UNLOAD] Ignoring non-existent module "{}"'.format(module_path))
|
||||||
|
continue
|
||||||
|
|
||||||
|
if "__path__" in dir(module):
|
||||||
|
log.warning('[UNLOAD] Ignoring namespace "{}"'.format(module_path))
|
||||||
|
continue
|
||||||
|
|
||||||
|
if handlers is None:
|
||||||
|
handlers = vars(module).keys()
|
||||||
|
warn_non_existent_functions = False
|
||||||
|
|
||||||
|
for name in handlers:
|
||||||
|
# noinspection PyBroadException
|
||||||
|
try:
|
||||||
|
handler, group = getattr(module, name)
|
||||||
|
|
||||||
|
if isinstance(handler, Handler) and isinstance(group, int):
|
||||||
|
self.remove_handler(handler, group)
|
||||||
|
|
||||||
|
log.info('[UNLOAD] {}("{}") from group {} in "{}"'.format(
|
||||||
|
type(handler).__name__, name, group, module_path))
|
||||||
|
|
||||||
|
count -= 1
|
||||||
|
except Exception:
|
||||||
|
if warn_non_existent_functions:
|
||||||
|
log.warning('[UNLOAD] Ignoring non-existent function "{}" from "{}"'.format(
|
||||||
|
name, module_path))
|
||||||
|
|
||||||
|
if count > 0:
|
||||||
|
log.warning('Successfully loaded {} plugin{} from "{}"'.format(count, "s" if count > 1 else "", root))
|
||||||
|
else:
|
||||||
|
log.warning('No plugin loaded from "{}"'.format(root))
|
||||||
|
|
||||||
def save_session(self):
|
def save_session(self):
|
||||||
auth_key = base64.b64encode(self.auth_key).decode()
|
auth_key = base64.b64encode(self.auth_key).decode()
|
||||||
@ -1292,6 +1465,8 @@ class Client(Methods, BaseClient):
|
|||||||
|
|
||||||
if progress:
|
if progress:
|
||||||
progress(self, min(file_part * part_size, file_size), file_size, *progress_args)
|
progress(self, min(file_part * part_size, file_size), file_size, *progress_args)
|
||||||
|
except Client.StopTransmission:
|
||||||
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error(e, exc_info=True)
|
log.error(e, exc_info=True)
|
||||||
else:
|
else:
|
||||||
@ -1493,6 +1668,7 @@ class Client(Methods, BaseClient):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise e
|
raise e
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
if not isinstance(e, Client.StopTransmission):
|
||||||
log.error(e, exc_info=True)
|
log.error(e, exc_info=True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -128,35 +128,37 @@ class Dispatcher:
|
|||||||
|
|
||||||
parser = self.update_parsers.get(type(update), None)
|
parser = self.update_parsers.get(type(update), None)
|
||||||
|
|
||||||
if parser is None:
|
parsed_update, handler_type = (
|
||||||
continue
|
parser(update, users, chats)
|
||||||
|
if parser is not None
|
||||||
parsed_update, handler_type = parser(update, users, chats)
|
else (None, type(None))
|
||||||
|
)
|
||||||
|
|
||||||
for group in self.groups.values():
|
for group in self.groups.values():
|
||||||
try:
|
|
||||||
for handler in group:
|
for handler in group:
|
||||||
args = None
|
args = None
|
||||||
|
|
||||||
if isinstance(handler, RawUpdateHandler):
|
if isinstance(handler, handler_type):
|
||||||
args = (update, users, chats)
|
|
||||||
elif isinstance(handler, handler_type):
|
|
||||||
if handler.check(parsed_update):
|
if handler.check(parsed_update):
|
||||||
args = (parsed_update,)
|
args = (parsed_update,)
|
||||||
|
elif isinstance(handler, RawUpdateHandler):
|
||||||
|
args = (update, users, chats)
|
||||||
|
|
||||||
if args is None:
|
if args is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
handler.callback(self.client, *args)
|
handler.callback(self.client, *args)
|
||||||
except StopIteration:
|
except pyrogram.StopPropagation:
|
||||||
raise
|
raise
|
||||||
|
except pyrogram.ContinuePropagation:
|
||||||
|
continue
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error(e, exc_info=True)
|
log.error(e, exc_info=True)
|
||||||
|
|
||||||
break
|
break
|
||||||
except StopIteration:
|
except pyrogram.StopPropagation:
|
||||||
break
|
pass
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error(e, exc_info=True)
|
log.error(e, exc_info=True)
|
||||||
|
|
||||||
|
@ -23,12 +23,13 @@ from threading import Lock
|
|||||||
|
|
||||||
from pyrogram import __version__
|
from pyrogram import __version__
|
||||||
from ..style import Markdown, HTML
|
from ..style import Markdown, HTML
|
||||||
from ...api.core import Object
|
|
||||||
from ...session import Session
|
|
||||||
from ...session.internals import MsgId
|
from ...session.internals import MsgId
|
||||||
|
|
||||||
|
|
||||||
class BaseClient:
|
class BaseClient:
|
||||||
|
class StopTransmission(StopIteration):
|
||||||
|
pass
|
||||||
|
|
||||||
APP_VERSION = "Pyrogram \U0001f525 {}".format(__version__)
|
APP_VERSION = "Pyrogram \U0001f525 {}".format(__version__)
|
||||||
|
|
||||||
DEVICE_MODEL = "{} {}".format(
|
DEVICE_MODEL = "{} {}".format(
|
||||||
@ -90,6 +91,8 @@ class BaseClient:
|
|||||||
self.is_started = None
|
self.is_started = None
|
||||||
self.is_idle = None
|
self.is_idle = None
|
||||||
|
|
||||||
|
self.takeout_id = None
|
||||||
|
|
||||||
self.updates_queue = Queue()
|
self.updates_queue = Queue()
|
||||||
self.updates_workers_list = []
|
self.updates_workers_list = []
|
||||||
self.download_queue = Queue()
|
self.download_queue = Queue()
|
||||||
@ -97,33 +100,32 @@ class BaseClient:
|
|||||||
|
|
||||||
self.disconnect_handler = None
|
self.disconnect_handler = None
|
||||||
|
|
||||||
def send(self, data: Object, retries: int = Session.MAX_RETRIES, timeout: float = Session.WAIT_TIMEOUT):
|
def send(self, *args, **kwargs):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def resolve_peer(self, peer_id: int or str):
|
def resolve_peer(self, *args, **kwargs):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def fetch_peers(self, entities):
|
def fetch_peers(self, *args, **kwargs):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def add_handler(self, handler, group: int = 0) -> tuple:
|
def add_handler(self, *args, **kwargs):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def save_file(
|
def save_file(self, *args, **kwargs):
|
||||||
self,
|
|
||||||
path: str,
|
|
||||||
file_id: int = None,
|
|
||||||
file_part: int = 0,
|
|
||||||
progress: callable = None,
|
|
||||||
progress_args: tuple = ()
|
|
||||||
):
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def get_messages(
|
def get_messages(self, *args, **kwargs):
|
||||||
self,
|
pass
|
||||||
chat_id: int or str,
|
|
||||||
message_ids: int or list = None,
|
def get_history(self, *args, **kwargs):
|
||||||
reply_to_message_ids: int or list = None,
|
pass
|
||||||
replies: int = 1
|
|
||||||
):
|
def get_dialogs(self, *args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_chat_members(self, *args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_chat_members_count(self, *args, **kwargs):
|
||||||
pass
|
pass
|
||||||
|
@ -61,6 +61,9 @@ class Filters:
|
|||||||
|
|
||||||
create = create
|
create = create
|
||||||
|
|
||||||
|
me = create("Me", lambda _, m: bool(m.from_user and m.from_user.is_self))
|
||||||
|
"""Filter messages coming from you yourself"""
|
||||||
|
|
||||||
bot = create("Bot", lambda _, m: bool(m.from_user and m.from_user.is_bot))
|
bot = create("Bot", lambda _, m: bool(m.from_user and m.from_user.is_bot))
|
||||||
"""Filter messages coming from bots"""
|
"""Filter messages coming from bots"""
|
||||||
|
|
||||||
@ -97,12 +100,18 @@ class Filters:
|
|||||||
sticker = create("Sticker", lambda _, m: bool(m.sticker))
|
sticker = create("Sticker", lambda _, m: bool(m.sticker))
|
||||||
"""Filter messages that contain :obj:`Sticker <pyrogram.Sticker>` objects."""
|
"""Filter messages that contain :obj:`Sticker <pyrogram.Sticker>` objects."""
|
||||||
|
|
||||||
animation = create("GIF", lambda _, m: bool(m.animation))
|
animation = create("Animation", lambda _, m: bool(m.animation))
|
||||||
"""Filter messages that contain :obj:`Animation <pyrogram.Animation>` objects."""
|
"""Filter messages that contain :obj:`Animation <pyrogram.Animation>` objects."""
|
||||||
|
|
||||||
|
game = create("Game", lambda _, m: bool(m.game))
|
||||||
|
"""Filter messages that contain :obj:`Game <pyrogram.Game>` objects."""
|
||||||
|
|
||||||
video = create("Video", lambda _, m: bool(m.video))
|
video = create("Video", lambda _, m: bool(m.video))
|
||||||
"""Filter messages that contain :obj:`Video <pyrogram.Video>` objects."""
|
"""Filter messages that contain :obj:`Video <pyrogram.Video>` objects."""
|
||||||
|
|
||||||
|
media_group = create("MediaGroup", lambda _, m: bool(m.media_group_id))
|
||||||
|
"""Filter messages containing photos or videos being part of an album."""
|
||||||
|
|
||||||
voice = create("Voice", lambda _, m: bool(m.voice))
|
voice = create("Voice", lambda _, m: bool(m.voice))
|
||||||
"""Filter messages that contain :obj:`Voice <pyrogram.Voice>` note objects."""
|
"""Filter messages that contain :obj:`Voice <pyrogram.Voice>` note objects."""
|
||||||
|
|
||||||
@ -166,6 +175,9 @@ class Filters:
|
|||||||
pinned_message = create("PinnedMessage", lambda _, m: bool(m.pinned_message))
|
pinned_message = create("PinnedMessage", lambda _, m: bool(m.pinned_message))
|
||||||
"""Filter service messages for pinned messages."""
|
"""Filter service messages for pinned messages."""
|
||||||
|
|
||||||
|
game_high_score = create("GameHighScore", lambda _, m: bool(m.game_high_score))
|
||||||
|
"""Filter service messages for game high scores."""
|
||||||
|
|
||||||
reply_keyboard = create("ReplyKeyboard", lambda _, m: isinstance(m.reply_markup, ReplyKeyboardMarkup))
|
reply_keyboard = create("ReplyKeyboard", lambda _, m: isinstance(m.reply_markup, ReplyKeyboardMarkup))
|
||||||
"""Filter messages containing reply keyboard markups"""
|
"""Filter messages containing reply keyboard markups"""
|
||||||
|
|
||||||
@ -190,7 +202,8 @@ class Filters:
|
|||||||
- channel_chat_created
|
- channel_chat_created
|
||||||
- migrate_to_chat_id
|
- migrate_to_chat_id
|
||||||
- migrate_from_chat_id
|
- migrate_from_chat_id
|
||||||
- pinned_message"""
|
- pinned_message
|
||||||
|
- game_score"""
|
||||||
|
|
||||||
media = create("Media", lambda _, m: bool(m.media))
|
media = create("Media", lambda _, m: bool(m.media))
|
||||||
"""Filter media messages. A media message contains any of the following fields set
|
"""Filter media messages. A media message contains any of the following fields set
|
||||||
@ -205,7 +218,8 @@ class Filters:
|
|||||||
- video_note
|
- video_note
|
||||||
- contact
|
- contact
|
||||||
- location
|
- location
|
||||||
- venue"""
|
- venue
|
||||||
|
- poll"""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def command(command: str or list,
|
def command(command: str or list,
|
||||||
@ -276,7 +290,7 @@ class Filters:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def f(_, m):
|
def f(_, m):
|
||||||
m.matches = [i for i in _.p.finditer(m.text or "")]
|
m.matches = [i for i in _.p.finditer(m.text or m.caption or "")]
|
||||||
return bool(m.matches)
|
return bool(m.matches)
|
||||||
|
|
||||||
return create("Regex", f, p=re.compile(pattern, flags))
|
return create("Regex", f, p=re.compile(pattern, flags))
|
||||||
|
@ -45,10 +45,3 @@ class CallbackQueryHandler(Handler):
|
|||||||
|
|
||||||
def __init__(self, callback: callable, filters=None):
|
def __init__(self, callback: callable, filters=None):
|
||||||
super().__init__(callback, filters)
|
super().__init__(callback, filters)
|
||||||
|
|
||||||
def check(self, callback_query):
|
|
||||||
return (
|
|
||||||
self.filters(callback_query)
|
|
||||||
if callable(self.filters)
|
|
||||||
else True
|
|
||||||
)
|
|
||||||
|
@ -48,8 +48,4 @@ class DeletedMessagesHandler(Handler):
|
|||||||
super().__init__(callback, filters)
|
super().__init__(callback, filters)
|
||||||
|
|
||||||
def check(self, messages):
|
def check(self, messages):
|
||||||
return (
|
return super().check(messages.messages[0])
|
||||||
self.filters(messages.messages[0])
|
|
||||||
if callable(self.filters)
|
|
||||||
else True
|
|
||||||
)
|
|
||||||
|
@ -21,3 +21,10 @@ class Handler:
|
|||||||
def __init__(self, callback: callable, filters=None):
|
def __init__(self, callback: callable, filters=None):
|
||||||
self.callback = callback
|
self.callback = callback
|
||||||
self.filters = filters
|
self.filters = filters
|
||||||
|
|
||||||
|
def check(self, update):
|
||||||
|
return (
|
||||||
|
self.filters(update)
|
||||||
|
if callable(self.filters)
|
||||||
|
else True
|
||||||
|
)
|
||||||
|
@ -46,10 +46,3 @@ class MessageHandler(Handler):
|
|||||||
|
|
||||||
def __init__(self, callback: callable, filters=None):
|
def __init__(self, callback: callable, filters=None):
|
||||||
super().__init__(callback, filters)
|
super().__init__(callback, filters)
|
||||||
|
|
||||||
def check(self, message):
|
|
||||||
return (
|
|
||||||
self.filters(message)
|
|
||||||
if callable(self.filters)
|
|
||||||
else True
|
|
||||||
)
|
|
||||||
|
@ -45,10 +45,3 @@ class UserStatusHandler(Handler):
|
|||||||
|
|
||||||
def __init__(self, callback: callable, filters=None):
|
def __init__(self, callback: callable, filters=None):
|
||||||
super().__init__(callback, filters)
|
super().__init__(callback, filters)
|
||||||
|
|
||||||
def check(self, user_status):
|
|
||||||
return (
|
|
||||||
self.filters(user_status)
|
|
||||||
if callable(self.filters)
|
|
||||||
else True
|
|
||||||
)
|
|
||||||
|
@ -17,15 +17,21 @@
|
|||||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from .answer_callback_query import AnswerCallbackQuery
|
from .answer_callback_query import AnswerCallbackQuery
|
||||||
|
from .get_game_high_scores import GetGameHighScores
|
||||||
from .get_inline_bot_results import GetInlineBotResults
|
from .get_inline_bot_results import GetInlineBotResults
|
||||||
from .request_callback_answer import RequestCallbackAnswer
|
from .request_callback_answer import RequestCallbackAnswer
|
||||||
|
from .send_game import SendGame
|
||||||
from .send_inline_bot_result import SendInlineBotResult
|
from .send_inline_bot_result import SendInlineBotResult
|
||||||
|
from .set_game_score import SetGameScore
|
||||||
|
|
||||||
|
|
||||||
class Bots(
|
class Bots(
|
||||||
AnswerCallbackQuery,
|
AnswerCallbackQuery,
|
||||||
GetInlineBotResults,
|
GetInlineBotResults,
|
||||||
RequestCallbackAnswer,
|
RequestCallbackAnswer,
|
||||||
SendInlineBotResult
|
SendInlineBotResult,
|
||||||
|
SendGame,
|
||||||
|
SetGameScore,
|
||||||
|
GetGameHighScores
|
||||||
):
|
):
|
||||||
pass
|
pass
|
||||||
|
66
pyrogram/client/methods/bots/get_game_high_scores.py
Normal file
66
pyrogram/client/methods/bots/get_game_high_scores.py
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
# Pyrogram - Telegram MTProto API Client Library for Python
|
||||||
|
# Copyright (C) 2017-2019 Dan Tès <https://github.com/delivrance>
|
||||||
|
#
|
||||||
|
# This file is part of Pyrogram.
|
||||||
|
#
|
||||||
|
# Pyrogram is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser General Public License as published
|
||||||
|
# by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Pyrogram is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
|
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
import pyrogram
|
||||||
|
from pyrogram.api import functions
|
||||||
|
from pyrogram.client.ext import BaseClient
|
||||||
|
|
||||||
|
|
||||||
|
class GetGameHighScores(BaseClient):
|
||||||
|
def get_game_high_scores(self,
|
||||||
|
user_id: Union[int, str],
|
||||||
|
chat_id: Union[int, str],
|
||||||
|
message_id: int = None):
|
||||||
|
"""Use this method to get data for high score tables.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_id (``int`` | ``str``):
|
||||||
|
Unique identifier (int) or username (str) of the target chat.
|
||||||
|
For your personal cloud (Saved Messages) you can simply use "me" or "self".
|
||||||
|
For a contact that exists in your Telegram address book you can use his phone number (str).
|
||||||
|
|
||||||
|
chat_id (``int`` | ``str``, *optional*):
|
||||||
|
Unique identifier (int) or username (str) of the target chat.
|
||||||
|
For your personal cloud (Saved Messages) you can simply use "me" or "self".
|
||||||
|
For a contact that exists in your Telegram address book you can use his phone number (str).
|
||||||
|
Required if inline_message_id is not specified.
|
||||||
|
|
||||||
|
message_id (``int``, *optional*):
|
||||||
|
Identifier of the sent message.
|
||||||
|
Required if inline_message_id is not specified.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
On success, a :obj:`GameHighScores <pyrogram.GameHighScores>` object is returned.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
|
"""
|
||||||
|
# TODO: inline_message_id
|
||||||
|
|
||||||
|
return pyrogram.GameHighScores._parse(
|
||||||
|
self,
|
||||||
|
self.send(
|
||||||
|
functions.messages.GetGameHighScores(
|
||||||
|
peer=self.resolve_peer(chat_id),
|
||||||
|
id=message_id,
|
||||||
|
user_id=self.resolve_peer(user_id)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
87
pyrogram/client/methods/bots/send_game.py
Normal file
87
pyrogram/client/methods/bots/send_game.py
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
# Pyrogram - Telegram MTProto API Client Library for Python
|
||||||
|
# Copyright (C) 2017-2019 Dan Tès <https://github.com/delivrance>
|
||||||
|
#
|
||||||
|
# This file is part of Pyrogram.
|
||||||
|
#
|
||||||
|
# Pyrogram is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser General Public License as published
|
||||||
|
# by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Pyrogram is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
|
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
import pyrogram
|
||||||
|
from pyrogram.api import functions, types
|
||||||
|
from pyrogram.client.ext import BaseClient
|
||||||
|
|
||||||
|
|
||||||
|
class SendGame(BaseClient):
|
||||||
|
def send_game(self,
|
||||||
|
chat_id: Union[int, str],
|
||||||
|
game_short_name: str,
|
||||||
|
disable_notification: bool = None,
|
||||||
|
reply_to_message_id: int = None,
|
||||||
|
reply_markup: Union["pyrogram.InlineKeyboardMarkup",
|
||||||
|
"pyrogram.ReplyKeyboardMarkup",
|
||||||
|
"pyrogram.ReplyKeyboardRemove",
|
||||||
|
"pyrogram.ForceReply"] = None) -> "pyrogram.Message":
|
||||||
|
"""Use this method to send a game.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
chat_id (``int`` | ``str``):
|
||||||
|
Unique identifier (int) or username (str) of the target chat.
|
||||||
|
For your personal cloud (Saved Messages) you can simply use "me" or "self".
|
||||||
|
For a contact that exists in your Telegram address book you can use his phone number (str).
|
||||||
|
|
||||||
|
game_short_name (``str``):
|
||||||
|
Short name of the game, serves as the unique identifier for the game. Set up your games via Botfather.
|
||||||
|
|
||||||
|
disable_notification (``bool``, *optional*):
|
||||||
|
Sends the message silently.
|
||||||
|
Users will receive a notification with no sound.
|
||||||
|
|
||||||
|
reply_to_message_id (``int``, *optional*):
|
||||||
|
If the message is a reply, ID of the original message.
|
||||||
|
|
||||||
|
reply_markup (:obj:`InlineKeyboardMarkup`, *optional*):
|
||||||
|
An object for an inline keyboard. If empty, one ‘Play game_title’ button will be shown automatically.
|
||||||
|
If not empty, the first button must launch the game.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
On success, the sent :obj:`Message` is returned.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
|
"""
|
||||||
|
r = self.send(
|
||||||
|
functions.messages.SendMedia(
|
||||||
|
peer=self.resolve_peer(chat_id),
|
||||||
|
media=types.InputMediaGame(
|
||||||
|
id=types.InputGameShortName(
|
||||||
|
bot_id=types.InputUserSelf(),
|
||||||
|
short_name=game_short_name
|
||||||
|
),
|
||||||
|
),
|
||||||
|
message="",
|
||||||
|
silent=disable_notification or None,
|
||||||
|
reply_to_msg_id=reply_to_message_id,
|
||||||
|
random_id=self.rnd_id(),
|
||||||
|
reply_markup=reply_markup.write() if reply_markup else None
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
for i in r.updates:
|
||||||
|
if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)):
|
||||||
|
return pyrogram.Message._parse(
|
||||||
|
self, i.message,
|
||||||
|
{i.id: i for i in r.users},
|
||||||
|
{i.id: i for i in r.chats}
|
||||||
|
)
|
90
pyrogram/client/methods/bots/set_game_score.py
Normal file
90
pyrogram/client/methods/bots/set_game_score.py
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
# Pyrogram - Telegram MTProto API Client Library for Python
|
||||||
|
# Copyright (C) 2017-2019 Dan Tès <https://github.com/delivrance>
|
||||||
|
#
|
||||||
|
# This file is part of Pyrogram.
|
||||||
|
#
|
||||||
|
# Pyrogram is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser General Public License as published
|
||||||
|
# by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Pyrogram is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
|
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
import pyrogram
|
||||||
|
from pyrogram.api import functions, types
|
||||||
|
from pyrogram.client.ext import BaseClient
|
||||||
|
|
||||||
|
|
||||||
|
class SetGameScore(BaseClient):
|
||||||
|
def set_game_score(self,
|
||||||
|
user_id: Union[int, str],
|
||||||
|
score: int,
|
||||||
|
force: bool = None,
|
||||||
|
disable_edit_message: bool = None,
|
||||||
|
chat_id: Union[int, str] = None,
|
||||||
|
message_id: int = None):
|
||||||
|
# inline_message_id: str = None): TODO Add inline_message_id
|
||||||
|
"""Use this method to set the score of the specified user in a game.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_id (``int`` | ``str``):
|
||||||
|
Unique identifier (int) or username (str) of the target chat.
|
||||||
|
For your personal cloud (Saved Messages) you can simply use "me" or "self".
|
||||||
|
For a contact that exists in your Telegram address book you can use his phone number (str).
|
||||||
|
|
||||||
|
score (``int``):
|
||||||
|
New score, must be non-negative.
|
||||||
|
|
||||||
|
force (``bool``, *optional*):
|
||||||
|
Pass True, if the high score is allowed to decrease.
|
||||||
|
This can be useful when fixing mistakes or banning cheaters.
|
||||||
|
|
||||||
|
disable_edit_message (``bool``, *optional*):
|
||||||
|
Pass True, if the game message should not be automatically edited to include the current scoreboard.
|
||||||
|
|
||||||
|
chat_id (``int`` | ``str``, *optional*):
|
||||||
|
Unique identifier (int) or username (str) of the target chat.
|
||||||
|
For your personal cloud (Saved Messages) you can simply use "me" or "self".
|
||||||
|
For a contact that exists in your Telegram address book you can use his phone number (str).
|
||||||
|
Required if inline_message_id is not specified.
|
||||||
|
|
||||||
|
message_id (``int``, *optional*):
|
||||||
|
Identifier of the sent message.
|
||||||
|
Required if inline_message_id is not specified.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
On success, if the message was sent by the bot, returns the edited :obj:`Message <pyrogram.Message>`,
|
||||||
|
otherwise returns True.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
|
:class:`BotScoreNotModified` if the new score is not greater than the user's current score in the chat and force is False.
|
||||||
|
"""
|
||||||
|
r = self.send(
|
||||||
|
functions.messages.SetGameScore(
|
||||||
|
peer=self.resolve_peer(chat_id),
|
||||||
|
score=score,
|
||||||
|
id=message_id,
|
||||||
|
user_id=self.resolve_peer(user_id),
|
||||||
|
force=force or None,
|
||||||
|
edit_message=not disable_edit_message or None
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
for i in r.updates:
|
||||||
|
if isinstance(i, (types.UpdateEditMessage, types.UpdateEditChannelMessage)):
|
||||||
|
return pyrogram.Message._parse(
|
||||||
|
self, i.message,
|
||||||
|
{i.id: i for i in r.users},
|
||||||
|
{i.id: i for i in r.chats}
|
||||||
|
)
|
||||||
|
|
||||||
|
return True
|
@ -24,6 +24,8 @@ from .get_chat_members import GetChatMembers
|
|||||||
from .get_chat_members_count import GetChatMembersCount
|
from .get_chat_members_count import GetChatMembersCount
|
||||||
from .get_chat_preview import GetChatPreview
|
from .get_chat_preview import GetChatPreview
|
||||||
from .get_dialogs import GetDialogs
|
from .get_dialogs import GetDialogs
|
||||||
|
from .iter_chat_members import IterChatMembers
|
||||||
|
from .iter_dialogs import IterDialogs
|
||||||
from .join_chat import JoinChat
|
from .join_chat import JoinChat
|
||||||
from .kick_chat_member import KickChatMember
|
from .kick_chat_member import KickChatMember
|
||||||
from .leave_chat import LeaveChat
|
from .leave_chat import LeaveChat
|
||||||
@ -56,6 +58,8 @@ class Chats(
|
|||||||
UnpinChatMessage,
|
UnpinChatMessage,
|
||||||
GetDialogs,
|
GetDialogs,
|
||||||
GetChatMembersCount,
|
GetChatMembersCount,
|
||||||
GetChatPreview
|
GetChatPreview,
|
||||||
|
IterDialogs,
|
||||||
|
IterChatMembers
|
||||||
):
|
):
|
||||||
pass
|
pass
|
||||||
|
@ -67,6 +67,8 @@ class GetChatMember(BaseClient):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
return pyrogram.ChatMember._parse(self, r.participant, r.users[0])
|
users = {i.id: i for i in r.users}
|
||||||
|
|
||||||
|
return pyrogram.ChatMember._parse(self, r.participant, users)
|
||||||
else:
|
else:
|
||||||
raise ValueError("The chat_id \"{}\" belongs to a user".format(chat_id))
|
raise ValueError("The chat_id \"{}\" belongs to a user".format(chat_id))
|
||||||
|
@ -39,10 +39,12 @@ class GetChatMembers(BaseClient):
|
|||||||
limit: int = 200,
|
limit: int = 200,
|
||||||
query: str = "",
|
query: str = "",
|
||||||
filter: str = Filters.ALL) -> "pyrogram.ChatMembers":
|
filter: str = Filters.ALL) -> "pyrogram.ChatMembers":
|
||||||
"""Use this method to get the members list of a chat.
|
"""Use this method to get a chunk of the members list of a chat.
|
||||||
|
|
||||||
|
You can get up to 200 chat members at once.
|
||||||
A chat can be either a basic group, a supergroup or a channel.
|
A chat can be either a basic group, a supergroup or a channel.
|
||||||
You must be admin to retrieve the members list of a channel (also known as "subscribers").
|
You must be admin to retrieve the members list of a channel (also known as "subscribers").
|
||||||
|
For a more convenient way of getting chat members see :meth:`iter_chat_members`.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
chat_id (``int`` | ``str``):
|
chat_id (``int`` | ``str``):
|
||||||
|
@ -16,19 +16,26 @@
|
|||||||
# You should have received a copy of the GNU Lesser General Public License
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
|
||||||
import pyrogram
|
import pyrogram
|
||||||
from pyrogram.api import functions, types
|
from pyrogram.api import functions, types
|
||||||
|
from pyrogram.api.errors import FloodWait
|
||||||
from ...ext import BaseClient
|
from ...ext import BaseClient
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class GetDialogs(BaseClient):
|
class GetDialogs(BaseClient):
|
||||||
def get_dialogs(self,
|
def get_dialogs(self,
|
||||||
offset_date: int = 0,
|
offset_date: int = 0,
|
||||||
limit: int = 100,
|
limit: int = 100,
|
||||||
pinned_only: bool = False) -> "pyrogram.Dialogs":
|
pinned_only: bool = False) -> "pyrogram.Dialogs":
|
||||||
"""Use this method to get the user's dialogs
|
"""Use this method to get a chunk of the user's dialogs
|
||||||
|
|
||||||
You can get up to 100 dialogs at once.
|
You can get up to 100 dialogs at once.
|
||||||
|
For a more convenient way of getting a user's dialogs see :meth:`iter_dialogs`.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
offset_date (``int``):
|
offset_date (``int``):
|
||||||
@ -50,6 +57,8 @@ class GetDialogs(BaseClient):
|
|||||||
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
if pinned_only:
|
if pinned_only:
|
||||||
r = self.send(functions.messages.GetPinnedDialogs())
|
r = self.send(functions.messages.GetPinnedDialogs())
|
||||||
else:
|
else:
|
||||||
@ -63,5 +72,10 @@ class GetDialogs(BaseClient):
|
|||||||
exclude_pinned=True
|
exclude_pinned=True
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
except FloodWait as e:
|
||||||
|
log.warning("Sleeping {}s".format(e.x))
|
||||||
|
time.sleep(e.x)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
return pyrogram.Dialogs._parse(self, r)
|
return pyrogram.Dialogs._parse(self, r)
|
||||||
|
125
pyrogram/client/methods/chats/iter_chat_members.py
Normal file
125
pyrogram/client/methods/chats/iter_chat_members.py
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
# Pyrogram - Telegram MTProto API Client Library for Python
|
||||||
|
# Copyright (C) 2017-2019 Dan Tès <https://github.com/delivrance>
|
||||||
|
#
|
||||||
|
# This file is part of Pyrogram.
|
||||||
|
#
|
||||||
|
# Pyrogram is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser General Public License as published
|
||||||
|
# by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Pyrogram is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
|
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from string import ascii_lowercase
|
||||||
|
from typing import Union, Generator
|
||||||
|
|
||||||
|
import pyrogram
|
||||||
|
from ...ext import BaseClient
|
||||||
|
|
||||||
|
|
||||||
|
class Filters:
|
||||||
|
ALL = "all"
|
||||||
|
KICKED = "kicked"
|
||||||
|
RESTRICTED = "restricted"
|
||||||
|
BOTS = "bots"
|
||||||
|
RECENT = "recent"
|
||||||
|
ADMINISTRATORS = "administrators"
|
||||||
|
|
||||||
|
|
||||||
|
QUERIES = [""] + [str(i) for i in range(10)] + list(ascii_lowercase)
|
||||||
|
QUERYABLE_FILTERS = (Filters.ALL, Filters.KICKED, Filters.RESTRICTED)
|
||||||
|
|
||||||
|
|
||||||
|
class IterChatMembers(BaseClient):
|
||||||
|
def iter_chat_members(self,
|
||||||
|
chat_id: Union[int, str],
|
||||||
|
limit: int = 0,
|
||||||
|
query: str = "",
|
||||||
|
filter: str = Filters.ALL) -> Generator["pyrogram.ChatMember", None, None]:
|
||||||
|
"""Use this method to iterate through the members of a chat sequentially.
|
||||||
|
|
||||||
|
This convenience method does the same as repeatedly calling :meth:`get_chat_members` in a loop, thus saving you
|
||||||
|
from the hassle of setting up boilerplate code. It is useful for getting the whole members list of a chat with
|
||||||
|
a single call.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
chat_id (``int`` | ``str``):
|
||||||
|
Unique identifier (int) or username (str) of the target chat.
|
||||||
|
|
||||||
|
limit (``int``, *optional*):
|
||||||
|
Limits the number of members to be retrieved.
|
||||||
|
By default, no limit is applied and all members are returned.
|
||||||
|
|
||||||
|
query (``str``, *optional*):
|
||||||
|
Query string to filter members based on their display names and usernames.
|
||||||
|
Defaults to "" (empty string).
|
||||||
|
|
||||||
|
filter (``str``, *optional*):
|
||||||
|
Filter used to select the kind of members you want to retrieve. Only applicable for supergroups
|
||||||
|
and channels. It can be any of the followings:
|
||||||
|
*"all"* - all kind of members,
|
||||||
|
*"kicked"* - kicked (banned) members only,
|
||||||
|
*"restricted"* - restricted members only,
|
||||||
|
*"bots"* - bots only,
|
||||||
|
*"recent"* - recent members only,
|
||||||
|
*"administrators"* - chat administrators only.
|
||||||
|
Defaults to *"all"*.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A generator yielding :obj:`ChatMember <pyrogram.ChatMember>` objects.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
|
"""
|
||||||
|
current = 0
|
||||||
|
yielded = set()
|
||||||
|
queries = [query] if query else QUERIES
|
||||||
|
total = limit or (1 << 31) - 1
|
||||||
|
limit = min(200, total)
|
||||||
|
|
||||||
|
filter = (
|
||||||
|
Filters.RECENT
|
||||||
|
if self.get_chat_members_count(chat_id) <= 10000 and filter == Filters.ALL
|
||||||
|
else filter
|
||||||
|
)
|
||||||
|
|
||||||
|
if filter not in QUERYABLE_FILTERS:
|
||||||
|
queries = [""]
|
||||||
|
|
||||||
|
for q in queries:
|
||||||
|
offset = 0
|
||||||
|
|
||||||
|
while True:
|
||||||
|
chat_members = self.get_chat_members(
|
||||||
|
chat_id=chat_id,
|
||||||
|
offset=offset,
|
||||||
|
limit=limit,
|
||||||
|
query=q,
|
||||||
|
filter=filter
|
||||||
|
).chat_members
|
||||||
|
|
||||||
|
if not chat_members:
|
||||||
|
break
|
||||||
|
|
||||||
|
offset += len(chat_members)
|
||||||
|
|
||||||
|
for chat_member in chat_members:
|
||||||
|
user_id = chat_member.user.id
|
||||||
|
|
||||||
|
if user_id in yielded:
|
||||||
|
continue
|
||||||
|
|
||||||
|
yield chat_member
|
||||||
|
|
||||||
|
yielded.add(chat_member.user.id)
|
||||||
|
|
||||||
|
current += 1
|
||||||
|
|
||||||
|
if current >= total:
|
||||||
|
return
|
82
pyrogram/client/methods/chats/iter_dialogs.py
Normal file
82
pyrogram/client/methods/chats/iter_dialogs.py
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
# Pyrogram - Telegram MTProto API Client Library for Python
|
||||||
|
# Copyright (C) 2017-2019 Dan Tès <https://github.com/delivrance>
|
||||||
|
#
|
||||||
|
# This file is part of Pyrogram.
|
||||||
|
#
|
||||||
|
# Pyrogram is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser General Public License as published
|
||||||
|
# by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Pyrogram is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
|
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from typing import Generator
|
||||||
|
|
||||||
|
import pyrogram
|
||||||
|
from ...ext import BaseClient
|
||||||
|
|
||||||
|
|
||||||
|
class IterDialogs(BaseClient):
|
||||||
|
def iter_dialogs(self,
|
||||||
|
offset_date: int = 0,
|
||||||
|
limit: int = 0) -> Generator["pyrogram.Dialog", None, None]:
|
||||||
|
"""Use this method to iterate through a user's dialogs sequentially.
|
||||||
|
|
||||||
|
This convenience method does the same as repeatedly calling :meth:`get_dialogs` in a loop, thus saving you from
|
||||||
|
the hassle of setting up boilerplate code. It is useful for getting the whole dialogs list with a single call.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
offset_date (``int``):
|
||||||
|
The offset date in Unix time taken from the top message of a :obj:`Dialog`.
|
||||||
|
Defaults to 0 (most recent dialog).
|
||||||
|
|
||||||
|
limit (``str``, *optional*):
|
||||||
|
Limits the number of dialogs to be retrieved.
|
||||||
|
By default, no limit is applied and all dialogs are returned.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A generator yielding :obj:`Dialog <pyrogram.Dialog>` objects.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
|
"""
|
||||||
|
current = 0
|
||||||
|
total = limit or (1 << 31) - 1
|
||||||
|
limit = min(100, total)
|
||||||
|
|
||||||
|
pinned_dialogs = self.get_dialogs(
|
||||||
|
pinned_only=True
|
||||||
|
).dialogs
|
||||||
|
|
||||||
|
for dialog in pinned_dialogs:
|
||||||
|
yield dialog
|
||||||
|
|
||||||
|
current += 1
|
||||||
|
|
||||||
|
if current >= total:
|
||||||
|
return
|
||||||
|
|
||||||
|
while True:
|
||||||
|
dialogs = self.get_dialogs(
|
||||||
|
offset_date=offset_date,
|
||||||
|
limit=limit
|
||||||
|
).dialogs
|
||||||
|
|
||||||
|
if not dialogs:
|
||||||
|
return
|
||||||
|
|
||||||
|
offset_date = dialogs[-1].top_message.date
|
||||||
|
|
||||||
|
for dialog in dialogs:
|
||||||
|
yield dialog
|
||||||
|
|
||||||
|
current += 1
|
||||||
|
|
||||||
|
if current >= total:
|
||||||
|
return
|
@ -27,7 +27,7 @@ class KickChatMember(BaseClient):
|
|||||||
def kick_chat_member(self,
|
def kick_chat_member(self,
|
||||||
chat_id: Union[int, str],
|
chat_id: Union[int, str],
|
||||||
user_id: Union[int, str],
|
user_id: Union[int, str],
|
||||||
until_date: int = 0) -> "pyrogram.Message":
|
until_date: int = 0) -> Union["pyrogram.Message", bool]:
|
||||||
"""Use this method to kick a user from a group, a supergroup or a channel.
|
"""Use this method to kick a user from a group, a supergroup or a channel.
|
||||||
In the case of supergroups and channels, the user will not be able to return to the group on their own using
|
In the case of supergroups and channels, the user will not be able to return to the group on their own using
|
||||||
invite links, etc., unless unbanned first. You must be an administrator in the chat for this to work and must
|
invite links, etc., unless unbanned first. You must be an administrator in the chat for this to work and must
|
||||||
@ -52,7 +52,7 @@ class KickChatMember(BaseClient):
|
|||||||
considered to be banned forever. Defaults to 0 (ban forever).
|
considered to be banned forever. Defaults to 0 (ban forever).
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
True on success.
|
On success, either True or a service :obj:`Message <pyrogram.Message>` will be returned (when applicable).
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
@ -93,3 +93,5 @@ class KickChatMember(BaseClient):
|
|||||||
{i.id: i for i in r.users},
|
{i.id: i for i in r.users},
|
||||||
{i.id: i for i in r.chats}
|
{i.id: i for i in r.chats}
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
# You should have received a copy of the GNU Lesser General Public License
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from .close_poll import ClosePoll
|
||||||
from .delete_messages import DeleteMessages
|
from .delete_messages import DeleteMessages
|
||||||
from .download_media import DownloadMedia
|
from .download_media import DownloadMedia
|
||||||
from .edit_message_caption import EditMessageCaption
|
from .edit_message_caption import EditMessageCaption
|
||||||
@ -25,6 +26,7 @@ from .edit_message_text import EditMessageText
|
|||||||
from .forward_messages import ForwardMessages
|
from .forward_messages import ForwardMessages
|
||||||
from .get_history import GetHistory
|
from .get_history import GetHistory
|
||||||
from .get_messages import GetMessages
|
from .get_messages import GetMessages
|
||||||
|
from .iter_history import IterHistory
|
||||||
from .retract_vote import RetractVote
|
from .retract_vote import RetractVote
|
||||||
from .send_animation import SendAnimation
|
from .send_animation import SendAnimation
|
||||||
from .send_audio import SendAudio
|
from .send_audio import SendAudio
|
||||||
@ -69,7 +71,9 @@ class Messages(
|
|||||||
SendVoice,
|
SendVoice,
|
||||||
SendPoll,
|
SendPoll,
|
||||||
VotePoll,
|
VotePoll,
|
||||||
|
ClosePoll,
|
||||||
RetractVote,
|
RetractVote,
|
||||||
DownloadMedia
|
DownloadMedia,
|
||||||
|
IterHistory
|
||||||
):
|
):
|
||||||
pass
|
pass
|
||||||
|
65
pyrogram/client/methods/messages/close_poll.py
Normal file
65
pyrogram/client/methods/messages/close_poll.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
# Pyrogram - Telegram MTProto API Client Library for Python
|
||||||
|
# Copyright (C) 2017-2019 Dan Tès <https://github.com/delivrance>
|
||||||
|
#
|
||||||
|
# This file is part of Pyrogram.
|
||||||
|
#
|
||||||
|
# Pyrogram is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser General Public License as published
|
||||||
|
# by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Pyrogram is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
|
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
from pyrogram.api import functions, types
|
||||||
|
from pyrogram.client.ext import BaseClient
|
||||||
|
|
||||||
|
|
||||||
|
class ClosePoll(BaseClient):
|
||||||
|
def close_poll(self,
|
||||||
|
chat_id: Union[int, str],
|
||||||
|
message_id: id) -> bool:
|
||||||
|
"""Use this method to close (stop) a poll.
|
||||||
|
|
||||||
|
Closed polls can't be reopened and nobody will be able to vote in it anymore.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
chat_id (``int`` | ``str``):
|
||||||
|
Unique identifier (int) or username (str) of the target chat.
|
||||||
|
For your personal cloud (Saved Messages) you can simply use "me" or "self".
|
||||||
|
For a contact that exists in your Telegram address book you can use his phone number (str).
|
||||||
|
|
||||||
|
message_id (``int``):
|
||||||
|
Unique poll message identifier inside this chat.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
On success, True is returned.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
|
"""
|
||||||
|
poll = self.get_messages(chat_id, message_id).poll
|
||||||
|
|
||||||
|
self.send(
|
||||||
|
functions.messages.EditMessage(
|
||||||
|
peer=self.resolve_peer(chat_id),
|
||||||
|
id=message_id,
|
||||||
|
media=types.InputMediaPoll(
|
||||||
|
poll=types.Poll(
|
||||||
|
id=poll.id,
|
||||||
|
closed=True,
|
||||||
|
question="",
|
||||||
|
answers=[]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return True
|
@ -72,6 +72,7 @@ class DownloadMedia(BaseClient):
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
On success, the absolute path of the downloaded file as string is returned, None otherwise.
|
On success, the absolute path of the downloaded file as string is returned, None otherwise.
|
||||||
|
In case the download is deliberately stopped with :meth:`stop_transmission`, None is returned as well.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
|
@ -16,12 +16,17 @@
|
|||||||
# You should have received a copy of the GNU Lesser General Public License
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
import pyrogram
|
import pyrogram
|
||||||
from pyrogram.api import functions
|
from pyrogram.api import functions
|
||||||
|
from pyrogram.api.errors import FloodWait
|
||||||
from ...ext import BaseClient
|
from ...ext import BaseClient
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class GetHistory(BaseClient):
|
class GetHistory(BaseClient):
|
||||||
def get_history(self,
|
def get_history(self,
|
||||||
@ -30,10 +35,11 @@ class GetHistory(BaseClient):
|
|||||||
offset: int = 0,
|
offset: int = 0,
|
||||||
offset_id: int = 0,
|
offset_id: int = 0,
|
||||||
offset_date: int = 0,
|
offset_date: int = 0,
|
||||||
reversed: bool = False):
|
reverse: bool = False):
|
||||||
"""Use this method to retrieve the history of a chat.
|
"""Use this method to retrieve a chunk of the history of a chat.
|
||||||
|
|
||||||
You can get up to 100 messages at once.
|
You can get up to 100 messages at once.
|
||||||
|
For a more convenient way of getting a chat history see :meth:`iter_history`.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
chat_id (``int`` | ``str``):
|
chat_id (``int`` | ``str``):
|
||||||
@ -55,7 +61,7 @@ class GetHistory(BaseClient):
|
|||||||
offset_date (``int``, *optional*):
|
offset_date (``int``, *optional*):
|
||||||
Pass a date in Unix time as offset to retrieve only older messages starting from that date.
|
Pass a date in Unix time as offset to retrieve only older messages starting from that date.
|
||||||
|
|
||||||
reversed (``bool``, *optional*):
|
reverse (``bool``, *optional*):
|
||||||
Pass True to retrieve the messages in reversed order (from older to most recent).
|
Pass True to retrieve the messages in reversed order (from older to most recent).
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@ -65,6 +71,8 @@ class GetHistory(BaseClient):
|
|||||||
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
messages = pyrogram.Messages._parse(
|
messages = pyrogram.Messages._parse(
|
||||||
self,
|
self,
|
||||||
self.send(
|
self.send(
|
||||||
@ -72,7 +80,7 @@ class GetHistory(BaseClient):
|
|||||||
peer=self.resolve_peer(chat_id),
|
peer=self.resolve_peer(chat_id),
|
||||||
offset_id=offset_id,
|
offset_id=offset_id,
|
||||||
offset_date=offset_date,
|
offset_date=offset_date,
|
||||||
add_offset=offset - (limit if reversed else 0),
|
add_offset=offset * (-1 if reverse else 1) - (limit if reverse else 0),
|
||||||
limit=limit,
|
limit=limit,
|
||||||
max_id=0,
|
max_id=0,
|
||||||
min_id=0,
|
min_id=0,
|
||||||
@ -80,8 +88,13 @@ class GetHistory(BaseClient):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
except FloodWait as e:
|
||||||
|
log.warning("Sleeping for {}s".format(e.x))
|
||||||
|
time.sleep(e.x)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
if reversed:
|
if reverse:
|
||||||
messages.messages.reverse()
|
messages.messages.reverse()
|
||||||
|
|
||||||
return messages
|
return messages
|
||||||
|
@ -16,19 +16,24 @@
|
|||||||
# You should have received a copy of the GNU Lesser General Public License
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
from typing import Union, Iterable
|
from typing import Union, Iterable
|
||||||
|
|
||||||
import pyrogram
|
import pyrogram
|
||||||
from pyrogram.api import functions, types
|
from pyrogram.api import functions, types
|
||||||
|
from pyrogram.api.errors import FloodWait
|
||||||
from ...ext import BaseClient
|
from ...ext import BaseClient
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class GetMessages(BaseClient):
|
class GetMessages(BaseClient):
|
||||||
def get_messages(self,
|
def get_messages(self,
|
||||||
chat_id: Union[int, str],
|
chat_id: Union[int, str],
|
||||||
message_ids: Union[int, Iterable[int]] = None,
|
message_ids: Union[int, Iterable[int]] = None,
|
||||||
reply_to_message_ids: Union[int, Iterable[int]] = None,
|
reply_to_message_ids: Union[int, Iterable[int]] = None,
|
||||||
replies: int = 1) -> "pyrogram.Messages":
|
replies: int = 1) -> Union["pyrogram.Message", "pyrogram.Messages"]:
|
||||||
"""Use this method to get one or more messages that belong to a specific chat.
|
"""Use this method to get one or more messages that belong to a specific chat.
|
||||||
You can retrieve up to 200 messages at once.
|
You can retrieve up to 200 messages at once.
|
||||||
|
|
||||||
@ -78,6 +83,15 @@ class GetMessages(BaseClient):
|
|||||||
else:
|
else:
|
||||||
rpc = functions.messages.GetMessages(id=ids)
|
rpc = functions.messages.GetMessages(id=ids)
|
||||||
|
|
||||||
messages = pyrogram.Messages._parse(self, self.send(rpc), replies)
|
while True:
|
||||||
|
try:
|
||||||
|
r = self.send(rpc)
|
||||||
|
except FloodWait as e:
|
||||||
|
log.warning("Sleeping for {}s".format(e.x))
|
||||||
|
time.sleep(e.x)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
messages = pyrogram.Messages._parse(self, r, replies=replies)
|
||||||
|
|
||||||
return messages if is_iterable else messages.messages[0]
|
return messages if is_iterable else messages.messages[0]
|
||||||
|
93
pyrogram/client/methods/messages/iter_history.py
Normal file
93
pyrogram/client/methods/messages/iter_history.py
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
# Pyrogram - Telegram MTProto API Client Library for Python
|
||||||
|
# Copyright (C) 2017-2019 Dan Tès <https://github.com/delivrance>
|
||||||
|
#
|
||||||
|
# This file is part of Pyrogram.
|
||||||
|
#
|
||||||
|
# Pyrogram is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser General Public License as published
|
||||||
|
# by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Pyrogram is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
|
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from typing import Union, Generator
|
||||||
|
|
||||||
|
import pyrogram
|
||||||
|
from ...ext import BaseClient
|
||||||
|
|
||||||
|
|
||||||
|
class IterHistory(BaseClient):
|
||||||
|
def iter_history(self,
|
||||||
|
chat_id: Union[int, str],
|
||||||
|
limit: int = 0,
|
||||||
|
offset: int = 0,
|
||||||
|
offset_id: int = 0,
|
||||||
|
offset_date: int = 0,
|
||||||
|
reverse: bool = False) -> Generator["pyrogram.Message", None, None]:
|
||||||
|
"""Use this method to iterate through a chat history sequentially.
|
||||||
|
|
||||||
|
This convenience method does the same as repeatedly calling :meth:`get_history` in a loop, thus saving you from
|
||||||
|
the hassle of setting up boilerplate code. It is useful for getting the whole chat history with a single call.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
chat_id (``int`` | ``str``):
|
||||||
|
Unique identifier (int) or username (str) of the target chat.
|
||||||
|
For your personal cloud (Saved Messages) you can simply use "me" or "self".
|
||||||
|
For a contact that exists in your Telegram address book you can use his phone number (str).
|
||||||
|
|
||||||
|
limit (``int``, *optional*):
|
||||||
|
Limits the number of messages to be retrieved.
|
||||||
|
By default, no limit is applied and all messages are returned.
|
||||||
|
|
||||||
|
offset (``int``, *optional*):
|
||||||
|
Sequential number of the first message to be returned..
|
||||||
|
Negative values are also accepted and become useful in case you set offset_id or offset_date.
|
||||||
|
|
||||||
|
offset_id (``int``, *optional*):
|
||||||
|
Identifier of the first message to be returned.
|
||||||
|
|
||||||
|
offset_date (``int``, *optional*):
|
||||||
|
Pass a date in Unix time as offset to retrieve only older messages starting from that date.
|
||||||
|
|
||||||
|
reverse (``bool``, *optional*):
|
||||||
|
Pass True to retrieve the messages in reversed order (from older to most recent).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A generator yielding :obj:`Message <pyrogram.Message>` objects.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
|
"""
|
||||||
|
offset_id = offset_id or (1 if reverse else 0)
|
||||||
|
current = 0
|
||||||
|
total = limit or (1 << 31) - 1
|
||||||
|
limit = min(100, total)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
messages = self.get_history(
|
||||||
|
chat_id=chat_id,
|
||||||
|
limit=limit,
|
||||||
|
offset=offset,
|
||||||
|
offset_id=offset_id,
|
||||||
|
offset_date=offset_date,
|
||||||
|
reverse=reverse
|
||||||
|
).messages
|
||||||
|
|
||||||
|
if not messages:
|
||||||
|
return
|
||||||
|
|
||||||
|
offset_id = messages[-1].message_id + (1 if reverse else 0)
|
||||||
|
|
||||||
|
for message in messages:
|
||||||
|
yield message
|
||||||
|
|
||||||
|
current += 1
|
||||||
|
|
||||||
|
if current >= total:
|
||||||
|
return
|
@ -45,7 +45,7 @@ class SendAnimation(BaseClient):
|
|||||||
"pyrogram.ReplyKeyboardRemove",
|
"pyrogram.ReplyKeyboardRemove",
|
||||||
"pyrogram.ForceReply"] = None,
|
"pyrogram.ForceReply"] = None,
|
||||||
progress: callable = None,
|
progress: callable = None,
|
||||||
progress_args: tuple = ()) -> "pyrogram.Message":
|
progress_args: tuple = ()) -> Union["pyrogram.Message", None]:
|
||||||
"""Use this method to send animation files (animation or H.264/MPEG-4 AVC video without sound).
|
"""Use this method to send animation files (animation or H.264/MPEG-4 AVC video without sound).
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -119,6 +119,7 @@ class SendAnimation(BaseClient):
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
On success, the sent :obj:`Message <pyrogram.Message>` is returned.
|
On success, the sent :obj:`Message <pyrogram.Message>` is returned.
|
||||||
|
In case the upload is deliberately stopped with :meth:`stop_transmission`, None is returned instead.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
@ -126,6 +127,7 @@ class SendAnimation(BaseClient):
|
|||||||
file = None
|
file = None
|
||||||
style = self.html if parse_mode.lower() == "html" else self.markdown
|
style = self.html if parse_mode.lower() == "html" else self.markdown
|
||||||
|
|
||||||
|
try:
|
||||||
if os.path.exists(animation):
|
if os.path.exists(animation):
|
||||||
thumb = None if thumb is None else self.save_file(thumb)
|
thumb = None if thumb is None else self.save_file(thumb)
|
||||||
file = self.save_file(animation, progress=progress, progress_args=progress_args)
|
file = self.save_file(animation, progress=progress, progress_args=progress_args)
|
||||||
@ -195,3 +197,5 @@ class SendAnimation(BaseClient):
|
|||||||
{i.id: i for i in r.users},
|
{i.id: i for i in r.users},
|
||||||
{i.id: i for i in r.chats}
|
{i.id: i for i in r.chats}
|
||||||
)
|
)
|
||||||
|
except BaseClient.StopTransmission:
|
||||||
|
return None
|
||||||
|
@ -45,7 +45,7 @@ class SendAudio(BaseClient):
|
|||||||
"pyrogram.ReplyKeyboardRemove",
|
"pyrogram.ReplyKeyboardRemove",
|
||||||
"pyrogram.ForceReply"] = None,
|
"pyrogram.ForceReply"] = None,
|
||||||
progress: callable = None,
|
progress: callable = None,
|
||||||
progress_args: tuple = ()) -> "pyrogram.Message":
|
progress_args: tuple = ()) -> Union["pyrogram.Message", None]:
|
||||||
"""Use this method to send audio files.
|
"""Use this method to send audio files.
|
||||||
|
|
||||||
For sending voice messages, use the :obj:`send_voice()` method instead.
|
For sending voice messages, use the :obj:`send_voice()` method instead.
|
||||||
@ -121,6 +121,7 @@ class SendAudio(BaseClient):
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
On success, the sent :obj:`Message <pyrogram.Message>` is returned.
|
On success, the sent :obj:`Message <pyrogram.Message>` is returned.
|
||||||
|
In case the upload is deliberately stopped with :meth:`stop_transmission`, None is returned instead.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
@ -128,6 +129,7 @@ class SendAudio(BaseClient):
|
|||||||
file = None
|
file = None
|
||||||
style = self.html if parse_mode.lower() == "html" else self.markdown
|
style = self.html if parse_mode.lower() == "html" else self.markdown
|
||||||
|
|
||||||
|
try:
|
||||||
if os.path.exists(audio):
|
if os.path.exists(audio):
|
||||||
thumb = None if thumb is None else self.save_file(thumb)
|
thumb = None if thumb is None else self.save_file(thumb)
|
||||||
file = self.save_file(audio, progress=progress, progress_args=progress_args)
|
file = self.save_file(audio, progress=progress, progress_args=progress_args)
|
||||||
@ -195,3 +197,5 @@ class SendAudio(BaseClient):
|
|||||||
{i.id: i for i in r.users},
|
{i.id: i for i in r.users},
|
||||||
{i.id: i for i in r.chats}
|
{i.id: i for i in r.chats}
|
||||||
)
|
)
|
||||||
|
except BaseClient.StopTransmission:
|
||||||
|
return None
|
||||||
|
@ -42,7 +42,7 @@ class SendDocument(BaseClient):
|
|||||||
"pyrogram.ReplyKeyboardRemove",
|
"pyrogram.ReplyKeyboardRemove",
|
||||||
"pyrogram.ForceReply"] = None,
|
"pyrogram.ForceReply"] = None,
|
||||||
progress: callable = None,
|
progress: callable = None,
|
||||||
progress_args: tuple = ()) -> "pyrogram.Message":
|
progress_args: tuple = ()) -> Union["pyrogram.Message", None]:
|
||||||
"""Use this method to send general files.
|
"""Use this method to send general files.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -107,6 +107,7 @@ class SendDocument(BaseClient):
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
On success, the sent :obj:`Message <pyrogram.Message>` is returned.
|
On success, the sent :obj:`Message <pyrogram.Message>` is returned.
|
||||||
|
In case the upload is deliberately stopped with :meth:`stop_transmission`, None is returned instead.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
@ -114,6 +115,7 @@ class SendDocument(BaseClient):
|
|||||||
file = None
|
file = None
|
||||||
style = self.html if parse_mode.lower() == "html" else self.markdown
|
style = self.html if parse_mode.lower() == "html" else self.markdown
|
||||||
|
|
||||||
|
try:
|
||||||
if os.path.exists(document):
|
if os.path.exists(document):
|
||||||
thumb = None if thumb is None else self.save_file(thumb)
|
thumb = None if thumb is None else self.save_file(thumb)
|
||||||
file = self.save_file(document, progress=progress, progress_args=progress_args)
|
file = self.save_file(document, progress=progress, progress_args=progress_args)
|
||||||
@ -176,3 +178,5 @@ class SendDocument(BaseClient):
|
|||||||
{i.id: i for i in r.users},
|
{i.id: i for i in r.users},
|
||||||
{i.id: i for i in r.chats}
|
{i.id: i for i in r.chats}
|
||||||
)
|
)
|
||||||
|
except BaseClient.StopTransmission:
|
||||||
|
return None
|
||||||
|
@ -88,10 +88,18 @@ class SendMessage(BaseClient):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if isinstance(r, types.UpdateShortSentMessage):
|
if isinstance(r, types.UpdateShortSentMessage):
|
||||||
|
peer = self.resolve_peer(chat_id)
|
||||||
|
|
||||||
|
peer_id = (
|
||||||
|
peer.user_id
|
||||||
|
if isinstance(peer, types.InputPeerUser)
|
||||||
|
else -peer.chat_id
|
||||||
|
)
|
||||||
|
|
||||||
return pyrogram.Message(
|
return pyrogram.Message(
|
||||||
message_id=r.id,
|
message_id=r.id,
|
||||||
chat=pyrogram.Chat(
|
chat=pyrogram.Chat(
|
||||||
id=list(self.resolve_peer(chat_id).__dict__.values())[0],
|
id=peer_id,
|
||||||
type="private",
|
type="private",
|
||||||
client=self
|
client=self
|
||||||
),
|
),
|
||||||
|
@ -41,7 +41,7 @@ class SendPhoto(BaseClient):
|
|||||||
"pyrogram.ReplyKeyboardRemove",
|
"pyrogram.ReplyKeyboardRemove",
|
||||||
"pyrogram.ForceReply"] = None,
|
"pyrogram.ForceReply"] = None,
|
||||||
progress: callable = None,
|
progress: callable = None,
|
||||||
progress_args: tuple = ()) -> "pyrogram.Message":
|
progress_args: tuple = ()) -> Union["pyrogram.Message", None]:
|
||||||
"""Use this method to send photos.
|
"""Use this method to send photos.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -105,6 +105,7 @@ class SendPhoto(BaseClient):
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
On success, the sent :obj:`Message <pyrogram.Message>` is returned.
|
On success, the sent :obj:`Message <pyrogram.Message>` is returned.
|
||||||
|
In case the upload is deliberately stopped with :meth:`stop_transmission`, None is returned instead.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
@ -112,6 +113,7 @@ class SendPhoto(BaseClient):
|
|||||||
file = None
|
file = None
|
||||||
style = self.html if parse_mode.lower() == "html" else self.markdown
|
style = self.html if parse_mode.lower() == "html" else self.markdown
|
||||||
|
|
||||||
|
try:
|
||||||
if os.path.exists(photo):
|
if os.path.exists(photo):
|
||||||
file = self.save_file(photo, progress=progress, progress_args=progress_args)
|
file = self.save_file(photo, progress=progress, progress_args=progress_args)
|
||||||
media = types.InputMediaUploadedPhoto(
|
media = types.InputMediaUploadedPhoto(
|
||||||
@ -171,3 +173,5 @@ class SendPhoto(BaseClient):
|
|||||||
{i.id: i for i in r.users},
|
{i.id: i for i in r.users},
|
||||||
{i.id: i for i in r.chats}
|
{i.id: i for i in r.chats}
|
||||||
)
|
)
|
||||||
|
except BaseClient.StopTransmission:
|
||||||
|
return None
|
||||||
|
@ -38,7 +38,7 @@ class SendSticker(BaseClient):
|
|||||||
"pyrogram.ReplyKeyboardRemove",
|
"pyrogram.ReplyKeyboardRemove",
|
||||||
"pyrogram.ForceReply"] = None,
|
"pyrogram.ForceReply"] = None,
|
||||||
progress: callable = None,
|
progress: callable = None,
|
||||||
progress_args: tuple = ()) -> "pyrogram.Message":
|
progress_args: tuple = ()) -> Union["pyrogram.Message", None]:
|
||||||
"""Use this method to send .webp stickers.
|
"""Use this method to send .webp stickers.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -89,12 +89,14 @@ class SendSticker(BaseClient):
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
On success, the sent :obj:`Message <pyrogram.Message>` is returned.
|
On success, the sent :obj:`Message <pyrogram.Message>` is returned.
|
||||||
|
In case the upload is deliberately stopped with :meth:`stop_transmission`, None is returned instead.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
"""
|
"""
|
||||||
file = None
|
file = None
|
||||||
|
|
||||||
|
try:
|
||||||
if os.path.exists(sticker):
|
if os.path.exists(sticker):
|
||||||
file = self.save_file(sticker, progress=progress, progress_args=progress_args)
|
file = self.save_file(sticker, progress=progress, progress_args=progress_args)
|
||||||
media = types.InputMediaUploadedDocument(
|
media = types.InputMediaUploadedDocument(
|
||||||
@ -155,3 +157,5 @@ class SendSticker(BaseClient):
|
|||||||
{i.id: i for i in r.users},
|
{i.id: i for i in r.users},
|
||||||
{i.id: i for i in r.chats}
|
{i.id: i for i in r.chats}
|
||||||
)
|
)
|
||||||
|
except BaseClient.StopTransmission:
|
||||||
|
return None
|
||||||
|
@ -46,7 +46,7 @@ class SendVideo(BaseClient):
|
|||||||
"pyrogram.ReplyKeyboardRemove",
|
"pyrogram.ReplyKeyboardRemove",
|
||||||
"pyrogram.ForceReply"] = None,
|
"pyrogram.ForceReply"] = None,
|
||||||
progress: callable = None,
|
progress: callable = None,
|
||||||
progress_args: tuple = ()) -> "pyrogram.Message":
|
progress_args: tuple = ()) -> Union["pyrogram.Message", None]:
|
||||||
"""Use this method to send video files.
|
"""Use this method to send video files.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -123,6 +123,7 @@ class SendVideo(BaseClient):
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
On success, the sent :obj:`Message <pyrogram.Message>` is returned.
|
On success, the sent :obj:`Message <pyrogram.Message>` is returned.
|
||||||
|
In case the upload is deliberately stopped with :meth:`stop_transmission`, None is returned instead.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
@ -130,6 +131,7 @@ class SendVideo(BaseClient):
|
|||||||
file = None
|
file = None
|
||||||
style = self.html if parse_mode.lower() == "html" else self.markdown
|
style = self.html if parse_mode.lower() == "html" else self.markdown
|
||||||
|
|
||||||
|
try:
|
||||||
if os.path.exists(video):
|
if os.path.exists(video):
|
||||||
thumb = None if thumb is None else self.save_file(thumb)
|
thumb = None if thumb is None else self.save_file(thumb)
|
||||||
file = self.save_file(video, progress=progress, progress_args=progress_args)
|
file = self.save_file(video, progress=progress, progress_args=progress_args)
|
||||||
@ -198,3 +200,5 @@ class SendVideo(BaseClient):
|
|||||||
{i.id: i for i in r.users},
|
{i.id: i for i in r.users},
|
||||||
{i.id: i for i in r.chats}
|
{i.id: i for i in r.chats}
|
||||||
)
|
)
|
||||||
|
except BaseClient.StopTransmission:
|
||||||
|
return None
|
||||||
|
@ -42,7 +42,7 @@ class SendVideoNote(BaseClient):
|
|||||||
"pyrogram.ReplyKeyboardRemove",
|
"pyrogram.ReplyKeyboardRemove",
|
||||||
"pyrogram.ForceReply"] = None,
|
"pyrogram.ForceReply"] = None,
|
||||||
progress: callable = None,
|
progress: callable = None,
|
||||||
progress_args: tuple = ()) -> "pyrogram.Message":
|
progress_args: tuple = ()) -> Union["pyrogram.Message", None]:
|
||||||
"""Use this method to send video messages.
|
"""Use this method to send video messages.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -105,12 +105,14 @@ class SendVideoNote(BaseClient):
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
On success, the sent :obj:`Message <pyrogram.Message>` is returned.
|
On success, the sent :obj:`Message <pyrogram.Message>` is returned.
|
||||||
|
In case the upload is deliberately stopped with :meth:`stop_transmission`, None is returned instead.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
"""
|
"""
|
||||||
file = None
|
file = None
|
||||||
|
|
||||||
|
try:
|
||||||
if os.path.exists(video_note):
|
if os.path.exists(video_note):
|
||||||
thumb = None if thumb is None else self.save_file(thumb)
|
thumb = None if thumb is None else self.save_file(thumb)
|
||||||
file = self.save_file(video_note, progress=progress, progress_args=progress_args)
|
file = self.save_file(video_note, progress=progress, progress_args=progress_args)
|
||||||
@ -174,3 +176,5 @@ class SendVideoNote(BaseClient):
|
|||||||
{i.id: i for i in r.users},
|
{i.id: i for i in r.users},
|
||||||
{i.id: i for i in r.chats}
|
{i.id: i for i in r.chats}
|
||||||
)
|
)
|
||||||
|
except BaseClient.StopTransmission:
|
||||||
|
return None
|
||||||
|
@ -42,7 +42,7 @@ class SendVoice(BaseClient):
|
|||||||
"pyrogram.ReplyKeyboardRemove",
|
"pyrogram.ReplyKeyboardRemove",
|
||||||
"pyrogram.ForceReply"] = None,
|
"pyrogram.ForceReply"] = None,
|
||||||
progress: callable = None,
|
progress: callable = None,
|
||||||
progress_args: tuple = ()) -> "pyrogram.Message":
|
progress_args: tuple = ()) -> Union["pyrogram.Message", None]:
|
||||||
"""Use this method to send audio files.
|
"""Use this method to send audio files.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -104,6 +104,7 @@ class SendVoice(BaseClient):
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
On success, the sent :obj:`Message <pyrogram.Message>` is returned.
|
On success, the sent :obj:`Message <pyrogram.Message>` is returned.
|
||||||
|
In case the upload is deliberately stopped with :meth:`stop_transmission`, None is returned instead.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
@ -111,6 +112,7 @@ class SendVoice(BaseClient):
|
|||||||
file = None
|
file = None
|
||||||
style = self.html if parse_mode.lower() == "html" else self.markdown
|
style = self.html if parse_mode.lower() == "html" else self.markdown
|
||||||
|
|
||||||
|
try:
|
||||||
if os.path.exists(voice):
|
if os.path.exists(voice):
|
||||||
file = self.save_file(voice, progress=progress, progress_args=progress_args)
|
file = self.save_file(voice, progress=progress, progress_args=progress_args)
|
||||||
media = types.InputMediaUploadedDocument(
|
media = types.InputMediaUploadedDocument(
|
||||||
@ -174,3 +176,5 @@ class SendVoice(BaseClient):
|
|||||||
{i.id: i for i in r.users},
|
{i.id: i for i in r.users},
|
||||||
{i.id: i for i in r.chats}
|
{i.id: i for i in r.chats}
|
||||||
)
|
)
|
||||||
|
except BaseClient.StopTransmission:
|
||||||
|
return None
|
||||||
|
@ -38,12 +38,12 @@ class HTML:
|
|||||||
def __init__(self, peers_by_id):
|
def __init__(self, peers_by_id):
|
||||||
self.peers_by_id = peers_by_id
|
self.peers_by_id = peers_by_id
|
||||||
|
|
||||||
def parse(self, text):
|
def parse(self, message: str):
|
||||||
entities = []
|
entities = []
|
||||||
text = utils.add_surrogates(text)
|
message = utils.add_surrogates(str(message))
|
||||||
offset = 0
|
offset = 0
|
||||||
|
|
||||||
for match in self.HTML_RE.finditer(text):
|
for match in self.HTML_RE.finditer(message):
|
||||||
start = match.start() - offset
|
start = match.start() - offset
|
||||||
style, url, body = match.group(1, 3, 4)
|
style, url, body = match.group(1, 3, 4)
|
||||||
|
|
||||||
@ -73,12 +73,12 @@ class HTML:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
entities.append(entity)
|
entities.append(entity)
|
||||||
text = text.replace(match.group(), body)
|
message = message.replace(match.group(), body)
|
||||||
offset += len(style) * 2 + 5 + (len(url) + 8 if url else 0)
|
offset += len(style) * 2 + 5 + (len(url) + 8 if url else 0)
|
||||||
|
|
||||||
# TODO: OrderedDict to be removed in Python3.6
|
# TODO: OrderedDict to be removed in Python3.6
|
||||||
return OrderedDict([
|
return OrderedDict([
|
||||||
("message", utils.remove_surrogates(text)),
|
("message", utils.remove_surrogates(message)),
|
||||||
("entities", entities)
|
("entities", entities)
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@ class Markdown:
|
|||||||
self.peers_by_id = peers_by_id
|
self.peers_by_id = peers_by_id
|
||||||
|
|
||||||
def parse(self, message: str):
|
def parse(self, message: str):
|
||||||
message = utils.add_surrogates(message).strip()
|
message = utils.add_surrogates(str(message)).strip()
|
||||||
entities = []
|
entities = []
|
||||||
offset = 0
|
offset = 0
|
||||||
|
|
||||||
|
@ -18,11 +18,8 @@
|
|||||||
|
|
||||||
from .bots import (
|
from .bots import (
|
||||||
CallbackQuery, ForceReply, InlineKeyboardButton, InlineKeyboardMarkup,
|
CallbackQuery, ForceReply, InlineKeyboardButton, InlineKeyboardMarkup,
|
||||||
KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove
|
KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove, CallbackGame,
|
||||||
)
|
GameHighScore, GameHighScores
|
||||||
from .bots import (
|
|
||||||
ForceReply, InlineKeyboardButton, InlineKeyboardMarkup,
|
|
||||||
KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove
|
|
||||||
)
|
)
|
||||||
from .input_media import (
|
from .input_media import (
|
||||||
InputMediaAudio, InputPhoneContact, InputMediaVideo, InputMediaPhoto,
|
InputMediaAudio, InputPhoneContact, InputMediaVideo, InputMediaPhoto,
|
||||||
@ -31,10 +28,10 @@ from .input_media import (
|
|||||||
from .messages_and_media import (
|
from .messages_and_media import (
|
||||||
Audio, Contact, Document, Animation, Location, Photo, PhotoSize,
|
Audio, Contact, Document, Animation, Location, Photo, PhotoSize,
|
||||||
Sticker, Venue, Video, VideoNote, Voice, UserProfilePhotos,
|
Sticker, Venue, Video, VideoNote, Voice, UserProfilePhotos,
|
||||||
Message, Messages, MessageEntity, Poll, PollOption
|
Message, Messages, MessageEntity, Poll, PollOption, Game
|
||||||
)
|
)
|
||||||
|
from .update import StopPropagation, ContinuePropagation
|
||||||
from .user_and_chats import (
|
from .user_and_chats import (
|
||||||
Chat, ChatMember, ChatMembers, ChatPhoto,
|
Chat, ChatMember, ChatMembers, ChatPhoto,
|
||||||
Dialog, Dialogs, User, UserStatus, ChatPreview
|
Dialog, Dialogs, User, UserStatus, ChatPreview
|
||||||
)
|
)
|
||||||
from .update import StopPropagation
|
|
||||||
|
@ -16,8 +16,11 @@
|
|||||||
# You should have received a copy of the GNU Lesser General Public License
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from .callback_game import CallbackGame
|
||||||
from .callback_query import CallbackQuery
|
from .callback_query import CallbackQuery
|
||||||
from .force_reply import ForceReply
|
from .force_reply import ForceReply
|
||||||
|
from .game_high_score import GameHighScore
|
||||||
|
from .game_high_scores import GameHighScores
|
||||||
from .inline_keyboard_button import InlineKeyboardButton
|
from .inline_keyboard_button import InlineKeyboardButton
|
||||||
from .inline_keyboard_markup import InlineKeyboardMarkup
|
from .inline_keyboard_markup import InlineKeyboardMarkup
|
||||||
from .keyboard_button import KeyboardButton
|
from .keyboard_button import KeyboardButton
|
||||||
|
29
pyrogram/client/types/bots/callback_game.py
Normal file
29
pyrogram/client/types/bots/callback_game.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# Pyrogram - Telegram MTProto API Client Library for Python
|
||||||
|
# Copyright (C) 2017-2019 Dan Tès <https://github.com/delivrance>
|
||||||
|
#
|
||||||
|
# This file is part of Pyrogram.
|
||||||
|
#
|
||||||
|
# Pyrogram is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser General Public License as published
|
||||||
|
# by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Pyrogram is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
|
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from ..pyrogram_type import PyrogramType
|
||||||
|
|
||||||
|
|
||||||
|
class CallbackGame(PyrogramType):
|
||||||
|
"""A placeholder, currently holds no information.
|
||||||
|
|
||||||
|
Use BotFather to set up your game.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(None)
|
@ -40,15 +40,15 @@ class CallbackQuery(PyrogramType, Update):
|
|||||||
Sender.
|
Sender.
|
||||||
|
|
||||||
chat_instance (``str``, *optional*):
|
chat_instance (``str``, *optional*):
|
||||||
|
Global identifier, uniquely corresponding to the chat to which the message with the callback button was
|
||||||
|
sent. Useful for high scores in games.
|
||||||
|
|
||||||
|
message (:obj:`Message <pyrogram.Message>`, *optional*):
|
||||||
Message with the callback button that originated the query. Note that message content and message date will
|
Message with the callback button that originated the query. Note that message content and message date will
|
||||||
not be available if the message is too old.
|
not be available if the message is too old.
|
||||||
|
|
||||||
message (:obj:`Message <pyrogram.Message>`, *optional*):
|
|
||||||
Identifier of the message sent via the bot in inline mode, that originated the query.
|
|
||||||
|
|
||||||
inline_message_id (``str``):
|
inline_message_id (``str``):
|
||||||
Global identifier, uniquely corresponding to the chat to which the message with the callback button was
|
Identifier of the message sent via the bot in inline mode, that originated the query.
|
||||||
sent. Useful for high scores in games.
|
|
||||||
|
|
||||||
data (``bytes``, *optional*):
|
data (``bytes``, *optional*):
|
||||||
Data associated with the callback button. Be aware that a bad client can send arbitrary data in this field.
|
Data associated with the callback button. Be aware that a bad client can send arbitrary data in this field.
|
||||||
@ -72,9 +72,9 @@ class CallbackQuery(PyrogramType, Update):
|
|||||||
|
|
||||||
self.id = id
|
self.id = id
|
||||||
self.from_user = from_user
|
self.from_user = from_user
|
||||||
|
self.chat_instance = chat_instance
|
||||||
self.message = message
|
self.message = message
|
||||||
self.inline_message_id = inline_message_id
|
self.inline_message_id = inline_message_id
|
||||||
self.chat_instance = chat_instance
|
|
||||||
self.data = data
|
self.data = data
|
||||||
self.game_short_name = game_short_name
|
self.game_short_name = game_short_name
|
||||||
|
|
||||||
|
69
pyrogram/client/types/bots/game_high_score.py
Normal file
69
pyrogram/client/types/bots/game_high_score.py
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
# Pyrogram - Telegram MTProto API Client Library for Python
|
||||||
|
# Copyright (C) 2017-2019 Dan Tès <https://github.com/delivrance>
|
||||||
|
#
|
||||||
|
# This file is part of Pyrogram.
|
||||||
|
#
|
||||||
|
# Pyrogram is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser General Public License as published
|
||||||
|
# by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Pyrogram is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
|
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import pyrogram
|
||||||
|
|
||||||
|
from pyrogram.api import types
|
||||||
|
from pyrogram.client.types.pyrogram_type import PyrogramType
|
||||||
|
from pyrogram.client.types.user_and_chats import User
|
||||||
|
|
||||||
|
|
||||||
|
class GameHighScore(PyrogramType):
|
||||||
|
"""This object represents one row of the high scores table for a game.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user (:obj:`User`):
|
||||||
|
User.
|
||||||
|
|
||||||
|
score (``int``):
|
||||||
|
Score.
|
||||||
|
|
||||||
|
position (``position``, *optional*):
|
||||||
|
Position in high score table for the game.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
*,
|
||||||
|
client: "pyrogram.client.ext.BaseClient",
|
||||||
|
user: User,
|
||||||
|
score: int,
|
||||||
|
position: int = None):
|
||||||
|
super().__init__(client)
|
||||||
|
|
||||||
|
self.user = user
|
||||||
|
self.score = score
|
||||||
|
self.position = position
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _parse(client, game_high_score: types.HighScore, users: dict) -> "GameHighScore":
|
||||||
|
users = {i.id: i for i in users}
|
||||||
|
|
||||||
|
return GameHighScore(
|
||||||
|
user=User._parse(client, users[game_high_score.user_id]),
|
||||||
|
score=game_high_score.score,
|
||||||
|
position=game_high_score.pos,
|
||||||
|
client=client
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _parse_action(client, service: types.MessageService, users: dict):
|
||||||
|
return GameHighScore(
|
||||||
|
user=User._parse(client, users[service.from_id]),
|
||||||
|
score=service.action.score,
|
||||||
|
client=client
|
||||||
|
)
|
56
pyrogram/client/types/bots/game_high_scores.py
Normal file
56
pyrogram/client/types/bots/game_high_scores.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# Pyrogram - Telegram MTProto API Client Library for Python
|
||||||
|
# Copyright (C) 2017-2019 Dan Tès <https://github.com/delivrance>
|
||||||
|
#
|
||||||
|
# This file is part of Pyrogram.
|
||||||
|
#
|
||||||
|
# Pyrogram is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser General Public License as published
|
||||||
|
# by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Pyrogram is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
|
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
import pyrogram
|
||||||
|
from pyrogram.api import types
|
||||||
|
from pyrogram.client.types.pyrogram_type import PyrogramType
|
||||||
|
from .game_high_score import GameHighScore
|
||||||
|
|
||||||
|
|
||||||
|
class GameHighScores(PyrogramType):
|
||||||
|
"""This object represents the high scores table for a game.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
total_count (``int``):
|
||||||
|
Total number of scores the target game has.
|
||||||
|
|
||||||
|
game_high_scores (List of :obj:`GameHighScore <pyrogram.GameHighScore>`):
|
||||||
|
Game scores.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
*,
|
||||||
|
client: "pyrogram.client.ext.BaseClient",
|
||||||
|
total_count: int,
|
||||||
|
game_high_scores: List[GameHighScore]):
|
||||||
|
super().__init__(client)
|
||||||
|
|
||||||
|
self.total_count = total_count
|
||||||
|
self.game_high_scores = game_high_scores
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _parse(client, game_high_scores: types.messages.HighScores) -> "GameHighScores":
|
||||||
|
return GameHighScores(
|
||||||
|
total_count=len(game_high_scores.scores),
|
||||||
|
game_high_scores=[
|
||||||
|
GameHighScore._parse(client, score, game_high_scores.users)
|
||||||
|
for score in game_high_scores.scores],
|
||||||
|
client=client
|
||||||
|
)
|
@ -18,8 +18,9 @@
|
|||||||
|
|
||||||
from pyrogram.api.types import (
|
from pyrogram.api.types import (
|
||||||
KeyboardButtonUrl, KeyboardButtonCallback,
|
KeyboardButtonUrl, KeyboardButtonCallback,
|
||||||
KeyboardButtonSwitchInline
|
KeyboardButtonSwitchInline, KeyboardButtonGame
|
||||||
)
|
)
|
||||||
|
from .callback_game import CallbackGame
|
||||||
from ..pyrogram_type import PyrogramType
|
from ..pyrogram_type import PyrogramType
|
||||||
|
|
||||||
|
|
||||||
@ -58,7 +59,8 @@ class InlineKeyboardButton(PyrogramType):
|
|||||||
callback_data: bytes = None,
|
callback_data: bytes = None,
|
||||||
url: str = None,
|
url: str = None,
|
||||||
switch_inline_query: str = None,
|
switch_inline_query: str = None,
|
||||||
switch_inline_query_current_chat: str = None):
|
switch_inline_query_current_chat: str = None,
|
||||||
|
callback_game: CallbackGame = None):
|
||||||
super().__init__(None)
|
super().__init__(None)
|
||||||
|
|
||||||
self.text = text
|
self.text = text
|
||||||
@ -66,7 +68,7 @@ class InlineKeyboardButton(PyrogramType):
|
|||||||
self.callback_data = callback_data
|
self.callback_data = callback_data
|
||||||
self.switch_inline_query = switch_inline_query
|
self.switch_inline_query = switch_inline_query
|
||||||
self.switch_inline_query_current_chat = switch_inline_query_current_chat
|
self.switch_inline_query_current_chat = switch_inline_query_current_chat
|
||||||
# self.callback_game = callback_game
|
self.callback_game = callback_game
|
||||||
# self.pay = pay
|
# self.pay = pay
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -95,6 +97,12 @@ class InlineKeyboardButton(PyrogramType):
|
|||||||
switch_inline_query=o.query
|
switch_inline_query=o.query
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if isinstance(o, KeyboardButtonGame):
|
||||||
|
return InlineKeyboardButton(
|
||||||
|
text=o.text,
|
||||||
|
callback_game=CallbackGame()
|
||||||
|
)
|
||||||
|
|
||||||
def write(self):
|
def write(self):
|
||||||
if self.callback_data:
|
if self.callback_data:
|
||||||
return KeyboardButtonCallback(self.text, self.callback_data)
|
return KeyboardButtonCallback(self.text, self.callback_data)
|
||||||
@ -107,3 +115,6 @@ class InlineKeyboardButton(PyrogramType):
|
|||||||
|
|
||||||
if self.switch_inline_query_current_chat:
|
if self.switch_inline_query_current_chat:
|
||||||
return KeyboardButtonSwitchInline(self.text, self.switch_inline_query_current_chat, same_peer=True)
|
return KeyboardButtonSwitchInline(self.text, self.switch_inline_query_current_chat, same_peer=True)
|
||||||
|
|
||||||
|
if self.callback_game:
|
||||||
|
return KeyboardButtonGame(self.text)
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
# You should have received a copy of the GNU Lesser General Public License
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from typing import List
|
from typing import List, Union
|
||||||
|
|
||||||
from pyrogram.api.types import KeyboardButtonRow
|
from pyrogram.api.types import KeyboardButtonRow
|
||||||
from pyrogram.api.types import ReplyKeyboardMarkup as RawReplyKeyboardMarkup
|
from pyrogram.api.types import ReplyKeyboardMarkup as RawReplyKeyboardMarkup
|
||||||
@ -50,7 +50,7 @@ class ReplyKeyboardMarkup(PyrogramType):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
keyboard: List[List[KeyboardButton]],
|
keyboard: List[List[Union[KeyboardButton, str]]],
|
||||||
resize_keyboard: bool = None,
|
resize_keyboard: bool = None,
|
||||||
one_time_keyboard: bool = None,
|
one_time_keyboard: bool = None,
|
||||||
selective: bool = None):
|
selective: bool = None):
|
||||||
|
@ -20,7 +20,7 @@ from . import InputMedia
|
|||||||
|
|
||||||
|
|
||||||
class InputMediaAudio(InputMedia):
|
class InputMediaAudio(InputMedia):
|
||||||
"""This object represents a video to be sent inside an album.
|
"""This object represents an audio to be sent inside an album.
|
||||||
It is intended to be used with :obj:`send_media_group() <pyrogram.Client.send_media_group>`.
|
It is intended to be used with :obj:`send_media_group() <pyrogram.Client.send_media_group>`.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -20,6 +20,7 @@ from .animation import Animation
|
|||||||
from .audio import Audio
|
from .audio import Audio
|
||||||
from .contact import Contact
|
from .contact import Contact
|
||||||
from .document import Document
|
from .document import Document
|
||||||
|
from .game import Game
|
||||||
from .location import Location
|
from .location import Location
|
||||||
from .message import Message
|
from .message import Message
|
||||||
from .message_entity import MessageEntity
|
from .message_entity import MessageEntity
|
||||||
|
98
pyrogram/client/types/messages_and_media/game.py
Normal file
98
pyrogram/client/types/messages_and_media/game.py
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
# Pyrogram - Telegram MTProto API Client Library for Python
|
||||||
|
# Copyright (C) 2017-2019 Dan Tès <https://github.com/delivrance>
|
||||||
|
#
|
||||||
|
# This file is part of Pyrogram.
|
||||||
|
#
|
||||||
|
# Pyrogram is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser General Public License as published
|
||||||
|
# by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Pyrogram is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
|
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import pyrogram
|
||||||
|
from pyrogram.api import types
|
||||||
|
from .animation import Animation
|
||||||
|
from .photo import Photo
|
||||||
|
from ..pyrogram_type import PyrogramType
|
||||||
|
|
||||||
|
|
||||||
|
class Game(PyrogramType):
|
||||||
|
"""This object represents a game.
|
||||||
|
Use BotFather to create and edit games, their short names will act as unique identifiers.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
id (``int``):
|
||||||
|
Unique identifier of the game.
|
||||||
|
|
||||||
|
title (``str``):
|
||||||
|
Title of the game.
|
||||||
|
|
||||||
|
short_name (``str``):
|
||||||
|
Unique short name of the game.
|
||||||
|
|
||||||
|
description (``str``):
|
||||||
|
Description of the game.
|
||||||
|
|
||||||
|
photo (:obj:`Photo <pyrogram.Photo>`):
|
||||||
|
Photo that will be displayed in the game message in chats.
|
||||||
|
|
||||||
|
animation (:obj:`Animation <pyrogram.Animation>`, *optional*):
|
||||||
|
Animation that will be displayed in the game message in chats.
|
||||||
|
Upload via BotFather.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
*,
|
||||||
|
client: "pyrogram.client.ext.BaseClient",
|
||||||
|
id: int,
|
||||||
|
title: str,
|
||||||
|
short_name: str,
|
||||||
|
description: str,
|
||||||
|
photo: Photo,
|
||||||
|
animation: Animation = None):
|
||||||
|
super().__init__(client)
|
||||||
|
|
||||||
|
self.id = id
|
||||||
|
self.title = title
|
||||||
|
self.short_name = short_name
|
||||||
|
self.description = description
|
||||||
|
self.photo = photo
|
||||||
|
self.animation = animation
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _parse(client, message: types.Message) -> "Game":
|
||||||
|
game = message.media.game # type: types.Game
|
||||||
|
animation = None
|
||||||
|
|
||||||
|
if game.document:
|
||||||
|
attributes = {type(i): i for i in game.document.attributes}
|
||||||
|
|
||||||
|
file_name = getattr(
|
||||||
|
attributes.get(
|
||||||
|
types.DocumentAttributeFilename, None
|
||||||
|
), "file_name", None
|
||||||
|
)
|
||||||
|
|
||||||
|
animation = Animation._parse(
|
||||||
|
client,
|
||||||
|
game.document,
|
||||||
|
attributes.get(types.DocumentAttributeVideo, None),
|
||||||
|
file_name
|
||||||
|
)
|
||||||
|
|
||||||
|
return Game(
|
||||||
|
id=game.id,
|
||||||
|
title=game.title,
|
||||||
|
short_name=game.short_name,
|
||||||
|
description=game.description,
|
||||||
|
photo=Photo._parse(client, game.photo),
|
||||||
|
animation=animation,
|
||||||
|
client=client
|
||||||
|
)
|
@ -121,6 +121,9 @@ class Message(PyrogramType, Update):
|
|||||||
animation (:obj:`Animation <pyrogram.Animation>`, *optional*):
|
animation (:obj:`Animation <pyrogram.Animation>`, *optional*):
|
||||||
Message is an animation, information about the animation.
|
Message is an animation, information about the animation.
|
||||||
|
|
||||||
|
game (:obj:`Game <pyrogram.Game>`, *optional*):
|
||||||
|
Message is a game, information about the game.
|
||||||
|
|
||||||
video (:obj:`Video <pyrogram.Video>`, *optional*):
|
video (:obj:`Video <pyrogram.Video>`, *optional*):
|
||||||
Message is a video, information about the video.
|
Message is a video, information about the video.
|
||||||
|
|
||||||
@ -199,6 +202,10 @@ class Message(PyrogramType, Update):
|
|||||||
Note that the Message object in this field will not contain further reply_to_message fields even if it
|
Note that the Message object in this field will not contain further reply_to_message fields even if it
|
||||||
is itself a reply.
|
is itself a reply.
|
||||||
|
|
||||||
|
game_high_score (:obj:`GameHighScore <pyrogram.GameHighScore>`, *optional*):
|
||||||
|
The game score for a user.
|
||||||
|
The reply_to_message field will contain the game Message.
|
||||||
|
|
||||||
views (``int``, *optional*):
|
views (``int``, *optional*):
|
||||||
Channel post views.
|
Channel post views.
|
||||||
|
|
||||||
@ -255,6 +262,7 @@ class Message(PyrogramType, Update):
|
|||||||
photo: "pyrogram.Photo" = None,
|
photo: "pyrogram.Photo" = None,
|
||||||
sticker: "pyrogram.Sticker" = None,
|
sticker: "pyrogram.Sticker" = None,
|
||||||
animation: "pyrogram.Animation" = None,
|
animation: "pyrogram.Animation" = None,
|
||||||
|
game: "pyrogram.Game" = None,
|
||||||
video: "pyrogram.Video" = None,
|
video: "pyrogram.Video" = None,
|
||||||
voice: "pyrogram.Voice" = None,
|
voice: "pyrogram.Voice" = None,
|
||||||
video_note: "pyrogram.VideoNote" = None,
|
video_note: "pyrogram.VideoNote" = None,
|
||||||
@ -275,6 +283,7 @@ class Message(PyrogramType, Update):
|
|||||||
migrate_to_chat_id: int = None,
|
migrate_to_chat_id: int = None,
|
||||||
migrate_from_chat_id: int = None,
|
migrate_from_chat_id: int = None,
|
||||||
pinned_message: "Message" = None,
|
pinned_message: "Message" = None,
|
||||||
|
game_high_score: int = None,
|
||||||
views: int = None,
|
views: int = None,
|
||||||
via_bot: User = None,
|
via_bot: User = None,
|
||||||
outgoing: bool = None,
|
outgoing: bool = None,
|
||||||
@ -311,6 +320,7 @@ class Message(PyrogramType, Update):
|
|||||||
self.photo = photo
|
self.photo = photo
|
||||||
self.sticker = sticker
|
self.sticker = sticker
|
||||||
self.animation = animation
|
self.animation = animation
|
||||||
|
self.game = game
|
||||||
self.video = video
|
self.video = video
|
||||||
self.voice = voice
|
self.voice = voice
|
||||||
self.video_note = video_note
|
self.video_note = video_note
|
||||||
@ -331,6 +341,7 @@ class Message(PyrogramType, Update):
|
|||||||
self.migrate_to_chat_id = migrate_to_chat_id
|
self.migrate_to_chat_id = migrate_to_chat_id
|
||||||
self.migrate_from_chat_id = migrate_from_chat_id
|
self.migrate_from_chat_id = migrate_from_chat_id
|
||||||
self.pinned_message = pinned_message
|
self.pinned_message = pinned_message
|
||||||
|
self.game_high_score = game_high_score
|
||||||
self.views = views
|
self.views = views
|
||||||
self.via_bot = via_bot
|
self.via_bot = via_bot
|
||||||
self.outgoing = outgoing
|
self.outgoing = outgoing
|
||||||
@ -407,6 +418,19 @@ class Message(PyrogramType, Update):
|
|||||||
except MessageIdsEmpty:
|
except MessageIdsEmpty:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
if isinstance(action, types.MessageActionGameScore):
|
||||||
|
parsed_message.game_high_score = pyrogram.GameHighScore._parse_action(client, message, users)
|
||||||
|
|
||||||
|
if message.reply_to_msg_id and replies:
|
||||||
|
try:
|
||||||
|
parsed_message.reply_to_message = client.get_messages(
|
||||||
|
parsed_message.chat.id,
|
||||||
|
reply_to_message_ids=message.id,
|
||||||
|
replies=0
|
||||||
|
)
|
||||||
|
except MessageIdsEmpty:
|
||||||
|
pass
|
||||||
|
|
||||||
return parsed_message
|
return parsed_message
|
||||||
|
|
||||||
if isinstance(message, types.Message):
|
if isinstance(message, types.Message):
|
||||||
@ -435,6 +459,7 @@ class Message(PyrogramType, Update):
|
|||||||
location = None
|
location = None
|
||||||
contact = None
|
contact = None
|
||||||
venue = None
|
venue = None
|
||||||
|
game = None
|
||||||
audio = None
|
audio = None
|
||||||
voice = None
|
voice = None
|
||||||
animation = None
|
animation = None
|
||||||
@ -456,6 +481,8 @@ class Message(PyrogramType, Update):
|
|||||||
contact = Contact._parse(client, media)
|
contact = Contact._parse(client, media)
|
||||||
elif isinstance(media, types.MessageMediaVenue):
|
elif isinstance(media, types.MessageMediaVenue):
|
||||||
venue = pyrogram.Venue._parse(client, media)
|
venue = pyrogram.Venue._parse(client, media)
|
||||||
|
elif isinstance(media, types.MessageMediaGame):
|
||||||
|
game = pyrogram.Game._parse(client, message)
|
||||||
elif isinstance(media, types.MessageMediaDocument):
|
elif isinstance(media, types.MessageMediaDocument):
|
||||||
doc = media.document
|
doc = media.document
|
||||||
|
|
||||||
@ -543,6 +570,7 @@ class Message(PyrogramType, Update):
|
|||||||
audio=audio,
|
audio=audio,
|
||||||
voice=voice,
|
voice=voice,
|
||||||
animation=animation,
|
animation=animation,
|
||||||
|
game=game,
|
||||||
video=video,
|
video=video,
|
||||||
video_note=video_note,
|
video_note=video_note,
|
||||||
sticker=sticker,
|
sticker=sticker,
|
||||||
@ -884,7 +912,7 @@ class Message(PyrogramType, Update):
|
|||||||
else:
|
else:
|
||||||
raise ValueError("The message doesn't contain any keyboard")
|
raise ValueError("The message doesn't contain any keyboard")
|
||||||
|
|
||||||
def download(self, file_name: str = "", block: bool = True, progress: callable = None, progress_args: tuple = None):
|
def download(self, file_name: str = "", block: bool = True, progress: callable = None, progress_args: tuple = ()):
|
||||||
"""Bound method *download* of :obj:`Message <pyrogram.Message>`.
|
"""Bound method *download* of :obj:`Message <pyrogram.Message>`.
|
||||||
|
|
||||||
Use as a shortcut for:
|
Use as a shortcut for:
|
||||||
|
@ -52,9 +52,38 @@ class Messages(PyrogramType, Update):
|
|||||||
users = {i.id: i for i in messages.users}
|
users = {i.id: i for i in messages.users}
|
||||||
chats = {i.id: i for i in messages.chats}
|
chats = {i.id: i for i in messages.chats}
|
||||||
|
|
||||||
|
total_count = getattr(messages, "count", len(messages.messages))
|
||||||
|
|
||||||
|
if not messages.messages:
|
||||||
return Messages(
|
return Messages(
|
||||||
total_count=getattr(messages, "count", len(messages.messages)),
|
total_count=total_count,
|
||||||
messages=[Message._parse(client, message, users, chats, replies) for message in messages.messages],
|
messages=[],
|
||||||
|
client=client
|
||||||
|
)
|
||||||
|
|
||||||
|
parsed_messages = [Message._parse(client, message, users, chats, replies=0) for message in messages.messages]
|
||||||
|
|
||||||
|
if replies:
|
||||||
|
messages_with_replies = {i.id: getattr(i, "reply_to_msg_id", None) for i in messages.messages}
|
||||||
|
reply_message_ids = [i[0] for i in filter(lambda x: x[1] is not None, messages_with_replies.items())]
|
||||||
|
|
||||||
|
if reply_message_ids:
|
||||||
|
reply_messages = client.get_messages(
|
||||||
|
parsed_messages[0].chat.id,
|
||||||
|
reply_to_message_ids=reply_message_ids,
|
||||||
|
replies=0
|
||||||
|
).messages
|
||||||
|
|
||||||
|
for message in parsed_messages:
|
||||||
|
reply_id = messages_with_replies[message.message_id]
|
||||||
|
|
||||||
|
for reply in reply_messages:
|
||||||
|
if reply.message_id == reply_id:
|
||||||
|
message.reply_to_message = reply
|
||||||
|
|
||||||
|
return Messages(
|
||||||
|
total_count=total_count,
|
||||||
|
messages=parsed_messages,
|
||||||
client=client
|
client=client
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -21,6 +21,13 @@ class StopPropagation(StopIteration):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ContinuePropagation(StopIteration):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Update:
|
class Update:
|
||||||
def stop_propagation(self):
|
def stop_propagation(self):
|
||||||
raise StopPropagation
|
raise StopPropagation
|
||||||
|
|
||||||
|
def continue_propagation(self):
|
||||||
|
raise ContinuePropagation
|
||||||
|
@ -33,6 +33,19 @@ class ChatMember(PyrogramType):
|
|||||||
The member's status in the chat. Can be "creator", "administrator", "member", "restricted",
|
The member's status in the chat. Can be "creator", "administrator", "member", "restricted",
|
||||||
"left" or "kicked".
|
"left" or "kicked".
|
||||||
|
|
||||||
|
date (``int``, *optional*):
|
||||||
|
Date when the user joined, unix time. Not available for creator.
|
||||||
|
|
||||||
|
invited_by (:obj:`User <pyrogram.User>`, *optional*):
|
||||||
|
Administrators and self member only. Information about the user who invited this member.
|
||||||
|
In case the user joined by himself this will be the same as "user".
|
||||||
|
|
||||||
|
promoted_by (:obj:`User <pyrogram.User>`, *optional*):
|
||||||
|
Administrators only. Information about the user who promoted this member as administrator.
|
||||||
|
|
||||||
|
restricted_by (:obj:`User <pyrogram.User>`, *optional*):
|
||||||
|
Restricted and kicked only. Information about the user who restricted or kicked this member.
|
||||||
|
|
||||||
until_date (``int``, *optional*):
|
until_date (``int``, *optional*):
|
||||||
Restricted and kicked only. Date when restrictions will be lifted for this user, unix time.
|
Restricted and kicked only. Date when restrictions will be lifted for this user, unix time.
|
||||||
|
|
||||||
@ -86,6 +99,10 @@ class ChatMember(PyrogramType):
|
|||||||
client: "pyrogram.client.ext.BaseClient",
|
client: "pyrogram.client.ext.BaseClient",
|
||||||
user: "pyrogram.User",
|
user: "pyrogram.User",
|
||||||
status: str,
|
status: str,
|
||||||
|
date: int = None,
|
||||||
|
invited_by: "pyrogram.User" = None,
|
||||||
|
promoted_by: "pyrogram.User" = None,
|
||||||
|
restricted_by: "pyrogram.User" = None,
|
||||||
until_date: int = None,
|
until_date: int = None,
|
||||||
can_be_edited: bool = None,
|
can_be_edited: bool = None,
|
||||||
can_change_info: bool = None,
|
can_change_info: bool = None,
|
||||||
@ -104,6 +121,10 @@ class ChatMember(PyrogramType):
|
|||||||
|
|
||||||
self.user = user
|
self.user = user
|
||||||
self.status = status
|
self.status = status
|
||||||
|
self.date = date
|
||||||
|
self.invited_by = invited_by
|
||||||
|
self.promoted_by = promoted_by
|
||||||
|
self.restricted_by = restricted_by
|
||||||
self.until_date = until_date
|
self.until_date = until_date
|
||||||
self.can_be_edited = can_be_edited
|
self.can_be_edited = can_be_edited
|
||||||
self.can_change_info = can_change_info
|
self.can_change_info = can_change_info
|
||||||
@ -120,17 +141,18 @@ class ChatMember(PyrogramType):
|
|||||||
self.can_add_web_page_previews = can_add_web_page_previews
|
self.can_add_web_page_previews = can_add_web_page_previews
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse(client, member, user) -> "ChatMember":
|
def _parse(client, member, users) -> "ChatMember":
|
||||||
user = pyrogram.User._parse(client, user)
|
user = pyrogram.User._parse(client, users[member.user_id])
|
||||||
|
invited_by = pyrogram.User._parse(client, users[member.inviter_id]) if hasattr(member, "inviter_id") else None
|
||||||
|
|
||||||
if isinstance(member, (types.ChannelParticipant, types.ChannelParticipantSelf, types.ChatParticipant)):
|
if isinstance(member, (types.ChannelParticipant, types.ChannelParticipantSelf, types.ChatParticipant)):
|
||||||
return ChatMember(user=user, status="member", client=client)
|
return ChatMember(user=user, status="member", date=member.date, invited_by=invited_by, client=client)
|
||||||
|
|
||||||
if isinstance(member, (types.ChannelParticipantCreator, types.ChatParticipantCreator)):
|
if isinstance(member, (types.ChannelParticipantCreator, types.ChatParticipantCreator)):
|
||||||
return ChatMember(user=user, status="creator", client=client)
|
return ChatMember(user=user, status="creator", client=client)
|
||||||
|
|
||||||
if isinstance(member, types.ChatParticipantAdmin):
|
if isinstance(member, types.ChatParticipantAdmin):
|
||||||
return ChatMember(user=user, status="administrator", client=client)
|
return ChatMember(user=user, status="administrator", date=member.date, invited_by=invited_by, client=client)
|
||||||
|
|
||||||
if isinstance(member, types.ChannelParticipantAdmin):
|
if isinstance(member, types.ChannelParticipantAdmin):
|
||||||
rights = member.admin_rights
|
rights = member.admin_rights
|
||||||
@ -138,6 +160,9 @@ class ChatMember(PyrogramType):
|
|||||||
return ChatMember(
|
return ChatMember(
|
||||||
user=user,
|
user=user,
|
||||||
status="administrator",
|
status="administrator",
|
||||||
|
date=member.date,
|
||||||
|
invited_by=invited_by,
|
||||||
|
promoted_by=pyrogram.User._parse(client, users[member.promoted_by]),
|
||||||
can_be_edited=member.can_edit,
|
can_be_edited=member.can_edit,
|
||||||
can_change_info=rights.change_info,
|
can_change_info=rights.change_info,
|
||||||
can_post_messages=rights.post_messages,
|
can_post_messages=rights.post_messages,
|
||||||
@ -155,7 +180,13 @@ class ChatMember(PyrogramType):
|
|||||||
|
|
||||||
chat_member = ChatMember(
|
chat_member = ChatMember(
|
||||||
user=user,
|
user=user,
|
||||||
status="kicked" if rights.view_messages else "restricted",
|
status=(
|
||||||
|
"kicked" if rights.view_messages
|
||||||
|
else "left" if member.left
|
||||||
|
else "restricted"
|
||||||
|
),
|
||||||
|
date=member.date,
|
||||||
|
restricted_by=pyrogram.User._parse(client, users[member.kicked_by]),
|
||||||
until_date=0 if rights.until_date == (1 << 31) - 1 else rights.until_date,
|
until_date=0 if rights.until_date == (1 << 31) - 1 else rights.until_date,
|
||||||
client=client
|
client=client
|
||||||
)
|
)
|
||||||
|
@ -59,7 +59,7 @@ class ChatMembers(PyrogramType):
|
|||||||
total_count = len(members)
|
total_count = len(members)
|
||||||
|
|
||||||
for member in members:
|
for member in members:
|
||||||
chat_members.append(ChatMember._parse(client, member, users[member.user_id]))
|
chat_members.append(ChatMember._parse(client, member, users))
|
||||||
|
|
||||||
return ChatMembers(
|
return ChatMembers(
|
||||||
total_count=total_count,
|
total_count=total_count,
|
||||||
|
6
setup.py
6
setup.py
@ -39,10 +39,10 @@ def get_version():
|
|||||||
|
|
||||||
|
|
||||||
def get_readme():
|
def get_readme():
|
||||||
# PyPI doesn"t like raw html
|
# PyPI doesn't like raw html
|
||||||
with open("README.rst", encoding="utf-8") as f:
|
with open("README.rst", encoding="utf-8") as f:
|
||||||
readme = re.sub(r"\.\. \|.+\| raw:: html(?:\s{4}.+)+\n\n", "", f.read())
|
readme = re.sub(r"\.\. \|.+\| raw:: html(?:\s{4}.+)+\n\n", "", f.read())
|
||||||
return re.sub(r"\|header\|", "|logo|\n\n|description|\n\n|scheme| |tgcrypto|", readme)
|
return re.sub(r"\|header\|", "|logo|\n\n|description|\n\n|schema| |tgcrypto|", readme)
|
||||||
|
|
||||||
|
|
||||||
class Clean(Command):
|
class Clean(Command):
|
||||||
@ -127,7 +127,7 @@ class Generate(Command):
|
|||||||
docs_compiler.start()
|
docs_compiler.start()
|
||||||
|
|
||||||
|
|
||||||
if len(argv) > 1 and argv[1] in ["bdist_wheel", "install"]:
|
if len(argv) > 1 and argv[1] in ["bdist_wheel", "install", "develop"]:
|
||||||
error_compiler.start()
|
error_compiler.start()
|
||||||
api_compiler.start()
|
api_compiler.start()
|
||||||
docs_compiler.start()
|
docs_compiler.start()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user