Compare commits

...

42 Commits

Author SHA1 Message Date
Hinrich Mahler bfbf6d3f94 Bump Version to v20.3 2023-05-07 15:31:23 +02:00
Bibo-Joshi 0c4180c74b Documentation Improvements (#3628, #3636, #3694)
Co-authored-by: Yossi Rafelson <yossi.rafelson@gmail.com>
Co-authored-by: Aditya <clot27@apx_managed.vanilla>
Co-authored-by: ibragimovgeorge <103066850+ibragimovgeorge@users.noreply.github.com>
2023-05-07 14:51:22 +02:00
Bibo-Joshi 1c6ae435bf Add a Stability Policy (#3622)
Co-authored-by: poolitzer <github@poolitzer.eu>
Co-authored-by: Harshil <37377066+harshil21@users.noreply.github.com>
Co-authored-by: Dmitry Kolomatskiy <58207913+lemontree210@users.noreply.github.com>
2023-05-07 14:50:41 +02:00
Bibo-Joshi 66b6d3c497 Recover 100% Type Completeness (#3676) 2023-05-07 14:10:20 +02:00
Bibo-Joshi 8c252c9822 API 6.7 (#3673)
Co-authored-by: Dmitry Kolomatskiy <58207913+lemontree210@users.noreply.github.com>
Co-authored-by: Harshil Mehta <37377066+harshil21@users.noreply.github.com>
Co-authored-by: poolitzer <github@poolitzer.eu>
Co-authored-by: Aditya <clot27@apx_managed.vanilla>
2023-05-07 13:44:34 +02:00
Bibo-Joshi 450dc2115c Shield Update Fetcher Task in Application.start (#3657) 2023-05-06 21:10:12 +02:00
pre-commit-ci[bot] 87a6890900 pre-commit autoupdate (#3688) 2023-05-06 20:52:58 +02:00
Bibo-Joshi 83ab12c387 Stabilize test_delete_sticker_set (#3685)
Co-authored-by: poolitzer <github@poolitzer.eu>
2023-04-30 11:03:00 +02:00
Bibo-Joshi 3f444dad8d Improve Warning Categories & Stacklevels (#3674) 2023-04-27 22:36:04 +02:00
Bibo-Joshi f23315d08b Add Logging for Invalid JSON Data in BasePersistence.parse_json_payload (#3668) 2023-04-18 16:17:20 +02:00
Luca Bellanti 7b116be344 Localize Received datetime Objects According to Defaults.tzinfo (#3632) 2023-04-18 16:16:23 +02:00
dependabot[bot] 934e4c9bd4 Bump pytest from 7.2.2 to 7.3.1 (#3661)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com>
2023-04-16 11:24:50 +02:00
dependabot[bot] 1966fb25c5 Update httpx requirement from ~=0.23.3 to ~=0.24.0 (#3660)
Co-authored-by: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com>
2023-04-16 11:20:36 +02:00
dependabot[bot] a333d8514a Bump sphinx-copybutton from 0.5.1 to 0.5.2 (#3662)
Co-authored-by: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com>
2023-04-16 11:15:06 +02:00
Bibo-Joshi b146c7131e Remove Deprecated codecov Package from CI (#3664) 2023-04-16 10:54:49 +02:00
Bibo-Joshi 53093ebceb Give Loggers Better Names (#3623) 2023-04-10 17:01:35 +02:00
Luca Bellanti 401b2decce Make Message.link Point to Thread View Where Possible (#3640) 2023-04-07 17:13:45 +02:00
pre-commit-ci[bot] 83a164e5ef pre-commit autoupdate (#3646)
Co-authored-by: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com>
Co-authored-by: Dmitry Kolomatskiy <58207913+lemontree210@users.noreply.github.com>
Co-authored-by: Harshil Mehta <37377066+harshil21@users.noreply.github.com>
2023-04-05 20:52:52 +02:00
Bibo-Joshi d91bc45cdc Add Application.mark_data_for_update_persistence (#3607) 2023-04-02 22:29:16 +02:00
dependabot[bot] 8967912f46 Bump furo from 2023.3.23 to 2023.3.27 (#3643) 2023-04-02 21:42:57 +02:00
dependabot[bot] 7ab2cafbee Bump actions/stale from 7 to 8 (#3644) 2023-04-02 21:41:37 +02:00
Harshil 7e0ed2235e Stabilize CI by Rerunning Failed Tests (#3631)
Co-authored-by: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com>
2023-04-01 22:35:20 +02:00
Harshil 53abb7b4bd Add String Representation for RequestParameter (#3634) 2023-03-27 22:03:02 +02:00
Harshil 11f86b8813 Drop Usage of sys.maxunicode (#3630) 2023-03-26 21:58:23 +02:00
Harshil 9997a9f47e Empower ruff (#3594) 2023-03-25 19:18:04 +01:00
Hinrich Mahler 4aedb33d37 Bump version to v20.2 2023-03-25 13:25:59 +01:00
Bibo-Joshi 4ddc36c625 Documentation Improvements (#3565, #3600)
Co-authored-by: Dmitry Kolomatskiy <58207913+lemontree210@users.noreply.github.com>
Co-authored-by: poolitzer <github@poolitzer.eu>
Co-authored-by: Louis Wang <beiluonever@users.noreply.github.com>
2023-03-25 12:48:34 +01:00
dependabot[bot] 311f88a716 Bump pytest-asyncio from 0.20.3 to 0.21.0 (#3624) 2023-03-25 12:25:57 +01:00
dependabot[bot] 179cb49dbc Bump furo from 2022.12.7 to 2023.3.23 (#3625) 2023-03-25 12:25:36 +01:00
Bibo-Joshi 512e390738 API 6.6 (#3584)
Co-authored-by: poolitzer <github@poolitzer.eu>
Co-authored-by: Dmitry Kolomatskiy <58207913+lemontree210@users.noreply.github.com>
Co-authored-by: Harshil <37377066+harshil21@users.noreply.github.com>
2023-03-25 11:47:26 +01:00
dependabot[bot] 33b677aeb4 Bump pytest-xdist from 3.2.0 to 3.2.1 (#3606) 2023-03-19 10:54:03 +01:00
Poolitzer 800598ced4 Revert to HTTP/1.1 as Default and make HTTP/2 an Optional Dependency (#3576)
Co-authored-by: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com>
Co-authored-by: Harshil <37377066+harshil21@users.noreply.github.com>
2023-03-12 16:30:39 +01:00
pre-commit-ci[bot] ec20f27a82 pre-commit autoupdate (#3577)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-03-08 20:46:51 +01:00
Bibo-Joshi cd59c1c075 Handle Symbolic Links in was_called_by (#3552)
Co-authored-by: hyfc <myloxyloto2012@gmail.com>
2023-03-06 21:59:01 +01:00
dependabot[bot] 52b0f2c3c9 Update apscheduler requirement from ~=3.10.0 to ~=3.10.1 (#3572)
Co-authored-by: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com>
2023-03-06 21:09:29 +01:00
dependabot[bot] 598f44f4c2 Bump pytest from 7.2.1 to 7.2.2 (#3573) 2023-03-05 16:21:57 +01:00
Bibo-Joshi 22d0dd1301 Tidy Up Tests Directory (#3553) 2023-02-22 20:19:46 +01:00
sam-mosleh c6b6b0a370 Enhance Application.create_task (#3543) 2023-02-20 19:53:27 +01:00
Bibo-Joshi ee6c8a5995 Make Type Completeness Workflow Usable for PRs from Forks (#3551) 2023-02-13 20:39:12 +01:00
dependabot[bot] d5a1a48145 Bump pytest-xdist from 3.1.0 to 3.2.0 (#3550) 2023-02-11 17:42:10 +01:00
Harshil 963edbf191 Refactor and Overhaul the Test Suite (#3426) 2023-02-11 10:45:17 +01:00
dependabot[bot] 43c3c8f568 Bump sphinxcontrib-mermaid from 0.7.1 to 0.8 (#3549) 2023-02-11 10:43:38 +01:00
306 changed files with 16895 additions and 9173 deletions
+56 -31
View File
@@ -67,6 +67,7 @@ Here's how to make a one-off code change.
$ git checkout -b your-branch-name
3. **Make a commit to your feature branch**. Each commit should be self-contained and have a descriptive commit message that helps other developers understand why the changes were made.
We also have a check-list for PRs `below`_.
- You can refer to relevant issues in the commit message by writing, e.g., "#105".
@@ -80,39 +81,17 @@ Here's how to make a one-off code change.
- The following exceptions to the above (Google's) style guides applies:
- Documenting types of global variables and complex types of class members can be done using the Sphinx docstring convention.
- Documenting types of global variables and complex types of class members can be done using the Sphinx docstring convention.
- In addition, PTB uses some formatting/styling and linting tools in the pre-commit setup. Some of those tools also have command line tools that can help to run these tools outside of the pre-commit step. If you'd like to leverage that, please have a look at the `pre-commit config file`_ for an overview of which tools (and which versions of them) are used. For example, we use `Black`_ for code formatting. Plugins for Black exist for some `popular editors`_. You can use those instead of manually formatting everything.
- Please ensure that the code you write is well-tested.
- Please ensure that the code you write is well-tested and that all automated tests still pass. We
have dedicated an `testing page`_ to help you with that.
- In addition to that, we provide the `dev` marker for pytest. If you write one or multiple tests and want to run only those, you can decorate them via `@pytest.mark.dev` and then run it with minimal overhead with `pytest ./path/to/test_file.py -m dev`.
- Dont break backward compatibility.
- Don't break backward compatibility.
- Add yourself to the AUTHORS.rst_ file in an alphabetical fashion.
- Before making a commit ensure that all automated tests still pass:
.. code-block:: bash
$ pytest -v
Since the tests can take a while to run, you can speed things up by running them in parallel
using `pytest-xdist`_ (note that this may effect the result of the test in some rare cases):
.. code-block:: bash
$ pytest -v -n auto --dist=loadfile
To run ``test_official`` (particularly useful if you made API changes), run
.. code-block:: bash
$ export TEST_OFFICIAL=true
prior to running the tests.
- If you want run style & type checks before committing run
.. code-block:: bash
@@ -143,11 +122,11 @@ Here's how to make a one-off code change.
- When your reviewer has reviewed the code, you'll get a notification. You'll need to respond in two ways:
- Make a new commit addressing the comments you agree with, and push it to the same branch. Ideally, the commit message would explain what the commit does (e.g. "Fix lint error"), but if there are lots of disparate review comments, it's fine to refer to the original commit message and add something like "(address review comments)".
- Make a new commit addressing the comments you agree with, and push it to the same branch. Ideally, the commit message would explain what the commit does (e.g. "Fix lint error"), but if there are lots of disparate review comments, it's fine to refer to the original commit message and add something like "(address review comments)".
- In order to keep the commit history intact, please avoid squashing or amending history and then force-pushing to the PR. Reviewers often want to look at individual commits.
- In order to keep the commit history intact, please avoid squashing or amending history and then force-pushing to the PR. Reviewers often want to look at individual commits.
- In addition, please reply to each comment. Each reply should be either "Done" or a response explaining why the corresponding suggestion wasn't implemented. All comments must be resolved before LGTM can be given.
- In addition, please reply to each comment. Each reply should be either "Done" or a response explaining why the corresponding suggestion wasn't implemented. All comments must be resolved before LGTM can be given.
- Resolve any merge conflicts that arise. To resolve conflicts between 'your-branch-name' (in your fork) and 'master' (in the ``python-telegram-bot`` repository), run:
@@ -172,6 +151,51 @@ Here's how to make a one-off code change.
7. **Celebrate.** Congratulations, you have contributed to ``python-telegram-bot``!
Check-list for PRs
------------------
This checklist is a non-exhaustive reminder of things that should be done before a PR is merged, both for you as contributor and for the maintainers.
Feel free to copy (parts of) the checklist to the PR description to remind you or the maintainers of open points or if you have questions on anything.
- Added ``.. versionadded:: NEXT.VERSION``, ``.. versionchanged:: NEXT.VERSION`` or ``.. deprecated:: NEXT.VERSION`` to the docstrings for user facing changes (for methods/class descriptions, arguments and attributes)
- Created new or adapted existing unit tests
- Documented code changes according to the `CSI standard <https://standards.mousepawmedia.com/en/stable/csi.html>`__
- Added myself alphabetically to ``AUTHORS.rst`` (optional)
- Added new classes & modules to the docs and all suitable ``__all__`` s
- Checked the `Stability Policy <https://docs.python-telegram-bot.org/stability_policy.html>`_ in case of deprecations or changes to documented behavior
**If the PR contains API changes (otherwise, you can ignore this passage)**
- Checked the Bot API specific sections of the `Stability Policy <https://docs.python-telegram-bot.org/stability_policy.html>`_
- New classes:
- Added ``self._id_attrs`` and corresponding documentation
- ``__init__`` accepts ``api_kwargs`` as kw-only
- Added new shortcuts:
- In :class:`~telegram.Chat` & :class:`~telegram.User` for all methods that accept ``chat/user_id``
- In :class:`~telegram.Message` for all methods that accept ``chat_id`` and ``message_id``
- For new :class:`~telegram.Message` shortcuts: Added ``quote`` argument if methods accepts ``reply_to_message_id``
- In :class:`~telegram.CallbackQuery` for all methods that accept either ``chat_id`` and ``message_id`` or ``inline_message_id``
- If relevant:
- Added new constants at :mod:`telegram.constants` and shortcuts to them as class variables
- Link new and existing constants in docstrings instead of hard-coded numbers and strings
- Add new message types to :attr:`telegram.Message.effective_attachment`
- Added new handlers for new update types
- Add the handlers to the warning loop in the :class:`~telegram.ext.ConversationHandler`
- Added new filters for new message (sub)types
- Added or updated documentation for the changed class(es) and/or method(s)
- Added the new method(s) to ``_extbot.py``
- Added or updated ``bot_methods.rst``
- 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_INFO``
- Added logic for arbitrary callback data in :class:`telegram.ext.ExtBot` for new methods that either accept a ``reply_markup`` in some form or have a return type that is/contains :class:`~telegram.Message`
Documenting
===========
@@ -205,7 +229,7 @@ or, if you don't have ``make`` available (e.g. on Windows):
Once the process terminates, you can view the built documentation by opening ``docs/build/html/index.html`` with a browser.
- Add ``.. versionadded:: version``, ``.. versionchanged:: version`` or ``.. deprecated:: version`` to the associated documentation of your changes, depending on what kind of change you made. This only applies if the change you made is visible to an end user. The directives should be added to class/method descriptions if their general behaviour changed and to the description of all arguments & attributes that changed.
- Add ``.. versionadded:: NEXT.VERSION``, ``.. versionchanged:: NEXT.VERSION`` or ``.. deprecated:: NEXT.VERSION`` to the associated documentation of your changes, depending on what kind of change you made. This only applies if the change you made is visible to an end user. The directives should be added to class/method descriptions if their general behaviour changed and to the description of all arguments & attributes that changed.
Dev facing documentation
------------------------
@@ -287,4 +311,5 @@ break the API classes. For example:
.. _`RTD build`: https://docs.python-telegram-bot.org/en/doc-fixes
.. _`CSI`: https://standards.mousepawmedia.com/en/stable/csi.html
.. _`section`: #documenting
.. _`pytest-xdist`: https://github.com/pytest-dev/pytest-xdist
.. _`testing page`: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/tests/README.rst
.. _`below`: #check-list-for-prs
+3 -35
View File
@@ -1,38 +1,6 @@
<!--
Hey! You're PRing? Cool! Please have a look at the below checklist. It's here to help both you and the maintainers to remember some aspects. Make sure to check out our contribution guide (https://github.com/python-telegram-bot/python-telegram-bot/blob/master/.github/CONTRIBUTING.rst).
Hey! You're PRing? Cool!
Please be sure to check out our contribution guide (https://github.com/python-telegram-bot/python-telegram-bot/blob/master/.github/CONTRIBUTING.rst).
Especially, please have a look at the check list for PRs (https://github.com/python-telegram-bot/python-telegram-bot/blob/master/.github/CONTRIBUTING.rst#checklist-for-prs). Feel free to copy (parts of) the checklist to the PR description to remind you or the maintainers of open points or if you have questions on anything.
-->
### Checklist for PRs
- [ ] Added `.. versionadded:: version`, `.. versionchanged:: version` or `.. deprecated:: version` to the docstrings for user facing changes (for methods/class descriptions, arguments and attributes)
- [ ] Created new or adapted existing unit tests
- [ ] Documented code changes according to the [CSI standard](https://standards.mousepawmedia.com/en/stable/csi.html)
- [ ] Added myself alphabetically to `AUTHORS.rst` (optional)
- [ ] Added new classes & modules to the docs and all suitable `__all__` s
### If the PR contains API changes (otherwise, you can delete this passage)
* New classes:
- [ ] Added `self._id_attrs` and corresponding documentation
- [ ] `__init__` accepts `api_kwargs` as kw-only
* Added new shortcuts:
- [ ] In `Chat` & `User` for all methods that accept `chat/user_id`
- [ ] In `Message` for all methods that accept `chat_id` and `message_id`
- [ ] For new `Message` shortcuts: Added `quote` argument if methods accepts `reply_to_message_id`
- [ ] In `CallbackQuery` for all methods that accept either `chat_id` and `message_id` or `inline_message_id`
* If relevant:
- [ ] Added new constants at `telegram.constants` and shortcuts to them as class variables
- [ ] Link new and existing constants in docstrings instead of hard coded number and strings
- [ ] Add new message types to `Message.effective_attachment`
- [ ] Added new handlers for new update types
- [ ] Add the handlers to the warning loop in the `ConversationHandler`
- [ ] Added new filters for new message (sub)types
- [ ] Added or updated documentation for the changed class(es) and/or method(s)
- [ ] Added the new method(s) to `_extbot.py`
- [ ] Added or updated `bot_methods.rst`
- [ ] 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_INFO`
- [ ] 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`
+1 -1
View File
@@ -7,7 +7,7 @@ jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v7
- uses: actions/stale@v8
with:
# PRs never get stale
days-before-stale: 3
File diff suppressed because one or more lines are too long
+17 -16
View File
@@ -23,21 +23,22 @@ jobs:
- name: Install Pyright
run: |
python -W ignore -m pip install pyright~=1.1.291
- name: Get PR Completeness
# Must run before base completeness, as base completeness will checkout the base branch
# And we can't go back to the PR branch after that in case the PR is coming from a fork
run: |
pip install . -U
pyright --verifytypes telegram --ignoreexternal --outputjson > pr.json || true
pyright --verifytypes telegram --ignoreexternal > pr.readable || true
- name: Get Base Completeness
run: |
git checkout ${{ github.base_ref }}
pip install . -U
pyright --verifytypes telegram --ignoreexternal --outputjson > base.json || true
- name: Get PR Completeness
run: |
git checkout ${{ github.head_ref }}
pip install . -U
pyright --verifytypes telegram --ignoreexternal --outputjson > pr.json || true
pyright --verifytypes telegram --ignoreexternal > pr.readable || true
- name: Compare Completeness
uses: jannekem/run-python-script-action@v1
with:
script: |
script: |
import json
import os
from pathlib import Path
@@ -48,21 +49,21 @@ jobs:
pr = float(
json.load(open("pr.json", "rb"))["typeCompleteness"]["completenessScore"]
)
base_text = f"After this PR, type completeness will be {round(pr, 3)}."
if pr < (base - 0.1):
text = f"This PR decreases type completeness by {round(base - pr, 3)}. ❌"
base_text = f"This PR changes type completeness from {round(base, 3)} to {round(pr, 3)}."
if pr < (base - 0.001):
text = f"{base_text} ❌"
set_summary(text)
print(Path("pr.readable").read_text(encoding="utf-8"))
error(f"{text}\n{base_text}")
error(text)
exit(1)
elif pr > (base + 0.1):
text = f"This PR increases type completeness by {round(pr - base, 3)}. ✨"
elif pr > (base + 0.001):
text = f"{base_text} ✨"
set_summary(text)
if pr < 1:
print(Path("pr.readable").read_text(encoding="utf-8"))
print(f"{text}\n{base_text}")
print(text)
else:
text = f"This PR does not change type completeness by more than 0.1. ✅"
text = f"{base_text} This is less than 0.1 percentage points. ✅"
set_summary(text)
print(Path("pr.readable").read_text(encoding="utf-8"))
print(f"{text}\n{base_text}")
print(text)
+2
View File
@@ -51,6 +51,8 @@ nosetests.xml
coverage.xml
*,cover
.coveralls.yml
.testmondata
.testmondata-journal
# Translations
*.mo
+10 -10
View File
@@ -9,7 +9,7 @@ ci:
repos:
- repo: https://github.com/psf/black
rev: 23.1.0
rev: 23.3.0
hooks:
- id: black
args:
@@ -20,7 +20,7 @@ repos:
hooks:
- id: flake8
- repo: https://github.com/PyCQA/pylint
rev: v2.16.1
rev: v3.0.0a6
hooks:
- id: pylint
files: ^(telegram|examples)/.*\.py$
@@ -33,12 +33,12 @@ repos:
additional_dependencies:
- httpx~=0.23.3
- tornado~=6.2
- APScheduler~=3.10.0
- APScheduler~=3.10.1
- cachetools~=5.3.0
- aiolimiter~=1.0.0
- . # this basically does `pip install -e .`
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.991
rev: v1.2.0
hooks:
- id: mypy
name: mypy-ptb
@@ -49,7 +49,7 @@ repos:
- types-cachetools
- httpx~=0.23.3
- tornado~=6.2
- APScheduler~=3.10.0
- APScheduler~=3.10.1
- cachetools~=5.3.0
- aiolimiter~=1.0.0
- . # this basically does `pip install -e .`
@@ -61,11 +61,11 @@ repos:
- --follow-imports=silent
additional_dependencies:
- tornado~=6.2
- APScheduler~=3.10.0
- APScheduler~=3.10.1
- cachetools~=5.3.0
- . # this basically does `pip install -e .`
- repo: https://github.com/asottile/pyupgrade
rev: v3.3.1
rev: v3.3.2
hooks:
- id: pyupgrade
files: ^(telegram|examples|tests|docs)/.*\.py$
@@ -80,14 +80,14 @@ repos:
- --diff
- --check
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: 'v0.0.243'
rev: 'v0.0.263'
hooks:
- id: ruff
name: ruff
files: ^(telegram|examples)/.*\.py$
files: ^(telegram|examples|tests)/.*\.py$
additional_dependencies:
- httpx~=0.23.3
- tornado~=6.2
- APScheduler~=3.10.0
- APScheduler~=3.10.1
- cachetools~=5.3.0
- aiolimiter~=1.0.0
+2
View File
@@ -72,6 +72,7 @@ The following wonderful people contributed directly or indirectly to this projec
- `Li-aung Yip <https://github.com/LiaungYip>`_
- `Loo Zheng Yuan <https://github.com/loozhengyuan>`_
- `LRezende <https://github.com/lrezende>`_
- `Luca Bellanti <https://github.com/Trifase>`_
- `macrojames <https://github.com/macrojames>`_
- `Matheus Lemos <https://github.com/mlemosf>`_
- `Michael Dix <https://github.com/Eisberge>`_
@@ -100,6 +101,7 @@ The following wonderful people contributed directly or indirectly to this projec
- `Riko Naka <https://github.com/rikonaka>`_
- `Rizlas <https://github.com/rizlas>`_
- `Sahil Sharma <https://github.com/sahilsharma811>`_
- `Sam Mosleh <https://github.com/sam-mosleh>`_
- `Sascha <https://github.com/saschalalala>`_
- `Shelomentsev D <https://github.com/shelomentsevd>`_
- `Shivam Saini <https://github.com/shivamsn97>`_
+124 -1
View File
@@ -1,7 +1,130 @@
.. _ptb-changelog:
=========
Changelog
=========
Version 20.3
============
*Released 2023-05-07*
This is the technical changelog for version 20.3. More elaborate release notes can be found in the news channel `@pythontelegrambotchannel <https://t.me/pythontelegrambotchannel>`_.
Major Changes
-------------
- Full support for API 6.7 (`#3673`_)
- Add a Stability Policy (`#3622`_)
New Features
------------
- Add ``Application.mark_data_for_update_persistence`` (`#3607`_)
- Make ``Message.link`` Point to Thread View Where Possible (`#3640`_)
- Localize Received ``datetime`` Objects According to ``Defaults.tzinfo`` (`#3632`_)
Minor Changes, Documentation Improvements and CI
------------------------------------------------
- Empower ``ruff`` (`#3594`_)
- Drop Usage of ``sys.maxunicode`` (`#3630`_)
- Add String Representation for ``RequestParameter`` (`#3634`_)
- Stabilize CI by Rerunning Failed Tests (`#3631`_)
- Give Loggers Better Names (`#3623`_)
- Add Logging for Invalid JSON Data in ``BasePersistence.parse_json_payload`` (`#3668`_)
- Improve Warning Categories & Stacklevels (`#3674`_)
- Stabilize ``test_delete_sticker_set`` (`#3685`_)
- Shield Update Fetcher Task in ``Application.start`` (`#3657`_)
- Recover 100% Type Completeness (`#3676`_)
- Documentation Improvements (`#3628`_, `#3636`_, `#3694`_)
Dependencies
------------
- Bump ``actions/stale`` from 7 to 8 (`#3644`_)
- Bump ``furo`` from 2023.3.23 to 2023.3.27 (`#3643`_)
- ``pre-commit`` autoupdate (`#3646`_, `#3688`_)
- Remove Deprecated ``codecov`` Package from CI (`#3664`_)
- Bump ``sphinx-copybutton`` from 0.5.1 to 0.5.2 (`#3662`_)
- Update ``httpx`` requirement from ~=0.23.3 to ~=0.24.0 (`#3660`_)
- Bump ``pytest`` from 7.2.2 to 7.3.1 (`#3661`_)
.. _`#3673`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3673
.. _`#3622`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3622
.. _`#3607`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3607
.. _`#3640`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3640
.. _`#3632`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3632
.. _`#3594`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3594
.. _`#3630`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3630
.. _`#3634`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3634
.. _`#3631`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3631
.. _`#3623`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3623
.. _`#3668`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3668
.. _`#3674`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3674
.. _`#3685`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3685
.. _`#3657`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3657
.. _`#3676`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3676
.. _`#3628`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3628
.. _`#3636`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3636
.. _`#3694`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3694
.. _`#3644`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3644
.. _`#3643`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3643
.. _`#3646`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3646
.. _`#3688`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3688
.. _`#3664`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3664
.. _`#3662`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3662
.. _`#3660`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3660
.. _`#3661`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3661
Version 20.2
============
*Released 2023-03-25*
This is the technical changelog for version 20.2. More elaborate release notes can be found in the news channel `@pythontelegrambotchannel <https://t.me/pythontelegrambotchannel>`_.
Major Changes
-------------
- Full Support for API 6.6 (`#3584`_)
- Revert to HTTP/1.1 as Default and make HTTP/2 an Optional Dependency (`#3576`_)
Minor Changes, Documentation Improvements and CI
------------------------------------------------
- Documentation Improvements (`#3565`_, `#3600`_)
- Handle Symbolic Links in ``was_called_by`` (`#3552`_)
- Tidy Up Tests Directory (`#3553`_)
- Enhance ``Application.create_task`` (`#3543`_)
- Make Type Completeness Workflow Usable for ``PRs`` from Forks (`#3551`_)
- Refactor and Overhaul the Test Suite (`#3426`_)
Dependencies
------------
- Bump ``pytest-asyncio`` from 0.20.3 to 0.21.0 (`#3624`_)
- Bump ``furo`` from 2022.12.7 to 2023.3.23 (`#3625`_)
- Bump ``pytest-xdist`` from 3.2.0 to 3.2.1 (`#3606`_)
- ``pre-commit`` autoupdate (`#3577`_)
- Update ``apscheduler`` requirement from ~=3.10.0 to ~=3.10.1 (`#3572`_)
- Bump ``pytest`` from 7.2.1 to 7.2.2 (`#3573`_)
- Bump ``pytest-xdist`` from 3.1.0 to 3.2.0 (`#3550`_)
- Bump ``sphinxcontrib-mermaid`` from 0.7.1 to 0.8 (`#3549`_)
.. _`#3584`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3584
.. _`#3576`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3576
.. _`#3565`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3565
.. _`#3600`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3600
.. _`#3552`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3552
.. _`#3553`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3553
.. _`#3543`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3543
.. _`#3551`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3551
.. _`#3426`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3426
.. _`#3624`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3624
.. _`#3625`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3625
.. _`#3606`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3606
.. _`#3577`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3577
.. _`#3572`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3572
.. _`#3573`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3573
.. _`#3550`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3550
.. _`#3549`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3549
Version 20.1
============
*Released 2023-02-09*
@@ -2108,7 +2231,7 @@ Changes
- Lots of small improvements to our tests and documentation.
.. _`see docs`: https://docs.python-telegram-bot.org/en/stable/telegram.ext.dispatcher.html#telegram.ext.Dispatcher.add_handler
.. _`see docs`: https://docs.python-telegram-bot.org/en/v13.11/telegram.ext.dispatcher.html?highlight=Dispatcher.add_handler#telegram.ext.Dispatcher.add_handler
.. _`#777`: https://github.com/python-telegram-bot/python-telegram-bot/pull/777
.. _`#806`: https://github.com/python-telegram-bot/python-telegram-bot/pull/806
.. _`#766`: https://github.com/python-telegram-bot/python-telegram-bot/pull/766
+8 -8
View File
@@ -14,7 +14,7 @@
:target: https://pypi.org/project/python-telegram-bot/
:alt: Supported Python versions
.. image:: https://img.shields.io/badge/Bot%20API-6.5-blue?logo=telegram
.. image:: https://img.shields.io/badge/Bot%20API-6.7-blue?logo=telegram
:target: https://core.telegram.org/bots/api-changelog
:alt: Supported Bot API versions
@@ -46,8 +46,8 @@
:target: https://app.codacy.com/gh/python-telegram-bot/python-telegram-bot/dashboard
: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
.. image:: https://app.deepsource.com/gh/python-telegram-bot/python-telegram-bot.svg/?label=active+issues
:target: https://app.deepsource.com/gh/python-telegram-bot/python-telegram-bot/?ref=repository-badge
:alt: Code quality: DeepSource
.. image:: https://results.pre-commit.ci/badge/github/python-telegram-bot/python-telegram-bot/master.svg
@@ -93,7 +93,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 **6.5** are supported.
All types and methods of the Telegram Bot API **6.7** are supported.
Installing
==========
@@ -135,9 +135,8 @@ As these features are *optional*, the corresponding 3rd party dependencies are n
Instead, they are listed as optional dependencies.
This allows to avoid unnecessary dependency conflicts for users who don't need the optional features.
The only required dependency is `httpx[http2] ~= 0.23.3 <https://www.python-httpx.org>`_ for
``telegram.request.HTTPXRequest``, the default networking backend. By default, HTTP/2 is used, as it
provides greater performance and stability, specially for concurrent requests.
The only required dependency is `httpx ~= 0.23.3 <https://www.python-httpx.org>`_ for
``telegram.request.HTTPXRequest``, the default networking backend.
``python-telegram-bot`` is most useful when used along with additional libraries.
To minimize dependency conflicts, we try to be liberal in terms of version requirements on the (optional) dependencies.
@@ -151,10 +150,11 @@ PTB can be installed with optional dependencies:
* ``pip install python-telegram-bot[passport]`` installs the `cryptography>=39.0.1 <https://cryptography.io/en/stable>`_ library. Use this, if you want to use Telegram Passport related functionality.
* ``pip install python-telegram-bot[socks]`` installs `httpx[socks] <https://www.python-httpx.org/#dependencies>`_. Use this, if you want to work behind a Socks5 server.
* ``pip install python-telegram-bot[http2]`` installs `httpx[http2] <https://www.python-httpx.org/#dependencies>`_. Use this, if you want to use HTTP/2.
* ``pip install python-telegram-bot[rate-limiter]`` installs `aiolimiter~=1.0.0 <https://aiolimiter.readthedocs.io/en/stable/>`_. Use this, if you want to use ``telegram.ext.AIORateLimiter``.
* ``pip install python-telegram-bot[webhooks]`` installs the `tornado~=6.2 <https://www.tornadoweb.org/en/stable/>`_ library. Use this, if you want to use ``telegram.ext.Updater.start_webhook``/``telegram.ext.Application.run_webhook``.
* ``pip install python-telegram-bot[callback-data]`` installs the `cachetools~=5.3.0 <https://cachetools.readthedocs.io/en/latest/>`_ library. Use this, if you want to use `arbitrary callback_data <https://github.com/python-telegram-bot/python-telegram-bot/wiki/Arbitrary-callback_data>`_.
* ``pip install python-telegram-bot[job-queue]`` installs the `APScheduler~=3.10.0 <https://apscheduler.readthedocs.io/en/3.x/>`_ library and enforces `pytz>=2018.6 <https://pypi.org/project/pytz/>`_, where ``pytz`` is a dependency of ``APScheduler``. Use this, if you want to use the ``telegram.ext.JobQueue``.
* ``pip install python-telegram-bot[job-queue]`` installs the `APScheduler~=3.10.1 <https://apscheduler.readthedocs.io/en/3.x/>`_ library and enforces `pytz>=2018.6 <https://pypi.org/project/pytz/>`_, where ``pytz`` is a dependency of ``APScheduler``. Use this, if you want to use the ``telegram.ext.JobQueue``.
To install multiple optional dependencies, separate them by commas, e.g. ``pip install python-telegram-bot[socks,webhooks]``.
+7 -7
View File
@@ -14,7 +14,7 @@
:target: https://pypi.org/project/python-telegram-bot-raw/
:alt: Supported Python versions
.. image:: https://img.shields.io/badge/Bot%20API-6.5-blue?logo=telegram
.. image:: https://img.shields.io/badge/Bot%20API-6.7-blue?logo=telegram
:target: https://core.telegram.org/bots/api-changelog
:alt: Supported Bot API versions
@@ -46,8 +46,8 @@
:target: https://app.codacy.com/gh/python-telegram-bot/python-telegram-bot/dashboard
: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
.. image:: https://app.deepsource.com/gh/python-telegram-bot/python-telegram-bot.svg/?label=active+issues
:target: https://app.deepsource.com/gh/python-telegram-bot/python-telegram-bot/?ref=repository-badge
:alt: Code quality: DeepSource
.. image:: https://results.pre-commit.ci/badge/github/python-telegram-bot/python-telegram-bot/master.svg
@@ -89,7 +89,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 **6.5** are supported.
All types and methods of the Telegram Bot API **6.7** are supported.
Installing
==========
@@ -136,9 +136,8 @@ As these features are *optional*, the corresponding 3rd party dependencies are n
Instead, they are listed as optional dependencies.
This allows to avoid unnecessary dependency conflicts for users who don't need the optional features.
The only required dependency is `httpx[http2] ~= 0.23.3 <https://www.python-httpx.org>`_ for
``telegram.request.HTTPXRequest``, the default networking backend. By default, HTTP/2 is used, as
it provides greater performance and stability, specially for concurrent requests.
The only required dependency is `httpx ~= 0.23.3 <https://www.python-httpx.org>`_ for
``telegram.request.HTTPXRequest``, the default networking backend.
``python-telegram-bot`` is most useful when used along with additional libraries.
To minimize dependency conflicts, we try to be liberal in terms of version requirements on the (optional) dependencies.
@@ -152,6 +151,7 @@ PTB can be installed with optional dependencies:
* ``pip install python-telegram-bot-raw[passport]`` installs the `cryptography>=39.0.1 <https://cryptography.io/en/stable>`_ library. Use this, if you want to use Telegram Passport related functionality.
* ``pip install python-telegram-bot-raw[socks]`` installs `httpx[socks] <https://www.python-httpx.org/#dependencies>`_. Use this, if you want to work behind a Socks5 server.
* ``pip install python-telegram-bot[http2]`` installs `httpx[http2] <https://www.python-httpx.org/#dependencies>`_. Use this, if you want to use HTTP/2.
To install multiple optional dependencies, separate them by commas, e.g. ``pip install python-telegram-bot-raw[passport,socks]``.
+3 -3
View File
@@ -1,7 +1,7 @@
sphinx==6.1.3
sphinx-pypi-upload
furo==2022.12.7
furo==2023.3.27
git+https://github.com/harshil21/furo-sphinx-search@01efc7be422d7dc02390aab9be68d6f5ce1a5618#egg=furo-sphinx-search
sphinx-paramlinks==0.5.4
sphinxcontrib-mermaid==0.7.1
sphinx-copybutton==0.5.1
sphinxcontrib-mermaid==0.8.1
sphinx-copybutton==0.5.2
+2 -2
View File
@@ -21,9 +21,9 @@ author = "Leandro Toledo"
# built documents.
#
# The short X.Y version.
version = "20.1" # telegram.__version__[:3]
version = "20.3" # telegram.__version__[:3]
# The full version, including alpha/beta/rc tags.
release = "20.1" # telegram.__version__
release = "20.3" # telegram.__version__
# If your documentation needs a minimal Sphinx version, state it here.
needs_sphinx = "6.1.3"
+1 -1
View File
@@ -8,7 +8,7 @@ aspect of the Telegram Bot API while others focus on one of the
mechanics of this library. Except for the
:any:`examples.rawapibot` example, they all use the high-level
framework this library provides with the
:any:`telegram.ext <telegram.ext>` submodule.
:mod:`telegram.ext` submodule.
All examples are licensed under the `CC0
License <https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/LICENSE.txt>`__
+43 -4
View File
@@ -157,6 +157,23 @@
- Used for getting the number of members in a chat
* - :meth:`~telegram.Bot.get_chat_member`
- Used for getting a member of a chat
* - :meth:`~telegram.Bot.leave_chat`
- Used for leaving a chat
.. raw:: html
</details>
<br>
.. raw:: html
<details>
<summary>Bot settings</summary>
.. list-table::
:align: left
:widths: 1 4
* - :meth:`~telegram.Bot.set_my_commands`
- Used for setting the list of commands
* - :meth:`~telegram.Bot.delete_my_commands`
@@ -171,8 +188,18 @@
- Used for obtaining the menu button of a private chat or the default menu button
* - :meth:`~telegram.Bot.set_chat_menu_button`
- Used for setting the menu button of a private chat or the default menu button
* - :meth:`~telegram.Bot.leave_chat`
- Used for leaving a chat
* - :meth:`~telegram.Bot.set_my_description`
- Used for setting the description of the bot
* - :meth:`~telegram.Bot.get_my_description`
- Used for obtaining the description of the bot
* - :meth:`~telegram.Bot.set_my_short_description`
- Used for setting the short description of the bot
* - :meth:`~telegram.Bot.get_my_short_description`
- Used for obtaining the short description of the bot
* - :meth:`~telegram.Bot.set_my_name`
- Used for setting the name of the bot
* - :meth:`~telegram.Bot.get_my_name`
- Used for obtaining the name of the bot
.. raw:: html
@@ -194,14 +221,26 @@
- Used for deleting a sticker from a set
* - :meth:`~telegram.Bot.create_new_sticker_set`
- Used for creating a new sticker set
* - :meth:`~telegram.Bot.delete_sticker_set`
- Used for deleting a sticker set made by a bot
* - :meth:`~telegram.Bot.set_chat_sticker_set`
- Used for setting a sticker set
- Used for setting a sticker set of a chat
* - :meth:`~telegram.Bot.delete_chat_sticker_set`
- Used for deleting the set sticker set
- Used for deleting the set sticker set of a chat
* - :meth:`~telegram.Bot.set_sticker_position_in_set`
- Used for moving a sticker's position in the set
* - :meth:`~telegram.Bot.set_sticker_set_title`
- Used for setting the title of a sticker set
* - :meth:`~telegram.Bot.set_sticker_emoji_list`
- Used for setting the emoji list of a sticker
* - :meth:`~telegram.Bot.set_sticker_keywords`
- Used for setting the keywords of a sticker
* - :meth:`~telegram.Bot.set_sticker_mask_position`
- Used for setting the mask position of a mask sticker
* - :meth:`~telegram.Bot.set_sticker_set_thumb`
- Used for setting the thumbnail of a sticker set
* - :meth:`~telegram.Bot.set_custom_emoji_sticker_set_thumbnail`
- Used for setting the thumbnail of a custom emoji sticker set
* - :meth:`~telegram.Bot.get_sticker_set`
- Used for getting a sticker set
* - :meth:`~telegram.Bot.upload_sticker_file`
+4 -4
View File
@@ -27,12 +27,12 @@
:hidden:
:caption: Project
stability_policy
changelog
coc
contributing
testing
Website <https://python-telegram-bot.org>
GitHub Repository <https://github.com/python-telegram-bot/python-telegram-bot/>
Telegram Channel <https://t.me/pythontelegrambotchannel/>
Telegram User Group <https://t.me/pythontelegrambotgroup/>
contributing
coc
+143
View File
@@ -0,0 +1,143 @@
Stability Policy
================
.. important::
This stability policy is in place since version 20.3.
While earlier versions of ``python-telegram-bot`` also had stable interfaces, they had no explicit stability policy and hence did not follow the rules outlined below in all detail.
Please also refer to the :ref:`changelog <ptb-changelog>`.
.. caution::
Large parts of the :mod:`telegram` package are the Python representations of the Telegram Bot API, whose stability policy PTB can not influence.
This policy hence includes some special cases for those parts.
What does this policy cover?
----------------------------
This policy includes any API or behavior that is covered in this documentation.
This covers both the :mod:`telegram` package and the :mod:`telegram.ext` package.
What doesn't this policy cover?
-------------------------------
Introduction of new features or changes of flavors of comparable behavior (e.g. the default for the HTTP protocol version being used) are not covered by this policy.
The internal structure of classes in PTB, i.e. things like the result of ``dir(obj))`` or the contents of ``obj.__dict__``, is not covered by this policy.
Objects are in general not guaranteed to be pickleable (unless stated otherwise) and pickled objects from one version of PTB may not be loadable in future versions.
We may provide a way to convert pickled objects from one version to another, but this is not guaranteed.
Functionality that is part of PTBs API but is explicitly documented as not being intended to be used directly by users (e.g. :meth:`telegram.request.BaseRequest.do_request`) may change.
This also applies to functions or attributes marked as final in the sense of `PEP 591 <https://www.python.org/dev/peps/pep-0591/>`__.
PTB has dependencies to third-party packages.
The versions that PTB uses of these third-party packages may change if that does not affect PTBs public API.
PTB does not give guarantees about which Python versions are supported.
In general, we will try to support all Python versions that have not yet reached their end of life, but we reserve ourselves the option to drop support for Python versions earlier if that benefits the advancement of the library.
.. _bot-api-functionality-1:
Bot API Functionality
~~~~~~~~~~~~~~~~~~~~~
Comparison of equality of instances of the classes in the :mod:`telegram` package is subject to change and the PTB team will update the behavior to best reflect updates in the Bot API.
Changes in this regard will be documented in the affected classes.
Note that equality comparison with objects that where serialized by an older version of PTB may hence give unexpected results.
When the order of arguments of the Bot API methods changes or they become optional/mandatory due to changes in the Bot API, PTB will always try to reflect these changes.
While we try to make such changes backward compatible, this is not always possible or only with significant effort.
In such cases we will find a trade-off between backward compatibility and fully complying with the Bot API, which may result in breaking changes.
We highly recommend using keyword arguments, which can help make such changes non-breaking on your end.
..
We have documented a few common cases and possible backwards compatible solutions in the wiki as a reference for the dev team: https://github.com/python-telegram-bot/python-telegram-bot/wiki/Bot-API-Backward-Compatibility
When the Bot API changes attributes of classes, the method :meth:`telegram.TelegramObject.to_dict` will change as necessary to reflect these changes.
In particular, attributes deprecated by Telegram will be removed from the returned dictionary.
Deprecated attributes that are still passed by Telegram will be available in the :attr:`~telegram.TelegramObject.api_kwargs` dictionary as long as PTB can support that with feasible effort.
Since attributes of the classes in the :mod:`telegram` package are not writable, we may change them to properties where appropriate.
Development Versions
~~~~~~~~~~~~~~~~~~~~
Pre-releases marked as alpha, beta or release candidate are not covered by this policy.
Before a feature is in a stable release, i.e. the feature was merged into the ``master`` branch but not released yet (or only in a pre-release), it is not covered by this policy either and may change.
Security
~~~~~~~~
We make exceptions from our stability policy for security.
We will violate this policy as necessary in order to resolve a security issue or harden PTB against a possible attack.
Versioning
----------
PTB uses a versioning scheme that roughly follows `https://semver.org/ <https://semver.org/>`_, although it may not be quite as strict.
Given a version of PTB X.Y.Z,
- X indicates the major version number.
This is incremented when backward incompatible changes are introduced.
- Y indicates the minor version number.
This is incremented when new functionality or backward compatible changes are introduced by PTB.
*This is also incremented when PTB adds support for a new Bot API version, which may include backward incompatible changes in some cases as outlined* :ref:`below <bot-api-versioning>`.
- Z is the patch version.
This is incremented if backward compatible bug fixes or smaller changes are introduced.
If this number is 0, it can be omitted, i.e. we just write X.Y instead of X.Y.0.
Deprecation
~~~~~~~~~~~
From time to time we will want to change the behavior of an API or remove it entirely, or we do so to comply with changes in the Telegram Bot API.
In those cases, we follow a deprecation schedule as detailed below.
Functionality is marked as deprecated by a corresponding note in the release notes and the documentation.
Where possible, a :class:`~telegram.warnings.PTBDeprecationWarning` is issued when deprecated functionality is used, but this is not mandatory.
From time to time, we may decide to deprecate an API that is particularly widely used.
In these cases, we may decide to provide an extended deprecation period, at our discretion.
With version 20.0.0, PTB introduced major structural breaking changes without the above deprecation period.
Should a similarly big change ever be deemed necessary again by the development team and should a deprecation period prove too much additional effort, this violation of the stability policy will be announced well ahead of the release in our channel, `as was done for v20 <https://t.me/pythontelegrambotchannel/94>`_.
Non-Bot API Functionality
#########################
Starting with version 20.3, deprecated functionality will stay available for the current and the next major version.
For example:
- In PTB v20.1.1 the feature exists
- In PTB v20.1.2 or v20.2.0 the feature is marked as deprecated
- In PTB v21.*.* the feature is marked as deprecated
- In PTB v22.0 the feature is removed or changed
.. _bot-api-versioning:
Bot API Functionality
#####################
As PTB has no control over deprecations introduced by Telegram and the schedule of these deprecations rarely coincides with PTBs deprecation schedule, we have a special policy for Bot API functionality.
Starting with 20.3, deprecated Bot API functionality will stay available for the current and the next major version of PTB *or* until the next version of the Bot API.
More precisely, two cases are possible, for which we show examples below.
Case 1
^^^^^^
- In PTB v20.1 the feature exists
- Bot API version 6.6 is released and deprecates the feature
- PTB v20.2 adds support for Bot API 6.6 and the feature is
marked as deprecated
- In PTB v21.0 the feature is removed or changed
Case 2
^^^^^^
- In PTB v20.1 the feature exists
- Bot API version 6.6 is released and deprecates the feature
- PTB v20.2 adds support for Bot API version 6.6 and the feature is marked as deprecated
- In PTB v20.2.* and v20.3.* the feature is marked as deprecated
- Bot API version 6.7 is released
- PTB v20.4 adds support for Bot API version 6.7 and the feature is removed or changed
+5
View File
@@ -15,6 +15,9 @@ Available Types
telegram.botcommandscopechatadministrators
telegram.botcommandscopechatmember
telegram.botcommandscopedefault
telegram.botdescription
telegram.botname
telegram.botshortdescription
telegram.callbackquery
telegram.chat
telegram.chatadministratorrights
@@ -53,6 +56,7 @@ Available Types
telegram.inputmediadocument
telegram.inputmediaphoto
telegram.inputmediavideo
telegram.inputsticker
telegram.keyboardbutton
telegram.keyboardbuttonpolltype
telegram.keyboardbuttonrequestchat
@@ -75,6 +79,7 @@ Available Types
telegram.replykeyboardmarkup
telegram.replykeyboardremove
telegram.sentwebappmessage
telegram.switchinlinequerychosenchat
telegram.telegramobject
telegram.update
telegram.user
+6
View File
@@ -0,0 +1,6 @@
BotDescription
==============
.. autoclass:: telegram.BotDescription
:members:
:show-inheritance:
+6
View File
@@ -0,0 +1,6 @@
BotName
=======
.. autoclass:: telegram.BotName
:members:
:show-inheritance:
@@ -0,0 +1,6 @@
BotShortDescription
===================
.. autoclass:: telegram.BotShortDescription
:members:
:show-inheritance:
+2
View File
@@ -1,6 +1,8 @@
telegram.ext package
====================
.. automodule:: telegram.ext
.. toctree::
:titlesonly:
+1
View File
@@ -24,6 +24,7 @@ Inline Mode
telegram.inlinequeryresultlocation
telegram.inlinequeryresultmpeg4gif
telegram.inlinequeryresultphoto
telegram.inlinequeryresultsbutton
telegram.inlinequeryresultvenue
telegram.inlinequeryresultvideo
telegram.inlinequeryresultvoice
@@ -0,0 +1,6 @@
InlineQueryResultsButton
========================
.. autoclass:: telegram.InlineQueryResultsButton
:members:
:show-inheritance:
+6
View File
@@ -0,0 +1,6 @@
InputSticker
============
.. autoclass:: telegram.InputSticker
:members:
:show-inheritance:
@@ -0,0 +1,6 @@
SwitchInlineQueryChosenChat
===========================
.. autoclass:: telegram.SwitchInlineQueryChosenChat
:members:
:show-inheritance:
+1
View File
@@ -0,0 +1 @@
.. include:: ../../tests/README.rst
+6
View File
@@ -14,6 +14,10 @@
.. |thumbdocstringnopath| replace:: |thumbdocstringbase| |uploadinputnopath|
.. |thumbargumentdeprecation| replace:: As of Bot API 6.6 this argument is deprecated in favor of
.. |thumbattributedeprecation| replace:: As of Bot API 6.6 this attribute is deprecated in favor of
.. |editreplymarkup| replace:: It is currently only possible to edit messages without :attr:`telegram.Message.reply_markup` or with inline keyboards.
.. |toapikwargsbase| replace:: These arguments are also considered by :meth:`~telegram.TelegramObject.to_dict` and :meth:`~telegram.TelegramObject.to_json`, i.e. when passing objects to Telegram. Passing them to Telegram is however not guaranteed to work for all kinds of objects, e.g. this will fail for objects that can not directly be JSON serialized.
@@ -51,3 +55,5 @@
.. |sequenceargs| replace:: Accepts any :class:`collections.abc.Sequence` as input instead of just a list.
.. |captionentitiesattr| replace:: Tuple of special entities that appear in the caption, which can be specified instead of ``parse_mode``.
.. |datetime_localization| replace:: The default timezone of the bot is used for localization, which is UTC unless :attr:`telegram.ext.Defaults.tzinfo` is used.
+6 -7
View File
@@ -103,13 +103,12 @@ async def track_chats(update: Update, context: ContextTypes.DEFAULT_TYPE) -> Non
elif was_member and not is_member:
logger.info("%s removed the bot from the group %s", cause_name, chat.title)
context.bot_data.setdefault("group_ids", set()).discard(chat.id)
else:
if not was_member and is_member:
logger.info("%s added the bot to the channel %s", cause_name, chat.title)
context.bot_data.setdefault("channel_ids", set()).add(chat.id)
elif was_member and not is_member:
logger.info("%s removed the bot from the channel %s", cause_name, chat.title)
context.bot_data.setdefault("channel_ids", set()).discard(chat.id)
elif not was_member and is_member:
logger.info("%s added the bot to the channel %s", cause_name, chat.title)
context.bot_data.setdefault("channel_ids", set()).add(chat.id)
elif was_member and not is_member:
logger.info("%s removed the bot from the channel %s", cause_name, chat.title)
context.bot_data.setdefault("channel_ids", set()).discard(chat.id)
async def show_chats(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
+1 -1
View File
@@ -39,7 +39,7 @@ DEVELOPER_CHAT_ID = 123456789
async def error_handler(update: object, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Log the error and send a telegram message to notify the developer."""
# Log the error before we do anything else, so we can see it even if something breaks.
logger.error(msg="Exception while handling an update:", exc_info=context.error)
logger.error("Exception while handling an update:", exc_info=context.error)
# traceback.format_exception returns the usual python message about an exception, but as a
# list of strings rather than a single string, so we have to join them together.
+1 -1
View File
@@ -58,7 +58,7 @@ async def inline_query(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No
"""Handle the inline query. This is run when you type: @botusername <query>"""
query = update.inline_query.query
if query == "":
if not query: # empty query should not be handled
return
results = [
+5 -2
View File
@@ -48,6 +48,9 @@ logging.basicConfig(
logger = logging.getLogger(__name__)
TOTAL_VOTER_COUNT = 3
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Inform user about what this bot can do"""
await update.message.reply_text(
@@ -101,7 +104,7 @@ async def receive_poll_answer(update: Update, context: ContextTypes.DEFAULT_TYPE
)
answered_poll["answers"] += 1
# Close poll after three participants voted
if answered_poll["answers"] == 3:
if answered_poll["answers"] == TOTAL_VOTER_COUNT:
await context.bot.stop_poll(answered_poll["chat_id"], answered_poll["message_id"])
@@ -123,7 +126,7 @@ async def receive_quiz_answer(update: Update, context: ContextTypes.DEFAULT_TYPE
# the bot can receive closed poll updates we don't care about
if update.poll.is_closed:
return
if update.poll.total_voter_count == 3:
if update.poll.total_voter_count == TOTAL_VOTER_COUNT:
try:
quiz_data = context.bot_data[update.poll.id]
# this means this poll answer update is from an old poll, we can't stop it then
+2 -3
View File
@@ -7,6 +7,7 @@ on the telegram.ext bot framework.
This program is dedicated to the public domain under the CC0 license.
"""
import asyncio
import contextlib
import logging
from typing import NoReturn
@@ -72,7 +73,5 @@ async def echo(bot: Bot, update_id: int) -> int:
if __name__ == "__main__":
try:
with contextlib.suppress(KeyboardInterrupt): # Ignore exception when Ctrl-C is pressed
asyncio.run(main())
except KeyboardInterrupt: # Ignore exception when Ctrl-C is pressed
pass
+2 -2
View File
@@ -18,8 +18,8 @@ Press Ctrl-C on the command line or send a signal to the process to stop the
bot.
Note:
To use arbitrary callback data, you must install ptb via
`pip install python-telegram-bot[callback-data]`
To use the JobQueue, you must install PTB via
`pip install python-telegram-bot[job-queue]`
"""
import logging
+7
View File
@@ -9,3 +9,10 @@ line_length = 99
[tool.ruff]
line-length = 99
target-version = "py37"
show-fixes = true
ignore = ["PLR2004", "PLR0911", "PLR0912", "PLR0913", "PLR0915"]
select = ["E", "F", "I", "PL", "UP", "RUF", "PTH", "C4", "B", "PIE", "SIM", "RET", "RSE",
"G", "ISC", "PT"]
[tool.ruff.per-file-ignores]
"tests/*.py" = ["B018"]
+6 -6
View File
@@ -1,10 +1,10 @@
pre-commit
pytest==7.2.1
pytest-asyncio==0.20.3
pytest-timeout==2.1.0 # used to timeout tests
pytest-xdist==3.1.0 # xdist runs tests in parallel
pre-commit # needed for pre-commit hooks in the git commit command
# For the test suite
pytest==7.3.1
pytest-asyncio==0.21.0 # needed because pytest doesn't come with native support for coroutines as tests
pytest-xdist==3.2.1 # xdist runs tests in parallel
flaky # Used for flaky tests (flaky decorator)
beautifulsoup4 # used in test_official for parsing tg docs
wheel # required for building the wheels for releases
+2 -1
View File
@@ -11,6 +11,7 @@
# versions and only increase the lower bound if necessary
httpx[socks] # socks
httpx[http2] # http2
cryptography!=3.4,!=3.4.1,!=3.4.2,!=3.4.3,>=39.0.1 # passport
aiolimiter~=1.0.0 # rate-limiter!ext
@@ -20,7 +21,7 @@ tornado~=6.2 # webhooks!ext
# Cachetools and APS don't have a strict stability policy.
# Let's be cautious for now.
cachetools~=5.3.0 # callback-data!ext
APScheduler~=3.10.0 # job-queue!ext
APScheduler~=3.10.1 # job-queue!ext
# pytz is required by APS and just needs the lower bound due to #2120
pytz>=2018.6 # job-queue!ext
+1 -2
View File
@@ -6,5 +6,4 @@
# versions and only increase the lower bound if necessary
# httpx has no stable release yet, so let's be cautious for now
# HTTP/2 is more performant and stable than HTTP/1.1, specially for concurrent requests
httpx[http2] ~= 0.23.3
httpx ~= 0.24.0
+4 -1
View File
@@ -36,7 +36,10 @@ filterwarnings =
; Unfortunately due to https://github.com/pytest-dev/pytest/issues/8343 we can't have this here
; and instead do a trick directly in tests/conftest.py
; ignore::telegram.utils.deprecate.TelegramDeprecationWarning
markers = dev: If you want to test a specific test, use this
markers =
dev: If you want to test a specific test, use this
no_req
req
asyncio_mode = auto
[coverage:run]
+11
View File
@@ -37,6 +37,9 @@ __all__ = ( # Keep this alphabetically ordered
"BotCommandScopeChatAdministrators",
"BotCommandScopeChatMember",
"BotCommandScopeDefault",
"BotDescription",
"BotName",
"BotShortDescription",
"CallbackGame",
"CallbackQuery",
"Chat",
@@ -100,6 +103,7 @@ __all__ = ( # Keep this alphabetically ordered
"InlineQueryResultLocation",
"InlineQueryResultMpeg4Gif",
"InlineQueryResultPhoto",
"InlineQueryResultsButton",
"InlineQueryResultVenue",
"InlineQueryResultVideo",
"InlineQueryResultVoice",
@@ -114,6 +118,7 @@ __all__ = ( # Keep this alphabetically ordered
"InputMediaPhoto",
"InputMediaVideo",
"InputMessageContent",
"InputSticker",
"InputTextMessageContent",
"InputVenueMessageContent",
"Invoice",
@@ -166,6 +171,7 @@ __all__ = ( # Keep this alphabetically ordered
"Sticker",
"StickerSet",
"SuccessfulPayment",
"SwitchInlineQueryChosenChat",
"TelegramObject",
"Update",
"User",
@@ -200,6 +206,8 @@ from ._botcommandscope import (
BotCommandScopeChatMember,
BotCommandScopeDefault,
)
from ._botdescription import BotDescription, BotShortDescription
from ._botname import BotName
from ._callbackquery import CallbackQuery
from ._chat import Chat
from ._chatadministratorrights import ChatAdministratorRights
@@ -234,6 +242,7 @@ from ._files.inputmedia import (
InputMediaPhoto,
InputMediaVideo,
)
from ._files.inputsticker import InputSticker
from ._files.location import Location
from ._files.photosize import PhotoSize
from ._files.sticker import MaskPosition, Sticker, StickerSet
@@ -275,6 +284,7 @@ from ._inline.inlinequeryresultgif import InlineQueryResultGif
from ._inline.inlinequeryresultlocation import InlineQueryResultLocation
from ._inline.inlinequeryresultmpeg4gif import InlineQueryResultMpeg4Gif
from ._inline.inlinequeryresultphoto import InlineQueryResultPhoto
from ._inline.inlinequeryresultsbutton import InlineQueryResultsButton
from ._inline.inlinequeryresultvenue import InlineQueryResultVenue
from ._inline.inlinequeryresultvideo import InlineQueryResultVideo
from ._inline.inlinequeryresultvoice import InlineQueryResultVoice
@@ -331,6 +341,7 @@ from ._replykeyboardmarkup import ReplyKeyboardMarkup
from ._replykeyboardremove import ReplyKeyboardRemove
from ._sentwebappmessage import SentWebAppMessage
from ._shared import ChatShared, UserShared
from ._switchinlinequerychosenchat import SwitchInlineQueryChosenChat
from ._telegramobject import TelegramObject
from ._update import Update
from ._user import User
+1155 -192
View File
File diff suppressed because it is too large Load Diff
+75
View File
@@ -0,0 +1,75 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2023
# 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 two objects that represent a Telegram bots (short) description."""
from telegram._telegramobject import TelegramObject
from telegram._utils.types import JSONDict
class BotDescription(TelegramObject):
"""This object represents the bot's description.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`description` is equal.
.. versionadded:: 20.2
Args:
description (:obj:`str`): The bot's description.
Attributes:
description (:obj:`str`): The bot's description.
"""
__slots__ = ("description",)
def __init__(self, description: str, *, api_kwargs: JSONDict = None):
super().__init__(api_kwargs=api_kwargs)
self.description: str = description
self._id_attrs = (self.description,)
self._freeze()
class BotShortDescription(TelegramObject):
"""This object represents the bot's short description.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`short_description` is equal.
.. versionadded:: 20.2
Args:
short_description (:obj:`str`): The bot's short description.
Attributes:
short_description (:obj:`str`): The bot's short description.
"""
__slots__ = ("short_description",)
def __init__(self, short_description: str, *, api_kwargs: JSONDict = None):
super().__init__(api_kwargs=api_kwargs)
self.short_description: str = short_description
self._id_attrs = (self.short_description,)
self._freeze()
+54
View File
@@ -0,0 +1,54 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2023
# 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 represent a Telegram bots name."""
from typing import ClassVar
from telegram import constants
from telegram._telegramobject import TelegramObject
from telegram._utils.types import JSONDict
class BotName(TelegramObject):
"""This object represents the bot's name.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`name` is equal.
.. versionadded:: 20.3
Args:
name (:obj:`str`): The bot's name.
Attributes:
name (:obj:`str`): The bot's name.
"""
__slots__ = ("name",)
def __init__(self, name: str, *, api_kwargs: JSONDict = None):
super().__init__(api_kwargs=api_kwargs)
self.name: str = name
self._id_attrs = (self.name,)
self._freeze()
MAX_LENGTH: ClassVar[int] = constants.BotNameLimit.MAX_NAME_LENGTH
""":const:`telegram.constants.BotNameLimit.MAX_NAME_LENGTH`"""
+12
View File
@@ -1532,6 +1532,7 @@ class Chat(TelegramObject):
caption_entities: Sequence["MessageEntity"] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
thumbnail: FileInput = None,
*,
filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1572,6 +1573,7 @@ class Chat(TelegramObject):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
thumbnail=thumbnail,
)
async def send_document(
@@ -1588,6 +1590,7 @@ class Chat(TelegramObject):
caption_entities: Sequence["MessageEntity"] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
thumbnail: FileInput = None,
*,
filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1620,6 +1623,7 @@ class Chat(TelegramObject):
pool_timeout=pool_timeout,
parse_mode=parse_mode,
thumb=thumb,
thumbnail=thumbnail,
api_kwargs=api_kwargs,
disable_content_type_detection=disable_content_type_detection,
allow_sending_without_reply=allow_sending_without_reply,
@@ -1875,6 +1879,7 @@ class Chat(TelegramObject):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
has_spoiler: bool = None,
thumbnail: FileInput = None,
*,
filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1916,6 +1921,7 @@ class Chat(TelegramObject):
protect_content=protect_content,
message_thread_id=message_thread_id,
has_spoiler=has_spoiler,
thumbnail=thumbnail,
)
async def send_sticker(
@@ -1927,6 +1933,7 @@ class Chat(TelegramObject):
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
emoji: str = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
@@ -1958,6 +1965,7 @@ class Chat(TelegramObject):
allow_sending_without_reply=allow_sending_without_reply,
protect_content=protect_content,
message_thread_id=message_thread_id,
emoji=emoji,
)
async def send_venue(
@@ -2036,6 +2044,7 @@ class Chat(TelegramObject):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
has_spoiler: bool = None,
thumbnail: FileInput = None,
*,
filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2071,6 +2080,7 @@ class Chat(TelegramObject):
parse_mode=parse_mode,
supports_streaming=supports_streaming,
thumb=thumb,
thumbnail=thumbnail,
api_kwargs=api_kwargs,
allow_sending_without_reply=allow_sending_without_reply,
caption_entities=caption_entities,
@@ -2092,6 +2102,7 @@ class Chat(TelegramObject):
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
thumbnail: FileInput = None,
*,
filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2123,6 +2134,7 @@ class Chat(TelegramObject):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
thumb=thumb,
thumbnail=thumbnail,
api_kwargs=api_kwargs,
allow_sending_without_reply=allow_sending_without_reply,
filename=filename,
+11 -2
View File
@@ -22,7 +22,7 @@ from typing import TYPE_CHECKING, Optional
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils.datetime import from_timestamp
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
@@ -54,6 +54,9 @@ class ChatInviteLink(TelegramObject):
is_revoked (:obj:`bool`): :obj:`True`, if the link is revoked.
expire_date (:class:`datetime.datetime`, optional): Date when the link will expire or
has been expired.
.. versionchanged:: 20.3
|datetime_localization|
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;
:tg-const:`telegram.constants.ChatInviteLinkLimit.MIN_MEMBER_LIMIT`-
@@ -78,6 +81,9 @@ class ChatInviteLink(TelegramObject):
is_revoked (:obj:`bool`): :obj:`True`, if the link is revoked.
expire_date (:class:`datetime.datetime`): Optional. Date when the link will expire or
has been expired.
.. versionchanged:: 20.3
|datetime_localization|
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;
:tg-const:`telegram.constants.ChatInviteLinkLimit.MIN_MEMBER_LIMIT`-
@@ -152,7 +158,10 @@ class ChatInviteLink(TelegramObject):
if not data:
return None
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["creator"] = User.de_json(data.get("creator"), bot)
data["expire_date"] = from_timestamp(data.get("expire_date", None))
data["expire_date"] = from_timestamp(data.get("expire_date", None), tzinfo=loc_tzinfo)
return super().de_json(data=data, bot=bot)
+12 -3
View File
@@ -24,7 +24,7 @@ from telegram._chat import Chat
from telegram._chatinvitelink import ChatInviteLink
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils.datetime import from_timestamp
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram._utils.types import JSONDict, ODVInput
@@ -41,7 +41,7 @@ class ChatJoinRequest(TelegramObject):
Note:
* Since Bot API 5.5, bots are allowed to contact users who sent a join request to a chat
where the bot is an administrator with the
:attr:`~telegram.ChatMemberAdministrator.can_invite_users` administrator right even
:attr:`~telegram.ChatMemberAdministrator.can_invite_users` administrator right - even
if the user never interacted with the bot before.
* Telegram does not guarantee that :attr:`from_user.id <from_user>` coincides with the
``chat_id`` of the user. Please use :attr:`user_chat_id` to contact the user in
@@ -56,6 +56,9 @@ class ChatJoinRequest(TelegramObject):
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.
.. versionchanged:: 20.3
|datetime_localization|
user_chat_id (:obj:`int`): Identifier of a private chat with the user who sent the join
request. This number may have more than 32 significant bits and some programming
languages may have difficulty/silent defects in interpreting it. But it has at most 52
@@ -73,6 +76,9 @@ class ChatJoinRequest(TelegramObject):
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.
.. versionchanged:: 20.3
|datetime_localization|
user_chat_id (:obj:`int`): Identifier of a private chat with the user who sent the join
request. This number may have more than 32 significant bits and some programming
languages may have difficulty/silent defects in interpreting it. But it has at most 52
@@ -124,9 +130,12 @@ class ChatJoinRequest(TelegramObject):
if not data:
return None
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["chat"] = Chat.de_json(data.get("chat"), bot)
data["from_user"] = User.de_json(data.pop("from", None), bot)
data["date"] = from_timestamp(data.get("date", None))
data["date"] = from_timestamp(data.get("date", None), tzinfo=loc_tzinfo)
data["invite_link"] = ChatInviteLink.de_json(data.get("invite_link"), bot)
return super().de_json(data=data, bot=bot)
+17 -2
View File
@@ -23,7 +23,7 @@ from typing import TYPE_CHECKING, ClassVar, Dict, Optional, Type
from telegram import constants
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils.datetime import from_timestamp
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
@@ -125,7 +125,10 @@ class ChatMember(TelegramObject):
data["user"] = User.de_json(data.get("user"), bot)
if "until_date" in data:
data["until_date"] = from_timestamp(data["until_date"])
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["until_date"] = from_timestamp(data["until_date"], tzinfo=loc_tzinfo)
return super().de_json(data=data, bot=bot)
@@ -386,6 +389,9 @@ class ChatMemberRestricted(ChatMember):
.. versionadded:: 20.0
until_date (:class:`datetime.datetime`): Date when restrictions
will be lifted for this user.
.. versionchanged:: 20.3
|datetime_localization|
can_send_audios (:obj:`bool`): :obj:`True`, if the user is allowed to send audios.
.. versionadded:: 20.1
@@ -438,6 +444,9 @@ class ChatMemberRestricted(ChatMember):
.. versionadded:: 20.0
until_date (:class:`datetime.datetime`): Date when restrictions
will be lifted for this user.
.. versionchanged:: 20.3
|datetime_localization|
can_send_audios (:obj:`bool`): :obj:`True`, if the user is allowed to send audios.
.. versionadded:: 20.1
@@ -565,6 +574,9 @@ class ChatMemberBanned(ChatMember):
until_date (:class:`datetime.datetime`): Date when restrictions
will be lifted for this user.
.. versionchanged:: 20.3
|datetime_localization|
Attributes:
status (:obj:`str`): The member's status in the chat,
always :tg-const:`telegram.ChatMember.BANNED`.
@@ -572,6 +584,9 @@ class ChatMemberBanned(ChatMember):
until_date (:class:`datetime.datetime`): Date when restrictions
will be lifted for this user.
.. versionchanged:: 20.3
|datetime_localization|
"""
__slots__ = ("until_date",)
+22 -2
View File
@@ -25,7 +25,7 @@ from telegram._chatinvitelink import ChatInviteLink
from telegram._chatmember import ChatMember
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils.datetime import from_timestamp
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
@@ -52,20 +52,34 @@ class ChatMemberUpdated(TelegramObject):
from_user (:class:`telegram.User`): Performer of the action, which resulted in the change.
date (:class:`datetime.datetime`): Date the change was done in Unix time. Converted to
:class:`datetime.datetime`.
.. versionchanged:: 20.3
|datetime_localization|
old_chat_member (:class:`telegram.ChatMember`): Previous information about the chat member.
new_chat_member (:class:`telegram.ChatMember`): New information about the chat member.
invite_link (:class:`telegram.ChatInviteLink`, optional): Chat invite link, which was used
by the user to join the chat. For joining by invite link events only.
via_chat_folder_invite_link (:obj:`bool`, optional): :obj:`True`, if the user joined the
chat via a chat folder invite link
.. versionadded:: 20.3
Attributes:
chat (:class:`telegram.Chat`): Chat the user belongs to.
from_user (:class:`telegram.User`): Performer of the action, which resulted in the change.
date (:class:`datetime.datetime`): Date the change was done in Unix time. Converted to
:class:`datetime.datetime`.
.. versionchanged:: 20.3
|datetime_localization|
old_chat_member (:class:`telegram.ChatMember`): Previous information about the chat member.
new_chat_member (:class:`telegram.ChatMember`): New information about the chat member.
invite_link (:class:`telegram.ChatInviteLink`): Optional. Chat invite link, which was used
by the user to join the chat. For joining by invite link events only.
via_chat_folder_invite_link (:obj:`bool`): Optional. :obj:`True`, if the user joined the
chat via a chat folder invite link
.. versionadded:: 20.3
"""
@@ -76,6 +90,7 @@ class ChatMemberUpdated(TelegramObject):
"old_chat_member",
"new_chat_member",
"invite_link",
"via_chat_folder_invite_link",
)
def __init__(
@@ -86,6 +101,7 @@ class ChatMemberUpdated(TelegramObject):
old_chat_member: ChatMember,
new_chat_member: ChatMember,
invite_link: ChatInviteLink = None,
via_chat_folder_invite_link: bool = None,
*,
api_kwargs: JSONDict = None,
):
@@ -96,6 +112,7 @@ class ChatMemberUpdated(TelegramObject):
self.date: datetime.datetime = date
self.old_chat_member: ChatMember = old_chat_member
self.new_chat_member: ChatMember = new_chat_member
self.via_chat_folder_invite_link: Optional[bool] = via_chat_folder_invite_link
# Optionals
self.invite_link: Optional[ChatInviteLink] = invite_link
@@ -118,9 +135,12 @@ class ChatMemberUpdated(TelegramObject):
if not data:
return None
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["chat"] = Chat.de_json(data.get("chat"), bot)
data["from_user"] = User.de_json(data.pop("from", None), bot)
data["date"] = from_timestamp(data.get("date"))
data["date"] = from_timestamp(data.get("date"), tzinfo=loc_tzinfo)
data["old_chat_member"] = ChatMember.de_json(data.get("old_chat_member"), bot)
data["new_chat_member"] = ChatMember.de_json(data.get("new_chat_member"), bot)
data["invite_link"] = ChatInviteLink.de_json(data.get("invite_link"), bot)
+44 -6
View File
@@ -22,6 +22,10 @@ from typing import TYPE_CHECKING, Optional, Type, TypeVar
from telegram._files._basemedium import _BaseMedium
from telegram._files.photosize import PhotoSize
from telegram._utils.types import JSONDict
from telegram._utils.warnings_transition import (
warn_about_deprecated_arg_return_new_arg,
warn_about_deprecated_attr_in_property,
)
if TYPE_CHECKING:
from telegram import Bot
@@ -46,18 +50,25 @@ class _BaseThumbedMedium(_BaseMedium):
file_size (:obj:`int`, optional): File size.
thumb (:class:`telegram.PhotoSize`, optional): Thumbnail as defined by sender.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail`.
thumbnail (:class:`telegram.PhotoSize`, optional): Thumbnail as defined by sender.
.. versionadded:: 20.2
Attributes:
file_id (:obj:`str`): File identifier.
file_unique_id (:obj:`str`): Unique identifier for this file, which
is supposed to be the same over time and for different bots.
Can't be used to download or reuse the file.
file_size (:obj:`int`): Optional. File size.
thumb (:class:`telegram.PhotoSize`): Optional. Thumbnail as defined by sender.
thumbnail (:class:`telegram.PhotoSize`): Optional. Thumbnail as defined by sender.
.. versionadded:: 20.2
"""
__slots__ = ("thumb",)
__slots__ = ("thumbnail",)
def __init__(
self,
@@ -65,6 +76,7 @@ class _BaseThumbedMedium(_BaseMedium):
file_unique_id: str,
file_size: int = None,
thumb: PhotoSize = None,
thumbnail: PhotoSize = None,
*,
api_kwargs: JSONDict = None,
):
@@ -74,7 +86,27 @@ class _BaseThumbedMedium(_BaseMedium):
file_size=file_size,
api_kwargs=api_kwargs,
)
self.thumb: Optional[PhotoSize] = thumb
self.thumbnail: Optional[PhotoSize] = warn_about_deprecated_arg_return_new_arg(
deprecated_arg=thumb,
new_arg=thumbnail,
deprecated_arg_name="thumb",
new_arg_name="thumbnail",
bot_api_version="6.6",
stacklevel=3,
)
@property
def thumb(self) -> Optional[PhotoSize]:
""":class:`telegram.PhotoSize`: Optional. Thumbnail as defined by sender.
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb", new_attr_name="thumbnail", bot_api_version="6.6"
)
return self.thumbnail
@classmethod
def de_json(
@@ -87,7 +119,13 @@ class _BaseThumbedMedium(_BaseMedium):
return None
# In case this wasn't already done by the subclass
if not isinstance(data.get("thumb"), PhotoSize):
data["thumb"] = PhotoSize.de_json(data.get("thumb"), bot)
if not isinstance(data.get("thumbnail"), PhotoSize):
data["thumbnail"] = PhotoSize.de_json(data.get("thumbnail"), bot)
return super().de_json(data=data, bot=bot)
api_kwargs = {}
# This is a deprecated field that TG still returns for backwards compatibility
# Let's filter it out to speed up the de-json process
if data.get("thumb") is not None:
api_kwargs["thumb"] = data.pop("thumb")
return super()._de_json(data=data, bot=bot, api_kwargs=api_kwargs)
+12 -1
View File
@@ -40,9 +40,16 @@ class Animation(_BaseThumbedMedium):
height (:obj:`int`): Video height as defined by sender.
duration (:obj:`int`): Duration of the video in seconds as defined by sender.
thumb (:class:`telegram.PhotoSize`, optional): Animation thumbnail as defined by sender.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail`.
file_name (:obj:`str`, optional): Original animation filename as defined by sender.
mime_type (:obj:`str`, optional): MIME type of the file as defined by sender.
file_size (:obj:`int`, optional): File size in bytes.
thumbnail (:class:`telegram.PhotoSize`, optional): Animation thumbnail as defined by
sender.
.. versionadded:: 20.2
Attributes:
file_id (:obj:`str`): Identifier for this file, which can be used to download
@@ -53,11 +60,13 @@ class Animation(_BaseThumbedMedium):
width (:obj:`int`): Video width as defined by sender.
height (:obj:`int`): Video height as defined by sender.
duration (:obj:`int`): Duration of the video in seconds as defined by sender.
thumb (:class:`telegram.PhotoSize`): Optional. Animation thumbnail as defined by sender.
file_name (:obj:`str`): Optional. Original animation filename as defined by sender.
mime_type (:obj:`str`): Optional. MIME type of the file as defined by sender.
file_size (:obj:`int`): Optional. File size in bytes.
thumbnail (:class:`telegram.PhotoSize`): Optional. Animation thumbnail as defined by
sender.
.. versionadded:: 20.2
"""
@@ -74,6 +83,7 @@ class Animation(_BaseThumbedMedium):
file_name: str = None,
mime_type: str = None,
file_size: int = None,
thumbnail: PhotoSize = None,
*,
api_kwargs: JSONDict = None,
):
@@ -83,6 +93,7 @@ class Animation(_BaseThumbedMedium):
file_size=file_size,
thumb=thumb,
api_kwargs=api_kwargs,
thumbnail=thumbnail,
)
with self._unfrozen():
# Required
+12 -1
View File
@@ -46,6 +46,13 @@ class Audio(_BaseThumbedMedium):
thumb (:class:`telegram.PhotoSize`, optional): Thumbnail of the album cover to
which the music file belongs.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail`.
thumbnail (:class:`telegram.PhotoSize`, optional): Thumbnail of the album cover to
which the music file belongs.
.. versionadded:: 20.2
Attributes:
file_id (:obj:`str`): Identifier for this file, which can be used to download
or reuse the file.
@@ -58,9 +65,11 @@ class Audio(_BaseThumbedMedium):
file_name (:obj:`str`): Optional. Original filename as defined by sender.
mime_type (:obj:`str`): Optional. MIME type of the file as defined by sender.
file_size (:obj:`int`): Optional. File size in bytes.
thumb (:class:`telegram.PhotoSize`): Optional. Thumbnail of the album cover to
thumbnail (:class:`telegram.PhotoSize`): Optional. Thumbnail of the album cover to
which the music file belongs.
.. versionadded:: 20.2
"""
@@ -77,6 +86,7 @@ class Audio(_BaseThumbedMedium):
file_size: int = None,
thumb: PhotoSize = None,
file_name: str = None,
thumbnail: PhotoSize = None,
*,
api_kwargs: JSONDict = None,
):
@@ -85,6 +95,7 @@ class Audio(_BaseThumbedMedium):
file_unique_id=file_unique_id,
file_size=file_size,
thumb=thumb,
thumbnail=thumbnail,
api_kwargs=api_kwargs,
)
with self._unfrozen():
+11 -1
View File
@@ -37,19 +37,27 @@ class Document(_BaseThumbedMedium):
file_unique_id (:obj:`str`): Unique identifier for this file, which is supposed to be
the same over time and for different bots. Can't be used to download or reuse the file.
thumb (:class:`telegram.PhotoSize`, optional): Document thumbnail as defined by sender.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail`.
file_name (:obj:`str`, optional): Original filename as defined by sender.
mime_type (:obj:`str`, optional): MIME type of the file as defined by sender.
file_size (:obj:`int`, optional): File size in bytes.
thumbnail (:class:`telegram.PhotoSize`, optional): Document thumbnail as defined by sender.
.. versionadded:: 20.2
Attributes:
file_id (:obj:`str`): Identifier for this file, which can be used to download
or reuse the file.
file_unique_id (:obj:`str`): Unique identifier for this file, which is supposed to be
the same over time and for different bots. Can't be used to download or reuse the file.
thumb (:class:`telegram.PhotoSize`): Optional. Document thumbnail as defined by sender.
file_name (:obj:`str`): Optional. Original filename as defined by sender.
mime_type (:obj:`str`): Optional. MIME type of the file as defined by sender.
file_size (:obj:`int`): Optional. File size in bytes.
thumbnail (:class:`telegram.PhotoSize`): Optional. Document thumbnail as defined by sender.
.. versionadded:: 20.2
"""
@@ -63,6 +71,7 @@ class Document(_BaseThumbedMedium):
file_name: str = None,
mime_type: str = None,
file_size: int = None,
thumbnail: PhotoSize = None,
*,
api_kwargs: JSONDict = None,
):
@@ -71,6 +80,7 @@ class Document(_BaseThumbedMedium):
file_unique_id=file_unique_id,
file_size=file_size,
thumb=thumb,
thumbnail=thumbnail,
api_kwargs=api_kwargs,
)
with self._unfrozen():
-2
View File
@@ -18,7 +18,6 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Telegram InputFile."""
import logging
import mimetypes
from typing import IO, Optional, Union
from uuid import uuid4
@@ -27,7 +26,6 @@ from telegram._utils.files import load_file
from telegram._utils.types import FieldTuple
_DEFAULT_MIME_TYPE = "application/octet-stream"
logger = logging.getLogger(__name__)
class InputFile:
+123 -12
View File
@@ -31,6 +31,10 @@ from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram._utils.files import parse_file_input
from telegram._utils.types import FileInput, JSONDict, ODVInput
from telegram._utils.warnings_transition import (
warn_about_deprecated_attr_in_property,
warn_about_thumb_return_thumbnail,
)
from telegram.constants import InputMediaType
MediaType = Union[Animation, Audio, Document, PhotoSize, Video]
@@ -138,6 +142,9 @@ class InputMediaAnimation(InputMedia):
.. versionchanged:: 13.2
Accept :obj:`bytes` as input.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail`.
caption (:obj:`str`, optional): Caption of the animation to be sent,
0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters
after entities parsing.
@@ -154,11 +161,14 @@ class InputMediaAnimation(InputMedia):
with a spoiler animation.
.. versionadded:: 20.0
thumbnail (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | :obj:`str`, \
optional): |thumbdocstringnopath|
.. versionadded:: 20.2
Attributes:
type (:obj:`str`): :tg-const:`telegram.constants.InputMediaType.ANIMATION`.
media (:obj:`str` | :class:`telegram.InputFile`): Animation to send.
thumb (:class:`telegram.InputFile`): Optional. |thumbdocstringbase|
caption (:obj:`str`): Optional. Caption of the animation to be sent,
0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters
after entities parsing.
@@ -176,9 +186,12 @@ class InputMediaAnimation(InputMedia):
spoiler animation.
.. versionadded:: 20.0
thumbnail (:class:`telegram.InputFile`): Optional. |thumbdocstringbase|
.. versionadded:: 20.2
"""
__slots__ = ("duration", "height", "thumb", "width", "has_spoiler")
__slots__ = ("duration", "height", "width", "has_spoiler", "thumbnail")
def __init__(
self,
@@ -192,6 +205,7 @@ class InputMediaAnimation(InputMedia):
caption_entities: Sequence[MessageEntity] = None,
filename: str = None,
has_spoiler: bool = None,
thumbnail: FileInput = None,
*,
api_kwargs: JSONDict = None,
):
@@ -205,6 +219,7 @@ class InputMediaAnimation(InputMedia):
# things to work in local mode.
media = parse_file_input(media, filename=filename, attach=True, local_mode=True)
thumbnail = warn_about_thumb_return_thumbnail(deprecated_arg=thumb, new_arg=thumbnail)
super().__init__(
InputMediaType.ANIMATION,
media,
@@ -214,12 +229,26 @@ class InputMediaAnimation(InputMedia):
api_kwargs=api_kwargs,
)
with self._unfrozen():
self.thumb: Optional[Union[str, InputFile]] = self._parse_thumb_input(thumb)
self.thumbnail: Optional[Union[str, InputFile]] = self._parse_thumb_input(thumbnail)
self.width: Optional[int] = width
self.height: Optional[int] = height
self.duration: Optional[int] = duration
self.has_spoiler: Optional[bool] = has_spoiler
@property
def thumb(self) -> Optional[Union[str, InputFile]]:
""":class:`telegram.InputFile`: Optional. |thumbdocstringbase|
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb",
new_attr_name="thumbnail",
bot_api_version="6.6",
)
return self.thumbnail
class InputMediaPhoto(InputMedia):
"""Represents a photo to be sent.
@@ -343,10 +372,17 @@ class InputMediaVideo(InputMedia):
.. versionchanged:: 13.2
Accept :obj:`bytes` as input.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail`.
has_spoiler (:obj:`bool`, optional): Pass :obj:`True`, if the video needs to be covered
with a spoiler animation.
.. versionadded:: 20.0
thumbnail (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | :obj:`str`, \
optional): |thumbdocstringnopath|
.. versionadded:: 20.2
Attributes:
type (:obj:`str`): :tg-const:`telegram.constants.InputMediaType.VIDEO`.
@@ -366,14 +402,23 @@ class InputMediaVideo(InputMedia):
duration (:obj:`int`): Optional. Video duration in seconds.
supports_streaming (:obj:`bool`): Optional. :obj:`True`, if the uploaded video is
suitable for streaming.
thumb (:class:`telegram.InputFile`): Optional. |thumbdocstringbase|
has_spoiler (:obj:`bool`): Optional. :obj:`True`, if the video is covered with a
spoiler animation.
.. versionadded:: 20.0
thumbnail (:class:`telegram.InputFile`): Optional. |thumbdocstringbase|
.. versionadded:: 20.2
"""
__slots__ = ("duration", "height", "thumb", "supports_streaming", "width", "has_spoiler")
__slots__ = (
"duration",
"height",
"supports_streaming",
"width",
"has_spoiler",
"thumbnail",
)
def __init__(
self,
@@ -388,6 +433,7 @@ class InputMediaVideo(InputMedia):
caption_entities: Sequence[MessageEntity] = None,
filename: str = None,
has_spoiler: bool = None,
thumbnail: FileInput = None,
*,
api_kwargs: JSONDict = None,
):
@@ -401,6 +447,7 @@ class InputMediaVideo(InputMedia):
# things to work in local mode.
media = parse_file_input(media, filename=filename, attach=True, local_mode=True)
thumbnail = warn_about_thumb_return_thumbnail(deprecated_arg=thumb, new_arg=thumbnail)
super().__init__(
InputMediaType.VIDEO,
media,
@@ -413,10 +460,24 @@ class InputMediaVideo(InputMedia):
self.width: Optional[int] = width
self.height: Optional[int] = height
self.duration: Optional[int] = duration
self.thumb: Optional[Union[str, InputFile]] = self._parse_thumb_input(thumb)
self.thumbnail: Optional[Union[str, InputFile]] = self._parse_thumb_input(thumbnail)
self.supports_streaming: Optional[bool] = supports_streaming
self.has_spoiler: Optional[bool] = has_spoiler
@property
def thumb(self) -> Optional[Union[str, InputFile]]:
""":class:`telegram.InputFile`: Optional. |thumbdocstringbase|
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb",
new_attr_name="thumbnail",
bot_api_version="6.6",
)
return self.thumbnail
class InputMediaAudio(InputMedia):
"""Represents an audio file to be treated as music to be sent.
@@ -459,6 +520,13 @@ class InputMediaAudio(InputMedia):
.. versionchanged:: 13.2
Accept :obj:`bytes` as input.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail`.
thumbnail (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | :obj:`str`, \
optional): |thumbdocstringnopath|
.. versionadded:: 20.2
Attributes:
type (:obj:`str`): :tg-const:`telegram.constants.InputMediaType.AUDIO`.
media (:obj:`str` | :class:`telegram.InputFile`): Audio file to send.
@@ -476,11 +544,13 @@ class InputMediaAudio(InputMedia):
performer (:obj:`str`): Optional. Performer of the audio as defined by sender or by audio
tags.
title (:obj:`str`): Optional. Title of the audio as defined by sender or by audio tags.
thumb (:class:`telegram.InputFile`): Optional. |thumbdocstringbase|
thumbnail (:class:`telegram.InputFile`): Optional. |thumbdocstringbase|
.. versionadded:: 20.2
"""
__slots__ = ("duration", "performer", "thumb", "title")
__slots__ = ("duration", "performer", "title", "thumbnail")
def __init__(
self,
@@ -493,6 +563,7 @@ class InputMediaAudio(InputMedia):
title: str = None,
caption_entities: Sequence[MessageEntity] = None,
filename: str = None,
thumbnail: FileInput = None,
*,
api_kwargs: JSONDict = None,
):
@@ -506,6 +577,7 @@ class InputMediaAudio(InputMedia):
# things to work in local mode.
media = parse_file_input(media, filename=filename, attach=True, local_mode=True)
thumbnail = warn_about_thumb_return_thumbnail(deprecated_arg=thumb, new_arg=thumbnail)
super().__init__(
InputMediaType.AUDIO,
media,
@@ -515,11 +587,25 @@ class InputMediaAudio(InputMedia):
api_kwargs=api_kwargs,
)
with self._unfrozen():
self.thumb: Optional[Union[str, InputFile]] = self._parse_thumb_input(thumb)
self.thumbnail: Optional[Union[str, InputFile]] = self._parse_thumb_input(thumbnail)
self.duration: Optional[int] = duration
self.title: Optional[str] = title
self.performer: Optional[str] = performer
@property
def thumb(self) -> Optional[Union[str, InputFile]]:
""":class:`telegram.InputFile`: Optional. |thumbdocstringbase|
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb",
new_attr_name="thumbnail",
bot_api_version="6.6",
)
return self.thumbnail
class InputMediaDocument(InputMedia):
"""Represents a general file to be sent.
@@ -552,9 +638,16 @@ class InputMediaDocument(InputMedia):
.. versionchanged:: 13.2
Accept :obj:`bytes` as input.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail`.
disable_content_type_detection (:obj:`bool`, optional): Disables automatic server-side
content type detection for files uploaded using multipart/form-data. Always
:obj:`True`, if the document is sent as part of an album.
thumbnail (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | :obj:`str`, \
optional): |thumbdocstringnopath|
.. versionadded:: 20.2
Attributes:
type (:obj:`str`): :tg-const:`telegram.constants.InputMediaType.DOCUMENT`.
@@ -569,14 +662,15 @@ class InputMediaDocument(InputMedia):
* |tupleclassattrs|
* |alwaystuple|
thumb (:class:`telegram.InputFile`): Optional. |thumbdocstringbase|
disable_content_type_detection (:obj:`bool`): Optional. Disables automatic server-side
content type detection for files uploaded using multipart/form-data. Always
:obj:`True`, if the document is sent as part of an album.
thumbnail (:class:`telegram.InputFile`): Optional. |thumbdocstringbase|
.. versionadded:: 20.2
"""
__slots__ = ("disable_content_type_detection", "thumb")
__slots__ = ("disable_content_type_detection", "thumbnail")
def __init__(
self,
@@ -587,12 +681,15 @@ class InputMediaDocument(InputMedia):
disable_content_type_detection: bool = None,
caption_entities: Sequence[MessageEntity] = None,
filename: str = None,
thumbnail: FileInput = None,
*,
api_kwargs: JSONDict = None,
):
# We use local_mode=True because we don't have access to the actual setting and want
# things to work in local mode.
media = parse_file_input(media, Document, filename=filename, attach=True, local_mode=True)
thumbnail = warn_about_thumb_return_thumbnail(deprecated_arg=thumb, new_arg=thumbnail)
super().__init__(
InputMediaType.DOCUMENT,
media,
@@ -602,5 +699,19 @@ class InputMediaDocument(InputMedia):
api_kwargs=api_kwargs,
)
with self._unfrozen():
self.thumb: Optional[Union[str, InputFile]] = self._parse_thumb_input(thumb)
self.thumbnail: Optional[Union[str, InputFile]] = self._parse_thumb_input(thumbnail)
self.disable_content_type_detection: Optional[bool] = disable_content_type_detection
@property
def thumb(self) -> Optional[Union[str, InputFile]]:
""":class:`telegram.InputFile`: Optional. |thumbdocstringbase|
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb",
new_attr_name="thumbnail",
bot_api_version="6.6",
)
return self.thumbnail
+95
View File
@@ -0,0 +1,95 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2023
# 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 InputSticker."""
from typing import Optional, Sequence, Tuple, Union
from telegram._files.inputfile import InputFile
from telegram._files.sticker import MaskPosition
from telegram._telegramobject import TelegramObject
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.files import parse_file_input
from telegram._utils.types import FileInput, JSONDict
class InputSticker(TelegramObject):
"""
This object describes a sticker to be added to a sticker set.
.. versionadded:: 20.2
Args:
sticker (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path`): The
added sticker. |uploadinputnopath| Animated and video stickers can't be uploaded via
HTTP URL.
emoji_list (Sequence[:obj:`str`]): Sequence of
:tg-const:`telegram.constants.StickerLimit.MIN_STICKER_EMOJI` -
:tg-const:`telegram.constants.StickerLimit.MAX_STICKER_EMOJI` emoji associated with the
sticker.
mask_position (:obj:`telegram.MaskPosition`, optional): Position where the mask should be
placed on faces. For ":tg-const:`telegram.constants.StickerType.MASK`" stickers only.
keywords (Sequence[:obj:`str`], optional): Sequence of
0-:tg-const:`telegram.constants.StickerLimit.MAX_SEARCH_KEYWORDS` search keywords
for the sticker with the total length of up to
:tg-const:`telegram.constants.StickerLimit.MAX_KEYWORD_LENGTH` characters. For
":tg-const:`telegram.constants.StickerType.REGULAR`" and
":tg-const:`telegram.constants.StickerType.CUSTOM_EMOJI`" stickers only.
Attributes:
sticker (:obj:`str` | :class:`telegram.InputFile`): The added sticker.
emoji_list (Tuple[:obj:`str`]): Tuple of
:tg-const:`telegram.constants.StickerLimit.MIN_STICKER_EMOJI` -
:tg-const:`telegram.constants.StickerLimit.MAX_STICKER_EMOJI` emoji associated with the
sticker.
mask_position (:obj:`telegram.MaskPosition`): Optional. Position where the mask should be
placed on faces. For ":tg-const:`telegram.constants.StickerType.MASK`" stickers only.
keywords (Tuple[:obj:`str`]): Optional. Tuple of
0-:tg-const:`telegram.constants.StickerLimit.MAX_SEARCH_KEYWORDS` search keywords
for the sticker with the total length of up to
:tg-const:`telegram.constants.StickerLimit.MAX_KEYWORD_LENGTH` characters. For
":tg-const:`telegram.constants.StickerType.REGULAR`" and
":tg-const:`telegram.constants.StickerType.CUSTOM_EMOJI`" stickers only.
"""
__slots__ = ("sticker", "emoji_list", "mask_position", "keywords")
def __init__(
self,
sticker: FileInput,
emoji_list: Sequence[str],
mask_position: MaskPosition = None,
keywords: Sequence[str] = None,
*,
api_kwargs: JSONDict = None,
):
super().__init__(api_kwargs=api_kwargs)
# We use local_mode=True because we don't have access to the actual setting and want
# things to work in local mode.
self.sticker: Union[str, InputFile] = parse_file_input(
sticker,
local_mode=True,
attach=True,
)
self.emoji_list: Tuple[str, ...] = parse_sequence_arg(emoji_list)
self.mask_position: Optional[MaskPosition] = mask_position
self.keywords: Tuple[str, ...] = parse_sequence_arg(keywords)
self._freeze()
+73 -12
View File
@@ -26,6 +26,10 @@ from telegram._files.photosize import PhotoSize
from telegram._telegramobject import TelegramObject
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.types import JSONDict
from telegram._utils.warnings_transition import (
warn_about_deprecated_attr_in_property,
warn_about_thumb_return_thumbnail,
)
if TYPE_CHECKING:
from telegram import Bot
@@ -61,6 +65,9 @@ class Sticker(_BaseThumbedMedium):
.. versionadded:: 20.0
thumb (:class:`telegram.PhotoSize`, optional): Sticker thumbnail in the ``.WEBP`` or
``.JPG`` format.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail`.
emoji (:obj:`str`, optional): Emoji associated with the sticker
set_name (:obj:`str`, optional): Name of the sticker set to which the sticker belongs.
mask_position (:class:`telegram.MaskPosition`, optional): For mask stickers, the position
@@ -75,6 +82,15 @@ class Sticker(_BaseThumbedMedium):
custom emoji.
.. versionadded:: 20.0
thumbnail (:class:`telegram.PhotoSize`, optional): Sticker thumbnail in the ``.WEBP`` or
``.JPG`` format.
.. versionadded:: 20.2
needs_repainting (:obj:`bool`, optional): :obj:`True`, if the sticker must be repainted to
a text color in messages, the color of the Telegram Premium badge in emoji status,
white color on chat photos, or another appropriate color in other places.
.. versionadded:: 20.2
Attributes:
file_id (:obj:`str`): Identifier for this file, which can be used to download
@@ -93,8 +109,6 @@ class Sticker(_BaseThumbedMedium):
format, which is determined by the fields :attr:`is_animated` and :attr:`is_video`.
.. versionadded:: 20.0
thumb (:class:`telegram.PhotoSize`): Optional. Sticker thumbnail in the ``.WEBP`` or
``.JPG`` format.
emoji (:obj:`str`): Optional. Emoji associated with the sticker.
set_name (:obj:`str`): Optional. Name of the sticker set to which the sticker belongs.
mask_position (:class:`telegram.MaskPosition`): Optional. For mask stickers, the position
@@ -109,6 +123,15 @@ class Sticker(_BaseThumbedMedium):
custom emoji.
.. versionadded:: 20.0
thumbnail (:class:`telegram.PhotoSize`): Optional. Sticker thumbnail in the ``.WEBP`` or
``.JPG`` format.
.. versionadded:: 20.2
needs_repainting (:obj:`bool`): Optional. :obj:`True`, if the sticker must be repainted to
a text color in messages, the color of the Telegram Premium badge in emoji status,
white color on chat photos, or another appropriate color in other places.
.. versionadded:: 20.2
"""
__slots__ = (
@@ -122,6 +145,7 @@ class Sticker(_BaseThumbedMedium):
"premium_animation",
"type",
"custom_emoji_id",
"needs_repainting",
)
def __init__(
@@ -140,6 +164,8 @@ class Sticker(_BaseThumbedMedium):
mask_position: "MaskPosition" = None,
premium_animation: "File" = None,
custom_emoji_id: str = None,
thumbnail: PhotoSize = None,
needs_repainting: bool = None,
*,
api_kwargs: JSONDict = None,
):
@@ -148,6 +174,7 @@ class Sticker(_BaseThumbedMedium):
file_unique_id=file_unique_id,
file_size=file_size,
thumb=thumb,
thumbnail=thumbnail,
api_kwargs=api_kwargs,
)
with self._unfrozen():
@@ -163,6 +190,7 @@ class Sticker(_BaseThumbedMedium):
self.mask_position: Optional[MaskPosition] = mask_position
self.premium_animation: Optional[File] = premium_animation
self.custom_emoji_id: Optional[str] = custom_emoji_id
self.needs_repainting: Optional[bool] = needs_repainting
REGULAR: ClassVar[str] = constants.StickerType.REGULAR
""":const:`telegram.constants.StickerType.REGULAR`"""
@@ -179,11 +207,17 @@ class Sticker(_BaseThumbedMedium):
if not data:
return None
data["thumb"] = PhotoSize.de_json(data.get("thumb"), bot)
data["thumbnail"] = PhotoSize.de_json(data.get("thumbnail"), bot)
data["mask_position"] = MaskPosition.de_json(data.get("mask_position"), bot)
data["premium_animation"] = File.de_json(data.get("premium_animation"), bot)
return super().de_json(data=data, bot=bot)
api_kwargs = {}
# This is a deprecated field that TG still returns for backwards compatibility
# Let's filter it out to speed up the de-json process
if data.get("thumb") is not None:
api_kwargs["thumb"] = data.pop("thumb")
return super()._de_json(data=data, bot=bot, api_kwargs=api_kwargs)
class StickerSet(TelegramObject):
@@ -220,6 +254,13 @@ class StickerSet(TelegramObject):
thumb (:class:`telegram.PhotoSize`, optional): Sticker set thumbnail in the ``.WEBP``,
``.TGS``, or ``.WEBM`` format.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail`.
thumbnail (:class:`telegram.PhotoSize`, optional): Sticker set thumbnail in the ``.WEBP``,
``.TGS``, or ``.WEBM`` format.
.. versionadded:: 20.2
Attributes:
name (:obj:`str`): Sticker set name.
title (:obj:`str`): Sticker set title.
@@ -237,9 +278,10 @@ class StickerSet(TelegramObject):
:attr:`telegram.Sticker.CUSTOM_EMOJI`.
.. versionadded:: 20.0
thumb (:class:`telegram.PhotoSize`): Optional. Sticker set thumbnail in the ``.WEBP``,
thumbnail (:class:`telegram.PhotoSize`): Optional. Sticker set thumbnail in the ``.WEBP``,
``.TGS``, or ``.WEBM`` format.
.. versionadded:: 20.2
"""
__slots__ = (
@@ -247,7 +289,7 @@ class StickerSet(TelegramObject):
"is_video",
"name",
"stickers",
"thumb",
"thumbnail",
"title",
"sticker_type",
)
@@ -261,6 +303,7 @@ class StickerSet(TelegramObject):
is_video: bool,
sticker_type: str,
thumb: PhotoSize = None,
thumbnail: PhotoSize = None,
*,
api_kwargs: JSONDict = None,
):
@@ -272,26 +315,44 @@ class StickerSet(TelegramObject):
self.stickers: Tuple[Sticker, ...] = parse_sequence_arg(stickers)
self.sticker_type: str = sticker_type
# Optional
self.thumb: Optional[PhotoSize] = thumb
self.thumbnail: Optional[PhotoSize] = warn_about_thumb_return_thumbnail(
deprecated_arg=thumb, new_arg=thumbnail
)
self._id_attrs = (self.name,)
self._freeze()
@property
def thumb(self) -> Optional[PhotoSize]:
""":class:`telegram.PhotoSize`: Optional. Sticker set thumbnail in the ``.WEBP``,
``.TGS``, or ``.WEBM`` format.
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb",
new_attr_name="thumbnail",
bot_api_version="6.6",
)
return self.thumbnail
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["StickerSet"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
if not data:
return None
data["thumb"] = PhotoSize.de_json(data.get("thumb"), bot)
data["thumbnail"] = PhotoSize.de_json(data.get("thumbnail"), bot)
data["stickers"] = Sticker.de_list(data.get("stickers"), bot)
api_kwargs = {}
# This is a deprecated field that TG still returns for backwards compatibility
# Let's filter it out to speed up the de-json process
if "contains_masks" in data:
api_kwargs["contains_masks"] = data.pop("contains_masks")
# These are deprecated fields that TG still returns for backwards compatibility
# Let's filter them out to speed up the de-json process
for deprecated_field in ("contains_masks", "thumb"):
if deprecated_field in data:
api_kwargs[deprecated_field] = data.pop(deprecated_field)
return super()._de_json(data=data, bot=bot, api_kwargs=api_kwargs)
+10 -1
View File
@@ -40,9 +40,15 @@ class Video(_BaseThumbedMedium):
height (:obj:`int`): Video height as defined by sender.
duration (:obj:`int`): Duration of the video in seconds as defined by sender.
thumb (:class:`telegram.PhotoSize`, optional): Video thumbnail.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail`.
file_name (:obj:`str`, optional): Original filename as defined by sender.
mime_type (:obj:`str`, optional): MIME type of a file as defined by sender.
file_size (:obj:`int`, optional): File size in bytes.
thumbnail (:class:`telegram.PhotoSize`, optional): Video thumbnail.
.. versionadded:: 20.2
Attributes:
file_id (:obj:`str`): Identifier for this file, which can be used to download
@@ -53,11 +59,12 @@ class Video(_BaseThumbedMedium):
width (:obj:`int`): Video width as defined by sender.
height (:obj:`int`): Video height as defined by sender.
duration (:obj:`int`): Duration of the video in seconds as defined by sender.
thumb (:class:`telegram.PhotoSize`): Optional. Video thumbnail.
file_name (:obj:`str`): Optional. Original filename as defined by sender.
mime_type (:obj:`str`): Optional. MIME type of a file as defined by sender.
file_size (:obj:`int`): Optional. File size in bytes.
thumbnail (:class:`telegram.PhotoSize`): Optional. Video thumbnail.
.. versionadded:: 20.2
"""
__slots__ = ("duration", "file_name", "height", "mime_type", "width")
@@ -73,6 +80,7 @@ class Video(_BaseThumbedMedium):
mime_type: str = None,
file_size: int = None,
file_name: str = None,
thumbnail: PhotoSize = None,
*,
api_kwargs: JSONDict = None,
):
@@ -81,6 +89,7 @@ class Video(_BaseThumbedMedium):
file_unique_id=file_unique_id,
file_size=file_size,
thumb=thumb,
thumbnail=thumbnail,
api_kwargs=api_kwargs,
)
with self._unfrozen():
+11 -1
View File
@@ -39,7 +39,13 @@ class VideoNote(_BaseThumbedMedium):
by sender.
duration (:obj:`int`): Duration of the video in seconds as defined by sender.
thumb (:class:`telegram.PhotoSize`, optional): Video thumbnail.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail`.
file_size (:obj:`int`, optional): File size in bytes.
thumbnail (:class:`telegram.PhotoSize`, optional): Video thumbnail.
.. versionadded:: 20.2
Attributes:
file_id (:obj:`str`): Identifier for this file, which can be used to download
@@ -50,8 +56,10 @@ class VideoNote(_BaseThumbedMedium):
length (:obj:`int`): Video width and height (diameter of the video message) as defined
by sender.
duration (:obj:`int`): Duration of the video in seconds as defined by sender.
thumb (:class:`telegram.PhotoSize`): Optional. Video thumbnail.
file_size (:obj:`int`): Optional. File size in bytes.
thumbnail (:class:`telegram.PhotoSize`): Optional. Video thumbnail.
.. versionadded:: 20.2
"""
@@ -65,6 +73,7 @@ class VideoNote(_BaseThumbedMedium):
duration: int,
thumb: PhotoSize = None,
file_size: int = None,
thumbnail: PhotoSize = None,
*,
api_kwargs: JSONDict = None,
):
@@ -73,6 +82,7 @@ class VideoNote(_BaseThumbedMedium):
file_unique_id=file_unique_id,
file_size=file_size,
thumb=thumb,
thumbnail=thumbnail,
api_kwargs=api_kwargs,
)
with self._unfrozen():
-4
View File
@@ -17,7 +17,6 @@
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Telegram Game."""
import sys
from typing import TYPE_CHECKING, Dict, List, Optional, Sequence, Tuple
from telegram._files.animation import Animation
@@ -158,9 +157,6 @@ class Game(TelegramObject):
if not self.text:
raise RuntimeError("This Game has no 'text'.")
# Is it a narrow build, if so we don't need to convert
if sys.maxunicode == 0xFFFF:
return self.text[entity.offset : entity.offset + entity.length]
entity_text = self.text.encode("utf-16-le")
entity_text = entity_text[entity.offset * 2 : (entity.offset + entity.length) * 2]
+44
View File
@@ -23,6 +23,7 @@ from typing import TYPE_CHECKING, ClassVar, Optional, Union
from telegram import constants
from telegram._games.callbackgame import CallbackGame
from telegram._loginurl import LoginUrl
from telegram._switchinlinequerychosenchat import SwitchInlineQueryChosenChat
from telegram._telegramobject import TelegramObject
from telegram._utils.types import JSONDict
from telegram._webappinfo import WebAppInfo
@@ -111,6 +112,10 @@ class InlineKeyboardButton(TelegramObject):
in inline mode when they are currently in a private chat with it. Especially useful
when combined with ``switch_pm*`` actions - in this case the user will be automatically
returned to the chat they switched from, skipping the chat selection screen.
Tip:
This is similar to the new parameter :paramref:`switch_inline_query_chosen_chat`,
but gives no control over which chats can be selected.
switch_inline_query_current_chat (:obj:`str`, optional): If set, pressing the button will
insert the bot's username and the specified inline query in the current chat's input
field. Can be empty, in which case only the bot's username will be inserted. This
@@ -122,6 +127,20 @@ class InlineKeyboardButton(TelegramObject):
pay (:obj:`bool`, optional): Specify :obj:`True`, to send a Pay button. This type of button
**must** always be the **first** button in the first row and can only be used in
invoice messages.
switch_inline_query_chosen_chat (:obj:`telegram.SwitchInlineQueryChosenChat`, optional):
If set, pressing the button will prompt the user to select one of their chats of the
specified type, open that chat and insert the bot's username and the specified inline
query in the input field.
.. versionadded:: 20.3
Tip:
This is similar to :paramref:`switch_inline_query`, but gives more control on
which chats can be selected.
Caution:
The PTB team has discovered that this field works correctly only if your Telegram
client is released after April 20th 2023.
Attributes:
text (:obj:`str`): Label text on the button.
@@ -154,6 +173,10 @@ class InlineKeyboardButton(TelegramObject):
in inline mode when they are currently in a private chat with it. Especially useful
when combined with ``switch_pm*`` actions - in this case the user will be automatically
returned to the chat they switched from, skipping the chat selection screen.
Tip:
This is similar to the new parameter :paramref:`switch_inline_query_chosen_chat`,
but gives no control over which chats can be selected.
switch_inline_query_current_chat (:obj:`str`): Optional. If set, pressing the button will
insert the bot's username and the specified inline query in the current chat's input
field. Can be empty, in which case only the bot's username will be inserted. This
@@ -165,7 +188,20 @@ class InlineKeyboardButton(TelegramObject):
pay (:obj:`bool`): Optional. Specify :obj:`True`, to send a Pay button. This type of button
**must** always be the **first** button in the first row and can only be used in
invoice messages.
switch_inline_query_chosen_chat (:obj:`telegram.SwitchInlineQueryChosenChat`): Optional.
If set, pressing the button will prompt the user to select one of their chats of the
specified type, open that chat and insert the bot's username and the specified inline
query in the input field.
.. versionadded:: 20.3
Tip:
This is similar to :attr:`switch_inline_query`, but gives more control on
which chats can be selected.
Caution:
The PTB team has discovered that this field works correctly only if your Telegram
client is released after April 20th 2023.
"""
__slots__ = (
@@ -178,6 +214,7 @@ class InlineKeyboardButton(TelegramObject):
"text",
"login_url",
"web_app",
"switch_inline_query_chosen_chat",
)
def __init__(
@@ -191,6 +228,7 @@ class InlineKeyboardButton(TelegramObject):
pay: bool = None,
login_url: LoginUrl = None,
web_app: WebAppInfo = None,
switch_inline_query_chosen_chat: SwitchInlineQueryChosenChat = None,
*,
api_kwargs: JSONDict = None,
):
@@ -207,6 +245,9 @@ class InlineKeyboardButton(TelegramObject):
self.callback_game: Optional[CallbackGame] = callback_game
self.pay: Optional[bool] = pay
self.web_app: Optional[WebAppInfo] = web_app
self.switch_inline_query_chosen_chat: Optional[
SwitchInlineQueryChosenChat
] = switch_inline_query_chosen_chat
self._id_attrs = ()
self._set_id_attrs()
@@ -236,6 +277,9 @@ class InlineKeyboardButton(TelegramObject):
data["login_url"] = LoginUrl.de_json(data.get("login_url"), bot)
data["web_app"] = WebAppInfo.de_json(data.get("web_app"), bot)
data["callback_game"] = CallbackGame.de_json(data.get("callback_game"), bot)
data["switch_inline_query_chosen_chat"] = SwitchInlineQueryChosenChat.de_json(
data.get("switch_inline_query_chosen_chat"), bot
)
return super().de_json(data=data, bot=bot)
+3
View File
@@ -23,6 +23,7 @@ from typing import TYPE_CHECKING, Callable, ClassVar, Optional, Sequence, Union
from telegram import constants
from telegram._files.location import Location
from telegram._inline.inlinequeryresultsbutton import InlineQueryResultsButton
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils.defaultvalue import DEFAULT_NONE
@@ -146,6 +147,7 @@ class InlineQuery(TelegramObject):
next_offset: str = None,
switch_pm_text: str = None,
switch_pm_parameter: str = None,
button: InlineQueryResultsButton = None,
*,
current_offset: str = None,
auto_pagination: bool = False,
@@ -192,6 +194,7 @@ class InlineQuery(TelegramObject):
next_offset=next_offset,
switch_pm_text=switch_pm_text,
switch_pm_parameter=switch_pm_parameter,
button=button,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
+100 -9
View File
@@ -23,6 +23,10 @@ from typing import TYPE_CHECKING, Optional
from telegram._inline.inlinekeyboardmarkup import InlineKeyboardMarkup
from telegram._inline.inlinequeryresult import InlineQueryResult
from telegram._utils.types import JSONDict
from telegram._utils.warnings_transition import (
warn_about_deprecated_arg_return_new_arg,
warn_about_deprecated_attr_in_property,
)
from telegram.constants import InlineQueryResultType
if TYPE_CHECKING:
@@ -49,9 +53,27 @@ class InlineQueryResultArticle(InlineQueryResult):
in the message.
description (:obj:`str`, optional): Short description of the result.
thumb_url (:obj:`str`, optional): Url of the thumbnail for the result.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail_url`.
thumb_width (:obj:`int`, optional): Thumbnail width.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail_width`.
thumb_height (:obj:`int`, optional): Thumbnail height.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail_height`.
thumbnail_url (:obj:`str`, optional): Url of the thumbnail for the result.
.. versionadded:: 20.2
thumbnail_width (:obj:`int`, optional): Thumbnail width.
.. versionadded:: 20.2
thumbnail_height (:obj:`int`, optional): Thumbnail height.
.. versionadded:: 20.2
Attributes:
type (:obj:`str`): :tg-const:`telegram.constants.InlineQueryResultType.ARTICLE`.
id (:obj:`str`): Unique identifier for this result,
@@ -66,22 +88,28 @@ class InlineQueryResultArticle(InlineQueryResult):
hide_url (:obj:`bool`): Optional. Pass :obj:`True`, if you don't want the URL to be shown
in the message.
description (:obj:`str`): Optional. Short description of the result.
thumb_url (:obj:`str`): Optional. Url of the thumbnail for the result.
thumb_width (:obj:`int`): Optional. Thumbnail width.
thumb_height (:obj:`int`): Optional. Thumbnail height.
thumbnail_url (:obj:`str`): Optional. Url of the thumbnail for the result.
.. versionadded:: 20.2
thumbnail_width (:obj:`int`): Optional. Thumbnail width.
.. versionadded:: 20.2
thumbnail_height (:obj:`int`): Optional. Thumbnail height.
.. versionadded:: 20.2
"""
__slots__ = (
"reply_markup",
"thumb_width",
"thumb_height",
"hide_url",
"url",
"title",
"description",
"input_message_content",
"thumb_url",
"thumbnail_width",
"thumbnail_height",
"thumbnail_url",
)
def __init__(
@@ -96,6 +124,9 @@ class InlineQueryResultArticle(InlineQueryResult):
thumb_url: str = None,
thumb_width: int = None,
thumb_height: int = None,
thumbnail_url: str = None,
thumbnail_width: int = None,
thumbnail_height: int = None,
*,
api_kwargs: JSONDict = None,
):
@@ -110,6 +141,66 @@ class InlineQueryResultArticle(InlineQueryResult):
self.url: Optional[str] = url
self.hide_url: Optional[bool] = hide_url
self.description: Optional[str] = description
self.thumb_url: Optional[str] = thumb_url
self.thumb_width: Optional[int] = thumb_width
self.thumb_height: Optional[int] = thumb_height
self.thumbnail_url: Optional[str] = warn_about_deprecated_arg_return_new_arg(
deprecated_arg=thumb_url,
new_arg=thumbnail_url,
deprecated_arg_name="thumb_url",
new_arg_name="thumbnail_url",
bot_api_version="6.6",
)
self.thumbnail_width: Optional[int] = warn_about_deprecated_arg_return_new_arg(
deprecated_arg=thumb_width,
new_arg=thumbnail_width,
deprecated_arg_name="thumb_width",
new_arg_name="thumbnail_width",
bot_api_version="6.6",
)
self.thumbnail_height: Optional[int] = warn_about_deprecated_arg_return_new_arg(
deprecated_arg=thumb_height,
new_arg=thumbnail_height,
deprecated_arg_name="thumb_height",
new_arg_name="thumbnail_height",
bot_api_version="6.6",
)
@property
def thumb_url(self) -> Optional[str]:
""":obj:`str`: Optional. Url of the thumbnail for the result.
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail_url`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb_url",
new_attr_name="thumbnail_url",
bot_api_version="6.6",
)
return self.thumbnail_url
@property
def thumb_width(self) -> Optional[int]:
""":obj:`str`: Optional. Thumbnail width.
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail_width`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb_width",
new_attr_name="thumbnail_width",
bot_api_version="6.6",
)
return self.thumbnail_width
@property
def thumb_height(self) -> Optional[int]:
""":obj:`str`: Optional. Thumbnail height.
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail_height`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb_height",
new_attr_name="thumbnail_height",
bot_api_version="6.6",
)
return self.thumbnail_height
+100 -9
View File
@@ -23,6 +23,10 @@ from typing import TYPE_CHECKING, Optional
from telegram._inline.inlinekeyboardmarkup import InlineKeyboardMarkup
from telegram._inline.inlinequeryresult import InlineQueryResult
from telegram._utils.types import JSONDict
from telegram._utils.warnings_transition import (
warn_about_deprecated_arg_return_new_arg,
warn_about_deprecated_attr_in_property,
)
from telegram.constants import InlineQueryResultType
if TYPE_CHECKING:
@@ -49,9 +53,27 @@ class InlineQueryResultContact(InlineQueryResult):
input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the
message to be sent instead of the contact.
thumb_url (:obj:`str`, optional): Url of the thumbnail for the result.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail_url`.
thumb_width (:obj:`int`, optional): Thumbnail width.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail_width`.
thumb_height (:obj:`int`, optional): Thumbnail height.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail_height`.
thumbnail_url (:obj:`str`, optional): Url of the thumbnail for the result.
.. versionadded:: 20.2
thumbnail_width (:obj:`int`, optional): Thumbnail width.
.. versionadded:: 20.2
thumbnail_height (:obj:`int`, optional): Thumbnail height.
.. versionadded:: 20.2
Attributes:
type (:obj:`str`): :tg-const:`telegram.constants.InlineQueryResultType.CONTACT`.
id (:obj:`str`): Unique identifier for this result,
@@ -66,22 +88,28 @@ class InlineQueryResultContact(InlineQueryResult):
to the message.
input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the
message to be sent instead of the contact.
thumb_url (:obj:`str`): Optional. Url of the thumbnail for the result.
thumb_width (:obj:`int`): Optional. Thumbnail width.
thumb_height (:obj:`int`): Optional. Thumbnail height.
thumbnail_url (:obj:`str`): Optional. Url of the thumbnail for the result.
.. versionadded:: 20.2
thumbnail_width (:obj:`int`): Optional. Thumbnail width.
.. versionadded:: 20.2
thumbnail_height (:obj:`int`): Optional. Thumbnail height.
.. versionadded:: 20.2
"""
__slots__ = (
"reply_markup",
"thumb_width",
"thumb_height",
"thumbnail_width",
"thumbnail_height",
"vcard",
"first_name",
"last_name",
"phone_number",
"input_message_content",
"thumb_url",
"thumbnail_url",
)
def __init__(
@@ -96,6 +124,9 @@ class InlineQueryResultContact(InlineQueryResult):
thumb_width: int = None,
thumb_height: int = None,
vcard: str = None,
thumbnail_url: str = None,
thumbnail_width: int = None,
thumbnail_height: int = None,
*,
api_kwargs: JSONDict = None,
):
@@ -110,6 +141,66 @@ class InlineQueryResultContact(InlineQueryResult):
self.vcard: Optional[str] = vcard
self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup
self.input_message_content: Optional[InputMessageContent] = input_message_content
self.thumb_url: Optional[str] = thumb_url
self.thumb_width: Optional[int] = thumb_width
self.thumb_height: Optional[int] = thumb_height
self.thumbnail_url: Optional[str] = warn_about_deprecated_arg_return_new_arg(
deprecated_arg=thumb_url,
new_arg=thumbnail_url,
deprecated_arg_name="thumb_url",
new_arg_name="thumbnail_url",
bot_api_version="6.6",
)
self.thumbnail_width: Optional[int] = warn_about_deprecated_arg_return_new_arg(
deprecated_arg=thumb_width,
new_arg=thumbnail_width,
deprecated_arg_name="thumb_width",
new_arg_name="thumbnail_width",
bot_api_version="6.6",
)
self.thumbnail_height: Optional[int] = warn_about_deprecated_arg_return_new_arg(
deprecated_arg=thumb_height,
new_arg=thumbnail_height,
deprecated_arg_name="thumb_height",
new_arg_name="thumbnail_height",
bot_api_version="6.6",
)
@property
def thumb_url(self) -> Optional[str]:
""":obj:`str`: Optional. Url of the thumbnail for the result.
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail_url`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb_url",
new_attr_name="thumbnail_url",
bot_api_version="6.6",
)
return self.thumbnail_url
@property
def thumb_width(self) -> Optional[int]:
""":obj:`str`: Optional. Thumbnail width.
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail_width`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb_width",
new_attr_name="thumbnail_width",
bot_api_version="6.6",
)
return self.thumbnail_width
@property
def thumb_height(self) -> Optional[int]:
""":obj:`str`: Optional. Thumbnail height.
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail_height`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb_height",
new_attr_name="thumbnail_height",
bot_api_version="6.6",
)
return self.thumbnail_height
+101 -9
View File
@@ -25,6 +25,10 @@ from telegram._messageentity import MessageEntity
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram._utils.types import JSONDict, ODVInput
from telegram._utils.warnings_transition import (
warn_about_deprecated_arg_return_new_arg,
warn_about_deprecated_attr_in_property,
)
from telegram.constants import InlineQueryResultType
if TYPE_CHECKING:
@@ -63,9 +67,28 @@ class InlineQueryResultDocument(InlineQueryResult):
input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the
message to be sent instead of the file.
thumb_url (:obj:`str`, optional): URL of the thumbnail (JPEG only) for the file.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail_url`.
thumb_width (:obj:`int`, optional): Thumbnail width.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail_width`.
thumb_height (:obj:`int`, optional): Thumbnail height.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail_height`.
thumbnail_url (:obj:`str`, optional): URL of the thumbnail (JPEG only) for the file.
.. versionadded:: 20.2
thumbnail_width (:obj:`int`, optional): Thumbnail width.
.. versionadded:: 20.2
thumbnail_height (:obj:`int`, optional): Thumbnail height.
.. versionadded:: 20.2
Attributes:
type (:obj:`str`): :tg-const:`telegram.constants.InlineQueryResultType.DOCUMENT`.
id (:obj:`str`): Unique identifier for this result,
@@ -90,9 +113,15 @@ class InlineQueryResultDocument(InlineQueryResult):
to the message.
input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the
message to be sent instead of the file.
thumb_url (:obj:`str`): Optional. URL of the thumbnail (JPEG only) for the file.
thumb_width (:obj:`int`): Optional. Thumbnail width.
thumb_height (:obj:`int`): Optional. Thumbnail height.
thumbnail_url (:obj:`str`): Optional. URL of the thumbnail (JPEG only) for the file.
.. versionadded:: 20.2
thumbnail_width (:obj:`int`): Optional. Thumbnail width.
.. versionadded:: 20.2
thumbnail_height (:obj:`int`): Optional. Thumbnail height.
.. versionadded:: 20.2
"""
@@ -100,14 +129,14 @@ class InlineQueryResultDocument(InlineQueryResult):
"reply_markup",
"caption_entities",
"document_url",
"thumb_width",
"thumb_height",
"thumbnail_width",
"thumbnail_height",
"caption",
"title",
"description",
"parse_mode",
"mime_type",
"thumb_url",
"thumbnail_url",
"input_message_content",
)
@@ -126,6 +155,9 @@ class InlineQueryResultDocument(InlineQueryResult):
thumb_height: int = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
caption_entities: Sequence[MessageEntity] = None,
thumbnail_url: str = None,
thumbnail_width: int = None,
thumbnail_height: int = None,
*,
api_kwargs: JSONDict = None,
):
@@ -143,6 +175,66 @@ class InlineQueryResultDocument(InlineQueryResult):
self.description: Optional[str] = description
self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup
self.input_message_content: Optional[InputMessageContent] = input_message_content
self.thumb_url: Optional[str] = thumb_url
self.thumb_width: Optional[int] = thumb_width
self.thumb_height: Optional[int] = thumb_height
self.thumbnail_url: Optional[str] = warn_about_deprecated_arg_return_new_arg(
deprecated_arg=thumb_url,
new_arg=thumbnail_url,
deprecated_arg_name="thumb_url",
new_arg_name="thumbnail_url",
bot_api_version="6.6",
)
self.thumbnail_width: Optional[int] = warn_about_deprecated_arg_return_new_arg(
deprecated_arg=thumb_width,
new_arg=thumbnail_width,
deprecated_arg_name="thumb_width",
new_arg_name="thumbnail_width",
bot_api_version="6.6",
)
self.thumbnail_height: Optional[int] = warn_about_deprecated_arg_return_new_arg(
deprecated_arg=thumb_height,
new_arg=thumbnail_height,
deprecated_arg_name="thumb_height",
new_arg_name="thumbnail_height",
bot_api_version="6.6",
)
@property
def thumb_url(self) -> Optional[str]:
""":obj:`str`: Optional. URL of the thumbnail (JPEG only) for the file.
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail_url`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb_url",
new_attr_name="thumbnail_url",
bot_api_version="6.6",
)
return self.thumbnail_url
@property
def thumb_width(self) -> Optional[int]:
""":obj:`str`: Optional. Thumbnail width.
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail_width`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb_width",
new_attr_name="thumbnail_width",
bot_api_version="6.6",
)
return self.thumbnail_width
@property
def thumb_height(self) -> Optional[int]:
""":obj:`str`: Optional. Thumbnail height.
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail_height`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb_height",
new_attr_name="thumbnail_height",
bot_api_version="6.6",
)
return self.thumbnail_height
+97 -11
View File
@@ -25,6 +25,10 @@ from telegram._messageentity import MessageEntity
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram._utils.types import JSONDict, ODVInput
from telegram._utils.warnings_transition import (
warn_about_deprecated_arg_return_new_arg,
warn_about_deprecated_attr_in_property,
)
from telegram.constants import InlineQueryResultType
if TYPE_CHECKING:
@@ -47,10 +51,20 @@ class InlineQueryResultGif(InlineQueryResult):
gif_width (:obj:`int`, optional): Width of the GIF.
gif_height (:obj:`int`, optional): Height of the GIF.
gif_duration (:obj:`int`, optional): Duration of the GIF in seconds.
thumb_url (:obj:`str`): URL of the static (JPEG or GIF) or animated (MPEG4) thumbnail for
the result.
thumb_mime_type (:obj:`str`, optional): MIME type of the thumbnail, must be one of
thumbnail_url (:obj:`str`, optional): URL of the static (JPEG or GIF) or animated (MPEG4)
thumbnail for the result.
Warning:
The Bot API does **not** define this as an optional argument. It is formally
optional for backwards compatibility with the deprecated :paramref:`thumb_url`.
If you pass neither :paramref:`thumbnail_url` nor :paramref:`thumb_url`,
:class:`ValueError` will be raised.
.. versionadded:: 20.2
thumbnail_mime_type (:obj:`str`, optional): MIME type of the thumbnail, must be one of
``'image/jpeg'``, ``'image/gif'``, or ``'video/mp4'``. Defaults to ``'image/jpeg'``.
.. versionadded:: 20.2
title (:obj:`str`, optional): Title for the result.
caption (:obj:`str`, optional): Caption of the GIF file to be sent,
0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters
@@ -65,6 +79,20 @@ class InlineQueryResultGif(InlineQueryResult):
to the message.
input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the
message to be sent instead of the GIF animation.
thumb_mime_type (:obj:`str`, optional): MIME type of the thumbnail, must be one of
``'image/jpeg'``, ``'image/gif'``, or ``'video/mp4'``. Defaults to ``'image/jpeg'``.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail_mime_type`.
thumb_url (:obj:`str`, optional): URL of the static (JPEG or GIF) or animated (MPEG4)
thumbnail for the result.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail_url`.
Raises:
:class:`ValueError`: If neither :paramref:`thumbnail_url` nor :paramref:`thumb_url` is
supplied or if both are supplied and are not equal.
Attributes:
type (:obj:`str`): :tg-const:`telegram.constants.InlineQueryResultType.GIF`.
@@ -75,10 +103,14 @@ class InlineQueryResultGif(InlineQueryResult):
gif_width (:obj:`int`): Optional. Width of the GIF.
gif_height (:obj:`int`): Optional. Height of the GIF.
gif_duration (:obj:`int`): Optional. Duration of the GIF in seconds.
thumb_url (:obj:`str`): URL of the static (JPEG or GIF) or animated (MPEG4) thumbnail for
the result.
thumb_mime_type (:obj:`str`): Optional. MIME type of the thumbnail, must be one of
thumbnail_url (:obj:`str`): URL of the static (JPEG or GIF) or animated (MPEG4) thumbnail
for the result.
.. versionadded:: 20.2
thumbnail_mime_type (:obj:`str`): Optional. MIME type of the thumbnail, must be one of
``'image/jpeg'``, ``'image/gif'``, or ``'video/mp4'``. Defaults to ``'image/jpeg'``.
.. versionadded:: 20.2
title (:obj:`str`): Optional. Title for the result.
caption (:obj:`str`): Optional. Caption of the GIF file to be sent,
0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters
@@ -100,7 +132,7 @@ class InlineQueryResultGif(InlineQueryResult):
__slots__ = (
"reply_markup",
"gif_height",
"thumb_mime_type",
"thumbnail_mime_type",
"caption_entities",
"gif_width",
"title",
@@ -109,14 +141,17 @@ class InlineQueryResultGif(InlineQueryResult):
"gif_duration",
"input_message_content",
"gif_url",
"thumb_url",
"thumbnail_url",
)
def __init__(
self,
id: str, # pylint: disable=redefined-builtin
gif_url: str,
thumb_url: str,
# thumbnail_url is not optional in Telegram API, but we want to support thumb_url as well,
# so thumbnail_url may not be passed. We will raise ValueError manually if neither
# thumbnail_url nor thumb_url are passed
thumbnail_url: str = None,
gif_width: int = None,
gif_height: int = None,
title: str = None,
@@ -127,14 +162,29 @@ class InlineQueryResultGif(InlineQueryResult):
parse_mode: ODVInput[str] = DEFAULT_NONE,
thumb_mime_type: str = None,
caption_entities: Sequence[MessageEntity] = None,
thumbnail_mime_type: str = None,
# thumb_url is not optional in Telegram API, but it is here, along with thumbnail_url.
thumb_url: str = None,
*,
api_kwargs: JSONDict = None,
):
if not (thumbnail_url or thumb_url):
raise ValueError(
"You must pass either 'thumbnail_url' or 'thumb_url'. Note that 'thumb_url' is "
"deprecated."
)
# Required
super().__init__(InlineQueryResultType.GIF, id, api_kwargs=api_kwargs)
with self._unfrozen():
self.gif_url: str = gif_url
self.thumb_url: str = thumb_url
self.thumbnail_url: str = warn_about_deprecated_arg_return_new_arg(
deprecated_arg=thumb_url,
new_arg=thumbnail_url,
deprecated_arg_name="thumb_url",
new_arg_name="thumbnail_url",
bot_api_version="6.6",
)
# Optionals
self.gif_width: Optional[int] = gif_width
@@ -146,4 +196,40 @@ class InlineQueryResultGif(InlineQueryResult):
self.caption_entities: Tuple[MessageEntity, ...] = parse_sequence_arg(caption_entities)
self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup
self.input_message_content: Optional[InputMessageContent] = input_message_content
self.thumb_mime_type: Optional[str] = thumb_mime_type
self.thumbnail_mime_type: Optional[str] = warn_about_deprecated_arg_return_new_arg(
deprecated_arg=thumb_mime_type,
new_arg=thumbnail_mime_type,
deprecated_arg_name="thumb_mime_type",
new_arg_name="thumbnail_mime_type",
bot_api_version="6.6",
)
@property
def thumb_url(self) -> str:
""":obj:`str`: URL of the static (JPEG or GIF) or animated (MPEG4) thumbnail for the
result.
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail_url`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb_url",
new_attr_name="thumbnail_url",
bot_api_version="6.6",
)
return self.thumbnail_url
@property
def thumb_mime_type(self) -> Optional[str]:
""":obj:`str`: Optional. Optional. MIME type of the thumbnail, must be one of
``'image/jpeg'``, ``'image/gif'``, or ``'video/mp4'``. Defaults to ``'image/jpeg'``.
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail_mime_type`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb_mime_type",
new_attr_name="thumbnail_mime_type",
bot_api_version="6.6",
)
return self.thumbnail_mime_type
+100 -9
View File
@@ -24,6 +24,10 @@ from telegram import constants
from telegram._inline.inlinekeyboardmarkup import InlineKeyboardMarkup
from telegram._inline.inlinequeryresult import InlineQueryResult
from telegram._utils.types import JSONDict
from telegram._utils.warnings_transition import (
warn_about_deprecated_arg_return_new_arg,
warn_about_deprecated_attr_in_property,
)
if TYPE_CHECKING:
from telegram import InputMessageContent
@@ -63,9 +67,27 @@ class InlineQueryResultLocation(InlineQueryResult):
input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the
message to be sent instead of the location.
thumb_url (:obj:`str`, optional): Url of the thumbnail for the result.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail_url`.
thumb_width (:obj:`int`, optional): Thumbnail width.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail_width`.
thumb_height (:obj:`int`, optional): Thumbnail height.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail_height`.
thumbnail_url (:obj:`str`, optional): Url of the thumbnail for the result.
.. versionadded:: 20.2
thumbnail_width (:obj:`int`, optional): Thumbnail width.
.. versionadded:: 20.2
thumbnail_height (:obj:`int`, optional): Thumbnail height.
.. versionadded:: 20.2
Attributes:
type (:obj:`str`): :tg-const:`telegram.constants.InlineQueryResultType.LOCATION`.
id (:obj:`str`): Unique identifier for this result,
@@ -94,17 +116,23 @@ class InlineQueryResultLocation(InlineQueryResult):
to the message.
input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the
message to be sent instead of the location.
thumb_url (:obj:`str`): Optional. Url of the thumbnail for the result.
thumb_width (:obj:`int`): Optional. Thumbnail width.
thumb_height (:obj:`int`): Optional. Thumbnail height.
thumbnail_url (:obj:`str`): Optional. Url of the thumbnail for the result.
.. versionadded:: 20.2
thumbnail_width (:obj:`int`): Optional. Thumbnail width.
.. versionadded:: 20.2
thumbnail_height (:obj:`int`): Optional. Thumbnail height.
.. versionadded:: 20.2
"""
__slots__ = (
"longitude",
"reply_markup",
"thumb_width",
"thumb_height",
"thumbnail_width",
"thumbnail_height",
"heading",
"title",
"live_period",
@@ -112,7 +140,7 @@ class InlineQueryResultLocation(InlineQueryResult):
"input_message_content",
"latitude",
"horizontal_accuracy",
"thumb_url",
"thumbnail_url",
)
def __init__(
@@ -130,6 +158,9 @@ class InlineQueryResultLocation(InlineQueryResult):
horizontal_accuracy: float = None,
heading: int = None,
proximity_alert_radius: int = None,
thumbnail_url: str = None,
thumbnail_width: int = None,
thumbnail_height: int = None,
*,
api_kwargs: JSONDict = None,
):
@@ -144,15 +175,75 @@ class InlineQueryResultLocation(InlineQueryResult):
self.live_period: Optional[int] = live_period
self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup
self.input_message_content: Optional[InputMessageContent] = input_message_content
self.thumb_url: Optional[str] = thumb_url
self.thumb_width: Optional[int] = thumb_width
self.thumb_height: Optional[int] = thumb_height
self.thumbnail_url: Optional[str] = warn_about_deprecated_arg_return_new_arg(
deprecated_arg=thumb_url,
new_arg=thumbnail_url,
deprecated_arg_name="thumb_url",
new_arg_name="thumbnail_url",
bot_api_version="6.6",
)
self.thumbnail_width: Optional[int] = warn_about_deprecated_arg_return_new_arg(
deprecated_arg=thumb_width,
new_arg=thumbnail_width,
deprecated_arg_name="thumb_width",
new_arg_name="thumbnail_width",
bot_api_version="6.6",
)
self.thumbnail_height: Optional[int] = warn_about_deprecated_arg_return_new_arg(
deprecated_arg=thumb_height,
new_arg=thumbnail_height,
deprecated_arg_name="thumb_height",
new_arg_name="thumbnail_height",
bot_api_version="6.6",
)
self.horizontal_accuracy: Optional[float] = horizontal_accuracy
self.heading: Optional[int] = heading
self.proximity_alert_radius: Optional[int] = (
int(proximity_alert_radius) if proximity_alert_radius else None
)
@property
def thumb_url(self) -> Optional[str]:
""":obj:`str`: Optional. Url of the thumbnail for the result.
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail_url`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb_url",
new_attr_name="thumbnail_url",
bot_api_version="6.6",
)
return self.thumbnail_url
@property
def thumb_width(self) -> Optional[int]:
""":obj:`str`: Optional. Thumbnail width.
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail_width`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb_width",
new_attr_name="thumbnail_width",
bot_api_version="6.6",
)
return self.thumbnail_width
@property
def thumb_height(self) -> Optional[int]:
""":obj:`str`: Optional. Thumbnail height.
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail_height`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb_height",
new_attr_name="thumbnail_height",
bot_api_version="6.6",
)
return self.thumbnail_height
HORIZONTAL_ACCURACY: ClassVar[int] = constants.LocationLimit.HORIZONTAL_ACCURACY
""":const:`telegram.constants.LocationLimit.HORIZONTAL_ACCURACY`
+87 -11
View File
@@ -25,6 +25,10 @@ from telegram._messageentity import MessageEntity
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram._utils.types import JSONDict, ODVInput
from telegram._utils.warnings_transition import (
warn_about_deprecated_arg_return_new_arg,
warn_about_deprecated_attr_in_property,
)
from telegram.constants import InlineQueryResultType
if TYPE_CHECKING:
@@ -48,10 +52,20 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult):
mpeg4_width (:obj:`int`, optional): Video width.
mpeg4_height (:obj:`int`, optional): Video height.
mpeg4_duration (:obj:`int`, optional): Video duration in seconds.
thumb_url (:obj:`str`): URL of the static (JPEG or GIF) or animated (MPEG4) thumbnail for
the result.
thumb_mime_type (:obj:`str`): Optional. MIME type of the thumbnail, must be one of
thumbnail_url (:obj:`str`, optional): URL of the static (JPEG or GIF) or animated (MPEG4)
thumbnail for the result.
Warning:
The Bot API does **not** define this as an optional argument. It is formally
optional for backwards compatibility with the deprecated :paramref:`thumb_url`.
If you pass neither :paramref:`thumbnail_url` nor :paramref:`thumb_url`,
:class:`ValueError` will be raised.
.. versionadded:: 20.2
thumbnail_mime_type (:obj:`str`, optional): MIME type of the thumbnail, must be one of
``'image/jpeg'``, ``'image/gif'``, or ``'video/mp4'``. Defaults to ``'image/jpeg'``.
.. versionadded:: 20.2
title (:obj:`str`, optional): Title for the result.
caption (:obj:`str`, optional): Caption of the MPEG-4 file to be sent,
0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters
@@ -68,6 +82,10 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult):
input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the
message to be sent instead of the video animation.
Raises:
:class:`ValueError`: If neither :paramref:`thumbnail_url` nor :paramref:`thumb_url` is
supplied or if both are supplied and are not equal.
Attributes:
type (:obj:`str`): :tg-const:`telegram.constants.InlineQueryResultType.MPEG4GIF`.
id (:obj:`str`): Unique identifier for this result,
@@ -77,10 +95,14 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult):
mpeg4_width (:obj:`int`): Optional. Video width.
mpeg4_height (:obj:`int`): Optional. Video height.
mpeg4_duration (:obj:`int`): Optional. Video duration in seconds.
thumb_url (:obj:`str`): URL of the static (JPEG or GIF) or animated (MPEG4) thumbnail for
the result.
thumb_mime_type (:obj:`str`): Optional. MIME type of the thumbnail, must be one of
thumbnail_url (:obj:`str`): URL of the static (JPEG or GIF) or animated (MPEG4) thumbnail
for the result.
.. versionadded:: 20.2
thumbnail_mime_type (:obj:`str`): Optional. MIME type of the thumbnail, must be one of
``'image/jpeg'``, ``'image/gif'``, or ``'video/mp4'``. Defaults to ``'image/jpeg'``.
.. versionadded:: 20.2
title (:obj:`str`): Optional. Title for the result.
caption (:obj:`str`): Optional. Caption of the MPEG-4 file to be sent,
0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters
@@ -102,7 +124,7 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult):
__slots__ = (
"reply_markup",
"thumb_mime_type",
"thumbnail_mime_type",
"caption_entities",
"mpeg4_duration",
"mpeg4_width",
@@ -112,14 +134,17 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult):
"input_message_content",
"mpeg4_url",
"mpeg4_height",
"thumb_url",
"thumbnail_url",
)
def __init__(
self,
id: str, # pylint: disable=redefined-builtin
mpeg4_url: str,
thumb_url: str,
# thumbnail_url is not optional in Telegram API, but we want to support thumb_url as well,
# so thumbnail_url may not be passed. We will raise ValueError manually if neither
# thumbnail_url nor thumb_url are passed
thumbnail_url: str = None,
mpeg4_width: int = None,
mpeg4_height: int = None,
title: str = None,
@@ -130,14 +155,29 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult):
parse_mode: ODVInput[str] = DEFAULT_NONE,
thumb_mime_type: str = None,
caption_entities: Sequence[MessageEntity] = None,
thumbnail_mime_type: str = None,
# thumb_url is not optional in Telegram API, but it is here, along with thumbnail_url.
thumb_url: str = None,
*,
api_kwargs: JSONDict = None,
):
if not (thumbnail_url or thumb_url):
raise ValueError(
"You must pass either 'thumbnail_url' or 'thumb_url'. Note that 'thumb_url' is "
"deprecated."
)
# Required
super().__init__(InlineQueryResultType.MPEG4GIF, id, api_kwargs=api_kwargs)
with self._unfrozen():
self.mpeg4_url: str = mpeg4_url
self.thumb_url: str = thumb_url
self.thumbnail_url: str = warn_about_deprecated_arg_return_new_arg(
deprecated_arg=thumb_url,
new_arg=thumbnail_url,
deprecated_arg_name="thumb_url",
new_arg_name="thumbnail_url",
bot_api_version="6.6",
)
# Optional
self.mpeg4_width: Optional[int] = mpeg4_width
@@ -149,4 +189,40 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult):
self.caption_entities: Tuple[MessageEntity, ...] = parse_sequence_arg(caption_entities)
self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup
self.input_message_content: Optional[InputMessageContent] = input_message_content
self.thumb_mime_type: Optional[str] = thumb_mime_type
self.thumbnail_mime_type: Optional[str] = warn_about_deprecated_arg_return_new_arg(
deprecated_arg=thumb_mime_type,
new_arg=thumbnail_mime_type,
deprecated_arg_name="thumb_mime_type",
new_arg_name="thumbnail_mime_type",
bot_api_version="6.6",
)
@property
def thumb_url(self) -> str:
""":obj:`str`: URL of the static (JPEG or GIF) or animated (MPEG4) thumbnail for the
result.
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail_url`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb_url",
new_attr_name="thumbnail_url",
bot_api_version="6.6",
)
return self.thumbnail_url
@property
def thumb_mime_type(self) -> Optional[str]:
""":obj:`str`: Optional. Optional. MIME type of the thumbnail, must be one of
``'image/jpeg'``, ``'image/gif'``, or ``'video/mp4'``. Defaults to ``'image/jpeg'``.
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail_mime_type`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb_mime_type",
new_attr_name="thumbnail_mime_type",
bot_api_version="6.6",
)
return self.thumbnail_mime_type
+56 -5
View File
@@ -25,6 +25,10 @@ from telegram._messageentity import MessageEntity
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram._utils.types import JSONDict, ODVInput
from telegram._utils.warnings_transition import (
warn_about_deprecated_arg_return_new_arg,
warn_about_deprecated_attr_in_property,
)
from telegram.constants import InlineQueryResultType
if TYPE_CHECKING:
@@ -45,7 +49,15 @@ class InlineQueryResultPhoto(InlineQueryResult):
:tg-const:`telegram.InlineQueryResult.MAX_ID_LENGTH` Bytes.
photo_url (:obj:`str`): A valid URL of the photo. Photo must be in JPEG format. Photo size
must not exceed 5MB.
thumb_url (:obj:`str`): URL of the thumbnail for the photo.
thumbnail_url (:obj:`str`, optional): URL of the thumbnail for the photo.
Warning:
The Bot API does **not** define this as an optional argument. It is formally
optional for backwards compatibility with the deprecated :paramref:`thumb_url`.
If you pass neither :paramref:`thumbnail_url` nor :paramref:`thumb_url`,
:class:`ValueError` will be raised.
.. versionadded:: 20.2
photo_width (:obj:`int`, optional): Width of the photo.
photo_height (:obj:`int`, optional): Height of the photo.
title (:obj:`str`, optional): Title for the result.
@@ -63,6 +75,14 @@ class InlineQueryResultPhoto(InlineQueryResult):
to the message.
input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the
message to be sent instead of the photo.
thumb_url (:obj:`str`, optional): URL of the thumbnail for the photo.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail_url`.
Raises:
:class:`ValueError`: If neither :paramref:`thumbnail_url` nor :paramref:`thumb_url` is
supplied or if both are supplied and are not equal.
Attributes:
type (:obj:`str`): :tg-const:`telegram.constants.InlineQueryResultType.PHOTO`.
@@ -71,7 +91,7 @@ class InlineQueryResultPhoto(InlineQueryResult):
:tg-const:`telegram.InlineQueryResult.MAX_ID_LENGTH` Bytes.
photo_url (:obj:`str`): A valid URL of the photo. Photo must be in JPEG format. Photo size
must not exceed 5MB.
thumb_url (:obj:`str`): URL of the thumbnail for the photo.
thumbnail_url (:obj:`str`): URL of the thumbnail for the photo.
photo_width (:obj:`int`): Optional. Width of the photo.
photo_height (:obj:`int`): Optional. Height of the photo.
title (:obj:`str`): Optional. Title for the result.
@@ -104,14 +124,17 @@ class InlineQueryResultPhoto(InlineQueryResult):
"parse_mode",
"input_message_content",
"photo_height",
"thumb_url",
"thumbnail_url",
)
def __init__(
self,
id: str, # pylint: disable=redefined-builtin
photo_url: str,
thumb_url: str,
# thumbnail_url is not optional in Telegram API, but we want to support thumb_url as well,
# so thumbnail_url may not be passed. We will raise ValueError manually if neither
# thumbnail_url nor thumb_url are passed
thumbnail_url: str = None,
photo_width: int = None,
photo_height: int = None,
title: str = None,
@@ -121,14 +144,28 @@ class InlineQueryResultPhoto(InlineQueryResult):
input_message_content: "InputMessageContent" = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
caption_entities: Sequence[MessageEntity] = None,
# thumb_url is not optional in Telegram API, but it is here, along with thumbnail_url.
thumb_url: str = None,
*,
api_kwargs: JSONDict = None,
):
if not (thumbnail_url or thumb_url):
raise ValueError(
"You must pass either 'thumbnail_url' or 'thumb_url'. Note that 'thumb_url' is "
"deprecated."
)
# Required
super().__init__(InlineQueryResultType.PHOTO, id, api_kwargs=api_kwargs)
with self._unfrozen():
self.photo_url: str = photo_url
self.thumb_url: str = thumb_url
self.thumbnail_url: str = warn_about_deprecated_arg_return_new_arg(
deprecated_arg=thumb_url,
new_arg=thumbnail_url,
deprecated_arg_name="thumb_url",
new_arg_name="thumbnail_url",
bot_api_version="6.6",
)
# Optionals
self.photo_width: Optional[int] = photo_width
@@ -140,3 +177,17 @@ class InlineQueryResultPhoto(InlineQueryResult):
self.caption_entities: Tuple[MessageEntity, ...] = parse_sequence_arg(caption_entities)
self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup
self.input_message_content: Optional[InputMessageContent] = input_message_content
@property
def thumb_url(self) -> Optional[str]:
""":obj:`str`: URL of the thumbnail for the photo.
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail_url`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb_url",
new_attr_name="thumbnail_url",
bot_api_version="6.6",
)
return self.thumbnail_url
@@ -0,0 +1,117 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2023
# 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=redefined-builtin
"""This module contains the class that represent a Telegram InlineQueryResultsButton."""
from typing import TYPE_CHECKING, ClassVar, Optional
from telegram import constants
from telegram._telegramobject import TelegramObject
from telegram._utils.types import JSONDict
from telegram._webappinfo import WebAppInfo
if TYPE_CHECKING:
from telegram import Bot
class InlineQueryResultsButton(TelegramObject):
"""This object represents a button to be shown above inline query results. You **must** use
exactly one of the optional fields.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`text`, :attr:`web_app` and :attr:`start_parameter` are equal.
Args:
text (:obj:`str`): Label text on the button.
web_app (:class:`telegram.WebAppInfo`, optional): Description of the
`Web App <https://core.telegram.org/bots/webapps>`_ that will be launched when the
user presses the button. The Web App will be able to switch back to the inline mode
using the method
`switchInlineQuery <https://core.telegram.org/bots/webapps#initializing-web-apps>`_
inside the Web App.
start_parameter (:obj:`str`, optional): Deep-linking parameter for the
:guilabel:`/start` message sent to the bot when user presses the switch button.
:tg-const:`telegram.InlineQuery.MIN_SWITCH_PM_TEXT_LENGTH`-
:tg-const:`telegram.InlineQuery.MAX_SWITCH_PM_TEXT_LENGTH` characters,
only ``A-Z``, ``a-z``, ``0-9``, ``_`` and ``-`` are allowed.
Example:
An inline bot that sends YouTube videos can ask the user to connect the bot to
their YouTube account to adapt search results accordingly. To do this, it displays
a 'Connect your YouTube account' button above the results, or even before showing
any. The user presses the button, switches to a private chat with the bot and, in
doing so, passes a start parameter that instructs the bot to return an OAuth link.
Once done, the bot can offer a switch_inline button so that the user can easily
return to the chat where they wanted to use the bot's inline capabilities.
Attributes:
text (:obj:`str`): Label text on the button.
web_app (:class:`telegram.WebAppInfo`): Optional. Description of the
`Web App <https://core.telegram.org/bots/webapps>`_ that will be launched when the
user presses the button. The Web App will be able to switch back to the inline mode
using the method ``web_app_switch_inline_query`` inside the Web App.
start_parameter (:obj:`str`): Optional. Deep-linking parameter for the
:guilabel:`/start` message sent to the bot when user presses the switch button.
:tg-const:`telegram.InlineQuery.MIN_SWITCH_PM_TEXT_LENGTH`-
:tg-const:`telegram.InlineQuery.MAX_SWITCH_PM_TEXT_LENGTH` characters,
only ``A-Z``, ``a-z``, ``0-9``, ``_`` and ``-`` are allowed.
"""
__slots__ = ("text", "web_app", "start_parameter")
def __init__(
self,
text: str,
web_app: WebAppInfo = None,
start_parameter: str = None,
*,
api_kwargs: JSONDict = None,
):
super().__init__(api_kwargs=api_kwargs)
# Required
self.text: str = text
# Optional
self.web_app: Optional[WebAppInfo] = web_app
self.start_parameter: Optional[str] = start_parameter
self._id_attrs = (self.text, self.web_app, self.start_parameter)
self._freeze()
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["InlineQueryResultsButton"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
if not data:
return None
data["web_app"] = WebAppInfo.de_json(data.get("web_app"), bot)
return super().de_json(data=data, bot=bot)
MIN_START_PARAMETER_LENGTH: ClassVar[
int
] = constants.InlineQueryResultsButtonLimit.MIN_START_PARAMETER_LENGTH
""":const:`telegram.constants.InlineQueryResultsButtonLimit.MIN_START_PARAMETER_LENGTH`"""
MAX_START_PARAMETER_LENGTH: ClassVar[
int
] = constants.InlineQueryResultsButtonLimit.MAX_START_PARAMETER_LENGTH
""":const:`telegram.constants.InlineQueryResultsButtonLimit.MAX_START_PARAMETER_LENGTH`"""
+100 -9
View File
@@ -23,6 +23,10 @@ from typing import TYPE_CHECKING, Optional
from telegram._inline.inlinekeyboardmarkup import InlineKeyboardMarkup
from telegram._inline.inlinequeryresult import InlineQueryResult
from telegram._utils.types import JSONDict
from telegram._utils.warnings_transition import (
warn_about_deprecated_arg_return_new_arg,
warn_about_deprecated_attr_in_property,
)
from telegram.constants import InlineQueryResultType
if TYPE_CHECKING:
@@ -60,9 +64,27 @@ class InlineQueryResultVenue(InlineQueryResult):
input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the
message to be sent instead of the venue.
thumb_url (:obj:`str`, optional): Url of the thumbnail for the result.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail_url`.
thumb_width (:obj:`int`, optional): Thumbnail width.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail_width`.
thumb_height (:obj:`int`, optional): Thumbnail height.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail_height`.
thumbnail_url (:obj:`str`, optional): Url of the thumbnail for the result.
.. versionadded:: 20.2
thumbnail_width (:obj:`int`, optional): Thumbnail width.
.. versionadded:: 20.2
thumbnail_height (:obj:`int`, optional): Thumbnail height.
.. versionadded:: 20.2
Attributes:
type (:obj:`str`): :tg-const:`telegram.constants.InlineQueryResultType.VENUE`.
id (:obj:`str`): Unique identifier for this result,
@@ -84,9 +106,15 @@ class InlineQueryResultVenue(InlineQueryResult):
to the message.
input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the
message to be sent instead of the venue.
thumb_url (:obj:`str`): Optional. Url of the thumbnail for the result.
thumb_width (:obj:`int`): Optional. Thumbnail width.
thumb_height (:obj:`int`): Optional. Thumbnail height.
thumbnail_url (:obj:`str`): Optional. Url of the thumbnail for the result.
.. versionadded:: 20.2
thumbnail_width (:obj:`int`): Optional. Thumbnail width.
.. versionadded:: 20.2
thumbnail_height (:obj:`int`): Optional. Thumbnail height.
.. versionadded:: 20.2
"""
@@ -94,8 +122,8 @@ class InlineQueryResultVenue(InlineQueryResult):
"longitude",
"reply_markup",
"google_place_type",
"thumb_width",
"thumb_height",
"thumbnail_width",
"thumbnail_height",
"title",
"address",
"foursquare_id",
@@ -103,7 +131,7 @@ class InlineQueryResultVenue(InlineQueryResult):
"google_place_id",
"input_message_content",
"latitude",
"thumb_url",
"thumbnail_url",
)
def __init__(
@@ -122,6 +150,9 @@ class InlineQueryResultVenue(InlineQueryResult):
thumb_height: int = None,
google_place_id: str = None,
google_place_type: str = None,
thumbnail_url: str = None,
thumbnail_width: int = None,
thumbnail_height: int = None,
*,
api_kwargs: JSONDict = None,
):
@@ -140,6 +171,66 @@ class InlineQueryResultVenue(InlineQueryResult):
self.google_place_type: Optional[str] = google_place_type
self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup
self.input_message_content: Optional[InputMessageContent] = input_message_content
self.thumb_url: Optional[str] = thumb_url
self.thumb_width: Optional[int] = thumb_width
self.thumb_height: Optional[int] = thumb_height
self.thumbnail_url: Optional[str] = warn_about_deprecated_arg_return_new_arg(
deprecated_arg=thumb_url,
new_arg=thumbnail_url,
deprecated_arg_name="thumb_url",
new_arg_name="thumbnail_url",
bot_api_version="6.6",
)
self.thumbnail_width: Optional[int] = warn_about_deprecated_arg_return_new_arg(
deprecated_arg=thumb_width,
new_arg=thumbnail_width,
deprecated_arg_name="thumb_width",
new_arg_name="thumbnail_width",
bot_api_version="6.6",
)
self.thumbnail_height: Optional[int] = warn_about_deprecated_arg_return_new_arg(
deprecated_arg=thumb_height,
new_arg=thumbnail_height,
deprecated_arg_name="thumb_height",
new_arg_name="thumbnail_height",
bot_api_version="6.6",
)
@property
def thumb_url(self) -> Optional[str]:
""":obj:`str`: Optional. Url of the thumbnail for the result.
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail_url`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb_url",
new_attr_name="thumbnail_url",
bot_api_version="6.6",
)
return self.thumbnail_url
@property
def thumb_width(self) -> Optional[int]:
""":obj:`str`: Optional. Thumbnail width.
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail_width`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb_width",
new_attr_name="thumbnail_width",
bot_api_version="6.6",
)
return self.thumbnail_width
@property
def thumb_height(self) -> Optional[int]:
""":obj:`str`: Optional. Thumbnail height.
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail_height`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb_height",
new_attr_name="thumbnail_height",
bot_api_version="6.6",
)
return self.thumbnail_height
+75 -7
View File
@@ -25,6 +25,10 @@ from telegram._messageentity import MessageEntity
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram._utils.types import JSONDict, ODVInput
from telegram._utils.warnings_transition import (
warn_about_deprecated_arg_return_new_arg,
warn_about_deprecated_attr_in_property,
)
from telegram.constants import InlineQueryResultType
if TYPE_CHECKING:
@@ -50,8 +54,22 @@ class InlineQueryResultVideo(InlineQueryResult):
:tg-const:`telegram.InlineQueryResult.MAX_ID_LENGTH` Bytes.
video_url (:obj:`str`): A valid URL for the embedded video player or video file.
mime_type (:obj:`str`): Mime type of the content of video url, "text/html" or "video/mp4".
thumb_url (:obj:`str`): URL of the thumbnail (JPEG only) for the video.
title (:obj:`str`): Title for the result.
thumbnail_url (:obj:`str`, optional): URL of the thumbnail (JPEG only) for the video.
Warning:
The Bot API does **not** define this as an optional argument. It is formally
optional for backwards compatibility with the deprecated :paramref:`thumb_url`.
If you pass neither :paramref:`thumbnail_url` nor :paramref:`thumb_url`,
:class:`ValueError` will be raised.
.. versionadded:: 20.2
title (:obj:`str`, optional): Title for the result.
Warning:
The Bot API does **not** define this as an optional argument. It is formally
optional to ensure backwards compatibility of :paramref:`thumbnail_url` with the
deprecated :paramref:`thumb_url`, which required that :paramref:`thumbnail_url`
become optional. :class:`TypeError` will be raised if no ``title`` is passed.
caption (:obj:`str`, optional): Caption of the video to be sent,
0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters after entities
parsing.
@@ -71,6 +89,15 @@ class InlineQueryResultVideo(InlineQueryResult):
message to be sent instead of the video. This field is required if
``InlineQueryResultVideo`` is used to send an HTML-page as a result
(e.g., a YouTube video).
thumb_url (:obj:`str`, optional): URL of the thumbnail (JPEG only) for the video.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail_url`.
Raises:
:class:`ValueError`: If neither :paramref:`thumbnail_url` nor :paramref:`thumb_url` is
supplied or if both are supplied and are not equal.
:class:`TypeError`: If no :paramref:`title` is passed.
Attributes:
type (:obj:`str`): :tg-const:`telegram.constants.InlineQueryResultType.VIDEO`.
@@ -79,7 +106,9 @@ class InlineQueryResultVideo(InlineQueryResult):
:tg-const:`telegram.InlineQueryResult.MAX_ID_LENGTH` Bytes.
video_url (:obj:`str`): A valid URL for the embedded video player or video file.
mime_type (:obj:`str`): Mime type of the content of video url, "text/html" or "video/mp4".
thumb_url (:obj:`str`): URL of the thumbnail (JPEG only) for the video.
thumbnail_url (:obj:`str`): URL of the thumbnail (JPEG only) for the video.
.. versionadded:: 20.2
title (:obj:`str`): Title for the result.
caption (:obj:`str`): Optional. Caption of the video to be sent,
0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters after entities
@@ -119,7 +148,7 @@ class InlineQueryResultVideo(InlineQueryResult):
"input_message_content",
"video_height",
"video_width",
"thumb_url",
"thumbnail_url",
)
def __init__(
@@ -127,8 +156,13 @@ class InlineQueryResultVideo(InlineQueryResult):
id: str, # pylint: disable=redefined-builtin
video_url: str,
mime_type: str,
thumb_url: str,
title: str,
# thumbnail_url and title are not optional in Telegram API, but we want to support
# thumb_url as well, so thumbnail_url may not be passed if thumb_url is passed.
# We will raise ValueError manually if neither thumbnail_url nor thumb_url are passed.
thumbnail_url: str = None,
# title had to be made optional because of thumbnail_url. This is compensated by raising
# TypeError manually if title is not passed.
title: str = None,
caption: str = None,
video_width: int = None,
video_height: int = None,
@@ -138,15 +172,35 @@ class InlineQueryResultVideo(InlineQueryResult):
input_message_content: "InputMessageContent" = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
caption_entities: Sequence[MessageEntity] = None,
# thumb_url is not optional in Telegram API, but it is here, along with thumbnail_url.
thumb_url: str = None,
*,
api_kwargs: JSONDict = None,
):
if not (thumbnail_url or thumb_url):
raise ValueError(
"You must pass either 'thumbnail_url' or 'thumb_url'. Note that 'thumb_url' is "
"deprecated."
)
if title is None:
raise TypeError(
"InlineQueryResultVideo.__init__() missing a required argument: you forgot to pass"
" either 'title' or 'thumbnail_url'."
)
# Required
super().__init__(InlineQueryResultType.VIDEO, id, api_kwargs=api_kwargs)
with self._unfrozen():
self.video_url: str = video_url
self.mime_type: str = mime_type
self.thumb_url: str = thumb_url
self.thumbnail_url: str = warn_about_deprecated_arg_return_new_arg(
deprecated_arg=thumb_url,
new_arg=thumbnail_url,
deprecated_arg_name="thumb_url",
new_arg_name="thumbnail_url",
bot_api_version="6.6",
)
self.title: str = title
# Optional
@@ -159,3 +213,17 @@ class InlineQueryResultVideo(InlineQueryResult):
self.description: Optional[str] = description
self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup
self.input_message_content: Optional[InputMessageContent] = input_message_content
@property
def thumb_url(self) -> str:
""":obj:`str`: URL of the thumbnail (JPEG only) for the video.
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail_url`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb_url",
new_attr_name="thumbnail_url",
bot_api_version="6.6",
)
return self.thumbnail_url
+8
View File
@@ -34,6 +34,10 @@ class KeyboardButtonRequestUser(TelegramObject):
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`request_id` is equal.
.. seealso::
`Telegram Docs on requesting users \
<https://core.telegram.org/bots/features#chat-and-user-selection>`_
.. versionadded:: 20.1
Args:
@@ -87,6 +91,10 @@ class KeyboardButtonRequestChat(TelegramObject):
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`request_id` is equal.
.. seealso::
`Telegram Docs on requesting chats \
<https://core.telegram.org/bots/features#chat-and-user-selection>`_
.. versionadded:: 20.1
Args:
+1 -2
View File
@@ -93,8 +93,7 @@ class MenuButton(TelegramObject):
if cls is MenuButton and data.get("type") in _class_mapping:
return _class_mapping[data.pop("type")].de_json(data, bot=bot)
out = super().de_json(data=data, bot=bot)
return out
return super().de_json(data=data, bot=bot)
COMMANDS: ClassVar[str] = constants.MenuButtonType.COMMANDS
""":const:`telegram.constants.MenuButtonType.COMMANDS`"""
+187 -156
View File
@@ -19,7 +19,6 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Telegram Message."""
import datetime
import sys
from html import escape
from typing import TYPE_CHECKING, Dict, List, Optional, Sequence, Tuple, Union
@@ -57,9 +56,10 @@ from telegram._shared import ChatShared, UserShared
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.datetime import from_timestamp
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.defaultvalue import DEFAULT_NONE, DefaultValue
from telegram._utils.types import DVInput, FileInput, JSONDict, ODVInput, ReplyMarkup
from telegram._utils.warnings import warn
from telegram._videochat import (
VideoChatEnded,
VideoChatParticipantsInvited,
@@ -70,6 +70,7 @@ from telegram._webappdata import WebAppData
from telegram._writeaccessallowed import WriteAccessAllowed
from telegram.constants import MessageAttachmentType, ParseMode
from telegram.helpers import escape_markdown
from telegram.warnings import PTBDeprecationWarning
if TYPE_CHECKING:
from telegram import (
@@ -122,6 +123,9 @@ class Message(TelegramObject):
sent on behalf of a chat.
date (:class:`datetime.datetime`): Date the message was sent in Unix time. Converted to
:class:`datetime.datetime`.
.. versionchanged:: 20.3
|datetime_localization|
chat (:class:`telegram.Chat`): Conversation the message belongs to.
forward_from (:class:`telegram.User`, optional): For forwarded messages, sender of
the original message.
@@ -133,6 +137,9 @@ class Message(TelegramObject):
users who disallow adding a link to their account in forwarded messages.
forward_date (:class:`datetime.datetime`, optional): For forwarded messages, date the
original message was sent in Unix time. Converted to :class:`datetime.datetime`.
.. versionchanged:: 20.3
|datetime_localization|
is_automatic_forward (:obj:`bool`, optional): :obj:`True`, if the message is a channel
post that was automatically forwarded to the connected discussion group.
@@ -142,6 +149,9 @@ class Message(TelegramObject):
``reply_to_message`` fields even if it itself is a reply.
edit_date (:class:`datetime.datetime`, optional): Date the message was last edited in Unix
time. Converted to :class:`datetime.datetime`.
.. versionchanged:: 20.3
|datetime_localization|
has_protected_content (:obj:`bool`, optional): :obj:`True`, if the message can't be
forwarded.
@@ -339,6 +349,9 @@ class Message(TelegramObject):
sent on behalf of a chat.
date (:class:`datetime.datetime`): Date the message was sent in Unix time. Converted to
:class:`datetime.datetime`.
.. versionchanged:: 20.3
|datetime_localization|
chat (:class:`telegram.Chat`): Conversation the message belongs to.
forward_from (:class:`telegram.User`): Optional. For forwarded messages, sender of the
original message.
@@ -348,6 +361,9 @@ class Message(TelegramObject):
the original message in the channel.
forward_date (:class:`datetime.datetime`): Optional. For forwarded messages, date the
original message was sent in Unix time. Converted to :class:`datetime.datetime`.
.. versionchanged:: 20.3
|datetime_localization|
is_automatic_forward (:obj:`bool`): Optional. :obj:`True`, if the message is a channel
post that was automatically forwarded to the connected discussion group.
@@ -357,6 +373,9 @@ class Message(TelegramObject):
``reply_to_message`` fields even if it itself is a reply.
edit_date (:class:`datetime.datetime`): Optional. Date the message was last edited in Unix
time. Converted to :class:`datetime.datetime`.
.. versionchanged:: 20.3
|datetime_localization|
has_protected_content (:obj:`bool`): Optional. :obj:`True`, if the message can't be
forwarded.
@@ -561,8 +580,13 @@ class Message(TelegramObject):
.. versionadded:: 20.1
.. |custom_emoji_formatting_note| replace:: Custom emoji entities will currently be ignored
by this function. Instead, the supplied replacement for the emoji will be used.
.. |custom_emoji_formatting_note| replace:: Custom emoji entities will be ignored by this
function. Instead, the supplied replacement for the emoji will be used.
.. |custom_emoji_md1_deprecation| replace:: Since custom emoji entities are not supported by
:attr:`~telegram.constants.ParseMode.MARKDOWN`, this method will raise a
:exc:`ValueError` in future versions instead of falling back to the supplied replacement
for the emoji.
"""
# fmt: on
@@ -827,14 +851,20 @@ class Message(TelegramObject):
def link(self) -> Optional[str]:
""":obj:`str`: Convenience property. If the chat of the message is not
a private chat or normal group, returns a t.me link of the message.
.. versionchanged:: 20.3
For messages that are replies or part of a forum topic, the link now points
to the corresponding thread view.
"""
if self.chat.type not in [Chat.PRIVATE, Chat.GROUP]:
if self.chat.username:
to_link = self.chat.username
else:
# Get rid of leading -100 for supergroups
to_link = f"c/{str(self.chat.id)[4:]}"
return f"https://t.me/{to_link}/{self.message_id}"
# the else block gets rid of leading -100 for supergroups:
to_link = self.chat.username if self.chat.username else f"c/{str(self.chat.id)[4:]}"
baselink = f"https://t.me/{to_link}/{self.message_id}"
# adds the thread for topics and replies
if (self.is_topic_message and self.message_thread_id) or self.reply_to_message:
baselink = f"{baselink}?thread={self.message_thread_id}"
return baselink
return None
@classmethod
@@ -845,17 +875,20 @@ class Message(TelegramObject):
if not data:
return None
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["from_user"] = User.de_json(data.pop("from", None), bot)
data["sender_chat"] = Chat.de_json(data.get("sender_chat"), bot)
data["date"] = from_timestamp(data["date"])
data["date"] = from_timestamp(data["date"], tzinfo=loc_tzinfo)
data["chat"] = Chat.de_json(data.get("chat"), bot)
data["entities"] = MessageEntity.de_list(data.get("entities"), bot)
data["caption_entities"] = MessageEntity.de_list(data.get("caption_entities"), bot)
data["forward_from"] = User.de_json(data.get("forward_from"), bot)
data["forward_from_chat"] = Chat.de_json(data.get("forward_from_chat"), bot)
data["forward_date"] = from_timestamp(data.get("forward_date"))
data["forward_date"] = from_timestamp(data.get("forward_date"), tzinfo=loc_tzinfo)
data["reply_to_message"] = Message.de_json(data.get("reply_to_message"), bot)
data["edit_date"] = from_timestamp(data.get("edit_date"))
data["edit_date"] = from_timestamp(data.get("edit_date"), tzinfo=loc_tzinfo)
data["audio"] = Audio.de_json(data.get("audio"), bot)
data["document"] = Document.de_json(data.get("document"), bot)
data["animation"] = Animation.de_json(data.get("animation"), bot)
@@ -1374,6 +1407,7 @@ class Message(TelegramObject):
caption_entities: Sequence["MessageEntity"] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
thumbnail: FileInput = None,
*,
filename: str = None,
quote: bool = None,
@@ -1421,6 +1455,7 @@ class Message(TelegramObject):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
thumbnail=thumbnail,
)
async def reply_document(
@@ -1437,6 +1472,7 @@ class Message(TelegramObject):
caption_entities: Sequence["MessageEntity"] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
thumbnail: FileInput = None,
*,
filename: str = None,
quote: bool = None,
@@ -1482,6 +1518,7 @@ class Message(TelegramObject):
caption_entities=caption_entities,
protect_content=protect_content,
message_thread_id=message_thread_id,
thumbnail=thumbnail,
)
async def reply_animation(
@@ -1501,6 +1538,7 @@ class Message(TelegramObject):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
has_spoiler: bool = None,
thumbnail: FileInput = None,
*,
filename: str = None,
quote: bool = None,
@@ -1550,6 +1588,7 @@ class Message(TelegramObject):
protect_content=protect_content,
message_thread_id=message_thread_id,
has_spoiler=has_spoiler,
thumbnail=thumbnail,
)
async def reply_sticker(
@@ -1561,6 +1600,7 @@ class Message(TelegramObject):
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
emoji: str = None,
*,
quote: bool = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1599,6 +1639,7 @@ class Message(TelegramObject):
allow_sending_without_reply=allow_sending_without_reply,
protect_content=protect_content,
message_thread_id=message_thread_id,
emoji=emoji,
)
async def reply_video(
@@ -1619,6 +1660,7 @@ class Message(TelegramObject):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
has_spoiler: bool = None,
thumbnail: FileInput = None,
*,
filename: str = None,
quote: bool = None,
@@ -1668,6 +1710,7 @@ class Message(TelegramObject):
protect_content=protect_content,
message_thread_id=message_thread_id,
has_spoiler=has_spoiler,
thumbnail=thumbnail,
)
async def reply_video_note(
@@ -1682,6 +1725,7 @@ class Message(TelegramObject):
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
thumbnail: FileInput = None,
*,
filename: str = None,
quote: bool = None,
@@ -1726,6 +1770,7 @@ class Message(TelegramObject):
filename=filename,
protect_content=protect_content,
message_thread_id=message_thread_id,
thumbnail=thumbnail,
)
async def reply_voice(
@@ -3125,10 +3170,6 @@ class Message(TelegramObject):
if not self.text:
raise RuntimeError("This Message has no 'text'.")
# Is it a narrow build, if so we don't need to convert
if sys.maxunicode == 0xFFFF:
return self.text[entity.offset : entity.offset + entity.length]
entity_text = self.text.encode("utf-16-le")
entity_text = entity_text[entity.offset * 2 : (entity.offset + entity.length) * 2]
return entity_text.decode("utf-16-le")
@@ -3155,10 +3196,6 @@ class Message(TelegramObject):
if not self.caption:
raise RuntimeError("This Message has no 'caption'.")
# Is it a narrow build, if so we don't need to convert
if sys.maxunicode == 0xFFFF:
return self.caption[entity.offset : entity.offset + entity.length]
entity_text = self.caption.encode("utf-16-le")
entity_text = entity_text[entity.offset * 2 : (entity.offset + entity.length) * 2]
return entity_text.decode("utf-16-le")
@@ -3235,8 +3272,7 @@ class Message(TelegramObject):
if message_text is None:
return None
if sys.maxunicode != 0xFFFF:
message_text = message_text.encode("utf-16-le") # type: ignore
message_text = message_text.encode("utf-16-le") # type: ignore
html_text = ""
last_offset = 0
@@ -3256,78 +3292,70 @@ class Message(TelegramObject):
parsed_entities.extend(list(nested_entities.keys()))
orig_text = text
text = escape(text)
escaped_text = escape(text)
if nested_entities:
text = Message._parse_html(
escaped_text = Message._parse_html(
orig_text, nested_entities, urled=urled, offset=entity.offset
)
if entity.type == MessageEntity.TEXT_LINK:
insert = f'<a href="{entity.url}">{text}</a>'
insert = f'<a href="{entity.url}">{escaped_text}</a>'
elif entity.type == MessageEntity.TEXT_MENTION and entity.user:
insert = f'<a href="tg://user?id={entity.user.id}">{text}</a>'
insert = f'<a href="tg://user?id={entity.user.id}">{escaped_text}</a>'
elif entity.type == MessageEntity.URL and urled:
insert = f'<a href="{text}">{text}</a>'
insert = f'<a href="{escaped_text}">{escaped_text}</a>'
elif entity.type == MessageEntity.BOLD:
insert = f"<b>{text}</b>"
insert = f"<b>{escaped_text}</b>"
elif entity.type == MessageEntity.ITALIC:
insert = f"<i>{text}</i>"
insert = f"<i>{escaped_text}</i>"
elif entity.type == MessageEntity.CODE:
insert = f"<code>{text}</code>"
insert = f"<code>{escaped_text}</code>"
elif entity.type == MessageEntity.PRE:
if entity.language:
insert = f'<pre><code class="{entity.language}">{text}</code></pre>'
insert = (
f'<pre><code class="{entity.language}">{escaped_text}</code></pre>'
)
else:
insert = f"<pre>{text}</pre>"
insert = f"<pre>{escaped_text}</pre>"
elif entity.type == MessageEntity.UNDERLINE:
insert = f"<u>{text}</u>"
insert = f"<u>{escaped_text}</u>"
elif entity.type == MessageEntity.STRIKETHROUGH:
insert = f"<s>{text}</s>"
insert = f"<s>{escaped_text}</s>"
elif entity.type == MessageEntity.SPOILER:
insert = f'<span class="tg-spoiler">{text}</span>'
insert = f'<span class="tg-spoiler">{escaped_text}</span>'
elif entity.type == MessageEntity.CUSTOM_EMOJI:
insert = (
f'<tg-emoji emoji-id="{entity.custom_emoji_id}">{escaped_text}</tg-emoji>'
)
else:
insert = text
insert = escaped_text
if offset == 0:
if sys.maxunicode == 0xFFFF:
html_text += (
escape(message_text[last_offset : entity.offset - offset]) + insert
)
else:
html_text += (
escape(
message_text[ # type: ignore
last_offset * 2 : (entity.offset - offset) * 2
].decode("utf-16-le")
)
+ insert
)
else:
if sys.maxunicode == 0xFFFF:
html_text += message_text[last_offset : entity.offset - offset] + insert
else:
html_text += (
html_text += (
escape(
message_text[ # type: ignore
last_offset * 2 : (entity.offset - offset) * 2
].decode("utf-16-le")
+ insert
)
+ insert
)
else:
html_text += (
message_text[ # type: ignore
last_offset * 2 : (entity.offset - offset) * 2
].decode("utf-16-le")
+ insert
)
last_offset = entity.offset - offset + entity.length
if offset == 0:
if sys.maxunicode == 0xFFFF:
html_text += escape(message_text[last_offset:])
else:
html_text += escape(
message_text[last_offset * 2 :].decode("utf-16-le") # type: ignore
)
html_text += escape(
message_text[last_offset * 2 :].decode("utf-16-le") # type: ignore
)
else:
if sys.maxunicode == 0xFFFF:
html_text += message_text[last_offset:]
else:
html_text += message_text[last_offset * 2 :].decode("utf-16-le") # type: ignore
html_text += message_text[last_offset * 2 :].decode("utf-16-le") # type: ignore
return html_text
@@ -3338,12 +3366,12 @@ class Message(TelegramObject):
Use this if you want to retrieve the message text with the entities formatted as HTML in
the same way the original message was formatted.
Note:
|custom_emoji_formatting_note|
.. versionchanged:: 13.10
Spoiler entities are now formatted as HTML.
.. versionchanged:: 20.3
Custom emoji entities are now supported.
Returns:
:obj:`str`: Message text with entities formatted as HTML.
@@ -3357,12 +3385,12 @@ class Message(TelegramObject):
Use this if you want to retrieve the message text with the entities formatted as HTML.
This also formats :attr:`telegram.MessageEntity.URL` as a hyperlink.
Note:
|custom_emoji_formatting_note|
.. versionchanged:: 13.10
Spoiler entities are now formatted as HTML.
.. versionchanged:: 20.3
Custom emoji entities are now supported.
Returns:
:obj:`str`: Message text with entities formatted as HTML.
@@ -3377,12 +3405,12 @@ class Message(TelegramObject):
Use this if you want to retrieve the message caption with the caption entities formatted as
HTML in the same way the original message was formatted.
Note:
|custom_emoji_formatting_note|
.. versionchanged:: 13.10
Spoiler entities are now formatted as HTML.
.. versionchanged:: 20.3
Custom emoji entities are now supported.
Returns:
:obj:`str`: Message caption with caption entities formatted as HTML.
"""
@@ -3396,12 +3424,12 @@ class Message(TelegramObject):
Use this if you want to retrieve the message caption with the caption entities formatted as
HTML. This also formats :attr:`telegram.MessageEntity.URL` as a hyperlink.
Note:
|custom_emoji_formatting_note|
.. versionchanged:: 13.10
Spoiler entities are now formatted as HTML.
.. versionchanged:: 20.3
Custom emoji entities are now supported.
Returns:
:obj:`str`: Message caption with caption entities formatted as HTML.
"""
@@ -3420,8 +3448,7 @@ class Message(TelegramObject):
if message_text is None:
return None
if sys.maxunicode != 0xFFFF:
message_text = message_text.encode("utf-16-le") # type: ignore
message_text = message_text.encode("utf-16-le") # type: ignore
markdown_text = ""
last_offset = 0
@@ -3440,8 +3467,7 @@ class Message(TelegramObject):
}
parsed_entities.extend(list(nested_entities.keys()))
orig_text = text
text = escape_markdown(text, version=version)
escaped_text = escape_markdown(text, version=version)
if nested_entities:
if version < 2:
@@ -3449,8 +3475,8 @@ class Message(TelegramObject):
"Nested entities are not supported for Markdown version 1"
)
text = Message._parse_markdown(
orig_text,
escaped_text = Message._parse_markdown(
text,
nested_entities,
urled=urled,
offset=entity.offset,
@@ -3465,105 +3491,98 @@ class Message(TelegramObject):
url = escape_markdown(
entity.url, version=version, entity_type=MessageEntity.TEXT_LINK
)
insert = f"[{text}]({url})"
insert = f"[{escaped_text}]({url})"
elif entity.type == MessageEntity.TEXT_MENTION and entity.user:
insert = f"[{text}](tg://user?id={entity.user.id})"
insert = f"[{escaped_text}](tg://user?id={entity.user.id})"
elif entity.type == MessageEntity.URL and urled:
if version == 1:
link = orig_text
else:
link = text
insert = f"[{link}]({orig_text})"
link = text if version == 1 else escaped_text
insert = f"[{link}]({text})"
elif entity.type == MessageEntity.BOLD:
insert = f"*{text}*"
insert = f"*{escaped_text}*"
elif entity.type == MessageEntity.ITALIC:
insert = f"_{text}_"
insert = f"_{escaped_text}_"
elif entity.type == MessageEntity.CODE:
# Monospace needs special escaping. Also can't have entities nested within
insert = f"`{escape_markdown(orig_text, version, MessageEntity.CODE)}`"
insert = f"`{escape_markdown(text, version, MessageEntity.CODE)}`"
elif entity.type == MessageEntity.PRE:
# Monospace needs special escaping. Also can't have entities nested within
code = escape_markdown(
orig_text, version=version, entity_type=MessageEntity.PRE
)
code = escape_markdown(text, version=version, entity_type=MessageEntity.PRE)
if entity.language:
prefix = f"```{entity.language}\n"
elif code.startswith("\\"):
prefix = "```"
else:
if code.startswith("\\"):
prefix = "```"
else:
prefix = "```\n"
prefix = "```\n"
insert = f"{prefix}{code}```"
elif entity.type == MessageEntity.UNDERLINE:
if version == 1:
raise ValueError(
"Underline entities are not supported for Markdown version 1"
)
insert = f"__{text}__"
insert = f"__{escaped_text}__"
elif entity.type == MessageEntity.STRIKETHROUGH:
if version == 1:
raise ValueError(
"Strikethrough entities are not supported for Markdown version 1"
)
insert = f"~{text}~"
insert = f"~{escaped_text}~"
elif entity.type == MessageEntity.SPOILER:
if version == 1:
raise ValueError(
"Spoiler entities are not supported for Markdown version 1"
)
insert = f"||{text}||"
insert = f"||{escaped_text}||"
elif entity.type == MessageEntity.CUSTOM_EMOJI:
if version == 1:
# this ensures compatibility to previous PTB versions
insert = escaped_text
warn(
"Custom emoji entities are not supported for Markdown version 1. "
"Future version of PTB will raise a ValueError instead of falling "
"back to the alternative standard emoji.",
stacklevel=3,
category=PTBDeprecationWarning,
)
else:
# This should never be needed because ids are numeric but the documentation
# specifically mentions it so here we are
custom_emoji_id = escape_markdown(
entity.custom_emoji_id,
version=version,
entity_type=MessageEntity.CUSTOM_EMOJI,
)
insert = f"![{escaped_text}](tg://emoji?id={custom_emoji_id})"
else:
insert = text
insert = escaped_text
if offset == 0:
if sys.maxunicode == 0xFFFF:
markdown_text += (
escape_markdown(
message_text[last_offset : entity.offset - offset], version=version
)
+ insert
)
else:
markdown_text += (
escape_markdown(
message_text[ # type: ignore
last_offset * 2 : (entity.offset - offset) * 2
].decode("utf-16-le"),
version=version,
)
+ insert
)
else:
if sys.maxunicode == 0xFFFF:
markdown_text += (
message_text[last_offset : entity.offset - offset] + insert
)
else:
markdown_text += (
markdown_text += (
escape_markdown(
message_text[ # type: ignore
last_offset * 2 : (entity.offset - offset) * 2
].decode("utf-16-le")
+ insert
].decode("utf-16-le"),
version=version,
)
+ insert
)
else:
markdown_text += (
message_text[ # type: ignore
last_offset * 2 : (entity.offset - offset) * 2
].decode("utf-16-le")
+ insert
)
last_offset = entity.offset - offset + entity.length
if offset == 0:
if sys.maxunicode == 0xFFFF:
markdown_text += escape_markdown(message_text[last_offset:], version=version)
else:
markdown_text += escape_markdown(
message_text[last_offset * 2 :].decode("utf-16-le"), # type: ignore
version=version,
)
markdown_text += escape_markdown(
message_text[last_offset * 2 :].decode("utf-16-le"), # type: ignore
version=version,
)
else:
if sys.maxunicode == 0xFFFF:
markdown_text += message_text[last_offset:]
else:
markdown_text += message_text[last_offset * 2 :].decode( # type: ignore
"utf-16-le"
)
markdown_text += message_text[last_offset * 2 :].decode("utf-16-le") # type: ignore
return markdown_text
@@ -3582,6 +3601,9 @@ class Message(TelegramObject):
* |custom_emoji_formatting_note|
.. deprecated:: 20.3
|custom_emoji_md1_deprecation|
Returns:
:obj:`str`: Message text with entities formatted as Markdown.
@@ -3600,12 +3622,12 @@ class Message(TelegramObject):
Use this if you want to retrieve the message text with the entities formatted as Markdown
in the same way the original message was formatted.
Note:
|custom_emoji_formatting_note|
.. versionchanged:: 13.10
Spoiler entities are now formatted as Markdown V2.
.. versionchanged:: 20.3
Custom emoji entities are now supported.
Returns:
:obj:`str`: Message text with entities formatted as Markdown.
"""
@@ -3626,6 +3648,9 @@ class Message(TelegramObject):
* |custom_emoji_formatting_note|
.. deprecated:: 20.3
|custom_emoji_md1_deprecation|
Returns:
:obj:`str`: Message text with entities formatted as Markdown.
@@ -3644,12 +3669,12 @@ class Message(TelegramObject):
Use this if you want to retrieve the message text with the entities formatted as Markdown.
This also formats :attr:`telegram.MessageEntity.URL` as a hyperlink.
Note:
|custom_emoji_formatting_note|
.. versionchanged:: 13.10
Spoiler entities are now formatted as Markdown V2.
.. versionchanged:: 20.3
Custom emoji entities are now supported.
Returns:
:obj:`str`: Message text with entities formatted as Markdown.
"""
@@ -3670,6 +3695,9 @@ class Message(TelegramObject):
* |custom_emoji_formatting_note|
.. deprecated:: 20.3
|custom_emoji_md1_deprecation|
Returns:
:obj:`str`: Message caption with caption entities formatted as Markdown.
@@ -3688,12 +3716,12 @@ class Message(TelegramObject):
Use this if you want to retrieve the message caption with the caption entities formatted as
Markdown in the same way the original message was formatted.
Note:
|custom_emoji_formatting_note|
.. versionchanged:: 13.10
Spoiler entities are now formatted as Markdown V2.
.. versionchanged:: 20.3
Custom emoji entities are now supported.
Returns:
:obj:`str`: Message caption with caption entities formatted as Markdown.
"""
@@ -3716,6 +3744,9 @@ class Message(TelegramObject):
* |custom_emoji_formatting_note|
.. deprecated:: 20.3
|custom_emoji_md1_deprecation|
Returns:
:obj:`str`: Message caption with caption entities formatted as Markdown.
@@ -3734,12 +3765,12 @@ class Message(TelegramObject):
Use this if you want to retrieve the message caption with the caption entities formatted as
Markdown. This also formats :attr:`telegram.MessageEntity.URL` as a hyperlink.
Note:
|custom_emoji_formatting_note|
.. versionchanged:: 13.10
Spoiler entities are now formatted as Markdown V2.
.. versionchanged:: 20.3
Custom emoji entities are now supported.
Returns:
:obj:`str`: Message caption with caption entities formatted as Markdown.
"""
+2 -2
View File
@@ -180,7 +180,7 @@ class PassportElementErrorFiles(PassportElementError):
with self._unfrozen():
self.file_hashes: str = file_hashes
self._id_attrs = (self.source, self.type, self.message) + tuple(file_hashes)
self._id_attrs = (self.source, self.type, self.message, *tuple(file_hashes))
class PassportElementErrorFrontSide(PassportElementError):
@@ -362,7 +362,7 @@ class PassportElementErrorTranslationFiles(PassportElementError):
with self._unfrozen():
self.file_hashes: str = file_hashes
self._id_attrs = (self.source, self.type, self.message) + tuple(file_hashes)
self._id_attrs = (self.source, self.type, self.message, *tuple(file_hashes))
class PassportElementErrorUnspecified(PassportElementError):
+1 -1
View File
@@ -24,7 +24,7 @@ from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
from telegram import LabeledPrice # noqa
from telegram import LabeledPrice
class ShippingOption(TelegramObject):
+11 -6
View File
@@ -18,7 +18,6 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Telegram Poll."""
import datetime
import sys
from typing import TYPE_CHECKING, ClassVar, Dict, List, Optional, Sequence, Tuple
from telegram import constants
@@ -27,7 +26,7 @@ from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils import enum
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.datetime import from_timestamp
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
@@ -174,6 +173,9 @@ class Poll(TelegramObject):
close_date (:obj:`datetime.datetime`, optional): Point in time (Unix timestamp) when the
poll will be automatically closed. Converted to :obj:`datetime.datetime`.
.. versionchanged:: 20.3
|datetime_localization|
Attributes:
id (:obj:`str`): Unique poll identifier.
question (:obj:`str`): Poll question, :tg-const:`telegram.Poll.MIN_QUESTION_LENGTH`-
@@ -207,6 +209,9 @@ class Poll(TelegramObject):
close_date (:obj:`datetime.datetime`): Optional. Point in time when the poll will be
automatically closed.
.. versionchanged:: 20.3
|datetime_localization|
"""
__slots__ = (
@@ -272,9 +277,12 @@ class Poll(TelegramObject):
if not data:
return None
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["options"] = [PollOption.de_json(option, bot) for option in data["options"]]
data["explanation_entities"] = MessageEntity.de_list(data.get("explanation_entities"), bot)
data["close_date"] = from_timestamp(data.get("close_date"))
data["close_date"] = from_timestamp(data.get("close_date"), tzinfo=loc_tzinfo)
return super().de_json(data=data, bot=bot)
@@ -300,9 +308,6 @@ class Poll(TelegramObject):
if not self.explanation:
raise RuntimeError("This Poll has no 'explanation'.")
# Is it a narrow build, if so we don't need to convert
if sys.maxunicode == 0xFFFF:
return self.explanation[entity.offset : entity.offset + entity.length]
entity_text = self.explanation.encode("utf-16-le")
entity_text = entity_text[entity.offset * 2 : (entity.offset + entity.length) * 2]
+100
View File
@@ -0,0 +1,100 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2023
# 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
"""This module contains a class that represents a Telegram SwitchInlineQueryChosenChat."""
from typing import Optional
from telegram._telegramobject import TelegramObject
from telegram._utils.types import JSONDict
class SwitchInlineQueryChosenChat(TelegramObject):
"""
This object represents an inline button that switches the current user to inline mode in a
chosen chat, with an optional default inline query.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`query`, :attr:`allow_user_chats`, :attr:`allow_bot_chats`,
:attr:`allow_group_chats`, and :attr:`allow_channel_chats` are equal.
.. versionadded:: 20.3
Caution:
The PTB team has discovered that you must pass at least one of
:paramref:`allow_user_chats`, :paramref:`allow_bot_chats`, :paramref:`allow_group_chats`,
or :paramref:`allow_channel_chats` to Telegram. Otherwise, an error will be raised.
Args:
query (:obj:`str`, optional): The default inline query to be inserted in the input field.
If left empty, only the bot's username will be inserted.
allow_user_chats (:obj:`bool`, optional): Pass :obj:`True`, if private chats with users
can be chosen.
allow_bot_chats (:obj:`bool`, optional): Pass :obj:`True`, if private chats with bots can
be chosen.
allow_group_chats (:obj:`bool`, optional): Pass :obj:`True`, if group and supergroup chats
can be chosen.
allow_channel_chats (:obj:`bool`, optional): Pass :obj:`True`, if channel chats can be
chosen.
Attributes:
query (:obj:`str`): Optional. The default inline query to be inserted in the input field.
If left empty, only the bot's username will be inserted.
allow_user_chats (:obj:`bool`): Optional. :obj:`True`, if private chats with users can be
chosen.
allow_bot_chats (:obj:`bool`): Optional. :obj:`True`, if private chats with bots can be
chosen.
allow_group_chats (:obj:`bool`): Optional. :obj:`True`, if group and supergroup chats can
be chosen.
allow_channel_chats (:obj:`bool`): Optional. :obj:`True`, if channel chats can be chosen.
"""
__slots__ = (
"query",
"allow_user_chats",
"allow_bot_chats",
"allow_group_chats",
"allow_channel_chats",
)
def __init__(
self,
query: str = None,
allow_user_chats: bool = None,
allow_bot_chats: bool = None,
allow_group_chats: bool = None,
allow_channel_chats: bool = None,
*,
api_kwargs: JSONDict = None,
):
super().__init__(api_kwargs=api_kwargs)
# Optional
self.query: Optional[str] = query
self.allow_user_chats: Optional[bool] = allow_user_chats
self.allow_bot_chats: Optional[bool] = allow_bot_chats
self.allow_group_chats: Optional[bool] = allow_group_chats
self.allow_channel_chats: Optional[bool] = allow_channel_chats
self._id_attrs = (
self.query,
self.allow_user_chats,
self.allow_bot_chats,
self.allow_group_chats,
self.allow_channel_chats,
)
self._freeze()
+9 -3
View File
@@ -58,6 +58,12 @@ class TelegramObject:
The :mod:`pickle` and :func:`~copy.deepcopy` behavior of objects of this type are defined by
:meth:`__getstate__`, :meth:`__setstate__` and :meth:`__deepcopy__`.
Tip:
Objects of this type can be serialized via Python's :mod:`pickle` module and pickled
objects from one version of PTB are usually loadable in future versions. However, we can
not guarantee that this compatibility will always be provided. At least a manual one-time
conversion of the data may be needed on major updates of the library.
.. versionchanged:: 20.0
* Removed argument and attribute ``bot`` for several subclasses. Use
@@ -281,7 +287,7 @@ class TelegramObject:
# Make sure that we have a `_bot` attribute. This is necessary, since __getstate__ omits
# this as Bots are not pickable.
setattr(self, "_bot", None)
self._bot = None
# get api_kwargs first because we may need to add entries to it (see try-except below)
api_kwargs = cast(Dict[str, object], state.pop("api_kwargs", {}))
@@ -299,7 +305,7 @@ class TelegramObject:
# and then set the rest as MappingProxyType attribute. Converting to MappingProxyType
# is necessary, since __getstate__ converts it to a dict as MPT is not pickable.
self._apply_api_kwargs(api_kwargs)
setattr(self, "api_kwargs", MappingProxyType(api_kwargs))
self.api_kwargs = MappingProxyType(api_kwargs)
# Apply freezing if necessary
# we .get(…) the setting for backwards compatibility with objects that were pickled
@@ -328,7 +334,7 @@ class TelegramObject:
result = cls.__new__(cls) # create a new instance
memodict[id(self)] = result # save the id of the object in the dict
setattr(result, "_frozen", False) # unfreeze the new object for setting the attributes
result._frozen = False # unfreeze the new object for setting the attributes
# now we set the attributes in the deepcopied object
for k in self._get_attrs_names(include_private=True):
+2 -2
View File
@@ -34,7 +34,7 @@ from telegram._telegramobject import TelegramObject
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
from telegram import Bot, Chat, User # noqa
from telegram import Bot, Chat, User
class Update(TelegramObject):
@@ -46,7 +46,7 @@ class Update(TelegramObject):
Note:
At most one of the optional parameters can be present in any given update.
.. seealso:: :wiki:`Your First Bot <Extensions--Your-first-Bot>`
.. seealso:: :wiki:`Your First Bot <Extensions---Your-first-Bot>`
Args:
update_id (:obj:`int`): The update's unique identifier. Update identifiers start from a
+12
View File
@@ -553,6 +553,7 @@ class User(TelegramObject):
caption_entities: Sequence["MessageEntity"] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
thumbnail: FileInput = None,
*,
filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -596,6 +597,7 @@ class User(TelegramObject):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
thumbnail=thumbnail,
)
async def send_chat_action(
@@ -748,6 +750,7 @@ class User(TelegramObject):
caption_entities: Sequence["MessageEntity"] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
thumbnail: FileInput = None,
*,
filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -783,6 +786,7 @@ class User(TelegramObject):
pool_timeout=pool_timeout,
parse_mode=parse_mode,
thumb=thumb,
thumbnail=thumbnail,
api_kwargs=api_kwargs,
disable_content_type_detection=disable_content_type_detection,
allow_sending_without_reply=allow_sending_without_reply,
@@ -1005,6 +1009,7 @@ class User(TelegramObject):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
has_spoiler: bool = None,
thumbnail: FileInput = None,
*,
filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1049,6 +1054,7 @@ class User(TelegramObject):
protect_content=protect_content,
message_thread_id=message_thread_id,
has_spoiler=has_spoiler,
thumbnail=thumbnail,
)
async def send_sticker(
@@ -1060,6 +1066,7 @@ class User(TelegramObject):
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
emoji: str = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
@@ -1094,6 +1101,7 @@ class User(TelegramObject):
allow_sending_without_reply=allow_sending_without_reply,
protect_content=protect_content,
message_thread_id=message_thread_id,
emoji=emoji,
)
async def send_video(
@@ -1114,6 +1122,7 @@ class User(TelegramObject):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
has_spoiler: bool = None,
thumbnail: FileInput = None,
*,
filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1152,6 +1161,7 @@ class User(TelegramObject):
parse_mode=parse_mode,
supports_streaming=supports_streaming,
thumb=thumb,
thumbnail=thumbnail,
api_kwargs=api_kwargs,
allow_sending_without_reply=allow_sending_without_reply,
caption_entities=caption_entities,
@@ -1234,6 +1244,7 @@ class User(TelegramObject):
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
thumbnail: FileInput = None,
*,
filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1273,6 +1284,7 @@ class User(TelegramObject):
filename=filename,
protect_content=protect_content,
message_thread_id=message_thread_id,
thumbnail=thumbnail,
)
async def send_voice(
+23 -6
View File
@@ -29,7 +29,10 @@ Warning:
"""
import datetime as dtm # skipcq: PYL-W0406
import time
from typing import Optional, Union
from typing import TYPE_CHECKING, Optional, Union
if TYPE_CHECKING:
from telegram import Bot
# pytz is only available if it was installed as dependency of APScheduler, so we make a little
# workaround here
@@ -162,7 +165,10 @@ def to_timestamp(
)
def from_timestamp(unixtime: Optional[int], tzinfo: dtm.tzinfo = UTC) -> Optional[dtm.datetime]:
def from_timestamp(
unixtime: Optional[int],
tzinfo: Optional[dtm.tzinfo] = None,
) -> Optional[dtm.datetime]:
"""
Converts an (integer) unix timestamp to a timezone aware datetime object.
:obj:`None` s are left alone (i.e. ``from_timestamp(None)`` is :obj:`None`).
@@ -170,7 +176,8 @@ def from_timestamp(unixtime: Optional[int], tzinfo: dtm.tzinfo = UTC) -> Optiona
Args:
unixtime (:obj:`int`): Integer POSIX timestamp.
tzinfo (:obj:`datetime.tzinfo`, optional): The timezone to which the timestamp is to be
converted to. Defaults to UTC.
converted to. Defaults to :obj:`None`, in which case the returned datetime object will
be timezone aware and in UTC.
Returns:
Timezone aware equivalent :obj:`datetime.datetime` value if :paramref:`unixtime` is not
@@ -179,9 +186,19 @@ def from_timestamp(unixtime: Optional[int], tzinfo: dtm.tzinfo = UTC) -> Optiona
if unixtime is None:
return None
if tzinfo is not None:
return dtm.datetime.fromtimestamp(unixtime, tz=tzinfo)
return dtm.datetime.utcfromtimestamp(unixtime)
return dtm.datetime.fromtimestamp(unixtime, tz=UTC if tzinfo is None else tzinfo)
def extract_tzinfo_from_defaults(bot: "Bot") -> Union[dtm.tzinfo, None]:
"""
Extracts the timezone info from the default values of the bot.
If the bot has no default values, :obj:`None` is returned.
"""
# We don't use `ininstance(bot, ExtBot)` here so that this works
# in `python-telegram-bot-raw` as well
if hasattr(bot, "defaults") and bot.defaults:
return bot.defaults.tzinfo
return None
def _datetime_to_float_timestamp(dt_obj: dtm.datetime) -> float:
+49
View File
@@ -0,0 +1,49 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2023
# 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 helper functions related to logging.
Warning:
Contents of this module are intended to be used internally by the library and *not* by the
user. Changes to this module are not considered breaking changes and may not be documented in
the changelog.
"""
import logging
def get_logger(file_name: str, class_name: str = None) -> logging.Logger:
"""Returns a logger with an appropriate name.
Use as follows::
logger = get_logger(__name__)
If for example `__name__` is `telegram.ext._updater`, the logger will be named
`telegram.ext.Updater`. If `class_name` is passed, this will result in
`telegram.ext.<class_name>`. Useful e.g. for CamelCase class names.
If the file name points to a utils module, the logger name will simply be `telegram(.ext)`.
Returns:
:class:`logging.Logger`: The logger.
"""
parts = file_name.split("_")
if parts[1].startswith("utils") and class_name is None:
name = parts[0].rstrip(".")
else:
name = f"{parts[0]}{class_name or parts[1].capitalize()}"
return logging.getLogger(name)
+1 -1
View File
@@ -60,7 +60,7 @@ DVInput = Union["DefaultValue[DVValueType]", DVValueType, "DefaultValue[None]"]
as ``Union[DefaultValue[type], type, DefaultValue[None]]``."""
RT = TypeVar("RT")
SCT = Union[RT, Collection[RT]]
SCT = Union[RT, Collection[RT]] # pylint: disable=invalid-name
"""Single instance or collection of instances."""
ReplyMarkup = Union[
+107
View File
@@ -0,0 +1,107 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2023
# 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 functionality used for transition warnings issued by this library.
It was created to prevent circular imports that would be caused by creating the warnings
inside warnings.py.
.. versionadded:: 20.2
"""
from typing import Any, Callable, Type
from telegram._utils.warnings import warn
from telegram.warnings import PTBDeprecationWarning
# Narrower type hints will cause linting errors and/or circular imports.
# We'll use `Any` here and put type hints in the calling code.
def warn_about_deprecated_arg_return_new_arg(
deprecated_arg: Any,
new_arg: Any,
deprecated_arg_name: str,
new_arg_name: str,
bot_api_version: str,
stacklevel: int = 2,
warn_callback: Callable[[str, Type[Warning], int], None] = warn,
) -> Any:
"""A helper function for the transition in API when argument is renamed.
Checks the `deprecated_arg` and `new_arg` objects; warns if non-None `deprecated_arg` object
was passed. Returns `new_arg` object (either the one originally passed by the user or the one
that user passed as `deprecated_arg`).
Raises `ValueError` if both `deprecated_arg` and `new_arg` objects were passed, and they are
different.
"""
if deprecated_arg and new_arg and deprecated_arg != new_arg:
raise ValueError(
f"You passed different entities as '{deprecated_arg_name}' and '{new_arg_name}'. "
f"The parameter '{deprecated_arg_name}' was renamed to '{new_arg_name}' in Bot API "
f"{bot_api_version}. We recommend using '{new_arg_name}' instead of "
f"'{deprecated_arg_name}'."
)
if deprecated_arg:
warn_callback(
f"Bot API {bot_api_version} renamed the argument '{deprecated_arg_name}' to "
f"'{new_arg_name}'.",
PTBDeprecationWarning,
stacklevel + 1,
)
return deprecated_arg
return new_arg
def warn_about_deprecated_attr_in_property(
deprecated_attr_name: str,
new_attr_name: str,
bot_api_version: str,
stacklevel: int = 2,
) -> None:
"""A helper function for the transition in API when attribute is renamed. Call from properties.
The properties replace deprecated attributes in classes and issue these deprecation warnings.
"""
warn(
f"Bot API {bot_api_version} renamed the attribute '{deprecated_attr_name}' to "
f"'{new_attr_name}'.",
PTBDeprecationWarning,
stacklevel=stacklevel + 1,
)
def warn_about_thumb_return_thumbnail(
deprecated_arg: Any,
new_arg: Any,
stacklevel: int = 2,
warn_callback: Callable[[str, Type[Warning], int], None] = warn,
) -> Any:
"""A helper function to warn about using a deprecated 'thumb' argument and return it or the
new 'thumbnail' argument, introduced in API 6.6.
"""
return warn_about_deprecated_arg_return_new_arg(
deprecated_arg=deprecated_arg,
new_arg=new_arg,
warn_callback=warn_callback,
deprecated_arg_name="thumb",
new_arg_name="thumbnail",
bot_api_version="6.6",
stacklevel=stacklevel + 1,
)
+1 -1
View File
@@ -50,7 +50,7 @@ class Version(NamedTuple):
return version
__version_info__ = Version(major=20, minor=1, micro=0, releaselevel="final", serial=0)
__version_info__ = Version(major=20, minor=3, micro=0, releaselevel="final", serial=0)
__version__ = str(__version_info__)
# # SETUP.PY MARKER
+11 -2
View File
@@ -23,7 +23,7 @@ from typing import TYPE_CHECKING, Optional, Sequence, Tuple
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.datetime import from_timestamp
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
@@ -149,10 +149,16 @@ class VideoChatScheduled(TelegramObject):
Args:
start_date (:obj:`datetime.datetime`): Point in time (Unix timestamp) when the video
chat is supposed to be started by a chat administrator
.. versionchanged:: 20.3
|datetime_localization|
Attributes:
start_date (:obj:`datetime.datetime`): Point in time (Unix timestamp) when the video
chat is supposed to be started by a chat administrator
.. versionchanged:: 20.3
|datetime_localization|
"""
__slots__ = ("start_date",)
@@ -178,6 +184,9 @@ class VideoChatScheduled(TelegramObject):
if not data:
return None
data["start_date"] = from_timestamp(data["start_date"])
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["start_date"] = from_timestamp(data["start_date"], tzinfo=loc_tzinfo)
return super().de_json(data=data, bot=bot)
+28 -11
View File
@@ -21,7 +21,7 @@ from typing import TYPE_CHECKING, Optional, Sequence, Tuple
from telegram._telegramobject import TelegramObject
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.datetime import from_timestamp
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
@@ -49,8 +49,11 @@ class WebhookInfo(TelegramObject):
webhook certificate checks.
pending_update_count (:obj:`int`): Number of updates awaiting delivery.
ip_address (:obj:`str`, optional): Currently used webhook IP address.
last_error_date (:obj:`int`, optional): Unix time for the most recent error that happened
when trying to deliver an update via webhook.
last_error_date (:class:`datetime.datetime`): Optional. Datetime for the most recent
error that happened when trying to deliver an update via webhook.
.. versionchanged:: 20.3
|datetime_localization|
last_error_message (:obj:`str`, optional): Error message in human-readable format for the
most recent error that happened when trying to deliver an update via webhook.
max_connections (:obj:`int`, optional): Maximum allowed number of simultaneous HTTPS
@@ -62,18 +65,25 @@ class WebhookInfo(TelegramObject):
.. versionchanged:: 20.0
|sequenceclassargs|
last_synchronization_error_date (:obj:`int`, optional): Unix time of the most recent error
that happened when trying to synchronize available updates with Telegram datacenters.
last_synchronization_error_date (:class:`datetime.datetime`, optional): Datetime of the
most recent error that happened when trying to synchronize available updates with
Telegram datacenters.
.. versionadded:: 20.0
.. versionchanged:: 20.3
|datetime_localization|
Attributes:
url (:obj:`str`): Webhook URL, may be empty if webhook is not set up.
has_custom_certificate (:obj:`bool`): :obj:`True`, if a custom certificate was provided for
webhook certificate checks.
pending_update_count (:obj:`int`): Number of updates awaiting delivery.
ip_address (:obj:`str`): Optional. Currently used webhook IP address.
last_error_date (:obj:`int`): Optional. Unix time for the most recent error that happened
when trying to deliver an update via webhook.
last_error_date (:class:`datetime.datetime`): Optional. Datetime for the most recent
error that happened when trying to deliver an update via webhook.
.. versionchanged:: 20.3
|datetime_localization|
last_error_message (:obj:`str`): Optional. Error message in human-readable format for the
most recent error that happened when trying to deliver an update via webhook.
max_connections (:obj:`int`): Optional. Maximum allowed number of simultaneous HTTPS
@@ -86,10 +96,14 @@ class WebhookInfo(TelegramObject):
* |tupleclassattrs|
* |alwaystuple|
last_synchronization_error_date (:obj:`int`): Optional. Unix time of the most recent error
that happened when trying to synchronize available updates with Telegram datacenters.
last_synchronization_error_date (:class:`datetime.datetime`, optional): Datetime of the
most recent error that happened when trying to synchronize available updates with
Telegram datacenters.
.. versionadded:: 20.0
.. versionchanged:: 20.3
|datetime_localization|
"""
__slots__ = (
@@ -154,9 +168,12 @@ class WebhookInfo(TelegramObject):
if not data:
return None
data["last_error_date"] = from_timestamp(data.get("last_error_date"))
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["last_error_date"] = from_timestamp(data.get("last_error_date"), tzinfo=loc_tzinfo)
data["last_synchronization_error_date"] = from_timestamp(
data.get("last_synchronization_error_date")
data.get("last_synchronization_error_date"), tzinfo=loc_tzinfo
)
return super().de_json(data=data, bot=bot)
+18 -4
View File
@@ -17,21 +17,35 @@
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains objects related to the write access allowed service message."""
from typing import Optional
from telegram._telegramobject import TelegramObject
from telegram._utils.types import JSONDict
class WriteAccessAllowed(TelegramObject):
"""
This object represents a service message about a user allowing a bot added to the attachment
menu to write messages. Currently holds no information.
This object represents a service message about a user allowing a bot to write messages after
adding the bot to the attachment menu or launching a Web App from a link.
.. versionadded:: 20.0
Args:
web_app_name (:obj:`str`, optional): Name of the Web App which was launched from a link.
.. versionadded:: 20.3
Attributes:
web_app_name (:obj:`str`): Optional. Name of the Web App which was launched from a link.
.. versionadded:: 20.3
"""
__slots__ = ()
__slots__ = ("web_app_name",)
def __init__(self, *, api_kwargs: JSONDict = None):
def __init__(self, web_app_name: str = None, *, api_kwargs: JSONDict = None):
super().__init__(api_kwargs=api_kwargs)
self.web_app_name: Optional[str] = web_app_name
self._freeze()
+164 -4
View File
@@ -36,6 +36,8 @@ __all__ = [
"BOT_API_VERSION_INFO",
"BotCommandLimit",
"BotCommandScopeType",
"BotDescriptionLimit",
"BotNameLimit",
"CallbackQueryLimit",
"ChatAction",
"ChatID",
@@ -56,6 +58,7 @@ __all__ = [
"InlineKeyboardMarkupLimit",
"InlineQueryLimit",
"InlineQueryResultLimit",
"InlineQueryResultsButtonLimit",
"InlineQueryResultType",
"InputMediaType",
"InvoiceLimit",
@@ -73,7 +76,9 @@ __all__ = [
"PollType",
"ReplyLimit",
"SUPPORTED_WEBHOOK_PORTS",
"StickerFormat",
"StickerLimit",
"StickerSetLimit",
"StickerType",
"WebhookLimit",
"UpdateType",
@@ -111,7 +116,7 @@ class _BotAPIVersion(NamedTuple):
#: :data:`telegram.__bot_api_version_info__`.
#:
#: .. versionadded:: 20.0
BOT_API_VERSION_INFO = _BotAPIVersion(major=6, minor=5)
BOT_API_VERSION_INFO = _BotAPIVersion(major=6, minor=7)
#: :obj:`str`: Telegram Bot API
#: version supported by this version of `python-telegram-bot`. Also available as
#: :data:`telegram.__bot_api_version__`.
@@ -184,6 +189,43 @@ class BotCommandScopeType(StringEnum):
""":obj:`str`: The type of :class:`telegram.BotCommandScopeChatMember`."""
class BotDescriptionLimit(IntEnum):
"""This enum contains limitations for the methods :meth:`telegram.Bot.set_my_description` and
:meth:`telegram.Bot.set_my_short_description`. The enum members of this enumeration are
instances of :class:`int` and can be treated as such.
.. versionadded:: 20.2
"""
__slots__ = ()
MAX_DESCRIPTION_LENGTH = 512
""":obj:`int`: Maximum length for the parameter
:paramref:`~telegram.Bot.set_my_description.description` of
:meth:`telegram.Bot.set_my_description`
"""
MAX_SHORT_DESCRIPTION_LENGTH = 120
""":obj:`int`: Maximum length for the parameter
:paramref:`~telegram.Bot.set_my_short_description.short_description` of
:meth:`telegram.Bot.set_my_short_description`
"""
class BotNameLimit(IntEnum):
"""This enum contains limitations for the methods :meth:`telegram.Bot.set_my_name`.
The enum members of this enumeration are instances of :class:`int` and can be treated as such.
.. versionadded:: 20.3
"""
__slots__ = ()
MAX_NAME_LENGTH = 64
""":obj:`int`: Maximum length for the parameter :paramref:`~telegram.Bot.set_my_name.name` of
:meth:`telegram.Bot.set_my_name`
"""
class CallbackQueryLimit(IntEnum):
"""This enum contains limitations for :class:`telegram.CallbackQuery`/
:meth:`telegram.Bot.answer_callback_query`. The enum members of this enumeration are instances
@@ -710,11 +752,19 @@ class InlineQueryLimit(IntEnum):
MIN_SWITCH_PM_TEXT_LENGTH = 1
""":obj:`int`: Minimum number of characters in a :obj:`str` passed as the
:paramref:`~telegram.Bot.answer_inline_query.switch_pm_parameter` parameter of
:meth:`telegram.Bot.answer_inline_query`."""
:meth:`telegram.Bot.answer_inline_query`.
.. deprecated:: 20.3
Deprecated in favor of :attr:`InlineQueryResultsButtonLimit.MIN_START_PARAMETER_LENGTH`.
"""
MAX_SWITCH_PM_TEXT_LENGTH = 64
""":obj:`int`: Maximum number of characters in a :obj:`str` passed as the
:paramref:`~telegram.Bot.answer_inline_query.switch_pm_parameter` parameter of
:meth:`telegram.Bot.answer_inline_query`."""
:meth:`telegram.Bot.answer_inline_query`.
.. deprecated:: 20.3
Deprecated in favor of :attr:`InlineQueryResultsButtonLimit.MAX_START_PARAMETER_LENGTH`.
"""
class InlineQueryResultLimit(IntEnum):
@@ -738,6 +788,26 @@ class InlineQueryResultLimit(IntEnum):
"""
class InlineQueryResultsButtonLimit(IntEnum):
"""This enum contains limitations for :class:`telegram.InlineQueryResultsButton`.
The enum members of this enumeration are instances of :class:`int` and can be treated as such.
.. versionadded:: 20.3
"""
__slots__ = ()
MIN_START_PARAMETER_LENGTH = InlineQueryLimit.MIN_SWITCH_PM_TEXT_LENGTH
""":obj:`int`: Minimum number of characters in a :obj:`str` passed as the
:paramref:`~telegram.InlineQueryResultsButton.start_parameter` parameter of
:meth:`telegram.InlineQueryResultsButton`."""
MAX_START_PARAMETER_LENGTH = InlineQueryLimit.MAX_SWITCH_PM_TEXT_LENGTH
""":obj:`int`: Maximum number of characters in a :obj:`str` passed as the
:paramref:`~telegram.InlineQueryResultsButton.start_parameter` parameter of
:meth:`telegram.InlineQueryResultsButton`."""
class InlineQueryResultType(StringEnum):
"""This enum contains the available types of :class:`telegram.InlineQueryResult`. The enum
members of this enumeration are instances of :class:`str` and can be treated as such.
@@ -1243,8 +1313,26 @@ class ReplyLimit(IntEnum):
"""
class StickerFormat(StringEnum):
"""This enum contains the available formats of :class:`telegram.Sticker` in the set. The enum
members of this enumeration are instances of :class:`str` and can be treated as such.
.. versionadded:: 20.2
"""
__slots__ = ()
STATIC = "static"
""":obj:`str`: Static sticker."""
ANIMATED = "animated"
""":obj:`str`: Animated sticker."""
VIDEO = "video"
""":obj:`str`: Video sticker."""
class StickerLimit(IntEnum):
"""This enum contains limitations for :meth:`telegram.Bot.create_new_sticker_set`.
"""This enum contains limitations for various sticker methods, such as
:meth:`telegram.Bot.create_new_sticker_set`.
The enum members of this enumeration are instances of :class:`int` and can be treated as such.
.. versionadded:: 20.0
@@ -1264,6 +1352,78 @@ class StickerLimit(IntEnum):
:paramref:`~telegram.Bot.create_new_sticker_set.title` parameter of
:meth:`telegram.Bot.create_new_sticker_set`.
"""
MIN_STICKER_EMOJI = 1
""":obj:`int`: Minimum number of emojis associated with a sticker, passed as the
:paramref:`~telegram.Bot.setStickerEmojiList.emoji_list` parameter of
:meth:`telegram.Bot.set_sticker_emoji_list`.
.. versionadded:: 20.2
"""
MAX_STICKER_EMOJI = 20
""":obj:`int`: Maximum number of emojis associated with a sticker, passed as the
:paramref:`~telegram.Bot.setStickerEmojiList.emoji_list` parameter of
:meth:`telegram.Bot.set_sticker_emoji_list`.
.. versionadded:: 20.2
"""
MAX_SEARCH_KEYWORDS = 20
""":obj:`int`: Maximum number of search keywords for a sticker, passed as the
:paramref:`~telegram.Bot.set_sticker_keywords.keywords` parameter of
:meth:`telegram.Bot.set_sticker_keywords`.
.. versionadded:: 20.2
"""
MAX_KEYWORD_LENGTH = 64
""":obj:`int`: Maximum number of characters in a search keyword for a sticker, for each item in
:paramref:`~telegram.Bot.set_sticker_keywords.keywords` sequence of
:meth:`telegram.Bot.set_sticker_keywords`.
.. versionadded:: 20.2
"""
class StickerSetLimit(IntEnum):
"""This enum contains limitations for various sticker set methods, such as
:meth:`telegram.Bot.create_new_sticker_set` and :meth:`telegram.Bot.add_sticker_to_set`.
The enum members of this enumeration are instances of :class:`int` and can be treated as such.
.. versionadded:: 20.2
"""
__slots__ = ()
MIN_INITIAL_STICKERS = 1
""":obj:`int`: Minimum number of stickers needed to create a sticker set, passed as the
:paramref:`~telegram.Bot.create_new_sticker_set.stickers` parameter of
:meth:`telegram.Bot.create_new_sticker_set`.
"""
MAX_INITIAL_STICKERS = 50
""":obj:`int`: Maximum number of stickers allowed while creating a sticker set, passed as the
:paramref:`~telegram.Bot.create_new_sticker_set.stickers` parameter of
:meth:`telegram.Bot.create_new_sticker_set`.
"""
MAX_EMOJI_STICKERS = 200
""":obj:`int`: Maximum number of stickers allowed in an emoji sticker set, as given in
:meth:`telegram.Bot.add_sticker_to_set`.
"""
MAX_ANIMATED_STICKERS = 50
""":obj:`int`: Maximum number of stickers allowed in an animated or video sticker set, as given
in :meth:`telegram.Bot.add_sticker_to_set`.
"""
MAX_STATIC_STICKERS = 120
""":obj:`int`: Maximum number of stickers allowed in a static sticker set, as given in
:meth:`telegram.Bot.add_sticker_to_set`.
"""
MAX_STATIC_THUMBNAIL_SIZE = 128
""":obj:`int`: Maximum size of the thumbnail if it is a **.WEBP** or **.PNG** in kilobytes,
as given in :meth:`telegram.Bot.set_sticker_set_thumbnail`."""
MAX_ANIMATED_THUMBNAIL_SIZE = 32
""":obj:`int`: Maximum size of the thumbnail if it is a **.TGS** or **.WEBM** in kilobytes,
as given in :meth:`telegram.Bot.set_sticker_set_thumbnail`."""
STATIC_THUMB_DIMENSIONS = 100
""":obj:`int`: Exact height and width of the thumbnail if it is a **.WEBP** or **.PNG** in
pixels, as given in :meth:`telegram.Bot.set_sticker_set_thumbnail`."""
class StickerType(StringEnum):
+7 -5
View File
@@ -48,17 +48,19 @@ def _lstrip_str(in_s: str, lstr: str) -> str:
:obj:`str`: The stripped string.
"""
if in_s.startswith(lstr):
res = in_s[len(lstr) :]
else:
res = in_s
return res
return in_s[len(lstr) :] if in_s.startswith(lstr) else in_s
class TelegramError(Exception):
"""
Base class for Telegram errors.
Tip:
Objects of this type can be serialized via Python's :mod:`pickle` module and pickled
objects from one version of PTB are usually loadable in future versions. However, we can
not guarantee that this compatibility will always be provided. At least a manual one-time
conversion of the data may be needed on major updates of the library.
.. seealso:: :wiki:`Exceptions, Warnings and Logging <Exceptions%2C-Warnings-and-Logging>`
"""
+9 -9
View File
@@ -21,7 +21,6 @@ library.
"""
import asyncio
import contextlib
import logging
import sys
from typing import Any, AsyncIterator, Callable, Coroutine, Dict, List, Optional, Union
@@ -32,6 +31,7 @@ try:
except ImportError:
AIO_LIMITER_AVAILABLE = False
from telegram._utils.logging import get_logger
from telegram._utils.types import JSONDict
from telegram.error import RetryAfter
from telegram.ext._baseratelimiter import BaseRateLimiter
@@ -48,6 +48,9 @@ else:
yield None
_LOGGER = get_logger(__name__, class_name="AIORateLimiter")
class AIORateLimiter(BaseRateLimiter[int]):
"""
Implementation of :class:`~telegram.ext.BaseRateLimiter` using the library
@@ -118,7 +121,6 @@ class AIORateLimiter(BaseRateLimiter[int]):
"_group_limiters",
"_group_max_rate",
"_group_time_period",
"_logger",
"_max_retries",
"_retry_after_event",
)
@@ -152,7 +154,6 @@ class AIORateLimiter(BaseRateLimiter[int]):
self._group_limiters: Dict[Union[str, int], AsyncLimiter] = {}
self._max_retries: int = max_retries
self._logger = logging.getLogger(__name__)
self._retry_after_event = asyncio.Event()
self._retry_after_event.set()
@@ -203,7 +204,7 @@ class AIORateLimiter(BaseRateLimiter[int]):
return await callback(*args, **kwargs)
# mypy doesn't understand that the last run of the for loop raises an exception
async def process_request( # type: ignore[return]
async def process_request(
self,
callback: Callable[..., Coroutine[Any, Any, Union[bool, JSONDict, List[JSONDict]]]],
args: Any,
@@ -232,10 +233,8 @@ class AIORateLimiter(BaseRateLimiter[int]):
chat = True
# In case user passes integer chat id as string
try:
with contextlib.suppress(ValueError, TypeError):
chat_id = int(chat_id)
except (ValueError, TypeError):
pass
if (isinstance(chat_id, int) and chat_id < 0) or isinstance(chat_id, str):
# string chat_id only works for channels and supergroups
@@ -249,16 +248,17 @@ class AIORateLimiter(BaseRateLimiter[int]):
)
except RetryAfter as exc:
if i == max_retries:
self._logger.exception(
_LOGGER.exception(
"Rate limit hit after maximum of %d retries", max_retries, exc_info=exc
)
raise exc
sleep = exc.retry_after + 0.1
self._logger.info("Rate limit hit. Retrying after %f seconds", sleep)
_LOGGER.info("Rate limit hit. Retrying after %f seconds", sleep)
# Make sure we don't allow other requests to be processed
self._retry_after_event.clear()
await asyncio.sleep(sleep)
finally:
# Allow other requests to be processed
self._retry_after_event.set()
return None # type: ignore[return-value]
+113 -59
View File
@@ -18,9 +18,9 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains the Application class."""
import asyncio
import contextlib
import inspect
import itertools
import logging
import platform
import signal
from collections import defaultdict
@@ -31,10 +31,12 @@ from typing import (
TYPE_CHECKING,
Any,
AsyncContextManager,
Awaitable,
Callable,
Coroutine,
DefaultDict,
Dict,
Generator,
Generic,
List,
Mapping,
@@ -50,7 +52,8 @@ from typing import (
from telegram._update import Update
from telegram._utils.defaultvalue import DEFAULT_NONE, DEFAULT_TRUE, DefaultValue
from telegram._utils.types import DVType, ODVInput
from telegram._utils.logging import get_logger
from telegram._utils.types import SCT, DVType, ODVInput
from telegram._utils.warnings import warn
from telegram.error import TelegramError
from telegram.ext._basepersistence import BasePersistence
@@ -71,10 +74,10 @@ if TYPE_CHECKING:
DEFAULT_GROUP: int = 0
_AppType = TypeVar("_AppType", bound="Application") # pylint: disable=invalid-name
_RT = TypeVar("_RT")
_STOP_SIGNAL = object()
_DEFAULT_0 = DefaultValue(0)
_logger = logging.getLogger(__name__)
_LOGGER = get_logger(__name__)
class ApplicationHandlerStop(Exception):
@@ -136,7 +139,7 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
Examples:
:any:`Echo Bot <examples.echobot>`
.. seealso:: :wiki:`Your First Bot <Extensions--Your-first-Bot>`,
.. seealso:: :wiki:`Your First Bot <Extensions---Your-first-Bot>`,
:wiki:`Architecture Overview <Architecture>`
.. versionchanged:: 20.0
@@ -390,7 +393,7 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
:meth:`shutdown`
"""
if self._initialized:
_logger.debug("This Application is already initialized.")
_LOGGER.debug("This Application is already initialized.")
return
await self.bot.initialize()
@@ -440,7 +443,7 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
raise RuntimeError("This Application is still running!")
if not self._initialized:
_logger.debug("This Application is already shut down. Returning.")
_LOGGER.debug("This Application is already shut down. Returning.")
return
await self.bot.shutdown()
@@ -448,10 +451,10 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
await self.updater.shutdown()
if self.persistence:
_logger.debug("Updating & flushing persistence before shutdown")
_LOGGER.debug("Updating & flushing persistence before shutdown")
await self.update_persistence()
await self.persistence.flush()
_logger.debug("Updated and flushed persistence")
_LOGGER.debug("Updated and flushed persistence")
self._initialized = False
@@ -554,18 +557,18 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
# TODO: Add this once we drop py3.7
# name=f'Application:{self.bot.id}:persistence_updater'
)
_logger.debug("Loop for updating persistence started")
_LOGGER.debug("Loop for updating persistence started")
if self._job_queue:
await self._job_queue.start() # type: ignore[union-attr]
_logger.debug("JobQueue started")
_LOGGER.debug("JobQueue started")
self.__update_fetcher_task = asyncio.create_task(
self._update_fetcher(),
# TODO: Add this once we drop py3.7
# name=f'Application:{self.bot.id}:update_fetcher'
)
_logger.info("Application started")
_LOGGER.info("Application started")
except Exception as exc:
self._running = False
@@ -598,32 +601,32 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
raise RuntimeError("This Application is not running!")
self._running = False
_logger.info("Application is stopping. This might take a moment.")
_LOGGER.info("Application is stopping. This might take a moment.")
# Stop listening for new updates and handle all pending ones
await self.update_queue.put(_STOP_SIGNAL)
_logger.debug("Waiting for update_queue to join")
_LOGGER.debug("Waiting for update_queue to join")
await self.update_queue.join()
if self.__update_fetcher_task:
await self.__update_fetcher_task
_logger.debug("Application stopped fetching of updates.")
_LOGGER.debug("Application stopped fetching of updates.")
if self._job_queue:
_logger.debug("Waiting for running jobs to finish")
_LOGGER.debug("Waiting for running jobs to finish")
await self._job_queue.stop(wait=True) # type: ignore[union-attr]
_logger.debug("JobQueue stopped")
_LOGGER.debug("JobQueue stopped")
_logger.debug("Waiting for `create_task` calls to be processed")
_LOGGER.debug("Waiting for `create_task` calls to be processed")
await asyncio.gather(*self.__create_task_tasks, return_exceptions=True)
# Make sure that this is the *last* step of stopping the application!
if self.persistence and self.__update_persistence_task:
_logger.debug("Waiting for persistence loop to finish")
_LOGGER.debug("Waiting for persistence loop to finish")
self.__update_persistence_event.set()
await self.__update_persistence_task
self.__update_persistence_event.clear()
_logger.info("Application.stop() complete")
_LOGGER.info("Application.stop() complete")
def run_polling(
self,
@@ -934,7 +937,9 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
loop.close()
def create_task(
self, coroutine: Coroutine[Any, Any, RT], update: object = None
self,
coroutine: Union[Generator[Optional["asyncio.Future[object]"], None, RT], Awaitable[RT]],
update: object = None,
) -> "asyncio.Task[RT]":
"""Thin wrapper around :func:`asyncio.create_task` that handles exceptions raised by
the :paramref:`coroutine` with :meth:`process_error`.
@@ -948,7 +953,10 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
.. seealso:: :wiki:`Concurrency`
Args:
coroutine (:term:`coroutine function`): The coroutine to run as task.
coroutine (:term:`awaitable`): The awaitable to run as task.
.. versionchanged:: 20.2
Accepts :class:`asyncio.Future` and generator-based coroutine functions.
update (:obj:`object`, optional): If set, will be passed to :meth:`process_error`
as additional information for the error handlers. Moreover, the corresponding
:attr:`chat_data` and :attr:`user_data` entries will be updated in the next run of
@@ -960,13 +968,16 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
return self.__create_task(coroutine=coroutine, update=update)
def __create_task(
self, coroutine: Coroutine, update: object = None, is_error_handler: bool = False
) -> asyncio.Task:
self,
coroutine: Union[Generator[Optional["asyncio.Future[object]"], None, RT], Awaitable[RT]],
update: object = None,
is_error_handler: bool = False,
) -> "asyncio.Task[RT]":
# Unfortunately, we can't know if `coroutine` runs one of the error handler functions
# but by passing `is_error_handler=True` from `process_error`, we can make sure that we
# get at most one recursion of the user calls `create_task` manually with an error handler
# function
task = asyncio.create_task(
task: "asyncio.Task[RT]" = asyncio.create_task(
self.__create_task_callback(
coroutine=coroutine, update=update, is_error_handler=is_error_handler
)
@@ -988,18 +999,18 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
self.__create_task_tasks.discard(task) # Discard from our set since we are done with it
# We just retrieve the eventual exception so that asyncio doesn't complain in case
# it's not retrieved somewhere else
try:
with contextlib.suppress(asyncio.CancelledError, asyncio.InvalidStateError):
task.exception()
except (asyncio.CancelledError, asyncio.InvalidStateError):
pass
async def __create_task_callback(
self,
coroutine: Coroutine[Any, Any, _RT],
coroutine: Union[Generator[Optional["asyncio.Future[object]"], None, RT], Awaitable[RT]],
update: object = None,
is_error_handler: bool = False,
) -> _RT:
) -> RT:
try:
if isinstance(coroutine, Generator):
return await asyncio.create_task(coroutine)
return await coroutine
except asyncio.CancelledError as cancel:
# TODO: in py3.8+, CancelledError is a subclass of BaseException, so we can drop this
@@ -1015,7 +1026,7 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
# Avoid infinite recursion of error handlers.
elif is_error_handler:
_logger.exception(
_LOGGER.exception(
"An error was raised and an uncaught error was raised while "
"handling the error with an error_handler.",
exc_info=exception,
@@ -1035,24 +1046,33 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
async def _update_fetcher(self) -> None:
# Continuously fetch updates from the queue. Exit only once the signal object is found.
while True:
update = await self.update_queue.get()
try:
update = await self.update_queue.get()
if update is _STOP_SIGNAL:
_logger.debug("Dropping pending updates")
while not self.update_queue.empty():
if update is _STOP_SIGNAL:
_LOGGER.debug("Dropping pending updates")
while not self.update_queue.empty():
self.update_queue.task_done()
# For the _STOP_SIGNAL
self.update_queue.task_done()
return
# For the _STOP_SIGNAL
self.update_queue.task_done()
return
_LOGGER.debug("Processing update %s", update)
_logger.debug("Processing update %s", update)
if self._concurrent_updates:
# We don't await the below because it has to be run concurrently
self.create_task(self.__process_update_wrapper(update), update=update)
else:
await self.__process_update_wrapper(update)
if self._concurrent_updates:
# We don't await the below because it has to be run concurrently
self.create_task(self.__process_update_wrapper(update), update=update)
else:
await self.__process_update_wrapper(update)
except asyncio.CancelledError:
# This may happen if the application is manually run via application.start() and
# then a KeyboardInterrupt is sent. We must prevent this loop to die since
# application.stop() will wait for it's clean shutdown.
_LOGGER.warning(
"Fetching updates got a asyncio.CancelledError. Ignoring as this task may only"
"be closed via `Application.stop`."
)
async def __process_update_wrapper(self, update: object) -> None:
async with self._concurrent_updates_sem:
@@ -1106,13 +1126,13 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
# Stop processing with any other handler.
except ApplicationHandlerStop:
_logger.debug("Stopping further handlers due to ApplicationHandlerStop")
_LOGGER.debug("Stopping further handlers due to ApplicationHandlerStop")
break
# Dispatch any error.
except Exception as exc:
if await self.process_error(update=update, error=exc):
_logger.debug("Error handler stopped further handlers.")
_LOGGER.debug("Error handler stopped further handlers.")
break
if any_blocking:
@@ -1189,7 +1209,7 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
Union[List[BaseHandler[Any, CCT]], Tuple[BaseHandler[Any, CCT]]],
Dict[int, Union[List[BaseHandler[Any, CCT]], Tuple[BaseHandler[Any, CCT]]]],
],
group: Union[int, DefaultValue[int]] = DefaultValue(0),
group: Union[int, DefaultValue[int]] = _DEFAULT_0,
) -> None:
"""Registers multiple handlers at once. The order of the handlers in the passed
sequence(s) matters. See :meth:`add_handler` for details.
@@ -1362,6 +1382,36 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
if job.user_id:
self._user_ids_to_be_updated_in_persistence.add(job.user_id)
def mark_data_for_update_persistence(
self, chat_ids: SCT[int] = None, user_ids: SCT[int] = None
) -> None:
"""Mark entries of :attr:`chat_data` and :attr:`user_data` to be updated on the next
run of :meth:`update_persistence`.
Tip:
Use this method sparingly. If you have to use this method, it likely means that you
access and modify ``context.application.chat/user_data[some_id]`` within a callback.
Note that for data which should be available globally in all handler callbacks
independent of the chat/user, it is recommended to use :attr:`bot_data` instead.
.. versionadded:: 20.3
Args:
chat_ids (:obj:`int` | Collection[:obj:`int`], optional): Chat IDs to mark.
user_ids (:obj:`int` | Collection[:obj:`int`], optional): User IDs to mark.
"""
if chat_ids:
if isinstance(chat_ids, int):
self._chat_ids_to_be_updated_in_persistence.add(chat_ids)
else:
self._chat_ids_to_be_updated_in_persistence.update(chat_ids)
if user_ids:
if isinstance(user_ids, int):
self._user_ids_to_be_updated_in_persistence.add(user_ids)
else:
self._user_ids_to_be_updated_in_persistence.update(user_ids)
async def _persistence_updater(self) -> None:
# Update the persistence in regular intervals. Exit only when the stop event has been set
while not self.__update_persistence_event.is_set():
@@ -1388,8 +1438,9 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
along with :attr:`~telegram.ext.ExtBot.callback_data_cache` and the conversation states of
any persistent :class:`~telegram.ext.ConversationHandler` registered for this application.
For :attr:`user_data`, :attr:`chat_data`, only entries used since the last run of this
method are updated.
For :attr:`user_data` and :attr:`chat_data`, only those entries are updated which either
were used or have been manually marked via :meth:`mark_data_for_update_persistence` since
the last run of this method.
Tip:
This method will be called in regular intervals by the application. There is usually
@@ -1399,7 +1450,8 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
Any data is deep copied with :func:`copy.deepcopy` before handing it over to the
persistence in order to avoid race conditions, so all persisted data must be copyable.
.. seealso:: :attr:`telegram.ext.BasePersistence.update_interval`.
.. seealso:: :attr:`telegram.ext.BasePersistence.update_interval`,
:meth:`mark_data_for_update_persistence`
"""
async with self.__update_persistence_lock:
await self.__update_persistence()
@@ -1408,7 +1460,7 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
if not self.persistence:
return
_logger.debug("Starting next run of updating the persistence.")
_LOGGER.debug("Starting next run of updating the persistence.")
coroutines: Set[Coroutine] = set()
@@ -1476,13 +1528,13 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
# *all* tasks will be done.
if not new_state.done():
if self.running:
_logger.debug(
_LOGGER.debug(
"A ConversationHandlers state was not yet resolved. Updating the "
"persistence with the current state. Will check again on next run of "
"Application.update_persistence."
)
else:
_logger.warning(
_LOGGER.warning(
"A ConversationHandlers state was not yet resolved. Updating the "
"persistence with the current state."
)
@@ -1502,7 +1554,7 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
)
results = await asyncio.gather(*coroutines, return_exceptions=True)
_logger.debug("Finished updating persistence.")
_LOGGER.debug("Finished updating persistence.")
# dispatch any errors
await asyncio.gather(
@@ -1543,7 +1595,7 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
:meth:`process_error`. Defaults to :obj:`True`.
"""
if callback in self.error_handlers:
_logger.warning("The callback is already registered as an error handler. Ignoring.")
_LOGGER.warning("The callback is already registered as an error handler. Ignoring.")
return
self.error_handlers[callback] = block
@@ -1562,7 +1614,9 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
update: Optional[object],
error: Exception,
job: "Job[CCT]" = None,
coroutine: Coroutine[Any, Any, Any] = None,
coroutine: Union[
Generator[Optional["asyncio.Future[object]"], None, RT], Awaitable[RT]
] = None,
) -> bool:
"""Processes an error by passing it to all error handlers registered with
:meth:`add_error_handler`. If one of the error handlers raises
@@ -1618,12 +1672,12 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
except ApplicationHandlerStop:
return True
except Exception as exc:
_logger.exception(
_LOGGER.exception(
"An error was raised and an uncaught error was raised while "
"handling the error with an error_handler.",
exc_info=exc,
)
return False
_logger.exception("No error handlers are registered, logging exception.", exc_info=error)
_LOGGER.exception("No error handlers are registered, logging exception.", exc_info=error)
return False
+44 -10
View File
@@ -114,7 +114,7 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
* Unless a custom :class:`telegram.Bot` instance is set via :meth:`bot`, :meth:`build` will
use :class:`telegram.ext.ExtBot` for the bot.
.. seealso:: :wiki:`Your First Bot <Extensions--Your-first-Bot>`,
.. seealso:: :wiki:`Your First Bot <Extensions---Your-first-Bot>`,
:wiki:`Builder Pattern <Builder-Pattern>`
.. _`builder pattern`: https://en.wikipedia.org/wiki/Builder_pattern
@@ -178,7 +178,7 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
self._get_updates_write_timeout: ODVInput[float] = DEFAULT_NONE
self._get_updates_pool_timeout: ODVInput[float] = DEFAULT_NONE
self._get_updates_request: DVInput["BaseRequest"] = DEFAULT_NONE
self._get_updates_http_version: DVInput[str] = DefaultValue("2")
self._get_updates_http_version: DVInput[str] = DefaultValue("1.1")
self._private_key: ODVInput[bytes] = DEFAULT_NONE
self._private_key_password: ODVInput[bytes] = DEFAULT_NONE
self._defaults: ODVInput["Defaults"] = DEFAULT_NONE
@@ -204,7 +204,7 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
self._post_shutdown: Optional[Callable[[Application], Coroutine[Any, Any, None]]] = None
self._post_stop: Optional[Callable[[Application], Coroutine[Any, Any, None]]] = None
self._rate_limiter: ODVInput["BaseRateLimiter"] = DEFAULT_NONE
self._http_version: DVInput[str] = DefaultValue("2")
self._http_version: DVInput[str] = DefaultValue("1.1")
def _build_request(self, get_updates: bool) -> BaseRequest:
prefix = "_get_updates_" if get_updates else "_"
@@ -232,7 +232,7 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
key: value for key, value in timeouts.items() if not isinstance(value, DefaultValue)
}
http_version = DefaultValue.get_value(getattr(self, f"{prefix}http_version")) or "2"
http_version = DefaultValue.get_value(getattr(self, f"{prefix}http_version")) or "1.1"
return HTTPXRequest(
connection_pool_size=connection_pool_size,
@@ -564,15 +564,33 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
def http_version(self: BuilderType, http_version: str) -> BuilderType:
"""Sets the HTTP protocol version which is used for the
:paramref:`~telegram.request.HTTPXRequest.http_version` parameter of
:attr:`telegram.Bot.request`. By default, HTTP/2 is used.
:attr:`telegram.Bot.request`. By default, HTTP/1.1 is used.
.. seealso:: :meth:`get_updates_http_version`
Note:
Users have observed stability issues with HTTP/2, which happen due to how the `h2
library handles <https://github.com/python-hyper/h2/issues/1181>`_ cancellations of
keepalive connections. See `#3556 <https://github.com/python-telegram-bot/
python-telegram-bot/issues/3556>`_ for a discussion.
If you want to use HTTP/2, you must install PTB with the optional requirement
``http2``, i.e.
.. code-block:: bash
pip install python-telegram-bot[http2]
Keep in mind that the HTTP/1.1 implementation may be considered the `"more
robust option at this time" <https://www.python-httpx.org/http2#enabling-http2>`_.
.. versionadded:: 20.1
.. versionchanged:: 20.2
Reset the default version to 1.1.
Args:
http_version (:obj:`str`): Pass ``"1.1"`` if you'd like to use HTTP/1.1 for making
requests to Telegram. Defaults to ``"2"``, in which case HTTP/2 is used.
http_version (:obj:`str`): Pass ``"2"`` if you'd like to use HTTP/2 for making
requests to Telegram. Defaults to ``"1.1"``, in which case HTTP/1.1 is used.
Returns:
:class:`ApplicationBuilder`: The same builder with the updated argument.
@@ -705,15 +723,31 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
def get_updates_http_version(self: BuilderType, get_updates_http_version: str) -> BuilderType:
"""Sets the HTTP protocol version which is used for the
:paramref:`~telegram.request.HTTPXRequest.http_version` parameter which is used in the
:meth:`telegram.Bot.get_updates` request. By default, HTTP/2 is used.
:meth:`telegram.Bot.get_updates` request. By default, HTTP/1.1 is used.
.. seealso:: :meth:`http_version`
Note:
Users have observed stability issues with HTTP/2, which happen due to how the `h2
library handles <https://github.com/python-hyper/h2/issues/1181>`_ cancellations of
keepalive connections. See `#3556 <https://github.com/python-telegram-bot/
python-telegram-bot/issues/3556>`_ for a discussion.
You will also need to install the http2 dependency. Keep in mind that the HTTP/1.1
implementation may be considered the `"more robust option at this time"
<https://www.python-httpx.org/http2#enabling-http2>`_.
.. code-block:: bash
pip install httpx[http2]
.. versionadded:: 20.1
.. versionchanged:: 20.2
Reset the default version to 1.1.
Args:
get_updates_http_version (:obj:`str`): Pass ``"1.1"`` if you'd like to use HTTP/1.1 for
making requests to Telegram. Defaults to ``"2"``, in which case HTTP/2 is used.
get_updates_http_version (:obj:`str`): Pass ``"2"`` if you'd like to use HTTP/2 for
making requests to Telegram. Defaults to ``"1.1"``, in which case HTTP/1.1 is used.
Returns:
:class:`ApplicationBuilder`: The same builder with the updated argument.
+15
View File
@@ -357,6 +357,11 @@ class BasePersistence(Generic[UD, CD, BD], ABC):
:attr:`~telegram.ext.Application.user_data` to a callback. Can be used to update data
stored in :attr:`~telegram.ext.Application.user_data` from an external source.
Warning:
When using :meth:`~telegram.ext.ApplicationBuilder.concurrent_updates`, this method
may be called while a handler callback is still running. This might lead to race
conditions.
.. versionadded:: 13.6
.. versionchanged:: 20.0
@@ -375,6 +380,11 @@ class BasePersistence(Generic[UD, CD, BD], ABC):
:attr:`~telegram.ext.Application.chat_data` to a callback. Can be used to update data
stored in :attr:`~telegram.ext.Application.chat_data` from an external source.
Warning:
When using :meth:`~telegram.ext.ApplicationBuilder.concurrent_updates`, this method
may be called while a handler callback is still running. This might lead to race
conditions.
.. versionadded:: 13.6
.. versionchanged:: 20.0
@@ -393,6 +403,11 @@ class BasePersistence(Generic[UD, CD, BD], ABC):
:attr:`~telegram.ext.Application.bot_data` to a callback. Can be used to update data stored
in :attr:`~telegram.ext.Application.bot_data` from an external source.
Warning:
When using :meth:`~telegram.ext.ApplicationBuilder.concurrent_updates`, this method
may be called while a handler callback is still running. This might lead to race
conditions.
.. versionadded:: 13.6
.. versionchanged:: 20.0
+14 -8
View File
@@ -20,14 +20,16 @@
from typing import (
TYPE_CHECKING,
Any,
Coroutine,
Awaitable,
Dict,
Generator,
Generic,
List,
Match,
NoReturn,
Optional,
Type,
Union,
)
from telegram._callbackquery import CallbackQuery
@@ -37,9 +39,9 @@ from telegram.ext._extbot import ExtBot
from telegram.ext._utils.types import BD, BT, CD, UD
if TYPE_CHECKING:
from asyncio import Queue
from asyncio import Future, Queue
from telegram.ext import Application, Job, JobQueue # noqa: F401
from telegram.ext import Application, Job, JobQueue
from telegram.ext._utils.types import CCT
_STORING_DATA_WIKI = (
@@ -96,8 +98,8 @@ class CallbackContext(Generic[BT, UD, CD, BD]):
.. versionadded:: 20.0
Attributes:
coroutine (:term:`coroutine function`): Optional. Only present in error handlers if the
error was caused by a coroutine run with :meth:`Application.create_task` or a handler
coroutine (:term:`awaitable`): Optional. Only present in error handlers if the
error was caused by an awaitable run with :meth:`Application.create_task` or a handler
callback with :attr:`block=False <BaseHandler.block>`.
matches (List[:meth:`re.Match <re.Match.expand>`]): Optional. If the associated update
originated from a :class:`filters.Regex`, this will contain a list of match objects for
@@ -143,7 +145,9 @@ class CallbackContext(Generic[BT, UD, CD, BD]):
self.matches: Optional[List[Match[str]]] = None
self.error: Optional[Exception] = None
self.job: Optional["Job[CCT]"] = None
self.coroutine: Optional[Coroutine[Any, Any, Any]] = None
self.coroutine: Optional[
Union[Generator[Optional["Future[object]"], None, Any], Awaitable[Any]]
] = None
@property
def application(self) -> "Application[BT, CCT, UD, CD, BD, Any]":
@@ -275,7 +279,7 @@ class CallbackContext(Generic[BT, UD, CD, BD]):
error: Exception,
application: "Application[BT, CCT, UD, CD, BD, Any]",
job: "Job[Any]" = None,
coroutine: Coroutine[Any, Any, Any] = None,
coroutine: Union[Generator[Optional["Future[object]"], None, Any], Awaitable[Any]] = None,
) -> "CCT":
"""
Constructs an instance of :class:`telegram.ext.CallbackContext` to be passed to the error
@@ -295,13 +299,15 @@ class CallbackContext(Generic[BT, UD, CD, BD]):
job (:class:`telegram.ext.Job`, optional): The job associated with the error.
.. versionadded:: 20.0
coroutine (:term:`coroutine function`, optional): The coroutine function associated
coroutine (:term:`awaitable`, optional): The awaitable associated
with this error if the error was caused by a coroutine run with
:meth:`Application.create_task` or a handler callback with
:attr:`block=False <BaseHandler.block>`.
.. versionadded:: 20.0
.. versionchanged:: 20.2
Accepts :class:`asyncio.Future` and generator-based coroutine functions.
Returns:
:class:`telegram.ext.CallbackContext`
"""
+27 -28
View File
@@ -19,7 +19,6 @@
"""This module contains the ConversationHandler."""
import asyncio
import datetime
import logging
from dataclasses import dataclass
from typing import (
TYPE_CHECKING,
@@ -38,6 +37,7 @@ from typing import (
from telegram import Update
from telegram._utils.defaultvalue import DEFAULT_TRUE, DefaultValue
from telegram._utils.logging import get_logger
from telegram._utils.types import DVType
from telegram._utils.warnings import warn
from telegram.ext._application import ApplicationHandlerStop
@@ -56,7 +56,7 @@ if TYPE_CHECKING:
from telegram.ext import Application, Job, JobQueue
_CheckUpdateType = Tuple[object, ConversationKey, BaseHandler[Update, CCT], object]
_logger = logging.getLogger(__name__)
_LOGGER = get_logger(__name__, class_name="ConversationHandler")
@dataclass
@@ -102,7 +102,7 @@ class PendingState:
exc = self.task.exception()
if exc:
_logger.exception(
_LOGGER.exception(
"Task function raised exception. Falling back to old state %s",
self.old_state,
)
@@ -259,7 +259,7 @@ class ConversationHandler(BaseHandler[Update, CCT]):
when :attr:`per_message`, :attr:`per_chat`, :attr:`per_user` are all :obj:`False`.
Attributes:
block (:obj:`bool`): Determines whether the callback will run in a blocking way.. Always
block (:obj:`bool`): Determines whether the callback will run in a blocking way. Always
:obj:`True` since conversation handlers handle any non-blocking callbacks internally.
"""
@@ -649,12 +649,12 @@ class ConversationHandler(BaseHandler[Update, CCT]):
try:
effective_new_state = await new_state
except Exception as exc:
_logger.debug(
_LOGGER.debug(
"Non-blocking handler callback raised exception. Not scheduling conversation "
"timeout.",
exc_info=exc,
)
return
return None
return self._schedule_job(
new_state=effective_new_state,
application=application,
@@ -684,7 +684,7 @@ class ConversationHandler(BaseHandler[Update, CCT]):
data=_ConversationTimeoutContext(conversation_key, update, application, context),
)
except Exception as exc:
_logger.exception("Failed to schedule timeout.", exc_info=exc)
_LOGGER.exception("Failed to schedule timeout.", exc_info=exc)
# pylint: disable=too-many-return-statements
def check_update(self, update: object) -> Optional[_CheckUpdateType[CCT]]:
@@ -719,7 +719,7 @@ class ConversationHandler(BaseHandler[Update, CCT]):
# Resolve futures
if isinstance(state, PendingState):
_logger.debug("Waiting for asyncio Task to finish ...")
_LOGGER.debug("Waiting for asyncio Task to finish ...")
# check if future is finished or not
if state.done():
@@ -741,7 +741,7 @@ class ConversationHandler(BaseHandler[Update, CCT]):
return self.WAITING, key, handler_, check
return None
_logger.debug("Selecting conversation %s with state %s", str(key), str(state))
_LOGGER.debug("Selecting conversation %s with state %s", str(key), str(state))
handler: Optional[BaseHandler] = None
@@ -813,13 +813,12 @@ class ConversationHandler(BaseHandler[Update, CCT]):
# 3. Default values of the bot
if handler.block is not DEFAULT_TRUE:
block = handler.block
elif self._block is not DEFAULT_TRUE:
block = self._block
elif isinstance(application.bot, ExtBot) and application.bot.defaults is not None:
block = application.bot.defaults.block
else:
if self._block is not DEFAULT_TRUE:
block = self._block
elif isinstance(application.bot, ExtBot) and application.bot.defaults is not None:
block = application.bot.defaults.block
else:
block = DefaultValue.get_value(handler.block)
block = DefaultValue.get_value(handler.block)
try: # Now create task or await the callback
if block:
@@ -841,26 +840,25 @@ class ConversationHandler(BaseHandler[Update, CCT]):
if application.job_queue is None:
warn(
"Ignoring `conversation_timeout` because the Application has no JobQueue.",
stacklevel=1,
)
elif not application.job_queue.scheduler.running:
warn(
"Ignoring `conversation_timeout` because the Applications JobQueue is "
"not running.",
stacklevel=1,
)
else:
elif isinstance(new_state, asyncio.Task):
# Add the new timeout job
# checking if the new state is self.END is done in _schedule_job
if isinstance(new_state, asyncio.Task):
application.create_task(
self._schedule_job_delayed(
new_state, application, update, context, conversation_key
),
update=update,
)
else:
self._schedule_job(
application.create_task(
self._schedule_job_delayed(
new_state, application, update, context, conversation_key
)
),
update=update,
)
else:
self._schedule_job(new_state, application, update, context, conversation_key)
if isinstance(self.map_to_parent, dict) and new_state in self.map_to_parent:
self._update_state(self.END, conversation_key, handler)
@@ -874,7 +872,7 @@ class ConversationHandler(BaseHandler[Update, CCT]):
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 ApplicationHandlerStop()
raise ApplicationHandlerStop
# Signals a possible parent conversation to stay in the current state
return None
@@ -909,7 +907,7 @@ class ConversationHandler(BaseHandler[Update, CCT]):
job = cast("Job", context.job)
ctxt = cast(_ConversationTimeoutContext, job.data)
_logger.debug(
_LOGGER.debug(
"Conversation timeout was triggered for conversation %s!", ctxt.conversation_key
)
@@ -935,6 +933,7 @@ class ConversationHandler(BaseHandler[Update, CCT]):
warn(
"ApplicationHandlerStop in TIMEOUT state of "
"ConversationHandler has no effect. Ignoring.",
stacklevel=2,
)
self._update_state(self.END, ctxt.conversation_key)
+5 -5
View File
@@ -468,12 +468,12 @@ class DictPersistence(BasePersistence[Dict[Any, Any], Dict[Any, Any], Dict[Any,
tmp: Dict[int, Dict[object, object]] = {}
decoded_data = json.loads(data)
for user, user_data in decoded_data.items():
user = int(user)
tmp[user] = {}
int_user_id = int(user)
tmp[int_user_id] = {}
for key, value in user_data.items():
try:
key = int(key)
_id = int(key)
except ValueError:
pass
tmp[user][key] = value
_id = key
tmp[int_user_id][_id] = value
return tmp
+346 -8
View File
@@ -30,6 +30,7 @@ from typing import (
Optional,
Sequence,
Tuple,
Type,
TypeVar,
Union,
cast,
@@ -44,6 +45,9 @@ from telegram import (
Bot,
BotCommand,
BotCommandScope,
BotDescription,
BotName,
BotShortDescription,
CallbackQuery,
Chat,
ChatAdministratorRights,
@@ -57,7 +61,9 @@ from telegram import (
ForumTopic,
GameHighScore,
InlineKeyboardMarkup,
InlineQueryResultsButton,
InputMedia,
InputSticker,
Location,
MaskPosition,
MenuButton,
@@ -81,10 +87,12 @@ from telegram import (
)
from telegram._utils.datetime import to_timestamp
from telegram._utils.defaultvalue import DEFAULT_NONE, DefaultValue
from telegram._utils.logging import get_logger
from telegram._utils.types import DVInput, FileInput, JSONDict, ODVInput, ReplyMarkup
from telegram.ext._callbackdatacache import CallbackDataCache
from telegram.ext._utils.types import RLARGS
from telegram.request import BaseRequest
from telegram.warnings import PTBUserWarning
if TYPE_CHECKING:
from telegram import (
@@ -150,6 +158,8 @@ class ExtBot(Bot, Generic[RLARGS]):
__slots__ = ("_callback_data_cache", "_defaults", "_rate_limiter")
_LOGGER = get_logger(__name__, class_name="ExtBot")
# using object() would be a tiny bit safer, but a string plays better with the typing setup
__RL_KEY = uuid4().hex
@@ -226,6 +236,15 @@ class ExtBot(Bot, Generic[RLARGS]):
self._callback_data_cache = CallbackDataCache(bot=self, maxsize=maxsize)
@classmethod
def _warn(
cls, message: str, category: Type[Warning] = PTBUserWarning, stacklevel: int = 0
) -> None:
"""We override this method to add one more level to the stacklevel, so that the warning
points to the user's code, not to the PTB code.
"""
super()._warn(message=message, category=category, stacklevel=stacklevel + 2)
@property
def callback_data_cache(self) -> Optional[CallbackDataCache]:
""":class:`telegram.ext.CallbackDataCache`: Optional. The cache for
@@ -318,7 +337,7 @@ class ExtBot(Bot, Generic[RLARGS]):
"connect_timeout": connect_timeout,
"pool_timeout": pool_timeout,
}
self._logger.debug(
self._LOGGER.debug(
"Passing request through rate limiter of type %s with rate_limit_args %s",
type(self.rate_limiter),
rate_limit_args,
@@ -379,10 +398,10 @@ class ExtBot(Bot, Generic[RLARGS]):
# 3)
elif isinstance(val, InputMedia) and val.parse_mode is DEFAULT_NONE:
# Copy object as not to edit it in-place
val = copy(val)
with val._unfrozen():
val.parse_mode = self.defaults.parse_mode if self.defaults else None
data[key] = val
copied_val = copy(val)
with copied_val._unfrozen():
copied_val.parse_mode = self.defaults.parse_mode if self.defaults else None
data[key] = copied_val
elif key == "media" and isinstance(val, Sequence):
# Copy objects as not to edit them in-place
copy_list = [copy(media) for media in val]
@@ -706,11 +725,12 @@ class ExtBot(Bot, Generic[RLARGS]):
self,
user_id: Union[str, int],
name: str,
emojis: str,
emojis: str = None, # Was made optional for compatibility reasons
png_sticker: FileInput = None,
mask_position: MaskPosition = None,
tgs_sticker: FileInput = None,
webm_sticker: FileInput = None,
sticker: InputSticker = None, # Actually a required param, but is optional for compat.
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
@@ -722,6 +742,7 @@ class ExtBot(Bot, Generic[RLARGS]):
return await super().add_sticker_to_set(
user_id=user_id,
name=name,
sticker=sticker,
emojis=emojis,
png_sticker=png_sticker,
mask_position=mask_position,
@@ -773,6 +794,7 @@ class ExtBot(Bot, Generic[RLARGS]):
next_offset: str = None,
switch_pm_text: str = None,
switch_pm_parameter: str = None,
button: InlineQueryResultsButton = None,
*,
current_offset: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -795,6 +817,7 @@ class ExtBot(Bot, Generic[RLARGS]):
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
button=button,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
@@ -1031,12 +1054,15 @@ class ExtBot(Bot, Generic[RLARGS]):
user_id: Union[str, int],
name: str,
title: str,
emojis: str,
emojis: str = None, # Was made optional for compatibility purposes
png_sticker: FileInput = None,
mask_position: MaskPosition = None,
tgs_sticker: FileInput = None,
webm_sticker: FileInput = None,
sticker_type: str = None,
stickers: Sequence[InputSticker] = None, # Actually a required param. Optional for compat.
sticker_format: str = None, # Actually a required param. Optional for compat.
needs_repainting: bool = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
@@ -1049,6 +1075,9 @@ class ExtBot(Bot, Generic[RLARGS]):
user_id=user_id,
name=name,
title=title,
stickers=stickers,
sticker_format=sticker_format,
needs_repainting=needs_repainting,
emojis=emojis,
png_sticker=png_sticker,
mask_position=mask_position,
@@ -2150,6 +2179,7 @@ class ExtBot(Bot, Generic[RLARGS]):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
has_spoiler: bool = None,
thumbnail: FileInput = None,
*,
filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2176,6 +2206,7 @@ class ExtBot(Bot, Generic[RLARGS]):
protect_content=protect_content,
message_thread_id=message_thread_id,
has_spoiler=has_spoiler,
thumbnail=thumbnail,
filename=filename,
read_timeout=read_timeout,
write_timeout=write_timeout,
@@ -2201,6 +2232,7 @@ class ExtBot(Bot, Generic[RLARGS]):
caption_entities: Sequence["MessageEntity"] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
thumbnail: FileInput = None,
*,
filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2226,6 +2258,7 @@ class ExtBot(Bot, Generic[RLARGS]):
caption_entities=caption_entities,
protect_content=protect_content,
message_thread_id=message_thread_id,
thumbnail=thumbnail,
filename=filename,
read_timeout=read_timeout,
write_timeout=write_timeout,
@@ -2349,6 +2382,7 @@ class ExtBot(Bot, Generic[RLARGS]):
caption_entities: Sequence["MessageEntity"] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
thumbnail: FileInput = None,
*,
filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2372,6 +2406,7 @@ class ExtBot(Bot, Generic[RLARGS]):
caption_entities=caption_entities,
protect_content=protect_content,
message_thread_id=message_thread_id,
thumbnail=thumbnail,
filename=filename,
read_timeout=read_timeout,
write_timeout=write_timeout,
@@ -2724,6 +2759,7 @@ class ExtBot(Bot, Generic[RLARGS]):
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
emoji: str = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
@@ -2745,6 +2781,7 @@ class ExtBot(Bot, Generic[RLARGS]):
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
emoji=emoji,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
@@ -2817,6 +2854,7 @@ class ExtBot(Bot, Generic[RLARGS]):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
has_spoiler: bool = None,
thumbnail: FileInput = None,
*,
filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2844,6 +2882,7 @@ class ExtBot(Bot, Generic[RLARGS]):
protect_content=protect_content,
message_thread_id=message_thread_id,
has_spoiler=has_spoiler,
thumbnail=thumbnail,
filename=filename,
read_timeout=read_timeout,
write_timeout=write_timeout,
@@ -2865,6 +2904,7 @@ class ExtBot(Bot, Generic[RLARGS]):
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
thumbnail: FileInput = None,
*,
filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2886,6 +2926,7 @@ class ExtBot(Bot, Generic[RLARGS]):
allow_sending_without_reply=allow_sending_without_reply,
protect_content=protect_content,
message_thread_id=message_thread_id,
thumbnail=thumbnail,
filename=filename,
read_timeout=read_timeout,
write_timeout=write_timeout,
@@ -3218,6 +3259,30 @@ class ExtBot(Bot, Generic[RLARGS]):
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
async def set_sticker_set_thumbnail(
self,
name: str,
user_id: Union[str, int],
thumbnail: FileInput = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
rate_limit_args: RLARGS = None,
) -> bool:
return await super().set_sticker_set_thumbnail(
name=name,
user_id=user_id,
thumbnail=thumbnail,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
async def set_sticker_set_thumb(
self,
name: str,
@@ -3413,7 +3478,9 @@ class ExtBot(Bot, Generic[RLARGS]):
async def upload_sticker_file(
self,
user_id: Union[str, int],
png_sticker: FileInput,
png_sticker: FileInput = None, # Deprecated since bot api 6.6. Optional for compatiblity.
sticker: FileInput = None, # Actually required, but optional for compatibility.
sticker_format: str = None, # Actually required, but optional for compatibility.
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
@@ -3424,6 +3491,8 @@ class ExtBot(Bot, Generic[RLARGS]):
) -> File:
return await super().upload_sticker_file(
user_id=user_id,
sticker=sticker,
sticker_format=sticker_format,
png_sticker=png_sticker,
read_timeout=read_timeout,
write_timeout=write_timeout,
@@ -3432,6 +3501,262 @@ class ExtBot(Bot, Generic[RLARGS]):
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
async def set_my_description(
self,
description: str = None,
language_code: str = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
rate_limit_args: RLARGS = None,
) -> bool:
return await super().set_my_description(
description=description,
language_code=language_code,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
async def set_my_short_description(
self,
short_description: str = None,
language_code: str = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
rate_limit_args: RLARGS = None,
) -> bool:
return await super().set_my_short_description(
short_description=short_description,
language_code=language_code,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
async def get_my_description(
self,
language_code: str = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
rate_limit_args: RLARGS = None,
) -> BotDescription:
return await super().get_my_description(
language_code=language_code,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
async def get_my_short_description(
self,
language_code: str = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
rate_limit_args: RLARGS = None,
) -> BotShortDescription:
return await super().get_my_short_description(
language_code=language_code,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
async def set_my_name(
self,
name: str = None,
language_code: str = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
rate_limit_args: RLARGS = None,
) -> bool:
return await super().set_my_name(
name=name,
language_code=language_code,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
async def get_my_name(
self,
language_code: str = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
rate_limit_args: RLARGS = None,
) -> BotName:
return await super().get_my_name(
language_code=language_code,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
async def set_custom_emoji_sticker_set_thumbnail(
self,
name: str,
custom_emoji_id: str = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
rate_limit_args: RLARGS = None,
) -> bool:
return await super().set_custom_emoji_sticker_set_thumbnail(
name=name,
custom_emoji_id=custom_emoji_id,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
async def set_sticker_set_title(
self,
name: str,
title: str,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
rate_limit_args: RLARGS = None,
) -> bool:
return await super().set_sticker_set_title(
name=name,
title=title,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
async def delete_sticker_set(
self,
name: str,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
rate_limit_args: RLARGS = None,
) -> bool:
return await super().delete_sticker_set(
name=name,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
async def set_sticker_emoji_list(
self,
sticker: str,
emoji_list: Sequence[str],
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
rate_limit_args: RLARGS = None,
) -> bool:
return await super().set_sticker_emoji_list(
sticker=sticker,
emoji_list=emoji_list,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
async def set_sticker_keywords(
self,
sticker: str,
keywords: Sequence[str] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
rate_limit_args: RLARGS = None,
) -> bool:
return await super().set_sticker_keywords(
sticker=sticker,
keywords=keywords,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
async def set_sticker_mask_position(
self,
sticker: str,
mask_position: MaskPosition = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
rate_limit_args: RLARGS = None,
) -> bool:
return await super().set_sticker_mask_position(
sticker=sticker,
mask_position=mask_position,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
# updated camelCase aliases
getMe = get_me
sendMessage = send_message
@@ -3507,6 +3832,7 @@ class ExtBot(Bot, Generic[RLARGS]):
setStickerPositionInSet = set_sticker_position_in_set
deleteStickerFromSet = delete_sticker_from_set
setStickerSetThumb = set_sticker_set_thumb
setStickerSetThumbnail = set_sticker_set_thumbnail
setPassportDataErrors = set_passport_data_errors
sendPoll = send_poll
stopPoll = stop_poll
@@ -3533,3 +3859,15 @@ class ExtBot(Bot, Generic[RLARGS]):
reopenGeneralForumTopic = reopen_general_forum_topic
hideGeneralForumTopic = hide_general_forum_topic
unhideGeneralForumTopic = unhide_general_forum_topic
setMyDescription = set_my_description
getMyDescription = get_my_description
setMyShortDescription = set_my_short_description
getMyShortDescription = get_my_short_description
setCustomEmojiStickerSetThumbnail = set_custom_emoji_sticker_set_thumbnail
setStickerSetTitle = set_sticker_set_title
deleteStickerSet = delete_sticker_set
setStickerEmojiList = set_sticker_emoji_list
setStickerKeywords = set_sticker_keywords
setStickerMaskPosition = set_sticker_mask_position
setMyName = set_my_name
getMyName = get_my_name
+16 -1
View File
@@ -41,6 +41,9 @@ if TYPE_CHECKING:
from telegram.ext import Application
_ALL_DAYS = tuple(range(7))
class JobQueue(Generic[CCT]):
"""This class allows you to periodically perform tasks with the bot. It is a convenience
wrapper for the APScheduler library.
@@ -288,6 +291,18 @@ class JobQueue(Generic[CCT]):
:attr:`telegram.ext.Defaults.tzinfo` is used.
Defaults to :paramref:`interval`
Note:
Setting :paramref:`first` to ``0``, ``datetime.datetime.now()`` or another
value that indicates that the job should run immediately will not work due
to how the APScheduler library works. If you want to run a job immediately,
we recommend to use an approach along the lines of::
job = context.job_queue.run_repeating(callback, interval=5)
await job.run(context.application)
.. seealso:: :meth:`telegram.ext.Job.run`
last (:obj:`int` | :obj:`float` | :obj:`datetime.timedelta` | \
:obj:`datetime.datetime` | :obj:`datetime.time`, optional):
Latest possible time for the job to run. This parameter will be interpreted
@@ -436,7 +451,7 @@ class JobQueue(Generic[CCT]):
self,
callback: JobCallback[CCT],
time: datetime.time,
days: Tuple[int, ...] = tuple(range(7)),
days: Tuple[int, ...] = _ALL_DAYS,
data: object = None,
name: str = None,
chat_id: int = None,

Some files were not shown because too many files have changed in this diff Show More