Compare commits

...

22 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
115 changed files with 3561 additions and 662 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@v2.0.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
+3 -3
View File
@@ -6,7 +6,7 @@ ci:
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: 'v0.3.5'
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.3.0
rev: 24.4.2
hooks:
- id: black
args:
@@ -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.9.0
rev: v1.10.0
hooks:
- id: mypy
name: mypy-ptb
+52
View File
@@ -4,6 +4,58 @@
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
==============
+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.2-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.2** 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.2-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.2** 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 -3
View File
@@ -20,9 +20,9 @@ author = "Leandro Toledo"
# built documents.
#
# The short X.Y version.
version = "21.1.1" # telegram.__version__[:3]
version = "21.2" # telegram.__version__[:3]
# The full version, including alpha/beta/rc tags.
release = "21.1.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
+12
View File
@@ -28,6 +28,16 @@ Available Types
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
@@ -36,6 +46,7 @@ Available Types
telegram.chatboostsourcegiveaway
telegram.chatboostsourcepremium
telegram.chatboostupdated
telegram.chatfullinfo
telegram.chatinvitelink
telegram.chatjoinrequest
telegram.chatlocation
@@ -77,6 +88,7 @@ Available Types
telegram.inputmediadocument
telegram.inputmediaphoto
telegram.inputmediavideo
telegram.inputpolloption
telegram.inputsticker
telegram.keyboardbutton
telegram.keyboardbuttonpolltype
+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:
+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:
+6
View File
@@ -0,0 +1,6 @@
InputPollOption
===============
.. autoclass:: telegram.InputPollOption
: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
+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", "W"]
"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
+26 -1
View File
@@ -22,6 +22,15 @@ __author__ = "devs@python-telegram-bot.org"
__all__ = (
"Animation",
"Audio",
"BackgroundFill",
"BackgroundFillFreeformGradient",
"BackgroundFillGradient",
"BackgroundFillSolid",
"BackgroundType",
"BackgroundTypeChatTheme",
"BackgroundTypeFill",
"BackgroundTypePattern",
"BackgroundTypeWallpaper",
"Birthdate",
"Bot",
"BotCommand",
@@ -46,6 +55,7 @@ __all__ = (
"CallbackQuery",
"Chat",
"ChatAdministratorRights",
"ChatBackground",
"ChatBoost",
"ChatBoostAdded",
"ChatBoostRemoved",
@@ -54,6 +64,7 @@ __all__ = (
"ChatBoostSourceGiveaway",
"ChatBoostSourcePremium",
"ChatBoostUpdated",
"ChatFullInfo",
"ChatInviteLink",
"ChatJoinRequest",
"ChatLocation",
@@ -131,6 +142,7 @@ __all__ = (
"InputMediaPhoto",
"InputMediaVideo",
"InputMessageContent",
"InputPollOption",
"InputSticker",
"InputTextMessageContent",
"InputVenueMessageContent",
@@ -258,6 +270,18 @@ from ._business import (
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,
@@ -269,6 +293,7 @@ from ._chatboost import (
ChatBoostUpdated,
UserChatBoosts,
)
from ._chatfullinfo import ChatFullInfo
from ._chatinvitelink import ChatInviteLink
from ._chatjoinrequest import ChatJoinRequest
from ._chatlocation import ChatLocation
@@ -403,7 +428,7 @@ 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
+10 -6
View File
@@ -17,7 +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 an object that represents a Telegram Birthday."""
from datetime import datetime
from datetime import date
from typing import Optional
from telegram._telegramobject import TelegramObject
@@ -26,7 +26,7 @@ from telegram._utils.types import JSONDict
class Birthdate(TelegramObject):
"""
This object represents a user's birthday.
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.
@@ -70,19 +70,23 @@ class Birthdate(TelegramObject):
self._freeze()
def to_date(self, year: Optional[int] = None) -> datetime:
"""Return the birthdate as a datetime object.
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.datetime`: The birthdate as a datetime object.
: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 datetime(year or self.year, self.month, self.day) # type: ignore[arg-type]
return date(year or self.year, self.month, self.day) # type: ignore[arg-type]
+70 -42
View File
@@ -58,9 +58,9 @@ from telegram._botcommandscope import BotCommandScope
from telegram._botdescription import BotDescription, BotShortDescription
from telegram._botname import BotName
from telegram._business import BusinessConnection
from telegram._chat import Chat
from telegram._chatadministratorrights import ChatAdministratorRights
from telegram._chatboost import UserChatBoosts
from telegram._chatfullinfo import ChatFullInfo
from telegram._chatinvitelink import ChatInviteLink
from telegram._chatmember import ChatMember
from telegram._chatpermissions import ChatPermissions
@@ -84,7 +84,7 @@ from telegram._inline.inlinequeryresultsbutton import InlineQueryResultsButton
from telegram._menubutton import MenuButton
from telegram._message import Message
from telegram._messageid import MessageId
from telegram._poll import Poll
from telegram._poll import InputPollOption, Poll
from telegram._reaction import ReactionType, ReactionTypeCustomEmoji, ReactionTypeEmoji
from telegram._reply import ReplyParameters
from telegram._sentwebappmessage import SentWebAppMessage
@@ -524,7 +524,10 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
@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:
"""Convenience method to issue a warning. This method is here mostly to make it easier
for ExtBot to add 1 level to all warning calls.
@@ -837,7 +840,6 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
f"Please use 'Bot.{endpoint}' instead of "
f"'Bot.do_api_request(\"{endpoint}\", ...)'"
),
PTBDeprecationWarning,
stacklevel=2,
)
@@ -2287,9 +2289,10 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
"""
Use this method to send audio files, if you want Telegram clients to display the file
as a playable voice message. For this to work, your audio must be in an ``.ogg`` file
encoded with OPUS (other formats may be sent as Audio or Document). Bots can currently
send voice messages of up to :tg-const:`telegram.constants.FileSizeLimit.FILESIZE_UPLOAD`
in size, this limit may be changed in the future.
encoded with OPUS , or in .MP3 format, or in .M4A format (other formats may be sent as
:class:`~telegram.Audio` or :class:`~telegram.Document`). Bots can currently send voice
messages of up to :tg-const:`telegram.constants.FileSizeLimit.FILESIZE_UPLOAD` in size,
this limit may be changed in the future.
Note:
To use this method, the file must have the type :mimetype:`audio/ogg` and be no more
@@ -2610,7 +2613,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
live_period (:obj:`int`, optional): Period in seconds for which the location will be
updated, should be between
:tg-const:`telegram.constants.LocationLimit.MIN_LIVE_PERIOD` and
:tg-const:`telegram.constants.LocationLimit.MAX_LIVE_PERIOD`.
:tg-const:`telegram.constants.LocationLimit.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.constants.LocationLimit.MIN_HEADING` and
@@ -2720,6 +2725,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
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,
@@ -2758,6 +2764,15 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
if specified.
reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): An object for a new
inline keyboard.
live_period (:obj:`int`, optional): New period in seconds during which the location
can be updated, starting from the message send date. If
:tg-const:`telegram.constants.LocationLimit.LIVE_PERIOD_FOREVER` is specified,
then the location can be updated forever. Otherwise, the new value must not exceed
the current ``live_period`` by more than a day, and the live location expiration
date must remain within the next 90 days. If not specified, then ``live_period``
remains unchanged
.. versionadded:: 21.2.
Keyword Args:
location (:class:`telegram.Location`, optional): The location to send.
@@ -2790,6 +2805,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
"horizontal_accuracy": horizontal_accuracy,
"heading": heading,
"proximity_alert_radius": proximity_alert_radius,
"live_period": live_period,
}
return await self._send_message(
@@ -4195,10 +4211,12 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
except NotImplementedError:
arg_read_timeout = 2
self._warn(
f"The class {self._request[0].__class__.__name__} does not override "
"the property `read_timeout`. Overriding this property will be mandatory in "
"future versions. Using 2 seconds as fallback.",
PTBDeprecationWarning,
PTBDeprecationWarning(
"20.7",
f"The class {self._request[0].__class__.__name__} does not override "
"the property `read_timeout`. Overriding this property will be mandatory "
"in future versions. Using 2 seconds as fallback.",
),
stacklevel=2,
)
@@ -4434,16 +4452,19 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
) -> Chat:
) -> ChatFullInfo:
"""
Use this method to get up to date information about the chat (current name of the user for
one-on-one conversations, current username of a user, group or channel, etc.).
.. versionchanged:: 21.2
In accordance to Bot API 7.3, this method now returns a :class:`telegram.ChatFullInfo`.
Args:
chat_id (:obj:`int` | :obj:`str`): |chat_id_channel|
Returns:
:class:`telegram.Chat`
:class:`telegram.ChatFullInfo`
Raises:
:class:`telegram.error.TelegramError`
@@ -4461,7 +4482,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
api_kwargs=api_kwargs,
)
return Chat.de_json(result, self) # type: ignore[return-value]
return ChatFullInfo.de_json(result, self) # type: ignore[return-value]
async def get_chat_administrators(
self,
@@ -5311,7 +5332,8 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
.. versionadded:: 20.6
can_edit_stories (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can
edit stories posted by other users.
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
can_delete_stories (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can
@@ -6312,7 +6334,6 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
name: str,
title: str,
stickers: Sequence["InputSticker"],
sticker_format: Optional[str] = None,
sticker_type: Optional[str] = None,
needs_repainting: Optional[bool] = None,
*,
@@ -6339,6 +6360,9 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
Removed the deprecated parameters mentioned above and adjusted the order of the
parameters.
.. versionremoved:: 21.2
Removed the deprecated parameter ``sticker_format``.
Args:
user_id (:obj:`int`): User identifier of created sticker set owner.
name (:obj:`str`): Short name of sticker set, to be used in t.me/addstickers/ URLs
@@ -6358,16 +6382,6 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
.. versionadded:: 20.2
sticker_format (:obj:`str`): Format of stickers in the set, must be one of
:attr:`~telegram.constants.StickerFormat.STATIC`,
:attr:`~telegram.constants.StickerFormat.ANIMATED` or
:attr:`~telegram.constants.StickerFormat.VIDEO`.
.. versionadded:: 20.2
.. deprecated:: 21.1
Use :paramref:`telegram.InputSticker.format` instead.
sticker_type (:obj:`str`, optional): Type of stickers in the set, pass
:attr:`telegram.Sticker.REGULAR` or :attr:`telegram.Sticker.MASK`, or
:attr:`telegram.Sticker.CUSTOM_EMOJI`. By default, a regular sticker set is created
@@ -6387,20 +6401,11 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
Raises:
:class:`telegram.error.TelegramError`
"""
if sticker_format is not None:
warn(
"The parameter `sticker_format` is deprecated. Use the parameter"
" `InputSticker.format` in the parameter `stickers` instead.",
stacklevel=2,
category=PTBDeprecationWarning,
)
data: JSONDict = {
"user_id": user_id,
"name": name,
"title": title,
"stickers": stickers,
"sticker_format": sticker_format,
"sticker_type": sticker_type,
"needs_repainting": needs_repainting,
}
@@ -6815,7 +6820,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
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,
@@ -6832,6 +6837,8 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
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,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@@ -6848,14 +6855,20 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
chat_id (:obj:`int` | :obj:`str`): |chat_id_channel|
question (:obj:`str`): Poll question, :tg-const:`telegram.Poll.MIN_QUESTION_LENGTH`-
:tg-const:`telegram.Poll.MAX_QUESTION_LENGTH` characters.
options (Sequence[:obj:`str`]): Sequence of answer options,
options (Sequence[:obj:`str` | :class:`telegram.InputPollOption`]): Sequence of
:tg-const:`telegram.Poll.MIN_OPTION_NUMBER`-
:tg-const:`telegram.Poll.MAX_OPTION_NUMBER` strings
:tg-const:`telegram.Poll.MAX_OPTION_NUMBER` answer options. Each option may either
be a string with
:tg-const:`telegram.Poll.MIN_OPTION_LENGTH`-
:tg-const:`telegram.Poll.MAX_OPTION_LENGTH` characters each.
:tg-const:`telegram.Poll.MAX_OPTION_LENGTH` characters or an
:class:`~telegram.InputPollOption` object. Strings are converted to
:class:`~telegram.InputPollOption` objects automatically.
.. versionchanged:: 20.0
|sequenceargs|
.. versionchanged:: 21.2
Bot API 7.3 adds support for :class:`~telegram.InputPollOption` objects.
is_anonymous (:obj:`bool`, optional): :obj:`True`, if the poll needs to be anonymous,
defaults to :obj:`True`.
type (:obj:`str`, optional): Poll type, :tg-const:`telegram.Poll.QUIZ` or
@@ -6910,6 +6923,16 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
business_connection_id (:obj:`str`, optional): |business_id_str|
.. versionadded:: 21.1
question_parse_mode (:obj:`str`, optional): Mode for parsing entities in the question.
See the constants in :class:`telegram.constants.ParseMode` for the available modes.
Currently, only custom emoji entities are allowed.
.. versionadded:: 21.2
question_entities (Sequence[:class:`telegram.Message`], optional): Special entities
that appear in the poll :paramref:`question`. It can be specified instead of
:paramref:`question_parse_mode`.
.. versionadded:: 21.2
Keyword Args:
allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
@@ -6941,7 +6964,10 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
data: JSONDict = {
"chat_id": chat_id,
"question": question,
"options": options,
"options": [
InputPollOption(option) if isinstance(option, str) else option
for option in options
],
"explanation_parse_mode": explanation_parse_mode,
"is_anonymous": is_anonymous,
"type": type,
@@ -6952,6 +6978,8 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
"explanation_entities": explanation_entities,
"open_period": open_period,
"close_date": close_date,
"question_parse_mode": question_parse_mode,
"question_entities": question_entities,
}
return await self._send_message(
@@ -8867,7 +8895,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
api_kwargs=api_kwargs,
)
def to_dict(self, recursive: bool = True) -> JSONDict:
def to_dict(self, recursive: bool = True) -> JSONDict: # noqa: ARG002
"""See :meth:`telegram.TelegramObject.to_dict`."""
data: JSONDict = {"id": self.id, "username": self.username, "first_name": self.first_name}
+4 -4
View File
@@ -189,7 +189,7 @@ class BusinessMessagesDeleted(TelegramObject):
class BusinessIntro(TelegramObject):
"""
This object represents the intro of a business account.
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
@@ -246,7 +246,7 @@ class BusinessIntro(TelegramObject):
class BusinessLocation(TelegramObject):
"""
This object represents the location of a business account.
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
@@ -298,7 +298,7 @@ class BusinessLocation(TelegramObject):
class BusinessOpeningHoursInterval(TelegramObject):
"""
This object represents the time intervals describing business opening hours.
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
@@ -390,7 +390,7 @@ class BusinessOpeningHoursInterval(TelegramObject):
class BusinessOpeningHours(TelegramObject):
"""
This object represents the opening hours of a business account.
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
+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(
+359 -10
View File
@@ -20,7 +20,7 @@
"""This module contains an object that represents a Telegram Chat."""
from datetime import datetime
from html import escape
from typing import TYPE_CHECKING, Final, Optional, Sequence, Tuple, Union
from typing import TYPE_CHECKING, Any, Final, Optional, Sequence, Tuple, Union
from telegram import constants
from telegram._birthdate import Birthdate
@@ -36,9 +36,11 @@ from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram._utils.types import CorrectOptionID, FileInput, JSONDict, ODVInput, ReplyMarkup
from telegram._utils.warnings import warn
from telegram.helpers import escape_markdown
from telegram.helpers import mention_html as helpers_mention_html
from telegram.helpers import mention_markdown as helpers_mention_markdown
from telegram.warnings import PTBDeprecationWarning
if TYPE_CHECKING:
from telegram import (
@@ -57,6 +59,7 @@ if TYPE_CHECKING:
InputMediaDocument,
InputMediaPhoto,
InputMediaVideo,
InputPollOption,
LabeledPrice,
LinkPreviewOptions,
Location,
@@ -74,6 +77,45 @@ if TYPE_CHECKING:
)
_deprecated_attrs = (
"accent_color_id",
"active_usernames",
"available_reactions",
"background_custom_emoji_id",
"bio",
"birthdate",
"business_intro",
"business_location",
"business_opening_hours",
"can_set_sticker_set",
"custom_emoji_sticker_set_name",
"description",
"emoji_status_custom_emoji_id",
"emoji_status_expiration_date",
"has_aggressive_anti_spam_enabled",
"has_hidden_members",
"has_private_forwards",
"has_protected_content",
"has_restricted_voice_and_video_messages",
"has_visible_history",
"invite_link",
"join_by_request",
"join_to_send_messages",
"linked_chat_id",
"location",
"message_auto_delete_time",
"permissions",
"personal_chat",
"photo",
"pinned_message",
"profile_accent_color_id",
"profile_background_custom_emoji_id",
"slow_mode_delay",
"sticker_set_name",
"unrestrict_boost_count",
)
class Chat(TelegramObject):
"""This object represents a chat.
@@ -107,62 +149,134 @@ class Chat(TelegramObject):
last_name (:obj:`str`, optional): Last name of the other party in a private chat.
photo (:class:`telegram.ChatPhoto`, optional): Chat photo.
Returned only in :meth:`telegram.Bot.get_chat`.
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
bio (:obj:`str`, optional): Bio of the other party in a private chat. Returned only in
:meth:`telegram.Bot.get_chat`.
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
has_private_forwards (:obj:`bool`, optional): :obj:`True`, if privacy settings of the other
party in the private chat allows to use ``tg://user?id=<user_id>`` links only in chats
with the user. Returned only in :meth:`telegram.Bot.get_chat`.
.. versionadded:: 13.9
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
description (:obj:`str`, optional): Description, for groups, supergroups and channel chats.
Returned only in :meth:`telegram.Bot.get_chat`.
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
invite_link (:obj:`str`, optional): Primary invite link, for groups, supergroups and
channel. Returned only in :meth:`telegram.Bot.get_chat`.
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
pinned_message (:class:`telegram.Message`, optional): The most recent pinned message
(by sending date). Returned only in :meth:`telegram.Bot.get_chat`.
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
permissions (:class:`telegram.ChatPermissions`): Optional. Default chat member permissions,
for groups and supergroups. Returned only in :meth:`telegram.Bot.get_chat`.
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
slow_mode_delay (:obj:`int`, optional): For supergroups, the minimum allowed delay between
consecutive messages sent by each unprivileged user.
Returned only in :meth:`telegram.Bot.get_chat`.
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
message_auto_delete_time (:obj:`int`, optional): The time after which all messages sent to
the chat will be automatically deleted; in seconds. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 13.4
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
has_protected_content (:obj:`bool`, optional): :obj:`True`, if messages from the chat can't
be forwarded to other chats. Returned only in :meth:`telegram.Bot.get_chat`.
.. versionadded:: 13.9
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
has_visible_history (:obj:`bool`, optional): :obj:`True`, if new chat members will have
access to old messages; available only to chat administrators. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.8
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
sticker_set_name (:obj:`str`, optional): For supergroups, name of group sticker set.
Returned only in :meth:`telegram.Bot.get_chat`.
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
can_set_sticker_set (:obj:`bool`, optional): :obj:`True`, if the bot can change group the
sticker set. Returned only in :meth:`telegram.Bot.get_chat`.
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
linked_chat_id (:obj:`int`, optional): Unique identifier for the linked chat, i.e. the
discussion group identifier for a channel and vice versa; for supergroups and channel
chats. Returned only in :meth:`telegram.Bot.get_chat`.
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
location (:class:`telegram.ChatLocation`, optional): For supergroups, the location to which
the supergroup is connected. Returned only in :meth:`telegram.Bot.get_chat`.
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
join_to_send_messages (:obj:`bool`, optional): :obj:`True`, if users need to join the
supergroup before they can send messages. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.0
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
join_by_request (:obj:`bool`, optional): :obj:`True`, if all users directly joining the
supergroup need to be approved by supergroup administrators. Returned only in
:meth:`telegram.Bot.get_chat`.
supergroup without using an invite link need to be approved by supergroup
administrators. Returned only in :meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.0
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
has_restricted_voice_and_video_messages (:obj:`bool`, optional): :obj:`True`, if the
privacy settings of the other party restrict sending voice and video note messages
in the private chat. Returned only in :meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.0
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
is_forum (:obj:`bool`, optional): :obj:`True`, if the supergroup chat is a forum
(has topics_ enabled).
@@ -173,27 +287,47 @@ class Chat(TelegramObject):
only in :meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.0
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
business_intro (:class:`telegram.BusinessIntro`, optional): For private chats with
business accounts, the intro of the business. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 21.1
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
business_location (:class:`telegram.BusinessLocation`, optional): For private chats with
business accounts, the location of the business. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 21.1
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
business_opening_hours (:class:`telegram.BusinessOpeningHours`, optional): For private
chats with business accounts, the opening hours of the business. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 21.1
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
available_reactions (Sequence[:class:`telegram.ReactionType`], optional): List of available
reactions allowed in the chat. If omitted, then all of
:const:`telegram.constants.ReactionEmoji` are allowed. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.8
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
accent_color_id (:obj:`int`, optional): Identifier of the
:class:`accent color <telegram.constants.AccentColor>` for the chat name and
backgrounds of the chat photo, reply header, and link preview. See `accent colors`_
@@ -201,62 +335,110 @@ class Chat(TelegramObject):
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.8
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
background_custom_emoji_id (:obj:`str`, optional): Custom emoji identifier of emoji chosen
by the chat for the reply header and link preview background. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.8
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
profile_accent_color_id (:obj:`int`, optional): Identifier of the
:class:`accent color <telegram.constants.ProfileAccentColor>` for the chat's profile
background. See profile `accent colors`_ for more details. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.8
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
profile_background_custom_emoji_id (:obj:`str`, optional): Custom emoji identifier of
the emoji chosen by the chat for its profile background. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.8
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
emoji_status_custom_emoji_id (:obj:`str`, optional): Custom emoji identifier of emoji
status of the chat or the other party in a private chat. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.0
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
emoji_status_expiration_date (:class:`datetime.datetime`, optional): Expiration date of
emoji status of the chat or the other party in a private chat, in seconds. Returned
only in :meth:`telegram.Bot.get_chat`.
|datetime_localization|
.. versionadded:: 20.5
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
has_aggressive_anti_spam_enabled (:obj:`bool`, optional): :obj:`True`, if aggressive
anti-spam checks are enabled in the supergroup. The field is only available to chat
administrators. Returned only in :meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.0
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
has_hidden_members (:obj:`bool`, optional): :obj:`True`, if non-administrators can only
get the list of bots and administrators in the chat. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.0
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
unrestrict_boost_count (:obj:`int`, optional): For supergroups, the minimum number of
boosts that a non-administrator user needs to add in order to ignore slow mode and chat
permissions. Returned only in :meth:`telegram.Bot.get_chat`.
.. versionadded:: 21.0
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
custom_emoji_sticker_set_name (:obj:`str`, optional): For supergroups, the name of the
group's custom emoji sticker set. Custom emoji from this set can be used by all users
and bots in the group. Returned only in :meth:`telegram.Bot.get_chat`.
.. versionadded:: 21.0
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
birthdate (:obj:`telegram.Birthdate`, optional): For private chats,
the date of birth of the user. Returned only in :meth:`telegram.Bot.get_chat`.
.. versionadded:: 21.1
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
personal_chat (:obj:`telegram.Chat`, optional): For private chats, the personal channel of
the user. Returned only in :meth:`telegram.Bot.get_chat`.
.. versionadded:: 21.1
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
Attributes:
id (:obj:`int`): Unique identifier for this chat. This number may be greater than 32 bits
and some programming languages may have difficulty/silent defects in interpreting it.
@@ -271,62 +453,134 @@ class Chat(TelegramObject):
last_name (:obj:`str`): Optional. Last name of the other party in a private chat.
photo (:class:`telegram.ChatPhoto`): Optional. Chat photo.
Returned only in :meth:`telegram.Bot.get_chat`.
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
bio (:obj:`str`): Optional. Bio of the other party in a private chat. Returned only in
:meth:`telegram.Bot.get_chat`.
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
has_private_forwards (:obj:`bool`): Optional. :obj:`True`, if privacy settings of the other
party in the private chat allows to use ``tg://user?id=<user_id>`` links only in chats
with the user. Returned only in :meth:`telegram.Bot.get_chat`.
.. versionadded:: 13.9
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
description (:obj:`str`): Optional. Description, for groups, supergroups and channel chats.
Returned only in :meth:`telegram.Bot.get_chat`.
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
invite_link (:obj:`str`): Optional. Primary invite link, for groups, supergroups and
channel. Returned only in :meth:`telegram.Bot.get_chat`.
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
pinned_message (:class:`telegram.Message`): Optional. The most recent pinned message
(by sending date). Returned only in :meth:`telegram.Bot.get_chat`.
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
permissions (:class:`telegram.ChatPermissions`): Optional. Default chat member permissions,
for groups and supergroups. Returned only in :meth:`telegram.Bot.get_chat`.
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
slow_mode_delay (:obj:`int`): Optional. For supergroups, the minimum allowed delay between
consecutive messages sent by each unprivileged user. Returned only in
:meth:`telegram.Bot.get_chat`.
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
message_auto_delete_time (:obj:`int`): Optional. The time after which all messages sent to
the chat will be automatically deleted; in seconds. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 13.4
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
has_protected_content (:obj:`bool`): Optional. :obj:`True`, if messages from the chat can't
be forwarded to other chats. Returned only in :meth:`telegram.Bot.get_chat`.
.. versionadded:: 13.9
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
has_visible_history (:obj:`bool`): Optional. :obj:`True`, if new chat members will have
access to old messages; available only to chat administrators. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.8
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
sticker_set_name (:obj:`str`): Optional. For supergroups, name of Group sticker set.
Returned only in :meth:`telegram.Bot.get_chat`.
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
can_set_sticker_set (:obj:`bool`): Optional. :obj:`True`, if the bot can change group the
sticker set. Returned only in :meth:`telegram.Bot.get_chat`.
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
linked_chat_id (:obj:`int`): Optional. Unique identifier for the linked chat, i.e. the
discussion group identifier for a channel and vice versa; for supergroups and channel
chats. Returned only in :meth:`telegram.Bot.get_chat`.
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
location (:class:`telegram.ChatLocation`): Optional. For supergroups, the location to which
the supergroup is connected. Returned only in :meth:`telegram.Bot.get_chat`.
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
join_to_send_messages (:obj:`bool`): Optional. :obj:`True`, if users need to join
the supergroup before they can send messages. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.0
join_by_request (:obj:`bool`): Optional. :obj:`True`, if all users directly
joining the supergroup need to be approved by supergroup administrators. Returned only
in :meth:`telegram.Bot.get_chat`.
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
join_by_request (:obj:`bool`): Optional. :obj:`True`, if all users directly joining the
supergroup without using an invite link need to be approved by supergroup
administrators. Returned only in :meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.0
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
has_restricted_voice_and_video_messages (:obj:`bool`): Optional. :obj:`True`, if the
privacy settings of the other party restrict sending voice and video note messages
in the private chat. Returned only in :meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.0
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
is_forum (:obj:`bool`): Optional. :obj:`True`, if the supergroup chat is a forum
(has topics_ enabled).
@@ -339,27 +593,47 @@ class Chat(TelegramObject):
obtained via :meth:`~telegram.Bot.get_chat`.
.. versionadded:: 20.0
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
business_intro (:class:`telegram.BusinessIntro`): Optional. For private chats with
business accounts, the intro of the business. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 21.1
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
business_location (:class:`telegram.BusinessLocation`): Optional. For private chats with
business accounts, the location of the business. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 21.1
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
business_opening_hours (:class:`telegram.BusinessOpeningHours`): Optional. For private
chats with business accounts, the opening hours of the business. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 21.1
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
available_reactions (Tuple[:class:`telegram.ReactionType`]): Optional. List of available
reactions allowed in the chat. If omitted, then all of
:const:`telegram.constants.ReactionEmoji` are allowed. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.8
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
accent_color_id (:obj:`int`): Optional. Identifier of the
:class:`accent color <telegram.constants.AccentColor>` for the chat name and
backgrounds of the chat photo, reply header, and link preview. See `accent colors`_
@@ -367,62 +641,110 @@ class Chat(TelegramObject):
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.8
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
background_custom_emoji_id (:obj:`str`): Optional. Custom emoji identifier of emoji chosen
by the chat for the reply header and link preview background. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.8
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
profile_accent_color_id (:obj:`int`): Optional. Identifier of the
:class:`accent color <telegram.constants.ProfileAccentColor>` for the chat's profile
background. See profile `accent colors`_ for more details. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.8
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
profile_background_custom_emoji_id (:obj:`str`): Optional. Custom emoji identifier of
the emoji chosen by the chat for its profile background. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.8
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
emoji_status_custom_emoji_id (:obj:`str`): Optional. Custom emoji identifier of emoji
status of the chat or the other party in a private chat. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.0
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
emoji_status_expiration_date (:class:`datetime.datetime`): Optional. Expiration date of
emoji status of the chat or the other party in a private chat, in seconds. Returned
only in :meth:`telegram.Bot.get_chat`.
|datetime_localization|
.. versionadded:: 20.5
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
has_aggressive_anti_spam_enabled (:obj:`bool`): Optional. :obj:`True`, if aggressive
anti-spam checks are enabled in the supergroup. The field is only available to chat
administrators. Returned only in :meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.0
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
has_hidden_members (:obj:`bool`): Optional. :obj:`True`, if non-administrators can only
get the list of bots and administrators in the chat. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.0
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
unrestrict_boost_count (:obj:`int`): Optional. For supergroups, the minimum number of
boosts that a non-administrator user needs to add in order to ignore slow mode and chat
permissions. Returned only in :meth:`telegram.Bot.get_chat`.
.. versionadded:: 21.0
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
custom_emoji_sticker_set_name (:obj:`str`): Optional. For supergroups, the name of the
group's custom emoji sticker set. Custom emoji from this set can be used by all users
and bots in the group. Returned only in :meth:`telegram.Bot.get_chat`.
.. versionadded:: 21.0
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
birthdate (:obj:`telegram.Birthdate`): Optional. For private chats,
the date of birth of the user. Returned only in :meth:`telegram.Bot.get_chat`.
.. versionadded:: 21.1
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
personal_chat (:obj:`telegram.Chat`): Optional. For private chats, the personal channel of
the user. Returned only in :meth:`telegram.Bot.get_chat`.
.. versionadded:: 21.1
.. deprecated:: 21.2
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
.. _topics: https://telegram.org/blog/topics-in-groups-collectible-usernames#topics-in-groups
.. _accent colors: https://core.telegram.org/bots/api#accent-colors
"""
@@ -471,7 +793,6 @@ class Chat(TelegramObject):
"unrestrict_boost_count",
"username",
)
SENDER: Final[str] = constants.ChatType.SENDER
""":const:`telegram.constants.ChatType.SENDER`
@@ -518,7 +839,7 @@ class Chat(TelegramObject):
has_aggressive_anti_spam_enabled: Optional[bool] = None,
has_hidden_members: Optional[bool] = None,
available_reactions: Optional[Sequence[ReactionType]] = None,
accent_color_id: Optional[int] = None,
accent_color_id: Optional[int] = None, # required in API 7.3 - Optional for back compat
background_custom_emoji_id: Optional[str] = None,
profile_accent_color_id: Optional[int] = None,
profile_background_custom_emoji_id: Optional[str] = None,
@@ -585,10 +906,34 @@ class Chat(TelegramObject):
self.business_location: Optional["BusinessLocation"] = business_location
self.business_opening_hours: Optional["BusinessOpeningHours"] = business_opening_hours
if self.__class__ is Chat:
for arg in _deprecated_attrs:
if (val := object.__getattribute__(self, arg)) is not None and val != ():
warn(
PTBDeprecationWarning(
"21.2",
f"The argument `{arg}` is deprecated and will only be available via "
"`ChatFullInfo` in the future.",
),
stacklevel=2,
)
self._id_attrs = (self.id,)
self._freeze()
def __getattribute__(self, name: str) -> Any:
if name in _deprecated_attrs and self.__class__ is Chat:
warn(
PTBDeprecationWarning(
"21.2",
f"The attribute `{name}` is deprecated and will only be accessible via "
"`ChatFullInfo` in the future.",
),
stacklevel=2,
)
return super().__getattribute__(name)
@property
def effective_name(self) -> Optional[str]:
"""
@@ -658,7 +1003,7 @@ class Chat(TelegramObject):
data["location"] = ChatLocation.de_json(data.get("location"), bot)
data["available_reactions"] = ReactionType.de_list(data.get("available_reactions"), bot)
data["birthdate"] = Birthdate.de_json(data.get("birthdate"), bot)
data["personal_chat"] = cls.de_json(data.get("personal_chat"), bot)
data["personal_chat"] = Chat.de_json(data.get("personal_chat"), bot)
data["business_intro"] = BusinessIntro.de_json(data.get("business_intro"), bot)
data["business_location"] = BusinessLocation.de_json(data.get("business_location"), bot)
data["business_opening_hours"] = BusinessOpeningHours.de_json(
@@ -2545,7 +2890,7 @@ class Chat(TelegramObject):
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,
@@ -2562,6 +2907,8 @@ class Chat(TelegramObject):
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,
@@ -2608,6 +2955,8 @@ class Chat(TelegramObject):
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(
+6 -4
View File
@@ -80,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
@@ -128,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
+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()
+6 -4
View File
@@ -235,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
@@ -294,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
+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,
+4 -38
View File
@@ -27,8 +27,6 @@ from telegram._telegramobject import TelegramObject
from telegram._utils import enum
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.types import JSONDict
from telegram._utils.warnings import warn
from telegram.warnings import PTBDeprecationWarning
if TYPE_CHECKING:
from telegram import Bot
@@ -237,20 +235,12 @@ class StickerSet(TelegramObject):
.. 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.
.. deprecated:: 21.1
Bot API 7.2 deprecated this field. This parameter will be removed in a future
version of the library.
is_video (:obj:`bool`): :obj:`True`, if the sticker set contains video stickers.
.. versionadded:: 13.11
.. deprecated:: 21.1
Bot API 7.2 deprecated this field. This parameter will be removed in a future
version of the library.
stickers (Sequence[:class:`telegram.Sticker`]): List of all set stickers.
.. versionchanged:: 20.0
@@ -269,17 +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.
.. deprecated:: 21.1
Bot API 7.2 deprecated this field. This parameter will be removed in a future
version of the library.
is_video (:obj:`bool`): :obj:`True`, if the sticker set contains video stickers.
.. versionadded:: 13.11
.. deprecated:: 21.1
Bot API 7.2 deprecated this field. This parameter will be removed in a future
version of the library.
stickers (Tuple[:class:`telegram.Sticker`]): List of all set stickers.
.. versionchanged:: 20.0
@@ -297,8 +276,6 @@ class StickerSet(TelegramObject):
"""
__slots__ = (
"is_animated",
"is_video",
"name",
"sticker_type",
"stickers",
@@ -312,8 +289,6 @@ class StickerSet(TelegramObject):
title: str,
stickers: Sequence[Sticker],
sticker_type: str,
is_animated: Optional[bool] = None,
is_video: Optional[bool] = None,
thumbnail: Optional[PhotoSize] = None,
*,
api_kwargs: Optional[JSONDict] = None,
@@ -325,15 +300,6 @@ class StickerSet(TelegramObject):
self.sticker_type: str = sticker_type
# Optional
self.thumbnail: Optional[PhotoSize] = thumbnail
if is_animated is not None or is_video is not None:
warn(
"The parameters `is_animated` and `is_video` are deprecated and will be removed "
"in a future version.",
PTBDeprecationWarning,
stacklevel=2,
)
self.is_animated: Optional[bool] = is_animated
self.is_video: Optional[bool] = is_video
self._id_attrs = (self.name,)
self._freeze()
@@ -350,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
+34 -25
View File
@@ -25,6 +25,7 @@ from html import escape
from typing import TYPE_CHECKING, Dict, List, Optional, Sequence, Tuple, TypedDict, Union
from telegram._chat import Chat
from telegram._chatbackground import ChatBackground
from telegram._chatboost import ChatBoostAdded
from telegram._dice import Dice
from telegram._files.animation import Animation
@@ -64,6 +65,7 @@ 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.defaultvalue import DEFAULT_NONE, DefaultValue
from telegram._utils.entities import parse_message_entities, parse_message_entity
from telegram._utils.types import (
CorrectOptionID,
FileInput,
@@ -99,6 +101,7 @@ if TYPE_CHECKING:
InputMediaDocument,
InputMediaPhoto,
InputMediaVideo,
InputPollOption,
LabeledPrice,
MessageId,
MessageOrigin,
@@ -198,7 +201,7 @@ class MaybeInaccessibleMessage(TelegramObject):
data["date"] = from_timestamp(data["date"], tzinfo=loc_tzinfo)
data["chat"] = Chat.de_json(data.get("chat"), bot)
return super()._de_json(data=data, bot=bot)
return super()._de_json(data=data, bot=bot, api_kwargs=api_kwargs)
class InaccessibleMessage(MaybeInaccessibleMessage):
@@ -553,6 +556,11 @@ class Message(MaybeInaccessibleMessage):
.. versionadded:: 21.1
chat_background_set (:obj:`telegram.ChatBackground`, optional): Service message: chat
background set.
.. versionadded:: 21.2
Attributes:
message_id (:obj:`int`): Unique message identifier inside this chat.
from_user (:class:`telegram.User`): Optional. Sender of the message; empty for messages
@@ -853,6 +861,11 @@ class Message(MaybeInaccessibleMessage):
.. versionadded:: 21.1
chat_background_set (:obj:`telegram.ChatBackground`): Optional. Service message: chat
background set
.. versionadded:: 21.2
.. |custom_emoji_no_md1_support| replace:: Since custom emoji entities are not supported by
:attr:`~telegram.constants.ParseMode.MARKDOWN`, this method now raises a
:exc:`ValueError` when encountering a custom emoji.
@@ -876,6 +889,7 @@ class Message(MaybeInaccessibleMessage):
"caption",
"caption_entities",
"channel_chat_created",
"chat_background_set",
"chat_shared",
"connected_website",
"contact",
@@ -1029,6 +1043,7 @@ class Message(MaybeInaccessibleMessage):
business_connection_id: Optional[str] = None,
sender_business_bot: Optional[User] = None,
is_from_offline: Optional[bool] = None,
chat_background_set: Optional[ChatBackground] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@@ -1127,6 +1142,7 @@ class Message(MaybeInaccessibleMessage):
self.business_connection_id: Optional[str] = business_connection_id
self.sender_business_bot: Optional[User] = sender_business_bot
self.is_from_offline: Optional[bool] = is_from_offline
self.chat_background_set: Optional[ChatBackground] = chat_background_set
self._effective_attachment = DEFAULT_NONE
@@ -1241,6 +1257,7 @@ class Message(MaybeInaccessibleMessage):
)
data["users_shared"] = UsersShared.de_json(data.get("users_shared"), bot)
data["chat_shared"] = ChatShared.de_json(data.get("chat_shared"), bot)
data["chat_background_set"] = ChatBackground.de_json(data.get("chat_background_set"), bot)
# Unfortunately, this needs to be here due to cyclic imports
from telegram._giveaway import ( # pylint: disable=import-outside-toplevel
@@ -1562,9 +1579,11 @@ class Message(MaybeInaccessibleMessage):
if quote is not None:
warn(
"The `quote` parameter is deprecated in favor of the `do_quote` parameter. Please "
"update your code to use `do_quote` instead.",
PTBDeprecationWarning,
PTBDeprecationWarning(
"20.8",
"The `quote` parameter is deprecated in favor of the `do_quote` parameter. "
"Please update your code to use `do_quote` instead.",
),
stacklevel=2,
)
@@ -2890,7 +2909,7 @@ class Message(MaybeInaccessibleMessage):
async def reply_poll(
self,
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,
@@ -2906,6 +2925,8 @@ class Message(MaybeInaccessibleMessage):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: ODVInput[int] = DEFAULT_NONE,
reply_parameters: Optional["ReplyParameters"] = 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,
@@ -2976,6 +2997,8 @@ class Message(MaybeInaccessibleMessage):
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=self.business_connection_id,
question_parse_mode=question_parse_mode,
question_entities=question_entities,
)
async def reply_dice(
@@ -3653,6 +3676,7 @@ class Message(MaybeInaccessibleMessage):
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,
@@ -3694,6 +3718,7 @@ class Message(MaybeInaccessibleMessage):
horizontal_accuracy=horizontal_accuracy,
heading=heading,
proximity_alert_radius=proximity_alert_radius,
live_period=live_period,
inline_message_id=None,
)
@@ -4184,9 +4209,7 @@ class Message(MaybeInaccessibleMessage):
if not self.text:
raise RuntimeError("This Message has no 'text'.")
entity_text = self.text.encode("utf-16-le")
entity_text = entity_text[entity.offset * 2 : (entity.offset + entity.length) * 2]
return entity_text.decode("utf-16-le")
return parse_message_entity(self.text, entity)
def parse_caption_entity(self, entity: MessageEntity) -> str:
"""Returns the text from a given :class:`telegram.MessageEntity`.
@@ -4210,9 +4233,7 @@ class Message(MaybeInaccessibleMessage):
if not self.caption:
raise RuntimeError("This Message has no 'caption'.")
entity_text = self.caption.encode("utf-16-le")
entity_text = entity_text[entity.offset * 2 : (entity.offset + entity.length) * 2]
return entity_text.decode("utf-16-le")
return parse_message_entity(self.caption, entity)
def parse_entities(self, types: Optional[List[str]] = None) -> Dict[MessageEntity, str]:
"""
@@ -4237,12 +4258,7 @@ class Message(MaybeInaccessibleMessage):
the text that belongs to them, calculated based on UTF-16 codepoints.
"""
if types is None:
types = MessageEntity.ALL_TYPES
return {
entity: self.parse_entity(entity) for entity in self.entities if entity.type in types
}
return parse_message_entities(self.text, self.entities, types=types)
def parse_caption_entities(
self, types: Optional[List[str]] = None
@@ -4269,14 +4285,7 @@ class Message(MaybeInaccessibleMessage):
the text that belongs to them, calculated based on UTF-16 codepoints.
"""
if types is None:
types = MessageEntity.ALL_TYPES
return {
entity: self.parse_caption_entity(entity)
for entity in self.caption_entities
if entity.type in types
}
return parse_message_entities(self.caption, self.caption_entities, types=types)
@classmethod
def _parse_html(
@@ -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
+5 -3
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
+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
+8 -60
View File
@@ -23,12 +23,6 @@ from telegram._files.photosize import PhotoSize
from telegram._telegramobject import TelegramObject
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.types import JSONDict
from telegram._utils.warnings import warn
from telegram._utils.warnings_transition import (
build_deprecation_warning_message,
warn_about_deprecated_attr_in_property,
)
from telegram.warnings import PTBDeprecationWarning
if TYPE_CHECKING:
from telegram._bot import Bot
@@ -44,11 +38,14 @@ class UsersShared(TelegramObject):
.. 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
:attr:`user_ids`.
``user_ids``.
.. versionremoved:: 21.2
Removed the deprecated argument and attribute ``user_ids``.
Args:
request_id (:obj:`int`): Identifier of the request.
@@ -57,18 +54,8 @@ class UsersShared(TelegramObject):
.. versionadded:: 21.1
.. deprecated:: 21.1
In future versions, this argument will become keyword only.
user_ids (Sequence[:obj:`int`], optional): 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.
.. deprecated:: 21.1
Bot API 7.2 introduced by :paramref:`users`, replacing this argument. Hence, this
argument is now optional and will be removed in future versions.
.. versionchanged:: 21.2
This argument is now required.
Attributes:
request_id (:obj:`int`): Identifier of the request.
@@ -83,31 +70,14 @@ class UsersShared(TelegramObject):
def __init__(
self,
request_id: int,
user_ids: Optional[Sequence[int]] = None,
users: Optional[Sequence["SharedUser"]] = None,
users: Sequence["SharedUser"],
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.request_id: int = request_id
if users is None:
raise TypeError("`users` is a required argument since Bot API 7.2")
self.users: Tuple[SharedUser, ...] = parse_sequence_arg(users)
if user_ids is not None:
warn(
build_deprecation_warning_message(
deprecated_name="user_ids",
new_name="users",
object_type="parameter",
bot_api_version="7.2",
),
PTBDeprecationWarning,
stacklevel=2,
)
self._id_attrs = (self.request_id, self.users)
self._freeze()
@@ -130,28 +100,6 @@ class UsersShared(TelegramObject):
return super()._de_json(data=data, bot=bot, api_kwargs=api_kwargs)
@property
def user_ids(self) -> Tuple[int, ...]:
"""
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.
.. deprecated:: 21.1
As Bot API 7.2 replaces this attribute with :attr:`users`, this attribute will be
removed in future versions.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="user_ids",
new_attr_name="users",
bot_api_version="7.2",
stacklevel=2,
)
return tuple(user.user_id for user in self.users)
class ChatShared(TelegramObject):
"""
+4 -4
View File
@@ -141,8 +141,8 @@ class Update(TelegramObject):
.. versionadded:: 21.1
business_message (:class:`telegram.Message`, optional): New non-service message
from a connected business account.
business_message (:class:`telegram.Message`, optional): New message from a connected
business account.
.. versionadded:: 21.1
@@ -249,8 +249,8 @@ class Update(TelegramObject):
.. versionadded:: 21.1
business_message (:class:`telegram.Message`): Optional. New non-service message
from a connected business account.
business_message (:class:`telegram.Message`): Optional. New message from a connected
business account.
.. versionadded:: 21.1
+6 -1
View File
@@ -40,6 +40,7 @@ if TYPE_CHECKING:
InputMediaDocument,
InputMediaPhoto,
InputMediaVideo,
InputPollOption,
LabeledPrice,
LinkPreviewOptions,
Location,
@@ -1482,7 +1483,7 @@ class User(TelegramObject):
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,
@@ -1499,6 +1500,8 @@ class User(TelegramObject):
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,
@@ -1548,6 +1551,8 @@ class User(TelegramObject):
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=1, micro=1, releaselevel="final", serial=0
major=21, minor=2, micro=0, releaselevel="final", serial=0
)
__version__: Final[str] = str(__version_info__)
+102 -2
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=2)
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:
@@ -1726,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"
@@ -2878,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."""
+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]]:
+143 -83
View File
@@ -365,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.
@@ -516,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(
@@ -670,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:
@@ -703,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,
@@ -733,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:
@@ -826,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,
)
@@ -874,9 +907,7 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
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
@@ -1038,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))
@@ -1151,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
@@ -1185,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.
@@ -1252,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:
@@ -1809,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."
)
+32 -8
View File
@@ -50,8 +50,8 @@ from telegram import (
BotShortDescription,
BusinessConnection,
CallbackQuery,
Chat,
ChatAdministratorRights,
ChatFullInfo,
ChatInviteLink,
ChatMember,
ChatPermissions,
@@ -64,6 +64,7 @@ from telegram import (
InlineKeyboardMarkup,
InlineQueryResultsButton,
InputMedia,
InputPollOption,
LinkPreviewOptions,
Location,
MaskPosition,
@@ -113,7 +114,7 @@ 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)
@@ -262,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.
@@ -436,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):
@@ -487,6 +492,21 @@ class ExtBot(Bot, Generic[RLARGS]):
data[key] = new_value
# 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
@@ -554,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
@@ -853,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,
@@ -1185,7 +1205,6 @@ class ExtBot(Bot, Generic[RLARGS]):
name: str,
title: str,
stickers: Sequence["InputSticker"],
sticker_format: Optional[str] = None,
sticker_type: Optional[str] = None,
needs_repainting: Optional[bool] = None,
*,
@@ -1201,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,
@@ -1522,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,
@@ -1541,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,
@@ -2917,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,
@@ -2934,6 +2954,8 @@ class ExtBot(Bot, Generic[RLARGS]):
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,
@@ -2971,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(
@@ -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
@@ -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
+2 -3
View File
@@ -152,7 +152,6 @@ class CommandHandler(BaseHandler[Update, CCT]):
Returns:
:obj:`bool`: Whether the args are valid for this handler.
"""
# pylint: disable=too-many-boolean-expressions
return bool(
(self.has_args is None)
or (self.has_args is True and args)
@@ -205,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 -1
View File
@@ -31,6 +31,7 @@ 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.ext._extbot import ExtBot
@@ -44,6 +45,7 @@ if TYPE_CHECKING:
_ALL_DAYS = tuple(range(7))
_LOGGER = get_logger(__name__, class_name="JobQueue")
class JobQueue(Generic[CCT]):
@@ -953,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:
+17 -7
View File
@@ -289,7 +289,7 @@ class BaseFilter:
:attr:`telegram.Update.edited_business_message`, or :obj:`False` otherwise.
"""
return bool( # Only message updates should be handled.
update.channel_post # pylint: disable=too-many-boolean-expressions
update.channel_post
or update.message
or update.edited_channel_post
or update.edited_message
@@ -401,7 +401,7 @@ class _InvertedFilter(UpdateFilter):
return f"<inverted {self.inv_filter}>"
@name.setter
def name(self, name: str) -> NoReturn:
def name(self, _: str) -> NoReturn:
raise RuntimeError("Cannot set name for combined filters.")
@@ -492,7 +492,7 @@ class _MergedFilter(UpdateFilter):
)
@name.setter
def name(self, name: str) -> NoReturn:
def name(self, _: str) -> NoReturn:
raise RuntimeError("Cannot set name for combined filters.")
@@ -522,14 +522,14 @@ class _XORFilter(UpdateFilter):
return f"<{self.base_filter} xor {self.xor_filter}>"
@name.setter
def name(self, name: str) -> NoReturn:
def name(self, _: str) -> NoReturn:
raise RuntimeError("Cannot set name for combined filters.")
class _All(MessageFilter):
__slots__ = ()
def filter(self, message: Message) -> bool:
def filter(self, message: Message) -> bool: # noqa: ARG002
return True
@@ -809,7 +809,7 @@ class _ChatUserBaseFilter(MessageFilter, ABC):
)
@name.setter
def name(self, name: str) -> NoReturn:
def name(self, _: str) -> NoReturn:
raise RuntimeError(f"Cannot set name for filters.{self.__class__.__name__}")
@@ -1909,7 +1909,8 @@ class StatusUpdate:
def filter(self, update: Update) -> bool:
return bool(
# keep this alphabetically sorted for easier maintenance
StatusUpdate.CHAT_CREATED.check_update(update)
StatusUpdate.CHAT_BACKGROUND_SET.check_update(update)
or StatusUpdate.CHAT_CREATED.check_update(update)
or StatusUpdate.CHAT_SHARED.check_update(update)
or StatusUpdate.CONNECTED_WEBSITE.check_update(update)
or StatusUpdate.DELETE_CHAT_PHOTO.check_update(update)
@@ -1942,6 +1943,15 @@ class StatusUpdate:
ALL = _All(name="filters.StatusUpdate.ALL")
"""Messages that contain any of the below."""
class _ChatBackgroundSet(MessageFilter):
__slots__ = ()
def filter(self, message: Message) -> bool:
return bool(message.chat_background_set)
CHAT_BACKGROUND_SET = _ChatBackgroundSet(name="filters.StatusUpdate.CHAT_BACKGROUND_SET")
"""Messages that contain :attr:`telegram.Message.chat_background_set`."""
class _ChatCreated(MessageFilter):
__slots__ = ()
+6 -4
View File
@@ -318,10 +318,12 @@ class BaseRequest(
and isinstance(write_timeout, DefaultValue)
):
warn(
f"The `write_timeout` parameter passed to {self.__class__.__name__}.do_request "
"will default to `BaseRequest.DEFAULT_NONE` instead of 20 in future versions "
"for *all* methods of the `Bot` class, including methods sending media.",
PTBDeprecationWarning,
PTBDeprecationWarning(
"20.7",
f"The `write_timeout` parameter passed to {self.__class__.__name__}.do_request"
" will default to `BaseRequest.DEFAULT_NONE` instead of 20 in future versions "
"for *all* methods of the `Bot` class, including methods sending media.",
),
stacklevel=3,
)
write_timeout = 20
+3 -3
View File
@@ -146,9 +146,9 @@ class HTTPXRequest(BaseRequest):
if proxy_url is not None:
proxy = proxy_url
warn(
"The parameter `proxy_url` is deprecated since version 20.7. Use `proxy` "
"instead.",
PTBDeprecationWarning,
PTBDeprecationWarning(
"20.7", "The parameter `proxy_url` is deprecated. Use `proxy` instead."
),
stacklevel=2,
)
+29 -1
View File
@@ -54,6 +54,34 @@ class PTBDeprecationWarning(PTBUserWarning, DeprecationWarning):
.. versionchanged:: 20.0
Renamed TelegramDeprecationWarning to PTBDeprecationWarning.
Args:
version (:obj:`str`): The version in which the feature was deprecated.
.. versionadded:: 21.2
message (:obj:`str`): The message to display.
.. versionadded:: 21.2
Attributes:
version (:obj:`str`): The version in which the feature was deprecated.
.. versionadded:: 21.2
message (:obj:`str`): The message to display.
.. versionadded:: 21.2
"""
__slots__ = ()
__slots__ = ("message", "version")
def __init__(self, version: str, message: str) -> None:
self.version: str = version
self.message: str = message
def __str__(self) -> str:
"""Returns a string representation of the warning, using :attr:`message` and
:attr:`version`.
.. versionadded:: 21.2
"""
return f"Deprecated since version {self.version}: {self.message}"
+1 -1
View File
@@ -351,7 +351,7 @@ class TestAnimationWithRequest(TestAnimationBase):
)
assert message.reply_to_message is None
else:
with pytest.raises(BadRequest, match="Message to reply not found"):
with pytest.raises(BadRequest, match="Message to be replied not found"):
await default_bot.send_animation(
chat_id, animation, reply_to_message_id=reply_to_message.message_id
)
+1 -1
View File
@@ -378,7 +378,7 @@ class TestAudioWithRequest(TestAudioBase):
)
assert message.reply_to_message is None
else:
with pytest.raises(BadRequest, match="Message to reply not found"):
with pytest.raises(BadRequest, match="Message to be replied not found"):
await default_bot.send_audio(
chat_id, audio, reply_to_message_id=reply_to_message.message_id
)
+1 -1
View File
@@ -185,7 +185,7 @@ class TestContactWithRequest(TestContactBase):
)
assert message.reply_to_message is None
else:
with pytest.raises(BadRequest, match="Message to reply not found"):
with pytest.raises(BadRequest, match="Message to be replied not found"):
await default_bot.send_contact(
chat_id, contact=contact, reply_to_message_id=reply_to_message.message_id
)
+1 -1
View File
@@ -364,7 +364,7 @@ class TestDocumentWithRequest(TestDocumentBase):
)
assert message.reply_to_message is None
else:
with pytest.raises(BadRequest, match="Message to reply not found"):
with pytest.raises(BadRequest, match="Message to be replied not found"):
await default_bot.send_document(
chat_id, document, reply_to_message_id=reply_to_message.message_id
)
+1 -1
View File
@@ -873,7 +873,7 @@ class TestSendMediaGroupWithRequest:
)
assert [m.reply_to_message is None for m in messages]
else:
with pytest.raises(BadRequest, match="Message to reply not found"):
with pytest.raises(BadRequest, match="Message to be replied not found"):
await default_bot.send_media_group(
chat_id, media_group, reply_to_message_id=reply_to_message.message_id
)
+6 -2
View File
@@ -124,7 +124,8 @@ class TestLocationWithoutRequest(TestLocationBase):
ha = data["horizontal_accuracy"] == "50"
heading = data["heading"] == "90"
prox_alert = data["proximity_alert_radius"] == "1000"
return lat and lon and id_ and ha and heading and prox_alert
live = data["live_period"] == "900"
return lat and lon and id_ and ha and heading and prox_alert and live
monkeypatch.setattr(bot.request, "post", make_assertion)
assert await bot.edit_message_live_location(
@@ -133,6 +134,7 @@ class TestLocationWithoutRequest(TestLocationBase):
horizontal_accuracy=50,
heading=90,
proximity_alert_radius=1000,
live_period=900,
)
# TODO: Needs improvement with in inline sent live location.
@@ -218,7 +220,7 @@ class TestLocationWithRequest:
)
assert message.reply_to_message is None
else:
with pytest.raises(BadRequest, match="Message to reply not found"):
with pytest.raises(BadRequest, match="Message to be replied not found"):
await default_bot.send_location(
chat_id, location=location, reply_to_message_id=reply_to_message.message_id
)
@@ -262,6 +264,7 @@ class TestLocationWithRequest:
horizontal_accuracy=30,
heading=10,
proximity_alert_radius=500,
live_period=200,
)
assert pytest.approx(message2.location.latitude, rel=1e-5) == 52.223098
@@ -269,6 +272,7 @@ class TestLocationWithRequest:
assert message2.location.horizontal_accuracy == 30
assert message2.location.heading == 10
assert message2.location.proximity_alert_radius == 500
assert message2.location.live_period == 200
await bot.stop_message_live_location(message.chat_id, message.message_id)
with pytest.raises(BadRequest, match="Message can't be edited"):
+1 -1
View File
@@ -384,7 +384,7 @@ class TestPhotoWithRequest(TestPhotoBase):
)
assert message.reply_to_message is None
else:
with pytest.raises(BadRequest, match="Message to reply not found"):
with pytest.raises(BadRequest, match="Message to be replied not found"):
await default_bot.send_photo(
chat_id, photo_file, reply_to_message_id=reply_to_message.message_id
)
+1 -25
View File
@@ -39,7 +39,6 @@ from telegram import (
from telegram.constants import ParseMode, StickerFormat, StickerType
from telegram.error import BadRequest, TelegramError
from telegram.request import RequestData
from telegram.warnings import PTBDeprecationWarning
from tests.auxil.bot_method_checks import (
check_defaults_handling,
check_shortcut_call,
@@ -457,7 +456,7 @@ class TestStickerWithRequest(TestStickerBase):
)
assert message.reply_to_message is None
else:
with pytest.raises(BadRequest, match="Message to reply not found"):
with pytest.raises(BadRequest, match="Message to be replied not found"):
await default_bot.send_sticker(
chat_id, sticker, reply_to_message_id=reply_to_message.message_id
)
@@ -714,7 +713,6 @@ class TestStickerSetWithoutRequest(TestStickerSetBase):
assert data["name"] == "name"
assert data["title"] == "title"
assert data["stickers"] == ["wow.png", "wow.tgs", "wow.webp"]
assert data["sticker_format"] == "static"
assert data["needs_repainting"] is True
monkeypatch.setattr(bot, "_post", make_assertion)
@@ -723,7 +721,6 @@ class TestStickerSetWithoutRequest(TestStickerSetBase):
"name",
"title",
stickers=["wow.png", "wow.tgs", "wow.webp"],
sticker_format=StickerFormat.STATIC,
needs_repainting=True,
)
@@ -784,27 +781,6 @@ class TestStickerSetWithoutRequest(TestStickerSetBase):
monkeypatch.setattr(sticker.get_bot(), "get_file", make_assertion)
assert await sticker.get_file()
async def test_create_new_sticker_set_format_arg_depr(
self, bot, chat_id, sticker_file, monkeypatch
):
async def make_assertion(*_, **kwargs):
pass
monkeypatch.setattr(bot, "_post", make_assertion)
with pytest.warns(PTBDeprecationWarning, match="`sticker_format` is deprecated"):
await bot.create_new_sticker_set(
chat_id,
"name",
"title",
stickers=sticker_file,
sticker_format="static",
)
async def test_deprecation_creation_args(self, recwarn):
with pytest.warns(PTBDeprecationWarning, match="The parameters `is_animated` and ") as w:
StickerSet("name", "title", [], "static", is_animated=True)
assert w[0].filename == __file__, "wrong stacklevel!"
@pytest.mark.xdist_group("stickerset")
class TestStickerSetWithRequest:
+1 -1
View File
@@ -200,7 +200,7 @@ class TestVenueWithRequest(TestVenueBase):
)
assert message.reply_to_message is None
else:
with pytest.raises(BadRequest, match="Message to reply not found"):
with pytest.raises(BadRequest, match="Message to be replied not found"):
await default_bot.send_venue(
chat_id, venue=venue, reply_to_message_id=reply_to_message.message_id
)
+1 -1
View File
@@ -386,7 +386,7 @@ class TestVideoWithRequest(TestVideoBase):
)
assert message.reply_to_message is None
else:
with pytest.raises(BadRequest, match="Message to reply not found"):
with pytest.raises(BadRequest, match="Message to be replied not found"):
await default_bot.send_video(
chat_id, video, reply_to_message_id=reply_to_message.message_id
)
+1 -1
View File
@@ -287,7 +287,7 @@ class TestVideoNoteWithRequest(TestVideoNoteBase):
)
assert message.reply_to_message is None
else:
with pytest.raises(BadRequest, match="Message to reply not found"):
with pytest.raises(BadRequest, match="Message to be replied not found"):
await default_bot.send_video_note(
chat_id, video_note, reply_to_message_id=reply_to_message.message_id
)
+1 -1
View File
@@ -335,7 +335,7 @@ class TestVoiceWithRequest(TestVoiceBase):
)
assert message.reply_to_message is None
else:
with pytest.raises(BadRequest, match="Message to reply not found"):
with pytest.raises(BadRequest, match="Message to be replied not found"):
await default_bot.send_voice(
chat_id, voice, reply_to_message_id=reply_to_message.message_id
)
+1 -1
View File
@@ -312,7 +312,7 @@ class TestInvoiceWithRequest(TestInvoiceBase):
)
assert message.reply_to_message is None
else:
with pytest.raises(BadRequest, match="Message to reply not found"):
with pytest.raises(BadRequest, match="Message to be replied not found"):
await default_bot.send_invoice(
chat_id,
self.title,
+2
View File
@@ -314,6 +314,8 @@ def build_kwargs(
elif name == "ok":
kws["ok"] = False
kws["error_message"] = "error"
elif name == "options":
kws[name] = ["option1", "option2"]
else:
kws[name] = True
+279 -62
View File
@@ -19,6 +19,7 @@
"""The integration of persistence into the application is tested in test_basepersistence.
"""
import asyncio
import functools
import inspect
import logging
import os
@@ -2083,75 +2084,174 @@ class TestApplication:
assert set(self.received.keys()) == set(expected.keys())
assert self.received == expected
@pytest.mark.skipif(
platform.system() == "Windows",
reason="Can't send signals without stopping whole process on windows",
)
async def test_cancellation_error_does_not_stop_polling(
self, one_time_bot, monkeypatch, caplog
@pytest.mark.parametrize("exception", [SystemExit, KeyboardInterrupt])
def test_raise_system_exit_keyboard_interrupt_post_init(
self, one_time_bot, monkeypatch, exception
):
"""
Ensures that hitting CTRL+C while polling *without* run_polling doesn't kill
the update_fetcher loop such that a shutdown is still possible.
This test is far from perfect, but it's the closest we can come with sane effort.
"""
async def post_init(application):
raise exception
called_callbacks = set()
async def callback(*args, **kwargs):
called_callbacks.add(kwargs["name"])
for cls, method, entry in [
(Application, "initialize", "app_initialize"),
(Application, "start", "app_start"),
(Application, "stop", "app_stop"),
(Application, "shutdown", "app_shutdown"),
(Updater, "initialize", "updater_initialize"),
(Updater, "shutdown", "updater_shutdown"),
(Updater, "stop", "updater_stop"),
(Updater, "start_polling", "updater_start_polling"),
]:
def after(_, name):
called_callbacks.add(name)
monkeypatch.setattr(
cls,
method,
call_after(getattr(cls, method), functools.partial(after, name=entry)),
)
app = (
ApplicationBuilder()
.bot(one_time_bot)
.post_init(post_init)
.post_stop(functools.partial(callback, name="post_stop"))
.post_shutdown(functools.partial(callback, name="post_shutdown"))
.build()
)
app.run_polling(close_loop=False)
# This checks two things:
# 1. start/stop are *not* called!
# 2. we do have a graceful shutdown
assert called_callbacks == {
"app_initialize",
"updater_initialize",
"app_shutdown",
"post_shutdown",
"updater_shutdown",
}
@pytest.mark.parametrize("exception", [SystemExit("PTBTest"), KeyboardInterrupt("PTBTest")])
@pytest.mark.parametrize("kind", ["handler", "error_handler", "job"])
# @pytest.mark.parametrize("block", [True, False])
# Testing with block=False would be nice but that doesn't work well with pytest for some reason
# in any case, block=False is the simpler behavior since it is roughly similar to what happens
# when you hit CTRL+C in the commandline.
def test_raise_system_exit_keyboard_jobs_handlers(
self, one_time_bot, monkeypatch, exception, kind, caplog
):
async def queue_and_raise(application):
await application.update_queue.put("will_not_be_processed")
raise exception
async def handler_callback(update, context):
if kind == "handler":
await queue_and_raise(context.application)
elif kind == "error_handler":
raise TelegramError("Triggering error callback")
async def error_callback(update, context):
await queue_and_raise(context.application)
async def job_callback(context):
await queue_and_raise(context.application)
async def enqueue_update():
await asyncio.sleep(0.5)
await app.update_queue.put(1)
async def post_init(application):
if kind == "job":
application.job_queue.run_once(when=0.5, callback=job_callback)
else:
app.create_task(enqueue_update())
async def update_logger_callback(update, context):
context.bot_data.setdefault("processed_updates", set()).add(update)
called_callbacks = set()
async def callback(*args, **kwargs):
called_callbacks.add(kwargs["name"])
async def get_updates(*args, **kwargs):
await asyncio.sleep(0)
return [None]
return []
monkeypatch.setattr(one_time_bot, "get_updates", get_updates)
app = ApplicationBuilder().bot(one_time_bot).build()
for cls, method, entry in [
(Application, "initialize", "app_initialize"),
(Application, "start", "app_start"),
(Application, "stop", "app_stop"),
(Application, "shutdown", "app_shutdown"),
(Updater, "initialize", "updater_initialize"),
(Updater, "shutdown", "updater_shutdown"),
(Updater, "stop", "updater_stop"),
(Updater, "start_polling", "updater_start_polling"),
]:
original_get = app.update_queue.get
raise_cancelled_error = threading.Event()
def after(_, name):
called_callbacks.add(name)
async def get(*arg, **kwargs):
await asyncio.sleep(0.05)
if raise_cancelled_error.is_set():
raise_cancelled_error.clear()
raise asyncio.CancelledError("Mocked CancelledError")
return await original_get(*arg, **kwargs)
monkeypatch.setattr(
cls,
method,
call_after(getattr(cls, method), functools.partial(after, name=entry)),
)
monkeypatch.setattr(app.update_queue, "get", get)
def thread_target():
waited = 0
while not app.running:
time.sleep(0.05)
waited += 0.05
if waited > 5:
pytest.fail("App apparently won't start")
time.sleep(0.1)
raise_cancelled_error.set()
async with app:
with caplog.at_level(logging.WARNING):
thread = Thread(target=thread_target)
await app.start()
thread.start()
assert thread.is_alive()
raise_cancelled_error.wait()
# The exit should have been caught and the app should still be running
assert not thread.is_alive()
assert app.running
# Explicit shutdown is required
await app.stop()
thread.join()
assert not thread.is_alive()
assert not app.running
# Make sure that we were warned about the necessity of a manual shutdown
assert len(caplog.records) == 1
record = caplog.records[0]
assert record.name == "telegram.ext.Application"
assert record.getMessage().startswith(
"Fetching updates got a asyncio.CancelledError. Ignoring"
app = (
ApplicationBuilder()
.bot(one_time_bot)
.post_init(post_init)
.post_stop(functools.partial(callback, name="post_stop"))
.post_shutdown(functools.partial(callback, name="post_shutdown"))
.build()
)
monkeypatch.setattr(app.bot, "get_updates", get_updates)
app.add_handler(TypeHandler(object, update_logger_callback), group=-10)
app.add_handler(TypeHandler(object, handler_callback))
app.add_error_handler(error_callback)
with caplog.at_level(logging.DEBUG):
app.run_polling(close_loop=False)
# This checks that we have a clean shutdown even when the user raises SystemExit
# or KeyboardInterrupt in a handler/error handler/job callback
assert called_callbacks == {
"app_initialize",
"app_shutdown",
"app_start",
"app_stop",
"post_shutdown",
"post_stop",
"updater_initialize",
"updater_shutdown",
"updater_start_polling",
"updater_stop",
}
# These next checks make sure that the update queue is properly cleaned even if there are
# still pending updates in the queue
# Unfortunately this is apparently extremely hard to get right with jobs, so we're
# skipping that case for the sake of simplicity
if kind == "job":
return
found = False
for record in caplog.records:
if record.getMessage() != "Dropping pending update: will_not_be_processed":
continue
assert record.name == "telegram.ext.Application"
assert record.levelno == logging.DEBUG
found = True
assert found, "`Dropping pending updates` message not found in logs!"
assert "will_not_be_processed" not in app.bot_data.get("processed_updates", set())
def test_run_without_updater(self, one_time_bot):
app = ApplicationBuilder().bot(one_time_bot).updater(None).build()
@@ -2297,7 +2397,7 @@ class TestApplication:
assert received_signals == [signal.SIGINT, signal.SIGTERM, signal.SIGABRT]
received_signals.clear()
loop.call_later(0.6, abort_app)
loop.call_later(0.8, abort_app)
app.run_webhook(port=49152, webhook_url="example.com", close_loop=False)
if platform.system() == "Windows":
@@ -2311,7 +2411,44 @@ class TestApplication:
assert len(caplog.records) == 1
assert caplog.records[-1].name == "telegram.ext.Application"
assert caplog.records[-1].getMessage().endswith("stop_running() does nothing.")
assert caplog.records[-1].getMessage().endswith("`stop_running()` likely has no effect.")
def test_stop_running_post_init(self, app, monkeypatch, caplog, one_time_bot):
async def post_init(app):
app.stop_running()
called_callbacks = []
async def callback(*args, **kwargs):
called_callbacks.append(kwargs["name"])
monkeypatch.setattr(Application, "start", functools.partial(callback, name="start"))
monkeypatch.setattr(
Updater, "start_polling", functools.partial(callback, name="start_polling")
)
app = (
ApplicationBuilder()
.bot(one_time_bot)
.post_init(post_init)
.post_stop(functools.partial(callback, name="post_stop"))
.post_shutdown(functools.partial(callback, name="post_shutdown"))
.build()
)
with caplog.at_level(logging.INFO):
app.run_polling(close_loop=False)
# The important part here is that start(_polling) are *not* called!
# post_stop must not be called either, since we never called stop()
assert called_callbacks == ["post_shutdown"]
assert len(caplog.records) == 1
assert caplog.records[-1].name == "telegram.ext.Application"
assert (
"Application received stop signal via `stop_running`"
in caplog.records[-1].getMessage()
)
@pytest.mark.parametrize("method", ["polling", "webhook"])
def test_stop_running(self, one_time_bot, monkeypatch, method):
@@ -2421,3 +2558,83 @@ class TestApplication:
assert len(assertions) == 5
for key, value in assertions.items():
assert value, f"assertion '{key}' failed!"
async def test_process_update_exception_in_building_context(self, monkeypatch, caplog, app):
# Makes sure that exceptions in building the context don't stop the application
exception = ValueError("TestException")
original_from_update = CallbackContext.from_update
def raise_exception(update, application):
if update == 1:
raise exception
return original_from_update(update, application)
monkeypatch.setattr(CallbackContext, "from_update", raise_exception)
received_updates = set()
async def callback(update, context):
received_updates.add(update)
app.add_handler(TypeHandler(int, callback))
async with app:
with caplog.at_level(logging.CRITICAL):
await app.process_update(1)
assert received_updates == set()
assert len(caplog.records) == 1
record = caplog.records[0]
assert record.name == "telegram.ext.Application"
assert record.getMessage().startswith(
"Error while building CallbackContext for update 1"
)
assert record.levelno == logging.CRITICAL
# Let's also check that no critical log is produced when the exception is not raised
caplog.clear()
with caplog.at_level(logging.CRITICAL):
await app.process_update(2)
assert received_updates == {2}
assert len(caplog.records) == 0
async def test_process_error_exception_in_building_context(self, monkeypatch, caplog, app):
# Makes sure that exceptions in building the context don't stop the application
exception = ValueError("TestException")
original_from_error = CallbackContext.from_error
def raise_exception(update, error, application, *args, **kwargs):
if error == 1:
raise exception
return original_from_error(update, error, application, *args, **kwargs)
monkeypatch.setattr(CallbackContext, "from_error", raise_exception)
received_errors = set()
async def callback(update, context):
received_errors.add(context.error)
app.add_error_handler(callback)
async with app:
with caplog.at_level(logging.CRITICAL):
await app.process_error(update=None, error=1)
assert received_errors == set()
assert len(caplog.records) == 1
record = caplog.records[0]
assert record.name == "telegram.ext.Application"
assert record.getMessage().startswith(
"Error while building CallbackContext for exception 1"
)
assert record.levelno == logging.CRITICAL
# Let's also check that no critical log is produced when the exception is not raised
caplog.clear()
with caplog.at_level(logging.CRITICAL):
await app.process_error(update=None, error=2)
assert received_errors == {2}
assert len(caplog.records) == 0
+5
View File
@@ -1090,6 +1090,11 @@ class TestFilters:
assert filters.StatusUpdate.GIVEAWAY_COMPLETED.check_update(update)
update.message.giveaway_completed = None
update.message.chat_background_set = "test_background"
assert filters.StatusUpdate.ALL.check_update(update)
assert filters.StatusUpdate.CHAT_BACKGROUND_SET.check_update(update)
update.message.chat_background_set = None
def test_filters_forwarded(self, update, message_origin_user):
assert filters.FORWARDED.check_update(update)
update.message.forward_origin = MessageOriginHiddenUser(datetime.datetime.utcnow(), 1)
+41
View File
@@ -646,3 +646,44 @@ class TestJobQueue:
tg_job = Job.from_aps_job(aps_job)
assert tg_job is job
assert tg_job.job is aps_job
async def test_run_job_exception_in_building_context(
self, monkeypatch, job_queue, caplog, app
):
# Makes sure that exceptions in building the context don't stop the application
exception = ValueError("TestException")
original_from_job = CallbackContext.from_job
def raise_exception(job, application):
if job.data == 1:
raise exception
return original_from_job(job, application)
monkeypatch.setattr(CallbackContext, "from_job", raise_exception)
received_jobs = set()
async def job_callback(context):
received_jobs.add(context.job.data)
with caplog.at_level(logging.CRITICAL):
job_queue.run_once(job_callback, 0.1, data=1)
await asyncio.sleep(0.2)
assert received_jobs == set()
assert len(caplog.records) == 1
record = caplog.records[0]
assert record.name == "telegram.ext.JobQueue"
assert record.getMessage().startswith(
"Error while building CallbackContext for job job_callback"
)
assert record.levelno == logging.CRITICAL
# Let's also check that no critical log is produced when the exception is not raised
caplog.clear()
with caplog.at_level(logging.CRITICAL):
job_queue.run_once(job_callback, 0.1, data=2)
await asyncio.sleep(0.2)
assert received_jobs == {2}
assert len(caplog.records) == 0

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