mirror of
https://github.com/python-telegram-bot/python-telegram-bot.git
synced 2026-06-19 15:45:13 +00:00
Compare commits
139 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 |
@@ -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
|
||||
#####################################
|
||||
@@ -111,6 +113,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
|
||||
@@ -235,9 +245,9 @@ 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
|
||||
|
||||
@@ -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 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,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,119 @@
|
||||
name: GitHub Actions
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
schedule:
|
||||
- cron: 7 3 * * *
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
pytest:
|
||||
name: pytest
|
||||
runs-on: ${{matrix.os}}
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [3.5, 3.6, 3.7, 3.8]
|
||||
os: [ubuntu-latest, windows-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@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 -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
|
||||
run: |
|
||||
if [ "$CODECOV_TOKEN" != "" ]; then
|
||||
codecov -F github -t $CODECOV_TOKEN --name "${{ matrix.os }}-${{ matrix.python-version }}"
|
||||
fi
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
shell: bash
|
||||
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
|
||||
@@ -1,17 +1,17 @@
|
||||
repos:
|
||||
- repo: git://github.com/python-telegram-bot/mirrors-yapf
|
||||
sha: 5769e088ef6e0a0d1eb63bd6d0c1fe9f3606d6c8
|
||||
rev: 5769e088ef6e0a0d1eb63bd6d0c1fe9f3606d6c8
|
||||
hooks:
|
||||
- id: yapf
|
||||
files: ^(telegram|tests)/.*\.py$
|
||||
args:
|
||||
- --diff
|
||||
- repo: git://github.com/pre-commit/pre-commit-hooks
|
||||
sha: 0b70e285e369bcb24b57b74929490ea7be9c4b19
|
||||
- repo: https://gitlab.com/pycqa/flake8
|
||||
rev: 3.7.1
|
||||
hooks:
|
||||
- id: flake8
|
||||
- repo: git://github.com/pre-commit/mirrors-pylint
|
||||
sha: 9d8dcbc2b86c796275680f239c1e90dcd50bd398
|
||||
rev: 9d8dcbc2b86c796275680f239c1e90dcd50bd398
|
||||
hooks:
|
||||
- id: pylint
|
||||
files: ^telegram/.*\.py$
|
||||
|
||||
-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
|
||||
@@ -18,6 +18,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 +27,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>`_
|
||||
@@ -35,6 +38,8 @@ The following wonderful people contributed directly or indirectly to this projec
|
||||
- `Evan Haberecht <https://github.com/habereet>`_
|
||||
- `evgfilim1 <https://github.com/evgfilim1>`_
|
||||
- `franciscod <https://github.com/franciscod>`_
|
||||
- `gamgi <https://github.com/gamgi>`_
|
||||
- `Harshil <https://github.com/harshil21>`_
|
||||
- `Hugo Damer <https://github.com/HakimusGIT>`_
|
||||
- `ihoru <https://github.com/ihoru>`_
|
||||
- `Jasmin Bom <https://github.com/jsmnbom>`_
|
||||
@@ -67,6 +72,8 @@ The following wonderful people contributed directly or indirectly to this projec
|
||||
- `Pieter Schutz <https://github.com/eldinnie>`_
|
||||
- `Poolitzer <https://github.com/Poolitzer>`_
|
||||
- `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>`_
|
||||
|
||||
+312
@@ -2,8 +2,320 @@
|
||||
Changelog
|
||||
=========
|
||||
|
||||
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`_)
|
||||
|
||||
**New Features:**
|
||||
|
||||
- New method ``run_mothly`` 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:**
|
||||
|
||||
- Added ``tzinfo`` argument to ``helpers.from_timestamp`` (`#1621`_)
|
||||
- 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:**
|
||||
|
||||
|
||||
+12
-16
@@ -29,14 +29,9 @@ We have a vibrant community of developers helping each other in our `Telegram gr
|
||||
: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?event=schedule
|
||||
: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
|
||||
@@ -88,7 +83,7 @@ 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.5+ and `PyPy <http://pypy.org/>`_.
|
||||
|
||||
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
|
||||
@@ -98,7 +93,7 @@ make the development of bots easy and straightforward. These classes are contain
|
||||
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 **4.8** are supported.
|
||||
|
||||
==========
|
||||
Installing
|
||||
@@ -142,9 +137,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 ``echobot2``, it is the de facto base for most of the bots out there. Best of all,
|
||||
the code for these examples are released to the public domain, so you can start by grabbing the
|
||||
code and building on top of it.
|
||||
|
||||
@@ -192,11 +187,12 @@ 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. Report bugs, request new features or ask questions by `creating an issue <https://github.com/python-telegram-bot/python-telegram-bot/issues/new/choose>`_.
|
||||
|
||||
3. You can ask for help on Stack Overflow using the `python-telegram-bot tag <https://stackoverflow.com/questions/tagged/python-telegram-bot>`_.
|
||||
3. Our `Wiki pages <https://github.com/python-telegram-bot/python-telegram-bot/wiki/>`_ offer a growing amount of resources.
|
||||
|
||||
4. You can even ask for help on Stack Overflow using the `python-telegram-bot tag <https://stackoverflow.com/questions/tagged/python-telegram-bot>`_.
|
||||
|
||||
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>`_.
|
||||
|
||||
|
||||
============
|
||||
|
||||
@@ -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"
|
||||
@@ -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-2020, 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 = '12.8' # telegram.__version__[:3]
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '12.2.0' # telegram.__version__
|
||||
release = '12.8' # 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.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,14 @@ telegram.ext package
|
||||
|
||||
telegram.ext.updater
|
||||
telegram.ext.dispatcher
|
||||
telegram.ext.dispatcherhandlerstop
|
||||
telegram.ext.filters
|
||||
telegram.ext.job
|
||||
telegram.ext.jobqueue
|
||||
telegram.ext.messagequeue
|
||||
telegram.ext.delayqueue
|
||||
telegram.ext.callbackcontext
|
||||
telegram.ext.defaults
|
||||
|
||||
Handlers
|
||||
--------
|
||||
@@ -24,6 +26,8 @@ Handlers
|
||||
telegram.ext.commandhandler
|
||||
telegram.ext.inlinequeryhandler
|
||||
telegram.ext.messagehandler
|
||||
telegram.ext.pollanswerhandler
|
||||
telegram.ext.pollhandler
|
||||
telegram.ext.precheckoutqueryhandler
|
||||
telegram.ext.prefixhandler
|
||||
telegram.ext.regexhandler
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
telegram.KeyboardButtonPollType
|
||||
===============================
|
||||
|
||||
.. autoclass:: telegram.KeyboardButtonPollType
|
||||
:members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,6 @@
|
||||
telegram.PollAnswer
|
||||
===================
|
||||
|
||||
.. autoclass:: telegram.PollAnswer
|
||||
:members:
|
||||
:show-inheritance:
|
||||
@@ -9,6 +9,7 @@ telegram package
|
||||
telegram.animation
|
||||
telegram.audio
|
||||
telegram.bot
|
||||
telegram.botcommand
|
||||
telegram.callbackquery
|
||||
telegram.chat
|
||||
telegram.chataction
|
||||
@@ -17,6 +18,7 @@ telegram package
|
||||
telegram.chatphoto
|
||||
telegram.constants
|
||||
telegram.contact
|
||||
telegram.dice
|
||||
telegram.document
|
||||
telegram.error
|
||||
telegram.file
|
||||
@@ -31,6 +33,7 @@ telegram package
|
||||
telegram.inputmediaphoto
|
||||
telegram.inputmediavideo
|
||||
telegram.keyboardbutton
|
||||
telegram.keyboardbuttonpolltype
|
||||
telegram.location
|
||||
telegram.loginurl
|
||||
telegram.message
|
||||
@@ -38,6 +41,7 @@ telegram package
|
||||
telegram.parsemode
|
||||
telegram.photosize
|
||||
telegram.poll
|
||||
telegram.pollanswer
|
||||
telegram.polloption
|
||||
telegram.replykeyboardremove
|
||||
telegram.replykeyboardmarkup
|
||||
|
||||
+16
-4
@@ -1,6 +1,6 @@
|
||||
# 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 [`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.
|
||||
|
||||
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.
|
||||
|
||||
@@ -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 costum 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 [`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.
|
||||
|
||||
@@ -108,11 +108,6 @@ def cancel(update, context):
|
||||
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.
|
||||
# Make sure to set use_context=True to use the new context based callbacks
|
||||
@@ -143,9 +138,6 @@ def main():
|
||||
|
||||
dp.add_handler(conv_handler)
|
||||
|
||||
# log all errors
|
||||
dp.add_error_handler(error)
|
||||
|
||||
# Start the Bot
|
||||
updater.start_polling()
|
||||
|
||||
|
||||
@@ -96,11 +96,6 @@ def done(update, context):
|
||||
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.
|
||||
# Make sure to set use_context=True to use the new context based callbacks
|
||||
@@ -135,9 +130,6 @@ def main():
|
||||
|
||||
dp.add_handler(conv_handler)
|
||||
|
||||
# log all errors
|
||||
dp.add_error_handler(error)
|
||||
|
||||
# Start the Bot
|
||||
updater.start_polling()
|
||||
|
||||
|
||||
+2
-10
@@ -61,7 +61,7 @@ def deep_linked_level_2(update, context):
|
||||
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)
|
||||
"[▶️ CLICK HERE]({}).".format(url)
|
||||
update.message.reply_text(text, parse_mode=ParseMode.MARKDOWN, disable_web_page_preview=True)
|
||||
|
||||
|
||||
@@ -69,12 +69,7 @@ def deep_linked_level_3(update, context):
|
||||
"""Reached through the USING_ENTITIES payload"""
|
||||
payload = context.args
|
||||
update.message.reply_text("Congratulations! This is as deep as it gets 👏🏻\n\n"
|
||||
"The payload was: {0}".format(payload))
|
||||
|
||||
|
||||
def error(update, context):
|
||||
"""Log Errors caused by Updates."""
|
||||
logger.warning('Update "%s" caused error "%s"', update, context.error)
|
||||
"The payload was: {}".format(payload))
|
||||
|
||||
|
||||
def main():
|
||||
@@ -103,9 +98,6 @@ def main():
|
||||
# Make sure the deep-linking handlers occur *before* the normal /start handler.
|
||||
dp.add_handler(CommandHandler("start", start))
|
||||
|
||||
# log all errors
|
||||
dp.add_error_handler(error)
|
||||
|
||||
# Start the Bot
|
||||
updater.start_polling()
|
||||
|
||||
|
||||
+2
-10
@@ -33,7 +33,7 @@ def start(update, context):
|
||||
update.message.reply_text('Hi!')
|
||||
|
||||
|
||||
def help(update, context):
|
||||
def help_command(update, context):
|
||||
"""Send a message when the command /help is issued."""
|
||||
update.message.reply_text('Help!')
|
||||
|
||||
@@ -43,11 +43,6 @@ def echo(update, context):
|
||||
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.
|
||||
@@ -60,14 +55,11 @@ def main():
|
||||
|
||||
# on different commands - answer in Telegram
|
||||
dp.add_handler(CommandHandler("start", start))
|
||||
dp.add_handler(CommandHandler("help", help))
|
||||
dp.add_handler(CommandHandler("help", help_command))
|
||||
|
||||
# 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()
|
||||
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# 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):
|
||||
"""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 = ''.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 = (
|
||||
'An exception was raised while handling an update\n'
|
||||
'<pre>update = {}</pre>\n\n'
|
||||
'<pre>context.chat_data = {}</pre>\n\n'
|
||||
'<pre>context.user_data = {}</pre>\n\n'
|
||||
'<pre>{}</pre>'
|
||||
).format(
|
||||
html.escape(json.dumps(update.to_dict(), indent=2, ensure_ascii=False)),
|
||||
html.escape(str(context.chat_data)),
|
||||
html.escape(str(context.user_data)),
|
||||
html.escape(tb)
|
||||
)
|
||||
|
||||
# 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):
|
||||
"""Raise an error to trigger the error handler."""
|
||||
context.bot.wrong_method_name()
|
||||
|
||||
|
||||
def start(update: Update, context: CallbackContext):
|
||||
update.effective_message.reply_html('Use /bad_command to cause an error.\n'
|
||||
'Your chat id is <code>{}</code>.'
|
||||
.format(update.effective_chat.id))
|
||||
|
||||
|
||||
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(BOT_TOKEN, use_context=True)
|
||||
|
||||
# Get the dispatcher to register handlers
|
||||
dp = updater.dispatcher
|
||||
|
||||
# Register the commands...
|
||||
dp.add_handler(CommandHandler('start', start))
|
||||
dp.add_handler(CommandHandler('bad_command', bad_command))
|
||||
|
||||
# ...and the error handler
|
||||
dp.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()
|
||||
+2
-10
@@ -34,7 +34,7 @@ def start(update, context):
|
||||
update.message.reply_text('Hi!')
|
||||
|
||||
|
||||
def help(update, context):
|
||||
def help_command(update, context):
|
||||
"""Send a message when the command /help is issued."""
|
||||
update.message.reply_text('Help!')
|
||||
|
||||
@@ -64,11 +64,6 @@ def inlinequery(update, context):
|
||||
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():
|
||||
# Create the Updater and pass it your bot's token.
|
||||
# Make sure to set use_context=True to use the new context based callbacks
|
||||
@@ -80,14 +75,11 @@ def main():
|
||||
|
||||
# on different commands - answer in Telegram
|
||||
dp.add_handler(CommandHandler("start", start))
|
||||
dp.add_handler(CommandHandler("help", help))
|
||||
dp.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)
|
||||
|
||||
# Start the Bot
|
||||
updater.start_polling()
|
||||
|
||||
|
||||
@@ -29,18 +29,17 @@ def start(update, context):
|
||||
def button(update, context):
|
||||
query = update.callback_query
|
||||
|
||||
# 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="Selected option: {}".format(query.data))
|
||||
|
||||
|
||||
def help(update, context):
|
||||
def help_command(update, context):
|
||||
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
|
||||
@@ -49,8 +48,7 @@ def main():
|
||||
|
||||
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()
|
||||
|
||||
+14
-33
@@ -55,8 +55,9 @@ def start_over(update, context):
|
||||
"""Prompt same text & keyboard as `start` does but not as new message"""
|
||||
# Get CallbackQuery from Update
|
||||
query = update.callback_query
|
||||
# Get Bot from CallbackContext
|
||||
bot = context.bot
|
||||
# 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))]
|
||||
@@ -65,9 +66,7 @@ def start_over(update, context):
|
||||
# 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,
|
||||
query.edit_message_text(
|
||||
text="Start handler, Choose a route",
|
||||
reply_markup=reply_markup
|
||||
)
|
||||
@@ -77,15 +76,13 @@ def start_over(update, context):
|
||||
def one(update, context):
|
||||
"""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))]
|
||||
]
|
||||
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||
bot.edit_message_text(
|
||||
chat_id=query.message.chat_id,
|
||||
message_id=query.message.message_id,
|
||||
query.edit_message_text(
|
||||
text="First CallbackQueryHandler, Choose a route",
|
||||
reply_markup=reply_markup
|
||||
)
|
||||
@@ -95,15 +92,13 @@ def one(update, context):
|
||||
def two(update, context):
|
||||
"""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))]
|
||||
]
|
||||
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||
bot.edit_message_text(
|
||||
chat_id=query.message.chat_id,
|
||||
message_id=query.message.message_id,
|
||||
query.edit_message_text(
|
||||
text="Second CallbackQueryHandler, Choose a route",
|
||||
reply_markup=reply_markup
|
||||
)
|
||||
@@ -113,15 +108,13 @@ def two(update, context):
|
||||
def three(update, context):
|
||||
"""Show new choice of buttons"""
|
||||
query = update.callback_query
|
||||
bot = context.bot
|
||||
query.answer()
|
||||
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,
|
||||
query.edit_message_text(
|
||||
text="Third CallbackQueryHandler. Do want to start over?",
|
||||
reply_markup=reply_markup
|
||||
)
|
||||
@@ -132,15 +125,13 @@ def three(update, context):
|
||||
def four(update, context):
|
||||
"""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))]
|
||||
]
|
||||
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||
bot.edit_message_text(
|
||||
chat_id=query.message.chat_id,
|
||||
message_id=query.message.message_id,
|
||||
query.edit_message_text(
|
||||
text="Fourth CallbackQueryHandler, Choose a route",
|
||||
reply_markup=reply_markup
|
||||
)
|
||||
@@ -151,20 +142,13 @@ def end(update, context):
|
||||
"""Returns `ConversationHandler.END`, which tells the
|
||||
ConversationHandler that the conversation is over"""
|
||||
query = update.callback_query
|
||||
bot = context.bot
|
||||
bot.edit_message_text(
|
||||
chat_id=query.message.chat_id,
|
||||
message_id=query.message.message_id,
|
||||
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)
|
||||
@@ -195,9 +179,6 @@ def main():
|
||||
# updates
|
||||
dp.add_handler(conv_handler)
|
||||
|
||||
# log all errors
|
||||
dp.add_error_handler(error)
|
||||
|
||||
# Start the Bot
|
||||
updater.start_polling()
|
||||
|
||||
|
||||
@@ -66,6 +66,7 @@ def start(update, context):
|
||||
|
||||
# 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'
|
||||
@@ -83,6 +84,7 @@ def adding_self(update, context):
|
||||
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
|
||||
@@ -98,14 +100,14 @@ 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 += '\nName: {}, Age: {}'.format(person.get(NAME, '-'), person.get(AGE, '-'))
|
||||
else:
|
||||
male, female = _name_switcher(level)
|
||||
|
||||
for person in user_data[level]:
|
||||
gender = female if person[GENDER] == FEMALE else male
|
||||
text += '\n{0}: Name: {1}, Age: {2}'.format(gender, person.get(NAME, '-'),
|
||||
person.get(AGE, '-'))
|
||||
text += '\n{}: Name: {}, Age: {}'.format(gender, person.get(NAME, '-'),
|
||||
person.get(AGE, '-'))
|
||||
return text
|
||||
|
||||
ud = context.user_data
|
||||
@@ -118,6 +120,7 @@ def show_data(update, context):
|
||||
]]
|
||||
keyboard = InlineKeyboardMarkup(buttons)
|
||||
|
||||
update.callback_query.answer()
|
||||
update.callback_query.edit_message_text(text=text, reply_markup=keyboard)
|
||||
ud[START_OVER] = True
|
||||
|
||||
@@ -133,6 +136,8 @@ def stop(update, context):
|
||||
|
||||
def end(update, context):
|
||||
"""End conversation from InlineKeyboardButton."""
|
||||
update.callback_query.answer()
|
||||
|
||||
text = 'See you around!'
|
||||
update.callback_query.edit_message_text(text=text)
|
||||
|
||||
@@ -151,6 +156,8 @@ def select_level(update, context):
|
||||
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
|
||||
@@ -172,8 +179,9 @@ def select_gender(update, context):
|
||||
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
|
||||
@@ -201,6 +209,8 @@ def select_feature(update, context):
|
||||
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:
|
||||
@@ -215,6 +225,8 @@ def ask_for_input(update, context):
|
||||
"""Prompt user to input data for selected feature."""
|
||||
context.user_data[CURRENT_FEATURE] = update.callback_query.data
|
||||
text = 'Okay, tell me.'
|
||||
|
||||
update.callback_query.answer()
|
||||
update.callback_query.edit_message_text(text=text)
|
||||
|
||||
return TYPING
|
||||
@@ -255,12 +267,6 @@ def stop_nested(update, context):
|
||||
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
|
||||
@@ -301,8 +307,8 @@ def main():
|
||||
|
||||
states={
|
||||
SELECTING_LEVEL: [CallbackQueryHandler(select_gender,
|
||||
pattern='^{0}$|^{1}$'.format(str(PARENTS),
|
||||
str(CHILDREN)))],
|
||||
pattern='^{}$|^{}$'.format(str(PARENTS),
|
||||
str(CHILDREN)))],
|
||||
SELECTING_GENDER: [description_conv]
|
||||
},
|
||||
|
||||
@@ -323,32 +329,30 @@ def main():
|
||||
)
|
||||
|
||||
# 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)
|
||||
|
||||
# Start the Bot
|
||||
updater.start_polling()
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def msg(bot, update):
|
||||
def msg(update, context):
|
||||
# If we received any passport data
|
||||
passport_data = update.message.passport_data
|
||||
if passport_data:
|
||||
@@ -77,11 +77,6 @@ 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
|
||||
@@ -93,9 +88,6 @@ def main():
|
||||
# On messages that include passport data call msg
|
||||
dp.add_handler(MessageHandler(Filters.passport_data, msg))
|
||||
|
||||
# log all errors
|
||||
dp.add_error_handler(error)
|
||||
|
||||
# Start the Bot
|
||||
updater.start_polling()
|
||||
|
||||
|
||||
+2
-10
@@ -19,11 +19,6 @@ logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s
|
||||
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):
|
||||
msg = "Use /shipping to get an invoice for shipping-payment, "
|
||||
msg += "or /noshipping for an invoice without shipping."
|
||||
@@ -42,7 +37,7 @@ 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)]
|
||||
|
||||
@@ -66,7 +61,7 @@ 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,
|
||||
@@ -134,9 +129,6 @@ def main():
|
||||
# Success! Notify your user!
|
||||
dp.add_handler(MessageHandler(Filters.successful_payment, successful_payment_callback))
|
||||
|
||||
# log all errors
|
||||
dp.add_error_handler(error)
|
||||
|
||||
# Start the Bot
|
||||
updater.start_polling()
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ def start(update, context):
|
||||
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 " \
|
||||
"about yourself? Or change anything I " \
|
||||
"already know.".format(", ".join(context.user_data.keys()))
|
||||
else:
|
||||
reply_text += " I will hold a more complex conversation with you. Why don't you tell me " \
|
||||
@@ -107,11 +107,6 @@ def done(update, context):
|
||||
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')
|
||||
@@ -149,8 +144,6 @@ def main():
|
||||
|
||||
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,147 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# 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)
|
||||
from telegram.ext import (Updater, CommandHandler, PollAnswerHandler, PollHandler, MessageHandler,
|
||||
Filters)
|
||||
from telegram.utils.helpers import mention_html
|
||||
|
||||
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def start(update, context):
|
||||
"""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, context):
|
||||
"""Sends a predefined poll"""
|
||||
questions = ["Good", "Really good", "Fantastic", "Great"]
|
||||
message = context.bot.send_poll(update.effective_user.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, context):
|
||||
"""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]
|
||||
user_mention = mention_html(update.effective_user.id, update.effective_user.full_name)
|
||||
context.bot.send_message(context.bot_data[poll_id]["chat_id"],
|
||||
"{} feels {}!".format(user_mention, 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, context):
|
||||
"""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, context):
|
||||
"""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, context):
|
||||
"""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, context):
|
||||
"""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, context):
|
||||
"""Display a help message"""
|
||||
update.message.reply_text("Use /quiz, /poll or /preview to test this "
|
||||
"bot.")
|
||||
|
||||
|
||||
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)
|
||||
dp = updater.dispatcher
|
||||
dp.add_handler(CommandHandler('start', start))
|
||||
dp.add_handler(CommandHandler('poll', poll))
|
||||
dp.add_handler(PollAnswerHandler(receive_poll_answer))
|
||||
dp.add_handler(CommandHandler('quiz', quiz))
|
||||
dp.add_handler(PollHandler(receive_quiz_answer))
|
||||
dp.add_handler(CommandHandler('preview', preview))
|
||||
dp.add_handler(MessageHandler(Filters.poll, receive_poll))
|
||||
dp.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()
|
||||
@@ -77,11 +77,6 @@ def unset(update, context):
|
||||
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)
|
||||
|
||||
|
||||
def main():
|
||||
"""Run bot."""
|
||||
# Create the Updater and pass it your bot's token.
|
||||
@@ -101,9 +96,6 @@ def main():
|
||||
pass_chat_data=True))
|
||||
dp.add_handler(CommandHandler("unset", unset, pass_chat_data=True))
|
||||
|
||||
# log all errors
|
||||
dp.add_error_handler(error)
|
||||
|
||||
# Start the Bot
|
||||
updater.start_polling()
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
future>=0.16.0
|
||||
certifi
|
||||
tornado>=5.1
|
||||
cryptography
|
||||
decorator>=4.4.0
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
import codecs
|
||||
import os
|
||||
import sys
|
||||
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
|
||||
@@ -18,6 +20,14 @@ def requirements():
|
||||
|
||||
|
||||
packages = find_packages(exclude=['tests*'])
|
||||
requirements = requirements()
|
||||
|
||||
# Allow for a package install to not use the vendored urllib3
|
||||
UPSTREAM_URLLIB3_FLAG = '--with-upstream-urllib3'
|
||||
if UPSTREAM_URLLIB3_FLAG in sys.argv:
|
||||
sys.argv.remove(UPSTREAM_URLLIB3_FLAG)
|
||||
requirements.append('urllib3 >= 1.19.1')
|
||||
packages = [x for x in packages if not x.startswith('telegram.vendor.ptb_urllib3')]
|
||||
|
||||
with codecs.open('README.rst', 'r', 'utf-8') as fd:
|
||||
fn = os.path.join('telegram', 'version.py')
|
||||
@@ -35,7 +45,7 @@ with codecs.open('README.rst', 'r', 'utf-8') as fd:
|
||||
description="We have made you a wrapper you can't refuse",
|
||||
long_description=fd.read(),
|
||||
packages=packages,
|
||||
install_requires=requirements(),
|
||||
install_requires=requirements,
|
||||
extras_require={
|
||||
'json': 'ujson',
|
||||
'socks': 'PySocks'
|
||||
@@ -50,11 +60,9 @@ with codecs.open('README.rst', 'r', 'utf-8') as fd:
|
||||
'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'
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
],)
|
||||
|
||||
@@ -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-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -19,6 +19,7 @@
|
||||
"""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
|
||||
@@ -36,8 +37,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
|
||||
@@ -48,7 +51,7 @@ from .files.file import File
|
||||
from .parsemode import ParseMode
|
||||
from .messageentity import MessageEntity
|
||||
from .games.game import Game
|
||||
from .poll import Poll, PollOption
|
||||
from .poll import Poll, PollOption, PollAnswer
|
||||
from .loginurl import LoginUrl
|
||||
from .games.callbackgame import CallbackGame
|
||||
from .payment.shippingaddress import ShippingAddress
|
||||
@@ -138,7 +141,7 @@ __all__ = [
|
||||
'InlineQueryResultPhoto', 'InlineQueryResultVenue', 'InlineQueryResultVideo',
|
||||
'InlineQueryResultVoice', 'InlineQueryResultGame', 'InputContactMessageContent', 'InputFile',
|
||||
'InputLocationMessageContent', 'InputMessageContent', 'InputTextMessageContent',
|
||||
'InputVenueMessageContent', 'KeyboardButton', 'Location', 'EncryptedCredentials',
|
||||
'InputVenueMessageContent', 'Location', 'EncryptedCredentials',
|
||||
'PassportFile', 'EncryptedPassportElement', 'PassportData', 'Message', 'MessageEntity',
|
||||
'ParseMode', 'PhotoSize', 'ReplyKeyboardRemove', 'ReplyKeyboardMarkup', 'ReplyMarkup',
|
||||
'Sticker', 'TelegramError', 'TelegramObject', 'Update', 'User', 'UserProfilePhotos', 'Venue',
|
||||
@@ -156,5 +159,6 @@ __all__ = [
|
||||
'InputMediaAudio', 'InputMediaDocument', 'TelegramDecryptionError',
|
||||
'PassportElementErrorSelfie', 'PassportElementErrorTranslationFile',
|
||||
'PassportElementErrorTranslationFiles', 'PassportElementErrorUnspecified', 'Poll',
|
||||
'PollOption', 'LoginUrl'
|
||||
'PollOption', 'PollAnswer', 'LoginUrl', 'KeyboardButton', 'KeyboardButtonPollType', 'Dice',
|
||||
'BotCommand'
|
||||
]
|
||||
|
||||
@@ -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-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -20,7 +20,6 @@ import sys
|
||||
import subprocess
|
||||
|
||||
import certifi
|
||||
import future
|
||||
|
||||
|
||||
from . import __version__ as telegram_ver
|
||||
@@ -37,11 +36,10 @@ def _git_revision():
|
||||
|
||||
def print_ver_info():
|
||||
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('python-telegram-bot {}'.format(telegram_ver) + (' ({})'.format(git_revision)
|
||||
if git_revision else ''))
|
||||
print('certifi {}'.format(certifi.__version__))
|
||||
print('Python {}'.format(sys.version.replace('\n', ' ')))
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
+5
-13
@@ -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-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -23,13 +23,10 @@ try:
|
||||
except ImportError:
|
||||
import json
|
||||
|
||||
from abc import ABCMeta
|
||||
|
||||
|
||||
class TelegramObject(object):
|
||||
class TelegramObject:
|
||||
"""Base class for most telegram objects."""
|
||||
|
||||
__metaclass__ = ABCMeta
|
||||
_id_attrs = ()
|
||||
|
||||
def __str__(self):
|
||||
@@ -60,12 +57,7 @@ class TelegramObject(object):
|
||||
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]
|
||||
@@ -82,9 +74,9 @@ class TelegramObject(object):
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, self.__class__):
|
||||
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):
|
||||
if self._id_attrs:
|
||||
return hash((self.__class__, self._id_attrs)) # pylint: disable=no-member
|
||||
return super(TelegramObject, self).__hash__()
|
||||
return super().__hash__()
|
||||
|
||||
+762
-302
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env python
|
||||
# pylint: disable=R0903
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2020
|
||||
# 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 telegram import TelegramObject
|
||||
|
||||
|
||||
class BotCommand(TelegramObject):
|
||||
"""
|
||||
This object represents a bot command.
|
||||
|
||||
Attributes:
|
||||
command (:obj:`str`): Text of the command.
|
||||
description (:obj:`str`): Description of the command.
|
||||
|
||||
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.
|
||||
"""
|
||||
def __init__(self, command, description, **kwargs):
|
||||
self.command = command
|
||||
self.description = description
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data, bot):
|
||||
if not data:
|
||||
return None
|
||||
|
||||
return cls(**data)
|
||||
+15
-11
@@ -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-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -36,30 +36,31 @@ class CallbackQuery(TelegramObject):
|
||||
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.
|
||||
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.
|
||||
bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods.
|
||||
|
||||
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
|
||||
@@ -98,10 +99,13 @@ class CallbackQuery(TelegramObject):
|
||||
if not data:
|
||||
return None
|
||||
|
||||
data = super(CallbackQuery, cls).de_json(data, bot)
|
||||
data = super().de_json(data, bot)
|
||||
|
||||
data['from_user'] = User.de_json(data.get('from'), bot)
|
||||
data['message'] = Message.de_json(data.get('message'), bot)
|
||||
message = data.get('message')
|
||||
if message:
|
||||
message['default_quote'] = data.get('default_quote')
|
||||
data['message'] = Message.de_json(message, bot)
|
||||
|
||||
return cls(bot=bot, **data)
|
||||
|
||||
|
||||
+22
-2
@@ -2,7 +2,7 @@
|
||||
# pylint: disable=C0103,W0622
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2018
|
||||
# Copyright (C) 2015-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -40,6 +40,8 @@ class Chat(TelegramObject):
|
||||
Returned only in get_chat.
|
||||
permissions (:class:`telegram.ChatPermission`): Optional. Default chat member permissions,
|
||||
for groups and supergroups. Returned only in getChat.
|
||||
slow_mode_delay (:obj:`int`): Optional. For supergroups, the minimum allowed delay between
|
||||
consecutive messages sent by each unpriviledged user. Returned only in getChat.
|
||||
sticker_set_name (:obj:`str`): Optional. For supergroups, name of Group sticker set.
|
||||
can_set_sticker_set (:obj:`bool`): Optional. ``True``, if the bot can change group the
|
||||
sticker set.
|
||||
@@ -65,6 +67,8 @@ class Chat(TelegramObject):
|
||||
Returned only in get_chat.
|
||||
permissions (:class:`telegram.ChatPermission`): Optional. Default chat member permissions,
|
||||
for groups and supergroups. Returned only in getChat.
|
||||
slow_mode_delay (:obj:`int`, optional): For supergroups, the minimum allowed delay between
|
||||
consecutive messages sent by each unpriviledged user. Returned only in getChat.
|
||||
bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods.
|
||||
sticker_set_name (:obj:`str`, optional): For supergroups, name of Group sticker set.
|
||||
Returned only in get_chat.
|
||||
@@ -98,6 +102,7 @@ class Chat(TelegramObject):
|
||||
permissions=None,
|
||||
sticker_set_name=None,
|
||||
can_set_sticker_set=None,
|
||||
slow_mode_delay=None,
|
||||
**kwargs):
|
||||
# Required
|
||||
self.id = int(id)
|
||||
@@ -114,6 +119,7 @@ class Chat(TelegramObject):
|
||||
self.invite_link = invite_link
|
||||
self.pinned_message = pinned_message
|
||||
self.permissions = permissions
|
||||
self.slow_mode_delay = slow_mode_delay
|
||||
self.sticker_set_name = sticker_set_name
|
||||
self.can_set_sticker_set = can_set_sticker_set
|
||||
|
||||
@@ -135,7 +141,10 @@ class Chat(TelegramObject):
|
||||
|
||||
data['photo'] = ChatPhoto.de_json(data.get('photo'), bot)
|
||||
from telegram import Message
|
||||
data['pinned_message'] = Message.de_json(data.get('pinned_message'), bot)
|
||||
pinned_message = data.get('pinned_message')
|
||||
if pinned_message:
|
||||
pinned_message['default_quote'] = data.get('default_quote')
|
||||
data['pinned_message'] = Message.de_json(pinned_message, bot)
|
||||
data['permissions'] = ChatPermissions.de_json(data.get('permissions'), bot)
|
||||
|
||||
return cls(bot=bot, **data)
|
||||
@@ -237,6 +246,17 @@ class Chat(TelegramObject):
|
||||
"""
|
||||
return self.bot.set_chat_permissions(self.id, *args, **kwargs)
|
||||
|
||||
def set_administrator_custom_title(self, *args, **kwargs):
|
||||
"""Shortcut for::
|
||||
|
||||
bot.set_chat_administrator_custom_title(update.message.chat.id, *args, **kwargs)
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: If the action was sent successfully.
|
||||
|
||||
"""
|
||||
return self.bot.set_chat_administrator_custom_title(self.id, *args, **kwargs)
|
||||
|
||||
def send_message(self, *args, **kwargs):
|
||||
"""Shortcut for::
|
||||
|
||||
|
||||
@@ -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-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -20,7 +20,7 @@
|
||||
"""This module contains an object that represents a Telegram ChatAction."""
|
||||
|
||||
|
||||
class ChatAction(object):
|
||||
class ChatAction:
|
||||
"""Helper class to provide constants for different chatactions."""
|
||||
|
||||
FIND_LOCATION = 'find_location'
|
||||
|
||||
@@ -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-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -28,6 +28,7 @@ class ChatMember(TelegramObject):
|
||||
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.
|
||||
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
|
||||
@@ -62,6 +63,8 @@ class ChatMember(TelegramObject):
|
||||
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.
|
||||
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
|
||||
@@ -118,10 +121,11 @@ class ChatMember(TelegramObject):
|
||||
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):
|
||||
can_add_web_page_previews=None, is_member=None, custom_title=None, **kwargs):
|
||||
# Required
|
||||
self.user = user
|
||||
self.status = status
|
||||
self.custom_title = custom_title
|
||||
self.until_date = until_date
|
||||
self.can_be_edited = can_be_edited
|
||||
self.can_change_info = can_change_info
|
||||
@@ -146,7 +150,7 @@ class ChatMember(TelegramObject):
|
||||
if not data:
|
||||
return None
|
||||
|
||||
data = super(ChatMember, cls).de_json(data, bot)
|
||||
data = super().de_json(data, bot)
|
||||
|
||||
data['user'] = User.de_json(data.get('user'), bot)
|
||||
data['until_date'] = from_timestamp(data.get('until_date', None))
|
||||
@@ -154,7 +158,7 @@ class ChatMember(TelegramObject):
|
||||
return cls(**data)
|
||||
|
||||
def to_dict(self):
|
||||
data = super(ChatMember, self).to_dict()
|
||||
data = super().to_dict()
|
||||
|
||||
data['until_date'] = to_timestamp(self.until_date)
|
||||
|
||||
|
||||
@@ -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-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -24,6 +24,11 @@ from telegram import TelegramObject
|
||||
class ChatPermissions(TelegramObject):
|
||||
"""Describes actions that a non-administrator user is allowed to take in a chat.
|
||||
|
||||
Note:
|
||||
Though not stated explicitly in the offical 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.
|
||||
|
||||
Attributes:
|
||||
can_send_messages (:obj:`bool`): Optional. True, if the user is allowed to send text
|
||||
messages, contacts, locations and venues.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# pylint: disable=R0902,R0912,R0913
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2018
|
||||
# Copyright (C) 2015-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -72,7 +72,7 @@ class ChosenInlineResult(TelegramObject):
|
||||
if not data:
|
||||
return None
|
||||
|
||||
data = super(ChosenInlineResult, cls).de_json(data, bot)
|
||||
data = super().de_json(data, bot)
|
||||
# Required
|
||||
data['from_user'] = User.de_json(data.pop('from'), bot)
|
||||
# Optionals
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# python-telegram-bot - a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2018
|
||||
# Copyright (C) 2015-2020
|
||||
# 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,6 +26,7 @@ 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
|
||||
@@ -47,6 +49,7 @@ MAX_CAPTION_LENGTH = 1024
|
||||
SUPPORTED_WEBHOOK_PORTS = [443, 80, 88, 8443]
|
||||
MAX_FILESIZE_DOWNLOAD = int(20E6) # (20MB)
|
||||
MAX_FILESIZE_UPLOAD = int(50E6) # (50MB)
|
||||
MAX_PHOTOSIZE_UPLOAD = int(10E6) # (10MB)
|
||||
MAX_MESSAGES_PER_SECOND_PER_CHAT = 1
|
||||
MAX_MESSAGES_PER_SECOND = 30
|
||||
MAX_MESSAGES_PER_MINUTE_PER_GROUP = 20
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env python
|
||||
# pylint: disable=R0903
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2020
|
||||
# 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 telegram import TelegramObject
|
||||
|
||||
|
||||
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".)
|
||||
|
||||
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.
|
||||
|
||||
Attributes:
|
||||
value (:obj:`int`): Value of the dice.
|
||||
emoji (:obj:`str`): Emoji on which the dice throw animation is based.
|
||||
|
||||
Args:
|
||||
value (:obj:`int`): Value of the dice. 1-6 for dice and darts, 1-5 for basketball.
|
||||
emoji (:obj:`str`): Emoji on which the dice throw animation is based.
|
||||
"""
|
||||
def __init__(self, value, emoji, **kwargs):
|
||||
self.value = value
|
||||
self.emoji = emoji
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data, bot):
|
||||
if not data:
|
||||
return None
|
||||
|
||||
return cls(**data)
|
||||
|
||||
DICE = '🎲'
|
||||
""":obj:`str`: '🎲'"""
|
||||
DARTS = '🎯'
|
||||
""":obj:`str`: '🎯'"""
|
||||
BASKETBALL = '🏀'
|
||||
""":obj:`str`: '🏀'"""
|
||||
ALL_EMOJI = [DICE, DARTS, BASKETBALL]
|
||||
"""List[:obj:`str`]: List of all supported base emoji. Currently :attr:`DICE`,
|
||||
:attr:`DARTS` and :attr:`BASKETBALL`."""
|
||||
+7
-9
@@ -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-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -38,7 +38,7 @@ def _lstrip_str(in_s, lstr):
|
||||
|
||||
class TelegramError(Exception):
|
||||
def __init__(self, message):
|
||||
super(TelegramError, self).__init__()
|
||||
super().__init__()
|
||||
|
||||
msg = _lstrip_str(message, 'Error: ')
|
||||
msg = _lstrip_str(msg, '[Error]: ')
|
||||
@@ -58,7 +58,7 @@ class Unauthorized(TelegramError):
|
||||
|
||||
class InvalidToken(TelegramError):
|
||||
def __init__(self):
|
||||
super(InvalidToken, self).__init__('Invalid token')
|
||||
super().__init__('Invalid token')
|
||||
|
||||
|
||||
class NetworkError(TelegramError):
|
||||
@@ -71,7 +71,7 @@ class BadRequest(NetworkError):
|
||||
|
||||
class TimedOut(NetworkError):
|
||||
def __init__(self):
|
||||
super(TimedOut, self).__init__('Timed out')
|
||||
super().__init__('Timed out')
|
||||
|
||||
|
||||
class ChatMigrated(TelegramError):
|
||||
@@ -82,8 +82,7 @@ class ChatMigrated(TelegramError):
|
||||
"""
|
||||
|
||||
def __init__(self, new_chat_id):
|
||||
super(ChatMigrated,
|
||||
self).__init__('Group migrated to supergroup. New chat id: {}'.format(new_chat_id))
|
||||
super().__init__('Group migrated to supergroup. New chat id: {}'.format(new_chat_id))
|
||||
self.new_chat_id = new_chat_id
|
||||
|
||||
|
||||
@@ -95,8 +94,7 @@ class RetryAfter(TelegramError):
|
||||
"""
|
||||
|
||||
def __init__(self, retry_after):
|
||||
super(RetryAfter,
|
||||
self).__init__('Flood control exceeded. Retry in {} seconds'.format(retry_after))
|
||||
super().__init__('Flood control exceeded. Retry in {} seconds'.format(retry_after))
|
||||
self.retry_after = float(retry_after)
|
||||
|
||||
|
||||
@@ -110,4 +108,4 @@ class Conflict(TelegramError):
|
||||
"""
|
||||
|
||||
def __init__(self, msg):
|
||||
super(Conflict, self).__init__(msg)
|
||||
super().__init__(msg)
|
||||
|
||||
@@ -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-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -41,6 +41,9 @@ 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',
|
||||
@@ -48,4 +51,5 @@ __all__ = ('Dispatcher', 'JobQueue', 'Job', 'Updater', 'CallbackQueryHandler',
|
||||
'StringRegexHandler', 'TypeHandler', 'ConversationHandler',
|
||||
'PreCheckoutQueryHandler', 'ShippingQueryHandler', 'MessageQueue', 'DelayQueue',
|
||||
'DispatcherHandlerStop', 'run_async', 'CallbackContext', 'BasePersistence',
|
||||
'PicklePersistence', 'DictPersistence', 'PrefixHandler')
|
||||
'PicklePersistence', 'DictPersistence', 'PrefixHandler', 'PollAnswerHandler',
|
||||
'PollHandler', 'Defaults')
|
||||
|
||||
@@ -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-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -18,13 +18,17 @@
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains the BasePersistence class."""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
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:
|
||||
|
||||
* If :attr:`store_bot_data` is ``True`` you must overwrite :meth:`get_bot_data` and
|
||||
:meth:`update_bot_data`.
|
||||
* 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
|
||||
@@ -38,18 +42,24 @@ class BasePersistence(object):
|
||||
persistence class.
|
||||
store_chat_data (:obj:`bool`): Optional. Whether chat_data should be saved by this
|
||||
persistence class.
|
||||
store_bot_data (:obj:`bool`): Optional. Whether bot_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. Default is ``True`` .
|
||||
"""
|
||||
|
||||
def __init__(self, store_user_data=True, store_chat_data=True):
|
||||
def __init__(self, store_user_data=True, store_chat_data=True, store_bot_data=True):
|
||||
self.store_user_data = store_user_data
|
||||
self.store_chat_data = store_chat_data
|
||||
self.store_bot_data = store_bot_data
|
||||
|
||||
@abstractmethod
|
||||
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
|
||||
@@ -58,8 +68,8 @@ class BasePersistence(object):
|
||||
Returns:
|
||||
:obj:`defaultdict`: The restored user data.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
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
|
||||
@@ -68,8 +78,18 @@ class BasePersistence(object):
|
||||
Returns:
|
||||
:obj:`defaultdict`: The restored chat data.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def get_bot_data(self):
|
||||
""""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
|
||||
``dict``.
|
||||
|
||||
Returns:
|
||||
:obj:`defaultdict`: The restored bot data.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_conversations(self, name):
|
||||
""""Will be called by :class:`telegram.ext.Dispatcher` when a
|
||||
:class:`telegram.ext.ConversationHandler` is added if
|
||||
@@ -82,8 +102,8 @@ class BasePersistence(object):
|
||||
Returns:
|
||||
:obj:`dict`: The restored conversations for the handler.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def update_conversation(self, name, key, new_state):
|
||||
"""Will be called when a :attr:`telegram.ext.ConversationHandler.update_state`
|
||||
is called. this allows the storeage of the new state in the persistence.
|
||||
@@ -93,8 +113,8 @@ class BasePersistence(object):
|
||||
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
|
||||
|
||||
@abstractmethod
|
||||
def update_user_data(self, user_id, data):
|
||||
"""Will be called by the :class:`telegram.ext.Dispatcher` after a handler has
|
||||
handled an update.
|
||||
@@ -103,17 +123,25 @@ 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
|
||||
|
||||
@abstractmethod
|
||||
def update_chat_data(self, chat_id, data):
|
||||
"""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].
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def update_bot_data(self, data):
|
||||
"""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` .
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def flush(self):
|
||||
"""Will be called by :class:`telegram.ext.Updater` upon receiving a stop signal. Gives the
|
||||
|
||||
@@ -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-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -21,7 +21,7 @@
|
||||
from telegram import Update
|
||||
|
||||
|
||||
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
|
||||
@@ -43,20 +43,29 @@ class CallbackContext(object):
|
||||
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.
|
||||
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.
|
||||
@@ -73,6 +82,7 @@ class CallbackContext(object):
|
||||
raise ValueError('CallbackContext should not be used with a non context aware '
|
||||
'dispatcher!')
|
||||
self._dispatcher = dispatcher
|
||||
self._bot_data = dispatcher.bot_data
|
||||
self._chat_data = None
|
||||
self._user_data = None
|
||||
self.args = None
|
||||
@@ -80,6 +90,20 @@ class CallbackContext(object):
|
||||
self.error = None
|
||||
self.job = None
|
||||
|
||||
@property
|
||||
def dispatcher(self):
|
||||
""":class:`telegram.ext.Dispatcher`: The dispatcher associated with this context."""
|
||||
return self._dispatcher
|
||||
|
||||
@property
|
||||
def bot_data(self):
|
||||
return self._bot_data
|
||||
|
||||
@bot_data.setter
|
||||
def bot_data(self, value):
|
||||
raise AttributeError("You can not assign a new value to bot_data, see "
|
||||
"https://git.io/fjxKe")
|
||||
|
||||
@property
|
||||
def chat_data(self):
|
||||
return self._chat_data
|
||||
@@ -107,6 +131,7 @@ class CallbackContext(object):
|
||||
@classmethod
|
||||
def from_update(cls, update, dispatcher):
|
||||
self = cls(dispatcher)
|
||||
|
||||
if update is not None and isinstance(update, Update):
|
||||
chat = update.effective_chat
|
||||
user = update.effective_user
|
||||
|
||||
@@ -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-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -20,8 +20,6 @@
|
||||
|
||||
import re
|
||||
|
||||
from future.utils import string_types
|
||||
|
||||
from telegram import Update
|
||||
from .handler import Handler
|
||||
|
||||
@@ -105,14 +103,14 @@ class CallbackQueryHandler(Handler):
|
||||
pass_groupdict=False,
|
||||
pass_user_data=False,
|
||||
pass_chat_data=False):
|
||||
super(CallbackQueryHandler, 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)
|
||||
|
||||
if isinstance(pattern, string_types):
|
||||
if isinstance(pattern, str):
|
||||
pattern = re.compile(pattern)
|
||||
|
||||
self.pattern = pattern
|
||||
@@ -139,9 +137,7 @@ class CallbackQueryHandler(Handler):
|
||||
return True
|
||||
|
||||
def collect_optional_args(self, dispatcher, update=None, check_result=None):
|
||||
optional_args = super(CallbackQueryHandler, self).collect_optional_args(dispatcher,
|
||||
update,
|
||||
check_result)
|
||||
optional_args = super().collect_optional_args(dispatcher, update, check_result)
|
||||
if self.pattern:
|
||||
if self.pass_groups:
|
||||
optional_args['groups'] = check_result.groups()
|
||||
|
||||
@@ -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-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -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-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -20,8 +20,6 @@
|
||||
import re
|
||||
import warnings
|
||||
|
||||
from future.utils import string_types
|
||||
|
||||
from telegram.ext import Filters
|
||||
from telegram.utils.deprecate import TelegramDeprecationWarning
|
||||
|
||||
@@ -125,14 +123,14 @@ class CommandHandler(Handler):
|
||||
pass_job_queue=False,
|
||||
pass_user_data=False,
|
||||
pass_chat_data=False):
|
||||
super(CommandHandler, 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)
|
||||
|
||||
if isinstance(command, string_types):
|
||||
if isinstance(command, str):
|
||||
self.command = [command.lower()]
|
||||
else:
|
||||
self.command = [x.lower() for x in command]
|
||||
@@ -184,7 +182,7 @@ class CommandHandler(Handler):
|
||||
return False
|
||||
|
||||
def collect_optional_args(self, dispatcher, update=None, check_result=None):
|
||||
optional_args = super(CommandHandler, self).collect_optional_args(dispatcher, update)
|
||||
optional_args = super().collect_optional_args(dispatcher, update)
|
||||
if self.pass_args:
|
||||
optional_args['args'] = check_result[0]
|
||||
return optional_args
|
||||
@@ -302,22 +300,47 @@ class PrefixHandler(CommandHandler):
|
||||
pass_user_data=False,
|
||||
pass_chat_data=False):
|
||||
|
||||
super(PrefixHandler, self).__init__(
|
||||
self._prefix = list()
|
||||
self._command = list()
|
||||
self._commands = 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)
|
||||
|
||||
if isinstance(prefix, string_types):
|
||||
self.prefix = [prefix.lower()]
|
||||
self.prefix = prefix
|
||||
self.command = command
|
||||
self._build_commands()
|
||||
|
||||
@property
|
||||
def prefix(self):
|
||||
return self._prefix
|
||||
|
||||
@prefix.setter
|
||||
def prefix(self, prefix):
|
||||
if isinstance(prefix, str):
|
||||
self._prefix = [prefix.lower()]
|
||||
else:
|
||||
self.prefix = prefix
|
||||
if isinstance(command, string_types):
|
||||
self.command = [command.lower()]
|
||||
self._prefix = prefix
|
||||
self._build_commands()
|
||||
|
||||
@property
|
||||
def command(self):
|
||||
return self._command
|
||||
|
||||
@command.setter
|
||||
def command(self, command):
|
||||
if isinstance(command, str):
|
||||
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._command = command
|
||||
self._build_commands()
|
||||
|
||||
def _build_commands(self):
|
||||
self._commands = [x.lower() + y.lower() for x in self.prefix for y in self.command]
|
||||
|
||||
def check_update(self, update):
|
||||
"""Determines whether an update should be passed to this handlers :attr:`callback`.
|
||||
@@ -334,7 +357,7 @@ 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:
|
||||
|
||||
@@ -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-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
import logging
|
||||
import warnings
|
||||
from threading import Lock
|
||||
|
||||
from telegram import Update
|
||||
from telegram.ext import (Handler, CallbackQueryHandler, InlineQueryHandler,
|
||||
@@ -27,18 +28,18 @@ from telegram.ext import (Handler, CallbackQueryHandler, InlineQueryHandler,
|
||||
from telegram.utils.promise import Promise
|
||||
|
||||
|
||||
class _ConversationTimeoutContext(object):
|
||||
def __init__(self, conversation_key, update, dispatcher):
|
||||
class _ConversationTimeoutContext:
|
||||
def __init__(self, conversation_key, update, dispatcher, callback_context):
|
||||
self.conversation_key = conversation_key
|
||||
self.update = update
|
||||
self.dispatcher = dispatcher
|
||||
self.callback_context = callback_context
|
||||
|
||||
|
||||
class ConversationHandler(Handler):
|
||||
"""
|
||||
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.
|
||||
handlers.
|
||||
|
||||
The first collection, a ``list`` named :attr:`entry_points`, is used to initiate the
|
||||
conversation, for example with a :class:`telegram.ext.CommandHandler` or
|
||||
@@ -93,11 +94,12 @@ class ConversationHandler(Handler):
|
||||
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 `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
|
||||
@@ -130,8 +132,9 @@ class ConversationHandler(Handler):
|
||||
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`.
|
||||
received update and the corresponding ``context`` 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
|
||||
@@ -165,26 +168,28 @@ class ConversationHandler(Handler):
|
||||
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
|
||||
self._persistence = None
|
||||
""":obj:`telegram.ext.BasePersistance`: 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_lock = Lock()
|
||||
self._conversations = dict()
|
||||
self._conversations_lock = Lock()
|
||||
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -223,6 +228,113 @@ class ConversationHandler(Handler):
|
||||
"since inline queries have no chat context.")
|
||||
break
|
||||
|
||||
@property
|
||||
def entry_points(self):
|
||||
return self._entry_points
|
||||
|
||||
@entry_points.setter
|
||||
def entry_points(self, value):
|
||||
raise ValueError('You can not assign a new value to entry_points after initialization.')
|
||||
|
||||
@property
|
||||
def states(self):
|
||||
return self._states
|
||||
|
||||
@states.setter
|
||||
def states(self, value):
|
||||
raise ValueError('You can not assign a new value to states after initialization.')
|
||||
|
||||
@property
|
||||
def fallbacks(self):
|
||||
return self._fallbacks
|
||||
|
||||
@fallbacks.setter
|
||||
def fallbacks(self, value):
|
||||
raise ValueError('You can not assign a new value to fallbacks after initialization.')
|
||||
|
||||
@property
|
||||
def allow_reentry(self):
|
||||
return self._allow_reentry
|
||||
|
||||
@allow_reentry.setter
|
||||
def allow_reentry(self, value):
|
||||
raise ValueError('You can not assign a new value to allow_reentry after initialization.')
|
||||
|
||||
@property
|
||||
def per_user(self):
|
||||
return self._per_user
|
||||
|
||||
@per_user.setter
|
||||
def per_user(self, value):
|
||||
raise ValueError('You can not assign a new value to per_user after initialization.')
|
||||
|
||||
@property
|
||||
def per_chat(self):
|
||||
return self._per_chat
|
||||
|
||||
@per_chat.setter
|
||||
def per_chat(self, value):
|
||||
raise ValueError('You can not assign a new value to per_chat after initialization.')
|
||||
|
||||
@property
|
||||
def per_message(self):
|
||||
return self._per_message
|
||||
|
||||
@per_message.setter
|
||||
def per_message(self, value):
|
||||
raise ValueError('You can not assign a new value to per_message after initialization.')
|
||||
|
||||
@property
|
||||
def conversation_timeout(self):
|
||||
return self._conversation_timeout
|
||||
|
||||
@conversation_timeout.setter
|
||||
def conversation_timeout(self, value):
|
||||
raise ValueError('You can not assign a new value to conversation_timeout after '
|
||||
'initialization.')
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
@name.setter
|
||||
def name(self, value):
|
||||
raise ValueError('You can not assign a new value to name after initialization.')
|
||||
|
||||
@property
|
||||
def map_to_parent(self):
|
||||
return self._map_to_parent
|
||||
|
||||
@map_to_parent.setter
|
||||
def map_to_parent(self, value):
|
||||
raise ValueError('You can not assign a new value to map_to_parent after initialization.')
|
||||
|
||||
@property
|
||||
def persistence(self):
|
||||
return self._persistence
|
||||
|
||||
@persistence.setter
|
||||
def persistence(self, persistence):
|
||||
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):
|
||||
return self._conversations
|
||||
|
||||
@conversations.setter
|
||||
def conversations(self, value):
|
||||
self._conversations = value
|
||||
# Set conversations for nested conversations
|
||||
for handlers in self.states.values():
|
||||
for handler in handlers:
|
||||
if isinstance(handler, ConversationHandler):
|
||||
handler.conversations = self.persistence.get_conversations(handler.name)
|
||||
|
||||
def _get_key(self, update):
|
||||
chat = update.effective_chat
|
||||
user = update.effective_user
|
||||
@@ -262,7 +374,8 @@ class ConversationHandler(Handler):
|
||||
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):
|
||||
@@ -281,7 +394,8 @@ class ConversationHandler(Handler):
|
||||
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:
|
||||
@@ -290,7 +404,7 @@ class ConversationHandler(Handler):
|
||||
return key, handler, check
|
||||
return None
|
||||
|
||||
self.logger.debug('selecting conversation %s with state %s' % (str(key), str(state)))
|
||||
self.logger.debug('selecting conversation {} with state {}'.format(str(key), str(state)))
|
||||
|
||||
handler = None
|
||||
|
||||
@@ -340,15 +454,23 @@ class ConversationHandler(Handler):
|
||||
|
||||
"""
|
||||
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)
|
||||
|
||||
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()
|
||||
|
||||
new_state = handler.handle_update(update, dispatcher, check_result, context)
|
||||
|
||||
with self._timeout_jobs_lock:
|
||||
if self.conversation_timeout and new_state != self.END:
|
||||
# Add the new timeout job
|
||||
self.timeout_jobs[conversation_key] = dispatcher.job_queue.run_once(
|
||||
self._trigger_timeout, 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)
|
||||
@@ -358,36 +480,47 @@ class ConversationHandler(Handler):
|
||||
|
||||
def update_state(self, new_state, key):
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
self.persistence.update_conversation(self.name, key, new_state)
|
||||
|
||||
def _trigger_timeout(self, context, job=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
|
||||
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)
|
||||
handler.handle_update(context.update, context.dispatcher, check, callback_context)
|
||||
self.update_state(self.END, context.conversation_key)
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2020
|
||||
# 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 class Defaults, which allows to pass default values to Updater."""
|
||||
|
||||
from telegram.utils.helpers import DEFAULT_NONE
|
||||
|
||||
|
||||
class Defaults:
|
||||
"""Convenience Class to gather all parameters with a (user defined) default value
|
||||
|
||||
Attributes:
|
||||
parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show
|
||||
bold, italic, fixed-width toxt 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.
|
||||
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 ``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: ``True`` in group chats and ``False`` in private chats.
|
||||
|
||||
Parameters:
|
||||
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show
|
||||
bold, italic, fixed-width toxt 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.
|
||||
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`, opitonal): If set to ``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: ``True`` in group chats and ``False`` in private chats.
|
||||
"""
|
||||
def __init__(self,
|
||||
parse_mode=None,
|
||||
disable_notification=None,
|
||||
disable_web_page_preview=None,
|
||||
# Timeout needs special treatment, since the bot methods have two different
|
||||
# default values for timeout (None and 20s)
|
||||
timeout=DEFAULT_NONE,
|
||||
quote=None):
|
||||
self._parse_mode = parse_mode
|
||||
self._disable_notification = disable_notification
|
||||
self._disable_web_page_preview = disable_web_page_preview
|
||||
self._timeout = timeout
|
||||
self._quote = quote
|
||||
|
||||
@property
|
||||
def parse_mode(self):
|
||||
return self._parse_mode
|
||||
|
||||
@parse_mode.setter
|
||||
def parse_mode(self, value):
|
||||
raise AttributeError("You can not assign a new value to defaults after because it would "
|
||||
"not have any effect.")
|
||||
|
||||
@property
|
||||
def disable_notification(self):
|
||||
return self._disable_notification
|
||||
|
||||
@disable_notification.setter
|
||||
def disable_notification(self, value):
|
||||
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):
|
||||
return self._disable_web_page_preview
|
||||
|
||||
@disable_web_page_preview.setter
|
||||
def disable_web_page_preview(self, value):
|
||||
raise AttributeError("You can not assign a new value to defaults after because it would "
|
||||
"not have any effect.")
|
||||
|
||||
@property
|
||||
def timeout(self):
|
||||
return self._timeout
|
||||
|
||||
@timeout.setter
|
||||
def timeout(self, value):
|
||||
raise AttributeError("You can not assign a new value to defaults after because it would "
|
||||
"not have any effect.")
|
||||
|
||||
@property
|
||||
def quote(self):
|
||||
return self._quote
|
||||
|
||||
@quote.setter
|
||||
def quote(self, value):
|
||||
raise AttributeError("You can not assign a new value to defaults after because it would "
|
||||
"not have any effect.")
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self._parse_mode,
|
||||
self._disable_notification,
|
||||
self._disable_web_page_preview,
|
||||
self._timeout,
|
||||
self._quote))
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, Defaults):
|
||||
return self.__dict__ == other.__dict__
|
||||
return False
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
@@ -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-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -20,7 +20,7 @@
|
||||
from copy import deepcopy
|
||||
|
||||
from telegram.utils.helpers import decode_user_chat_data_from_json,\
|
||||
decode_conversations_from_json, enocde_conversations_to_json
|
||||
decode_conversations_from_json, encode_conversations_to_json
|
||||
|
||||
try:
|
||||
import ujson as json
|
||||
@@ -31,36 +31,51 @@ from telegram.ext import BasePersistence
|
||||
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
store_bot_data (:obj:`bool`): Whether bot_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``.
|
||||
store_bot_data (:obj:`bool`, optional): Whether bot_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 ``""``.
|
||||
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 ``""``.
|
||||
"""
|
||||
|
||||
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=True,
|
||||
store_chat_data=True,
|
||||
store_bot_data=True,
|
||||
user_data_json='',
|
||||
chat_data_json='',
|
||||
bot_data_json='',
|
||||
conversations_json=''):
|
||||
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:
|
||||
@@ -74,6 +89,14 @@ class DictPersistence(BasePersistence):
|
||||
self._chat_data_json = chat_data_json
|
||||
except (ValueError, AttributeError):
|
||||
raise TypeError("Unable to deserialize chat_data_json. Not valid JSON")
|
||||
if bot_data_json:
|
||||
try:
|
||||
self._bot_data = json.loads(bot_data_json)
|
||||
self._bot_data_json = bot_data_json
|
||||
except (ValueError, AttributeError):
|
||||
raise TypeError("Unable to deserialize bot_data_json. Not valid JSON")
|
||||
if not isinstance(self._bot_data, dict):
|
||||
raise TypeError("bot_data_json must be serialized dict")
|
||||
|
||||
if conversations_json:
|
||||
try:
|
||||
@@ -108,6 +131,19 @@ class DictPersistence(BasePersistence):
|
||||
else:
|
||||
return json.dumps(self.chat_data)
|
||||
|
||||
@property
|
||||
def bot_data(self):
|
||||
""":obj:`dict`: The bot_data as a dict"""
|
||||
return self._bot_data
|
||||
|
||||
@property
|
||||
def bot_data_json(self):
|
||||
""":obj:`str`: The bot_data serialized as a JSON-string."""
|
||||
if self._bot_data_json:
|
||||
return self._bot_data_json
|
||||
else:
|
||||
return json.dumps(self.bot_data)
|
||||
|
||||
@property
|
||||
def conversations(self):
|
||||
""":obj:`dict`: The conversations as a dict"""
|
||||
@@ -119,7 +155,7 @@ class DictPersistence(BasePersistence):
|
||||
if self._conversations_json:
|
||||
return self._conversations_json
|
||||
else:
|
||||
return enocde_conversations_to_json(self.conversations)
|
||||
return encode_conversations_to_json(self.conversations)
|
||||
|
||||
def get_user_data(self):
|
||||
"""Returns the user_data created from the ``user_data_json`` or an empty defaultdict.
|
||||
@@ -145,6 +181,18 @@ class DictPersistence(BasePersistence):
|
||||
self._chat_data = defaultdict(dict)
|
||||
return deepcopy(self.chat_data)
|
||||
|
||||
def get_bot_data(self):
|
||||
"""Returns the bot_data created from the ``bot_data_json`` or an empty dict.
|
||||
|
||||
Returns:
|
||||
:obj:`defaultdict`: The restored user data.
|
||||
"""
|
||||
if self.bot_data:
|
||||
pass
|
||||
else:
|
||||
self._bot_data = {}
|
||||
return deepcopy(self.bot_data)
|
||||
|
||||
def get_conversations(self, name):
|
||||
"""Returns the conversations created from the ``conversations_json`` or an empty
|
||||
defaultdict.
|
||||
@@ -178,6 +226,8 @@ class DictPersistence(BasePersistence):
|
||||
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
|
||||
@@ -190,7 +240,20 @@ class DictPersistence(BasePersistence):
|
||||
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):
|
||||
"""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
|
||||
|
||||
+72
-57
@@ -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-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -29,8 +29,6 @@ from collections import defaultdict
|
||||
|
||||
from queue import Queue, Empty
|
||||
|
||||
from future.builtins import range
|
||||
|
||||
from telegram import TelegramError, Update
|
||||
from telegram.ext.handler import Handler
|
||||
from telegram.ext.callbackcontext import CallbackContext
|
||||
@@ -38,7 +36,6 @@ from telegram.utils.deprecate import TelegramDeprecationWarning
|
||||
from telegram.utils.promise import Promise
|
||||
from telegram.ext import BasePersistence
|
||||
|
||||
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
||||
DEFAULT_GROUP = 0
|
||||
|
||||
|
||||
@@ -67,7 +64,7 @@ class DispatcherHandlerStop(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Dispatcher(object):
|
||||
class Dispatcher:
|
||||
"""This class dispatches all kinds of updates to its registered handlers.
|
||||
|
||||
Attributes:
|
||||
@@ -79,6 +76,7 @@ class Dispatcher(object):
|
||||
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.
|
||||
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
|
||||
|
||||
@@ -121,8 +119,8 @@ class Dispatcher(object):
|
||||
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.bot_data = {}
|
||||
if persistence:
|
||||
if not isinstance(persistence, BasePersistence):
|
||||
raise TypeError("persistence should be based on telegram.ext.BasePersistence")
|
||||
@@ -135,11 +133,13 @@ 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 = {}
|
||||
"""Dict[:obj:`int`, List[:class:`telegram.ext.Handler`]]: Holds the handlers per group."""
|
||||
self.groups = []
|
||||
@@ -162,6 +162,10 @@ class Dispatcher(object):
|
||||
else:
|
||||
self._set_singleton(None)
|
||||
|
||||
@property
|
||||
def exception_event(self):
|
||||
return self.__exception_event
|
||||
|
||||
def _init_async_threads(self, base_name, workers):
|
||||
base_name = '{}_'.format(base_name) if base_name else ''
|
||||
|
||||
@@ -298,10 +302,10 @@ 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 {}/{} to end'.format(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 {}/{} has ended'.format(i + 1, total))
|
||||
|
||||
@property
|
||||
def has_running_threads(self):
|
||||
@@ -316,42 +320,6 @@ class Dispatcher(object):
|
||||
|
||||
"""
|
||||
|
||||
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:
|
||||
@@ -370,13 +338,13 @@ class Dispatcher(object):
|
||||
if not context and self.use_context:
|
||||
context = CallbackContext.from_update(update, self)
|
||||
handler.handle_update(update, self, check, context)
|
||||
persist_update(update)
|
||||
self.update_persistence(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.
|
||||
@@ -395,7 +363,8 @@ class Dispatcher(object):
|
||||
def add_handler(self, handler, group=DEFAULT_GROUP):
|
||||
"""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
|
||||
@@ -420,7 +389,7 @@ class Dispatcher(object):
|
||||
from .conversationhandler import ConversationHandler
|
||||
|
||||
if not isinstance(handler, Handler):
|
||||
raise TypeError('handler is not an instance of {0}'.format(Handler.__name__))
|
||||
raise TypeError('handler is not an instance of {}'.format(Handler.__name__))
|
||||
if not isinstance(group, int):
|
||||
raise TypeError('group is not int')
|
||||
if isinstance(handler, ConversationHandler) and handler.persistent:
|
||||
@@ -428,8 +397,8 @@ class Dispatcher(object):
|
||||
raise ValueError(
|
||||
"Conversationhandler {} can not be persistent if dispatcher has no "
|
||||
"persistence".format(handler.name))
|
||||
handler.conversations = self.persistence.get_conversations(handler.name)
|
||||
handler.persistence = self.persistence
|
||||
handler.conversations = self.persistence.get_conversations(handler.name)
|
||||
|
||||
if group not in self.handlers:
|
||||
self.handlers[group] = list()
|
||||
@@ -452,16 +421,62 @@ 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`.
|
||||
def update_persistence(self, update=None):
|
||||
"""Update :attr:`user_data`, :attr:`chat_data` and :attr:`bot_data` in :attr:`persistence`.
|
||||
|
||||
Args:
|
||||
update (:class:`telegram.Update`, optional): The update to process. If passed, only the
|
||||
corresponding ``user_data`` and ``chat_data`` will be updated.
|
||||
"""
|
||||
if self.persistence:
|
||||
chat_ids = self.chat_data.keys()
|
||||
user_ids = 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 e:
|
||||
try:
|
||||
self.dispatch_error(update, e)
|
||||
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 self.chat_data:
|
||||
self.persistence.update_chat_data(chat_id, self.chat_data[chat_id])
|
||||
for chat_id in chat_ids:
|
||||
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:
|
||||
for user_id in self.user_data:
|
||||
self.persistence.update_user_data(user_id, self.user_data[user_id])
|
||||
for user_id in user_ids:
|
||||
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)
|
||||
|
||||
def add_error_handler(self, callback):
|
||||
"""Registers an error handler in the Dispatcher. This handler will receive every error
|
||||
|
||||
+507
-90
@@ -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-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -20,14 +20,15 @@
|
||||
|
||||
import re
|
||||
|
||||
from future.utils import string_types
|
||||
from abc import ABC, abstractmethod
|
||||
from threading import Lock
|
||||
|
||||
from telegram import Chat
|
||||
from telegram import Chat, Update, MessageEntity
|
||||
|
||||
__all__ = ['Filters', 'BaseFilter', 'InvertedFilter', 'MergedFilter']
|
||||
|
||||
|
||||
class BaseFilter(object):
|
||||
class BaseFilter(ABC):
|
||||
"""Base class for all Message Filters.
|
||||
|
||||
Subclassing from this class filters to be combined using bitwise operators:
|
||||
@@ -50,7 +51,7 @@ class BaseFilter(object):
|
||||
>>> Filters.text & (~ Filters.forwarded)
|
||||
|
||||
Note:
|
||||
Filters use the same short circuiting logic that pythons `and`, `or` and `not`.
|
||||
Filters use the same short circuiting logic as python's `and`, `or` and `not`.
|
||||
This means that for example:
|
||||
|
||||
>>> Filters.regex(r'(a?x)') | Filters.regex(r'(b?x)')
|
||||
@@ -103,6 +104,7 @@ class BaseFilter(object):
|
||||
self.name = self.__class__.__name__
|
||||
return self.name
|
||||
|
||||
@abstractmethod
|
||||
def filter(self, update):
|
||||
"""This method must be overwritten.
|
||||
|
||||
@@ -118,8 +120,6 @@ class BaseFilter(object):
|
||||
|
||||
"""
|
||||
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class InvertedFilter(BaseFilter):
|
||||
"""Represents a filter that has been inverted.
|
||||
@@ -215,7 +215,39 @@ class MergedFilter(BaseFilter):
|
||||
self.and_filter or self.or_filter)
|
||||
|
||||
|
||||
class Filters(object):
|
||||
class _DiceEmoji(BaseFilter):
|
||||
|
||||
def __init__(self, emoji=None, name=None):
|
||||
self.name = 'Filters.dice.{}'.format(name) if name else 'Filters.dice'
|
||||
self.emoji = emoji
|
||||
|
||||
class _DiceValues(BaseFilter):
|
||||
|
||||
def __init__(self, values, name, emoji=None):
|
||||
self.values = [values] if isinstance(values, int) else values
|
||||
self.emoji = emoji
|
||||
self.name = '{}({})'.format(name, values)
|
||||
|
||||
def filter(self, message):
|
||||
if bool(message.dice and message.dice.value in self.values):
|
||||
if self.emoji:
|
||||
return message.dice.emoji == self.emoji
|
||||
return True
|
||||
|
||||
def __call__(self, update):
|
||||
if isinstance(update, Update):
|
||||
return self.filter(update.effective_message)
|
||||
else:
|
||||
return self._DiceValues(update, self.name, emoji=self.emoji)
|
||||
|
||||
def filter(self, message):
|
||||
if bool(message.dice):
|
||||
if self.emoji:
|
||||
return message.dice.emoji == self.emoji
|
||||
return True
|
||||
|
||||
|
||||
class Filters:
|
||||
"""Predefined filters for use as the `filter` argument of :class:`telegram.ext.MessageHandler`.
|
||||
|
||||
Examples:
|
||||
@@ -236,20 +268,130 @@ class Filters(object):
|
||||
class _Text(BaseFilter):
|
||||
name = 'Filters.text'
|
||||
|
||||
class _TextStrings(BaseFilter):
|
||||
|
||||
def __init__(self, strings):
|
||||
self.strings = strings
|
||||
self.name = 'Filters.text({})'.format(strings)
|
||||
|
||||
def filter(self, message):
|
||||
if message.text:
|
||||
return message.text in self.strings
|
||||
return False
|
||||
|
||||
def __call__(self, update):
|
||||
if isinstance(update, Update):
|
||||
return self.filter(update.effective_message)
|
||||
else:
|
||||
return self._TextStrings(update)
|
||||
|
||||
def filter(self, message):
|
||||
return bool(message.text and not message.text.startswith('/'))
|
||||
return bool(message.text)
|
||||
|
||||
text = _Text()
|
||||
"""Text Messages."""
|
||||
"""Text Messages. If a list of strings is passed, it filters messages to only allow those
|
||||
whose text is appearing in the given list.
|
||||
|
||||
Examples:
|
||||
To allow any text message, simply use
|
||||
``MessageHandler(Filters.text, callback_method)``.
|
||||
|
||||
A simple usecase for passing a list is to allow only messages that were send by a
|
||||
custom :class:`telegram.ReplyKeyboardMarkup`::
|
||||
|
||||
buttons = ['Start', 'Settings', 'Back']
|
||||
markup = ReplyKeyboardMarkup.from_column(buttons)
|
||||
...
|
||||
MessageHandler(Filters.text(buttons), callback_method)
|
||||
|
||||
Note:
|
||||
* Dice messages don't have text. If you want to filter either text or dice messages, use
|
||||
``Filters.text | Filters.dice``.
|
||||
* Messages containing a command are accepted by this filter. Use
|
||||
``Filters.text & (~Filters.command)``, if you want to filter only text messages without
|
||||
commands.
|
||||
|
||||
Args:
|
||||
update (List[:obj:`str`] | Tuple[:obj:`str`], optional): Which messages to allow. Only
|
||||
exact matches are allowed. If not specified, will allow any text message.
|
||||
"""
|
||||
|
||||
class _Caption(BaseFilter):
|
||||
name = 'Filters.caption'
|
||||
|
||||
class _CaptionStrings(BaseFilter):
|
||||
|
||||
def __init__(self, strings):
|
||||
self.strings = strings
|
||||
self.name = 'Filters.caption({})'.format(strings)
|
||||
|
||||
def filter(self, message):
|
||||
if message.caption:
|
||||
return message.caption in self.strings
|
||||
return False
|
||||
|
||||
def __call__(self, update):
|
||||
if isinstance(update, Update):
|
||||
return self.filter(update.effective_message)
|
||||
else:
|
||||
return self._CaptionStrings(update)
|
||||
|
||||
def filter(self, message):
|
||||
return bool(message.caption)
|
||||
|
||||
caption = _Caption()
|
||||
"""Messages with a caption. If a list of strings is passed, it filters messages to only
|
||||
allow those whose caption is appearing in the given list.
|
||||
|
||||
Examples:
|
||||
``MessageHandler(Filters.caption, callback_method)``
|
||||
|
||||
Args:
|
||||
update (List[:obj:`str`] | Tuple[:obj:`str`], optional): Which captions to allow. Only
|
||||
exact matches are allowed. If not specified, will allow any message with a caption.
|
||||
"""
|
||||
|
||||
class _Command(BaseFilter):
|
||||
name = 'Filters.command'
|
||||
|
||||
class _CommandOnlyStart(BaseFilter):
|
||||
|
||||
def __init__(self, only_start):
|
||||
self.only_start = only_start
|
||||
self.name = 'Filters.command({})'.format(only_start)
|
||||
|
||||
def filter(self, message):
|
||||
return (message.entities
|
||||
and any([e.type == MessageEntity.BOT_COMMAND for e in message.entities]))
|
||||
|
||||
def __call__(self, update):
|
||||
if isinstance(update, Update):
|
||||
return self.filter(update.effective_message)
|
||||
else:
|
||||
return self._CommandOnlyStart(update)
|
||||
|
||||
def filter(self, message):
|
||||
return bool(message.text and message.text.startswith('/'))
|
||||
return (message.entities and message.entities[0].type == MessageEntity.BOT_COMMAND
|
||||
and message.entities[0].offset == 0)
|
||||
|
||||
command = _Command()
|
||||
"""Messages starting with ``/``."""
|
||||
"""
|
||||
Messages with a :attr:`telegram.MessageEntity.BOT_COMMAND`. By default only allows
|
||||
messages `starting` with a bot command. Pass ``False`` to also allow messages that contain a
|
||||
bot command `anywhere` in the text.
|
||||
|
||||
Examples::
|
||||
|
||||
MessageHandler(Filters.command, command_at_start_callback)
|
||||
MessageHandler(Filters.command(False), command_anywhere_callback)
|
||||
|
||||
Note:
|
||||
``Filters.text`` also accepts messages containing a command.
|
||||
|
||||
Args:
|
||||
update (:obj:`bool`, optional): Whether to only allow messages that `start` with a bot
|
||||
command. Defaults to ``True``.
|
||||
"""
|
||||
|
||||
class regex(BaseFilter):
|
||||
"""
|
||||
@@ -268,7 +410,7 @@ class Filters(object):
|
||||
if you need to specify flags on your pattern.
|
||||
|
||||
Note:
|
||||
Filters use the same short circuiting logic that pythons `and`, `or` and `not`.
|
||||
Filters use the same short circuiting logic as python's `and`, `or` and `not`.
|
||||
This means that for example:
|
||||
|
||||
>>> Filters.regex(r'(a?x)') | Filters.regex(r'(b?x)')
|
||||
@@ -283,7 +425,7 @@ class Filters(object):
|
||||
data_filter = True
|
||||
|
||||
def __init__(self, pattern):
|
||||
if isinstance(pattern, string_types):
|
||||
if isinstance(pattern, str):
|
||||
pattern = re.compile(pattern)
|
||||
self.pattern = pattern
|
||||
self.name = 'Filters.regex({})'.format(self.pattern)
|
||||
@@ -327,7 +469,7 @@ class Filters(object):
|
||||
send media with wrong types that don't fit to this handler.
|
||||
|
||||
Example:
|
||||
Filters.documents.category('audio/') returnes `True` for all types
|
||||
Filters.documents.category('audio/') returns `True` for all types
|
||||
of audio sent as file, for example 'audio/mpeg' or 'audio/x-wav'
|
||||
"""
|
||||
|
||||
@@ -405,38 +547,6 @@ class Filters(object):
|
||||
``Filters.document`` for all document messages.
|
||||
|
||||
Attributes:
|
||||
category: This Filter filters documents by their category in the mime-type attribute.
|
||||
|
||||
Example:
|
||||
``Filters.documents.category('audio/')`` filters all types
|
||||
of audio sent as file, for example 'audio/mpeg' or 'audio/x-wav'. The following
|
||||
attributes can be used as a shortcut like: ``Filters.document.audio``
|
||||
|
||||
application:
|
||||
audio:
|
||||
image:
|
||||
video:
|
||||
text:
|
||||
mime_type: This Filter filters documents by their mime-type attribute.
|
||||
|
||||
Example:
|
||||
``Filters.documents.mime_type('audio/mpeg')`` filters all audio in mp3 format. The
|
||||
following attributes can be used as a shortcut like: ``Filters.document.jpg``
|
||||
apk:
|
||||
doc:
|
||||
docx:
|
||||
exe:
|
||||
gif:
|
||||
jpg:
|
||||
mp3:
|
||||
pdf:
|
||||
py:
|
||||
svg:
|
||||
txt:
|
||||
targz:
|
||||
wav:
|
||||
xml:
|
||||
zip:
|
||||
category: This Filter filters documents by their category in the mime-type attribute
|
||||
|
||||
Note:
|
||||
@@ -782,38 +892,166 @@ officedocument.wordprocessingml.document")``-
|
||||
Examples:
|
||||
``MessageHandler(Filters.user(1234), callback_method)``
|
||||
|
||||
Warning:
|
||||
:attr:`user_ids` will give a *copy* of the saved user ids as :class:`frozenset`. This
|
||||
is to ensure thread safety. To add/remove a user, you should use :meth:`add_usernames`,
|
||||
:meth:`add_user_ids`, :meth:`remove_usernames` and :meth:`remove_user_ids`. Only update
|
||||
the entire set by ``filter.user_ids/usernames = new_set``, if you are entirely sure
|
||||
that it is not causing race conditions, as this will complete replace the current set
|
||||
of allowed users.
|
||||
|
||||
Attributes:
|
||||
user_ids(set(:obj:`int`), optional): Which user ID(s) to allow through.
|
||||
usernames(set(:obj:`str`), optional): Which username(s) (without leading '@') to allow
|
||||
through.
|
||||
allow_empty(:obj:`bool`, optional): Whether updates should be processed, if no user
|
||||
is specified in :attr:`user_ids` and :attr:`usernames`.
|
||||
|
||||
Args:
|
||||
user_id(:obj:`int` | List[:obj:`int`], optional): Which user ID(s) to allow through.
|
||||
username(:obj:`str` | List[:obj:`str`], optional): Which username(s) to allow through.
|
||||
If username starts with '@' symbol, it will be ignored.
|
||||
user_id(:obj:`int` | List[:obj:`int`], optional): Which user ID(s) to allow
|
||||
through.
|
||||
username(:obj:`str` | List[:obj:`str`], optional): Which username(s) to allow
|
||||
through. Leading '@'s in usernames will be discarded.
|
||||
allow_empty(:obj:`bool`, optional): Whether updates should be processed, if no user
|
||||
is specified in :attr:`user_ids` and :attr:`usernames`. Defaults to :obj:`False`
|
||||
|
||||
Raises:
|
||||
ValueError: If chat_id and username are both present, or neither is.
|
||||
RuntimeError: If user_id and username are both present.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, user_id=None, username=None):
|
||||
if not (bool(user_id) ^ bool(username)):
|
||||
raise ValueError('One and only one of user_id or username must be used')
|
||||
if user_id is not None and isinstance(user_id, int):
|
||||
self.user_ids = [user_id]
|
||||
else:
|
||||
self.user_ids = user_id
|
||||
def __init__(self, user_id=None, username=None, allow_empty=False):
|
||||
self.allow_empty = allow_empty
|
||||
self.__lock = Lock()
|
||||
|
||||
self._user_ids = set()
|
||||
self._usernames = set()
|
||||
|
||||
self._set_user_ids(user_id)
|
||||
self._set_usernames(username)
|
||||
|
||||
@staticmethod
|
||||
def _parse_user_id(user_id):
|
||||
if user_id is None:
|
||||
return set()
|
||||
if isinstance(user_id, int):
|
||||
return {user_id}
|
||||
return set(user_id)
|
||||
|
||||
@staticmethod
|
||||
def _parse_username(username):
|
||||
if username is None:
|
||||
self.usernames = username
|
||||
elif isinstance(username, string_types):
|
||||
self.usernames = [username.replace('@', '')]
|
||||
else:
|
||||
self.usernames = [user.replace('@', '') for user in username]
|
||||
return set()
|
||||
if isinstance(username, str):
|
||||
return {username[1:] if username.startswith('@') else username}
|
||||
return {user[1:] if user.startswith('@') else user for user in username}
|
||||
|
||||
def _set_user_ids(self, user_id):
|
||||
with self.__lock:
|
||||
if user_id and self._usernames:
|
||||
raise RuntimeError("Can't set user_id in conjunction with (already set) "
|
||||
"usernames.")
|
||||
self._user_ids = self._parse_user_id(user_id)
|
||||
|
||||
def _set_usernames(self, username):
|
||||
with self.__lock:
|
||||
if username and self._user_ids:
|
||||
raise RuntimeError("Can't set username in conjunction with (already set) "
|
||||
"user_ids.")
|
||||
self._usernames = self._parse_username(username)
|
||||
|
||||
@property
|
||||
def user_ids(self):
|
||||
with self.__lock:
|
||||
return frozenset(self._user_ids)
|
||||
|
||||
@user_ids.setter
|
||||
def user_ids(self, user_id):
|
||||
self._set_user_ids(user_id)
|
||||
|
||||
@property
|
||||
def usernames(self):
|
||||
with self.__lock:
|
||||
return frozenset(self._usernames)
|
||||
|
||||
@usernames.setter
|
||||
def usernames(self, username):
|
||||
self._set_usernames(username)
|
||||
|
||||
def add_usernames(self, username):
|
||||
"""
|
||||
Add one or more users to the allowed usernames.
|
||||
|
||||
Args:
|
||||
username(:obj:`str` | List[:obj:`str`], optional): Which username(s) to allow
|
||||
through. Leading '@'s in usernames will be discarded.
|
||||
"""
|
||||
with self.__lock:
|
||||
if self._user_ids:
|
||||
raise RuntimeError("Can't set username in conjunction with (already set) "
|
||||
"user_ids.")
|
||||
|
||||
username = self._parse_username(username)
|
||||
self._usernames |= username
|
||||
|
||||
def add_user_ids(self, user_id):
|
||||
"""
|
||||
Add one or more users to the allowed user ids.
|
||||
|
||||
Args:
|
||||
user_id(:obj:`int` | List[:obj:`int`], optional): Which user ID(s) to allow
|
||||
through.
|
||||
"""
|
||||
with self.__lock:
|
||||
if self._usernames:
|
||||
raise RuntimeError("Can't set user_id in conjunction with (already set) "
|
||||
"usernames.")
|
||||
|
||||
user_id = self._parse_user_id(user_id)
|
||||
|
||||
self._user_ids |= user_id
|
||||
|
||||
def remove_usernames(self, username):
|
||||
"""
|
||||
Remove one or more users from allowed usernames.
|
||||
|
||||
Args:
|
||||
username(:obj:`str` | List[:obj:`str`], optional): Which username(s) to disallow
|
||||
through. Leading '@'s in usernames will be discarded.
|
||||
"""
|
||||
with self.__lock:
|
||||
if self._user_ids:
|
||||
raise RuntimeError("Can't set username in conjunction with (already set) "
|
||||
"user_ids.")
|
||||
|
||||
username = self._parse_username(username)
|
||||
self._usernames -= username
|
||||
|
||||
def remove_user_ids(self, user_id):
|
||||
"""
|
||||
Remove one or more users from allowed user ids.
|
||||
|
||||
Args:
|
||||
user_id(:obj:`int` | List[:obj:`int`], optional): Which user ID(s) to disallow
|
||||
through.
|
||||
"""
|
||||
with self.__lock:
|
||||
if self._usernames:
|
||||
raise RuntimeError("Can't set user_id in conjunction with (already set) "
|
||||
"usernames.")
|
||||
user_id = self._parse_user_id(user_id)
|
||||
self._user_ids -= user_id
|
||||
|
||||
def filter(self, message):
|
||||
"""""" # remove method from docs
|
||||
if self.user_ids is not None:
|
||||
return bool(message.from_user and message.from_user.id in self.user_ids)
|
||||
else:
|
||||
# self.usernames is not None
|
||||
return bool(message.from_user and message.from_user.username
|
||||
if message.from_user:
|
||||
if self.user_ids:
|
||||
return message.from_user.id in self.user_ids
|
||||
if self.usernames:
|
||||
return (message.from_user.username
|
||||
and message.from_user.username in self.usernames)
|
||||
return self.allow_empty
|
||||
return False
|
||||
|
||||
class chat(BaseFilter):
|
||||
"""Filters messages to allow only those which are from specified chat ID.
|
||||
@@ -821,37 +1059,166 @@ officedocument.wordprocessingml.document")``-
|
||||
Examples:
|
||||
``MessageHandler(Filters.chat(-1234), callback_method)``
|
||||
|
||||
Warning:
|
||||
:attr:`chat_ids` will give a *copy* of the saved chat ids as :class:`frozenset`. This
|
||||
is to ensure thread safety. To add/remove a chat, you should use :meth:`add_usernames`,
|
||||
:meth:`add_chat_ids`, :meth:`remove_usernames` and :meth:`remove_chat_ids`. Only update
|
||||
the entire set by ``filter.chat_ids/usernames = new_set``, if you are entirely sure
|
||||
that it is not causing race conditions, as this will complete replace the current set
|
||||
of allowed chats.
|
||||
|
||||
Attributes:
|
||||
chat_ids(set(:obj:`int`), optional): Which chat ID(s) to allow through.
|
||||
usernames(set(:obj:`str`), optional): Which username(s) (without leading '@') to allow
|
||||
through.
|
||||
allow_empty(:obj:`bool`, optional): Whether updates should be processed, if no chat
|
||||
is specified in :attr:`chat_ids` and :attr:`usernames`.
|
||||
|
||||
Args:
|
||||
chat_id(:obj:`int` | List[:obj:`int`], optional): Which chat ID(s) to allow through.
|
||||
username(:obj:`str` | List[:obj:`str`], optional): Which username(s) to allow through.
|
||||
If username start swith '@' symbol, it will be ignored.
|
||||
chat_id(:obj:`int` | List[:obj:`int`], optional): Which chat ID(s) to allow
|
||||
through.
|
||||
username(:obj:`str` | List[:obj:`str`], optional): Which username(s) to allow
|
||||
through. Leading '@'s in usernames will be discarded.
|
||||
allow_empty(:obj:`bool`, optional): Whether updates should be processed, if no chat
|
||||
is specified in :attr:`chat_ids` and :attr:`usernames`. Defaults to :obj:`False`
|
||||
|
||||
Raises:
|
||||
ValueError: If chat_id and username are both present, or neither is.
|
||||
RuntimeError: If chat_id and username are both present.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, chat_id=None, username=None):
|
||||
if not (bool(chat_id) ^ bool(username)):
|
||||
raise ValueError('One and only one of chat_id or username must be used')
|
||||
if chat_id is not None and isinstance(chat_id, int):
|
||||
self.chat_ids = [chat_id]
|
||||
else:
|
||||
self.chat_ids = chat_id
|
||||
def __init__(self, chat_id=None, username=None, allow_empty=False):
|
||||
self.allow_empty = allow_empty
|
||||
self.__lock = Lock()
|
||||
|
||||
self._chat_ids = set()
|
||||
self._usernames = set()
|
||||
|
||||
self._set_chat_ids(chat_id)
|
||||
self._set_usernames(username)
|
||||
|
||||
@staticmethod
|
||||
def _parse_chat_id(chat_id):
|
||||
if chat_id is None:
|
||||
return set()
|
||||
if isinstance(chat_id, int):
|
||||
return {chat_id}
|
||||
return set(chat_id)
|
||||
|
||||
@staticmethod
|
||||
def _parse_username(username):
|
||||
if username is None:
|
||||
self.usernames = username
|
||||
elif isinstance(username, string_types):
|
||||
self.usernames = [username.replace('@', '')]
|
||||
else:
|
||||
self.usernames = [chat.replace('@', '') for chat in username]
|
||||
return set()
|
||||
if isinstance(username, str):
|
||||
return {username[1:] if username.startswith('@') else username}
|
||||
return {chat[1:] if chat.startswith('@') else chat for chat in username}
|
||||
|
||||
def _set_chat_ids(self, chat_id):
|
||||
with self.__lock:
|
||||
if chat_id and self._usernames:
|
||||
raise RuntimeError("Can't set chat_id in conjunction with (already set) "
|
||||
"usernames.")
|
||||
self._chat_ids = self._parse_chat_id(chat_id)
|
||||
|
||||
def _set_usernames(self, username):
|
||||
with self.__lock:
|
||||
if username and self._chat_ids:
|
||||
raise RuntimeError("Can't set username in conjunction with (already set) "
|
||||
"chat_ids.")
|
||||
self._usernames = self._parse_username(username)
|
||||
|
||||
@property
|
||||
def chat_ids(self):
|
||||
with self.__lock:
|
||||
return frozenset(self._chat_ids)
|
||||
|
||||
@chat_ids.setter
|
||||
def chat_ids(self, chat_id):
|
||||
self._set_chat_ids(chat_id)
|
||||
|
||||
@property
|
||||
def usernames(self):
|
||||
with self.__lock:
|
||||
return frozenset(self._usernames)
|
||||
|
||||
@usernames.setter
|
||||
def usernames(self, username):
|
||||
self._set_usernames(username)
|
||||
|
||||
def add_usernames(self, username):
|
||||
"""
|
||||
Add one or more chats to the allowed usernames.
|
||||
|
||||
Args:
|
||||
username(:obj:`str` | List[:obj:`str`], optional): Which username(s) to allow
|
||||
through. Leading '@'s in usernames will be discarded.
|
||||
"""
|
||||
with self.__lock:
|
||||
if self._chat_ids:
|
||||
raise RuntimeError("Can't set username in conjunction with (already set) "
|
||||
"chat_ids.")
|
||||
|
||||
username = self._parse_username(username)
|
||||
self._usernames |= username
|
||||
|
||||
def add_chat_ids(self, chat_id):
|
||||
"""
|
||||
Add one or more chats to the allowed chat ids.
|
||||
|
||||
Args:
|
||||
chat_id(:obj:`int` | List[:obj:`int`], optional): Which chat ID(s) to allow
|
||||
through.
|
||||
"""
|
||||
with self.__lock:
|
||||
if self._usernames:
|
||||
raise RuntimeError("Can't set chat_id in conjunction with (already set) "
|
||||
"usernames.")
|
||||
|
||||
chat_id = self._parse_chat_id(chat_id)
|
||||
|
||||
self._chat_ids |= chat_id
|
||||
|
||||
def remove_usernames(self, username):
|
||||
"""
|
||||
Remove one or more chats from allowed usernames.
|
||||
|
||||
Args:
|
||||
username(:obj:`str` | List[:obj:`str`], optional): Which username(s) to disallow
|
||||
through. Leading '@'s in usernames will be discarded.
|
||||
"""
|
||||
with self.__lock:
|
||||
if self._chat_ids:
|
||||
raise RuntimeError("Can't set username in conjunction with (already set) "
|
||||
"chat_ids.")
|
||||
|
||||
username = self._parse_username(username)
|
||||
self._usernames -= username
|
||||
|
||||
def remove_chat_ids(self, chat_id):
|
||||
"""
|
||||
Remove one or more chats from allowed chat ids.
|
||||
|
||||
Args:
|
||||
chat_id(:obj:`int` | List[:obj:`int`], optional): Which chat ID(s) to disallow
|
||||
through.
|
||||
"""
|
||||
with self.__lock:
|
||||
if self._usernames:
|
||||
raise RuntimeError("Can't set chat_id in conjunction with (already set) "
|
||||
"usernames.")
|
||||
chat_id = self._parse_chat_id(chat_id)
|
||||
self._chat_ids -= chat_id
|
||||
|
||||
def filter(self, message):
|
||||
"""""" # remove method from docs
|
||||
if self.chat_ids is not None:
|
||||
return bool(message.chat_id in self.chat_ids)
|
||||
else:
|
||||
# self.usernames is not None
|
||||
return bool(message.chat.username and message.chat.username in self.usernames)
|
||||
if message.chat:
|
||||
if self.chat_ids:
|
||||
return message.chat.id in self.chat_ids
|
||||
if self.usernames:
|
||||
return (message.chat.username
|
||||
and message.chat.username in self.usernames)
|
||||
return self.allow_empty
|
||||
return False
|
||||
|
||||
class _Invoice(BaseFilter):
|
||||
name = 'Filters.invoice'
|
||||
@@ -880,6 +1247,49 @@ officedocument.wordprocessingml.document")``-
|
||||
passport_data = _PassportData()
|
||||
"""Messages that contain a :class:`telegram.PassportData`"""
|
||||
|
||||
class _Poll(BaseFilter):
|
||||
name = 'Filters.poll'
|
||||
|
||||
def filter(self, message):
|
||||
return bool(message.poll)
|
||||
|
||||
poll = _Poll()
|
||||
"""Messages that contain a :class:`telegram.Poll`."""
|
||||
|
||||
class _Dice(_DiceEmoji):
|
||||
dice = _DiceEmoji('🎲', 'dice')
|
||||
darts = _DiceEmoji('🎯', 'darts')
|
||||
basketball = _DiceEmoji('🏀', 'basketball')
|
||||
|
||||
dice = _Dice()
|
||||
"""Dice Messages. If an integer or a list of integers is passed, it filters messages to only
|
||||
allow those whose dice value is appearing in the given list.
|
||||
|
||||
Examples:
|
||||
To allow any dice message, simply use
|
||||
``MessageHandler(Filters.dice, callback_method)``.
|
||||
To allow only dice with value 6, use
|
||||
``MessageHandler(Filters.dice(6), callback_method)``.
|
||||
To allow only dice with value 5 `or` 6, use
|
||||
``MessageHandler(Filters.dice([5, 6]), callback_method)``.
|
||||
|
||||
Args:
|
||||
update (:obj:`int` | List[:obj:`int`], optional): Which values to allow. If not
|
||||
specified, will allow any dice message.
|
||||
|
||||
Note:
|
||||
Dice messages don't have text. If you want to filter either text or dice messages, use
|
||||
``Filters.text | Filters.dice``.
|
||||
|
||||
Attributes:
|
||||
dice: Dice messages with the emoji 🎲. Passing a list of integers is supported just as for
|
||||
:attr:`Filters.dice`.
|
||||
darts: Dice messages with the emoji 🎯. Passing a list of integers is supported just as for
|
||||
:attr:`Filters.dice`.
|
||||
basketball: Dice messages with the emoji 🏀. Passing a list of integers is supported just
|
||||
as for :attr:`Filters.dice`.
|
||||
"""
|
||||
|
||||
class language(BaseFilter):
|
||||
"""Filters messages to only allow those which are from users with a certain language code.
|
||||
|
||||
@@ -898,7 +1308,7 @@ officedocument.wordprocessingml.document")``-
|
||||
"""
|
||||
|
||||
def __init__(self, lang):
|
||||
if isinstance(lang, string_types):
|
||||
if isinstance(lang, str):
|
||||
self.lang = [lang]
|
||||
else:
|
||||
self.lang = lang
|
||||
@@ -911,8 +1321,10 @@ officedocument.wordprocessingml.document")``-
|
||||
|
||||
class _UpdateType(BaseFilter):
|
||||
update_filter = True
|
||||
name = 'Filters.update'
|
||||
|
||||
class _Message(BaseFilter):
|
||||
name = 'Filters.update.message'
|
||||
update_filter = True
|
||||
|
||||
def filter(self, update):
|
||||
@@ -921,6 +1333,7 @@ officedocument.wordprocessingml.document")``-
|
||||
message = _Message()
|
||||
|
||||
class _EditedMessage(BaseFilter):
|
||||
name = 'Filters.update.edited_message'
|
||||
update_filter = True
|
||||
|
||||
def filter(self, update):
|
||||
@@ -929,6 +1342,7 @@ officedocument.wordprocessingml.document")``-
|
||||
edited_message = _EditedMessage()
|
||||
|
||||
class _Messages(BaseFilter):
|
||||
name = 'Filters.update.messages'
|
||||
update_filter = True
|
||||
|
||||
def filter(self, update):
|
||||
@@ -937,6 +1351,7 @@ officedocument.wordprocessingml.document")``-
|
||||
messages = _Messages()
|
||||
|
||||
class _ChannelPost(BaseFilter):
|
||||
name = 'Filters.update.channel_post'
|
||||
update_filter = True
|
||||
|
||||
def filter(self, update):
|
||||
@@ -946,6 +1361,7 @@ officedocument.wordprocessingml.document")``-
|
||||
|
||||
class _EditedChannelPost(BaseFilter):
|
||||
update_filter = True
|
||||
name = 'Filters.update.edited_channel_post'
|
||||
|
||||
def filter(self, update):
|
||||
return update.edited_channel_post is not None
|
||||
@@ -954,6 +1370,7 @@ officedocument.wordprocessingml.document")``-
|
||||
|
||||
class _ChannelPosts(BaseFilter):
|
||||
update_filter = True
|
||||
name = 'Filters.update.channel_posts'
|
||||
|
||||
def filter(self, update):
|
||||
return update.channel_post is not None or update.edited_channel_post is not 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-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -18,8 +18,10 @@
|
||||
# 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
|
||||
|
||||
class Handler(object):
|
||||
|
||||
class Handler(ABC):
|
||||
"""The base class for all update handlers. Create custom handlers by inheriting from it.
|
||||
|
||||
Attributes:
|
||||
@@ -82,6 +84,7 @@ class Handler(object):
|
||||
self.pass_user_data = pass_user_data
|
||||
self.pass_chat_data = pass_chat_data
|
||||
|
||||
@abstractmethod
|
||||
def check_update(self, update):
|
||||
"""
|
||||
This method is called to determine if an update should be handled by
|
||||
@@ -96,7 +99,6 @@ class Handler(object):
|
||||
when the update gets handled.
|
||||
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def handle_update(self, update, dispatcher, check_result, context=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-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -19,9 +19,8 @@
|
||||
""" This module contains the InlineQueryHandler class """
|
||||
import re
|
||||
|
||||
from future.utils import string_types
|
||||
|
||||
from telegram import Update
|
||||
|
||||
from .handler import Handler
|
||||
|
||||
|
||||
@@ -104,14 +103,14 @@ class InlineQueryHandler(Handler):
|
||||
pass_groupdict=False,
|
||||
pass_user_data=False,
|
||||
pass_chat_data=False):
|
||||
super(InlineQueryHandler, 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)
|
||||
|
||||
if isinstance(pattern, string_types):
|
||||
if isinstance(pattern, str):
|
||||
pattern = re.compile(pattern)
|
||||
|
||||
self.pattern = pattern
|
||||
@@ -140,8 +139,7 @@ class InlineQueryHandler(Handler):
|
||||
return True
|
||||
|
||||
def collect_optional_args(self, dispatcher, update=None, check_result=None):
|
||||
optional_args = super(InlineQueryHandler, self).collect_optional_args(dispatcher,
|
||||
update, check_result)
|
||||
optional_args = super().collect_optional_args(dispatcher, update, check_result)
|
||||
if self.pattern:
|
||||
if self.pass_groups:
|
||||
optional_args['groups'] = check_result.groups()
|
||||
|
||||
+262
-65
@@ -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-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -18,6 +18,7 @@
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains the classes JobQueue and Job."""
|
||||
|
||||
import calendar
|
||||
import datetime
|
||||
import logging
|
||||
import time
|
||||
@@ -29,20 +30,22 @@ from threading import Thread, Lock, Event
|
||||
|
||||
from telegram.ext.callbackcontext import CallbackContext
|
||||
from telegram.utils.deprecate import TelegramDeprecationWarning
|
||||
from telegram.utils.helpers import to_float_timestamp
|
||||
|
||||
|
||||
class Days(object):
|
||||
class Days:
|
||||
MON, TUE, WED, THU, FRI, SAT, SUN = range(7)
|
||||
EVERY_DAY = tuple(range(7))
|
||||
|
||||
|
||||
class JobQueue(object):
|
||||
class JobQueue:
|
||||
"""This class allows you to periodically perform tasks with the bot.
|
||||
|
||||
Attributes:
|
||||
_queue (:obj:`PriorityQueue`): The queue that holds the Jobs.
|
||||
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):
|
||||
@@ -51,7 +54,7 @@ class JobQueue(object):
|
||||
warnings.warn("Passing bot to jobqueue is deprecated. Please use set_dispatcher "
|
||||
"instead!", TelegramDeprecationWarning, stacklevel=2)
|
||||
|
||||
class MockDispatcher(object):
|
||||
class MockDispatcher:
|
||||
def __init__(self):
|
||||
self.bot = bot
|
||||
self.use_context = False
|
||||
@@ -68,33 +71,41 @@ class JobQueue(object):
|
||||
self._running = False
|
||||
|
||||
def set_dispatcher(self, dispatcher):
|
||||
"""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
|
||||
|
||||
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')
|
||||
def _put(self, job, time_spec=None, previous_t=None):
|
||||
"""
|
||||
Enqueues the job, scheduling its next run at the correct time.
|
||||
|
||||
if isinstance(next_t, datetime.datetime):
|
||||
next_t = (next_t - datetime.datetime.now()).total_seconds()
|
||||
Args:
|
||||
job (telegram.ext.Job): job to enqueue
|
||||
time_spec (optional):
|
||||
Specification of the time for which the job should be scheduled. The precise
|
||||
semantics of this parameter depend on its type (see
|
||||
:func:`telegram.ext.JobQueue.run_repeating` for details).
|
||||
Defaults to now + ``job.interval``.
|
||||
previous_t (optional):
|
||||
Time at which the job last ran (``None`` if it hasn't run yet).
|
||||
|
||||
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)
|
||||
"""
|
||||
# get time at which to run:
|
||||
if time_spec is None:
|
||||
time_spec = job.interval
|
||||
if time_spec is None:
|
||||
raise ValueError("no time specification given for scheduling non-repeating job")
|
||||
next_t = to_float_timestamp(time_spec, reference_timestamp=previous_t)
|
||||
|
||||
# enqueue:
|
||||
self.logger.debug('Putting job %s with t=%s', job.name, time_spec)
|
||||
self._queue.put((next_t, job))
|
||||
job._set_next_t(next_t)
|
||||
|
||||
# Wake up the loop if this job should be executed next
|
||||
self._set_next_peek(next_t)
|
||||
@@ -104,9 +115,12 @@ class JobQueue(object):
|
||||
|
||||
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,10 +131,14 @@ 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 ``None``, UTC
|
||||
will be assumed.
|
||||
* :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 ``None``, UTC will be assumed.
|
||||
|
||||
If ``when`` is :obj:`datetime.datetime` or :obj:`datetime.time` type
|
||||
then ``when.tzinfo`` will define ``Job.tzinfo``. Otherwise UTC will be assumed.
|
||||
|
||||
context (:obj:`object`, optional): Additional data needed for the callback function.
|
||||
Can be accessed through ``job.context`` in the callback. Defaults to ``None``.
|
||||
@@ -132,8 +150,15 @@ class JobQueue(object):
|
||||
queue.
|
||||
|
||||
"""
|
||||
job = Job(callback, repeat=False, context=context, name=name, job_queue=self)
|
||||
self._put(job, next_t=when)
|
||||
tzinfo = when.tzinfo if isinstance(when, (datetime.datetime, datetime.time)) else None
|
||||
|
||||
job = Job(callback,
|
||||
repeat=False,
|
||||
context=context,
|
||||
name=name,
|
||||
job_queue=self,
|
||||
tzinfo=tzinfo)
|
||||
self._put(job, time_spec=when)
|
||||
return job
|
||||
|
||||
def run_repeating(self, callback, interval, first=None, context=None, name=None):
|
||||
@@ -141,9 +166,12 @@ class JobQueue(object):
|
||||
|
||||
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,10 +185,14 @@ 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 ``None``, UTC
|
||||
will be assumed.
|
||||
* :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 ``None``, UTC will be assumed.
|
||||
|
||||
If ``first`` is :obj:`datetime.datetime` or :obj:`datetime.time` type
|
||||
then ``first.tzinfo`` will define ``Job.tzinfo``. Otherwise UTC will be assumed.
|
||||
|
||||
Defaults to ``interval``
|
||||
context (:obj:`object`, optional): Additional data needed for the callback function.
|
||||
@@ -172,30 +204,145 @@ class JobQueue(object):
|
||||
: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.
|
||||
|
||||
"""
|
||||
tzinfo = first.tzinfo if isinstance(first, (datetime.datetime, datetime.time)) else None
|
||||
|
||||
job = Job(callback,
|
||||
interval=interval,
|
||||
repeat=True,
|
||||
context=context,
|
||||
name=name,
|
||||
job_queue=self)
|
||||
self._put(job, next_t=first)
|
||||
job_queue=self,
|
||||
tzinfo=tzinfo)
|
||||
self._put(job, time_spec=first)
|
||||
return job
|
||||
|
||||
def run_monthly(self, callback, when, day, context=None, name=None, day_is_strict=True):
|
||||
"""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 ``None``, UTC will be assumed. This will also implicitly
|
||||
define ``Job.tzinfo``.
|
||||
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 ``None``.
|
||||
name (:obj:`str`, optional): The name of the new job. Defaults to
|
||||
``callback.__name__``.
|
||||
day_is_strict (:obj:`bool`, optional): If ``False`` and day > month.days, will pick
|
||||
the last day in the month. Defaults to ``True``.
|
||||
|
||||
Returns:
|
||||
:class:`telegram.ext.Job`: The new ``Job`` instance that has been added to the job
|
||||
queue.
|
||||
|
||||
"""
|
||||
tzinfo = when.tzinfo if isinstance(when, (datetime.datetime, datetime.time)) else None
|
||||
if 1 <= day <= 31:
|
||||
next_dt = self._get_next_month_date(day, day_is_strict, when, allow_now=True)
|
||||
job = Job(callback, repeat=False, context=context, name=name, job_queue=self,
|
||||
is_monthly=True, day_is_strict=day_is_strict, tzinfo=tzinfo)
|
||||
self._put(job, time_spec=next_dt)
|
||||
return job
|
||||
else:
|
||||
raise ValueError("The elements of the 'day' argument should be from 1 up to"
|
||||
" and including 31")
|
||||
|
||||
def _get_next_month_date(self, day, day_is_strict, when, allow_now=False):
|
||||
"""This method returns the date that the next monthly job should be scheduled.
|
||||
|
||||
Args:
|
||||
day (:obj:`int`): The day of the month the job should run.
|
||||
day_is_strict (:obj:`bool`):
|
||||
Specification as to whether the specified day of job should be strictly
|
||||
respected. If day_is_strict is ``True`` it ignores months whereby the
|
||||
specified date does not exist (e.g February 31st). If it set to ``False``,
|
||||
it returns the last valid date of the month instead. For example,
|
||||
if the user runs a job on the 31st of every month, and sets
|
||||
the day_is_strict variable to ``False``, April, for example,
|
||||
the job would run on April 30th.
|
||||
when (:obj:`datetime.time`): Time of day at which the job should run. If the
|
||||
timezone (``time.tzinfo``) is ``None``, UTC will be assumed.
|
||||
allow_now (:obj:`bool`): Whether executing the job right now is a feasible options.
|
||||
For stability reasons, this defaults to :obj:`False`, but it needs to be :obj:`True`
|
||||
on initializing a job.
|
||||
|
||||
"""
|
||||
dt = datetime.datetime.now(tz=when.tzinfo or datetime.timezone.utc)
|
||||
dt_time = dt.time().replace(tzinfo=when.tzinfo)
|
||||
days_in_current_month = calendar.monthrange(dt.year, dt.month)[1]
|
||||
days_till_months_end = days_in_current_month - dt.day
|
||||
if days_in_current_month < day:
|
||||
# if the day does not exist in the current month (e.g Feb 31st)
|
||||
if day_is_strict is False:
|
||||
# set day as last day of month instead
|
||||
next_dt = dt + datetime.timedelta(days=days_till_months_end)
|
||||
else:
|
||||
# else set as day in subsequent month. Subsequent month is
|
||||
# guaranteed to have the date, if current month does not have the date.
|
||||
next_dt = dt + datetime.timedelta(days=days_till_months_end + day)
|
||||
else:
|
||||
# if the day exists in the current month
|
||||
if dt.day < day:
|
||||
# day is upcoming
|
||||
next_dt = dt + datetime.timedelta(day - dt.day)
|
||||
elif dt.day > day or (dt.day == day and ((not allow_now and dt_time >= when)
|
||||
or (allow_now and dt_time > when))):
|
||||
# run next month if day has already passed
|
||||
next_year = dt.year + 1 if dt.month == 12 else dt.year
|
||||
next_month = 1 if dt.month == 12 else dt.month + 1
|
||||
days_in_next_month = calendar.monthrange(next_year, next_month)[1]
|
||||
next_month_has_date = days_in_next_month >= day
|
||||
if next_month_has_date:
|
||||
next_dt = dt + datetime.timedelta(days=days_till_months_end + day)
|
||||
elif day_is_strict:
|
||||
# schedule the subsequent month if day is strict
|
||||
next_dt = dt + datetime.timedelta(
|
||||
days=days_till_months_end + days_in_next_month + day)
|
||||
else:
|
||||
# schedule in the next month last date if day is not strict
|
||||
next_dt = dt + datetime.timedelta(days=days_till_months_end
|
||||
+ days_in_next_month)
|
||||
|
||||
else:
|
||||
# day is today but time has not yet come
|
||||
next_dt = dt
|
||||
|
||||
# Set the correct time
|
||||
next_dt = next_dt.replace(hour=when.hour, minute=when.minute, second=when.second,
|
||||
microsecond=when.microsecond)
|
||||
# fold is new in Py3.6
|
||||
if hasattr(next_dt, 'fold'):
|
||||
next_dt = next_dt.replace(fold=when.fold)
|
||||
return next_dt
|
||||
|
||||
def run_daily(self, callback, time, days=Days.EVERY_DAY, context=None, name=None):
|
||||
"""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 ``None``, UTC will be assumed.
|
||||
``time.tzinfo`` will implicitly define ``Job.tzinfo``.
|
||||
days (Tuple[:obj:`int`], optional): Defines on which days of the week the job should
|
||||
run. Defaults to ``EVERY_DAY``
|
||||
context (:obj:`object`, optional): Additional data needed for the callback function.
|
||||
@@ -207,7 +354,7 @@ class JobQueue(object):
|
||||
:class:`telegram.ext.Job`: The new ``Job`` instance that has been added to the job
|
||||
queue.
|
||||
|
||||
Notes:
|
||||
Note:
|
||||
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.
|
||||
@@ -217,10 +364,11 @@ class JobQueue(object):
|
||||
interval=datetime.timedelta(days=1),
|
||||
repeat=True,
|
||||
days=days,
|
||||
tzinfo=time.tzinfo,
|
||||
context=context,
|
||||
name=name,
|
||||
job_queue=self)
|
||||
self._put(job, next_t=time)
|
||||
self._put(job, time_spec=time)
|
||||
return job
|
||||
|
||||
def _set_next_peek(self, t):
|
||||
@@ -264,10 +412,11 @@ class JobQueue(object):
|
||||
|
||||
if job.enabled:
|
||||
try:
|
||||
current_week_day = datetime.datetime.now().weekday()
|
||||
if any(day == current_week_day for day in job.days):
|
||||
current_week_day = datetime.datetime.now(job.tzinfo).date().weekday()
|
||||
if current_week_day in job.days:
|
||||
self.logger.debug('Running job %s', job.name)
|
||||
job.run(self._dispatcher)
|
||||
self._dispatcher.update_persistence()
|
||||
|
||||
except Exception:
|
||||
self.logger.exception('An uncaught error was raised while executing job %s',
|
||||
@@ -276,8 +425,14 @@ class JobQueue(object):
|
||||
self.logger.debug('Skipping disabled job %s', job.name)
|
||||
|
||||
if job.repeat and not job.removed:
|
||||
self._put(job, last_t=t)
|
||||
self._put(job, previous_t=t)
|
||||
elif job.is_monthly and not job.removed:
|
||||
dt = datetime.datetime.now(tz=job.tzinfo)
|
||||
dt_time = dt.time().replace(tzinfo=job.tzinfo)
|
||||
self._put(job, time_spec=self._get_next_month_date(dt.day, job.day_is_strict,
|
||||
dt_time))
|
||||
else:
|
||||
job._set_next_t(None)
|
||||
self.logger.debug('Dropping non-repeating or removed job %s', job.name)
|
||||
|
||||
def start(self):
|
||||
@@ -337,23 +492,29 @@ class JobQueue(object):
|
||||
return tuple(job[1] for job in self._queue.queue if job and job[1].name == name)
|
||||
|
||||
|
||||
class Job(object):
|
||||
class Job:
|
||||
"""This class encapsulates a Job.
|
||||
|
||||
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.
|
||||
is_monthly (:obj: `bool`): Optional. Indicates whether it is a monthly job.
|
||||
day_is_strict (:obj: `bool`): Optional. Indicates whether the monthly jobs day is strict.
|
||||
|
||||
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.
|
||||
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.
|
||||
interval (:obj:`int` | :obj:`float` | :obj:`datetime.timedelta`, optional): The time
|
||||
interval between executions of the job. 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:`time_spec` 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
|
||||
@@ -363,7 +524,14 @@ class Job(object):
|
||||
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()``.
|
||||
|
||||
tzinfo (:obj:`datetime.tzinfo`, optional): timezone associated to this job. Used when
|
||||
checking the day of the week to determine whether a job should run (only relevant when
|
||||
``days is not Days.EVERY_DAY``). Defaults to UTC.
|
||||
is_monthly (:obj:`bool`, optional): If this job is supposed to be a monthly scheduled job.
|
||||
Defaults to ``False``.
|
||||
day_is_strict (:obj:`bool`, optional): If ``False`` and day > month.days, will pick the
|
||||
last day in the month. Defaults to ``True``. Only relevant when ``is_monthly`` is
|
||||
``True``.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
@@ -373,19 +541,26 @@ class Job(object):
|
||||
context=None,
|
||||
days=Days.EVERY_DAY,
|
||||
name=None,
|
||||
job_queue=None):
|
||||
job_queue=None,
|
||||
tzinfo=None,
|
||||
is_monthly=False,
|
||||
day_is_strict=True):
|
||||
|
||||
self.callback = callback
|
||||
self.context = context
|
||||
self.name = name or callback.__name__
|
||||
|
||||
self._repeat = repeat
|
||||
self._repeat = None
|
||||
self._interval = None
|
||||
self.interval = interval
|
||||
self._next_t = None
|
||||
self.repeat = repeat
|
||||
self.is_monthly = is_monthly
|
||||
self.day_is_strict = day_is_strict
|
||||
|
||||
self._days = None
|
||||
self.days = days
|
||||
self.tzinfo = tzinfo or datetime.timezone.utc
|
||||
|
||||
self._job_queue = weakref.proxy(job_queue) if job_queue is not None else None
|
||||
|
||||
@@ -407,6 +582,7 @@ class Job(object):
|
||||
|
||||
"""
|
||||
self._remove.set()
|
||||
self._next_t = None
|
||||
|
||||
@property
|
||||
def removed(self):
|
||||
@@ -440,8 +616,8 @@ class Job(object):
|
||||
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'")
|
||||
raise TypeError("The 'interval' must be of type 'datetime.timedelta',"
|
||||
" 'int' or 'float'")
|
||||
|
||||
self._interval = interval
|
||||
|
||||
@@ -454,6 +630,27 @@ class Job(object):
|
||||
else:
|
||||
return interval
|
||||
|
||||
@property
|
||||
def next_t(self):
|
||||
"""
|
||||
: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 ``None``.
|
||||
|
||||
"""
|
||||
return datetime.datetime.fromtimestamp(self._next_t, self.tzinfo) if self._next_t else None
|
||||
|
||||
def _set_next_t(self, next_t):
|
||||
if isinstance(next_t, datetime.datetime):
|
||||
# Set timezone to UTC in case datetime is in local timezone.
|
||||
next_t = next_t.astimezone(datetime.timezone.utc)
|
||||
next_t = to_float_timestamp(next_t)
|
||||
elif not (isinstance(next_t, Number) or next_t is None):
|
||||
raise TypeError("The 'next_t' argument should be one of the following types: "
|
||||
"'float', 'int', 'datetime.datetime' or 'NoneType'")
|
||||
|
||||
self._next_t = next_t
|
||||
|
||||
@property
|
||||
def repeat(self):
|
||||
""":obj:`bool`: Optional. If this job should periodically execute its callback function."""
|
||||
@@ -473,10 +670,10 @@ class Job(object):
|
||||
@days.setter
|
||||
def days(self, days):
|
||||
if not isinstance(days, tuple):
|
||||
raise ValueError("The 'days' argument should be of type 'tuple'")
|
||||
raise TypeError("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'")
|
||||
raise TypeError("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 "
|
||||
|
||||
@@ -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-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -117,7 +117,7 @@ class MessageHandler(Handler):
|
||||
channel_post_updates=None,
|
||||
edited_updates=None):
|
||||
|
||||
super(MessageHandler, self).__init__(
|
||||
super().__init__(
|
||||
callback,
|
||||
pass_update_queue=pass_update_queue,
|
||||
pass_job_queue=pass_job_queue,
|
||||
|
||||
@@ -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-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -23,24 +23,9 @@
|
||||
from telegram.utils import promise
|
||||
|
||||
import functools
|
||||
import sys
|
||||
import time
|
||||
import threading
|
||||
if sys.version_info.major > 2:
|
||||
import queue as q
|
||||
else:
|
||||
import Queue as q
|
||||
|
||||
# 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
|
||||
import queue as q
|
||||
|
||||
|
||||
class DelayQueueError(RuntimeError):
|
||||
@@ -95,11 +80,11 @@ class DelayQueue(threading.Thread):
|
||||
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 = '{}-{}'.format(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):
|
||||
"""
|
||||
@@ -114,7 +99,7 @@ class DelayQueue(threading.Thread):
|
||||
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
|
||||
@@ -146,7 +131,7 @@ class DelayQueue(threading.Thread):
|
||||
|
||||
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):
|
||||
@@ -180,7 +165,7 @@ class DelayQueue(threading.Thread):
|
||||
# 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.
|
||||
@@ -249,7 +234,7 @@ class MessageQueue(object):
|
||||
``DelayQueue`` (if set to ``False``), resulting in needed delays to avoid
|
||||
hitting specified limits. Defaults to ``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
|
||||
|
||||
@@ -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-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -34,6 +34,8 @@ class PicklePersistence(BasePersistence):
|
||||
persistence class.
|
||||
store_chat_data (:obj:`bool`): Optional. Whether user_data should be saved by this
|
||||
persistence class.
|
||||
store_bot_data (:obj:`bool`): Optional. Whether bot_data should be saved by this
|
||||
persistence class.
|
||||
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``.
|
||||
@@ -48,6 +50,8 @@ class PicklePersistence(BasePersistence):
|
||||
persistence class. Default is ``True``.
|
||||
store_chat_data (:obj:`bool`, optional): Whether user_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. 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``.
|
||||
@@ -56,29 +60,38 @@ class PicklePersistence(BasePersistence):
|
||||
on any transaction *and* on call fo :meth:`flush`. Default is ``False``.
|
||||
"""
|
||||
|
||||
def __init__(self, filename, store_user_data=True, store_chat_data=True, single_file=True,
|
||||
def __init__(self, filename,
|
||||
store_user_data=True,
|
||||
store_chat_data=True,
|
||||
store_bot_data=True,
|
||||
single_file=True,
|
||||
on_flush=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.bot_data = None
|
||||
self.conversations = None
|
||||
|
||||
def load_singlefile(self):
|
||||
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']
|
||||
data = pickle.load(f)
|
||||
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 IOError:
|
||||
self.conversations = {}
|
||||
self.user_data = defaultdict(dict)
|
||||
self.chat_data = defaultdict(dict)
|
||||
self.bot_data = {}
|
||||
except pickle.UnpicklingError:
|
||||
raise TypeError("File {} does not contain valid pickle data".format(filename))
|
||||
except Exception:
|
||||
@@ -97,9 +110,9 @@ class PicklePersistence(BasePersistence):
|
||||
|
||||
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)
|
||||
data = {'conversations': self.conversations, 'user_data': self.user_data,
|
||||
'chat_data': self.chat_data, 'bot_data': self.bot_data}
|
||||
pickle.dump(data, f)
|
||||
|
||||
def dump_file(self, filename, data):
|
||||
with open(filename, "wb") as f:
|
||||
@@ -145,6 +158,24 @@ class PicklePersistence(BasePersistence):
|
||||
self.load_singlefile()
|
||||
return deepcopy(self.chat_data)
|
||||
|
||||
def get_bot_data(self):
|
||||
"""Returns the bot_data from the pickle file if it exsists or an empty dict.
|
||||
|
||||
Returns:
|
||||
:obj:`defaultdict`: The restored bot data.
|
||||
"""
|
||||
if self.bot_data:
|
||||
pass
|
||||
elif not self.single_file:
|
||||
filename = "{}_bot_data".format(self.filename)
|
||||
data = self.load_file(filename)
|
||||
if not data:
|
||||
data = {}
|
||||
self.bot_data = data
|
||||
else:
|
||||
self.load_singlefile()
|
||||
return deepcopy(self.bot_data)
|
||||
|
||||
def get_conversations(self, name):
|
||||
"""Returns the conversations from the pickle file if it exsists or an empty defaultdict.
|
||||
|
||||
@@ -193,6 +224,8 @@ class PicklePersistence(BasePersistence):
|
||||
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
|
||||
@@ -211,6 +244,8 @@ class PicklePersistence(BasePersistence):
|
||||
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
|
||||
@@ -221,6 +256,23 @@ class PicklePersistence(BasePersistence):
|
||||
else:
|
||||
self.dump_singlefile()
|
||||
|
||||
def update_bot_data(self, data):
|
||||
"""Will update the bot_data (if changed) 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 = "{}_bot_data".format(self.filename)
|
||||
self.dump_file(filename, self.bot_data)
|
||||
else:
|
||||
self.dump_singlefile()
|
||||
|
||||
def flush(self):
|
||||
""" Will save all data in memory to pickle file(s).
|
||||
"""
|
||||
@@ -232,5 +284,7 @@ class PicklePersistence(BasePersistence):
|
||||
self.dump_file("{}_user_data".format(self.filename), self.user_data)
|
||||
if self.chat_data:
|
||||
self.dump_file("{}_chat_data".format(self.filename), self.chat_data)
|
||||
if self.bot_data:
|
||||
self.dump_file("{}_bot_data".format(self.filename), self.bot_data)
|
||||
if self.conversations:
|
||||
self.dump_file("{}_conversations".format(self.filename), self.conversations)
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2019
|
||||
# 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/].
|
||||
|
||||
from telegram import Update
|
||||
from .handler import Handler
|
||||
|
||||
|
||||
class PollAnswerHandler(Handler):
|
||||
"""Handler class to handle Telegram updates that contain a poll answer.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
"""
|
||||
|
||||
def check_update(self, update):
|
||||
"""Determines whether an update should be passed to this handlers :attr:`callback`.
|
||||
|
||||
Args:
|
||||
update (:class:`telegram.Update`): Incoming telegram update.
|
||||
|
||||
Returns:
|
||||
:obj:`bool`
|
||||
|
||||
"""
|
||||
return isinstance(update, Update) and update.poll_answer
|
||||
@@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2019
|
||||
# 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/].
|
||||
|
||||
from telegram import Update
|
||||
from .handler import Handler
|
||||
|
||||
|
||||
class PollHandler(Handler):
|
||||
"""Handler class to handle Telegram updates that contain a poll.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
"""
|
||||
|
||||
def check_update(self, update):
|
||||
"""Determines whether an update should be passed to this handlers :attr:`callback`.
|
||||
|
||||
Args:
|
||||
update (:class:`telegram.Update`): Incoming telegram update.
|
||||
|
||||
Returns:
|
||||
:obj:`bool`
|
||||
|
||||
"""
|
||||
return isinstance(update, Update) and 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-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -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-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -110,21 +110,20 @@ class RegexHandler(MessageHandler):
|
||||
warnings.warn('RegexHandler is deprecated. See https://git.io/fxJuV for more info',
|
||||
TelegramDeprecationWarning,
|
||||
stacklevel=2)
|
||||
super(RegexHandler, self).__init__(Filters.regex(pattern),
|
||||
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,
|
||||
message_updates=message_updates,
|
||||
channel_post_updates=channel_post_updates,
|
||||
edited_updates=edited_updates)
|
||||
super().__init__(Filters.regex(pattern),
|
||||
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,
|
||||
message_updates=message_updates,
|
||||
channel_post_updates=channel_post_updates,
|
||||
edited_updates=edited_updates)
|
||||
self.pass_groups = pass_groups
|
||||
self.pass_groupdict = pass_groupdict
|
||||
|
||||
def collect_optional_args(self, dispatcher, update=None, check_result=None):
|
||||
optional_args = super(RegexHandler, self).collect_optional_args(dispatcher, update,
|
||||
check_result)
|
||||
optional_args = super().collect_optional_args(dispatcher, update, check_result)
|
||||
if self.pass_groups:
|
||||
optional_args['groups'] = check_result['matches'][0].groups()
|
||||
if self.pass_groupdict:
|
||||
|
||||
@@ -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-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -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-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -18,8 +18,6 @@
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains the StringCommandHandler class."""
|
||||
|
||||
from future.utils import string_types
|
||||
|
||||
from .handler import Handler
|
||||
|
||||
|
||||
@@ -73,7 +71,7 @@ class StringCommandHandler(Handler):
|
||||
pass_args=False,
|
||||
pass_update_queue=False,
|
||||
pass_job_queue=False):
|
||||
super(StringCommandHandler, self).__init__(
|
||||
super().__init__(
|
||||
callback,
|
||||
pass_update_queue=pass_update_queue,
|
||||
pass_job_queue=pass_job_queue)
|
||||
@@ -90,15 +88,13 @@ class StringCommandHandler(Handler):
|
||||
:obj:`bool`
|
||||
|
||||
"""
|
||||
if isinstance(update, string_types) and update.startswith('/'):
|
||||
if isinstance(update, str) and update.startswith('/'):
|
||||
args = update[1:].split(' ')
|
||||
if args[0] == self.command:
|
||||
return args[1:]
|
||||
|
||||
def collect_optional_args(self, dispatcher, update=None, check_result=None):
|
||||
optional_args = super(StringCommandHandler, self).collect_optional_args(dispatcher,
|
||||
update,
|
||||
check_result)
|
||||
optional_args = super().collect_optional_args(dispatcher, update, check_result)
|
||||
if self.pass_args:
|
||||
optional_args['args'] = check_result
|
||||
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-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -20,8 +20,6 @@
|
||||
|
||||
import re
|
||||
|
||||
from future.utils import string_types
|
||||
|
||||
from .handler import Handler
|
||||
|
||||
|
||||
@@ -85,12 +83,12 @@ class StringRegexHandler(Handler):
|
||||
pass_groupdict=False,
|
||||
pass_update_queue=False,
|
||||
pass_job_queue=False):
|
||||
super(StringRegexHandler, self).__init__(
|
||||
super().__init__(
|
||||
callback,
|
||||
pass_update_queue=pass_update_queue,
|
||||
pass_job_queue=pass_job_queue)
|
||||
|
||||
if isinstance(pattern, string_types):
|
||||
if isinstance(pattern, str):
|
||||
pattern = re.compile(pattern)
|
||||
|
||||
self.pattern = pattern
|
||||
@@ -107,14 +105,13 @@ class StringRegexHandler(Handler):
|
||||
:obj:`bool`
|
||||
|
||||
"""
|
||||
if isinstance(update, string_types):
|
||||
if isinstance(update, str):
|
||||
match = re.match(self.pattern, update)
|
||||
if match:
|
||||
return match
|
||||
|
||||
def collect_optional_args(self, dispatcher, update=None, check_result=None):
|
||||
optional_args = super(StringRegexHandler, self).collect_optional_args(dispatcher,
|
||||
update, check_result)
|
||||
optional_args = super().collect_optional_args(dispatcher, update, check_result)
|
||||
if self.pattern:
|
||||
if self.pass_groups:
|
||||
optional_args['groups'] = check_result.groups()
|
||||
|
||||
@@ -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-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -65,7 +65,7 @@ class TypeHandler(Handler):
|
||||
strict=False,
|
||||
pass_update_queue=False,
|
||||
pass_job_queue=False):
|
||||
super(TypeHandler, self).__init__(
|
||||
super().__init__(
|
||||
callback,
|
||||
pass_update_queue=pass_update_queue,
|
||||
pass_job_queue=pass_job_queue)
|
||||
|
||||
+134
-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-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -32,10 +32,8 @@ from telegram.utils.helpers import get_signal_name
|
||||
from telegram.utils.request import Request
|
||||
from telegram.utils.webhookhandler import (WebhookServer, WebhookAppClass)
|
||||
|
||||
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
||||
|
||||
|
||||
class Updater(object):
|
||||
class Updater:
|
||||
"""
|
||||
This class, which employs the :class:`telegram.ext.Dispatcher`, provides a frontend to
|
||||
:class:`telegram.Bot` to the programmer, so they can focus on coding the bot. Its purpose is to
|
||||
@@ -49,7 +47,8 @@ class Updater(object):
|
||||
|
||||
Attributes:
|
||||
bot (:class:`telegram.Bot`): The bot used with this Updater.
|
||||
user_sig_handler (:obj:`signal`): signals the updater will respond to.
|
||||
user_sig_handler (:obj:`function`): Optional. Function to be called when a signal is
|
||||
received.
|
||||
update_queue (:obj:`Queue`): Queue for the updates.
|
||||
job_queue (:class:`telegram.ext.JobQueue`): Jobqueue for the updater.
|
||||
dispatcher (:class:`telegram.ext.Dispatcher`): Dispatcher that handles the updates and
|
||||
@@ -57,33 +56,43 @@ class Updater(object):
|
||||
running (:obj:`bool`): Indicates if the updater is running.
|
||||
persistence (:class:`telegram.ext.BasePersistence`): Optional. The persistence class to
|
||||
store data that should be persistent over restarts.
|
||||
use_context (:obj:`bool`, optional): ``True`` if using context based callbacks.
|
||||
use_context (:obj:`bool`): Optional. ``True`` if using context based callbacks.
|
||||
|
||||
Args:
|
||||
token (:obj:`str`, optional): The bot's token given by the @BotFather.
|
||||
base_url (:obj:`str`, optional): Base_url for the bot.
|
||||
base_file_url (:obj:`str`, optional): Base_file_url for the bot.
|
||||
workers (:obj:`int`, optional): Amount of threads in the thread pool for functions
|
||||
decorated with ``@run_async``.
|
||||
bot (:class:`telegram.Bot`, optional): A pre-initialized bot instance. If a pre-initialized
|
||||
bot is used, it is the user's responsibility to create it using a `Request`
|
||||
instance with a large enough connection pool.
|
||||
decorated with ``@run_async`` (ignored if `dispatcher` argument is used).
|
||||
bot (:class:`telegram.Bot`, optional): A pre-initialized bot instance (ignored if
|
||||
`dispatcher` argument is used). If a pre-initialized bot is used, it is the user's
|
||||
responsibility to create it using a `Request` instance with a large enough connection
|
||||
pool.
|
||||
dispatcher (:class:`telegram.ext.Dispatcher`, optional): A pre-initialized dispatcher
|
||||
instance. If a pre-initialized dispatcher is used, it is the user's responsibility to
|
||||
create it with proper arguments.
|
||||
private_key (:obj:`bytes`, optional): Private key for decryption of telegram passport data.
|
||||
private_key_password (:obj:`bytes`, optional): Password for above private key.
|
||||
user_sig_handler (:obj:`function`, optional): Takes ``signum, frame`` as positional
|
||||
arguments. This will be called when a signal is received, defaults are (SIGINT,
|
||||
SIGTERM, SIGABRT) setable with :attr:`idle`.
|
||||
request_kwargs (:obj:`dict`, optional): Keyword args to control the creation of a
|
||||
`telegram.utils.request.Request` object (ignored if `bot` argument is used). The
|
||||
request_kwargs are very useful for the advanced users who would like to control the
|
||||
default timeouts and/or control the proxy used for http communication.
|
||||
use_context (:obj:`bool`, optional): If set to ``True`` Use the context based callback API.
|
||||
During the deprecation period of the old API the default is ``False``. **New users**:
|
||||
set this to ``True``.
|
||||
`telegram.utils.request.Request` object (ignored if `bot` or `dispatcher` argument is
|
||||
used). The request_kwargs are very useful for the advanced users who would like to
|
||||
control the default timeouts and/or control the proxy used for http communication.
|
||||
use_context (:obj:`bool`, optional): If set to ``True`` Use the context based callback API
|
||||
(ignored if `dispatcher` argument is used). During the deprecation period of the old
|
||||
API the default is ``False``. **New users**: set this to ``True``.
|
||||
persistence (:class:`telegram.ext.BasePersistence`, optional): The persistence class to
|
||||
store data that should be persistent over restarts.
|
||||
store data that should be persistent over restarts (ignored if `dispatcher` argument is
|
||||
used).
|
||||
defaults (:class:`telegram.ext.Defaults`, optional): An object containing default values to
|
||||
be used if not set explicitly in the bot methods.
|
||||
|
||||
Note:
|
||||
You must supply either a :attr:`bot` or a :attr:`token` argument.
|
||||
* You must supply either a :attr:`bot` or a :attr:`token` argument.
|
||||
* If you supply a :attr:`bot`, you will need to pass :attr:`defaults` to *both* the bot and
|
||||
the :class:`telegram.ext.Updater`.
|
||||
|
||||
Raises:
|
||||
ValueError: If both :attr:`token` and :attr:`bot` are passed or none of them.
|
||||
@@ -102,53 +111,85 @@ class Updater(object):
|
||||
user_sig_handler=None,
|
||||
request_kwargs=None,
|
||||
persistence=None,
|
||||
use_context=False):
|
||||
defaults=None,
|
||||
use_context=False,
|
||||
dispatcher=None,
|
||||
base_file_url=None):
|
||||
|
||||
if (token is None) and (bot is None):
|
||||
raise ValueError('`token` or `bot` must be passed')
|
||||
if (token is not None) and (bot is not None):
|
||||
raise ValueError('`token` and `bot` are mutually exclusive')
|
||||
if (private_key is not None) and (bot is not None):
|
||||
raise ValueError('`bot` and `private_key` are mutually exclusive')
|
||||
if dispatcher is None:
|
||||
if (token is None) and (bot is None):
|
||||
raise ValueError('`token` or `bot` must be passed')
|
||||
if (token is not None) and (bot is not None):
|
||||
raise ValueError('`token` and `bot` are mutually exclusive')
|
||||
if (private_key is not None) and (bot is not None):
|
||||
raise ValueError('`bot` and `private_key` are mutually exclusive')
|
||||
else:
|
||||
if bot is not None:
|
||||
raise ValueError('`dispatcher` and `bot` are mutually exclusive')
|
||||
if persistence is not None:
|
||||
raise ValueError('`dispatcher` and `persistence` are mutually exclusive')
|
||||
if workers is not None:
|
||||
raise ValueError('`dispatcher` and `workers` are mutually exclusive')
|
||||
if use_context != dispatcher.use_context:
|
||||
raise ValueError('`dispatcher` and `use_context` are mutually exclusive')
|
||||
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
||||
con_pool_size = workers + 4
|
||||
if dispatcher is None:
|
||||
con_pool_size = workers + 4
|
||||
|
||||
if bot is not None:
|
||||
self.bot = bot
|
||||
if bot.request.con_pool_size < con_pool_size:
|
||||
if bot is not None:
|
||||
self.bot = bot
|
||||
if bot.request.con_pool_size < con_pool_size:
|
||||
self.logger.warning(
|
||||
'Connection pool of Request object is smaller than optimal value (%s)',
|
||||
con_pool_size)
|
||||
else:
|
||||
# we need a connection pool the size of:
|
||||
# * for each of the workers
|
||||
# * 1 for Dispatcher
|
||||
# * 1 for polling Updater (even if webhook is used, we can spare a connection)
|
||||
# * 1 for JobQueue
|
||||
# * 1 for main thread
|
||||
if request_kwargs is None:
|
||||
request_kwargs = {}
|
||||
if 'con_pool_size' not in request_kwargs:
|
||||
request_kwargs['con_pool_size'] = con_pool_size
|
||||
self._request = Request(**request_kwargs)
|
||||
self.bot = Bot(token,
|
||||
base_url,
|
||||
base_file_url=base_file_url,
|
||||
request=self._request,
|
||||
private_key=private_key,
|
||||
private_key_password=private_key_password,
|
||||
defaults=defaults)
|
||||
self.update_queue = Queue()
|
||||
self.job_queue = JobQueue()
|
||||
self.__exception_event = Event()
|
||||
self.persistence = persistence
|
||||
self.dispatcher = Dispatcher(self.bot,
|
||||
self.update_queue,
|
||||
job_queue=self.job_queue,
|
||||
workers=workers,
|
||||
exception_event=self.__exception_event,
|
||||
persistence=persistence,
|
||||
use_context=use_context)
|
||||
self.job_queue.set_dispatcher(self.dispatcher)
|
||||
else:
|
||||
con_pool_size = dispatcher.workers + 4
|
||||
|
||||
self.bot = dispatcher.bot
|
||||
if self.bot.request.con_pool_size < con_pool_size:
|
||||
self.logger.warning(
|
||||
'Connection pool of Request object is smaller than optimal value (%s)',
|
||||
con_pool_size)
|
||||
else:
|
||||
# we need a connection pool the size of:
|
||||
# * for each of the workers
|
||||
# * 1 for Dispatcher
|
||||
# * 1 for polling Updater (even if webhook is used, we can spare a connection)
|
||||
# * 1 for JobQueue
|
||||
# * 1 for main thread
|
||||
if request_kwargs is None:
|
||||
request_kwargs = {}
|
||||
if 'con_pool_size' not in request_kwargs:
|
||||
request_kwargs['con_pool_size'] = con_pool_size
|
||||
self._request = Request(**request_kwargs)
|
||||
self.bot = Bot(token, base_url, request=self._request, private_key=private_key,
|
||||
private_key_password=private_key_password)
|
||||
self.update_queue = dispatcher.update_queue
|
||||
self.__exception_event = dispatcher.exception_event
|
||||
self.persistence = dispatcher.persistence
|
||||
self.job_queue = dispatcher.job_queue
|
||||
self.dispatcher = dispatcher
|
||||
|
||||
self.user_sig_handler = user_sig_handler
|
||||
self.update_queue = Queue()
|
||||
self.job_queue = JobQueue()
|
||||
self.__exception_event = Event()
|
||||
self.persistence = persistence
|
||||
self.dispatcher = Dispatcher(
|
||||
self.bot,
|
||||
self.update_queue,
|
||||
job_queue=self.job_queue,
|
||||
workers=workers,
|
||||
exception_event=self.__exception_event,
|
||||
persistence=persistence,
|
||||
use_context=use_context)
|
||||
self.job_queue.set_dispatcher(self.dispatcher)
|
||||
self.last_update_id = 0
|
||||
self.running = False
|
||||
self.is_idle = False
|
||||
@@ -156,22 +197,27 @@ class Updater(object):
|
||||
self.__lock = Lock()
|
||||
self.__threads = []
|
||||
|
||||
# Just for passing to WebhookAppClass
|
||||
self._default_quote = defaults.quote if defaults else None
|
||||
|
||||
def _init_thread(self, target, name, *args, **kwargs):
|
||||
thr = Thread(target=self._thread_wrapper, name="Bot:{}:{}".format(self.bot.id, name),
|
||||
args=(target,) + args, kwargs=kwargs)
|
||||
thr = Thread(target=self._thread_wrapper,
|
||||
name="Bot:{}:{}".format(self.bot.id, name),
|
||||
args=(target,) + args,
|
||||
kwargs=kwargs)
|
||||
thr.start()
|
||||
self.__threads.append(thr)
|
||||
|
||||
def _thread_wrapper(self, target, *args, **kwargs):
|
||||
thr_name = current_thread().name
|
||||
self.logger.debug('{0} - started'.format(thr_name))
|
||||
self.logger.debug('{} - started'.format(thr_name))
|
||||
try:
|
||||
target(*args, **kwargs)
|
||||
except Exception:
|
||||
self.__exception_event.set()
|
||||
self.logger.exception('unhandled exception in %s', thr_name)
|
||||
raise
|
||||
self.logger.debug('{0} - ended'.format(thr_name))
|
||||
self.logger.debug('{} - ended'.format(thr_name))
|
||||
|
||||
def start_polling(self,
|
||||
poll_interval=0.0,
|
||||
@@ -288,9 +334,10 @@ class Updater(object):
|
||||
self.logger.debug('Bootstrap done')
|
||||
|
||||
def polling_action_cb():
|
||||
updates = self.bot.get_updates(
|
||||
self.last_update_id, timeout=timeout, read_latency=read_latency,
|
||||
allowed_updates=allowed_updates)
|
||||
updates = self.bot.get_updates(self.last_update_id,
|
||||
timeout=timeout,
|
||||
read_latency=read_latency,
|
||||
allowed_updates=allowed_updates)
|
||||
|
||||
if updates:
|
||||
if not self.running:
|
||||
@@ -367,10 +414,11 @@ class Updater(object):
|
||||
self.logger.debug('Updater thread started (webhook)')
|
||||
use_ssl = cert is not None and key is not None
|
||||
if not url_path.startswith('/'):
|
||||
url_path = '/{0}'.format(url_path)
|
||||
url_path = '/{}'.format(url_path)
|
||||
|
||||
# Create Tornado app instance
|
||||
app = WebhookAppClass(url_path, self.bot, self.update_queue)
|
||||
app = WebhookAppClass(url_path, self.bot, self.update_queue,
|
||||
default_quote=self._default_quote)
|
||||
|
||||
# Form SSL Context
|
||||
# An SSLError is raised if the private key does not match with the certificate
|
||||
@@ -391,12 +439,11 @@ class Updater(object):
|
||||
if not webhook_url:
|
||||
webhook_url = self._gen_webhook_url(listen, port, url_path)
|
||||
|
||||
self._bootstrap(
|
||||
max_retries=bootstrap_retries,
|
||||
clean=clean,
|
||||
webhook_url=webhook_url,
|
||||
cert=open(cert, 'rb'),
|
||||
allowed_updates=allowed_updates)
|
||||
self._bootstrap(max_retries=bootstrap_retries,
|
||||
clean=clean,
|
||||
webhook_url=webhook_url,
|
||||
cert=open(cert, 'rb'),
|
||||
allowed_updates=allowed_updates)
|
||||
elif clean:
|
||||
self.logger.warning("cleaning updates is not supported if "
|
||||
"SSL-termination happens elsewhere; skipping")
|
||||
@@ -407,7 +454,12 @@ class Updater(object):
|
||||
def _gen_webhook_url(listen, port, url_path):
|
||||
return 'https://{listen}:{port}{path}'.format(listen=listen, port=port, path=url_path)
|
||||
|
||||
def _bootstrap(self, max_retries, clean, webhook_url, allowed_updates, cert=None,
|
||||
def _bootstrap(self,
|
||||
max_retries,
|
||||
clean,
|
||||
webhook_url,
|
||||
allowed_updates,
|
||||
cert=None,
|
||||
bootstrap_interval=5):
|
||||
retries = [0]
|
||||
|
||||
@@ -423,15 +475,16 @@ class Updater(object):
|
||||
return False
|
||||
|
||||
def bootstrap_set_webhook():
|
||||
self.bot.set_webhook(
|
||||
url=webhook_url, certificate=cert, allowed_updates=allowed_updates)
|
||||
self.bot.set_webhook(url=webhook_url,
|
||||
certificate=cert,
|
||||
allowed_updates=allowed_updates)
|
||||
return False
|
||||
|
||||
def bootstrap_onerr_cb(exc):
|
||||
if not isinstance(exc, Unauthorized) and (max_retries < 0 or retries[0] < max_retries):
|
||||
retries[0] += 1
|
||||
self.logger.warning('Failed bootstrap phase; try=%s max_retries=%s',
|
||||
retries[0], max_retries)
|
||||
self.logger.warning('Failed bootstrap phase; try=%s max_retries=%s', retries[0],
|
||||
max_retries)
|
||||
else:
|
||||
self.logger.error('Failed bootstrap phase after %s retries (%s)', retries[0], exc)
|
||||
raise exc
|
||||
@@ -490,9 +543,9 @@ class Updater(object):
|
||||
|
||||
def _join_threads(self):
|
||||
for thr in self.__threads:
|
||||
self.logger.debug('Waiting for {0} thread to end'.format(thr.name))
|
||||
self.logger.debug('Waiting for {} thread to end'.format(thr.name))
|
||||
thr.join()
|
||||
self.logger.debug('{0} thread has ended'.format(thr.name))
|
||||
self.logger.debug('{} thread has ended'.format(thr.name))
|
||||
self.__threads = []
|
||||
|
||||
def signal_handler(self, signum, frame):
|
||||
@@ -516,9 +569,9 @@ class Updater(object):
|
||||
"""Blocks until one of the signals are received and stops the updater.
|
||||
|
||||
Args:
|
||||
stop_signals (:obj:`iterable`): Iterable containing signals from the signal module that
|
||||
should be subscribed to. Updater.stop() will be called on receiving one of those
|
||||
signals. Defaults to (``SIGINT``, ``SIGTERM``, ``SIGABRT``).
|
||||
stop_signals (:obj:`list` | :obj:`tuple`): List containing signals from the signal
|
||||
module that should be subscribed to. Updater.stop() will be called on receiving one
|
||||
of those signals. Defaults to (``SIGINT``, ``SIGTERM``, ``SIGABRT``).
|
||||
|
||||
"""
|
||||
for sig in stop_signals:
|
||||
|
||||
@@ -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-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -25,7 +25,10 @@ class Animation(TelegramObject):
|
||||
"""This object represents an animation file to be displayed in the message containing a game.
|
||||
|
||||
Attributes:
|
||||
file_id (:obj:`str`): Unique file identifier.
|
||||
file_id (:obj:`str`): File identifier.
|
||||
file_unique_id (:obj:`str`): Unique identifier for this file, which
|
||||
is supposed to be the same over time and for different bots.
|
||||
Can't be used to download or reuse the file.
|
||||
width (:obj:`int`): Video width as defined by sender.
|
||||
height (:obj:`int`): Video height as defined by sender.
|
||||
duration (:obj:`int`): Duration of the video in seconds as defined by sender.
|
||||
@@ -37,7 +40,10 @@ class Animation(TelegramObject):
|
||||
bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods.
|
||||
|
||||
Args:
|
||||
file_id (:obj:`str`): Unique file identifier.
|
||||
file_id (:obj:`str`): Identifier for this file, which can be used to download
|
||||
or reuse the file.
|
||||
file_unique_id (:obj:`str`): Unique and the same over time and
|
||||
for different bots file identifier.
|
||||
width (:obj:`int`): Video width as defined by sender.
|
||||
height (:obj:`int`): Video height as defined by sender.
|
||||
duration (:obj:`int`): Duration of the video in seconds as defined by sender.
|
||||
@@ -52,6 +58,7 @@ class Animation(TelegramObject):
|
||||
|
||||
def __init__(self,
|
||||
file_id,
|
||||
file_unique_id,
|
||||
width,
|
||||
height,
|
||||
duration,
|
||||
@@ -63,6 +70,7 @@ class Animation(TelegramObject):
|
||||
**kwargs):
|
||||
# Required
|
||||
self.file_id = str(file_id)
|
||||
self.file_unique_id = str(file_unique_id)
|
||||
self.width = int(width)
|
||||
self.height = int(height)
|
||||
self.duration = duration
|
||||
@@ -73,14 +81,14 @@ class Animation(TelegramObject):
|
||||
self.file_size = file_size
|
||||
self.bot = bot
|
||||
|
||||
self._id_attrs = (self.file_id,)
|
||||
self._id_attrs = (self.file_unique_id,)
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data, bot):
|
||||
if not data:
|
||||
return None
|
||||
|
||||
data = super(Animation, cls).de_json(data, bot)
|
||||
data = super().de_json(data, bot)
|
||||
|
||||
data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot)
|
||||
|
||||
|
||||
+11
-3
@@ -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-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -26,6 +26,9 @@ class Audio(TelegramObject):
|
||||
|
||||
Attributes:
|
||||
file_id (:obj:`str`): Unique identifier for this file.
|
||||
file_unique_id (:obj:`str`): Unique identifier for this file, which
|
||||
is supposed to be the same over time and for different bots.
|
||||
Can't be used to download or reuse the file.
|
||||
duration (:obj:`int`): Duration of the audio in seconds.
|
||||
performer (:obj:`str`): Optional. Performer of the audio as defined by sender or by audio
|
||||
tags.
|
||||
@@ -37,7 +40,10 @@ class Audio(TelegramObject):
|
||||
bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods.
|
||||
|
||||
Args:
|
||||
file_id (:obj:`str`): Unique identifier for this file.
|
||||
file_id (:obj:`str`): Identifier for this file, which can be used to download
|
||||
or reuse the file.
|
||||
file_unique_id (:obj:`str`): Unique and the same over time and
|
||||
for different bots file identifier.
|
||||
duration (:obj:`int`): Duration of the audio in seconds as defined by sender.
|
||||
performer (:obj:`str`, optional): Performer of the audio as defined by sender or by audio
|
||||
tags.
|
||||
@@ -53,6 +59,7 @@ class Audio(TelegramObject):
|
||||
|
||||
def __init__(self,
|
||||
file_id,
|
||||
file_unique_id,
|
||||
duration,
|
||||
performer=None,
|
||||
title=None,
|
||||
@@ -63,6 +70,7 @@ class Audio(TelegramObject):
|
||||
**kwargs):
|
||||
# Required
|
||||
self.file_id = str(file_id)
|
||||
self.file_unique_id = str(file_unique_id)
|
||||
self.duration = int(duration)
|
||||
# Optionals
|
||||
self.performer = performer
|
||||
@@ -72,7 +80,7 @@ class Audio(TelegramObject):
|
||||
self.thumb = thumb
|
||||
self.bot = bot
|
||||
|
||||
self._id_attrs = (self.file_id,)
|
||||
self._id_attrs = (self.file_unique_id,)
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data, bot):
|
||||
|
||||
@@ -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-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -25,24 +25,48 @@ class ChatPhoto(TelegramObject):
|
||||
|
||||
Attributes:
|
||||
small_file_id (:obj:`str`): File identifier of small (160x160) chat photo.
|
||||
This file_id can be used only for photo download and only for as long
|
||||
as the photo is not changed.
|
||||
small_file_unique_id (:obj:`str`): Unique file identifier of small (160x160) chat photo,
|
||||
which is supposed to be the same over time and for different bots.
|
||||
Can't be used to download or reuse the file.
|
||||
big_file_id (:obj:`str`): File identifier of big (640x640) chat photo.
|
||||
|
||||
This file_id can be used only for photo download and only for as long as
|
||||
the photo is not changed.
|
||||
big_file_unique_id (:obj:`str`): Unique file identifier of big (640x640) chat photo,
|
||||
which is supposed to be the same over time and for different bots.
|
||||
Can't be used to download or reuse the file.
|
||||
Args:
|
||||
small_file_id (:obj:`str`): File identifier of small (160x160) chat photo. This file_id can
|
||||
be used only for photo download and only for as long as the photo is not changed.
|
||||
big_file_id (:obj:`str`): File identifier of big (640x640) chat photo. This file_id can be
|
||||
used only for photo download and only for as long as the photo is not changed.
|
||||
small_file_id (:obj:`str`): Unique file identifier of small (160x160) chat photo. This
|
||||
file_id can be used only for photo download and only for as long
|
||||
as the photo is not changed.
|
||||
small_file_unique_id (:obj:`str`): Unique file identifier of small (160x160) chat photo,
|
||||
which is supposed to be the same over time and for different bots.
|
||||
Can't be used to download or reuse the file.
|
||||
big_file_id (:obj:`str`): Unique file identifier of big (640x640) chat photo. This file_id
|
||||
can be used only for photo download and only for as long as the photo is not changed.
|
||||
big_file_unique_id (:obj:`str`): Unique file identifier of big (640x640) chat photo,
|
||||
which is supposed to be the same over time and for different bots.
|
||||
Can't be used to download or reuse the file.
|
||||
bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods
|
||||
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, small_file_id, big_file_id, bot=None, **kwargs):
|
||||
def __init__(self,
|
||||
small_file_id,
|
||||
small_file_unique_id,
|
||||
big_file_id,
|
||||
big_file_unique_id,
|
||||
bot=None, **kwargs):
|
||||
self.small_file_id = small_file_id
|
||||
self.small_file_unique_id = small_file_unique_id
|
||||
self.big_file_id = big_file_id
|
||||
self.big_file_unique_id = big_file_unique_id
|
||||
|
||||
self.bot = bot
|
||||
|
||||
self._id_attrs = (self.small_file_id, self.big_file_id)
|
||||
self._id_attrs = (self.small_file_unique_id, self.big_file_unique_id,)
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data, bot):
|
||||
|
||||
@@ -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-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -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-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -26,6 +26,9 @@ class Document(TelegramObject):
|
||||
|
||||
Attributes:
|
||||
file_id (:obj:`str`): Unique file identifier.
|
||||
file_unique_id (:obj:`str`): Unique identifier for this file, which
|
||||
is supposed to be the same over time and for different bots.
|
||||
Can't be used to download or reuse the file.
|
||||
thumb (:class:`telegram.PhotoSize`): Optional. Document thumbnail.
|
||||
file_name (:obj:`str`): Original filename.
|
||||
mime_type (:obj:`str`): Optional. MIME type of the file.
|
||||
@@ -33,7 +36,10 @@ class Document(TelegramObject):
|
||||
bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods.
|
||||
|
||||
Args:
|
||||
file_id (:obj:`str`): Unique file identifier
|
||||
file_id (:obj:`str`): Identifier for this file, which can be used to download
|
||||
or reuse the file.
|
||||
file_unique_id (:obj:`str`): Unique and the same over time and
|
||||
for different bots file identifier.
|
||||
thumb (:class:`telegram.PhotoSize`, optional): Document thumbnail as defined by sender.
|
||||
file_name (:obj:`str`, optional): Original filename as defined by sender.
|
||||
mime_type (:obj:`str`, optional): MIME type of the file as defined by sender.
|
||||
@@ -46,6 +52,7 @@ class Document(TelegramObject):
|
||||
|
||||
def __init__(self,
|
||||
file_id,
|
||||
file_unique_id,
|
||||
thumb=None,
|
||||
file_name=None,
|
||||
mime_type=None,
|
||||
@@ -54,6 +61,7 @@ class Document(TelegramObject):
|
||||
**kwargs):
|
||||
# Required
|
||||
self.file_id = str(file_id)
|
||||
self.file_unique_id = str(file_unique_id)
|
||||
# Optionals
|
||||
self.thumb = thumb
|
||||
self.file_name = file_name
|
||||
@@ -61,14 +69,14 @@ class Document(TelegramObject):
|
||||
self.file_size = file_size
|
||||
self.bot = bot
|
||||
|
||||
self._id_attrs = (self.file_id,)
|
||||
self._id_attrs = (self.file_unique_id,)
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data, bot):
|
||||
if not data:
|
||||
return None
|
||||
|
||||
data = super(Document, cls).de_json(data, bot)
|
||||
data = super().de_json(data, bot)
|
||||
|
||||
data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot)
|
||||
|
||||
|
||||
+26
-11
@@ -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-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -19,8 +19,9 @@
|
||||
"""This module contains an object that represents a Telegram File."""
|
||||
from base64 import b64decode
|
||||
from os.path import basename
|
||||
import os
|
||||
|
||||
from future.backports.urllib import parse as urllib_parse
|
||||
import urllib.parse as urllib_parse
|
||||
|
||||
from telegram import TelegramObject
|
||||
from telegram.passport.credentials import decrypt
|
||||
@@ -37,11 +38,17 @@ class File(TelegramObject):
|
||||
|
||||
Attributes:
|
||||
file_id (:obj:`str`): Unique identifier for this file.
|
||||
file_unique_id (:obj:`str`): Unique identifier for this file, which
|
||||
is supposed to be the same over time and for different bots.
|
||||
Can't be used to download or reuse the file.
|
||||
file_size (:obj:`str`): Optional. File size.
|
||||
file_path (:obj:`str`): Optional. File path. Use :attr:`download` to get the file.
|
||||
|
||||
Args:
|
||||
file_id (:obj:`str`): Unique identifier for this file.
|
||||
file_id (:obj:`str`): Identifier for this file, which can be used to download
|
||||
or reuse the file.
|
||||
file_unique_id (:obj:`str`): Unique and the same over time and
|
||||
for different bots file identifier.
|
||||
file_size (:obj:`int`, optional): Optional. File size, if known.
|
||||
file_path (:obj:`str`, optional): File path. Use :attr:`download` to get the file.
|
||||
bot (:obj:`telegram.Bot`, optional): Bot to use with shortcut method.
|
||||
@@ -53,18 +60,23 @@ class File(TelegramObject):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, file_id, bot=None, file_size=None, file_path=None, **kwargs):
|
||||
def __init__(self,
|
||||
file_id,
|
||||
file_unique_id,
|
||||
bot=None,
|
||||
file_size=None,
|
||||
file_path=None,
|
||||
**kwargs):
|
||||
# Required
|
||||
self.file_id = str(file_id)
|
||||
|
||||
self.file_unique_id = str(file_unique_id)
|
||||
# Optionals
|
||||
self.file_size = file_size
|
||||
self.file_path = file_path
|
||||
|
||||
self.bot = bot
|
||||
self._credentials = None
|
||||
|
||||
self._id_attrs = (self.file_id,)
|
||||
self._id_attrs = (self.file_unique_id,)
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data, bot):
|
||||
@@ -76,9 +88,10 @@ class File(TelegramObject):
|
||||
def download(self, custom_path=None, out=None, timeout=None):
|
||||
"""
|
||||
Download this file. By default, the file is saved in the current working directory with its
|
||||
original filename as reported by Telegram. If a :attr:`custom_path` is supplied, it will be
|
||||
saved to that path instead. If :attr:`out` is defined, the file contents will be saved to
|
||||
that object using the ``out.write`` method.
|
||||
original filename as reported by Telegram. If the file has no filename, it the file ID will
|
||||
be used as filename. If a :attr:`custom_path` is supplied, it will be saved to that path
|
||||
instead. If :attr:`out` is defined, the file contents will be saved to that object using
|
||||
the ``out.write`` method.
|
||||
|
||||
Note:
|
||||
:attr:`custom_path` and :attr:`out` are mutually exclusive.
|
||||
@@ -116,8 +129,10 @@ class File(TelegramObject):
|
||||
else:
|
||||
if custom_path:
|
||||
filename = custom_path
|
||||
else:
|
||||
elif self.file_path:
|
||||
filename = basename(self.file_path)
|
||||
else:
|
||||
filename = os.path.join(os.getcwd(), self.file_id)
|
||||
|
||||
buf = self.bot.request.retrieve(url, timeout=timeout)
|
||||
if self._credentials:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# pylint: disable=W0622,E0611
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2018
|
||||
# Copyright (C) 2015-2020
|
||||
# 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 telegram import TelegramError
|
||||
DEFAULT_MIME_TYPE = 'application/octet-stream'
|
||||
|
||||
|
||||
class InputFile(object):
|
||||
class InputFile:
|
||||
"""This object represents a Telegram InputFile.
|
||||
|
||||
Attributes:
|
||||
@@ -55,11 +55,7 @@ class InputFile(object):
|
||||
|
||||
if filename:
|
||||
self.filename = filename
|
||||
elif (hasattr(obj, 'name')
|
||||
and not isinstance(obj.name, int) # py3
|
||||
and obj.name != '<fdopen>'): # py2
|
||||
# on py2.7, pylint fails to understand this properly
|
||||
# pylint: disable=E1101
|
||||
elif (hasattr(obj, 'name') and not isinstance(obj.name, int)):
|
||||
self.filename = os.path.basename(obj.name)
|
||||
|
||||
try:
|
||||
|
||||
+87
-113
@@ -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-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -19,6 +19,7 @@
|
||||
"""Base class for Telegram InputMedia Objects."""
|
||||
|
||||
from telegram import TelegramObject, InputFile, PhotoSize, Animation, Video, Audio, Document
|
||||
from telegram.utils.helpers import DEFAULT_NONE
|
||||
|
||||
|
||||
class InputMedia(TelegramObject):
|
||||
@@ -37,33 +38,27 @@ class InputMediaAnimation(InputMedia):
|
||||
|
||||
Attributes:
|
||||
type (:obj:`str`): ``animation``.
|
||||
media (:obj:`str` | `filelike object` | :class:`telegram.Animation`): Animation to
|
||||
send. Pass a file_id as String to send an animation that exists on the Telegram
|
||||
servers (recommended), pass an HTTP URL as a String for Telegram to get an
|
||||
animation from the Internet, or upload a new animation using multipart/form-data.
|
||||
Lastly you can pass an existing :class:`telegram.Animation` object to send.
|
||||
thumb (`filelike object`): Optional. Thumbnail of the
|
||||
file sent. The thumbnail should be in JPEG format and less than 200 kB in size.
|
||||
A thumbnail's width and height should not exceed 90. Ignored if the file is not
|
||||
is passed as a string or file_id.
|
||||
caption (:obj:`str`): Optional. Caption of the animation to be sent, 0-1024 characters.
|
||||
parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show
|
||||
bold, italic, fixed-width text or inline URLs in the media caption. See the constants
|
||||
in :class:`telegram.ParseMode` for the available modes.
|
||||
media (:obj:`str` | :class:`telegram.InputFile`): Animation to send.
|
||||
caption (:obj:`str`): Optional. Caption of the document to be sent.
|
||||
parse_mode (:obj:`str`): Optional. The parse mode to use for text formatting.
|
||||
thumb (:class:`telegram.InputFile`): Optional. Thumbnail of the file to send.
|
||||
width (:obj:`int`): Optional. Animation width.
|
||||
height (:obj:`int`): Optional. Animation height.
|
||||
duration (:obj:`int`): Optional. Animation duration.
|
||||
|
||||
|
||||
Args:
|
||||
media (:obj:`str`): File to send. Pass a file_id to send a file that exists on the Telegram
|
||||
servers (recommended), pass an HTTP URL for Telegram to get a file from the Internet.
|
||||
Lastly you can pass an existing :class:`telegram.Animation` object to send.
|
||||
thumb (`filelike object`, optional): Thumbnail of the
|
||||
file sent. The thumbnail should be in JPEG format and less than 200 kB in size.
|
||||
A thumbnail's width and height should not exceed 90. Ignored if the file is not
|
||||
is passed as a string or file_id.
|
||||
caption (:obj:`str`, optional): Caption of the animation to be sent, 0-1024 characters.
|
||||
media (:obj:`str` | `filelike object` | :class:`telegram.Animation`): File to send. Pass a
|
||||
file_id to send a file that exists on the Telegram servers (recommended), pass an HTTP
|
||||
URL for Telegram to get a file from the Internet. Lastly you can pass an existing
|
||||
:class:`telegram.Animation` object to send.
|
||||
thumb (`filelike object`, optional): Thumbnail of the file sent; can be ignored if
|
||||
thumbnail generation for the file is supported server-side. The thumbnail should be
|
||||
in JPEG format and less than 200 kB in size. A thumbnail's width and height should
|
||||
not exceed 320. Ignored if the file is not uploaded using multipart/form-data.
|
||||
Thumbnails can't be reused and can be only uploaded as a new file.
|
||||
caption (:obj:`str`, optional): Caption of the animation to be sent, 0-1024 characters
|
||||
after entities parsing.
|
||||
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show
|
||||
bold, italic, fixed-width text or inline URLs in the media caption. See the constants
|
||||
in :class:`telegram.ParseMode` for the available modes.
|
||||
@@ -77,7 +72,13 @@ class InputMediaAnimation(InputMedia):
|
||||
arguments.
|
||||
"""
|
||||
|
||||
def __init__(self, media, thumb=None, caption=None, parse_mode=None, width=None, height=None,
|
||||
def __init__(self,
|
||||
media,
|
||||
thumb=None,
|
||||
caption=None,
|
||||
parse_mode=DEFAULT_NONE,
|
||||
width=None,
|
||||
height=None,
|
||||
duration=None):
|
||||
self.type = 'animation'
|
||||
|
||||
@@ -98,8 +99,7 @@ class InputMediaAnimation(InputMedia):
|
||||
|
||||
if caption:
|
||||
self.caption = caption
|
||||
if parse_mode:
|
||||
self.parse_mode = parse_mode
|
||||
self.parse_mode = parse_mode
|
||||
if width:
|
||||
self.width = width
|
||||
if height:
|
||||
@@ -113,27 +113,23 @@ class InputMediaPhoto(InputMedia):
|
||||
|
||||
Attributes:
|
||||
type (:obj:`str`): ``photo``.
|
||||
media (:obj:`str` | `filelike object` | :class:`telegram.PhotoSize`): Photo to send.
|
||||
Pass a file_id as String to send a photo that exists on the Telegram servers
|
||||
(recommended), pass an HTTP URL as a String for Telegram to get a photo from the
|
||||
Internet, or upload a new photo using multipart/form-data. Lastly you can pass
|
||||
an existing :class:`telegram.PhotoSize` object to send.
|
||||
caption (:obj:`str`): Optional. Caption of the photo to be sent, 0-1024 characters.
|
||||
parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show
|
||||
bold, italic, fixed-width text or inline URLs in the media caption. See the constants
|
||||
in :class:`telegram.ParseMode` for the available modes.
|
||||
media (:obj:`str` | :class:`telegram.InputFile`): Photo to send.
|
||||
caption (:obj:`str`): Optional. Caption of the document to be sent.
|
||||
parse_mode (:obj:`str`): Optional. The parse mode to use for text formatting.
|
||||
|
||||
Args:
|
||||
media (:obj:`str`): File to send. Pass a file_id to send a file that exists on the
|
||||
Telegram servers (recommended), pass an HTTP URL for Telegram to get a file from the
|
||||
Internet. Lastly you can pass an existing :class:`telegram.PhotoSize` object to send.
|
||||
caption (:obj:`str`, optional ): Caption of the photo to be sent, 0-1024 characters.
|
||||
media (:obj:`str` | `filelike object` | :class:`telegram.PhotoSize`): File to send. Pass a
|
||||
file_id to send a file that exists on the Telegram servers (recommended), pass an HTTP
|
||||
URL for Telegram to get a file from the Internet. Lastly you can pass an existing
|
||||
:class:`telegram.PhotoSize` object to send.
|
||||
caption (:obj:`str`, optional ): Caption of the photo to be sent, 0-1024 characters after
|
||||
entities parsing.
|
||||
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show
|
||||
bold, italic, fixed-width text or inline URLs in the media caption. See the constants
|
||||
in :class:`telegram.ParseMode` for the available modes.
|
||||
"""
|
||||
|
||||
def __init__(self, media, caption=None, parse_mode=None):
|
||||
def __init__(self, media, caption=None, parse_mode=DEFAULT_NONE):
|
||||
self.type = 'photo'
|
||||
|
||||
if isinstance(media, PhotoSize):
|
||||
@@ -145,8 +141,7 @@ class InputMediaPhoto(InputMedia):
|
||||
|
||||
if caption:
|
||||
self.caption = caption
|
||||
if parse_mode:
|
||||
self.parse_mode = parse_mode
|
||||
self.parse_mode = parse_mode
|
||||
|
||||
|
||||
class InputMediaVideo(InputMedia):
|
||||
@@ -154,30 +149,23 @@ class InputMediaVideo(InputMedia):
|
||||
|
||||
Attributes:
|
||||
type (:obj:`str`): ``video``.
|
||||
media (:obj:`str` | `filelike object` | :class:`telegram.Video`): Video file to send.
|
||||
Pass a file_id as String to send an video file that exists on the Telegram servers
|
||||
(recommended), pass an HTTP URL as a String for Telegram to get an video file from
|
||||
the Internet, or upload a new one using multipart/form-data. Lastly you can pass
|
||||
an existing :class:`telegram.Video` object to send.
|
||||
caption (:obj:`str`): Optional. Caption of the video to be sent, 0-1024 characters.
|
||||
parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show
|
||||
bold, italic, fixed-width text or inline URLs in the media caption. See the constants
|
||||
in :class:`telegram.ParseMode` for the available modes.
|
||||
media (:obj:`str` | :class:`telegram.InputFile`): Video file to send.
|
||||
caption (:obj:`str`): Optional. Caption of the document to be sent.
|
||||
parse_mode (:obj:`str`): Optional. The parse mode to use for text formatting.
|
||||
width (:obj:`int`): Optional. Video width.
|
||||
height (:obj:`int`): Optional. Video height.
|
||||
duration (:obj:`int`): Optional. Video duration.
|
||||
supports_streaming (:obj:`bool`): Optional. Pass True, if the uploaded video is suitable
|
||||
for streaming.
|
||||
thumb (`filelike object`): Optional. Thumbnail of the
|
||||
file sent. The thumbnail should be in JPEG format and less than 200 kB in size.
|
||||
A thumbnail's width and height should not exceed 90. Ignored if the file is not
|
||||
is passed as a string or file_id.
|
||||
thumb (:class:`telegram.InputFile`): Optional. Thumbnail of the file to send.
|
||||
|
||||
Args:
|
||||
media (:obj:`str`): File to send. Pass a file_id to send a file that exists on the Telegram
|
||||
servers (recommended), pass an HTTP URL for Telegram to get a file from the Internet.
|
||||
Lastly you can pass an existing :class:`telegram.Video` object to send.
|
||||
caption (:obj:`str`, optional): Caption of the video to be sent, 0-1024 characters.
|
||||
media (:obj:`str` | `filelike object` | :class:`telegram.Video`): File to send. Pass a
|
||||
file_id to send a file that exists on the Telegram servers (recommended), pass an HTTP
|
||||
URL for Telegram to get a file from the Internet. Lastly you can pass an existing
|
||||
:class:`telegram.Video` object to send.
|
||||
caption (:obj:`str`, optional): Caption of the video to be sent, 0-1024 characters after
|
||||
entities parsing.
|
||||
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show
|
||||
bold, italic, fixed-width text or inline URLs in the media caption. See the constants
|
||||
in :class:`telegram.ParseMode` for the available modes.
|
||||
@@ -186,10 +174,11 @@ class InputMediaVideo(InputMedia):
|
||||
duration (:obj:`int`, optional): Video duration.
|
||||
supports_streaming (:obj:`bool`, optional): Pass True, if the uploaded video is suitable
|
||||
for streaming.
|
||||
thumb (`filelike object`, optional): Thumbnail of the
|
||||
file sent. The thumbnail should be in JPEG format and less than 200 kB in size.
|
||||
A thumbnail's width and height should not exceed 90. Ignored if the file is not
|
||||
is passed as a string or file_id.
|
||||
thumb (`filelike object`, optional): Thumbnail of the file sent; can be ignored if
|
||||
thumbnail generation for the file is supported server-side. The thumbnail should be
|
||||
in JPEG format and less than 200 kB in size. A thumbnail's width and height should
|
||||
not exceed 320. Ignored if the file is not uploaded using multipart/form-data.
|
||||
Thumbnails can't be reused and can be only uploaded as a new file.
|
||||
|
||||
Note:
|
||||
When using a :class:`telegram.Video` for the :attr:`media` attribute. It will take the
|
||||
@@ -198,7 +187,7 @@ class InputMediaVideo(InputMedia):
|
||||
"""
|
||||
|
||||
def __init__(self, media, caption=None, width=None, height=None, duration=None,
|
||||
supports_streaming=None, parse_mode=None, thumb=None):
|
||||
supports_streaming=None, parse_mode=DEFAULT_NONE, thumb=None):
|
||||
self.type = 'video'
|
||||
|
||||
if isinstance(media, Video):
|
||||
@@ -218,8 +207,7 @@ class InputMediaVideo(InputMedia):
|
||||
|
||||
if caption:
|
||||
self.caption = caption
|
||||
if parse_mode:
|
||||
self.parse_mode = parse_mode
|
||||
self.parse_mode = parse_mode
|
||||
if width:
|
||||
self.width = width
|
||||
if height:
|
||||
@@ -235,29 +223,22 @@ class InputMediaAudio(InputMedia):
|
||||
|
||||
Attributes:
|
||||
type (:obj:`str`): ``audio``.
|
||||
media (:obj:`str` | `filelike object` | :class:`telegram.Audio`): Audio file to send.
|
||||
Pass a file_id as String to send an audio file that exists on the Telegram servers
|
||||
(recommended), pass an HTTP URL as a String for Telegram to get an audio file from
|
||||
the Internet, or upload a new one using multipart/form-data. Lastly you can pass
|
||||
an existing :class:`telegram.Audio` object to send.
|
||||
caption (:obj:`str`): Optional. Caption of the audio to be sent, 0-1024 characters.
|
||||
parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show
|
||||
bold, italic, fixed-width text or inline URLs in the media caption. See the constants
|
||||
in :class:`telegram.ParseMode` for the available modes.
|
||||
media (:obj:`str` | :class:`telegram.InputFile`): Audio file to send.
|
||||
caption (:obj:`str`): Optional. Caption of the document to be sent.
|
||||
parse_mode (:obj:`str`): Optional. The parse mode to use for text formatting.
|
||||
duration (:obj:`int`): Duration of the audio in seconds.
|
||||
performer (:obj:`str`): Optional. Performer of the audio as defined by sender or by audio
|
||||
tags.
|
||||
title (:obj:`str`): Optional. Title of the audio as defined by sender or by audio tags.
|
||||
thumb (`filelike object`): Optional. Thumbnail of the
|
||||
file sent. The thumbnail should be in JPEG format and less than 200 kB in size.
|
||||
A thumbnail's width and height should not exceed 90. Ignored if the file is not
|
||||
is passed as a string or file_id.
|
||||
thumb (:class:`telegram.InputFile`): Optional. Thumbnail of the file to send.
|
||||
|
||||
Args:
|
||||
media (:obj:`str`): File to send. Pass a file_id to send a file that exists on the Telegram
|
||||
servers (recommended), pass an HTTP URL for Telegram to get a file from the Internet.
|
||||
Lastly you can pass an existing :class:`telegram.Document` object to send.
|
||||
caption (:obj:`str`, optional): Caption of the audio to be sent, 0-1024 characters.
|
||||
media (:obj:`str` | `filelike object` | :class:`telegram.Audio`): File to send. Pass a
|
||||
file_id to send a file that exists on the Telegram servers (recommended), pass an HTTP
|
||||
URL for Telegram to get a file from the Internet. Lastly you can pass an existing
|
||||
:class:`telegram.Document` object to send.
|
||||
caption (:obj:`str`, optional): Caption of the audio to be sent, 0-1024 characters after
|
||||
entities parsing.
|
||||
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show
|
||||
bold, italic, fixed-width text or inline URLs in the media caption. See the constants
|
||||
in :class:`telegram.ParseMode` for the available modes.
|
||||
@@ -265,10 +246,11 @@ class InputMediaAudio(InputMedia):
|
||||
performer (:obj:`str`, optional): Performer of the audio as defined by sender or by audio
|
||||
tags.
|
||||
title (:obj:`str`, optional): Title of the audio as defined by sender or by audio tags.
|
||||
thumb (`filelike object`, optional): Thumbnail of the
|
||||
file sent. The thumbnail should be in JPEG format and less than 200 kB in size.
|
||||
A thumbnail's width and height should not exceed 90. Ignored if the file is not
|
||||
is passed as a string or file_id.
|
||||
thumb (`filelike object`, optional): Thumbnail of the file sent; can be ignored if
|
||||
thumbnail generation for the file is supported server-side. The thumbnail should be
|
||||
in JPEG format and less than 200 kB in size. A thumbnail's width and height should
|
||||
not exceed 320. Ignored if the file is not uploaded using multipart/form-data.
|
||||
Thumbnails can't be reused and can be only uploaded as a new file.
|
||||
|
||||
Note:
|
||||
When using a :class:`telegram.Audio` for the :attr:`media` attribute. It will take the
|
||||
@@ -276,7 +258,7 @@ class InputMediaAudio(InputMedia):
|
||||
optional arguments.
|
||||
"""
|
||||
|
||||
def __init__(self, media, thumb=None, caption=None, parse_mode=None,
|
||||
def __init__(self, media, thumb=None, caption=None, parse_mode=DEFAULT_NONE,
|
||||
duration=None, performer=None, title=None):
|
||||
self.type = 'audio'
|
||||
|
||||
@@ -297,8 +279,7 @@ class InputMediaAudio(InputMedia):
|
||||
|
||||
if caption:
|
||||
self.caption = caption
|
||||
if parse_mode:
|
||||
self.parse_mode = parse_mode
|
||||
self.parse_mode = parse_mode
|
||||
if duration:
|
||||
self.duration = duration
|
||||
if performer:
|
||||
@@ -312,35 +293,29 @@ class InputMediaDocument(InputMedia):
|
||||
|
||||
Attributes:
|
||||
type (:obj:`str`): ``document``.
|
||||
media (:obj:`str` | `filelike object` | :class:`telegram.Document`): File to send.
|
||||
Pass a file_id as String to send a file that exists on the Telegram servers
|
||||
(recommended), pass an HTTP URL as a String for Telegram to get a file from the
|
||||
Internet, or upload a new one using multipart/form-data. Lastly you can pass
|
||||
an existing :class:`telegram.Document` object to send.
|
||||
caption (:obj:`str`): Optional. Caption of the document to be sent, 0-1024 characters.
|
||||
parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show
|
||||
bold, italic, fixed-width text or inline URLs in the media caption. See the constants
|
||||
in :class:`telegram.ParseMode` for the available modes.
|
||||
thumb (`filelike object`): Optional. Thumbnail of the
|
||||
file sent. The thumbnail should be in JPEG format and less than 200 kB in size.
|
||||
A thumbnail's width and height should not exceed 90. Ignored if the file is not
|
||||
is passed as a string or file_id.
|
||||
media (:obj:`str` | :class:`telegram.InputFile`): File to send.
|
||||
caption (:obj:`str`): Optional. Caption of the document to be sent.
|
||||
parse_mode (:obj:`str`): Optional. The parse mode to use for text formatting.
|
||||
thumb (:class:`telegram.InputFile`): Optional. Thumbnail of the file to send.
|
||||
|
||||
Args:
|
||||
media (:obj:`str`): File to send. Pass a file_id to send a file that exists on the Telegram
|
||||
servers (recommended), pass an HTTP URL for Telegram to get a file from the Internet.
|
||||
Lastly you can pass an existing :class:`telegram.Document` object to send.
|
||||
caption (:obj:`str`, optional): Caption of the document to be sent, 0-1024 characters.
|
||||
media (:obj:`str` | `filelike object` | :class:`telegram.Document`): File to send. Pass a
|
||||
file_id to send a file that exists on the Telegram servers (recommended), pass an HTTP
|
||||
URL for Telegram to get a file from the Internet. Lastly you can pass an existing
|
||||
:class:`telegram.Document` object to send.
|
||||
caption (:obj:`str`, optional): Caption of the document to be sent, 0-1024 characters after
|
||||
entities parsing.
|
||||
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show
|
||||
bold, italic, fixed-width text or inline URLs in the media caption. See the constants
|
||||
in :class:`telegram.ParseMode` for the available modes.
|
||||
thumb (`filelike object`, optional): Thumbnail of the
|
||||
file sent. The thumbnail should be in JPEG format and less than 200 kB in size.
|
||||
A thumbnail's width and height should not exceed 90. Ignored if the file is not
|
||||
is passed as a string or file_id.
|
||||
thumb (`filelike object`, optional): Thumbnail of the file sent; can be ignored if
|
||||
thumbnail generation for the file is supported server-side. The thumbnail should be
|
||||
in JPEG format and less than 200 kB in size. A thumbnail's width and height should
|
||||
not exceed 320. Ignored if the file is not uploaded using multipart/form-data.
|
||||
Thumbnails can't be reused and can be only uploaded as a new file.
|
||||
"""
|
||||
|
||||
def __init__(self, media, thumb=None, caption=None, parse_mode=None):
|
||||
def __init__(self, media, thumb=None, caption=None, parse_mode=DEFAULT_NONE):
|
||||
self.type = 'document'
|
||||
|
||||
if isinstance(media, Document):
|
||||
@@ -357,5 +332,4 @@ class InputMediaDocument(InputMedia):
|
||||
|
||||
if caption:
|
||||
self.caption = caption
|
||||
if parse_mode:
|
||||
self.parse_mode = parse_mode
|
||||
self.parse_mode = parse_mode
|
||||
|
||||
@@ -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-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -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-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -26,13 +26,19 @@ class PhotoSize(TelegramObject):
|
||||
|
||||
Attributes:
|
||||
file_id (:obj:`str`): Unique identifier for this file.
|
||||
file_unique_id (:obj:`str`): Unique identifier for this file, which
|
||||
is supposed to be the same over time and for different bots.
|
||||
Can't be used to download or reuse the file.
|
||||
width (:obj:`int`): Photo width.
|
||||
height (:obj:`int`): Photo height.
|
||||
file_size (:obj:`int`): Optional. File size.
|
||||
bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods.
|
||||
|
||||
Args:
|
||||
file_id (:obj:`str`): Unique identifier for this file.
|
||||
file_id (:obj:`str`): Identifier for this file, which can be used to download
|
||||
or reuse the file.
|
||||
file_unique_id (:obj:`str`): Unique and the same over time and
|
||||
for different bots file identifier.
|
||||
width (:obj:`int`): Photo width.
|
||||
height (:obj:`int`): Photo height.
|
||||
file_size (:obj:`int`, optional): File size.
|
||||
@@ -41,16 +47,24 @@ class PhotoSize(TelegramObject):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, file_id, width, height, file_size=None, bot=None, **kwargs):
|
||||
def __init__(self,
|
||||
file_id,
|
||||
file_unique_id,
|
||||
width,
|
||||
height,
|
||||
file_size=None,
|
||||
bot=None,
|
||||
**kwargs):
|
||||
# Required
|
||||
self.file_id = str(file_id)
|
||||
self.file_unique_id = str(file_unique_id)
|
||||
self.width = int(width)
|
||||
self.height = int(height)
|
||||
# Optionals
|
||||
self.file_size = file_size
|
||||
self.bot = bot
|
||||
|
||||
self._id_attrs = (self.file_id,)
|
||||
self._id_attrs = (self.file_unique_id,)
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data, bot):
|
||||
|
||||
+27
-11
@@ -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-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -26,6 +26,9 @@ class Sticker(TelegramObject):
|
||||
|
||||
Attributes:
|
||||
file_id (:obj:`str`): Unique identifier for this file.
|
||||
file_unique_id (:obj:`str`): Unique identifier for this file, which
|
||||
is supposed to be the same over time and for different bots.
|
||||
Can't be used to download or reuse the file.
|
||||
width (:obj:`int`): Sticker width.
|
||||
height (:obj:`int`): Sticker height.
|
||||
is_animated (:obj:`bool`): True, if the sticker is animated.
|
||||
@@ -39,7 +42,10 @@ class Sticker(TelegramObject):
|
||||
bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods.
|
||||
|
||||
Args:
|
||||
file_id (:obj:`str`): Unique identifier for this file.
|
||||
file_id (:obj:`str`): Identifier for this file, which can be used to download
|
||||
or reuse the file.
|
||||
file_unique_id (:obj:`str`): Unique and the same over time and
|
||||
for different bots file identifier.
|
||||
width (:obj:`int`): Sticker width.
|
||||
height (:obj:`int`): Sticker height.
|
||||
is_animated (:obj:`bool`): True, if the sticker is animated.
|
||||
@@ -58,6 +64,7 @@ class Sticker(TelegramObject):
|
||||
|
||||
def __init__(self,
|
||||
file_id,
|
||||
file_unique_id,
|
||||
width,
|
||||
height,
|
||||
is_animated,
|
||||
@@ -70,6 +77,7 @@ class Sticker(TelegramObject):
|
||||
**kwargs):
|
||||
# Required
|
||||
self.file_id = str(file_id)
|
||||
self.file_unique_id = str(file_unique_id)
|
||||
self.width = int(width)
|
||||
self.height = int(height)
|
||||
self.is_animated = is_animated
|
||||
@@ -81,14 +89,14 @@ class Sticker(TelegramObject):
|
||||
self.mask_position = mask_position
|
||||
self.bot = bot
|
||||
|
||||
self._id_attrs = (self.file_id,)
|
||||
self._id_attrs = (self.file_unique_id,)
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data, bot):
|
||||
if not data:
|
||||
return None
|
||||
|
||||
data = super(Sticker, cls).de_json(data, bot)
|
||||
data = super().de_json(data, bot)
|
||||
|
||||
data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot)
|
||||
data['mask_position'] = MaskPosition.de_json(data.get('mask_position'), bot)
|
||||
@@ -130,6 +138,8 @@ class StickerSet(TelegramObject):
|
||||
is_animated (:obj:`bool`): True, if the sticker set contains animated stickers.
|
||||
contains_masks (:obj:`bool`): True, if the sticker set contains masks.
|
||||
stickers (List[:class:`telegram.Sticker`]): List of all set stickers.
|
||||
thumb (:class:`telegram.PhotoSize`): Optional. Sticker set thumbnail in the .WEBP or .TGS
|
||||
format
|
||||
|
||||
Args:
|
||||
name (:obj:`str`): Sticker set name.
|
||||
@@ -137,31 +147,37 @@ class StickerSet(TelegramObject):
|
||||
is_animated (:obj:`bool`): True, if the sticker set contains animated stickers.
|
||||
contains_masks (:obj:`bool`): True, if the sticker set contains masks.
|
||||
stickers (List[:class:`telegram.Sticker`]): List of all set stickers.
|
||||
thumb (:class:`telegram.PhotoSize`, optional): Sticker set thumbnail in the .WEBP or .TGS
|
||||
format
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, name, title, is_animated, contains_masks, stickers, bot=None, **kwargs):
|
||||
def __init__(self, name, title, is_animated, contains_masks, stickers, bot=None, thumb=None,
|
||||
**kwargs):
|
||||
self.name = name
|
||||
self.title = title
|
||||
self.is_animated = is_animated
|
||||
self.contains_masks = contains_masks
|
||||
self.stickers = stickers
|
||||
# Optionals
|
||||
self.thumb = thumb
|
||||
|
||||
self._id_attrs = (self.name,)
|
||||
|
||||
@staticmethod
|
||||
def de_json(data, bot):
|
||||
@classmethod
|
||||
def de_json(cls, data, bot):
|
||||
if not data:
|
||||
return None
|
||||
|
||||
data = super(StickerSet, StickerSet).de_json(data, bot)
|
||||
data = super().de_json(data, bot)
|
||||
|
||||
data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot)
|
||||
data['stickers'] = Sticker.de_list(data.get('stickers'), bot)
|
||||
|
||||
return StickerSet(bot=bot, **data)
|
||||
return cls(bot=bot, **data)
|
||||
|
||||
def to_dict(self):
|
||||
data = super(StickerSet, self).to_dict()
|
||||
data = super().to_dict()
|
||||
|
||||
data['stickers'] = [s.to_dict() for s in data.get('stickers')]
|
||||
|
||||
@@ -179,7 +195,7 @@ class MaskPosition(TelegramObject):
|
||||
size, from top to bottom.
|
||||
scale (:obj:`float`): Mask scaling coefficient. For example, 2.0 means double size.
|
||||
|
||||
Notes:
|
||||
Note:
|
||||
:attr:`type` should be one of the following: `forehead`, `eyes`, `mouth` or `chin`. You can
|
||||
use the classconstants for those.
|
||||
|
||||
|
||||
@@ -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-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -57,7 +57,7 @@ class Venue(TelegramObject):
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data, bot):
|
||||
data = super(Venue, cls).de_json(data, bot)
|
||||
data = super().de_json(data, bot)
|
||||
|
||||
if not data:
|
||||
return None
|
||||
|
||||
+12
-4
@@ -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-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -26,6 +26,9 @@ class Video(TelegramObject):
|
||||
|
||||
Attributes:
|
||||
file_id (:obj:`str`): Unique identifier for this file.
|
||||
file_unique_id (:obj:`str`): Unique identifier for this file, which
|
||||
is supposed to be the same over time and for different bots.
|
||||
Can't be used to download or reuse the file.
|
||||
width (:obj:`int`): Video width as defined by sender.
|
||||
height (:obj:`int`): Video height as defined by sender.
|
||||
duration (:obj:`int`): Duration of the video in seconds as defined by sender.
|
||||
@@ -35,7 +38,10 @@ class Video(TelegramObject):
|
||||
bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods.
|
||||
|
||||
Args:
|
||||
file_id (:obj:`str`): Unique identifier for this file.
|
||||
file_id (:obj:`str`): Identifier for this file, which can be used to download
|
||||
or reuse the file.
|
||||
file_unique_id (:obj:`str`): Unique and the same over time and
|
||||
for different bots file identifier.
|
||||
width (:obj:`int`): Video width as defined by sender.
|
||||
height (:obj:`int`): Video height as defined by sender.
|
||||
duration (:obj:`int`): Duration of the video in seconds as defined by sender.
|
||||
@@ -49,6 +55,7 @@ class Video(TelegramObject):
|
||||
|
||||
def __init__(self,
|
||||
file_id,
|
||||
file_unique_id,
|
||||
width,
|
||||
height,
|
||||
duration,
|
||||
@@ -59,6 +66,7 @@ class Video(TelegramObject):
|
||||
**kwargs):
|
||||
# Required
|
||||
self.file_id = str(file_id)
|
||||
self.file_unique_id = str(file_unique_id)
|
||||
self.width = int(width)
|
||||
self.height = int(height)
|
||||
self.duration = int(duration)
|
||||
@@ -68,14 +76,14 @@ class Video(TelegramObject):
|
||||
self.file_size = file_size
|
||||
self.bot = bot
|
||||
|
||||
self._id_attrs = (self.file_id,)
|
||||
self._id_attrs = (self.file_unique_id,)
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data, bot):
|
||||
if not data:
|
||||
return None
|
||||
|
||||
data = super(Video, cls).de_json(data, bot)
|
||||
data = super().de_json(data, bot)
|
||||
|
||||
data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot)
|
||||
|
||||
|
||||
@@ -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-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -26,6 +26,9 @@ class VideoNote(TelegramObject):
|
||||
|
||||
Attributes:
|
||||
file_id (:obj:`str`): Unique identifier for this file.
|
||||
file_unique_id (:obj:`str`): Unique identifier for this file, which
|
||||
is supposed to be the same over time and for different bots.
|
||||
Can't be used to download or reuse the file.
|
||||
length (:obj:`int`): Video width and height as defined by sender.
|
||||
duration (:obj:`int`): Duration of the video in seconds as defined by sender.
|
||||
thumb (:class:`telegram.PhotoSize`): Optional. Video thumbnail.
|
||||
@@ -33,7 +36,10 @@ class VideoNote(TelegramObject):
|
||||
bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods.
|
||||
|
||||
Args:
|
||||
file_id (:obj:`str`): Unique identifier for this file.
|
||||
file_id (:obj:`str`): Identifier for this file, which can be used to download
|
||||
or reuse the file.
|
||||
file_unique_id (:obj:`str`): Unique and the same over time and
|
||||
for different bots file identifier.
|
||||
length (:obj:`int`): Video width and height as defined by sender.
|
||||
duration (:obj:`int`): Duration of the video in seconds as defined by sender.
|
||||
thumb (:class:`telegram.PhotoSize`, optional): Video thumbnail.
|
||||
@@ -43,9 +49,18 @@ class VideoNote(TelegramObject):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, file_id, length, duration, thumb=None, file_size=None, bot=None, **kwargs):
|
||||
def __init__(self,
|
||||
file_id,
|
||||
file_unique_id,
|
||||
length,
|
||||
duration,
|
||||
thumb=None,
|
||||
file_size=None,
|
||||
bot=None,
|
||||
**kwargs):
|
||||
# Required
|
||||
self.file_id = str(file_id)
|
||||
self.file_unique_id = str(file_unique_id)
|
||||
self.length = int(length)
|
||||
self.duration = int(duration)
|
||||
# Optionals
|
||||
@@ -53,14 +68,14 @@ class VideoNote(TelegramObject):
|
||||
self.file_size = file_size
|
||||
self.bot = bot
|
||||
|
||||
self._id_attrs = (self.file_id,)
|
||||
self._id_attrs = (self.file_unique_id,)
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data, bot):
|
||||
if not data:
|
||||
return None
|
||||
|
||||
data = super(VideoNote, cls).de_json(data, bot)
|
||||
data = super().de_json(data, bot)
|
||||
|
||||
data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot)
|
||||
|
||||
|
||||
+19
-5
@@ -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-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -26,13 +26,19 @@ class Voice(TelegramObject):
|
||||
|
||||
Attributes:
|
||||
file_id (:obj:`str`): Unique identifier for this file.
|
||||
file_unique_id (:obj:`str`): Unique identifier for this file, which
|
||||
is supposed to be the same over time and for different bots.
|
||||
Can't be used to download or reuse the file.
|
||||
duration (:obj:`int`): Duration of the audio in seconds as defined by sender.
|
||||
mime_type (:obj:`str`): Optional. MIME type of the file as defined by sender.
|
||||
file_size (:obj:`int`): Optional. File size.
|
||||
bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods.
|
||||
|
||||
Args:
|
||||
file_id (:obj:`str`): Unique identifier for this file.
|
||||
file_id (:obj:`str`): Identifier for this file, which can be used to download
|
||||
or reuse the file.
|
||||
file_unique_id (:obj:`str`): Unique and the same over time and
|
||||
for different bots file identifier.
|
||||
duration (:obj:`int`, optional): Duration of the audio in seconds as defined by sender.
|
||||
mime_type (:obj:`str`, optional): MIME type of the file as defined by sender.
|
||||
file_size (:obj:`int`, optional): File size.
|
||||
@@ -41,23 +47,31 @@ class Voice(TelegramObject):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, file_id, duration, mime_type=None, file_size=None, bot=None, **kwargs):
|
||||
def __init__(self,
|
||||
file_id,
|
||||
file_unique_id,
|
||||
duration,
|
||||
mime_type=None,
|
||||
file_size=None,
|
||||
bot=None,
|
||||
**kwargs):
|
||||
# Required
|
||||
self.file_id = str(file_id)
|
||||
self.file_unique_id = str(file_unique_id)
|
||||
self.duration = int(duration)
|
||||
# Optionals
|
||||
self.mime_type = mime_type
|
||||
self.file_size = file_size
|
||||
self.bot = bot
|
||||
|
||||
self._id_attrs = (self.file_id,)
|
||||
self._id_attrs = (self.file_unique_id,)
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data, bot):
|
||||
if not data:
|
||||
return None
|
||||
|
||||
data = super(Voice, cls).de_json(data, bot)
|
||||
data = super().de_json(data, bot)
|
||||
|
||||
return cls(bot=bot, **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-2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user