mirror of
https://github.com/python-telegram-bot/python-telegram-bot.git
synced 2026-06-20 16:15:28 +00:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 51a4a6664c | |||
| e4dc80f41d | |||
| bc7c422a11 | |||
| c3e3bb77e5 | |||
| a25c76e6a3 | |||
| 0c5085022c | |||
| 1fdaaac809 | |||
| bcec6f03cb | |||
| ed147813ab | |||
| 4315225642 | |||
| a75dffd4a8 | |||
| fce2993d21 | |||
| 9aec8deec6 | |||
| ec3026673b | |||
| 105f1ccdb5 | |||
| 52ce03929b | |||
| ac4768155f | |||
| d08172b4b0 | |||
| e982a5a567 | |||
| cf4d3cae01 | |||
| 8531a7a40c | |||
| fce7cc903c | |||
| 5da1dd7ce9 | |||
| 46cdeb495a | |||
| 653691fafb | |||
| 92ff6a8e2b | |||
| cc43aef64b | |||
| e2c6d60721 | |||
| 8bf88c3231 | |||
| 1572c61063 | |||
| 5ff3b76e18 | |||
| cd69f69b28 | |||
| 8b0d2e5f75 | |||
| 7d0fb85c8c | |||
| 08ba7c7793 | |||
| 9737b1d3c7 |
@@ -0,0 +1,20 @@
|
||||
version = 1
|
||||
|
||||
test_patterns = ["tests/**"]
|
||||
|
||||
exclude_patterns = [
|
||||
"tests/**",
|
||||
"docs/**",
|
||||
"telegram/vendor/**",
|
||||
"setup.py",
|
||||
"setup-raw.py"
|
||||
]
|
||||
|
||||
[[analyzers]]
|
||||
name = "python"
|
||||
enabled = true
|
||||
|
||||
[analyzers.meta]
|
||||
runtime_version = "3.x.x"
|
||||
max_line_length = 99
|
||||
skip_doc_coverage = ["module", "magic", "init", "nonpublic"]
|
||||
@@ -0,0 +1,76 @@
|
||||
name: Bug Report
|
||||
description: Create a report to help us improve
|
||||
title: "[BUG]"
|
||||
labels: ["bug :bug:"]
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for reporting issues of python-telegram-bot!
|
||||
|
||||
Use this template to notify us if you found a bug.
|
||||
|
||||
To make it easier for us to help you please enter detailed information below.
|
||||
|
||||
Please note, we only support the latest version of python-telegram-bot and master branch. Please make sure to upgrade & recreate the issue on the latest version prior to opening an issue.
|
||||
|
||||
- type: textarea
|
||||
id: steps-to-reproduce
|
||||
attributes:
|
||||
label: Steps to Reproduce
|
||||
value: |
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: expected-behaviour
|
||||
attributes:
|
||||
label: Expected behaviour
|
||||
description: Tell us what should happen
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: actual-behaviour
|
||||
attributes:
|
||||
label: Actual behaviour
|
||||
description: Tell us what happens instead
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "### Configuration"
|
||||
|
||||
- type: input
|
||||
id: operating-system
|
||||
attributes:
|
||||
label: Operating System
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: versions
|
||||
attributes:
|
||||
label: Version of Python, python-telegram-bot & dependencies
|
||||
description: Paste the output of `$ python -m telegram` here. This will be automatically formatted into code, so no need for backticks.
|
||||
render: shell
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Relevant log output
|
||||
description: Insert logs here (if necessary). This will be automatically formatted into code, so no need for backticks.
|
||||
render: python
|
||||
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
attributes:
|
||||
label: Additional Context
|
||||
description: You may provide any other additional context to the bug here.
|
||||
@@ -1,43 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: "[BUG]"
|
||||
labels: 'bug :bug:'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
Thanks for reporting issues of python-telegram-bot!
|
||||
|
||||
Use this template to notify us if you found a bug.
|
||||
|
||||
To make it easier for us to help you please enter detailed information below.
|
||||
|
||||
Please note, we only support the latest version of python-telegram-bot and
|
||||
master branch. Please make sure to upgrade & recreate the issue on the latest
|
||||
version prior to opening an issue.
|
||||
-->
|
||||
### Steps to reproduce
|
||||
1.
|
||||
|
||||
2.
|
||||
|
||||
3.
|
||||
|
||||
### Expected behaviour
|
||||
Tell us what should happen
|
||||
|
||||
### Actual behaviour
|
||||
Tell us what happens instead
|
||||
|
||||
### Configuration
|
||||
**Operating System:**
|
||||
|
||||
|
||||
**Version of Python, python-telegram-bot & dependencies:**
|
||||
|
||||
``$ python -m telegram``
|
||||
|
||||
### Logs
|
||||
Insert logs here (if necessary)
|
||||
@@ -3,6 +3,6 @@ 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
|
||||
- name: GitHub Discussions
|
||||
url: https://github.com/python-telegram-bot/python-telegram-bot/discussions
|
||||
about: For getting answers to usage on GitHub, Discussions is even better than this bug tracker :)
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
name: Feature Request
|
||||
description: Suggest an idea for this project
|
||||
title: "[FEATURE]"
|
||||
labels: ["enhancement"]
|
||||
|
||||
body:
|
||||
- type: textarea
|
||||
id: related-problem
|
||||
attributes:
|
||||
label: "What kind of feature are you missing? Where do you notice a shortcoming of PTB?"
|
||||
description: "A clear and concise description of what the problem is."
|
||||
placeholder: "Example: I want to do X, but there is no way to do it."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: solution
|
||||
attributes:
|
||||
label: "Describe the solution you'd like"
|
||||
description: "A clear and concise description of what you want to happen."
|
||||
placeholder: "Example: I think it would be nice if you would add feature Y so I can do X."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
attributes:
|
||||
label: "Describe alternatives you've considered"
|
||||
description: "A clear and concise description of any alternative solutions or features you've considered."
|
||||
placeholder: "Example: I considered Z to be able to do X, but that didn't work because..."
|
||||
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
attributes:
|
||||
label: "Additional context"
|
||||
description: "Add any other context or screenshots about the feature request here."
|
||||
placeholder: "Example: Here's a photo of my cat!"
|
||||
@@ -1,24 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: "[FEATURE]"
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
#### Is your feature request related to a problem? Please describe.
|
||||
A clear and concise description of what the problem is.
|
||||
Ex. *I want to do X, but there is no way to do it.*
|
||||
|
||||
#### Describe the solution you'd like
|
||||
A clear and concise description of what you want to happen.
|
||||
Ex. *I think it would be nice if you would add feature Y so it will make it easier.*
|
||||
|
||||
#### Describe alternatives you've considered
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
Ex. *I considered Z, but that didn't work because...*
|
||||
|
||||
#### Additional context
|
||||
Add any other context or screenshots about the feature request here.
|
||||
Ex. *Here's a photo of my cat!*
|
||||
@@ -1,29 +0,0 @@
|
||||
---
|
||||
name: Question
|
||||
about: Get help with errors or general questions
|
||||
title: "[QUESTION]"
|
||||
labels: question
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
Hey there, you have a question? We are happy to answer. Please make sure no similar question was opened already.
|
||||
|
||||
To make it easier for us to help you, please read this article https://git.io/JURJO and try to follow the template below as closely as possible.
|
||||
|
||||
Please mind that there is also a users' Telegram group at https://t.me/pythontelegrambotgroup for questions about the library. Questions asked there might be answered quicker than here. In case you are unable to join our group due to Telegram restrictions, you can use our IRC channel at https://webchat.freenode.net/?channels=##python-telegram-bot to participate in the group.
|
||||
-->
|
||||
|
||||
### Issue I am facing
|
||||
Please describe the issue here in as much detail as possible
|
||||
|
||||
### Traceback to the issue
|
||||
```
|
||||
put it here
|
||||
```
|
||||
|
||||
### Related part of your code
|
||||
```python
|
||||
put it here
|
||||
```
|
||||
@@ -0,0 +1,68 @@
|
||||
name: Question
|
||||
description: Get help with errors or general questions
|
||||
title: "[QUESTION]"
|
||||
labels: ["question"]
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Hey there, you have a question? We are happy to answer. Please make sure no similar question was opened already.
|
||||
|
||||
To make it easier for us to help you, please read this [article](https://git.io/JURJO).
|
||||
|
||||
Please mind that there is also a users' [Telegram group](https://t.me/pythontelegrambotgroup) for questions about the library. Questions asked there might be answered quicker than here. Moreover, [GitHub Discussions](https://git.io/JG3rk) offer a slightly better format to discuss usage questions.
|
||||
|
||||
- type: textarea
|
||||
id: issue-faced
|
||||
attributes:
|
||||
label: "Issue I am facing"
|
||||
description: "Please describe the issue here in as much detail as possible"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: traceback
|
||||
attributes:
|
||||
label: "Traceback to the issue"
|
||||
description: "If you are facing a specific error message, please paste the traceback here. This will be automatically formatted into python code, so no need for backticks."
|
||||
placeholder: |
|
||||
Traceback (most recent call last):
|
||||
File "/home/bot.py", line 1, in main
|
||||
foo = bar()
|
||||
...
|
||||
telegram.error.BadRequest: Traceback not found
|
||||
render: python
|
||||
|
||||
- type: textarea
|
||||
id: related-code
|
||||
attributes:
|
||||
label: "Related part of your code"
|
||||
description: "This will be automatically formatted into code (python), so no need for backticks."
|
||||
placeholder: |
|
||||
logging.basicConfig(
|
||||
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
render: python
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "### Configuration"
|
||||
|
||||
- type: input
|
||||
id: operating-system
|
||||
attributes:
|
||||
label: Operating System
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: versions
|
||||
attributes:
|
||||
label: Version of Python, python-telegram-bot & dependencies
|
||||
description: Paste the output of `$ python -m telegram` here. This will be automatically formatted into code, so no need for backticks.
|
||||
render: shell
|
||||
validations:
|
||||
required: true
|
||||
@@ -28,3 +28,4 @@ Hey! You're PRing? Cool! Please have a look at the below checklist. It's here to
|
||||
- [ ] Added new filters for new message (sub)types
|
||||
- [ ] Added or updated documentation for the changed class(es) and/or method(s)
|
||||
- [ ] Updated the Bot API version number in all places: `README.rst` and `README_RAW.rst` (including the badge), as well as `telegram.constants.BOT_API_VERSION`
|
||||
- [ ] Added logic for arbitrary callback data in `tg.ext.Bot` for new methods that either accept a `reply_markup` in some form or have a return type that is/contains `telegram.Message`
|
||||
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
shell: bash --noprofile --norc {0}
|
||||
|
||||
- name: Submit coverage
|
||||
uses: codecov/codecov-action@v1.0.13
|
||||
uses: codecov/codecov-action@v1
|
||||
with:
|
||||
env_vars: OS,PYTHON
|
||||
name: ${{ matrix.os }}-${{ matrix.python-version }}
|
||||
@@ -79,7 +79,7 @@ jobs:
|
||||
run:
|
||||
git submodule update --init --recursive
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v1
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install dependencies
|
||||
@@ -108,7 +108,7 @@ jobs:
|
||||
run:
|
||||
git submodule update --init --recursive
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v1
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install dependencies
|
||||
|
||||
@@ -10,11 +10,11 @@ repos:
|
||||
- --diff
|
||||
- --check
|
||||
- repo: https://gitlab.com/pycqa/flake8
|
||||
rev: 3.9.1
|
||||
rev: 3.9.2
|
||||
hooks:
|
||||
- id: flake8
|
||||
- repo: https://github.com/PyCQA/pylint
|
||||
rev: v2.8.2
|
||||
rev: v2.8.3
|
||||
hooks:
|
||||
- id: pylint
|
||||
files: ^(telegram|examples)/.*\.py$
|
||||
@@ -22,8 +22,9 @@ repos:
|
||||
- --rcfile=setup.cfg
|
||||
additional_dependencies:
|
||||
- certifi
|
||||
- tornado>=5.1
|
||||
- tornado>=6.1
|
||||
- APScheduler==3.6.3
|
||||
- cachetools==4.2.2
|
||||
- . # this basically does `pip install -e .`
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: v0.812
|
||||
@@ -33,8 +34,9 @@ repos:
|
||||
files: ^telegram/.*\.py$
|
||||
additional_dependencies:
|
||||
- certifi
|
||||
- tornado>=5.1
|
||||
- tornado>=6.1
|
||||
- APScheduler==3.6.3
|
||||
- cachetools==4.2.2
|
||||
- . # this basically does `pip install -e .`
|
||||
- id: mypy
|
||||
name: mypy-examples
|
||||
@@ -44,11 +46,12 @@ repos:
|
||||
- --follow-imports=silent
|
||||
additional_dependencies:
|
||||
- certifi
|
||||
- tornado>=5.1
|
||||
- tornado>=6.1
|
||||
- APScheduler==3.6.3
|
||||
- cachetools==4.2.2
|
||||
- . # this basically does `pip install -e .`
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v2.13.0
|
||||
rev: v2.19.1
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
files: ^(telegram|examples|tests)/.*\.py$
|
||||
|
||||
@@ -26,6 +26,7 @@ Contributors
|
||||
|
||||
The following wonderful people contributed directly or indirectly to this project:
|
||||
|
||||
- `Abshar <https://github.com/abxhr>`_
|
||||
- `Alateas <https://github.com/alateas>`_
|
||||
- `Ales Dokshanin <https://github.com/alesdokshanin>`_
|
||||
- `Ambro17 <https://github.com/Ambro17>`_
|
||||
@@ -39,6 +40,7 @@ The following wonderful people contributed directly or indirectly to this projec
|
||||
- `daimajia <https://github.com/daimajia>`_
|
||||
- `Daniel Reed <https://github.com/nmlorg>`_
|
||||
- `D David Livingston <https://github.com/daviddl9>`_
|
||||
- `DonalDuck004 <https://github.com/DonalDuck004>`_
|
||||
- `Eana Hufwe <https://github.com/blueset>`_
|
||||
- `Ehsan Online <https://github.com/ehsanonline>`_
|
||||
- `Eli Gao <https://github.com/eligao>`_
|
||||
@@ -77,12 +79,14 @@ The following wonderful people contributed directly or indirectly to this projec
|
||||
- `naveenvhegde <https://github.com/naveenvhegde>`_
|
||||
- `neurrone <https://github.com/neurrone>`_
|
||||
- `NikitaPirate <https://github.com/NikitaPirate>`_
|
||||
- `Nikolai Krivenko <https://github.com/nkrivenko>`_
|
||||
- `njittam <https://github.com/njittam>`_
|
||||
- `Noam Meltzer <https://github.com/tsnoam>`_
|
||||
- `Oleg Shlyazhko <https://github.com/ollmer>`_
|
||||
- `Oleg Sushchenko <https://github.com/feuillemorte>`_
|
||||
- `Or Bin <https://github.com/OrBin>`_
|
||||
- `overquota <https://github.com/overquota>`_
|
||||
- `Paradox <https://github.com/paradox70>`_
|
||||
- `Patrick Hofmann <https://github.com/PH89>`_
|
||||
- `Paul Larsen <https://github.com/PaulSonOfLars>`_
|
||||
- `Pieter Schutz <https://github.com/eldinnie>`_
|
||||
@@ -105,6 +109,7 @@ The following wonderful people contributed directly or indirectly to this projec
|
||||
- `Vorobjev Simon <https://github.com/simonvorobjev>`_
|
||||
- `Wagner Macedo <https://github.com/wagnerluis1982>`_
|
||||
- `wjt <https://github.com/wjt>`_
|
||||
- `zeroone2numeral2 <https://github.com/zeroone2numeral2>`_
|
||||
- `zeshuaro <https://github.com/zeshuaro>`_
|
||||
|
||||
Please add yourself here alphabetically when you submit your first pull request.
|
||||
|
||||
+108
@@ -2,6 +2,114 @@
|
||||
Changelog
|
||||
=========
|
||||
|
||||
Version 13.8
|
||||
============
|
||||
*Released 2021-11-08*
|
||||
|
||||
This is the technical changelog for version 13.8. More elaborate release notes can be found in the news channel `@pythontelegrambotchannel <https://t.me/pythontelegrambotchannel>`_.
|
||||
|
||||
**Major Changes:**
|
||||
|
||||
- Full support for API 5.4 (`#2767`_)
|
||||
|
||||
**Minor changes, CI improvements, Doc fixes and Type hinting:**
|
||||
|
||||
- Create Issue Template Forms (`#2689`_)
|
||||
- Fix ``camelCase`` Functions in ``ExtBot`` (`#2659`_)
|
||||
- Fix Empty Captions not Being Passed by ``Bot.copy_message`` (`#2651`_)
|
||||
- Fix Setting Thumbs When Uploading A Single File (`#2583`_)
|
||||
- Fix Bug in ``BasePersistence.insert``/``replace_bot`` for Objects with ``__dict__`` not in ``__slots__`` (`#2603`_)
|
||||
|
||||
.. _`#2767`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2767
|
||||
.. _`#2689`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2689
|
||||
.. _`#2659`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2659
|
||||
.. _`#2651`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2651
|
||||
.. _`#2583`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2583
|
||||
.. _`#2603`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2603
|
||||
|
||||
Version 13.7
|
||||
============
|
||||
*Released 2021-07-01*
|
||||
|
||||
This is the technical changelog for version 13.7. More elaborate release notes can be found in the news channel `@pythontelegrambotchannel <https://t.me/pythontelegrambotchannel>`_.
|
||||
|
||||
**Major Changes:**
|
||||
|
||||
- Full support for Bot API 5.3 (`#2572`_)
|
||||
|
||||
**Bug Fixes:**
|
||||
|
||||
- Fix Bug in ``BasePersistence.insert/replace_bot`` for Objects with ``__dict__`` in their slots (`#2561`_)
|
||||
- Remove Incorrect Warning About ``Defaults`` and ``ExtBot`` (`#2553`_)
|
||||
|
||||
**Minor changes, CI improvements, Doc fixes and Type hinting:**
|
||||
|
||||
- Type Hinting Fixes (`#2552`_)
|
||||
- Doc Fixes (`#2551`_)
|
||||
- Improve Deprecation Warning for ``__slots__`` (`#2574`_)
|
||||
- Stabilize CI (`#2575`_)
|
||||
- Fix Coverage Configuration (`#2571`_)
|
||||
- Better Exception-Handling for ``BasePersistence.replace/insert_bot`` (`#2564`_)
|
||||
- Remove Deprecated ``pass_args`` from Deeplinking Example (`#2550`_)
|
||||
|
||||
.. _`#2572`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2572
|
||||
.. _`#2561`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2561
|
||||
.. _`#2553`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2553
|
||||
.. _`#2552`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2552
|
||||
.. _`#2551`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2551
|
||||
.. _`#2574`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2574
|
||||
.. _`#2575`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2575
|
||||
.. _`#2571`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2571
|
||||
.. _`#2564`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2564
|
||||
.. _`#2550`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2550
|
||||
|
||||
Version 13.6
|
||||
============
|
||||
*Released 2021-06-06*
|
||||
|
||||
New Features:
|
||||
|
||||
- Arbitrary ``callback_data`` (`#1844`_)
|
||||
- Add ``ContextTypes`` & ``BasePersistence.refresh_user/chat/bot_data`` (`#2262`_)
|
||||
- Add ``Filters.attachment`` (`#2528`_)
|
||||
- Add ``pattern`` Argument to ``ChosenInlineResultHandler`` (`#2517`_)
|
||||
|
||||
Major Changes:
|
||||
|
||||
- Add ``slots`` (`#2345`_)
|
||||
|
||||
Minor changes, CI improvements, Doc fixes and Type hinting:
|
||||
|
||||
- Doc Fixes (`#2495`_, `#2510`_)
|
||||
- Add ``max_connections`` Parameter to ``Updater.start_webhook`` (`#2547`_)
|
||||
- Fix for ``Promise.done_callback`` (`#2544`_)
|
||||
- Improve Code Quality (`#2536`_, `#2454`_)
|
||||
- Increase Test Coverage of ``CallbackQueryHandler`` (`#2520`_)
|
||||
- Stabilize CI (`#2522`_, `#2537`_, `#2541`_)
|
||||
- Fix ``send_phone_number_to_provider`` argument for ``Bot.send_invoice`` (`#2527`_)
|
||||
- Handle Classes as Input for ``BasePersistence.replace/insert_bot`` (`#2523`_)
|
||||
- Bump Tornado Version and Remove Workaround from `#2067`_ (`#2494`_)
|
||||
|
||||
.. _`#1844`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1844
|
||||
.. _`#2262`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2262
|
||||
.. _`#2528`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2528
|
||||
.. _`#2517`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2517
|
||||
.. _`#2345`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2345
|
||||
.. _`#2495`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2495
|
||||
.. _`#2547`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2547
|
||||
.. _`#2544`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2544
|
||||
.. _`#2536`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2536
|
||||
.. _`#2454`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2454
|
||||
.. _`#2520`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2520
|
||||
.. _`#2522`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2522
|
||||
.. _`#2537`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2537
|
||||
.. _`#2541`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2541
|
||||
.. _`#2527`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2527
|
||||
.. _`#2523`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2523
|
||||
.. _`#2067`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2067
|
||||
.. _`#2494`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2494
|
||||
.. _`#2510`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2510
|
||||
|
||||
Version 13.5
|
||||
============
|
||||
*Released 2021-04-30*
|
||||
|
||||
+12
-14
@@ -20,7 +20,7 @@ We have a vibrant community of developers helping each other in our `Telegram gr
|
||||
:target: https://pypi.org/project/python-telegram-bot/
|
||||
:alt: Supported Python versions
|
||||
|
||||
.. image:: https://img.shields.io/badge/Bot%20API-5.2-blue?logo=telegram
|
||||
.. image:: https://img.shields.io/badge/Bot%20API-5.4-blue?logo=telegram
|
||||
:target: https://core.telegram.org/bots/api-changelog
|
||||
:alt: Supported Bot API versions
|
||||
|
||||
@@ -50,19 +50,19 @@ We have a vibrant community of developers helping each other in our `Telegram gr
|
||||
|
||||
.. image:: https://api.codacy.com/project/badge/Grade/99d901eaa09b44b4819aec05c330c968
|
||||
:target: https://www.codacy.com/app/python-telegram-bot/python-telegram-bot?utm_source=github.com&utm_medium=referral&utm_content=python-telegram-bot/python-telegram-bot&utm_campaign=Badge_Grade
|
||||
:alt: Code quality
|
||||
:alt: Code quality: Codacy
|
||||
|
||||
.. image:: https://deepsource.io/gh/python-telegram-bot/python-telegram-bot.svg/?label=active+issues
|
||||
:target: https://deepsource.io/gh/python-telegram-bot/python-telegram-bot/?ref=repository-badge
|
||||
:alt: Code quality: DeepSource
|
||||
|
||||
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
|
||||
:target: https://github.com/psf/black
|
||||
:target: https://github.com/psf/black
|
||||
|
||||
.. image:: https://img.shields.io/badge/Telegram-Group-blue.svg?logo=telegram
|
||||
:target: https://telegram.me/pythontelegrambotgroup
|
||||
:alt: Telegram Group
|
||||
|
||||
.. image:: https://img.shields.io/badge/IRC-Channel-blue.svg
|
||||
:target: https://webchat.freenode.net/?channels=##python-telegram-bot
|
||||
:alt: IRC Bridge
|
||||
|
||||
=================
|
||||
Table of contents
|
||||
=================
|
||||
@@ -93,7 +93,7 @@ Introduction
|
||||
|
||||
This library provides a pure Python interface for the
|
||||
`Telegram Bot API <https://core.telegram.org/bots/api>`_.
|
||||
It's compatible with Python versions 3.6+. PTB might also work on `PyPy <http://pypy.org/>`_, though there have been a lot of issues before. Hence, PyPy is not officially supported.
|
||||
It's compatible with Python versions 3.6.8+. PTB might also work on `PyPy <http://pypy.org/>`_, though there have been a lot of issues before. Hence, PyPy is not officially supported.
|
||||
|
||||
In addition to the pure API implementation, this library features a number of high-level classes to
|
||||
make the development of bots easy and straightforward. These classes are contained in the
|
||||
@@ -111,7 +111,7 @@ Installing both ``python-telegram-bot`` and ``python-telegram-bot-raw`` in conju
|
||||
Telegram API support
|
||||
====================
|
||||
|
||||
All types and methods of the Telegram Bot API **5.2** are supported.
|
||||
All types and methods of the Telegram Bot API **5.4** are supported.
|
||||
|
||||
==========
|
||||
Installing
|
||||
@@ -215,13 +215,11 @@ You can get help in several ways:
|
||||
|
||||
1. We have a vibrant community of developers helping each other in our `Telegram group <https://telegram.me/pythontelegrambotgroup>`_. Join us!
|
||||
|
||||
2. In case you are unable to join our group due to Telegram restrictions, you can use our `IRC channel <https://webchat.freenode.net/?channels=##python-telegram-bot>`_.
|
||||
2. Report bugs, request new features or ask questions by `creating an issue <https://github.com/python-telegram-bot/python-telegram-bot/issues/new/choose>`_ or `a discussion <https://github.com/python-telegram-bot/python-telegram-bot/discussions/new>`_.
|
||||
|
||||
3. Report bugs, request new features or ask questions by `creating an issue <https://github.com/python-telegram-bot/python-telegram-bot/issues/new/choose>`_ or `a discussion <https://github.com/python-telegram-bot/python-telegram-bot/discussions/new>`_.
|
||||
3. Our `Wiki pages <https://github.com/python-telegram-bot/python-telegram-bot/wiki/>`_ offer a growing amount of resources.
|
||||
|
||||
4. Our `Wiki pages <https://github.com/python-telegram-bot/python-telegram-bot/wiki/>`_ offer a growing amount of resources.
|
||||
|
||||
5. You can even ask for help on Stack Overflow using the `python-telegram-bot tag <https://stackoverflow.com/questions/tagged/python-telegram-bot>`_.
|
||||
4. You can even ask for help on Stack Overflow using the `python-telegram-bot tag <https://stackoverflow.com/questions/tagged/python-telegram-bot>`_.
|
||||
|
||||
|
||||
============
|
||||
|
||||
+12
-14
@@ -20,7 +20,7 @@ We have a vibrant community of developers helping each other in our `Telegram gr
|
||||
:target: https://pypi.org/project/python-telegram-bot-raw/
|
||||
:alt: Supported Python versions
|
||||
|
||||
.. image:: https://img.shields.io/badge/Bot%20API-5.2-blue?logo=telegram
|
||||
.. image:: https://img.shields.io/badge/Bot%20API-5.4-blue?logo=telegram
|
||||
:target: https://core.telegram.org/bots/api-changelog
|
||||
:alt: Supported Bot API versions
|
||||
|
||||
@@ -50,19 +50,19 @@ We have a vibrant community of developers helping each other in our `Telegram gr
|
||||
|
||||
.. image:: https://api.codacy.com/project/badge/Grade/99d901eaa09b44b4819aec05c330c968
|
||||
:target: https://www.codacy.com/app/python-telegram-bot/python-telegram-bot?utm_source=github.com&utm_medium=referral&utm_content=python-telegram-bot/python-telegram-bot&utm_campaign=Badge_Grade
|
||||
:alt: Code quality
|
||||
:alt: Code quality: Codacy
|
||||
|
||||
.. image:: https://deepsource.io/gh/python-telegram-bot/python-telegram-bot.svg/?label=active+issues
|
||||
:target: https://deepsource.io/gh/python-telegram-bot/python-telegram-bot/?ref=repository-badge
|
||||
:alt: Code quality: DeepSource
|
||||
|
||||
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
|
||||
:target: https://github.com/psf/black
|
||||
:target: https://github.com/psf/black
|
||||
|
||||
.. image:: https://img.shields.io/badge/Telegram-Group-blue.svg?logo=telegram
|
||||
:target: https://telegram.me/pythontelegrambotgroup
|
||||
:alt: Telegram Group
|
||||
|
||||
.. image:: https://img.shields.io/badge/IRC-Channel-blue.svg
|
||||
:target: https://webchat.freenode.net/?channels=##python-telegram-bot
|
||||
:alt: IRC Bridge
|
||||
|
||||
=================
|
||||
Table of contents
|
||||
=================
|
||||
@@ -91,7 +91,7 @@ Introduction
|
||||
|
||||
This library provides a pure Python, lightweight interface for the
|
||||
`Telegram Bot API <https://core.telegram.org/bots/api>`_.
|
||||
It's compatible with Python versions 3.6+. PTB-Raw might also work on `PyPy <http://pypy.org/>`_, though there have been a lot of issues before. Hence, PyPy is not officially supported.
|
||||
It's compatible with Python versions 3.6.8+. PTB-Raw might also work on `PyPy <http://pypy.org/>`_, though there have been a lot of issues before. Hence, PyPy is not officially supported.
|
||||
|
||||
``python-telegram-bot-raw`` is part of the `python-telegram-bot <https://python-telegram-bot.org>`_ ecosystem and provides the pure API functionality extracted from PTB. It therefore does *not* have independent release schedules, changelogs or documentation. Please consult the PTB resources.
|
||||
|
||||
@@ -105,7 +105,7 @@ Installing both ``python-telegram-bot`` and ``python-telegram-bot-raw`` in conju
|
||||
Telegram API support
|
||||
====================
|
||||
|
||||
All types and methods of the Telegram Bot API **5.2** are supported.
|
||||
All types and methods of the Telegram Bot API **5.4** are supported.
|
||||
|
||||
==========
|
||||
Installing
|
||||
@@ -198,13 +198,11 @@ You can get help in several ways:
|
||||
|
||||
1. We have a vibrant community of developers helping each other in our `Telegram group <https://telegram.me/pythontelegrambotgroup>`_. Join us!
|
||||
|
||||
2. In case you are unable to join our group due to Telegram restrictions, you can use our `IRC channel <https://webchat.freenode.net/?channels=##python-telegram-bot>`_.
|
||||
2. Report bugs, request new features or ask questions by `creating an issue <https://github.com/python-telegram-bot/python-telegram-bot/issues/new/choose>`_ or `a discussion <https://github.com/python-telegram-bot/python-telegram-bot/discussions/new>`_.
|
||||
|
||||
3. Report bugs, request new features or ask questions by `creating an issue <https://github.com/python-telegram-bot/python-telegram-bot/issues/new/choose>`_ or `a discussion <https://github.com/python-telegram-bot/python-telegram-bot/discussions/new>`_.
|
||||
3. Our `Wiki pages <https://github.com/python-telegram-bot/python-telegram-bot/wiki/>`_ offer a growing amount of resources.
|
||||
|
||||
4. Our `Wiki pages <https://github.com/python-telegram-bot/python-telegram-bot/wiki/>`_ offer a growing amount of resources.
|
||||
|
||||
5. You can even ask for help on Stack Overflow using the `python-telegram-bot tag <https://stackoverflow.com/questions/tagged/python-telegram-bot>`_.
|
||||
4. You can even ask for help on Stack Overflow using the `python-telegram-bot tag <https://stackoverflow.com/questions/tagged/python-telegram-bot>`_.
|
||||
|
||||
============
|
||||
Contributing
|
||||
|
||||
+2
-2
@@ -60,9 +60,9 @@ author = u'Leandro Toledo'
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '13.5' # telegram.__version__[:3]
|
||||
version = '13.8' # telegram.__version__[:3]
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '13.5' # telegram.__version__
|
||||
release = '13.8' # telegram.__version__
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
:github_url: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/telegram/botcommandscope.py
|
||||
|
||||
telegram.BotCommandScope
|
||||
========================
|
||||
|
||||
.. autoclass:: telegram.BotCommandScope
|
||||
:members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,8 @@
|
||||
:github_url: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/telegram/botcommandscope.py
|
||||
|
||||
telegram.BotCommandScopeAllChatAdministrators
|
||||
=============================================
|
||||
|
||||
.. autoclass:: telegram.BotCommandScopeAllChatAdministrators
|
||||
:members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,8 @@
|
||||
:github_url: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/telegram/botcommandscope.py
|
||||
|
||||
telegram.BotCommandScopeAllGroupChats
|
||||
=======================================
|
||||
|
||||
.. autoclass:: telegram.BotCommandScopeAllGroupChats
|
||||
:members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,8 @@
|
||||
:github_url: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/telegram/botcommandscope.py
|
||||
|
||||
telegram.BotCommandScopeAllPrivateChats
|
||||
=======================================
|
||||
|
||||
.. autoclass:: telegram.BotCommandScopeAllPrivateChats
|
||||
:members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,8 @@
|
||||
:github_url: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/telegram/botcommandscope.py
|
||||
|
||||
telegram.BotCommandScopeChat
|
||||
============================
|
||||
|
||||
.. autoclass:: telegram.BotCommandScopeChat
|
||||
:members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,8 @@
|
||||
:github_url: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/telegram/botcommandscope.py
|
||||
|
||||
telegram.BotCommandScopeChatAdministrators
|
||||
==========================================
|
||||
|
||||
.. autoclass:: telegram.BotCommandScopeChatAdministrators
|
||||
:members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,8 @@
|
||||
:github_url: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/telegram/botcommandscope.py
|
||||
|
||||
telegram.BotCommandScopeChatMember
|
||||
==================================
|
||||
|
||||
.. autoclass:: telegram.BotCommandScopeChatMember
|
||||
:members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,8 @@
|
||||
:github_url: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/telegram/botcommandscope.py
|
||||
|
||||
telegram.BotCommandScopeDefault
|
||||
===============================
|
||||
|
||||
.. autoclass:: telegram.BotCommandScopeDefault
|
||||
:members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,8 @@
|
||||
:github_url: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/telegram/chatmember.py
|
||||
|
||||
telegram.ChatMemberAdministrator
|
||||
================================
|
||||
|
||||
.. autoclass:: telegram.ChatMemberAdministrator
|
||||
:members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,8 @@
|
||||
:github_url: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/telegram/chatmember.py
|
||||
|
||||
telegram.ChatMemberBanned
|
||||
=========================
|
||||
|
||||
.. autoclass:: telegram.ChatMemberBanned
|
||||
:members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,8 @@
|
||||
:github_url: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/telegram/chatmember.py
|
||||
|
||||
telegram.ChatMemberLeft
|
||||
=======================
|
||||
|
||||
.. autoclass:: telegram.ChatMemberLeft
|
||||
:members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,8 @@
|
||||
:github_url: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/telegram/chatmember.py
|
||||
|
||||
telegram.ChatMemberMember
|
||||
=========================
|
||||
|
||||
.. autoclass:: telegram.ChatMemberMember
|
||||
:members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,9 @@
|
||||
:github_url: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/telegram/chatmember.py
|
||||
|
||||
telegram.ChatMemberOwner
|
||||
========================
|
||||
|
||||
.. autoclass:: telegram.ChatMemberOwner
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
:github_url: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/telegram/chatmember.py
|
||||
|
||||
telegram.ChatMemberRestricted
|
||||
=============================
|
||||
|
||||
.. autoclass:: telegram.ChatMemberRestricted
|
||||
:members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,8 @@
|
||||
:github_url: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/telegram/ext/callbackdatacache.py
|
||||
|
||||
telegram.ext.CallbackDataCache
|
||||
==============================
|
||||
|
||||
.. autoclass:: telegram.ext.CallbackDataCache
|
||||
:members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,8 @@
|
||||
:github_url: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/telegram/ext/contexttypes.py
|
||||
|
||||
telegram.ext.ContextTypes
|
||||
=========================
|
||||
|
||||
.. autoclass:: telegram.ext.ContextTypes
|
||||
:members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,9 @@
|
||||
:github_url: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/telegram/ext/extbot.py
|
||||
|
||||
telegram.ext.ExtBot
|
||||
===================
|
||||
|
||||
.. autoclass:: telegram.ext.ExtBot
|
||||
:show-inheritance:
|
||||
|
||||
.. autofunction:: telegram.ext.ExtBot.insert_callback_data
|
||||
@@ -5,5 +5,4 @@ telegram.ext.Handler
|
||||
|
||||
.. autoclass:: telegram.ext.Handler
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
:github_url: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/telegram/ext/callbackdatacache.py
|
||||
|
||||
telegram.ext.InvalidCallbackData
|
||||
================================
|
||||
|
||||
.. autoclass:: telegram.ext.InvalidCallbackData
|
||||
:members:
|
||||
:show-inheritance:
|
||||
@@ -3,15 +3,17 @@ telegram.ext package
|
||||
|
||||
.. toctree::
|
||||
|
||||
telegram.ext.extbot
|
||||
telegram.ext.updater
|
||||
telegram.ext.dispatcher
|
||||
telegram.ext.dispatcherhandlerstop
|
||||
telegram.ext.callbackcontext
|
||||
telegram.ext.defaults
|
||||
telegram.ext.job
|
||||
telegram.ext.jobqueue
|
||||
telegram.ext.messagequeue
|
||||
telegram.ext.delayqueue
|
||||
telegram.ext.contexttypes
|
||||
telegram.ext.defaults
|
||||
|
||||
Handlers
|
||||
--------
|
||||
@@ -46,9 +48,18 @@ Persistence
|
||||
telegram.ext.picklepersistence
|
||||
telegram.ext.dictpersistence
|
||||
|
||||
Arbitrary Callback Data
|
||||
-----------------------
|
||||
|
||||
.. toctree::
|
||||
|
||||
telegram.ext.callbackdatacache
|
||||
telegram.ext.invalidcallbackdata
|
||||
|
||||
utils
|
||||
-----
|
||||
|
||||
.. toctree::
|
||||
|
||||
telegram.ext.utils.promise
|
||||
telegram.ext.utils.promise
|
||||
telegram.ext.utils.types
|
||||
@@ -0,0 +1,8 @@
|
||||
:github_url: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/telegram/ext/utils/types.py
|
||||
|
||||
telegram.ext.utils.types Module
|
||||
================================
|
||||
|
||||
.. automodule:: telegram.ext.utils.types
|
||||
:members:
|
||||
:show-inheritance:
|
||||
@@ -7,12 +7,26 @@ telegram package
|
||||
telegram.audio
|
||||
telegram.bot
|
||||
telegram.botcommand
|
||||
telegram.botcommandscope
|
||||
telegram.botcommandscopedefault
|
||||
telegram.botcommandscopeallprivatechats
|
||||
telegram.botcommandscopeallgroupchats
|
||||
telegram.botcommandscopeallchatadministrators
|
||||
telegram.botcommandscopechat
|
||||
telegram.botcommandscopechatadministrators
|
||||
telegram.botcommandscopechatmember
|
||||
telegram.callbackquery
|
||||
telegram.chat
|
||||
telegram.chataction
|
||||
telegram.chatinvitelink
|
||||
telegram.chatlocation
|
||||
telegram.chatmember
|
||||
telegram.chatmemberowner
|
||||
telegram.chatmemberadministrator
|
||||
telegram.chatmembermember
|
||||
telegram.chatmemberrestricted
|
||||
telegram.chatmemberleft
|
||||
telegram.chatmemberbanned
|
||||
telegram.chatmemberupdated
|
||||
telegram.chatpermissions
|
||||
telegram.chatphoto
|
||||
|
||||
@@ -5,5 +5,4 @@ telegram.Update
|
||||
|
||||
.. autoclass:: telegram.Update
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
@@ -5,5 +5,4 @@ telegram.User
|
||||
|
||||
.. autoclass:: telegram.User
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
+9
-1
@@ -4,6 +4,8 @@ In this folder are small examples to show what a bot written with `python-telegr
|
||||
|
||||
All examples are licensed under the [CC0 License](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/LICENSE.txt) and are therefore fully dedicated to the public domain. You can use them as the base for your own bots without worrying about copyrights.
|
||||
|
||||
Do note that we ignore one pythonic convention. Best practice would dictate, in many handler callbacks function signatures, to replace the argument `context` with an underscore, since `context` is an unused local variable in those callbacks. However, since these are examples and not having a name for that argument confuses beginners, we decided to have it present.
|
||||
|
||||
### [`echobot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/echobot.py)
|
||||
This is probably the base for most of the bots made with `python-telegram-bot`. It simply replies to each text message with a message that contains the same text.
|
||||
|
||||
@@ -47,7 +49,13 @@ A basic example of a bot that can accept payments. Don't forget to enable and co
|
||||
A basic example on how to set up a custom error handler.
|
||||
|
||||
### [`chatmemberbot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/chatmemberbot.py)
|
||||
A basic example on how `(my_)chat_member` updates can be used.
|
||||
A basic example on how `(my_)chat_member` updates can be used.
|
||||
|
||||
### [`contexttypesbot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/contexttypesbot.py)
|
||||
This example showcases how `telegram.ext.ContextTypes` can be used to customize the `context` argument of handler and job callbacks.
|
||||
|
||||
### [`arbitrarycallbackdatabot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/arbitrarycallbackdatabot.py)
|
||||
This example showcases how PTBs "arbitrary callback data" feature can be used.
|
||||
|
||||
## Pure API
|
||||
The [`rawapibot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/rawapibot.py) example uses only the pure, "bare-metal" API wrapper.
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
#!/usr/bin/env python
|
||||
# pylint: disable=C0116,W0613
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
"""This example showcases how PTBs "arbitrary callback data" feature can be used.
|
||||
|
||||
For detailed info on arbitrary callback data, see the wiki page at https://git.io/JGBDI
|
||||
"""
|
||||
import logging
|
||||
from typing import List, Tuple, cast
|
||||
|
||||
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
|
||||
from telegram.ext import (
|
||||
Updater,
|
||||
CommandHandler,
|
||||
CallbackQueryHandler,
|
||||
CallbackContext,
|
||||
InvalidCallbackData,
|
||||
PicklePersistence,
|
||||
)
|
||||
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def start(update: Update, context: CallbackContext) -> None:
|
||||
"""Sends a message with 5 inline buttons attached."""
|
||||
number_list: List[int] = []
|
||||
update.message.reply_text('Please choose:', reply_markup=build_keyboard(number_list))
|
||||
|
||||
|
||||
def help_command(update: Update, context: CallbackContext) -> None:
|
||||
"""Displays info on how to use the bot."""
|
||||
update.message.reply_text(
|
||||
"Use /start to test this bot. Use /clear to clear the stored data so that you can see "
|
||||
"what happens, if the button data is not available. "
|
||||
)
|
||||
|
||||
|
||||
def clear(update: Update, context: CallbackContext) -> None:
|
||||
"""Clears the callback data cache"""
|
||||
context.bot.callback_data_cache.clear_callback_data() # type: ignore[attr-defined]
|
||||
context.bot.callback_data_cache.clear_callback_queries() # type: ignore[attr-defined]
|
||||
update.effective_message.reply_text('All clear!')
|
||||
|
||||
|
||||
def build_keyboard(current_list: List[int]) -> InlineKeyboardMarkup:
|
||||
"""Helper function to build the next inline keyboard."""
|
||||
return InlineKeyboardMarkup.from_column(
|
||||
[InlineKeyboardButton(str(i), callback_data=(i, current_list)) for i in range(1, 6)]
|
||||
)
|
||||
|
||||
|
||||
def list_button(update: Update, context: CallbackContext) -> None:
|
||||
"""Parses the CallbackQuery and updates the message text."""
|
||||
query = update.callback_query
|
||||
query.answer()
|
||||
# Get the data from the callback_data.
|
||||
# If you're using a type checker like MyPy, you'll have to use typing.cast
|
||||
# to make the checker get the expected type of the callback_data
|
||||
number, number_list = cast(Tuple[int, List[int]], query.data)
|
||||
# append the number to the list
|
||||
number_list.append(number)
|
||||
|
||||
query.edit_message_text(
|
||||
text=f"So far you've selected {number_list}. Choose the next item:",
|
||||
reply_markup=build_keyboard(number_list),
|
||||
)
|
||||
|
||||
# we can delete the data stored for the query, because we've replaced the buttons
|
||||
context.drop_callback_data(query)
|
||||
|
||||
|
||||
def handle_invalid_button(update: Update, context: CallbackContext) -> None:
|
||||
"""Informs the user that the button is no longer available."""
|
||||
update.callback_query.answer()
|
||||
update.effective_message.edit_text(
|
||||
'Sorry, I could not process this button click 😕 Please send /start to get a new keyboard.'
|
||||
)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Run the bot."""
|
||||
# We use persistence to demonstrate how buttons can still work after the bot was restarted
|
||||
persistence = PicklePersistence(
|
||||
filename='arbitrarycallbackdatabot.pickle', store_callback_data=True
|
||||
)
|
||||
# Create the Updater and pass it your bot's token.
|
||||
updater = Updater("TOKEN", persistence=persistence, arbitrary_callback_data=True)
|
||||
|
||||
updater.dispatcher.add_handler(CommandHandler('start', start))
|
||||
updater.dispatcher.add_handler(CommandHandler('help', help_command))
|
||||
updater.dispatcher.add_handler(CommandHandler('clear', clear))
|
||||
updater.dispatcher.add_handler(
|
||||
CallbackQueryHandler(handle_invalid_button, pattern=InvalidCallbackData)
|
||||
)
|
||||
updater.dispatcher.add_handler(CallbackQueryHandler(list_button))
|
||||
|
||||
# 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()
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
# pylint: disable=C0116
|
||||
# pylint: disable=C0116,W0613
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
"""
|
||||
@@ -35,7 +35,8 @@ def extract_status_change(
|
||||
) -> Optional[Tuple[bool, bool]]:
|
||||
"""Takes a ChatMemberUpdated instance and extracts whether the 'old_chat_member' was a member
|
||||
of the chat and whether the 'new_chat_member' is a member of the chat. Returns None, if
|
||||
the status didn't change."""
|
||||
the status didn't change.
|
||||
"""
|
||||
status_change = chat_member_update.difference().get("status")
|
||||
old_is_member, new_is_member = chat_member_update.difference().get("is_member", (None, None))
|
||||
|
||||
@@ -113,7 +114,7 @@ def show_chats(update: Update, context: CallbackContext) -> None:
|
||||
update.effective_message.reply_text(text)
|
||||
|
||||
|
||||
def greet_chat_members(update: Update, _: CallbackContext) -> None:
|
||||
def greet_chat_members(update: Update, context: CallbackContext) -> None:
|
||||
"""Greets new users in chats and announces when someone leaves"""
|
||||
result = extract_status_change(update.chat_member)
|
||||
if result is None:
|
||||
@@ -151,11 +152,9 @@ def main() -> None:
|
||||
dispatcher.add_handler(ChatMemberHandler(greet_chat_members, ChatMemberHandler.CHAT_MEMBER))
|
||||
|
||||
# Start the Bot
|
||||
# We pass 'allowed_updates' to *only* handle updates with '(my_)chat_member' or 'message'
|
||||
# If you want to handle *all* updates, pass Update.ALL_TYPES
|
||||
updater.start_polling(
|
||||
allowed_updates=[Update.MESSAGE, Update.CHAT_MEMBER, Update.MY_CHAT_MEMBER]
|
||||
)
|
||||
# We pass 'allowed_updates' handle *all* updates including `chat_member` updates
|
||||
# To reset this, simply pass `allowed_updates=[]`
|
||||
updater.start_polling(allowed_updates=Update.ALL_TYPES)
|
||||
|
||||
# Run the bot until you press Ctrl-C or the process receives SIGINT,
|
||||
# SIGTERM or SIGABRT. This should be used most of the time, since
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
#!/usr/bin/env python
|
||||
# pylint: disable=C0116,W0613
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
"""
|
||||
Simple Bot to showcase `telegram.ext.ContextTypes`.
|
||||
|
||||
Usage:
|
||||
Press Ctrl-C on the command line or send a signal to the process to stop the
|
||||
bot.
|
||||
"""
|
||||
|
||||
from collections import defaultdict
|
||||
from typing import DefaultDict, Optional, Set
|
||||
|
||||
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, ParseMode
|
||||
from telegram.ext import (
|
||||
Updater,
|
||||
CommandHandler,
|
||||
CallbackContext,
|
||||
ContextTypes,
|
||||
CallbackQueryHandler,
|
||||
TypeHandler,
|
||||
Dispatcher,
|
||||
)
|
||||
|
||||
|
||||
class ChatData:
|
||||
"""Custom class for chat_data. Here we store data per message."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.clicks_per_message: DefaultDict[int, int] = defaultdict(int)
|
||||
|
||||
|
||||
# The [dict, ChatData, dict] is for type checkers like mypy
|
||||
class CustomContext(CallbackContext[dict, ChatData, dict]):
|
||||
"""Custom class for context."""
|
||||
|
||||
def __init__(self, dispatcher: Dispatcher):
|
||||
super().__init__(dispatcher=dispatcher)
|
||||
self._message_id: Optional[int] = None
|
||||
|
||||
@property
|
||||
def bot_user_ids(self) -> Set[int]:
|
||||
"""Custom shortcut to access a value stored in the bot_data dict"""
|
||||
return self.bot_data.setdefault('user_ids', set())
|
||||
|
||||
@property
|
||||
def message_clicks(self) -> Optional[int]:
|
||||
"""Access the number of clicks for the message this context object was built for."""
|
||||
if self._message_id:
|
||||
return self.chat_data.clicks_per_message[self._message_id]
|
||||
return None
|
||||
|
||||
@message_clicks.setter
|
||||
def message_clicks(self, value: int) -> None:
|
||||
"""Allow to change the count"""
|
||||
if not self._message_id:
|
||||
raise RuntimeError('There is no message associated with this context obejct.')
|
||||
self.chat_data.clicks_per_message[self._message_id] = value
|
||||
|
||||
@classmethod
|
||||
def from_update(cls, update: object, dispatcher: 'Dispatcher') -> 'CustomContext':
|
||||
"""Override from_update to set _message_id."""
|
||||
# Make sure to call super()
|
||||
context = super().from_update(update, dispatcher)
|
||||
|
||||
if context.chat_data and isinstance(update, Update) and update.effective_message:
|
||||
context._message_id = update.effective_message.message_id # pylint: disable=W0212
|
||||
|
||||
# Remember to return the object
|
||||
return context
|
||||
|
||||
|
||||
def start(update: Update, context: CustomContext) -> None:
|
||||
"""Display a message with a button."""
|
||||
update.message.reply_html(
|
||||
'This button was clicked <i>0</i> times.',
|
||||
reply_markup=InlineKeyboardMarkup.from_button(
|
||||
InlineKeyboardButton(text='Click me!', callback_data='button')
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def count_click(update: Update, context: CustomContext) -> None:
|
||||
"""Update the click count for the message."""
|
||||
context.message_clicks += 1
|
||||
update.callback_query.answer()
|
||||
update.effective_message.edit_text(
|
||||
f'This button was clicked <i>{context.message_clicks}</i> times.',
|
||||
reply_markup=InlineKeyboardMarkup.from_button(
|
||||
InlineKeyboardButton(text='Click me!', callback_data='button')
|
||||
),
|
||||
parse_mode=ParseMode.HTML,
|
||||
)
|
||||
|
||||
|
||||
def print_users(update: Update, context: CustomContext) -> None:
|
||||
"""Show which users have been using this bot."""
|
||||
update.message.reply_text(
|
||||
'The following user IDs have used this bot: '
|
||||
f'{", ".join(map(str, context.bot_user_ids))}'
|
||||
)
|
||||
|
||||
|
||||
def track_users(update: Update, context: CustomContext) -> None:
|
||||
"""Store the user id of the incoming update, if any."""
|
||||
if update.effective_user:
|
||||
context.bot_user_ids.add(update.effective_user.id)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Run the bot."""
|
||||
context_types = ContextTypes(context=CustomContext, chat_data=ChatData)
|
||||
updater = Updater("TOKEN", context_types=context_types)
|
||||
|
||||
dispatcher = updater.dispatcher
|
||||
# run track_users in its own group to not interfere with the user handlers
|
||||
dispatcher.add_handler(TypeHandler(Update, track_users), group=-1)
|
||||
dispatcher.add_handler(CommandHandler("start", start))
|
||||
dispatcher.add_handler(CallbackQueryHandler(count_click))
|
||||
dispatcher.add_handler(CommandHandler("print_users", print_users))
|
||||
|
||||
updater.start_polling()
|
||||
updater.idle()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
+21
-10
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
# pylint: disable=C0116
|
||||
# pylint: disable=C0116,W0613
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
"""
|
||||
@@ -36,20 +36,24 @@ logger = logging.getLogger(__name__)
|
||||
GENDER, PHOTO, LOCATION, BIO = range(4)
|
||||
|
||||
|
||||
def start(update: Update, _: CallbackContext) -> int:
|
||||
def start(update: Update, context: CallbackContext) -> int:
|
||||
"""Starts the conversation and asks the user about their gender."""
|
||||
reply_keyboard = [['Boy', 'Girl', 'Other']]
|
||||
|
||||
update.message.reply_text(
|
||||
'Hi! My name is Professor Bot. I will hold a conversation with you. '
|
||||
'Send /cancel to stop talking to me.\n\n'
|
||||
'Are you a boy or a girl?',
|
||||
reply_markup=ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True),
|
||||
reply_markup=ReplyKeyboardMarkup(
|
||||
reply_keyboard, one_time_keyboard=True, input_field_placeholder='Boy or Girl?'
|
||||
),
|
||||
)
|
||||
|
||||
return GENDER
|
||||
|
||||
|
||||
def gender(update: Update, _: CallbackContext) -> int:
|
||||
def gender(update: Update, context: CallbackContext) -> int:
|
||||
"""Stores the selected gender and asks for a photo."""
|
||||
user = update.message.from_user
|
||||
logger.info("Gender of %s: %s", user.first_name, update.message.text)
|
||||
update.message.reply_text(
|
||||
@@ -61,7 +65,8 @@ def gender(update: Update, _: CallbackContext) -> int:
|
||||
return PHOTO
|
||||
|
||||
|
||||
def photo(update: Update, _: CallbackContext) -> int:
|
||||
def photo(update: Update, context: CallbackContext) -> int:
|
||||
"""Stores the photo and asks for a location."""
|
||||
user = update.message.from_user
|
||||
photo_file = update.message.photo[-1].get_file()
|
||||
photo_file.download('user_photo.jpg')
|
||||
@@ -73,7 +78,8 @@ def photo(update: Update, _: CallbackContext) -> int:
|
||||
return LOCATION
|
||||
|
||||
|
||||
def skip_photo(update: Update, _: CallbackContext) -> int:
|
||||
def skip_photo(update: Update, context: CallbackContext) -> int:
|
||||
"""Skips the photo and asks for a location."""
|
||||
user = update.message.from_user
|
||||
logger.info("User %s did not send a photo.", user.first_name)
|
||||
update.message.reply_text(
|
||||
@@ -83,7 +89,8 @@ def skip_photo(update: Update, _: CallbackContext) -> int:
|
||||
return LOCATION
|
||||
|
||||
|
||||
def location(update: Update, _: CallbackContext) -> int:
|
||||
def location(update: Update, context: CallbackContext) -> int:
|
||||
"""Stores the location and asks for some info about the user."""
|
||||
user = update.message.from_user
|
||||
user_location = update.message.location
|
||||
logger.info(
|
||||
@@ -96,7 +103,8 @@ def location(update: Update, _: CallbackContext) -> int:
|
||||
return BIO
|
||||
|
||||
|
||||
def skip_location(update: Update, _: CallbackContext) -> int:
|
||||
def skip_location(update: Update, context: CallbackContext) -> int:
|
||||
"""Skips the location and asks for info about the user."""
|
||||
user = update.message.from_user
|
||||
logger.info("User %s did not send a location.", user.first_name)
|
||||
update.message.reply_text(
|
||||
@@ -106,7 +114,8 @@ def skip_location(update: Update, _: CallbackContext) -> int:
|
||||
return BIO
|
||||
|
||||
|
||||
def bio(update: Update, _: CallbackContext) -> int:
|
||||
def bio(update: Update, context: CallbackContext) -> int:
|
||||
"""Stores the info about the user and ends the conversation."""
|
||||
user = update.message.from_user
|
||||
logger.info("Bio of %s: %s", user.first_name, update.message.text)
|
||||
update.message.reply_text('Thank you! I hope we can talk again some day.')
|
||||
@@ -114,7 +123,8 @@ def bio(update: Update, _: CallbackContext) -> int:
|
||||
return ConversationHandler.END
|
||||
|
||||
|
||||
def cancel(update: Update, _: CallbackContext) -> int:
|
||||
def cancel(update: Update, context: CallbackContext) -> int:
|
||||
"""Cancels and ends the conversation."""
|
||||
user = update.message.from_user
|
||||
logger.info("User %s canceled the conversation.", user.first_name)
|
||||
update.message.reply_text(
|
||||
@@ -125,6 +135,7 @@ def cancel(update: Update, _: CallbackContext) -> int:
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Run the bot."""
|
||||
# Create the Updater and pass it your bot's token.
|
||||
updater = Updater("TOKEN")
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
# pylint: disable=C0116
|
||||
# pylint: disable=C0116,W0613
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
"""
|
||||
@@ -45,15 +45,13 @@ markup = ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True)
|
||||
|
||||
|
||||
def facts_to_str(user_data: Dict[str, str]) -> str:
|
||||
facts = list()
|
||||
|
||||
for key, value in user_data.items():
|
||||
facts.append(f'{key} - {value}')
|
||||
|
||||
"""Helper function for formatting the gathered user info."""
|
||||
facts = [f'{key} - {value}' for key, value in user_data.items()]
|
||||
return "\n".join(facts).join(['\n', '\n'])
|
||||
|
||||
|
||||
def start(update: Update, _: CallbackContext) -> int:
|
||||
def start(update: Update, context: CallbackContext) -> int:
|
||||
"""Start the conversation and ask user for input."""
|
||||
update.message.reply_text(
|
||||
"Hi! My name is Doctor Botter. I will hold a more complex conversation with you. "
|
||||
"Why don't you tell me something about yourself?",
|
||||
@@ -64,6 +62,7 @@ def start(update: Update, _: CallbackContext) -> int:
|
||||
|
||||
|
||||
def regular_choice(update: Update, context: CallbackContext) -> int:
|
||||
"""Ask the user for info about the selected predefined choice."""
|
||||
text = update.message.text
|
||||
context.user_data['choice'] = text
|
||||
update.message.reply_text(f'Your {text.lower()}? Yes, I would love to hear about that!')
|
||||
@@ -71,7 +70,8 @@ def regular_choice(update: Update, context: CallbackContext) -> int:
|
||||
return TYPING_REPLY
|
||||
|
||||
|
||||
def custom_choice(update: Update, _: CallbackContext) -> int:
|
||||
def custom_choice(update: Update, context: CallbackContext) -> int:
|
||||
"""Ask the user for a description of a custom category."""
|
||||
update.message.reply_text(
|
||||
'Alright, please send me the category first, for example "Most impressive skill"'
|
||||
)
|
||||
@@ -80,6 +80,7 @@ def custom_choice(update: Update, _: CallbackContext) -> int:
|
||||
|
||||
|
||||
def received_information(update: Update, context: CallbackContext) -> int:
|
||||
"""Store info provided by user and ask for the next category."""
|
||||
user_data = context.user_data
|
||||
text = update.message.text
|
||||
category = user_data['choice']
|
||||
@@ -97,6 +98,7 @@ def received_information(update: Update, context: CallbackContext) -> int:
|
||||
|
||||
|
||||
def done(update: Update, context: CallbackContext) -> int:
|
||||
"""Display the gathered info and end the conversation."""
|
||||
user_data = context.user_data
|
||||
if 'choice' in user_data:
|
||||
del user_data['choice']
|
||||
@@ -111,6 +113,7 @@ def done(update: Update, context: CallbackContext) -> int:
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Run the bot."""
|
||||
# Create the Updater and pass it your bot's token.
|
||||
updater = Updater("TOKEN")
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
# pylint: disable=C0116
|
||||
# pylint: disable=C0116,W0613
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
"""Bot that explains Telegram's "Deep Linking Parameters" functionality.
|
||||
@@ -78,7 +78,7 @@ def deep_linked_level_2(update: Update, context: CallbackContext) -> None:
|
||||
update.message.reply_text(text, parse_mode=ParseMode.MARKDOWN, disable_web_page_preview=True)
|
||||
|
||||
|
||||
def deep_linked_level_3(update: Update, _: CallbackContext) -> None:
|
||||
def deep_linked_level_3(update: Update, context: CallbackContext) -> None:
|
||||
"""Reached through the USING_ENTITIES payload"""
|
||||
update.message.reply_text(
|
||||
"It is also possible to make deep-linking using InlineKeyboardButtons.",
|
||||
@@ -124,7 +124,7 @@ def main() -> None:
|
||||
|
||||
# We can also pass on the deep-linking payload
|
||||
dispatcher.add_handler(
|
||||
CommandHandler("start", deep_linked_level_3, Filters.regex(USING_ENTITIES), pass_args=True)
|
||||
CommandHandler("start", deep_linked_level_3, Filters.regex(USING_ENTITIES))
|
||||
)
|
||||
|
||||
# Possible with inline keyboard buttons as well
|
||||
|
||||
+4
-4
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
# pylint: disable=C0116
|
||||
# pylint: disable=C0116,W0613
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
"""
|
||||
@@ -30,7 +30,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
# Define a few command handlers. These usually take the two arguments update and
|
||||
# context.
|
||||
def start(update: Update, _: CallbackContext) -> None:
|
||||
def start(update: Update, context: CallbackContext) -> None:
|
||||
"""Send a message when the command /start is issued."""
|
||||
user = update.effective_user
|
||||
update.message.reply_markdown_v2(
|
||||
@@ -39,12 +39,12 @@ def start(update: Update, _: CallbackContext) -> None:
|
||||
)
|
||||
|
||||
|
||||
def help_command(update: Update, _: CallbackContext) -> None:
|
||||
def help_command(update: Update, context: CallbackContext) -> None:
|
||||
"""Send a message when the command /help is issued."""
|
||||
update.message.reply_text('Help!')
|
||||
|
||||
|
||||
def echo(update: Update, _: CallbackContext) -> None:
|
||||
def echo(update: Update, context: CallbackContext) -> None:
|
||||
"""Echo the user message."""
|
||||
update.message.reply_text(update.message.text)
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
#!/usr/bin/env python
|
||||
# pylint: disable=C0116
|
||||
# pylint: disable=C0116,W0613
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
"""
|
||||
This is a very simple example on how one could implement a custom error handler
|
||||
"""
|
||||
"""This is a very simple example on how one could implement a custom error handler."""
|
||||
import html
|
||||
import json
|
||||
import logging
|
||||
@@ -53,12 +51,13 @@ def error_handler(update: object, context: CallbackContext) -> None:
|
||||
context.bot.send_message(chat_id=DEVELOPER_CHAT_ID, text=message, parse_mode=ParseMode.HTML)
|
||||
|
||||
|
||||
def bad_command(_: Update, context: CallbackContext) -> None:
|
||||
def bad_command(update: Update, context: CallbackContext) -> None:
|
||||
"""Raise an error to trigger the error handler."""
|
||||
context.bot.wrong_method_name() # type: ignore[attr-defined]
|
||||
|
||||
|
||||
def start(update: Update, _: CallbackContext) -> None:
|
||||
def start(update: Update, context: CallbackContext) -> None:
|
||||
"""Displays info on how to trigger an error."""
|
||||
update.effective_message.reply_html(
|
||||
'Use /bad_command to cause an error.\n'
|
||||
f'Your chat id is <code>{update.effective_chat.id}</code>.'
|
||||
@@ -66,6 +65,7 @@ def start(update: Update, _: CallbackContext) -> None:
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Run the bot."""
|
||||
# Create the Updater and pass it your bot's token.
|
||||
updater = Updater(BOT_TOKEN)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
# pylint: disable=C0116
|
||||
# pylint: disable=C0116,W0613
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
"""
|
||||
@@ -29,17 +29,17 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
# Define a few command handlers. These usually take the two arguments update and
|
||||
# context. Error handlers also receive the raised TelegramError object in error.
|
||||
def start(update: Update, _: CallbackContext) -> None:
|
||||
def start(update: Update, context: CallbackContext) -> None:
|
||||
"""Send a message when the command /start is issued."""
|
||||
update.message.reply_text('Hi!')
|
||||
|
||||
|
||||
def help_command(update: Update, _: CallbackContext) -> None:
|
||||
def help_command(update: Update, context: CallbackContext) -> None:
|
||||
"""Send a message when the command /help is issued."""
|
||||
update.message.reply_text('Help!')
|
||||
|
||||
|
||||
def inlinequery(update: Update, _: CallbackContext) -> None:
|
||||
def inlinequery(update: Update, context: CallbackContext) -> None:
|
||||
"""Handle the inline query."""
|
||||
query = update.inline_query.query
|
||||
|
||||
@@ -72,6 +72,7 @@ def inlinequery(update: Update, _: CallbackContext) -> None:
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Run the bot."""
|
||||
# Create the Updater and pass it your bot's token.
|
||||
updater = Updater("TOKEN")
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
# pylint: disable=C0116
|
||||
# pylint: disable=C0116,W0613
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
"""
|
||||
@@ -17,7 +17,8 @@ logging.basicConfig(
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def start(update: Update, _: CallbackContext) -> None:
|
||||
def start(update: Update, context: CallbackContext) -> None:
|
||||
"""Sends a message with three inline buttons attached."""
|
||||
keyboard = [
|
||||
[
|
||||
InlineKeyboardButton("Option 1", callback_data='1'),
|
||||
@@ -31,7 +32,8 @@ def start(update: Update, _: CallbackContext) -> None:
|
||||
update.message.reply_text('Please choose:', reply_markup=reply_markup)
|
||||
|
||||
|
||||
def button(update: Update, _: CallbackContext) -> None:
|
||||
def button(update: Update, context: CallbackContext) -> None:
|
||||
"""Parses the CallbackQuery and updates the message text."""
|
||||
query = update.callback_query
|
||||
|
||||
# CallbackQueries need to be answered, even if no notification to the user is needed
|
||||
@@ -41,11 +43,13 @@ def button(update: Update, _: CallbackContext) -> None:
|
||||
query.edit_message_text(text=f"Selected option: {query.data}")
|
||||
|
||||
|
||||
def help_command(update: Update, _: CallbackContext) -> None:
|
||||
def help_command(update: Update, context: CallbackContext) -> None:
|
||||
"""Displays info on how to use the bot."""
|
||||
update.message.reply_text("Use /start to test this bot.")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Run the bot."""
|
||||
# Create the Updater and pass it your bot's token.
|
||||
updater = Updater("TOKEN")
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
# pylint: disable=C0116
|
||||
# pylint: disable=C0116,W0613
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
"""Simple inline keyboard bot with multiple CallbackQueryHandlers.
|
||||
@@ -37,7 +37,7 @@ FIRST, SECOND = range(2)
|
||||
ONE, TWO, THREE, FOUR = range(4)
|
||||
|
||||
|
||||
def start(update: Update, _: CallbackContext) -> int:
|
||||
def start(update: Update, context: CallbackContext) -> int:
|
||||
"""Send message on `/start`."""
|
||||
# Get user that sent /start and log his name
|
||||
user = update.message.from_user
|
||||
@@ -59,7 +59,7 @@ def start(update: Update, _: CallbackContext) -> int:
|
||||
return FIRST
|
||||
|
||||
|
||||
def start_over(update: Update, _: CallbackContext) -> int:
|
||||
def start_over(update: Update, context: CallbackContext) -> int:
|
||||
"""Prompt same text & keyboard as `start` does but not as new message"""
|
||||
# Get CallbackQuery from Update
|
||||
query = update.callback_query
|
||||
@@ -80,7 +80,7 @@ def start_over(update: Update, _: CallbackContext) -> int:
|
||||
return FIRST
|
||||
|
||||
|
||||
def one(update: Update, _: CallbackContext) -> int:
|
||||
def one(update: Update, context: CallbackContext) -> int:
|
||||
"""Show new choice of buttons"""
|
||||
query = update.callback_query
|
||||
query.answer()
|
||||
@@ -97,7 +97,7 @@ def one(update: Update, _: CallbackContext) -> int:
|
||||
return FIRST
|
||||
|
||||
|
||||
def two(update: Update, _: CallbackContext) -> int:
|
||||
def two(update: Update, context: CallbackContext) -> int:
|
||||
"""Show new choice of buttons"""
|
||||
query = update.callback_query
|
||||
query.answer()
|
||||
@@ -114,7 +114,7 @@ def two(update: Update, _: CallbackContext) -> int:
|
||||
return FIRST
|
||||
|
||||
|
||||
def three(update: Update, _: CallbackContext) -> int:
|
||||
def three(update: Update, context: CallbackContext) -> int:
|
||||
"""Show new choice of buttons"""
|
||||
query = update.callback_query
|
||||
query.answer()
|
||||
@@ -132,7 +132,7 @@ def three(update: Update, _: CallbackContext) -> int:
|
||||
return SECOND
|
||||
|
||||
|
||||
def four(update: Update, _: CallbackContext) -> int:
|
||||
def four(update: Update, context: CallbackContext) -> int:
|
||||
"""Show new choice of buttons"""
|
||||
query = update.callback_query
|
||||
query.answer()
|
||||
@@ -149,9 +149,10 @@ def four(update: Update, _: CallbackContext) -> int:
|
||||
return FIRST
|
||||
|
||||
|
||||
def end(update: Update, _: CallbackContext) -> int:
|
||||
def end(update: Update, context: CallbackContext) -> int:
|
||||
"""Returns `ConversationHandler.END`, which tells the
|
||||
ConversationHandler that the conversation is over"""
|
||||
ConversationHandler that the conversation is over.
|
||||
"""
|
||||
query = update.callback_query
|
||||
query.answer()
|
||||
query.edit_message_text(text="See you next time!")
|
||||
@@ -159,6 +160,7 @@ def end(update: Update, _: CallbackContext) -> int:
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Run the bot."""
|
||||
# Create the Updater and pass it your bot's token.
|
||||
updater = Updater("TOKEN")
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
# pylint: disable=C0116
|
||||
# pylint: disable=C0116,W0613
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
"""
|
||||
@@ -152,14 +152,14 @@ def show_data(update: Update, context: CallbackContext) -> str:
|
||||
return SHOWING
|
||||
|
||||
|
||||
def stop(update: Update, _: CallbackContext) -> int:
|
||||
def stop(update: Update, context: CallbackContext) -> int:
|
||||
"""End Conversation by command."""
|
||||
update.message.reply_text('Okay, bye.')
|
||||
|
||||
return END
|
||||
|
||||
|
||||
def end(update: Update, _: CallbackContext) -> int:
|
||||
def end(update: Update, context: CallbackContext) -> int:
|
||||
"""End conversation from InlineKeyboardButton."""
|
||||
update.callback_query.answer()
|
||||
|
||||
@@ -170,7 +170,7 @@ def end(update: Update, _: CallbackContext) -> int:
|
||||
|
||||
|
||||
# Second level conversation callbacks
|
||||
def select_level(update: Update, _: CallbackContext) -> str:
|
||||
def select_level(update: Update, context: CallbackContext) -> str:
|
||||
"""Choose to add a parent or a child."""
|
||||
text = 'You may add a parent or a child. Also you can show the gathered data or go back.'
|
||||
buttons = [
|
||||
@@ -293,7 +293,7 @@ def end_describing(update: Update, context: CallbackContext) -> int:
|
||||
return END
|
||||
|
||||
|
||||
def stop_nested(update: Update, _: CallbackContext) -> str:
|
||||
def stop_nested(update: Update, context: CallbackContext) -> str:
|
||||
"""Completely end conversation from within nested conversation."""
|
||||
update.message.reply_text('Okay, bye.')
|
||||
|
||||
@@ -301,6 +301,7 @@ def stop_nested(update: Update, _: CallbackContext) -> str:
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Run the bot."""
|
||||
# Create the Updater and pass it your bot's token.
|
||||
updater = Updater("TOKEN")
|
||||
|
||||
|
||||
+23
-18
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
# pylint: disable=C0116
|
||||
# pylint: disable=C0116,W0613
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
"""
|
||||
@@ -23,7 +23,8 @@ logging.basicConfig(
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def msg(update: Update, _: CallbackContext) -> None:
|
||||
def msg(update: Update, context: CallbackContext) -> None:
|
||||
"""Downloads and prints the received passport data."""
|
||||
# Retrieve passport data
|
||||
passport_data = update.message.passport_data
|
||||
# If our nonce doesn't match what we think, this Update did not originate from us
|
||||
@@ -61,21 +62,24 @@ def msg(update: Update, _: CallbackContext) -> None:
|
||||
actual_file = file.get_file()
|
||||
print(actual_file)
|
||||
actual_file.download()
|
||||
if data.type in ('passport', 'driver_license', 'identity_card', 'internal_passport'):
|
||||
if data.front_side:
|
||||
front_file = data.front_side.get_file()
|
||||
print(data.type, front_file)
|
||||
front_file.download()
|
||||
if data.type in ('driver_license' and 'identity_card'):
|
||||
if data.reverse_side:
|
||||
reverse_file = data.reverse_side.get_file()
|
||||
print(data.type, reverse_file)
|
||||
reverse_file.download()
|
||||
if data.type in ('passport', 'driver_license', 'identity_card', 'internal_passport'):
|
||||
if data.selfie:
|
||||
selfie_file = data.selfie.get_file()
|
||||
print(data.type, selfie_file)
|
||||
selfie_file.download()
|
||||
if (
|
||||
data.type in ('passport', 'driver_license', 'identity_card', 'internal_passport')
|
||||
and data.front_side
|
||||
):
|
||||
front_file = data.front_side.get_file()
|
||||
print(data.type, front_file)
|
||||
front_file.download()
|
||||
if data.type in ('driver_license' and 'identity_card') and data.reverse_side:
|
||||
reverse_file = data.reverse_side.get_file()
|
||||
print(data.type, reverse_file)
|
||||
reverse_file.download()
|
||||
if (
|
||||
data.type in ('passport', 'driver_license', 'identity_card', 'internal_passport')
|
||||
and data.selfie
|
||||
):
|
||||
selfie_file = data.selfie.get_file()
|
||||
print(data.type, selfie_file)
|
||||
selfie_file.download()
|
||||
if data.type in (
|
||||
'passport',
|
||||
'driver_license',
|
||||
@@ -97,7 +101,8 @@ def msg(update: Update, _: CallbackContext) -> None:
|
||||
def main() -> None:
|
||||
"""Start the bot."""
|
||||
# Create the Updater and pass it your token and private key
|
||||
updater = Updater("TOKEN", private_key=open('private.key', 'rb').read())
|
||||
with open('private.key', 'rb') as private_key:
|
||||
updater = Updater("TOKEN", private_key=private_key.read())
|
||||
|
||||
# Get the dispatcher to register handlers
|
||||
dispatcher = updater.dispatcher
|
||||
|
||||
+16
-12
@@ -1,10 +1,8 @@
|
||||
#!/usr/bin/env python
|
||||
# pylint: disable=C0116
|
||||
# pylint: disable=C0116,W0613
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
"""
|
||||
Basic example for a bot that can receive payment from user.
|
||||
"""
|
||||
"""Basic example for a bot that can receive payment from user."""
|
||||
|
||||
import logging
|
||||
|
||||
@@ -27,7 +25,8 @@ logging.basicConfig(
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def start_callback(update: Update, _: CallbackContext) -> None:
|
||||
def start_callback(update: Update, context: CallbackContext) -> None:
|
||||
"""Displays info on how to use the bot."""
|
||||
msg = (
|
||||
"Use /shipping to get an invoice for shipping-payment, or /noshipping for an "
|
||||
"invoice without shipping."
|
||||
@@ -37,6 +36,7 @@ def start_callback(update: Update, _: CallbackContext) -> None:
|
||||
|
||||
|
||||
def start_with_shipping_callback(update: Update, context: CallbackContext) -> None:
|
||||
"""Sends an invoice with shipping-payment."""
|
||||
chat_id = update.message.chat_id
|
||||
title = "Payment Example"
|
||||
description = "Payment Example using python-telegram-bot"
|
||||
@@ -70,6 +70,7 @@ def start_with_shipping_callback(update: Update, context: CallbackContext) -> No
|
||||
|
||||
|
||||
def start_without_shipping_callback(update: Update, context: CallbackContext) -> None:
|
||||
"""Sends an invoice without shipping-payment."""
|
||||
chat_id = update.message.chat_id
|
||||
title = "Payment Example"
|
||||
description = "Payment Example using python-telegram-bot"
|
||||
@@ -90,7 +91,8 @@ def start_without_shipping_callback(update: Update, context: CallbackContext) ->
|
||||
)
|
||||
|
||||
|
||||
def shipping_callback(update: Update, _: CallbackContext) -> None:
|
||||
def shipping_callback(update: Update, context: CallbackContext) -> None:
|
||||
"""Answers the ShippingQuery with ShippingOptions"""
|
||||
query = update.shipping_query
|
||||
# check the payload, is this from your bot?
|
||||
if query.invoice_payload != 'Custom-Payload':
|
||||
@@ -98,17 +100,17 @@ def shipping_callback(update: Update, _: CallbackContext) -> None:
|
||||
query.answer(ok=False, error_message="Something went wrong...")
|
||||
return
|
||||
|
||||
options = list()
|
||||
# a single LabeledPrice
|
||||
options.append(ShippingOption('1', 'Shipping Option A', [LabeledPrice('A', 100)]))
|
||||
# an array of LabeledPrice objects
|
||||
# First option has a single LabeledPrice
|
||||
options = [ShippingOption('1', 'Shipping Option A', [LabeledPrice('A', 100)])]
|
||||
# second option has an array of LabeledPrice objects
|
||||
price_list = [LabeledPrice('B1', 150), LabeledPrice('B2', 200)]
|
||||
options.append(ShippingOption('2', 'Shipping Option B', price_list))
|
||||
query.answer(ok=True, shipping_options=options)
|
||||
|
||||
|
||||
# after (optional) shipping, it's the pre-checkout
|
||||
def precheckout_callback(update: Update, _: CallbackContext) -> None:
|
||||
def precheckout_callback(update: Update, context: CallbackContext) -> None:
|
||||
"""Answers the PreQecheckoutQuery"""
|
||||
query = update.pre_checkout_query
|
||||
# check the payload, is this from your bot?
|
||||
if query.invoice_payload != 'Custom-Payload':
|
||||
@@ -119,12 +121,14 @@ def precheckout_callback(update: Update, _: CallbackContext) -> None:
|
||||
|
||||
|
||||
# finally, after contacting the payment provider...
|
||||
def successful_payment_callback(update: Update, _: CallbackContext) -> None:
|
||||
def successful_payment_callback(update: Update, context: CallbackContext) -> None:
|
||||
"""Confirms the successful payment."""
|
||||
# do something after successfully receiving payment?
|
||||
update.message.reply_text("Thank you for your payment!")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Run the bot."""
|
||||
# Create the Updater and pass it your bot's token.
|
||||
updater = Updater("TOKEN")
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
# pylint: disable=C0116
|
||||
# pylint: disable=C0116,W0613
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
"""
|
||||
@@ -28,7 +28,6 @@ from telegram.ext import (
|
||||
CallbackContext,
|
||||
)
|
||||
|
||||
|
||||
# Enable logging
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||
@@ -47,15 +46,13 @@ markup = ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True)
|
||||
|
||||
|
||||
def facts_to_str(user_data: Dict[str, str]) -> str:
|
||||
facts = []
|
||||
|
||||
for key, value in user_data.items():
|
||||
facts.append(f'{key} - {value}')
|
||||
|
||||
"""Helper function for formatting the gathered user info."""
|
||||
facts = [f'{key} - {value}' for key, value in user_data.items()]
|
||||
return "\n".join(facts).join(['\n', '\n'])
|
||||
|
||||
|
||||
def start(update: Update, context: CallbackContext) -> int:
|
||||
"""Start the conversation, display any stored data and ask user for input."""
|
||||
reply_text = "Hi! My name is Doctor Botter."
|
||||
if context.user_data:
|
||||
reply_text += (
|
||||
@@ -73,6 +70,7 @@ def start(update: Update, context: CallbackContext) -> int:
|
||||
|
||||
|
||||
def regular_choice(update: Update, context: CallbackContext) -> int:
|
||||
"""Ask the user for info about the selected predefined choice."""
|
||||
text = update.message.text.lower()
|
||||
context.user_data['choice'] = text
|
||||
if context.user_data.get(text):
|
||||
@@ -86,7 +84,8 @@ def regular_choice(update: Update, context: CallbackContext) -> int:
|
||||
return TYPING_REPLY
|
||||
|
||||
|
||||
def custom_choice(update: Update, _: CallbackContext) -> int:
|
||||
def custom_choice(update: Update, context: CallbackContext) -> int:
|
||||
"""Ask the user for a description of a custom category."""
|
||||
update.message.reply_text(
|
||||
'Alright, please send me the category first, for example "Most impressive skill"'
|
||||
)
|
||||
@@ -95,6 +94,7 @@ def custom_choice(update: Update, _: CallbackContext) -> int:
|
||||
|
||||
|
||||
def received_information(update: Update, context: CallbackContext) -> int:
|
||||
"""Store info provided by user and ask for the next category."""
|
||||
text = update.message.text
|
||||
category = context.user_data['choice']
|
||||
context.user_data[category] = text.lower()
|
||||
@@ -103,8 +103,7 @@ def received_information(update: Update, context: CallbackContext) -> int:
|
||||
update.message.reply_text(
|
||||
"Neat! Just so you know, this is what you already told me:"
|
||||
f"{facts_to_str(context.user_data)}"
|
||||
"You can tell me more, or change your opinion on "
|
||||
"something.",
|
||||
"You can tell me more, or change your opinion on something.",
|
||||
reply_markup=markup,
|
||||
)
|
||||
|
||||
@@ -112,23 +111,26 @@ def received_information(update: Update, context: CallbackContext) -> int:
|
||||
|
||||
|
||||
def show_data(update: Update, context: CallbackContext) -> None:
|
||||
"""Display the gathered info."""
|
||||
update.message.reply_text(
|
||||
f"This is what you already told me: {facts_to_str(context.user_data)}"
|
||||
)
|
||||
|
||||
|
||||
def done(update: Update, context: CallbackContext) -> int:
|
||||
"""Display the gathered info and end the conversation."""
|
||||
if 'choice' in context.user_data:
|
||||
del context.user_data['choice']
|
||||
|
||||
update.message.reply_text(
|
||||
"I learned these facts about you:" f"{facts_to_str(context.user_data)}Until next time!",
|
||||
f"I learned these facts about you: {facts_to_str(context.user_data)}Until next time!",
|
||||
reply_markup=ReplyKeyboardRemove(),
|
||||
)
|
||||
return ConversationHandler.END
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Run the bot."""
|
||||
# Create the Updater and pass it your bot's token.
|
||||
persistence = PicklePersistence(filename='conversationbot')
|
||||
updater = Updater("TOKEN", persistence=persistence)
|
||||
|
||||
+6
-5
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
# pylint: disable=C0116
|
||||
# pylint: disable=C0116,W0613
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
"""
|
||||
@@ -34,7 +34,7 @@ logging.basicConfig(
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def start(update: Update, _: CallbackContext) -> None:
|
||||
def start(update: Update, context: CallbackContext) -> None:
|
||||
"""Inform user about what this bot can do"""
|
||||
update.message.reply_text(
|
||||
'Please select /poll to get a Poll, /quiz to get a Quiz or /preview'
|
||||
@@ -120,7 +120,7 @@ def receive_quiz_answer(update: Update, context: CallbackContext) -> None:
|
||||
context.bot.stop_poll(quiz_data["chat_id"], quiz_data["message_id"])
|
||||
|
||||
|
||||
def preview(update: Update, _: CallbackContext) -> None:
|
||||
def preview(update: Update, context: CallbackContext) -> None:
|
||||
"""Ask user to create a poll and display a preview of it"""
|
||||
# using this without a type lets the user chooses what he wants (quiz or poll)
|
||||
button = [[KeyboardButton("Press me!", request_poll=KeyboardButtonPollType())]]
|
||||
@@ -131,7 +131,7 @@ def preview(update: Update, _: CallbackContext) -> None:
|
||||
)
|
||||
|
||||
|
||||
def receive_poll(update: Update, _: CallbackContext) -> None:
|
||||
def receive_poll(update: Update, context: CallbackContext) -> None:
|
||||
"""On receiving polls, reply to it by a closed poll copying the received poll"""
|
||||
actual_poll = update.effective_message.poll
|
||||
# Only need to set the question and options, since all other parameters don't matter for
|
||||
@@ -145,12 +145,13 @@ def receive_poll(update: Update, _: CallbackContext) -> None:
|
||||
)
|
||||
|
||||
|
||||
def help_handler(update: Update, _: CallbackContext) -> None:
|
||||
def help_handler(update: Update, context: CallbackContext) -> None:
|
||||
"""Display a help message"""
|
||||
update.message.reply_text("Use /quiz, /poll or /preview to test this bot.")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Run bot."""
|
||||
# Create the Updater and pass it your bot's token.
|
||||
updater = Updater("TOKEN")
|
||||
dispatcher = updater.dispatcher
|
||||
|
||||
@@ -49,10 +49,11 @@ def echo(bot: telegram.Bot) -> None:
|
||||
for update in bot.get_updates(offset=UPDATE_ID, timeout=10):
|
||||
UPDATE_ID = update.update_id + 1
|
||||
|
||||
if update.message: # your bot can receive updates without messages
|
||||
if update.message.text: # not all messages contain text
|
||||
# Reply to the message
|
||||
update.message.reply_text(update.message.text)
|
||||
# your bot can receive updates without messages
|
||||
# and not all messages contain text
|
||||
if update.message and update.message.text:
|
||||
# Reply to the message
|
||||
update.message.reply_text(update.message.text)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
# pylint: disable=C0116
|
||||
# pylint: disable=C0116,W0613
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
"""
|
||||
@@ -33,7 +33,12 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
# Define a few command handlers. These usually take the two arguments update and
|
||||
# context. Error handlers also receive the raised TelegramError object in error.
|
||||
def start(update: Update, _: CallbackContext) -> None:
|
||||
# Best practice would be to replace context with an underscore,
|
||||
# since context is an unused local variable.
|
||||
# This being an example and not having context present confusing beginners,
|
||||
# we decided to have it present as context.
|
||||
def start(update: Update, context: CallbackContext) -> None:
|
||||
"""Sends explanation on how to use the bot."""
|
||||
update.message.reply_text('Hi! Use /set <seconds> to set a timer')
|
||||
|
||||
|
||||
|
||||
@@ -4,12 +4,12 @@ cryptography!=3.4,!=3.4.1,!=3.4.2,!=3.4.3
|
||||
pre-commit
|
||||
# Make sure that the versions specified here match the pre-commit settings!
|
||||
black==20.8b1
|
||||
flake8==3.9.1
|
||||
pylint==2.8.2
|
||||
flake8==3.9.2
|
||||
pylint==2.8.3
|
||||
mypy==0.812
|
||||
pyupgrade==2.13.0
|
||||
pyupgrade==2.19.1
|
||||
|
||||
pytest==6.2.3
|
||||
pytest==6.2.4
|
||||
|
||||
flaky
|
||||
beautifulsoup4
|
||||
|
||||
+2
-1
@@ -2,6 +2,7 @@
|
||||
# pre-commit hooks for pylint & mypy
|
||||
certifi
|
||||
# only telegram.ext: # Keep this line here; used in setup(-raw).py
|
||||
tornado>=5.1
|
||||
tornado>=6.1
|
||||
APScheduler==3.6.3
|
||||
pytz>=2018.6
|
||||
cachetools==4.2.2
|
||||
|
||||
@@ -43,6 +43,8 @@ omit =
|
||||
|
||||
[coverage:report]
|
||||
exclude_lines =
|
||||
pragma: no cover
|
||||
@overload
|
||||
if TYPE_CHECKING:
|
||||
|
||||
[mypy]
|
||||
@@ -58,7 +60,7 @@ ignore_errors = True
|
||||
|
||||
# Disable strict optional for telegram objects with class methods
|
||||
# We don't want to clutter the code with 'if self.bot is None: raise RuntimeError()'
|
||||
[mypy-telegram.callbackquery,telegram.chat,telegram.message,telegram.user,telegram.files.*,telegram.inline.inlinequery,telegram.payment.precheckoutquery,telegram.payment.shippingquery,telegram.passport.passportdata,telegram.passport.credentials,telegram.passport.passportfile,telegram.ext.filters]
|
||||
[mypy-telegram.callbackquery,telegram.chat,telegram.message,telegram.user,telegram.files.*,telegram.inline.inlinequery,telegram.payment.precheckoutquery,telegram.payment.shippingquery,telegram.passport.passportdata,telegram.passport.credentials,telegram.passport.passportfile,telegram.ext.filters,telegram.chatjoinrequest]
|
||||
strict_optional = False
|
||||
|
||||
# type hinting for asyncio in webhookhandler is a bit tricky because it depends on the OS
|
||||
|
||||
+35
-1
@@ -25,7 +25,16 @@ from .files.chatphoto import ChatPhoto
|
||||
from .chat import Chat
|
||||
from .chatlocation import ChatLocation
|
||||
from .chatinvitelink import ChatInviteLink
|
||||
from .chatmember import ChatMember
|
||||
from .chatjoinrequest import ChatJoinRequest
|
||||
from .chatmember import (
|
||||
ChatMember,
|
||||
ChatMemberOwner,
|
||||
ChatMemberAdministrator,
|
||||
ChatMemberMember,
|
||||
ChatMemberRestricted,
|
||||
ChatMemberLeft,
|
||||
ChatMemberBanned,
|
||||
)
|
||||
from .chatmemberupdated import ChatMemberUpdated
|
||||
from .chatpermissions import ChatPermissions
|
||||
from .files.photosize import PhotoSize
|
||||
@@ -153,6 +162,16 @@ from .passport.credentials import (
|
||||
FileCredentials,
|
||||
TelegramDecryptionError,
|
||||
)
|
||||
from .botcommandscope import (
|
||||
BotCommandScope,
|
||||
BotCommandScopeDefault,
|
||||
BotCommandScopeAllPrivateChats,
|
||||
BotCommandScopeAllGroupChats,
|
||||
BotCommandScopeAllChatAdministrators,
|
||||
BotCommandScopeChat,
|
||||
BotCommandScopeChatAdministrators,
|
||||
BotCommandScopeChatMember,
|
||||
)
|
||||
from .bot import Bot
|
||||
from .version import __version__, bot_api_version # noqa: F401
|
||||
|
||||
@@ -163,13 +182,28 @@ __all__ = ( # Keep this alphabetically ordered
|
||||
'Audio',
|
||||
'Bot',
|
||||
'BotCommand',
|
||||
'BotCommandScope',
|
||||
'BotCommandScopeAllChatAdministrators',
|
||||
'BotCommandScopeAllGroupChats',
|
||||
'BotCommandScopeAllPrivateChats',
|
||||
'BotCommandScopeChat',
|
||||
'BotCommandScopeChatAdministrators',
|
||||
'BotCommandScopeChatMember',
|
||||
'BotCommandScopeDefault',
|
||||
'CallbackGame',
|
||||
'CallbackQuery',
|
||||
'Chat',
|
||||
'ChatAction',
|
||||
'ChatInviteLink',
|
||||
'ChatJoinRequest',
|
||||
'ChatLocation',
|
||||
'ChatMember',
|
||||
'ChatMemberOwner',
|
||||
'ChatMemberAdministrator',
|
||||
'ChatMemberMember',
|
||||
'ChatMemberRestricted',
|
||||
'ChatMemberLeft',
|
||||
'ChatMemberBanned',
|
||||
'ChatMemberUpdated',
|
||||
'ChatPermissions',
|
||||
'ChatPhoto',
|
||||
|
||||
@@ -29,7 +29,7 @@ from .constants import BOT_API_VERSION
|
||||
|
||||
def _git_revision() -> Optional[str]:
|
||||
try:
|
||||
output = subprocess.check_output(
|
||||
output = subprocess.check_output( # skipcq: BAN-B607
|
||||
["git", "describe", "--long", "--tags"], stderr=subprocess.STDOUT
|
||||
)
|
||||
except (subprocess.SubprocessError, OSError):
|
||||
@@ -37,7 +37,7 @@ def _git_revision() -> Optional[str]:
|
||||
return output.decode().strip()
|
||||
|
||||
|
||||
def print_ver_info() -> None:
|
||||
def print_ver_info() -> None: # skipcq: PY-D0003
|
||||
git_revision = _git_revision()
|
||||
print(f'python-telegram-bot {telegram_ver}' + (f' ({git_revision})' if git_revision else ''))
|
||||
print(f'Bot API {BOT_API_VERSION}')
|
||||
@@ -46,7 +46,7 @@ def print_ver_info() -> None:
|
||||
print(f'Python {sys_version}')
|
||||
|
||||
|
||||
def main() -> None:
|
||||
def main() -> None: # skipcq: PY-D0003
|
||||
print_ver_info()
|
||||
|
||||
|
||||
|
||||
+47
-9
@@ -26,6 +26,7 @@ import warnings
|
||||
from typing import TYPE_CHECKING, List, Optional, Tuple, Type, TypeVar
|
||||
|
||||
from telegram.utils.types import JSONDict
|
||||
from telegram.utils.deprecate import set_new_attribute_deprecated
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Bot
|
||||
@@ -34,23 +35,41 @@ TO = TypeVar('TO', bound='TelegramObject', covariant=True)
|
||||
|
||||
|
||||
class TelegramObject:
|
||||
"""Base class for most telegram objects."""
|
||||
"""Base class for most Telegram objects."""
|
||||
|
||||
_id_attrs: Tuple[object, ...] = ()
|
||||
|
||||
# Adding slots reduces memory usage & allows for faster attribute access.
|
||||
# Only instance variables should be added to __slots__.
|
||||
# We add __dict__ here for backward compatibility & also to avoid repetition for subclasses.
|
||||
__slots__ = ('__dict__',)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return str(self.to_dict())
|
||||
|
||||
def __getitem__(self, item: str) -> object:
|
||||
return self.__dict__[item]
|
||||
return getattr(self, item, None)
|
||||
|
||||
def __setattr__(self, key: str, value: object) -> None:
|
||||
set_new_attribute_deprecated(self, key, value)
|
||||
|
||||
@staticmethod
|
||||
def parse_data(data: Optional[JSONDict]) -> Optional[JSONDict]:
|
||||
def _parse_data(data: Optional[JSONDict]) -> Optional[JSONDict]:
|
||||
return None if data is None else data.copy()
|
||||
|
||||
@classmethod
|
||||
def de_json(cls: Type[TO], data: Optional[JSONDict], bot: 'Bot') -> Optional[TO]:
|
||||
data = cls.parse_data(data)
|
||||
"""Converts JSON data to a Telegram object.
|
||||
|
||||
Args:
|
||||
data (Dict[:obj:`str`, ...]): The JSON data.
|
||||
bot (:class:`telegram.Bot`): The bot associated with this object.
|
||||
|
||||
Returns:
|
||||
The Telegram object.
|
||||
|
||||
"""
|
||||
data = cls._parse_data(data)
|
||||
|
||||
if data is None:
|
||||
return None
|
||||
@@ -61,28 +80,47 @@ class TelegramObject:
|
||||
|
||||
@classmethod
|
||||
def de_list(cls: Type[TO], data: Optional[List[JSONDict]], bot: 'Bot') -> List[Optional[TO]]:
|
||||
"""Converts JSON data to a list of Telegram objects.
|
||||
|
||||
Args:
|
||||
data (Dict[:obj:`str`, ...]): The JSON data.
|
||||
bot (:class:`telegram.Bot`): The bot associated with these objects.
|
||||
|
||||
Returns:
|
||||
A list of Telegram objects.
|
||||
|
||||
"""
|
||||
if not data:
|
||||
return []
|
||||
|
||||
return [cls.de_json(d, bot) for d in data]
|
||||
|
||||
def to_json(self) -> str:
|
||||
"""
|
||||
"""Gives a JSON representation of object.
|
||||
|
||||
Returns:
|
||||
:obj:`str`
|
||||
|
||||
"""
|
||||
|
||||
return json.dumps(self.to_dict())
|
||||
|
||||
def to_dict(self) -> JSONDict:
|
||||
"""Gives representation of object as :obj:`dict`.
|
||||
|
||||
Returns:
|
||||
:obj:`dict`
|
||||
"""
|
||||
data = {}
|
||||
|
||||
for key in iter(self.__dict__):
|
||||
# We want to get all attributes for the class, using self.__slots__ only includes the
|
||||
# attributes used by that class itself, and not its superclass(es). Hence we get its MRO
|
||||
# and then get their attributes. The `[:-2]` slice excludes the `object` class & the
|
||||
# TelegramObject class itself.
|
||||
attrs = {attr for cls in self.__class__.__mro__[:-2] for attr in cls.__slots__}
|
||||
for key in attrs:
|
||||
if key == 'bot' or key.startswith('_'):
|
||||
continue
|
||||
|
||||
value = self.__dict__[key]
|
||||
value = getattr(self, key, None)
|
||||
if value is not None:
|
||||
if hasattr(value, 'to_dict'):
|
||||
data[key] = value.to_dict()
|
||||
|
||||
+429
-71
@@ -21,6 +21,7 @@
|
||||
|
||||
import functools
|
||||
import logging
|
||||
import warnings
|
||||
from datetime import datetime
|
||||
|
||||
from typing import (
|
||||
@@ -56,6 +57,7 @@ from telegram import (
|
||||
Animation,
|
||||
Audio,
|
||||
BotCommand,
|
||||
BotCommandScope,
|
||||
Chat,
|
||||
ChatMember,
|
||||
ChatPermissions,
|
||||
@@ -89,6 +91,7 @@ from telegram import (
|
||||
)
|
||||
from telegram.constants import MAX_INLINE_QUERY_RESULTS
|
||||
from telegram.error import InvalidToken, TelegramError
|
||||
from telegram.utils.deprecate import TelegramDeprecationWarning
|
||||
from telegram.utils.helpers import (
|
||||
DEFAULT_NONE,
|
||||
DefaultValue,
|
||||
@@ -116,7 +119,7 @@ if TYPE_CHECKING:
|
||||
RT = TypeVar('RT')
|
||||
|
||||
|
||||
def log(
|
||||
def log( # skipcq: PY-D0003
|
||||
func: Callable[..., RT], *args: object, **kwargs: object # pylint: disable=W0613
|
||||
) -> Callable[..., RT]:
|
||||
logger = logging.getLogger(func.__module__)
|
||||
@@ -156,8 +159,25 @@ class Bot(TelegramObject):
|
||||
defaults (:class:`telegram.ext.Defaults`, optional): An object containing default values to
|
||||
be used if not set explicitly in the bot methods.
|
||||
|
||||
.. deprecated:: 13.6
|
||||
Passing :class:`telegram.ext.Defaults` to :class:`telegram.Bot` is deprecated. If
|
||||
you want to use :class:`telegram.ext.Defaults`, please use
|
||||
:class:`telegram.ext.ExtBot` instead.
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
'token',
|
||||
'base_url',
|
||||
'base_file_url',
|
||||
'private_key',
|
||||
'defaults',
|
||||
'_bot',
|
||||
'_commands',
|
||||
'_request',
|
||||
'logger',
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
token: str,
|
||||
@@ -173,6 +193,13 @@ class Bot(TelegramObject):
|
||||
# Gather default
|
||||
self.defaults = defaults
|
||||
|
||||
if self.defaults:
|
||||
warnings.warn(
|
||||
'Passing Defaults to telegram.Bot is deprecated. Use telegram.ext.ExtBot instead.',
|
||||
TelegramDeprecationWarning,
|
||||
stacklevel=3,
|
||||
)
|
||||
|
||||
if base_url is None:
|
||||
base_url = 'https://api.telegram.org/bot'
|
||||
|
||||
@@ -184,6 +211,7 @@ class Bot(TelegramObject):
|
||||
self._bot: Optional[User] = None
|
||||
self._commands: Optional[List[BotCommand]] = None
|
||||
self._request = request or Request()
|
||||
self.private_key = None
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
||||
if private_key:
|
||||
@@ -196,6 +224,14 @@ class Bot(TelegramObject):
|
||||
private_key, password=private_key_password, backend=default_backend()
|
||||
)
|
||||
|
||||
# The ext_bot argument is a little hack to get warnings handled correctly.
|
||||
# It's not very clean, but the warnings will be dropped at some point anyway.
|
||||
def __setattr__(self, key: str, value: object, ext_bot: bool = False) -> None:
|
||||
if issubclass(self.__class__, Bot) and self.__class__ is not Bot and not ext_bot:
|
||||
object.__setattr__(self, key, value)
|
||||
return
|
||||
super().__setattr__(key, value)
|
||||
|
||||
def _insert_defaults(
|
||||
self, data: Dict[str, object], timeout: ODVInput[float]
|
||||
) -> Optional[float]:
|
||||
@@ -301,7 +337,7 @@ class Bot(TelegramObject):
|
||||
return Message.de_json(result, self) # type: ignore[return-value, arg-type]
|
||||
|
||||
@property
|
||||
def request(self) -> Request:
|
||||
def request(self) -> Request: # skip-cq: PY-D0003
|
||||
return self._request
|
||||
|
||||
@staticmethod
|
||||
@@ -319,7 +355,6 @@ class Bot(TelegramObject):
|
||||
@property
|
||||
def bot(self) -> User:
|
||||
""":class:`telegram.User`: User instance for the bot as returned by :meth:`get_me`."""
|
||||
|
||||
if self._bot is None:
|
||||
self._bot = self.get_me()
|
||||
return self._bot
|
||||
@@ -327,54 +362,58 @@ class Bot(TelegramObject):
|
||||
@property
|
||||
def id(self) -> int: # pylint: disable=C0103
|
||||
""":obj:`int`: Unique identifier for this bot."""
|
||||
|
||||
return self.bot.id
|
||||
|
||||
@property
|
||||
def first_name(self) -> str:
|
||||
""":obj:`str`: Bot's first name."""
|
||||
|
||||
return self.bot.first_name
|
||||
|
||||
@property
|
||||
def last_name(self) -> str:
|
||||
""":obj:`str`: Optional. Bot's last name."""
|
||||
|
||||
return self.bot.last_name # type: ignore
|
||||
|
||||
@property
|
||||
def username(self) -> str:
|
||||
""":obj:`str`: Bot's username."""
|
||||
|
||||
return self.bot.username # type: ignore
|
||||
|
||||
@property
|
||||
def link(self) -> str:
|
||||
""":obj:`str`: Convenience property. Returns the t.me link of the bot."""
|
||||
|
||||
return f"https://t.me/{self.username}"
|
||||
|
||||
@property
|
||||
def can_join_groups(self) -> bool:
|
||||
""":obj:`bool`: Bot's :attr:`telegram.User.can_join_groups` attribute."""
|
||||
|
||||
return self.bot.can_join_groups # type: ignore
|
||||
|
||||
@property
|
||||
def can_read_all_group_messages(self) -> bool:
|
||||
""":obj:`bool`: Bot's :attr:`telegram.User.can_read_all_group_messages` attribute."""
|
||||
|
||||
return self.bot.can_read_all_group_messages # type: ignore
|
||||
|
||||
@property
|
||||
def supports_inline_queries(self) -> bool:
|
||||
""":obj:`bool`: Bot's :attr:`telegram.User.supports_inline_queries` attribute."""
|
||||
|
||||
return self.bot.supports_inline_queries # type: ignore
|
||||
|
||||
@property
|
||||
def commands(self) -> List[BotCommand]:
|
||||
"""List[:class:`BotCommand`]: Bot's commands."""
|
||||
"""
|
||||
List[:class:`BotCommand`]: Bot's commands as available in the default scope.
|
||||
|
||||
.. deprecated:: 13.7
|
||||
This property has been deprecated since there can be different commands available for
|
||||
different scopes.
|
||||
"""
|
||||
warnings.warn(
|
||||
"Bot.commands has been deprecated since there can be different command "
|
||||
"lists for different scopes.",
|
||||
TelegramDeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
if self._commands is None:
|
||||
self._commands = self.get_my_commands()
|
||||
@@ -383,7 +422,6 @@ class Bot(TelegramObject):
|
||||
@property
|
||||
def name(self) -> str:
|
||||
""":obj:`str`: Bot's @username."""
|
||||
|
||||
return f'@{self.username}'
|
||||
|
||||
@log
|
||||
@@ -1986,6 +2024,62 @@ class Bot(TelegramObject):
|
||||
|
||||
return result # type: ignore[return-value]
|
||||
|
||||
def _effective_inline_results( # pylint: disable=R0201
|
||||
self,
|
||||
results: Union[
|
||||
Sequence['InlineQueryResult'], Callable[[int], Optional[Sequence['InlineQueryResult']]]
|
||||
],
|
||||
next_offset: str = None,
|
||||
current_offset: str = None,
|
||||
) -> Tuple[Sequence['InlineQueryResult'], Optional[str]]:
|
||||
"""
|
||||
Builds the effective results from the results input.
|
||||
We make this a stand-alone method so tg.ext.ExtBot can wrap it.
|
||||
|
||||
Returns:
|
||||
Tuple of 1. the effective results and 2. correct the next_offset
|
||||
|
||||
"""
|
||||
if current_offset is not None and next_offset is not None:
|
||||
raise ValueError('`current_offset` and `next_offset` are mutually exclusive!')
|
||||
|
||||
if current_offset is not None:
|
||||
# Convert the string input to integer
|
||||
if current_offset == '':
|
||||
current_offset_int = 0
|
||||
else:
|
||||
current_offset_int = int(current_offset)
|
||||
|
||||
# for now set to empty string, stating that there are no more results
|
||||
# might change later
|
||||
next_offset = ''
|
||||
|
||||
if callable(results):
|
||||
callable_output = results(current_offset_int)
|
||||
if not callable_output:
|
||||
effective_results: Sequence['InlineQueryResult'] = []
|
||||
else:
|
||||
effective_results = callable_output
|
||||
# the callback *might* return more results on the next call, so we increment
|
||||
# the page count
|
||||
next_offset = str(current_offset_int + 1)
|
||||
else:
|
||||
if len(results) > (current_offset_int + 1) * MAX_INLINE_QUERY_RESULTS:
|
||||
# we expect more results for the next page
|
||||
next_offset_int = current_offset_int + 1
|
||||
next_offset = str(next_offset_int)
|
||||
effective_results = results[
|
||||
current_offset_int
|
||||
* MAX_INLINE_QUERY_RESULTS : next_offset_int
|
||||
* MAX_INLINE_QUERY_RESULTS
|
||||
]
|
||||
else:
|
||||
effective_results = results[current_offset_int * MAX_INLINE_QUERY_RESULTS :]
|
||||
else:
|
||||
effective_results = results # type: ignore[assignment]
|
||||
|
||||
return effective_results, next_offset
|
||||
|
||||
@log
|
||||
def answer_inline_query(
|
||||
self,
|
||||
@@ -2090,38 +2184,11 @@ class Bot(TelegramObject):
|
||||
else:
|
||||
res.input_message_content.disable_web_page_preview = None
|
||||
|
||||
if current_offset is not None and next_offset is not None:
|
||||
raise ValueError('`current_offset` and `next_offset` are mutually exclusive!')
|
||||
|
||||
if current_offset is not None:
|
||||
if current_offset == '':
|
||||
current_offset_int = 0
|
||||
else:
|
||||
current_offset_int = int(current_offset)
|
||||
|
||||
next_offset = ''
|
||||
|
||||
if callable(results):
|
||||
callable_output = results(current_offset_int)
|
||||
if not callable_output:
|
||||
effective_results: Sequence['InlineQueryResult'] = []
|
||||
else:
|
||||
effective_results = callable_output
|
||||
next_offset = str(current_offset_int + 1)
|
||||
else:
|
||||
if len(results) > (current_offset_int + 1) * MAX_INLINE_QUERY_RESULTS:
|
||||
next_offset_int = current_offset_int + 1
|
||||
next_offset = str(next_offset_int)
|
||||
effective_results = results[
|
||||
current_offset_int
|
||||
* MAX_INLINE_QUERY_RESULTS : next_offset_int
|
||||
* MAX_INLINE_QUERY_RESULTS
|
||||
]
|
||||
else:
|
||||
effective_results = results[current_offset_int * MAX_INLINE_QUERY_RESULTS :]
|
||||
else:
|
||||
effective_results = results # type: ignore[assignment]
|
||||
effective_results, next_offset = self._effective_inline_results(
|
||||
results=results, next_offset=next_offset, current_offset=current_offset
|
||||
)
|
||||
|
||||
# Apply defaults
|
||||
for result in effective_results:
|
||||
_set_defaults(result)
|
||||
|
||||
@@ -2259,11 +2326,43 @@ class Bot(TelegramObject):
|
||||
revoke_messages: bool = None,
|
||||
) -> bool:
|
||||
"""
|
||||
Use this method to kick a user from a group, supergroup or a channel. In the case of
|
||||
Deprecated, use :func:`~telegram.Bot.ban_chat_member` instead.
|
||||
|
||||
.. deprecated:: 13.7
|
||||
|
||||
"""
|
||||
warnings.warn(
|
||||
'`bot.kick_chat_member` is deprecated. Use `bot.ban_chat_member` instead.',
|
||||
TelegramDeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self.ban_chat_member(
|
||||
chat_id=chat_id,
|
||||
user_id=user_id,
|
||||
timeout=timeout,
|
||||
until_date=until_date,
|
||||
api_kwargs=api_kwargs,
|
||||
revoke_messages=revoke_messages,
|
||||
)
|
||||
|
||||
@log
|
||||
def ban_chat_member(
|
||||
self,
|
||||
chat_id: Union[str, int],
|
||||
user_id: Union[str, int],
|
||||
timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
until_date: Union[int, datetime] = None,
|
||||
api_kwargs: JSONDict = None,
|
||||
revoke_messages: bool = None,
|
||||
) -> bool:
|
||||
"""
|
||||
Use this method to ban a user from a group, supergroup or a channel. In the case of
|
||||
supergroups and channels, the user will not be able to return to the group on their own
|
||||
using invite links, etc., unless unbanned first. The bot must be an administrator in the
|
||||
chat for this to work and must have the appropriate admin rights.
|
||||
|
||||
.. versionadded:: 13.7
|
||||
|
||||
Args:
|
||||
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target group or username
|
||||
of the target supergroup or channel (in the format ``@channelusername``).
|
||||
@@ -2305,7 +2404,7 @@ class Bot(TelegramObject):
|
||||
if revoke_messages is not None:
|
||||
data['revoke_messages'] = revoke_messages
|
||||
|
||||
result = self._post('kickChatMember', data, timeout=timeout, api_kwargs=api_kwargs)
|
||||
result = self._post('banChatMember', data, timeout=timeout, api_kwargs=api_kwargs)
|
||||
|
||||
return result # type: ignore[return-value]
|
||||
|
||||
@@ -2606,7 +2705,6 @@ class Bot(TelegramObject):
|
||||
Raises:
|
||||
:class:`telegram.error.TelegramError`
|
||||
"""
|
||||
|
||||
if inline_message_id is None and (chat_id is None or message_id is None):
|
||||
raise ValueError(
|
||||
'edit_message_media: Both chat_id and message_id are required when '
|
||||
@@ -2758,18 +2856,22 @@ class Bot(TelegramObject):
|
||||
# * Long polling poses a different problem: the connection might have been dropped while
|
||||
# waiting for the server to return and there's no way of knowing the connection had been
|
||||
# dropped in real time.
|
||||
result = self._post(
|
||||
'getUpdates', data, timeout=float(read_latency) + float(timeout), api_kwargs=api_kwargs
|
||||
result = cast(
|
||||
List[JSONDict],
|
||||
self._post(
|
||||
'getUpdates',
|
||||
data,
|
||||
timeout=float(read_latency) + float(timeout),
|
||||
api_kwargs=api_kwargs,
|
||||
),
|
||||
)
|
||||
|
||||
if result:
|
||||
self.logger.debug(
|
||||
'Getting updates: %s', [u['update_id'] for u in result] # type: ignore
|
||||
)
|
||||
self.logger.debug('Getting updates: %s', [u['update_id'] for u in result])
|
||||
else:
|
||||
self.logger.debug('No new updates found.')
|
||||
|
||||
return [Update.de_json(u, self) for u in result] # type: ignore
|
||||
return Update.de_list(result, self) # type: ignore[return-value]
|
||||
|
||||
@log
|
||||
def set_webhook(
|
||||
@@ -3005,9 +3107,31 @@ class Bot(TelegramObject):
|
||||
chat_id: Union[str, int],
|
||||
timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
api_kwargs: JSONDict = None,
|
||||
) -> int:
|
||||
"""
|
||||
Deprecated, use :func:`~telegram.Bot.get_chat_member_count` instead.
|
||||
|
||||
.. deprecated:: 13.7
|
||||
"""
|
||||
warnings.warn(
|
||||
'`bot.get_chat_members_count` is deprecated. '
|
||||
'Use `bot.get_chat_member_count` instead.',
|
||||
TelegramDeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self.get_chat_member_count(chat_id=chat_id, timeout=timeout, api_kwargs=api_kwargs)
|
||||
|
||||
@log
|
||||
def get_chat_member_count(
|
||||
self,
|
||||
chat_id: Union[str, int],
|
||||
timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
api_kwargs: JSONDict = None,
|
||||
) -> int:
|
||||
"""Use this method to get the number of members in a chat.
|
||||
|
||||
.. versionadded:: 13.7
|
||||
|
||||
Args:
|
||||
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
|
||||
of the target supergroup or channel (in the format ``@channelusername``).
|
||||
@@ -3026,7 +3150,7 @@ class Bot(TelegramObject):
|
||||
"""
|
||||
data: JSONDict = {'chat_id': chat_id}
|
||||
|
||||
result = self._post('getChatMembersCount', data, timeout=timeout, api_kwargs=api_kwargs)
|
||||
result = self._post('getChatMemberCount', data, timeout=timeout, api_kwargs=api_kwargs)
|
||||
|
||||
return result # type: ignore[return-value]
|
||||
|
||||
@@ -3227,6 +3351,11 @@ class Bot(TelegramObject):
|
||||
Use this method to get data for high score tables. Will return the score of the specified
|
||||
user and several of their neighbors in a game.
|
||||
|
||||
Note:
|
||||
This method will currently return scores for the target user, plus two of their
|
||||
closest neighbors on each side. Will also return the top three users if the user and
|
||||
his neighbors are not among them. Please note that this behavior is subject to change.
|
||||
|
||||
Args:
|
||||
user_id (:obj:`int`): Target user id.
|
||||
chat_id (:obj:`int` | :obj:`str`, optional): Required if inline_message_id is not
|
||||
@@ -3425,7 +3554,7 @@ class Bot(TelegramObject):
|
||||
if is_flexible is not None:
|
||||
data['is_flexible'] = is_flexible
|
||||
if send_phone_number_to_provider is not None:
|
||||
data['send_phone_number_to_provider'] = send_email_to_provider
|
||||
data['send_phone_number_to_provider'] = send_phone_number_to_provider
|
||||
if send_email_to_provider is not None:
|
||||
data['send_email_to_provider'] = send_email_to_provider
|
||||
|
||||
@@ -3856,6 +3985,8 @@ class Bot(TelegramObject):
|
||||
member_limit: int = None,
|
||||
timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
api_kwargs: JSONDict = None,
|
||||
name: str = None,
|
||||
creates_join_request: bool = None,
|
||||
) -> ChatInviteLink:
|
||||
"""
|
||||
Use this method to create an additional invite link for a chat. The bot must be an
|
||||
@@ -3868,7 +3999,7 @@ class Bot(TelegramObject):
|
||||
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
|
||||
of the target channel (in the format ``@channelusername``).
|
||||
expire_date (:obj:`int` | :obj:`datetime.datetime`, optional): Date when the link will
|
||||
expire.
|
||||
expire. Integer input will be interpreted as Unix timestamp.
|
||||
For timezone naive :obj:`datetime.datetime` objects, the default timezone of the
|
||||
bot will be used.
|
||||
member_limit (:obj:`int`, optional): Maximum number of users that can be members of
|
||||
@@ -3878,6 +4009,14 @@ class Bot(TelegramObject):
|
||||
the connection pool).
|
||||
api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the
|
||||
Telegram API.
|
||||
name (:obj:`str`, optional): Invite link name; 0-32 characters.
|
||||
|
||||
.. versionadded:: 13.8
|
||||
creates_join_request (:obj:`bool`, optional): :obj:`True`, if users joining the chat
|
||||
via the link need to be approved by chat administrators.
|
||||
If :obj:`True`, ``member_limit`` can't be specified.
|
||||
|
||||
.. versionadded:: 13.8
|
||||
|
||||
Returns:
|
||||
:class:`telegram.ChatInviteLink`
|
||||
@@ -3886,6 +4025,11 @@ class Bot(TelegramObject):
|
||||
:class:`telegram.error.TelegramError`
|
||||
|
||||
"""
|
||||
if creates_join_request and member_limit:
|
||||
raise ValueError(
|
||||
"If `creates_join_request` is `True`, `member_limit` can't be specified."
|
||||
)
|
||||
|
||||
data: JSONDict = {
|
||||
'chat_id': chat_id,
|
||||
}
|
||||
@@ -3900,6 +4044,12 @@ class Bot(TelegramObject):
|
||||
if member_limit is not None:
|
||||
data['member_limit'] = member_limit
|
||||
|
||||
if name is not None:
|
||||
data['name'] = name
|
||||
|
||||
if creates_join_request is not None:
|
||||
data['creates_join_request'] = creates_join_request
|
||||
|
||||
result = self._post('createChatInviteLink', data, timeout=timeout, api_kwargs=api_kwargs)
|
||||
|
||||
return ChatInviteLink.de_json(result, self) # type: ignore[return-value, arg-type]
|
||||
@@ -3913,11 +4063,19 @@ class Bot(TelegramObject):
|
||||
member_limit: int = None,
|
||||
timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
api_kwargs: JSONDict = None,
|
||||
name: str = None,
|
||||
creates_join_request: bool = None,
|
||||
) -> ChatInviteLink:
|
||||
"""
|
||||
Use this method to edit a non-primary invite link created by the bot. The bot must be an
|
||||
administrator in the chat for this to work and must have the appropriate admin rights.
|
||||
|
||||
Note:
|
||||
Though not stated explicitly in the official docs, Telegram changes not only the
|
||||
optional parameters that are explicitly passed, but also replaces all other optional
|
||||
parameters to the default values. However, since not documented, this behaviour may
|
||||
change unbeknown to PTB.
|
||||
|
||||
.. versionadded:: 13.4
|
||||
|
||||
Args:
|
||||
@@ -3935,6 +4093,14 @@ class Bot(TelegramObject):
|
||||
the connection pool).
|
||||
api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the
|
||||
Telegram API.
|
||||
name (:obj:`str`, optional): Invite link name; 0-32 characters.
|
||||
|
||||
.. versionadded:: 13.8
|
||||
creates_join_request (:obj:`bool`, optional): :obj:`True`, if users joining the chat
|
||||
via the link need to be approved by chat administrators.
|
||||
If :obj:`True`, ``member_limit`` can't be specified.
|
||||
|
||||
.. versionadded:: 13.8
|
||||
|
||||
Returns:
|
||||
:class:`telegram.ChatInviteLink`
|
||||
@@ -3943,6 +4109,11 @@ class Bot(TelegramObject):
|
||||
:class:`telegram.error.TelegramError`
|
||||
|
||||
"""
|
||||
if creates_join_request and member_limit:
|
||||
raise ValueError(
|
||||
"If `creates_join_request` is `True`, `member_limit` can't be specified."
|
||||
)
|
||||
|
||||
data: JSONDict = {'chat_id': chat_id, 'invite_link': invite_link}
|
||||
|
||||
if expire_date is not None:
|
||||
@@ -3955,6 +4126,12 @@ class Bot(TelegramObject):
|
||||
if member_limit is not None:
|
||||
data['member_limit'] = member_limit
|
||||
|
||||
if name is not None:
|
||||
data['name'] = name
|
||||
|
||||
if creates_join_request is not None:
|
||||
data['creates_join_request'] = creates_join_request
|
||||
|
||||
result = self._post('editChatInviteLink', data, timeout=timeout, api_kwargs=api_kwargs)
|
||||
|
||||
return ChatInviteLink.de_json(result, self) # type: ignore[return-value, arg-type]
|
||||
@@ -3997,6 +4174,80 @@ class Bot(TelegramObject):
|
||||
|
||||
return ChatInviteLink.de_json(result, self) # type: ignore[return-value, arg-type]
|
||||
|
||||
@log
|
||||
def approve_chat_join_request(
|
||||
self,
|
||||
chat_id: Union[str, int],
|
||||
user_id: int,
|
||||
timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
api_kwargs: JSONDict = None,
|
||||
) -> bool:
|
||||
"""Use this method to approve a chat join request.
|
||||
|
||||
The bot must be an administrator in the chat for this to work and must have the
|
||||
:attr:`telegram.ChatPermissions.can_invite_users` administrator right.
|
||||
|
||||
.. versionadded:: 13.8
|
||||
|
||||
Args:
|
||||
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
|
||||
of the target channel (in the format ``@channelusername``).
|
||||
user_id (:obj:`int`): Unique identifier of the target user.
|
||||
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
|
||||
the read timeout from the server (instead of the one specified during creation of
|
||||
the connection pool).
|
||||
api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the
|
||||
Telegram API.
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
|
||||
Raises:
|
||||
:class:`telegram.error.TelegramError`
|
||||
"""
|
||||
data: JSONDict = {'chat_id': chat_id, 'user_id': user_id}
|
||||
|
||||
result = self._post('approveChatJoinRequest', data, timeout=timeout, api_kwargs=api_kwargs)
|
||||
|
||||
return result # type: ignore[return-value]
|
||||
|
||||
@log
|
||||
def decline_chat_join_request(
|
||||
self,
|
||||
chat_id: Union[str, int],
|
||||
user_id: int,
|
||||
timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
api_kwargs: JSONDict = None,
|
||||
) -> bool:
|
||||
"""Use this method to decline a chat join request.
|
||||
|
||||
The bot must be an administrator in the chat for this to work and must have the
|
||||
:attr:`telegram.ChatPermissions.can_invite_users` administrator right.
|
||||
|
||||
.. versionadded:: 13.8
|
||||
|
||||
Args:
|
||||
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
|
||||
of the target channel (in the format ``@channelusername``).
|
||||
user_id (:obj:`int`): Unique identifier of the target user.
|
||||
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
|
||||
the read timeout from the server (instead of the one specified during creation of
|
||||
the connection pool).
|
||||
api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the
|
||||
Telegram API.
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
|
||||
Raises:
|
||||
:class:`telegram.error.TelegramError`
|
||||
"""
|
||||
data: JSONDict = {'chat_id': chat_id, 'user_id': user_id}
|
||||
|
||||
result = self._post('declineChatJoinRequest', data, timeout=timeout, api_kwargs=api_kwargs)
|
||||
|
||||
return result # type: ignore[return-value]
|
||||
|
||||
@log
|
||||
def set_chat_photo(
|
||||
self,
|
||||
@@ -4257,7 +4508,6 @@ class Bot(TelegramObject):
|
||||
:class:`telegram.error.TelegramError`
|
||||
|
||||
"""
|
||||
|
||||
data: JSONDict = {'chat_id': chat_id}
|
||||
|
||||
return self._post( # type: ignore[return-value]
|
||||
@@ -4899,10 +5149,15 @@ class Bot(TelegramObject):
|
||||
|
||||
@log
|
||||
def get_my_commands(
|
||||
self, timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None
|
||||
self,
|
||||
timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
api_kwargs: JSONDict = None,
|
||||
scope: BotCommandScope = None,
|
||||
language_code: str = None,
|
||||
) -> List[BotCommand]:
|
||||
"""
|
||||
Use this method to get the current list of the bot's commands.
|
||||
Use this method to get the current list of the bot's commands for the given scope and user
|
||||
language.
|
||||
|
||||
Args:
|
||||
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
|
||||
@@ -4910,19 +5165,39 @@ class Bot(TelegramObject):
|
||||
the connection pool).
|
||||
api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the
|
||||
Telegram API.
|
||||
scope (:class:`telegram.BotCommandScope`, optional): A JSON-serialized object,
|
||||
describing scope of users. Defaults to :class:`telegram.BotCommandScopeDefault`.
|
||||
|
||||
.. versionadded:: 13.7
|
||||
|
||||
language_code (:obj:`str`, optional): A two-letter ISO 639-1 language code or an empty
|
||||
string.
|
||||
|
||||
.. versionadded:: 13.7
|
||||
|
||||
Returns:
|
||||
List[:class:`telegram.BotCommand]`: On success, the commands set for the bot
|
||||
List[:class:`telegram.BotCommand`]: On success, the commands set for the bot. An empty
|
||||
list is returned if commands are not set.
|
||||
|
||||
Raises:
|
||||
:class:`telegram.error.TelegramError`
|
||||
|
||||
"""
|
||||
result = self._post('getMyCommands', timeout=timeout, api_kwargs=api_kwargs)
|
||||
data: JSONDict = {}
|
||||
|
||||
self._commands = BotCommand.de_list(result, self) # type: ignore[assignment,arg-type]
|
||||
if scope:
|
||||
data['scope'] = scope.to_dict()
|
||||
|
||||
return self._commands # type: ignore[return-value]
|
||||
if language_code:
|
||||
data['language_code'] = language_code
|
||||
|
||||
result = self._post('getMyCommands', data, timeout=timeout, api_kwargs=api_kwargs)
|
||||
|
||||
if (scope is None or scope.type == scope.DEFAULT) and language_code is None:
|
||||
self._commands = BotCommand.de_list(result, self) # type: ignore[assignment,arg-type]
|
||||
return self._commands # type: ignore[return-value]
|
||||
|
||||
return BotCommand.de_list(result, self) # type: ignore[return-value,arg-type]
|
||||
|
||||
@log
|
||||
def set_my_commands(
|
||||
@@ -4930,9 +5205,13 @@ class Bot(TelegramObject):
|
||||
commands: List[Union[BotCommand, Tuple[str, str]]],
|
||||
timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
api_kwargs: JSONDict = None,
|
||||
scope: BotCommandScope = None,
|
||||
language_code: str = None,
|
||||
) -> bool:
|
||||
"""
|
||||
Use this method to change the list of the bot's commands.
|
||||
Use this method to change the list of the bot's commands. See the
|
||||
`Telegram docs <https://core.telegram.org/bots#commands>`_ for more details about bot
|
||||
commands.
|
||||
|
||||
Args:
|
||||
commands (List[:class:`BotCommand` | (:obj:`str`, :obj:`str`)]): A JSON-serialized list
|
||||
@@ -4943,9 +5222,20 @@ class Bot(TelegramObject):
|
||||
the connection pool).
|
||||
api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the
|
||||
Telegram API.
|
||||
scope (:class:`telegram.BotCommandScope`, optional): A JSON-serialized object,
|
||||
describing scope of users for which the commands are relevant. Defaults to
|
||||
:class:`telegram.BotCommandScopeDefault`.
|
||||
|
||||
.. versionadded:: 13.7
|
||||
|
||||
language_code (:obj:`str`, optional): A two-letter ISO 639-1 language code. If empty,
|
||||
commands will be applied to all users from the given scope, for whose language
|
||||
there are no dedicated commands.
|
||||
|
||||
.. versionadded:: 13.7
|
||||
|
||||
Returns:
|
||||
:obj:`True`: On success
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
|
||||
Raises:
|
||||
:class:`telegram.error.TelegramError`
|
||||
@@ -4955,11 +5245,68 @@ class Bot(TelegramObject):
|
||||
|
||||
data: JSONDict = {'commands': [c.to_dict() for c in cmds]}
|
||||
|
||||
if scope:
|
||||
data['scope'] = scope.to_dict()
|
||||
|
||||
if language_code:
|
||||
data['language_code'] = language_code
|
||||
|
||||
result = self._post('setMyCommands', data, timeout=timeout, api_kwargs=api_kwargs)
|
||||
|
||||
# Set commands. No need to check for outcome.
|
||||
# Set commands only for default scope. No need to check for outcome.
|
||||
# If request failed, we won't come this far
|
||||
self._commands = cmds
|
||||
if (scope is None or scope.type == scope.DEFAULT) and language_code is None:
|
||||
self._commands = cmds
|
||||
|
||||
return result # type: ignore[return-value]
|
||||
|
||||
@log
|
||||
def delete_my_commands(
|
||||
self,
|
||||
scope: BotCommandScope = None,
|
||||
language_code: str = None,
|
||||
api_kwargs: JSONDict = None,
|
||||
timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
) -> bool:
|
||||
"""
|
||||
Use this method to delete the list of the bot's commands for the given scope and user
|
||||
language. After deletion,
|
||||
`higher level commands <https://core.telegram.org/bots/api#determining-list-of-commands>`_
|
||||
will be shown to affected users.
|
||||
|
||||
.. versionadded:: 13.7
|
||||
|
||||
Args:
|
||||
scope (:class:`telegram.BotCommandScope`, optional): A JSON-serialized object,
|
||||
describing scope of users for which the commands are relevant. Defaults to
|
||||
:class:`telegram.BotCommandScopeDefault`.
|
||||
language_code (:obj:`str`, optional): A two-letter ISO 639-1 language code. If empty,
|
||||
commands will be applied to all users from the given scope, for whose language
|
||||
there are no dedicated commands.
|
||||
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
|
||||
the read timeout from the server (instead of the one specified during creation of
|
||||
the connection pool).
|
||||
api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the
|
||||
Telegram API.
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
|
||||
Raises:
|
||||
:class:`telegram.error.TelegramError`
|
||||
"""
|
||||
data: JSONDict = {}
|
||||
|
||||
if scope:
|
||||
data['scope'] = scope.to_dict()
|
||||
|
||||
if language_code:
|
||||
data['language_code'] = language_code
|
||||
|
||||
result = self._post('deleteMyCommands', data, timeout=timeout, api_kwargs=api_kwargs)
|
||||
|
||||
if (scope is None or scope.type == scope.DEFAULT) and language_code is None:
|
||||
self._commands = []
|
||||
|
||||
return result # type: ignore[return-value]
|
||||
|
||||
@@ -5071,7 +5418,7 @@ class Bot(TelegramObject):
|
||||
'disable_notification': disable_notification,
|
||||
'allow_sending_without_reply': allow_sending_without_reply,
|
||||
}
|
||||
if caption:
|
||||
if caption is not None:
|
||||
data['caption'] = caption
|
||||
if caption_entities:
|
||||
data['caption_entities'] = caption_entities
|
||||
@@ -5089,6 +5436,7 @@ class Bot(TelegramObject):
|
||||
return MessageId.de_json(result, self) # type: ignore[return-value, arg-type]
|
||||
|
||||
def to_dict(self) -> JSONDict:
|
||||
"""See :meth:`telegram.TelegramObject.to_dict`."""
|
||||
data: JSONDict = {'id': self.id, 'username': self.username, 'first_name': self.first_name}
|
||||
|
||||
if self.last_name:
|
||||
@@ -5149,6 +5497,8 @@ class Bot(TelegramObject):
|
||||
"""Alias for :meth:`get_user_profile_photos`"""
|
||||
getFile = get_file
|
||||
"""Alias for :meth:`get_file`"""
|
||||
banChatMember = ban_chat_member
|
||||
"""Alias for :meth:`ban_chat_member`"""
|
||||
kickChatMember = kick_chat_member
|
||||
"""Alias for :meth:`kick_chat_member`"""
|
||||
unbanChatMember = unban_chat_member
|
||||
@@ -5181,6 +5531,8 @@ class Bot(TelegramObject):
|
||||
"""Alias for :meth:`set_chat_sticker_set`"""
|
||||
deleteChatStickerSet = delete_chat_sticker_set
|
||||
"""Alias for :meth:`delete_chat_sticker_set`"""
|
||||
getChatMemberCount = get_chat_member_count
|
||||
"""Alias for :meth:`get_chat_member_count`"""
|
||||
getChatMembersCount = get_chat_members_count
|
||||
"""Alias for :meth:`get_chat_members_count`"""
|
||||
getWebhookInfo = get_webhook_info
|
||||
@@ -5206,11 +5558,15 @@ class Bot(TelegramObject):
|
||||
exportChatInviteLink = export_chat_invite_link
|
||||
"""Alias for :meth:`export_chat_invite_link`"""
|
||||
createChatInviteLink = create_chat_invite_link
|
||||
"""Alias for :attr:`create_chat_invite_link`"""
|
||||
"""Alias for :meth:`create_chat_invite_link`"""
|
||||
editChatInviteLink = edit_chat_invite_link
|
||||
"""Alias for :attr:`edit_chat_invite_link`"""
|
||||
"""Alias for :meth:`edit_chat_invite_link`"""
|
||||
revokeChatInviteLink = revoke_chat_invite_link
|
||||
"""Alias for :attr:`revoke_chat_invite_link`"""
|
||||
"""Alias for :meth:`revoke_chat_invite_link`"""
|
||||
approveChatJoinRequest = approve_chat_join_request
|
||||
"""Alias for :meth:`approve_chat_join_request`"""
|
||||
declineChatJoinRequest = decline_chat_join_request
|
||||
"""Alias for :meth:`decline_chat_join_request`"""
|
||||
setChatPhoto = set_chat_photo
|
||||
"""Alias for :meth:`set_chat_photo`"""
|
||||
deleteChatPhoto = delete_chat_photo
|
||||
@@ -5251,6 +5607,8 @@ class Bot(TelegramObject):
|
||||
"""Alias for :meth:`get_my_commands`"""
|
||||
setMyCommands = set_my_commands
|
||||
"""Alias for :meth:`set_my_commands`"""
|
||||
deleteMyCommands = delete_my_commands
|
||||
"""Alias for :meth:`delete_my_commands`"""
|
||||
logOut = log_out
|
||||
"""Alias for :meth:`log_out`"""
|
||||
copyMessage = copy_message
|
||||
|
||||
@@ -41,6 +41,8 @@ class BotCommand(TelegramObject):
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ('description', '_id_attrs', 'command')
|
||||
|
||||
def __init__(self, command: str, description: str, **_kwargs: Any):
|
||||
self.command = command
|
||||
self.description = description
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2021
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
# pylint: disable=W0622
|
||||
"""This module contains objects representing Telegram bot command scopes."""
|
||||
from typing import Any, Union, Optional, TYPE_CHECKING, Dict, Type
|
||||
|
||||
from telegram import TelegramObject, constants
|
||||
from telegram.utils.types import JSONDict
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Bot
|
||||
|
||||
|
||||
class BotCommandScope(TelegramObject):
|
||||
"""Base class for objects that represent the scope to which bot commands are applied.
|
||||
Currently, the following 7 scopes are supported:
|
||||
|
||||
* :class:`telegram.BotCommandScopeDefault`
|
||||
* :class:`telegram.BotCommandScopeAllPrivateChats`
|
||||
* :class:`telegram.BotCommandScopeAllGroupChats`
|
||||
* :class:`telegram.BotCommandScopeAllChatAdministrators`
|
||||
* :class:`telegram.BotCommandScopeChat`
|
||||
* :class:`telegram.BotCommandScopeChatAdministrators`
|
||||
* :class:`telegram.BotCommandScopeChatMember`
|
||||
|
||||
Objects of this class are comparable in terms of equality. Two objects of this class are
|
||||
considered equal, if their :attr:`type` is equal. For subclasses with additional attributes,
|
||||
the notion of equality is overridden.
|
||||
|
||||
Note:
|
||||
Please see the `official docs`_ on how Telegram determines which commands to display.
|
||||
|
||||
.. _`official docs`: https://core.telegram.org/bots/api#determining-list-of-commands
|
||||
|
||||
.. versionadded:: 13.7
|
||||
|
||||
Args:
|
||||
type (:obj:`str`): Scope type.
|
||||
|
||||
Attributes:
|
||||
type (:obj:`str`): Scope type.
|
||||
"""
|
||||
|
||||
__slots__ = ('type', '_id_attrs')
|
||||
|
||||
DEFAULT = constants.BOT_COMMAND_SCOPE_DEFAULT
|
||||
""":const:`telegram.constants.BOT_COMMAND_SCOPE_DEFAULT`"""
|
||||
ALL_PRIVATE_CHATS = constants.BOT_COMMAND_SCOPE_ALL_PRIVATE_CHATS
|
||||
""":const:`telegram.constants.BOT_COMMAND_SCOPE_ALL_PRIVATE_CHATS`"""
|
||||
ALL_GROUP_CHATS = constants.BOT_COMMAND_SCOPE_ALL_GROUP_CHATS
|
||||
""":const:`telegram.constants.BOT_COMMAND_SCOPE_ALL_GROUP_CHATS`"""
|
||||
ALL_CHAT_ADMINISTRATORS = constants.BOT_COMMAND_SCOPE_ALL_CHAT_ADMINISTRATORS
|
||||
""":const:`telegram.constants.BOT_COMMAND_SCOPE_ALL_CHAT_ADMINISTRATORS`"""
|
||||
CHAT = constants.BOT_COMMAND_SCOPE_CHAT
|
||||
""":const:`telegram.constants.BOT_COMMAND_SCOPE_CHAT`"""
|
||||
CHAT_ADMINISTRATORS = constants.BOT_COMMAND_SCOPE_CHAT_ADMINISTRATORS
|
||||
""":const:`telegram.constants.BOT_COMMAND_SCOPE_CHAT_ADMINISTRATORS`"""
|
||||
CHAT_MEMBER = constants.BOT_COMMAND_SCOPE_CHAT_MEMBER
|
||||
""":const:`telegram.constants.BOT_COMMAND_SCOPE_CHAT_MEMBER`"""
|
||||
|
||||
def __init__(self, type: str, **_kwargs: Any):
|
||||
self.type = type
|
||||
self._id_attrs = (self.type,)
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['BotCommandScope']:
|
||||
"""Converts JSON data to the appropriate :class:`BotCommandScope` object, i.e. takes
|
||||
care of selecting the correct subclass.
|
||||
|
||||
Args:
|
||||
data (Dict[:obj:`str`, ...]): The JSON data.
|
||||
bot (:class:`telegram.Bot`): The bot associated with this object.
|
||||
|
||||
Returns:
|
||||
The Telegram object.
|
||||
|
||||
"""
|
||||
data = cls._parse_data(data)
|
||||
|
||||
if not data:
|
||||
return None
|
||||
|
||||
_class_mapping: Dict[str, Type['BotCommandScope']] = {
|
||||
cls.DEFAULT: BotCommandScopeDefault,
|
||||
cls.ALL_PRIVATE_CHATS: BotCommandScopeAllPrivateChats,
|
||||
cls.ALL_GROUP_CHATS: BotCommandScopeAllGroupChats,
|
||||
cls.ALL_CHAT_ADMINISTRATORS: BotCommandScopeAllChatAdministrators,
|
||||
cls.CHAT: BotCommandScopeChat,
|
||||
cls.CHAT_ADMINISTRATORS: BotCommandScopeChatAdministrators,
|
||||
cls.CHAT_MEMBER: BotCommandScopeChatMember,
|
||||
}
|
||||
|
||||
if cls is BotCommandScope:
|
||||
return _class_mapping.get(data['type'], cls)(**data, bot=bot)
|
||||
return cls(**data)
|
||||
|
||||
|
||||
class BotCommandScopeDefault(BotCommandScope):
|
||||
"""Represents the default scope of bot commands. Default commands are used if no commands with
|
||||
a `narrower scope`_ are specified for the user.
|
||||
|
||||
.. _`narrower scope`: https://core.telegram.org/bots/api#determining-list-of-commands
|
||||
|
||||
.. versionadded:: 13.7
|
||||
|
||||
Attributes:
|
||||
type (:obj:`str`): Scope type :attr:`telegram.BotCommandScope.DEFAULT`.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def __init__(self, **_kwargs: Any):
|
||||
super().__init__(type=BotCommandScope.DEFAULT)
|
||||
|
||||
|
||||
class BotCommandScopeAllPrivateChats(BotCommandScope):
|
||||
"""Represents the scope of bot commands, covering all private chats.
|
||||
|
||||
.. versionadded:: 13.7
|
||||
|
||||
Attributes:
|
||||
type (:obj:`str`): Scope type :attr:`telegram.BotCommandScope.ALL_PRIVATE_CHATS`.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def __init__(self, **_kwargs: Any):
|
||||
super().__init__(type=BotCommandScope.ALL_PRIVATE_CHATS)
|
||||
|
||||
|
||||
class BotCommandScopeAllGroupChats(BotCommandScope):
|
||||
"""Represents the scope of bot commands, covering all group and supergroup chats.
|
||||
|
||||
.. versionadded:: 13.7
|
||||
|
||||
Attributes:
|
||||
type (:obj:`str`): Scope type :attr:`telegram.BotCommandScope.ALL_GROUP_CHATS`.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def __init__(self, **_kwargs: Any):
|
||||
super().__init__(type=BotCommandScope.ALL_GROUP_CHATS)
|
||||
|
||||
|
||||
class BotCommandScopeAllChatAdministrators(BotCommandScope):
|
||||
"""Represents the scope of bot commands, covering all group and supergroup chat administrators.
|
||||
|
||||
.. versionadded:: 13.7
|
||||
|
||||
Attributes:
|
||||
type (:obj:`str`): Scope type :attr:`telegram.BotCommandScope.ALL_CHAT_ADMINISTRATORS`.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def __init__(self, **_kwargs: Any):
|
||||
super().__init__(type=BotCommandScope.ALL_CHAT_ADMINISTRATORS)
|
||||
|
||||
|
||||
class BotCommandScopeChat(BotCommandScope):
|
||||
"""Represents the scope of bot commands, covering a specific chat.
|
||||
|
||||
Objects of this class are comparable in terms of equality. Two objects of this class are
|
||||
considered equal, if their :attr:`type` and :attr:`chat_id` are equal.
|
||||
|
||||
.. versionadded:: 13.7
|
||||
|
||||
Args:
|
||||
chat_id (:obj:`str` | :obj:`int`): Unique identifier for the target chat or username of the
|
||||
target supergroup (in the format ``@supergroupusername``)
|
||||
|
||||
Attributes:
|
||||
type (:obj:`str`): Scope type :attr:`telegram.BotCommandScope.CHAT`.
|
||||
chat_id (:obj:`str` | :obj:`int`): Unique identifier for the target chat or username of the
|
||||
target supergroup (in the format ``@supergroupusername``)
|
||||
"""
|
||||
|
||||
__slots__ = ('chat_id',)
|
||||
|
||||
def __init__(self, chat_id: Union[str, int], **_kwargs: Any):
|
||||
super().__init__(type=BotCommandScope.CHAT)
|
||||
self.chat_id = (
|
||||
chat_id if isinstance(chat_id, str) and chat_id.startswith('@') else int(chat_id)
|
||||
)
|
||||
self._id_attrs = (self.type, self.chat_id)
|
||||
|
||||
|
||||
class BotCommandScopeChatAdministrators(BotCommandScope):
|
||||
"""Represents the scope of bot commands, covering all administrators of a specific group or
|
||||
supergroup chat.
|
||||
|
||||
Objects of this class are comparable in terms of equality. Two objects of this class are
|
||||
considered equal, if their :attr:`type` and :attr:`chat_id` are equal.
|
||||
|
||||
.. versionadded:: 13.7
|
||||
|
||||
Args:
|
||||
chat_id (:obj:`str` | :obj:`int`): Unique identifier for the target chat or username of the
|
||||
target supergroup (in the format ``@supergroupusername``)
|
||||
|
||||
Attributes:
|
||||
type (:obj:`str`): Scope type :attr:`telegram.BotCommandScope.CHAT_ADMINISTRATORS`.
|
||||
chat_id (:obj:`str` | :obj:`int`): Unique identifier for the target chat or username of the
|
||||
target supergroup (in the format ``@supergroupusername``)
|
||||
"""
|
||||
|
||||
__slots__ = ('chat_id',)
|
||||
|
||||
def __init__(self, chat_id: Union[str, int], **_kwargs: Any):
|
||||
super().__init__(type=BotCommandScope.CHAT_ADMINISTRATORS)
|
||||
self.chat_id = (
|
||||
chat_id if isinstance(chat_id, str) and chat_id.startswith('@') else int(chat_id)
|
||||
)
|
||||
self._id_attrs = (self.type, self.chat_id)
|
||||
|
||||
|
||||
class BotCommandScopeChatMember(BotCommandScope):
|
||||
"""Represents the scope of bot commands, covering a specific member of a group or supergroup
|
||||
chat.
|
||||
|
||||
Objects of this class are comparable in terms of equality. Two objects of this class are
|
||||
considered equal, if their :attr:`type`, :attr:`chat_id` and :attr:`user_id` are equal.
|
||||
|
||||
.. versionadded:: 13.7
|
||||
|
||||
Args:
|
||||
chat_id (:obj:`str` | :obj:`int`): Unique identifier for the target chat or username of the
|
||||
target supergroup (in the format ``@supergroupusername``)
|
||||
user_id (:obj:`int`): Unique identifier of the target user.
|
||||
|
||||
Attributes:
|
||||
type (:obj:`str`): Scope type :attr:`telegram.BotCommandScope.CHAT_MEMBER`.
|
||||
chat_id (:obj:`str` | :obj:`int`): Unique identifier for the target chat or username of the
|
||||
target supergroup (in the format ``@supergroupusername``)
|
||||
user_id (:obj:`int`): Unique identifier of the target user.
|
||||
"""
|
||||
|
||||
__slots__ = ('chat_id', 'user_id')
|
||||
|
||||
def __init__(self, chat_id: Union[str, int], user_id: int, **_kwargs: Any):
|
||||
super().__init__(type=BotCommandScope.CHAT_MEMBER)
|
||||
self.chat_id = (
|
||||
chat_id if isinstance(chat_id, str) and chat_id.startswith('@') else int(chat_id)
|
||||
)
|
||||
self.user_id = int(user_id)
|
||||
self._id_attrs = (self.type, self.chat_id, self.user_id)
|
||||
+39
-22
@@ -53,6 +53,13 @@ class CallbackQuery(TelegramObject):
|
||||
until you call :attr:`answer`. It is, therefore, necessary to react
|
||||
by calling :attr:`telegram.Bot.answer_callback_query` even if no notification to the user
|
||||
is needed (e.g., without specifying any of the optional parameters).
|
||||
* If you're using :attr:`Bot.arbitrary_callback_data`, :attr:`data` may be an instance
|
||||
of :class:`telegram.ext.InvalidCallbackData`. This will be the case, if the data
|
||||
associated with the button triggering the :class:`telegram.CallbackQuery` was already
|
||||
deleted or if :attr:`data` was manipulated by a malicious client.
|
||||
|
||||
.. versionadded:: 13.6
|
||||
|
||||
|
||||
Args:
|
||||
id (:obj:`str`): Unique identifier for this query.
|
||||
@@ -77,7 +84,7 @@ class CallbackQuery(TelegramObject):
|
||||
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.
|
||||
data (:obj:`str` | :obj:`object`): Optional. Data associated with the callback button.
|
||||
inline_message_id (:obj:`str`): Optional. Identifier of the message sent via the bot in
|
||||
inline mode, that originated the query.
|
||||
game_short_name (:obj:`str`): Optional. Short name of a Game to be returned.
|
||||
@@ -85,6 +92,18 @@ class CallbackQuery(TelegramObject):
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
'bot',
|
||||
'game_short_name',
|
||||
'message',
|
||||
'chat_instance',
|
||||
'id',
|
||||
'from_user',
|
||||
'inline_message_id',
|
||||
'data',
|
||||
'_id_attrs',
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
id: str, # pylint: disable=W0622
|
||||
@@ -113,7 +132,8 @@ class CallbackQuery(TelegramObject):
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['CallbackQuery']:
|
||||
data = cls.parse_data(data)
|
||||
"""See :meth:`telegram.TelegramObject.de_json`."""
|
||||
data = cls._parse_data(data)
|
||||
|
||||
if not data:
|
||||
return None
|
||||
@@ -173,7 +193,7 @@ class CallbackQuery(TelegramObject):
|
||||
*args, **kwargs)
|
||||
|
||||
For the documentation of the arguments, please see
|
||||
:meth:`telegram.Bot.edit_message_text`.
|
||||
:meth:`telegram.Bot.edit_message_text` and :meth:`telegram.Message.edit_text`.
|
||||
|
||||
Returns:
|
||||
:class:`telegram.Message`: On success, if edited message is sent by the bot, the
|
||||
@@ -223,7 +243,7 @@ class CallbackQuery(TelegramObject):
|
||||
*args, **kwargs)
|
||||
|
||||
For the documentation of the arguments, please see
|
||||
:meth:`telegram.Bot.edit_message_caption`.
|
||||
:meth:`telegram.Bot.edit_message_caption` and :meth:`telegram.Message.edit_caption`.
|
||||
|
||||
Returns:
|
||||
:class:`telegram.Message`: On success, if edited message is sent by the bot, the
|
||||
@@ -275,7 +295,8 @@ class CallbackQuery(TelegramObject):
|
||||
)
|
||||
|
||||
For the documentation of the arguments, please see
|
||||
:meth:`telegram.Bot.edit_message_reply_markup`.
|
||||
:meth:`telegram.Bot.edit_message_reply_markup` and
|
||||
:meth:`telegram.Message.edit_reply_markup`.
|
||||
|
||||
Returns:
|
||||
:class:`telegram.Message`: On success, if edited message is sent by the bot, the
|
||||
@@ -314,7 +335,7 @@ class CallbackQuery(TelegramObject):
|
||||
*args, **kwargs)
|
||||
|
||||
For the documentation of the arguments, please see
|
||||
:meth:`telegram.Bot.edit_message_media`.
|
||||
:meth:`telegram.Bot.edit_message_media` and :meth:`telegram.Message.edit_media`.
|
||||
|
||||
Returns:
|
||||
:class:`telegram.Message`: On success, if edited message is sent by the bot, the
|
||||
@@ -362,7 +383,8 @@ class CallbackQuery(TelegramObject):
|
||||
)
|
||||
|
||||
For the documentation of the arguments, please see
|
||||
:meth:`telegram.Bot.edit_message_live_location`.
|
||||
:meth:`telegram.Bot.edit_message_live_location` and
|
||||
:meth:`telegram.Message.edit_live_location`.
|
||||
|
||||
Returns:
|
||||
:class:`telegram.Message`: On success, if edited message is sent by the bot, the
|
||||
@@ -414,7 +436,8 @@ class CallbackQuery(TelegramObject):
|
||||
)
|
||||
|
||||
For the documentation of the arguments, please see
|
||||
:meth:`telegram.Bot.stop_message_live_location`.
|
||||
:meth:`telegram.Bot.stop_message_live_location` and
|
||||
:meth:`telegram.Message.stop_live_location`.
|
||||
|
||||
Returns:
|
||||
:class:`telegram.Message`: On success, if edited message is sent by the bot, the
|
||||
@@ -455,7 +478,7 @@ class CallbackQuery(TelegramObject):
|
||||
*args, **kwargs)
|
||||
|
||||
For the documentation of the arguments, please see
|
||||
:meth:`telegram.Bot.set_game_score`.
|
||||
:meth:`telegram.Bot.set_game_score` and :meth:`telegram.Message.set_game_score`.
|
||||
|
||||
Returns:
|
||||
:class:`telegram.Message`: On success, if edited message is sent by the bot, the
|
||||
@@ -499,7 +522,7 @@ class CallbackQuery(TelegramObject):
|
||||
*args, **kwargs)
|
||||
|
||||
For the documentation of the arguments, please see
|
||||
:meth:`telegram.Bot.get_game_high_scores`.
|
||||
:meth:`telegram.Bot.get_game_high_scores` and :meth:`telegram.Message.get_game_high_score`.
|
||||
|
||||
Returns:
|
||||
List[:class:`telegram.GameHighScore`]
|
||||
@@ -530,7 +553,7 @@ class CallbackQuery(TelegramObject):
|
||||
update.callback_query.message.delete(*args, **kwargs)
|
||||
|
||||
For the documentation of the arguments, please see
|
||||
:meth:`telegram.Bot.delete_message`.
|
||||
:meth:`telegram.Message.delete`.
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
@@ -549,13 +572,10 @@ class CallbackQuery(TelegramObject):
|
||||
) -> bool:
|
||||
"""Shortcut for::
|
||||
|
||||
bot.pin_chat_message(chat_id=message.chat_id,
|
||||
message_id=message.message_id,
|
||||
*args,
|
||||
**kwargs)
|
||||
update.callback_query.message.pin(*args, **kwargs)
|
||||
|
||||
For the documentation of the arguments, please see
|
||||
:meth:`telegram.Bot.pin_chat_message`.
|
||||
:meth:`telegram.Message.pin`.
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
@@ -574,13 +594,10 @@ class CallbackQuery(TelegramObject):
|
||||
) -> bool:
|
||||
"""Shortcut for::
|
||||
|
||||
bot.unpin_chat_message(chat_id=message.chat_id,
|
||||
message_id=message.message_id,
|
||||
*args,
|
||||
**kwargs)
|
||||
update.callback_query.message.unpin(*args, **kwargs)
|
||||
|
||||
For the documentation of the arguments, please see
|
||||
:meth:`telegram.Bot.unpin_chat_message`.
|
||||
:meth:`telegram.Message.unpin`.
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
@@ -614,7 +631,7 @@ class CallbackQuery(TelegramObject):
|
||||
**kwargs)
|
||||
|
||||
For the documentation of the arguments, please see
|
||||
:meth:`telegram.Bot.copy_message`.
|
||||
:meth:`telegram.Message.copy`.
|
||||
|
||||
Returns:
|
||||
:class:`telegram.MessageId`: On success, returns the MessageId of the sent message.
|
||||
|
||||
+151
-23
@@ -18,11 +18,13 @@
|
||||
# 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 Chat."""
|
||||
import warnings
|
||||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING, List, Optional, ClassVar, Union, Tuple, Any
|
||||
|
||||
from telegram import ChatPhoto, TelegramObject, constants
|
||||
from telegram.utils.types import JSONDict, FileInput, ODVInput, DVInput
|
||||
from telegram.utils.deprecate import TelegramDeprecationWarning
|
||||
|
||||
from .chatpermissions import ChatPermissions
|
||||
from .chatlocation import ChatLocation
|
||||
@@ -143,6 +145,30 @@ class Chat(TelegramObject):
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
'bio',
|
||||
'id',
|
||||
'type',
|
||||
'last_name',
|
||||
'bot',
|
||||
'sticker_set_name',
|
||||
'slow_mode_delay',
|
||||
'location',
|
||||
'first_name',
|
||||
'permissions',
|
||||
'invite_link',
|
||||
'pinned_message',
|
||||
'description',
|
||||
'can_set_sticker_set',
|
||||
'username',
|
||||
'title',
|
||||
'photo',
|
||||
'linked_chat_id',
|
||||
'all_members_are_administrators',
|
||||
'message_auto_delete_time',
|
||||
'_id_attrs',
|
||||
)
|
||||
|
||||
SENDER: ClassVar[str] = constants.CHAT_SENDER
|
||||
""":const:`telegram.constants.CHAT_SENDER`
|
||||
|
||||
@@ -229,14 +255,16 @@ class Chat(TelegramObject):
|
||||
@property
|
||||
def link(self) -> Optional[str]:
|
||||
""":obj:`str`: Convenience property. If the chat has a :attr:`username`, returns a t.me
|
||||
link of the chat."""
|
||||
link of the chat.
|
||||
"""
|
||||
if self.username:
|
||||
return f"https://t.me/{self.username}"
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Chat']:
|
||||
data = cls.parse_data(data)
|
||||
"""See :meth:`telegram.TelegramObject.de_json`."""
|
||||
data = cls._parse_data(data)
|
||||
|
||||
if not data:
|
||||
return None
|
||||
@@ -258,7 +286,7 @@ class Chat(TelegramObject):
|
||||
For the documentation of the arguments, please see :meth:`telegram.Bot.leave_chat`.
|
||||
|
||||
Returns:
|
||||
:obj:`bool` If the action was sent successfully.
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
|
||||
"""
|
||||
return self.bot.leave_chat(
|
||||
@@ -292,19 +320,37 @@ class Chat(TelegramObject):
|
||||
|
||||
def get_members_count(
|
||||
self, timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None
|
||||
) -> int:
|
||||
"""
|
||||
Deprecated, use :func:`~telegram.Chat.get_member_count` instead.
|
||||
|
||||
.. deprecated:: 13.7
|
||||
"""
|
||||
warnings.warn(
|
||||
'`Chat.get_members_count` is deprecated. Use `Chat.get_member_count` instead.',
|
||||
TelegramDeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
return self.get_member_count(
|
||||
timeout=timeout,
|
||||
api_kwargs=api_kwargs,
|
||||
)
|
||||
|
||||
def get_member_count(
|
||||
self, timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None
|
||||
) -> int:
|
||||
"""Shortcut for::
|
||||
|
||||
bot.get_chat_members_count(update.effective_chat.id, *args, **kwargs)
|
||||
bot.get_chat_member_count(update.effective_chat.id, *args, **kwargs)
|
||||
|
||||
For the documentation of the arguments, please see
|
||||
:meth:`telegram.Bot.get_chat_members_count`.
|
||||
:meth:`telegram.Bot.get_chat_member_count`.
|
||||
|
||||
Returns:
|
||||
:obj:`int`
|
||||
|
||||
"""
|
||||
return self.bot.get_chat_members_count(
|
||||
return self.bot.get_chat_member_count(
|
||||
chat_id=self.id,
|
||||
timeout=timeout,
|
||||
api_kwargs=api_kwargs,
|
||||
@@ -340,24 +386,45 @@ class Chat(TelegramObject):
|
||||
until_date: Union[int, datetime] = None,
|
||||
api_kwargs: JSONDict = None,
|
||||
revoke_messages: bool = None,
|
||||
) -> bool:
|
||||
"""
|
||||
Deprecated, use :func:`~telegram.Chat.ban_member` instead.
|
||||
|
||||
.. deprecated:: 13.7
|
||||
"""
|
||||
warnings.warn(
|
||||
'`Chat.kick_member` is deprecated. Use `Chat.ban_member` instead.',
|
||||
TelegramDeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
return self.ban_member(
|
||||
user_id=user_id,
|
||||
timeout=timeout,
|
||||
until_date=until_date,
|
||||
api_kwargs=api_kwargs,
|
||||
revoke_messages=revoke_messages,
|
||||
)
|
||||
|
||||
def ban_member(
|
||||
self,
|
||||
user_id: Union[str, int],
|
||||
timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
until_date: Union[int, datetime] = None,
|
||||
api_kwargs: JSONDict = None,
|
||||
revoke_messages: bool = None,
|
||||
) -> bool:
|
||||
"""Shortcut for::
|
||||
|
||||
bot.kick_chat_member(update.effective_chat.id, *args, **kwargs)
|
||||
bot.ban_chat_member(update.effective_chat.id, *args, **kwargs)
|
||||
|
||||
For the documentation of the arguments, please see
|
||||
:meth:`telegram.Bot.kick_chat_member`.
|
||||
:meth:`telegram.Bot.ban_chat_member`.
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: If the action was sent successfully.
|
||||
|
||||
Note:
|
||||
This method will only work if the `All Members Are Admins` setting is off in the
|
||||
target group. Otherwise members may only be removed by the group's creator or by the
|
||||
member that added them.
|
||||
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
"""
|
||||
return self.bot.kick_chat_member(
|
||||
return self.bot.ban_chat_member(
|
||||
chat_id=self.id,
|
||||
user_id=user_id,
|
||||
timeout=timeout,
|
||||
@@ -380,7 +447,7 @@ class Chat(TelegramObject):
|
||||
For the documentation of the arguments, please see :meth:`telegram.Bot.unban_chat_member`.
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: If the action was sent successfully.
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
|
||||
"""
|
||||
return self.bot.unban_chat_member(
|
||||
@@ -418,7 +485,7 @@ class Chat(TelegramObject):
|
||||
.. versionadded:: 13.2
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: If the action was sent successfully.
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
|
||||
"""
|
||||
return self.bot.promote_chat_member(
|
||||
@@ -457,7 +524,7 @@ class Chat(TelegramObject):
|
||||
.. versionadded:: 13.2
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: If the action was sent successfully.
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
|
||||
"""
|
||||
return self.bot.restrict_chat_member(
|
||||
@@ -483,7 +550,7 @@ class Chat(TelegramObject):
|
||||
:meth:`telegram.Bot.set_chat_permissions`.
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: If the action was sent successfully.
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
|
||||
"""
|
||||
return self.bot.set_chat_permissions(
|
||||
@@ -508,7 +575,7 @@ class Chat(TelegramObject):
|
||||
:meth:`telegram.Bot.set_chat_administrator_custom_title`.
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: If the action was sent successfully.
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
|
||||
"""
|
||||
return self.bot.set_chat_administrator_custom_title(
|
||||
@@ -652,7 +719,7 @@ class Chat(TelegramObject):
|
||||
For the documentation of the arguments, please see :meth:`telegram.Bot.send_media_group`.
|
||||
|
||||
Returns:
|
||||
List[:class:`telegram.Message`:] On success, instance representing the message posted.
|
||||
List[:class:`telegram.Message`]: On success, instance representing the message posted.
|
||||
|
||||
"""
|
||||
return self.bot.send_media_group(
|
||||
@@ -1457,6 +1524,8 @@ class Chat(TelegramObject):
|
||||
member_limit: int = None,
|
||||
timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
api_kwargs: JSONDict = None,
|
||||
name: str = None,
|
||||
creates_join_request: bool = None,
|
||||
) -> 'ChatInviteLink':
|
||||
"""Shortcut for::
|
||||
|
||||
@@ -1467,6 +1536,10 @@ class Chat(TelegramObject):
|
||||
|
||||
.. versionadded:: 13.4
|
||||
|
||||
.. versionchanged:: 13.8
|
||||
Edited signature according to the changes of
|
||||
:meth:`telegram.Bot.create_chat_invite_link`.
|
||||
|
||||
Returns:
|
||||
:class:`telegram.ChatInviteLink`
|
||||
|
||||
@@ -1477,6 +1550,8 @@ class Chat(TelegramObject):
|
||||
member_limit=member_limit,
|
||||
timeout=timeout,
|
||||
api_kwargs=api_kwargs,
|
||||
name=name,
|
||||
creates_join_request=creates_join_request,
|
||||
)
|
||||
|
||||
def edit_invite_link(
|
||||
@@ -1486,6 +1561,8 @@ class Chat(TelegramObject):
|
||||
member_limit: int = None,
|
||||
timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
api_kwargs: JSONDict = None,
|
||||
name: str = None,
|
||||
creates_join_request: bool = None,
|
||||
) -> 'ChatInviteLink':
|
||||
"""Shortcut for::
|
||||
|
||||
@@ -1496,6 +1573,9 @@ class Chat(TelegramObject):
|
||||
|
||||
.. versionadded:: 13.4
|
||||
|
||||
.. versionchanged:: 13.8
|
||||
Edited signature according to the changes of :meth:`telegram.Bot.edit_chat_invite_link`.
|
||||
|
||||
Returns:
|
||||
:class:`telegram.ChatInviteLink`
|
||||
|
||||
@@ -1507,6 +1587,8 @@ class Chat(TelegramObject):
|
||||
member_limit=member_limit,
|
||||
timeout=timeout,
|
||||
api_kwargs=api_kwargs,
|
||||
name=name,
|
||||
creates_join_request=creates_join_request,
|
||||
)
|
||||
|
||||
def revoke_invite_link(
|
||||
@@ -1531,3 +1613,49 @@ class Chat(TelegramObject):
|
||||
return self.bot.revoke_chat_invite_link(
|
||||
chat_id=self.id, invite_link=invite_link, timeout=timeout, api_kwargs=api_kwargs
|
||||
)
|
||||
|
||||
def approve_join_request(
|
||||
self,
|
||||
user_id: int,
|
||||
timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
api_kwargs: JSONDict = None,
|
||||
) -> bool:
|
||||
"""Shortcut for::
|
||||
|
||||
bot.approve_chat_join_request(chat_id=update.effective_chat.id, *args, **kwargs)
|
||||
|
||||
For the documentation of the arguments, please see
|
||||
:meth:`telegram.Bot.approve_chat_join_request`.
|
||||
|
||||
.. versionadded:: 13.8
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
|
||||
"""
|
||||
return self.bot.approve_chat_join_request(
|
||||
chat_id=self.id, user_id=user_id, timeout=timeout, api_kwargs=api_kwargs
|
||||
)
|
||||
|
||||
def decline_join_request(
|
||||
self,
|
||||
user_id: int,
|
||||
timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
api_kwargs: JSONDict = None,
|
||||
) -> bool:
|
||||
"""Shortcut for::
|
||||
|
||||
bot.decline_chat_join_request(chat_id=update.effective_chat.id, *args, **kwargs)
|
||||
|
||||
For the documentation of the arguments, please see
|
||||
:meth:`telegram.Bot.decline_chat_join_request`.
|
||||
|
||||
.. versionadded:: 13.8
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
|
||||
"""
|
||||
return self.bot.decline_chat_join_request(
|
||||
chat_id=self.id, user_id=user_id, timeout=timeout, api_kwargs=api_kwargs
|
||||
)
|
||||
|
||||
+11
-4
@@ -20,19 +20,20 @@
|
||||
"""This module contains an object that represents a Telegram ChatAction."""
|
||||
from typing import ClassVar
|
||||
from telegram import constants
|
||||
from telegram.utils.deprecate import set_new_attribute_deprecated
|
||||
|
||||
|
||||
class ChatAction:
|
||||
"""Helper class to provide constants for different chat actions."""
|
||||
|
||||
__slots__ = ('__dict__',) # Adding __dict__ here since it doesn't subclass TGObject
|
||||
FIND_LOCATION: ClassVar[str] = constants.CHATACTION_FIND_LOCATION
|
||||
""":const:`telegram.constants.CHATACTION_FIND_LOCATION`"""
|
||||
RECORD_AUDIO: ClassVar[str] = constants.CHATACTION_RECORD_AUDIO
|
||||
""":const:`telegram.constants.CHATACTION_RECORD_AUDIO`
|
||||
|
||||
.. deprecated:: 13.5
|
||||
Deprecated by Telegram. Use :attr:`RECORD_VOICE` instead, as backwards
|
||||
compatibility is not guaranteed by Telegram.
|
||||
Deprecated by Telegram. Use :attr:`RECORD_VOICE` instead.
|
||||
"""
|
||||
RECORD_VOICE: ClassVar[str] = constants.CHATACTION_RECORD_VOICE
|
||||
""":const:`telegram.constants.CHATACTION_RECORD_VOICE`
|
||||
@@ -49,8 +50,7 @@ class ChatAction:
|
||||
""":const:`telegram.constants.CHATACTION_UPLOAD_AUDIO`
|
||||
|
||||
.. deprecated:: 13.5
|
||||
Deprecated by Telegram. Use :attr:`UPLOAD_VOICE` instead, as backwards
|
||||
compatibility is not guaranteed by Telegram.
|
||||
Deprecated by Telegram. Use :attr:`UPLOAD_VOICE` instead.
|
||||
"""
|
||||
UPLOAD_VOICE: ClassVar[str] = constants.CHATACTION_UPLOAD_VOICE
|
||||
""":const:`telegram.constants.CHATACTION_UPLOAD_VOICE`
|
||||
@@ -59,9 +59,16 @@ class ChatAction:
|
||||
"""
|
||||
UPLOAD_DOCUMENT: ClassVar[str] = constants.CHATACTION_UPLOAD_DOCUMENT
|
||||
""":const:`telegram.constants.CHATACTION_UPLOAD_DOCUMENT`"""
|
||||
CHOOSE_STICKER: ClassVar[str] = constants.CHATACTION_CHOOSE_STICKER
|
||||
""":const:`telegram.constants.CHOOSE_STICKER`
|
||||
|
||||
.. versionadded:: 13.8"""
|
||||
UPLOAD_PHOTO: ClassVar[str] = constants.CHATACTION_UPLOAD_PHOTO
|
||||
""":const:`telegram.constants.CHATACTION_UPLOAD_PHOTO`"""
|
||||
UPLOAD_VIDEO: ClassVar[str] = constants.CHATACTION_UPLOAD_VIDEO
|
||||
""":const:`telegram.constants.CHATACTION_UPLOAD_VIDEO`"""
|
||||
UPLOAD_VIDEO_NOTE: ClassVar[str] = constants.CHATACTION_UPLOAD_VIDEO_NOTE
|
||||
""":const:`telegram.constants.CHATACTION_UPLOAD_VIDEO_NOTE`"""
|
||||
|
||||
def __setattr__(self, key: str, value: object) -> None:
|
||||
set_new_attribute_deprecated(self, key, value)
|
||||
|
||||
@@ -46,6 +46,17 @@ class ChatInviteLink(TelegramObject):
|
||||
has been expired.
|
||||
member_limit (:obj:`int`, optional): Maximum number of users that can be members of the
|
||||
chat simultaneously after joining the chat via this invite link; 1-99999.
|
||||
name (:obj:`str`, optional): Invite link name.
|
||||
|
||||
.. versionadded:: 13.8
|
||||
creates_join_request (:obj:`bool`, optional): :obj:`True`, if users joining the chat via
|
||||
the link need to be approved by chat administrators.
|
||||
|
||||
.. versionadded:: 13.8
|
||||
pending_join_request_count (:obj:`int`, optional): Number of pending join requests
|
||||
created using this link.
|
||||
|
||||
.. versionadded:: 13.8
|
||||
|
||||
Attributes:
|
||||
invite_link (:obj:`str`): The invite link. If the link was created by another chat
|
||||
@@ -57,9 +68,33 @@ class ChatInviteLink(TelegramObject):
|
||||
has been expired.
|
||||
member_limit (:obj:`int`): Optional. Maximum number of users that can be members
|
||||
of the chat simultaneously after joining the chat via this invite link; 1-99999.
|
||||
name (:obj:`str`): Optional. Invite link name.
|
||||
|
||||
.. versionadded:: 13.8
|
||||
creates_join_request (:obj:`bool`): Optional. :obj:`True`, if users joining the chat via
|
||||
the link need to be approved by chat administrators.
|
||||
|
||||
.. versionadded:: 13.8
|
||||
pending_join_request_count (:obj:`int`): Optional. Number of pending join requests
|
||||
created using this link.
|
||||
|
||||
.. versionadded:: 13.8
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
'invite_link',
|
||||
'creator',
|
||||
'is_primary',
|
||||
'is_revoked',
|
||||
'expire_date',
|
||||
'member_limit',
|
||||
'name',
|
||||
'creates_join_request',
|
||||
'pending_join_request_count',
|
||||
'_id_attrs',
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
invite_link: str,
|
||||
@@ -68,6 +103,9 @@ class ChatInviteLink(TelegramObject):
|
||||
is_revoked: bool,
|
||||
expire_date: datetime.datetime = None,
|
||||
member_limit: int = None,
|
||||
name: str = None,
|
||||
creates_join_request: bool = None,
|
||||
pending_join_request_count: int = None,
|
||||
**_kwargs: Any,
|
||||
):
|
||||
# Required
|
||||
@@ -79,12 +117,17 @@ class ChatInviteLink(TelegramObject):
|
||||
# Optionals
|
||||
self.expire_date = expire_date
|
||||
self.member_limit = int(member_limit) if member_limit is not None else None
|
||||
|
||||
self.name = name
|
||||
self.creates_join_request = creates_join_request
|
||||
self.pending_join_request_count = (
|
||||
int(pending_join_request_count) if pending_join_request_count is not None else None
|
||||
)
|
||||
self._id_attrs = (self.invite_link, self.creator, self.is_primary, self.is_revoked)
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['ChatInviteLink']:
|
||||
data = cls.parse_data(data)
|
||||
"""See :meth:`telegram.TelegramObject.de_json`."""
|
||||
data = cls._parse_data(data)
|
||||
|
||||
if not data:
|
||||
return None
|
||||
@@ -95,6 +138,7 @@ class ChatInviteLink(TelegramObject):
|
||||
return cls(**data)
|
||||
|
||||
def to_dict(self) -> JSONDict:
|
||||
"""See :meth:`telegram.TelegramObject.to_dict`."""
|
||||
data = super().to_dict()
|
||||
|
||||
data['expire_date'] = to_timestamp(self.expire_date)
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2021
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains an object that represents a Telegram ChatJoinRequest."""
|
||||
import datetime
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
|
||||
from telegram import TelegramObject, User, Chat, ChatInviteLink
|
||||
from telegram.utils.helpers import from_timestamp, to_timestamp, DEFAULT_NONE
|
||||
from telegram.utils.types import JSONDict, ODVInput
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Bot
|
||||
|
||||
|
||||
class ChatJoinRequest(TelegramObject):
|
||||
"""This object represents a join request sent to a chat.
|
||||
|
||||
Objects of this class are comparable in terms of equality. Two objects of this class are
|
||||
considered equal, if their :attr:`chat`, :attr:`from_user` and :attr:`date` are equal.
|
||||
|
||||
.. versionadded:: 13.8
|
||||
|
||||
Args:
|
||||
chat (:class:`telegram.Chat`): Chat to which the request was sent.
|
||||
from_user (:class:`telegram.User`): User that sent the join request.
|
||||
date (:class:`datetime.datetime`): Date the request was sent.
|
||||
bio (:obj:`str`, optional): Bio of the user.
|
||||
invite_link (:class:`telegram.ChatInviteLink`, optional): Chat invite link that was used
|
||||
by the user to send the join request.
|
||||
bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods.
|
||||
|
||||
Attributes:
|
||||
chat (:class:`telegram.Chat`): Chat to which the request was sent.
|
||||
from_user (:class:`telegram.User`): User that sent the join request.
|
||||
date (:class:`datetime.datetime`): Date the request was sent.
|
||||
bio (:obj:`str`): Optional. Bio of the user.
|
||||
invite_link (:class:`telegram.ChatInviteLink`): Optional. Chat invite link that was used
|
||||
by the user to send the join request.
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
'chat',
|
||||
'from_user',
|
||||
'date',
|
||||
'bio',
|
||||
'invite_link',
|
||||
'bot',
|
||||
'_id_attrs',
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
chat: Chat,
|
||||
from_user: User,
|
||||
date: datetime.datetime,
|
||||
bio: str = None,
|
||||
invite_link: ChatInviteLink = None,
|
||||
bot: 'Bot' = None,
|
||||
**_kwargs: Any,
|
||||
):
|
||||
# Required
|
||||
self.chat = chat
|
||||
self.from_user = from_user
|
||||
self.date = date
|
||||
|
||||
# Optionals
|
||||
self.bio = bio
|
||||
self.invite_link = invite_link
|
||||
|
||||
self.bot = bot
|
||||
self._id_attrs = (self.chat, self.from_user, self.date)
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['ChatJoinRequest']:
|
||||
"""See :meth:`telegram.TelegramObject.de_json`."""
|
||||
data = cls._parse_data(data)
|
||||
|
||||
if not data:
|
||||
return None
|
||||
|
||||
data['chat'] = Chat.de_json(data.get('chat'), bot)
|
||||
data['from_user'] = User.de_json(data.get('from'), bot)
|
||||
data['date'] = from_timestamp(data.get('date', None))
|
||||
data['invite_link'] = ChatInviteLink.de_json(data.get('invite_link'), bot)
|
||||
|
||||
return cls(bot=bot, **data)
|
||||
|
||||
def to_dict(self) -> JSONDict:
|
||||
"""See :meth:`telegram.TelegramObject.to_dict`."""
|
||||
data = super().to_dict()
|
||||
|
||||
data['date'] = to_timestamp(self.date)
|
||||
|
||||
return data
|
||||
|
||||
def approve(
|
||||
self,
|
||||
timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
api_kwargs: JSONDict = None,
|
||||
) -> bool:
|
||||
"""Shortcut for::
|
||||
|
||||
bot.approve_chat_join_request(chat_id=update.effective_chat.id,
|
||||
user_id=update.effective_user.id, *args, **kwargs)
|
||||
|
||||
For the documentation of the arguments, please see
|
||||
:meth:`telegram.Bot.approve_chat_join_request`.
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
|
||||
"""
|
||||
return self.bot.approve_chat_join_request(
|
||||
chat_id=self.chat.id, user_id=self.from_user.id, timeout=timeout, api_kwargs=api_kwargs
|
||||
)
|
||||
|
||||
def decline(
|
||||
self,
|
||||
timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
api_kwargs: JSONDict = None,
|
||||
) -> bool:
|
||||
"""Shortcut for::
|
||||
|
||||
bot.decline_chat_join_request(chat_id=update.effective_chat.id,
|
||||
user_id=update.effective_user.id, *args, **kwargs)
|
||||
|
||||
For the documentation of the arguments, please see
|
||||
:meth:`telegram.Bot.decline_chat_join_request`.
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
|
||||
"""
|
||||
return self.bot.decline_chat_join_request(
|
||||
chat_id=self.chat.id, user_id=self.from_user.id, timeout=timeout, api_kwargs=api_kwargs
|
||||
)
|
||||
@@ -47,6 +47,8 @@ class ChatLocation(TelegramObject):
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ('location', '_id_attrs', 'address')
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
location: Location,
|
||||
@@ -60,7 +62,8 @@ class ChatLocation(TelegramObject):
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['ChatLocation']:
|
||||
data = cls.parse_data(data)
|
||||
"""See :meth:`telegram.TelegramObject.de_json`."""
|
||||
data = cls._parse_data(data)
|
||||
|
||||
if not data:
|
||||
return None
|
||||
|
||||
+495
-5
@@ -18,7 +18,7 @@
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains an object that represents a Telegram ChatMember."""
|
||||
import datetime
|
||||
from typing import TYPE_CHECKING, Any, Optional, ClassVar
|
||||
from typing import TYPE_CHECKING, Any, Optional, ClassVar, Dict, Type
|
||||
|
||||
from telegram import TelegramObject, User, constants
|
||||
from telegram.utils.helpers import from_timestamp, to_timestamp
|
||||
@@ -29,115 +29,267 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
class ChatMember(TelegramObject):
|
||||
"""This object contains information about one member of a chat.
|
||||
"""Base class for Telegram ChatMember Objects.
|
||||
Currently, the following 6 types of chat members are supported:
|
||||
|
||||
* :class:`telegram.ChatMemberOwner`
|
||||
* :class:`telegram.ChatMemberAdministrator`
|
||||
* :class:`telegram.ChatMemberMember`
|
||||
* :class:`telegram.ChatMemberRestricted`
|
||||
* :class:`telegram.ChatMemberLeft`
|
||||
* :class:`telegram.ChatMemberBanned`
|
||||
|
||||
Objects of this class are comparable in terms of equality. Two objects of this class are
|
||||
considered equal, if their :attr:`user` and :attr:`status` are equal.
|
||||
|
||||
Note:
|
||||
As of Bot API 5.3, :class:`ChatMember` is nothing but the base class for the subclasses
|
||||
listed above and is no longer returned directly by :meth:`~telegram.Bot.get_chat`.
|
||||
Therefore, most of the arguments and attributes were deprecated and you should no longer
|
||||
use :class:`ChatMember` directly.
|
||||
|
||||
Args:
|
||||
user (:class:`telegram.User`): Information about the user.
|
||||
status (:obj:`str`): The member's status in the chat. Can be 'creator', 'administrator',
|
||||
'member', 'restricted', 'left' or 'kicked'.
|
||||
status (:obj:`str`): The member's status in the chat. Can be
|
||||
:attr:`~telegram.ChatMember.ADMINISTRATOR`, :attr:`~telegram.ChatMember.CREATOR`,
|
||||
:attr:`~telegram.ChatMember.KICKED`, :attr:`~telegram.ChatMember.LEFT`,
|
||||
:attr:`~telegram.ChatMember.MEMBER` or :attr:`~telegram.ChatMember.RESTRICTED`.
|
||||
custom_title (:obj:`str`, optional): Owner and administrators only.
|
||||
Custom title for this user.
|
||||
|
||||
.. deprecated:: 13.7
|
||||
|
||||
is_anonymous (:obj:`bool`, optional): Owner and administrators only. :obj:`True`, if the
|
||||
user's presence in the chat is hidden.
|
||||
|
||||
.. deprecated:: 13.7
|
||||
|
||||
until_date (:class:`datetime.datetime`, optional): Restricted and kicked only. Date when
|
||||
restrictions will be lifted for this user.
|
||||
|
||||
.. deprecated:: 13.7
|
||||
|
||||
can_be_edited (:obj:`bool`, optional): Administrators only. :obj:`True`, if the bot is
|
||||
allowed to edit administrator privileges of that user.
|
||||
|
||||
.. deprecated:: 13.7
|
||||
|
||||
can_manage_chat (:obj:`bool`, optional): Administrators only. :obj:`True`, if the
|
||||
administrator can access the chat event log, chat statistics, message statistics in
|
||||
channels, see channel members, see anonymous administrators in supergroups and ignore
|
||||
slow mode. Implied by any other administrator privilege.
|
||||
|
||||
.. versionadded:: 13.4
|
||||
.. deprecated:: 13.7
|
||||
|
||||
can_manage_voice_chats (:obj:`bool`, optional): Administrators only. :obj:`True`, if the
|
||||
administrator can manage voice chats.
|
||||
|
||||
.. versionadded:: 13.4
|
||||
.. deprecated:: 13.7
|
||||
|
||||
can_change_info (:obj:`bool`, optional): Administrators and restricted only. :obj:`True`,
|
||||
if the user can change the chat title, photo and other settings.
|
||||
|
||||
.. deprecated:: 13.7
|
||||
|
||||
can_post_messages (:obj:`bool`, optional): Administrators only. :obj:`True`, if the
|
||||
administrator can post in the channel, channels only.
|
||||
|
||||
.. deprecated:: 13.7
|
||||
|
||||
can_edit_messages (:obj:`bool`, optional): Administrators only. :obj:`True`, if the
|
||||
administrator can edit messages of other users and can pin messages; channels only.
|
||||
|
||||
.. deprecated:: 13.7
|
||||
|
||||
can_delete_messages (:obj:`bool`, optional): Administrators only. :obj:`True`, if the
|
||||
administrator can delete messages of other users.
|
||||
|
||||
.. deprecated:: 13.7
|
||||
|
||||
can_invite_users (:obj:`bool`, optional): Administrators and restricted only. :obj:`True`,
|
||||
if the user can invite new users to the chat.
|
||||
|
||||
.. deprecated:: 13.7
|
||||
|
||||
can_restrict_members (:obj:`bool`, optional): Administrators only. :obj:`True`, if the
|
||||
administrator can restrict, ban or unban chat members.
|
||||
|
||||
.. deprecated:: 13.7
|
||||
|
||||
can_pin_messages (:obj:`bool`, optional): Administrators and restricted only. :obj:`True`,
|
||||
if the user can pin messages, groups and supergroups only.
|
||||
|
||||
.. deprecated:: 13.7
|
||||
|
||||
can_promote_members (:obj:`bool`, optional): Administrators only. :obj:`True`, if the
|
||||
administrator can add new administrators with a subset of his own privileges or demote
|
||||
administrators that he has promoted, directly or indirectly (promoted by administrators
|
||||
that were appointed by the user).
|
||||
|
||||
.. deprecated:: 13.7
|
||||
|
||||
is_member (:obj:`bool`, optional): Restricted only. :obj:`True`, if the user is a member of
|
||||
the chat at the moment of the request.
|
||||
|
||||
.. deprecated:: 13.7
|
||||
|
||||
can_send_messages (:obj:`bool`, optional): Restricted only. :obj:`True`, if the user can
|
||||
send text messages, contacts, locations and venues.
|
||||
|
||||
.. deprecated:: 13.7
|
||||
|
||||
can_send_media_messages (:obj:`bool`, optional): Restricted only. :obj:`True`, if the user
|
||||
can send audios, documents, photos, videos, video notes and voice notes.
|
||||
|
||||
.. deprecated:: 13.7
|
||||
|
||||
can_send_polls (:obj:`bool`, optional): Restricted only. :obj:`True`, if the user is
|
||||
allowed to send polls.
|
||||
|
||||
.. deprecated:: 13.7
|
||||
|
||||
can_send_other_messages (:obj:`bool`, optional): Restricted only. :obj:`True`, if the user
|
||||
can send animations, games, stickers and use inline bots.
|
||||
|
||||
.. deprecated:: 13.7
|
||||
|
||||
can_add_web_page_previews (:obj:`bool`, optional): Restricted only. :obj:`True`, if user
|
||||
may add web page previews to his messages.
|
||||
|
||||
.. deprecated:: 13.7
|
||||
|
||||
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.
|
||||
|
||||
.. deprecated:: 13.7
|
||||
|
||||
is_anonymous (:obj:`bool`): Optional. :obj:`True`, if the user's presence in the chat is
|
||||
hidden.
|
||||
|
||||
.. deprecated:: 13.7
|
||||
|
||||
until_date (:class:`datetime.datetime`): Optional. Date when restrictions will be lifted
|
||||
for this user.
|
||||
|
||||
.. deprecated:: 13.7
|
||||
|
||||
can_be_edited (:obj:`bool`): Optional. If the bot is allowed to edit administrator
|
||||
privileges of that user.
|
||||
|
||||
.. deprecated:: 13.7
|
||||
|
||||
can_manage_chat (:obj:`bool`): Optional. If the administrator can access the chat event
|
||||
log, chat statistics, message statistics in channels, see channel members, see
|
||||
anonymous administrators in supergroups and ignore slow mode.
|
||||
|
||||
.. versionadded:: 13.4
|
||||
.. deprecated:: 13.7
|
||||
|
||||
can_manage_voice_chats (:obj:`bool`): Optional. if the administrator can manage
|
||||
voice chats.
|
||||
|
||||
.. versionadded:: 13.4
|
||||
.. deprecated:: 13.7
|
||||
|
||||
can_change_info (:obj:`bool`): Optional. If the user can change the chat title, photo and
|
||||
other settings.
|
||||
|
||||
.. deprecated:: 13.7
|
||||
|
||||
can_post_messages (:obj:`bool`): Optional. If the administrator can post in the channel.
|
||||
|
||||
.. deprecated:: 13.7
|
||||
|
||||
can_edit_messages (:obj:`bool`): Optional. If the administrator can edit messages of other
|
||||
users.
|
||||
|
||||
.. deprecated:: 13.7
|
||||
|
||||
can_delete_messages (:obj:`bool`): Optional. If the administrator can delete messages of
|
||||
other users.
|
||||
|
||||
.. deprecated:: 13.7
|
||||
|
||||
can_invite_users (:obj:`bool`): Optional. If the user can invite new users to the chat.
|
||||
|
||||
.. deprecated:: 13.7
|
||||
|
||||
can_restrict_members (:obj:`bool`): Optional. If the administrator can restrict, ban or
|
||||
unban chat members.
|
||||
|
||||
.. deprecated:: 13.7
|
||||
|
||||
can_pin_messages (:obj:`bool`): Optional. If the user can pin messages.
|
||||
|
||||
.. deprecated:: 13.7
|
||||
|
||||
can_promote_members (:obj:`bool`): Optional. If the administrator can add new
|
||||
administrators.
|
||||
|
||||
.. deprecated:: 13.7
|
||||
|
||||
is_member (:obj:`bool`): Optional. Restricted only. :obj:`True`, if the user is a member of
|
||||
the chat at the moment of the request.
|
||||
|
||||
.. deprecated:: 13.7
|
||||
|
||||
can_send_messages (:obj:`bool`): Optional. If the user can send text messages, contacts,
|
||||
locations and venues.
|
||||
|
||||
.. deprecated:: 13.7
|
||||
|
||||
can_send_media_messages (:obj:`bool`): Optional. If the user can send media messages,
|
||||
implies can_send_messages.
|
||||
|
||||
.. deprecated:: 13.7
|
||||
|
||||
can_send_polls (:obj:`bool`): Optional. :obj:`True`, if the user is allowed to
|
||||
send polls.
|
||||
|
||||
.. deprecated:: 13.7
|
||||
|
||||
can_send_other_messages (:obj:`bool`): Optional. If the user can send animations, games,
|
||||
stickers and use inline bots, implies can_send_media_messages.
|
||||
|
||||
.. deprecated:: 13.7
|
||||
|
||||
can_add_web_page_previews (:obj:`bool`): Optional. If user may add web page previews to his
|
||||
messages, implies can_send_media_messages
|
||||
|
||||
.. deprecated:: 13.7
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
'is_member',
|
||||
'can_restrict_members',
|
||||
'can_delete_messages',
|
||||
'custom_title',
|
||||
'can_be_edited',
|
||||
'can_post_messages',
|
||||
'can_send_messages',
|
||||
'can_edit_messages',
|
||||
'can_send_media_messages',
|
||||
'is_anonymous',
|
||||
'can_add_web_page_previews',
|
||||
'can_send_other_messages',
|
||||
'can_invite_users',
|
||||
'can_send_polls',
|
||||
'user',
|
||||
'can_promote_members',
|
||||
'status',
|
||||
'can_change_info',
|
||||
'can_pin_messages',
|
||||
'can_manage_chat',
|
||||
'can_manage_voice_chats',
|
||||
'until_date',
|
||||
'_id_attrs',
|
||||
)
|
||||
|
||||
ADMINISTRATOR: ClassVar[str] = constants.CHATMEMBER_ADMINISTRATOR
|
||||
""":const:`telegram.constants.CHATMEMBER_ADMINISTRATOR`"""
|
||||
CREATOR: ClassVar[str] = constants.CHATMEMBER_CREATOR
|
||||
@@ -207,7 +359,8 @@ class ChatMember(TelegramObject):
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['ChatMember']:
|
||||
data = cls.parse_data(data)
|
||||
"""See :meth:`telegram.TelegramObject.de_json`."""
|
||||
data = cls._parse_data(data)
|
||||
|
||||
if not data:
|
||||
return None
|
||||
@@ -215,11 +368,348 @@ class ChatMember(TelegramObject):
|
||||
data['user'] = User.de_json(data.get('user'), bot)
|
||||
data['until_date'] = from_timestamp(data.get('until_date', None))
|
||||
|
||||
_class_mapping: Dict[str, Type['ChatMember']] = {
|
||||
cls.CREATOR: ChatMemberOwner,
|
||||
cls.ADMINISTRATOR: ChatMemberAdministrator,
|
||||
cls.MEMBER: ChatMemberMember,
|
||||
cls.RESTRICTED: ChatMemberRestricted,
|
||||
cls.LEFT: ChatMemberLeft,
|
||||
cls.KICKED: ChatMemberBanned,
|
||||
}
|
||||
|
||||
if cls is ChatMember:
|
||||
return _class_mapping.get(data['status'], cls)(**data, bot=bot)
|
||||
return cls(**data)
|
||||
|
||||
def to_dict(self) -> JSONDict:
|
||||
"""See :meth:`telegram.TelegramObject.to_dict`."""
|
||||
data = super().to_dict()
|
||||
|
||||
data['until_date'] = to_timestamp(self.until_date)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class ChatMemberOwner(ChatMember):
|
||||
"""
|
||||
Represents a chat member that owns the chat
|
||||
and has all administrator privileges.
|
||||
|
||||
.. versionadded:: 13.7
|
||||
|
||||
Args:
|
||||
user (:class:`telegram.User`): Information about the user.
|
||||
custom_title (:obj:`str`, optional): Custom title for this user.
|
||||
is_anonymous (:obj:`bool`, optional): :obj:`True`, if the
|
||||
user's presence in the chat is hidden.
|
||||
|
||||
Attributes:
|
||||
status (:obj:`str`): The member's status in the chat,
|
||||
always :attr:`telegram.ChatMember.CREATOR`.
|
||||
user (:class:`telegram.User`): Information about the user.
|
||||
custom_title (:obj:`str`): Optional. Custom title for
|
||||
this user.
|
||||
is_anonymous (:obj:`bool`): Optional. :obj:`True`, if the user's
|
||||
presence in the chat is hidden.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
user: User,
|
||||
custom_title: str = None,
|
||||
is_anonymous: bool = None,
|
||||
**_kwargs: Any,
|
||||
):
|
||||
super().__init__(
|
||||
status=ChatMember.CREATOR,
|
||||
user=user,
|
||||
custom_title=custom_title,
|
||||
is_anonymous=is_anonymous,
|
||||
)
|
||||
|
||||
|
||||
class ChatMemberAdministrator(ChatMember):
|
||||
"""
|
||||
Represents a chat member that has some additional privileges.
|
||||
|
||||
.. versionadded:: 13.7
|
||||
|
||||
Args:
|
||||
user (:class:`telegram.User`): Information about the user.
|
||||
can_be_edited (:obj:`bool`, optional): :obj:`True`, if the bot
|
||||
is allowed to edit administrator privileges of that user.
|
||||
custom_title (:obj:`str`, optional): Custom title for this user.
|
||||
is_anonymous (:obj:`bool`, optional): :obj:`True`, if the user's
|
||||
presence in the chat is hidden.
|
||||
can_manage_chat (:obj:`bool`, optional): :obj:`True`, if the administrator
|
||||
can access the chat event log, chat statistics, message statistics in
|
||||
channels, see channel members, see anonymous administrators in supergroups
|
||||
and ignore slow mode. Implied by any other administrator privilege.
|
||||
can_post_messages (:obj:`bool`, optional): :obj:`True`, if the
|
||||
administrator can post in the channel, channels only.
|
||||
can_edit_messages (:obj:`bool`, optional): :obj:`True`, if the
|
||||
administrator can edit messages of other users and can pin
|
||||
messages; channels only.
|
||||
can_delete_messages (:obj:`bool`, optional): :obj:`True`, if the
|
||||
administrator can delete messages of other users.
|
||||
can_manage_voice_chats (:obj:`bool`, optional): :obj:`True`, if the
|
||||
administrator can manage voice chats.
|
||||
can_restrict_members (:obj:`bool`, optional): :obj:`True`, if the
|
||||
administrator can restrict, ban or unban chat members.
|
||||
can_promote_members (:obj:`bool`, optional): :obj:`True`, if the administrator
|
||||
can add new administrators with a subset of his own privileges or demote
|
||||
administrators that he has promoted, directly or indirectly (promoted by
|
||||
administrators that were appointed by the user).
|
||||
can_change_info (:obj:`bool`, optional): :obj:`True`, if the user can change
|
||||
the chat title, photo and other settings.
|
||||
can_invite_users (:obj:`bool`, optional): :obj:`True`, if the user can invite
|
||||
new users to the chat.
|
||||
can_pin_messages (:obj:`bool`, optional): :obj:`True`, if the user is allowed
|
||||
to pin messages; groups and supergroups only.
|
||||
|
||||
Attributes:
|
||||
status (:obj:`str`): The member's status in the chat,
|
||||
always :attr:`telegram.ChatMember.ADMINISTRATOR`.
|
||||
user (:class:`telegram.User`): Information about the user.
|
||||
can_be_edited (:obj:`bool`): Optional. :obj:`True`, if the bot
|
||||
is allowed to edit administrator privileges of that user.
|
||||
custom_title (:obj:`str`): Optional. Custom title for this user.
|
||||
is_anonymous (:obj:`bool`): Optional. :obj:`True`, if the user's
|
||||
presence in the chat is hidden.
|
||||
can_manage_chat (:obj:`bool`): Optional. :obj:`True`, if the administrator
|
||||
can access the chat event log, chat statistics, message statistics in
|
||||
channels, see channel members, see anonymous administrators in supergroups
|
||||
and ignore slow mode. Implied by any other administrator privilege.
|
||||
can_post_messages (:obj:`bool`): Optional. :obj:`True`, if the
|
||||
administrator can post in the channel, channels only.
|
||||
can_edit_messages (:obj:`bool`): Optional. :obj:`True`, if the
|
||||
administrator can edit messages of other users and can pin
|
||||
messages; channels only.
|
||||
can_delete_messages (:obj:`bool`): Optional. :obj:`True`, if the
|
||||
administrator can delete messages of other users.
|
||||
can_manage_voice_chats (:obj:`bool`): Optional. :obj:`True`, if the
|
||||
administrator can manage voice chats.
|
||||
can_restrict_members (:obj:`bool`): Optional. :obj:`True`, if the
|
||||
administrator can restrict, ban or unban chat members.
|
||||
can_promote_members (:obj:`bool`): Optional. :obj:`True`, if the administrator
|
||||
can add new administrators with a subset of his own privileges or demote
|
||||
administrators that he has promoted, directly or indirectly (promoted by
|
||||
administrators that were appointed by the user).
|
||||
can_change_info (:obj:`bool`): Optional. :obj:`True`, if the user can change
|
||||
the chat title, photo and other settings.
|
||||
can_invite_users (:obj:`bool`): Optional. :obj:`True`, if the user can invite
|
||||
new users to the chat.
|
||||
can_pin_messages (:obj:`bool`): Optional. :obj:`True`, if the user is allowed
|
||||
to pin messages; groups and supergroups only.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
user: User,
|
||||
can_be_edited: bool = None,
|
||||
custom_title: str = None,
|
||||
is_anonymous: bool = None,
|
||||
can_manage_chat: bool = None,
|
||||
can_post_messages: bool = None,
|
||||
can_edit_messages: bool = None,
|
||||
can_delete_messages: bool = None,
|
||||
can_manage_voice_chats: bool = None,
|
||||
can_restrict_members: bool = None,
|
||||
can_promote_members: bool = None,
|
||||
can_change_info: bool = None,
|
||||
can_invite_users: bool = None,
|
||||
can_pin_messages: bool = None,
|
||||
**_kwargs: Any,
|
||||
):
|
||||
super().__init__(
|
||||
status=ChatMember.ADMINISTRATOR,
|
||||
user=user,
|
||||
can_be_edited=can_be_edited,
|
||||
custom_title=custom_title,
|
||||
is_anonymous=is_anonymous,
|
||||
can_manage_chat=can_manage_chat,
|
||||
can_post_messages=can_post_messages,
|
||||
can_edit_messages=can_edit_messages,
|
||||
can_delete_messages=can_delete_messages,
|
||||
can_manage_voice_chats=can_manage_voice_chats,
|
||||
can_restrict_members=can_restrict_members,
|
||||
can_promote_members=can_promote_members,
|
||||
can_change_info=can_change_info,
|
||||
can_invite_users=can_invite_users,
|
||||
can_pin_messages=can_pin_messages,
|
||||
)
|
||||
|
||||
|
||||
class ChatMemberMember(ChatMember):
|
||||
"""
|
||||
Represents a chat member that has no additional
|
||||
privileges or restrictions.
|
||||
|
||||
.. versionadded:: 13.7
|
||||
|
||||
Args:
|
||||
user (:class:`telegram.User`): Information about the user.
|
||||
|
||||
Attributes:
|
||||
status (:obj:`str`): The member's status in the chat,
|
||||
always :attr:`telegram.ChatMember.MEMBER`.
|
||||
user (:class:`telegram.User`): Information about the user.
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def __init__(self, user: User, **_kwargs: Any):
|
||||
super().__init__(status=ChatMember.MEMBER, user=user)
|
||||
|
||||
|
||||
class ChatMemberRestricted(ChatMember):
|
||||
"""
|
||||
Represents a chat member that is under certain restrictions
|
||||
in the chat. Supergroups only.
|
||||
|
||||
.. versionadded:: 13.7
|
||||
|
||||
Args:
|
||||
user (:class:`telegram.User`): Information about the user.
|
||||
is_member (:obj:`bool`, optional): :obj:`True`, if the user is a
|
||||
member of the chat at the moment of the request.
|
||||
can_change_info (:obj:`bool`, optional): :obj:`True`, if the user can change
|
||||
the chat title, photo and other settings.
|
||||
can_invite_users (:obj:`bool`, optional): :obj:`True`, if the user can invite
|
||||
new users to the chat.
|
||||
can_pin_messages (:obj:`bool`, optional): :obj:`True`, if the user is allowed
|
||||
to pin messages; groups and supergroups only.
|
||||
can_send_messages (:obj:`bool`, optional): :obj:`True`, if the user is allowed
|
||||
to send text messages, contacts, locations and venues.
|
||||
can_send_media_messages (:obj:`bool`, optional): :obj:`True`, if the user is allowed
|
||||
to send audios, documents, photos, videos, video notes and voice notes.
|
||||
can_send_polls (:obj:`bool`, optional): :obj:`True`, if the user is allowed
|
||||
to send polls.
|
||||
can_send_other_messages (:obj:`bool`, optional): :obj:`True`, if the user is allowed
|
||||
to send animations, games, stickers and use inline bots.
|
||||
can_add_web_page_previews (:obj:`bool`, optional): :obj:`True`, if the user is
|
||||
allowed to add web page previews to their messages.
|
||||
until_date (:class:`datetime.datetime`, optional): Date when restrictions
|
||||
will be lifted for this user.
|
||||
|
||||
Attributes:
|
||||
status (:obj:`str`): The member's status in the chat,
|
||||
always :attr:`telegram.ChatMember.RESTRICTED`.
|
||||
user (:class:`telegram.User`): Information about the user.
|
||||
is_member (:obj:`bool`): Optional. :obj:`True`, if the user is a
|
||||
member of the chat at the moment of the request.
|
||||
can_change_info (:obj:`bool`): Optional. :obj:`True`, if the user can change
|
||||
the chat title, photo and other settings.
|
||||
can_invite_users (:obj:`bool`): Optional. :obj:`True`, if the user can invite
|
||||
new users to the chat.
|
||||
can_pin_messages (:obj:`bool`): Optional. :obj:`True`, if the user is allowed
|
||||
to pin messages; groups and supergroups only.
|
||||
can_send_messages (:obj:`bool`): Optional. :obj:`True`, if the user is allowed
|
||||
to send text messages, contacts, locations and venues.
|
||||
can_send_media_messages (:obj:`bool`): Optional. :obj:`True`, if the user is allowed
|
||||
to send audios, documents, photos, videos, video notes and voice notes.
|
||||
can_send_polls (:obj:`bool`): Optional. :obj:`True`, if the user is allowed
|
||||
to send polls.
|
||||
can_send_other_messages (:obj:`bool`): Optional. :obj:`True`, if the user is allowed
|
||||
to send animations, games, stickers and use inline bots.
|
||||
can_add_web_page_previews (:obj:`bool`): Optional. :obj:`True`, if the user is
|
||||
allowed to add web page previews to their messages.
|
||||
until_date (:class:`datetime.datetime`): Optional. Date when restrictions
|
||||
will be lifted for this user.
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
user: User,
|
||||
is_member: bool = None,
|
||||
can_change_info: bool = None,
|
||||
can_invite_users: bool = None,
|
||||
can_pin_messages: bool = None,
|
||||
can_send_messages: bool = None,
|
||||
can_send_media_messages: bool = None,
|
||||
can_send_polls: bool = None,
|
||||
can_send_other_messages: bool = None,
|
||||
can_add_web_page_previews: bool = None,
|
||||
until_date: datetime.datetime = None,
|
||||
**_kwargs: Any,
|
||||
):
|
||||
super().__init__(
|
||||
status=ChatMember.RESTRICTED,
|
||||
user=user,
|
||||
is_member=is_member,
|
||||
can_change_info=can_change_info,
|
||||
can_invite_users=can_invite_users,
|
||||
can_pin_messages=can_pin_messages,
|
||||
can_send_messages=can_send_messages,
|
||||
can_send_media_messages=can_send_media_messages,
|
||||
can_send_polls=can_send_polls,
|
||||
can_send_other_messages=can_send_other_messages,
|
||||
can_add_web_page_previews=can_add_web_page_previews,
|
||||
until_date=until_date,
|
||||
)
|
||||
|
||||
|
||||
class ChatMemberLeft(ChatMember):
|
||||
"""
|
||||
Represents a chat member that isn't currently a member of the chat,
|
||||
but may join it themselves.
|
||||
|
||||
.. versionadded:: 13.7
|
||||
|
||||
Args:
|
||||
user (:class:`telegram.User`): Information about the user.
|
||||
|
||||
Attributes:
|
||||
status (:obj:`str`): The member's status in the chat,
|
||||
always :attr:`telegram.ChatMember.LEFT`.
|
||||
user (:class:`telegram.User`): Information about the user.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def __init__(self, user: User, **_kwargs: Any):
|
||||
super().__init__(status=ChatMember.LEFT, user=user)
|
||||
|
||||
|
||||
class ChatMemberBanned(ChatMember):
|
||||
"""
|
||||
Represents a chat member that was banned in the chat and
|
||||
can't return to the chat or view chat messages.
|
||||
|
||||
.. versionadded:: 13.7
|
||||
|
||||
Args:
|
||||
user (:class:`telegram.User`): Information about the user.
|
||||
until_date (:class:`datetime.datetime`, optional): Date when restrictions
|
||||
will be lifted for this user.
|
||||
|
||||
Attributes:
|
||||
status (:obj:`str`): The member's status in the chat,
|
||||
always :attr:`telegram.ChatMember.KICKED`.
|
||||
user (:class:`telegram.User`): Information about the user.
|
||||
until_date (:class:`datetime.datetime`): Optional. Date when restrictions
|
||||
will be lifted for this user.
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
user: User,
|
||||
until_date: datetime.datetime = None,
|
||||
**_kwargs: Any,
|
||||
):
|
||||
super().__init__(
|
||||
status=ChatMember.KICKED,
|
||||
user=user,
|
||||
until_date=until_date,
|
||||
)
|
||||
|
||||
@@ -62,6 +62,16 @@ class ChatMemberUpdated(TelegramObject):
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
'chat',
|
||||
'from_user',
|
||||
'date',
|
||||
'old_chat_member',
|
||||
'new_chat_member',
|
||||
'invite_link',
|
||||
'_id_attrs',
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
chat: Chat,
|
||||
@@ -92,7 +102,8 @@ class ChatMemberUpdated(TelegramObject):
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['ChatMemberUpdated']:
|
||||
data = cls.parse_data(data)
|
||||
"""See :meth:`telegram.TelegramObject.de_json`."""
|
||||
data = cls._parse_data(data)
|
||||
|
||||
if not data:
|
||||
return None
|
||||
@@ -107,6 +118,7 @@ class ChatMemberUpdated(TelegramObject):
|
||||
return cls(**data)
|
||||
|
||||
def to_dict(self) -> JSONDict:
|
||||
"""See :meth:`telegram.TelegramObject.to_dict`."""
|
||||
data = super().to_dict()
|
||||
|
||||
# Required
|
||||
|
||||
@@ -78,6 +78,18 @@ class ChatPermissions(TelegramObject):
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
'can_send_other_messages',
|
||||
'can_invite_users',
|
||||
'can_send_polls',
|
||||
'_id_attrs',
|
||||
'can_send_messages',
|
||||
'can_send_media_messages',
|
||||
'can_change_info',
|
||||
'can_pin_messages',
|
||||
'can_add_web_page_previews',
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
can_send_messages: bool = None,
|
||||
|
||||
@@ -61,6 +61,8 @@ class ChosenInlineResult(TelegramObject):
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ('location', 'result_id', 'from_user', 'inline_message_id', '_id_attrs', 'query')
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
result_id: str,
|
||||
@@ -82,7 +84,8 @@ class ChosenInlineResult(TelegramObject):
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['ChosenInlineResult']:
|
||||
data = cls.parse_data(data)
|
||||
"""See :meth:`telegram.TelegramObject.de_json`."""
|
||||
data = cls._parse_data(data)
|
||||
|
||||
if not data:
|
||||
return None
|
||||
|
||||
+45
-2
@@ -21,7 +21,7 @@ The following constants were extracted from the
|
||||
`Telegram Bots API <https://core.telegram.org/bots/api>`_.
|
||||
|
||||
Attributes:
|
||||
BOT_API_VERSION (:obj:`str`): `5.2`. Telegram Bot API version supported by this
|
||||
BOT_API_VERSION (:obj:`str`): `5.3`. Telegram Bot API version supported by this
|
||||
version of `python-telegram-bot`. Also available as ``telegram.bot_api_version``.
|
||||
|
||||
.. versionadded:: 13.4
|
||||
@@ -86,6 +86,9 @@ Attributes:
|
||||
|
||||
.. versionadded:: 13.5
|
||||
CHATACTION_UPLOAD_DOCUMENT (:obj:`str`): ``'upload_document'``
|
||||
CHATACTION_CHOOSE_STICKER (:obj:`str`): ``'choose_sticker'``
|
||||
|
||||
.. versionadded:: 13.8
|
||||
CHATACTION_UPLOAD_PHOTO (:obj:`str`): ``'upload_photo'``
|
||||
CHATACTION_UPLOAD_VIDEO (:obj:`str`): ``'upload_video'``
|
||||
CHATACTION_UPLOAD_VIDEO_NOTE (:obj:`str`): ``'upload_video_note'``
|
||||
@@ -201,14 +204,43 @@ Attributes:
|
||||
UPDATE_CHAT_MEMBER (:obj:`str`): ``'chat_member'``
|
||||
|
||||
.. versionadded:: 13.5
|
||||
UPDATE_CHAT_JOIN_REQUEST (:obj:`str`): ``'chat_join_request'``
|
||||
|
||||
.. versionadded:: 13.8
|
||||
UPDATE_ALL_TYPES (List[:obj:`str`]): List of all update types.
|
||||
|
||||
.. versionadded:: 13.5
|
||||
.. versionchanged:: 13.8
|
||||
|
||||
:class:`telegram.BotCommandScope`:
|
||||
|
||||
Attributes:
|
||||
BOT_COMMAND_SCOPE_DEFAULT (:obj:`str`): ``'default'``
|
||||
|
||||
..versionadded:: 13.7
|
||||
BOT_COMMAND_SCOPE_ALL_PRIVATE_CHATS (:obj:`str`): ``'all_private_chats'``
|
||||
|
||||
..versionadded:: 13.7
|
||||
BOT_COMMAND_SCOPE_ALL_GROUP_CHATS (:obj:`str`): ``'all_group_chats'``
|
||||
|
||||
..versionadded:: 13.7
|
||||
BOT_COMMAND_SCOPE_ALL_CHAT_ADMINISTRATORS (:obj:`str`): ``'all_chat_administrators'``
|
||||
|
||||
..versionadded:: 13.7
|
||||
BOT_COMMAND_SCOPE_CHAT (:obj:`str`): ``'chat'``
|
||||
|
||||
..versionadded:: 13.7
|
||||
BOT_COMMAND_SCOPE_CHAT_ADMINISTRATORS (:obj:`str`): ``'chat_administrators'``
|
||||
|
||||
..versionadded:: 13.7
|
||||
BOT_COMMAND_SCOPE_CHAT_MEMBER (:obj:`str`): ``'chat_member'``
|
||||
|
||||
..versionadded:: 13.7
|
||||
|
||||
"""
|
||||
from typing import List
|
||||
|
||||
BOT_API_VERSION: str = '5.2'
|
||||
BOT_API_VERSION: str = '5.4'
|
||||
MAX_MESSAGE_LENGTH: int = 4096
|
||||
MAX_CAPTION_LENGTH: int = 1024
|
||||
ANONYMOUS_ADMIN_ID: int = 1087968824
|
||||
@@ -242,6 +274,7 @@ CHATACTION_TYPING: str = 'typing'
|
||||
CHATACTION_UPLOAD_AUDIO: str = 'upload_audio'
|
||||
CHATACTION_UPLOAD_VOICE: str = 'upload_voice'
|
||||
CHATACTION_UPLOAD_DOCUMENT: str = 'upload_document'
|
||||
CHATACTION_CHOOSE_STICKER: str = 'choose_sticker'
|
||||
CHATACTION_UPLOAD_PHOTO: str = 'upload_photo'
|
||||
CHATACTION_UPLOAD_VIDEO: str = 'upload_video'
|
||||
CHATACTION_UPLOAD_VIDEO_NOTE: str = 'upload_video_note'
|
||||
@@ -328,6 +361,7 @@ UPDATE_POLL = 'poll'
|
||||
UPDATE_POLL_ANSWER = 'poll_answer'
|
||||
UPDATE_MY_CHAT_MEMBER = 'my_chat_member'
|
||||
UPDATE_CHAT_MEMBER = 'chat_member'
|
||||
UPDATE_CHAT_JOIN_REQUEST = 'chat_join_request'
|
||||
UPDATE_ALL_TYPES = [
|
||||
UPDATE_MESSAGE,
|
||||
UPDATE_EDITED_MESSAGE,
|
||||
@@ -342,4 +376,13 @@ UPDATE_ALL_TYPES = [
|
||||
UPDATE_POLL_ANSWER,
|
||||
UPDATE_MY_CHAT_MEMBER,
|
||||
UPDATE_CHAT_MEMBER,
|
||||
UPDATE_CHAT_JOIN_REQUEST,
|
||||
]
|
||||
|
||||
BOT_COMMAND_SCOPE_DEFAULT = 'default'
|
||||
BOT_COMMAND_SCOPE_ALL_PRIVATE_CHATS = 'all_private_chats'
|
||||
BOT_COMMAND_SCOPE_ALL_GROUP_CHATS = 'all_group_chats'
|
||||
BOT_COMMAND_SCOPE_ALL_CHAT_ADMINISTRATORS = 'all_chat_administrators'
|
||||
BOT_COMMAND_SCOPE_CHAT = 'chat'
|
||||
BOT_COMMAND_SCOPE_CHAT_ADMINISTRATORS = 'chat_administrators'
|
||||
BOT_COMMAND_SCOPE_CHAT_MEMBER = 'chat_member'
|
||||
|
||||
+3
-1
@@ -64,13 +64,15 @@ class Dice(TelegramObject):
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ('emoji', 'value', '_id_attrs')
|
||||
|
||||
def __init__(self, value: int, emoji: str, **_kwargs: Any):
|
||||
self.value = value
|
||||
self.emoji = emoji
|
||||
|
||||
self._id_attrs = (self.value, self.emoji)
|
||||
|
||||
DICE: ClassVar[str] = constants.DICE_DICE
|
||||
DICE: ClassVar[str] = constants.DICE_DICE # skipcq: PTC-W0052
|
||||
""":const:`telegram.constants.DICE_DICE`"""
|
||||
DARTS: ClassVar[str] = constants.DICE_DARTS
|
||||
""":const:`telegram.constants.DICE_DARTS`"""
|
||||
|
||||
+32
-9
@@ -39,6 +39,11 @@ def _lstrip_str(in_s: str, lstr: str) -> str:
|
||||
|
||||
|
||||
class TelegramError(Exception):
|
||||
"""Base class for Telegram errors."""
|
||||
|
||||
# Apparently the base class Exception already has __dict__ in it, so its not included here
|
||||
__slots__ = ('message',)
|
||||
|
||||
def __init__(self, message: str):
|
||||
super().__init__()
|
||||
|
||||
@@ -58,10 +63,16 @@ class TelegramError(Exception):
|
||||
|
||||
|
||||
class Unauthorized(TelegramError):
|
||||
pass
|
||||
"""Raised when the bot has not enough rights to perform the requested action."""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
|
||||
class InvalidToken(TelegramError):
|
||||
"""Raised when the token is invalid."""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__('Invalid token')
|
||||
|
||||
@@ -70,14 +81,22 @@ class InvalidToken(TelegramError):
|
||||
|
||||
|
||||
class NetworkError(TelegramError):
|
||||
pass
|
||||
"""Base class for exceptions due to networking errors."""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
|
||||
class BadRequest(NetworkError):
|
||||
pass
|
||||
"""Raised when Telegram could not process the request correctly."""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
|
||||
class TimedOut(NetworkError):
|
||||
"""Raised when a request took too long to finish."""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__('Timed out')
|
||||
|
||||
@@ -87,11 +106,15 @@ class TimedOut(NetworkError):
|
||||
|
||||
class ChatMigrated(TelegramError):
|
||||
"""
|
||||
Raised when the requested group chat migrated to supergroup and has a new chat id.
|
||||
|
||||
Args:
|
||||
new_chat_id (:obj:`int`): The new chat id of the group.
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ('new_chat_id',)
|
||||
|
||||
def __init__(self, new_chat_id: int):
|
||||
super().__init__(f'Group migrated to supergroup. New chat id: {new_chat_id}')
|
||||
self.new_chat_id = new_chat_id
|
||||
@@ -102,11 +125,15 @@ class ChatMigrated(TelegramError):
|
||||
|
||||
class RetryAfter(TelegramError):
|
||||
"""
|
||||
Raised when flood limits where exceeded.
|
||||
|
||||
Args:
|
||||
retry_after (:obj:`int`): Time in seconds, after which the bot can retry the request.
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ('retry_after',)
|
||||
|
||||
def __init__(self, retry_after: int):
|
||||
super().__init__(f'Flood control exceeded. Retry in {float(retry_after)} seconds')
|
||||
self.retry_after = float(retry_after)
|
||||
@@ -116,13 +143,9 @@ class RetryAfter(TelegramError):
|
||||
|
||||
|
||||
class Conflict(TelegramError):
|
||||
"""
|
||||
Raised when a long poll or webhook conflicts with another one.
|
||||
"""Raised when a long poll or webhook conflicts with another one."""
|
||||
|
||||
Args:
|
||||
msg (:obj:`str`): The message from telegrams server.
|
||||
|
||||
"""
|
||||
__slots__ = ()
|
||||
|
||||
def __reduce__(self) -> Tuple[type, Tuple[str]]:
|
||||
return self.__class__, (self.message,)
|
||||
|
||||
+45
-23
@@ -16,14 +16,29 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
# pylint: disable=C0413
|
||||
"""Extensions over the Telegram Bot API to facilitate bot making"""
|
||||
|
||||
from .extbot import ExtBot
|
||||
from .basepersistence import BasePersistence
|
||||
from .picklepersistence import PicklePersistence
|
||||
from .dictpersistence import DictPersistence
|
||||
from .handler import Handler
|
||||
from .callbackcontext import CallbackContext
|
||||
from .contexttypes import ContextTypes
|
||||
from .dispatcher import Dispatcher, DispatcherHandlerStop, run_async
|
||||
|
||||
# https://bugs.python.org/issue41451, fixed on 3.7+, doesn't actually remove slots
|
||||
# try-except is just here in case the __init__ is called twice (like in the tests)
|
||||
# this block is also the reason for the pylint-ignore at the top of the file
|
||||
try:
|
||||
del Dispatcher.__slots__
|
||||
except AttributeError as exc:
|
||||
if str(exc) == '__slots__':
|
||||
pass
|
||||
else:
|
||||
raise exc
|
||||
|
||||
from .jobqueue import JobQueue, Job
|
||||
from .updater import Updater
|
||||
from .callbackqueryhandler import CallbackQueryHandler
|
||||
@@ -44,41 +59,48 @@ from .messagequeue import DelayQueue
|
||||
from .pollanswerhandler import PollAnswerHandler
|
||||
from .pollhandler import PollHandler
|
||||
from .chatmemberhandler import ChatMemberHandler
|
||||
from .chatjoinrequesthandler import ChatJoinRequestHandler
|
||||
from .defaults import Defaults
|
||||
from .callbackdatacache import CallbackDataCache, InvalidCallbackData
|
||||
|
||||
__all__ = (
|
||||
'Dispatcher',
|
||||
'JobQueue',
|
||||
'Job',
|
||||
'Updater',
|
||||
'BaseFilter',
|
||||
'BasePersistence',
|
||||
'CallbackContext',
|
||||
'CallbackDataCache',
|
||||
'CallbackQueryHandler',
|
||||
'ChatJoinRequestHandler',
|
||||
'ChatMemberHandler',
|
||||
'ChosenInlineResultHandler',
|
||||
'CommandHandler',
|
||||
'ContextTypes',
|
||||
'ConversationHandler',
|
||||
'Defaults',
|
||||
'DelayQueue',
|
||||
'DictPersistence',
|
||||
'Dispatcher',
|
||||
'DispatcherHandlerStop',
|
||||
'ExtBot',
|
||||
'Filters',
|
||||
'Handler',
|
||||
'InlineQueryHandler',
|
||||
'MessageHandler',
|
||||
'BaseFilter',
|
||||
'InvalidCallbackData',
|
||||
'Job',
|
||||
'JobQueue',
|
||||
'MessageFilter',
|
||||
'UpdateFilter',
|
||||
'Filters',
|
||||
'MessageHandler',
|
||||
'MessageQueue',
|
||||
'PicklePersistence',
|
||||
'PollAnswerHandler',
|
||||
'PollHandler',
|
||||
'PreCheckoutQueryHandler',
|
||||
'PrefixHandler',
|
||||
'RegexHandler',
|
||||
'ShippingQueryHandler',
|
||||
'StringCommandHandler',
|
||||
'StringRegexHandler',
|
||||
'TypeHandler',
|
||||
'ConversationHandler',
|
||||
'PreCheckoutQueryHandler',
|
||||
'ShippingQueryHandler',
|
||||
'MessageQueue',
|
||||
'DelayQueue',
|
||||
'DispatcherHandlerStop',
|
||||
'UpdateFilter',
|
||||
'Updater',
|
||||
'run_async',
|
||||
'CallbackContext',
|
||||
'BasePersistence',
|
||||
'PicklePersistence',
|
||||
'DictPersistence',
|
||||
'PrefixHandler',
|
||||
'PollAnswerHandler',
|
||||
'PollHandler',
|
||||
'ChatMemberHandler',
|
||||
'Defaults',
|
||||
)
|
||||
|
||||
+246
-64
@@ -18,16 +18,20 @@
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains the BasePersistence class."""
|
||||
import warnings
|
||||
from sys import version_info as py_ver
|
||||
from abc import ABC, abstractmethod
|
||||
from copy import copy
|
||||
from typing import DefaultDict, Dict, Optional, Tuple, cast, ClassVar
|
||||
from typing import Dict, Optional, Tuple, cast, ClassVar, Generic, DefaultDict
|
||||
|
||||
from telegram.utils.deprecate import set_new_attribute_deprecated
|
||||
|
||||
from telegram import Bot
|
||||
import telegram.ext.extbot
|
||||
|
||||
from telegram.utils.types import ConversationDict
|
||||
from telegram.ext.utils.types import UD, CD, BD, ConversationDict, CDCData
|
||||
|
||||
|
||||
class BasePersistence(ABC):
|
||||
class BasePersistence(Generic[UD, CD, BD], ABC):
|
||||
"""Interface class for adding persistence to your bot.
|
||||
Subclass this object for different implementations of a persistent bot.
|
||||
|
||||
@@ -35,16 +39,22 @@ class BasePersistence(ABC):
|
||||
|
||||
* :meth:`get_bot_data`
|
||||
* :meth:`update_bot_data`
|
||||
* :meth:`refresh_bot_data`
|
||||
* :meth:`get_chat_data`
|
||||
* :meth:`update_chat_data`
|
||||
* :meth:`refresh_chat_data`
|
||||
* :meth:`get_user_data`
|
||||
* :meth:`update_user_data`
|
||||
* :meth:`refresh_user_data`
|
||||
* :meth:`get_callback_data`
|
||||
* :meth:`update_callback_data`
|
||||
* :meth:`get_conversations`
|
||||
* :meth:`update_conversation`
|
||||
* :meth:`flush`
|
||||
|
||||
If you don't actually need one of those methods, a simple ``pass`` is enough. For example, if
|
||||
``store_bot_data=False``, you don't need :meth:`get_bot_data` and :meth:`update_bot_data`.
|
||||
``store_bot_data=False``, you don't need :meth:`get_bot_data`, :meth:`update_bot_data` or
|
||||
:meth:`refresh_bot_data`.
|
||||
|
||||
Warning:
|
||||
Persistence will try to replace :class:`telegram.Bot` instances by :attr:`REPLACED_BOT` and
|
||||
@@ -64,7 +74,11 @@ class BasePersistence(ABC):
|
||||
store_chat_data (:obj:`bool`, optional): Whether chat_data should be saved by this
|
||||
persistence class. Default is :obj:`True` .
|
||||
store_bot_data (:obj:`bool`, optional): Whether bot_data should be saved by this
|
||||
persistence class. Default is :obj:`True` .
|
||||
persistence class. Default is :obj:`True`.
|
||||
store_callback_data (:obj:`bool`, optional): Whether callback_data should be saved by this
|
||||
persistence class. Default is :obj:`False`.
|
||||
|
||||
.. versionadded:: 13.6
|
||||
|
||||
Attributes:
|
||||
store_user_data (:obj:`bool`): Optional, Whether user_data should be saved by this
|
||||
@@ -73,43 +87,88 @@ class BasePersistence(ABC):
|
||||
persistence class.
|
||||
store_bot_data (:obj:`bool`): Optional. Whether bot_data should be saved by this
|
||||
persistence class.
|
||||
store_callback_data (:obj:`bool`): Optional. Whether callback_data should be saved by this
|
||||
persistence class.
|
||||
|
||||
.. versionadded:: 13.6
|
||||
"""
|
||||
|
||||
# Apparently Py 3.7 and below have '__dict__' in ABC
|
||||
if py_ver < (3, 7):
|
||||
__slots__ = (
|
||||
'store_user_data',
|
||||
'store_chat_data',
|
||||
'store_bot_data',
|
||||
'store_callback_data',
|
||||
'bot',
|
||||
)
|
||||
else:
|
||||
__slots__ = (
|
||||
'store_user_data', # type: ignore[assignment]
|
||||
'store_chat_data',
|
||||
'store_bot_data',
|
||||
'store_callback_data',
|
||||
'bot',
|
||||
'__dict__',
|
||||
)
|
||||
|
||||
def __new__(
|
||||
cls, *args: object, **kwargs: object # pylint: disable=W0613
|
||||
) -> 'BasePersistence':
|
||||
"""This overrides the get_* and update_* methods to use insert/replace_bot.
|
||||
That has the side effect that we always pass deepcopied data to those methods, so in
|
||||
Pickle/DictPersistence we don't have to worry about copying the data again.
|
||||
|
||||
Note: This doesn't hold for second tuple-entry of callback_data. That's a Dict[str, str],
|
||||
so no bots to replace anyway.
|
||||
"""
|
||||
instance = super().__new__(cls)
|
||||
get_user_data = instance.get_user_data
|
||||
get_chat_data = instance.get_chat_data
|
||||
get_bot_data = instance.get_bot_data
|
||||
get_callback_data = instance.get_callback_data
|
||||
update_user_data = instance.update_user_data
|
||||
update_chat_data = instance.update_chat_data
|
||||
update_bot_data = instance.update_bot_data
|
||||
update_callback_data = instance.update_callback_data
|
||||
|
||||
def get_user_data_insert_bot() -> DefaultDict[int, Dict[object, object]]:
|
||||
def get_user_data_insert_bot() -> DefaultDict[int, UD]:
|
||||
return instance.insert_bot(get_user_data())
|
||||
|
||||
def get_chat_data_insert_bot() -> DefaultDict[int, Dict[object, object]]:
|
||||
def get_chat_data_insert_bot() -> DefaultDict[int, CD]:
|
||||
return instance.insert_bot(get_chat_data())
|
||||
|
||||
def get_bot_data_insert_bot() -> Dict[object, object]:
|
||||
def get_bot_data_insert_bot() -> BD:
|
||||
return instance.insert_bot(get_bot_data())
|
||||
|
||||
def update_user_data_replace_bot(user_id: int, data: Dict) -> None:
|
||||
def get_callback_data_insert_bot() -> Optional[CDCData]:
|
||||
cdc_data = get_callback_data()
|
||||
if cdc_data is None:
|
||||
return None
|
||||
return instance.insert_bot(cdc_data[0]), cdc_data[1]
|
||||
|
||||
def update_user_data_replace_bot(user_id: int, data: UD) -> None:
|
||||
return update_user_data(user_id, instance.replace_bot(data))
|
||||
|
||||
def update_chat_data_replace_bot(chat_id: int, data: Dict) -> None:
|
||||
def update_chat_data_replace_bot(chat_id: int, data: CD) -> None:
|
||||
return update_chat_data(chat_id, instance.replace_bot(data))
|
||||
|
||||
def update_bot_data_replace_bot(data: Dict) -> None:
|
||||
def update_bot_data_replace_bot(data: BD) -> None:
|
||||
return update_bot_data(instance.replace_bot(data))
|
||||
|
||||
instance.get_user_data = get_user_data_insert_bot
|
||||
instance.get_chat_data = get_chat_data_insert_bot
|
||||
instance.get_bot_data = get_bot_data_insert_bot
|
||||
instance.update_user_data = update_user_data_replace_bot
|
||||
instance.update_chat_data = update_chat_data_replace_bot
|
||||
instance.update_bot_data = update_bot_data_replace_bot
|
||||
def update_callback_data_replace_bot(data: CDCData) -> None:
|
||||
obj_data, queue = data
|
||||
return update_callback_data((instance.replace_bot(obj_data), queue))
|
||||
|
||||
# We want to ignore TGDeprecation warnings so we use obj.__setattr__. Adds to __dict__
|
||||
object.__setattr__(instance, 'get_user_data', get_user_data_insert_bot)
|
||||
object.__setattr__(instance, 'get_chat_data', get_chat_data_insert_bot)
|
||||
object.__setattr__(instance, 'get_bot_data', get_bot_data_insert_bot)
|
||||
object.__setattr__(instance, 'get_callback_data', get_callback_data_insert_bot)
|
||||
object.__setattr__(instance, 'update_user_data', update_user_data_replace_bot)
|
||||
object.__setattr__(instance, 'update_chat_data', update_chat_data_replace_bot)
|
||||
object.__setattr__(instance, 'update_bot_data', update_bot_data_replace_bot)
|
||||
object.__setattr__(instance, 'update_callback_data', update_callback_data_replace_bot)
|
||||
return instance
|
||||
|
||||
def __init__(
|
||||
@@ -117,18 +176,33 @@ class BasePersistence(ABC):
|
||||
store_user_data: bool = True,
|
||||
store_chat_data: bool = True,
|
||||
store_bot_data: bool = True,
|
||||
store_callback_data: bool = False,
|
||||
):
|
||||
self.store_user_data = store_user_data
|
||||
self.store_chat_data = store_chat_data
|
||||
self.store_bot_data = store_bot_data
|
||||
self.store_callback_data = store_callback_data
|
||||
self.bot: Bot = None # type: ignore[assignment]
|
||||
|
||||
def __setattr__(self, key: str, value: object) -> None:
|
||||
# Allow user defined subclasses to have custom attributes.
|
||||
if issubclass(self.__class__, BasePersistence) and self.__class__.__name__ not in {
|
||||
'DictPersistence',
|
||||
'PicklePersistence',
|
||||
}:
|
||||
object.__setattr__(self, key, value)
|
||||
return
|
||||
set_new_attribute_deprecated(self, key, value)
|
||||
|
||||
def set_bot(self, bot: Bot) -> None:
|
||||
"""Set the Bot to be used by this persistence instance.
|
||||
|
||||
Args:
|
||||
bot (:class:`telegram.Bot`): The bot.
|
||||
"""
|
||||
if self.store_callback_data and not isinstance(bot, telegram.ext.extbot.ExtBot):
|
||||
raise TypeError('store_callback_data can only be used with telegram.ext.ExtBot.')
|
||||
|
||||
self.bot = bot
|
||||
|
||||
@classmethod
|
||||
@@ -137,7 +211,9 @@ class BasePersistence(ABC):
|
||||
Replaces all instances of :class:`telegram.Bot` that occur within the passed object with
|
||||
:attr:`REPLACED_BOT`. Currently, this handles objects of type ``list``, ``tuple``, ``set``,
|
||||
``frozenset``, ``dict``, ``defaultdict`` and objects that have a ``__dict__`` or
|
||||
``__slot__`` attribute, excluding objects that can't be copied with `copy.copy`.
|
||||
``__slots__`` attribute, excluding classes and objects that can't be copied with
|
||||
``copy.copy``. If the parsing of an object fails, the object will be returned unchanged and
|
||||
the error will be logged.
|
||||
|
||||
Args:
|
||||
obj (:obj:`object`): The object
|
||||
@@ -168,6 +244,14 @@ class BasePersistence(ABC):
|
||||
new_immutable = obj.__class__(cls._replace_bot(item, memo) for item in obj)
|
||||
memo[obj_id] = new_immutable
|
||||
return new_immutable
|
||||
if isinstance(obj, type):
|
||||
# classes usually do have a __dict__, but it's not writable
|
||||
warnings.warn(
|
||||
'BasePersistence.replace_bot does not handle classes. See '
|
||||
'the docs of BasePersistence.replace_bot for more information.',
|
||||
RuntimeWarning,
|
||||
)
|
||||
return obj
|
||||
|
||||
try:
|
||||
new_obj = copy(obj)
|
||||
@@ -193,21 +277,34 @@ class BasePersistence(ABC):
|
||||
new_obj[cls._replace_bot(k, memo)] = cls._replace_bot(val, memo)
|
||||
memo[obj_id] = new_obj
|
||||
return new_obj
|
||||
if hasattr(obj, '__dict__'):
|
||||
for attr_name, attr in new_obj.__dict__.items():
|
||||
setattr(new_obj, attr_name, cls._replace_bot(attr, memo))
|
||||
memo[obj_id] = new_obj
|
||||
return new_obj
|
||||
if hasattr(obj, '__slots__'):
|
||||
for attr_name in new_obj.__slots__:
|
||||
setattr(
|
||||
new_obj,
|
||||
attr_name,
|
||||
cls._replace_bot(cls._replace_bot(getattr(new_obj, attr_name), memo), memo),
|
||||
)
|
||||
memo[obj_id] = new_obj
|
||||
return new_obj
|
||||
try:
|
||||
if hasattr(obj, '__slots__'):
|
||||
for attr_name in new_obj.__slots__:
|
||||
setattr(
|
||||
new_obj,
|
||||
attr_name,
|
||||
cls._replace_bot(
|
||||
cls._replace_bot(getattr(new_obj, attr_name), memo), memo
|
||||
),
|
||||
)
|
||||
if '__dict__' in obj.__slots__:
|
||||
# In this case, we have already covered the case that obj has __dict__
|
||||
# Note that obj may have a __dict__ even if it's not in __slots__!
|
||||
memo[obj_id] = new_obj
|
||||
return new_obj
|
||||
if hasattr(obj, '__dict__'):
|
||||
for attr_name, attr in new_obj.__dict__.items():
|
||||
setattr(new_obj, attr_name, cls._replace_bot(attr, memo))
|
||||
memo[obj_id] = new_obj
|
||||
return new_obj
|
||||
except Exception as exception:
|
||||
warnings.warn(
|
||||
f'Parsing of an object failed with the following exception: {exception}. '
|
||||
f'See the docs of BasePersistence.replace_bot for more information.',
|
||||
RuntimeWarning,
|
||||
)
|
||||
|
||||
memo[obj_id] = obj
|
||||
return obj
|
||||
|
||||
def insert_bot(self, obj: object) -> object:
|
||||
@@ -215,7 +312,9 @@ class BasePersistence(ABC):
|
||||
Replaces all instances of :attr:`REPLACED_BOT` that occur within the passed object with
|
||||
:attr:`bot`. Currently, this handles objects of type ``list``, ``tuple``, ``set``,
|
||||
``frozenset``, ``dict``, ``defaultdict`` and objects that have a ``__dict__`` or
|
||||
``__slot__`` attribute, excluding objects that can't be copied with `copy.copy`.
|
||||
``__slots__`` attribute, excluding classes and objects that can't be copied with
|
||||
``copy.copy``. If the parsing of an object fails, the object will be returned unchanged and
|
||||
the error will be logged.
|
||||
|
||||
Args:
|
||||
obj (:obj:`object`): The object
|
||||
@@ -248,6 +347,14 @@ class BasePersistence(ABC):
|
||||
new_immutable = obj.__class__(self._insert_bot(item, memo) for item in obj)
|
||||
memo[obj_id] = new_immutable
|
||||
return new_immutable
|
||||
if isinstance(obj, type):
|
||||
# classes usually do have a __dict__, but it's not writable
|
||||
warnings.warn(
|
||||
'BasePersistence.insert_bot does not handle classes. See '
|
||||
'the docs of BasePersistence.insert_bot for more information.',
|
||||
RuntimeWarning,
|
||||
)
|
||||
return obj
|
||||
|
||||
try:
|
||||
new_obj = copy(obj)
|
||||
@@ -272,53 +379,78 @@ class BasePersistence(ABC):
|
||||
new_obj[self._insert_bot(k, memo)] = self._insert_bot(val, memo)
|
||||
memo[obj_id] = new_obj
|
||||
return new_obj
|
||||
if hasattr(obj, '__dict__'):
|
||||
for attr_name, attr in new_obj.__dict__.items():
|
||||
setattr(new_obj, attr_name, self._insert_bot(attr, memo))
|
||||
memo[obj_id] = new_obj
|
||||
return new_obj
|
||||
if hasattr(obj, '__slots__'):
|
||||
for attr_name in obj.__slots__:
|
||||
setattr(
|
||||
new_obj,
|
||||
attr_name,
|
||||
self._insert_bot(self._insert_bot(getattr(new_obj, attr_name), memo), memo),
|
||||
)
|
||||
memo[obj_id] = new_obj
|
||||
return new_obj
|
||||
try:
|
||||
if hasattr(obj, '__slots__'):
|
||||
for attr_name in obj.__slots__:
|
||||
setattr(
|
||||
new_obj,
|
||||
attr_name,
|
||||
self._insert_bot(
|
||||
self._insert_bot(getattr(new_obj, attr_name), memo), memo
|
||||
),
|
||||
)
|
||||
if '__dict__' in obj.__slots__:
|
||||
# In this case, we have already covered the case that obj has __dict__
|
||||
# Note that obj may have a __dict__ even if it's not in __slots__!
|
||||
memo[obj_id] = new_obj
|
||||
return new_obj
|
||||
if hasattr(obj, '__dict__'):
|
||||
for attr_name, attr in new_obj.__dict__.items():
|
||||
setattr(new_obj, attr_name, self._insert_bot(attr, memo))
|
||||
memo[obj_id] = new_obj
|
||||
return new_obj
|
||||
except Exception as exception:
|
||||
warnings.warn(
|
||||
f'Parsing of an object failed with the following exception: {exception}. '
|
||||
f'See the docs of BasePersistence.insert_bot for more information.',
|
||||
RuntimeWarning,
|
||||
)
|
||||
|
||||
memo[obj_id] = obj
|
||||
return obj
|
||||
|
||||
@abstractmethod
|
||||
def get_user_data(self) -> DefaultDict[int, Dict[object, object]]:
|
||||
def get_user_data(self) -> DefaultDict[int, UD]:
|
||||
"""Will be called by :class:`telegram.ext.Dispatcher` upon creation with a
|
||||
persistence object. It should return the ``user_data`` if stored, or an empty
|
||||
``defaultdict(dict)``.
|
||||
:obj:`defaultdict(telegram.ext.utils.types.UD)` with integer keys.
|
||||
|
||||
Returns:
|
||||
:obj:`defaultdict`: The restored user data.
|
||||
DefaultDict[:obj:`int`, :class:`telegram.ext.utils.types.UD`]: The restored user data.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_chat_data(self) -> DefaultDict[int, Dict[object, object]]:
|
||||
def get_chat_data(self) -> DefaultDict[int, CD]:
|
||||
"""Will be called by :class:`telegram.ext.Dispatcher` upon creation with a
|
||||
persistence object. It should return the ``chat_data`` if stored, or an empty
|
||||
``defaultdict(dict)``.
|
||||
:obj:`defaultdict(telegram.ext.utils.types.CD)` with integer keys.
|
||||
|
||||
Returns:
|
||||
:obj:`defaultdict`: The restored chat data.
|
||||
DefaultDict[:obj:`int`, :class:`telegram.ext.utils.types.CD`]: The restored chat data.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_bot_data(self) -> Dict[object, object]:
|
||||
def get_bot_data(self) -> BD:
|
||||
"""Will be called by :class:`telegram.ext.Dispatcher` upon creation with a
|
||||
persistence object. It should return the ``bot_data`` if stored, or an empty
|
||||
:obj:`dict`.
|
||||
:class:`telegram.ext.utils.types.BD`.
|
||||
|
||||
Returns:
|
||||
:obj:`dict`: The restored bot data.
|
||||
:class:`telegram.ext.utils.types.BD`: The restored bot data.
|
||||
"""
|
||||
|
||||
def get_callback_data(self) -> Optional[CDCData]:
|
||||
"""Will be called by :class:`telegram.ext.Dispatcher` upon creation with a
|
||||
persistence object. If callback data was stored, it should be returned.
|
||||
|
||||
.. versionadded:: 13.6
|
||||
|
||||
Returns:
|
||||
Optional[:class:`telegram.ext.utils.types.CDCData`]: The restored meta data or
|
||||
:obj:`None`, if no data was stored.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def get_conversations(self, name: str) -> ConversationDict:
|
||||
"""Will be called by :class:`telegram.ext.Dispatcher` when a
|
||||
@@ -337,8 +469,8 @@ class BasePersistence(ABC):
|
||||
def update_conversation(
|
||||
self, name: str, key: Tuple[int, ...], new_state: Optional[object]
|
||||
) -> None:
|
||||
"""Will be called when a :attr:`telegram.ext.ConversationHandler.update_state`
|
||||
is called. This allows the storage of the new state in the persistence.
|
||||
"""Will be called when a :class:`telegram.ext.ConversationHandler` changes states.
|
||||
This allows the storage of the new state in the persistence.
|
||||
|
||||
Args:
|
||||
name (:obj:`str`): The handler's name.
|
||||
@@ -347,34 +479,84 @@ class BasePersistence(ABC):
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def update_user_data(self, user_id: int, data: Dict) -> None:
|
||||
def update_user_data(self, user_id: int, data: UD) -> None:
|
||||
"""Will be called by the :class:`telegram.ext.Dispatcher` after a handler has
|
||||
handled an update.
|
||||
|
||||
Args:
|
||||
user_id (:obj:`int`): The user the data might have been changed for.
|
||||
data (:obj:`dict`): The :attr:`telegram.ext.dispatcher.user_data` [user_id].
|
||||
data (:class:`telegram.ext.utils.types.UD`): The
|
||||
:attr:`telegram.ext.Dispatcher.user_data` ``[user_id]``.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def update_chat_data(self, chat_id: int, data: Dict) -> None:
|
||||
def update_chat_data(self, chat_id: int, data: CD) -> None:
|
||||
"""Will be called by the :class:`telegram.ext.Dispatcher` after a handler has
|
||||
handled an update.
|
||||
|
||||
Args:
|
||||
chat_id (:obj:`int`): The chat the data might have been changed for.
|
||||
data (:obj:`dict`): The :attr:`telegram.ext.dispatcher.chat_data` [chat_id].
|
||||
data (:class:`telegram.ext.utils.types.CD`): The
|
||||
:attr:`telegram.ext.Dispatcher.chat_data` ``[chat_id]``.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def update_bot_data(self, data: Dict) -> None:
|
||||
def update_bot_data(self, data: BD) -> None:
|
||||
"""Will be called by the :class:`telegram.ext.Dispatcher` after a handler has
|
||||
handled an update.
|
||||
|
||||
Args:
|
||||
data (:obj:`dict`): The :attr:`telegram.ext.dispatcher.bot_data` .
|
||||
data (:class:`telegram.ext.utils.types.BD`): The
|
||||
:attr:`telegram.ext.Dispatcher.bot_data`.
|
||||
"""
|
||||
|
||||
def refresh_user_data(self, user_id: int, user_data: UD) -> None:
|
||||
"""Will be called by the :class:`telegram.ext.Dispatcher` before passing the
|
||||
:attr:`user_data` to a callback. Can be used to update data stored in :attr:`user_data`
|
||||
from an external source.
|
||||
|
||||
.. versionadded:: 13.6
|
||||
|
||||
Args:
|
||||
user_id (:obj:`int`): The user ID this :attr:`user_data` is associated with.
|
||||
user_data (:class:`telegram.ext.utils.types.UD`): The ``user_data`` of a single user.
|
||||
"""
|
||||
|
||||
def refresh_chat_data(self, chat_id: int, chat_data: CD) -> None:
|
||||
"""Will be called by the :class:`telegram.ext.Dispatcher` before passing the
|
||||
:attr:`chat_data` to a callback. Can be used to update data stored in :attr:`chat_data`
|
||||
from an external source.
|
||||
|
||||
.. versionadded:: 13.6
|
||||
|
||||
Args:
|
||||
chat_id (:obj:`int`): The chat ID this :attr:`chat_data` is associated with.
|
||||
chat_data (:class:`telegram.ext.utils.types.CD`): The ``chat_data`` of a single chat.
|
||||
"""
|
||||
|
||||
def refresh_bot_data(self, bot_data: BD) -> None:
|
||||
"""Will be called by the :class:`telegram.ext.Dispatcher` before passing the
|
||||
:attr:`bot_data` to a callback. Can be used to update data stored in :attr:`bot_data`
|
||||
from an external source.
|
||||
|
||||
.. versionadded:: 13.6
|
||||
|
||||
Args:
|
||||
bot_data (:class:`telegram.ext.utils.types.BD`): The ``bot_data``.
|
||||
"""
|
||||
|
||||
def update_callback_data(self, data: CDCData) -> None:
|
||||
"""Will be called by the :class:`telegram.ext.Dispatcher` after a handler has
|
||||
handled an update.
|
||||
|
||||
.. versionadded:: 13.6
|
||||
|
||||
Args:
|
||||
data (:class:`telegram.ext.utils.types.CDCData`): The relevant data to restore
|
||||
:class:`telegram.ext.CallbackDataCache`.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def flush(self) -> None:
|
||||
"""Will be called by :class:`telegram.ext.Updater` upon receiving a stop signal. Gives the
|
||||
persistence a chance to finish up saving or close a database connection gracefully.
|
||||
|
||||
+176
-32
@@ -19,16 +19,32 @@
|
||||
# pylint: disable=R0201
|
||||
"""This module contains the CallbackContext class."""
|
||||
from queue import Queue
|
||||
from typing import TYPE_CHECKING, Dict, List, Match, NoReturn, Optional, Tuple, Union
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Dict,
|
||||
List,
|
||||
Match,
|
||||
NoReturn,
|
||||
Optional,
|
||||
Tuple,
|
||||
Union,
|
||||
Generic,
|
||||
Type,
|
||||
TypeVar,
|
||||
)
|
||||
|
||||
from telegram import Update
|
||||
from telegram import Update, CallbackQuery
|
||||
from telegram.ext import ExtBot
|
||||
from telegram.ext.utils.types import UD, CD, BD
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Bot
|
||||
from telegram.ext import Dispatcher, Job, JobQueue
|
||||
|
||||
CC = TypeVar('CC', bound='CallbackContext')
|
||||
|
||||
class CallbackContext:
|
||||
|
||||
class CallbackContext(Generic[UD, CD, BD]):
|
||||
"""
|
||||
This is a context object passed to the callback called by :class:`telegram.ext.Handler`
|
||||
or by the :class:`telegram.ext.Dispatcher` in an error handler added by
|
||||
@@ -50,20 +66,10 @@ class CallbackContext:
|
||||
almost certainly execute the callbacks for an update out of order, and the attributes
|
||||
that you think you added will not be present.
|
||||
|
||||
Args:
|
||||
dispatcher (:class:`telegram.ext.Dispatcher`): The dispatcher associated with this context.
|
||||
|
||||
Attributes:
|
||||
bot_data (:obj:`dict`): Optional. A dict that can be used to keep any data in. For each
|
||||
update it will be the same ``dict``.
|
||||
chat_data (:obj:`dict`): Optional. A dict that can be used to keep any data in. For each
|
||||
update from the same chat id it will be the same ``dict``.
|
||||
|
||||
Warning:
|
||||
When a group chat migrates to a supergroup, its chat id will change and the
|
||||
``chat_data`` needs to be transferred. For details see our `wiki page
|
||||
<https://github.com/python-telegram-bot/python-telegram-bot/wiki/
|
||||
Storing-user--and-chat-related-data#chat-migration>`_.
|
||||
|
||||
user_data (:obj:`dict`): Optional. A dict that can be used to keep any data in. For each
|
||||
update from the same user it will be the same ``dict``.
|
||||
matches (List[:obj:`re match object`]): Optional. If the associated update originated from
|
||||
a regex-supported handler or had a :class:`Filters.regex`, this will contain a list of
|
||||
match objects for every pattern where ``re.search(pattern, string)`` returned a match.
|
||||
@@ -86,6 +92,19 @@ class CallbackContext:
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
'_dispatcher',
|
||||
'_chat_id_and_data',
|
||||
'_user_id_and_data',
|
||||
'args',
|
||||
'matches',
|
||||
'error',
|
||||
'job',
|
||||
'async_args',
|
||||
'async_kwargs',
|
||||
'__dict__',
|
||||
)
|
||||
|
||||
def __init__(self, dispatcher: 'Dispatcher'):
|
||||
"""
|
||||
Args:
|
||||
@@ -96,9 +115,8 @@ class CallbackContext:
|
||||
'CallbackContext should not be used with a non context aware ' 'dispatcher!'
|
||||
)
|
||||
self._dispatcher = dispatcher
|
||||
self._bot_data = dispatcher.bot_data
|
||||
self._chat_data: Optional[Dict[object, object]] = None
|
||||
self._user_data: Optional[Dict[object, object]] = None
|
||||
self._chat_id_and_data: Optional[Tuple[int, CD]] = None
|
||||
self._user_id_and_data: Optional[Tuple[int, UD]] = None
|
||||
self.args: Optional[List[str]] = None
|
||||
self.matches: Optional[List[Match]] = None
|
||||
self.error: Optional[Exception] = None
|
||||
@@ -112,8 +130,11 @@ class CallbackContext:
|
||||
return self._dispatcher
|
||||
|
||||
@property
|
||||
def bot_data(self) -> Dict:
|
||||
return self._bot_data
|
||||
def bot_data(self) -> BD:
|
||||
""":obj:`dict`: Optional. A dict that can be used to keep any data in. For each
|
||||
update it will be the same ``dict``.
|
||||
"""
|
||||
return self.dispatcher.bot_data
|
||||
|
||||
@bot_data.setter
|
||||
def bot_data(self, value: object) -> NoReturn:
|
||||
@@ -122,8 +143,19 @@ class CallbackContext:
|
||||
)
|
||||
|
||||
@property
|
||||
def chat_data(self) -> Optional[Dict]:
|
||||
return self._chat_data
|
||||
def chat_data(self) -> Optional[CD]:
|
||||
""":obj:`dict`: Optional. A dict that can be used to keep any data in. For each
|
||||
update from the same chat id it will be the same ``dict``.
|
||||
|
||||
Warning:
|
||||
When a group chat migrates to a supergroup, its chat id will change and the
|
||||
``chat_data`` needs to be transferred. For details see our `wiki page
|
||||
<https://github.com/python-telegram-bot/python-telegram-bot/wiki/
|
||||
Storing-bot,-user-and-chat-related-data#chat-migration>`_.
|
||||
"""
|
||||
if self._chat_id_and_data:
|
||||
return self._chat_id_and_data[1]
|
||||
return None
|
||||
|
||||
@chat_data.setter
|
||||
def chat_data(self, value: object) -> NoReturn:
|
||||
@@ -132,8 +164,13 @@ class CallbackContext:
|
||||
)
|
||||
|
||||
@property
|
||||
def user_data(self) -> Optional[Dict]:
|
||||
return self._user_data
|
||||
def user_data(self) -> Optional[UD]:
|
||||
""":obj:`dict`: Optional. A dict that can be used to keep any data in. For each
|
||||
update from the same user it will be the same ``dict``.
|
||||
"""
|
||||
if self._user_id_and_data:
|
||||
return self._user_id_and_data[1]
|
||||
return None
|
||||
|
||||
@user_data.setter
|
||||
def user_data(self, value: object) -> NoReturn:
|
||||
@@ -141,15 +178,82 @@ class CallbackContext:
|
||||
"You can not assign a new value to user_data, see https://git.io/Jt6ic"
|
||||
)
|
||||
|
||||
def refresh_data(self) -> None:
|
||||
"""If :attr:`dispatcher` uses persistence, calls
|
||||
:meth:`telegram.ext.BasePersistence.refresh_bot_data` on :attr:`bot_data`,
|
||||
:meth:`telegram.ext.BasePersistence.refresh_chat_data` on :attr:`chat_data` and
|
||||
:meth:`telegram.ext.BasePersistence.refresh_user_data` on :attr:`user_data`, if
|
||||
appropriate.
|
||||
|
||||
.. versionadded:: 13.6
|
||||
"""
|
||||
if self.dispatcher.persistence:
|
||||
if self.dispatcher.persistence.store_bot_data:
|
||||
self.dispatcher.persistence.refresh_bot_data(self.bot_data)
|
||||
if self.dispatcher.persistence.store_chat_data and self._chat_id_and_data is not None:
|
||||
self.dispatcher.persistence.refresh_chat_data(*self._chat_id_and_data)
|
||||
if self.dispatcher.persistence.store_user_data and self._user_id_and_data is not None:
|
||||
self.dispatcher.persistence.refresh_user_data(*self._user_id_and_data)
|
||||
|
||||
def drop_callback_data(self, callback_query: CallbackQuery) -> None:
|
||||
"""
|
||||
Deletes the cached data for the specified callback query.
|
||||
|
||||
.. versionadded:: 13.6
|
||||
|
||||
Note:
|
||||
Will *not* raise exceptions in case the data is not found in the cache.
|
||||
*Will* raise :class:`KeyError` in case the callback query can not be found in the
|
||||
cache.
|
||||
|
||||
Args:
|
||||
callback_query (:class:`telegram.CallbackQuery`): The callback query.
|
||||
|
||||
Raises:
|
||||
KeyError | RuntimeError: :class:`KeyError`, if the callback query can not be found in
|
||||
the cache and :class:`RuntimeError`, if the bot doesn't allow for arbitrary
|
||||
callback data.
|
||||
"""
|
||||
if isinstance(self.bot, ExtBot):
|
||||
if not self.bot.arbitrary_callback_data:
|
||||
raise RuntimeError(
|
||||
'This telegram.ext.ExtBot instance does not use arbitrary callback data.'
|
||||
)
|
||||
self.bot.callback_data_cache.drop_data(callback_query)
|
||||
else:
|
||||
raise RuntimeError('telegram.Bot does not allow for arbitrary callback data.')
|
||||
|
||||
@classmethod
|
||||
def from_error(
|
||||
cls,
|
||||
cls: Type[CC],
|
||||
update: object,
|
||||
error: Exception,
|
||||
dispatcher: 'Dispatcher',
|
||||
async_args: Union[List, Tuple] = None,
|
||||
async_kwargs: Dict[str, object] = None,
|
||||
) -> 'CallbackContext':
|
||||
) -> CC:
|
||||
"""
|
||||
Constructs an instance of :class:`telegram.ext.CallbackContext` to be passed to the error
|
||||
handlers.
|
||||
|
||||
.. seealso:: :meth:`telegram.ext.Dispatcher.add_error_handler`
|
||||
|
||||
Args:
|
||||
update (:obj:`object` | :class:`telegram.Update`): The update associated with the
|
||||
error. May be :obj:`None`, e.g. for errors in job callbacks.
|
||||
error (:obj:`Exception`): The error.
|
||||
dispatcher (:class:`telegram.ext.Dispatcher`): The dispatcher associated with this
|
||||
context.
|
||||
async_args (List[:obj:`object`]): Optional. Positional arguments of the function that
|
||||
raised the error. Pass only when the raising function was run asynchronously using
|
||||
:meth:`telegram.ext.Dispatcher.run_async`.
|
||||
async_kwargs (Dict[:obj:`str`, :obj:`object`]): Optional. Keyword arguments of the
|
||||
function that raised the error. Pass only when the raising function was run
|
||||
asynchronously using :meth:`telegram.ext.Dispatcher.run_async`.
|
||||
|
||||
Returns:
|
||||
:class:`telegram.ext.CallbackContext`
|
||||
"""
|
||||
self = cls.from_update(update, dispatcher)
|
||||
self.error = error
|
||||
self.async_args = async_args
|
||||
@@ -157,7 +261,21 @@ class CallbackContext:
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def from_update(cls, update: object, dispatcher: 'Dispatcher') -> 'CallbackContext':
|
||||
def from_update(cls: Type[CC], update: object, dispatcher: 'Dispatcher') -> CC:
|
||||
"""
|
||||
Constructs an instance of :class:`telegram.ext.CallbackContext` to be passed to the
|
||||
handlers.
|
||||
|
||||
.. seealso:: :meth:`telegram.ext.Dispatcher.add_handler`
|
||||
|
||||
Args:
|
||||
update (:obj:`object` | :class:`telegram.Update`): The update.
|
||||
dispatcher (:class:`telegram.ext.Dispatcher`): The dispatcher associated with this
|
||||
context.
|
||||
|
||||
Returns:
|
||||
:class:`telegram.ext.CallbackContext`
|
||||
"""
|
||||
self = cls(dispatcher)
|
||||
|
||||
if update is not None and isinstance(update, Update):
|
||||
@@ -165,19 +283,45 @@ class CallbackContext:
|
||||
user = update.effective_user
|
||||
|
||||
if chat:
|
||||
self._chat_data = dispatcher.chat_data[chat.id] # pylint: disable=W0212
|
||||
self._chat_id_and_data = (
|
||||
chat.id,
|
||||
dispatcher.chat_data[chat.id], # pylint: disable=W0212
|
||||
)
|
||||
if user:
|
||||
self._user_data = dispatcher.user_data[user.id] # pylint: disable=W0212
|
||||
self._user_id_and_data = (
|
||||
user.id,
|
||||
dispatcher.user_data[user.id], # pylint: disable=W0212
|
||||
)
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def from_job(cls, job: 'Job', dispatcher: 'Dispatcher') -> 'CallbackContext':
|
||||
def from_job(cls: Type[CC], job: 'Job', dispatcher: 'Dispatcher') -> CC:
|
||||
"""
|
||||
Constructs an instance of :class:`telegram.ext.CallbackContext` to be passed to a
|
||||
job callback.
|
||||
|
||||
.. seealso:: :meth:`telegram.ext.JobQueue`
|
||||
|
||||
Args:
|
||||
job (:class:`telegram.ext.Job`): The job.
|
||||
dispatcher (:class:`telegram.ext.Dispatcher`): The dispatcher associated with this
|
||||
context.
|
||||
|
||||
Returns:
|
||||
:class:`telegram.ext.CallbackContext`
|
||||
"""
|
||||
self = cls(dispatcher)
|
||||
self.job = job
|
||||
return self
|
||||
|
||||
def update(self, data: Dict[str, object]) -> None:
|
||||
self.__dict__.update(data)
|
||||
"""Updates ``self.__slots__`` with the passed data.
|
||||
|
||||
Args:
|
||||
data (Dict[:obj:`str`, :obj:`object`]): The data.
|
||||
"""
|
||||
for key, value in data.items():
|
||||
setattr(self, key, value)
|
||||
|
||||
@property
|
||||
def bot(self) -> 'Bot':
|
||||
|
||||
@@ -0,0 +1,427 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2021
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2021
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains the CallbackDataCache class."""
|
||||
import logging
|
||||
import time
|
||||
from datetime import datetime
|
||||
from threading import Lock
|
||||
from typing import Dict, Tuple, Union, Optional, MutableMapping, TYPE_CHECKING, cast
|
||||
from uuid import uuid4
|
||||
|
||||
from cachetools import LRUCache # pylint: disable=E0401
|
||||
|
||||
from telegram import (
|
||||
InlineKeyboardMarkup,
|
||||
InlineKeyboardButton,
|
||||
TelegramError,
|
||||
CallbackQuery,
|
||||
Message,
|
||||
User,
|
||||
)
|
||||
from telegram.utils.helpers import to_float_timestamp
|
||||
from telegram.ext.utils.types import CDCData
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram.ext import ExtBot
|
||||
|
||||
|
||||
class InvalidCallbackData(TelegramError):
|
||||
"""
|
||||
Raised when the received callback data has been tempered with or deleted from cache.
|
||||
|
||||
.. versionadded:: 13.6
|
||||
|
||||
Args:
|
||||
callback_data (:obj:`int`, optional): The button data of which the callback data could not
|
||||
be found.
|
||||
|
||||
Attributes:
|
||||
callback_data (:obj:`int`): Optional. The button data of which the callback data could not
|
||||
be found.
|
||||
"""
|
||||
|
||||
__slots__ = ('callback_data',)
|
||||
|
||||
def __init__(self, callback_data: str = None) -> None:
|
||||
super().__init__(
|
||||
'The object belonging to this callback_data was deleted or the callback_data was '
|
||||
'manipulated.'
|
||||
)
|
||||
self.callback_data = callback_data
|
||||
|
||||
def __reduce__(self) -> Tuple[type, Tuple[Optional[str]]]: # type: ignore[override]
|
||||
return self.__class__, (self.callback_data,)
|
||||
|
||||
|
||||
class _KeyboardData:
|
||||
__slots__ = ('keyboard_uuid', 'button_data', 'access_time')
|
||||
|
||||
def __init__(
|
||||
self, keyboard_uuid: str, access_time: float = None, button_data: Dict[str, object] = None
|
||||
):
|
||||
self.keyboard_uuid = keyboard_uuid
|
||||
self.button_data = button_data or {}
|
||||
self.access_time = access_time or time.time()
|
||||
|
||||
def update_access_time(self) -> None:
|
||||
"""Updates the access time with the current time."""
|
||||
self.access_time = time.time()
|
||||
|
||||
def to_tuple(self) -> Tuple[str, float, Dict[str, object]]:
|
||||
"""Gives a tuple representation consisting of the keyboard uuid, the access time and the
|
||||
button data.
|
||||
"""
|
||||
return self.keyboard_uuid, self.access_time, self.button_data
|
||||
|
||||
|
||||
class CallbackDataCache:
|
||||
"""A custom cache for storing the callback data of a :class:`telegram.ext.ExtBot`. Internally,
|
||||
it keeps two mappings with fixed maximum size:
|
||||
|
||||
* One for mapping the data received in callback queries to the cached objects
|
||||
* One for mapping the IDs of received callback queries to the cached objects
|
||||
|
||||
The second mapping allows to manually drop data that has been cached for keyboards of messages
|
||||
sent via inline mode.
|
||||
If necessary, will drop the least recently used items.
|
||||
|
||||
.. versionadded:: 13.6
|
||||
|
||||
Args:
|
||||
bot (:class:`telegram.ext.ExtBot`): The bot this cache is for.
|
||||
maxsize (:obj:`int`, optional): Maximum number of items in each of the internal mappings.
|
||||
Defaults to 1024.
|
||||
persistent_data (:obj:`telegram.ext.utils.types.CDCData`, optional): Data to initialize
|
||||
the cache with, as returned by :meth:`telegram.ext.BasePersistence.get_callback_data`.
|
||||
|
||||
Attributes:
|
||||
bot (:class:`telegram.ext.ExtBot`): The bot this cache is for.
|
||||
maxsize (:obj:`int`): maximum size of the cache.
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ('bot', 'maxsize', '_keyboard_data', '_callback_queries', '__lock', 'logger')
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
bot: 'ExtBot',
|
||||
maxsize: int = 1024,
|
||||
persistent_data: CDCData = None,
|
||||
):
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
||||
self.bot = bot
|
||||
self.maxsize = maxsize
|
||||
self._keyboard_data: MutableMapping[str, _KeyboardData] = LRUCache(maxsize=maxsize)
|
||||
self._callback_queries: MutableMapping[str, str] = LRUCache(maxsize=maxsize)
|
||||
self.__lock = Lock()
|
||||
|
||||
if persistent_data:
|
||||
keyboard_data, callback_queries = persistent_data
|
||||
for key, value in callback_queries.items():
|
||||
self._callback_queries[key] = value
|
||||
for uuid, access_time, data in keyboard_data:
|
||||
self._keyboard_data[uuid] = _KeyboardData(
|
||||
keyboard_uuid=uuid, access_time=access_time, button_data=data
|
||||
)
|
||||
|
||||
@property
|
||||
def persistence_data(self) -> CDCData:
|
||||
""":obj:`telegram.ext.utils.types.CDCData`: The data that needs to be persisted to allow
|
||||
caching callback data across bot reboots.
|
||||
"""
|
||||
# While building a list/dict from the LRUCaches has linear runtime (in the number of
|
||||
# entries), the runtime is bounded by maxsize and it has the big upside of not throwing a
|
||||
# highly customized data structure at users trying to implement a custom persistence class
|
||||
with self.__lock:
|
||||
return [data.to_tuple() for data in self._keyboard_data.values()], dict(
|
||||
self._callback_queries.items()
|
||||
)
|
||||
|
||||
def process_keyboard(self, reply_markup: InlineKeyboardMarkup) -> InlineKeyboardMarkup:
|
||||
"""Registers the reply markup to the cache. If any of the buttons have
|
||||
:attr:`callback_data`, stores that data and builds a new keyboard with the correspondingly
|
||||
replaced buttons. Otherwise does nothing and returns the original reply markup.
|
||||
|
||||
Args:
|
||||
reply_markup (:class:`telegram.InlineKeyboardMarkup`): The keyboard.
|
||||
|
||||
Returns:
|
||||
:class:`telegram.InlineKeyboardMarkup`: The keyboard to be passed to Telegram.
|
||||
|
||||
"""
|
||||
with self.__lock:
|
||||
return self.__process_keyboard(reply_markup)
|
||||
|
||||
def __process_keyboard(self, reply_markup: InlineKeyboardMarkup) -> InlineKeyboardMarkup:
|
||||
keyboard_uuid = uuid4().hex
|
||||
keyboard_data = _KeyboardData(keyboard_uuid)
|
||||
|
||||
# Built a new nested list of buttons by replacing the callback data if needed
|
||||
buttons = [
|
||||
[
|
||||
# We create a new button instead of replacing callback_data in case the
|
||||
# same object is used elsewhere
|
||||
InlineKeyboardButton(
|
||||
btn.text,
|
||||
callback_data=self.__put_button(btn.callback_data, keyboard_data),
|
||||
)
|
||||
if btn.callback_data
|
||||
else btn
|
||||
for btn in column
|
||||
]
|
||||
for column in reply_markup.inline_keyboard
|
||||
]
|
||||
|
||||
if not keyboard_data.button_data:
|
||||
# If we arrive here, no data had to be replaced and we can return the input
|
||||
return reply_markup
|
||||
|
||||
self._keyboard_data[keyboard_uuid] = keyboard_data
|
||||
return InlineKeyboardMarkup(buttons)
|
||||
|
||||
@staticmethod
|
||||
def __put_button(callback_data: object, keyboard_data: _KeyboardData) -> str:
|
||||
"""Stores the data for a single button in :attr:`keyboard_data`.
|
||||
Returns the string that should be passed instead of the callback_data, which is
|
||||
``keyboard_uuid + button_uuids``.
|
||||
"""
|
||||
uuid = uuid4().hex
|
||||
keyboard_data.button_data[uuid] = callback_data
|
||||
return f'{keyboard_data.keyboard_uuid}{uuid}'
|
||||
|
||||
def __get_keyboard_uuid_and_button_data(
|
||||
self, callback_data: str
|
||||
) -> Union[Tuple[str, object], Tuple[None, InvalidCallbackData]]:
|
||||
keyboard, button = self.extract_uuids(callback_data)
|
||||
try:
|
||||
# we get the values before calling update() in case KeyErrors are raised
|
||||
# we don't want to update in that case
|
||||
keyboard_data = self._keyboard_data[keyboard]
|
||||
button_data = keyboard_data.button_data[button]
|
||||
# Update the timestamp for the LRU
|
||||
keyboard_data.update_access_time()
|
||||
return keyboard, button_data
|
||||
except KeyError:
|
||||
return None, InvalidCallbackData(callback_data)
|
||||
|
||||
@staticmethod
|
||||
def extract_uuids(callback_data: str) -> Tuple[str, str]:
|
||||
"""Extracts the keyboard uuid and the button uuid from the given ``callback_data``.
|
||||
|
||||
Args:
|
||||
callback_data (:obj:`str`): The ``callback_data`` as present in the button.
|
||||
|
||||
Returns:
|
||||
(:obj:`str`, :obj:`str`): Tuple of keyboard and button uuid
|
||||
|
||||
"""
|
||||
# Extract the uuids as put in __put_button
|
||||
return callback_data[:32], callback_data[32:]
|
||||
|
||||
def process_message(self, message: Message) -> None:
|
||||
"""Replaces the data in the inline keyboard attached to the message with the cached
|
||||
objects, if necessary. If the data could not be found,
|
||||
:class:`telegram.ext.InvalidCallbackData` will be inserted.
|
||||
|
||||
Note:
|
||||
Checks :attr:`telegram.Message.via_bot` and :attr:`telegram.Message.from_user` to check
|
||||
if the reply markup (if any) was actually sent by this caches bot. If it was not, the
|
||||
message will be returned unchanged.
|
||||
|
||||
Note that this will fail for channel posts, as :attr:`telegram.Message.from_user` is
|
||||
:obj:`None` for those! In the corresponding reply markups the callback data will be
|
||||
replaced by :class:`telegram.ext.InvalidCallbackData`.
|
||||
|
||||
Warning:
|
||||
* Does *not* consider :attr:`telegram.Message.reply_to_message` and
|
||||
:attr:`telegram.Message.pinned_message`. Pass them to these method separately.
|
||||
* *In place*, i.e. the passed :class:`telegram.Message` will be changed!
|
||||
|
||||
Args:
|
||||
message (:class:`telegram.Message`): The message.
|
||||
|
||||
"""
|
||||
with self.__lock:
|
||||
self.__process_message(message)
|
||||
|
||||
def __process_message(self, message: Message) -> Optional[str]:
|
||||
"""As documented in process_message, but returns the uuid of the attached keyboard, if any,
|
||||
which is relevant for process_callback_query.
|
||||
|
||||
**IN PLACE**
|
||||
"""
|
||||
if not message.reply_markup:
|
||||
return None
|
||||
|
||||
if message.via_bot:
|
||||
sender: Optional[User] = message.via_bot
|
||||
elif message.from_user:
|
||||
sender = message.from_user
|
||||
else:
|
||||
sender = None
|
||||
|
||||
if sender is not None and sender != self.bot.bot:
|
||||
return None
|
||||
|
||||
keyboard_uuid = None
|
||||
|
||||
for row in message.reply_markup.inline_keyboard:
|
||||
for button in row:
|
||||
if button.callback_data:
|
||||
button_data = cast(str, button.callback_data)
|
||||
keyboard_id, callback_data = self.__get_keyboard_uuid_and_button_data(
|
||||
button_data
|
||||
)
|
||||
# update_callback_data makes sure that the _id_attrs are updated
|
||||
button.update_callback_data(callback_data)
|
||||
|
||||
# This is lazy loaded. The firsts time we find a button
|
||||
# we load the associated keyboard - afterwards, there is
|
||||
if not keyboard_uuid and not isinstance(callback_data, InvalidCallbackData):
|
||||
keyboard_uuid = keyboard_id
|
||||
|
||||
return keyboard_uuid
|
||||
|
||||
def process_callback_query(self, callback_query: CallbackQuery) -> None:
|
||||
"""Replaces the data in the callback query and the attached messages keyboard with the
|
||||
cached objects, if necessary. If the data could not be found,
|
||||
:class:`telegram.ext.InvalidCallbackData` will be inserted.
|
||||
If :attr:`callback_query.data` or :attr:`callback_query.message` is present, this also
|
||||
saves the callback queries ID in order to be able to resolve it to the stored data.
|
||||
|
||||
Note:
|
||||
Also considers inserts data into the buttons of
|
||||
:attr:`telegram.Message.reply_to_message` and :attr:`telegram.Message.pinned_message`
|
||||
if necessary.
|
||||
|
||||
Warning:
|
||||
*In place*, i.e. the passed :class:`telegram.CallbackQuery` will be changed!
|
||||
|
||||
Args:
|
||||
callback_query (:class:`telegram.CallbackQuery`): The callback query.
|
||||
|
||||
"""
|
||||
with self.__lock:
|
||||
mapped = False
|
||||
|
||||
if callback_query.data:
|
||||
data = callback_query.data
|
||||
|
||||
# Get the cached callback data for the CallbackQuery
|
||||
keyboard_uuid, button_data = self.__get_keyboard_uuid_and_button_data(data)
|
||||
callback_query.data = button_data # type: ignore[assignment]
|
||||
|
||||
# Map the callback queries ID to the keyboards UUID for later use
|
||||
if not mapped and not isinstance(button_data, InvalidCallbackData):
|
||||
self._callback_queries[callback_query.id] = keyboard_uuid # type: ignore
|
||||
mapped = True
|
||||
|
||||
# Get the cached callback data for the inline keyboard attached to the
|
||||
# CallbackQuery.
|
||||
if callback_query.message:
|
||||
self.__process_message(callback_query.message)
|
||||
for message in (
|
||||
callback_query.message.pinned_message,
|
||||
callback_query.message.reply_to_message,
|
||||
):
|
||||
if message:
|
||||
self.__process_message(message)
|
||||
|
||||
def drop_data(self, callback_query: CallbackQuery) -> None:
|
||||
"""Deletes the data for the specified callback query.
|
||||
|
||||
Note:
|
||||
Will *not* raise exceptions in case the callback data is not found in the cache.
|
||||
*Will* raise :class:`KeyError` in case the callback query can not be found in the
|
||||
cache.
|
||||
|
||||
Args:
|
||||
callback_query (:class:`telegram.CallbackQuery`): The callback query.
|
||||
|
||||
Raises:
|
||||
KeyError: If the callback query can not be found in the cache
|
||||
"""
|
||||
with self.__lock:
|
||||
try:
|
||||
keyboard_uuid = self._callback_queries.pop(callback_query.id)
|
||||
self.__drop_keyboard(keyboard_uuid)
|
||||
except KeyError as exc:
|
||||
raise KeyError('CallbackQuery was not found in cache.') from exc
|
||||
|
||||
def __drop_keyboard(self, keyboard_uuid: str) -> None:
|
||||
try:
|
||||
self._keyboard_data.pop(keyboard_uuid)
|
||||
except KeyError:
|
||||
return
|
||||
|
||||
def clear_callback_data(self, time_cutoff: Union[float, datetime] = None) -> None:
|
||||
"""Clears the stored callback data.
|
||||
|
||||
Args:
|
||||
time_cutoff (:obj:`float` | :obj:`datetime.datetime`, optional): Pass a UNIX timestamp
|
||||
or a :obj:`datetime.datetime` to clear only entries which are older.
|
||||
For timezone naive :obj:`datetime.datetime` objects, the default timezone of the
|
||||
bot will be used.
|
||||
|
||||
"""
|
||||
with self.__lock:
|
||||
self.__clear(self._keyboard_data, time_cutoff=time_cutoff)
|
||||
|
||||
def clear_callback_queries(self) -> None:
|
||||
"""Clears the stored callback query IDs."""
|
||||
with self.__lock:
|
||||
self.__clear(self._callback_queries)
|
||||
|
||||
def __clear(self, mapping: MutableMapping, time_cutoff: Union[float, datetime] = None) -> None:
|
||||
if not time_cutoff:
|
||||
mapping.clear()
|
||||
return
|
||||
|
||||
if isinstance(time_cutoff, datetime):
|
||||
effective_cutoff = to_float_timestamp(
|
||||
time_cutoff, tzinfo=self.bot.defaults.tzinfo if self.bot.defaults else None
|
||||
)
|
||||
else:
|
||||
effective_cutoff = time_cutoff
|
||||
|
||||
# We need a list instead of a generator here, as the list doesn't change it's size
|
||||
# during the iteration
|
||||
to_drop = [key for key, data in mapping.items() if data.access_time < effective_cutoff]
|
||||
for key in to_drop:
|
||||
mapping.pop(key)
|
||||
@@ -35,26 +35,35 @@ from telegram import Update
|
||||
from telegram.utils.helpers import DefaultValue, DEFAULT_FALSE
|
||||
|
||||
from .handler import Handler
|
||||
from .utils.types import CCT
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram.ext import CallbackContext, Dispatcher
|
||||
from telegram.ext import Dispatcher
|
||||
|
||||
RT = TypeVar('RT')
|
||||
|
||||
|
||||
class CallbackQueryHandler(Handler[Update]):
|
||||
class CallbackQueryHandler(Handler[Update, CCT]):
|
||||
"""Handler class to handle Telegram callback queries. Optionally based on a regex.
|
||||
|
||||
Read the documentation of the ``re`` module for more information.
|
||||
|
||||
Note:
|
||||
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you
|
||||
can use to keep any data in will be sent to the :attr:`callback` function. Related to
|
||||
either the user or the chat that the update was sent in. For each update from the same user
|
||||
or in the same chat, it will be the same ``dict``.
|
||||
* :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.
|
||||
Note that this is DEPRECATED, and you should use context based callbacks. See
|
||||
https://git.io/fxJuV for more info.
|
||||
* If your bot allows arbitrary objects as ``callback_data``, it may happen that the
|
||||
original ``callback_data`` for the incoming :class:`telegram.CallbackQuery`` can not be
|
||||
found. This is the case when either a malicious client tempered with the
|
||||
``callback_data`` or the data was simply dropped from cache or not persisted. In these
|
||||
cases, an instance of :class:`telegram.ext.InvalidCallbackData` will be set as
|
||||
``callback_data``.
|
||||
|
||||
.. versionadded:: 13.6
|
||||
|
||||
Warning:
|
||||
When setting ``run_async`` to :obj:`True`, you cannot rely on adding custom
|
||||
@@ -79,9 +88,24 @@ class CallbackQueryHandler(Handler[Update]):
|
||||
:class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater`
|
||||
which can be used to schedule new jobs. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pattern (:obj:`str` | `Pattern`, optional): Regex pattern. If not :obj:`None`, ``re.match``
|
||||
is used on :attr:`telegram.CallbackQuery.data` to determine if an update should be
|
||||
handled by this handler.
|
||||
pattern (:obj:`str` | `Pattern` | :obj:`callable` | :obj:`type`, optional):
|
||||
Pattern to test :attr:`telegram.CallbackQuery.data` against. If a string or a regex
|
||||
pattern is passed, :meth:`re.match` is used on :attr:`telegram.CallbackQuery.data` to
|
||||
determine if an update should be handled by this handler. If your bot allows arbitrary
|
||||
objects as ``callback_data``, non-strings will be accepted. To filter arbitrary
|
||||
objects you may pass
|
||||
|
||||
* a callable, accepting exactly one argument, namely the
|
||||
:attr:`telegram.CallbackQuery.data`. It must return :obj:`True` or
|
||||
:obj:`False`/:obj:`None` to indicate, whether the update should be handled.
|
||||
* a :obj:`type`. If :attr:`telegram.CallbackQuery.data` is an instance of that type
|
||||
(or a subclass), the update will be handled.
|
||||
|
||||
If :attr:`telegram.CallbackQuery.data` is :obj:`None`, the
|
||||
:class:`telegram.CallbackQuery` update will not be handled.
|
||||
|
||||
.. versionchanged:: 13.6
|
||||
Added support for arbitrary callback data.
|
||||
pass_groups (:obj:`bool`, optional): If the callback should be passed the result of
|
||||
``re.match(pattern, data).groups()`` as a keyword argument called ``groups``.
|
||||
Default is :obj:`False`
|
||||
@@ -105,8 +129,11 @@ class CallbackQueryHandler(Handler[Update]):
|
||||
passed to the callback function.
|
||||
pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to
|
||||
the callback function.
|
||||
pattern (:obj:`str` | `Pattern`): Optional. Regex pattern to test
|
||||
:attr:`telegram.CallbackQuery.data` against.
|
||||
pattern (`Pattern` | :obj:`callable` | :obj:`type`): Optional. Regex pattern, callback or
|
||||
type to test :attr:`telegram.CallbackQuery.data` against.
|
||||
|
||||
.. versionchanged:: 13.6
|
||||
Added support for arbitrary callback data.
|
||||
pass_groups (:obj:`bool`): Determines whether ``groups`` will be passed to the
|
||||
callback function.
|
||||
pass_groupdict (:obj:`bool`): Determines whether ``groupdict``. will be passed to
|
||||
@@ -119,12 +146,14 @@ class CallbackQueryHandler(Handler[Update]):
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ('pattern', 'pass_groups', 'pass_groupdict')
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
callback: Callable[[Update, 'CallbackContext'], RT],
|
||||
callback: Callable[[Update, CCT], RT],
|
||||
pass_update_queue: bool = False,
|
||||
pass_job_queue: bool = False,
|
||||
pattern: Union[str, Pattern] = None,
|
||||
pattern: Union[str, Pattern, type, Callable[[object], Optional[bool]]] = None,
|
||||
pass_groups: bool = False,
|
||||
pass_groupdict: bool = False,
|
||||
pass_user_data: bool = False,
|
||||
@@ -158,11 +187,17 @@ class CallbackQueryHandler(Handler[Update]):
|
||||
|
||||
"""
|
||||
if isinstance(update, Update) and update.callback_query:
|
||||
callback_data = update.callback_query.data
|
||||
if self.pattern:
|
||||
if update.callback_query.data:
|
||||
match = re.match(self.pattern, update.callback_query.data)
|
||||
if match:
|
||||
return match
|
||||
if callback_data is None:
|
||||
return False
|
||||
if isinstance(self.pattern, type):
|
||||
return isinstance(callback_data, self.pattern)
|
||||
if callable(self.pattern):
|
||||
return self.pattern(callback_data)
|
||||
match = re.match(self.pattern, callback_data)
|
||||
if match:
|
||||
return match
|
||||
else:
|
||||
return True
|
||||
return None
|
||||
@@ -173,8 +208,12 @@ class CallbackQueryHandler(Handler[Update]):
|
||||
update: Update = None,
|
||||
check_result: Union[bool, Match] = None,
|
||||
) -> Dict[str, object]:
|
||||
"""Pass the results of ``re.match(pattern, data).{groups(), groupdict()}`` to the
|
||||
callback as a keyword arguments called ``groups`` and ``groupdict``, respectively, if
|
||||
needed.
|
||||
"""
|
||||
optional_args = super().collect_optional_args(dispatcher, update, check_result)
|
||||
if self.pattern:
|
||||
if self.pattern and not callable(self.pattern):
|
||||
check_result = cast(Match, check_result)
|
||||
if self.pass_groups:
|
||||
optional_args['groups'] = check_result.groups()
|
||||
@@ -184,11 +223,14 @@ class CallbackQueryHandler(Handler[Update]):
|
||||
|
||||
def collect_additional_context(
|
||||
self,
|
||||
context: 'CallbackContext',
|
||||
context: CCT,
|
||||
update: Update,
|
||||
dispatcher: 'Dispatcher',
|
||||
check_result: Union[bool, Match],
|
||||
) -> None:
|
||||
"""Add the result of ``re.match(pattern, update.callback_query.data)`` to
|
||||
:attr:`CallbackContext.matches` as list with one element.
|
||||
"""
|
||||
if self.pattern:
|
||||
check_result = cast(Match, check_result)
|
||||
context.matches = [check_result]
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2021
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains the ChatJoinRequestHandler class."""
|
||||
|
||||
|
||||
from telegram import Update
|
||||
|
||||
from .handler import Handler
|
||||
from .utils.types import CCT
|
||||
|
||||
|
||||
class ChatJoinRequestHandler(Handler[Update, CCT]):
|
||||
"""Handler class to handle Telegram updates that contain a chat join request.
|
||||
|
||||
Note:
|
||||
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you
|
||||
can use to keep any data in will be sent to the :attr:`callback` function. Related to
|
||||
either the user or the chat that the update was sent in. For each update from the same user
|
||||
or in the same chat, it will be the same ``dict``.
|
||||
|
||||
Note that this is DEPRECATED, and you should use context based callbacks. See
|
||||
https://git.io/fxJuV for more info.
|
||||
|
||||
Warning:
|
||||
When setting ``run_async`` to :obj:`True`, you cannot rely on adding custom
|
||||
attributes to :class:`telegram.ext.CallbackContext`. See its docs for more info.
|
||||
|
||||
.. versionadded:: 13.8
|
||||
|
||||
Args:
|
||||
callback (:obj:`callable`): The callback function for this handler. Will be called when
|
||||
:attr:`check_update` has determined that an update should be processed by this handler.
|
||||
Callback signature for context based API:
|
||||
|
||||
``def callback(update: Update, context: CallbackContext)``
|
||||
|
||||
The return value of the callback is usually ignored except for the special case of
|
||||
:class:`telegram.ext.ConversationHandler`.
|
||||
pass_update_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``update_queue`` will be passed to the callback function. It will be the ``Queue``
|
||||
instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher`
|
||||
that contains new updates which can be used to insert updates. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_job_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``job_queue`` will be passed to the callback function. It will be a
|
||||
:class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater`
|
||||
which can be used to schedule new jobs. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_user_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``user_data`` will be passed to the callback function. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
pass_chat_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
|
||||
``chat_data`` will be passed to the callback function. Default is :obj:`False`.
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
run_async (:obj:`bool`): Determines whether the callback will run asynchronously.
|
||||
Defaults to :obj:`False`.
|
||||
|
||||
Attributes:
|
||||
callback (:obj:`callable`): The callback function for this handler.
|
||||
pass_update_queue (:obj:`bool`): Determines whether ``update_queue`` will be
|
||||
passed to the callback function.
|
||||
pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to
|
||||
the callback function.
|
||||
pass_user_data (:obj:`bool`): Determines whether ``user_data`` will be passed to
|
||||
the callback function.
|
||||
pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to
|
||||
the callback function.
|
||||
run_async (:obj:`bool`): Determines whether the callback will run asynchronously.
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def check_update(self, update: object) -> bool:
|
||||
"""Determines whether an update should be passed to this handlers :attr:`callback`.
|
||||
|
||||
Args:
|
||||
update (:class:`telegram.Update` | :obj:`object`): Incoming update.
|
||||
|
||||
Returns:
|
||||
:obj:`bool`
|
||||
|
||||
"""
|
||||
return isinstance(update, Update) and bool(update.chat_join_request)
|
||||
@@ -17,19 +17,17 @@
|
||||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains the ChatMemberHandler classes."""
|
||||
from typing import ClassVar, TypeVar, Union, Callable, TYPE_CHECKING
|
||||
from typing import ClassVar, TypeVar, Union, Callable
|
||||
|
||||
from telegram import Update
|
||||
from telegram.utils.helpers import DefaultValue, DEFAULT_FALSE
|
||||
from .handler import Handler
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram.ext import CallbackContext
|
||||
from .utils.types import CCT
|
||||
|
||||
RT = TypeVar('RT')
|
||||
|
||||
|
||||
class ChatMemberHandler(Handler[Update]):
|
||||
class ChatMemberHandler(Handler[Update, CCT]):
|
||||
"""Handler class to handle Telegram updates that contain a chat member update.
|
||||
|
||||
.. versionadded:: 13.4
|
||||
@@ -96,6 +94,7 @@ class ChatMemberHandler(Handler[Update]):
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ('chat_member_types',)
|
||||
MY_CHAT_MEMBER: ClassVar[int] = -1
|
||||
""":obj:`int`: Used as a constant to handle only :attr:`telegram.Update.my_chat_member`."""
|
||||
CHAT_MEMBER: ClassVar[int] = 0
|
||||
@@ -106,7 +105,7 @@ class ChatMemberHandler(Handler[Update]):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
callback: Callable[[Update, 'CallbackContext'], RT],
|
||||
callback: Callable[[Update, CCT], RT],
|
||||
chat_member_types: int = MY_CHAT_MEMBER,
|
||||
pass_update_queue: bool = False,
|
||||
pass_job_queue: bool = False,
|
||||
|
||||
@@ -17,17 +17,22 @@
|
||||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains the ChosenInlineResultHandler class."""
|
||||
|
||||
from typing import Optional, TypeVar, Union
|
||||
import re
|
||||
from typing import Optional, TypeVar, Union, Callable, TYPE_CHECKING, Pattern, Match, cast
|
||||
|
||||
from telegram import Update
|
||||
|
||||
from telegram.utils.helpers import DefaultValue, DEFAULT_FALSE
|
||||
from .handler import Handler
|
||||
from .utils.types import CCT
|
||||
|
||||
RT = TypeVar('RT')
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram.ext import CallbackContext, Dispatcher
|
||||
|
||||
class ChosenInlineResultHandler(Handler[Update]):
|
||||
|
||||
class ChosenInlineResultHandler(Handler[Update, CCT]):
|
||||
"""Handler class to handle Telegram updates that contain a chosen inline result.
|
||||
|
||||
Note:
|
||||
@@ -70,6 +75,12 @@ class ChosenInlineResultHandler(Handler[Update]):
|
||||
DEPRECATED: Please switch to context based callbacks.
|
||||
run_async (:obj:`bool`): Determines whether the callback will run asynchronously.
|
||||
Defaults to :obj:`False`.
|
||||
pattern (:obj:`str` | `Pattern`, optional): Regex pattern. If not :obj:`None`, ``re.match``
|
||||
is used on :attr:`telegram.ChosenInlineResult.result_id` to determine if an update
|
||||
should be handled by this handler. This is accessible in the callback as
|
||||
:attr:`telegram.ext.CallbackContext.matches`.
|
||||
|
||||
.. versionadded:: 13.6
|
||||
|
||||
Attributes:
|
||||
callback (:obj:`callable`): The callback function for this handler.
|
||||
@@ -82,9 +93,39 @@ class ChosenInlineResultHandler(Handler[Update]):
|
||||
pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to
|
||||
the callback function.
|
||||
run_async (:obj:`bool`): Determines whether the callback will run asynchronously.
|
||||
pattern (`Pattern`): Optional. Regex pattern to test
|
||||
:attr:`telegram.ChosenInlineResult.result_id` against.
|
||||
|
||||
.. versionadded:: 13.6
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ('pattern',)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
callback: Callable[[Update, 'CallbackContext'], RT],
|
||||
pass_update_queue: bool = False,
|
||||
pass_job_queue: bool = False,
|
||||
pass_user_data: bool = False,
|
||||
pass_chat_data: bool = False,
|
||||
run_async: Union[bool, DefaultValue] = DEFAULT_FALSE,
|
||||
pattern: Union[str, Pattern] = None,
|
||||
):
|
||||
super().__init__(
|
||||
callback,
|
||||
pass_update_queue=pass_update_queue,
|
||||
pass_job_queue=pass_job_queue,
|
||||
pass_user_data=pass_user_data,
|
||||
pass_chat_data=pass_chat_data,
|
||||
run_async=run_async,
|
||||
)
|
||||
|
||||
if isinstance(pattern, str):
|
||||
pattern = re.compile(pattern)
|
||||
|
||||
self.pattern = pattern
|
||||
|
||||
def check_update(self, update: object) -> Optional[Union[bool, object]]:
|
||||
"""Determines whether an update should be passed to this handlers :attr:`callback`.
|
||||
|
||||
@@ -95,4 +136,25 @@ class ChosenInlineResultHandler(Handler[Update]):
|
||||
:obj:`bool`
|
||||
|
||||
"""
|
||||
return isinstance(update, Update) and update.chosen_inline_result
|
||||
if isinstance(update, Update) and update.chosen_inline_result:
|
||||
if self.pattern:
|
||||
match = re.match(self.pattern, update.chosen_inline_result.result_id)
|
||||
if match:
|
||||
return match
|
||||
else:
|
||||
return True
|
||||
return None
|
||||
|
||||
def collect_additional_context(
|
||||
self,
|
||||
context: 'CallbackContext',
|
||||
update: Update,
|
||||
dispatcher: 'Dispatcher',
|
||||
check_result: Union[bool, Match],
|
||||
) -> None:
|
||||
"""This function adds the matched regex pattern result to
|
||||
:attr:`telegram.ext.CallbackContext.matches`.
|
||||
"""
|
||||
if self.pattern:
|
||||
check_result = cast(Match, check_result)
|
||||
context.matches = [check_result]
|
||||
|
||||
@@ -27,15 +27,16 @@ from telegram.utils.deprecate import TelegramDeprecationWarning
|
||||
from telegram.utils.types import SLT
|
||||
from telegram.utils.helpers import DefaultValue, DEFAULT_FALSE
|
||||
|
||||
from .utils.types import CCT
|
||||
from .handler import Handler
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram.ext import CallbackContext, Dispatcher
|
||||
from telegram.ext import Dispatcher
|
||||
|
||||
RT = TypeVar('RT')
|
||||
|
||||
|
||||
class CommandHandler(Handler[Update]):
|
||||
class CommandHandler(Handler[Update, CCT]):
|
||||
"""Handler class to handle Telegram commands.
|
||||
|
||||
Commands are Telegram messages that start with ``/``, optionally followed by an ``@`` and the
|
||||
@@ -129,10 +130,12 @@ class CommandHandler(Handler[Update]):
|
||||
run_async (:obj:`bool`): Determines whether the callback will run asynchronously.
|
||||
"""
|
||||
|
||||
__slots__ = ('command', 'filters', 'pass_args')
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
command: SLT[str],
|
||||
callback: Callable[[Update, 'CallbackContext'], RT],
|
||||
callback: Callable[[Update, CCT], RT],
|
||||
filters: BaseFilter = None,
|
||||
allow_edited: bool = None,
|
||||
pass_args: bool = False,
|
||||
@@ -219,6 +222,9 @@ class CommandHandler(Handler[Update]):
|
||||
update: Update = None,
|
||||
check_result: Optional[Union[bool, Tuple[List[str], Optional[bool]]]] = None,
|
||||
) -> Dict[str, object]:
|
||||
"""Provide text after the command to the callback the ``args`` argument as list, split on
|
||||
single whitespaces.
|
||||
"""
|
||||
optional_args = super().collect_optional_args(dispatcher, update)
|
||||
if self.pass_args and isinstance(check_result, tuple):
|
||||
optional_args['args'] = check_result[0]
|
||||
@@ -226,11 +232,14 @@ class CommandHandler(Handler[Update]):
|
||||
|
||||
def collect_additional_context(
|
||||
self,
|
||||
context: 'CallbackContext',
|
||||
context: CCT,
|
||||
update: Update,
|
||||
dispatcher: 'Dispatcher',
|
||||
check_result: Optional[Union[bool, Tuple[List[str], Optional[bool]]]],
|
||||
) -> None:
|
||||
"""Add text after the command to :attr:`CallbackContext.args` as list, split on single
|
||||
whitespaces and add output of data filters to :attr:`CallbackContext` as well.
|
||||
"""
|
||||
if isinstance(check_result, tuple):
|
||||
context.args = check_result[0]
|
||||
if isinstance(check_result[1], dict):
|
||||
@@ -238,7 +247,7 @@ class CommandHandler(Handler[Update]):
|
||||
|
||||
|
||||
class PrefixHandler(CommandHandler):
|
||||
"""Handler class to handle custom prefix commands
|
||||
"""Handler class to handle custom prefix commands.
|
||||
|
||||
This is a intermediate handler between :class:`MessageHandler` and :class:`CommandHandler`.
|
||||
It supports configurable commands with the same options as CommandHandler. It will respond to
|
||||
@@ -265,7 +274,7 @@ class PrefixHandler(CommandHandler):
|
||||
.. code:: python
|
||||
|
||||
PrefixHandler(['!', '#'], ['test', 'help'], callback) # will respond to '!test', \
|
||||
'#test', '!help' and '#help'.
|
||||
'#test', '!help' and '#help'.
|
||||
|
||||
|
||||
By default the handler listens to messages as well as edited messages. To change this behavior
|
||||
@@ -344,11 +353,14 @@ class PrefixHandler(CommandHandler):
|
||||
|
||||
"""
|
||||
|
||||
# 'prefix' is a class property, & 'command' is included in the superclass, so they're left out.
|
||||
__slots__ = ('_prefix', '_command', '_commands')
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
prefix: SLT[str],
|
||||
command: SLT[str],
|
||||
callback: Callable[[Update, 'CallbackContext'], RT],
|
||||
callback: Callable[[Update, CCT], RT],
|
||||
filters: BaseFilter = None,
|
||||
pass_args: bool = False,
|
||||
pass_update_queue: bool = False,
|
||||
@@ -442,15 +454,3 @@ class PrefixHandler(CommandHandler):
|
||||
return text_list[1:], filter_result
|
||||
return False
|
||||
return None
|
||||
|
||||
def collect_additional_context(
|
||||
self,
|
||||
context: 'CallbackContext',
|
||||
update: Update,
|
||||
dispatcher: 'Dispatcher',
|
||||
check_result: Optional[Union[bool, Tuple[List[str], Optional[bool]]]],
|
||||
) -> None:
|
||||
if isinstance(check_result, tuple):
|
||||
context.args = check_result[0]
|
||||
if isinstance(check_result[1], dict):
|
||||
context.update(check_result[1])
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2020
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
# pylint: disable=R0201
|
||||
"""This module contains the auxiliary class ContextTypes."""
|
||||
from typing import Type, Generic, overload, Dict # pylint: disable=W0611
|
||||
|
||||
from telegram.ext.callbackcontext import CallbackContext
|
||||
from telegram.ext.utils.types import CCT, UD, CD, BD
|
||||
|
||||
|
||||
class ContextTypes(Generic[CCT, UD, CD, BD]):
|
||||
"""
|
||||
Convenience class to gather customizable types of the :class:`telegram.ext.CallbackContext`
|
||||
interface.
|
||||
|
||||
.. versionadded:: 13.6
|
||||
|
||||
Args:
|
||||
context (:obj:`type`, optional): Determines the type of the ``context`` argument of all
|
||||
(error-)handler callbacks and job callbacks. Must be a subclass of
|
||||
:class:`telegram.ext.CallbackContext`. Defaults to
|
||||
:class:`telegram.ext.CallbackContext`.
|
||||
bot_data (:obj:`type`, optional): Determines the type of ``context.bot_data`` of all
|
||||
(error-)handler callbacks and job callbacks. Defaults to :obj:`dict`. Must support
|
||||
instantiating without arguments.
|
||||
chat_data (:obj:`type`, optional): Determines the type of ``context.chat_data`` of all
|
||||
(error-)handler callbacks and job callbacks. Defaults to :obj:`dict`. Must support
|
||||
instantiating without arguments.
|
||||
user_data (:obj:`type`, optional): Determines the type of ``context.user_data`` of all
|
||||
(error-)handler callbacks and job callbacks. Defaults to :obj:`dict`. Must support
|
||||
instantiating without arguments.
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ('_context', '_bot_data', '_chat_data', '_user_data')
|
||||
|
||||
# overload signatures generated with https://git.io/JtJPj
|
||||
|
||||
@overload
|
||||
def __init__(
|
||||
self: 'ContextTypes[CallbackContext[Dict, Dict, Dict], Dict, Dict, Dict]',
|
||||
):
|
||||
...
|
||||
|
||||
@overload
|
||||
def __init__(self: 'ContextTypes[CCT, Dict, Dict, Dict]', context: Type[CCT]):
|
||||
...
|
||||
|
||||
@overload
|
||||
def __init__(
|
||||
self: 'ContextTypes[CallbackContext[UD, Dict, Dict], UD, Dict, Dict]', user_data: Type[UD]
|
||||
):
|
||||
...
|
||||
|
||||
@overload
|
||||
def __init__(
|
||||
self: 'ContextTypes[CallbackContext[Dict, CD, Dict], Dict, CD, Dict]', chat_data: Type[CD]
|
||||
):
|
||||
...
|
||||
|
||||
@overload
|
||||
def __init__(
|
||||
self: 'ContextTypes[CallbackContext[Dict, Dict, BD], Dict, Dict, BD]', bot_data: Type[BD]
|
||||
):
|
||||
...
|
||||
|
||||
@overload
|
||||
def __init__(
|
||||
self: 'ContextTypes[CCT, UD, Dict, Dict]', context: Type[CCT], user_data: Type[UD]
|
||||
):
|
||||
...
|
||||
|
||||
@overload
|
||||
def __init__(
|
||||
self: 'ContextTypes[CCT, Dict, CD, Dict]', context: Type[CCT], chat_data: Type[CD]
|
||||
):
|
||||
...
|
||||
|
||||
@overload
|
||||
def __init__(
|
||||
self: 'ContextTypes[CCT, Dict, Dict, BD]', context: Type[CCT], bot_data: Type[BD]
|
||||
):
|
||||
...
|
||||
|
||||
@overload
|
||||
def __init__(
|
||||
self: 'ContextTypes[CallbackContext[UD, CD, Dict], UD, CD, Dict]',
|
||||
user_data: Type[UD],
|
||||
chat_data: Type[CD],
|
||||
):
|
||||
...
|
||||
|
||||
@overload
|
||||
def __init__(
|
||||
self: 'ContextTypes[CallbackContext[UD, Dict, BD], UD, Dict, BD]',
|
||||
user_data: Type[UD],
|
||||
bot_data: Type[BD],
|
||||
):
|
||||
...
|
||||
|
||||
@overload
|
||||
def __init__(
|
||||
self: 'ContextTypes[CallbackContext[Dict, CD, BD], Dict, CD, BD]',
|
||||
chat_data: Type[CD],
|
||||
bot_data: Type[BD],
|
||||
):
|
||||
...
|
||||
|
||||
@overload
|
||||
def __init__(
|
||||
self: 'ContextTypes[CCT, UD, CD, Dict]',
|
||||
context: Type[CCT],
|
||||
user_data: Type[UD],
|
||||
chat_data: Type[CD],
|
||||
):
|
||||
...
|
||||
|
||||
@overload
|
||||
def __init__(
|
||||
self: 'ContextTypes[CCT, UD, Dict, BD]',
|
||||
context: Type[CCT],
|
||||
user_data: Type[UD],
|
||||
bot_data: Type[BD],
|
||||
):
|
||||
...
|
||||
|
||||
@overload
|
||||
def __init__(
|
||||
self: 'ContextTypes[CCT, Dict, CD, BD]',
|
||||
context: Type[CCT],
|
||||
chat_data: Type[CD],
|
||||
bot_data: Type[BD],
|
||||
):
|
||||
...
|
||||
|
||||
@overload
|
||||
def __init__(
|
||||
self: 'ContextTypes[CallbackContext[UD, CD, BD], UD, CD, BD]',
|
||||
user_data: Type[UD],
|
||||
chat_data: Type[CD],
|
||||
bot_data: Type[BD],
|
||||
):
|
||||
...
|
||||
|
||||
@overload
|
||||
def __init__(
|
||||
self: 'ContextTypes[CCT, UD, CD, BD]',
|
||||
context: Type[CCT],
|
||||
user_data: Type[UD],
|
||||
chat_data: Type[CD],
|
||||
bot_data: Type[BD],
|
||||
):
|
||||
...
|
||||
|
||||
def __init__( # type: ignore[no-untyped-def]
|
||||
self,
|
||||
context=CallbackContext,
|
||||
bot_data=dict,
|
||||
chat_data=dict,
|
||||
user_data=dict,
|
||||
):
|
||||
if not issubclass(context, CallbackContext):
|
||||
raise ValueError('context must be a subclass of CallbackContext.')
|
||||
|
||||
# We make all those only accessible via properties because we don't currently support
|
||||
# changing this at runtime, so overriding the attributes doesn't make sense
|
||||
self._context = context
|
||||
self._bot_data = bot_data
|
||||
self._chat_data = chat_data
|
||||
self._user_data = user_data
|
||||
|
||||
@property
|
||||
def context(self) -> Type[CCT]:
|
||||
return self._context
|
||||
|
||||
@property
|
||||
def bot_data(self) -> Type[BD]:
|
||||
return self._bot_data
|
||||
|
||||
@property
|
||||
def chat_data(self) -> Type[CD]:
|
||||
return self._chat_data
|
||||
|
||||
@property
|
||||
def user_data(self) -> Type[UD]:
|
||||
return self._user_data
|
||||
@@ -37,7 +37,8 @@ from telegram.ext import (
|
||||
InlineQueryHandler,
|
||||
)
|
||||
from telegram.ext.utils.promise import Promise
|
||||
from telegram.utils.types import ConversationDict
|
||||
from telegram.ext.utils.types import ConversationDict
|
||||
from telegram.ext.utils.types import CCT
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram.ext import Dispatcher, Job
|
||||
@@ -45,6 +46,9 @@ CheckUpdateType = Optional[Tuple[Tuple[int, ...], Handler, object]]
|
||||
|
||||
|
||||
class _ConversationTimeoutContext:
|
||||
# '__dict__' is not included since this a private class
|
||||
__slots__ = ('conversation_key', 'update', 'dispatcher', 'callback_context')
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
conversation_key: Tuple[int, ...],
|
||||
@@ -58,7 +62,7 @@ class _ConversationTimeoutContext:
|
||||
self.callback_context = callback_context
|
||||
|
||||
|
||||
class ConversationHandler(Handler[Update]):
|
||||
class ConversationHandler(Handler[Update, CCT]):
|
||||
"""
|
||||
A handler to hold a conversation with a single or multiple users through Telegram updates by
|
||||
managing four collections of other handlers.
|
||||
@@ -173,33 +177,8 @@ class ConversationHandler(Handler[Update]):
|
||||
ValueError
|
||||
|
||||
Attributes:
|
||||
entry_points (List[:class:`telegram.ext.Handler`]): A list of ``Handler`` objects that can
|
||||
trigger the start of the conversation.
|
||||
states (Dict[:obj:`object`, List[:class:`telegram.ext.Handler`]]): A :obj:`dict` that
|
||||
defines the different states of conversation a user can be in and one or more
|
||||
associated ``Handler`` objects that should be used in that state.
|
||||
fallbacks (List[:class:`telegram.ext.Handler`]): A list of handlers that might be used if
|
||||
the user is in a conversation, but every handler for their current state returned
|
||||
:obj:`False` on :attr:`check_update`.
|
||||
allow_reentry (:obj:`bool`): Determines if a user can restart a conversation with
|
||||
an entry point.
|
||||
per_chat (:obj:`bool`): If the conversationkey should contain the Chat's ID.
|
||||
per_user (:obj:`bool`): If the conversationkey should contain the User's ID.
|
||||
per_message (:obj:`bool`): If the conversationkey should contain the Message's
|
||||
ID.
|
||||
conversation_timeout (:obj:`float` | :obj:`datetime.timedelta`): Optional. When this
|
||||
handler is inactive more than this timeout (in seconds), it will be automatically
|
||||
ended. If this value is 0 (default), there will be no timeout. When it's triggered, the
|
||||
last received update and the corresponding ``context`` will be handled by ALL the
|
||||
handler's who's :attr:`check_update` method returns :obj:`True` that are in the state
|
||||
:attr:`ConversationHandler.TIMEOUT`.
|
||||
name (:obj:`str`): Optional. The name for this conversationhandler. Required for
|
||||
persistence
|
||||
persistent (:obj:`bool`): Optional. If the conversations dict for this handler should be
|
||||
saved. Name is required and persistence has to be set in :class:`telegram.ext.Updater`
|
||||
map_to_parent (Dict[:obj:`object`, :obj:`object`]): Optional. A :obj:`dict` that can be
|
||||
used to instruct a nested conversationhandler to transition into a mapped state on
|
||||
its parent conversationhandler in place of a specified nested state.
|
||||
run_async (:obj:`bool`): If :obj:`True`, will override the
|
||||
:attr:`Handler.run_async` setting of all internal handlers on initialization.
|
||||
|
||||
@@ -207,6 +186,26 @@ class ConversationHandler(Handler[Update]):
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
'_entry_points',
|
||||
'_states',
|
||||
'_fallbacks',
|
||||
'_allow_reentry',
|
||||
'_per_user',
|
||||
'_per_chat',
|
||||
'_per_message',
|
||||
'_conversation_timeout',
|
||||
'_name',
|
||||
'persistent',
|
||||
'_persistence',
|
||||
'_map_to_parent',
|
||||
'timeout_jobs',
|
||||
'_timeout_jobs_lock',
|
||||
'_conversations',
|
||||
'_conversations_lock',
|
||||
'logger',
|
||||
)
|
||||
|
||||
END: ClassVar[int] = -1
|
||||
""":obj:`int`: Used as a constant to return when a conversation is ended."""
|
||||
TIMEOUT: ClassVar[int] = -2
|
||||
@@ -217,9 +216,9 @@ class ConversationHandler(Handler[Update]):
|
||||
# pylint: disable=W0231
|
||||
def __init__(
|
||||
self,
|
||||
entry_points: List[Handler],
|
||||
states: Dict[object, List[Handler]],
|
||||
fallbacks: List[Handler],
|
||||
entry_points: List[Handler[Update, CCT]],
|
||||
states: Dict[object, List[Handler[Update, CCT]]],
|
||||
fallbacks: List[Handler[Update, CCT]],
|
||||
allow_reentry: bool = False,
|
||||
per_chat: bool = True,
|
||||
per_user: bool = True,
|
||||
@@ -316,6 +315,9 @@ class ConversationHandler(Handler[Update]):
|
||||
|
||||
@property
|
||||
def entry_points(self) -> List[Handler]:
|
||||
"""List[:class:`telegram.ext.Handler`]: A list of ``Handler`` objects that can trigger the
|
||||
start of the conversation.
|
||||
"""
|
||||
return self._entry_points
|
||||
|
||||
@entry_points.setter
|
||||
@@ -324,6 +326,10 @@ class ConversationHandler(Handler[Update]):
|
||||
|
||||
@property
|
||||
def states(self) -> Dict[object, List[Handler]]:
|
||||
"""Dict[:obj:`object`, List[:class:`telegram.ext.Handler`]]: A :obj:`dict` that
|
||||
defines the different states of conversation a user can be in and one or more
|
||||
associated ``Handler`` objects that should be used in that state.
|
||||
"""
|
||||
return self._states
|
||||
|
||||
@states.setter
|
||||
@@ -332,6 +338,10 @@ class ConversationHandler(Handler[Update]):
|
||||
|
||||
@property
|
||||
def fallbacks(self) -> List[Handler]:
|
||||
"""List[:class:`telegram.ext.Handler`]: A list of handlers that might be used if
|
||||
the user is in a conversation, but every handler for their current state returned
|
||||
:obj:`False` on :attr:`check_update`.
|
||||
"""
|
||||
return self._fallbacks
|
||||
|
||||
@fallbacks.setter
|
||||
@@ -340,6 +350,7 @@ class ConversationHandler(Handler[Update]):
|
||||
|
||||
@property
|
||||
def allow_reentry(self) -> bool:
|
||||
""":obj:`bool`: Determines if a user can restart a conversation with an entry point."""
|
||||
return self._allow_reentry
|
||||
|
||||
@allow_reentry.setter
|
||||
@@ -348,6 +359,7 @@ class ConversationHandler(Handler[Update]):
|
||||
|
||||
@property
|
||||
def per_user(self) -> bool:
|
||||
""":obj:`bool`: If the conversation key should contain the User's ID."""
|
||||
return self._per_user
|
||||
|
||||
@per_user.setter
|
||||
@@ -356,6 +368,7 @@ class ConversationHandler(Handler[Update]):
|
||||
|
||||
@property
|
||||
def per_chat(self) -> bool:
|
||||
""":obj:`bool`: If the conversation key should contain the Chat's ID."""
|
||||
return self._per_chat
|
||||
|
||||
@per_chat.setter
|
||||
@@ -364,6 +377,7 @@ class ConversationHandler(Handler[Update]):
|
||||
|
||||
@property
|
||||
def per_message(self) -> bool:
|
||||
""":obj:`bool`: If the conversation key should contain the message's ID."""
|
||||
return self._per_message
|
||||
|
||||
@per_message.setter
|
||||
@@ -374,16 +388,21 @@ class ConversationHandler(Handler[Update]):
|
||||
def conversation_timeout(
|
||||
self,
|
||||
) -> Optional[Union[float, datetime.timedelta]]:
|
||||
""":obj:`float` | :obj:`datetime.timedelta`: Optional. When this
|
||||
handler is inactive more than this timeout (in seconds), it will be automatically
|
||||
ended.
|
||||
"""
|
||||
return self._conversation_timeout
|
||||
|
||||
@conversation_timeout.setter
|
||||
def conversation_timeout(self, value: object) -> NoReturn:
|
||||
raise ValueError(
|
||||
'You can not assign a new value to conversation_timeout after ' 'initialization.'
|
||||
'You can not assign a new value to conversation_timeout after initialization.'
|
||||
)
|
||||
|
||||
@property
|
||||
def name(self) -> Optional[str]:
|
||||
""":obj:`str`: Optional. The name for this :class:`ConversationHandler`."""
|
||||
return self._name
|
||||
|
||||
@name.setter
|
||||
@@ -392,6 +411,10 @@ class ConversationHandler(Handler[Update]):
|
||||
|
||||
@property
|
||||
def map_to_parent(self) -> Optional[Dict[object, object]]:
|
||||
"""Dict[:obj:`object`, :obj:`object`]: Optional. A :obj:`dict` that can be
|
||||
used to instruct a nested :class:`ConversationHandler` to transition into a mapped state on
|
||||
its parent :class:`ConversationHandler` in place of a specified nested state.
|
||||
"""
|
||||
return self._map_to_parent
|
||||
|
||||
@map_to_parent.setter
|
||||
@@ -400,6 +423,7 @@ class ConversationHandler(Handler[Update]):
|
||||
|
||||
@property
|
||||
def persistence(self) -> Optional[BasePersistence]:
|
||||
"""The persistence class as provided by the :class:`Dispatcher`."""
|
||||
return self._persistence
|
||||
|
||||
@persistence.setter
|
||||
@@ -412,7 +436,7 @@ class ConversationHandler(Handler[Update]):
|
||||
handler.persistence = self.persistence
|
||||
|
||||
@property
|
||||
def conversations(self) -> ConversationDict:
|
||||
def conversations(self) -> ConversationDict: # skipcq: PY-D0003
|
||||
return self._conversations
|
||||
|
||||
@conversations.setter
|
||||
@@ -518,7 +542,7 @@ class ConversationHandler(Handler[Update]):
|
||||
# check if promise is finished or not
|
||||
if state[1].done.wait(0):
|
||||
res = self._resolve_promise(state)
|
||||
self.update_state(res, key)
|
||||
self._update_state(res, key)
|
||||
with self._conversations_lock:
|
||||
state = self.conversations.get(key)
|
||||
|
||||
@@ -627,19 +651,19 @@ class ConversationHandler(Handler[Update]):
|
||||
)
|
||||
|
||||
if isinstance(self.map_to_parent, dict) and new_state in self.map_to_parent:
|
||||
self.update_state(self.END, conversation_key)
|
||||
self._update_state(self.END, conversation_key)
|
||||
if raise_dp_handler_stop:
|
||||
raise DispatcherHandlerStop(self.map_to_parent.get(new_state))
|
||||
return self.map_to_parent.get(new_state)
|
||||
|
||||
self.update_state(new_state, conversation_key)
|
||||
self._update_state(new_state, conversation_key)
|
||||
if raise_dp_handler_stop:
|
||||
# Don't pass the new state here. If we're in a nested conversation, the parent is
|
||||
# expecting None as return value.
|
||||
raise DispatcherHandlerStop()
|
||||
return None
|
||||
|
||||
def update_state(self, new_state: object, key: Tuple[int, ...]) -> None:
|
||||
def _update_state(self, new_state: object, key: Tuple[int, ...]) -> None:
|
||||
if new_state == self.END:
|
||||
with self._conversations_lock:
|
||||
if key in self.conversations:
|
||||
@@ -698,4 +722,4 @@ class ConversationHandler(Handler[Update]):
|
||||
'ConversationHandler has no effect. Ignoring.'
|
||||
)
|
||||
|
||||
self.update_state(self.END, ctxt.conversation_key)
|
||||
self._update_state(self.END, ctxt.conversation_key)
|
||||
|
||||
+54
-30
@@ -22,6 +22,7 @@ from typing import NoReturn, Optional, Dict, Any
|
||||
|
||||
import pytz
|
||||
|
||||
from telegram.utils.deprecate import set_new_attribute_deprecated
|
||||
from telegram.utils.helpers import DEFAULT_NONE
|
||||
from telegram.utils.types import ODVInput
|
||||
|
||||
@@ -41,6 +42,9 @@ class Defaults:
|
||||
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as the
|
||||
read timeout from the server (instead of the one specified during creation of the
|
||||
connection pool).
|
||||
|
||||
Note:
|
||||
Will *not* be used for :meth:`telegram.Bot.get_updates`!
|
||||
quote (:obj:`bool`, optional): If set to :obj:`True`, the reply is sent as an actual reply
|
||||
to the message. If ``reply_to_message_id`` is passed in ``kwargs``, this parameter will
|
||||
be ignored. Default: :obj:`True` in group chats and :obj:`False` in private chats.
|
||||
@@ -48,37 +52,24 @@ class Defaults:
|
||||
appearing throughout PTB, i.e. if a timezone naive date(time) object is passed
|
||||
somewhere, it will be assumed to be in ``tzinfo``. Must be a timezone provided by the
|
||||
``pytz`` module. Defaults to UTC.
|
||||
|
||||
Note:
|
||||
Will *not* be used for :meth:`telegram.Bot.get_updates`!
|
||||
run_async (:obj:`bool`, optional): Default setting for the ``run_async`` parameter of
|
||||
handlers and error handlers registered through :meth:`Dispatcher.add_handler` and
|
||||
:meth:`Dispatcher.add_error_handler`. Defaults to :obj:`False`.
|
||||
|
||||
Attributes:
|
||||
parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show
|
||||
bold, italic, fixed-width text or URLs in your bot's message.
|
||||
explanation_parse_mode (:obj:`str`): Optional. Alias for :attr:`parse_mode`, used for
|
||||
the corresponding parameter of :meth:`telegram.Bot.send_poll`.
|
||||
disable_notification (:obj:`bool`): Optional. Sends the message silently. Users will
|
||||
receive a notification with no sound.
|
||||
disable_web_page_preview (:obj:`bool`): Optional. Disables link previews for links in this
|
||||
message.
|
||||
allow_sending_without_reply (:obj:`bool`): Optional. Pass :obj:`True`, if the message
|
||||
should be sent even if the specified replied-to message is not found.
|
||||
timeout (:obj:`int` | :obj:`float`): Optional. If this value is specified, use it as the
|
||||
read timeout from the server (instead of the one specified during creation of the
|
||||
connection pool).
|
||||
quote (:obj:`bool`): Optional. If set to :obj:`True`, the reply is sent as an actual reply
|
||||
to the message. If ``reply_to_message_id`` is passed in ``kwargs``, this parameter will
|
||||
be ignored. Default: :obj:`True` in group chats and :obj:`False` in private chats.
|
||||
tzinfo (:obj:`tzinfo`): A timezone to be used for all date(time) objects appearing
|
||||
throughout PTB.
|
||||
run_async (:obj:`bool`): Optional. Default setting for the ``run_async`` parameter of
|
||||
handlers and error handlers registered through :meth:`Dispatcher.add_handler` and
|
||||
:meth:`Dispatcher.add_error_handler`.
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
'_timeout',
|
||||
'_tzinfo',
|
||||
'_disable_web_page_preview',
|
||||
'_run_async',
|
||||
'_quote',
|
||||
'_disable_notification',
|
||||
'_allow_sending_without_reply',
|
||||
'_parse_mode',
|
||||
'_api_defaults',
|
||||
'__dict__',
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
parse_mode: str = None,
|
||||
@@ -114,15 +105,21 @@ class Defaults:
|
||||
if value not in [None, DEFAULT_NONE]:
|
||||
self._api_defaults[kwarg] = value
|
||||
# Special casing, as None is a valid default value
|
||||
if self.timeout != DEFAULT_NONE:
|
||||
self._api_defaults['timeout'] = self.timeout
|
||||
if self._timeout != DEFAULT_NONE:
|
||||
self._api_defaults['timeout'] = self._timeout
|
||||
|
||||
def __setattr__(self, key: str, value: object) -> None:
|
||||
set_new_attribute_deprecated(self, key, value)
|
||||
|
||||
@property
|
||||
def api_defaults(self) -> Dict[str, Any]:
|
||||
def api_defaults(self) -> Dict[str, Any]: # skip-cq: PY-D0003
|
||||
return self._api_defaults
|
||||
|
||||
@property
|
||||
def parse_mode(self) -> Optional[str]:
|
||||
""":obj:`str`: Optional. Send Markdown or HTML, if you want Telegram apps to show
|
||||
bold, italic, fixed-width text or URLs in your bot's message.
|
||||
"""
|
||||
return self._parse_mode
|
||||
|
||||
@parse_mode.setter
|
||||
@@ -134,6 +131,9 @@ class Defaults:
|
||||
|
||||
@property
|
||||
def explanation_parse_mode(self) -> Optional[str]:
|
||||
""":obj:`str`: Optional. Alias for :attr:`parse_mode`, used for
|
||||
the corresponding parameter of :meth:`telegram.Bot.send_poll`.
|
||||
"""
|
||||
return self._parse_mode
|
||||
|
||||
@explanation_parse_mode.setter
|
||||
@@ -145,6 +145,9 @@ class Defaults:
|
||||
|
||||
@property
|
||||
def disable_notification(self) -> Optional[bool]:
|
||||
""":obj:`bool`: Optional. Sends the message silently. Users will
|
||||
receive a notification with no sound.
|
||||
"""
|
||||
return self._disable_notification
|
||||
|
||||
@disable_notification.setter
|
||||
@@ -156,6 +159,9 @@ class Defaults:
|
||||
|
||||
@property
|
||||
def disable_web_page_preview(self) -> Optional[bool]:
|
||||
""":obj:`bool`: Optional. Disables link previews for links in this
|
||||
message.
|
||||
"""
|
||||
return self._disable_web_page_preview
|
||||
|
||||
@disable_web_page_preview.setter
|
||||
@@ -167,6 +173,9 @@ class Defaults:
|
||||
|
||||
@property
|
||||
def allow_sending_without_reply(self) -> Optional[bool]:
|
||||
""":obj:`bool`: Optional. Pass :obj:`True`, if the message
|
||||
should be sent even if the specified replied-to message is not found.
|
||||
"""
|
||||
return self._allow_sending_without_reply
|
||||
|
||||
@allow_sending_without_reply.setter
|
||||
@@ -178,6 +187,10 @@ class Defaults:
|
||||
|
||||
@property
|
||||
def timeout(self) -> ODVInput[float]:
|
||||
""":obj:`int` | :obj:`float`: Optional. If this value is specified, use it as the
|
||||
read timeout from the server (instead of the one specified during creation of the
|
||||
connection pool).
|
||||
"""
|
||||
return self._timeout
|
||||
|
||||
@timeout.setter
|
||||
@@ -189,6 +202,10 @@ class Defaults:
|
||||
|
||||
@property
|
||||
def quote(self) -> Optional[bool]:
|
||||
""":obj:`bool`: Optional. If set to :obj:`True`, the reply is sent as an actual reply
|
||||
to the message. If ``reply_to_message_id`` is passed in ``kwargs``, this parameter will
|
||||
be ignored. Default: :obj:`True` in group chats and :obj:`False` in private chats.
|
||||
"""
|
||||
return self._quote
|
||||
|
||||
@quote.setter
|
||||
@@ -200,6 +217,9 @@ class Defaults:
|
||||
|
||||
@property
|
||||
def tzinfo(self) -> pytz.BaseTzInfo:
|
||||
""":obj:`tzinfo`: A timezone to be used for all date(time) objects appearing
|
||||
throughout PTB.
|
||||
"""
|
||||
return self._tzinfo
|
||||
|
||||
@tzinfo.setter
|
||||
@@ -211,6 +231,10 @@ class Defaults:
|
||||
|
||||
@property
|
||||
def run_async(self) -> bool:
|
||||
""":obj:`bool`: Optional. Default setting for the ``run_async`` parameter of
|
||||
handlers and error handlers registered through :meth:`Dispatcher.add_handler` and
|
||||
:meth:`Dispatcher.add_error_handler`.
|
||||
"""
|
||||
return self._run_async
|
||||
|
||||
@run_async.setter
|
||||
@@ -236,7 +260,7 @@ class Defaults:
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if isinstance(other, Defaults):
|
||||
return self.__dict__ == other.__dict__
|
||||
return all(getattr(self, attr) == getattr(other, attr) for attr in self.__slots__)
|
||||
return False
|
||||
|
||||
def __ne__(self, other: object) -> bool:
|
||||
|
||||
+146
-30
@@ -17,9 +17,8 @@
|
||||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains the DictPersistence class."""
|
||||
from copy import deepcopy
|
||||
|
||||
from typing import DefaultDict, Dict, Optional, Tuple
|
||||
from typing import DefaultDict, Dict, Optional, Tuple, cast
|
||||
from collections import defaultdict
|
||||
|
||||
from telegram.utils.helpers import (
|
||||
@@ -28,7 +27,7 @@ from telegram.utils.helpers import (
|
||||
encode_conversations_to_json,
|
||||
)
|
||||
from telegram.ext import BasePersistence
|
||||
from telegram.utils.types import ConversationDict
|
||||
from telegram.ext.utils.types import ConversationDict, CDCData
|
||||
|
||||
try:
|
||||
import ujson as json
|
||||
@@ -37,7 +36,7 @@ except ImportError:
|
||||
|
||||
|
||||
class DictPersistence(BasePersistence):
|
||||
"""Using python's dicts and json for making your bot persistent.
|
||||
"""Using Python's :obj:`dict` and ``json`` for making your bot persistent.
|
||||
|
||||
Note:
|
||||
This class does *not* implement a :meth:`flush` method, meaning that data managed by
|
||||
@@ -57,17 +56,25 @@ class DictPersistence(BasePersistence):
|
||||
Args:
|
||||
store_user_data (:obj:`bool`, optional): Whether user_data should be saved by this
|
||||
persistence class. Default is :obj:`True`.
|
||||
store_chat_data (:obj:`bool`, optional): Whether user_data should be saved by this
|
||||
store_chat_data (:obj:`bool`, optional): Whether chat_data should be saved by this
|
||||
persistence class. Default is :obj:`True`.
|
||||
store_bot_data (:obj:`bool`, optional): Whether bot_data should be saved by this
|
||||
persistence class. Default is :obj:`True` .
|
||||
user_data_json (:obj:`str`, optional): Json string that will be used to reconstruct
|
||||
persistence class. Default is :obj:`True`.
|
||||
store_callback_data (:obj:`bool`, optional): Whether callback_data should be saved by this
|
||||
persistence class. Default is :obj:`False`.
|
||||
|
||||
.. versionadded:: 13.6
|
||||
user_data_json (:obj:`str`, optional): JSON string that will be used to reconstruct
|
||||
user_data on creating this persistence. Default is ``""``.
|
||||
chat_data_json (:obj:`str`, optional): Json string that will be used to reconstruct
|
||||
chat_data_json (:obj:`str`, optional): JSON string that will be used to reconstruct
|
||||
chat_data on creating this persistence. Default is ``""``.
|
||||
bot_data_json (:obj:`str`, optional): Json string that will be used to reconstruct
|
||||
bot_data_json (:obj:`str`, optional): JSON string that will be used to reconstruct
|
||||
bot_data on creating this persistence. Default is ``""``.
|
||||
conversations_json (:obj:`str`, optional): Json string that will be used to reconstruct
|
||||
callback_data_json (:obj:`str`, optional): Json string that will be used to reconstruct
|
||||
callback_data on creating this persistence. Default is ``""``.
|
||||
|
||||
.. versionadded:: 13.6
|
||||
conversations_json (:obj:`str`, optional): JSON string that will be used to reconstruct
|
||||
conversation on creating this persistence. Default is ``""``.
|
||||
|
||||
Attributes:
|
||||
@@ -77,8 +84,25 @@ class DictPersistence(BasePersistence):
|
||||
persistence class.
|
||||
store_bot_data (:obj:`bool`): Whether bot_data should be saved by this
|
||||
persistence class.
|
||||
store_callback_data (:obj:`bool`): Whether callback_data be saved by this
|
||||
persistence class.
|
||||
|
||||
.. versionadded:: 13.6
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
'_user_data',
|
||||
'_chat_data',
|
||||
'_bot_data',
|
||||
'_callback_data',
|
||||
'_conversations',
|
||||
'_user_data_json',
|
||||
'_chat_data_json',
|
||||
'_bot_data_json',
|
||||
'_callback_data_json',
|
||||
'_conversations_json',
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
store_user_data: bool = True,
|
||||
@@ -88,19 +112,24 @@ class DictPersistence(BasePersistence):
|
||||
chat_data_json: str = '',
|
||||
bot_data_json: str = '',
|
||||
conversations_json: str = '',
|
||||
store_callback_data: bool = False,
|
||||
callback_data_json: str = '',
|
||||
):
|
||||
super().__init__(
|
||||
store_user_data=store_user_data,
|
||||
store_chat_data=store_chat_data,
|
||||
store_bot_data=store_bot_data,
|
||||
store_callback_data=store_callback_data,
|
||||
)
|
||||
self._user_data = None
|
||||
self._chat_data = None
|
||||
self._bot_data = None
|
||||
self._callback_data = None
|
||||
self._conversations = None
|
||||
self._user_data_json = None
|
||||
self._chat_data_json = None
|
||||
self._bot_data_json = None
|
||||
self._callback_data_json = None
|
||||
self._conversations_json = None
|
||||
if user_data_json:
|
||||
try:
|
||||
@@ -122,6 +151,34 @@ class DictPersistence(BasePersistence):
|
||||
raise TypeError("Unable to deserialize bot_data_json. Not valid JSON") from exc
|
||||
if not isinstance(self._bot_data, dict):
|
||||
raise TypeError("bot_data_json must be serialized dict")
|
||||
if callback_data_json:
|
||||
try:
|
||||
data = json.loads(callback_data_json)
|
||||
except (ValueError, AttributeError) as exc:
|
||||
raise TypeError(
|
||||
"Unable to deserialize callback_data_json. Not valid JSON"
|
||||
) from exc
|
||||
# We are a bit more thorough with the checking of the format here, because it's
|
||||
# more complicated than for the other things
|
||||
try:
|
||||
if data is None:
|
||||
self._callback_data = None
|
||||
else:
|
||||
self._callback_data = cast(
|
||||
CDCData,
|
||||
([(one, float(two), three) for one, two, three in data[0]], data[1]),
|
||||
)
|
||||
self._callback_data_json = callback_data_json
|
||||
except (ValueError, IndexError) as exc:
|
||||
raise TypeError("callback_data_json is not in the required format") from exc
|
||||
if self._callback_data is not None and (
|
||||
not all(
|
||||
isinstance(entry[2], dict) and isinstance(entry[0], str)
|
||||
for entry in self._callback_data[0]
|
||||
)
|
||||
or not isinstance(self._callback_data[1], dict)
|
||||
):
|
||||
raise TypeError("callback_data_json is not in the required format")
|
||||
|
||||
if conversations_json:
|
||||
try:
|
||||
@@ -169,7 +226,25 @@ class DictPersistence(BasePersistence):
|
||||
return json.dumps(self.bot_data)
|
||||
|
||||
@property
|
||||
def conversations(self) -> Optional[Dict[str, Dict[Tuple, object]]]:
|
||||
def callback_data(self) -> Optional[CDCData]:
|
||||
""":class:`telegram.ext.utils.types.CDCData`: The meta data on the stored callback data.
|
||||
|
||||
.. versionadded:: 13.6
|
||||
"""
|
||||
return self._callback_data
|
||||
|
||||
@property
|
||||
def callback_data_json(self) -> str:
|
||||
""":obj:`str`: The meta data on the stored callback data as a JSON-string.
|
||||
|
||||
.. versionadded:: 13.6
|
||||
"""
|
||||
if self._callback_data_json:
|
||||
return self._callback_data_json
|
||||
return json.dumps(self.callback_data)
|
||||
|
||||
@property
|
||||
def conversations(self) -> Optional[Dict[str, ConversationDict]]:
|
||||
""":obj:`dict`: The conversations as a dict."""
|
||||
return self._conversations
|
||||
|
||||
@@ -187,11 +262,9 @@ class DictPersistence(BasePersistence):
|
||||
Returns:
|
||||
:obj:`defaultdict`: The restored user data.
|
||||
"""
|
||||
if self.user_data:
|
||||
pass
|
||||
else:
|
||||
if self.user_data is None:
|
||||
self._user_data = defaultdict(dict)
|
||||
return deepcopy(self.user_data) # type: ignore[arg-type]
|
||||
return self.user_data # type: ignore[return-value]
|
||||
|
||||
def get_chat_data(self) -> DefaultDict[int, Dict[object, object]]:
|
||||
"""Returns the chat_data created from the ``chat_data_json`` or an empty
|
||||
@@ -200,11 +273,9 @@ class DictPersistence(BasePersistence):
|
||||
Returns:
|
||||
:obj:`defaultdict`: The restored chat data.
|
||||
"""
|
||||
if self.chat_data:
|
||||
pass
|
||||
else:
|
||||
if self.chat_data is None:
|
||||
self._chat_data = defaultdict(dict)
|
||||
return deepcopy(self.chat_data) # type: ignore[arg-type]
|
||||
return self.chat_data # type: ignore[return-value]
|
||||
|
||||
def get_bot_data(self) -> Dict[object, object]:
|
||||
"""Returns the bot_data created from the ``bot_data_json`` or an empty :obj:`dict`.
|
||||
@@ -212,11 +283,23 @@ class DictPersistence(BasePersistence):
|
||||
Returns:
|
||||
:obj:`dict`: The restored bot data.
|
||||
"""
|
||||
if self.bot_data:
|
||||
pass
|
||||
else:
|
||||
if self.bot_data is None:
|
||||
self._bot_data = {}
|
||||
return deepcopy(self.bot_data) # type: ignore[arg-type]
|
||||
return self.bot_data # type: ignore[return-value]
|
||||
|
||||
def get_callback_data(self) -> Optional[CDCData]:
|
||||
"""Returns the callback_data created from the ``callback_data_json`` or :obj:`None`.
|
||||
|
||||
.. versionadded:: 13.6
|
||||
|
||||
Returns:
|
||||
Optional[:class:`telegram.ext.utils.types.CDCData`]: The restored meta data or
|
||||
:obj:`None`, if no data was stored.
|
||||
"""
|
||||
if self.callback_data is None:
|
||||
self._callback_data = None
|
||||
return None
|
||||
return self.callback_data[0], self.callback_data[1].copy()
|
||||
|
||||
def get_conversations(self, name: str) -> ConversationDict:
|
||||
"""Returns the conversations created from the ``conversations_json`` or an empty
|
||||
@@ -225,9 +308,7 @@ class DictPersistence(BasePersistence):
|
||||
Returns:
|
||||
:obj:`dict`: The restored conversations data.
|
||||
"""
|
||||
if self.conversations:
|
||||
pass
|
||||
else:
|
||||
if self.conversations is None:
|
||||
self._conversations = {}
|
||||
return self.conversations.get(name, {}).copy() # type: ignore[union-attr]
|
||||
|
||||
@@ -253,7 +334,7 @@ class DictPersistence(BasePersistence):
|
||||
|
||||
Args:
|
||||
user_id (:obj:`int`): The user the data might have been changed for.
|
||||
data (:obj:`dict`): The :attr:`telegram.ext.dispatcher.user_data` [user_id].
|
||||
data (:obj:`dict`): The :attr:`telegram.ext.Dispatcher.user_data` ``[user_id]``.
|
||||
"""
|
||||
if self._user_data is None:
|
||||
self._user_data = defaultdict(dict)
|
||||
@@ -267,7 +348,7 @@ class DictPersistence(BasePersistence):
|
||||
|
||||
Args:
|
||||
chat_id (:obj:`int`): The chat the data might have been changed for.
|
||||
data (:obj:`dict`): The :attr:`telegram.ext.dispatcher.chat_data` [chat_id].
|
||||
data (:obj:`dict`): The :attr:`telegram.ext.Dispatcher.chat_data` ``[chat_id]``.
|
||||
"""
|
||||
if self._chat_data is None:
|
||||
self._chat_data = defaultdict(dict)
|
||||
@@ -280,9 +361,44 @@ class DictPersistence(BasePersistence):
|
||||
"""Will update the bot_data (if changed).
|
||||
|
||||
Args:
|
||||
data (:obj:`dict`): The :attr:`telegram.ext.dispatcher.bot_data`.
|
||||
data (:obj:`dict`): The :attr:`telegram.ext.Dispatcher.bot_data`.
|
||||
"""
|
||||
if self._bot_data == data:
|
||||
return
|
||||
self._bot_data = data.copy()
|
||||
self._bot_data = data
|
||||
self._bot_data_json = None
|
||||
|
||||
def update_callback_data(self, data: CDCData) -> None:
|
||||
"""Will update the callback_data (if changed).
|
||||
|
||||
.. versionadded:: 13.6
|
||||
|
||||
Args:
|
||||
data (:class:`telegram.ext.utils.types.CDCData`): The relevant data to restore
|
||||
:class:`telegram.ext.CallbackDataCache`.
|
||||
"""
|
||||
if self._callback_data == data:
|
||||
return
|
||||
self._callback_data = (data[0], data[1].copy())
|
||||
self._callback_data_json = None
|
||||
|
||||
def refresh_user_data(self, user_id: int, user_data: Dict) -> None:
|
||||
"""Does nothing.
|
||||
|
||||
.. versionadded:: 13.6
|
||||
.. seealso:: :meth:`telegram.ext.BasePersistence.refresh_user_data`
|
||||
"""
|
||||
|
||||
def refresh_chat_data(self, chat_id: int, chat_data: Dict) -> None:
|
||||
"""Does nothing.
|
||||
|
||||
.. versionadded:: 13.6
|
||||
.. seealso:: :meth:`telegram.ext.BasePersistence.refresh_chat_data`
|
||||
"""
|
||||
|
||||
def refresh_bot_data(self, bot_data: Dict) -> None:
|
||||
"""Does nothing.
|
||||
|
||||
.. versionadded:: 13.6
|
||||
.. seealso:: :meth:`telegram.ext.BasePersistence.refresh_bot_data`
|
||||
"""
|
||||
|
||||
+157
-26
@@ -26,16 +26,32 @@ from functools import wraps
|
||||
from queue import Empty, Queue
|
||||
from threading import BoundedSemaphore, Event, Lock, Thread, current_thread
|
||||
from time import sleep
|
||||
from typing import TYPE_CHECKING, Callable, DefaultDict, Dict, List, Optional, Set, Union
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Callable,
|
||||
DefaultDict,
|
||||
Dict,
|
||||
List,
|
||||
Optional,
|
||||
Set,
|
||||
Union,
|
||||
Generic,
|
||||
TypeVar,
|
||||
overload,
|
||||
cast,
|
||||
)
|
||||
from uuid import uuid4
|
||||
|
||||
from telegram import TelegramError, Update
|
||||
from telegram.ext import BasePersistence
|
||||
from telegram.ext import BasePersistence, ContextTypes
|
||||
from telegram.ext.callbackcontext import CallbackContext
|
||||
from telegram.ext.handler import Handler
|
||||
from telegram.utils.deprecate import TelegramDeprecationWarning
|
||||
import telegram.ext.extbot
|
||||
from telegram.ext.callbackdatacache import CallbackDataCache
|
||||
from telegram.utils.deprecate import TelegramDeprecationWarning, set_new_attribute_deprecated
|
||||
from telegram.ext.utils.promise import Promise
|
||||
from telegram.utils.helpers import DefaultValue, DEFAULT_FALSE
|
||||
from telegram.ext.utils.types import CCT, UD, CD, BD
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Bot
|
||||
@@ -43,6 +59,8 @@ if TYPE_CHECKING:
|
||||
|
||||
DEFAULT_GROUP: int = 0
|
||||
|
||||
UT = TypeVar('UT')
|
||||
|
||||
|
||||
def run_async(
|
||||
func: Callable[[Update, CallbackContext], object]
|
||||
@@ -98,12 +116,14 @@ class DispatcherHandlerStop(Exception):
|
||||
state (:obj:`object`, optional): The next state of the conversation.
|
||||
"""
|
||||
|
||||
__slots__ = ('state',)
|
||||
|
||||
def __init__(self, state: object = None) -> None:
|
||||
super().__init__()
|
||||
self.state = state
|
||||
|
||||
|
||||
class Dispatcher:
|
||||
class Dispatcher(Generic[CCT, UD, CD, BD]):
|
||||
"""This class dispatches all kinds of updates to its registered handlers.
|
||||
|
||||
Args:
|
||||
@@ -118,6 +138,12 @@ class Dispatcher:
|
||||
use_context (:obj:`bool`, optional): If set to :obj:`True` uses the context based callback
|
||||
API (ignored if `dispatcher` argument is used). Defaults to :obj:`True`.
|
||||
**New users**: set this to :obj:`True`.
|
||||
context_types (:class:`telegram.ext.ContextTypes`, optional): Pass an instance
|
||||
of :class:`telegram.ext.ContextTypes` to customize the types used in the
|
||||
``context`` interface. If not passed, the defaults documented in
|
||||
:class:`telegram.ext.ContextTypes` will be used.
|
||||
|
||||
.. versionadded:: 13.6
|
||||
|
||||
Attributes:
|
||||
bot (:class:`telegram.Bot`): The bot object that should be passed to the handlers.
|
||||
@@ -131,14 +157,70 @@ class Dispatcher:
|
||||
bot_data (:obj:`dict`): A dictionary handlers can use to store data for the bot.
|
||||
persistence (:class:`telegram.ext.BasePersistence`): Optional. The persistence class to
|
||||
store data that should be persistent over restarts.
|
||||
context_types (:class:`telegram.ext.ContextTypes`): Container for the types used
|
||||
in the ``context`` interface.
|
||||
|
||||
.. versionadded:: 13.6
|
||||
|
||||
"""
|
||||
|
||||
# Allowing '__weakref__' creation here since we need it for the singleton
|
||||
__slots__ = (
|
||||
'workers',
|
||||
'persistence',
|
||||
'use_context',
|
||||
'update_queue',
|
||||
'job_queue',
|
||||
'user_data',
|
||||
'chat_data',
|
||||
'bot_data',
|
||||
'_update_persistence_lock',
|
||||
'handlers',
|
||||
'groups',
|
||||
'error_handlers',
|
||||
'running',
|
||||
'__stop_event',
|
||||
'__exception_event',
|
||||
'__async_queue',
|
||||
'__async_threads',
|
||||
'bot',
|
||||
'__dict__',
|
||||
'__weakref__',
|
||||
'context_types',
|
||||
)
|
||||
|
||||
__singleton_lock = Lock()
|
||||
__singleton_semaphore = BoundedSemaphore()
|
||||
__singleton = None
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@overload
|
||||
def __init__(
|
||||
self: 'Dispatcher[CallbackContext[Dict, Dict, Dict], Dict, Dict, Dict]',
|
||||
bot: 'Bot',
|
||||
update_queue: Queue,
|
||||
workers: int = 4,
|
||||
exception_event: Event = None,
|
||||
job_queue: 'JobQueue' = None,
|
||||
persistence: BasePersistence = None,
|
||||
use_context: bool = True,
|
||||
):
|
||||
...
|
||||
|
||||
@overload
|
||||
def __init__(
|
||||
self: 'Dispatcher[CCT, UD, CD, BD]',
|
||||
bot: 'Bot',
|
||||
update_queue: Queue,
|
||||
workers: int = 4,
|
||||
exception_event: Event = None,
|
||||
job_queue: 'JobQueue' = None,
|
||||
persistence: BasePersistence = None,
|
||||
use_context: bool = True,
|
||||
context_types: ContextTypes[CCT, UD, CD, BD] = None,
|
||||
):
|
||||
...
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
bot: 'Bot',
|
||||
@@ -148,12 +230,14 @@ class Dispatcher:
|
||||
job_queue: 'JobQueue' = None,
|
||||
persistence: BasePersistence = None,
|
||||
use_context: bool = True,
|
||||
context_types: ContextTypes[CCT, UD, CD, BD] = None,
|
||||
):
|
||||
self.bot = bot
|
||||
self.update_queue = update_queue
|
||||
self.job_queue = job_queue
|
||||
self.workers = workers
|
||||
self.use_context = use_context
|
||||
self.context_types = cast(ContextTypes[CCT, UD, CD, BD], context_types or ContextTypes())
|
||||
|
||||
if not use_context:
|
||||
warnings.warn(
|
||||
@@ -167,9 +251,9 @@ class Dispatcher:
|
||||
'Asynchronous callbacks can not be processed without at least one worker thread.'
|
||||
)
|
||||
|
||||
self.user_data: DefaultDict[int, Dict[object, object]] = defaultdict(dict)
|
||||
self.chat_data: DefaultDict[int, Dict[object, object]] = defaultdict(dict)
|
||||
self.bot_data = {}
|
||||
self.user_data: DefaultDict[int, UD] = defaultdict(self.context_types.user_data)
|
||||
self.chat_data: DefaultDict[int, CD] = defaultdict(self.context_types.chat_data)
|
||||
self.bot_data = self.context_types.bot_data()
|
||||
self.persistence: Optional[BasePersistence] = None
|
||||
self._update_persistence_lock = Lock()
|
||||
if persistence:
|
||||
@@ -187,8 +271,21 @@ class Dispatcher:
|
||||
raise ValueError("chat_data must be of type defaultdict")
|
||||
if self.persistence.store_bot_data:
|
||||
self.bot_data = self.persistence.get_bot_data()
|
||||
if not isinstance(self.bot_data, dict):
|
||||
raise ValueError("bot_data must be of type dict")
|
||||
if not isinstance(self.bot_data, self.context_types.bot_data):
|
||||
raise ValueError(
|
||||
f"bot_data must be of type {self.context_types.bot_data.__name__}"
|
||||
)
|
||||
if self.persistence.store_callback_data:
|
||||
self.bot = cast(telegram.ext.extbot.ExtBot, self.bot)
|
||||
persistent_data = self.persistence.get_callback_data()
|
||||
if persistent_data is not None:
|
||||
if not isinstance(persistent_data, tuple) and len(persistent_data) != 2:
|
||||
raise ValueError('callback_data must be a 2-tuple')
|
||||
self.bot.callback_data_cache = CallbackDataCache(
|
||||
self.bot,
|
||||
self.bot.callback_data_cache.maxsize,
|
||||
persistent_data=persistent_data,
|
||||
)
|
||||
else:
|
||||
self.persistence = None
|
||||
|
||||
@@ -215,8 +312,19 @@ class Dispatcher:
|
||||
else:
|
||||
self._set_singleton(None)
|
||||
|
||||
def __setattr__(self, key: str, value: object) -> None:
|
||||
# Mangled names don't automatically apply in __setattr__ (see
|
||||
# https://docs.python.org/3/tutorial/classes.html#private-variables), so we have to make
|
||||
# it mangled so they don't raise TelegramDeprecationWarning unnecessarily
|
||||
if key.startswith('__'):
|
||||
key = f"_{self.__class__.__name__}{key}"
|
||||
if issubclass(self.__class__, Dispatcher) and self.__class__ is not Dispatcher:
|
||||
object.__setattr__(self, key, value)
|
||||
return
|
||||
set_new_attribute_deprecated(self, key, value)
|
||||
|
||||
@property
|
||||
def exception_event(self) -> Event:
|
||||
def exception_event(self) -> Event: # skipcq: PY-D0003
|
||||
return self.__exception_event
|
||||
|
||||
def _init_async_threads(self, base_name: str, workers: int) -> None:
|
||||
@@ -404,7 +512,7 @@ class Dispatcher:
|
||||
self.logger.debug('async thread %s/%s has ended', i + 1, total)
|
||||
|
||||
@property
|
||||
def has_running_threads(self) -> bool:
|
||||
def has_running_threads(self) -> bool: # skipcq: PY-D0003
|
||||
return self.running or bool(self.__async_threads)
|
||||
|
||||
def process_update(self, update: object) -> None:
|
||||
@@ -422,7 +530,6 @@ class Dispatcher:
|
||||
The update to process.
|
||||
|
||||
"""
|
||||
|
||||
# An error happened while polling
|
||||
if isinstance(update, TelegramError):
|
||||
try:
|
||||
@@ -441,7 +548,8 @@ class Dispatcher:
|
||||
check = handler.check_update(update)
|
||||
if check is not None and check is not False:
|
||||
if not context and self.use_context:
|
||||
context = CallbackContext.from_update(update, self)
|
||||
context = self.context_types.context.from_update(update, self)
|
||||
context.refresh_data()
|
||||
handled = True
|
||||
sync_modes.append(handler.run_async)
|
||||
handler.handle_update(update, self, check, context)
|
||||
@@ -474,7 +582,7 @@ class Dispatcher:
|
||||
if not handled_only_async:
|
||||
self.update_persistence(update=update)
|
||||
|
||||
def add_handler(self, handler: Handler, group: int = DEFAULT_GROUP) -> None:
|
||||
def add_handler(self, handler: Handler[UT, CCT], group: int = DEFAULT_GROUP) -> None:
|
||||
"""Register a handler.
|
||||
|
||||
TL;DR: Order and priority counts. 0 or 1 handlers per group will be used. End handling of
|
||||
@@ -506,14 +614,22 @@ class Dispatcher:
|
||||
raise TypeError(f'handler is not an instance of {Handler.__name__}')
|
||||
if not isinstance(group, int):
|
||||
raise TypeError('group is not int')
|
||||
if isinstance(handler, ConversationHandler) and handler.persistent and handler.name:
|
||||
# For some reason MyPy infers the type of handler is <nothing> here,
|
||||
# so for now we just ignore all the errors
|
||||
if (
|
||||
isinstance(handler, ConversationHandler)
|
||||
and handler.persistent # type: ignore[attr-defined]
|
||||
and handler.name # type: ignore[attr-defined]
|
||||
):
|
||||
if not self.persistence:
|
||||
raise ValueError(
|
||||
f"ConversationHandler {handler.name} can not be persistent if dispatcher has "
|
||||
f"no persistence"
|
||||
f"ConversationHandler {handler.name} " # type: ignore[attr-defined]
|
||||
f"can not be persistent if dispatcher has no persistence"
|
||||
)
|
||||
handler.persistence = self.persistence
|
||||
handler.conversations = self.persistence.get_conversations(handler.name)
|
||||
handler.persistence = self.persistence # type: ignore[attr-defined]
|
||||
handler.conversations = ( # type: ignore[attr-defined]
|
||||
self.persistence.get_conversations(handler.name) # type: ignore[attr-defined]
|
||||
)
|
||||
|
||||
if group not in self.handlers:
|
||||
self.handlers[group] = []
|
||||
@@ -541,7 +657,7 @@ class Dispatcher:
|
||||
|
||||
Args:
|
||||
update (:class:`telegram.Update`, optional): The update to process. If passed, only the
|
||||
corresponding ``user_data`` and ``chat_data`` will be updated.
|
||||
corresponding ``user_data`` and ``chat_data`` will be updated.
|
||||
"""
|
||||
with self._update_persistence_lock:
|
||||
self.__update_persistence(update)
|
||||
@@ -563,6 +679,22 @@ class Dispatcher:
|
||||
else:
|
||||
user_ids = []
|
||||
|
||||
if self.persistence.store_callback_data:
|
||||
self.bot = cast(telegram.ext.extbot.ExtBot, self.bot)
|
||||
try:
|
||||
self.persistence.update_callback_data(
|
||||
self.bot.callback_data_cache.persistence_data
|
||||
)
|
||||
except Exception as exc:
|
||||
try:
|
||||
self.dispatch_error(update, exc)
|
||||
except Exception:
|
||||
message = (
|
||||
'Saving callback 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_bot_data:
|
||||
try:
|
||||
self.persistence.update_bot_data(self.bot_data)
|
||||
@@ -607,7 +739,7 @@ class Dispatcher:
|
||||
|
||||
def add_error_handler(
|
||||
self,
|
||||
callback: Callable[[object, CallbackContext], None],
|
||||
callback: Callable[[object, CCT], None],
|
||||
run_async: Union[bool, DefaultValue] = DEFAULT_FALSE, # pylint: disable=W0621
|
||||
) -> None:
|
||||
"""Registers an error handler in the Dispatcher. This handler will receive every error
|
||||
@@ -637,13 +769,12 @@ class Dispatcher:
|
||||
self.logger.debug('The callback is already registered as an error handler. Ignoring.')
|
||||
return
|
||||
|
||||
if run_async is DEFAULT_FALSE and self.bot.defaults:
|
||||
if self.bot.defaults.run_async:
|
||||
run_async = True
|
||||
if run_async is DEFAULT_FALSE and self.bot.defaults and self.bot.defaults.run_async:
|
||||
run_async = True
|
||||
|
||||
self.error_handlers[callback] = run_async
|
||||
|
||||
def remove_error_handler(self, callback: Callable[[object, CallbackContext], None]) -> None:
|
||||
def remove_error_handler(self, callback: Callable[[object, CCT], None]) -> None:
|
||||
"""Removes an error handler.
|
||||
|
||||
Args:
|
||||
@@ -670,7 +801,7 @@ class Dispatcher:
|
||||
if self.error_handlers:
|
||||
for callback, run_async in self.error_handlers.items(): # pylint: disable=W0621
|
||||
if self.use_context:
|
||||
context = CallbackContext.from_error(
|
||||
context = self.context_types.context.from_error(
|
||||
update, error, self, async_args=async_args, async_kwargs=async_kwargs
|
||||
)
|
||||
if run_async:
|
||||
|
||||
@@ -0,0 +1,337 @@
|
||||
#!/usr/bin/env python
|
||||
# pylint: disable=E0611,E0213,E1102,C0103,E1101,R0913,R0904
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2021
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains an object that represents a Telegram Bot with convenience extensions."""
|
||||
from copy import copy
|
||||
from typing import Union, cast, List, Callable, Optional, Tuple, TypeVar, TYPE_CHECKING, Sequence
|
||||
|
||||
import telegram.bot
|
||||
from telegram import (
|
||||
ReplyMarkup,
|
||||
Message,
|
||||
InlineKeyboardMarkup,
|
||||
Poll,
|
||||
MessageId,
|
||||
Update,
|
||||
Chat,
|
||||
CallbackQuery,
|
||||
)
|
||||
|
||||
from telegram.ext.callbackdatacache import CallbackDataCache
|
||||
from telegram.utils.types import JSONDict, ODVInput, DVInput
|
||||
from ..utils.helpers import DEFAULT_NONE
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import InlineQueryResult, MessageEntity
|
||||
from telegram.utils.request import Request
|
||||
from .defaults import Defaults
|
||||
|
||||
HandledTypes = TypeVar('HandledTypes', bound=Union[Message, CallbackQuery, Chat])
|
||||
|
||||
|
||||
class ExtBot(telegram.bot.Bot):
|
||||
"""This object represents a Telegram Bot with convenience extensions.
|
||||
|
||||
Warning:
|
||||
Not to be confused with :class:`telegram.Bot`.
|
||||
|
||||
For the documentation of the arguments, methods and attributes, please see
|
||||
:class:`telegram.Bot`.
|
||||
|
||||
.. versionadded:: 13.6
|
||||
|
||||
Args:
|
||||
defaults (:class:`telegram.ext.Defaults`, optional): An object containing default values to
|
||||
be used if not set explicitly in the bot methods.
|
||||
arbitrary_callback_data (:obj:`bool` | :obj:`int`, optional): Whether to
|
||||
allow arbitrary objects as callback data for :class:`telegram.InlineKeyboardButton`.
|
||||
Pass an integer to specify the maximum number of objects cached in memory. For more
|
||||
details, please see our `wiki <https://git.io/JGBDI>`_. Defaults to :obj:`False`.
|
||||
|
||||
Attributes:
|
||||
arbitrary_callback_data (:obj:`bool` | :obj:`int`): Whether this bot instance
|
||||
allows to use arbitrary objects as callback data for
|
||||
:class:`telegram.InlineKeyboardButton`.
|
||||
callback_data_cache (:class:`telegram.ext.CallbackDataCache`): The cache for objects passed
|
||||
as callback data for :class:`telegram.InlineKeyboardButton`.
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ('arbitrary_callback_data', 'callback_data_cache')
|
||||
|
||||
# The ext_bot argument is a little hack to get warnings handled correctly.
|
||||
# It's not very clean, but the warnings will be dropped at some point anyway.
|
||||
def __setattr__(self, key: str, value: object, ext_bot: bool = True) -> None:
|
||||
if issubclass(self.__class__, ExtBot) and self.__class__ is not ExtBot:
|
||||
object.__setattr__(self, key, value)
|
||||
return
|
||||
super().__setattr__(key, value, ext_bot=ext_bot) # type: ignore[call-arg]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
token: str,
|
||||
base_url: str = None,
|
||||
base_file_url: str = None,
|
||||
request: 'Request' = None,
|
||||
private_key: bytes = None,
|
||||
private_key_password: bytes = None,
|
||||
defaults: 'Defaults' = None,
|
||||
arbitrary_callback_data: Union[bool, int] = False,
|
||||
):
|
||||
super().__init__(
|
||||
token=token,
|
||||
base_url=base_url,
|
||||
base_file_url=base_file_url,
|
||||
request=request,
|
||||
private_key=private_key,
|
||||
private_key_password=private_key_password,
|
||||
)
|
||||
# We don't pass this to super().__init__ to avoid the deprecation warning
|
||||
self.defaults = defaults
|
||||
|
||||
# set up callback_data
|
||||
if not isinstance(arbitrary_callback_data, bool):
|
||||
maxsize = cast(int, arbitrary_callback_data)
|
||||
self.arbitrary_callback_data = True
|
||||
else:
|
||||
maxsize = 1024
|
||||
self.arbitrary_callback_data = arbitrary_callback_data
|
||||
self.callback_data_cache: CallbackDataCache = CallbackDataCache(bot=self, maxsize=maxsize)
|
||||
|
||||
def _replace_keyboard(self, reply_markup: Optional[ReplyMarkup]) -> Optional[ReplyMarkup]:
|
||||
# If the reply_markup is an inline keyboard and we allow arbitrary callback data, let the
|
||||
# CallbackDataCache build a new keyboard with the data replaced. Otherwise return the input
|
||||
if isinstance(reply_markup, InlineKeyboardMarkup) and self.arbitrary_callback_data:
|
||||
return self.callback_data_cache.process_keyboard(reply_markup)
|
||||
|
||||
return reply_markup
|
||||
|
||||
def insert_callback_data(self, update: Update) -> None:
|
||||
"""If this bot allows for arbitrary callback data, this inserts the cached data into all
|
||||
corresponding buttons within this update.
|
||||
|
||||
Note:
|
||||
Checks :attr:`telegram.Message.via_bot` and :attr:`telegram.Message.from_user` to check
|
||||
if the reply markup (if any) was actually sent by this caches bot. If it was not, the
|
||||
message will be returned unchanged.
|
||||
|
||||
Note that this will fail for channel posts, as :attr:`telegram.Message.from_user` is
|
||||
:obj:`None` for those! In the corresponding reply markups the callback data will be
|
||||
replaced by :class:`telegram.ext.InvalidCallbackData`.
|
||||
|
||||
Warning:
|
||||
*In place*, i.e. the passed :class:`telegram.Message` will be changed!
|
||||
|
||||
Args:
|
||||
update (:class`telegram.Update`): The update.
|
||||
|
||||
"""
|
||||
# The only incoming updates that can directly contain a message sent by the bot itself are:
|
||||
# * CallbackQueries
|
||||
# * Messages where the pinned_message is sent by the bot
|
||||
# * Messages where the reply_to_message is sent by the bot
|
||||
# * Messages where via_bot is the bot
|
||||
# Finally there is effective_chat.pinned message, but that's only returned in get_chat
|
||||
if update.callback_query:
|
||||
self._insert_callback_data(update.callback_query)
|
||||
# elif instead of if, as effective_message includes callback_query.message
|
||||
# and that has already been processed
|
||||
elif update.effective_message:
|
||||
self._insert_callback_data(update.effective_message)
|
||||
|
||||
def _insert_callback_data(self, obj: HandledTypes) -> HandledTypes:
|
||||
if not self.arbitrary_callback_data:
|
||||
return obj
|
||||
|
||||
if isinstance(obj, CallbackQuery):
|
||||
self.callback_data_cache.process_callback_query(obj)
|
||||
return obj # type: ignore[return-value]
|
||||
|
||||
if isinstance(obj, Message):
|
||||
if obj.reply_to_message:
|
||||
# reply_to_message can't contain further reply_to_messages, so no need to check
|
||||
self.callback_data_cache.process_message(obj.reply_to_message)
|
||||
if obj.reply_to_message.pinned_message:
|
||||
# pinned messages can't contain reply_to_message, no need to check
|
||||
self.callback_data_cache.process_message(obj.reply_to_message.pinned_message)
|
||||
if obj.pinned_message:
|
||||
# pinned messages can't contain reply_to_message, no need to check
|
||||
self.callback_data_cache.process_message(obj.pinned_message)
|
||||
|
||||
# Finally, handle the message itself
|
||||
self.callback_data_cache.process_message(message=obj)
|
||||
return obj # type: ignore[return-value]
|
||||
|
||||
if isinstance(obj, Chat) and obj.pinned_message:
|
||||
self.callback_data_cache.process_message(obj.pinned_message)
|
||||
|
||||
return obj
|
||||
|
||||
def _message(
|
||||
self,
|
||||
endpoint: str,
|
||||
data: JSONDict,
|
||||
reply_to_message_id: int = None,
|
||||
disable_notification: ODVInput[bool] = DEFAULT_NONE,
|
||||
reply_markup: ReplyMarkup = None,
|
||||
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
|
||||
timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
api_kwargs: JSONDict = None,
|
||||
) -> Union[bool, Message]:
|
||||
# We override this method to call self._replace_keyboard and self._insert_callback_data.
|
||||
# This covers most methods that have a reply_markup
|
||||
result = super()._message(
|
||||
endpoint=endpoint,
|
||||
data=data,
|
||||
reply_to_message_id=reply_to_message_id,
|
||||
disable_notification=disable_notification,
|
||||
reply_markup=self._replace_keyboard(reply_markup),
|
||||
allow_sending_without_reply=allow_sending_without_reply,
|
||||
timeout=timeout,
|
||||
api_kwargs=api_kwargs,
|
||||
)
|
||||
if isinstance(result, Message):
|
||||
self._insert_callback_data(result)
|
||||
return result
|
||||
|
||||
def get_updates(
|
||||
self,
|
||||
offset: int = None,
|
||||
limit: int = 100,
|
||||
timeout: float = 0,
|
||||
read_latency: float = 2.0,
|
||||
allowed_updates: List[str] = None,
|
||||
api_kwargs: JSONDict = None,
|
||||
) -> List[Update]:
|
||||
updates = super().get_updates(
|
||||
offset=offset,
|
||||
limit=limit,
|
||||
timeout=timeout,
|
||||
read_latency=read_latency,
|
||||
allowed_updates=allowed_updates,
|
||||
api_kwargs=api_kwargs,
|
||||
)
|
||||
|
||||
for update in updates:
|
||||
self.insert_callback_data(update)
|
||||
|
||||
return updates
|
||||
|
||||
def _effective_inline_results( # pylint: disable=R0201
|
||||
self,
|
||||
results: Union[
|
||||
Sequence['InlineQueryResult'], Callable[[int], Optional[Sequence['InlineQueryResult']]]
|
||||
],
|
||||
next_offset: str = None,
|
||||
current_offset: str = None,
|
||||
) -> Tuple[Sequence['InlineQueryResult'], Optional[str]]:
|
||||
"""
|
||||
This method is called by Bot.answer_inline_query to build the actual results list.
|
||||
Overriding this to call self._replace_keyboard suffices
|
||||
"""
|
||||
effective_results, next_offset = super()._effective_inline_results(
|
||||
results=results, next_offset=next_offset, current_offset=current_offset
|
||||
)
|
||||
|
||||
# Process arbitrary callback
|
||||
if not self.arbitrary_callback_data:
|
||||
return effective_results, next_offset
|
||||
results = []
|
||||
for result in effective_results:
|
||||
# All currently existingInlineQueryResults have a reply_markup, but future ones
|
||||
# might not have. Better be save than sorry
|
||||
if not hasattr(result, 'reply_markup'):
|
||||
results.append(result)
|
||||
else:
|
||||
# We build a new result in case the user wants to use the same object in
|
||||
# different places
|
||||
new_result = copy(result)
|
||||
markup = self._replace_keyboard(result.reply_markup) # type: ignore[attr-defined]
|
||||
new_result.reply_markup = markup
|
||||
results.append(new_result)
|
||||
|
||||
return results, next_offset
|
||||
|
||||
def stop_poll(
|
||||
self,
|
||||
chat_id: Union[int, str],
|
||||
message_id: int,
|
||||
reply_markup: InlineKeyboardMarkup = None,
|
||||
timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
api_kwargs: JSONDict = None,
|
||||
) -> Poll:
|
||||
# We override this method to call self._replace_keyboard
|
||||
return super().stop_poll(
|
||||
chat_id=chat_id,
|
||||
message_id=message_id,
|
||||
reply_markup=self._replace_keyboard(reply_markup),
|
||||
timeout=timeout,
|
||||
api_kwargs=api_kwargs,
|
||||
)
|
||||
|
||||
def copy_message(
|
||||
self,
|
||||
chat_id: Union[int, str],
|
||||
from_chat_id: Union[str, int],
|
||||
message_id: int,
|
||||
caption: str = None,
|
||||
parse_mode: ODVInput[str] = DEFAULT_NONE,
|
||||
caption_entities: Union[Tuple['MessageEntity', ...], List['MessageEntity']] = None,
|
||||
disable_notification: DVInput[bool] = DEFAULT_NONE,
|
||||
reply_to_message_id: int = None,
|
||||
allow_sending_without_reply: DVInput[bool] = DEFAULT_NONE,
|
||||
reply_markup: ReplyMarkup = None,
|
||||
timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
api_kwargs: JSONDict = None,
|
||||
) -> MessageId:
|
||||
# We override this method to call self._replace_keyboard
|
||||
return super().copy_message(
|
||||
chat_id=chat_id,
|
||||
from_chat_id=from_chat_id,
|
||||
message_id=message_id,
|
||||
caption=caption,
|
||||
parse_mode=parse_mode,
|
||||
caption_entities=caption_entities,
|
||||
disable_notification=disable_notification,
|
||||
reply_to_message_id=reply_to_message_id,
|
||||
allow_sending_without_reply=allow_sending_without_reply,
|
||||
reply_markup=self._replace_keyboard(reply_markup),
|
||||
timeout=timeout,
|
||||
api_kwargs=api_kwargs,
|
||||
)
|
||||
|
||||
def get_chat(
|
||||
self,
|
||||
chat_id: Union[str, int],
|
||||
timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
api_kwargs: JSONDict = None,
|
||||
) -> Chat:
|
||||
# We override this method to call self._insert_callback_data
|
||||
result = super().get_chat(chat_id=chat_id, timeout=timeout, api_kwargs=api_kwargs)
|
||||
return self._insert_callback_data(result)
|
||||
|
||||
# updated camelCase aliases
|
||||
getChat = get_chat
|
||||
"""Alias for :meth:`get_chat`"""
|
||||
copyMessage = copy_message
|
||||
"""Alias for :meth:`copy_message`"""
|
||||
getUpdates = get_updates
|
||||
"""Alias for :meth:`get_updates`"""
|
||||
stopPoll = stop_poll
|
||||
"""Alias for :meth:`stop_poll`"""
|
||||
+190
-27
@@ -23,6 +23,7 @@ import re
|
||||
import warnings
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from sys import version_info as py_ver
|
||||
from threading import Lock
|
||||
from typing import (
|
||||
Dict,
|
||||
@@ -50,7 +51,7 @@ __all__ = [
|
||||
'XORFilter',
|
||||
]
|
||||
|
||||
from telegram.utils.deprecate import TelegramDeprecationWarning
|
||||
from telegram.utils.deprecate import TelegramDeprecationWarning, set_new_attribute_deprecated
|
||||
from telegram.utils.types import SLT
|
||||
|
||||
DataDict = Dict[str, list]
|
||||
@@ -112,12 +113,21 @@ class BaseFilter(ABC):
|
||||
(depends on the handler).
|
||||
"""
|
||||
|
||||
_name = None
|
||||
data_filter = False
|
||||
if py_ver < (3, 7):
|
||||
__slots__ = ('_name', '_data_filter')
|
||||
else:
|
||||
__slots__ = ('_name', '_data_filter', '__dict__') # type: ignore[assignment]
|
||||
|
||||
def __new__(cls, *args: object, **kwargs: object) -> 'BaseFilter': # pylint: disable=W0613
|
||||
instance = super().__new__(cls)
|
||||
instance._name = None
|
||||
instance._data_filter = False
|
||||
|
||||
return instance
|
||||
|
||||
@abstractmethod
|
||||
def __call__(self, update: Update) -> Optional[Union[bool, DataDict]]:
|
||||
pass
|
||||
...
|
||||
|
||||
def __and__(self, other: 'BaseFilter') -> 'BaseFilter':
|
||||
return MergedFilter(self, and_filter=other)
|
||||
@@ -131,13 +141,33 @@ class BaseFilter(ABC):
|
||||
def __invert__(self) -> 'BaseFilter':
|
||||
return InvertedFilter(self)
|
||||
|
||||
def __setattr__(self, key: str, value: object) -> None:
|
||||
# Allow setting custom attributes w/o warning for user defined custom filters.
|
||||
# To differentiate between a custom and a PTB filter, we use this hacky but
|
||||
# simple way of checking the module name where the class is defined from.
|
||||
if (
|
||||
issubclass(self.__class__, (UpdateFilter, MessageFilter))
|
||||
and self.__class__.__module__ != __name__
|
||||
): # __name__ is telegram.ext.filters
|
||||
object.__setattr__(self, key, value)
|
||||
return
|
||||
set_new_attribute_deprecated(self, key, value)
|
||||
|
||||
@property
|
||||
def data_filter(self) -> bool:
|
||||
return self._data_filter
|
||||
|
||||
@data_filter.setter
|
||||
def data_filter(self, value: bool) -> None:
|
||||
self._data_filter = value
|
||||
|
||||
@property
|
||||
def name(self) -> Optional[str]:
|
||||
return self._name
|
||||
|
||||
@name.setter
|
||||
def name(self, name: Optional[str]) -> None:
|
||||
self._name = name
|
||||
self._name = name # pylint: disable=E0237
|
||||
|
||||
def __repr__(self) -> str:
|
||||
# We do this here instead of in a __init__ so filter don't have to call __init__ or super()
|
||||
@@ -146,7 +176,7 @@ class BaseFilter(ABC):
|
||||
return self.name
|
||||
|
||||
|
||||
class MessageFilter(BaseFilter, ABC):
|
||||
class MessageFilter(BaseFilter):
|
||||
"""Base class for all Message Filters. In contrast to :class:`UpdateFilter`, the object passed
|
||||
to :meth:`filter` is ``update.effective_message``.
|
||||
|
||||
@@ -162,6 +192,8 @@ class MessageFilter(BaseFilter, ABC):
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def __call__(self, update: Update) -> Optional[Union[bool, DataDict]]:
|
||||
return self.filter(update.effective_message)
|
||||
|
||||
@@ -178,7 +210,7 @@ class MessageFilter(BaseFilter, ABC):
|
||||
"""
|
||||
|
||||
|
||||
class UpdateFilter(BaseFilter, ABC):
|
||||
class UpdateFilter(BaseFilter):
|
||||
"""Base class for all Update Filters. In contrast to :class:`MessageFilter`, the object
|
||||
passed to :meth:`filter` is ``update``, which allows to create filters like
|
||||
:attr:`Filters.update.edited_message`.
|
||||
@@ -195,6 +227,8 @@ class UpdateFilter(BaseFilter, ABC):
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def __call__(self, update: Update) -> Optional[Union[bool, DataDict]]:
|
||||
return self.filter(update)
|
||||
|
||||
@@ -219,6 +253,8 @@ class InvertedFilter(UpdateFilter):
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ('f',)
|
||||
|
||||
def __init__(self, f: BaseFilter):
|
||||
self.f = f
|
||||
|
||||
@@ -244,6 +280,8 @@ class MergedFilter(UpdateFilter):
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ('base_filter', 'and_filter', 'or_filter')
|
||||
|
||||
def __init__(
|
||||
self, base_filter: BaseFilter, and_filter: BaseFilter = None, or_filter: BaseFilter = None
|
||||
):
|
||||
@@ -328,6 +366,8 @@ class XORFilter(UpdateFilter):
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ('base_filter', 'xor_filter', 'merged_filter')
|
||||
|
||||
def __init__(self, base_filter: BaseFilter, xor_filter: BaseFilter):
|
||||
self.base_filter = base_filter
|
||||
self.xor_filter = xor_filter
|
||||
@@ -346,11 +386,15 @@ class XORFilter(UpdateFilter):
|
||||
|
||||
|
||||
class _DiceEmoji(MessageFilter):
|
||||
__slots__ = ('emoji',)
|
||||
|
||||
def __init__(self, emoji: str = None, name: str = None):
|
||||
self.name = f'Filters.dice.{name}' if name else 'Filters.dice'
|
||||
self.emoji = emoji
|
||||
|
||||
class _DiceValues(MessageFilter):
|
||||
__slots__ = ('values', 'emoji')
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
values: SLT[int],
|
||||
@@ -393,7 +437,13 @@ class Filters:
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ('__dict__',)
|
||||
|
||||
def __setattr__(self, key: str, value: object) -> None:
|
||||
set_new_attribute_deprecated(self, key, value)
|
||||
|
||||
class _All(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.all'
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
@@ -403,9 +453,12 @@ class Filters:
|
||||
"""All Messages."""
|
||||
|
||||
class _Text(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.text'
|
||||
|
||||
class _TextStrings(MessageFilter):
|
||||
__slots__ = ('strings',)
|
||||
|
||||
def __init__(self, strings: Union[List[str], Tuple[str]]):
|
||||
self.strings = strings
|
||||
self.name = f'Filters.text({strings})'
|
||||
@@ -454,9 +507,12 @@ class Filters:
|
||||
"""
|
||||
|
||||
class _Caption(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.caption'
|
||||
|
||||
class _CaptionStrings(MessageFilter):
|
||||
__slots__ = ('strings',)
|
||||
|
||||
def __init__(self, strings: Union[List[str], Tuple[str]]):
|
||||
self.strings = strings
|
||||
self.name = f'Filters.caption({strings})'
|
||||
@@ -489,9 +545,12 @@ class Filters:
|
||||
"""
|
||||
|
||||
class _Command(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.command'
|
||||
|
||||
class _CommandOnlyStart(MessageFilter):
|
||||
__slots__ = ('only_start',)
|
||||
|
||||
def __init__(self, only_start: bool):
|
||||
self.only_start = only_start
|
||||
self.name = f'Filters.command({only_start})'
|
||||
@@ -564,6 +623,7 @@ class Filters:
|
||||
pattern (:obj:`str` | :obj:`Pattern`): The regex pattern.
|
||||
"""
|
||||
|
||||
__slots__ = ('pattern',)
|
||||
data_filter = True
|
||||
|
||||
def __init__(self, pattern: Union[str, Pattern]):
|
||||
@@ -599,6 +659,7 @@ class Filters:
|
||||
pattern (:obj:`str` | :obj:`Pattern`): The regex pattern.
|
||||
"""
|
||||
|
||||
__slots__ = ('pattern',)
|
||||
data_filter = True
|
||||
|
||||
def __init__(self, pattern: Union[str, Pattern]):
|
||||
@@ -617,6 +678,7 @@ class Filters:
|
||||
return {}
|
||||
|
||||
class _Reply(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.reply'
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
@@ -626,6 +688,7 @@ class Filters:
|
||||
"""Messages that are a reply to another message."""
|
||||
|
||||
class _Audio(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.audio'
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
@@ -635,6 +698,7 @@ class Filters:
|
||||
"""Messages that contain :class:`telegram.Audio`."""
|
||||
|
||||
class _Document(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.document'
|
||||
|
||||
class category(MessageFilter):
|
||||
@@ -651,18 +715,21 @@ class Filters:
|
||||
of audio sent as file, for example 'audio/mpeg' or 'audio/x-wav'.
|
||||
"""
|
||||
|
||||
__slots__ = ('_category',)
|
||||
|
||||
def __init__(self, category: Optional[str]):
|
||||
"""Initialize the category you want to filter
|
||||
|
||||
Args:
|
||||
category (str, optional): category of the media you want to filter"""
|
||||
self.category = category
|
||||
self.name = f"Filters.document.category('{self.category}')"
|
||||
category (str, optional): category of the media you want to filter
|
||||
"""
|
||||
self._category = category
|
||||
self.name = f"Filters.document.category('{self._category}')"
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
"""""" # remove method from docs
|
||||
if message.document:
|
||||
return message.document.mime_type.startswith(self.category)
|
||||
return message.document.mime_type.startswith(self._category)
|
||||
return False
|
||||
|
||||
application = category('application/')
|
||||
@@ -684,11 +751,9 @@ class Filters:
|
||||
``Filters.document.mime_type('audio/mpeg')`` filters all audio in mp3 format.
|
||||
"""
|
||||
|
||||
def __init__(self, mimetype: Optional[str]):
|
||||
"""Initialize the category you want to filter
|
||||
__slots__ = ('mimetype',)
|
||||
|
||||
Args:
|
||||
mimetype (str, optional): mime_type of the media you want to filter"""
|
||||
def __init__(self, mimetype: Optional[str]):
|
||||
self.mimetype = mimetype
|
||||
self.name = f"Filters.document.mime_type('{self.mimetype}')"
|
||||
|
||||
@@ -740,6 +805,8 @@ class Filters:
|
||||
filters files without a dot in the filename.
|
||||
"""
|
||||
|
||||
__slots__ = ('_file_extension', 'is_case_sensitive')
|
||||
|
||||
def __init__(self, file_extension: Optional[str], case_sensitive: bool = False):
|
||||
"""Initialize the extension you want to filter.
|
||||
|
||||
@@ -752,29 +819,29 @@ class Filters:
|
||||
"""
|
||||
self.is_case_sensitive = case_sensitive
|
||||
if file_extension is None:
|
||||
self.file_extension = None
|
||||
self._file_extension = None
|
||||
self.name = "Filters.document.file_extension(None)"
|
||||
elif case_sensitive:
|
||||
self.file_extension = f".{file_extension}"
|
||||
elif self.is_case_sensitive:
|
||||
self._file_extension = f".{file_extension}"
|
||||
self.name = (
|
||||
f"Filters.document.file_extension({file_extension!r},"
|
||||
" case_sensitive=True)"
|
||||
)
|
||||
else:
|
||||
self.file_extension = f".{file_extension}".lower()
|
||||
self._file_extension = f".{file_extension}".lower()
|
||||
self.name = f"Filters.document.file_extension({file_extension.lower()!r})"
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
"""""" # remove method from docs
|
||||
if message.document is None:
|
||||
return False
|
||||
if self.file_extension is None:
|
||||
if self._file_extension is None:
|
||||
return "." not in message.document.file_name
|
||||
if self.is_case_sensitive:
|
||||
filename = message.document.file_name
|
||||
else:
|
||||
filename = message.document.file_name.lower()
|
||||
return filename.endswith(self.file_extension)
|
||||
return filename.endswith(self._file_extension)
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
return bool(message.document)
|
||||
@@ -858,6 +925,7 @@ officedocument.wordprocessingml.document")``.
|
||||
"""
|
||||
|
||||
class _Animation(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.animation'
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
@@ -867,6 +935,7 @@ officedocument.wordprocessingml.document")``.
|
||||
"""Messages that contain :class:`telegram.Animation`."""
|
||||
|
||||
class _Photo(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.photo'
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
@@ -876,6 +945,7 @@ officedocument.wordprocessingml.document")``.
|
||||
"""Messages that contain :class:`telegram.PhotoSize`."""
|
||||
|
||||
class _Sticker(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.sticker'
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
@@ -885,6 +955,7 @@ officedocument.wordprocessingml.document")``.
|
||||
"""Messages that contain :class:`telegram.Sticker`."""
|
||||
|
||||
class _Video(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.video'
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
@@ -894,6 +965,7 @@ officedocument.wordprocessingml.document")``.
|
||||
"""Messages that contain :class:`telegram.Video`."""
|
||||
|
||||
class _Voice(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.voice'
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
@@ -903,6 +975,7 @@ officedocument.wordprocessingml.document")``.
|
||||
"""Messages that contain :class:`telegram.Voice`."""
|
||||
|
||||
class _VideoNote(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.video_note'
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
@@ -912,6 +985,7 @@ officedocument.wordprocessingml.document")``.
|
||||
"""Messages that contain :class:`telegram.VideoNote`."""
|
||||
|
||||
class _Contact(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.contact'
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
@@ -921,6 +995,7 @@ officedocument.wordprocessingml.document")``.
|
||||
"""Messages that contain :class:`telegram.Contact`."""
|
||||
|
||||
class _Location(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.location'
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
@@ -930,6 +1005,7 @@ officedocument.wordprocessingml.document")``.
|
||||
"""Messages that contain :class:`telegram.Location`."""
|
||||
|
||||
class _Venue(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.venue'
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
@@ -947,7 +1023,10 @@ officedocument.wordprocessingml.document")``.
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
class _NewChatMembers(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.status_update.new_chat_members'
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
@@ -957,6 +1036,7 @@ officedocument.wordprocessingml.document")``.
|
||||
"""Messages that contain :attr:`telegram.Message.new_chat_members`."""
|
||||
|
||||
class _LeftChatMember(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.status_update.left_chat_member'
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
@@ -966,6 +1046,7 @@ officedocument.wordprocessingml.document")``.
|
||||
"""Messages that contain :attr:`telegram.Message.left_chat_member`."""
|
||||
|
||||
class _NewChatTitle(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.status_update.new_chat_title'
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
@@ -975,6 +1056,7 @@ officedocument.wordprocessingml.document")``.
|
||||
"""Messages that contain :attr:`telegram.Message.new_chat_title`."""
|
||||
|
||||
class _NewChatPhoto(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.status_update.new_chat_photo'
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
@@ -984,6 +1066,7 @@ officedocument.wordprocessingml.document")``.
|
||||
"""Messages that contain :attr:`telegram.Message.new_chat_photo`."""
|
||||
|
||||
class _DeleteChatPhoto(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.status_update.delete_chat_photo'
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
@@ -993,6 +1076,7 @@ officedocument.wordprocessingml.document")``.
|
||||
"""Messages that contain :attr:`telegram.Message.delete_chat_photo`."""
|
||||
|
||||
class _ChatCreated(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.status_update.chat_created'
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
@@ -1008,6 +1092,7 @@ officedocument.wordprocessingml.document")``.
|
||||
:attr: `telegram.Message.channel_chat_created`."""
|
||||
|
||||
class _MessageAutoDeleteTimerChanged(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'MessageAutoDeleteTimerChanged'
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
@@ -1017,6 +1102,7 @@ officedocument.wordprocessingml.document")``.
|
||||
"""Messages that contain :attr:`message_auto_delete_timer_changed`"""
|
||||
|
||||
class _Migrate(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.status_update.migrate'
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
@@ -1027,6 +1113,7 @@ officedocument.wordprocessingml.document")``.
|
||||
:attr:`telegram.Message.migrate_to_chat_id`."""
|
||||
|
||||
class _PinnedMessage(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.status_update.pinned_message'
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
@@ -1036,6 +1123,7 @@ officedocument.wordprocessingml.document")``.
|
||||
"""Messages that contain :attr:`telegram.Message.pinned_message`."""
|
||||
|
||||
class _ConnectedWebsite(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.status_update.connected_website'
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
@@ -1045,6 +1133,7 @@ officedocument.wordprocessingml.document")``.
|
||||
"""Messages that contain :attr:`telegram.Message.connected_website`."""
|
||||
|
||||
class _ProximityAlertTriggered(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.status_update.proximity_alert_triggered'
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
@@ -1054,6 +1143,7 @@ officedocument.wordprocessingml.document")``.
|
||||
"""Messages that contain :attr:`telegram.Message.proximity_alert_triggered`."""
|
||||
|
||||
class _VoiceChatScheduled(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.status_update.voice_chat_scheduled'
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
@@ -1063,6 +1153,7 @@ officedocument.wordprocessingml.document")``.
|
||||
"""Messages that contain :attr:`telegram.Message.voice_chat_scheduled`."""
|
||||
|
||||
class _VoiceChatStarted(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.status_update.voice_chat_started'
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
@@ -1072,6 +1163,7 @@ officedocument.wordprocessingml.document")``.
|
||||
"""Messages that contain :attr:`telegram.Message.voice_chat_started`."""
|
||||
|
||||
class _VoiceChatEnded(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.status_update.voice_chat_ended'
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
@@ -1081,6 +1173,7 @@ officedocument.wordprocessingml.document")``.
|
||||
"""Messages that contain :attr:`telegram.Message.voice_chat_ended`."""
|
||||
|
||||
class _VoiceChatParticipantsInvited(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.status_update.voice_chat_participants_invited'
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
@@ -1165,6 +1258,7 @@ officedocument.wordprocessingml.document")``.
|
||||
"""
|
||||
|
||||
class _Forwarded(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.forwarded'
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
@@ -1174,6 +1268,7 @@ officedocument.wordprocessingml.document")``.
|
||||
"""Messages that are forwarded."""
|
||||
|
||||
class _Game(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.game'
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
@@ -1196,6 +1291,8 @@ officedocument.wordprocessingml.document")``.
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ('entity_type',)
|
||||
|
||||
def __init__(self, entity_type: str):
|
||||
self.entity_type = entity_type
|
||||
self.name = f'Filters.entity({self.entity_type})'
|
||||
@@ -1218,6 +1315,8 @@ officedocument.wordprocessingml.document")``.
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ('entity_type',)
|
||||
|
||||
def __init__(self, entity_type: str):
|
||||
self.entity_type = entity_type
|
||||
self.name = f'Filters.caption_entity({self.entity_type})'
|
||||
@@ -1227,6 +1326,7 @@ officedocument.wordprocessingml.document")``.
|
||||
return any(entity.type == self.entity_type for entity in message.caption_entities)
|
||||
|
||||
class _Private(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.private'
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
@@ -1247,6 +1347,7 @@ officedocument.wordprocessingml.document")``.
|
||||
"""
|
||||
|
||||
class _Group(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.group'
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
@@ -1267,9 +1368,11 @@ officedocument.wordprocessingml.document")``.
|
||||
"""
|
||||
|
||||
class _ChatType(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.chat_type'
|
||||
|
||||
class _Channel(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.chat_type.channel'
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
@@ -1278,6 +1381,7 @@ officedocument.wordprocessingml.document")``.
|
||||
channel = _Channel()
|
||||
|
||||
class _Group(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.chat_type.group'
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
@@ -1286,6 +1390,7 @@ officedocument.wordprocessingml.document")``.
|
||||
group = _Group()
|
||||
|
||||
class _SuperGroup(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.chat_type.supergroup'
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
@@ -1294,6 +1399,7 @@ officedocument.wordprocessingml.document")``.
|
||||
supergroup = _SuperGroup()
|
||||
|
||||
class _Groups(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.chat_type.groups'
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
@@ -1302,6 +1408,7 @@ officedocument.wordprocessingml.document")``.
|
||||
groups = _Groups()
|
||||
|
||||
class _Private(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.chat_type.private'
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
@@ -1328,7 +1435,16 @@ officedocument.wordprocessingml.document")``.
|
||||
private: Updates sent in private chat
|
||||
"""
|
||||
|
||||
class _ChatUserBaseFilter(MessageFilter):
|
||||
class _ChatUserBaseFilter(MessageFilter, ABC):
|
||||
__slots__ = (
|
||||
'chat_id_name',
|
||||
'username_name',
|
||||
'allow_empty',
|
||||
'__lock',
|
||||
'_chat_ids',
|
||||
'_usernames',
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
chat_id: SLT[int] = None,
|
||||
@@ -1348,7 +1464,7 @@ officedocument.wordprocessingml.document")``.
|
||||
|
||||
@abstractmethod
|
||||
def get_chat_or_user(self, message: Message) -> Union[Chat, User, None]:
|
||||
pass
|
||||
...
|
||||
|
||||
@staticmethod
|
||||
def _parse_chat_id(chat_id: SLT[int]) -> Set[int]:
|
||||
@@ -1505,6 +1621,8 @@ officedocument.wordprocessingml.document")``.
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
user_id: SLT[int] = None,
|
||||
@@ -1604,6 +1722,8 @@ officedocument.wordprocessingml.document")``.
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
bot_id: SLT[int] = None,
|
||||
@@ -1703,6 +1823,8 @@ officedocument.wordprocessingml.document")``.
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def get_chat_or_user(self, message: Message) -> Optional[Chat]:
|
||||
return message.chat
|
||||
|
||||
@@ -1794,6 +1916,8 @@ officedocument.wordprocessingml.document")``.
|
||||
is specified in :attr:`chat_ids` and :attr:`usernames`.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def get_chat_or_user(self, message: Message) -> Union[User, Chat, None]:
|
||||
return message.forward_from or message.forward_from_chat
|
||||
|
||||
@@ -1899,6 +2023,8 @@ officedocument.wordprocessingml.document")``.
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def get_chat_or_user(self, message: Message) -> Optional[Chat]:
|
||||
return message.sender_chat
|
||||
|
||||
@@ -1945,12 +2071,16 @@ officedocument.wordprocessingml.document")``.
|
||||
return super().remove_chat_ids(chat_id)
|
||||
|
||||
class _SuperGroup(MessageFilter):
|
||||
__slots__ = ()
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
if message.sender_chat:
|
||||
return message.sender_chat.type == Chat.SUPERGROUP
|
||||
return False
|
||||
|
||||
class _Channel(MessageFilter):
|
||||
__slots__ = ()
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
if message.sender_chat:
|
||||
return message.sender_chat.type == Chat.CHANNEL
|
||||
@@ -1960,6 +2090,7 @@ officedocument.wordprocessingml.document")``.
|
||||
channel = _Channel()
|
||||
|
||||
class _Invoice(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.invoice'
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
@@ -1969,6 +2100,7 @@ officedocument.wordprocessingml.document")``.
|
||||
"""Messages that contain :class:`telegram.Invoice`."""
|
||||
|
||||
class _SuccessfulPayment(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.successful_payment'
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
@@ -1978,6 +2110,7 @@ officedocument.wordprocessingml.document")``.
|
||||
"""Messages that confirm a :class:`telegram.SuccessfulPayment`."""
|
||||
|
||||
class _PassportData(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.passport_data'
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
@@ -1987,6 +2120,7 @@ officedocument.wordprocessingml.document")``.
|
||||
"""Messages that contain a :class:`telegram.PassportData`"""
|
||||
|
||||
class _Poll(MessageFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.poll'
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
@@ -1996,6 +2130,7 @@ officedocument.wordprocessingml.document")``.
|
||||
"""Messages that contain a :class:`telegram.Poll`."""
|
||||
|
||||
class _Dice(_DiceEmoji):
|
||||
__slots__ = ()
|
||||
dice = _DiceEmoji('🎲', 'dice')
|
||||
darts = _DiceEmoji('🎯', 'darts')
|
||||
basketball = _DiceEmoji('🏀', 'basketball')
|
||||
@@ -2010,10 +2145,15 @@ officedocument.wordprocessingml.document")``.
|
||||
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)``.
|
||||
|
||||
To allow only dice messages with the emoji 🎲, but any value, use
|
||||
``MessageHandler(Filters.dice.dice, callback_method)``.
|
||||
|
||||
To allow only dice messages with the emoji 🎯 and with value 6, use
|
||||
``MessageHandler(Filters.dice.darts(6), callback_method)``.
|
||||
|
||||
To allow only dice messages with the emoji ⚽ and with value 5 `or` 6, use
|
||||
``MessageHandler(Filters.dice.football([5, 6]), callback_method)``.
|
||||
|
||||
Note:
|
||||
Dice messages don't have text. If you want to filter either text or dice messages, use
|
||||
@@ -2059,6 +2199,8 @@ officedocument.wordprocessingml.document")``.
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ('lang',)
|
||||
|
||||
def __init__(self, lang: SLT[str]):
|
||||
if isinstance(lang, str):
|
||||
lang = cast(str, lang)
|
||||
@@ -2075,10 +2217,26 @@ officedocument.wordprocessingml.document")``.
|
||||
and any(message.from_user.language_code.startswith(x) for x in self.lang)
|
||||
)
|
||||
|
||||
class _Attachment(MessageFilter):
|
||||
__slots__ = ()
|
||||
|
||||
name = 'Filters.attachment'
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
return bool(message.effective_attachment)
|
||||
|
||||
attachment = _Attachment()
|
||||
"""Messages that contain :meth:`telegram.Message.effective_attachment`.
|
||||
|
||||
|
||||
.. versionadded:: 13.6"""
|
||||
|
||||
class _UpdateType(UpdateFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.update'
|
||||
|
||||
class _Message(UpdateFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.update.message'
|
||||
|
||||
def filter(self, update: Update) -> bool:
|
||||
@@ -2087,6 +2245,7 @@ officedocument.wordprocessingml.document")``.
|
||||
message = _Message()
|
||||
|
||||
class _EditedMessage(UpdateFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.update.edited_message'
|
||||
|
||||
def filter(self, update: Update) -> bool:
|
||||
@@ -2095,6 +2254,7 @@ officedocument.wordprocessingml.document")``.
|
||||
edited_message = _EditedMessage()
|
||||
|
||||
class _Messages(UpdateFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.update.messages'
|
||||
|
||||
def filter(self, update: Update) -> bool:
|
||||
@@ -2103,6 +2263,7 @@ officedocument.wordprocessingml.document")``.
|
||||
messages = _Messages()
|
||||
|
||||
class _ChannelPost(UpdateFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.update.channel_post'
|
||||
|
||||
def filter(self, update: Update) -> bool:
|
||||
@@ -2111,6 +2272,7 @@ officedocument.wordprocessingml.document")``.
|
||||
channel_post = _ChannelPost()
|
||||
|
||||
class _EditedChannelPost(UpdateFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.update.edited_channel_post'
|
||||
|
||||
def filter(self, update: Update) -> bool:
|
||||
@@ -2119,6 +2281,7 @@ officedocument.wordprocessingml.document")``.
|
||||
edited_channel_post = _EditedChannelPost()
|
||||
|
||||
class _ChannelPosts(UpdateFilter):
|
||||
__slots__ = ()
|
||||
name = 'Filters.update.channel_posts'
|
||||
|
||||
def filter(self, update: Update) -> bool:
|
||||
|
||||
+47
-9
@@ -17,22 +17,25 @@
|
||||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains the base class for handlers as used by the Dispatcher."""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, TypeVar, Union, Generic
|
||||
from sys import version_info as py_ver
|
||||
|
||||
from telegram.utils.deprecate import set_new_attribute_deprecated
|
||||
|
||||
from telegram import Update
|
||||
from telegram.ext.utils.promise import Promise
|
||||
from telegram.utils.helpers import DefaultValue, DEFAULT_FALSE
|
||||
from telegram.ext.utils.types import CCT
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram.ext import CallbackContext, Dispatcher
|
||||
from telegram.ext import Dispatcher
|
||||
|
||||
RT = TypeVar('RT')
|
||||
UT = TypeVar('UT')
|
||||
|
||||
|
||||
class Handler(Generic[UT], ABC):
|
||||
class Handler(Generic[UT, CCT], ABC):
|
||||
"""The base class for all update handlers. Create custom handlers by inheriting from it.
|
||||
|
||||
Note:
|
||||
@@ -90,9 +93,30 @@ class Handler(Generic[UT], ABC):
|
||||
|
||||
"""
|
||||
|
||||
# Apparently Py 3.7 and below have '__dict__' in ABC
|
||||
if py_ver < (3, 7):
|
||||
__slots__ = (
|
||||
'callback',
|
||||
'pass_update_queue',
|
||||
'pass_job_queue',
|
||||
'pass_user_data',
|
||||
'pass_chat_data',
|
||||
'run_async',
|
||||
)
|
||||
else:
|
||||
__slots__ = (
|
||||
'callback', # type: ignore[assignment]
|
||||
'pass_update_queue',
|
||||
'pass_job_queue',
|
||||
'pass_user_data',
|
||||
'pass_chat_data',
|
||||
'run_async',
|
||||
'__dict__',
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
callback: Callable[[UT, 'CallbackContext'], RT],
|
||||
callback: Callable[[UT, CCT], RT],
|
||||
pass_update_queue: bool = False,
|
||||
pass_job_queue: bool = False,
|
||||
pass_user_data: bool = False,
|
||||
@@ -106,6 +130,17 @@ class Handler(Generic[UT], ABC):
|
||||
self.pass_chat_data = pass_chat_data
|
||||
self.run_async = run_async
|
||||
|
||||
def __setattr__(self, key: str, value: object) -> None:
|
||||
# See comment on BaseFilter to know why this was done.
|
||||
if key.startswith('__'):
|
||||
key = f"_{self.__class__.__name__}{key}"
|
||||
if issubclass(self.__class__, Handler) and not self.__class__.__module__.startswith(
|
||||
'telegram.ext.'
|
||||
):
|
||||
object.__setattr__(self, key, value)
|
||||
return
|
||||
set_new_attribute_deprecated(self, key, value)
|
||||
|
||||
@abstractmethod
|
||||
def check_update(self, update: object) -> Optional[Union[bool, object]]:
|
||||
"""
|
||||
@@ -131,7 +166,7 @@ class Handler(Generic[UT], ABC):
|
||||
update: UT,
|
||||
dispatcher: 'Dispatcher',
|
||||
check_result: object,
|
||||
context: 'CallbackContext' = None,
|
||||
context: CCT = None,
|
||||
) -> Union[RT, Promise]:
|
||||
"""
|
||||
This method is called if it was determined that an update should indeed
|
||||
@@ -149,9 +184,12 @@ class Handler(Generic[UT], ABC):
|
||||
|
||||
"""
|
||||
run_async = self.run_async
|
||||
if self.run_async is DEFAULT_FALSE and dispatcher.bot.defaults:
|
||||
if dispatcher.bot.defaults.run_async:
|
||||
run_async = True
|
||||
if (
|
||||
self.run_async is DEFAULT_FALSE
|
||||
and dispatcher.bot.defaults
|
||||
and dispatcher.bot.defaults.run_async
|
||||
):
|
||||
run_async = True
|
||||
|
||||
if context:
|
||||
self.collect_additional_context(context, update, dispatcher, check_result)
|
||||
@@ -168,7 +206,7 @@ class Handler(Generic[UT], ABC):
|
||||
|
||||
def collect_additional_context(
|
||||
self,
|
||||
context: 'CallbackContext',
|
||||
context: CCT,
|
||||
update: UT,
|
||||
dispatcher: 'Dispatcher',
|
||||
check_result: Any,
|
||||
|
||||
@@ -16,7 +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/].
|
||||
""" This module contains the InlineQueryHandler class """
|
||||
"""This module contains the InlineQueryHandler class."""
|
||||
import re
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
@@ -35,14 +35,15 @@ from telegram import Update
|
||||
from telegram.utils.helpers import DefaultValue, DEFAULT_FALSE
|
||||
|
||||
from .handler import Handler
|
||||
from .utils.types import CCT
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram.ext import CallbackContext, Dispatcher
|
||||
from telegram.ext import Dispatcher
|
||||
|
||||
RT = TypeVar('RT')
|
||||
|
||||
|
||||
class InlineQueryHandler(Handler[Update]):
|
||||
class InlineQueryHandler(Handler[Update, CCT]):
|
||||
"""
|
||||
Handler class to handle Telegram inline queries. Optionally based on a regex. Read the
|
||||
documentation of the ``re`` module for more information.
|
||||
@@ -129,9 +130,11 @@ class InlineQueryHandler(Handler[Update]):
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ('pattern', 'chat_types', 'pass_groups', 'pass_groupdict')
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
callback: Callable[[Update, 'CallbackContext'], RT],
|
||||
callback: Callable[[Update, CCT], RT],
|
||||
pass_update_queue: bool = False,
|
||||
pass_job_queue: bool = False,
|
||||
pattern: Union[str, Pattern] = None,
|
||||
@@ -170,7 +173,6 @@ class InlineQueryHandler(Handler[Update]):
|
||||
:obj:`bool`
|
||||
|
||||
"""
|
||||
|
||||
if isinstance(update, Update) and update.inline_query:
|
||||
if (self.chat_types is not None) and (
|
||||
update.inline_query.chat_type not in self.chat_types
|
||||
@@ -191,6 +193,10 @@ class InlineQueryHandler(Handler[Update]):
|
||||
update: Update = None,
|
||||
check_result: Optional[Union[bool, Match]] = None,
|
||||
) -> Dict[str, object]:
|
||||
"""Pass the results of ``re.match(pattern, query).{groups(), groupdict()}`` to the
|
||||
callback as a keyword arguments called ``groups`` and ``groupdict``, respectively, if
|
||||
needed.
|
||||
"""
|
||||
optional_args = super().collect_optional_args(dispatcher, update, check_result)
|
||||
if self.pattern:
|
||||
check_result = cast(Match, check_result)
|
||||
@@ -202,11 +208,14 @@ class InlineQueryHandler(Handler[Update]):
|
||||
|
||||
def collect_additional_context(
|
||||
self,
|
||||
context: 'CallbackContext',
|
||||
context: CCT,
|
||||
update: Update,
|
||||
dispatcher: 'Dispatcher',
|
||||
check_result: Optional[Union[bool, Match]],
|
||||
) -> None:
|
||||
"""Add the result of ``re.match(pattern, update.inline_query.query)`` to
|
||||
:attr:`CallbackContext.matches` as list with one element.
|
||||
"""
|
||||
if self.pattern:
|
||||
check_result = cast(Match, check_result)
|
||||
context.matches = [check_result]
|
||||
|
||||
+49
-29
@@ -31,6 +31,7 @@ from apscheduler.job import Job as APSJob
|
||||
|
||||
from telegram.ext.callbackcontext import CallbackContext
|
||||
from telegram.utils.types import JSONDict
|
||||
from telegram.utils.deprecate import set_new_attribute_deprecated
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Bot
|
||||
@@ -38,11 +39,6 @@ if TYPE_CHECKING:
|
||||
import apscheduler.job # noqa: F401
|
||||
|
||||
|
||||
class Days:
|
||||
MON, TUE, WED, THU, FRI, SAT, SUN = range(7)
|
||||
EVERY_DAY = tuple(range(7))
|
||||
|
||||
|
||||
class JobQueue:
|
||||
"""This class allows you to periodically perform tasks with the bot. It is a convenience
|
||||
wrapper for the APScheduler library.
|
||||
@@ -54,6 +50,8 @@ class JobQueue:
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ('_dispatcher', 'logger', 'scheduler', '__dict__')
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._dispatcher: 'Dispatcher' = None # type: ignore[assignment]
|
||||
self.logger = logging.getLogger(self.__class__.__name__)
|
||||
@@ -69,15 +67,18 @@ class JobQueue:
|
||||
logging.getLogger('apscheduler.executors.default').addFilter(aps_log_filter)
|
||||
self.scheduler.add_listener(self._dispatch_error, EVENT_JOB_ERROR)
|
||||
|
||||
def __setattr__(self, key: str, value: object) -> None:
|
||||
set_new_attribute_deprecated(self, key, value)
|
||||
|
||||
def _build_args(self, job: 'Job') -> List[Union[CallbackContext, 'Bot', 'Job']]:
|
||||
if self._dispatcher.use_context:
|
||||
return [CallbackContext.from_job(job, self._dispatcher)]
|
||||
return [self._dispatcher.context_types.context.from_job(job, self._dispatcher)]
|
||||
return [self._dispatcher.bot, job]
|
||||
|
||||
def _tz_now(self) -> datetime.datetime:
|
||||
return datetime.datetime.now(self.scheduler.timezone)
|
||||
|
||||
def _update_persistence(self, event: JobEvent) -> None: # pylint: disable=W0613
|
||||
def _update_persistence(self, _: JobEvent) -> None:
|
||||
self._dispatcher.update_persistence()
|
||||
|
||||
def _dispatch_error(self, event: JobEvent) -> None:
|
||||
@@ -136,8 +137,7 @@ class JobQueue:
|
||||
"""
|
||||
self._dispatcher = dispatcher
|
||||
if dispatcher.bot.defaults:
|
||||
if dispatcher.bot.defaults:
|
||||
self.scheduler.configure(timezone=dispatcher.bot.defaults.tzinfo or pytz.utc)
|
||||
self.scheduler.configure(timezone=dispatcher.bot.defaults.tzinfo or pytz.utc)
|
||||
|
||||
def run_once(
|
||||
self,
|
||||
@@ -218,6 +218,12 @@ class JobQueue:
|
||||
) -> 'Job':
|
||||
"""Creates a new ``Job`` that runs at specified intervals and adds it to the queue.
|
||||
|
||||
Note:
|
||||
For a note about DST, please see the documentation of `APScheduler`_.
|
||||
|
||||
.. _`APScheduler`: https://apscheduler.readthedocs.io/en/stable/modules/triggers/cron.html
|
||||
#daylight-saving-time-behavior
|
||||
|
||||
Args:
|
||||
callback (:obj:`callable`): The callback function that should be executed by the new
|
||||
job. Callback signature for context based API:
|
||||
@@ -268,11 +274,6 @@ class JobQueue:
|
||||
:class:`telegram.ext.Job`: The new ``Job`` instance that has been added to the job
|
||||
queue.
|
||||
|
||||
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.
|
||||
|
||||
"""
|
||||
if not job_kwargs:
|
||||
job_kwargs = {}
|
||||
@@ -392,13 +393,19 @@ class JobQueue:
|
||||
self,
|
||||
callback: Callable[['CallbackContext'], None],
|
||||
time: datetime.time,
|
||||
days: Tuple[int, ...] = Days.EVERY_DAY,
|
||||
days: Tuple[int, ...] = tuple(range(7)),
|
||||
context: object = None,
|
||||
name: str = None,
|
||||
job_kwargs: JSONDict = None,
|
||||
) -> 'Job':
|
||||
"""Creates a new ``Job`` that runs on a daily basis and adds it to the queue.
|
||||
|
||||
Note:
|
||||
For a note about DST, please see the documentation of `APScheduler`_.
|
||||
|
||||
.. _`APScheduler`: https://apscheduler.readthedocs.io/en/stable/modules/triggers/cron.html
|
||||
#daylight-saving-time-behavior
|
||||
|
||||
Args:
|
||||
callback (:obj:`callable`): The callback function that should be executed by the new
|
||||
job. Callback signature for context based API:
|
||||
@@ -422,12 +429,6 @@ class JobQueue:
|
||||
:class:`telegram.ext.Job`: The new ``Job`` instance that has been added to the job
|
||||
queue.
|
||||
|
||||
Note:
|
||||
For a note about DST, please see the documentation of `APScheduler`_.
|
||||
|
||||
.. _`APScheduler`: https://apscheduler.readthedocs.io/en/stable/modules/triggers/cron.html
|
||||
#daylight-saving-time-behavior
|
||||
|
||||
"""
|
||||
if not job_kwargs:
|
||||
job_kwargs = {}
|
||||
@@ -499,14 +500,16 @@ class JobQueue:
|
||||
self.scheduler.shutdown()
|
||||
|
||||
def jobs(self) -> Tuple['Job', ...]:
|
||||
"""
|
||||
Returns a tuple of all *pending/scheduled* jobs that are currently in the ``JobQueue``.
|
||||
"""
|
||||
return tuple(Job.from_aps_job(job, self) for job in self.scheduler.get_jobs())
|
||||
"""Returns a tuple of all *scheduled* jobs that are currently in the ``JobQueue``."""
|
||||
return tuple(
|
||||
Job._from_aps_job(job, self) # pylint: disable=W0212
|
||||
for job in self.scheduler.get_jobs()
|
||||
)
|
||||
|
||||
def get_jobs_by_name(self, name: str) -> Tuple['Job', ...]:
|
||||
"""Returns a tuple of all *pending/scheduled* jobs with the given name that are currently
|
||||
in the ``JobQueue``"""
|
||||
in the ``JobQueue``.
|
||||
"""
|
||||
return tuple(job for job in self.jobs() if job.name == name)
|
||||
|
||||
|
||||
@@ -515,6 +518,9 @@ class Job:
|
||||
With the current backend APScheduler, :attr:`job` holds a :class:`apscheduler.job.Job`
|
||||
instance.
|
||||
|
||||
Objects of this class are comparable in terms of equality. Two objects of this class are
|
||||
considered equal, if their :attr:`id` is equal.
|
||||
|
||||
Note:
|
||||
* All attributes and instance methods of :attr:`job` are also directly available as
|
||||
attributes/methods of the corresponding :class:`telegram.ext.Job` object.
|
||||
@@ -546,6 +552,17 @@ class Job:
|
||||
job (:class:`apscheduler.job.Job`): Optional. The APS Job this job is a wrapper for.
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
'callback',
|
||||
'context',
|
||||
'name',
|
||||
'job_queue',
|
||||
'_removed',
|
||||
'_enabled',
|
||||
'job',
|
||||
'__dict__',
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
callback: Callable[['CallbackContext'], None],
|
||||
@@ -563,13 +580,16 @@ class Job:
|
||||
self._removed = False
|
||||
self._enabled = False
|
||||
|
||||
self.job = cast(APSJob, job)
|
||||
self.job = cast(APSJob, job) # skipcq: PTC-W0052
|
||||
|
||||
def __setattr__(self, key: str, value: object) -> None:
|
||||
set_new_attribute_deprecated(self, key, value)
|
||||
|
||||
def run(self, dispatcher: 'Dispatcher') -> None:
|
||||
"""Executes the callback function independently of the jobs schedule."""
|
||||
try:
|
||||
if dispatcher.use_context:
|
||||
self.callback(CallbackContext.from_job(self, dispatcher))
|
||||
self.callback(dispatcher.context_types.context.from_job(self, dispatcher))
|
||||
else:
|
||||
self.callback(dispatcher.bot, self) # type: ignore[arg-type,call-arg]
|
||||
except Exception as exc:
|
||||
@@ -619,7 +639,7 @@ class Job:
|
||||
return self.job.next_run_time
|
||||
|
||||
@classmethod
|
||||
def from_aps_job(cls, job: APSJob, job_queue: JobQueue) -> 'Job':
|
||||
def _from_aps_job(cls, job: APSJob, job_queue: JobQueue) -> 'Job':
|
||||
# context based callbacks
|
||||
if len(job.args) == 1:
|
||||
context = job.args[0].job.context
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user