mirror of
https://github.com/python-telegram-bot/python-telegram-bot.git
synced 2025-01-10 20:12:52 +01:00
Merge branch 'master' into default_parse_mode
This commit is contained in:
commit
0af02eb59d
62 changed files with 2150 additions and 777 deletions
21
.github/CONTRIBUTING.rst
vendored
21
.github/CONTRIBUTING.rst
vendored
|
@ -66,6 +66,26 @@ Here's how to make a one-off code change.
|
|||
- You can refer to relevant issues in the commit message by writing, e.g., "#105".
|
||||
|
||||
- Your code should adhere to the `PEP 8 Style Guide`_, with the exception that we have a maximum line length of 99.
|
||||
|
||||
- Document your code. This project uses `sphinx`_ to generate static HTML docs. To build them, first make sure you have the required dependencies:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ pip install -r docs/requirements-docs.txt
|
||||
|
||||
then run the following from the PTB root directory:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ make -C docs html
|
||||
|
||||
or, if you don't have ``make`` available (e.g. on Windows):
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ sphinx-build docs/source docs/build/html
|
||||
|
||||
Once the process terminates, you can view the built documentation by opening ``docs/build/html/index.html`` with a browser.
|
||||
|
||||
- For consistency, please conform to `Google Python Style Guide`_ and `Google Python Style Docstrings`_. In addition, code should be formatted consistently with other code around it.
|
||||
|
||||
|
@ -217,6 +237,7 @@ break the API classes. For example:
|
|||
.. _`issue tracker`: https://github.com/python-telegram-bot/python-telegram-bot/issues
|
||||
.. _`developers' mailing list`: mailto:devs@python-telegram-bot.org
|
||||
.. _`PEP 8 Style Guide`: https://www.python.org/dev/peps/pep-0008/
|
||||
.. _`sphinx`: http://sphinx-doc.org
|
||||
.. _`Google Python Style Guide`: https://google-styleguide.googlecode.com/svn/trunk/pyguide.html
|
||||
.. _`Google Python Style Docstrings`: http://sphinx-doc.org/latest/ext/example_google.html
|
||||
.. _AUTHORS.rst: ../AUTHORS.rst
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -69,6 +69,7 @@ target/
|
|||
*.sublime*
|
||||
|
||||
# unitests files
|
||||
game.gif
|
||||
telegram.mp3
|
||||
telegram.mp4
|
||||
telegram2.mp4
|
||||
|
|
|
@ -10,9 +10,6 @@ repos:
|
|||
sha: 0b70e285e369bcb24b57b74929490ea7be9c4b19
|
||||
hooks:
|
||||
- id: flake8
|
||||
exclude: ^(setup.py|docs/source/conf.py)$
|
||||
args:
|
||||
- --ignore=W605,W503
|
||||
- repo: git://github.com/pre-commit/mirrors-pylint
|
||||
sha: 9d8dcbc2b86c796275680f239c1e90dcd50bd398
|
||||
hooks:
|
||||
|
|
16
.travis.yml
16
.travis.yml
|
@ -2,16 +2,20 @@ language: python
|
|||
matrix:
|
||||
include:
|
||||
- python: 2.7
|
||||
- python: 3.4
|
||||
- python: 3.5
|
||||
- python: 3.6
|
||||
- python: 3.7
|
||||
dist: xenial
|
||||
sudo: true
|
||||
- python: 3.7
|
||||
dist: xenial
|
||||
env: TEST_OFFICIAL=true
|
||||
- python: pypy2.7-5.10.0
|
||||
dist: xenial
|
||||
- python: pypy3.5-5.10.1
|
||||
dist: xenial
|
||||
- python: 3.8-dev
|
||||
dist: xenial
|
||||
allow_failures:
|
||||
- python: pypy2.7-5.10.0
|
||||
- python: pypy3.5-5.10.1
|
||||
|
@ -33,16 +37,18 @@ before_cache:
|
|||
- rm -f $HOME/.pre-commit/pre-commit.log
|
||||
|
||||
install:
|
||||
# fix TypeError from old version of this
|
||||
- pip install -U codecov pytest-cov
|
||||
- echo $TRAVIS_PYTHON_VERSION
|
||||
- if [[ $TRAVIS_PYTHON_VERSION == '3.7'* ]]; then pip install -U git+https://github.com/yaml/pyyaml.git; fi
|
||||
- if [[ $TRAVIS_PYTHON_VERSION == '3.7'* ]]; then pip install -U git+https://github.com/yaml/pyyaml.git; else true; fi
|
||||
- pip install -U -r requirements.txt
|
||||
- pip install -U -r requirements-dev.txt
|
||||
- if [[ $TRAVIS_PYTHON_VERSION != 'pypy'* ]]; then pip install ujson; fi
|
||||
- if [[ $TRAVIS_PYTHON_VERSION != 'pypy'* ]]; then pip install ujson; else true; fi
|
||||
|
||||
script:
|
||||
- pytest -v -m nocoverage
|
||||
- pytest -v -m "not nocoverage" --cov
|
||||
- if [[ $TEST_OFFICIAL != 'true' ]]; then pytest -v -m nocoverage; else true; fi
|
||||
- if [[ $TEST_OFFICIAL != 'true' ]]; then pytest -v -m "not nocoverage" --cov; else true; fi
|
||||
- if [[ $TEST_OFFICIAL == 'true' ]]; then pytest -v tests/test_official.py; else true; fi
|
||||
|
||||
after_success:
|
||||
- coverage combine
|
||||
|
|
|
@ -4,7 +4,7 @@ Credits
|
|||
``python-telegram-bot`` was originally created by
|
||||
`Leandro Toledo <https://github.com/leandrotoledo>`_ and is now maintained by
|
||||
`Jannes Höke <https://github.com/jh0ker>`_ (`@jh0ker <https://t.me/jh0ker>`_ on Telegram),
|
||||
`Noam Meltzer <https://github.com/tsnoam>`_, `Pieter Schutz <https://github.com/eldinnie>`_ and `Jasmin Bom <https://github.com/jsmnbom>`_.
|
||||
`Noam Meltzer <https://github.com/tsnoam>`_, `Pieter Schutz <https://github.com/eldinnie>`_, `Jasmin Bom <https://github.com/jsmnbom>`_ and `Hinrich Mahler <https://github.com/Bibo-Joshi>`_.
|
||||
|
||||
We're vendoring urllib3 as part of ``python-telegram-bot`` which is distributed under the MIT
|
||||
license. For more info, full credits & license terms, the sources can be found here:
|
||||
|
@ -65,11 +65,14 @@ The following wonderful people contributed directly or indirectly to this projec
|
|||
- `Patrick Hofmann <https://github.com/PH89>`_
|
||||
- `Paul Larsen <https://github.com/PaulSonOfLars>`_
|
||||
- `Pieter Schutz <https://github.com/eldinnie>`_
|
||||
- `Poolitzer <https://github.com/Poolitzer>`_
|
||||
- `Rahiel Kasim <https://github.com/rahiel>`_
|
||||
- `Sahil Sharma <https://github.com/sahilsharma811>`_
|
||||
- `Sascha <https://github.com/saschalalala>`_
|
||||
- `Shelomentsev D <https://github.com/shelomentsevd>`_
|
||||
- `Simon Schürrle <https://github.com/SitiSchu>`_
|
||||
- `sooyhwang <https://github.com/sooyhwang>`_
|
||||
- `syntx <https://github.com/syntx>`_
|
||||
- `thodnev <https://github.com/thodnev>`_
|
||||
- `Trainer Jono <https://github.com/Tr-Jono>`_
|
||||
- `Valentijn <https://github.com/Faalentijn>`_
|
||||
|
@ -77,5 +80,6 @@ The following wonderful people contributed directly or indirectly to this projec
|
|||
- `Vorobjev Simon <https://github.com/simonvorobjev>`_
|
||||
- `Wagner Macedo <https://github.com/wagnerluis1982>`_
|
||||
- `wjt <https://github.com/wjt>`_
|
||||
- `zeshuaro <https://github.com/zeshuaro>`_
|
||||
|
||||
Please add yourself here alphabetically when you submit your first pull request.
|
||||
|
|
112
CHANGES.rst
112
CHANGES.rst
|
@ -2,22 +2,86 @@
|
|||
Changelog
|
||||
=========
|
||||
|
||||
Version 12.0.0b1
|
||||
Version 12.2.0
|
||||
==============
|
||||
|
||||
**New features:**
|
||||
|
||||
- Nested ConversationHandlers (`#1512`_).
|
||||
|
||||
**Minor changes, CI improvments or bug fixes:**
|
||||
|
||||
- Fix CI failures due to non-backward compat attrs depndency (`#1540`_).
|
||||
- travis.yaml: TEST_OFFICIAL removed from allowed_failures.
|
||||
- Fix typos in examples (`#1537`_).
|
||||
- Fix Bot.to_dict to use proper first_name (`#1525`_).
|
||||
- Refactor ``test_commandhandler.py`` (`#1408`_).
|
||||
- Add Python 3.8 (RC version) to Travis testing matrix (`#1543`_).
|
||||
- test_bot.py: Add to_dict test (`#1544`_).
|
||||
- Flake config moved into setup.cfg (`#1546`_).
|
||||
|
||||
.. _`#1512`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1512
|
||||
.. _`#1540`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1540
|
||||
.. _`#1537`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1537
|
||||
.. _`#1525`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1525
|
||||
.. _`#1408`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1408
|
||||
.. _`#1543`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1543
|
||||
.. _`#1544`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1544
|
||||
.. _`#1546`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1546
|
||||
|
||||
Version 12.1.1
|
||||
==============
|
||||
*Released 2019-09-18*
|
||||
|
||||
**Hot fix release**
|
||||
|
||||
Fixed regression in the vendored urllib3 (`#1517`_).
|
||||
|
||||
.. _`#1517`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1517
|
||||
|
||||
Version 12.1.0
|
||||
================
|
||||
*Released 2019-02-13*
|
||||
|
||||
First beta release ever.
|
||||
It has been so long since last release that we would like to test the impact before a final release.
|
||||
|
||||
*We do NOT recommend using this beta release in production.*
|
||||
*Released 2019-09-13*
|
||||
|
||||
**Major changes:**
|
||||
|
||||
- Bot API 4.4 support (`#1464`_, `#1510`_)
|
||||
- Add `get_file` method to `Animation` & `ChatPhoto`. Add, `get_small_file` & `get_big_file`
|
||||
methods to `ChatPhoto` (`#1489`_)
|
||||
- Tools for deep linking (`#1049`_)
|
||||
|
||||
**Minor changes and/or bug fixes:**
|
||||
|
||||
- Documentation fixes (`#1500`_, `#1499`_)
|
||||
- Improved examples (`#1502`_)
|
||||
|
||||
.. _`#1464`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1464
|
||||
.. _`#1502`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1502
|
||||
.. _`#1499`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1499
|
||||
.. _`#1500`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1500
|
||||
.. _`#1049`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1049
|
||||
.. _`#1489`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1489
|
||||
.. _`#1510`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1510
|
||||
|
||||
Version 12.0.0
|
||||
================
|
||||
*Released 2019-08-29*
|
||||
|
||||
Well... This felt like decades. But here we are with a new release.
|
||||
|
||||
Expect minor releases soon (mainly complete Bot API 4.4 support)
|
||||
|
||||
**Major and/or breaking changes:**
|
||||
|
||||
- Context based callbacks
|
||||
- Persistence
|
||||
- PrefixHandler added (Handler overhaul)
|
||||
- Deprecation of RegexHandler and edited_messages, channel_post, etc. arguments (Filter overhaul)
|
||||
- Various ConversationHandler changes and fixes
|
||||
- Bot API 4.1, 4.2, 4.3 support
|
||||
- Python 3.4 is no longer supported
|
||||
- Error Handler now handles all types of exceptions (`#1485`_)
|
||||
- Return UTC from from_timestamp() (`#1485`_)
|
||||
|
||||
**See the wiki page at https://git.io/fxJuV for a detailed guide on how to migrate from version 11 to version 12.**
|
||||
|
||||
|
@ -64,6 +128,7 @@ ConversationHandler
|
|||
- Use warnings.warn for ConversationHandler warnings (`#1343`_)
|
||||
- Fix unresolvable promises (`#1270`_)
|
||||
|
||||
|
||||
Bug fixes & improvements
|
||||
------------------------
|
||||
|
||||
|
@ -82,6 +147,22 @@ Bug fixes & improvements
|
|||
- Allow SOCKSConnection to parse username and password from URL (`#1211`_)
|
||||
- Fix for arguments in passport/data.py (`#1213`_)
|
||||
- Improve message entity parsing by adding text_mention (`#1206`_)
|
||||
- Documentation fixes (`#1348`_, `#1397`_, `#1436`_)
|
||||
- Merged filters short-circuit (`#1350`_)
|
||||
- Fix webhook listen with tornado (`#1383`_)
|
||||
- Call task_done() on update queue after update processing finished (`#1428`_)
|
||||
- Fix send_location() - latitude may be 0 (`#1437`_)
|
||||
- Make MessageEntity objects comparable (`#1465`_)
|
||||
- Add prefix to thread names (`#1358`_)
|
||||
|
||||
Buf fixes since v12.0.0b1
|
||||
-------------------------
|
||||
|
||||
- Fix setting bot on ShippingQuery (`#1355`_)
|
||||
- Fix _trigger_timeout() missing 1 required positional argument: 'job' (`#1367`_)
|
||||
- Add missing message.text check in PrefixHandler check_update (`#1375`_)
|
||||
- Make updates persist even on DispatcherHandlerStop (`#1463`_)
|
||||
- Dispatcher force updating persistence object's chat data attribute(`#1462`_)
|
||||
|
||||
.. _`#1100`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1100
|
||||
.. _`#1283`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1283
|
||||
|
@ -110,6 +191,22 @@ Bug fixes & improvements
|
|||
.. _`#1319`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1319
|
||||
.. _`#1343`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1343
|
||||
.. _`#1270`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1270
|
||||
.. _`#1348`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1348
|
||||
.. _`#1350`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1350
|
||||
.. _`#1383`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1383
|
||||
.. _`#1397`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1397
|
||||
.. _`#1428`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1428
|
||||
.. _`#1436`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1436
|
||||
.. _`#1437`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1437
|
||||
.. _`#1465`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1465
|
||||
.. _`#1358`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1358
|
||||
.. _`#1355`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1355
|
||||
.. _`#1367`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1367
|
||||
.. _`#1375`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1375
|
||||
.. _`#1463`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1463
|
||||
.. _`#1462`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1462
|
||||
.. _`#1483`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1483
|
||||
.. _`#1485`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1485
|
||||
|
||||
Internal improvements
|
||||
---------------------
|
||||
|
@ -118,6 +215,7 @@ Internal improvements
|
|||
- Use multiple bots for CI to improve testing times significantly.
|
||||
- Allow pypy to fail in CI.
|
||||
- Remove the last CamelCase CheckUpdate methods from the handlers we missed earlier.
|
||||
- test_official is now executed in a different job
|
||||
|
||||
Version 11.1.0
|
||||
==============
|
||||
|
|
16
README.rst
16
README.rst
|
@ -104,21 +104,7 @@ All types and methods of the Telegram Bot API **4.1** are supported.
|
|||
Installing
|
||||
==========
|
||||
|
||||
**Beta note**
|
||||
|
||||
The newest stable release is currently version 11.1.0.
|
||||
|
||||
The newest release is a beta release for version 12.
|
||||
Install or upgrade with:
|
||||
|
||||
.. code:: shell
|
||||
|
||||
$ pip install python-telegram-bot==12.0.0b1 --upgrade
|
||||
|
||||
|
||||
See CHANGES.rst for the changelog and make sure to report any bugs you find!
|
||||
|
||||
You can install or upgrade the stable python-telegram-bot with:
|
||||
You can install or upgrade python-telegram-bot with:
|
||||
|
||||
.. code:: shell
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ environment:
|
|||
|
||||
matrix:
|
||||
# For Python versions available on Appveyor, see
|
||||
# http://www.appveyor.com/docs/installed-software#python
|
||||
# https://www.appveyor.com/docs/windows-images-software/#python
|
||||
# The list here is complete (excluding Python 2.6, which
|
||||
# isn't covered by this document) at the time of writing.
|
||||
|
||||
|
@ -10,6 +10,7 @@ environment:
|
|||
- PYTHON: "C:\\Python35"
|
||||
- PYTHON: "C:\\Python36"
|
||||
- PYTHON: "C:\\Python37"
|
||||
# - PYTHON: "C:\\Python38"
|
||||
|
||||
branches:
|
||||
only:
|
||||
|
@ -26,6 +27,8 @@ install:
|
|||
# Check that we have the expected version and architecture for Python
|
||||
- "python --version"
|
||||
# We need wheel installed to build wheels
|
||||
# fix TypeError from an old version of this
|
||||
- "pip install attrs==17.4.0"
|
||||
- "pip install -U codecov pytest-cov"
|
||||
- "pip install -r requirements.txt"
|
||||
- "pip install -r requirements-dev.txt"
|
||||
|
@ -33,6 +36,7 @@ install:
|
|||
build: off
|
||||
|
||||
test_script:
|
||||
- "pytest --version"
|
||||
- "pytest -m \"not nocoverage\" --cov --cov-report xml:coverage.xml"
|
||||
|
||||
after_test:
|
||||
|
|
|
@ -58,9 +58,9 @@ author = u'Leandro Toledo'
|
|||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '12.0' # telegram.__version__[:3]
|
||||
version = '12.1' # telegram.__version__[:3]
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '12.0.0b1' # telegram.__version__
|
||||
release = '12.2.0' # telegram.__version__
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
|
|
|
@ -22,7 +22,7 @@ Reference
|
|||
=========
|
||||
|
||||
Below you can find a reference of all the classes and methods in python-telegram-bot.
|
||||
Apart from the telegram.ext package the objects should reflect the types defined in the `official telegram bot api documentation <https://core.telegram.org/bots/api>`_
|
||||
Apart from the `telegram.ext` package the objects should reflect the types defined in the `official telegram bot api documentation <https://core.telegram.org/bots/api>`_.
|
||||
|
||||
.. toctree::
|
||||
telegram
|
||||
|
|
6
docs/source/telegram.chatpermissions.rst
Normal file
6
docs/source/telegram.chatpermissions.rst
Normal file
|
@ -0,0 +1,6 @@
|
|||
telegram.ChatPermissions
|
||||
========================
|
||||
|
||||
.. autoclass:: telegram.ChatPermissions
|
||||
:members:
|
||||
:show-inheritance:
|
6
docs/source/telegram.loginurl.rst
Normal file
6
docs/source/telegram.loginurl.rst
Normal file
|
@ -0,0 +1,6 @@
|
|||
telegram.LoginUrl
|
||||
=================
|
||||
|
||||
.. autoclass:: telegram.LoginUrl
|
||||
:members:
|
||||
:show-inheritance:
|
|
@ -13,6 +13,7 @@ telegram package
|
|||
telegram.chat
|
||||
telegram.chataction
|
||||
telegram.chatmember
|
||||
telegram.chatpermissions
|
||||
telegram.chatphoto
|
||||
telegram.constants
|
||||
telegram.contact
|
||||
|
@ -31,6 +32,7 @@ telegram package
|
|||
telegram.inputmediavideo
|
||||
telegram.keyboardbutton
|
||||
telegram.location
|
||||
telegram.loginurl
|
||||
telegram.message
|
||||
telegram.messageentity
|
||||
telegram.parsemode
|
||||
|
@ -140,8 +142,4 @@ Passport
|
|||
telegram.encryptedpassportelement
|
||||
telegram.encryptedcredentials
|
||||
|
||||
telegram.utils
|
||||
--------------
|
||||
|
||||
.. toctree::
|
||||
telegram.utils
|
||||
.. include:: telegram.utils.rst
|
||||
|
|
|
@ -16,9 +16,15 @@ A common task for a bot is to ask information from the user. In v5.0 of this lib
|
|||
### [`conversationbot2.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/conversationbot2.py)
|
||||
A more complex example of a bot that uses the `ConversationHandler`. It is also more confusing. Good thing there is a [fancy state diagram](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/conversationbot2.png) for this one, too!
|
||||
|
||||
### [`nestedconversationbot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/nestedconversationbot.py)
|
||||
A even more complex example of a bot that uses the nested `ConversationHandler`s. While it's certainly not that complex that you couldn't built it without nested `ConversationHanldler`s, it gives a good impression on how to work with them. Of course, there is a [fancy state diagram](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/nestedconversationbot.png) for this example, too!
|
||||
|
||||
### [`inlinekeyboard.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/inlinekeyboard.py)
|
||||
This example sheds some light on inline keyboards, callback queries and message editing.
|
||||
|
||||
### [`inlinekeyboard2.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/inlinekeyboard2.py)
|
||||
A more complex example about inline keyboards, callback queries and message editing. This example showcases how an interactive menu could be build using inline keyboards.
|
||||
|
||||
### [`inlinebot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/inlinebot.py)
|
||||
A basic example of an [inline bot](https://core.telegram.org/bots/inline). Don't forget to enable inline mode with [@BotFather](https://telegram.me/BotFather).
|
||||
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
#
|
||||
# THIS EXAMPLE HAS BEEN UPDATED TO WORK WITH THE BETA VERSION 12 OF PYTHON-TELEGRAM-BOT.
|
||||
# If you're still using version 11.1.0, please see the examples at
|
||||
# https://github.com/python-telegram-bot/python-telegram-bot/tree/v11.1.0/examples
|
||||
|
||||
"""
|
||||
First, a few callback functions are defined. Then, those functions are passed to
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
#
|
||||
# THIS EXAMPLE HAS BEEN UPDATED TO WORK WITH THE BETA VERSION 12 OF PYTHON-TELEGRAM-BOT.
|
||||
# If you're still using version 11.1.0, please see the examples at
|
||||
# https://github.com/python-telegram-bot/python-telegram-bot/tree/v11.1.0/examples
|
||||
|
||||
"""
|
||||
First, a few callback functions are defined. Then, those functions are passed to
|
||||
|
|
119
examples/deeplinking.py
Normal file
119
examples/deeplinking.py
Normal file
|
@ -0,0 +1,119 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Bot that explains Telegram's "Deep Linking Parameters" functionality.
|
||||
|
||||
This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
This Bot uses the Updater class to handle the bot.
|
||||
|
||||
First, a few handler functions are defined. Then, those functions are passed to
|
||||
the Dispatcher and registered at their respective places.
|
||||
Then, the bot is started and runs until we press Ctrl-C on the command line.
|
||||
|
||||
Usage:
|
||||
Deep Linking example. Send /start to get the link.
|
||||
Press Ctrl-C on the command line or send a signal to the process to stop the
|
||||
bot.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from telegram import ParseMode, InlineKeyboardMarkup, InlineKeyboardButton
|
||||
from telegram.ext import Updater, CommandHandler, Filters
|
||||
|
||||
# Enable logging
|
||||
from telegram.utils import helpers
|
||||
|
||||
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
level=logging.INFO)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Define constants that will allow us to reuse the deep-linking parameters.
|
||||
CHECK_THIS_OUT = 'check-this-out'
|
||||
USING_ENTITIES = 'using-entities-here'
|
||||
SO_COOL = 'so-cool'
|
||||
|
||||
|
||||
def start(update, context):
|
||||
"""Send a deep-linked URL when the command /start is issued."""
|
||||
bot = context.bot
|
||||
url = helpers.create_deep_linked_url(bot.get_me().username, CHECK_THIS_OUT, group=True)
|
||||
text = "Feel free to tell your friends about it:\n\n" + url
|
||||
update.message.reply_text(text)
|
||||
|
||||
|
||||
def deep_linked_level_1(update, context):
|
||||
"""Reached through the CHECK_THIS_OUT payload"""
|
||||
bot = context.bot
|
||||
url = helpers.create_deep_linked_url(bot.get_me().username, SO_COOL)
|
||||
text = "Awesome, you just accessed hidden functionality! " \
|
||||
" Now let's get back to the private chat."
|
||||
keyboard = InlineKeyboardMarkup.from_button(
|
||||
InlineKeyboardButton(text='Continue here!', url=url)
|
||||
)
|
||||
update.message.reply_text(text, reply_markup=keyboard)
|
||||
|
||||
|
||||
def deep_linked_level_2(update, context):
|
||||
"""Reached through the SO_COOL payload"""
|
||||
bot = context.bot
|
||||
url = helpers.create_deep_linked_url(bot.get_me().username, USING_ENTITIES)
|
||||
text = "You can also mask the deep-linked URLs as links: " \
|
||||
"[▶️ CLICK HERE]({0}).".format(url)
|
||||
update.message.reply_text(text, parse_mode=ParseMode.MARKDOWN, disable_web_page_preview=True)
|
||||
|
||||
|
||||
def deep_linked_level_3(update, context):
|
||||
"""Reached through the USING_ENTITIES payload"""
|
||||
payload = context.args
|
||||
update.message.reply_text("Congratulations! This is as deep as it gets 👏🏻\n\n"
|
||||
"The payload was: {0}".format(payload))
|
||||
|
||||
|
||||
def error(update, context):
|
||||
"""Log Errors caused by Updates."""
|
||||
logger.warning('Update "%s" caused error "%s"', update, context.error)
|
||||
|
||||
|
||||
def main():
|
||||
"""Start the bot."""
|
||||
# Create the Updater and pass it your bot's token.
|
||||
updater = Updater("TOKEN", use_context=True)
|
||||
|
||||
# Get the dispatcher to register handlers
|
||||
dp = updater.dispatcher
|
||||
|
||||
# More info on what deep linking actually is (read this first if it's unclear to you):
|
||||
# https://core.telegram.org/bots#deep-linking
|
||||
|
||||
# Register a deep-linking handler
|
||||
dp.add_handler(CommandHandler("start", deep_linked_level_1, Filters.regex(CHECK_THIS_OUT)))
|
||||
|
||||
# This one works with a textual link instead of an URL
|
||||
dp.add_handler(CommandHandler("start", deep_linked_level_2, Filters.regex(SO_COOL)))
|
||||
|
||||
# We can also pass on the deep-linking payload
|
||||
dp.add_handler(CommandHandler("start",
|
||||
deep_linked_level_3,
|
||||
Filters.regex(USING_ENTITIES),
|
||||
pass_args=True))
|
||||
|
||||
# Make sure the deep-linking handlers occur *before* the normal /start handler.
|
||||
dp.add_handler(CommandHandler("start", start))
|
||||
|
||||
# log all errors
|
||||
dp.add_error_handler(error)
|
||||
|
||||
# Start the Bot
|
||||
updater.start_polling()
|
||||
|
||||
# Run the bot until you press Ctrl-C or the process receives SIGINT,
|
||||
# SIGTERM or SIGABRT. This should be used most of the time, since
|
||||
# start_polling() is non-blocking and will stop the bot gracefully.
|
||||
updater.idle()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,10 +1,6 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
#
|
||||
# THIS EXAMPLE HAS BEEN UPDATED TO WORK WITH THE BETA VERSION 12 OF PYTHON-TELEGRAM-BOT.
|
||||
# If you're still using version 11.1.0, please see the examples at
|
||||
# https://github.com/python-telegram-bot/python-telegram-bot/tree/v11.1.0/examples
|
||||
|
||||
"""
|
||||
Simple Bot to reply to Telegram messages.
|
||||
|
@ -30,8 +26,8 @@ logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# Define a few command handlers. These usually take the two arguments bot and
|
||||
# update. Error handlers also receive the raised TelegramError object in error.
|
||||
# Define a few command handlers. These usually take the two arguments update and
|
||||
# context. Error handlers also receive the raised TelegramError object in error.
|
||||
def start(update, context):
|
||||
"""Send a message when the command /start is issued."""
|
||||
update.message.reply_text('Hi!')
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
#
|
||||
# THIS EXAMPLE HAS BEEN UPDATED TO WORK WITH THE BETA VERSION 12 OF PYTHON-TELEGRAM-BOT.
|
||||
# If you're still using version 11.1.0, please see the examples at
|
||||
# https://github.com/python-telegram-bot/python-telegram-bot/tree/v11.1.0/examples
|
||||
|
||||
"""
|
||||
First, a few handler functions are defined. Then, those functions are passed to
|
||||
|
@ -31,8 +27,8 @@ logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# Define a few command handlers. These usually take the two arguments bot and
|
||||
# update. Error handlers also receive the raised TelegramError object in error.
|
||||
# Define a few command handlers. These usually take the two arguments update and
|
||||
# context. Error handlers also receive the raised TelegramError object in error.
|
||||
def start(update, context):
|
||||
"""Send a message when the command /start is issued."""
|
||||
update.message.reply_text('Hi!')
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
#
|
||||
# THIS EXAMPLE HAS BEEN UPDATED TO WORK WITH THE BETA VERSION 12 OF PYTHON-TELEGRAM-BOT.
|
||||
# If you're still using version 11.1.0, please see the examples at
|
||||
# https://github.com/python-telegram-bot/python-telegram-bot/tree/v11.1.0/examples
|
||||
|
||||
"""
|
||||
Basic example for a bot that uses inline keyboards.
|
||||
|
|
211
examples/inlinekeyboard2.py
Normal file
211
examples/inlinekeyboard2.py
Normal file
|
@ -0,0 +1,211 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Simple inline keyboard bot with multiple CallbackQueryHandlers.
|
||||
|
||||
This Bot uses the Updater class to handle the bot.
|
||||
First, a few callback functions are defined as callback query handler. Then, those functions are
|
||||
passed to the Dispatcher and registered at their respective places.
|
||||
Then, the bot is started and runs until we press Ctrl-C on the command line.
|
||||
Usage:
|
||||
Example of a bot that uses inline keyboard that has multiple CallbackQueryHandlers arranged in a
|
||||
ConversationHandler.
|
||||
Send /start to initiate the conversation.
|
||||
Press Ctrl-C on the command line to stop the bot.
|
||||
"""
|
||||
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
|
||||
from telegram.ext import Updater, CommandHandler, CallbackQueryHandler, ConversationHandler
|
||||
import logging
|
||||
|
||||
# Enable logging
|
||||
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
level=logging.INFO)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Stages
|
||||
FIRST, SECOND = range(2)
|
||||
# Callback data
|
||||
ONE, TWO, THREE, FOUR = range(4)
|
||||
|
||||
|
||||
def start(update, context):
|
||||
"""Send message on `/start`."""
|
||||
# Get user that sent /start and log his name
|
||||
user = update.message.from_user
|
||||
logger.info("User %s started the conversation.", user.first_name)
|
||||
# Build InlineKeyboard where each button has a displayed text
|
||||
# and a string as callback_data
|
||||
# The keyboard is a list of button rows, where each row is in turn
|
||||
# a list (hence `[[...]]`).
|
||||
keyboard = [
|
||||
[InlineKeyboardButton("1", callback_data=str(ONE)),
|
||||
InlineKeyboardButton("2", callback_data=str(TWO))]
|
||||
]
|
||||
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||
# Send message with text and appended InlineKeyboard
|
||||
update.message.reply_text(
|
||||
"Start handler, Choose a route",
|
||||
reply_markup=reply_markup
|
||||
)
|
||||
# Tell ConversationHandler that we're in state `FIRST` now
|
||||
return FIRST
|
||||
|
||||
|
||||
def start_over(update, context):
|
||||
"""Prompt same text & keyboard as `start` does but not as new message"""
|
||||
# Get CallbackQuery from Update
|
||||
query = update.callback_query
|
||||
# Get Bot from CallbackContext
|
||||
bot = context.bot
|
||||
keyboard = [
|
||||
[InlineKeyboardButton("1", callback_data=str(ONE)),
|
||||
InlineKeyboardButton("2", callback_data=str(TWO))]
|
||||
]
|
||||
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||
# Instead of sending a new message, edit the message that
|
||||
# originated the CallbackQuery. This gives the feeling of an
|
||||
# interactive menu.
|
||||
bot.edit_message_text(
|
||||
chat_id=query.message.chat_id,
|
||||
message_id=query.message.message_id,
|
||||
text="Start handler, Choose a route",
|
||||
reply_markup=reply_markup
|
||||
)
|
||||
return FIRST
|
||||
|
||||
|
||||
def one(update, context):
|
||||
"""Show new choice of buttons"""
|
||||
query = update.callback_query
|
||||
bot = context.bot
|
||||
keyboard = [
|
||||
[InlineKeyboardButton("3", callback_data=str(THREE)),
|
||||
InlineKeyboardButton("4", callback_data=str(FOUR))]
|
||||
]
|
||||
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||
bot.edit_message_text(
|
||||
chat_id=query.message.chat_id,
|
||||
message_id=query.message.message_id,
|
||||
text="First CallbackQueryHandler, Choose a route",
|
||||
reply_markup=reply_markup
|
||||
)
|
||||
return FIRST
|
||||
|
||||
|
||||
def two(update, context):
|
||||
"""Show new choice of buttons"""
|
||||
query = update.callback_query
|
||||
bot = context.bot
|
||||
keyboard = [
|
||||
[InlineKeyboardButton("1", callback_data=str(ONE)),
|
||||
InlineKeyboardButton("3", callback_data=str(THREE))]
|
||||
]
|
||||
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||
bot.edit_message_text(
|
||||
chat_id=query.message.chat_id,
|
||||
message_id=query.message.message_id,
|
||||
text="Second CallbackQueryHandler, Choose a route",
|
||||
reply_markup=reply_markup
|
||||
)
|
||||
return FIRST
|
||||
|
||||
|
||||
def three(update, context):
|
||||
"""Show new choice of buttons"""
|
||||
query = update.callback_query
|
||||
bot = context.bot
|
||||
keyboard = [
|
||||
[InlineKeyboardButton("Yes, let's do it again!", callback_data=str(ONE)),
|
||||
InlineKeyboardButton("Nah, I've had enough ...", callback_data=str(TWO))]
|
||||
]
|
||||
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||
bot.edit_message_text(
|
||||
chat_id=query.message.chat_id,
|
||||
message_id=query.message.message_id,
|
||||
text="Third CallbackQueryHandler. Do want to start over?",
|
||||
reply_markup=reply_markup
|
||||
)
|
||||
# Transfer to conversation state `SECOND`
|
||||
return SECOND
|
||||
|
||||
|
||||
def four(update, context):
|
||||
"""Show new choice of buttons"""
|
||||
query = update.callback_query
|
||||
bot = context.bot
|
||||
keyboard = [
|
||||
[InlineKeyboardButton("2", callback_data=str(TWO)),
|
||||
InlineKeyboardButton("4", callback_data=str(FOUR))]
|
||||
]
|
||||
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||
bot.edit_message_text(
|
||||
chat_id=query.message.chat_id,
|
||||
message_id=query.message.message_id,
|
||||
text="Fourth CallbackQueryHandler, Choose a route",
|
||||
reply_markup=reply_markup
|
||||
)
|
||||
return FIRST
|
||||
|
||||
|
||||
def end(update, context):
|
||||
"""Returns `ConversationHandler.END`, which tells the
|
||||
ConversationHandler that the conversation is over"""
|
||||
query = update.callback_query
|
||||
bot = context.bot
|
||||
bot.edit_message_text(
|
||||
chat_id=query.message.chat_id,
|
||||
message_id=query.message.message_id,
|
||||
text="See you next time!"
|
||||
)
|
||||
return ConversationHandler.END
|
||||
|
||||
|
||||
def error(update, context):
|
||||
"""Log Errors caused by Updates."""
|
||||
logger.warning('Update "%s" caused error "%s"', update, context.error)
|
||||
|
||||
|
||||
def main():
|
||||
# Create the Updater and pass it your bot's token.
|
||||
updater = Updater("TOKEN", use_context=True)
|
||||
|
||||
# Get the dispatcher to register handlers
|
||||
dp = updater.dispatcher
|
||||
|
||||
# Setup conversation handler with the states FIRST and SECOND
|
||||
# Use the pattern parameter to pass CallbackQueries with specific
|
||||
# data pattern to the corresponding handlers.
|
||||
# ^ means "start of line/string"
|
||||
# $ means "end of line/string"
|
||||
# So ^ABC$ will only allow 'ABC'
|
||||
conv_handler = ConversationHandler(
|
||||
entry_points=[CommandHandler('start', start)],
|
||||
states={
|
||||
FIRST: [CallbackQueryHandler(one, pattern='^' + str(ONE) + '$'),
|
||||
CallbackQueryHandler(two, pattern='^' + str(TWO) + '$'),
|
||||
CallbackQueryHandler(three, pattern='^' + str(THREE) + '$'),
|
||||
CallbackQueryHandler(four, pattern='^' + str(FOUR) + '$')],
|
||||
SECOND: [CallbackQueryHandler(start_over, pattern='^' + str(ONE) + '$'),
|
||||
CallbackQueryHandler(end, pattern='^' + str(TWO) + '$')]
|
||||
},
|
||||
fallbacks=[CommandHandler('start', start)]
|
||||
)
|
||||
|
||||
# Add ConversationHandler to dispatcher that will be used for handling
|
||||
# updates
|
||||
dp.add_handler(conv_handler)
|
||||
|
||||
# log all errors
|
||||
dp.add_error_handler(error)
|
||||
|
||||
# Start the Bot
|
||||
updater.start_polling()
|
||||
|
||||
# Run the bot until you press Ctrl-C or the process receives SIGINT,
|
||||
# SIGTERM or SIGABRT. This should be used most of the time, since
|
||||
# start_polling() is non-blocking and will stop the bot gracefully.
|
||||
updater.idle()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
BIN
examples/nestedconversationbot.png
Normal file
BIN
examples/nestedconversationbot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 492 KiB |
362
examples/nestedconversationbot.py
Normal file
362
examples/nestedconversationbot.py
Normal file
|
@ -0,0 +1,362 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
"""
|
||||
First, a few callback functions are defined. Then, those functions are passed to
|
||||
the Dispatcher and registered at their respective places.
|
||||
Then, the bot is started and runs until we press Ctrl-C on the command line.
|
||||
|
||||
Usage:
|
||||
Example of a bot-user conversation using nested ConversationHandlers.
|
||||
Send /start to initiate the conversation.
|
||||
Press Ctrl-C on the command line or send a signal to the process to stop the
|
||||
bot.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from telegram import (InlineKeyboardMarkup, InlineKeyboardButton)
|
||||
from telegram.ext import (Updater, CommandHandler, MessageHandler, Filters,
|
||||
ConversationHandler, CallbackQueryHandler)
|
||||
|
||||
# Enable logging
|
||||
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
level=logging.INFO)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# State definitions for top level conversation
|
||||
SELECTING_ACTION, ADDING_MEMBER, ADDING_SELF, DESCRIBING_SELF = map(chr, range(4))
|
||||
# State definitions for second level conversation
|
||||
SELECTING_LEVEL, SELECTING_GENDER = map(chr, range(4, 6))
|
||||
# State definitions for descriptions conversation
|
||||
SELECTING_FEATURE, TYPING = map(chr, range(6, 8))
|
||||
# Meta states
|
||||
STOPPING, SHOWING = map(chr, range(8, 10))
|
||||
# Shortcut for ConversationHandler.END
|
||||
END = ConversationHandler.END
|
||||
|
||||
# Different constants for this example
|
||||
(PARENTS, CHILDREN, SELF, GENDER, MALE, FEMALE, AGE, NAME, START_OVER, FEATURES,
|
||||
CURRENT_FEATURE, CURRENT_LEVEL) = map(chr, range(10, 22))
|
||||
|
||||
|
||||
# Helper
|
||||
def _name_switcher(level):
|
||||
if level == PARENTS:
|
||||
return ('Father', 'Mother')
|
||||
elif level == CHILDREN:
|
||||
return ('Brother', 'Sister')
|
||||
|
||||
|
||||
# Top level conversation callbacks
|
||||
def start(update, context):
|
||||
"""Select an action: Adding parent/child or show data."""
|
||||
text = 'You may add a familiy member, yourself show the gathered data or end the ' \
|
||||
'conversation. To abort, simply type /stop.'
|
||||
buttons = [[
|
||||
InlineKeyboardButton(text='Add family member', callback_data=str(ADDING_MEMBER)),
|
||||
InlineKeyboardButton(text='Add yourself', callback_data=str(ADDING_SELF))
|
||||
], [
|
||||
InlineKeyboardButton(text='Show data', callback_data=str(SHOWING)),
|
||||
InlineKeyboardButton(text='Done', callback_data=str(END))
|
||||
]]
|
||||
keyboard = InlineKeyboardMarkup(buttons)
|
||||
|
||||
# If we're starting over we don't need do send a new message
|
||||
if context.user_data.get(START_OVER):
|
||||
update.callback_query.edit_message_text(text=text, reply_markup=keyboard)
|
||||
else:
|
||||
update.message.reply_text('Hi, I\'m FamiliyBot and here to help you gather information'
|
||||
'about your family.')
|
||||
update.message.reply_text(text=text, reply_markup=keyboard)
|
||||
|
||||
context.user_data[START_OVER] = False
|
||||
return SELECTING_ACTION
|
||||
|
||||
|
||||
def adding_self(update, context):
|
||||
"""Add information about youself."""
|
||||
context.user_data[CURRENT_LEVEL] = SELF
|
||||
text = 'Okay, please tell me about yourself.'
|
||||
button = InlineKeyboardButton(text='Add info', callback_data=str(MALE))
|
||||
keyboard = InlineKeyboardMarkup.from_button(button)
|
||||
|
||||
update.callback_query.edit_message_text(text=text, reply_markup=keyboard)
|
||||
|
||||
return DESCRIBING_SELF
|
||||
|
||||
|
||||
def show_data(update, context):
|
||||
"""Pretty print gathered data."""
|
||||
def prettyprint(user_data, level):
|
||||
people = user_data.get(level)
|
||||
if not people:
|
||||
return '\nNo information yet.'
|
||||
|
||||
text = ''
|
||||
if level == SELF:
|
||||
for person in user_data[level]:
|
||||
text += '\nName: {0}, Age: {1}'.format(person.get(NAME, '-'), person.get(AGE, '-'))
|
||||
else:
|
||||
male, female = _name_switcher(level)
|
||||
|
||||
for person in user_data[level]:
|
||||
gender = female if person[GENDER] == FEMALE else male
|
||||
text += '\n{0}: Name: {1}, Age: {2}'.format(gender, person.get(NAME, '-'),
|
||||
person.get(AGE, '-'))
|
||||
return text
|
||||
|
||||
ud = context.user_data
|
||||
text = 'Yourself:' + prettyprint(ud, SELF)
|
||||
text += '\n\nParents:' + prettyprint(ud, PARENTS)
|
||||
text += '\n\nChildren:' + prettyprint(ud, CHILDREN)
|
||||
|
||||
buttons = [[
|
||||
InlineKeyboardButton(text='Back', callback_data=str(END))
|
||||
]]
|
||||
keyboard = InlineKeyboardMarkup(buttons)
|
||||
|
||||
update.callback_query.edit_message_text(text=text, reply_markup=keyboard)
|
||||
ud[START_OVER] = True
|
||||
|
||||
return SHOWING
|
||||
|
||||
|
||||
def stop(update, context):
|
||||
"""End Conversation by command."""
|
||||
update.message.reply_text('Okay, bye.')
|
||||
|
||||
return END
|
||||
|
||||
|
||||
def end(update, context):
|
||||
"""End conversation from InlineKeyboardButton."""
|
||||
text = 'See you around!'
|
||||
update.callback_query.edit_message_text(text=text)
|
||||
|
||||
return END
|
||||
|
||||
|
||||
# Second level conversation callbacks
|
||||
def select_level(update, context):
|
||||
"""Choose to add a parent or a child."""
|
||||
text = 'You may add a parent or a child. Also you can show the gathered data or go back.'
|
||||
buttons = [[
|
||||
InlineKeyboardButton(text='Add parent', callback_data=str(PARENTS)),
|
||||
InlineKeyboardButton(text='Add child', callback_data=str(CHILDREN))
|
||||
], [
|
||||
InlineKeyboardButton(text='Show data', callback_data=str(SHOWING)),
|
||||
InlineKeyboardButton(text='Back', callback_data=str(END))
|
||||
]]
|
||||
keyboard = InlineKeyboardMarkup(buttons)
|
||||
update.callback_query.edit_message_text(text=text, reply_markup=keyboard)
|
||||
|
||||
return SELECTING_LEVEL
|
||||
|
||||
|
||||
def select_gender(update, context):
|
||||
"""Choose to add mother or father."""
|
||||
level = update.callback_query.data
|
||||
context.user_data[CURRENT_LEVEL] = level
|
||||
|
||||
text = 'Please choose, whom to add.'
|
||||
|
||||
male, female = _name_switcher(level)
|
||||
|
||||
buttons = [[
|
||||
InlineKeyboardButton(text='Add ' + male, callback_data=str(MALE)),
|
||||
InlineKeyboardButton(text='Add ' + female, callback_data=str(FEMALE))
|
||||
], [
|
||||
InlineKeyboardButton(text='Show data', callback_data=str(SHOWING)),
|
||||
InlineKeyboardButton(text='Back', callback_data=str(END))
|
||||
]]
|
||||
|
||||
keyboard = InlineKeyboardMarkup(buttons)
|
||||
update.callback_query.edit_message_text(text=text, reply_markup=keyboard)
|
||||
|
||||
return SELECTING_GENDER
|
||||
|
||||
|
||||
def end_second_level(update, context):
|
||||
"""Return to top level conversation."""
|
||||
context.user_data[START_OVER] = True
|
||||
start(update, context)
|
||||
|
||||
return END
|
||||
|
||||
|
||||
# Third level callbacks
|
||||
def select_feature(update, context):
|
||||
"""Select a feature to update for the person."""
|
||||
buttons = [[
|
||||
InlineKeyboardButton(text='Name', callback_data=str(NAME)),
|
||||
InlineKeyboardButton(text='Age', callback_data=str(AGE)),
|
||||
InlineKeyboardButton(text='Done', callback_data=str(END)),
|
||||
]]
|
||||
keyboard = InlineKeyboardMarkup(buttons)
|
||||
|
||||
# If we collect features for a new person, clear the cache and save the gender
|
||||
if not context.user_data.get(START_OVER):
|
||||
context.user_data[FEATURES] = {GENDER: update.callback_query.data}
|
||||
text = 'Please select a feature to update.'
|
||||
update.callback_query.edit_message_text(text=text, reply_markup=keyboard)
|
||||
# But after we do that, we need to send a new message
|
||||
else:
|
||||
text = 'Got it! Please select a feature to update.'
|
||||
update.message.reply_text(text=text, reply_markup=keyboard)
|
||||
|
||||
context.user_data[START_OVER] = False
|
||||
return SELECTING_FEATURE
|
||||
|
||||
|
||||
def ask_for_input(update, context):
|
||||
"""Prompt user to input data for selected feature."""
|
||||
context.user_data[CURRENT_FEATURE] = update.callback_query.data
|
||||
text = 'Okay, tell me.'
|
||||
update.callback_query.edit_message_text(text=text)
|
||||
|
||||
return TYPING
|
||||
|
||||
|
||||
def save_input(update, context):
|
||||
"""Save input for feature and return to feature selection."""
|
||||
ud = context.user_data
|
||||
ud[FEATURES][ud[CURRENT_FEATURE]] = update.message.text
|
||||
|
||||
ud[START_OVER] = True
|
||||
|
||||
return select_feature(update, context)
|
||||
|
||||
|
||||
def end_describing(update, context):
|
||||
"""End gathering of features and return to parent conversation."""
|
||||
ud = context.user_data
|
||||
level = ud[CURRENT_LEVEL]
|
||||
if not ud.get(level):
|
||||
ud[level] = []
|
||||
ud[level].append(ud[FEATURES])
|
||||
|
||||
# Print upper level menu
|
||||
if level == SELF:
|
||||
ud[START_OVER] = True
|
||||
start(update, context)
|
||||
else:
|
||||
select_level(update, context)
|
||||
|
||||
return END
|
||||
|
||||
|
||||
def stop_nested(update, context):
|
||||
"""Completely end conversation from within nested conversation."""
|
||||
update.message.reply_text('Okay, bye.')
|
||||
|
||||
return STOPPING
|
||||
|
||||
|
||||
# Error handler
|
||||
def error(update, context):
|
||||
"""Log Errors caused by Updates."""
|
||||
logger.warning('Update "%s" caused error "%s"', update, context.error)
|
||||
|
||||
|
||||
def main():
|
||||
# Create the Updater and pass it your bot's token.
|
||||
# Make sure to set use_context=True to use the new context based callbacks
|
||||
# Post version 12 this will no longer be necessary
|
||||
updater = Updater("TOKEN", use_context=True)
|
||||
|
||||
# Get the dispatcher to register handlers
|
||||
dp = updater.dispatcher
|
||||
|
||||
# Set up third level ConversationHandler (collecting features)
|
||||
description_conv = ConversationHandler(
|
||||
entry_points=[CallbackQueryHandler(select_feature,
|
||||
pattern='^' + str(MALE) + '$|^' + str(FEMALE) + '$')],
|
||||
|
||||
states={
|
||||
SELECTING_FEATURE: [CallbackQueryHandler(ask_for_input,
|
||||
pattern='^(?!' + str(END) + ').*$')],
|
||||
TYPING: [MessageHandler(Filters.text, save_input)],
|
||||
},
|
||||
|
||||
fallbacks=[
|
||||
CallbackQueryHandler(end_describing, pattern='^' + str(END) + '$'),
|
||||
CommandHandler('stop', stop_nested)
|
||||
],
|
||||
|
||||
map_to_parent={
|
||||
# Return to second level menu
|
||||
END: SELECTING_LEVEL,
|
||||
# End conversation alltogether
|
||||
STOPPING: STOPPING,
|
||||
}
|
||||
)
|
||||
|
||||
# Set up second level ConversationHandler (adding a person)
|
||||
add_member_conv = ConversationHandler(
|
||||
entry_points=[CallbackQueryHandler(select_level,
|
||||
pattern='^' + str(ADDING_MEMBER) + '$')],
|
||||
|
||||
states={
|
||||
SELECTING_LEVEL: [CallbackQueryHandler(select_gender,
|
||||
pattern='^{0}$|^{1}$'.format(str(PARENTS),
|
||||
str(CHILDREN)))],
|
||||
SELECTING_GENDER: [description_conv]
|
||||
},
|
||||
|
||||
fallbacks=[
|
||||
CallbackQueryHandler(show_data, pattern='^' + str(SHOWING) + '$'),
|
||||
CallbackQueryHandler(end_second_level, pattern='^' + str(END) + '$'),
|
||||
CommandHandler('stop', stop_nested)
|
||||
],
|
||||
|
||||
map_to_parent={
|
||||
# After showing data return to top level menu
|
||||
SHOWING: SHOWING,
|
||||
# Return to top level menu
|
||||
END: SELECTING_ACTION,
|
||||
# End conversation alltogether
|
||||
STOPPING: END,
|
||||
}
|
||||
)
|
||||
|
||||
# Set up top level ConversationHandler (selecting action)
|
||||
conv_handler = ConversationHandler(
|
||||
entry_points=[CommandHandler('start', start)],
|
||||
|
||||
states={
|
||||
SHOWING: [CallbackQueryHandler(start, pattern='^' + str(END) + '$')],
|
||||
SELECTING_ACTION: [
|
||||
add_member_conv,
|
||||
CallbackQueryHandler(show_data, pattern='^' + str(SHOWING) + '$'),
|
||||
CallbackQueryHandler(adding_self, pattern='^' + str(ADDING_SELF) + '$'),
|
||||
CallbackQueryHandler(end, pattern='^' + str(END) + '$'),
|
||||
],
|
||||
DESCRIBING_SELF: [description_conv],
|
||||
},
|
||||
|
||||
fallbacks=[CommandHandler('stop', stop)],
|
||||
)
|
||||
# Because the states of the third level conversation map to the ones of the
|
||||
# second level conversation, we need to be a bit hacky about that:
|
||||
conv_handler.states[SELECTING_LEVEL] = conv_handler.states[SELECTING_ACTION]
|
||||
conv_handler.states[STOPPING] = conv_handler.entry_points
|
||||
|
||||
dp.add_handler(conv_handler)
|
||||
|
||||
# log all errors
|
||||
dp.add_error_handler(error)
|
||||
|
||||
# Start the Bot
|
||||
updater.start_polling()
|
||||
|
||||
# Run the bot until you press Ctrl-C or the process receives SIGINT,
|
||||
# SIGTERM or SIGABRT. This should be used most of the time, since
|
||||
# start_polling() is non-blocking and will stop the bot gracefully.
|
||||
updater.idle()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,10 +1,6 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
#
|
||||
# THIS EXAMPLE HAS BEEN UPDATED TO WORK WITH THE BETA VERSION 12 OF PYTHON-TELEGRAM-BOT.
|
||||
# If you're still using version 11.1.0, please see the examples at
|
||||
# https://github.com/python-telegram-bot/python-telegram-bot/tree/v11.1.0/examples
|
||||
|
||||
"""
|
||||
Simple Bot to print/download all incoming passport data
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
#
|
||||
# THIS EXAMPLE HAS BEEN UPDATED TO WORK WITH THE BETA VERSION 12 OF PYTHON-TELEGRAM-BOT.
|
||||
# If you're still using version 11.1.0, please see the examples at
|
||||
# https://github.com/python-telegram-bot/python-telegram-bot/tree/v11.1.0/examples
|
||||
|
||||
"""
|
||||
Basic example for a bot that can receive payment from user.
|
||||
|
@ -107,9 +103,9 @@ def precheckout_callback(update, context):
|
|||
query.answer(ok=True)
|
||||
|
||||
|
||||
# finally, after contacting to the payment provider...
|
||||
# finally, after contacting the payment provider...
|
||||
def successful_payment_callback(update, context):
|
||||
# do something after successful receive of payment?
|
||||
# do something after successfully receiving payment?
|
||||
update.message.reply_text("Thank you for your payment!")
|
||||
|
||||
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
#
|
||||
# THIS EXAMPLE HAS BEEN UPDATED TO WORK WITH THE BETA VERSION 12 OF PYTHON-TELEGRAM-BOT.
|
||||
# If you're still using version 11.1.0, please see the examples at
|
||||
# https://github.com/python-telegram-bot/python-telegram-bot/tree/v11.1.0/examples
|
||||
|
||||
"""
|
||||
First, a few callback functions are defined. Then, those functions are passed to
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
#
|
||||
# THIS EXAMPLE HAS BEEN UPDATED TO WORK WITH THE BETA VERSION 12 OF PYTHON-TELEGRAM-BOT.
|
||||
# If you're still using version 11.1.0, please see the examples at
|
||||
# https://github.com/python-telegram-bot/python-telegram-bot/tree/v11.1.0/examples
|
||||
|
||||
"""
|
||||
Simple Bot to send timed Telegram messages.
|
||||
|
@ -33,8 +29,8 @@ logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# Define a few command handlers. These usually take the two arguments bot and
|
||||
# update. Error handlers also receive the raised TelegramError object in error.
|
||||
# Define a few command handlers. These usually take the two arguments update and
|
||||
# context. Error handlers also receive the raised TelegramError object in error.
|
||||
def start(update, context):
|
||||
update.message.reply_text('Hi! Use /set <seconds> to set a timer')
|
||||
|
||||
|
|
|
@ -8,3 +8,4 @@ beautifulsoup4
|
|||
pytest==4.2.0
|
||||
pytest-timeout
|
||||
wheel
|
||||
attrs==19.1.0
|
||||
|
|
|
@ -14,7 +14,8 @@ upload-dir = docs/build/html
|
|||
|
||||
[flake8]
|
||||
max-line-length = 99
|
||||
ignore = W503
|
||||
ignore = W503, W605
|
||||
exclude = setup.py, docs/source/conf.py
|
||||
|
||||
[yapf]
|
||||
based_on_style = google
|
||||
|
|
|
@ -23,6 +23,7 @@ from .user import User
|
|||
from .files.chatphoto import ChatPhoto
|
||||
from .chat import Chat
|
||||
from .chatmember import ChatMember
|
||||
from .chatpermissions import ChatPermissions
|
||||
from .files.photosize import PhotoSize
|
||||
from .files.audio import Audio
|
||||
from .files.voice import Voice
|
||||
|
@ -125,8 +126,8 @@ from .version import __version__ # noqa: F401
|
|||
__author__ = 'devs@python-telegram-bot.org'
|
||||
|
||||
__all__ = [
|
||||
'Audio', 'Bot', 'Chat', 'ChatMember', 'ChatAction', 'ChosenInlineResult', 'CallbackQuery',
|
||||
'Contact', 'Document', 'File', 'ForceReply', 'InlineKeyboardButton',
|
||||
'Audio', 'Bot', 'Chat', 'ChatMember', 'ChatPermissions', 'ChatAction', 'ChosenInlineResult',
|
||||
'CallbackQuery', 'Contact', 'Document', 'File', 'ForceReply', 'InlineKeyboardButton',
|
||||
'InlineKeyboardMarkup', 'InlineQuery', 'InlineQueryResult', 'InlineQueryResult',
|
||||
'InlineQueryResultArticle', 'InlineQueryResultAudio', 'InlineQueryResultCachedAudio',
|
||||
'InlineQueryResultCachedDocument', 'InlineQueryResultCachedGif',
|
||||
|
|
|
@ -1545,7 +1545,8 @@ class Bot(TelegramObject):
|
|||
calling get_file again.
|
||||
|
||||
Args:
|
||||
file_id (:obj:`str` | :class:`telegram.Audio` | :class:`telegram.Document` | \
|
||||
file_id (:obj:`str` | :class:`telegram.Animation` | :class:`telegram.Audio` | \
|
||||
:class:`telegram.ChatPhoto` | :class:`telegram.Document` | \
|
||||
:class:`telegram.PhotoSize` | :class:`telegram.Sticker` | \
|
||||
:class:`telegram.Video` | :class:`telegram.VideoNote` | \
|
||||
:class:`telegram.Voice`):
|
||||
|
@ -2720,14 +2721,18 @@ class Bot(TelegramObject):
|
|||
return result
|
||||
|
||||
@log
|
||||
def restrict_chat_member(self, chat_id, user_id, until_date=None, can_send_messages=None,
|
||||
can_send_media_messages=None, can_send_other_messages=None,
|
||||
can_add_web_page_previews=None, timeout=None, **kwargs):
|
||||
def restrict_chat_member(self, chat_id, user_id, permissions, until_date=None,
|
||||
timeout=None, **kwargs):
|
||||
"""
|
||||
Use this method to restrict a user in a supergroup. The bot must be an administrator in
|
||||
the supergroup for this to work and must have the appropriate admin rights. Pass True for
|
||||
all boolean parameters to lift restrictions from a user.
|
||||
|
||||
Note:
|
||||
Since Bot API 4.4, :attr:`restrict_chat_member` takes the new user permissions in a
|
||||
single argument of type :class:`telegram.ChatPermissions`. The old way of passing
|
||||
parameters will not keep working forever.
|
||||
|
||||
Args:
|
||||
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
|
||||
of the target supergroup (in the format @supergroupusername).
|
||||
|
@ -2736,15 +2741,7 @@ class Bot(TelegramObject):
|
|||
will be lifted for the user, unix time. If user is restricted for more than 366
|
||||
days or less than 30 seconds from the current time, they are considered to be
|
||||
restricted forever.
|
||||
can_send_messages (:obj:`bool`, optional): Pass True, if the user can send text
|
||||
messages, contacts, locations and venues.
|
||||
can_send_media_messages (:obj:`bool`, optional): Pass True, if the user can send
|
||||
audios, documents, photos, videos, video notes and voice notes, implies
|
||||
can_send_messages.
|
||||
can_send_other_messages (:obj:`bool`, optional): Pass True, if the user can send
|
||||
animations, games, stickers and use inline bots, implies can_send_media_messages.
|
||||
can_add_web_page_previews (:obj:`bool`, optional): Pass True, if the user may add
|
||||
web page previews to their messages, implies can_send_media_messages.
|
||||
permissions (:class:`telegram.ChatPermissions`): New user permissions.
|
||||
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
|
||||
the read timeout from the server (instead of the one specified during creation of
|
||||
the connection pool).
|
||||
|
@ -2755,24 +2752,15 @@ class Bot(TelegramObject):
|
|||
|
||||
Raises:
|
||||
:class:`telegram.TelegramError`
|
||||
|
||||
"""
|
||||
url = '{0}/restrictChatMember'.format(self.base_url)
|
||||
|
||||
data = {'chat_id': chat_id, 'user_id': user_id}
|
||||
data = {'chat_id': chat_id, 'user_id': user_id, 'permissions': permissions.to_dict()}
|
||||
|
||||
if until_date is not None:
|
||||
if isinstance(until_date, datetime):
|
||||
until_date = to_timestamp(until_date)
|
||||
data['until_date'] = until_date
|
||||
if can_send_messages is not None:
|
||||
data['can_send_messages'] = can_send_messages
|
||||
if can_send_media_messages is not None:
|
||||
data['can_send_media_messages'] = can_send_media_messages
|
||||
if can_send_other_messages is not None:
|
||||
data['can_send_other_messages'] = can_send_other_messages
|
||||
if can_add_web_page_previews is not None:
|
||||
data['can_add_web_page_previews'] = can_add_web_page_previews
|
||||
data.update(kwargs)
|
||||
|
||||
result = self._request.post(url, data, timeout=timeout)
|
||||
|
@ -2850,6 +2838,38 @@ class Bot(TelegramObject):
|
|||
|
||||
return result
|
||||
|
||||
@log
|
||||
def set_chat_permissions(self, chat_id, permissions, timeout=None, **kwargs):
|
||||
"""
|
||||
Use this method to set default chat permissions for all members. The bot must be an
|
||||
administrator in the group or a supergroup for this to work and must have the
|
||||
:attr:`can_restrict_members` admin rights. Returns True on success.
|
||||
|
||||
Args:
|
||||
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username of
|
||||
the target supergroup (in the format `@supergroupusername`).
|
||||
permissions (:class:`telegram.ChatPermissions`): New default chat permissions.
|
||||
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
|
||||
the read timeout from the server (instead of the one specified during creation of
|
||||
the connection pool).
|
||||
**kwargs (:obj:`dict`): Arbitrary keyword arguments
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: Returns True on success.
|
||||
|
||||
Raises:
|
||||
:class:`telegram.TelegramError`
|
||||
|
||||
"""
|
||||
url = '{0}/setChatPermissions'.format(self.base_url)
|
||||
|
||||
data = {'chat_id': chat_id, 'permissions': permissions.to_dict()}
|
||||
data.update(kwargs)
|
||||
|
||||
result = self._request.post(url, data, timeout=timeout)
|
||||
|
||||
return result
|
||||
|
||||
@log
|
||||
def export_chat_invite_link(self, chat_id, timeout=None, **kwargs):
|
||||
"""
|
||||
|
@ -2993,8 +3013,9 @@ class Bot(TelegramObject):
|
|||
@log
|
||||
def set_chat_description(self, chat_id, description, timeout=None, **kwargs):
|
||||
"""
|
||||
Use this method to change the description of a supergroup or a channel. The bot must be an
|
||||
administrator in the chat for this to work and must have the appropriate admin rights.
|
||||
Use this method to change the description of a group, a supergroup or a channel. The bot
|
||||
must be an administrator in the chat for this to work and must have the appropriate admin
|
||||
rights.
|
||||
|
||||
Args:
|
||||
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
|
||||
|
@ -3453,7 +3474,7 @@ class Bot(TelegramObject):
|
|||
return Poll.de_json(result, self)
|
||||
|
||||
def to_dict(self):
|
||||
data = {'id': self.id, 'username': self.username, 'first_name': self.username}
|
||||
data = {'id': self.id, 'username': self.username, 'first_name': self.first_name}
|
||||
|
||||
if self.last_name:
|
||||
data['last_name'] = self.last_name
|
||||
|
@ -3561,6 +3582,8 @@ class Bot(TelegramObject):
|
|||
"""Alias for :attr:`restrict_chat_member`"""
|
||||
promoteChatMember = promote_chat_member
|
||||
"""Alias for :attr:`promote_chat_member`"""
|
||||
setChatPermissions = set_chat_permissions
|
||||
"""Alias for :attr:`set_chat_permissions`"""
|
||||
exportChatInviteLink = export_chat_invite_link
|
||||
"""Alias for :attr:`export_chat_invite_link`"""
|
||||
setChatPhoto = set_chat_photo
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
"""This module contains an object that represents a Telegram Chat."""
|
||||
|
||||
from telegram import TelegramObject, ChatPhoto
|
||||
from .chatpermissions import ChatPermissions
|
||||
|
||||
|
||||
class Chat(TelegramObject):
|
||||
|
@ -32,12 +33,13 @@ class Chat(TelegramObject):
|
|||
username (:obj:`str`): Optional. Username.
|
||||
first_name (:obj:`str`): Optional. First name of the other party in a private chat.
|
||||
last_name (:obj:`str`): Optional. Last name of the other party in a private chat.
|
||||
all_members_are_administrators (:obj:`bool`): Optional.
|
||||
photo (:class:`telegram.ChatPhoto`): Optional. Chat photo.
|
||||
description (:obj:`str`): Optional. Description, for supergroups and channel chats.
|
||||
description (:obj:`str`): Optional. Description, for groups, supergroups and channel chats.
|
||||
invite_link (:obj:`str`): Optional. Chat invite link, for supergroups and channel chats.
|
||||
pinned_message (:class:`telegram.Message`): Optional. Pinned message, for supergroups.
|
||||
Returned only in get_chat.
|
||||
permissions (:class:`telegram.ChatPermission`): Optional. Default chat member permissions,
|
||||
for groups and supergroups. Returned only in getChat.
|
||||
sticker_set_name (:obj:`str`): Optional. For supergroups, name of Group sticker set.
|
||||
can_set_sticker_set (:obj:`bool`): Optional. ``True``, if the bot can change group the
|
||||
sticker set.
|
||||
|
@ -54,15 +56,15 @@ class Chat(TelegramObject):
|
|||
available.
|
||||
first_name(:obj:`str`, optional): First name of the other party in a private chat.
|
||||
last_name(:obj:`str`, optional): Last name of the other party in a private chat.
|
||||
all_members_are_administrators (:obj:`bool`, optional): True if a group has `All Members
|
||||
Are Admins` enabled.
|
||||
photo (:class:`telegram.ChatPhoto`, optional): Chat photo. Returned only in getChat.
|
||||
description (:obj:`str`, optional): Description, for supergroups and channel chats.
|
||||
description (:obj:`str`, optional): Description, for groups, supergroups and channel chats.
|
||||
Returned only in get_chat.
|
||||
invite_link (:obj:`str`, optional): Chat invite link, for supergroups and channel chats.
|
||||
Returned only in get_chat.
|
||||
pinned_message (:class:`telegram.Message`, optional): Pinned message, for supergroups.
|
||||
Returned only in get_chat.
|
||||
permissions (:class:`telegram.ChatPermission`): Optional. Default chat member permissions,
|
||||
for groups and supergroups. Returned only in getChat.
|
||||
bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods.
|
||||
sticker_set_name (:obj:`str`, optional): For supergroups, name of Group sticker set.
|
||||
Returned only in get_chat.
|
||||
|
@ -88,12 +90,12 @@ class Chat(TelegramObject):
|
|||
username=None,
|
||||
first_name=None,
|
||||
last_name=None,
|
||||
all_members_are_administrators=None,
|
||||
bot=None,
|
||||
photo=None,
|
||||
description=None,
|
||||
invite_link=None,
|
||||
pinned_message=None,
|
||||
permissions=None,
|
||||
sticker_set_name=None,
|
||||
can_set_sticker_set=None,
|
||||
**kwargs):
|
||||
|
@ -105,11 +107,13 @@ class Chat(TelegramObject):
|
|||
self.username = username
|
||||
self.first_name = first_name
|
||||
self.last_name = last_name
|
||||
self.all_members_are_administrators = all_members_are_administrators
|
||||
# TODO: Remove (also from tests), when Telegram drops this completely
|
||||
self.all_members_are_administrators = kwargs.get('all_members_are_administrators')
|
||||
self.photo = photo
|
||||
self.description = description
|
||||
self.invite_link = invite_link
|
||||
self.pinned_message = pinned_message
|
||||
self.permissions = permissions
|
||||
self.sticker_set_name = sticker_set_name
|
||||
self.can_set_sticker_set = can_set_sticker_set
|
||||
|
||||
|
@ -132,6 +136,7 @@ class Chat(TelegramObject):
|
|||
data['photo'] = ChatPhoto.de_json(data.get('photo'), bot)
|
||||
from telegram import Message
|
||||
data['pinned_message'] = Message.de_json(data.get('pinned_message'), bot)
|
||||
data['permissions'] = ChatPermissions.de_json(data.get('permissions'), bot)
|
||||
|
||||
return cls(bot=bot, **data)
|
||||
|
||||
|
@ -221,6 +226,17 @@ class Chat(TelegramObject):
|
|||
"""
|
||||
return self.bot.unban_chat_member(self.id, *args, **kwargs)
|
||||
|
||||
def set_permissions(self, *args, **kwargs):
|
||||
"""Shortcut for::
|
||||
|
||||
bot.set_chat_permissions(update.message.chat.id, *args, **kwargs)
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: If the action was sent successfully.
|
||||
|
||||
"""
|
||||
return self.bot.set_chat_permissions(self.id, *args, **kwargs)
|
||||
|
||||
def send_message(self, *args, **kwargs):
|
||||
"""Shortcut for::
|
||||
|
||||
|
|
|
@ -32,18 +32,17 @@ class ChatMember(TelegramObject):
|
|||
for this user.
|
||||
can_be_edited (:obj:`bool`): Optional. If the bot is allowed to edit administrator
|
||||
privileges of that user.
|
||||
can_change_info (:obj:`bool`): Optional. If the administrator can change the chat title,
|
||||
photo and other settings.
|
||||
can_change_info (:obj:`bool`): Optional. If the user can change the chat title, photo and
|
||||
other settings.
|
||||
can_post_messages (:obj:`bool`): Optional. If the administrator can post in the channel.
|
||||
can_edit_messages (:obj:`bool`): Optional. If the administrator can edit messages of other
|
||||
users.
|
||||
can_delete_messages (:obj:`bool`): Optional. If the administrator can delete messages of
|
||||
other users.
|
||||
can_invite_users (:obj:`bool`): Optional. If the administrator can invite new users to the
|
||||
chat.
|
||||
can_invite_users (:obj:`bool`): Optional. If the user can invite new users to the chat.
|
||||
can_restrict_members (:obj:`bool`): Optional. If the administrator can restrict, ban or
|
||||
unban chat members.
|
||||
can_pin_messages (:obj:`bool`): Optional. If the administrator can pin messages.
|
||||
can_pin_messages (:obj:`bool`): Optional. If the user can pin messages.
|
||||
can_promote_members (:obj:`bool`): Optional. If the administrator can add new
|
||||
administrators.
|
||||
is_member (:obj:`bool`): Optional. Restricted only. True, if the user is a member of the
|
||||
|
@ -52,6 +51,8 @@ class ChatMember(TelegramObject):
|
|||
locations and venues.
|
||||
can_send_media_messages (:obj:`bool`): Optional. If the user can send media messages,
|
||||
implies can_send_messages.
|
||||
can_send_polls (:obj:`bool`): Optional. True, if the user is allowed to
|
||||
send polls.
|
||||
can_send_other_messages (:obj:`bool`): Optional. If the user can send animations, games,
|
||||
stickers and use inline bots, implies can_send_media_messages.
|
||||
can_add_web_page_previews (:obj:`bool`): Optional. If user may add web page previews to his
|
||||
|
@ -65,20 +66,20 @@ class ChatMember(TelegramObject):
|
|||
restrictions will be lifted for this user.
|
||||
can_be_edited (:obj:`bool`, optional): Administrators only. True, if the bot is allowed to
|
||||
edit administrator privileges of that user.
|
||||
can_change_info (:obj:`bool`, optional): Administrators only. True, if the administrator
|
||||
can change the chat title, photo and other settings.
|
||||
can_change_info (:obj:`bool`, optional): Administrators and restricted only. True, if the
|
||||
user can change the chat title, photo and other settings.
|
||||
can_post_messages (:obj:`bool`, optional): Administrators only. True, if the administrator
|
||||
can post in the channel, channels only.
|
||||
can_edit_messages (:obj:`bool`, optional): Administrators only. True, if the administrator
|
||||
can edit messages of other users, channels only.
|
||||
can_delete_messages (:obj:`bool`, optional): Administrators only. True, if the
|
||||
administrator can delete messages of other user.
|
||||
can_invite_users (:obj:`bool`, optional): Administrators only. True, if the administrator
|
||||
can invite new users to the chat.
|
||||
can_invite_users (:obj:`bool`, optional): Administrators and restricted only. True, if the
|
||||
user can invite new users to the chat.
|
||||
can_restrict_members (:obj:`bool`, optional): Administrators only. True, if the
|
||||
administrator can restrict, ban or unban chat members.
|
||||
can_pin_messages (:obj:`bool`, optional): Administrators only. True, if the administrator
|
||||
can pin messages, supergroups only.
|
||||
can_pin_messages (:obj:`bool`, optional): Administrators and restricted only. True, if the
|
||||
user can pin messages, supergroups only.
|
||||
can_promote_members (:obj:`bool`, optional): Administrators only. True, if the
|
||||
administrator can add new administrators with a subset of his own privileges or demote
|
||||
administrators that he has promoted, directly or indirectly (promoted by administrators
|
||||
|
@ -90,6 +91,8 @@ class ChatMember(TelegramObject):
|
|||
can_send_media_messages (:obj:`bool`, optional): Restricted only. True, if the user can
|
||||
send audios, documents, photos, videos, video notes and voice notes, implies
|
||||
can_send_messages.
|
||||
can_send_polls (:obj:`bool`, optional): Restricted only. True, if the user is allowed to
|
||||
send polls.
|
||||
can_send_other_messages (:obj:`bool`, optional): Restricted only. True, if the user can
|
||||
send animations, games, stickers and use inline bots, implies can_send_media_messages.
|
||||
can_add_web_page_previews (:obj:`bool`, optional): Restricted only. True, if user may add
|
||||
|
@ -114,7 +117,7 @@ class ChatMember(TelegramObject):
|
|||
can_delete_messages=None, can_invite_users=None,
|
||||
can_restrict_members=None, can_pin_messages=None,
|
||||
can_promote_members=None, can_send_messages=None,
|
||||
can_send_media_messages=None, can_send_other_messages=None,
|
||||
can_send_media_messages=None, can_send_polls=None, can_send_other_messages=None,
|
||||
can_add_web_page_previews=None, is_member=None, **kwargs):
|
||||
# Required
|
||||
self.user = user
|
||||
|
@ -131,6 +134,7 @@ class ChatMember(TelegramObject):
|
|||
self.can_promote_members = can_promote_members
|
||||
self.can_send_messages = can_send_messages
|
||||
self.can_send_media_messages = can_send_media_messages
|
||||
self.can_send_polls = can_send_polls
|
||||
self.can_send_other_messages = can_send_other_messages
|
||||
self.can_add_web_page_previews = can_add_web_page_previews
|
||||
self.is_member = is_member
|
||||
|
|
87
telegram/chatpermissions.py
Normal file
87
telegram/chatpermissions.py
Normal file
|
@ -0,0 +1,87 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2018
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program 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 Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains an object that represents a Telegram ChatPermission."""
|
||||
|
||||
from telegram import TelegramObject
|
||||
|
||||
|
||||
class ChatPermissions(TelegramObject):
|
||||
"""Describes actions that a non-administrator user is allowed to take in a chat.
|
||||
|
||||
Attributes:
|
||||
can_send_messages (:obj:`bool`): Optional. True, if the user is allowed to send text
|
||||
messages, contacts, locations and venues.
|
||||
can_send_media_messages (:obj:`bool`): Optional. True, if the user is allowed to send
|
||||
audios, documents, photos, videos, video notes and voice notes, implies
|
||||
:attr:`can_send_messages`.
|
||||
can_send_polls (:obj:`bool`): Optional. True, if the user is allowed to send polls, implies
|
||||
:attr:`can_send_messages`.
|
||||
can_send_other_messages (:obj:`bool`): Optional. True, if the user is allowed to send
|
||||
animations, games, stickers and use inline bots, implies
|
||||
:attr:`can_send_media_messages`.
|
||||
can_add_web_page_previews (:obj:`bool`): Optional. True, if the user is allowed to add web
|
||||
page previews to their messages, implies :attr:`can_send_media_messages`.
|
||||
can_change_info (:obj:`bool`): Optional. True, if the user is allowed to change the chat
|
||||
title, photo and other settings. Ignored in public supergroups.
|
||||
can_invite_users (:obj:`bool`): Optional. True, if the user is allowed to invite new users
|
||||
to the chat.
|
||||
can_pin_messages (:obj:`bool`): Optional. True, if the user is allowed to pin messages.
|
||||
Ignored in public supergroups.
|
||||
|
||||
Args:
|
||||
can_send_messages (:obj:`bool`, optional): True, if the user is allowed to send text
|
||||
messages, contacts, locations and venues.
|
||||
can_send_media_messages (:obj:`bool`, optional): True, if the user is allowed to send
|
||||
audios, documents, photos, videos, video notes and voice notes, implies
|
||||
:attr:`can_send_messages`.
|
||||
can_send_polls (:obj:`bool`, optional): True, if the user is allowed to send polls, implies
|
||||
:attr:`can_send_messages`.
|
||||
can_send_other_messages (:obj:`bool`, optional): True, if the user is allowed to send
|
||||
animations, games, stickers and use inline bots, implies
|
||||
:attr:`can_send_media_messages`.
|
||||
can_add_web_page_previews (:obj:`bool`, optional): True, if the user is allowed to add web
|
||||
page previews to their messages, implies :attr:`can_send_media_messages`.
|
||||
can_change_info (:obj:`bool`, optional): True, if the user is allowed to change the chat
|
||||
title, photo and other settings. Ignored in public supergroups.
|
||||
can_invite_users (:obj:`bool`, optional): True, if the user is allowed to invite new users
|
||||
to the chat.
|
||||
can_pin_messages (:obj:`bool`, optional): True, if the user is allowed to pin messages.
|
||||
Ignored in public supergroups.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, can_send_messages=None, can_send_media_messages=None, can_send_polls=None,
|
||||
can_send_other_messages=None, can_add_web_page_previews=None,
|
||||
can_change_info=None, can_invite_users=None, can_pin_messages=None, **kwargs):
|
||||
# Required
|
||||
self.can_send_messages = can_send_messages
|
||||
self.can_send_media_messages = can_send_media_messages
|
||||
self.can_send_polls = can_send_polls
|
||||
self.can_send_other_messages = can_send_other_messages
|
||||
self.can_add_web_page_previews = can_add_web_page_previews
|
||||
self.can_change_info = can_change_info
|
||||
self.can_invite_users = can_invite_users
|
||||
self.can_pin_messages = can_pin_messages
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data, bot):
|
||||
if not data:
|
||||
return None
|
||||
|
||||
return cls(**data)
|
|
@ -73,13 +73,31 @@ class CallbackContext(object):
|
|||
raise ValueError('CallbackContext should not be used with a non context aware '
|
||||
'dispatcher!')
|
||||
self._dispatcher = dispatcher
|
||||
self.chat_data = None
|
||||
self.user_data = None
|
||||
self._chat_data = None
|
||||
self._user_data = None
|
||||
self.args = None
|
||||
self.matches = None
|
||||
self.error = None
|
||||
self.job = None
|
||||
|
||||
@property
|
||||
def chat_data(self):
|
||||
return self._chat_data
|
||||
|
||||
@chat_data.setter
|
||||
def chat_data(self, value):
|
||||
raise AttributeError("You can not assign a new value to chat_data, see "
|
||||
"https://git.io/fjxKe")
|
||||
|
||||
@property
|
||||
def user_data(self):
|
||||
return self._user_data
|
||||
|
||||
@user_data.setter
|
||||
def user_data(self, value):
|
||||
raise AttributeError("You can not assign a new value to user_data, see "
|
||||
"https://git.io/fjxKe")
|
||||
|
||||
@classmethod
|
||||
def from_error(cls, update, error, dispatcher):
|
||||
self = cls.from_update(update, dispatcher)
|
||||
|
@ -94,9 +112,9 @@ class CallbackContext(object):
|
|||
user = update.effective_user
|
||||
|
||||
if chat:
|
||||
self.chat_data = dispatcher.chat_data[chat.id]
|
||||
self._chat_data = dispatcher.chat_data[chat.id]
|
||||
if user:
|
||||
self.user_data = dispatcher.user_data[user.id]
|
||||
self._user_data = dispatcher.user_data[user.id]
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
|
|
|
@ -57,11 +57,6 @@ class ConversationHandler(Handler):
|
|||
a regular text message is expected. You could use this for a ``/cancel`` command or to let the
|
||||
user know their message was not recognized.
|
||||
|
||||
The fourth, optional collection of handlers, a ``list`` named :attr:`timed_out_behavior` is
|
||||
used if the wait for ``run_async`` takes longer than defined in :attr:`run_async_timeout`.
|
||||
For example, you can let the user know that they should wait for a bit before they can
|
||||
continue.
|
||||
|
||||
To change the state of conversation, the callback function of a handler must return the new
|
||||
state after responding to the user. If it does not return anything (returning ``None`` by
|
||||
default), the state will not change. If an entry point callback function returns None,
|
||||
|
@ -69,6 +64,20 @@ class ConversationHandler(Handler):
|
|||
To end the conversation, the callback function must return :attr:`END` or ``-1``. To
|
||||
handle the conversation timeout, use handler :attr:`TIMEOUT` or ``-2``.
|
||||
|
||||
Note:
|
||||
In each of the described collections of handlers, a handler may in turn be a
|
||||
:class:`ConversationHandler`. In that case, the nested :class:`ConversationHandler` should
|
||||
have the attribute :attr:`map_to_parent` which allows to return to the parent conversation
|
||||
at specified states within the nested conversation.
|
||||
|
||||
Note that the keys in :attr:`map_to_parent` must not appear as keys in :attr:`states`
|
||||
attribute or else the latter will be ignored. You may map :attr:`END` to one of the parents
|
||||
states to continue the parent conversation after this has ended or even map a state to
|
||||
:attr:`END` to end the *parent* conversation from within the nested one. For an example on
|
||||
nested :class:`ConversationHandler` s, see our `examples`_.
|
||||
|
||||
.. _`examples`: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples
|
||||
|
||||
Attributes:
|
||||
entry_points (List[:class:`telegram.ext.Handler`]): A list of ``Handler`` objects that can
|
||||
trigger the start of the conversation.
|
||||
|
@ -86,13 +95,16 @@ class ConversationHandler(Handler):
|
|||
ID.
|
||||
conversation_timeout (:obj:`float`|:obj:`datetime.timedelta`): Optional. When this handler
|
||||
is inactive more than this timeout (in seconds), it will be automatically ended. If
|
||||
this value is 0 (default), there will be no timeout. when it's triggered. The last
|
||||
this value is 0 (default), there will be no timeout. When it's triggered, the last
|
||||
received update will be handled by ALL the handler's who's `check_update` method
|
||||
returns True that are in the state :attr:`ConversationHandler.TIMEOUT`.
|
||||
name (:obj:`str`): Optional. The name for this conversationhandler. Required for
|
||||
persistence
|
||||
persistent (:obj:`bool`): Optional. If the conversations dict for this handler should be
|
||||
saved. Name is required and persistence has to be set in :class:`telegram.ext.Updater`
|
||||
map_to_parent (Dict[:obj:`object`, :obj:`object`]): Optional. A :obj:`dict` that can be
|
||||
used to instruct a nested conversationhandler to transition into a mapped state on
|
||||
its parent conversationhandler in place of a specified nested state.
|
||||
|
||||
Args:
|
||||
entry_points (List[:class:`telegram.ext.Handler`]): A list of ``Handler`` objects that can
|
||||
|
@ -124,6 +136,9 @@ class ConversationHandler(Handler):
|
|||
persistence
|
||||
persistent (:obj:`bool`, optional): If the conversations dict for this handler should be
|
||||
saved. Name is required and persistence has to be set in :class:`telegram.ext.Updater`
|
||||
map_to_parent (Dict[:obj:`object`, :obj:`object`], optional): A :obj:`dict` that can be
|
||||
used to instruct a nested conversationhandler to transition into a mapped state on
|
||||
its parent conversationhandler in place of a specified nested state.
|
||||
|
||||
Raises:
|
||||
ValueError
|
||||
|
@ -147,7 +162,8 @@ class ConversationHandler(Handler):
|
|||
per_message=False,
|
||||
conversation_timeout=None,
|
||||
name=None,
|
||||
persistent=False):
|
||||
persistent=False,
|
||||
map_to_parent=None):
|
||||
|
||||
self.entry_points = entry_points
|
||||
self.states = states
|
||||
|
@ -165,6 +181,7 @@ class ConversationHandler(Handler):
|
|||
self.persistence = None
|
||||
""":obj:`telegram.ext.BasePersistance`: The persistence used to store conversations.
|
||||
Set by dispatcher"""
|
||||
self.map_to_parent = map_to_parent
|
||||
|
||||
self.timeout_jobs = dict()
|
||||
self.conversations = dict()
|
||||
|
@ -333,7 +350,11 @@ class ConversationHandler(Handler):
|
|||
self._trigger_timeout, self.conversation_timeout,
|
||||
context=_ConversationTimeoutContext(conversation_key, update, dispatcher))
|
||||
|
||||
self.update_state(new_state, conversation_key)
|
||||
if isinstance(self.map_to_parent, dict) and new_state in self.map_to_parent:
|
||||
self.update_state(self.END, conversation_key)
|
||||
return self.map_to_parent.get(new_state)
|
||||
else:
|
||||
self.update_state(new_state, conversation_key)
|
||||
|
||||
def update_state(self, new_state, key):
|
||||
if new_state == self.END:
|
||||
|
|
|
@ -328,15 +328,29 @@ class Dispatcher(object):
|
|||
if self.persistence.store_chat_data and update.effective_chat:
|
||||
chat_id = update.effective_chat.id
|
||||
try:
|
||||
self.persistence.update_chat_data(chat_id, self.chat_data[chat_id])
|
||||
except Exception:
|
||||
self.logger.exception('Saving chat data raised an error')
|
||||
self.persistence.update_chat_data(chat_id,
|
||||
self.chat_data[chat_id])
|
||||
except Exception as e:
|
||||
try:
|
||||
self.dispatch_error(update, e)
|
||||
except Exception:
|
||||
message = 'Saving chat data raised an error and an ' \
|
||||
'uncaught error was raised while handling ' \
|
||||
'the error with an error_handler'
|
||||
self.logger.exception(message)
|
||||
if self.persistence.store_user_data and update.effective_user:
|
||||
user_id = update.effective_user.id
|
||||
try:
|
||||
self.persistence.update_user_data(user_id, self.user_data[user_id])
|
||||
except Exception:
|
||||
self.logger.exception('Saving user data raised an error')
|
||||
self.persistence.update_user_data(user_id,
|
||||
self.user_data[user_id])
|
||||
except Exception as e:
|
||||
try:
|
||||
self.dispatch_error(update, e)
|
||||
except Exception:
|
||||
message = 'Saving user data raised an error and an ' \
|
||||
'uncaught error was raised while handling ' \
|
||||
'the error with an error_handler'
|
||||
self.logger.exception(message)
|
||||
|
||||
# An error happened while polling
|
||||
if isinstance(update, TelegramError):
|
||||
|
@ -366,20 +380,17 @@ class Dispatcher(object):
|
|||
break
|
||||
|
||||
# Dispatch any error.
|
||||
except TelegramError as te:
|
||||
self.logger.warning('A TelegramError was raised while processing the Update')
|
||||
|
||||
except Exception as e:
|
||||
try:
|
||||
self.dispatch_error(update, te)
|
||||
self.dispatch_error(update, e)
|
||||
except DispatcherHandlerStop:
|
||||
self.logger.debug('Error handler stopped further handlers')
|
||||
break
|
||||
# Errors should not stop the thread.
|
||||
except Exception:
|
||||
self.logger.exception('An uncaught error was raised while handling the error')
|
||||
|
||||
# Errors should not stop the thread.
|
||||
except Exception:
|
||||
self.logger.exception('An uncaught error was raised while processing the update')
|
||||
self.logger.exception('An error was raised while processing the update and an '
|
||||
'uncaught error was raised while handling the error '
|
||||
'with an error_handler')
|
||||
|
||||
def add_handler(self, handler, group=DEFAULT_GROUP):
|
||||
"""Register a handler.
|
||||
|
@ -453,11 +464,15 @@ class Dispatcher(object):
|
|||
self.persistence.update_user_data(user_id, self.user_data[user_id])
|
||||
|
||||
def add_error_handler(self, callback):
|
||||
"""Registers an error handler in the Dispatcher.
|
||||
"""Registers an error handler in the Dispatcher. This handler will receive every error
|
||||
which happens in your bot.
|
||||
|
||||
Warning: The errors handled within these handlers won't show up in the logger, so you
|
||||
need to make sure that you reraise the error.
|
||||
|
||||
Args:
|
||||
callback (:obj:`callable`): The callback function for this error handler. Will be
|
||||
called when an error is raised Callback signature for context based API:
|
||||
called when an error is raised. Callback signature for context based API:
|
||||
|
||||
``def callback(update: Update, context: CallbackContext)``
|
||||
|
||||
|
@ -483,7 +498,7 @@ class Dispatcher(object):
|
|||
|
||||
Args:
|
||||
update (:obj:`str` | :class:`telegram.Update` | None): The update that caused the error
|
||||
error (:class:`telegram.TelegramError`): The Telegram error that was raised.
|
||||
error (:obj:`Exception`): The error that was raised.
|
||||
|
||||
"""
|
||||
if self.error_handlers:
|
||||
|
|
|
@ -151,14 +151,11 @@ class Handler(object):
|
|||
optional_args['update_queue'] = dispatcher.update_queue
|
||||
if self.pass_job_queue:
|
||||
optional_args['job_queue'] = dispatcher.job_queue
|
||||
if self.pass_user_data or self.pass_chat_data:
|
||||
chat = update.effective_chat
|
||||
if self.pass_user_data:
|
||||
user = update.effective_user
|
||||
|
||||
if self.pass_user_data:
|
||||
optional_args['user_data'] = dispatcher.user_data[user.id if user else None]
|
||||
|
||||
if self.pass_chat_data:
|
||||
optional_args['chat_data'] = dispatcher.chat_data[chat.id if chat else None]
|
||||
optional_args['user_data'] = dispatcher.user_data[user.id if user else None]
|
||||
if self.pass_chat_data:
|
||||
chat = update.effective_chat
|
||||
optional_args['chat_data'] = dispatcher.chat_data[chat.id if chat else None]
|
||||
|
||||
return optional_args
|
||||
|
|
|
@ -56,12 +56,12 @@ class PicklePersistence(BasePersistence):
|
|||
on any transaction *and* on call fo :meth:`flush`. Default is ``False``.
|
||||
"""
|
||||
|
||||
def __init__(self, filename, store_user_data=True, store_chat_data=True, singe_file=True,
|
||||
def __init__(self, filename, store_user_data=True, store_chat_data=True, single_file=True,
|
||||
on_flush=False):
|
||||
self.filename = filename
|
||||
self.store_user_data = store_user_data
|
||||
self.store_chat_data = store_chat_data
|
||||
self.single_file = singe_file
|
||||
self.single_file = single_file
|
||||
self.on_flush = on_flush
|
||||
self.user_data = None
|
||||
self.chat_data = None
|
||||
|
|
|
@ -34,6 +34,7 @@ class Animation(TelegramObject):
|
|||
file_name (:obj:`str`): Optional. Original animation filename as defined by sender.
|
||||
mime_type (:obj:`str`): Optional. MIME type of the file as defined by sender.
|
||||
file_size (:obj:`int`): Optional. File size.
|
||||
bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods.
|
||||
|
||||
Args:
|
||||
file_id (:obj:`str`): Unique file identifier.
|
||||
|
@ -44,6 +45,8 @@ class Animation(TelegramObject):
|
|||
file_name (:obj:`str`, optional): Original animation filename as defined by sender.
|
||||
mime_type (:obj:`str`, optional): MIME type of the file as defined by sender.
|
||||
file_size (:obj:`int`, optional): File size.
|
||||
bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods.
|
||||
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
||||
|
||||
"""
|
||||
|
||||
|
@ -56,16 +59,19 @@ class Animation(TelegramObject):
|
|||
file_name=None,
|
||||
mime_type=None,
|
||||
file_size=None,
|
||||
bot=None,
|
||||
**kwargs):
|
||||
# Required
|
||||
self.file_id = str(file_id)
|
||||
self.width = int(width)
|
||||
self.height = int(height)
|
||||
self.duration = duration
|
||||
# Optionals
|
||||
self.thumb = thumb
|
||||
self.file_name = file_name
|
||||
self.mime_type = mime_type
|
||||
self.file_size = file_size
|
||||
self.bot = bot
|
||||
|
||||
self._id_attrs = (self.file_id,)
|
||||
|
||||
|
@ -78,4 +84,22 @@ class Animation(TelegramObject):
|
|||
|
||||
data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot)
|
||||
|
||||
return cls(**data)
|
||||
return cls(bot=bot, **data)
|
||||
|
||||
def get_file(self, timeout=None, **kwargs):
|
||||
"""Convenience wrapper over :attr:`telegram.Bot.get_file`
|
||||
|
||||
Args:
|
||||
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
|
||||
the read timeout from the server (instead of the one specified during creation of
|
||||
the connection pool).
|
||||
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
||||
|
||||
Returns:
|
||||
:class:`telegram.File`
|
||||
|
||||
Raises:
|
||||
:class:`telegram.TelegramError`
|
||||
|
||||
"""
|
||||
return self.bot.get_file(self.file_id, timeout=timeout, **kwargs)
|
||||
|
|
|
@ -17,9 +17,6 @@
|
|||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains an object that represents a Telegram ChatPhoto."""
|
||||
|
||||
# TODO: add direct download shortcuts.
|
||||
|
||||
from telegram import TelegramObject
|
||||
|
||||
|
||||
|
@ -27,14 +24,14 @@ class ChatPhoto(TelegramObject):
|
|||
"""This object represents a chat photo.
|
||||
|
||||
Attributes:
|
||||
small_file_id (:obj:`str`): Unique file identifier of small (160x160) chat photo.
|
||||
big_file_id (:obj:`str`): Unique file identifier of big (640x640) chat photo.
|
||||
small_file_id (:obj:`str`): File identifier of small (160x160) chat photo.
|
||||
big_file_id (:obj:`str`): File identifier of big (640x640) chat photo.
|
||||
|
||||
Args:
|
||||
small_file_id (:obj:`str`): Unique file identifier of small (160x160) chat photo. This
|
||||
file_id can be used only for photo download.
|
||||
big_file_id (:obj:`str`): Unique file identifier of big (640x640) chat photo. This file_id
|
||||
can be used only for photo download.
|
||||
small_file_id (:obj:`str`): File identifier of small (160x160) chat photo. This file_id can
|
||||
be used only for photo download and only for as long as the photo is not changed.
|
||||
big_file_id (:obj:`str`): File identifier of big (640x640) chat photo. This file_id can be
|
||||
used only for photo download and only for as long as the photo is not changed.
|
||||
bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods
|
||||
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
||||
|
||||
|
@ -43,6 +40,9 @@ class ChatPhoto(TelegramObject):
|
|||
def __init__(self, small_file_id, big_file_id, bot=None, **kwargs):
|
||||
self.small_file_id = small_file_id
|
||||
self.big_file_id = big_file_id
|
||||
self.bot = bot
|
||||
|
||||
self._id_attrs = (self.small_file_id, self.big_file_id)
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data, bot):
|
||||
|
@ -50,3 +50,41 @@ class ChatPhoto(TelegramObject):
|
|||
return None
|
||||
|
||||
return cls(bot=bot, **data)
|
||||
|
||||
def get_small_file(self, timeout=None, **kwargs):
|
||||
"""Convenience wrapper over :attr:`telegram.Bot.get_file` for getting the
|
||||
small (160x160) chat photo
|
||||
|
||||
Args:
|
||||
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
|
||||
the read timeout from the server (instead of the one specified during creation of
|
||||
the connection pool).
|
||||
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
||||
|
||||
Returns:
|
||||
:class:`telegram.File`
|
||||
|
||||
Raises:
|
||||
:class:`telegram.TelegramError`
|
||||
|
||||
"""
|
||||
return self.bot.get_file(self.small_file_id, timeout=timeout, **kwargs)
|
||||
|
||||
def get_big_file(self, timeout=None, **kwargs):
|
||||
"""Convenience wrapper over :attr:`telegram.Bot.get_file` for getting the
|
||||
big (640x640) chat photo
|
||||
|
||||
Args:
|
||||
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
|
||||
the read timeout from the server (instead of the one specified during creation of
|
||||
the connection pool).
|
||||
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
||||
|
||||
Returns:
|
||||
:class:`telegram.File`
|
||||
|
||||
Raises:
|
||||
:class:`telegram.TelegramError`
|
||||
|
||||
"""
|
||||
return self.bot.get_file(self.big_file_id, timeout=timeout, **kwargs)
|
||||
|
|
|
@ -28,6 +28,7 @@ class Sticker(TelegramObject):
|
|||
file_id (:obj:`str`): Unique identifier for this file.
|
||||
width (:obj:`int`): Sticker width.
|
||||
height (:obj:`int`): Sticker height.
|
||||
is_animated (:obj:`bool`): True, if the sticker is animated.
|
||||
thumb (:class:`telegram.PhotoSize`): Optional. Sticker thumbnail in the .webp or .jpg
|
||||
format.
|
||||
emoji (:obj:`str`): Optional. Emoji associated with the sticker.
|
||||
|
@ -41,6 +42,7 @@ class Sticker(TelegramObject):
|
|||
file_id (:obj:`str`): Unique identifier for this file.
|
||||
width (:obj:`int`): Sticker width.
|
||||
height (:obj:`int`): Sticker height.
|
||||
is_animated (:obj:`bool`): True, if the sticker is animated.
|
||||
thumb (:class:`telegram.PhotoSize`, optional): Sticker thumbnail in the .webp or .jpg
|
||||
format.
|
||||
emoji (:obj:`str`, optional): Emoji associated with the sticker
|
||||
|
@ -58,6 +60,7 @@ class Sticker(TelegramObject):
|
|||
file_id,
|
||||
width,
|
||||
height,
|
||||
is_animated,
|
||||
thumb=None,
|
||||
emoji=None,
|
||||
file_size=None,
|
||||
|
@ -69,6 +72,7 @@ class Sticker(TelegramObject):
|
|||
self.file_id = str(file_id)
|
||||
self.width = int(width)
|
||||
self.height = int(height)
|
||||
self.is_animated = is_animated
|
||||
# Optionals
|
||||
self.thumb = thumb
|
||||
self.emoji = emoji
|
||||
|
@ -123,20 +127,23 @@ class StickerSet(TelegramObject):
|
|||
Attributes:
|
||||
name (:obj:`str`): Sticker set name.
|
||||
title (:obj:`str`): Sticker set title.
|
||||
is_animated (:obj:`bool`): True, if the sticker set contains animated stickers.
|
||||
contains_masks (:obj:`bool`): True, if the sticker set contains masks.
|
||||
stickers (List[:class:`telegram.Sticker`]): List of all set stickers.
|
||||
|
||||
Args:
|
||||
name (:obj:`str`): Sticker set name.
|
||||
title (:obj:`str`): Sticker set title.
|
||||
is_animated (:obj:`bool`): True, if the sticker set contains animated stickers.
|
||||
contains_masks (:obj:`bool`): True, if the sticker set contains masks.
|
||||
stickers (List[:class:`telegram.Sticker`]): List of all set stickers.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, name, title, contains_masks, stickers, bot=None, **kwargs):
|
||||
def __init__(self, name, title, is_animated, contains_masks, stickers, bot=None, **kwargs):
|
||||
self.name = name
|
||||
self.title = title
|
||||
self.is_animated = is_animated
|
||||
self.contains_masks = contains_masks
|
||||
self.stickers = stickers
|
||||
|
||||
|
|
|
@ -26,7 +26,8 @@ class LoginUrl(TelegramObject):
|
|||
authorize a user. Serves as a great replacement for the Telegram Login Widget when the user is
|
||||
coming from Telegram. All the user needs to do is tap/click a button and confirm that they want
|
||||
to log in. Telegram apps support these buttons as of version 5.7.
|
||||
Sample bot: @discussbot
|
||||
|
||||
Sample bot: `@discussbot <https://t.me/dicussbot>`_
|
||||
|
||||
Attributes:
|
||||
url (:obj:`str`): An HTTP URL to be opened with user authorization data.
|
||||
|
|
|
@ -865,7 +865,7 @@ class Message(TelegramObject):
|
|||
|
||||
Args:
|
||||
entity (:class:`telegram.MessageEntity`): The entity to extract the text from. It must
|
||||
be an entity that belongs to this message.
|
||||
be an entity that belongs to this message.
|
||||
|
||||
Returns:
|
||||
:obj:`str`: The text of the given entity
|
||||
|
@ -890,7 +890,7 @@ class Message(TelegramObject):
|
|||
|
||||
Args:
|
||||
entity (:class:`telegram.MessageEntity`): The entity to extract the text from. It must
|
||||
be an entity that belongs to this message.
|
||||
be an entity that belongs to this message.
|
||||
|
||||
Returns:
|
||||
:obj:`str`: The text of the given entity
|
||||
|
|
|
@ -87,7 +87,7 @@ def from_timestamp(unixtime):
|
|||
if not unixtime:
|
||||
return None
|
||||
|
||||
return datetime.fromtimestamp(unixtime)
|
||||
return datetime.utcfromtimestamp(unixtime)
|
||||
|
||||
|
||||
def mention_html(user_id, name):
|
||||
|
@ -147,6 +147,55 @@ def effective_message_type(entity):
|
|||
return None
|
||||
|
||||
|
||||
def create_deep_linked_url(bot_username, payload=None, group=False):
|
||||
"""
|
||||
Creates a deep-linked URL for this ``bot_username`` with the specified ``payload``.
|
||||
See https://core.telegram.org/bots#deep-linking to learn more.
|
||||
|
||||
The ``payload`` may consist of the following characters: ``A-Z, a-z, 0-9, _, -``
|
||||
|
||||
Note:
|
||||
Works well in conjunction with
|
||||
``CommandHandler("start", callback, filters = Filters.regex('payload'))``
|
||||
|
||||
Examples:
|
||||
``create_deep_linked_url(bot.get_me().username, "some-params")``
|
||||
|
||||
Args:
|
||||
bot_username (:obj:`str`): The username to link to
|
||||
payload (:obj:`str`, optional): Parameters to encode in the created URL
|
||||
group (:obj:`bool`, optional): If `True` the user is prompted to select a group to add the
|
||||
bot to. If `False`, opens a one-on-one conversation with the bot. Defaults to `False`.
|
||||
|
||||
Returns:
|
||||
:obj:`str`: An URL to start the bot with specific parameters
|
||||
"""
|
||||
if bot_username is None or len(bot_username) <= 3:
|
||||
raise ValueError("You must provide a valid bot_username.")
|
||||
|
||||
base_url = 'https://t.me/{}'.format(bot_username)
|
||||
if not payload:
|
||||
return base_url
|
||||
|
||||
if len(payload) > 64:
|
||||
raise ValueError("The deep-linking payload must not exceed 64 characters.")
|
||||
|
||||
if not re.match(r'^[A-Za-z0-9_-]+$', payload):
|
||||
raise ValueError("Only the following characters are allowed for deep-linked "
|
||||
"URLs: A-Z, a-z, 0-9, _ and -")
|
||||
|
||||
if group:
|
||||
key = 'startgroup'
|
||||
else:
|
||||
key = 'start'
|
||||
|
||||
return '{0}?{1}={2}'.format(
|
||||
base_url,
|
||||
key,
|
||||
payload
|
||||
)
|
||||
|
||||
|
||||
def enocde_conversations_to_json(conversations):
|
||||
"""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.
|
||||
|
|
|
@ -17,4 +17,4 @@
|
|||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
|
||||
__version__ = '12.0.0b1'
|
||||
__version__ = '12.2.0'
|
||||
|
|
|
@ -16,8 +16,10 @@
|
|||
#
|
||||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
import datetime
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
from collections import defaultdict
|
||||
from queue import Queue
|
||||
from threading import Thread, Event
|
||||
|
@ -25,8 +27,9 @@ from time import sleep
|
|||
|
||||
import pytest
|
||||
|
||||
from telegram import Bot
|
||||
from telegram.ext import Dispatcher, JobQueue, Updater
|
||||
from telegram import Bot, Message, User, Chat, MessageEntity, Update, \
|
||||
InlineQuery, CallbackQuery, ShippingQuery, PreCheckoutQuery, ChosenInlineResult
|
||||
from telegram.ext import Dispatcher, JobQueue, Updater, BaseFilter
|
||||
from tests.bots import get_bot
|
||||
|
||||
TRAVIS = os.getenv('TRAVIS', False)
|
||||
|
@ -46,7 +49,7 @@ def bot_info():
|
|||
|
||||
@pytest.fixture(scope='session')
|
||||
def bot(bot_info):
|
||||
return Bot(bot_info['token'], private_key=PRIVATE_KEY)
|
||||
return make_bot(bot_info)
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
|
@ -146,3 +149,107 @@ def pytest_configure(config):
|
|||
if sys.version_info >= (3,):
|
||||
config.addinivalue_line('filterwarnings', 'ignore::ResourceWarning')
|
||||
# TODO: Write so good code that we don't need to ignore ResourceWarnings anymore
|
||||
|
||||
|
||||
def make_bot(bot_info):
|
||||
return Bot(bot_info['token'], private_key=PRIVATE_KEY)
|
||||
|
||||
|
||||
CMD_PATTERN = re.compile(r'/[\da-z_]{1,32}(?:@\w{1,32})?')
|
||||
DATE = datetime.datetime.now()
|
||||
|
||||
|
||||
def make_message(text, **kwargs):
|
||||
"""
|
||||
Testing utility factory to create a fake ``telegram.Message`` with
|
||||
reasonable defaults for mimicking a real message.
|
||||
:param text: (str) message text
|
||||
:return: a (fake) ``telegram.Message``
|
||||
"""
|
||||
return Message(message_id=1,
|
||||
from_user=kwargs.pop('user', User(id=1, first_name='', is_bot=False)),
|
||||
date=kwargs.pop('date', DATE),
|
||||
chat=kwargs.pop('chat', Chat(id=1, type='')),
|
||||
text=text,
|
||||
bot=kwargs.pop('bot', make_bot(get_bot())),
|
||||
**kwargs)
|
||||
|
||||
|
||||
def make_command_message(text, **kwargs):
|
||||
"""
|
||||
Testing utility factory to create a message containing a single telegram
|
||||
command.
|
||||
Mimics the Telegram API in that it identifies commands within the message
|
||||
and tags the returned ``Message`` object with the appropriate ``MessageEntity``
|
||||
tag (but it does this only for commands).
|
||||
|
||||
:param text: (str) message text containing (or not) the command
|
||||
:return: a (fake) ``telegram.Message`` containing only the command
|
||||
"""
|
||||
|
||||
match = re.search(CMD_PATTERN, text)
|
||||
entities = [MessageEntity(type=MessageEntity.BOT_COMMAND,
|
||||
offset=match.start(0),
|
||||
length=len(match.group(0)))] if match else []
|
||||
|
||||
return make_message(text, entities=entities, **kwargs)
|
||||
|
||||
|
||||
def make_message_update(message, message_factory=make_message, edited=False, **kwargs):
|
||||
"""
|
||||
Testing utility factory to create an update from a message, as either a
|
||||
``telegram.Message`` or a string. In the latter case ``message_factory``
|
||||
is used to convert ``message`` to a ``telegram.Message``.
|
||||
:param message: either a ``telegram.Message`` or a string with the message text
|
||||
:param message_factory: function to convert the message text into a ``telegram.Message``
|
||||
:param edited: whether the message should be stored as ``edited_message`` (vs. ``message``)
|
||||
:return: ``telegram.Update`` with the given message
|
||||
"""
|
||||
if not isinstance(message, Message):
|
||||
message = message_factory(message, **kwargs)
|
||||
update_kwargs = {'message' if not edited else 'edited_message': message}
|
||||
return Update(0, **update_kwargs)
|
||||
|
||||
|
||||
def make_command_update(message, edited=False, **kwargs):
|
||||
"""
|
||||
Testing utility factory to create an update from a message that potentially
|
||||
contains a command. See ``make_command_message`` for more details.
|
||||
:param message: message potentially containing a command
|
||||
:param edited: whether the message should be stored as ``edited_message`` (vs. ``message``)
|
||||
:return: ``telegram.Update`` with the given message
|
||||
"""
|
||||
return make_message_update(message, make_command_message, edited, **kwargs)
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def mock_filter():
|
||||
class MockFilter(BaseFilter):
|
||||
def __init__(self):
|
||||
self.tested = False
|
||||
|
||||
def filter(self, message):
|
||||
self.tested = True
|
||||
|
||||
return MockFilter()
|
||||
|
||||
|
||||
def get_false_update_fixture_decorator_params():
|
||||
message = Message(1, User(1, '', False), DATE, Chat(1, ''), text='test')
|
||||
params = [
|
||||
{'callback_query': CallbackQuery(1, User(1, '', False), 'chat', message=message)},
|
||||
{'channel_post': message},
|
||||
{'edited_channel_post': message},
|
||||
{'inline_query': InlineQuery(1, User(1, '', False), '', '')},
|
||||
{'chosen_inline_result': ChosenInlineResult('id', User(1, '', False), '')},
|
||||
{'shipping_query': ShippingQuery('id', User(1, '', False), '', None)},
|
||||
{'pre_checkout_query': PreCheckoutQuery('id', User(1, '', False), '', 0, '')},
|
||||
{'callback_query': CallbackQuery(1, User(1, '', False), 'chat')}
|
||||
]
|
||||
ids = tuple(key for kwargs in params for key in kwargs)
|
||||
return {'params': params, 'ids': ids}
|
||||
|
||||
|
||||
@pytest.fixture(scope='function', **get_false_update_fixture_decorator_params())
|
||||
def false_update(request):
|
||||
return Update(update_id=1, **request.param)
|
||||
|
|
|
@ -17,10 +17,11 @@
|
|||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
|
||||
import os
|
||||
import pytest
|
||||
from flaky import flaky
|
||||
|
||||
from telegram import PhotoSize, Animation, Voice
|
||||
from telegram import PhotoSize, Animation, Voice, TelegramError
|
||||
from telegram.utils.helpers import escape_markdown
|
||||
|
||||
|
||||
|
@ -43,6 +44,9 @@ class TestAnimation(object):
|
|||
width = 320
|
||||
height = 180
|
||||
duration = 1
|
||||
# animation_file_url = 'https://python-telegram-bot.org/static/testfiles/game.gif'
|
||||
# Shortened link, the above one is cached with the wrong duration.
|
||||
animation_file_url = 'http://bit.ly/2L18jua'
|
||||
file_name = 'game.gif.mp4'
|
||||
mime_type = 'video/mp4'
|
||||
file_size = 4127
|
||||
|
@ -73,17 +77,34 @@ class TestAnimation(object):
|
|||
assert message.animation.file_name == animation.file_name
|
||||
assert message.animation.mime_type == animation.mime_type
|
||||
assert message.animation.file_size == animation.file_size
|
||||
assert message.animation.thumb.width == 320
|
||||
assert message.animation.thumb.height == 180
|
||||
assert message.animation.thumb.width == self.width
|
||||
assert message.animation.thumb.height == self.height
|
||||
|
||||
@flaky(3, 1)
|
||||
def test_resend(self, bot, chat_id, animation):
|
||||
message = bot.send_animation(chat_id, animation.file_id)
|
||||
@pytest.mark.timeout(10)
|
||||
def test_get_and_download(self, bot, animation):
|
||||
new_file = bot.get_file(animation.file_id)
|
||||
|
||||
assert new_file.file_size == self.file_size
|
||||
assert new_file.file_id == animation.file_id
|
||||
assert new_file.file_path.startswith('https://')
|
||||
|
||||
new_file.download('game.gif')
|
||||
|
||||
assert os.path.isfile('game.gif')
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
def test_send_animation_url_file(self, bot, chat_id, animation):
|
||||
message = bot.send_animation(chat_id=chat_id, animation=self.animation_file_url,
|
||||
caption=self.caption)
|
||||
|
||||
assert message.caption == self.caption
|
||||
|
||||
assert isinstance(message.animation, Animation)
|
||||
assert isinstance(message.animation.file_id, str)
|
||||
assert message.animation.file_id != ''
|
||||
assert message.animation.file_name == animation.file_name
|
||||
assert message.animation.file_id is not None
|
||||
assert message.animation.duration == animation.duration
|
||||
assert message.animation.mime_type == animation.mime_type
|
||||
assert message.animation.file_size == animation.file_size
|
||||
|
||||
|
@ -129,6 +150,21 @@ class TestAnimation(object):
|
|||
|
||||
bot.default_parse_mode = None
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
def test_resend(self, bot, chat_id, animation):
|
||||
message = bot.send_animation(chat_id, animation.file_id)
|
||||
|
||||
assert message.animation == animation
|
||||
|
||||
def test_send_with_animation(self, monkeypatch, bot, chat_id, animation):
|
||||
def test(_, url, data, **kwargs):
|
||||
return data['animation'] == animation.file_id
|
||||
|
||||
monkeypatch.setattr('telegram.utils.request.Request.post', test)
|
||||
message = bot.send_animation(animation=animation, chat_id=chat_id)
|
||||
assert message
|
||||
|
||||
def test_de_json(self, bot, animation):
|
||||
json_dict = {
|
||||
'file_id': self.animation_file_id,
|
||||
|
@ -160,6 +196,31 @@ class TestAnimation(object):
|
|||
assert animation_dict['mime_type'] == animation.mime_type
|
||||
assert animation_dict['file_size'] == animation.file_size
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
def test_error_send_empty_file(self, bot, chat_id):
|
||||
animation_file = open(os.devnull, 'rb')
|
||||
|
||||
with pytest.raises(TelegramError):
|
||||
bot.send_animation(chat_id=chat_id, animation=animation_file)
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
def test_error_send_empty_file_id(self, bot, chat_id):
|
||||
with pytest.raises(TelegramError):
|
||||
bot.send_animation(chat_id=chat_id, animation='')
|
||||
|
||||
def test_error_send_without_required_args(self, bot, chat_id):
|
||||
with pytest.raises(TypeError):
|
||||
bot.send_animation(chat_id=chat_id)
|
||||
|
||||
def test_get_file_instance_method(self, monkeypatch, animation):
|
||||
def test(*args, **kwargs):
|
||||
return args[1] == animation.file_id
|
||||
|
||||
monkeypatch.setattr('telegram.Bot.get_file', test)
|
||||
assert animation.get_file()
|
||||
|
||||
def test_equality(self):
|
||||
a = Animation(self.animation_file_id, self.height, self.width, self.duration)
|
||||
b = Animation(self.animation_file_id, self.height, self.width, self.duration)
|
||||
|
|
|
@ -28,7 +28,7 @@ from future.utils import string_types
|
|||
|
||||
from telegram import (Bot, Update, ChatAction, TelegramError, User, InlineKeyboardMarkup,
|
||||
InlineKeyboardButton, InlineQueryResultArticle, InputTextMessageContent,
|
||||
ShippingOption, LabeledPrice, Poll)
|
||||
ShippingOption, LabeledPrice, ChatPermissions, Poll)
|
||||
from telegram.error import BadRequest, InvalidToken, NetworkError, RetryAfter
|
||||
from telegram.utils.helpers import from_timestamp, escape_markdown
|
||||
|
||||
|
@ -50,6 +50,11 @@ def media_message(bot, chat_id):
|
|||
return bot.send_voice(chat_id, voice=f, caption='my caption', timeout=10)
|
||||
|
||||
|
||||
@pytest.fixture(scope='class')
|
||||
def chat_permissions():
|
||||
return ChatPermissions(can_send_messages=False, can_change_info=False, can_invite_users=False)
|
||||
|
||||
|
||||
class TestBot(object):
|
||||
@pytest.mark.parametrize('token', argvalues=[
|
||||
'123',
|
||||
|
@ -84,6 +89,18 @@ class TestBot(object):
|
|||
assert get_me_bot.last_name == bot.last_name
|
||||
assert get_me_bot.name == bot.name
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
def test_to_dict(self, bot):
|
||||
to_dict_bot = bot.to_dict()
|
||||
|
||||
assert isinstance(to_dict_bot, dict)
|
||||
assert to_dict_bot["id"] == bot.id
|
||||
assert to_dict_bot["username"] == bot.username
|
||||
assert to_dict_bot["first_name"] == bot.first_name
|
||||
if bot.last_name:
|
||||
assert to_dict_bot["last_name"] == bot.last_name
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
def test_forward_message(self, bot, chat_id, message):
|
||||
|
@ -260,6 +277,16 @@ class TestBot(object):
|
|||
|
||||
assert bot.unban_chat_member(2, 32)
|
||||
|
||||
def test_set_chat_permissions(self, monkeypatch, bot, chat_permissions):
|
||||
def test(_, url, data, *args, **kwargs):
|
||||
chat_id = data['chat_id'] == 2
|
||||
permissions = data['permissions'] == chat_permissions.to_dict()
|
||||
return chat_id and permissions
|
||||
|
||||
monkeypatch.setattr('telegram.utils.request.Request.post', test)
|
||||
|
||||
assert bot.set_chat_permissions(2, chat_permissions)
|
||||
|
||||
# TODO: Needs improvement. Need an incoming callbackquery to test
|
||||
def test_answer_callback_query(self, monkeypatch, bot):
|
||||
# For now just test that our internals pass the correct data
|
||||
|
@ -643,16 +670,13 @@ class TestBot(object):
|
|||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
def test_restrict_chat_member(self, bot, channel_id):
|
||||
def test_restrict_chat_member(self, bot, channel_id, chat_permissions):
|
||||
# TODO: Add bot to supergroup so this can be tested properly
|
||||
with pytest.raises(BadRequest, match='Method is available only for supergroups'):
|
||||
assert bot.restrict_chat_member(channel_id,
|
||||
95205500,
|
||||
until_date=datetime.now(),
|
||||
can_send_messages=False,
|
||||
can_send_media_messages=False,
|
||||
can_send_other_messages=False,
|
||||
can_add_web_page_previews=False)
|
||||
chat_permissions,
|
||||
until_date=datetime.now())
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
|
|
|
@ -105,3 +105,15 @@ class TestCallbackContext(object):
|
|||
callback_context.matches = ['test', 'blah']
|
||||
|
||||
assert callback_context.match == 'test'
|
||||
|
||||
def test_data_assignment(self, cdp):
|
||||
update = Update(0, message=Message(0, User(1, 'user', False), None, Chat(1, 'chat')))
|
||||
|
||||
callback_context = CallbackContext.from_update(update, cdp)
|
||||
|
||||
with pytest.raises(AttributeError):
|
||||
callback_context.chat_data = {"test": 123}
|
||||
with pytest.raises(AttributeError):
|
||||
callback_context.user_data = {}
|
||||
with pytest.raises(AttributeError):
|
||||
callback_context.chat_data = "test"
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
import pytest
|
||||
|
||||
from telegram import Chat, ChatAction
|
||||
from telegram import Chat, ChatAction, ChatPermissions
|
||||
from telegram import User
|
||||
|
||||
|
||||
|
@ -28,7 +28,8 @@ def chat(bot):
|
|||
return Chat(TestChat.id, TestChat.title, TestChat.type, username=TestChat.username,
|
||||
all_members_are_administrators=TestChat.all_members_are_administrators,
|
||||
bot=bot, sticker_set_name=TestChat.sticker_set_name,
|
||||
can_set_sticker_set=TestChat.can_set_sticker_set)
|
||||
can_set_sticker_set=TestChat.can_set_sticker_set,
|
||||
permissions=TestChat.permissions)
|
||||
|
||||
|
||||
class TestChat(object):
|
||||
|
@ -39,6 +40,11 @@ class TestChat(object):
|
|||
all_members_are_administrators = False
|
||||
sticker_set_name = 'stickers'
|
||||
can_set_sticker_set = False
|
||||
permissions = ChatPermissions(
|
||||
can_send_messages=True,
|
||||
can_change_info=False,
|
||||
can_invite_users=True,
|
||||
)
|
||||
|
||||
def test_de_json(self, bot):
|
||||
json_dict = {
|
||||
|
@ -48,7 +54,8 @@ class TestChat(object):
|
|||
'username': self.username,
|
||||
'all_members_are_administrators': self.all_members_are_administrators,
|
||||
'sticker_set_name': self.sticker_set_name,
|
||||
'can_set_sticker_set': self.can_set_sticker_set
|
||||
'can_set_sticker_set': self.can_set_sticker_set,
|
||||
'permissions': self.permissions.to_dict()
|
||||
}
|
||||
chat = Chat.de_json(json_dict, bot)
|
||||
|
||||
|
@ -59,6 +66,7 @@ class TestChat(object):
|
|||
assert chat.all_members_are_administrators == self.all_members_are_administrators
|
||||
assert chat.sticker_set_name == self.sticker_set_name
|
||||
assert chat.can_set_sticker_set == self.can_set_sticker_set
|
||||
assert chat.permissions == self.permissions
|
||||
|
||||
def test_to_dict(self, chat):
|
||||
chat_dict = chat.to_dict()
|
||||
|
@ -69,6 +77,7 @@ class TestChat(object):
|
|||
assert chat_dict['type'] == chat.type
|
||||
assert chat_dict['username'] == chat.username
|
||||
assert chat_dict['all_members_are_administrators'] == chat.all_members_are_administrators
|
||||
assert chat_dict['permissions'] == chat.permissions.to_dict()
|
||||
|
||||
def test_link(self, chat):
|
||||
assert chat.link == 'https://t.me/{}'.format(chat.username)
|
||||
|
@ -133,6 +142,15 @@ class TestChat(object):
|
|||
monkeypatch.setattr('telegram.Bot.unban_chat_member', test)
|
||||
assert chat.unban_member(42)
|
||||
|
||||
def test_set_permissions(self, monkeypatch, chat):
|
||||
def test(*args, **kwargs):
|
||||
chat_id = args[1] == chat.id
|
||||
permissions = args[2] == self.permissions
|
||||
return chat_id and permissions
|
||||
|
||||
monkeypatch.setattr('telegram.Bot.set_chat_permissions', test)
|
||||
assert chat.set_permissions(self.permissions)
|
||||
|
||||
def test_instance_method_send_message(self, monkeypatch, chat):
|
||||
def test(*args, **kwargs):
|
||||
return args[1] == chat.id and args[2] == 'test'
|
||||
|
|
|
@ -61,8 +61,9 @@ class TestChatMember(object):
|
|||
'can_promote_members': True,
|
||||
'can_send_messages': False,
|
||||
'can_send_media_messages': True,
|
||||
'can_send_other_messages': False,
|
||||
'can_add_web_page_previews': True}
|
||||
'can_send_polls': False,
|
||||
'can_send_other_messages': True,
|
||||
'can_add_web_page_previews': False}
|
||||
|
||||
chat_member = ChatMember.de_json(json_dict, bot)
|
||||
|
||||
|
@ -79,8 +80,9 @@ class TestChatMember(object):
|
|||
assert chat_member.can_promote_members is True
|
||||
assert chat_member.can_send_messages is False
|
||||
assert chat_member.can_send_media_messages is True
|
||||
assert chat_member.can_send_other_messages is False
|
||||
assert chat_member.can_add_web_page_previews is True
|
||||
assert chat_member.can_send_polls is False
|
||||
assert chat_member.can_send_other_messages is True
|
||||
assert chat_member.can_add_web_page_previews is False
|
||||
|
||||
def test_to_dict(self, chat_member):
|
||||
chat_member_dict = chat_member.to_dict()
|
||||
|
|
79
tests/test_chatpermissions.py
Normal file
79
tests/test_chatpermissions.py
Normal file
|
@ -0,0 +1,79 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2018
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program 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 Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
|
||||
import pytest
|
||||
|
||||
from telegram import ChatPermissions
|
||||
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def chat_permissions():
|
||||
return ChatPermissions(can_send_messages=True, can_send_media_messages=True,
|
||||
can_send_polls=True, can_send_other_messages=True,
|
||||
can_add_web_page_previews=True, can_change_info=True,
|
||||
can_invite_users=True, can_pin_messages=True)
|
||||
|
||||
|
||||
class TestChatPermissions(object):
|
||||
can_send_messages = True
|
||||
can_send_media_messages = True
|
||||
can_send_polls = True
|
||||
can_send_other_messages = False
|
||||
can_add_web_page_previews = False
|
||||
can_change_info = False
|
||||
can_invite_users = None
|
||||
can_pin_messages = None
|
||||
|
||||
def test_de_json(self, bot):
|
||||
json_dict = {
|
||||
'can_send_messages': self.can_send_messages,
|
||||
'can_send_media_messages': self.can_send_media_messages,
|
||||
'can_send_polls': self.can_send_polls,
|
||||
'can_send_other_messages': self.can_send_other_messages,
|
||||
'can_add_web_page_previews': self.can_add_web_page_previews,
|
||||
'can_change_info': self.can_change_info,
|
||||
'can_invite_users': self.can_invite_users,
|
||||
'can_pin_messages': self.can_pin_messages
|
||||
}
|
||||
permissions = ChatPermissions.de_json(json_dict, bot)
|
||||
|
||||
assert permissions.can_send_messages == self.can_send_messages
|
||||
assert permissions.can_send_media_messages == self.can_send_media_messages
|
||||
assert permissions.can_send_polls == self.can_send_polls
|
||||
assert permissions.can_send_other_messages == self.can_send_other_messages
|
||||
assert permissions.can_add_web_page_previews == self.can_add_web_page_previews
|
||||
assert permissions.can_change_info == self.can_change_info
|
||||
assert permissions.can_invite_users == self.can_invite_users
|
||||
assert permissions.can_pin_messages == self.can_pin_messages
|
||||
|
||||
def test_to_dict(self, chat_permissions):
|
||||
permissions_dict = chat_permissions.to_dict()
|
||||
|
||||
assert isinstance(permissions_dict, dict)
|
||||
assert permissions_dict['can_send_messages'] == chat_permissions.can_send_messages
|
||||
assert (permissions_dict['can_send_media_messages']
|
||||
== chat_permissions.can_send_media_messages)
|
||||
assert permissions_dict['can_send_polls'] == chat_permissions.can_send_polls
|
||||
assert (permissions_dict['can_send_other_messages']
|
||||
== chat_permissions.can_send_other_messages)
|
||||
assert (permissions_dict['can_add_web_page_previews']
|
||||
== chat_permissions.can_add_web_page_previews)
|
||||
assert permissions_dict['can_change_info'] == chat_permissions.can_change_info
|
||||
assert permissions_dict['can_invite_users'] == chat_permissions.can_invite_users
|
||||
assert permissions_dict['can_pin_messages'] == chat_permissions.can_pin_messages
|
|
@ -21,50 +21,30 @@ import re
|
|||
from queue import Queue
|
||||
|
||||
import pytest
|
||||
import itertools
|
||||
from telegram.utils.deprecate import TelegramDeprecationWarning
|
||||
|
||||
from telegram import (Message, Update, Chat, Bot, User, CallbackQuery, InlineQuery,
|
||||
ChosenInlineResult, ShippingQuery, PreCheckoutQuery, MessageEntity)
|
||||
from telegram.ext import CommandHandler, Filters, BaseFilter, CallbackContext, JobQueue, \
|
||||
PrefixHandler
|
||||
|
||||
message = Message(1, User(1, '', False), None, Chat(1, ''), text='test')
|
||||
|
||||
params = [
|
||||
{'callback_query': CallbackQuery(1, User(1, '', False), 'chat', message=message)},
|
||||
{'channel_post': message},
|
||||
{'edited_channel_post': message},
|
||||
{'inline_query': InlineQuery(1, User(1, '', False), '', '')},
|
||||
{'chosen_inline_result': ChosenInlineResult('id', User(1, '', False), '')},
|
||||
{'shipping_query': ShippingQuery('id', User(1, '', False), '', None)},
|
||||
{'pre_checkout_query': PreCheckoutQuery('id', User(1, '', False), '', 0, '')},
|
||||
{'callback_query': CallbackQuery(1, User(1, '', False), 'chat')}
|
||||
]
|
||||
|
||||
ids = ('callback_query', 'channel_post', 'edited_channel_post', 'inline_query',
|
||||
'chosen_inline_result', 'shipping_query', 'pre_checkout_query',
|
||||
'callback_query_without_message',)
|
||||
from telegram import Message, Update, Chat, Bot
|
||||
from telegram.ext import CommandHandler, Filters, CallbackContext, JobQueue, PrefixHandler
|
||||
from tests.conftest import make_command_message, make_command_update, make_message, \
|
||||
make_message_update
|
||||
|
||||
|
||||
@pytest.fixture(scope='class', params=params, ids=ids)
|
||||
def false_update(request):
|
||||
return Update(update_id=1, **request.param)
|
||||
def is_match(handler, update):
|
||||
"""
|
||||
Utility function that returns whether an update matched
|
||||
against a specific handler.
|
||||
:param handler: ``CommandHandler`` to check against
|
||||
:param update: update to check
|
||||
:return: (bool) whether ``update`` matched with ``handler``
|
||||
"""
|
||||
check = handler.check_update(update)
|
||||
return check is not None and check is not False
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def message(bot):
|
||||
return Message(message_id=1,
|
||||
from_user=User(id=1, first_name='', is_bot=False),
|
||||
date=None,
|
||||
chat=Chat(id=1, type=''),
|
||||
message='/test',
|
||||
bot=bot,
|
||||
entities=[MessageEntity(type=MessageEntity.BOT_COMMAND,
|
||||
offset=0,
|
||||
length=len('/test'))])
|
||||
|
||||
|
||||
class TestCommandHandler(object):
|
||||
class BaseTest(object):
|
||||
"""Base class for command and prefix handler test classes. Contains
|
||||
utility methods an several callbacks used by both classes."""
|
||||
test_flag = False
|
||||
SRE_TYPE = type(re.match("", ""))
|
||||
|
||||
|
@ -72,30 +52,33 @@ class TestCommandHandler(object):
|
|||
def reset(self):
|
||||
self.test_flag = False
|
||||
|
||||
PASS_KEYWORDS = ('pass_user_data', 'pass_chat_data', 'pass_job_queue', 'pass_update_queue')
|
||||
|
||||
@pytest.fixture(scope='module', params=itertools.combinations(PASS_KEYWORDS, 2))
|
||||
def pass_combination(self, request):
|
||||
return {key: True for key in request.param}
|
||||
|
||||
def response(self, dispatcher, update):
|
||||
"""
|
||||
Utility to send an update to a dispatcher and assert
|
||||
whether the callback was called appropriately. Its purpose is
|
||||
for repeated usage in the same test function.
|
||||
"""
|
||||
self.test_flag = False
|
||||
dispatcher.process_update(update)
|
||||
return self.test_flag
|
||||
|
||||
def callback_basic(self, bot, update):
|
||||
test_bot = isinstance(bot, Bot)
|
||||
test_update = isinstance(update, Update)
|
||||
self.test_flag = test_bot and test_update
|
||||
|
||||
def callback_data_1(self, bot, update, user_data=None, chat_data=None):
|
||||
self.test_flag = (user_data is not None) or (chat_data is not None)
|
||||
def make_callback_for(self, pass_keyword):
|
||||
def callback(bot, update, **kwargs):
|
||||
self.test_flag = kwargs.get(keyword, None) is not None
|
||||
|
||||
def callback_data_2(self, bot, update, user_data=None, chat_data=None):
|
||||
self.test_flag = (user_data is not None) and (chat_data is not None)
|
||||
|
||||
def callback_queue_1(self, bot, update, job_queue=None, update_queue=None):
|
||||
self.test_flag = (job_queue is not None) or (update_queue is not None)
|
||||
|
||||
def callback_queue_2(self, bot, update, job_queue=None, update_queue=None):
|
||||
self.test_flag = (job_queue is not None) and (update_queue is not None)
|
||||
|
||||
def ch_callback_args(self, bot, update, args):
|
||||
if update.message.text == '/test':
|
||||
self.test_flag = len(args) == 0
|
||||
elif update.message.text == '/test@{}'.format(bot.username):
|
||||
self.test_flag = len(args) == 0
|
||||
else:
|
||||
self.test_flag = args == ['one', 'two']
|
||||
keyword = pass_keyword[5:]
|
||||
return callback
|
||||
|
||||
def callback_context(self, update, context):
|
||||
self.test_flag = (isinstance(context, CallbackContext)
|
||||
|
@ -122,550 +105,295 @@ class TestCommandHandler(object):
|
|||
num = len(context.matches) == 2
|
||||
self.test_flag = types and num
|
||||
|
||||
def test_basic(self, dp, message):
|
||||
handler = CommandHandler('test', self.callback_basic)
|
||||
def _test_context_args_or_regex(self, cdp, handler, text):
|
||||
cdp.add_handler(handler)
|
||||
update = make_command_update(text)
|
||||
assert not self.response(cdp, update)
|
||||
update.message.text += ' one two'
|
||||
assert self.response(cdp, update)
|
||||
|
||||
def _test_edited(self, message, handler_edited, handler_not_edited):
|
||||
"""
|
||||
Assert whether a handler that should accept edited messages
|
||||
and a handler that shouldn't work correctly.
|
||||
:param message: ``telegram.Message`` to check against the handlers
|
||||
:param handler_edited: handler that should accept edited messages
|
||||
:param handler_not_edited: handler that should not accept edited messages
|
||||
"""
|
||||
update = make_command_update(message)
|
||||
edited_update = make_command_update(message, edited=True)
|
||||
|
||||
assert is_match(handler_edited, update)
|
||||
assert is_match(handler_edited, edited_update)
|
||||
assert is_match(handler_not_edited, update)
|
||||
assert not is_match(handler_not_edited, edited_update)
|
||||
|
||||
|
||||
# ----------------------------- CommandHandler -----------------------------
|
||||
|
||||
class TestCommandHandler(BaseTest):
|
||||
CMD = '/test'
|
||||
|
||||
@pytest.fixture(scope='class')
|
||||
def command(self):
|
||||
return self.CMD
|
||||
|
||||
@pytest.fixture(scope='class')
|
||||
def command_message(self, command):
|
||||
return make_command_message(command)
|
||||
|
||||
@pytest.fixture(scope='class')
|
||||
def command_update(self, command_message):
|
||||
return make_command_update(command_message)
|
||||
|
||||
def ch_callback_args(self, bot, update, args):
|
||||
if update.message.text == self.CMD:
|
||||
self.test_flag = len(args) == 0
|
||||
elif update.message.text == '{}@{}'.format(self.CMD, bot.username):
|
||||
self.test_flag = len(args) == 0
|
||||
else:
|
||||
self.test_flag = args == ['one', 'two']
|
||||
|
||||
def make_default_handler(self, callback=None, **kwargs):
|
||||
callback = callback or self.callback_basic
|
||||
return CommandHandler(self.CMD[1:], callback, **kwargs)
|
||||
|
||||
def test_basic(self, dp, command):
|
||||
"""Test whether a command handler responds to its command
|
||||
and not to others, or badly formatted commands"""
|
||||
handler = self.make_default_handler()
|
||||
dp.add_handler(handler)
|
||||
|
||||
message.text = '/test'
|
||||
dp.process_update(Update(0, message))
|
||||
assert self.test_flag
|
||||
assert self.response(dp, make_command_update(command))
|
||||
assert not is_match(handler, make_command_update(command[1:]))
|
||||
assert not is_match(handler, make_command_update('/not{}'.format(command[1:])))
|
||||
assert not is_match(handler, make_command_update('not {} at start'.format(command)))
|
||||
|
||||
message.text = '/nottest'
|
||||
check = handler.check_update(Update(0, message))
|
||||
assert check is None or check is False
|
||||
|
||||
message.text = 'test'
|
||||
check = handler.check_update(Update(0, message))
|
||||
assert check is None or check is False
|
||||
|
||||
message.text = 'not /test at start'
|
||||
check = handler.check_update(Update(0, message))
|
||||
assert check is None or check is False
|
||||
|
||||
message.entities = []
|
||||
message.text = '/test'
|
||||
check = handler.check_update(Update(0, message))
|
||||
assert check is None or check is False
|
||||
|
||||
@pytest.mark.parametrize('command',
|
||||
@pytest.mark.parametrize('cmd',
|
||||
['way_too_longcommand1234567yes_way_toooooooLong', 'ïñválídletters',
|
||||
'invalid #&* chars'],
|
||||
ids=['too long', 'invalid letter', 'invalid characters'])
|
||||
def test_invalid_commands(self, command):
|
||||
def test_invalid_commands(self, cmd):
|
||||
with pytest.raises(ValueError, match='not a valid bot command'):
|
||||
CommandHandler(command, self.callback_basic)
|
||||
CommandHandler(cmd, self.callback_basic)
|
||||
|
||||
def test_command_list(self, message):
|
||||
def test_command_list(self):
|
||||
"""A command handler with multiple commands registered should respond to all of them."""
|
||||
handler = CommandHandler(['test', 'star'], self.callback_basic)
|
||||
|
||||
message.text = '/test'
|
||||
check = handler.check_update(Update(0, message))
|
||||
|
||||
message.text = '/star'
|
||||
check = handler.check_update(Update(0, message))
|
||||
|
||||
message.text = '/stop'
|
||||
check = handler.check_update(Update(0, message))
|
||||
assert check is None or check is False
|
||||
assert is_match(handler, make_command_update('/test'))
|
||||
assert is_match(handler, make_command_update('/star'))
|
||||
assert not is_match(handler, make_command_update('/stop'))
|
||||
|
||||
def test_deprecation_warning(self):
|
||||
"""``allow_edited`` deprecated in favor of filters"""
|
||||
with pytest.warns(TelegramDeprecationWarning, match='See https://git.io/fxJuV'):
|
||||
CommandHandler('test', self.callback_basic, allow_edited=True)
|
||||
self.make_default_handler(allow_edited=True)
|
||||
|
||||
def test_no_edited(self, message):
|
||||
handler = CommandHandler('test', self.callback_basic)
|
||||
message.text = '/test'
|
||||
check = handler.check_update(Update(0, message))
|
||||
assert check is not None and check is not False
|
||||
def test_edited(self, command_message):
|
||||
"""Test that a CH responds to an edited message iff its filters allow it"""
|
||||
handler_edited = self.make_default_handler()
|
||||
handler_no_edited = self.make_default_handler(filters=~Filters.update.edited_message)
|
||||
self._test_edited(command_message, handler_edited, handler_no_edited)
|
||||
|
||||
check = handler.check_update(Update(0, edited_message=message))
|
||||
assert check is not None and check is not False
|
||||
def test_edited_deprecated(self, command_message):
|
||||
"""Test that a CH responds to an edited message iff ``allow_edited`` is True"""
|
||||
handler_edited = self.make_default_handler(allow_edited=True)
|
||||
handler_no_edited = self.make_default_handler(allow_edited=False)
|
||||
self._test_edited(command_message, handler_edited, handler_no_edited)
|
||||
|
||||
handler = CommandHandler('test', self.callback_basic,
|
||||
filters=~Filters.update.edited_message)
|
||||
check = handler.check_update(Update(0, message))
|
||||
assert check is not None and check is not False
|
||||
def test_directed_commands(self, bot, command):
|
||||
"""Test recognition of commands with a mention to the bot"""
|
||||
handler = self.make_default_handler()
|
||||
assert is_match(handler, make_command_update(command + '@' + bot.username, bot=bot))
|
||||
assert not is_match(handler, make_command_update(command + '@otherbot', bot=bot))
|
||||
|
||||
check = handler.check_update(Update(0, edited_message=message))
|
||||
assert check is None or check is False
|
||||
def test_with_filter(self, command):
|
||||
"""Test that a CH with a (generic) filter responds iff its filters match"""
|
||||
handler = self.make_default_handler(filters=Filters.group)
|
||||
assert is_match(handler, make_command_update(command, chat=Chat(-23, Chat.GROUP)))
|
||||
assert not is_match(handler, make_command_update(command, chat=Chat(23, Chat.PRIVATE)))
|
||||
|
||||
def test_edited_deprecated(self, message):
|
||||
handler = CommandHandler('test', self.callback_basic,
|
||||
allow_edited=False)
|
||||
message.text = '/test'
|
||||
check = handler.check_update(Update(0, message))
|
||||
assert check is not None and check is not False
|
||||
|
||||
check = handler.check_update(Update(0, edited_message=message))
|
||||
assert check is None or check is False
|
||||
|
||||
handler = CommandHandler('test', self.callback_basic,
|
||||
allow_edited=True)
|
||||
check = handler.check_update(Update(0, message))
|
||||
assert check is not None and check is not False
|
||||
|
||||
check = handler.check_update(Update(0, edited_message=message))
|
||||
assert check is not None and check is not False
|
||||
|
||||
def test_directed_commands(self, message):
|
||||
handler = CommandHandler('test', self.callback_basic)
|
||||
|
||||
message.text = '/test@{}'.format(message.bot.username)
|
||||
message.entities[0].length = len(message.text)
|
||||
check = handler.check_update(Update(0, message))
|
||||
assert check is not None and check is not False
|
||||
|
||||
message.text = '/test@otherbot'
|
||||
check = handler.check_update(Update(0, message))
|
||||
assert check is None or check is False
|
||||
|
||||
def test_with_filter(self, message):
|
||||
handler = CommandHandler('test', self.callback_basic, Filters.group)
|
||||
|
||||
message.chat = Chat(-23, 'group')
|
||||
message.text = '/test'
|
||||
check = handler.check_update(Update(0, message))
|
||||
assert check is not None and check is not False
|
||||
|
||||
message.chat = Chat(23, 'private')
|
||||
check = handler.check_update(Update(0, message))
|
||||
assert check is None or check is False
|
||||
|
||||
def test_pass_args(self, dp, message):
|
||||
handler = CommandHandler('test', self.ch_callback_args, pass_args=True)
|
||||
def test_pass_args(self, dp, bot, command):
|
||||
"""Test the passing of arguments alongside a command"""
|
||||
handler = self.make_default_handler(self.ch_callback_args, pass_args=True)
|
||||
dp.add_handler(handler)
|
||||
at_command = '{}@{}'.format(command, bot.username)
|
||||
assert self.response(dp, make_command_update(command))
|
||||
assert self.response(dp, make_command_update(command + ' one two'))
|
||||
assert self.response(dp, make_command_update(at_command, bot=bot))
|
||||
assert self.response(dp, make_command_update(at_command + ' one two', bot=bot))
|
||||
|
||||
message.text = '/test'
|
||||
dp.process_update(Update(0, message=message))
|
||||
assert self.test_flag
|
||||
|
||||
self.test_flag = False
|
||||
message.text = '/test@{}'.format(message.bot.username)
|
||||
message.entities[0].length = len(message.text)
|
||||
dp.process_update(Update(0, message=message))
|
||||
assert self.test_flag
|
||||
|
||||
self.test_flag = False
|
||||
message.text = '/test@{} one two'.format(message.bot.username)
|
||||
dp.process_update(Update(0, message=message))
|
||||
assert self.test_flag
|
||||
|
||||
self.test_flag = False
|
||||
message.text = '/test one two'
|
||||
message.entities[0].length = len('/test')
|
||||
dp.process_update(Update(0, message=message))
|
||||
assert self.test_flag
|
||||
|
||||
def test_newline(self, dp, message):
|
||||
handler = CommandHandler('test', self.callback_basic)
|
||||
def test_newline(self, dp, command):
|
||||
"""Assert that newlines don't interfere with a command handler matching a message"""
|
||||
handler = self.make_default_handler()
|
||||
dp.add_handler(handler)
|
||||
update = make_command_update(command + '\nfoobar')
|
||||
assert is_match(handler, update)
|
||||
assert self.response(dp, update)
|
||||
|
||||
message.text = '/test\nfoobar'
|
||||
check = handler.check_update(Update(0, message))
|
||||
assert check is not None and check is not False
|
||||
|
||||
dp.process_update(Update(0, message))
|
||||
assert self.test_flag
|
||||
|
||||
def test_pass_user_or_chat_data(self, dp, message):
|
||||
handler = CommandHandler('test', self.callback_data_1,
|
||||
pass_user_data=True)
|
||||
@pytest.mark.parametrize('pass_keyword', BaseTest.PASS_KEYWORDS)
|
||||
def test_pass_data(self, dp, command_update, pass_combination, pass_keyword):
|
||||
handler = CommandHandler('test', self.make_callback_for(pass_keyword), **pass_combination)
|
||||
dp.add_handler(handler)
|
||||
|
||||
message.text = '/test'
|
||||
dp.process_update(Update(0, message=message))
|
||||
assert self.test_flag
|
||||
|
||||
dp.remove_handler(handler)
|
||||
handler = CommandHandler('test', self.callback_data_1,
|
||||
pass_chat_data=True)
|
||||
dp.add_handler(handler)
|
||||
|
||||
self.test_flag = False
|
||||
dp.process_update(Update(0, message=message))
|
||||
assert self.test_flag
|
||||
|
||||
dp.remove_handler(handler)
|
||||
handler = CommandHandler('test', self.callback_data_2,
|
||||
pass_chat_data=True,
|
||||
pass_user_data=True)
|
||||
dp.add_handler(handler)
|
||||
|
||||
self.test_flag = False
|
||||
dp.process_update(Update(0, message=message))
|
||||
assert self.test_flag
|
||||
|
||||
def test_pass_job_or_update_queue(self, dp, message):
|
||||
handler = CommandHandler('test', self.callback_queue_1,
|
||||
pass_job_queue=True)
|
||||
dp.add_handler(handler)
|
||||
|
||||
message.text = '/test'
|
||||
dp.process_update(Update(0, message=message))
|
||||
assert self.test_flag
|
||||
|
||||
dp.remove_handler(handler)
|
||||
handler = CommandHandler('test', self.callback_queue_1,
|
||||
pass_update_queue=True)
|
||||
dp.add_handler(handler)
|
||||
|
||||
self.test_flag = False
|
||||
dp.process_update(Update(0, message=message))
|
||||
assert self.test_flag
|
||||
|
||||
dp.remove_handler(handler)
|
||||
handler = CommandHandler('test', self.callback_queue_2,
|
||||
pass_job_queue=True,
|
||||
pass_update_queue=True)
|
||||
dp.add_handler(handler)
|
||||
|
||||
self.test_flag = False
|
||||
dp.process_update(Update(0, message=message))
|
||||
assert self.test_flag
|
||||
assert self.response(dp, command_update) == pass_combination.get(pass_keyword, False)
|
||||
|
||||
def test_other_update_types(self, false_update):
|
||||
handler = CommandHandler('test', self.callback_basic)
|
||||
check = handler.check_update(false_update)
|
||||
assert check is None or check is False
|
||||
"""Test that a command handler doesn't respond to unrelated updates"""
|
||||
handler = self.make_default_handler()
|
||||
assert not is_match(handler, false_update)
|
||||
|
||||
def test_filters_for_wrong_command(self, message):
|
||||
def test_filters_for_wrong_command(self, mock_filter):
|
||||
"""Filters should not be executed if the command does not match the handler"""
|
||||
handler = self.make_default_handler(filters=mock_filter)
|
||||
assert not is_match(handler, make_command_update('/star'))
|
||||
assert not mock_filter.tested
|
||||
|
||||
class TestFilter(BaseFilter):
|
||||
def __init__(self):
|
||||
self.tested = False
|
||||
|
||||
def filter(self, message):
|
||||
self.tested = True
|
||||
|
||||
test_filter = TestFilter()
|
||||
|
||||
handler = CommandHandler('test', self.callback_basic,
|
||||
filters=test_filter)
|
||||
message.text = '/star'
|
||||
|
||||
check = handler.check_update(Update(0, message=message))
|
||||
assert check is None or check is False
|
||||
|
||||
assert not test_filter.tested
|
||||
|
||||
def test_context(self, cdp, message):
|
||||
handler = CommandHandler('test', self.callback_context)
|
||||
def test_context(self, cdp, command_update):
|
||||
"""Test correct behaviour of CHs with context-based callbacks"""
|
||||
handler = self.make_default_handler(self.callback_context)
|
||||
cdp.add_handler(handler)
|
||||
assert self.response(cdp, command_update)
|
||||
|
||||
message.text = '/test'
|
||||
cdp.process_update(Update(0, message))
|
||||
assert self.test_flag
|
||||
def test_context_args(self, cdp, command):
|
||||
"""Test CHs that pass arguments through ``context``"""
|
||||
handler = self.make_default_handler(self.callback_context_args)
|
||||
self._test_context_args_or_regex(cdp, handler, command)
|
||||
|
||||
def test_context_args(self, cdp, message):
|
||||
handler = CommandHandler('test', self.callback_context_args)
|
||||
cdp.add_handler(handler)
|
||||
def test_context_regex(self, cdp, command):
|
||||
"""Test CHs with context-based callbacks and a single filter"""
|
||||
handler = self.make_default_handler(self.callback_context_regex1,
|
||||
filters=Filters.regex('one two'))
|
||||
self._test_context_args_or_regex(cdp, handler, command)
|
||||
|
||||
message.text = '/test'
|
||||
cdp.process_update(Update(0, message))
|
||||
assert not self.test_flag
|
||||
|
||||
message.text = '/test one two'
|
||||
cdp.process_update(Update(0, message))
|
||||
assert self.test_flag
|
||||
|
||||
def test_context_regex(self, cdp, message):
|
||||
handler = CommandHandler('test', self.callback_context_regex1, Filters.regex('one two'))
|
||||
cdp.add_handler(handler)
|
||||
|
||||
message.text = '/test'
|
||||
cdp.process_update(Update(0, message))
|
||||
assert not self.test_flag
|
||||
|
||||
message.text += ' one two'
|
||||
cdp.process_update(Update(0, message))
|
||||
assert self.test_flag
|
||||
|
||||
def test_context_multiple_regex(self, cdp, message):
|
||||
handler = CommandHandler('test', self.callback_context_regex2,
|
||||
Filters.regex('one') & Filters.regex('two'))
|
||||
cdp.add_handler(handler)
|
||||
|
||||
message.text = '/test'
|
||||
cdp.process_update(Update(0, message))
|
||||
assert not self.test_flag
|
||||
|
||||
message.text += ' one two'
|
||||
cdp.process_update(Update(0, message))
|
||||
assert self.test_flag
|
||||
def test_context_multiple_regex(self, cdp, command):
|
||||
"""Test CHs with context-based callbacks and filters combined"""
|
||||
handler = self.make_default_handler(self.callback_context_regex2,
|
||||
filters=Filters.regex('one') & Filters.regex('two'))
|
||||
self._test_context_args_or_regex(cdp, handler, command)
|
||||
|
||||
|
||||
par = ['!help', '!test', '#help', '#test', 'mytrig-help', 'mytrig-test']
|
||||
# ----------------------------- PrefixHandler -----------------------------
|
||||
|
||||
def combinations(prefixes, commands):
|
||||
return (prefix + command for prefix in prefixes for command in commands)
|
||||
|
||||
|
||||
@pytest.fixture(scope='function', params=par)
|
||||
def prefixmessage(bot, request):
|
||||
return Message(message_id=1,
|
||||
from_user=User(id=1, first_name='', is_bot=False),
|
||||
date=None,
|
||||
chat=Chat(id=1, type=''),
|
||||
text=request.param,
|
||||
bot=bot)
|
||||
class TestPrefixHandler(BaseTest):
|
||||
# Prefixes and commands with which to test PrefixHandler:
|
||||
PREFIXES = ['!', '#', 'mytrig-']
|
||||
COMMANDS = ['help', 'test']
|
||||
COMBINATIONS = list(combinations(PREFIXES, COMMANDS))
|
||||
|
||||
@pytest.fixture(scope='class', params=PREFIXES)
|
||||
def prefix(self, request):
|
||||
return request.param
|
||||
|
||||
class TestPrefixHandler(object):
|
||||
test_flag = False
|
||||
SRE_TYPE = type(re.match("", ""))
|
||||
@pytest.fixture(scope='class', params=[1, 2], ids=['single prefix', 'multiple prefixes'])
|
||||
def prefixes(self, request):
|
||||
return TestPrefixHandler.PREFIXES[:request.param]
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def reset(self):
|
||||
self.test_flag = False
|
||||
@pytest.fixture(scope='class', params=COMMANDS)
|
||||
def command(self, request):
|
||||
return request.param
|
||||
|
||||
def callback_basic(self, bot, update):
|
||||
test_bot = isinstance(bot, Bot)
|
||||
test_update = isinstance(update, Update)
|
||||
self.test_flag = test_bot and test_update
|
||||
@pytest.fixture(scope='class', params=[1, 2], ids=['single command', 'multiple commands'])
|
||||
def commands(self, request):
|
||||
return TestPrefixHandler.COMMANDS[:request.param]
|
||||
|
||||
def callback_data_1(self, bot, update, user_data=None, chat_data=None):
|
||||
self.test_flag = (user_data is not None) or (chat_data is not None)
|
||||
@pytest.fixture(scope='class')
|
||||
def prefix_message_text(self, prefix, command):
|
||||
return prefix + command
|
||||
|
||||
def callback_data_2(self, bot, update, user_data=None, chat_data=None):
|
||||
self.test_flag = (user_data is not None) and (chat_data is not None)
|
||||
@pytest.fixture(scope='class')
|
||||
def prefix_message(self, prefix_message_text):
|
||||
return make_message(prefix_message_text)
|
||||
|
||||
def callback_queue_1(self, bot, update, job_queue=None, update_queue=None):
|
||||
self.test_flag = (job_queue is not None) or (update_queue is not None)
|
||||
@pytest.fixture(scope='class')
|
||||
def prefix_message_update(self, prefix_message):
|
||||
return make_message_update(prefix_message)
|
||||
|
||||
def callback_queue_2(self, bot, update, job_queue=None, update_queue=None):
|
||||
self.test_flag = (job_queue is not None) and (update_queue is not None)
|
||||
def make_default_handler(self, callback=None, **kwargs):
|
||||
callback = callback or self.callback_basic
|
||||
return PrefixHandler(self.PREFIXES, self.COMMANDS, callback, **kwargs)
|
||||
|
||||
def ch_callback_args(self, bot, update, args):
|
||||
if update.message.text in par:
|
||||
if update.message.text in TestPrefixHandler.COMBINATIONS:
|
||||
self.test_flag = len(args) == 0
|
||||
else:
|
||||
self.test_flag = args == ['one', 'two']
|
||||
|
||||
def callback_context(self, update, context):
|
||||
self.test_flag = (isinstance(context, CallbackContext)
|
||||
and isinstance(context.bot, Bot)
|
||||
and isinstance(update, Update)
|
||||
and isinstance(context.update_queue, Queue)
|
||||
and isinstance(context.job_queue, JobQueue)
|
||||
and isinstance(context.user_data, dict)
|
||||
and isinstance(context.chat_data, dict)
|
||||
and isinstance(update.message, Message))
|
||||
|
||||
def callback_context_args(self, update, context):
|
||||
self.test_flag = context.args == ['one', 'two']
|
||||
|
||||
def callback_context_regex1(self, update, context):
|
||||
if context.matches:
|
||||
types = all([type(res) == self.SRE_TYPE for res in context.matches])
|
||||
num = len(context.matches) == 1
|
||||
self.test_flag = types and num
|
||||
|
||||
def callback_context_regex2(self, update, context):
|
||||
if context.matches:
|
||||
types = all([type(res) == self.SRE_TYPE for res in context.matches])
|
||||
num = len(context.matches) == 2
|
||||
self.test_flag = types and num
|
||||
|
||||
def test_basic(self, dp, prefixmessage):
|
||||
handler = PrefixHandler(['!', '#', 'mytrig-'], ['help', 'test'], self.callback_basic)
|
||||
def test_basic(self, dp, prefix, command):
|
||||
"""Test the basic expected response from a prefix handler"""
|
||||
handler = self.make_default_handler()
|
||||
dp.add_handler(handler)
|
||||
text = prefix + command
|
||||
|
||||
dp.process_update(Update(0, prefixmessage))
|
||||
assert self.test_flag
|
||||
assert self.response(dp, make_message_update(text))
|
||||
assert not is_match(handler, make_message_update(command))
|
||||
assert not is_match(handler, make_message_update(prefix + 'notacommand'))
|
||||
assert not is_match(handler, make_command_update('not {} at start'.format(text)))
|
||||
|
||||
prefixmessage.text = 'test'
|
||||
check = handler.check_update(Update(0, prefixmessage))
|
||||
assert check is None or check is False
|
||||
def test_single_multi_prefixes_commands(self, prefixes, commands, prefix_message_update):
|
||||
"""Test various combinations of prefixes and commands"""
|
||||
handler = self.make_default_handler()
|
||||
result = is_match(handler, prefix_message_update)
|
||||
expected = prefix_message_update.message.text in combinations(prefixes, commands)
|
||||
return result == expected
|
||||
|
||||
prefixmessage.text = '#nocom'
|
||||
check = handler.check_update(Update(0, prefixmessage))
|
||||
assert check is None or check is False
|
||||
def test_edited(self, prefix_message):
|
||||
handler_edited = self.make_default_handler()
|
||||
handler_no_edited = self.make_default_handler(filters=~Filters.update.edited_message)
|
||||
self._test_edited(prefix_message, handler_edited, handler_no_edited)
|
||||
|
||||
message.text = 'not !test at start'
|
||||
check = handler.check_update(Update(0, message))
|
||||
assert check is None or check is False
|
||||
def test_with_filter(self, prefix_message_text):
|
||||
handler = self.make_default_handler(filters=Filters.group)
|
||||
text = prefix_message_text
|
||||
assert is_match(handler, make_message_update(text, chat=Chat(-23, Chat.GROUP)))
|
||||
assert not is_match(handler, make_message_update(text, chat=Chat(23, Chat.PRIVATE)))
|
||||
|
||||
def test_single_prefix_single_command(self, prefixmessage):
|
||||
handler = PrefixHandler('!', 'test', self.callback_basic)
|
||||
|
||||
check = handler.check_update(Update(0, prefixmessage))
|
||||
if prefixmessage.text in ['!test']:
|
||||
assert check is not None and check is not False
|
||||
else:
|
||||
assert check is None or check is False
|
||||
|
||||
def test_single_prefix_multi_command(self, prefixmessage):
|
||||
handler = PrefixHandler('!', ['test', 'help'], self.callback_basic)
|
||||
|
||||
check = handler.check_update(Update(0, prefixmessage))
|
||||
if prefixmessage.text in ['!test', '!help']:
|
||||
assert check is not None and check is not False
|
||||
else:
|
||||
assert check is None or check is False
|
||||
|
||||
def test_multi_prefix_single_command(self, prefixmessage):
|
||||
handler = PrefixHandler(['!', '#'], 'test', self.callback_basic)
|
||||
|
||||
check = handler.check_update(Update(0, prefixmessage))
|
||||
if prefixmessage.text in ['!test', '#test']:
|
||||
assert check is not None and check is not False
|
||||
else:
|
||||
assert check is None or check is False
|
||||
|
||||
def test_no_edited(self, prefixmessage):
|
||||
handler = PrefixHandler(['!', '#', 'mytrig-'], ['help', 'test'], self.callback_basic)
|
||||
check = handler.check_update(Update(0, prefixmessage))
|
||||
assert check is not None and check is not False
|
||||
|
||||
check = handler.check_update(Update(0, edited_message=prefixmessage))
|
||||
assert check is not None and check is not False
|
||||
|
||||
handler = PrefixHandler(['!', '#', 'mytrig-'], ['help', 'test'], self.callback_basic,
|
||||
filters=~Filters.update.edited_message)
|
||||
check = handler.check_update(Update(0, prefixmessage))
|
||||
assert check is not None and check is not False
|
||||
|
||||
check = handler.check_update(Update(0, edited_message=prefixmessage))
|
||||
assert check is None or check is False
|
||||
|
||||
def test_with_filter(self, prefixmessage):
|
||||
handler = PrefixHandler(['!', '#', 'mytrig-'], ['help', 'test'], self.callback_basic,
|
||||
filters=Filters.group)
|
||||
|
||||
prefixmessage.chat = Chat(-23, 'group')
|
||||
check = handler.check_update(Update(0, prefixmessage))
|
||||
assert check is not None and check is not False
|
||||
|
||||
prefixmessage.chat = Chat(23, 'private')
|
||||
check = handler.check_update(Update(0, prefixmessage))
|
||||
assert check is None or check is False
|
||||
|
||||
def test_pass_args(self, dp, prefixmessage):
|
||||
handler = PrefixHandler(['!', '#', 'mytrig-'], ['help', 'test'], self.ch_callback_args,
|
||||
pass_args=True)
|
||||
def test_pass_args(self, dp, prefix_message):
|
||||
handler = self.make_default_handler(self.ch_callback_args, pass_args=True)
|
||||
dp.add_handler(handler)
|
||||
assert self.response(dp, make_message_update(prefix_message))
|
||||
|
||||
dp.process_update(Update(0, message=prefixmessage))
|
||||
assert self.test_flag
|
||||
update_with_args = make_message_update(prefix_message.text + ' one two')
|
||||
assert self.response(dp, update_with_args)
|
||||
|
||||
self.test_flag = False
|
||||
prefixmessage.text += ' one two'
|
||||
dp.process_update(Update(0, message=prefixmessage))
|
||||
assert self.test_flag
|
||||
|
||||
def test_pass_user_or_chat_data(self, dp, prefixmessage):
|
||||
handler = PrefixHandler(['!', '#', 'mytrig-'], ['help', 'test'], self.callback_data_1,
|
||||
pass_user_data=True)
|
||||
@pytest.mark.parametrize('pass_keyword', BaseTest.PASS_KEYWORDS)
|
||||
def test_pass_data(self, dp, pass_combination, prefix_message_update, pass_keyword):
|
||||
"""Assert that callbacks receive data iff its corresponding ``pass_*`` kwarg is enabled"""
|
||||
handler = self.make_default_handler(self.make_callback_for(pass_keyword),
|
||||
**pass_combination)
|
||||
dp.add_handler(handler)
|
||||
|
||||
dp.process_update(Update(0, message=prefixmessage))
|
||||
assert self.test_flag
|
||||
|
||||
dp.remove_handler(handler)
|
||||
self.test_flag = False
|
||||
handler = PrefixHandler(['!', '#', 'mytrig-'], ['help', 'test'], self.callback_data_1,
|
||||
pass_chat_data=True)
|
||||
dp.add_handler(handler)
|
||||
dp.process_update(Update(0, message=prefixmessage))
|
||||
assert self.test_flag
|
||||
|
||||
dp.remove_handler(handler)
|
||||
self.test_flag = False
|
||||
handler = PrefixHandler(['!', '#', 'mytrig-'], ['help', 'test'], self.callback_data_2,
|
||||
pass_chat_data=True, pass_user_data=True)
|
||||
dp.add_handler(handler)
|
||||
dp.process_update(Update(0, message=prefixmessage))
|
||||
assert self.test_flag
|
||||
|
||||
def test_pass_job_or_update_queue(self, dp, prefixmessage):
|
||||
handler = PrefixHandler(['!', '#', 'mytrig-'], ['help', 'test'], self.callback_queue_1,
|
||||
pass_job_queue=True)
|
||||
dp.add_handler(handler)
|
||||
|
||||
dp.process_update(Update(0, message=prefixmessage))
|
||||
assert self.test_flag
|
||||
|
||||
dp.remove_handler(handler)
|
||||
self.test_flag = False
|
||||
handler = PrefixHandler(['!', '#', 'mytrig-'], ['help', 'test'], self.callback_queue_1,
|
||||
pass_update_queue=True)
|
||||
dp.add_handler(handler)
|
||||
dp.process_update(Update(0, message=prefixmessage))
|
||||
assert self.test_flag
|
||||
|
||||
dp.remove_handler(handler)
|
||||
self.test_flag = False
|
||||
handler = PrefixHandler(['!', '#', 'mytrig-'], ['help', 'test'], self.callback_queue_2,
|
||||
pass_job_queue=True, pass_update_queue=True)
|
||||
dp.add_handler(handler)
|
||||
dp.process_update(Update(0, message=prefixmessage))
|
||||
assert self.test_flag
|
||||
assert self.response(dp, prefix_message_update) \
|
||||
== pass_combination.get(pass_keyword, False)
|
||||
|
||||
def test_other_update_types(self, false_update):
|
||||
handler = PrefixHandler(['!', '#', 'mytrig-'], ['help', 'test'], self.callback_basic)
|
||||
check = handler.check_update(false_update)
|
||||
assert check is None or check is False
|
||||
handler = self.make_default_handler()
|
||||
assert not is_match(handler, false_update)
|
||||
|
||||
def test_filters_for_wrong_command(self, prefixmessage):
|
||||
def test_filters_for_wrong_command(self, mock_filter):
|
||||
"""Filters should not be executed if the command does not match the handler"""
|
||||
handler = self.make_default_handler(filters=mock_filter)
|
||||
assert not is_match(handler, make_message_update('/test'))
|
||||
assert not mock_filter.tested
|
||||
|
||||
class TestFilter(BaseFilter):
|
||||
def __init__(self):
|
||||
self.tested = False
|
||||
|
||||
def filter(self, message):
|
||||
self.tested = True
|
||||
|
||||
test_filter = TestFilter()
|
||||
|
||||
handler = PrefixHandler(['!', '#', 'mytrig-'], ['help', 'test'], self.callback_basic,
|
||||
filters=test_filter)
|
||||
|
||||
prefixmessage.text = '/star'
|
||||
|
||||
check = handler.check_update(Update(0, message=prefixmessage))
|
||||
assert check is None or check is False
|
||||
|
||||
assert not test_filter.tested
|
||||
|
||||
def test_context(self, cdp, prefixmessage):
|
||||
handler = PrefixHandler(['!', '#', 'mytrig-'], ['help', 'test'], self.callback_context)
|
||||
def test_context(self, cdp, prefix_message_update):
|
||||
handler = self.make_default_handler(self.callback_context)
|
||||
cdp.add_handler(handler)
|
||||
assert self.response(cdp, prefix_message_update)
|
||||
|
||||
cdp.process_update(Update(0, prefixmessage))
|
||||
assert self.test_flag
|
||||
def test_context_args(self, cdp, prefix_message_text):
|
||||
handler = self.make_default_handler(self.callback_context_args)
|
||||
self._test_context_args_or_regex(cdp, handler, prefix_message_text)
|
||||
|
||||
def test_context_args(self, cdp, prefixmessage):
|
||||
handler = PrefixHandler(['!', '#', 'mytrig-'], ['help', 'test'],
|
||||
self.callback_context_args)
|
||||
cdp.add_handler(handler)
|
||||
def test_context_regex(self, cdp, prefix_message_text):
|
||||
handler = self.make_default_handler(self.callback_context_regex1,
|
||||
filters=Filters.regex('one two'))
|
||||
self._test_context_args_or_regex(cdp, handler, prefix_message_text)
|
||||
|
||||
cdp.process_update(Update(0, prefixmessage))
|
||||
assert not self.test_flag
|
||||
|
||||
prefixmessage.text += ' one two'
|
||||
cdp.process_update(Update(0, prefixmessage))
|
||||
assert self.test_flag
|
||||
|
||||
def test_context_regex(self, cdp, prefixmessage):
|
||||
handler = PrefixHandler(['!', '#', 'mytrig-'], ['help', 'test'],
|
||||
self.callback_context_regex1, Filters.regex('one two'))
|
||||
cdp.add_handler(handler)
|
||||
|
||||
cdp.process_update(Update(0, prefixmessage))
|
||||
assert not self.test_flag
|
||||
|
||||
prefixmessage.text += ' one two'
|
||||
cdp.process_update(Update(0, prefixmessage))
|
||||
assert self.test_flag
|
||||
|
||||
def test_context_multiple_regex(self, cdp, prefixmessage):
|
||||
handler = PrefixHandler(['!', '#', 'mytrig-'], ['help', 'test'],
|
||||
self.callback_context_regex2,
|
||||
Filters.regex('one') & Filters.regex('two'))
|
||||
cdp.add_handler(handler)
|
||||
|
||||
cdp.process_update(Update(0, prefixmessage))
|
||||
assert not self.test_flag
|
||||
|
||||
prefixmessage.text += ' one two'
|
||||
cdp.process_update(Update(0, prefixmessage))
|
||||
assert self.test_flag
|
||||
def test_context_multiple_regex(self, cdp, prefix_message_text):
|
||||
handler = self.make_default_handler(self.callback_context_regex2,
|
||||
filters=Filters.regex('one') & Filters.regex(
|
||||
'two'))
|
||||
self._test_context_args_or_regex(cdp, handler, prefix_message_text)
|
||||
|
|
|
@ -43,6 +43,10 @@ class TestConversationHandler(object):
|
|||
# and then we can start coding!
|
||||
END, THIRSTY, BREWING, DRINKING, CODING = range(-1, 4)
|
||||
|
||||
# Drinking state definitions (nested)
|
||||
# At first we're holding the cup. Then we sip coffee, and last we swallow it
|
||||
HOLDING, SIPPING, SWALLOWING, REPLENISHING, STOPPING = map(chr, range(ord('a'), ord('f')))
|
||||
|
||||
current_state, entry_points, states, fallbacks = None, None, None, None
|
||||
group = Chat(0, Chat.GROUP)
|
||||
second_group = Chat(1, Chat.GROUP)
|
||||
|
@ -69,6 +73,43 @@ class TestConversationHandler(object):
|
|||
self.fallbacks = [CommandHandler('eat', self.start)]
|
||||
self.is_timeout = False
|
||||
|
||||
# for nesting tests
|
||||
self.nested_states = {
|
||||
self.THIRSTY: [CommandHandler('brew', self.brew), CommandHandler('wait', self.start)],
|
||||
self.BREWING: [CommandHandler('pourCoffee', self.drink)],
|
||||
self.CODING: [
|
||||
CommandHandler('keepCoding', self.code),
|
||||
CommandHandler('gettingThirsty', self.start),
|
||||
CommandHandler('drinkMore', self.drink)
|
||||
],
|
||||
}
|
||||
self.drinking_entry_points = [CommandHandler('hold', self.hold)]
|
||||
self.drinking_states = {
|
||||
self.HOLDING: [CommandHandler('sip', self.sip)],
|
||||
self.SIPPING: [CommandHandler('swallow', self.swallow)],
|
||||
self.SWALLOWING: [CommandHandler('hold', self.hold)]
|
||||
}
|
||||
self.drinking_fallbacks = [CommandHandler('replenish', self.replenish),
|
||||
CommandHandler('stop', self.stop),
|
||||
CommandHandler('end', self.end),
|
||||
CommandHandler('startCoding', self.code),
|
||||
CommandHandler('drinkMore', self.drink)]
|
||||
self.drinking_entry_points.extend(self.drinking_fallbacks)
|
||||
|
||||
# Map nested states to parent states:
|
||||
self.drinking_map_to_parent = {
|
||||
# Option 1 - Map a fictional internal state to an external parent state
|
||||
self.REPLENISHING: self.BREWING,
|
||||
# Option 2 - Map a fictional internal state to the END state on the parent
|
||||
self.STOPPING: self.END,
|
||||
# Option 3 - Map the internal END state to an external parent state
|
||||
self.END: self.CODING,
|
||||
# Option 4 - Map an external state to the same external parent state
|
||||
self.CODING: self.CODING,
|
||||
# Option 5 - Map an external state to the internal entry point
|
||||
self.DRINKING: self.DRINKING
|
||||
}
|
||||
|
||||
# State handlers
|
||||
def _set_state(self, update, state):
|
||||
self.current_state[update.message.from_user.id] = state
|
||||
|
@ -103,6 +144,23 @@ class TestConversationHandler(object):
|
|||
def passout2(self, bot, update):
|
||||
self.is_timeout = True
|
||||
|
||||
# Drinking actions (nested)
|
||||
|
||||
def hold(self, bot, update):
|
||||
return self._set_state(update, self.HOLDING)
|
||||
|
||||
def sip(self, bot, update):
|
||||
return self._set_state(update, self.SIPPING)
|
||||
|
||||
def swallow(self, bot, update):
|
||||
return self._set_state(update, self.SWALLOWING)
|
||||
|
||||
def replenish(self, bot, update):
|
||||
return self._set_state(update, self.REPLENISHING)
|
||||
|
||||
def stop(self, bot, update):
|
||||
return self._set_state(update, self.STOPPING)
|
||||
|
||||
# Tests
|
||||
def test_per_all_false(self):
|
||||
with pytest.raises(ValueError, match="can't all be 'False'"):
|
||||
|
@ -609,3 +667,108 @@ class TestConversationHandler(object):
|
|||
"If 'per_chat=True', 'InlineQueryHandler' can not be used,"
|
||||
" since inline queries have no chat context."
|
||||
)
|
||||
|
||||
def test_nested_conversation_handler(self, dp, bot, user1, user2):
|
||||
self.nested_states[self.DRINKING] = [ConversationHandler(
|
||||
entry_points=self.drinking_entry_points,
|
||||
states=self.drinking_states,
|
||||
fallbacks=self.drinking_fallbacks,
|
||||
map_to_parent=self.drinking_map_to_parent)]
|
||||
handler = ConversationHandler(entry_points=self.entry_points,
|
||||
states=self.nested_states,
|
||||
fallbacks=self.fallbacks)
|
||||
dp.add_handler(handler)
|
||||
|
||||
# User one, starts the state machine.
|
||||
message = Message(0, user1, None, self.group, text='/start', bot=bot,
|
||||
entities=[MessageEntity(type=MessageEntity.BOT_COMMAND,
|
||||
offset=0, length=len('/start'))])
|
||||
dp.process_update(Update(update_id=0, message=message))
|
||||
assert self.current_state[user1.id] == self.THIRSTY
|
||||
|
||||
# The user is thirsty and wants to brew coffee.
|
||||
message.text = '/brew'
|
||||
message.entities[0].length = len('/brew')
|
||||
dp.process_update(Update(update_id=0, message=message))
|
||||
assert self.current_state[user1.id] == self.BREWING
|
||||
|
||||
# Lets pour some coffee.
|
||||
message.text = '/pourCoffee'
|
||||
message.entities[0].length = len('/pourCoffee')
|
||||
dp.process_update(Update(update_id=0, message=message))
|
||||
assert self.current_state[user1.id] == self.DRINKING
|
||||
|
||||
# The user is holding the cup
|
||||
message.text = '/hold'
|
||||
message.entities[0].length = len('/hold')
|
||||
dp.process_update(Update(update_id=0, message=message))
|
||||
assert self.current_state[user1.id] == self.HOLDING
|
||||
|
||||
# The user is sipping coffee
|
||||
message.text = '/sip'
|
||||
message.entities[0].length = len('/sip')
|
||||
dp.process_update(Update(update_id=0, message=message))
|
||||
assert self.current_state[user1.id] == self.SIPPING
|
||||
|
||||
# The user is swallowing
|
||||
message.text = '/swallow'
|
||||
message.entities[0].length = len('/swallow')
|
||||
dp.process_update(Update(update_id=0, message=message))
|
||||
assert self.current_state[user1.id] == self.SWALLOWING
|
||||
|
||||
# The user is holding the cup again
|
||||
message.text = '/hold'
|
||||
message.entities[0].length = len('/hold')
|
||||
dp.process_update(Update(update_id=0, message=message))
|
||||
assert self.current_state[user1.id] == self.HOLDING
|
||||
|
||||
# The user wants to replenish the coffee supply
|
||||
message.text = '/replenish'
|
||||
message.entities[0].length = len('/replenish')
|
||||
dp.process_update(Update(update_id=0, message=message))
|
||||
assert self.current_state[user1.id] == self.REPLENISHING
|
||||
assert handler.conversations[(0, user1.id)] == self.BREWING
|
||||
|
||||
# The user wants to drink their coffee again
|
||||
message.text = '/pourCoffee'
|
||||
message.entities[0].length = len('/pourCoffee')
|
||||
dp.process_update(Update(update_id=0, message=message))
|
||||
assert self.current_state[user1.id] == self.DRINKING
|
||||
|
||||
# The user is now ready to start coding
|
||||
message.text = '/startCoding'
|
||||
message.entities[0].length = len('/startCoding')
|
||||
dp.process_update(Update(update_id=0, message=message))
|
||||
assert self.current_state[user1.id] == self.CODING
|
||||
|
||||
# The user decides it's time to drink again
|
||||
message.text = '/drinkMore'
|
||||
message.entities[0].length = len('/drinkMore')
|
||||
dp.process_update(Update(update_id=0, message=message))
|
||||
assert self.current_state[user1.id] == self.DRINKING
|
||||
|
||||
# The user is holding their cup
|
||||
message.text = '/hold'
|
||||
message.entities[0].length = len('/hold')
|
||||
dp.process_update(Update(update_id=0, message=message))
|
||||
assert self.current_state[user1.id] == self.HOLDING
|
||||
|
||||
# The user wants to end with the drinking and go back to coding
|
||||
message.text = '/end'
|
||||
message.entities[0].length = len('/end')
|
||||
dp.process_update(Update(update_id=0, message=message))
|
||||
assert self.current_state[user1.id] == self.END
|
||||
assert handler.conversations[(0, user1.id)] == self.CODING
|
||||
|
||||
# The user wants to drink once more
|
||||
message.text = '/drinkMore'
|
||||
message.entities[0].length = len('/drinkMore')
|
||||
dp.process_update(Update(update_id=0, message=message))
|
||||
assert self.current_state[user1.id] == self.DRINKING
|
||||
|
||||
# The user wants to stop altogether
|
||||
message.text = '/stop'
|
||||
message.entities[0].length = len('/stop')
|
||||
dp.process_update(Update(update_id=0, message=message))
|
||||
assert self.current_state[user1.id] == self.STOPPING
|
||||
assert handler.conversations.get((0, user1.id)) is None
|
||||
|
|
|
@ -24,10 +24,12 @@ from time import sleep
|
|||
import pytest
|
||||
|
||||
from telegram import TelegramError, Message, User, Chat, Update, Bot, MessageEntity
|
||||
from telegram.ext import MessageHandler, Filters, CommandHandler, CallbackContext, JobQueue
|
||||
from telegram.ext import (MessageHandler, Filters, CommandHandler, CallbackContext,
|
||||
JobQueue, BasePersistence)
|
||||
from telegram.ext.dispatcher import run_async, Dispatcher, DispatcherHandlerStop
|
||||
from telegram.utils.deprecate import TelegramDeprecationWarning
|
||||
from tests.conftest import create_dp
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
|
@ -276,10 +278,11 @@ class TestDispatcher(object):
|
|||
|
||||
def test_exception_in_handler(self, dp, bot):
|
||||
passed = []
|
||||
err = Exception('General exception')
|
||||
|
||||
def start1(b, u):
|
||||
passed.append('start1')
|
||||
raise Exception('General exception')
|
||||
raise err
|
||||
|
||||
def start2(b, u):
|
||||
passed.append('start2')
|
||||
|
@ -298,14 +301,14 @@ class TestDispatcher(object):
|
|||
bot=bot))
|
||||
|
||||
# If an unhandled exception was caught, no further handlers from the same group should be
|
||||
# called.
|
||||
# called. Also, the error handler should be called and receive the exception
|
||||
passed = []
|
||||
dp.add_handler(CommandHandler('start', start1), 1)
|
||||
dp.add_handler(CommandHandler('start', start2), 1)
|
||||
dp.add_handler(CommandHandler('start', start3), 2)
|
||||
dp.add_error_handler(error)
|
||||
dp.process_update(update)
|
||||
assert passed == ['start1', 'start3']
|
||||
assert passed == ['start1', 'error', err, 'start3']
|
||||
|
||||
def test_telegram_error_in_handler(self, dp, bot):
|
||||
passed = []
|
||||
|
@ -341,6 +344,49 @@ class TestDispatcher(object):
|
|||
assert passed == ['start1', 'error', err, 'start3']
|
||||
assert passed[2] is err
|
||||
|
||||
def test_error_while_saving_chat_data(self, dp, bot):
|
||||
increment = []
|
||||
|
||||
class OwnPersistence(BasePersistence):
|
||||
def __init__(self):
|
||||
super(BasePersistence, self).__init__()
|
||||
self.store_user_data = True
|
||||
self.store_chat_data = True
|
||||
|
||||
def get_chat_data(self):
|
||||
return defaultdict(dict)
|
||||
|
||||
def update_chat_data(self, chat_id, data):
|
||||
raise Exception
|
||||
|
||||
def get_user_data(self):
|
||||
return defaultdict(dict)
|
||||
|
||||
def update_user_data(self, user_id, data):
|
||||
raise Exception
|
||||
|
||||
def start1(b, u):
|
||||
pass
|
||||
|
||||
def error(b, u, e):
|
||||
increment.append("error")
|
||||
|
||||
# If updating a user_data or chat_data from a persistence object throws an error,
|
||||
# the error handler should catch it
|
||||
|
||||
update = Update(1, message=Message(1, User(1, "Test", False), None, Chat(1, "lala"),
|
||||
text='/start',
|
||||
entities=[MessageEntity(type=MessageEntity.BOT_COMMAND,
|
||||
offset=0,
|
||||
length=len('/start'))],
|
||||
bot=bot))
|
||||
my_persistence = OwnPersistence()
|
||||
dp = Dispatcher(bot, None, persistence=my_persistence)
|
||||
dp.add_handler(CommandHandler('start', start1))
|
||||
dp.add_error_handler(error)
|
||||
dp.process_update(update)
|
||||
assert increment == ["error", "error"]
|
||||
|
||||
def test_flow_stop_in_error_handler(self, dp, bot):
|
||||
passed = []
|
||||
err = TelegramError('Telegram error')
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#
|
||||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
import pytest
|
||||
|
||||
from telegram import Sticker
|
||||
from telegram import Update
|
||||
|
@ -31,6 +32,36 @@ class TestHelpers(object):
|
|||
|
||||
assert expected_str == helpers.escape_markdown(test_str)
|
||||
|
||||
def test_create_deep_linked_url(self):
|
||||
username = 'JamesTheMock'
|
||||
|
||||
payload = "hello"
|
||||
expected = "https://t.me/{}?start={}".format(username, payload)
|
||||
actual = helpers.create_deep_linked_url(username, payload)
|
||||
assert expected == actual
|
||||
|
||||
expected = "https://t.me/{}?startgroup={}".format(username, payload)
|
||||
actual = helpers.create_deep_linked_url(username, payload, group=True)
|
||||
assert expected == actual
|
||||
|
||||
payload = ""
|
||||
expected = "https://t.me/{}".format(username)
|
||||
assert expected == helpers.create_deep_linked_url(username)
|
||||
assert expected == helpers.create_deep_linked_url(username, payload)
|
||||
payload = None
|
||||
assert expected == helpers.create_deep_linked_url(username, payload)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
helpers.create_deep_linked_url(username, 'text with spaces')
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
helpers.create_deep_linked_url(username, '0' * 65)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
helpers.create_deep_linked_url(None, None)
|
||||
with pytest.raises(ValueError): # too short username (4 is minimum)
|
||||
helpers.create_deep_linked_url("abc", None)
|
||||
|
||||
def test_effective_message_type(self):
|
||||
|
||||
def build_test_message(**kwargs):
|
||||
|
@ -47,7 +78,7 @@ class TestHelpers(object):
|
|||
assert helpers.effective_message_type(test_message) == 'text'
|
||||
test_message.text = None
|
||||
|
||||
test_message = build_test_message(sticker=Sticker('sticker_id', 50, 50))
|
||||
test_message = build_test_message(sticker=Sticker('sticker_id', 50, 50, False))
|
||||
assert helpers.effective_message_type(test_message) == 'sticker'
|
||||
test_message.sticker = None
|
||||
|
||||
|
|
|
@ -57,7 +57,7 @@ def message(bot):
|
|||
[PhotoSize('game_photo_id', 30, 30), ])},
|
||||
{'photo': [PhotoSize('photo_id', 50, 50)],
|
||||
'caption': 'photo_file'},
|
||||
{'sticker': Sticker('sticker_id', 50, 50)},
|
||||
{'sticker': Sticker('sticker_id', 50, 50, True)},
|
||||
{'video': Video('video_id', 12, 12, 12),
|
||||
'caption': 'video_file'},
|
||||
{'voice': Voice('voice_id', 5)},
|
||||
|
|
|
@ -16,9 +16,8 @@
|
|||
#
|
||||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
import os
|
||||
import inspect
|
||||
import sys
|
||||
from platform import python_implementation
|
||||
|
||||
import certifi
|
||||
import pytest
|
||||
|
@ -149,7 +148,7 @@ for thing in soup.select('h4 > a.anchor'):
|
|||
|
||||
|
||||
@pytest.mark.parametrize(('method', 'data'), argvalues=argvalues, ids=names)
|
||||
@pytest.mark.skipif(not sys.version_info >= (3, 7) or python_implementation() != 'CPython',
|
||||
reason='follow_wrapped (inspect.signature) is not supported on this platform')
|
||||
@pytest.mark.skipif(os.getenv('TEST_OFFICIAL') != 'true',
|
||||
reason='test_official is not enabled')
|
||||
def test_official(method, data):
|
||||
method(data)
|
||||
|
|
|
@ -176,10 +176,10 @@ class TestBasePersistence(object):
|
|||
with caplog.at_level(logging.ERROR):
|
||||
dp.process_update(u)
|
||||
rec = caplog.records[-1]
|
||||
assert rec.msg == 'Saving user data raised an error'
|
||||
assert rec.msg == 'No error handlers are registered, logging exception.'
|
||||
assert rec.levelname == 'ERROR'
|
||||
rec = caplog.records[-2]
|
||||
assert rec.msg == 'Saving chat data raised an error'
|
||||
assert rec.msg == 'No error handlers are registered, logging exception.'
|
||||
assert rec.levelname == 'ERROR'
|
||||
m.from_user = user2
|
||||
m.chat = chat1
|
||||
|
@ -224,7 +224,7 @@ def pickle_persistence():
|
|||
return PicklePersistence(filename='pickletest',
|
||||
store_user_data=True,
|
||||
store_chat_data=True,
|
||||
singe_file=False,
|
||||
single_file=False,
|
||||
on_flush=False)
|
||||
|
||||
|
||||
|
@ -233,7 +233,7 @@ def pickle_persistence_only_chat():
|
|||
return PicklePersistence(filename='pickletest',
|
||||
store_user_data=False,
|
||||
store_chat_data=True,
|
||||
singe_file=False,
|
||||
single_file=False,
|
||||
on_flush=False)
|
||||
|
||||
|
||||
|
@ -242,7 +242,7 @@ def pickle_persistence_only_user():
|
|||
return PicklePersistence(filename='pickletest',
|
||||
store_user_data=True,
|
||||
store_chat_data=False,
|
||||
singe_file=False,
|
||||
single_file=False,
|
||||
on_flush=False)
|
||||
|
||||
|
||||
|
@ -552,7 +552,7 @@ class TestPickelPersistence(object):
|
|||
pickle_persistence_2 = PicklePersistence(filename='pickletest',
|
||||
store_user_data=True,
|
||||
store_chat_data=True,
|
||||
singe_file=False,
|
||||
single_file=False,
|
||||
on_flush=False)
|
||||
u = Updater(bot=bot, persistence=pickle_persistence_2)
|
||||
dp = u.dispatcher
|
||||
|
@ -572,7 +572,7 @@ class TestPickelPersistence(object):
|
|||
pickle_persistence_2 = PicklePersistence(filename='pickletest',
|
||||
store_user_data=True,
|
||||
store_chat_data=True,
|
||||
singe_file=False,
|
||||
single_file=False,
|
||||
on_flush=False)
|
||||
assert pickle_persistence_2.get_user_data()[4242424242]['my_test'] == 'Working!'
|
||||
assert pickle_persistence_2.get_chat_data()[-4242424242]['my_test2'] == 'Working2!'
|
||||
|
@ -592,7 +592,7 @@ class TestPickelPersistence(object):
|
|||
pickle_persistence_2 = PicklePersistence(filename='pickletest',
|
||||
store_user_data=False,
|
||||
store_chat_data=True,
|
||||
singe_file=False,
|
||||
single_file=False,
|
||||
on_flush=False)
|
||||
assert pickle_persistence_2.get_user_data() == {}
|
||||
assert pickle_persistence_2.get_chat_data()[-4242424242]['my_test2'] == 'Working2!'
|
||||
|
@ -611,7 +611,7 @@ class TestPickelPersistence(object):
|
|||
pickle_persistence_2 = PicklePersistence(filename='pickletest',
|
||||
store_user_data=True,
|
||||
store_chat_data=False,
|
||||
singe_file=False,
|
||||
single_file=False,
|
||||
on_flush=False)
|
||||
assert pickle_persistence_2.get_user_data()[4242424242]['my_test'] == 'Working!'
|
||||
assert pickle_persistence_2.get_chat_data()[-4242424242] == {}
|
||||
|
|
|
@ -360,7 +360,7 @@ class TestPhoto(object):
|
|||
b = PhotoSize(photo.file_id, self.width, self.height)
|
||||
c = PhotoSize(photo.file_id, 0, 0)
|
||||
d = PhotoSize('', self.width, self.height)
|
||||
e = Sticker(photo.file_id, self.width, self.height)
|
||||
e = Sticker(photo.file_id, self.width, self.height, False)
|
||||
|
||||
assert a == b
|
||||
assert hash(a) == hash(b)
|
||||
|
|
|
@ -49,6 +49,7 @@ class TestSticker(object):
|
|||
emoji = '💪'
|
||||
width = 510
|
||||
height = 512
|
||||
is_animated = False
|
||||
file_size = 39518
|
||||
thumb_width = 319
|
||||
thumb_height = 320
|
||||
|
@ -66,6 +67,7 @@ class TestSticker(object):
|
|||
def test_expected_values(self, sticker):
|
||||
assert sticker.width == self.width
|
||||
assert sticker.height == self.height
|
||||
assert sticker.is_animated == self.is_animated
|
||||
assert sticker.file_size == self.file_size
|
||||
assert sticker.thumb.width == self.thumb_width
|
||||
assert sticker.thumb.height == self.thumb_height
|
||||
|
@ -81,6 +83,7 @@ class TestSticker(object):
|
|||
assert message.sticker.file_id != ''
|
||||
assert message.sticker.width == sticker.width
|
||||
assert message.sticker.height == sticker.height
|
||||
assert message.sticker.is_animated == sticker.is_animated
|
||||
assert message.sticker.file_size == sticker.file_size
|
||||
|
||||
assert isinstance(message.sticker.thumb, PhotoSize)
|
||||
|
@ -132,6 +135,7 @@ class TestSticker(object):
|
|||
assert message.sticker.file_id != ''
|
||||
assert message.sticker.width == sticker.width
|
||||
assert message.sticker.height == sticker.height
|
||||
assert message.sticker.is_animated == sticker.is_animated
|
||||
assert message.sticker.file_size == sticker.file_size
|
||||
|
||||
assert isinstance(message.sticker.thumb, PhotoSize)
|
||||
|
@ -146,6 +150,7 @@ class TestSticker(object):
|
|||
'file_id': 'not a file id',
|
||||
'width': self.width,
|
||||
'height': self.height,
|
||||
'is_animated': self.is_animated,
|
||||
'thumb': sticker.thumb.to_dict(),
|
||||
'emoji': self.emoji,
|
||||
'file_size': self.file_size
|
||||
|
@ -155,6 +160,7 @@ class TestSticker(object):
|
|||
assert json_sticker.file_id == 'not a file id'
|
||||
assert json_sticker.width == self.width
|
||||
assert json_sticker.height == self.height
|
||||
assert json_sticker.is_animated == self.is_animated
|
||||
assert json_sticker.emoji == self.emoji
|
||||
assert json_sticker.file_size == self.file_size
|
||||
assert json_sticker.thumb == sticker.thumb
|
||||
|
@ -174,6 +180,7 @@ class TestSticker(object):
|
|||
assert sticker_dict['file_id'] == sticker.file_id
|
||||
assert sticker_dict['width'] == sticker.width
|
||||
assert sticker_dict['height'] == sticker.height
|
||||
assert sticker_dict['is_animated'] == sticker.is_animated
|
||||
assert sticker_dict['file_size'] == sticker.file_size
|
||||
assert sticker_dict['thumb'] == sticker.thumb.to_dict()
|
||||
|
||||
|
@ -194,11 +201,11 @@ class TestSticker(object):
|
|||
bot.send_sticker(chat_id)
|
||||
|
||||
def test_equality(self, sticker):
|
||||
a = Sticker(sticker.file_id, self.width, self.height)
|
||||
b = Sticker(sticker.file_id, self.width, self.height)
|
||||
c = Sticker(sticker.file_id, 0, 0)
|
||||
d = Sticker('', self.width, self.height)
|
||||
e = PhotoSize(sticker.file_id, self.width, self.height)
|
||||
a = Sticker(sticker.file_id, self.width, self.height, self.is_animated)
|
||||
b = Sticker(sticker.file_id, self.width, self.height, self.is_animated)
|
||||
c = Sticker(sticker.file_id, 0, 0, False)
|
||||
d = Sticker('', self.width, self.height, self.is_animated)
|
||||
e = PhotoSize(sticker.file_id, self.width, self.height, self.is_animated)
|
||||
|
||||
assert a == b
|
||||
assert hash(a) == hash(b)
|
||||
|
@ -224,8 +231,9 @@ def sticker_set(bot):
|
|||
|
||||
class TestStickerSet(object):
|
||||
title = 'Test stickers'
|
||||
is_animated = True
|
||||
contains_masks = False
|
||||
stickers = [Sticker('file_id', 512, 512)]
|
||||
stickers = [Sticker('file_id', 512, 512, True)]
|
||||
name = 'NOTAREALNAME'
|
||||
|
||||
def test_de_json(self, bot):
|
||||
|
@ -233,6 +241,7 @@ class TestStickerSet(object):
|
|||
json_dict = {
|
||||
'name': name,
|
||||
'title': self.title,
|
||||
'is_animated': self.is_animated,
|
||||
'contains_masks': self.contains_masks,
|
||||
'stickers': [x.to_dict() for x in self.stickers]
|
||||
}
|
||||
|
@ -240,6 +249,7 @@ class TestStickerSet(object):
|
|||
|
||||
assert sticker_set.name == name
|
||||
assert sticker_set.title == self.title
|
||||
assert sticker_set.is_animated == self.is_animated
|
||||
assert sticker_set.contains_masks == self.contains_masks
|
||||
assert sticker_set.stickers == self.stickers
|
||||
|
||||
|
@ -258,6 +268,7 @@ class TestStickerSet(object):
|
|||
assert isinstance(sticker_set_dict, dict)
|
||||
assert sticker_set_dict['name'] == sticker_set.name
|
||||
assert sticker_set_dict['title'] == sticker_set.title
|
||||
assert sticker_set_dict['is_animated'] == sticker_set.is_animated
|
||||
assert sticker_set_dict['contains_masks'] == sticker_set.contains_masks
|
||||
assert sticker_set_dict['stickers'][0] == sticker_set.stickers[0].to_dict()
|
||||
|
||||
|
@ -282,10 +293,10 @@ class TestStickerSet(object):
|
|||
assert sticker.get_file()
|
||||
|
||||
def test_equality(self):
|
||||
a = StickerSet(self.name, self.title, self.contains_masks, self.stickers)
|
||||
b = StickerSet(self.name, self.title, self.contains_masks, self.stickers)
|
||||
c = StickerSet(self.name, None, None, None)
|
||||
d = StickerSet('blah', self.title, self.contains_masks, self.stickers)
|
||||
a = StickerSet(self.name, self.title, self.is_animated, self.contains_masks, self.stickers)
|
||||
b = StickerSet(self.name, self.title, self.is_animated, self.contains_masks, self.stickers)
|
||||
c = StickerSet(self.name, None, None, None, None)
|
||||
d = StickerSet('blah', self.title, self.is_animated, self.contains_masks, self.stickers)
|
||||
e = Audio(self.name, 0, None, None)
|
||||
|
||||
assert a == b
|
||||
|
|
Loading…
Reference in a new issue