Compare commits

...

7 Commits

Author SHA1 Message Date
Hinrich Mahler 132a3b203d Bump version to v13.13 2022-06-28 19:54:45 +02:00
Poolitzer debe86aea2 API 6.1 (#3117)
Co-authored-by: Harshil <37377066+harshil21@users.noreply.github.com>
2022-06-28 19:34:12 +02:00
Poolitzer 7a7465e8e2 Fix: python_requires ammending 2022-06-12 15:35:58 +02:00
Poolitzer 977c54e693 Fix: Making clear that we drop python 3.6 2022-06-12 15:27:50 +02:00
Hinrich Mahler a769861b32 Bump version to v13.11 2022-05-26 15:15:40 +02:00
Bibo-Joshi 0a00a821cb Documentation Improvements (#3029)
Co-authored-by: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com>

Co-authored-by: Sukka <isukkaw@gmail.com>
2022-05-26 11:10:45 +02:00
Poolitzer ef2a0527fe API 6.0 (#3027)
Co-authored-by: Harshil <37377066+harshil21@users.noreply.github.com>
2022-05-25 17:40:01 +02:00
102 changed files with 3297 additions and 348 deletions
+2 -2
View File
@@ -9,9 +9,9 @@ body:
value: |
Hey there, you have a question? We are happy to answer. Please make sure no similar question was opened already.
To make it easier for us to help you, please read this [article](https://git.io/JURJO).
To make it easier for us to help you, please read this [article](https://github.com/python-telegram-bot/python-telegram-bot/wiki/Ask-Right).
Please mind that there is also a users' [Telegram group](https://t.me/pythontelegrambotgroup) for questions about the library. Questions asked there might be answered quicker than here. Moreover, [GitHub Discussions](https://git.io/JG3rk) offer a slightly better format to discuss usage questions.
Please mind that there is also a users' [Telegram group](https://t.me/pythontelegrambotgroup) for questions about the library. Questions asked there might be answered quicker than here. Moreover, [GitHub Discussions](https://github.com/python-telegram-bot/python-telegram-bot/discussions) offer a slightly better format to discuss usage questions.
- type: textarea
id: issue-faced
+3 -25
View File
@@ -3,9 +3,11 @@ on:
pull_request:
branches:
- master
- v13.x
push:
branches:
- master
- v13.x
jobs:
pytest:
@@ -13,7 +15,7 @@ jobs:
runs-on: ${{matrix.os}}
strategy:
matrix:
python-version: [3.6, 3.7, 3.8, 3.9]
python-version: [3.7, 3.8, 3.9]
os: [ubuntu-latest, windows-latest, macos-latest]
fail-fast: False
steps:
@@ -94,27 +96,3 @@ jobs:
env:
TEST_OFFICIAL: "true"
shell: bash --noprofile --norc {0}
test_pre_commit:
name: test-pre-commit
runs-on: ${{matrix.os}}
strategy:
matrix:
python-version: [3.7]
os: [ubuntu-latest]
fail-fast: False
steps:
- uses: actions/checkout@v2
- name: Initialize vendored libs
run:
git submodule update --init --recursive
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- 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-dev.txt
- name: Run pre-commit tests
run: pre-commit run --all-files
+6 -1
View File
@@ -1,9 +1,14 @@
# Make sure that
# * the revs specified here match requirements-dev.txt
# * the additional_dependencies here match requirements.txt
ci:
# We currently only need this behavior on the v13.x branch were we have the vendored urllib
submodules: true
repos:
- repo: https://github.com/psf/black
rev: 20.8b1
rev: 22.3.0
hooks:
- id: black
args:
+35 -2
View File
@@ -2,6 +2,39 @@
Changelog
=========
Version 13.13
=============
*Released 2022-06-28*
This is the technical changelog for version 13.13. More elaborate release notes can be found in the news channel `@pythontelegrambotchannel <https://t.me/pythontelegrambotchannel>`_.
**Major Changes:**
- Full Support for API 6.1 (`#3117`_)
.. _`#3117`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3117
Version 13.12
=============
*Released 2022-05-26*
This is the technical changelog for version 13.12. More elaborate release notes can be found in the news channel `@pythontelegrambotchannel <https://t.me/pythontelegrambotchannel>`_.
**Breaking changes:**
- Drop support for python 3.6
**Major Changes:**
- Full Support for API 6.0 (`#3027`_)
**Minor Changes:**
- Documentation Improvements (`#3029`_)
.. _`#3027`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3027
.. _`#3029`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3029
Version 13.11
=============
*Released 2022-02-02*
@@ -895,7 +928,7 @@ Expect minor releases soon (mainly complete Bot API 4.4 support)
- Error Handler now handles all types of exceptions (`#1485`_)
- Return UTC from from_timestamp() (`#1485`_)
**See the wiki page at https://git.io/fxJuV for a detailed guide on how to migrate from version 11 to version 12.**
**See the wiki page at https://github.com/python-telegram-bot/python-telegram-bot/wiki/Transition-guide-to-Version-12.0 for a detailed guide on how to migrate from version 11 to version 12.**
Context based callbacks (`#1100`_)
----------------------------------
@@ -1100,7 +1133,7 @@ Non Bot API 4.0 changes:
.. _`#1172`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1172
.. _`#1179`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1179
.. _`#1184`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1184
.. _`our telegram passport wiki page`: https://git.io/fAvYd
.. _`our telegram passport wiki page`: https://github.com/python-telegram-bot/python-telegram-bot/wiki/Telegram-Passport
Version 10.1.0
==============
+7 -6
View File
@@ -20,7 +20,7 @@ We have a vibrant community of developers helping each other in our `Telegram gr
:target: https://pypi.org/project/python-telegram-bot/
:alt: Supported Python versions
.. image:: https://img.shields.io/badge/Bot%20API-5.7-blue?logo=telegram
.. image:: https://img.shields.io/badge/Bot%20API-6.1-blue?logo=telegram
:target: https://core.telegram.org/bots/api-changelog
:alt: Supported Bot API versions
@@ -87,19 +87,20 @@ Table of contents
- `License`_
============
Introduction
============
This library provides a pure Python interface for the
`Telegram Bot API <https://core.telegram.org/bots/api>`_.
It's compatible with Python versions 3.6.8+. PTB might also work on `PyPy <http://pypy.org/>`_, though there have been a lot of issues before. Hence, PyPy is not officially supported.
It's compatible with Python versions 3.7+. PTB might also work on `PyPy <http://pypy.org/>`_, though there have been a lot of issues before. Hence, PyPy is not officially supported.
In addition to the pure API implementation, this library features a number of high-level classes to
make the development of bots easy and straightforward. These classes are contained in the
``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>`_
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/v13.x/README_RAW.rst>`_
----
Note
@@ -111,7 +112,7 @@ Installing both ``python-telegram-bot`` and ``python-telegram-bot-raw`` in conju
Telegram API support
====================
All types and methods of the Telegram Bot API **5.7** are supported.
All types and methods of the Telegram Bot API **6.1** are supported.
==========
Installing
@@ -171,7 +172,7 @@ look at ``echobot.py``, it is the de facto base for most of the bots out there.
the code for these examples are released to the public domain, so you can start by grabbing the
code and building on top of it.
Visit `this page <https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/README.md>`_ to discover the official examples or look at the examples on the `wiki <https://github.com/python-telegram-bot/python-telegram-bot/wiki/Examples>`_ to see other bots the community has built.
Visit `this page <https://github.com/python-telegram-bot/python-telegram-bot/blob/v13.x/examples/README.md>`_ to discover the official examples or look at the examples on the `wiki <https://github.com/python-telegram-bot/python-telegram-bot/wiki/Examples>`_ to see other bots the community has built.
-------
Logging
@@ -226,7 +227,7 @@ You can get help in several ways:
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 <https://github.com/python-telegram-bot/python-telegram-bot/issues/new>`_.
Contributions of all sizes are welcome. Please review our `contribution guidelines <https://github.com/python-telegram-bot/python-telegram-bot/blob/v13.x/.github/CONTRIBUTING.rst>`_ to get started. You can also help by `reporting bugs <https://github.com/python-telegram-bot/python-telegram-bot/issues/new>`_.
========
Donating
+4 -4
View File
@@ -20,7 +20,7 @@ We have a vibrant community of developers helping each other in our `Telegram gr
:target: https://pypi.org/project/python-telegram-bot-raw/
:alt: Supported Python versions
.. image:: https://img.shields.io/badge/Bot%20API-5.7-blue?logo=telegram
.. image:: https://img.shields.io/badge/Bot%20API-6.1-blue?logo=telegram
:target: https://core.telegram.org/bots/api-changelog
:alt: Supported Bot API versions
@@ -91,7 +91,7 @@ Introduction
This library provides a pure Python, lightweight interface for the
`Telegram Bot API <https://core.telegram.org/bots/api>`_.
It's compatible with Python versions 3.6.8+. PTB-Raw might also work on `PyPy <http://pypy.org/>`_, though there have been a lot of issues before. Hence, PyPy is not officially supported.
It's compatible with Python versions 3.7+. PTB-Raw might also work on `PyPy <http://pypy.org/>`_, though there have been a lot of issues before. Hence, PyPy is not officially supported.
``python-telegram-bot-raw`` is part of the `python-telegram-bot <https://python-telegram-bot.org>`_ ecosystem and provides the pure API functionality extracted from PTB. It therefore does *not* have independent release schedules, changelogs or documentation. Please consult the PTB resources.
@@ -105,7 +105,7 @@ Installing both ``python-telegram-bot`` and ``python-telegram-bot-raw`` in conju
Telegram API support
====================
All types and methods of the Telegram Bot API **5.7** are supported.
All types and methods of the Telegram Bot API **6.1** are supported.
==========
Installing
@@ -208,7 +208,7 @@ You can get help in several ways:
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 <https://github.com/python-telegram-bot/python-telegram-bot/issues/new>`_.
Contributions of all sizes are welcome. Please review our `contribution guidelines <https://github.com/python-telegram-bot/python-telegram-bot/blob/v13.x/.github/CONTRIBUTING.rst>`_ to get started. You can also help by `reporting bugs <https://github.com/python-telegram-bot/python-telegram-bot/issues/new>`_.
========
Donating
+1 -1
View File
@@ -2,7 +2,7 @@
/*
Generated by https://darkreader.org
Instructions: Install the extension on a Chromium-based browser
Then do this to export the CSS: https://git.io/JOM6t and drop it here
Then do this to export the CSS: https://github.com/darkreader/darkreader/issues/604#issuecomment-661107893 and drop it here
Some color values where manually changed - just search for "/*" in this file and insert them in the new css
*/
/* User-Agent Style */
+2 -2
View File
@@ -60,9 +60,9 @@ author = u'Leandro Toledo'
# built documents.
#
# The short X.Y version.
version = '13.11' # telegram.__version__[:3]
version = '13.13' # telegram.__version__[:3]
# The full version, including alpha/beta/rc tags.
release = '13.11' # telegram.__version__
release = '13.13' # telegram.__version__
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
@@ -0,0 +1,8 @@
:github_url: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/telegram/chatadministratorrights.py
telegram.ChatAdministratorRights
================================
.. autoclass:: telegram.ChatAdministratorRights
:members:
:show-inheritance:
+8
View File
@@ -0,0 +1,8 @@
:github_url: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/telegram/menubutton.py
telegram.MenuButton
===================
.. autoclass:: telegram.MenuButton
:members:
:show-inheritance:
@@ -0,0 +1,8 @@
:github_url: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/telegram/menubutton.py
telegram.MenuButtonCommands
===========================
.. autoclass:: telegram.MenuButtonCommands
:members:
:show-inheritance:
@@ -0,0 +1,8 @@
:github_url: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/telegram/menubutton.py
telegram.MenuButtonDefault
==========================
.. autoclass:: telegram.MenuButtonDefault
:members:
:show-inheritance:
@@ -0,0 +1,8 @@
:github_url: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/telegram/menubutton.py
telegram.MenuButtonWebApp
=========================
.. autoclass:: telegram.MenuButtonWebApp
:members:
:show-inheritance:
+12
View File
@@ -17,6 +17,7 @@ telegram package
telegram.botcommandscopechatmember
telegram.callbackquery
telegram.chat
telegram.chatadministratorrights
telegram.chataction
telegram.chatinvitelink
telegram.chatjoinrequest
@@ -51,6 +52,10 @@ telegram package
telegram.keyboardbuttonpolltype
telegram.location
telegram.loginurl
telegram.menubutton
telegram.menubuttoncommands
telegram.menubuttondefault
telegram.menubuttonwebapp
telegram.message
telegram.messageautodeletetimerchanged
telegram.messageid
@@ -64,18 +69,25 @@ telegram package
telegram.replykeyboardremove
telegram.replykeyboardmarkup
telegram.replymarkup
telegram.sentwebappmessage
telegram.telegramobject
telegram.update
telegram.user
telegram.userprofilephotos
telegram.venue
telegram.video
telegram.videochatended
telegram.videochatparticipantsinvited
telegram.videochatscheduled
telegram.videochatstarted
telegram.videonote
telegram.voice
telegram.voicechatstarted
telegram.voicechatended
telegram.voicechatscheduled
telegram.voicechatparticipantsinvited
telegram.webappdata
telegram.webappinfo
telegram.webhookinfo
Stickers
@@ -0,0 +1,8 @@
:github_url: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/telegram/sentwebappmessage.py
telegram.SentWebAppMessage
==========================
.. autoclass:: telegram.SentWebAppMessage
:members:
:show-inheritance:
+9
View File
@@ -0,0 +1,9 @@
:github_url: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/telegram/videochatended.py
telegram.VideoChatEnded
=======================
.. autoclass:: telegram.VideoChatEnded
:members:
:show-inheritance:
@@ -0,0 +1,8 @@
:github_url: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/telegram/videochatparticipantsinvited.py
telegram.VideoChatParticipantsInvited
=====================================
.. autoclass:: telegram.VideoChatParticipantsInvited
:members:
:show-inheritance:
@@ -0,0 +1,8 @@
:github_url: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/telegram/videochatscheduled.py
telegram.VideoChatScheduled
===========================
.. autoclass:: telegram.VideoChatScheduled
:members:
:show-inheritance:
@@ -0,0 +1,8 @@
:github_url: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/telegram/videochatstarted.py
telegram.VideoChatStarted
=========================
.. autoclass:: telegram.VideoChatStarted
:members:
:show-inheritance:
+2
View File
@@ -7,3 +7,5 @@ telegram.VoiceChatEnded
:members:
:show-inheritance:
.. versionchanged:: v13.12
Since Bot API 6.0, voice chat was renamed to video chat.
@@ -7,3 +7,5 @@ telegram.VoiceChatParticipantsInvited
:members:
:show-inheritance:
.. versionchanged:: v13.12
Since Bot API 6.0, voice chat was renamed to video chat.
@@ -7,3 +7,5 @@ telegram.VoiceChatScheduled
:members:
:show-inheritance:
.. versionchanged:: v13.12
Since Bot API 6.0, voice chat was renamed to video chat.
@@ -7,3 +7,5 @@ telegram.VoiceChatStarted
:members:
:show-inheritance:
.. versionchanged:: v13.12
Since Bot API 6.0, voice chat was renamed to video chat.
+8
View File
@@ -0,0 +1,8 @@
:github_url: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/telegram/webappdata.py
telegram.WebAppData
===================
.. autoclass:: telegram.WebAppData
:members:
:show-inheritance:
+8
View File
@@ -0,0 +1,8 @@
:github_url: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/telegram/webappinfo.py
telegram.WebAppInfo
===================
.. autoclass:: telegram.WebAppInfo
:members:
:show-inheritance:
+24 -24
View File
@@ -2,60 +2,60 @@
In this folder are small examples to show what a bot written with `python-telegram-bot` looks like. Some bots focus on one specific aspect of the Telegram Bot API while others focus on one of the mechanics of this library. Except for the [`rawapibot.py`](#pure-api) example, they all use the high-level framework this library provides with the [`telegram.ext`](https://python-telegram-bot.readthedocs.io/en/latest/telegram.ext.html) submodule.
All examples are licensed under the [CC0 License](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/LICENSE.txt) and are therefore fully dedicated to the public domain. You can use them as the base for your own bots without worrying about copyrights.
All examples are licensed under the [CC0 License](https://github.com/python-telegram-bot/python-telegram-bot/blob/v13.x/examples/LICENSE.txt) and are therefore fully dedicated to the public domain. You can use them as the base for your own bots without worrying about copyrights.
Do note that we ignore one pythonic convention. Best practice would dictate, in many handler callbacks function signatures, to replace the argument `context` with an underscore, since `context` is an unused local variable in those callbacks. However, since these are examples and not having a name for that argument confuses beginners, we decided to have it present.
### [`echobot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/echobot.py)
### [`echobot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/v13.x/examples/echobot.py)
This is probably the base for most of the bots made with `python-telegram-bot`. It simply replies to each text message with a message that contains the same text.
### [`timerbot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/timerbot.py)
### [`timerbot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/v13.x/examples/timerbot.py)
This bot uses the [`JobQueue`](https://python-telegram-bot.readthedocs.io/en/latest/telegram.ext.jobqueue.html) class to send timed messages. The user sets a timer by using `/set` command with a specific time, for example `/set 30`. The bot then sets up a job to send a message to that user after 30 seconds. The user can also cancel the timer by sending `/unset`. To learn more about the `JobQueue`, read [this wiki article](https://github.com/python-telegram-bot/python-telegram-bot/wiki/Extensions-%E2%80%93-JobQueue).
### [`conversationbot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/conversationbot.py)
A common task for a bot is to ask information from the user. In v5.0 of this library, we introduced the [`ConversationHandler`](https://python-telegram-bot.readthedocs.io/en/latest/telegram.ext.conversationhandler.html) for that exact purpose. This example uses it to retrieve user-information in a conversation-like style. To get a better understanding, take a look at the [state diagram](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/conversationbot.png).
### [`conversationbot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/v13.x/examples/conversationbot.py)
A common task for a bot is to ask information from the user. In v5.0 of this library, we introduced the [`ConversationHandler`](https://python-telegram-bot.readthedocs.io/en/latest/telegram.ext.conversationhandler.html) for that exact purpose. This example uses it to retrieve user-information in a conversation-like style. To get a better understanding, take a look at the [state diagram](https://github.com/python-telegram-bot/python-telegram-bot/blob/v13.x/examples/conversationbot.png).
### [`conversationbot2.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/conversationbot2.py)
A more complex example of a bot that uses the `ConversationHandler`. It is also more confusing. Good thing there is a [fancy state diagram](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/conversationbot2.png) for this one, too!
### [`conversationbot2.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/v13.x/examples/conversationbot2.py)
A more complex example of a bot that uses the `ConversationHandler`. It is also more confusing. Good thing there is a [fancy state diagram](https://github.com/python-telegram-bot/python-telegram-bot/blob/v13.x/examples/conversationbot2.png) for this one, too!
### [`nestedconversationbot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/nestedconversationbot.py)
A even more complex example of a bot that uses the nested `ConversationHandler`s. While it's certainly not that complex that you couldn't built it without nested `ConversationHanldler`s, it gives a good impression on how to work with them. Of course, there is a [fancy state diagram](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/nestedconversationbot.png) for this example, too!
### [`nestedconversationbot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/v13.x/examples/nestedconversationbot.py)
A even more complex example of a bot that uses the nested `ConversationHandler`s. While it's certainly not that complex that you couldn't built it without nested `ConversationHanldler`s, it gives a good impression on how to work with them. Of course, there is a [fancy state diagram](https://github.com/python-telegram-bot/python-telegram-bot/blob/v13.x/examples/nestedconversationbot.png) for this example, too!
### [`persistentconversationbot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/persistentconversationbot.py)
### [`persistentconversationbot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/v13.x/examples/persistentconversationbot.py)
A basic example of a bot store conversation state and user_data over multiple restarts.
### [`inlinekeyboard.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/inlinekeyboard.py)
This example sheds some light on inline keyboards, callback queries and message editing. A wikipedia site explaining this examples lives at https://git.io/JOmFw.
### [`inlinekeyboard.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/v13.x/examples/inlinekeyboard.py)
This example sheds some light on inline keyboards, callback queries and message editing. A wikipedia site explaining this examples lives at https://github.com/python-telegram-bot/python-telegram-bot/wiki/InlineKeyboard-Example.
### [`inlinekeyboard2.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/inlinekeyboard2.py)
### [`inlinekeyboard2.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/v13.x/examples/inlinekeyboard2.py)
A more complex example about inline keyboards, callback queries and message editing. This example showcases how an interactive menu could be build using inline keyboards.
### [`deeplinking.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/deeplinking.py)
### [`deeplinking.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/v13.x/examples/deeplinking.py)
A basic example on how to use deeplinking with inline keyboards.
### [`inlinebot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/inlinebot.py)
### [`inlinebot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/v13.x/examples/inlinebot.py)
A basic example of an [inline bot](https://core.telegram.org/bots/inline). Don't forget to enable inline mode with [@BotFather](https://telegram.me/BotFather).
### [`pollbot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/pollbot.py)
### [`pollbot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/v13.x/examples/pollbot.py)
This example sheds some light on polls, poll answers and the corresponding handlers.
### [`passportbot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/passportbot.py)
A basic example of a bot that can accept passports. Use in combination with [`passportbot.html`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/passportbot.html). Don't forget to enable and configure payments with [@BotFather](https://telegram.me/BotFather). Check out this [guide](https://git.io/fAvYd) on Telegram passports in PTB.
### [`passportbot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/v13.x/examples/passportbot.py)
A basic example of a bot that can accept passports. Use in combination with [`passportbot.html`](https://github.com/python-telegram-bot/python-telegram-bot/blob/v13.x/examples/passportbot.html). Don't forget to enable and configure payments with [@BotFather](https://telegram.me/BotFather). Check out this [guide](https://github.com/python-telegram-bot/python-telegram-bot/wiki/Telegram-Passport) on Telegram passports in PTB.
### [`paymentbot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/paymentbot.py)
### [`paymentbot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/v13.x/examples/paymentbot.py)
A basic example of a bot that can accept payments. Don't forget to enable and configure payments with [@BotFather](https://telegram.me/BotFather).
### [`errorhandlerbot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/errorhandlerbot.py)
### [`errorhandlerbot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/v13.x/examples/errorhandlerbot.py)
A basic example on how to set up a custom error handler.
### [`chatmemberbot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/chatmemberbot.py)
### [`chatmemberbot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/v13.x/examples/chatmemberbot.py)
A basic example on how `(my_)chat_member` updates can be used.
### [`contexttypesbot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/contexttypesbot.py)
### [`contexttypesbot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/v13.x/examples/contexttypesbot.py)
This example showcases how `telegram.ext.ContextTypes` can be used to customize the `context` argument of handler and job callbacks.
### [`arbitrarycallbackdatabot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/arbitrarycallbackdatabot.py)
### [`arbitrarycallbackdatabot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/v13.x/examples/arbitrarycallbackdatabot.py)
This example showcases how PTBs "arbitrary callback data" feature can be used.
## Pure API
The [`rawapibot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/rawapibot.py) example uses only the pure, "bare-metal" API wrapper.
The [`rawapibot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/v13.x/examples/rawapibot.py) example uses only the pure, "bare-metal" API wrapper.
+2 -1
View File
@@ -4,7 +4,8 @@
"""This example showcases how PTBs "arbitrary callback data" feature can be used.
For detailed info on arbitrary callback data, see the wiki page at https://git.io/JGBDI
For detailed info on arbitrary callback data, see the wiki page at
https://github.com/python-telegram-bot/python-telegram-bot/wiki/Arbitrary-callback_data
"""
import logging
from typing import List, Tuple, cast
+10 -18
View File
@@ -44,24 +44,16 @@ def extract_status_change(
return None
old_status, new_status = status_change
was_member = (
old_status
in [
ChatMember.MEMBER,
ChatMember.CREATOR,
ChatMember.ADMINISTRATOR,
]
or (old_status == ChatMember.RESTRICTED and old_is_member is True)
)
is_member = (
new_status
in [
ChatMember.MEMBER,
ChatMember.CREATOR,
ChatMember.ADMINISTRATOR,
]
or (new_status == ChatMember.RESTRICTED and new_is_member is True)
)
was_member = old_status in [
ChatMember.MEMBER,
ChatMember.CREATOR,
ChatMember.ADMINISTRATOR,
] or (old_status == ChatMember.RESTRICTED and old_is_member is True)
is_member = new_status in [
ChatMember.MEMBER,
ChatMember.CREATOR,
ChatMember.ADMINISTRATOR,
] or (new_status == ChatMember.RESTRICTED and new_is_member is True)
return was_member, is_member
+1 -1
View File
@@ -4,7 +4,7 @@
"""
Basic example for a bot that uses inline keyboards. For an in-depth explanation, check out
https://git.io/JOmFw.
https://github.com/python-telegram-bot/python-telegram-bot/wiki/InlineKeyboard-Example.
"""
import logging
+2 -1
View File
@@ -7,7 +7,8 @@ Simple Bot to print/download all incoming passport data
See https://telegram.org/blog/passport for info about what telegram passport is.
See https://git.io/fAvYd for how to use Telegram Passport properly with python-telegram-bot.
See https://github.com/python-telegram-bot/python-telegram-bot/wiki/Telegram-Passport
for how to use Telegram Passport properly with python-telegram-bot.
"""
import logging
+1 -1
View File
@@ -3,7 +3,7 @@ cryptography!=3.4,!=3.4.1,!=3.4.2,!=3.4.3
pre-commit
# Make sure that the versions specified here match the pre-commit settings!
black==20.8b1
black==22.3.0
flake8==3.9.2
pylint==2.8.3
mypy==0.812
+1
View File
@@ -30,6 +30,7 @@ filterwarnings =
; Unfortunately due to https://github.com/pytest-dev/pytest/issues/8343 we can't have this here
; and instead do a trick directly in tests/conftest.py
; ignore::telegram.utils.deprecate.TelegramDeprecationWarning
markers = dev: If you want to test a specific test, use this
[coverage:run]
branch = True
+2 -3
View File
@@ -65,7 +65,7 @@ def get_setup_kwargs(raw=False):
author_email='devs@python-telegram-bot.org',
license='LGPLv3',
url='https://python-telegram-bot.org/',
# Keywords supported by PyPI can be found at https://git.io/JtLIZ
# 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://python-telegram-bot.readthedocs.io",
"Bug Tracker": "https://github.com/python-telegram-bot/python-telegram-bot/issues",
@@ -98,12 +98,11 @@ def get_setup_kwargs(raw=False):
'Topic :: Internet',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
],
python_requires='>=3.6'
python_requires='>=3.7'
)
return kwargs
+25 -2
View File
@@ -20,9 +20,16 @@
from .base import TelegramObject
from .botcommand import BotCommand
from .webappdata import WebAppData
from .webappinfo import WebAppInfo
from .sentwebappmessage import SentWebAppMessage
from .menubutton import MenuButton, MenuButtonCommands, MenuButtonDefault, MenuButtonWebApp
from .loginurl import LoginUrl
from .games.callbackgame import CallbackGame
from .user import User
from .files.chatphoto import ChatPhoto
from .chat import Chat
from .chatadministratorrights import ChatAdministratorRights
from .chatlocation import ChatLocation
from .chatinvitelink import ChatInviteLink
from .chatjoinrequest import ChatJoinRequest
@@ -71,9 +78,13 @@ from .voicechat import (
VoiceChatParticipantsInvited,
VoiceChatScheduled,
)
from .loginurl import LoginUrl
from .videochat import (
VideoChatStarted,
VideoChatEnded,
VideoChatParticipantsInvited,
VideoChatScheduled,
)
from .proximityalerttriggered import ProximityAlertTriggered
from .games.callbackgame import CallbackGame
from .payment.shippingaddress import ShippingAddress
from .payment.orderinfo import OrderInfo
from .payment.successfulpayment import SuccessfulPayment
@@ -193,6 +204,7 @@ __all__ = ( # Keep this alphabetically ordered
'CallbackGame',
'CallbackQuery',
'Chat',
'ChatAdministratorRights',
'ChatAction',
'ChatInviteLink',
'ChatJoinRequest',
@@ -272,6 +284,10 @@ __all__ = ( # Keep this alphabetically ordered
'MAX_MESSAGES_PER_SECOND_PER_CHAT',
'MAX_MESSAGE_LENGTH',
'MaskPosition',
'MenuButton',
'MenuButtonCommands',
'MenuButtonDefault',
'MenuButtonWebApp',
'Message',
'MessageAutoDeleteTimerChanged',
'MessageEntity',
@@ -304,6 +320,7 @@ __all__ = ( # Keep this alphabetically ordered
'SUPPORTED_WEBHOOK_PORTS',
'SecureData',
'SecureValue',
'SentWebAppMessage',
'ShippingAddress',
'ShippingOption',
'ShippingQuery',
@@ -318,11 +335,17 @@ __all__ = ( # Keep this alphabetically ordered
'UserProfilePhotos',
'Venue',
'Video',
'VideoChatEnded',
'VideoChatParticipantsInvited',
'VideoChatScheduled',
'VideoChatStarted',
'VideoNote',
'Voice',
'VoiceChatStarted',
'VoiceChatEnded',
'VoiceChatScheduled',
'VoiceChatParticipantsInvited',
'WebAppData',
'WebAppInfo',
'WebhookInfo',
)
+1 -1
View File
@@ -41,7 +41,7 @@ def print_ver_info() -> None: # skipcq: PY-D0003
git_revision = _git_revision()
print(f'python-telegram-bot {telegram_ver}' + (f' ({git_revision})' if git_revision else ''))
print(f'Bot API {BOT_API_VERSION}')
print(f'certifi {certifi.__version__}') # type: ignore[attr-defined]
print('certifi' + certifi.__version__)
sys_version = sys.version.replace('\n', ' ')
print(f'Python {sys_version}')
+391 -4
View File
@@ -88,6 +88,9 @@ from telegram import (
WebhookInfo,
InlineKeyboardMarkup,
ChatInviteLink,
SentWebAppMessage,
ChatAdministratorRights,
MenuButton,
)
from telegram.constants import MAX_INLINE_QUERY_RESULTS
from telegram.error import InvalidToken, TelegramError
@@ -3077,6 +3080,7 @@ class Bot(TelegramObject):
api_kwargs: JSONDict = None,
ip_address: str = None,
drop_pending_updates: bool = None,
secret_token: str = None,
) -> bool:
"""
Use this method to specify a url and receive incoming updates via an outgoing webhook.
@@ -3084,9 +3088,9 @@ class Bot(TelegramObject):
specified url, containing a JSON-serialized Update. In case of an unsuccessful request,
Telegram will give up after a reasonable amount of attempts.
If you'd like to make sure that the Webhook request comes from Telegram, Telegram
recommends using a secret path in the URL, e.g. https://www.example.com/<token>. Since
nobody else knows your bot's token, you can be pretty sure it's us.
If you'd like to make sure that the Webhook was set by you, you can specify secret data in
the parameter ``secret_token``. If specified, the request will contain a header
``X-Telegram-Bot-Api-Secret-Token`` with the secret token as content.
Note:
The certificate argument should be a file from disk ``open(filename, 'rb')``.
@@ -3114,6 +3118,12 @@ class Bot(TelegramObject):
a short period of time.
drop_pending_updates (:obj:`bool`, optional): Pass :obj:`True` to drop all pending
updates.
secret_token (:obj:`str`, optional): A secret token to be sent in a header
``X-Telegram-Bot-Api-Secret-Token`` in every webhook request, 1-256 characters.
Only characters ``A-Z``, ``a-z``, ``0-9``, ``_`` and ``-`` are allowed.
The header is useful to ensure that the request comes from a webhook set by you.
.. versionadded:: 13.13
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
the read timeout from the server (instead of the one specified during creation of
the connection pool).
@@ -3154,6 +3164,8 @@ class Bot(TelegramObject):
data['ip_address'] = ip_address
if drop_pending_updates:
data['drop_pending_updates'] = drop_pending_updates
if secret_token is not None:
data["secret_token"] = secret_token
result = self._post('setWebhook', data, timeout=timeout, api_kwargs=api_kwargs)
@@ -3897,6 +3909,47 @@ class Bot(TelegramObject):
return result # type: ignore[return-value]
@log
def answer_web_app_query(
self,
web_app_query_id: str,
result: 'InlineQueryResult',
timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
) -> SentWebAppMessage:
"""Use this method to set the result of an interaction with a Web App and send a
corresponding message on behalf of the user to the chat from which the query originated.
.. versionadded:: 13.12
Args:
web_app_query_id (:obj:`str`): Unique identifier for the query to be answered.
result (:class:`telegram.InlineQueryResult`): An object describing the message to be
sent.
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
the read timeout from the server (instead of the one specified during creation of
the connection pool).
api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the
Telegram API.
Returns:
:class:`telegram.SentWebAppMessage`: On success, a sent
:class:`telegram.SentWebAppMessage` is returned.
Raises:
:class:`telegram.error.TelegramError`
"""
data: JSONDict = {'web_app_query_id': web_app_query_id, 'result': result}
api_result = self._post(
'answerWebAppQuery',
data,
timeout=timeout,
api_kwargs=api_kwargs,
)
return SentWebAppMessage.de_json(api_result, self) # type: ignore[return-value, arg-type]
@log
def restrict_chat_member(
self,
@@ -3976,6 +4029,7 @@ class Bot(TelegramObject):
is_anonymous: bool = None,
can_manage_chat: bool = None,
can_manage_voice_chats: bool = None,
can_manage_video_chats: bool = None,
) -> bool:
"""
Use this method to promote or demote a user in a supergroup or a channel. The bot must be
@@ -4000,6 +4054,14 @@ class Bot(TelegramObject):
.. versionadded:: 13.4
.. deprecated:: 13.12
Since Bot API 6.0, voice chat was renamed to video chat.
can_manage_video_chats (:obj:`bool`, optional): Pass :obj:`True`, if the administrator
can manage video chats.
.. versionadded:: 13.12
can_change_info (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can
change chat title, photo and other settings.
can_post_messages (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can
@@ -4031,6 +4093,11 @@ class Bot(TelegramObject):
:class:`telegram.error.TelegramError`
"""
if can_manage_voice_chats is not None and can_manage_video_chats is not None:
raise ValueError(
"Only supply one of `can_manage_video_chats`/`can_manage_voice_chats`, not both."
)
data: JSONDict = {'chat_id': chat_id, 'user_id': user_id}
if is_anonymous is not None:
@@ -4054,7 +4121,9 @@ class Bot(TelegramObject):
if can_manage_chat is not None:
data['can_manage_chat'] = can_manage_chat
if can_manage_voice_chats is not None:
data['can_manage_voice_chats'] = can_manage_voice_chats
data['can_manage_video_chats'] = can_manage_voice_chats
if can_manage_video_chats is not None:
data['can_manage_video_chats'] = can_manage_video_chats
result = self._post('promoteChatMember', data, timeout=timeout, api_kwargs=api_kwargs)
@@ -5381,6 +5450,101 @@ class Bot(TelegramObject):
protect_content=protect_content,
)
@log
def get_my_default_administrator_rights(
self,
for_channels: bool = None,
timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
) -> ChatAdministratorRights:
"""Use this method to get the current default administrator rights of the bot.
.. seealso:: :meth:`set_my_default_administrator_rights`
.. versionadded:: 13.12
Args:
for_channels (:obj:`bool`, optional): Pass :obj:`True` to get default administrator
rights of the bot in channels. Otherwise, default administrator rights of the bot
for groups and supergroups will be returned.
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
the read timeout from the server (instead of the one specified during creation of
the connection pool).
api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the
Telegram API.
Returns:
:class:`telegram.ChatAdministratorRights`: On success.
Raises:
:class:`telegram.error.TelegramError`
"""
data: JSONDict = {}
if for_channels is not None:
data['for_channels'] = for_channels
result = self._post(
'getMyDefaultAdministratorRights',
data,
timeout=timeout,
api_kwargs=api_kwargs,
)
return ChatAdministratorRights.de_json(result, self) # type: ignore[return-value,arg-type]
@log
def set_my_default_administrator_rights(
self,
rights: ChatAdministratorRights = None,
for_channels: bool = None,
timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
) -> bool:
"""Use this method to change the default administrator rights requested by the bot when
it's added as an administrator to groups or channels. These rights will be suggested to
users, but they are are free to modify the list before adding the bot.
.. seealso:: :meth:`get_my_default_administrator_rights`
.. versionadded:: 13.12
Args:
rights (:obj:`telegram.ChatAdministratorRights`, optional): A
:obj:`telegram.ChatAdministratorRights` object describing new default administrator
rights. If not specified, the default administrator rights will be cleared.
for_channels (:obj:`bool`, optional): Pass :obj:`True` to change the default
administrator rights of the bot in channels. Otherwise, the default administrator
rights of the bot for groups and supergroups will be changed.
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
the read timeout from the server (instead of the one specified during creation of
the connection pool).
api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the
Telegram API.
Returns:
:obj:`bool`: Returns :obj:`True` on success.
Raises:
:obj:`telegram.error.TelegramError`
"""
data: JSONDict = {}
if rights is not None:
data['rights'] = rights.to_dict()
if for_channels is not None:
data['for_channels'] = for_channels
result = self._post(
'setMyDefaultAdministratorRights',
data,
timeout=timeout,
api_kwargs=api_kwargs,
)
return result # type: ignore[return-value]
@log
def get_my_commands(
self,
@@ -5677,6 +5841,217 @@ class Bot(TelegramObject):
result = self._post('copyMessage', data, timeout=timeout, api_kwargs=api_kwargs)
return MessageId.de_json(result, self) # type: ignore[return-value, arg-type]
@log
def set_chat_menu_button(
self,
chat_id: int = None,
menu_button: MenuButton = None,
timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
) -> bool:
"""Use this method to change the bot's menu button in a private chat, or the default menu
button.
.. seealso:: :meth:`get_chat_menu_button`, :meth:`telegram.Chat.set_menu_button`,
:meth:`telegram.User.set_menu_button`
.. versionadded:: 13.12
Args:
chat_id (:obj:`int`, optional): Unique identifier for the target private chat. If not
specified, default bot's menu button will be changed
menu_button (:class:`telegram.MenuButton`, optional): An object for the new bot's menu
button. Defaults to :class:`telegram.MenuButtonDefault`.
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
the read timeout from the server (instead of the one specified during creation of
the connection pool).
api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the
Telegram API.
Returns:
:obj:`bool`: On success, :obj:`True` is returned.
"""
data: JSONDict = {}
if chat_id is not None:
data['chat_id'] = chat_id
if menu_button is not None:
data['menu_button'] = menu_button.to_dict()
return self._post( # type: ignore[return-value]
'setChatMenuButton',
data,
timeout=timeout,
api_kwargs=api_kwargs,
)
@log
def get_chat_menu_button(
self,
chat_id: int = None,
timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
) -> MenuButton:
"""Use this method to get the current value of the bot's menu button in a private chat, or
the default menu button.
.. seealso:: :meth:`set_chat_menu_button`, :meth:`telegram.Chat.get_menu_button`,
:meth:`telegram.User.get_menu_button`
.. versionadded:: 13.12
Args:
chat_id (:obj:`int`, optional): Unique identifier for the target private chat. If not
specified, default bot's menu button will be returned.
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
the read timeout from the server (instead of the one specified during creation of
the connection pool).
api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the
Telegram API.
Returns:
:class:`telegram.MenuButton`: On success, the current menu button is returned.
"""
data = {}
if chat_id is not None:
data['chat_id'] = chat_id
result = self._post(
'getChatMenuButton',
data,
timeout=timeout,
api_kwargs=api_kwargs,
)
return MenuButton.de_json(result, bot=self) # type: ignore[return-value, arg-type]
@log
def create_invoice_link(
self,
title: str,
description: str,
payload: str,
provider_token: str,
currency: str,
prices: List["LabeledPrice"],
max_tip_amount: int = None,
suggested_tip_amounts: List[int] = None,
provider_data: Union[str, object] = None,
photo_url: str = None,
photo_size: int = None,
photo_width: int = None,
photo_height: int = None,
need_name: bool = None,
need_phone_number: bool = None,
need_email: bool = None,
need_shipping_address: bool = None,
send_phone_number_to_provider: bool = None,
send_email_to_provider: bool = None,
is_flexible: bool = None,
timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
) -> str:
"""Use this method to create a link for an invoice.
.. versionadded:: 13.13
Args:
title (:obj:`str`): Product name. 1-32 characters.
description (:obj:`str`): Product description. 1-255 characters.
payload (:obj:`str`): Bot-defined invoice payload. 1-128 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>`_.
currency (:obj:`str`): Three-letter ISO 4217 currency code, see `more on currencies
<https://core.telegram.org/bots/payments#supported-currencies>`_.
prices (List[:class:`telegram.LabeledPrice`)]: Price breakdown, a list
of components (e.g. product price, tax, discount, delivery cost, delivery tax,
bonus, etc.).
max_tip_amount (:obj:`int`, optional): The maximum accepted amount for tips in the
*smallest* units of the currency (integer, **not** float/double). For example, for
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``.
suggested_tip_amounts (List[:obj:`int`], optional): An array of
suggested amounts of tips 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 positive, passed in a strictly increased order and must not exceed
``max_tip_amount``.
provider_data (:obj:`str` | :obj:`object`, optional): Data about the
invoice, which will be shared with the payment provider. A detailed description of
required fields should be provided by the payment provider. When an object is
passed, it will be encoded as JSON.
photo_url (:obj:`str`, optional): URL of the product photo for the invoice. Can be a
photo of the goods or a marketing image for a service.
photo_size (:obj:`int`, optional): Photo size in bytes.
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_phone_number (:obj:`bool`, optional): Pass :obj:`True`, if you require the user's
phone number to complete the order.
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.
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
the read timeout from the server (instead of the one specified during creation of
the connection pool).
api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the
Telegram API.
Returns:
:class:`str`: On success, the created invoice link is returned.
"""
data: JSONDict = {
"title": title,
"description": description,
"payload": payload,
"provider_token": provider_token,
"currency": currency,
"prices": [p.to_dict() for p in prices],
}
if max_tip_amount is not None:
data["max_tip_amount"] = max_tip_amount
if suggested_tip_amounts is not None:
data["suggested_tip_amounts"] = suggested_tip_amounts
if provider_data is not None:
data["provider_data"] = provider_data
if photo_url is not None:
data["photo_url"] = photo_url
if photo_size is not None:
data["photo_size"] = photo_size
if photo_width is not None:
data["photo_width"] = photo_width
if photo_height is not None:
data["photo_height"] = photo_height
if need_name is not None:
data["need_name"] = need_name
if need_phone_number is not None:
data["need_phone_number"] = need_phone_number
if need_email is not None:
data["need_email"] = need_email
if need_shipping_address is not None:
data["need_shipping_address"] = need_shipping_address
if is_flexible is not None:
data["is_flexible"] = is_flexible
if send_phone_number_to_provider is not None:
data["send_phone_number_to_provider"] = send_phone_number_to_provider
if send_email_to_provider is not None:
data["send_email_to_provider"] = send_email_to_provider
return self._post( # type: ignore[return-value]
"createInvoiceLink",
data,
timeout=timeout,
api_kwargs=api_kwargs,
)
def to_dict(self) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict`."""
data: JSONDict = {'id': self.id, 'username': self.username, 'first_name': self.first_name}
@@ -5793,6 +6168,8 @@ class Bot(TelegramObject):
"""Alias for :meth:`answer_shipping_query`"""
answerPreCheckoutQuery = answer_pre_checkout_query
"""Alias for :meth:`answer_pre_checkout_query`"""
answerWebAppQuery = answer_web_app_query
"""Alias for :meth:`answer_web_app_query`"""
restrictChatMember = restrict_chat_member
"""Alias for :meth:`restrict_chat_member`"""
promoteChatMember = promote_chat_member
@@ -5859,3 +6236,13 @@ class Bot(TelegramObject):
"""Alias for :meth:`log_out`"""
copyMessage = copy_message
"""Alias for :meth:`copy_message`"""
getChatMenuButton = get_chat_menu_button
"""Alias for :meth:`get_chat_menu_button`"""
setChatMenuButton = set_chat_menu_button
"""Alias for :meth:`set_chat_menu_button`"""
getMyDefaultAdministratorRights = get_my_default_administrator_rights
"""Alias for :meth:`get_my_default_administrator_rights`"""
setMyDefaultAdministratorRights = set_my_default_administrator_rights
"""Alias for :meth:`set_my_default_administrator_rights`"""
createInvoiceLink = create_invoice_link
"""Alias for :meth:`create_invoice_link`"""
+89 -1
View File
@@ -22,7 +22,7 @@ import warnings
from datetime import datetime
from typing import TYPE_CHECKING, List, Optional, ClassVar, Union, Tuple, Any
from telegram import ChatPhoto, TelegramObject, constants
from telegram import ChatPhoto, TelegramObject, constants, MenuButton
from telegram.utils.types import JSONDict, FileInput, ODVInput, DVInput
from telegram.utils.deprecate import TelegramDeprecationWarning
@@ -116,6 +116,16 @@ class Chat(TelegramObject):
chats. Returned only in :meth:`telegram.Bot.get_chat`.
location (:class:`telegram.ChatLocation`, optional): For supergroups, the location to which
the supergroup is connected. Returned only in :meth:`telegram.Bot.get_chat`.
join_to_send_messages (:obj:`bool`, optional): :obj:`True`, if users need to join the
supergroup before they can send messages. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 13.13
join_by_request (:obj:`bool`, optional): :obj:`True`, if all users directly joining the
supergroup need to be approved by supergroup administrators. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 13.13
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
Attributes:
@@ -160,7 +170,16 @@ class Chat(TelegramObject):
chats. Returned only in :meth:`telegram.Bot.get_chat`.
location (:class:`telegram.ChatLocation`): Optional. For supergroups, the location to which
the supergroup is connected. Returned only in :meth:`telegram.Bot.get_chat`.
join_to_send_messages (:obj:`bool`): Optional. :obj:`True`, if users need to join the
supergroup before they can send messages. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 13.13
join_by_request (:obj:`bool`): Optional. :obj:`True`, if all users directly joining the
supergroup need to be approved by supergroup administrators. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 13.13
"""
__slots__ = (
@@ -186,6 +205,8 @@ class Chat(TelegramObject):
'message_auto_delete_time',
'has_protected_content',
'has_private_forwards',
'join_to_send_messages',
'join_by_request',
'_id_attrs',
)
@@ -226,6 +247,8 @@ class Chat(TelegramObject):
message_auto_delete_time: int = None,
has_private_forwards: bool = None,
has_protected_content: bool = None,
join_to_send_messages: bool = None,
join_by_request: bool = None,
**_kwargs: Any,
):
# Required
@@ -254,6 +277,8 @@ class Chat(TelegramObject):
self.can_set_sticker_set = can_set_sticker_set
self.linked_chat_id = linked_chat_id
self.location = location
self.join_to_send_messages = join_to_send_messages
self.join_by_request = join_by_request
self.bot = bot
self._id_attrs = (self.id,)
@@ -590,6 +615,7 @@ class Chat(TelegramObject):
is_anonymous: bool = None,
can_manage_chat: bool = None,
can_manage_voice_chats: bool = None,
can_manage_video_chats: bool = None,
) -> bool:
"""Shortcut for::
@@ -600,6 +626,9 @@ class Chat(TelegramObject):
.. versionadded:: 13.2
..versionchanged:: 13.12
Since Bot API 6.0, voice chat was renamed to video chat.
Returns:
:obj:`bool`: On success, :obj:`True` is returned.
@@ -620,6 +649,7 @@ class Chat(TelegramObject):
is_anonymous=is_anonymous,
can_manage_chat=can_manage_chat,
can_manage_voice_chats=can_manage_voice_chats,
can_manage_video_chats=can_manage_video_chats,
)
def restrict_member(
@@ -1813,3 +1843,61 @@ class Chat(TelegramObject):
return self.bot.decline_chat_join_request(
chat_id=self.id, user_id=user_id, timeout=timeout, api_kwargs=api_kwargs
)
def set_menu_button(
self,
menu_button: MenuButton = None,
timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
) -> bool:
"""Shortcut for::
bot.set_chat_menu_button(chat_id=update.effective_chat.id, *args, **kwargs)
For the documentation of the arguments, please see
:meth:`telegram.Bot.set_chat_menu_button`.
Caution:
Can only work, if the chat is a private chat.
..seealso:: :meth:`get_menu_button`
.. versionadded:: 13.12
Returns:
:obj:`bool`: On success, :obj:`True` is returned.
"""
return self.bot.set_chat_menu_button(
chat_id=self.id,
menu_button=menu_button,
timeout=timeout,
api_kwargs=api_kwargs,
)
def get_menu_button(
self,
timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
) -> MenuButton:
"""Shortcut for::
bot.get_chat_menu_button(chat_id=update.effective_chat.id, *args, **kwargs)
For the documentation of the arguments, please see
:meth:`telegram.Bot.set_chat_menu_button`.
Caution:
Can only work, if the chat is a private chat.
..seealso:: :meth:`set_menu_button`
.. versionadded:: 13.12
Returns:
:class:`telegram.MenuButton`: On success, the current menu button is returned.
"""
return self.bot.get_chat_menu_button(
chat_id=self.id,
timeout=timeout,
api_kwargs=api_kwargs,
)
+167
View File
@@ -0,0 +1,167 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2022
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains the class which represents a Telegram ChatAdministratorRights."""
from typing import Any
from telegram import TelegramObject
class ChatAdministratorRights(TelegramObject):
"""Represents the rights of an administrator in a chat.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`is_anonymous`, :attr:`can_manage_chat`,
:attr:`can_delete_messages`, :attr:`can_manage_video_chats`, :attr:`can_restrict_members`,
:attr:`can_promote_members`, :attr:`can_change_info`, :attr:`can_invite_users`,
:attr:`can_post_messages`, :attr:`can_edit_messages`, :attr:`can_pin_messages` are equal.
.. seealso: :meth:`Bot.set_my_default_administrator_rights`,
:meth:`Bot.get_my_default_administrator_rights`
.. versionadded:: 13.12
Args:
is_anonymous (:obj:`bool`): :obj:`True`, if the user's presence in the chat is hidden.
can_manage_chat (:obj:`bool`): :obj:`True`, if the administrator can access the chat event
log, chat statistics, message statistics in channels, see channel members, see
anonymous administrators in supergroups and ignore slow mode. Implied by any other
administrator privilege.
can_delete_messages (:obj:`bool`): :obj:`True`, if the administrator can delete messages of
other users.
can_manage_video_chats (:obj:`bool`): :obj:`True`, if the administrator can manage video
chats.
can_restrict_members (:obj:`bool`): :obj:`True`, if the administrator can restrict, ban or
unban chat members.
can_promote_members (:obj:`bool`): :obj:`True`, if the administrator can add new
administrators with a subset of their own privileges or demote administrators that he
has promoted, directly or indirectly (promoted by administrators that were appointed by
the user.)
can_change_info (:obj:`bool`): :obj:`True`, if the user is allowed to change the chat title
, photo and other settings.
can_invite_users (:obj:`bool`): :obj:`True`, if the user is allowed to invite new users to
the chat.
can_post_messages (:obj:`bool`, optional): :obj:`True`, if the administrator can post
messages in the channel; channels only.
can_edit_messages (:obj:`bool`, optional): :obj:`True`, if the administrator can edit
messages of other users.
can_pin_messages (:obj:`bool`, optional): :obj:`True`, if the user is allowed to pin
messages; groups and supergroups only.
Attributes:
is_anonymous (:obj:`bool`): :obj:`True`, if the user's presence in the chat is hidden.
can_manage_chat (:obj:`bool`): :obj:`True`, if the administrator can access the chat event
log, chat statistics, message statistics in channels, see channel members, see
anonymous administrators in supergroups and ignore slow mode. Implied by any other
administrator privilege.
can_delete_messages (:obj:`bool`): :obj:`True`, if the administrator can delete messages of
other users.
can_manage_video_chats (:obj:`bool`): :obj:`True`, if the administrator can manage video
chats.
can_restrict_members (:obj:`bool`): :obj:`True`, if the administrator can restrict, ban or
unban chat members.
can_promote_members (:obj:`bool`): :obj:`True`, if the administrator can add new
administrators with a subset of their own privileges or demote administrators that he
has promoted, directly or indirectly (promoted by administrators that were appointed by
the user.)
can_change_info (:obj:`bool`): :obj:`True`, if the user is allowed to change the chat title
,photo and other settings.
can_invite_users (:obj:`bool`): :obj:`True`, if the user is allowed to invite new users to
the chat.
can_post_messages (:obj:`bool`): Optional. :obj:`True`, if the administrator can post
messages in the channel; channels only.
can_edit_messages (:obj:`bool`): Optional. :obj:`True`, if the administrator can edit
messages of other users.
can_pin_messages (:obj:`bool`): Optional. :obj:`True`, if the user is allowed to pin
messages; groups and supergroups only.
"""
__slots__ = (
'is_anonymous',
'can_manage_chat',
'can_delete_messages',
'can_manage_video_chats',
'can_restrict_members',
'can_promote_members',
'can_change_info',
'can_invite_users',
'can_post_messages',
'can_edit_messages',
'can_pin_messages',
'_id_attrs',
)
def __init__(
self,
is_anonymous: bool,
can_manage_chat: bool,
can_delete_messages: bool,
can_manage_video_chats: bool,
can_restrict_members: bool,
can_promote_members: bool,
can_change_info: bool,
can_invite_users: bool,
can_post_messages: bool = None,
can_edit_messages: bool = None,
can_pin_messages: bool = None,
**_kwargs: Any,
) -> None:
# Required
self.is_anonymous = is_anonymous
self.can_manage_chat = can_manage_chat
self.can_delete_messages = can_delete_messages
self.can_manage_video_chats = can_manage_video_chats
self.can_restrict_members = can_restrict_members
self.can_promote_members = can_promote_members
self.can_change_info = can_change_info
self.can_invite_users = can_invite_users
# Optionals
self.can_post_messages = can_post_messages
self.can_edit_messages = can_edit_messages
self.can_pin_messages = can_pin_messages
self._id_attrs = (
self.is_anonymous,
self.can_manage_chat,
self.can_delete_messages,
self.can_manage_video_chats,
self.can_restrict_members,
self.can_promote_members,
self.can_change_info,
self.can_invite_users,
self.can_post_messages,
self.can_edit_messages,
self.can_pin_messages,
)
@classmethod
def all_rights(cls) -> 'ChatAdministratorRights':
"""
This method returns the :class:`ChatAdministratorRights` object with all attributes set to
:obj:`True`. This is e.g. useful when changing the bot's default administrator rights with
:meth:`telegram.Bot.set_my_default_administrator_rights`.
"""
return cls(True, True, True, True, True, True, True, True, True, True, True)
@classmethod
def no_rights(cls) -> 'ChatAdministratorRights':
"""
This method returns the :class:`ChatAdministratorRights` object with all attributes set to
:obj:`False`.
"""
return cls(False, False, False, False, False, False, False, False, False, False, False)
+36 -1
View File
@@ -286,6 +286,7 @@ class ChatMember(TelegramObject):
'can_pin_messages',
'can_manage_chat',
'can_manage_voice_chats',
'can_manage_video_chats',
'until_date',
'_id_attrs',
)
@@ -327,8 +328,18 @@ class ChatMember(TelegramObject):
is_anonymous: bool = None,
can_manage_chat: bool = None,
can_manage_voice_chats: bool = None,
can_manage_video_chats: bool = None,
**_kwargs: Any,
):
# check before required to not waste resources if the error is raised
if can_manage_voice_chats is not None and can_manage_video_chats is not None:
# if they are the same it's fine...
if can_manage_voice_chats != can_manage_video_chats:
raise ValueError(
"Only supply one of `can_manage_video_chats`/`can_manage_voice_chats`,"
" not both."
)
# Required
self.user = user
self.status = status
@@ -353,7 +364,13 @@ class ChatMember(TelegramObject):
self.can_add_web_page_previews = can_add_web_page_previews
self.is_member = is_member
self.can_manage_chat = can_manage_chat
self.can_manage_voice_chats = can_manage_voice_chats
temp = (
can_manage_video_chats
if can_manage_video_chats is not None
else can_manage_voice_chats
)
self.can_manage_voice_chats = temp
self.can_manage_video_chats = temp
self._id_attrs = (self.user, self.status)
@@ -436,6 +453,9 @@ class ChatMemberAdministrator(ChatMember):
.. versionadded:: 13.7
.. versionchanged:: 13.12
Since Bot API 6.0, voice chat was renamed to video chat.
Args:
user (:class:`telegram.User`): Information about the user.
can_be_edited (:obj:`bool`, optional): :obj:`True`, if the bot
@@ -456,6 +476,12 @@ class ChatMemberAdministrator(ChatMember):
administrator can delete messages of other users.
can_manage_voice_chats (:obj:`bool`, optional): :obj:`True`, if the
administrator can manage voice chats.
.. deprecated:: 13.12
can_manage_video_chats (:obj:`bool`): :obj:`True`, if the
administrator can manage video chats.
.. versionadded:: 13.12
can_restrict_members (:obj:`bool`, optional): :obj:`True`, if the
administrator can restrict, ban or unban chat members.
can_promote_members (:obj:`bool`, optional): :obj:`True`, if the administrator
@@ -491,6 +517,13 @@ class ChatMemberAdministrator(ChatMember):
administrator can delete messages of other users.
can_manage_voice_chats (:obj:`bool`): Optional. :obj:`True`, if the
administrator can manage voice chats.
.. deprecated:: 13.12 contains the same value as :attr:`can_manage_video_chats`
for backwards compatibility.
can_manage_video_chats (:obj:`bool`): :obj:`True`, if the
administrator can manage video chats.
.. versionadded:: 13.12
can_restrict_members (:obj:`bool`): Optional. :obj:`True`, if the
administrator can restrict, ban or unban chat members.
can_promote_members (:obj:`bool`): Optional. :obj:`True`, if the administrator
@@ -523,6 +556,7 @@ class ChatMemberAdministrator(ChatMember):
can_change_info: bool = None,
can_invite_users: bool = None,
can_pin_messages: bool = None,
can_manage_video_chats: bool = None,
**_kwargs: Any,
):
super().__init__(
@@ -541,6 +575,7 @@ class ChatMemberAdministrator(ChatMember):
can_change_info=can_change_info,
can_invite_users=can_invite_users,
can_pin_messages=can_pin_messages,
can_manage_video_chats=can_manage_video_chats,
)
+6 -2
View File
@@ -21,7 +21,7 @@ The following constants were extracted from the
`Telegram Bots API <https://core.telegram.org/bots/api>`_.
Attributes:
BOT_API_VERSION (:obj:`str`): `5.7`. Telegram Bot API version supported by this
BOT_API_VERSION (:obj:`str`): `6.1`. Telegram Bot API version supported by this
version of `python-telegram-bot`. Also available as ``telegram.bot_api_version``.
.. versionadded:: 13.4
@@ -247,7 +247,7 @@ Attributes:
"""
from typing import List
BOT_API_VERSION: str = '5.7'
BOT_API_VERSION: str = '6.1'
MAX_MESSAGE_LENGTH: int = 4096
MAX_CAPTION_LENGTH: int = 1024
ANONYMOUS_ADMIN_ID: int = 1087968824
@@ -396,3 +396,7 @@ BOT_COMMAND_SCOPE_ALL_CHAT_ADMINISTRATORS = 'all_chat_administrators'
BOT_COMMAND_SCOPE_CHAT = 'chat'
BOT_COMMAND_SCOPE_CHAT_ADMINISTRATORS = 'chat_administrators'
BOT_COMMAND_SCOPE_CHAT_MEMBER = 'chat_member'
MENU_BUTTON_COMMANDS = 'commands'
MENU_BUTTON_WEB_APP = 'web_app'
MENU_BUTTON_DEFAULT = 'default'
+3 -2
View File
@@ -50,8 +50,9 @@ class Dice(TelegramObject):
Telegram.
If :attr:`emoji` is "🎰", each value corresponds to a unique combination of symbols, which
can be found at our `wiki <https://git.io/JkeC6>`_. However, this behaviour is undocumented
and might be changed by Telegram.
can be found at our `wiki <https://github.com/python-telegram-bot/python-telegram-bot/\
wiki/Code-snippets#map-a-slot-machine-dice-value-to-the-corresponding-symbols>`_.
However, this behaviour is undocumented and might be changed by Telegram.
Args:
value (:obj:`int`): Value of the dice. 1-6 for dice, darts and bowling balls, 1-5 for
-12
View File
@@ -16,7 +16,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/].
# pylint: disable=C0413
"""Extensions over the Telegram Bot API to facilitate bot making"""
from .extbot import ExtBot
@@ -28,17 +27,6 @@ from .callbackcontext import CallbackContext
from .contexttypes import ContextTypes
from .dispatcher import Dispatcher, DispatcherHandlerStop, run_async
# https://bugs.python.org/issue41451, fixed on 3.7+, doesn't actually remove slots
# try-except is just here in case the __init__ is called twice (like in the tests)
# this block is also the reason for the pylint-ignore at the top of the file
try:
del Dispatcher.__slots__
except AttributeError as exc:
if str(exc) == '__slots__':
pass
else:
raise exc
from .jobqueue import JobQueue, Job
from .updater import Updater
from .callbackqueryhandler import CallbackQueryHandler
+9 -3
View File
@@ -139,7 +139,9 @@ class CallbackContext(Generic[UD, CD, BD]):
@bot_data.setter
def bot_data(self, value: object) -> NoReturn:
raise AttributeError(
"You can not assign a new value to bot_data, see https://git.io/Jt6ic"
"You can not assign a new value to bot_data, "
"see https://github.com/python-telegram-bot/python-telegram-bot/wiki/Storing-bot%2C"
"-user-and-chat-related-data "
)
@property
@@ -160,7 +162,9 @@ class CallbackContext(Generic[UD, CD, BD]):
@chat_data.setter
def chat_data(self, value: object) -> NoReturn:
raise AttributeError(
"You can not assign a new value to chat_data, see https://git.io/Jt6ic"
"You can not assign a new value to chat_data, "
"see https://github.com/python-telegram-bot/python-telegram-bot/wiki/Storing-bot%2C"
"-user-and-chat-related-data "
)
@property
@@ -175,7 +179,9 @@ class CallbackContext(Generic[UD, CD, BD]):
@user_data.setter
def user_data(self, value: object) -> NoReturn:
raise AttributeError(
"You can not assign a new value to user_data, see https://git.io/Jt6ic"
"You can not assign a new value to user_data, "
"see https://github.com/python-telegram-bot/python-telegram-bot/wiki/Storing-bot%2C"
"-user-and-chat-related-data "
)
def refresh_data(self) -> None:
+2 -1
View File
@@ -55,7 +55,8 @@ class CallbackQueryHandler(Handler[Update, CCT]):
user or in the same chat, it will be the same ``dict``.
Note that this is DEPRECATED, and you should use context based callbacks. See
https://git.io/fxJuV for more info.
https://github.com/python-telegram-bot/python-telegram-bot/wiki\
/Transition-guide-to-Version-12.0 for more info.
* If your bot allows arbitrary objects as ``callback_data``, it may happen that the
original ``callback_data`` for the incoming :class:`telegram.CallbackQuery`` can not be
found. This is the case when either a malicious client tempered with the
+2 -1
View File
@@ -35,7 +35,8 @@ class ChatJoinRequestHandler(Handler[Update, CCT]):
or in the same chat, it will be the same ``dict``.
Note that this is DEPRECATED, and you should use context based callbacks. See
https://git.io/fxJuV for more info.
https://github.com/python-telegram-bot/python-telegram-bot/wiki\
/Transition-guide-to-Version-12.0 for more info.
Warning:
When setting ``run_async`` to :obj:`True`, you cannot rely on adding custom
+2 -1
View File
@@ -39,7 +39,8 @@ class ChatMemberHandler(Handler[Update, CCT]):
or in the same chat, it will be the same ``dict``.
Note that this is DEPRECATED, and you should use context based callbacks. See
https://git.io/fxJuV for more info.
https://github.com/python-telegram-bot/python-telegram-bot/wiki\
/Transition-guide-to-Version-12.0 for more info.
Warning:
When setting ``run_async`` to :obj:`True`, you cannot rely on adding custom
+2 -1
View File
@@ -42,7 +42,8 @@ class ChosenInlineResultHandler(Handler[Update, CCT]):
or in the same chat, it will be the same ``dict``.
Note that this is DEPRECATED, and you should use context based callbacks. See
https://git.io/fxJuV for more info.
https://github.com/python-telegram-bot/python-telegram-bot/wiki\
/Transition-guide-to-Version-12.0 for more info.
Warning:
When setting ``run_async`` to :obj:`True`, you cannot rely on adding custom
+7 -3
View File
@@ -55,7 +55,8 @@ class CommandHandler(Handler[Update, CCT]):
user or in the same chat, it will be the same :obj:`dict`.
Note that this is DEPRECATED, and you should use context based callbacks. See
https://git.io/fxJuV for more info.
https://github.com/python-telegram-bot/python-telegram-bot/wiki\
/Transition-guide-to-Version-12.0 for more info.
Warning:
When setting ``run_async`` to :obj:`True`, you cannot rely on adding custom
@@ -169,7 +170,9 @@ class CommandHandler(Handler[Update, CCT]):
if allow_edited is not None:
warnings.warn(
'allow_edited is deprecated. See https://git.io/fxJuV for more info',
'allow_edited is deprecated. See '
'https://github.com/python-telegram-bot/python-telegram-bot/wiki/Transition'
'-guide-to-Version-12.0 for more info',
TelegramDeprecationWarning,
stacklevel=2,
)
@@ -288,7 +291,8 @@ class PrefixHandler(CommandHandler):
user or in the same chat, it will be the same :obj:`dict`.
Note that this is DEPRECATED, and you should use context based callbacks. See
https://git.io/fxJuV for more info.
https://github.com/python-telegram-bot/python-telegram-bot/wiki\
/Transition-guide-to-Version-12.0 for more info.
Warning:
When setting ``run_async`` to :obj:`True`, you cannot rely on adding custom
+2 -1
View File
@@ -50,7 +50,8 @@ class ContextTypes(Generic[CCT, UD, CD, BD]):
__slots__ = ('_context', '_bot_data', '_chat_data', '_user_data')
# overload signatures generated with https://git.io/JtJPj
# overload signatures generated with
# https://gist.github.com/Bibo-Joshi/399382cda537fb01bd86b13c3d03a956
@overload
def __init__(
+3 -2
View File
@@ -79,7 +79,8 @@ class ConversationHandler(Handler[Update, CCT]):
Finally, ``ConversationHandler``, does *not* handle (edited) channel posts.
.. _`FAQ`: https://git.io/JtcyU
.. _`FAQ`: https://github.com/python-telegram-bot/python-telegram-bot/wiki\
/Frequently-Asked-Questions#what-do-the-per_-settings-in-conversationhandler-do
The first collection, a ``list`` named :attr:`entry_points`, is used to initiate the
conversation, for example with a :class:`telegram.ext.CommandHandler` or
@@ -119,7 +120,7 @@ class ConversationHandler(Handler[Update, CCT]):
:attr:`END` to end the *parent* conversation from within the nested one. For an example on
nested :class:`ConversationHandler` s, see our `examples`_.
.. _`examples`: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples
.. _`examples`: https://github.com/python-telegram-bot/python-telegram-bot/blob/v13.x/examples
Args:
entry_points (List[:class:`telegram.ext.Handler`]): A list of ``Handler`` objects that can
+5 -2
View File
@@ -241,7 +241,9 @@ class Dispatcher(Generic[CCT, UD, CD, BD]):
if not use_context:
warnings.warn(
'Old Handler API is deprecated - see https://git.io/fxJuV for details',
'Old Handler API is deprecated - see '
'https://github.com/python-telegram-bot/python-telegram-bot/wiki/Transition'
'-guide-to-Version-12.0 for details',
TelegramDeprecationWarning,
stacklevel=3,
)
@@ -763,7 +765,8 @@ class Dispatcher(Generic[CCT, UD, CD, BD]):
asynchronously using :meth:`run_async`. Defaults to :obj:`False`.
Note:
See https://git.io/fxJuV for more info about switching to context based API.
See https://github.com/python-telegram-bot/python-telegram-bot/wiki\
/Transition-guide-to-Version-12.0 for more info about switching to context based API.
"""
if callback in self.error_handlers:
self.logger.debug('The callback is already registered as an error handler. Ignoring.')
+3 -1
View File
@@ -62,7 +62,9 @@ class ExtBot(telegram.bot.Bot):
arbitrary_callback_data (:obj:`bool` | :obj:`int`, optional): Whether to
allow arbitrary objects as callback data for :class:`telegram.InlineKeyboardButton`.
Pass an integer to specify the maximum number of objects cached in memory. For more
details, please see our `wiki <https://git.io/JGBDI>`_. Defaults to :obj:`False`.
details, please see our `wiki <https://github.com/python-telegram-bot/\
python-telegram-bot/wiki/Arbitrary-callback_data>`_.
Defaults to :obj:`False`.
Attributes:
arbitrary_callback_data (:obj:`bool` | :obj:`int`): Whether this bot instance
+107
View File
@@ -1004,6 +1004,38 @@ officedocument.wordprocessingml.document")``.
location = _Location()
"""Messages that contain :class:`telegram.Location`."""
class _UserAttachment(UpdateFilter):
__slots__ = ()
name = "Filters.user_attachment"
def filter(self, update: Update) -> bool:
return bool(update.effective_user) and bool(
update.effective_user.added_to_attachment_menu
)
user_attachment = _UserAttachment()
"""This filter filters *any* message that have a user who added the bot to their
:attr:`attachment menu <telegram.User.added_to_attachment_menu>` as
:attr:`telegram.Update.effective_user`.
.. versionadded:: 13.13
"""
class _UserPremium(UpdateFilter):
__slots__ = ()
name = "Filters.premium_user"
def filter(self, update: Update) -> bool:
return bool(update.effective_user) and bool(update.effective_user.is_premium)
premium_user = _UserPremium()
"""This filter filters *any* message from a
:attr:`Telegram Premium user <telegram.User.is_premium>` as
:attr:`telegram.Update.effective_user`.
.. versionadded:: 13.13
"""
class _Venue(MessageFilter):
__slots__ = ()
name = 'Filters.venue'
@@ -1182,6 +1214,56 @@ officedocument.wordprocessingml.document")``.
voice_chat_participants_invited = _VoiceChatParticipantsInvited()
"""Messages that contain :attr:`telegram.Message.voice_chat_participants_invited`."""
class _VideoChatScheduled(MessageFilter):
__slots__ = ()
name = 'Filters.status_update.video_chat_scheduled'
def filter(self, message: Message) -> bool:
return bool(message.video_chat_scheduled)
video_chat_scheduled = _VideoChatScheduled()
"""Messages that contain :attr:`telegram.Message.video_chat_scheduled`."""
class _VideoChatStarted(MessageFilter):
__slots__ = ()
name = 'Filters.status_update.video_chat_started'
def filter(self, message: Message) -> bool:
return bool(message.video_chat_started)
video_chat_started = _VideoChatStarted()
"""Messages that contain :attr:`telegram.Message.video_chat_started`."""
class _VideoChatEnded(MessageFilter):
__slots__ = ()
name = 'Filters.status_update.video_chat_ended'
def filter(self, message: Message) -> bool:
return bool(message.video_chat_ended)
video_chat_ended = _VideoChatEnded()
"""Messages that contain :attr:`telegram.Message.voice_chat_ended`."""
class _VideoChatParticipantsInvited(MessageFilter):
__slots__ = ()
name = 'Filters.status_update.video_chat_participants_invited'
def filter(self, message: Message) -> bool:
return bool(message.video_chat_participants_invited)
video_chat_participants_invited = _VideoChatParticipantsInvited()
"""Messages that contain :attr:`telegram.Message.video_chat_participants_invited`."""
class _WebAppData(MessageFilter):
__slots__ = ()
name = 'Filters.status_update.web_app_data'
def filter(self, message: Message) -> bool:
return bool(message.web_app_data)
web_app_data = _WebAppData()
"""Messages that contain :attr:`telegram.Message.web_app_data`."""
name = 'Filters.status_update'
def filter(self, message: Update) -> bool:
@@ -1201,6 +1283,11 @@ officedocument.wordprocessingml.document")``.
or self.voice_chat_started(message)
or self.voice_chat_ended(message)
or self.voice_chat_participants_invited(message)
or self.video_chat_scheduled(message)
or self.video_chat_started(message)
or self.video_chat_ended(message)
or self.video_chat_participants_invited(message)
or self.web_app_data(message)
)
status_update = _StatusUpdate()
@@ -1242,18 +1329,38 @@ officedocument.wordprocessingml.document")``.
:attr:`telegram.Message.voice_chat_scheduled`.
.. versionadded:: 13.5
.. deprecated:: 13.12
voice_chat_started: Messages that contain
:attr:`telegram.Message.voice_chat_started`.
.. versionadded:: 13.4
.. deprecated:: 13.12
voice_chat_ended: Messages that contain
:attr:`telegram.Message.voice_chat_ended`.
.. versionadded:: 13.4
.. deprecated:: 13.12
voice_chat_participants_invited: Messages that contain
:attr:`telegram.Message.voice_chat_participants_invited`.
.. versionadded:: 13.4
.. deprecated:: 13.12
video_chat_scheduled: Messages that contain
:attr:`telegram.Message.video_chat_scheduled`.
.. versionadded:: 13.12
video_chat_started: Messages that contain
:attr:`telegram.Message.video_chat_started`.
.. versionadded:: 13.12
video_chat_ended: Messages that contain
:attr:`telegram.Message.video_chat_ended`.
.. versionadded:: 13.12
video_chat_participants_invited: Messages that contain
:attr:`telegram.Message.video_chat_participants_invited`.
.. versionadded:: 13.12
"""
+4 -2
View File
@@ -45,7 +45,8 @@ class Handler(Generic[UT, CCT], ABC):
or in the same chat, it will be the same ``dict``.
Note that this is DEPRECATED, and you should use context based callbacks. See
https://git.io/fxJuV for more info.
https://github.com/python-telegram-bot/python-telegram-bot/wiki\
/Transition-guide-to-Version-12.0 for more info.
Warning:
When setting ``run_async`` to :obj:`True`, you cannot rely on adding custom
@@ -232,7 +233,8 @@ class Handler(Generic[UT, CCT], ABC):
it should subclass this method, but remember to call this super method.
DEPRECATED: This method is being replaced by new context based callbacks. Please see
https://git.io/fxJuV for more info.
https://github.com/python-telegram-bot/python-telegram-bot/wiki\
/Transition-guide-to-Version-12.0 for more info.
Args:
dispatcher (:class:`telegram.ext.Dispatcher`): The dispatcher.
+2 -1
View File
@@ -55,7 +55,8 @@ class InlineQueryHandler(Handler[Update, CCT]):
or in the same chat, it will be the same ``dict``.
Note that this is DEPRECATED, and you should use context based callbacks. See
https://git.io/fxJuV for more info.
https://github.com/python-telegram-bot/python-telegram-bot/wiki\
/Transition-guide-to-Version-12.0 for more info.
Warning:
* When setting ``run_async`` to :obj:`True`, you cannot rely on adding custom
+12 -4
View File
@@ -45,7 +45,8 @@ class MessageHandler(Handler[Update, CCT]):
or in the same chat, it will be the same ``dict``.
Note that this is DEPRECATED, and you should use context based callbacks. See
https://git.io/fxJuV for more info.
https://github.com/python-telegram-bot/python-telegram-bot/wiki\
/Transition-guide-to-Version-12.0 for more info.
Warning:
When setting ``run_async`` to :obj:`True`, you cannot rely on adding custom
@@ -155,7 +156,9 @@ class MessageHandler(Handler[Update, CCT]):
self.filters = Filters.update
if message_updates is not None:
warnings.warn(
'message_updates is deprecated. See https://git.io/fxJuV for more info',
'message_updates is deprecated. See '
'https://github.com/python-telegram-bot/python-telegram-bot/wiki/Transition'
'-guide-to-Version-12.0 for more info',
TelegramDeprecationWarning,
stacklevel=2,
)
@@ -164,7 +167,10 @@ class MessageHandler(Handler[Update, CCT]):
if channel_post_updates is not None:
warnings.warn(
'channel_post_updates is deprecated. See https://git.io/fxJuV ' 'for more info',
'channel_post_updates is deprecated. See '
'https://github.com/python-telegram-bot/python-telegram-bot/wiki/Transition'
'-guide-to-Version-12.0 '
'for more info',
TelegramDeprecationWarning,
stacklevel=2,
)
@@ -173,7 +179,9 @@ class MessageHandler(Handler[Update, CCT]):
if edited_updates is not None:
warnings.warn(
'edited_updates is deprecated. See https://git.io/fxJuV for more info',
'edited_updates is deprecated. See '
'https://github.com/python-telegram-bot/python-telegram-bot/wiki/Transition'
'-guide-to-Version-12.0 for more info',
TelegramDeprecationWarning,
stacklevel=2,
)
+8 -4
View File
@@ -50,7 +50,8 @@ class DelayQueue(threading.Thread):
.. deprecated:: 13.3
:class:`telegram.ext.DelayQueue` in its current form is deprecated and will be reinvented
in a future release. See `this thread <https://git.io/JtDbF>`_ for a list of known bugs.
in a future release. See `this thread <https://github.com/python-telegram-bot/\
python-telegram-bot/issues/2139>`_ for a list of known bugs.
Args:
queue (:obj:`Queue`, optional): Used to pass callbacks to thread. Creates ``Queue``
@@ -92,7 +93,8 @@ class DelayQueue(threading.Thread):
):
warnings.warn(
'DelayQueue in its current form is deprecated and will be reinvented in a future '
'release. See https://git.io/JtDbF for a list of known bugs.',
'release. See https://github.com/python-telegram-bot/python-telegram-bot/issues/2139 '
'for a list of known bugs.',
category=TelegramDeprecationWarning,
)
@@ -194,7 +196,8 @@ class MessageQueue:
.. deprecated:: 13.3
:class:`telegram.ext.MessageQueue` in its current form is deprecated and will be reinvented
in a future release. See `this thread <https://git.io/JtDbF>`_ for a list of known bugs.
in a future release. See `this thread <https://github.com/python-telegram-bot/\
python-telegram-bot/issues/2139>`_ for a list of known bugs.
Args:
all_burst_limit (:obj:`int`, optional): Number of maximum *all-type* callbacks to process
@@ -226,7 +229,8 @@ class MessageQueue:
):
warnings.warn(
'MessageQueue in its current form is deprecated and will be reinvented in a future '
'release. See https://git.io/JtDbF for a list of known bugs.',
'release. See https://github.com/python-telegram-bot/python-telegram-bot/issues/2139 '
'for a list of known bugs.',
category=TelegramDeprecationWarning,
)
+2 -1
View File
@@ -35,7 +35,8 @@ class PollAnswerHandler(Handler[Update, CCT]):
or in the same chat, it will be the same ``dict``.
Note that this is DEPRECATED, and you should use context based callbacks. See
https://git.io/fxJuV for more info.
https://github.com/python-telegram-bot/python-telegram-bot/wiki\
/Transition-guide-to-Version-12.0 for more info.
Warning:
When setting ``run_async`` to :obj:`True`, you cannot rely on adding custom
+2 -1
View File
@@ -35,7 +35,8 @@ class PollHandler(Handler[Update, CCT]):
or in the same chat, it will be the same ``dict``.
Note that this is DEPRECATED, and you should use context based callbacks. See
https://git.io/fxJuV for more info.
https://github.com/python-telegram-bot/python-telegram-bot/wiki\
/Transition-guide-to-Version-12.0 for more info.
Warning:
When setting ``run_async`` to :obj:`True`, you cannot rely on adding custom
+2 -1
View File
@@ -35,7 +35,8 @@ class PreCheckoutQueryHandler(Handler[Update, CCT]):
or in the same chat, it will be the same ``dict``.
Note that this is DEPRECATED, and you should use context based callbacks. See
https://git.io/fxJuV for more info.
https://github.com/python-telegram-bot/python-telegram-bot/wiki\
/Transition-guide-to-Version-12.0 for more info.
Warning:
When setting ``run_async`` to :obj:`True`, you cannot rely on adding custom
+3 -1
View File
@@ -128,7 +128,9 @@ class RegexHandler(MessageHandler):
run_async: Union[bool, DefaultValue] = DEFAULT_FALSE,
):
warnings.warn(
'RegexHandler is deprecated. See https://git.io/fxJuV for more info',
'RegexHandler is deprecated. See '
'https://github.com/python-telegram-bot/python-telegram-bot/wiki/Transition-guide-to'
'-Version-12.0 for more info',
TelegramDeprecationWarning,
stacklevel=2,
)
+2 -1
View File
@@ -34,7 +34,8 @@ class ShippingQueryHandler(Handler[Update, CCT]):
or in the same chat, it will be the same ``dict``.
Note that this is DEPRECATED, and you should use context based callbacks. See
https://git.io/fxJuV for more info.
https://github.com/python-telegram-bot/python-telegram-bot/wiki\
/Transition-guide-to-Version-12.0 for more info.
Warning:
When setting ``run_async`` to :obj:`True`, you cannot rely on adding custom
+5
View File
@@ -463,6 +463,11 @@ class Updater(Generic[CCT, UD, CD, BD]):
application. Else, the webhook will be started on
https://listen:port/url_path. Also calls :meth:`telegram.Bot.set_webhook` as required.
Note:
``telegram.Bot.set_webhook.secret_token`` is not checked by this webhook
implementation. If you want to use this new security parameter, either build your own
webhook server or update your code to version 20.0a2+.
.. versionchanged:: 13.4
:meth:`start_webhook` now *always* calls :meth:`telegram.Bot.set_webhook`, so pass
``webhook_url`` instead of calling ``updater.bot.set_webhook(webhook_url)`` manually.
+16
View File
@@ -60,6 +60,10 @@ class Sticker(TelegramObject):
position where the mask should be placed.
file_size (:obj:`int`, optional): File size.
bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods.
premium_animation (:class:`telegram.File`, optional): Premium animation for the sticker,
if the sticker is premium.
.. versionadded:: 13.13
**kwargs (obj:`dict`): Arbitrary keyword arguments.
Attributes:
@@ -80,6 +84,10 @@ class Sticker(TelegramObject):
mask_position (:class:`telegram.MaskPosition`): Optional. For mask stickers, the position
where the mask should be placed.
file_size (:obj:`int`): Optional. File size.
premium_animation (:class:`telegram.File`): Optional. Premium animation for the sticker,
if the sticker is premium.
.. versionadded:: 13.13
bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods.
"""
@@ -97,6 +105,7 @@ class Sticker(TelegramObject):
'height',
'file_unique_id',
'emoji',
'premium_animation',
'_id_attrs',
)
@@ -114,6 +123,7 @@ class Sticker(TelegramObject):
set_name: str = None,
mask_position: 'MaskPosition' = None,
bot: 'Bot' = None,
premium_animation: 'File' = None,
**_kwargs: Any,
):
# Required
@@ -130,12 +140,17 @@ class Sticker(TelegramObject):
self.set_name = set_name
self.mask_position = mask_position
self.bot = bot
self.premium_animation = premium_animation
self._id_attrs = (self.file_unique_id,)
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Sticker']:
"""See :meth:`telegram.TelegramObject.de_json`."""
# needs to be here to avoid circular imports
# pylint: disable=import-outside-toplevel
from telegram import File
data = cls._parse_data(data)
if not data:
@@ -143,6 +158,7 @@ class Sticker(TelegramObject):
data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot)
data['mask_position'] = MaskPosition.de_json(data.get('mask_position'), bot)
data["premium_animation"] = File.de_json(data.get("premium_animation"), bot)
return cls(bot=bot, **data)
+12
View File
@@ -17,11 +17,23 @@
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Telegram CallbackGame."""
from typing import Optional, TYPE_CHECKING
from telegram import TelegramObject
from telegram.utils.types import JSONDict
if TYPE_CHECKING:
from telegram import Bot
class CallbackGame(TelegramObject):
"""A placeholder, currently holds no information. Use BotFather to set up your game."""
__slots__ = ()
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['CallbackGame']:
"""See :meth:`telegram.TelegramObject.de_json`."""
if data is None:
return None
return cls()
+50 -11
View File
@@ -18,12 +18,13 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Telegram InlineKeyboardButton."""
from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING, Any, Optional
from telegram import TelegramObject
from telegram import TelegramObject, WebAppInfo, CallbackGame, LoginUrl
from telegram.utils.types import JSONDict
if TYPE_CHECKING:
from telegram import CallbackGame, LoginUrl
from telegram import Bot
class InlineKeyboardButton(TelegramObject):
@@ -50,11 +51,12 @@ class InlineKeyboardButton(TelegramObject):
Older clients will display *unsupported message*.
Warning:
If your bot allows your arbitrary callback data, buttons whose callback data is a
non-hashable object will become unhashable. Trying to evaluate ``hash(button)`` will
result in a :class:`TypeError`.
* If your bot allows your arbitrary callback data, buttons whose callback data is a
non-hashable object will become unhashable. Trying to evaluate ``hash(button)`` will
result in a :class:`TypeError`.
.. versionchanged:: 13.6
.. versionchanged:: 13.6
* After Bot API 6.1, only ``HTTPS`` links will be allowed in :attr:`login_url`.
Args:
text (:obj:`str`): Label text on the button.
@@ -64,11 +66,21 @@ class InlineKeyboardButton(TelegramObject):
.. versionchanged:: 13.9
You can now mention a user using ``tg://user?id=<user_id>``.
login_url (:class:`telegram.LoginUrl`, optional): An HTTP URL used to automatically
login_url (:class:`telegram.LoginUrl`, optional): An ``HTTPS`` URL used to automatically
authorize the user. Can be used as a replacement for the Telegram Login Widget.
Caution:
Only ``HTTPS`` links are allowed after Bot API 6.1.
callback_data (:obj:`str` | :obj:`Any`, optional): Data to be sent in a callback query to
the bot when button is pressed, UTF-8 1-64 bytes. If the bot instance allows arbitrary
callback data, anything can be passed.
web_app (:obj:`telegram.WebAppInfo`, optional): Description of the `Web App
<https://core.telegram.org/bots/webapps>`_ that will be launched when the user presses
the button. The Web App will be able to send an arbitrary message on behalf of the user
using the method :meth:`~telegram.Bot.answer_web_app_query`. Available only in
private chats between a user and the bot.
.. versionadded:: 13.12
switch_inline_query (:obj:`str`, optional): If set, pressing the button will prompt the
user to select one of their chats, open that chat and insert the bot's username and the
specified inline query in the input field. Can be empty, in which case just the bot's
@@ -97,10 +109,20 @@ class InlineKeyboardButton(TelegramObject):
.. versionchanged:: 13.9
You can now mention a user using ``tg://user?id=<user_id>``.
login_url (:class:`telegram.LoginUrl`): Optional. An HTTP URL used to automatically
login_url (:class:`telegram.LoginUrl`): Optional. An ``HTTPS`` URL used to automatically
authorize the user. Can be used as a replacement for the Telegram Login Widget.
Caution:
Only ``HTTPS`` links are allowed after Bot API 6.1.
callback_data (:obj:`str` | :obj:`object`): Optional. Data to be sent in a callback query
to the bot when button is pressed, UTF-8 1-64 bytes.
web_app (:obj:`telegram.WebAppInfo`): Optional. Description of the `Web App
<https://core.telegram.org/bots/webapps>`_ that will be launched when the user presses
the button. The Web App will be able to send an arbitrary message on behalf of the user
using the method :meth:`~telegram.Bot.answer_web_app_query`. Available only in
private chats between a user and the bot.
.. versionadded:: 13.12
switch_inline_query (:obj:`str`): Optional. Will prompt the user to select one of their
chats, open that chat and insert the bot's username and the specified inline query in
the input field. Can be empty, in which case just the bots username will be inserted.
@@ -123,6 +145,7 @@ class InlineKeyboardButton(TelegramObject):
'text',
'_id_attrs',
'login_url',
'web_app',
)
def __init__(
@@ -132,9 +155,10 @@ class InlineKeyboardButton(TelegramObject):
callback_data: object = None,
switch_inline_query: str = None,
switch_inline_query_current_chat: str = None,
callback_game: 'CallbackGame' = None,
callback_game: CallbackGame = None,
pay: bool = None,
login_url: 'LoginUrl' = None,
login_url: LoginUrl = None,
web_app: WebAppInfo = None,
**_kwargs: Any,
):
# Required
@@ -148,6 +172,7 @@ class InlineKeyboardButton(TelegramObject):
self.switch_inline_query_current_chat = switch_inline_query_current_chat
self.callback_game = callback_game
self.pay = pay
self.web_app = web_app
self._id_attrs = ()
self._set_id_attrs()
@@ -163,6 +188,20 @@ class InlineKeyboardButton(TelegramObject):
self.pay,
)
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['InlineKeyboardButton']:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data['login_url'] = LoginUrl.de_json(data.get('login_url'), bot)
data['web_app'] = WebAppInfo.de_json(data.get('web_app'), bot)
data['callback_game'] = CallbackGame.de_json(data.get('callback_game'), bot)
return cls(**data)
def update_callback_data(self, callback_data: object) -> None:
"""
Sets :attr:`callback_data` to the passed object. Intended to be used by
+41 -4
View File
@@ -18,9 +18,13 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Telegram KeyboardButton."""
from typing import Any
from typing import TYPE_CHECKING, Any, Optional
from telegram import TelegramObject, KeyboardButtonPollType
from telegram import TelegramObject, KeyboardButtonPollType, WebAppInfo
from telegram.utils.types import JSONDict
if TYPE_CHECKING:
from telegram import Bot
class KeyboardButton(TelegramObject):
@@ -35,9 +39,11 @@ class KeyboardButton(TelegramObject):
Note:
* Optional fields are mutually exclusive.
* :attr:`request_contact` and :attr:`request_location` options will only work in Telegram
versions released after 9 April, 2016. Older clients will ignore them.
versions released after 9 April, 2016. Older clients will display unsupported message.
* :attr:`request_poll` option will only work in Telegram versions released after 23
January, 2020. Older clients will receive unsupported message.
* :attr:`web_app` option will only work in Telegram versions released after 16 April, 2022.
Older clients will display unsupported message.
Args:
text (:obj:`str`): Text of the button. If none of the optional fields are used, it will be
@@ -49,16 +55,32 @@ class KeyboardButton(TelegramObject):
request_poll (:class:`KeyboardButtonPollType`, optional): If specified, the user will be
asked to create a poll and send it to the bot when the button is pressed. Available in
private chats only.
web_app (:class:`WebAppInfo`, optional): If specified, the described `Web App
<https://core.telegram.org/bots/webapps>`_ will be launched when the button is pressed.
The Web App will be able to send a :attr:`Message.web_app_data` service message.
Available in private chats only.
.. versionadded:: 13.12
Attributes:
text (:obj:`str`): Text of the button.
request_contact (:obj:`bool`): Optional. The user's phone number will be sent.
request_location (:obj:`bool`): Optional. The user's current location will be sent.
request_poll (:class:`KeyboardButtonPollType`): Optional. If the user should create a poll.
web_app (:class:`WebAppInfo`): Optional. If the described Web App will be launched when the
button is pressed.
.. versionadded:: 13.12
"""
__slots__ = ('request_location', 'request_contact', 'request_poll', 'text', '_id_attrs')
__slots__ = (
'request_location',
'request_contact',
'request_poll',
'text',
'web_app',
'_id_attrs',
)
def __init__(
self,
@@ -66,6 +88,7 @@ class KeyboardButton(TelegramObject):
request_contact: bool = None,
request_location: bool = None,
request_poll: KeyboardButtonPollType = None,
web_app: WebAppInfo = None,
**_kwargs: Any,
):
# Required
@@ -74,6 +97,7 @@ class KeyboardButton(TelegramObject):
self.request_contact = request_contact
self.request_location = request_location
self.request_poll = request_poll
self.web_app = web_app
self._id_attrs = (
self.text,
@@ -81,3 +105,16 @@ class KeyboardButton(TelegramObject):
self.request_location,
self.request_poll,
)
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['KeyboardButton']:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data['request_poll'] = KeyboardButtonPollType.de_json(data.get('request_poll'), bot)
data['web_app'] = WebAppInfo.de_json(data.get('web_app'), bot)
return cls(**data)
+2 -2
View File
@@ -40,7 +40,7 @@ class LoginUrl(TelegramObject):
`Checking authorization <https://core.telegram.org/widgets/login#checking-authorization>`_
Args:
url (:obj:`str`): An HTTP URL to be opened with user authorization data added to the query
url (:obj:`str`): An HTTPS URL to be opened with user authorization data added to the query
string when the button is pressed. If the user refuses to provide authorization data,
the original URL without information about the user will be opened. The data added is
the same as described in
@@ -60,7 +60,7 @@ class LoginUrl(TelegramObject):
for your bot to send messages to the user.
Attributes:
url (:obj:`str`): An HTTP URL to be opened with user authorization data.
url (:obj:`str`): An HTTPS URL to be opened with user authorization data.
forward_text (:obj:`str`): Optional. New text of the button in forwarded messages.
bot_username (:obj:`str`): Optional. Username of a bot, which will be used for user
authorization.
+179
View File
@@ -0,0 +1,179 @@
#!/usr/bin/env python
# pylint: disable=too-few-public-methods
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2022
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains objects related to Telegram menu buttons."""
from typing import Any, ClassVar, Optional, TYPE_CHECKING, Dict, Type
from telegram import TelegramObject, constants, WebAppInfo
from telegram.utils.types import JSONDict
if TYPE_CHECKING:
from telegram import Bot
class MenuButton(TelegramObject):
"""This object describes the bot's menu button in a private chat. It should be one of
* :class:`telegram.MenuButtonCommands`
* :class:`telegram.MenuButtonWebApp`
* :class:`telegram.MenuButtonDefault`
If a menu button other than :class:`telegram.MenuButtonDefault` is set for a private chat,
then it is applied in the chat. Otherwise the default menu button is applied. By default, the
menu button opens the list of bot commands.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`type` is equal. For subclasses with additional attributes,
the notion of equality is overridden.
.. versionadded:: 13.12
Args:
type (:obj:`str`): Type of menu button that the instance represents.
Attributes:
type (:obj:`str`): Type of menu button that the instance represents.
"""
__slots__ = ('type', '_id_attrs')
def __init__(self, type: str, **_kwargs: Any): # pylint: disable=redefined-builtin
self.type = type
self._id_attrs = (self.type,)
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['MenuButton']:
"""Converts JSON data to the appropriate :class:`MenuButton` object, i.e. takes
care of selecting the correct subclass.
Args:
data (Dict[:obj:`str`, ...]): The JSON data.
bot (:class:`telegram.Bot`): The bot associated with this object.
Returns:
The Telegram object.
"""
data = cls._parse_data(data)
if not data:
return None
_class_mapping: Dict[str, Type['MenuButton']] = {
cls.COMMANDS: MenuButtonCommands,
cls.WEB_APP: MenuButtonWebApp,
cls.DEFAULT: MenuButtonDefault,
}
if cls is MenuButton and data['type'] in _class_mapping:
return _class_mapping[data['type']].de_json(data, bot=bot)
return cls(**data, bot=bot)
COMMANDS: ClassVar[str] = constants.MENU_BUTTON_COMMANDS
""":const:`telegram.constants.MENU_BUTTON_COMMANDS`"""
WEB_APP: ClassVar[str] = constants.MENU_BUTTON_WEB_APP
""":const:`telegram.constants.MENU_BUTTON_WEB_APP`"""
DEFAULT: ClassVar[str] = constants.MENU_BUTTON_DEFAULT
""":const:`telegram.constants.MENU_BUTTON_DEFAULT`"""
class MenuButtonCommands(MenuButton):
"""Represents a menu button, which opens the bot's list of commands.
.. versionadded:: 13.12
Attributes:
type (:obj:`str`): :const:`telegram.constants.MENU_BUTTON_COMMANDS`.
"""
__slots__ = ()
def __init__(self, **_kwargs: Any):
super().__init__(type=constants.MENU_BUTTON_COMMANDS)
class MenuButtonWebApp(MenuButton):
"""Represents a menu button, which launches a
`Web App <https://core.telegram.org/bots/webapps>`_.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`type`, :attr:`text` and :attr:`web_app`
are equal.
.. versionadded:: 13.12
Args:
text (:obj:`str`): Text of the button.
web_app (:class:`telegram.WebAppInfo`): Description of the Web App that will be launched
when the user presses the button. The Web App will be able to send an arbitrary
message on behalf of the user using the method :meth:`~telegram.Bot.answerWebAppQuery`.
Attributes:
type (:obj:`str`): :const:`telegram.constants.MENU_BUTTON_WEB_APP`.
text (:obj:`str`): Text of the button.
web_app (:class:`telegram.WebAppInfo`): Description of the Web App that will be launched
when the user presses the button. The Web App will be able to send an arbitrary
message on behalf of the user using the method :meth:`~telegram.Bot.answerWebAppQuery`.
"""
__slots__ = (
'text',
'web_app',
)
def __init__(self, text: str, web_app: WebAppInfo, **_kwargs: Any):
super().__init__(type=constants.MENU_BUTTON_WEB_APP)
self.text = text
self.web_app = web_app
self._id_attrs = (self.type, self.text, self.web_app)
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['MenuButtonWebApp']:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data['web_app'] = WebAppInfo.de_json(data.get('web_app'), bot)
return cls(bot=bot, **data)
def to_dict(self) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict`."""
data = super().to_dict()
data['web_app'] = self.web_app.to_dict()
return data
class MenuButtonDefault(MenuButton):
"""Describes that no specific value for the menu button was set.
.. versionadded:: 13.12
Attributes:
type (:obj:`str`): :const:`telegram.constants.MENU_BUTTON_DEFAULT`.
"""
__slots__ = ()
def __init__(self, **_kwargs: Any):
super().__init__(type=constants.MENU_BUTTON_DEFAULT)
+129 -8
View File
@@ -54,6 +54,11 @@ from telegram import (
ReplyMarkup,
MessageAutoDeleteTimerChanged,
VoiceChatScheduled,
VideoChatStarted,
VideoChatEnded,
VideoChatParticipantsInvited,
WebAppData,
VideoChatScheduled,
)
from telegram.utils.helpers import (
escape_markdown,
@@ -88,6 +93,9 @@ class Message(TelegramObject):
Note:
In Python ``from`` is a reserved word, use ``from_user`` instead.
.. versionchanged:: 13.12
Since Bot API 6.0, voice chat was renamed to video chat.
Args:
message_id (:obj:`int`): Unique message identifier inside this chat.
from_user (:class:`telegram.User`, optional): Sender of the message; empty for messages
@@ -218,18 +226,41 @@ class Message(TelegramObject):
voice chat scheduled.
.. versionadded:: 13.5
.. deprecated:: 13.12
voice_chat_started (:class:`telegram.VoiceChatStarted`, optional): Service message: voice
chat started.
.. versionadded:: 13.4
.. deprecated:: 13.12
voice_chat_ended (:class:`telegram.VoiceChatEnded`, optional): Service message: voice chat
ended.
.. versionadded:: 13.4
.. deprecated:: 13.12
voice_chat_participants_invited (:class:`telegram.VoiceChatParticipantsInvited` optional):
Service message: new participants invited to a voice chat.
.. versionadded:: 13.4
.. deprecated:: 13.12
video_chat_scheduled (:class:`telegram.VideoChatScheduled`, optional): Service message:
video chat scheduled.
.. versionadded:: 13.12
video_chat_started (:class:`telegram.VideaChatStarted`, optional): Service message: video
chat started.
.. versionadded:: 13.12
video_chat_ended (:class:`telegram.VideaChatEnded`, optional): Service message: video chat
ended.
.. versionadded:: 13.12
video_chat_participants_invited (:class:`telegram.VideoChatParticipantsInvited` optional):
Service message: new participants invited to a video chat.
.. versionadded:: 13.12
web_app_data (:class:`telegram.WebAppData`, optional): Service message: data sent by a Web
App.
.. versionadded:: 13.12
reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached
to the message. ``login_url`` buttons are represented as ordinary url buttons.
bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods.
@@ -332,18 +363,46 @@ class Message(TelegramObject):
voice chat scheduled.
.. versionadded:: 13.5
.. deprecated:: 13.12 contains the same value as :attr:`VideoChatScheduled`
for backwards compatibility.
voice_chat_started (:class:`telegram.VoiceChatStarted`): Optional. Service message: voice
chat started.
.. versionadded:: 13.4
.. deprecated:: 13.12 contains the same value as :attr:`VideoChatStarted`
for backwards compatibility.
voice_chat_ended (:class:`telegram.VoiceChatEnded`): Optional. Service message: voice chat
ended.
.. versionadded:: 13.4
.. deprecated:: 13.12 contains the same value as :attr:`VideoChatEnded`
for backwards compatibility.
voice_chat_participants_invited (:class:`telegram.VoiceChatParticipantsInvited`): Optional.
Service message: new participants invited to a voice chat.
.. versionadded:: 13.4
.. deprecated:: 13.12 contains the same value as :attr:`VideoChatParticipantsInvited`
for backwards compatibility.
video_chat_scheduled (:class:`telegram.VideoChatScheduled`): Optional. Service message:
video chat scheduled.
.. versionadded:: 13.12
video_chat_started (:class:`telegram.VideoChatStarted`): Optional. Service message: video
chat started.
.. versionadded:: 13.12
video_chat_ended (:class:`telegram.VideoChatEnded`): Optional. Service message: video chat
ended.
.. versionadded:: 13.12
video_chat_participants_invited (:class:`telegram.VideoChatParticipantsInvited`): Optional.
Service message: new participants invited to a video chat.
.. versionadded:: 13.12
web_app_data (:class:`telegram.WebAppData`): Optional. Service message: data sent by a Web
App.
.. versionadded:: 13.12
reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached
to the message.
bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods.
@@ -410,8 +469,13 @@ class Message(TelegramObject):
'voice_chat_participants_invited',
'voice_chat_started',
'voice_chat_scheduled',
'video_chat_ended',
'video_chat_participants_invited',
'video_chat_started',
'video_chat_scheduled',
'is_automatic_forward',
'has_protected_content',
'web_app_data',
'_id_attrs',
)
@@ -516,8 +580,36 @@ class Message(TelegramObject):
voice_chat_scheduled: VoiceChatScheduled = None,
is_automatic_forward: bool = None,
has_protected_content: bool = None,
video_chat_scheduled: VideoChatScheduled = None,
video_chat_started: VideoChatStarted = None,
video_chat_ended: VideoChatEnded = None,
video_chat_participants_invited: VideoChatParticipantsInvited = None,
web_app_data: WebAppData = None,
**_kwargs: Any,
):
if (
voice_chat_scheduled is not None
and video_chat_scheduled is not None
and voice_chat_scheduled != video_chat_scheduled
):
raise ValueError(
"Only supply one of `video_chat_scheduled`/`voice_chat_scheduled`, not both."
)
if (
voice_chat_ended is not None
and video_chat_ended is not None
and voice_chat_ended != video_chat_ended
):
raise ValueError("Only supply one of `video_chat_ended`/`voice_chat_ended`, not both.")
if (
voice_chat_participants_invited is not None
and video_chat_participants_invited is not None
) and voice_chat_participants_invited != video_chat_participants_invited:
raise ValueError(
"Only supply one of `video_chat_participants_invited`/"
"`voice_chat_participants_invited`, not both."
)
# Required
self.message_id = int(message_id)
# Optionals
@@ -573,11 +665,30 @@ class Message(TelegramObject):
self.dice = dice
self.via_bot = via_bot
self.proximity_alert_triggered = proximity_alert_triggered
self.voice_chat_scheduled = voice_chat_scheduled
self.voice_chat_started = voice_chat_started
self.voice_chat_ended = voice_chat_ended
self.voice_chat_participants_invited = voice_chat_participants_invited
self.reply_markup = reply_markup
temp0 = video_chat_scheduled if video_chat_scheduled is not None else voice_chat_scheduled
self.voice_chat_scheduled = temp0
self.video_chat_scheduled = temp0
# those are empty classes, so they don't need a comparison like the others.
temp1 = video_chat_started if video_chat_started is not None else voice_chat_started
self.voice_chat_started = temp1
self.video_chat_started = temp1
temp2 = video_chat_ended if video_chat_ended is not None else voice_chat_ended
self.voice_chat_ended = temp2
self.video_chat_ended = temp2
temp3 = (
video_chat_participants_invited
if video_chat_participants_invited is not None
else voice_chat_participants_invited
)
self.voice_chat_participants_invited = temp3
self.video_chat_participants_invited = temp3
self.web_app_data = web_app_data
self.bot = bot
self._effective_attachment = DEFAULT_NONE
@@ -651,14 +762,24 @@ class Message(TelegramObject):
data.get('proximity_alert_triggered'), bot
)
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'), bot)
data['voice_chat_scheduled'] = VoiceChatScheduled.de_json(
data['voice_chat_scheduled'] = VideoChatScheduled.de_json(
data.get('voice_chat_scheduled'), bot
)
data['voice_chat_started'] = VoiceChatStarted.de_json(data.get('voice_chat_started'), bot)
data['voice_chat_ended'] = VoiceChatEnded.de_json(data.get('voice_chat_ended'), bot)
data['voice_chat_participants_invited'] = VoiceChatParticipantsInvited.de_json(
data['voice_chat_started'] = VideoChatStarted.de_json(data.get('voice_chat_started'), bot)
data['voice_chat_ended'] = VideoChatEnded.de_json(data.get('voice_chat_ended'), bot)
data['voice_chat_participants_invited'] = VideoChatParticipantsInvited.de_json(
data.get('voice_chat_participants_invited'), bot
)
data['video_chat_scheduled'] = VideoChatScheduled.de_json(
data.get('video_chat_scheduled'), bot
)
data['video_chat_started'] = VideoChatStarted.de_json(data.get('video_chat_started'), bot)
data['video_chat_ended'] = VideoChatEnded.de_json(data.get('video_chat_ended'), bot)
data['video_chat_participants_invited'] = VideoChatParticipantsInvited.de_json(
data.get('video_chat_participants_invited'), bot
)
data['web_app_data'] = WebAppData.de_json(data.get('web_app_data'), bot)
return cls(bot=bot, **data)
@property
+52
View File
@@ -0,0 +1,52 @@
#!/usr/bin/env python
# pylint: disable=too-many-instance-attributes, too-many-arguments
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2022
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Telegram Sent Web App Message."""
from typing import Any
from telegram import TelegramObject
class SentWebAppMessage(TelegramObject):
"""Contains information about an inline message sent by a Web App on behalf of a user.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`inline_message_id` are equal.
.. versionadded:: 13.12
Args:
inline_message_id (:obj:`str`, optional): Identifier of the sent inline message. Available
only if there is an :attr:`inline keyboard <telegram.InlineKeyboardMarkup>` attached to
the message.
Attributes:
inline_message_id (:obj:`str`): Optional. Identifier of the sent inline message. Available
only if there is an :attr:`inline keyboard <telegram.InlineKeyboardMarkup>` attached to
the message.
"""
__slots__ = ('inline_message_id',)
def __init__(self, inline_message_id: str = None, **_kwargs: Any):
# Optionals
self.inline_message_id = inline_message_id
self._id_attrs = (self.inline_message_id,)
+74 -1
View File
@@ -21,7 +21,7 @@
from datetime import datetime
from typing import TYPE_CHECKING, Any, List, Optional, Union, Tuple
from telegram import TelegramObject, constants
from telegram import TelegramObject, constants, MenuButton
from telegram.inline.inlinekeyboardbutton import InlineKeyboardButton
from telegram.utils.helpers import (
mention_html as util_mention_html,
@@ -79,6 +79,13 @@ class User(TelegramObject):
supports_inline_queries (:obj:`str`, optional): :obj:`True`, if the bot supports inline
queries. Returned only in :attr:`telegram.Bot.get_me` requests.
bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods.
is_premium (:obj:`bool`, optional): :obj:`True`, if this user is a Telegram Premium user.
.. versionadded:: 13.13
added_to_attachment_menu (:obj:`bool`, optional): :obj:`True`, if this user added
the bot to the attachment menu.
.. versionadded:: 13.13
Attributes:
id (:obj:`int`): Unique identifier for this user or bot.
@@ -94,6 +101,14 @@ class User(TelegramObject):
supports_inline_queries (:obj:`str`): Optional. :obj:`True`, if the bot supports inline
queries. Returned only in :attr:`telegram.Bot.get_me` requests.
bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods.
is_premium (:obj:`bool`): Optional. :obj:`True`, if this user is a Telegram
Premium user.
.. versionadded:: 13.13
added_to_attachment_menu (:obj:`bool`): Optional. :obj:`True`, if this user added
the bot to the attachment menu.
.. versionadded:: 13.13
"""
@@ -108,6 +123,8 @@ class User(TelegramObject):
'id',
'bot',
'language_code',
'is_premium',
'added_to_attachment_menu',
'_id_attrs',
)
@@ -123,6 +140,8 @@ class User(TelegramObject):
can_read_all_group_messages: bool = None,
supports_inline_queries: bool = None,
bot: 'Bot' = None,
is_premium: bool = None,
added_to_attachment_menu: bool = None,
**_kwargs: Any,
):
# Required
@@ -136,6 +155,8 @@ class User(TelegramObject):
self.can_join_groups = can_join_groups
self.can_read_all_group_messages = can_read_all_group_messages
self.supports_inline_queries = supports_inline_queries
self.is_premium = is_premium
self.added_to_attachment_menu = added_to_attachment_menu
self.bot = bot
self._id_attrs = (self.id,)
@@ -1241,3 +1262,55 @@ class User(TelegramObject):
return self.bot.decline_chat_join_request(
user_id=self.id, chat_id=chat_id, timeout=timeout, api_kwargs=api_kwargs
)
def set_menu_button(
self,
menu_button: MenuButton = None,
timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
) -> bool:
"""Shortcut for::
bot.set_chat_menu_button(chat_id=update.effective_user.id, *args, **kwargs)
For the documentation of the arguments, please see
:meth:`telegram.Bot.set_chat_menu_button`.
..seealso:: :meth:`get_menu_button`
.. versionadded:: 13.12
Returns:
:obj:`bool`: On success, :obj:`True` is returned.
"""
return self.bot.set_chat_menu_button(
chat_id=self.id,
menu_button=menu_button,
timeout=timeout,
api_kwargs=api_kwargs,
)
def get_menu_button(
self,
timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
) -> MenuButton:
"""Shortcut for::
bot.get_chat_menu_button(chat_id=update.effective_user.id, *args, **kwargs)
For the documentation of the arguments, please see
:meth:`telegram.Bot.get_chat_menu_button`.
..seealso:: :meth:`set_menu_button`
.. versionadded:: 13.12
Returns:
:class:`telegram.MenuButton`: On success, the current menu button is returned.
"""
return self.bot.get_chat_menu_button(
chat_id=self.id,
timeout=timeout,
api_kwargs=api_kwargs,
)
+1 -1
View File
@@ -20,5 +20,5 @@
from telegram import constants
__version__ = '13.11'
__version__ = '13.13'
bot_api_version = constants.BOT_API_VERSION # pylint: disable=C0103
+167
View File
@@ -0,0 +1,167 @@
#!/usr/bin/env python
# pylint: disable=R0903
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2022
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains objects related to Telegram video chats."""
import datetime as dtm
from typing import TYPE_CHECKING, Any, Optional, List
from telegram import TelegramObject, User
from telegram.utils.helpers import from_timestamp, to_timestamp
from telegram.utils.types import JSONDict
if TYPE_CHECKING:
from telegram import Bot
class VideoChatStarted(TelegramObject):
"""
This object represents a service message about a video
chat started in the chat. Currently holds no information.
.. versionadded:: 13.12
"""
__slots__ = ()
def __init__(self, **_kwargs: Any): # skipcq: PTC-W0049
pass
class VideoChatEnded(TelegramObject):
"""
This object represents a service message about a
video chat ended in the chat.
Objects of this class are comparable in terms of equality.
Two objects of this class are considered equal, if their
:attr:`duration` are equal.
.. versionadded:: 13.12
Args:
duration (:obj:`int`): Voice chat duration in seconds.
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
Attributes:
duration (:obj:`int`): Voice chat duration in seconds.
"""
__slots__ = ('duration', '_id_attrs')
def __init__(self, duration: int, **_kwargs: Any) -> None:
self.duration = int(duration) if duration is not None else None
self._id_attrs = (self.duration,)
class VideoChatParticipantsInvited(TelegramObject):
"""
This object represents a service message about new members invited to a video chat.
Objects of this class are comparable in terms of equality.
Two objects of this class are considered equal, if their :attr:`users` are equal.
.. versionadded:: 13.12
Args:
users (List[:class:`telegram.User`]): New members that were invited to the video chat.
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
Attributes:
users (List[:class:`telegram.User`]): New members that were invited to the video chat.
"""
__slots__ = ('users', '_id_attrs')
def __init__(self, users: List[User], **_kwargs: Any) -> None:
self.users = users
self._id_attrs = (self.users,)
def __hash__(self) -> int:
return hash(tuple(self.users))
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: 'Bot'
) -> Optional['VideoChatParticipantsInvited']:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data['users'] = User.de_list(data.get('users', []), bot)
return cls(**data)
def to_dict(self) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict`."""
data = super().to_dict()
data["users"] = [u.to_dict() for u in self.users]
return data
class VideoChatScheduled(TelegramObject):
"""This object represents a service message about a video chat scheduled in the chat.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`start_date` are equal.
.. versionadded:: 13.12
Args:
start_date (:obj:`datetime.datetime`): Point in time (Unix timestamp) when the video
chat is supposed to be started by a chat administrator
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
Attributes:
start_date (:obj:`datetime.datetime`): Point in time (Unix timestamp) when the video
chat is supposed to be started by a chat administrator
"""
__slots__ = ('start_date', '_id_attrs')
def __init__(self, start_date: dtm.datetime, **_kwargs: Any) -> None:
self.start_date = start_date
self._id_attrs = (self.start_date,)
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['VideoChatScheduled']:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data['start_date'] = from_timestamp(data['start_date'])
return cls(**data, bot=bot)
def to_dict(self) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict`."""
data = super().to_dict()
# Required
data['start_date'] = to_timestamp(self.start_date)
return data
+10 -144
View File
@@ -19,151 +19,17 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains objects related to Telegram voice chats."""
import datetime as dtm
from typing import TYPE_CHECKING, Any, Optional, List
from telegram.videochat import (
VideoChatStarted,
VideoChatEnded,
VideoChatScheduled,
VideoChatParticipantsInvited,
)
from telegram import TelegramObject, User
from telegram.utils.helpers import from_timestamp, to_timestamp
from telegram.utils.types import JSONDict
VoiceChatStarted = VideoChatStarted
if TYPE_CHECKING:
from telegram import Bot
VoiceChatEnded = VideoChatEnded
VoiceChatParticipantsInvited = VideoChatParticipantsInvited
class VoiceChatStarted(TelegramObject):
"""
This object represents a service message about a voice
chat started in the chat. Currently holds no information.
.. versionadded:: 13.4
"""
__slots__ = ()
def __init__(self, **_kwargs: Any): # skipcq: PTC-W0049
pass
class VoiceChatEnded(TelegramObject):
"""
This object represents a service message about a
voice chat ended in the chat.
Objects of this class are comparable in terms of equality.
Two objects of this class are considered equal, if their
:attr:`duration` are equal.
.. versionadded:: 13.4
Args:
duration (:obj:`int`): Voice chat duration in seconds.
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
Attributes:
duration (:obj:`int`): Voice chat duration in seconds.
"""
__slots__ = ('duration', '_id_attrs')
def __init__(self, duration: int, **_kwargs: Any) -> None:
self.duration = int(duration) if duration is not None else None
self._id_attrs = (self.duration,)
class VoiceChatParticipantsInvited(TelegramObject):
"""
This object represents a service message about
new members invited to a voice chat.
Objects of this class are comparable in terms of equality.
Two objects of this class are considered equal, if their
:attr:`users` are equal.
.. versionadded:: 13.4
Args:
users (List[:class:`telegram.User`]): New members that
were invited to the voice chat.
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
Attributes:
users (List[:class:`telegram.User`]): New members that
were invited to the voice chat.
"""
__slots__ = ('users', '_id_attrs')
def __init__(self, users: List[User], **_kwargs: Any) -> None:
self.users = users
self._id_attrs = (self.users,)
def __hash__(self) -> int:
return hash(tuple(self.users))
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: 'Bot'
) -> Optional['VoiceChatParticipantsInvited']:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data['users'] = User.de_list(data.get('users', []), bot)
return cls(**data)
def to_dict(self) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict`."""
data = super().to_dict()
data["users"] = [u.to_dict() for u in self.users]
return data
class VoiceChatScheduled(TelegramObject):
"""This object represents a service message about a voice chat scheduled in the chat.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`start_date` are equal.
Args:
start_date (:obj:`datetime.datetime`): Point in time (Unix timestamp) when the voice
chat is supposed to be started by a chat administrator
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
Attributes:
start_date (:obj:`datetime.datetime`): Point in time (Unix timestamp) when the voice
chat is supposed to be started by a chat administrator
"""
__slots__ = ('start_date', '_id_attrs')
def __init__(self, start_date: dtm.datetime, **_kwargs: Any) -> None:
self.start_date = start_date
self._id_attrs = (self.start_date,)
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['VoiceChatScheduled']:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data['start_date'] = from_timestamp(data['start_date'])
return cls(**data, bot=bot)
def to_dict(self) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict`."""
data = super().to_dict()
# Required
data['start_date'] = to_timestamp(self.start_date)
return data
VoiceChatScheduled = VideoChatScheduled
+58
View File
@@ -0,0 +1,58 @@
#!/usr/bin/env python
# pylint: disable=too-many-instance-attributes, too-many-arguments
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2022
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Telegram WebAppData."""
from typing import Any
from telegram import TelegramObject
class WebAppData(TelegramObject):
"""Contains data sent from a `Web App <https://core.telegram.org/bots/webapps>`_ to the bot.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`data` and :attr:`button_text` are equal.
.. versionadded:: 13.12
Args:
data (:obj:`str`): The data. Be aware that a bad client can send arbitrary data in this
field.
button_text (:obj:`str`): Text of the :attr:`~telegram.KeyboardButton.web_app` keyboard
button, from which the Web App was opened.
Attributes:
data (:obj:`str`): The data. Be aware that a bad client can send arbitrary data in this
field.
button_text (:obj:`str`): Text of the :attr:`~telegram.KeyboardButton.web_app` keyboard
button, from which the Web App was opened.
Warning:
Be aware that a bad client can send arbitrary data in this field.
"""
__slots__ = ('data', 'button_text', '_id_attrs')
def __init__(self, data: str, button_text: str, **_kwargs: Any):
# Required
self.data = data
self.button_text = button_text
self._id_attrs = (self.data, self.button_text)
+52
View File
@@ -0,0 +1,52 @@
#!/usr/bin/env python
# pylint: disable=too-many-instance-attributes, too-many-arguments
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2022
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Telegram Web App Info."""
from typing import Any
from telegram import TelegramObject
class WebAppInfo(TelegramObject):
"""
This object contains information about a `Web App <https://core.telegram.org/bots/webapps>`_.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`url` are equal.
.. versionadded:: 13.12
Args:
url (:obj:`str`): An HTTPS URL of a Web App to be opened with additional data as specified
in `Initializing Web Apps \
<https://core.telegram.org/bots/webapps#initializing-web-apps>`_.
Attributes:
url (:obj:`str`): An HTTPS URL of a Web App to be opened with additional data as specified
in `Initializing Web Apps \
<https://core.telegram.org/bots/webapps#initializing-web-apps>`_.
"""
__slots__ = ('url', '_id_attrs')
def __init__(self, url: str, **_kwargs: Any):
# Required
self.url = url
self._id_attrs = (url,)
+32 -1
View File
@@ -18,9 +18,14 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Telegram WebhookInfo."""
from typing import Any, List
from typing import Any, List, Optional, TYPE_CHECKING
from telegram import TelegramObject
from telegram.utils.types import JSONDict
from telegram.utils.helpers import from_timestamp
if TYPE_CHECKING:
from telegram import Bot
class WebhookInfo(TelegramObject):
@@ -47,6 +52,10 @@ class WebhookInfo(TelegramObject):
connections to the webhook for update delivery.
allowed_updates (List[:obj:`str`], optional): A list of update types the bot is subscribed
to. Defaults to all update types, except :attr:`telegram.Update.chat_member`.
last_synchronization_error_date (:obj:`int`, optional): Unix time of the most recent error
that happened when trying to synchronize available updates with Telegram datacenters.
.. versionadded:: 13.12
Attributes:
url (:obj:`str`): Webhook URL.
@@ -59,6 +68,10 @@ class WebhookInfo(TelegramObject):
connections.
allowed_updates (List[:obj:`str`]): Optional. A list of update types the bot is subscribed
to. Defaults to all update types, except :attr:`telegram.Update.chat_member`.
last_synchronization_error_date (:obj:`int`): Optional. Unix time of the most recent error
that happened when trying to synchronize available updates with Telegram datacenters.
.. versionadded:: 13.12
"""
@@ -71,6 +84,7 @@ class WebhookInfo(TelegramObject):
'last_error_message',
'pending_update_count',
'has_custom_certificate',
'last_synchronization_error_date',
'_id_attrs',
)
@@ -84,6 +98,7 @@ class WebhookInfo(TelegramObject):
max_connections: int = None,
allowed_updates: List[str] = None,
ip_address: str = None,
last_synchronization_error_date: int = None,
**_kwargs: Any,
):
# Required
@@ -97,6 +112,7 @@ class WebhookInfo(TelegramObject):
self.last_error_message = last_error_message
self.max_connections = max_connections
self.allowed_updates = allowed_updates
self.last_synchronization_error_date = last_synchronization_error_date
self._id_attrs = (
self.url,
@@ -108,3 +124,18 @@ class WebhookInfo(TelegramObject):
self.max_connections,
self.allowed_updates,
)
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['WebhookInfo']:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data['last_error_date'] = from_timestamp(data.get('last_error_date'))
data['last_synchronization_error_date'] = from_timestamp(
data.get('last_synchronization_error_date')
)
return cls(bot=bot, **data)
+166 -2
View File
@@ -27,6 +27,7 @@ import pytest
import pytz
from flaky import flaky
from telegram import constants
from telegram import (
Bot,
Update,
@@ -52,6 +53,13 @@ from telegram import (
InlineQueryResultVoice,
PollOption,
BotCommandScopeChat,
SentWebAppMessage,
ChatAdministratorRights,
MenuButton,
MenuButtonWebApp,
WebAppInfo,
MenuButtonCommands,
MenuButtonDefault,
)
from telegram.constants import MAX_INLINE_QUERY_RESULTS
from telegram.ext import ExtBot, Defaults
@@ -752,6 +760,27 @@ class TestBot:
with pytest.raises(BadRequest, match='Wrong parameter action'):
bot.send_chat_action(chat_id, 'unknown action')
def test_answer_web_app_query(self, bot, monkeypatch):
params = False
# For now just test that our internals pass the correct data
def make_assertion(url, data, *args, **kwargs):
nonlocal params
params = data == {
'web_app_query_id': '12345',
'result': result,
}
web_app_msg = SentWebAppMessage('321').to_dict()
return web_app_msg
monkeypatch.setattr(bot.request, 'post', make_assertion)
result = InlineQueryResultArticle('1', 'title', InputTextMessageContent('text'))
web_app_msg = bot.answer_web_app_query('12345', result)
assert params, "something went wrong with passing arguments to the request"
assert isinstance(web_app_msg, SentWebAppMessage)
assert web_app_msg.inline_message_id == '321'
# TODO: Needs improvement. We need incoming inline query to test answer.
def test_answer_inline_query(self, monkeypatch, bot):
# For now just test that our internals pass the correct data
@@ -1381,6 +1410,32 @@ class TestBot:
assert bot.set_webhook(drop_pending_updates=drop_pending_updates)
assert bot.delete_webhook(drop_pending_updates=drop_pending_updates)
def test_set_webhook_params(self, bot, monkeypatch):
# actually making calls to TG is done in
# test_set_webhook_get_webhook_info_and_delete_webhook. Sadly secret_token can't be tested
# there so we have this function \o/
def make_assertion(*args, **_):
kwargs = args[1]
return (
kwargs["url"] == "example.com"
and kwargs["max_connections"] == 7
and kwargs["allowed_updates"] == ["messages"]
and kwargs["ip_address"] == "127.0.0.1"
and kwargs["drop_pending_updates"]
and kwargs["secret_token"] == "SoSecretToken"
)
monkeypatch.setattr(bot, "_post", make_assertion)
assert bot.set_webhook(
"example.com",
max_connections=7,
allowed_updates=["messages"],
ip_address="127.0.0.1",
drop_pending_updates=True,
secret_token="SoSecretToken",
)
@flaky(3, 1)
def test_leave_chat(self, bot):
with pytest.raises(BadRequest, match='Chat not found'):
@@ -1701,6 +1756,28 @@ class TestBot:
can_manage_voice_chats=True,
)
with pytest.raises(
ValueError,
match='Only supply one of `can_manage_video_chats`/`'
'can_manage_voice_chats`, not both.',
):
assert bot.promote_chat_member(
channel_id,
95205500,
is_anonymous=True,
can_change_info=True,
can_post_messages=True,
can_edit_messages=True,
can_delete_messages=True,
can_invite_users=True,
can_restrict_members=True,
can_pin_messages=True,
can_promote_members=True,
can_manage_chat=True,
can_manage_voice_chats=True,
can_manage_video_chats=True,
)
# Test that we pass the correct params to TG
def make_assertion(*args, **_):
data = args[1]
@@ -1717,7 +1794,7 @@ class TestBot:
and data.get('can_pin_messages') == 8
and data.get('can_promote_members') == 9
and data.get('can_manage_chat') == 10
and data.get('can_manage_voice_chats') == 11
and data.get('can_manage_video_chats') == 11
)
monkeypatch.setattr(bot, '_post', make_assertion)
@@ -1737,6 +1814,42 @@ class TestBot:
can_manage_voice_chats=11,
)
# Test that video_chats also works
def make_assertion(*args, **_):
data = args[1]
return (
data.get('chat_id') == channel_id
and data.get('user_id') == 95205500
and data.get('is_anonymous') == 1
and data.get('can_change_info') == 2
and data.get('can_post_messages') == 3
and data.get('can_edit_messages') == 4
and data.get('can_delete_messages') == 5
and data.get('can_invite_users') == 6
and data.get('can_restrict_members') == 7
and data.get('can_pin_messages') == 8
and data.get('can_promote_members') == 9
and data.get('can_manage_chat') == 10
and data.get('can_manage_video_chats') == 11
)
monkeypatch.setattr(bot, '_post', make_assertion)
assert bot.promote_chat_member(
channel_id,
95205500,
is_anonymous=1,
can_change_info=2,
can_post_messages=3,
can_edit_messages=4,
can_delete_messages=5,
can_invite_users=6,
can_restrict_members=7,
can_pin_messages=8,
can_promote_members=9,
can_manage_chat=10,
can_manage_video_chats=11,
)
@flaky(3, 1)
def test_export_chat_invite_link(self, bot, channel_id):
# Each link is unique apparently
@@ -1886,7 +1999,10 @@ class TestBot:
# TODO: Need incoming join request to properly test
# Since we can't create join requests on the fly, we just tests the call to TG
# by checking that it complains about declining a user who is already in the chat
with pytest.raises(BadRequest, match='User_already_participant'):
#
# The error message Hide_requester_missing started showing up instead of
# User_already_participant. Don't know why …
with pytest.raises(BadRequest, match="User_already_participant|Hide_requester_missing"):
bot.decline_chat_join_request(chat_id=channel_id, user_id=chat_id)
@flaky(3, 1)
@@ -2068,6 +2184,54 @@ class TestBot:
chat_id, 'test', reply_to_message_id=reply_to_message.message_id
)
def test_get_set_my_default_administrator_rights(self, bot):
# Test that my default administrator rights for group are as all False
bot.set_my_default_administrator_rights()
my_admin_rights_grp = bot.get_my_default_administrator_rights()
assert isinstance(my_admin_rights_grp, ChatAdministratorRights)
for attribute in my_admin_rights_grp.__slots__:
if attribute == "_id_attrs":
continue
assert not getattr(my_admin_rights_grp, attribute)
# Test setting my default admin rights for channel
my_rights = ChatAdministratorRights.all_rights()
bot.set_my_default_administrator_rights(my_rights, for_channels=True)
my_admin_rights_ch = bot.get_my_default_administrator_rights(for_channels=True)
# tg bug? is_anonymous is False despite setting it True for channels:
assert my_admin_rights_ch.is_anonymous is not my_rights.is_anonymous
assert my_admin_rights_ch.can_invite_users is my_rights.can_invite_users
assert my_admin_rights_ch.can_manage_chat is my_rights.can_manage_chat
assert my_admin_rights_ch.can_delete_messages is my_rights.can_delete_messages
assert my_admin_rights_ch.can_edit_messages is my_rights.can_edit_messages
assert my_admin_rights_ch.can_post_messages is my_rights.can_post_messages
assert my_admin_rights_ch.can_change_info is my_rights.can_change_info
assert my_admin_rights_ch.can_promote_members is my_rights.can_promote_members
assert my_admin_rights_ch.can_restrict_members is my_rights.can_restrict_members
assert my_admin_rights_ch.can_pin_messages is None # Not returned for channels
def test_get_set_chat_menu_button(self, bot, chat_id):
# Test our chat menu button is commands-
menu_button = bot.get_chat_menu_button()
assert isinstance(menu_button, MenuButton)
assert isinstance(menu_button, MenuButtonCommands)
assert menu_button.type == constants.MENU_BUTTON_COMMANDS
# Test setting our chat menu button to Webapp.
my_menu = MenuButtonWebApp('click me!', WebAppInfo('https://telegram.org/'))
bot.set_chat_menu_button(chat_id, my_menu)
menu_button = bot.get_chat_menu_button(chat_id)
assert isinstance(menu_button, MenuButtonWebApp)
assert menu_button.type == constants.MENU_BUTTON_WEB_APP
assert menu_button.text == my_menu.text
assert menu_button.web_app.url == my_menu.web_app.url
bot.set_chat_menu_button(chat_id=chat_id, menu_button=MenuButtonDefault())
menu_button = bot.get_chat_menu_button(chat_id=chat_id)
assert isinstance(menu_button, MenuButtonDefault)
@flaky(3, 1)
def test_set_and_get_my_commands(self, bot):
commands = [
+46
View File
@@ -43,6 +43,8 @@ def chat(bot):
location=TestChat.location,
has_private_forwards=True,
has_protected_content=True,
join_to_send_messages=True,
join_by_request=True,
)
@@ -66,6 +68,8 @@ class TestChat:
location = ChatLocation(Location(123, 456), 'Barbie World')
has_protected_content = True
has_private_forwards = True
join_to_send_messages = True
join_by_request = True
def test_slot_behaviour(self, chat, recwarn, mro_slots):
for attr in chat.__slots__:
@@ -92,6 +96,8 @@ class TestChat:
'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,
}
chat = Chat.de_json(json_dict, bot)
@@ -111,6 +117,8 @@ class TestChat:
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
def test_to_dict(self, chat):
chat_dict = chat.to_dict()
@@ -129,6 +137,8 @@ class TestChat:
assert chat_dict['has_protected_content'] == chat.has_protected_content
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
def test_link(self, chat):
assert chat.link == f'https://t.me/{chat.username}'
@@ -706,6 +716,42 @@ class TestChat:
monkeypatch.setattr(chat.bot, 'revoke_chat_invite_link', make_assertion)
assert chat.revoke_invite_link(invite_link=link)
def test_instance_method_get_menu_button(self, monkeypatch, chat):
def make_assertion(*_, **kwargs):
return kwargs['chat_id'] == chat.id
assert check_shortcut_signature(
Chat.get_menu_button, Bot.get_chat_menu_button, ['chat_id'], []
)
assert check_shortcut_call(
chat.get_menu_button,
chat.bot,
'get_chat_menu_button',
shortcut_kwargs=['chat_id'],
)
assert check_defaults_handling(chat.get_menu_button, chat.bot)
monkeypatch.setattr(chat.bot, 'get_chat_menu_button', make_assertion)
assert chat.get_menu_button()
def test_instance_method_set_menu_button(self, monkeypatch, chat):
def make_assertion(*_, **kwargs):
return kwargs['chat_id'] == chat.id and kwargs['menu_button'] == 'menu_button'
assert check_shortcut_signature(
Chat.set_menu_button, Bot.set_chat_menu_button, ['chat_id'], []
)
assert check_shortcut_call(
chat.set_menu_button,
chat.bot,
'set_chat_menu_button',
shortcut_kwargs=['chat_id'],
)
assert check_defaults_handling(chat.set_menu_button, chat.bot)
monkeypatch.setattr(chat.bot, 'set_chat_menu_button', make_assertion)
assert chat.set_menu_button(menu_button='menu_button')
def test_approve_join_request(self, monkeypatch, chat):
def make_assertion(*_, **kwargs):
return kwargs['chat_id'] == chat.id and kwargs['user_id'] == 42
+130
View File
@@ -0,0 +1,130 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2022
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
from telegram import ChatAdministratorRights
import pytest
@pytest.fixture(scope='class')
def chat_admin_rights():
return ChatAdministratorRights(
can_change_info=True,
can_delete_messages=True,
can_invite_users=True,
can_pin_messages=True,
can_promote_members=True,
can_restrict_members=True,
can_post_messages=True,
can_edit_messages=True,
can_manage_chat=True,
can_manage_video_chats=True,
is_anonymous=True,
)
class TestChatAdministratorRights:
def test_slot_behaviour(self, chat_admin_rights, mro_slots):
inst = chat_admin_rights
for attr in inst.__slots__:
assert getattr(inst, attr, 'err') != 'err', f"got extra slot '{attr}'"
assert len(mro_slots(inst)) == len(set(mro_slots(inst))), "duplicate slot"
def test_de_json(self, bot, chat_admin_rights):
json_dict = {
'can_change_info': True,
'can_delete_messages': True,
'can_invite_users': True,
'can_pin_messages': True,
'can_promote_members': True,
'can_restrict_members': True,
'can_post_messages': True,
'can_edit_messages': True,
'can_manage_chat': True,
'can_manage_video_chats': True,
'is_anonymous': True,
}
chat_administrator_rights_de = ChatAdministratorRights.de_json(json_dict, bot)
assert chat_admin_rights == chat_administrator_rights_de
def test_to_dict(self, chat_admin_rights):
car = chat_admin_rights
admin_rights_dict = car.to_dict()
assert isinstance(admin_rights_dict, dict)
assert admin_rights_dict['can_change_info'] == car.can_change_info
assert admin_rights_dict['can_delete_messages'] == car.can_delete_messages
assert admin_rights_dict['can_invite_users'] == car.can_invite_users
assert admin_rights_dict['can_pin_messages'] == car.can_pin_messages
assert admin_rights_dict['can_promote_members'] == car.can_promote_members
assert admin_rights_dict['can_restrict_members'] == car.can_restrict_members
assert admin_rights_dict['can_post_messages'] == car.can_post_messages
assert admin_rights_dict['can_edit_messages'] == car.can_edit_messages
assert admin_rights_dict['can_manage_chat'] == car.can_manage_chat
assert admin_rights_dict['is_anonymous'] == car.is_anonymous
assert admin_rights_dict['can_manage_video_chats'] == car.can_manage_video_chats
def test_equality(self):
a = ChatAdministratorRights(True, False, False, False, False, False, False, False)
b = ChatAdministratorRights(True, False, False, False, False, False, False, False)
c = ChatAdministratorRights(False, False, False, False, False, False, False, False)
d = ChatAdministratorRights(True, True, False, False, False, False, False, False)
e = ChatAdministratorRights(True, True, False, False, False, False, False, False)
assert a == b
assert hash(a) == hash(b)
assert a is not b
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)
assert d == e
assert hash(d) == hash(e)
def test_all_rights(self):
f = ChatAdministratorRights(True, True, True, True, True, True, True, True)
t = ChatAdministratorRights.all_rights()
# if the dirs are the same, the attributes will all be there
assert dir(f) == dir(t)
# now we just need to check that all attributes are True. __slots__ returns all values,
# if a new one is added without defaulting to True, this will fail. And we can skip
# _id_attrs, doesn't have any information.
for key in t.__slots__:
if key == "_id_attrs":
continue
assert t[key] is True
# and as a finisher, make sure the default is different.
assert f != t
def test_no_rights(self):
f = ChatAdministratorRights(False, False, False, False, False, False, False, False)
t = ChatAdministratorRights.no_rights()
# if the dirs are the same, the attributes will all be there
assert dir(f) == dir(t)
# now we just need to check that all attributes are True. __slots__ returns all values,
# if a new one is added without defaulting to True, this will fail
for key in t.__slots__:
if key == "_id_attrs":
continue
assert t[key] is False
# and as a finisher, make sure the default is different.
assert f != t
+13 -1
View File
@@ -183,6 +183,9 @@ class TestChatMember:
if chat_member_type.can_manage_voice_chats is not None:
assert chat_member_type.can_manage_voice_chats is True
assert type(chat_member_type) == ChatMemberAdministrator
if chat_member_type.can_manage_video_chats is not None:
assert chat_member_type.can_manage_video_chats is True
assert type(chat_member_type) == ChatMemberAdministrator
def test_de_json_invalid_status(self, bot, user):
json_dict = {'status': 'invalid', 'user': user.to_dict()}
@@ -217,7 +220,7 @@ class TestChatMember:
'can_send_other_messages': True,
'can_add_web_page_previews': False,
'can_manage_chat': True,
'can_manage_voice_chats': True,
'can_manage_video_chats': True,
}
assert type(cls.de_json(json_dict, bot)) is cls
@@ -252,3 +255,12 @@ class TestChatMember:
assert c != e
assert hash(c) != hash(e)
def test_invalid_input(self, user):
with pytest.raises(ValueError):
ChatMember(
user=user,
status="status",
can_manage_video_chats=True,
can_manage_voice_chats=False,
)
+8 -1
View File
@@ -222,4 +222,11 @@ class TestChatMemberUpdated:
chat_member_updated = ChatMemberUpdated(
chat, user, datetime.datetime.utcnow(), old_chat_member, new_chat_member
)
assert chat_member_updated.difference() == {optional_attribute: (old_value, new_value)}
# for backwards compatibility, supplying video/voice chats right also leads
# to the other name being generated. So we need to add them to the compare to dict
compare_to_dict = {optional_attribute: (old_value, new_value)}
if optional_attribute == "can_manage_video_chats":
compare_to_dict["can_manage_voice_chats"] = (old_value, new_value)
elif optional_attribute == "can_manage_voice_chats":
compare_to_dict["can_manage_video_chats"] = (old_value, new_value)
assert chat_member_updated.difference() == compare_to_dict
+2 -2
View File
@@ -70,7 +70,7 @@ class TestChatPhoto:
def test_get_and_download(self, bot, chat_photo):
new_file = bot.get_file(chat_photo.small_file_id)
assert new_file.file_id == chat_photo.small_file_id
assert new_file.file_unique_id == chat_photo.small_file_unique_id
assert new_file.file_path.startswith('https://')
new_file.download('telegram.jpg')
@@ -79,7 +79,7 @@ class TestChatPhoto:
new_file = bot.get_file(chat_photo.big_file_id)
assert new_file.file_id == chat_photo.big_file_id
assert new_file.file_unique_id == chat_photo.big_file_unique_id
assert new_file.file_path.startswith('https://')
new_file.download('telegram.jpg')
+5 -1
View File
@@ -204,7 +204,11 @@ class TestCommandHandler(BaseTest):
def test_deprecation_warning(self):
"""``allow_edited`` deprecated in favor of filters"""
with pytest.warns(TelegramDeprecationWarning, match='See https://git.io/fxJuV'):
with pytest.warns(
TelegramDeprecationWarning,
match='See https://github.com/python-telegram-bot/python-telegram-bot/wiki'
'/Transition-guide-to-Version-12.0',
):
self.make_default_handler(allow_edited=True)
def test_edited(self, command_message):
+39
View File
@@ -943,6 +943,32 @@ class TestFilters:
assert Filters.status_update.voice_chat_participants_invited(update)
update.message.voice_chat_participants_invited = None
# same as above, just with video instead of voice.
update.message.video_chat_scheduled = 'scheduled'
assert Filters.status_update(update)
assert Filters.status_update.video_chat_scheduled(update)
update.message.video_chat_scheduled = None
update.message.video_chat_started = 'hello'
assert Filters.status_update(update)
assert Filters.status_update.video_chat_started(update)
update.message.video_chat_started = None
update.message.video_chat_ended = 'bye'
assert Filters.status_update(update)
assert Filters.status_update.video_chat_ended(update)
update.message.video_chat_ended = None
update.message.video_chat_participants_invited = 'invited'
assert Filters.status_update(update)
assert Filters.status_update.video_chat_participants_invited(update)
update.message.video_chat_participants_invited = None
update.message.web_app_data = 'data'
assert Filters.status_update(update)
assert Filters.status_update.web_app_data(update)
update.message.web_app_data = None
def test_filters_forwarded(self, update):
assert not Filters.forwarded(update)
update.message.forward_date = datetime.datetime.utcnow()
@@ -1163,6 +1189,19 @@ class TestFilters:
with pytest.raises(RuntimeError, match='Cannot set name'):
f.name = 'foo'
def test_filters_user_attributes(self, update):
assert not Filters.user_attachment(update)
assert not Filters.premium_user(update)
update.message.from_user.added_to_attachment_menu = True
assert Filters.user_attachment(update)
assert not Filters.premium_user(update)
update.message.from_user.is_premium = True
assert Filters.user_attachment(update)
assert Filters.premium_user(update)
update.message.from_user.added_to_attachment_menu = False
assert not Filters.user_attachment(update)
assert Filters.premium_user(update)
def test_filters_chat_init(self):
with pytest.raises(RuntimeError, match='in conjunction with'):
Filters.chat(chat_id=1, username='chat')
+4 -4
View File
@@ -126,7 +126,7 @@ class TestHelpers:
"""Conversion from timezone-naive datetime to timestamp.
Naive datetimes should be assumed to be in UTC.
"""
datetime = dtm.datetime(2019, 11, 11, 0, 26, 16, 10 ** 5)
datetime = dtm.datetime(2019, 11, 11, 0, 26, 16, 10**5)
assert helpers.to_float_timestamp(datetime) == 1573431976.1
def test_to_float_timestamp_absolute_naive_no_pytz(self, monkeypatch):
@@ -134,14 +134,14 @@ class TestHelpers:
Naive datetimes should be assumed to be in UTC.
"""
monkeypatch.setattr(helpers, 'UTC', helpers.DTM_UTC)
datetime = dtm.datetime(2019, 11, 11, 0, 26, 16, 10 ** 5)
datetime = dtm.datetime(2019, 11, 11, 0, 26, 16, 10**5)
assert helpers.to_float_timestamp(datetime) == 1573431976.1
def test_to_float_timestamp_absolute_aware(self, timezone):
"""Conversion from timezone-aware datetime to timestamp"""
# we're parametrizing this with two different UTC offsets to exclude the possibility
# of an xpass when the test is run in a timezone with the same UTC offset
test_datetime = dtm.datetime(2019, 11, 11, 0, 26, 16, 10 ** 5)
test_datetime = dtm.datetime(2019, 11, 11, 0, 26, 16, 10**5)
datetime = timezone.localize(test_datetime)
assert (
helpers.to_float_timestamp(datetime)
@@ -217,7 +217,7 @@ class TestHelpers:
def test_from_timestamp_aware(self, timezone):
# we're parametrizing this with two different UTC offsets to exclude the possibility
# of an xpass when the test is run in a timezone with the same UTC offset
test_datetime = dtm.datetime(2019, 11, 11, 0, 26, 16, 10 ** 5)
test_datetime = dtm.datetime(2019, 11, 11, 0, 26, 16, 10**5)
datetime = timezone.localize(test_datetime)
assert (
helpers.from_timestamp(
+22 -7
View File
@@ -19,7 +19,7 @@
import pytest
from telegram import InlineKeyboardButton, LoginUrl
from telegram import InlineKeyboardButton, LoginUrl, WebAppInfo, CallbackGame
@pytest.fixture(scope='class')
@@ -33,6 +33,7 @@ def inline_keyboard_button():
callback_game=TestInlineKeyboardButton.callback_game,
pay=TestInlineKeyboardButton.pay,
login_url=TestInlineKeyboardButton.login_url,
web_app=TestInlineKeyboardButton.web_app,
)
@@ -42,9 +43,10 @@ class TestInlineKeyboardButton:
callback_data = 'callback data'
switch_inline_query = 'switch_inline_query'
switch_inline_query_current_chat = 'switch_inline_query_current_chat'
callback_game = 'callback_game'
pay = 'pay'
callback_game = CallbackGame()
pay = True
login_url = LoginUrl("http://google.com")
web_app = WebAppInfo(url="https://example.com")
def test_slot_behaviour(self, inline_keyboard_button, recwarn, mro_slots):
inst = inline_keyboard_button
@@ -64,9 +66,10 @@ class TestInlineKeyboardButton:
inline_keyboard_button.switch_inline_query_current_chat
== self.switch_inline_query_current_chat
)
assert inline_keyboard_button.callback_game == self.callback_game
assert isinstance(inline_keyboard_button.callback_game, CallbackGame)
assert inline_keyboard_button.pay == self.pay
assert inline_keyboard_button.login_url == self.login_url
assert inline_keyboard_button.web_app == self.web_app
def test_to_dict(self, inline_keyboard_button):
inline_keyboard_button_dict = inline_keyboard_button.to_dict()
@@ -83,11 +86,15 @@ class TestInlineKeyboardButton:
inline_keyboard_button_dict['switch_inline_query_current_chat']
== inline_keyboard_button.switch_inline_query_current_chat
)
assert inline_keyboard_button_dict['callback_game'] == inline_keyboard_button.callback_game
assert (
inline_keyboard_button_dict['callback_game']
== inline_keyboard_button.callback_game.to_dict()
)
assert inline_keyboard_button_dict['pay'] == inline_keyboard_button.pay
assert (
inline_keyboard_button_dict['login_url'] == inline_keyboard_button.login_url.to_dict()
) # NOQA: E127
assert inline_keyboard_button_dict['web_app'] == inline_keyboard_button.web_app.to_dict()
def test_de_json(self, bot):
json_dict = {
@@ -96,7 +103,9 @@ class TestInlineKeyboardButton:
'callback_data': self.callback_data,
'switch_inline_query': self.switch_inline_query,
'switch_inline_query_current_chat': self.switch_inline_query_current_chat,
'callback_game': self.callback_game,
'callback_game': self.callback_game.to_dict(),
'web_app': self.web_app.to_dict(),
'login_url': self.login_url.to_dict(),
'pay': self.pay,
}
@@ -109,8 +118,14 @@ class TestInlineKeyboardButton:
inline_keyboard_button.switch_inline_query_current_chat
== self.switch_inline_query_current_chat
)
assert inline_keyboard_button.callback_game == self.callback_game
# CallbackGame has empty _id_attrs, so just test if the class is created.
assert isinstance(inline_keyboard_button.callback_game, CallbackGame)
assert inline_keyboard_button.pay == self.pay
assert inline_keyboard_button.login_url == self.login_url
assert inline_keyboard_button.web_app == self.web_app
none = InlineKeyboardButton.de_json({}, bot)
assert none is None
def test_equality(self):
a = InlineKeyboardButton('text', callback_data='data')
+62 -1
View File
@@ -100,8 +100,19 @@ class TestInvoice:
assert message.invoice.title == self.title
assert message.invoice.total_amount == self.total_amount
link = bot.create_invoice_link(
title=self.title,
description=self.description,
payload=self.payload,
provider_token=provider_token,
currency=self.currency,
prices=self.prices,
)
assert isinstance(link, str)
assert link != ""
@flaky(3, 1)
def test_send_all_args(self, bot, chat_id, provider_token, monkeypatch):
def test_send_all_args_send_invoice(self, bot, chat_id, provider_token, monkeypatch):
message = bot.send_invoice(
chat_id,
self.title,
@@ -195,6 +206,56 @@ class TestInvoice:
protect_content=True,
)
def test_send_all_args_create_invoice_link(self, bot, chat_id, provider_token, monkeypatch):
def make_assertion(*args, **_):
kwargs = args[1]
return (
kwargs["title"] == "title"
and kwargs["description"] == "description"
and kwargs["payload"] == "payload"
and kwargs["provider_token"] == "provider_token"
and kwargs["currency"] == "currency"
and kwargs["prices"] == [p.to_dict() for p in self.prices]
and kwargs["max_tip_amount"] == "max_tip_amount"
and kwargs["suggested_tip_amounts"] == "suggested_tip_amounts"
and kwargs["provider_data"] == "provider_data"
and kwargs["photo_url"] == "photo_url"
and kwargs["photo_size"] == "photo_size"
and kwargs["photo_width"] == "photo_width"
and kwargs["photo_height"] == "photo_height"
and kwargs["need_name"] == "need_name"
and kwargs["need_phone_number"] == "need_phone_number"
and kwargs["need_email"] == "need_email"
and kwargs["need_shipping_address"] == "need_shipping_address"
and kwargs["send_phone_number_to_provider"] == "send_phone_number_to_provider"
and kwargs["send_email_to_provider"] == "send_email_to_provider"
and kwargs["is_flexible"] == "is_flexible"
)
monkeypatch.setattr(bot, "_post", make_assertion)
assert bot.create_invoice_link(
title="title",
description="description",
payload="payload",
provider_token="provider_token",
currency="currency",
prices=self.prices,
max_tip_amount="max_tip_amount",
suggested_tip_amounts="suggested_tip_amounts",
provider_data="provider_data",
photo_url="photo_url",
photo_size="photo_size",
photo_width="photo_width",
photo_height="photo_height",
need_name="need_name",
need_phone_number="need_phone_number",
need_email="need_email",
need_shipping_address="need_shipping_address",
send_phone_number_to_provider="send_phone_number_to_provider",
send_email_to_provider="send_email_to_provider",
is_flexible="is_flexible",
)
def test_send_object_as_provider_data(self, monkeypatch, bot, chat_id, provider_token):
def test(url, data, **kwargs):
# depends on whether we're using ujson
+24 -1
View File
@@ -18,7 +18,7 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
import pytest
from telegram import KeyboardButton, InlineKeyboardButton
from telegram import KeyboardButton, InlineKeyboardButton, WebAppInfo
from telegram.keyboardbuttonpolltype import KeyboardButtonPollType
@@ -29,6 +29,7 @@ def keyboard_button():
request_location=TestKeyboardButton.request_location,
request_contact=TestKeyboardButton.request_contact,
request_poll=TestKeyboardButton.request_poll,
web_app=TestKeyboardButton.web_app,
)
@@ -37,6 +38,7 @@ class TestKeyboardButton:
request_location = True
request_contact = True
request_poll = KeyboardButtonPollType("quiz")
web_app = WebAppInfo(url="https://example.com")
def test_slot_behaviour(self, keyboard_button, recwarn, mro_slots):
inst = keyboard_button
@@ -52,6 +54,7 @@ class TestKeyboardButton:
assert keyboard_button.request_location == self.request_location
assert keyboard_button.request_contact == self.request_contact
assert keyboard_button.request_poll == self.request_poll
assert keyboard_button.web_app == self.web_app
def test_to_dict(self, keyboard_button):
keyboard_button_dict = keyboard_button.to_dict()
@@ -61,6 +64,26 @@ class TestKeyboardButton:
assert keyboard_button_dict['request_location'] == keyboard_button.request_location
assert keyboard_button_dict['request_contact'] == keyboard_button.request_contact
assert keyboard_button_dict['request_poll'] == keyboard_button.request_poll.to_dict()
assert keyboard_button_dict['web_app'] == keyboard_button.web_app.to_dict()
def test_de_json(self, bot):
json_dict = {
'text': self.text,
'request_location': self.request_location,
'request_contact': self.request_contact,
'request_poll': self.request_poll.to_dict(),
'web_app': self.web_app.to_dict(),
}
inline_keyboard_button = KeyboardButton.de_json(json_dict, None)
assert inline_keyboard_button.text == self.text
assert inline_keyboard_button.request_location == self.request_location
assert inline_keyboard_button.request_contact == self.request_contact
assert inline_keyboard_button.request_poll == self.request_poll
assert inline_keyboard_button.web_app == self.web_app
none = KeyboardButton.de_json({}, None)
assert none is None
def test_equality(self):
a = KeyboardButton('test', request_contact=True)
+176
View File
@@ -0,0 +1,176 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2022
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
from copy import deepcopy
import pytest
from telegram import (
Dice,
MenuButton,
MenuButtonDefault,
MenuButtonCommands,
MenuButtonWebApp,
WebAppInfo,
)
@pytest.fixture(
scope="class",
params=[
MenuButton.DEFAULT,
MenuButton.WEB_APP,
MenuButton.COMMANDS,
],
)
def scope_type(request):
return request.param
@pytest.fixture(
scope="class",
params=[
MenuButtonDefault,
MenuButtonCommands,
MenuButtonWebApp,
],
ids=[
MenuButton.DEFAULT,
MenuButton.COMMANDS,
MenuButton.WEB_APP,
],
)
def scope_class(request):
return request.param
@pytest.fixture(
scope="class",
params=[
(MenuButtonDefault, MenuButton.DEFAULT),
(MenuButtonCommands, MenuButton.COMMANDS),
(MenuButtonWebApp, MenuButton.WEB_APP),
],
ids=[
MenuButton.DEFAULT,
MenuButton.COMMANDS,
MenuButton.WEB_APP,
],
)
def scope_class_and_type(request):
return request.param
@pytest.fixture(scope='class')
def menu_button(scope_class_and_type):
return scope_class_and_type[0](
type=scope_class_and_type[1], text=TestMenuButton.text, web_app=TestMenuButton.web_app
)
# All the scope types are very similar, so we test everything via parametrization
class TestMenuButton:
text = 'button_text'
web_app = WebAppInfo(url='https://python-telegram-bot.org/web_app')
def test_slot_behaviour(self, menu_button, mro_slots):
for attr in menu_button.__slots__:
assert getattr(menu_button, attr, 'err') != 'err', f"got extra slot '{attr}'"
assert len(mro_slots(menu_button)) == len(set(mro_slots(menu_button))), "duplicate slot"
def test_de_json(self, bot, scope_class_and_type):
cls = scope_class_and_type[0]
type_ = scope_class_and_type[1]
assert cls.de_json({}, bot) is None
assert cls.de_json(None, bot) is None
json_dict = {'type': type_, 'text': self.text, 'web_app': self.web_app.to_dict()}
menu_button = MenuButton.de_json(json_dict, bot)
assert isinstance(menu_button, MenuButton)
assert type(menu_button) is cls
assert menu_button.type == type_
if 'web_app' in cls.__slots__:
assert menu_button.web_app == self.web_app
if 'text' in cls.__slots__:
assert menu_button.text == self.text
def test_de_json_invalid_type(self, bot):
json_dict = {'type': 'invalid', 'text': self.text, 'web_app': self.web_app.to_dict()}
menu_button = MenuButton.de_json(json_dict, bot)
assert type(menu_button) is MenuButton
assert menu_button.type == 'invalid'
def test_de_json_subclass(self, scope_class, bot):
"""This makes sure that e.g. MenuButtonDefault(data) never returns a
MenuButtonChat instance."""
json_dict = {'type': 'invalid', 'text': self.text, 'web_app': self.web_app.to_dict()}
assert type(scope_class.de_json(json_dict, bot)) is scope_class
def test_to_dict(self, menu_button):
menu_button_dict = menu_button.to_dict()
assert isinstance(menu_button_dict, dict)
assert menu_button_dict['type'] == menu_button.type
if hasattr(menu_button, 'web_app'):
assert menu_button_dict['web_app'] == menu_button.web_app.to_dict()
if hasattr(menu_button, 'text'):
assert menu_button_dict['text'] == menu_button.text
def test_equality(self, menu_button, bot):
a = MenuButton('base_type')
b = MenuButton('base_type')
c = menu_button
d = deepcopy(menu_button)
e = Dice(4, 'emoji')
assert a == b
assert hash(a) == hash(b)
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)
assert a != e
assert hash(a) != hash(e)
assert c == d
assert hash(c) == hash(d)
assert c != e
assert hash(c) != hash(e)
if hasattr(c, 'web_app'):
json_dict = c.to_dict()
json_dict['web_app'] = WebAppInfo('https://foo.bar/web_app').to_dict()
f = c.__class__.de_json(json_dict, bot)
assert c != f
assert hash(c) != hash(f)
if hasattr(c, 'text'):
json_dict = c.to_dict()
json_dict['text'] = 'other text'
g = c.__class__.de_json(json_dict, bot)
assert c != g
assert hash(c) != hash(g)
+53 -1
View File
@@ -48,11 +48,16 @@ from telegram import (
Dice,
Bot,
ChatAction,
VoiceChatScheduled,
VoiceChatStarted,
VoiceChatEnded,
VoiceChatParticipantsInvited,
VideoChatScheduled,
VideoChatStarted,
VideoChatEnded,
VideoChatParticipantsInvited,
MessageAutoDeleteTimerChanged,
VoiceChatScheduled,
WebAppData,
)
from telegram.ext import Defaults
from tests.conftest import check_shortcut_signature, check_shortcut_call, check_defaults_handling
@@ -182,6 +187,15 @@ def message(bot):
{'sender_chat': Chat(-123, 'discussion_channel')},
{'is_automatic_forward': True},
{'has_protected_content': True},
{'video_chat_scheduled': VideoChatScheduled(datetime.utcnow())},
{'video_chat_started': VideoChatStarted()},
{'video_chat_ended': VideoChatEnded(100)},
{
'video_chat_participants_invited': VideoChatParticipantsInvited(
[User(1, 'Rem', False), User(2, 'Emilia', False)]
)
},
{'web_app_data': WebAppData('some_data', 'some_button_text')},
],
ids=[
'forwarded_user',
@@ -230,9 +244,14 @@ def message(bot):
'voice_chat_started',
'voice_chat_ended',
'voice_chat_participants_invited',
'video_chat_scheduled',
'video_chat_started',
'video_chat_ended',
'video_chat_participants_invited',
'sender_chat',
'is_automatic_forward',
'has_protected_content',
'web_app_data',
],
)
def message_params(bot, request):
@@ -1585,3 +1604,36 @@ class TestMessage:
assert a != e
assert hash(a) != hash(e)
def test_invalid_input(
self,
):
with pytest.raises(ValueError):
Message(
self.id_,
self.date,
self.chat,
from_user=self.from_user,
voice_chat_scheduled=True,
video_chat_scheduled=False,
)
with pytest.raises(ValueError):
Message(
self.id_,
self.date,
self.chat,
from_user=self.from_user,
voice_chat_ended=True,
video_chat_ended=False,
)
with pytest.raises(ValueError):
Message(
self.id_,
self.date,
self.chat,
from_user=self.from_user,
voice_chat_participants_invited=True,
video_chat_participants_invited=False,
)
+5 -1
View File
@@ -16,7 +16,7 @@
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
from telegram import MessageAutoDeleteTimerChanged, VoiceChatEnded
from telegram import MessageAutoDeleteTimerChanged, VoiceChatEnded, VideoChatEnded
class TestMessageAutoDeleteTimerChanged:
@@ -49,6 +49,7 @@ class TestMessageAutoDeleteTimerChanged:
b = MessageAutoDeleteTimerChanged(100)
c = MessageAutoDeleteTimerChanged(50)
d = VoiceChatEnded(25)
e = VideoChatEnded(30)
assert a == b
assert hash(a) == hash(b)
@@ -58,3 +59,6 @@ class TestMessageAutoDeleteTimerChanged:
assert a != d
assert hash(a) != hash(d)
assert a != e
assert hash(a) != hash(e)
+15 -3
View File
@@ -149,11 +149,23 @@ class TestMessageHandler:
assert self.test_flag
def test_deprecation_warning(self):
with pytest.warns(TelegramDeprecationWarning, match='See https://git.io/fxJuV'):
with pytest.warns(
TelegramDeprecationWarning,
match='See https://github.com/python-telegram-bot/python-telegram-bot/wiki'
'/Transition-guide-to-Version-12.0',
):
MessageHandler(None, self.callback_basic, edited_updates=True)
with pytest.warns(TelegramDeprecationWarning, match='See https://git.io/fxJuV'):
with pytest.warns(
TelegramDeprecationWarning,
match='See https://github.com/python-telegram-bot/python-telegram-bot/wiki'
'/Transition-guide-to-Version-12.0',
):
MessageHandler(None, self.callback_basic, message_updates=False)
with pytest.warns(TelegramDeprecationWarning, match='See https://git.io/fxJuV'):
with pytest.warns(
TelegramDeprecationWarning,
match='See https://github.com/python-telegram-bot/python-telegram-bot/wiki'
'/Transition-guide-to-Version-12.0',
):
MessageHandler(None, self.callback_basic, channel_post_updates=True)
def test_edited_deprecated(self, message):
+20 -1
View File
@@ -100,6 +100,8 @@ def check_method(h4):
ignored |= {'venue'} # Added for ease of use
elif name == 'answerInlineQuery':
ignored |= {'current_offset'} # Added for ease of use
elif name == 'promoteChatMember':
ignored |= {'can_manage_voice_chats'} # for backwards compatibility
assert (sig.parameters.keys() ^ checked) - ignored == set()
@@ -121,6 +123,7 @@ def check_object(h4):
name.startswith('InlineQueryResult')
or name.startswith('InputMedia')
or name.startswith('BotCommandScope')
or name.startswith('MenuButton')
) and field == 'type':
continue
elif (name.startswith('ChatMember')) and field == 'status':
@@ -142,7 +145,11 @@ def check_object(h4):
if name == 'InlineQueryResult':
ignored |= {'id', 'type'} # attributes common to all subclasses
if name == 'ChatMember':
ignored |= {'user', 'status'} # attributes common to all subclasses
ignored |= {
'user',
'status',
'can_manage_video_chats',
} # attributes common to all subclasses
if name == 'ChatMember':
ignored |= {
'can_add_web_page_previews', # for backwards compatibility
@@ -168,6 +175,8 @@ def check_object(h4):
}
if name == 'BotCommandScope':
ignored |= {'type'} # attributes common to all subclasses
if name == 'MenuButton':
ignored |= {'type'} # attributes common to all subclasses
elif name == 'User':
ignored |= {'type'} # TODO: Deprecation
elif name in ('PassportFile', 'EncryptedPassportElement'):
@@ -176,6 +185,16 @@ def check_object(h4):
ignored |= {'message', 'type', 'source'}
elif name.startswith('InputMedia'):
ignored |= {'filename'} # Convenience parameter
elif name == 'ChatMemberAdministrator':
ignored |= {'can_manage_voice_chats'} # for backwards compatibility
elif name == 'Message':
# for backwards compatibility
ignored |= {
'voice_chat_ended',
'voice_chat_participants_invited',
'voice_chat_scheduled',
'voice_chat_started',
}
assert (sig.parameters.keys() ^ checked) - ignored == set()
+66
View File
@@ -0,0 +1,66 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2022
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
from telegram import SentWebAppMessage
import pytest
@pytest.fixture(scope='class')
def sent_web_app_message():
return SentWebAppMessage(
inline_message_id=TestSentWebAppMessage.inline_message_id,
)
class TestSentWebAppMessage:
inline_message_id = '123'
def test_slot_behaviour(self, sent_web_app_message, mro_slots):
inst = sent_web_app_message
for attr in inst.__slots__:
assert getattr(inst, attr, 'err') != 'err', f"got extra slot '{attr}'"
assert len(mro_slots(inst)) == len(set(mro_slots(inst))), "duplicate slot"
def test_to_dict(self, sent_web_app_message):
sent_web_app_message_dict = sent_web_app_message.to_dict()
assert isinstance(sent_web_app_message_dict, dict)
assert sent_web_app_message_dict['inline_message_id'] == self.inline_message_id
def test_de_json(self, bot):
data = {'inline_message_id': self.inline_message_id}
m = SentWebAppMessage.de_json(data, None)
assert m.inline_message_id == self.inline_message_id
def test_equality(self):
a = SentWebAppMessage(self.inline_message_id)
b = SentWebAppMessage(self.inline_message_id)
c = SentWebAppMessage("")
d = SentWebAppMessage("not_inline_message_id")
assert a == b
assert hash(a) == hash(b)
assert a is not b
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)
+27 -1
View File
@@ -23,7 +23,7 @@ from time import sleep
import pytest
from flaky import flaky
from telegram import Sticker, PhotoSize, TelegramError, StickerSet, Audio, MaskPosition, Bot
from telegram import Sticker, PhotoSize, TelegramError, StickerSet, Audio, MaskPosition, Bot, File
from telegram.error import BadRequest
from tests.conftest import check_shortcut_call, check_shortcut_signature, check_defaults_handling
@@ -87,6 +87,8 @@ class TestSticker:
sticker_file_id = '5a3128a4d2a04750b5b58397f3b5e812'
sticker_file_unique_id = 'adc3145fd2e84d95b64d68eaa22aa33e'
premium_animation = File("this_is_an_id", "this_is_an_unique_id")
def test_slot_behaviour(self, sticker, mro_slots, recwarn):
for attr in sticker.__slots__:
assert getattr(sticker, attr, 'err') != 'err', f"got extra slot '{attr}'"
@@ -117,6 +119,8 @@ class TestSticker:
assert sticker.thumb.width == self.thumb_width
assert sticker.thumb.height == self.thumb_height
assert sticker.thumb.file_size == self.thumb_file_size
# we need to be a premium TG user to send a premium sticker, so the below is not tested
# assert sticker.premium_animation == self.premium_animation
@flaky(3, 1)
def test_send_all_args(self, bot, chat_id, sticker_file, sticker):
@@ -134,6 +138,8 @@ class TestSticker:
assert message.sticker.is_animated == sticker.is_animated
assert message.sticker.is_video == sticker.is_video
assert message.sticker.file_size == sticker.file_size
# we need to be a premium TG user to send a premium sticker, so the below is not tested
# assert message.sticker.premium_animation == sticker.premium_animation
assert isinstance(message.sticker.thumb, PhotoSize)
assert isinstance(message.sticker.thumb.file_id, str)
@@ -207,6 +213,7 @@ class TestSticker:
'thumb': sticker.thumb.to_dict(),
'emoji': self.emoji,
'file_size': self.file_size,
'premium_animation': self.premium_animation.to_dict(),
}
json_sticker = Sticker.de_json(json_dict, bot)
@@ -219,6 +226,7 @@ class TestSticker:
assert json_sticker.emoji == self.emoji
assert json_sticker.file_size == self.file_size
assert json_sticker.thumb == sticker.thumb
assert json_sticker.premium_animation == self.premium_animation
def test_send_with_sticker(self, monkeypatch, bot, chat_id, sticker):
def test(url, data, **kwargs):
@@ -304,6 +312,24 @@ class TestSticker:
with pytest.raises(TypeError):
bot.send_sticker(chat_id)
@flaky(3, 1)
def test_premium_animation(self, bot):
# testing animation sucks a bit since we can't create a premium sticker. What we can do is
# get a sticker set which includes a premium sticker and check that specific one.
premium_sticker_set = bot.get_sticker_set("Flame")
# the first one to appear here is a sticker with unique file id of AQADOBwAAifPOElr
# this could change in the future ofc.
premium_sticker = premium_sticker_set.stickers[20]
assert premium_sticker.premium_animation.file_unique_id == "AQADOBwAAifPOElr"
assert isinstance(premium_sticker.premium_animation.file_id, str)
assert premium_sticker.premium_animation.file_id != ""
premium_sticker_dict = {
"file_unique_id": "AQADOBwAAifPOElr",
"file_id": premium_sticker.premium_animation.file_id,
"file_size": premium_sticker.premium_animation.file_size,
}
assert premium_sticker.premium_animation.to_dict() == premium_sticker_dict
def test_equality(self, sticker):
a = Sticker(
sticker.file_id,
+48
View File
@@ -35,6 +35,8 @@ def json_dict():
'can_join_groups': TestUser.can_join_groups,
'can_read_all_group_messages': TestUser.can_read_all_group_messages,
'supports_inline_queries': TestUser.supports_inline_queries,
'is_premium': TestUser.is_premium,
'added_to_attachment_menu': TestUser.added_to_attachment_menu,
}
@@ -51,6 +53,8 @@ def user(bot):
can_read_all_group_messages=TestUser.can_read_all_group_messages,
supports_inline_queries=TestUser.supports_inline_queries,
bot=bot,
is_premium=TestUser.is_premium,
added_to_attachment_menu=TestUser.added_to_attachment_menu,
)
@@ -64,6 +68,8 @@ class TestUser:
can_join_groups = True
can_read_all_group_messages = True
supports_inline_queries = False
is_premium = True
added_to_attachment_menu = False
def test_slot_behaviour(self, user, mro_slots, recwarn):
for attr in user.__slots__:
@@ -85,6 +91,8 @@ class TestUser:
assert user.can_join_groups == self.can_join_groups
assert user.can_read_all_group_messages == self.can_read_all_group_messages
assert user.supports_inline_queries == self.supports_inline_queries
assert user.is_premium == self.is_premium
assert user.added_to_attachment_menu == self.added_to_attachment_menu
def test_de_json_without_username(self, json_dict, bot):
del json_dict['username']
@@ -100,6 +108,8 @@ class TestUser:
assert user.can_join_groups == self.can_join_groups
assert user.can_read_all_group_messages == self.can_read_all_group_messages
assert user.supports_inline_queries == self.supports_inline_queries
assert user.is_premium == self.is_premium
assert user.added_to_attachment_menu == self.added_to_attachment_menu
def test_de_json_without_username_and_last_name(self, json_dict, bot):
del json_dict['username']
@@ -116,6 +126,8 @@ class TestUser:
assert user.can_join_groups == self.can_join_groups
assert user.can_read_all_group_messages == self.can_read_all_group_messages
assert user.supports_inline_queries == self.supports_inline_queries
assert user.is_premium == self.is_premium
assert user.added_to_attachment_menu == self.added_to_attachment_menu
def test_name(self, user):
assert user.name == '@username'
@@ -430,6 +442,42 @@ class TestUser:
monkeypatch.setattr(user.bot, 'copy_message', make_assertion)
assert user.copy_message(chat_id='chat_id', message_id='message_id')
def test_instance_method_get_menu_button(self, monkeypatch, user):
def make_assertion(*_, **kwargs):
return kwargs['chat_id'] == user.id
assert check_shortcut_signature(
User.get_menu_button, Bot.get_chat_menu_button, ['chat_id'], []
)
assert check_shortcut_call(
user.get_menu_button,
user.bot,
'get_chat_menu_button',
shortcut_kwargs=['chat_id'],
)
assert check_defaults_handling(user.get_menu_button, user.bot)
monkeypatch.setattr(user.bot, 'get_chat_menu_button', make_assertion)
assert user.get_menu_button()
def test_instance_method_set_menu_button(self, monkeypatch, user):
def make_assertion(*_, **kwargs):
return kwargs['chat_id'] == user.id and kwargs['menu_button'] == 'menu_button'
assert check_shortcut_signature(
User.set_menu_button, Bot.set_chat_menu_button, ['chat_id'], []
)
assert check_shortcut_call(
user.set_menu_button,
user.bot,
'set_chat_menu_button',
shortcut_kwargs=['chat_id'],
)
assert check_defaults_handling(user.set_menu_button, user.bot)
monkeypatch.setattr(user.bot, 'set_chat_menu_button', make_assertion)
assert user.set_menu_button(menu_button='menu_button')
def test_instance_method_approve_join_request(self, monkeypatch, user):
def make_assertion(*_, **kwargs):
chat_id = kwargs['chat_id'] == 'chat_id'
+195
View File
@@ -0,0 +1,195 @@
#!/usr/bin/env python
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2022
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import datetime as dtm
import pytest
from telegram import (
VideoChatStarted,
VideoChatEnded,
VideoChatParticipantsInvited,
User,
VideoChatScheduled,
)
from telegram.utils.helpers import to_timestamp
@pytest.fixture(scope='class')
def user1():
return User(first_name='Misses Test', id=123, is_bot=False)
@pytest.fixture(scope='class')
def user2():
return User(first_name='Mister Test', id=124, is_bot=False)
class TestVideoChatStarted:
def test_slot_behaviour(self, recwarn, mro_slots):
action = VideoChatStarted()
for attr in action.__slots__:
assert getattr(action, attr, 'err') != 'err', f"got extra slot '{attr}'"
assert not action.__dict__, f"got missing slot(s): {action.__dict__}"
assert len(mro_slots(action)) == len(set(mro_slots(action))), "duplicate slot"
action.custom = 'should give warning'
assert len(recwarn) == 1 and 'custom' in str(recwarn[0].message), recwarn.list
def test_de_json(self):
video_chat_started = VideoChatStarted.de_json({}, None)
assert isinstance(video_chat_started, VideoChatStarted)
def test_to_dict(self):
video_chat_started = VideoChatStarted()
video_chat_dict = video_chat_started.to_dict()
assert video_chat_dict == {}
class TestVideoChatEnded:
duration = 100
def test_slot_behaviour(self, recwarn, mro_slots):
action = VideoChatEnded(8)
for attr in action.__slots__:
assert getattr(action, attr, 'err') != 'err', f"got extra slot '{attr}'"
assert not action.__dict__, f"got missing slot(s): {action.__dict__}"
assert len(mro_slots(action)) == len(set(mro_slots(action))), "duplicate slot"
action.custom = 'should give warning'
assert len(recwarn) == 1 and 'custom' in str(recwarn[0].message), recwarn.list
def test_de_json(self):
json_dict = {'duration': self.duration}
video_chat_ended = VideoChatEnded.de_json(json_dict, None)
assert video_chat_ended.duration == self.duration
def test_to_dict(self):
video_chat_ended = VideoChatEnded(self.duration)
video_chat_dict = video_chat_ended.to_dict()
assert isinstance(video_chat_dict, dict)
assert video_chat_dict["duration"] == self.duration
def test_equality(self):
a = VideoChatEnded(100)
b = VideoChatEnded(100)
c = VideoChatEnded(50)
d = VideoChatStarted()
assert a == b
assert hash(a) == hash(b)
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)
class TestVideoChatParticipantsInvited:
def test_slot_behaviour(self, recwarn, mro_slots):
action = VideoChatParticipantsInvited([user1])
for attr in action.__slots__:
assert getattr(action, attr, 'err') != 'err', f"got extra slot '{attr}'"
assert not action.__dict__, f"got missing slot(s): {action.__dict__}"
assert len(mro_slots(action)) == len(set(mro_slots(action))), "duplicate slot"
action.custom = 'should give warning'
assert len(recwarn) == 1 and 'custom' in str(recwarn[0].message), recwarn.list
def test_de_json(self, user1, user2, bot):
json_data = {"users": [user1.to_dict(), user2.to_dict()]}
video_chat_participants = VideoChatParticipantsInvited.de_json(json_data, bot)
assert isinstance(video_chat_participants.users, list)
assert video_chat_participants.users[0] == user1
assert video_chat_participants.users[1] == user2
assert video_chat_participants.users[0].id == user1.id
assert video_chat_participants.users[1].id == user2.id
def test_to_dict(self, user1, user2):
video_chat_participants = VideoChatParticipantsInvited([user1, user2])
video_chat_dict = video_chat_participants.to_dict()
assert isinstance(video_chat_dict, dict)
assert video_chat_dict["users"] == [user1.to_dict(), user2.to_dict()]
assert video_chat_dict["users"][0]["id"] == user1.id
assert video_chat_dict["users"][1]["id"] == user2.id
def test_equality(self, user1, user2):
a = VideoChatParticipantsInvited([user1])
b = VideoChatParticipantsInvited([user1])
c = VideoChatParticipantsInvited([user1, user2])
d = VideoChatParticipantsInvited([user2])
e = VideoChatStarted()
assert a == b
assert hash(a) == hash(b)
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)
assert a != e
assert hash(a) != hash(e)
class TestVideoChatScheduled:
start_date = dtm.datetime.utcnow()
def test_slot_behaviour(self, recwarn, mro_slots):
inst = VideoChatScheduled(self.start_date)
for attr in inst.__slots__:
assert getattr(inst, attr, 'err') != 'err', f"got extra slot '{attr}'"
assert not inst.__dict__, f"got missing slot(s): {inst.__dict__}"
assert len(mro_slots(inst)) == len(set(mro_slots(inst))), "duplicate slot"
inst.custom, inst.start_date = 'should give warning', self.start_date
assert len(recwarn) == 1 and 'custom' in str(recwarn[0].message), recwarn.list
def test_expected_values(self):
assert pytest.approx(VideoChatScheduled(start_date=self.start_date) == self.start_date)
def test_de_json(self, bot):
assert VideoChatScheduled.de_json({}, bot=bot) is None
json_dict = {'start_date': to_timestamp(self.start_date)}
video_chat_scheduled = VideoChatScheduled.de_json(json_dict, bot)
assert pytest.approx(video_chat_scheduled.start_date == self.start_date)
def test_to_dict(self):
video_chat_scheduled = VideoChatScheduled(self.start_date)
video_chat_scheduled_dict = video_chat_scheduled.to_dict()
assert isinstance(video_chat_scheduled_dict, dict)
assert video_chat_scheduled_dict["start_date"] == to_timestamp(self.start_date)
def test_equality(self):
a = VideoChatScheduled(self.start_date)
b = VideoChatScheduled(self.start_date)
c = VideoChatScheduled(dtm.datetime.utcnow() + dtm.timedelta(seconds=5))
d = VideoChatStarted()
assert a == b
assert hash(a) == hash(b)
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)
+70
View File
@@ -0,0 +1,70 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2022
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
from telegram import WebAppData
import pytest
@pytest.fixture(scope='class')
def web_app_data():
return WebAppData(
data=TestWebAppData.data,
button_text=TestWebAppData.button_text,
)
class TestWebAppData:
data = 'data'
button_text = 'button_text'
def test_slot_behaviour(self, web_app_data, mro_slots):
for attr in web_app_data.__slots__:
assert getattr(web_app_data, attr, 'err') != 'err', f"got extra slot '{attr}'"
assert len(mro_slots(web_app_data)) == len(set(mro_slots(web_app_data))), "duplicate slot"
def test_to_dict(self, web_app_data):
web_app_data_dict = web_app_data.to_dict()
assert isinstance(web_app_data_dict, dict)
assert web_app_data_dict['data'] == self.data
assert web_app_data_dict['button_text'] == self.button_text
def test_de_json(self, bot):
json_dict = {'data': self.data, 'button_text': self.button_text}
web_app_data = WebAppData.de_json(json_dict, bot)
assert web_app_data.data == self.data
assert web_app_data.button_text == self.button_text
def test_equality(self):
a = WebAppData(self.data, self.button_text)
b = WebAppData(self.data, self.button_text)
c = WebAppData("", "")
d = WebAppData("not_data", "not_button_text")
assert a == b
assert hash(a) == hash(b)
assert a is not b
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)

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