Compare commits

..

37 Commits

Author SHA1 Message Date
Hinrich Mahler 6fc45a803d Bump version to v21.2 2024-05-20 17:23:38 +02:00
Harshil 512a0b7417 Add Version to PTBDeprecationWarning (#4262) 2024-05-20 16:12:34 +02:00
Bibo-Joshi 7d952d8707 Documentation Improvements (#4217)
Co-authored-by: poolitzer <github@poolitzer.eu>
2024-05-20 15:53:04 +02:00
Bibo-Joshi b496fabf62 Call Application.post_stop Only if Application.stop was called (#4211) 2024-05-20 15:52:30 +02:00
Bibo-Joshi 637b8e260b Handle SystemExit raised in Handlers (#4157) 2024-05-20 15:27:08 +02:00
Bibo-Joshi 912fe45d8c Handle Exceptions in building CallbackContext (#4222) 2024-05-20 15:25:56 +02:00
Bibo-Joshi 805b7bff32 API 7.3 (#4243, #4248, #4247, #4242, #4246, #4260)
Co-authored-by: Harshil <37377066+harshil21@users.noreply.github.com>
Co-authored-by: Abdelrahman Elkheir <90580077+aelkheir@users.noreply.github.com>
2024-05-20 15:25:25 +02:00
Harshil f3bd0f1462 Add New Rules to ruff Config (#4250) 2024-05-12 09:13:20 +02:00
dependabot[bot] 5b0e0b5f78 Bump furo from 2024.4.27 to 2024.5.6 (#4252)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-11 15:37:54 +02:00
Bibo-Joshi c4623c4476 Make Birthdate.to_date Return a datetime.date Object (#4251)
Co-authored-by: Harshil <37377066+harshil21@users.noreply.github.com>
2024-05-10 22:23:19 +02:00
Bibo-Joshi ee6e82d7ad Remove Functionality Deprecated by Bot API 7.2 (#4245) 2024-05-10 17:42:22 +02:00
pre-commit-ci[bot] c34f4811ea pre-commit autoupdate (#4239)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-05-07 07:42:09 +02:00
dependabot[bot] d768abdd6b Bump pytest from 8.1.1 to 8.2.0 (#4231)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-06 22:36:18 +02:00
dependabot[bot] 2561ffd16b Bump dependabot/fetch-metadata from 2.0.0 to 2.1.0 (#4228)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <dependabot[bot]@users.noreply.github.com>
Co-authored-by: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com>
2024-05-06 22:35:52 +02:00
Bibo-Joshi 622fdf7fa3 Adapt Test Suite to Changes in Error Messages (#4238) 2024-05-06 22:35:11 +02:00
dependabot[bot] 615f1bf20b Bump pytest-asyncio from 0.21.1 to 0.21.2 (#4232)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-05 10:03:38 +02:00
dependabot[bot] 7a8b1be5a4 Bump pytest-xdist from 3.6.0 to 3.6.1 (#4233)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-04 17:30:18 +02:00
dependabot[bot] 3a8ace2e8b Bump furo from 2024.1.29 to 2024.4.27 (#4230)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-04 11:12:26 +02:00
dependabot[bot] 169bd47de3 Bump srvaroa/labeler from 1.10.0 to 1.10.1 (#4227)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-04 11:07:21 +02:00
dependabot[bot] a956dcc6a4 Bump pytest from 7.4.4 to 8.1.1 (#4218)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Harshil <37377066+harshil21@users.noreply.github.com>
Co-authored-by: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com>
2024-04-28 22:32:20 +02:00
Bibo-Joshi ee88973fee Bump pytest-xdist from 3.5.0 to 3.6.0
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-20 10:44:39 +02:00
dependabot[bot] 8f9fc65be0 Bump sphinx from 7.2.6 to 7.3.7 (#4215)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-20 09:22:42 +02:00
Hinrich Mahler 75d946e4be Bump version to v21.1.1 2024-04-15 17:00:17 +02:00
Bibo-Joshi fed8d8875e Fix Bug With Parameter message_thread_id of Message.reply_* (#4207) 2024-04-15 16:49:36 +02:00
Nano 42b68f1a70 Remove Deprecation Warning in JobQueue.run_daily (#4206) 2024-04-14 14:14:45 +02:00
marinelay 58b8ef4ce4 Fix Annotation of EncryptedCredentials.decrypted_secret (#4199)
Co-authored-by: marinelay <marinelay@gmail.com>
2024-04-13 20:27:29 +02:00
Hinrich Mahler f6d009d3ac Bump version to v21.1 2024-04-12 12:39:38 +02:00
Bibo-Joshi 153894728c Documentation Improvements (#4171, #4158)
Signed-off-by: teslaedison <qingchengqiushuang@gmail.com>
Co-authored-by: teslaedison <156734008+teslaedison@users.noreply.github.com>
2024-04-12 12:03:01 +02:00
Harshil 5fa457974d API 7.2 (#4180, #4181)
Co-authored-by: Mahdyar Hasanpour <mahdyar@duck.com>
Co-authored-by: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com>
Co-authored-by: Abdelrahman Elkheir <90580077+aelkheir@users.noreply.github.com>
Co-authored-by: Aditya <adityayadav11082@gmail.com>
2024-04-12 11:58:25 +02:00
Harshil 3ec7bb819c Make ChatAdministratorRights.can_*_stories Required (API 7.1) (#4192)
Co-authored-by: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com>
2024-04-06 17:13:43 +02:00
pre-commit-ci[bot] 040cd2c2fc pre-commit autoupdate (#4184)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com>
2024-04-05 17:26:08 +02:00
Abdelrahman Elkheir 474f9c9693 Make Message.reply_* Reply in the Same Topic by Default (#4170)
Co-authored-by: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com>
2024-04-03 11:32:39 +02:00
dependabot[bot] e18ca0d5e1 Bump dependabot/fetch-metadata from 1.6.0 to 2.0.0 (#4185)
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>
2024-04-02 22:19:33 +02:00
Bibo-Joshi 7331fff3fc Temporarily Mark Tests with get_sticker_set as XFAIL due to API 7.2 Update (#4190) 2024-04-02 22:13:54 +02:00
Abdelrahman Elkheir 23536ee759 Add Update.effective_sender (#4168) 2024-03-30 18:21:13 +01:00
Poolitzer 2d8d43f2a5 Accept Socket Objects for Webhooks (#4161) 2024-03-24 21:04:10 +01:00
Bibo-Joshi 8a542e22a0 Refactor Debug logging in Bot to Improve Type Hinting (#4151) 2024-03-24 12:34:08 +01:00
151 changed files with 7529 additions and 1044 deletions
+2 -2
View File
@@ -157,7 +157,7 @@ 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)
- Added ``.. versionadded:: NEXT.VERSION``, ``.. versionchanged:: NEXT.VERSION``, ``.. deprecated:: NEXT.VERSION`` or ``.. versionremoved:: 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)
@@ -276,7 +276,7 @@ This gives us the flexibility to re-order arguments and more importantly
to add new required arguments. It's also more explicit and easier to read.
.. _`Code of Conduct`: https://www.python.org/psf/conduct/
.. _`Code of Conduct`: https://policies.python.org/python.org/code-of-conduct/
.. _`issue tracker`: https://github.com/python-telegram-bot/python-telegram-bot/issues
.. _`Telegram group`: https://telegram.me/pythontelegrambotgroup
.. _`PEP 8 Style Guide`: https://peps.python.org/pep-0008/
+2
View File
@@ -12,6 +12,8 @@ body:
To make it easier for us to help you, please read this [article](https://github.com/python-telegram-bot/python-telegram-bot/wiki/Ask-Right).
Please mind that there is also a users' [Telegram group](https://t.me/pythontelegrambotgroup) for questions about the library. Questions asked there might be answered quicker than here. Moreover, [GitHub Discussions](https://github.com/python-telegram-bot/python-telegram-bot/discussions) offer a slightly better format to discuss usage questions.
If you have asked the same question elsewhere (e.g. the [Telegram group](https://t.me/pythontelegrambotgroup) or [StackOverflow](https://stackoverflow.com/questions/tagged/python-telegram-bot)), provide a link to that thread.
- type: textarea
id: issue-faced
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
- name: Fetch Dependabot metadata
id: dependabot-metadata
uses: dependabot/fetch-metadata@v1.6.0
uses: dependabot/fetch-metadata@v2.1.0
- uses: actions/checkout@v4
with:
+1 -1
View File
@@ -11,7 +11,7 @@ jobs:
pull-requests: write # for srvaroa/labeler to add labels in PR
runs-on: ubuntu-latest
steps:
- uses: srvaroa/labeler@v1.10.0
- uses: srvaroa/labeler@v1.10.1
# Config file at .github/labeler.yml
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
+1
View File
@@ -6,6 +6,7 @@ on:
- tests/**
- requirements.txt
- requirements-opts.txt
- requirements-dev.txt
push:
branches:
- master
+5 -5
View File
@@ -6,7 +6,7 @@ ci:
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: 'v0.2.1'
rev: 'v0.4.3'
hooks:
- id: ruff
name: ruff
@@ -17,7 +17,7 @@ repos:
- cachetools~=5.3.3
- aiolimiter~=1.1.0
- repo: https://github.com/psf/black-pre-commit-mirror
rev: 24.1.1
rev: 24.4.2
hooks:
- id: black
args:
@@ -28,7 +28,7 @@ repos:
hooks:
- id: flake8
- repo: https://github.com/PyCQA/pylint
rev: v3.0.3
rev: v3.1.0
hooks:
- id: pylint
files: ^(?!(tests|docs)).*\.py$
@@ -40,7 +40,7 @@ repos:
- aiolimiter~=1.1.0
- . # this basically does `pip install -e .`
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.8.0
rev: v1.10.0
hooks:
- id: mypy
name: mypy-ptb
@@ -67,7 +67,7 @@ repos:
- cachetools~=5.3.3
- . # this basically does `pip install -e .`
- repo: https://github.com/asottile/pyupgrade
rev: v3.15.0
rev: v3.15.2
hooks:
- id: pyupgrade
args:
+1
View File
@@ -123,6 +123,7 @@ The following wonderful people contributed directly or indirectly to this projec
- `Vorobjev Simon <https://github.com/simonvorobjev>`_
- `Wagner Macedo <https://github.com/wagnerluis1982>`_
- `wjt <https://github.com/wjt>`_
- `Wonseok Oh <https://github.com/marinelay>`_
- `Yaw Danso <https://github.com/dglitxh>`_
- `Yao Kuan <https://github.com/thatguylah>`_
- `zeroone2numeral2 <https://github.com/zeroone2numeral2>`_
+113
View File
@@ -4,6 +4,119 @@
Changelog
=========
Version 21.2
============
*Released 2024-05-20*
This is the technical changelog for version 21.2. More elaborate release notes can be found in the news channel `@pythontelegrambotchannel <https://t.me/pythontelegrambotchannel>`_.
Major Changes
-------------
- Full Support for Bot API 7.3 (:pr:`4246`, :pr:`4260`, :pr:`4243`, :pr:`4248`, :pr:`4242` closes :issue:`4236`, :pr:`4247` by `aelkheir <https://github.com/aelkheir>`_)
- Remove Functionality Deprecated by Bot API 7.2 (:pr:`4245`)
New Features
------------
- Add Version to ``PTBDeprecationWarning`` (:pr:`4262` closes :issue:`4261`)
- Handle Exceptions in building ``CallbackContext`` (:pr:`4222`)
Bug Fixes
---------
- Call ``Application.post_stop`` Only if ``Application.stop`` was called (:pr:`4211` closes :issue:`4210`)
- Handle ``SystemExit`` raised in Handlers (:pr:`4157` closes :issue:`4155` and :issue:`4156`)
- Make ``Birthdate.to_date`` Return a ``datetime.date`` Object (:pr:`4251`)
Documentation Improvements
--------------------------
- Documentation Improvements (:pr:`4217`)
Internal Changes
----------------
- Add New Rules to ``ruff`` Config (:pr:`4250`)
- Adapt Test Suite to Changes in Error Messages (:pr:`4238`)
Dependency Updates
------------------
- Bump ``furo`` from 2024.4.27 to 2024.5.6 (:pr:`4252`)
- ``pre-commit`` autoupdate (:pr:`4239`)
- Bump ``pytest`` from 8.1.1 to 8.2.0 (:pr:`4231`)
- Bump ``dependabot/fetch-metadata`` from 2.0.0 to 2.1.0 (:pr:`4228`)
- Bump ``pytest-asyncio`` from 0.21.1 to 0.21.2 (:pr:`4232`)
- Bump ``pytest-xdist`` from 3.6.0 to 3.6.1 (:pr:`4233`)
- Bump ``furo`` from 2024.1.29 to 2024.4.27 (:pr:`4230`)
- Bump ``srvaroa/labeler`` from 1.10.0 to 1.10.1 (:pr:`4227`)
- Bump ``pytest`` from 7.4.4 to 8.1.1 (:pr:`4218`)
- Bump ``sphinx`` from 7.2.6 to 7.3.7 (:pr:`4215`)
- Bump ``pytest-xdist`` from 3.5.0 to 3.6.0 (:pr:`4215`)
Version 21.1.1
==============
*Released 2024-04-15*
This is the technical changelog for version 21.1.1. More elaborate release notes can be found in the news channel `@pythontelegrambotchannel <https://t.me/pythontelegrambotchannel>`__.
Bug Fixes
---------
- Fix Bug With Parameter ``message_thread_id`` of ``Message.reply_*`` (:pr:`4207` closes :issue:`4205`)
Minor Changes
-------------
- Remove Deprecation Warning in ``JobQueue.run_daily`` (:pr:`4206` by `@Konano <https://github.com/Konano>`__)
- Fix Annotation of ``EncryptedCredentials.decrypted_secret`` (:pr:`4199` by `@marinelay <https://github.com/marinelay>`__ closes :issue:`4198`)
Version 21.1
==============
*Released 2024-04-12*
This is the technical changelog for version 21.1. More elaborate release notes can be found in the news channel `@pythontelegrambotchannel <https://t.me/pythontelegrambotchannel>`__.
Major Changes
-------------
- API 7.2 (:pr:`4180` closes :issue:`4179` and :issue:`4181`, :issue:`4181`)
- Make ``ChatAdministratorRights/ChatMemberAdministrator.can_*_stories`` Required (API 7.1) (:pr:`4192`)
Minor Changes
-------------
- Refactor Debug logging in ``Bot`` to Improve Type Hinting (:pr:`4151` closes :issue:`4010`)
New Features
------------
- Make ``Message.reply_*`` Reply in the Same Topic by Default (:pr:`4170` by `@aelkheir <https://github.com/aelkheir>`__ closes :issue:`4139`)
- Accept Socket Objects for Webhooks (:pr:`4161` closes :issue:`4078`)
- Add ``Update.effective_sender`` (:pr:`4168` by `@aelkheir <https://github.com/aelkheir>`__ closes :issue:`4085`)
Documentation Improvements
--------------------------
- Documentation Improvements (:pr:`4171`, :pr:`4158` by `@teslaedison <https://github.com/teslaedison>`__)
Internal Changes
----------------
- Temporarily Mark Tests with ``get_sticker_set`` as XFAIL due to API 7.2 Update (:pr:`4190`)
Dependency Updates
------------------
- ``pre-commit`` autoupdate (:pr:`4184`)
- Bump ``dependabot/fetch-metadata`` from 1.6.0 to 2.0.0 (:pr:`4185`)
Version 21.0.1
==============
+3 -3
View File
@@ -14,9 +14,9 @@
:target: https://pypi.org/project/python-telegram-bot/
:alt: Supported Python versions
.. image:: https://img.shields.io/badge/Bot%20API-7.1-blue?logo=telegram
.. image:: https://img.shields.io/badge/Bot%20API-7.3-blue?logo=telegram
:target: https://core.telegram.org/bots/api-changelog
:alt: Supported Bot API versions
:alt: Supported Bot API version
.. image:: https://img.shields.io/pypi/dm/python-telegram-bot
:target: https://pypistats.org/packages/python-telegram-bot
@@ -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 **7.1** are supported.
All types and methods of the Telegram Bot API **7.3** are supported.
Installing
==========
+3 -3
View File
@@ -14,9 +14,9 @@
:target: https://pypi.org/project/python-telegram-bot-raw/
:alt: Supported Python versions
.. image:: https://img.shields.io/badge/Bot%20API-7.1-blue?logo=telegram
.. image:: https://img.shields.io/badge/Bot%20API-7.3-blue?logo=telegram
:target: https://core.telegram.org/bots/api-changelog
:alt: Supported Bot API versions
:alt: Supported Bot API version
.. image:: https://img.shields.io/pypi/dm/python-telegram-bot-raw
:target: https://pypistats.org/packages/python-telegram-bot-raw
@@ -85,7 +85,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 **7.1** are supported.
All types and methods of the Telegram Bot API **7.3** are supported.
Installing
==========
+2 -2
View File
@@ -1,5 +1,5 @@
sphinx==7.2.6
furo==2024.1.29
sphinx==7.3.7
furo==2024.5.6
furo-sphinx-search @ git+https://github.com/harshil21/furo-sphinx-search@v0.2.0.1
sphinx-paramlinks==0.6.0
sphinxcontrib-mermaid==0.9.2
+5 -9
View File
@@ -20,9 +20,9 @@ author = "Leandro Toledo"
# built documents.
#
# The short X.Y version.
version = "21.0.1" # telegram.__version__[:3]
version = "21.2" # telegram.__version__[:3]
# The full version, including alpha/beta/rc tags.
release = "21.0.1" # telegram.__version__
release = "21.2" # telegram.__version__
# If your documentation needs a minimal Sphinx version, state it here.
needs_sphinx = "6.1.3"
@@ -67,7 +67,9 @@ source_suffix = ".rst"
master_doc = "index"
# Global substitutions
rst_prolog = (Path.cwd() / "../substitutions/global.rst").read_text(encoding="utf-8")
rst_prolog = ""
for file in Path.cwd().glob("../substitutions/*.rst"):
rst_prolog += "\n" + file.read_text(encoding="utf-8")
# -- Extension settings ------------------------------------------------
napoleon_use_admonition_for_examples = True
@@ -140,12 +142,6 @@ html_theme_options = {
"admonition-title-font-size": "0.95rem",
"admonition-font-size": "0.92rem",
},
"announcement": (
"PTB has undergone significant changes in v20. Please read the documentation "
"carefully and also check out the transition guide in the "
'<a href="https://github.com/python-telegram-bot/python-telegram-bot/wiki/'
'Transition-guide-to-Version-20.0">wiki</a>.'
),
"footer_icons": [
{
# Telegram channel logo
+8 -4
View File
@@ -113,6 +113,10 @@
:align: left
:widths: 1 4
* - :meth:`~telegram.Bot.approve_chat_join_request`
- Used for approving a chat join request
* - :meth:`~telegram.Bot.decline_chat_join_request`
- Used for declining a chat join request
* - :meth:`~telegram.Bot.ban_chat_member`
- Used for banning a member from the chat
* - :meth:`~telegram.Bot.unban_chat_member`
@@ -137,10 +141,6 @@
- Used for editing a non-primary invite link
* - :meth:`~telegram.Bot.revoke_chat_invite_link`
- Used for revoking an invite link created by the bot
* - :meth:`~telegram.Bot.approve_chat_join_request`
- Used for approving a chat join request
* - :meth:`~telegram.Bot.decline_chat_join_request`
- Used for declining a chat join request
* - :meth:`~telegram.Bot.set_chat_photo`
- Used for setting a photo to a chat
* - :meth:`~telegram.Bot.delete_chat_photo`
@@ -155,6 +155,8 @@
- Used for unpinning a message
* - :meth:`~telegram.Bot.unpin_all_chat_messages`
- Used for unpinning all pinned chat messages
* - :meth:`~telegram.Bot.get_business_connection`
- Used for getting information about the business account.
* - :meth:`~telegram.Bot.get_user_profile_photos`
- Used for obtaining user's profile pictures
* - :meth:`~telegram.Bot.get_chat`
@@ -237,6 +239,8 @@
- Used for setting a sticker set of a chat
* - :meth:`~telegram.Bot.delete_chat_sticker_set`
- Used for deleting the set sticker set of a chat
* - :meth:`~telegram.Bot.replace_sticker_in_set`
- Used for replacing a sticker in a set
* - :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`
+20
View File
@@ -6,6 +6,7 @@ Available Types
telegram.animation
telegram.audio
telegram.birthdate
telegram.botcommand
telegram.botcommandscope
telegram.botcommandscopeallchatadministrators
@@ -18,9 +19,25 @@ Available Types
telegram.botdescription
telegram.botname
telegram.botshortdescription
telegram.businessconnection
telegram.businessintro
telegram.businesslocation
telegram.businessopeninghours
telegram.businessopeninghoursinterval
telegram.businessmessagesdeleted
telegram.callbackquery
telegram.chat
telegram.chatadministratorrights
telegram.chatbackground
telegram.backgroundtype
telegram.backgroundtypefill
telegram.backgroundtypewallpaper
telegram.backgroundtypepattern
telegram.backgroundtypechattheme
telegram.backgroundfill
telegram.backgroundfillsolid
telegram.backgroundfillgradient
telegram.backgroundfillfreeformgradient
telegram.chatboost
telegram.chatboostadded
telegram.chatboostremoved
@@ -29,6 +46,7 @@ Available Types
telegram.chatboostsourcegiveaway
telegram.chatboostsourcepremium
telegram.chatboostupdated
telegram.chatfullinfo
telegram.chatinvitelink
telegram.chatjoinrequest
telegram.chatlocation
@@ -70,6 +88,7 @@ Available Types
telegram.inputmediadocument
telegram.inputmediaphoto
telegram.inputmediavideo
telegram.inputpolloption
telegram.inputsticker
telegram.keyboardbutton
telegram.keyboardbuttonpolltype
@@ -107,6 +126,7 @@ Available Types
telegram.replykeyboardremove
telegram.replyparameters
telegram.sentwebappmessage
telegram.shareduser
telegram.story
telegram.switchinlinequerychosenchat
telegram.telegramobject
+8
View File
@@ -0,0 +1,8 @@
BackgroundFill
==============
.. versionadded:: 21.2
.. autoclass:: telegram.BackgroundFill
:members:
:show-inheritance:
@@ -0,0 +1,8 @@
BackgroundFillFreeformGradient
==============================
.. versionadded:: 21.2
.. autoclass:: telegram.BackgroundFillFreeformGradient
:members:
:show-inheritance:
@@ -0,0 +1,8 @@
BackgroundFillGradient
======================
.. versionadded:: 21.2
.. autoclass:: telegram.BackgroundFillGradient
:members:
:show-inheritance:
@@ -0,0 +1,8 @@
BackgroundFillSolid
===================
.. versionadded:: 21.2
.. autoclass:: telegram.BackgroundFillSolid
:members:
:show-inheritance:
+8
View File
@@ -0,0 +1,8 @@
BackgroundType
==============
.. versionadded:: 21.2
.. autoclass:: telegram.BackgroundType
:members:
:show-inheritance:
@@ -0,0 +1,8 @@
BackgroundTypeChatTheme
=======================
.. versionadded:: 21.2
.. autoclass:: telegram.BackgroundTypeChatTheme
:members:
:show-inheritance:
@@ -0,0 +1,8 @@
BackgroundTypeFill
==================
.. versionadded:: 21.2
.. autoclass:: telegram.BackgroundTypeFill
:members:
:show-inheritance:
@@ -0,0 +1,8 @@
BackgroundTypePattern
=====================
.. versionadded:: 21.2
.. autoclass:: telegram.BackgroundTypePattern
:members:
:show-inheritance:
@@ -0,0 +1,8 @@
BackgroundTypeWallpaper
=======================
.. versionadded:: 21.2
.. autoclass:: telegram.BackgroundTypeWallpaper
:members:
:show-inheritance:
+7
View File
@@ -0,0 +1,7 @@
Birthdate
=========
.. autoclass:: telegram.Birthdate
:members:
:show-inheritance:
@@ -0,0 +1,6 @@
BusinessConnection
==================
.. autoclass:: telegram.BusinessConnection
:members:
:show-inheritance:
+6
View File
@@ -0,0 +1,6 @@
BusinessIntro
==================
.. autoclass:: telegram.BusinessIntro
:members:
:show-inheritance:
@@ -0,0 +1,6 @@
BusinessLocation
==================
.. autoclass:: telegram.BusinessLocation
:members:
:show-inheritance:
@@ -0,0 +1,6 @@
BusinessMessagesDeleted
=======================
.. autoclass:: telegram.BusinessMessagesDeleted
:members:
:show-inheritance:
@@ -0,0 +1,6 @@
BusinessOpeningHours
====================
.. autoclass:: telegram.BusinessOpeningHours
:members:
:show-inheritance:
@@ -0,0 +1,6 @@
BusinessOpeningHoursInterval
============================
.. autoclass:: telegram.BusinessOpeningHoursInterval
:members:
:show-inheritance:
+8
View File
@@ -0,0 +1,8 @@
ChatBackground
==============
.. versionadded:: 21.2
.. autoclass:: telegram.ChatBackground
:members:
:show-inheritance:
+6
View File
@@ -0,0 +1,6 @@
ChatFullInfo
============
.. autoclass:: telegram.ChatFullInfo
:members:
:show-inheritance:
@@ -0,0 +1,6 @@
BusinessConnectionHandler
=========================
.. autoclass:: telegram.ext.BusinessConnectionHandler
:members:
:show-inheritance:
@@ -0,0 +1,6 @@
BusinessMessagesDeletedHandler
==============================
.. autoclass:: telegram.ext.BusinessMessagesDeletedHandler
:members:
:show-inheritance:
@@ -5,6 +5,8 @@ Handlers
:titlesonly:
telegram.ext.basehandler
telegram.ext.businessconnectionhandler
telegram.ext.businessmessagesdeletedhandler
telegram.ext.callbackqueryhandler
telegram.ext.chatboosthandler
telegram.ext.chatjoinrequesthandler
+6
View File
@@ -0,0 +1,6 @@
InputPollOption
===============
.. autoclass:: telegram.InputPollOption
:members:
:show-inheritance:
+7
View File
@@ -0,0 +1,7 @@
SharedUser
==========
.. autoclass:: telegram.SharedUser
:members:
:show-inheritance:
+1
View File
@@ -0,0 +1 @@
.. |app_run_shutdown| replace:: The app will shut down when :exc:`KeyboardInterrupt` or :exc:`SystemExit` is raised. This also works from within handlers, error handlers and jobs. However, using :meth:`~telegram.ext.Application.stop_running` will give a somewhat cleaner shutdown behavior than manually raising those exceptions. On unix, the app will also shut down on receiving the signals specified by
+2
View File
@@ -79,3 +79,5 @@
.. |do_quote| replace:: If set to :obj:`True`, the replied message is quoted. For a dict, it must be the output of :meth:`~telegram.Message.build_reply_arguments` to specify exact ``reply_parameters``. If ``reply_to_message_id`` or ``reply_parameters`` are passed, this parameter will be ignored. Default: :obj:`True` in group chats and :obj:`False` in private chats.
.. |non_optional_story_argument| replace:: As of this version, this argument is now required. In accordance with our `stability policy <https://docs.python-telegram-bot.org/en/stable/stability_policy.html>`__, the signature will be kept as optional for now, though they are mandatory and an error will be raised if you don't pass it.
.. |business_id_str| replace:: Unique identifier of the business connection on behalf of which the message will be sent.
+6 -4
View File
@@ -20,13 +20,15 @@ explicit-preview-rules = true
ignore = ["PLR2004", "PLR0911", "PLR0912", "PLR0913", "PLR0915", "PERF203"]
select = ["E", "F", "I", "PL", "UP", "RUF", "PTH", "C4", "B", "PIE", "SIM", "RET", "RSE",
"G", "ISC", "PT", "ASYNC", "TCH", "SLOT", "PERF", "PYI", "FLY", "AIR", "RUF022",
"RUF023", "Q", "INP",]
"RUF023", "Q", "INP", "W", "YTT", "DTZ", "ARG"]
# Add "FURB" after it's out of preview
# Add "A (flake8-builtins)" after we drop pylint
[tool.ruff.lint.per-file-ignores]
"tests/*.py" = ["B018"]
"tests/**.py" = ["RUF012", "ASYNC101"]
"docs/**.py" = ["INP001"]
"tests/**.py" = ["RUF012", "ASYNC101", "DTZ", "ARG"]
"docs/**.py" = ["INP001", "ARG"]
"examples/**.py" = ["ARG"]
# PYLINT:
[tool.pylint."messages control"]
@@ -48,7 +50,7 @@ exclude-protected = ["_unfrozen"]
# PYTEST:
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "--no-success-flaky-report -rsxX"
addopts = "--no-success-flaky-report -rX"
filterwarnings = [
"error",
"ignore::DeprecationWarning",
+3 -3
View File
@@ -1,9 +1,9 @@
pre-commit # needed for pre-commit hooks in the git commit command
# For the test suite
pytest==7.4.4
pytest-asyncio==0.21.1 # needed because pytest doesn't come with native support for coroutines as tests
pytest-xdist==3.5.0 # xdist runs tests in parallel
pytest==8.2.0
pytest-asyncio==0.21.2 # needed because pytest doesn't come with native support for coroutines as tests
pytest-xdist==3.6.1 # xdist runs tests in parallel
flaky # Used for flaky tests (flaky decorator)
beautifulsoup4 # used in test_official for parsing tg docs
+44 -2
View File
@@ -22,6 +22,16 @@ __author__ = "devs@python-telegram-bot.org"
__all__ = (
"Animation",
"Audio",
"BackgroundFill",
"BackgroundFillFreeformGradient",
"BackgroundFillGradient",
"BackgroundFillSolid",
"BackgroundType",
"BackgroundTypeChatTheme",
"BackgroundTypeFill",
"BackgroundTypePattern",
"BackgroundTypeWallpaper",
"Birthdate",
"Bot",
"BotCommand",
"BotCommandScope",
@@ -35,10 +45,17 @@ __all__ = (
"BotDescription",
"BotName",
"BotShortDescription",
"BusinessConnection",
"BusinessIntro",
"BusinessLocation",
"BusinessMessagesDeleted",
"BusinessOpeningHours",
"BusinessOpeningHoursInterval",
"CallbackGame",
"CallbackQuery",
"Chat",
"ChatAdministratorRights",
"ChatBackground",
"ChatBoost",
"ChatBoostAdded",
"ChatBoostRemoved",
@@ -47,6 +64,7 @@ __all__ = (
"ChatBoostSourceGiveaway",
"ChatBoostSourcePremium",
"ChatBoostUpdated",
"ChatFullInfo",
"ChatInviteLink",
"ChatJoinRequest",
"ChatLocation",
@@ -124,6 +142,7 @@ __all__ = (
"InputMediaPhoto",
"InputMediaVideo",
"InputMessageContent",
"InputPollOption",
"InputSticker",
"InputTextMessageContent",
"InputVenueMessageContent",
@@ -184,6 +203,7 @@ __all__ = (
"SecureData",
"SecureValue",
"SentWebAppMessage",
"SharedUser",
"ShippingAddress",
"ShippingOption",
"ShippingQuery",
@@ -224,6 +244,7 @@ __all__ = (
from . import _version, constants, error, helpers, request, warnings
from ._birthdate import Birthdate
from ._bot import Bot
from ._botcommand import BotCommand
from ._botcommandscope import (
@@ -238,9 +259,29 @@ from ._botcommandscope import (
)
from ._botdescription import BotDescription, BotShortDescription
from ._botname import BotName
from ._business import (
BusinessConnection,
BusinessIntro,
BusinessLocation,
BusinessMessagesDeleted,
BusinessOpeningHours,
BusinessOpeningHoursInterval,
)
from ._callbackquery import CallbackQuery
from ._chat import Chat
from ._chatadministratorrights import ChatAdministratorRights
from ._chatbackground import (
BackgroundFill,
BackgroundFillFreeformGradient,
BackgroundFillGradient,
BackgroundFillSolid,
BackgroundType,
BackgroundTypeChatTheme,
BackgroundTypeFill,
BackgroundTypePattern,
BackgroundTypeWallpaper,
ChatBackground,
)
from ._chatboost import (
ChatBoost,
ChatBoostAdded,
@@ -252,6 +293,7 @@ from ._chatboost import (
ChatBoostUpdated,
UserChatBoosts,
)
from ._chatfullinfo import ChatFullInfo
from ._chatinvitelink import ChatInviteLink
from ._chatjoinrequest import ChatJoinRequest
from ._chatlocation import ChatLocation
@@ -386,14 +428,14 @@ from ._payment.shippingaddress import ShippingAddress
from ._payment.shippingoption import ShippingOption
from ._payment.shippingquery import ShippingQuery
from ._payment.successfulpayment import SuccessfulPayment
from ._poll import Poll, PollAnswer, PollOption
from ._poll import InputPollOption, Poll, PollAnswer, PollOption
from ._proximityalerttriggered import ProximityAlertTriggered
from ._reaction import ReactionCount, ReactionType, ReactionTypeCustomEmoji, ReactionTypeEmoji
from ._reply import ExternalReplyInfo, ReplyParameters, TextQuote
from ._replykeyboardmarkup import ReplyKeyboardMarkup
from ._replykeyboardremove import ReplyKeyboardRemove
from ._sentwebappmessage import SentWebAppMessage
from ._shared import ChatShared, UsersShared
from ._shared import ChatShared, SharedUser, UsersShared
from ._story import Story
from ._switchinlinequerychosenchat import SwitchInlineQueryChosenChat
from ._telegramobject import TelegramObject
+92
View File
@@ -0,0 +1,92 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2024
# 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 Birthday."""
from datetime import date
from typing import Optional
from telegram._telegramobject import TelegramObject
from telegram._utils.types import JSONDict
class Birthdate(TelegramObject):
"""
This object describes the birthdate of a user.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`day`, and :attr:`month` are equal.
.. versionadded:: 21.1
Args:
day (:obj:`int`): Day of the user's birth; 1-31.
month (:obj:`int`): Month of the user's birth; 1-12.
year (:obj:`int`, optional): Year of the user's birth.
Attributes:
day (:obj:`int`): Day of the user's birth; 1-31.
month (:obj:`int`): Month of the user's birth; 1-12.
year (:obj:`int`): Optional. Year of the user's birth.
"""
__slots__ = ("day", "month", "year")
def __init__(
self,
day: int,
month: int,
year: Optional[int] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
# Required
self.day: int = day
self.month: int = month
# Optional
self.year: Optional[int] = year
self._id_attrs = (
self.day,
self.month,
)
self._freeze()
def to_date(self, year: Optional[int] = None) -> date:
"""Return the birthdate as a date object.
.. versionchanged:: 21.2
Now returns a :obj:`datetime.date` object instead of a :obj:`datetime.datetime` object,
as was originally intended.
Args:
year (:obj:`int`, optional): The year to use. Required, if the :attr:`year` was not
present.
Returns:
:obj:`datetime.date`: The birthdate as a date object.
"""
if self.year is None and year is None:
raise ValueError(
"The `year` argument is required if the `year` attribute was not present."
)
return date(year or self.year, self.month, self.day) # type: ignore[arg-type]
+281 -176
View File
File diff suppressed because it is too large Load Diff
+445
View File
@@ -0,0 +1,445 @@
#!/usr/bin/env python
# pylint: disable=redefined-builtin
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2024
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/]
"""This module contains the Telegram Business related classes."""
from datetime import datetime
from typing import TYPE_CHECKING, Optional, Sequence, Tuple
from telegram._chat import Chat
from telegram._files.location import Location
from telegram._files.sticker import Sticker
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
from telegram import Bot
class BusinessConnection(TelegramObject):
"""
Describes the connection of the bot with a business account.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal if their :attr:`id`, :attr:`user`, :attr:`user_chat_id`, :attr:`date`,
:attr:`can_reply`, and :attr:`is_enabled` are equal.
.. versionadded:: 21.1
Args:
id (:obj:`str`): Unique identifier of the business connection.
user (:class:`telegram.User`): Business account user that created the business connection.
user_chat_id (:obj:`int`): Identifier of a private chat with the user who created the
business connection.
date (:obj:`datetime.datetime`): Date the connection was established in Unix time.
can_reply (:obj:`bool`): True, if the bot can act on behalf of the business account in
chats that were active in the last 24 hours.
is_enabled (:obj:`bool`): True, if the connection is active.
Attributes:
id (:obj:`str`): Unique identifier of the business connection.
user (:class:`telegram.User`): Business account user that created the business connection.
user_chat_id (:obj:`int`): Identifier of a private chat with the user who created the
business connection.
date (:obj:`datetime.datetime`): Date the connection was established in Unix time.
can_reply (:obj:`bool`): True, if the bot can act on behalf of the business account in
chats that were active in the last 24 hours.
is_enabled (:obj:`bool`): True, if the connection is active.
"""
__slots__ = (
"can_reply",
"date",
"id",
"is_enabled",
"user",
"user_chat_id",
)
def __init__(
self,
id: str,
user: "User",
user_chat_id: int,
date: datetime,
can_reply: bool,
is_enabled: bool,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.id: str = id
self.user: User = user
self.user_chat_id: int = user_chat_id
self.date: datetime = date
self.can_reply: bool = can_reply
self.is_enabled: bool = is_enabled
self._id_attrs = (
self.id,
self.user,
self.user_chat_id,
self.date,
self.can_reply,
self.is_enabled,
)
self._freeze()
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["BusinessConnection"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["date"] = from_timestamp(data.get("date"), tzinfo=loc_tzinfo)
data["user"] = User.de_json(data.get("user"), bot)
return super().de_json(data=data, bot=bot)
class BusinessMessagesDeleted(TelegramObject):
"""
This object is received when messages are deleted from a connected business account.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal if their :attr:`business_connection_id`, :attr:`message_ids`, and
:attr:`chat` are equal.
.. versionadded:: 21.1
Args:
business_connection_id (:obj:`str`): Unique identifier of the business connection.
chat (:class:`telegram.Chat`): Information about a chat in the business account. The bot
may not have access to the chat or the corresponding user.
message_ids (Sequence[:obj:`int`]): A list of identifiers of the deleted messages in the
chat of the business account.
Attributes:
business_connection_id (:obj:`str`): Unique identifier of the business connection.
chat (:class:`telegram.Chat`): Information about a chat in the business account. The bot
may not have access to the chat or the corresponding user.
message_ids (Tuple[:obj:`int`]): A list of identifiers of the deleted messages in the
chat of the business account.
"""
__slots__ = (
"business_connection_id",
"chat",
"message_ids",
)
def __init__(
self,
business_connection_id: str,
chat: Chat,
message_ids: Sequence[int],
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.business_connection_id: str = business_connection_id
self.chat: Chat = chat
self.message_ids: Tuple[int, ...] = parse_sequence_arg(message_ids)
self._id_attrs = (
self.business_connection_id,
self.chat,
self.message_ids,
)
self._freeze()
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["BusinessMessagesDeleted"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["chat"] = Chat.de_json(data.get("chat"), bot)
return super().de_json(data=data, bot=bot)
class BusinessIntro(TelegramObject):
"""
This object contains information about the start page settings of a Telegram Business account.
Objects of this class are comparable in terms of equality.
Two objects of this class are considered equal, if their
:attr:`title`, :attr:`message` and :attr:`sticker` are equal.
.. versionadded:: 21.1
Args:
title (:obj:`str`, optional): Title text of the business intro.
message (:obj:`str`, optional): Message text of the business intro.
sticker (:class:`telegram.Sticker`, optional): Sticker of the business intro.
Attributes:
title (:obj:`str`): Optional. Title text of the business intro.
message (:obj:`str`): Optional. Message text of the business intro.
sticker (:class:`telegram.Sticker`): Optional. Sticker of the business intro.
"""
__slots__ = (
"message",
"sticker",
"title",
)
def __init__(
self,
title: Optional[str] = None,
message: Optional[str] = None,
sticker: Optional[Sticker] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.title: Optional[str] = title
self.message: Optional[str] = message
self.sticker: Optional[Sticker] = sticker
self._id_attrs = (self.title, self.message, self.sticker)
self._freeze()
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["BusinessIntro"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["sticker"] = Sticker.de_json(data.get("sticker"), bot)
return super().de_json(data=data, bot=bot)
class BusinessLocation(TelegramObject):
"""
This object contains information about the location of a Telegram Business account.
Objects of this class are comparable in terms of equality.
Two objects of this class are considered equal, if their
:attr:`address` is equal.
.. versionadded:: 21.1
Args:
address (:obj:`str`): Address of the business.
location (:class:`telegram.Location`, optional): Location of the business.
Attributes:
address (:obj:`str`): Address of the business.
location (:class:`telegram.Location`): Optional. Location of the business.
"""
__slots__ = (
"address",
"location",
)
def __init__(
self,
address: str,
location: Optional[Location] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.address: str = address
self.location: Optional[Location] = location
self._id_attrs = (self.address,)
self._freeze()
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["BusinessLocation"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["location"] = Location.de_json(data.get("location"), bot)
return super().de_json(data=data, bot=bot)
class BusinessOpeningHoursInterval(TelegramObject):
"""
This object describes an interval of time during which a business is open.
Objects of this class are comparable in terms of equality.
Two objects of this class are considered equal, if their
:attr:`opening_minute` and :attr:`closing_minute` are equal.
.. versionadded:: 21.1
Examples:
A day has (24 * 60 =) 1440 minutes, a week has (7 * 1440 =) 10080 minutes.
Starting the the minute's sequence from Monday, example values of
:attr:`opening_minute`, :attr:`closing_minute` will map to the following day times:
* Monday - 8am to 8:30pm:
- ``opening_minute = 480`` :guilabel:`8 * 60`
- ``closing_minute = 1230`` :guilabel:`20 * 60 + 30`
* Tuesday - 24 hours:
- ``opening_minute = 1440`` :guilabel:`24 * 60`
- ``closing_minute = 2879`` :guilabel:`2 * 24 * 60 - 1`
* Sunday - 12am - 11:58pm:
- ``opening_minute = 8640`` :guilabel:`6 * 24 * 60`
- ``closing_minute = 10078`` :guilabel:`7 * 24 * 60 - 2`
Args:
opening_minute (:obj:`int`): The minute's sequence number in a week, starting on Monday,
marking the start of the time interval during which the business is open;
0 - 7 * 24 * 60.
closing_minute (:obj:`int`): The minute's
sequence number in a week, starting on Monday, marking the end of the time interval
during which the business is open; 0 - 8 * 24 * 60
Attributes:
opening_minute (:obj:`int`): The minute's sequence number in a week, starting on Monday,
marking the start of the time interval during which the business is open;
0 - 7 * 24 * 60.
closing_minute (:obj:`int`): The minute's
sequence number in a week, starting on Monday, marking the end of the time interval
during which the business is open; 0 - 8 * 24 * 60
"""
__slots__ = ("_closing_time", "_opening_time", "closing_minute", "opening_minute")
def __init__(
self,
opening_minute: int,
closing_minute: int,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.opening_minute: int = opening_minute
self.closing_minute: int = closing_minute
self._opening_time: Optional[Tuple[int, int, int]] = None
self._closing_time: Optional[Tuple[int, int, int]] = None
self._id_attrs = (self.opening_minute, self.closing_minute)
self._freeze()
def _parse_minute(self, minute: int) -> Tuple[int, int, int]:
return (minute // 1440, minute % 1440 // 60, minute % 1440 % 60)
@property
def opening_time(self) -> Tuple[int, int, int]:
"""Convenience attribute. A :obj:`tuple` parsed from :attr:`opening_minute`. It contains
the `weekday`, `hour` and `minute` in the same ranges as :attr:`datetime.datetime.weekday`,
:attr:`datetime.datetime.hour` and :attr:`datetime.datetime.minute`
Returns:
Tuple[:obj:`int`, :obj:`int`, :obj:`int`]:
"""
if self._opening_time is None:
self._opening_time = self._parse_minute(self.opening_minute)
return self._opening_time
@property
def closing_time(self) -> Tuple[int, int, int]:
"""Convenience attribute. A :obj:`tuple` parsed from :attr:`closing_minute`. It contains
the `weekday`, `hour` and `minute` in the same ranges as :attr:`datetime.datetime.weekday`,
:attr:`datetime.datetime.hour` and :attr:`datetime.datetime.minute`
Returns:
Tuple[:obj:`int`, :obj:`int`, :obj:`int`]:
"""
if self._closing_time is None:
self._closing_time = self._parse_minute(self.closing_minute)
return self._closing_time
class BusinessOpeningHours(TelegramObject):
"""
This object describes the opening hours of a business.
Objects of this class are comparable in terms of equality.
Two objects of this class are considered equal, if their
:attr:`time_zone_name` and :attr:`opening_hours` are equal.
.. versionadded:: 21.1
Args:
time_zone_name (:obj:`str`): Unique name of the time zone for which the opening
hours are defined.
opening_hours (Sequence[:class:`telegram.BusinessOpeningHoursInterval`]): List of
time intervals describing business opening hours.
Attributes:
time_zone_name (:obj:`str`): Unique name of the time zone for which the opening
hours are defined.
opening_hours (Sequence[:class:`telegram.BusinessOpeningHoursInterval`]): List of
time intervals describing business opening hours.
"""
__slots__ = ("opening_hours", "time_zone_name")
def __init__(
self,
time_zone_name: str,
opening_hours: Sequence[BusinessOpeningHoursInterval],
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.time_zone_name: str = time_zone_name
self.opening_hours: Sequence[BusinessOpeningHoursInterval] = parse_sequence_arg(
opening_hours
)
self._id_attrs = (self.time_zone_name, self.opening_hours)
self._freeze()
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["BusinessOpeningHours"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["opening_hours"] = BusinessOpeningHoursInterval.de_list(
data.get("opening_hours"), bot
)
return super().de_json(data=data, bot=bot)
+3
View File
@@ -461,6 +461,7 @@ class CallbackQuery(TelegramObject):
horizontal_accuracy: Optional[float] = None,
heading: Optional[int] = None,
proximity_alert_radius: Optional[int] = None,
live_period: Optional[int] = None,
*,
location: Optional[Location] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -509,6 +510,7 @@ class CallbackQuery(TelegramObject):
horizontal_accuracy=horizontal_accuracy,
heading=heading,
proximity_alert_radius=proximity_alert_radius,
live_period=live_period,
chat_id=None,
message_id=None,
)
@@ -525,6 +527,7 @@ class CallbackQuery(TelegramObject):
horizontal_accuracy=horizontal_accuracy,
heading=heading,
proximity_alert_radius=proximity_alert_radius,
live_period=live_period,
)
async def stop_message_live_location(
+471 -11
View File
File diff suppressed because it is too large Load Diff
+14 -13
View File
@@ -44,6 +44,11 @@ class ChatAdministratorRights(TelegramObject):
:attr:`can_post_stories`, :attr:`can_edit_stories`, and :attr:`can_delete_stories` are
considered as well when comparing objects of this type in terms of equality.
.. versionchanged:: 21.1
As of this version, :attr:`can_post_stories`, :attr:`can_edit_stories`,
and :attr:`can_delete_stories` is now required. Thus, the order of arguments had to be
changed.
Args:
is_anonymous (:obj:`bool`): :obj:`True`, if the user's presence in the chat is hidden.
can_manage_chat (:obj:`bool`): :obj:`True`, if the administrator can access the chat event
@@ -75,8 +80,9 @@ class ChatAdministratorRights(TelegramObject):
.. versionadded:: 20.6
.. versionchanged:: 21.0
|non_optional_story_argument|
can_edit_stories (:obj:`bool`): :obj:`True`, if the administrator can edit
stories posted by other users.
can_edit_stories (:obj:`bool`): :obj:`True`, if the administrator can edit stories posted
by other users, post stories to the chat page, pin chat stories, and access the chat's
story archive
.. versionadded:: 20.6
.. versionchanged:: 21.0
@@ -123,8 +129,9 @@ class ChatAdministratorRights(TelegramObject):
.. versionadded:: 20.6
.. versionchanged:: 21.0
|non_optional_story_argument|
can_edit_stories (:obj:`bool`): :obj:`True`, if the administrator can edit
stories posted by other users.
can_edit_stories (:obj:`bool`): :obj:`True`, if the administrator can edit stories posted
by other users, post stories to the chat page, pin chat stories, and access the chat's
story archive
.. versionadded:: 20.6
.. versionchanged:: 21.0
@@ -169,13 +176,13 @@ class ChatAdministratorRights(TelegramObject):
can_promote_members: bool,
can_change_info: bool,
can_invite_users: bool,
can_post_stories: bool,
can_edit_stories: bool,
can_delete_stories: bool,
can_post_messages: Optional[bool] = None,
can_edit_messages: Optional[bool] = None,
can_pin_messages: Optional[bool] = None,
can_manage_topics: Optional[bool] = None,
can_post_stories: Optional[bool] = None,
can_edit_stories: Optional[bool] = None,
can_delete_stories: Optional[bool] = None,
*,
api_kwargs: Optional[JSONDict] = None,
) -> None:
@@ -189,12 +196,6 @@ class ChatAdministratorRights(TelegramObject):
self.can_promote_members: bool = can_promote_members
self.can_change_info: bool = can_change_info
self.can_invite_users: bool = can_invite_users
# Not actually optionals but because of backwards compatability we pretend they are
if can_post_stories is None or can_edit_stories is None or can_delete_stories is None:
raise TypeError(
"As of v21.0 can_post_stories, can_edit_stories and can_delete_stories"
" must be set in order to create this object."
)
self.can_post_stories: bool = can_post_stories
self.can_edit_stories: bool = can_edit_stories
self.can_delete_stories: bool = can_delete_stories
+540
View File
@@ -0,0 +1,540 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2024
# 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 objects related to chat backgrounds."""
from typing import TYPE_CHECKING, Dict, Final, Optional, Sequence, Tuple, Type
from telegram import constants
from telegram._files.document import Document
from telegram._telegramobject import TelegramObject
from telegram._utils import enum
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
from telegram import Bot
class BackgroundFill(TelegramObject):
"""Base class for Telegram BackgroundFill Objects. It can be one of:
* :class:`telegram.BackgroundFillSolid`
* :class:`telegram.BackgroundFillGradient`
* :class:`telegram.BackgroundFillFreeformGradient`
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`type` is equal.
.. versionadded:: 21.2
Args:
type (:obj:`str`): Type of the background fill. Can be one of:
:attr:`~telegram.BackgroundFill.SOLID`, :attr:`~telegram.BackgroundFill.GRADIENT`
or :attr:`~telegram.BackgroundFill.FREEFORM_GRADIENT`.
Attributes:
type (:obj:`str`): Type of the background fill. Can be one of:
:attr:`~telegram.BackgroundFill.SOLID`, :attr:`~telegram.BackgroundFill.GRADIENT`
or :attr:`~telegram.BackgroundFill.FREEFORM_GRADIENT`.
"""
__slots__ = ("type",)
SOLID: Final[constants.BackgroundFillType] = constants.BackgroundFillType.SOLID
""":const:`telegram.constants.BackgroundFillType.SOLID`"""
GRADIENT: Final[constants.BackgroundFillType] = constants.BackgroundFillType.GRADIENT
""":const:`telegram.constants.BackgroundFillType.GRADIENT`"""
FREEFORM_GRADIENT: Final[constants.BackgroundFillType] = (
constants.BackgroundFillType.FREEFORM_GRADIENT
)
""":const:`telegram.constants.BackgroundFillType.FREEFORM_GRADIENT`"""
def __init__(
self,
type: str, # pylint: disable=redefined-builtin
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
# Required by all subclasses
self.type: str = enum.get_member(constants.BackgroundFillType, type, type)
self._id_attrs = (self.type,)
self._freeze()
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["BackgroundFill"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
_class_mapping: Dict[str, Type[BackgroundFill]] = {
cls.SOLID: BackgroundFillSolid,
cls.GRADIENT: BackgroundFillGradient,
cls.FREEFORM_GRADIENT: BackgroundFillFreeformGradient,
}
if cls is BackgroundFill and data.get("type") in _class_mapping:
return _class_mapping[data.pop("type")].de_json(data=data, bot=bot)
return super().de_json(data=data, bot=bot)
class BackgroundFillSolid(BackgroundFill):
"""
The background is filled using the selected color.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`color` is equal.
.. versionadded:: 21.2
Args:
color (:obj:`int`): The color of the background fill in the `RGB24` format.
Attributes:
type (:obj:`str`): Type of the background fill. Always
:attr:`~telegram.BackgroundFill.SOLID`.
color (:obj:`int`): The color of the background fill in the `RGB24` format.
"""
__slots__ = ("color",)
def __init__(
self,
color: int,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(type=self.SOLID, api_kwargs=api_kwargs)
with self._unfrozen():
self.color: int = color
self._id_attrs = (self.color,)
class BackgroundFillGradient(BackgroundFill):
"""
The background is a gradient fill.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`top_color`, :attr:`bottom_color`
and :attr:`rotation_angle` are equal.
.. versionadded:: 21.2
Args:
top_color (:obj:`int`): Top color of the gradient in the `RGB24` format.
bottom_color (:obj:`int`): Bottom color of the gradient in the `RGB24` format.
rotation_angle (:obj:`int`): Clockwise rotation angle of the background
fill in degrees;
0-:tg-const:`telegram.constants.BackgroundFillLimit.MAX_ROTATION_ANGLE`.
Attributes:
type (:obj:`str`): Type of the background fill. Always
:attr:`~telegram.BackgroundFill.GRADIENT`.
top_color (:obj:`int`): Top color of the gradient in the `RGB24` format.
bottom_color (:obj:`int`): Bottom color of the gradient in the `RGB24` format.
rotation_angle (:obj:`int`): Clockwise rotation angle of the background
fill in degrees;
0-:tg-const:`telegram.constants.BackgroundFillLimit.MAX_ROTATION_ANGLE`.
"""
__slots__ = ("bottom_color", "rotation_angle", "top_color")
def __init__(
self,
top_color: int,
bottom_color: int,
rotation_angle: int,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(type=self.GRADIENT, api_kwargs=api_kwargs)
with self._unfrozen():
self.top_color: int = top_color
self.bottom_color: int = bottom_color
self.rotation_angle: int = rotation_angle
self._id_attrs = (self.top_color, self.bottom_color, self.rotation_angle)
class BackgroundFillFreeformGradient(BackgroundFill):
"""
The background is a freeform gradient that rotates after every message in the chat.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`colors` is equal.
.. versionadded:: 21.2
Args:
colors (Sequence[:obj:`int`]): A list of the 3 or 4 base colors that are used to
generate the freeform gradient in the `RGB24` format
Attributes:
type (:obj:`str`): Type of the background fill. Always
:attr:`~telegram.BackgroundFill.FREEFORM_GRADIENT`.
colors (Sequence[:obj:`int`]): A list of the 3 or 4 base colors that are used to
generate the freeform gradient in the `RGB24` format
"""
__slots__ = ("colors",)
def __init__(
self,
colors: Sequence[int],
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(type=self.FREEFORM_GRADIENT, api_kwargs=api_kwargs)
with self._unfrozen():
self.colors: Tuple[int, ...] = parse_sequence_arg(colors)
self._id_attrs = (self.colors,)
class BackgroundType(TelegramObject):
"""Base class for Telegram BackgroundType Objects. It can be one of:
* :class:`telegram.BackgroundTypeFill`
* :class:`telegram.BackgroundTypeWallpaper`
* :class:`telegram.BackgroundTypePattern`
* :class:`telegram.BackgroundTypeChatTheme`.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`type` is equal.
.. versionadded:: 21.2
Args:
type (:obj:`str`): Type of the background. Can be one of:
:attr:`~telegram.BackgroundType.FILL`, :attr:`~telegram.BackgroundType.WALLPAPER`
:attr:`~telegram.BackgroundType.PATTERN` or
:attr:`~telegram.BackgroundType.CHAT_THEME`.
Attributes:
type (:obj:`str`): Type of the background. Can be one of:
:attr:`~telegram.BackgroundType.FILL`, :attr:`~telegram.BackgroundType.WALLPAPER`
:attr:`~telegram.BackgroundType.PATTERN` or
:attr:`~telegram.BackgroundType.CHAT_THEME`.
"""
__slots__ = ("type",)
FILL: Final[constants.BackgroundTypeType] = constants.BackgroundTypeType.FILL
""":const:`telegram.constants.BackgroundTypeType.FILL`"""
WALLPAPER: Final[constants.BackgroundTypeType] = constants.BackgroundTypeType.WALLPAPER
""":const:`telegram.constants.BackgroundTypeType.WALLPAPER`"""
PATTERN: Final[constants.BackgroundTypeType] = constants.BackgroundTypeType.PATTERN
""":const:`telegram.constants.BackgroundTypeType.PATTERN`"""
CHAT_THEME: Final[constants.BackgroundTypeType] = constants.BackgroundTypeType.CHAT_THEME
""":const:`telegram.constants.BackgroundTypeType.CHAT_THEME`"""
def __init__(
self,
type: str, # pylint: disable=redefined-builtin
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
# Required by all subclasses
self.type: str = enum.get_member(constants.BackgroundTypeType, type, type)
self._id_attrs = (self.type,)
self._freeze()
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["BackgroundType"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
_class_mapping: Dict[str, Type[BackgroundType]] = {
cls.FILL: BackgroundTypeFill,
cls.WALLPAPER: BackgroundTypeWallpaper,
cls.PATTERN: BackgroundTypePattern,
cls.CHAT_THEME: BackgroundTypeChatTheme,
}
if cls is BackgroundType and data.get("type") in _class_mapping:
return _class_mapping[data.pop("type")].de_json(data=data, bot=bot)
if "fill" in data:
data["fill"] = BackgroundFill.de_json(data.get("fill"), bot)
if "document" in data:
data["document"] = Document.de_json(data.get("document"), bot)
return super().de_json(data=data, bot=bot)
class BackgroundTypeFill(BackgroundType):
"""
The background is automatically filled based on the selected colors.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`fill` and :attr:`dark_theme_dimming` are equal.
.. versionadded:: 21.2
Args:
fill (:obj:`telegram.BackgroundFill`): The background fill.
dark_theme_dimming (:obj:`int`): Dimming of the background in dark themes, as a
percentage;
0-:tg-const:`telegram.constants.BackgroundTypeLimit.MAX_DIMMING`.
Attributes:
type (:obj:`str`): Type of the background. Always
:attr:`~telegram.BackgroundType.FILL`.
fill (:obj:`telegram.BackgroundFill`): The background fill.
dark_theme_dimming (:obj:`int`): Dimming of the background in dark themes, as a
percentage;
0-:tg-const:`telegram.constants.BackgroundTypeLimit.MAX_DIMMING`.
"""
__slots__ = ("dark_theme_dimming", "fill")
def __init__(
self,
fill: BackgroundFill,
dark_theme_dimming: int,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(type=self.FILL, api_kwargs=api_kwargs)
with self._unfrozen():
self.fill: BackgroundFill = fill
self.dark_theme_dimming: int = dark_theme_dimming
self._id_attrs = (self.fill, self.dark_theme_dimming)
class BackgroundTypeWallpaper(BackgroundType):
"""
The background is a wallpaper in the `JPEG` format.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`document` and :attr:`dark_theme_dimming` are equal.
.. versionadded:: 21.2
Args:
document (:obj:`telegram.Document`): Document with the wallpaper
dark_theme_dimming (:obj:`int`): Dimming of the background in dark themes, as a
percentage;
0-:tg-const:`telegram.constants.BackgroundTypeLimit.MAX_DIMMING`.
is_blurred (:obj:`bool`, optional): :obj:`True`, if the wallpaper is downscaled to fit
in a 450x450 square and then box-blurred with radius 12
is_moving (:obj:`bool`, optional): :obj:`True`, if the background moves slightly
when the device is tilted
Attributes:
type (:obj:`str`): Type of the background. Always
:attr:`~telegram.BackgroundType.WALLPAPER`.
document (:obj:`telegram.Document`): Document with the wallpaper
dark_theme_dimming (:obj:`int`): Dimming of the background in dark themes, as a
percentage;
0-:tg-const:`telegram.constants.BackgroundTypeLimit.MAX_DIMMING`.
is_blurred (:obj:`bool`): Optional. :obj:`True`, if the wallpaper is downscaled to fit
in a 450x450 square and then box-blurred with radius 12
is_moving (:obj:`bool`): Optional. :obj:`True`, if the background moves slightly
when the device is tilted
"""
__slots__ = ("dark_theme_dimming", "document", "is_blurred", "is_moving")
def __init__(
self,
document: Document,
dark_theme_dimming: int,
is_blurred: Optional[bool] = None,
is_moving: Optional[bool] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(type=self.WALLPAPER, api_kwargs=api_kwargs)
with self._unfrozen():
# Required
self.document: Document = document
self.dark_theme_dimming: int = dark_theme_dimming
# Optionals
self.is_blurred: Optional[bool] = is_blurred
self.is_moving: Optional[bool] = is_moving
self._id_attrs = (self.document, self.dark_theme_dimming)
class BackgroundTypePattern(BackgroundType):
"""
The background is a `PNG` or `TGV` (gzipped subset of `SVG` with `MIME` type
`"application/x-tgwallpattern"`) pattern to be combined with the background fill
chosen by the user.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`document` and :attr:`fill` and :attr:`intensity` are equal.
.. versionadded:: 21.2
Args:
document (:obj:`telegram.Document`): Document with the pattern.
fill (:obj:`telegram.BackgroundFill`): The background fill that is combined with
the pattern.
intensity (:obj:`int`): Intensity of the pattern when it is shown above the filled
background;
0-:tg-const:`telegram.constants.BackgroundTypeLimit.MAX_INTENSITY`.
is_inverted (:obj:`int`, optional): :obj:`True`, if the background fill must be applied
only to the pattern itself. All other pixels are black in this case. For dark
themes only.
is_moving (:obj:`bool`, optional): :obj:`True`, if the background moves slightly
when the device is tilted.
Attributes:
type (:obj:`str`): Type of the background. Always
:attr:`~telegram.BackgroundType.PATTERN`.
document (:obj:`telegram.Document`): Document with the pattern.
fill (:obj:`telegram.BackgroundFill`): The background fill that is combined with
the pattern.
intensity (:obj:`int`): Intensity of the pattern when it is shown above the filled
background;
0-:tg-const:`telegram.constants.BackgroundTypeLimit.MAX_INTENSITY`.
is_inverted (:obj:`int`): Optional. :obj:`True`, if the background fill must be applied
only to the pattern itself. All other pixels are black in this case. For dark
themes only.
is_moving (:obj:`bool`): Optional. :obj:`True`, if the background moves slightly
when the device is tilted.
"""
__slots__ = (
"document",
"fill",
"intensity",
"is_inverted",
"is_moving",
)
def __init__(
self,
document: Document,
fill: BackgroundFill,
intensity: int,
is_inverted: Optional[bool] = None,
is_moving: Optional[bool] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(type=self.PATTERN, api_kwargs=api_kwargs)
with self._unfrozen():
# Required
self.document: Document = document
self.fill: BackgroundFill = fill
self.intensity: int = intensity
# Optionals
self.is_inverted: Optional[bool] = is_inverted
self.is_moving: Optional[bool] = is_moving
self._id_attrs = (self.document, self.fill, self.intensity)
class BackgroundTypeChatTheme(BackgroundType):
"""
The background is taken directly from a built-in chat theme.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`theme_name` is equal.
.. versionadded:: 21.2
Args:
theme_name (:obj:`str`): Name of the chat theme, which is usually an emoji.
Attributes:
type (:obj:`str`): Type of the background. Always
:attr:`~telegram.BackgroundType.CHAT_THEME`.
theme_name (:obj:`str`): Name of the chat theme, which is usually an emoji.
"""
__slots__ = ("theme_name",)
def __init__(
self,
theme_name: str,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(type=self.CHAT_THEME, api_kwargs=api_kwargs)
with self._unfrozen():
self.theme_name: str = theme_name
self._id_attrs = (self.theme_name,)
class ChatBackground(TelegramObject):
"""
This object represents a chat background.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`type` is equal.
.. versionadded:: 21.2
Args:
type (:obj:`telegram.BackgroundType`): Type of the background.
Attributes:
type (:obj:`telegram.BackgroundType`): Type of the background.
"""
__slots__ = ("type",)
def __init__(
self,
type: BackgroundType, # pylint: disable=redefined-builtin
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.type: BackgroundType = type
self._id_attrs = (self.type,)
self._freeze()
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["ChatBackground"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["type"] = BackgroundType.de_json(data.get("type"), bot)
return super().de_json(data=data, bot=bot)
+166
View File
@@ -0,0 +1,166 @@
#!/usr/bin/env python
# pylint: disable=redefined-builtin
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2024
# 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 ChatFullInfo."""
from datetime import datetime
from typing import TYPE_CHECKING, Optional, Sequence
from telegram._birthdate import Birthdate
from telegram._chat import Chat
from telegram._chatlocation import ChatLocation
from telegram._chatpermissions import ChatPermissions
from telegram._files.chatphoto import ChatPhoto
from telegram._reaction import ReactionType
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
from telegram import BusinessIntro, BusinessLocation, BusinessOpeningHours, Message
class ChatFullInfo(Chat):
"""
This object contains full information about a chat.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`~telegram.Chat.id` is equal.
Caution:
This class is a subclass of :class:`telegram.Chat` and inherits all attributes and methods
for backwards compatibility. In the future, this class will *NOT* inherit from
:class:`telegram.Chat`.
.. seealso::
All arguments and attributes can be found in :class:`telegram.Chat`.
.. versionadded:: 21.2
Args:
max_reaction_count (:obj:`int`): The maximum number of reactions that can be set on a
message in the chat.
.. versionadded:: 21.2
Attributes:
max_reaction_count (:obj:`int`): The maximum number of reactions that can be set on a
message in the chat.
.. versionadded:: 21.2
"""
__slots__ = ("max_reaction_count",)
def __init__(
self,
id: int,
type: str,
accent_color_id: int, # API 7.3 made this argument required
max_reaction_count: int, # NEW arg in api 7.3 and is required
title: Optional[str] = None,
username: Optional[str] = None,
first_name: Optional[str] = None,
last_name: Optional[str] = None,
is_forum: Optional[bool] = None,
photo: Optional[ChatPhoto] = None,
active_usernames: Optional[Sequence[str]] = None,
birthdate: Optional[Birthdate] = None,
business_intro: Optional["BusinessIntro"] = None,
business_location: Optional["BusinessLocation"] = None,
business_opening_hours: Optional["BusinessOpeningHours"] = None,
personal_chat: Optional["Chat"] = None,
available_reactions: Optional[Sequence[ReactionType]] = None,
background_custom_emoji_id: Optional[str] = None,
profile_accent_color_id: Optional[int] = None,
profile_background_custom_emoji_id: Optional[str] = None,
emoji_status_custom_emoji_id: Optional[str] = None,
emoji_status_expiration_date: Optional[datetime] = None,
bio: Optional[str] = None,
has_private_forwards: Optional[bool] = None,
has_restricted_voice_and_video_messages: Optional[bool] = None,
join_to_send_messages: Optional[bool] = None,
join_by_request: Optional[bool] = None,
description: Optional[str] = None,
invite_link: Optional[str] = None,
pinned_message: Optional["Message"] = None,
permissions: Optional[ChatPermissions] = None,
slow_mode_delay: Optional[int] = None,
unrestrict_boost_count: Optional[int] = None,
message_auto_delete_time: Optional[int] = None,
has_aggressive_anti_spam_enabled: Optional[bool] = None,
has_hidden_members: Optional[bool] = None,
has_protected_content: Optional[bool] = None,
has_visible_history: Optional[bool] = None,
sticker_set_name: Optional[str] = None,
can_set_sticker_set: Optional[bool] = None,
custom_emoji_sticker_set_name: Optional[str] = None,
linked_chat_id: Optional[int] = None,
location: Optional[ChatLocation] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(
id=id,
type=type,
title=title,
username=username,
first_name=first_name,
last_name=last_name,
photo=photo,
description=description,
invite_link=invite_link,
pinned_message=pinned_message,
permissions=permissions,
sticker_set_name=sticker_set_name,
can_set_sticker_set=can_set_sticker_set,
slow_mode_delay=slow_mode_delay,
bio=bio,
linked_chat_id=linked_chat_id,
location=location,
message_auto_delete_time=message_auto_delete_time,
has_private_forwards=has_private_forwards,
has_protected_content=has_protected_content,
join_to_send_messages=join_to_send_messages,
join_by_request=join_by_request,
has_restricted_voice_and_video_messages=has_restricted_voice_and_video_messages,
is_forum=is_forum,
active_usernames=active_usernames,
emoji_status_custom_emoji_id=emoji_status_custom_emoji_id,
emoji_status_expiration_date=emoji_status_expiration_date,
has_aggressive_anti_spam_enabled=has_aggressive_anti_spam_enabled,
has_hidden_members=has_hidden_members,
available_reactions=available_reactions,
accent_color_id=accent_color_id,
background_custom_emoji_id=background_custom_emoji_id,
profile_accent_color_id=profile_accent_color_id,
profile_background_custom_emoji_id=profile_background_custom_emoji_id,
has_visible_history=has_visible_history,
unrestrict_boost_count=unrestrict_boost_count,
custom_emoji_sticker_set_name=custom_emoji_sticker_set_name,
birthdate=birthdate,
personal_chat=personal_chat,
business_intro=business_intro,
business_location=business_location,
business_opening_hours=business_opening_hours,
api_kwargs=api_kwargs,
)
# Required and unique to this class-
with self._unfrozen():
self.max_reaction_count: int = max_reaction_count
self._freeze()
+14 -13
View File
@@ -191,6 +191,11 @@ class ChatMemberAdministrator(ChatMember):
* The argument :paramref:`can_manage_topics` was added, which changes the position of the
optional argument :paramref:`custom_title`.
.. versionchanged:: 21.1
As of this version, :attr:`can_post_stories`, :attr:`can_edit_stories`,
and :attr:`can_delete_stories` is now required. Thus, the order of arguments had to be
changed.
Args:
user (:class:`telegram.User`): Information about the user.
can_be_edited (:obj:`bool`): :obj:`True`, if the bot
@@ -230,8 +235,9 @@ class ChatMemberAdministrator(ChatMember):
.. versionadded:: 20.6
.. versionchanged:: 21.0
|non_optional_story_argument|
can_edit_stories (:obj:`bool`): :obj:`True`, if the administrator can edit
stories posted by other users.
can_edit_stories (:obj:`bool`): :obj:`True`, if the administrator can edit stories posted
by other users, post stories to the chat page, pin chat stories, and access the chat's
story archive
.. versionadded:: 20.6
.. versionchanged:: 21.0
@@ -289,8 +295,9 @@ class ChatMemberAdministrator(ChatMember):
.. versionadded:: 20.6
.. versionchanged:: 21.0
|non_optional_story_argument|
can_edit_stories (:obj:`bool`): :obj:`True`, if the administrator can edit
stories posted by other users.
can_edit_stories (:obj:`bool`): :obj:`True`, if the administrator can edit stories posted
by other users, post stories to the chat page, pin chat stories, and access the chat's
story archive
.. versionadded:: 20.6
.. versionchanged:: 21.0
@@ -340,14 +347,14 @@ class ChatMemberAdministrator(ChatMember):
can_promote_members: bool,
can_change_info: bool,
can_invite_users: bool,
can_post_stories: bool,
can_edit_stories: bool,
can_delete_stories: bool,
can_post_messages: Optional[bool] = None,
can_edit_messages: Optional[bool] = None,
can_pin_messages: Optional[bool] = None,
can_manage_topics: Optional[bool] = None,
custom_title: Optional[str] = None,
can_post_stories: Optional[bool] = None,
can_edit_stories: Optional[bool] = None,
can_delete_stories: Optional[bool] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@@ -362,12 +369,6 @@ class ChatMemberAdministrator(ChatMember):
self.can_promote_members: bool = can_promote_members
self.can_change_info: bool = can_change_info
self.can_invite_users: bool = can_invite_users
# Not actually optionals but because of backwards compatability we pretend they are
if can_post_stories is None or can_edit_stories is None or can_delete_stories is None:
raise TypeError(
"As of 21.0 can_post_stories, can_edit_stories and can_delete_stories "
"must be set in order to create this object."
)
self.can_post_stories: bool = can_post_stories
self.can_edit_stories: bool = can_edit_stories
self.can_delete_stories: bool = can_delete_stories
+13
View File
@@ -63,6 +63,11 @@ class ChatMemberUpdated(TelegramObject):
chat via a chat folder invite link
.. versionadded:: 20.3
via_join_request (:obj:`bool`, optional): :obj:`True`, if the user joined the chat after
sending a direct join request without using an invite link and being approved by
an administrator
.. versionadded:: 21.2
Attributes:
chat (:class:`telegram.Chat`): Chat the user belongs to.
@@ -80,6 +85,11 @@ class ChatMemberUpdated(TelegramObject):
chat via a chat folder invite link
.. versionadded:: 20.3
via_join_request (:obj:`bool`): Optional. :obj:`True`, if the user joined the chat after
sending a direct join request without using an invite link and being approved
by an administrator
.. versionadded:: 21.2
"""
@@ -91,6 +101,7 @@ class ChatMemberUpdated(TelegramObject):
"new_chat_member",
"old_chat_member",
"via_chat_folder_invite_link",
"via_join_request",
)
def __init__(
@@ -102,6 +113,7 @@ class ChatMemberUpdated(TelegramObject):
new_chat_member: ChatMember,
invite_link: Optional[ChatInviteLink] = None,
via_chat_folder_invite_link: Optional[bool] = None,
via_join_request: Optional[bool] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@@ -116,6 +128,7 @@ class ChatMemberUpdated(TelegramObject):
# Optionals
self.invite_link: Optional[ChatInviteLink] = invite_link
self.via_join_request: Optional[bool] = via_join_request
self._id_attrs = (
self.chat,
+21 -1
View File
@@ -36,6 +36,10 @@ class InputSticker(TelegramObject):
.. versionadded:: 20.2
.. versionchanged:: 21.1
As of Bot API 7.2, the new argument :paramref:`format` is a required argument, and thus the
order of the arguments has changed.
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
@@ -52,6 +56,13 @@ class InputSticker(TelegramObject):
: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.
format (:obj:`str`): Format of the added sticker, must be one of
:tg-const:`telegram.constants.StickerFormat.STATIC` for a
``.WEBP`` or ``.PNG`` image, :tg-const:`telegram.constants.StickerFormat.ANIMATED`
for a ``.TGS`` animation, :tg-const:`telegram.constants.StickerFormat.VIDEO` for a WEBM
video.
.. versionadded:: 21.1
Attributes:
sticker (:obj:`str` | :class:`telegram.InputFile`): The added sticker.
@@ -67,15 +78,23 @@ class InputSticker(TelegramObject):
: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.
":tg-const:`telegram.constants.StickerType.CUSTOM_EMOJI`" stickers only.
format (:obj:`str`): Format of the added sticker, must be one of
:tg-const:`telegram.constants.StickerFormat.STATIC` for a
``.WEBP`` or ``.PNG`` image, :tg-const:`telegram.constants.StickerFormat.ANIMATED`
for a ``.TGS`` animation, :tg-const:`telegram.constants.StickerFormat.VIDEO` for a WEBM
video.
.. versionadded:: 21.1
"""
__slots__ = ("emoji_list", "keywords", "mask_position", "sticker")
__slots__ = ("emoji_list", "format", "keywords", "mask_position", "sticker")
def __init__(
self,
sticker: FileInput,
emoji_list: Sequence[str],
format: str, # pylint: disable=redefined-builtin
mask_position: Optional[MaskPosition] = None,
keywords: Optional[Sequence[str]] = None,
*,
@@ -91,6 +110,7 @@ class InputSticker(TelegramObject):
attach=True,
)
self.emoji_list: Tuple[str, ...] = parse_sequence_arg(emoji_list)
self.format: str = format
self.mask_position: Optional[MaskPosition] = mask_position
self.keywords: Tuple[str, ...] = parse_sequence_arg(keywords)
+9 -16
View File
@@ -227,16 +227,20 @@ class StickerSet(TelegramObject):
.. versionchanged:: 20.0
The parameter ``contains_masks`` has been removed. Use :paramref:`sticker_type` instead.
.. versionchanged:: 21.1
The parameters ``is_video`` and ``is_animated`` are deprecated and now made optional. Thus,
the order of the arguments had to be changed.
.. versionchanged:: 20.5
|removed_thumb_note|
.. versionremoved:: 21.2
Removed the deprecated arguments and attributes ``is_animated`` and ``is_video``.
Args:
name (:obj:`str`): Sticker set name.
title (:obj:`str`): Sticker set title.
is_animated (:obj:`bool`): :obj:`True`, if the sticker set contains animated stickers.
is_video (:obj:`bool`): :obj:`True`, if the sticker set contains video stickers.
.. versionadded:: 13.11
stickers (Sequence[:class:`telegram.Sticker`]): List of all set stickers.
.. versionchanged:: 20.0
@@ -255,10 +259,6 @@ class StickerSet(TelegramObject):
Attributes:
name (:obj:`str`): Sticker set name.
title (:obj:`str`): Sticker set title.
is_animated (:obj:`bool`): :obj:`True`, if the sticker set contains animated stickers.
is_video (:obj:`bool`): :obj:`True`, if the sticker set contains video stickers.
.. versionadded:: 13.11
stickers (Tuple[:class:`telegram.Sticker`]): List of all set stickers.
.. versionchanged:: 20.0
@@ -276,8 +276,6 @@ class StickerSet(TelegramObject):
"""
__slots__ = (
"is_animated",
"is_video",
"name",
"sticker_type",
"stickers",
@@ -289,9 +287,7 @@ class StickerSet(TelegramObject):
self,
name: str,
title: str,
is_animated: bool,
stickers: Sequence[Sticker],
is_video: bool,
sticker_type: str,
thumbnail: Optional[PhotoSize] = None,
*,
@@ -300,12 +296,9 @@ class StickerSet(TelegramObject):
super().__init__(api_kwargs=api_kwargs)
self.name: str = name
self.title: str = title
self.is_animated: bool = is_animated
self.is_video: bool = is_video
self.stickers: Tuple[Sticker, ...] = parse_sequence_arg(stickers)
self.sticker_type: str = sticker_type
# Optional
self.thumbnail: Optional[PhotoSize] = thumbnail
self._id_attrs = (self.name,)
@@ -323,7 +316,7 @@ class StickerSet(TelegramObject):
api_kwargs = {}
# 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"):
for deprecated_field in ("contains_masks", "thumb", "is_animated", "is_video"):
if deprecated_field in data:
api_kwargs[deprecated_field] = data.pop(deprecated_field)
+2 -1
View File
@@ -30,7 +30,8 @@ class ForceReply(TelegramObject):
Upon receiving a message with this object, Telegram clients will display a reply interface to
the user (act as if the user has selected the bot's message and tapped 'Reply'). This can be
extremely useful if you want to create user-friendly step-by-step interfaces without having
to sacrifice privacy mode.
to sacrifice `privacy mode <https://core.telegram.org/bots/features#privacy-mode>`_. Not
supported in channels and for messages sent on behalf of a Telegram Business account.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`selective` is equal.
+30 -26
View File
@@ -91,6 +91,7 @@ class InlineKeyboardButton(TelegramObject):
to the bot when button is pressed, UTF-8
:tg-const:`telegram.InlineKeyboardButton.MIN_CALLBACK_DATA`-
:tg-const:`telegram.InlineKeyboardButton.MAX_CALLBACK_DATA` bytes.
Not supported for messages sent on behalf of a Telegram Business account.
If the bot instance allows arbitrary callback data, anything can be passed.
Tip:
@@ -102,25 +103,25 @@ class InlineKeyboardButton(TelegramObject):
<https://core.telegram.org/bots/webapps>`_ that will be launched when the user presses
the button. The Web App will be able to send an arbitrary message on behalf of the user
using the method :meth:`~telegram.Bot.answer_web_app_query`. Available only in
private chats between a user and the bot.
private chats between a user and the bot. Not supported for messages sent on behalf of
a Telegram Business account.
.. versionadded:: 20.0
switch_inline_query (:obj:`str`, optional): If set, pressing the button will prompt the
user to select one of their chats, open that chat and insert the bot's username and the
specified inline query in the input field. Can be empty, in which case just the bot's
username will be inserted. This offers an easy way for users to start using your bot
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.
switch_inline_query (: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. May be
empty, in which case only the bot's username will be inserted.
This offers a quick way for the user to open your bot in inline mode in the same chat -
good for selecting something from multiple options. Not supported in channels and for
messages sent on behalf of a Telegram Business account.
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
offers a quick way for the user to open your bot in inline mode in the same chat - good
for selecting something from multiple options.
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. Not
supported for messages sent on behalf of a Telegram Business account.
callback_game (:class:`telegram.CallbackGame`, optional): Description of the game that will
be launched when the user presses the button. This type of button **must** always be
the **first** button in the first row.
@@ -130,7 +131,8 @@ class InlineKeyboardButton(TelegramObject):
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.
query in the input field. Not supported for messages sent on behalf of a Telegram
Business account.
.. versionadded:: 20.3
@@ -159,29 +161,30 @@ class InlineKeyboardButton(TelegramObject):
to the bot when button is pressed, UTF-8
:tg-const:`telegram.InlineKeyboardButton.MIN_CALLBACK_DATA`-
:tg-const:`telegram.InlineKeyboardButton.MAX_CALLBACK_DATA` bytes.
Not supported for messages sent on behalf of a Telegram Business account.
web_app (:obj:`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 send an arbitrary message on behalf of the user
using the method :meth:`~telegram.Bot.answer_web_app_query`. Available only in
private chats between a user and the bot.
private chats between a user and the bot. Not supported for messages sent on behalf of
a Telegram Business account.
.. versionadded:: 20.0
switch_inline_query (:obj:`str`): Optional. If set, pressing the button will prompt the
user to select one of their chats, open that chat and insert the bot's username and the
specified inline query in the input field. Can be empty, in which case just the bot's
username will be inserted. This offers an easy way for users to start using your bot
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.
switch_inline_query (: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. May be
empty, in which case only the bot's username will be inserted.
This offers a quick way for the user to open your bot in inline mode in the same chat -
good for selecting something from multiple options. Not supported in channels and for
messages sent on behalf of a Telegram Business account.
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
offers a quick way for the user to open your bot in inline mode in the same chat - good
for selecting something from multiple options.
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. Not
supported for messages sent on behalf of a Telegram Business account.
callback_game (:class:`telegram.CallbackGame`): Optional. Description of the game that will
be launched when the user presses the button. This type of button **must** always be
the **first** button in the first row.
@@ -191,7 +194,8 @@ class InlineKeyboardButton(TelegramObject):
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.
query in the input field. Not supported for messages sent on behalf of a Telegram
Business account.
.. versionadded:: 20.3
@@ -89,7 +89,9 @@ class InlineQueryResultLocation(InlineQueryResult):
live_period (:obj:`int`): Optional. Period in seconds for which the location will be
updated, should be between
:tg-const:`telegram.InlineQueryResultLocation.MIN_LIVE_PERIOD` and
:tg-const:`telegram.InlineQueryResultLocation.MAX_LIVE_PERIOD`.
:tg-const:`telegram.InlineQueryResultLocation.MAX_LIVE_PERIOD` or
:tg-const:`telegram.constants.LocationLimit.LIVE_PERIOD_FOREVER` for live
locations that can be edited indefinitely.
heading (:obj:`int`): Optional. For live locations, a direction in which the user is
moving, in degrees. Must be between
:tg-const:`telegram.InlineQueryResultLocation.MIN_HEADING` and
@@ -42,7 +42,9 @@ class InputLocationMessageContent(InputMessageContent):
live_period (:obj:`int`, optional): Period in seconds for which the location will be
updated, should be between
:tg-const:`telegram.InputLocationMessageContent.MIN_LIVE_PERIOD` and
:tg-const:`telegram.InputLocationMessageContent.MAX_LIVE_PERIOD`.
:tg-const:`telegram.InputLocationMessageContent.MAX_LIVE_PERIOD` or
:tg-const:`telegram.constants.LocationLimit.LIVE_PERIOD_FOREVER` for live
locations that can be edited indefinitely.
heading (:obj:`int`, optional): For live locations, a direction in which the user is
moving, in degrees. Must be between
:tg-const:`telegram.InputLocationMessageContent.MIN_HEADING` and
+59 -1
View File
@@ -17,6 +17,7 @@
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains two objects to request chats/users."""
from typing import TYPE_CHECKING, Optional
from telegram._chatadministratorrights import ChatAdministratorRights
@@ -56,6 +57,16 @@ class KeyboardButtonRequestUsers(TelegramObject):
.
.. versionadded:: 20.8
request_name (:obj:`bool`, optional): Pass :obj:`True` to request the users' first and last
name.
.. versionadded:: 21.1
request_username (:obj:`bool`, optional): Pass :obj:`True` to request the users' username.
.. versionadded:: 21.1
request_photo (:obj:`bool`, optional): Pass :obj:`True` to request the users' photo.
.. versionadded:: 21.1
Attributes:
request_id (:obj:`int`): Identifier of the request.
@@ -71,11 +82,25 @@ class KeyboardButtonRequestUsers(TelegramObject):
.
.. versionadded:: 20.8
request_name (:obj:`bool`): Optional. Pass :obj:`True` to request the users' first and last
name.
.. versionadded:: 21.1
request_username (:obj:`bool`): Optional. Pass :obj:`True` to request the users' username.
.. versionadded:: 21.1
request_photo (:obj:`bool`): Optional. Pass :obj:`True` to request the users' photo.
.. versionadded:: 21.1
"""
__slots__ = (
"max_quantity",
"request_id",
"request_name",
"request_photo",
"request_username",
"user_is_bot",
"user_is_premium",
)
@@ -86,6 +111,9 @@ class KeyboardButtonRequestUsers(TelegramObject):
user_is_bot: Optional[bool] = None,
user_is_premium: Optional[bool] = None,
max_quantity: Optional[int] = None,
request_name: Optional[bool] = None,
request_username: Optional[bool] = None,
request_photo: Optional[bool] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@@ -97,6 +125,9 @@ class KeyboardButtonRequestUsers(TelegramObject):
self.user_is_bot: Optional[bool] = user_is_bot
self.user_is_premium: Optional[bool] = user_is_premium
self.max_quantity: Optional[int] = max_quantity
self.request_name: Optional[bool] = request_name
self.request_username: Optional[bool] = request_username
self.request_photo: Optional[bool] = request_photo
self._id_attrs = (self.request_id,)
@@ -138,6 +169,15 @@ class KeyboardButtonRequestChat(TelegramObject):
applied.
bot_is_member (:obj:`bool`, optional): Pass :obj:`True` to request a chat with the bot
as a member. Otherwise, no additional restrictions are applied.
request_title (:obj:`bool`, optional): Pass :obj:`True` to request the chat's title.
.. versionadded:: 21.1
request_username (:obj:`bool`, optional): Pass :obj:`True` to request the chat's username.
.. versionadded:: 21.1
request_photo (:obj:`bool`, optional): Pass :obj:`True` to request the chat's photo.
.. versionadded:: 21.1
Attributes:
request_id (:obj:`int`): Identifier of the request.
chat_is_channel (:obj:`bool`): Pass :obj:`True` to request a channel chat, pass
@@ -145,7 +185,7 @@ class KeyboardButtonRequestChat(TelegramObject):
chat_is_forum (:obj:`bool`): Optional. Pass :obj:`True` to request a forum supergroup, pass
:obj:`False` to request a non-forum chat. If not specified, no additional
restrictions are applied.
chat_has_username (:obj:`bool`, optional): Pass :obj:`True` to request a supergroup or a
chat_has_username (:obj:`bool`): Optional. Pass :obj:`True` to request a supergroup or a
channel with a username, pass :obj:`False` to request a chat without a username. If
not specified, no additional restrictions are applied.
chat_is_created (:obj:`bool`) Optional. Pass :obj:`True` to request a chat owned by the
@@ -159,6 +199,15 @@ class KeyboardButtonRequestChat(TelegramObject):
applied.
bot_is_member (:obj:`bool`) Optional. Pass :obj:`True` to request a chat with the bot
as a member. Otherwise, no additional restrictions are applied.
request_title (:obj:`bool`): Optional. Pass :obj:`True` to request the chat's title.
.. versionadded:: 21.1
request_username (:obj:`bool`): Optional. Pass :obj:`True` to request the chat's username.
.. versionadded:: 21.1
request_photo (:obj:`bool`): Optional. Pass :obj:`True` to request the chat's photo.
.. versionadded:: 21.1
"""
__slots__ = (
@@ -169,6 +218,9 @@ class KeyboardButtonRequestChat(TelegramObject):
"chat_is_created",
"chat_is_forum",
"request_id",
"request_photo",
"request_title",
"request_username",
"user_administrator_rights",
)
@@ -182,6 +234,9 @@ class KeyboardButtonRequestChat(TelegramObject):
user_administrator_rights: Optional[ChatAdministratorRights] = None,
bot_administrator_rights: Optional[ChatAdministratorRights] = None,
bot_is_member: Optional[bool] = None,
request_title: Optional[bool] = None,
request_username: Optional[bool] = None,
request_photo: Optional[bool] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@@ -199,6 +254,9 @@ class KeyboardButtonRequestChat(TelegramObject):
)
self.bot_administrator_rights: Optional[ChatAdministratorRights] = bot_administrator_rights
self.bot_is_member: Optional[bool] = bot_is_member
self.request_title: Optional[bool] = request_title
self.request_username: Optional[bool] = request_username
self.request_photo: Optional[bool] = request_photo
self._id_attrs = (self.request_id,)
+367 -68
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -153,15 +153,15 @@ class EncryptedCredentials(TelegramObject):
self._id_attrs = (self.data, self.hash, self.secret)
self._decrypted_secret: Optional[str] = None
self._decrypted_secret: Optional[bytes] = None
self._decrypted_data: Optional[Credentials] = None
self._freeze()
@property
def decrypted_secret(self) -> str:
def decrypted_secret(self) -> bytes:
"""
:obj:`str`: Lazily decrypt and return secret.
:obj:`bytes`: Lazily decrypt and return secret.
Raises:
telegram.error.PassportDecryptionError: Decryption failed. Usually due to bad
+12 -9
View File
@@ -60,8 +60,8 @@ class EncryptedPassportElement(TelegramObject):
email (:obj:`str`, optional): User's verified email address; available only for "email"
type.
files (Sequence[:class:`telegram.PassportFile`], optional): Array of encrypted/decrypted
files with documents provided by the user; available only for "utility_bill",
"bank_statement", "rental_agreement", "passport_registration" and
files with documents provided by the user; available only for "utility_bill",
"bank_statement", "rental_agreement", "passport_registration" and
"temporary_registration" types.
.. versionchanged:: 20.0
@@ -74,12 +74,12 @@ class EncryptedPassportElement(TelegramObject):
reverse side of the document, provided by the user; Available only for
"driver_license" and "identity_card".
selfie (:class:`telegram.PassportFile`, optional): Encrypted/decrypted file with the
selfie of the user holding a document, provided by the user; available if requested for
selfie of the user holding a document, provided by the user; available if requested for
"passport", "driver_license", "identity_card" and "internal_passport".
translation (Sequence[:class:`telegram.PassportFile`], optional): Array of
encrypted/decrypted files with translated versions of documents provided by the user;
available if requested requested for "passport", "driver_license", "identity_card",
"internal_passport", "utility_bill", "bank_statement", "rental_agreement",
encrypted/decrypted files with translated versions of documents provided by the user;
available if requested requested for "passport", "driver_license", "identity_card",
"internal_passport", "utility_bill", "bank_statement", "rental_agreement",
"passport_registration" and "temporary_registration" types.
.. versionchanged:: 20.0
@@ -101,8 +101,8 @@ class EncryptedPassportElement(TelegramObject):
email (:obj:`str`): Optional. User's verified email address; available only for "email"
type.
files (Tuple[:class:`telegram.PassportFile`]): Optional. Array of encrypted/decrypted
files with documents provided by the user; available only for "utility_bill",
"bank_statement", "rental_agreement", "passport_registration" and
files with documents provided by the user; available only for "utility_bill",
"bank_statement", "rental_agreement", "passport_registration" and
"temporary_registration" types.
.. versionchanged:: 20.0
@@ -157,7 +157,10 @@ class EncryptedPassportElement(TelegramObject):
reverse_side: Optional[PassportFile] = None,
selfie: Optional[PassportFile] = None,
translation: Optional[Sequence[PassportFile]] = None,
credentials: Optional["Credentials"] = None, # pylint: disable=unused-argument
# TODO: Remove the credentials argument in 22.0 or later
credentials: Optional[ # pylint: disable=unused-argument # noqa: ARG002
"Credentials"
] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
+11 -7
View File
@@ -210,9 +210,11 @@ class PassportElementErrorFiles(PassportElementError):
This attribute will return a tuple instead of a list in future major versions.
"""
warn(
"The attribute `file_hashes` will return a tuple instead of a list in future major"
" versions.",
PTBDeprecationWarning,
PTBDeprecationWarning(
"20.6",
"The attribute `file_hashes` will return a tuple instead of a list in future major"
" versions.",
),
stacklevel=2,
)
return self._file_hashes
@@ -427,10 +429,12 @@ class PassportElementErrorTranslationFiles(PassportElementError):
This attribute will return a tuple instead of a list in future major versions.
"""
warn(
"The attribute `file_hashes` will return a tuple instead of a list in future major"
" versions. See the stability policy:"
" https://docs.python-telegram-bot.org/en/stable/stability_policy.html",
PTBDeprecationWarning,
PTBDeprecationWarning(
"20.6",
"The attribute `file_hashes` will return a tuple instead of a list in future major"
" versions. See the stability policy:"
" https://docs.python-telegram-bot.org/en/stable/stability_policy.html",
),
stacklevel=2,
)
return self._file_hashes
+7 -4
View File
@@ -107,9 +107,11 @@ class PassportFile(TelegramObject):
This attribute will return a datetime instead of a integer in future major versions.
"""
warn(
"The attribute `file_date` will return a datetime instead of an integer in future"
" major versions.",
PTBDeprecationWarning,
PTBDeprecationWarning(
"20.6",
"The attribute `file_date` will return a datetime instead of an integer in future"
" major versions.",
),
stacklevel=2,
)
return self._file_date
@@ -203,5 +205,6 @@ class PassportFile(TelegramObject):
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
file.set_credentials(self._credentials)
if self._credentials:
file.set_credentials(self._credentials)
return file
+219 -17
View File
@@ -28,12 +28,80 @@ from telegram._user import User
from telegram._utils import enum
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.types import JSONDict
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram._utils.entities import parse_message_entities, parse_message_entity
from telegram._utils.types import JSONDict, ODVInput
if TYPE_CHECKING:
from telegram import Bot
class InputPollOption(TelegramObject):
"""
This object contains information about one answer option in a poll to send.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`text` is equal.
.. versionadded:: 21.2
Args:
text (:obj:`str`): Option text,
:tg-const:`telegram.PollOption.MIN_LENGTH`-:tg-const:`telegram.PollOption.MAX_LENGTH`
characters.
text_parse_mode (:obj:`str`, optional): |parse_mode|
Currently, only custom emoji entities are allowed.
text_entities (Sequence[:class:`telegram.MessageEntity`], optional): Special entities
that appear in the option :paramref:`text`. It can be specified instead of
:paramref:`text_parse_mode`.
Currently, only custom emoji entities are allowed.
This list is empty if the text does not contain entities.
Attributes:
text (:obj:`str`): Option text,
:tg-const:`telegram.PollOption.MIN_LENGTH`-:tg-const:`telegram.PollOption.MAX_LENGTH`
characters.
text_parse_mode (:obj:`str`): Optional. |parse_mode|
Currently, only custom emoji entities are allowed.
text_entities (Sequence[:class:`telegram.MessageEntity`]): Special entities
that appear in the option :paramref:`text`. It can be specified instead of
:paramref:`text_parse_mode`.
Currently, only custom emoji entities are allowed.
This list is empty if the text does not contain entities.
"""
__slots__ = ("text", "text_entities", "text_parse_mode")
def __init__(
self,
text: str,
text_parse_mode: ODVInput[str] = DEFAULT_NONE,
text_entities: Optional[Sequence[MessageEntity]] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.text: str = text
self.text_parse_mode: ODVInput[str] = text_parse_mode
self.text_entities: Tuple[MessageEntity, ...] = parse_sequence_arg(text_entities)
self._id_attrs = (self.text,)
self._freeze()
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["InputPollOption"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["text_entities"] = MessageEntity.de_list(data.get("text_entities"), bot)
return super().de_json(data=data, bot=bot)
class PollOption(TelegramObject):
"""
This object contains information about one answer option in a poll.
@@ -46,26 +114,101 @@ class PollOption(TelegramObject):
:tg-const:`telegram.PollOption.MIN_LENGTH`-:tg-const:`telegram.PollOption.MAX_LENGTH`
characters.
voter_count (:obj:`int`): Number of users that voted for this option.
text_entities (Sequence[:class:`telegram.MessageEntity`], optional): Special entities
that appear in the option text. Currently, only custom emoji entities are allowed in
poll option texts.
.. versionadded:: 21.2
Attributes:
text (:obj:`str`): Option text,
:tg-const:`telegram.PollOption.MIN_LENGTH`-:tg-const:`telegram.PollOption.MAX_LENGTH`
characters.
voter_count (:obj:`int`): Number of users that voted for this option.
text_entities (Tuple[:class:`telegram.MessageEntity`]): Special entities
that appear in the option text. Currently, only custom emoji entities are allowed in
poll option texts.
This list is empty if the question does not contain entities.
.. versionadded:: 21.2
"""
__slots__ = ("text", "voter_count")
__slots__ = ("text", "text_entities", "voter_count")
def __init__(self, text: str, voter_count: int, *, api_kwargs: Optional[JSONDict] = None):
def __init__(
self,
text: str,
voter_count: int,
text_entities: Optional[Sequence[MessageEntity]] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.text: str = text
self.voter_count: int = voter_count
self.text_entities: Tuple[MessageEntity, ...] = parse_sequence_arg(text_entities)
self._id_attrs = (self.text, self.voter_count)
self._freeze()
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["PollOption"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["text_entities"] = MessageEntity.de_list(data.get("text_entities"), bot)
return super().de_json(data=data, bot=bot)
def parse_entity(self, entity: MessageEntity) -> str:
"""Returns the text in :attr:`text`
from a given :class:`telegram.MessageEntity` of :attr:`text_entities`.
Note:
This method is present because Telegram calculates the offset and length in
UTF-16 codepoint pairs, which some versions of Python don't handle automatically.
(That is, you can't just slice ``Message.text`` with the offset and length.)
.. versionadded:: 21.2
Args:
entity (:class:`telegram.MessageEntity`): The entity to extract the text from. It must
be an entity that belongs to :attr:`text_entities`.
Returns:
:obj:`str`: The text of the given entity.
"""
return parse_message_entity(self.text, entity)
def parse_entities(self, types: Optional[List[str]] = None) -> Dict[MessageEntity, str]:
"""
Returns a :obj:`dict` that maps :class:`telegram.MessageEntity` to :obj:`str`.
It contains entities from this polls question filtered by their ``type`` attribute as
the key, and the text that each entity belongs to as the value of the :obj:`dict`.
Note:
This method should always be used instead of the :attr:`text_entities`
attribute, since it calculates the correct substring from the message text based on
UTF-16 codepoints. See :attr:`parse_entity` for more info.
.. versionadded:: 21.2
Args:
types (List[:obj:`str`], optional): List of ``MessageEntity`` types as strings. If the
``type`` attribute of an entity is contained in this list, it will be returned.
Defaults to :attr:`telegram.MessageEntity.ALL_TYPES`.
Returns:
Dict[:class:`telegram.MessageEntity`, :obj:`str`]: A dictionary of entities mapped to
the text that belongs to them, calculated based on UTF-16 codepoints.
"""
return parse_message_entities(self.text, self.text_entities, types)
MIN_LENGTH: Final[int] = constants.PollLimit.MIN_OPTION_LENGTH
""":const:`telegram.constants.PollLimit.MIN_OPTION_LENGTH`
@@ -215,6 +358,11 @@ class Poll(TelegramObject):
.. versionchanged:: 20.3
|datetime_localization|
question_entities (Sequence[:class:`telegram.MessageEntity`], optional): Special entities
that appear in the :attr:`question`. Currently, only custom emoji entities are allowed
in poll questions.
.. versionadded:: 21.2
Attributes:
id (:obj:`str`): Unique poll identifier.
@@ -251,6 +399,12 @@ class Poll(TelegramObject):
.. versionchanged:: 20.3
|datetime_localization|
question_entities (Tuple[:class:`telegram.MessageEntity`]): Special entities
that appear in the :attr:`question`. Currently, only custom emoji entities are allowed
in poll questions.
This list is empty if the question does not contain entities.
.. versionadded:: 21.2
"""
@@ -266,6 +420,7 @@ class Poll(TelegramObject):
"open_period",
"options",
"question",
"question_entities",
"total_voter_count",
"type",
)
@@ -285,6 +440,7 @@ class Poll(TelegramObject):
explanation_entities: Optional[Sequence[MessageEntity]] = None,
open_period: Optional[int] = None,
close_date: Optional[datetime.datetime] = None,
question_entities: Optional[Sequence[MessageEntity]] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@@ -304,6 +460,7 @@ class Poll(TelegramObject):
)
self.open_period: Optional[int] = open_period
self.close_date: Optional[datetime.datetime] = close_date
self.question_entities: Tuple[MessageEntity, ...] = parse_sequence_arg(question_entities)
self._id_attrs = (self.id,)
@@ -323,11 +480,13 @@ class Poll(TelegramObject):
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"), tzinfo=loc_tzinfo)
data["question_entities"] = MessageEntity.de_list(data.get("question_entities"), bot)
return super().de_json(data=data, bot=bot)
def parse_explanation_entity(self, entity: MessageEntity) -> str:
"""Returns the text from a given :class:`telegram.MessageEntity`.
"""Returns the text in :attr:`explanation` from a given :class:`telegram.MessageEntity` of
:attr:`explanation_entities`.
Note:
This method is present because Telegram calculates the offset and length in
@@ -336,7 +495,7 @@ class Poll(TelegramObject):
Args:
entity (:class:`telegram.MessageEntity`): The entity to extract the text from. It must
be an entity that belongs to this message.
be an entity that belongs to :attr:`explanation_entities`.
Returns:
:obj:`str`: The text of the given entity.
@@ -348,10 +507,7 @@ class Poll(TelegramObject):
if not self.explanation:
raise RuntimeError("This Poll has no 'explanation'.")
entity_text = self.explanation.encode("utf-16-le")
entity_text = entity_text[entity.offset * 2 : (entity.offset + entity.length) * 2]
return entity_text.decode("utf-16-le")
return parse_message_entity(self.explanation, entity)
def parse_explanation_entities(
self, types: Optional[List[str]] = None
@@ -375,15 +531,61 @@ class Poll(TelegramObject):
Dict[:class:`telegram.MessageEntity`, :obj:`str`]: A dictionary of entities mapped to
the text that belongs to them, calculated based on UTF-16 codepoints.
"""
if types is None:
types = MessageEntity.ALL_TYPES
Raises:
RuntimeError: If the poll has no explanation.
return {
entity: self.parse_explanation_entity(entity)
for entity in self.explanation_entities
if entity.type in types
}
"""
if not self.explanation:
raise RuntimeError("This Poll has no 'explanation'.")
return parse_message_entities(self.explanation, self.explanation_entities, types)
def parse_question_entity(self, entity: MessageEntity) -> str:
"""Returns the text in :attr:`question` from a given :class:`telegram.MessageEntity` of
:attr:`question_entities`.
.. versionadded:: 21.2
Note:
This method is present because Telegram calculates the offset and length in
UTF-16 codepoint pairs, which some versions of Python don't handle automatically.
(That is, you can't just slice ``Message.text`` with the offset and length.)
Args:
entity (:class:`telegram.MessageEntity`): The entity to extract the text from. It must
be an entity that belongs to :attr:`question_entities`.
Returns:
:obj:`str`: The text of the given entity.
"""
return parse_message_entity(self.question, entity)
def parse_question_entities(
self, types: Optional[List[str]] = None
) -> Dict[MessageEntity, str]:
"""
Returns a :obj:`dict` that maps :class:`telegram.MessageEntity` to :obj:`str`.
It contains entities from this polls question filtered by their ``type`` attribute as
the key, and the text that each entity belongs to as the value of the :obj:`dict`.
.. versionadded:: 21.2
Note:
This method should always be used instead of the :attr:`question_entities`
attribute, since it calculates the correct substring from the message text based on
UTF-16 codepoints. See :attr:`parse_question_entity` for more info.
Args:
types (List[:obj:`str`], optional): List of ``MessageEntity`` types as strings. If the
``type`` attribute of an entity is contained in this list, it will be returned.
Defaults to :attr:`telegram.MessageEntity.ALL_TYPES`.
Returns:
Dict[:class:`telegram.MessageEntity`, :obj:`str`]: A dictionary of entities mapped to
the text that belongs to them, calculated based on UTF-16 codepoints.
"""
return parse_message_entities(self.question, self.question_entities, types)
REGULAR: Final[str] = constants.PollType.REGULAR
""":const:`telegram.constants.PollType.REGULAR`"""
+2
View File
@@ -355,6 +355,7 @@ class ReplyParameters(TelegramObject):
chat, or in the chat :paramref:`chat_id` if it is specified.
chat_id (:obj:`int` | :obj:`str`, optional): If the message to be replied to is from a
different chat, |chat_id_channel|
Not supported for messages sent on behalf of a business account.
allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply| Can be
used only for replies in the same chat and forum topic.
quote (:obj:`str`, optional): Quoted part of the message to be replied to; 0-1024
@@ -376,6 +377,7 @@ class ReplyParameters(TelegramObject):
chat, or in the chat :paramref:`chat_id` if it is specified.
chat_id (:obj:`int` | :obj:`str`): Optional. If the message to be replied to is from a
different chat, |chat_id_channel|
Not supported for messages sent on behalf of a business account.
allow_sending_without_reply (:obj:`bool`): Optional. |allow_sending_without_reply| Can be
used only for replies in the same chat and forum topic.
quote (:obj:`str`): Optional. Quoted part of the message to be replied to; 0-1024
+2 -1
View File
@@ -28,7 +28,8 @@ from telegram._utils.types import JSONDict
class ReplyKeyboardMarkup(TelegramObject):
"""This object represents a custom keyboard with reply options.
"""This object represents a custom keyboard with reply options. Not supported in channels and
for messages sent on behalf of a Telegram Business account.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their size of :attr:`keyboard` and all the buttons are equal.
+1
View File
@@ -29,6 +29,7 @@ class ReplyKeyboardRemove(TelegramObject):
keyboard and display the default letter-keyboard. By default, custom keyboards are displayed
until a new keyboard is sent by a bot. An exception is made for one-time keyboards that are
hidden immediately after the user presses a button (see :class:`telegram.ReplyKeyboardMarkup`).
Not supported in channels and for messages sent on behalf of a Telegram Business account.
Note:
User will not be able to summon this keyboard; if you want to hide the keyboard from
+166 -20
View File
@@ -17,11 +17,16 @@
# 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 used for request chats/users service messages."""
from typing import Optional, Sequence, Tuple
from typing import TYPE_CHECKING, Optional, Sequence, Tuple
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
if TYPE_CHECKING:
from telegram._bot import Bot
class UsersShared(TelegramObject):
"""
@@ -29,48 +34,72 @@ class UsersShared(TelegramObject):
using a :class:`telegram.KeyboardButtonRequestUsers` button.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`request_id` and :attr:`user_ids` are equal.
considered equal, if their :attr:`request_id` and :attr:`users` are equal.
.. versionadded:: 20.8
Bot API 7.0 replaces ``UserShared`` with this class. The only difference is that now
the :attr:`user_ids` is a sequence instead of a single integer.
the ``user_ids`` is a sequence instead of a single integer.
.. versionchanged:: 21.1
The argument :attr:`users` is now considered for the equality comparison instead of
``user_ids``.
.. versionremoved:: 21.2
Removed the deprecated argument and attribute ``user_ids``.
Args:
request_id (:obj:`int`): Identifier of the request.
user_ids (Sequence[:obj:`int`]): Identifiers of the shared users. These numbers may have
more than 32 significant bits and some programming languages may have difficulty/silent
defects in interpreting them. But they have at most 52 significant bits, so 64-bit
integers or double-precision float types are safe for storing these identifiers. The
bot may not have access to the users and could be unable to use these identifiers,
unless the users are already known to the bot by some other means.
users (Sequence[:class:`telegram.SharedUser`]): Information about users shared with the
bot.
.. versionadded:: 21.1
.. versionchanged:: 21.2
This argument is now required.
Attributes:
request_id (:obj:`int`): Identifier of the request.
user_ids (Tuple[:obj:`int`]): Identifiers of the shared users. These numbers may have
more than 32 significant bits and some programming languages may have difficulty/silent
defects in interpreting them. But they have at most 52 significant bits, so 64-bit
integers or double-precision float types are safe for storing these identifiers. The
bot may not have access to the users and could be unable to use these identifiers,
unless the users are already known to the bot by some other means.
users (Tuple[:class:`telegram.SharedUser`]): Information about users shared with the
bot.
.. versionadded:: 21.1
"""
__slots__ = ("request_id", "user_ids")
__slots__ = ("request_id", "users")
def __init__(
self,
request_id: int,
user_ids: Sequence[int],
users: Sequence["SharedUser"],
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.request_id: int = request_id
self.user_ids: Tuple[int, ...] = tuple(user_ids)
self.users: Tuple[SharedUser, ...] = parse_sequence_arg(users)
self._id_attrs = (self.request_id, self.user_ids)
self._id_attrs = (self.request_id, self.users)
self._freeze()
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["UsersShared"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["users"] = SharedUser.de_list(data.get("users"), 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 user_ids := data.get("user_ids"):
api_kwargs = {"user_ids": user_ids}
return super()._de_json(data=data, bot=bot, api_kwargs=api_kwargs)
class ChatShared(TelegramObject):
"""
@@ -88,6 +117,17 @@ class ChatShared(TelegramObject):
bits and some programming languages may have difficulty/silent defects in interpreting
it. But it is smaller than 52 bits, so a signed 64-bit integer or double-precision
float type are safe for storing this identifier.
title (:obj:`str`, optional): Title of the chat, if the title was requested by the bot.
.. versionadded:: 21.1
username (:obj:`str`, optional): Username of the chat, if the username was requested by
the bot and available.
.. versionadded:: 21.1
photo (Sequence[:class:`telegram.PhotoSize`], optional): Available sizes of the chat photo,
if the photo was requested by the bot
.. versionadded:: 21.1
Attributes:
request_id (:obj:`int`): Identifier of the request.
@@ -95,21 +135,127 @@ class ChatShared(TelegramObject):
bits and some programming languages may have difficulty/silent defects in interpreting
it. But it is smaller than 52 bits, so a signed 64-bit integer or double-precision
float type are safe for storing this identifier.
title (:obj:`str`): Optional. Title of the chat, if the title was requested by the bot.
.. versionadded:: 21.1
username (:obj:`str`): Optional. Username of the chat, if the username was requested by
the bot and available.
.. versionadded:: 21.1
photo (Tuple[:class:`telegram.PhotoSize`]): Optional. Available sizes of the chat photo,
if the photo was requested by the bot
.. versionadded:: 21.1
"""
__slots__ = ("chat_id", "request_id")
__slots__ = ("chat_id", "photo", "request_id", "title", "username")
def __init__(
self,
request_id: int,
chat_id: int,
title: Optional[str] = None,
username: Optional[str] = None,
photo: Optional[Sequence[PhotoSize]] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.request_id: int = request_id
self.chat_id: int = chat_id
self.title: Optional[str] = title
self.username: Optional[str] = username
self.photo: Optional[Tuple[PhotoSize, ...]] = parse_sequence_arg(photo)
self._id_attrs = (self.request_id, self.chat_id)
self._freeze()
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["ChatShared"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["photo"] = PhotoSize.de_list(data.get("photo"), bot)
return super().de_json(data=data, bot=bot)
class SharedUser(TelegramObject):
"""
This object contains information about a user that was shared with the bot using a
:class:`telegram.KeyboardButtonRequestUsers` button.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`user_id` is equal.
.. versionadded:: 21.1
Args:
user_id (:obj:`int`): Identifier of the shared user. This number may have 32 significant
bits and some programming languages may have difficulty/silent defects in interpreting
it. But it has atmost 52 significant bits, so 64-bit integers or double-precision
float types are safe for storing these identifiers. The bot may not have access to the
user and could be unable to use this identifier, unless the user is already known to
the bot by some other means.
first_name (:obj:`str`, optional): First name of the user, if the name was requested by the
bot.
last_name (:obj:`str`, optional): Last name of the user, if the name was requested by the
bot.
username (:obj:`str`, optional): Username of the user, if the username was requested by the
bot.
photo (Sequence[:class:`telegram.PhotoSize`], optional): Available sizes of the chat photo,
if the photo was requested by the bot.
Attributes:
user_id (:obj:`int`): Identifier of the shared user. This number may have 32 significant
bits and some programming languages may have difficulty/silent defects in interpreting
it. But it has atmost 52 significant bits, so 64-bit integers or double-precision
float types are safe for storing these identifiers. The bot may not have access to the
user and could be unable to use this identifier, unless the user is already known to
the bot by some other means.
first_name (:obj:`str`): Optional. First name of the user, if the name was requested by the
bot.
last_name (:obj:`str`): Optional. Last name of the user, if the name was requested by the
bot.
username (:obj:`str`): Optional. Username of the user, if the username was requested by the
bot.
photo (Tuple[:class:`telegram.PhotoSize`]): Available sizes of the chat photo, if
the photo was requested by the bot. This list is empty if the photo was not requsted.
"""
__slots__ = ("first_name", "last_name", "photo", "user_id", "username")
def __init__(
self,
user_id: int,
first_name: Optional[str] = None,
last_name: Optional[str] = None,
username: Optional[str] = None,
photo: Optional[Sequence[PhotoSize]] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.user_id: int = user_id
self.first_name: Optional[str] = first_name
self.last_name: Optional[str] = last_name
self.username: Optional[str] = username
self.photo: Optional[Tuple[PhotoSize, ...]] = parse_sequence_arg(photo)
self._id_attrs = (self.user_id,)
self._freeze()
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["SharedUser"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["photo"] = PhotoSize.de_list(data.get("photo"), bot)
return super().de_json(data=data, bot=bot)
+182 -3
View File
@@ -18,9 +18,10 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Telegram Update."""
from typing import TYPE_CHECKING, Final, List, Optional
from typing import TYPE_CHECKING, Final, List, Optional, Union
from telegram import constants
from telegram._business import BusinessConnection, BusinessMessagesDeleted
from telegram._callbackquery import CallbackQuery
from telegram._chatboost import ChatBoostRemoved, ChatBoostUpdated
from telegram._chatjoinrequest import ChatJoinRequest
@@ -134,6 +135,28 @@ class Update(TelegramObject):
.. versionadded:: 20.8
business_connection (:class:`telegram.BusinessConnection`, optional): The bot was connected
to or disconnected from a business account, or a user edited an existing connection
with the bot.
.. versionadded:: 21.1
business_message (:class:`telegram.Message`, optional): New message from a connected
business account.
.. versionadded:: 21.1
edited_business_message (:class:`telegram.Message`, optional): New version of a message
from a connected business account.
.. versionadded:: 21.1
deleted_business_messages (:class:`telegram.BusinessMessagesDeleted`, optional): Messages
were deleted from a connected business account.
.. versionadded:: 21.1
Attributes:
update_id (:obj:`int`): The update's unique identifier. Update identifiers start from a
certain positive number and increase sequentially. This ID becomes especially handy if
@@ -219,18 +242,44 @@ class Update(TelegramObject):
with delay up to a few minutes.
.. versionadded:: 20.8
business_connection (:class:`telegram.BusinessConnection`): Optional. The bot was connected
to or disconnected from a business account, or a user edited an existing connection
with the bot.
.. versionadded:: 21.1
business_message (:class:`telegram.Message`): Optional. New message from a connected
business account.
.. versionadded:: 21.1
edited_business_message (:class:`telegram.Message`): Optional. New version of a message
from a connected business account.
.. versionadded:: 21.1
deleted_business_messages (:class:`telegram.BusinessMessagesDeleted`): Optional. Messages
were deleted from a connected business account.
.. versionadded:: 21.1
"""
__slots__ = (
"_effective_chat",
"_effective_message",
"_effective_sender",
"_effective_user",
"business_connection",
"business_message",
"callback_query",
"channel_post",
"chat_boost",
"chat_join_request",
"chat_member",
"chosen_inline_result",
"deleted_business_messages",
"edited_business_message",
"edited_channel_post",
"edited_message",
"inline_query",
@@ -318,6 +367,22 @@ class Update(TelegramObject):
""":const:`telegram.constants.UpdateType.MESSAGE_REACTION_COUNT`
.. versionadded:: 20.8"""
BUSINESS_CONNECTION: Final[str] = constants.UpdateType.BUSINESS_CONNECTION
""":const:`telegram.constants.UpdateType.BUSINESS_CONNECTION`
.. versionadded:: 21.1"""
BUSINESS_MESSAGE: Final[str] = constants.UpdateType.BUSINESS_MESSAGE
""":const:`telegram.constants.UpdateType.BUSINESS_MESSAGE`
.. versionadded:: 21.1"""
EDITED_BUSINESS_MESSAGE: Final[str] = constants.UpdateType.EDITED_BUSINESS_MESSAGE
""":const:`telegram.constants.UpdateType.EDITED_BUSINESS_MESSAGE`
.. versionadded:: 21.1"""
DELETED_BUSINESS_MESSAGES: Final[str] = constants.UpdateType.DELETED_BUSINESS_MESSAGES
""":const:`telegram.constants.UpdateType.DELETED_BUSINESS_MESSAGES`
.. versionadded:: 21.1"""
ALL_TYPES: Final[List[str]] = list(constants.UpdateType)
"""List[:obj:`str`]: A list of all available update types.
@@ -344,6 +409,10 @@ class Update(TelegramObject):
removed_chat_boost: Optional[ChatBoostRemoved] = None,
message_reaction: Optional[MessageReactionUpdated] = None,
message_reaction_count: Optional[MessageReactionCountUpdated] = None,
business_connection: Optional[BusinessConnection] = None,
business_message: Optional[Message] = None,
edited_business_message: Optional[Message] = None,
deleted_business_messages: Optional[BusinessMessagesDeleted] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@@ -369,8 +438,15 @@ class Update(TelegramObject):
self.removed_chat_boost: Optional[ChatBoostRemoved] = removed_chat_boost
self.message_reaction: Optional[MessageReactionUpdated] = message_reaction
self.message_reaction_count: Optional[MessageReactionCountUpdated] = message_reaction_count
self.business_connection: Optional[BusinessConnection] = business_connection
self.business_message: Optional[Message] = business_message
self.edited_business_message: Optional[Message] = edited_business_message
self.deleted_business_messages: Optional[BusinessMessagesDeleted] = (
deleted_business_messages
)
self._effective_user: Optional[User] = None
self._effective_sender: Optional[Union["User", "Chat"]] = None
self._effective_chat: Optional[Chat] = None
self._effective_message: Optional[Message] = None
@@ -391,9 +467,14 @@ class Update(TelegramObject):
* :attr:`chat_boost`
* :attr:`removed_chat_boost`
* :attr:`message_reaction_count`
* :attr:`deleted_business_messages`
is present.
.. versionchanged:: 21.1
This property now also considers :attr:`business_connection`, :attr:`business_message`
and :attr:`edited_business_message`.
Example:
* If :attr:`message` is present, this will give
:attr:`telegram.Message.from_user`.
@@ -441,9 +522,76 @@ class Update(TelegramObject):
elif self.message_reaction:
user = self.message_reaction.user
elif self.business_message:
user = self.business_message.from_user
elif self.edited_business_message:
user = self.edited_business_message.from_user
elif self.business_connection:
user = self.business_connection.user
self._effective_user = user
return user
@property
def effective_sender(self) -> Optional[Union["User", "Chat"]]:
"""
:class:`telegram.User` or :class:`telegram.Chat`: The user or chat that sent this update,
no matter what kind of update this is.
Note:
* Depending on the type of update and the user's 'Remain anonymous' setting, this
could either be :class:`telegram.User`, :class:`telegram.Chat` or :obj:`None`.
If no user whatsoever is associated with this update, this gives :obj:`None`. This
is the case if any of
* :attr:`poll`
* :attr:`chat_boost`
* :attr:`removed_chat_boost`
* :attr:`message_reaction_count`
* :attr:`deleted_business_messages`
is present.
Example:
* If :attr:`message` is present, this will give either
:attr:`telegram.Message.from_user` or :attr:`telegram.Message.sender_chat`.
* If :attr:`poll_answer` is present, this will give either
:attr:`telegram.PollAnswer.user` or :attr:`telegram.PollAnswer.voter_chat`.
* If :attr:`channel_post` is present, this will give
:attr:`telegram.Message.sender_chat`.
.. versionadded:: 21.1
"""
if self._effective_sender:
return self._effective_sender
sender: Optional[Union["User", "Chat"]] = None
if message := (
self.message
or self.edited_message
or self.channel_post
or self.edited_channel_post
or self.business_message
or self.edited_business_message
):
sender = message.sender_chat
elif self.poll_answer:
sender = self.poll_answer.voter_chat
elif self.message_reaction:
sender = self.message_reaction.actor_chat
if sender is None:
sender = self.effective_user
self._effective_sender = sender
return sender
@property
def effective_chat(self) -> Optional["Chat"]:
"""
@@ -452,8 +600,12 @@ class Update(TelegramObject):
If no chat is associated with this update, this gives :obj:`None`.
This is the case, if :attr:`inline_query`,
:attr:`chosen_inline_result`, :attr:`callback_query` from inline messages,
:attr:`shipping_query`, :attr:`pre_checkout_query`, :attr:`poll` or
:attr:`poll_answer` is present.
:attr:`shipping_query`, :attr:`pre_checkout_query`, :attr:`poll`,
:attr:`poll_answer`, or :attr:`business_connection` is present.
.. versionchanged:: 21.1
This property now also considers :attr:`business_message`,
:attr:`edited_business_message`, and :attr:`deleted_business_messages`.
Example:
If :attr:`message` is present, this will give :attr:`telegram.Message.chat`.
@@ -500,6 +652,15 @@ class Update(TelegramObject):
elif self.message_reaction_count:
chat = self.message_reaction_count.chat
elif self.business_message:
chat = self.business_message.chat
elif self.edited_business_message:
chat = self.edited_business_message.chat
elif self.deleted_business_messages:
chat = self.deleted_business_messages.chat
self._effective_chat = chat
return chat
@@ -512,6 +673,10 @@ class Update(TelegramObject):
:attr:`callback_query` (i.e. :attr:`telegram.CallbackQuery.message`) or :obj:`None`, if
none of those are present.
.. versionchanged:: 21.1
This property now also considers :attr:`business_message`, and
:attr:`edited_business_message`.
Tip:
This property will only ever return objects of type :class:`telegram.Message` or
:obj:`None`, never :class:`telegram.MaybeInaccessibleMessage` or
@@ -554,6 +719,12 @@ class Update(TelegramObject):
elif self.edited_channel_post:
message = self.edited_channel_post
elif self.business_message:
message = self.business_message
elif self.edited_business_message:
message = self.edited_business_message
self._effective_message = message
return message
@@ -589,5 +760,13 @@ class Update(TelegramObject):
data["message_reaction_count"] = MessageReactionCountUpdated.de_json(
data.get("message_reaction_count"), bot
)
data["business_connection"] = BusinessConnection.de_json(
data.get("business_connection"), bot
)
data["business_message"] = Message.de_json(data.get("business_message"), bot)
data["edited_business_message"] = Message.de_json(data.get("edited_business_message"), bot)
data["deleted_business_messages"] = BusinessMessagesDeleted.de_json(
data.get("deleted_business_messages"), bot
)
return super().de_json(data=data, bot=bot)
+57 -4
View File
@@ -40,6 +40,7 @@ if TYPE_CHECKING:
InputMediaDocument,
InputMediaPhoto,
InputMediaVideo,
InputPollOption,
LabeledPrice,
LinkPreviewOptions,
Location,
@@ -78,11 +79,11 @@ class User(TelegramObject):
username (:obj:`str`, optional): User's or bot's username.
language_code (:obj:`str`, optional): IETF language tag of the user's language.
can_join_groups (:obj:`str`, optional): :obj:`True`, if the bot can be invited to groups.
Returned only in :attr:`telegram.Bot.get_me` requests.
Returned only in :meth:`telegram.Bot.get_me`.
can_read_all_group_messages (:obj:`str`, optional): :obj:`True`, if privacy mode is
disabled for the bot. Returned only in :attr:`telegram.Bot.get_me` requests.
disabled for the bot. Returned only in :meth:`telegram.Bot.get_me`.
supports_inline_queries (:obj:`str`, optional): :obj:`True`, if the bot supports inline
queries. Returned only in :attr:`telegram.Bot.get_me` requests.
queries. Returned only in :meth:`telegram.Bot.get_me`.
is_premium (:obj:`bool`, optional): :obj:`True`, if this user is a Telegram Premium user.
@@ -91,6 +92,12 @@ class User(TelegramObject):
the bot to the attachment menu.
.. versionadded:: 20.0
can_connect_to_business (:obj:`bool`, optional): :obj:`True`, if the bot can be connected
to a Telegram Business account to receive its messages. Returned only in
:meth:`telegram.Bot.get_me`.
.. versionadded:: 21.1
Attributes:
id (:obj:`int`): Unique identifier for this user or bot.
is_bot (:obj:`bool`): :obj:`True`, if this user is a bot.
@@ -112,6 +119,11 @@ class User(TelegramObject):
the bot to the attachment menu.
.. versionadded:: 20.0
can_connect_to_business (:obj:`bool`): Optional. :obj:`True`, if the bot can be connected
to a Telegram Business account to receive its messages. Returned only in
:meth:`telegram.Bot.get_me`.
.. versionadded:: 21.1
.. |user_chat_id_note| replace:: This shortcuts build on the assumption that :attr:`User.id`
coincides with the :attr:`Chat.id` of the private chat with the user. This has been the
case so far, but Telegram does not guarantee that this stays this way.
@@ -119,6 +131,7 @@ class User(TelegramObject):
__slots__ = (
"added_to_attachment_menu",
"can_connect_to_business",
"can_join_groups",
"can_read_all_group_messages",
"first_name",
@@ -144,6 +157,7 @@ class User(TelegramObject):
supports_inline_queries: Optional[bool] = None,
is_premium: Optional[bool] = None,
added_to_attachment_menu: Optional[bool] = None,
can_connect_to_business: Optional[bool] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@@ -161,6 +175,7 @@ class User(TelegramObject):
self.supports_inline_queries: Optional[bool] = supports_inline_queries
self.is_premium: Optional[bool] = is_premium
self.added_to_attachment_menu: Optional[bool] = added_to_attachment_menu
self.can_connect_to_business: Optional[bool] = can_connect_to_business
self._id_attrs = (self.id,)
@@ -393,6 +408,7 @@ class User(TelegramObject):
message_thread_id: Optional[int] = None,
link_preview_options: ODVInput["LinkPreviewOptions"] = DEFAULT_NONE,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
disable_web_page_preview: Optional[bool] = None,
@@ -435,6 +451,7 @@ class User(TelegramObject):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=business_connection_id,
)
async def delete_message(
@@ -513,6 +530,7 @@ class User(TelegramObject):
message_thread_id: Optional[int] = None,
has_spoiler: Optional[bool] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -556,6 +574,7 @@ class User(TelegramObject):
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
has_spoiler=has_spoiler,
business_connection_id=business_connection_id,
)
async def send_media_group(
@@ -567,6 +586,7 @@ class User(TelegramObject):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -610,6 +630,7 @@ class User(TelegramObject):
caption=caption,
parse_mode=parse_mode,
caption_entities=caption_entities,
business_connection_id=business_connection_id,
)
async def send_audio(
@@ -627,6 +648,7 @@ class User(TelegramObject):
message_thread_id: Optional[int] = None,
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -673,12 +695,14 @@ class User(TelegramObject):
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
thumbnail=thumbnail,
business_connection_id=business_connection_id,
)
async def send_chat_action(
self,
action: str,
message_thread_id: Optional[int] = None,
business_connection_id: Optional[str] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -708,6 +732,7 @@ class User(TelegramObject):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=business_connection_id,
)
send_action = send_chat_action
@@ -724,6 +749,7 @@ class User(TelegramObject):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -766,6 +792,7 @@ class User(TelegramObject):
allow_sending_without_reply=allow_sending_without_reply,
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=business_connection_id,
)
async def send_dice(
@@ -776,6 +803,7 @@ class User(TelegramObject):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -813,6 +841,7 @@ class User(TelegramObject):
allow_sending_without_reply=allow_sending_without_reply,
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=business_connection_id,
)
async def send_document(
@@ -828,6 +857,7 @@ class User(TelegramObject):
message_thread_id: Optional[int] = None,
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -872,6 +902,7 @@ class User(TelegramObject):
caption_entities=caption_entities,
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=business_connection_id,
)
async def send_game(
@@ -882,6 +913,7 @@ class User(TelegramObject):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -919,6 +951,7 @@ class User(TelegramObject):
allow_sending_without_reply=allow_sending_without_reply,
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=business_connection_id,
)
async def send_invoice(
@@ -1031,6 +1064,7 @@ class User(TelegramObject):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -1075,6 +1109,7 @@ class User(TelegramObject):
allow_sending_without_reply=allow_sending_without_reply,
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=business_connection_id,
)
async def send_animation(
@@ -1093,6 +1128,7 @@ class User(TelegramObject):
has_spoiler: Optional[bool] = None,
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -1140,6 +1176,7 @@ class User(TelegramObject):
message_thread_id=message_thread_id,
has_spoiler=has_spoiler,
thumbnail=thumbnail,
business_connection_id=business_connection_id,
)
async def send_sticker(
@@ -1151,6 +1188,7 @@ class User(TelegramObject):
message_thread_id: Optional[int] = None,
emoji: Optional[str] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -1189,6 +1227,7 @@ class User(TelegramObject):
protect_content=protect_content,
message_thread_id=message_thread_id,
emoji=emoji,
business_connection_id=business_connection_id,
)
async def send_video(
@@ -1208,6 +1247,7 @@ class User(TelegramObject):
has_spoiler: Optional[bool] = None,
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -1256,6 +1296,7 @@ class User(TelegramObject):
protect_content=protect_content,
message_thread_id=message_thread_id,
has_spoiler=has_spoiler,
business_connection_id=business_connection_id,
)
async def send_venue(
@@ -1273,6 +1314,7 @@ class User(TelegramObject):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -1319,6 +1361,7 @@ class User(TelegramObject):
allow_sending_without_reply=allow_sending_without_reply,
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=business_connection_id,
)
async def send_video_note(
@@ -1332,6 +1375,7 @@ class User(TelegramObject):
message_thread_id: Optional[int] = None,
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -1374,6 +1418,7 @@ class User(TelegramObject):
protect_content=protect_content,
message_thread_id=message_thread_id,
thumbnail=thumbnail,
business_connection_id=business_connection_id,
)
async def send_voice(
@@ -1388,6 +1433,7 @@ class User(TelegramObject):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -1431,12 +1477,13 @@ class User(TelegramObject):
filename=filename,
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=business_connection_id,
)
async def send_poll(
self,
question: str,
options: Sequence[str],
options: Sequence[Union[str, "InputPollOption"]],
is_anonymous: Optional[bool] = None,
type: Optional[str] = None,
allows_multiple_answers: Optional[bool] = None,
@@ -1452,6 +1499,9 @@ class User(TelegramObject):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
question_parse_mode: ODVInput[str] = DEFAULT_NONE,
question_entities: Optional[Sequence["MessageEntity"]] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -1500,6 +1550,9 @@ class User(TelegramObject):
explanation_entities=explanation_entities,
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=business_connection_id,
question_parse_mode=question_parse_mode,
question_entities=question_entities,
)
async def send_copy(
+71
View File
@@ -0,0 +1,71 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2024
# 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 auxiliary functionality for parsing MessageEntity objects.
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.
"""
from typing import Dict, Optional, Sequence
from telegram._messageentity import MessageEntity
def parse_message_entity(text: str, entity: MessageEntity) -> str:
"""Returns the text from a given :class:`telegram.MessageEntity`.
Args:
text (:obj:`str`): The text to extract the entity from.
entity (:class:`telegram.MessageEntity`): The entity to extract the text from.
Returns:
:obj:`str`: The text of the given entity.
"""
entity_text = text.encode("utf-16-le")
entity_text = entity_text[entity.offset * 2 : (entity.offset + entity.length) * 2]
return entity_text.decode("utf-16-le")
def parse_message_entities(
text: str, entities: Sequence[MessageEntity], types: Optional[Sequence[str]] = None
) -> Dict[MessageEntity, str]:
"""
Returns a :obj:`dict` that maps :class:`telegram.MessageEntity` to :obj:`str`.
It contains entities filtered by their ``type`` attribute as
the key, and the text that each entity belongs to as the value of the :obj:`dict`.
Args:
text (:obj:`str`): The text to extract the entity from.
entities (List[:class:`telegram.MessageEntity`]): The entities to extract the text from.
types (List[:obj:`str`], optional): List of ``MessageEntity`` types as strings. If the
``type`` attribute of an entity is contained in this list, it will be returned.
Defaults to :attr:`telegram.MessageEntity.ALL_TYPES`.
Returns:
Dict[:class:`telegram.MessageEntity`, :obj:`str`]: A dictionary of entities mapped to
the text that belongs to them, calculated based on UTF-16 codepoints.
"""
if types is None:
types = MessageEntity.ALL_TYPES
return {
entity: parse_message_entity(text, entity) for entity in entities if entity.type in types
}
+1 -1
View File
@@ -60,7 +60,7 @@ class StringEnum(str, _enum.Enum):
# Apply the __repr__ modification and __str__ fix to IntEnum
class IntEnum(_enum.IntEnum):
class IntEnum(_enum.IntEnum): # pylint: disable=invalid-slots
"""Helper class for int enums where ``str(member)`` prints the value, but ``repr(member)``
gives ``EnumName.MEMBER_NAME``.
"""
+12 -3
View File
@@ -26,19 +26,28 @@ Warning:
the changelog.
"""
import warnings
from typing import Type
from typing import Type, Union
from telegram.warnings import PTBUserWarning
def warn(message: str, category: Type[Warning] = PTBUserWarning, stacklevel: int = 0) -> None:
def warn(
message: Union[str, PTBUserWarning],
category: Type[Warning] = PTBUserWarning,
stacklevel: int = 0,
) -> None:
"""
Helper function used as a shortcut for warning with default values.
.. versionadded:: 20.0
Args:
message (:obj:`str`): Specify the warnings message to pass to ``warnings.warn()``.
message (:obj:`str` | :obj:`PTBUserWarning`): Specify the warnings message to pass to
``warnings.warn()``.
.. versionchanged:: 21.2
Now also accepts a :obj:`PTBUserWarning` instance.
category (:obj:`Type[Warning]`, optional): Specify the Warning class to pass to
``warnings.warn()``. Defaults to :class:`telegram.warnings.PTBUserWarning`.
stacklevel (:obj:`int`, optional): Specify the stacklevel to pass to ``warnings.warn()``.
+16 -10
View File
@@ -23,10 +23,10 @@ inside warnings.py.
.. versionadded:: 20.2
"""
from typing import Any, Callable, Type
from typing import Any, Callable, Type, Union
from telegram._utils.warnings import warn
from telegram.warnings import PTBDeprecationWarning
from telegram.warnings import PTBDeprecationWarning, PTBUserWarning
def build_deprecation_warning_message(
@@ -54,8 +54,9 @@ def warn_about_deprecated_arg_return_new_arg(
deprecated_arg_name: str,
new_arg_name: str,
bot_api_version: str,
ptb_version: str,
stacklevel: int = 2,
warn_callback: Callable[[str, Type[Warning], int], None] = warn,
warn_callback: Callable[[Union[str, PTBUserWarning], Type[Warning], int], None] = warn,
) -> Any:
"""A helper function for the transition in API when argument is renamed.
@@ -80,10 +81,12 @@ def warn_about_deprecated_arg_return_new_arg(
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,
PTBDeprecationWarning(
ptb_version,
f"Bot API {bot_api_version} renamed the argument '{deprecated_arg_name}' to "
f"'{new_arg_name}'.",
),
stacklevel=stacklevel + 1, # type: ignore[call-arg]
)
return deprecated_arg
@@ -94,6 +97,7 @@ def warn_about_deprecated_attr_in_property(
deprecated_attr_name: str,
new_attr_name: str,
bot_api_version: str,
ptb_version: str,
stacklevel: int = 2,
) -> None:
"""A helper function for the transition in API when attribute is renamed. Call from properties.
@@ -101,8 +105,10 @@ def warn_about_deprecated_attr_in_property(
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,
PTBDeprecationWarning(
ptb_version,
f"Bot API {bot_api_version} renamed the attribute '{deprecated_attr_name}' to "
f"'{new_attr_name}'.",
),
stacklevel=stacklevel + 1,
)
+1 -1
View File
@@ -51,7 +51,7 @@ class Version(NamedTuple):
__version_info__: Final[Version] = Version(
major=21, minor=0, micro=1, releaselevel="final", serial=0
major=21, minor=2, micro=0, releaselevel="final", serial=0
)
__version__: Final[str] = str(__version_info__)
+141 -4
View File
@@ -29,7 +29,7 @@ those classes.
* Most of the constants in this module are grouped into enums.
"""
# TODO: Remove this when https://github.com/PyCQA/pylint/issues/6887 is resolved.
# pylint: disable=invalid-enum-extension
# pylint: disable=invalid-enum-extension,invalid-slots
__all__ = [
"BOT_API_VERSION",
@@ -37,6 +37,10 @@ __all__ = [
"SUPPORTED_WEBHOOK_PORTS",
"ZERO_DATE",
"AccentColor",
"BackgroundFillLimit",
"BackgroundFillType",
"BackgroundTypeLimit",
"BackgroundTypeType",
"BotCommandLimit",
"BotCommandScopeType",
"BotDescriptionLimit",
@@ -142,7 +146,7 @@ class _AccentColor(NamedTuple):
#: :data:`telegram.__bot_api_version_info__`.
#:
#: .. versionadded:: 20.0
BOT_API_VERSION_INFO: Final[_BotAPIVersion] = _BotAPIVersion(major=7, minor=1)
BOT_API_VERSION_INFO: Final[_BotAPIVersion] = _BotAPIVersion(major=7, minor=3)
#: :obj:`str`: Telegram Bot API
#: version supported by this version of `python-telegram-bot`. Also available as
#: :data:`telegram.__bot_api_version__`.
@@ -822,6 +826,46 @@ class ChatLimit(IntEnum):
"""
class BackgroundTypeLimit(IntEnum):
"""This enum contains limitations for :class:`telegram.BackgroundTypeFill`,
:class:`telegram.BackgroundTypeWallpaper` and :class:`telegram.BackgroundTypePattern`.
The enum members of this enumeration are instances of :class:`int` and can be treated as such.
.. versionadded:: 21.2
"""
__slots__ = ()
MAX_DIMMING = 100
""":obj:`int`: Maximum value allowed for:
* :paramref:`~telegram.BackgroundTypeFill.dark_theme_dimming` parameter of
:class:`telegram.BackgroundTypeFill`
* :paramref:`~telegram.BackgroundTypeWallpaper.dark_theme_dimming` parameter of
:class:`telegram.BackgroundTypeWallpaper`
"""
MAX_INTENSITY = 100
""":obj:`int`: Maximum value allowed for :paramref:`~telegram.BackgroundTypePattern.intensity`
parameter of :class:`telegram.BackgroundTypePattern`
"""
class BackgroundFillLimit(IntEnum):
"""This enum contains limitations for :class:`telegram.BackgroundFillGradient`.
The enum members of this enumeration are instances of :class:`int` and can be treated as such.
.. versionadded:: 21.2
"""
__slots__ = ()
MAX_ROTATION_ANGLE = 359
""":obj:`int`: Maximum value allowed for:
:paramref:`~telegram.BackgroundFillGradient.rotation_angle` parameter of
:class:`telegram.BackgroundFillGradient`
"""
class ChatMemberStatus(StringEnum):
"""This enum contains the available states for :class:`telegram.ChatMember`. The enum
members of this enumeration are instances of :class:`str` and can be treated as such.
@@ -1427,6 +1471,21 @@ class LocationLimit(IntEnum):
:meth:`telegram.Bot.send_location`
"""
LIVE_PERIOD_FOREVER = int(hex(0x7FFFFFFF), 16)
""":obj:`int`: Value for live locations that can be edited indefinitely. Passed in:
* :paramref:`~telegram.InlineQueryResultLocation.live_period` parameter of
:class:`telegram.InlineQueryResultLocation`
* :paramref:`~telegram.InputLocationMessageContent.live_period` parameter of
:class:`telegram.InputLocationMessageContent`
* :paramref:`~telegram.Bot.edit_message_live_location.live_period` parameter of
:meth:`telegram.Bot.edit_message_live_location`
* :paramref:`~telegram.Bot.send_location.live_period` parameter of
:meth:`telegram.Bot.send_location`
.. versionadded:: 21.2
"""
MIN_PROXIMITY_ALERT_RADIUS = 1
""":obj:`int`: Minimum value allowed for:
@@ -1690,8 +1749,12 @@ class MessageOriginType(StringEnum):
class MessageType(StringEnum):
"""This enum contains the available types of :class:`telegram.Message`. The enum
members of this enumeration are instances of :class:`str` and can be treated as such.
"""This enum contains the available types of :class:`telegram.Message`. Here, a "type" means
a kind of message that is visually distinct from other kinds of messages in the Telegram app.
In particular, auxiliary attributes that can be present for multiple types of messages are
not considered in this enumeration.
The enum members of this enumeration are instances of :class:`str` and can be treated as such.
.. versionadded:: 20.0
"""
@@ -1710,6 +1773,11 @@ class MessageType(StringEnum):
.. versionadded:: 21.0
"""
BUSINESS_CONNECTION_ID = "business_connection_id"
""":obj:`str`: Messages with :attr:`telegram.Message.business_connection_id`.
.. versionadded:: 21.1
"""
CHANNEL_CHAT_CREATED = "channel_chat_created"
""":obj:`str`: Messages with :attr:`telegram.Message.channel_chat_created`."""
CHAT_SHARED = "chat_shared"
@@ -1717,6 +1785,11 @@ class MessageType(StringEnum):
.. versionadded:: 20.8
"""
CHAT_BACKGROUND_SET = "chat_background_set"
""":obj:`str`: Messages with :attr:`telegram.Message.chat_background_set`.
.. versionadded:: 21.2
"""
CONNECTED_WEBSITE = "connected_website"
""":obj:`str`: Messages with :attr:`telegram.Message.connected_website`."""
CONTACT = "contact"
@@ -1817,6 +1890,11 @@ class MessageType(StringEnum):
.. versionadded:: 21.0
"""
SENDER_BUSINESS_BOT = "sender_business_bot"
""":obj:`str`: Messages with :attr:`telegram.Message.sender_business_bot`.
.. versionadded:: 21.1
"""
STICKER = "sticker"
""":obj:`str`: Messages with :attr:`telegram.Message.sticker`."""
STORY = "story"
@@ -2312,6 +2390,9 @@ class StickerSetLimit(IntEnum):
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`.
.. deprecated:: 21.1
The animated sticker limit is now 120, the same as :attr:`MAX_STATIC_STICKERS`.
"""
MAX_STATIC_STICKERS = 120
""":obj:`int`: Maximum number of stickers allowed in a static sticker set, as given in
@@ -2504,6 +2585,26 @@ class UpdateType(StringEnum):
.. versionadded:: 20.8
"""
BUSINESS_CONNECTION = "business_connection"
""":obj:`str`: Updates with :attr:`telegram.Update.business_connection`.
.. versionadded:: 21.1
"""
BUSINESS_MESSAGE = "business_message"
""":obj:`str`: Updates with :attr:`telegram.Update.business_message`.
.. versionadded:: 21.1
"""
EDITED_BUSINESS_MESSAGE = "edited_business_message"
""":obj:`str`: Updates with :attr:`telegram.Update.edited_business_message`.
.. versionadded:: 21.1
"""
DELETED_BUSINESS_MESSAGES = "deleted_business_messages"
""":obj:`str`: Updates with :attr:`telegram.Update.deleted_business_messages`.
.. versionadded:: 21.1
"""
class InvoiceLimit(IntEnum):
@@ -2841,3 +2942,39 @@ class ReactionEmoji(StringEnum):
""":obj:`str`: Woman Shrugging"""
POUTING_FACE = "😡"
""":obj:`str`: Pouting face"""
class BackgroundTypeType(StringEnum):
"""This enum contains the available types of :class:`telegram.BackgroundType`. The enum
members of this enumeration are instances of :class:`str` and can be treated as such.
.. versionadded:: 21.2
"""
__slots__ = ()
FILL = "fill"
""":obj:`str`: A :class:`telegram.BackgroundType` with fill background."""
WALLPAPER = "wallpaper"
""":obj:`str`: A :class:`telegram.BackgroundType` with wallpaper background."""
PATTERN = "pattern"
""":obj:`str`: A :class:`telegram.BackgroundType` with pattern background."""
CHAT_THEME = "chat_theme"
""":obj:`str`: A :class:`telegram.BackgroundType` with chat_theme background."""
class BackgroundFillType(StringEnum):
"""This enum contains the available types of :class:`telegram.BackgroundFill`. The enum
members of this enumeration are instances of :class:`str` and can be treated as such.
.. versionadded:: 21.2
"""
__slots__ = ()
SOLID = "solid"
""":obj:`str`: A :class:`telegram.BackgroundFill` with solid fill."""
GRADIENT = "gradient"
""":obj:`str`: A :class:`telegram.BackgroundFill` with gradient fill."""
FREEFORM_GRADIENT = "freeform_gradient"
""":obj:`str`: A :class:`telegram.BackgroundFill` with freeform_gradient fill."""
+4
View File
@@ -27,6 +27,8 @@ __all__ = (
"BasePersistence",
"BaseRateLimiter",
"BaseUpdateProcessor",
"BusinessConnectionHandler",
"BusinessMessagesDeletedHandler",
"CallbackContext",
"CallbackDataCache",
"CallbackQueryHandler",
@@ -75,6 +77,8 @@ from ._defaults import Defaults
from ._dictpersistence import DictPersistence
from ._extbot import ExtBot
from ._handlers.basehandler import BaseHandler
from ._handlers.businessconnectionhandler import BusinessConnectionHandler
from ._handlers.businessmessagesdeletedhandler import BusinessMessagesDeletedHandler
from ._handlers.callbackqueryhandler import CallbackQueryHandler
from ._handlers.chatboosthandler import ChatBoostHandler
from ._handlers.chatjoinrequesthandler import ChatJoinRequestHandler
+1 -1
View File
@@ -208,7 +208,7 @@ class AIORateLimiter(BaseRateLimiter[int]):
callback: Callable[..., Coroutine[Any, Any, Union[bool, JSONDict, List[JSONDict]]]],
args: Any,
kwargs: Dict[str, Any],
endpoint: str,
endpoint: str, # noqa: ARG002
data: Dict[str, Any],
rate_limit_args: Optional[int],
) -> Union[bool, JSONDict, List[JSONDict]]:
+159 -86
View File
@@ -75,6 +75,8 @@ from telegram.ext._utils.types import BD, BT, CCT, CD, JQ, RT, UD, ConversationK
from telegram.warnings import PTBDeprecationWarning
if TYPE_CHECKING:
from socket import socket
from telegram import Message
from telegram.ext import ConversationHandler, JobQueue
from telegram.ext._applicationbuilder import InitApplicationBuilder
@@ -363,6 +365,7 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
self.__update_persistence_event = asyncio.Event()
self.__update_persistence_lock = asyncio.Lock()
self.__create_task_tasks: Set[asyncio.Task] = set() # Used for awaiting tasks upon exit
self.__stop_running_marker = asyncio.Event()
async def __aenter__(self: _AppType) -> _AppType: # noqa: PYI019
"""|async_context_manager| :meth:`initializes <initialize>` the App.
@@ -514,6 +517,7 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
await self._add_ch_to_persistence(handler)
self._initialized = True
self.__stop_running_marker.clear()
async def _add_ch_to_persistence(self, handler: "ConversationHandler") -> None:
self._conversation_handler_conversations.update(
@@ -668,14 +672,26 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
raise RuntimeError("This Application is not running!")
self._running = False
self.__stop_running_marker.clear()
_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")
await self.update_queue.join()
if self.__update_fetcher_task:
await self.__update_fetcher_task
if self.__update_fetcher_task.done():
try:
self.__update_fetcher_task.result()
except BaseException as exc:
_LOGGER.critical(
"Fetching updates was aborted due to %r. Suppressing "
"exception to ensure graceful shutdown.",
exc,
exc_info=True,
)
else:
await self.update_queue.put(_STOP_SIGNAL)
_LOGGER.debug("Waiting for update_queue to join")
await self.update_queue.join()
await self.__update_fetcher_task
_LOGGER.debug("Application stopped fetching of updates.")
if self._job_queue:
@@ -701,17 +717,36 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
shutdown of the application, i.e. the methods listed in :attr:`run_polling` and
:attr:`run_webhook` will still be executed.
This method can also be called within :meth:`post_init`. This allows for a graceful,
early shutdown of the application if some condition is met (e.g., a database connection
could not be established).
Note:
If the application is not running, this method does nothing.
If the application is not running and this method is not called within
:meth:`post_init`, this method does nothing.
Warning:
This method is designed to for use in combination with :meth:`run_polling` or
:meth:`run_webhook`. Using this method in combination with a custom logic for starting
and stopping the application is not guaranteed to work as expected. Use at your own
risk.
.. versionadded:: 20.5
.. versionchanged:: 21.2
Added support for calling within :meth:`post_init`.
"""
if self.running:
# This works because `__run` is using `loop.run_forever()`. If that changes, this
# method needs to be adapted.
asyncio.get_running_loop().stop()
else:
_LOGGER.debug("Application is not running, stop_running() does nothing.")
self.__stop_running_marker.set()
if not self._initialized:
_LOGGER.debug(
"Application is not running and not initialized. `stop_running()` likely has "
"no effect."
)
def run_polling(
self,
@@ -731,9 +766,7 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
polling updates from Telegram using :meth:`telegram.ext.Updater.start_polling` and
a graceful shutdown of the app on exit.
The app will shut down when :exc:`KeyboardInterrupt` or :exc:`SystemExit` is raised.
On unix, the app will also shut down on receiving the signals specified by
:paramref:`stop_signals`.
|app_run_shutdown| :paramref:`stop_signals`.
The order of execution by :meth:`run_polling` is roughly as follows:
@@ -824,9 +857,11 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
if (read_timeout, write_timeout, connect_timeout, pool_timeout) != ((DEFAULT_NONE,) * 4):
warn(
"Setting timeouts via `Application.run_polling` is deprecated. "
"Please use `ApplicationBuilder.get_updates_*_timeout` instead.",
PTBDeprecationWarning,
PTBDeprecationWarning(
"20.6",
"Setting timeouts via `Application.run_polling` is deprecated. "
"Please use `ApplicationBuilder.get_updates_*_timeout` instead.",
),
stacklevel=2,
)
@@ -866,15 +901,13 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
close_loop: bool = True,
stop_signals: ODVInput[Sequence[int]] = DEFAULT_NONE,
secret_token: Optional[str] = None,
unix: Optional[Union[str, Path]] = None,
unix: Optional[Union[str, Path, "socket"]] = None,
) -> None:
"""Convenience method that takes care of initializing and starting the app,
listening for updates from Telegram using :meth:`telegram.ext.Updater.start_webhook` and
a graceful shutdown of the app on exit.
The app will shut down when :exc:`KeyboardInterrupt` or :exc:`SystemExit` is raised.
On unix, the app will also shut down on receiving the signals specified by
:paramref:`stop_signals`.
|app_run_shutdown| :paramref:`stop_signals`.
If :paramref:`cert`
and :paramref:`key` are not provided, the webhook will be started directly on
@@ -959,8 +992,17 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
header isn't set or it is set to a wrong token.
.. versionadded:: 20.0
unix (:class:`pathlib.Path` | :obj:`str`, optional): Path to the unix socket file. Path
does not need to exist, in which case the file will be created.
unix (:class:`pathlib.Path` | :obj:`str` | :class:`socket.socket`, optional): Can be
either:
* the path to the unix socket file as :class:`pathlib.Path` or :obj:`str`. This
will be passed to `tornado.netutil.bind_unix_socket <https://www.tornadoweb.org/
en/stable/netutil.html#tornado.netutil.bind_unix_socket>`_ to create the socket.
If the Path does not exist, the file will be created.
* or the socket itself. This option allows you to e.g. restrict the permissions of
the socket for improved security. Note that you need to pass the correct family,
type and socket options yourself.
Caution:
This parameter is a replacement for the default TCP bind. Therefore, it is
@@ -969,6 +1011,8 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
appropriate :paramref:`webhook_url`.
.. versionadded:: 20.8
.. versionchanged:: 21.1
Added support to pass a socket instance itself.
"""
if not self.updater:
raise RuntimeError(
@@ -1025,25 +1069,28 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
loop.run_until_complete(self.initialize())
if self.post_init:
loop.run_until_complete(self.post_init(self))
if self.__stop_running_marker.is_set():
_LOGGER.info("Application received stop signal via `stop_running`. Shutting down.")
return
loop.run_until_complete(updater_coroutine) # one of updater.start_webhook/polling
loop.run_until_complete(self.start())
loop.run_forever()
except (KeyboardInterrupt, SystemExit):
_LOGGER.debug("Application received stop signal. Shutting down.")
except Exception as exc:
# In case the coroutine wasn't awaited, we don't need to bother the user with a warning
updater_coroutine.close()
raise exc
finally:
# We arrive here either by catching the exceptions above or if the loop gets stopped
# In case the coroutine wasn't awaited, we don't need to bother the user with a warning
updater_coroutine.close()
try:
# Mypy doesn't know that we already check if updater is None
if self.updater.running: # type: ignore[union-attr]
loop.run_until_complete(self.updater.stop()) # type: ignore[union-attr]
if self.running:
loop.run_until_complete(self.stop())
if self.post_stop:
loop.run_until_complete(self.post_stop(self))
# post_stop should be called only if stop was called!
if self.post_stop:
loop.run_until_complete(self.post_stop(self))
loop.run_until_complete(self.shutdown())
if self.post_shutdown:
loop.run_until_complete(self.post_shutdown(self))
@@ -1138,9 +1185,11 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
# Generator-based coroutines are not supported in Python 3.12+
if sys.version_info < (3, 12) and isinstance(coroutine, Generator):
warn(
"Generator-based coroutines are deprecated in create_task and will not work"
" in Python 3.12+",
category=PTBDeprecationWarning,
PTBDeprecationWarning(
"20.4",
"Generator-based coroutines are deprecated in create_task and will not"
" work in Python 3.12+",
),
)
return await asyncio.create_task(coroutine)
# If user uses generator in python 3.12+, Exception will happen and we cannot do
@@ -1172,45 +1221,44 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
finally:
self._mark_for_persistence_update(update=update)
async def _update_fetcher(self) -> None:
async def __update_fetcher(self) -> None:
# Continuously fetch updates from the queue. Exit only once the signal object is found.
while True:
try:
update = await self.update_queue.get()
update = await self.update_queue.get()
if update is _STOP_SIGNAL:
_LOGGER.debug("Dropping pending updates")
while not self.update_queue.empty():
self.update_queue.task_done()
if update is _STOP_SIGNAL:
# 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._update_processor.max_concurrent_updates > 1:
# We don't await the below because it has to be run concurrently
self.create_task(
self.__process_update_wrapper(update),
update=update,
name=f"Application:{self.bot.id}:process_concurrent_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`."
if self._update_processor.max_concurrent_updates > 1:
# We don't await the below because it has to be run concurrently
self.create_task(
self.__process_update_wrapper(update),
update=update,
name=f"Application:{self.bot.id}:process_concurrent_update",
)
else:
await self.__process_update_wrapper(update)
async def _update_fetcher(self) -> None:
try:
await self.__update_fetcher()
finally:
while not self.update_queue.empty():
_LOGGER.debug("Dropping pending update: %s", self.update_queue.get_nowait())
with contextlib.suppress(ValueError):
# Since we're shutting down here, it's not too bad if we call task_done
# on an empty queue
self.update_queue.task_done()
async def __process_update_wrapper(self, update: object) -> None:
await self._update_processor.process_update(update, self.process_update(update))
self.update_queue.task_done()
try:
await self._update_processor.process_update(update, self.process_update(update))
finally:
self.update_queue.task_done()
async def process_update(self, update: object) -> None:
"""Processes a single update and marks the update to be updated by the persistence later.
@@ -1239,30 +1287,43 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
try:
for handler in handlers:
check = handler.check_update(update) # Should the handler handle this update?
if not (check is None or check is False): # if yes,
if not context: # build a context if not already built
context = self.context_types.context.from_update(update, self)
await context.refresh_data()
coroutine: Coroutine = handler.handle_update(update, self, check, context)
if check is None or check is False:
continue
if not handler.block or ( # if handler is running with block=False,
handler.block is DEFAULT_TRUE
and isinstance(self.bot, ExtBot)
and self.bot.defaults
and not self.bot.defaults.block
):
self.create_task(
coroutine,
update=update,
name=(
f"Application:{self.bot.id}:process_update_non_blocking"
f":{handler}"
if not context: # build a context if not already built
try:
context = self.context_types.context.from_update(update, self)
except Exception as exc:
_LOGGER.critical(
(
"Error while building CallbackContext for update %s. "
"Update will not be processed."
),
update,
exc_info=exc,
)
else:
any_blocking = True
await coroutine
break # Only a max of 1 handler per group is handled
return
await context.refresh_data()
coroutine: Coroutine = handler.handle_update(update, self, check, context)
if not handler.block or ( # if handler is running with block=False,
handler.block is DEFAULT_TRUE
and isinstance(self.bot, ExtBot)
and self.bot.defaults
and not self.bot.defaults.block
):
self.create_task(
coroutine,
update=update,
name=(
f"Application:{self.bot.id}:process_update_non_blocking"
f":{handler}"
),
)
else:
any_blocking = True
await coroutine
break # Only a max of 1 handler per group is handled
# Stop processing with any other handler.
except ApplicationHandlerStop:
@@ -1796,13 +1857,25 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
callback,
block,
) in self.error_handlers.items():
context = self.context_types.context.from_error(
update=update,
error=error,
application=self,
job=job,
coroutine=coroutine,
)
try:
context = self.context_types.context.from_error(
update=update,
error=error,
application=self,
job=job,
coroutine=coroutine,
)
except Exception as exc:
_LOGGER.critical(
(
"Error while building CallbackContext for exception %s. "
"Exception will not be processed by error handlers."
),
error,
exc_info=exc,
)
return False
if not block or ( # If error handler has `block=False`, create a Task to run cb
block is DEFAULT_TRUE
and isinstance(self.bot, ExtBot)
+17 -7
View File
@@ -528,9 +528,11 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
:class:`ApplicationBuilder`: The same builder with the updated argument.
"""
warn(
"`ApplicationBuilder.proxy_url` is deprecated since version "
"20.7. Use `ApplicationBuilder.proxy` instead.",
PTBDeprecationWarning,
PTBDeprecationWarning(
"20.7",
"`ApplicationBuilder.proxy_url` is deprecated. Use `ApplicationBuilder.proxy` "
"instead.",
),
stacklevel=2,
)
return self.proxy(proxy_url)
@@ -760,9 +762,11 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
:class:`ApplicationBuilder`: The same builder with the updated argument.
"""
warn(
"`ApplicationBuilder.get_updates_proxy_url` is deprecated since version "
"20.7. Use `ApplicationBuilder.get_updates_proxy` instead.",
PTBDeprecationWarning,
PTBDeprecationWarning(
"20.7",
"`ApplicationBuilder.get_updates_proxy_url` is deprecated. Use "
"`ApplicationBuilder.get_updates_proxy` instead.",
),
stacklevel=2,
)
return self.get_updates_proxy(get_updates_proxy_url)
@@ -1334,7 +1338,13 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
Tip:
This can be used for custom stop logic that requires to await coroutines, e.g.
sending message to a chat before shutting down the bot
sending message to a chat before shutting down the bot.
Hint:
The callback will be called only, if :meth:`Application.stop` was indeed called
successfully. For example, if the application is stopped early by calling
:meth:`Application.stop_running` within :meth:`post_init`, then the set callback will
*not* be called.
Example:
.. code::
+1 -1
View File
@@ -163,7 +163,7 @@ class BasePersistence(Generic[UD, CD, BD], ABC):
return self._update_interval
@update_interval.setter
def update_interval(self, value: object) -> NoReturn:
def update_interval(self, _: object) -> NoReturn:
raise AttributeError(
"You can not assign a new value to update_interval after initialization."
)
+1 -1
View File
@@ -165,7 +165,7 @@ class SimpleUpdateProcessor(BaseUpdateProcessor):
async def do_process_update(
self,
update: object,
update: object, # noqa: ARG002
coroutine: "Awaitable[Any]",
) -> None:
"""Immediately awaits the coroutine, i.e. does not apply any additional processing.
+3 -3
View File
@@ -165,7 +165,7 @@ class CallbackContext(Generic[BT, UD, CD, BD]):
return self.application.bot_data
@bot_data.setter
def bot_data(self, value: object) -> NoReturn:
def bot_data(self, _: object) -> NoReturn:
raise AttributeError(
f"You can not assign a new value to bot_data, see {_STORING_DATA_WIKI}"
)
@@ -192,7 +192,7 @@ class CallbackContext(Generic[BT, UD, CD, BD]):
return None
@chat_data.setter
def chat_data(self, value: object) -> NoReturn:
def chat_data(self, _: object) -> NoReturn:
raise AttributeError(
f"You can not assign a new value to chat_data, see {_STORING_DATA_WIKI}"
)
@@ -214,7 +214,7 @@ class CallbackContext(Generic[BT, UD, CD, BD]):
return None
@user_data.setter
def user_data(self, value: object) -> NoReturn:
def user_data(self, _: object) -> NoReturn:
raise AttributeError(
f"You can not assign a new value to user_data, see {_STORING_DATA_WIKI}"
)
+54 -20
View File
@@ -156,9 +156,11 @@ class Defaults:
raise ValueError("`quote` and `do_quote` are mutually exclusive")
if disable_web_page_preview is not None:
warn(
"`Defaults.disable_web_page_preview` is deprecated. Use "
"`Defaults.link_preview_options` instead.",
category=PTBDeprecationWarning,
PTBDeprecationWarning(
"20.8",
"`Defaults.disable_web_page_preview` is deprecated. Use "
"`Defaults.link_preview_options` instead.",
),
stacklevel=2,
)
self._link_preview_options: Optional[LinkPreviewOptions] = LinkPreviewOptions(
@@ -169,8 +171,9 @@ class Defaults:
if quote is not None:
warn(
"`Defaults.quote` is deprecated. Use `Defaults.do_quote` instead.",
category=PTBDeprecationWarning,
PTBDeprecationWarning(
"20.8", "`Defaults.quote` is deprecated. Use `Defaults.do_quote` instead."
),
stacklevel=2,
)
self._do_quote: Optional[bool] = quote
@@ -179,13 +182,14 @@ class Defaults:
# Gather all defaults that actually have a default value
self._api_defaults = {}
for kwarg in (
"parse_mode",
"explanation_parse_mode",
"disable_notification",
"allow_sending_without_reply",
"protect_content",
"link_preview_options",
"disable_notification",
"do_quote",
"explanation_parse_mode",
"link_preview_options",
"parse_mode",
"protect_content",
"question_parse_mode",
):
value = getattr(self, kwarg)
if value is not None:
@@ -235,7 +239,7 @@ class Defaults:
return self._parse_mode
@parse_mode.setter
def parse_mode(self, value: object) -> NoReturn:
def parse_mode(self, _: object) -> NoReturn:
raise AttributeError("You can not assign a new value to parse_mode after initialization.")
@property
@@ -246,7 +250,7 @@ class Defaults:
return self._parse_mode
@explanation_parse_mode.setter
def explanation_parse_mode(self, value: object) -> NoReturn:
def explanation_parse_mode(self, _: object) -> NoReturn:
raise AttributeError(
"You can not assign a new value to explanation_parse_mode after initialization."
)
@@ -259,11 +263,41 @@ class Defaults:
return self._parse_mode
@quote_parse_mode.setter
def quote_parse_mode(self, value: object) -> NoReturn:
def quote_parse_mode(self, _: object) -> NoReturn:
raise AttributeError(
"You can not assign a new value to quote_parse_mode after initialization."
)
@property
def text_parse_mode(self) -> Optional[str]:
""":obj:`str`: Optional. Alias for :attr:`parse_mode`, used for
the corresponding parameter of :class:`telegram.InputPollOption`.
.. versionadded:: 21.2
"""
return self._parse_mode
@text_parse_mode.setter
def text_parse_mode(self, _: object) -> NoReturn:
raise AttributeError(
"You can not assign a new value to text_parse_mode after initialization."
)
@property
def question_parse_mode(self) -> Optional[str]:
""":obj:`str`: Optional. Alias for :attr:`parse_mode`, used for
the corresponding parameter of :meth:`telegram.Bot.send_poll`.
.. versionadded:: 21.2
"""
return self._parse_mode
@question_parse_mode.setter
def question_parse_mode(self, _: object) -> NoReturn:
raise AttributeError(
"You can not assign a new value to question_parse_mode after initialization."
)
@property
def disable_notification(self) -> Optional[bool]:
""":obj:`bool`: Optional. Sends the message silently. Users will
@@ -272,7 +306,7 @@ class Defaults:
return self._disable_notification
@disable_notification.setter
def disable_notification(self, value: object) -> NoReturn:
def disable_notification(self, _: object) -> NoReturn:
raise AttributeError(
"You can not assign a new value to disable_notification after initialization."
)
@@ -289,7 +323,7 @@ class Defaults:
return self._link_preview_options.is_disabled if self._link_preview_options else None
@disable_web_page_preview.setter
def disable_web_page_preview(self, value: object) -> NoReturn:
def disable_web_page_preview(self, _: object) -> NoReturn:
raise AttributeError(
"You can not assign a new value to disable_web_page_preview after initialization."
)
@@ -302,7 +336,7 @@ class Defaults:
return self._allow_sending_without_reply
@allow_sending_without_reply.setter
def allow_sending_without_reply(self, value: object) -> NoReturn:
def allow_sending_without_reply(self, _: object) -> NoReturn:
raise AttributeError(
"You can not assign a new value to allow_sending_without_reply after initialization."
)
@@ -318,7 +352,7 @@ class Defaults:
return self._do_quote if self._do_quote is not None else None
@quote.setter
def quote(self, value: object) -> NoReturn:
def quote(self, _: object) -> NoReturn:
raise AttributeError("You can not assign a new value to quote after initialization.")
@property
@@ -329,7 +363,7 @@ class Defaults:
return self._tzinfo
@tzinfo.setter
def tzinfo(self, value: object) -> NoReturn:
def tzinfo(self, _: object) -> NoReturn:
raise AttributeError("You can not assign a new value to tzinfo after initialization.")
@property
@@ -341,7 +375,7 @@ class Defaults:
return self._block
@block.setter
def block(self, value: object) -> NoReturn:
def block(self, _: object) -> NoReturn:
raise AttributeError("You can not assign a new value to block after initialization.")
@property
@@ -354,7 +388,7 @@ class Defaults:
return self._protect_content
@protect_content.setter
def protect_content(self, value: object) -> NoReturn:
def protect_content(self, _: object) -> NoReturn:
raise AttributeError(
"You can't assign a new value to protect_content after initialization."
)
+125 -10
View File
@@ -48,9 +48,10 @@ from telegram import (
BotDescription,
BotName,
BotShortDescription,
BusinessConnection,
CallbackQuery,
Chat,
ChatAdministratorRights,
ChatFullInfo,
ChatInviteLink,
ChatMember,
ChatPermissions,
@@ -63,6 +64,7 @@ from telegram import (
InlineKeyboardMarkup,
InlineQueryResultsButton,
InputMedia,
InputPollOption,
LinkPreviewOptions,
Location,
MaskPosition,
@@ -112,7 +114,8 @@ if TYPE_CHECKING:
)
from telegram.ext import BaseRateLimiter, Defaults
HandledTypes = TypeVar("HandledTypes", bound=Union[Message, CallbackQuery, Chat])
HandledTypes = TypeVar("HandledTypes", bound=Union[Message, CallbackQuery, ChatFullInfo])
KT = TypeVar("KT", bound=ReplyMarkup)
class ExtBot(Bot, Generic[RLARGS]):
@@ -260,7 +263,10 @@ class ExtBot(Bot, Generic[RLARGS]):
@classmethod
def _warn(
cls, message: str, category: Type[Warning] = PTBUserWarning, stacklevel: int = 0
cls,
message: Union[str, PTBUserWarning],
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.
@@ -434,6 +440,7 @@ class ExtBot(Bot, Generic[RLARGS]):
# 3) set the correct parse_mode for all InputMedia objects
# 4) handle the LinkPreviewOptions case (see below)
# 5) handle the ReplyParameters case (see below)
# 6) handle text_parse_mode in InputPollOption
for key, val in data.items():
# 1)
if isinstance(val, DefaultValue):
@@ -485,11 +492,29 @@ class ExtBot(Bot, Generic[RLARGS]):
data[key] = new_value
def _replace_keyboard(self, reply_markup: Optional[ReplyMarkup]) -> Optional[ReplyMarkup]:
# 6)
elif isinstance(val, Sequence) and all(
isinstance(obj, InputPollOption) for obj in val
):
new_val = []
for option in val:
if not isinstance(option.text_parse_mode, DefaultValue):
new_val.append(option)
else:
new_option = copy(option)
with new_option._unfrozen():
new_option.text_parse_mode = self.defaults.text_parse_mode
new_val.append(new_option)
data[key] = new_val
def _replace_keyboard(self, reply_markup: Optional[KT]) -> Optional[KT]:
# If the reply_markup is an inline keyboard and we allow arbitrary callback data, let the
# CallbackDataCache build a new keyboard with the data replaced. Otherwise return the input
if isinstance(reply_markup, InlineKeyboardMarkup) and self.callback_data_cache is not None:
return self.callback_data_cache.process_keyboard(reply_markup)
# for some reason mypy doesn't understand that IKB is a subtype of Optional[KT]
return self.callback_data_cache.process_keyboard( # type: ignore[return-value]
reply_markup
)
return reply_markup
@@ -549,7 +574,7 @@ class ExtBot(Bot, Generic[RLARGS]):
self.callback_data_cache.process_message(message=obj)
return obj # type: ignore[return-value]
if isinstance(obj, Chat) and obj.pinned_message:
if isinstance(obj, ChatFullInfo) and obj.pinned_message:
self.callback_data_cache.process_message(obj.pinned_message)
return obj
@@ -567,6 +592,7 @@ class ExtBot(Bot, Generic[RLARGS]):
caption_entities: Optional[Sequence["MessageEntity"]] = None,
link_preview_options: ODVInput["LinkPreviewOptions"] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -597,6 +623,7 @@ class ExtBot(Bot, Generic[RLARGS]):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=business_connection_id,
)
if isinstance(result, Message):
self._insert_callback_data(result)
@@ -846,7 +873,7 @@ class ExtBot(Bot, Generic[RLARGS]):
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
rate_limit_args: Optional[RLARGS] = None,
) -> Chat:
) -> ChatFullInfo:
# We override this method to call self._insert_callback_data
result = await super().get_chat(
chat_id=chat_id,
@@ -1178,7 +1205,6 @@ class ExtBot(Bot, Generic[RLARGS]):
name: str,
title: str,
stickers: Sequence["InputSticker"],
sticker_format: str,
sticker_type: Optional[str] = None,
needs_repainting: Optional[bool] = None,
*,
@@ -1194,7 +1220,6 @@ class ExtBot(Bot, Generic[RLARGS]):
name=name,
title=title,
stickers=stickers,
sticker_format=sticker_format,
sticker_type=sticker_type,
needs_repainting=needs_repainting,
read_timeout=read_timeout,
@@ -1515,6 +1540,7 @@ class ExtBot(Bot, Generic[RLARGS]):
horizontal_accuracy: Optional[float] = None,
heading: Optional[int] = None,
proximity_alert_radius: Optional[int] = None,
live_period: Optional[int] = None,
*,
location: Optional[Location] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1534,6 +1560,7 @@ class ExtBot(Bot, Generic[RLARGS]):
horizontal_accuracy=horizontal_accuracy,
heading=heading,
proximity_alert_radius=proximity_alert_radius,
live_period=live_period,
location=location,
read_timeout=read_timeout,
write_timeout=write_timeout,
@@ -2351,6 +2378,7 @@ class ExtBot(Bot, Generic[RLARGS]):
has_spoiler: Optional[bool] = None,
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -2384,6 +2412,7 @@ class ExtBot(Bot, Generic[RLARGS]):
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
business_connection_id=business_connection_id,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
@@ -2404,6 +2433,7 @@ class ExtBot(Bot, Generic[RLARGS]):
message_thread_id: Optional[int] = None,
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -2420,6 +2450,7 @@ class ExtBot(Bot, Generic[RLARGS]):
audio=audio,
duration=duration,
performer=performer,
business_connection_id=business_connection_id,
title=title,
caption=caption,
disable_notification=disable_notification,
@@ -2445,6 +2476,7 @@ class ExtBot(Bot, Generic[RLARGS]):
chat_id: Union[str, int],
action: str,
message_thread_id: Optional[int] = None,
business_connection_id: Optional[str] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2455,6 +2487,7 @@ class ExtBot(Bot, Generic[RLARGS]):
) -> bool:
return await super().send_chat_action(
chat_id=chat_id,
business_connection_id=business_connection_id,
action=action,
message_thread_id=message_thread_id,
read_timeout=read_timeout,
@@ -2476,6 +2509,7 @@ class ExtBot(Bot, Generic[RLARGS]):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -2505,6 +2539,7 @@ class ExtBot(Bot, Generic[RLARGS]):
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
business_connection_id=business_connection_id,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
@@ -2517,6 +2552,7 @@ class ExtBot(Bot, Generic[RLARGS]):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -2530,6 +2566,7 @@ class ExtBot(Bot, Generic[RLARGS]):
return await super().send_dice(
chat_id=chat_id,
disable_notification=disable_notification,
business_connection_id=business_connection_id,
reply_to_message_id=reply_to_message_id,
reply_markup=reply_markup,
emoji=emoji,
@@ -2558,6 +2595,7 @@ class ExtBot(Bot, Generic[RLARGS]):
message_thread_id: Optional[int] = None,
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -2581,6 +2619,7 @@ class ExtBot(Bot, Generic[RLARGS]):
allow_sending_without_reply=allow_sending_without_reply,
caption_entities=caption_entities,
protect_content=protect_content,
business_connection_id=business_connection_id,
message_thread_id=message_thread_id,
thumbnail=thumbnail,
reply_parameters=reply_parameters,
@@ -2601,6 +2640,7 @@ class ExtBot(Bot, Generic[RLARGS]):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -2617,6 +2657,7 @@ class ExtBot(Bot, Generic[RLARGS]):
disable_notification=disable_notification,
reply_to_message_id=reply_to_message_id,
reply_markup=reply_markup,
business_connection_id=business_connection_id,
allow_sending_without_reply=allow_sending_without_reply,
protect_content=protect_content,
message_thread_id=message_thread_id,
@@ -2718,6 +2759,7 @@ class ExtBot(Bot, Generic[RLARGS]):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -2748,6 +2790,7 @@ class ExtBot(Bot, Generic[RLARGS]):
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
business_connection_id=business_connection_id,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
@@ -2762,6 +2805,7 @@ class ExtBot(Bot, Generic[RLARGS]):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -2790,6 +2834,7 @@ class ExtBot(Bot, Generic[RLARGS]):
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
caption=caption,
business_connection_id=business_connection_id,
parse_mode=parse_mode,
caption_entities=caption_entities,
)
@@ -2806,6 +2851,7 @@ class ExtBot(Bot, Generic[RLARGS]):
message_thread_id: Optional[int] = None,
link_preview_options: ODVInput["LinkPreviewOptions"] = DEFAULT_NONE,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
disable_web_page_preview: Optional[bool] = None,
reply_to_message_id: Optional[int] = None,
@@ -2824,6 +2870,7 @@ class ExtBot(Bot, Generic[RLARGS]):
entities=entities,
disable_web_page_preview=disable_web_page_preview,
disable_notification=disable_notification,
business_connection_id=business_connection_id,
protect_content=protect_content,
message_thread_id=message_thread_id,
reply_to_message_id=reply_to_message_id,
@@ -2851,6 +2898,7 @@ class ExtBot(Bot, Generic[RLARGS]):
message_thread_id: Optional[int] = None,
has_spoiler: Optional[bool] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -2877,6 +2925,7 @@ class ExtBot(Bot, Generic[RLARGS]):
has_spoiler=has_spoiler,
reply_parameters=reply_parameters,
filename=filename,
business_connection_id=business_connection_id,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
@@ -2888,7 +2937,7 @@ class ExtBot(Bot, Generic[RLARGS]):
self,
chat_id: Union[int, str],
question: str,
options: Sequence[str],
options: Sequence[Union[str, "InputPollOption"]],
is_anonymous: Optional[bool] = None,
type: Optional[str] = None, # pylint: disable=redefined-builtin
allows_multiple_answers: Optional[bool] = None,
@@ -2904,6 +2953,9 @@ class ExtBot(Bot, Generic[RLARGS]):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
question_parse_mode: ODVInput[str] = DEFAULT_NONE,
question_entities: Optional[Sequence["MessageEntity"]] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -2932,6 +2984,7 @@ class ExtBot(Bot, Generic[RLARGS]):
close_date=close_date,
allow_sending_without_reply=allow_sending_without_reply,
explanation_entities=explanation_entities,
business_connection_id=business_connection_id,
protect_content=protect_content,
message_thread_id=message_thread_id,
reply_parameters=reply_parameters,
@@ -2940,6 +2993,8 @@ class ExtBot(Bot, Generic[RLARGS]):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
question_parse_mode=question_parse_mode,
question_entities=question_entities,
)
async def send_sticker(
@@ -2952,6 +3007,7 @@ class ExtBot(Bot, Generic[RLARGS]):
message_thread_id: Optional[int] = None,
emoji: Optional[str] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -2968,6 +3024,7 @@ class ExtBot(Bot, Generic[RLARGS]):
disable_notification=disable_notification,
reply_to_message_id=reply_to_message_id,
reply_markup=reply_markup,
business_connection_id=business_connection_id,
allow_sending_without_reply=allow_sending_without_reply,
protect_content=protect_content,
message_thread_id=message_thread_id,
@@ -2996,6 +3053,7 @@ class ExtBot(Bot, Generic[RLARGS]):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -3022,6 +3080,7 @@ class ExtBot(Bot, Generic[RLARGS]):
google_place_type=google_place_type,
allow_sending_without_reply=allow_sending_without_reply,
protect_content=protect_content,
business_connection_id=business_connection_id,
message_thread_id=message_thread_id,
reply_parameters=reply_parameters,
venue=venue,
@@ -3050,6 +3109,7 @@ class ExtBot(Bot, Generic[RLARGS]):
has_spoiler: Optional[bool] = None,
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -3077,6 +3137,7 @@ class ExtBot(Bot, Generic[RLARGS]):
caption_entities=caption_entities,
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=business_connection_id,
has_spoiler=has_spoiler,
thumbnail=thumbnail,
filename=filename,
@@ -3100,6 +3161,7 @@ class ExtBot(Bot, Generic[RLARGS]):
message_thread_id: Optional[int] = None,
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -3130,6 +3192,7 @@ class ExtBot(Bot, Generic[RLARGS]):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
business_connection_id=business_connection_id,
)
async def send_voice(
@@ -3145,6 +3208,7 @@ class ExtBot(Bot, Generic[RLARGS]):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -3176,6 +3240,7 @@ class ExtBot(Bot, Generic[RLARGS]):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
business_connection_id=business_connection_id,
)
async def set_chat_administrator_custom_title(
@@ -3462,6 +3527,7 @@ class ExtBot(Bot, Generic[RLARGS]):
self,
name: str,
user_id: int,
format: str, # pylint: disable=redefined-builtin
thumbnail: Optional[FileInput] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -3475,6 +3541,7 @@ class ExtBot(Bot, Generic[RLARGS]):
name=name,
user_id=user_id,
thumbnail=thumbnail,
format=format,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
@@ -3998,6 +4065,52 @@ class ExtBot(Bot, Generic[RLARGS]):
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
async def get_business_connection(
self,
business_connection_id: 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: Optional[JSONDict] = None,
rate_limit_args: Optional[RLARGS] = None,
) -> BusinessConnection:
return await super().get_business_connection(
business_connection_id=business_connection_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 replace_sticker_in_set(
self,
user_id: int,
name: str,
old_sticker: str,
sticker: "InputSticker",
*,
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: Optional[JSONDict] = None,
rate_limit_args: Optional[RLARGS] = None,
) -> bool:
return await super().replace_sticker_in_set(
user_id=user_id,
name=name,
old_sticker=old_sticker,
sticker=sticker,
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
@@ -4117,3 +4230,5 @@ class ExtBot(Bot, Generic[RLARGS]):
unpinAllGeneralForumTopicMessages = unpin_all_general_forum_topic_messages
getUserChatBoosts = get_user_chat_boosts
setMessageReaction = set_message_reaction
getBusinessConnection = get_business_connection
replaceStickerInSet = replace_sticker_in_set
@@ -0,0 +1,95 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2024
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains the BusinessConnectionHandler class."""
from typing import Optional, TypeVar
from telegram import Update
from telegram._utils.defaultvalue import DEFAULT_TRUE
from telegram._utils.types import SCT, DVType
from telegram.ext._handlers.basehandler import BaseHandler
from telegram.ext._utils._update_parsing import parse_chat_id, parse_username
from telegram.ext._utils.types import CCT, HandlerCallback
RT = TypeVar("RT")
class BusinessConnectionHandler(BaseHandler[Update, CCT]):
"""Handler class to handle Telegram
:attr:`Business Connections <telegram.Update.business_connection>`.
.. versionadded:: 21.1
Args:
callback (:term:`coroutine function`): The callback function for this handler. Will be
called when :meth:`check_update` has determined that an update should be processed by
this handler. Callback signature::
async def callback(update: Update, context: CallbackContext)
user_id (:obj:`int` | Collection[:obj:`int`], optional): Filters requests to allow only
those which are from the specified user ID(s).
username (:obj:`str` | Collection[:obj:`str`], optional): Filters requests to allow only
those which are from the specified username(s).
block (:obj:`bool`, optional): Determines whether the return value of the callback should
be awaited before processing the next handler in
:meth:`telegram.ext.Application.process_update`. Defaults to :obj:`True`.
.. seealso:: :wiki:`Concurrency`
Attributes:
callback (:term:`coroutine function`): The callback function for this handler.
block (:obj:`bool`): Determines whether the return value of the callback should be
awaited before processing the next handler in
:meth:`telegram.ext.Application.process_update`.
"""
__slots__ = (
"_user_ids",
"_usernames",
)
def __init__(
self,
callback: HandlerCallback[Update, CCT, RT],
user_id: Optional[SCT[int]] = None,
username: Optional[SCT[str]] = None,
block: DVType[bool] = DEFAULT_TRUE,
):
super().__init__(callback, block=block)
self._user_ids = parse_chat_id(user_id)
self._usernames = parse_username(username)
def check_update(self, update: object) -> bool:
"""Determines whether an update should be passed to this handler's :attr:`callback`.
Args:
update (:class:`telegram.Update` | :obj:`object`): Incoming update.
Returns:
:obj:`bool`
"""
if isinstance(update, Update) and update.business_connection:
if not self._user_ids and not self._usernames:
return True
if update.business_connection.user.id in self._user_ids:
return True
return update.business_connection.user.username in self._usernames
return False
@@ -0,0 +1,95 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2024
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains the BusinessMessagesDeletedHandler class."""
from typing import Optional, TypeVar
from telegram import Update
from telegram._utils.defaultvalue import DEFAULT_TRUE
from telegram._utils.types import SCT, DVType
from telegram.ext._handlers.basehandler import BaseHandler
from telegram.ext._utils._update_parsing import parse_chat_id, parse_username
from telegram.ext._utils.types import CCT, HandlerCallback
RT = TypeVar("RT")
class BusinessMessagesDeletedHandler(BaseHandler[Update, CCT]):
"""Handler class to handle
:attr:`deleted Telegram Business messages <telegram.Update.deleted_business_messages>`.
.. versionadded:: 21.1
Args:
callback (:term:`coroutine function`): The callback function for this handler. Will be
called when :meth:`check_update` has determined that an update should be processed by
this handler. Callback signature::
async def callback(update: Update, context: CallbackContext)
chat_id (:obj:`int` | Collection[:obj:`int`], optional): Filters requests to allow only
those which are from the specified chat ID(s).
username (:obj:`str` | Collection[:obj:`str`], optional): Filters requests to allow only
those which are from the specified username(s).
block (:obj:`bool`, optional): Determines whether the return value of the callback should
be awaited before processing the next handler in
:meth:`telegram.ext.Application.process_update`. Defaults to :obj:`True`.
.. seealso:: :wiki:`Concurrency`
Attributes:
callback (:term:`coroutine function`): The callback function for this handler.
block (:obj:`bool`): Determines whether the return value of the callback should be
awaited before processing the next handler in
:meth:`telegram.ext.Application.process_update`.
"""
__slots__ = (
"_chat_ids",
"_usernames",
)
def __init__(
self,
callback: HandlerCallback[Update, CCT, RT],
chat_id: Optional[SCT[int]] = None,
username: Optional[SCT[str]] = None,
block: DVType[bool] = DEFAULT_TRUE,
):
super().__init__(callback, block=block)
self._chat_ids = parse_chat_id(chat_id)
self._usernames = parse_username(username)
def check_update(self, update: object) -> bool:
"""Determines whether an update should be passed to this handler's :attr:`callback`.
Args:
update (:class:`telegram.Update` | :obj:`object`): Incoming update.
Returns:
:obj:`bool`
"""
if isinstance(update, Update) and update.deleted_business_messages:
if not self._chat_ids and not self._usernames:
return True
if update.deleted_business_messages.chat.id in self._chat_ids:
return True
return update.deleted_business_messages.chat.username in self._usernames
return False
@@ -159,8 +159,8 @@ class CallbackQueryHandler(BaseHandler[Update, CCT]):
def collect_additional_context(
self,
context: CCT,
update: Update,
application: "Application[Any, CCT, Any, Any, Any, Any]",
update: Update, # noqa: ARG002
application: "Application[Any, CCT, Any, Any, Any, Any]", # noqa: ARG002
check_result: Union[bool, Match[str]],
) -> None:
"""Add the result of ``re.match(pattern, update.callback_query.data)`` to
@@ -107,7 +107,5 @@ class ChatJoinRequestHandler(BaseHandler[Update, CCT]):
return True
if update.chat_join_request.chat.id in self._chat_ids:
return True
if update.chat_join_request.from_user.username in self._usernames:
return True
return False
return update.chat_join_request.from_user.username in self._usernames
return False
@@ -109,8 +109,8 @@ class ChosenInlineResultHandler(BaseHandler[Update, CCT]):
def collect_additional_context(
self,
context: CCT,
update: Update,
application: "Application[Any, CCT, Any, Any, Any, Any]",
update: Update, # noqa: ARG002
application: "Application[Any, CCT, Any, Any, Any, Any]", # noqa: ARG002
check_result: Union[bool, Match[str]],
) -> None:
"""This function adds the matched regex pattern result to
+4 -7
View File
@@ -152,15 +152,12 @@ class CommandHandler(BaseHandler[Update, CCT]):
Returns:
:obj:`bool`: Whether the args are valid for this handler.
"""
# pylint: disable=too-many-boolean-expressions
if (
return bool(
(self.has_args is None)
or (self.has_args is True and args)
or (self.has_args is False and not args)
or (isinstance(self.has_args, int) and len(args) == self.has_args)
):
return True
return False
)
def check_update(
self, update: object
@@ -207,8 +204,8 @@ class CommandHandler(BaseHandler[Update, CCT]):
def collect_additional_context(
self,
context: CCT,
update: Update,
application: "Application[Any, CCT, Any, Any, Any, Any]",
update: Update, # noqa: ARG002
application: "Application[Any, CCT, Any, Any, Any, Any]", # noqa: ARG002
check_result: Optional[Union[bool, Tuple[List[str], Optional[bool]]]],
) -> None:
"""Add text after the command to :attr:`CallbackContext.args` as list, split on single
+11 -11
View File
@@ -473,7 +473,7 @@ class ConversationHandler(BaseHandler[Update, CCT]):
return self._entry_points
@entry_points.setter
def entry_points(self, value: object) -> NoReturn:
def entry_points(self, _: object) -> NoReturn:
raise AttributeError(
"You can not assign a new value to entry_points after initialization."
)
@@ -487,7 +487,7 @@ class ConversationHandler(BaseHandler[Update, CCT]):
return self._states
@states.setter
def states(self, value: object) -> NoReturn:
def states(self, _: object) -> NoReturn:
raise AttributeError("You can not assign a new value to states after initialization.")
@property
@@ -499,7 +499,7 @@ class ConversationHandler(BaseHandler[Update, CCT]):
return self._fallbacks
@fallbacks.setter
def fallbacks(self, value: object) -> NoReturn:
def fallbacks(self, _: object) -> NoReturn:
raise AttributeError("You can not assign a new value to fallbacks after initialization.")
@property
@@ -508,7 +508,7 @@ class ConversationHandler(BaseHandler[Update, CCT]):
return self._allow_reentry
@allow_reentry.setter
def allow_reentry(self, value: object) -> NoReturn:
def allow_reentry(self, _: object) -> NoReturn:
raise AttributeError(
"You can not assign a new value to allow_reentry after initialization."
)
@@ -519,7 +519,7 @@ class ConversationHandler(BaseHandler[Update, CCT]):
return self._per_user
@per_user.setter
def per_user(self, value: object) -> NoReturn:
def per_user(self, _: object) -> NoReturn:
raise AttributeError("You can not assign a new value to per_user after initialization.")
@property
@@ -528,7 +528,7 @@ class ConversationHandler(BaseHandler[Update, CCT]):
return self._per_chat
@per_chat.setter
def per_chat(self, value: object) -> NoReturn:
def per_chat(self, _: object) -> NoReturn:
raise AttributeError("You can not assign a new value to per_chat after initialization.")
@property
@@ -537,7 +537,7 @@ class ConversationHandler(BaseHandler[Update, CCT]):
return self._per_message
@per_message.setter
def per_message(self, value: object) -> NoReturn:
def per_message(self, _: object) -> NoReturn:
raise AttributeError("You can not assign a new value to per_message after initialization.")
@property
@@ -551,7 +551,7 @@ class ConversationHandler(BaseHandler[Update, CCT]):
return self._conversation_timeout
@conversation_timeout.setter
def conversation_timeout(self, value: object) -> NoReturn:
def conversation_timeout(self, _: object) -> NoReturn:
raise AttributeError(
"You can not assign a new value to conversation_timeout after initialization."
)
@@ -562,7 +562,7 @@ class ConversationHandler(BaseHandler[Update, CCT]):
return self._name
@name.setter
def name(self, value: object) -> NoReturn:
def name(self, _: object) -> NoReturn:
raise AttributeError("You can not assign a new value to name after initialization.")
@property
@@ -574,7 +574,7 @@ class ConversationHandler(BaseHandler[Update, CCT]):
return self._persistent
@persistent.setter
def persistent(self, value: object) -> NoReturn:
def persistent(self, _: object) -> NoReturn:
raise AttributeError("You can not assign a new value to persistent after initialization.")
@property
@@ -586,7 +586,7 @@ class ConversationHandler(BaseHandler[Update, CCT]):
return self._map_to_parent
@map_to_parent.setter
def map_to_parent(self, value: object) -> NoReturn:
def map_to_parent(self, _: object) -> NoReturn:
raise AttributeError(
"You can not assign a new value to map_to_parent after initialization."
)
+2 -2
View File
@@ -130,8 +130,8 @@ class InlineQueryHandler(BaseHandler[Update, CCT]):
def collect_additional_context(
self,
context: CCT,
update: Update,
application: "Application[Any, CCT, Any, Any, Any, Any]",
update: Update, # noqa: ARG002
application: "Application[Any, CCT, Any, Any, Any, Any]", # noqa: ARG002
check_result: Optional[Union[bool, Match[str]]],
) -> None:
"""Add the result of ``re.match(pattern, update.inline_query.query)`` to
+2 -2
View File
@@ -102,8 +102,8 @@ class MessageHandler(BaseHandler[Update, CCT]):
def collect_additional_context(
self,
context: CCT,
update: Update,
application: "Application[Any, CCT, Any, Any, Any, Any]",
update: Update, # noqa: ARG002
application: "Application[Any, CCT, Any, Any, Any, Any]", # noqa: ARG002
check_result: Optional[Union[bool, Dict[str, object]]],
) -> None:
"""Adds possible output of data filters to the :class:`CallbackContext`."""
+2 -2
View File
@@ -171,8 +171,8 @@ class PrefixHandler(BaseHandler[Update, CCT]):
def collect_additional_context(
self,
context: CCT,
update: Update,
application: "Application[Any, CCT, Any, Any, Any, Any]",
update: Update, # noqa: ARG002
application: "Application[Any, CCT, Any, Any, Any, Any]", # noqa: ARG002
check_result: Optional[Union[bool, Tuple[List[str], Optional[bool]]]],
) -> None:
"""Add text after the command to :attr:`CallbackContext.args` as list, split on single
@@ -49,7 +49,7 @@ class StringCommandHandler(BaseHandler[str, CCT]):
called when :meth:`check_update` has determined that an update should be processed by
this handler. Callback signature::
async def callback(update: Update, context: CallbackContext)
async def callback(update: str, context: CallbackContext)
The return value of the callback is usually ignored except for the special case of
:class:`telegram.ext.ConversationHandler`.
@@ -98,8 +98,8 @@ class StringCommandHandler(BaseHandler[str, CCT]):
def collect_additional_context(
self,
context: CCT,
update: str,
application: "Application[Any, CCT, Any, Any, Any, Any]",
update: str, # noqa: ARG002
application: "Application[Any, CCT, Any, Any, Any, Any]", # noqa: ARG002
check_result: Optional[List[str]],
) -> None:
"""Add text after the command to :attr:`CallbackContext.args` as list, split on single
+3 -3
View File
@@ -52,7 +52,7 @@ class StringRegexHandler(BaseHandler[str, CCT]):
called when :meth:`check_update` has determined that an update should be processed by
this handler. Callback signature::
async def callback(update: Update, context: CallbackContext)
async def callback(update: str, context: CallbackContext)
The return value of the callback is usually ignored except for the special case of
:class:`telegram.ext.ConversationHandler`.
@@ -103,8 +103,8 @@ class StringRegexHandler(BaseHandler[str, CCT]):
def collect_additional_context(
self,
context: CCT,
update: str,
application: "Application[Any, CCT, Any, Any, Any, Any]",
update: str, # noqa: ARG002
application: "Application[Any, CCT, Any, Any, Any, Any]", # noqa: ARG002
check_result: Optional[Match[str]],
) -> None:
"""Add the result of ``re.match(pattern, update)`` to :attr:`CallbackContext.matches` as
+1 -1
View File
@@ -43,7 +43,7 @@ class TypeHandler(BaseHandler[UT, CCT]):
called when :meth:`check_update` has determined that an update should be processed by
this handler. Callback signature::
async def callback(update: Update, context: CallbackContext)
async def callback(update: object, context: CallbackContext)
The return value of the callback is usually ignored except for the special case of
:class:`telegram.ext.ConversationHandler`.
+12 -9
View File
@@ -31,9 +31,9 @@ try:
except ImportError:
APS_AVAILABLE = False
from telegram._utils.logging import get_logger
from telegram._utils.repr import build_repr_with_selected_attrs
from telegram._utils.types import JSONDict
from telegram._utils.warnings import warn
from telegram.ext._extbot import ExtBot
from telegram.ext._utils.types import CCT, JobCallback
@@ -45,6 +45,7 @@ if TYPE_CHECKING:
_ALL_DAYS = tuple(range(7))
_LOGGER = get_logger(__name__, class_name="JobQueue")
class JobQueue(Generic[CCT]):
@@ -587,13 +588,6 @@ class JobQueue(Generic[CCT]):
queue.
"""
# TODO: After v20.0, we should remove this warning.
if days != tuple(range(7)): # checks if user passed a custom value
warn(
"Prior to v20.0 the `days` parameter was not aligned to that of cron's weekday "
"scheme. We recommend double checking if the passed value is correct.",
stacklevel=2,
)
if not job_kwargs:
job_kwargs = {}
@@ -961,7 +955,16 @@ class Job(Generic[CCT]):
self, application: "Application[Any, CCT, Any, Any, Any, JobQueue[CCT]]"
) -> None:
try:
context = application.context_types.context.from_job(self, application)
try:
context = application.context_types.context.from_job(self, application)
except Exception as exc:
_LOGGER.critical(
"Error while building CallbackContext for job %s. Job will not be run.",
self._job,
exc_info=exc,
)
return
await context.refresh_data()
await self.callback(context)
except Exception as exc:

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