mirror of
https://github.com/python-telegram-bot/python-telegram-bot.git
synced 2026-06-21 08:35:28 +00:00
Compare commits
53 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 |
@@ -117,7 +117,7 @@ Here's how to make a one-off code change.
|
||||
|
||||
.. code-block::
|
||||
|
||||
$ export TEST_OFFICIAL=True
|
||||
$ export TEST_OFFICIAL=true
|
||||
|
||||
prior to running the tests.
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -2,7 +2,7 @@
|
||||
name: Question
|
||||
about: Get help with errors or general questions
|
||||
title: "[QUESTION]"
|
||||
labels: 'question :question:'
|
||||
labels: question
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
@@ -10,7 +10,7 @@ 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 :)
|
||||
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.
|
||||
-->
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 5
|
||||
# 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.
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
runs-on: ${{matrix.os}}
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [3.5, 3.6, 3.7]
|
||||
python-version: [3.5, 3.6, 3.7, 3.8]
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
test-build: True
|
||||
fail-fast: False
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v2
|
||||
- name: Initialize vendored libs
|
||||
run:
|
||||
git submodule update --init --recursive
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
exit ${global_exit}
|
||||
env:
|
||||
JOB_INDEX: ${{ strategy.job-index }}
|
||||
BOTS: W3sidG9rZW4iOiAiNjk2MTg4NzMyOkFBR1Z3RUtmSEhsTmpzY3hFRE5LQXdraEdzdFpfa28xbUMwIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6WldGaU1UUmxNbVF5TnpNeSIsICJib3RfbmFtZSI6ICJQVEIgdGVzdHMgb24gVHJhdmlzIHVzaW5nIENQeXRob24gMi43IiwgImJvdF91c2VybmFtZSI6ICJAcHRiX3RyYXZpc19jcHl0aG9uXzI3X2JvdCJ9LCB7InRva2VuIjogIjY3MTQ2ODg4NjpBQUdQR2ZjaVJJQlVORmU4MjR1SVZkcTdKZTNfWW5BVE5HdyIsICJwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY4NTA2MzpURVNUOlpHWXdPVGxrTXpNeE4yWTIiLCAiYm90X25hbWUiOiAiUFRCIHRlc3RzIG9uIFRyYXZpcyB1c2luZyBDUHl0aG9uIDMuNCIsICJib3RfdXNlcm5hbWUiOiAiQHB0Yl90cmF2aXNfY3B5dGhvbl8zNF9ib3QifSwgeyJ0b2tlbiI6ICI2MjkzMjY1Mzg6QUFGUnJaSnJCN29CM211ekdzR0pYVXZHRTVDUXpNNUNVNG8iLCAicGF5bWVudF9wcm92aWRlcl90b2tlbiI6ICIyODQ2ODUwNjM6VEVTVDpNbU01WVdKaFl6a3hNMlUxIiwgImJvdF9uYW1lIjogIlBUQiB0ZXN0cyBvbiBUcmF2aXMgdXNpbmcgQ1B5dGhvbiAzLjUiLCAiYm90X3VzZXJuYW1lIjogIkBwdGJfdHJhdmlzX2NweXRob25fMzVfYm90In0sIHsidG9rZW4iOiAiNjQwMjA4OTQzOkFBRmhCalFwOXFtM1JUeFN6VXBZekJRakNsZS1Kano1aGNrIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6WXpoa1pUZzFOamMxWXpWbCIsICJib3RfbmFtZSI6ICJQVEIgdGVzdHMgb24gVHJhdmlzIHVzaW5nIENQeXRob24gMy42IiwgImJvdF91c2VybmFtZSI6ICJAcHRiX3RyYXZpc19jcHl0aG9uXzM2X2JvdCJ9LCB7InRva2VuIjogIjY5NTEwNDA4ODpBQUhmenlsSU9qU0lJUy1lT25JMjB5MkUyMEhvZEhzZnotMCIsICJwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY4NTA2MzpURVNUOk9HUTFNRGd3WmpJd1pqRmwiLCAiYm90X25hbWUiOiAiUFRCIHRlc3RzIG9uIFRyYXZpcyB1c2luZyBDUHl0aG9uIDMuNyIsICJib3RfdXNlcm5hbWUiOiAiQHB0Yl90cmF2aXNfY3B5dGhvbl8zN19ib3QifSwgeyJ0b2tlbiI6ICI2OTE0MjM1NTQ6QUFGOFdrakNaYm5IcVBfaTZHaFRZaXJGRWxackdhWU9oWDAiLCAicGF5bWVudF9wcm92aWRlcl90b2tlbiI6ICIyODQ2ODUwNjM6VEVTVDpZamM1TlRoaU1tUXlNV1ZoIiwgImJvdF9uYW1lIjogIlBUQiB0ZXN0cyBvbiBUcmF2aXMgdXNpbmcgUHlQeSAyLjciLCAiYm90X3VzZXJuYW1lIjogIkBwdGJfdHJhdmlzX3B5cHlfMjdfYm90In0sIHsidG9rZW4iOiAiNjg0MzM5OTg0OkFBRk1nRUVqcDAxcjVyQjAwN3lDZFZOc2c4QWxOc2FVLWNjIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6TVRBek1UWTNNR1V5TmpnMCIsICJib3RfbmFtZSI6ICJQVEIgdGVzdHMgb24gVHJhdmlzIHVzaW5nIFB5UHkgMy41IiwgImJvdF91c2VybmFtZSI6ICJAcHRiX3RyYXZpc19weXB5XzM1X2JvdCJ9LCB7InRva2VuIjogIjY5MDA5MTM0NzpBQUZMbVI1cEFCNVljcGVfbU9oN3pNNEpGQk9oMHozVDBUbyIsICJwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY4NTA2MzpURVNUOlpEaGxOekU1TURrd1lXSmkiLCAiYm90X25hbWUiOiAiUFRCIHRlc3RzIG9uIEFwcFZleW9yIHVzaW5nIENQeXRob24gMy40IiwgImJvdF91c2VybmFtZSI6ICJAcHRiX2FwcHZleW9yX2NweXRob25fMzRfYm90In0sIHsidG9rZW4iOiAiNjk0MzA4MDUyOkFBRUIyX3NvbkNrNTVMWTlCRzlBTy1IOGp4aVBTNTVvb0JBIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6WW1aaVlXWm1NakpoWkdNeSIsICJib3RfbmFtZSI6ICJQVEIgdGVzdHMgb24gQXBwVmV5b3IgdXNpbmcgQ1B5dGhvbiAyLjciLCAiYm90X3VzZXJuYW1lIjogIkBwdGJfYXBwdmV5b3JfY3B5dGhvbl8yN19ib3QifV0=
|
||||
BOTS: W3sidG9rZW4iOiAiNjk2MTg4NzMyOkFBR1Z3RUtmSEhsTmpzY3hFRE5LQXdraEdzdFpfa28xbUMwIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6WldGaU1UUmxNbVF5TnpNeSIsICJib3RfbmFtZSI6ICJQVEIgdGVzdHMgb24gVHJhdmlzIHVzaW5nIENQeXRob24gMi43IiwgInN1cGVyX2dyb3VwX2lkIjogIi0xMDAxMzkwOTgzOTk3IiwgImJvdF91c2VybmFtZSI6ICJAcHRiX3RyYXZpc19jcHl0aG9uXzI3X2JvdCJ9LCB7InRva2VuIjogIjY3MTQ2ODg4NjpBQUdQR2ZjaVJJQlVORmU4MjR1SVZkcTdKZTNfWW5BVE5HdyIsICJwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY4NTA2MzpURVNUOlpHWXdPVGxrTXpNeE4yWTIiLCAiYm90X25hbWUiOiAiUFRCIHRlc3RzIG9uIFRyYXZpcyB1c2luZyBDUHl0aG9uIDMuNCIsICJzdXBlcl9ncm91cF9pZCI6ICItMTAwMTQ0NjAyMjUyMiIsICJib3RfdXNlcm5hbWUiOiAiQHB0Yl90cmF2aXNfY3B5dGhvbl8zNF9ib3QifSwgeyJ0b2tlbiI6ICI2MjkzMjY1Mzg6QUFGUnJaSnJCN29CM211ekdzR0pYVXZHRTVDUXpNNUNVNG8iLCAicGF5bWVudF9wcm92aWRlcl90b2tlbiI6ICIyODQ2ODUwNjM6VEVTVDpNbU01WVdKaFl6a3hNMlUxIiwgImJvdF9uYW1lIjogIlBUQiB0ZXN0cyBvbiBUcmF2aXMgdXNpbmcgQ1B5dGhvbiAzLjUiLCAic3VwZXJfZ3JvdXBfaWQiOiAiLTEwMDE0OTY5MTc3NTAiLCAiYm90X3VzZXJuYW1lIjogIkBwdGJfdHJhdmlzX2NweXRob25fMzVfYm90In0sIHsidG9rZW4iOiAiNjQwMjA4OTQzOkFBRmhCalFwOXFtM1JUeFN6VXBZekJRakNsZS1Kano1aGNrIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6WXpoa1pUZzFOamMxWXpWbCIsICJib3RfbmFtZSI6ICJQVEIgdGVzdHMgb24gVHJhdmlzIHVzaW5nIENQeXRob24gMy42IiwgInN1cGVyX2dyb3VwX2lkIjogIi0xMDAxMzMzODcxNDYxIiwgImJvdF91c2VybmFtZSI6ICJAcHRiX3RyYXZpc19jcHl0aG9uXzM2X2JvdCJ9LCB7InRva2VuIjogIjY5NTEwNDA4ODpBQUhmenlsSU9qU0lJUy1lT25JMjB5MkUyMEhvZEhzZnotMCIsICJwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY4NTA2MzpURVNUOk9HUTFNRGd3WmpJd1pqRmwiLCAiYm90X25hbWUiOiAiUFRCIHRlc3RzIG9uIFRyYXZpcyB1c2luZyBDUHl0aG9uIDMuNyIsICJzdXBlcl9ncm91cF9pZCI6ICItMTAwMTQ3ODI5MzcxNCIsICJib3RfdXNlcm5hbWUiOiAiQHB0Yl90cmF2aXNfY3B5dGhvbl8zN19ib3QifSwgeyJ0b2tlbiI6ICI2OTE0MjM1NTQ6QUFGOFdrakNaYm5IcVBfaTZHaFRZaXJGRWxackdhWU9oWDAiLCAicGF5bWVudF9wcm92aWRlcl90b2tlbiI6ICIyODQ2ODUwNjM6VEVTVDpZamM1TlRoaU1tUXlNV1ZoIiwgImJvdF9uYW1lIjogIlBUQiB0ZXN0cyBvbiBUcmF2aXMgdXNpbmcgUHlQeSAyLjciLCAic3VwZXJfZ3JvdXBfaWQiOiAiLTEwMDEzNjM5MzI1NzMiLCAiYm90X3VzZXJuYW1lIjogIkBwdGJfdHJhdmlzX3B5cHlfMjdfYm90In0sIHsidG9rZW4iOiAiNjg0MzM5OTg0OkFBRk1nRUVqcDAxcjVyQjAwN3lDZFZOc2c4QWxOc2FVLWNjIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6TVRBek1UWTNNR1V5TmpnMCIsICJib3RfbmFtZSI6ICJQVEIgdGVzdHMgb24gVHJhdmlzIHVzaW5nIFB5UHkgMy41IiwgInN1cGVyX2dyb3VwX2lkIjogIi0xMDAxNDA3ODM2NjA1IiwgImJvdF91c2VybmFtZSI6ICJAcHRiX3RyYXZpc19weXB5XzM1X2JvdCJ9LCB7InRva2VuIjogIjY5MDA5MTM0NzpBQUZMbVI1cEFCNVljcGVfbU9oN3pNNEpGQk9oMHozVDBUbyIsICJwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY4NTA2MzpURVNUOlpEaGxOekU1TURrd1lXSmkiLCAiYm90X25hbWUiOiAiUFRCIHRlc3RzIG9uIEFwcFZleW9yIHVzaW5nIENQeXRob24gMy40IiwgInN1cGVyX2dyb3VwX2lkIjogIi0xMDAxMjc5NjAwMDI2IiwgImJvdF91c2VybmFtZSI6ICJAcHRiX2FwcHZleW9yX2NweXRob25fMzRfYm90In0sIHsidG9rZW4iOiAiNjk0MzA4MDUyOkFBRUIyX3NvbkNrNTVMWTlCRzlBTy1IOGp4aVBTNTVvb0JBIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6WW1aaVlXWm1NakpoWkdNeSIsICJib3RfbmFtZSI6ICJQVEIgdGVzdHMgb24gQXBwVmV5b3IgdXNpbmcgQ1B5dGhvbiAyLjciLCAic3VwZXJfZ3JvdXBfaWQiOiAiLTEwMDEyOTMwNzkxNjUiLCAiYm90X3VzZXJuYW1lIjogIkBwdGJfYXBwdmV5b3JfY3B5dGhvbl8yN19ib3QifSwgeyJ0b2tlbiI6ICIxMDU1Mzk3NDcxOkFBRzE4bkJfUzJXQXd1SjNnN29oS0JWZ1hYY2VNbklPeVNjIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6TmpBd056QXpZalZpTkdOayIsICJuYW1lIjogIlBUQiB0ZXN0cyBbMF0iLCAic3VwZXJfZ3JvdXBfaWQiOiAiLTEwMDExODU1MDk2MzYiLCAidXNlcm5hbWUiOiAicHRiXzBfYm90In0sIHsidG9rZW4iOiAiMTA0NzMyNjc3MTpBQUY4bk90ODFGcFg4bGJidno4VWV3UVF2UmZUYkZmQnZ1SSIsICJwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY4NTA2MzpURVNUOllUVTFOVEk0WkdSallqbGkiLCAibmFtZSI6ICJQVEIgdGVzdHMgWzFdIiwgInN1cGVyX2dyb3VwX2lkIjogIi0xMDAxNDg0Nzk3NjEyIiwgInVzZXJuYW1lIjogInB0Yl8xX2JvdCJ9LCB7InRva2VuIjogIjk3MTk5Mjc0NTpBQUdPa09hVzBOSGpnSXY1LTlqUWJPajR2R3FkaFNGLVV1cyIsICJwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY4NTA2MzpURVNUOk5XWmtNV1ZoWWpsallqVTUiLCAibmFtZSI6ICJQVEIgdGVzdHMgWzJdIiwgInN1cGVyX2dyb3VwX2lkIjogIi0xMDAxNDAyMjU1MDcwIiwgInVzZXJuYW1lIjogInB0Yl8yX2JvdCJ9XQ==
|
||||
TEST_BUILD: ${{ matrix.test-build }}
|
||||
TEST_PRE_COMMIT: ${{ matrix.test-pre-commit }}
|
||||
shell: bash --noprofile --norc {0}
|
||||
@@ -73,7 +73,7 @@ jobs:
|
||||
os: [ubuntu-latest]
|
||||
fail-fast: False
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v2
|
||||
- name: Initialize vendored libs
|
||||
run:
|
||||
git submodule update --init --recursive
|
||||
@@ -90,7 +90,6 @@ jobs:
|
||||
run: |
|
||||
pytest -v tests/test_official.py
|
||||
exit $?
|
||||
continue-on-error: True
|
||||
env:
|
||||
TEST_OFFICIAL: "true"
|
||||
shell: bash --noprofile --norc {0}
|
||||
@@ -103,7 +102,7 @@ jobs:
|
||||
os: [ubuntu-latest]
|
||||
fail-fast: False
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v2
|
||||
- name: Initialize vendored libs
|
||||
run:
|
||||
git submodule update --init --recursive
|
||||
|
||||
@@ -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,7 @@ 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>`_
|
||||
@@ -36,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>`_
|
||||
|
||||
+135
@@ -2,6 +2,141 @@
|
||||
Changelog
|
||||
=========
|
||||
|
||||
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*
|
||||
|
||||
+1
-1
@@ -93,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
|
||||
|
||||
@@ -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"
|
||||
+2
-2
@@ -58,9 +58,9 @@ author = u'Leandro Toledo'
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '12.4' # telegram.__version__[:3]
|
||||
version = '12.7' # telegram.__version__[:3]
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '12.4.2' # telegram.__version__
|
||||
release = '12.7' # 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.PollAnswerHandler
|
||||
==============================
|
||||
|
||||
.. autoclass:: telegram.ext.PollAnswerHandler
|
||||
:members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,6 @@
|
||||
telegram.ext.PollHandler
|
||||
========================
|
||||
|
||||
.. autoclass:: telegram.ext.PollHandler
|
||||
:members:
|
||||
:show-inheritance:
|
||||
@@ -26,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
|
||||
|
||||
@@ -29,6 +29,10 @@ 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))
|
||||
|
||||
|
||||
|
||||
+14
-25
@@ -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,10 +142,8 @@ 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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
@@ -63,5 +63,6 @@ with codecs.open('README.rst', 'r', 'utf-8') as fd:
|
||||
'Programming Language :: Python :: 3',
|
||||
'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',
|
||||
],)
|
||||
|
||||
@@ -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'
|
||||
]
|
||||
|
||||
@@ -23,13 +23,10 @@ try:
|
||||
except ImportError:
|
||||
import json
|
||||
|
||||
from abc import ABCMeta
|
||||
|
||||
|
||||
class TelegramObject(object):
|
||||
"""Base class for most telegram objects."""
|
||||
|
||||
__metaclass__ = ABCMeta
|
||||
_id_attrs = ()
|
||||
|
||||
def __str__(self):
|
||||
|
||||
+538
-178
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)
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -240,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::
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
#!/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 a dice with random value from 1 to 6 for currently supported base eomji.
|
||||
(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.
|
||||
|
||||
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.
|
||||
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`: '🎯'"""
|
||||
ALL_EMOJI = [DICE, DARTS]
|
||||
"""List[:obj:`str`]: List of all supported base emoji. Currently :attr:`DICE` and
|
||||
:attr:`DARTS`."""
|
||||
@@ -41,6 +41,8 @@ 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',
|
||||
@@ -49,4 +51,5 @@ __all__ = ('Dispatcher', 'JobQueue', 'Job', 'Updater', 'CallbackQueryHandler',
|
||||
'StringRegexHandler', 'TypeHandler', 'ConversationHandler',
|
||||
'PreCheckoutQueryHandler', 'ShippingQueryHandler', 'MessageQueue', 'DelayQueue',
|
||||
'DispatcherHandlerStop', 'run_async', 'CallbackContext', 'BasePersistence',
|
||||
'PicklePersistence', 'DictPersistence', 'PrefixHandler', 'Defaults')
|
||||
'PicklePersistence', 'DictPersistence', 'PrefixHandler', 'PollAnswerHandler',
|
||||
'PollHandler', 'Defaults')
|
||||
|
||||
@@ -18,8 +18,10 @@
|
||||
# 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.
|
||||
|
||||
@@ -57,6 +59,7 @@ class BasePersistence(object):
|
||||
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
|
||||
@@ -65,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
|
||||
@@ -75,8 +78,8 @@ 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
|
||||
@@ -85,8 +88,8 @@ class BasePersistence(object):
|
||||
Returns:
|
||||
:obj:`defaultdict`: The restored bot data.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def get_conversations(self, name):
|
||||
""""Will be called by :class:`telegram.ext.Dispatcher` when a
|
||||
:class:`telegram.ext.ConversationHandler` is added if
|
||||
@@ -99,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.
|
||||
@@ -110,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.
|
||||
@@ -120,8 +123,8 @@ 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.
|
||||
@@ -130,8 +133,8 @@ class BasePersistence(object):
|
||||
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].
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def update_bot_data(self, data):
|
||||
"""Will be called by the :class:`telegram.ext.Dispatcher` after a handler has
|
||||
handled an update.
|
||||
@@ -139,7 +142,6 @@ class BasePersistence(object):
|
||||
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
|
||||
|
||||
@@ -302,6 +302,10 @@ class PrefixHandler(CommandHandler):
|
||||
pass_user_data=False,
|
||||
pass_chat_data=False):
|
||||
|
||||
self._prefix = list()
|
||||
self._command = list()
|
||||
self._commands = list()
|
||||
|
||||
super(PrefixHandler, self).__init__(
|
||||
'nocommand', callback, filters=filters, allow_edited=None, pass_args=pass_args,
|
||||
pass_update_queue=pass_update_queue,
|
||||
@@ -309,15 +313,36 @@ class PrefixHandler(CommandHandler):
|
||||
pass_user_data=pass_user_data,
|
||||
pass_chat_data=pass_chat_data)
|
||||
|
||||
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, string_types):
|
||||
self.prefix = [prefix.lower()]
|
||||
self._prefix = [prefix.lower()]
|
||||
else:
|
||||
self.prefix = prefix
|
||||
self._prefix = prefix
|
||||
self._build_commands()
|
||||
|
||||
@property
|
||||
def command(self):
|
||||
return self._command
|
||||
|
||||
@command.setter
|
||||
def command(self, command):
|
||||
if isinstance(command, string_types):
|
||||
self.command = [command.lower()]
|
||||
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 +359,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:
|
||||
|
||||
@@ -29,10 +29,11 @@ from telegram.utils.promise import Promise
|
||||
|
||||
|
||||
class _ConversationTimeoutContext(object):
|
||||
def __init__(self, conversation_key, update, dispatcher):
|
||||
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):
|
||||
@@ -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,23 +168,23 @@ 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
|
||||
""":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._timeout_jobs_lock = Lock()
|
||||
@@ -225,6 +228,87 @@ 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
|
||||
@@ -385,7 +469,8 @@ class ConversationHandler(Handler):
|
||||
# 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=_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)
|
||||
@@ -422,9 +507,9 @@ class ConversationHandler(Handler):
|
||||
callback_context = None
|
||||
if isinstance(context, CallbackContext):
|
||||
job = context.job
|
||||
callback_context = context
|
||||
|
||||
context = job.context
|
||||
callback_context = context.callback_context
|
||||
|
||||
with self._timeout_jobs_lock:
|
||||
found_job = self.timeout_jobs[context.conversation_key]
|
||||
|
||||
@@ -226,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
|
||||
@@ -238,6 +240,8 @@ 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
|
||||
|
||||
+52
-55
@@ -323,53 +323,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_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 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:
|
||||
@@ -388,13 +341,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.
|
||||
@@ -471,18 +424,62 @@ class Dispatcher(object):
|
||||
del self.handlers[group]
|
||||
self.groups.remove(group)
|
||||
|
||||
def update_persistence(self):
|
||||
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:
|
||||
self.persistence.update_bot_data(self.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
|
||||
|
||||
+100
-27
@@ -21,13 +21,14 @@
|
||||
import re
|
||||
|
||||
from future.utils import string_types
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
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,6 +215,38 @@ class MergedFilter(BaseFilter):
|
||||
self.and_filter or self.or_filter)
|
||||
|
||||
|
||||
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(object):
|
||||
"""Predefined filters for use as the `filter` argument of :class:`telegram.ext.MessageHandler`.
|
||||
|
||||
@@ -236,35 +268,35 @@ class Filters(object):
|
||||
class _Text(BaseFilter):
|
||||
name = 'Filters.text'
|
||||
|
||||
class _TextIterable(BaseFilter):
|
||||
class _TextStrings(BaseFilter):
|
||||
|
||||
def __init__(self, iterable):
|
||||
self.iterable = iterable
|
||||
self.name = 'Filters.text({})'.format(iterable)
|
||||
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.iterable
|
||||
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._TextIterable(update)
|
||||
return self._TextStrings(update)
|
||||
|
||||
def filter(self, message):
|
||||
return bool(message.text)
|
||||
|
||||
text = _Text()
|
||||
"""Text Messages. If an iterable of strings is passed, it filters messages to only allow those
|
||||
whose text is appearing in the given iterable.
|
||||
"""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 an iterable is to allow only messages that were send by a
|
||||
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']
|
||||
@@ -272,44 +304,51 @@ class Filters(object):
|
||||
...
|
||||
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 (Iterable[:obj:`str`], optional): Which messages to allow. Only exact matches
|
||||
are allowed. If not specified, will allow any text message.
|
||||
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 _CaptionIterable(BaseFilter):
|
||||
class _CaptionStrings(BaseFilter):
|
||||
|
||||
def __init__(self, iterable):
|
||||
self.iterable = iterable
|
||||
self.name = 'Filters.caption({})'.format(iterable)
|
||||
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.iterable
|
||||
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._CaptionIterable(update)
|
||||
return self._CaptionStrings(update)
|
||||
|
||||
def filter(self, message):
|
||||
return bool(message.caption)
|
||||
|
||||
caption = _Caption()
|
||||
"""Messages with a caption. If an iterable of strings is passed, it filters messages to only
|
||||
allow those whose caption is appearing in the given iterable.
|
||||
"""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 (Iterable[:obj:`str`], optional): Which captions to allow. Only exact matches
|
||||
are allowed. If not specified, will allow any message with a caption.
|
||||
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):
|
||||
@@ -346,6 +385,9 @@ class Filters(object):
|
||||
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``.
|
||||
@@ -368,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)')
|
||||
@@ -427,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'
|
||||
"""
|
||||
|
||||
@@ -957,6 +999,37 @@ officedocument.wordprocessingml.document")``-
|
||||
poll = _Poll()
|
||||
"""Messages that contain a :class:`telegram.Poll`."""
|
||||
|
||||
class _Dice(_DiceEmoji):
|
||||
dice = _DiceEmoji('🎲', 'dice')
|
||||
darts = _DiceEmoji('🎯', 'darts')
|
||||
|
||||
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`.
|
||||
"""
|
||||
|
||||
class language(BaseFilter):
|
||||
"""Filters messages to only allow those which are from users with a certain language code.
|
||||
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
+185
-16
@@ -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,7 +30,7 @@ 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, _UTC
|
||||
from telegram.utils.helpers import to_float_timestamp
|
||||
|
||||
|
||||
class Days(object):
|
||||
@@ -104,6 +105,7 @@ class JobQueue(object):
|
||||
# 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)
|
||||
@@ -129,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``.
|
||||
@@ -144,7 +150,14 @@ class JobQueue(object):
|
||||
queue.
|
||||
|
||||
"""
|
||||
job = Job(callback, repeat=False, context=context, name=name, job_queue=self)
|
||||
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
|
||||
|
||||
@@ -172,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.
|
||||
@@ -187,21 +204,131 @@ 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)
|
||||
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.
|
||||
|
||||
@@ -215,6 +342,7 @@ class JobQueue(object):
|
||||
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.
|
||||
@@ -226,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.
|
||||
@@ -285,9 +413,10 @@ class JobQueue(object):
|
||||
if job.enabled:
|
||||
try:
|
||||
current_week_day = datetime.datetime.now(job.tzinfo).date().weekday()
|
||||
if any(day == current_week_day for day in job.days):
|
||||
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',
|
||||
@@ -297,7 +426,13 @@ class JobQueue(object):
|
||||
|
||||
if job.repeat and not job.removed:
|
||||
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):
|
||||
@@ -364,6 +499,8 @@ class Job(object):
|
||||
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.
|
||||
@@ -390,6 +527,11 @@ class Job(object):
|
||||
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,
|
||||
@@ -400,7 +542,9 @@ class Job(object):
|
||||
days=Days.EVERY_DAY,
|
||||
name=None,
|
||||
job_queue=None,
|
||||
tzinfo=_UTC):
|
||||
tzinfo=None,
|
||||
is_monthly=False,
|
||||
day_is_strict=True):
|
||||
|
||||
self.callback = callback
|
||||
self.context = context
|
||||
@@ -409,11 +553,14 @@ class Job(object):
|
||||
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
|
||||
self.tzinfo = tzinfo or datetime.timezone.utc
|
||||
|
||||
self._job_queue = weakref.proxy(job_queue) if job_queue is not None else None
|
||||
|
||||
@@ -435,6 +582,7 @@ class Job(object):
|
||||
|
||||
"""
|
||||
self._remove.set()
|
||||
self._next_t = None
|
||||
|
||||
@property
|
||||
def removed(self):
|
||||
@@ -468,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
|
||||
|
||||
@@ -482,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."""
|
||||
@@ -501,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 "
|
||||
|
||||
@@ -249,7 +249,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
|
||||
|
||||
@@ -81,12 +81,12 @@ class PicklePersistence(BasePersistence):
|
||||
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'])
|
||||
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 = all.get('bot_data', {})
|
||||
self.conversations = all['conversations']
|
||||
self.bot_data = data.get('bot_data', {})
|
||||
self.conversations = data['conversations']
|
||||
except IOError:
|
||||
self.conversations = {}
|
||||
self.user_data = defaultdict(dict)
|
||||
@@ -110,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, 'bot_data': self.bot_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:
|
||||
@@ -224,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
|
||||
@@ -242,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
|
||||
|
||||
@@ -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
|
||||
@@ -91,7 +91,9 @@ class Updater(object):
|
||||
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.
|
||||
@@ -568,9 +570,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:
|
||||
|
||||
@@ -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,7 +81,7 @@ 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):
|
||||
|
||||
+10
-2
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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,7 +69,7 @@ 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):
|
||||
|
||||
+16
-5
@@ -38,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.
|
||||
@@ -54,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):
|
||||
|
||||
@@ -47,7 +47,8 @@ class InputMediaAnimation(InputMedia):
|
||||
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 320. 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.
|
||||
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.
|
||||
@@ -64,7 +65,8 @@ class InputMediaAnimation(InputMedia):
|
||||
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 320. 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.
|
||||
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.
|
||||
@@ -124,7 +126,8 @@ class InputMediaPhoto(InputMedia):
|
||||
(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.
|
||||
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.
|
||||
@@ -133,7 +136,8 @@ class InputMediaPhoto(InputMedia):
|
||||
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.
|
||||
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.
|
||||
@@ -164,7 +168,8 @@ class InputMediaVideo(InputMedia):
|
||||
(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.
|
||||
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.
|
||||
@@ -182,7 +187,8 @@ class InputMediaVideo(InputMedia):
|
||||
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.
|
||||
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.
|
||||
@@ -244,7 +250,8 @@ class InputMediaAudio(InputMedia):
|
||||
(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.
|
||||
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.
|
||||
@@ -261,7 +268,8 @@ class InputMediaAudio(InputMedia):
|
||||
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.
|
||||
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.
|
||||
@@ -320,7 +328,8 @@ class InputMediaDocument(InputMedia):
|
||||
(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.
|
||||
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.
|
||||
@@ -333,7 +342,8 @@ class InputMediaDocument(InputMedia):
|
||||
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.
|
||||
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.
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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,7 +89,7 @@ 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):
|
||||
@@ -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,15 +147,20 @@ 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,)
|
||||
|
||||
@@ -156,6 +171,7 @@ class StickerSet(TelegramObject):
|
||||
|
||||
data = super(StickerSet, StickerSet).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)
|
||||
@@ -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.
|
||||
|
||||
|
||||
+10
-2
@@ -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,7 +76,7 @@ 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):
|
||||
|
||||
@@ -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,7 +68,7 @@ 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):
|
||||
|
||||
+17
-3
@@ -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,16 +47,24 @@ 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):
|
||||
|
||||
@@ -30,27 +30,28 @@ class InlineKeyboardButton(TelegramObject):
|
||||
|
||||
Attributes:
|
||||
text (:obj:`str`): Label text on the button.
|
||||
url (:obj:`str`): Optional. HTTP url to be opened when button is pressed.
|
||||
url (:obj:`str`): Optional. HTTP or tg:// url to be opened when button is pressed.
|
||||
login_url (:class:`telegram.LoginUrl`) Optional. An HTTP URL used to automatically
|
||||
authorize the user.
|
||||
authorize the user. Can be used as a replacement for the Telegram Login Widget.
|
||||
callback_data (:obj:`str`): Optional. Data to be sent in a callback query to the bot when
|
||||
button is pressed, UTF-8 1-64 bytes.
|
||||
switch_inline_query (:obj:`str`): Optional. Will prompt the user to select one of their
|
||||
chats, open that chat and insert the bot's username and the specified inline query in
|
||||
the input field.
|
||||
the input field. Can be empty, in which case just the bot’s username will be inserted.
|
||||
switch_inline_query_current_chat (:obj:`str`): Optional. Will insert the bot's username and
|
||||
the specified inline query in the current chat's input field.
|
||||
the specified inline query in the current chat's input field. Can be empty, in which
|
||||
case just the bot’s username will be inserted.
|
||||
callback_game (:class:`telegram.CallbackGame`): Optional. Description of the game that will
|
||||
be launched when the user presses the button.
|
||||
pay (:obj:`bool`): Optional. Specify True, to send a Pay button.
|
||||
|
||||
Args:
|
||||
text (:obj:`str`): Label text on the button.
|
||||
url (:obj:`str`): HTTP url to be opened when button is pressed.
|
||||
url (:obj:`str`): HTTP or tg:// url to be opened when button is pressed.
|
||||
login_url (:class:`telegram.LoginUrl`, optional) An HTTP URL used to automatically
|
||||
authorize the user.
|
||||
authorize the user. Can be used as a replacement for the Telegram Login Widget.
|
||||
callback_data (:obj:`str`, optional): Data to be sent in a callback query to the bot when
|
||||
button is pressed, 1-64 UTF-8 bytes.
|
||||
button is pressed, UTF-8 1-64 bytes.
|
||||
switch_inline_query (:obj:`str`, optional): If set, pressing the button will prompt the
|
||||
user to select one of their chats, open that chat and insert the bot's username and the
|
||||
specified inline query in the input field. Can be empty, in which case just the bot's
|
||||
|
||||
@@ -33,9 +33,9 @@ class InlineQueryResultAudio(InlineQueryResult):
|
||||
id (:obj:`str`): Unique identifier for this result, 1-64 bytes.
|
||||
audio_url (:obj:`str`): A valid URL for the audio file.
|
||||
title (:obj:`str`): Title.
|
||||
performer (:obj:`str`): Optional. Caption, 0-200 characters.
|
||||
audio_duration (:obj:`str`): Optional. Performer.
|
||||
caption (:obj:`str`): Optional. Audio duration in seconds.
|
||||
performer (:obj:`str`): Optional. Performer.
|
||||
audio_duration (:obj:`str`): Optional. Audio duration in seconds.
|
||||
caption (:obj:`str`): Optional. Caption, 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.
|
||||
@@ -48,9 +48,9 @@ class InlineQueryResultAudio(InlineQueryResult):
|
||||
id (:obj:`str`): Unique identifier for this result, 1-64 bytes.
|
||||
audio_url (:obj:`str`): A valid URL for the audio file.
|
||||
title (:obj:`str`): Title.
|
||||
performer (:obj:`str`, optional): Caption, 0-200 characters.
|
||||
audio_duration (:obj:`str`, optional): Performer.
|
||||
caption (:obj:`str`, optional): Audio duration in seconds.
|
||||
performer (:obj:`str`, optional): Performer.
|
||||
audio_duration (:obj:`str`, optional): Audio duration in seconds.
|
||||
caption (:obj:`str`, optional): Caption, 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.
|
||||
|
||||
@@ -26,13 +26,13 @@ class InlineQueryResultCachedAudio(InlineQueryResult):
|
||||
"""
|
||||
Represents a link to an mp3 audio file stored on the Telegram servers. By default, this audio
|
||||
file will be sent by the user. Alternatively, you can use :attr:`input_message_content` to
|
||||
send amessage with the specified content instead of the audio.
|
||||
send a message with the specified content instead of the audio.
|
||||
|
||||
Attributes:
|
||||
type (:obj:`str`): 'audio'.
|
||||
id (:obj:`str`): Unique identifier for this result, 1-64 bytes.
|
||||
audio_file_id (:obj:`str`): A valid file identifier for the audio file.
|
||||
caption (:obj:`str`): Optional. Caption, 0-1024 characters
|
||||
caption (:obj:`str`): Optional. Caption, 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.
|
||||
@@ -44,7 +44,7 @@ class InlineQueryResultCachedAudio(InlineQueryResult):
|
||||
Args:
|
||||
id (:obj:`str`): Unique identifier for this result, 1-64 bytes.
|
||||
audio_file_id (:obj:`str`): A valid file identifier for the audio file.
|
||||
caption (:obj:`str`, optional): Caption, 0-1024 characters
|
||||
caption (:obj:`str`, optional): Caption, 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.
|
||||
|
||||
@@ -34,7 +34,8 @@ class InlineQueryResultCachedDocument(InlineQueryResult):
|
||||
title (:obj:`str`): Title for the result.
|
||||
document_file_id (:obj:`str`): A valid file identifier for the file.
|
||||
description (:obj:`str`): Optional. Short description of the result.
|
||||
caption (:obj:`str`): Optional. Caption, 0-1024 characters
|
||||
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.
|
||||
@@ -48,7 +49,8 @@ class InlineQueryResultCachedDocument(InlineQueryResult):
|
||||
title (:obj:`str`): Title for the result.
|
||||
document_file_id (:obj:`str`): A valid file identifier for the file.
|
||||
description (:obj:`str`, optional): Short description of the result.
|
||||
caption (:obj:`str`, optional): Caption, 0-1024 characters
|
||||
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.
|
||||
|
||||
@@ -34,7 +34,8 @@ class InlineQueryResultCachedGif(InlineQueryResult):
|
||||
id (:obj:`str`): Unique identifier for this result, 1-64 bytes.
|
||||
gif_file_id (:obj:`str`): A valid file identifier for the GIF file.
|
||||
title (:obj:`str`): Optional. Title for the result.
|
||||
caption (:obj:`str`): Optional. Caption, 0-1024 characters
|
||||
caption (:obj:`str`): Optional. Caption of the GIF file 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.
|
||||
@@ -47,7 +48,8 @@ class InlineQueryResultCachedGif(InlineQueryResult):
|
||||
id (:obj:`str`): Unique identifier for this result, 1-64 bytes.
|
||||
gif_file_id (:obj:`str`): A valid file identifier for the GIF file.
|
||||
title (:obj:`str`, optional): Title for the result.caption (:obj:`str`, optional):
|
||||
caption (:obj:`str`, optional): Caption, 0-1024 characters
|
||||
caption (:obj:`str`, optional): Caption of the GIF file 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.
|
||||
|
||||
@@ -34,7 +34,8 @@ class InlineQueryResultCachedMpeg4Gif(InlineQueryResult):
|
||||
id (:obj:`str`): Unique identifier for this result, 1-64 bytes.
|
||||
mpeg4_file_id (:obj:`str`): A valid file identifier for the MP4 file.
|
||||
title (:obj:`str`): Optional. Title for the result.
|
||||
caption (:obj:`str`): Optional. Caption, 0-1024 characters
|
||||
caption (:obj:`str`): Optional. Caption of the MPEG-4 file to be sent, 0-1024 characters
|
||||
after entities parsing.
|
||||
parse_mode (:obj:`str`): 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.
|
||||
@@ -47,7 +48,8 @@ class InlineQueryResultCachedMpeg4Gif(InlineQueryResult):
|
||||
id (:obj:`str`): Unique identifier for this result, 1-64 bytes.
|
||||
mpeg4_file_id (:obj:`str`): A valid file identifier for the MP4 file.
|
||||
title (:obj:`str`, optional): Title for the result.
|
||||
caption (:obj:`str`, optional): Caption, 0-1024 characters
|
||||
caption (:obj:`str`, optional): Caption of the MPEG-4 file 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.
|
||||
|
||||
@@ -35,7 +35,8 @@ class InlineQueryResultCachedPhoto(InlineQueryResult):
|
||||
photo_file_id (:obj:`str`): A valid file identifier of the photo.
|
||||
title (:obj:`str`): Optional. Title for the result.
|
||||
description (:obj:`str`): Optional. Short description of the result.
|
||||
caption (:obj:`str`): Optional. Caption, 0-1024 characters
|
||||
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.
|
||||
@@ -49,7 +50,8 @@ class InlineQueryResultCachedPhoto(InlineQueryResult):
|
||||
photo_file_id (:obj:`str`): A valid file identifier of the photo.
|
||||
title (:obj:`str`, optional): Title for the result.
|
||||
description (:obj:`str`, optional): Short description of the result.
|
||||
caption (:obj:`str`, optional): Caption, 0-1024 characters
|
||||
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.
|
||||
|
||||
@@ -37,8 +37,8 @@ class InlineQueryResultCachedSticker(InlineQueryResult):
|
||||
message to be sent instead of the sticker.
|
||||
|
||||
Args:
|
||||
id (:obj:`str`):
|
||||
sticker_file_id (:obj:`str`):
|
||||
id (:obj:`str`): Unique identifier for this result, 1-64 bytes.
|
||||
sticker_file_id (:obj:`str`): A valid file identifier of the sticker.
|
||||
reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached
|
||||
to the message.
|
||||
input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the
|
||||
|
||||
@@ -35,7 +35,8 @@ class InlineQueryResultCachedVideo(InlineQueryResult):
|
||||
video_file_id (:obj:`str`): A valid file identifier for the video file.
|
||||
title (:obj:`str`): Title for the result.
|
||||
description (:obj:`str`): Optional. Short description of the result.
|
||||
caption (:obj:`str`): Optional. Caption, 0-1024 characters.
|
||||
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.
|
||||
@@ -49,7 +50,8 @@ class InlineQueryResultCachedVideo(InlineQueryResult):
|
||||
video_file_id (:obj:`str`): A valid file identifier for the video file.
|
||||
title (:obj:`str`): Title for the result.
|
||||
description (:obj:`str`, optional): Short description of the result.
|
||||
caption (:obj:`str`, optional): Caption, 0-1024 characters.
|
||||
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.
|
||||
|
||||
@@ -33,7 +33,7 @@ class InlineQueryResultCachedVoice(InlineQueryResult):
|
||||
id (:obj:`str`): Unique identifier for this result, 1-64 bytes.
|
||||
voice_file_id (:obj:`str`): A valid file identifier for the voice message.
|
||||
title (:obj:`str`): Voice message title.
|
||||
caption (:obj:`str`): Optional. Caption, 0-1024 characters.
|
||||
caption (:obj:`str`): Optional. Caption, 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.
|
||||
@@ -46,7 +46,7 @@ class InlineQueryResultCachedVoice(InlineQueryResult):
|
||||
id (:obj:`str`): Unique identifier for this result, 1-64 bytes.
|
||||
voice_file_id (:obj:`str`): A valid file identifier for the voice message.
|
||||
title (:obj:`str`): Voice message title.
|
||||
caption (:obj:`str`, optional): Caption, 0-1024 characters.
|
||||
caption (:obj:`str`, optional): Caption, 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.
|
||||
|
||||
@@ -33,7 +33,8 @@ class InlineQueryResultDocument(InlineQueryResult):
|
||||
type (:obj:`str`): 'document'.
|
||||
id (:obj:`str`): Unique identifier for this result, 1-64 bytes.
|
||||
title (:obj:`str`): Title for the result.
|
||||
caption (:obj:`str`): Optional. Caption, 0-1024 characters
|
||||
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.
|
||||
@@ -52,7 +53,8 @@ class InlineQueryResultDocument(InlineQueryResult):
|
||||
Args:
|
||||
id (:obj:`str`): Unique identifier for this result, 1-64 bytes.
|
||||
title (:obj:`str`): Title for the result.
|
||||
caption (:obj:`str`, optional): Caption, 0-1024 characters
|
||||
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.
|
||||
@@ -60,9 +62,9 @@ class InlineQueryResultDocument(InlineQueryResult):
|
||||
mime_type (:obj:`str`): Mime type of the content of the file, either "application/pdf"
|
||||
or "application/zip".
|
||||
description (:obj:`str`, optional): Short description of the result.
|
||||
reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached
|
||||
reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached
|
||||
to the message.
|
||||
input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the
|
||||
input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the
|
||||
message to be sent instead of the file.
|
||||
thumb_url (:obj:`str`, optional): URL of the thumbnail (jpeg only) for the file.
|
||||
thumb_width (:obj:`int`, optional): Thumbnail width.
|
||||
|
||||
@@ -37,14 +37,15 @@ class InlineQueryResultGif(InlineQueryResult):
|
||||
gif_duration (:obj:`int`): Optional. Duration of the GIF.
|
||||
thumb_url (:obj:`str`): URL of the static thumbnail for the result (jpeg or gif).
|
||||
title (:obj:`str`): Optional. Title for the result.
|
||||
caption (:obj:`str`): Optional. Caption, 0-1024 characters
|
||||
caption (:obj:`str`): Optional. Caption of the GIF file 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.
|
||||
reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached
|
||||
to the message.
|
||||
input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the
|
||||
message to be sent instead of the gif.
|
||||
message to be sent instead of the GIF animation.
|
||||
|
||||
Args:
|
||||
id (:obj:`str`): Unique identifier for this result, 1-64 bytes.
|
||||
@@ -53,15 +54,16 @@ class InlineQueryResultGif(InlineQueryResult):
|
||||
gif_height (:obj:`int`, optional): Height of the GIF.
|
||||
gif_duration (:obj:`int`, optional): Duration of the GIF
|
||||
thumb_url (:obj:`str`): URL of the static thumbnail for the result (jpeg or gif).
|
||||
title (:obj:`str`, optional): Title for the result.caption (:obj:`str`, optional):
|
||||
caption (:obj:`str`, optional): Caption, 0-1024 characters
|
||||
title (:obj:`str`, optional): Title for the result.
|
||||
caption (:obj:`str`, optional): Caption of the GIF file 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.
|
||||
reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached
|
||||
to the message.
|
||||
input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the
|
||||
message to be sent instead of the gif.
|
||||
message to be sent instead of the GIF animation.
|
||||
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
||||
|
||||
"""
|
||||
|
||||
@@ -38,14 +38,15 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult):
|
||||
mpeg4_duration (:obj:`int`): Optional. Video duration.
|
||||
thumb_url (:obj:`str`): URL of the static thumbnail (jpeg or gif) for the result.
|
||||
title (:obj:`str`): Optional. Title for the result.
|
||||
caption (:obj:`str`): Optional. Caption, 0-1024 characters
|
||||
caption (:obj:`str`): Optional. Caption of the MPEG-4 file 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.
|
||||
reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached
|
||||
to the message.
|
||||
input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the
|
||||
message to be sent instead of the MPEG-4 file.
|
||||
message to be sent instead of the video animation.
|
||||
|
||||
Args:
|
||||
id (:obj:`str`): Unique identifier for this result, 1-64 bytes.
|
||||
@@ -55,14 +56,15 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult):
|
||||
mpeg4_duration (:obj:`int`, optional): Video duration.
|
||||
thumb_url (:obj:`str`): URL of the static thumbnail (jpeg or gif) for the result.
|
||||
title (:obj:`str`, optional): Title for the result.
|
||||
caption (:obj:`str`, optional): Caption, 0-1024 characters
|
||||
caption (:obj:`str`, optional): Caption of the MPEG-4 file 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.
|
||||
reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached
|
||||
to the message.
|
||||
input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the
|
||||
message to be sent instead of the MPEG-4 file.
|
||||
message to be sent instead of the video animation.
|
||||
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
||||
|
||||
"""
|
||||
|
||||
@@ -38,7 +38,8 @@ class InlineQueryResultPhoto(InlineQueryResult):
|
||||
photo_height (:obj:`int`): Optional. Height of the photo.
|
||||
title (:obj:`str`): Optional. Title for the result.
|
||||
description (:obj:`str`): Optional. Short description of the result.
|
||||
caption (:obj:`str`): Optional. Caption, 0-1024 characters
|
||||
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.
|
||||
@@ -56,7 +57,8 @@ class InlineQueryResultPhoto(InlineQueryResult):
|
||||
photo_height (:obj:`int`, optional): Height of the photo.
|
||||
title (:obj:`str`, optional): Title for the result.
|
||||
description (:obj:`str`, optional): Short description of the result.
|
||||
caption (:obj:`str`, optional): Caption, 0-1024 characters
|
||||
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.
|
||||
|
||||
@@ -29,6 +29,10 @@ class InlineQueryResultVideo(InlineQueryResult):
|
||||
:attr:`input_message_content` to send a message with the specified content instead of
|
||||
the video.
|
||||
|
||||
Note:
|
||||
If an InlineQueryResultVideo message contains an embedded video (e.g., YouTube), you must
|
||||
replace its content using :attr:`input_message_content`.
|
||||
|
||||
Attributes:
|
||||
type (:obj:`str`): 'video'.
|
||||
id (:obj:`str`): Unique identifier for this result, 1-64 bytes.
|
||||
@@ -36,7 +40,8 @@ class InlineQueryResultVideo(InlineQueryResult):
|
||||
mime_type (:obj:`str`): Mime type of the content of video url, "text/html" or "video/mp4".
|
||||
thumb_url (:obj:`str`): URL of the thumbnail (jpeg only) for the video.
|
||||
title (:obj:`str`): Title for the result.
|
||||
caption (:obj:`str`): Optional. Caption, 0-1024 characters
|
||||
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.
|
||||
@@ -47,7 +52,9 @@ class InlineQueryResultVideo(InlineQueryResult):
|
||||
reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached
|
||||
to the message.
|
||||
input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the
|
||||
message to be sent instead of the video.
|
||||
message to be sent instead of the video. This field is required if
|
||||
InlineQueryResultVideo is used to send an HTML-page as a result
|
||||
(e.g., a YouTube video).
|
||||
|
||||
Args:
|
||||
id (:obj:`str`): Unique identifier for this result, 1-64 bytes.
|
||||
@@ -55,7 +62,7 @@ class InlineQueryResultVideo(InlineQueryResult):
|
||||
mime_type (:obj:`str`): Mime type of the content of video url, "text/html" or "video/mp4".
|
||||
thumb_url (:obj:`str`): URL of the thumbnail (jpeg only) for the video.
|
||||
title (:obj:`str`): Title for the result.
|
||||
caption (:obj:`str`, optional): Caption, 0-1024 characters.
|
||||
caption (:obj:`str`, optional): Caption, 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.
|
||||
@@ -66,7 +73,9 @@ class InlineQueryResultVideo(InlineQueryResult):
|
||||
reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached
|
||||
to the message.
|
||||
input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the
|
||||
message to be sent instead of the video.
|
||||
message to be sent instead of the video. This field is required if
|
||||
InlineQueryResultVideo is used to send an HTML-page as a result
|
||||
(e.g., a YouTube video).
|
||||
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
||||
|
||||
"""
|
||||
|
||||
@@ -33,30 +33,30 @@ class InlineQueryResultVoice(InlineQueryResult):
|
||||
type (:obj:`str`): 'voice'.
|
||||
id (:obj:`str`): Unique identifier for this result, 1-64 bytes.
|
||||
voice_url (:obj:`str`): A valid URL for the voice recording.
|
||||
title (:obj:`str`): Voice message title.
|
||||
caption (:obj:`str`): Optional. Caption, 0-1024 characters.
|
||||
title (:obj:`str`): Recording title.
|
||||
caption (:obj:`str`): Optional. Caption, 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
|
||||
bold, italic, fixed-width text or inline URLs in the media caption. See the constants
|
||||
in :class:`telegram.ParseMode` for the available modes.
|
||||
voice_duration (:obj:`int`): Optional. Recording duration in seconds.
|
||||
reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached
|
||||
to the message.
|
||||
input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the
|
||||
message to be sent instead of the voice.
|
||||
message to be sent instead of the voice recording.
|
||||
|
||||
Args:
|
||||
id (:obj:`str`): Unique identifier for this result, 1-64 bytes.
|
||||
voice_url (:obj:`str`): A valid URL for the voice recording.
|
||||
title (:obj:`str`): Voice message title.
|
||||
caption (:obj:`str`, optional): Caption, 0-1024 characters.
|
||||
title (:obj:`str`): Recording title.
|
||||
caption (:obj:`str`, optional): Caption, 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
|
||||
bold, italic, fixed-width text or inline URLs in the media caption. See the constants
|
||||
in :class:`telegram.ParseMode` for the available modes.
|
||||
voice_duration (:obj:`int`, optional): Recording duration in seconds.
|
||||
reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached
|
||||
to the message.
|
||||
input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the
|
||||
message to be sent instead of the voice.
|
||||
message to be sent instead of the voice recording.
|
||||
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
||||
|
||||
"""
|
||||
|
||||
@@ -27,15 +27,16 @@ class InputTextMessageContent(InputMessageContent):
|
||||
Represents the content of a text message to be sent as the result of an inline query.
|
||||
|
||||
Attributes:
|
||||
message_text (:obj:`str`): Text of the message to be sent, 1-4096 characters.
|
||||
message_text (:obj:`str`): Text of the message to be sent, 1-4096 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 your bot's message.
|
||||
disable_web_page_preview (:obj:`bool`): Optional. Disables link previews for links in the
|
||||
sent message.
|
||||
|
||||
Args:
|
||||
message_text (:obj:`str`): Text of the message to be sent, 1-4096 characters. Also found
|
||||
as :attr:`telegram.constants.MAX_MESSAGE_LENGTH`.
|
||||
message_text (:obj:`str`): Text of the message to be sent, 1-4096 characters after entities
|
||||
parsing. Also found as :attr:`telegram.constants.MAX_MESSAGE_LENGTH`.
|
||||
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 your bot's message.
|
||||
disable_web_page_preview (:obj:`bool`, optional): Disables link previews for links in the
|
||||
|
||||
@@ -33,6 +33,7 @@ class KeyboardButton(TelegramObject):
|
||||
text (:obj:`str`): Text of the button.
|
||||
request_contact (:obj:`bool`): Optional. If the user's phone number will be sent.
|
||||
request_location (:obj:`bool`): Optional. If the user's current location will be sent.
|
||||
request_poll (:class:`KeyboardButtonPollType`): Optional. If the user should create a poll.
|
||||
|
||||
Args:
|
||||
text (:obj:`str`): Text of the button. If none of the optional fields are used, it will be
|
||||
@@ -41,16 +42,24 @@ class KeyboardButton(TelegramObject):
|
||||
a contact when the button is pressed. Available in private chats only.
|
||||
request_location (:obj:`bool`, optional): If True, the user's current location will be sent
|
||||
when the button is pressed. Available in private chats only.
|
||||
request_poll (:class:`KeyboardButtonPollType`, optional): If specified, the user will be
|
||||
asked to create a poll and send it to the bot when the button is pressed. Available in
|
||||
private chats only.
|
||||
|
||||
Note:
|
||||
:attr:`request_contact` and :attr:`request_location` options will only work in Telegram
|
||||
versions released after 9 April, 2016. Older clients will ignore them.
|
||||
|
||||
:attr:`request_poll` option will only work in Telegram versions released after 23 January,
|
||||
2020. Older clients will receive unsupported message.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, text, request_contact=None, request_location=None, **kwargs):
|
||||
def __init__(self, text, request_contact=None, request_location=None, request_poll=None,
|
||||
**kwargs):
|
||||
# Required
|
||||
self.text = text
|
||||
# Optionals
|
||||
self.request_contact = request_contact
|
||||
self.request_location = request_location
|
||||
self.request_poll = request_poll
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env python
|
||||
# pylint: disable=R0903
|
||||
#
|
||||
# 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 an object that represents a type of a Telegram Poll."""
|
||||
from telegram import TelegramObject
|
||||
|
||||
|
||||
class KeyboardButtonPollType(TelegramObject):
|
||||
"""This object represents type of a poll, which is allowed to be created
|
||||
and sent when the corresponding button is pressed.
|
||||
|
||||
Attributes:
|
||||
type (:obj:`str`): Optional. If :attr:`telegram.Poll.QUIZ` is passed, the user will be
|
||||
allowed to create only polls in the quiz mode. If :attr:`telegram.Poll.REGULAR` is
|
||||
passed, only regular polls will be allowed. Otherwise, the user will be allowed to
|
||||
create a poll of any type.
|
||||
"""
|
||||
def __init__(self, type=None):
|
||||
self.type = type
|
||||
|
||||
self._id_attrs = (self.type,)
|
||||
+302
-64
@@ -23,7 +23,7 @@ from html import escape
|
||||
|
||||
from telegram import (Animation, Audio, Contact, Document, Chat, Location, PhotoSize, Sticker,
|
||||
TelegramObject, User, Video, Voice, Venue, MessageEntity, Game, Invoice,
|
||||
SuccessfulPayment, VideoNote, PassportData, Poll, InlineKeyboardMarkup)
|
||||
SuccessfulPayment, VideoNote, PassportData, Poll, InlineKeyboardMarkup, Dice)
|
||||
from telegram import ParseMode
|
||||
from telegram.utils.helpers import escape_markdown, to_timestamp, from_timestamp
|
||||
|
||||
@@ -106,6 +106,7 @@ class Message(TelegramObject):
|
||||
passport_data (:class:`telegram.PassportData`): Optional. Telegram Passport data.
|
||||
poll (:class:`telegram.Poll`): Optional. Message is a native poll,
|
||||
information about the poll.
|
||||
dice (:class:`telegram.Dice`): Optional. Message is a dice.
|
||||
reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached
|
||||
to the message.
|
||||
bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods.
|
||||
@@ -199,7 +200,7 @@ class Message(TelegramObject):
|
||||
smaller than 52 bits, so a signed 64 bit integer or double-precision float type are
|
||||
safe for storing this identifier.
|
||||
pinned_message (:class:`telegram.message`, optional): Specified message was pinned. Note
|
||||
that the Message object in this field will not contain further attr:`reply_to_message`
|
||||
that the Message object in this field will not contain further :attr:`reply_to_message`
|
||||
fields even if it is itself a reply.
|
||||
invoice (:class:`telegram.Invoice`, optional): Message is an invoice for a payment,
|
||||
information about the invoice.
|
||||
@@ -214,6 +215,7 @@ class Message(TelegramObject):
|
||||
passport_data (:class:`telegram.PassportData`, optional): Telegram Passport data.
|
||||
poll (:class:`telegram.Poll`, optional): Message is a native poll,
|
||||
information about the poll.
|
||||
dice (:class:`telegram.Dice`, optional): Message is a dice with random value from 1 to 6.
|
||||
reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached
|
||||
to the message. login_url buttons are represented as ordinary url buttons.
|
||||
default_quote (:obj:`bool`, optional): Default setting for the `quote` parameter of the
|
||||
@@ -229,7 +231,7 @@ class Message(TelegramObject):
|
||||
MESSAGE_TYPES = ['text', 'new_chat_members', 'left_chat_member', 'new_chat_title',
|
||||
'new_chat_photo', 'delete_chat_photo', 'group_chat_created',
|
||||
'supergroup_chat_created', 'channel_chat_created', 'migrate_to_chat_id',
|
||||
'migrate_from_chat_id', 'pinned_message',
|
||||
'migrate_from_chat_id', 'pinned_message', 'poll', 'dice',
|
||||
'passport_data'] + ATTACHMENT_TYPES
|
||||
|
||||
def __init__(self,
|
||||
@@ -282,6 +284,7 @@ class Message(TelegramObject):
|
||||
reply_markup=None,
|
||||
bot=None,
|
||||
default_quote=None,
|
||||
dice=None,
|
||||
**kwargs):
|
||||
# Required
|
||||
self.message_id = int(message_id)
|
||||
@@ -331,6 +334,7 @@ class Message(TelegramObject):
|
||||
self.animation = animation
|
||||
self.passport_data = passport_data
|
||||
self.poll = poll
|
||||
self.dice = dice
|
||||
self.reply_markup = reply_markup
|
||||
self.bot = bot
|
||||
self.default_quote = default_quote
|
||||
@@ -404,6 +408,7 @@ class Message(TelegramObject):
|
||||
data['successful_payment'] = SuccessfulPayment.de_json(data.get('successful_payment'), bot)
|
||||
data['passport_data'] = PassportData.de_json(data.get('passport_data'), bot)
|
||||
data['poll'] = Poll.de_json(data.get('poll'), bot)
|
||||
data['dice'] = Dice.de_json(data.get('dice'), bot)
|
||||
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'), bot)
|
||||
|
||||
return cls(bot=bot, **data)
|
||||
@@ -510,7 +515,7 @@ class Message(TelegramObject):
|
||||
bot.send_message(update.message.chat_id, parse_mode=ParseMode.MARKDOWN, *args,
|
||||
**kwargs)
|
||||
|
||||
Sends a message with markdown formatting.
|
||||
Sends a message with markdown version 1 formatting.
|
||||
|
||||
Keyword Args:
|
||||
quote (:obj:`bool`, optional): If set to ``True``, the message is sent as an actual
|
||||
@@ -528,6 +533,30 @@ class Message(TelegramObject):
|
||||
|
||||
return self.bot.send_message(self.chat_id, *args, **kwargs)
|
||||
|
||||
def reply_markdown_v2(self, *args, **kwargs):
|
||||
"""Shortcut for::
|
||||
|
||||
bot.send_message(update.message.chat_id, parse_mode=ParseMode.MARKDOWN_V2, *args,
|
||||
**kwargs)
|
||||
|
||||
Sends a message with markdown version 2 formatting.
|
||||
|
||||
Keyword Args:
|
||||
quote (:obj:`bool`, optional): If set to ``True``, the message is sent as an actual
|
||||
reply to this 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.
|
||||
|
||||
Returns:
|
||||
:class:`telegram.Message`: On success, instance representing the message posted.
|
||||
"""
|
||||
|
||||
kwargs['parse_mode'] = ParseMode.MARKDOWN_V2
|
||||
|
||||
self._quote(kwargs)
|
||||
|
||||
return self.bot.send_message(self.chat_id, *args, **kwargs)
|
||||
|
||||
def reply_html(self, *args, **kwargs):
|
||||
"""Shortcut for::
|
||||
|
||||
@@ -784,6 +813,23 @@ class Message(TelegramObject):
|
||||
self._quote(kwargs)
|
||||
return self.bot.send_poll(self.chat_id, *args, **kwargs)
|
||||
|
||||
def reply_dice(self, *args, **kwargs):
|
||||
"""Shortcut for::
|
||||
|
||||
bot.send_dice(update.message.chat_id, *args, **kwargs)
|
||||
|
||||
Keyword Args:
|
||||
quote (:obj:`bool`, optional): If set to ``True``, the dice is sent as an actual reply
|
||||
to this 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.
|
||||
|
||||
Returns:
|
||||
:class:`telegram.Message`: On success, instance representing the message posted.
|
||||
|
||||
"""
|
||||
self._quote(kwargs)
|
||||
return self.bot.send_dice(self.chat_id, *args, **kwargs)
|
||||
|
||||
def forward(self, chat_id, *args, **kwargs):
|
||||
"""Shortcut for::
|
||||
|
||||
@@ -899,6 +945,22 @@ class Message(TelegramObject):
|
||||
return self.bot.delete_message(
|
||||
chat_id=self.chat_id, message_id=self.message_id, *args, **kwargs)
|
||||
|
||||
def stop_poll(self, *args, **kwargs):
|
||||
"""Shortcut for::
|
||||
|
||||
bot.stop_poll(chat_id=message.chat_id,
|
||||
message_id=message.message_id,
|
||||
*args,
|
||||
**kwargs)
|
||||
|
||||
Returns:
|
||||
:class:`telegram.Poll`: On success, the stopped Poll with the
|
||||
final results is returned.
|
||||
|
||||
"""
|
||||
return self.bot.stop_poll(
|
||||
chat_id=self.chat_id, message_id=self.message_id, *args, **kwargs)
|
||||
|
||||
def parse_entity(self, entity):
|
||||
"""Returns the text from a given :class:`telegram.MessageEntity`.
|
||||
|
||||
@@ -1012,7 +1074,7 @@ class Message(TelegramObject):
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _parse_html(message_text, entities, urled=False):
|
||||
def _parse_html(message_text, entities, urled=False, offset=0):
|
||||
if message_text is None:
|
||||
return None
|
||||
|
||||
@@ -1022,38 +1084,78 @@ class Message(TelegramObject):
|
||||
html_text = ''
|
||||
last_offset = 0
|
||||
|
||||
for entity, text in sorted(entities.items(), key=(lambda item: item[0].offset)):
|
||||
text = escape(text)
|
||||
sorted_entities = sorted(entities.items(), key=(lambda item: item[0].offset))
|
||||
parsed_entities = []
|
||||
|
||||
if entity.type == MessageEntity.TEXT_LINK:
|
||||
insert = '<a href="{}">{}</a>'.format(entity.url, text)
|
||||
elif entity.type == MessageEntity.TEXT_MENTION and entity.user:
|
||||
insert = '<a href="tg://user?id={}">{}</a>'.format(entity.user.id, text)
|
||||
elif entity.type == MessageEntity.URL and urled:
|
||||
insert = '<a href="{0}">{0}</a>'.format(text)
|
||||
elif entity.type == MessageEntity.BOLD:
|
||||
insert = '<b>' + text + '</b>'
|
||||
elif entity.type == MessageEntity.ITALIC:
|
||||
insert = '<i>' + text + '</i>'
|
||||
elif entity.type == MessageEntity.CODE:
|
||||
insert = '<code>' + text + '</code>'
|
||||
elif entity.type == MessageEntity.PRE:
|
||||
insert = '<pre>' + text + '</pre>'
|
||||
else:
|
||||
insert = text
|
||||
for (entity, text) in sorted_entities:
|
||||
if entity not in parsed_entities:
|
||||
nested_entities = {
|
||||
e: t
|
||||
for (e, t) in sorted_entities if e.offset >= entity.offset
|
||||
and e.offset + e.length <= entity.offset + entity.length
|
||||
and e != entity
|
||||
}
|
||||
parsed_entities.extend([e for e in nested_entities.keys()])
|
||||
|
||||
text = escape(text)
|
||||
|
||||
if nested_entities:
|
||||
text = Message._parse_html(text, nested_entities,
|
||||
urled=urled, offset=entity.offset)
|
||||
|
||||
if entity.type == MessageEntity.TEXT_LINK:
|
||||
insert = '<a href="{}">{}</a>'.format(entity.url, text)
|
||||
elif entity.type == MessageEntity.TEXT_MENTION and entity.user:
|
||||
insert = '<a href="tg://user?id={}">{}</a>'.format(entity.user.id, text)
|
||||
elif entity.type == MessageEntity.URL and urled:
|
||||
insert = '<a href="{0}">{0}</a>'.format(text)
|
||||
elif entity.type == MessageEntity.BOLD:
|
||||
insert = '<b>' + text + '</b>'
|
||||
elif entity.type == MessageEntity.ITALIC:
|
||||
insert = '<i>' + text + '</i>'
|
||||
elif entity.type == MessageEntity.CODE:
|
||||
insert = '<code>' + text + '</code>'
|
||||
elif entity.type == MessageEntity.PRE:
|
||||
if entity.language:
|
||||
insert = '<pre><code class="{}">{}</code></pre>'.format(entity.language,
|
||||
text)
|
||||
else:
|
||||
insert = '<pre>' + text + '</pre>'
|
||||
elif entity.type == MessageEntity.UNDERLINE:
|
||||
insert = '<u>' + text + '</u>'
|
||||
elif entity.type == MessageEntity.STRIKETHROUGH:
|
||||
insert = '<s>' + text + '</s>'
|
||||
else:
|
||||
insert = text
|
||||
|
||||
if offset == 0:
|
||||
if sys.maxunicode == 0xffff:
|
||||
html_text += escape(message_text[last_offset:entity.offset
|
||||
- offset]) + insert
|
||||
else:
|
||||
html_text += escape(message_text[last_offset * 2:(entity.offset
|
||||
- offset) * 2]
|
||||
.decode('utf-16-le')) + insert
|
||||
else:
|
||||
if sys.maxunicode == 0xffff:
|
||||
html_text += message_text[last_offset:entity.offset - offset] + insert
|
||||
else:
|
||||
html_text += message_text[last_offset * 2:(entity.offset
|
||||
- offset) * 2].decode('utf-16-le') + insert
|
||||
|
||||
last_offset = entity.offset - offset + entity.length
|
||||
|
||||
if offset == 0:
|
||||
if sys.maxunicode == 0xffff:
|
||||
html_text += escape(message_text[last_offset:entity.offset]) + insert
|
||||
html_text += escape(message_text[last_offset:])
|
||||
else:
|
||||
html_text += escape(message_text[last_offset * 2:entity.offset * 2]
|
||||
.decode('utf-16-le')) + insert
|
||||
|
||||
last_offset = entity.offset + entity.length
|
||||
|
||||
if sys.maxunicode == 0xffff:
|
||||
html_text += escape(message_text[last_offset:])
|
||||
html_text += escape(message_text[last_offset * 2:].decode('utf-16-le'))
|
||||
else:
|
||||
html_text += escape(message_text[last_offset * 2:].decode('utf-16-le'))
|
||||
if sys.maxunicode == 0xffff:
|
||||
html_text += message_text[last_offset:]
|
||||
else:
|
||||
html_text += message_text[last_offset * 2:].decode('utf-16-le')
|
||||
|
||||
return html_text
|
||||
|
||||
@property
|
||||
@@ -1111,7 +1213,9 @@ class Message(TelegramObject):
|
||||
return self._parse_html(self.caption, self.parse_caption_entities(), urled=True)
|
||||
|
||||
@staticmethod
|
||||
def _parse_markdown(message_text, entities, urled=False):
|
||||
def _parse_markdown(message_text, entities, urled=False, version=1, offset=0):
|
||||
version = int(version)
|
||||
|
||||
if message_text is None:
|
||||
return None
|
||||
|
||||
@@ -1121,42 +1225,117 @@ class Message(TelegramObject):
|
||||
markdown_text = ''
|
||||
last_offset = 0
|
||||
|
||||
for entity, text in sorted(entities.items(), key=(lambda item: item[0].offset)):
|
||||
text = escape_markdown(text)
|
||||
sorted_entities = sorted(entities.items(), key=(lambda item: item[0].offset))
|
||||
parsed_entities = []
|
||||
|
||||
if entity.type == MessageEntity.TEXT_LINK:
|
||||
insert = '[{}]({})'.format(text, entity.url)
|
||||
elif entity.type == MessageEntity.TEXT_MENTION and entity.user:
|
||||
insert = '[{}](tg://user?id={})'.format(text, entity.user.id)
|
||||
elif entity.type == MessageEntity.URL and urled:
|
||||
insert = '[{0}]({0})'.format(text)
|
||||
elif entity.type == MessageEntity.BOLD:
|
||||
insert = '*' + text + '*'
|
||||
elif entity.type == MessageEntity.ITALIC:
|
||||
insert = '_' + text + '_'
|
||||
elif entity.type == MessageEntity.CODE:
|
||||
insert = '`' + text + '`'
|
||||
elif entity.type == MessageEntity.PRE:
|
||||
insert = '```' + text + '```'
|
||||
else:
|
||||
insert = text
|
||||
for (entity, text) in sorted_entities:
|
||||
if entity not in parsed_entities:
|
||||
nested_entities = {
|
||||
e: t
|
||||
for (e, t) in sorted_entities if e.offset >= entity.offset
|
||||
and e.offset + e.length <= entity.offset + entity.length
|
||||
and e != entity
|
||||
}
|
||||
parsed_entities.extend([e for e in nested_entities.keys()])
|
||||
|
||||
orig_text = text
|
||||
text = escape_markdown(text, version=version)
|
||||
|
||||
if nested_entities:
|
||||
if version < 2:
|
||||
raise ValueError('Nested entities are not supported for Markdown '
|
||||
'version 1')
|
||||
|
||||
text = Message._parse_markdown(text, nested_entities,
|
||||
urled=urled, offset=entity.offset,
|
||||
version=version)
|
||||
|
||||
if entity.type == MessageEntity.TEXT_LINK:
|
||||
if version == 1:
|
||||
url = entity.url
|
||||
else:
|
||||
# Links need special escaping. Also can't have entities nested within
|
||||
url = escape_markdown(entity.url, version=version,
|
||||
entity_type=MessageEntity.TEXT_LINK)
|
||||
insert = '[{}]({})'.format(text, url)
|
||||
elif entity.type == MessageEntity.TEXT_MENTION and entity.user:
|
||||
insert = '[{}](tg://user?id={})'.format(text, entity.user.id)
|
||||
elif entity.type == MessageEntity.URL and urled:
|
||||
if version == 1:
|
||||
link = orig_text
|
||||
else:
|
||||
link = text
|
||||
insert = '[{}]({})'.format(link, orig_text)
|
||||
elif entity.type == MessageEntity.BOLD:
|
||||
insert = '*' + text + '*'
|
||||
elif entity.type == MessageEntity.ITALIC:
|
||||
insert = '_' + text + '_'
|
||||
elif entity.type == MessageEntity.CODE:
|
||||
# Monospace needs special escaping. Also can't have entities nested within
|
||||
insert = '`' + escape_markdown(orig_text, version=version,
|
||||
entity_type=MessageEntity.CODE) + '`'
|
||||
elif entity.type == MessageEntity.PRE:
|
||||
# Monospace needs special escaping. Also can't have entities nested within
|
||||
code = escape_markdown(orig_text, version=version,
|
||||
entity_type=MessageEntity.PRE)
|
||||
if entity.language:
|
||||
prefix = '```' + entity.language + '\n'
|
||||
else:
|
||||
if code.startswith('\\'):
|
||||
prefix = '```'
|
||||
else:
|
||||
prefix = '```\n'
|
||||
insert = prefix + code + '```'
|
||||
elif entity.type == MessageEntity.UNDERLINE:
|
||||
if version == 1:
|
||||
raise ValueError('Underline entities are not supported for Markdown '
|
||||
'version 1')
|
||||
insert = '__' + text + '__'
|
||||
elif entity.type == MessageEntity.STRIKETHROUGH:
|
||||
if version == 1:
|
||||
raise ValueError('Strikethrough entities are not supported for Markdown '
|
||||
'version 1')
|
||||
insert = '~' + text + '~'
|
||||
else:
|
||||
insert = text
|
||||
|
||||
if offset == 0:
|
||||
if sys.maxunicode == 0xffff:
|
||||
markdown_text += escape_markdown(message_text[last_offset:entity.offset
|
||||
- offset],
|
||||
version=version) + insert
|
||||
else:
|
||||
markdown_text += escape_markdown(message_text[last_offset * 2:
|
||||
(entity.offset - offset) * 2]
|
||||
.decode('utf-16-le'),
|
||||
version=version) + insert
|
||||
else:
|
||||
if sys.maxunicode == 0xffff:
|
||||
markdown_text += message_text[last_offset:entity.offset - offset] + insert
|
||||
else:
|
||||
markdown_text += message_text[last_offset * 2:(entity.offset
|
||||
- offset) * 2].decode('utf-16-le') + insert
|
||||
|
||||
last_offset = entity.offset - offset + entity.length
|
||||
|
||||
if offset == 0:
|
||||
if sys.maxunicode == 0xffff:
|
||||
markdown_text += escape_markdown(message_text[last_offset:entity.offset]) + insert
|
||||
markdown_text += escape_markdown(message_text[last_offset:], version=version)
|
||||
else:
|
||||
markdown_text += escape_markdown(message_text[last_offset * 2:entity.offset * 2]
|
||||
.decode('utf-16-le')) + insert
|
||||
|
||||
last_offset = entity.offset + entity.length
|
||||
|
||||
if sys.maxunicode == 0xffff:
|
||||
markdown_text += escape_markdown(message_text[last_offset:])
|
||||
markdown_text += escape_markdown(message_text[last_offset * 2:]
|
||||
.decode('utf-16-le'), version=version)
|
||||
else:
|
||||
markdown_text += escape_markdown(message_text[last_offset * 2:].decode('utf-16-le'))
|
||||
if sys.maxunicode == 0xffff:
|
||||
markdown_text += message_text[last_offset:]
|
||||
else:
|
||||
markdown_text += message_text[last_offset * 2:].decode('utf-16-le')
|
||||
|
||||
return markdown_text
|
||||
|
||||
@property
|
||||
def text_markdown(self):
|
||||
"""Creates an Markdown-formatted string from the markup entities found in the message.
|
||||
"""Creates an Markdown-formatted string from the markup entities found in the message
|
||||
using :class:`telegram.ParseMode.MARKDOWN`.
|
||||
|
||||
Use this if you want to retrieve the message text with the entities formatted as Markdown
|
||||
in the same way the original message was formatted.
|
||||
@@ -1167,9 +1346,24 @@ class Message(TelegramObject):
|
||||
"""
|
||||
return self._parse_markdown(self.text, self.parse_entities(), urled=False)
|
||||
|
||||
@property
|
||||
def text_markdown_v2(self):
|
||||
"""Creates an Markdown-formatted string from the markup entities found in the message
|
||||
using :class:`telegram.ParseMode.MARKDOWN_V2`.
|
||||
|
||||
Use this if you want to retrieve the message text with the entities formatted as Markdown
|
||||
in the same way the original message was formatted.
|
||||
|
||||
Returns:
|
||||
:obj:`str`: Message text with entities formatted as Markdown.
|
||||
|
||||
"""
|
||||
return self._parse_markdown(self.text, self.parse_entities(), urled=False, version=2)
|
||||
|
||||
@property
|
||||
def text_markdown_urled(self):
|
||||
"""Creates an Markdown-formatted string from the markup entities found in the message.
|
||||
"""Creates an Markdown-formatted string from the markup entities found in the message
|
||||
using :class:`telegram.ParseMode.MARKDOWN`.
|
||||
|
||||
Use this if you want to retrieve the message text with the entities formatted as Markdown.
|
||||
This also formats :attr:`telegram.MessageEntity.URL` as a hyperlink.
|
||||
@@ -1180,10 +1374,24 @@ class Message(TelegramObject):
|
||||
"""
|
||||
return self._parse_markdown(self.text, self.parse_entities(), urled=True)
|
||||
|
||||
@property
|
||||
def text_markdown_v2_urled(self):
|
||||
"""Creates an Markdown-formatted string from the markup entities found in the message
|
||||
using :class:`telegram.ParseMode.MARKDOWN_V2`.
|
||||
|
||||
Use this if you want to retrieve the message text with the entities formatted as Markdown.
|
||||
This also formats :attr:`telegram.MessageEntity.URL` as a hyperlink.
|
||||
|
||||
Returns:
|
||||
:obj:`str`: Message text with entities formatted as Markdown.
|
||||
|
||||
"""
|
||||
return self._parse_markdown(self.text, self.parse_entities(), urled=True, version=2)
|
||||
|
||||
@property
|
||||
def caption_markdown(self):
|
||||
"""Creates an Markdown-formatted string from the markup entities found in the message's
|
||||
caption.
|
||||
caption using :class:`telegram.ParseMode.MARKDOWN`.
|
||||
|
||||
Use this if you want to retrieve the message caption with the caption entities formatted as
|
||||
Markdown in the same way the original message was formatted.
|
||||
@@ -1194,10 +1402,25 @@ class Message(TelegramObject):
|
||||
"""
|
||||
return self._parse_markdown(self.caption, self.parse_caption_entities(), urled=False)
|
||||
|
||||
@property
|
||||
def caption_markdown_v2(self):
|
||||
"""Creates an Markdown-formatted string from the markup entities found in the message's
|
||||
caption using :class:`telegram.ParseMode.MARKDOWN_V2`.
|
||||
|
||||
Use this if you want to retrieve the message caption with the caption entities formatted as
|
||||
Markdown in the same way the original message was formatted.
|
||||
|
||||
Returns:
|
||||
:obj:`str`: Message caption with caption entities formatted as Markdown.
|
||||
|
||||
"""
|
||||
return self._parse_markdown(self.caption, self.parse_caption_entities(),
|
||||
urled=False, version=2)
|
||||
|
||||
@property
|
||||
def caption_markdown_urled(self):
|
||||
"""Creates an Markdown-formatted string from the markup entities found in the message's
|
||||
caption.
|
||||
caption using :class:`telegram.ParseMode.MARKDOWN`.
|
||||
|
||||
Use this if you want to retrieve the message caption with the caption entities formatted as
|
||||
Markdown. This also formats :attr:`telegram.MessageEntity.URL` as a hyperlink.
|
||||
@@ -1207,3 +1430,18 @@ class Message(TelegramObject):
|
||||
|
||||
"""
|
||||
return self._parse_markdown(self.caption, self.parse_caption_entities(), urled=True)
|
||||
|
||||
@property
|
||||
def caption_markdown_v2_urled(self):
|
||||
"""Creates an Markdown-formatted string from the markup entities found in the message's
|
||||
caption using :class:`telegram.ParseMode.MARKDOWN_V2`.
|
||||
|
||||
Use this if you want to retrieve the message caption with the caption entities formatted as
|
||||
Markdown. This also formats :attr:`telegram.MessageEntity.URL` as a hyperlink.
|
||||
|
||||
Returns:
|
||||
:obj:`str`: Message caption with caption entities formatted as Markdown.
|
||||
|
||||
"""
|
||||
return self._parse_markdown(self.caption, self.parse_caption_entities(),
|
||||
urled=True, version=2)
|
||||
|
||||
@@ -32,6 +32,8 @@ class MessageEntity(TelegramObject):
|
||||
length (:obj:`int`): Length of the entity in UTF-16 code units.
|
||||
url (:obj:`str`): Optional. Url that will be opened after user taps on the text.
|
||||
user (:class:`telegram.User`): Optional. The mentioned user.
|
||||
language (:obj:`str`): Optional. Programming language of the entity
|
||||
text
|
||||
|
||||
Args:
|
||||
type (:obj:`str`): Type of the entity. Can be mention (@username), hashtag, bot_command,
|
||||
@@ -40,13 +42,16 @@ class MessageEntity(TelegramObject):
|
||||
without usernames).
|
||||
offset (:obj:`int`): Offset in UTF-16 code units to the start of the entity.
|
||||
length (:obj:`int`): Length of the entity in UTF-16 code units.
|
||||
url (:obj:`str`, optional): For "text_link" only, url that will be opened after usertaps on
|
||||
the text.
|
||||
user (:class:`telegram.User`, optional): For "text_mention" only, the mentioned user.
|
||||
url (:obj:`str`, optional): For :attr:`TEXT_LINK` only, url that will be opened after
|
||||
usertaps on the text.
|
||||
user (:class:`telegram.User`, optional): For :attr:`TEXT_MENTION` only, the mentioned
|
||||
user.
|
||||
language (:obj:`str`, optional): For :attr:`PRE` only, the programming language of
|
||||
the entity text
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, type, offset, length, url=None, user=None, **kwargs):
|
||||
def __init__(self, type, offset, length, url=None, user=None, language=None, **kwargs):
|
||||
# Required
|
||||
self.type = type
|
||||
self.offset = offset
|
||||
@@ -54,6 +59,7 @@ class MessageEntity(TelegramObject):
|
||||
# Optionals
|
||||
self.url = url
|
||||
self.user = user
|
||||
self.language = language
|
||||
|
||||
self._id_attrs = (self.type, self.offset, self.length)
|
||||
|
||||
@@ -105,8 +111,12 @@ class MessageEntity(TelegramObject):
|
||||
""":obj:`str`: 'text_link'"""
|
||||
TEXT_MENTION = 'text_mention'
|
||||
""":obj:`str`: 'text_mention'"""
|
||||
UNDERLINE = 'underline'
|
||||
""":obj:`str`: 'underline'"""
|
||||
STRIKETHROUGH = 'strikethrough'
|
||||
""":obj:`str`: 'strikethrough'"""
|
||||
ALL_TYPES = [
|
||||
MENTION, HASHTAG, CASHTAG, PHONE_NUMBER, BOT_COMMAND, URL,
|
||||
EMAIL, BOLD, ITALIC, CODE, PRE, TEXT_LINK, TEXT_MENTION
|
||||
EMAIL, BOLD, ITALIC, CODE, PRE, TEXT_LINK, TEXT_MENTION, UNDERLINE, STRIKETHROUGH
|
||||
]
|
||||
"""List[:obj:`str`]: List of all the types."""
|
||||
|
||||
@@ -25,5 +25,7 @@ class ParseMode(object):
|
||||
|
||||
MARKDOWN = 'Markdown'
|
||||
""":obj:`str`: 'Markdown'"""
|
||||
MARKDOWN_V2 = 'MarkdownV2'
|
||||
""":obj:`str`: 'MarkdownV2'"""
|
||||
HTML = 'HTML'
|
||||
""":obj:`str`: 'HTML'"""
|
||||
|
||||
@@ -32,10 +32,12 @@ class PersonalDetails(TelegramObject):
|
||||
country_code (:obj:`str`): Citizenship (ISO 3166-1 alpha-2 country code).
|
||||
residence_country_code (:obj:`str`): Country of residence (ISO 3166-1 alpha-2 country
|
||||
code).
|
||||
first_name (:obj:`str`): First Name in the language of the user's country of residence.
|
||||
middle_name (:obj:`str`): Optional. Middle Name in the language of the user's country of
|
||||
first_name_native (:obj:`str`): First Name in the language of the user's country of
|
||||
residence.
|
||||
middle_name_native (:obj:`str`): Optional. Middle Name in the language of the user's
|
||||
country of residence.
|
||||
last_name_native (:obj:`str`): Last Name in the language of the user's country of
|
||||
residence.
|
||||
last_name (:obj:`str`): Last Name in the language of the user's country of residence.
|
||||
"""
|
||||
|
||||
def __init__(self, first_name, last_name, birth_date, gender, country_code,
|
||||
|
||||
@@ -28,12 +28,18 @@ class PassportFile(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:`int`): File size.
|
||||
file_date (:obj:`int`): Unix time when the file was uploaded.
|
||||
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.
|
||||
file_size (:obj:`int`): File size.
|
||||
file_date (:obj:`int`): Unix time when the file was uploaded.
|
||||
bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods.
|
||||
@@ -41,16 +47,24 @@ class PassportFile(TelegramObject):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, file_id, file_date, file_size=None, bot=None, credentials=None, **kwargs):
|
||||
def __init__(self,
|
||||
file_id,
|
||||
file_unique_id,
|
||||
file_date,
|
||||
file_size=None,
|
||||
bot=None,
|
||||
credentials=None,
|
||||
**kwargs):
|
||||
# Required
|
||||
self.file_id = file_id
|
||||
self.file_unique_id = file_unique_id
|
||||
self.file_size = file_size
|
||||
self.file_date = file_date
|
||||
# Optionals
|
||||
self.bot = bot
|
||||
self._credentials = credentials
|
||||
|
||||
self._id_attrs = (self.file_id,)
|
||||
self._id_attrs = (self.file_unique_id,)
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data, bot):
|
||||
|
||||
+152
-2
@@ -19,7 +19,10 @@
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains an object that represents a Telegram Poll."""
|
||||
|
||||
from telegram import (TelegramObject)
|
||||
import sys
|
||||
|
||||
from telegram import (TelegramObject, User, MessageEntity)
|
||||
from telegram.utils.helpers import to_timestamp, from_timestamp
|
||||
|
||||
|
||||
class PollOption(TelegramObject):
|
||||
@@ -48,6 +51,39 @@ class PollOption(TelegramObject):
|
||||
return cls(**data)
|
||||
|
||||
|
||||
class PollAnswer(TelegramObject):
|
||||
"""
|
||||
This object represents an answer of a user in a non-anonymous poll.
|
||||
|
||||
Attributes:
|
||||
poll_id (:obj:`str`): Unique poll identifier.
|
||||
user (:class:`telegram.User`): The user, who changed the answer to the poll.
|
||||
option_ids (List[:obj:`int`]): Identifiers of answer options, chosen by the user.
|
||||
|
||||
Args:
|
||||
poll_id (:obj:`str`): Unique poll identifier.
|
||||
user (:class:`telegram.User`): The user, who changed the answer to the poll.
|
||||
option_ids (List[:obj:`int`]): 0-based identifiers of answer options, chosen by the user.
|
||||
May be empty if the user retracted their vote.
|
||||
|
||||
"""
|
||||
def __init__(self, poll_id, user, option_ids, **kwargs):
|
||||
self.poll_id = poll_id
|
||||
self.user = user
|
||||
self.option_ids = option_ids
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data, bot):
|
||||
if not data:
|
||||
return None
|
||||
|
||||
data = super(PollAnswer, cls).de_json(data, bot)
|
||||
|
||||
data['user'] = User.de_json(data.get('user'), bot)
|
||||
|
||||
return cls(**data)
|
||||
|
||||
|
||||
class Poll(TelegramObject):
|
||||
"""
|
||||
This object contains information about a poll.
|
||||
@@ -56,21 +92,71 @@ class Poll(TelegramObject):
|
||||
id (:obj:`str`): Unique poll identifier.
|
||||
question (:obj:`str`): Poll question, 1-255 characters.
|
||||
options (List[:class:`PollOption`]): List of poll options.
|
||||
total_voter_count (:obj:`int`): Total number of users that voted in the poll.
|
||||
is_closed (:obj:`bool`): True, if the poll is closed.
|
||||
is_anonymous (:obj:`bool`): True, if the poll is anonymous.
|
||||
type (:obj:`str`): Poll type, currently can be :attr:`REGULAR` or :attr:`QUIZ`.
|
||||
allows_multiple_answers (:obj:`bool`): True, if the poll allows multiple answers.
|
||||
correct_option_id (:obj:`int`): Optional. Identifier of the correct answer option.
|
||||
explanation (:obj:`str`): Optional. Text that is shown when a user chooses an incorrect
|
||||
answer or taps on the lamp icon in a quiz-style poll.
|
||||
explanation_entities (List[:class:`telegram.MessageEntity`]): Optional. Special entities
|
||||
like usernames, URLs, bot commands, etc. that appear in the :attr:`explanation`.
|
||||
open_period (:obj:`int`): Optional. Amount of time in seconds the poll will be active
|
||||
after creation.
|
||||
close_date (:obj:`datetime.datetime`): Optional. Point in time when the poll will be
|
||||
automatically closed.
|
||||
|
||||
Args:
|
||||
id (:obj:`str`): Unique poll identifier.
|
||||
question (:obj:`str`): Poll question, 1-255 characters.
|
||||
options (List[:class:`PollOption`]): List of poll options.
|
||||
is_closed (:obj:`bool`): True, if the poll is closed.
|
||||
is_anonymous (:obj:`bool`): True, if the poll is anonymous.
|
||||
type (:obj:`str`): Poll type, currently can be :attr:`REGULAR` or :attr:`QUIZ`.
|
||||
allows_multiple_answers (:obj:`bool`): True, if the poll allows multiple answers.
|
||||
correct_option_id (:obj:`int`, optional): 0-based identifier of the correct answer option.
|
||||
Available only for polls in the quiz mode, which are closed, or was sent (not
|
||||
forwarded) by the bot or to the private chat with the bot.
|
||||
explanation (:obj:`str`, optional): Text that is shown when a user chooses an incorrect
|
||||
answer or taps on the lamp icon in a quiz-style poll, 0-200 characters.
|
||||
explanation_entities (List[:class:`telegram.MessageEntity`], optional): Special entities
|
||||
like usernames, URLs, bot commands, etc. that appear in the :attr:`explanation`.
|
||||
open_period (:obj:`int`, optional): Amount of time in seconds the poll will be active
|
||||
after creation.
|
||||
close_date (:obj:`datetime.datetime`, optional): Point in time (Unix timestamp) when the
|
||||
poll will be automatically closed. Converted to :obj:`datetime.datetime`.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, id, question, options, is_closed, **kwargs):
|
||||
def __init__(self,
|
||||
id,
|
||||
question,
|
||||
options,
|
||||
total_voter_count,
|
||||
is_closed,
|
||||
is_anonymous,
|
||||
type,
|
||||
allows_multiple_answers,
|
||||
correct_option_id=None,
|
||||
explanation=None,
|
||||
explanation_entities=None,
|
||||
open_period=None,
|
||||
close_date=None,
|
||||
**kwargs):
|
||||
self.id = id
|
||||
self.question = question
|
||||
self.options = options
|
||||
self.total_voter_count = total_voter_count
|
||||
self.is_closed = is_closed
|
||||
self.is_anonymous = is_anonymous
|
||||
self.type = type
|
||||
self.allows_multiple_answers = allows_multiple_answers
|
||||
self.correct_option_id = correct_option_id
|
||||
self.explanation = explanation
|
||||
self.explanation_entities = explanation_entities
|
||||
self.open_period = open_period
|
||||
self.close_date = close_date
|
||||
|
||||
self._id_attrs = (self.id,)
|
||||
|
||||
@@ -82,6 +168,8 @@ class Poll(TelegramObject):
|
||||
data = super(Poll, cls).de_json(data, bot)
|
||||
|
||||
data['options'] = [PollOption.de_json(option, bot) for option in data['options']]
|
||||
data['explanation_entities'] = MessageEntity.de_list(data.get('explanation_entities'), bot)
|
||||
data['close_date'] = from_timestamp(data.get('close_date'))
|
||||
|
||||
return cls(**data)
|
||||
|
||||
@@ -89,5 +177,67 @@ class Poll(TelegramObject):
|
||||
data = super(Poll, self).to_dict()
|
||||
|
||||
data['options'] = [x.to_dict() for x in self.options]
|
||||
if self.explanation_entities:
|
||||
data['explanation_entities'] = [e.to_dict() for e in self.explanation_entities]
|
||||
data['close_date'] = to_timestamp(data.get('close_date'))
|
||||
|
||||
return data
|
||||
|
||||
def parse_explanation_entity(self, entity):
|
||||
"""Returns the text from a given :class:`telegram.MessageEntity`.
|
||||
|
||||
Note:
|
||||
This method is present because Telegram calculates the offset and length in
|
||||
UTF-16 codepoint pairs, which some versions of Python don't handle automatically.
|
||||
(That is, you can't just slice ``Message.text`` with the offset and length.)
|
||||
|
||||
Args:
|
||||
entity (:class:`telegram.MessageEntity`): The entity to extract the text from. It must
|
||||
be an entity that belongs to this message.
|
||||
|
||||
Returns:
|
||||
:obj:`str`: The text of the given entity.
|
||||
|
||||
"""
|
||||
# Is it a narrow build, if so we don't need to convert
|
||||
if sys.maxunicode == 0xffff:
|
||||
return self.explanation[entity.offset:entity.offset + entity.length]
|
||||
else:
|
||||
entity_text = self.explanation.encode('utf-16-le')
|
||||
entity_text = entity_text[entity.offset * 2:(entity.offset + entity.length) * 2]
|
||||
|
||||
return entity_text.decode('utf-16-le')
|
||||
|
||||
def parse_explanation_entities(self, types=None):
|
||||
"""
|
||||
Returns a :obj:`dict` that maps :class:`telegram.MessageEntity` to :obj:`str`.
|
||||
It contains entities from this polls explanation filtered by their ``type`` attribute as
|
||||
the key, and the text that each entity belongs to as the value of the :obj:`dict`.
|
||||
|
||||
Note:
|
||||
This method should always be used instead of the :attr:`explanation_entities`
|
||||
attribute, since it calculates the correct substring from the message text based on
|
||||
UTF-16 codepoints. See :attr:`parse_explanation_entity` for more info.
|
||||
|
||||
Args:
|
||||
types (List[:obj:`str`], optional): List of ``MessageEntity`` types as strings. If the
|
||||
``type`` attribute of an entity is contained in this list, it will be returned.
|
||||
Defaults to :attr:`telegram.MessageEntity.ALL_TYPES`.
|
||||
|
||||
Returns:
|
||||
Dict[:class:`telegram.MessageEntity`, :obj:`str`]: A dictionary of entities mapped to
|
||||
the text that belongs to them, calculated based on UTF-16 codepoints.
|
||||
|
||||
"""
|
||||
if types is None:
|
||||
types = MessageEntity.ALL_TYPES
|
||||
|
||||
return {
|
||||
entity: self.parse_explanation_entity(entity)
|
||||
for entity in self.explanation_entities if entity.type in types
|
||||
}
|
||||
|
||||
REGULAR = "regular"
|
||||
""":obj:`str`: 'regular'"""
|
||||
QUIZ = "quiz"
|
||||
""":obj:`str`: 'quiz'"""
|
||||
|
||||
+18
-3
@@ -20,6 +20,7 @@
|
||||
|
||||
from telegram import (Message, TelegramObject, InlineQuery, ChosenInlineResult,
|
||||
CallbackQuery, ShippingQuery, PreCheckoutQuery, Poll)
|
||||
from telegram.poll import PollAnswer
|
||||
|
||||
|
||||
class Update(TelegramObject):
|
||||
@@ -42,7 +43,10 @@ class Update(TelegramObject):
|
||||
pre_checkout_query (:class:`telegram.PreCheckoutQuery`): Optional. New incoming
|
||||
pre-checkout query.
|
||||
poll (:class:`telegram.Poll`): Optional. New poll state. Bots receive only updates
|
||||
about polls, which are sent or stopped by the bot
|
||||
about stopped polls and polls, which are sent by the bot
|
||||
poll_answer (:class:`telegram.PollAnswer`): Optional. A user changed their answer
|
||||
in a non-anonymous poll. Bots receive new votes only in polls that were sent
|
||||
by the bot itself.
|
||||
|
||||
Args:
|
||||
update_id (:obj:`int`): The update's unique identifier. Update identifiers start from a
|
||||
@@ -67,6 +71,9 @@ class Update(TelegramObject):
|
||||
pre-checkout query. Contains full information about checkout
|
||||
poll (:class:`telegram.Poll`, optional): New poll state. Bots receive only updates
|
||||
about polls, which are sent or stopped by the bot
|
||||
poll_answer (:class:`telegram.PollAnswer`, optional): A user changed their answer
|
||||
in a non-anonymous poll. Bots receive new votes only in polls that were sent
|
||||
by the bot itself.
|
||||
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
||||
|
||||
"""
|
||||
@@ -83,6 +90,7 @@ class Update(TelegramObject):
|
||||
shipping_query=None,
|
||||
pre_checkout_query=None,
|
||||
poll=None,
|
||||
poll_answer=None,
|
||||
**kwargs):
|
||||
# Required
|
||||
self.update_id = int(update_id)
|
||||
@@ -97,6 +105,7 @@ class Update(TelegramObject):
|
||||
self.channel_post = channel_post
|
||||
self.edited_channel_post = edited_channel_post
|
||||
self.poll = poll
|
||||
self.poll_answer = poll_answer
|
||||
|
||||
self._effective_user = None
|
||||
self._effective_chat = None
|
||||
@@ -137,6 +146,9 @@ class Update(TelegramObject):
|
||||
elif self.pre_checkout_query:
|
||||
user = self.pre_checkout_query.from_user
|
||||
|
||||
elif self.poll_answer:
|
||||
user = self.poll_answer.user
|
||||
|
||||
self._effective_user = user
|
||||
return user
|
||||
|
||||
@@ -146,7 +158,8 @@ class Update(TelegramObject):
|
||||
:class:`telegram.Chat`: The chat that this update was sent in, no matter what kind of
|
||||
update this is. Will be ``None`` for :attr:`inline_query`,
|
||||
:attr:`chosen_inline_result`, :attr:`callback_query` from inline messages,
|
||||
:attr:`shipping_query`, :attr:`pre_checkout_query` and :attr:`poll`.
|
||||
:attr:`shipping_query`, :attr:`pre_checkout_query`, :attr:`poll` and
|
||||
:attr:`poll_answer`.
|
||||
|
||||
"""
|
||||
if self._effective_chat:
|
||||
@@ -178,7 +191,8 @@ class Update(TelegramObject):
|
||||
:class:`telegram.Message`: The message included in this update, no matter what kind of
|
||||
update this is. Will be ``None`` for :attr:`inline_query`,
|
||||
:attr:`chosen_inline_result`, :attr:`callback_query` from inline messages,
|
||||
:attr:`shipping_query`, :attr:`pre_checkout_query` and :attr:`poll`.
|
||||
:attr:`shipping_query`, :attr:`pre_checkout_query`, :attr:`poll` and
|
||||
:attr:`poll_answer`.
|
||||
|
||||
"""
|
||||
if self._effective_message:
|
||||
@@ -237,5 +251,6 @@ class Update(TelegramObject):
|
||||
edited_channel_post['default_quote'] = data.get('default_quote')
|
||||
data['edited_channel_post'] = Message.de_json(edited_channel_post, bot)
|
||||
data['poll'] = Poll.de_json(data.get('poll'), bot)
|
||||
data['poll_answer'] = PollAnswer.de_json(data.get('poll_answer'), bot)
|
||||
|
||||
return cls(**data)
|
||||
|
||||
+32
-3
@@ -34,6 +34,12 @@ class User(TelegramObject):
|
||||
last_name (:obj:`str`): Optional. User's or bot's last name.
|
||||
username (:obj:`str`): Optional. User's or bot's username.
|
||||
language_code (:obj:`str`): Optional. IETF language tag of the user's language.
|
||||
can_join_groups (:obj:`str`): Optional. True, if the bot can be invited to groups.
|
||||
Returned only in :attr:`telegram.Bot.get_me` requests.
|
||||
can_read_all_group_messages (:obj:`str`): Optional. True, if privacy mode is disabled
|
||||
for the bot. Returned only in :attr:`telegram.Bot.get_me` requests.
|
||||
supports_inline_queries (:obj:`str`): Optional. True, if the bot supports inline queries.
|
||||
Returned only in :attr:`telegram.Bot.get_me` requests.
|
||||
bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods.
|
||||
|
||||
Args:
|
||||
@@ -43,6 +49,12 @@ class User(TelegramObject):
|
||||
last_name (:obj:`str`, optional): User's or bot's last name.
|
||||
username (:obj:`str`, optional): User's or bot's username.
|
||||
language_code (:obj:`str`, optional): IETF language tag of the user's language.
|
||||
can_join_groups (:obj:`str`, optional): True, if the bot can be invited to groups.
|
||||
Returned only in :attr:`telegram.Bot.get_me` requests.
|
||||
can_read_all_group_messages (:obj:`str`, optional): True, if privacy mode is disabled
|
||||
for the bot. Returned only in :attr:`telegram.Bot.get_me` requests.
|
||||
supports_inline_queries (:obj:`str`, optional): True, if the bot supports inline queries.
|
||||
Returned only in :attr:`telegram.Bot.get_me` requests.
|
||||
bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods.
|
||||
|
||||
"""
|
||||
@@ -54,6 +66,9 @@ class User(TelegramObject):
|
||||
last_name=None,
|
||||
username=None,
|
||||
language_code=None,
|
||||
can_join_groups=None,
|
||||
can_read_all_group_messages=None,
|
||||
supports_inline_queries=None,
|
||||
bot=None,
|
||||
**kwargs):
|
||||
# Required
|
||||
@@ -64,7 +79,9 @@ class User(TelegramObject):
|
||||
self.last_name = last_name
|
||||
self.username = username
|
||||
self.language_code = language_code
|
||||
|
||||
self.can_join_groups = can_join_groups
|
||||
self.can_read_all_group_messages = can_read_all_group_messages
|
||||
self.supports_inline_queries = supports_inline_queries
|
||||
self.bot = bot
|
||||
|
||||
self._id_attrs = (self.id,)
|
||||
@@ -99,7 +116,6 @@ class User(TelegramObject):
|
||||
def de_json(cls, data, bot):
|
||||
if not data:
|
||||
return None
|
||||
|
||||
data = super(User, cls).de_json(data, bot)
|
||||
|
||||
return cls(bot=bot, **data)
|
||||
@@ -131,13 +147,26 @@ class User(TelegramObject):
|
||||
name (:obj:`str`): The name used as a link for the user. Defaults to :attr:`full_name`.
|
||||
|
||||
Returns:
|
||||
:obj:`str`: The inline mention for the user as markdown.
|
||||
:obj:`str`: The inline mention for the user as markdown (version 1).
|
||||
|
||||
"""
|
||||
if name:
|
||||
return util_mention_markdown(self.id, name)
|
||||
return util_mention_markdown(self.id, self.full_name)
|
||||
|
||||
def mention_markdown_v2(self, name=None):
|
||||
"""
|
||||
Args:
|
||||
name (:obj:`str`): The name used as a link for the user. Defaults to :attr:`full_name`.
|
||||
|
||||
Returns:
|
||||
:obj:`str`: The inline mention for the user as markdown (version 2).
|
||||
|
||||
"""
|
||||
if name:
|
||||
return util_mention_markdown(self.id, name, version=2)
|
||||
return util_mention_markdown(self.id, self.full_name, version=2)
|
||||
|
||||
def mention_html(self, name=None):
|
||||
"""
|
||||
Args:
|
||||
|
||||
+43
-48
@@ -44,55 +44,43 @@ def get_signal_name(signum):
|
||||
return _signames[signum]
|
||||
|
||||
|
||||
def escape_markdown(text):
|
||||
"""Helper function to escape telegram markup symbols."""
|
||||
escape_chars = '\*_`\['
|
||||
def escape_markdown(text, version=1, entity_type=None):
|
||||
"""
|
||||
Helper function to escape telegram markup symbols.
|
||||
|
||||
Args:
|
||||
text (:obj:`str`): The text.
|
||||
version (:obj:`int` | :obj:`str`): Use to specify the version of telegrams Markdown.
|
||||
Either ``1`` or ``2``. Defaults to ``1``.
|
||||
entity_type (:obj:`str`, optional): For the entity types ``PRE``, ``CODE`` and the link
|
||||
part of ``TEXT_LINKS``, only certain characters need to be escaped in ``MarkdownV2``.
|
||||
See the official API documentation for details. Only valid in combination with
|
||||
``version=2``, will be ignored else.
|
||||
"""
|
||||
if int(version) == 1:
|
||||
escape_chars = '\*_`\['
|
||||
elif int(version) == 2:
|
||||
if entity_type == 'pre' or entity_type == 'code':
|
||||
escape_chars = '`\\\\'
|
||||
elif entity_type == 'text_link':
|
||||
escape_chars = ')\\\\'
|
||||
else:
|
||||
escape_chars = '_*\[\]()~`>\#\+\-=|{}\.!'
|
||||
else:
|
||||
raise ValueError('Markdown version musst be either 1 or 2!')
|
||||
|
||||
return re.sub(r'([%s])' % escape_chars, r'\\\1', text)
|
||||
|
||||
|
||||
# -------- date/time related helpers --------
|
||||
# TODO: add generic specification of UTC for naive datetimes to docs
|
||||
|
||||
if hasattr(dtm, 'timezone'):
|
||||
# Python 3.3+
|
||||
def _datetime_to_float_timestamp(dt_obj):
|
||||
if dt_obj.tzinfo is None:
|
||||
dt_obj = dt_obj.replace(tzinfo=_UTC)
|
||||
return dt_obj.timestamp()
|
||||
|
||||
_UtcOffsetTimezone = dtm.timezone
|
||||
_UTC = dtm.timezone.utc
|
||||
else:
|
||||
# Python < 3.3 (incl 2.7)
|
||||
|
||||
# hardcoded timezone class (`datetime.timezone` isn't available in py2)
|
||||
class _UtcOffsetTimezone(dtm.tzinfo):
|
||||
def __init__(self, offset):
|
||||
self.offset = offset
|
||||
|
||||
def tzname(self, dt):
|
||||
return 'UTC +{}'.format(self.offset)
|
||||
|
||||
def utcoffset(self, dt):
|
||||
return self.offset
|
||||
|
||||
def dst(self, dt):
|
||||
return dtm.timedelta(0)
|
||||
|
||||
_UTC = _UtcOffsetTimezone(dtm.timedelta(0))
|
||||
__EPOCH_DT = dtm.datetime.fromtimestamp(0, tz=_UTC)
|
||||
__NAIVE_EPOCH_DT = __EPOCH_DT.replace(tzinfo=None)
|
||||
|
||||
# _datetime_to_float_timestamp
|
||||
# Not using future.backports.datetime here as datetime value might be an input from the user,
|
||||
# making every isinstace() call more delicate. So we just use our own compat layer.
|
||||
def _datetime_to_float_timestamp(dt_obj):
|
||||
epoch_dt = __EPOCH_DT if dt_obj.tzinfo is not None else __NAIVE_EPOCH_DT
|
||||
return (dt_obj - epoch_dt).total_seconds()
|
||||
|
||||
_datetime_to_float_timestamp.__doc__ = \
|
||||
def _datetime_to_float_timestamp(dt_obj):
|
||||
"""Converts a datetime object to a float timestamp (with sub-second precision).
|
||||
If the datetime object is timezone-naive, it is assumed to be in UTC."""
|
||||
If the datetime object is timezone-naive, it is assumed to be in UTC."""
|
||||
if dt_obj.tzinfo is None:
|
||||
dt_obj = dt_obj.replace(tzinfo=dtm.timezone.utc)
|
||||
return dt_obj.timestamp()
|
||||
|
||||
|
||||
def to_float_timestamp(t, reference_timestamp=None):
|
||||
@@ -174,22 +162,27 @@ def to_timestamp(dt_obj, reference_timestamp=None):
|
||||
return int(to_float_timestamp(dt_obj, reference_timestamp)) if dt_obj is not None else None
|
||||
|
||||
|
||||
def from_timestamp(unixtime):
|
||||
def from_timestamp(unixtime, tzinfo=dtm.timezone.utc):
|
||||
"""
|
||||
Converts an (integer) unix timestamp to a naive datetime object in UTC.
|
||||
Converts an (integer) unix timestamp to a timezone aware datetime object.
|
||||
``None`` s are left alone (i.e. ``from_timestamp(None)`` is ``None``).
|
||||
|
||||
Args:
|
||||
unixtime (int): integer POSIX timestamp
|
||||
tzinfo (:obj:`datetime.tzinfo`, optional): The timezone, the timestamp is to be converted
|
||||
to. Defaults to UTC.
|
||||
|
||||
Returns:
|
||||
equivalent :obj:`datetime.datetime` value in naive UTC if ``timestamp`` is not
|
||||
timezone aware equivalent :obj:`datetime.datetime` value if ``timestamp`` is not
|
||||
``None``; else ``None``
|
||||
"""
|
||||
if unixtime is None:
|
||||
return None
|
||||
|
||||
return dtm.datetime.utcfromtimestamp(unixtime)
|
||||
if tzinfo is not None:
|
||||
return dtm.datetime.fromtimestamp(unixtime, tz=tzinfo)
|
||||
else:
|
||||
return dtm.datetime.utcfromtimestamp(unixtime)
|
||||
|
||||
# -------- end --------
|
||||
|
||||
@@ -207,17 +200,19 @@ def mention_html(user_id, name):
|
||||
return u'<a href="tg://user?id={}">{}</a>'.format(user_id, escape(name))
|
||||
|
||||
|
||||
def mention_markdown(user_id, name):
|
||||
def mention_markdown(user_id, name, version=1):
|
||||
"""
|
||||
Args:
|
||||
user_id (:obj:`int`) The user's id which you want to mention.
|
||||
name (:obj:`str`) The name the mention is showing.
|
||||
version (:obj:`int` | :obj:`str`): Use to specify the version of telegrams Markdown.
|
||||
Either ``1`` or ``2``. Defaults to ``1``
|
||||
|
||||
Returns:
|
||||
:obj:`str`: The inline mention for the user as markdown.
|
||||
"""
|
||||
if isinstance(user_id, int):
|
||||
return u'[{}](tg://user?id={})'.format(escape_markdown(name), user_id)
|
||||
return u'[{}](tg://user?id={})'.format(escape_markdown(name, version=version), user_id)
|
||||
|
||||
|
||||
def effective_message_type(entity):
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
import sys
|
||||
import logging
|
||||
from telegram import Update
|
||||
from future.utils import bytes_to_native_str
|
||||
@@ -27,7 +28,6 @@ except ImportError:
|
||||
from tornado.httpserver import HTTPServer
|
||||
from tornado.ioloop import IOLoop
|
||||
import tornado.web
|
||||
import tornado.iostream
|
||||
|
||||
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
||||
|
||||
@@ -91,6 +91,35 @@ class WebhookHandler(tornado.web.RequestHandler):
|
||||
def __init__(self, application, request, **kwargs):
|
||||
super(WebhookHandler, self).__init__(application, request, **kwargs)
|
||||
self.logger = logging.getLogger(__name__)
|
||||
self._init_asyncio_patch()
|
||||
|
||||
def _init_asyncio_patch(self):
|
||||
"""set default asyncio policy to be compatible with tornado
|
||||
Tornado 6 (at least) is not compatible with the default
|
||||
asyncio implementation on Windows
|
||||
Pick the older SelectorEventLoopPolicy on Windows
|
||||
if the known-incompatible default policy is in use.
|
||||
do this as early as possible to make it a low priority and overrideable
|
||||
ref: https://github.com/tornadoweb/tornado/issues/2608
|
||||
TODO: if/when tornado supports the defaults in asyncio,
|
||||
remove and bump tornado requirement for py38
|
||||
Copied from https://github.com/ipython/ipykernel/pull/456/
|
||||
"""
|
||||
if sys.platform.startswith("win") and sys.version_info >= (3, 8):
|
||||
import asyncio
|
||||
try:
|
||||
from asyncio import (
|
||||
WindowsProactorEventLoopPolicy,
|
||||
WindowsSelectorEventLoopPolicy,
|
||||
)
|
||||
except ImportError:
|
||||
pass
|
||||
# not affected
|
||||
else:
|
||||
if isinstance(asyncio.get_event_loop_policy(), WindowsProactorEventLoopPolicy):
|
||||
# WindowsProactorEventLoopPolicy is not compatible with tornado 6
|
||||
# fallback to the pre-3.8 default of Selector
|
||||
asyncio.set_event_loop_policy(WindowsSelectorEventLoopPolicy())
|
||||
|
||||
def initialize(self, bot, update_queue, default_quote=None):
|
||||
self.bot = bot
|
||||
|
||||
Vendored
+1
-1
Submodule telegram/vendor/ptb_urllib3 updated: d2403a79fc...1954df0395
+1
-1
@@ -17,4 +17,4 @@
|
||||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
|
||||
__version__ = '12.4.2'
|
||||
__version__ = '12.7'
|
||||
|
||||
+19
-2
@@ -21,6 +21,9 @@ import json
|
||||
import base64
|
||||
import os
|
||||
import random
|
||||
import pytest
|
||||
from telegram.utils.request import Request
|
||||
from telegram.error import RetryAfter
|
||||
|
||||
# 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
|
||||
@@ -30,7 +33,7 @@ FALLBACKS = [
|
||||
'token': '579694714:AAHRLL5zBVy4Blx2jRFKe1HlfnXCg08WuLY',
|
||||
'payment_provider_token': '284685063:TEST:NjQ0NjZlNzI5YjJi',
|
||||
'chat_id': '675666224',
|
||||
'super_group_id': '-1001493296829',
|
||||
'super_group_id': '-1001310911135',
|
||||
'channel_id': '@pythontelegrambottests',
|
||||
'bot_name': 'PTB tests fallback 1',
|
||||
'bot_username': '@ptb_fallback_1_bot'
|
||||
@@ -38,7 +41,7 @@ FALLBACKS = [
|
||||
'token': '558194066:AAEEylntuKSLXj9odiv3TnX7Z5KY2J3zY3M',
|
||||
'payment_provider_token': '284685063:TEST:YjEwODQwMTFmNDcy',
|
||||
'chat_id': '675666224',
|
||||
'super_group_id': '-1001493296829',
|
||||
'super_group_id': '-1001221216830',
|
||||
'channel_id': '@pythontelegrambottests',
|
||||
'bot_name': 'PTB tests fallback 2',
|
||||
'bot_username': '@ptb_fallback_2_bot'
|
||||
@@ -73,3 +76,17 @@ def get(name, fallback):
|
||||
|
||||
def get_bot():
|
||||
return {k: get(k, v) for k, v in random.choice(FALLBACKS).items()}
|
||||
|
||||
|
||||
# Patch request to xfail on flood control errors
|
||||
original_request_wrapper = Request._request_wrapper
|
||||
|
||||
|
||||
def patient_request_wrapper(*args, **kwargs):
|
||||
try:
|
||||
return original_request_wrapper(*args, **kwargs)
|
||||
except RetryAfter as e:
|
||||
pytest.xfail('Not waiting for flood control: {}'.format(e))
|
||||
|
||||
|
||||
Request._request_wrapper = patient_request_wrapper
|
||||
|
||||
+25
-7
@@ -31,14 +31,9 @@ from telegram import (Bot, Message, User, Chat, MessageEntity, Update,
|
||||
InlineQuery, CallbackQuery, ShippingQuery, PreCheckoutQuery,
|
||||
ChosenInlineResult)
|
||||
from telegram.ext import Dispatcher, JobQueue, Updater, BaseFilter, Defaults
|
||||
from telegram.utils.helpers import _UtcOffsetTimezone
|
||||
from telegram.error import BadRequest
|
||||
from tests.bots import get_bot
|
||||
|
||||
TRAVIS = os.getenv('TRAVIS', False)
|
||||
|
||||
if TRAVIS:
|
||||
pytest_plugins = ['tests.travis_fold']
|
||||
|
||||
GITHUB_ACTION = os.getenv('GITHUB_ACTION', False)
|
||||
|
||||
if GITHUB_ACTION:
|
||||
@@ -285,4 +280,27 @@ def utc_offset(request):
|
||||
|
||||
@pytest.fixture()
|
||||
def timezone(utc_offset):
|
||||
return _UtcOffsetTimezone(utc_offset)
|
||||
return datetime.timezone(utc_offset)
|
||||
|
||||
|
||||
def expect_bad_request(func, message, reason):
|
||||
"""
|
||||
Wrapper for testing bot functions expected to result in an :class:`telegram.error.BadRequest`.
|
||||
Makes it XFAIL, if the specified error message is present.
|
||||
|
||||
Args:
|
||||
func: The callable to be executed.
|
||||
message: The expected message of the bad request error. If another message is present,
|
||||
the error will be reraised.
|
||||
reason: Explanation for the XFAIL.
|
||||
|
||||
Returns:
|
||||
On success, returns the return value of :attr:`func`
|
||||
"""
|
||||
try:
|
||||
return func()
|
||||
except BadRequest as e:
|
||||
if message in str(e):
|
||||
pytest.xfail('{}. {}'.format(reason, e))
|
||||
else:
|
||||
raise e
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
Binary file not shown.
+19
-6
@@ -41,6 +41,7 @@ def animation(bot, chat_id):
|
||||
|
||||
class TestAnimation(object):
|
||||
animation_file_id = 'CgADAQADngIAAuyVeEez0xRovKi9VAI'
|
||||
animation_file_unique_id = 'adc3145fd2e84d95b64d68eaa22aa33e'
|
||||
width = 320
|
||||
height = 180
|
||||
duration = 1
|
||||
@@ -55,7 +56,9 @@ class TestAnimation(object):
|
||||
def test_creation(self, animation):
|
||||
assert isinstance(animation, Animation)
|
||||
assert isinstance(animation.file_id, str)
|
||||
assert isinstance(animation.file_unique_id, str)
|
||||
assert animation.file_id != ''
|
||||
assert animation.file_unique_id != ''
|
||||
|
||||
def test_expected_values(self, animation):
|
||||
assert animation.file_size == self.file_size
|
||||
@@ -73,7 +76,9 @@ class TestAnimation(object):
|
||||
|
||||
assert isinstance(message.animation, Animation)
|
||||
assert isinstance(message.animation.file_id, str)
|
||||
assert isinstance(message.animation.file_unique_id, str)
|
||||
assert message.animation.file_id != ''
|
||||
assert message.animation.file_unique_id != ''
|
||||
assert message.animation.file_name == animation.file_name
|
||||
assert message.animation.mime_type == animation.mime_type
|
||||
assert message.animation.file_size == animation.file_size
|
||||
@@ -103,8 +108,12 @@ class TestAnimation(object):
|
||||
|
||||
assert isinstance(message.animation, Animation)
|
||||
assert isinstance(message.animation.file_id, str)
|
||||
assert message.animation.file_id is not None
|
||||
assert isinstance(message.animation.file_unique_id, str)
|
||||
assert message.animation.file_id != ''
|
||||
assert message.animation.file_unique_id != ''
|
||||
|
||||
assert message.animation.duration == animation.duration
|
||||
assert message.animation.file_name == animation.file_name
|
||||
assert message.animation.mime_type == animation.mime_type
|
||||
assert message.animation.file_size == animation.file_size
|
||||
|
||||
@@ -143,7 +152,6 @@ class TestAnimation(object):
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
@pytest.mark.skip(reason='Doesnt work without API 4.5')
|
||||
def test_resend(self, bot, chat_id, animation):
|
||||
message = bot.send_animation(chat_id, animation.file_id)
|
||||
|
||||
@@ -160,6 +168,7 @@ class TestAnimation(object):
|
||||
def test_de_json(self, bot, animation):
|
||||
json_dict = {
|
||||
'file_id': self.animation_file_id,
|
||||
'file_unique_id': self.animation_file_unique_id,
|
||||
'width': self.width,
|
||||
'height': self.height,
|
||||
'duration': self.duration,
|
||||
@@ -170,6 +179,7 @@ class TestAnimation(object):
|
||||
}
|
||||
animation = Animation.de_json(json_dict, bot)
|
||||
assert animation.file_id == self.animation_file_id
|
||||
assert animation.file_unique_id == self.animation_file_unique_id
|
||||
assert animation.thumb == animation.thumb
|
||||
assert animation.file_name == self.file_name
|
||||
assert animation.mime_type == self.mime_type
|
||||
@@ -180,6 +190,7 @@ class TestAnimation(object):
|
||||
|
||||
assert isinstance(animation_dict, dict)
|
||||
assert animation_dict['file_id'] == animation.file_id
|
||||
assert animation_dict['file_unique_id'] == animation.file_unique_id
|
||||
assert animation_dict['width'] == animation.width
|
||||
assert animation_dict['height'] == animation.height
|
||||
assert animation_dict['duration'] == animation.duration
|
||||
@@ -214,10 +225,12 @@ class TestAnimation(object):
|
||||
assert animation.get_file()
|
||||
|
||||
def test_equality(self):
|
||||
a = Animation(self.animation_file_id, self.height, self.width, self.duration)
|
||||
b = Animation(self.animation_file_id, self.height, self.width, self.duration)
|
||||
d = Animation('', 0, 0, 0)
|
||||
e = Voice(self.animation_file_id, 0)
|
||||
a = Animation(self.animation_file_id, self.animation_file_unique_id,
|
||||
self.height, self.width, self.duration)
|
||||
b = Animation('', self.animation_file_unique_id,
|
||||
self.height, self.width, self.duration)
|
||||
d = Animation('', '', 0, 0, 0)
|
||||
e = Voice(self.animation_file_id, self.animation_file_unique_id, 0)
|
||||
|
||||
assert a == b
|
||||
assert hash(a) == hash(b)
|
||||
|
||||
+28
-15
@@ -52,12 +52,16 @@ class TestAudio(object):
|
||||
thumb_file_size = 1427
|
||||
thumb_width = 50
|
||||
thumb_height = 50
|
||||
audio_file_id = '5a3128a4d2a04750b5b58397f3b5e812'
|
||||
audio_file_unique_id = 'adc3145fd2e84d95b64d68eaa22aa33e'
|
||||
|
||||
def test_creation(self, audio):
|
||||
# Make sure file has been uploaded.
|
||||
assert isinstance(audio, Audio)
|
||||
assert isinstance(audio.file_id, str)
|
||||
assert isinstance(audio.file_unique_id, str)
|
||||
assert audio.file_id != ''
|
||||
assert audio.file_unique_id != ''
|
||||
|
||||
def test_expected_values(self, audio):
|
||||
assert audio.duration == self.duration
|
||||
@@ -81,6 +85,8 @@ class TestAudio(object):
|
||||
|
||||
assert isinstance(message.audio, Audio)
|
||||
assert isinstance(message.audio.file_id, str)
|
||||
assert isinstance(message.audio.file_unique_id, str)
|
||||
assert message.audio.file_unique_id is not None
|
||||
assert message.audio.file_id is not None
|
||||
assert message.audio.duration == self.duration
|
||||
assert message.audio.performer == self.performer
|
||||
@@ -98,6 +104,7 @@ class TestAudio(object):
|
||||
|
||||
assert new_file.file_size == self.file_size
|
||||
assert new_file.file_id == audio.file_id
|
||||
assert new_file.file_unique_id == audio.file_unique_id
|
||||
assert new_file.file_path.startswith('https://')
|
||||
|
||||
new_file.download('telegram.mp3')
|
||||
@@ -113,6 +120,8 @@ class TestAudio(object):
|
||||
|
||||
assert isinstance(message.audio, Audio)
|
||||
assert isinstance(message.audio.file_id, str)
|
||||
assert isinstance(message.audio.file_unique_id, str)
|
||||
assert message.audio.file_unique_id is not None
|
||||
assert message.audio.file_id is not None
|
||||
assert message.audio.duration == audio.duration
|
||||
assert message.audio.mime_type == audio.mime_type
|
||||
@@ -120,7 +129,6 @@ class TestAudio(object):
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
@pytest.mark.skip(reason='Doesnt work without API 4.5')
|
||||
def test_resend(self, bot, chat_id, audio):
|
||||
message = bot.send_audio(chat_id=chat_id, audio=audio.file_id)
|
||||
|
||||
@@ -168,17 +176,21 @@ class TestAudio(object):
|
||||
assert message.caption_markdown == escape_markdown(test_markdown_string)
|
||||
|
||||
def test_de_json(self, bot, audio):
|
||||
json_dict = {'file_id': 'not a file id',
|
||||
'duration': self.duration,
|
||||
'performer': self.performer,
|
||||
'title': self.title,
|
||||
'caption': self.caption,
|
||||
'mime_type': self.mime_type,
|
||||
'file_size': self.file_size,
|
||||
'thumb': audio.thumb.to_dict()}
|
||||
json_dict = {
|
||||
'file_id': self.audio_file_id,
|
||||
'file_unique_id': self.audio_file_unique_id,
|
||||
'duration': self.duration,
|
||||
'performer': self.performer,
|
||||
'title': self.title,
|
||||
'caption': self.caption,
|
||||
'mime_type': self.mime_type,
|
||||
'file_size': self.file_size,
|
||||
'thumb': audio.thumb.to_dict()
|
||||
}
|
||||
json_audio = Audio.de_json(json_dict, bot)
|
||||
|
||||
assert json_audio.file_id == 'not a file id'
|
||||
assert json_audio.file_id == self.audio_file_id
|
||||
assert json_audio.file_unique_id == self.audio_file_unique_id
|
||||
assert json_audio.duration == self.duration
|
||||
assert json_audio.performer == self.performer
|
||||
assert json_audio.title == self.title
|
||||
@@ -191,6 +203,7 @@ class TestAudio(object):
|
||||
|
||||
assert isinstance(audio_dict, dict)
|
||||
assert audio_dict['file_id'] == audio.file_id
|
||||
assert audio_dict['file_unique_id'] == audio.file_unique_id
|
||||
assert audio_dict['duration'] == audio.duration
|
||||
assert audio_dict['mime_type'] == audio.mime_type
|
||||
assert audio_dict['file_size'] == audio.file_size
|
||||
@@ -221,11 +234,11 @@ class TestAudio(object):
|
||||
assert audio.get_file()
|
||||
|
||||
def test_equality(self, audio):
|
||||
a = Audio(audio.file_id, audio.duration)
|
||||
b = Audio(audio.file_id, audio.duration)
|
||||
c = Audio(audio.file_id, 0)
|
||||
d = Audio('', audio.duration)
|
||||
e = Voice(audio.file_id, audio.duration)
|
||||
a = Audio(audio.file_id, audio.file_unique_id, audio.duration)
|
||||
b = Audio('', audio.file_unique_id, audio.duration)
|
||||
c = Audio(audio.file_id, audio.file_unique_id, 0)
|
||||
d = Audio('', '', audio.duration)
|
||||
e = Voice(audio.file_id, audio.file_unique_id, audio.duration)
|
||||
|
||||
assert a == b
|
||||
assert hash(a) == hash(b)
|
||||
|
||||
+174
-23
@@ -16,8 +16,6 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import datetime as dtm
|
||||
from platform import python_implementation
|
||||
@@ -28,10 +26,11 @@ from future.utils import string_types
|
||||
|
||||
from telegram import (Bot, Update, ChatAction, TelegramError, User, InlineKeyboardMarkup,
|
||||
InlineKeyboardButton, InlineQueryResultArticle, InputTextMessageContent,
|
||||
ShippingOption, LabeledPrice, ChatPermissions, Poll,
|
||||
InlineQueryResultDocument)
|
||||
ShippingOption, LabeledPrice, ChatPermissions, Poll, BotCommand,
|
||||
InlineQueryResultDocument, Dice, MessageEntity, ParseMode)
|
||||
from telegram.error import BadRequest, InvalidToken, NetworkError, RetryAfter
|
||||
from telegram.utils.helpers import from_timestamp, escape_markdown
|
||||
from tests.conftest import expect_bad_request
|
||||
|
||||
BASE_TIME = time.time()
|
||||
HIGHSCORE_DELTA = 1450000000
|
||||
@@ -82,6 +81,7 @@ class TestBot(object):
|
||||
@pytest.mark.timeout(10)
|
||||
def test_get_me_and_properties(self, bot):
|
||||
get_me_bot = bot.get_me()
|
||||
commands = bot.get_my_commands()
|
||||
|
||||
assert isinstance(get_me_bot, User)
|
||||
assert get_me_bot.id == bot.id
|
||||
@@ -89,6 +89,11 @@ class TestBot(object):
|
||||
assert get_me_bot.first_name == bot.first_name
|
||||
assert get_me_bot.last_name == bot.last_name
|
||||
assert get_me_bot.name == bot.name
|
||||
assert get_me_bot.can_join_groups == bot.can_join_groups
|
||||
assert get_me_bot.can_read_all_group_messages == bot.can_read_all_group_messages
|
||||
assert get_me_bot.supports_inline_queries == bot.supports_inline_queries
|
||||
assert 'https://t.me/{}'.format(get_me_bot.username) == bot.link
|
||||
assert commands == bot.commands
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
@@ -172,20 +177,32 @@ class TestBot(object):
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
def test_send_and_stop_poll(self, bot, super_group_id):
|
||||
@pytest.mark.parametrize('reply_markup', [
|
||||
None,
|
||||
InlineKeyboardMarkup.from_button(InlineKeyboardButton(text='text', callback_data='data')),
|
||||
InlineKeyboardMarkup.from_button(
|
||||
InlineKeyboardButton(text='text', callback_data='data')).to_dict()
|
||||
])
|
||||
def test_send_and_stop_poll(self, bot, super_group_id, reply_markup):
|
||||
question = 'Is this a test?'
|
||||
answers = ['Yes', 'No', 'Maybe']
|
||||
message = bot.send_poll(chat_id=super_group_id, question=question, options=answers,
|
||||
timeout=60)
|
||||
is_anonymous=False, allows_multiple_answers=True, timeout=60)
|
||||
|
||||
assert message.poll
|
||||
assert message.poll.question == question
|
||||
assert message.poll.options[0].text == answers[0]
|
||||
assert message.poll.options[1].text == answers[1]
|
||||
assert message.poll.options[2].text == answers[2]
|
||||
assert not message.poll.is_anonymous
|
||||
assert message.poll.allows_multiple_answers
|
||||
assert not message.poll.is_closed
|
||||
assert message.poll.type == Poll.REGULAR
|
||||
|
||||
poll = bot.stop_poll(chat_id=super_group_id, message_id=message.message_id, timeout=60)
|
||||
# Since only the poll and not the complete message is returned, we can't check that the
|
||||
# reply_markup is correct. So we just test that sending doesn't give an error.
|
||||
poll = bot.stop_poll(chat_id=super_group_id, message_id=message.message_id,
|
||||
reply_markup=reply_markup, timeout=60)
|
||||
assert isinstance(poll, Poll)
|
||||
assert poll.is_closed
|
||||
assert poll.options[0].text == answers[0]
|
||||
@@ -195,18 +212,88 @@ class TestBot(object):
|
||||
assert poll.options[2].text == answers[2]
|
||||
assert poll.options[2].voter_count == 0
|
||||
assert poll.question == question
|
||||
assert poll.total_voter_count == 0
|
||||
|
||||
explanation = '[Here is a link](https://google.com)'
|
||||
explanation_entities = [
|
||||
MessageEntity(MessageEntity.TEXT_LINK, 0, 14, url='https://google.com')
|
||||
]
|
||||
message_quiz = bot.send_poll(chat_id=super_group_id, question=question, options=answers,
|
||||
type=Poll.QUIZ, correct_option_id=2, is_closed=True,
|
||||
explanation=explanation,
|
||||
explanation_parse_mode=ParseMode.MARKDOWN_V2)
|
||||
assert message_quiz.poll.correct_option_id == 2
|
||||
assert message_quiz.poll.type == Poll.QUIZ
|
||||
assert message_quiz.poll.is_closed
|
||||
assert message_quiz.poll.explanation == 'Here is a link'
|
||||
assert message_quiz.poll.explanation_entities == explanation_entities
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
def test_send_game(self, bot, chat_id):
|
||||
game_short_name = 'test_game'
|
||||
message = bot.send_game(chat_id, game_short_name)
|
||||
@pytest.mark.parametrize(['open_period', 'close_date'], [(5, None), (None, True)])
|
||||
def test_send_open_period(self, bot, super_group_id, open_period, close_date):
|
||||
question = 'Is this a test?'
|
||||
answers = ['Yes', 'No', 'Maybe']
|
||||
reply_markup = InlineKeyboardMarkup.from_button(
|
||||
InlineKeyboardButton(text='text', callback_data='data'))
|
||||
|
||||
assert message.game
|
||||
assert message.game.description == ('A no-op test game, for python-telegram-bot '
|
||||
'bot framework testing.')
|
||||
assert message.game.animation.file_id != ''
|
||||
assert message.game.photo[0].file_size == 851
|
||||
if close_date:
|
||||
close_date = dtm.datetime.utcnow() + dtm.timedelta(seconds=5)
|
||||
|
||||
message = bot.send_poll(chat_id=super_group_id, question=question, options=answers,
|
||||
is_anonymous=False, allows_multiple_answers=True, timeout=60,
|
||||
open_period=open_period, close_date=close_date)
|
||||
time.sleep(5.1)
|
||||
new_message = bot.edit_message_reply_markup(chat_id=super_group_id,
|
||||
message_id=message.message_id,
|
||||
reply_markup=reply_markup, timeout=60)
|
||||
assert new_message.poll.id == message.poll.id
|
||||
assert new_message.poll.is_closed
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
@pytest.mark.parametrize('default_bot', [{'parse_mode': 'Markdown'}], indirect=True)
|
||||
def test_send_poll_default_parse_mode(self, default_bot, super_group_id):
|
||||
explanation = 'Italic Bold Code'
|
||||
explanation_markdown = '_Italic_ *Bold* `Code`'
|
||||
question = 'Is this a test?'
|
||||
answers = ['Yes', 'No', 'Maybe']
|
||||
|
||||
message = default_bot.send_poll(chat_id=super_group_id, question=question, options=answers,
|
||||
type=Poll.QUIZ, correct_option_id=2, is_closed=True,
|
||||
explanation=explanation_markdown)
|
||||
assert message.poll.explanation == explanation
|
||||
assert message.poll.explanation_entities == [
|
||||
MessageEntity(MessageEntity.ITALIC, 0, 6),
|
||||
MessageEntity(MessageEntity.BOLD, 7, 4),
|
||||
MessageEntity(MessageEntity.CODE, 12, 4)
|
||||
]
|
||||
|
||||
message = default_bot.send_poll(chat_id=super_group_id, question=question, options=answers,
|
||||
type=Poll.QUIZ, correct_option_id=2, is_closed=True,
|
||||
explanation=explanation_markdown,
|
||||
explanation_parse_mode=None)
|
||||
assert message.poll.explanation == explanation_markdown
|
||||
assert message.poll.explanation_entities == []
|
||||
|
||||
message = default_bot.send_poll(chat_id=super_group_id, question=question, options=answers,
|
||||
type=Poll.QUIZ, correct_option_id=2, is_closed=True,
|
||||
explanation=explanation_markdown,
|
||||
explanation_parse_mode='HTML')
|
||||
assert message.poll.explanation == explanation_markdown
|
||||
assert message.poll.explanation_entities == []
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
@pytest.mark.parametrize('emoji', Dice.ALL_EMOJI + [None])
|
||||
def test_send_dice(self, bot, chat_id, emoji):
|
||||
message = bot.send_dice(chat_id, emoji=emoji)
|
||||
|
||||
assert message.dice
|
||||
if emoji is None:
|
||||
assert message.dice.emoji == Dice.DICE
|
||||
else:
|
||||
assert message.dice.emoji == emoji
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
@@ -350,6 +437,16 @@ class TestBot(object):
|
||||
|
||||
assert bot.set_chat_permissions(2, chat_permissions)
|
||||
|
||||
def test_set_chat_administrator_custom_title(self, monkeypatch, bot):
|
||||
def test(_, url, data, *args, **kwargs):
|
||||
chat_id = data['chat_id'] == 2
|
||||
user_id = data['user_id'] == 32
|
||||
custom_title = data['custom_title'] == 'custom_title'
|
||||
return chat_id and user_id and custom_title
|
||||
|
||||
monkeypatch.setattr('telegram.utils.request.Request.post', test)
|
||||
assert bot.set_chat_administrator_custom_title(2, 32, 'custom_title')
|
||||
|
||||
# TODO: Needs improvement. Need an incoming callbackquery to test
|
||||
def test_answer_callback_query(self, monkeypatch, bot):
|
||||
# For now just test that our internals pass the correct data
|
||||
@@ -493,8 +590,6 @@ class TestBot(object):
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.timeout(15)
|
||||
@pytest.mark.xfail
|
||||
@pytest.mark.skipif(os.getenv('APPVEYOR') and (sys.version_info < (3, 6)),
|
||||
reason='only run on 3.6 on appveyor')
|
||||
def test_set_webhook_get_webhook_info_and_delete_webhook(self, bot):
|
||||
url = 'https://python-telegram-bot.org/test/webhook'
|
||||
max_connections = 7
|
||||
@@ -526,7 +621,7 @@ class TestBot(object):
|
||||
chat = bot.get_chat(super_group_id)
|
||||
|
||||
assert chat.type == 'supergroup'
|
||||
assert chat.title == '>>> telegram.Bot(test)'
|
||||
assert chat.title == '>>> telegram.Bot(test) @{}'.format(bot.username)
|
||||
assert chat.id == int(super_group_id)
|
||||
|
||||
# TODO: Add bot to group to test there too
|
||||
@@ -576,6 +671,18 @@ class TestBot(object):
|
||||
def test_delete_chat_sticker_set(self):
|
||||
pass
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
def test_send_game(self, bot, chat_id):
|
||||
game_short_name = 'test_game'
|
||||
message = bot.send_game(chat_id, game_short_name)
|
||||
|
||||
assert message.game
|
||||
assert message.game.description == ('A no-op test game, for python-telegram-bot '
|
||||
'bot framework testing.')
|
||||
assert message.game.animation.file_id != ''
|
||||
assert message.game.photo[0].file_size == 851
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
def test_set_game_score_1(self, bot, chat_id):
|
||||
@@ -777,14 +884,20 @@ class TestBot(object):
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
def test_delete_chat_photo(self, bot, channel_id):
|
||||
assert bot.delete_chat_photo(channel_id)
|
||||
def test_set_chat_photo(self, bot, channel_id):
|
||||
def func():
|
||||
assert bot.set_chat_photo(channel_id, f)
|
||||
|
||||
with open('tests/data/telegram_test_channel.jpg', 'rb') as f:
|
||||
expect_bad_request(func, 'Type of file mismatch', 'Telegram did not accept the file.')
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
def test_set_chat_photo(self, bot, channel_id):
|
||||
with open('tests/data/telegram_test_channel.jpg', 'rb') as f:
|
||||
assert bot.set_chat_photo(channel_id, f)
|
||||
def test_delete_chat_photo(self, bot, channel_id):
|
||||
def func():
|
||||
assert bot.delete_chat_photo(channel_id)
|
||||
|
||||
expect_bad_request(func, 'Chat_not_modified', 'Chat photo was not set.')
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
@@ -884,3 +997,41 @@ class TestBot(object):
|
||||
def test_send_message_default_quote(self, default_bot, chat_id):
|
||||
message = default_bot.send_message(chat_id, 'test')
|
||||
assert message.default_quote is True
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
def test_set_and_get_my_commands(self, bot):
|
||||
commands = [
|
||||
BotCommand('cmd1', 'descr1'),
|
||||
BotCommand('cmd2', 'descr2'),
|
||||
]
|
||||
bot.set_my_commands([])
|
||||
assert bot.get_my_commands() == []
|
||||
assert bot.commands == []
|
||||
assert bot.set_my_commands(commands)
|
||||
|
||||
for bc in [bot.get_my_commands(), bot.commands]:
|
||||
assert len(bc) == 2
|
||||
assert bc[0].command == 'cmd1'
|
||||
assert bc[0].description == 'descr1'
|
||||
assert bc[1].command == 'cmd2'
|
||||
assert bc[1].description == 'descr2'
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
def test_set_and_get_my_commands_strings(self, bot):
|
||||
commands = [
|
||||
['cmd1', 'descr1'],
|
||||
['cmd2', 'descr2'],
|
||||
]
|
||||
bot.set_my_commands([])
|
||||
assert bot.get_my_commands() == []
|
||||
assert bot.commands == []
|
||||
assert bot.set_my_commands(commands)
|
||||
|
||||
for bc in [bot.get_my_commands(), bot.commands]:
|
||||
assert len(bc) == 2
|
||||
assert bc[0].command == 'cmd1'
|
||||
assert bc[0].description == 'descr1'
|
||||
assert bc[1].command == 'cmd2'
|
||||
assert bc[1].description == 'descr2'
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# 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/].
|
||||
|
||||
import pytest
|
||||
|
||||
from telegram import BotCommand
|
||||
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def bot_command():
|
||||
return BotCommand(command='start', description='A command')
|
||||
|
||||
|
||||
class TestBotCommand(object):
|
||||
command = 'start'
|
||||
description = 'A command'
|
||||
|
||||
def test_de_json(self, bot):
|
||||
json_dict = {'command': self.command, 'description': self.description}
|
||||
bot_command = BotCommand.de_json(json_dict, bot)
|
||||
|
||||
assert bot_command.command == self.command
|
||||
assert bot_command.description == self.description
|
||||
|
||||
assert BotCommand.de_json(None, bot) is None
|
||||
|
||||
def test_to_dict(self, bot_command):
|
||||
bot_command_dict = bot_command.to_dict()
|
||||
|
||||
assert isinstance(bot_command_dict, dict)
|
||||
assert bot_command_dict['command'] == bot_command.command
|
||||
assert bot_command_dict['description'] == bot_command.description
|
||||
+18
-18
@@ -24,7 +24,7 @@ from telegram import CallbackQuery, User, Message, Chat, Audio
|
||||
|
||||
@pytest.fixture(scope='class', params=['message', 'inline'])
|
||||
def callback_query(bot, request):
|
||||
cbq = CallbackQuery(TestCallbackQuery.id,
|
||||
cbq = CallbackQuery(TestCallbackQuery.id_,
|
||||
TestCallbackQuery.from_user,
|
||||
TestCallbackQuery.chat_instance,
|
||||
data=TestCallbackQuery.data,
|
||||
@@ -38,7 +38,7 @@ def callback_query(bot, request):
|
||||
|
||||
|
||||
class TestCallbackQuery(object):
|
||||
id = 'id'
|
||||
id_ = 'id'
|
||||
from_user = User(1, 'test_user', False)
|
||||
chat_instance = 'chat_instance'
|
||||
message = Message(3, User(5, 'bot', False), None, Chat(4, 'private'))
|
||||
@@ -47,7 +47,7 @@ class TestCallbackQuery(object):
|
||||
game_short_name = 'the_game'
|
||||
|
||||
def test_de_json(self, bot):
|
||||
json_dict = {'id': self.id,
|
||||
json_dict = {'id': self.id_,
|
||||
'from': self.from_user.to_dict(),
|
||||
'chat_instance': self.chat_instance,
|
||||
'message': self.message.to_dict(),
|
||||
@@ -57,7 +57,7 @@ class TestCallbackQuery(object):
|
||||
'default_quote': True}
|
||||
callback_query = CallbackQuery.de_json(json_dict, bot)
|
||||
|
||||
assert callback_query.id == self.id
|
||||
assert callback_query.id == self.id_
|
||||
assert callback_query.from_user == self.from_user
|
||||
assert callback_query.chat_instance == self.chat_instance
|
||||
assert callback_query.message == self.message
|
||||
@@ -92,8 +92,8 @@ class TestCallbackQuery(object):
|
||||
def test(*args, **kwargs):
|
||||
text = args[0] == 'test'
|
||||
try:
|
||||
id = kwargs['inline_message_id'] == callback_query.inline_message_id
|
||||
return id and text
|
||||
id_ = kwargs['inline_message_id'] == callback_query.inline_message_id
|
||||
return id_ and text
|
||||
except KeyError:
|
||||
chat_id = kwargs['chat_id'] == callback_query.message.chat_id
|
||||
message_id = kwargs['message_id'] == callback_query.message.message_id
|
||||
@@ -107,12 +107,12 @@ class TestCallbackQuery(object):
|
||||
def test(*args, **kwargs):
|
||||
caption = kwargs['caption'] == 'new caption'
|
||||
try:
|
||||
id = kwargs['inline_message_id'] == callback_query.inline_message_id
|
||||
return id and caption
|
||||
id_ = kwargs['inline_message_id'] == callback_query.inline_message_id
|
||||
return id_ and caption
|
||||
except KeyError:
|
||||
id = kwargs['chat_id'] == callback_query.message.chat_id
|
||||
id_ = kwargs['chat_id'] == callback_query.message.chat_id
|
||||
message = kwargs['message_id'] == callback_query.message.message_id
|
||||
return id and message and caption
|
||||
return id_ and message and caption
|
||||
|
||||
monkeypatch.setattr(callback_query.bot, 'edit_message_caption', test)
|
||||
assert callback_query.edit_message_caption(caption='new caption')
|
||||
@@ -122,23 +122,23 @@ class TestCallbackQuery(object):
|
||||
def test(*args, **kwargs):
|
||||
reply_markup = kwargs['reply_markup'] == [['1', '2']]
|
||||
try:
|
||||
id = kwargs['inline_message_id'] == callback_query.inline_message_id
|
||||
return id and reply_markup
|
||||
id_ = kwargs['inline_message_id'] == callback_query.inline_message_id
|
||||
return id_ and reply_markup
|
||||
except KeyError:
|
||||
id = kwargs['chat_id'] == callback_query.message.chat_id
|
||||
id_ = kwargs['chat_id'] == callback_query.message.chat_id
|
||||
message = kwargs['message_id'] == callback_query.message.message_id
|
||||
return id and message and reply_markup
|
||||
return id_ and message and reply_markup
|
||||
|
||||
monkeypatch.setattr(callback_query.bot, 'edit_message_reply_markup', test)
|
||||
assert callback_query.edit_message_reply_markup(reply_markup=[['1', '2']])
|
||||
assert callback_query.edit_message_reply_markup([['1', '2']])
|
||||
|
||||
def test_equality(self):
|
||||
a = CallbackQuery(self.id, self.from_user, 'chat')
|
||||
b = CallbackQuery(self.id, self.from_user, 'chat')
|
||||
c = CallbackQuery(self.id, None, '')
|
||||
a = CallbackQuery(self.id_, self.from_user, 'chat')
|
||||
b = CallbackQuery(self.id_, self.from_user, 'chat')
|
||||
c = CallbackQuery(self.id_, None, '')
|
||||
d = CallbackQuery('', None, 'chat')
|
||||
e = Audio(self.id, 1)
|
||||
e = Audio(self.id_, 'unique_id', 1)
|
||||
|
||||
assert a == b
|
||||
assert hash(a) == hash(b)
|
||||
|
||||
+33
-18
@@ -25,17 +25,18 @@ from telegram import User, Message
|
||||
|
||||
@pytest.fixture(scope='class')
|
||||
def chat(bot):
|
||||
return Chat(TestChat.id, TestChat.title, TestChat.type, username=TestChat.username,
|
||||
return Chat(TestChat.id_, TestChat.title, TestChat.type_, username=TestChat.username,
|
||||
all_members_are_administrators=TestChat.all_members_are_administrators,
|
||||
bot=bot, sticker_set_name=TestChat.sticker_set_name,
|
||||
can_set_sticker_set=TestChat.can_set_sticker_set,
|
||||
permissions=TestChat.permissions)
|
||||
permissions=TestChat.permissions,
|
||||
slow_mode_delay=TestChat.slow_mode_delay)
|
||||
|
||||
|
||||
class TestChat(object):
|
||||
id = -28767330
|
||||
id_ = -28767330
|
||||
title = 'ToledosPalaceBot - Group'
|
||||
type = 'group'
|
||||
type_ = 'group'
|
||||
username = 'username'
|
||||
all_members_are_administrators = False
|
||||
sticker_set_name = 'stickers'
|
||||
@@ -45,33 +46,36 @@ class TestChat(object):
|
||||
can_change_info=False,
|
||||
can_invite_users=True,
|
||||
)
|
||||
slow_mode_delay = 30
|
||||
|
||||
def test_de_json(self, bot):
|
||||
json_dict = {
|
||||
'id': self.id,
|
||||
'id': self.id_,
|
||||
'title': self.title,
|
||||
'type': self.type,
|
||||
'type': self.type_,
|
||||
'username': self.username,
|
||||
'all_members_are_administrators': self.all_members_are_administrators,
|
||||
'sticker_set_name': self.sticker_set_name,
|
||||
'can_set_sticker_set': self.can_set_sticker_set,
|
||||
'permissions': self.permissions.to_dict()
|
||||
'permissions': self.permissions.to_dict(),
|
||||
'slow_mode_delay': self.slow_mode_delay
|
||||
}
|
||||
chat = Chat.de_json(json_dict, bot)
|
||||
|
||||
assert chat.id == self.id
|
||||
assert chat.id == self.id_
|
||||
assert chat.title == self.title
|
||||
assert chat.type == self.type
|
||||
assert chat.type == self.type_
|
||||
assert chat.username == self.username
|
||||
assert chat.all_members_are_administrators == self.all_members_are_administrators
|
||||
assert chat.sticker_set_name == self.sticker_set_name
|
||||
assert chat.can_set_sticker_set == self.can_set_sticker_set
|
||||
assert chat.permissions == self.permissions
|
||||
assert chat.slow_mode_delay == self.slow_mode_delay
|
||||
|
||||
def test_de_json_default_quote(self, bot):
|
||||
json_dict = {
|
||||
'id': self.id,
|
||||
'type': self.type,
|
||||
'id': self.id_,
|
||||
'type': self.type_,
|
||||
'pinned_message': Message(
|
||||
message_id=123,
|
||||
from_user=None,
|
||||
@@ -94,6 +98,7 @@ class TestChat(object):
|
||||
assert chat_dict['username'] == chat.username
|
||||
assert chat_dict['all_members_are_administrators'] == chat.all_members_are_administrators
|
||||
assert chat_dict['permissions'] == chat.permissions.to_dict()
|
||||
assert chat_dict['slow_mode_delay'] == chat.slow_mode_delay
|
||||
|
||||
def test_link(self, chat):
|
||||
assert chat.link == 'https://t.me/{}'.format(chat.username)
|
||||
@@ -102,9 +107,9 @@ class TestChat(object):
|
||||
|
||||
def test_send_action(self, monkeypatch, chat):
|
||||
def test(*args, **kwargs):
|
||||
id = args[0] == chat.id
|
||||
id_ = args[0] == chat.id
|
||||
action = kwargs['action'] == ChatAction.TYPING
|
||||
return id and action
|
||||
return id_ and action
|
||||
|
||||
monkeypatch.setattr(chat.bot, 'send_chat_action', test)
|
||||
assert chat.send_action(action=ChatAction.TYPING)
|
||||
@@ -167,6 +172,16 @@ class TestChat(object):
|
||||
monkeypatch.setattr(chat.bot, 'set_chat_permissions', test)
|
||||
assert chat.set_permissions(self.permissions)
|
||||
|
||||
def test_set_administrator_custom_title(self, monkeypatch, chat):
|
||||
def test(*args, **kwargs):
|
||||
chat_id = args[1] == chat.id
|
||||
user_id = args[2] == 42
|
||||
custom_title = args[3] == 'custom_title'
|
||||
return chat_id and user_id and custom_title
|
||||
|
||||
monkeypatch.setattr('telegram.Bot.set_chat_administrator_custom_title', test)
|
||||
assert chat.set_administrator_custom_title(42, 'custom_title')
|
||||
|
||||
def test_instance_method_send_message(self, monkeypatch, chat):
|
||||
def test(*args, **kwargs):
|
||||
return args[0] == chat.id and args[1] == 'test'
|
||||
@@ -238,11 +253,11 @@ class TestChat(object):
|
||||
assert chat.send_poll('test_poll')
|
||||
|
||||
def test_equality(self):
|
||||
a = Chat(self.id, self.title, self.type)
|
||||
b = Chat(self.id, self.title, self.type)
|
||||
c = Chat(self.id, '', '')
|
||||
d = Chat(0, self.title, self.type)
|
||||
e = User(self.id, '', False)
|
||||
a = Chat(self.id_, self.title, self.type_)
|
||||
b = Chat(self.id_, self.title, self.type_)
|
||||
c = Chat(self.id_, '', '')
|
||||
d = Chat(0, self.title, self.type_)
|
||||
e = User(self.id_, '', False)
|
||||
|
||||
assert a == b
|
||||
assert hash(a) == hash(b)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user