mirror of
https://github.com/python-telegram-bot/python-telegram-bot.git
synced 2024-12-22 22:45:09 +01:00
Merge master
This commit is contained in:
commit
5e42eac5ef
19 changed files with 215 additions and 103 deletions
14
.github/CONTRIBUTING.rst
vendored
14
.github/CONTRIBUTING.rst
vendored
|
@ -43,6 +43,8 @@ If you have an idea for something to do, first check if it's already been filed
|
||||||
|
|
||||||
Another great way to start contributing is by writing tests. Tests are really important because they help prevent developers from accidentally breaking existing code, allowing them to build cool things faster. If you're interested in helping out, let the development team know by posting to the `developers' mailing list`_, and we'll help you get started.
|
Another great way to start contributing is by writing tests. Tests are really important because they help prevent developers from accidentally breaking existing code, allowing them to build cool things faster. If you're interested in helping out, let the development team know by posting to the `developers' mailing list`_, and we'll help you get started.
|
||||||
|
|
||||||
|
That being said, we want to mention that we are very hesistant about adding new requirements to our projects. If you intend to do this, please state this in an issue and get a verification from one of the maintainers.
|
||||||
|
|
||||||
Instructions for making a code change
|
Instructions for making a code change
|
||||||
#####################################
|
#####################################
|
||||||
|
|
||||||
|
@ -111,6 +113,14 @@ Here's how to make a one-off code change.
|
||||||
|
|
||||||
$ pytest -v
|
$ pytest -v
|
||||||
|
|
||||||
|
To run ``test_official`` (particularly useful if you made API changes), run
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
$ export TEST_OFFICIAL=True
|
||||||
|
|
||||||
|
prior to running the tests.
|
||||||
|
|
||||||
- To actually make the commit (this will trigger tests for yapf, lint and pep8 automatically):
|
- To actually make the commit (this will trigger tests for yapf, lint and pep8 automatically):
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
@ -238,6 +248,6 @@ break the API classes. For example:
|
||||||
.. _`developers' mailing list`: mailto:devs@python-telegram-bot.org
|
.. _`developers' mailing list`: mailto:devs@python-telegram-bot.org
|
||||||
.. _`PEP 8 Style Guide`: https://www.python.org/dev/peps/pep-0008/
|
.. _`PEP 8 Style Guide`: https://www.python.org/dev/peps/pep-0008/
|
||||||
.. _`sphinx`: http://sphinx-doc.org
|
.. _`sphinx`: http://sphinx-doc.org
|
||||||
.. _`Google Python Style Guide`: https://google-styleguide.googlecode.com/svn/trunk/pyguide.html
|
.. _`Google Python Style Guide`: http://google.github.io/styleguide/pyguide.html
|
||||||
.. _`Google Python Style Docstrings`: http://sphinx-doc.org/latest/ext/example_google.html
|
.. _`Google Python Style Docstrings`: https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html
|
||||||
.. _AUTHORS.rst: ../AUTHORS.rst
|
.. _AUTHORS.rst: ../AUTHORS.rst
|
||||||
|
|
6
.github/workflows/test.yml
vendored
6
.github/workflows/test.yml
vendored
|
@ -5,6 +5,9 @@ on:
|
||||||
- master
|
- master
|
||||||
schedule:
|
schedule:
|
||||||
- cron: 7 3 * * *
|
- cron: 7 3 * * *
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
pytest:
|
pytest:
|
||||||
|
@ -12,7 +15,7 @@ jobs:
|
||||||
runs-on: ${{matrix.os}}
|
runs-on: ${{matrix.os}}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: [2.7, 3.5, 3.6, 3.7]
|
python-version: [3.5, 3.6, 3.7]
|
||||||
os: [ubuntu-latest, windows-latest]
|
os: [ubuntu-latest, windows-latest]
|
||||||
include:
|
include:
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
|
@ -87,6 +90,7 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
pytest -v tests/test_official.py
|
pytest -v tests/test_official.py
|
||||||
exit $?
|
exit $?
|
||||||
|
continue-on-error: True
|
||||||
env:
|
env:
|
||||||
TEST_OFFICIAL: "true"
|
TEST_OFFICIAL: "true"
|
||||||
shell: bash --noprofile --norc {0}
|
shell: bash --noprofile --norc {0}
|
||||||
|
|
|
@ -26,6 +26,7 @@ The following wonderful people contributed directly or indirectly to this projec
|
||||||
- `d-qoi <https://github.com/d-qoi>`_
|
- `d-qoi <https://github.com/d-qoi>`_
|
||||||
- `daimajia <https://github.com/daimajia>`_
|
- `daimajia <https://github.com/daimajia>`_
|
||||||
- `Daniel Reed <https://github.com/nmlorg>`_
|
- `Daniel Reed <https://github.com/nmlorg>`_
|
||||||
|
- `Eana Hufwe <https://github.com/blueset>`_
|
||||||
- `Ehsan Online <https://github.com/ehsanonline>`_
|
- `Ehsan Online <https://github.com/ehsanonline>`_
|
||||||
- `Eli Gao <https://github.com/eligao>`_
|
- `Eli Gao <https://github.com/eligao>`_
|
||||||
- `Emilio Molinari <https://github.com/xates>`_
|
- `Emilio Molinari <https://github.com/xates>`_
|
||||||
|
@ -67,6 +68,7 @@ The following wonderful people contributed directly or indirectly to this projec
|
||||||
- `Pieter Schutz <https://github.com/eldinnie>`_
|
- `Pieter Schutz <https://github.com/eldinnie>`_
|
||||||
- `Poolitzer <https://github.com/Poolitzer>`_
|
- `Poolitzer <https://github.com/Poolitzer>`_
|
||||||
- `Rahiel Kasim <https://github.com/rahiel>`_
|
- `Rahiel Kasim <https://github.com/rahiel>`_
|
||||||
|
- `Rizlas <https://github.com/rizlas>`_
|
||||||
- `Sahil Sharma <https://github.com/sahilsharma811>`_
|
- `Sahil Sharma <https://github.com/sahilsharma811>`_
|
||||||
- `Sascha <https://github.com/saschalalala>`_
|
- `Sascha <https://github.com/saschalalala>`_
|
||||||
- `Shelomentsev D <https://github.com/shelomentsevd>`_
|
- `Shelomentsev D <https://github.com/shelomentsevd>`_
|
||||||
|
|
|
@ -83,7 +83,7 @@ Introduction
|
||||||
|
|
||||||
This library provides a pure Python interface for the
|
This library provides a pure Python interface for the
|
||||||
`Telegram Bot API <https://core.telegram.org/bots/api>`_.
|
`Telegram Bot API <https://core.telegram.org/bots/api>`_.
|
||||||
It's compatible with Python versions 2.7, 3.3+ and `PyPy <http://pypy.org/>`_.
|
It's compatible with Python versions 3.5+ and `PyPy <http://pypy.org/>`_.
|
||||||
|
|
||||||
In addition to the pure API implementation, this library features a number of high-level classes to
|
In addition to the pure API implementation, this library features a number of high-level classes to
|
||||||
make the development of bots easy and straightforward. These classes are contained in the
|
make the development of bots easy and straightforward. These classes are contained in the
|
||||||
|
@ -137,9 +137,9 @@ Other references:
|
||||||
Learning by example
|
Learning by example
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
We believe that the best way to learn and understand this simple package is by example. So here
|
We believe that the best way to learn this package is by example. Here
|
||||||
are some examples for you to review. Even if it's not your approach for learning, please take a
|
are some examples for you to review. Even if it is not your approach for learning, please take a
|
||||||
look at ``echobot2``, it is de facto the base for most of the bots out there. Best of all,
|
look at ``echobot2``, it is the de facto base for most of the bots out there. Best of all,
|
||||||
the code for these examples are released to the public domain, so you can start by grabbing the
|
the code for these examples are released to the public domain, so you can start by grabbing the
|
||||||
code and building on top of it.
|
code and building on top of it.
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,7 @@ def start(update, context):
|
||||||
reply_text = "Hi! My name is Doctor Botter."
|
reply_text = "Hi! My name is Doctor Botter."
|
||||||
if context.user_data:
|
if context.user_data:
|
||||||
reply_text += " You already told me your {}. Why don't you tell me something more " \
|
reply_text += " You already told me your {}. Why don't you tell me something more " \
|
||||||
"about yourself? Or change enything I " \
|
"about yourself? Or change anything I " \
|
||||||
"already know.".format(", ".join(context.user_data.keys()))
|
"already know.".format(", ".join(context.user_data.keys()))
|
||||||
else:
|
else:
|
||||||
reply_text += " I will hold a more complex conversation with you. Why don't you tell me " \
|
reply_text += " I will hold a more complex conversation with you. Why don't you tell me " \
|
||||||
|
|
3
setup.py
3
setup.py
|
@ -50,10 +50,7 @@ with codecs.open('README.rst', 'r', 'utf-8') as fd:
|
||||||
'Topic :: Communications :: Chat',
|
'Topic :: Communications :: Chat',
|
||||||
'Topic :: Internet',
|
'Topic :: Internet',
|
||||||
'Programming Language :: Python',
|
'Programming Language :: Python',
|
||||||
'Programming Language :: Python :: 2',
|
|
||||||
'Programming Language :: Python :: 2.7',
|
|
||||||
'Programming Language :: Python :: 3',
|
'Programming Language :: Python :: 3',
|
||||||
'Programming Language :: Python :: 3.4',
|
|
||||||
'Programming Language :: Python :: 3.5',
|
'Programming Language :: Python :: 3.5',
|
||||||
'Programming Language :: Python :: 3.6',
|
'Programming Language :: Python :: 3.6',
|
||||||
'Programming Language :: Python :: 3.7'
|
'Programming Language :: Python :: 3.7'
|
||||||
|
|
|
@ -522,10 +522,10 @@ class Bot(TelegramObject):
|
||||||
reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A
|
reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A
|
||||||
JSON-serialized object for an inline keyboard, custom reply keyboard, instructions
|
JSON-serialized object for an inline keyboard, custom reply keyboard, instructions
|
||||||
to remove reply keyboard or to force a reply from the user.
|
to remove reply keyboard or to force a reply from the user.
|
||||||
thumb (`filelike object`, optional): Thumbnail of the
|
thumb (`filelike object`, optional): Thumbnail of the file sent.
|
||||||
file sent. The thumbnail should be in JPEG format and less than 200 kB in size.
|
The thumbnail should be in JPEG format and less than 200 kB in size.
|
||||||
A thumbnail's width and height should not exceed 90. Ignored if the file is not
|
A thumbnail's width and height should not exceed 320.
|
||||||
is passed as a string or file_id.
|
Ignored if the file is passed as a string or file_id.
|
||||||
timeout (:obj:`int` | :obj:`float`, optional): Send file timeout (default: 20 seconds).
|
timeout (:obj:`int` | :obj:`float`, optional): Send file timeout (default: 20 seconds).
|
||||||
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
||||||
|
|
||||||
|
@ -605,10 +605,10 @@ class Bot(TelegramObject):
|
||||||
reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A
|
reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A
|
||||||
JSON-serialized object for an inline keyboard, custom reply keyboard, instructions
|
JSON-serialized object for an inline keyboard, custom reply keyboard, instructions
|
||||||
to remove reply keyboard or to force a reply from the user.
|
to remove reply keyboard or to force a reply from the user.
|
||||||
thumb (`filelike object`, optional): Thumbnail of the
|
thumb (`filelike object`, optional): Thumbnail of the file sent.
|
||||||
file sent. The thumbnail should be in JPEG format and less than 200 kB in size.
|
The thumbnail should be in JPEG format and less than 200 kB in size.
|
||||||
A thumbnail's width and height should not exceed 90. Ignored if the file is not
|
A thumbnail's width and height should not exceed 320.
|
||||||
is passed as a string or file_id.
|
Ignored if the file is passed as a string or file_id.
|
||||||
timeout (:obj:`int` | :obj:`float`, optional): Send file timeout (default: 20 seconds).
|
timeout (:obj:`int` | :obj:`float`, optional): Send file timeout (default: 20 seconds).
|
||||||
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
||||||
|
|
||||||
|
@ -743,10 +743,10 @@ class Bot(TelegramObject):
|
||||||
reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A
|
reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A
|
||||||
JSON-serialized object for an inline keyboard, custom reply keyboard, instructions
|
JSON-serialized object for an inline keyboard, custom reply keyboard, instructions
|
||||||
to remove reply keyboard or to force a reply from the user.
|
to remove reply keyboard or to force a reply from the user.
|
||||||
thumb (`filelike object`, optional): Thumbnail of the
|
thumb (`filelike object`, optional): Thumbnail of the file sent.
|
||||||
file sent. The thumbnail should be in JPEG format and less than 200 kB in size.
|
The thumbnail should be in JPEG format and less than 200 kB in size.
|
||||||
A thumbnail's width and height should not exceed 90. Ignored if the file is not
|
A thumbnail's width and height should not exceed 320.
|
||||||
is passed as a string or file_id.
|
Ignored if the file is passed as a string or file_id.
|
||||||
timeout (:obj:`int` | :obj:`float`, optional): Send file timeout (default: 20 seconds).
|
timeout (:obj:`int` | :obj:`float`, optional): Send file timeout (default: 20 seconds).
|
||||||
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
||||||
|
|
||||||
|
@ -822,10 +822,10 @@ class Bot(TelegramObject):
|
||||||
reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A
|
reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A
|
||||||
JSON-serialized object for an inline keyboard, custom reply keyboard,
|
JSON-serialized object for an inline keyboard, custom reply keyboard,
|
||||||
instructions to remove reply keyboard or to force a reply from the user.
|
instructions to remove reply keyboard or to force a reply from the user.
|
||||||
thumb (`filelike object`, optional): Thumbnail of the
|
thumb (`filelike object`, optional): Thumbnail of the file sent.
|
||||||
file sent. The thumbnail should be in JPEG format and less than 200 kB in size.
|
The thumbnail should be in JPEG format and less than 200 kB in size.
|
||||||
A thumbnail's width and height should not exceed 90. Ignored if the file is not
|
A thumbnail's width and height should not exceed 320.
|
||||||
is passed as a string or file_id.
|
Ignored if the file is passed as a string or file_id.
|
||||||
timeout (:obj:`int` | :obj:`float`, optional): Send file timeout (default: 20 seconds).
|
timeout (:obj:`int` | :obj:`float`, optional): Send file timeout (default: 20 seconds).
|
||||||
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
||||||
|
|
||||||
|
@ -887,10 +887,10 @@ class Bot(TelegramObject):
|
||||||
duration (:obj:`int`, optional): Duration of sent animation in seconds.
|
duration (:obj:`int`, optional): Duration of sent animation in seconds.
|
||||||
width (:obj:`int`, optional): Animation width.
|
width (:obj:`int`, optional): Animation width.
|
||||||
height (:obj:`int`, optional): Animation height.
|
height (:obj:`int`, optional): Animation height.
|
||||||
thumb (`filelike object`, optional): Thumbnail of the
|
thumb (`filelike object`, optional): Thumbnail of the file sent.
|
||||||
file sent. The thumbnail should be in JPEG format and less than 200 kB in size.
|
The thumbnail should be in JPEG format and less than 200 kB in size.
|
||||||
A thumbnail's width and height should not exceed 90. Ignored if the file is not
|
A thumbnail's width and height should not exceed 320.
|
||||||
is passed as a string or file_id.
|
Ignored if the file is passed as a string or file_id.
|
||||||
caption (:obj:`str`, optional): Animation caption (may also be used when resending
|
caption (:obj:`str`, optional): Animation caption (may also be used when resending
|
||||||
animations by file_id), 0-1024 characters.
|
animations by file_id), 0-1024 characters.
|
||||||
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to
|
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to
|
||||||
|
|
|
@ -44,7 +44,14 @@ class CallbackContext(object):
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
chat_data (:obj:`dict`, optional): A dict that can be used to keep any data in. For each
|
chat_data (:obj:`dict`, optional): A dict that can be used to keep any data in. For each
|
||||||
update from the same chat it will be the same ``dict``.
|
update from the same chat id it will be the same ``dict``.
|
||||||
|
|
||||||
|
Warning:
|
||||||
|
When a group chat migrates to a supergroup, its chat id will change and the
|
||||||
|
``chat_data`` needs to be transferred. For details see our `wiki page
|
||||||
|
<https://github.com/python-telegram-bot/python-telegram-bot/wiki/
|
||||||
|
Storing-user--and-chat-related-data#chat-migration>`_.
|
||||||
|
|
||||||
user_data (:obj:`dict`, optional): A dict that can be used to keep any data in. For each
|
user_data (:obj:`dict`, optional): A dict that can be used to keep any data in. For each
|
||||||
update from the same user it will be the same ``dict``.
|
update from the same user it will be the same ``dict``.
|
||||||
matches (List[:obj:`re match object`], optional): If the associated update originated from
|
matches (List[:obj:`re match object`], optional): If the associated update originated from
|
||||||
|
@ -80,6 +87,11 @@ class CallbackContext(object):
|
||||||
self.error = None
|
self.error = None
|
||||||
self.job = None
|
self.job = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def dispatcher(self):
|
||||||
|
""":class:`telegram.ext.Dispatcher`: The dispatcher associated with this context."""
|
||||||
|
return self._dispatcher
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def chat_data(self):
|
def chat_data(self):
|
||||||
return self._chat_data
|
return self._chat_data
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
|
||||||
from telegram.utils.helpers import decode_user_chat_data_from_json,\
|
from telegram.utils.helpers import decode_user_chat_data_from_json,\
|
||||||
decode_conversations_from_json, enocde_conversations_to_json
|
decode_conversations_from_json, encode_conversations_to_json
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import ujson as json
|
import ujson as json
|
||||||
|
@ -119,7 +119,7 @@ class DictPersistence(BasePersistence):
|
||||||
if self._conversations_json:
|
if self._conversations_json:
|
||||||
return self._conversations_json
|
return self._conversations_json
|
||||||
else:
|
else:
|
||||||
return enocde_conversations_to_json(self.conversations)
|
return encode_conversations_to_json(self.conversations)
|
||||||
|
|
||||||
def get_user_data(self):
|
def get_user_data(self):
|
||||||
"""Returns the user_data created from the ``user_data_json`` or an empty defaultdict.
|
"""Returns the user_data created from the ``user_data_json`` or an empty defaultdict.
|
||||||
|
|
|
@ -138,8 +138,6 @@ class Dispatcher(object):
|
||||||
else:
|
else:
|
||||||
self.persistence = None
|
self.persistence = None
|
||||||
|
|
||||||
self.job_queue = job_queue
|
|
||||||
|
|
||||||
self.handlers = {}
|
self.handlers = {}
|
||||||
"""Dict[:obj:`int`, List[:class:`telegram.ext.Handler`]]: Holds the handlers per group."""
|
"""Dict[:obj:`int`, List[:class:`telegram.ext.Handler`]]: Holds the handlers per group."""
|
||||||
self.groups = []
|
self.groups = []
|
||||||
|
@ -162,6 +160,10 @@ class Dispatcher(object):
|
||||||
else:
|
else:
|
||||||
self._set_singleton(None)
|
self._set_singleton(None)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def exception_event(self):
|
||||||
|
return self.__exception_event
|
||||||
|
|
||||||
def _init_async_threads(self, base_name, workers):
|
def _init_async_threads(self, base_name, workers):
|
||||||
base_name = '{}_'.format(base_name) if base_name else ''
|
base_name = '{}_'.format(base_name) if base_name else ''
|
||||||
|
|
||||||
|
|
|
@ -243,7 +243,7 @@ class Filters(object):
|
||||||
self.name = 'Filters.text({})'.format(iterable)
|
self.name = 'Filters.text({})'.format(iterable)
|
||||||
|
|
||||||
def filter(self, message):
|
def filter(self, message):
|
||||||
if message.text and not message.text.startswith('/'):
|
if message.text:
|
||||||
return message.text in self.iterable
|
return message.text in self.iterable
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -257,7 +257,7 @@ class Filters(object):
|
||||||
return self._TextIterable(update)
|
return self._TextIterable(update)
|
||||||
|
|
||||||
def filter(self, message):
|
def filter(self, message):
|
||||||
return bool(message.text and not message.text.startswith('/'))
|
return bool(message.text)
|
||||||
|
|
||||||
text = _Text()
|
text = _Text()
|
||||||
"""Text Messages. If an iterable of strings is passed, it filters messages to only allow those
|
"""Text Messages. If an iterable of strings is passed, it filters messages to only allow those
|
||||||
|
@ -956,6 +956,15 @@ officedocument.wordprocessingml.document")``-
|
||||||
passport_data = _PassportData()
|
passport_data = _PassportData()
|
||||||
"""Messages that contain a :class:`telegram.PassportData`"""
|
"""Messages that contain a :class:`telegram.PassportData`"""
|
||||||
|
|
||||||
|
class _Poll(BaseFilter):
|
||||||
|
name = 'Filters.poll'
|
||||||
|
|
||||||
|
def filter(self, message):
|
||||||
|
return bool(message.poll)
|
||||||
|
|
||||||
|
poll = _Poll()
|
||||||
|
"""Messages that contain a :class:`telegram.Poll`."""
|
||||||
|
|
||||||
class language(BaseFilter):
|
class language(BaseFilter):
|
||||||
"""Filters messages to only allow those which are from users with a certain language code.
|
"""Filters messages to only allow those which are from users with a certain language code.
|
||||||
|
|
||||||
|
@ -987,8 +996,10 @@ officedocument.wordprocessingml.document")``-
|
||||||
|
|
||||||
class _UpdateType(BaseFilter):
|
class _UpdateType(BaseFilter):
|
||||||
update_filter = True
|
update_filter = True
|
||||||
|
name = 'Filters.update'
|
||||||
|
|
||||||
class _Message(BaseFilter):
|
class _Message(BaseFilter):
|
||||||
|
name = 'Filters.update.message'
|
||||||
update_filter = True
|
update_filter = True
|
||||||
|
|
||||||
def filter(self, update):
|
def filter(self, update):
|
||||||
|
@ -997,6 +1008,7 @@ officedocument.wordprocessingml.document")``-
|
||||||
message = _Message()
|
message = _Message()
|
||||||
|
|
||||||
class _EditedMessage(BaseFilter):
|
class _EditedMessage(BaseFilter):
|
||||||
|
name = 'Filters.update.edited_message'
|
||||||
update_filter = True
|
update_filter = True
|
||||||
|
|
||||||
def filter(self, update):
|
def filter(self, update):
|
||||||
|
@ -1005,6 +1017,7 @@ officedocument.wordprocessingml.document")``-
|
||||||
edited_message = _EditedMessage()
|
edited_message = _EditedMessage()
|
||||||
|
|
||||||
class _Messages(BaseFilter):
|
class _Messages(BaseFilter):
|
||||||
|
name = 'Filters.update.messages'
|
||||||
update_filter = True
|
update_filter = True
|
||||||
|
|
||||||
def filter(self, update):
|
def filter(self, update):
|
||||||
|
@ -1013,6 +1026,7 @@ officedocument.wordprocessingml.document")``-
|
||||||
messages = _Messages()
|
messages = _Messages()
|
||||||
|
|
||||||
class _ChannelPost(BaseFilter):
|
class _ChannelPost(BaseFilter):
|
||||||
|
name = 'Filters.update.channel_post'
|
||||||
update_filter = True
|
update_filter = True
|
||||||
|
|
||||||
def filter(self, update):
|
def filter(self, update):
|
||||||
|
@ -1022,6 +1036,7 @@ officedocument.wordprocessingml.document")``-
|
||||||
|
|
||||||
class _EditedChannelPost(BaseFilter):
|
class _EditedChannelPost(BaseFilter):
|
||||||
update_filter = True
|
update_filter = True
|
||||||
|
name = 'Filters.update.edited_channel_post'
|
||||||
|
|
||||||
def filter(self, update):
|
def filter(self, update):
|
||||||
return update.edited_channel_post is not None
|
return update.edited_channel_post is not None
|
||||||
|
@ -1030,6 +1045,7 @@ officedocument.wordprocessingml.document")``-
|
||||||
|
|
||||||
class _ChannelPosts(BaseFilter):
|
class _ChannelPosts(BaseFilter):
|
||||||
update_filter = True
|
update_filter = True
|
||||||
|
name = 'Filters.update.channel_posts'
|
||||||
|
|
||||||
def filter(self, update):
|
def filter(self, update):
|
||||||
return update.channel_post is not None or update.edited_channel_post is not None
|
return update.channel_post is not None or update.edited_channel_post is not None
|
||||||
|
|
|
@ -95,13 +95,14 @@ class JobQueue(object):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# get time at which to run:
|
# get time at which to run:
|
||||||
time_spec = time_spec or job.interval
|
if time_spec is None:
|
||||||
|
time_spec = job.interval
|
||||||
if time_spec is None:
|
if time_spec is None:
|
||||||
raise ValueError("no time specification given for scheduling non-repeating job")
|
raise ValueError("no time specification given for scheduling non-repeating job")
|
||||||
next_t = to_float_timestamp(time_spec, reference_timestamp=previous_t)
|
next_t = to_float_timestamp(time_spec, reference_timestamp=previous_t)
|
||||||
|
|
||||||
# enqueue:
|
# enqueue:
|
||||||
self.logger.debug('Putting job %s with t=%f', job.name, time_spec)
|
self.logger.debug('Putting job %s with t=%s', job.name, time_spec)
|
||||||
self._queue.put((next_t, job))
|
self._queue.put((next_t, job))
|
||||||
|
|
||||||
# Wake up the loop if this job should be executed next
|
# Wake up the loop if this job should be executed next
|
||||||
|
|
|
@ -63,24 +63,29 @@ class Updater(object):
|
||||||
token (:obj:`str`, optional): The bot's token given by the @BotFather.
|
token (:obj:`str`, optional): The bot's token given by the @BotFather.
|
||||||
base_url (:obj:`str`, optional): Base_url for the bot.
|
base_url (:obj:`str`, optional): Base_url for the bot.
|
||||||
workers (:obj:`int`, optional): Amount of threads in the thread pool for functions
|
workers (:obj:`int`, optional): Amount of threads in the thread pool for functions
|
||||||
decorated with ``@run_async``.
|
decorated with ``@run_async`` (ignored if `dispatcher` argument is used).
|
||||||
bot (:class:`telegram.Bot`, optional): A pre-initialized bot instance. If a pre-initialized
|
bot (:class:`telegram.Bot`, optional): A pre-initialized bot instance (ignored if
|
||||||
bot is used, it is the user's responsibility to create it using a `Request`
|
`dispatcher` argument is used). If a pre-initialized bot is used, it is the user's
|
||||||
instance with a large enough connection pool.
|
responsibility to create it using a `Request` instance with a large enough connection
|
||||||
|
pool.
|
||||||
|
dispatcher (:class:`telegram.ext.Dispatcher`, optional): A pre-initialized dispatcher
|
||||||
|
instance. If a pre-initialized dispatcher is used, it is the user's responsibility to
|
||||||
|
create it with proper arguments.
|
||||||
private_key (:obj:`bytes`, optional): Private key for decryption of telegram passport data.
|
private_key (:obj:`bytes`, optional): Private key for decryption of telegram passport data.
|
||||||
private_key_password (:obj:`bytes`, optional): Password for above private key.
|
private_key_password (:obj:`bytes`, optional): Password for above private key.
|
||||||
user_sig_handler (:obj:`function`, optional): Takes ``signum, frame`` as positional
|
user_sig_handler (:obj:`function`, optional): Takes ``signum, frame`` as positional
|
||||||
arguments. This will be called when a signal is received, defaults are (SIGINT,
|
arguments. This will be called when a signal is received, defaults are (SIGINT,
|
||||||
SIGTERM, SIGABRT) setable with :attr:`idle`.
|
SIGTERM, SIGABRT) setable with :attr:`idle`.
|
||||||
request_kwargs (:obj:`dict`, optional): Keyword args to control the creation of a
|
request_kwargs (:obj:`dict`, optional): Keyword args to control the creation of a
|
||||||
`telegram.utils.request.Request` object (ignored if `bot` argument is used). The
|
`telegram.utils.request.Request` object (ignored if `bot` or `dispatcher` argument is
|
||||||
request_kwargs are very useful for the advanced users who would like to control the
|
used). The request_kwargs are very useful for the advanced users who would like to
|
||||||
default timeouts and/or control the proxy used for http communication.
|
control the default timeouts and/or control the proxy used for http communication.
|
||||||
use_context (:obj:`bool`, optional): If set to ``True`` Use the context based callback API.
|
use_context (:obj:`bool`, optional): If set to ``True`` Use the context based callback API
|
||||||
During the deprecation period of the old API the default is ``False``. **New users**:
|
(ignored if `dispatcher` argument is used). During the deprecation period of the old
|
||||||
set this to ``True``.
|
API the default is ``False``. **New users**: set this to ``True``.
|
||||||
persistence (:class:`telegram.ext.BasePersistence`, optional): The persistence class to
|
persistence (:class:`telegram.ext.BasePersistence`, optional): The persistence class to
|
||||||
store data that should be persistent over restarts.
|
store data that should be persistent over restarts (ignored if `dispatcher` argument is
|
||||||
|
used).
|
||||||
default_parse_mode (:obj:`str`, optional): Default parse mode used if not set explicitly in
|
default_parse_mode (:obj:`str`, optional): Default parse mode used if not set explicitly in
|
||||||
method call. See the constants in :class:`telegram.ParseMode` for the available modes.
|
method call. See the constants in :class:`telegram.ParseMode` for the available modes.
|
||||||
default_disable_notification (:obj:`bool`, optional): Default setting for the
|
default_disable_notification (:obj:`bool`, optional): Default setting for the
|
||||||
|
@ -117,58 +122,84 @@ class Updater(object):
|
||||||
default_disable_web_page_preview=None,
|
default_disable_web_page_preview=None,
|
||||||
default_timeout=DEFAULT_NONE,
|
default_timeout=DEFAULT_NONE,
|
||||||
default_quote=None,
|
default_quote=None,
|
||||||
use_context=False):
|
use_context=False,
|
||||||
|
dispatcher=None):
|
||||||
|
|
||||||
if (token is None) and (bot is None):
|
if dispatcher is None:
|
||||||
raise ValueError('`token` or `bot` must be passed')
|
if (token is None) and (bot is None):
|
||||||
if (token is not None) and (bot is not None):
|
raise ValueError('`token` or `bot` must be passed')
|
||||||
raise ValueError('`token` and `bot` are mutually exclusive')
|
if (token is not None) and (bot is not None):
|
||||||
if (private_key is not None) and (bot is not None):
|
raise ValueError('`token` and `bot` are mutually exclusive')
|
||||||
raise ValueError('`bot` and `private_key` are mutually exclusive')
|
if (private_key is not None) and (bot is not None):
|
||||||
|
raise ValueError('`bot` and `private_key` are mutually exclusive')
|
||||||
|
else:
|
||||||
|
if bot is not None:
|
||||||
|
raise ValueError('`dispatcher` and `bot` are mutually exclusive')
|
||||||
|
if persistence is not None:
|
||||||
|
raise ValueError('`dispatcher` and `persistence` are mutually exclusive')
|
||||||
|
if workers is not None:
|
||||||
|
raise ValueError('`dispatcher` and `workers` are mutually exclusive')
|
||||||
|
if use_context != dispatcher.use_context:
|
||||||
|
raise ValueError('`dispatcher` and `use_context` are mutually exclusive')
|
||||||
|
|
||||||
self.logger = logging.getLogger(__name__)
|
self.logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
con_pool_size = workers + 4
|
if dispatcher is None:
|
||||||
|
con_pool_size = workers + 4
|
||||||
|
|
||||||
if bot is not None:
|
if bot is not None:
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
if bot.request.con_pool_size < con_pool_size:
|
if bot.request.con_pool_size < con_pool_size:
|
||||||
|
self.logger.warning(
|
||||||
|
'Connection pool of Request object is smaller than optimal value (%s)',
|
||||||
|
con_pool_size)
|
||||||
|
else:
|
||||||
|
# we need a connection pool the size of:
|
||||||
|
# * for each of the workers
|
||||||
|
# * 1 for Dispatcher
|
||||||
|
# * 1 for polling Updater (even if webhook is used, we can spare a connection)
|
||||||
|
# * 1 for JobQueue
|
||||||
|
# * 1 for main thread
|
||||||
|
if request_kwargs is None:
|
||||||
|
request_kwargs = {}
|
||||||
|
if 'con_pool_size' not in request_kwargs:
|
||||||
|
request_kwargs['con_pool_size'] = con_pool_size
|
||||||
|
self._request = Request(**request_kwargs)
|
||||||
|
self.bot = Bot(token, base_url, request=self._request, private_key=private_key,
|
||||||
|
private_key_password=private_key_password,
|
||||||
|
default_parse_mode=default_parse_mode,
|
||||||
|
default_disable_notification=default_disable_notification,
|
||||||
|
default_disable_web_page_preview=default_disable_web_page_preview,
|
||||||
|
default_timeout=default_timeout,
|
||||||
|
default_quote=default_quote)
|
||||||
|
self.update_queue = Queue()
|
||||||
|
self.job_queue = JobQueue()
|
||||||
|
self.__exception_event = Event()
|
||||||
|
self.persistence = persistence
|
||||||
|
self.dispatcher = Dispatcher(
|
||||||
|
self.bot,
|
||||||
|
self.update_queue,
|
||||||
|
job_queue=self.job_queue,
|
||||||
|
workers=workers,
|
||||||
|
exception_event=self.__exception_event,
|
||||||
|
persistence=persistence,
|
||||||
|
use_context=use_context)
|
||||||
|
self.job_queue.set_dispatcher(self.dispatcher)
|
||||||
|
else:
|
||||||
|
con_pool_size = dispatcher.workers + 4
|
||||||
|
|
||||||
|
self.bot = dispatcher.bot
|
||||||
|
if self.bot.request.con_pool_size < con_pool_size:
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'Connection pool of Request object is smaller than optimal value (%s)',
|
'Connection pool of Request object is smaller than optimal value (%s)',
|
||||||
con_pool_size)
|
con_pool_size)
|
||||||
else:
|
self.update_queue = dispatcher.update_queue
|
||||||
# we need a connection pool the size of:
|
self.__exception_event = dispatcher.exception_event
|
||||||
# * for each of the workers
|
self.persistence = dispatcher.persistence
|
||||||
# * 1 for Dispatcher
|
self.job_queue = dispatcher.job_queue
|
||||||
# * 1 for polling Updater (even if webhook is used, we can spare a connection)
|
self.dispatcher = dispatcher
|
||||||
# * 1 for JobQueue
|
|
||||||
# * 1 for main thread
|
|
||||||
if request_kwargs is None:
|
|
||||||
request_kwargs = {}
|
|
||||||
if 'con_pool_size' not in request_kwargs:
|
|
||||||
request_kwargs['con_pool_size'] = con_pool_size
|
|
||||||
self._request = Request(**request_kwargs)
|
|
||||||
self.bot = Bot(token, base_url, request=self._request, private_key=private_key,
|
|
||||||
private_key_password=private_key_password,
|
|
||||||
default_parse_mode=default_parse_mode,
|
|
||||||
default_disable_notification=default_disable_notification,
|
|
||||||
default_disable_web_page_preview=default_disable_web_page_preview,
|
|
||||||
default_timeout=default_timeout,
|
|
||||||
default_quote=default_quote)
|
|
||||||
self.user_sig_handler = user_sig_handler
|
self.user_sig_handler = user_sig_handler
|
||||||
self.update_queue = Queue()
|
|
||||||
self.job_queue = JobQueue()
|
|
||||||
self.__exception_event = Event()
|
|
||||||
self.persistence = persistence
|
|
||||||
self.dispatcher = Dispatcher(
|
|
||||||
self.bot,
|
|
||||||
self.update_queue,
|
|
||||||
job_queue=self.job_queue,
|
|
||||||
workers=workers,
|
|
||||||
exception_event=self.__exception_event,
|
|
||||||
persistence=persistence,
|
|
||||||
use_context=use_context)
|
|
||||||
self.job_queue.set_dispatcher(self.dispatcher)
|
|
||||||
self.last_update_id = 0
|
self.last_update_id = 0
|
||||||
self.running = False
|
self.running = False
|
||||||
self.is_idle = False
|
self.is_idle = False
|
||||||
|
|
|
@ -300,7 +300,7 @@ def create_deep_linked_url(bot_username, payload=None, group=False):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def enocde_conversations_to_json(conversations):
|
def encode_conversations_to_json(conversations):
|
||||||
"""Helper method to encode a conversations dict (that uses tuples as keys) to a
|
"""Helper method to encode a conversations dict (that uses tuples as keys) to a
|
||||||
JSON-serializable way. Use :attr:`_decode_conversations_from_json` to decode.
|
JSON-serializable way. Use :attr:`_decode_conversations_from_json` to decode.
|
||||||
|
|
||||||
|
|
|
@ -117,3 +117,7 @@ class TestCallbackContext(object):
|
||||||
callback_context.user_data = {}
|
callback_context.user_data = {}
|
||||||
with pytest.raises(AttributeError):
|
with pytest.raises(AttributeError):
|
||||||
callback_context.chat_data = "test"
|
callback_context.chat_data = "test"
|
||||||
|
|
||||||
|
def test_dispatcher_attribute(self, cdp):
|
||||||
|
callback_context = CallbackContext(cdp)
|
||||||
|
assert callback_context.dispatcher == cdp
|
||||||
|
|
|
@ -45,11 +45,11 @@ class TestFilters(object):
|
||||||
update.message.text = 'test'
|
update.message.text = 'test'
|
||||||
assert (Filters.text)(update)
|
assert (Filters.text)(update)
|
||||||
update.message.text = '/test'
|
update.message.text = '/test'
|
||||||
assert not (Filters.text)(update)
|
assert (Filters.text)(update)
|
||||||
|
|
||||||
def test_filters_text_iterable(self, update):
|
def test_filters_text_iterable(self, update):
|
||||||
update.message.text = 'test'
|
update.message.text = '/test'
|
||||||
assert Filters.text({'test', 'test1'})(update)
|
assert Filters.text({'/test', 'test1'})(update)
|
||||||
assert not Filters.text(['test1', 'test2'])(update)
|
assert not Filters.text(['test1', 'test2'])(update)
|
||||||
|
|
||||||
def test_filters_caption(self, update):
|
def test_filters_caption(self, update):
|
||||||
|
@ -599,6 +599,11 @@ class TestFilters(object):
|
||||||
update.message.passport_data = 'test'
|
update.message.passport_data = 'test'
|
||||||
assert Filters.passport_data(update)
|
assert Filters.passport_data(update)
|
||||||
|
|
||||||
|
def test_filters_poll(self, update):
|
||||||
|
assert not Filters.poll(update)
|
||||||
|
update.message.poll = 'test'
|
||||||
|
assert Filters.poll(update)
|
||||||
|
|
||||||
def test_language_filter_single(self, update):
|
def test_language_filter_single(self, update):
|
||||||
update.message.from_user.language_code = 'en_US'
|
update.message.from_user.language_code = 'en_US'
|
||||||
assert (Filters.language('en_US'))(update)
|
assert (Filters.language('en_US'))(update)
|
||||||
|
@ -625,7 +630,7 @@ class TestFilters(object):
|
||||||
update.message.forward_date = datetime.datetime.utcnow()
|
update.message.forward_date = datetime.datetime.utcnow()
|
||||||
assert (Filters.text & Filters.forwarded)(update)
|
assert (Filters.text & Filters.forwarded)(update)
|
||||||
update.message.text = '/test'
|
update.message.text = '/test'
|
||||||
assert not (Filters.text & Filters.forwarded)(update)
|
assert (Filters.text & Filters.forwarded)(update)
|
||||||
update.message.text = 'test'
|
update.message.text = 'test'
|
||||||
update.message.forward_date = None
|
update.message.forward_date = None
|
||||||
assert not (Filters.text & Filters.forwarded)(update)
|
assert not (Filters.text & Filters.forwarded)(update)
|
||||||
|
|
|
@ -119,6 +119,11 @@ class TestJobQueue(object):
|
||||||
sleep(0.07)
|
sleep(0.07)
|
||||||
assert self.result == 1
|
assert self.result == 1
|
||||||
|
|
||||||
|
def test_run_repeating_first_immediate(self, job_queue):
|
||||||
|
job_queue.run_repeating(self.job_run_once, 0.1, first=0)
|
||||||
|
sleep(0.05)
|
||||||
|
assert self.result == 1
|
||||||
|
|
||||||
def test_run_repeating_first_timezone(self, job_queue, timezone):
|
def test_run_repeating_first_timezone(self, job_queue, timezone):
|
||||||
"""Test correct scheduling of job when passing a timezone-aware datetime as ``first``"""
|
"""Test correct scheduling of job when passing a timezone-aware datetime as ``first``"""
|
||||||
first = (dtm.datetime.utcnow() + timezone.utcoffset(None)).replace(tzinfo=timezone)
|
first = (dtm.datetime.utcnow() + timezone.utcoffset(None)).replace(tzinfo=timezone)
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||||
import signal
|
import signal
|
||||||
|
|
||||||
from telegram.utils.helpers import enocde_conversations_to_json
|
from telegram.utils.helpers import encode_conversations_to_json
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import ujson as json
|
import ujson as json
|
||||||
|
@ -774,7 +774,7 @@ class TestDictPersistence(object):
|
||||||
dict_persistence.update_conversation('name3', (1, 2), 3)
|
dict_persistence.update_conversation('name3', (1, 2), 3)
|
||||||
assert dict_persistence.conversations == conversations_two
|
assert dict_persistence.conversations == conversations_two
|
||||||
assert dict_persistence.conversations_json != conversations_json
|
assert dict_persistence.conversations_json != conversations_json
|
||||||
assert dict_persistence.conversations_json == enocde_conversations_to_json(
|
assert dict_persistence.conversations_json == encode_conversations_to_json(
|
||||||
conversations_two)
|
conversations_two)
|
||||||
|
|
||||||
def test_with_handler(self, bot, update):
|
def test_with_handler(self, bot, update):
|
||||||
|
|
|
@ -39,7 +39,7 @@ from future.builtins import bytes
|
||||||
|
|
||||||
from telegram import TelegramError, Message, User, Chat, Update, Bot
|
from telegram import TelegramError, Message, User, Chat, Update, Bot
|
||||||
from telegram.error import Unauthorized, InvalidToken, TimedOut, RetryAfter
|
from telegram.error import Unauthorized, InvalidToken, TimedOut, RetryAfter
|
||||||
from telegram.ext import Updater
|
from telegram.ext import Updater, Dispatcher, BasePersistence
|
||||||
|
|
||||||
signalskip = pytest.mark.skipif(sys.platform == 'win32',
|
signalskip = pytest.mark.skipif(sys.platform == 'win32',
|
||||||
reason='Can\'t send signals without stopping '
|
reason='Can\'t send signals without stopping '
|
||||||
|
@ -393,7 +393,7 @@ class TestUpdater(object):
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
Updater(token='123:abcd', bot=bot)
|
Updater(token='123:abcd', bot=bot)
|
||||||
|
|
||||||
def test_no_token_or_bot(self):
|
def test_no_token_or_bot_or_dispatcher(self):
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
Updater()
|
Updater()
|
||||||
|
|
||||||
|
@ -401,3 +401,26 @@ class TestUpdater(object):
|
||||||
bot = Bot('123:zyxw')
|
bot = Bot('123:zyxw')
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
Updater(bot=bot, private_key=b'key')
|
Updater(bot=bot, private_key=b'key')
|
||||||
|
|
||||||
|
def test_mutual_exclude_bot_dispatcher(self):
|
||||||
|
dispatcher = Dispatcher(None, None)
|
||||||
|
bot = Bot('123:zyxw')
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
Updater(bot=bot, dispatcher=dispatcher)
|
||||||
|
|
||||||
|
def test_mutual_exclude_persistence_dispatcher(self):
|
||||||
|
dispatcher = Dispatcher(None, None)
|
||||||
|
persistence = BasePersistence()
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
Updater(dispatcher=dispatcher, persistence=persistence)
|
||||||
|
|
||||||
|
def test_mutual_exclude_workers_dispatcher(self):
|
||||||
|
dispatcher = Dispatcher(None, None)
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
Updater(dispatcher=dispatcher, workers=8)
|
||||||
|
|
||||||
|
def test_mutual_exclude_use_context_dispatcher(self):
|
||||||
|
dispatcher = Dispatcher(None, None)
|
||||||
|
use_context = not dispatcher.use_context
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
Updater(dispatcher=dispatcher, use_context=use_context)
|
||||||
|
|
Loading…
Reference in a new issue