mirror of
https://github.com/python-telegram-bot/python-telegram-bot.git
synced 2024-12-23 15:00:40 +01:00
Merge remote-tracking branch 'origin/master' into default_parse_mode
This commit is contained in:
commit
b83376a3fe
25 changed files with 455 additions and 75 deletions
2
.github/CONTRIBUTING.rst
vendored
2
.github/CONTRIBUTING.rst
vendored
|
@ -25,7 +25,7 @@ Setting things up
|
||||||
|
|
||||||
.. code-block:: bash
|
.. 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:
|
5. Install pre-commit hooks:
|
||||||
|
|
|
@ -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!
|
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.
|
Use this template to notify us if you found a bug.
|
||||||
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
|
|
||||||
|
|
||||||
To make it easier for us to help you please enter detailed information below.
|
To make it easier for us to help you please enter detailed information below.
|
||||||
|
|
24
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
24
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
|
@ -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!*
|
29
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
29
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
---
|
||||||
|
name: Question
|
||||||
|
about: Get help with errors or general questions
|
||||||
|
title: "[QUESTION]"
|
||||||
|
labels: 'question :question:'
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Hey there, you have a question? We are happy to answer. Please make sure no similar question was opened already.
|
||||||
|
|
||||||
|
The following template is a suggestion how you can report an issue you run into whilst using our library. If you just want to ask a question, feel free to delete everything; just make sure you have a describing title :)
|
||||||
|
|
||||||
|
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
|
||||||
|
```
|
14
.github/workflows/example_notifier.yml
vendored
Normal file
14
.github/workflows/example_notifier.yml
vendored
Normal file
|
@ -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 }}
|
92
.github/workflows/test.yml
vendored
Normal file
92
.github/workflows/test.yml
vendored
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
name: Testing your PR
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
pytest:
|
||||||
|
name: pytest
|
||||||
|
runs-on: ${{matrix.os}}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
python-version: [2.7, 3.5, 3.6, 3.7]
|
||||||
|
os: [ubuntu-latest, windows-latest]
|
||||||
|
include:
|
||||||
|
- os: ubuntu-latest
|
||||||
|
python-version: 3.7
|
||||||
|
test-build: True
|
||||||
|
test-pre-commit: True
|
||||||
|
- os: windows-latest
|
||||||
|
python-version: 3.7
|
||||||
|
test-build: True
|
||||||
|
fail-fast: False
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v1
|
||||||
|
- 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: W3sidG9rZW4iOiAiNjk2MTg4NzMyOkFBR1Z3RUtmSEhsTmpzY3hFRE5LQXdraEdzdFpfa28xbUMwIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6WldGaU1UUmxNbVF5TnpNeSIsICJib3RfbmFtZSI6ICJQVEIgdGVzdHMgb24gVHJhdmlzIHVzaW5nIENQeXRob24gMi43IiwgImJvdF91c2VybmFtZSI6ICJAcHRiX3RyYXZpc19jcHl0aG9uXzI3X2JvdCJ9LCB7InRva2VuIjogIjY3MTQ2ODg4NjpBQUdQR2ZjaVJJQlVORmU4MjR1SVZkcTdKZTNfWW5BVE5HdyIsICJwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY4NTA2MzpURVNUOlpHWXdPVGxrTXpNeE4yWTIiLCAiYm90X25hbWUiOiAiUFRCIHRlc3RzIG9uIFRyYXZpcyB1c2luZyBDUHl0aG9uIDMuNCIsICJib3RfdXNlcm5hbWUiOiAiQHB0Yl90cmF2aXNfY3B5dGhvbl8zNF9ib3QifSwgeyJ0b2tlbiI6ICI2MjkzMjY1Mzg6QUFGUnJaSnJCN29CM211ekdzR0pYVXZHRTVDUXpNNUNVNG8iLCAicGF5bWVudF9wcm92aWRlcl90b2tlbiI6ICIyODQ2ODUwNjM6VEVTVDpNbU01WVdKaFl6a3hNMlUxIiwgImJvdF9uYW1lIjogIlBUQiB0ZXN0cyBvbiBUcmF2aXMgdXNpbmcgQ1B5dGhvbiAzLjUiLCAiYm90X3VzZXJuYW1lIjogIkBwdGJfdHJhdmlzX2NweXRob25fMzVfYm90In0sIHsidG9rZW4iOiAiNjQwMjA4OTQzOkFBRmhCalFwOXFtM1JUeFN6VXBZekJRakNsZS1Kano1aGNrIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6WXpoa1pUZzFOamMxWXpWbCIsICJib3RfbmFtZSI6ICJQVEIgdGVzdHMgb24gVHJhdmlzIHVzaW5nIENQeXRob24gMy42IiwgImJvdF91c2VybmFtZSI6ICJAcHRiX3RyYXZpc19jcHl0aG9uXzM2X2JvdCJ9LCB7InRva2VuIjogIjY5NTEwNDA4ODpBQUhmenlsSU9qU0lJUy1lT25JMjB5MkUyMEhvZEhzZnotMCIsICJwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY4NTA2MzpURVNUOk9HUTFNRGd3WmpJd1pqRmwiLCAiYm90X25hbWUiOiAiUFRCIHRlc3RzIG9uIFRyYXZpcyB1c2luZyBDUHl0aG9uIDMuNyIsICJib3RfdXNlcm5hbWUiOiAiQHB0Yl90cmF2aXNfY3B5dGhvbl8zN19ib3QifSwgeyJ0b2tlbiI6ICI2OTE0MjM1NTQ6QUFGOFdrakNaYm5IcVBfaTZHaFRZaXJGRWxackdhWU9oWDAiLCAicGF5bWVudF9wcm92aWRlcl90b2tlbiI6ICIyODQ2ODUwNjM6VEVTVDpZamM1TlRoaU1tUXlNV1ZoIiwgImJvdF9uYW1lIjogIlBUQiB0ZXN0cyBvbiBUcmF2aXMgdXNpbmcgUHlQeSAyLjciLCAiYm90X3VzZXJuYW1lIjogIkBwdGJfdHJhdmlzX3B5cHlfMjdfYm90In0sIHsidG9rZW4iOiAiNjg0MzM5OTg0OkFBRk1nRUVqcDAxcjVyQjAwN3lDZFZOc2c4QWxOc2FVLWNjIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6TVRBek1UWTNNR1V5TmpnMCIsICJib3RfbmFtZSI6ICJQVEIgdGVzdHMgb24gVHJhdmlzIHVzaW5nIFB5UHkgMy41IiwgImJvdF91c2VybmFtZSI6ICJAcHRiX3RyYXZpc19weXB5XzM1X2JvdCJ9LCB7InRva2VuIjogIjY5MDA5MTM0NzpBQUZMbVI1cEFCNVljcGVfbU9oN3pNNEpGQk9oMHozVDBUbyIsICJwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY4NTA2MzpURVNUOlpEaGxOekU1TURrd1lXSmkiLCAiYm90X25hbWUiOiAiUFRCIHRlc3RzIG9uIEFwcFZleW9yIHVzaW5nIENQeXRob24gMy40IiwgImJvdF91c2VybmFtZSI6ICJAcHRiX2FwcHZleW9yX2NweXRob25fMzRfYm90In0sIHsidG9rZW4iOiAiNjk0MzA4MDUyOkFBRUIyX3NvbkNrNTVMWTlCRzlBTy1IOGp4aVBTNTVvb0JBIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6WW1aaVlXWm1NakpoWkdNeSIsICJib3RfbmFtZSI6ICJQVEIgdGVzdHMgb24gQXBwVmV5b3IgdXNpbmcgQ1B5dGhvbiAyLjciLCAiYm90X3VzZXJuYW1lIjogIkBwdGJfYXBwdmV5b3JfY3B5dGhvbl8yN19ib3QifV0=
|
||||||
|
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@v1
|
||||||
|
- 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}
|
||||||
|
|
|
@ -26,6 +26,7 @@ The following wonderful people contributed directly or indirectly to this projec
|
||||||
- `d-qoi <https://github.com/d-qoi>`_
|
- `d-qoi <https://github.com/d-qoi>`_
|
||||||
- `daimajia <https://github.com/daimajia>`_
|
- `daimajia <https://github.com/daimajia>`_
|
||||||
- `Daniel Reed <https://github.com/nmlorg>`_
|
- `Daniel Reed <https://github.com/nmlorg>`_
|
||||||
|
- `Dmitry Grigoryev <https://github.com/icecom-dg>`_
|
||||||
- `Ehsan Online <https://github.com/ehsanonline>`_
|
- `Ehsan Online <https://github.com/ehsanonline>`_
|
||||||
- `Eli Gao <https://github.com/eligao>`_
|
- `Eli Gao <https://github.com/eligao>`_
|
||||||
- `Emilio Molinari <https://github.com/xates>`_
|
- `Emilio Molinari <https://github.com/xates>`_
|
||||||
|
|
|
@ -192,11 +192,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!
|
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,6 +1,6 @@
|
||||||
# Examples
|
# 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.
|
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.
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def msg(bot, update):
|
def msg(update, context):
|
||||||
# If we received any passport data
|
# If we received any passport data
|
||||||
passport_data = update.message.passport_data
|
passport_data = update.message.passport_data
|
||||||
if passport_data:
|
if passport_data:
|
||||||
|
@ -77,9 +77,9 @@ def msg(bot, update):
|
||||||
actual_file.download()
|
actual_file.download()
|
||||||
|
|
||||||
|
|
||||||
def error(bot, update, error):
|
def error(update, context):
|
||||||
"""Log Errors caused by Updates."""
|
"""Log Errors caused by Updates."""
|
||||||
logger.warning('Update "%s" caused error "%s"', update, error)
|
logger.warning('Update "%s" caused error "%s"', update, context.error)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|
|
@ -42,7 +42,7 @@ def start_with_shipping_callback(update, context):
|
||||||
currency = "USD"
|
currency = "USD"
|
||||||
# price in dollars
|
# price in dollars
|
||||||
price = 1
|
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
|
# check https://core.telegram.org/bots/payments#supported-currencies for more details
|
||||||
prices = [LabeledPrice("Test", price * 100)]
|
prices = [LabeledPrice("Test", price * 100)]
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ def start_without_shipping_callback(update, context):
|
||||||
currency = "USD"
|
currency = "USD"
|
||||||
# price in dollars
|
# price in dollars
|
||||||
price = 1
|
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)]
|
prices = [LabeledPrice("Test", price * 100)]
|
||||||
|
|
||||||
# optionally pass need_name=True, need_phone_number=True,
|
# optionally pass need_name=True, need_phone_number=True,
|
||||||
|
|
|
@ -17,7 +17,8 @@
|
||||||
"""Constants in the Telegram network.
|
"""Constants in the Telegram network.
|
||||||
|
|
||||||
The following constants were extracted from the
|
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:
|
Attributes:
|
||||||
MAX_MESSAGE_LENGTH (:obj:`int`): 4096
|
MAX_MESSAGE_LENGTH (:obj:`int`): 4096
|
||||||
|
@ -25,6 +26,7 @@ Attributes:
|
||||||
SUPPORTED_WEBHOOK_PORTS (List[:obj:`int`]): [443, 80, 88, 8443]
|
SUPPORTED_WEBHOOK_PORTS (List[:obj:`int`]): [443, 80, 88, 8443]
|
||||||
MAX_FILESIZE_DOWNLOAD (:obj:`int`): In bytes (20MB)
|
MAX_FILESIZE_DOWNLOAD (:obj:`int`): In bytes (20MB)
|
||||||
MAX_FILESIZE_UPLOAD (:obj:`int`): In bytes (50MB)
|
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
|
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.
|
over this limit, but eventually you'll begin receiving 429 errors.
|
||||||
MAX_MESSAGES_PER_SECOND (:obj:`int`): 30
|
MAX_MESSAGES_PER_SECOND (:obj:`int`): 30
|
||||||
|
@ -47,6 +49,7 @@ MAX_CAPTION_LENGTH = 1024
|
||||||
SUPPORTED_WEBHOOK_PORTS = [443, 80, 88, 8443]
|
SUPPORTED_WEBHOOK_PORTS = [443, 80, 88, 8443]
|
||||||
MAX_FILESIZE_DOWNLOAD = int(20E6) # (20MB)
|
MAX_FILESIZE_DOWNLOAD = int(20E6) # (20MB)
|
||||||
MAX_FILESIZE_UPLOAD = int(50E6) # (50MB)
|
MAX_FILESIZE_UPLOAD = int(50E6) # (50MB)
|
||||||
|
MAX_PHOTOSIZE_UPLOAD = int(10E6) # (10MB)
|
||||||
MAX_MESSAGES_PER_SECOND_PER_CHAT = 1
|
MAX_MESSAGES_PER_SECOND_PER_CHAT = 1
|
||||||
MAX_MESSAGES_PER_SECOND = 30
|
MAX_MESSAGES_PER_SECOND = 30
|
||||||
MAX_MESSAGES_PER_MINUTE_PER_GROUP = 20
|
MAX_MESSAGES_PER_MINUTE_PER_GROUP = 20
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import warnings
|
import warnings
|
||||||
|
from threading import Lock
|
||||||
|
|
||||||
from telegram import Update
|
from telegram import Update
|
||||||
from telegram.ext import (Handler, CallbackQueryHandler, InlineQueryHandler,
|
from telegram.ext import (Handler, CallbackQueryHandler, InlineQueryHandler,
|
||||||
|
@ -37,8 +38,7 @@ class _ConversationTimeoutContext(object):
|
||||||
class ConversationHandler(Handler):
|
class ConversationHandler(Handler):
|
||||||
"""
|
"""
|
||||||
A handler to hold a conversation with a single user by managing four collections of other
|
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
|
handlers.
|
||||||
users are managed by instances of this class.
|
|
||||||
|
|
||||||
The first collection, a ``list`` named :attr:`entry_points`, is used to initiate the
|
The first collection, a ``list`` named :attr:`entry_points`, is used to initiate the
|
||||||
conversation, for example with a :class:`telegram.ext.CommandHandler` or
|
conversation, for example with a :class:`telegram.ext.CommandHandler` or
|
||||||
|
@ -184,7 +184,9 @@ class ConversationHandler(Handler):
|
||||||
self.map_to_parent = map_to_parent
|
self.map_to_parent = map_to_parent
|
||||||
|
|
||||||
self.timeout_jobs = dict()
|
self.timeout_jobs = dict()
|
||||||
|
self._timeout_jobs_lock = Lock()
|
||||||
self.conversations = dict()
|
self.conversations = dict()
|
||||||
|
self._conversations_lock = Lock()
|
||||||
|
|
||||||
self.logger = logging.getLogger(__name__)
|
self.logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -262,6 +264,7 @@ class ConversationHandler(Handler):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
key = self._get_key(update)
|
key = self._get_key(update)
|
||||||
|
with self._conversations_lock:
|
||||||
state = self.conversations.get(key)
|
state = self.conversations.get(key)
|
||||||
|
|
||||||
# Resolve promises
|
# Resolve promises
|
||||||
|
@ -281,6 +284,7 @@ class ConversationHandler(Handler):
|
||||||
if res is None and old_state is None:
|
if res is None and old_state is None:
|
||||||
res = self.END
|
res = self.END
|
||||||
self.update_state(res, key)
|
self.update_state(res, key)
|
||||||
|
with self._conversations_lock:
|
||||||
state = self.conversations.get(key)
|
state = self.conversations.get(key)
|
||||||
else:
|
else:
|
||||||
handlers = self.states.get(self.WAITING, [])
|
handlers = self.states.get(self.WAITING, [])
|
||||||
|
@ -340,12 +344,19 @@ class ConversationHandler(Handler):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
conversation_key, handler, check_result = check_result
|
conversation_key, handler, check_result = check_result
|
||||||
new_state = handler.handle_update(update, dispatcher, check_result, context)
|
|
||||||
|
with self._timeout_jobs_lock:
|
||||||
|
# Remove the old timeout job (if present)
|
||||||
timeout_job = self.timeout_jobs.pop(conversation_key, None)
|
timeout_job = self.timeout_jobs.pop(conversation_key, None)
|
||||||
|
|
||||||
if timeout_job is not None:
|
if timeout_job is not None:
|
||||||
timeout_job.schedule_removal()
|
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:
|
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.timeout_jobs[conversation_key] = dispatcher.job_queue.run_once(
|
||||||
self._trigger_timeout, self.conversation_timeout,
|
self._trigger_timeout, self.conversation_timeout,
|
||||||
context=_ConversationTimeoutContext(conversation_key, update, dispatcher))
|
context=_ConversationTimeoutContext(conversation_key, update, dispatcher))
|
||||||
|
@ -358,6 +369,7 @@ class ConversationHandler(Handler):
|
||||||
|
|
||||||
def update_state(self, new_state, key):
|
def update_state(self, new_state, key):
|
||||||
if new_state == self.END:
|
if new_state == self.END:
|
||||||
|
with self._conversations_lock:
|
||||||
if key in self.conversations:
|
if key in self.conversations:
|
||||||
# If there is no key in conversations, nothing is done.
|
# If there is no key in conversations, nothing is done.
|
||||||
del self.conversations[key]
|
del self.conversations[key]
|
||||||
|
@ -365,12 +377,14 @@ class ConversationHandler(Handler):
|
||||||
self.persistence.update_conversation(self.name, key, None)
|
self.persistence.update_conversation(self.name, key, None)
|
||||||
|
|
||||||
elif isinstance(new_state, Promise):
|
elif isinstance(new_state, Promise):
|
||||||
|
with self._conversations_lock:
|
||||||
self.conversations[key] = (self.conversations.get(key), new_state)
|
self.conversations[key] = (self.conversations.get(key), new_state)
|
||||||
if self.persistent:
|
if self.persistent:
|
||||||
self.persistence.update_conversation(self.name, key,
|
self.persistence.update_conversation(self.name, key,
|
||||||
(self.conversations.get(key), new_state))
|
(self.conversations.get(key), new_state))
|
||||||
|
|
||||||
elif new_state is not None:
|
elif new_state is not None:
|
||||||
|
with self._conversations_lock:
|
||||||
self.conversations[key] = new_state
|
self.conversations[key] = new_state
|
||||||
if self.persistent:
|
if self.persistent:
|
||||||
self.persistence.update_conversation(self.name, key, new_state)
|
self.persistence.update_conversation(self.name, key, new_state)
|
||||||
|
@ -380,11 +394,17 @@ class ConversationHandler(Handler):
|
||||||
|
|
||||||
# Backward compatibility with bots that do not use CallbackContext
|
# Backward compatibility with bots that do not use CallbackContext
|
||||||
if isinstance(context, CallbackContext):
|
if isinstance(context, CallbackContext):
|
||||||
context = context.job.context
|
job = context.job
|
||||||
else:
|
|
||||||
context = job.context
|
context = job.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, [])
|
handlers = self.states.get(self.TIMEOUT, [])
|
||||||
for handler in handlers:
|
for handler in handlers:
|
||||||
check = handler.check_update(context.update)
|
check = handler.check_update(context.update)
|
||||||
|
|
|
@ -909,6 +909,39 @@ officedocument.wordprocessingml.document")``-
|
||||||
return message.from_user.language_code and any(
|
return message.from_user.language_code and any(
|
||||||
[message.from_user.language_code.startswith(x) for x in self.lang])
|
[message.from_user.language_code.startswith(x) for x in self.lang])
|
||||||
|
|
||||||
|
class msg_in(BaseFilter):
|
||||||
|
"""Filters messages to only allow those whose text/caption appears in a given list.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
A simple usecase 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.msg_in(buttons), callback_method)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
list_ (List[:obj:`str`]): Which messages to allow through. Only exact matches
|
||||||
|
are allowed.
|
||||||
|
caption (:obj:`bool`): Optional. Whether the caption should be used instead of text.
|
||||||
|
Default is ``False``.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, list_, caption=False):
|
||||||
|
self.list_ = list_
|
||||||
|
self.caption = caption
|
||||||
|
self.name = 'Filters.msg_in({!r}, caption={!r})'.format(self.list_, self.caption)
|
||||||
|
|
||||||
|
def filter(self, message):
|
||||||
|
if self.caption:
|
||||||
|
txt = message.caption
|
||||||
|
else:
|
||||||
|
txt = message.text
|
||||||
|
|
||||||
|
return txt in self.list_
|
||||||
|
|
||||||
class _UpdateType(BaseFilter):
|
class _UpdateType(BaseFilter):
|
||||||
update_filter = True
|
update_filter = True
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,8 @@ class JobQueue(object):
|
||||||
Attributes:
|
Attributes:
|
||||||
_queue (:obj:`PriorityQueue`): The queue that holds the Jobs.
|
_queue (:obj:`PriorityQueue`): The queue that holds the Jobs.
|
||||||
bot (:class:`telegram.Bot`): The bot instance that should be passed to 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):
|
def __init__(self, bot=None):
|
||||||
|
@ -68,6 +69,13 @@ class JobQueue(object):
|
||||||
self._running = False
|
self._running = False
|
||||||
|
|
||||||
def set_dispatcher(self, dispatcher):
|
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
|
self._dispatcher = dispatcher
|
||||||
|
|
||||||
def _put(self, job, next_t=None, last_t=None):
|
def _put(self, job, next_t=None, last_t=None):
|
||||||
|
|
|
@ -742,13 +742,14 @@ class Message(TelegramObject):
|
||||||
self._quote(kwargs)
|
self._quote(kwargs)
|
||||||
return self.bot.send_poll(self.chat_id, *args, **kwargs)
|
return self.bot.send_poll(self.chat_id, *args, **kwargs)
|
||||||
|
|
||||||
def forward(self, chat_id, disable_notification=False):
|
def forward(self, chat_id, *args, **kwargs):
|
||||||
"""Shortcut for::
|
"""Shortcut for::
|
||||||
|
|
||||||
bot.forward_message(chat_id=chat_id,
|
bot.forward_message(chat_id=chat_id,
|
||||||
from_chat_id=update.message.chat_id,
|
from_chat_id=update.message.chat_id,
|
||||||
disable_notification=disable_notification,
|
message_id=update.message.message_id,
|
||||||
message_id=update.message.message_id)
|
*args,
|
||||||
|
**kwargs)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
:class:`telegram.Message`: On success, instance representing the message forwarded.
|
:class:`telegram.Message`: On success, instance representing the message forwarded.
|
||||||
|
@ -757,8 +758,9 @@ class Message(TelegramObject):
|
||||||
return self.bot.forward_message(
|
return self.bot.forward_message(
|
||||||
chat_id=chat_id,
|
chat_id=chat_id,
|
||||||
from_chat_id=self.chat_id,
|
from_chat_id=self.chat_id,
|
||||||
disable_notification=disable_notification,
|
message_id=self.message_id,
|
||||||
message_id=self.message_id)
|
*args,
|
||||||
|
**kwargs)
|
||||||
|
|
||||||
def edit_text(self, *args, **kwargs):
|
def edit_text(self, *args, **kwargs):
|
||||||
"""Shortcut for::
|
"""Shortcut for::
|
||||||
|
|
|
@ -17,11 +17,10 @@
|
||||||
# You should have received a copy of the GNU Lesser Public License
|
# You should have received a copy of the GNU Lesser Public License
|
||||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||||
"""Provide a bot to tests"""
|
"""Provide a bot to tests"""
|
||||||
|
import json
|
||||||
|
import base64
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
import sys
|
|
||||||
|
|
||||||
from platform import python_implementation
|
|
||||||
|
|
||||||
# Provide some public fallbacks so it's easy for contributors to run tests on their local machine
|
# Provide some public fallbacks so it's easy for contributors to run tests on their local machine
|
||||||
# These bots are only able to talk in our test chats, so they are quite useless for other
|
# These bots are only able to talk in our test chats, so they are quite useless for other
|
||||||
|
@ -32,32 +31,42 @@ FALLBACKS = [
|
||||||
'payment_provider_token': '284685063:TEST:NjQ0NjZlNzI5YjJi',
|
'payment_provider_token': '284685063:TEST:NjQ0NjZlNzI5YjJi',
|
||||||
'chat_id': '675666224',
|
'chat_id': '675666224',
|
||||||
'super_group_id': '-1001493296829',
|
'super_group_id': '-1001493296829',
|
||||||
'channel_id': '@pythontelegrambottests'
|
'channel_id': '@pythontelegrambottests',
|
||||||
|
'bot_name': 'PTB tests fallback 1',
|
||||||
|
'bot_username': '@ptb_fallback_1_bot'
|
||||||
}, {
|
}, {
|
||||||
'token': '558194066:AAEEylntuKSLXj9odiv3TnX7Z5KY2J3zY3M',
|
'token': '558194066:AAEEylntuKSLXj9odiv3TnX7Z5KY2J3zY3M',
|
||||||
'payment_provider_token': '284685063:TEST:YjEwODQwMTFmNDcy',
|
'payment_provider_token': '284685063:TEST:YjEwODQwMTFmNDcy',
|
||||||
'chat_id': '675666224',
|
'chat_id': '675666224',
|
||||||
'super_group_id': '-1001493296829',
|
'super_group_id': '-1001493296829',
|
||||||
'channel_id': '@pythontelegrambottests'
|
'channel_id': '@pythontelegrambottests',
|
||||||
|
'bot_name': 'PTB tests fallback 2',
|
||||||
|
'bot_username': '@ptb_fallback_2_bot'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
GITHUB_ACTION = os.getenv('GITHUB_ACTION', None)
|
||||||
|
BOTS = os.getenv('BOTS', None)
|
||||||
|
JOB_INDEX = os.getenv('JOB_INDEX', None)
|
||||||
|
if GITHUB_ACTION is not None and BOTS is not None and JOB_INDEX is not None:
|
||||||
|
BOTS = json.loads(base64.b64decode(BOTS).decode('utf-8'))
|
||||||
|
JOB_INDEX = int(JOB_INDEX)
|
||||||
|
|
||||||
|
|
||||||
def get(name, fallback):
|
def get(name, fallback):
|
||||||
full_name = '{0}_{1}_{2[0]}{2[1]}'.format(name, python_implementation(),
|
# If we have TOKEN, PAYMENT_PROVIDER_TOKEN, CHAT_ID, SUPER_GROUP_ID,
|
||||||
sys.version_info).upper()
|
# CHANNEL_ID, BOT_NAME, or BOT_USERNAME in the environment, then use that
|
||||||
# First try full_names such as
|
|
||||||
# TOKEN_CPYTHON_33
|
|
||||||
# CHAT_ID_PYPY_27
|
|
||||||
val = os.getenv(full_name)
|
|
||||||
if val:
|
|
||||||
return val
|
|
||||||
# Then try short names
|
|
||||||
# TOKEN
|
|
||||||
# CHAT_ID
|
|
||||||
val = os.getenv(name.upper())
|
val = os.getenv(name.upper())
|
||||||
if val:
|
if val:
|
||||||
return val
|
return val
|
||||||
|
|
||||||
|
# If we're running as a github action then fetch bots from the repo secrets
|
||||||
|
if GITHUB_ACTION is not None and BOTS is not None and JOB_INDEX is not None:
|
||||||
|
try:
|
||||||
|
return BOTS[JOB_INDEX][name]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
# Otherwise go with the fallback
|
# Otherwise go with the fallback
|
||||||
return fallback
|
return fallback
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,11 @@ TRAVIS = os.getenv('TRAVIS', False)
|
||||||
if TRAVIS:
|
if TRAVIS:
|
||||||
pytest_plugins = ['tests.travis_fold']
|
pytest_plugins = ['tests.travis_fold']
|
||||||
|
|
||||||
|
GITHUB_ACTION = os.getenv('GITHUB_ACTION', False)
|
||||||
|
|
||||||
|
if GITHUB_ACTION:
|
||||||
|
pytest_plugins = ['tests.plugin_github_group']
|
||||||
|
|
||||||
# THIS KEY IS OBVIOUSLY COMPROMISED
|
# THIS KEY IS OBVIOUSLY COMPROMISED
|
||||||
# DO NOT USE IN PRODUCTION!
|
# DO NOT USE IN PRODUCTION!
|
||||||
PRIVATE_KEY = b"-----BEGIN RSA PRIVATE KEY-----\r\nMIIEowIBAAKCAQEA0AvEbNaOnfIL3GjB8VI4M5IaWe+GcK8eSPHkLkXREIsaddum\r\nwPBm/+w8lFYdnY+O06OEJrsaDtwGdU//8cbGJ/H/9cJH3dh0tNbfszP7nTrQD+88\r\nydlcYHzClaG8G+oTe9uEZSVdDXj5IUqR0y6rDXXb9tC9l+oSz+ShYg6+C4grAb3E\r\nSTv5khZ9Zsi/JEPWStqNdpoNuRh7qEYc3t4B/a5BH7bsQENyJSc8AWrfv+drPAEe\r\njQ8xm1ygzWvJp8yZPwOIYuL+obtANcoVT2G2150Wy6qLC0bD88Bm40GqLbSazueC\r\nRHZRug0B9rMUKvKc4FhG4AlNzBCaKgIcCWEqKwIDAQABAoIBACcIjin9d3Sa3S7V\r\nWM32JyVF3DvTfN3XfU8iUzV7U+ZOswA53eeFM04A/Ly4C4ZsUNfUbg72O8Vd8rg/\r\n8j1ilfsYpHVvphwxaHQlfIMa1bKCPlc/A6C7b2GLBtccKTbzjARJA2YWxIaqk9Nz\r\nMjj1IJK98i80qt29xRnMQ5sqOO3gn2SxTErvNchtBiwOH8NirqERXig8VCY6fr3n\r\nz7ZImPU3G/4qpD0+9ULrt9x/VkjqVvNdK1l7CyAuve3D7ha3jPMfVHFtVH5gqbyp\r\nKotyIHAyD+Ex3FQ1JV+H7DkP0cPctQiss7OiO9Zd9C1G2OrfQz9el7ewAPqOmZtC\r\nKjB3hUECgYEA/4MfKa1cvaCqzd3yUprp1JhvssVkhM1HyucIxB5xmBcVLX2/Kdhn\r\nhiDApZXARK0O9IRpFF6QVeMEX7TzFwB6dfkyIePsGxputA5SPbtBlHOvjZa8omMl\r\nEYfNa8x/mJkvSEpzvkWPascuHJWv1cEypqphu/70DxubWB5UKo/8o6cCgYEA0HFy\r\ncgwPMB//nltHGrmaQZPFT7/Qgl9ErZT3G9S8teWY4o4CXnkdU75tBoKAaJnpSfX3\r\nq8VuRerF45AFhqCKhlG4l51oW7TUH50qE3GM+4ivaH5YZB3biwQ9Wqw+QyNLAh/Q\r\nnS4/Wwb8qC9QuyEgcCju5lsCaPEXZiZqtPVxZd0CgYEAshBG31yZjO0zG1TZUwfy\r\nfN3euc8mRgZpSdXIHiS5NSyg7Zr8ZcUSID8jAkJiQ3n3OiAsuq1MGQ6kNa582kLT\r\nFPQdI9Ea8ahyDbkNR0gAY9xbM2kg/Gnro1PorH9PTKE0ekSodKk1UUyNrg4DBAwn\r\nqE6E3ebHXt/2WmqIbUD653ECgYBQCC8EAQNX3AFegPd1GGxU33Lz4tchJ4kMCNU0\r\nN2NZh9VCr3nTYjdTbxsXU8YP44CCKFG2/zAO4kymyiaFAWEOn5P7irGF/JExrjt4\r\nibGy5lFLEq/HiPtBjhgsl1O0nXlwUFzd7OLghXc+8CPUJaz5w42unqT3PBJa40c3\r\nQcIPdQKBgBnSb7BcDAAQ/Qx9juo/RKpvhyeqlnp0GzPSQjvtWi9dQRIu9Pe7luHc\r\nm1Img1EO1OyE3dis/rLaDsAa2AKu1Yx6h85EmNjavBqP9wqmFa0NIQQH8fvzKY3/\r\nP8IHY6009aoamLqYaexvrkHVq7fFKiI6k8myMJ6qblVNFv14+KXU\r\n-----END RSA PRIVATE KEY-----" # noqa: E501
|
PRIVATE_KEY = b"-----BEGIN RSA PRIVATE KEY-----\r\nMIIEowIBAAKCAQEA0AvEbNaOnfIL3GjB8VI4M5IaWe+GcK8eSPHkLkXREIsaddum\r\nwPBm/+w8lFYdnY+O06OEJrsaDtwGdU//8cbGJ/H/9cJH3dh0tNbfszP7nTrQD+88\r\nydlcYHzClaG8G+oTe9uEZSVdDXj5IUqR0y6rDXXb9tC9l+oSz+ShYg6+C4grAb3E\r\nSTv5khZ9Zsi/JEPWStqNdpoNuRh7qEYc3t4B/a5BH7bsQENyJSc8AWrfv+drPAEe\r\njQ8xm1ygzWvJp8yZPwOIYuL+obtANcoVT2G2150Wy6qLC0bD88Bm40GqLbSazueC\r\nRHZRug0B9rMUKvKc4FhG4AlNzBCaKgIcCWEqKwIDAQABAoIBACcIjin9d3Sa3S7V\r\nWM32JyVF3DvTfN3XfU8iUzV7U+ZOswA53eeFM04A/Ly4C4ZsUNfUbg72O8Vd8rg/\r\n8j1ilfsYpHVvphwxaHQlfIMa1bKCPlc/A6C7b2GLBtccKTbzjARJA2YWxIaqk9Nz\r\nMjj1IJK98i80qt29xRnMQ5sqOO3gn2SxTErvNchtBiwOH8NirqERXig8VCY6fr3n\r\nz7ZImPU3G/4qpD0+9ULrt9x/VkjqVvNdK1l7CyAuve3D7ha3jPMfVHFtVH5gqbyp\r\nKotyIHAyD+Ex3FQ1JV+H7DkP0cPctQiss7OiO9Zd9C1G2OrfQz9el7ewAPqOmZtC\r\nKjB3hUECgYEA/4MfKa1cvaCqzd3yUprp1JhvssVkhM1HyucIxB5xmBcVLX2/Kdhn\r\nhiDApZXARK0O9IRpFF6QVeMEX7TzFwB6dfkyIePsGxputA5SPbtBlHOvjZa8omMl\r\nEYfNa8x/mJkvSEpzvkWPascuHJWv1cEypqphu/70DxubWB5UKo/8o6cCgYEA0HFy\r\ncgwPMB//nltHGrmaQZPFT7/Qgl9ErZT3G9S8teWY4o4CXnkdU75tBoKAaJnpSfX3\r\nq8VuRerF45AFhqCKhlG4l51oW7TUH50qE3GM+4ivaH5YZB3biwQ9Wqw+QyNLAh/Q\r\nnS4/Wwb8qC9QuyEgcCju5lsCaPEXZiZqtPVxZd0CgYEAshBG31yZjO0zG1TZUwfy\r\nfN3euc8mRgZpSdXIHiS5NSyg7Zr8ZcUSID8jAkJiQ3n3OiAsuq1MGQ6kNa582kLT\r\nFPQdI9Ea8ahyDbkNR0gAY9xbM2kg/Gnro1PorH9PTKE0ekSodKk1UUyNrg4DBAwn\r\nqE6E3ebHXt/2WmqIbUD653ECgYBQCC8EAQNX3AFegPd1GGxU33Lz4tchJ4kMCNU0\r\nN2NZh9VCr3nTYjdTbxsXU8YP44CCKFG2/zAO4kymyiaFAWEOn5P7irGF/JExrjt4\r\nibGy5lFLEq/HiPtBjhgsl1O0nXlwUFzd7OLghXc+8CPUJaz5w42unqT3PBJa40c3\r\nQcIPdQKBgBnSb7BcDAAQ/Qx9juo/RKpvhyeqlnp0GzPSQjvtWi9dQRIu9Pe7luHc\r\nm1Img1EO1OyE3dis/rLaDsAa2AKu1Yx6h85EmNjavBqP9wqmFa0NIQQH8fvzKY3/\r\nP8IHY6009aoamLqYaexvrkHVq7fFKiI6k8myMJ6qblVNFv14+KXU\r\n-----END RSA PRIVATE KEY-----" # noqa: E501
|
||||||
|
|
77
tests/plugin_github_group.py
Normal file
77
tests/plugin_github_group.py
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# A library that provides a Python interface to the Telegram Bot API
|
||||||
|
# Copyright (C) 2015-2018
|
||||||
|
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Lesser Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser Public License
|
||||||
|
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||||
|
import _pytest.config
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
fold_plugins = {'_cov': 'Coverage report', 'flaky': 'Flaky report'}
|
||||||
|
|
||||||
|
|
||||||
|
def terminal_summary_wrapper(original, plugin_name):
|
||||||
|
text = fold_plugins[plugin_name]
|
||||||
|
|
||||||
|
def pytest_terminal_summary(terminalreporter):
|
||||||
|
terminalreporter.write('##[group] {}\n'.format(text))
|
||||||
|
original(terminalreporter)
|
||||||
|
terminalreporter.write('##[endgroup]')
|
||||||
|
|
||||||
|
return pytest_terminal_summary
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.trylast
|
||||||
|
def pytest_configure(config):
|
||||||
|
for hookimpl in config.pluginmanager.hook.pytest_terminal_summary._nonwrappers:
|
||||||
|
if hookimpl.plugin_name in fold_plugins.keys():
|
||||||
|
hookimpl.function = terminal_summary_wrapper(hookimpl.function,
|
||||||
|
hookimpl.plugin_name)
|
||||||
|
|
||||||
|
|
||||||
|
terminal = None
|
||||||
|
previous_name = None
|
||||||
|
|
||||||
|
|
||||||
|
def _get_name(location):
|
||||||
|
if location[0].startswith('tests/'):
|
||||||
|
return location[0][6:]
|
||||||
|
return location[0]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.trylast
|
||||||
|
def pytest_itemcollected(item):
|
||||||
|
item._nodeid = item._nodeid.split('::', 1)[1]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
|
||||||
|
def pytest_runtest_protocol(item, nextitem):
|
||||||
|
# This is naughty but pytests' own plugins does something similar too, so who cares
|
||||||
|
global terminal
|
||||||
|
if terminal is None:
|
||||||
|
terminal = _pytest.config.create_terminal_writer(item.config)
|
||||||
|
|
||||||
|
global previous_name
|
||||||
|
|
||||||
|
name = _get_name(item.location)
|
||||||
|
|
||||||
|
if previous_name is None or previous_name != name:
|
||||||
|
previous_name = name
|
||||||
|
terminal.write('\n##[group] {}'.format(name))
|
||||||
|
|
||||||
|
yield
|
||||||
|
|
||||||
|
if nextitem is None or _get_name(nextitem.location) != name:
|
||||||
|
terminal.write('\n##[endgroup]')
|
|
@ -114,13 +114,14 @@ class TestBot(object):
|
||||||
@pytest.mark.timeout(10)
|
@pytest.mark.timeout(10)
|
||||||
def test_delete_message(self, bot, chat_id):
|
def test_delete_message(self, bot, chat_id):
|
||||||
message = bot.send_message(chat_id, text='will be deleted')
|
message = bot.send_message(chat_id, text='will be deleted')
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
assert bot.delete_message(chat_id=chat_id, message_id=message.message_id) is True
|
assert bot.delete_message(chat_id=chat_id, message_id=message.message_id) is True
|
||||||
|
|
||||||
@flaky(3, 1)
|
@flaky(3, 1)
|
||||||
@pytest.mark.timeout(10)
|
@pytest.mark.timeout(10)
|
||||||
def test_delete_message_old_message(self, bot, chat_id):
|
def test_delete_message_old_message(self, bot, chat_id):
|
||||||
with pytest.raises(TelegramError, match='Message to delete not found'):
|
with pytest.raises(BadRequest):
|
||||||
# Considering that the first message is old enough
|
# Considering that the first message is old enough
|
||||||
bot.delete_message(chat_id=chat_id, message_id=1)
|
bot.delete_message(chat_id=chat_id, message_id=1)
|
||||||
|
|
||||||
|
|
|
@ -613,6 +613,53 @@ class TestConversationHandler(object):
|
||||||
assert handler.conversations.get((self.group.id, user1.id)) is None
|
assert handler.conversations.get((self.group.id, user1.id)) is None
|
||||||
assert not self.is_timeout
|
assert not self.is_timeout
|
||||||
|
|
||||||
|
def test_conversation_timeout_cancel_conflict(self, dp, bot, user1):
|
||||||
|
# Start state machine, wait half the timeout,
|
||||||
|
# then call a callback that takes more than the timeout
|
||||||
|
# t=0 /start (timeout=.5)
|
||||||
|
# t=.25 /slowbrew (sleep .5)
|
||||||
|
# | t=.5 original timeout (should not execute)
|
||||||
|
# | t=.75 /slowbrew returns (timeout=1.25)
|
||||||
|
# t=1.25 timeout
|
||||||
|
|
||||||
|
def slowbrew(_bot, update):
|
||||||
|
sleep(0.25)
|
||||||
|
# Let's give to the original timeout a chance to execute
|
||||||
|
dp.job_queue.tick()
|
||||||
|
sleep(0.25)
|
||||||
|
# By returning None we do not override the conversation state so
|
||||||
|
# we can see if the timeout has been executed
|
||||||
|
|
||||||
|
states = self.states
|
||||||
|
states[self.THIRSTY].append(CommandHandler('slowbrew', slowbrew))
|
||||||
|
states.update({ConversationHandler.TIMEOUT: [
|
||||||
|
MessageHandler(None, self.passout2)
|
||||||
|
]})
|
||||||
|
|
||||||
|
handler = ConversationHandler(entry_points=self.entry_points, states=states,
|
||||||
|
fallbacks=self.fallbacks, conversation_timeout=0.5)
|
||||||
|
dp.add_handler(handler)
|
||||||
|
|
||||||
|
# CommandHandler timeout
|
||||||
|
message = Message(0, user1, None, self.group, text='/start',
|
||||||
|
entities=[MessageEntity(type=MessageEntity.BOT_COMMAND, offset=0,
|
||||||
|
length=len('/start'))],
|
||||||
|
bot=bot)
|
||||||
|
dp.process_update(Update(update_id=0, message=message))
|
||||||
|
sleep(0.25)
|
||||||
|
dp.job_queue.tick()
|
||||||
|
message.text = '/slowbrew'
|
||||||
|
message.entities[0].length = len('/slowbrew')
|
||||||
|
dp.process_update(Update(update_id=0, message=message))
|
||||||
|
dp.job_queue.tick()
|
||||||
|
assert handler.conversations.get((self.group.id, user1.id)) is not None
|
||||||
|
assert not self.is_timeout
|
||||||
|
|
||||||
|
sleep(0.5)
|
||||||
|
dp.job_queue.tick()
|
||||||
|
assert handler.conversations.get((self.group.id, user1.id)) is None
|
||||||
|
assert self.is_timeout
|
||||||
|
|
||||||
def test_per_message_warning_is_only_shown_once(self, recwarn):
|
def test_per_message_warning_is_only_shown_once(self, recwarn):
|
||||||
ConversationHandler(
|
ConversationHandler(
|
||||||
entry_points=self.entry_points,
|
entry_points=self.entry_points,
|
||||||
|
|
|
@ -604,6 +604,16 @@ class TestFilters(object):
|
||||||
update.message.from_user.language_code = 'da'
|
update.message.from_user.language_code = 'da'
|
||||||
assert f(update)
|
assert f(update)
|
||||||
|
|
||||||
|
def test_msg_in_filter(self, update):
|
||||||
|
update.message.text = 'test'
|
||||||
|
update.message.caption = 'caption'
|
||||||
|
|
||||||
|
assert Filters.msg_in(['test'])(update)
|
||||||
|
assert Filters.msg_in(['caption'], caption=True)(update)
|
||||||
|
|
||||||
|
assert not Filters.msg_in(['test'], caption=True)(update)
|
||||||
|
assert not Filters.msg_in(['caption'])(update)
|
||||||
|
|
||||||
def test_and_filters(self, update):
|
def test_and_filters(self, update):
|
||||||
update.message.text = 'test'
|
update.message.text = 'test'
|
||||||
update.message.forward_date = datetime.datetime.now()
|
update.message.forward_date = datetime.datetime.now()
|
||||||
|
|
|
@ -40,6 +40,8 @@ def job_queue(bot, _dp):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(os.getenv('APPVEYOR'), reason="On Appveyor precise timings are not accurate.")
|
@pytest.mark.skipif(os.getenv('APPVEYOR'), reason="On Appveyor precise timings are not accurate.")
|
||||||
|
@pytest.mark.skipif(os.getenv('GITHUB_ACTIONS', False) and os.name == 'nt',
|
||||||
|
reason="On windows precise timings are not accurate.")
|
||||||
@flaky(10, 1) # Timings aren't quite perfect
|
@flaky(10, 1) # Timings aren't quite perfect
|
||||||
class TestJobQueue(object):
|
class TestJobQueue(object):
|
||||||
result = 0
|
result = 0
|
||||||
|
|
|
@ -26,6 +26,8 @@ import telegram.ext.messagequeue as mq
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(os.getenv('APPVEYOR'), reason="On Appveyor precise timings are not accurate.")
|
@pytest.mark.skipif(os.getenv('APPVEYOR'), reason="On Appveyor precise timings are not accurate.")
|
||||||
|
@pytest.mark.skipif(os.getenv('GITHUB_ACTIONS', False) and os.name == 'nt',
|
||||||
|
reason="On windows precise timings are not accurate.")
|
||||||
class TestDelayQueue(object):
|
class TestDelayQueue(object):
|
||||||
N = 128
|
N = 128
|
||||||
burst_limit = 30
|
burst_limit = 30
|
||||||
|
|
|
@ -17,8 +17,6 @@
|
||||||
# You should have received a copy of the GNU Lesser Public License
|
# You should have received a copy of the GNU Lesser Public License
|
||||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
from platform import python_implementation
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
@ -30,17 +28,12 @@ def call_pre_commit_hook(hook_id):
|
||||||
|
|
||||||
@pytest.mark.nocoverage
|
@pytest.mark.nocoverage
|
||||||
@pytest.mark.parametrize('hook_id', argvalues=('yapf', 'flake8', 'pylint'))
|
@pytest.mark.parametrize('hook_id', argvalues=('yapf', 'flake8', 'pylint'))
|
||||||
@pytest.mark.skipif(not (os.getenv('TRAVIS') or os.getenv('APPVEYOR')), reason='Not running in CI')
|
@pytest.mark.skipif(not os.getenv('TEST_PRE_COMMIT', False), reason='TEST_PRE_COMMIT not enabled')
|
||||||
@pytest.mark.skipif(not sys.version_info[:2] == (3, 6) or python_implementation() != 'CPython',
|
|
||||||
reason='Only running pre-commit-hooks on newest tested python version, '
|
|
||||||
'as they are slow and consistent across platforms.')
|
|
||||||
def test_pre_commit_hook(hook_id):
|
def test_pre_commit_hook(hook_id):
|
||||||
assert call_pre_commit_hook(hook_id) == 0 # pragma: no cover
|
assert call_pre_commit_hook(hook_id) == 0 # pragma: no cover
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.nocoverage
|
@pytest.mark.nocoverage
|
||||||
@pytest.mark.skipif(
|
@pytest.mark.skipif(not os.getenv('TEST_BUILD', False), reason='TEST_BUILD not enabled')
|
||||||
not sys.version_info[:2] in ((3, 6), (2, 7)) or python_implementation() != 'CPython',
|
|
||||||
reason='Only testing build on 2.7 and 3.6')
|
|
||||||
def test_build():
|
def test_build():
|
||||||
assert os.system('python setup.py bdist_dumb') == 0 # pragma: no cover
|
assert os.system('python setup.py bdist_dumb') == 0 # pragma: no cover
|
||||||
|
|
Loading…
Reference in a new issue