Merge branch 'refs/heads/master' into overhaul-tests

# Conflicts:
#	tests/test_bot.py
This commit is contained in:
Hinrich Mahler 2024-06-22 11:29:33 +02:00
commit 9fe518f023
93 changed files with 2073 additions and 2018 deletions

View file

@ -26,7 +26,7 @@ Setting things up
.. code-block:: bash
$ pip install -r requirements-all.txt
$ pip install -r requirements-dev-all.txt
5. Install pre-commit hooks:
@ -210,13 +210,8 @@ doc strings don't have a separate documentation site they generate, instead, the
User facing documentation
-------------------------
We use `sphinx`_ to generate static HTML docs. To build them, first make sure you're running Python 3.9 or above and have the required dependencies:
.. code-block:: bash
$ pip install -r docs/requirements-docs.txt
then run the following from the PTB root directory:
We use `sphinx`_ to generate static HTML docs. To build them, first make sure you're running Python 3.9 or above and have the required dependencies installed as explained above.
Then, run the following from the PTB root directory:
.. code-block:: bash

View file

@ -28,7 +28,7 @@ jobs:
- name: Install dependencies
run: |
python -W ignore -m pip install --upgrade pip
python -W ignore -m pip install -r requirements-all.txt
python -W ignore -m pip install -r requirements-dev-all.txt
- name: Test autogeneration of admonitions
run: pytest -v --tb=short tests/docs/admonition_inserter.py
- name: Build docs

View file

@ -1,19 +0,0 @@
name: Warning maintainers
on:
pull_request_target:
paths:
- requirements.txt
- requirements-opts.txt
- .pre-commit-config.yaml
permissions:
pull-requests: write
jobs:
job:
runs-on: ubuntu-latest
name: about pre-commit and dependency change
steps:
- name: running the check
uses: Poolitzer/notifier-action@master
with:
notify-message: Hey! Looks like you edited the (optional) requirements or the pre-commit hooks. I'm just a friendly reminder to keep the additional dependencies for the hooks in sync with the requirements :)
repo-token: ${{ secrets.GITHUB_TOKEN }}

View file

@ -1,18 +0,0 @@
name: Warning maintainers
on:
pull_request_target:
paths:
- README.rst
- README_RAW.rst
permissions:
pull-requests: write
jobs:
job:
runs-on: ubuntu-latest
name: about readme change
steps:
- name: running the check
uses: Poolitzer/notifier-action@master
with:
notify-message: Hey! Looks like you edited README.rst or README_RAW.rst. I'm just a friendly reminder to apply relevant changes to both of those files :)
repo-token: ${{ secrets.GITHUB_TOKEN }}

View file

@ -29,9 +29,8 @@ jobs:
- name: Install dependencies
run: |
python -W ignore -m pip install --upgrade pip
python -W ignore -m pip install -r requirements.txt
python -W ignore -m pip install -r requirements-opts.txt
python -W ignore -m pip install -r requirements-dev.txt
python -W ignore -m pip install .[all]
python -W ignore -m pip install -r requirements-unit-tests.txt
- name: Compare to official api
run: |
pytest -v tests/test_official/test_official.py --junit-xml=.test_report_official.xml

View file

@ -3,8 +3,7 @@ on:
pull_request:
paths:
- telegram/**
- requirements.txt
- requirements-opts.txt
- pyproject.toml
push:
branches:
- master
@ -19,12 +18,12 @@ jobs:
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: 3.9
python-version: 3.12
cache: 'pip'
cache-dependency-path: '**/requirements*.txt'
- name: Install Pyright
run: |
python -W ignore -m pip install pyright~=1.1.316
python -W ignore -m pip install pyright~=1.1.367
- name: Get PR Completeness
# Must run before base completeness, as base completeness will checkout the base branch
# And we can't go back to the PR branch after that in case the PR is coming from a fork

View file

@ -4,9 +4,8 @@ on:
paths:
- telegram/**
- tests/**
- requirements.txt
- requirements-opts.txt
- requirements-dev.txt
- pyproject.toml
- requirements-unit-tests.txt
push:
branches:
- master
@ -20,7 +19,7 @@ jobs:
runs-on: ${{matrix.os}}
strategy:
matrix:
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13.0-beta.2']
os: [ubuntu-latest, windows-latest, macos-latest]
fail-fast: False
steps:
@ -35,8 +34,8 @@ jobs:
run: |
python -W ignore -m pip install --upgrade pip
python -W ignore -m pip install -U pytest-cov
python -W ignore -m pip install -r requirements.txt
python -W ignore -m pip install -r requirements-dev.txt
python -W ignore -m pip install .
python -W ignore -m pip install -r requirements-unit-tests.txt
python -W ignore -m pip install pytest-xdist[psutil]
- name: Test with pytest
@ -65,7 +64,7 @@ jobs:
# Test the rest
export TEST_WITH_OPT_DEPS='true'
pip install -r requirements-opts.txt
pip install .[all]
# `-n auto --dist loadfile` uses pytest-xdist to run each test file on a different CPU
# worker. Increasing number of workers has little effect on test duration, but it seems
# to increase flakyness, specially on python 3.7 with --dist=loadgroup.

3
.gitignore vendored
View file

@ -92,3 +92,6 @@ telegram.jpg
# virtual env
venv*
# environment manager:
.mise.toml

View file

@ -1,4 +1,4 @@
# Make sure that the additional_dependencies here match requirements(-opts).txt
# Make sure that the additional_dependencies here match pyproject.toml
ci:
autofix_prs: false

View file

@ -4,6 +4,40 @@
Changelog
=========
Version 21.3
============
*Released 2024-06-07*
This is the technical changelog for version 21.3. More elaborate release notes can be found in the news channel `@pythontelegrambotchannel <https://t.me/pythontelegrambotchannel>`_.
Major Changes
-------------
- Full Support for Bot API 7.4 (:pr:`4286`, :pr:`4276` closes :issue:`4275`, :pr:`4285`, :pr:`4283`, :pr:`4280`, :pr:`4278`, :pr:`4279`)
- Deprecate ``python-telegram-bot-raw`` (:pr:`4270`)
- Remove Functionality Deprecated in Bot API 7.3 (:pr:`4266` closes :issue:`4244`)
New Features
------------
- Add Parameter ``chat_id`` to ``ChatMemberHandler`` (:pr:`4290` by `uniquetrij <https://github.com/uniquetrij>`_ closes :issue:`4287`)
Documentation Improvements
--------------------------
- Documentation Improvements (:pr:`4264` closes :issue:`4240`)
Internal Changes
----------------
- Add ``setuptools`` to ``requirements-dev.txt`` (:pr:`4282`)
- Update Settings for pre-commit.ci (:pr:`4265`)
Dependency Updates
------------------
- Bump ``pytest`` from 8.2.0 to 8.2.1 (:pr:`4272`)
Version 21.2
============

View file

@ -1 +0,0 @@
include LICENSE LICENSE.lesser requirements.txt requirements-opts.txt README_RAW.rst telegram/py.typed

View file

@ -1,6 +1,3 @@
..
Make sure to apply any changes to this file to README_RAW.rst as well!
.. image:: https://raw.githubusercontent.com/python-telegram-bot/logos/master/logo-text/png/ptb-logo-text_768.png
:align: center
:target: https://python-telegram-bot.org
@ -14,7 +11,7 @@
:target: https://pypi.org/project/python-telegram-bot/
:alt: Supported Python versions
.. image:: https://img.shields.io/badge/Bot%20API-7.3-blue?logo=telegram
.. image:: https://img.shields.io/badge/Bot%20API-7.4-blue?logo=telegram
:target: https://core.telegram.org/bots/api-changelog
:alt: Supported Bot API version
@ -79,17 +76,10 @@ In addition to the pure API implementation, this library features a number of hi
make the development of bots easy and straightforward. These classes are contained in the
``telegram.ext`` submodule.
A pure API implementation *without* ``telegram.ext`` is available as the standalone package ``python-telegram-bot-raw``. `See here for details. <https://github.com/python-telegram-bot/python-telegram-bot/blob/master/README_RAW.rst>`_
Note
----
Installing both ``python-telegram-bot`` and ``python-telegram-bot-raw`` in conjunction will result in undesired side-effects, so only install *one* of both.
Telegram API support
====================
All types and methods of the Telegram Bot API **7.3** are supported.
All types and methods of the Telegram Bot API **7.4** are supported.
Installing
==========
@ -108,7 +98,8 @@ You can also install ``python-telegram-bot`` from source, though this is usually
$ git clone https://github.com/python-telegram-bot/python-telegram-bot
$ cd python-telegram-bot
$ python setup.py install
$ pip install build
$ python -m build
Verifying Releases
------------------

View file

@ -1,206 +0,0 @@
..
Make sure to apply any changes to this file to README.rst as well!
.. image:: https://github.com/python-telegram-bot/logos/blob/master/logo-text/png/ptb-raw-logo-text_768.png?raw=true
:align: center
:target: https://python-telegram-bot.org
:alt: python-telegram-bot-raw Logo
.. image:: https://img.shields.io/pypi/v/python-telegram-bot-raw.svg
:target: https://pypi.org/project/python-telegram-bot-raw/
:alt: PyPi Package Version
.. image:: https://img.shields.io/pypi/pyversions/python-telegram-bot-raw.svg
:target: https://pypi.org/project/python-telegram-bot-raw/
:alt: Supported Python versions
.. 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 version
.. image:: https://img.shields.io/pypi/dm/python-telegram-bot-raw
:target: https://pypistats.org/packages/python-telegram-bot-raw
:alt: PyPi Package Monthly Download
.. image:: https://readthedocs.org/projects/python-telegram-bot/badge/?version=stable
:target: https://docs.python-telegram-bot.org/
:alt: Documentation Status
.. image:: https://img.shields.io/pypi/l/python-telegram-bot-raw.svg
:target: https://www.gnu.org/licenses/lgpl-3.0.html
:alt: LGPLv3 License
.. image:: https://github.com/python-telegram-bot/python-telegram-bot/actions/workflows/unit_tests.yml/badge.svg?branch=master
:target: https://github.com/python-telegram-bot/python-telegram-bot/
:alt: Github Actions workflow
.. image:: https://codecov.io/gh/python-telegram-bot/python-telegram-bot/branch/master/graph/badge.svg
:target: https://app.codecov.io/gh/python-telegram-bot/python-telegram-bot
:alt: Code coverage
.. image:: https://isitmaintained.com/badge/resolution/python-telegram-bot/python-telegram-bot.svg
:target: https://isitmaintained.com/project/python-telegram-bot/python-telegram-bot
:alt: Median time to resolve an issue
.. image:: https://api.codacy.com/project/badge/Grade/99d901eaa09b44b4819aec05c330c968
:target: https://app.codacy.com/gh/python-telegram-bot/python-telegram-bot/dashboard
:alt: Code quality: Codacy
.. image:: https://results.pre-commit.ci/badge/github/python-telegram-bot/python-telegram-bot/master.svg
:target: https://results.pre-commit.ci/latest/github/python-telegram-bot/python-telegram-bot/master
:alt: pre-commit.ci status
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
:target: https://github.com/psf/black
:alt: Code Style: Black
.. image:: https://img.shields.io/badge/Telegram-Channel-blue.svg?logo=telegram
:target: https://t.me/pythontelegrambotchannel
:alt: Telegram Channel
.. image:: https://img.shields.io/badge/Telegram-Group-blue.svg?logo=telegram
:target: https://telegram.me/pythontelegrambotgroup
:alt: Telegram Group
We have made you a wrapper you can't refuse
We have a vibrant community of developers helping each other in our `Telegram group <https://telegram.me/pythontelegrambotgroup>`_. Join us!
*Stay tuned for library updates and new releases on our* `Telegram Channel <https://telegram.me/pythontelegrambotchannel>`_.
Introduction
============
This library provides a pure Python, asynchronous interface for the
`Telegram Bot API <https://core.telegram.org/bots/api>`_.
It's compatible with Python versions **3.8+**.
``python-telegram-bot-raw`` is part of the `python-telegram-bot <https://python-telegram-bot.org>`_ ecosystem and provides the pure API functionality extracted from PTB. It therefore does not have independent release schedules, changelogs or documentation.
Note
----
Installing both ``python-telegram-bot`` and ``python-telegram-bot-raw`` in conjunction will result in undesired side-effects, so only install *one* of both.
Telegram API support
====================
All types and methods of the Telegram Bot API **7.3** are supported.
Installing
==========
You can install or upgrade ``python-telegram-bot`` via
.. code:: shell
$ pip install python-telegram-bot-raw --upgrade
To install a pre-release, use the ``--pre`` `flag <https://pip.pypa.io/en/stable/cli/pip_install/#cmdoption-pre>`_ in addition.
You can also install ``python-telegram-bot-raw`` from source, though this is usually not necessary.
.. code:: shell
$ git clone https://github.com/python-telegram-bot/python-telegram-bot
$ cd python-telegram-bot
$ python setup_raw.py install
Note
----
Installing the ``.tar.gz`` archive available on PyPi directly via ``pip`` will *not* work as expected, as ``pip`` does not recognize that it should use ``setup_raw.py`` instead of ``setup.py``.
Verifying Releases
------------------
We sign all the releases with a GPG key.
The signatures are uploaded to both the `GitHub releases page <https://github.com/python-telegram-bot/python-telegram-bot/releases>`_ and the `PyPI project <https://pypi.org/project/python-telegram-bot/>`_ and end with a suffix ``.asc``.
Please find the public keys `here <https://github.com/python-telegram-bot/python-telegram-bot/tree/master/public_keys>`_.
The keys are named in the format ``<first_version>-<last_version>.gpg`` or ``<first_version>-current.gpg`` if the key is currently being used for new releases.
In addition, the GitHub release page also contains the sha1 hashes of the release files in the files with the suffix ``.sha1``.
This allows you to verify that a release file that you downloaded was indeed provided by the ``python-telegram-bot`` team.
Dependencies & Their Versions
-----------------------------
``python-telegram-bot`` tries to use as few 3rd party dependencies as possible.
However, for some features using a 3rd party library is more sane than implementing the functionality again.
As these features are *optional*, the corresponding 3rd party dependencies are not installed by default.
Instead, they are listed as optional dependencies.
This allows to avoid unnecessary dependency conflicts for users who don't need the optional features.
The only required dependency is `httpx ~= 0.27 <https://www.python-httpx.org>`_ for
``telegram.request.HTTPXRequest``, the default networking backend.
``python-telegram-bot`` is most useful when used along with additional libraries.
To minimize dependency conflicts, we try to be liberal in terms of version requirements on the (optional) dependencies.
On the other hand, we have to ensure stability of ``python-telegram-bot``, which is why we do apply version bounds.
If you encounter dependency conflicts due to these bounds, feel free to reach out.
Optional Dependencies
#####################
PTB can be installed with optional dependencies:
* ``pip install "python-telegram-bot-raw[passport]"`` installs the `cryptography>=39.0.1 <https://cryptography.io/en/stable>`_ library. Use this, if you want to use Telegram Passport related functionality.
* ``pip install "python-telegram-bot-raw[socks]"`` installs `httpx[socks] <https://www.python-httpx.org/#dependencies>`_. Use this, if you want to work behind a Socks5 server.
* ``pip install "python-telegram-bot-raw[http2]"`` installs `httpx[http2] <https://www.python-httpx.org/#dependencies>`_. Use this, if you want to use HTTP/2.
To install multiple optional dependencies, separate them by commas, e.g. ``pip install "python-telegram-bot-raw[passport,socks]"``.
Additionally, the shortcut ``pip install "python-telegram-bot-raw[all]"`` installs all optional dependencies.
Quick Start
===========
Our Wiki contains an `Introduction to the API <https://github.com/python-telegram-bot/python-telegram-bot/wiki/Introduction-to-the-API>`_ explaining how the pure Bot API can be accessed via ``python-telegram-bot``.
Resources
=========
- The `package documentation <https://docs.python-telegram-bot.org/>`_ is the technical reference for ``python-telegram-bot``.
It contains descriptions of all available classes, modules, methods and arguments as well as the `changelog <https://docs.python-telegram-bot.org/changelog.html>`_.
- The `wiki <https://github.com/python-telegram-bot/python-telegram-bot/wiki/>`_ is home to number of more elaborate introductions of the different features of ``python-telegram-bot`` and other useful resources that go beyond the technical documentation.
- Our `examples section <https://docs.python-telegram-bot.org/examples.html>`_ contains several examples that showcase the different features of both the Bot API and ``python-telegram-bot``.
Even if it is not your approach for learning, please take a look at ``echobot.py``. It is the de facto base for most of the bots out there.
The code for these examples is released to the public domain, so you can start by grabbing the code and building on top of it.
- The `official Telegram Bot API documentation <https://core.telegram.org/bots/api>`_ is of course always worth a read.
Getting help
============
If the resources mentioned above don't answer your questions or simply overwhelm you, there are several ways of getting help.
1. We have a vibrant community of developers helping each other in our `Telegram group <https://telegram.me/pythontelegrambotgroup>`_. Join us! Asking a question here is often the quickest way to get a pointer in the right direction.
2. Ask questions by opening `a discussion <https://github.com/python-telegram-bot/python-telegram-bot/discussions/new>`_.
3. You can even ask for help on Stack Overflow using the `python-telegram-bot tag <https://stackoverflow.com/questions/tagged/python-telegram-bot>`_.
Concurrency
===========
Since v20.0, ``python-telegram-bot`` is built on top of Pythons ``asyncio`` module.
Because ``asyncio`` is in general single-threaded, ``python-telegram-bot`` does currently not aim to be thread-safe.
Contributing
============
Contributions of all sizes are welcome.
Please review our `contribution guidelines <https://github.com/python-telegram-bot/python-telegram-bot/blob/master/.github/CONTRIBUTING.rst>`_ to get started.
You can also help by `reporting bugs or feature requests <https://github.com/python-telegram-bot/python-telegram-bot/issues/new/choose>`_.
Donating
========
Occasionally we are asked if we accept donations to support the development.
While we appreciate the thought, maintaining PTB is our hobby, and we have almost no running costs for it. We therefore have nothing set up to accept donations.
If you still want to donate, we kindly ask you to donate to another open source project/initiative of your choice instead.
License
=======
You may copy, distribute and modify the software provided that modifications are described and licensed for free under `LGPL-3 <https://www.gnu.org/licenses/lgpl-3.0.html>`_.
Derivatives works (including modifications or anything statically linked to the library) can only be redistributed under LGPL-3, but applications that use the library don't have to be.

View file

@ -46,6 +46,7 @@ PRIVATE_BASE_CLASSES = {
"_BaseThumbedMedium": "TelegramObject",
"_BaseMedium": "TelegramObject",
"_CredentialsBase": "TelegramObject",
"_ChatBase": "TelegramObject",
}

View file

@ -4,4 +4,4 @@ furo-sphinx-search @ git+https://github.com/harshil21/furo-sphinx-search@v0.2.0.
sphinx-paramlinks==0.6.0
sphinxcontrib-mermaid==0.9.2
sphinx-copybutton==0.5.2
sphinx-inline-tabs==2023.4.21
sphinx-inline-tabs==2023.4.21

View file

@ -20,9 +20,13 @@ author = "Leandro Toledo"
# built documents.
#
# The short X.Y version.
version = "21.2" # telegram.__version__[:3]
# Import needs to be below the sys.path.insert above
import telegram # noqa: E402
version = telegram.__version__
# The full version, including alpha/beta/rc tags.
release = "21.2" # telegram.__version__
release = telegram.__version__
# If your documentation needs a minimal Sphinx version, state it here.
needs_sphinx = "6.1.3"

View file

@ -369,6 +369,8 @@
- Used for getting basic info about a file
* - :meth:`~telegram.Bot.get_me`
- Used for getting basic information about the bot
* - :meth:`~telegram.Bot.refund_star_payment`
- Used for refunding a payment in Telegram Stars
.. raw:: html

View file

@ -1,6 +1,8 @@
Chat
====
.. Also lists methods of _ChatBase, but not the ones of TelegramObject
.. autoclass:: telegram.Chat
:members:
:show-inheritance:
:inherited-members: TelegramObject

View file

@ -1,6 +1,8 @@
ChatFullInfo
============
.. Also lists methods of _ChatBase, but not the ones of TelegramObject
.. autoclass:: telegram.ChatFullInfo
:members:
:show-inheritance:
:show-inheritance:
:inherited-members: TelegramObject

View file

@ -1,6 +1,6 @@
PhotoSize
=========
.. Also lists methods of _BaseThumbedMedium, but not the ones of TelegramObject
.. Also lists methods of _BaseMedium, but not the ones of TelegramObject
.. autoclass:: telegram.PhotoSize
:members:

View file

@ -81,3 +81,9 @@
.. |non_optional_story_argument| replace:: As of this version, this argument is now required. In accordance with our `stability policy <https://docs.python-telegram-bot.org/en/stable/stability_policy.html>`__, the signature will be kept as optional for now, though they are mandatory and an error will be raised if you don't pass it.
.. |business_id_str| replace:: Unique identifier of the business connection on behalf of which the message will be sent.
.. |message_effect_id| replace:: Unique identifier of the message effect to be added to the message; for private chats only.
.. |show_cap_above_med| replace:: :obj:`True`, if the caption must be shown above the message media.
.. |tg_stars| replace:: `Telegram Stars <https://t.me/BotNews/90>`__

View file

@ -1,7 +1,116 @@
# PACKAGING
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
dynamic = ["version"]
name = "python-telegram-bot"
description = "We have made you a wrapper you can't refuse"
readme = "README.rst"
requires-python = ">=3.8"
license = "LGPL-3.0-only"
license-files = { paths = ["LICENSE", "LICENSE.dual", "LICENSE.lesser"] }
authors = [
{ name = "Leandro Toledo", email = "devs@python-telegram-bot.org" }
]
keywords = [
"python",
"telegram",
"bot",
"api",
"wrapper",
]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
"Operating System :: OS Independent",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Communications :: Chat",
"Topic :: Internet",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
]
dependencies = [
"httpx ~= 0.27",
]
[project.urls]
"Homepage" = "https://python-telegram-bot.org"
"Documentation" = "https://docs.python-telegram-bot.org"
"Bug Tracker" = "https://github.com/python-telegram-bot/python-telegram-bot/issues"
"Source Code" = "https://github.com/python-telegram-bot/python-telegram-bot"
"News" = "https://t.me/pythontelegrambotchannel"
"Changelog" = "https://docs.python-telegram-bot.org/en/stable/changelog.html"
"Support" = "https://t.me/pythontelegrambotgroup"
[project.optional-dependencies]
# Make sure to install those as additional_dependencies in the
# pre-commit hooks for pylint & mypy
# Also update the readme accordingly
#
# When dependencies release new versions and tests succeed, we should try to expand the allowed
# versions and only increase the lower bound if necessary
#
# When adding new groups, make sure to update `ext` and `all` accordingly
# Optional dependencies for production
all = [
"python-telegram-bot[ext,http2,passport,socks]",
]
callback-data = [
# Cachetools doesn't have a strict stability policy. Let's be cautious for now.
"cachetools~=5.3.3",
]
ext = [
"python-telegram-bot[callback-data,job-queue,rate-limiter,webhooks]",
]
http2 = [
"httpx[http2]",
]
job-queue = [
# APS doesn't have a strict stability policy. Let's be cautious for now.
"APScheduler~=3.10.4",
# pytz is required by APS and just needs the lower bound due to #2120
"pytz>=2018.6",
]
passport = [
"cryptography!=3.4,!=3.4.1,!=3.4.2,!=3.4.3,>=39.0.1",
# cffi is a dependency of cryptography and added support for python 3.13 in 1.17.0rc1
"cffi >= 1.17.0rc1; python_version > '3.12'"
]
rate-limiter = [
"aiolimiter~=1.1.0",
]
socks = [
"httpx[socks]",
]
webhooks = [
# tornado is rather stable, but let's not allow the next major release without prior testing
"tornado~=6.4",
]
# HATCH
[tool.hatch.version]
# dynamically evaluates the `__version__` variable in that file
source = "code"
path = "telegram/_version.py"
search-paths = ["telegram"]
[tool.hatch.build]
packages = ["telegram"]
# BLACK:
[tool.black]
line-length = 99
target-version = ['py38', 'py39', 'py310', 'py311']
# ISORT:
[tool.isort] # black config
@ -11,7 +120,6 @@ line_length = 99
# RUFF:
[tool.ruff]
line-length = 99
target-version = "py38"
show-fixes = true
[tool.ruff.lint]

View file

@ -1,4 +0,0 @@
-r requirements.txt
-r requirements-dev.txt
-r requirements-opts.txt
-r docs/requirements-docs.txt

5
requirements-dev-all.txt Normal file
View file

@ -0,0 +1,5 @@
-e .[all]
# needed for pre-commit hooks in the git commit command
pre-commit
-r requirements-unit-tests.txt
-r docs/requirements-docs.txt

View file

@ -1,10 +0,0 @@
pre-commit # needed for pre-commit hooks in the git commit command
# For the test suite
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
wheel # required for building the wheels for releases

View file

@ -1,27 +0,0 @@
# Format:
# package_name==version # req-1, req-2, req-3!ext
# `pip install ptb-raw[req-1/2]` will install `package_name`
# `pip install ptb[req-1/2/3]` will also install `package_name`
# Make sure to install those as additional_dependencies in the
# pre-commit hooks for pylint & mypy
# Also update the readme accordingly
# When dependencies release new versions and tests succeed, we should try to expand the allowed
# versions and only increase the lower bound if necessary
httpx[socks] # socks
httpx[http2] # http2
cryptography!=3.4,!=3.4.1,!=3.4.2,!=3.4.3,>=39.0.1 # passport
aiolimiter~=1.1.0 # rate-limiter!ext
# tornado is rather stable, but let's not allow the next mayor release without prior testing
tornado~=6.4 # webhooks!ext
# Cachetools and APS don't have a strict stability policy.
# Let's be cautious for now.
cachetools~=5.3.3 # callback-data!ext
APScheduler~=3.10.4 # job-queue!ext
# pytz is required by APS and just needs the lower bound due to #2120
pytz>=2018.6 # job-queue!ext

View file

@ -0,0 +1,19 @@
-e .
# required for building the wheels for releases
build
# For the test suite
pytest==8.2.2
# needed because pytest doesn't come with native support for coroutines as tests
pytest-asyncio==0.21.2
# xdist runs tests in parallel
pytest-xdist==3.6.1
# Used for flaky tests (flaky decorator)
flaky
# used in test_official for parsing tg docs
beautifulsoup4

View file

@ -1,10 +0,0 @@
# Make sure to install those as additional_dependencies in the
# pre-commit hooks for pylint & mypy
# Also update the readme accordingly
# When dependencies release new versions and tests succeed, we should try to expand the allowed
# versions and only increase the lower bound if necessary
# httpx has no stable release yet, but we've had no stability problems since v20.0a0 either
# Since there have been requests to relax the bound a bit, we allow versions < 1.0.0
httpx ~= 0.27

View file

@ -1,8 +1,5 @@
[metadata]
license_files = LICENSE, LICENSE.dual, LICENSE.lesser
[flake8]
max-line-length = 99
ignore = W503, W605
extend-ignore = E203, E704
exclude = setup.py, setup_raw.py docs/source/conf.py
exclude = docs/source/conf.py

131
setup.py
View file

@ -1,131 +0,0 @@
#!/usr/bin/env python
"""The setup and build script for the python-telegram-bot library."""
import subprocess
import sys
from collections import defaultdict
from pathlib import Path
from typing import Any, Dict, List, Tuple
from setuptools import find_packages, setup
def get_requirements() -> List[str]:
"""Build the requirements list for this project"""
requirements_list = []
with Path("requirements.txt").open(encoding="utf-8") as reqs:
for install in reqs:
if install.startswith("#"):
continue
requirements_list.append(install.strip())
return requirements_list
def get_packages_requirements(raw: bool = False) -> Tuple[List[str], List[str]]:
"""Build the package & requirements list for this project"""
reqs = get_requirements()
exclude = ["tests*", "docs*"]
if raw:
exclude.append("telegram.ext*")
packs = find_packages(exclude=exclude)
return packs, reqs
def get_optional_requirements(raw: bool = False) -> Dict[str, List[str]]:
"""Build the optional dependencies"""
requirements = defaultdict(list)
with Path("requirements-opts.txt").open(encoding="utf-8") as reqs:
for line in reqs:
effective_line = line.strip()
if not effective_line or effective_line.startswith("#"):
continue
dependency, names = effective_line.split("#")
dependency = dependency.strip()
for name in names.split(","):
effective_name = name.strip()
if effective_name.endswith("!ext"):
if raw:
continue
effective_name = effective_name[:-4]
requirements["ext"].append(dependency)
requirements[effective_name].append(dependency)
requirements["all"].append(dependency)
return requirements
def get_setup_kwargs(raw: bool = False) -> Dict[str, Any]:
"""Builds a dictionary of kwargs for the setup function"""
packages, requirements = get_packages_requirements(raw=raw)
raw_ext = "-raw" if raw else ""
readme = Path(f'README{"_RAW" if raw else ""}.rst')
version_file = Path("telegram/_version.py").read_text(encoding="utf-8")
first_part = version_file.split("# SETUP.PY MARKER")[0]
exec(first_part) # pylint: disable=exec-used
return {
"script_name": f"setup{raw_ext}.py",
"name": f"python-telegram-bot{raw_ext}",
"version": locals()["__version__"],
"author": "Leandro Toledo",
"author_email": "devs@python-telegram-bot.org",
"license": "LGPLv3",
"url": "https://python-telegram-bot.org/",
# Keywords supported by PyPI can be found at
# https://github.com/pypa/warehouse/blob/aafc5185e57e67d43487ce4faa95913dd4573e14/
# warehouse/templates/packaging/detail.html#L20-L58
"project_urls": {
"Documentation": "https://docs.python-telegram-bot.org",
"Bug Tracker": "https://github.com/python-telegram-bot/python-telegram-bot/issues",
"Source Code": "https://github.com/python-telegram-bot/python-telegram-bot",
"News": "https://t.me/pythontelegrambotchannel",
"Changelog": "https://docs.python-telegram-bot.org/en/stable/changelog.html",
},
"download_url": f"https://pypi.org/project/python-telegram-bot{raw_ext}/",
"keywords": "python telegram bot api wrapper",
"description": "We have made you a wrapper you can't refuse",
"long_description": readme.read_text(encoding="utf-8"),
"long_description_content_type": "text/x-rst",
"packages": packages,
"install_requires": requirements,
"extras_require": get_optional_requirements(raw=raw),
"include_package_data": True,
"classifiers": [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
"Operating System :: OS Independent",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Communications :: Chat",
"Topic :: Internet",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
],
"python_requires": ">=3.8",
}
def main() -> None:
# If we're building, build ptb-raw as well
if set(sys.argv[1:]) in [{"bdist_wheel"}, {"sdist"}, {"sdist", "bdist_wheel"}]:
args = ["python", "setup_raw.py"]
args.extend(sys.argv[1:])
subprocess.run(args, check=True, capture_output=True)
setup(**get_setup_kwargs(raw=False))
if __name__ == "__main__":
main()

View file

@ -1,8 +0,0 @@
#!/usr/bin/env python
"""The setup and build script for the python-telegram-bot-raw library."""
from setuptools import setup
from setup import get_setup_kwargs
setup(**get_setup_kwargs(raw=True))

View file

@ -242,7 +242,6 @@ __all__ = (
"warnings",
)
from . import _version, constants, error, helpers, request, warnings
from ._birthdate import Birthdate
from ._bot import Bot
@ -470,8 +469,8 @@ __version_info__: _version.Version = _version.__version_info__
#:
#: .. versionchanged:: 20.0
#: This constant was previously named ``bot_api_version``.
__bot_api_version__: str = _version.__bot_api_version__
__bot_api_version__: str = constants.BOT_API_VERSION
#: :class:`typing.NamedTuple`: Shortcut for :const:`telegram.constants.BOT_API_VERSION_INFO`.
#:
#: .. versionadded:: 20.0
__bot_api_version_info__: constants._BotAPIVersion = _version.__bot_api_version_info__
__bot_api_version_info__: constants._BotAPIVersion = constants.BOT_API_VERSION_INFO

View file

@ -672,6 +672,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
link_preview_options: ODVInput["LinkPreviewOptions"] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
message_effect_id: Optional[str] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@ -711,7 +712,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
data["disable_notification"] = disable_notification
data["protect_content"] = protect_content
data["parse_mode"] = parse_mode
data["reply_parameters"] = reply_parameters
if reply_parameters is not None:
data["reply_parameters"] = reply_parameters
if link_preview_options is not None:
data["link_preview_options"] = link_preview_options
@ -731,6 +734,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
if business_connection_id is not None:
data["business_connection_id"] = business_connection_id
if message_effect_id is not None:
data["message_effect_id"] = message_effect_id
result = await self._post(
endpoint,
data,
@ -919,6 +925,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
link_preview_options: ODVInput["LinkPreviewOptions"] = DEFAULT_NONE,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
message_effect_id: Optional[str] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@ -967,6 +974,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
business_connection_id (:obj:`str`, optional): |business_id_str|
.. versionadded:: 21.1
message_effect_id (:obj:`str`, optional): |message_effect_id|
.. versionadded:: 21.3
Keyword Args:
allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
@ -1024,6 +1034,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
parse_mode=parse_mode,
link_preview_options=link_preview_options,
reply_parameters=reply_parameters,
message_effect_id=message_effect_id,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
@ -1152,7 +1163,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
Note:
Since the release of Bot API 5.5 it can be impossible to forward messages from
some chats. Use the attributes :attr:`telegram.Message.has_protected_content` and
:attr:`telegram.Chat.has_protected_content` to check this.
:attr:`telegram.ChatFullInfo.has_protected_content` to check this.
As a workaround, it is still possible to use :meth:`copy_message`. However, this
behaviour is undocumented and might be changed by Telegram.
@ -1272,6 +1283,8 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
has_spoiler: Optional[bool] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
message_effect_id: Optional[str] = None,
show_caption_above_media: Optional[bool] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@ -1335,6 +1348,12 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
business_connection_id (:obj:`str`, optional): |business_id_str|
.. versionadded:: 21.1
message_effect_id (:obj:`str`, optional): |message_effect_id|
.. versionadded:: 21.3
show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med|
.. versionadded:: 21.3
Keyword Args:
allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
@ -1372,6 +1391,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
"chat_id": chat_id,
"photo": self._parse_file_input(photo, PhotoSize, filename=filename),
"has_spoiler": has_spoiler,
"show_caption_above_media": show_caption_above_media,
}
return await self._send_message(
@ -1393,6 +1413,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=business_connection_id,
message_effect_id=message_effect_id,
)
async def send_audio(
@ -1412,6 +1433,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
message_effect_id: Optional[str] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@ -1484,6 +1506,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
business_connection_id (:obj:`str`, optional): |business_id_str|
.. versionadded:: 21.1
message_effect_id (:obj:`str`, optional): |message_effect_id|
.. versionadded:: 21.3
Keyword Args:
allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
@ -1545,6 +1570,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=business_connection_id,
message_effect_id=message_effect_id,
)
async def send_document(
@ -1562,6 +1588,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
message_effect_id: Optional[str] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@ -1633,6 +1660,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
business_connection_id (:obj:`str`, optional): |business_id_str|
.. versionadded:: 21.1
message_effect_id (:obj:`str`, optional): |message_effect_id|
.. versionadded:: 21.3
Keyword Args:
allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
@ -1690,6 +1720,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=business_connection_id,
message_effect_id=message_effect_id,
)
async def send_sticker(
@ -1703,6 +1734,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
emoji: Optional[str] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
message_effect_id: Optional[str] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@ -1754,6 +1786,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
business_connection_id (:obj:`str`, optional): |business_id_str|
.. versionadded:: 21.1
message_effect_id (:obj:`str`, optional): |message_effect_id|
.. versionadded:: 21.3
Keyword Args:
allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
@ -1803,6 +1838,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=business_connection_id,
message_effect_id=message_effect_id,
)
async def send_video(
@ -1824,6 +1860,8 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
message_effect_id: Optional[str] = None,
show_caption_above_media: Optional[bool] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@ -1904,6 +1942,12 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
business_connection_id (:obj:`str`, optional): |business_id_str|
.. versionadded:: 21.1
message_effect_id (:obj:`str`, optional): |message_effect_id|
.. versionadded:: 21.3
show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med|
.. versionadded:: 21.3
Keyword Args:
allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
@ -1946,6 +1990,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
"supports_streaming": supports_streaming,
"thumbnail": self._parse_file_input(thumbnail, attach=True) if thumbnail else None,
"has_spoiler": has_spoiler,
"show_caption_above_media": show_caption_above_media,
}
return await self._send_message(
@ -1967,6 +2012,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=business_connection_id,
message_effect_id=message_effect_id,
)
async def send_video_note(
@ -1982,6 +2028,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
message_effect_id: Optional[str] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@ -2047,6 +2094,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
business_connection_id (:obj:`str`, optional): |business_id_str|
.. versionadded:: 21.1
message_effect_id (:obj:`str`, optional): |message_effect_id|
.. versionadded:: 21.3
Keyword Args:
allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
@ -2104,6 +2154,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=business_connection_id,
message_effect_id=message_effect_id,
)
async def send_animation(
@ -2124,6 +2175,8 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
message_effect_id: Optional[str] = None,
show_caption_above_media: Optional[bool] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@ -2198,6 +2251,12 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
business_connection_id (:obj:`str`, optional): |business_id_str|
.. versionadded:: 21.1
message_effect_id (:obj:`str`, optional): |message_effect_id|
.. versionadded:: 21.3
show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med|
.. versionadded:: 21.3
Keyword Args:
allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
@ -2239,6 +2298,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
"height": height,
"thumbnail": self._parse_file_input(thumbnail, attach=True) if thumbnail else None,
"has_spoiler": has_spoiler,
"show_caption_above_media": show_caption_above_media,
}
return await self._send_message(
@ -2260,6 +2320,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=business_connection_id,
message_effect_id=message_effect_id,
)
async def send_voice(
@ -2276,6 +2337,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
message_effect_id: Optional[str] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@ -2344,6 +2406,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
business_connection_id (:obj:`str`, optional): |business_id_str|
.. versionadded:: 21.1
message_effect_id (:obj:`str`, optional): |message_effect_id|
.. versionadded:: 21.3
Keyword Args:
allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
@ -2402,6 +2467,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=business_connection_id,
message_effect_id=message_effect_id,
)
async def send_media_group(
@ -2415,6 +2481,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
message_effect_id: Optional[str] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@ -2465,6 +2532,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
business_connection_id (:obj:`str`, optional): |business_id_str|
.. versionadded:: 21.1
message_effect_id (:obj:`str`, optional): |message_effect_id|
.. versionadded:: 21.3
Keyword Args:
allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
@ -2558,6 +2628,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
"message_thread_id": message_thread_id,
"reply_parameters": reply_parameters,
"business_connection_id": business_connection_id,
"message_effect_id": message_effect_id,
}
result = await self._post(
@ -2587,6 +2658,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
message_effect_id: Optional[str] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@ -2643,6 +2715,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
business_connection_id (:obj:`str`, optional): |business_id_str|
.. versionadded:: 21.1
message_effect_id (:obj:`str`, optional): |message_effect_id|
.. versionadded:: 21.3
Keyword Args:
allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
@ -2712,6 +2787,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=business_connection_id,
message_effect_id=message_effect_id,
)
async def edit_message_live_location(
@ -2883,6 +2959,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
message_effect_id: Optional[str] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@ -2935,6 +3012,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
business_connection_id (:obj:`str`, optional): |business_id_str|
.. versionadded:: 21.1
message_effect_id (:obj:`str`, optional): |message_effect_id|
.. versionadded:: 21.3
Keyword Args:
allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
@ -3015,6 +3095,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=business_connection_id,
message_effect_id=message_effect_id,
)
async def send_contact(
@ -3030,6 +3111,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
message_effect_id: Optional[str] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@ -3072,6 +3154,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
business_connection_id (:obj:`str`, optional): |business_id_str|
.. versionadded:: 21.1
message_effect_id (:obj:`str`, optional): |message_effect_id|
.. versionadded:: 21.3
Keyword Args:
allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
@ -3143,6 +3228,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=business_connection_id,
message_effect_id=message_effect_id,
)
async def send_game(
@ -3155,6 +3241,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
message_effect_id: Optional[str] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@ -3187,6 +3274,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
business_connection_id (:obj:`str`, optional): |business_id_str|
.. versionadded:: 21.1
message_effect_id (:obj:`str`, optional): |message_effect_id|
.. versionadded:: 21.3
Keyword Args:
allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
@ -3233,6 +3323,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=business_connection_id,
message_effect_id=message_effect_id,
)
async def send_chat_action(
@ -3954,6 +4045,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
reply_markup: Optional["InlineKeyboardMarkup"] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
caption_entities: Optional[Sequence["MessageEntity"]] = None,
show_caption_above_media: Optional[bool] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@ -3985,6 +4077,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
|sequenceargs|
reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): An object for an
inline keyboard.
show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med|
.. versionadded:: 21.3
Returns:
:class:`telegram.Message`: On success, if edited message is not an inline message, the
@ -3998,6 +4093,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
"chat_id": chat_id,
"message_id": message_id,
"inline_message_id": inline_message_id,
"show_caption_above_media": show_caption_above_media,
}
return await self._send_message(
@ -4610,8 +4706,8 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
) -> bool:
"""Use this method to set a new group sticker set for a supergroup.
The bot must be an administrator in the chat for this to work and must have the appropriate
admin rights. Use the field :attr:`telegram.Chat.can_set_sticker_set` optionally returned
in :meth:`get_chat` requests to check if the bot can use this method.
admin rights. Use the field :attr:`telegram.ChatFullInfo.can_set_sticker_set` optionally
returned in :meth:`get_chat` requests to check if the bot can use this method.
Args:
chat_id (:obj:`int` | :obj:`str`): |chat_id_group|
@ -4644,7 +4740,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
) -> bool:
"""Use this method to delete a group sticker set from a supergroup. The bot must be an
administrator in the chat for this to work and must have the appropriate admin rights.
Use the field :attr:`telegram.Chat.can_set_sticker_set` optionally returned in
Use the field :attr:`telegram.ChatFullInfo.can_set_sticker_set` optionally returned in
:meth:`get_chat` requests to check if the bot can use this method.
Args:
@ -4822,7 +4918,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
title: str,
description: str,
payload: str,
provider_token: str,
provider_token: Optional[str], # This arg is now optional as of Bot API 7.4
currency: str,
prices: Sequence["LabeledPrice"],
start_parameter: Optional[str] = None,
@ -4845,6 +4941,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
message_effect_id: Optional[str] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@ -4876,12 +4973,19 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
:tg-const:`telegram.Invoice.MAX_PAYLOAD_LENGTH` bytes. This will not be
displayed to the user, use for your internal processes.
provider_token (:obj:`str`): Payments provider token, obtained via
`@BotFather <https://t.me/BotFather>`_.
`@BotFather <https://t.me/BotFather>`_. Pass an empty string for payments in
|tg_stars|.
.. deprecated:: 21.3
As of Bot API 7.4, this parameter is now optional and future versions of the
library will make it optional as well.
currency (:obj:`str`): Three-letter ISO 4217 currency code, see `more on currencies
<https://core.telegram.org/bots/payments#supported-currencies>`_.
<https://core.telegram.org/bots/payments#supported-currencies>`_. Pass ``XTR`` for
payment in |tg_stars|.
prices (Sequence[:class:`telegram.LabeledPrice`]): Price breakdown, a sequence
of components (e.g. product price, tax, discount, delivery cost, delivery tax,
bonus, etc.).
bonus, etc.). Must contain exactly one item for payment in |tg_stars|.
.. versionchanged:: 20.0
|sequenceargs|
@ -4890,7 +4994,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
a maximum tip of US$ 1.45 pass ``max_tip_amount = 145``. See the exp parameter in
`currencies.json <https://core.telegram.org/bots/payments/currencies.json>`_, it
shows the number of digits past the decimal point for each currency (2 for the
majority of currencies). Defaults to ``0``.
majority of currencies). Defaults to ``0``. Not supported for payment in |tg_stars|
.. versionadded:: 13.5
suggested_tip_amounts (Sequence[:obj:`int`], optional): An array of
@ -4923,19 +5027,20 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
photo_width (:obj:`int`, optional): Photo width.
photo_height (:obj:`int`, optional): Photo height.
need_name (:obj:`bool`, optional): Pass :obj:`True`, if you require the user's full
name to complete the order.
name to complete the order. Ignored for payments in |tg_stars|.
need_phone_number (:obj:`bool`, optional): Pass :obj:`True`, if you require the user's
phone number to complete the order.
phone number to complete the order. Ignored for payments in |tg_stars|.
need_email (:obj:`bool`, optional): Pass :obj:`True`, if you require the user's email
to complete the order.
to complete the order. Ignored for payments in |tg_stars|.
need_shipping_address (:obj:`bool`, optional): Pass :obj:`True`, if you require the
user's shipping address to complete the order.
user's shipping address to complete the order. Ignored for payments in
|tg_stars|.
send_phone_number_to_provider (:obj:`bool`, optional): Pass :obj:`True`, if user's
phone number should be sent to provider.
phone number should be sent to provider. Ignored for payments in |tg_stars|.
send_email_to_provider (:obj:`bool`, optional): Pass :obj:`True`, if user's email
address should be sent to provider.
address should be sent to provider. Ignored for payments in |tg_stars|.
is_flexible (:obj:`bool`, optional): Pass :obj:`True`, if the final price depends on
the shipping method.
the shipping method. Ignored for payments in |tg_stars|.
disable_notification (:obj:`bool`, optional): |disable_notification|
protect_content (:obj:`bool`, optional): |protect_content|
@ -4950,6 +5055,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
reply_parameters (:class:`telegram.ReplyParameters`, optional): |reply_parameters|
.. versionadded:: 20.8
message_effect_id (:obj:`str`, optional): |message_effect_id|
.. versionadded:: 21.3
Keyword Args:
allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
@ -5018,6 +5126,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
message_effect_id=message_effect_id,
)
async def answer_shipping_query(
@ -6839,6 +6948,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
business_connection_id: Optional[str] = None,
question_parse_mode: ODVInput[str] = DEFAULT_NONE,
question_entities: Optional[Sequence["MessageEntity"]] = None,
message_effect_id: Optional[str] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@ -6933,6 +7043,9 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
:paramref:`question_parse_mode`.
.. versionadded:: 21.2
message_effect_id (:obj:`str`, optional): |message_effect_id|
.. versionadded:: 21.3
Keyword Args:
allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
@ -6998,6 +7111,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=business_connection_id,
message_effect_id=message_effect_id,
)
async def stop_poll(
@ -7055,6 +7169,7 @@ 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,
message_effect_id: Optional[str] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@ -7101,6 +7216,9 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
business_connection_id (:obj:`str`, optional): |business_id_str|
.. versionadded:: 21.1
message_effect_id (:obj:`str`, optional): |message_effect_id|
.. versionadded:: 21.3
Keyword Args:
allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
@ -7148,6 +7266,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=business_connection_id,
message_effect_id=message_effect_id,
)
async def get_my_default_administrator_rights(
@ -7417,7 +7536,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
minutes.
Returns:
:obj:`True`: On success
:obj:`True`: On success, :obj:`True` is returned.
Raises:
:class:`telegram.error.TelegramError`
@ -7448,7 +7567,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
10 minutes after the bot is launched.
Returns:
:obj:`True`: On success
:obj:`True`: On success, :obj:`True` is returned.
Raises:
:class:`telegram.error.TelegramError`
@ -7476,6 +7595,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
show_caption_above_media: Optional[bool] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@ -7519,6 +7639,9 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
reply_parameters (:class:`telegram.ReplyParameters`, optional): |reply_parameters|
.. versionadded:: 20.8
show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med|
.. versionadded:: 21.3
Keyword Args:
allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
@ -7541,7 +7664,8 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
|keyword_only_arg|
Returns:
:class:`telegram.MessageId`: On success
:class:`telegram.MessageId`: On success, the :class:`telegram.MessageId` of the sent
message is returned.
Raises:
:class:`telegram.error.TelegramError`
@ -7575,6 +7699,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
"reply_markup": reply_markup,
"message_thread_id": message_thread_id,
"reply_parameters": reply_parameters,
"show_caption_above_media": show_caption_above_media,
}
result = await self._post(
@ -7743,7 +7868,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
title: str,
description: str,
payload: str,
provider_token: str,
provider_token: Optional[str], # This arg is now optional as of Bot API 7.4
currency: str,
prices: Sequence["LabeledPrice"],
max_tip_amount: Optional[int] = None,
@ -7782,12 +7907,19 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
:tg-const:`telegram.Invoice.MAX_PAYLOAD_LENGTH` bytes. This will not be
displayed to the user, use for your internal processes.
provider_token (:obj:`str`): Payments provider token, obtained via
`@BotFather <https://t.me/BotFather>`_.
`@BotFather <https://t.me/BotFather>`_. Pass an empty string for payments in
|tg_stars|.
.. deprecated:: 21.3
As of Bot API 7.4, this parameter is now optional and future versions of the
library will make it optional as well.
currency (:obj:`str`): Three-letter ISO 4217 currency code, see `more on currencies
<https://core.telegram.org/bots/payments#supported-currencies>`_.
<https://core.telegram.org/bots/payments#supported-currencies>`_. Pass ``XTR`` for
payments in |tg_stars|.
prices (Sequence[:class:`telegram.LabeledPrice`)]: Price breakdown, a sequence
of components (e.g. product price, tax, discount, delivery cost, delivery tax,
bonus, etc.).
bonus, etc.). Must contain exactly one item for payments in |tg_stars|.
.. versionchanged:: 20.0
|sequenceargs|
@ -7796,7 +7928,8 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
a maximum tip of US$ 1.45 pass ``max_tip_amount = 145``. See the exp parameter in
`currencies.json <https://core.telegram.org/bots/payments/currencies.json>`_, it
shows the number of digits past the decimal point for each currency (2 for the
majority of currencies). Defaults to ``0``.
majority of currencies). Defaults to ``0``. Not supported for payments in
|tg_stars|.
suggested_tip_amounts (Sequence[:obj:`int`], optional): An array of
suggested amounts of tips in the *smallest* units of the currency (integer, **not**
float/double). At most :tg-const:`telegram.Invoice.MAX_TIP_AMOUNTS` suggested tip
@ -7815,19 +7948,20 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
photo_width (:obj:`int`, optional): Photo width.
photo_height (:obj:`int`, optional): Photo height.
need_name (:obj:`bool`, optional): Pass :obj:`True`, if you require the user's full
name to complete the order.
name to complete the order. Ignored for payments in |tg_stars|.
need_phone_number (:obj:`bool`, optional): Pass :obj:`True`, if you require the user's
phone number to complete the order.
phone number to complete the order. Ignored for payments in |tg_stars|.
need_email (:obj:`bool`, optional): Pass :obj:`True`, if you require the user's email
address to complete the order.
address to complete the order. Ignored for payments in |tg_stars|.
need_shipping_address (:obj:`bool`, optional): Pass :obj:`True`, if you require the
user's shipping address to complete the order.
user's shipping address to complete the order. Ignored for payments in
|tg_stars|.
send_phone_number_to_provider (:obj:`bool`, optional): Pass :obj:`True`, if user's
phone number should be sent to provider.
phone number should be sent to provider. Ignored for payments in |tg_stars|.
send_email_to_provider (:obj:`bool`, optional): Pass :obj:`True`, if user's email
address should be sent to provider.
address should be sent to provider. Ignored for payments in |tg_stars|.
is_flexible (:obj:`bool`, optional): Pass :obj:`True`, if the final price depends on
the shipping method.
the shipping method. Ignored for payments in |tg_stars|.
Returns:
:class:`str`: On success, the created invoice link is returned.
@ -8895,6 +9029,47 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
api_kwargs=api_kwargs,
)
async def refund_star_payment(
self,
user_id: int,
telegram_payment_charge_id: str,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
) -> bool:
"""Refunds a successful payment in |tg_stars|.
.. versionadded:: 21.3
Args:
user_id (:obj:`int`): User identifier of the user whose payment will be refunded.
telegram_payment_charge_id (:obj:`str`): Telegram payment identifier.
Returns:
:obj:`bool`: On success, :obj:`True` is returned.
Raises:
:class:`telegram.error.TelegramError`
"""
data: JSONDict = {
"user_id": user_id,
"telegram_payment_charge_id": telegram_payment_charge_id,
}
return await self._post(
"refundStarPayment",
data,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
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}
@ -9145,3 +9320,5 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
"""Alias for :meth:`get_business_connection`"""
replaceStickerInSet = replace_sticker_in_set
"""Alias for :meth:`replace_sticker_in_set`"""
refundStarPayment = refund_star_payment
"""Alias for :meth:`refund_star_payment`"""

View file

@ -281,6 +281,7 @@ class CallbackQuery(TelegramObject):
reply_markup: Optional["InlineKeyboardMarkup"] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
caption_entities: Optional[Sequence["MessageEntity"]] = None,
show_caption_above_media: Optional[bool] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@ -326,6 +327,7 @@ class CallbackQuery(TelegramObject):
caption_entities=caption_entities,
chat_id=None,
message_id=None,
show_caption_above_media=show_caption_above_media,
)
return await self._get_message().edit_caption(
caption=caption,
@ -337,6 +339,7 @@ class CallbackQuery(TelegramObject):
parse_mode=parse_mode,
api_kwargs=api_kwargs,
caption_entities=caption_entities,
show_caption_above_media=show_caption_above_media,
)
async def edit_message_reply_markup(
@ -815,6 +818,7 @@ class CallbackQuery(TelegramObject):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
show_caption_above_media: Optional[bool] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@ -861,6 +865,7 @@ class CallbackQuery(TelegramObject):
protect_content=protect_content,
message_thread_id=message_thread_id,
reply_parameters=reply_parameters,
show_caption_above_media=show_caption_above_media,
)
MAX_ANSWER_TEXT_LENGTH: Final[int] = (

File diff suppressed because it is too large Load diff

View file

@ -19,58 +19,380 @@
# 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 typing import TYPE_CHECKING, Optional, Sequence, Tuple
from telegram._birthdate import Birthdate
from telegram._chat import Chat
from telegram._chat import Chat, _ChatBase
from telegram._chatlocation import ChatLocation
from telegram._chatpermissions import ChatPermissions
from telegram._files.chatphoto import ChatPhoto
from telegram._reaction import ReactionType
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
from telegram import BusinessIntro, BusinessLocation, BusinessOpeningHours, Message
from telegram import Bot, BusinessIntro, BusinessLocation, BusinessOpeningHours, Message
class ChatFullInfo(Chat):
class ChatFullInfo(_ChatBase):
"""
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
.. versionchanged:: 21.3
Explicit support for all shortcut methods known from :class:`telegram.Chat` on this
object. Previously those were only available because this class inherited from
:class:`telegram.Chat`.
Args:
id (:obj:`int`): Unique identifier for this chat.
type (:obj:`str`): Type of chat, can be either :attr:`PRIVATE`, :attr:`GROUP`,
:attr:`SUPERGROUP` or :attr:`CHANNEL`.
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`_
for more details.
.. versionadded:: 20.8
max_reaction_count (:obj:`int`): The maximum number of reactions that can be set on a
message in the chat.
.. versionadded:: 21.2
title (:obj:`str`, optional): Title, for supergroups, channels and group chats.
username (:obj:`str`, optional): Username, for private chats, supergroups and channels if
available.
first_name (:obj:`str`, optional): First name of the other party in a private chat.
last_name (:obj:`str`, optional): Last name of the other party in a private chat.
is_forum (:obj:`bool`, optional): :obj:`True`, if the supergroup chat is a forum
(has topics_ enabled).
.. versionadded:: 20.0
photo (:class:`telegram.ChatPhoto`, optional): Chat photo.
active_usernames (Sequence[:obj:`str`], optional): If set, the list of all `active chat
usernames <https://telegram.org/blog/topics-in-groups-collectible-usernames\
#collectible-usernames>`_; for private chats, supergroups and channels.
.. versionadded:: 20.0
birthdate (:obj:`telegram.Birthdate`, optional): For private chats,
the date of birth of the user.
.. versionadded:: 21.1
business_intro (:class:`telegram.BusinessIntro`, optional): For private chats with
business accounts, the intro of the business.
.. versionadded:: 21.1
business_location (:class:`telegram.BusinessLocation`, optional): For private chats with
business accounts, the location of the business.
.. versionadded:: 21.1
business_opening_hours (:class:`telegram.BusinessOpeningHours`, optional): For private
chats with business accounts, the opening hours of the business.
.. versionadded:: 21.1
personal_chat (:obj:`telegram.Chat`, optional): For private chats, the personal channel of
the user.
.. versionadded:: 21.1
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.
.. versionadded:: 20.8
background_custom_emoji_id (:obj:`str`, optional): Custom emoji identifier of emoji chosen
by the chat for the reply header and link preview background.
.. versionadded:: 20.8
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.
.. versionadded:: 20.8
profile_background_custom_emoji_id (:obj:`str`, optional): Custom emoji identifier of
the emoji chosen by the chat for its profile background.
.. versionadded:: 20.8
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.
.. versionadded:: 20.0
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.
|datetime_localization|
.. versionadded:: 20.5
bio (:obj:`str`, optional): Bio of the other party in a private chat.
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.
.. versionadded:: 13.9
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.
.. versionadded:: 20.0
join_to_send_messages (:obj:`bool`, optional): :obj:`True`, if users need to join the
supergroup before they can send messages.
.. versionadded:: 20.0
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.
.. versionadded:: 20.0
description (:obj:`str`, optional): Description, for groups, supergroups and channel chats.
invite_link (:obj:`str`, optional): Primary invite link, for groups, supergroups and
channel.
pinned_message (:class:`telegram.Message`, optional): The most recent pinned message
(by sending date).
permissions (:class:`telegram.ChatPermissions`): Optional. Default chat member permissions,
for groups and supergroups.
slow_mode_delay (:obj:`int`, optional): For supergroups, the minimum allowed delay between
consecutive messages sent by each unprivileged user.
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.
.. versionadded:: 21.0
message_auto_delete_time (:obj:`int`, optional): The time after which all messages sent to
the chat will be automatically deleted; in seconds.
.. versionadded:: 13.4
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.
.. versionadded:: 20.0
has_hidden_members (:obj:`bool`, optional): :obj:`True`, if non-administrators can only
get the list of bots and administrators in the chat.
.. versionadded:: 20.0
has_protected_content (:obj:`bool`, optional): :obj:`True`, if messages from the chat can't
be forwarded to other chats.
.. versionadded:: 13.9
has_visible_history (:obj:`bool`, optional): :obj:`True`, if new chat members will have
access to old messages; available only to chat administrators.
.. versionadded:: 20.8
sticker_set_name (:obj:`str`, optional): For supergroups, name of group sticker set.
can_set_sticker_set (:obj:`bool`, optional): :obj:`True`, if the bot can change group the
sticker set.
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.
.. versionadded:: 21.0
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.
location (:class:`telegram.ChatLocation`, optional): For supergroups, the location to which
the supergroup is connected.
Attributes:
id (:obj:`int`): Unique identifier for this chat.
type (:obj:`str`): Type of chat, can be either :attr:`PRIVATE`, :attr:`GROUP`,
:attr:`SUPERGROUP` or :attr:`CHANNEL`.
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`_
for more details.
.. versionadded:: 20.8
max_reaction_count (:obj:`int`): The maximum number of reactions that can be set on a
message in the chat.
.. versionadded:: 21.2
title (:obj:`str`, optional): Title, for supergroups, channels and group chats.
username (:obj:`str`, optional): Username, for private chats, supergroups and channels if
available.
first_name (:obj:`str`, optional): First name of the other party in a private chat.
last_name (:obj:`str`, optional): Last name of the other party in a private chat.
is_forum (:obj:`bool`, optional): :obj:`True`, if the supergroup chat is a forum
(has topics_ enabled).
.. versionadded:: 20.0
photo (:class:`telegram.ChatPhoto`): Optional. Chat photo.
active_usernames (Tuple[:obj:`str`]): Optional. If set, the list of all `active chat
usernames <https://telegram.org/blog/topics-in-groups-collectible-usernames\
#collectible-usernames>`_; for private chats, supergroups and channels.
This list is empty if the chat has no active usernames or this chat instance was not
obtained via :meth:`~telegram.Bot.get_chat`.
.. versionadded:: 20.0
birthdate (:obj:`telegram.Birthdate`): Optional. For private chats,
the date of birth of the user.
.. versionadded:: 21.1
business_intro (:class:`telegram.BusinessIntro`): Optional. For private chats with
business accounts, the intro of the business.
.. versionadded:: 21.1
business_location (:class:`telegram.BusinessLocation`): Optional. For private chats with
business accounts, the location of the business.
.. versionadded:: 21.1
business_opening_hours (:class:`telegram.BusinessOpeningHours`): Optional. For private
chats with business accounts, the opening hours of the business.
.. versionadded:: 21.1
personal_chat (:obj:`telegram.Chat`): Optional. For private chats, the personal channel of
the user.
.. versionadded:: 21.1
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.
.. versionadded:: 20.8
background_custom_emoji_id (:obj:`str`): Optional. Custom emoji identifier of emoji chosen
by the chat for the reply header and link preview background.
.. versionadded:: 20.8
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.
.. versionadded:: 20.8
profile_background_custom_emoji_id (:obj:`str`): Optional. Custom emoji identifier of
the emoji chosen by the chat for its profile background.
.. versionadded:: 20.8
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.
.. versionadded:: 20.0
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.
|datetime_localization|
.. versionadded:: 20.5
bio (:obj:`str`): Optional. Bio of the other party in a private chat.
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.
.. versionadded:: 13.9
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.
.. versionadded:: 20.0
join_to_send_messages (:obj:`bool`): Optional. :obj:`True`, if users need to join
the supergroup before they can send messages.
.. versionadded:: 20.0
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.
.. versionadded:: 20.0
description (:obj:`str`): Optional. Description, for groups, supergroups and channel chats.
invite_link (:obj:`str`): Optional. Primary invite link, for groups, supergroups and
channel.
pinned_message (:class:`telegram.Message`): Optional. The most recent pinned message
(by sending date).
permissions (:class:`telegram.ChatPermissions`): Optional. Default chat member permissions,
for groups and supergroups.
slow_mode_delay (:obj:`int`): Optional. For supergroups, the minimum allowed delay between
consecutive messages sent by each unprivileged user.
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.
.. versionadded:: 21.0
message_auto_delete_time (:obj:`int`): Optional. The time after which all messages sent to
the chat will be automatically deleted; in seconds.
.. versionadded:: 13.4
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.
.. versionadded:: 20.0
has_hidden_members (:obj:`bool`): Optional. :obj:`True`, if non-administrators can only
get the list of bots and administrators in the chat.
.. versionadded:: 20.0
has_protected_content (:obj:`bool`): Optional. :obj:`True`, if messages from the chat can't
be forwarded to other chats.
.. versionadded:: 13.9
has_visible_history (:obj:`bool`): Optional. :obj:`True`, if new chat members will have
access to old messages; available only to chat administrators.
.. versionadded:: 20.8
sticker_set_name (:obj:`str`): Optional. For supergroups, name of Group sticker set.
can_set_sticker_set (:obj:`bool`): Optional. :obj:`True`, if the bot can change group the
sticker set.
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.
.. versionadded:: 21.0
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.
location (:class:`telegram.ChatLocation`): Optional. For supergroups, the location to which
the supergroup is connected.
.. _accent colors: https://core.telegram.org/bots/api#accent-colors
.. _topics: https://telegram.org/blog/topics-in-groups-collectible-usernames#topics-in-groups
"""
__slots__ = ("max_reaction_count",)
__slots__ = (
"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",
"max_reaction_count",
"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",
)
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
accent_color_id: int,
max_reaction_count: int,
title: Optional[str] = None,
username: Optional[str] = None,
first_name: Optional[str] = None,
@ -120,47 +442,93 @@ class ChatFullInfo(Chat):
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.photo: Optional[ChatPhoto] = photo
self.bio: Optional[str] = bio
self.has_private_forwards: Optional[bool] = has_private_forwards
self.description: Optional[str] = description
self.invite_link: Optional[str] = invite_link
self.pinned_message: Optional[Message] = pinned_message
self.permissions: Optional[ChatPermissions] = permissions
self.slow_mode_delay: Optional[int] = slow_mode_delay
self.message_auto_delete_time: Optional[int] = (
int(message_auto_delete_time) if message_auto_delete_time is not None else None
)
self.has_protected_content: Optional[bool] = has_protected_content
self.has_visible_history: Optional[bool] = has_visible_history
self.sticker_set_name: Optional[str] = sticker_set_name
self.can_set_sticker_set: Optional[bool] = can_set_sticker_set
self.linked_chat_id: Optional[int] = linked_chat_id
self.location: Optional[ChatLocation] = location
self.join_to_send_messages: Optional[bool] = join_to_send_messages
self.join_by_request: Optional[bool] = join_by_request
self.has_restricted_voice_and_video_messages: Optional[bool] = (
has_restricted_voice_and_video_messages
)
self.active_usernames: Tuple[str, ...] = parse_sequence_arg(active_usernames)
self.emoji_status_custom_emoji_id: Optional[str] = emoji_status_custom_emoji_id
self.emoji_status_expiration_date: Optional[datetime] = emoji_status_expiration_date
self.has_aggressive_anti_spam_enabled: Optional[bool] = (
has_aggressive_anti_spam_enabled
)
self.has_hidden_members: Optional[bool] = has_hidden_members
self.available_reactions: Optional[Tuple[ReactionType, ...]] = parse_sequence_arg(
available_reactions
)
self.accent_color_id: Optional[int] = accent_color_id
self.background_custom_emoji_id: Optional[str] = background_custom_emoji_id
self.profile_accent_color_id: Optional[int] = profile_accent_color_id
self.profile_background_custom_emoji_id: Optional[str] = (
profile_background_custom_emoji_id
)
self.unrestrict_boost_count: Optional[int] = unrestrict_boost_count
self.custom_emoji_sticker_set_name: Optional[str] = custom_emoji_sticker_set_name
self.birthdate: Optional[Birthdate] = birthdate
self.personal_chat: Optional[Chat] = personal_chat
self.business_intro: Optional[BusinessIntro] = business_intro
self.business_location: Optional[BusinessLocation] = business_location
self.business_opening_hours: Optional[BusinessOpeningHours] = business_opening_hours
self._freeze()
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["ChatFullInfo"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["emoji_status_expiration_date"] = from_timestamp(
data.get("emoji_status_expiration_date"), tzinfo=loc_tzinfo
)
data["photo"] = ChatPhoto.de_json(data.get("photo"), bot)
from telegram import ( # pylint: disable=import-outside-toplevel
BusinessIntro,
BusinessLocation,
BusinessOpeningHours,
Message,
)
data["pinned_message"] = Message.de_json(data.get("pinned_message"), bot)
data["permissions"] = ChatPermissions.de_json(data.get("permissions"), bot)
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"] = 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(
data.get("business_opening_hours"), bot
)
return super().de_json(data=data, bot=bot)

View file

@ -160,6 +160,9 @@ class InputMediaAnimation(InputMedia):
optional): |thumbdocstringnopath|
.. versionadded:: 20.2
show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med|
.. versionadded:: 21.3
Attributes:
type (:obj:`str`): :tg-const:`telegram.constants.InputMediaType.ANIMATION`.
@ -184,9 +187,19 @@ class InputMediaAnimation(InputMedia):
thumbnail (:class:`telegram.InputFile`): Optional. |thumbdocstringbase|
.. versionadded:: 20.2
show_caption_above_media (:obj:`bool`): Optional. |show_cap_above_med|
.. versionadded:: 21.3
"""
__slots__ = ("duration", "has_spoiler", "height", "thumbnail", "width")
__slots__ = (
"duration",
"has_spoiler",
"height",
"show_caption_above_media",
"thumbnail",
"width",
)
def __init__(
self,
@ -200,6 +213,7 @@ class InputMediaAnimation(InputMedia):
filename: Optional[str] = None,
has_spoiler: Optional[bool] = None,
thumbnail: Optional[FileInput] = None,
show_caption_above_media: Optional[bool] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@ -229,6 +243,7 @@ class InputMediaAnimation(InputMedia):
self.height: Optional[int] = height
self.duration: Optional[int] = duration
self.has_spoiler: Optional[bool] = has_spoiler
self.show_caption_above_media: Optional[bool] = show_caption_above_media
class InputMediaPhoto(InputMedia):
@ -260,6 +275,9 @@ class InputMediaPhoto(InputMedia):
with a spoiler animation.
.. versionadded:: 20.0
show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med|
.. versionadded:: 21.3
Attributes:
type (:obj:`str`): :tg-const:`telegram.constants.InputMediaType.PHOTO`.
@ -278,9 +296,15 @@ class InputMediaPhoto(InputMedia):
spoiler animation.
.. versionadded:: 20.0
show_caption_above_media (:obj:`bool`): Optional. |show_cap_above_med|
.. versionadded:: 21.3
"""
__slots__ = ("has_spoiler",)
__slots__ = (
"has_spoiler",
"show_caption_above_media",
)
def __init__(
self,
@ -290,6 +314,7 @@ class InputMediaPhoto(InputMedia):
caption_entities: Optional[Sequence[MessageEntity]] = None,
filename: Optional[str] = None,
has_spoiler: Optional[bool] = None,
show_caption_above_media: Optional[bool] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@ -307,6 +332,7 @@ class InputMediaPhoto(InputMedia):
with self._unfrozen():
self.has_spoiler: Optional[bool] = has_spoiler
self.show_caption_above_media: Optional[bool] = show_caption_above_media
class InputMediaVideo(InputMedia):
@ -359,6 +385,9 @@ class InputMediaVideo(InputMedia):
optional): |thumbdocstringnopath|
.. versionadded:: 20.2
show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med|
.. versionadded:: 21.3
Attributes:
type (:obj:`str`): :tg-const:`telegram.constants.InputMediaType.VIDEO`.
@ -385,12 +414,16 @@ class InputMediaVideo(InputMedia):
thumbnail (:class:`telegram.InputFile`): Optional. |thumbdocstringbase|
.. versionadded:: 20.2
show_caption_above_media (:obj:`bool`): Optional. |show_cap_above_med|
.. versionadded:: 21.3
"""
__slots__ = (
"duration",
"has_spoiler",
"height",
"show_caption_above_media",
"supports_streaming",
"thumbnail",
"width",
@ -409,6 +442,7 @@ class InputMediaVideo(InputMedia):
filename: Optional[str] = None,
has_spoiler: Optional[bool] = None,
thumbnail: Optional[FileInput] = None,
show_caption_above_media: Optional[bool] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@ -439,6 +473,7 @@ class InputMediaVideo(InputMedia):
)
self.supports_streaming: Optional[bool] = supports_streaming
self.has_spoiler: Optional[bool] = has_spoiler
self.show_caption_above_media: Optional[bool] = show_caption_above_media
class InputMediaAudio(InputMedia):

View file

@ -313,7 +313,7 @@ class GiveawayCompleted(TelegramObject):
self.winner_count: int = winner_count
self.unclaimed_prize_count: Optional[int] = unclaimed_prize_count
self.giveaway_message: Optional["Message"] = giveaway_message
self.giveaway_message: Optional[Message] = giveaway_message
self._id_attrs = (
self.winner_count,

View file

@ -41,7 +41,8 @@ class InlineKeyboardButton(TelegramObject):
:attr:`web_app` and :attr:`pay` are equal.
Note:
* You must use exactly one of the optional fields. Mind that :attr:`callback_game` is not
* Exactly one of the optional fields must be used to specify type of the button.
* Mind that :attr:`callback_game` is not
working as expected. Putting a game short name in it might, but is not guaranteed to
work.
* If your bot allows for arbitrary callback data, in keyboards returned in a response
@ -123,11 +124,17 @@ class InlineKeyboardButton(TelegramObject):
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.
pay (:obj:`bool`, optional): Specify :obj:`True`, to send a Pay button. This type of button
**must** always be the **first** button in the first row and can only be used in
invoice messages.
be launched when the user presses the button
Note:
This type of button **must** always be the first button in the first row.
pay (:obj:`bool`, optional): Specify :obj:`True`, to send a Pay button.
Substrings ```` and ``XTR`` in the buttons's text will be replaced with a
Telegram Star icon.
Note:
This type of button **must** always be the first button in the first row and can
only be used in invoice messages.
switch_inline_query_chosen_chat (:obj:`telegram.SwitchInlineQueryChosenChat`, optional):
If set, pressing the button will prompt the user to select one of their chats of the
specified type, open that chat and insert the bot's username and the specified inline
@ -186,11 +193,17 @@ class InlineKeyboardButton(TelegramObject):
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.
pay (:obj:`bool`): Optional. Specify :obj:`True`, to send a Pay button. This type of button
**must** always be the **first** button in the first row and can only be used in
invoice messages.
be launched when the user presses the button.
Note:
This type of button **must** always be the first button in the first row.
pay (:obj:`bool`): Optional. Specify :obj:`True`, to send a Pay button.
Substrings ```` and ``XTR`` in the buttons's text will be replaced with a
Telegram Star icon.
Note:
This type of button **must** always be the first button in the first row and can
only be used in invoice messages.
switch_inline_query_chosen_chat (:obj:`telegram.SwitchInlineQueryChosenChat`): Optional.
If set, pressing the button will prompt the user to select one of their chats of the
specified type, open that chat and insert the bot's username and the specified inline

View file

@ -59,6 +59,9 @@ class InlineQueryResultCachedGif(InlineQueryResult):
to the message.
input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the
message to be sent instead of the gif.
show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med|
.. versionadded:: 21.3
Attributes:
type (:obj:`str`): :tg-const:`telegram.constants.InlineQueryResultType.GIF`.
@ -81,6 +84,9 @@ class InlineQueryResultCachedGif(InlineQueryResult):
to the message.
input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the
message to be sent instead of the gif.
show_caption_above_media (:obj:`bool`): Optional. |show_cap_above_med|
.. versionadded:: 21.3
"""
@ -91,6 +97,7 @@ class InlineQueryResultCachedGif(InlineQueryResult):
"input_message_content",
"parse_mode",
"reply_markup",
"show_caption_above_media",
"title",
)
@ -104,6 +111,7 @@ class InlineQueryResultCachedGif(InlineQueryResult):
input_message_content: Optional["InputMessageContent"] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
caption_entities: Optional[Sequence[MessageEntity]] = None,
show_caption_above_media: Optional[bool] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@ -119,3 +127,4 @@ class InlineQueryResultCachedGif(InlineQueryResult):
self.caption_entities: Tuple[MessageEntity, ...] = parse_sequence_arg(caption_entities)
self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup
self.input_message_content: Optional[InputMessageContent] = input_message_content
self.show_caption_above_media: Optional[bool] = show_caption_above_media

View file

@ -59,6 +59,9 @@ class InlineQueryResultCachedMpeg4Gif(InlineQueryResult):
to the message.
input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the
message to be sent instead of the MPEG-4 file.
show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med|
.. versionadded:: 21.3
Attributes:
type (:obj:`str`): :tg-const:`telegram.constants.InlineQueryResultType.MPEG4GIF`.
@ -81,6 +84,9 @@ class InlineQueryResultCachedMpeg4Gif(InlineQueryResult):
to the message.
input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the
message to be sent instead of the MPEG-4 file.
show_caption_above_media (:obj:`bool`): Optional. |show_cap_above_med|
.. versionadded:: 21.3
"""
@ -91,6 +97,7 @@ class InlineQueryResultCachedMpeg4Gif(InlineQueryResult):
"mpeg4_file_id",
"parse_mode",
"reply_markup",
"show_caption_above_media",
"title",
)
@ -104,6 +111,7 @@ class InlineQueryResultCachedMpeg4Gif(InlineQueryResult):
input_message_content: Optional["InputMessageContent"] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
caption_entities: Optional[Sequence[MessageEntity]] = None,
show_caption_above_media: Optional[bool] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@ -119,3 +127,4 @@ class InlineQueryResultCachedMpeg4Gif(InlineQueryResult):
self.caption_entities: Tuple[MessageEntity, ...] = parse_sequence_arg(caption_entities)
self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup
self.input_message_content: Optional[InputMessageContent] = input_message_content
self.show_caption_above_media: Optional[bool] = show_caption_above_media

View file

@ -60,6 +60,9 @@ class InlineQueryResultCachedPhoto(InlineQueryResult):
to the message.
input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the
message to be sent instead of the photo.
show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med|
.. versionadded:: 21.3
Attributes:
type (:obj:`str`): :tg-const:`telegram.constants.InlineQueryResultType.PHOTO`.
@ -83,6 +86,9 @@ class InlineQueryResultCachedPhoto(InlineQueryResult):
to the message.
input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the
message to be sent instead of the photo.
show_caption_above_media (:obj:`bool`): Optional. |show_cap_above_med|
.. versionadded:: 21.3
"""
@ -94,6 +100,7 @@ class InlineQueryResultCachedPhoto(InlineQueryResult):
"parse_mode",
"photo_file_id",
"reply_markup",
"show_caption_above_media",
"title",
)
@ -108,6 +115,7 @@ class InlineQueryResultCachedPhoto(InlineQueryResult):
input_message_content: Optional["InputMessageContent"] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
caption_entities: Optional[Sequence[MessageEntity]] = None,
show_caption_above_media: Optional[bool] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@ -124,3 +132,4 @@ class InlineQueryResultCachedPhoto(InlineQueryResult):
self.caption_entities: Tuple[MessageEntity, ...] = parse_sequence_arg(caption_entities)
self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup
self.input_message_content: Optional[InputMessageContent] = input_message_content
self.show_caption_above_media: Optional[bool] = show_caption_above_media

View file

@ -56,6 +56,9 @@ class InlineQueryResultCachedVideo(InlineQueryResult):
to the message.
input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the
message to be sent instead of the video.
show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med|
.. versionadded:: 21.3
Attributes:
type (:obj:`str`): :tg-const:`telegram.constants.InlineQueryResultType.VIDEO`.
@ -79,6 +82,9 @@ class InlineQueryResultCachedVideo(InlineQueryResult):
to the message.
input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the
message to be sent instead of the video.
show_caption_above_media (:obj:`bool`): Optional. |show_cap_above_med|
.. versionadded:: 21.3
"""
@ -89,6 +95,7 @@ class InlineQueryResultCachedVideo(InlineQueryResult):
"input_message_content",
"parse_mode",
"reply_markup",
"show_caption_above_media",
"title",
"video_file_id",
)
@ -104,6 +111,7 @@ class InlineQueryResultCachedVideo(InlineQueryResult):
input_message_content: Optional["InputMessageContent"] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
caption_entities: Optional[Sequence[MessageEntity]] = None,
show_caption_above_media: Optional[bool] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@ -120,3 +128,4 @@ class InlineQueryResultCachedVideo(InlineQueryResult):
self.caption_entities: Tuple[MessageEntity, ...] = parse_sequence_arg(caption_entities)
self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup
self.input_message_content: Optional[InputMessageContent] = input_message_content
self.show_caption_above_media: Optional[bool] = show_caption_above_media

View file

@ -78,6 +78,9 @@ class InlineQueryResultGif(InlineQueryResult):
to the message.
input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the
message to be sent instead of the GIF animation.
show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med|
.. versionadded:: 21.3
Raises:
:class:`ValueError`: If neither :paramref:`thumbnail_url` nor :paramref:`thumb_url` is
@ -115,6 +118,9 @@ class InlineQueryResultGif(InlineQueryResult):
to the message.
input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the
message to be sent instead of the GIF animation.
show_caption_above_media (:obj:`bool`): Optional. |show_cap_above_med|
.. versionadded:: 21.3
"""
@ -128,6 +134,7 @@ class InlineQueryResultGif(InlineQueryResult):
"input_message_content",
"parse_mode",
"reply_markup",
"show_caption_above_media",
"thumbnail_mime_type",
"thumbnail_url",
"title",
@ -148,6 +155,7 @@ class InlineQueryResultGif(InlineQueryResult):
parse_mode: ODVInput[str] = DEFAULT_NONE,
caption_entities: Optional[Sequence[MessageEntity]] = None,
thumbnail_mime_type: Optional[str] = None,
show_caption_above_media: Optional[bool] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@ -168,3 +176,4 @@ class InlineQueryResultGif(InlineQueryResult):
self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup
self.input_message_content: Optional[InputMessageContent] = input_message_content
self.thumbnail_mime_type: Optional[str] = thumbnail_mime_type
self.show_caption_above_media: Optional[bool] = show_caption_above_media

View file

@ -80,7 +80,9 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult):
to the message.
input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the
message to be sent instead of the video animation.
show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med|
.. versionadded:: 21.3
Raises:
:class:`ValueError`: If neither :paramref:`thumbnail_url` nor :paramref:`thumb_url` is
supplied or if both are supplied and are not equal.
@ -118,7 +120,9 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult):
to the message.
input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the
message to be sent instead of the video animation.
show_caption_above_media (:obj:`bool`): Optional. |show_cap_above_med|
.. versionadded:: 21.3
"""
__slots__ = (
@ -131,6 +135,7 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult):
"mpeg4_width",
"parse_mode",
"reply_markup",
"show_caption_above_media",
"thumbnail_mime_type",
"thumbnail_url",
"title",
@ -151,6 +156,7 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult):
parse_mode: ODVInput[str] = DEFAULT_NONE,
caption_entities: Optional[Sequence[MessageEntity]] = None,
thumbnail_mime_type: Optional[str] = None,
show_caption_above_media: Optional[bool] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@ -171,3 +177,4 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult):
self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup
self.input_message_content: Optional[InputMessageContent] = input_message_content
self.thumbnail_mime_type: Optional[str] = thumbnail_mime_type
self.show_caption_above_media: Optional[bool] = show_caption_above_media

View file

@ -74,6 +74,9 @@ class InlineQueryResultPhoto(InlineQueryResult):
to the message.
input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the
message to be sent instead of the photo.
show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med|
.. versionadded:: 21.3
Raises:
:class:`ValueError`: If neither :paramref:`thumbnail_url` nor :paramref:`thumb_url` is
@ -105,6 +108,9 @@ class InlineQueryResultPhoto(InlineQueryResult):
to the message.
input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the
message to be sent instead of the photo.
show_caption_above_media (:obj:`bool`): Optional. |show_cap_above_med|
.. versionadded:: 21.3
"""
@ -118,6 +124,7 @@ class InlineQueryResultPhoto(InlineQueryResult):
"photo_url",
"photo_width",
"reply_markup",
"show_caption_above_media",
"thumbnail_url",
"title",
)
@ -136,6 +143,7 @@ class InlineQueryResultPhoto(InlineQueryResult):
input_message_content: Optional["InputMessageContent"] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
caption_entities: Optional[Sequence[MessageEntity]] = None,
show_caption_above_media: Optional[bool] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@ -155,3 +163,4 @@ class InlineQueryResultPhoto(InlineQueryResult):
self.caption_entities: Tuple[MessageEntity, ...] = parse_sequence_arg(caption_entities)
self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup
self.input_message_content: Optional[InputMessageContent] = input_message_content
self.show_caption_above_media: Optional[bool] = show_caption_above_media

View file

@ -88,6 +88,9 @@ class InlineQueryResultVideo(InlineQueryResult):
message to be sent instead of the video. This field is required if
``InlineQueryResultVideo`` is used to send an HTML-page as a result
(e.g., a YouTube video).
show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med|
.. versionadded:: 21.3
Raises:
:class:`ValueError`: If neither :paramref:`thumbnail_url` nor :paramref:`thumb_url` is
@ -127,6 +130,9 @@ class InlineQueryResultVideo(InlineQueryResult):
message to be sent instead of the video. This field is required if
``InlineQueryResultVideo`` is used to send an HTML-page as a result
(e.g., a YouTube video).
show_caption_above_media (:obj:`bool`): Optional. |show_cap_above_med|
.. versionadded:: 21.3
"""
@ -138,6 +144,7 @@ class InlineQueryResultVideo(InlineQueryResult):
"mime_type",
"parse_mode",
"reply_markup",
"show_caption_above_media",
"thumbnail_url",
"title",
"video_duration",
@ -162,6 +169,7 @@ class InlineQueryResultVideo(InlineQueryResult):
input_message_content: Optional["InputMessageContent"] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
caption_entities: Optional[Sequence[MessageEntity]] = None,
show_caption_above_media: Optional[bool] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@ -183,3 +191,4 @@ class InlineQueryResultVideo(InlineQueryResult):
self.description: Optional[str] = description
self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup
self.input_message_content: Optional[InputMessageContent] = input_message_content
self.show_caption_above_media: Optional[bool] = show_caption_above_media

View file

@ -49,12 +49,18 @@ class InputInvoiceMessageContent(InputMessageContent):
:tg-const:`telegram.Invoice.MAX_PAYLOAD_LENGTH` bytes. This will not be displayed
to the user, use for your internal processes.
provider_token (:obj:`str`): Payment provider token, obtained via
`@Botfather <https://t.me/Botfather>`_.
`@Botfather <https://t.me/Botfather>`_. Pass an empty string for payments in
|tg_stars|.
.. deprecated:: 21.3
As of Bot API 7.4, this parameter is now optional and future versions of the
library will make it optional as well.
currency (:obj:`str`): Three-letter ISO 4217 currency code, see more on
`currencies <https://core.telegram.org/bots/payments#supported-currencies>`_
`currencies <https://core.telegram.org/bots/payments#supported-currencies>`_.
Pass ``XTR`` for payments in |tg_stars|.
prices (Sequence[:class:`telegram.LabeledPrice`]): Price breakdown, a list of
components (e.g. product price, tax, discount, delivery cost, delivery tax, bonus,
etc.)
etc.). Must contain exactly one item for payments in |tg_stars|.
.. versionchanged:: 20.0
|sequenceclassargs|
@ -64,7 +70,8 @@ class InputInvoiceMessageContent(InputMessageContent):
maximum tip of US$ 1.45 pass ``max_tip_amount = 145``. See the ``exp`` parameter in
`currencies.json <https://core.telegram.org/bots/payments/currencies.json>`_, it
shows the number of digits past the decimal point for each currency (2 for the majority
of currencies). Defaults to ``0``.
of currencies). Defaults to ``0``. Defaults to ``0``. Not supported for payments in
|tg_stars|.
suggested_tip_amounts (Sequence[:obj:`int`], optional): An array of suggested
amounts of tip in the *smallest* units of the currency (integer, **not** float/double).
At most 4 suggested tip amounts can be specified. The suggested tip amounts must be
@ -85,20 +92,20 @@ class InputInvoiceMessageContent(InputMessageContent):
photo_size (:obj:`int`, optional): Photo size.
photo_width (:obj:`int`, optional): Photo width.
photo_height (:obj:`int`, optional): Photo height.
need_name (:obj:`bool`, optional): Pass :obj:`True`, if you require the user's full name to
complete the order.
need_name (:obj:`bool`, optional): Pass :obj:`True`, if you require the user's full
name to complete the order. Ignored for payments in |tg_stars|.
need_phone_number (:obj:`bool`, optional): Pass :obj:`True`, if you require the user's
phone number to complete the order
phone number to complete the order. Ignored for payments in |tg_stars|.
need_email (:obj:`bool`, optional): Pass :obj:`True`, if you require the user's email
address to complete the order.
need_shipping_address (:obj:`bool`, optional): Pass :obj:`True`, if you require the user's
shipping address to complete the order
send_phone_number_to_provider (:obj:`bool`, optional): Pass :obj:`True`, if user's phone
number should be sent to provider.
send_email_to_provider (:obj:`bool`, optional): Pass :obj:`True`, if user's email address
should be sent to provider.
is_flexible (:obj:`bool`, optional): Pass :obj:`True`, if the final price depends on the
shipping method.
address to complete the order. Ignored for payments in |tg_stars|.
need_shipping_address (:obj:`bool`, optional): Pass :obj:`True`, if you require the
user's shipping address to complete the order. Ignored for payments in |tg_stars|
send_phone_number_to_provider (:obj:`bool`, optional): Pass :obj:`True`, if user's
phone number should be sent to provider. Ignored for payments in |tg_stars|.
send_email_to_provider (:obj:`bool`, optional): Pass :obj:`True`, if user's email
address should be sent to provider. Ignored for payments in |tg_stars|.
is_flexible (:obj:`bool`, optional): Pass :obj:`True`, if the final price depends on
the shipping method. Ignored for payments in |tg_stars|.
Attributes:
title (:obj:`str`): Product name. :tg-const:`telegram.Invoice.MIN_TITLE_LENGTH`-
@ -111,12 +118,14 @@ class InputInvoiceMessageContent(InputMessageContent):
:tg-const:`telegram.Invoice.MAX_PAYLOAD_LENGTH` bytes. This will not be displayed
to the user, use for your internal processes.
provider_token (:obj:`str`): Payment provider token, obtained via
`@Botfather <https://t.me/Botfather>`_.
`@Botfather <https://t.me/Botfather>`_. Pass an empty string for payments in `Telegram
Stars <https://t.me/BotNews/90>`_.
currency (:obj:`str`): Three-letter ISO 4217 currency code, see more on
`currencies <https://core.telegram.org/bots/payments#supported-currencies>`_
`currencies <https://core.telegram.org/bots/payments#supported-currencies>`_.
Pass ``XTR`` for payments in |tg_stars|.
prices (Tuple[:class:`telegram.LabeledPrice`]): Price breakdown, a list of
components (e.g. product price, tax, discount, delivery cost, delivery tax, bonus,
etc.)
etc.). Must contain exactly one item for payments in |tg_stars|.
.. versionchanged:: 20.0
|tupleclassattrs|
@ -126,7 +135,7 @@ class InputInvoiceMessageContent(InputMessageContent):
maximum tip of US$ 1.45 ``max_tip_amount`` is ``145``. See the ``exp`` parameter in
`currencies.json <https://core.telegram.org/bots/payments/currencies.json>`_, it
shows the number of digits past the decimal point for each currency (2 for the majority
of currencies). Defaults to ``0``.
of currencies). Defaults to ``0``. Not supported for payments in |tg_stars|.
suggested_tip_amounts (Tuple[:obj:`int`]): Optional. An array of suggested
amounts of tip in the *smallest* units of the currency (integer, **not** float/double).
At most 4 suggested tip amounts can be specified. The suggested tip amounts must be
@ -146,19 +155,19 @@ class InputInvoiceMessageContent(InputMessageContent):
photo_width (:obj:`int`): Optional. Photo width.
photo_height (:obj:`int`): Optional. Photo height.
need_name (:obj:`bool`): Optional. Pass :obj:`True`, if you require the user's full name to
complete the order.
complete the order. Ignored for payments in |tg_stars|.
need_phone_number (:obj:`bool`): Optional. Pass :obj:`True`, if you require the user's
phone number to complete the order
phone number to complete the order. Ignored for payments in |tg_stars|.
need_email (:obj:`bool`): Optional. Pass :obj:`True`, if you require the user's email
address to complete the order.
address to complete the order. Ignored for payments in |tg_stars|.
need_shipping_address (:obj:`bool`): Optional. Pass :obj:`True`, if you require the user's
shipping address to complete the order
shipping address to complete the order. Ignored for payments in |tg_stars|.
send_phone_number_to_provider (:obj:`bool`): Optional. Pass :obj:`True`, if user's phone
number should be sent to provider.
number should be sent to provider. Ignored for payments in |tg_stars|.
send_email_to_provider (:obj:`bool`): Optional. Pass :obj:`True`, if user's email address
should be sent to provider.
should be sent to provider. Ignored for payments in |tg_stars|.
is_flexible (:obj:`bool`): Optional. Pass :obj:`True`, if the final price depends on the
shipping method.
shipping method. Ignored for payments in |tg_stars|.
"""
@ -190,7 +199,7 @@ class InputInvoiceMessageContent(InputMessageContent):
title: str,
description: str,
payload: str,
provider_token: str,
provider_token: Optional[str], # This arg is now optional since Bot API 7.4
currency: str,
prices: Sequence[LabeledPrice],
max_tip_amount: Optional[int] = None,
@ -216,7 +225,7 @@ class InputInvoiceMessageContent(InputMessageContent):
self.title: str = title
self.description: str = description
self.payload: str = payload
self.provider_token: str = provider_token
self.provider_token: Optional[str] = provider_token
self.currency: str = currency
self.prices: Tuple[LabeledPrice, ...] = parse_sequence_arg(prices)
# Optionals

View file

@ -108,7 +108,7 @@ class InputTextMessageContent(InputMessageContent):
# Optionals
self.parse_mode: ODVInput[str] = parse_mode
self.entities: Tuple[MessageEntity, ...] = parse_sequence_arg(entities)
self.link_preview_options: ODVInput["LinkPreviewOptions"] = parse_lpo_and_dwpp(
self.link_preview_options: ODVInput[LinkPreviewOptions] = parse_lpo_and_dwpp(
disable_web_page_preview, link_preview_options
)

View file

@ -32,7 +32,8 @@ if TYPE_CHECKING:
class KeyboardButton(TelegramObject):
"""
This object represents one button of the reply keyboard. For simple text buttons, :obj:`str`
This object represents one button of the reply keyboard. At most one of the optional fields
must be used to specify type of the button. For simple text buttons, :obj:`str`
can be used instead of this object to specify text of the button.
Objects of this class are comparable in terms of equality. Two objects of this class are

View file

@ -255,8 +255,8 @@ class Message(MaybeInaccessibleMessage):
.. versionchanged:: 20.8
* This class is now a subclass of :class:`telegram.MaybeInaccessibleMessage`.
* The :paramref:`pinned_message` now can be either class:`telegram.Message` or
class:`telegram.InaccessibleMessage`.
* The :paramref:`pinned_message` now can be either :class:`telegram.Message` or
:class:`telegram.InaccessibleMessage`.
.. versionchanged:: 20.0
@ -328,6 +328,11 @@ class Message(MaybeInaccessibleMessage):
.. versionadded:: 20.8
effect_id (:obj:`str`, optional): Unique identifier of the message effect added to the
message.
..versionadded:: 21.3
caption_entities (Sequence[:class:`telegram.MessageEntity`], optional): For messages with a
Caption. Special entities like usernames, URLs, bot commands, etc. that appear in the
caption. See :attr:`Message.parse_caption_entity` and :attr:`parse_caption_entities`
@ -337,6 +342,9 @@ class Message(MaybeInaccessibleMessage):
.. versionchanged:: 20.0
|sequenceclassargs|
show_caption_above_media (:obj:`bool`, optional): |show_cap_above_med|
.. versionadded:: 21.3
audio (:class:`telegram.Audio`, optional): Message is an audio file, information
about the file.
document (:class:`telegram.Document`, optional): Message is a general file, information
@ -411,8 +419,8 @@ class Message(MaybeInaccessibleMessage):
:attr:`reply_to_message` fields even if it is itself a reply.
.. versionchanged:: 20.8
This attribute now is either class:`telegram.Message` or
class:`telegram.InaccessibleMessage`.
This attribute now is either :class:`telegram.Message` or
:class:`telegram.InaccessibleMessage`.
invoice (:class:`telegram.Invoice`, optional): Message is an invoice for a payment,
information about the invoice.
successful_payment (:class:`telegram.SuccessfulPayment`, optional): Message is a service
@ -617,6 +625,11 @@ class Message(MaybeInaccessibleMessage):
.. versionadded:: 20.8
effect_id (:obj:`str`): Optional. Unique identifier of the message effect added to the
message.
..versionadded:: 21.3
caption_entities (Tuple[:class:`telegram.MessageEntity`]): Optional. For messages with a
Caption. Special entities like usernames, URLs, bot commands, etc. that appear in the
caption. See :attr:`Message.parse_caption_entity` and :attr:`parse_caption_entities`
@ -626,6 +639,9 @@ class Message(MaybeInaccessibleMessage):
.. versionchanged:: 20.0
|tupleclassattrs|
show_caption_above_media (:obj:`bool`): Optional. |show_cap_above_med|
.. versionadded:: 21.3
audio (:class:`telegram.Audio`): Optional. Message is an audio file, information
about the file.
@ -715,8 +731,8 @@ class Message(MaybeInaccessibleMessage):
:attr:`reply_to_message` fields even if it is itself a reply.
.. versionchanged:: 20.8
This attribute now is either class:`telegram.Message` or
class:`telegram.InaccessibleMessage`.
This attribute now is either :class:`telegram.Message` or
:class:`telegram.InaccessibleMessage`.
invoice (:class:`telegram.Invoice`): Optional. Message is an invoice for a payment,
information about the invoice.
successful_payment (:class:`telegram.SuccessfulPayment`): Optional. Message is a service
@ -897,6 +913,7 @@ class Message(MaybeInaccessibleMessage):
"dice",
"document",
"edit_date",
"effect_id",
"entities",
"external_reply",
"forum_topic_closed",
@ -942,6 +959,7 @@ class Message(MaybeInaccessibleMessage):
"sender_boost_count",
"sender_business_bot",
"sender_chat",
"show_caption_above_media",
"sticker",
"story",
"successful_payment",
@ -1044,6 +1062,8 @@ class Message(MaybeInaccessibleMessage):
sender_business_bot: Optional[User] = None,
is_from_offline: Optional[bool] = None,
chat_background_set: Optional[ChatBackground] = None,
effect_id: Optional[str] = None,
show_caption_above_media: Optional[bool] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@ -1143,6 +1163,8 @@ class Message(MaybeInaccessibleMessage):
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.effect_id: Optional[str] = effect_id
self.show_caption_above_media: Optional[bool] = show_caption_above_media
self._effective_attachment = DEFAULT_NONE
@ -1634,6 +1656,7 @@ class Message(MaybeInaccessibleMessage):
message_thread_id: ODVInput[int] = DEFAULT_NONE,
link_preview_options: ODVInput["LinkPreviewOptions"] = DEFAULT_NONE,
reply_parameters: Optional["ReplyParameters"] = None,
message_effect_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -1698,6 +1721,7 @@ class Message(MaybeInaccessibleMessage):
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=self.business_connection_id,
message_effect_id=message_effect_id,
)
async def reply_markdown(
@ -1710,6 +1734,7 @@ class Message(MaybeInaccessibleMessage):
message_thread_id: ODVInput[int] = DEFAULT_NONE,
link_preview_options: ODVInput["LinkPreviewOptions"] = DEFAULT_NONE,
reply_parameters: Optional["ReplyParameters"] = None,
message_effect_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -1780,6 +1805,7 @@ class Message(MaybeInaccessibleMessage):
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=self.business_connection_id,
message_effect_id=message_effect_id,
)
async def reply_markdown_v2(
@ -1792,6 +1818,7 @@ class Message(MaybeInaccessibleMessage):
message_thread_id: ODVInput[int] = DEFAULT_NONE,
link_preview_options: ODVInput["LinkPreviewOptions"] = DEFAULT_NONE,
reply_parameters: Optional["ReplyParameters"] = None,
message_effect_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -1858,6 +1885,7 @@ class Message(MaybeInaccessibleMessage):
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=self.business_connection_id,
message_effect_id=message_effect_id,
)
async def reply_html(
@ -1870,6 +1898,7 @@ class Message(MaybeInaccessibleMessage):
message_thread_id: ODVInput[int] = DEFAULT_NONE,
link_preview_options: ODVInput["LinkPreviewOptions"] = DEFAULT_NONE,
reply_parameters: Optional["ReplyParameters"] = None,
message_effect_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -1936,6 +1965,7 @@ class Message(MaybeInaccessibleMessage):
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=self.business_connection_id,
message_effect_id=message_effect_id,
)
async def reply_media_group(
@ -1947,6 +1977,7 @@ class Message(MaybeInaccessibleMessage):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: ODVInput[int] = DEFAULT_NONE,
reply_parameters: Optional["ReplyParameters"] = None,
message_effect_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -2013,6 +2044,7 @@ class Message(MaybeInaccessibleMessage):
parse_mode=parse_mode,
caption_entities=caption_entities,
business_connection_id=self.business_connection_id,
message_effect_id=message_effect_id,
)
async def reply_photo(
@ -2027,6 +2059,8 @@ class Message(MaybeInaccessibleMessage):
message_thread_id: ODVInput[int] = DEFAULT_NONE,
has_spoiler: Optional[bool] = None,
reply_parameters: Optional["ReplyParameters"] = None,
message_effect_id: Optional[str] = None,
show_caption_above_media: Optional[bool] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -2092,6 +2126,8 @@ class Message(MaybeInaccessibleMessage):
api_kwargs=api_kwargs,
has_spoiler=has_spoiler,
business_connection_id=self.business_connection_id,
message_effect_id=message_effect_id,
show_caption_above_media=show_caption_above_media,
)
async def reply_audio(
@ -2109,6 +2145,7 @@ class Message(MaybeInaccessibleMessage):
message_thread_id: ODVInput[int] = DEFAULT_NONE,
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
message_effect_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -2177,6 +2214,7 @@ class Message(MaybeInaccessibleMessage):
api_kwargs=api_kwargs,
thumbnail=thumbnail,
business_connection_id=self.business_connection_id,
message_effect_id=message_effect_id,
)
async def reply_document(
@ -2192,6 +2230,7 @@ class Message(MaybeInaccessibleMessage):
message_thread_id: ODVInput[int] = DEFAULT_NONE,
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
message_effect_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -2258,6 +2297,7 @@ class Message(MaybeInaccessibleMessage):
message_thread_id=message_thread_id,
thumbnail=thumbnail,
business_connection_id=self.business_connection_id,
message_effect_id=message_effect_id,
)
async def reply_animation(
@ -2276,6 +2316,8 @@ class Message(MaybeInaccessibleMessage):
has_spoiler: Optional[bool] = None,
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
message_effect_id: Optional[str] = None,
show_caption_above_media: Optional[bool] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -2345,6 +2387,8 @@ class Message(MaybeInaccessibleMessage):
has_spoiler=has_spoiler,
thumbnail=thumbnail,
business_connection_id=self.business_connection_id,
message_effect_id=message_effect_id,
show_caption_above_media=show_caption_above_media,
)
async def reply_sticker(
@ -2356,6 +2400,7 @@ class Message(MaybeInaccessibleMessage):
message_thread_id: ODVInput[int] = DEFAULT_NONE,
emoji: Optional[str] = None,
reply_parameters: Optional["ReplyParameters"] = None,
message_effect_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -2416,6 +2461,7 @@ class Message(MaybeInaccessibleMessage):
message_thread_id=message_thread_id,
emoji=emoji,
business_connection_id=self.business_connection_id,
message_effect_id=message_effect_id,
)
async def reply_video(
@ -2435,6 +2481,8 @@ class Message(MaybeInaccessibleMessage):
has_spoiler: Optional[bool] = None,
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
message_effect_id: Optional[str] = None,
show_caption_above_media: Optional[bool] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -2505,6 +2553,8 @@ class Message(MaybeInaccessibleMessage):
has_spoiler=has_spoiler,
thumbnail=thumbnail,
business_connection_id=self.business_connection_id,
message_effect_id=message_effect_id,
show_caption_above_media=show_caption_above_media,
)
async def reply_video_note(
@ -2518,6 +2568,7 @@ class Message(MaybeInaccessibleMessage):
message_thread_id: ODVInput[int] = DEFAULT_NONE,
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
message_effect_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -2582,6 +2633,7 @@ class Message(MaybeInaccessibleMessage):
message_thread_id=message_thread_id,
thumbnail=thumbnail,
business_connection_id=self.business_connection_id,
message_effect_id=message_effect_id,
)
async def reply_voice(
@ -2596,6 +2648,7 @@ class Message(MaybeInaccessibleMessage):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: ODVInput[int] = DEFAULT_NONE,
reply_parameters: Optional["ReplyParameters"] = None,
message_effect_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -2661,6 +2714,7 @@ class Message(MaybeInaccessibleMessage):
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=self.business_connection_id,
message_effect_id=message_effect_id,
)
async def reply_location(
@ -2676,6 +2730,7 @@ class Message(MaybeInaccessibleMessage):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: ODVInput[int] = DEFAULT_NONE,
reply_parameters: Optional["ReplyParameters"] = None,
message_effect_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -2742,6 +2797,7 @@ class Message(MaybeInaccessibleMessage):
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=self.business_connection_id,
message_effect_id=message_effect_id,
)
async def reply_venue(
@ -2759,6 +2815,7 @@ class Message(MaybeInaccessibleMessage):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: ODVInput[int] = DEFAULT_NONE,
reply_parameters: Optional["ReplyParameters"] = None,
message_effect_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -2827,6 +2884,7 @@ class Message(MaybeInaccessibleMessage):
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=self.business_connection_id,
message_effect_id=message_effect_id,
)
async def reply_contact(
@ -2840,6 +2898,7 @@ class Message(MaybeInaccessibleMessage):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: ODVInput[int] = DEFAULT_NONE,
reply_parameters: Optional["ReplyParameters"] = None,
message_effect_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -2904,6 +2963,7 @@ class Message(MaybeInaccessibleMessage):
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=self.business_connection_id,
message_effect_id=message_effect_id,
)
async def reply_poll(
@ -2927,6 +2987,7 @@ class Message(MaybeInaccessibleMessage):
reply_parameters: Optional["ReplyParameters"] = None,
question_parse_mode: ODVInput[str] = DEFAULT_NONE,
question_entities: Optional[Sequence["MessageEntity"]] = None,
message_effect_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -2999,6 +3060,7 @@ class Message(MaybeInaccessibleMessage):
business_connection_id=self.business_connection_id,
question_parse_mode=question_parse_mode,
question_entities=question_entities,
message_effect_id=message_effect_id,
)
async def reply_dice(
@ -3009,6 +3071,7 @@ class Message(MaybeInaccessibleMessage):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: ODVInput[int] = DEFAULT_NONE,
reply_parameters: Optional["ReplyParameters"] = None,
message_effect_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -3068,6 +3131,7 @@ class Message(MaybeInaccessibleMessage):
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=self.business_connection_id,
message_effect_id=message_effect_id,
)
async def reply_chat_action(
@ -3122,6 +3186,7 @@ class Message(MaybeInaccessibleMessage):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: ODVInput[int] = DEFAULT_NONE,
reply_parameters: Optional["ReplyParameters"] = None,
message_effect_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -3183,6 +3248,7 @@ class Message(MaybeInaccessibleMessage):
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=self.business_connection_id,
message_effect_id=message_effect_id,
)
async def reply_invoice(
@ -3190,7 +3256,7 @@ class Message(MaybeInaccessibleMessage):
title: str,
description: str,
payload: str,
provider_token: str,
provider_token: Optional[str],
currency: str,
prices: Sequence["LabeledPrice"],
start_parameter: Optional[str] = None,
@ -3213,6 +3279,7 @@ class Message(MaybeInaccessibleMessage):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: ODVInput[int] = DEFAULT_NONE,
reply_parameters: Optional["ReplyParameters"] = None,
message_effect_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -3302,6 +3369,7 @@ class Message(MaybeInaccessibleMessage):
suggested_tip_amounts=suggested_tip_amounts,
protect_content=protect_content,
message_thread_id=message_thread_id,
message_effect_id=message_effect_id,
)
async def forward(
@ -3331,7 +3399,7 @@ class Message(MaybeInaccessibleMessage):
Note:
Since the release of Bot API 5.5 it can be impossible to forward messages from
some chats. Use the attributes :attr:`telegram.Message.has_protected_content` and
:attr:`telegram.Chat.has_protected_content` to check this.
:attr:`telegram.ChatFullInfo.has_protected_content` to check this.
As a workaround, it is still possible to use :meth:`copy`. However, this
behaviour is undocumented and might be changed by Telegram.
@ -3365,6 +3433,7 @@ class Message(MaybeInaccessibleMessage):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
show_caption_above_media: Optional[bool] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -3409,6 +3478,7 @@ class Message(MaybeInaccessibleMessage):
api_kwargs=api_kwargs,
protect_content=protect_content,
message_thread_id=message_thread_id,
show_caption_above_media=show_caption_above_media,
)
async def reply_copy(
@ -3423,6 +3493,7 @@ class Message(MaybeInaccessibleMessage):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: ODVInput[int] = DEFAULT_NONE,
reply_parameters: Optional["ReplyParameters"] = None,
show_caption_above_media: Optional[bool] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -3486,6 +3557,7 @@ class Message(MaybeInaccessibleMessage):
api_kwargs=api_kwargs,
protect_content=protect_content,
message_thread_id=message_thread_id,
show_caption_above_media=show_caption_above_media,
)
async def edit_text(
@ -3544,6 +3616,7 @@ class Message(MaybeInaccessibleMessage):
reply_markup: Optional["InlineKeyboardMarkup"] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
caption_entities: Optional[Sequence["MessageEntity"]] = None,
show_caption_above_media: Optional[bool] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@ -3583,6 +3656,7 @@ class Message(MaybeInaccessibleMessage):
api_kwargs=api_kwargs,
caption_entities=caption_entities,
inline_message_id=None,
show_caption_above_media=show_caption_above_media,
)
async def edit_media(
@ -4333,6 +4407,8 @@ class Message(MaybeInaccessibleMessage):
insert = f'<a href="{escaped_text}">{escaped_text}</a>'
elif entity.type == MessageEntity.BLOCKQUOTE:
insert = f"<blockquote>{escaped_text}</blockquote>"
elif entity.type == MessageEntity.EXPANDABLE_BLOCKQUOTE:
insert = f"<blockquote expandable>{escaped_text}</blockquote>"
elif entity.type == MessageEntity.BOLD:
insert = f"<b>{escaped_text}</b>"
elif entity.type == MessageEntity.ITALIC:
@ -4483,11 +4559,12 @@ class Message(MaybeInaccessibleMessage):
) -> Optional[str]:
if version == 1:
for entity_type in (
MessageEntity.UNDERLINE,
MessageEntity.STRIKETHROUGH,
MessageEntity.SPOILER,
MessageEntity.EXPANDABLE_BLOCKQUOTE,
MessageEntity.BLOCKQUOTE,
MessageEntity.CUSTOM_EMOJI,
MessageEntity.SPOILER,
MessageEntity.STRIKETHROUGH,
MessageEntity.UNDERLINE,
):
if any(entity.type == entity_type for entity in entities):
name = entity_type.name.title().replace("_", " ") # type:ignore[attr-defined]
@ -4567,8 +4644,10 @@ class Message(MaybeInaccessibleMessage):
insert = f"~{escaped_text}~"
elif entity.type == MessageEntity.SPOILER:
insert = f"||{escaped_text}||"
elif entity.type == MessageEntity.BLOCKQUOTE:
elif entity.type in (MessageEntity.BLOCKQUOTE, MessageEntity.EXPANDABLE_BLOCKQUOTE):
insert = ">" + "\n>".join(escaped_text.splitlines())
if entity.type == MessageEntity.EXPANDABLE_BLOCKQUOTE:
insert = f"{insert}||"
elif entity.type == MessageEntity.CUSTOM_EMOJI:
# This should never be needed because ids are numeric but the documentation
# specifically mentions it so here we are

View file

@ -140,50 +140,55 @@ class MessageEntity(TelegramObject):
return super().de_json(data=data, bot=bot)
MENTION: Final[str] = constants.MessageEntityType.MENTION
""":const:`telegram.constants.MessageEntityType.MENTION`"""
HASHTAG: Final[str] = constants.MessageEntityType.HASHTAG
""":const:`telegram.constants.MessageEntityType.HASHTAG`"""
CASHTAG: Final[str] = constants.MessageEntityType.CASHTAG
""":const:`telegram.constants.MessageEntityType.CASHTAG`"""
PHONE_NUMBER: Final[str] = constants.MessageEntityType.PHONE_NUMBER
""":const:`telegram.constants.MessageEntityType.PHONE_NUMBER`"""
BOT_COMMAND: Final[str] = constants.MessageEntityType.BOT_COMMAND
""":const:`telegram.constants.MessageEntityType.BOT_COMMAND`"""
URL: Final[str] = constants.MessageEntityType.URL
""":const:`telegram.constants.MessageEntityType.URL`"""
EMAIL: Final[str] = constants.MessageEntityType.EMAIL
""":const:`telegram.constants.MessageEntityType.EMAIL`"""
ALL_TYPES: Final[List[str]] = list(constants.MessageEntityType)
"""List[:obj:`str`]: A list of all available message entity types."""
BLOCKQUOTE: Final[str] = constants.MessageEntityType.BLOCKQUOTE
""":const:`telegram.constants.MessageEntityType.BLOCKQUOTE`
.. versionadded:: 20.8
"""
BOLD: Final[str] = constants.MessageEntityType.BOLD
""":const:`telegram.constants.MessageEntityType.BOLD`"""
ITALIC: Final[str] = constants.MessageEntityType.ITALIC
""":const:`telegram.constants.MessageEntityType.ITALIC`"""
BOT_COMMAND: Final[str] = constants.MessageEntityType.BOT_COMMAND
""":const:`telegram.constants.MessageEntityType.BOT_COMMAND`"""
CASHTAG: Final[str] = constants.MessageEntityType.CASHTAG
""":const:`telegram.constants.MessageEntityType.CASHTAG`"""
CODE: Final[str] = constants.MessageEntityType.CODE
""":const:`telegram.constants.MessageEntityType.CODE`"""
CUSTOM_EMOJI: Final[str] = constants.MessageEntityType.CUSTOM_EMOJI
""":const:`telegram.constants.MessageEntityType.CUSTOM_EMOJI`
.. versionadded:: 20.0
"""
EMAIL: Final[str] = constants.MessageEntityType.EMAIL
""":const:`telegram.constants.MessageEntityType.EMAIL`"""
EXPANDABLE_BLOCKQUOTE: Final[str] = constants.MessageEntityType.EXPANDABLE_BLOCKQUOTE
""":const:`telegram.constants.MessageEntityType.EXPANDABLE_BLOCKQUOTE`
.. versionadded:: 21.3
"""
HASHTAG: Final[str] = constants.MessageEntityType.HASHTAG
""":const:`telegram.constants.MessageEntityType.HASHTAG`"""
ITALIC: Final[str] = constants.MessageEntityType.ITALIC
""":const:`telegram.constants.MessageEntityType.ITALIC`"""
MENTION: Final[str] = constants.MessageEntityType.MENTION
""":const:`telegram.constants.MessageEntityType.MENTION`"""
PHONE_NUMBER: Final[str] = constants.MessageEntityType.PHONE_NUMBER
""":const:`telegram.constants.MessageEntityType.PHONE_NUMBER`"""
PRE: Final[str] = constants.MessageEntityType.PRE
""":const:`telegram.constants.MessageEntityType.PRE`"""
SPOILER: Final[str] = constants.MessageEntityType.SPOILER
""":const:`telegram.constants.MessageEntityType.SPOILER`
.. versionadded:: 13.10
"""
STRIKETHROUGH: Final[str] = constants.MessageEntityType.STRIKETHROUGH
""":const:`telegram.constants.MessageEntityType.STRIKETHROUGH`"""
TEXT_LINK: Final[str] = constants.MessageEntityType.TEXT_LINK
""":const:`telegram.constants.MessageEntityType.TEXT_LINK`"""
TEXT_MENTION: Final[str] = constants.MessageEntityType.TEXT_MENTION
""":const:`telegram.constants.MessageEntityType.TEXT_MENTION`"""
UNDERLINE: Final[str] = constants.MessageEntityType.UNDERLINE
""":const:`telegram.constants.MessageEntityType.UNDERLINE`"""
STRIKETHROUGH: Final[str] = constants.MessageEntityType.STRIKETHROUGH
""":const:`telegram.constants.MessageEntityType.STRIKETHROUGH`"""
SPOILER: Final[str] = constants.MessageEntityType.SPOILER
""":const:`telegram.constants.MessageEntityType.SPOILER`
.. versionadded:: 13.10
"""
CUSTOM_EMOJI: Final[str] = constants.MessageEntityType.CUSTOM_EMOJI
""":const:`telegram.constants.MessageEntityType.CUSTOM_EMOJI`
.. versionadded:: 20.0
"""
BLOCKQUOTE: Final[str] = constants.MessageEntityType.BLOCKQUOTE
""":const:`telegram.constants.MessageEntityType.BLOCKQUOTE`
.. versionadded:: 20.8
"""
ALL_TYPES: Final[List[str]] = list(constants.MessageEntityType)
"""List[:obj:`str`]: A list of all available message entity types."""
URL: Final[str] = constants.MessageEntityType.URL
""":const:`telegram.constants.MessageEntityType.URL`"""

View file

@ -37,7 +37,8 @@ class Invoice(TelegramObject):
description (:obj:`str`): Product description.
start_parameter (:obj:`str`): Unique bot deep-linking parameter that can be used to
generate this invoice.
currency (:obj:`str`): Three-letter ISO 4217 currency code.
currency (:obj:`str`): Three-letter ISO 4217 currency code, or ``XTR`` for payments in
|tg_stars|.
total_amount (:obj:`int`): Total price in the smallest units of the currency (integer, not
float/double). For example, for a price of US$ 1.45 pass ``amount = 145``. See the
``exp`` parameter in
@ -50,7 +51,8 @@ class Invoice(TelegramObject):
description (:obj:`str`): Product description.
start_parameter (:obj:`str`): Unique bot deep-linking parameter that can be used to
generate this invoice.
currency (:obj:`str`): Three-letter ISO 4217 currency code.
currency (:obj:`str`): Three-letter ISO 4217 currency code, or ``XTR`` for payments in
|tg_stars|.
total_amount (:obj:`int`): Total price in the smallest units of the currency (integer, not
float/double). For example, for a price of US$ 1.45 ``amount`` is ``145``. See the
``exp`` parameter in

View file

@ -42,7 +42,8 @@ class PreCheckoutQuery(TelegramObject):
Args:
id (:obj:`str`): Unique query identifier.
from_user (:class:`telegram.User`): User who sent the query.
currency (:obj:`str`): Three-letter ISO 4217 currency code.
currency (:obj:`str`): Three-letter ISO 4217 currency code, or ``XTR`` for payments in
|tg_stars|.
total_amount (:obj:`int`): Total price in the smallest units of the currency (integer, not
float/double). For example, for a price of US$ 1.45 pass ``amount = 145``.
See the ``exp`` parameter in
@ -57,7 +58,8 @@ class PreCheckoutQuery(TelegramObject):
Attributes:
id (:obj:`str`): Unique query identifier.
from_user (:class:`telegram.User`): User who sent the query.
currency (:obj:`str`): Three-letter ISO 4217 currency code.
currency (:obj:`str`): Three-letter ISO 4217 currency code, or ``XTR`` for payments in
|tg_stars|.
total_amount (:obj:`int`): Total price in the smallest units of the currency (integer, not
float/double). For example, for a price of US$ 1.45 ``amount`` is ``145``.
See the ``exp`` parameter in

View file

@ -36,7 +36,8 @@ class SuccessfulPayment(TelegramObject):
:attr:`provider_payment_charge_id` are equal.
Args:
currency (:obj:`str`): Three-letter ISO 4217 currency code.
currency (:obj:`str`): Three-letter ISO 4217 currency code, or ``XTR`` for payments in
|tg_stars|.
total_amount (:obj:`int`): Total price in the smallest units of the currency (integer, not
float/double). For example, for a price of US$ 1.45 pass ``amount = 145``.
See the ``exp`` parameter in
@ -51,7 +52,8 @@ class SuccessfulPayment(TelegramObject):
provider_payment_charge_id (:obj:`str`): Provider payment identifier.
Attributes:
currency (:obj:`str`): Three-letter ISO 4217 currency code.
currency (:obj:`str`): Three-letter ISO 4217 currency code, or ``XTR`` for payments in
|tg_stars|.
total_amount (:obj:`int`): Total price in the smallest units of the currency (integer, not
float/double). For example, for a price of US$ 1.45 ``amount`` is ``145``.
See the ``exp`` parameter in

View file

@ -446,7 +446,7 @@ class Update(TelegramObject):
)
self._effective_user: Optional[User] = None
self._effective_sender: Optional[Union["User", "Chat"]] = None
self._effective_sender: Optional[Union[User, Chat]] = None
self._effective_chat: Optional[Chat] = None
self._effective_message: Optional[Message] = None
@ -568,7 +568,7 @@ class Update(TelegramObject):
if self._effective_sender:
return self._effective_sender
sender: Optional[Union["User", "Chat"]] = None
sender: Optional[Union[User, Chat]] = None
if message := (
self.message

View file

@ -409,6 +409,7 @@ class User(TelegramObject):
link_preview_options: ODVInput["LinkPreviewOptions"] = DEFAULT_NONE,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
message_effect_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
disable_web_page_preview: Optional[bool] = None,
@ -452,6 +453,7 @@ class User(TelegramObject):
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=business_connection_id,
message_effect_id=message_effect_id,
)
async def delete_message(
@ -531,6 +533,8 @@ class User(TelegramObject):
has_spoiler: Optional[bool] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
message_effect_id: Optional[str] = None,
show_caption_above_media: Optional[bool] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -575,6 +579,8 @@ class User(TelegramObject):
api_kwargs=api_kwargs,
has_spoiler=has_spoiler,
business_connection_id=business_connection_id,
message_effect_id=message_effect_id,
show_caption_above_media=show_caption_above_media,
)
async def send_media_group(
@ -587,6 +593,7 @@ class User(TelegramObject):
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
message_effect_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -631,6 +638,7 @@ class User(TelegramObject):
parse_mode=parse_mode,
caption_entities=caption_entities,
business_connection_id=business_connection_id,
message_effect_id=message_effect_id,
)
async def send_audio(
@ -649,6 +657,7 @@ class User(TelegramObject):
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
message_effect_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -696,6 +705,7 @@ class User(TelegramObject):
api_kwargs=api_kwargs,
thumbnail=thumbnail,
business_connection_id=business_connection_id,
message_effect_id=message_effect_id,
)
async def send_chat_action(
@ -750,6 +760,7 @@ class User(TelegramObject):
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
message_effect_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -793,6 +804,7 @@ class User(TelegramObject):
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=business_connection_id,
message_effect_id=message_effect_id,
)
async def send_dice(
@ -804,6 +816,7 @@ class User(TelegramObject):
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
message_effect_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -842,6 +855,7 @@ class User(TelegramObject):
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=business_connection_id,
message_effect_id=message_effect_id,
)
async def send_document(
@ -858,6 +872,7 @@ class User(TelegramObject):
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
message_effect_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -903,6 +918,7 @@ class User(TelegramObject):
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=business_connection_id,
message_effect_id=message_effect_id,
)
async def send_game(
@ -914,6 +930,7 @@ class User(TelegramObject):
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
message_effect_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -952,6 +969,7 @@ class User(TelegramObject):
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=business_connection_id,
message_effect_id=message_effect_id,
)
async def send_invoice(
@ -959,7 +977,7 @@ class User(TelegramObject):
title: str,
description: str,
payload: str,
provider_token: str,
provider_token: Optional[str],
currency: str,
prices: Sequence["LabeledPrice"],
start_parameter: Optional[str] = None,
@ -982,6 +1000,7 @@ class User(TelegramObject):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
message_effect_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -1049,6 +1068,7 @@ class User(TelegramObject):
suggested_tip_amounts=suggested_tip_amounts,
protect_content=protect_content,
message_thread_id=message_thread_id,
message_effect_id=message_effect_id,
)
async def send_location(
@ -1065,6 +1085,7 @@ class User(TelegramObject):
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
message_effect_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -1110,6 +1131,7 @@ class User(TelegramObject):
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=business_connection_id,
message_effect_id=message_effect_id,
)
async def send_animation(
@ -1129,6 +1151,8 @@ class User(TelegramObject):
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
message_effect_id: Optional[str] = None,
show_caption_above_media: Optional[bool] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -1177,6 +1201,8 @@ class User(TelegramObject):
has_spoiler=has_spoiler,
thumbnail=thumbnail,
business_connection_id=business_connection_id,
message_effect_id=message_effect_id,
show_caption_above_media=show_caption_above_media,
)
async def send_sticker(
@ -1189,6 +1215,7 @@ class User(TelegramObject):
emoji: Optional[str] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
message_effect_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -1228,6 +1255,7 @@ class User(TelegramObject):
message_thread_id=message_thread_id,
emoji=emoji,
business_connection_id=business_connection_id,
message_effect_id=message_effect_id,
)
async def send_video(
@ -1248,6 +1276,8 @@ class User(TelegramObject):
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
message_effect_id: Optional[str] = None,
show_caption_above_media: Optional[bool] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -1297,6 +1327,8 @@ class User(TelegramObject):
message_thread_id=message_thread_id,
has_spoiler=has_spoiler,
business_connection_id=business_connection_id,
message_effect_id=message_effect_id,
show_caption_above_media=show_caption_above_media,
)
async def send_venue(
@ -1315,6 +1347,7 @@ class User(TelegramObject):
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
message_effect_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -1362,6 +1395,7 @@ class User(TelegramObject):
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=business_connection_id,
message_effect_id=message_effect_id,
)
async def send_video_note(
@ -1376,6 +1410,7 @@ class User(TelegramObject):
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
message_effect_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -1419,6 +1454,7 @@ class User(TelegramObject):
message_thread_id=message_thread_id,
thumbnail=thumbnail,
business_connection_id=business_connection_id,
message_effect_id=message_effect_id,
)
async def send_voice(
@ -1434,6 +1470,7 @@ class User(TelegramObject):
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
message_effect_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -1478,6 +1515,7 @@ class User(TelegramObject):
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=business_connection_id,
message_effect_id=message_effect_id,
)
async def send_poll(
@ -1502,6 +1540,7 @@ class User(TelegramObject):
business_connection_id: Optional[str] = None,
question_parse_mode: ODVInput[str] = DEFAULT_NONE,
question_entities: Optional[Sequence["MessageEntity"]] = None,
message_effect_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -1553,6 +1592,7 @@ class User(TelegramObject):
business_connection_id=business_connection_id,
question_parse_mode=question_parse_mode,
question_entities=question_entities,
message_effect_id=message_effect_id,
)
async def send_copy(
@ -1567,6 +1607,7 @@ class User(TelegramObject):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
show_caption_above_media: Optional[bool] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -1608,6 +1649,7 @@ class User(TelegramObject):
api_kwargs=api_kwargs,
protect_content=protect_content,
message_thread_id=message_thread_id,
show_caption_above_media=show_caption_above_media,
)
async def copy_message(
@ -1622,6 +1664,7 @@ class User(TelegramObject):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
show_caption_above_media: Optional[bool] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -1663,6 +1706,7 @@ class User(TelegramObject):
api_kwargs=api_kwargs,
protect_content=protect_content,
message_thread_id=message_thread_id,
show_caption_above_media=show_caption_above_media,
)
async def send_copies(
@ -2101,3 +2145,35 @@ class User(TelegramObject):
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
async def refund_star_payment(
self,
telegram_payment_charge_id: str,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
) -> bool:
"""Shortcut for::
await bot.refund_star_payment(user_id=update.effective_user.id, *args, **kwargs)
For the documentation of the arguments, please see
:meth:`telegram.Bot.refund_star_payment`.
.. versionadded:: 21.3
Returns:
:obj:`bool`: On success, :obj:`True` is returned.
"""
return await self.get_bot().refund_star_payment(
user_id=self.id,
telegram_payment_charge_id=telegram_payment_charge_id,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)

View file

@ -194,7 +194,7 @@ def extract_tzinfo_from_defaults(bot: "Bot") -> Union[dtm.tzinfo, None]:
If the bot has no default values, :obj:`None` is returned.
"""
# We don't use `ininstance(bot, ExtBot)` here so that this works
# in `python-telegram-bot-raw` as well
# without the job-queue extra dependencies as well
if hasattr(bot, "defaults") and bot.defaults:
return bot.defaults.tzinfo
return None

View file

@ -19,7 +19,7 @@
# pylint: disable=missing-module-docstring
from typing import Final, NamedTuple
__all__ = ("__bot_api_version__", "__bot_api_version_info__", "__version__", "__version_info__")
__all__ = ("__version__", "__version_info__")
class Version(NamedTuple):
@ -51,15 +51,6 @@ class Version(NamedTuple):
__version_info__: Final[Version] = Version(
major=21, minor=2, micro=0, releaselevel="final", serial=0
major=21, minor=3, micro=0, releaselevel="final", serial=0
)
__version__: Final[str] = str(__version_info__)
# # SETUP.PY MARKER
# Lines above this line will be `exec`-cuted in setup.py. Make sure that this only contains
# std-lib imports!
from telegram import constants # noqa: E402 # pylint: disable=wrong-import-position
__bot_api_version__: Final[str] = constants.BOT_API_VERSION
__bot_api_version_info__: Final[constants._BotAPIVersion] = constants.BOT_API_VERSION_INFO

View file

@ -146,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=3)
BOT_API_VERSION_INFO: Final[_BotAPIVersion] = _BotAPIVersion(major=7, minor=4)
#: :obj:`str`: Telegram Bot API
#: version supported by this version of `python-telegram-bot`. Also available as
#: :data:`telegram.__bot_api_version__`.
@ -168,7 +168,8 @@ ZERO_DATE: Final[datetime.datetime] = datetime.datetime(1970, 1, 1, tzinfo=UTC)
class AccentColor(Enum):
"""This enum contains the available accent colors for :class:`telegram.Chat.accent_color_id`.
"""This enum contains the available accent colors for
:class:`telegram.ChatFullInfo.accent_color_id`.
The members of this enum are named tuples with the following attributes:
- ``identifier`` (:obj:`int`): The identifier of the accent color.
@ -1629,48 +1630,53 @@ class MessageEntityType(StringEnum):
__slots__ = ()
MENTION = "mention"
""":obj:`str`: Message entities representing a mention."""
HASHTAG = "hashtag"
""":obj:`str`: Message entities representing a hashtag."""
CASHTAG = "cashtag"
""":obj:`str`: Message entities representing a cashtag."""
PHONE_NUMBER = "phone_number"
""":obj:`str`: Message entities representing a phone number."""
BOT_COMMAND = "bot_command"
""":obj:`str`: Message entities representing a bot command."""
URL = "url"
""":obj:`str`: Message entities representing a url."""
EMAIL = "email"
""":obj:`str`: Message entities representing a email."""
BLOCKQUOTE = "blockquote"
""":obj:`str`: Message entities representing a block quotation.
.. versionadded:: 20.8
"""
BOLD = "bold"
""":obj:`str`: Message entities representing bold text."""
ITALIC = "italic"
""":obj:`str`: Message entities representing italic text."""
BOT_COMMAND = "bot_command"
""":obj:`str`: Message entities representing a bot command."""
CASHTAG = "cashtag"
""":obj:`str`: Message entities representing a cashtag."""
CODE = "code"
""":obj:`str`: Message entities representing monowidth string."""
CUSTOM_EMOJI = "custom_emoji"
""":obj:`str`: Message entities representing inline custom emoji stickers.
.. versionadded:: 20.0
"""
EMAIL = "email"
""":obj:`str`: Message entities representing a email."""
EXPANDABLE_BLOCKQUOTE = "expandable_blockquote"
""":obj:`str`: Message entities representing collapsed-by-default block quotation.
.. versionadded:: 21.3
"""
HASHTAG = "hashtag"
""":obj:`str`: Message entities representing a hashtag."""
ITALIC = "italic"
""":obj:`str`: Message entities representing italic text."""
MENTION = "mention"
""":obj:`str`: Message entities representing a mention."""
PHONE_NUMBER = "phone_number"
""":obj:`str`: Message entities representing a phone number."""
PRE = "pre"
""":obj:`str`: Message entities representing monowidth block."""
SPOILER = "spoiler"
""":obj:`str`: Message entities representing spoiler text."""
STRIKETHROUGH = "strikethrough"
""":obj:`str`: Message entities representing strikethrough text."""
TEXT_LINK = "text_link"
""":obj:`str`: Message entities representing clickable text URLs."""
TEXT_MENTION = "text_mention"
""":obj:`str`: Message entities representing text mention for users without usernames."""
UNDERLINE = "underline"
""":obj:`str`: Message entities representing underline text."""
STRIKETHROUGH = "strikethrough"
""":obj:`str`: Message entities representing strikethrough text."""
SPOILER = "spoiler"
""":obj:`str`: Message entities representing spoiler text."""
CUSTOM_EMOJI = "custom_emoji"
""":obj:`str`: Message entities representing inline custom emoji stickers.
.. versionadded:: 20.0
"""
BLOCKQUOTE = "blockquote"
""":obj:`str`: Message entities representing a block quotation.
.. versionadded:: 20.8
"""
URL = "url"
""":obj:`str`: Message entities representing a url."""
class MessageLimit(IntEnum):
@ -1800,6 +1806,10 @@ class MessageType(StringEnum):
""":obj:`str`: Messages with :attr:`telegram.Message.dice`."""
DOCUMENT = "document"
""":obj:`str`: Messages with :attr:`telegram.Message.document`."""
EFFECT_ID = "effect_id"
""":obj:`str`: Messages with :attr:`telegram.Message.effect_id`.
.. versionadded:: 21.3"""
FORUM_TOPIC_CREATED = "forum_topic_created"
""":obj:`str`: Messages with :attr:`telegram.Message.forum_topic_created`.
@ -1959,7 +1969,7 @@ class PollingLimit(IntEnum):
class ProfileAccentColor(Enum):
"""This enum contains the available accent colors for
:class:`telegram.Chat.profile_accent_color_id`.
:class:`telegram.ChatFullInfo.profile_accent_color_id`.
The members of this enum are named tuples with the following attributes:
- ``identifier`` (:obj:`int`): The identifier of the accent color.

View file

@ -251,39 +251,44 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
"""
__slots__ = (
"__create_task_tasks",
"__update_fetcher_task",
"__update_persistence_event",
"__update_persistence_lock",
"__update_persistence_task",
( # noqa: RUF005
"__create_task_tasks",
"__update_fetcher_task",
"__update_persistence_event",
"__update_persistence_lock",
"__update_persistence_task",
"__stop_running_marker",
"_chat_data",
"_chat_ids_to_be_deleted_in_persistence",
"_chat_ids_to_be_updated_in_persistence",
"_conversation_handler_conversations",
"_initialized",
"_job_queue",
"_running",
"_update_processor",
"_user_data",
"_user_ids_to_be_deleted_in_persistence",
"_user_ids_to_be_updated_in_persistence",
"bot",
"bot_data",
"chat_data",
"context_types",
"error_handlers",
"handlers",
"persistence",
"post_init",
"post_shutdown",
"post_stop",
"update_queue",
"updater",
"user_data",
)
# Allowing '__weakref__' creation here since we need it for the JobQueue
# Uncomment if necessary - currently the __weakref__ slot is already created
# in the AsyncContextManager base class
# "__weakref__",
"_chat_data",
"_chat_ids_to_be_deleted_in_persistence",
"_chat_ids_to_be_updated_in_persistence",
"_conversation_handler_conversations",
"_initialized",
"_job_queue",
"_running",
"_update_processor",
"_user_data",
"_user_ids_to_be_deleted_in_persistence",
"_user_ids_to_be_updated_in_persistence",
"bot",
"bot_data",
"chat_data",
"context_types",
"error_handlers",
"handlers",
"persistence",
"post_init",
"post_shutdown",
"post_stop",
"update_queue",
"updater",
"user_data",
# Currently the __weakref__ slot is already created
# in the AsyncContextManager base class for pythons < 3.13
+ ("__weakref__",)
if sys.version_info >= (3, 13)
else ()
)
def __init__(

View file

@ -593,6 +593,7 @@ class ExtBot(Bot, Generic[RLARGS]):
link_preview_options: ODVInput["LinkPreviewOptions"] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
message_effect_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -624,6 +625,7 @@ class ExtBot(Bot, Generic[RLARGS]):
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=business_connection_id,
message_effect_id=message_effect_id,
)
if isinstance(result, Message):
self._insert_callback_data(result)
@ -798,6 +800,7 @@ class ExtBot(Bot, Generic[RLARGS]):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
show_caption_above_media: Optional[bool] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -828,6 +831,7 @@ class ExtBot(Bot, Generic[RLARGS]):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
show_caption_above_media=show_caption_above_media,
)
async def copy_messages(
@ -1146,7 +1150,7 @@ class ExtBot(Bot, Generic[RLARGS]):
title: str,
description: str,
payload: str,
provider_token: str,
provider_token: Optional[str],
currency: str,
prices: Sequence["LabeledPrice"],
max_tip_amount: Optional[int] = None,
@ -1506,6 +1510,7 @@ class ExtBot(Bot, Generic[RLARGS]):
reply_markup: Optional["InlineKeyboardMarkup"] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
caption_entities: Optional[Sequence["MessageEntity"]] = None,
show_caption_above_media: Optional[bool] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@ -1527,6 +1532,7 @@ class ExtBot(Bot, Generic[RLARGS]):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
show_caption_above_media=show_caption_above_media,
)
async def edit_message_live_location(
@ -2379,6 +2385,8 @@ class ExtBot(Bot, Generic[RLARGS]):
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
message_effect_id: Optional[str] = None,
show_caption_above_media: Optional[bool] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -2415,6 +2423,8 @@ class ExtBot(Bot, Generic[RLARGS]):
business_connection_id=business_connection_id,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
message_effect_id=message_effect_id,
show_caption_above_media=show_caption_above_media,
)
async def send_audio(
@ -2434,6 +2444,7 @@ class ExtBot(Bot, Generic[RLARGS]):
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
message_effect_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -2469,6 +2480,7 @@ class ExtBot(Bot, Generic[RLARGS]):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
message_effect_id=message_effect_id,
)
async def send_chat_action(
@ -2510,6 +2522,7 @@ class ExtBot(Bot, Generic[RLARGS]):
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
message_effect_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -2541,6 +2554,7 @@ class ExtBot(Bot, Generic[RLARGS]):
pool_timeout=pool_timeout,
business_connection_id=business_connection_id,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
message_effect_id=message_effect_id,
)
async def send_dice(
@ -2553,6 +2567,7 @@ class ExtBot(Bot, Generic[RLARGS]):
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
message_effect_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -2579,6 +2594,7 @@ class ExtBot(Bot, Generic[RLARGS]):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
message_effect_id=message_effect_id,
)
async def send_document(
@ -2596,6 +2612,7 @@ class ExtBot(Bot, Generic[RLARGS]):
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
message_effect_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -2629,6 +2646,7 @@ class ExtBot(Bot, Generic[RLARGS]):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
message_effect_id=message_effect_id,
)
async def send_game(
@ -2641,6 +2659,7 @@ class ExtBot(Bot, Generic[RLARGS]):
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
message_effect_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -2667,6 +2686,7 @@ class ExtBot(Bot, Generic[RLARGS]):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
message_effect_id=message_effect_id,
)
async def send_invoice(
@ -2675,7 +2695,7 @@ class ExtBot(Bot, Generic[RLARGS]):
title: str,
description: str,
payload: str,
provider_token: str,
provider_token: Optional[str],
currency: str,
prices: Sequence["LabeledPrice"],
start_parameter: Optional[str] = None,
@ -2698,6 +2718,7 @@ class ExtBot(Bot, Generic[RLARGS]):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
message_effect_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -2743,6 +2764,7 @@ class ExtBot(Bot, Generic[RLARGS]):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
message_effect_id=message_effect_id,
)
async def send_location(
@ -2760,6 +2782,7 @@ class ExtBot(Bot, Generic[RLARGS]):
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
message_effect_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -2793,6 +2816,7 @@ class ExtBot(Bot, Generic[RLARGS]):
business_connection_id=business_connection_id,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
message_effect_id=message_effect_id,
)
async def send_media_group(
@ -2806,6 +2830,7 @@ class ExtBot(Bot, Generic[RLARGS]):
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
message_effect_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -2837,6 +2862,7 @@ class ExtBot(Bot, Generic[RLARGS]):
business_connection_id=business_connection_id,
parse_mode=parse_mode,
caption_entities=caption_entities,
message_effect_id=message_effect_id,
)
async def send_message(
@ -2852,6 +2878,7 @@ class ExtBot(Bot, Generic[RLARGS]):
link_preview_options: ODVInput["LinkPreviewOptions"] = DEFAULT_NONE,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
message_effect_id: Optional[str] = None,
*,
disable_web_page_preview: Optional[bool] = None,
reply_to_message_id: Optional[int] = None,
@ -2883,6 +2910,7 @@ class ExtBot(Bot, Generic[RLARGS]):
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
link_preview_options=link_preview_options,
message_effect_id=message_effect_id,
)
async def send_photo(
@ -2899,6 +2927,8 @@ class ExtBot(Bot, Generic[RLARGS]):
has_spoiler: Optional[bool] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
message_effect_id: Optional[str] = None,
show_caption_above_media: Optional[bool] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -2931,6 +2961,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),
message_effect_id=message_effect_id,
show_caption_above_media=show_caption_above_media,
)
async def send_poll(
@ -2956,6 +2988,7 @@ class ExtBot(Bot, Generic[RLARGS]):
business_connection_id: Optional[str] = None,
question_parse_mode: ODVInput[str] = DEFAULT_NONE,
question_entities: Optional[Sequence["MessageEntity"]] = None,
message_effect_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -2995,6 +3028,7 @@ class ExtBot(Bot, Generic[RLARGS]):
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
question_parse_mode=question_parse_mode,
question_entities=question_entities,
message_effect_id=message_effect_id,
)
async def send_sticker(
@ -3008,6 +3042,7 @@ class ExtBot(Bot, Generic[RLARGS]):
emoji: Optional[str] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
message_effect_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -3035,6 +3070,7 @@ class ExtBot(Bot, Generic[RLARGS]):
pool_timeout=pool_timeout,
emoji=emoji,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
message_effect_id=message_effect_id,
)
async def send_venue(
@ -3054,6 +3090,7 @@ class ExtBot(Bot, Generic[RLARGS]):
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
message_effect_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -3089,6 +3126,7 @@ class ExtBot(Bot, Generic[RLARGS]):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
message_effect_id=message_effect_id,
)
async def send_video(
@ -3110,6 +3148,8 @@ class ExtBot(Bot, Generic[RLARGS]):
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
message_effect_id: Optional[str] = None,
show_caption_above_media: Optional[bool] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -3147,6 +3187,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),
message_effect_id=message_effect_id,
show_caption_above_media=show_caption_above_media,
)
async def send_video_note(
@ -3162,6 +3204,7 @@ class ExtBot(Bot, Generic[RLARGS]):
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
message_effect_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -3193,6 +3236,7 @@ class ExtBot(Bot, Generic[RLARGS]):
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
business_connection_id=business_connection_id,
message_effect_id=message_effect_id,
)
async def send_voice(
@ -3209,6 +3253,7 @@ class ExtBot(Bot, Generic[RLARGS]):
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
message_effect_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -3241,6 +3286,7 @@ class ExtBot(Bot, Generic[RLARGS]):
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
business_connection_id=business_connection_id,
message_effect_id=message_effect_id,
)
async def set_chat_administrator_custom_title(
@ -4111,6 +4157,28 @@ class ExtBot(Bot, Generic[RLARGS]):
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
async def refund_star_payment(
self,
user_id: int,
telegram_payment_charge_id: str,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
rate_limit_args: Optional[RLARGS] = None,
) -> bool:
return await super().refund_star_payment(
user_id=user_id,
telegram_payment_charge_id=telegram_payment_charge_id,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
# updated camelCase aliases
getMe = get_me
sendMessage = send_message
@ -4232,3 +4300,4 @@ class ExtBot(Bot, Generic[RLARGS]):
setMessageReaction = set_message_reaction
getBusinessConnection = get_business_connection
replaceStickerInSet = replace_sticker_in_set
refundStarPayment = refund_star_payment

View file

@ -21,8 +21,9 @@ from typing import Final, Optional, TypeVar
from telegram import Update
from telegram._utils.defaultvalue import DEFAULT_TRUE
from telegram._utils.types import DVType
from telegram._utils.types import SCT, DVType
from telegram.ext._handlers.basehandler import BaseHandler
from telegram.ext._utils._update_parsing import parse_chat_id
from telegram.ext._utils.types import CCT, HandlerCallback
RT = TypeVar("RT")
@ -58,6 +59,9 @@ class ChatMemberHandler(BaseHandler[Update, CCT]):
:meth:`telegram.ext.Application.process_update`. Defaults to :obj:`True`.
.. seealso:: :wiki:`Concurrency`
chat_id (:obj:`int` | Collection[:obj:`int`], optional): Filters chat member updates from
specified chat ID(s) only.
.. versionadded:: 21.3
Attributes:
callback (:term:`coroutine function`): The callback function for this handler.
@ -70,7 +74,10 @@ class ChatMemberHandler(BaseHandler[Update, CCT]):
"""
__slots__ = ("chat_member_types",)
__slots__ = (
"_chat_ids",
"chat_member_types",
)
MY_CHAT_MEMBER: Final[int] = -1
""":obj:`int`: Used as a constant to handle only :attr:`telegram.Update.my_chat_member`."""
CHAT_MEMBER: Final[int] = 0
@ -84,10 +91,12 @@ class ChatMemberHandler(BaseHandler[Update, CCT]):
callback: HandlerCallback[Update, CCT, RT],
chat_member_types: int = MY_CHAT_MEMBER,
block: DVType[bool] = DEFAULT_TRUE,
chat_id: Optional[SCT[int]] = None,
):
super().__init__(callback, block=block)
self.chat_member_types: Optional[int] = chat_member_types
self._chat_ids = parse_chat_id(chat_id)
def check_update(self, update: object) -> bool:
"""Determines whether an update should be passed to this handler's :attr:`callback`.
@ -99,12 +108,18 @@ class ChatMemberHandler(BaseHandler[Update, CCT]):
:obj:`bool`
"""
if isinstance(update, Update):
if not (update.my_chat_member or update.chat_member):
return False
if self.chat_member_types == self.ANY_CHAT_MEMBER:
return True
if self.chat_member_types == self.CHAT_MEMBER:
return bool(update.chat_member)
return bool(update.my_chat_member)
return False
if not isinstance(update, Update):
return False
if not (update.my_chat_member or update.chat_member):
return False
if (
self._chat_ids
and update.effective_chat
and update.effective_chat.id not in self._chat_ids
):
return False
if self.chat_member_types == self.ANY_CHAT_MEMBER:
return True
if self.chat_member_types == self.CHAT_MEMBER:
return bool(update.chat_member)
return bool(update.my_chat_member)

View file

@ -46,6 +46,7 @@ __all__ = (
"CHAT",
"COMMAND",
"CONTACT",
"EFFECT_ID",
"FORWARDED",
"GAME",
"GIVEAWAY",
@ -1338,6 +1339,19 @@ class Document:
"""Use as ``filters.Document.ZIP``."""
class _EffectId(MessageFilter):
__slots__ = ()
def filter(self, message: Message) -> bool:
return bool(message.effect_id)
EFFECT_ID = _EffectId(name="filters.EFFECT_ID")
"""Messages that contain :attr:`telegram.Message.effect_id`.
.. versionadded:: 21.3"""
class Entity(MessageFilter):
"""
Filters messages to only allow those which have a :class:`telegram.MessageEntity`

View file

@ -4,7 +4,7 @@ Testing in PTB
PTB uses `pytest`_ for testing. To run the tests, you need to
have pytest installed along with a few other dependencies. You can find the list of dependencies
in the ``requirements-dev.txt`` file in the root of the repository.
in the ``pyproject.toml`` file in the root of the repository.
Running tests
=============

View file

@ -219,6 +219,7 @@ class TestAnimationWithRequest(TestAnimationBase):
protect_content=True,
thumbnail=thumb_file,
has_spoiler=True,
show_caption_above_media=True,
)
assert isinstance(message.animation, Animation)
@ -232,6 +233,7 @@ class TestAnimationWithRequest(TestAnimationBase):
assert message.animation.thumbnail.width == self.width
assert message.animation.thumbnail.height == self.height
assert message.has_protected_content
assert message.show_caption_above_media
try:
assert message.has_media_spoiler
except AssertionError:

View file

@ -58,6 +58,7 @@ def input_media_video(class_thumb_file):
thumbnail=class_thumb_file,
supports_streaming=TestInputMediaVideoBase.supports_streaming,
has_spoiler=TestInputMediaVideoBase.has_spoiler,
show_caption_above_media=TestInputMediaVideoBase.show_caption_above_media,
)
@ -69,6 +70,7 @@ def input_media_photo():
parse_mode=TestInputMediaPhotoBase.parse_mode,
caption_entities=TestInputMediaPhotoBase.caption_entities,
has_spoiler=TestInputMediaPhotoBase.has_spoiler,
show_caption_above_media=TestInputMediaPhotoBase.show_caption_above_media,
)
@ -84,6 +86,7 @@ def input_media_animation(class_thumb_file):
thumbnail=class_thumb_file,
duration=TestInputMediaAnimationBase.duration,
has_spoiler=TestInputMediaAnimationBase.has_spoiler,
show_caption_above_media=TestInputMediaAnimationBase.show_caption_above_media,
)
@ -124,6 +127,7 @@ class TestInputMediaVideoBase:
supports_streaming = True
caption_entities = [MessageEntity(MessageEntity.BOLD, 0, 2)]
has_spoiler = True
show_caption_above_media = True
class TestInputMediaVideoWithoutRequest(TestInputMediaVideoBase):
@ -145,6 +149,7 @@ class TestInputMediaVideoWithoutRequest(TestInputMediaVideoBase):
assert input_media_video.supports_streaming == self.supports_streaming
assert isinstance(input_media_video.thumbnail, InputFile)
assert input_media_video.has_spoiler == self.has_spoiler
assert input_media_video.show_caption_above_media == self.show_caption_above_media
def test_caption_entities_always_tuple(self):
input_media_video = InputMediaVideo(self.media)
@ -164,6 +169,10 @@ class TestInputMediaVideoWithoutRequest(TestInputMediaVideoBase):
]
assert input_media_video_dict["supports_streaming"] == input_media_video.supports_streaming
assert input_media_video_dict["has_spoiler"] == input_media_video.has_spoiler
assert (
input_media_video_dict["show_caption_above_media"]
== input_media_video.show_caption_above_media
)
def test_with_video(self, video):
# fixture found in test_video
@ -217,6 +226,7 @@ class TestInputMediaPhotoBase:
parse_mode = "Markdown"
caption_entities = [MessageEntity(MessageEntity.BOLD, 0, 2)]
has_spoiler = True
show_caption_above_media = True
class TestInputMediaPhotoWithoutRequest(TestInputMediaPhotoBase):
@ -233,6 +243,7 @@ class TestInputMediaPhotoWithoutRequest(TestInputMediaPhotoBase):
assert input_media_photo.parse_mode == self.parse_mode
assert input_media_photo.caption_entities == tuple(self.caption_entities)
assert input_media_photo.has_spoiler == self.has_spoiler
assert input_media_photo.show_caption_above_media == self.show_caption_above_media
def test_caption_entities_always_tuple(self):
input_media_photo = InputMediaPhoto(self.media)
@ -248,6 +259,10 @@ class TestInputMediaPhotoWithoutRequest(TestInputMediaPhotoBase):
ce.to_dict() for ce in input_media_photo.caption_entities
]
assert input_media_photo_dict["has_spoiler"] == input_media_photo.has_spoiler
assert (
input_media_photo_dict["show_caption_above_media"]
== input_media_photo.show_caption_above_media
)
def test_with_photo(self, photo):
# fixture found in test_photo
@ -278,6 +293,7 @@ class TestInputMediaAnimationBase:
height = 30
duration = 1
has_spoiler = True
show_caption_above_media = True
class TestInputMediaAnimationWithoutRequest(TestInputMediaAnimationBase):
@ -295,6 +311,7 @@ class TestInputMediaAnimationWithoutRequest(TestInputMediaAnimationBase):
assert input_media_animation.caption_entities == tuple(self.caption_entities)
assert isinstance(input_media_animation.thumbnail, InputFile)
assert input_media_animation.has_spoiler == self.has_spoiler
assert input_media_animation.show_caption_above_media == self.show_caption_above_media
def test_caption_entities_always_tuple(self):
input_media_animation = InputMediaAnimation(self.media)
@ -313,6 +330,10 @@ class TestInputMediaAnimationWithoutRequest(TestInputMediaAnimationBase):
assert input_media_animation_dict["height"] == input_media_animation.height
assert input_media_animation_dict["duration"] == input_media_animation.duration
assert input_media_animation_dict["has_spoiler"] == input_media_animation.has_spoiler
assert (
input_media_animation_dict["show_caption_above_media"]
== input_media_animation.show_caption_above_media
)
def test_with_animation(self, animation):
# fixture found in test_animation

View file

@ -219,6 +219,7 @@ class TestPhotoWithRequest(TestPhotoBase):
protect_content=True,
parse_mode="Markdown",
has_spoiler=True,
show_caption_above_media=True,
)
assert isinstance(message.photo[-2], PhotoSize)
@ -236,6 +237,7 @@ class TestPhotoWithRequest(TestPhotoBase):
assert message.caption == self.caption.replace("*", "")
assert message.has_protected_content
assert message.has_media_spoiler
assert message.show_caption_above_media
async def test_send_photo_parse_mode_markdown(self, bot, chat_id, photo_file):
message = await bot.send_photo(

View file

@ -232,6 +232,7 @@ class TestVideoWithRequest(TestVideoBase):
parse_mode="Markdown",
thumbnail=thumb_file,
has_spoiler=True,
show_caption_above_media=True,
)
assert isinstance(message.video, Video)
@ -253,6 +254,7 @@ class TestVideoWithRequest(TestVideoBase):
assert message.video.file_name == self.file_name
assert message.has_protected_content
assert message.has_media_spoiler
assert message.show_caption_above_media
async def test_get_and_download(self, bot, video, chat_id, tmp_file):
new_file = await bot.get_file(video.file_id)

View file

@ -40,6 +40,7 @@ def inline_query_result_cached_gif():
caption_entities=TestInlineQueryResultCachedGifBase.caption_entities,
input_message_content=TestInlineQueryResultCachedGifBase.input_message_content,
reply_markup=TestInlineQueryResultCachedGifBase.reply_markup,
show_caption_above_media=TestInlineQueryResultCachedGifBase.show_caption_above_media,
)
@ -53,6 +54,7 @@ class TestInlineQueryResultCachedGifBase:
caption_entities = [MessageEntity(MessageEntity.ITALIC, 0, 7)]
input_message_content = InputTextMessageContent("input_message_content")
reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]])
show_caption_above_media = True
class TestInlineQueryResultCachedGifWithoutRequest(TestInlineQueryResultCachedGifBase):
@ -75,6 +77,10 @@ class TestInlineQueryResultCachedGifWithoutRequest(TestInlineQueryResultCachedGi
== self.input_message_content.to_dict()
)
assert inline_query_result_cached_gif.reply_markup.to_dict() == self.reply_markup.to_dict()
assert (
inline_query_result_cached_gif.show_caption_above_media
== self.show_caption_above_media
)
def test_caption_entities_always_tuple(self):
result = InlineQueryResultCachedGif(self.id_, self.gif_file_id)
@ -110,6 +116,10 @@ class TestInlineQueryResultCachedGifWithoutRequest(TestInlineQueryResultCachedGi
inline_query_result_cached_gif_dict["reply_markup"]
== inline_query_result_cached_gif.reply_markup.to_dict()
)
assert (
inline_query_result_cached_gif_dict["show_caption_above_media"]
== inline_query_result_cached_gif.show_caption_above_media
)
def test_equality(self):
a = InlineQueryResultCachedGif(self.id_, self.gif_file_id)

View file

@ -40,6 +40,7 @@ def inline_query_result_cached_mpeg4_gif():
caption_entities=TestInlineQueryResultCachedMpeg4GifBase.caption_entities,
input_message_content=TestInlineQueryResultCachedMpeg4GifBase.input_message_content,
reply_markup=TestInlineQueryResultCachedMpeg4GifBase.reply_markup,
show_caption_above_media=TestInlineQueryResultCachedMpeg4GifBase.show_caption_above_media,
)
@ -53,6 +54,7 @@ class TestInlineQueryResultCachedMpeg4GifBase:
caption_entities = [MessageEntity(MessageEntity.ITALIC, 0, 7)]
input_message_content = InputTextMessageContent("input_message_content")
reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]])
show_caption_above_media = True
class TestInlineQueryResultCachedMpeg4GifWithoutRequest(TestInlineQueryResultCachedMpeg4GifBase):
@ -80,6 +82,10 @@ class TestInlineQueryResultCachedMpeg4GifWithoutRequest(TestInlineQueryResultCac
inline_query_result_cached_mpeg4_gif.reply_markup.to_dict()
== self.reply_markup.to_dict()
)
assert (
inline_query_result_cached_mpeg4_gif.show_caption_above_media
== self.show_caption_above_media
)
def test_caption_entities_always_tuple(self):
result = InlineQueryResultCachedMpeg4Gif(self.id_, self.mpeg4_file_id)
@ -124,6 +130,10 @@ class TestInlineQueryResultCachedMpeg4GifWithoutRequest(TestInlineQueryResultCac
inline_query_result_cached_mpeg4_gif_dict["reply_markup"]
== inline_query_result_cached_mpeg4_gif.reply_markup.to_dict()
)
assert (
inline_query_result_cached_mpeg4_gif_dict["show_caption_above_media"]
== inline_query_result_cached_mpeg4_gif.show_caption_above_media
)
def test_equality(self):
a = InlineQueryResultCachedMpeg4Gif(self.id_, self.mpeg4_file_id)

View file

@ -41,6 +41,7 @@ def inline_query_result_cached_photo():
caption_entities=TestInlineQueryResultCachedPhotoBase.caption_entities,
input_message_content=TestInlineQueryResultCachedPhotoBase.input_message_content,
reply_markup=TestInlineQueryResultCachedPhotoBase.reply_markup,
show_caption_above_media=TestInlineQueryResultCachedPhotoBase.show_caption_above_media,
)
@ -55,6 +56,7 @@ class TestInlineQueryResultCachedPhotoBase:
caption_entities = [MessageEntity(MessageEntity.ITALIC, 0, 7)]
input_message_content = InputTextMessageContent("input_message_content")
reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]])
show_caption_above_media = True
class TestInlineQueryResultCachedPhotoWithoutRequest(TestInlineQueryResultCachedPhotoBase):
@ -80,6 +82,10 @@ class TestInlineQueryResultCachedPhotoWithoutRequest(TestInlineQueryResultCached
assert (
inline_query_result_cached_photo.reply_markup.to_dict() == self.reply_markup.to_dict()
)
assert (
inline_query_result_cached_photo.show_caption_above_media
== self.show_caption_above_media
)
def test_caption_entities_always_tuple(self):
result = InlineQueryResultCachedPhoto(self.id_, self.photo_file_id)
@ -124,6 +130,10 @@ class TestInlineQueryResultCachedPhotoWithoutRequest(TestInlineQueryResultCached
inline_query_result_cached_photo_dict["reply_markup"]
== inline_query_result_cached_photo.reply_markup.to_dict()
)
assert (
inline_query_result_cached_photo_dict["show_caption_above_media"]
== inline_query_result_cached_photo.show_caption_above_media
)
def test_equality(self):
a = InlineQueryResultCachedPhoto(self.id_, self.photo_file_id)

View file

@ -41,6 +41,7 @@ def inline_query_result_cached_video():
description=TestInlineQueryResultCachedVideoBase.description,
input_message_content=TestInlineQueryResultCachedVideoBase.input_message_content,
reply_markup=TestInlineQueryResultCachedVideoBase.reply_markup,
show_caption_above_media=TestInlineQueryResultCachedVideoBase.show_caption_above_media,
)
@ -55,6 +56,7 @@ class TestInlineQueryResultCachedVideoBase:
description = "description"
input_message_content = InputTextMessageContent("input_message_content")
reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]])
show_caption_above_media = True
class TestInlineQueryResultCachedVideoWithoutRequest(TestInlineQueryResultCachedVideoBase):
@ -80,6 +82,10 @@ class TestInlineQueryResultCachedVideoWithoutRequest(TestInlineQueryResultCached
assert (
inline_query_result_cached_video.reply_markup.to_dict() == self.reply_markup.to_dict()
)
assert (
inline_query_result_cached_video.show_caption_above_media
== self.show_caption_above_media
)
def test_caption_entities_always_tuple(self):
video = InlineQueryResultCachedVideo(self.id_, self.video_file_id, self.title)
@ -125,6 +131,10 @@ class TestInlineQueryResultCachedVideoWithoutRequest(TestInlineQueryResultCached
inline_query_result_cached_video_dict["reply_markup"]
== inline_query_result_cached_video.reply_markup.to_dict()
)
assert (
inline_query_result_cached_video_dict["show_caption_above_media"]
== inline_query_result_cached_video.show_caption_above_media
)
def test_equality(self):
a = InlineQueryResultCachedVideo(self.id_, self.video_file_id, self.title)

View file

@ -45,6 +45,7 @@ def inline_query_result_gif():
input_message_content=TestInlineQueryResultGifBase.input_message_content,
reply_markup=TestInlineQueryResultGifBase.reply_markup,
thumbnail_mime_type=TestInlineQueryResultGifBase.thumbnail_mime_type,
show_caption_above_media=TestInlineQueryResultGifBase.show_caption_above_media,
)
@ -63,6 +64,7 @@ class TestInlineQueryResultGifBase:
caption_entities = [MessageEntity(MessageEntity.ITALIC, 0, 7)]
input_message_content = InputTextMessageContent("input_message_content")
reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]])
show_caption_above_media = True
class TestInlineQueryResultGifWithoutRequest(TestInlineQueryResultGifBase):
@ -94,6 +96,7 @@ class TestInlineQueryResultGifWithoutRequest(TestInlineQueryResultGifBase):
== self.input_message_content.to_dict()
)
assert inline_query_result_gif.reply_markup.to_dict() == self.reply_markup.to_dict()
assert inline_query_result_gif.show_caption_above_media == self.show_caption_above_media
def test_to_dict(self, inline_query_result_gif):
inline_query_result_gif_dict = inline_query_result_gif.to_dict()
@ -126,6 +129,10 @@ class TestInlineQueryResultGifWithoutRequest(TestInlineQueryResultGifBase):
inline_query_result_gif_dict["reply_markup"]
== inline_query_result_gif.reply_markup.to_dict()
)
assert (
inline_query_result_gif_dict["show_caption_above_media"]
== inline_query_result_gif.show_caption_above_media
)
def test_equality(self):
a = InlineQueryResultGif(self.id_, self.gif_url, self.thumbnail_url)

View file

@ -45,6 +45,7 @@ def inline_query_result_mpeg4_gif():
input_message_content=TestInlineQueryResultMpeg4GifBase.input_message_content,
reply_markup=TestInlineQueryResultMpeg4GifBase.reply_markup,
thumbnail_mime_type=TestInlineQueryResultMpeg4GifBase.thumbnail_mime_type,
show_caption_above_media=TestInlineQueryResultMpeg4GifBase.show_caption_above_media,
)
@ -63,6 +64,7 @@ class TestInlineQueryResultMpeg4GifBase:
caption_entities = [MessageEntity(MessageEntity.ITALIC, 0, 7)]
input_message_content = InputTextMessageContent("input_message_content")
reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]])
show_caption_above_media = True
class TestInlineQueryResultMpeg4GifWithoutRequest(TestInlineQueryResultMpeg4GifBase):
@ -90,6 +92,9 @@ class TestInlineQueryResultMpeg4GifWithoutRequest(TestInlineQueryResultMpeg4GifB
== self.input_message_content.to_dict()
)
assert inline_query_result_mpeg4_gif.reply_markup.to_dict() == self.reply_markup.to_dict()
assert (
inline_query_result_mpeg4_gif.show_caption_above_media == self.show_caption_above_media
)
def test_caption_entities_always_tuple(self):
result = InlineQueryResultMpeg4Gif(self.id_, self.mpeg4_url, self.thumbnail_url)
@ -144,6 +149,10 @@ class TestInlineQueryResultMpeg4GifWithoutRequest(TestInlineQueryResultMpeg4GifB
inline_query_result_mpeg4_gif_dict["reply_markup"]
== inline_query_result_mpeg4_gif.reply_markup.to_dict()
)
assert (
inline_query_result_mpeg4_gif_dict["show_caption_above_media"]
== inline_query_result_mpeg4_gif.show_caption_above_media
)
def test_equality(self):
a = InlineQueryResultMpeg4Gif(self.id_, self.mpeg4_url, self.thumbnail_url)

View file

@ -44,6 +44,7 @@ def inline_query_result_photo():
caption_entities=TestInlineQueryResultPhotoBase.caption_entities,
input_message_content=TestInlineQueryResultPhotoBase.input_message_content,
reply_markup=TestInlineQueryResultPhotoBase.reply_markup,
show_caption_above_media=TestInlineQueryResultPhotoBase.show_caption_above_media,
)
@ -62,6 +63,7 @@ class TestInlineQueryResultPhotoBase:
input_message_content = InputTextMessageContent("input_message_content")
reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]])
show_caption_above_media = True
class TestInlineQueryResultPhotoWithoutRequest(TestInlineQueryResultPhotoBase):
@ -88,6 +90,7 @@ class TestInlineQueryResultPhotoWithoutRequest(TestInlineQueryResultPhotoBase):
== self.input_message_content.to_dict()
)
assert inline_query_result_photo.reply_markup.to_dict() == self.reply_markup.to_dict()
assert inline_query_result_photo.show_caption_above_media == self.show_caption_above_media
def test_caption_entities_always_tuple(self):
result = InlineQueryResultPhoto(self.id_, self.photo_url, self.thumbnail_url)
@ -128,6 +131,10 @@ class TestInlineQueryResultPhotoWithoutRequest(TestInlineQueryResultPhotoBase):
inline_query_result_photo_dict["reply_markup"]
== inline_query_result_photo.reply_markup.to_dict()
)
assert (
inline_query_result_photo_dict["show_caption_above_media"]
== inline_query_result_photo.show_caption_above_media
)
def test_equality(self):
a = InlineQueryResultPhoto(self.id_, self.photo_url, self.thumbnail_url)

View file

@ -46,6 +46,7 @@ def inline_query_result_video():
description=TestInlineQueryResultVideoBase.description,
input_message_content=TestInlineQueryResultVideoBase.input_message_content,
reply_markup=TestInlineQueryResultVideoBase.reply_markup,
show_caption_above_media=TestInlineQueryResultVideoBase.show_caption_above_media,
)
@ -65,6 +66,7 @@ class TestInlineQueryResultVideoBase:
description = "description"
input_message_content = InputTextMessageContent("input_message_content")
reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]])
show_caption_above_media = True
class TestInlineQueryResultVideoWithoutRequest(TestInlineQueryResultVideoBase):
@ -93,6 +95,7 @@ class TestInlineQueryResultVideoWithoutRequest(TestInlineQueryResultVideoBase):
== self.input_message_content.to_dict()
)
assert inline_query_result_video.reply_markup.to_dict() == self.reply_markup.to_dict()
assert inline_query_result_video.show_caption_above_media == self.show_caption_above_media
def test_caption_entities_always_tuple(self):
video = InlineQueryResultVideo(
@ -140,6 +143,10 @@ class TestInlineQueryResultVideoWithoutRequest(TestInlineQueryResultVideoBase):
inline_query_result_video_dict["reply_markup"]
== inline_query_result_video.reply_markup.to_dict()
)
assert (
inline_query_result_video_dict["show_caption_above_media"]
== inline_query_result_video.show_caption_above_media
)
def test_equality(self):
a = InlineQueryResultVideo(

View file

@ -292,13 +292,14 @@ class TestInvoiceWithRequest(TestInvoiceBase):
self.title,
self.description,
self.payload,
provider_token,
self.currency,
self.prices,
"", # using tg stars
"XTR",
[self.prices[0]],
allow_sending_without_reply=custom,
reply_to_message_id=reply_to_message.message_id,
)
assert message.reply_to_message is None
assert message.invoice.currency == "XTR"
elif default_bot.defaults.allow_sending_without_reply:
message = await default_bot.send_invoice(
chat_id,

View file

@ -21,7 +21,7 @@ modify behavior of the respective parent classes in order to make them easier to
pytest framework. A common change is to allow monkeypatching of the class members by not
enforcing slots in the subclasses."""
from telegram import Bot, Message, User
from telegram.ext import Application, ExtBot
from telegram.ext import Application, ExtBot, Updater
from tests.auxil.ci_bots import BOT_INFO_PROVIDER
from tests.auxil.constants import PRIVATE_KEY
from tests.auxil.envvars import TEST_WITH_OPT_DEPS
@ -85,6 +85,10 @@ class PytestMessage(Message):
pass
class PytestUpdater(Updater):
pass
def make_bot(bot_info=None, **kwargs):
"""
Tests are executed on tg.ext.ExtBot, as that class only extends the functionality of tg.bot

View file

@ -20,6 +20,7 @@ import asyncio
import datetime
import logging
import sys
from pathlib import Path
from typing import Dict, List
from uuid import uuid4
@ -344,6 +345,5 @@ def timezone(tzinfo):
@pytest.fixture()
def tmp_file(tmp_path):
with tmp_path / uuid4().hex as file:
yield file
def tmp_file(tmp_path) -> Path:
return tmp_path / uuid4().hex

View file

@ -61,7 +61,7 @@ from tests.auxil.asyncio_helpers import call_after
from tests.auxil.build_messages import make_message_update
from tests.auxil.files import PROJECT_ROOT_PATH
from tests.auxil.networking import send_webhook_message
from tests.auxil.pytest_classes import make_bot
from tests.auxil.pytest_classes import PytestApplication, PytestUpdater, make_bot
from tests.auxil.slots import mro_slots
@ -1581,7 +1581,13 @@ class TestApplication:
async def post_init(app: Application) -> None:
events.append("post_init")
app = Application.builder().bot(one_time_bot).post_init(post_init).build()
app = (
Application.builder()
.application_class(PytestApplication)
.updater(PytestUpdater(one_time_bot, asyncio.Queue()))
.post_init(post_init)
.build()
)
app.bot._unfreeze()
monkeypatch.setattr(app.bot, "get_updates", get_updates)
monkeypatch.setattr(
@ -1624,7 +1630,13 @@ class TestApplication:
async def post_shutdown(app: Application) -> None:
events.append("post_shutdown")
app = Application.builder().bot(one_time_bot).post_shutdown(post_shutdown).build()
app = (
Application.builder()
.application_class(PytestApplication)
.updater(PytestUpdater(one_time_bot, asyncio.Queue()))
.post_shutdown(post_shutdown)
.build()
)
app.bot._unfreeze()
monkeypatch.setattr(app.bot, "get_updates", get_updates)
monkeypatch.setattr(
@ -1650,7 +1662,7 @@ class TestApplication:
platform.system() == "Windows",
reason="Can't send signals without stopping whole process on windows",
)
def test_run_polling_post_stop(self, bot, monkeypatch):
def test_run_polling_post_stop(self, one_time_bot, monkeypatch):
events = []
async def get_updates(*args, **kwargs):
@ -1671,7 +1683,13 @@ class TestApplication:
async def post_stop(app: Application) -> None:
events.append("post_stop")
app = Application.builder().token(bot.token).post_stop(post_stop).build()
app = (
Application.builder()
.application_class(PytestApplication)
.updater(PytestUpdater(one_time_bot, asyncio.Queue()))
.post_stop(post_stop)
.build()
)
app.bot._unfreeze()
monkeypatch.setattr(app.bot, "get_updates", get_updates)
monkeypatch.setattr(app, "stop", call_after(app.stop, lambda _: events.append("stop")))
@ -1863,7 +1881,13 @@ class TestApplication:
async def post_init(app: Application) -> None:
events.append("post_init")
app = Application.builder().bot(one_time_bot).post_init(post_init).build()
app = (
Application.builder()
.post_init(post_init)
.application_class(PytestApplication)
.updater(PytestUpdater(one_time_bot, asyncio.Queue()))
.build()
)
app.bot._unfreeze()
monkeypatch.setattr(app.bot, "set_webhook", set_webhook)
monkeypatch.setattr(app.bot, "delete_webhook", delete_webhook)
@ -1923,7 +1947,13 @@ class TestApplication:
async def post_shutdown(app: Application) -> None:
events.append("post_shutdown")
app = Application.builder().bot(one_time_bot).post_shutdown(post_shutdown).build()
app = (
Application.builder()
.application_class(PytestApplication)
.updater(PytestUpdater(one_time_bot, asyncio.Queue()))
.post_shutdown(post_shutdown)
.build()
)
app.bot._unfreeze()
monkeypatch.setattr(app.bot, "set_webhook", set_webhook)
monkeypatch.setattr(app.bot, "delete_webhook", delete_webhook)
@ -1960,7 +1990,7 @@ class TestApplication:
platform.system() == "Windows",
reason="Can't send signals without stopping whole process on windows",
)
def test_run_webhook_post_stop(self, bot, monkeypatch):
def test_run_webhook_post_stop(self, one_time_bot, monkeypatch):
events = []
async def delete_webhook(*args, **kwargs):
@ -1987,7 +2017,13 @@ class TestApplication:
async def post_stop(app: Application) -> None:
events.append("post_stop")
app = Application.builder().token(bot.token).post_stop(post_stop).build()
app = (
Application.builder()
.application_class(PytestApplication)
.updater(PytestUpdater(one_time_bot, asyncio.Queue()))
.post_stop(post_stop)
.build()
)
app.bot._unfreeze()
monkeypatch.setattr(app.bot, "set_webhook", set_webhook)
monkeypatch.setattr(app.bot, "delete_webhook", delete_webhook)
@ -2480,7 +2516,13 @@ class TestApplication:
app.create_task(task(app))
app = ApplicationBuilder().bot(one_time_bot).post_init(post_init).build()
app = (
ApplicationBuilder()
.application_class(PytestApplication)
.updater(PytestUpdater(one_time_bot, asyncio.Queue()))
.post_init(post_init)
.build()
)
monkeypatch.setattr(app.bot, "get_updates", get_updates)
monkeypatch.setattr(app.bot, "set_webhook", set_webhook)
monkeypatch.setattr(app.bot, "delete_webhook", delete_webhook)

View file

@ -144,6 +144,66 @@ class TestChatMemberHandler:
await app.process_update(chat_member)
assert self.test_flag == result_2
@pytest.mark.parametrize(
argnames=["allowed_types", "chat_id", "expected"],
argvalues=[
(ChatMemberHandler.MY_CHAT_MEMBER, None, (True, False)),
(ChatMemberHandler.CHAT_MEMBER, None, (False, True)),
(ChatMemberHandler.ANY_CHAT_MEMBER, None, (True, True)),
(ChatMemberHandler.MY_CHAT_MEMBER, 1, (True, False)),
(ChatMemberHandler.CHAT_MEMBER, 1, (False, True)),
(ChatMemberHandler.ANY_CHAT_MEMBER, 1, (True, True)),
(ChatMemberHandler.MY_CHAT_MEMBER, [1], (True, False)),
(ChatMemberHandler.CHAT_MEMBER, [1], (False, True)),
(ChatMemberHandler.ANY_CHAT_MEMBER, [1], (True, True)),
(ChatMemberHandler.MY_CHAT_MEMBER, 2, (False, False)),
(ChatMemberHandler.CHAT_MEMBER, 2, (False, False)),
(ChatMemberHandler.ANY_CHAT_MEMBER, 2, (False, False)),
(ChatMemberHandler.MY_CHAT_MEMBER, [2], (False, False)),
(ChatMemberHandler.CHAT_MEMBER, [2], (False, False)),
(ChatMemberHandler.ANY_CHAT_MEMBER, [2], (False, False)),
],
ids=[
"MY_CHAT_MEMBER",
"CHAT_MEMBER",
"ANY_CHAT_MEMBER",
"MY_CHAT_MEMBER, CHAT=1 ",
"CHAT_MEMBER, CHAT=1",
"ANY_CHAT_MEMBER, CHAT=1",
"MY_CHAT_MEMBER, CHAT=[1] ",
"CHAT_MEMBER, CHAT=[1]",
"ANY_CHAT_MEMBER, CHAT=[1]",
"MY_CHAT_MEMBER, CHAT=2 ",
"CHAT_MEMBER, CHAT=2",
"ANY_CHAT_MEMBER, CHAT=2",
"MY_CHAT_MEMBER, CHAT=[2] ",
"CHAT_MEMBER, CHAT=[2]",
"ANY_CHAT_MEMBER, CHAT=[2]",
],
)
async def test_chat_member_types_with_chat_id(
self, app, chat_member_updated, chat_member, expected, allowed_types, chat_id
):
result_1, result_2 = expected
handler = ChatMemberHandler(
self.callback, chat_member_types=allowed_types, chat_id=chat_id
)
app.add_handler(handler)
async with app:
assert handler.check_update(chat_member) == result_1
await app.process_update(chat_member)
assert self.test_flag == result_1
self.test_flag = False
chat_member.my_chat_member = None
chat_member.chat_member = chat_member_updated
assert handler.check_update(chat_member) == result_2
await app.process_update(chat_member)
assert self.test_flag == result_2
def test_other_update_types(self, false_update):
handler = ChatMemberHandler(self.callback)
assert not handler.check_update(false_update)

View file

@ -1107,6 +1107,11 @@ class TestFilters:
update.message.game = "test"
assert filters.GAME.check_update(update)
def test_filters_effect_id(self, update):
assert not filters.EFFECT_ID.check_update(update)
update.message.effect_id = "test"
assert filters.EFFECT_ID.check_update(update)
def test_entities_filter(self, update, message_entity):
update.message.entities = [message_entity]
assert filters.Entity(message_entity.type).check_update(update)

View file

@ -47,6 +47,7 @@ from telegram.request._httpxrequest import HTTPXRequest
from telegram.request._requestparameter import RequestParameter
from telegram.warnings import PTBDeprecationWarning
from tests.auxil.envvars import TEST_WITH_OPT_DEPS
from tests.auxil.networking import NonchalantHttpxRequest
from tests.auxil.slots import mro_slots
# We only need mixed_rqs fixture, but it uses the others, so pytest needs us to import them as well
@ -72,7 +73,7 @@ def mocker_factory(
@pytest.fixture()
async def httpx_request():
async with HTTPXRequest() as rq:
async with NonchalantHttpxRequest() as rq:
yield rq
@ -137,7 +138,7 @@ class TestRequestWithoutRequest:
async def shutdown():
self.test_flag.append("stop")
httpx_request = HTTPXRequest()
httpx_request = NonchalantHttpxRequest()
monkeypatch.setattr(httpx_request, "initialize", initialize)
monkeypatch.setattr(httpx_request, "shutdown", shutdown)
@ -154,7 +155,7 @@ class TestRequestWithoutRequest:
async def shutdown():
self.test_flag = "stop"
httpx_request = HTTPXRequest()
httpx_request = NonchalantHttpxRequest()
monkeypatch.setattr(httpx_request, "initialize", initialize)
monkeypatch.setattr(httpx_request, "shutdown", shutdown)
@ -545,7 +546,7 @@ class TestHTTPXRequestWithoutRequest:
async def aclose(*args):
self.test_flag.append("stop")
httpx_request = HTTPXRequest()
httpx_request = NonchalantHttpxRequest()
monkeypatch.setattr(httpx_request, "initialize", initialize)
monkeypatch.setattr(httpx.AsyncClient, "aclose", aclose)
@ -562,7 +563,7 @@ class TestHTTPXRequestWithoutRequest:
async def aclose(*args):
self.test_flag = "stop"
httpx_request = HTTPXRequest()
httpx_request = NonchalantHttpxRequest()
monkeypatch.setattr(httpx_request, "initialize", initialize)
monkeypatch.setattr(httpx.AsyncClient, "aclose", aclose)

View file

@ -97,7 +97,7 @@ from tests.auxil.bot_method_checks import check_defaults_handling
from tests.auxil.ci_bots import FALLBACKS
from tests.auxil.envvars import GITHUB_ACTION, TEST_WITH_OPT_DEPS
from tests.auxil.files import data_file
from tests.auxil.networking import expect_bad_request
from tests.auxil.networking import NonchalantHttpxRequest, expect_bad_request
from tests.auxil.pytest_classes import PytestBot, PytestExtBot, make_bot
from tests.auxil.slots import mro_slots
@ -252,7 +252,7 @@ class TestBotWithoutRequest:
async def stop(*args, **kwargs):
self.test_flag.append("stop")
temp_bot = PytestBot(token=bot.token)
temp_bot = PytestBot(token=bot.token, request=NonchalantHttpxRequest())
orig_stop = temp_bot.request.shutdown
try:
@ -2179,6 +2179,16 @@ class TestBotWithoutRequest:
monkeypatch.setattr(bot.request, "post", make_assertion)
assert await bot.send_message(2, "text", business_connection_id=42)
async def test_message_effect_id_argument(self, bot, monkeypatch):
"""We can't test every single method easily, so we just test one. Our linting will catch
any unused args with the others."""
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
return request_data.parameters.get("message_effect_id") == 42
monkeypatch.setattr(bot.request, "post", make_assertion)
assert await bot.send_message(2, "text", message_effect_id=42)
async def test_get_business_connection(self, bot, monkeypatch):
bci = "42"
user = User(1, "first", False)
@ -2212,6 +2222,17 @@ class TestBotWithoutRequest:
monkeypatch.setattr(bot, "_post", make_assertion)
assert await bot.send_chat_action(chat_id, "action", 1, 3)
async def test_refund_star_payment(self, bot, monkeypatch):
# can't make actual request so we just test that the correct data is passed
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
return (
request_data.parameters.get("user_id") == 42
and request_data.parameters.get("telegram_payment_charge_id") == "37"
)
monkeypatch.setattr(bot.request, "post", make_assertion)
assert await bot.refund_star_payment(42, "37")
class TestBotWithRequest:
"""
@ -2813,9 +2834,11 @@ class TestBotWithRequest:
caption="new_caption",
chat_id=media_message.chat_id,
message_id=media_message.message_id,
show_caption_above_media=False,
)
assert message.caption == "new_caption"
assert not message.show_caption_above_media
async def test_edit_message_caption_entities(self, bot, media_message):
test_string = "Italic Bold Code"
@ -3804,6 +3827,7 @@ class TestBotWithRequest:
parse_mode=ParseMode.HTML,
reply_to_message_id=media_message.message_id,
reply_markup=keyboard,
show_caption_above_media=False,
)
# we send a temp message which replies to the returned message id in order to get a
# message object

View file

@ -17,31 +17,12 @@
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import datetime
import warnings
import pytest
from telegram import (
Birthdate,
Bot,
BusinessIntro,
BusinessLocation,
BusinessOpeningHours,
BusinessOpeningHoursInterval,
Chat,
ChatLocation,
ChatPermissions,
Location,
ReactionTypeCustomEmoji,
ReactionTypeEmoji,
User,
)
from telegram._chat import _deprecated_attrs
from telegram._utils.datetime import UTC, to_timestamp
from telegram import Bot, Chat, ChatPermissions, ReactionTypeEmoji, User
from telegram.constants import ChatAction, ChatType, ReactionEmoji
from telegram.helpers import escape_markdown
from telegram.warnings import PTBDeprecationWarning
from tests.auxil.bot_method_checks import (
check_defaults_handling,
check_shortcut_call,
@ -57,37 +38,7 @@ def chat(bot):
title=TestChatBase.title,
type=TestChatBase.type_,
username=TestChatBase.username,
sticker_set_name=TestChatBase.sticker_set_name,
can_set_sticker_set=TestChatBase.can_set_sticker_set,
permissions=TestChatBase.permissions,
slow_mode_delay=TestChatBase.slow_mode_delay,
bio=TestChatBase.bio,
linked_chat_id=TestChatBase.linked_chat_id,
location=TestChatBase.location,
has_private_forwards=True,
has_protected_content=True,
has_visible_history=True,
join_to_send_messages=True,
join_by_request=True,
has_restricted_voice_and_video_messages=True,
is_forum=True,
active_usernames=TestChatBase.active_usernames,
emoji_status_custom_emoji_id=TestChatBase.emoji_status_custom_emoji_id,
emoji_status_expiration_date=TestChatBase.emoji_status_expiration_date,
has_aggressive_anti_spam_enabled=TestChatBase.has_aggressive_anti_spam_enabled,
has_hidden_members=TestChatBase.has_hidden_members,
available_reactions=TestChatBase.available_reactions,
accent_color_id=TestChatBase.accent_color_id,
background_custom_emoji_id=TestChatBase.background_custom_emoji_id,
profile_accent_color_id=TestChatBase.profile_accent_color_id,
profile_background_custom_emoji_id=TestChatBase.profile_background_custom_emoji_id,
unrestrict_boost_count=TestChatBase.unrestrict_boost_count,
custom_emoji_sticker_set_name=TestChatBase.custom_emoji_sticker_set_name,
business_intro=TestChatBase.business_intro,
business_location=TestChatBase.business_location,
business_opening_hours=TestChatBase.business_opening_hours,
birthdate=Birthdate(1, 1),
personal_chat=TestChatBase.personal_chat,
first_name=TestChatBase.first_name,
last_name=TestChatBase.last_name,
)
@ -101,48 +52,7 @@ class TestChatBase:
title = "ToledosPalaceBot - Group"
type_ = "group"
username = "username"
all_members_are_administrators = False
sticker_set_name = "stickers"
can_set_sticker_set = False
permissions = ChatPermissions(
can_send_messages=True,
can_change_info=False,
can_invite_users=True,
)
slow_mode_delay = 30
bio = "I'm a Barbie Girl in a Barbie World"
linked_chat_id = 11880
location = ChatLocation(Location(123, 456), "Barbie World")
has_protected_content = True
has_visible_history = True
has_private_forwards = True
join_to_send_messages = True
join_by_request = True
has_restricted_voice_and_video_messages = True
is_forum = True
active_usernames = ["These", "Are", "Usernames!"]
emoji_status_custom_emoji_id = "VeryUniqueCustomEmojiID"
emoji_status_expiration_date = datetime.datetime.now(tz=UTC).replace(microsecond=0)
has_aggressive_anti_spam_enabled = True
has_hidden_members = True
available_reactions = [
ReactionTypeEmoji(ReactionEmoji.THUMBS_DOWN),
ReactionTypeCustomEmoji("custom_emoji_id"),
]
business_intro = BusinessIntro("Title", "Description", None)
business_location = BusinessLocation("Address", Location(123, 456))
business_opening_hours = BusinessOpeningHours(
"Country/City",
[BusinessOpeningHoursInterval(opening, opening + 60) for opening in (0, 24 * 60)],
)
accent_color_id = 1
background_custom_emoji_id = "background_custom_emoji_id"
profile_accent_color_id = 2
profile_background_custom_emoji_id = "profile_background_custom_emoji_id"
unrestrict_boost_count = 100
custom_emoji_sticker_set_name = "custom_emoji_sticker_set_name"
birthdate = Birthdate(1, 1)
personal_chat = Chat(3, "private", "private")
first_name = "first"
last_name = "last"
@ -159,40 +69,7 @@ class TestChatWithoutRequest(TestChatBase):
"title": self.title,
"type": self.type_,
"username": self.username,
"all_members_are_administrators": self.all_members_are_administrators,
"sticker_set_name": self.sticker_set_name,
"can_set_sticker_set": self.can_set_sticker_set,
"permissions": self.permissions.to_dict(),
"slow_mode_delay": self.slow_mode_delay,
"bio": self.bio,
"business_intro": self.business_intro.to_dict(),
"business_location": self.business_location.to_dict(),
"business_opening_hours": self.business_opening_hours.to_dict(),
"has_protected_content": self.has_protected_content,
"has_visible_history": self.has_visible_history,
"has_private_forwards": self.has_private_forwards,
"linked_chat_id": self.linked_chat_id,
"location": self.location.to_dict(),
"join_to_send_messages": self.join_to_send_messages,
"join_by_request": self.join_by_request,
"has_restricted_voice_and_video_messages": (
self.has_restricted_voice_and_video_messages
),
"is_forum": self.is_forum,
"active_usernames": self.active_usernames,
"emoji_status_custom_emoji_id": self.emoji_status_custom_emoji_id,
"emoji_status_expiration_date": to_timestamp(self.emoji_status_expiration_date),
"has_aggressive_anti_spam_enabled": self.has_aggressive_anti_spam_enabled,
"has_hidden_members": self.has_hidden_members,
"available_reactions": [reaction.to_dict() for reaction in self.available_reactions],
"accent_color_id": self.accent_color_id,
"background_custom_emoji_id": self.background_custom_emoji_id,
"profile_accent_color_id": self.profile_accent_color_id,
"profile_background_custom_emoji_id": self.profile_background_custom_emoji_id,
"unrestrict_boost_count": self.unrestrict_boost_count,
"custom_emoji_sticker_set_name": self.custom_emoji_sticker_set_name,
"birthdate": self.birthdate.to_dict(),
"personal_chat": self.personal_chat.to_dict(),
"first_name": self.first_name,
"last_name": self.last_name,
}
@ -202,76 +79,10 @@ class TestChatWithoutRequest(TestChatBase):
assert chat.title == self.title
assert chat.type == self.type_
assert chat.username == self.username
assert chat.sticker_set_name == self.sticker_set_name
assert chat.can_set_sticker_set == self.can_set_sticker_set
assert chat.permissions == self.permissions
assert chat.slow_mode_delay == self.slow_mode_delay
assert chat.bio == self.bio
assert chat.business_intro == self.business_intro
assert chat.business_location == self.business_location
assert chat.business_opening_hours == self.business_opening_hours
assert chat.has_protected_content == self.has_protected_content
assert chat.has_visible_history == self.has_visible_history
assert chat.has_private_forwards == self.has_private_forwards
assert chat.linked_chat_id == self.linked_chat_id
assert chat.location.location == self.location.location
assert chat.location.address == self.location.address
assert chat.join_to_send_messages == self.join_to_send_messages
assert chat.join_by_request == self.join_by_request
assert (
chat.has_restricted_voice_and_video_messages
== self.has_restricted_voice_and_video_messages
)
assert chat.api_kwargs == {
"all_members_are_administrators": self.all_members_are_administrators
}
assert chat.is_forum == self.is_forum
assert chat.active_usernames == tuple(self.active_usernames)
assert chat.emoji_status_custom_emoji_id == self.emoji_status_custom_emoji_id
assert chat.emoji_status_expiration_date == (self.emoji_status_expiration_date)
assert chat.has_aggressive_anti_spam_enabled == self.has_aggressive_anti_spam_enabled
assert chat.has_hidden_members == self.has_hidden_members
assert chat.available_reactions == tuple(self.available_reactions)
assert chat.accent_color_id == self.accent_color_id
assert chat.background_custom_emoji_id == self.background_custom_emoji_id
assert chat.profile_accent_color_id == self.profile_accent_color_id
assert chat.profile_background_custom_emoji_id == self.profile_background_custom_emoji_id
assert chat.unrestrict_boost_count == self.unrestrict_boost_count
assert chat.custom_emoji_sticker_set_name == self.custom_emoji_sticker_set_name
assert chat.birthdate == self.birthdate
assert chat.personal_chat == self.personal_chat
assert chat.first_name == self.first_name
assert chat.last_name == self.last_name
def test_de_json_localization(self, bot, raw_bot, tz_bot):
json_dict = {
"id": self.id_,
"type": self.type_,
"emoji_status_expiration_date": to_timestamp(self.emoji_status_expiration_date),
}
chat_bot = Chat.de_json(json_dict, bot)
chat_bot_raw = Chat.de_json(json_dict, raw_bot)
chat_bot_tz = Chat.de_json(json_dict, tz_bot)
# comparing utcoffsets because comparing tzinfo objects is not reliable
emoji_expire_offset = chat_bot_tz.emoji_status_expiration_date.utcoffset()
emoji_expire_offset_tz = tz_bot.defaults.tzinfo.utcoffset(
chat_bot_tz.emoji_status_expiration_date.replace(tzinfo=None)
)
assert chat_bot.emoji_status_expiration_date.tzinfo == UTC
assert chat_bot_raw.emoji_status_expiration_date.tzinfo == UTC
assert emoji_expire_offset_tz == emoji_expire_offset
def test_always_tuples_attributes(self):
chat = Chat(
id=123,
title="title",
type=Chat.PRIVATE,
)
assert isinstance(chat.active_usernames, tuple)
assert chat.active_usernames == ()
def test_to_dict(self, chat):
chat_dict = chat.to_dict()
@ -280,67 +91,10 @@ class TestChatWithoutRequest(TestChatBase):
assert chat_dict["title"] == chat.title
assert chat_dict["type"] == chat.type
assert chat_dict["username"] == chat.username
assert chat_dict["permissions"] == chat.permissions.to_dict()
assert chat_dict["slow_mode_delay"] == chat.slow_mode_delay
assert chat_dict["bio"] == chat.bio
assert chat_dict["business_intro"] == chat.business_intro.to_dict()
assert chat_dict["business_location"] == chat.business_location.to_dict()
assert chat_dict["business_opening_hours"] == chat.business_opening_hours.to_dict()
assert chat_dict["has_private_forwards"] == chat.has_private_forwards
assert chat_dict["has_protected_content"] == chat.has_protected_content
assert chat_dict["has_visible_history"] == chat.has_visible_history
assert chat_dict["linked_chat_id"] == chat.linked_chat_id
assert chat_dict["location"] == chat.location.to_dict()
assert chat_dict["join_to_send_messages"] == chat.join_to_send_messages
assert chat_dict["join_by_request"] == chat.join_by_request
assert (
chat_dict["has_restricted_voice_and_video_messages"]
== chat.has_restricted_voice_and_video_messages
)
assert chat_dict["is_forum"] == chat.is_forum
assert chat_dict["active_usernames"] == list(chat.active_usernames)
assert chat_dict["emoji_status_custom_emoji_id"] == chat.emoji_status_custom_emoji_id
assert chat_dict["emoji_status_expiration_date"] == to_timestamp(
chat.emoji_status_expiration_date
)
assert (
chat_dict["has_aggressive_anti_spam_enabled"] == chat.has_aggressive_anti_spam_enabled
)
assert chat_dict["has_hidden_members"] == chat.has_hidden_members
assert chat_dict["available_reactions"] == [
reaction.to_dict() for reaction in chat.available_reactions
]
assert chat_dict["accent_color_id"] == chat.accent_color_id
assert chat_dict["background_custom_emoji_id"] == chat.background_custom_emoji_id
assert chat_dict["profile_accent_color_id"] == chat.profile_accent_color_id
assert (
chat_dict["profile_background_custom_emoji_id"]
== chat.profile_background_custom_emoji_id
)
assert chat_dict["custom_emoji_sticker_set_name"] == chat.custom_emoji_sticker_set_name
assert chat_dict["unrestrict_boost_count"] == chat.unrestrict_boost_count
assert chat_dict["birthdate"] == chat.birthdate.to_dict()
assert chat_dict["personal_chat"] == chat.personal_chat.to_dict()
assert chat_dict["first_name"] == chat.first_name
assert chat_dict["last_name"] == chat.last_name
def test_deprecated_attributes(self, chat):
for depr_attr in _deprecated_attrs:
with pytest.warns(PTBDeprecationWarning, match="deprecated and will only be accessib"):
getattr(chat, depr_attr)
with warnings.catch_warnings(): # No warning should be raised
warnings.simplefilter("error")
chat.id
chat.first_name
def test_deprecated_arguments(self):
for depr_attr in _deprecated_attrs:
with pytest.warns(PTBDeprecationWarning, match="deprecated and will only be availabl"):
Chat(1, "type", **{depr_attr: "1"})
with warnings.catch_warnings(): # No warning should be raised
warnings.simplefilter("error")
Chat(1, "type", first_name="first_name")
def test_enum_init(self):
chat = Chat(id=1, type="foo")
assert chat.type == "foo"
@ -957,8 +711,8 @@ class TestChatWithoutRequest(TestChatBase):
return from_chat_id and message_id and chat_id
assert check_shortcut_signature(Chat.send_copy, Bot.copy_message, ["chat_id"], [])
assert await check_shortcut_call(chat.copy_message, chat.get_bot(), "copy_message")
assert await check_defaults_handling(chat.copy_message, chat.get_bot())
assert await check_shortcut_call(chat.send_copy, chat.get_bot(), "copy_message")
assert await check_defaults_handling(chat.send_copy, chat.get_bot())
monkeypatch.setattr(chat.get_bot(), "copy_message", make_assertion)
assert await chat.send_copy(from_chat_id="test_copy", message_id=42)

View file

@ -17,7 +17,6 @@
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import datetime
import warnings
import pytest
@ -35,7 +34,6 @@ from telegram import (
ReactionTypeCustomEmoji,
ReactionTypeEmoji,
)
from telegram._chat import _deprecated_attrs
from telegram._utils.datetime import UTC, to_timestamp
from telegram.constants import ReactionEmoji
from tests.auxil.slots import mro_slots
@ -44,19 +42,19 @@ from tests.auxil.slots import mro_slots
@pytest.fixture(scope="module")
def chat_full_info(bot):
chat = ChatFullInfo(
TestChatInfoBase.id_,
type=TestChatInfoBase.type_,
accent_color_id=TestChatInfoBase.accent_color_id,
max_reaction_count=TestChatInfoBase.max_reaction_count,
title=TestChatInfoBase.title,
username=TestChatInfoBase.username,
sticker_set_name=TestChatInfoBase.sticker_set_name,
can_set_sticker_set=TestChatInfoBase.can_set_sticker_set,
permissions=TestChatInfoBase.permissions,
slow_mode_delay=TestChatInfoBase.slow_mode_delay,
bio=TestChatInfoBase.bio,
linked_chat_id=TestChatInfoBase.linked_chat_id,
location=TestChatInfoBase.location,
TestChatFullInfoBase.id_,
type=TestChatFullInfoBase.type_,
accent_color_id=TestChatFullInfoBase.accent_color_id,
max_reaction_count=TestChatFullInfoBase.max_reaction_count,
title=TestChatFullInfoBase.title,
username=TestChatFullInfoBase.username,
sticker_set_name=TestChatFullInfoBase.sticker_set_name,
can_set_sticker_set=TestChatFullInfoBase.can_set_sticker_set,
permissions=TestChatFullInfoBase.permissions,
slow_mode_delay=TestChatFullInfoBase.slow_mode_delay,
bio=TestChatFullInfoBase.bio,
linked_chat_id=TestChatFullInfoBase.linked_chat_id,
location=TestChatFullInfoBase.location,
has_private_forwards=True,
has_protected_content=True,
has_visible_history=True,
@ -64,35 +62,37 @@ def chat_full_info(bot):
join_by_request=True,
has_restricted_voice_and_video_messages=True,
is_forum=True,
active_usernames=TestChatInfoBase.active_usernames,
emoji_status_custom_emoji_id=TestChatInfoBase.emoji_status_custom_emoji_id,
emoji_status_expiration_date=TestChatInfoBase.emoji_status_expiration_date,
has_aggressive_anti_spam_enabled=TestChatInfoBase.has_aggressive_anti_spam_enabled,
has_hidden_members=TestChatInfoBase.has_hidden_members,
available_reactions=TestChatInfoBase.available_reactions,
background_custom_emoji_id=TestChatInfoBase.background_custom_emoji_id,
profile_accent_color_id=TestChatInfoBase.profile_accent_color_id,
profile_background_custom_emoji_id=TestChatInfoBase.profile_background_custom_emoji_id,
unrestrict_boost_count=TestChatInfoBase.unrestrict_boost_count,
custom_emoji_sticker_set_name=TestChatInfoBase.custom_emoji_sticker_set_name,
business_intro=TestChatInfoBase.business_intro,
business_location=TestChatInfoBase.business_location,
business_opening_hours=TestChatInfoBase.business_opening_hours,
active_usernames=TestChatFullInfoBase.active_usernames,
emoji_status_custom_emoji_id=TestChatFullInfoBase.emoji_status_custom_emoji_id,
emoji_status_expiration_date=TestChatFullInfoBase.emoji_status_expiration_date,
has_aggressive_anti_spam_enabled=TestChatFullInfoBase.has_aggressive_anti_spam_enabled,
has_hidden_members=TestChatFullInfoBase.has_hidden_members,
available_reactions=TestChatFullInfoBase.available_reactions,
background_custom_emoji_id=TestChatFullInfoBase.background_custom_emoji_id,
profile_accent_color_id=TestChatFullInfoBase.profile_accent_color_id,
profile_background_custom_emoji_id=TestChatFullInfoBase.profile_background_custom_emoji_id,
unrestrict_boost_count=TestChatFullInfoBase.unrestrict_boost_count,
custom_emoji_sticker_set_name=TestChatFullInfoBase.custom_emoji_sticker_set_name,
business_intro=TestChatFullInfoBase.business_intro,
business_location=TestChatFullInfoBase.business_location,
business_opening_hours=TestChatFullInfoBase.business_opening_hours,
birthdate=Birthdate(1, 1),
personal_chat=TestChatInfoBase.personal_chat,
personal_chat=TestChatFullInfoBase.personal_chat,
first_name="first_name",
last_name="last_name",
)
chat.set_bot(bot)
chat._unfreeze()
return chat
class TestChatInfoBase:
# Shortcut methods are tested in test_chat.py.
class TestChatFullInfoBase:
id_ = -28767330
max_reaction_count = 2
title = "ToledosPalaceBot - Group"
type_ = "group"
username = "username"
all_members_are_administrators = False
sticker_set_name = "stickers"
can_set_sticker_set = False
permissions = ChatPermissions(
@ -134,13 +134,16 @@ class TestChatInfoBase:
custom_emoji_sticker_set_name = "custom_emoji_sticker_set_name"
birthdate = Birthdate(1, 1)
personal_chat = Chat(3, "private", "private")
first_name = "first_name"
last_name = "last_name"
class TestChatWithoutRequest(TestChatInfoBase):
class TestChatFullInfoWithoutRequest(TestChatFullInfoBase):
def test_slot_behaviour(self, chat_full_info):
cfi = chat_full_info
for attr in cfi.__slots__:
assert getattr(cfi, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(cfi)) == len(set(mro_slots(cfi))), "duplicate slot"
def test_de_json(self, bot):
@ -151,7 +154,6 @@ class TestChatWithoutRequest(TestChatInfoBase):
"accent_color_id": self.accent_color_id,
"max_reaction_count": self.max_reaction_count,
"username": self.username,
"all_members_are_administrators": self.all_members_are_administrators,
"sticker_set_name": self.sticker_set_name,
"can_set_sticker_set": self.can_set_sticker_set,
"permissions": self.permissions.to_dict(),
@ -184,26 +186,134 @@ class TestChatWithoutRequest(TestChatInfoBase):
"custom_emoji_sticker_set_name": self.custom_emoji_sticker_set_name,
"birthdate": self.birthdate.to_dict(),
"personal_chat": self.personal_chat.to_dict(),
"first_name": self.first_name,
"last_name": self.last_name,
}
cfi = ChatFullInfo.de_json(json_dict, bot)
assert cfi.id == self.id_
assert cfi.title == self.title
assert cfi.type == self.type_
assert cfi.username == self.username
assert cfi.sticker_set_name == self.sticker_set_name
assert cfi.can_set_sticker_set == self.can_set_sticker_set
assert cfi.permissions == self.permissions
assert cfi.slow_mode_delay == self.slow_mode_delay
assert cfi.bio == self.bio
assert cfi.business_intro == self.business_intro
assert cfi.business_location == self.business_location
assert cfi.business_opening_hours == self.business_opening_hours
assert cfi.has_protected_content == self.has_protected_content
assert cfi.has_visible_history == self.has_visible_history
assert cfi.has_private_forwards == self.has_private_forwards
assert cfi.linked_chat_id == self.linked_chat_id
assert cfi.location.location == self.location.location
assert cfi.location.address == self.location.address
assert cfi.join_to_send_messages == self.join_to_send_messages
assert cfi.join_by_request == self.join_by_request
assert (
cfi.has_restricted_voice_and_video_messages
== self.has_restricted_voice_and_video_messages
)
assert cfi.is_forum == self.is_forum
assert cfi.active_usernames == tuple(self.active_usernames)
assert cfi.emoji_status_custom_emoji_id == self.emoji_status_custom_emoji_id
assert cfi.emoji_status_expiration_date == (self.emoji_status_expiration_date)
assert cfi.has_aggressive_anti_spam_enabled == self.has_aggressive_anti_spam_enabled
assert cfi.has_hidden_members == self.has_hidden_members
assert cfi.available_reactions == tuple(self.available_reactions)
assert cfi.accent_color_id == self.accent_color_id
assert cfi.background_custom_emoji_id == self.background_custom_emoji_id
assert cfi.profile_accent_color_id == self.profile_accent_color_id
assert cfi.profile_background_custom_emoji_id == self.profile_background_custom_emoji_id
assert cfi.unrestrict_boost_count == self.unrestrict_boost_count
assert cfi.custom_emoji_sticker_set_name == self.custom_emoji_sticker_set_name
assert cfi.birthdate == self.birthdate
assert cfi.personal_chat == self.personal_chat
assert cfi.first_name == self.first_name
assert cfi.last_name == self.last_name
assert cfi.max_reaction_count == self.max_reaction_count
def test_de_json_localization(self, bot, raw_bot, tz_bot):
json_dict = {
"id": self.id_,
"type": self.type_,
"accent_color_id": self.accent_color_id,
"max_reaction_count": self.max_reaction_count,
"emoji_status_expiration_date": to_timestamp(self.emoji_status_expiration_date),
}
cfi_bot = ChatFullInfo.de_json(json_dict, bot)
cfi_bot_raw = ChatFullInfo.de_json(json_dict, raw_bot)
cfi_bot_tz = ChatFullInfo.de_json(json_dict, tz_bot)
# comparing utcoffsets because comparing tzinfo objects is not reliable
emoji_expire_offset = cfi_bot_tz.emoji_status_expiration_date.utcoffset()
emoji_expire_offset_tz = tz_bot.defaults.tzinfo.utcoffset(
cfi_bot_tz.emoji_status_expiration_date.replace(tzinfo=None)
)
assert cfi_bot.emoji_status_expiration_date.tzinfo == UTC
assert cfi_bot_raw.emoji_status_expiration_date.tzinfo == UTC
assert emoji_expire_offset_tz == emoji_expire_offset
def test_to_dict(self, chat_full_info):
cfi = chat_full_info
cfi_dict = cfi.to_dict()
assert isinstance(cfi_dict, dict)
assert cfi_dict["id"] == cfi.id
assert cfi_dict["title"] == cfi.title
assert cfi_dict["type"] == cfi.type
assert cfi_dict["username"] == cfi.username
assert cfi_dict["permissions"] == cfi.permissions.to_dict()
assert cfi_dict["slow_mode_delay"] == cfi.slow_mode_delay
assert cfi_dict["bio"] == cfi.bio
assert cfi_dict["business_intro"] == cfi.business_intro.to_dict()
assert cfi_dict["business_location"] == cfi.business_location.to_dict()
assert cfi_dict["business_opening_hours"] == cfi.business_opening_hours.to_dict()
assert cfi_dict["has_private_forwards"] == cfi.has_private_forwards
assert cfi_dict["has_protected_content"] == cfi.has_protected_content
assert cfi_dict["has_visible_history"] == cfi.has_visible_history
assert cfi_dict["linked_chat_id"] == cfi.linked_chat_id
assert cfi_dict["location"] == cfi.location.to_dict()
assert cfi_dict["join_to_send_messages"] == cfi.join_to_send_messages
assert cfi_dict["join_by_request"] == cfi.join_by_request
assert (
cfi_dict["has_restricted_voice_and_video_messages"]
== cfi.has_restricted_voice_and_video_messages
)
assert cfi_dict["is_forum"] == cfi.is_forum
assert cfi_dict["active_usernames"] == list(cfi.active_usernames)
assert cfi_dict["emoji_status_custom_emoji_id"] == cfi.emoji_status_custom_emoji_id
assert cfi_dict["emoji_status_expiration_date"] == to_timestamp(
cfi.emoji_status_expiration_date
)
assert cfi_dict["has_aggressive_anti_spam_enabled"] == cfi.has_aggressive_anti_spam_enabled
assert cfi_dict["has_hidden_members"] == cfi.has_hidden_members
assert cfi_dict["available_reactions"] == [
reaction.to_dict() for reaction in cfi.available_reactions
]
assert cfi_dict["accent_color_id"] == cfi.accent_color_id
assert cfi_dict["background_custom_emoji_id"] == cfi.background_custom_emoji_id
assert cfi_dict["profile_accent_color_id"] == cfi.profile_accent_color_id
assert (
cfi_dict["profile_background_custom_emoji_id"]
== cfi.profile_background_custom_emoji_id
)
assert cfi_dict["custom_emoji_sticker_set_name"] == cfi.custom_emoji_sticker_set_name
assert cfi_dict["unrestrict_boost_count"] == cfi.unrestrict_boost_count
assert cfi_dict["birthdate"] == cfi.birthdate.to_dict()
assert cfi_dict["personal_chat"] == cfi.personal_chat.to_dict()
assert cfi_dict["first_name"] == cfi.first_name
assert cfi_dict["last_name"] == cfi.last_name
assert cfi_dict["max_reaction_count"] == cfi.max_reaction_count
def test_attr_access_no_warning(self, chat_full_info):
cfi = chat_full_info
for depr_attr in _deprecated_attrs:
with warnings.catch_warnings(): # No warning should be raised
warnings.simplefilter("error")
getattr(cfi, depr_attr)
def test_cfi_creation_no_warning(self, chat_full_info):
cfi = chat_full_info
with warnings.catch_warnings():
dict = cfi.to_dict()
ChatFullInfo(**dict)
def test_always_tuples_attributes(self):
cfi = ChatFullInfo(
id=123,
type=Chat.PRIVATE,
accent_color_id=1,
max_reaction_count=2,
)
assert isinstance(cfi.active_usernames, tuple)
assert cfi.active_usernames == ()

View file

@ -173,10 +173,9 @@ class TestConstantsWithoutRequest:
"is_accessible",
"quote",
"external_reply",
# attribute is deprecated, no need to add it to MessageType
"user_shared",
"via_bot",
"is_from_offline",
"show_caption_above_media",
}
@pytest.mark.parametrize(

View file

@ -273,6 +273,8 @@ def message(bot):
{"sender_business_bot": User(1, "BusinessBot", True)},
{"business_connection_id": "123456789"},
{"chat_background_set": ChatBackground(type=BackgroundTypeChatTheme("ice"))},
{"effect_id": "123456789"},
{"show_caption_above_media": True},
],
ids=[
"reply",
@ -342,6 +344,8 @@ def message(bot):
"business_connection_id",
"is_from_offline",
"chat_background_set",
"effect_id",
"show_caption_above_media",
],
)
def message_params(bot, request):
@ -399,11 +403,12 @@ class TestMessageBase:
{"length": 2, "offset": 150, "type": "custom_emoji", "custom_emoji_id": "1"},
{"length": 34, "offset": 154, "type": "blockquote"},
{"length": 6, "offset": 181, "type": "bold"},
{"length": 33, "offset": 190, "type": "expandable_blockquote"},
]
test_text_v2 = (
r"Test for <bold, ita_lic, \`code, links, text-mention and `\pre. "
"http://google.com and bold nested in strk>trgh nested in italic. Python pre. Spoiled. "
"👍.\nMultiline\nblock quote\nwith nested."
"👍.\nMultiline\nblock quote\nwith nested.\n\nMultiline\nexpandable\nblock quote."
)
test_message = Message(
message_id=1,
@ -728,7 +733,8 @@ class TestMessageWithoutRequest(TestMessageBase):
'<pre><code class="python">Python pre</code></pre>. '
'<span class="tg-spoiler">Spoiled</span>. '
'<tg-emoji emoji-id="1">👍</tg-emoji>.\n'
"<blockquote>Multiline\nblock quote\nwith <b>nested</b>.</blockquote>"
"<blockquote>Multiline\nblock quote\nwith <b>nested</b>.</blockquote>\n\n"
"<blockquote expandable>Multiline\nexpandable\nblock quote.</blockquote>"
)
text_html = self.test_message_v2.text_html
assert text_html == test_html_string
@ -749,7 +755,8 @@ class TestMessageWithoutRequest(TestMessageBase):
'<pre><code class="python">Python pre</code></pre>. '
'<span class="tg-spoiler">Spoiled</span>. '
'<tg-emoji emoji-id="1">👍</tg-emoji>.\n'
"<blockquote>Multiline\nblock quote\nwith <b>nested</b>.</blockquote>"
"<blockquote>Multiline\nblock quote\nwith <b>nested</b>.</blockquote>\n\n"
"<blockquote expandable>Multiline\nexpandable\nblock quote.</blockquote>"
)
text_html = self.test_message_v2.text_html_urled
assert text_html == test_html_string
@ -774,6 +781,9 @@ class TestMessageWithoutRequest(TestMessageBase):
">Multiline\n"
">block quote\n"
r">with *nested*\."
"\n\n>Multiline\n"
">expandable\n"
r">block quote\.||"
)
text_markdown = self.test_message_v2.text_markdown_v2
assert text_markdown == test_md_string
@ -830,6 +840,9 @@ class TestMessageWithoutRequest(TestMessageBase):
">Multiline\n"
">block quote\n"
r">with *nested*\."
"\n\n>Multiline\n"
">expandable\n"
r">block quote\.||"
)
text_markdown = self.test_message_v2.text_markdown_v2_urled
assert text_markdown == test_md_string
@ -946,7 +959,8 @@ class TestMessageWithoutRequest(TestMessageBase):
'<pre><code class="python">Python pre</code></pre>. '
'<span class="tg-spoiler">Spoiled</span>. '
'<tg-emoji emoji-id="1">👍</tg-emoji>.\n'
"<blockquote>Multiline\nblock quote\nwith <b>nested</b>.</blockquote>"
"<blockquote>Multiline\nblock quote\nwith <b>nested</b>.</blockquote>\n\n"
"<blockquote expandable>Multiline\nexpandable\nblock quote.</blockquote>"
)
caption_html = self.test_message_v2.caption_html
assert caption_html == test_html_string
@ -967,7 +981,8 @@ class TestMessageWithoutRequest(TestMessageBase):
'<pre><code class="python">Python pre</code></pre>. '
'<span class="tg-spoiler">Spoiled</span>. '
'<tg-emoji emoji-id="1">👍</tg-emoji>.\n'
"<blockquote>Multiline\nblock quote\nwith <b>nested</b>.</blockquote>"
"<blockquote>Multiline\nblock quote\nwith <b>nested</b>.</blockquote>\n\n"
"<blockquote expandable>Multiline\nexpandable\nblock quote.</blockquote>"
)
caption_html = self.test_message_v2.caption_html_urled
assert caption_html == test_html_string
@ -992,6 +1007,9 @@ class TestMessageWithoutRequest(TestMessageBase):
">Multiline\n"
">block quote\n"
r">with *nested*\."
"\n\n>Multiline\n"
">expandable\n"
r">block quote\.||"
)
caption_markdown = self.test_message_v2.caption_markdown_v2
assert caption_markdown == test_md_string
@ -1023,6 +1041,9 @@ class TestMessageWithoutRequest(TestMessageBase):
">Multiline\n"
">block quote\n"
r">with *nested*\."
"\n\n>Multiline\n"
">expandable\n"
r">block quote\.||"
)
caption_markdown = self.test_message_v2.caption_markdown_v2_urled
assert caption_markdown == test_md_string
@ -1484,6 +1505,9 @@ class TestMessageWithoutRequest(TestMessageBase):
">Multiline\n"
">block quote\n"
r">with *nested*\."
"\n\n>Multiline\n"
">expandable\n"
r">block quote\.||"
)
async def make_assertion(*_, **kwargs):
@ -1534,7 +1558,8 @@ class TestMessageWithoutRequest(TestMessageBase):
'<pre><code class="python">Python pre</code></pre>. '
'<span class="tg-spoiler">Spoiled</span>. '
'<tg-emoji emoji-id="1">👍</tg-emoji>.\n'
"<blockquote>Multiline\nblock quote\nwith <b>nested</b>.</blockquote>"
"<blockquote>Multiline\nblock quote\nwith <b>nested</b>.</blockquote>\n\n"
"<blockquote expandable>Multiline\nexpandable\nblock quote.</blockquote>"
)
async def make_assertion(*_, **kwargs):
@ -2599,7 +2624,9 @@ class TestMessageWithoutRequest(TestMessageBase):
async def test_default_do_quote(
self, bot, message, default_quote, chat_type, expected, monkeypatch
):
message.set_bot(PytestExtBot(token=bot.token, defaults=Defaults(do_quote=default_quote)))
original_bot = message.get_bot()
temp_bot = PytestExtBot(token=bot.token, defaults=Defaults(do_quote=default_quote))
message.set_bot(temp_bot)
async def make_assertion(*_, **kwargs):
reply_parameters = kwargs.get("reply_parameters") or ReplyParameters(message_id=False)
@ -2612,7 +2639,7 @@ class TestMessageWithoutRequest(TestMessageBase):
message.chat.type = chat_type
assert await message.reply_text("test")
finally:
message.get_bot()._defaults = None
message.set_bot(original_bot)
async def test_edit_forum_topic(self, monkeypatch, message):
async def make_assertion(*_, **kwargs):

View file

@ -35,9 +35,4 @@ def _change_test_dir(request, monkeypatch):
@skip_disabled
def test_build():
assert os.system("python setup.py bdist_dumb") == 0 # pragma: no cover
@skip_disabled
def test_build_raw():
assert os.system("python setup_raw.py bdist_dumb") == 0 # pragma: no cover
assert os.system("python -m build") == 0 # pragma: no cover

View file

@ -20,7 +20,6 @@
from telegram import Animation, Audio, Document, PhotoSize, Sticker, Video, VideoNote, Voice
from telegram._chat import _deprecated_attrs
from tests.test_official.helpers import _get_params_base
IGNORED_OBJECTS = ("ResponseParameters",)
@ -174,7 +173,7 @@ def ignored_param_requirements(object_name: str) -> set[str]:
# Arguments that are optional arguments for now for backwards compatibility
BACKWARDS_COMPAT_KWARGS: dict[str, set[str]] = {
"Chat": set(_deprecated_attrs), # removed by bot api 7.3
"send_invoice|create_invoice_link|InputInvoiceMessageContent": {"provider_token"}
}

View file

@ -342,10 +342,14 @@ class TestTelegramObject:
chat = (await pp.get_chat_data())[1]
assert chat.id == 1
assert chat.type == Chat.PRIVATE
assert chat.api_kwargs == {
api_kwargs_expected = {
"all_members_are_administrators": True,
"something": "Manually inserted",
}
# There are older attrs in Chat's api_kwargs which are present but we don't care about them
for k, v in api_kwargs_expected.items():
assert chat.api_kwargs[k] == v
with pytest.raises(AttributeError):
# removed attribute should not be available as attribute, only though api_kwargs
chat.all_members_are_administrators

View file

@ -439,8 +439,8 @@ class TestUserWithoutRequest(TestUserBase):
return from_chat_id and message_id and user_id
assert check_shortcut_signature(User.send_copy, Bot.copy_message, ["chat_id"], [])
assert await check_shortcut_call(user.copy_message, user.get_bot(), "copy_message")
assert await check_defaults_handling(user.copy_message, user.get_bot())
assert await check_shortcut_call(user.send_copy, user.get_bot(), "copy_message")
assert await check_defaults_handling(user.send_copy, user.get_bot())
monkeypatch.setattr(user.get_bot(), "copy_message", make_assertion)
assert await user.send_copy(from_chat_id="from_chat_id", message_id="message_id")
@ -700,3 +700,18 @@ class TestUserWithoutRequest(TestUserBase):
monkeypatch.setattr(user.get_bot(), "forward_messages", make_assertion)
assert await user.forward_messages_to(chat_id="test_forwards", message_ids=(42, 43))
async def test_instance_method_refund_star_payment(self, monkeypatch, user):
async def make_assertion(*_, **kwargs):
return kwargs["user_id"] == user.id and kwargs["telegram_payment_charge_id"] == 42
assert check_shortcut_signature(
user.refund_star_payment, Bot.refund_star_payment, ["user_id"], []
)
assert await check_shortcut_call(
user.refund_star_payment, user.get_bot(), "refund_star_payment"
)
assert await check_defaults_handling(user.refund_star_payment, user.get_bot())
monkeypatch.setattr(user.get_bot(), "refund_star_payment", make_assertion)
assert await user.refund_star_payment(telegram_payment_charge_id=42)