mirror of
https://github.com/python-telegram-bot/python-telegram-bot.git
synced 2026-06-19 15:45:13 +00:00
Compare commits
236 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1902c0ac36 | |||
| 544a3fbf48 | |||
| 36d49ea9cd | |||
| 25506f131d | |||
| 70aba136e4 | |||
| 40995b19fe | |||
| 32da6d6fce | |||
| f31787a8ef | |||
| b43a599e53 | |||
| 7a3fd83570 | |||
| 9ada2a7cca | |||
| be54cf4ece | |||
| 0c9915243d | |||
| 9930725e2a | |||
| ffd675daec | |||
| 6a831f926b | |||
| 6903d58142 | |||
| 07b6ee69d2 | |||
| c8a3c31dcc | |||
| 91e0271e4c | |||
| 9ddb361f76 | |||
| 89c522d883 | |||
| 2effff8254 | |||
| 2788191657 | |||
| aec6d3bada | |||
| 80b34811ab | |||
| 2d7a974b8f | |||
| ef703d19e9 | |||
| 77a8c64f6c | |||
| 786762bb73 | |||
| e0dbb99b08 | |||
| 73b0e29a30 | |||
| d27d1ea4d5 | |||
| ca04daf782 | |||
| ae9ce60b55 | |||
| 1cd3a0a156 | |||
| 58b9882021 | |||
| df6d5f0840 | |||
| 425716f966 | |||
| 8d9bb26cca | |||
| d1438a9b23 | |||
| 27b03edc59 | |||
| ac449deb5d | |||
| 3b9187ed5a | |||
| 9831458e22 | |||
| a0cd6e8fef | |||
| 8e7c0d6976 | |||
| 92b9370c23 | |||
| 237e73bfb4 | |||
| ff3fd34f08 | |||
| 83791d34e7 | |||
| 02cd7b642f | |||
| 165a24e13d | |||
| 88440079e3 | |||
| 9be4c7563b | |||
| b554f1a85d | |||
| 3b4559dd95 | |||
| 9ae48fecfe | |||
| 6af6648509 | |||
| 264b2c9c72 | |||
| 8efb05290a | |||
| 83a8874bb5 | |||
| bf68942c91 | |||
| 5fd7606084 | |||
| 103b115486 | |||
| b07e42ef33 | |||
| 3842846b2d | |||
| 7daddfb54d | |||
| 2d4d48b89d | |||
| 2381724b7c | |||
| 19a4f9e53a | |||
| 3930072659 | |||
| 5555582b72 | |||
| e67b995e64 | |||
| 0d419ed6b4 | |||
| 97adcdf538 | |||
| 2989108e95 | |||
| 897a20d758 | |||
| ed3a9b64e2 | |||
| 49c0c9e4d1 | |||
| bb34c79909 | |||
| a0720b9ac6 | |||
| faa93fbf75 | |||
| da452df07d | |||
| 3304cc5c90 | |||
| 9105d83d37 | |||
| b6b42b2043 | |||
| f857e1c23b | |||
| fc5844c13d | |||
| dea24bcb7c | |||
| 2789fd2bff | |||
| d6f8077a50 | |||
| 0189442525 | |||
| fd0325fbe5 | |||
| ff4bb15fef | |||
| 9288e4f2e4 | |||
| e60318166e | |||
| 15268acb27 | |||
| 927502e588 | |||
| 0af5cc2db8 | |||
| 6005861f46 | |||
| 8406889179 | |||
| a4e78f6183 | |||
| 8c6cb44a85 | |||
| ac7cc7fe5e | |||
| a42b68933c | |||
| c2d91c752f | |||
| 5057825586 | |||
| 613175b2c4 | |||
| 1330696259 | |||
| 5898e1fe7a | |||
| b6dec118c1 | |||
| 186fd1b418 | |||
| 284786fdb8 | |||
| c7c56ad24e | |||
| ae17ce977e | |||
| 7e231183c4 | |||
| 8427346a0d | |||
| 632b989d90 | |||
| 76567ba635 | |||
| 2bd3f2a65a | |||
| 26a5006bf1 | |||
| 110e2df443 | |||
| 57546795c5 | |||
| 314f87ec44 | |||
| 4bbcd51ef5 | |||
| 38a33581b1 | |||
| fe821c08e6 | |||
| 0a9f4bfbdd | |||
| c4364c7166 | |||
| d63e710784 | |||
| f379f54d5a | |||
| bdf0cb91f3 | |||
| 3101ea8432 | |||
| beb8ba3db0 | |||
| f0b1aeb6fd | |||
| d65558888e | |||
| 61a66a32c8 | |||
| 392d4e1a9c | |||
| 9cb34af65a | |||
| e9cb6675ca | |||
| 982f6707e1 | |||
| d55d981e22 | |||
| f20953f7a9 | |||
| e18220be10 | |||
| 90729c21d7 | |||
| 55e3ecf9f8 | |||
| 8d2c7af1f3 | |||
| e86ae25a62 | |||
| 2d3357bfeb | |||
| b6f4783fd3 | |||
| f94ea9acbb | |||
| 157652cfdf | |||
| 104d0127aa | |||
| e0b22e60b4 | |||
| 16613d7ce0 | |||
| eac7f02211 | |||
| 28ded6718e | |||
| 13a641b3d7 | |||
| 27cccc7734 | |||
| f7ec7a7c4c | |||
| 8d6970ab02 | |||
| 1dc67dcbda | |||
| 14f712b3c4 | |||
| 0fb0fbb93f | |||
| 72ecc696cb | |||
| a447760411 | |||
| 6da529c46b | |||
| bd1b2fb6c1 | |||
| f9a8cd924c | |||
| 7b3b278c7c | |||
| cf3635d408 | |||
| 84cfc6f7fa | |||
| bf06fa2c18 | |||
| 960c7a0c70 | |||
| bacabbe767 | |||
| d8dcdeea75 | |||
| 818475bd93 | |||
| f97ac90af7 | |||
| 90eeb40ae8 | |||
| 6d9d11b8bd | |||
| f6b663f175 | |||
| 43bfebb150 | |||
| 743e2fce07 | |||
| 4c2a3d07ce | |||
| 423794f473 | |||
| a397e6c3c6 | |||
| 7cde6ca268 | |||
| 408062dd43 | |||
| d96d233dc5 | |||
| 3eb2cef600 | |||
| 0b87f4b274 | |||
| 0df526d390 | |||
| cb9af36937 | |||
| fbb7e0e645 | |||
| d9d65cc2ac | |||
| 62f514f068 | |||
| 08bbeca8ec | |||
| 38b9f4b9bc | |||
| 3d59b2f581 | |||
| e3c8466e41 | |||
| 1d92f52c6a | |||
| 33280a7fe0 | |||
| 4b5ba15d31 | |||
| d2466f1e6e | |||
| 883c6b5901 | |||
| 1e7f4fae6f | |||
| dae5ab47a0 | |||
| a9d9b1d750 | |||
| 90496f70a5 | |||
| 940b42e048 | |||
| a582515766 | |||
| 3d42df3366 | |||
| 2c67a9833b | |||
| 5e8a961669 | |||
| a5ba64becb | |||
| 2a3169a22f | |||
| 894d8281ab | |||
| 2fdf48023b | |||
| 4e717a172b | |||
| 10c9ec2313 | |||
| 096a7c3593 | |||
| e9d9f01bd4 | |||
| 8b4b22cc89 | |||
| b294c92bad | |||
| 264de2b7c1 | |||
| ac64027580 | |||
| 34bdbc632a | |||
| bbcff96804 | |||
| 93449443b2 | |||
| 8cdb20a85a | |||
| 6fddb49af5 | |||
| b0aef0c718 | |||
| 88eccc6608 | |||
| 3d8771bbdf | |||
| 7152b5aaf9 |
+27
-12
@@ -25,7 +25,7 @@ Setting things up
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ sudo pip install -r requirements.txt -r requirements-dev.txt
|
||||
$ pip install -r requirements.txt -r requirements-dev.txt
|
||||
|
||||
|
||||
5. Install pre-commit hooks:
|
||||
@@ -41,7 +41,9 @@ If you already know what you'd like to work on, you can skip this section.
|
||||
|
||||
If you have an idea for something to do, first check if it's already been filed on the `issue tracker`_. If so, add a comment to the issue saying you'd like to work on it, and we'll help you get started! Otherwise, please file a new issue and assign yourself to it.
|
||||
|
||||
Another great way to start contributing is by writing tests. Tests are really important because they help prevent developers from accidentally breaking existing code, allowing them to build cool things faster. If you're interested in helping out, let the development team know by posting to the `developers' mailing list`_, and we'll help you get started.
|
||||
Another great way to start contributing is by writing tests. Tests are really important because they help prevent developers from accidentally breaking existing code, allowing them to build cool things faster. If you're interested in helping out, let the development team know by posting to the `Telegram group`_ (use `@admins` to mention the maintainers), and we'll help you get started.
|
||||
|
||||
That being said, we want to mention that we are very hesitant about adding new requirements to our projects. If you intend to do this, please state this in an issue and get a verification from one of the maintainers.
|
||||
|
||||
Instructions for making a code change
|
||||
#####################################
|
||||
@@ -66,7 +68,9 @@ 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.
|
||||
|
||||
|
||||
- Provide static typing with signature annotations. The documentation of `MyPy`_ will be a good start, the cheat sheet is `here`_. We also have some custom type aliases in ``telegram.utils.helpers.typing``.
|
||||
|
||||
- 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
|
||||
@@ -87,12 +91,16 @@ Here's how to make a one-off code change.
|
||||
|
||||
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.
|
||||
- Add ``.. versionadded:: version``, ``.. versionchanged:: version`` or ``.. deprecated:: version`` to the associated documentation of your changes, depending on what kind of change you made. This only applies if the change you made is visible to an end user. The directives should be added to class/method descriptions if their general behaviour changed and to the description of all arguments & attributes that changed.
|
||||
|
||||
- For consistency, please conform to `Google Python Style Guide`_ and `Google Python Style Docstrings`_.
|
||||
|
||||
- The following exceptions to the above (Google's) style guides applies:
|
||||
|
||||
- Documenting types of global variables and complex types of class members can be done using the Sphinx docstring convention.
|
||||
|
||||
- In addition, PTB uses the `Black`_ coder formatting. Plugins for Black exist for some `popular editors`_. You can use those instead of manually formatting everything.
|
||||
|
||||
- Please ensure that the code you write is well-tested.
|
||||
|
||||
- Don’t break backward compatibility.
|
||||
@@ -111,6 +119,14 @@ Here's how to make a one-off code change.
|
||||
|
||||
$ pytest -v
|
||||
|
||||
To run ``test_official`` (particularly useful if you made API changes), run
|
||||
|
||||
.. code-block::
|
||||
|
||||
$ export TEST_OFFICIAL=true
|
||||
|
||||
prior to running the tests.
|
||||
|
||||
- To actually make the commit (this will trigger tests for yapf, lint and pep8 automatically):
|
||||
|
||||
.. code-block:: bash
|
||||
@@ -177,11 +193,6 @@ Here's how to make a one-off code change.
|
||||
Style commandments
|
||||
------------------
|
||||
|
||||
Specific commandments
|
||||
#####################
|
||||
|
||||
- Avoid using "double quotes" where you can reasonably use 'single quotes'.
|
||||
|
||||
Assert comparison order
|
||||
#######################
|
||||
|
||||
@@ -235,9 +246,13 @@ break the API classes. For example:
|
||||
|
||||
.. _`Code of Conduct`: https://www.python.org/psf/codeofconduct/
|
||||
.. _`issue tracker`: https://github.com/python-telegram-bot/python-telegram-bot/issues
|
||||
.. _`developers' mailing list`: mailto:devs@python-telegram-bot.org
|
||||
.. _`Telegram group`: https://telegram.me/pythontelegrambotgroup
|
||||
.. _`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
|
||||
.. _`Google Python Style Guide`: http://google.github.io/styleguide/pyguide.html
|
||||
.. _`Google Python Style Docstrings`: https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html
|
||||
.. _AUTHORS.rst: ../AUTHORS.rst
|
||||
.. _`MyPy`: https://mypy.readthedocs.io/en/stable/index.html
|
||||
.. _`here`: https://mypy.readthedocs.io/en/stable/cheat_sheet_py3.html
|
||||
.. _`Black`: https://black.readthedocs.io/en/stable/index.html
|
||||
.. _`popular editors`: https://black.readthedocs.io/en/stable/editor_integration.html
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: "[BUG]"
|
||||
labels: 'bug :bug:'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
Thanks for reporting issues of python-telegram-bot!
|
||||
|
||||
Use this template to notify us if you found a bug, or if you want to request a new feature.
|
||||
If you're looking for help with programming your bot using our library, feel free to ask your
|
||||
questions in out telegram group at: https://t.me/pythontelegrambotgroup
|
||||
Use this template to notify us if you found a bug.
|
||||
|
||||
To make it easier for us to help you please enter detailed information below.
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Telegram Group
|
||||
url: https://telegram.me/pythontelegrambotgroup
|
||||
about: Questions asked on the group usually get answered faster.
|
||||
- name: IRC Channel
|
||||
url: https://webchat.freenode.net/?channels=##python-telegram-bot
|
||||
about: In case you are unable to join our group due to Telegram restrictions, you can use our IRC channel
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: "[FEATURE]"
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
#### Is your feature request related to a problem? Please describe.
|
||||
A clear and concise description of what the problem is.
|
||||
Ex. *I want to do X, but there is no way to do it.*
|
||||
|
||||
#### Describe the solution you'd like
|
||||
A clear and concise description of what you want to happen.
|
||||
Ex. *I think it would be nice if you would add feature Y so it will make it easier.*
|
||||
|
||||
#### Describe alternatives you've considered
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
Ex. *I considered Z, but that didn't work because...*
|
||||
|
||||
#### Additional context
|
||||
Add any other context or screenshots about the feature request here.
|
||||
Ex. *Here's a photo of my cat!*
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
name: Question
|
||||
about: Get help with errors or general questions
|
||||
title: "[QUESTION]"
|
||||
labels: question
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
Hey there, you have a question? We are happy to answer. Please make sure no similar question was opened already.
|
||||
|
||||
To make it easier for us to help you, please read this article https://git.io/JURJO and try to follow the template below as closely as possible.
|
||||
|
||||
Please mind that there is also a users' Telegram group at https://t.me/pythontelegrambotgroup for questions about the library. Questions asked there might be answered quicker than here. In case you are unable to join our group due to Telegram restrictions, you can use our IRC channel at https://webchat.freenode.net/?channels=##python-telegram-bot to participate in the group.
|
||||
-->
|
||||
|
||||
### Issue I am facing
|
||||
Please describe the issue here in as much detail as possible
|
||||
|
||||
### Traceback to the issue
|
||||
```
|
||||
put it here
|
||||
```
|
||||
|
||||
### Related part of your code
|
||||
```python
|
||||
put it here
|
||||
```
|
||||
@@ -0,0 +1,30 @@
|
||||
<!--
|
||||
Hey! You're PRing? Cool! Please have a look at the below checklist. It's here to help both you and the maintainers to remember some aspects. Make sure to check out our contribution guide (https://github.com/python-telegram-bot/python-telegram-bot/blob/master/.github/CONTRIBUTING.rst).
|
||||
-->
|
||||
|
||||
### Checklist for PRs
|
||||
|
||||
- [ ] Added `.. versionadded:: version`, `.. versionchanged:: version` or `.. deprecated:: version` to the docstrings for user facing changes (for methods/class descriptions, arguments and attributes)
|
||||
- [ ] Created new or adapted existing unit tests
|
||||
- [ ] Added myself alphabetically to `AUTHORS.rst` (optional)
|
||||
|
||||
|
||||
### If the PR contains API changes (otherwise, you can delete this passage)
|
||||
|
||||
* New classes:
|
||||
- [ ] Added `self._id_attrs` and corresponding documentation
|
||||
- [ ] `__init__` accepts `**_kwargs`
|
||||
|
||||
* Added new shortcuts:
|
||||
- [ ] In `Chat` & `User` for all methods that accept `chat/user_id`
|
||||
- [ ] In `Message` for all methods that accept `chat_id` and `message_id`
|
||||
- [ ] For new `Message` shortcuts: Added `quote` argument if methods accepts `reply_to_message_id`
|
||||
- [ ] In `CallbackQuery` for all methods that accept either `chat_id` and `message_id` or `inline_message_id`
|
||||
|
||||
* If relevant:
|
||||
|
||||
- [ ] Added new constants at `telegram.constants` and shortcuts to them as class variables
|
||||
- [ ] Added new handlers for new update types
|
||||
- [ ] Added new filters for new message (sub)types
|
||||
- [ ] Added or updated documentation for the changed class(es) and/or method(s)
|
||||
- [ ] Updated the Bot API version number in all places in `README.rst` and `README_RAW.rst`, including the badge
|
||||
@@ -0,0 +1,14 @@
|
||||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 3
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 2
|
||||
# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
|
||||
onlyLabels: question
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: stale
|
||||
# Comment to post when marking as stale. Set to `false` to disable
|
||||
markComment: false
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: >
|
||||
This issue has been automatically closed due to inactivity. Feel free to comment in order to reopen
|
||||
or ask again in our Telegram support group at https://t.me/pythontelegrambotgroup.
|
||||
@@ -0,0 +1,14 @@
|
||||
name: Warning maintainers
|
||||
on:
|
||||
pull_request:
|
||||
paths: examples/**
|
||||
jobs:
|
||||
job:
|
||||
runs-on: ubuntu-latest
|
||||
name: about example change
|
||||
steps:
|
||||
- name: running the check
|
||||
uses: Poolitzer/notifier-action@master
|
||||
with:
|
||||
notify-message: Hey there. Relax, I am just a little warning for the maintainers to release directly after merging your PR, otherwise we have broken examples and people might get confused :)
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -0,0 +1,18 @@
|
||||
name: 'Lock Closed Threads'
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '8 4 * * *'
|
||||
- cron: '42 17 * * *'
|
||||
|
||||
jobs:
|
||||
lock:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v2.0.1
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
issue-lock-inactive-days: '1'
|
||||
issue-lock-reason: ''
|
||||
pr-lock-inactive-days: '1'
|
||||
pr-lock-reason: ''
|
||||
@@ -0,0 +1,16 @@
|
||||
name: Warning maintainers
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- README.rst
|
||||
- README_RAW.rst
|
||||
jobs:
|
||||
job:
|
||||
runs-on: ubuntu-latest
|
||||
name: about readme change
|
||||
steps:
|
||||
- name: running the check
|
||||
uses: Poolitzer/notifier-action@master
|
||||
with:
|
||||
notify-message: Hey! Looks like you edited README.rst or README_RAW.rst. I'm just a friendly reminder to apply relevant changes to both of those files :)
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -0,0 +1,115 @@
|
||||
name: GitHub Actions
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
pytest:
|
||||
name: pytest
|
||||
runs-on: ${{matrix.os}}
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [3.6, 3.7, 3.8, 3.9]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
python-version: 3.7
|
||||
test-build: True
|
||||
- os: windows-latest
|
||||
python-version: 3.7
|
||||
test-build: True
|
||||
fail-fast: False
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Initialize vendored libs
|
||||
run:
|
||||
git submodule update --init --recursive
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -W ignore -m pip install --upgrade pip
|
||||
python -W ignore -m pip install -U codecov pytest-cov
|
||||
python -W ignore -m pip install -r requirements.txt
|
||||
python -W ignore -m pip install -r requirements-dev.txt
|
||||
|
||||
- name: Test with pytest
|
||||
run: |
|
||||
pytest -v -m nocoverage
|
||||
nocov_exit=$?
|
||||
pytest -v -m "not nocoverage" --cov
|
||||
cov_exit=$?
|
||||
global_exit=$(( nocov_exit > cov_exit ? nocov_exit : cov_exit ))
|
||||
exit ${global_exit}
|
||||
env:
|
||||
JOB_INDEX: ${{ strategy.job-index }}
|
||||
BOTS: W3sidG9rZW4iOiAiNjk2MTg4NzMyOkFBR1Z3RUtmSEhsTmpzY3hFRE5LQXdraEdzdFpfa28xbUMwIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6WldGaU1UUmxNbVF5TnpNeSIsICJib3RfbmFtZSI6ICJQVEIgdGVzdHMgb24gVHJhdmlzIHVzaW5nIENQeXRob24gMi43IiwgInN1cGVyX2dyb3VwX2lkIjogIi0xMDAxMzkwOTgzOTk3IiwgImJvdF91c2VybmFtZSI6ICJAcHRiX3RyYXZpc19jcHl0aG9uXzI3X2JvdCJ9LCB7InRva2VuIjogIjY3MTQ2ODg4NjpBQUdQR2ZjaVJJQlVORmU4MjR1SVZkcTdKZTNfWW5BVE5HdyIsICJwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY4NTA2MzpURVNUOlpHWXdPVGxrTXpNeE4yWTIiLCAiYm90X25hbWUiOiAiUFRCIHRlc3RzIG9uIFRyYXZpcyB1c2luZyBDUHl0aG9uIDMuNCIsICJzdXBlcl9ncm91cF9pZCI6ICItMTAwMTQ0NjAyMjUyMiIsICJib3RfdXNlcm5hbWUiOiAiQHB0Yl90cmF2aXNfY3B5dGhvbl8zNF9ib3QifSwgeyJ0b2tlbiI6ICI2MjkzMjY1Mzg6QUFGUnJaSnJCN29CM211ekdzR0pYVXZHRTVDUXpNNUNVNG8iLCAicGF5bWVudF9wcm92aWRlcl90b2tlbiI6ICIyODQ2ODUwNjM6VEVTVDpNbU01WVdKaFl6a3hNMlUxIiwgImJvdF9uYW1lIjogIlBUQiB0ZXN0cyBvbiBUcmF2aXMgdXNpbmcgQ1B5dGhvbiAzLjUiLCAic3VwZXJfZ3JvdXBfaWQiOiAiLTEwMDE0OTY5MTc3NTAiLCAiYm90X3VzZXJuYW1lIjogIkBwdGJfdHJhdmlzX2NweXRob25fMzVfYm90In0sIHsidG9rZW4iOiAiNjQwMjA4OTQzOkFBRmhCalFwOXFtM1JUeFN6VXBZekJRakNsZS1Kano1aGNrIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6WXpoa1pUZzFOamMxWXpWbCIsICJib3RfbmFtZSI6ICJQVEIgdGVzdHMgb24gVHJhdmlzIHVzaW5nIENQeXRob24gMy42IiwgInN1cGVyX2dyb3VwX2lkIjogIi0xMDAxMzMzODcxNDYxIiwgImJvdF91c2VybmFtZSI6ICJAcHRiX3RyYXZpc19jcHl0aG9uXzM2X2JvdCJ9LCB7InRva2VuIjogIjY5NTEwNDA4ODpBQUhmenlsSU9qU0lJUy1lT25JMjB5MkUyMEhvZEhzZnotMCIsICJwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY4NTA2MzpURVNUOk9HUTFNRGd3WmpJd1pqRmwiLCAiYm90X25hbWUiOiAiUFRCIHRlc3RzIG9uIFRyYXZpcyB1c2luZyBDUHl0aG9uIDMuNyIsICJzdXBlcl9ncm91cF9pZCI6ICItMTAwMTQ3ODI5MzcxNCIsICJib3RfdXNlcm5hbWUiOiAiQHB0Yl90cmF2aXNfY3B5dGhvbl8zN19ib3QifSwgeyJ0b2tlbiI6ICI2OTE0MjM1NTQ6QUFGOFdrakNaYm5IcVBfaTZHaFRZaXJGRWxackdhWU9oWDAiLCAicGF5bWVudF9wcm92aWRlcl90b2tlbiI6ICIyODQ2ODUwNjM6VEVTVDpZamM1TlRoaU1tUXlNV1ZoIiwgImJvdF9uYW1lIjogIlBUQiB0ZXN0cyBvbiBUcmF2aXMgdXNpbmcgUHlQeSAyLjciLCAic3VwZXJfZ3JvdXBfaWQiOiAiLTEwMDEzNjM5MzI1NzMiLCAiYm90X3VzZXJuYW1lIjogIkBwdGJfdHJhdmlzX3B5cHlfMjdfYm90In0sIHsidG9rZW4iOiAiNjg0MzM5OTg0OkFBRk1nRUVqcDAxcjVyQjAwN3lDZFZOc2c4QWxOc2FVLWNjIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6TVRBek1UWTNNR1V5TmpnMCIsICJib3RfbmFtZSI6ICJQVEIgdGVzdHMgb24gVHJhdmlzIHVzaW5nIFB5UHkgMy41IiwgInN1cGVyX2dyb3VwX2lkIjogIi0xMDAxNDA3ODM2NjA1IiwgImJvdF91c2VybmFtZSI6ICJAcHRiX3RyYXZpc19weXB5XzM1X2JvdCJ9LCB7InRva2VuIjogIjY5MDA5MTM0NzpBQUZMbVI1cEFCNVljcGVfbU9oN3pNNEpGQk9oMHozVDBUbyIsICJwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY4NTA2MzpURVNUOlpEaGxOekU1TURrd1lXSmkiLCAiYm90X25hbWUiOiAiUFRCIHRlc3RzIG9uIEFwcFZleW9yIHVzaW5nIENQeXRob24gMy40IiwgInN1cGVyX2dyb3VwX2lkIjogIi0xMDAxMjc5NjAwMDI2IiwgImJvdF91c2VybmFtZSI6ICJAcHRiX2FwcHZleW9yX2NweXRob25fMzRfYm90In0sIHsidG9rZW4iOiAiNjk0MzA4MDUyOkFBRUIyX3NvbkNrNTVMWTlCRzlBTy1IOGp4aVBTNTVvb0JBIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6WW1aaVlXWm1NakpoWkdNeSIsICJib3RfbmFtZSI6ICJQVEIgdGVzdHMgb24gQXBwVmV5b3IgdXNpbmcgQ1B5dGhvbiAyLjciLCAic3VwZXJfZ3JvdXBfaWQiOiAiLTEwMDEyOTMwNzkxNjUiLCAiYm90X3VzZXJuYW1lIjogIkBwdGJfYXBwdmV5b3JfY3B5dGhvbl8yN19ib3QifSwgeyJ0b2tlbiI6ICIxMDU1Mzk3NDcxOkFBRzE4bkJfUzJXQXd1SjNnN29oS0JWZ1hYY2VNbklPeVNjIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6TmpBd056QXpZalZpTkdOayIsICJuYW1lIjogIlBUQiB0ZXN0cyBbMF0iLCAic3VwZXJfZ3JvdXBfaWQiOiAiLTEwMDExODU1MDk2MzYiLCAidXNlcm5hbWUiOiAicHRiXzBfYm90In0sIHsidG9rZW4iOiAiMTA0NzMyNjc3MTpBQUY4bk90ODFGcFg4bGJidno4VWV3UVF2UmZUYkZmQnZ1SSIsICJwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY4NTA2MzpURVNUOllUVTFOVEk0WkdSallqbGkiLCAibmFtZSI6ICJQVEIgdGVzdHMgWzFdIiwgInN1cGVyX2dyb3VwX2lkIjogIi0xMDAxNDg0Nzk3NjEyIiwgInVzZXJuYW1lIjogInB0Yl8xX2JvdCJ9LCB7InRva2VuIjogIjk3MTk5Mjc0NTpBQUdPa09hVzBOSGpnSXY1LTlqUWJPajR2R3FkaFNGLVV1cyIsICJwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY4NTA2MzpURVNUOk5XWmtNV1ZoWWpsallqVTUiLCAibmFtZSI6ICJQVEIgdGVzdHMgWzJdIiwgInN1cGVyX2dyb3VwX2lkIjogIi0xMDAxNDAyMjU1MDcwIiwgInVzZXJuYW1lIjogInB0Yl8yX2JvdCJ9XQ==
|
||||
TEST_BUILD: ${{ matrix.test-build }}
|
||||
TEST_PRE_COMMIT: ${{ matrix.test-pre-commit }}
|
||||
shell: bash --noprofile --norc {0}
|
||||
|
||||
- name: Submit coverage
|
||||
uses: codecov/codecov-action@v1.0.13
|
||||
with:
|
||||
env_vars: OS,PYTHON
|
||||
name: ${{ matrix.os }}-${{ matrix.python-version }}
|
||||
fail_ci_if_error: true
|
||||
test_official:
|
||||
name: test-official
|
||||
runs-on: ${{matrix.os}}
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [3.7]
|
||||
os: [ubuntu-latest]
|
||||
fail-fast: False
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Initialize vendored libs
|
||||
run:
|
||||
git submodule update --init --recursive
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -W ignore -m pip install --upgrade pip
|
||||
python -W ignore -m pip install -r requirements.txt
|
||||
python -W ignore -m pip install -r requirements-dev.txt
|
||||
- name: Compare to official api
|
||||
run: |
|
||||
pytest -v tests/test_official.py
|
||||
exit $?
|
||||
env:
|
||||
TEST_OFFICIAL: "true"
|
||||
shell: bash --noprofile --norc {0}
|
||||
test_pre_commit:
|
||||
name: test-pre-commit
|
||||
runs-on: ${{matrix.os}}
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [3.7]
|
||||
os: [ubuntu-latest]
|
||||
fail-fast: False
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Initialize vendored libs
|
||||
run:
|
||||
git submodule update --init --recursive
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -W ignore -m pip install --upgrade pip
|
||||
python -W ignore -m pip install -r requirements.txt
|
||||
python -W ignore -m pip install -r requirements-dev.txt
|
||||
- name: Run pre-commit tests
|
||||
run: pre-commit run --all-files
|
||||
@@ -46,6 +46,7 @@ htmlcov/
|
||||
.coverage.*
|
||||
.cache
|
||||
.pytest_cache
|
||||
.mypy_cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*,cover
|
||||
@@ -83,3 +84,6 @@ telegram.jpg
|
||||
|
||||
# Exclude .exrc file for Vim
|
||||
.exrc
|
||||
|
||||
# virtual env
|
||||
venv*
|
||||
|
||||
+25
-11
@@ -1,20 +1,34 @@
|
||||
# Make sure that
|
||||
# * the revs specified here match requirements-dev.txt
|
||||
# * the makefile checks the same files as pre-commit
|
||||
repos:
|
||||
- repo: git://github.com/python-telegram-bot/mirrors-yapf
|
||||
sha: 5769e088ef6e0a0d1eb63bd6d0c1fe9f3606d6c8
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 20.8b1
|
||||
hooks:
|
||||
- id: yapf
|
||||
files: ^(telegram|tests)/.*\.py$
|
||||
- id: black
|
||||
args:
|
||||
- --diff
|
||||
- repo: git://github.com/pre-commit/pre-commit-hooks
|
||||
sha: 0b70e285e369bcb24b57b74929490ea7be9c4b19
|
||||
- --check
|
||||
- repo: https://gitlab.com/pycqa/flake8
|
||||
rev: 3.8.4
|
||||
hooks:
|
||||
- id: flake8
|
||||
- repo: git://github.com/pre-commit/mirrors-pylint
|
||||
sha: 9d8dcbc2b86c796275680f239c1e90dcd50bd398
|
||||
- repo: https://github.com/PyCQA/pylint
|
||||
rev: pylint-2.6.0
|
||||
hooks:
|
||||
- id: pylint
|
||||
files: ^telegram/.*\.py$
|
||||
files: ^(telegram|examples)/.*\.py$
|
||||
args:
|
||||
- --errors-only
|
||||
- --disable=import-error
|
||||
- --rcfile=setup.cfg
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: v0.790
|
||||
hooks:
|
||||
- id: mypy
|
||||
files: ^(telegram|examples)/.*\.py$
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v2.7.4
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
files: ^(telegram|examples|tests)/.*\.py$
|
||||
args:
|
||||
- --py36-plus
|
||||
|
||||
-55
@@ -1,55 +0,0 @@
|
||||
language: python
|
||||
matrix:
|
||||
include:
|
||||
- python: 2.7
|
||||
- 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
|
||||
|
||||
dist: trusty
|
||||
sudo: false
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- /^[vV]\d+$/
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.cache/pip
|
||||
- $HOME/.pre-commit
|
||||
before_cache:
|
||||
- rm -f $HOME/.cache/pip/log/debug.log
|
||||
- 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; 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; else true; fi
|
||||
|
||||
script:
|
||||
- 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
|
||||
- codecov -F Travis
|
||||
+19
-2
@@ -2,9 +2,12 @@ Credits
|
||||
=======
|
||||
|
||||
``python-telegram-bot`` was originally created by
|
||||
`Leandro Toledo <https://github.com/leandrotoledo>`_ and is now maintained by
|
||||
`Leandro Toledo <https://github.com/leandrotoledo>`_ and is now maintained by `Hinrich Mahler <https://github.com/Bibo-Joshi>`_.
|
||||
Emeritus maintainers include
|
||||
`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>`_, `Jasmin Bom <https://github.com/jsmnbom>`_ and `Hinrich Mahler <https://github.com/Bibo-Joshi>`_.
|
||||
`Noam Meltzer <https://github.com/tsnoam>`_, `Pieter Schutz <https://github.com/eldinnie>`_ and `Jasmin Bom <https://github.com/jsmnbom>`_.
|
||||
|
||||
The maintainers are actively supported by `Poolitzer <https://github.com/Poolitzer>`_ in terms of development and community liaison.
|
||||
|
||||
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:
|
||||
@@ -18,6 +21,7 @@ The following wonderful people contributed directly or indirectly to this projec
|
||||
- `Alateas <https://github.com/alateas>`_
|
||||
- `Ales Dokshanin <https://github.com/alesdokshanin>`_
|
||||
- `Ambro17 <https://github.com/Ambro17>`_
|
||||
- `Andrej Zhilenkov <https://github.com/Andrej730>`_
|
||||
- `Anton Tagunov <https://github.com/anton-tagunov>`_
|
||||
- `Avanatiker <https://github.com/Avanatiker>`_
|
||||
- `Balduro <https://github.com/Balduro>`_
|
||||
@@ -26,6 +30,8 @@ The following wonderful people contributed directly or indirectly to this projec
|
||||
- `d-qoi <https://github.com/d-qoi>`_
|
||||
- `daimajia <https://github.com/daimajia>`_
|
||||
- `Daniel Reed <https://github.com/nmlorg>`_
|
||||
- `D David Livingston <https://github.com/daviddl9>`_
|
||||
- `Eana Hufwe <https://github.com/blueset>`_
|
||||
- `Ehsan Online <https://github.com/ehsanonline>`_
|
||||
- `Eli Gao <https://github.com/eligao>`_
|
||||
- `Emilio Molinari <https://github.com/xates>`_
|
||||
@@ -33,8 +39,12 @@ The following wonderful people contributed directly or indirectly to this projec
|
||||
- `Eugene Lisitsky <https://github.com/lisitsky>`_
|
||||
- `Eugenio Panadero <https://github.com/azogue>`_
|
||||
- `Evan Haberecht <https://github.com/habereet>`_
|
||||
- `Evgeny Denisov <https://github.com/eIGato>`_
|
||||
- `evgfilim1 <https://github.com/evgfilim1>`_
|
||||
- `franciscod <https://github.com/franciscod>`_
|
||||
- `gamgi <https://github.com/gamgi>`_
|
||||
- `Gauthamram Ravichandran <https://github.com/GauthamramRavichandran>`_
|
||||
- `Harshil <https://github.com/harshil21>`_
|
||||
- `Hugo Damer <https://github.com/HakimusGIT>`_
|
||||
- `ihoru <https://github.com/ihoru>`_
|
||||
- `Jasmin Bom <https://github.com/jsmnbom>`_
|
||||
@@ -51,11 +61,14 @@ The following wonderful people contributed directly or indirectly to this projec
|
||||
- `Kjwon15 <https://github.com/kjwon15>`_
|
||||
- `Li-aung Yip <https://github.com/LiaungYip>`_
|
||||
- `Loo Zheng Yuan <https://github.com/loozhengyuan>`_
|
||||
- `LRezende <https://github.com/lrezende>`_
|
||||
- `macrojames <https://github.com/macrojames>`_
|
||||
- `Matheus Lemos <https://github.com/mlemosf>`_
|
||||
- `Michael Elovskikh <https://github.com/wronglink>`_
|
||||
- `Mischa Krüger <https://github.com/Makman2>`_
|
||||
- `naveenvhegde <https://github.com/naveenvhegde>`_
|
||||
- `neurrone <https://github.com/neurrone>`_
|
||||
- `NikitaPirate <https://github.com/NikitaPirate>`_
|
||||
- `njittam <https://github.com/njittam>`_
|
||||
- `Noam Meltzer <https://github.com/tsnoam>`_
|
||||
- `Oleg Shlyazhko <https://github.com/ollmer>`_
|
||||
@@ -66,7 +79,10 @@ The following wonderful people contributed directly or indirectly to this projec
|
||||
- `Paul Larsen <https://github.com/PaulSonOfLars>`_
|
||||
- `Pieter Schutz <https://github.com/eldinnie>`_
|
||||
- `Poolitzer <https://github.com/Poolitzer>`_
|
||||
- `Pranjalya Tiwari <https://github.com/Pranjalya>`_
|
||||
- `Rahiel Kasim <https://github.com/rahiel>`_
|
||||
- `Riko Naka <https://github.com/rikonaka>`_
|
||||
- `Rizlas <https://github.com/rizlas>`_
|
||||
- `Sahil Sharma <https://github.com/sahilsharma811>`_
|
||||
- `Sascha <https://github.com/saschalalala>`_
|
||||
- `Shelomentsev D <https://github.com/shelomentsevd>`_
|
||||
@@ -74,6 +90,7 @@ The following wonderful people contributed directly or indirectly to this projec
|
||||
- `sooyhwang <https://github.com/sooyhwang>`_
|
||||
- `syntx <https://github.com/syntx>`_
|
||||
- `thodnev <https://github.com/thodnev>`_
|
||||
- `Timur Kushukov <https://github.com/timqsh>`_
|
||||
- `Trainer Jono <https://github.com/Tr-Jono>`_
|
||||
- `Valentijn <https://github.com/Faalentijn>`_
|
||||
- `voider1 <https://github.com/voider1>`_
|
||||
|
||||
+540
@@ -2,8 +2,548 @@
|
||||
Changelog
|
||||
=========
|
||||
|
||||
Version 13.2
|
||||
============
|
||||
*Released 2021-02-02*
|
||||
|
||||
**Major Changes:**
|
||||
|
||||
- Introduce ``python-telegram-bot-raw`` (`#2324`_)
|
||||
- Explicit Signatures for Shortcuts (`#2240`_)
|
||||
|
||||
**New Features:**
|
||||
|
||||
- Add Missing Shortcuts to ``Message`` (`#2330`_)
|
||||
- Rich Comparison for ``Bot`` (`#2320`_)
|
||||
- Add ``run_async`` Parameter to ``ConversationHandler`` (`#2292`_)
|
||||
- Add New Shortcuts to ``Chat`` (`#2291`_)
|
||||
- Add New Constant ``MAX_ANSWER_CALLBACK_QUERY_TEXT_LENGTH`` (`#2282`_)
|
||||
- Allow Passing Custom Filename For All Media (`#2249`_)
|
||||
- Handle Bytes as File Input (`#2233`_)
|
||||
|
||||
**Bug Fixes:**
|
||||
|
||||
- Fix Escaping in Nested Entities in ``Message`` Properties (`#2312`_)
|
||||
- Adjust Calling of ``Dispatcher.update_persistence`` (`#2285`_)
|
||||
- Add ``quote`` kwarg to ``Message.reply_copy`` (`#2232`_)
|
||||
- ``ConversationHandler``: Docs & ``edited_channel_post`` behavior (`#2339`_)
|
||||
|
||||
**Minor changes, CI improvements, doc fixes and type hinting:**
|
||||
|
||||
- Doc Fixes (`#2253`_, `#2225`_)
|
||||
- Reduce Usage of ``typing.Any`` (`#2321`_)
|
||||
- Extend Deeplinking Example (`#2335`_)
|
||||
- Add pyupgrade to pre-commit Hooks (`#2301`_)
|
||||
- Add PR Template (`#2299`_)
|
||||
- Drop Nightly Tests & Update Badges (`#2323`_)
|
||||
- Update Copyright (`#2289`_, `#2287`_)
|
||||
- Change Order of Class DocStrings (`#2256`_)
|
||||
- Add macOS to Test Matrix (`#2266`_)
|
||||
- Start Using Versioning Directives in Docs (`#2252`_)
|
||||
- Improve Annotations & Docs of Handlers (`#2243`_)
|
||||
|
||||
.. _`#2324`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2324
|
||||
.. _`#2240`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2240
|
||||
.. _`#2330`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2330
|
||||
.. _`#2320`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2320
|
||||
.. _`#2292`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2292
|
||||
.. _`#2291`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2291
|
||||
.. _`#2282`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2282
|
||||
.. _`#2249`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2249
|
||||
.. _`#2233`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2233
|
||||
.. _`#2312`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2312
|
||||
.. _`#2285`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2285
|
||||
.. _`#2232`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2232
|
||||
.. _`#2339`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2339
|
||||
.. _`#2253`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2253
|
||||
.. _`#2225`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2225
|
||||
.. _`#2321`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2321
|
||||
.. _`#2335`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2335
|
||||
.. _`#2301`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2301
|
||||
.. _`#2299`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2299
|
||||
.. _`#2323`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2323
|
||||
.. _`#2289`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2289
|
||||
.. _`#2287`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2287
|
||||
.. _`#2256`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2256
|
||||
.. _`#2266`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2266
|
||||
.. _`#2252`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2252
|
||||
.. _`#2243`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2243
|
||||
|
||||
Version 13.1
|
||||
============
|
||||
*Released 2020-11-29*
|
||||
|
||||
**Major Changes:**
|
||||
|
||||
- Full support of Bot API 5.0 (`#2181`_, `#2186`_, `#2190`_, `#2189`_, `#2183`_, `#2184`_, `#2188`_, `#2185`_, `#2192`_, `#2196`_, `#2193`_, `#2223`_, `#2199`_, `#2187`_, `#2147`_, `#2205`_)
|
||||
|
||||
**New Features:**
|
||||
|
||||
- Add ``Defaults.run_async`` (`#2210`_)
|
||||
- Improve and Expand ``CallbackQuery`` Shortcuts (`#2172`_)
|
||||
- Add XOR Filters and make ``Filters.name`` a Property (`#2179`_)
|
||||
- Add ``Filters.document.file_extension`` (`#2169`_)
|
||||
- Add ``Filters.caption_regex`` (`#2163`_)
|
||||
- Add ``Filters.chat_type`` (`#2128`_)
|
||||
- Handle Non-Binary File Input (`#2202`_)
|
||||
|
||||
**Bug Fixes:**
|
||||
|
||||
- Improve Handling of Custom Objects in ``BasePersistence.insert``/``replace_bot`` (`#2151`_)
|
||||
- Fix bugs in ``replace/insert_bot`` (`#2218`_)
|
||||
|
||||
**Minor changes, CI improvements, doc fixes and type hinting:**
|
||||
|
||||
- Improve Type hinting (`#2204`_, `#2118`_, `#2167`_, `#2136`_)
|
||||
- Doc Fixes & Extensions (`#2201`_, `#2161`_)
|
||||
- Use F-Strings Where Possible (`#2222`_)
|
||||
- Rename kwargs to _kwargs where possible (`#2182`_)
|
||||
- Comply with PEP561 (`#2168`_)
|
||||
- Improve Code Quality (`#2131`_)
|
||||
- Switch Code Formatting to Black (`#2122`_, `#2159`_, `#2158`_)
|
||||
- Update Wheel Settings (`#2142`_)
|
||||
- Update ``timerbot.py`` to ``v13.0`` (`#2149`_)
|
||||
- Overhaul Constants (`#2137`_)
|
||||
- Add Python 3.9 to Test Matrix (`#2132`_)
|
||||
- Switch Codecov to ``GitHub`` Action (`#2127`_)
|
||||
- Specify Required pytz Version (`#2121`_)
|
||||
|
||||
|
||||
.. _`#2181`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2181
|
||||
.. _`#2186`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2186
|
||||
.. _`#2190`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2190
|
||||
.. _`#2189`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2189
|
||||
.. _`#2183`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2183
|
||||
.. _`#2184`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2184
|
||||
.. _`#2188`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2188
|
||||
.. _`#2185`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2185
|
||||
.. _`#2192`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2192
|
||||
.. _`#2196`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2196
|
||||
.. _`#2193`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2193
|
||||
.. _`#2223`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2223
|
||||
.. _`#2199`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2199
|
||||
.. _`#2187`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2187
|
||||
.. _`#2147`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2147
|
||||
.. _`#2205`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2205
|
||||
.. _`#2210`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2210
|
||||
.. _`#2172`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2172
|
||||
.. _`#2179`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2179
|
||||
.. _`#2169`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2169
|
||||
.. _`#2163`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2163
|
||||
.. _`#2128`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2128
|
||||
.. _`#2202`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2202
|
||||
.. _`#2151`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2151
|
||||
.. _`#2218`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2218
|
||||
.. _`#2204`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2204
|
||||
.. _`#2118`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2118
|
||||
.. _`#2167`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2167
|
||||
.. _`#2136`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2136
|
||||
.. _`#2201`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2201
|
||||
.. _`#2161`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2161
|
||||
.. _`#2222`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2222
|
||||
.. _`#2182`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2182
|
||||
.. _`#2168`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2168
|
||||
.. _`#2131`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2131
|
||||
.. _`#2122`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2122
|
||||
.. _`#2159`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2159
|
||||
.. _`#2158`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2158
|
||||
.. _`#2142`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2142
|
||||
.. _`#2149`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2149
|
||||
.. _`#2137`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2137
|
||||
.. _`#2132`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2132
|
||||
.. _`#2127`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2127
|
||||
.. _`#2121`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2121
|
||||
|
||||
Version 13.0
|
||||
============
|
||||
*Released 2020-10-07*
|
||||
|
||||
**For a detailed guide on how to migrate from v12 to v13, see this** `wiki page <https://github.com/python-telegram-bot/python-telegram-bot/wiki/Transition-guide-to-Version-13.0>`_.
|
||||
|
||||
**Major Changes:**
|
||||
|
||||
- Deprecate old-style callbacks, i.e. set ``use_context=True`` by default (`#2050`_)
|
||||
- Refactor Handling of Message VS Update Filters (`#2032`_)
|
||||
- Deprecate ``Message.default_quote`` (`#1965`_)
|
||||
- Refactor persistence of Bot instances (`#1994`_)
|
||||
- Refactor ``JobQueue`` (`#1981`_)
|
||||
- Refactor handling of kwargs in Bot methods (`#1924`_)
|
||||
- Refactor ``Dispatcher.run_async``, deprecating the ``@run_async`` decorator (`#2051`_)
|
||||
|
||||
**New Features:**
|
||||
|
||||
- Type Hinting (`#1920`_)
|
||||
- Automatic Pagination for ``answer_inline_query`` (`#2072`_)
|
||||
- ``Defaults.tzinfo`` (`#2042`_)
|
||||
- Extend rich comparison of objects (`#1724`_)
|
||||
- Add ``Filters.via_bot`` (`#2009`_)
|
||||
- Add missing shortcuts (`#2043`_)
|
||||
- Allow ``DispatcherHandlerStop`` in ``ConversationHandler`` (`#2059`_)
|
||||
- Make Errors picklable (`#2106`_)
|
||||
|
||||
**Minor changes, CI improvements, doc fixes or bug fixes:**
|
||||
|
||||
- Fix Webhook not working on Windows with Python 3.8+ (`#2067`_)
|
||||
- Fix setting thumbs with ``send_media_group`` (`#2093`_)
|
||||
- Make ``MessageHandler`` filter for ``Filters.update`` first (`#2085`_)
|
||||
- Fix ``PicklePersistence.flush()`` with only ``bot_data`` (`#2017`_)
|
||||
- Add test for clean argument of ``Updater.start_polling/webhook`` (`#2002`_)
|
||||
- Doc fixes, refinements and additions (`#2005`_, `#2008`_, `#2089`_, `#2094`_, `#2090`_)
|
||||
- CI fixes (`#2018`_, `#2061`_)
|
||||
- Refine ``pollbot.py`` example (`#2047`_)
|
||||
- Refine Filters in examples (`#2027`_)
|
||||
- Rename ``echobot`` examples (`#2025`_)
|
||||
- Use Lock-Bot to lock old threads (`#2048`_, `#2052`_, `#2049`_, `#2053`_)
|
||||
|
||||
.. _`#2050`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2050
|
||||
.. _`#2032`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2032
|
||||
.. _`#1965`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1965
|
||||
.. _`#1994`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1994
|
||||
.. _`#1981`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1981
|
||||
.. _`#1924`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1924
|
||||
.. _`#2051`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2051
|
||||
.. _`#1920`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1920
|
||||
.. _`#2072`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2072
|
||||
.. _`#2042`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2042
|
||||
.. _`#1724`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1724
|
||||
.. _`#2009`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2009
|
||||
.. _`#2043`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2043
|
||||
.. _`#2059`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2059
|
||||
.. _`#2106`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2106
|
||||
.. _`#2067`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2067
|
||||
.. _`#2093`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2093
|
||||
.. _`#2085`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2085
|
||||
.. _`#2017`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2017
|
||||
.. _`#2002`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2002
|
||||
.. _`#2005`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2005
|
||||
.. _`#2008`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2008
|
||||
.. _`#2089`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2089
|
||||
.. _`#2094`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2094
|
||||
.. _`#2090`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2090
|
||||
.. _`#2018`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2018
|
||||
.. _`#2061`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2061
|
||||
.. _`#2047`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2047
|
||||
.. _`#2027`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2027
|
||||
.. _`#2025`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2025
|
||||
.. _`#2048`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2048
|
||||
.. _`#2052`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2052
|
||||
.. _`#2049`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2049
|
||||
.. _`#2053`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2053
|
||||
|
||||
Version 12.8
|
||||
============
|
||||
*Released 2020-06-22*
|
||||
|
||||
**Major Changes:**
|
||||
|
||||
- Remove Python 2 support (`#1715`_)
|
||||
- Bot API 4.9 support (`#1980`_)
|
||||
- IDs/Usernames of ``Filters.user`` and ``Filters.chat`` can now be updated (`#1757`_)
|
||||
|
||||
**Minor changes, CI improvements, doc fixes or bug fixes:**
|
||||
|
||||
- Update contribution guide and stale bot (`#1937`_)
|
||||
- Remove ``NullHandlers`` (`#1913`_)
|
||||
- Improve and expand examples (`#1943`_, `#1995`_, `#1983`_, `#1997`_)
|
||||
- Doc fixes (`#1940`_, `#1962`_)
|
||||
- Add ``User.send_poll()`` shortcut (`#1968`_)
|
||||
- Ignore private attributes en ``TelegramObject.to_dict()`` (`#1989`_)
|
||||
- Stabilize CI (`#2000`_)
|
||||
|
||||
.. _`#1937`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1937
|
||||
.. _`#1913`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1913
|
||||
.. _`#1943`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1943
|
||||
.. _`#1757`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1757
|
||||
.. _`#1940`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1940
|
||||
.. _`#1962`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1962
|
||||
.. _`#1968`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1968
|
||||
.. _`#1989`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1989
|
||||
.. _`#1995`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1995
|
||||
.. _`#1983`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1983
|
||||
.. _`#1715`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1715
|
||||
.. _`#2000`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2000
|
||||
.. _`#1997`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1997
|
||||
.. _`#1980`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1980
|
||||
|
||||
Version 12.7
|
||||
============
|
||||
*Released 2020-05-02*
|
||||
|
||||
**Major Changes:**
|
||||
|
||||
- Bot API 4.8 support. **Note:** The ``Dice`` object now has a second positional argument ``emoji``. This is relevant, if you instantiate ``Dice`` objects manually. (`#1917`_)
|
||||
- Added ``tzinfo`` argument to ``helpers.from_timestamp``. It now returns an timezone aware object. This is relevant for ``Message.{date,forward_date,edit_date}``, ``Poll.close_date`` and ``ChatMember.until_date`` (`#1621`_)
|
||||
|
||||
**New Features:**
|
||||
|
||||
- New method ``run_monthly`` for the ``JobQueue`` (`#1705`_)
|
||||
- ``Job.next_t`` now gives the datetime of the jobs next execution (`#1685`_)
|
||||
|
||||
**Minor changes, CI improvements, doc fixes or bug fixes:**
|
||||
|
||||
- Stabalize CI (`#1919`_, `#1931`_)
|
||||
- Use ABCs ``@abstractmethod`` instead of raising ``NotImplementedError`` for ``Handler``, ``BasePersistence`` and ``BaseFilter`` (`#1905`_)
|
||||
- Doc fixes (`#1914`_, `#1902`_, `#1910`_)
|
||||
|
||||
.. _`#1902`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1902
|
||||
.. _`#1685`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1685
|
||||
.. _`#1910`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1910
|
||||
.. _`#1914`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1914
|
||||
.. _`#1931`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1931
|
||||
.. _`#1905`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1905
|
||||
.. _`#1919`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1919
|
||||
.. _`#1621`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1621
|
||||
.. _`#1705`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1705
|
||||
.. _`#1917`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1917
|
||||
|
||||
Version 12.6.1
|
||||
==============
|
||||
*Released 2020-04-11*
|
||||
|
||||
**Bug fixes:**
|
||||
|
||||
- Fix serialization of ``reply_markup`` in media messages (`#1889`_)
|
||||
|
||||
.. _`#1889`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1889
|
||||
|
||||
Version 12.6
|
||||
============
|
||||
*Released 2020-04-10*
|
||||
|
||||
**Major Changes:**
|
||||
|
||||
- Bot API 4.7 support. **Note:** In ``Bot.create_new_sticker_set`` and ``Bot.add_sticker_to_set``, the order of the parameters had be changed, as the ``png_sticker`` parameter is now optional. (`#1858`_)
|
||||
|
||||
**Minor changes, CI improvements or bug fixes:**
|
||||
|
||||
- Add tests for ``swtich_inline_query(_current_chat)`` with empty string (`#1635`_)
|
||||
- Doc fixes (`#1854`_, `#1874`_, `#1884`_)
|
||||
- Update issue templates (`#1880`_)
|
||||
- Favor concrete types over "Iterable" (`#1882`_)
|
||||
- Pass last valid ``CallbackContext`` to ``TIMEOUT`` handlers of ``ConversationHandler`` (`#1826`_)
|
||||
- Tweak handling of persistence and update persistence after job calls (`#1827`_)
|
||||
- Use checkout@v2 for GitHub actions (`#1887`_)
|
||||
|
||||
.. _`#1858`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1858
|
||||
.. _`#1635`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1635
|
||||
.. _`#1854`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1854
|
||||
.. _`#1874`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1874
|
||||
.. _`#1884`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1884
|
||||
.. _`#1880`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1880
|
||||
.. _`#1882`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1882
|
||||
.. _`#1826`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1826
|
||||
.. _`#1827`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1827
|
||||
.. _`#1887`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1887
|
||||
|
||||
Version 12.5.1
|
||||
==============
|
||||
*Released 2020-03-30*
|
||||
|
||||
**Minor changes, doc fixes or bug fixes:**
|
||||
|
||||
- Add missing docs for `PollHandler` and `PollAnswerHandler` (`#1853`_)
|
||||
- Fix wording in `Filters` docs (`#1855`_)
|
||||
- Reorder tests to make them more stable (`#1835`_)
|
||||
- Make `ConversationHandler` attributes immutable (`#1756`_)
|
||||
- Make `PrefixHandler` attributes `command` and `prefix` editable (`#1636`_)
|
||||
- Fix UTC as default `tzinfo` for `Job` (`#1696`_)
|
||||
|
||||
.. _`#1853`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1853
|
||||
.. _`#1855`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1855
|
||||
.. _`#1835`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1835
|
||||
.. _`#1756`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1756
|
||||
.. _`#1636`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1636
|
||||
.. _`#1696`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1696
|
||||
|
||||
Version 12.5
|
||||
============
|
||||
*Released 2020-03-29*
|
||||
|
||||
**New Features:**
|
||||
|
||||
- `Bot.link` gives the `t.me` link of the bot (`#1770`_)
|
||||
|
||||
**Major Changes:**
|
||||
|
||||
- Bot API 4.5 and 4.6 support. (`#1508`_, `#1723`_)
|
||||
|
||||
**Minor changes, CI improvements or bug fixes:**
|
||||
|
||||
- Remove legacy CI files (`#1783`_, `#1791`_)
|
||||
- Update pre-commit config file (`#1787`_)
|
||||
- Remove builtin names (`#1792`_)
|
||||
- CI improvements (`#1808`_, `#1848`_)
|
||||
- Support Python 3.8 (`#1614`_, `#1824`_)
|
||||
- Use stale bot for auto closing stale issues (`#1820`_, `#1829`_, `#1840`_)
|
||||
- Doc fixes (`#1778`_, `#1818`_)
|
||||
- Fix typo in `edit_message_media` (`#1779`_)
|
||||
- In examples, answer CallbackQueries and use `edit_message_text` shortcut (`#1721`_)
|
||||
- Revert accidental change in vendored urllib3 (`#1775`_)
|
||||
|
||||
.. _`#1783`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1783
|
||||
.. _`#1787`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1787
|
||||
.. _`#1792`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1792
|
||||
.. _`#1791`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1791
|
||||
.. _`#1808`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1808
|
||||
.. _`#1614`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1614
|
||||
.. _`#1770`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1770
|
||||
.. _`#1824`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1824
|
||||
.. _`#1820`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1820
|
||||
.. _`#1829`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1829
|
||||
.. _`#1840`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1840
|
||||
.. _`#1778`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1778
|
||||
.. _`#1779`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1779
|
||||
.. _`#1721`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1721
|
||||
.. _`#1775`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1775
|
||||
.. _`#1848`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1848
|
||||
.. _`#1818`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1818
|
||||
.. _`#1508`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1508
|
||||
.. _`#1723`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1723
|
||||
|
||||
Version 12.4.2
|
||||
==============
|
||||
*Released 2020-02-10*
|
||||
|
||||
**Bug Fixes**
|
||||
|
||||
- Pass correct parse_mode to InlineResults if bot.defaults is None (`#1763`_)
|
||||
- Make sure PP can read files that dont have bot_data (`#1760`_)
|
||||
|
||||
.. _`#1763`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1763
|
||||
.. _`#1760`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1760
|
||||
|
||||
Version 12.4.1
|
||||
==============
|
||||
*Released 2020-02-08*
|
||||
|
||||
This is a quick release for `#1744`_ which was accidently left out of v12.4.0 though mentioned in the
|
||||
release notes.
|
||||
|
||||
|
||||
Version 12.4.0
|
||||
==============
|
||||
*Released 2020-02-08*
|
||||
|
||||
**New features:**
|
||||
|
||||
- Set default values for arguments appearing repeatedly. We also have a `wiki page for the new defaults`_. (`#1490`_)
|
||||
- Store data in ``CallbackContext.bot_data`` to access it in every callback. Also persists. (`#1325`_)
|
||||
- ``Filters.poll`` allows only messages containing a poll (`#1673`_)
|
||||
|
||||
**Major changes:**
|
||||
|
||||
- ``Filters.text`` now accepts messages that start with a slash, because ``CommandHandler`` checks for ``MessageEntity.BOT_COMMAND`` since v12. This might lead to your MessageHandlers receiving more updates than before (`#1680`_).
|
||||
- ``Filters.command`` new checks for ``MessageEntity.BOT_COMMAND`` instead of just a leading slash. Also by ``Filters.command(False)`` you can now filters for messages containing a command `anywhere` in the text (`#1744`_).
|
||||
|
||||
**Minor changes, CI improvements or bug fixes:**
|
||||
|
||||
- Add ``disptacher`` argument to ``Updater`` to allow passing a customized ``Dispatcher`` (`#1484`_)
|
||||
- Add missing names for ``Filters`` (`#1632`_)
|
||||
- Documentation fixes (`#1624`_, `#1647`_, `#1669`_, `#1703`_, `#1718`_, `#1734`_, `#1740`_, `#1642`_, `#1739`_, `#1746`_)
|
||||
- CI improvements (`#1716`_, `#1731`_, `#1738`_, `#1748`_, `#1749`_, `#1750`_, `#1752`_)
|
||||
- Fix spelling issue for ``encode_conversations_to_json`` (`#1661`_)
|
||||
- Remove double assignement of ``Dispatcher.job_queue`` (`#1698`_)
|
||||
- Expose dispatcher as property for ``CallbackContext`` (`#1684`_)
|
||||
- Fix ``None`` check in ``JobQueue._put()`` (`#1707`_)
|
||||
- Log datetimes correctly in ``JobQueue`` (`#1714`_)
|
||||
- Fix false ``Message.link`` creation for private groups (`#1741`_)
|
||||
- Add option ``--with-upstream-urllib3`` to `setup.py` to allow using non-vendored version (`#1725`_)
|
||||
- Fix persistence for nested ``ConversationHandlers`` (`#1679`_)
|
||||
- Improve handling of non-decodable server responses (`#1623`_)
|
||||
- Fix download for files without ``file_path`` (`#1591`_)
|
||||
- test_webhook_invalid_posts is now considered flaky and retried on failure (`#1758`_)
|
||||
|
||||
.. _`wiki page for the new defaults`: https://github.com/python-telegram-bot/python-telegram-bot/wiki/Adding-defaults-to-your-bot
|
||||
.. _`#1744`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1744
|
||||
.. _`#1752`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1752
|
||||
.. _`#1750`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1750
|
||||
.. _`#1591`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1591
|
||||
.. _`#1490`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1490
|
||||
.. _`#1749`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1749
|
||||
.. _`#1623`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1623
|
||||
.. _`#1748`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1748
|
||||
.. _`#1679`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1679
|
||||
.. _`#1711`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1711
|
||||
.. _`#1325`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1325
|
||||
.. _`#1746`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1746
|
||||
.. _`#1725`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1725
|
||||
.. _`#1739`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1739
|
||||
.. _`#1741`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1741
|
||||
.. _`#1642`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1642
|
||||
.. _`#1738`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1738
|
||||
.. _`#1740`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1740
|
||||
.. _`#1734`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1734
|
||||
.. _`#1680`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1680
|
||||
.. _`#1718`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1718
|
||||
.. _`#1714`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1714
|
||||
.. _`#1707`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1707
|
||||
.. _`#1731`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1731
|
||||
.. _`#1673`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1673
|
||||
.. _`#1684`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1684
|
||||
.. _`#1703`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1703
|
||||
.. _`#1698`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1698
|
||||
.. _`#1669`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1669
|
||||
.. _`#1661`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1661
|
||||
.. _`#1647`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1647
|
||||
.. _`#1632`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1632
|
||||
.. _`#1624`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1624
|
||||
.. _`#1716`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1716
|
||||
.. _`#1484`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1484
|
||||
.. _`#1758`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1484
|
||||
|
||||
Version 12.3.0
|
||||
==============
|
||||
*Released 2020-01-11*
|
||||
|
||||
**New features:**
|
||||
|
||||
- `Filters.caption` allows only messages with caption (`#1631`_).
|
||||
- Filter for exact messages/captions with new capability of `Filters.text` and `Filters.caption`. Especially useful in combination with ReplyKeyboardMarkup. (`#1631`_).
|
||||
|
||||
**Major changes:**
|
||||
|
||||
- Fix inconsistent handling of naive datetimes (`#1506`_).
|
||||
|
||||
**Minor changes, CI improvements or bug fixes:**
|
||||
|
||||
- Documentation fixes (`#1558`_, `#1569`_, `#1579`_, `#1572`_, `#1566`_, `#1577`_, `#1656`_).
|
||||
- Add mutex protection on `ConversationHandler` (`#1533`_).
|
||||
- Add `MAX_PHOTOSIZE_UPLOAD` constant (`#1560`_).
|
||||
- Add args and kwargs to `Message.forward()` (`#1574`_).
|
||||
- Transfer to GitHub Actions CI (`#1555`_, `#1556`_, `#1605`_, `#1606`_, `#1607`_, `#1612`_, `#1615`_, `#1645`_).
|
||||
- Fix deprecation warning with Py3.8 by vendored urllib3 (`#1618`_).
|
||||
- Simplify assignements for optional arguments (`#1600`_)
|
||||
- Allow private groups for `Message.link` (`#1619`_).
|
||||
- Fix wrong signature call for `ConversationHandler.TIMEOUT` handlers (`#1653`_).
|
||||
|
||||
.. _`#1631`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1631
|
||||
.. _`#1506`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1506
|
||||
.. _`#1558`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1558
|
||||
.. _`#1569`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1569
|
||||
.. _`#1579`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1579
|
||||
.. _`#1572`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1572
|
||||
.. _`#1566`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1566
|
||||
.. _`#1577`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1577
|
||||
.. _`#1533`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1533
|
||||
.. _`#1560`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1560
|
||||
.. _`#1574`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1574
|
||||
.. _`#1555`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1555
|
||||
.. _`#1556`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1556
|
||||
.. _`#1605`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1605
|
||||
.. _`#1606`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1606
|
||||
.. _`#1607`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1607
|
||||
.. _`#1612`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1612
|
||||
.. _`#1615`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1615
|
||||
.. _`#1618`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1618
|
||||
.. _`#1600`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1600
|
||||
.. _`#1619`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1619
|
||||
.. _`#1653`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1653
|
||||
.. _`#1656`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1656
|
||||
.. _`#1645`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1645
|
||||
|
||||
Version 12.2.0
|
||||
==============
|
||||
*Released 2019-10-14*
|
||||
|
||||
**New features:**
|
||||
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
include LICENSE LICENSE.lesser Makefile requirements.txt
|
||||
include LICENSE LICENSE.lesser Makefile requirements.txt README_RAW.rst telegram/py.typed
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
.DEFAULT_GOAL := help
|
||||
.PHONY: clean pep257 pep8 yapf lint test install
|
||||
.PHONY: clean pep8 black lint test install
|
||||
|
||||
PYLINT := pylint
|
||||
PYTEST := pytest
|
||||
PEP257 := pep257
|
||||
PEP8 := flake8
|
||||
YAPF := yapf
|
||||
BLACK := black
|
||||
MYPY := mypy
|
||||
PIP := pip
|
||||
|
||||
clean:
|
||||
@@ -14,19 +14,20 @@ clean:
|
||||
find . -name '*.pyc' -exec rm -f {} \;
|
||||
find . -name '*.pyo' -exec rm -f {} \;
|
||||
find . -name '*~' -exec rm -f {} \;
|
||||
find . -regex "./telegram.\(mp3\|mp4\|ogg\|png\|webp\)" -exec rm {} \;
|
||||
|
||||
pep257:
|
||||
$(PEP257) telegram
|
||||
find . -regex "./telegram[0-9]*.\(jpg\|mp3\|mp4\|ogg\|png\|webp\)" -exec rm {} \;
|
||||
|
||||
pep8:
|
||||
$(PEP8) telegram
|
||||
$(PEP8) telegram tests examples
|
||||
|
||||
yapf:
|
||||
$(YAPF) -r telegram
|
||||
black:
|
||||
$(BLACK) .
|
||||
|
||||
lint:
|
||||
$(PYLINT) -E telegram --disable=no-name-in-module,import-error
|
||||
$(PYLINT) --rcfile=setup.cfg telegram examples
|
||||
|
||||
mypy:
|
||||
$(MYPY) -p telegram
|
||||
$(MYPY) examples
|
||||
|
||||
test:
|
||||
$(PYTEST) -v
|
||||
@@ -37,16 +38,16 @@ install:
|
||||
help:
|
||||
@echo "Available targets:"
|
||||
@echo "- clean Clean up the source directory"
|
||||
@echo "- pep257 Check docstring style with pep257"
|
||||
@echo "- pep8 Check style with flake8"
|
||||
@echo "- lint Check style with pylint"
|
||||
@echo "- yapf Check style with yapf"
|
||||
@echo "- black Check style with black"
|
||||
@echo "- mypy Check type hinting with mypy"
|
||||
@echo "- test Run tests using pytest"
|
||||
@echo
|
||||
@echo "Available variables:"
|
||||
@echo "- PYLINT default: $(PYLINT)"
|
||||
@echo "- PYTEST default: $(PYTEST)"
|
||||
@echo "- PEP257 default: $(PEP257)"
|
||||
@echo "- PEP8 default: $(PEP8)"
|
||||
@echo "- YAPF default: $(YAPF)"
|
||||
@echo "- BLACK default: $(BLACK)"
|
||||
@echo "- MYPY default: $(MYPY)"
|
||||
@echo "- PIP default: $(PIP)"
|
||||
|
||||
+37
-22
@@ -1,3 +1,6 @@
|
||||
..
|
||||
Make user to apply any changes to this file to README_RAW.rst as well!
|
||||
|
||||
.. image:: https://github.com/python-telegram-bot/logos/blob/master/logo-text/png/ptb-logo-text_768.png?raw=true
|
||||
:align: center
|
||||
:target: https://python-telegram-bot.org
|
||||
@@ -17,31 +20,30 @@ We have a vibrant community of developers helping each other in our `Telegram gr
|
||||
:target: https://pypi.org/project/python-telegram-bot/
|
||||
:alt: Supported Python versions
|
||||
|
||||
.. image:: https://www.cpu.re/static/python-telegram-bot/downloads.svg
|
||||
:target: https://www.cpu.re/static/python-telegram-bot/downloads-by-python-version.txt
|
||||
.. image:: https://img.shields.io/badge/Bot%20API-5.0-blue?logo=telegram
|
||||
:target: https://core.telegram.org/bots/api-changelog
|
||||
:alt: Supported Bot API versions
|
||||
|
||||
.. image:: https://img.shields.io/pypi/dm/python-telegram-bot
|
||||
:target: https://pypistats.org/packages/python-telegram-bot
|
||||
:alt: PyPi Package Monthly Download
|
||||
|
||||
.. image:: https://img.shields.io/badge/docs-latest-af1a97.svg
|
||||
:target: https://python-telegram-bot.readthedocs.io/
|
||||
.. image:: https://readthedocs.org/projects/python-telegram-bot/badge/?version=stable
|
||||
:target: https://python-telegram-bot.readthedocs.io/en/stable/?badge=stable
|
||||
:alt: Documentation Status
|
||||
|
||||
.. image:: https://img.shields.io/pypi/l/python-telegram-bot.svg
|
||||
:target: https://www.gnu.org/licenses/lgpl-3.0.html
|
||||
:alt: LGPLv3 License
|
||||
|
||||
.. image:: https://travis-ci.org/python-telegram-bot/python-telegram-bot.svg?branch=master
|
||||
:target: https://travis-ci.org/python-telegram-bot/python-telegram-bot
|
||||
:alt: Travis CI Status
|
||||
|
||||
.. image:: https://img.shields.io/appveyor/ci/python-telegram-bot/python-telegram-bot/master.svg?logo=appveyor
|
||||
:target: https://ci.appveyor.com/project/python-telegram-bot/python-telegram-bot
|
||||
:alt: AppVeyor CI Status
|
||||
|
||||
.. image:: https://github.com/python-telegram-bot/python-telegram-bot/workflows/GitHub%20Actions/badge.svg
|
||||
:target: https://github.com/python-telegram-bot/python-telegram-bot/
|
||||
:alt: Github Actions workflow
|
||||
|
||||
.. image:: https://codecov.io/gh/python-telegram-bot/python-telegram-bot/branch/master/graph/badge.svg
|
||||
:target: https://codecov.io/gh/python-telegram-bot/python-telegram-bot
|
||||
:alt: Code coverage
|
||||
|
||||
|
||||
.. image:: http://isitmaintained.com/badge/resolution/python-telegram-bot/python-telegram-bot.svg
|
||||
:target: http://isitmaintained.com/project/python-telegram-bot/python-telegram-bot
|
||||
:alt: Median time to resolve an issue
|
||||
@@ -50,7 +52,10 @@ We have a vibrant community of developers helping each other in our `Telegram gr
|
||||
:target: https://www.codacy.com/app/python-telegram-bot/python-telegram-bot?utm_source=github.com&utm_medium=referral&utm_content=python-telegram-bot/python-telegram-bot&utm_campaign=Badge_Grade
|
||||
:alt: Code quality
|
||||
|
||||
.. image:: https://img.shields.io/badge/Telegram-Group-blue.svg
|
||||
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
|
||||
:target: https://github.com/psf/black
|
||||
|
||||
.. image:: https://img.shields.io/badge/Telegram-Group-blue.svg?logo=telegram
|
||||
:target: https://telegram.me/pythontelegrambotgroup
|
||||
:alt: Telegram Group
|
||||
|
||||
@@ -88,17 +93,25 @@ Introduction
|
||||
|
||||
This library provides a pure Python interface for the
|
||||
`Telegram Bot API <https://core.telegram.org/bots/api>`_.
|
||||
It's compatible with Python versions 2.7, 3.3+ and `PyPy <http://pypy.org/>`_.
|
||||
It's compatible with Python versions 3.6+. PTB might also work on `PyPy <http://pypy.org/>`_, though there have been a lot of issues before. Hence, PyPy is not officially supported.
|
||||
|
||||
In addition to the pure API implementation, this library features a number of high-level classes to
|
||||
make the development of bots easy and straightforward. These classes are contained in the
|
||||
``telegram.ext`` submodule.
|
||||
|
||||
A pure API implementation *without* ``telegram.ext`` is available as the standalone package ``python-telegram-bot-raw``. `See here for details. <https://github.com/python-telegram-bot/python-telegram-bot/blob/master/README_RAW.rst>`_
|
||||
|
||||
----
|
||||
Note
|
||||
----
|
||||
|
||||
Installing both ``python-telegram-bot`` and ``python-telegram-bot-raw`` in conjunction will result in undesired side-effects, so only install *one* of both.
|
||||
|
||||
====================
|
||||
Telegram API support
|
||||
====================
|
||||
|
||||
All types and methods of the Telegram Bot API **4.1** are supported.
|
||||
All types and methods of the Telegram Bot API **5.0** are supported.
|
||||
|
||||
==========
|
||||
Installing
|
||||
@@ -142,9 +155,9 @@ Other references:
|
||||
Learning by example
|
||||
-------------------
|
||||
|
||||
We believe that the best way to learn and understand this simple package is by example. So here
|
||||
are some examples for you to review. Even if it's not your approach for learning, please take a
|
||||
look at ``echobot2``, it is de facto the base for most of the bots out there. Best of all,
|
||||
We believe that the best way to learn this package is by example. Here
|
||||
are some examples for you to review. Even if it is not your approach for learning, please take a
|
||||
look at ``echobot.py``, it is the de facto base for most of the bots out there. Best of all,
|
||||
the code for these examples are released to the public domain, so you can start by grabbing the
|
||||
code and building on top of it.
|
||||
|
||||
@@ -192,11 +205,13 @@ You can get help in several ways:
|
||||
|
||||
1. We have a vibrant community of developers helping each other in our `Telegram group <https://telegram.me/pythontelegrambotgroup>`_. Join us!
|
||||
|
||||
2. Our `Wiki pages <https://github.com/python-telegram-bot/python-telegram-bot/wiki/>`_ offer a growing amount of resources.
|
||||
2. In case you are unable to join our group due to Telegram restrictions, you can use our `IRC channel <https://webchat.freenode.net/?channels=##python-telegram-bot>`_.
|
||||
|
||||
3. You can ask for help on Stack Overflow using the `python-telegram-bot tag <https://stackoverflow.com/questions/tagged/python-telegram-bot>`_.
|
||||
3. Report bugs, request new features or ask questions by `creating an issue <https://github.com/python-telegram-bot/python-telegram-bot/issues/new/choose>`_ or `a discussion <https://github.com/python-telegram-bot/python-telegram-bot/discussions/new>`_.
|
||||
|
||||
4. As last resort, the developers are ready to help you with `serious issues <https://github.com/python-telegram-bot/python-telegram-bot/issues/new>`_.
|
||||
4. Our `Wiki pages <https://github.com/python-telegram-bot/python-telegram-bot/wiki/>`_ offer a growing amount of resources.
|
||||
|
||||
5. You can even ask for help on Stack Overflow using the `python-telegram-bot tag <https://stackoverflow.com/questions/tagged/python-telegram-bot>`_.
|
||||
|
||||
|
||||
============
|
||||
|
||||
+210
@@ -0,0 +1,210 @@
|
||||
..
|
||||
Make user to apply any changes to this file to README.rst as well!
|
||||
|
||||
.. image:: https://github.com/python-telegram-bot/logos/blob/master/logo-text/png/ptb-raw-logo-text_768.png?raw=true
|
||||
:align: center
|
||||
:target: https://python-telegram-bot.org
|
||||
:alt: python-telegram-bot-raw Logo
|
||||
|
||||
We have made you a wrapper you can't refuse
|
||||
|
||||
We have a vibrant community of developers helping each other in our `Telegram group <https://telegram.me/pythontelegrambotgroup>`_. Join us!
|
||||
|
||||
*Stay tuned for library updates and new releases on our* `Telegram Channel <https://telegram.me/pythontelegrambotchannel>`_.
|
||||
|
||||
.. image:: https://img.shields.io/pypi/v/python-telegram-bot-raw.svg
|
||||
:target: https://pypi.org/project/python-telegram-bot/
|
||||
:alt: PyPi Package Version
|
||||
|
||||
.. image:: https://img.shields.io/pypi/pyversions/python-telegram-bot-raw.svg
|
||||
:target: https://pypi.org/project/python-telegram-bot/
|
||||
:alt: Supported Python versions
|
||||
|
||||
.. image:: https://img.shields.io/badge/Bot%20API-5.0-blue?logo=telegram
|
||||
:target: https://core.telegram.org/bots/api-changelog
|
||||
:alt: Supported Bot API versions
|
||||
|
||||
.. image:: https://img.shields.io/pypi/dm/python-telegram-bot-raw
|
||||
:target: https://pypistats.org/packages/python-telegram-bot
|
||||
:alt: PyPi Package Monthly Download
|
||||
|
||||
.. image:: https://img.shields.io/badge/docs-latest-af1a97.svg
|
||||
:target: https://python-telegram-bot.readthedocs.io/
|
||||
:alt: Documentation Status
|
||||
|
||||
.. image:: https://img.shields.io/pypi/l/python-telegram-bot-raw.svg
|
||||
:target: https://www.gnu.org/licenses/lgpl-3.0.html
|
||||
:alt: LGPLv3 License
|
||||
|
||||
.. image:: https://github.com/python-telegram-bot/python-telegram-bot/workflows/GitHub%20Actions/badge.svg
|
||||
:target: https://github.com/python-telegram-bot/python-telegram-bot/
|
||||
:alt: Github Actions workflow
|
||||
|
||||
.. image:: https://codecov.io/gh/python-telegram-bot/python-telegram-bot/branch/master/graph/badge.svg
|
||||
:target: https://codecov.io/gh/python-telegram-bot/python-telegram-bot
|
||||
:alt: Code coverage
|
||||
|
||||
.. image:: http://isitmaintained.com/badge/resolution/python-telegram-bot/python-telegram-bot.svg
|
||||
:target: http://isitmaintained.com/project/python-telegram-bot/python-telegram-bot
|
||||
:alt: Median time to resolve an issue
|
||||
|
||||
.. image:: https://api.codacy.com/project/badge/Grade/99d901eaa09b44b4819aec05c330c968
|
||||
:target: https://www.codacy.com/app/python-telegram-bot/python-telegram-bot?utm_source=github.com&utm_medium=referral&utm_content=python-telegram-bot/python-telegram-bot&utm_campaign=Badge_Grade
|
||||
:alt: Code quality
|
||||
|
||||
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
|
||||
:target: https://github.com/psf/black
|
||||
|
||||
.. image:: https://img.shields.io/badge/Telegram-Group-blue.svg?logo=telegram
|
||||
:target: https://telegram.me/pythontelegrambotgroup
|
||||
:alt: Telegram Group
|
||||
|
||||
.. image:: https://img.shields.io/badge/IRC-Channel-blue.svg
|
||||
:target: https://webchat.freenode.net/?channels=##python-telegram-bot
|
||||
:alt: IRC Bridge
|
||||
|
||||
=================
|
||||
Table of contents
|
||||
=================
|
||||
|
||||
- `Introduction`_
|
||||
|
||||
- `Telegram API support`_
|
||||
|
||||
- `Installing`_
|
||||
|
||||
- `Getting started`_
|
||||
|
||||
#. `Logging`_
|
||||
|
||||
#. `Documentation`_
|
||||
|
||||
- `Getting help`_
|
||||
|
||||
- `Contributing`_
|
||||
|
||||
- `License`_
|
||||
|
||||
============
|
||||
Introduction
|
||||
============
|
||||
|
||||
This library provides a pure Python, lightweight interface for the
|
||||
`Telegram Bot API <https://core.telegram.org/bots/api>`_.
|
||||
It's compatible with Python versions 3.6+. PTB-Raw might also work on `PyPy <http://pypy.org/>`_, though there have been a lot of issues before. Hence, PyPy is not officially supported.
|
||||
|
||||
``python-telegram-bot-raw`` is part of the `python-telegram-bot <https://python-telegram-bot.org>`_ ecosystem and provides the pure API functionality extracted from PTB. It therefore does *not* have independent release schedules, changelogs or documentation. Please consult the PTB resources.
|
||||
|
||||
----
|
||||
Note
|
||||
----
|
||||
|
||||
Installing both ``python-telegram-bot`` and ``python-telegram-bot-raw`` in conjunction will result in undesired side-effects, so only install *one* of both.
|
||||
|
||||
====================
|
||||
Telegram API support
|
||||
====================
|
||||
|
||||
All types and methods of the Telegram Bot API **5.0** are supported.
|
||||
|
||||
==========
|
||||
Installing
|
||||
==========
|
||||
|
||||
You can install or upgrade python-telegram-bot-raw with:
|
||||
|
||||
.. code:: shell
|
||||
|
||||
$ pip install python-telegram-bot-raw --upgrade
|
||||
|
||||
Or you can install from source with:
|
||||
|
||||
.. code:: shell
|
||||
|
||||
$ git clone https://github.com/python-telegram-bot/python-telegram-bot --recursive
|
||||
$ cd python-telegram-bot
|
||||
$ python setup-raw.py install
|
||||
|
||||
In case you have a previously cloned local repository already, you should initialize the added urllib3 submodule before installing with:
|
||||
|
||||
.. code:: shell
|
||||
|
||||
$ git submodule update --init --recursive
|
||||
|
||||
----
|
||||
Note
|
||||
----
|
||||
|
||||
Installing the `.tar.gz` archive available on PyPi directly via `pip` will *not* work as expected, as `pip` does not recognize that it should use `setup-raw.py` instead of `setup.py`.
|
||||
|
||||
===============
|
||||
Getting started
|
||||
===============
|
||||
|
||||
Our Wiki contains an `Introduction to the API <https://github.com/python-telegram-bot/python-telegram-bot/wiki/Introduction-to-the-API>`_. Other references are:
|
||||
|
||||
- the `Telegram API documentation <https://core.telegram.org/bots/api>`_
|
||||
- the `python-telegram-bot documentation <https://python-telegram-bot.readthedocs.io/>`_
|
||||
|
||||
-------
|
||||
Logging
|
||||
-------
|
||||
|
||||
This library uses the ``logging`` module. To set up logging to standard output, put:
|
||||
|
||||
.. code:: python
|
||||
|
||||
import logging
|
||||
logging.basicConfig(level=logging.DEBUG,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
|
||||
at the beginning of your script.
|
||||
|
||||
You can also use logs in your application by calling ``logging.getLogger()`` and setting the log level you want:
|
||||
|
||||
.. code:: python
|
||||
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
If you want DEBUG logs instead:
|
||||
|
||||
.. code:: python
|
||||
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
|
||||
=============
|
||||
Documentation
|
||||
=============
|
||||
|
||||
``python-telegram-bot``'s documentation lives at `readthedocs.io <https://python-telegram-bot.readthedocs.io/>`_, which
|
||||
includes the relevant documentation for ``python-telegram-bot-raw``.
|
||||
|
||||
============
|
||||
Getting help
|
||||
============
|
||||
|
||||
You can get help in several ways:
|
||||
|
||||
1. We have a vibrant community of developers helping each other in our `Telegram group <https://telegram.me/pythontelegrambotgroup>`_. Join us!
|
||||
|
||||
2. In case you are unable to join our group due to Telegram restrictions, you can use our `IRC channel <https://webchat.freenode.net/?channels=##python-telegram-bot>`_.
|
||||
|
||||
3. Report bugs, request new features or ask questions by `creating an issue <https://github.com/python-telegram-bot/python-telegram-bot/issues/new/choose>`_ or `a discussion <https://github.com/python-telegram-bot/python-telegram-bot/discussions/new>`_.
|
||||
|
||||
4. Our `Wiki pages <https://github.com/python-telegram-bot/python-telegram-bot/wiki/>`_ offer a growing amount of resources.
|
||||
|
||||
5. You can even ask for help on Stack Overflow using the `python-telegram-bot tag <https://stackoverflow.com/questions/tagged/python-telegram-bot>`_.
|
||||
|
||||
============
|
||||
Contributing
|
||||
============
|
||||
|
||||
Contributions of all sizes are welcome. Please review our `contribution guidelines <https://github.com/python-telegram-bot/python-telegram-bot/blob/master/.github/CONTRIBUTING.rst>`_ to get started. You can also help by `reporting bugs <https://github.com/python-telegram-bot/python-telegram-bot/issues/new>`_.
|
||||
|
||||
=======
|
||||
License
|
||||
=======
|
||||
|
||||
You may copy, distribute and modify the software provided that modifications are described and licensed for free under `LGPL-3 <https://www.gnu.org/licenses/lgpl-3.0.html>`_. Derivatives works (including modifications or anything statically linked to the library) can only be redistributed under LGPL-3, but applications that use the library don't have to be.
|
||||
@@ -1,43 +0,0 @@
|
||||
environment:
|
||||
|
||||
matrix:
|
||||
# For Python versions available on Appveyor, see
|
||||
# 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.
|
||||
|
||||
- PYTHON: "C:\\Python27"
|
||||
- PYTHON: "C:\\Python35"
|
||||
- PYTHON: "C:\\Python36"
|
||||
- PYTHON: "C:\\Python37"
|
||||
# - PYTHON: "C:\\Python38"
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- /^[vV]\d+$/
|
||||
|
||||
skip_branch_with_pr: true
|
||||
|
||||
max_jobs: 1
|
||||
|
||||
install:
|
||||
- "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
|
||||
- "git submodule update --init --recursive"
|
||||
# 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"
|
||||
|
||||
build: off
|
||||
|
||||
test_script:
|
||||
- "pytest --version"
|
||||
- "pytest -m \"not nocoverage\" --cov --cov-report xml:coverage.xml"
|
||||
|
||||
after_test:
|
||||
- "codecov -f coverage.xml -F Appveyor"
|
||||
@@ -1 +1,10 @@
|
||||
comment: false
|
||||
|
||||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
# We allow small coverage decreases in the project because we don't retry
|
||||
# on hitting flood limits, which adds noise to the coverage
|
||||
target: auto
|
||||
threshold: 0.1%
|
||||
|
||||
@@ -15,7 +15,7 @@ Depends: ${python3:Depends}, ${misc:Depends}
|
||||
Description: We have made you a wrapper you can't refuse!
|
||||
The Python Telegram bot (Python 3)
|
||||
This library provides a pure Python interface for the Telegram Bot API.
|
||||
It's compatible with Python versions 2.7, 3.3+ and PyPy.
|
||||
It's compatible with Python versions 3.5+ and PyPy.
|
||||
.
|
||||
In addition to the pure API implementation, this library features
|
||||
a number of high-level
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
export PYBUILD_NAME=telegram
|
||||
|
||||
%:
|
||||
DEB_BUILD_OPTIONS=nocheck dh $@ --with python2,python3 --buildsystem=pybuild
|
||||
DEB_BUILD_OPTIONS=nocheck dh $@ --with python3 --buildsystem=pybuild
|
||||
|
||||
|
||||
# If you need to rebuild the Sphinx documentation
|
||||
|
||||
+3
-3
@@ -50,7 +50,7 @@ master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'Python Telegram Bot'
|
||||
copyright = u'2015-2018, Leandro Toledo'
|
||||
copyright = u'2015-2021, Leandro Toledo'
|
||||
author = u'Leandro Toledo'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
@@ -58,9 +58,9 @@ author = u'Leandro Toledo'
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '12.1' # telegram.__version__[:3]
|
||||
version = '13.2' # telegram.__version__[:3]
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '12.2.0' # telegram.__version__
|
||||
release = '13.2' # telegram.__version__
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
telegram.BotCommand
|
||||
===================
|
||||
|
||||
.. autoclass:: telegram.BotCommand
|
||||
:members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,6 @@
|
||||
telegram.ChatLocation
|
||||
=====================
|
||||
|
||||
.. autoclass:: telegram.ChatLocation
|
||||
:members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,6 @@
|
||||
telegram.Dice
|
||||
=============
|
||||
|
||||
.. autoclass:: telegram.Dice
|
||||
:members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,6 @@
|
||||
telegram.ext.Defaults
|
||||
=====================
|
||||
|
||||
.. autoclass:: telegram.ext.Defaults
|
||||
:members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,6 @@
|
||||
telegram.ext.DispatcherHandlerStop
|
||||
==================================
|
||||
|
||||
.. autoclass:: telegram.ext.DispatcherHandlerStop
|
||||
:members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,6 @@
|
||||
telegram.ext.PollAnswerHandler
|
||||
==============================
|
||||
|
||||
.. autoclass:: telegram.ext.PollAnswerHandler
|
||||
:members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,6 @@
|
||||
telegram.ext.PollHandler
|
||||
========================
|
||||
|
||||
.. autoclass:: telegram.ext.PollHandler
|
||||
:members:
|
||||
:show-inheritance:
|
||||
@@ -5,12 +5,13 @@ telegram.ext package
|
||||
|
||||
telegram.ext.updater
|
||||
telegram.ext.dispatcher
|
||||
telegram.ext.filters
|
||||
telegram.ext.dispatcherhandlerstop
|
||||
telegram.ext.callbackcontext
|
||||
telegram.ext.defaults
|
||||
telegram.ext.job
|
||||
telegram.ext.jobqueue
|
||||
telegram.ext.messagequeue
|
||||
telegram.ext.delayqueue
|
||||
telegram.ext.callbackcontext
|
||||
|
||||
Handlers
|
||||
--------
|
||||
@@ -20,10 +21,13 @@ Handlers
|
||||
telegram.ext.handler
|
||||
telegram.ext.callbackqueryhandler
|
||||
telegram.ext.choseninlineresulthandler
|
||||
telegram.ext.conversationhandler
|
||||
telegram.ext.commandhandler
|
||||
telegram.ext.conversationhandler
|
||||
telegram.ext.inlinequeryhandler
|
||||
telegram.ext.messagehandler
|
||||
telegram.ext.filters
|
||||
telegram.ext.pollanswerhandler
|
||||
telegram.ext.pollhandler
|
||||
telegram.ext.precheckoutqueryhandler
|
||||
telegram.ext.prefixhandler
|
||||
telegram.ext.regexhandler
|
||||
@@ -39,4 +43,11 @@ Persistence
|
||||
|
||||
telegram.ext.basepersistence
|
||||
telegram.ext.picklepersistence
|
||||
telegram.ext.dictpersistence
|
||||
telegram.ext.dictpersistence
|
||||
|
||||
utils
|
||||
-----
|
||||
|
||||
.. toctree::
|
||||
|
||||
telegram.ext.utils.promise
|
||||
@@ -0,0 +1,6 @@
|
||||
telegram.ext.utils.promise.Promise
|
||||
==================================
|
||||
|
||||
.. autoclass:: telegram.ext.utils.promise.Promise
|
||||
:members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,6 @@
|
||||
telegram.KeyboardButtonPollType
|
||||
===============================
|
||||
|
||||
.. autoclass:: telegram.KeyboardButtonPollType
|
||||
:members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,6 @@
|
||||
telegram.MessageId
|
||||
==================
|
||||
|
||||
.. autoclass:: telegram.MessageId
|
||||
:members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,6 @@
|
||||
telegram.PollAnswer
|
||||
===================
|
||||
|
||||
.. autoclass:: telegram.PollAnswer
|
||||
:members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,6 @@
|
||||
telegram.ProximityAlertTriggered
|
||||
================================
|
||||
|
||||
.. autoclass:: telegram.ProximityAlertTriggered
|
||||
:members:
|
||||
:show-inheritance:
|
||||
@@ -9,14 +9,17 @@ telegram package
|
||||
telegram.animation
|
||||
telegram.audio
|
||||
telegram.bot
|
||||
telegram.botcommand
|
||||
telegram.callbackquery
|
||||
telegram.chat
|
||||
telegram.chataction
|
||||
telegram.chatlocation
|
||||
telegram.chatmember
|
||||
telegram.chatpermissions
|
||||
telegram.chatphoto
|
||||
telegram.constants
|
||||
telegram.contact
|
||||
telegram.dice
|
||||
telegram.document
|
||||
telegram.error
|
||||
telegram.file
|
||||
@@ -31,14 +34,18 @@ telegram package
|
||||
telegram.inputmediaphoto
|
||||
telegram.inputmediavideo
|
||||
telegram.keyboardbutton
|
||||
telegram.keyboardbuttonpolltype
|
||||
telegram.location
|
||||
telegram.loginurl
|
||||
telegram.message
|
||||
telegram.messageid
|
||||
telegram.messageentity
|
||||
telegram.parsemode
|
||||
telegram.photosize
|
||||
telegram.poll
|
||||
telegram.pollanswer
|
||||
telegram.polloption
|
||||
telegram.proximityalerttriggered
|
||||
telegram.replykeyboardremove
|
||||
telegram.replykeyboardmarkup
|
||||
telegram.replymarkup
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
telegram.utils.promise.Promise
|
||||
==============================
|
||||
|
||||
.. autoclass:: telegram.utils.promise.Promise
|
||||
:members:
|
||||
:show-inheritance:
|
||||
.. py:class:: telegram.utils.promise.Promise
|
||||
|
||||
Shortcut for :class:`telegram.ext.utils.promise.Promise`.
|
||||
|
||||
.. deprecated:: 13.2
|
||||
Use :class:`telegram.ext.utils.promise.Promise` instead.
|
||||
|
||||
@@ -6,3 +6,4 @@ telegram.utils package
|
||||
telegram.utils.helpers
|
||||
telegram.utils.promise
|
||||
telegram.utils.request
|
||||
telegram.utils.types
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
telegram.utils.types Module
|
||||
===========================
|
||||
|
||||
.. automodule:: telegram.utils.types
|
||||
:members:
|
||||
:show-inheritance:
|
||||
+17
-5
@@ -1,10 +1,10 @@
|
||||
# Examples
|
||||
|
||||
In this folder there are small examples to show what a bot written with `python-telegram-bot` looks like. Some bots focus on one specific aspect of the Telegram Bot API while others focus on one of the mechanics of this library. Except for the [`echobot.py`](#pure-api) example, they all use the high-level framework this library provides with the [`telegram.ext`](https://python-telegram-bot.readthedocs.io/en/latest/telegram.ext.html) submodule.
|
||||
In this folder are small examples to show what a bot written with `python-telegram-bot` looks like. Some bots focus on one specific aspect of the Telegram Bot API while others focus on one of the mechanics of this library. Except for the [`rawapibot.py`](#pure-api) example, they all use the high-level framework this library provides with the [`telegram.ext`](https://python-telegram-bot.readthedocs.io/en/latest/telegram.ext.html) submodule.
|
||||
|
||||
All examples are licensed under the [CC0 License](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/LICENSE.txt) and are therefore fully dedicated to the public domain. You can use them as the base for your own bots without worrying about copyrights.
|
||||
|
||||
### [`echobot2.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/echobot2.py)
|
||||
### [`echobot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/echobot.py)
|
||||
This is probably the base for most of the bots made with `python-telegram-bot`. It simply replies to each text message with a message that contains the same text.
|
||||
|
||||
### [`timerbot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/timerbot.py)
|
||||
@@ -19,20 +19,32 @@ A more complex example of a bot that uses the `ConversationHandler`. It is also
|
||||
### [`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!
|
||||
|
||||
### [`persistentconversationbot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/persistentconversationbot.py)
|
||||
A basic example of a bot store conversation state and user_data over multiple restarts.
|
||||
|
||||
### [`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.
|
||||
|
||||
### [`deeplinking.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/deeplinking.py)
|
||||
A basic example on how to use deeplinking with 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).
|
||||
|
||||
### [`pollbot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/pollbot.py)
|
||||
This example sheds some light on polls, poll answers and the corresponding handlers.
|
||||
|
||||
### [`passportbot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/passportbot.py)
|
||||
A basic example of a bot that can accept passports. Use in combination with [`passportbot.html`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/passportbot.html). Don't forget to enable and configure payments with [@BotFather](https://telegram.me/BotFather). Check out this [guide](https://git.io/fAvYd) on Telegram passports in PTB.
|
||||
|
||||
### [`paymentbot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/paymentbot.py)
|
||||
A basic example of a bot that can accept payments. Don't forget to enable and configure payments with [@BotFather](https://telegram.me/BotFather).
|
||||
|
||||
### [`persistentconversationbot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/persistentconversationbot.py)
|
||||
A basic example of a bot store conversation state and user_data over multiple restarts.
|
||||
### [`errorhandlerbot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/errorhandlerbot.py)
|
||||
A basic example on how to set up a custom error handler.
|
||||
|
||||
## Pure API
|
||||
The [`echobot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/echobot.py) example uses only the pure, "bare-metal" API wrapper.
|
||||
The [`rawapibot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/rawapibot.py) example uses only the pure, "bare-metal" API wrapper.
|
||||
|
||||
+58
-55
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# pylint: disable=W0613, C0116
|
||||
# type: ignore[union-attr]
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
"""
|
||||
@@ -16,82 +17,97 @@ bot.
|
||||
|
||||
import logging
|
||||
|
||||
from telegram import (ReplyKeyboardMarkup, ReplyKeyboardRemove)
|
||||
from telegram.ext import (Updater, CommandHandler, MessageHandler, Filters,
|
||||
ConversationHandler)
|
||||
from telegram import ReplyKeyboardMarkup, ReplyKeyboardRemove, Update
|
||||
from telegram.ext import (
|
||||
Updater,
|
||||
CommandHandler,
|
||||
MessageHandler,
|
||||
Filters,
|
||||
ConversationHandler,
|
||||
CallbackContext,
|
||||
)
|
||||
|
||||
# Enable logging
|
||||
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
level=logging.INFO)
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
GENDER, PHOTO, LOCATION, BIO = range(4)
|
||||
|
||||
|
||||
def start(update, context):
|
||||
def start(update: Update, context: CallbackContext) -> int:
|
||||
reply_keyboard = [['Boy', 'Girl', 'Other']]
|
||||
|
||||
update.message.reply_text(
|
||||
'Hi! My name is Professor Bot. I will hold a conversation with you. '
|
||||
'Send /cancel to stop talking to me.\n\n'
|
||||
'Are you a boy or a girl?',
|
||||
reply_markup=ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True))
|
||||
reply_markup=ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True),
|
||||
)
|
||||
|
||||
return GENDER
|
||||
|
||||
|
||||
def gender(update, context):
|
||||
def gender(update: Update, context: CallbackContext) -> int:
|
||||
user = update.message.from_user
|
||||
logger.info("Gender of %s: %s", user.first_name, update.message.text)
|
||||
update.message.reply_text('I see! Please send me a photo of yourself, '
|
||||
'so I know what you look like, or send /skip if you don\'t want to.',
|
||||
reply_markup=ReplyKeyboardRemove())
|
||||
update.message.reply_text(
|
||||
'I see! Please send me a photo of yourself, '
|
||||
'so I know what you look like, or send /skip if you don\'t want to.',
|
||||
reply_markup=ReplyKeyboardRemove(),
|
||||
)
|
||||
|
||||
return PHOTO
|
||||
|
||||
|
||||
def photo(update, context):
|
||||
def photo(update: Update, context: CallbackContext) -> int:
|
||||
user = update.message.from_user
|
||||
photo_file = update.message.photo[-1].get_file()
|
||||
photo_file.download('user_photo.jpg')
|
||||
logger.info("Photo of %s: %s", user.first_name, 'user_photo.jpg')
|
||||
update.message.reply_text('Gorgeous! Now, send me your location please, '
|
||||
'or send /skip if you don\'t want to.')
|
||||
update.message.reply_text(
|
||||
'Gorgeous! Now, send me your location please, ' 'or send /skip if you don\'t want to.'
|
||||
)
|
||||
|
||||
return LOCATION
|
||||
|
||||
|
||||
def skip_photo(update, context):
|
||||
def skip_photo(update: Update, context: CallbackContext) -> int:
|
||||
user = update.message.from_user
|
||||
logger.info("User %s did not send a photo.", user.first_name)
|
||||
update.message.reply_text('I bet you look great! Now, send me your location please, '
|
||||
'or send /skip.')
|
||||
update.message.reply_text(
|
||||
'I bet you look great! Now, send me your location please, ' 'or send /skip.'
|
||||
)
|
||||
|
||||
return LOCATION
|
||||
|
||||
|
||||
def location(update, context):
|
||||
def location(update: Update, context: CallbackContext) -> int:
|
||||
user = update.message.from_user
|
||||
user_location = update.message.location
|
||||
logger.info("Location of %s: %f / %f", user.first_name, user_location.latitude,
|
||||
user_location.longitude)
|
||||
update.message.reply_text('Maybe I can visit you sometime! '
|
||||
'At last, tell me something about yourself.')
|
||||
logger.info(
|
||||
"Location of %s: %f / %f", user.first_name, user_location.latitude, user_location.longitude
|
||||
)
|
||||
update.message.reply_text(
|
||||
'Maybe I can visit you sometime! ' 'At last, tell me something about yourself.'
|
||||
)
|
||||
|
||||
return BIO
|
||||
|
||||
|
||||
def skip_location(update, context):
|
||||
def skip_location(update: Update, context: CallbackContext) -> int:
|
||||
user = update.message.from_user
|
||||
logger.info("User %s did not send a location.", user.first_name)
|
||||
update.message.reply_text('You seem a bit paranoid! '
|
||||
'At last, tell me something about yourself.')
|
||||
update.message.reply_text(
|
||||
'You seem a bit paranoid! ' 'At last, tell me something about yourself.'
|
||||
)
|
||||
|
||||
return BIO
|
||||
|
||||
|
||||
def bio(update, context):
|
||||
def bio(update: Update, context: CallbackContext) -> int:
|
||||
user = update.message.from_user
|
||||
logger.info("Bio of %s: %s", user.first_name, update.message.text)
|
||||
update.message.reply_text('Thank you! I hope we can talk again some day.')
|
||||
@@ -99,52 +115,39 @@ def bio(update, context):
|
||||
return ConversationHandler.END
|
||||
|
||||
|
||||
def cancel(update, context):
|
||||
def cancel(update: Update, context: CallbackContext) -> int:
|
||||
user = update.message.from_user
|
||||
logger.info("User %s canceled the conversation.", user.first_name)
|
||||
update.message.reply_text('Bye! I hope we can talk again some day.',
|
||||
reply_markup=ReplyKeyboardRemove())
|
||||
update.message.reply_text(
|
||||
'Bye! I hope we can talk again some day.', reply_markup=ReplyKeyboardRemove()
|
||||
)
|
||||
|
||||
return ConversationHandler.END
|
||||
|
||||
|
||||
def error(update, context):
|
||||
"""Log Errors caused by Updates."""
|
||||
logger.warning('Update "%s" caused error "%s"', update, context.error)
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
# 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)
|
||||
updater = Updater("TOKEN")
|
||||
|
||||
# Get the dispatcher to register handlers
|
||||
dp = updater.dispatcher
|
||||
dispatcher = updater.dispatcher
|
||||
|
||||
# Add conversation handler with the states GENDER, PHOTO, LOCATION and BIO
|
||||
conv_handler = ConversationHandler(
|
||||
entry_points=[CommandHandler('start', start)],
|
||||
|
||||
states={
|
||||
GENDER: [MessageHandler(Filters.regex('^(Boy|Girl|Other)$'), gender)],
|
||||
|
||||
PHOTO: [MessageHandler(Filters.photo, photo),
|
||||
CommandHandler('skip', skip_photo)],
|
||||
|
||||
LOCATION: [MessageHandler(Filters.location, location),
|
||||
CommandHandler('skip', skip_location)],
|
||||
|
||||
BIO: [MessageHandler(Filters.text, bio)]
|
||||
PHOTO: [MessageHandler(Filters.photo, photo), CommandHandler('skip', skip_photo)],
|
||||
LOCATION: [
|
||||
MessageHandler(Filters.location, location),
|
||||
CommandHandler('skip', skip_location),
|
||||
],
|
||||
BIO: [MessageHandler(Filters.text & ~Filters.command, bio)],
|
||||
},
|
||||
|
||||
fallbacks=[CommandHandler('cancel', cancel)]
|
||||
fallbacks=[CommandHandler('cancel', cancel)],
|
||||
)
|
||||
|
||||
dp.add_handler(conv_handler)
|
||||
|
||||
# log all errors
|
||||
dp.add_error_handler(error)
|
||||
dispatcher.add_handler(conv_handler)
|
||||
|
||||
# Start the Bot
|
||||
updater.start_polling()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# pylint: disable=W0613, C0116
|
||||
# type: ignore[union-attr]
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
"""
|
||||
@@ -15,128 +16,133 @@ bot.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Dict
|
||||
|
||||
from telegram import ReplyKeyboardMarkup
|
||||
from telegram.ext import (Updater, CommandHandler, MessageHandler, Filters,
|
||||
ConversationHandler)
|
||||
from telegram import ReplyKeyboardMarkup, Update
|
||||
from telegram.ext import (
|
||||
Updater,
|
||||
CommandHandler,
|
||||
MessageHandler,
|
||||
Filters,
|
||||
ConversationHandler,
|
||||
CallbackContext,
|
||||
)
|
||||
|
||||
# Enable logging
|
||||
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
level=logging.INFO)
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
CHOOSING, TYPING_REPLY, TYPING_CHOICE = range(3)
|
||||
|
||||
reply_keyboard = [['Age', 'Favourite colour'],
|
||||
['Number of siblings', 'Something else...'],
|
||||
['Done']]
|
||||
reply_keyboard = [
|
||||
['Age', 'Favourite colour'],
|
||||
['Number of siblings', 'Something else...'],
|
||||
['Done'],
|
||||
]
|
||||
markup = ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True)
|
||||
|
||||
|
||||
def facts_to_str(user_data):
|
||||
def facts_to_str(user_data: Dict[str, str]) -> str:
|
||||
facts = list()
|
||||
|
||||
for key, value in user_data.items():
|
||||
facts.append('{} - {}'.format(key, value))
|
||||
facts.append(f'{key} - {value}')
|
||||
|
||||
return "\n".join(facts).join(['\n', '\n'])
|
||||
|
||||
|
||||
def start(update, context):
|
||||
def start(update: Update, context: CallbackContext) -> int:
|
||||
update.message.reply_text(
|
||||
"Hi! My name is Doctor Botter. I will hold a more complex conversation with you. "
|
||||
"Why don't you tell me something about yourself?",
|
||||
reply_markup=markup)
|
||||
reply_markup=markup,
|
||||
)
|
||||
|
||||
return CHOOSING
|
||||
|
||||
|
||||
def regular_choice(update, context):
|
||||
def regular_choice(update: Update, context: CallbackContext) -> int:
|
||||
text = update.message.text
|
||||
context.user_data['choice'] = text
|
||||
update.message.reply_text(
|
||||
'Your {}? Yes, I would love to hear about that!'.format(text.lower()))
|
||||
update.message.reply_text(f'Your {text.lower()}? Yes, I would love to hear about that!')
|
||||
|
||||
return TYPING_REPLY
|
||||
|
||||
|
||||
def custom_choice(update, context):
|
||||
update.message.reply_text('Alright, please send me the category first, '
|
||||
'for example "Most impressive skill"')
|
||||
def custom_choice(update: Update, context: CallbackContext) -> int:
|
||||
update.message.reply_text(
|
||||
'Alright, please send me the category first, ' 'for example "Most impressive skill"'
|
||||
)
|
||||
|
||||
return TYPING_CHOICE
|
||||
|
||||
|
||||
def received_information(update, context):
|
||||
def received_information(update: Update, context: CallbackContext) -> int:
|
||||
user_data = context.user_data
|
||||
text = update.message.text
|
||||
category = user_data['choice']
|
||||
user_data[category] = text
|
||||
del user_data['choice']
|
||||
|
||||
update.message.reply_text("Neat! Just so you know, this is what you already told me:"
|
||||
"{} You can tell me more, or change your opinion"
|
||||
" on something.".format(facts_to_str(user_data)),
|
||||
reply_markup=markup)
|
||||
update.message.reply_text(
|
||||
"Neat! Just so you know, this is what you already told me:"
|
||||
f"{facts_to_str(user_data)} You can tell me more, or change your opinion"
|
||||
" on something.",
|
||||
reply_markup=markup,
|
||||
)
|
||||
|
||||
return CHOOSING
|
||||
|
||||
|
||||
def done(update, context):
|
||||
def done(update: Update, context: CallbackContext) -> int:
|
||||
user_data = context.user_data
|
||||
if 'choice' in user_data:
|
||||
del user_data['choice']
|
||||
|
||||
update.message.reply_text("I learned these facts about you:"
|
||||
"{}"
|
||||
"Until next time!".format(facts_to_str(user_data)))
|
||||
update.message.reply_text(
|
||||
f"I learned these facts about you: {facts_to_str(user_data)} Until next time!"
|
||||
)
|
||||
|
||||
user_data.clear()
|
||||
return ConversationHandler.END
|
||||
|
||||
|
||||
def error(update, context):
|
||||
"""Log Errors caused by Updates."""
|
||||
logger.warning('Update "%s" caused error "%s"', update, context.error)
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
# 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)
|
||||
updater = Updater("TOKEN")
|
||||
|
||||
# Get the dispatcher to register handlers
|
||||
dp = updater.dispatcher
|
||||
dispatcher = updater.dispatcher
|
||||
|
||||
# Add conversation handler with the states CHOOSING, TYPING_CHOICE and TYPING_REPLY
|
||||
conv_handler = ConversationHandler(
|
||||
entry_points=[CommandHandler('start', start)],
|
||||
|
||||
states={
|
||||
CHOOSING: [MessageHandler(Filters.regex('^(Age|Favourite colour|Number of siblings)$'),
|
||||
regular_choice),
|
||||
MessageHandler(Filters.regex('^Something else...$'),
|
||||
custom_choice)
|
||||
],
|
||||
|
||||
TYPING_CHOICE: [MessageHandler(Filters.text,
|
||||
regular_choice)
|
||||
],
|
||||
|
||||
TYPING_REPLY: [MessageHandler(Filters.text,
|
||||
received_information),
|
||||
],
|
||||
CHOOSING: [
|
||||
MessageHandler(
|
||||
Filters.regex('^(Age|Favourite colour|Number of siblings)$'), regular_choice
|
||||
),
|
||||
MessageHandler(Filters.regex('^Something else...$'), custom_choice),
|
||||
],
|
||||
TYPING_CHOICE: [
|
||||
MessageHandler(
|
||||
Filters.text & ~(Filters.command | Filters.regex('^Done$')), regular_choice
|
||||
)
|
||||
],
|
||||
TYPING_REPLY: [
|
||||
MessageHandler(
|
||||
Filters.text & ~(Filters.command | Filters.regex('^Done$')),
|
||||
received_information,
|
||||
)
|
||||
],
|
||||
},
|
||||
|
||||
fallbacks=[MessageHandler(Filters.regex('^Done$'), done)]
|
||||
fallbacks=[MessageHandler(Filters.regex('^Done$'), done)],
|
||||
)
|
||||
|
||||
dp.add_handler(conv_handler)
|
||||
|
||||
# log all errors
|
||||
dp.add_error_handler(error)
|
||||
dispatcher.add_handler(conv_handler)
|
||||
|
||||
# Start the Bot
|
||||
updater.start_polling()
|
||||
|
||||
+75
-40
@@ -1,5 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# pylint: disable=W0613, C0116
|
||||
# type: ignore[union-attr]
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
"""Bot that explains Telegram's "Deep Linking Parameters" functionality.
|
||||
|
||||
@@ -19,92 +21,125 @@ bot.
|
||||
|
||||
import logging
|
||||
|
||||
from telegram import ParseMode, InlineKeyboardMarkup, InlineKeyboardButton
|
||||
from telegram.ext import Updater, CommandHandler, Filters
|
||||
from telegram import ParseMode, InlineKeyboardMarkup, InlineKeyboardButton, Update
|
||||
from telegram.ext import (
|
||||
Updater,
|
||||
CommandHandler,
|
||||
CallbackQueryHandler,
|
||||
Filters,
|
||||
CallbackContext,
|
||||
)
|
||||
|
||||
# Enable logging
|
||||
from telegram.utils import helpers
|
||||
|
||||
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
level=logging.INFO)
|
||||
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'
|
||||
CHECK_THIS_OUT = "check-this-out"
|
||||
USING_ENTITIES = "using-entities-here"
|
||||
USING_KEYBOARD = "using-keyboard-here"
|
||||
SO_COOL = "so-cool"
|
||||
|
||||
# Callback data to pass in 3rd level deeplinking
|
||||
KEYBOARD_CALLBACKDATA = "keyboard-callback-data"
|
||||
|
||||
|
||||
def start(update, context):
|
||||
def start(update: Update, context: CallbackContext) -> None:
|
||||
"""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)
|
||||
url = helpers.create_deep_linked_url(bot.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):
|
||||
def deep_linked_level_1(update: Update, context: CallbackContext) -> None:
|
||||
"""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."
|
||||
url = helpers.create_deep_linked_url(bot.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)
|
||||
InlineKeyboardButton(text="Continue here!", url=url)
|
||||
)
|
||||
update.message.reply_text(text, reply_markup=keyboard)
|
||||
|
||||
|
||||
def deep_linked_level_2(update, context):
|
||||
def deep_linked_level_2(update: Update, context: CallbackContext) -> None:
|
||||
"""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)
|
||||
url = helpers.create_deep_linked_url(bot.username, USING_ENTITIES)
|
||||
text = f"You can also mask the deep-linked URLs as links: [▶️ CLICK HERE]({url})."
|
||||
update.message.reply_text(text, parse_mode=ParseMode.MARKDOWN, disable_web_page_preview=True)
|
||||
|
||||
|
||||
def deep_linked_level_3(update, context):
|
||||
def deep_linked_level_3(update: Update, context: CallbackContext) -> None:
|
||||
"""Reached through the USING_ENTITIES payload"""
|
||||
update.message.reply_text(
|
||||
"It is also possible to make deep-linking using InlineKeyboardButtons.",
|
||||
reply_markup=InlineKeyboardMarkup(
|
||||
[[InlineKeyboardButton(text="Like this!", callback_data=KEYBOARD_CALLBACKDATA)]]
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def deep_link_level_3_callback(update: Update, context: CallbackContext) -> None:
|
||||
"""Answers CallbackQuery with deeplinking url."""
|
||||
bot = context.bot
|
||||
url = helpers.create_deep_linked_url(bot.username, USING_KEYBOARD)
|
||||
update.callback_query.answer(url=url)
|
||||
|
||||
|
||||
def deep_linked_level_4(update: Update, context: CallbackContext) -> None:
|
||||
"""Reached through the USING_KEYBOARD 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)
|
||||
update.message.reply_text(
|
||||
f"Congratulations! This is as deep as it gets 👏🏻\n\nThe payload was: {payload}"
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
"""Start the bot."""
|
||||
# Create the Updater and pass it your bot's token.
|
||||
updater = Updater("TOKEN", use_context=True)
|
||||
updater = Updater("TOKEN")
|
||||
|
||||
# Get the dispatcher to register handlers
|
||||
dp = updater.dispatcher
|
||||
dispatcher = 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)))
|
||||
dispatcher.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)))
|
||||
dispatcher.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))
|
||||
dispatcher.add_handler(
|
||||
CommandHandler("start", deep_linked_level_3, Filters.regex(USING_ENTITIES), pass_args=True)
|
||||
)
|
||||
|
||||
# Possible with inline keyboard buttons aswell
|
||||
dispatcher.add_handler(
|
||||
CommandHandler("start", deep_linked_level_4, Filters.regex(USING_KEYBOARD))
|
||||
)
|
||||
|
||||
# register callback handler for inline keyboard button
|
||||
dispatcher.add_handler(
|
||||
CallbackQueryHandler(deep_link_level_3_callback, pattern=KEYBOARD_CALLBACKDATA)
|
||||
)
|
||||
|
||||
# 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)
|
||||
dispatcher.add_handler(CommandHandler("start", start))
|
||||
|
||||
# Start the Bot
|
||||
updater.start_polling()
|
||||
@@ -115,5 +150,5 @@ def main():
|
||||
updater.idle()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
+56
-38
@@ -1,55 +1,73 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Simple Bot to reply to Telegram messages.
|
||||
# pylint: disable=W0613, C0116
|
||||
# type: ignore[union-attr]
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
This is built on the API wrapper, see echobot2.py to see the same example built
|
||||
on the telegram.ext bot framework.
|
||||
This program is dedicated to the public domain under the CC0 license.
|
||||
"""
|
||||
Simple Bot to reply to Telegram messages.
|
||||
|
||||
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:
|
||||
Basic Echobot example, repeats messages.
|
||||
Press Ctrl-C on the command line or send a signal to the process to stop the
|
||||
bot.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import telegram
|
||||
from telegram.error import NetworkError, Unauthorized
|
||||
from time import sleep
|
||||
|
||||
from telegram import Update
|
||||
from telegram.ext import Updater, CommandHandler, MessageHandler, Filters, CallbackContext
|
||||
|
||||
# Enable logging
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
update_id = None
|
||||
# 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: Update, context: CallbackContext) -> None:
|
||||
"""Send a message when the command /start is issued."""
|
||||
update.message.reply_text('Hi!')
|
||||
|
||||
|
||||
def help_command(update: Update, context: CallbackContext) -> None:
|
||||
"""Send a message when the command /help is issued."""
|
||||
update.message.reply_text('Help!')
|
||||
|
||||
|
||||
def echo(update: Update, context: CallbackContext) -> None:
|
||||
"""Echo the user message."""
|
||||
update.message.reply_text(update.message.text)
|
||||
|
||||
|
||||
def main():
|
||||
"""Run the bot."""
|
||||
global update_id
|
||||
# Telegram Bot Authorization Token
|
||||
bot = telegram.Bot('TOKEN')
|
||||
"""Start the bot."""
|
||||
# Create the Updater and pass it your bot's token.
|
||||
updater = Updater("TOKEN")
|
||||
|
||||
# get the first pending update_id, this is so we can skip over it in case
|
||||
# we get an "Unauthorized" exception.
|
||||
try:
|
||||
update_id = bot.get_updates()[0].update_id
|
||||
except IndexError:
|
||||
update_id = None
|
||||
# Get the dispatcher to register handlers
|
||||
dispatcher = updater.dispatcher
|
||||
|
||||
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
# on different commands - answer in Telegram
|
||||
dispatcher.add_handler(CommandHandler("start", start))
|
||||
dispatcher.add_handler(CommandHandler("help", help_command))
|
||||
|
||||
while True:
|
||||
try:
|
||||
echo(bot)
|
||||
except NetworkError:
|
||||
sleep(1)
|
||||
except Unauthorized:
|
||||
# The user has removed or blocked the bot.
|
||||
update_id += 1
|
||||
# on noncommand i.e message - echo the message on Telegram
|
||||
dispatcher.add_handler(MessageHandler(Filters.text & ~Filters.command, echo))
|
||||
|
||||
# Start the Bot
|
||||
updater.start_polling()
|
||||
|
||||
def echo(bot):
|
||||
"""Echo the message the user sent."""
|
||||
global update_id
|
||||
# Request updates after the last update_id
|
||||
for update in bot.get_updates(offset=update_id, timeout=10):
|
||||
update_id = update.update_id + 1
|
||||
|
||||
if update.message: # your bot can receive updates without messages
|
||||
# Reply to the message
|
||||
update.message.reply_text(update.message.text)
|
||||
# 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__':
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
"""
|
||||
Simple Bot to reply to Telegram messages.
|
||||
|
||||
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:
|
||||
Basic Echobot example, repeats messages.
|
||||
Press Ctrl-C on the command line or send a signal to the process to stop the
|
||||
bot.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from telegram.ext import Updater, CommandHandler, MessageHandler, Filters
|
||||
|
||||
# Enable logging
|
||||
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
level=logging.INFO)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# 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!')
|
||||
|
||||
|
||||
def help(update, context):
|
||||
"""Send a message when the command /help is issued."""
|
||||
update.message.reply_text('Help!')
|
||||
|
||||
|
||||
def echo(update, context):
|
||||
"""Echo the user message."""
|
||||
update.message.reply_text(update.message.text)
|
||||
|
||||
|
||||
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.
|
||||
# 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
|
||||
|
||||
# on different commands - answer in Telegram
|
||||
dp.add_handler(CommandHandler("start", start))
|
||||
dp.add_handler(CommandHandler("help", help))
|
||||
|
||||
# on noncommand i.e message - echo the message on Telegram
|
||||
dp.add_handler(MessageHandler(Filters.text, echo))
|
||||
|
||||
# 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()
|
||||
@@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env python
|
||||
# pylint: disable=W0613, C0116
|
||||
# type: ignore[union-attr]
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
"""
|
||||
This is a very simple example on how one could implement a custom error handler
|
||||
"""
|
||||
import html
|
||||
import json
|
||||
import logging
|
||||
import traceback
|
||||
|
||||
from telegram import Update, ParseMode
|
||||
from telegram.ext import Updater, CallbackContext, CommandHandler
|
||||
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# The token you got from @botfather when you created the bot
|
||||
BOT_TOKEN = "TOKEN"
|
||||
|
||||
# This can be your own ID, or one for a developer group/channel.
|
||||
# You can use the /start command of this bot to see your chat id.
|
||||
DEVELOPER_CHAT_ID = 123456789
|
||||
|
||||
|
||||
def error_handler(update: Update, context: CallbackContext) -> None:
|
||||
"""Log the error and send a telegram message to notify the developer."""
|
||||
# Log the error before we do anything else, so we can see it even if something breaks.
|
||||
logger.error(msg="Exception while handling an update:", exc_info=context.error)
|
||||
|
||||
# traceback.format_exception returns the usual python message about an exception, but as a
|
||||
# list of strings rather than a single string, so we have to join them together.
|
||||
tb_list = traceback.format_exception(None, context.error, context.error.__traceback__)
|
||||
tb_string = ''.join(tb_list)
|
||||
|
||||
# Build the message with some markup and additional information about what happened.
|
||||
# You might need to add some logic to deal with messages longer than the 4096 character limit.
|
||||
message = (
|
||||
f'An exception was raised while handling an update\n'
|
||||
f'<pre>update = {html.escape(json.dumps(update.to_dict(), indent=2, ensure_ascii=False))}'
|
||||
'</pre>\n\n'
|
||||
f'<pre>context.chat_data = {html.escape(str(context.chat_data))}</pre>\n\n'
|
||||
f'<pre>context.user_data = {html.escape(str(context.user_data))}</pre>\n\n'
|
||||
f'<pre>{html.escape(tb_string)}</pre>'
|
||||
)
|
||||
|
||||
# Finally, send the message
|
||||
context.bot.send_message(chat_id=DEVELOPER_CHAT_ID, text=message, parse_mode=ParseMode.HTML)
|
||||
|
||||
|
||||
def bad_command(update: Update, context: CallbackContext) -> None:
|
||||
"""Raise an error to trigger the error handler."""
|
||||
context.bot.wrong_method_name()
|
||||
|
||||
|
||||
def start(update: Update, context: CallbackContext) -> None:
|
||||
update.effective_message.reply_html(
|
||||
'Use /bad_command to cause an error.\n'
|
||||
f'Your chat id is <code>{update.effective_chat.id}</code>.'
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
# Create the Updater and pass it your bot's token.
|
||||
updater = Updater(BOT_TOKEN)
|
||||
|
||||
# Get the dispatcher to register handlers
|
||||
dispatcher = updater.dispatcher
|
||||
|
||||
# Register the commands...
|
||||
dispatcher.add_handler(CommandHandler('start', start))
|
||||
dispatcher.add_handler(CommandHandler('bad_command', bad_command))
|
||||
|
||||
# ...and the error handler
|
||||
dispatcher.add_error_handler(error_handler)
|
||||
|
||||
# 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()
|
||||
+25
-33
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# pylint: disable=W0613, C0116
|
||||
# type: ignore[union-attr]
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
"""
|
||||
@@ -15,78 +16,69 @@ bot.
|
||||
import logging
|
||||
from uuid import uuid4
|
||||
|
||||
from telegram import InlineQueryResultArticle, ParseMode, \
|
||||
InputTextMessageContent
|
||||
from telegram.ext import Updater, InlineQueryHandler, CommandHandler
|
||||
from telegram import InlineQueryResultArticle, ParseMode, InputTextMessageContent, Update
|
||||
from telegram.ext import Updater, InlineQueryHandler, CommandHandler, CallbackContext
|
||||
from telegram.utils.helpers import escape_markdown
|
||||
|
||||
# Enable logging
|
||||
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
level=logging.INFO)
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# 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):
|
||||
def start(update: Update, context: CallbackContext) -> None:
|
||||
"""Send a message when the command /start is issued."""
|
||||
update.message.reply_text('Hi!')
|
||||
|
||||
|
||||
def help(update, context):
|
||||
def help_command(update: Update, context: CallbackContext) -> None:
|
||||
"""Send a message when the command /help is issued."""
|
||||
update.message.reply_text('Help!')
|
||||
|
||||
|
||||
def inlinequery(update, context):
|
||||
def inlinequery(update: Update, context: CallbackContext) -> None:
|
||||
"""Handle the inline query."""
|
||||
query = update.inline_query.query
|
||||
results = [
|
||||
InlineQueryResultArticle(
|
||||
id=uuid4(),
|
||||
title="Caps",
|
||||
input_message_content=InputTextMessageContent(
|
||||
query.upper())),
|
||||
id=uuid4(), title="Caps", input_message_content=InputTextMessageContent(query.upper())
|
||||
),
|
||||
InlineQueryResultArticle(
|
||||
id=uuid4(),
|
||||
title="Bold",
|
||||
input_message_content=InputTextMessageContent(
|
||||
"*{}*".format(escape_markdown(query)),
|
||||
parse_mode=ParseMode.MARKDOWN)),
|
||||
f"*{escape_markdown(query)}*", parse_mode=ParseMode.MARKDOWN
|
||||
),
|
||||
),
|
||||
InlineQueryResultArticle(
|
||||
id=uuid4(),
|
||||
title="Italic",
|
||||
input_message_content=InputTextMessageContent(
|
||||
"_{}_".format(escape_markdown(query)),
|
||||
parse_mode=ParseMode.MARKDOWN))]
|
||||
f"_{escape_markdown(query)}_", parse_mode=ParseMode.MARKDOWN
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
update.inline_query.answer(results)
|
||||
|
||||
|
||||
def error(update, context):
|
||||
"""Log Errors caused by Updates."""
|
||||
logger.warning('Update "%s" caused error "%s"', update, context.error)
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
# 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)
|
||||
updater = Updater("TOKEN")
|
||||
|
||||
# Get the dispatcher to register handlers
|
||||
dp = updater.dispatcher
|
||||
dispatcher = updater.dispatcher
|
||||
|
||||
# on different commands - answer in Telegram
|
||||
dp.add_handler(CommandHandler("start", start))
|
||||
dp.add_handler(CommandHandler("help", help))
|
||||
dispatcher.add_handler(CommandHandler("start", start))
|
||||
dispatcher.add_handler(CommandHandler("help", help_command))
|
||||
|
||||
# on noncommand i.e message - echo the message on Telegram
|
||||
dp.add_handler(InlineQueryHandler(inlinequery))
|
||||
|
||||
# log all errors
|
||||
dp.add_error_handler(error)
|
||||
dispatcher.add_handler(InlineQueryHandler(inlinequery))
|
||||
|
||||
# Start the Bot
|
||||
updater.start_polling()
|
||||
|
||||
+24
-23
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# pylint: disable=W0613, C0116
|
||||
# type: ignore[union-attr]
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
"""
|
||||
@@ -7,50 +8,50 @@ Basic example for a bot that uses inline keyboards.
|
||||
"""
|
||||
import logging
|
||||
|
||||
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
|
||||
from telegram.ext import Updater, CommandHandler, CallbackQueryHandler
|
||||
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
|
||||
from telegram.ext import Updater, CommandHandler, CallbackQueryHandler, CallbackContext
|
||||
|
||||
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
level=logging.INFO)
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def start(update, context):
|
||||
keyboard = [[InlineKeyboardButton("Option 1", callback_data='1'),
|
||||
InlineKeyboardButton("Option 2", callback_data='2')],
|
||||
|
||||
[InlineKeyboardButton("Option 3", callback_data='3')]]
|
||||
def start(update: Update, context: CallbackContext) -> None:
|
||||
keyboard = [
|
||||
[
|
||||
InlineKeyboardButton("Option 1", callback_data='1'),
|
||||
InlineKeyboardButton("Option 2", callback_data='2'),
|
||||
],
|
||||
[InlineKeyboardButton("Option 3", callback_data='3')],
|
||||
]
|
||||
|
||||
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||
|
||||
update.message.reply_text('Please choose:', reply_markup=reply_markup)
|
||||
|
||||
|
||||
def button(update, context):
|
||||
def button(update: Update, context: CallbackContext) -> None:
|
||||
query = update.callback_query
|
||||
|
||||
query.edit_message_text(text="Selected option: {}".format(query.data))
|
||||
# CallbackQueries need to be answered, even if no notification to the user is needed
|
||||
# Some clients may have trouble otherwise. See https://core.telegram.org/bots/api#callbackquery
|
||||
query.answer()
|
||||
|
||||
query.edit_message_text(text=f"Selected option: {query.data}")
|
||||
|
||||
|
||||
def help(update, context):
|
||||
def help_command(update: Update, context: CallbackContext) -> None:
|
||||
update.message.reply_text("Use /start to test this bot.")
|
||||
|
||||
|
||||
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)
|
||||
updater = Updater("TOKEN")
|
||||
|
||||
updater.dispatcher.add_handler(CommandHandler('start', start))
|
||||
updater.dispatcher.add_handler(CallbackQueryHandler(button))
|
||||
updater.dispatcher.add_handler(CommandHandler('help', help))
|
||||
updater.dispatcher.add_error_handler(error)
|
||||
updater.dispatcher.add_handler(CommandHandler('help', help_command))
|
||||
|
||||
# Start the Bot
|
||||
updater.start_polling()
|
||||
|
||||
+87
-92
@@ -1,5 +1,8 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# pylint: disable=W0613, C0116
|
||||
# type: ignore[union-attr]
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
"""Simple inline keyboard bot with multiple CallbackQueryHandlers.
|
||||
|
||||
This Bot uses the Updater class to handle the bot.
|
||||
@@ -12,13 +15,20 @@ 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
|
||||
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
|
||||
from telegram.ext import (
|
||||
Updater,
|
||||
CommandHandler,
|
||||
CallbackQueryHandler,
|
||||
ConversationHandler,
|
||||
CallbackContext,
|
||||
)
|
||||
|
||||
# Enable logging
|
||||
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
level=logging.INFO)
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -28,7 +38,7 @@ FIRST, SECOND = range(2)
|
||||
ONE, TWO, THREE, FOUR = range(4)
|
||||
|
||||
|
||||
def start(update, context):
|
||||
def start(update: Update, context: CallbackContext) -> None:
|
||||
"""Send message on `/start`."""
|
||||
# Get user that sent /start and log his name
|
||||
user = update.message.from_user
|
||||
@@ -38,139 +48,123 @@ def start(update, context):
|
||||
# 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))]
|
||||
[
|
||||
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
|
||||
)
|
||||
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):
|
||||
def start_over(update: Update, context: CallbackContext) -> None:
|
||||
"""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
|
||||
# CallbackQueries need to be answered, even if no notification to the user is needed
|
||||
# Some clients may have trouble otherwise. See https://core.telegram.org/bots/api#callbackquery
|
||||
query.answer()
|
||||
keyboard = [
|
||||
[InlineKeyboardButton("1", callback_data=str(ONE)),
|
||||
InlineKeyboardButton("2", callback_data=str(TWO))]
|
||||
[
|
||||
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
|
||||
query.edit_message_text(text="Start handler, Choose a route", reply_markup=reply_markup)
|
||||
return FIRST
|
||||
|
||||
|
||||
def one(update: Update, context: CallbackContext) -> None:
|
||||
"""Show new choice of buttons"""
|
||||
query = update.callback_query
|
||||
query.answer()
|
||||
keyboard = [
|
||||
[
|
||||
InlineKeyboardButton("3", callback_data=str(THREE)),
|
||||
InlineKeyboardButton("4", callback_data=str(FOUR)),
|
||||
]
|
||||
]
|
||||
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||
query.edit_message_text(
|
||||
text="First CallbackQueryHandler, Choose a route", reply_markup=reply_markup
|
||||
)
|
||||
return FIRST
|
||||
|
||||
|
||||
def one(update, context):
|
||||
def two(update: Update, context: CallbackContext) -> None:
|
||||
"""Show new choice of buttons"""
|
||||
query = update.callback_query
|
||||
bot = context.bot
|
||||
query.answer()
|
||||
keyboard = [
|
||||
[InlineKeyboardButton("3", callback_data=str(THREE)),
|
||||
InlineKeyboardButton("4", callback_data=str(FOUR))]
|
||||
[
|
||||
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="First CallbackQueryHandler, Choose a route",
|
||||
reply_markup=reply_markup
|
||||
query.edit_message_text(
|
||||
text="Second CallbackQueryHandler, Choose a route", reply_markup=reply_markup
|
||||
)
|
||||
return FIRST
|
||||
|
||||
|
||||
def two(update, context):
|
||||
def three(update: Update, context: CallbackContext) -> None:
|
||||
"""Show new choice of buttons"""
|
||||
query = update.callback_query
|
||||
bot = context.bot
|
||||
query.answer()
|
||||
keyboard = [
|
||||
[InlineKeyboardButton("1", callback_data=str(ONE)),
|
||||
InlineKeyboardButton("3", callback_data=str(THREE))]
|
||||
[
|
||||
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="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
|
||||
query.edit_message_text(
|
||||
text="Third CallbackQueryHandler. Do want to start over?", reply_markup=reply_markup
|
||||
)
|
||||
# Transfer to conversation state `SECOND`
|
||||
return SECOND
|
||||
|
||||
|
||||
def four(update, context):
|
||||
def four(update: Update, context: CallbackContext) -> None:
|
||||
"""Show new choice of buttons"""
|
||||
query = update.callback_query
|
||||
bot = context.bot
|
||||
query.answer()
|
||||
keyboard = [
|
||||
[InlineKeyboardButton("2", callback_data=str(TWO)),
|
||||
InlineKeyboardButton("4", callback_data=str(FOUR))]
|
||||
[
|
||||
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
|
||||
query.edit_message_text(
|
||||
text="Fourth CallbackQueryHandler, Choose a route", reply_markup=reply_markup
|
||||
)
|
||||
return FIRST
|
||||
|
||||
|
||||
def end(update, context):
|
||||
def end(update: Update, context: CallbackContext) -> None:
|
||||
"""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!"
|
||||
)
|
||||
query.answer()
|
||||
query.edit_message_text(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)
|
||||
updater = Updater("TOKEN")
|
||||
|
||||
# Get the dispatcher to register handlers
|
||||
dp = updater.dispatcher
|
||||
dispatcher = updater.dispatcher
|
||||
|
||||
# Setup conversation handler with the states FIRST and SECOND
|
||||
# Use the pattern parameter to pass CallbackQueries with specific
|
||||
@@ -181,22 +175,23 @@ def main():
|
||||
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) + '$')]
|
||||
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)]
|
||||
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)
|
||||
dispatcher.add_handler(conv_handler)
|
||||
|
||||
# Start the Bot
|
||||
updater.start_polling()
|
||||
|
||||
+151
-122
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# pylint: disable=W0613, C0116
|
||||
# type: ignore[union-attr]
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
"""
|
||||
@@ -16,13 +17,21 @@ bot.
|
||||
|
||||
import logging
|
||||
|
||||
from telegram import (InlineKeyboardMarkup, InlineKeyboardButton)
|
||||
from telegram.ext import (Updater, CommandHandler, MessageHandler, Filters,
|
||||
ConversationHandler, CallbackQueryHandler)
|
||||
from telegram import InlineKeyboardMarkup, InlineKeyboardButton, Update
|
||||
from telegram.ext import (
|
||||
Updater,
|
||||
CommandHandler,
|
||||
MessageHandler,
|
||||
Filters,
|
||||
ConversationHandler,
|
||||
CallbackQueryHandler,
|
||||
CallbackContext,
|
||||
)
|
||||
|
||||
# Enable logging
|
||||
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
level=logging.INFO)
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -38,58 +47,78 @@ STOPPING, SHOWING = map(chr, range(8, 10))
|
||||
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))
|
||||
(
|
||||
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')
|
||||
return 'Father', 'Mother'
|
||||
return 'Brother', 'Sister'
|
||||
|
||||
|
||||
# Top level conversation callbacks
|
||||
def start(update, context):
|
||||
def start(update: Update, context: CallbackContext) -> None:
|
||||
"""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))
|
||||
]]
|
||||
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.answer()
|
||||
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(
|
||||
'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):
|
||||
def adding_self(update: Update, context: CallbackContext) -> None:
|
||||
"""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.answer()
|
||||
update.callback_query.edit_message_text(text=text, reply_markup=keyboard)
|
||||
|
||||
return DESCRIBING_SELF
|
||||
|
||||
|
||||
def show_data(update, context):
|
||||
def show_data(update: Update, context: CallbackContext) -> None:
|
||||
"""Pretty print gathered data."""
|
||||
|
||||
def prettyprint(user_data, level):
|
||||
people = user_data.get(level)
|
||||
if not people:
|
||||
@@ -98,41 +127,41 @@ def show_data(update, context):
|
||||
text = ''
|
||||
if level == SELF:
|
||||
for person in user_data[level]:
|
||||
text += '\nName: {0}, Age: {1}'.format(person.get(NAME, '-'), person.get(AGE, '-'))
|
||||
text += f"\nName: {person.get(NAME, '-')}, Age: {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, '-'))
|
||||
text += f"\n{gender}: Name: {person.get(NAME, '-')}, Age: {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)
|
||||
user_data = context.user_data
|
||||
text = 'Yourself:' + prettyprint(user_data, SELF)
|
||||
text += '\n\nParents:' + prettyprint(user_data, PARENTS)
|
||||
text += '\n\nChildren:' + prettyprint(user_data, CHILDREN)
|
||||
|
||||
buttons = [[
|
||||
InlineKeyboardButton(text='Back', callback_data=str(END))
|
||||
]]
|
||||
buttons = [[InlineKeyboardButton(text='Back', callback_data=str(END))]]
|
||||
keyboard = InlineKeyboardMarkup(buttons)
|
||||
|
||||
update.callback_query.answer()
|
||||
update.callback_query.edit_message_text(text=text, reply_markup=keyboard)
|
||||
ud[START_OVER] = True
|
||||
user_data[START_OVER] = True
|
||||
|
||||
return SHOWING
|
||||
|
||||
|
||||
def stop(update, context):
|
||||
def stop(update: Update, context: CallbackContext) -> None:
|
||||
"""End Conversation by command."""
|
||||
update.message.reply_text('Okay, bye.')
|
||||
|
||||
return END
|
||||
|
||||
|
||||
def end(update, context):
|
||||
def end(update: Update, context: CallbackContext) -> None:
|
||||
"""End conversation from InlineKeyboardButton."""
|
||||
update.callback_query.answer()
|
||||
|
||||
text = 'See you around!'
|
||||
update.callback_query.edit_message_text(text=text)
|
||||
|
||||
@@ -140,23 +169,28 @@ def end(update, context):
|
||||
|
||||
|
||||
# Second level conversation callbacks
|
||||
def select_level(update, context):
|
||||
def select_level(update: Update, context: CallbackContext) -> None:
|
||||
"""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))
|
||||
]]
|
||||
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.answer()
|
||||
update.callback_query.edit_message_text(text=text, reply_markup=keyboard)
|
||||
|
||||
return SELECTING_LEVEL
|
||||
|
||||
|
||||
def select_gender(update, context):
|
||||
def select_gender(update: Update, context: CallbackContext) -> None:
|
||||
"""Choose to add mother or father."""
|
||||
level = update.callback_query.data
|
||||
context.user_data[CURRENT_LEVEL] = level
|
||||
@@ -165,21 +199,25 @@ def select_gender(update, context):
|
||||
|
||||
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))
|
||||
]]
|
||||
|
||||
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.answer()
|
||||
update.callback_query.edit_message_text(text=text, reply_markup=keyboard)
|
||||
|
||||
return SELECTING_GENDER
|
||||
|
||||
|
||||
def end_second_level(update, context):
|
||||
def end_second_level(update: Update, context: CallbackContext) -> None:
|
||||
"""Return to top level conversation."""
|
||||
context.user_data[START_OVER] = True
|
||||
start(update, context)
|
||||
@@ -188,19 +226,23 @@ def end_second_level(update, context):
|
||||
|
||||
|
||||
# Third level callbacks
|
||||
def select_feature(update, context):
|
||||
def select_feature(update: Update, context: CallbackContext) -> None:
|
||||
"""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)),
|
||||
]]
|
||||
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.answer()
|
||||
update.callback_query.edit_message_text(text=text, reply_markup=keyboard)
|
||||
# But after we do that, we need to send a new message
|
||||
else:
|
||||
@@ -211,36 +253,38 @@ def select_feature(update, context):
|
||||
return SELECTING_FEATURE
|
||||
|
||||
|
||||
def ask_for_input(update, context):
|
||||
def ask_for_input(update: Update, context: CallbackContext) -> None:
|
||||
"""Prompt user to input data for selected feature."""
|
||||
context.user_data[CURRENT_FEATURE] = update.callback_query.data
|
||||
text = 'Okay, tell me.'
|
||||
|
||||
update.callback_query.answer()
|
||||
update.callback_query.edit_message_text(text=text)
|
||||
|
||||
return TYPING
|
||||
|
||||
|
||||
def save_input(update, context):
|
||||
def save_input(update: Update, context: CallbackContext) -> None:
|
||||
"""Save input for feature and return to feature selection."""
|
||||
ud = context.user_data
|
||||
ud[FEATURES][ud[CURRENT_FEATURE]] = update.message.text
|
||||
user_data = context.user_data
|
||||
user_data[FEATURES][user_data[CURRENT_FEATURE]] = update.message.text
|
||||
|
||||
ud[START_OVER] = True
|
||||
user_data[START_OVER] = True
|
||||
|
||||
return select_feature(update, context)
|
||||
|
||||
|
||||
def end_describing(update, context):
|
||||
def end_describing(update: Update, context: CallbackContext) -> None:
|
||||
"""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])
|
||||
user_data = context.user_data
|
||||
level = user_data[CURRENT_LEVEL]
|
||||
if not user_data.get(level):
|
||||
user_data[level] = []
|
||||
user_data[level].append(user_data[FEATURES])
|
||||
|
||||
# Print upper level menu
|
||||
if level == SELF:
|
||||
ud[START_OVER] = True
|
||||
user_data[START_OVER] = True
|
||||
start(update, context)
|
||||
else:
|
||||
select_level(update, context)
|
||||
@@ -248,70 +292,59 @@ def end_describing(update, context):
|
||||
return END
|
||||
|
||||
|
||||
def stop_nested(update, context):
|
||||
def stop_nested(update: Update, context: CallbackContext) -> None:
|
||||
"""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)
|
||||
updater = Updater("TOKEN")
|
||||
|
||||
# Get the dispatcher to register handlers
|
||||
dp = updater.dispatcher
|
||||
dispatcher = updater.dispatcher
|
||||
|
||||
# Set up third level ConversationHandler (collecting features)
|
||||
description_conv = ConversationHandler(
|
||||
entry_points=[CallbackQueryHandler(select_feature,
|
||||
pattern='^' + str(MALE) + '$|^' + str(FEMALE) + '$')],
|
||||
|
||||
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)],
|
||||
SELECTING_FEATURE: [
|
||||
CallbackQueryHandler(ask_for_input, pattern='^(?!' + str(END) + ').*$')
|
||||
],
|
||||
TYPING: [MessageHandler(Filters.text & ~Filters.command, save_input)],
|
||||
},
|
||||
|
||||
fallbacks=[
|
||||
CallbackQueryHandler(end_describing, pattern='^' + str(END) + '$'),
|
||||
CommandHandler('stop', stop_nested)
|
||||
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) + '$')],
|
||||
|
||||
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]
|
||||
SELECTING_LEVEL: [
|
||||
CallbackQueryHandler(select_gender, pattern=f'^{PARENTS}$|^{CHILDREN}$')
|
||||
],
|
||||
SELECTING_GENDER: [description_conv],
|
||||
},
|
||||
|
||||
fallbacks=[
|
||||
CallbackQueryHandler(show_data, pattern='^' + str(SHOWING) + '$'),
|
||||
CallbackQueryHandler(end_second_level, pattern='^' + str(END) + '$'),
|
||||
CommandHandler('stop', stop_nested)
|
||||
CommandHandler('stop', stop_nested),
|
||||
],
|
||||
|
||||
map_to_parent={
|
||||
# After showing data return to top level menu
|
||||
SHOWING: SHOWING,
|
||||
@@ -319,35 +352,31 @@ def main():
|
||||
END: SELECTING_ACTION,
|
||||
# End conversation alltogether
|
||||
STOPPING: END,
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
# Set up top level ConversationHandler (selecting action)
|
||||
# Because the states of the third level conversation map to the ones of the econd level
|
||||
# conversation, we need to make sure the top level conversation can also handle them
|
||||
selection_handlers = [
|
||||
add_member_conv,
|
||||
CallbackQueryHandler(show_data, pattern='^' + str(SHOWING) + '$'),
|
||||
CallbackQueryHandler(adding_self, pattern='^' + str(ADDING_SELF) + '$'),
|
||||
CallbackQueryHandler(end, pattern='^' + str(END) + '$'),
|
||||
]
|
||||
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) + '$'),
|
||||
],
|
||||
SELECTING_ACTION: selection_handlers,
|
||||
SELECTING_LEVEL: selection_handlers,
|
||||
DESCRIBING_SELF: [description_conv],
|
||||
STOPPING: [CommandHandler('start', start)],
|
||||
},
|
||||
|
||||
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)
|
||||
dispatcher.add_handler(conv_handler)
|
||||
|
||||
# Start the Bot
|
||||
updater.start_polling()
|
||||
|
||||
+38
-27
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# pylint: disable=W0613, C0116
|
||||
# type: ignore[union-attr]
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
"""
|
||||
@@ -12,16 +13,18 @@ See https://git.io/fAvYd for how to use Telegram Passport properly with python-t
|
||||
"""
|
||||
import logging
|
||||
|
||||
from telegram.ext import Updater, MessageHandler, Filters
|
||||
from telegram import Update
|
||||
from telegram.ext import Updater, MessageHandler, Filters, CallbackContext
|
||||
|
||||
# Enable logging
|
||||
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
level=logging.DEBUG)
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.DEBUG
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def msg(bot, update):
|
||||
def msg(update: Update, context: CallbackContext) -> None:
|
||||
# If we received any passport data
|
||||
passport_data = update.message.passport_data
|
||||
if passport_data:
|
||||
@@ -39,18 +42,28 @@ def msg(bot, update):
|
||||
print('Phone: ', data.phone_number)
|
||||
elif data.type == 'email':
|
||||
print('Email: ', data.email)
|
||||
if data.type in ('personal_details', 'passport', 'driver_license', 'identity_card',
|
||||
'internal_passport', 'address'):
|
||||
if data.type in (
|
||||
'personal_details',
|
||||
'passport',
|
||||
'driver_license',
|
||||
'identity_card',
|
||||
'internal_passport',
|
||||
'address',
|
||||
):
|
||||
print(data.type, data.data)
|
||||
if data.type in ('utility_bill', 'bank_statement', 'rental_agreement',
|
||||
'passport_registration', 'temporary_registration'):
|
||||
if data.type in (
|
||||
'utility_bill',
|
||||
'bank_statement',
|
||||
'rental_agreement',
|
||||
'passport_registration',
|
||||
'temporary_registration',
|
||||
):
|
||||
print(data.type, len(data.files), 'files')
|
||||
for file in data.files:
|
||||
actual_file = file.get_file()
|
||||
print(actual_file)
|
||||
actual_file.download()
|
||||
if data.type in ('passport', 'driver_license', 'identity_card',
|
||||
'internal_passport'):
|
||||
if data.type in ('passport', 'driver_license', 'identity_card', 'internal_passport'):
|
||||
if data.front_side:
|
||||
file = data.front_side.get_file()
|
||||
print(data.type, file)
|
||||
@@ -60,16 +73,22 @@ def msg(bot, update):
|
||||
file = data.reverse_side.get_file()
|
||||
print(data.type, file)
|
||||
file.download()
|
||||
if data.type in ('passport', 'driver_license', 'identity_card',
|
||||
'internal_passport'):
|
||||
if data.type in ('passport', 'driver_license', 'identity_card', 'internal_passport'):
|
||||
if data.selfie:
|
||||
file = data.selfie.get_file()
|
||||
print(data.type, file)
|
||||
file.download()
|
||||
if data.type in ('passport', 'driver_license', 'identity_card',
|
||||
'internal_passport', 'utility_bill', 'bank_statement',
|
||||
'rental_agreement', 'passport_registration',
|
||||
'temporary_registration'):
|
||||
if data.type in (
|
||||
'passport',
|
||||
'driver_license',
|
||||
'identity_card',
|
||||
'internal_passport',
|
||||
'utility_bill',
|
||||
'bank_statement',
|
||||
'rental_agreement',
|
||||
'passport_registration',
|
||||
'temporary_registration',
|
||||
):
|
||||
print(data.type, len(data.translation), 'translation')
|
||||
for file in data.translation:
|
||||
actual_file = file.get_file()
|
||||
@@ -77,24 +96,16 @@ def msg(bot, update):
|
||||
actual_file.download()
|
||||
|
||||
|
||||
def error(bot, update, error):
|
||||
"""Log Errors caused by Updates."""
|
||||
logger.warning('Update "%s" caused error "%s"', update, error)
|
||||
|
||||
|
||||
def main():
|
||||
"""Start the bot."""
|
||||
# Create the Updater and pass it your token and private key
|
||||
updater = Updater("TOKEN", private_key=open('private.key', 'rb').read())
|
||||
|
||||
# Get the dispatcher to register handlers
|
||||
dp = updater.dispatcher
|
||||
dispatcher = updater.dispatcher
|
||||
|
||||
# On messages that include passport data call msg
|
||||
dp.add_handler(MessageHandler(Filters.passport_data, msg))
|
||||
|
||||
# log all errors
|
||||
dp.add_error_handler(error)
|
||||
dispatcher.add_handler(MessageHandler(Filters.passport_data, msg))
|
||||
|
||||
# Start the Bot
|
||||
updater.start_polling()
|
||||
|
||||
+57
-46
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# pylint: disable=W0613, C0116
|
||||
# type: ignore[union-attr]
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
"""
|
||||
@@ -8,29 +9,32 @@ Basic example for a bot that can receive payment from user.
|
||||
|
||||
import logging
|
||||
|
||||
from telegram import (LabeledPrice, ShippingOption)
|
||||
from telegram.ext import (Updater, CommandHandler, MessageHandler,
|
||||
Filters, PreCheckoutQueryHandler, ShippingQueryHandler)
|
||||
from telegram import LabeledPrice, ShippingOption, Update
|
||||
from telegram.ext import (
|
||||
Updater,
|
||||
CommandHandler,
|
||||
MessageHandler,
|
||||
Filters,
|
||||
PreCheckoutQueryHandler,
|
||||
ShippingQueryHandler,
|
||||
CallbackContext,
|
||||
)
|
||||
|
||||
# Enable logging
|
||||
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
level=logging.INFO)
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def error(update, context):
|
||||
"""Log Errors caused by Updates."""
|
||||
logger.warning('Update "%s" caused error "%s"', update, context.error)
|
||||
|
||||
|
||||
def start_callback(update, context):
|
||||
def start_callback(update: Update, context: CallbackContext) -> None:
|
||||
msg = "Use /shipping to get an invoice for shipping-payment, "
|
||||
msg += "or /noshipping for an invoice without shipping."
|
||||
update.message.reply_text(msg)
|
||||
|
||||
|
||||
def start_with_shipping_callback(update, context):
|
||||
def start_with_shipping_callback(update: Update, context: CallbackContext) -> None:
|
||||
chat_id = update.message.chat_id
|
||||
title = "Payment Example"
|
||||
description = "Payment Example using python-telegram-bot"
|
||||
@@ -42,19 +46,30 @@ def start_with_shipping_callback(update, context):
|
||||
currency = "USD"
|
||||
# price in dollars
|
||||
price = 1
|
||||
# price * 100 so as to include 2 d.p.
|
||||
# price * 100 so as to include 2 decimal points
|
||||
# check https://core.telegram.org/bots/payments#supported-currencies for more details
|
||||
prices = [LabeledPrice("Test", price * 100)]
|
||||
|
||||
# optionally pass need_name=True, need_phone_number=True,
|
||||
# need_email=True, need_shipping_address=True, is_flexible=True
|
||||
context.bot.send_invoice(chat_id, title, description, payload,
|
||||
provider_token, start_parameter, currency, prices,
|
||||
need_name=True, need_phone_number=True,
|
||||
need_email=True, need_shipping_address=True, is_flexible=True)
|
||||
context.bot.send_invoice(
|
||||
chat_id,
|
||||
title,
|
||||
description,
|
||||
payload,
|
||||
provider_token,
|
||||
start_parameter,
|
||||
currency,
|
||||
prices,
|
||||
need_name=True,
|
||||
need_phone_number=True,
|
||||
need_email=True,
|
||||
need_shipping_address=True,
|
||||
is_flexible=True,
|
||||
)
|
||||
|
||||
|
||||
def start_without_shipping_callback(update, context):
|
||||
def start_without_shipping_callback(update: Update, context: CallbackContext) -> None:
|
||||
chat_id = update.message.chat_id
|
||||
title = "Payment Example"
|
||||
description = "Payment Example using python-telegram-bot"
|
||||
@@ -66,34 +81,35 @@ def start_without_shipping_callback(update, context):
|
||||
currency = "USD"
|
||||
# price in dollars
|
||||
price = 1
|
||||
# price * 100 so as to include 2 d.p.
|
||||
# price * 100 so as to include 2 decimal points
|
||||
prices = [LabeledPrice("Test", price * 100)]
|
||||
|
||||
# optionally pass need_name=True, need_phone_number=True,
|
||||
# need_email=True, need_shipping_address=True, is_flexible=True
|
||||
context.bot.send_invoice(chat_id, title, description, payload,
|
||||
provider_token, start_parameter, currency, prices)
|
||||
context.bot.send_invoice(
|
||||
chat_id, title, description, payload, provider_token, start_parameter, currency, prices
|
||||
)
|
||||
|
||||
|
||||
def shipping_callback(update, context):
|
||||
def shipping_callback(update: Update, context: CallbackContext) -> None:
|
||||
query = update.shipping_query
|
||||
# check the payload, is this from your bot?
|
||||
if query.invoice_payload != 'Custom-Payload':
|
||||
# answer False pre_checkout_query
|
||||
query.answer(ok=False, error_message="Something went wrong...")
|
||||
return
|
||||
else:
|
||||
options = list()
|
||||
# a single LabeledPrice
|
||||
options.append(ShippingOption('1', 'Shipping Option A', [LabeledPrice('A', 100)]))
|
||||
# an array of LabeledPrice objects
|
||||
price_list = [LabeledPrice('B1', 150), LabeledPrice('B2', 200)]
|
||||
options.append(ShippingOption('2', 'Shipping Option B', price_list))
|
||||
query.answer(ok=True, shipping_options=options)
|
||||
|
||||
options = list()
|
||||
# a single LabeledPrice
|
||||
options.append(ShippingOption('1', 'Shipping Option A', [LabeledPrice('A', 100)]))
|
||||
# an array of LabeledPrice objects
|
||||
price_list = [LabeledPrice('B1', 150), LabeledPrice('B2', 200)]
|
||||
options.append(ShippingOption('2', 'Shipping Option B', price_list))
|
||||
query.answer(ok=True, shipping_options=options)
|
||||
|
||||
|
||||
# after (optional) shipping, it's the pre-checkout
|
||||
def precheckout_callback(update, context):
|
||||
def precheckout_callback(update: Update, context: CallbackContext) -> None:
|
||||
query = update.pre_checkout_query
|
||||
# check the payload, is this from your bot?
|
||||
if query.invoice_payload != 'Custom-Payload':
|
||||
@@ -104,38 +120,33 @@ def precheckout_callback(update, context):
|
||||
|
||||
|
||||
# finally, after contacting the payment provider...
|
||||
def successful_payment_callback(update, context):
|
||||
def successful_payment_callback(update: Update, context: CallbackContext) -> None:
|
||||
# do something after successfully receiving payment?
|
||||
update.message.reply_text("Thank you for your payment!")
|
||||
|
||||
|
||||
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)
|
||||
updater = Updater("TOKEN")
|
||||
|
||||
# Get the dispatcher to register handlers
|
||||
dp = updater.dispatcher
|
||||
dispatcher = updater.dispatcher
|
||||
|
||||
# simple start function
|
||||
dp.add_handler(CommandHandler("start", start_callback))
|
||||
dispatcher.add_handler(CommandHandler("start", start_callback))
|
||||
|
||||
# Add command handler to start the payment invoice
|
||||
dp.add_handler(CommandHandler("shipping", start_with_shipping_callback))
|
||||
dp.add_handler(CommandHandler("noshipping", start_without_shipping_callback))
|
||||
dispatcher.add_handler(CommandHandler("shipping", start_with_shipping_callback))
|
||||
dispatcher.add_handler(CommandHandler("noshipping", start_without_shipping_callback))
|
||||
|
||||
# Optional handler if your product requires shipping
|
||||
dp.add_handler(ShippingQueryHandler(shipping_callback))
|
||||
dispatcher.add_handler(ShippingQueryHandler(shipping_callback))
|
||||
|
||||
# Pre-checkout handler to final check
|
||||
dp.add_handler(PreCheckoutQueryHandler(precheckout_callback))
|
||||
dispatcher.add_handler(PreCheckoutQueryHandler(precheckout_callback))
|
||||
|
||||
# Success! Notify your user!
|
||||
dp.add_handler(MessageHandler(Filters.successful_payment, successful_payment_callback))
|
||||
|
||||
# log all errors
|
||||
dp.add_error_handler(error)
|
||||
dispatcher.add_handler(MessageHandler(Filters.successful_payment, successful_payment_callback))
|
||||
|
||||
# Start the Bot
|
||||
updater.start_polling()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# pylint: disable=W0613, C0116, C0103
|
||||
# type: ignore[union-attr]
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
"""
|
||||
@@ -14,23 +15,34 @@ Press Ctrl-C on the command line or send a signal to the process to stop the
|
||||
bot.
|
||||
"""
|
||||
|
||||
from telegram import ReplyKeyboardMarkup
|
||||
from telegram.ext import (Updater, CommandHandler, MessageHandler, Filters,
|
||||
ConversationHandler, PicklePersistence)
|
||||
|
||||
import logging
|
||||
|
||||
from telegram import ReplyKeyboardMarkup, Update
|
||||
from telegram.ext import (
|
||||
Updater,
|
||||
CommandHandler,
|
||||
MessageHandler,
|
||||
Filters,
|
||||
ConversationHandler,
|
||||
PicklePersistence,
|
||||
CallbackContext,
|
||||
)
|
||||
|
||||
|
||||
# Enable logging
|
||||
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
level=logging.INFO)
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
CHOOSING, TYPING_REPLY, TYPING_CHOICE = range(3)
|
||||
|
||||
reply_keyboard = [['Age', 'Favourite colour'],
|
||||
['Number of siblings', 'Something else...'],
|
||||
['Done']]
|
||||
reply_keyboard = [
|
||||
['Age', 'Favourite colour'],
|
||||
['Number of siblings', 'Something else...'],
|
||||
['Done'],
|
||||
]
|
||||
markup = ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True)
|
||||
|
||||
|
||||
@@ -38,84 +50,87 @@ def facts_to_str(user_data):
|
||||
facts = list()
|
||||
|
||||
for key, value in user_data.items():
|
||||
facts.append('{} - {}'.format(key, value))
|
||||
facts.append(f'{key} - {value}')
|
||||
|
||||
return "\n".join(facts).join(['\n', '\n'])
|
||||
|
||||
|
||||
def start(update, context):
|
||||
def start(update: Update, context: CallbackContext) -> None:
|
||||
reply_text = "Hi! My name is Doctor Botter."
|
||||
if context.user_data:
|
||||
reply_text += " You already told me your {}. Why don't you tell me something more " \
|
||||
"about yourself? Or change enything I " \
|
||||
"already know.".format(", ".join(context.user_data.keys()))
|
||||
reply_text += (
|
||||
f" You already told me your {', '.join(context.user_data.keys())}. Why don't you "
|
||||
f"tell me something more about yourself? Or change anything I already know."
|
||||
)
|
||||
else:
|
||||
reply_text += " I will hold a more complex conversation with you. Why don't you tell me " \
|
||||
"something about yourself?"
|
||||
reply_text += (
|
||||
" I will hold a more complex conversation with you. Why don't you tell me "
|
||||
"something about yourself?"
|
||||
)
|
||||
update.message.reply_text(reply_text, reply_markup=markup)
|
||||
|
||||
return CHOOSING
|
||||
|
||||
|
||||
def regular_choice(update, context):
|
||||
def regular_choice(update: Update, context: CallbackContext) -> None:
|
||||
text = update.message.text.lower()
|
||||
context.user_data['choice'] = text
|
||||
if context.user_data.get(text):
|
||||
reply_text = 'Your {}, I already know the following ' \
|
||||
'about that: {}'.format(text, context.user_data[text])
|
||||
reply_text = (
|
||||
f'Your {text}, I already know the following about that: {context.user_data[text]}'
|
||||
)
|
||||
else:
|
||||
reply_text = 'Your {}? Yes, I would love to hear about that!'.format(text)
|
||||
reply_text = f'Your {text}? Yes, I would love to hear about that!'
|
||||
update.message.reply_text(reply_text)
|
||||
|
||||
return TYPING_REPLY
|
||||
|
||||
|
||||
def custom_choice(update, context):
|
||||
update.message.reply_text('Alright, please send me the category first, '
|
||||
'for example "Most impressive skill"')
|
||||
def custom_choice(update: Update, context: CallbackContext) -> None:
|
||||
update.message.reply_text(
|
||||
'Alright, please send me the category first, ' 'for example "Most impressive skill"'
|
||||
)
|
||||
|
||||
return TYPING_CHOICE
|
||||
|
||||
|
||||
def received_information(update, context):
|
||||
def received_information(update: Update, context: CallbackContext) -> None:
|
||||
text = update.message.text
|
||||
category = context.user_data['choice']
|
||||
context.user_data[category] = text.lower()
|
||||
del context.user_data['choice']
|
||||
|
||||
update.message.reply_text("Neat! Just so you know, this is what you already told me:"
|
||||
"{}"
|
||||
"You can tell me more, or change your opinion on "
|
||||
"something.".format(facts_to_str(context.user_data)),
|
||||
reply_markup=markup)
|
||||
update.message.reply_text(
|
||||
"Neat! Just so you know, this is what you already told me:"
|
||||
f"{facts_to_str(context.user_data)}"
|
||||
"You can tell me more, or change your opinion on "
|
||||
"something.",
|
||||
reply_markup=markup,
|
||||
)
|
||||
|
||||
return CHOOSING
|
||||
|
||||
|
||||
def show_data(update, context):
|
||||
update.message.reply_text("This is what you already told me:"
|
||||
"{}".format(facts_to_str(context.user_data)))
|
||||
def show_data(update: Update, context: CallbackContext) -> None:
|
||||
update.message.reply_text(
|
||||
f"This is what you already told me: {facts_to_str(context.user_data)}"
|
||||
)
|
||||
|
||||
|
||||
def done(update, context):
|
||||
def done(update: Update, context: CallbackContext) -> None:
|
||||
if 'choice' in context.user_data:
|
||||
del context.user_data['choice']
|
||||
|
||||
update.message.reply_text("I learned these facts about you:"
|
||||
"{}"
|
||||
"Until next time!".format(facts_to_str(context.user_data)))
|
||||
update.message.reply_text(
|
||||
"I learned these facts about you:" f"{facts_to_str(context.user_data)}" "Until 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.
|
||||
pp = PicklePersistence(filename='conversationbot')
|
||||
updater = Updater("TOKEN", persistence=pp, use_context=True)
|
||||
updater = Updater("TOKEN", persistence=pp)
|
||||
|
||||
# Get the dispatcher to register handlers
|
||||
dp = updater.dispatcher
|
||||
@@ -123,34 +138,34 @@ def main():
|
||||
# Add conversation handler with the states CHOOSING, TYPING_CHOICE and TYPING_REPLY
|
||||
conv_handler = ConversationHandler(
|
||||
entry_points=[CommandHandler('start', start)],
|
||||
|
||||
states={
|
||||
CHOOSING: [MessageHandler(Filters.regex('^(Age|Favourite colour|Number of siblings)$'),
|
||||
regular_choice),
|
||||
MessageHandler(Filters.regex('^Something else...$'),
|
||||
custom_choice),
|
||||
],
|
||||
|
||||
TYPING_CHOICE: [MessageHandler(Filters.text,
|
||||
regular_choice),
|
||||
],
|
||||
|
||||
TYPING_REPLY: [MessageHandler(Filters.text,
|
||||
received_information),
|
||||
],
|
||||
CHOOSING: [
|
||||
MessageHandler(
|
||||
Filters.regex('^(Age|Favourite colour|Number of siblings)$'), regular_choice
|
||||
),
|
||||
MessageHandler(Filters.regex('^Something else...$'), custom_choice),
|
||||
],
|
||||
TYPING_CHOICE: [
|
||||
MessageHandler(
|
||||
Filters.text & ~(Filters.command | Filters.regex('^Done$')), regular_choice
|
||||
)
|
||||
],
|
||||
TYPING_REPLY: [
|
||||
MessageHandler(
|
||||
Filters.text & ~(Filters.command | Filters.regex('^Done$')),
|
||||
received_information,
|
||||
)
|
||||
],
|
||||
},
|
||||
|
||||
fallbacks=[MessageHandler(Filters.regex('^Done$'), done)],
|
||||
name="my_conversation",
|
||||
persistent=True
|
||||
persistent=True,
|
||||
)
|
||||
|
||||
dp.add_handler(conv_handler)
|
||||
|
||||
show_data_handler = CommandHandler('show_data', show_data)
|
||||
dp.add_handler(show_data_handler)
|
||||
# log all errors
|
||||
dp.add_error_handler(error)
|
||||
|
||||
# Start the Bot
|
||||
updater.start_polling()
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
#!/usr/bin/env python
|
||||
# pylint: disable=W0613, C0116
|
||||
# type: ignore[union-attr]
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
"""
|
||||
Basic example for a bot that works with polls. Only 3 people are allowed to interact with each
|
||||
poll/quiz the bot generates. The preview command generates a closed poll/quiz, excatly like the
|
||||
one the user sends the bot
|
||||
"""
|
||||
import logging
|
||||
|
||||
from telegram import (
|
||||
Poll,
|
||||
ParseMode,
|
||||
KeyboardButton,
|
||||
KeyboardButtonPollType,
|
||||
ReplyKeyboardMarkup,
|
||||
ReplyKeyboardRemove,
|
||||
Update,
|
||||
)
|
||||
from telegram.ext import (
|
||||
Updater,
|
||||
CommandHandler,
|
||||
PollAnswerHandler,
|
||||
PollHandler,
|
||||
MessageHandler,
|
||||
Filters,
|
||||
CallbackContext,
|
||||
)
|
||||
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def start(update: Update, context: CallbackContext) -> None:
|
||||
"""Inform user about what this bot can do"""
|
||||
update.message.reply_text(
|
||||
'Please select /poll to get a Poll, /quiz to get a Quiz or /preview'
|
||||
' to generate a preview for your poll'
|
||||
)
|
||||
|
||||
|
||||
def poll(update: Update, context: CallbackContext) -> None:
|
||||
"""Sends a predefined poll"""
|
||||
questions = ["Good", "Really good", "Fantastic", "Great"]
|
||||
message = context.bot.send_poll(
|
||||
update.effective_chat.id,
|
||||
"How are you?",
|
||||
questions,
|
||||
is_anonymous=False,
|
||||
allows_multiple_answers=True,
|
||||
)
|
||||
# Save some info about the poll the bot_data for later use in receive_poll_answer
|
||||
payload = {
|
||||
message.poll.id: {
|
||||
"questions": questions,
|
||||
"message_id": message.message_id,
|
||||
"chat_id": update.effective_chat.id,
|
||||
"answers": 0,
|
||||
}
|
||||
}
|
||||
context.bot_data.update(payload)
|
||||
|
||||
|
||||
def receive_poll_answer(update: Update, context: CallbackContext) -> None:
|
||||
"""Summarize a users poll vote"""
|
||||
answer = update.poll_answer
|
||||
poll_id = answer.poll_id
|
||||
try:
|
||||
questions = context.bot_data[poll_id]["questions"]
|
||||
# this means this poll answer update is from an old poll, we can't do our answering then
|
||||
except KeyError:
|
||||
return
|
||||
selected_options = answer.option_ids
|
||||
answer_string = ""
|
||||
for question_id in selected_options:
|
||||
if question_id != selected_options[-1]:
|
||||
answer_string += questions[question_id] + " and "
|
||||
else:
|
||||
answer_string += questions[question_id]
|
||||
context.bot.send_message(
|
||||
context.bot_data[poll_id]["chat_id"],
|
||||
f"{update.effective_user.mention_html()} feels {answer_string}!",
|
||||
parse_mode=ParseMode.HTML,
|
||||
)
|
||||
context.bot_data[poll_id]["answers"] += 1
|
||||
# Close poll after three participants voted
|
||||
if context.bot_data[poll_id]["answers"] == 3:
|
||||
context.bot.stop_poll(
|
||||
context.bot_data[poll_id]["chat_id"], context.bot_data[poll_id]["message_id"]
|
||||
)
|
||||
|
||||
|
||||
def quiz(update: Update, context: CallbackContext) -> None:
|
||||
"""Send a predefined poll"""
|
||||
questions = ["1", "2", "4", "20"]
|
||||
message = update.effective_message.reply_poll(
|
||||
"How many eggs do you need for a cake?", questions, type=Poll.QUIZ, correct_option_id=2
|
||||
)
|
||||
# Save some info about the poll the bot_data for later use in receive_quiz_answer
|
||||
payload = {
|
||||
message.poll.id: {"chat_id": update.effective_chat.id, "message_id": message.message_id}
|
||||
}
|
||||
context.bot_data.update(payload)
|
||||
|
||||
|
||||
def receive_quiz_answer(update: Update, context: CallbackContext) -> None:
|
||||
"""Close quiz after three participants took it"""
|
||||
# the bot can receive closed poll updates we don't care about
|
||||
if update.poll.is_closed:
|
||||
return
|
||||
if update.poll.total_voter_count == 3:
|
||||
try:
|
||||
quiz_data = context.bot_data[update.poll.id]
|
||||
# this means this poll answer update is from an old poll, we can't stop it then
|
||||
except KeyError:
|
||||
return
|
||||
context.bot.stop_poll(quiz_data["chat_id"], quiz_data["message_id"])
|
||||
|
||||
|
||||
def preview(update: Update, context: CallbackContext) -> None:
|
||||
"""Ask user to create a poll and display a preview of it"""
|
||||
# using this without a type lets the user chooses what he wants (quiz or poll)
|
||||
button = [[KeyboardButton("Press me!", request_poll=KeyboardButtonPollType())]]
|
||||
message = "Press the button to let the bot generate a preview for your poll"
|
||||
# using one_time_keyboard to hide the keyboard
|
||||
update.effective_message.reply_text(
|
||||
message, reply_markup=ReplyKeyboardMarkup(button, one_time_keyboard=True)
|
||||
)
|
||||
|
||||
|
||||
def receive_poll(update: Update, context: CallbackContext) -> None:
|
||||
"""On receiving polls, reply to it by a closed poll copying the received poll"""
|
||||
actual_poll = update.effective_message.poll
|
||||
# Only need to set the question and options, since all other parameters don't matter for
|
||||
# a closed poll
|
||||
update.effective_message.reply_poll(
|
||||
question=actual_poll.question,
|
||||
options=[o.text for o in actual_poll.options],
|
||||
# with is_closed true, the poll/quiz is immediately closed
|
||||
is_closed=True,
|
||||
reply_markup=ReplyKeyboardRemove(),
|
||||
)
|
||||
|
||||
|
||||
def help_handler(update: Update, context: CallbackContext) -> None:
|
||||
"""Display a help message"""
|
||||
update.message.reply_text("Use /quiz, /poll or /preview to test this " "bot.")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
# Create the Updater and pass it your bot's token.
|
||||
updater = Updater("TOKEN")
|
||||
dispatcher = updater.dispatcher
|
||||
dispatcher.add_handler(CommandHandler('start', start))
|
||||
dispatcher.add_handler(CommandHandler('poll', poll))
|
||||
dispatcher.add_handler(PollAnswerHandler(receive_poll_answer))
|
||||
dispatcher.add_handler(CommandHandler('quiz', quiz))
|
||||
dispatcher.add_handler(PollHandler(receive_quiz_answer))
|
||||
dispatcher.add_handler(CommandHandler('preview', preview))
|
||||
dispatcher.add_handler(MessageHandler(Filters.poll, receive_poll))
|
||||
dispatcher.add_handler(CommandHandler('help', help_handler))
|
||||
|
||||
# Start the Bot
|
||||
updater.start_polling()
|
||||
|
||||
# Run the bot until the user presses Ctrl-C or the process receives SIGINT,
|
||||
# SIGTERM or SIGABRT
|
||||
updater.idle()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env python
|
||||
# pylint: disable=W0603
|
||||
"""Simple Bot to reply to Telegram messages.
|
||||
|
||||
This is built on the API wrapper, see echobot.py to see the same example built
|
||||
on the telegram.ext bot framework.
|
||||
This program is dedicated to the public domain under the CC0 license.
|
||||
"""
|
||||
import logging
|
||||
from typing import NoReturn
|
||||
from time import sleep
|
||||
|
||||
import telegram
|
||||
from telegram.error import NetworkError, Unauthorized
|
||||
|
||||
|
||||
UPDATE_ID = None
|
||||
|
||||
|
||||
def main() -> NoReturn:
|
||||
"""Run the bot."""
|
||||
global UPDATE_ID
|
||||
# Telegram Bot Authorization Token
|
||||
bot = telegram.Bot('TOKEN')
|
||||
|
||||
# get the first pending update_id, this is so we can skip over it in case
|
||||
# we get an "Unauthorized" exception.
|
||||
try:
|
||||
UPDATE_ID = bot.get_updates()[0].update_id
|
||||
except IndexError:
|
||||
UPDATE_ID = None
|
||||
|
||||
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
|
||||
while True:
|
||||
try:
|
||||
echo(bot)
|
||||
except NetworkError:
|
||||
sleep(1)
|
||||
except Unauthorized:
|
||||
# The user has removed or blocked the bot.
|
||||
UPDATE_ID += 1 # type: ignore[operator]
|
||||
|
||||
|
||||
def echo(bot: telegram.Bot) -> None:
|
||||
"""Echo the message the user sent."""
|
||||
global UPDATE_ID
|
||||
# Request updates after the last update_id
|
||||
for update in bot.get_updates(offset=UPDATE_ID, timeout=10):
|
||||
UPDATE_ID = update.update_id + 1
|
||||
|
||||
if update.message: # your bot can receive updates without messages
|
||||
if update.message.text: # not all messages contain text
|
||||
# Reply to the message
|
||||
update.message.reply_text(update.message.text)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
+36
-42
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# pylint: disable=W0613, C0116
|
||||
# type: ignore[union-attr]
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
"""
|
||||
@@ -20,18 +21,20 @@ bot.
|
||||
|
||||
import logging
|
||||
|
||||
from telegram.ext import Updater, CommandHandler
|
||||
from telegram import Update
|
||||
from telegram.ext import Updater, CommandHandler, CallbackContext
|
||||
|
||||
# Enable logging
|
||||
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
level=logging.INFO)
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# 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):
|
||||
def start(update: Update, context: CallbackContext) -> None:
|
||||
update.message.reply_text('Hi! Use /set <seconds> to set a timer')
|
||||
|
||||
|
||||
@@ -41,7 +44,17 @@ def alarm(context):
|
||||
context.bot.send_message(job.context, text='Beep!')
|
||||
|
||||
|
||||
def set_timer(update, context):
|
||||
def remove_job_if_exists(name, context):
|
||||
"""Remove job with given name. Returns whether job was removed."""
|
||||
current_jobs = context.job_queue.get_jobs_by_name(name)
|
||||
if not current_jobs:
|
||||
return False
|
||||
for job in current_jobs:
|
||||
job.schedule_removal()
|
||||
return True
|
||||
|
||||
|
||||
def set_timer(update: Update, context: CallbackContext) -> None:
|
||||
"""Add a job to the queue."""
|
||||
chat_id = update.message.chat_id
|
||||
try:
|
||||
@@ -51,58 +64,39 @@ def set_timer(update, context):
|
||||
update.message.reply_text('Sorry we can not go back to future!')
|
||||
return
|
||||
|
||||
# Add job to queue and stop current one if there is a timer already
|
||||
if 'job' in context.chat_data:
|
||||
old_job = context.chat_data['job']
|
||||
old_job.schedule_removal()
|
||||
new_job = context.job_queue.run_once(alarm, due, context=chat_id)
|
||||
context.chat_data['job'] = new_job
|
||||
job_removed = remove_job_if_exists(str(chat_id), context)
|
||||
context.job_queue.run_once(alarm, due, context=chat_id, name=str(chat_id))
|
||||
|
||||
update.message.reply_text('Timer successfully set!')
|
||||
text = 'Timer successfully set!'
|
||||
if job_removed:
|
||||
text += ' Old one was removed.'
|
||||
update.message.reply_text(text)
|
||||
|
||||
except (IndexError, ValueError):
|
||||
update.message.reply_text('Usage: /set <seconds>')
|
||||
|
||||
|
||||
def unset(update, context):
|
||||
def unset(update: Update, context: CallbackContext) -> None:
|
||||
"""Remove the job if the user changed their mind."""
|
||||
if 'job' not in context.chat_data:
|
||||
update.message.reply_text('You have no active timer')
|
||||
return
|
||||
|
||||
job = context.chat_data['job']
|
||||
job.schedule_removal()
|
||||
del context.chat_data['job']
|
||||
|
||||
update.message.reply_text('Timer successfully unset!')
|
||||
|
||||
|
||||
def error(update, context):
|
||||
"""Log Errors caused by Updates."""
|
||||
logger.warning('Update "%s" caused error "%s"', update, context.error)
|
||||
chat_id = update.message.chat_id
|
||||
job_removed = remove_job_if_exists(str(chat_id), context)
|
||||
text = 'Timer successfully cancelled!' if job_removed else 'You have no active timer.'
|
||||
update.message.reply_text(text)
|
||||
|
||||
|
||||
def main():
|
||||
"""Run bot."""
|
||||
# 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)
|
||||
updater = Updater("TOKEN")
|
||||
|
||||
# Get the dispatcher to register handlers
|
||||
dp = updater.dispatcher
|
||||
dispatcher = updater.dispatcher
|
||||
|
||||
# on different commands - answer in Telegram
|
||||
dp.add_handler(CommandHandler("start", start))
|
||||
dp.add_handler(CommandHandler("help", start))
|
||||
dp.add_handler(CommandHandler("set", set_timer,
|
||||
pass_args=True,
|
||||
pass_job_queue=True,
|
||||
pass_chat_data=True))
|
||||
dp.add_handler(CommandHandler("unset", unset, pass_chat_data=True))
|
||||
|
||||
# log all errors
|
||||
dp.add_error_handler(error)
|
||||
dispatcher.add_handler(CommandHandler("start", start))
|
||||
dispatcher.add_handler(CommandHandler("help", start))
|
||||
dispatcher.add_handler(CommandHandler("set", set_timer))
|
||||
dispatcher.add_handler(CommandHandler("unset", unset))
|
||||
|
||||
# Start the Bot
|
||||
updater.start_polling()
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
[tool.black]
|
||||
line-length = 99
|
||||
target-version = ['py36']
|
||||
skip-string-normalization = true
|
||||
|
||||
# We need to force-exclude the negated include pattern
|
||||
# so that pre-commit run --all-files does the correct thing
|
||||
# see https://github.com/psf/black/issues/1778
|
||||
force-exclude = '^(?!/(telegram|examples|tests)/).*\.py$'
|
||||
include = '(telegram|examples|tests)/.*\.py$'
|
||||
exclude = 'telegram/vendor'
|
||||
+12
-7
@@ -1,11 +1,16 @@
|
||||
flake8
|
||||
pep257
|
||||
pylint
|
||||
flaky
|
||||
yapf
|
||||
pre-commit
|
||||
beautifulsoup4
|
||||
# Make sure that the versions specified here match the pre-commit settings
|
||||
black==20.8b1
|
||||
flake8==3.8.4
|
||||
pylint==2.6.0
|
||||
mypy==0.790
|
||||
pyupgrade==2.7.4
|
||||
|
||||
pytest==4.2.0
|
||||
# Need older attrs version for pytest 4.2.0
|
||||
attrs==19.1.0
|
||||
|
||||
flaky
|
||||
beautifulsoup4
|
||||
pytest-timeout
|
||||
wheel
|
||||
attrs==19.1.0
|
||||
|
||||
+4
-2
@@ -1,4 +1,6 @@
|
||||
future>=0.16.0
|
||||
certifi
|
||||
tornado>=5.1
|
||||
cryptography
|
||||
# only telegram.ext: # Keep this line here; used in setup(-raw).py
|
||||
tornado>=5.1
|
||||
APScheduler==3.6.3
|
||||
pytz>=2018.6
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
"""The setup and build script for the python-telegram-bot-raw library."""
|
||||
|
||||
from setuptools import setup
|
||||
from setup import get_setup_kwargs
|
||||
|
||||
setup(**get_setup_kwargs(raw=True))
|
||||
@@ -1,6 +1,3 @@
|
||||
[wheel]
|
||||
universal = 1
|
||||
|
||||
[metadata]
|
||||
license_file = LICENSE.dual
|
||||
|
||||
@@ -15,12 +12,14 @@ upload-dir = docs/build/html
|
||||
[flake8]
|
||||
max-line-length = 99
|
||||
ignore = W503, W605
|
||||
exclude = setup.py, docs/source/conf.py
|
||||
extend-ignore = E203
|
||||
exclude = setup.py, setup-raw.py docs/source/conf.py, telegram/vendor
|
||||
|
||||
[yapf]
|
||||
based_on_style = google
|
||||
split_before_logical_operator = True
|
||||
column_limit = 99
|
||||
[pylint]
|
||||
ignore=vendor
|
||||
|
||||
[pylint.message-control]
|
||||
disable = C0330,R0801,R0913,R0904,R0903,R0902,W0511,C0116,C0115,W0703,R0914,R0914,C0302,R0912,R0915,R0401
|
||||
|
||||
[tool:pytest]
|
||||
testpaths = tests
|
||||
@@ -40,3 +39,28 @@ omit =
|
||||
telegram/__main__.py
|
||||
telegram/vendor/*
|
||||
|
||||
[coverage:report]
|
||||
exclude_lines =
|
||||
if TYPE_CHECKING:
|
||||
|
||||
[mypy]
|
||||
warn_unused_ignores = True
|
||||
warn_unused_configs = True
|
||||
disallow_untyped_defs = True
|
||||
disallow_incomplete_defs = True
|
||||
disallow_untyped_decorators = True
|
||||
show_error_codes = True
|
||||
|
||||
[mypy-telegram.vendor.*]
|
||||
ignore_errors = True
|
||||
|
||||
# Disable strict optional for telegram objects with class methods
|
||||
# We don't want to clutter the code with 'if self.bot is None: raise RuntimeError()'
|
||||
[mypy-telegram.callbackquery,telegram.chat,telegram.message,telegram.user,telegram.files.*,telegram.inline.inlinequery,telegram.payment.precheckoutquery,telegram.payment.shippingquery,telegram.passport.passportdata,telegram.passport.credentials,telegram.passport.passportfile,telegram.ext.filters]
|
||||
strict_optional = False
|
||||
|
||||
[mypy-urllib3.*]
|
||||
ignore_missing_imports = True
|
||||
|
||||
[mypy-apscheduler.*]
|
||||
ignore_missing_imports = True
|
||||
|
||||
@@ -3,58 +3,120 @@
|
||||
|
||||
import codecs
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
UPSTREAM_URLLIB3_FLAG = '--with-upstream-urllib3'
|
||||
|
||||
def requirements():
|
||||
|
||||
def get_requirements(raw=False):
|
||||
"""Build the requirements list for this project"""
|
||||
requirements_list = []
|
||||
|
||||
with open('requirements.txt') as requirements:
|
||||
for install in requirements:
|
||||
with open('requirements.txt') as reqs:
|
||||
for install in reqs:
|
||||
if install.startswith('# only telegram.ext:'):
|
||||
if raw:
|
||||
break
|
||||
continue
|
||||
requirements_list.append(install.strip())
|
||||
|
||||
return requirements_list
|
||||
|
||||
|
||||
packages = find_packages(exclude=['tests*'])
|
||||
def get_packages_requirements(raw=False):
|
||||
"""Build the package & requirements list for this project"""
|
||||
reqs = get_requirements(raw=raw)
|
||||
|
||||
exclude = ['tests*']
|
||||
if raw:
|
||||
exclude.append('telegram.ext*')
|
||||
|
||||
packs = find_packages(exclude=exclude)
|
||||
# Allow for a package install to not use the vendored urllib3
|
||||
if UPSTREAM_URLLIB3_FLAG in sys.argv:
|
||||
sys.argv.remove(UPSTREAM_URLLIB3_FLAG)
|
||||
reqs.append('urllib3 >= 1.19.1')
|
||||
packs = [x for x in packs if not x.startswith('telegram.vendor.ptb_urllib3')]
|
||||
|
||||
return packs, reqs
|
||||
|
||||
|
||||
def get_setup_kwargs(raw=False):
|
||||
"""Builds a dictionary of kwargs for the setup function"""
|
||||
packages, requirements = get_packages_requirements(raw=raw)
|
||||
|
||||
raw_ext = "-raw" if raw else ""
|
||||
readme = f'README{"_RAW" if raw else ""}.rst'
|
||||
|
||||
with codecs.open('README.rst', 'r', 'utf-8') as fd:
|
||||
fn = os.path.join('telegram', 'version.py')
|
||||
with open(fn) as fh:
|
||||
code = compile(fh.read(), fn, 'exec')
|
||||
exec(code)
|
||||
|
||||
setup(name='python-telegram-bot',
|
||||
version=__version__,
|
||||
author='Leandro Toledo',
|
||||
author_email='devs@python-telegram-bot.org',
|
||||
license='LGPLv3',
|
||||
url='https://python-telegram-bot.org/',
|
||||
keywords='python telegram bot api wrapper',
|
||||
description="We have made you a wrapper you can't refuse",
|
||||
long_description=fd.read(),
|
||||
packages=packages,
|
||||
install_requires=requirements(),
|
||||
extras_require={
|
||||
'json': 'ujson',
|
||||
'socks': 'PySocks'
|
||||
},
|
||||
include_package_data=True,
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)',
|
||||
'Operating System :: OS Independent',
|
||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||
'Topic :: Communications :: Chat',
|
||||
'Topic :: Internet',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 2',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7'
|
||||
],)
|
||||
with open(readme, 'r', encoding='utf-8') as fd:
|
||||
|
||||
kwargs = dict(
|
||||
script_name=f'setup{raw_ext}.py',
|
||||
name=f'python-telegram-bot{raw_ext}',
|
||||
version=locals()['__version__'],
|
||||
author='Leandro Toledo',
|
||||
author_email='devs@python-telegram-bot.org',
|
||||
license='LGPLv3',
|
||||
url='https://python-telegram-bot.org/',
|
||||
# Keywords supported by PyPI can be found at https://git.io/JtLIZ
|
||||
project_urls={
|
||||
"Documentation": "https://python-telegram-bot.readthedocs.io",
|
||||
"Bug Tracker": "https://github.com/python-telegram-bot/python-telegram-bot/issues",
|
||||
"Source Code": "https://github.com/python-telegram-bot/python-telegram-bot",
|
||||
"News": "https://t.me/pythontelegrambotchannel",
|
||||
"Changelog": "https://python-telegram-bot.readthedocs.io/en/stable/changelog.html",
|
||||
},
|
||||
download_url=f'https://pypi.org/project/python-telegram-bot{raw_ext}/',
|
||||
keywords='python telegram bot api wrapper',
|
||||
description="We have made you a wrapper you can't refuse",
|
||||
long_description=fd.read(),
|
||||
long_description_content_type='text/x-rst',
|
||||
packages=packages,
|
||||
|
||||
install_requires=requirements,
|
||||
extras_require={
|
||||
'json': 'ujson',
|
||||
'socks': 'PySocks'
|
||||
},
|
||||
include_package_data=True,
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)',
|
||||
'Operating System :: OS Independent',
|
||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||
'Topic :: Communications :: Chat',
|
||||
'Topic :: Internet',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
],
|
||||
python_requires='>=3.6'
|
||||
)
|
||||
|
||||
return kwargs
|
||||
|
||||
|
||||
def main():
|
||||
# If we're building, build ptb-raw as well
|
||||
if set(sys.argv[1:]) in [{'bdist_wheel'}, {'sdist'}, {'sdist', 'bdist_wheel'}]:
|
||||
args = ['python', 'setup-raw.py']
|
||||
args.extend(sys.argv[1:])
|
||||
subprocess.run(args, check=True, capture_output=True)
|
||||
|
||||
setup(**get_setup_kwargs(raw=False))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
+171
-54
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2018
|
||||
# Copyright (C) 2015-2021
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -19,9 +19,11 @@
|
||||
"""A library that provides a Python interface to the Telegram Bot API"""
|
||||
|
||||
from .base import TelegramObject
|
||||
from .botcommand import BotCommand
|
||||
from .user import User
|
||||
from .files.chatphoto import ChatPhoto
|
||||
from .chat import Chat
|
||||
from .chatlocation import ChatLocation
|
||||
from .chatmember import ChatMember
|
||||
from .chatpermissions import ChatPermissions
|
||||
from .files.photosize import PhotoSize
|
||||
@@ -36,8 +38,10 @@ from .files.location import Location
|
||||
from .files.venue import Venue
|
||||
from .files.videonote import VideoNote
|
||||
from .chataction import ChatAction
|
||||
from .dice import Dice
|
||||
from .userprofilephotos import UserProfilePhotos
|
||||
from .keyboardbutton import KeyboardButton
|
||||
from .keyboardbuttonpolltype import KeyboardButtonPollType
|
||||
from .replymarkup import ReplyMarkup
|
||||
from .replykeyboardmarkup import ReplyKeyboardMarkup
|
||||
from .replykeyboardremove import ReplyKeyboardRemove
|
||||
@@ -47,9 +51,11 @@ from .files.inputfile import InputFile
|
||||
from .files.file import File
|
||||
from .parsemode import ParseMode
|
||||
from .messageentity import MessageEntity
|
||||
from .messageid import MessageId
|
||||
from .games.game import Game
|
||||
from .poll import Poll, PollOption
|
||||
from .poll import Poll, PollOption, PollAnswer
|
||||
from .loginurl import LoginUrl
|
||||
from .proximityalerttriggered import ProximityAlertTriggered
|
||||
from .games.callbackgame import CallbackGame
|
||||
from .payment.shippingaddress import ShippingAddress
|
||||
from .payment.orderinfo import OrderInfo
|
||||
@@ -99,62 +105,173 @@ from .payment.shippingquery import ShippingQuery
|
||||
from .webhookinfo import WebhookInfo
|
||||
from .games.gamehighscore import GameHighScore
|
||||
from .update import Update
|
||||
from .files.inputmedia import (InputMedia, InputMediaVideo, InputMediaPhoto, InputMediaAnimation,
|
||||
InputMediaAudio, InputMediaDocument)
|
||||
from .files.inputmedia import (
|
||||
InputMedia,
|
||||
InputMediaVideo,
|
||||
InputMediaPhoto,
|
||||
InputMediaAnimation,
|
||||
InputMediaAudio,
|
||||
InputMediaDocument,
|
||||
)
|
||||
from .constants import (
|
||||
MAX_MESSAGE_LENGTH,
|
||||
MAX_CAPTION_LENGTH,
|
||||
SUPPORTED_WEBHOOK_PORTS,
|
||||
MAX_FILESIZE_DOWNLOAD,
|
||||
MAX_FILESIZE_UPLOAD,
|
||||
MAX_MESSAGES_PER_SECOND_PER_CHAT,
|
||||
MAX_MESSAGES_PER_SECOND,
|
||||
MAX_MESSAGES_PER_MINUTE_PER_GROUP,
|
||||
)
|
||||
from .passport.passportelementerrors import (
|
||||
PassportElementError,
|
||||
PassportElementErrorDataField,
|
||||
PassportElementErrorFile,
|
||||
PassportElementErrorFiles,
|
||||
PassportElementErrorFrontSide,
|
||||
PassportElementErrorReverseSide,
|
||||
PassportElementErrorSelfie,
|
||||
PassportElementErrorTranslationFile,
|
||||
PassportElementErrorTranslationFiles,
|
||||
PassportElementErrorUnspecified,
|
||||
)
|
||||
from .passport.credentials import (
|
||||
Credentials,
|
||||
DataCredentials,
|
||||
SecureData,
|
||||
FileCredentials,
|
||||
TelegramDecryptionError,
|
||||
)
|
||||
from .bot import Bot
|
||||
from .constants import (MAX_MESSAGE_LENGTH, MAX_CAPTION_LENGTH, SUPPORTED_WEBHOOK_PORTS,
|
||||
MAX_FILESIZE_DOWNLOAD, MAX_FILESIZE_UPLOAD,
|
||||
MAX_MESSAGES_PER_SECOND_PER_CHAT, MAX_MESSAGES_PER_SECOND,
|
||||
MAX_MESSAGES_PER_MINUTE_PER_GROUP)
|
||||
from .passport.passportelementerrors import (PassportElementError,
|
||||
PassportElementErrorDataField,
|
||||
PassportElementErrorFile,
|
||||
PassportElementErrorFiles,
|
||||
PassportElementErrorFrontSide,
|
||||
PassportElementErrorReverseSide,
|
||||
PassportElementErrorSelfie,
|
||||
PassportElementErrorTranslationFile,
|
||||
PassportElementErrorTranslationFiles,
|
||||
PassportElementErrorUnspecified)
|
||||
from .passport.credentials import (Credentials,
|
||||
DataCredentials,
|
||||
SecureData,
|
||||
FileCredentials,
|
||||
TelegramDecryptionError)
|
||||
from .version import __version__ # noqa: F401
|
||||
|
||||
__author__ = 'devs@python-telegram-bot.org'
|
||||
|
||||
__all__ = [
|
||||
'Audio', 'Bot', 'Chat', 'ChatMember', 'ChatPermissions', 'ChatAction', 'ChosenInlineResult',
|
||||
'CallbackQuery', 'Contact', 'Document', 'File', 'ForceReply', 'InlineKeyboardButton',
|
||||
'InlineKeyboardMarkup', 'InlineQuery', 'InlineQueryResult', 'InlineQueryResult',
|
||||
'InlineQueryResultArticle', 'InlineQueryResultAudio', 'InlineQueryResultCachedAudio',
|
||||
'InlineQueryResultCachedDocument', 'InlineQueryResultCachedGif',
|
||||
'InlineQueryResultCachedMpeg4Gif', 'InlineQueryResultCachedPhoto',
|
||||
'InlineQueryResultCachedSticker', 'InlineQueryResultCachedVideo',
|
||||
'InlineQueryResultCachedVoice', 'InlineQueryResultContact', 'InlineQueryResultDocument',
|
||||
'InlineQueryResultGif', 'InlineQueryResultLocation', 'InlineQueryResultMpeg4Gif',
|
||||
'InlineQueryResultPhoto', 'InlineQueryResultVenue', 'InlineQueryResultVideo',
|
||||
'InlineQueryResultVoice', 'InlineQueryResultGame', 'InputContactMessageContent', 'InputFile',
|
||||
'InputLocationMessageContent', 'InputMessageContent', 'InputTextMessageContent',
|
||||
'InputVenueMessageContent', 'KeyboardButton', 'Location', 'EncryptedCredentials',
|
||||
'PassportFile', 'EncryptedPassportElement', 'PassportData', 'Message', 'MessageEntity',
|
||||
'ParseMode', 'PhotoSize', 'ReplyKeyboardRemove', 'ReplyKeyboardMarkup', 'ReplyMarkup',
|
||||
'Sticker', 'TelegramError', 'TelegramObject', 'Update', 'User', 'UserProfilePhotos', 'Venue',
|
||||
'Video', 'Voice', 'MAX_MESSAGE_LENGTH', 'MAX_CAPTION_LENGTH', 'SUPPORTED_WEBHOOK_PORTS',
|
||||
'MAX_FILESIZE_DOWNLOAD', 'MAX_FILESIZE_UPLOAD', 'MAX_MESSAGES_PER_SECOND_PER_CHAT',
|
||||
'MAX_MESSAGES_PER_SECOND', 'MAX_MESSAGES_PER_MINUTE_PER_GROUP', 'WebhookInfo', 'Animation',
|
||||
'Game', 'GameHighScore', 'VideoNote', 'LabeledPrice', 'SuccessfulPayment', 'ShippingOption',
|
||||
'ShippingAddress', 'PreCheckoutQuery', 'OrderInfo', 'Invoice', 'ShippingQuery', 'ChatPhoto',
|
||||
'StickerSet', 'MaskPosition', 'CallbackGame', 'InputMedia', 'InputMediaPhoto',
|
||||
'InputMediaVideo', 'PassportElementError', 'PassportElementErrorFile',
|
||||
'PassportElementErrorReverseSide', 'PassportElementErrorFrontSide',
|
||||
'PassportElementErrorFiles', 'PassportElementErrorDataField', 'PassportElementErrorFile',
|
||||
'Credentials', 'DataCredentials', 'SecureData', 'FileCredentials', 'IdDocumentData',
|
||||
'PersonalDetails', 'ResidentialAddress', 'InputMediaVideo', 'InputMediaAnimation',
|
||||
'InputMediaAudio', 'InputMediaDocument', 'TelegramDecryptionError',
|
||||
'PassportElementErrorSelfie', 'PassportElementErrorTranslationFile',
|
||||
'PassportElementErrorTranslationFiles', 'PassportElementErrorUnspecified', 'Poll',
|
||||
'PollOption', 'LoginUrl'
|
||||
'Audio',
|
||||
'Bot',
|
||||
'Chat',
|
||||
'ChatMember',
|
||||
'ChatPermissions',
|
||||
'ChatAction',
|
||||
'ChosenInlineResult',
|
||||
'CallbackQuery',
|
||||
'Contact',
|
||||
'Document',
|
||||
'File',
|
||||
'ForceReply',
|
||||
'InlineKeyboardButton',
|
||||
'InlineKeyboardMarkup',
|
||||
'InlineQuery',
|
||||
'InlineQueryResult',
|
||||
'InlineQueryResult',
|
||||
'InlineQueryResultArticle',
|
||||
'InlineQueryResultAudio',
|
||||
'InlineQueryResultCachedAudio',
|
||||
'InlineQueryResultCachedDocument',
|
||||
'InlineQueryResultCachedGif',
|
||||
'InlineQueryResultCachedMpeg4Gif',
|
||||
'InlineQueryResultCachedPhoto',
|
||||
'InlineQueryResultCachedSticker',
|
||||
'InlineQueryResultCachedVideo',
|
||||
'InlineQueryResultCachedVoice',
|
||||
'InlineQueryResultContact',
|
||||
'InlineQueryResultDocument',
|
||||
'InlineQueryResultGif',
|
||||
'InlineQueryResultLocation',
|
||||
'InlineQueryResultMpeg4Gif',
|
||||
'InlineQueryResultPhoto',
|
||||
'InlineQueryResultVenue',
|
||||
'InlineQueryResultVideo',
|
||||
'InlineQueryResultVoice',
|
||||
'InlineQueryResultGame',
|
||||
'InputContactMessageContent',
|
||||
'InputFile',
|
||||
'InputLocationMessageContent',
|
||||
'InputMessageContent',
|
||||
'InputTextMessageContent',
|
||||
'InputVenueMessageContent',
|
||||
'Location',
|
||||
'ChatLocation',
|
||||
'ProximityAlertTriggered',
|
||||
'EncryptedCredentials',
|
||||
'PassportFile',
|
||||
'EncryptedPassportElement',
|
||||
'PassportData',
|
||||
'Message',
|
||||
'MessageEntity',
|
||||
'ParseMode',
|
||||
'PhotoSize',
|
||||
'ReplyKeyboardRemove',
|
||||
'ReplyKeyboardMarkup',
|
||||
'ReplyMarkup',
|
||||
'Sticker',
|
||||
'TelegramError',
|
||||
'TelegramObject',
|
||||
'Update',
|
||||
'User',
|
||||
'UserProfilePhotos',
|
||||
'Venue',
|
||||
'Video',
|
||||
'Voice',
|
||||
'MAX_MESSAGE_LENGTH',
|
||||
'MAX_CAPTION_LENGTH',
|
||||
'SUPPORTED_WEBHOOK_PORTS',
|
||||
'MAX_FILESIZE_DOWNLOAD',
|
||||
'MAX_FILESIZE_UPLOAD',
|
||||
'MAX_MESSAGES_PER_SECOND_PER_CHAT',
|
||||
'MAX_MESSAGES_PER_SECOND',
|
||||
'MAX_MESSAGES_PER_MINUTE_PER_GROUP',
|
||||
'WebhookInfo',
|
||||
'Animation',
|
||||
'Game',
|
||||
'GameHighScore',
|
||||
'VideoNote',
|
||||
'LabeledPrice',
|
||||
'SuccessfulPayment',
|
||||
'ShippingOption',
|
||||
'ShippingAddress',
|
||||
'PreCheckoutQuery',
|
||||
'OrderInfo',
|
||||
'Invoice',
|
||||
'ShippingQuery',
|
||||
'ChatPhoto',
|
||||
'StickerSet',
|
||||
'MaskPosition',
|
||||
'CallbackGame',
|
||||
'InputMedia',
|
||||
'InputMediaPhoto',
|
||||
'InputMediaVideo',
|
||||
'PassportElementError',
|
||||
'PassportElementErrorFile',
|
||||
'PassportElementErrorReverseSide',
|
||||
'PassportElementErrorFrontSide',
|
||||
'PassportElementErrorFiles',
|
||||
'PassportElementErrorDataField',
|
||||
'PassportElementErrorFile',
|
||||
'Credentials',
|
||||
'DataCredentials',
|
||||
'SecureData',
|
||||
'FileCredentials',
|
||||
'IdDocumentData',
|
||||
'PersonalDetails',
|
||||
'ResidentialAddress',
|
||||
'InputMediaVideo',
|
||||
'InputMediaAnimation',
|
||||
'InputMediaAudio',
|
||||
'InputMediaDocument',
|
||||
'TelegramDecryptionError',
|
||||
'PassportElementErrorSelfie',
|
||||
'PassportElementErrorTranslationFile',
|
||||
'PassportElementErrorTranslationFiles',
|
||||
'PassportElementErrorUnspecified',
|
||||
'Poll',
|
||||
'PollOption',
|
||||
'PollAnswer',
|
||||
'LoginUrl',
|
||||
'KeyboardButton',
|
||||
'KeyboardButtonPollType',
|
||||
'Dice',
|
||||
'BotCommand',
|
||||
'MessageId',
|
||||
]
|
||||
|
||||
+14
-14
@@ -1,7 +1,7 @@
|
||||
# !/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2018
|
||||
# Copyright (C) 2015-2021
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -16,35 +16,35 @@
|
||||
#
|
||||
# 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 sys
|
||||
# pylint: disable=E0401, C0114
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import Optional
|
||||
|
||||
import certifi
|
||||
import future
|
||||
|
||||
|
||||
from . import __version__ as telegram_ver
|
||||
|
||||
|
||||
def _git_revision():
|
||||
def _git_revision() -> Optional[str]:
|
||||
try:
|
||||
output = subprocess.check_output(["git", "describe", "--long", "--tags"],
|
||||
stderr=subprocess.STDOUT)
|
||||
output = subprocess.check_output(
|
||||
["git", "describe", "--long", "--tags"], stderr=subprocess.STDOUT
|
||||
)
|
||||
except (subprocess.SubprocessError, OSError):
|
||||
return None
|
||||
return output.decode().strip()
|
||||
|
||||
|
||||
def print_ver_info():
|
||||
def print_ver_info() -> None:
|
||||
git_revision = _git_revision()
|
||||
print('python-telegram-bot {0}'.format(telegram_ver) + (' ({0})'.format(git_revision)
|
||||
if git_revision else ''))
|
||||
print('certifi {0}'.format(certifi.__version__))
|
||||
print('future {0}'.format(future.__version__))
|
||||
print('Python {0}'.format(sys.version.replace('\n', ' ')))
|
||||
print(f'python-telegram-bot {telegram_ver}' + (f' ({git_revision})' if git_revision else ''))
|
||||
print(f'certifi {certifi.__version__}') # type: ignore[attr-defined]
|
||||
sys_version = sys.version.replace('\n', ' ')
|
||||
print(f'Python {sys_version}')
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
print_ver_info()
|
||||
|
||||
|
||||
|
||||
+50
-24
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2018
|
||||
# Copyright (C) 2015-2021
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -17,37 +17,58 @@
|
||||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""Base class for Telegram Objects."""
|
||||
|
||||
try:
|
||||
import ujson as json
|
||||
except ImportError:
|
||||
import json
|
||||
import json # type: ignore[no-redef]
|
||||
|
||||
from abc import ABCMeta
|
||||
import warnings
|
||||
from typing import TYPE_CHECKING, List, Optional, Tuple, Type, TypeVar
|
||||
|
||||
from telegram.utils.types import JSONDict
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Bot
|
||||
|
||||
TO = TypeVar('TO', bound='TelegramObject', covariant=True)
|
||||
|
||||
|
||||
class TelegramObject(object):
|
||||
class TelegramObject:
|
||||
"""Base class for most telegram objects."""
|
||||
|
||||
__metaclass__ = ABCMeta
|
||||
_id_attrs = ()
|
||||
_id_attrs: Tuple[object, ...] = ()
|
||||
|
||||
def __str__(self):
|
||||
def __str__(self) -> str:
|
||||
return str(self.to_dict())
|
||||
|
||||
def __getitem__(self, item):
|
||||
def __getitem__(self, item: str) -> object:
|
||||
return self.__dict__[item]
|
||||
|
||||
@staticmethod
|
||||
def parse_data(data: Optional[JSONDict]) -> Optional[JSONDict]:
|
||||
if not data:
|
||||
return None
|
||||
return data.copy()
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data, bot):
|
||||
def de_json(cls: Type[TO], data: Optional[JSONDict], bot: 'Bot') -> Optional[TO]:
|
||||
data = cls.parse_data(data)
|
||||
|
||||
if not data:
|
||||
return None
|
||||
|
||||
data = data.copy()
|
||||
if cls == TelegramObject:
|
||||
return cls()
|
||||
return cls(bot=bot, **data) # type: ignore[call-arg]
|
||||
|
||||
return data
|
||||
@classmethod
|
||||
def de_list(cls: Type[TO], data: Optional[List[JSONDict]], bot: 'Bot') -> List[Optional[TO]]:
|
||||
if not data:
|
||||
return []
|
||||
|
||||
def to_json(self):
|
||||
return [cls.de_json(d, bot) for d in data]
|
||||
|
||||
def to_json(self) -> str:
|
||||
"""
|
||||
Returns:
|
||||
:obj:`str`
|
||||
@@ -56,16 +77,11 @@ class TelegramObject(object):
|
||||
|
||||
return json.dumps(self.to_dict())
|
||||
|
||||
def to_dict(self):
|
||||
def to_dict(self) -> JSONDict:
|
||||
data = dict()
|
||||
|
||||
for key in iter(self.__dict__):
|
||||
if key in ('bot',
|
||||
'_id_attrs',
|
||||
'_credentials',
|
||||
'_decrypted_credentials',
|
||||
'_decrypted_data',
|
||||
'_decrypted_secret'):
|
||||
if key == 'bot' or key.startswith('_'):
|
||||
continue
|
||||
|
||||
value = self.__dict__[key]
|
||||
@@ -79,12 +95,22 @@ class TelegramObject(object):
|
||||
data['from'] = data.pop('from_user', None)
|
||||
return data
|
||||
|
||||
def __eq__(self, other):
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if isinstance(other, self.__class__):
|
||||
if self._id_attrs == ():
|
||||
warnings.warn(
|
||||
f"Objects of type {self.__class__.__name__} can not be meaningfully tested for"
|
||||
" equivalence."
|
||||
)
|
||||
if other._id_attrs == ():
|
||||
warnings.warn(
|
||||
f"Objects of type {other.__class__.__name__} can not be meaningfully tested"
|
||||
" for equivalence."
|
||||
)
|
||||
return self._id_attrs == other._id_attrs
|
||||
return super(TelegramObject, self).__eq__(other) # pylint: disable=no-member
|
||||
return super().__eq__(other) # pylint: disable=no-member
|
||||
|
||||
def __hash__(self):
|
||||
def __hash__(self) -> int:
|
||||
if self._id_attrs:
|
||||
return hash((self.__class__, self._id_attrs)) # pylint: disable=no-member
|
||||
return super(TelegramObject, self).__hash__()
|
||||
return super().__hash__()
|
||||
|
||||
+2715
-1339
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env python
|
||||
# pylint: disable=R0903
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2021
|
||||
# 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 Bot Command."""
|
||||
from typing import Any
|
||||
|
||||
from telegram import TelegramObject
|
||||
|
||||
|
||||
class BotCommand(TelegramObject):
|
||||
"""
|
||||
This object represents a bot command.
|
||||
|
||||
Objects of this class are comparable in terms of equality. Two objects of this class are
|
||||
considered equal, if their :attr:`command` and :attr:`description` are equal.
|
||||
|
||||
Args:
|
||||
command (:obj:`str`): Text of the command, 1-32 characters. Can contain only lowercase
|
||||
English letters, digits and underscores.
|
||||
description (:obj:`str`): Description of the command, 3-256 characters.
|
||||
|
||||
Attributes:
|
||||
command (:obj:`str`): Text of the command.
|
||||
description (:obj:`str`): Description of the command.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, command: str, description: str, **_kwargs: Any):
|
||||
self.command = command
|
||||
self.description = description
|
||||
|
||||
self._id_attrs = (self.command, self.description)
|
||||
+523
-81
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2018
|
||||
# Copyright (C) 2015-2021
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -16,9 +16,22 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
# pylint: disable=W0622
|
||||
"""This module contains an object that represents a Telegram CallbackQuery"""
|
||||
from typing import TYPE_CHECKING, Any, List, Optional, Union, Tuple, ClassVar
|
||||
|
||||
from telegram import TelegramObject, Message, User
|
||||
from telegram import Message, TelegramObject, User, Location, ReplyMarkup, constants
|
||||
from telegram.utils.types import JSONDict
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import (
|
||||
Bot,
|
||||
GameHighScore,
|
||||
InlineKeyboardMarkup,
|
||||
MessageId,
|
||||
InputMedia,
|
||||
MessageEntity,
|
||||
)
|
||||
|
||||
|
||||
class CallbackQuery(TelegramObject):
|
||||
@@ -29,58 +42,62 @@ class CallbackQuery(TelegramObject):
|
||||
:attr:`message` will be present. If the button was attached to a message sent via the bot (in
|
||||
inline mode), the field :attr:`inline_message_id` will be present.
|
||||
|
||||
Objects of this class are comparable in terms of equality. Two objects of this class are
|
||||
considered equal, if their :attr:`id` is equal.
|
||||
|
||||
Note:
|
||||
* In Python `from` is a reserved word, use `from_user` instead.
|
||||
* Exactly one of the fields :attr:`data` or :attr:`game_short_name` will be present.
|
||||
|
||||
Attributes:
|
||||
id (:obj:`str`): Unique identifier for this query.
|
||||
from_user (:class:`telegram.User`): Sender.
|
||||
message (:class:`telegram.Message`): Optional. Message with the callback button that
|
||||
originated the query.
|
||||
inline_message_id (:obj:`str`): Optional. Identifier of the message sent via the bot in
|
||||
inline mode, that originated the query.
|
||||
chat_instance (:obj:`str`): Optional. Global identifier, uniquely corresponding to the chat
|
||||
to which the message with the callback button was sent.
|
||||
data (:obj:`str`): Optional. Data associated with the callback button.
|
||||
game_short_name (:obj:`str`): Optional. Short name of a Game to be returned.
|
||||
* After the user presses an inline button, Telegram clients will display a progress bar
|
||||
until you call :attr:`answer`. It is, therefore, necessary to react
|
||||
by calling :attr:`telegram.Bot.answer_callback_query` even if no notification to the user
|
||||
is needed (e.g., without specifying any of the optional parameters).
|
||||
|
||||
Args:
|
||||
id (:obj:`str`): Unique identifier for this query.
|
||||
from_user (:class:`telegram.User`): Sender.
|
||||
chat_instance (:obj:`str`): Global identifier, uniquely corresponding to the chat to which
|
||||
the message with the callback button was sent. Useful for high scores in games.
|
||||
message (:class:`telegram.Message`, optional): Message with the callback button that
|
||||
originated the query. Note that message content and message date will not be available
|
||||
if the message is too old.
|
||||
inline_message_id (:obj:`str`, optional): Identifier of the message sent via the bot in
|
||||
inline mode, that originated the query.
|
||||
chat_instance (:obj:`str`, optional): Global identifier, uniquely corresponding to the chat
|
||||
to which the message with the callback button was sent. Useful for high scores in
|
||||
games.
|
||||
data (:obj:`str`, optional): Data associated with the callback button. Be aware that a bad
|
||||
client can send arbitrary data in this field.
|
||||
inline_message_id (:obj:`str`, optional): Identifier of the message sent via the bot in
|
||||
inline mode, that originated the query.
|
||||
game_short_name (:obj:`str`, optional): Short name of a Game to be returned, serves as
|
||||
the unique identifier for the game
|
||||
bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods.
|
||||
|
||||
Note:
|
||||
After the user presses an inline button, Telegram clients will display a progress bar
|
||||
until you call :attr:`answer`. It is, therefore, necessary to react
|
||||
by calling :attr:`telegram.Bot.answer_callback_query` even if no notification to the user
|
||||
is needed (e.g., without specifying any of the optional parameters).
|
||||
Attributes:
|
||||
id (:obj:`str`): Unique identifier for this query.
|
||||
from_user (:class:`telegram.User`): Sender.
|
||||
chat_instance (:obj:`str`): Global identifier, uniquely corresponding to the chat to which
|
||||
the message with the callback button was sent.
|
||||
message (:class:`telegram.Message`): Optional. Message with the callback button that
|
||||
originated the query.
|
||||
data (:obj:`str`): Optional. Data associated with the callback button.
|
||||
inline_message_id (:obj:`str`): Optional. Identifier of the message sent via the bot in
|
||||
inline mode, that originated the query.
|
||||
game_short_name (:obj:`str`): Optional. Short name of a Game to be returned.
|
||||
bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
id,
|
||||
from_user,
|
||||
chat_instance,
|
||||
message=None,
|
||||
data=None,
|
||||
inline_message_id=None,
|
||||
game_short_name=None,
|
||||
bot=None,
|
||||
**kwargs):
|
||||
def __init__(
|
||||
self,
|
||||
id: str, # pylint: disable=W0622
|
||||
from_user: User,
|
||||
chat_instance: str,
|
||||
message: Message = None,
|
||||
data: str = None,
|
||||
inline_message_id: str = None,
|
||||
game_short_name: str = None,
|
||||
bot: 'Bot' = None,
|
||||
**_kwargs: Any,
|
||||
):
|
||||
# Required
|
||||
self.id = id
|
||||
self.id = id # pylint: disable=C0103
|
||||
self.from_user = from_user
|
||||
self.chat_instance = chat_instance
|
||||
# Optionals
|
||||
@@ -94,59 +111,109 @@ class CallbackQuery(TelegramObject):
|
||||
self._id_attrs = (self.id,)
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data, bot):
|
||||
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['CallbackQuery']:
|
||||
data = cls.parse_data(data)
|
||||
|
||||
if not data:
|
||||
return None
|
||||
|
||||
data = super(CallbackQuery, cls).de_json(data, bot)
|
||||
|
||||
data['from_user'] = User.de_json(data.get('from'), bot)
|
||||
data['message'] = Message.de_json(data.get('message'), bot)
|
||||
|
||||
return cls(bot=bot, **data)
|
||||
|
||||
def answer(self, *args, **kwargs):
|
||||
def answer(
|
||||
self,
|
||||
text: str = None,
|
||||
show_alert: bool = False,
|
||||
url: str = None,
|
||||
cache_time: int = None,
|
||||
timeout: float = None,
|
||||
api_kwargs: JSONDict = None,
|
||||
) -> bool:
|
||||
"""Shortcut for::
|
||||
|
||||
bot.answer_callback_query(update.callback_query.id, *args, **kwargs)
|
||||
|
||||
For the documentation of the arguments, please see
|
||||
:meth:`telegram.Bot.answer_callback_query`.
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: On success, ``True`` is returned.
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
|
||||
"""
|
||||
return self.bot.answerCallbackQuery(self.id, *args, **kwargs)
|
||||
return self.bot.answer_callback_query(
|
||||
callback_query_id=self.id,
|
||||
text=text,
|
||||
show_alert=show_alert,
|
||||
url=url,
|
||||
cache_time=cache_time,
|
||||
timeout=timeout,
|
||||
api_kwargs=api_kwargs,
|
||||
)
|
||||
|
||||
def edit_message_text(self, text, *args, **kwargs):
|
||||
def edit_message_text(
|
||||
self,
|
||||
text: str,
|
||||
parse_mode: str = None,
|
||||
disable_web_page_preview: bool = None,
|
||||
reply_markup: 'InlineKeyboardMarkup' = None,
|
||||
timeout: float = None,
|
||||
api_kwargs: JSONDict = None,
|
||||
entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None,
|
||||
) -> Union[Message, bool]:
|
||||
"""Shortcut for either::
|
||||
|
||||
bot.edit_message_text(text, chat_id=update.callback_query.message.chat_id,
|
||||
message_id=update.callback_query.message.message_id,
|
||||
*args, **kwargs)
|
||||
update.callback_query.message.edit_text(text, *args, **kwargs)
|
||||
|
||||
or::
|
||||
|
||||
bot.edit_message_text(text, inline_message_id=update.callback_query.inline_message_id,
|
||||
*args, **kwargs)
|
||||
|
||||
For the documentation of the arguments, please see
|
||||
:meth:`telegram.Bot.edit_message_text`.
|
||||
|
||||
Returns:
|
||||
:class:`telegram.Message`: On success, if edited message is sent by the bot, the
|
||||
edited Message is returned, otherwise ``True`` is returned.
|
||||
edited Message is returned, otherwise :obj:`True` is returned.
|
||||
|
||||
"""
|
||||
if self.inline_message_id:
|
||||
return self.bot.edit_message_text(text, inline_message_id=self.inline_message_id,
|
||||
*args, **kwargs)
|
||||
else:
|
||||
return self.bot.edit_message_text(text, chat_id=self.message.chat_id,
|
||||
message_id=self.message.message_id, *args, **kwargs)
|
||||
return self.bot.edit_message_text(
|
||||
inline_message_id=self.inline_message_id,
|
||||
text=text,
|
||||
parse_mode=parse_mode,
|
||||
disable_web_page_preview=disable_web_page_preview,
|
||||
reply_markup=reply_markup,
|
||||
timeout=timeout,
|
||||
api_kwargs=api_kwargs,
|
||||
entities=entities,
|
||||
chat_id=None,
|
||||
message_id=None,
|
||||
)
|
||||
return self.message.edit_text(
|
||||
text=text,
|
||||
parse_mode=parse_mode,
|
||||
disable_web_page_preview=disable_web_page_preview,
|
||||
reply_markup=reply_markup,
|
||||
timeout=timeout,
|
||||
api_kwargs=api_kwargs,
|
||||
entities=entities,
|
||||
)
|
||||
|
||||
def edit_message_caption(self, caption, *args, **kwargs):
|
||||
def edit_message_caption(
|
||||
self,
|
||||
caption: str = None,
|
||||
reply_markup: 'InlineKeyboardMarkup' = None,
|
||||
timeout: float = None,
|
||||
parse_mode: str = None,
|
||||
api_kwargs: JSONDict = None,
|
||||
caption_entities: Union[List['MessageEntity'], Tuple['MessageEntity', ...]] = None,
|
||||
) -> Union[Message, bool]:
|
||||
"""Shortcut for either::
|
||||
|
||||
bot.edit_message_caption(caption=caption,
|
||||
chat_id=update.callback_query.message.chat_id,
|
||||
message_id=update.callback_query.message.message_id,
|
||||
*args, **kwargs)
|
||||
update.callback_query.message.edit_caption(caption, *args, **kwargs)
|
||||
|
||||
or::
|
||||
|
||||
@@ -154,45 +221,420 @@ class CallbackQuery(TelegramObject):
|
||||
inline_message_id=update.callback_query.inline_message_id,
|
||||
*args, **kwargs)
|
||||
|
||||
For the documentation of the arguments, please see
|
||||
:meth:`telegram.Bot.edit_message_caption`.
|
||||
|
||||
Returns:
|
||||
:class:`telegram.Message`: On success, if edited message is sent by the bot, the
|
||||
edited Message is returned, otherwise ``True`` is returned.
|
||||
edited Message is returned, otherwise :obj:`True` is returned.
|
||||
|
||||
"""
|
||||
if self.inline_message_id:
|
||||
return self.bot.edit_message_caption(caption=caption,
|
||||
inline_message_id=self.inline_message_id,
|
||||
*args, **kwargs)
|
||||
else:
|
||||
return self.bot.edit_message_caption(caption=caption, chat_id=self.message.chat_id,
|
||||
message_id=self.message.message_id,
|
||||
*args, **kwargs)
|
||||
return self.bot.edit_message_caption(
|
||||
caption=caption,
|
||||
inline_message_id=self.inline_message_id,
|
||||
reply_markup=reply_markup,
|
||||
timeout=timeout,
|
||||
parse_mode=parse_mode,
|
||||
api_kwargs=api_kwargs,
|
||||
caption_entities=caption_entities,
|
||||
chat_id=None,
|
||||
message_id=None,
|
||||
)
|
||||
return self.message.edit_caption(
|
||||
caption=caption,
|
||||
reply_markup=reply_markup,
|
||||
timeout=timeout,
|
||||
parse_mode=parse_mode,
|
||||
api_kwargs=api_kwargs,
|
||||
caption_entities=caption_entities,
|
||||
)
|
||||
|
||||
def edit_message_reply_markup(self, reply_markup, *args, **kwargs):
|
||||
def edit_message_reply_markup(
|
||||
self,
|
||||
reply_markup: Optional['InlineKeyboardMarkup'] = None,
|
||||
timeout: float = None,
|
||||
api_kwargs: JSONDict = None,
|
||||
) -> Union[Message, bool]:
|
||||
"""Shortcut for either::
|
||||
|
||||
bot.edit_message_replyMarkup(chat_id=update.callback_query.message.chat_id,
|
||||
message_id=update.callback_query.message.message_id,
|
||||
reply_markup=reply_markup,
|
||||
*args, **kwargs)
|
||||
update.callback_query.message.edit_reply_markup(
|
||||
reply_markup=reply_markup,
|
||||
*args,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
or::
|
||||
|
||||
bot.edit_message_reply_markup(inline_message_id=update.callback_query.inline_message_id,
|
||||
reply_markup=reply_markup,
|
||||
*args, **kwargs)
|
||||
bot.edit_message_reply_markup
|
||||
inline_message_id=update.callback_query.inline_message_id,
|
||||
reply_markup=reply_markup,
|
||||
*args,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
For the documentation of the arguments, please see
|
||||
:meth:`telegram.Bot.edit_message_reply_markup`.
|
||||
|
||||
Returns:
|
||||
:class:`telegram.Message`: On success, if edited message is sent by the bot, the
|
||||
edited Message is returned, otherwise ``True`` is returned.
|
||||
edited Message is returned, otherwise :obj:`True` is returned.
|
||||
|
||||
"""
|
||||
if self.inline_message_id:
|
||||
return self.bot.edit_message_reply_markup(reply_markup=reply_markup,
|
||||
inline_message_id=self.inline_message_id,
|
||||
*args, **kwargs)
|
||||
else:
|
||||
return self.bot.edit_message_reply_markup(reply_markup=reply_markup,
|
||||
chat_id=self.message.chat_id,
|
||||
message_id=self.message.message_id,
|
||||
*args, **kwargs)
|
||||
return self.bot.edit_message_reply_markup(
|
||||
reply_markup=reply_markup,
|
||||
inline_message_id=self.inline_message_id,
|
||||
timeout=timeout,
|
||||
api_kwargs=api_kwargs,
|
||||
chat_id=None,
|
||||
message_id=None,
|
||||
)
|
||||
return self.message.edit_reply_markup(
|
||||
reply_markup=reply_markup,
|
||||
timeout=timeout,
|
||||
api_kwargs=api_kwargs,
|
||||
)
|
||||
|
||||
def edit_message_media(
|
||||
self,
|
||||
media: 'InputMedia' = None,
|
||||
reply_markup: 'InlineKeyboardMarkup' = None,
|
||||
timeout: float = None,
|
||||
api_kwargs: JSONDict = None,
|
||||
) -> Union[Message, bool]:
|
||||
"""Shortcut for either::
|
||||
|
||||
update.callback_query.message.edit_media(*args, **kwargs)
|
||||
|
||||
or::
|
||||
|
||||
bot.edit_message_media(inline_message_id=update.callback_query.inline_message_id,
|
||||
*args, **kwargs)
|
||||
|
||||
For the documentation of the arguments, please see
|
||||
:meth:`telegram.Bot.edit_message_media`.
|
||||
|
||||
Returns:
|
||||
:class:`telegram.Message`: On success, if edited message is sent by the bot, the
|
||||
edited Message is returned, otherwise :obj:`True` is returned.
|
||||
|
||||
"""
|
||||
if self.inline_message_id:
|
||||
return self.bot.edit_message_media(
|
||||
inline_message_id=self.inline_message_id,
|
||||
media=media,
|
||||
reply_markup=reply_markup,
|
||||
timeout=timeout,
|
||||
api_kwargs=api_kwargs,
|
||||
chat_id=None,
|
||||
message_id=None,
|
||||
)
|
||||
return self.message.edit_media(
|
||||
media=media,
|
||||
reply_markup=reply_markup,
|
||||
timeout=timeout,
|
||||
api_kwargs=api_kwargs,
|
||||
)
|
||||
|
||||
def edit_message_live_location(
|
||||
self,
|
||||
latitude: float = None,
|
||||
longitude: float = None,
|
||||
location: Location = None,
|
||||
reply_markup: 'InlineKeyboardMarkup' = None,
|
||||
timeout: float = None,
|
||||
api_kwargs: JSONDict = None,
|
||||
horizontal_accuracy: float = None,
|
||||
heading: int = None,
|
||||
proximity_alert_radius: int = None,
|
||||
) -> Union[Message, bool]:
|
||||
"""Shortcut for either::
|
||||
|
||||
update.callback_query.message.edit_live_location(*args, **kwargs)
|
||||
|
||||
or::
|
||||
|
||||
bot.edit_message_live_location(
|
||||
inline_message_id=update.callback_query.inline_message_id,
|
||||
*args, **kwargs
|
||||
)
|
||||
|
||||
For the documentation of the arguments, please see
|
||||
:meth:`telegram.Bot.edit_message_live_location`.
|
||||
|
||||
Returns:
|
||||
:class:`telegram.Message`: On success, if edited message is sent by the bot, the
|
||||
edited Message is returned, otherwise :obj:`True` is returned.
|
||||
|
||||
"""
|
||||
if self.inline_message_id:
|
||||
return self.bot.edit_message_live_location(
|
||||
inline_message_id=self.inline_message_id,
|
||||
latitude=latitude,
|
||||
longitude=longitude,
|
||||
location=location,
|
||||
reply_markup=reply_markup,
|
||||
timeout=timeout,
|
||||
api_kwargs=api_kwargs,
|
||||
horizontal_accuracy=horizontal_accuracy,
|
||||
heading=heading,
|
||||
proximity_alert_radius=proximity_alert_radius,
|
||||
chat_id=None,
|
||||
message_id=None,
|
||||
)
|
||||
return self.message.edit_live_location(
|
||||
latitude=latitude,
|
||||
longitude=longitude,
|
||||
location=location,
|
||||
reply_markup=reply_markup,
|
||||
timeout=timeout,
|
||||
api_kwargs=api_kwargs,
|
||||
horizontal_accuracy=horizontal_accuracy,
|
||||
heading=heading,
|
||||
proximity_alert_radius=proximity_alert_radius,
|
||||
)
|
||||
|
||||
def stop_message_live_location(
|
||||
self,
|
||||
reply_markup: 'InlineKeyboardMarkup' = None,
|
||||
timeout: float = None,
|
||||
api_kwargs: JSONDict = None,
|
||||
) -> Union[Message, bool]:
|
||||
"""Shortcut for either::
|
||||
|
||||
update.callback_query.message.stop_live_location(*args, **kwargs)
|
||||
|
||||
or::
|
||||
|
||||
bot.stop_message_live_location(
|
||||
inline_message_id=update.callback_query.inline_message_id,
|
||||
*args, **kwargs
|
||||
)
|
||||
|
||||
For the documentation of the arguments, please see
|
||||
:meth:`telegram.Bot.stop_message_live_location`.
|
||||
|
||||
Returns:
|
||||
:class:`telegram.Message`: On success, if edited message is sent by the bot, the
|
||||
edited Message is returned, otherwise :obj:`True` is returned.
|
||||
|
||||
"""
|
||||
if self.inline_message_id:
|
||||
return self.bot.stop_message_live_location(
|
||||
inline_message_id=self.inline_message_id,
|
||||
reply_markup=reply_markup,
|
||||
timeout=timeout,
|
||||
api_kwargs=api_kwargs,
|
||||
chat_id=None,
|
||||
message_id=None,
|
||||
)
|
||||
return self.message.stop_live_location(
|
||||
reply_markup=reply_markup,
|
||||
timeout=timeout,
|
||||
api_kwargs=api_kwargs,
|
||||
)
|
||||
|
||||
def set_game_score(
|
||||
self,
|
||||
user_id: Union[int, str],
|
||||
score: int,
|
||||
force: bool = None,
|
||||
disable_edit_message: bool = None,
|
||||
timeout: float = None,
|
||||
api_kwargs: JSONDict = None,
|
||||
) -> Union[Message, bool]:
|
||||
"""Shortcut for either::
|
||||
|
||||
update.callback_query.message.set_game_score(*args, **kwargs)
|
||||
|
||||
or::
|
||||
|
||||
bot.set_game_score(inline_message_id=update.callback_query.inline_message_id,
|
||||
*args, **kwargs)
|
||||
|
||||
For the documentation of the arguments, please see
|
||||
:meth:`telegram.Bot.set_game_score`.
|
||||
|
||||
Returns:
|
||||
:class:`telegram.Message`: On success, if edited message is sent by the bot, the
|
||||
edited Message is returned, otherwise :obj:`True` is returned.
|
||||
|
||||
"""
|
||||
if self.inline_message_id:
|
||||
return self.bot.set_game_score(
|
||||
inline_message_id=self.inline_message_id,
|
||||
user_id=user_id,
|
||||
score=score,
|
||||
force=force,
|
||||
disable_edit_message=disable_edit_message,
|
||||
timeout=timeout,
|
||||
api_kwargs=api_kwargs,
|
||||
chat_id=None,
|
||||
message_id=None,
|
||||
)
|
||||
return self.message.set_game_score(
|
||||
user_id=user_id,
|
||||
score=score,
|
||||
force=force,
|
||||
disable_edit_message=disable_edit_message,
|
||||
timeout=timeout,
|
||||
api_kwargs=api_kwargs,
|
||||
)
|
||||
|
||||
def get_game_high_scores(
|
||||
self,
|
||||
user_id: Union[int, str],
|
||||
timeout: float = None,
|
||||
api_kwargs: JSONDict = None,
|
||||
) -> List['GameHighScore']:
|
||||
"""Shortcut for either::
|
||||
|
||||
update.callback_query.message.get_game_high_score(*args, **kwargs)
|
||||
|
||||
or::
|
||||
|
||||
bot.get_game_high_scores(inline_message_id=update.callback_query.inline_message_id,
|
||||
*args, **kwargs)
|
||||
|
||||
For the documentation of the arguments, please see
|
||||
:meth:`telegram.Bot.get_game_high_scores`.
|
||||
|
||||
Returns:
|
||||
List[:class:`telegram.GameHighScore`]
|
||||
|
||||
"""
|
||||
if self.inline_message_id:
|
||||
return self.bot.get_game_high_scores(
|
||||
inline_message_id=self.inline_message_id,
|
||||
user_id=user_id,
|
||||
timeout=timeout,
|
||||
api_kwargs=api_kwargs,
|
||||
chat_id=None,
|
||||
message_id=None,
|
||||
)
|
||||
return self.message.get_game_high_scores(
|
||||
user_id=user_id,
|
||||
timeout=timeout,
|
||||
api_kwargs=api_kwargs,
|
||||
)
|
||||
|
||||
def delete_message(
|
||||
self,
|
||||
timeout: float = None,
|
||||
api_kwargs: JSONDict = None,
|
||||
) -> bool:
|
||||
"""Shortcut for::
|
||||
|
||||
update.callback_query.message.delete(*args, **kwargs)
|
||||
|
||||
For the documentation of the arguments, please see
|
||||
:meth:`telegram.Bot.delete_message`.
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
|
||||
"""
|
||||
return self.message.delete(
|
||||
timeout=timeout,
|
||||
api_kwargs=api_kwargs,
|
||||
)
|
||||
|
||||
def pin_message(
|
||||
self,
|
||||
disable_notification: bool = None,
|
||||
timeout: float = None,
|
||||
api_kwargs: JSONDict = None,
|
||||
) -> bool:
|
||||
"""Shortcut for::
|
||||
|
||||
bot.pin_chat_message(chat_id=message.chat_id,
|
||||
message_id=message.message_id,
|
||||
*args,
|
||||
**kwargs)
|
||||
|
||||
For the documentation of the arguments, please see
|
||||
:meth:`telegram.Bot.pin_chat_message`.
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
|
||||
"""
|
||||
return self.message.pin(
|
||||
disable_notification=disable_notification,
|
||||
timeout=timeout,
|
||||
api_kwargs=api_kwargs,
|
||||
)
|
||||
|
||||
def unpin_message(
|
||||
self,
|
||||
timeout: float = None,
|
||||
api_kwargs: JSONDict = None,
|
||||
) -> bool:
|
||||
"""Shortcut for::
|
||||
|
||||
bot.unpin_chat_message(chat_id=message.chat_id,
|
||||
message_id=message.message_id,
|
||||
*args,
|
||||
**kwargs)
|
||||
|
||||
For the documentation of the arguments, please see
|
||||
:meth:`telegram.Bot.unpin_chat_message`.
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
|
||||
"""
|
||||
return self.message.unpin(
|
||||
timeout=timeout,
|
||||
api_kwargs=api_kwargs,
|
||||
)
|
||||
|
||||
def copy_message(
|
||||
self,
|
||||
chat_id: Union[int, str],
|
||||
caption: str = None,
|
||||
parse_mode: str = None,
|
||||
caption_entities: Union[Tuple['MessageEntity', ...], List['MessageEntity']] = None,
|
||||
disable_notification: bool = False,
|
||||
reply_to_message_id: Union[int, str] = None,
|
||||
allow_sending_without_reply: bool = False,
|
||||
reply_markup: ReplyMarkup = None,
|
||||
timeout: float = None,
|
||||
api_kwargs: JSONDict = None,
|
||||
) -> 'MessageId':
|
||||
"""Shortcut for::
|
||||
|
||||
update.callback_query.message.copy(
|
||||
chat_id,
|
||||
from_chat_id=update.message.chat_id,
|
||||
message_id=update.message.message_id,
|
||||
*args,
|
||||
**kwargs)
|
||||
|
||||
For the documentation of the arguments, please see
|
||||
:meth:`telegram.Bot.copy_message`.
|
||||
|
||||
Returns:
|
||||
:class:`telegram.MessageId`: On success, returns the MessageId of the sent message.
|
||||
|
||||
"""
|
||||
return self.message.copy(
|
||||
chat_id=chat_id,
|
||||
caption=caption,
|
||||
parse_mode=parse_mode,
|
||||
caption_entities=caption_entities,
|
||||
disable_notification=disable_notification,
|
||||
reply_to_message_id=reply_to_message_id,
|
||||
allow_sending_without_reply=allow_sending_without_reply,
|
||||
reply_markup=reply_markup,
|
||||
timeout=timeout,
|
||||
api_kwargs=api_kwargs,
|
||||
)
|
||||
|
||||
MAX_ANSWER_TEXT_LENGTH: ClassVar[int] = constants.MAX_ANSWER_CALLBACK_QUERY_TEXT_LENGTH
|
||||
"""
|
||||
:const:`telegram.constants.MAX_ANSWER_CALLBACK_QUERY_TEXT_LENGTH`
|
||||
|
||||
.. versionadded:: 13.2
|
||||
"""
|
||||
|
||||
+1158
-135
File diff suppressed because it is too large
Load Diff
+25
-23
@@ -2,7 +2,7 @@
|
||||
# pylint: disable=R0903
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2018
|
||||
# Copyright (C) 2015-2021
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -18,28 +18,30 @@
|
||||
# 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 ChatAction."""
|
||||
from typing import ClassVar
|
||||
from telegram import constants
|
||||
|
||||
|
||||
class ChatAction(object):
|
||||
"""Helper class to provide constants for different chatactions."""
|
||||
class ChatAction:
|
||||
"""Helper class to provide constants for different chat actions."""
|
||||
|
||||
FIND_LOCATION = 'find_location'
|
||||
""":obj:`str`: 'find_location'"""
|
||||
RECORD_AUDIO = 'record_audio'
|
||||
""":obj:`str`: 'record_audio'"""
|
||||
RECORD_VIDEO = 'record_video'
|
||||
""":obj:`str`: 'record_video'"""
|
||||
RECORD_VIDEO_NOTE = 'record_video_note'
|
||||
""":obj:`str`: 'record_video_note'"""
|
||||
TYPING = 'typing'
|
||||
""":obj:`str`: 'typing'"""
|
||||
UPLOAD_AUDIO = 'upload_audio'
|
||||
""":obj:`str`: 'upload_audio'"""
|
||||
UPLOAD_DOCUMENT = 'upload_document'
|
||||
""":obj:`str`: 'upload_document'"""
|
||||
UPLOAD_PHOTO = 'upload_photo'
|
||||
""":obj:`str`: 'upload_photo'"""
|
||||
UPLOAD_VIDEO = 'upload_video'
|
||||
""":obj:`str`: 'upload_video'"""
|
||||
UPLOAD_VIDEO_NOTE = 'upload_video_note'
|
||||
""":obj:`str`: 'upload_video_note'"""
|
||||
FIND_LOCATION: ClassVar[str] = constants.CHATACTION_FIND_LOCATION
|
||||
""":const:`telegram.constants.CHATACTION_FIND_LOCATION`"""
|
||||
RECORD_AUDIO: ClassVar[str] = constants.CHATACTION_RECORD_AUDIO
|
||||
""":const:`telegram.constants.CHATACTION_RECORD_AUDIO`"""
|
||||
RECORD_VIDEO: ClassVar[str] = constants.CHATACTION_RECORD_VIDEO
|
||||
""":const:`telegram.constants.CHATACTION_RECORD_VIDEO`"""
|
||||
RECORD_VIDEO_NOTE: ClassVar[str] = constants.CHATACTION_RECORD_VIDEO_NOTE
|
||||
""":const:`telegram.constants.CHATACTION_RECORD_VIDEO_NOTE`"""
|
||||
TYPING: ClassVar[str] = constants.CHATACTION_TYPING
|
||||
""":const:`telegram.constants.CHATACTION_TYPING`"""
|
||||
UPLOAD_AUDIO: ClassVar[str] = constants.CHATACTION_UPLOAD_AUDIO
|
||||
""":const:`telegram.constants.CHATACTION_UPLOAD_AUDIO`"""
|
||||
UPLOAD_DOCUMENT: ClassVar[str] = constants.CHATACTION_UPLOAD_DOCUMENT
|
||||
""":const:`telegram.constants.CHATACTION_UPLOAD_DOCUMENT`"""
|
||||
UPLOAD_PHOTO: ClassVar[str] = constants.CHATACTION_UPLOAD_PHOTO
|
||||
""":const:`telegram.constants.CHATACTION_UPLOAD_PHOTO`"""
|
||||
UPLOAD_VIDEO: ClassVar[str] = constants.CHATACTION_UPLOAD_VIDEO
|
||||
""":const:`telegram.constants.CHATACTION_UPLOAD_VIDEO`"""
|
||||
UPLOAD_VIDEO_NOTE: ClassVar[str] = constants.CHATACTION_UPLOAD_VIDEO_NOTE
|
||||
""":const:`telegram.constants.CHATACTION_UPLOAD_VIDEO_NOTE`"""
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2021
|
||||
# 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 location to which a chat is connected."""
|
||||
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
|
||||
from telegram import TelegramObject
|
||||
from telegram.utils.types import JSONDict
|
||||
|
||||
from .files.location import Location
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Bot
|
||||
|
||||
|
||||
class ChatLocation(TelegramObject):
|
||||
"""This object represents a location to which a chat is connected.
|
||||
|
||||
Objects of this class are comparable in terms of equality. Two objects of this class are
|
||||
considered equal, if their :attr:`location` is equal.
|
||||
|
||||
Args:
|
||||
location (:class:`telegram.Location`): The location to which the supergroup is connected.
|
||||
Can't be a live location.
|
||||
address (:obj:`str`): Location address; 1-64 characters, as defined by the chat owner
|
||||
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
||||
|
||||
Attributes:
|
||||
location (:class:`telegram.Location`): The location to which the supergroup is connected.
|
||||
address (:obj:`str`): Location address, as defined by the chat owner
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
location: Location,
|
||||
address: str,
|
||||
**_kwargs: Any,
|
||||
):
|
||||
self.location = location
|
||||
self.address = address
|
||||
|
||||
self._id_attrs = (self.location,)
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['ChatLocation']:
|
||||
data = cls.parse_data(data)
|
||||
|
||||
if not data:
|
||||
return None
|
||||
|
||||
data['location'] = Location.de_json(data.get('location'), bot)
|
||||
|
||||
return cls(bot=bot, **data)
|
||||
+108
-71
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2018
|
||||
# Copyright (C) 2015-2021
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -17,17 +17,72 @@
|
||||
# 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 ChatMember."""
|
||||
import datetime
|
||||
from typing import TYPE_CHECKING, Any, Optional, ClassVar
|
||||
|
||||
from telegram import User, TelegramObject
|
||||
from telegram.utils.helpers import to_timestamp, from_timestamp
|
||||
from telegram import TelegramObject, User, constants
|
||||
from telegram.utils.helpers import from_timestamp, to_timestamp
|
||||
from telegram.utils.types import JSONDict
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Bot
|
||||
|
||||
|
||||
class ChatMember(TelegramObject):
|
||||
"""This object contains information about one member of the chat.
|
||||
"""This object contains information about one member of a chat.
|
||||
|
||||
Objects of this class are comparable in terms of equality. Two objects of this class are
|
||||
considered equal, if their :attr:`user` and :attr:`status` are equal.
|
||||
|
||||
Args:
|
||||
user (:class:`telegram.User`): Information about the user.
|
||||
status (:obj:`str`): The member's status in the chat. Can be 'creator', 'administrator',
|
||||
'member', 'restricted', 'left' or 'kicked'.
|
||||
custom_title (:obj:`str`, optional): Owner and administrators only.
|
||||
Custom title for this user.
|
||||
is_anonymous (:obj:`bool`, optional): Owner and administrators only. :obj:`True`, if the
|
||||
user's presence in the chat is hidden.
|
||||
until_date (:class:`datetime.datetime`, optional): Restricted and kicked only. Date when
|
||||
restrictions will be lifted for this user.
|
||||
can_be_edited (:obj:`bool`, optional): Administrators only. :obj:`True`, if the bot is
|
||||
allowed to edit administrator privileges of that user.
|
||||
can_change_info (:obj:`bool`, optional): Administrators and restricted only. :obj:`True`,
|
||||
if the user can change the chat title, photo and other settings.
|
||||
can_post_messages (:obj:`bool`, optional): Administrators only. :obj:`True`, if the
|
||||
administrator can post in the channel, channels only.
|
||||
can_edit_messages (:obj:`bool`, optional): Administrators only. :obj:`True`, if the
|
||||
administrator can edit messages of other users and can pin messages; channels only.
|
||||
can_delete_messages (:obj:`bool`, optional): Administrators only. :obj:`True`, if the
|
||||
administrator can delete messages of other users.
|
||||
can_invite_users (:obj:`bool`, optional): Administrators and restricted only. :obj:`True`,
|
||||
if the user can invite new users to the chat.
|
||||
can_restrict_members (:obj:`bool`, optional): Administrators only. :obj:`True`, if the
|
||||
administrator can restrict, ban or unban chat members.
|
||||
can_pin_messages (:obj:`bool`, optional): Administrators and restricted only. :obj:`True`,
|
||||
if the user can pin messages, groups and supergroups only.
|
||||
can_promote_members (:obj:`bool`, optional): Administrators only. :obj:`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
|
||||
that were appointed by the user).
|
||||
is_member (:obj:`bool`, optional): Restricted only. :obj:`True`, if the user is a member of
|
||||
the chat at the moment of the request.
|
||||
can_send_messages (:obj:`bool`, optional): Restricted only. :obj:`True`, if the user can
|
||||
send text messages, contacts, locations and venues.
|
||||
can_send_media_messages (:obj:`bool`, optional): Restricted only. :obj:`True`, if the user
|
||||
can send audios, documents, photos, videos, video notes and voice notes.
|
||||
can_send_polls (:obj:`bool`, optional): Restricted only. :obj:`True`, if the user is
|
||||
allowed to send polls.
|
||||
can_send_other_messages (:obj:`bool`, optional): Restricted only. :obj:`True`, if the user
|
||||
can send animations, games, stickers and use inline bots.
|
||||
can_add_web_page_previews (:obj:`bool`, optional): Restricted only. :obj:`True`, if user
|
||||
may add web page previews to his messages.
|
||||
|
||||
Attributes:
|
||||
user (:class:`telegram.User`): Information about the user.
|
||||
status (:obj:`str`): The member's status in the chat.
|
||||
custom_title (:obj:`str`): Optional. Custom title for owner and administrators.
|
||||
is_anonymous (:obj:`bool`): Optional. :obj:`True`, if the user's presence in the chat is
|
||||
hidden.
|
||||
until_date (:class:`datetime.datetime`): Optional. Date when restrictions will be lifted
|
||||
for this user.
|
||||
can_be_edited (:obj:`bool`): Optional. If the bot is allowed to edit administrator
|
||||
@@ -45,83 +100,65 @@ class ChatMember(TelegramObject):
|
||||
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
|
||||
chat at the moment of the request.
|
||||
is_member (:obj:`bool`): Optional. Restricted only. :obj:`True`, if the user is a member of
|
||||
the chat at the moment of the request.
|
||||
can_send_messages (:obj:`bool`): Optional. If the user can send text messages, contacts,
|
||||
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
|
||||
can_send_polls (:obj:`bool`): Optional. :obj:`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
|
||||
messages, implies can_send_media_messages
|
||||
|
||||
Args:
|
||||
user (:class:`telegram.User`): Information about the user.
|
||||
status (:obj:`str`): The member's status in the chat. Can be 'creator', 'administrator',
|
||||
'member', 'restricted', 'left' or 'kicked'.
|
||||
until_date (:class:`datetime.datetime`, optional): Restricted and kicked only. Date when
|
||||
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 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 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 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
|
||||
that were appointed by the user).
|
||||
is_member (:obj:`bool`, optional): Restricted only. True, if the user is a member of the
|
||||
chat at the moment of the request.
|
||||
can_send_messages (:obj:`bool`, optional): Restricted only. True, if the user can send text
|
||||
messages, contacts, locations and venues.
|
||||
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
|
||||
web page previews to his messages, implies can_send_media_messages.
|
||||
|
||||
"""
|
||||
ADMINISTRATOR = 'administrator'
|
||||
""":obj:`str`: 'administrator'"""
|
||||
CREATOR = 'creator'
|
||||
""":obj:`str`: 'creator'"""
|
||||
KICKED = 'kicked'
|
||||
""":obj:`str`: 'kicked'"""
|
||||
LEFT = 'left'
|
||||
""":obj:`str`: 'left'"""
|
||||
MEMBER = 'member'
|
||||
""":obj:`str`: 'member'"""
|
||||
RESTRICTED = 'restricted'
|
||||
""":obj:`str`: 'restricted'"""
|
||||
|
||||
def __init__(self, user, status, until_date=None, can_be_edited=None,
|
||||
can_change_info=None, can_post_messages=None, can_edit_messages=None,
|
||||
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_polls=None, can_send_other_messages=None,
|
||||
can_add_web_page_previews=None, is_member=None, **kwargs):
|
||||
ADMINISTRATOR: ClassVar[str] = constants.CHATMEMBER_ADMINISTRATOR
|
||||
""":const:`telegram.constants.CHATMEMBER_ADMINISTRATOR`"""
|
||||
CREATOR: ClassVar[str] = constants.CHATMEMBER_CREATOR
|
||||
""":const:`telegram.constants.CHATMEMBER_CREATOR`"""
|
||||
KICKED: ClassVar[str] = constants.CHATMEMBER_KICKED
|
||||
""":const:`telegram.constants.CHATMEMBER_KICKED`"""
|
||||
LEFT: ClassVar[str] = constants.CHATMEMBER_LEFT
|
||||
""":const:`telegram.constants.CHATMEMBER_LEFT`"""
|
||||
MEMBER: ClassVar[str] = constants.CHATMEMBER_MEMBER
|
||||
""":const:`telegram.constants.CHATMEMBER_MEMBER`"""
|
||||
RESTRICTED: ClassVar[str] = constants.CHATMEMBER_RESTRICTED
|
||||
""":const:`telegram.constants.CHATMEMBER_RESTRICTED`"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
user: User,
|
||||
status: str,
|
||||
until_date: datetime.datetime = None,
|
||||
can_be_edited: bool = None,
|
||||
can_change_info: bool = None,
|
||||
can_post_messages: bool = None,
|
||||
can_edit_messages: bool = None,
|
||||
can_delete_messages: bool = None,
|
||||
can_invite_users: bool = None,
|
||||
can_restrict_members: bool = None,
|
||||
can_pin_messages: bool = None,
|
||||
can_promote_members: bool = None,
|
||||
can_send_messages: bool = None,
|
||||
can_send_media_messages: bool = None,
|
||||
can_send_polls: bool = None,
|
||||
can_send_other_messages: bool = None,
|
||||
can_add_web_page_previews: bool = None,
|
||||
is_member: bool = None,
|
||||
custom_title: str = None,
|
||||
is_anonymous: bool = None,
|
||||
**_kwargs: Any,
|
||||
):
|
||||
# Required
|
||||
self.user = user
|
||||
self.status = status
|
||||
|
||||
# Optionals
|
||||
self.custom_title = custom_title
|
||||
self.is_anonymous = is_anonymous
|
||||
self.until_date = until_date
|
||||
self.can_be_edited = can_be_edited
|
||||
self.can_change_info = can_change_info
|
||||
@@ -142,19 +179,19 @@ class ChatMember(TelegramObject):
|
||||
self._id_attrs = (self.user, self.status)
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data, bot):
|
||||
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['ChatMember']:
|
||||
data = cls.parse_data(data)
|
||||
|
||||
if not data:
|
||||
return None
|
||||
|
||||
data = super(ChatMember, cls).de_json(data, bot)
|
||||
|
||||
data['user'] = User.de_json(data.get('user'), bot)
|
||||
data['until_date'] = from_timestamp(data.get('until_date', None))
|
||||
|
||||
return cls(**data)
|
||||
|
||||
def to_dict(self):
|
||||
data = super(ChatMember, self).to_dict()
|
||||
def to_dict(self) -> JSONDict:
|
||||
data = super().to_dict()
|
||||
|
||||
data['until_date'] = to_timestamp(self.until_date)
|
||||
|
||||
|
||||
+69
-44
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2018
|
||||
# Copyright (C) 2015-2021
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -18,57 +18,78 @@
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains an object that represents a Telegram ChatPermission."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
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.
|
||||
Objects of this class are comparable in terms of equality. Two objects of this class are
|
||||
considered equal, if their :attr:`can_send_messages`, :attr:`can_send_media_messages`,
|
||||
:attr:`can_send_polls`, :attr:`can_send_other_messages`, :attr:`can_add_web_page_previews`,
|
||||
:attr:`can_change_info`, :attr:`can_invite_users` and :attr:`can_pin_message` are equal.
|
||||
|
||||
Note:
|
||||
Though not stated explicitly in the official docs, Telegram changes not only the
|
||||
permissions that are set, but also sets all the others to :obj:`False`. However, since not
|
||||
documented, this behaviour may change unbeknown to PTB.
|
||||
|
||||
Args:
|
||||
can_send_messages (:obj:`bool`, optional): True, if the user is allowed to send text
|
||||
can_send_messages (:obj:`bool`, optional): :obj:`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
|
||||
can_send_media_messages (:obj:`bool`, optional): :obj:`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
|
||||
can_send_polls (:obj:`bool`, optional): :obj:`True`, if the user is allowed to send polls,
|
||||
implies :attr:`can_send_messages`.
|
||||
can_send_other_messages (:obj:`bool`, optional): :obj:`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.
|
||||
can_add_web_page_previews (:obj:`bool`, optional): :obj:`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): :obj:`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): :obj:`True`, if the user is allowed to invite new
|
||||
users to the chat.
|
||||
can_pin_messages (:obj:`bool`, optional): :obj:`True`, if the user is allowed to pin
|
||||
messages. Ignored in public supergroups.
|
||||
|
||||
Attributes:
|
||||
can_send_messages (:obj:`bool`): Optional. :obj:`True`, if the user is allowed to send text
|
||||
messages, contacts, locations and venues.
|
||||
can_send_media_messages (:obj:`bool`): Optional. :obj:`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. :obj:`True`, if the user is allowed to send polls,
|
||||
implies :attr:`can_send_messages`.
|
||||
can_send_other_messages (:obj:`bool`): Optional. :obj:`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. :obj:`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. :obj:`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. :obj:`True`, if the user is allowed to invite
|
||||
new users to the chat.
|
||||
can_pin_messages (:obj:`bool`): Optional. :obj:`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):
|
||||
def __init__(
|
||||
self,
|
||||
can_send_messages: bool = None,
|
||||
can_send_media_messages: bool = None,
|
||||
can_send_polls: bool = None,
|
||||
can_send_other_messages: bool = None,
|
||||
can_add_web_page_previews: bool = None,
|
||||
can_change_info: bool = None,
|
||||
can_invite_users: bool = None,
|
||||
can_pin_messages: bool = None,
|
||||
**_kwargs: Any,
|
||||
):
|
||||
# Required
|
||||
self.can_send_messages = can_send_messages
|
||||
self.can_send_media_messages = can_send_media_messages
|
||||
@@ -79,9 +100,13 @@ class ChatPermissions(TelegramObject):
|
||||
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)
|
||||
self._id_attrs = (
|
||||
self.can_send_messages,
|
||||
self.can_send_media_messages,
|
||||
self.can_send_polls,
|
||||
self.can_send_other_messages,
|
||||
self.can_add_web_page_previews,
|
||||
self.can_change_info,
|
||||
self.can_invite_users,
|
||||
self.can_pin_messages,
|
||||
)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env python
|
||||
# pylint: disable=R0902,R0912,R0913
|
||||
# pylint: disable=R0902,R0913
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2018
|
||||
# Copyright (C) 2015-2021
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -19,7 +19,13 @@
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains an object that represents a Telegram ChosenInlineResult."""
|
||||
|
||||
from telegram import TelegramObject, User, Location
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
|
||||
from telegram import Location, TelegramObject, User
|
||||
from telegram.utils.types import JSONDict
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Bot
|
||||
|
||||
|
||||
class ChosenInlineResult(TelegramObject):
|
||||
@@ -27,15 +33,13 @@ class ChosenInlineResult(TelegramObject):
|
||||
Represents a result of an inline query that was chosen by the user and sent to their chat
|
||||
partner.
|
||||
|
||||
Note:
|
||||
In Python `from` is a reserved word, use `from_user` instead.
|
||||
Objects of this class are comparable in terms of equality. Two objects of this class are
|
||||
considered equal, if their :attr:`result_id` is equal.
|
||||
|
||||
Attributes:
|
||||
result_id (:obj:`str`): The unique identifier for the result that was chosen.
|
||||
from_user (:class:`telegram.User`): The user that chose the result.
|
||||
location (:class:`telegram.Location`): Optional. Sender location.
|
||||
inline_message_id (:obj:`str`): Optional. Identifier of the sent inline message.
|
||||
query (:obj:`str`): The query that was used to obtain the result.
|
||||
Note:
|
||||
* In Python `from` is a reserved word, use `from_user` instead.
|
||||
* It is necessary to enable inline feedback via `@Botfather <https://t.me/BotFather>`_ in
|
||||
order to receive these objects in updates.
|
||||
|
||||
Args:
|
||||
result_id (:obj:`str`): The unique identifier for the result that was chosen.
|
||||
@@ -48,15 +52,24 @@ class ChosenInlineResult(TelegramObject):
|
||||
query (:obj:`str`): The query that was used to obtain the result.
|
||||
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
||||
|
||||
Attributes:
|
||||
result_id (:obj:`str`): The unique identifier for the result that was chosen.
|
||||
from_user (:class:`telegram.User`): The user that chose the result.
|
||||
location (:class:`telegram.Location`): Optional. Sender location.
|
||||
inline_message_id (:obj:`str`): Optional. Identifier of the sent inline message.
|
||||
query (:obj:`str`): The query that was used to obtain the result.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
result_id,
|
||||
from_user,
|
||||
query,
|
||||
location=None,
|
||||
inline_message_id=None,
|
||||
**kwargs):
|
||||
def __init__(
|
||||
self,
|
||||
result_id: str,
|
||||
from_user: User,
|
||||
query: str,
|
||||
location: Location = None,
|
||||
inline_message_id: str = None,
|
||||
**_kwargs: Any,
|
||||
):
|
||||
# Required
|
||||
self.result_id = result_id
|
||||
self.from_user = from_user
|
||||
@@ -68,11 +81,12 @@ class ChosenInlineResult(TelegramObject):
|
||||
self._id_attrs = (self.result_id,)
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data, bot):
|
||||
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['ChosenInlineResult']:
|
||||
data = cls.parse_data(data)
|
||||
|
||||
if not data:
|
||||
return None
|
||||
|
||||
data = super(ChosenInlineResult, cls).de_json(data, bot)
|
||||
# Required
|
||||
data['from_user'] = User.de_json(data.pop('from'), bot)
|
||||
# Optionals
|
||||
|
||||
+196
-12
@@ -1,5 +1,5 @@
|
||||
# python-telegram-bot - a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2018
|
||||
# Copyright (C) 2015-2021
|
||||
# by the python-telegram-bot contributors <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -17,7 +17,8 @@
|
||||
"""Constants in the Telegram network.
|
||||
|
||||
The following constants were extracted from the
|
||||
`Telegram Bots FAQ <https://core.telegram.org/bots/faq>`_.
|
||||
`Telegram Bots FAQ <https://core.telegram.org/bots/faq>`_ and
|
||||
`Telegram Bots API <https://core.telegram.org/bots/api>`_.
|
||||
|
||||
Attributes:
|
||||
MAX_MESSAGE_LENGTH (:obj:`int`): 4096
|
||||
@@ -25,30 +26,213 @@ Attributes:
|
||||
SUPPORTED_WEBHOOK_PORTS (List[:obj:`int`]): [443, 80, 88, 8443]
|
||||
MAX_FILESIZE_DOWNLOAD (:obj:`int`): In bytes (20MB)
|
||||
MAX_FILESIZE_UPLOAD (:obj:`int`): In bytes (50MB)
|
||||
MAX_PHOTOSIZE_UPLOAD (:obj:`int`): In bytes (10MB)
|
||||
MAX_MESSAGES_PER_SECOND_PER_CHAT (:obj:`int`): `1`. Telegram may allow short bursts that go
|
||||
over this limit, but eventually you'll begin receiving 429 errors.
|
||||
MAX_MESSAGES_PER_SECOND (:obj:`int`): 30
|
||||
MAX_MESSAGES_PER_MINUTE_PER_GROUP (:obj:`int`): 20
|
||||
MAX_INLINE_QUERY_RESULTS (:obj:`int`): 50
|
||||
MAX_ANSWER_CALLBACK_QUERY_TEXT_LENGTH (:obj:`int`): 200
|
||||
|
||||
.. versionadded:: 13.2
|
||||
|
||||
The following constant have been found by experimentation:
|
||||
|
||||
Attributes:
|
||||
MAX_MESSAGE_ENTITIES (:obj:`int`): 100 (Beyond this cap telegram will simply ignore further
|
||||
formatting styles)
|
||||
ANONYMOUS_ADMIN_ID (:obj:`int`): ``1087968824`` (User id in groups for anonymous admin)
|
||||
SERVICE_CHAT_ID (:obj:`int`): ``777000`` (Telegram service chat, that also acts as sender of
|
||||
channel posts forwarded to discussion groups)
|
||||
|
||||
The following constants are related to specific classes and are also available
|
||||
as attributes of those classes:
|
||||
|
||||
:class:`telegram.Chat`:
|
||||
|
||||
Attributes:
|
||||
CHAT_PRIVATE (:obj:`str`): 'private'
|
||||
CHAT_GROUP (:obj:`str`): 'group'
|
||||
CHAT_SUPERGROUP (:obj:`str`): 'supergroup'
|
||||
CHAT_CHANNEL (:obj:`str`): 'channel'
|
||||
|
||||
:class:`telegram.ChatAction`:
|
||||
|
||||
Attributes:
|
||||
CHATACTION_FIND_LOCATION (:obj:`str`): 'find_location'
|
||||
CHATACTION_RECORD_AUDIO (:obj:`str`): 'record_audio'
|
||||
CHATACTION_RECORD_VIDEO (:obj:`str`): 'record_video'
|
||||
CHATACTION_RECORD_VIDEO_NOTE (:obj:`str`): 'record_video_note'
|
||||
CHATACTION_TYPING (:obj:`str`): 'typing'
|
||||
CHATACTION_UPLOAD_AUDIO (:obj:`str`): 'upload_audio'
|
||||
CHATACTION_UPLOAD_DOCUMENT (:obj:`str`): 'upload_document'
|
||||
CHATACTION_UPLOAD_PHOTO (:obj:`str`): 'upload_photo'
|
||||
CHATACTION_UPLOAD_VIDEO (:obj:`str`): 'upload_video'
|
||||
CHATACTION_UPLOAD_VIDEO_NOTE (:obj:`str`): 'upload_video_note'
|
||||
|
||||
:class:`telegram.ChatMember`:
|
||||
|
||||
Attributes:
|
||||
CHATMEMBER_ADMINISTRATOR (:obj:`str`): 'administrator'
|
||||
CHATMEMBER_CREATOR (:obj:`str`): 'creator'
|
||||
CHATMEMBER_KICKED (:obj:`str`): 'kicked'
|
||||
CHATMEMBER_LEFT (:obj:`str`): 'left'
|
||||
CHATMEMBER_MEMBER (:obj:`str`): 'member'
|
||||
CHATMEMBER_RESTRICTED (:obj:`str`): 'restricted'
|
||||
|
||||
:class:`telegram.Dice`:
|
||||
|
||||
Attributes:
|
||||
DICE_DICE (:obj:`str`): '🎲'
|
||||
DICE_DARTS (:obj:`str`): '🎯'
|
||||
DICE_BASKETBALL (:obj:`str`): '🏀'
|
||||
DICE_FOOTBALL (:obj:`str`): '⚽'
|
||||
DICE_SLOT_MACHINE (:obj:`str`): '🎰'
|
||||
DICE_ALL_EMOJI (List[:obj:`str`]): List of all supported base emoji.
|
||||
|
||||
:class:`telegram.MessageEntity`:
|
||||
|
||||
Attributes:
|
||||
MESSAGEENTITY_MENTION (:obj:`str`): 'mention'
|
||||
MESSAGEENTITY_HASHTAG (:obj:`str`): 'hashtag'
|
||||
MESSAGEENTITY_CASHTAG (:obj:`str`): 'cashtag'
|
||||
MESSAGEENTITY_PHONE_NUMBER (:obj:`str`): 'phone_number'
|
||||
MESSAGEENTITY_BOT_COMMAND (:obj:`str`): 'bot_command'
|
||||
MESSAGEENTITY_URL (:obj:`str`): 'url'
|
||||
MESSAGEENTITY_EMAIL (:obj:`str`): 'email'
|
||||
MESSAGEENTITY_BOLD (:obj:`str`): 'bold'
|
||||
MESSAGEENTITY_ITALIC (:obj:`str`): 'italic'
|
||||
MESSAGEENTITY_CODE (:obj:`str`): 'code'
|
||||
MESSAGEENTITY_PRE (:obj:`str`): 'pre'
|
||||
MESSAGEENTITY_TEXT_LINK (:obj:`str`): 'text_link'
|
||||
MESSAGEENTITY_TEXT_MENTION (:obj:`str`): 'text_mention'
|
||||
MESSAGEENTITY_UNDERLINE (:obj:`str`): 'underline'
|
||||
MESSAGEENTITY_STRIKETHROUGH (:obj:`str`): 'strikethrough'
|
||||
MESSAGEENTITY_ALL_TYPES (List[:obj:`str`]): List of all the types of message entity.
|
||||
|
||||
:class:`telegram.ParseMode`:
|
||||
|
||||
Attributes:
|
||||
PARSEMODE_MARKDOWN (:obj:`str`): 'Markdown'
|
||||
PARSEMODE_MARKDOWN_V2 (:obj:`str`): 'MarkdownV2'
|
||||
PARSEMODE_HTML (:obj:`str`): 'HTML'
|
||||
|
||||
:class:`telegram.Poll`:
|
||||
|
||||
Attributes:
|
||||
POLL_REGULAR (:obj:`str`): 'regular'
|
||||
POLL_QUIZ (:obj:`str`): 'quiz'
|
||||
MAX_POLL_QUESTION_LENGTH (:obj:`int`): 300
|
||||
MAX_POLL_OPTION_LENGTH (:obj:`int`): 100
|
||||
|
||||
:class:`telegram.files.MaskPosition`:
|
||||
|
||||
Attributes:
|
||||
STICKER_FOREHEAD (:obj:`str`): 'forehead'
|
||||
STICKER_EYES (:obj:`str`): 'eyes'
|
||||
STICKER_MOUTH (:obj:`str`): 'mouth'
|
||||
STICKER_CHIN (:obj:`str`): 'chin'
|
||||
|
||||
"""
|
||||
from typing import List
|
||||
|
||||
MAX_MESSAGE_LENGTH = 4096
|
||||
MAX_CAPTION_LENGTH = 1024
|
||||
MAX_MESSAGE_LENGTH: int = 4096
|
||||
MAX_CAPTION_LENGTH: int = 1024
|
||||
ANONYMOUS_ADMIN_ID: int = 1087968824
|
||||
SERVICE_CHAT_ID: int = 777000
|
||||
|
||||
# constants above this line are tested
|
||||
|
||||
SUPPORTED_WEBHOOK_PORTS = [443, 80, 88, 8443]
|
||||
MAX_FILESIZE_DOWNLOAD = int(20E6) # (20MB)
|
||||
MAX_FILESIZE_UPLOAD = int(50E6) # (50MB)
|
||||
MAX_MESSAGES_PER_SECOND_PER_CHAT = 1
|
||||
MAX_MESSAGES_PER_SECOND = 30
|
||||
MAX_MESSAGES_PER_MINUTE_PER_GROUP = 20
|
||||
MAX_MESSAGE_ENTITIES = 100
|
||||
MAX_INLINE_QUERY_RESULTS = 50
|
||||
SUPPORTED_WEBHOOK_PORTS: List[int] = [443, 80, 88, 8443]
|
||||
MAX_FILESIZE_DOWNLOAD: int = int(20e6) # (20MB)
|
||||
MAX_FILESIZE_UPLOAD: int = int(50e6) # (50MB)
|
||||
MAX_PHOTOSIZE_UPLOAD: int = int(10e6) # (10MB)
|
||||
MAX_MESSAGES_PER_SECOND_PER_CHAT: int = 1
|
||||
MAX_MESSAGES_PER_SECOND: int = 30
|
||||
MAX_MESSAGES_PER_MINUTE_PER_GROUP: int = 20
|
||||
MAX_MESSAGE_ENTITIES: int = 100
|
||||
MAX_INLINE_QUERY_RESULTS: int = 50
|
||||
MAX_ANSWER_CALLBACK_QUERY_TEXT_LENGTH: int = 200
|
||||
|
||||
CHAT_PRIVATE: str = 'private'
|
||||
CHAT_GROUP: str = 'group'
|
||||
CHAT_SUPERGROUP: str = 'supergroup'
|
||||
CHAT_CHANNEL: str = 'channel'
|
||||
|
||||
CHATACTION_FIND_LOCATION: str = 'find_location'
|
||||
CHATACTION_RECORD_AUDIO: str = 'record_audio'
|
||||
CHATACTION_RECORD_VIDEO: str = 'record_video'
|
||||
CHATACTION_RECORD_VIDEO_NOTE: str = 'record_video_note'
|
||||
CHATACTION_TYPING: str = 'typing'
|
||||
CHATACTION_UPLOAD_AUDIO: str = 'upload_audio'
|
||||
CHATACTION_UPLOAD_DOCUMENT: str = 'upload_document'
|
||||
CHATACTION_UPLOAD_PHOTO: str = 'upload_photo'
|
||||
CHATACTION_UPLOAD_VIDEO: str = 'upload_video'
|
||||
CHATACTION_UPLOAD_VIDEO_NOTE: str = 'upload_video_note'
|
||||
|
||||
CHATMEMBER_ADMINISTRATOR: str = 'administrator'
|
||||
CHATMEMBER_CREATOR: str = 'creator'
|
||||
CHATMEMBER_KICKED: str = 'kicked'
|
||||
CHATMEMBER_LEFT: str = 'left'
|
||||
CHATMEMBER_MEMBER: str = 'member'
|
||||
CHATMEMBER_RESTRICTED: str = 'restricted'
|
||||
|
||||
DICE_DICE: str = '🎲'
|
||||
DICE_DARTS: str = '🎯'
|
||||
DICE_BASKETBALL: str = '🏀'
|
||||
DICE_FOOTBALL: str = '⚽'
|
||||
DICE_SLOT_MACHINE: str = '🎰'
|
||||
DICE_ALL_EMOJI: List[str] = [
|
||||
DICE_DICE,
|
||||
DICE_DARTS,
|
||||
DICE_BASKETBALL,
|
||||
DICE_FOOTBALL,
|
||||
DICE_SLOT_MACHINE,
|
||||
]
|
||||
|
||||
MESSAGEENTITY_MENTION: str = 'mention'
|
||||
MESSAGEENTITY_HASHTAG: str = 'hashtag'
|
||||
MESSAGEENTITY_CASHTAG: str = 'cashtag'
|
||||
MESSAGEENTITY_PHONE_NUMBER: str = 'phone_number'
|
||||
MESSAGEENTITY_BOT_COMMAND: str = 'bot_command'
|
||||
MESSAGEENTITY_URL: str = 'url'
|
||||
MESSAGEENTITY_EMAIL: str = 'email'
|
||||
MESSAGEENTITY_BOLD: str = 'bold'
|
||||
MESSAGEENTITY_ITALIC: str = 'italic'
|
||||
MESSAGEENTITY_CODE: str = 'code'
|
||||
MESSAGEENTITY_PRE: str = 'pre'
|
||||
MESSAGEENTITY_TEXT_LINK: str = 'text_link'
|
||||
MESSAGEENTITY_TEXT_MENTION: str = 'text_mention'
|
||||
MESSAGEENTITY_UNDERLINE: str = 'underline'
|
||||
MESSAGEENTITY_STRIKETHROUGH: str = 'strikethrough'
|
||||
MESSAGEENTITY_ALL_TYPES: List[str] = [
|
||||
MESSAGEENTITY_MENTION,
|
||||
MESSAGEENTITY_HASHTAG,
|
||||
MESSAGEENTITY_CASHTAG,
|
||||
MESSAGEENTITY_PHONE_NUMBER,
|
||||
MESSAGEENTITY_BOT_COMMAND,
|
||||
MESSAGEENTITY_URL,
|
||||
MESSAGEENTITY_EMAIL,
|
||||
MESSAGEENTITY_BOLD,
|
||||
MESSAGEENTITY_ITALIC,
|
||||
MESSAGEENTITY_CODE,
|
||||
MESSAGEENTITY_PRE,
|
||||
MESSAGEENTITY_TEXT_LINK,
|
||||
MESSAGEENTITY_TEXT_MENTION,
|
||||
MESSAGEENTITY_UNDERLINE,
|
||||
MESSAGEENTITY_STRIKETHROUGH,
|
||||
]
|
||||
|
||||
PARSEMODE_MARKDOWN: str = 'Markdown'
|
||||
PARSEMODE_MARKDOWN_V2: str = 'MarkdownV2'
|
||||
PARSEMODE_HTML: str = 'HTML'
|
||||
|
||||
POLL_REGULAR: str = 'regular'
|
||||
POLL_QUIZ: str = 'quiz'
|
||||
MAX_POLL_QUESTION_LENGTH: int = 300
|
||||
MAX_POLL_OPTION_LENGTH: int = 100
|
||||
|
||||
STICKER_FOREHEAD: str = 'forehead'
|
||||
STICKER_EYES: str = 'eyes'
|
||||
STICKER_MOUTH: str = 'mouth'
|
||||
STICKER_CHIN: str = 'chin'
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
#!/usr/bin/env python
|
||||
# pylint: disable=R0903
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2021
|
||||
# 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 Dice."""
|
||||
from typing import Any, List, ClassVar
|
||||
|
||||
from telegram import TelegramObject, constants
|
||||
|
||||
|
||||
class Dice(TelegramObject):
|
||||
"""
|
||||
This object represents an animated emoji with a random value for currently supported base
|
||||
emoji. (The singular form of "dice" is "die". However, PTB mimics the Telegram API, which uses
|
||||
the term "dice".)
|
||||
|
||||
Objects of this class are comparable in terms of equality. Two objects of this class are
|
||||
considered equal, if their :attr:`value` and :attr:`emoji` are equal.
|
||||
|
||||
Note:
|
||||
If :attr:`emoji` is "🎯", a value of 6 currently represents a bullseye, while a value of 1
|
||||
indicates that the dartboard was missed. However, this behaviour is undocumented and might
|
||||
be changed by Telegram.
|
||||
|
||||
If :attr:`emoji` is "🏀", a value of 4 or 5 currently score a basket, while a value of 1 to
|
||||
3 indicates that the basket was missed. However, this behaviour is undocumented and might
|
||||
be changed by Telegram.
|
||||
|
||||
If :attr:`emoji` is "⚽", a value of 4 to 5 currently scores a goal, while a value of 1 to
|
||||
3 indicates that the goal was missed. However, this behaviour is undocumented and might
|
||||
be changed by Telegram.
|
||||
|
||||
If :attr:`emoji` is "🎰", each value corresponds to a unique combination of symbols, which
|
||||
can be found at our `wiki <https://git.io/JkeC6>`_. However, this behaviour is undocumented
|
||||
and might be changed by Telegram.
|
||||
|
||||
Args:
|
||||
value (:obj:`int`): Value of the dice. 1-6 for dice and darts, 1-5 for basketball and
|
||||
football/soccer ball, 1-64 for slot machine.
|
||||
emoji (:obj:`str`): Emoji on which the dice throw animation is based.
|
||||
|
||||
Attributes:
|
||||
value (:obj:`int`): Value of the dice.
|
||||
emoji (:obj:`str`): Emoji on which the dice throw animation is based.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, value: int, emoji: str, **_kwargs: Any):
|
||||
self.value = value
|
||||
self.emoji = emoji
|
||||
|
||||
self._id_attrs = (self.value, self.emoji)
|
||||
|
||||
DICE: ClassVar[str] = constants.DICE_DICE
|
||||
""":const:`telegram.constants.DICE_DICE`"""
|
||||
DARTS: ClassVar[str] = constants.DICE_DARTS
|
||||
""":const:`telegram.constants.DICE_DARTS`"""
|
||||
BASKETBALL: ClassVar[str] = constants.DICE_BASKETBALL
|
||||
""":const:`telegram.constants.DICE_BASKETBALL`"""
|
||||
FOOTBALL: ClassVar[str] = constants.DICE_FOOTBALL
|
||||
""":const:`telegram.constants.DICE_FOOTBALL`"""
|
||||
SLOT_MACHINE: ClassVar[str] = constants.DICE_SLOT_MACHINE
|
||||
""":const:`telegram.constants.DICE_SLOT_MACHINE`"""
|
||||
ALL_EMOJI: ClassVar[List[str]] = constants.DICE_ALL_EMOJI
|
||||
""":const:`telegram.constants.DICE_ALL_EMOJI`"""
|
||||
+40
-25
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2018
|
||||
# Copyright (C) 2015-2021
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -16,29 +16,31 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
# pylint: disable=C0115
|
||||
"""This module contains an object that represents Telegram errors."""
|
||||
from typing import Tuple
|
||||
|
||||
|
||||
def _lstrip_str(in_s, lstr):
|
||||
def _lstrip_str(in_s: str, lstr: str) -> str:
|
||||
"""
|
||||
Args:
|
||||
in_s (:obj:`str`): in string
|
||||
lstr (:obj:`str`): substr to strip from left side
|
||||
|
||||
Returns:
|
||||
str:
|
||||
:obj:`str`: The stripped string.
|
||||
|
||||
"""
|
||||
if in_s.startswith(lstr):
|
||||
res = in_s[len(lstr):]
|
||||
res = in_s[len(lstr) :]
|
||||
else:
|
||||
res = in_s
|
||||
return res
|
||||
|
||||
|
||||
class TelegramError(Exception):
|
||||
def __init__(self, message):
|
||||
super(TelegramError, self).__init__()
|
||||
def __init__(self, message: str):
|
||||
super().__init__()
|
||||
|
||||
msg = _lstrip_str(message, 'Error: ')
|
||||
msg = _lstrip_str(msg, '[Error]: ')
|
||||
@@ -48,8 +50,11 @@ class TelegramError(Exception):
|
||||
msg = msg.capitalize()
|
||||
self.message = msg
|
||||
|
||||
def __str__(self):
|
||||
return '%s' % (self.message)
|
||||
def __str__(self) -> str:
|
||||
return '%s' % self.message
|
||||
|
||||
def __reduce__(self) -> Tuple[type, Tuple[str]]:
|
||||
return self.__class__, (self.message,)
|
||||
|
||||
|
||||
class Unauthorized(TelegramError):
|
||||
@@ -57,8 +62,11 @@ class Unauthorized(TelegramError):
|
||||
|
||||
|
||||
class InvalidToken(TelegramError):
|
||||
def __init__(self):
|
||||
super(InvalidToken, self).__init__('Invalid token')
|
||||
def __init__(self) -> None:
|
||||
super().__init__('Invalid token')
|
||||
|
||||
def __reduce__(self) -> Tuple[type, Tuple]: # type: ignore[override]
|
||||
return self.__class__, ()
|
||||
|
||||
|
||||
class NetworkError(TelegramError):
|
||||
@@ -70,44 +78,51 @@ class BadRequest(NetworkError):
|
||||
|
||||
|
||||
class TimedOut(NetworkError):
|
||||
def __init__(self):
|
||||
super(TimedOut, self).__init__('Timed out')
|
||||
def __init__(self) -> None:
|
||||
super().__init__('Timed out')
|
||||
|
||||
def __reduce__(self) -> Tuple[type, Tuple]: # type: ignore[override]
|
||||
return self.__class__, ()
|
||||
|
||||
|
||||
class ChatMigrated(TelegramError):
|
||||
"""
|
||||
Args:
|
||||
new_chat_id (:obj:`int`):
|
||||
new_chat_id (:obj:`int`): The new chat id of the group.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, new_chat_id):
|
||||
super(ChatMigrated,
|
||||
self).__init__('Group migrated to supergroup. New chat id: {}'.format(new_chat_id))
|
||||
def __init__(self, new_chat_id: int):
|
||||
super().__init__(f'Group migrated to supergroup. New chat id: {new_chat_id}')
|
||||
self.new_chat_id = new_chat_id
|
||||
|
||||
def __reduce__(self) -> Tuple[type, Tuple[int]]: # type: ignore[override]
|
||||
return self.__class__, (self.new_chat_id,)
|
||||
|
||||
|
||||
class RetryAfter(TelegramError):
|
||||
"""
|
||||
Args:
|
||||
retry_after (:obj:`int`):
|
||||
retry_after (:obj:`int`): Time in seconds, after which the bot can retry the request.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, retry_after):
|
||||
super(RetryAfter,
|
||||
self).__init__('Flood control exceeded. Retry in {} seconds'.format(retry_after))
|
||||
def __init__(self, retry_after: int):
|
||||
super().__init__(f'Flood control exceeded. Retry in {float(retry_after)} seconds')
|
||||
self.retry_after = float(retry_after)
|
||||
|
||||
def __reduce__(self) -> Tuple[type, Tuple[float]]: # type: ignore[override]
|
||||
return self.__class__, (self.retry_after,)
|
||||
|
||||
|
||||
class Conflict(TelegramError):
|
||||
"""
|
||||
Raised when a long poll or webhook conflicts with another one.
|
||||
Raised when a long poll or webhook conflicts with another one.
|
||||
|
||||
Args:
|
||||
msg (:obj:`str`): The message from telegrams server.
|
||||
Args:
|
||||
msg (:obj:`str`): The message from telegrams server.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, msg):
|
||||
super(Conflict, self).__init__(msg)
|
||||
def __reduce__(self) -> Tuple[type, Tuple[str]]:
|
||||
return self.__class__, (self.message,)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2018
|
||||
# Copyright (C) 2015-2021
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -29,7 +29,7 @@ from .updater import Updater
|
||||
from .callbackqueryhandler import CallbackQueryHandler
|
||||
from .choseninlineresulthandler import ChosenInlineResultHandler
|
||||
from .inlinequeryhandler import InlineQueryHandler
|
||||
from .filters import BaseFilter, Filters
|
||||
from .filters import BaseFilter, MessageFilter, UpdateFilter, Filters
|
||||
from .messagehandler import MessageHandler
|
||||
from .commandhandler import CommandHandler, PrefixHandler
|
||||
from .regexhandler import RegexHandler
|
||||
@@ -41,11 +41,42 @@ from .precheckoutqueryhandler import PreCheckoutQueryHandler
|
||||
from .shippingqueryhandler import ShippingQueryHandler
|
||||
from .messagequeue import MessageQueue
|
||||
from .messagequeue import DelayQueue
|
||||
from .pollanswerhandler import PollAnswerHandler
|
||||
from .pollhandler import PollHandler
|
||||
from .defaults import Defaults
|
||||
|
||||
__all__ = ('Dispatcher', 'JobQueue', 'Job', 'Updater', 'CallbackQueryHandler',
|
||||
'ChosenInlineResultHandler', 'CommandHandler', 'Handler', 'InlineQueryHandler',
|
||||
'MessageHandler', 'BaseFilter', 'Filters', 'RegexHandler', 'StringCommandHandler',
|
||||
'StringRegexHandler', 'TypeHandler', 'ConversationHandler',
|
||||
'PreCheckoutQueryHandler', 'ShippingQueryHandler', 'MessageQueue', 'DelayQueue',
|
||||
'DispatcherHandlerStop', 'run_async', 'CallbackContext', 'BasePersistence',
|
||||
'PicklePersistence', 'DictPersistence', 'PrefixHandler')
|
||||
__all__ = (
|
||||
'Dispatcher',
|
||||
'JobQueue',
|
||||
'Job',
|
||||
'Updater',
|
||||
'CallbackQueryHandler',
|
||||
'ChosenInlineResultHandler',
|
||||
'CommandHandler',
|
||||
'Handler',
|
||||
'InlineQueryHandler',
|
||||
'MessageHandler',
|
||||
'BaseFilter',
|
||||
'MessageFilter',
|
||||
'UpdateFilter',
|
||||
'Filters',
|
||||
'RegexHandler',
|
||||
'StringCommandHandler',
|
||||
'StringRegexHandler',
|
||||
'TypeHandler',
|
||||
'ConversationHandler',
|
||||
'PreCheckoutQueryHandler',
|
||||
'ShippingQueryHandler',
|
||||
'MessageQueue',
|
||||
'DelayQueue',
|
||||
'DispatcherHandlerStop',
|
||||
'run_async',
|
||||
'CallbackContext',
|
||||
'BasePersistence',
|
||||
'PicklePersistence',
|
||||
'DictPersistence',
|
||||
'PrefixHandler',
|
||||
'PollAnswerHandler',
|
||||
'PollHandler',
|
||||
'Defaults',
|
||||
)
|
||||
|
||||
+305
-44
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2018
|
||||
# Copyright (C) 2015-2021
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -17,64 +17,314 @@
|
||||
# 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 the BasePersistence class."""
|
||||
import warnings
|
||||
from abc import ABC, abstractmethod
|
||||
from copy import copy
|
||||
from typing import DefaultDict, Dict, Optional, Tuple, cast, ClassVar
|
||||
|
||||
from telegram import Bot
|
||||
|
||||
from telegram.utils.types import ConversationDict
|
||||
|
||||
|
||||
class BasePersistence(object):
|
||||
class BasePersistence(ABC):
|
||||
"""Interface class for adding persistence to your bot.
|
||||
Subclass this object for different implementations of a persistent bot.
|
||||
|
||||
All relevant methods must be overwritten. This means:
|
||||
All relevant methods must be overwritten. This includes:
|
||||
|
||||
* If :attr:`store_chat_data` is ``True`` you must overwrite :meth:`get_chat_data` and
|
||||
:meth:`update_chat_data`.
|
||||
* If :attr:`store_user_data` is ``True`` you must overwrite :meth:`get_user_data` and
|
||||
:meth:`update_user_data`.
|
||||
* If you want to store conversation data with :class:`telegram.ext.ConversationHandler`, you
|
||||
must overwrite :meth:`get_conversations` and :meth:`update_conversation`.
|
||||
* :meth:`flush` will be called when the bot is shutdown.
|
||||
* :meth:`get_bot_data`
|
||||
* :meth:`update_bot_data`
|
||||
* :meth:`get_chat_data`
|
||||
* :meth:`update_chat_data`
|
||||
* :meth:`get_user_data`
|
||||
* :meth:`update_user_data`
|
||||
* :meth:`get_conversations`
|
||||
* :meth:`update_conversation`
|
||||
* :meth:`flush`
|
||||
|
||||
If you don't actually need one of those methods, a simple ``pass`` is enough. For example, if
|
||||
``store_bot_data=False``, you don't need :meth:`get_bot_data` and :meth:`update_bot_data`.
|
||||
|
||||
Warning:
|
||||
Persistence will try to replace :class:`telegram.Bot` instances by :attr:`REPLACED_BOT` and
|
||||
insert the bot set with :meth:`set_bot` upon loading of the data. This is to ensure that
|
||||
changes to the bot apply to the saved objects, too. If you change the bots token, this may
|
||||
lead to e.g. ``Chat not found`` errors. For the limitations on replacing bots see
|
||||
:meth:`replace_bot` and :meth:`insert_bot`.
|
||||
|
||||
Note:
|
||||
:meth:`replace_bot` and :meth:`insert_bot` are used *independently* of the implementation
|
||||
of the :meth:`update/get_*` methods, i.e. you don't need to worry about it while
|
||||
implementing a custom persistence subclass.
|
||||
|
||||
Args:
|
||||
store_user_data (:obj:`bool`, optional): Whether user_data should be saved by this
|
||||
persistence class. Default is :obj:`True`.
|
||||
store_chat_data (:obj:`bool`, optional): Whether chat_data should be saved by this
|
||||
persistence class. Default is :obj:`True` .
|
||||
store_bot_data (:obj:`bool`, optional): Whether bot_data should be saved by this
|
||||
persistence class. Default is :obj:`True` .
|
||||
|
||||
Attributes:
|
||||
store_user_data (:obj:`bool`): Optional, Whether user_data should be saved by this
|
||||
persistence class.
|
||||
store_chat_data (:obj:`bool`): Optional. Whether chat_data should be saved by this
|
||||
persistence class.
|
||||
|
||||
Args:
|
||||
store_user_data (:obj:`bool`, optional): Whether user_data should be saved by this
|
||||
persistence class. Default is ``True``.
|
||||
store_chat_data (:obj:`bool`, optional): Whether chat_data should be saved by this
|
||||
persistence class. Default is ``True`` .
|
||||
store_bot_data (:obj:`bool`): Optional. Whether bot_data should be saved by this
|
||||
persistence class.
|
||||
"""
|
||||
|
||||
def __init__(self, store_user_data=True, store_chat_data=True):
|
||||
def __new__(
|
||||
cls, *args: object, **kwargs: object # pylint: disable=W0613
|
||||
) -> 'BasePersistence':
|
||||
instance = super().__new__(cls)
|
||||
get_user_data = instance.get_user_data
|
||||
get_chat_data = instance.get_chat_data
|
||||
get_bot_data = instance.get_bot_data
|
||||
update_user_data = instance.update_user_data
|
||||
update_chat_data = instance.update_chat_data
|
||||
update_bot_data = instance.update_bot_data
|
||||
|
||||
def get_user_data_insert_bot() -> DefaultDict[int, Dict[object, object]]:
|
||||
return instance.insert_bot(get_user_data())
|
||||
|
||||
def get_chat_data_insert_bot() -> DefaultDict[int, Dict[object, object]]:
|
||||
return instance.insert_bot(get_chat_data())
|
||||
|
||||
def get_bot_data_insert_bot() -> Dict[object, object]:
|
||||
return instance.insert_bot(get_bot_data())
|
||||
|
||||
def update_user_data_replace_bot(user_id: int, data: Dict) -> None:
|
||||
return update_user_data(user_id, instance.replace_bot(data))
|
||||
|
||||
def update_chat_data_replace_bot(chat_id: int, data: Dict) -> None:
|
||||
return update_chat_data(chat_id, instance.replace_bot(data))
|
||||
|
||||
def update_bot_data_replace_bot(data: Dict) -> None:
|
||||
return update_bot_data(instance.replace_bot(data))
|
||||
|
||||
instance.get_user_data = get_user_data_insert_bot
|
||||
instance.get_chat_data = get_chat_data_insert_bot
|
||||
instance.get_bot_data = get_bot_data_insert_bot
|
||||
instance.update_user_data = update_user_data_replace_bot
|
||||
instance.update_chat_data = update_chat_data_replace_bot
|
||||
instance.update_bot_data = update_bot_data_replace_bot
|
||||
return instance
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
store_user_data: bool = True,
|
||||
store_chat_data: bool = True,
|
||||
store_bot_data: bool = True,
|
||||
):
|
||||
self.store_user_data = store_user_data
|
||||
self.store_chat_data = store_chat_data
|
||||
self.store_bot_data = store_bot_data
|
||||
self.bot: Bot = None # type: ignore[assignment]
|
||||
|
||||
def get_user_data(self):
|
||||
""""Will be called by :class:`telegram.ext.Dispatcher` upon creation with a
|
||||
persistence object. It should return the user_data if stored, or an empty
|
||||
def set_bot(self, bot: Bot) -> None:
|
||||
"""Set the Bot to be used by this persistence instance.
|
||||
|
||||
Args:
|
||||
bot (:class:`telegram.Bot`): The bot.
|
||||
"""
|
||||
self.bot = bot
|
||||
|
||||
@classmethod
|
||||
def replace_bot(cls, obj: object) -> object:
|
||||
"""
|
||||
Replaces all instances of :class:`telegram.Bot` that occur within the passed object with
|
||||
:attr:`REPLACED_BOT`. Currently, this handles objects of type ``list``, ``tuple``, ``set``,
|
||||
``frozenset``, ``dict``, ``defaultdict`` and objects that have a ``__dict__`` or
|
||||
``__slot__`` attribute, excluding objects that can't be copied with `copy.copy`.
|
||||
|
||||
Args:
|
||||
obj (:obj:`object`): The object
|
||||
|
||||
Returns:
|
||||
:obj:`obj`: Copy of the object with Bot instances replaced.
|
||||
"""
|
||||
return cls._replace_bot(obj, {})
|
||||
|
||||
@classmethod
|
||||
def _replace_bot(cls, obj: object, memo: Dict[int, object]) -> object: # pylint: disable=R0911
|
||||
obj_id = id(obj)
|
||||
if obj_id in memo:
|
||||
return memo[obj_id]
|
||||
|
||||
if isinstance(obj, Bot):
|
||||
memo[obj_id] = cls.REPLACED_BOT
|
||||
return cls.REPLACED_BOT
|
||||
if isinstance(obj, (list, set)):
|
||||
# We copy the iterable here for thread safety, i.e. make sure the object we iterate
|
||||
# over doesn't change its length during the iteration
|
||||
temp_iterable = obj.copy()
|
||||
new_iterable = obj.__class__(cls._replace_bot(item, memo) for item in temp_iterable)
|
||||
memo[obj_id] = new_iterable
|
||||
return new_iterable
|
||||
if isinstance(obj, (tuple, frozenset)):
|
||||
# tuples and frozensets are immutable so we don't need to worry about thread safety
|
||||
new_immutable = obj.__class__(cls._replace_bot(item, memo) for item in obj)
|
||||
memo[obj_id] = new_immutable
|
||||
return new_immutable
|
||||
|
||||
try:
|
||||
new_obj = copy(obj)
|
||||
memo[obj_id] = new_obj
|
||||
except Exception:
|
||||
warnings.warn(
|
||||
'BasePersistence.replace_bot does not handle objects that can not be copied. See '
|
||||
'the docs of BasePersistence.replace_bot for more information.',
|
||||
RuntimeWarning,
|
||||
)
|
||||
memo[obj_id] = obj
|
||||
return obj
|
||||
|
||||
if isinstance(obj, dict):
|
||||
# We handle dicts via copy(obj) so we don't have to make a
|
||||
# difference between dict and defaultdict
|
||||
new_obj = cast(dict, new_obj)
|
||||
# We can't iterate over obj.items() due to thread safety, i.e. the dicts length may
|
||||
# change during the iteration
|
||||
temp_dict = new_obj.copy()
|
||||
new_obj.clear()
|
||||
for k, val in temp_dict.items():
|
||||
new_obj[cls._replace_bot(k, memo)] = cls._replace_bot(val, memo)
|
||||
memo[obj_id] = new_obj
|
||||
return new_obj
|
||||
if hasattr(obj, '__dict__'):
|
||||
for attr_name, attr in new_obj.__dict__.items():
|
||||
setattr(new_obj, attr_name, cls._replace_bot(attr, memo))
|
||||
memo[obj_id] = new_obj
|
||||
return new_obj
|
||||
if hasattr(obj, '__slots__'):
|
||||
for attr_name in new_obj.__slots__:
|
||||
setattr(
|
||||
new_obj,
|
||||
attr_name,
|
||||
cls._replace_bot(cls._replace_bot(getattr(new_obj, attr_name), memo), memo),
|
||||
)
|
||||
memo[obj_id] = new_obj
|
||||
return new_obj
|
||||
|
||||
return obj
|
||||
|
||||
def insert_bot(self, obj: object) -> object:
|
||||
"""
|
||||
Replaces all instances of :attr:`REPLACED_BOT` that occur within the passed object with
|
||||
:attr:`bot`. Currently, this handles objects of type ``list``, ``tuple``, ``set``,
|
||||
``frozenset``, ``dict``, ``defaultdict`` and objects that have a ``__dict__`` or
|
||||
``__slot__`` attribute, excluding objects that can't be copied with `copy.copy`.
|
||||
|
||||
Args:
|
||||
obj (:obj:`object`): The object
|
||||
|
||||
Returns:
|
||||
:obj:`obj`: Copy of the object with Bot instances inserted.
|
||||
"""
|
||||
return self._insert_bot(obj, {})
|
||||
|
||||
def _insert_bot(self, obj: object, memo: Dict[int, object]) -> object: # pylint: disable=R0911
|
||||
obj_id = id(obj)
|
||||
if obj_id in memo:
|
||||
return memo[obj_id]
|
||||
|
||||
if isinstance(obj, Bot):
|
||||
memo[obj_id] = self.bot
|
||||
return self.bot
|
||||
if isinstance(obj, str) and obj == self.REPLACED_BOT:
|
||||
memo[obj_id] = self.bot
|
||||
return self.bot
|
||||
if isinstance(obj, (list, set)):
|
||||
# We copy the iterable here for thread safety, i.e. make sure the object we iterate
|
||||
# over doesn't change its length during the iteration
|
||||
temp_iterable = obj.copy()
|
||||
new_iterable = obj.__class__(self._insert_bot(item, memo) for item in temp_iterable)
|
||||
memo[obj_id] = new_iterable
|
||||
return new_iterable
|
||||
if isinstance(obj, (tuple, frozenset)):
|
||||
# tuples and frozensets are immutable so we don't need to worry about thread safety
|
||||
new_immutable = obj.__class__(self._insert_bot(item, memo) for item in obj)
|
||||
memo[obj_id] = new_immutable
|
||||
return new_immutable
|
||||
|
||||
try:
|
||||
new_obj = copy(obj)
|
||||
except Exception:
|
||||
warnings.warn(
|
||||
'BasePersistence.insert_bot does not handle objects that can not be copied. See '
|
||||
'the docs of BasePersistence.insert_bot for more information.',
|
||||
RuntimeWarning,
|
||||
)
|
||||
memo[obj_id] = obj
|
||||
return obj
|
||||
|
||||
if isinstance(obj, dict):
|
||||
# We handle dicts via copy(obj) so we don't have to make a
|
||||
# difference between dict and defaultdict
|
||||
new_obj = cast(dict, new_obj)
|
||||
# We can't iterate over obj.items() due to thread safety, i.e. the dicts length may
|
||||
# change during the iteration
|
||||
temp_dict = new_obj.copy()
|
||||
new_obj.clear()
|
||||
for k, val in temp_dict.items():
|
||||
new_obj[self._insert_bot(k, memo)] = self._insert_bot(val, memo)
|
||||
memo[obj_id] = new_obj
|
||||
return new_obj
|
||||
if hasattr(obj, '__dict__'):
|
||||
for attr_name, attr in new_obj.__dict__.items():
|
||||
setattr(new_obj, attr_name, self._insert_bot(attr, memo))
|
||||
memo[obj_id] = new_obj
|
||||
return new_obj
|
||||
if hasattr(obj, '__slots__'):
|
||||
for attr_name in obj.__slots__:
|
||||
setattr(
|
||||
new_obj,
|
||||
attr_name,
|
||||
self._insert_bot(self._insert_bot(getattr(new_obj, attr_name), memo), memo),
|
||||
)
|
||||
memo[obj_id] = new_obj
|
||||
return new_obj
|
||||
|
||||
return obj
|
||||
|
||||
@abstractmethod
|
||||
def get_user_data(self) -> DefaultDict[int, Dict[object, object]]:
|
||||
""" "Will be called by :class:`telegram.ext.Dispatcher` upon creation with a
|
||||
persistence object. It should return the ``user_data`` if stored, or an empty
|
||||
``defaultdict(dict)``.
|
||||
|
||||
Returns:
|
||||
:obj:`defaultdict`: The restored user data.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_chat_data(self):
|
||||
""""Will be called by :class:`telegram.ext.Dispatcher` upon creation with a
|
||||
persistence object. It should return the chat_data if stored, or an empty
|
||||
@abstractmethod
|
||||
def get_chat_data(self) -> DefaultDict[int, Dict[object, object]]:
|
||||
""" "Will be called by :class:`telegram.ext.Dispatcher` upon creation with a
|
||||
persistence object. It should return the ``chat_data`` if stored, or an empty
|
||||
``defaultdict(dict)``.
|
||||
|
||||
Returns:
|
||||
:obj:`defaultdict`: The restored chat data.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_conversations(self, name):
|
||||
""""Will be called by :class:`telegram.ext.Dispatcher` when a
|
||||
@abstractmethod
|
||||
def get_bot_data(self) -> Dict[object, object]:
|
||||
""" "Will be called by :class:`telegram.ext.Dispatcher` upon creation with a
|
||||
persistence object. It should return the ``bot_data`` if stored, or an empty
|
||||
:obj:`dict`.
|
||||
|
||||
Returns:
|
||||
:obj:`dict`: The restored bot data.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_conversations(self, name: str) -> ConversationDict:
|
||||
""" "Will be called by :class:`telegram.ext.Dispatcher` when a
|
||||
:class:`telegram.ext.ConversationHandler` is added if
|
||||
:attr:`telegram.ext.ConversationHandler.persistent` is ``True``.
|
||||
It should return the conversations for the handler with `name` or an empty ``dict``
|
||||
:attr:`telegram.ext.ConversationHandler.persistent` is :obj:`True`.
|
||||
It should return the conversations for the handler with `name` or an empty :obj:`dict`
|
||||
|
||||
Args:
|
||||
name (:obj:`str`): The handlers name.
|
||||
@@ -82,20 +332,22 @@ class BasePersistence(object):
|
||||
Returns:
|
||||
:obj:`dict`: The restored conversations for the handler.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def update_conversation(self, name, key, new_state):
|
||||
@abstractmethod
|
||||
def update_conversation(
|
||||
self, name: str, key: Tuple[int, ...], new_state: Optional[object]
|
||||
) -> None:
|
||||
"""Will be called when a :attr:`telegram.ext.ConversationHandler.update_state`
|
||||
is called. this allows the storeage of the new state in the persistence.
|
||||
is called. This allows the storage of the new state in the persistence.
|
||||
|
||||
Args:
|
||||
name (:obj:`str`): The handlers name.
|
||||
name (:obj:`str`): The handler's name.
|
||||
key (:obj:`tuple`): The key the state is changed for.
|
||||
new_state (:obj:`tuple` | :obj:`any`): The new state for the given key.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def update_user_data(self, user_id, data):
|
||||
@abstractmethod
|
||||
def update_user_data(self, user_id: int, data: Dict) -> None:
|
||||
"""Will be called by the :class:`telegram.ext.Dispatcher` after a handler has
|
||||
handled an update.
|
||||
|
||||
@@ -103,21 +355,30 @@ class BasePersistence(object):
|
||||
user_id (:obj:`int`): The user the data might have been changed for.
|
||||
data (:obj:`dict`): The :attr:`telegram.ext.dispatcher.user_data` [user_id].
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def update_chat_data(self, chat_id, data):
|
||||
@abstractmethod
|
||||
def update_chat_data(self, chat_id: int, data: Dict) -> None:
|
||||
"""Will be called by the :class:`telegram.ext.Dispatcher` after a handler has
|
||||
handled an update.
|
||||
|
||||
Args:
|
||||
chat_id (:obj:`int`): The chat the data might have been changed for.
|
||||
data (:obj:`dict`): The :attr:`telegram.ext.dispatcher.chat_data` [user_id].
|
||||
data (:obj:`dict`): The :attr:`telegram.ext.dispatcher.chat_data` [chat_id].
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def flush(self):
|
||||
"""Will be called by :class:`telegram.ext.Updater` upon receiving a stop signal. Gives the
|
||||
persistence a chance to finish up saving or close a database connection gracefully. If this
|
||||
is not of any importance just pass will be sufficient.
|
||||
@abstractmethod
|
||||
def update_bot_data(self, data: Dict) -> None:
|
||||
"""Will be called by the :class:`telegram.ext.Dispatcher` after a handler has
|
||||
handled an update.
|
||||
|
||||
Args:
|
||||
data (:obj:`dict`): The :attr:`telegram.ext.dispatcher.bot_data` .
|
||||
"""
|
||||
pass
|
||||
|
||||
def flush(self) -> None:
|
||||
"""Will be called by :class:`telegram.ext.Updater` upon receiving a stop signal. Gives the
|
||||
persistence a chance to finish up saving or close a database connection gracefully.
|
||||
"""
|
||||
|
||||
REPLACED_BOT: ClassVar[str] = 'bot_instance_replaced_by_ptb_persistence'
|
||||
""":obj:`str`: Placeholder for :class:`telegram.Bot` instances replaced in saved data."""
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2018
|
||||
# Copyright (C) 2015-2021
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -16,12 +16,19 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
# pylint: disable=R0201
|
||||
"""This module contains the CallbackContext class."""
|
||||
from queue import Queue
|
||||
from typing import TYPE_CHECKING, Dict, List, Match, NoReturn, Optional, Tuple, Union
|
||||
|
||||
from telegram import Update
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Bot
|
||||
from telegram.ext import Dispatcher, Job, JobQueue
|
||||
|
||||
class CallbackContext(object):
|
||||
|
||||
class CallbackContext:
|
||||
"""
|
||||
This is a context object passed to the callback called by :class:`telegram.ext.Handler`
|
||||
or by the :class:`telegram.ext.Dispatcher` in an error handler added by
|
||||
@@ -38,101 +45,147 @@ class CallbackContext(object):
|
||||
use a fairly unique name for the attributes.
|
||||
|
||||
Warning:
|
||||
Do not combine custom attributes and @run_async. Due to how @run_async works, it will
|
||||
Do not combine custom attributes and ``@run_async``/
|
||||
:meth:`telegram.ext.Disptacher.run_async`. Due to how ``run_async`` works, it will
|
||||
almost certainly execute the callbacks for an update out of order, and the attributes
|
||||
that you think you added will not be present.
|
||||
|
||||
Attributes:
|
||||
chat_data (:obj:`dict`, optional): A dict that can be used to keep any data in. For each
|
||||
update from the same chat it will be the same ``dict``.
|
||||
user_data (:obj:`dict`, optional): A dict that can be used to keep any data in. For each
|
||||
bot_data (:obj:`dict`): Optional. A dict that can be used to keep any data in. For each
|
||||
update it will be the same ``dict``.
|
||||
chat_data (:obj:`dict`): Optional. A dict that can be used to keep any data in. For each
|
||||
update from the same chat id it will be the same ``dict``.
|
||||
|
||||
Warning:
|
||||
When a group chat migrates to a supergroup, its chat id will change and the
|
||||
``chat_data`` needs to be transferred. For details see our `wiki page
|
||||
<https://github.com/python-telegram-bot/python-telegram-bot/wiki/
|
||||
Storing-user--and-chat-related-data#chat-migration>`_.
|
||||
|
||||
user_data (:obj:`dict`): Optional. A dict that can be used to keep any data in. For each
|
||||
update from the same user it will be the same ``dict``.
|
||||
matches (List[:obj:`re match object`], optional): If the associated update originated from
|
||||
matches (List[:obj:`re match object`]): Optional. If the associated update originated from
|
||||
a regex-supported handler or had a :class:`Filters.regex`, this will contain a list of
|
||||
match objects for every pattern where ``re.search(pattern, string)`` returned a match.
|
||||
Note that filters short circuit, so combined regex filters will not always
|
||||
be evaluated.
|
||||
args (List[:obj:`str`], optional): Arguments passed to a command if the associated update
|
||||
args (List[:obj:`str`]): Optional. Arguments passed to a command if the associated update
|
||||
is handled by :class:`telegram.ext.CommandHandler`, :class:`telegram.ext.PrefixHandler`
|
||||
or :class:`telegram.ext.StringCommandHandler`. It contains a list of the words in the
|
||||
text after the command, using any whitespace string as a delimiter.
|
||||
error (:class:`telegram.TelegramError`, optional): The Telegram error that was raised.
|
||||
Only present when passed to a error handler registered with
|
||||
:attr:`telegram.ext.Dispatcher.add_error_handler`.
|
||||
job (:class:`telegram.ext.Job`): The job that that originated this callback.
|
||||
error (:obj:`Exception`): Optional. The error that was raised. Only present when passed
|
||||
to a error handler registered with :attr:`telegram.ext.Dispatcher.add_error_handler`.
|
||||
async_args (List[:obj:`object`]): Optional. Positional arguments of the function that
|
||||
raised the error. Only present when the raising function was run asynchronously using
|
||||
:meth:`telegram.ext.Dispatcher.run_async`.
|
||||
async_kwargs (Dict[:obj:`str`, :obj:`object`]): Optional. Keyword arguments of the function
|
||||
that raised the error. Only present when the raising function was run asynchronously
|
||||
using :meth:`telegram.ext.Dispatcher.run_async`.
|
||||
job (:class:`telegram.ext.Job`): Optional. The job which originated this callback.
|
||||
Only present when passed to the callback of :class:`telegram.ext.Job`.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, dispatcher):
|
||||
def __init__(self, dispatcher: 'Dispatcher'):
|
||||
"""
|
||||
Args:
|
||||
dispatcher (:class:`telegram.ext.Dispatcher`):
|
||||
"""
|
||||
if not dispatcher.use_context:
|
||||
raise ValueError('CallbackContext should not be used with a non context aware '
|
||||
'dispatcher!')
|
||||
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.args = None
|
||||
self.matches = None
|
||||
self.error = None
|
||||
self.job = None
|
||||
self._bot_data = dispatcher.bot_data
|
||||
self._chat_data: Optional[Dict[object, object]] = None
|
||||
self._user_data: Optional[Dict[object, object]] = None
|
||||
self.args: Optional[List[str]] = None
|
||||
self.matches: Optional[List[Match]] = None
|
||||
self.error: Optional[Exception] = None
|
||||
self.job: Optional['Job'] = None
|
||||
self.async_args: Optional[Union[List, Tuple]] = None
|
||||
self.async_kwargs: Optional[Dict[str, object]] = None
|
||||
|
||||
@property
|
||||
def chat_data(self):
|
||||
def dispatcher(self) -> 'Dispatcher':
|
||||
""":class:`telegram.ext.Dispatcher`: The dispatcher associated with this context."""
|
||||
return self._dispatcher
|
||||
|
||||
@property
|
||||
def bot_data(self) -> Dict:
|
||||
return self._bot_data
|
||||
|
||||
@bot_data.setter
|
||||
def bot_data(self, value: object) -> NoReturn:
|
||||
raise AttributeError(
|
||||
"You can not assign a new value to bot_data, see " "https://git.io/fjxKe"
|
||||
)
|
||||
|
||||
@property
|
||||
def chat_data(self) -> Optional[Dict]:
|
||||
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")
|
||||
def chat_data(self, value: object) -> NoReturn:
|
||||
raise AttributeError(
|
||||
"You can not assign a new value to chat_data, see " "https://git.io/fjxKe"
|
||||
)
|
||||
|
||||
@property
|
||||
def user_data(self):
|
||||
def user_data(self) -> Optional[Dict]:
|
||||
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")
|
||||
def user_data(self, value: object) -> NoReturn:
|
||||
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):
|
||||
def from_error(
|
||||
cls,
|
||||
update: object,
|
||||
error: Exception,
|
||||
dispatcher: 'Dispatcher',
|
||||
async_args: Union[List, Tuple] = None,
|
||||
async_kwargs: Dict[str, object] = None,
|
||||
) -> 'CallbackContext':
|
||||
self = cls.from_update(update, dispatcher)
|
||||
self.error = error
|
||||
self.async_args = async_args
|
||||
self.async_kwargs = async_kwargs
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def from_update(cls, update, dispatcher):
|
||||
def from_update(cls, update: object, dispatcher: 'Dispatcher') -> 'CallbackContext':
|
||||
self = cls(dispatcher)
|
||||
|
||||
if update is not None and isinstance(update, Update):
|
||||
chat = update.effective_chat
|
||||
user = update.effective_user
|
||||
|
||||
if chat:
|
||||
self._chat_data = dispatcher.chat_data[chat.id]
|
||||
self._chat_data = dispatcher.chat_data[chat.id] # pylint: disable=W0212
|
||||
if user:
|
||||
self._user_data = dispatcher.user_data[user.id]
|
||||
self._user_data = dispatcher.user_data[user.id] # pylint: disable=W0212
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def from_job(cls, job, dispatcher):
|
||||
def from_job(cls, job: 'Job', dispatcher: 'Dispatcher') -> 'CallbackContext':
|
||||
self = cls(dispatcher)
|
||||
self.job = job
|
||||
return self
|
||||
|
||||
def update(self, data):
|
||||
def update(self, data: Dict[str, object]) -> None:
|
||||
self.__dict__.update(data)
|
||||
|
||||
@property
|
||||
def bot(self):
|
||||
def bot(self) -> 'Bot':
|
||||
""":class:`telegram.Bot`: The bot associated with this context."""
|
||||
return self._dispatcher.bot
|
||||
|
||||
@property
|
||||
def job_queue(self):
|
||||
def job_queue(self) -> Optional['JobQueue']:
|
||||
"""
|
||||
:class:`telegram.ext.JobQueue`: The ``JobQueue`` used by the
|
||||
:class:`telegram.ext.Dispatcher` and (usually) the :class:`telegram.ext.Updater`
|
||||
@@ -142,7 +195,7 @@ class CallbackContext(object):
|
||||
return self._dispatcher.job_queue
|
||||
|
||||
@property
|
||||
def update_queue(self):
|
||||
def update_queue(self) -> Queue:
|
||||
"""
|
||||
:class:`queue.Queue`: The ``Queue`` instance used by the
|
||||
:class:`telegram.ext.Dispatcher` and (usually) the :class:`telegram.ext.Updater`
|
||||
@@ -152,13 +205,13 @@ class CallbackContext(object):
|
||||
return self._dispatcher.update_queue
|
||||
|
||||
@property
|
||||
def match(self):
|
||||
def match(self) -> Optional[Match[str]]:
|
||||
"""
|
||||
`Regex match type`: The first match from :attr:`matches`.
|
||||
Useful if you are only filtering using a single regex filter.
|
||||
Returns `None` if :attr:`matches` is empty.
|
||||
"""
|
||||
try:
|
||||
return self.matches[0] # pylint: disable=unsubscriptable-object
|
||||
return self.matches[0] # type: ignore[index] # pylint: disable=unsubscriptable-object
|
||||
except (IndexError, TypeError):
|
||||
return None
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2018
|
||||
# Copyright (C) 2015-2021
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -19,18 +19,86 @@
|
||||
"""This module contains the CallbackQueryHandler class."""
|
||||
|
||||
import re
|
||||
|
||||
from future.utils import string_types
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Callable,
|
||||
Dict,
|
||||
Match,
|
||||
Optional,
|
||||
Pattern,
|
||||
TypeVar,
|
||||
Union,
|
||||
cast,
|
||||
)
|
||||
|
||||
from telegram import Update
|
||||
from telegram.utils.helpers import DefaultValue, DEFAULT_FALSE
|
||||
|
||||
from .handler import Handler
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram.ext import CallbackContext, Dispatcher
|
||||
|
||||
class CallbackQueryHandler(Handler):
|
||||
RT = TypeVar('RT')
|
||||
|
||||
|
||||
class CallbackQueryHandler(Handler[Update]):
|
||||
"""Handler class to handle Telegram callback queries. Optionally based on a regex.
|
||||
|
||||
Read the documentation of the ``re`` module for more information.
|
||||
|
||||
Note:
|
||||
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you
|
||||
can use to keep any data in will be sent to the :attr:`callback` function. Related to
|
||||
either the user or the chat that the update was sent in. For each update from the same user
|
||||
or in the same chat, it will be the same ``dict``.
|
||||
|
||||
Note that this is DEPRECATED, and you should use context based callbacks. See
|
||||
https://git.io/fxJuV for more info.
|
||||
|
||||
Warning:
|
||||
When setting ``run_async`` to :obj:`True`, you cannot rely on adding custom
|
||||
attributes to :class:`telegram.ext.CallbackContext`. See its docs for more info.
|
||||
|
||||
Args:
|
||||
callback (:obj:`callable`): The callback function for this handler. Will be called when
|
||||
:attr:`check_update` has determined that an update should be processed by this handler.
|
||||
Callback signature for context based API:
|
||||
|
||||
``def callback(update: Update, context: CallbackContext)``
|
||||
|
||||
The return value of the callback is usually ignored except for the special case of
|
||||
:class:`telegram.ext.ConversationHandler`.
|
||||
pass_update_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``update_queue`` will be passed to the callback function. It will be the ``Queue``
|
||||
instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher`
|
||||
that contains new updates which can be used to insert updates. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_job_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``job_queue`` will be passed to the callback function. It will be a
|
||||
:class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater`
|
||||
which can be used to schedule new jobs. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pattern (:obj:`str` | `Pattern`, optional): Regex pattern. If not :obj:`None`, ``re.match``
|
||||
is used on :attr:`telegram.CallbackQuery.data` to determine if an update should be
|
||||
handled by this handler.
|
||||
pass_groups (:obj:`bool`, optional): If the callback should be passed the result of
|
||||
``re.match(pattern, data).groups()`` as a keyword argument called ``groups``.
|
||||
Default is :obj:`False`
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_groupdict (:obj:`bool`, optional): If the callback should be passed the result of
|
||||
``re.match(pattern, data).groupdict()`` as a keyword argument called ``groupdict``.
|
||||
Default is :obj:`False`
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_user_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``user_data`` will be passed to the callback function. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_chat_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``chat_data`` will be passed to the callback function. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
run_async (:obj:`bool`): Determines whether the callback will run asynchronously.
|
||||
Defaults to :obj:`False`.
|
||||
|
||||
Attributes:
|
||||
callback (:obj:`callable`): The callback function for this handler.
|
||||
pass_update_queue (:obj:`bool`): Determines whether ``update_queue`` will be
|
||||
@@ -47,83 +115,43 @@ class CallbackQueryHandler(Handler):
|
||||
the callback function.
|
||||
pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to
|
||||
the callback function.
|
||||
|
||||
Note:
|
||||
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you
|
||||
can use to keep any data in will be sent to the :attr:`callback` function. Related to
|
||||
either the user or the chat that the update was sent in. For each update from the same user
|
||||
or in the same chat, it will be the same ``dict``.
|
||||
|
||||
Note that this is DEPRECATED, and you should use context based callbacks. See
|
||||
https://git.io/fxJuV for more info.
|
||||
|
||||
Args:
|
||||
callback (:obj:`callable`): The callback function for this handler. Will be called when
|
||||
:attr:`check_update` has determined that an update should be processed by this handler.
|
||||
Callback signature for context based API:
|
||||
|
||||
``def callback(update: Update, context: CallbackContext)``
|
||||
|
||||
The return value of the callback is usually ignored except for the special case of
|
||||
:class:`telegram.ext.ConversationHandler`.
|
||||
pass_update_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called
|
||||
``update_queue`` will be passed to the callback function. It will be the ``Queue``
|
||||
instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher`
|
||||
that contains new updates which can be used to insert updates. Default is ``False``.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_job_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called
|
||||
``job_queue`` will be passed to the callback function. It will be a
|
||||
:class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater`
|
||||
which can be used to schedule new jobs. Default is ``False``.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pattern (:obj:`str` | `Pattern`, optional): Regex pattern. If not ``None``, ``re.match``
|
||||
is used on :attr:`telegram.CallbackQuery.data` to determine if an update should be
|
||||
handled by this handler.
|
||||
pass_groups (:obj:`bool`, optional): If the callback should be passed the result of
|
||||
``re.match(pattern, data).groups()`` as a keyword argument called ``groups``.
|
||||
Default is ``False``
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_groupdict (:obj:`bool`, optional): If the callback should be passed the result of
|
||||
``re.match(pattern, data).groupdict()`` as a keyword argument called ``groupdict``.
|
||||
Default is ``False``
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_user_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called
|
||||
``user_data`` will be passed to the callback function. Default is ``False``.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_chat_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called
|
||||
``chat_data`` will be passed to the callback function. Default is ``False``.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
run_async (:obj:`bool`): Determines whether the callback will run asynchronously.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
callback,
|
||||
pass_update_queue=False,
|
||||
pass_job_queue=False,
|
||||
pattern=None,
|
||||
pass_groups=False,
|
||||
pass_groupdict=False,
|
||||
pass_user_data=False,
|
||||
pass_chat_data=False):
|
||||
super(CallbackQueryHandler, self).__init__(
|
||||
def __init__(
|
||||
self,
|
||||
callback: Callable[[Update, 'CallbackContext'], RT],
|
||||
pass_update_queue: bool = False,
|
||||
pass_job_queue: bool = False,
|
||||
pattern: Union[str, Pattern] = None,
|
||||
pass_groups: bool = False,
|
||||
pass_groupdict: bool = False,
|
||||
pass_user_data: bool = False,
|
||||
pass_chat_data: bool = False,
|
||||
run_async: Union[bool, DefaultValue] = DEFAULT_FALSE,
|
||||
):
|
||||
super().__init__(
|
||||
callback,
|
||||
pass_update_queue=pass_update_queue,
|
||||
pass_job_queue=pass_job_queue,
|
||||
pass_user_data=pass_user_data,
|
||||
pass_chat_data=pass_chat_data)
|
||||
pass_chat_data=pass_chat_data,
|
||||
run_async=run_async,
|
||||
)
|
||||
|
||||
if isinstance(pattern, string_types):
|
||||
if isinstance(pattern, str):
|
||||
pattern = re.compile(pattern)
|
||||
|
||||
self.pattern = pattern
|
||||
self.pass_groups = pass_groups
|
||||
self.pass_groupdict = pass_groupdict
|
||||
|
||||
def check_update(self, update):
|
||||
def check_update(self, update: object) -> Optional[Union[bool, object]]:
|
||||
"""Determines whether an update should be passed to this handlers :attr:`callback`.
|
||||
|
||||
Args:
|
||||
update (:class:`telegram.Update`): Incoming telegram update.
|
||||
update (:class:`telegram.Update` | :obj:`object`): Incoming update.
|
||||
|
||||
Returns:
|
||||
:obj:`bool`
|
||||
@@ -137,18 +165,30 @@ class CallbackQueryHandler(Handler):
|
||||
return match
|
||||
else:
|
||||
return True
|
||||
return None
|
||||
|
||||
def collect_optional_args(self, dispatcher, update=None, check_result=None):
|
||||
optional_args = super(CallbackQueryHandler, self).collect_optional_args(dispatcher,
|
||||
update,
|
||||
check_result)
|
||||
def collect_optional_args(
|
||||
self,
|
||||
dispatcher: 'Dispatcher',
|
||||
update: Update = None,
|
||||
check_result: Union[bool, Match] = None,
|
||||
) -> Dict[str, object]:
|
||||
optional_args = super().collect_optional_args(dispatcher, update, check_result)
|
||||
if self.pattern:
|
||||
check_result = cast(Match, check_result)
|
||||
if self.pass_groups:
|
||||
optional_args['groups'] = check_result.groups()
|
||||
if self.pass_groupdict:
|
||||
optional_args['groupdict'] = check_result.groupdict()
|
||||
return optional_args
|
||||
|
||||
def collect_additional_context(self, context, update, dispatcher, check_result):
|
||||
def collect_additional_context(
|
||||
self,
|
||||
context: 'CallbackContext',
|
||||
update: Update,
|
||||
dispatcher: 'Dispatcher',
|
||||
check_result: Union[bool, Match],
|
||||
) -> None:
|
||||
if self.pattern:
|
||||
check_result = cast(Match, check_result)
|
||||
context.matches = [check_result]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2018
|
||||
# Copyright (C) 2015-2021
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -18,13 +18,59 @@
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains the ChosenInlineResultHandler class."""
|
||||
|
||||
from typing import Optional, TypeVar, Union
|
||||
|
||||
from telegram import Update
|
||||
|
||||
from .handler import Handler
|
||||
|
||||
RT = TypeVar('RT')
|
||||
|
||||
class ChosenInlineResultHandler(Handler):
|
||||
|
||||
class ChosenInlineResultHandler(Handler[Update]):
|
||||
"""Handler class to handle Telegram updates that contain a chosen inline result.
|
||||
|
||||
Note:
|
||||
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you
|
||||
can use to keep any data in will be sent to the :attr:`callback` function. Related to
|
||||
either the user or the chat that the update was sent in. For each update from the same user
|
||||
or in the same chat, it will be the same ``dict``.
|
||||
|
||||
Note that this is DEPRECATED, and you should use context based callbacks. See
|
||||
https://git.io/fxJuV for more info.
|
||||
|
||||
Warning:
|
||||
When setting ``run_async`` to :obj:`True`, you cannot rely on adding custom
|
||||
attributes to :class:`telegram.ext.CallbackContext`. See its docs for more info.
|
||||
|
||||
Args:
|
||||
callback (:obj:`callable`): The callback function for this handler. Will be called when
|
||||
:attr:`check_update` has determined that an update should be processed by this handler.
|
||||
Callback signature for context based API:
|
||||
|
||||
``def callback(update: Update, context: CallbackContext)``
|
||||
|
||||
The return value of the callback is usually ignored except for the special case of
|
||||
:class:`telegram.ext.ConversationHandler`.
|
||||
pass_update_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``update_queue`` will be passed to the callback function. It will be the ``Queue``
|
||||
instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher`
|
||||
that contains new updates which can be used to insert updates. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_job_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``job_queue`` will be passed to the callback function. It will be a
|
||||
:class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater`
|
||||
which can be used to schedule new jobs. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_user_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``user_data`` will be passed to the callback function. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_chat_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``chat_data`` will be passed to the callback function. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
run_async (:obj:`bool`): Determines whether the callback will run asynchronously.
|
||||
Defaults to :obj:`False`.
|
||||
|
||||
Attributes:
|
||||
callback (:obj:`callable`): The callback function for this handler.
|
||||
pass_update_queue (:obj:`bool`): Determines whether ``update_queue`` will be
|
||||
@@ -35,49 +81,15 @@ class ChosenInlineResultHandler(Handler):
|
||||
the callback function.
|
||||
pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to
|
||||
the callback function.
|
||||
|
||||
Note:
|
||||
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you
|
||||
can use to keep any data in will be sent to the :attr:`callback` function. Related to
|
||||
either the user or the chat that the update was sent in. For each update from the same user
|
||||
or in the same chat, it will be the same ``dict``.
|
||||
|
||||
Note that this is DEPRECATED, and you should use context based callbacks. See
|
||||
https://git.io/fxJuV for more info.
|
||||
|
||||
Args:
|
||||
callback (:obj:`callable`): The callback function for this handler. Will be called when
|
||||
:attr:`check_update` has determined that an update should be processed by this handler.
|
||||
Callback signature for context based API:
|
||||
|
||||
``def callback(update: Update, context: CallbackContext)``
|
||||
|
||||
The return value of the callback is usually ignored except for the special case of
|
||||
:class:`telegram.ext.ConversationHandler`.
|
||||
pass_update_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called
|
||||
``update_queue`` will be passed to the callback function. It will be the ``Queue``
|
||||
instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher`
|
||||
that contains new updates which can be used to insert updates. Default is ``False``.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_job_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called
|
||||
``job_queue`` will be passed to the callback function. It will be a
|
||||
:class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater`
|
||||
which can be used to schedule new jobs. Default is ``False``.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_user_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called
|
||||
``user_data`` will be passed to the callback function. Default is ``False``.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_chat_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called
|
||||
``chat_data`` will be passed to the callback function. Default is ``False``.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
run_async (:obj:`bool`): Determines whether the callback will run asynchronously.
|
||||
|
||||
"""
|
||||
|
||||
def check_update(self, update):
|
||||
def check_update(self, update: object) -> Optional[Union[bool, object]]:
|
||||
"""Determines whether an update should be passed to this handlers :attr:`callback`.
|
||||
|
||||
Args:
|
||||
update (:class:`telegram.Update`): Incoming telegram update.
|
||||
update (:class:`telegram.Update` | :obj:`object`): Incoming update.
|
||||
|
||||
Returns:
|
||||
:obj:`bool`
|
||||
|
||||
+227
-123
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2018
|
||||
# Copyright (C) 2015-2021
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -19,17 +19,23 @@
|
||||
"""This module contains the CommandHandler and PrefixHandler classes."""
|
||||
import re
|
||||
import warnings
|
||||
from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Tuple, TypeVar, Union
|
||||
|
||||
from future.utils import string_types
|
||||
|
||||
from telegram.ext import Filters
|
||||
from telegram import MessageEntity, Update
|
||||
from telegram.ext import BaseFilter, Filters
|
||||
from telegram.utils.deprecate import TelegramDeprecationWarning
|
||||
from telegram.utils.types import SLT
|
||||
from telegram.utils.helpers import DefaultValue, DEFAULT_FALSE
|
||||
|
||||
from telegram import Update, MessageEntity
|
||||
from .handler import Handler
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram.ext import CallbackContext, Dispatcher
|
||||
|
||||
class CommandHandler(Handler):
|
||||
RT = TypeVar('RT')
|
||||
|
||||
|
||||
class CommandHandler(Handler[Update]):
|
||||
"""Handler class to handle Telegram commands.
|
||||
|
||||
Commands are Telegram messages that start with ``/``, optionally followed by an ``@`` and the
|
||||
@@ -40,39 +46,26 @@ class CommandHandler(Handler):
|
||||
By default the handler listens to messages as well as edited messages. To change this behavior
|
||||
use ``~Filters.update.edited_message`` in the filter argument.
|
||||
|
||||
Attributes:
|
||||
command (:obj:`str` | List[:obj:`str`]): The command or list of commands this handler
|
||||
should listen for. Limitations are the same as described here
|
||||
https://core.telegram.org/bots#commands
|
||||
callback (:obj:`callable`): The callback function for this handler.
|
||||
filters (:class:`telegram.ext.BaseFilter`): Optional. Only allow updates with these
|
||||
Filters.
|
||||
allow_edited (:obj:`bool`): Determines Whether the handler should also accept
|
||||
edited messages.
|
||||
pass_args (:obj:`bool`): Determines whether the handler should be passed
|
||||
``args``.
|
||||
pass_update_queue (:obj:`bool`): Determines whether ``update_queue`` will be
|
||||
passed to the callback function.
|
||||
pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to
|
||||
the callback function.
|
||||
pass_user_data (:obj:`bool`): Determines whether ``user_data`` will be passed to
|
||||
the callback function.
|
||||
pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to
|
||||
the callback function.
|
||||
Note:
|
||||
:class:`telegram.ext.CommandHandler` does *not* handle (edited) channel posts.
|
||||
|
||||
Note:
|
||||
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you
|
||||
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a :obj:`dict` you
|
||||
can use to keep any data in will be sent to the :attr:`callback` function. Related to
|
||||
either the user or the chat that the update was sent in. For each update from the same user
|
||||
or in the same chat, it will be the same ``dict``.
|
||||
or in the same chat, it will be the same :obj:`dict`.
|
||||
|
||||
Note that this is DEPRECATED, and you should use context based callbacks. See
|
||||
https://git.io/fxJuV for more info.
|
||||
|
||||
Warning:
|
||||
When setting ``run_async`` to :obj:`True`, you cannot rely on adding custom
|
||||
attributes to :class:`telegram.ext.CallbackContext`. See its docs for more info.
|
||||
|
||||
Args:
|
||||
command (:obj:`str` | List[:obj:`str`]): The command or list of commands this handler
|
||||
should listen for. Limitations are the same as described here
|
||||
https://core.telegram.org/bots#commands
|
||||
command (:class:`telegram.utils.types.SLT[str]`):
|
||||
The command or list of commands this handler should listen for.
|
||||
Limitations are the same as described here https://core.telegram.org/bots#commands
|
||||
callback (:obj:`callable`): The callback function for this handler. Will be called when
|
||||
:attr:`check_update` has determined that an update should be processed by this handler.
|
||||
Callback signature for context based API:
|
||||
@@ -86,53 +79,81 @@ class CommandHandler(Handler):
|
||||
:class:`telegram.ext.filters.Filters`. Filters can be combined using bitwise
|
||||
operators (& for and, | for or, ~ for not).
|
||||
allow_edited (:obj:`bool`, optional): Determines whether the handler should also accept
|
||||
edited messages. Default is ``False``.
|
||||
edited messages. Default is :obj:`False`.
|
||||
DEPRECATED: Edited is allowed by default. To change this behavior use
|
||||
``~Filters.update.edited_message``.
|
||||
pass_args (:obj:`bool`, optional): Determines whether the handler should be passed the
|
||||
arguments passed to the command as a keyword argument called ``args``. It will contain
|
||||
a list of strings, which is the text following the command split on single or
|
||||
consecutive whitespace characters. Default is ``False``
|
||||
consecutive whitespace characters. Default is :obj:`False`
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_update_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called
|
||||
pass_update_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``update_queue`` will be passed to the callback function. It will be the ``Queue``
|
||||
instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher`
|
||||
that contains new updates which can be used to insert updates. Default is ``False``.
|
||||
that contains new updates which can be used to insert updates. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_job_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called
|
||||
pass_job_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``job_queue`` will be passed to the callback function. It will be a
|
||||
:class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater`
|
||||
which can be used to schedule new jobs. Default is ``False``.
|
||||
which can be used to schedule new jobs. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_user_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called
|
||||
``user_data`` will be passed to the callback function. Default is ``False``.
|
||||
pass_user_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``user_data`` will be passed to the callback function. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_chat_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called
|
||||
``chat_data`` will be passed to the callback function. Default is ``False``.
|
||||
pass_chat_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``chat_data`` will be passed to the callback function. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
run_async (:obj:`bool`): Determines whether the callback will run asynchronously.
|
||||
Defaults to :obj:`False`.
|
||||
|
||||
Raises:
|
||||
ValueError - when command is too long or has illegal chars.
|
||||
|
||||
Attributes:
|
||||
command (:class:`telegram.utils.types.SLT[str]`):
|
||||
The command or list of commands this handler should listen for.
|
||||
Limitations are the same as described here https://core.telegram.org/bots#commands
|
||||
callback (:obj:`callable`): The callback function for this handler.
|
||||
filters (:class:`telegram.ext.BaseFilter`): Optional. Only allow updates with these
|
||||
Filters.
|
||||
allow_edited (:obj:`bool`): Determines whether the handler should also accept
|
||||
edited messages.
|
||||
pass_args (:obj:`bool`): Determines whether the handler should be passed
|
||||
``args``.
|
||||
pass_update_queue (:obj:`bool`): Determines whether ``update_queue`` will be
|
||||
passed to the callback function.
|
||||
pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to
|
||||
the callback function.
|
||||
pass_user_data (:obj:`bool`): Determines whether ``user_data`` will be passed to
|
||||
the callback function.
|
||||
pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to
|
||||
the callback function.
|
||||
run_async (:obj:`bool`): Determines whether the callback will run asynchronously.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
command,
|
||||
callback,
|
||||
filters=None,
|
||||
allow_edited=None,
|
||||
pass_args=False,
|
||||
pass_update_queue=False,
|
||||
pass_job_queue=False,
|
||||
pass_user_data=False,
|
||||
pass_chat_data=False):
|
||||
super(CommandHandler, self).__init__(
|
||||
def __init__(
|
||||
self,
|
||||
command: SLT[str],
|
||||
callback: Callable[[Update, 'CallbackContext'], RT],
|
||||
filters: BaseFilter = None,
|
||||
allow_edited: bool = None,
|
||||
pass_args: bool = False,
|
||||
pass_update_queue: bool = False,
|
||||
pass_job_queue: bool = False,
|
||||
pass_user_data: bool = False,
|
||||
pass_chat_data: bool = False,
|
||||
run_async: Union[bool, DefaultValue] = DEFAULT_FALSE,
|
||||
):
|
||||
super().__init__(
|
||||
callback,
|
||||
pass_update_queue=pass_update_queue,
|
||||
pass_job_queue=pass_job_queue,
|
||||
pass_user_data=pass_user_data,
|
||||
pass_chat_data=pass_chat_data)
|
||||
pass_chat_data=pass_chat_data,
|
||||
run_async=run_async,
|
||||
)
|
||||
|
||||
if isinstance(command, string_types):
|
||||
if isinstance(command, str):
|
||||
self.command = [command.lower()]
|
||||
else:
|
||||
self.command = [x.lower() for x in command]
|
||||
@@ -146,53 +167,76 @@ class CommandHandler(Handler):
|
||||
self.filters = Filters.update.messages
|
||||
|
||||
if allow_edited is not None:
|
||||
warnings.warn('allow_edited is deprecated. See https://git.io/fxJuV for more info',
|
||||
TelegramDeprecationWarning,
|
||||
stacklevel=2)
|
||||
warnings.warn(
|
||||
'allow_edited is deprecated. See https://git.io/fxJuV for more info',
|
||||
TelegramDeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
if not allow_edited:
|
||||
self.filters &= ~Filters.update.edited_message
|
||||
self.pass_args = pass_args
|
||||
|
||||
def check_update(self, update):
|
||||
def check_update(
|
||||
self, update: object
|
||||
) -> Optional[Union[bool, Tuple[List[str], Optional[Union[bool, Dict]]]]]:
|
||||
"""Determines whether an update should be passed to this handlers :attr:`callback`.
|
||||
|
||||
Args:
|
||||
update (:class:`telegram.Update`): Incoming telegram update.
|
||||
update (:class:`telegram.Update` | :obj:`object`): Incoming update.
|
||||
|
||||
Returns:
|
||||
:obj:`list`: The list of args for the handler
|
||||
:obj:`list`: The list of args for the handler.
|
||||
|
||||
"""
|
||||
if isinstance(update, Update) and update.effective_message:
|
||||
message = update.effective_message
|
||||
|
||||
if (message.entities and message.entities[0].type == MessageEntity.BOT_COMMAND
|
||||
and message.entities[0].offset == 0):
|
||||
command = message.text[1:message.entities[0].length]
|
||||
if (
|
||||
message.entities
|
||||
and message.entities[0].type == MessageEntity.BOT_COMMAND
|
||||
and message.entities[0].offset == 0
|
||||
and message.text
|
||||
and message.bot
|
||||
):
|
||||
command = message.text[1 : message.entities[0].length]
|
||||
args = message.text.split()[1:]
|
||||
command = command.split('@')
|
||||
command.append(message.bot.username)
|
||||
command_parts = command.split('@')
|
||||
command_parts.append(message.bot.username)
|
||||
|
||||
if not (command[0].lower() in self.command
|
||||
and command[1].lower() == message.bot.username.lower()):
|
||||
if not (
|
||||
command_parts[0].lower() in self.command
|
||||
and command_parts[1].lower() == message.bot.username.lower()
|
||||
):
|
||||
return None
|
||||
|
||||
filter_result = self.filters(update)
|
||||
if filter_result:
|
||||
return args, filter_result
|
||||
else:
|
||||
return False
|
||||
return False
|
||||
return None
|
||||
|
||||
def collect_optional_args(self, dispatcher, update=None, check_result=None):
|
||||
optional_args = super(CommandHandler, self).collect_optional_args(dispatcher, update)
|
||||
if self.pass_args:
|
||||
def collect_optional_args(
|
||||
self,
|
||||
dispatcher: 'Dispatcher',
|
||||
update: Update = None,
|
||||
check_result: Optional[Union[bool, Tuple[List[str], Optional[bool]]]] = None,
|
||||
) -> Dict[str, object]:
|
||||
optional_args = super().collect_optional_args(dispatcher, update)
|
||||
if self.pass_args and isinstance(check_result, tuple):
|
||||
optional_args['args'] = check_result[0]
|
||||
return optional_args
|
||||
|
||||
def collect_additional_context(self, context, update, dispatcher, check_result):
|
||||
context.args = check_result[0]
|
||||
if isinstance(check_result[1], dict):
|
||||
context.update(check_result[1])
|
||||
def collect_additional_context(
|
||||
self,
|
||||
context: 'CallbackContext',
|
||||
update: Update,
|
||||
dispatcher: 'Dispatcher',
|
||||
check_result: Optional[Union[bool, Tuple[List[str], Optional[bool]]]],
|
||||
) -> None:
|
||||
if isinstance(check_result, tuple):
|
||||
context.args = check_result[0]
|
||||
if isinstance(check_result[1], dict):
|
||||
context.update(check_result[1])
|
||||
|
||||
|
||||
class PrefixHandler(CommandHandler):
|
||||
@@ -215,7 +259,7 @@ class PrefixHandler(CommandHandler):
|
||||
PrefixHandler(['!', '#'], 'test', callback) will respond to '!test' and
|
||||
'#test'.
|
||||
|
||||
Miltiple prefixes and commands:
|
||||
Multiple prefixes and commands:
|
||||
|
||||
PrefixHandler(['!', '#'], ['test', 'help`], callback) will respond to '!test',
|
||||
'#test', '!help' and '#help'.
|
||||
@@ -225,9 +269,6 @@ class PrefixHandler(CommandHandler):
|
||||
use ~``Filters.update.edited_message``.
|
||||
|
||||
Attributes:
|
||||
prefix (:obj:`str` | List[:obj:`str`]): The prefix(es) that will precede :attr:`command`.
|
||||
command (:obj:`str` | List[:obj:`str`]): The command or list of commands this handler
|
||||
should listen for.
|
||||
callback (:obj:`callable`): The callback function for this handler.
|
||||
filters (:class:`telegram.ext.BaseFilter`): Optional. Only allow updates with these
|
||||
Filters.
|
||||
@@ -241,6 +282,7 @@ class PrefixHandler(CommandHandler):
|
||||
the callback function.
|
||||
pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to
|
||||
the callback function.
|
||||
run_async (:obj:`bool`): Determines whether the callback will run asynchronously.
|
||||
|
||||
Note:
|
||||
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you
|
||||
@@ -251,10 +293,15 @@ class PrefixHandler(CommandHandler):
|
||||
Note that this is DEPRECATED, and you should use context based callbacks. See
|
||||
https://git.io/fxJuV for more info.
|
||||
|
||||
Warning:
|
||||
When setting ``run_async`` to :obj:`True`, you cannot rely on adding custom
|
||||
attributes to :class:`telegram.ext.CallbackContext`. See its docs for more info.
|
||||
|
||||
Args:
|
||||
prefix (:obj:`str` | List[:obj:`str`]): The prefix(es) that will precede :attr:`command`.
|
||||
command (:obj:`str` | List[:obj:`str`]): The command or list of commands this handler
|
||||
should listen for.
|
||||
prefix (:class:`telegram.utils.types.SLT[str]`):
|
||||
The prefix(es) that will precede :attr:`command`.
|
||||
command (:class:`telegram.utils.types.SLT[str]`):
|
||||
The command or list of commands this handler should listen for.
|
||||
callback (:obj:`callable`): The callback function for this handler. Will be called when
|
||||
:attr:`check_update` has determined that an update should be processed by this handler.
|
||||
Callback signature for context based API:
|
||||
@@ -270,63 +317,113 @@ class PrefixHandler(CommandHandler):
|
||||
pass_args (:obj:`bool`, optional): Determines whether the handler should be passed the
|
||||
arguments passed to the command as a keyword argument called ``args``. It will contain
|
||||
a list of strings, which is the text following the command split on single or
|
||||
consecutive whitespace characters. Default is ``False``
|
||||
consecutive whitespace characters. Default is :obj:`False`
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_update_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called
|
||||
pass_update_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``update_queue`` will be passed to the callback function. It will be the ``Queue``
|
||||
instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher`
|
||||
that contains new updates which can be used to insert updates. Default is ``False``.
|
||||
that contains new updates which can be used to insert updates. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_job_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called
|
||||
pass_job_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``job_queue`` will be passed to the callback function. It will be a
|
||||
:class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater`
|
||||
which can be used to schedule new jobs. Default is ``False``.
|
||||
which can be used to schedule new jobs. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_user_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called
|
||||
``user_data`` will be passed to the callback function. Default is ``False``.
|
||||
pass_user_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``user_data`` will be passed to the callback function. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_chat_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called
|
||||
``chat_data`` will be passed to the callback function. Default is ``False``.
|
||||
pass_chat_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``chat_data`` will be passed to the callback function. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
run_async (:obj:`bool`): Determines whether the callback will run asynchronously.
|
||||
Defaults to :obj:`False`.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
prefix,
|
||||
command,
|
||||
callback,
|
||||
filters=None,
|
||||
pass_args=False,
|
||||
pass_update_queue=False,
|
||||
pass_job_queue=False,
|
||||
pass_user_data=False,
|
||||
pass_chat_data=False):
|
||||
def __init__(
|
||||
self,
|
||||
prefix: SLT[str],
|
||||
command: SLT[str],
|
||||
callback: Callable[[Update, 'CallbackContext'], RT],
|
||||
filters: BaseFilter = None,
|
||||
pass_args: bool = False,
|
||||
pass_update_queue: bool = False,
|
||||
pass_job_queue: bool = False,
|
||||
pass_user_data: bool = False,
|
||||
pass_chat_data: bool = False,
|
||||
run_async: Union[bool, DefaultValue] = DEFAULT_FALSE,
|
||||
):
|
||||
|
||||
super(PrefixHandler, self).__init__(
|
||||
'nocommand', callback, filters=filters, allow_edited=None, pass_args=pass_args,
|
||||
self._prefix: List[str] = list()
|
||||
self._command: List[str] = list()
|
||||
self._commands: List[str] = list()
|
||||
|
||||
super().__init__(
|
||||
'nocommand',
|
||||
callback,
|
||||
filters=filters,
|
||||
allow_edited=None,
|
||||
pass_args=pass_args,
|
||||
pass_update_queue=pass_update_queue,
|
||||
pass_job_queue=pass_job_queue,
|
||||
pass_user_data=pass_user_data,
|
||||
pass_chat_data=pass_chat_data)
|
||||
pass_chat_data=pass_chat_data,
|
||||
run_async=run_async,
|
||||
)
|
||||
|
||||
if isinstance(prefix, string_types):
|
||||
self.prefix = [prefix.lower()]
|
||||
else:
|
||||
self.prefix = prefix
|
||||
if isinstance(command, string_types):
|
||||
self.command = [command.lower()]
|
||||
else:
|
||||
self.command = command
|
||||
self.command = [x.lower() + y.lower() for x in self.prefix for y in self.command]
|
||||
self.prefix = prefix # type: ignore[assignment]
|
||||
self.command = command # type: ignore[assignment]
|
||||
self._build_commands()
|
||||
|
||||
def check_update(self, update):
|
||||
@property
|
||||
def prefix(self) -> List[str]:
|
||||
"""
|
||||
The prefixes that will precede :attr:`command`.
|
||||
|
||||
Returns:
|
||||
List[:obj:`str`]
|
||||
"""
|
||||
return self._prefix
|
||||
|
||||
@prefix.setter
|
||||
def prefix(self, prefix: Union[str, List[str]]) -> None:
|
||||
if isinstance(prefix, str):
|
||||
self._prefix = [prefix.lower()]
|
||||
else:
|
||||
self._prefix = prefix
|
||||
self._build_commands()
|
||||
|
||||
@property # type: ignore[override]
|
||||
def command(self) -> List[str]: # type: ignore[override]
|
||||
"""
|
||||
The list of commands this handler should listen for.
|
||||
|
||||
Returns:
|
||||
List[:obj:`str`]
|
||||
"""
|
||||
return self._command
|
||||
|
||||
@command.setter
|
||||
def command(self, command: Union[str, List[str]]) -> None:
|
||||
if isinstance(command, str):
|
||||
self._command = [command.lower()]
|
||||
else:
|
||||
self._command = command
|
||||
self._build_commands()
|
||||
|
||||
def _build_commands(self) -> None:
|
||||
self._commands = [x.lower() + y.lower() for x in self.prefix for y in self.command]
|
||||
|
||||
def check_update(
|
||||
self, update: object
|
||||
) -> Optional[Union[bool, Tuple[List[str], Optional[Union[bool, Dict]]]]]:
|
||||
"""Determines whether an update should be passed to this handlers :attr:`callback`.
|
||||
|
||||
Args:
|
||||
update (:class:`telegram.Update`): Incoming telegram update.
|
||||
update (:class:`telegram.Update` | :obj:`object`): Incoming update.
|
||||
|
||||
Returns:
|
||||
:obj:`list`: The list of args for the handler
|
||||
:obj:`list`: The list of args for the handler.
|
||||
|
||||
"""
|
||||
if isinstance(update, Update) and update.effective_message:
|
||||
@@ -334,15 +431,22 @@ class PrefixHandler(CommandHandler):
|
||||
|
||||
if message.text:
|
||||
text_list = message.text.split()
|
||||
if text_list[0].lower() not in self.command:
|
||||
if text_list[0].lower() not in self._commands:
|
||||
return None
|
||||
filter_result = self.filters(update)
|
||||
if filter_result:
|
||||
return text_list[1:], filter_result
|
||||
else:
|
||||
return False
|
||||
return False
|
||||
return None
|
||||
|
||||
def collect_additional_context(self, context, update, dispatcher, check_result):
|
||||
context.args = check_result[0]
|
||||
if isinstance(check_result[1], dict):
|
||||
context.update(check_result[1])
|
||||
def collect_additional_context(
|
||||
self,
|
||||
context: 'CallbackContext',
|
||||
update: Update,
|
||||
dispatcher: 'Dispatcher',
|
||||
check_result: Optional[Union[bool, Tuple[List[str], Optional[bool]]]],
|
||||
) -> None:
|
||||
if isinstance(check_result, tuple):
|
||||
context.args = check_result[0]
|
||||
if isinstance(check_result[1], dict):
|
||||
context.update(check_result[1])
|
||||
|
||||
+386
-148
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2018
|
||||
# Copyright (C) 2015-2021
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -16,33 +16,68 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
# pylint: disable=R0201
|
||||
"""This module contains the ConversationHandler."""
|
||||
|
||||
import logging
|
||||
import warnings
|
||||
from threading import Lock
|
||||
from typing import TYPE_CHECKING, Dict, List, NoReturn, Optional, Tuple, cast, ClassVar
|
||||
|
||||
from telegram import Update
|
||||
from telegram.ext import (Handler, CallbackQueryHandler, InlineQueryHandler,
|
||||
ChosenInlineResultHandler, CallbackContext)
|
||||
from telegram.utils.promise import Promise
|
||||
from telegram.ext import (
|
||||
BasePersistence,
|
||||
CallbackContext,
|
||||
CallbackQueryHandler,
|
||||
ChosenInlineResultHandler,
|
||||
DispatcherHandlerStop,
|
||||
Handler,
|
||||
InlineQueryHandler,
|
||||
)
|
||||
from telegram.ext.utils.promise import Promise
|
||||
from telegram.utils.types import ConversationDict
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram.ext import Dispatcher, Job
|
||||
CheckUpdateType = Optional[Tuple[Tuple[int, ...], Handler, object]]
|
||||
|
||||
|
||||
class _ConversationTimeoutContext(object):
|
||||
def __init__(self, conversation_key, update, dispatcher):
|
||||
class _ConversationTimeoutContext:
|
||||
def __init__(
|
||||
self,
|
||||
conversation_key: Tuple[int, ...],
|
||||
update: Update,
|
||||
dispatcher: 'Dispatcher',
|
||||
callback_context: Optional[CallbackContext],
|
||||
):
|
||||
self.conversation_key = conversation_key
|
||||
self.update = update
|
||||
self.dispatcher = dispatcher
|
||||
self.callback_context = callback_context
|
||||
|
||||
|
||||
class ConversationHandler(Handler):
|
||||
class ConversationHandler(Handler[Update]):
|
||||
"""
|
||||
A handler to hold a conversation with a single user by managing four collections of other
|
||||
handlers. Note that neither posts in Telegram Channels, nor group interactions with multiple
|
||||
users are managed by instances of this class.
|
||||
A handler to hold a conversation with a single or multiple users through Telegram updates by
|
||||
managing four collections of other handlers.
|
||||
|
||||
Note:
|
||||
``ConversationHandler`` will only accept updates that are (subclass-)instances of
|
||||
:class:`telegram.Update`. This is, because depending on the :attr:`per_user` and
|
||||
:attr:`per_chat` ``ConversationHandler`` relies on
|
||||
:attr:`telegram.Update.effective_user` and/or :attr:`telegram.Update.effective_chat` in
|
||||
order to determine which conversation an update should belong to. For ``per_message=True``,
|
||||
``ConversationHandler`` uses ``update.callback_query.message.message_id`` when
|
||||
``per_chat=True`` and ``update.callback_query.inline_message_id`` when ``per_chat=False``.
|
||||
For a more detailed explanation, please see our `FAQ`_.
|
||||
|
||||
Finally, ``ConversationHandler``, does *not* handle (edited) channel posts.
|
||||
|
||||
.. _`FAQ`: https://git.io/JtcyU
|
||||
|
||||
The first collection, a ``list`` named :attr:`entry_points`, is used to initiate the
|
||||
conversation, for example with a :class:`telegram.ext.CommandHandler` or
|
||||
:class:`telegram.ext.RegexHandler`.
|
||||
:class:`telegram.ext.MessageHandler`.
|
||||
|
||||
The second collection, a ``dict`` named :attr:`states`, contains the different conversation
|
||||
steps and one or more associated handlers that should be used if the user sends a message when
|
||||
@@ -58,11 +93,13 @@ class ConversationHandler(Handler):
|
||||
user know their message was not recognized.
|
||||
|
||||
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,
|
||||
state after responding to the user. If it does not return anything (returning :obj:`None` by
|
||||
default), the state will not change. If an entry point callback function returns :obj:`None`,
|
||||
the conversation ends immediately after the execution of this callback function.
|
||||
To end the conversation, the callback function must return :attr:`END` or ``-1``. To
|
||||
handle the conversation timeout, use handler :attr:`TIMEOUT` or ``-2``.
|
||||
Finally, :class:`telegram.ext.DispatcherHandlerStop` can be used in conversations as described
|
||||
in the corresponding documentation.
|
||||
|
||||
Note:
|
||||
In each of the described collections of handlers, a handler may in turn be a
|
||||
@@ -78,6 +115,54 @@ class ConversationHandler(Handler):
|
||||
|
||||
.. _`examples`: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples
|
||||
|
||||
Args:
|
||||
entry_points (List[:class:`telegram.ext.Handler`]): A list of ``Handler`` objects that can
|
||||
trigger the start of the conversation. The first handler which :attr:`check_update`
|
||||
method returns :obj:`True` will be used. If all return :obj:`False`, the update is not
|
||||
handled.
|
||||
states (Dict[:obj:`object`, List[:class:`telegram.ext.Handler`]]): A :obj:`dict` that
|
||||
defines the different states of conversation a user can be in and one or more
|
||||
associated ``Handler`` objects that should be used in that state. The first handler
|
||||
which :attr:`check_update` method returns :obj:`True` will be used.
|
||||
fallbacks (List[:class:`telegram.ext.Handler`]): A list of handlers that might be used if
|
||||
the user is in a conversation, but every handler for their current state returned
|
||||
:obj:`False` on :attr:`check_update`. The first handler which :attr:`check_update`
|
||||
method returns :obj:`True` will be used. If all return :obj:`False`, the update is not
|
||||
handled.
|
||||
allow_reentry (:obj:`bool`, optional): If set to :obj:`True`, a user that is currently in a
|
||||
conversation can restart the conversation by triggering one of the entry points.
|
||||
per_chat (:obj:`bool`, optional): If the conversationkey should contain the Chat's ID.
|
||||
Default is :obj:`True`.
|
||||
per_user (:obj:`bool`, optional): If the conversationkey should contain the User's ID.
|
||||
Default is :obj:`True`.
|
||||
per_message (:obj:`bool`, optional): If the conversationkey should contain the Message's
|
||||
ID. Default is :obj:`False`.
|
||||
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 or :obj:`None` (default), there will be no timeout. The last
|
||||
received update and the corresponding ``context`` will be handled by ALL the handler's
|
||||
who's :attr:`check_update` method returns :obj:`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.
|
||||
run_async (:obj:`bool`, optional): Pass :obj:`True` to *override* the
|
||||
:attr:`Handler.run_async` setting of all handlers (in :attr:`entry_points`,
|
||||
:attr:`states` and :attr:`fallbacks`).
|
||||
|
||||
Note:
|
||||
If set to :obj:`True`, you should not pass a handler instance, that needs to be
|
||||
run synchronously in another context.
|
||||
|
||||
.. versionadded:: 13.2
|
||||
|
||||
Raises:
|
||||
ValueError
|
||||
|
||||
Attributes:
|
||||
entry_points (List[:class:`telegram.ext.Handler`]): A list of ``Handler`` objects that can
|
||||
trigger the start of the conversation.
|
||||
@@ -86,18 +171,19 @@ class ConversationHandler(Handler):
|
||||
associated ``Handler`` objects that should be used in that state.
|
||||
fallbacks (List[:class:`telegram.ext.Handler`]): A list of handlers that might be used if
|
||||
the user is in a conversation, but every handler for their current state returned
|
||||
``False`` on :attr:`check_update`.
|
||||
:obj:`False` on :attr:`check_update`.
|
||||
allow_reentry (:obj:`bool`): Determines if a user can restart a conversation with
|
||||
an entry point.
|
||||
per_chat (:obj:`bool`): If the conversationkey should contain the Chat's ID.
|
||||
per_user (:obj:`bool`): If the conversationkey should contain the User's ID.
|
||||
per_message (:obj:`bool`): If the conversationkey should contain the Message's
|
||||
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
|
||||
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`.
|
||||
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 received update and the corresponding ``context`` will be handled by ALL the
|
||||
handler's who's :attr:`check_update` method returns :obj:`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
|
||||
@@ -105,86 +191,60 @@ class ConversationHandler(Handler):
|
||||
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.
|
||||
run_async (:obj:`bool`): If :obj:`True`, will override the
|
||||
:attr:`Handler.run_async` setting of all internal handlers on initialization.
|
||||
|
||||
Args:
|
||||
entry_points (List[:class:`telegram.ext.Handler`]): A list of ``Handler`` objects that can
|
||||
trigger the start of the conversation. The first handler which :attr:`check_update`
|
||||
method returns ``True`` will be used. If all return ``False``, the update is not
|
||||
handled.
|
||||
states (Dict[:obj:`object`, List[:class:`telegram.ext.Handler`]]): A :obj:`dict` that
|
||||
defines the different states of conversation a user can be in and one or more
|
||||
associated ``Handler`` objects that should be used in that state. The first handler
|
||||
which :attr:`check_update` method returns ``True`` will be used.
|
||||
fallbacks (List[:class:`telegram.ext.Handler`]): A list of handlers that might be used if
|
||||
the user is in a conversation, but every handler for their current state returned
|
||||
``False`` on :attr:`check_update`. The first handler which :attr:`check_update` method
|
||||
returns ``True`` will be used. If all return ``False``, the update is not handled.
|
||||
allow_reentry (:obj:`bool`, optional): If set to ``True``, a user that is currently in a
|
||||
conversation can restart the conversation by triggering one of the entry points.
|
||||
per_chat (:obj:`bool`, optional): If the conversationkey should contain the Chat's ID.
|
||||
Default is ``True``.
|
||||
per_user (:obj:`bool`, optional): If the conversationkey should contain the User's ID.
|
||||
Default is ``True``.
|
||||
per_message (:obj:`bool`, optional): If the conversationkey should contain the Message's
|
||||
ID. Default is ``False``.
|
||||
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 or None (default), there will be no timeout. 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.
|
||||
|
||||
Raises:
|
||||
ValueError
|
||||
.. versionadded:: 13.2
|
||||
|
||||
"""
|
||||
END = -1
|
||||
|
||||
END: ClassVar[int] = -1
|
||||
""":obj:`int`: Used as a constant to return when a conversation is ended."""
|
||||
TIMEOUT = -2
|
||||
TIMEOUT: ClassVar[int] = -2
|
||||
""":obj:`int`: Used as a constant to handle state when a conversation is timed out."""
|
||||
WAITING = -3
|
||||
WAITING: ClassVar[int] = -3
|
||||
""":obj:`int`: Used as a constant to handle state when a conversation is still waiting on the
|
||||
previous ``@run_sync`` decorated running handler to finish."""
|
||||
# pylint: disable=W0231
|
||||
def __init__(
|
||||
self,
|
||||
entry_points: List[Handler],
|
||||
states: Dict[object, List[Handler]],
|
||||
fallbacks: List[Handler],
|
||||
allow_reentry: bool = False,
|
||||
per_chat: bool = True,
|
||||
per_user: bool = True,
|
||||
per_message: bool = False,
|
||||
conversation_timeout: int = None,
|
||||
name: str = None,
|
||||
persistent: bool = False,
|
||||
map_to_parent: Dict[object, object] = None,
|
||||
run_async: bool = False,
|
||||
):
|
||||
self.run_async = run_async
|
||||
|
||||
def __init__(self,
|
||||
entry_points,
|
||||
states,
|
||||
fallbacks,
|
||||
allow_reentry=False,
|
||||
per_chat=True,
|
||||
per_user=True,
|
||||
per_message=False,
|
||||
conversation_timeout=None,
|
||||
name=None,
|
||||
persistent=False,
|
||||
map_to_parent=None):
|
||||
self._entry_points = entry_points
|
||||
self._states = states
|
||||
self._fallbacks = fallbacks
|
||||
|
||||
self.entry_points = entry_points
|
||||
self.states = states
|
||||
self.fallbacks = fallbacks
|
||||
|
||||
self.allow_reentry = allow_reentry
|
||||
self.per_user = per_user
|
||||
self.per_chat = per_chat
|
||||
self.per_message = per_message
|
||||
self.conversation_timeout = conversation_timeout
|
||||
self.name = name
|
||||
self._allow_reentry = allow_reentry
|
||||
self._per_user = per_user
|
||||
self._per_chat = per_chat
|
||||
self._per_message = per_message
|
||||
self._conversation_timeout = conversation_timeout
|
||||
self._name = name
|
||||
if persistent and not self.name:
|
||||
raise ValueError("Conversations can't be persistent when handler is unnamed.")
|
||||
self.persistent = persistent
|
||||
self.persistence = None
|
||||
""":obj:`telegram.ext.BasePersistance`: The persistence used to store conversations.
|
||||
self.persistent: bool = persistent
|
||||
self._persistence: Optional[BasePersistence] = None
|
||||
""":obj:`telegram.ext.BasePersistence`: The persistence used to store conversations.
|
||||
Set by dispatcher"""
|
||||
self.map_to_parent = map_to_parent
|
||||
self._map_to_parent = map_to_parent
|
||||
|
||||
self.timeout_jobs = dict()
|
||||
self.conversations = dict()
|
||||
self.timeout_jobs: Dict[Tuple[int, ...], 'Job'] = dict()
|
||||
self._timeout_jobs_lock = Lock()
|
||||
self._conversations: ConversationDict = dict()
|
||||
self._conversations_lock = Lock()
|
||||
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -192,10 +252,12 @@ class ConversationHandler(Handler):
|
||||
raise ValueError("'per_user', 'per_chat' and 'per_message' can't all be 'False'")
|
||||
|
||||
if self.per_message and not self.per_chat:
|
||||
warnings.warn("If 'per_message=True' is used, 'per_chat=True' should also be used, "
|
||||
"since message IDs are not globally unique.")
|
||||
warnings.warn(
|
||||
"If 'per_message=True' is used, 'per_chat=True' should also be used, "
|
||||
"since message IDs are not globally unique."
|
||||
)
|
||||
|
||||
all_handlers = list()
|
||||
all_handlers: List[Handler] = list()
|
||||
all_handlers.extend(entry_points)
|
||||
all_handlers.extend(fallbacks)
|
||||
|
||||
@@ -205,64 +267,189 @@ class ConversationHandler(Handler):
|
||||
if self.per_message:
|
||||
for handler in all_handlers:
|
||||
if not isinstance(handler, CallbackQueryHandler):
|
||||
warnings.warn("If 'per_message=True', all entry points and state handlers"
|
||||
" must be 'CallbackQueryHandler', since no other handlers "
|
||||
"have a message context.")
|
||||
warnings.warn(
|
||||
"If 'per_message=True', all entry points and state handlers"
|
||||
" must be 'CallbackQueryHandler', since no other handlers "
|
||||
"have a message context."
|
||||
)
|
||||
break
|
||||
else:
|
||||
for handler in all_handlers:
|
||||
if isinstance(handler, CallbackQueryHandler):
|
||||
warnings.warn("If 'per_message=False', 'CallbackQueryHandler' will not be "
|
||||
"tracked for every message.")
|
||||
warnings.warn(
|
||||
"If 'per_message=False', 'CallbackQueryHandler' will not be "
|
||||
"tracked for every message."
|
||||
)
|
||||
break
|
||||
|
||||
if self.per_chat:
|
||||
for handler in all_handlers:
|
||||
if isinstance(handler, (InlineQueryHandler, ChosenInlineResultHandler)):
|
||||
warnings.warn("If 'per_chat=True', 'InlineQueryHandler' can not be used, "
|
||||
"since inline queries have no chat context.")
|
||||
warnings.warn(
|
||||
"If 'per_chat=True', 'InlineQueryHandler' can not be used, "
|
||||
"since inline queries have no chat context."
|
||||
)
|
||||
break
|
||||
|
||||
def _get_key(self, update):
|
||||
if self.run_async:
|
||||
for handler in all_handlers:
|
||||
handler.run_async = True
|
||||
|
||||
@property
|
||||
def entry_points(self) -> List[Handler]:
|
||||
return self._entry_points
|
||||
|
||||
@entry_points.setter
|
||||
def entry_points(self, value: object) -> NoReturn:
|
||||
raise ValueError('You can not assign a new value to entry_points after initialization.')
|
||||
|
||||
@property
|
||||
def states(self) -> Dict[object, List[Handler]]:
|
||||
return self._states
|
||||
|
||||
@states.setter
|
||||
def states(self, value: object) -> NoReturn:
|
||||
raise ValueError('You can not assign a new value to states after initialization.')
|
||||
|
||||
@property
|
||||
def fallbacks(self) -> List[Handler]:
|
||||
return self._fallbacks
|
||||
|
||||
@fallbacks.setter
|
||||
def fallbacks(self, value: object) -> NoReturn:
|
||||
raise ValueError('You can not assign a new value to fallbacks after initialization.')
|
||||
|
||||
@property
|
||||
def allow_reentry(self) -> bool:
|
||||
return self._allow_reentry
|
||||
|
||||
@allow_reentry.setter
|
||||
def allow_reentry(self, value: object) -> NoReturn:
|
||||
raise ValueError('You can not assign a new value to allow_reentry after initialization.')
|
||||
|
||||
@property
|
||||
def per_user(self) -> bool:
|
||||
return self._per_user
|
||||
|
||||
@per_user.setter
|
||||
def per_user(self, value: object) -> NoReturn:
|
||||
raise ValueError('You can not assign a new value to per_user after initialization.')
|
||||
|
||||
@property
|
||||
def per_chat(self) -> bool:
|
||||
return self._per_chat
|
||||
|
||||
@per_chat.setter
|
||||
def per_chat(self, value: object) -> NoReturn:
|
||||
raise ValueError('You can not assign a new value to per_chat after initialization.')
|
||||
|
||||
@property
|
||||
def per_message(self) -> bool:
|
||||
return self._per_message
|
||||
|
||||
@per_message.setter
|
||||
def per_message(self, value: object) -> NoReturn:
|
||||
raise ValueError('You can not assign a new value to per_message after initialization.')
|
||||
|
||||
@property
|
||||
def conversation_timeout(self) -> Optional[int]:
|
||||
return self._conversation_timeout
|
||||
|
||||
@conversation_timeout.setter
|
||||
def conversation_timeout(self, value: object) -> NoReturn:
|
||||
raise ValueError(
|
||||
'You can not assign a new value to conversation_timeout after ' 'initialization.'
|
||||
)
|
||||
|
||||
@property
|
||||
def name(self) -> Optional[str]:
|
||||
return self._name
|
||||
|
||||
@name.setter
|
||||
def name(self, value: object) -> NoReturn:
|
||||
raise ValueError('You can not assign a new value to name after initialization.')
|
||||
|
||||
@property
|
||||
def map_to_parent(self) -> Optional[Dict[object, object]]:
|
||||
return self._map_to_parent
|
||||
|
||||
@map_to_parent.setter
|
||||
def map_to_parent(self, value: object) -> NoReturn:
|
||||
raise ValueError('You can not assign a new value to map_to_parent after initialization.')
|
||||
|
||||
@property
|
||||
def persistence(self) -> Optional[BasePersistence]:
|
||||
return self._persistence
|
||||
|
||||
@persistence.setter
|
||||
def persistence(self, persistence: BasePersistence) -> None:
|
||||
self._persistence = persistence
|
||||
# Set persistence for nested conversations
|
||||
for handlers in self.states.values():
|
||||
for handler in handlers:
|
||||
if isinstance(handler, ConversationHandler):
|
||||
handler.persistence = self.persistence
|
||||
|
||||
@property
|
||||
def conversations(self) -> ConversationDict:
|
||||
return self._conversations
|
||||
|
||||
@conversations.setter
|
||||
def conversations(self, value: ConversationDict) -> None:
|
||||
self._conversations = value
|
||||
# Set conversations for nested conversations
|
||||
for handlers in self.states.values():
|
||||
for handler in handlers:
|
||||
if isinstance(handler, ConversationHandler) and self.persistence and handler.name:
|
||||
handler.conversations = self.persistence.get_conversations(handler.name)
|
||||
|
||||
def _get_key(self, update: Update) -> Tuple[int, ...]:
|
||||
chat = update.effective_chat
|
||||
user = update.effective_user
|
||||
|
||||
key = list()
|
||||
|
||||
if self.per_chat:
|
||||
key.append(chat.id)
|
||||
key.append(chat.id) # type: ignore[union-attr]
|
||||
|
||||
if self.per_user and user is not None:
|
||||
key.append(user.id)
|
||||
|
||||
if self.per_message:
|
||||
key.append(update.callback_query.inline_message_id
|
||||
or update.callback_query.message.message_id)
|
||||
key.append(
|
||||
update.callback_query.inline_message_id # type: ignore[union-attr]
|
||||
or update.callback_query.message.message_id # type: ignore[union-attr]
|
||||
)
|
||||
|
||||
return tuple(key)
|
||||
|
||||
def check_update(self, update):
|
||||
def check_update(self, update: object) -> CheckUpdateType: # pylint: disable=R0911
|
||||
"""
|
||||
Determines whether an update should be handled by this conversationhandler, and if so in
|
||||
which state the conversation currently is.
|
||||
|
||||
Args:
|
||||
update (:class:`telegram.Update`): Incoming telegram update.
|
||||
update (:class:`telegram.Update` | :obj:`object`): Incoming update.
|
||||
|
||||
Returns:
|
||||
:obj:`bool`
|
||||
|
||||
"""
|
||||
if not isinstance(update, Update):
|
||||
return None
|
||||
# Ignore messages in channels
|
||||
if (not isinstance(update, Update)
|
||||
or update.channel_post
|
||||
or self.per_chat and not update.effective_chat
|
||||
or self.per_message and not update.callback_query
|
||||
or update.callback_query and self.per_chat and not update.callback_query.message):
|
||||
if update.channel_post or update.edited_channel_post:
|
||||
return None
|
||||
if self.per_chat and not update.effective_chat:
|
||||
return None
|
||||
if self.per_message and not update.callback_query:
|
||||
return None
|
||||
if update.callback_query and self.per_chat and not update.callback_query.message:
|
||||
return None
|
||||
|
||||
key = self._get_key(update)
|
||||
state = self.conversations.get(key)
|
||||
with self._conversations_lock:
|
||||
state = self.conversations.get(key)
|
||||
|
||||
# Resolve promises
|
||||
if isinstance(state, tuple) and len(state) == 2 and isinstance(state[1], Promise):
|
||||
@@ -275,22 +462,23 @@ class ConversationHandler(Handler):
|
||||
res = res if res is not None else old_state
|
||||
except Exception as exc:
|
||||
self.logger.exception("Promise function raised exception")
|
||||
self.logger.exception("{}".format(exc))
|
||||
self.logger.exception("%s", exc)
|
||||
res = old_state
|
||||
finally:
|
||||
if res is None and old_state is None:
|
||||
res = self.END
|
||||
self.update_state(res, key)
|
||||
state = self.conversations.get(key)
|
||||
with self._conversations_lock:
|
||||
state = self.conversations.get(key)
|
||||
else:
|
||||
handlers = self.states.get(self.WAITING, [])
|
||||
for handler in handlers:
|
||||
check = handler.check_update(update)
|
||||
hdlrs = self.states.get(self.WAITING, [])
|
||||
for hdlr in hdlrs:
|
||||
check = hdlr.check_update(update)
|
||||
if check is not None and check is not False:
|
||||
return key, handler, check
|
||||
return key, hdlr, check
|
||||
return None
|
||||
|
||||
self.logger.debug('selecting conversation %s with state %s' % (str(key), str(state)))
|
||||
self.logger.debug('selecting conversation %s with state %s', str(key), str(state))
|
||||
|
||||
handler = None
|
||||
|
||||
@@ -310,7 +498,7 @@ class ConversationHandler(Handler):
|
||||
if state is not None and not handler:
|
||||
handlers = self.states.get(state)
|
||||
|
||||
for candidate in (handlers or []):
|
||||
for candidate in handlers or []:
|
||||
check = candidate.check_update(update)
|
||||
if check is not None and check is not False:
|
||||
handler = candidate
|
||||
@@ -327,9 +515,15 @@ class ConversationHandler(Handler):
|
||||
else:
|
||||
return None
|
||||
|
||||
return key, handler, check
|
||||
return key, handler, check # type: ignore[return-value]
|
||||
|
||||
def handle_update(self, update, dispatcher, check_result, context=None):
|
||||
def handle_update( # type: ignore[override]
|
||||
self,
|
||||
update: Update,
|
||||
dispatcher: 'Dispatcher',
|
||||
check_result: CheckUpdateType,
|
||||
context: CallbackContext = None,
|
||||
) -> Optional[object]:
|
||||
"""Send the update to the callback for the current state and Handler
|
||||
|
||||
Args:
|
||||
@@ -337,57 +531,101 @@ class ConversationHandler(Handler):
|
||||
handler, and the handler's check result.
|
||||
update (:class:`telegram.Update`): Incoming telegram update.
|
||||
dispatcher (:class:`telegram.ext.Dispatcher`): Dispatcher that originated the Update.
|
||||
context (:class:`telegram.ext.CallbackContext`, optional): The context as provided by
|
||||
the dispatcher.
|
||||
|
||||
"""
|
||||
conversation_key, handler, check_result = check_result
|
||||
new_state = handler.handle_update(update, dispatcher, check_result, context)
|
||||
timeout_job = self.timeout_jobs.pop(conversation_key, None)
|
||||
update = cast(Update, update) # for mypy
|
||||
conversation_key, handler, check_result = check_result # type: ignore[assignment,misc]
|
||||
raise_dp_handler_stop = False
|
||||
|
||||
if timeout_job is not None:
|
||||
timeout_job.schedule_removal()
|
||||
if self.conversation_timeout and new_state != self.END:
|
||||
self.timeout_jobs[conversation_key] = dispatcher.job_queue.run_once(
|
||||
self._trigger_timeout, self.conversation_timeout,
|
||||
context=_ConversationTimeoutContext(conversation_key, update, dispatcher))
|
||||
with self._timeout_jobs_lock:
|
||||
# Remove the old timeout job (if present)
|
||||
timeout_job = self.timeout_jobs.pop(conversation_key, None)
|
||||
|
||||
if timeout_job is not None:
|
||||
timeout_job.schedule_removal()
|
||||
try:
|
||||
new_state = handler.handle_update(update, dispatcher, check_result, context)
|
||||
except DispatcherHandlerStop as exception:
|
||||
new_state = exception.state
|
||||
raise_dp_handler_stop = True
|
||||
with self._timeout_jobs_lock:
|
||||
if self.conversation_timeout and new_state != self.END and dispatcher.job_queue:
|
||||
# Add the new timeout job
|
||||
self.timeout_jobs[conversation_key] = dispatcher.job_queue.run_once(
|
||||
self._trigger_timeout, # type: ignore[arg-type]
|
||||
self.conversation_timeout,
|
||||
context=_ConversationTimeoutContext(
|
||||
conversation_key, update, dispatcher, context
|
||||
),
|
||||
)
|
||||
|
||||
if isinstance(self.map_to_parent, dict) and new_state in self.map_to_parent:
|
||||
self.update_state(self.END, conversation_key)
|
||||
if raise_dp_handler_stop:
|
||||
raise DispatcherHandlerStop(self.map_to_parent.get(new_state))
|
||||
return self.map_to_parent.get(new_state)
|
||||
else:
|
||||
self.update_state(new_state, conversation_key)
|
||||
|
||||
def update_state(self, new_state, key):
|
||||
self.update_state(new_state, conversation_key)
|
||||
if raise_dp_handler_stop:
|
||||
# Don't pass the new state here. If we're in a nested conversation, the parent is
|
||||
# expecting None as return value.
|
||||
raise DispatcherHandlerStop()
|
||||
return None
|
||||
|
||||
def update_state(self, new_state: object, key: Tuple[int, ...]) -> None:
|
||||
if new_state == self.END:
|
||||
if key in self.conversations:
|
||||
# If there is no key in conversations, nothing is done.
|
||||
del self.conversations[key]
|
||||
if self.persistent:
|
||||
self.persistence.update_conversation(self.name, key, None)
|
||||
with self._conversations_lock:
|
||||
if key in self.conversations:
|
||||
# If there is no key in conversations, nothing is done.
|
||||
del self.conversations[key]
|
||||
if self.persistent and self.persistence and self.name:
|
||||
self.persistence.update_conversation(self.name, key, None)
|
||||
|
||||
elif isinstance(new_state, Promise):
|
||||
self.conversations[key] = (self.conversations.get(key), new_state)
|
||||
if self.persistent:
|
||||
self.persistence.update_conversation(self.name, key,
|
||||
(self.conversations.get(key), new_state))
|
||||
with self._conversations_lock:
|
||||
self.conversations[key] = (self.conversations.get(key), new_state)
|
||||
if self.persistent and self.persistence and self.name:
|
||||
self.persistence.update_conversation(
|
||||
self.name, key, (self.conversations.get(key), new_state)
|
||||
)
|
||||
|
||||
elif new_state is not None:
|
||||
self.conversations[key] = new_state
|
||||
if self.persistent:
|
||||
self.persistence.update_conversation(self.name, key, new_state)
|
||||
with self._conversations_lock:
|
||||
self.conversations[key] = new_state
|
||||
if self.persistent and self.persistence and self.name:
|
||||
self.persistence.update_conversation(self.name, key, new_state)
|
||||
|
||||
def _trigger_timeout(self, context, job=None):
|
||||
def _trigger_timeout(self, context: _ConversationTimeoutContext, job: 'Job' = None) -> None:
|
||||
self.logger.debug('conversation timeout was triggered!')
|
||||
|
||||
# Backward compatibility with bots that do not use CallbackContext
|
||||
callback_context = None
|
||||
if isinstance(context, CallbackContext):
|
||||
context = context.job.context
|
||||
else:
|
||||
context = job.context
|
||||
job = context.job
|
||||
|
||||
context = job.context # type:ignore[union-attr,assignment]
|
||||
callback_context = context.callback_context
|
||||
|
||||
with self._timeout_jobs_lock:
|
||||
found_job = self.timeout_jobs[context.conversation_key]
|
||||
if found_job is not job:
|
||||
# The timeout has been canceled in handle_update
|
||||
return
|
||||
del self.timeout_jobs[context.conversation_key]
|
||||
|
||||
del self.timeout_jobs[context.conversation_key]
|
||||
handlers = self.states.get(self.TIMEOUT, [])
|
||||
for handler in handlers:
|
||||
check = handler.check_update(context.update)
|
||||
if check is not None and check is not False:
|
||||
handler.handle_update(context.update, context.dispatcher, check)
|
||||
try:
|
||||
handler.handle_update(
|
||||
context.update, context.dispatcher, check, callback_context
|
||||
)
|
||||
except DispatcherHandlerStop:
|
||||
self.logger.warning(
|
||||
'DispatcherHandlerStop in TIMEOUT state of '
|
||||
'ConversationHandler has no effect. Ignoring.'
|
||||
)
|
||||
self.update_state(self.END, context.conversation_key)
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2020-2021
|
||||
# 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/].
|
||||
# pylint: disable=R0201, E0401
|
||||
"""This module contains the class Defaults, which allows to pass default values to Updater."""
|
||||
from typing import NoReturn, Optional, Union
|
||||
|
||||
import pytz
|
||||
|
||||
from telegram.utils.helpers import DEFAULT_NONE, DefaultValue
|
||||
|
||||
|
||||
class Defaults:
|
||||
"""Convenience Class to gather all parameters with a (user defined) default value
|
||||
|
||||
Parameters:
|
||||
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show
|
||||
bold, italic, fixed-width text or URLs in your bot's message.
|
||||
disable_notification (:obj:`bool`, optional): Sends the message silently. Users will
|
||||
receive a notification with no sound.
|
||||
disable_web_page_preview (:obj:`bool`, optional): Disables link previews for links in this
|
||||
message.
|
||||
allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message
|
||||
should be sent even if the specified replied-to message is not found.
|
||||
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).
|
||||
quote (:obj:`bool`, optional): If set to :obj:`True`, the reply is sent as an actual reply
|
||||
to the message. If ``reply_to_message_id`` is passed in ``kwargs``, this parameter will
|
||||
be ignored. Default: :obj:`True` in group chats and :obj:`False` in private chats.
|
||||
tzinfo (:obj:`tzinfo`, optional): A timezone to be used for all date(time) inputs
|
||||
appearing throughout PTB, i.e. if a timezone naive date(time) object is passed
|
||||
somewhere, it will be assumed to be in ``tzinfo``. Must be a timezone provided by the
|
||||
``pytz`` module. Defaults to UTC.
|
||||
run_async (:obj:`bool`, optional): Default setting for the ``run_async`` parameter of
|
||||
handlers and error handlers registered through :meth:`Dispatcher.add_handler` and
|
||||
:meth:`Dispatcher.add_error_handler`. Defaults to :obj:`False`.
|
||||
|
||||
Attributes:
|
||||
parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show
|
||||
bold, italic, fixed-width text or URLs in your bot's message.
|
||||
disable_notification (:obj:`bool`): Optional. Sends the message silently. Users will
|
||||
receive a notification with no sound.
|
||||
disable_web_page_preview (:obj:`bool`): Optional. Disables link previews for links in this
|
||||
message.
|
||||
allow_sending_without_reply (:obj:`bool`): Optional. Pass :obj:`True`, if the message
|
||||
should be sent even if the specified replied-to message is not found.
|
||||
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).
|
||||
quote (:obj:`bool`): Optional. If set to :obj:`True`, the reply is sent as an actual reply
|
||||
to the message. If ``reply_to_message_id`` is passed in ``kwargs``, this parameter will
|
||||
be ignored. Default: :obj:`True` in group chats and :obj:`False` in private chats.
|
||||
tzinfo (:obj:`tzinfo`): A timezone to be used for all date(time) objects appearing
|
||||
throughout PTB.
|
||||
run_async (:obj:`bool`): Optional. Default setting for the ``run_async`` parameter of
|
||||
handlers and error handlers registered through :meth:`Dispatcher.add_handler` and
|
||||
:meth:`Dispatcher.add_error_handler`.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
parse_mode: str = None,
|
||||
disable_notification: bool = None,
|
||||
disable_web_page_preview: bool = None,
|
||||
# Timeout needs special treatment, since the bot methods have two different
|
||||
# default values for timeout (None and 20s)
|
||||
timeout: Union[float, DefaultValue] = DEFAULT_NONE,
|
||||
quote: bool = None,
|
||||
tzinfo: pytz.BaseTzInfo = pytz.utc,
|
||||
run_async: bool = False,
|
||||
allow_sending_without_reply: bool = None,
|
||||
):
|
||||
self._parse_mode = parse_mode
|
||||
self._disable_notification = disable_notification
|
||||
self._disable_web_page_preview = disable_web_page_preview
|
||||
self._allow_sending_without_reply = allow_sending_without_reply
|
||||
self._timeout = timeout
|
||||
self._quote = quote
|
||||
self._tzinfo = tzinfo
|
||||
self._run_async = run_async
|
||||
|
||||
@property
|
||||
def parse_mode(self) -> Optional[str]:
|
||||
return self._parse_mode
|
||||
|
||||
@parse_mode.setter
|
||||
def parse_mode(self, value: object) -> NoReturn:
|
||||
raise AttributeError(
|
||||
"You can not assign a new value to defaults after because it would "
|
||||
"not have any effect."
|
||||
)
|
||||
|
||||
@property
|
||||
def disable_notification(self) -> Optional[bool]:
|
||||
return self._disable_notification
|
||||
|
||||
@disable_notification.setter
|
||||
def disable_notification(self, value: object) -> NoReturn:
|
||||
raise AttributeError(
|
||||
"You can not assign a new value to defaults after because it would "
|
||||
"not have any effect."
|
||||
)
|
||||
|
||||
@property
|
||||
def disable_web_page_preview(self) -> Optional[bool]:
|
||||
return self._disable_web_page_preview
|
||||
|
||||
@disable_web_page_preview.setter
|
||||
def disable_web_page_preview(self, value: object) -> NoReturn:
|
||||
raise AttributeError(
|
||||
"You can not assign a new value to defaults after because it would "
|
||||
"not have any effect."
|
||||
)
|
||||
|
||||
@property
|
||||
def allow_sending_without_reply(self) -> Optional[bool]:
|
||||
return self._allow_sending_without_reply
|
||||
|
||||
@allow_sending_without_reply.setter
|
||||
def allow_sending_without_reply(self, value: object) -> NoReturn:
|
||||
raise AttributeError(
|
||||
"You can not assign a new value to defaults after because it would "
|
||||
"not have any effect."
|
||||
)
|
||||
|
||||
@property
|
||||
def timeout(self) -> Union[float, DefaultValue]:
|
||||
return self._timeout
|
||||
|
||||
@timeout.setter
|
||||
def timeout(self, value: object) -> NoReturn:
|
||||
raise AttributeError(
|
||||
"You can not assign a new value to defaults after because it would "
|
||||
"not have any effect."
|
||||
)
|
||||
|
||||
@property
|
||||
def quote(self) -> Optional[bool]:
|
||||
return self._quote
|
||||
|
||||
@quote.setter
|
||||
def quote(self, value: object) -> NoReturn:
|
||||
raise AttributeError(
|
||||
"You can not assign a new value to defaults after because it would "
|
||||
"not have any effect."
|
||||
)
|
||||
|
||||
@property
|
||||
def tzinfo(self) -> pytz.BaseTzInfo:
|
||||
return self._tzinfo
|
||||
|
||||
@tzinfo.setter
|
||||
def tzinfo(self, value: object) -> NoReturn:
|
||||
raise AttributeError(
|
||||
"You can not assign a new value to defaults after because it would "
|
||||
"not have any effect."
|
||||
)
|
||||
|
||||
@property
|
||||
def run_async(self) -> bool:
|
||||
return self._run_async
|
||||
|
||||
@run_async.setter
|
||||
def run_async(self, value: object) -> NoReturn:
|
||||
raise AttributeError(
|
||||
"You can not assign a new value to defaults after because it would "
|
||||
"not have any effect."
|
||||
)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(
|
||||
(
|
||||
self._parse_mode,
|
||||
self._disable_notification,
|
||||
self._disable_web_page_preview,
|
||||
self._allow_sending_without_reply,
|
||||
self._timeout,
|
||||
self._quote,
|
||||
self._tzinfo,
|
||||
self._run_async,
|
||||
)
|
||||
)
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if isinstance(other, Defaults):
|
||||
return self.__dict__ == other.__dict__
|
||||
return False
|
||||
|
||||
def __ne__(self, other: object) -> bool:
|
||||
return not self == other
|
||||
+152
-60
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2018
|
||||
# Copyright (C) 2015-2021
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -19,110 +19,170 @@
|
||||
"""This module contains the DictPersistence class."""
|
||||
from copy import deepcopy
|
||||
|
||||
from telegram.utils.helpers import decode_user_chat_data_from_json,\
|
||||
decode_conversations_from_json, enocde_conversations_to_json
|
||||
from typing import DefaultDict, Dict, Optional, Tuple
|
||||
from collections import defaultdict
|
||||
|
||||
from telegram.utils.helpers import (
|
||||
decode_conversations_from_json,
|
||||
decode_user_chat_data_from_json,
|
||||
encode_conversations_to_json,
|
||||
)
|
||||
from telegram.ext import BasePersistence
|
||||
from telegram.utils.types import ConversationDict
|
||||
|
||||
try:
|
||||
import ujson as json
|
||||
except ImportError:
|
||||
import json
|
||||
from collections import defaultdict
|
||||
from telegram.ext import BasePersistence
|
||||
import json # type: ignore[no-redef]
|
||||
|
||||
|
||||
class DictPersistence(BasePersistence):
|
||||
"""Using python's dicts and json for making you bot persistent.
|
||||
"""Using python's dicts and json for making your bot persistent.
|
||||
|
||||
Note:
|
||||
This class does *not* implement a :meth:`flush` method, meaning that data managed by
|
||||
``DictPersistence`` is in-memory only and will be lost when the bot shuts down. This is,
|
||||
because ``DictPersistence`` is mainly intended as starting point for custom persistence
|
||||
classes that need to JSON-serialize the stored data before writing them to file/database.
|
||||
|
||||
Warning:
|
||||
:class:`DictPersistence` will try to replace :class:`telegram.Bot` instances by
|
||||
:attr:`REPLACED_BOT` and insert the bot set with
|
||||
:meth:`telegram.ext.BasePersistence.set_bot` upon loading of the data. This is to ensure
|
||||
that changes to the bot apply to the saved objects, too. If you change the bots token, this
|
||||
may lead to e.g. ``Chat not found`` errors. For the limitations on replacing bots see
|
||||
:meth:`telegram.ext.BasePersistence.replace_bot` and
|
||||
:meth:`telegram.ext.BasePersistence.insert_bot`.
|
||||
|
||||
Args:
|
||||
store_user_data (:obj:`bool`, optional): Whether user_data should be saved by this
|
||||
persistence class. Default is :obj:`True`.
|
||||
store_chat_data (:obj:`bool`, optional): Whether user_data should be saved by this
|
||||
persistence class. Default is :obj:`True`.
|
||||
store_bot_data (:obj:`bool`, optional): Whether bot_data should be saved by this
|
||||
persistence class. Default is :obj:`True` .
|
||||
user_data_json (:obj:`str`, optional): Json string that will be used to reconstruct
|
||||
user_data on creating this persistence. Default is ``""``.
|
||||
chat_data_json (:obj:`str`, optional): Json string that will be used to reconstruct
|
||||
chat_data on creating this persistence. Default is ``""``.
|
||||
bot_data_json (:obj:`str`, optional): Json string that will be used to reconstruct
|
||||
bot_data on creating this persistence. Default is ``""``.
|
||||
conversations_json (:obj:`str`, optional): Json string that will be used to reconstruct
|
||||
conversation on creating this persistence. Default is ``""``.
|
||||
|
||||
Attributes:
|
||||
store_user_data (:obj:`bool`): Whether user_data should be saved by this
|
||||
persistence class.
|
||||
store_chat_data (:obj:`bool`): Whether chat_data should be saved by this
|
||||
persistence class.
|
||||
|
||||
Args:
|
||||
store_user_data (:obj:`bool`, optional): Whether user_data should be saved by this
|
||||
persistence class. Default is ``True``.
|
||||
store_chat_data (:obj:`bool`, optional): Whether user_data should be saved by this
|
||||
persistence class. Default is ``True``.
|
||||
user_data_json (:obj:`str`, optional): Json string that will be used to reconstruct
|
||||
user_data on creating this persistence. Default is ``""``.
|
||||
chat_data_json (:obj:`str`, optional): Json string that will be used to reconstruct
|
||||
chat_data on creating this persistence. Default is ``""``.
|
||||
conversations_json (:obj:`str`, optional): Json string that will be used to reconstruct
|
||||
conversation on creating this persistence. Default is ``""``.
|
||||
store_bot_data (:obj:`bool`): Whether bot_data should be saved by this
|
||||
persistence class.
|
||||
"""
|
||||
|
||||
def __init__(self, store_user_data=True, store_chat_data=True, user_data_json='',
|
||||
chat_data_json='', conversations_json=''):
|
||||
self.store_user_data = store_user_data
|
||||
self.store_chat_data = store_chat_data
|
||||
def __init__(
|
||||
self,
|
||||
store_user_data: bool = True,
|
||||
store_chat_data: bool = True,
|
||||
store_bot_data: bool = True,
|
||||
user_data_json: str = '',
|
||||
chat_data_json: str = '',
|
||||
bot_data_json: str = '',
|
||||
conversations_json: str = '',
|
||||
):
|
||||
super().__init__(
|
||||
store_user_data=store_user_data,
|
||||
store_chat_data=store_chat_data,
|
||||
store_bot_data=store_bot_data,
|
||||
)
|
||||
self._user_data = None
|
||||
self._chat_data = None
|
||||
self._bot_data = None
|
||||
self._conversations = None
|
||||
self._user_data_json = None
|
||||
self._chat_data_json = None
|
||||
self._bot_data_json = None
|
||||
self._conversations_json = None
|
||||
if user_data_json:
|
||||
try:
|
||||
self._user_data = decode_user_chat_data_from_json(user_data_json)
|
||||
self._user_data_json = user_data_json
|
||||
except (ValueError, AttributeError):
|
||||
raise TypeError("Unable to deserialize user_data_json. Not valid JSON")
|
||||
except (ValueError, AttributeError) as exc:
|
||||
raise TypeError("Unable to deserialize user_data_json. Not valid JSON") from exc
|
||||
if chat_data_json:
|
||||
try:
|
||||
self._chat_data = decode_user_chat_data_from_json(chat_data_json)
|
||||
self._chat_data_json = chat_data_json
|
||||
except (ValueError, AttributeError):
|
||||
raise TypeError("Unable to deserialize chat_data_json. Not valid JSON")
|
||||
except (ValueError, AttributeError) as exc:
|
||||
raise TypeError("Unable to deserialize chat_data_json. Not valid JSON") from exc
|
||||
if bot_data_json:
|
||||
try:
|
||||
self._bot_data = json.loads(bot_data_json)
|
||||
self._bot_data_json = bot_data_json
|
||||
except (ValueError, AttributeError) as exc:
|
||||
raise TypeError("Unable to deserialize bot_data_json. Not valid JSON") from exc
|
||||
if not isinstance(self._bot_data, dict):
|
||||
raise TypeError("bot_data_json must be serialized dict")
|
||||
|
||||
if conversations_json:
|
||||
try:
|
||||
self._conversations = decode_conversations_from_json(conversations_json)
|
||||
self._conversations_json = conversations_json
|
||||
except (ValueError, AttributeError):
|
||||
raise TypeError("Unable to deserialize conversations_json. Not valid JSON")
|
||||
except (ValueError, AttributeError) as exc:
|
||||
raise TypeError(
|
||||
"Unable to deserialize conversations_json. Not valid JSON"
|
||||
) from exc
|
||||
|
||||
@property
|
||||
def user_data(self):
|
||||
""":obj:`dict`: The user_data as a dict"""
|
||||
def user_data(self) -> Optional[DefaultDict[int, Dict]]:
|
||||
""":obj:`dict`: The user_data as a dict."""
|
||||
return self._user_data
|
||||
|
||||
@property
|
||||
def user_data_json(self):
|
||||
def user_data_json(self) -> str:
|
||||
""":obj:`str`: The user_data serialized as a JSON-string."""
|
||||
if self._user_data_json:
|
||||
return self._user_data_json
|
||||
else:
|
||||
return json.dumps(self.user_data)
|
||||
return json.dumps(self.user_data)
|
||||
|
||||
@property
|
||||
def chat_data(self):
|
||||
""":obj:`dict`: The chat_data as a dict"""
|
||||
def chat_data(self) -> Optional[DefaultDict[int, Dict]]:
|
||||
""":obj:`dict`: The chat_data as a dict."""
|
||||
return self._chat_data
|
||||
|
||||
@property
|
||||
def chat_data_json(self):
|
||||
def chat_data_json(self) -> str:
|
||||
""":obj:`str`: The chat_data serialized as a JSON-string."""
|
||||
if self._chat_data_json:
|
||||
return self._chat_data_json
|
||||
else:
|
||||
return json.dumps(self.chat_data)
|
||||
return json.dumps(self.chat_data)
|
||||
|
||||
@property
|
||||
def conversations(self):
|
||||
""":obj:`dict`: The conversations as a dict"""
|
||||
def bot_data(self) -> Optional[Dict]:
|
||||
""":obj:`dict`: The bot_data as a dict."""
|
||||
return self._bot_data
|
||||
|
||||
@property
|
||||
def bot_data_json(self) -> str:
|
||||
""":obj:`str`: The bot_data serialized as a JSON-string."""
|
||||
if self._bot_data_json:
|
||||
return self._bot_data_json
|
||||
return json.dumps(self.bot_data)
|
||||
|
||||
@property
|
||||
def conversations(self) -> Optional[Dict[str, Dict[Tuple, object]]]:
|
||||
""":obj:`dict`: The conversations as a dict."""
|
||||
return self._conversations
|
||||
|
||||
@property
|
||||
def conversations_json(self):
|
||||
def conversations_json(self) -> str:
|
||||
""":obj:`str`: The conversations serialized as a JSON-string."""
|
||||
if self._conversations_json:
|
||||
return self._conversations_json
|
||||
else:
|
||||
return enocde_conversations_to_json(self.conversations)
|
||||
return encode_conversations_to_json(self.conversations) # type: ignore[arg-type]
|
||||
|
||||
def get_user_data(self):
|
||||
"""Returns the user_data created from the ``user_data_json`` or an empty defaultdict.
|
||||
def get_user_data(self) -> DefaultDict[int, Dict[object, object]]:
|
||||
"""Returns the user_data created from the ``user_data_json`` or an empty
|
||||
:obj:`defaultdict`.
|
||||
|
||||
Returns:
|
||||
:obj:`defaultdict`: The restored user data.
|
||||
@@ -131,66 +191,98 @@ class DictPersistence(BasePersistence):
|
||||
pass
|
||||
else:
|
||||
self._user_data = defaultdict(dict)
|
||||
return deepcopy(self.user_data)
|
||||
return deepcopy(self.user_data) # type: ignore[arg-type]
|
||||
|
||||
def get_chat_data(self):
|
||||
"""Returns the chat_data created from the ``chat_data_json`` or an empty defaultdict.
|
||||
def get_chat_data(self) -> DefaultDict[int, Dict[object, object]]:
|
||||
"""Returns the chat_data created from the ``chat_data_json`` or an empty
|
||||
:obj:`defaultdict`.
|
||||
|
||||
Returns:
|
||||
:obj:`defaultdict`: The restored user data.
|
||||
:obj:`defaultdict`: The restored chat data.
|
||||
"""
|
||||
if self.chat_data:
|
||||
pass
|
||||
else:
|
||||
self._chat_data = defaultdict(dict)
|
||||
return deepcopy(self.chat_data)
|
||||
return deepcopy(self.chat_data) # type: ignore[arg-type]
|
||||
|
||||
def get_conversations(self, name):
|
||||
"""Returns the conversations created from the ``conversations_json`` or an empty
|
||||
defaultdict.
|
||||
def get_bot_data(self) -> Dict[object, object]:
|
||||
"""Returns the bot_data created from the ``bot_data_json`` or an empty :obj:`dict`.
|
||||
|
||||
Returns:
|
||||
:obj:`defaultdict`: The restored user data.
|
||||
:obj:`dict`: The restored bot data.
|
||||
"""
|
||||
if self.bot_data:
|
||||
pass
|
||||
else:
|
||||
self._bot_data = {}
|
||||
return deepcopy(self.bot_data) # type: ignore[arg-type]
|
||||
|
||||
def get_conversations(self, name: str) -> ConversationDict:
|
||||
"""Returns the conversations created from the ``conversations_json`` or an empty
|
||||
:obj:`dict`.
|
||||
|
||||
Returns:
|
||||
:obj:`dict`: The restored conversations data.
|
||||
"""
|
||||
if self.conversations:
|
||||
pass
|
||||
else:
|
||||
self._conversations = {}
|
||||
return self.conversations.get(name, {}).copy()
|
||||
return self.conversations.get(name, {}).copy() # type: ignore[union-attr]
|
||||
|
||||
def update_conversation(self, name, key, new_state):
|
||||
def update_conversation(
|
||||
self, name: str, key: Tuple[int, ...], new_state: Optional[object]
|
||||
) -> None:
|
||||
"""Will update the conversations for the given handler.
|
||||
|
||||
Args:
|
||||
name (:obj:`str`): The handlers name.
|
||||
name (:obj:`str`): The handler's name.
|
||||
key (:obj:`tuple`): The key the state is changed for.
|
||||
new_state (:obj:`tuple` | :obj:`any`): The new state for the given key.
|
||||
"""
|
||||
if not self._conversations:
|
||||
self._conversations = {}
|
||||
if self._conversations.setdefault(name, {}).get(key) == new_state:
|
||||
return
|
||||
self._conversations[name][key] = new_state
|
||||
self._conversations_json = None
|
||||
|
||||
def update_user_data(self, user_id, data):
|
||||
def update_user_data(self, user_id: int, data: Dict) -> None:
|
||||
"""Will update the user_data (if changed).
|
||||
|
||||
Args:
|
||||
user_id (:obj:`int`): The user the data might have been changed for.
|
||||
data (:obj:`dict`): The :attr:`telegram.ext.dispatcher.user_data` [user_id].
|
||||
"""
|
||||
if self._user_data is None:
|
||||
self._user_data = defaultdict(dict)
|
||||
if self._user_data.get(user_id) == data:
|
||||
return
|
||||
self._user_data[user_id] = data
|
||||
self._user_data_json = None
|
||||
|
||||
def update_chat_data(self, chat_id, data):
|
||||
def update_chat_data(self, chat_id: int, data: Dict) -> None:
|
||||
"""Will update the chat_data (if changed).
|
||||
|
||||
Args:
|
||||
chat_id (:obj:`int`): The chat the data might have been changed for.
|
||||
data (:obj:`dict`): The :attr:`telegram.ext.dispatcher.chat_data` [chat_id].
|
||||
"""
|
||||
if self._chat_data is None:
|
||||
self._chat_data = defaultdict(dict)
|
||||
if self._chat_data.get(chat_id) == data:
|
||||
return
|
||||
self._chat_data[chat_id] = data
|
||||
self._chat_data_json = None
|
||||
|
||||
def update_bot_data(self, data: Dict) -> None:
|
||||
"""Will update the bot_data (if changed).
|
||||
|
||||
Args:
|
||||
data (:obj:`dict`): The :attr:`telegram.ext.dispatcher.bot_data`.
|
||||
"""
|
||||
if self._bot_data == data:
|
||||
return
|
||||
self._bot_data = data.copy()
|
||||
self._bot_data_json = None
|
||||
|
||||
+342
-171
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2018
|
||||
# Copyright (C) 2015-2021
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -21,28 +21,32 @@
|
||||
import logging
|
||||
import warnings
|
||||
import weakref
|
||||
from functools import wraps
|
||||
from threading import Thread, Lock, Event, current_thread, BoundedSemaphore
|
||||
from time import sleep
|
||||
from uuid import uuid4
|
||||
from collections import defaultdict
|
||||
|
||||
from queue import Queue, Empty
|
||||
|
||||
from future.builtins import range
|
||||
from functools import wraps
|
||||
from queue import Empty, Queue
|
||||
from threading import BoundedSemaphore, Event, Lock, Thread, current_thread
|
||||
from time import sleep
|
||||
from typing import TYPE_CHECKING, Callable, DefaultDict, Dict, List, Optional, Set, Union
|
||||
from uuid import uuid4
|
||||
|
||||
from telegram import TelegramError, Update
|
||||
from telegram.ext.handler import Handler
|
||||
from telegram.ext.callbackcontext import CallbackContext
|
||||
from telegram.utils.deprecate import TelegramDeprecationWarning
|
||||
from telegram.utils.promise import Promise
|
||||
from telegram.ext import BasePersistence
|
||||
from telegram.ext.callbackcontext import CallbackContext
|
||||
from telegram.ext.handler import Handler
|
||||
from telegram.utils.deprecate import TelegramDeprecationWarning
|
||||
from telegram.ext.utils.promise import Promise
|
||||
from telegram.utils.helpers import DefaultValue, DEFAULT_FALSE
|
||||
|
||||
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
||||
DEFAULT_GROUP = 0
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Bot
|
||||
from telegram.ext import JobQueue
|
||||
|
||||
DEFAULT_GROUP: int = 0
|
||||
|
||||
|
||||
def run_async(func):
|
||||
def run_async(
|
||||
func: Callable[[Update, CallbackContext], object]
|
||||
) -> Callable[[Update, CallbackContext], object]:
|
||||
"""
|
||||
Function decorator that will run the function in a new thread.
|
||||
|
||||
@@ -50,37 +54,57 @@ def run_async(func):
|
||||
|
||||
Using this decorator is only possible when only a single Dispatcher exist in the system.
|
||||
|
||||
Note:
|
||||
DEPRECATED. Use :attr:`telegram.ext.Dispatcher.run_async` directly instead or the
|
||||
:attr:`Handler.run_async` parameter.
|
||||
|
||||
Warning:
|
||||
If you're using @run_async you cannot rely on adding custom attributes to
|
||||
If you're using ``@run_async`` you cannot rely on adding custom attributes to
|
||||
:class:`telegram.ext.CallbackContext`. See its docs for more info.
|
||||
"""
|
||||
|
||||
@wraps(func)
|
||||
def async_func(*args, **kwargs):
|
||||
return Dispatcher.get_instance().run_async(func, *args, **kwargs)
|
||||
def async_func(*args: object, **kwargs: object) -> object:
|
||||
warnings.warn(
|
||||
'The @run_async decorator is deprecated. Use the `run_async` parameter of '
|
||||
'your Handler or `Dispatcher.run_async` instead.',
|
||||
TelegramDeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return Dispatcher.get_instance()._run_async( # pylint: disable=W0212
|
||||
func, *args, update=None, error_handling=False, **kwargs
|
||||
)
|
||||
|
||||
return async_func
|
||||
|
||||
|
||||
class DispatcherHandlerStop(Exception):
|
||||
"""Raise this in handler to prevent execution any other handler (even in different group)."""
|
||||
pass
|
||||
"""
|
||||
Raise this in handler to prevent execution any other handler (even in different group).
|
||||
|
||||
In order to use this exception in a :class:`telegram.ext.ConversationHandler`, pass the
|
||||
optional ``state`` parameter instead of returning the next state:
|
||||
|
||||
class Dispatcher(object):
|
||||
"""This class dispatches all kinds of updates to its registered handlers.
|
||||
.. code-block:: python
|
||||
|
||||
def callback(update, context):
|
||||
...
|
||||
raise DispatcherHandlerStop(next_state)
|
||||
|
||||
Attributes:
|
||||
bot (:class:`telegram.Bot`): The bot object that should be passed to the handlers.
|
||||
update_queue (:obj:`Queue`): The synchronized queue that will contain the updates.
|
||||
job_queue (:class:`telegram.ext.JobQueue`): Optional. The :class:`telegram.ext.JobQueue`
|
||||
instance to pass onto handler callbacks.
|
||||
workers (:obj:`int`): Number of maximum concurrent worker threads for the ``@run_async``
|
||||
decorator.
|
||||
user_data (:obj:`defaultdict`): A dictionary handlers can use to store data for the user.
|
||||
chat_data (:obj:`defaultdict`): A dictionary handlers can use to store data for the chat.
|
||||
persistence (:class:`telegram.ext.BasePersistence`): Optional. The persistence class to
|
||||
store data that should be persistent over restarts
|
||||
state (:obj:`object`): Optional. The next state of the conversation.
|
||||
|
||||
Args:
|
||||
state (:obj:`object`, optional): The next state of the conversation.
|
||||
"""
|
||||
|
||||
def __init__(self, state: object = None) -> None:
|
||||
super().__init__()
|
||||
self.state = state
|
||||
|
||||
|
||||
class Dispatcher:
|
||||
"""This class dispatches all kinds of updates to its registered handlers.
|
||||
|
||||
Args:
|
||||
bot (:class:`telegram.Bot`): The bot object that should be passed to the handlers.
|
||||
@@ -88,12 +112,25 @@ class Dispatcher(object):
|
||||
job_queue (:class:`telegram.ext.JobQueue`, optional): The :class:`telegram.ext.JobQueue`
|
||||
instance to pass onto handler callbacks.
|
||||
workers (:obj:`int`, optional): Number of maximum concurrent worker threads for the
|
||||
``@run_async`` decorator. defaults to 4.
|
||||
``@run_async`` decorator and :meth:`run_async`. Defaults to 4.
|
||||
persistence (:class:`telegram.ext.BasePersistence`, optional): The persistence class to
|
||||
store data that should be persistent over restarts
|
||||
use_context (:obj:`bool`, optional): If set to ``True`` Use the context based callback API.
|
||||
During the deprecation period of the old API the default is ``False``. **New users**:
|
||||
set this to ``True``.
|
||||
store data that should be persistent over restarts.
|
||||
use_context (:obj:`bool`, optional): If set to :obj:`True` uses the context based callback
|
||||
API (ignored if `dispatcher` argument is used). Defaults to :obj:`True`.
|
||||
**New users**: set this to :obj:`True`.
|
||||
|
||||
Attributes:
|
||||
bot (:class:`telegram.Bot`): The bot object that should be passed to the handlers.
|
||||
update_queue (:obj:`Queue`): The synchronized queue that will contain the updates.
|
||||
job_queue (:class:`telegram.ext.JobQueue`): Optional. The :class:`telegram.ext.JobQueue`
|
||||
instance to pass onto handler callbacks.
|
||||
workers (:obj:`int`, optional): Number of maximum concurrent worker threads for the
|
||||
``@run_async`` decorator and :meth:`run_async`.
|
||||
user_data (:obj:`defaultdict`): A dictionary handlers can use to store data for the user.
|
||||
chat_data (:obj:`defaultdict`): A dictionary handlers can use to store data for the chat.
|
||||
bot_data (:obj:`dict`): A dictionary handlers can use to store data for the bot.
|
||||
persistence (:class:`telegram.ext.BasePersistence`): Optional. The persistence class to
|
||||
store data that should be persistent over restarts.
|
||||
|
||||
"""
|
||||
|
||||
@@ -102,14 +139,16 @@ class Dispatcher(object):
|
||||
__singleton = None
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def __init__(self,
|
||||
bot,
|
||||
update_queue,
|
||||
workers=4,
|
||||
exception_event=None,
|
||||
job_queue=None,
|
||||
persistence=None,
|
||||
use_context=False):
|
||||
def __init__(
|
||||
self,
|
||||
bot: 'Bot',
|
||||
update_queue: Queue,
|
||||
workers: int = 4,
|
||||
exception_event: Event = None,
|
||||
job_queue: 'JobQueue' = None,
|
||||
persistence: BasePersistence = None,
|
||||
use_context: bool = True,
|
||||
):
|
||||
self.bot = bot
|
||||
self.update_queue = update_queue
|
||||
self.job_queue = job_queue
|
||||
@@ -117,16 +156,22 @@ class Dispatcher(object):
|
||||
self.use_context = use_context
|
||||
|
||||
if not use_context:
|
||||
warnings.warn('Old Handler API is deprecated - see https://git.io/fxJuV for details',
|
||||
TelegramDeprecationWarning, stacklevel=3)
|
||||
warnings.warn(
|
||||
'Old Handler API is deprecated - see https://git.io/fxJuV for details',
|
||||
TelegramDeprecationWarning,
|
||||
stacklevel=3,
|
||||
)
|
||||
|
||||
self.user_data = defaultdict(dict)
|
||||
""":obj:`dict`: A dictionary handlers can use to store data for the user."""
|
||||
self.chat_data = defaultdict(dict)
|
||||
self.user_data: DefaultDict[int, Dict[object, object]] = defaultdict(dict)
|
||||
self.chat_data: DefaultDict[int, Dict[object, object]] = defaultdict(dict)
|
||||
self.bot_data = {}
|
||||
self.persistence: Optional[BasePersistence] = None
|
||||
self._update_persistence_lock = Lock()
|
||||
if persistence:
|
||||
if not isinstance(persistence, BasePersistence):
|
||||
raise TypeError("persistence should be based on telegram.ext.BasePersistence")
|
||||
raise TypeError("persistence must be based on telegram.ext.BasePersistence")
|
||||
self.persistence = persistence
|
||||
self.persistence.set_bot(self.bot)
|
||||
if self.persistence.store_user_data:
|
||||
self.user_data = self.persistence.get_user_data()
|
||||
if not isinstance(self.user_data, defaultdict):
|
||||
@@ -135,49 +180,55 @@ class Dispatcher(object):
|
||||
self.chat_data = self.persistence.get_chat_data()
|
||||
if not isinstance(self.chat_data, defaultdict):
|
||||
raise ValueError("chat_data must be of type defaultdict")
|
||||
if self.persistence.store_bot_data:
|
||||
self.bot_data = self.persistence.get_bot_data()
|
||||
if not isinstance(self.bot_data, dict):
|
||||
raise ValueError("bot_data must be of type dict")
|
||||
else:
|
||||
self.persistence = None
|
||||
|
||||
self.job_queue = job_queue
|
||||
|
||||
self.handlers = {}
|
||||
self.handlers: Dict[int, List[Handler]] = {}
|
||||
"""Dict[:obj:`int`, List[:class:`telegram.ext.Handler`]]: Holds the handlers per group."""
|
||||
self.groups = []
|
||||
self.groups: List[int] = []
|
||||
"""List[:obj:`int`]: A list with all groups."""
|
||||
self.error_handlers = []
|
||||
"""List[:obj:`callable`]: A list of errorHandlers."""
|
||||
self.error_handlers: Dict[Callable, Union[bool, DefaultValue]] = {}
|
||||
"""Dict[:obj:`callable`, :obj:`bool`]: A dict, where the keys are error handlers and the
|
||||
values indicate whether they are to be run asynchronously."""
|
||||
|
||||
self.running = False
|
||||
""":obj:`bool`: Indicates if this dispatcher is running."""
|
||||
self.__stop_event = Event()
|
||||
self.__exception_event = exception_event or Event()
|
||||
self.__async_queue = Queue()
|
||||
self.__async_threads = set()
|
||||
self.__async_queue: Queue = Queue()
|
||||
self.__async_threads: Set[Thread] = set()
|
||||
|
||||
# For backward compatibility, we allow a "singleton" mode for the dispatcher. When there's
|
||||
# only one instance of Dispatcher, it will be possible to use the `run_async` decorator.
|
||||
with self.__singleton_lock:
|
||||
if self.__singleton_semaphore.acquire(blocking=0):
|
||||
if self.__singleton_semaphore.acquire(blocking=False):
|
||||
self._set_singleton(self)
|
||||
else:
|
||||
self._set_singleton(None)
|
||||
|
||||
def _init_async_threads(self, base_name, workers):
|
||||
base_name = '{}_'.format(base_name) if base_name else ''
|
||||
@property
|
||||
def exception_event(self) -> Event:
|
||||
return self.__exception_event
|
||||
|
||||
def _init_async_threads(self, base_name: str, workers: int) -> None:
|
||||
base_name = f'{base_name}_' if base_name else ''
|
||||
|
||||
for i in range(workers):
|
||||
thread = Thread(target=self._pooled, name='Bot:{}:worker:{}{}'.format(self.bot.id,
|
||||
base_name, i))
|
||||
thread = Thread(target=self._pooled, name=f'Bot:{self.bot.id}:worker:{base_name}{i}')
|
||||
self.__async_threads.add(thread)
|
||||
thread.start()
|
||||
|
||||
@classmethod
|
||||
def _set_singleton(cls, val):
|
||||
def _set_singleton(cls, val: Optional['Dispatcher']) -> None:
|
||||
cls.logger.debug('Setting singleton dispatcher as %s', val)
|
||||
cls.__singleton = weakref.ref(val) if val else None
|
||||
|
||||
@classmethod
|
||||
def get_instance(cls):
|
||||
def get_instance(cls) -> 'Dispatcher':
|
||||
"""Get the singleton instance of this class.
|
||||
|
||||
Returns:
|
||||
@@ -188,51 +239,95 @@ class Dispatcher(object):
|
||||
|
||||
"""
|
||||
if cls.__singleton is not None:
|
||||
return cls.__singleton() # pylint: disable=not-callable
|
||||
else:
|
||||
raise RuntimeError('{} not initialized or multiple instances exist'.format(
|
||||
cls.__name__))
|
||||
return cls.__singleton() # type: ignore[return-value] # pylint: disable=not-callable
|
||||
raise RuntimeError(f'{cls.__name__} not initialized or multiple instances exist')
|
||||
|
||||
def _pooled(self):
|
||||
def _pooled(self) -> None:
|
||||
thr_name = current_thread().getName()
|
||||
while 1:
|
||||
promise = self.__async_queue.get()
|
||||
|
||||
# If unpacking fails, the thread pool is being closed from Updater._join_async_threads
|
||||
if not isinstance(promise, Promise):
|
||||
self.logger.debug("Closing run_async thread %s/%d", thr_name,
|
||||
len(self.__async_threads))
|
||||
self.logger.debug(
|
||||
"Closing run_async thread %s/%d", thr_name, len(self.__async_threads)
|
||||
)
|
||||
break
|
||||
|
||||
promise.run()
|
||||
|
||||
if not promise.exception:
|
||||
self.update_persistence(update=promise.update)
|
||||
continue
|
||||
|
||||
if isinstance(promise.exception, DispatcherHandlerStop):
|
||||
self.logger.warning(
|
||||
'DispatcherHandlerStop is not supported with async functions; func: %s',
|
||||
promise.pooled_function.__name__)
|
||||
promise.pooled_function.__name__,
|
||||
)
|
||||
continue
|
||||
|
||||
def run_async(self, func, *args, **kwargs):
|
||||
"""Queue a function (with given args/kwargs) to be run asynchronously.
|
||||
# Avoid infinite recursion of error handlers.
|
||||
if promise.pooled_function in self.error_handlers:
|
||||
self.logger.error('An uncaught error was raised while handling the error.')
|
||||
continue
|
||||
|
||||
# Don't perform error handling for a `Promise` with deactivated error handling. This
|
||||
# should happen only via the deprecated `@run_async` decorator or `Promises` created
|
||||
# within error handlers
|
||||
if not promise.error_handling:
|
||||
self.logger.error('A promise with deactivated error handling raised an error.')
|
||||
continue
|
||||
|
||||
# If we arrive here, an exception happened in the promise and was neither
|
||||
# DispatcherHandlerStop nor raised by an error handler. So we can and must handle it
|
||||
try:
|
||||
self.dispatch_error(promise.update, promise.exception, promise=promise)
|
||||
except Exception:
|
||||
self.logger.exception('An uncaught error was raised while handling the error.')
|
||||
|
||||
def run_async(
|
||||
self, func: Callable[..., object], *args: object, update: object = None, **kwargs: object
|
||||
) -> Promise:
|
||||
"""
|
||||
Queue a function (with given args/kwargs) to be run asynchronously. Exceptions raised
|
||||
by the function will be handled by the error handlers registered with
|
||||
:meth:`add_error_handler`.
|
||||
|
||||
Warning:
|
||||
If you're using @run_async you cannot rely on adding custom attributes to
|
||||
:class:`telegram.ext.CallbackContext`. See its docs for more info.
|
||||
* If you're using ``@run_async``/:meth:`run_async` you cannot rely on adding custom
|
||||
attributes to :class:`telegram.ext.CallbackContext`. See its docs for more info.
|
||||
* Calling a function through :meth:`run_async` from within an error handler can lead to
|
||||
an infinite error handling loop.
|
||||
|
||||
Args:
|
||||
func (:obj:`callable`): The function to run in the thread.
|
||||
*args (:obj:`tuple`, optional): Arguments to `func`.
|
||||
**kwargs (:obj:`dict`, optional): Keyword arguments to `func`.
|
||||
*args (:obj:`tuple`, optional): Arguments to ``func``.
|
||||
update (:class:`telegram.Update` | :obj:`object`, optional): The update associated with
|
||||
the functions call. If passed, it will be available in the error handlers, in case
|
||||
an exception is raised by :attr:`func`.
|
||||
**kwargs (:obj:`dict`, optional): Keyword arguments to ``func``.
|
||||
|
||||
Returns:
|
||||
Promise
|
||||
|
||||
"""
|
||||
# TODO: handle exception in async threads
|
||||
# set a threading.Event to notify caller thread
|
||||
promise = Promise(func, args, kwargs)
|
||||
return self._run_async(func, *args, update=update, error_handling=True, **kwargs)
|
||||
|
||||
def _run_async(
|
||||
self,
|
||||
func: Callable[..., object],
|
||||
*args: object,
|
||||
update: object = None,
|
||||
error_handling: bool = True,
|
||||
**kwargs: object,
|
||||
) -> Promise:
|
||||
# TODO: Remove error_handling parameter once we drop the @run_async decorator
|
||||
promise = Promise(func, args, kwargs, update=update, error_handling=error_handling)
|
||||
self.__async_queue.put(promise)
|
||||
return promise
|
||||
|
||||
def start(self, ready=None):
|
||||
def start(self, ready: Event = None) -> None:
|
||||
"""Thread target of thread 'dispatcher'.
|
||||
|
||||
Runs in background and processes the update queue.
|
||||
@@ -253,7 +348,7 @@ class Dispatcher(object):
|
||||
self.logger.error(msg)
|
||||
raise TelegramError(msg)
|
||||
|
||||
self._init_async_threads(uuid4(), self.workers)
|
||||
self._init_async_threads(str(uuid4()), self.workers)
|
||||
self.running = True
|
||||
self.logger.debug('Dispatcher started')
|
||||
|
||||
@@ -268,19 +363,19 @@ class Dispatcher(object):
|
||||
if self.__stop_event.is_set():
|
||||
self.logger.debug('orderly stopping')
|
||||
break
|
||||
elif self.__exception_event.is_set():
|
||||
if self.__exception_event.is_set():
|
||||
self.logger.critical('stopping due to exception in another thread')
|
||||
break
|
||||
continue
|
||||
|
||||
self.logger.debug('Processing Update: %s' % update)
|
||||
self.logger.debug('Processing Update: %s', update)
|
||||
self.process_update(update)
|
||||
self.update_queue.task_done()
|
||||
|
||||
self.running = False
|
||||
self.logger.debug('Dispatcher thread stopped')
|
||||
|
||||
def stop(self):
|
||||
def stop(self) -> None:
|
||||
"""Stops the thread."""
|
||||
if self.running:
|
||||
self.__stop_event.set()
|
||||
@@ -298,69 +393,42 @@ class Dispatcher(object):
|
||||
self.__async_queue.put(None)
|
||||
|
||||
for i, thr in enumerate(threads):
|
||||
self.logger.debug('Waiting for async thread {0}/{1} to end'.format(i + 1, total))
|
||||
self.logger.debug('Waiting for async thread %s/%s to end', i + 1, total)
|
||||
thr.join()
|
||||
self.__async_threads.remove(thr)
|
||||
self.logger.debug('async thread {0}/{1} has ended'.format(i + 1, total))
|
||||
self.logger.debug('async thread %s/%s has ended', i + 1, total)
|
||||
|
||||
@property
|
||||
def has_running_threads(self):
|
||||
def has_running_threads(self) -> bool:
|
||||
return self.running or bool(self.__async_threads)
|
||||
|
||||
def process_update(self, update):
|
||||
"""Processes a single update.
|
||||
def process_update(self, update: object) -> None:
|
||||
"""Processes a single update and updates the persistence.
|
||||
|
||||
Note:
|
||||
If the update is handled by least one synchronously running handlers (i.e.
|
||||
``run_async=False``), :meth:`update_persistence` is called *once* after all handlers
|
||||
synchronous handlers are done. Each asynchronously running handler will trigger
|
||||
:meth:`update_persistence` on its own.
|
||||
|
||||
Args:
|
||||
update (:obj:`str` | :class:`telegram.Update` | :class:`telegram.TelegramError`):
|
||||
update (:class:`telegram.Update` | :obj:`object` | \
|
||||
:class:`telegram.error.TelegramError`):
|
||||
The update to process.
|
||||
|
||||
"""
|
||||
|
||||
def persist_update(update):
|
||||
"""Persist a single update.
|
||||
|
||||
Args:
|
||||
update (:class:`telegram.Update`):
|
||||
The update to process.
|
||||
|
||||
"""
|
||||
if self.persistence and isinstance(update, Update):
|
||||
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 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 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):
|
||||
try:
|
||||
self.dispatch_error(None, update)
|
||||
except Exception:
|
||||
self.logger.exception('An uncaught error was raised while handling the error')
|
||||
self.logger.exception('An uncaught error was raised while handling the error.')
|
||||
return
|
||||
|
||||
context = None
|
||||
handled = False
|
||||
sync_modes = []
|
||||
|
||||
for group in self.groups:
|
||||
try:
|
||||
@@ -369,33 +437,43 @@ class Dispatcher(object):
|
||||
if check is not None and check is not False:
|
||||
if not context and self.use_context:
|
||||
context = CallbackContext.from_update(update, self)
|
||||
handled = True
|
||||
sync_modes.append(handler.run_async)
|
||||
handler.handle_update(update, self, check, context)
|
||||
persist_update(update)
|
||||
break
|
||||
|
||||
# Stop processing with any other handler.
|
||||
except DispatcherHandlerStop:
|
||||
self.logger.debug('Stopping further handlers due to DispatcherHandlerStop')
|
||||
persist_update(update)
|
||||
self.update_persistence(update=update)
|
||||
break
|
||||
|
||||
# Dispatch any error.
|
||||
except Exception as e:
|
||||
except Exception as exc:
|
||||
try:
|
||||
self.dispatch_error(update, e)
|
||||
self.dispatch_error(update, exc)
|
||||
except DispatcherHandlerStop:
|
||||
self.logger.debug('Error handler stopped further handlers')
|
||||
break
|
||||
# Errors should not stop the thread.
|
||||
except Exception:
|
||||
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')
|
||||
self.logger.exception('An uncaught error was raised while handling the error.')
|
||||
|
||||
def add_handler(self, handler, group=DEFAULT_GROUP):
|
||||
# Update persistence, if handled
|
||||
handled_only_async = all(sync_modes)
|
||||
if handled:
|
||||
# Respect default settings
|
||||
if all(mode is DEFAULT_FALSE for mode in sync_modes) and self.bot.defaults:
|
||||
handled_only_async = self.bot.defaults.run_async
|
||||
# If update was only handled by async handlers, we don't need to update here
|
||||
if not handled_only_async:
|
||||
self.update_persistence(update=update)
|
||||
|
||||
def add_handler(self, handler: Handler, group: int = DEFAULT_GROUP) -> None:
|
||||
"""Register a handler.
|
||||
|
||||
TL;DR: Order and priority counts. 0 or 1 handlers per group will be used.
|
||||
TL;DR: Order and priority counts. 0 or 1 handlers per group will be used. End handling of
|
||||
update with :class:`telegram.ext.DispatcherHandlerStop`.
|
||||
|
||||
A handler must be an instance of a subclass of :class:`telegram.ext.Handler`. All handlers
|
||||
are organized in groups with a numeric value. The default group is 0. All groups will be
|
||||
@@ -417,19 +495,20 @@ class Dispatcher(object):
|
||||
|
||||
"""
|
||||
# Unfortunately due to circular imports this has to be here
|
||||
from .conversationhandler import ConversationHandler
|
||||
from .conversationhandler import ConversationHandler # pylint: disable=C0415
|
||||
|
||||
if not isinstance(handler, Handler):
|
||||
raise TypeError('handler is not an instance of {0}'.format(Handler.__name__))
|
||||
raise TypeError(f'handler is not an instance of {Handler.__name__}')
|
||||
if not isinstance(group, int):
|
||||
raise TypeError('group is not int')
|
||||
if isinstance(handler, ConversationHandler) and handler.persistent:
|
||||
if isinstance(handler, ConversationHandler) and handler.persistent and handler.name:
|
||||
if not self.persistence:
|
||||
raise ValueError(
|
||||
"Conversationhandler {} can not be persistent if dispatcher has no "
|
||||
"persistence".format(handler.name))
|
||||
handler.conversations = self.persistence.get_conversations(handler.name)
|
||||
f"ConversationHandler {handler.name} can not be persistent if dispatcher has "
|
||||
f"no persistence"
|
||||
)
|
||||
handler.persistence = self.persistence
|
||||
handler.conversations = self.persistence.get_conversations(handler.name)
|
||||
|
||||
if group not in self.handlers:
|
||||
self.handlers[group] = list()
|
||||
@@ -438,7 +517,7 @@ class Dispatcher(object):
|
||||
|
||||
self.handlers[group].append(handler)
|
||||
|
||||
def remove_handler(self, handler, group=DEFAULT_GROUP):
|
||||
def remove_handler(self, handler: Handler, group: int = DEFAULT_GROUP) -> None:
|
||||
"""Remove a handler from the specified group.
|
||||
|
||||
Args:
|
||||
@@ -452,23 +531,89 @@ class Dispatcher(object):
|
||||
del self.handlers[group]
|
||||
self.groups.remove(group)
|
||||
|
||||
def update_persistence(self):
|
||||
"""Update :attr:`user_data` and :attr:`chat_data` in :attr:`persistence`.
|
||||
"""
|
||||
if self.persistence:
|
||||
if self.persistence.store_chat_data:
|
||||
for chat_id in self.chat_data:
|
||||
self.persistence.update_chat_data(chat_id, self.chat_data[chat_id])
|
||||
if self.persistence.store_user_data:
|
||||
for user_id in self.user_data:
|
||||
self.persistence.update_user_data(user_id, self.user_data[user_id])
|
||||
def update_persistence(self, update: object = None) -> None:
|
||||
"""Update :attr:`user_data`, :attr:`chat_data` and :attr:`bot_data` in :attr:`persistence`.
|
||||
|
||||
def add_error_handler(self, callback):
|
||||
Args:
|
||||
update (:class:`telegram.Update`, optional): The update to process. If passed, only the
|
||||
corresponding ``user_data`` and ``chat_data`` will be updated.
|
||||
"""
|
||||
with self._update_persistence_lock:
|
||||
self.__update_persistence(update)
|
||||
|
||||
def __update_persistence(self, update: object = None) -> None:
|
||||
if self.persistence:
|
||||
# We use list() here in order to decouple chat_ids from self.chat_data, as dict view
|
||||
# objects will change, when the dict does and we want to loop over chat_ids
|
||||
chat_ids = list(self.chat_data.keys())
|
||||
user_ids = list(self.user_data.keys())
|
||||
|
||||
if isinstance(update, Update):
|
||||
if update.effective_chat:
|
||||
chat_ids = [update.effective_chat.id]
|
||||
else:
|
||||
chat_ids = []
|
||||
if update.effective_user:
|
||||
user_ids = [update.effective_user.id]
|
||||
else:
|
||||
user_ids = []
|
||||
|
||||
if self.persistence.store_bot_data:
|
||||
try:
|
||||
self.persistence.update_bot_data(self.bot_data)
|
||||
except Exception as exc:
|
||||
try:
|
||||
self.dispatch_error(update, exc)
|
||||
except Exception:
|
||||
message = (
|
||||
'Saving bot 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_chat_data:
|
||||
for chat_id in chat_ids:
|
||||
try:
|
||||
self.persistence.update_chat_data(chat_id, self.chat_data[chat_id])
|
||||
except Exception as exc:
|
||||
try:
|
||||
self.dispatch_error(update, exc)
|
||||
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:
|
||||
for user_id in user_ids:
|
||||
try:
|
||||
self.persistence.update_user_data(user_id, self.user_data[user_id])
|
||||
except Exception as exc:
|
||||
try:
|
||||
self.dispatch_error(update, exc)
|
||||
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)
|
||||
|
||||
def add_error_handler(
|
||||
self,
|
||||
callback: Callable[[object, CallbackContext], None],
|
||||
run_async: Union[bool, DefaultValue] = DEFAULT_FALSE, # pylint: disable=W0621
|
||||
) -> None:
|
||||
"""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.
|
||||
Note:
|
||||
Attempts to add the same callback multiple times will be ignored.
|
||||
|
||||
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
|
||||
@@ -477,37 +622,63 @@ class Dispatcher(object):
|
||||
``def callback(update: Update, context: CallbackContext)``
|
||||
|
||||
The error that happened will be present in context.error.
|
||||
run_async (:obj:`bool`, optional): Whether this handlers callback should be run
|
||||
asynchronously using :meth:`run_async`. Defaults to :obj:`False`.
|
||||
|
||||
Note:
|
||||
See https://git.io/fxJuV for more info about switching to context based API.
|
||||
"""
|
||||
self.error_handlers.append(callback)
|
||||
if callback in self.error_handlers:
|
||||
self.logger.debug('The callback is already registered as an error handler. Ignoring.')
|
||||
return
|
||||
|
||||
def remove_error_handler(self, callback):
|
||||
if run_async is DEFAULT_FALSE and self.bot.defaults:
|
||||
if self.bot.defaults.run_async:
|
||||
run_async = True
|
||||
|
||||
self.error_handlers[callback] = run_async
|
||||
|
||||
def remove_error_handler(self, callback: Callable[[object, CallbackContext], None]) -> None:
|
||||
"""Removes an error handler.
|
||||
|
||||
Args:
|
||||
callback (:obj:`callable`): The error handler to remove.
|
||||
|
||||
"""
|
||||
if callback in self.error_handlers:
|
||||
self.error_handlers.remove(callback)
|
||||
self.error_handlers.pop(callback, None)
|
||||
|
||||
def dispatch_error(self, update, error):
|
||||
def dispatch_error(
|
||||
self, update: Optional[object], error: Exception, promise: Promise = None
|
||||
) -> None:
|
||||
"""Dispatches an error.
|
||||
|
||||
Args:
|
||||
update (:obj:`str` | :class:`telegram.Update` | None): The update that caused the error
|
||||
update (:obj:`object` | :class:`telegram.Update`): The update that caused the error.
|
||||
error (:obj:`Exception`): The error that was raised.
|
||||
promise (:class:`telegram.utils.Promise`, optional): The promise whose pooled function
|
||||
raised the error.
|
||||
|
||||
"""
|
||||
async_args = None if not promise else promise.args
|
||||
async_kwargs = None if not promise else promise.kwargs
|
||||
|
||||
if self.error_handlers:
|
||||
for callback in self.error_handlers:
|
||||
for callback, run_async in self.error_handlers.items(): # pylint: disable=W0621
|
||||
if self.use_context:
|
||||
callback(update, CallbackContext.from_error(update, error, self))
|
||||
context = CallbackContext.from_error(
|
||||
update, error, self, async_args=async_args, async_kwargs=async_kwargs
|
||||
)
|
||||
if run_async:
|
||||
self.run_async(callback, update, context, update=update)
|
||||
else:
|
||||
callback(update, context)
|
||||
else:
|
||||
callback(self.bot, update, error)
|
||||
if run_async:
|
||||
self.run_async(callback, self.bot, update, error, update=update)
|
||||
else:
|
||||
callback(self.bot, update, error)
|
||||
|
||||
else:
|
||||
self.logger.exception(
|
||||
'No error handlers are registered, logging exception.', exc_info=error)
|
||||
'No error handlers are registered, logging exception.', exc_info=error
|
||||
)
|
||||
|
||||
+1298
-312
File diff suppressed because it is too large
Load Diff
+124
-63
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2018
|
||||
# Copyright (C) 2015-2021
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -18,10 +18,64 @@
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains the base class for handlers as used by the Dispatcher."""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, TypeVar, Union, Generic
|
||||
|
||||
class Handler(object):
|
||||
from telegram import Update
|
||||
from telegram.ext.utils.promise import Promise
|
||||
from telegram.utils.helpers import DefaultValue, DEFAULT_FALSE
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram.ext import CallbackContext, Dispatcher
|
||||
|
||||
RT = TypeVar('RT')
|
||||
UT = TypeVar('UT')
|
||||
|
||||
|
||||
class Handler(Generic[UT], ABC):
|
||||
"""The base class for all update handlers. Create custom handlers by inheriting from it.
|
||||
|
||||
Note:
|
||||
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you
|
||||
can use to keep any data in will be sent to the :attr:`callback` function. Related to
|
||||
either the user or the chat that the update was sent in. For each update from the same user
|
||||
or in the same chat, it will be the same ``dict``.
|
||||
|
||||
Note that this is DEPRECATED, and you should use context based callbacks. See
|
||||
https://git.io/fxJuV for more info.
|
||||
|
||||
Warning:
|
||||
When setting ``run_async`` to :obj:`True`, you cannot rely on adding custom
|
||||
attributes to :class:`telegram.ext.CallbackContext`. See its docs for more info.
|
||||
|
||||
Args:
|
||||
callback (:obj:`callable`): The callback function for this handler. Will be called when
|
||||
:attr:`check_update` has determined that an update should be processed by this handler.
|
||||
Callback signature for context based API:
|
||||
|
||||
``def callback(update: Update, context: CallbackContext)``
|
||||
|
||||
The return value of the callback is usually ignored except for the special case of
|
||||
:class:`telegram.ext.ConversationHandler`.
|
||||
pass_update_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``update_queue`` will be passed to the callback function. It will be the ``Queue``
|
||||
instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher`
|
||||
that contains new updates which can be used to insert updates. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_job_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``job_queue`` will be passed to the callback function. It will be a
|
||||
:class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater`
|
||||
which can be used to schedule new jobs. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_user_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``user_data`` will be passed to the callback function. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_chat_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``chat_data`` will be passed to the callback function. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
run_async (:obj:`bool`): Determines whether the callback will run asynchronously.
|
||||
Defaults to :obj:`False`.
|
||||
|
||||
Attributes:
|
||||
callback (:obj:`callable`): The callback function for this handler.
|
||||
pass_update_queue (:obj:`bool`): Determines whether ``update_queue`` will be
|
||||
@@ -32,94 +86,93 @@ class Handler(object):
|
||||
the callback function.
|
||||
pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to
|
||||
the callback function.
|
||||
|
||||
Note:
|
||||
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you
|
||||
can use to keep any data in will be sent to the :attr:`callback` function. Related to
|
||||
either the user or the chat that the update was sent in. For each update from the same user
|
||||
or in the same chat, it will be the same ``dict``.
|
||||
|
||||
Note that this is DEPRECATED, and you should use context based callbacks. See
|
||||
https://git.io/fxJuV for more info.
|
||||
|
||||
Args:
|
||||
callback (:obj:`callable`): The callback function for this handler. Will be called when
|
||||
:attr:`check_update` has determined that an update should be processed by this handler.
|
||||
Callback signature for context based API:
|
||||
|
||||
``def callback(update: Update, context: CallbackContext)``
|
||||
|
||||
The return value of the callback is usually ignored except for the special case of
|
||||
:class:`telegram.ext.ConversationHandler`.
|
||||
pass_update_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called
|
||||
``update_queue`` will be passed to the callback function. It will be the ``Queue``
|
||||
instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher`
|
||||
that contains new updates which can be used to insert updates. Default is ``False``.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_job_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called
|
||||
``job_queue`` will be passed to the callback function. It will be a
|
||||
:class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater`
|
||||
which can be used to schedule new jobs. Default is ``False``.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_user_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called
|
||||
``user_data`` will be passed to the callback function. Default is ``False``.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_chat_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called
|
||||
``chat_data`` will be passed to the callback function. Default is ``False``.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
run_async (:obj:`bool`): Determines whether the callback will run asynchronously.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
callback,
|
||||
pass_update_queue=False,
|
||||
pass_job_queue=False,
|
||||
pass_user_data=False,
|
||||
pass_chat_data=False):
|
||||
def __init__(
|
||||
self,
|
||||
callback: Callable[[UT, 'CallbackContext'], RT],
|
||||
pass_update_queue: bool = False,
|
||||
pass_job_queue: bool = False,
|
||||
pass_user_data: bool = False,
|
||||
pass_chat_data: bool = False,
|
||||
run_async: Union[bool, DefaultValue] = DEFAULT_FALSE,
|
||||
):
|
||||
self.callback = callback
|
||||
self.pass_update_queue = pass_update_queue
|
||||
self.pass_job_queue = pass_job_queue
|
||||
self.pass_user_data = pass_user_data
|
||||
self.pass_chat_data = pass_chat_data
|
||||
self.run_async = run_async
|
||||
|
||||
def check_update(self, update):
|
||||
@abstractmethod
|
||||
def check_update(self, update: object) -> Optional[Union[bool, object]]:
|
||||
"""
|
||||
This method is called to determine if an update should be handled by
|
||||
this handler instance. It should always be overridden.
|
||||
|
||||
Note:
|
||||
Custom updates types can be handled by the dispatcher. Therefore, an implementation of
|
||||
this method should always check the type of :attr:`update`.
|
||||
|
||||
Args:
|
||||
update (:obj:`str` | :class:`telegram.Update`): The update to be tested.
|
||||
|
||||
Returns:
|
||||
Either ``None`` or ``False`` if the update should not be handled. Otherwise an object
|
||||
that will be passed to :attr:`handle_update` and :attr:`collect_additional_context`
|
||||
when the update gets handled.
|
||||
Either :obj:`None` or :obj:`False` if the update should not be handled. Otherwise an
|
||||
object that will be passed to :meth:`handle_update` and
|
||||
:meth:`collect_additional_context` when the update gets handled.
|
||||
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def handle_update(self, update, dispatcher, check_result, context=None):
|
||||
def handle_update(
|
||||
self,
|
||||
update: UT,
|
||||
dispatcher: 'Dispatcher',
|
||||
check_result: object,
|
||||
context: 'CallbackContext' = None,
|
||||
) -> Union[RT, Promise]:
|
||||
"""
|
||||
This method is called if it was determined that an update should indeed
|
||||
be handled by this instance. Calls :attr:`self.callback` along with its respectful
|
||||
be handled by this instance. Calls :attr:`callback` along with its respectful
|
||||
arguments. To work with the :class:`telegram.ext.ConversationHandler`, this method
|
||||
returns the value returned from ``self.callback``.
|
||||
returns the value returned from :attr:`callback`.
|
||||
Note that it can be overridden if needed by the subclassing handler.
|
||||
|
||||
Args:
|
||||
update (:obj:`str` | :class:`telegram.Update`): The update to be handled.
|
||||
dispatcher (:class:`telegram.ext.Dispatcher`): The calling dispatcher.
|
||||
check_result: The result from :attr:`check_update`.
|
||||
check_result (:obj:`obj`): The result from :attr:`check_update`.
|
||||
context (:class:`telegram.ext.CallbackContext`, optional): The context as provided by
|
||||
the dispatcher.
|
||||
|
||||
"""
|
||||
run_async = self.run_async
|
||||
if self.run_async is DEFAULT_FALSE and dispatcher.bot.defaults:
|
||||
if dispatcher.bot.defaults.run_async:
|
||||
run_async = True
|
||||
|
||||
if context:
|
||||
self.collect_additional_context(context, update, dispatcher, check_result)
|
||||
if run_async:
|
||||
return dispatcher.run_async(self.callback, update, context, update=update)
|
||||
return self.callback(update, context)
|
||||
else:
|
||||
optional_args = self.collect_optional_args(dispatcher, update, check_result)
|
||||
return self.callback(dispatcher.bot, update, **optional_args)
|
||||
|
||||
def collect_additional_context(self, context, update, dispatcher, check_result):
|
||||
optional_args = self.collect_optional_args(dispatcher, update, check_result)
|
||||
if run_async:
|
||||
return dispatcher.run_async(
|
||||
self.callback, dispatcher.bot, update, update=update, **optional_args
|
||||
)
|
||||
return self.callback(dispatcher.bot, update, **optional_args) # type: ignore
|
||||
|
||||
def collect_additional_context(
|
||||
self,
|
||||
context: 'CallbackContext',
|
||||
update: UT,
|
||||
dispatcher: 'Dispatcher',
|
||||
check_result: Any,
|
||||
) -> None:
|
||||
"""Prepares additional arguments for the context. Override if needed.
|
||||
|
||||
Args:
|
||||
@@ -129,9 +182,13 @@ class Handler(object):
|
||||
check_result: The result (return value) from :attr:`check_update`.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
def collect_optional_args(self, dispatcher, update=None, check_result=None):
|
||||
def collect_optional_args(
|
||||
self,
|
||||
dispatcher: 'Dispatcher',
|
||||
update: UT = None,
|
||||
check_result: Any = None, # pylint: disable=W0613
|
||||
) -> Dict[str, object]:
|
||||
"""
|
||||
Prepares the optional arguments. If the handler has additional optional args,
|
||||
it should subclass this method, but remember to call this super method.
|
||||
@@ -145,17 +202,21 @@ class Handler(object):
|
||||
check_result: The result from check_update
|
||||
|
||||
"""
|
||||
optional_args = dict()
|
||||
optional_args: Dict[str, object] = dict()
|
||||
|
||||
if self.pass_update_queue:
|
||||
optional_args['update_queue'] = dispatcher.update_queue
|
||||
if self.pass_job_queue:
|
||||
optional_args['job_queue'] = dispatcher.job_queue
|
||||
if self.pass_user_data:
|
||||
if self.pass_user_data and isinstance(update, Update):
|
||||
user = update.effective_user
|
||||
optional_args['user_data'] = dispatcher.user_data[user.id if user else None]
|
||||
if self.pass_chat_data:
|
||||
optional_args['user_data'] = dispatcher.user_data[
|
||||
user.id if user else None # type: ignore[index]
|
||||
]
|
||||
if self.pass_chat_data and isinstance(update, Update):
|
||||
chat = update.effective_chat
|
||||
optional_args['chat_data'] = dispatcher.chat_data[chat.id if chat else None]
|
||||
optional_args['chat_data'] = dispatcher.chat_data[
|
||||
chat.id if chat else None # type: ignore[index]
|
||||
]
|
||||
|
||||
return optional_args
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2018
|
||||
# Copyright (C) 2015-2021
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -18,18 +18,86 @@
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
""" This module contains the InlineQueryHandler class """
|
||||
import re
|
||||
|
||||
from future.utils import string_types
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Callable,
|
||||
Dict,
|
||||
Match,
|
||||
Optional,
|
||||
Pattern,
|
||||
TypeVar,
|
||||
Union,
|
||||
cast,
|
||||
)
|
||||
|
||||
from telegram import Update
|
||||
from telegram.utils.helpers import DefaultValue, DEFAULT_FALSE
|
||||
|
||||
from .handler import Handler
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram.ext import CallbackContext, Dispatcher
|
||||
|
||||
class InlineQueryHandler(Handler):
|
||||
RT = TypeVar('RT')
|
||||
|
||||
|
||||
class InlineQueryHandler(Handler[Update]):
|
||||
"""
|
||||
Handler class to handle Telegram inline queries. Optionally based on a regex. Read the
|
||||
documentation of the ``re`` module for more information.
|
||||
|
||||
Note:
|
||||
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you
|
||||
can use to keep any data in will be sent to the :attr:`callback` function. Related to
|
||||
either the user or the chat that the update was sent in. For each update from the same user
|
||||
or in the same chat, it will be the same ``dict``.
|
||||
|
||||
Note that this is DEPRECATED, and you should use context based callbacks. See
|
||||
https://git.io/fxJuV for more info.
|
||||
|
||||
Warning:
|
||||
When setting ``run_async`` to :obj:`True`, you cannot rely on adding custom
|
||||
attributes to :class:`telegram.ext.CallbackContext`. See its docs for more info.
|
||||
|
||||
Args:
|
||||
callback (:obj:`callable`): The callback function for this handler. Will be called when
|
||||
:attr:`check_update` has determined that an update should be processed by this handler.
|
||||
Callback signature for context based API:
|
||||
|
||||
``def callback(update: Update, context: CallbackContext)``
|
||||
|
||||
The return value of the callback is usually ignored except for the special case of
|
||||
:class:`telegram.ext.ConversationHandler`.
|
||||
pass_update_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``update_queue`` will be passed to the callback function. It will be the ``Queue``
|
||||
instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher`
|
||||
that contains new updates which can be used to insert updates. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_job_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``job_queue`` will be passed to the callback function. It will be a
|
||||
:class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater`
|
||||
which can be used to schedule new jobs. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pattern (:obj:`str` | :obj:`Pattern`, optional): Regex pattern. If not :obj:`None`,
|
||||
``re.match`` is used on :attr:`telegram.InlineQuery.query` to determine if an update
|
||||
should be handled by this handler.
|
||||
pass_groups (:obj:`bool`, optional): If the callback should be passed the result of
|
||||
``re.match(pattern, data).groups()`` as a keyword argument called ``groups``.
|
||||
Default is :obj:`False`
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_groupdict (:obj:`bool`, optional): If the callback should be passed the result of
|
||||
``re.match(pattern, data).groupdict()`` as a keyword argument called ``groupdict``.
|
||||
Default is :obj:`False`
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_user_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``user_data`` will be passed to the callback function. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_chat_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``chat_data`` will be passed to the callback function. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
run_async (:obj:`bool`): Determines whether the callback will run asynchronously.
|
||||
Defaults to :obj:`False`.
|
||||
|
||||
Attributes:
|
||||
callback (:obj:`callable`): The callback function for this handler.
|
||||
pass_update_queue (:obj:`bool`): Determines whether ``update_queue`` will be
|
||||
@@ -46,84 +114,44 @@ class InlineQueryHandler(Handler):
|
||||
the callback function.
|
||||
pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to
|
||||
the callback function.
|
||||
|
||||
Note:
|
||||
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you
|
||||
can use to keep any data in will be sent to the :attr:`callback` function. Related to
|
||||
either the user or the chat that the update was sent in. For each update from the same user
|
||||
or in the same chat, it will be the same ``dict``.
|
||||
|
||||
Note that this is DEPRECATED, and you should use context based callbacks. See
|
||||
https://git.io/fxJuV for more info.
|
||||
|
||||
Args:
|
||||
callback (:obj:`callable`): The callback function for this handler. Will be called when
|
||||
:attr:`check_update` has determined that an update should be processed by this handler.
|
||||
Callback signature for context based API:
|
||||
|
||||
``def callback(update: Update, context: CallbackContext)``
|
||||
|
||||
The return value of the callback is usually ignored except for the special case of
|
||||
:class:`telegram.ext.ConversationHandler`.
|
||||
pass_update_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called
|
||||
``update_queue`` will be passed to the callback function. It will be the ``Queue``
|
||||
instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher`
|
||||
that contains new updates which can be used to insert updates. Default is ``False``.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_job_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called
|
||||
``job_queue`` will be passed to the callback function. It will be a
|
||||
:class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater`
|
||||
which can be used to schedule new jobs. Default is ``False``.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pattern (:obj:`str` | :obj:`Pattern`, optional): Regex pattern. If not ``None``,
|
||||
``re.match`` is used on :attr:`telegram.InlineQuery.query` to determine if an update
|
||||
should be handled by this handler.
|
||||
pass_groups (:obj:`bool`, optional): If the callback should be passed the result of
|
||||
``re.match(pattern, data).groups()`` as a keyword argument called ``groups``.
|
||||
Default is ``False``
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_groupdict (:obj:`bool`, optional): If the callback should be passed the result of
|
||||
``re.match(pattern, data).groupdict()`` as a keyword argument called ``groupdict``.
|
||||
Default is ``False``
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_user_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called
|
||||
``user_data`` will be passed to the callback function. Default is ``False``.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_chat_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called
|
||||
``chat_data`` will be passed to the callback function. Default is ``False``.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
run_async (:obj:`bool`): Determines whether the callback will run asynchronously.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
callback,
|
||||
pass_update_queue=False,
|
||||
pass_job_queue=False,
|
||||
pattern=None,
|
||||
pass_groups=False,
|
||||
pass_groupdict=False,
|
||||
pass_user_data=False,
|
||||
pass_chat_data=False):
|
||||
super(InlineQueryHandler, self).__init__(
|
||||
def __init__(
|
||||
self,
|
||||
callback: Callable[[Update, 'CallbackContext'], RT],
|
||||
pass_update_queue: bool = False,
|
||||
pass_job_queue: bool = False,
|
||||
pattern: Union[str, Pattern] = None,
|
||||
pass_groups: bool = False,
|
||||
pass_groupdict: bool = False,
|
||||
pass_user_data: bool = False,
|
||||
pass_chat_data: bool = False,
|
||||
run_async: Union[bool, DefaultValue] = DEFAULT_FALSE,
|
||||
):
|
||||
super().__init__(
|
||||
callback,
|
||||
pass_update_queue=pass_update_queue,
|
||||
pass_job_queue=pass_job_queue,
|
||||
pass_user_data=pass_user_data,
|
||||
pass_chat_data=pass_chat_data)
|
||||
pass_chat_data=pass_chat_data,
|
||||
run_async=run_async,
|
||||
)
|
||||
|
||||
if isinstance(pattern, string_types):
|
||||
if isinstance(pattern, str):
|
||||
pattern = re.compile(pattern)
|
||||
|
||||
self.pattern = pattern
|
||||
self.pass_groups = pass_groups
|
||||
self.pass_groupdict = pass_groupdict
|
||||
|
||||
def check_update(self, update):
|
||||
def check_update(self, update: object) -> Optional[Union[bool, Match]]:
|
||||
"""
|
||||
Determines whether an update should be passed to this handlers :attr:`callback`.
|
||||
|
||||
Args:
|
||||
update (:class:`telegram.Update`): Incoming telegram update.
|
||||
update (:class:`telegram.Update` | :obj:`object`): Incoming update.
|
||||
|
||||
Returns:
|
||||
:obj:`bool`
|
||||
@@ -138,17 +166,30 @@ class InlineQueryHandler(Handler):
|
||||
return match
|
||||
else:
|
||||
return True
|
||||
return None
|
||||
|
||||
def collect_optional_args(self, dispatcher, update=None, check_result=None):
|
||||
optional_args = super(InlineQueryHandler, self).collect_optional_args(dispatcher,
|
||||
update, check_result)
|
||||
def collect_optional_args(
|
||||
self,
|
||||
dispatcher: 'Dispatcher',
|
||||
update: Update = None,
|
||||
check_result: Optional[Union[bool, Match]] = None,
|
||||
) -> Dict[str, object]:
|
||||
optional_args = super().collect_optional_args(dispatcher, update, check_result)
|
||||
if self.pattern:
|
||||
check_result = cast(Match, check_result)
|
||||
if self.pass_groups:
|
||||
optional_args['groups'] = check_result.groups()
|
||||
if self.pass_groupdict:
|
||||
optional_args['groupdict'] = check_result.groupdict()
|
||||
return optional_args
|
||||
|
||||
def collect_additional_context(self, context, update, dispatcher, check_result):
|
||||
def collect_additional_context(
|
||||
self,
|
||||
context: 'CallbackContext',
|
||||
update: Update,
|
||||
dispatcher: 'Dispatcher',
|
||||
check_result: Optional[Union[bool, Match]],
|
||||
) -> None:
|
||||
if self.pattern:
|
||||
check_result = cast(Match, check_result)
|
||||
context.matches = [check_result]
|
||||
|
||||
+476
-337
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2018
|
||||
# Copyright (C) 2015-2021
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -16,97 +16,148 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
# pylint: disable=E0401
|
||||
"""This module contains the classes JobQueue and Job."""
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
import time
|
||||
import warnings
|
||||
import weakref
|
||||
from numbers import Number
|
||||
from queue import PriorityQueue, Empty
|
||||
from threading import Thread, Lock, Event
|
||||
from typing import TYPE_CHECKING, Callable, List, Optional, Tuple, Union, cast, overload
|
||||
|
||||
import pytz
|
||||
from apscheduler.events import EVENT_JOB_ERROR, EVENT_JOB_EXECUTED, JobEvent
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
from apscheduler.triggers.combining import OrTrigger
|
||||
from apscheduler.triggers.cron import CronTrigger
|
||||
from apscheduler.job import Job as APSJob
|
||||
|
||||
from telegram.ext.callbackcontext import CallbackContext
|
||||
from telegram.utils.deprecate import TelegramDeprecationWarning
|
||||
from telegram.utils.types import JSONDict
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Bot
|
||||
from telegram.ext import Dispatcher
|
||||
import apscheduler.job # noqa: F401
|
||||
|
||||
|
||||
class Days(object):
|
||||
class Days:
|
||||
MON, TUE, WED, THU, FRI, SAT, SUN = range(7)
|
||||
EVERY_DAY = tuple(range(7))
|
||||
|
||||
|
||||
class JobQueue(object):
|
||||
"""This class allows you to periodically perform tasks with the bot.
|
||||
class JobQueue:
|
||||
"""This class allows you to periodically perform tasks with the bot. It is a convenience
|
||||
wrapper for the APScheduler library.
|
||||
|
||||
Attributes:
|
||||
_queue (:obj:`PriorityQueue`): The queue that holds the Jobs.
|
||||
scheduler (:class:`apscheduler.schedulers.background.BackgroundScheduler`): The APScheduler
|
||||
bot (:class:`telegram.Bot`): The bot instance that should be passed to the jobs.
|
||||
DEPRECATED: Use set_dispatcher instead.
|
||||
DEPRECATED: Use :attr:`set_dispatcher` instead.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, bot=None):
|
||||
self._queue = PriorityQueue()
|
||||
if bot:
|
||||
warnings.warn("Passing bot to jobqueue is deprecated. Please use set_dispatcher "
|
||||
"instead!", TelegramDeprecationWarning, stacklevel=2)
|
||||
|
||||
class MockDispatcher(object):
|
||||
def __init__(self):
|
||||
self.bot = bot
|
||||
self.use_context = False
|
||||
|
||||
self._dispatcher = MockDispatcher()
|
||||
else:
|
||||
self._dispatcher = None
|
||||
def __init__(self) -> None:
|
||||
self._dispatcher: 'Dispatcher' = None # type: ignore[assignment]
|
||||
self.logger = logging.getLogger(self.__class__.__name__)
|
||||
self.__start_lock = Lock()
|
||||
self.__next_peek_lock = Lock() # to protect self._next_peek & self.__tick
|
||||
self.__tick = Event()
|
||||
self.__thread = None
|
||||
self._next_peek = None
|
||||
self._running = False
|
||||
self.scheduler = BackgroundScheduler(timezone=pytz.utc)
|
||||
self.scheduler.add_listener(
|
||||
self._update_persistence, mask=EVENT_JOB_EXECUTED | EVENT_JOB_ERROR
|
||||
)
|
||||
|
||||
def set_dispatcher(self, dispatcher):
|
||||
# Dispatch errors and don't log them in the APS logger
|
||||
def aps_log_filter(record): # type: ignore
|
||||
return 'raised an exception' not in record.msg
|
||||
|
||||
logging.getLogger('apscheduler.executors.default').addFilter(aps_log_filter)
|
||||
self.scheduler.add_listener(self._dispatch_error, EVENT_JOB_ERROR)
|
||||
|
||||
def _build_args(self, job: 'Job') -> List[Union[CallbackContext, 'Bot', 'Job']]:
|
||||
if self._dispatcher.use_context:
|
||||
return [CallbackContext.from_job(job, self._dispatcher)]
|
||||
return [self._dispatcher.bot, job]
|
||||
|
||||
def _tz_now(self) -> datetime.datetime:
|
||||
return datetime.datetime.now(self.scheduler.timezone)
|
||||
|
||||
def _update_persistence(self, event: JobEvent) -> None: # pylint: disable=W0613
|
||||
self._dispatcher.update_persistence()
|
||||
|
||||
def _dispatch_error(self, event: JobEvent) -> None:
|
||||
try:
|
||||
self._dispatcher.dispatch_error(None, event.exception)
|
||||
# Errors should not stop the thread.
|
||||
except Exception:
|
||||
self.logger.exception(
|
||||
'An error was raised while processing the job and an '
|
||||
'uncaught error was raised while handling the error '
|
||||
'with an error_handler.'
|
||||
)
|
||||
|
||||
@overload
|
||||
def _parse_time_input(self, time: None, shift_day: bool = False) -> None:
|
||||
...
|
||||
|
||||
@overload
|
||||
def _parse_time_input(
|
||||
self,
|
||||
time: Union[float, int, datetime.timedelta, datetime.datetime, datetime.time],
|
||||
shift_day: bool = False,
|
||||
) -> datetime.datetime:
|
||||
...
|
||||
|
||||
def _parse_time_input(
|
||||
self,
|
||||
time: Union[float, int, datetime.timedelta, datetime.datetime, datetime.time, None],
|
||||
shift_day: bool = False,
|
||||
) -> Optional[datetime.datetime]:
|
||||
if time is None:
|
||||
return None
|
||||
if isinstance(time, (int, float)):
|
||||
return self._tz_now() + datetime.timedelta(seconds=time)
|
||||
if isinstance(time, datetime.timedelta):
|
||||
return self._tz_now() + time
|
||||
if isinstance(time, datetime.time):
|
||||
date_time = datetime.datetime.combine(
|
||||
datetime.datetime.now(tz=time.tzinfo or self.scheduler.timezone).date(), time
|
||||
)
|
||||
if date_time.tzinfo is None:
|
||||
date_time = self.scheduler.timezone.localize(date_time)
|
||||
if shift_day and date_time <= datetime.datetime.now(pytz.utc):
|
||||
date_time += datetime.timedelta(days=1)
|
||||
return date_time
|
||||
# isinstance(time, datetime.datetime):
|
||||
return time
|
||||
|
||||
def set_dispatcher(self, dispatcher: 'Dispatcher') -> None:
|
||||
"""Set the dispatcher to be used by this JobQueue. Use this instead of passing a
|
||||
:class:`telegram.Bot` to the JobQueue, which is deprecated.
|
||||
|
||||
Args:
|
||||
dispatcher (:class:`telegram.ext.Dispatcher`): The dispatcher.
|
||||
|
||||
"""
|
||||
self._dispatcher = dispatcher
|
||||
if dispatcher.bot.defaults:
|
||||
if dispatcher.bot.defaults:
|
||||
self.scheduler.configure(timezone=dispatcher.bot.defaults.tzinfo or pytz.utc)
|
||||
|
||||
def _put(self, job, next_t=None, last_t=None):
|
||||
if next_t is None:
|
||||
next_t = job.interval
|
||||
if next_t is None:
|
||||
raise ValueError('next_t is None')
|
||||
|
||||
if isinstance(next_t, datetime.datetime):
|
||||
next_t = (next_t - datetime.datetime.now()).total_seconds()
|
||||
|
||||
elif isinstance(next_t, datetime.time):
|
||||
next_datetime = datetime.datetime.combine(datetime.date.today(), next_t)
|
||||
|
||||
if datetime.datetime.now().time() > next_t:
|
||||
next_datetime += datetime.timedelta(days=1)
|
||||
|
||||
next_t = (next_datetime - datetime.datetime.now()).total_seconds()
|
||||
|
||||
elif isinstance(next_t, datetime.timedelta):
|
||||
next_t = next_t.total_seconds()
|
||||
|
||||
next_t += last_t or time.time()
|
||||
|
||||
self.logger.debug('Putting job %s with t=%f', job.name, next_t)
|
||||
|
||||
self._queue.put((next_t, job))
|
||||
|
||||
# Wake up the loop if this job should be executed next
|
||||
self._set_next_peek(next_t)
|
||||
|
||||
def run_once(self, callback, when, context=None, name=None):
|
||||
def run_once(
|
||||
self,
|
||||
callback: Callable[['CallbackContext'], None],
|
||||
when: Union[float, datetime.timedelta, datetime.datetime, datetime.time],
|
||||
context: object = None,
|
||||
name: str = None,
|
||||
job_kwargs: JSONDict = None,
|
||||
) -> 'Job':
|
||||
"""Creates a new ``Job`` that runs once and adds it to the queue.
|
||||
|
||||
Args:
|
||||
callback (:obj:`callable`): The callback function that should be executed by the new
|
||||
job. It should take ``bot, job`` as parameters, where ``job`` is the
|
||||
:class:`telegram.ext.Job` instance. It can be used to access its
|
||||
``job.context`` or change it to a repeating job.
|
||||
job. Callback signature for context based API:
|
||||
|
||||
``def callback(CallbackContext)``
|
||||
|
||||
``context.job`` is the :class:`telegram.ext.Job` instance. It can be used to access
|
||||
its ``job.context`` or change it to a repeating job.
|
||||
when (:obj:`int` | :obj:`float` | :obj:`datetime.timedelta` | \
|
||||
:obj:`datetime.datetime` | :obj:`datetime.time`):
|
||||
Time in or at which the job should run. This parameter will be interpreted
|
||||
@@ -117,33 +168,65 @@ class JobQueue(object):
|
||||
* :obj:`datetime.timedelta` will be interpreted as "time from now" in which the
|
||||
job should run.
|
||||
* :obj:`datetime.datetime` will be interpreted as a specific date and time at
|
||||
which the job should run.
|
||||
which the job should run. If the timezone (``datetime.tzinfo``) is :obj:`None`,
|
||||
the default timezone of the bot will be used.
|
||||
* :obj:`datetime.time` will be interpreted as a specific time of day at which the
|
||||
job should run. This could be either today or, if the time has already passed,
|
||||
tomorrow.
|
||||
tomorrow. If the timezone (``time.tzinfo``) is :obj:`None`, the
|
||||
default timezone of the bot will be used.
|
||||
|
||||
context (:obj:`object`, optional): Additional data needed for the callback function.
|
||||
Can be accessed through ``job.context`` in the callback. Defaults to ``None``.
|
||||
Can be accessed through ``job.context`` in the callback. Defaults to :obj:`None`.
|
||||
name (:obj:`str`, optional): The name of the new job. Defaults to
|
||||
``callback.__name__``.
|
||||
job_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to pass to the
|
||||
``scheduler.add_job()``.
|
||||
|
||||
Returns:
|
||||
:class:`telegram.ext.Job`: The new ``Job`` instance that has been added to the job
|
||||
queue.
|
||||
|
||||
"""
|
||||
job = Job(callback, repeat=False, context=context, name=name, job_queue=self)
|
||||
self._put(job, next_t=when)
|
||||
if not job_kwargs:
|
||||
job_kwargs = {}
|
||||
|
||||
name = name or callback.__name__
|
||||
job = Job(callback, context, name, self)
|
||||
date_time = self._parse_time_input(when, shift_day=True)
|
||||
|
||||
j = self.scheduler.add_job(
|
||||
callback,
|
||||
name=name,
|
||||
trigger='date',
|
||||
run_date=date_time,
|
||||
args=self._build_args(job),
|
||||
timezone=date_time.tzinfo or self.scheduler.timezone,
|
||||
**job_kwargs,
|
||||
)
|
||||
|
||||
job.job = j
|
||||
return job
|
||||
|
||||
def run_repeating(self, callback, interval, first=None, context=None, name=None):
|
||||
def run_repeating(
|
||||
self,
|
||||
callback: Callable[['CallbackContext'], None],
|
||||
interval: Union[float, datetime.timedelta],
|
||||
first: Union[float, datetime.timedelta, datetime.datetime, datetime.time] = None,
|
||||
last: Union[float, datetime.timedelta, datetime.datetime, datetime.time] = None,
|
||||
context: object = None,
|
||||
name: str = None,
|
||||
job_kwargs: JSONDict = None,
|
||||
) -> 'Job':
|
||||
"""Creates a new ``Job`` that runs at specified intervals and adds it to the queue.
|
||||
|
||||
Args:
|
||||
callback (:obj:`callable`): The callback function that should be executed by the new
|
||||
job. It should take ``bot, job`` as parameters, where ``job`` is the
|
||||
:class:`telegram.ext.Job` instance. It can be used to access its
|
||||
``Job.context`` or change it to a repeating job.
|
||||
job. Callback signature for context based API:
|
||||
|
||||
``def callback(CallbackContext)``
|
||||
|
||||
``context.job`` is the :class:`telegram.ext.Job` instance. It can be used to access
|
||||
its ``job.context`` or change it to a repeating job.
|
||||
interval (:obj:`int` | :obj:`float` | :obj:`datetime.timedelta`): The interval in which
|
||||
the job will run. If it is an :obj:`int` or a :obj:`float`, it will be interpreted
|
||||
as seconds.
|
||||
@@ -157,47 +240,237 @@ class JobQueue(object):
|
||||
* :obj:`datetime.timedelta` will be interpreted as "time from now" in which the
|
||||
job should run.
|
||||
* :obj:`datetime.datetime` will be interpreted as a specific date and time at
|
||||
which the job should run.
|
||||
which the job should run. If the timezone (``datetime.tzinfo``) is :obj:`None`,
|
||||
the default timezone of the bot will be used.
|
||||
* :obj:`datetime.time` will be interpreted as a specific time of day at which the
|
||||
job should run. This could be either today or, if the time has already passed,
|
||||
tomorrow.
|
||||
tomorrow. If the timezone (``time.tzinfo``) is :obj:`None`, the
|
||||
default timezone of the bot will be used.
|
||||
|
||||
Defaults to ``interval``
|
||||
last (:obj:`int` | :obj:`float` | :obj:`datetime.timedelta` | \
|
||||
:obj:`datetime.datetime` | :obj:`datetime.time`, optional):
|
||||
Latest possible time for the job to run. This parameter will be interpreted
|
||||
depending on its type. See ``first`` for details.
|
||||
|
||||
If ``last`` is :obj:`datetime.datetime` or :obj:`datetime.time` type
|
||||
and ``last.tzinfo`` is :obj:`None`, the default timezone of the bot will be
|
||||
assumed.
|
||||
|
||||
Defaults to :obj:`None`.
|
||||
context (:obj:`object`, optional): Additional data needed for the callback function.
|
||||
Can be accessed through ``job.context`` in the callback. Defaults to ``None``.
|
||||
Can be accessed through ``job.context`` in the callback. Defaults to :obj:`None`.
|
||||
name (:obj:`str`, optional): The name of the new job. Defaults to
|
||||
``callback.__name__``.
|
||||
job_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to pass to the
|
||||
``scheduler.add_job()``.
|
||||
|
||||
Returns:
|
||||
:class:`telegram.ext.Job`: The new ``Job`` instance that has been added to the job
|
||||
queue.
|
||||
|
||||
Notes:
|
||||
Note:
|
||||
`interval` is always respected "as-is". That means that if DST changes during that
|
||||
interval, the job might not run at the time one would expect. It is always recommended
|
||||
to pin servers to UTC time, then time related behaviour can always be expected.
|
||||
|
||||
"""
|
||||
job = Job(callback,
|
||||
interval=interval,
|
||||
repeat=True,
|
||||
context=context,
|
||||
name=name,
|
||||
job_queue=self)
|
||||
self._put(job, next_t=first)
|
||||
if not job_kwargs:
|
||||
job_kwargs = {}
|
||||
|
||||
name = name or callback.__name__
|
||||
job = Job(callback, context, name, self)
|
||||
|
||||
dt_first = self._parse_time_input(first)
|
||||
dt_last = self._parse_time_input(last)
|
||||
|
||||
if dt_last and dt_first and dt_last < dt_first:
|
||||
raise ValueError("'last' must not be before 'first'!")
|
||||
|
||||
if isinstance(interval, datetime.timedelta):
|
||||
interval = interval.total_seconds()
|
||||
|
||||
j = self.scheduler.add_job(
|
||||
callback,
|
||||
trigger='interval',
|
||||
args=self._build_args(job),
|
||||
start_date=dt_first,
|
||||
end_date=dt_last,
|
||||
seconds=interval,
|
||||
name=name,
|
||||
**job_kwargs,
|
||||
)
|
||||
|
||||
job.job = j
|
||||
return job
|
||||
|
||||
def run_daily(self, callback, time, days=Days.EVERY_DAY, context=None, name=None):
|
||||
def run_monthly(
|
||||
self,
|
||||
callback: Callable[['CallbackContext'], None],
|
||||
when: datetime.time,
|
||||
day: int,
|
||||
context: object = None,
|
||||
name: str = None,
|
||||
day_is_strict: bool = True,
|
||||
job_kwargs: JSONDict = None,
|
||||
) -> 'Job':
|
||||
"""Creates a new ``Job`` that runs on a monthly basis and adds it to the queue.
|
||||
|
||||
Args:
|
||||
callback (:obj:`callable`): The callback function that should be executed by the new
|
||||
job. Callback signature for context based API:
|
||||
|
||||
``def callback(CallbackContext)``
|
||||
|
||||
``context.job`` is the :class:`telegram.ext.Job` instance. It can be used to access
|
||||
its ``job.context`` or change it to a repeating job.
|
||||
when (:obj:`datetime.time`): Time of day at which the job should run. If the timezone
|
||||
(``when.tzinfo``) is :obj:`None`, the default timezone of the bot will be used.
|
||||
day (:obj:`int`): Defines the day of the month whereby the job would run. It should
|
||||
be within the range of 1 and 31, inclusive.
|
||||
context (:obj:`object`, optional): Additional data needed for the callback function.
|
||||
Can be accessed through ``job.context`` in the callback. Defaults to :obj:`None`.
|
||||
name (:obj:`str`, optional): The name of the new job. Defaults to
|
||||
``callback.__name__``.
|
||||
day_is_strict (:obj:`bool`, optional): If :obj:`False` and day > month.days, will pick
|
||||
the last day in the month. Defaults to :obj:`True`.
|
||||
job_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to pass to the
|
||||
``scheduler.add_job()``.
|
||||
|
||||
Returns:
|
||||
:class:`telegram.ext.Job`: The new ``Job`` instance that has been added to the job
|
||||
queue.
|
||||
|
||||
"""
|
||||
if not job_kwargs:
|
||||
job_kwargs = {}
|
||||
|
||||
name = name or callback.__name__
|
||||
job = Job(callback, context, name, self)
|
||||
|
||||
if day_is_strict:
|
||||
j = self.scheduler.add_job(
|
||||
callback,
|
||||
trigger='cron',
|
||||
args=self._build_args(job),
|
||||
name=name,
|
||||
day=day,
|
||||
hour=when.hour,
|
||||
minute=when.minute,
|
||||
second=when.second,
|
||||
timezone=when.tzinfo or self.scheduler.timezone,
|
||||
**job_kwargs,
|
||||
)
|
||||
else:
|
||||
trigger = OrTrigger(
|
||||
[
|
||||
CronTrigger(
|
||||
day=day,
|
||||
hour=when.hour,
|
||||
minute=when.minute,
|
||||
second=when.second,
|
||||
timezone=when.tzinfo,
|
||||
**job_kwargs,
|
||||
),
|
||||
CronTrigger(
|
||||
day='last',
|
||||
hour=when.hour,
|
||||
minute=when.minute,
|
||||
second=when.second,
|
||||
timezone=when.tzinfo or self.scheduler.timezone,
|
||||
**job_kwargs,
|
||||
),
|
||||
]
|
||||
)
|
||||
j = self.scheduler.add_job(
|
||||
callback, trigger=trigger, args=self._build_args(job), name=name, **job_kwargs
|
||||
)
|
||||
|
||||
job.job = j
|
||||
return job
|
||||
|
||||
def run_daily(
|
||||
self,
|
||||
callback: Callable[['CallbackContext'], None],
|
||||
time: datetime.time,
|
||||
days: Tuple[int, ...] = Days.EVERY_DAY,
|
||||
context: object = None,
|
||||
name: str = None,
|
||||
job_kwargs: JSONDict = None,
|
||||
) -> 'Job':
|
||||
"""Creates a new ``Job`` that runs on a daily basis and adds it to the queue.
|
||||
|
||||
Args:
|
||||
callback (:obj:`callable`): The callback function that should be executed by the new
|
||||
job. It should take ``bot, job`` as parameters, where ``job`` is the
|
||||
:class:`telegram.ext.Job` instance. It can be used to access its ``Job.context``
|
||||
or change it to a repeating job.
|
||||
time (:obj:`datetime.time`): Time of day at which the job should run.
|
||||
job. Callback signature for context based API:
|
||||
|
||||
``def callback(CallbackContext)``
|
||||
|
||||
``context.job`` is the :class:`telegram.ext.Job` instance. It can be used to access
|
||||
its ``job.context`` or change it to a repeating job.
|
||||
time (:obj:`datetime.time`): Time of day at which the job should run. If the timezone
|
||||
(``time.tzinfo``) is :obj:`None`, the default timezone of the bot will be used.
|
||||
days (Tuple[:obj:`int`], optional): Defines on which days of the week the job should
|
||||
run. Defaults to ``EVERY_DAY``
|
||||
run (where ``0-6`` correspond to monday - sunday). Defaults to ``EVERY_DAY``
|
||||
context (:obj:`object`, optional): Additional data needed for the callback function.
|
||||
Can be accessed through ``job.context`` in the callback. Defaults to :obj:`None`.
|
||||
name (:obj:`str`, optional): The name of the new job. Defaults to
|
||||
``callback.__name__``.
|
||||
job_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to pass to the
|
||||
``scheduler.add_job()``.
|
||||
|
||||
Returns:
|
||||
:class:`telegram.ext.Job`: The new ``Job`` instance that has been added to the job
|
||||
queue.
|
||||
|
||||
Note:
|
||||
For a note about DST, please see the documentation of `APScheduler`_.
|
||||
|
||||
.. _`APScheduler`: https://apscheduler.readthedocs.io/en/stable/modules/triggers/cron.html
|
||||
#daylight-saving-time-behavior
|
||||
|
||||
"""
|
||||
if not job_kwargs:
|
||||
job_kwargs = {}
|
||||
|
||||
name = name or callback.__name__
|
||||
job = Job(callback, context, name, self)
|
||||
|
||||
j = self.scheduler.add_job(
|
||||
callback,
|
||||
name=name,
|
||||
args=self._build_args(job),
|
||||
trigger='cron',
|
||||
day_of_week=','.join([str(d) for d in days]),
|
||||
hour=time.hour,
|
||||
minute=time.minute,
|
||||
second=time.second,
|
||||
timezone=time.tzinfo or self.scheduler.timezone,
|
||||
**job_kwargs,
|
||||
)
|
||||
|
||||
job.job = j
|
||||
return job
|
||||
|
||||
def run_custom(
|
||||
self,
|
||||
callback: Callable[['CallbackContext'], None],
|
||||
job_kwargs: JSONDict,
|
||||
context: object = None,
|
||||
name: str = None,
|
||||
) -> 'Job':
|
||||
"""Creates a new customly defined ``Job``.
|
||||
|
||||
Args:
|
||||
callback (:obj:`callable`): The callback function that should be executed by the new
|
||||
job. Callback signature for context based API:
|
||||
|
||||
``def callback(CallbackContext)``
|
||||
|
||||
``context.job`` is the :class:`telegram.ext.Job` instance. It can be used to access
|
||||
its ``job.context`` or change it to a repeating job.
|
||||
job_kwargs (:obj:`dict`): Arbitrary keyword arguments. Used as arguments for
|
||||
``scheduler.add_job``.
|
||||
context (:obj:`object`, optional): Additional data needed for the callback function.
|
||||
Can be accessed through ``job.context`` in the callback. Defaults to ``None``.
|
||||
name (:obj:`str`, optional): The name of the new job. Defaults to
|
||||
@@ -207,295 +480,161 @@ class JobQueue(object):
|
||||
:class:`telegram.ext.Job`: The new ``Job`` instance that has been added to the job
|
||||
queue.
|
||||
|
||||
Notes:
|
||||
Daily is just an alias for "24 Hours". That means that if DST changes during that
|
||||
interval, the job might not run at the time one would expect. It is always recommended
|
||||
to pin servers to UTC time, then time related behaviour can always be expected.
|
||||
|
||||
"""
|
||||
job = Job(callback,
|
||||
interval=datetime.timedelta(days=1),
|
||||
repeat=True,
|
||||
days=days,
|
||||
context=context,
|
||||
name=name,
|
||||
job_queue=self)
|
||||
self._put(job, next_t=time)
|
||||
name = name or callback.__name__
|
||||
job = Job(callback, context, name, self)
|
||||
|
||||
j = self.scheduler.add_job(callback, args=self._build_args(job), name=name, **job_kwargs)
|
||||
|
||||
job.job = j
|
||||
return job
|
||||
|
||||
def _set_next_peek(self, t):
|
||||
# """
|
||||
# Set next peek if not defined or `t` is before next peek.
|
||||
# In case the next peek was set, also trigger the `self.__tick` event.
|
||||
# """
|
||||
with self.__next_peek_lock:
|
||||
if not self._next_peek or self._next_peek > t:
|
||||
self._next_peek = t
|
||||
self.__tick.set()
|
||||
|
||||
def tick(self):
|
||||
"""Run all jobs that are due and re-enqueue them with their interval."""
|
||||
now = time.time()
|
||||
|
||||
self.logger.debug('Ticking jobs with t=%f', now)
|
||||
|
||||
while True:
|
||||
try:
|
||||
t, job = self._queue.get(False)
|
||||
except Empty:
|
||||
break
|
||||
|
||||
self.logger.debug('Peeked at %s with t=%f', job.name, t)
|
||||
|
||||
if t > now:
|
||||
# We can get here in two conditions:
|
||||
# 1. At the second or later pass of the while loop, after we've already
|
||||
# processed the job(s) we were supposed to at this time.
|
||||
# 2. At the first iteration of the loop only if `self.put()` had triggered
|
||||
# `self.__tick` because `self._next_peek` wasn't set
|
||||
self.logger.debug("Next task isn't due yet. Finished!")
|
||||
self._queue.put((t, job))
|
||||
self._set_next_peek(t)
|
||||
break
|
||||
|
||||
if job.removed:
|
||||
self.logger.debug('Removing job %s', job.name)
|
||||
continue
|
||||
|
||||
if job.enabled:
|
||||
try:
|
||||
current_week_day = datetime.datetime.now().weekday()
|
||||
if any(day == current_week_day for day in job.days):
|
||||
self.logger.debug('Running job %s', job.name)
|
||||
job.run(self._dispatcher)
|
||||
|
||||
except Exception:
|
||||
self.logger.exception('An uncaught error was raised while executing job %s',
|
||||
job.name)
|
||||
else:
|
||||
self.logger.debug('Skipping disabled job %s', job.name)
|
||||
|
||||
if job.repeat and not job.removed:
|
||||
self._put(job, last_t=t)
|
||||
else:
|
||||
self.logger.debug('Dropping non-repeating or removed job %s', job.name)
|
||||
|
||||
def start(self):
|
||||
def start(self) -> None:
|
||||
"""Starts the job_queue thread."""
|
||||
self.__start_lock.acquire()
|
||||
if not self.scheduler.running:
|
||||
self.scheduler.start()
|
||||
|
||||
if not self._running:
|
||||
self._running = True
|
||||
self.__start_lock.release()
|
||||
self.__thread = Thread(target=self._main_loop,
|
||||
name="Bot:{}:job_queue".format(self._dispatcher.bot.id))
|
||||
self.__thread.start()
|
||||
self.logger.debug('%s thread started', self.__class__.__name__)
|
||||
else:
|
||||
self.__start_lock.release()
|
||||
|
||||
def _main_loop(self):
|
||||
"""
|
||||
Thread target of thread ``job_queue``. Runs in background and performs ticks on the job
|
||||
queue.
|
||||
|
||||
"""
|
||||
while self._running:
|
||||
# self._next_peek may be (re)scheduled during self.tick() or self.put()
|
||||
with self.__next_peek_lock:
|
||||
tmout = self._next_peek - time.time() if self._next_peek else None
|
||||
self._next_peek = None
|
||||
self.__tick.clear()
|
||||
|
||||
self.__tick.wait(tmout)
|
||||
|
||||
# If we were woken up by self.stop(), just bail out
|
||||
if not self._running:
|
||||
break
|
||||
|
||||
self.tick()
|
||||
|
||||
self.logger.debug('%s thread stopped', self.__class__.__name__)
|
||||
|
||||
def stop(self):
|
||||
def stop(self) -> None:
|
||||
"""Stops the thread."""
|
||||
with self.__start_lock:
|
||||
self._running = False
|
||||
if self.scheduler.running:
|
||||
self.scheduler.shutdown()
|
||||
|
||||
self.__tick.set()
|
||||
if self.__thread is not None:
|
||||
self.__thread.join()
|
||||
def jobs(self) -> Tuple['Job', ...]:
|
||||
"""
|
||||
Returns a tuple of all *pending/scheduled* jobs that are currently in the ``JobQueue``.
|
||||
"""
|
||||
return tuple(Job.from_aps_job(job, self) for job in self.scheduler.get_jobs())
|
||||
|
||||
def jobs(self):
|
||||
"""Returns a tuple of all jobs that are currently in the ``JobQueue``."""
|
||||
with self._queue.mutex:
|
||||
return tuple(job[1] for job in self._queue.queue if job)
|
||||
|
||||
def get_jobs_by_name(self, name):
|
||||
"""Returns a tuple of jobs with the given name that are currently in the ``JobQueue``"""
|
||||
with self._queue.mutex:
|
||||
return tuple(job[1] for job in self._queue.queue if job and job[1].name == name)
|
||||
def get_jobs_by_name(self, name: str) -> Tuple['Job', ...]:
|
||||
"""Returns a tuple of all *pending/scheduled* jobs with the given name that are currently
|
||||
in the ``JobQueue``"""
|
||||
return tuple(job for job in self.jobs() if job.name == name)
|
||||
|
||||
|
||||
class Job(object):
|
||||
"""This class encapsulates a Job.
|
||||
class Job:
|
||||
"""This class is a convenience wrapper for the jobs held in a :class:`telegram.ext.JobQueue`.
|
||||
With the current backend APScheduler, :attr:`job` holds a :class:`apscheduler.job.Job`
|
||||
instance.
|
||||
|
||||
Note:
|
||||
* All attributes and instance methods of :attr:`job` are also directly available as
|
||||
attributes/methods of the corresponding :class:`telegram.ext.Job` object.
|
||||
* Two instances of :class:`telegram.ext.Job` are considered equal, if their corresponding
|
||||
``job`` attributes have the same ``id``.
|
||||
* If :attr:`job` isn't passed on initialization, it must be set manually afterwards for
|
||||
this :class:`telegram.ext.Job` to be useful.
|
||||
|
||||
Args:
|
||||
callback (:obj:`callable`): The callback function that should be executed by the new job.
|
||||
Callback signature for context based API:
|
||||
|
||||
``def callback(CallbackContext)``
|
||||
|
||||
a ``context.job`` is the :class:`telegram.ext.Job` instance. It can be used to access
|
||||
its ``job.context`` or change it to a repeating job.
|
||||
context (:obj:`object`, optional): Additional data needed for the callback function. Can be
|
||||
accessed through ``job.context`` in the callback. Defaults to :obj:`None`.
|
||||
name (:obj:`str`, optional): The name of the new job. Defaults to ``callback.__name__``.
|
||||
job_queue (:class:`telegram.ext.JobQueue`, optional): The ``JobQueue`` this job belongs to.
|
||||
Only optional for backward compatibility with ``JobQueue.put()``.
|
||||
job (:class:`apscheduler.job.Job`, optional): The APS Job this job is a wrapper for.
|
||||
|
||||
Attributes:
|
||||
callback (:obj:`callable`): The callback function that should be executed by the new job.
|
||||
context (:obj:`object`): Optional. Additional data needed for the callback function.
|
||||
name (:obj:`str`): Optional. The name of the new job.
|
||||
|
||||
Args:
|
||||
callback (:obj:`callable`): The callback function that should be executed by the new job.
|
||||
It should take ``bot, job`` as parameters, where ``job`` is the
|
||||
:class:`telegram.ext.Job` instance. It can be used to access it's :attr:`context`
|
||||
or change it to a repeating job.
|
||||
interval (:obj:`int` | :obj:`float` | :obj:`datetime.timedelta`, optional): The interval in
|
||||
which the job will run. If it is an :obj:`int` or a :obj:`float`, it will be
|
||||
interpreted as seconds. If you don't set this value, you must set :attr:`repeat` to
|
||||
``False`` and specify :attr:`next_t` when you put the job into the job queue.
|
||||
repeat (:obj:`bool`, optional): If this job should be periodically execute its callback
|
||||
function (``True``) or only once (``False``). Defaults to ``True``.
|
||||
context (:obj:`object`, optional): Additional data needed for the callback function. Can be
|
||||
accessed through ``job.context`` in the callback. Defaults to ``None``.
|
||||
name (:obj:`str`, optional): The name of the new job. Defaults to ``callback.__name__``.
|
||||
days (Tuple[:obj:`int`], optional): Defines on which days of the week the job should run.
|
||||
Defaults to ``Days.EVERY_DAY``
|
||||
job_queue (:class:`telegram.ext.JobQueue`, optional): The ``JobQueue`` this job belongs to.
|
||||
Only optional for backward compatibility with ``JobQueue.put()``.
|
||||
|
||||
job_queue (:class:`telegram.ext.JobQueue`): Optional. The ``JobQueue`` this job belongs to.
|
||||
job (:class:`apscheduler.job.Job`): Optional. The APS Job this job is a wrapper for.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
callback,
|
||||
interval=None,
|
||||
repeat=True,
|
||||
context=None,
|
||||
days=Days.EVERY_DAY,
|
||||
name=None,
|
||||
job_queue=None):
|
||||
def __init__(
|
||||
self,
|
||||
callback: Callable[['CallbackContext'], None],
|
||||
context: object = None,
|
||||
name: str = None,
|
||||
job_queue: JobQueue = None,
|
||||
job: APSJob = None,
|
||||
):
|
||||
|
||||
self.callback = callback
|
||||
self.context = context
|
||||
self.name = name or callback.__name__
|
||||
self.job_queue = job_queue
|
||||
|
||||
self._repeat = repeat
|
||||
self._interval = None
|
||||
self.interval = interval
|
||||
self.repeat = repeat
|
||||
self._removed = False
|
||||
self._enabled = False
|
||||
|
||||
self._days = None
|
||||
self.days = days
|
||||
self.job = cast(APSJob, job)
|
||||
|
||||
self._job_queue = weakref.proxy(job_queue) if job_queue is not None else None
|
||||
def run(self, dispatcher: 'Dispatcher') -> None:
|
||||
"""Executes the callback function independently of the jobs schedule."""
|
||||
try:
|
||||
if dispatcher.use_context:
|
||||
self.callback(CallbackContext.from_job(self, dispatcher))
|
||||
else:
|
||||
self.callback(dispatcher.bot, self) # type: ignore[arg-type,call-arg]
|
||||
except Exception as exc:
|
||||
try:
|
||||
dispatcher.dispatch_error(None, exc)
|
||||
# Errors should not stop the thread.
|
||||
except Exception:
|
||||
dispatcher.logger.exception(
|
||||
'An error was raised while processing the job and an '
|
||||
'uncaught error was raised while handling the error '
|
||||
'with an error_handler.'
|
||||
)
|
||||
|
||||
self._remove = Event()
|
||||
self._enabled = Event()
|
||||
self._enabled.set()
|
||||
|
||||
def run(self, dispatcher):
|
||||
"""Executes the callback function."""
|
||||
if dispatcher.use_context:
|
||||
self.callback(CallbackContext.from_job(self, dispatcher))
|
||||
else:
|
||||
self.callback(dispatcher.bot, self)
|
||||
|
||||
def schedule_removal(self):
|
||||
def schedule_removal(self) -> None:
|
||||
"""
|
||||
Schedules this job for removal from the ``JobQueue``. It will be removed without executing
|
||||
its callback function again.
|
||||
|
||||
"""
|
||||
self._remove.set()
|
||||
self.job.remove()
|
||||
self._removed = True
|
||||
|
||||
@property
|
||||
def removed(self):
|
||||
def removed(self) -> bool:
|
||||
""":obj:`bool`: Whether this job is due to be removed."""
|
||||
return self._remove.is_set()
|
||||
return self._removed
|
||||
|
||||
@property
|
||||
def enabled(self):
|
||||
def enabled(self) -> bool:
|
||||
""":obj:`bool`: Whether this job is enabled."""
|
||||
return self._enabled.is_set()
|
||||
return self._enabled
|
||||
|
||||
@enabled.setter
|
||||
def enabled(self, status):
|
||||
def enabled(self, status: bool) -> None:
|
||||
if status:
|
||||
self._enabled.set()
|
||||
self.job.resume()
|
||||
else:
|
||||
self._enabled.clear()
|
||||
self.job.pause()
|
||||
self._enabled = status
|
||||
|
||||
@property
|
||||
def interval(self):
|
||||
def next_t(self) -> Optional[datetime.datetime]:
|
||||
"""
|
||||
:obj:`int` | :obj:`float` | :obj:`datetime.timedelta`: Optional. The interval in which the
|
||||
job will run.
|
||||
|
||||
:obj:`datetime.datetime`: Datetime for the next job execution.
|
||||
Datetime is localized according to :attr:`tzinfo`.
|
||||
If job is removed or already ran it equals to :obj:`None`.
|
||||
"""
|
||||
return self._interval
|
||||
return self.job.next_run_time
|
||||
|
||||
@interval.setter
|
||||
def interval(self, interval):
|
||||
if interval is None and self.repeat:
|
||||
raise ValueError("The 'interval' can not be 'None' when 'repeat' is set to 'True'")
|
||||
|
||||
if not (interval is None or isinstance(interval, (Number, datetime.timedelta))):
|
||||
raise ValueError("The 'interval' must be of type 'datetime.timedelta',"
|
||||
" 'int' or 'float'")
|
||||
|
||||
self._interval = interval
|
||||
|
||||
@property
|
||||
def interval_seconds(self):
|
||||
""":obj:`int`: The interval for this job in seconds."""
|
||||
interval = self.interval
|
||||
if isinstance(interval, datetime.timedelta):
|
||||
return interval.total_seconds()
|
||||
@classmethod
|
||||
def from_aps_job(cls, job: APSJob, job_queue: JobQueue) -> 'Job':
|
||||
# context based callbacks
|
||||
if len(job.args) == 1:
|
||||
context = job.args[0].job.context
|
||||
else:
|
||||
return interval
|
||||
context = job.args[1].context
|
||||
return cls(job.func, context=context, name=job.name, job_queue=job_queue, job=job)
|
||||
|
||||
@property
|
||||
def repeat(self):
|
||||
""":obj:`bool`: Optional. If this job should periodically execute its callback function."""
|
||||
return self._repeat
|
||||
def __getattr__(self, item: str) -> object:
|
||||
return getattr(self.job, item)
|
||||
|
||||
@repeat.setter
|
||||
def repeat(self, repeat):
|
||||
if self.interval is None and repeat:
|
||||
raise ValueError("'repeat' can not be set to 'True' when no 'interval' is set")
|
||||
self._repeat = repeat
|
||||
|
||||
@property
|
||||
def days(self):
|
||||
"""Tuple[:obj:`int`]: Optional. Defines on which days of the week the job should run."""
|
||||
return self._days
|
||||
|
||||
@days.setter
|
||||
def days(self, days):
|
||||
if not isinstance(days, tuple):
|
||||
raise ValueError("The 'days' argument should be of type 'tuple'")
|
||||
|
||||
if not all(isinstance(day, int) for day in days):
|
||||
raise ValueError("The elements of the 'days' argument should be of type 'int'")
|
||||
|
||||
if not all(0 <= day <= 6 for day in days):
|
||||
raise ValueError("The elements of the 'days' argument should be from 0 up to and "
|
||||
"including 6")
|
||||
|
||||
self._days = days
|
||||
|
||||
@property
|
||||
def job_queue(self):
|
||||
""":class:`telegram.ext.JobQueue`: Optional. The ``JobQueue`` this job belongs to."""
|
||||
return self._job_queue
|
||||
|
||||
@job_queue.setter
|
||||
def job_queue(self, job_queue):
|
||||
# Property setter for backward compatibility with JobQueue.put()
|
||||
if not self._job_queue:
|
||||
self._job_queue = weakref.proxy(job_queue)
|
||||
else:
|
||||
raise RuntimeError("The 'job_queue' attribute can only be set once.")
|
||||
|
||||
def __lt__(self, other):
|
||||
def __lt__(self, other: object) -> bool:
|
||||
return False
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if isinstance(other, self.__class__):
|
||||
return self.id == other.id
|
||||
return False
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2018
|
||||
# Copyright (C) 2015-2021
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -19,36 +19,24 @@
|
||||
# TODO: Remove allow_edited
|
||||
"""This module contains the MessageHandler class."""
|
||||
import warnings
|
||||
|
||||
from telegram.utils.deprecate import TelegramDeprecationWarning
|
||||
from typing import TYPE_CHECKING, Callable, Dict, Optional, TypeVar, Union
|
||||
|
||||
from telegram import Update
|
||||
from telegram.ext import Filters
|
||||
from telegram.ext import BaseFilter, Filters
|
||||
from telegram.utils.deprecate import TelegramDeprecationWarning
|
||||
from telegram.utils.helpers import DefaultValue, DEFAULT_FALSE
|
||||
|
||||
from .handler import Handler
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram.ext import CallbackContext, Dispatcher
|
||||
|
||||
class MessageHandler(Handler):
|
||||
RT = TypeVar('RT')
|
||||
|
||||
|
||||
class MessageHandler(Handler[Update]):
|
||||
"""Handler class to handle telegram messages. They might contain text, media or status updates.
|
||||
|
||||
Attributes:
|
||||
filters (:obj:`Filter`): Only allow updates with these Filters. See
|
||||
:mod:`telegram.ext.filters` for a full list of all available filters.
|
||||
callback (:obj:`callable`): The callback function for this handler.
|
||||
pass_update_queue (:obj:`bool`): Determines whether ``update_queue`` will be
|
||||
passed to the callback function.
|
||||
pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to
|
||||
the callback function.
|
||||
pass_user_data (:obj:`bool`): Determines whether ``user_data`` will be passed to
|
||||
the callback function.
|
||||
pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to
|
||||
the callback function.
|
||||
message_updates (:obj:`bool`): Should "normal" message updates be handled?
|
||||
Default is ``None``.
|
||||
channel_post_updates (:obj:`bool`): Should channel posts updates be handled?
|
||||
Default is ``None``.
|
||||
edited_updates (:obj:`bool`): Should "edited" message updates be handled?
|
||||
Default is ``None``.
|
||||
|
||||
Note:
|
||||
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you
|
||||
can use to keep any data in will be sent to the :attr:`callback` function. Related to
|
||||
@@ -58,6 +46,10 @@ class MessageHandler(Handler):
|
||||
Note that this is DEPRECATED, and you should use context based callbacks. See
|
||||
https://git.io/fxJuV for more info.
|
||||
|
||||
Warning:
|
||||
When setting ``run_async`` to :obj:`True`, you cannot rely on adding custom
|
||||
attributes to :class:`telegram.ext.CallbackContext`. See its docs for more info.
|
||||
|
||||
Args:
|
||||
filters (:class:`telegram.ext.BaseFilter`, optional): A filter inheriting from
|
||||
:class:`telegram.ext.filters.BaseFilter`. Standard filters can be found in
|
||||
@@ -75,90 +67,123 @@ class MessageHandler(Handler):
|
||||
|
||||
The return value of the callback is usually ignored except for the special case of
|
||||
:class:`telegram.ext.ConversationHandler`.
|
||||
pass_update_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called
|
||||
pass_update_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``update_queue`` will be passed to the callback function. It will be the ``Queue``
|
||||
instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher`
|
||||
that contains new updates which can be used to insert updates. Default is ``False``.
|
||||
that contains new updates which can be used to insert updates. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_job_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called
|
||||
pass_job_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``job_queue`` will be passed to the callback function. It will be a
|
||||
:class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater`
|
||||
which can be used to schedule new jobs. Default is ``False``.
|
||||
which can be used to schedule new jobs. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_user_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called
|
||||
``user_data`` will be passed to the callback function. Default is ``False``.
|
||||
pass_user_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``user_data`` will be passed to the callback function. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_chat_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called
|
||||
``chat_data`` will be passed to the callback function. Default is ``False``.
|
||||
pass_chat_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``chat_data`` will be passed to the callback function. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
message_updates (:obj:`bool`, optional): Should "normal" message updates be handled?
|
||||
Default is ``None``.
|
||||
Default is :obj:`None`.
|
||||
DEPRECATED: Please switch to filters for update filtering.
|
||||
channel_post_updates (:obj:`bool`, optional): Should channel posts updates be handled?
|
||||
Default is ``None``.
|
||||
Default is :obj:`None`.
|
||||
DEPRECATED: Please switch to filters for update filtering.
|
||||
edited_updates (:obj:`bool`, optional): Should "edited" message updates be handled? Default
|
||||
is ``None``.
|
||||
is :obj:`None`.
|
||||
DEPRECATED: Please switch to filters for update filtering.
|
||||
run_async (:obj:`bool`): Determines whether the callback will run asynchronously.
|
||||
Defaults to :obj:`False`.
|
||||
|
||||
Raises:
|
||||
ValueError
|
||||
|
||||
Attributes:
|
||||
filters (:obj:`Filter`): Only allow updates with these Filters. See
|
||||
:mod:`telegram.ext.filters` for a full list of all available filters.
|
||||
callback (:obj:`callable`): The callback function for this handler.
|
||||
pass_update_queue (:obj:`bool`): Determines whether ``update_queue`` will be
|
||||
passed to the callback function.
|
||||
pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to
|
||||
the callback function.
|
||||
pass_user_data (:obj:`bool`): Determines whether ``user_data`` will be passed to
|
||||
the callback function.
|
||||
pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to
|
||||
the callback function.
|
||||
message_updates (:obj:`bool`): Should "normal" message updates be handled?
|
||||
Default is :obj:`None`.
|
||||
channel_post_updates (:obj:`bool`): Should channel posts updates be handled?
|
||||
Default is :obj:`None`.
|
||||
edited_updates (:obj:`bool`): Should "edited" message updates be handled?
|
||||
Default is :obj:`None`.
|
||||
run_async (:obj:`bool`): Determines whether the callback will run asynchronously.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
filters,
|
||||
callback,
|
||||
pass_update_queue=False,
|
||||
pass_job_queue=False,
|
||||
pass_user_data=False,
|
||||
pass_chat_data=False,
|
||||
message_updates=None,
|
||||
channel_post_updates=None,
|
||||
edited_updates=None):
|
||||
def __init__(
|
||||
self,
|
||||
filters: BaseFilter,
|
||||
callback: Callable[[Update, 'CallbackContext'], RT],
|
||||
pass_update_queue: bool = False,
|
||||
pass_job_queue: bool = False,
|
||||
pass_user_data: bool = False,
|
||||
pass_chat_data: bool = False,
|
||||
message_updates: bool = None,
|
||||
channel_post_updates: bool = None,
|
||||
edited_updates: bool = None,
|
||||
run_async: Union[bool, DefaultValue] = DEFAULT_FALSE,
|
||||
):
|
||||
|
||||
super(MessageHandler, self).__init__(
|
||||
super().__init__(
|
||||
callback,
|
||||
pass_update_queue=pass_update_queue,
|
||||
pass_job_queue=pass_job_queue,
|
||||
pass_user_data=pass_user_data,
|
||||
pass_chat_data=pass_chat_data)
|
||||
pass_chat_data=pass_chat_data,
|
||||
run_async=run_async,
|
||||
)
|
||||
if message_updates is False and channel_post_updates is False and edited_updates is False:
|
||||
raise ValueError(
|
||||
'message_updates, channel_post_updates and edited_updates are all False')
|
||||
self.filters = filters
|
||||
if self.filters is not None:
|
||||
self.filters &= Filters.update
|
||||
'message_updates, channel_post_updates and edited_updates are all False'
|
||||
)
|
||||
if filters is not None:
|
||||
self.filters = Filters.update & filters
|
||||
else:
|
||||
self.filters = Filters.update
|
||||
if message_updates is not None:
|
||||
warnings.warn('message_updates is deprecated. See https://git.io/fxJuV for more info',
|
||||
TelegramDeprecationWarning,
|
||||
stacklevel=2)
|
||||
warnings.warn(
|
||||
'message_updates is deprecated. See https://git.io/fxJuV for more info',
|
||||
TelegramDeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
if message_updates is False:
|
||||
self.filters &= ~Filters.update.message
|
||||
|
||||
if channel_post_updates is not None:
|
||||
warnings.warn('channel_post_updates is deprecated. See https://git.io/fxJuV '
|
||||
'for more info',
|
||||
TelegramDeprecationWarning,
|
||||
stacklevel=2)
|
||||
warnings.warn(
|
||||
'channel_post_updates is deprecated. See https://git.io/fxJuV ' 'for more info',
|
||||
TelegramDeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
if channel_post_updates is False:
|
||||
self.filters &= ~Filters.update.channel_post
|
||||
|
||||
if edited_updates is not None:
|
||||
warnings.warn('edited_updates is deprecated. See https://git.io/fxJuV for more info',
|
||||
TelegramDeprecationWarning,
|
||||
stacklevel=2)
|
||||
warnings.warn(
|
||||
'edited_updates is deprecated. See https://git.io/fxJuV for more info',
|
||||
TelegramDeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
if edited_updates is False:
|
||||
self.filters &= ~(Filters.update.edited_message
|
||||
| Filters.update.edited_channel_post)
|
||||
self.filters &= ~(
|
||||
Filters.update.edited_message | Filters.update.edited_channel_post
|
||||
)
|
||||
|
||||
def check_update(self, update):
|
||||
def check_update(self, update: object) -> Optional[Union[bool, Dict[str, object]]]:
|
||||
"""Determines whether an update should be passed to this handlers :attr:`callback`.
|
||||
|
||||
Args:
|
||||
update (:class:`telegram.Update`): Incoming telegram update.
|
||||
update (:class:`telegram.Update` | :obj:`object`): Incoming update.
|
||||
|
||||
Returns:
|
||||
:obj:`bool`
|
||||
@@ -166,7 +191,14 @@ class MessageHandler(Handler):
|
||||
"""
|
||||
if isinstance(update, Update) and update.effective_message:
|
||||
return self.filters(update)
|
||||
return None
|
||||
|
||||
def collect_additional_context(self, context, update, dispatcher, check_result):
|
||||
def collect_additional_context(
|
||||
self,
|
||||
context: 'CallbackContext',
|
||||
update: Update,
|
||||
dispatcher: 'Dispatcher',
|
||||
check_result: Optional[Union[bool, Dict[str, object]]],
|
||||
) -> None:
|
||||
if isinstance(check_result, dict):
|
||||
context.update(check_result)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
# Tymofii A. Khodniev (thodnev) <thodnev@mail.ru>
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2018
|
||||
# Copyright (C) 2015-2021
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -20,32 +20,23 @@
|
||||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/]
|
||||
"""A throughput-limiting message processor for Telegram bots."""
|
||||
from telegram.utils import promise
|
||||
|
||||
import functools
|
||||
import sys
|
||||
import time
|
||||
import queue as q
|
||||
import threading
|
||||
if sys.version_info.major > 2:
|
||||
import queue as q
|
||||
else:
|
||||
import Queue as q
|
||||
import time
|
||||
from typing import TYPE_CHECKING, Callable, List, NoReturn
|
||||
|
||||
from telegram.ext.utils.promise import Promise
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Bot
|
||||
|
||||
# We need to count < 1s intervals, so the most accurate timer is needed
|
||||
# Starting from Python 3.3 we have time.perf_counter which is the clock
|
||||
# with the highest resolution available to the system, so let's use it there.
|
||||
# In Python 2.7, there's no perf_counter yet, so fallback on what we have:
|
||||
# on Windows, the best available is time.clock while time.time is on
|
||||
# another platforms (M. Lutz, "Learning Python," 4ed, p.630-634)
|
||||
if sys.version_info.major == 3 and sys.version_info.minor >= 3:
|
||||
curtime = time.perf_counter # pylint: disable=E1101
|
||||
else:
|
||||
curtime = time.clock if sys.platform[:3] == 'win' else time.time
|
||||
curtime = time.perf_counter
|
||||
|
||||
|
||||
class DelayQueueError(RuntimeError):
|
||||
"""Indicates processing errors."""
|
||||
pass
|
||||
|
||||
|
||||
class DelayQueue(threading.Thread):
|
||||
@@ -53,14 +44,6 @@ class DelayQueue(threading.Thread):
|
||||
Processes callbacks from queue with specified throughput limits. Creates a separate thread to
|
||||
process callbacks with delays.
|
||||
|
||||
Attributes:
|
||||
burst_limit (:obj:`int`): Number of maximum callbacks to process per time-window.
|
||||
time_limit (:obj:`int`): Defines width of time-window used when each processing limit is
|
||||
calculated.
|
||||
exc_route (:obj:`callable`): A callable, accepting 1 positional argument; used to route
|
||||
exceptions from processor thread to main thread;
|
||||
name (:obj:`str`): Thread's name.
|
||||
|
||||
Args:
|
||||
queue (:obj:`Queue`, optional): Used to pass callbacks to thread. Creates ``Queue``
|
||||
implicitly if not provided.
|
||||
@@ -72,49 +55,60 @@ class DelayQueue(threading.Thread):
|
||||
route exceptions from processor thread to main thread; is called on `Exception`
|
||||
subclass exceptions. If not provided, exceptions are routed through dummy handler,
|
||||
which re-raises them.
|
||||
autostart (:obj:`bool`, optional): If True, processor is started immediately after object's
|
||||
creation; if ``False``, should be started manually by `start` method. Defaults to True.
|
||||
autostart (:obj:`bool`, optional): If :obj:`True`, processor is started immediately after
|
||||
object's creation; if :obj:`False`, should be started manually by `start` method.
|
||||
Defaults to :obj:`True`.
|
||||
name (:obj:`str`, optional): Thread's name. Defaults to ``'DelayQueue-N'``, where N is
|
||||
sequential number of object created.
|
||||
|
||||
Attributes:
|
||||
burst_limit (:obj:`int`): Number of maximum callbacks to process per time-window.
|
||||
time_limit (:obj:`int`): Defines width of time-window used when each processing limit is
|
||||
calculated.
|
||||
exc_route (:obj:`callable`): A callable, accepting 1 positional argument; used to route
|
||||
exceptions from processor thread to main thread;
|
||||
name (:obj:`str`): Thread's name.
|
||||
|
||||
"""
|
||||
|
||||
_instcnt = 0 # instance counter
|
||||
|
||||
def __init__(self,
|
||||
queue=None,
|
||||
burst_limit=30,
|
||||
time_limit_ms=1000,
|
||||
exc_route=None,
|
||||
autostart=True,
|
||||
name=None):
|
||||
def __init__(
|
||||
self,
|
||||
queue: q.Queue = None,
|
||||
burst_limit: int = 30,
|
||||
time_limit_ms: int = 1000,
|
||||
exc_route: Callable[[Exception], None] = None,
|
||||
autostart: bool = True,
|
||||
name: str = None,
|
||||
):
|
||||
self._queue = queue if queue is not None else q.Queue()
|
||||
self.burst_limit = burst_limit
|
||||
self.time_limit = time_limit_ms / 1000
|
||||
self.exc_route = (exc_route if exc_route is not None else self._default_exception_handler)
|
||||
self.exc_route = exc_route if exc_route is not None else self._default_exception_handler
|
||||
self.__exit_req = False # flag to gently exit thread
|
||||
self.__class__._instcnt += 1
|
||||
if name is None:
|
||||
name = '%s-%s' % (self.__class__.__name__, self.__class__._instcnt)
|
||||
super(DelayQueue, self).__init__(name=name)
|
||||
name = f'{self.__class__.__name__}-{self.__class__._instcnt}'
|
||||
super().__init__(name=name)
|
||||
self.daemon = False
|
||||
if autostart: # immediately start processing
|
||||
super(DelayQueue, self).start()
|
||||
super().start()
|
||||
|
||||
def run(self):
|
||||
def run(self) -> None:
|
||||
"""
|
||||
Do not use the method except for unthreaded testing purposes, the method normally is
|
||||
automatically called by autostart argument.
|
||||
|
||||
"""
|
||||
|
||||
times = [] # used to store each callable processing time
|
||||
times: List[float] = [] # used to store each callable processing time
|
||||
while True:
|
||||
item = self._queue.get()
|
||||
if self.__exit_req:
|
||||
return # shutdown thread
|
||||
# delay routine
|
||||
now = curtime()
|
||||
now = time.perf_counter()
|
||||
t_delta = now - self.time_limit # calculate early to improve perf.
|
||||
if times and t_delta > times[-1]:
|
||||
# if last call was before the limit time-window
|
||||
@@ -133,23 +127,24 @@ class DelayQueue(threading.Thread):
|
||||
except Exception as exc: # re-route any exceptions
|
||||
self.exc_route(exc) # to prevent thread exit
|
||||
|
||||
def stop(self, timeout=None):
|
||||
def stop(self, timeout: float = None) -> None:
|
||||
"""Used to gently stop processor and shutdown its thread.
|
||||
|
||||
Args:
|
||||
timeout (:obj:`float`): Indicates maximum time to wait for processor to stop and its
|
||||
thread to exit. If timeout exceeds and processor has not stopped, method silently
|
||||
returns. :attr:`is_alive` could be used afterwards to check the actual status.
|
||||
``timeout`` set to None, blocks until processor is shut down. Defaults to None.
|
||||
``timeout`` set to :obj:`None`, blocks until processor is shut down.
|
||||
Defaults to :obj:`None`.
|
||||
|
||||
"""
|
||||
|
||||
self.__exit_req = True # gently request
|
||||
self._queue.put(None) # put something to unfreeze if frozen
|
||||
super(DelayQueue, self).join(timeout=timeout)
|
||||
super().join(timeout=timeout)
|
||||
|
||||
@staticmethod
|
||||
def _default_exception_handler(exc):
|
||||
def _default_exception_handler(exc: Exception) -> NoReturn:
|
||||
"""
|
||||
Dummy exception handler which re-raises exception in thread. Could be possibly overwritten
|
||||
by subclasses.
|
||||
@@ -158,7 +153,7 @@ class DelayQueue(threading.Thread):
|
||||
|
||||
raise exc
|
||||
|
||||
def __call__(self, func, *args, **kwargs):
|
||||
def __call__(self, func: Callable, *args: object, **kwargs: object) -> None:
|
||||
"""Used to process callbacks in throughput-limiting thread through queue.
|
||||
|
||||
Args:
|
||||
@@ -174,13 +169,13 @@ class DelayQueue(threading.Thread):
|
||||
self._queue.put((func, args, kwargs))
|
||||
|
||||
|
||||
# The most straightforward way to implement this is to use 2 sequenital delay
|
||||
# The most straightforward way to implement this is to use 2 sequential delay
|
||||
# queues, like on classic delay chain schematics in electronics.
|
||||
# So, message path is:
|
||||
# msg --> group delay if group msg, else no delay --> normal msg delay --> out
|
||||
# This way OS threading scheduler cares of timings accuracy.
|
||||
# (see time.time, time.clock, time.perf_counter, time.sleep @ docs.python.org)
|
||||
class MessageQueue(object):
|
||||
class MessageQueue:
|
||||
"""
|
||||
Implements callback processing with proper delays to avoid hitting Telegram's message limits.
|
||||
Contains two ``DelayQueue``, for group and for all messages, interconnected in delay chain.
|
||||
@@ -200,56 +195,60 @@ class MessageQueue(object):
|
||||
to route exceptions from processor threads to main thread; is called on ``Exception``
|
||||
subclass exceptions. If not provided, exceptions are routed through dummy handler,
|
||||
which re-raises them.
|
||||
autostart (:obj:`bool`, optional): If True, processors are started immediately after
|
||||
object's creation; if ``False``, should be started manually by :attr:`start` method.
|
||||
Defaults to ``True``.
|
||||
autostart (:obj:`bool`, optional): If :obj:`True`, processors are started immediately after
|
||||
object's creation; if :obj:`False`, should be started manually by :attr:`start` method.
|
||||
Defaults to :obj:`True`.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
all_burst_limit=30,
|
||||
all_time_limit_ms=1000,
|
||||
group_burst_limit=20,
|
||||
group_time_limit_ms=60000,
|
||||
exc_route=None,
|
||||
autostart=True):
|
||||
# create accoring delay queues, use composition
|
||||
def __init__(
|
||||
self,
|
||||
all_burst_limit: int = 30,
|
||||
all_time_limit_ms: int = 1000,
|
||||
group_burst_limit: int = 20,
|
||||
group_time_limit_ms: int = 60000,
|
||||
exc_route: Callable[[Exception], None] = None,
|
||||
autostart: bool = True,
|
||||
):
|
||||
# create according delay queues, use composition
|
||||
self._all_delayq = DelayQueue(
|
||||
burst_limit=all_burst_limit,
|
||||
time_limit_ms=all_time_limit_ms,
|
||||
exc_route=exc_route,
|
||||
autostart=autostart)
|
||||
autostart=autostart,
|
||||
)
|
||||
self._group_delayq = DelayQueue(
|
||||
burst_limit=group_burst_limit,
|
||||
time_limit_ms=group_time_limit_ms,
|
||||
exc_route=exc_route,
|
||||
autostart=autostart)
|
||||
autostart=autostart,
|
||||
)
|
||||
|
||||
def start(self):
|
||||
def start(self) -> None:
|
||||
"""Method is used to manually start the ``MessageQueue`` processing."""
|
||||
self._all_delayq.start()
|
||||
self._group_delayq.start()
|
||||
|
||||
def stop(self, timeout=None):
|
||||
def stop(self, timeout: float = None) -> None:
|
||||
self._group_delayq.stop(timeout=timeout)
|
||||
self._all_delayq.stop(timeout=timeout)
|
||||
|
||||
stop.__doc__ = DelayQueue.stop.__doc__ or '' # reuse docsting if any
|
||||
stop.__doc__ = DelayQueue.stop.__doc__ or '' # reuse docstring if any
|
||||
|
||||
def __call__(self, promise, is_group_msg=False):
|
||||
def __call__(self, promise: Callable, is_group_msg: bool = False) -> Callable:
|
||||
"""
|
||||
Processes callables in troughput-limiting queues to avoid hitting limits (specified with
|
||||
Processes callables in throughput-limiting queues to avoid hitting limits (specified with
|
||||
:attr:`burst_limit` and :attr:`time_limit`.
|
||||
|
||||
Args:
|
||||
promise (:obj:`callable`): Mainly the ``telegram.utils.promise.Promise`` (see Notes for
|
||||
other callables), that is processed in delay queues.
|
||||
is_group_msg (:obj:`bool`, optional): Defines whether ``promise`` would be processed in
|
||||
group*+*all* ``DelayQueue``s (if set to ``True``), or only through *all*
|
||||
``DelayQueue`` (if set to ``False``), resulting in needed delays to avoid
|
||||
hitting specified limits. Defaults to ``False``.
|
||||
group*+*all* ``DelayQueue``s (if set to :obj:`True`), or only through *all*
|
||||
``DelayQueue`` (if set to :obj:`False`), resulting in needed delays to avoid
|
||||
hitting specified limits. Defaults to :obj:`False`.
|
||||
|
||||
Notes:
|
||||
Note:
|
||||
Method is designed to accept ``telegram.utils.promise.Promise`` as ``promise``
|
||||
argument, but other callables could be used too. For example, lambdas or simple
|
||||
functions could be used to wrap original func to be called with needed args. In that
|
||||
@@ -268,7 +267,7 @@ class MessageQueue(object):
|
||||
return promise
|
||||
|
||||
|
||||
def queuedmessage(method):
|
||||
def queuedmessage(method: Callable) -> Callable:
|
||||
"""A decorator to be used with :attr:`telegram.Bot` send* methods.
|
||||
|
||||
Note:
|
||||
@@ -287,12 +286,12 @@ def queuedmessage(method):
|
||||
Wrapped method starts accepting the next kwargs:
|
||||
|
||||
Args:
|
||||
queued (:obj:`bool`, optional): If set to ``True``, the ``MessageQueue`` is used to process
|
||||
output messages. Defaults to `self._is_queued_out`.
|
||||
isgroup (:obj:`bool`, optional): If set to ``True``, the message is meant to be group-type
|
||||
(as there's no obvious way to determine its type in other way at the moment).
|
||||
queued (:obj:`bool`, optional): If set to :obj:`True`, the ``MessageQueue`` is used to
|
||||
process output messages. Defaults to `self._is_queued_out`.
|
||||
isgroup (:obj:`bool`, optional): If set to :obj:`True`, the message is meant to be
|
||||
group-type(as there's no obvious way to determine its type in other way at the moment).
|
||||
Group-type messages could have additional processing delay according to limits set
|
||||
in `self._out_queue`. Defaults to ``False``.
|
||||
in `self._out_queue`. Defaults to :obj:`False`.
|
||||
|
||||
Returns:
|
||||
``telegram.utils.promise.Promise``: In case call is queued or original method's return
|
||||
@@ -301,12 +300,15 @@ def queuedmessage(method):
|
||||
"""
|
||||
|
||||
@functools.wraps(method)
|
||||
def wrapped(self, *args, **kwargs):
|
||||
queued = kwargs.pop('queued', self._is_messages_queued_default)
|
||||
def wrapped(self: 'Bot', *args: object, **kwargs: object) -> object:
|
||||
# pylint: disable=W0212
|
||||
queued = kwargs.pop(
|
||||
'queued', self._is_messages_queued_default # type: ignore[attr-defined]
|
||||
)
|
||||
isgroup = kwargs.pop('isgroup', False)
|
||||
if queued:
|
||||
prom = promise.Promise(method, (self, ) + args, kwargs)
|
||||
return self._msg_queue(prom, isgroup)
|
||||
prom = Promise(method, (self,) + args, kwargs)
|
||||
return self._msg_queue(prom, isgroup) # type: ignore[attr-defined]
|
||||
return method(self, *args, **kwargs)
|
||||
|
||||
return wrapped
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2018
|
||||
# Copyright (C) 2015-2021
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -20,93 +20,130 @@
|
||||
import pickle
|
||||
from collections import defaultdict
|
||||
from copy import deepcopy
|
||||
from typing import Any, DefaultDict, Dict, Optional, Tuple
|
||||
|
||||
from telegram.ext import BasePersistence
|
||||
from telegram.utils.types import ConversationDict
|
||||
|
||||
|
||||
class PicklePersistence(BasePersistence):
|
||||
"""Using python's builtin pickle for making you bot persistent.
|
||||
|
||||
Warning:
|
||||
:class:`PicklePersistence` will try to replace :class:`telegram.Bot` instances by
|
||||
:attr:`REPLACED_BOT` and insert the bot set with
|
||||
:meth:`telegram.ext.BasePersistence.set_bot` upon loading of the data. This is to ensure
|
||||
that changes to the bot apply to the saved objects, too. If you change the bots token, this
|
||||
may lead to e.g. ``Chat not found`` errors. For the limitations on replacing bots see
|
||||
:meth:`telegram.ext.BasePersistence.replace_bot` and
|
||||
:meth:`telegram.ext.BasePersistence.insert_bot`.
|
||||
|
||||
Args:
|
||||
filename (:obj:`str`): The filename for storing the pickle files. When :attr:`single_file`
|
||||
is :obj:`False` this will be used as a prefix.
|
||||
store_user_data (:obj:`bool`, optional): Whether user_data should be saved by this
|
||||
persistence class. Default is :obj:`True`.
|
||||
store_chat_data (:obj:`bool`, optional): Whether user_data should be saved by this
|
||||
persistence class. Default is :obj:`True`.
|
||||
store_bot_data (:obj:`bool`, optional): Whether bot_data should be saved by this
|
||||
persistence class. Default is :obj:`True` .
|
||||
single_file (:obj:`bool`, optional): When :obj:`False` will store 3 separate files of
|
||||
`filename_user_data`, `filename_chat_data` and `filename_conversations`. Default is
|
||||
:obj:`True`.
|
||||
on_flush (:obj:`bool`, optional): When :obj:`True` will only save to file when
|
||||
:meth:`flush` is called and keep data in memory until that happens. When
|
||||
:obj:`False` will store data on any transaction *and* on call to :meth:`flush`.
|
||||
Default is :obj:`False`.
|
||||
|
||||
Attributes:
|
||||
filename (:obj:`str`): The filename for storing the pickle files. When :attr:`single_file`
|
||||
is false this will be used as a prefix.
|
||||
is :obj:`False` this will be used as a prefix.
|
||||
store_user_data (:obj:`bool`): Optional. Whether user_data should be saved by this
|
||||
persistence class.
|
||||
store_chat_data (:obj:`bool`): Optional. Whether user_data should be saved by this
|
||||
persistence class.
|
||||
single_file (:obj:`bool`): Optional. When ``False`` will store 3 sperate files of
|
||||
store_bot_data (:obj:`bool`): Optional. Whether bot_data should be saved by this
|
||||
persistence class.
|
||||
single_file (:obj:`bool`): Optional. When :obj:`False` will store 3 separate files of
|
||||
`filename_user_data`, `filename_chat_data` and `filename_conversations`. Default is
|
||||
``True``.
|
||||
on_flush (:obj:`bool`, optional): When ``True`` will only save to file when :meth:`flush`
|
||||
is called and keep data in memory until that happens. When ``False`` will store data
|
||||
on any transaction *and* on call fo :meth:`flush`. Default is ``False``.
|
||||
|
||||
Args:
|
||||
filename (:obj:`str`): The filename for storing the pickle files. When :attr:`single_file`
|
||||
is false this will be used as a prefix.
|
||||
store_user_data (:obj:`bool`, optional): Whether user_data should be saved by this
|
||||
persistence class. Default is ``True``.
|
||||
store_chat_data (:obj:`bool`, optional): Whether user_data should be saved by this
|
||||
persistence class. Default is ``True``.
|
||||
single_file (:obj:`bool`, optional): When ``False`` will store 3 sperate files of
|
||||
`filename_user_data`, `filename_chat_data` and `filename_conversations`. Default is
|
||||
``True``.
|
||||
on_flush (:obj:`bool`, optional): When ``True`` will only save to file when :meth:`flush`
|
||||
is called and keep data in memory until that happens. When ``False`` will store data
|
||||
on any transaction *and* on call fo :meth:`flush`. Default is ``False``.
|
||||
:obj:`True`.
|
||||
on_flush (:obj:`bool`, optional): When :obj:`True` will only save to file when
|
||||
:meth:`flush` is called and keep data in memory until that happens. When
|
||||
:obj:`False` will store data on any transaction *and* on call to :meth:`flush`.
|
||||
Default is :obj:`False`.
|
||||
"""
|
||||
|
||||
def __init__(self, filename, store_user_data=True, store_chat_data=True, single_file=True,
|
||||
on_flush=False):
|
||||
def __init__(
|
||||
self,
|
||||
filename: str,
|
||||
store_user_data: bool = True,
|
||||
store_chat_data: bool = True,
|
||||
store_bot_data: bool = True,
|
||||
single_file: bool = True,
|
||||
on_flush: bool = False,
|
||||
):
|
||||
super().__init__(
|
||||
store_user_data=store_user_data,
|
||||
store_chat_data=store_chat_data,
|
||||
store_bot_data=store_bot_data,
|
||||
)
|
||||
self.filename = filename
|
||||
self.store_user_data = store_user_data
|
||||
self.store_chat_data = store_chat_data
|
||||
self.single_file = single_file
|
||||
self.on_flush = on_flush
|
||||
self.user_data = None
|
||||
self.chat_data = None
|
||||
self.conversations = None
|
||||
self.user_data: Optional[DefaultDict[int, Dict]] = None
|
||||
self.chat_data: Optional[DefaultDict[int, Dict]] = None
|
||||
self.bot_data: Optional[Dict] = None
|
||||
self.conversations: Optional[Dict[str, Dict[Tuple, object]]] = None
|
||||
|
||||
def load_singlefile(self):
|
||||
def load_singlefile(self) -> None:
|
||||
try:
|
||||
filename = self.filename
|
||||
with open(self.filename, "rb") as f:
|
||||
all = pickle.load(f)
|
||||
self.user_data = defaultdict(dict, all['user_data'])
|
||||
self.chat_data = defaultdict(dict, all['chat_data'])
|
||||
self.conversations = all['conversations']
|
||||
except IOError:
|
||||
self.conversations = {}
|
||||
with open(self.filename, "rb") as file:
|
||||
data = pickle.load(file)
|
||||
self.user_data = defaultdict(dict, data['user_data'])
|
||||
self.chat_data = defaultdict(dict, data['chat_data'])
|
||||
# For backwards compatibility with files not containing bot data
|
||||
self.bot_data = data.get('bot_data', {})
|
||||
self.conversations = data['conversations']
|
||||
except OSError:
|
||||
self.conversations = dict()
|
||||
self.user_data = defaultdict(dict)
|
||||
self.chat_data = defaultdict(dict)
|
||||
except pickle.UnpicklingError:
|
||||
raise TypeError("File {} does not contain valid pickle data".format(filename))
|
||||
except Exception:
|
||||
raise TypeError("Something went wrong unpickling {}".format(filename))
|
||||
self.bot_data = {}
|
||||
except pickle.UnpicklingError as exc:
|
||||
raise TypeError(f"File {filename} does not contain valid pickle data") from exc
|
||||
except Exception as exc:
|
||||
raise TypeError(f"Something went wrong unpickling {filename}") from exc
|
||||
|
||||
def load_file(self, filename):
|
||||
@staticmethod
|
||||
def load_file(filename: str) -> Any:
|
||||
try:
|
||||
with open(filename, "rb") as f:
|
||||
return pickle.load(f)
|
||||
except IOError:
|
||||
with open(filename, "rb") as file:
|
||||
return pickle.load(file)
|
||||
except OSError:
|
||||
return None
|
||||
except pickle.UnpicklingError:
|
||||
raise TypeError("File {} does not contain valid pickle data".format(filename))
|
||||
except Exception:
|
||||
raise TypeError("Something went wrong unpickling {}".format(filename))
|
||||
except pickle.UnpicklingError as exc:
|
||||
raise TypeError(f"File {filename} does not contain valid pickle data") from exc
|
||||
except Exception as exc:
|
||||
raise TypeError(f"Something went wrong unpickling {filename}") from exc
|
||||
|
||||
def dump_singlefile(self):
|
||||
with open(self.filename, "wb") as f:
|
||||
all = {'conversations': self.conversations, 'user_data': self.user_data,
|
||||
'chat_data': self.chat_data}
|
||||
pickle.dump(all, f)
|
||||
def dump_singlefile(self) -> None:
|
||||
with open(self.filename, "wb") as file:
|
||||
data = {
|
||||
'conversations': self.conversations,
|
||||
'user_data': self.user_data,
|
||||
'chat_data': self.chat_data,
|
||||
'bot_data': self.bot_data,
|
||||
}
|
||||
pickle.dump(data, file)
|
||||
|
||||
def dump_file(self, filename, data):
|
||||
with open(filename, "wb") as f:
|
||||
pickle.dump(data, f)
|
||||
@staticmethod
|
||||
def dump_file(filename: str, data: object) -> None:
|
||||
with open(filename, "wb") as file:
|
||||
pickle.dump(data, file)
|
||||
|
||||
def get_user_data(self):
|
||||
"""Returns the user_data from the pickle file if it exsists or an empty defaultdict.
|
||||
def get_user_data(self) -> DefaultDict[int, Dict[object, object]]:
|
||||
"""Returns the user_data from the pickle file if it exists or an empty :obj:`defaultdict`.
|
||||
|
||||
Returns:
|
||||
:obj:`defaultdict`: The restored user data.
|
||||
@@ -114,7 +151,7 @@ class PicklePersistence(BasePersistence):
|
||||
if self.user_data:
|
||||
pass
|
||||
elif not self.single_file:
|
||||
filename = "{}_user_data".format(self.filename)
|
||||
filename = f"{self.filename}_user_data"
|
||||
data = self.load_file(filename)
|
||||
if not data:
|
||||
data = defaultdict(dict)
|
||||
@@ -123,10 +160,10 @@ class PicklePersistence(BasePersistence):
|
||||
self.user_data = data
|
||||
else:
|
||||
self.load_singlefile()
|
||||
return deepcopy(self.user_data)
|
||||
return deepcopy(self.user_data) # type: ignore[arg-type]
|
||||
|
||||
def get_chat_data(self):
|
||||
"""Returns the chat_data from the pickle file if it exsists or an empty defaultdict.
|
||||
def get_chat_data(self) -> DefaultDict[int, Dict[object, object]]:
|
||||
"""Returns the chat_data from the pickle file if it exists or an empty :obj:`defaultdict`.
|
||||
|
||||
Returns:
|
||||
:obj:`defaultdict`: The restored chat data.
|
||||
@@ -134,7 +171,7 @@ class PicklePersistence(BasePersistence):
|
||||
if self.chat_data:
|
||||
pass
|
||||
elif not self.single_file:
|
||||
filename = "{}_chat_data".format(self.filename)
|
||||
filename = f"{self.filename}_chat_data"
|
||||
data = self.load_file(filename)
|
||||
if not data:
|
||||
data = defaultdict(dict)
|
||||
@@ -143,10 +180,28 @@ class PicklePersistence(BasePersistence):
|
||||
self.chat_data = data
|
||||
else:
|
||||
self.load_singlefile()
|
||||
return deepcopy(self.chat_data)
|
||||
return deepcopy(self.chat_data) # type: ignore[arg-type]
|
||||
|
||||
def get_conversations(self, name):
|
||||
"""Returns the conversations from the pickle file if it exsists or an empty defaultdict.
|
||||
def get_bot_data(self) -> Dict[object, object]:
|
||||
"""Returns the bot_data from the pickle file if it exists or an empty :obj:`dict`.
|
||||
|
||||
Returns:
|
||||
:obj:`dict`: The restored bot data.
|
||||
"""
|
||||
if self.bot_data:
|
||||
pass
|
||||
elif not self.single_file:
|
||||
filename = f"{self.filename}_bot_data"
|
||||
data = self.load_file(filename)
|
||||
if not data:
|
||||
data = {}
|
||||
self.bot_data = data
|
||||
else:
|
||||
self.load_singlefile()
|
||||
return deepcopy(self.bot_data) # type: ignore[arg-type]
|
||||
|
||||
def get_conversations(self, name: str) -> ConversationDict:
|
||||
"""Returns the conversations from the pickle file if it exsists or an empty dict.
|
||||
|
||||
Args:
|
||||
name (:obj:`str`): The handlers name.
|
||||
@@ -157,80 +212,103 @@ class PicklePersistence(BasePersistence):
|
||||
if self.conversations:
|
||||
pass
|
||||
elif not self.single_file:
|
||||
filename = "{}_conversations".format(self.filename)
|
||||
filename = f"{self.filename}_conversations"
|
||||
data = self.load_file(filename)
|
||||
if not data:
|
||||
data = {name: {}}
|
||||
self.conversations = data
|
||||
else:
|
||||
self.load_singlefile()
|
||||
return self.conversations.get(name, {}).copy()
|
||||
return self.conversations.get(name, {}).copy() # type: ignore[union-attr]
|
||||
|
||||
def update_conversation(self, name, key, new_state):
|
||||
def update_conversation(
|
||||
self, name: str, key: Tuple[int, ...], new_state: Optional[object]
|
||||
) -> None:
|
||||
"""Will update the conversations for the given handler and depending on :attr:`on_flush`
|
||||
save the pickle file.
|
||||
|
||||
Args:
|
||||
name (:obj:`str`): The handlers name.
|
||||
name (:obj:`str`): The handler's name.
|
||||
key (:obj:`tuple`): The key the state is changed for.
|
||||
new_state (:obj:`tuple` | :obj:`any`): The new state for the given key.
|
||||
"""
|
||||
if not self.conversations:
|
||||
self.conversations = dict()
|
||||
if self.conversations.setdefault(name, {}).get(key) == new_state:
|
||||
return
|
||||
self.conversations[name][key] = new_state
|
||||
if not self.on_flush:
|
||||
if not self.single_file:
|
||||
filename = "{}_conversations".format(self.filename)
|
||||
filename = f"{self.filename}_conversations"
|
||||
self.dump_file(filename, self.conversations)
|
||||
else:
|
||||
self.dump_singlefile()
|
||||
|
||||
def update_user_data(self, user_id, data):
|
||||
"""Will update the user_data (if changed) and depending on :attr:`on_flush` save the
|
||||
pickle file.
|
||||
def update_user_data(self, user_id: int, data: Dict) -> None:
|
||||
"""Will update the user_data and depending on :attr:`on_flush` save the pickle file.
|
||||
|
||||
Args:
|
||||
user_id (:obj:`int`): The user the data might have been changed for.
|
||||
data (:obj:`dict`): The :attr:`telegram.ext.dispatcher.user_data` [user_id].
|
||||
"""
|
||||
if self.user_data is None:
|
||||
self.user_data = defaultdict(dict)
|
||||
if self.user_data.get(user_id) == data:
|
||||
return
|
||||
self.user_data[user_id] = data
|
||||
if not self.on_flush:
|
||||
if not self.single_file:
|
||||
filename = "{}_user_data".format(self.filename)
|
||||
filename = f"{self.filename}_user_data"
|
||||
self.dump_file(filename, self.user_data)
|
||||
else:
|
||||
self.dump_singlefile()
|
||||
|
||||
def update_chat_data(self, chat_id, data):
|
||||
"""Will update the chat_data (if changed) and depending on :attr:`on_flush` save the
|
||||
pickle file.
|
||||
def update_chat_data(self, chat_id: int, data: Dict) -> None:
|
||||
"""Will update the chat_data and depending on :attr:`on_flush` save the pickle file.
|
||||
|
||||
Args:
|
||||
chat_id (:obj:`int`): The chat the data might have been changed for.
|
||||
data (:obj:`dict`): The :attr:`telegram.ext.dispatcher.chat_data` [chat_id].
|
||||
"""
|
||||
if self.chat_data is None:
|
||||
self.chat_data = defaultdict(dict)
|
||||
if self.chat_data.get(chat_id) == data:
|
||||
return
|
||||
self.chat_data[chat_id] = data
|
||||
if not self.on_flush:
|
||||
if not self.single_file:
|
||||
filename = "{}_chat_data".format(self.filename)
|
||||
filename = f"{self.filename}_chat_data"
|
||||
self.dump_file(filename, self.chat_data)
|
||||
else:
|
||||
self.dump_singlefile()
|
||||
|
||||
def flush(self):
|
||||
""" Will save all data in memory to pickle file(s).
|
||||
def update_bot_data(self, data: Dict) -> None:
|
||||
"""Will update the bot_data and depending on :attr:`on_flush` save the pickle file.
|
||||
|
||||
Args:
|
||||
data (:obj:`dict`): The :attr:`telegram.ext.dispatcher.bot_data`.
|
||||
"""
|
||||
if self.bot_data == data:
|
||||
return
|
||||
self.bot_data = data.copy()
|
||||
if not self.on_flush:
|
||||
if not self.single_file:
|
||||
filename = f"{self.filename}_bot_data"
|
||||
self.dump_file(filename, self.bot_data)
|
||||
else:
|
||||
self.dump_singlefile()
|
||||
|
||||
def flush(self) -> None:
|
||||
"""Will save all data in memory to pickle file(s)."""
|
||||
if self.single_file:
|
||||
if self.user_data or self.chat_data or self.conversations:
|
||||
if self.user_data or self.chat_data or self.bot_data or self.conversations:
|
||||
self.dump_singlefile()
|
||||
else:
|
||||
if self.user_data:
|
||||
self.dump_file("{}_user_data".format(self.filename), self.user_data)
|
||||
self.dump_file(f"{self.filename}_user_data", self.user_data)
|
||||
if self.chat_data:
|
||||
self.dump_file("{}_chat_data".format(self.filename), self.chat_data)
|
||||
self.dump_file(f"{self.filename}_chat_data", self.chat_data)
|
||||
if self.bot_data:
|
||||
self.dump_file(f"{self.filename}_bot_data", self.bot_data)
|
||||
if self.conversations:
|
||||
self.dump_file("{}_conversations".format(self.filename), self.conversations)
|
||||
self.dump_file(f"{self.filename}_conversations", self.conversations)
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2019-2021
|
||||
# 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 the PollAnswerHandler class."""
|
||||
|
||||
|
||||
from telegram import Update
|
||||
|
||||
from .handler import Handler
|
||||
|
||||
|
||||
class PollAnswerHandler(Handler[Update]):
|
||||
"""Handler class to handle Telegram updates that contain a poll answer.
|
||||
|
||||
Note:
|
||||
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you
|
||||
can use to keep any data in will be sent to the :attr:`callback` function. Related to
|
||||
either the user or the chat that the update was sent in. For each update from the same user
|
||||
or in the same chat, it will be the same ``dict``.
|
||||
|
||||
Note that this is DEPRECATED, and you should use context based callbacks. See
|
||||
https://git.io/fxJuV for more info.
|
||||
|
||||
Warning:
|
||||
When setting ``run_async`` to :obj:`True`, you cannot rely on adding custom
|
||||
attributes to :class:`telegram.ext.CallbackContext`. See its docs for more info.
|
||||
|
||||
Args:
|
||||
callback (:obj:`callable`): The callback function for this handler. Will be called when
|
||||
:attr:`check_update` has determined that an update should be processed by this handler.
|
||||
Callback signature for context based API:
|
||||
|
||||
``def callback(update: Update, context: CallbackContext)``
|
||||
|
||||
The return value of the callback is usually ignored except for the special case of
|
||||
:class:`telegram.ext.ConversationHandler`.
|
||||
pass_update_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``update_queue`` will be passed to the callback function. It will be the ``Queue``
|
||||
instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher`
|
||||
that contains new updates which can be used to insert updates. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_job_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``job_queue`` will be passed to the callback function. It will be a
|
||||
:class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater`
|
||||
which can be used to schedule new jobs. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_user_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``user_data`` will be passed to the callback function. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_chat_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``chat_data`` will be passed to the callback function. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
run_async (:obj:`bool`): Determines whether the callback will run asynchronously.
|
||||
Defaults to :obj:`False`.
|
||||
|
||||
Attributes:
|
||||
callback (:obj:`callable`): The callback function for this handler.
|
||||
pass_update_queue (:obj:`bool`): Determines whether ``update_queue`` will be
|
||||
passed to the callback function.
|
||||
pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to
|
||||
the callback function.
|
||||
pass_user_data (:obj:`bool`): Determines whether ``user_data`` will be passed to
|
||||
the callback function.
|
||||
pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to
|
||||
the callback function.
|
||||
run_async (:obj:`bool`): Determines whether the callback will run asynchronously.
|
||||
|
||||
"""
|
||||
|
||||
def check_update(self, update: object) -> bool:
|
||||
"""Determines whether an update should be passed to this handlers :attr:`callback`.
|
||||
|
||||
Args:
|
||||
update (:class:`telegram.Update` | :obj:`object`): Incoming update.
|
||||
|
||||
Returns:
|
||||
:obj:`bool`
|
||||
|
||||
"""
|
||||
return isinstance(update, Update) and bool(update.poll_answer)
|
||||
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2019-2021
|
||||
# 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 the PollHandler classes."""
|
||||
|
||||
|
||||
from telegram import Update
|
||||
|
||||
from .handler import Handler
|
||||
|
||||
|
||||
class PollHandler(Handler[Update]):
|
||||
"""Handler class to handle Telegram updates that contain a poll.
|
||||
|
||||
Note:
|
||||
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you
|
||||
can use to keep any data in will be sent to the :attr:`callback` function. Related to
|
||||
either the user or the chat that the update was sent in. For each update from the same user
|
||||
or in the same chat, it will be the same ``dict``.
|
||||
|
||||
Note that this is DEPRECATED, and you should use context based callbacks. See
|
||||
https://git.io/fxJuV for more info.
|
||||
|
||||
Warning:
|
||||
When setting ``run_async`` to :obj:`True`, you cannot rely on adding custom
|
||||
attributes to :class:`telegram.ext.CallbackContext`. See its docs for more info.
|
||||
|
||||
Args:
|
||||
callback (:obj:`callable`): The callback function for this handler. Will be called when
|
||||
:attr:`check_update` has determined that an update should be processed by this handler.
|
||||
Callback signature for context based API:
|
||||
|
||||
``def callback(update: Update, context: CallbackContext)``
|
||||
|
||||
The return value of the callback is usually ignored except for the special case of
|
||||
:class:`telegram.ext.ConversationHandler`.
|
||||
pass_update_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``update_queue`` will be passed to the callback function. It will be the ``Queue``
|
||||
instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher`
|
||||
that contains new updates which can be used to insert updates. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_job_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``job_queue`` will be passed to the callback function. It will be a
|
||||
:class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater`
|
||||
which can be used to schedule new jobs. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_user_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``user_data`` will be passed to the callback function. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_chat_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``chat_data`` will be passed to the callback function. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
run_async (:obj:`bool`): Determines whether the callback will run asynchronously.
|
||||
Defaults to :obj:`False`.
|
||||
|
||||
Attributes:
|
||||
callback (:obj:`callable`): The callback function for this handler.
|
||||
pass_update_queue (:obj:`bool`): Determines whether ``update_queue`` will be
|
||||
passed to the callback function.
|
||||
pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to
|
||||
the callback function.
|
||||
pass_user_data (:obj:`bool`): Determines whether ``user_data`` will be passed to
|
||||
the callback function.
|
||||
pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to
|
||||
the callback function.
|
||||
run_async (:obj:`bool`): Determines whether the callback will run asynchronously.
|
||||
|
||||
"""
|
||||
|
||||
def check_update(self, update: object) -> bool:
|
||||
"""Determines whether an update should be passed to this handlers :attr:`callback`.
|
||||
|
||||
Args:
|
||||
update (:class:`telegram.Update` | :obj:`object`): Incoming update.
|
||||
|
||||
Returns:
|
||||
:obj:`bool`
|
||||
|
||||
"""
|
||||
return isinstance(update, Update) and bool(update.poll)
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2018
|
||||
# Copyright (C) 2015-2021
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -18,13 +18,56 @@
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains the PreCheckoutQueryHandler class."""
|
||||
|
||||
|
||||
from telegram import Update
|
||||
|
||||
from .handler import Handler
|
||||
|
||||
|
||||
class PreCheckoutQueryHandler(Handler):
|
||||
class PreCheckoutQueryHandler(Handler[Update]):
|
||||
"""Handler class to handle Telegram PreCheckout callback queries.
|
||||
|
||||
Note:
|
||||
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you
|
||||
can use to keep any data in will be sent to the :attr:`callback` function. Related to
|
||||
either the user or the chat that the update was sent in. For each update from the same user
|
||||
or in the same chat, it will be the same ``dict``.
|
||||
|
||||
Note that this is DEPRECATED, and you should use context based callbacks. See
|
||||
https://git.io/fxJuV for more info.
|
||||
|
||||
Warning:
|
||||
When setting ``run_async`` to :obj:`True`, you cannot rely on adding custom
|
||||
attributes to :class:`telegram.ext.CallbackContext`. See its docs for more info.
|
||||
|
||||
Args:
|
||||
callback (:obj:`callable`): The callback function for this handler. Will be called when
|
||||
:attr:`check_update` has determined that an update should be processed by this handler.
|
||||
Callback signature for context based API:
|
||||
|
||||
``def callback(update: Update, context: CallbackContext)``
|
||||
|
||||
The return value of the callback is usually ignored except for the special case of
|
||||
:class:`telegram.ext.ConversationHandler`.
|
||||
pass_update_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``update_queue`` will be passed to the callback function. It will be the ``Queue``
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher`
|
||||
that contains new updates which can be used to insert updates. Default is :obj:`False`.
|
||||
pass_job_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``job_queue`` will be passed to the callback function. It will be a
|
||||
:class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater`
|
||||
which can be used to schedule new jobs. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_user_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``user_data`` will be passed to the callback function. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_chat_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``chat_data`` will be passed to the callback function. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
run_async (:obj:`bool`): Determines whether the callback will run asynchronously.
|
||||
Defaults to :obj:`False`.
|
||||
|
||||
Attributes:
|
||||
callback (:obj:`callable`): The callback function for this handler.
|
||||
pass_update_queue (:obj:`bool`): Determines whether ``update_queue`` will be
|
||||
@@ -35,52 +78,18 @@ class PreCheckoutQueryHandler(Handler):
|
||||
the callback function.
|
||||
pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to
|
||||
the callback function.
|
||||
|
||||
Note:
|
||||
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you
|
||||
can use to keep any data in will be sent to the :attr:`callback` function. Related to
|
||||
either the user or the chat that the update was sent in. For each update from the same user
|
||||
or in the same chat, it will be the same ``dict``.
|
||||
|
||||
Note that this is DEPRECATED, and you should use context based callbacks. See
|
||||
https://git.io/fxJuV for more info.
|
||||
|
||||
Args:
|
||||
callback (:obj:`callable`): The callback function for this handler. Will be called when
|
||||
:attr:`check_update` has determined that an update should be processed by this handler.
|
||||
Callback signature for context based API:
|
||||
|
||||
``def callback(update: Update, context: CallbackContext)``
|
||||
|
||||
The return value of the callback is usually ignored except for the special case of
|
||||
:class:`telegram.ext.ConversationHandler`.
|
||||
pass_update_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called
|
||||
``update_queue`` will be passed to the callback function. It will be the ``Queue``
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher`
|
||||
that contains new updates which can be used to insert updates. Default is ``False``.
|
||||
pass_job_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called
|
||||
``job_queue`` will be passed to the callback function. It will be a
|
||||
:class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater`
|
||||
which can be used to schedule new jobs. Default is ``False``.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_user_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called
|
||||
``user_data`` will be passed to the callback function. Default is ``False``.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_chat_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called
|
||||
``chat_data`` will be passed to the callback function. Default is ``False``.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
run_async (:obj:`bool`): Determines whether the callback will run asynchronously.
|
||||
|
||||
"""
|
||||
|
||||
def check_update(self, update):
|
||||
def check_update(self, update: object) -> bool:
|
||||
"""Determines whether an update should be passed to this handlers :attr:`callback`.
|
||||
|
||||
Args:
|
||||
update (:class:`telegram.Update`): Incoming telegram update.
|
||||
update (:class:`telegram.Update` | :obj:`object`): Incoming update.
|
||||
|
||||
Returns:
|
||||
:obj:`bool`
|
||||
|
||||
"""
|
||||
return isinstance(update, Update) and update.pre_checkout_query
|
||||
return isinstance(update, Update) and bool(update.pre_checkout_query)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user