Compare commits

..

8 Commits

Author SHA1 Message Date
Hinrich Mahler 21ded420e2 Bump version to v13.4 2021-03-14 17:00:26 +01:00
Bibo-Joshi 7d893fd04b Doc Fixes (#2404)
* Improve some badges for PTB-Raw

* doc fix for add_error_handler

* Some rendering

* Bump sphinx dependency

* Change signature annotation setting

* fix: chat_id can be string, message_id only int

* feat: add RTD link to documentation

* improving sender chat docstring (#2412)

* fix: improving sender chat docstring

also adding a note to a weird edge case

* fix: words being hard

* Add note on donations

* typo

* typo in User.get_profile_pictures docstrings

* Fix: meth, not attr for meth, not attr

* filters + inlinequery doc fix

* Bump versions, update RTD config file

* Try fix build

* Revert "fix: chat_id can be string, message_id only int"

This reverts commit ba04e5aa

* Add Starry & Harshil to credits

Co-authored-by: poolitzer <25934244+Poolitzer@users.noreply.github.com>
Co-authored-by: Harshil <ilovebhagwan@gmail.com>
2021-03-14 16:46:37 +01:00
Bibo-Joshi 7015f8dedc Type Hinting Fixes (#2425)
Co-authored-by: poolitzer <25934244+Poolitzer@users.noreply.github.com>
2021-03-14 16:42:03 +01:00
Poolitzer ac02bce109 API 5.1 (#2424)
* Feat: New invite links

* Fix: doc strings

Co-authored-by: Bibo-Joshi <hinrich.mahler@freenet.de>

* new dice, new admin privilege, revoke_messages, update and fix some docs

* add missing param to shortcut

* Add ChatMemberUpdated

* Add voicechat related objects

Signed-off-by: starry69 <starry369126@outlook.com>

* add versionadd tags

Signed-off-by: starry69 <starry369126@outlook.com>

* Fix filter tests

* Update tg.Update

* ChatMemberHandler

* Add versioning directives

* add can_manage_voice_chats attr and fix docs

Signed-off-by: starry69 <starry369126@outlook.com>

* fix chat shortcut

Signed-off-by: starry69 <starry369126@outlook.com>

* address review

* MADTC

* Chat.message_auto_delete_time

* Some doc fixes

* address review

Signed-off-by: starry69 <starry369126@outlook.com>

* welp

Signed-off-by: starry69 <starry369126@outlook.com>

* Add voicechat related filters

Signed-off-by: starry69 <starry369126@outlook.com>

* Fix: Addressing review

change place of version adding, added obj:True as doc string, changing how member limit is initiated

* feat: adding chat shortcuts for invite links

* fix: changing equality of chatinviteobjects

* Non-test comments

* Some test fixes

* A bit more tests

* Bump API version in both readmes

* Increase coverage

* Add Bot API Version in telegram.constants (#2429)

* add bot api version in constants

Signed-off-by: starry69 <starry369126@outlook.com>

* addressing review

Signed-off-by: starry69 <starry369126@outlook.com>

* add versioning directive

Co-authored-by: Bibo-Joshi <hinrich.mahler@freenet.de>

* pre-commit & coverage

Co-authored-by: Bibo-Joshi <hinrich.mahler@freenet.de>
Co-authored-by: Harshil <ilovebhagwan@gmail.com>
Co-authored-by: starry69 <starry369126@outlook.com>
2021-03-14 16:41:35 +01:00
Bibo-Joshi 3a9a0ab96d Update pre-commit Settings (#2415)
Co-authored-by: Harshil <ilovebhagwan@gmail.com>
2021-03-13 16:21:03 +01:00
Bibo-Joshi aba17cb997 Improve Updater.set_webhook (#2419)
* Get started

* tests

* Some doc fixes

* Some doc fixes
2021-03-13 15:35:26 +01:00
Bibo-Joshi 038a3b4452 Fix Logging for Vendored urllib3 (#2427) 2021-03-13 15:14:10 +01:00
Bibo-Joshi b03ebc5a65 Stabilize Tests (#2409)
* Fix Photo Tests

* Try harder

* Try stabalizing pin&unpin test

* Drop file size tests

* Fix that one failing test …
2021-03-10 16:51:56 +01:00
86 changed files with 2819 additions and 627 deletions
+11 -14
View File
@@ -50,6 +50,8 @@ Instructions for making a code change
The central development branch is ``master``, which should be clean and ready for release at any time. In general, all changes should be done as feature branches based off of ``master``.
If you want to do solely documentation changes, base them and PR to the branch ``doc-fixes``. This branch also has its own `RTD build`_.
Here's how to make a one-off code change.
1. **Choose a descriptive branch name.** It should be lowercase, hyphen-separated, and a noun describing the change (so, ``fuzzy-rules``, but not ``implement-fuzzy-rules``). Also, it shouldn't start with ``hotfix`` or ``release``.
@@ -109,12 +111,6 @@ Here's how to make a one-off code change.
- Before making a commit ensure that all automated tests still pass:
.. code-block::
$ make test
If you don't have ``make``, do:
.. code-block::
$ pytest -v
@@ -127,18 +123,18 @@ Here's how to make a one-off code change.
prior to running the tests.
- To actually make the commit (this will trigger tests for yapf, lint and pep8 automatically):
- If you want run style & type checks before committing run
.. code-block::
$ pre-commit run -a
- To actually make the commit (this will trigger tests style & type checks automatically):
.. code-block:: bash
$ git add your-file-changed.py
- yapf may change code formatting, make sure to re-add them to your commit.
.. code-block:: bash
$ git commit -a -m "your-commit-message-here"
- Finally, push it to your GitHub fork, run:
.. code-block:: bash
@@ -196,7 +192,7 @@ Style commandments
Assert comparison order
#######################
- assert statements should compare in **actual** == **expected** order.
Assert statements should compare in **actual** == **expected** order.
For example (assuming ``test_call`` is the thing being tested):
.. code-block:: python
@@ -256,3 +252,4 @@ break the API classes. For example:
.. _`here`: https://mypy.readthedocs.io/en/stable/cheat_sheet_py3.html
.. _`Black`: https://black.readthedocs.io/en/stable/index.html
.. _`popular editors`: https://black.readthedocs.io/en/stable/editor_integration.html
.. _`RTD build`: https://python-telegram-bot.readthedocs.io/en/doc-fixes
+1 -1
View File
@@ -27,4 +27,4 @@ Hey! You're PRing? Cool! Please have a look at the below checklist. It's here to
- [ ] Added new handlers for new update types
- [ ] Added new filters for new message (sub)types
- [ ] Added or updated documentation for the changed class(es) and/or method(s)
- [ ] Updated the Bot API version number in all places in `README.rst` and `README_RAW.rst`, including the badge
- [ ] Updated the Bot API version number in all places: `README.rst` and `README_RAW.rst` (including the badge), as well as `telegram.constants.BOT_API_VERSION`
@@ -0,0 +1,17 @@
name: Warning maintainers
on:
pull_request:
paths:
- requirements.txt
- requirements-dev.txt
- .pre-commit-config.yaml
jobs:
job:
runs-on: ubuntu-latest
name: about pre-commit and dependency change
steps:
- name: running the check
uses: Poolitzer/notifier-action@master
with:
notify-message: Hey! Looks like you edited the (dev) requirements or the pre-commit hooks. I'm just a friendly reminder to keep the pre-commit hook versions in sync with the dev requirements and the additional dependencies for the hooks in sync with the requirements :)
repo-token: ${{ secrets.GITHUB_TOKEN }}
+28 -6
View File
@@ -1,6 +1,6 @@
# Make sure that
# * the revs specified here match requirements-dev.txt
# * the makefile checks the same files as pre-commit
# * the additional_dependencies here match requirements.txt
repos:
- repo: https://github.com/psf/black
rev: 20.8b1
@@ -14,21 +14,43 @@ repos:
hooks:
- id: flake8
- repo: https://github.com/PyCQA/pylint
rev: pylint-2.6.0
rev: pylint-2.7.2
hooks:
- id: pylint
files: ^(telegram|examples)/.*\.py$
args:
- --rcfile=setup.cfg
- --rcfile=setup.cfg
additional_dependencies:
- certifi
- tornado>=5.1
- APScheduler==3.6.3
- . # this basically does `pip install -e .`
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.800
rev: v0.812
hooks:
- id: mypy
files: ^(telegram|examples)/.*\.py$
name: mypy-ptb
files: ^telegram/.*\.py$
additional_dependencies:
- certifi
- tornado>=5.1
- APScheduler==3.6.3
- . # this basically does `pip install -e .`
- id: mypy
name: mypy-examples
files: ^examples/.*\.py$
args:
- --no-strict-optional
- --follow-imports=silent
additional_dependencies:
- certifi
- tornado>=5.1
- APScheduler==3.6.3
- . # this basically does `pip install -e .`
- repo: https://github.com/asottile/pyupgrade
rev: v2.10.0
hooks:
- id: pyupgrade
files: ^(telegram|examples|tests)/.*\.py$
args:
- --py36-plus
- --py36-plus
+17 -5
View File
@@ -1,10 +1,22 @@
# syntax: https://docs.readthedocs.io/en/latest/yaml-config.html
# .readthedocs.yaml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
# Build documentation in the docs/ directory with Sphinx
sphinx:
configuration: docs/source/conf.py
# Optionally build your docs in additional formats such as PDF
formats:
- pdf
- pdf
# Optionally set the version of Python and requirements required to build your docs
python:
setup_py_install: true
version: 3
requirements_file: docs/requirements-docs.txt
install:
- method: pip
path: .
- requirements: docs/requirements-docs.txt
+10 -2
View File
@@ -2,12 +2,20 @@ Credits
=======
``python-telegram-bot`` was originally created by
`Leandro Toledo <https://github.com/leandrotoledo>`_ and is now maintained by `Hinrich Mahler <https://github.com/Bibo-Joshi>`_.
`Leandro Toledo <https://github.com/leandrotoledo>`_.
The current development team includes
- `Hinrich Mahler <https://github.com/Bibo-Joshi>`_ (maintainer)
- `Poolitzer <https://github.com/Poolitzer>`_ (community liaison)
- `Shivam <https://github.com/Starry69>`_
- `Harshil <https://github.com/harshil21>`_
Emeritus maintainers include
`Jannes Höke <https://github.com/jh0ker>`_ (`@jh0ker <https://t.me/jh0ker>`_ on Telegram),
`Noam Meltzer <https://github.com/tsnoam>`_, `Pieter Schutz <https://github.com/eldinnie>`_ and `Jasmin Bom <https://github.com/jsmnbom>`_.
The maintainers are actively supported by `Poolitzer <https://github.com/Poolitzer>`_ in terms of development and community liaison.
Vendored packages
-----------------
We're vendoring urllib3 as part of ``python-telegram-bot`` which is distributed under the MIT
license. For more info, full credits & license terms, the sources can be found here:
+25
View File
@@ -2,6 +2,31 @@
Changelog
=========
Version 13.4
============
*Released 2021-03-14*
**Major Changes:**
- Full support of Bot API 5.1 (`#2424`_)
**Minor changes, CI improvements, doc fixes and type hinting:**
- Improve ``Updater.set_webhook`` (`#2419`_)
- Doc Fixes (`#2404`_)
- Type Hinting Fixes (`#2425`_)
- Update ``pre-commit`` Settings (`#2415`_)
- Fix Logging for Vendored ``urllib3`` (`#2427`_)
- Stabilize Tests (`#2409`_)
.. _`#2424`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2424
.. _`#2419`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2419
.. _`#2404`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2404
.. _`#2425`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2425
.. _`#2415`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2415
.. _`#2427`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2427
.. _`#2409`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2409
Version 13.3
============
*Released 2021-02-19*
-53
View File
@@ -1,53 +0,0 @@
.DEFAULT_GOAL := help
.PHONY: clean pep8 black lint test install
PYLINT := pylint
PYTEST := pytest
PEP8 := flake8
BLACK := black
MYPY := mypy
PIP := pip
clean:
rm -fr build
rm -fr dist
find . -name '*.pyc' -exec rm -f {} \;
find . -name '*.pyo' -exec rm -f {} \;
find . -name '*~' -exec rm -f {} \;
find . -regex "./telegram[0-9]*.\(jpg\|mp3\|mp4\|ogg\|png\|webp\)" -exec rm {} \;
pep8:
$(PEP8) telegram tests examples
black:
$(BLACK) .
lint:
$(PYLINT) --rcfile=setup.cfg telegram examples
mypy:
$(MYPY) -p telegram
$(MYPY) examples
test:
$(PYTEST) -v
install:
$(PIP) install -r requirements.txt -r requirements-dev.txt
help:
@echo "Available targets:"
@echo "- clean Clean up the source directory"
@echo "- pep8 Check style with flake8"
@echo "- lint Check style with pylint"
@echo "- black Check style with black"
@echo "- mypy Check type hinting with mypy"
@echo "- test Run tests using pytest"
@echo
@echo "Available variables:"
@echo "- PYLINT default: $(PYLINT)"
@echo "- PYTEST default: $(PYTEST)"
@echo "- PEP8 default: $(PEP8)"
@echo "- BLACK default: $(BLACK)"
@echo "- MYPY default: $(MYPY)"
@echo "- PIP default: $(PIP)"
+7 -2
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.0-blue?logo=telegram
.. image:: https://img.shields.io/badge/Bot%20API-5.1-blue?logo=telegram
:target: https://core.telegram.org/bots/api-changelog
:alt: Supported Bot API versions
@@ -111,7 +111,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.0** are supported.
All types and methods of the Telegram Bot API **5.1** are supported.
==========
Installing
@@ -230,6 +230,11 @@ 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>`_.
========
Donating
========
Occasionally we are asked if we accept donations to support the development. While we appreciate the thought, maintaining PTB is our hobby and we have almost no running costs for it. We therefore have nothing set up to accept donations. If you still want to donate, we kindly ask you to donate to another open source project/initiative of your choice instead.
=======
License
=======
+7 -2
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.0-blue?logo=telegram
.. image:: https://img.shields.io/badge/Bot%20API-5.1-blue?logo=telegram
:target: https://core.telegram.org/bots/api-changelog
:alt: Supported Bot API versions
@@ -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.0** are supported.
All types and methods of the Telegram Bot API **5.1** are supported.
==========
Installing
@@ -212,6 +212,11 @@ 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>`_.
========
Donating
========
Occasionally we are asked if we accept donations to support the development. While we appreciate the thought, maintaining PTB is our hobby and we have almost no running costs for it. We therefore have nothing set up to accept donations. If you still want to donate, we kindly ask you to donate to another open source project/initiative of your choice instead.
=======
License
=======
+2 -2
View File
@@ -1,3 +1,3 @@
sphinx>=1.7.9
sphinx_rtd_theme
sphinx==3.5.2
sphinx_rtd_theme==0.5.1
sphinx-pypi-upload
+4 -4
View File
@@ -24,7 +24,7 @@ sys.path.insert(0, os.path.abspath('../..'))
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
needs_sphinx = '1.7.9'
needs_sphinx = '3.5.2'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
@@ -35,7 +35,7 @@ extensions = [
]
# Don't show type hints in the signature - that just makes it hardly readable
# and we document the types anyway
autodoc_typehints = 'description'
autodoc_typehints = 'none'
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
@@ -61,9 +61,9 @@ author = u'Leandro Toledo'
# built documents.
#
# The short X.Y version.
version = '13.3' # telegram.__version__[:3]
version = '13.4' # telegram.__version__[:3]
# The full version, including alpha/beta/rc tags.
release = '13.3' # telegram.__version__
release = '13.4' # telegram.__version__
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
+6
View File
@@ -0,0 +1,6 @@
telegram.ChatInviteLink
=======================
.. autoclass:: telegram.ChatInviteLink
:members:
:show-inheritance:
@@ -0,0 +1,6 @@
telegram.ChatMemberUpdated
==========================
.. autoclass:: telegram.ChatMemberUpdated
:members:
:show-inheritance:
@@ -0,0 +1,6 @@
telegram.ext.ChatMemberHandler
==============================
.. autoclass:: telegram.ext.ChatMemberHandler
:members:
:show-inheritance:
+1
View File
@@ -21,6 +21,7 @@ Handlers
telegram.ext.handler
telegram.ext.callbackqueryhandler
telegram.ext.choseninlineresulthandler
telegram.ext.chatmemberhandler
telegram.ext.commandhandler
telegram.ext.conversationhandler
telegram.ext.inlinequeryhandler
@@ -0,0 +1,6 @@
telegram.MessageAutoDeleteTimerChanged
======================================
.. autoclass:: telegram.MessageAutoDeleteTimerChanged
:members:
:show-inheritance:
+6
View File
@@ -13,8 +13,10 @@ telegram package
telegram.callbackquery
telegram.chat
telegram.chataction
telegram.chatinvitelink
telegram.chatlocation
telegram.chatmember
telegram.chatmemberupdated
telegram.chatpermissions
telegram.chatphoto
telegram.constants
@@ -38,6 +40,7 @@ telegram package
telegram.location
telegram.loginurl
telegram.message
telegram.messageautodeletetimerchanged
telegram.messageid
telegram.messageentity
telegram.parsemode
@@ -57,6 +60,9 @@ telegram package
telegram.video
telegram.videonote
telegram.voice
telegram.voicechatstarted
telegram.voicechatended
telegram.voicechatparticipantsinvited
telegram.webhookinfo
Stickers
+7
View File
@@ -0,0 +1,7 @@
telegram.VoiceChatEnded
=======================
.. autoclass:: telegram.VoiceChatEnded
:members:
:show-inheritance:
@@ -0,0 +1,7 @@
telegram.VoiceChatParticipantsInvited
=====================================
.. autoclass:: telegram.VoiceChatParticipantsInvited
:members:
:show-inheritance:
@@ -0,0 +1,7 @@
telegram.VoiceChatStarted
=========================
.. autoclass:: telegram.VoiceChatStarted
:members:
:show-inheritance:
+13 -14
View File
@@ -1,6 +1,5 @@
#!/usr/bin/env python
# pylint: disable=W0613, C0116
# type: ignore[union-attr]
# pylint: disable=C0116
# This program is dedicated to the public domain under the CC0 license.
"""
@@ -37,7 +36,7 @@ logger = logging.getLogger(__name__)
GENDER, PHOTO, LOCATION, BIO = range(4)
def start(update: Update, context: CallbackContext) -> int:
def start(update: Update, _: CallbackContext) -> int:
reply_keyboard = [['Boy', 'Girl', 'Other']]
update.message.reply_text(
@@ -50,7 +49,7 @@ def start(update: Update, context: CallbackContext) -> int:
return GENDER
def gender(update: Update, context: CallbackContext) -> int:
def gender(update: Update, _: CallbackContext) -> int:
user = update.message.from_user
logger.info("Gender of %s: %s", user.first_name, update.message.text)
update.message.reply_text(
@@ -62,52 +61,52 @@ def gender(update: Update, context: CallbackContext) -> int:
return PHOTO
def photo(update: Update, context: CallbackContext) -> int:
def photo(update: Update, _: CallbackContext) -> int:
user = update.message.from_user
photo_file = update.message.photo[-1].get_file()
photo_file.download('user_photo.jpg')
logger.info("Photo of %s: %s", user.first_name, 'user_photo.jpg')
update.message.reply_text(
'Gorgeous! Now, send me your location please, ' 'or send /skip if you don\'t want to.'
'Gorgeous! Now, send me your location please, or send /skip if you don\'t want to.'
)
return LOCATION
def skip_photo(update: Update, context: CallbackContext) -> int:
def skip_photo(update: Update, _: CallbackContext) -> int:
user = update.message.from_user
logger.info("User %s did not send a photo.", user.first_name)
update.message.reply_text(
'I bet you look great! Now, send me your location please, ' 'or send /skip.'
'I bet you look great! Now, send me your location please, or send /skip.'
)
return LOCATION
def location(update: Update, context: CallbackContext) -> int:
def location(update: Update, _: CallbackContext) -> int:
user = update.message.from_user
user_location = update.message.location
logger.info(
"Location of %s: %f / %f", user.first_name, user_location.latitude, user_location.longitude
)
update.message.reply_text(
'Maybe I can visit you sometime! ' 'At last, tell me something about yourself.'
'Maybe I can visit you sometime! At last, tell me something about yourself.'
)
return BIO
def skip_location(update: Update, context: CallbackContext) -> int:
def skip_location(update: Update, _: CallbackContext) -> int:
user = update.message.from_user
logger.info("User %s did not send a location.", user.first_name)
update.message.reply_text(
'You seem a bit paranoid! ' 'At last, tell me something about yourself.'
'You seem a bit paranoid! At last, tell me something about yourself.'
)
return BIO
def bio(update: Update, context: CallbackContext) -> int:
def bio(update: Update, _: CallbackContext) -> int:
user = update.message.from_user
logger.info("Bio of %s: %s", user.first_name, update.message.text)
update.message.reply_text('Thank you! I hope we can talk again some day.')
@@ -115,7 +114,7 @@ def bio(update: Update, context: CallbackContext) -> int:
return ConversationHandler.END
def cancel(update: Update, context: CallbackContext) -> int:
def cancel(update: Update, _: CallbackContext) -> int:
user = update.message.from_user
logger.info("User %s canceled the conversation.", user.first_name)
update.message.reply_text(
+4 -5
View File
@@ -1,6 +1,5 @@
#!/usr/bin/env python
# pylint: disable=W0613, C0116
# type: ignore[union-attr]
# pylint: disable=C0116
# This program is dedicated to the public domain under the CC0 license.
"""
@@ -54,7 +53,7 @@ def facts_to_str(user_data: Dict[str, str]) -> str:
return "\n".join(facts).join(['\n', '\n'])
def start(update: Update, context: CallbackContext) -> int:
def start(update: Update, _: CallbackContext) -> int:
update.message.reply_text(
"Hi! My name is Doctor Botter. I will hold a more complex conversation with you. "
"Why don't you tell me something about yourself?",
@@ -72,9 +71,9 @@ def regular_choice(update: Update, context: CallbackContext) -> int:
return TYPING_REPLY
def custom_choice(update: Update, context: CallbackContext) -> int:
def custom_choice(update: Update, _: CallbackContext) -> int:
update.message.reply_text(
'Alright, please send me the category first, ' 'for example "Most impressive skill"'
'Alright, please send me the category first, for example "Most impressive skill"'
)
return TYPING_CHOICE
+3 -4
View File
@@ -1,6 +1,5 @@
#!/usr/bin/env python
# pylint: disable=W0613, C0116
# type: ignore[union-attr]
# pylint: disable=C0116
# This program is dedicated to the public domain under the CC0 license.
"""Bot that explains Telegram's "Deep Linking Parameters" functionality.
@@ -79,7 +78,7 @@ def deep_linked_level_2(update: Update, context: CallbackContext) -> None:
update.message.reply_text(text, parse_mode=ParseMode.MARKDOWN, disable_web_page_preview=True)
def deep_linked_level_3(update: Update, context: CallbackContext) -> None:
def deep_linked_level_3(update: Update, _: CallbackContext) -> None:
"""Reached through the USING_ENTITIES payload"""
update.message.reply_text(
"It is also possible to make deep-linking using InlineKeyboardButtons.",
@@ -104,7 +103,7 @@ def deep_linked_level_4(update: Update, context: CallbackContext) -> None:
)
def main():
def main() -> None:
"""Start the bot."""
# Create the Updater and pass it your bot's token.
updater = Updater("TOKEN")
+5 -6
View File
@@ -1,6 +1,5 @@
#!/usr/bin/env python
# pylint: disable=W0613, C0116
# type: ignore[union-attr]
# pylint: disable=C0116
# This program is dedicated to the public domain under the CC0 license.
"""
@@ -31,22 +30,22 @@ logger = logging.getLogger(__name__)
# Define a few command handlers. These usually take the two arguments update and
# context. Error handlers also receive the raised TelegramError object in error.
def start(update: Update, context: CallbackContext) -> None:
def start(update: Update, _: CallbackContext) -> None:
"""Send a message when the command /start is issued."""
update.message.reply_text('Hi!')
def help_command(update: Update, context: CallbackContext) -> None:
def help_command(update: Update, _: CallbackContext) -> None:
"""Send a message when the command /help is issued."""
update.message.reply_text('Help!')
def echo(update: Update, context: CallbackContext) -> None:
def echo(update: Update, _: CallbackContext) -> None:
"""Echo the user message."""
update.message.reply_text(update.message.text)
def main():
def main() -> None:
"""Start the bot."""
# Create the Updater and pass it your bot's token.
updater = Updater("TOKEN")
+8 -8
View File
@@ -1,6 +1,5 @@
#!/usr/bin/env python
# pylint: disable=W0613, C0116
# type: ignore[union-attr]
# pylint: disable=C0116
# This program is dedicated to the public domain under the CC0 license.
"""
@@ -28,7 +27,7 @@ BOT_TOKEN = "TOKEN"
DEVELOPER_CHAT_ID = 123456789
def error_handler(update: Update, context: CallbackContext) -> None:
def error_handler(update: object, context: CallbackContext) -> None:
"""Log the error and send a telegram message to notify the developer."""
# Log the error before we do anything else, so we can see it even if something breaks.
logger.error(msg="Exception while handling an update:", exc_info=context.error)
@@ -40,9 +39,10 @@ def error_handler(update: Update, context: CallbackContext) -> None:
# Build the message with some markup and additional information about what happened.
# You might need to add some logic to deal with messages longer than the 4096 character limit.
update_str = update.to_dict() if isinstance(update, Update) else str(update)
message = (
f'An exception was raised while handling an update\n'
f'<pre>update = {html.escape(json.dumps(update.to_dict(), indent=2, ensure_ascii=False))}'
f'<pre>update = {html.escape(json.dumps(update_str, indent=2, ensure_ascii=False))}'
'</pre>\n\n'
f'<pre>context.chat_data = {html.escape(str(context.chat_data))}</pre>\n\n'
f'<pre>context.user_data = {html.escape(str(context.user_data))}</pre>\n\n'
@@ -53,19 +53,19 @@ def error_handler(update: Update, context: CallbackContext) -> None:
context.bot.send_message(chat_id=DEVELOPER_CHAT_ID, text=message, parse_mode=ParseMode.HTML)
def bad_command(update: Update, context: CallbackContext) -> None:
def bad_command(_: Update, context: CallbackContext) -> None:
"""Raise an error to trigger the error handler."""
context.bot.wrong_method_name()
context.bot.wrong_method_name() # type: ignore[attr-defined]
def start(update: Update, context: CallbackContext) -> None:
def start(update: Update, _: CallbackContext) -> None:
update.effective_message.reply_html(
'Use /bad_command to cause an error.\n'
f'Your chat id is <code>{update.effective_chat.id}</code>.'
)
def main():
def main() -> None:
# Create the Updater and pass it your bot's token.
updater = Updater(BOT_TOKEN)
+9 -8
View File
@@ -1,6 +1,5 @@
#!/usr/bin/env python
# pylint: disable=W0613, C0116
# type: ignore[union-attr]
# pylint: disable=C0116
# This program is dedicated to the public domain under the CC0 license.
"""
@@ -30,32 +29,34 @@ logger = logging.getLogger(__name__)
# Define a few command handlers. These usually take the two arguments update and
# context. Error handlers also receive the raised TelegramError object in error.
def start(update: Update, context: CallbackContext) -> None:
def start(update: Update, _: CallbackContext) -> None:
"""Send a message when the command /start is issued."""
update.message.reply_text('Hi!')
def help_command(update: Update, context: CallbackContext) -> None:
def help_command(update: Update, _: CallbackContext) -> None:
"""Send a message when the command /help is issued."""
update.message.reply_text('Help!')
def inlinequery(update: Update, context: CallbackContext) -> None:
def inlinequery(update: Update, _: CallbackContext) -> None:
"""Handle the inline query."""
query = update.inline_query.query
results = [
InlineQueryResultArticle(
id=uuid4(), title="Caps", input_message_content=InputTextMessageContent(query.upper())
id=str(uuid4()),
title="Caps",
input_message_content=InputTextMessageContent(query.upper()),
),
InlineQueryResultArticle(
id=uuid4(),
id=str(uuid4()),
title="Bold",
input_message_content=InputTextMessageContent(
f"*{escape_markdown(query)}*", parse_mode=ParseMode.MARKDOWN
),
),
InlineQueryResultArticle(
id=uuid4(),
id=str(uuid4()),
title="Italic",
input_message_content=InputTextMessageContent(
f"_{escape_markdown(query)}_", parse_mode=ParseMode.MARKDOWN
+5 -6
View File
@@ -1,6 +1,5 @@
#!/usr/bin/env python
# pylint: disable=W0613, C0116
# type: ignore[union-attr]
# pylint: disable=C0116
# This program is dedicated to the public domain under the CC0 license.
"""
@@ -17,7 +16,7 @@ logging.basicConfig(
logger = logging.getLogger(__name__)
def start(update: Update, context: CallbackContext) -> None:
def start(update: Update, _: CallbackContext) -> None:
keyboard = [
[
InlineKeyboardButton("Option 1", callback_data='1'),
@@ -31,7 +30,7 @@ def start(update: Update, context: CallbackContext) -> None:
update.message.reply_text('Please choose:', reply_markup=reply_markup)
def button(update: Update, context: CallbackContext) -> None:
def button(update: Update, _: CallbackContext) -> None:
query = update.callback_query
# CallbackQueries need to be answered, even if no notification to the user is needed
@@ -41,11 +40,11 @@ def button(update: Update, context: CallbackContext) -> None:
query.edit_message_text(text=f"Selected option: {query.data}")
def help_command(update: Update, context: CallbackContext) -> None:
def help_command(update: Update, _: CallbackContext) -> None:
update.message.reply_text("Use /start to test this bot.")
def main():
def main() -> None:
# Create the Updater and pass it your bot's token.
updater = Updater("TOKEN")
+9 -10
View File
@@ -1,6 +1,5 @@
#!/usr/bin/env python
# pylint: disable=W0613, C0116
# type: ignore[union-attr]
# pylint: disable=C0116
# This program is dedicated to the public domain under the CC0 license.
"""Simple inline keyboard bot with multiple CallbackQueryHandlers.
@@ -38,7 +37,7 @@ FIRST, SECOND = range(2)
ONE, TWO, THREE, FOUR = range(4)
def start(update: Update, context: CallbackContext) -> None:
def start(update: Update, _: CallbackContext) -> int:
"""Send message on `/start`."""
# Get user that sent /start and log his name
user = update.message.from_user
@@ -60,7 +59,7 @@ def start(update: Update, context: CallbackContext) -> None:
return FIRST
def start_over(update: Update, context: CallbackContext) -> None:
def start_over(update: Update, _: CallbackContext) -> int:
"""Prompt same text & keyboard as `start` does but not as new message"""
# Get CallbackQuery from Update
query = update.callback_query
@@ -81,7 +80,7 @@ def start_over(update: Update, context: CallbackContext) -> None:
return FIRST
def one(update: Update, context: CallbackContext) -> None:
def one(update: Update, _: CallbackContext) -> int:
"""Show new choice of buttons"""
query = update.callback_query
query.answer()
@@ -98,7 +97,7 @@ def one(update: Update, context: CallbackContext) -> None:
return FIRST
def two(update: Update, context: CallbackContext) -> None:
def two(update: Update, _: CallbackContext) -> int:
"""Show new choice of buttons"""
query = update.callback_query
query.answer()
@@ -115,7 +114,7 @@ def two(update: Update, context: CallbackContext) -> None:
return FIRST
def three(update: Update, context: CallbackContext) -> None:
def three(update: Update, _: CallbackContext) -> int:
"""Show new choice of buttons"""
query = update.callback_query
query.answer()
@@ -133,7 +132,7 @@ def three(update: Update, context: CallbackContext) -> None:
return SECOND
def four(update: Update, context: CallbackContext) -> None:
def four(update: Update, _: CallbackContext) -> int:
"""Show new choice of buttons"""
query = update.callback_query
query.answer()
@@ -150,7 +149,7 @@ def four(update: Update, context: CallbackContext) -> None:
return FIRST
def end(update: Update, context: CallbackContext) -> None:
def end(update: Update, _: CallbackContext) -> int:
"""Returns `ConversationHandler.END`, which tells the
ConversationHandler that the conversation is over"""
query = update.callback_query
@@ -159,7 +158,7 @@ def end(update: Update, context: CallbackContext) -> None:
return ConversationHandler.END
def main():
def main() -> None:
# Create the Updater and pass it your bot's token.
updater = Updater("TOKEN")
+18 -18
View File
@@ -1,6 +1,5 @@
#!/usr/bin/env python
# pylint: disable=W0613, C0116
# type: ignore[union-attr]
# pylint: disable=C0116
# This program is dedicated to the public domain under the CC0 license.
"""
@@ -16,6 +15,7 @@ bot.
"""
import logging
from typing import Tuple, Dict, Any
from telegram import InlineKeyboardMarkup, InlineKeyboardButton, Update
from telegram.ext import (
@@ -64,14 +64,14 @@ END = ConversationHandler.END
# Helper
def _name_switcher(level):
def _name_switcher(level: str) -> Tuple[str, str]:
if level == PARENTS:
return 'Father', 'Mother'
return 'Brother', 'Sister'
# Top level conversation callbacks
def start(update: Update, context: CallbackContext) -> None:
def start(update: Update, context: CallbackContext) -> str:
"""Select an action: Adding parent/child or show data."""
text = (
'You may add a familiy member, yourself show the gathered data or end the '
@@ -103,7 +103,7 @@ def start(update: Update, context: CallbackContext) -> None:
return SELECTING_ACTION
def adding_self(update: Update, context: CallbackContext) -> None:
def adding_self(update: Update, context: CallbackContext) -> str:
"""Add information about youself."""
context.user_data[CURRENT_LEVEL] = SELF
text = 'Okay, please tell me about yourself.'
@@ -116,10 +116,10 @@ def adding_self(update: Update, context: CallbackContext) -> None:
return DESCRIBING_SELF
def show_data(update: Update, context: CallbackContext) -> None:
def show_data(update: Update, context: CallbackContext) -> str:
"""Pretty print gathered data."""
def prettyprint(user_data, level):
def prettyprint(user_data: Dict[str, Any], level: str) -> str:
people = user_data.get(level)
if not people:
return '\nNo information yet.'
@@ -151,14 +151,14 @@ def show_data(update: Update, context: CallbackContext) -> None:
return SHOWING
def stop(update: Update, context: CallbackContext) -> None:
def stop(update: Update, _: CallbackContext) -> int:
"""End Conversation by command."""
update.message.reply_text('Okay, bye.')
return END
def end(update: Update, context: CallbackContext) -> None:
def end(update: Update, _: CallbackContext) -> int:
"""End conversation from InlineKeyboardButton."""
update.callback_query.answer()
@@ -169,7 +169,7 @@ def end(update: Update, context: CallbackContext) -> None:
# Second level conversation callbacks
def select_level(update: Update, context: CallbackContext) -> None:
def select_level(update: Update, _: CallbackContext) -> str:
"""Choose to add a parent or a child."""
text = 'You may add a parent or a child. Also you can show the gathered data or go back.'
buttons = [
@@ -190,7 +190,7 @@ def select_level(update: Update, context: CallbackContext) -> None:
return SELECTING_LEVEL
def select_gender(update: Update, context: CallbackContext) -> None:
def select_gender(update: Update, context: CallbackContext) -> str:
"""Choose to add mother or father."""
level = update.callback_query.data
context.user_data[CURRENT_LEVEL] = level
@@ -217,7 +217,7 @@ def select_gender(update: Update, context: CallbackContext) -> None:
return SELECTING_GENDER
def end_second_level(update: Update, context: CallbackContext) -> None:
def end_second_level(update: Update, context: CallbackContext) -> int:
"""Return to top level conversation."""
context.user_data[START_OVER] = True
start(update, context)
@@ -226,7 +226,7 @@ def end_second_level(update: Update, context: CallbackContext) -> None:
# Third level callbacks
def select_feature(update: Update, context: CallbackContext) -> None:
def select_feature(update: Update, context: CallbackContext) -> str:
"""Select a feature to update for the person."""
buttons = [
[
@@ -253,7 +253,7 @@ def select_feature(update: Update, context: CallbackContext) -> None:
return SELECTING_FEATURE
def ask_for_input(update: Update, context: CallbackContext) -> None:
def ask_for_input(update: Update, context: CallbackContext) -> str:
"""Prompt user to input data for selected feature."""
context.user_data[CURRENT_FEATURE] = update.callback_query.data
text = 'Okay, tell me.'
@@ -264,7 +264,7 @@ def ask_for_input(update: Update, context: CallbackContext) -> None:
return TYPING
def save_input(update: Update, context: CallbackContext) -> None:
def save_input(update: Update, context: CallbackContext) -> str:
"""Save input for feature and return to feature selection."""
user_data = context.user_data
user_data[FEATURES][user_data[CURRENT_FEATURE]] = update.message.text
@@ -274,7 +274,7 @@ def save_input(update: Update, context: CallbackContext) -> None:
return select_feature(update, context)
def end_describing(update: Update, context: CallbackContext) -> None:
def end_describing(update: Update, context: CallbackContext) -> int:
"""End gathering of features and return to parent conversation."""
user_data = context.user_data
level = user_data[CURRENT_LEVEL]
@@ -292,14 +292,14 @@ def end_describing(update: Update, context: CallbackContext) -> None:
return END
def stop_nested(update: Update, context: CallbackContext) -> None:
def stop_nested(update: Update, _: CallbackContext) -> str:
"""Completely end conversation from within nested conversation."""
update.message.reply_text('Okay, bye.')
return STOPPING
def main():
def main() -> None:
# Create the Updater and pass it your bot's token.
updater = Updater("TOKEN")
+12 -13
View File
@@ -1,6 +1,5 @@
#!/usr/bin/env python
# pylint: disable=W0613, C0116
# type: ignore[union-attr]
# pylint: disable=C0116
# This program is dedicated to the public domain under the CC0 license.
"""
@@ -24,7 +23,7 @@ logging.basicConfig(
logger = logging.getLogger(__name__)
def msg(update: Update, context: CallbackContext) -> None:
def msg(update: Update, _: CallbackContext) -> None:
# If we received any passport data
passport_data = update.message.passport_data
if passport_data:
@@ -65,19 +64,19 @@ def msg(update: Update, context: CallbackContext) -> None:
actual_file.download()
if data.type in ('passport', 'driver_license', 'identity_card', 'internal_passport'):
if data.front_side:
file = data.front_side.get_file()
print(data.type, file)
file.download()
front_file = data.front_side.get_file()
print(data.type, front_file)
front_file.download()
if data.type in ('driver_license' and 'identity_card'):
if data.reverse_side:
file = data.reverse_side.get_file()
print(data.type, file)
file.download()
reverse_file = data.reverse_side.get_file()
print(data.type, reverse_file)
reverse_file.download()
if data.type in ('passport', 'driver_license', 'identity_card', 'internal_passport'):
if data.selfie:
file = data.selfie.get_file()
print(data.type, file)
file.download()
selfie_file = data.selfie.get_file()
print(data.type, selfie_file)
selfie_file.download()
if data.type in (
'passport',
'driver_license',
@@ -96,7 +95,7 @@ def msg(update: Update, context: CallbackContext) -> None:
actual_file.download()
def main():
def main() -> None:
"""Start the bot."""
# Create the Updater and pass it your token and private key
updater = Updater("TOKEN", private_key=open('private.key', 'rb').read())
+6 -7
View File
@@ -1,6 +1,5 @@
#!/usr/bin/env python
# pylint: disable=W0613, C0116
# type: ignore[union-attr]
# pylint: disable=C0116
# This program is dedicated to the public domain under the CC0 license.
"""
@@ -28,7 +27,7 @@ logging.basicConfig(
logger = logging.getLogger(__name__)
def start_callback(update: Update, context: CallbackContext) -> None:
def start_callback(update: Update, _: CallbackContext) -> None:
msg = "Use /shipping to get an invoice for shipping-payment, "
msg += "or /noshipping for an invoice without shipping."
update.message.reply_text(msg)
@@ -91,7 +90,7 @@ def start_without_shipping_callback(update: Update, context: CallbackContext) ->
)
def shipping_callback(update: Update, context: CallbackContext) -> None:
def shipping_callback(update: Update, _: CallbackContext) -> None:
query = update.shipping_query
# check the payload, is this from your bot?
if query.invoice_payload != 'Custom-Payload':
@@ -109,7 +108,7 @@ def shipping_callback(update: Update, context: CallbackContext) -> None:
# after (optional) shipping, it's the pre-checkout
def precheckout_callback(update: Update, context: CallbackContext) -> None:
def precheckout_callback(update: Update, _: CallbackContext) -> None:
query = update.pre_checkout_query
# check the payload, is this from your bot?
if query.invoice_payload != 'Custom-Payload':
@@ -120,12 +119,12 @@ def precheckout_callback(update: Update, context: CallbackContext) -> None:
# finally, after contacting the payment provider...
def successful_payment_callback(update: Update, context: CallbackContext) -> None:
def successful_payment_callback(update: Update, _: CallbackContext) -> None:
# do something after successfully receiving payment?
update.message.reply_text("Thank you for your payment!")
def main():
def main() -> None:
# Create the Updater and pass it your bot's token.
updater = Updater("TOKEN")
+14 -14
View File
@@ -1,6 +1,5 @@
#!/usr/bin/env python
# pylint: disable=W0613, C0116, C0103
# type: ignore[union-attr]
# pylint: disable=C0116
# This program is dedicated to the public domain under the CC0 license.
"""
@@ -16,6 +15,7 @@ bot.
"""
import logging
from typing import Dict
from telegram import ReplyKeyboardMarkup, Update
from telegram.ext import (
@@ -46,7 +46,7 @@ reply_keyboard = [
markup = ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True)
def facts_to_str(user_data):
def facts_to_str(user_data: Dict[str, str]) -> str:
facts = list()
for key, value in user_data.items():
@@ -55,7 +55,7 @@ def facts_to_str(user_data):
return "\n".join(facts).join(['\n', '\n'])
def start(update: Update, context: CallbackContext) -> None:
def start(update: Update, context: CallbackContext) -> int:
reply_text = "Hi! My name is Doctor Botter."
if context.user_data:
reply_text += (
@@ -72,7 +72,7 @@ def start(update: Update, context: CallbackContext) -> None:
return CHOOSING
def regular_choice(update: Update, context: CallbackContext) -> None:
def regular_choice(update: Update, context: CallbackContext) -> int:
text = update.message.text.lower()
context.user_data['choice'] = text
if context.user_data.get(text):
@@ -86,7 +86,7 @@ def regular_choice(update: Update, context: CallbackContext) -> None:
return TYPING_REPLY
def custom_choice(update: Update, context: CallbackContext) -> None:
def custom_choice(update: Update, _: CallbackContext) -> int:
update.message.reply_text(
'Alright, please send me the category first, ' 'for example "Most impressive skill"'
)
@@ -94,7 +94,7 @@ def custom_choice(update: Update, context: CallbackContext) -> None:
return TYPING_CHOICE
def received_information(update: Update, context: CallbackContext) -> None:
def received_information(update: Update, context: CallbackContext) -> int:
text = update.message.text
category = context.user_data['choice']
context.user_data[category] = text.lower()
@@ -117,7 +117,7 @@ def show_data(update: Update, context: CallbackContext) -> None:
)
def done(update: Update, context: CallbackContext) -> None:
def done(update: Update, context: CallbackContext) -> int:
if 'choice' in context.user_data:
del context.user_data['choice']
@@ -127,13 +127,13 @@ def done(update: Update, context: CallbackContext) -> None:
return ConversationHandler.END
def main():
def main() -> None:
# Create the Updater and pass it your bot's token.
pp = PicklePersistence(filename='conversationbot')
updater = Updater("TOKEN", persistence=pp)
persistence = PicklePersistence(filename='conversationbot')
updater = Updater("TOKEN", persistence=persistence)
# Get the dispatcher to register handlers
dp = updater.dispatcher
dispatcher = updater.dispatcher
# Add conversation handler with the states CHOOSING, TYPING_CHOICE and TYPING_REPLY
conv_handler = ConversationHandler(
@@ -162,10 +162,10 @@ def main():
persistent=True,
)
dp.add_handler(conv_handler)
dispatcher.add_handler(conv_handler)
show_data_handler = CommandHandler('show_data', show_data)
dp.add_handler(show_data_handler)
dispatcher.add_handler(show_data_handler)
# Start the Bot
updater.start_polling()
+5 -6
View File
@@ -1,6 +1,5 @@
#!/usr/bin/env python
# pylint: disable=W0613, C0116
# type: ignore[union-attr]
# pylint: disable=C0116
# This program is dedicated to the public domain under the CC0 license.
"""
@@ -35,7 +34,7 @@ logging.basicConfig(
logger = logging.getLogger(__name__)
def start(update: Update, context: CallbackContext) -> None:
def start(update: Update, _: CallbackContext) -> None:
"""Inform user about what this bot can do"""
update.message.reply_text(
'Please select /poll to get a Poll, /quiz to get a Quiz or /preview'
@@ -121,7 +120,7 @@ def receive_quiz_answer(update: Update, context: CallbackContext) -> None:
context.bot.stop_poll(quiz_data["chat_id"], quiz_data["message_id"])
def preview(update: Update, context: CallbackContext) -> None:
def preview(update: Update, _: CallbackContext) -> None:
"""Ask user to create a poll and display a preview of it"""
# using this without a type lets the user chooses what he wants (quiz or poll)
button = [[KeyboardButton("Press me!", request_poll=KeyboardButtonPollType())]]
@@ -132,7 +131,7 @@ def preview(update: Update, context: CallbackContext) -> None:
)
def receive_poll(update: Update, context: CallbackContext) -> None:
def receive_poll(update: Update, _: CallbackContext) -> None:
"""On receiving polls, reply to it by a closed poll copying the received poll"""
actual_poll = update.effective_message.poll
# Only need to set the question and options, since all other parameters don't matter for
@@ -146,7 +145,7 @@ def receive_poll(update: Update, context: CallbackContext) -> None:
)
def help_handler(update: Update, context: CallbackContext) -> None:
def help_handler(update: Update, _: CallbackContext) -> None:
"""Display a help message"""
update.message.reply_text("Use /quiz, /poll or /preview to test this bot.")
+1 -1
View File
@@ -39,7 +39,7 @@ def main() -> NoReturn:
sleep(1)
except Unauthorized:
# The user has removed or blocked the bot.
UPDATE_ID += 1 # type: ignore[operator]
UPDATE_ID += 1
def echo(bot: telegram.Bot) -> None:
+5 -6
View File
@@ -1,6 +1,5 @@
#!/usr/bin/env python
# pylint: disable=W0613, C0116
# type: ignore[union-attr]
# pylint: disable=C0116
# This program is dedicated to the public domain under the CC0 license.
"""
@@ -34,17 +33,17 @@ logger = logging.getLogger(__name__)
# Define a few command handlers. These usually take the two arguments update and
# context. Error handlers also receive the raised TelegramError object in error.
def start(update: Update, context: CallbackContext) -> None:
def start(update: Update, _: CallbackContext) -> None:
update.message.reply_text('Hi! Use /set <seconds> to set a timer')
def alarm(context):
def alarm(context: CallbackContext) -> None:
"""Send the alarm message."""
job = context.job
context.bot.send_message(job.context, text='Beep!')
def remove_job_if_exists(name, context):
def remove_job_if_exists(name: str, context: CallbackContext) -> bool:
"""Remove job with given name. Returns whether job was removed."""
current_jobs = context.job_queue.get_jobs_by_name(name)
if not current_jobs:
@@ -84,7 +83,7 @@ def unset(update: Update, context: CallbackContext) -> None:
update.message.reply_text(text)
def main():
def main() -> None:
"""Run bot."""
# Create the Updater and pass it your bot's token.
updater = Updater("TOKEN")
+3 -3
View File
@@ -2,11 +2,11 @@
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
# Make sure that the versions specified here match the pre-commit settings!
black==20.8b1
flake8==3.8.4
pylint==2.6.0
mypy==0.800
pylint==2.7.2
mypy==0.812
pyupgrade==2.10.0
pytest==6.2.2
+2
View File
@@ -1,3 +1,5 @@
# Make sure to install those as additional_dependencies in the
# pre-commit hooks for pylint & mypy
certifi
# only telegram.ext: # Keep this line here; used in setup(-raw).py
tornado>=5.1
+4
View File
@@ -61,6 +61,10 @@ ignore_errors = True
[mypy-telegram.callbackquery,telegram.chat,telegram.message,telegram.user,telegram.files.*,telegram.inline.inlinequery,telegram.payment.precheckoutquery,telegram.payment.shippingquery,telegram.passport.passportdata,telegram.passport.credentials,telegram.passport.passportfile,telegram.ext.filters]
strict_optional = False
# type hinting for asyncio in webhookhandler is a bit tricky because it depends on the OS
[mypy-telegram.ext.utils.webhookhandler]
warn_unused_ignores = False
[mypy-urllib3.*]
ignore_missing_imports = True
+12 -2
View File
@@ -24,7 +24,9 @@ from .user import User
from .files.chatphoto import ChatPhoto
from .chat import Chat
from .chatlocation import ChatLocation
from .chatinvitelink import ChatInviteLink
from .chatmember import ChatMember
from .chatmemberupdated import ChatMemberUpdated
from .chatpermissions import ChatPermissions
from .files.photosize import PhotoSize
from .files.audio import Audio
@@ -40,8 +42,8 @@ from .files.videonote import VideoNote
from .chataction import ChatAction
from .dice import Dice
from .userprofilephotos import UserProfilePhotos
from .keyboardbutton import KeyboardButton
from .keyboardbuttonpolltype import KeyboardButtonPollType
from .keyboardbutton import KeyboardButton
from .replymarkup import ReplyMarkup
from .replykeyboardmarkup import ReplyKeyboardMarkup
from .replykeyboardremove import ReplyKeyboardRemove
@@ -54,6 +56,7 @@ from .messageentity import MessageEntity
from .messageid import MessageId
from .games.game import Game
from .poll import Poll, PollOption, PollAnswer
from .voicechat import VoiceChatStarted, VoiceChatEnded, VoiceChatParticipantsInvited
from .loginurl import LoginUrl
from .proximityalerttriggered import ProximityAlertTriggered
from .games.callbackgame import CallbackGame
@@ -68,6 +71,7 @@ from .passport.encryptedpassportelement import EncryptedPassportElement
from .passport.passportdata import PassportData
from .inline.inlinekeyboardbutton import InlineKeyboardButton
from .inline.inlinekeyboardmarkup import InlineKeyboardMarkup
from .messageautodeletetimerchanged import MessageAutoDeleteTimerChanged
from .message import Message
from .callbackquery import CallbackQuery
from .choseninlineresult import ChosenInlineResult
@@ -144,7 +148,7 @@ from .passport.credentials import (
TelegramDecryptionError,
)
from .bot import Bot
from .version import __version__ # noqa: F401
from .version import __version__, bot_api_version # noqa: F401
__author__ = 'devs@python-telegram-bot.org'
@@ -157,8 +161,10 @@ __all__ = ( # Keep this alphabetically ordered
'CallbackQuery',
'Chat',
'ChatAction',
'ChatInviteLink',
'ChatLocation',
'ChatMember',
'ChatMemberUpdated',
'ChatPermissions',
'ChatPhoto',
'ChosenInlineResult',
@@ -226,6 +232,7 @@ __all__ = ( # Keep this alphabetically ordered
'MAX_MESSAGE_LENGTH',
'MaskPosition',
'Message',
'MessageAutoDeleteTimerChanged',
'MessageEntity',
'MessageId',
'OrderInfo',
@@ -272,5 +279,8 @@ __all__ = ( # Keep this alphabetically ordered
'Video',
'VideoNote',
'Voice',
'VoiceChatStarted',
'VoiceChatEnded',
'VoiceChatParticipantsInvited',
'WebhookInfo',
)
+3 -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/].
# pylint: disable=E0401, C0114
# pylint: disable=C0114
import subprocess
import sys
from typing import Optional
@@ -24,6 +24,7 @@ from typing import Optional
import certifi
from . import __version__ as telegram_ver
from .constants import BOT_API_VERSION
def _git_revision() -> Optional[str]:
@@ -39,6 +40,7 @@ def _git_revision() -> Optional[str]:
def print_ver_info() -> None:
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]
sys_version = sys.version.replace('\n', ' ')
print(f'Python {sys_version}')
+2 -4
View File
@@ -46,15 +46,13 @@ class TelegramObject:
@staticmethod
def parse_data(data: Optional[JSONDict]) -> Optional[JSONDict]:
if not data:
return None
return data.copy()
return None if data is None else data.copy()
@classmethod
def de_json(cls: Type[TO], data: Optional[JSONDict], bot: 'Bot') -> Optional[TO]:
data = cls.parse_data(data)
if not data:
if data is None:
return None
if cls == TelegramObject:
+364 -167
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -47,7 +47,7 @@ class CallbackQuery(TelegramObject):
considered equal, if their :attr:`id` is equal.
Note:
* In Python `from` is a reserved word, use `from_user` instead.
* In Python ``from`` is a reserved word, use ``from_user`` instead.
* Exactly one of the fields :attr:`data` or :attr:`game_short_name` will be present.
* After the user presses an inline button, Telegram clients will display a progress bar
until you call :attr:`answer`. It is, therefore, necessary to react
@@ -598,7 +598,7 @@ class CallbackQuery(TelegramObject):
parse_mode: ODVInput[str] = DEFAULT_NONE,
caption_entities: Union[Tuple['MessageEntity', ...], List['MessageEntity']] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
allow_sending_without_reply: DVInput[bool] = DEFAULT_NONE,
reply_markup: ReplyMarkup = None,
timeout: ODVInput[float] = DEFAULT_NONE,
+154 -31
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python
# pylint: disable=C0103,W0622
# pylint: disable=W0622
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2021
@@ -32,6 +32,7 @@ if TYPE_CHECKING:
from telegram import (
Bot,
ChatMember,
ChatInviteLink,
Message,
MessageId,
ReplyMarkup,
@@ -80,10 +81,8 @@ class Chat(TelegramObject):
:meth:`telegram.Bot.get_chat`.
description (:obj:`str`, optional): Description, for groups, supergroups and channel chats.
Returned only in :meth:`telegram.Bot.get_chat`.
invite_link (:obj:`str`, optional): Chat invite link, for groups, supergroups and channel
chats. Each administrator in a chat generates their own invite links, so the bot must
first generate the link using ``export_chat_invite_link()``. Returned only
in :meth:`telegram.Bot.get_chat`.
invite_link (:obj:`str`, optional): Primary invite link, for groups, supergroups and
channel. Returned only in :meth:`telegram.Bot.get_chat`.
pinned_message (:class:`telegram.Message`, optional): The most recent pinned message
(by sending date). Returned only in :meth:`telegram.Bot.get_chat`.
permissions (:class:`telegram.ChatPermissions`): Optional. Default chat member permissions,
@@ -91,6 +90,11 @@ class Chat(TelegramObject):
slow_mode_delay (:obj:`int`, optional): For supergroups, the minimum allowed delay between
consecutive messages sent by each unprivileged user.
Returned only in :meth:`telegram.Bot.get_chat`.
message_auto_delete_time (:obj:`int`, optional): The time after which all messages sent to
the chat will be automatically deleted; in seconds. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 13.4
bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods.
sticker_set_name (:obj:`str`, optional): For supergroups, name of group sticker set.
Returned only in :meth:`telegram.Bot.get_chat`.
@@ -114,7 +118,8 @@ class Chat(TelegramObject):
bio (:obj:`str`): Optional. Bio of the other party in a private chat. Returned only in
:meth:`telegram.Bot.get_chat`.
description (:obj:`str`): Optional. Description, for groups, supergroups and channel chats.
invite_link (:obj:`str`): Optional. Chat invite link, for supergroups and channel chats.
invite_link (:obj:`str`): Optional. Primary invite link, for groups, supergroups and
channel. Returned only in :meth:`telegram.Bot.get_chat`.
pinned_message (:class:`telegram.Message`): Optional. The most recent pinned message
(by sending date). Returned only in :meth:`telegram.Bot.get_chat`.
permissions (:class:`telegram.ChatPermissions`): Optional. Default chat member permissions,
@@ -122,6 +127,11 @@ class Chat(TelegramObject):
slow_mode_delay (:obj:`int`): Optional. For supergroups, the minimum allowed delay between
consecutive messages sent by each unprivileged user. Returned only in
:meth:`telegram.Bot.get_chat`.
message_auto_delete_time (:obj:`int`): Optional. The time after which all messages sent to
the chat will be automatically deleted; in seconds. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 13.4
sticker_set_name (:obj:`str`): Optional. For supergroups, name of Group sticker set.
can_set_sticker_set (:obj:`bool`): Optional. :obj:`True`, if the bot can change group the
sticker set.
@@ -162,10 +172,11 @@ class Chat(TelegramObject):
bio: str = None,
linked_chat_id: int = None,
location: ChatLocation = None,
message_auto_delete_time: int = None,
**_kwargs: Any,
):
# Required
self.id = int(id)
self.id = int(id) # pylint: disable=C0103
self.type = type
# Optionals
self.title = title
@@ -181,6 +192,9 @@ class Chat(TelegramObject):
self.pinned_message = pinned_message
self.permissions = permissions
self.slow_mode_delay = slow_mode_delay
self.message_auto_delete_time = (
int(message_auto_delete_time) if message_auto_delete_time is not None else None
)
self.sticker_set_name = sticker_set_name
self.can_set_sticker_set = can_set_sticker_set
self.linked_chat_id = linked_chat_id
@@ -216,7 +230,7 @@ class Chat(TelegramObject):
return None
@classmethod
def de_json(cls, data: JSONDict, bot: 'Bot') -> Optional['Chat']:
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Chat']:
data = cls.parse_data(data)
if not data:
@@ -320,6 +334,7 @@ class Chat(TelegramObject):
timeout: ODVInput[float] = DEFAULT_NONE,
until_date: Union[int, datetime] = None,
api_kwargs: JSONDict = None,
revoke_messages: bool = None,
) -> bool:
"""Shortcut for::
@@ -343,6 +358,7 @@ class Chat(TelegramObject):
timeout=timeout,
until_date=until_date,
api_kwargs=api_kwargs,
revoke_messages=revoke_messages,
)
def unban_member(
@@ -384,6 +400,8 @@ class Chat(TelegramObject):
timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
is_anonymous: bool = None,
can_manage_chat: bool = None,
can_manage_voice_chats: bool = None,
) -> bool:
"""Shortcut for::
@@ -412,6 +430,8 @@ class Chat(TelegramObject):
timeout=timeout,
api_kwargs=api_kwargs,
is_anonymous=is_anonymous,
can_manage_chat=can_manage_chat,
can_manage_voice_chats=can_manage_voice_chats,
)
def restrict_member(
@@ -496,7 +516,7 @@ class Chat(TelegramObject):
def pin_message(
self,
message_id: Union[str, int],
message_id: int,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
@@ -526,7 +546,7 @@ class Chat(TelegramObject):
self,
timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
message_id: Union[str, int] = None,
message_id: int = None,
) -> bool:
"""Shortcut for::
@@ -578,7 +598,7 @@ class Chat(TelegramObject):
parse_mode: ODVInput[str] = DEFAULT_NONE,
disable_web_page_preview: ODVInput[bool] = DEFAULT_NONE,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: 'ReplyMarkup' = None,
timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
@@ -615,7 +635,7 @@ class Chat(TelegramObject):
Union['InputMediaAudio', 'InputMediaDocument', 'InputMediaPhoto', 'InputMediaVideo']
],
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
timeout: DVInput[float] = DEFAULT_20,
api_kwargs: JSONDict = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -671,7 +691,7 @@ class Chat(TelegramObject):
photo: Union[FileInput, 'PhotoSize'],
caption: str = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: 'ReplyMarkup' = None,
timeout: DVInput[float] = DEFAULT_20,
parse_mode: ODVInput[str] = DEFAULT_NONE,
@@ -711,7 +731,7 @@ class Chat(TelegramObject):
first_name: str = None,
last_name: str = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: 'ReplyMarkup' = None,
timeout: ODVInput[float] = DEFAULT_NONE,
contact: 'Contact' = None,
@@ -752,7 +772,7 @@ class Chat(TelegramObject):
title: str = None,
caption: str = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: 'ReplyMarkup' = None,
timeout: DVInput[float] = DEFAULT_20,
parse_mode: ODVInput[str] = DEFAULT_NONE,
@@ -797,7 +817,7 @@ class Chat(TelegramObject):
filename: str = None,
caption: str = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: 'ReplyMarkup' = None,
timeout: DVInput[float] = DEFAULT_20,
parse_mode: ODVInput[str] = DEFAULT_NONE,
@@ -837,7 +857,7 @@ class Chat(TelegramObject):
def send_dice(
self,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: 'ReplyMarkup' = None,
timeout: ODVInput[float] = DEFAULT_NONE,
emoji: str = None,
@@ -869,7 +889,7 @@ class Chat(TelegramObject):
self,
game_short_name: str,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: 'InlineKeyboardMarkup' = None,
timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
@@ -915,7 +935,7 @@ class Chat(TelegramObject):
need_shipping_address: bool = None,
is_flexible: bool = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: 'InlineKeyboardMarkup' = None,
provider_data: Union[str, object] = None,
send_phone_number_to_provider: bool = None,
@@ -968,7 +988,7 @@ class Chat(TelegramObject):
latitude: float = None,
longitude: float = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: 'ReplyMarkup' = None,
timeout: ODVInput[float] = DEFAULT_NONE,
location: 'Location' = None,
@@ -1016,7 +1036,7 @@ class Chat(TelegramObject):
caption: str = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: 'ReplyMarkup' = None,
timeout: DVInput[float] = DEFAULT_20,
api_kwargs: JSONDict = None,
@@ -1057,7 +1077,7 @@ class Chat(TelegramObject):
self,
sticker: Union[FileInput, 'Sticker'],
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: 'ReplyMarkup' = None,
timeout: DVInput[float] = DEFAULT_20,
api_kwargs: JSONDict = None,
@@ -1092,7 +1112,7 @@ class Chat(TelegramObject):
address: str = None,
foursquare_id: str = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: 'ReplyMarkup' = None,
timeout: ODVInput[float] = DEFAULT_NONE,
venue: 'Venue' = None,
@@ -1137,7 +1157,7 @@ class Chat(TelegramObject):
duration: int = None,
caption: str = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: 'ReplyMarkup' = None,
timeout: DVInput[float] = DEFAULT_20,
width: int = None,
@@ -1186,7 +1206,7 @@ class Chat(TelegramObject):
duration: int = None,
length: int = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: 'ReplyMarkup' = None,
timeout: DVInput[float] = DEFAULT_20,
thumb: FileInput = None,
@@ -1225,7 +1245,7 @@ class Chat(TelegramObject):
duration: int = None,
caption: str = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: 'ReplyMarkup' = None,
timeout: DVInput[float] = DEFAULT_20,
parse_mode: ODVInput[str] = DEFAULT_NONE,
@@ -1271,7 +1291,7 @@ class Chat(TelegramObject):
correct_option_id: int = None,
is_closed: bool = None,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: 'ReplyMarkup' = None,
timeout: ODVInput[float] = DEFAULT_NONE,
explanation: str = None,
@@ -1317,12 +1337,12 @@ class Chat(TelegramObject):
def send_copy(
self,
from_chat_id: Union[str, int],
message_id: Union[str, int],
message_id: int,
caption: str = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
caption_entities: Union[Tuple['MessageEntity', ...], List['MessageEntity']] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
allow_sending_without_reply: DVInput[bool] = DEFAULT_NONE,
reply_markup: 'ReplyMarkup' = None,
timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1356,12 +1376,12 @@ class Chat(TelegramObject):
def copy_message(
self,
chat_id: Union[int, str],
message_id: Union[str, int],
message_id: int,
caption: str = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
caption_entities: Union[Tuple['MessageEntity', ...], List['MessageEntity']] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
allow_sending_without_reply: DVInput[bool] = DEFAULT_NONE,
reply_markup: 'ReplyMarkup' = None,
timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1391,3 +1411,106 @@ class Chat(TelegramObject):
timeout=timeout,
api_kwargs=api_kwargs,
)
def export_invite_link(
self,
timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
) -> str:
"""Shortcut for::
bot.export_chat_invite_link(chat_id=update.effective_chat.id, *args, **kwargs)
For the documentation of the arguments, please see
:meth:`telegram.Bot.export_chat_invite_link`.
.. versionadded:: 13.4
Returns:
:obj:`str`: New invite link on success.
"""
return self.bot.export_chat_invite_link(
chat_id=self.id, timeout=timeout, api_kwargs=api_kwargs
)
def create_invite_link(
self,
expire_date: Union[int, datetime] = None,
member_limit: int = None,
timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
) -> 'ChatInviteLink':
"""Shortcut for::
bot.create_chat_invite_link(chat_id=update.effective_chat.id, *args, **kwargs)
For the documentation of the arguments, please see
:meth:`telegram.Bot.create_chat_invite_link`.
.. versionadded:: 13.4
Returns:
:class:`telegram.ChatInviteLink`
"""
return self.bot.create_chat_invite_link(
chat_id=self.id,
expire_date=expire_date,
member_limit=member_limit,
timeout=timeout,
api_kwargs=api_kwargs,
)
def edit_invite_link(
self,
invite_link: str,
expire_date: Union[int, datetime] = None,
member_limit: int = None,
timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
) -> 'ChatInviteLink':
"""Shortcut for::
bot.edit_chat_invite_link(chat_id=update.effective_chat.id, *args, **kwargs)
For the documentation of the arguments, please see
:meth:`telegram.Bot.edit_chat_invite_link`.
.. versionadded:: 13.4
Returns:
:class:`telegram.ChatInviteLink`
"""
return self.bot.edit_chat_invite_link(
chat_id=self.id,
invite_link=invite_link,
expire_date=expire_date,
member_limit=member_limit,
timeout=timeout,
api_kwargs=api_kwargs,
)
def revoke_invite_link(
self,
invite_link: str,
timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
) -> 'ChatInviteLink':
"""Shortcut for::
bot.revoke_chat_invite_link(chat_id=update.effective_chat.id, *args, **kwargs)
For the documentation of the arguments, please see
:meth:`telegram.Bot.revoke_chat_invite_link`.
.. versionadded:: 13.4
Returns:
:class:`telegram.ChatInviteLink`
"""
return self.bot.revoke_chat_invite_link(
chat_id=self.id, invite_link=invite_link, timeout=timeout, api_kwargs=api_kwargs
)
+102
View File
@@ -0,0 +1,102 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2021
# 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 an invite link for a chat."""
import datetime
from typing import TYPE_CHECKING, Any, Optional
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 ChatInviteLink(TelegramObject):
"""This object represents an invite link for a chat.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`invite_link`, :attr:`creator`, :attr:`is_primary` and
:attr:`is_revoked` are equal.
.. versionadded:: 13.4
Args:
invite_link (:obj:`str`): The invite link.
creator (:class:`telegram.User`): Creator of the link.
is_primary (:obj:`bool`): :obj:`True`, if the link is primary.
is_revoked (:obj:`bool`): :obj:`True`, if the link is revoked.
expire_date (:class:`datetime.datetime`, optional): Date when the link will expire or
has been expired.
member_limit (:obj:`int`, optional): Maximum number of users that can be members of the
chat simultaneously after joining the chat via this invite link; 1-99999.
Attributes:
invite_link (:obj:`str`): The invite link. If the link was created by another chat
administrator, then the second part of the link will be replaced with ``''``.
creator (:class:`telegram.User`): Creator of the link.
is_primary (:obj:`bool`): :obj:`True`, if the link is primary.
is_revoked (:obj:`bool`): :obj:`True`, if the link is revoked.
expire_date (:class:`datetime.datetime`): Optional. Date when the link will expire or
has been expired.
member_limit (:obj:`int`): Optional. Maximum number of users that can be members
of the chat simultaneously after joining the chat via this invite link; 1-99999.
"""
def __init__(
self,
invite_link: str,
creator: User,
is_primary: bool,
is_revoked: bool,
expire_date: datetime.datetime = None,
member_limit: int = None,
**_kwargs: Any,
):
# Required
self.invite_link = invite_link
self.creator = creator
self.is_primary = is_primary
self.is_revoked = is_revoked
# Optionals
self.expire_date = expire_date
self.member_limit = int(member_limit) if member_limit is not None else None
self._id_attrs = (self.invite_link, self.creator, self.is_primary, self.is_revoked)
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['ChatInviteLink']:
data = cls.parse_data(data)
if not data:
return None
data['creator'] = User.de_json(data.get('creator'), bot)
data['expire_date'] = from_timestamp(data.get('expire_date', None))
return cls(**data)
def to_dict(self) -> JSONDict:
data = super().to_dict()
data['expire_date'] = to_timestamp(self.expire_date)
return data
+27
View File
@@ -46,6 +46,18 @@ class ChatMember(TelegramObject):
restrictions will be lifted for this user.
can_be_edited (:obj:`bool`, optional): Administrators only. :obj:`True`, if the bot is
allowed to edit administrator privileges of that user.
can_manage_chat (:obj:`bool`, optional): Administrators only. :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.
.. versionadded:: 13.4
can_manage_voice_chats (:obj:`bool`, optional): Administrators only. :obj:`True`, if the
administrator can manage voice chats.
.. versionadded:: 13.4
can_change_info (:obj:`bool`, optional): Administrators and restricted only. :obj:`True`,
if the user can change the chat title, photo and other settings.
can_post_messages (:obj:`bool`, optional): Administrators only. :obj:`True`, if the
@@ -87,6 +99,17 @@ class ChatMember(TelegramObject):
for this user.
can_be_edited (:obj:`bool`): Optional. If the bot is allowed to edit administrator
privileges of that user.
can_manage_chat (:obj:`bool`): Optional. 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.
.. versionadded:: 13.4
can_manage_voice_chats (:obj:`bool`): Optional. if the administrator can manage
voice chats.
.. versionadded:: 13.4
can_change_info (:obj:`bool`): Optional. If the user can change the chat title, photo and
other settings.
can_post_messages (:obj:`bool`): Optional. If the administrator can post in the channel.
@@ -150,6 +173,8 @@ class ChatMember(TelegramObject):
is_member: bool = None,
custom_title: str = None,
is_anonymous: bool = None,
can_manage_chat: bool = None,
can_manage_voice_chats: bool = None,
**_kwargs: Any,
):
# Required
@@ -175,6 +200,8 @@ class ChatMember(TelegramObject):
self.can_send_other_messages = can_send_other_messages
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
self._id_attrs = (self.user, self.status)
+115
View File
@@ -0,0 +1,115 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2021
# 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 ChatMemberUpdated."""
import datetime
from typing import TYPE_CHECKING, Any, Optional
from telegram import TelegramObject, User, Chat, ChatMember, ChatInviteLink
from telegram.utils.helpers import from_timestamp, to_timestamp
from telegram.utils.types import JSONDict
if TYPE_CHECKING:
from telegram import Bot
class ChatMemberUpdated(TelegramObject):
"""This object represents changes in the status of a chat member.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`chat`, :attr:`from_user`, :attr:`date`,
:attr:`old_chat_member` and :attr:`new_chat_member` are equal.
.. versionadded:: 13.4
Note:
In Python ``from`` is a reserved word, use ``from_user`` instead.
Args:
chat (:class:`telegram.Chat`): Chat the user belongs to.
from_user (:class:`telegram.User`): Performer of the action, which resulted in the change.
date (:class:`datetime.datetime`): Date the change was done in Unix time. Converted to
:class:`datetime.datetime`.
old_chat_member (:class:`telegram.ChatMember`): Previous information about the chat member.
new_chat_member (:class:`telegram.ChatMember`): New information about the chat member.
invite_link (:class:`telegram.ChatInviteLink`, optional): Chat invite link, which was used
by the user to join the chat. For joining by invite link events only.
Attributes:
chat (:class:`telegram.Chat`): Chat the user belongs to.
from_user (:class:`telegram.User`): Performer of the action, which resulted in the change.
date (:class:`datetime.datetime`): Date the change was done in Unix time. Converted to
:class:`datetime.datetime`.
old_chat_member (:class:`telegram.ChatMember`): Previous information about the chat member.
new_chat_member (:class:`telegram.ChatMember`): New information about the chat member.
invite_link (:class:`telegram.ChatInviteLink`): Optional. Chat invite link, which was used
by the user to join the chat.
"""
def __init__(
self,
chat: Chat,
from_user: User,
date: datetime.datetime,
old_chat_member: ChatMember,
new_chat_member: ChatMember,
invite_link: ChatInviteLink = None,
**_kwargs: Any,
):
# Required
self.chat = chat
self.from_user = from_user
self.date = date
self.old_chat_member = old_chat_member
self.new_chat_member = new_chat_member
# Optionals
self.invite_link = invite_link
self._id_attrs = (
self.chat,
self.from_user,
self.date,
self.old_chat_member,
self.new_chat_member,
)
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['ChatMemberUpdated']:
data = cls.parse_data(data)
if not data:
return None
data['chat'] = Chat.de_json(data.get('chat'), bot)
data['from_user'] = User.de_json(data.get('from'), bot)
data['date'] = from_timestamp(data.get('date'))
data['old_chat_member'] = ChatMember.de_json(data.get('old_chat_member'), bot)
data['new_chat_member'] = ChatMember.de_json(data.get('new_chat_member'), bot)
data['invite_link'] = ChatInviteLink.de_json(data.get('invite_link'), bot)
return cls(**data)
def to_dict(self) -> JSONDict:
data = super().to_dict()
# Required
data['date'] = to_timestamp(self.date)
return data
+1 -1
View File
@@ -37,7 +37,7 @@ class ChosenInlineResult(TelegramObject):
considered equal, if their :attr:`result_id` is equal.
Note:
* In Python `from` is a reserved word, use `from_user` instead.
* In Python ``from`` is a reserved word, use ``from_user`` instead.
* It is necessary to enable inline feedback via `@Botfather <https://t.me/BotFather>`_ in
order to receive these objects in updates.
+13
View File
@@ -21,6 +21,10 @@ The following constants were extracted from the
`Telegram Bots API <https://core.telegram.org/bots/api>`_.
Attributes:
BOT_API_VERSION (:obj:`str`): `5.1`. Telegram Bot API version supported by this
version of `python-telegram-bot`. Also available as ``telegram.bot_api_version``.
.. versionadded:: 13.4
MAX_MESSAGE_LENGTH (:obj:`int`): 4096
MAX_CAPTION_LENGTH (:obj:`int`): 1024
SUPPORTED_WEBHOOK_PORTS (List[:obj:`int`]): [443, 80, 88, 8443]
@@ -88,8 +92,14 @@ Attributes:
DICE_BASKETBALL (:obj:`str`): '🏀'
DICE_FOOTBALL (:obj:`str`): ''
DICE_SLOT_MACHINE (:obj:`str`): '🎰'
DICE_BOWLING (:obj:`str`): '🎳'
.. versionadded:: 13.4
DICE_ALL_EMOJI (List[:obj:`str`]): List of all supported base emoji.
.. versionchanged:: 13.4
Added :attr:`DICE_BOWLING`
:class:`telegram.MessageEntity`:
Attributes:
@@ -136,6 +146,7 @@ Attributes:
"""
from typing import List
BOT_API_VERSION: str = '5.1'
MAX_MESSAGE_LENGTH: int = 4096
MAX_CAPTION_LENGTH: int = 1024
ANONYMOUS_ADMIN_ID: int = 1087968824
@@ -182,12 +193,14 @@ DICE_DARTS: str = '🎯'
DICE_BASKETBALL: str = '🏀'
DICE_FOOTBALL: str = ''
DICE_SLOT_MACHINE: str = '🎰'
DICE_BOWLING: str = '🎳'
DICE_ALL_EMOJI: List[str] = [
DICE_DICE,
DICE_DARTS,
DICE_BASKETBALL,
DICE_FOOTBALL,
DICE_SLOT_MACHINE,
DICE_BOWLING,
]
MESSAGEENTITY_MENTION: str = 'mention'
+12 -2
View File
@@ -45,13 +45,17 @@ class Dice(TelegramObject):
3 indicates that the goal was missed. However, this behaviour is undocumented and might
be changed by Telegram.
If :attr:`emoji` is "🎳", a value of 6 knocks all the pins, while a value of 1 means all
the pins were missed. However, this behaviour is undocumented and might be changed by
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.
Args:
value (:obj:`int`): Value of the dice. 1-6 for dice and darts, 1-5 for basketball and
football/soccer ball, 1-64 for slot machine.
value (:obj:`int`): Value of the dice. 1-6 for dice, darts and bowling balls, 1-5 for
basketball and football/soccer ball, 1-64 for slot machine.
emoji (:obj:`str`): Emoji on which the dice throw animation is based.
Attributes:
@@ -76,5 +80,11 @@ class Dice(TelegramObject):
""":const:`telegram.constants.DICE_FOOTBALL`"""
SLOT_MACHINE: ClassVar[str] = constants.DICE_SLOT_MACHINE
""":const:`telegram.constants.DICE_SLOT_MACHINE`"""
BOWLING: ClassVar[str] = constants.DICE_BOWLING
"""
:const:`telegram.constants.DICE_BOWLING`
.. versionadded:: 13.4
"""
ALL_EMOJI: ClassVar[List[str]] = constants.DICE_ALL_EMOJI
""":const:`telegram.constants.DICE_ALL_EMOJI`"""
+2
View File
@@ -43,6 +43,7 @@ from .messagequeue import MessageQueue
from .messagequeue import DelayQueue
from .pollanswerhandler import PollAnswerHandler
from .pollhandler import PollHandler
from .chatmemberhandler import ChatMemberHandler
from .defaults import Defaults
__all__ = (
@@ -78,5 +79,6 @@ __all__ = (
'PrefixHandler',
'PollAnswerHandler',
'PollHandler',
'ChatMemberHandler',
'Defaults',
)
+146
View File
@@ -0,0 +1,146 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2019-2021
# 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 ChatMemberHandler classes."""
from typing import ClassVar, TypeVar, Union, Callable, TYPE_CHECKING
from telegram import Update
from telegram.utils.helpers import DefaultValue, DEFAULT_FALSE
from .handler import Handler
if TYPE_CHECKING:
from telegram.ext import CallbackContext
RT = TypeVar('RT')
class ChatMemberHandler(Handler[Update]):
"""Handler class to handle Telegram updates that contain a chat member update.
.. versionadded:: 13.4
Note:
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you
can use to keep any data in will be sent to the :attr:`callback` function. Related to
either the user or the chat that the update was sent in. For each update from the same 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.
Warning:
When setting ``run_async`` to :obj:`True`, you cannot rely on adding custom
attributes to :class:`telegram.ext.CallbackContext`. See its docs for more info.
Args:
callback (:obj:`callable`): The callback function for this handler. Will be called when
:attr:`check_update` has determined that an update should be processed by this handler.
Callback signature for context based API:
``def callback(update: Update, context: CallbackContext)``
The return value of the callback is usually ignored except for the special case of
:class:`telegram.ext.ConversationHandler`.
chat_member_types (:obj:`int`, optional): Pass one of :attr:`MY_CHAT_MEMBER`,
:attr:`CHAT_MEMBER` or :attr:`ANY_CHAT_MEMBER` to specify if this handler should handle
only updates with :attr:`telegram.Update.my_chat_member`,
:attr:`telegram.Update.chat_member` or both. Defaults to :attr:`MY_CHAT_MEMBER`.
pass_update_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
``update_queue`` will be passed to the callback function. It will be the ``Queue``
instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher`
that contains new updates which can be used to insert updates. Default is :obj:`False`.
DEPRECATED: Please switch to context based callbacks.
pass_job_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
``job_queue`` will be passed to the callback function. It will be a
:class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater`
which can be used to schedule new jobs. Default is :obj:`False`.
DEPRECATED: Please switch to context based callbacks.
pass_user_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
``user_data`` will be passed to the callback function. Default is :obj:`False`.
DEPRECATED: Please switch to context based callbacks.
pass_chat_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
``chat_data`` will be passed to the callback function. Default is :obj:`False`.
DEPRECATED: Please switch to context based callbacks.
run_async (:obj:`bool`): Determines whether the callback will run asynchronously.
Defaults to :obj:`False`.
Attributes:
callback (:obj:`callable`): The callback function for this handler.
chat_member_types (:obj:`int`, optional): Specifies if this handler should handle
only updates with :attr:`telegram.Update.my_chat_member`,
:attr:`telegram.Update.chat_member` or both.
pass_update_queue (:obj:`bool`): Determines whether ``update_queue`` will be
passed to the callback function.
pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to
the callback function.
pass_user_data (:obj:`bool`): Determines whether ``user_data`` will be passed to
the callback function.
pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to
the callback function.
run_async (:obj:`bool`): Determines whether the callback will run asynchronously.
"""
MY_CHAT_MEMBER: ClassVar[int] = -1
""":obj:`int`: Used as a constant to handle only :attr:`telegram.Update.my_chat_member`."""
CHAT_MEMBER: ClassVar[int] = 0
""":obj:`int`: Used as a constant to handle only :attr:`telegram.Update.chat_member`."""
ANY_CHAT_MEMBER: ClassVar[int] = 1
""":obj:`int`: Used as a constant to handle bot :attr:`telegram.Update.my_chat_member`
and :attr:`telegram.Update.chat_member`."""
def __init__(
self,
callback: Callable[[Update, 'CallbackContext'], RT],
chat_member_types: int = MY_CHAT_MEMBER,
pass_update_queue: bool = False,
pass_job_queue: bool = False,
pass_user_data: bool = False,
pass_chat_data: bool = False,
run_async: Union[bool, DefaultValue] = DEFAULT_FALSE,
):
super().__init__(
callback,
pass_update_queue=pass_update_queue,
pass_job_queue=pass_job_queue,
pass_user_data=pass_user_data,
pass_chat_data=pass_chat_data,
run_async=run_async,
)
self.chat_member_types = chat_member_types
def check_update(self, update: object) -> bool:
"""Determines whether an update should be passed to this handlers :attr:`callback`.
Args:
update (:class:`telegram.Update` | :obj:`object`): Incoming update.
Returns:
:obj:`bool`
"""
if isinstance(update, Update):
if not (update.my_chat_member or update.chat_member):
return False
if self.chat_member_types == self.ANY_CHAT_MEMBER:
return True
if self.chat_member_types == self.CHAT_MEMBER:
return bool(update.chat_member)
return bool(update.my_chat_member)
return False
+1 -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/].
# pylint: disable=R0201, E0401
# pylint: disable=R0201
"""This module contains the class Defaults, which allows to pass default values to Updater."""
from typing import NoReturn, Optional, Dict, Any
+76 -7
View File
@@ -497,7 +497,7 @@ class Filters:
def filter(self, message: Message) -> bool:
return bool(
message.entities
and any([e.type == MessageEntity.BOT_COMMAND for e in message.entities])
and any(e.type == MessageEntity.BOT_COMMAND for e in message.entities)
)
def __call__( # type: ignore[override]
@@ -1005,6 +1005,15 @@ officedocument.wordprocessingml.document")``.
:attr: `telegram.Message.supergroup_chat_created` or
:attr: `telegram.Message.channel_chat_created`."""
class _MessageAutoDeleteTimerChanged(MessageFilter):
name = 'MessageAutoDeleteTimerChanged'
def filter(self, message: Message) -> bool:
return bool(message.message_auto_delete_timer_changed)
message_auto_delete_timer_changed = _MessageAutoDeleteTimerChanged()
"""Messages that contain :attr:`message_auto_delete_timer_changed`"""
class _Migrate(MessageFilter):
name = 'Filters.status_update.migrate'
@@ -1042,6 +1051,33 @@ officedocument.wordprocessingml.document")``.
proximity_alert_triggered = _ProximityAlertTriggered()
"""Messages that contain :attr:`telegram.Message.proximity_alert_triggered`."""
class _VoiceChatStarted(MessageFilter):
name = 'Filters.status_update.voice_chat_started'
def filter(self, message: Message) -> bool:
return bool(message.voice_chat_started)
voice_chat_started = _VoiceChatStarted()
"""Messages that contain :attr:`telegram.Message.voice_chat_started`."""
class _VoiceChatEnded(MessageFilter):
name = 'Filters.status_update.voice_chat_ended'
def filter(self, message: Message) -> bool:
return bool(message.voice_chat_ended)
voice_chat_ended = _VoiceChatEnded()
"""Messages that contain :attr:`telegram.Message.voice_chat_ended`."""
class _VoiceChatParticipantsInvited(MessageFilter):
name = 'Filters.status_update.voice_chat_participants_invited'
def filter(self, message: Message) -> bool:
return bool(message.voice_chat_participants_invited)
voice_chat_participants_invited = _VoiceChatParticipantsInvited()
"""Messages that contain :attr:`telegram.Message.voice_chat_participants_invited`."""
name = 'Filters.status_update'
def filter(self, message: Update) -> bool:
@@ -1052,10 +1088,14 @@ officedocument.wordprocessingml.document")``.
or self.new_chat_photo(message)
or self.delete_chat_photo(message)
or self.chat_created(message)
or self.message_auto_delete_timer_changed(message)
or self.migrate(message)
or self.pinned_message(message)
or self.connected_website(message)
or self.proximity_alert_triggered(message)
or self.voice_chat_started(message)
or self.voice_chat_ended(message)
or self.voice_chat_participants_invited(message)
)
status_update = _StatusUpdate()
@@ -1077,18 +1117,35 @@ officedocument.wordprocessingml.document")``.
left_chat_member: Messages that contain
:attr:`telegram.Message.left_chat_member`.
migrate: Messages that contain
:attr:`telegram.Message.migrate_from_chat_id` or
:attr: `telegram.Message.migrate_from_chat_id`.
:attr:`telegram.Message.migrate_to_chat_id` or
:attr:`telegram.Message.migrate_from_chat_id`.
new_chat_members: Messages that contain
:attr:`telegram.Message.new_chat_members`.
new_chat_photo: Messages that contain
:attr:`telegram.Message.new_chat_photo`.
new_chat_title: Messages that contain
:attr:`telegram.Message.new_chat_title`.
message_auto_delete_timer_changed: Messages that contain
:attr:`message_auto_delete_timer_changed`.
.. versionadded:: 13.4
pinned_message: Messages that contain
:attr:`telegram.Message.pinned_message`.
proximity_alert_triggered: Messages that contain
:attr:`telegram.Message.proximity_alert_triggered`.
voice_chat_started: Messages that contain
:attr:`telegram.Message.voice_chat_started`.
.. versionadded:: 13.4
voice_chat_ended: Messages that contain
:attr:`telegram.Message.voice_chat_ended`.
.. versionadded:: 13.4
voice_chat_participants_invited: Messages that contain
:attr:`telegram.Message.voice_chat_participants_invited`.
.. versionadded:: 13.4
"""
class _Forwarded(MessageFilter):
@@ -1681,16 +1738,22 @@ officedocument.wordprocessingml.document")``.
username.
Examples:
* To filter for messages forwarded from a channel with ID ``-1234``, use
``MessageHandler(Filters.sender_chat(-1234), callback_method)``.
* To filter for messages forwarded to a discussion group from a channel with ID
``-1234``, use ``MessageHandler(Filters.sender_chat(-1234), callback_method)``.
* To filter for messages of anonymous admins in a super group with username
``@anonymous``, use
``MessageHandler(Filters.sender_chat(username='anonymous'), callback_method)``.
* To filter for messages forwarded from *any* channel, use
* To filter for messages forwarded to a discussion group from *any* channel, use
``MessageHandler(Filters.sender_chat.channel, callback_method)``.
* To filter for messages of anonymous admins in *any* super group, use
``MessageHandler(Filters.sender_chat.super_group, callback_method)``.
Note:
Remember, ``sender_chat`` is also set for messages in a channel as the channel itself,
so when your bot is an admin in a channel and the linked discussion group, you would
receive the message twice (once from inside the channel, once inside the discussion
group).
Warning:
:attr:`chat_ids` will return a *copy* of the saved chat ids as :class:`frozenset`. This
is to ensure thread safety. To add/remove a chat, you should use :meth:`add_usernames`,
@@ -1831,6 +1894,7 @@ officedocument.wordprocessingml.document")``.
basketball = _DiceEmoji('🏀', 'basketball')
football = _DiceEmoji('')
slot_machine = _DiceEmoji('🎰')
bowling = _DiceEmoji('🎳', 'bowling')
dice = _Dice()
"""Dice Messages. If an integer or a list of integers is passed, it filters messages to only
@@ -1863,6 +1927,11 @@ officedocument.wordprocessingml.document")``.
as for :attr:`Filters.dice`.
slot_machine: Dice messages with the emoji 🎰. Passing a list of integers is supported just
as for :attr:`Filters.dice`.
bowling: Dice messages with the emoji 🎳. Passing a list of integers is supported just
as for :attr:`Filters.dice`.
.. versionadded:: 13.4
"""
class language(MessageFilter):
@@ -1896,7 +1965,7 @@ officedocument.wordprocessingml.document")``.
"""""" # remove method from docs
return bool(
message.from_user.language_code
and any([message.from_user.language_code.startswith(x) for x in self.lang])
and any(message.from_user.language_code.startswith(x) for x in self.lang)
)
class _UpdateType(UpdateFilter):
-1
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=E0401
"""This module contains the classes JobQueue and Job."""
import datetime
+112 -61
View File
@@ -244,36 +244,56 @@ class Updater:
self,
poll_interval: float = 0.0,
timeout: float = 10,
clean: bool = False,
clean: bool = None,
bootstrap_retries: int = -1,
read_latency: float = 2.0,
allowed_updates: List[str] = None,
drop_pending_updates: bool = None,
) -> Optional[Queue]:
"""Starts polling updates from Telegram.
Args:
poll_interval (:obj:`float`, optional): Time to wait between polling updates from
Telegram in seconds. Default is 0.0.
timeout (:obj:`float`, optional): Passed to :attr:`telegram.Bot.get_updates`.
clean (:obj:`bool`, optional): Whether to clean any pending updates on Telegram servers
before actually starting to poll. Default is :obj:`False`.
timeout (:obj:`float`, optional): Passed to :meth:`telegram.Bot.get_updates`.
drop_pending_updates (:obj:`bool`, optional): Whether to clean any pending updates on
Telegram servers before actually starting to poll. Default is :obj:`False`.
.. versionadded :: 13.4
clean (:obj:`bool`, optional): Alias for ``drop_pending_updates``.
.. deprecated:: 13.4
Use ``drop_pending_updates`` instead.
bootstrap_retries (:obj:`int`, optional): Whether the bootstrapping phase of the
`Updater` will retry on failures on the Telegram server.
:class:`telegram.ext.Updater` will retry on failures on the Telegram server.
* < 0 - retry indefinitely (default)
* 0 - no retries
* > 0 - retry up to X times
allowed_updates (List[:obj:`str`], optional): Passed to
:attr:`telegram.Bot.get_updates`.
:meth:`telegram.Bot.get_updates`.
read_latency (:obj:`float` | :obj:`int`, optional): Grace time in seconds for receiving
the reply from server. Will be added to the `timeout` value and used as the read
the reply from server. Will be added to the ``timeout`` value and used as the read
timeout from server (Default: 2).
Returns:
:obj:`Queue`: The update queue that can be filled from the main thread.
"""
if (clean is not None) and (drop_pending_updates is not None):
raise TypeError('`clean` and `drop_pending_updates` are mutually exclusive.')
if clean is not None:
warnings.warn(
'The argument `clean` of `start_polling` is deprecated. Please use '
'`drop_pending_updates` instead.',
category=TelegramDeprecationWarning,
stacklevel=2,
)
drop_pending_updates = drop_pending_updates if drop_pending_updates is not None else clean
with self.__lock:
if not self.running:
self.running = True
@@ -290,7 +310,7 @@ class Updater:
timeout,
read_latency,
bootstrap_retries,
clean,
drop_pending_updates,
allowed_updates,
ready=polling_ready,
)
@@ -310,18 +330,20 @@ class Updater:
url_path: str = '',
cert: str = None,
key: str = None,
clean: bool = False,
clean: bool = None,
bootstrap_retries: int = 0,
webhook_url: str = None,
allowed_updates: List[str] = None,
force_event_loop: bool = False,
drop_pending_updates: bool = None,
ip_address: str = None,
) -> Optional[Queue]:
"""
Starts a small http server to listen for updates via webhook. If cert
and key are not provided, the webhook will be started directly on
http://listen:port/url_path, so SSL can be handled by another
application. Else, the webhook will be started on
https://listen:port/url_path
https://listen:port/url_path. Also calls :meth:`telegram.Bot.set_webhook` as required.
Note:
Due to an incompatibility of the Tornado library PTB uses for the webhook with Python
@@ -338,19 +360,29 @@ class Updater:
url_path (:obj:`str`, optional): Path inside url.
cert (:obj:`str`, optional): Path to the SSL certificate file.
key (:obj:`str`, optional): Path to the SSL key file.
clean (:obj:`bool`, optional): Whether to clean any pending updates on Telegram servers
before actually starting the webhook. Default is :obj:`False`.
drop_pending_updates (:obj:`bool`, optional): Whether to clean any pending updates on
Telegram servers before actually starting to poll. Default is :obj:`False`.
.. versionadded :: 13.4
clean (:obj:`bool`, optional): Alias for ``drop_pending_updates``.
.. deprecated:: 13.4
Use ``drop_pending_updates`` instead.
bootstrap_retries (:obj:`int`, optional): Whether the bootstrapping phase of the
`Updater` will retry on failures on the Telegram server.
:class:`telegram.ext.Updater` will retry on failures on the Telegram server.
* < 0 - retry indefinitely (default)
* 0 - no retries
* > 0 - retry up to X times
webhook_url (:obj:`str`, optional): Explicitly specify the webhook url. Useful behind
NAT, reverse proxy, etc. Default is derived from `listen`, `port` & `url_path`.
NAT, reverse proxy, etc. Default is derived from ``listen``, ``port`` &
``url_path``.
ip_address (:obj:`str`, optional): Passed to :meth:`telegram.Bot.set_webhook`.
.. versionadded :: 13.4
allowed_updates (List[:obj:`str`], optional): Passed to
:attr:`telegram.Bot.set_webhook`.
:meth:`telegram.Bot.set_webhook`.
force_event_loop (:obj:`bool`, optional): Force using the current event loop. See above
note for details. Defaults to :obj:`False`
@@ -358,6 +390,19 @@ class Updater:
:obj:`Queue`: The update queue that can be filled from the main thread.
"""
if (clean is not None) and (drop_pending_updates is not None):
raise TypeError('`clean` and `drop_pending_updates` are mutually exclusive.')
if clean is not None:
warnings.warn(
'The argument `clean` of `start_webhook` is deprecated. Please use '
'`drop_pending_updates` instead.',
category=TelegramDeprecationWarning,
stacklevel=2,
)
drop_pending_updates = drop_pending_updates if drop_pending_updates is not None else clean
with self.__lock:
if not self.running:
self.running = True
@@ -376,11 +421,12 @@ class Updater:
cert,
key,
bootstrap_retries,
clean,
drop_pending_updates,
webhook_url,
allowed_updates,
ready=webhook_ready,
force_event_loop=force_event_loop,
ip_address=ip_address,
)
self.logger.debug('Waiting for Dispatcher and Webhook to start')
@@ -398,7 +444,7 @@ class Updater:
timeout,
read_latency,
bootstrap_retries,
clean,
drop_pending_updates,
allowed_updates,
ready=None,
): # pragma: no cover
@@ -408,7 +454,12 @@ class Updater:
self.logger.debug('Updater thread started (polling)')
self._bootstrap(bootstrap_retries, clean=clean, webhook_url='', allowed_updates=None)
self._bootstrap(
bootstrap_retries,
drop_pending_updates=drop_pending_updates,
webhook_url='',
allowed_updates=None,
)
self.logger.debug('Bootstrap done')
@@ -504,14 +555,20 @@ class Updater:
cert,
key,
bootstrap_retries,
clean,
drop_pending_updates,
webhook_url,
allowed_updates,
ready=None,
force_event_loop=False,
ip_address=None,
):
self.logger.debug('Updater thread started (webhook)')
# Note that we only use the SSL certificate for the WebhookServer, if the key is also
# present. This is because the WebhookServer may not actually be in charge of performing
# the SSL handshake, e.g. in case a reverse proxy is used
use_ssl = cert is not None and key is not None
if not url_path.startswith('/'):
url_path = f'/{url_path}'
@@ -532,23 +589,18 @@ class Updater:
# Create and start server
self.httpd = WebhookServer(listen, port, app, ssl_ctx)
if use_ssl:
# DO NOT CHANGE: Only set webhook if SSL is handled by library
if not webhook_url:
webhook_url = self._gen_webhook_url(listen, port, url_path)
if not webhook_url:
webhook_url = self._gen_webhook_url(listen, port, url_path)
self._bootstrap(
max_retries=bootstrap_retries,
clean=clean,
webhook_url=webhook_url,
cert=open(cert, 'rb'),
allowed_updates=allowed_updates,
)
elif clean:
self.logger.warning(
"cleaning updates is not supported if "
"SSL-termination happens elsewhere; skipping"
)
# We pass along the cert to the webhook if present.
self._bootstrap(
max_retries=bootstrap_retries,
drop_pending_updates=drop_pending_updates,
webhook_url=webhook_url,
allowed_updates=allowed_updates,
cert=open(cert, 'rb') if cert is not None else None,
ip_address=ip_address,
)
self.httpd.serve_forever(force_event_loop=force_event_loop, ready=ready)
@@ -558,24 +610,34 @@ class Updater:
@no_type_check
def _bootstrap(
self, max_retries, clean, webhook_url, allowed_updates, cert=None, bootstrap_interval=5
self,
max_retries,
drop_pending_updates,
webhook_url,
allowed_updates,
cert=None,
bootstrap_interval=5,
ip_address=None,
):
retries = [0]
def bootstrap_del_webhook():
self.bot.delete_webhook()
return False
def bootstrap_clean_updates():
self.logger.debug('Cleaning updates from Telegram server')
updates = self.bot.get_updates()
while updates:
updates = self.bot.get_updates(updates[-1].update_id + 1)
self.logger.debug('Deleting webhook')
if drop_pending_updates:
self.logger.debug('Dropping pending updates from Telegram server')
self.bot.delete_webhook(drop_pending_updates=drop_pending_updates)
return False
def bootstrap_set_webhook():
self.logger.debug('Setting webhook')
if drop_pending_updates:
self.logger.debug('Dropping pending updates from Telegram server')
self.bot.set_webhook(
url=webhook_url, certificate=cert, allowed_updates=allowed_updates
url=webhook_url,
certificate=cert,
allowed_updates=allowed_updates,
ip_address=ip_address,
drop_pending_updates=drop_pending_updates,
)
return False
@@ -589,11 +651,11 @@ class Updater:
self.logger.error('Failed bootstrap phase after %s retries (%s)', retries[0], exc)
raise exc
# Cleaning pending messages is done by polling for them - so we need to delete webhook if
# one is configured.
# We also take this chance to delete pre-configured webhook if this is a polling Updater.
# NOTE: We don't know ahead if a webhook is configured, so we just delete.
if clean or not webhook_url:
# Dropping pending updates from TG can be efficiently done with the drop_pending_updates
# parameter of delete/start_webhook, even in the case of polling. Also we want to make
# sure that no webhook is configured in case of polling, so we just always call
# delete_webhook for polling
if drop_pending_updates or not webhook_url:
self._network_loop_retry(
bootstrap_del_webhook,
bootstrap_onerr_cb,
@@ -602,17 +664,6 @@ class Updater:
)
retries[0] = 0
# Clean pending messages, if requested.
if clean:
self._network_loop_retry(
bootstrap_clean_updates,
bootstrap_onerr_cb,
'bootstrap clean updates',
bootstrap_interval,
)
retries[0] = 0
sleep(1)
# Restore/set webhook settings, if needed. Again, we don't know ahead if a webhook is set,
# so we set it anyhow.
if webhook_url:
+5 -5
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/].
# pylint: disable=E0401, C0114
# pylint: disable=C0114
import asyncio
import logging
@@ -95,7 +95,7 @@ class WebhookServer:
not force_event_loop
and os.name == 'nt'
and sys.version_info >= (3, 8)
and isinstance(loop, asyncio.ProactorEventLoop)
and isinstance(loop, asyncio.ProactorEventLoop) # type: ignore[attr-defined]
):
raise TypeError(
'`ProactorEventLoop` is incompatible with '
@@ -123,7 +123,7 @@ class WebhookServer:
and (
isinstance(
asyncio.get_event_loop_policy(),
asyncio.WindowsProactorEventLoopPolicy, # pylint: disable=E1101
asyncio.WindowsProactorEventLoopPolicy, # type: ignore # pylint: disable
)
)
): # pylint: disable=E1101
@@ -140,7 +140,7 @@ class WebhookAppClass(tornado.web.Application):
def __init__(self, webhook_path: str, bot: 'Bot', update_queue: Queue):
self.shared_objects = {"bot": bot, "update_queue": update_queue}
handlers = [(rf"{webhook_path}/?", WebhookHandler, self.shared_objects)] # noqa
tornado.web.Application.__init__(self, handlers)
tornado.web.Application.__init__(self, handlers) # type: ignore
def log_request(self, handler: tornado.web.RequestHandler) -> None:
pass
@@ -149,7 +149,7 @@ class WebhookAppClass(tornado.web.Application):
# WebhookHandler, process webhook calls
# pylint: disable=W0223
class WebhookHandler(tornado.web.RequestHandler):
SUPPORTED_METHODS = ["POST"]
SUPPORTED_METHODS = ["POST"] # type: ignore
def __init__(
self,
+4 -4
View File
@@ -19,7 +19,7 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Telegram InlineQuery."""
from typing import TYPE_CHECKING, Any, Optional, List, Union, Callable, ClassVar
from typing import TYPE_CHECKING, Any, Optional, Union, Callable, ClassVar, Sequence
from telegram import Location, TelegramObject, User, constants
from telegram.utils.helpers import DEFAULT_NONE
@@ -38,7 +38,7 @@ class InlineQuery(TelegramObject):
considered equal, if their :attr:`id` is equal.
Note:
* In Python `from` is a reserved word, use `from_user` instead.
* In Python ``from`` is a reserved word, use ``from_user`` instead.
Args:
id (:obj:`str`): Unique identifier for this query.
@@ -97,7 +97,7 @@ class InlineQuery(TelegramObject):
def answer(
self,
results: Union[
List['InlineQueryResult'], Callable[[int], Optional[List['InlineQueryResult']]]
Sequence['InlineQueryResult'], Callable[[int], Optional[Sequence['InlineQueryResult']]]
],
cache_time: int = 300,
is_personal: bool = None,
@@ -125,7 +125,7 @@ class InlineQuery(TelegramObject):
Defaults to :obj:`False`.
Raises:
TypeError: If both :attr:`current_offset` and attr:`auto_pagination` are supplied.
TypeError: If both :attr:`current_offset` and :attr:`auto_pagination` are supplied.
"""
if current_offset and auto_pagination:
# We raise TypeError instead of ValueError for backwards compatibility with versions
+4
View File
@@ -31,6 +31,10 @@ class InlineQueryResult(TelegramObject):
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`id` is equal.
Note:
All URLs passed in inline query results will be available to end users and therefore must
be assumed to be public.
Args:
type (:obj:`str`): Type of the result.
id (:obj:`str`): Unique identifier for this result, 1-64 Bytes.
+2 -2
View File
@@ -20,7 +20,7 @@
from typing import Any
from telegram import TelegramObject
from telegram import TelegramObject, KeyboardButtonPollType
class KeyboardButton(TelegramObject):
@@ -63,7 +63,7 @@ class KeyboardButton(TelegramObject):
text: str,
request_contact: bool = None,
request_location: bool = None,
request_poll: bool = None,
request_poll: KeyboardButtonPollType = None,
**_kwargs: Any,
):
# Required
+81 -28
View File
@@ -47,8 +47,12 @@ from telegram import (
Video,
VideoNote,
Voice,
VoiceChatStarted,
VoiceChatEnded,
VoiceChatParticipantsInvited,
ProximityAlertTriggered,
ReplyMarkup,
MessageAutoDeleteTimerChanged,
)
from telegram.utils.helpers import (
escape_markdown,
@@ -83,7 +87,7 @@ class Message(TelegramObject):
considered equal, if their :attr:`message_id` and :attr:`chat` are equal.
Note:
In Python `from` is a reserved word, use `from_user` instead.
In Python ``from`` is a reserved word, use ``from_user`` instead.
Args:
message_id (:obj:`int`): Unique message identifier inside this chat.
@@ -165,6 +169,10 @@ class Message(TelegramObject):
created. This field can't be received in a message coming through updates, because bot
can't be a member of a channel when it is created. It can only be found in
:attr:`reply_to_message` if someone replies to a very first message in a channel.
message_auto_delete_timer_changed (:class:`telegram.MessageAutoDeleteTimerChanged`, \
optional): Service message: auto-delete timer settings changed in the chat.
.. versionadded:: 13.4
migrate_to_chat_id (:obj:`int`, optional): The group has been migrated to a supergroup with
the specified identifier. This number may be greater than 32 bits and some programming
languages may have difficulty/silent defects in interpreting it. But it is smaller than
@@ -196,6 +204,18 @@ class Message(TelegramObject):
proximity_alert_triggered (:class:`telegram.ProximityAlertTriggered`, optional): Service
message. A user in the chat triggered another user's proximity alert while sharing
Live Location.
voice_chat_started (:class:`telegram.VoiceChatStarted`, optional): Service message: voice
chat started.
.. versionadded:: 13.4
voice_chat_ended (:class:`telegram.VoiceChatEnded`, optional): Service message: voice chat
ended.
.. versionadded:: 13.4
voice_chat_participants_invited (:class:`telegram.VoiceChatParticipantsInvited` optional):
Service message: new participants invited to a voice chat.
.. versionadded:: 13.4
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.
@@ -257,6 +277,10 @@ class Message(TelegramObject):
group_chat_created (:obj:`bool`): Optional. The group has been created.
supergroup_chat_created (:obj:`bool`): Optional. The supergroup has been created.
channel_chat_created (:obj:`bool`): Optional. The channel has been created.
message_auto_delete_timer_changed (:class:`telegram.MessageAutoDeleteTimerChanged`):
Optional. Service message: auto-delete timer settings changed in the chat.
.. versionadded:: 13.4
migrate_to_chat_id (:obj:`int`): Optional. The group has been migrated to a supergroup with
the specified identifier.
migrate_from_chat_id (:obj:`int`): Optional. The supergroup has been migrated from a group
@@ -281,6 +305,18 @@ class Message(TelegramObject):
proximity_alert_triggered (:class:`telegram.ProximityAlertTriggered`): Optional. Service
message. A user in the chat triggered another user's proximity alert while sharing
Live Location.
voice_chat_started (:class:`telegram.VoiceChatStarted`): Optional. Service message: voice
chat started
.. versionadded:: 13.4
voice_chat_ended (:class:`telegram.VoiceChatEnded`): Optional. Service message: voice chat
ended.
.. versionadded:: 13.4
voice_chat_participants_invited (:class:`telegram.VoiceChatParticipantsInvited`): Optional.
Service message: new participants invited to a voice chat.
.. versionadded:: 13.4
reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached
to the message.
bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods.
@@ -316,6 +352,7 @@ class Message(TelegramObject):
'group_chat_created',
'supergroup_chat_created',
'channel_chat_created',
'message_auto_delete_timer_changed',
'migrate_to_chat_id',
'migrate_from_chat_id',
'pinned_message',
@@ -323,6 +360,9 @@ class Message(TelegramObject):
'dice',
'passport_data',
'proximity_alert_triggered',
'voice_chat_started',
'voice_chat_ended',
'voice_chat_participants_invited',
] + ATTACHMENT_TYPES
def __init__(
@@ -379,6 +419,10 @@ class Message(TelegramObject):
via_bot: User = None,
proximity_alert_triggered: ProximityAlertTriggered = None,
sender_chat: Chat = None,
voice_chat_started: VoiceChatStarted = None,
voice_chat_ended: VoiceChatEnded = None,
voice_chat_participants_invited: VoiceChatParticipantsInvited = None,
message_auto_delete_timer_changed: MessageAutoDeleteTimerChanged = None,
**_kwargs: Any,
):
# Required
@@ -418,6 +462,7 @@ class Message(TelegramObject):
self.migrate_to_chat_id = migrate_to_chat_id
self.migrate_from_chat_id = migrate_from_chat_id
self.channel_chat_created = bool(channel_chat_created)
self.message_auto_delete_timer_changed = message_auto_delete_timer_changed
self.pinned_message = pinned_message
self.forward_from_message_id = forward_from_message_id
self.invoice = invoice
@@ -433,6 +478,9 @@ class Message(TelegramObject):
self.dice = dice
self.via_bot = via_bot
self.proximity_alert_triggered = proximity_alert_triggered
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
self.bot = bot
@@ -489,6 +537,9 @@ class Message(TelegramObject):
data['new_chat_members'] = User.de_list(data.get('new_chat_members'), bot)
data['left_chat_member'] = User.de_json(data.get('left_chat_member'), bot)
data['new_chat_photo'] = PhotoSize.de_list(data.get('new_chat_photo'), bot)
data['message_auto_delete_timer_changed'] = MessageAutoDeleteTimerChanged.de_json(
data.get('message_auto_delete_timer_changed'), bot
)
data['pinned_message'] = Message.de_json(data.get('pinned_message'), bot)
data['invoice'] = Invoice.de_json(data.get('invoice'), bot)
data['successful_payment'] = SuccessfulPayment.de_json(data.get('successful_payment'), bot)
@@ -500,7 +551,11 @@ class Message(TelegramObject):
data.get('proximity_alert_triggered'), bot
)
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'), 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.get('voice_chat_participants_invited'), bot
)
return cls(bot=bot, **data)
@property
@@ -581,9 +636,7 @@ class Message(TelegramObject):
return data
def _quote(
self, quote: Optional[bool], reply_to_message_id: Optional[Union[int, str]]
) -> Optional[Union[int, str]]:
def _quote(self, quote: Optional[bool], reply_to_message_id: Optional[int]) -> Optional[int]:
"""Modify kwargs for replying with or without quoting."""
if reply_to_message_id is not None:
return reply_to_message_id
@@ -608,7 +661,7 @@ class Message(TelegramObject):
parse_mode: ODVInput[str] = DEFAULT_NONE,
disable_web_page_preview: ODVInput[bool] = DEFAULT_NONE,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: ReplyMarkup = None,
timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
@@ -652,7 +705,7 @@ class Message(TelegramObject):
text: str,
disable_web_page_preview: ODVInput[bool] = DEFAULT_NONE,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: ReplyMarkup = None,
timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
@@ -706,7 +759,7 @@ class Message(TelegramObject):
text: str,
disable_web_page_preview: ODVInput[bool] = DEFAULT_NONE,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: ReplyMarkup = None,
timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
@@ -756,7 +809,7 @@ class Message(TelegramObject):
text: str,
disable_web_page_preview: ODVInput[bool] = DEFAULT_NONE,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: ReplyMarkup = None,
timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
@@ -807,7 +860,7 @@ class Message(TelegramObject):
Union['InputMediaAudio', 'InputMediaDocument', 'InputMediaPhoto', 'InputMediaVideo']
],
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
timeout: DVInput[float] = DEFAULT_20,
api_kwargs: JSONDict = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -847,7 +900,7 @@ class Message(TelegramObject):
photo: Union[FileInput, 'PhotoSize'],
caption: str = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: ReplyMarkup = None,
timeout: DVInput[float] = DEFAULT_20,
parse_mode: ODVInput[str] = DEFAULT_NONE,
@@ -897,7 +950,7 @@ class Message(TelegramObject):
title: str = None,
caption: str = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: ReplyMarkup = None,
timeout: DVInput[float] = DEFAULT_20,
parse_mode: ODVInput[str] = DEFAULT_NONE,
@@ -950,7 +1003,7 @@ class Message(TelegramObject):
filename: str = None,
caption: str = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: ReplyMarkup = None,
timeout: DVInput[float] = DEFAULT_20,
parse_mode: ODVInput[str] = DEFAULT_NONE,
@@ -1005,7 +1058,7 @@ class Message(TelegramObject):
caption: str = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: ReplyMarkup = None,
timeout: DVInput[float] = DEFAULT_20,
api_kwargs: JSONDict = None,
@@ -1054,7 +1107,7 @@ class Message(TelegramObject):
self,
sticker: Union[FileInput, 'Sticker'],
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: ReplyMarkup = None,
timeout: DVInput[float] = DEFAULT_20,
api_kwargs: JSONDict = None,
@@ -1095,7 +1148,7 @@ class Message(TelegramObject):
duration: int = None,
caption: str = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: ReplyMarkup = None,
timeout: DVInput[float] = DEFAULT_20,
width: int = None,
@@ -1152,7 +1205,7 @@ class Message(TelegramObject):
duration: int = None,
length: int = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: ReplyMarkup = None,
timeout: DVInput[float] = DEFAULT_20,
thumb: FileInput = None,
@@ -1199,7 +1252,7 @@ class Message(TelegramObject):
duration: int = None,
caption: str = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: ReplyMarkup = None,
timeout: DVInput[float] = DEFAULT_20,
parse_mode: ODVInput[str] = DEFAULT_NONE,
@@ -1247,7 +1300,7 @@ class Message(TelegramObject):
latitude: float = None,
longitude: float = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: ReplyMarkup = None,
timeout: ODVInput[float] = DEFAULT_NONE,
location: Location = None,
@@ -1301,7 +1354,7 @@ class Message(TelegramObject):
address: str = None,
foursquare_id: str = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: ReplyMarkup = None,
timeout: ODVInput[float] = DEFAULT_NONE,
venue: Venue = None,
@@ -1354,7 +1407,7 @@ class Message(TelegramObject):
first_name: str = None,
last_name: str = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: ReplyMarkup = None,
timeout: ODVInput[float] = DEFAULT_NONE,
contact: Contact = None,
@@ -1405,7 +1458,7 @@ class Message(TelegramObject):
correct_option_id: int = None,
is_closed: bool = None,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: ReplyMarkup = None,
timeout: ODVInput[float] = DEFAULT_NONE,
explanation: str = None,
@@ -1459,7 +1512,7 @@ class Message(TelegramObject):
def reply_dice(
self,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: ReplyMarkup = None,
timeout: ODVInput[float] = DEFAULT_NONE,
emoji: str = None,
@@ -1524,7 +1577,7 @@ class Message(TelegramObject):
self,
game_short_name: str,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: 'InlineKeyboardMarkup' = None,
timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
@@ -1580,7 +1633,7 @@ class Message(TelegramObject):
need_shipping_address: bool = None,
is_flexible: bool = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: 'InlineKeyboardMarkup' = None,
provider_data: Union[str, object] = None,
send_phone_number_to_provider: bool = None,
@@ -1675,7 +1728,7 @@ class Message(TelegramObject):
parse_mode: ODVInput[str] = DEFAULT_NONE,
caption_entities: Union[Tuple['MessageEntity', ...], List['MessageEntity']] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
allow_sending_without_reply: DVInput[bool] = DEFAULT_NONE,
reply_markup: ReplyMarkup = None,
timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1713,12 +1766,12 @@ class Message(TelegramObject):
def reply_copy(
self,
from_chat_id: Union[str, int],
message_id: Union[str, int],
message_id: int,
caption: str = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
caption_entities: Union[Tuple['MessageEntity', ...], List['MessageEntity']] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
allow_sending_without_reply: DVInput[bool] = DEFAULT_NONE,
reply_markup: ReplyMarkup = None,
timeout: ODVInput[float] = DEFAULT_NONE,
+53
View File
@@ -0,0 +1,53 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2021
# 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 change in the Telegram message auto
deletion."""
from typing import Any
from telegram import TelegramObject
class MessageAutoDeleteTimerChanged(TelegramObject):
"""This object represents a service message about a change in auto-delete timer settings.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`message_auto_delete_time` is equal.
.. versionadded:: 13.4
Args:
message_auto_delete_time (:obj:`int`): New auto-delete time for messages in the
chat.
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
Attributes:
message_auto_delete_time (:obj:`int`): New auto-delete time for messages in the
chat.
"""
def __init__(
self,
message_auto_delete_time: int,
**_kwargs: Any,
):
self.message_auto_delete_time = int(message_auto_delete_time)
self._id_attrs = (self.message_auto_delete_time,)
+1 -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/].
# pylint: disable=C0114, E0401, W0622
# pylint: disable=C0114, W0622
try:
import ujson as json
except ImportError:
+1 -1
View File
@@ -35,7 +35,7 @@ class PreCheckoutQuery(TelegramObject):
considered equal, if their :attr:`id` is equal.
Note:
In Python `from` is a reserved word, use `from_user` instead.
In Python ``from`` is a reserved word, use ``from_user`` instead.
Args:
id (:obj:`str`): Unique query identifier.
+1 -1
View File
@@ -35,7 +35,7 @@ class ShippingQuery(TelegramObject):
considered equal, if their :attr:`id` is equal.
Note:
In Python `from` is a reserved word, use `from_user` instead.
In Python ``from`` is a reserved word, use ``from_user`` instead.
Args:
id (:obj:`str`): Unique query identifier.
+2 -2
View File
@@ -18,7 +18,7 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Telegram ReplyKeyboardMarkup."""
from typing import Any, List, Union
from typing import Any, List, Union, Sequence
from telegram import KeyboardButton, ReplyMarkup
from telegram.utils.types import JSONDict
@@ -67,7 +67,7 @@ class ReplyKeyboardMarkup(ReplyMarkup):
def __init__(
self,
keyboard: List[List[Union[str, KeyboardButton]]],
keyboard: Sequence[Sequence[Union[str, KeyboardButton]]],
resize_keyboard: bool = False,
one_time_keyboard: bool = False,
selective: bool = False,
+45
View File
@@ -29,6 +29,7 @@ from telegram import (
PreCheckoutQuery,
ShippingQuery,
TelegramObject,
ChatMemberUpdated,
)
from telegram.poll import PollAnswer
from telegram.utils.types import JSONDict
@@ -74,6 +75,19 @@ class Update(TelegramObject):
poll_answer (:class:`telegram.PollAnswer`, optional): A user changed their answer
in a non-anonymous poll. Bots receive new votes only in polls that were sent
by the bot itself.
my_chat_member (:class:`telegram.ChatMemberUpdated`, optional): The bot's chat member
status was updated in a chat. For private chats, this update is received only when the
bot is blocked or unblocked by the user.
.. versionadded:: 13.4
chat_member (:class:`telegram.ChatMemberUpdated`, optional): A chat member's status was
updated in a chat. The bot must be an administrator in the chat and must explicitly
specify ``'chat_member'`` in the list of ``'allowed_updates'`` to receive these
updates (see :meth:`telegram.Bot.get_updates`, :meth:`telegram.Bot.set_webhook`,
:meth:`telegram.ext.Updater.start_polling` and
:meth:`telegram.ext.Updater.start_webhook`).
.. versionadded:: 13.4
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
Attributes:
@@ -94,6 +108,19 @@ class Update(TelegramObject):
poll_answer (:class:`telegram.PollAnswer`): Optional. A user changed their answer
in a non-anonymous poll. Bots receive new votes only in polls that were sent
by the bot itself.
my_chat_member (:class:`telegram.ChatMemberUpdated`): Optional. The bot's chat member
status was updated in a chat. For private chats, this update is received only when the
bot is blocked or unblocked by the user.
.. versionadded:: 13.4
chat_member (:class:`telegram.ChatMemberUpdated`): Optional. A chat member's status was
updated in a chat. The bot must be an administrator in the chat and must explicitly
specify ``'chat_member'`` in the list of ``'allowed_updates'`` to receive these
updates (see :meth:`telegram.Bot.get_updates`, :meth:`telegram.Bot.set_webhook`,
:meth:`telegram.ext.Updater.start_polling` and
:meth:`telegram.ext.Updater.start_webhook`).
.. versionadded:: 13.4
"""
@@ -111,6 +138,8 @@ class Update(TelegramObject):
pre_checkout_query: PreCheckoutQuery = None,
poll: Poll = None,
poll_answer: PollAnswer = None,
my_chat_member: ChatMemberUpdated = None,
chat_member: ChatMemberUpdated = None,
**_kwargs: Any,
):
# Required
@@ -127,6 +156,8 @@ class Update(TelegramObject):
self.edited_channel_post = edited_channel_post
self.poll = poll
self.poll_answer = poll_answer
self.my_chat_member = my_chat_member
self.chat_member = chat_member
self._effective_user: Optional['User'] = None
self._effective_chat: Optional['Chat'] = None
@@ -170,6 +201,12 @@ class Update(TelegramObject):
elif self.poll_answer:
user = self.poll_answer.user
elif self.my_chat_member:
user = self.my_chat_member.from_user
elif self.chat_member:
user = self.chat_member.from_user
self._effective_user = user
return user
@@ -203,6 +240,12 @@ class Update(TelegramObject):
elif self.edited_channel_post:
chat = self.edited_channel_post.chat
elif self.my_chat_member:
chat = self.my_chat_member.chat
elif self.chat_member:
chat = self.chat_member.chat
self._effective_chat = chat
return chat
@@ -259,5 +302,7 @@ class Update(TelegramObject):
data['edited_channel_post'] = Message.de_json(data.get('edited_channel_post'), bot)
data['poll'] = Poll.de_json(data.get('poll'), bot)
data['poll_answer'] = PollAnswer.de_json(data.get('poll_answer'), bot)
data['my_chat_member'] = ChatMemberUpdated.de_json(data.get('my_chat_member'), bot)
data['chat_member'] = ChatMemberUpdated.de_json(data.get('chat_member'), bot)
return cls(**data)
+26 -26
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python
# pylint: disable=C0103,W0622
# pylint: disable=W0622
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2021
@@ -111,7 +111,7 @@ class User(TelegramObject):
**_kwargs: Any,
):
# Required
self.id = int(id)
self.id = int(id) # pylint: disable=C0103
self.first_name = first_name
self.is_bot = is_bot
# Optionals
@@ -161,7 +161,7 @@ class User(TelegramObject):
"""
Shortcut for::
bot.send_message(update.effective_user.id, *args, **kwargs)
bot.get_user_profile_photos(update.effective_user.id, *args, **kwargs)
For the documentation of the arguments, please see
:meth:`telegram.Bot.get_user_profile_photos`.
@@ -221,7 +221,7 @@ class User(TelegramObject):
def pin_message(
self,
message_id: Union[str, int],
message_id: int,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
@@ -250,7 +250,7 @@ class User(TelegramObject):
self,
timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
message_id: Union[str, int] = None,
message_id: int = None,
) -> bool:
"""Shortcut for::
@@ -301,7 +301,7 @@ class User(TelegramObject):
parse_mode: ODVInput[str] = DEFAULT_NONE,
disable_web_page_preview: ODVInput[bool] = DEFAULT_NONE,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: 'ReplyMarkup' = None,
timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
@@ -337,7 +337,7 @@ class User(TelegramObject):
photo: Union[FileInput, 'PhotoSize'],
caption: str = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: 'ReplyMarkup' = None,
timeout: DVInput[float] = DEFAULT_20,
parse_mode: ODVInput[str] = DEFAULT_NONE,
@@ -377,7 +377,7 @@ class User(TelegramObject):
Union['InputMediaAudio', 'InputMediaDocument', 'InputMediaPhoto', 'InputMediaVideo']
],
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
timeout: DVInput[float] = DEFAULT_20,
api_kwargs: JSONDict = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -410,7 +410,7 @@ class User(TelegramObject):
title: str = None,
caption: str = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: 'ReplyMarkup' = None,
timeout: DVInput[float] = DEFAULT_20,
parse_mode: ODVInput[str] = DEFAULT_NONE,
@@ -481,7 +481,7 @@ class User(TelegramObject):
first_name: str = None,
last_name: str = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: 'ReplyMarkup' = None,
timeout: ODVInput[float] = DEFAULT_NONE,
contact: 'Contact' = None,
@@ -517,7 +517,7 @@ class User(TelegramObject):
def send_dice(
self,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: 'ReplyMarkup' = None,
timeout: ODVInput[float] = DEFAULT_NONE,
emoji: str = None,
@@ -551,7 +551,7 @@ class User(TelegramObject):
filename: str = None,
caption: str = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: 'ReplyMarkup' = None,
timeout: DVInput[float] = DEFAULT_20,
parse_mode: ODVInput[str] = DEFAULT_NONE,
@@ -592,7 +592,7 @@ class User(TelegramObject):
self,
game_short_name: str,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: 'InlineKeyboardMarkup' = None,
timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
@@ -638,7 +638,7 @@ class User(TelegramObject):
need_shipping_address: bool = None,
is_flexible: bool = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: 'InlineKeyboardMarkup' = None,
provider_data: Union[str, object] = None,
send_phone_number_to_provider: bool = None,
@@ -691,7 +691,7 @@ class User(TelegramObject):
latitude: float = None,
longitude: float = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: 'ReplyMarkup' = None,
timeout: ODVInput[float] = DEFAULT_NONE,
location: 'Location' = None,
@@ -739,7 +739,7 @@ class User(TelegramObject):
caption: str = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: 'ReplyMarkup' = None,
timeout: DVInput[float] = DEFAULT_20,
api_kwargs: JSONDict = None,
@@ -780,7 +780,7 @@ class User(TelegramObject):
self,
sticker: Union[FileInput, 'Sticker'],
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: 'ReplyMarkup' = None,
timeout: DVInput[float] = DEFAULT_20,
api_kwargs: JSONDict = None,
@@ -813,7 +813,7 @@ class User(TelegramObject):
duration: int = None,
caption: str = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: 'ReplyMarkup' = None,
timeout: DVInput[float] = DEFAULT_20,
width: int = None,
@@ -864,7 +864,7 @@ class User(TelegramObject):
address: str = None,
foursquare_id: str = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: 'ReplyMarkup' = None,
timeout: ODVInput[float] = DEFAULT_NONE,
venue: 'Venue' = None,
@@ -909,7 +909,7 @@ class User(TelegramObject):
duration: int = None,
length: int = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: 'ReplyMarkup' = None,
timeout: DVInput[float] = DEFAULT_20,
thumb: FileInput = None,
@@ -948,7 +948,7 @@ class User(TelegramObject):
duration: int = None,
caption: str = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: 'ReplyMarkup' = None,
timeout: DVInput[float] = DEFAULT_20,
parse_mode: ODVInput[str] = DEFAULT_NONE,
@@ -994,7 +994,7 @@ class User(TelegramObject):
correct_option_id: int = None,
is_closed: bool = None,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
reply_markup: 'ReplyMarkup' = None,
timeout: ODVInput[float] = DEFAULT_NONE,
explanation: str = None,
@@ -1040,12 +1040,12 @@ class User(TelegramObject):
def send_copy(
self,
from_chat_id: Union[str, int],
message_id: Union[str, int],
message_id: int,
caption: str = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
caption_entities: Union[Tuple['MessageEntity', ...], List['MessageEntity']] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
allow_sending_without_reply: DVInput[bool] = DEFAULT_NONE,
reply_markup: 'ReplyMarkup' = None,
timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1079,12 +1079,12 @@ class User(TelegramObject):
def copy_message(
self,
chat_id: Union[int, str],
message_id: Union[str, int],
message_id: int,
caption: str = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
caption_entities: Union[Tuple['MessageEntity', ...], List['MessageEntity']] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Union[int, str] = None,
reply_to_message_id: int = None,
allow_sending_without_reply: DVInput[bool] = DEFAULT_NONE,
reply_markup: 'ReplyMarkup' = None,
timeout: ODVInput[float] = DEFAULT_NONE,
+1 -1
View File
@@ -51,7 +51,7 @@ if TYPE_CHECKING:
# in PTB-Raw we don't have pytz, so we make a little workaround here
DTM_UTC = dtm.timezone.utc
try:
import pytz # pylint: disable=E0401
import pytz
UTC = pytz.utc
except ImportError:
+2 -2
View File
@@ -30,7 +30,7 @@ except ImportError:
from typing import Any, Union
import certifi # pylint: disable=E0401
import certifi
try:
import telegram.vendor.ptb_urllib3.urllib3 as urllib3
@@ -85,7 +85,7 @@ def _render_part(self: RequestField, name: str, value: str) -> str: # pylint: d
RequestField._render_part = _render_part # type: ignore # pylint: disable=W0212
logging.getLogger('urllib3').setLevel(logging.WARNING)
logging.getLogger('telegram.vendor.ptb_urllib3.urllib3').setLevel(logging.WARNING)
USER_AGENT = 'Python Telegram Bot (https://github.com/python-telegram-bot/python-telegram-bot)'
+4 -1
View File
@@ -18,4 +18,7 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
# pylint: disable=C0114
__version__ = '13.3'
from telegram import constants
__version__ = '13.4'
bot_api_version = constants.BOT_API_VERSION # pylint: disable=C0103
+111
View File
@@ -0,0 +1,111 @@
#!/usr/bin/env python
# pylint: disable=R0903
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2021
# 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 voice chats."""
from typing import TYPE_CHECKING, Any, Optional, List
from telegram import TelegramObject, User
from telegram.utils.types import JSONDict
if TYPE_CHECKING:
from telegram import Bot
class VoiceChatStarted(TelegramObject):
"""
This object represents a service message about a voice
chat started in the chat. Currently holds no information.
.. versionadded:: 13.4
"""
def __init__(self, **_kwargs: Any):
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.
Attributes:
duration (:obj:`int`): Voice chat duration in seconds.
"""
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.
Attributes:
users (List[:class:`telegram.User`]): New members that
were invited to the voice chat.
"""
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']:
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:
data = super().to_dict()
data["users"] = [u.to_dict() for u in self.users]
return data
+115 -5
View File
@@ -23,6 +23,7 @@ from pathlib import Path
from platform import python_implementation
import pytest
import pytz
from flaky import flaky
from telegram import (
@@ -868,13 +869,14 @@ class TestBot:
assert bot.token not in resulting_path
assert resulting_path == path
# TODO: Needs improvement. No feasable way to test until bots can add members.
# TODO: Needs improvement. No feasible way to test until bots can add members.
def test_kick_chat_member(self, monkeypatch, bot):
def test(url, data, *args, **kwargs):
chat_id = data['chat_id'] == 2
user_id = data['user_id'] == 32
until_date = data.get('until_date', 1577887200) == 1577887200
return chat_id and user_id and until_date
revoke_msgs = data.get('revoke_messages', True) is True
return chat_id and user_id and until_date and revoke_msgs
monkeypatch.setattr(bot.request, 'post', test)
until = from_timestamp(1577887200)
@@ -882,6 +884,7 @@ class TestBot:
assert bot.kick_chat_member(2, 32)
assert bot.kick_chat_member(2, 32, until_date=until)
assert bot.kick_chat_member(2, 32, until_date=1577887200)
assert bot.kick_chat_member(2, 32, revoke_messages=True)
def test_kick_chat_member_default_tz(self, monkeypatch, tz_bot):
until = dtm.datetime(2020, 1, 11, 16, 13)
@@ -1497,7 +1500,7 @@ class TestBot:
@flaky(3, 1)
@pytest.mark.timeout(10)
def test_promote_chat_member(self, bot, channel_id):
def test_promote_chat_member(self, bot, channel_id, monkeypatch):
# TODO: Add bot to supergroup so this can be tested properly / give bot perms
with pytest.raises(BadRequest, match='Not enough rights'):
assert bot.promote_chat_member(
@@ -1512,8 +1515,46 @@ class TestBot:
can_restrict_members=True,
can_pin_messages=True,
can_promote_members=True,
can_manage_chat=True,
can_manage_voice_chats=True,
)
# Test that we pass the correct params to TG
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_voice_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_voice_chats=11,
)
@flaky(3, 1)
@pytest.mark.timeout(10)
def test_export_chat_invite_link(self, bot, channel_id):
@@ -1522,6 +1563,72 @@ class TestBot:
assert isinstance(invite_link, str)
assert invite_link != ''
@flaky(3, 1)
@pytest.mark.timeout(10)
@pytest.mark.parametrize('datetime', argvalues=[True, False], ids=['datetime', 'integer'])
def test_advanced_chat_invite_links(self, bot, channel_id, datetime):
# we are testing this all in one function in order to save api calls
timestamp = dtm.datetime.utcnow()
add_seconds = dtm.timedelta(0, 70)
time_in_future = timestamp + add_seconds
expire_time = time_in_future if datetime else to_timestamp(time_in_future)
aware_time_in_future = pytz.UTC.localize(time_in_future)
invite_link = bot.create_chat_invite_link(
channel_id, expire_date=expire_time, member_limit=10
)
assert invite_link.invite_link != ''
assert not invite_link.invite_link.endswith('...')
assert pytest.approx(invite_link.expire_date == aware_time_in_future)
assert invite_link.member_limit == 10
add_seconds = dtm.timedelta(0, 80)
time_in_future = timestamp + add_seconds
expire_time = time_in_future if datetime else to_timestamp(time_in_future)
aware_time_in_future = pytz.UTC.localize(time_in_future)
edited_invite_link = bot.edit_chat_invite_link(
channel_id, invite_link.invite_link, expire_date=expire_time, member_limit=20
)
assert edited_invite_link.invite_link == invite_link.invite_link
assert pytest.approx(edited_invite_link.expire_date == aware_time_in_future)
assert edited_invite_link.member_limit == 20
revoked_invite_link = bot.revoke_chat_invite_link(channel_id, invite_link.invite_link)
assert revoked_invite_link.invite_link == invite_link.invite_link
assert revoked_invite_link.is_revoked is True
@flaky(3, 1)
@pytest.mark.timeout(10)
def test_advanced_chat_invite_links_default_tzinfo(self, tz_bot, channel_id):
# we are testing this all in one function in order to save api calls
add_seconds = dtm.timedelta(0, 70)
aware_expire_date = dtm.datetime.now(tz=tz_bot.defaults.tzinfo) + add_seconds
time_in_future = aware_expire_date.replace(tzinfo=None)
invite_link = tz_bot.create_chat_invite_link(
channel_id, expire_date=time_in_future, member_limit=10
)
assert invite_link.invite_link != ''
assert not invite_link.invite_link.endswith('...')
assert pytest.approx(invite_link.expire_date == aware_expire_date)
assert invite_link.member_limit == 10
add_seconds = dtm.timedelta(0, 80)
aware_expire_date += add_seconds
time_in_future = aware_expire_date.replace(tzinfo=None)
edited_invite_link = tz_bot.edit_chat_invite_link(
channel_id, invite_link.invite_link, expire_date=time_in_future, member_limit=20
)
assert edited_invite_link.invite_link == invite_link.invite_link
assert pytest.approx(edited_invite_link.expire_date == aware_expire_date)
assert edited_invite_link.member_limit == 20
revoked_invite_link = tz_bot.revoke_chat_invite_link(channel_id, invite_link.invite_link)
assert revoked_invite_link.invite_link == invite_link.invite_link
assert revoked_invite_link.is_revoked is True
@flaky(3, 1)
@pytest.mark.timeout(10)
def test_set_chat_photo(self, bot, channel_id):
@@ -1565,7 +1672,7 @@ class TestBot:
# TODO: Add bot to group to test there too
@flaky(3, 1)
@pytest.mark.timeout(10)
@pytest.mark.timeout(20)
def test_pin_and_unpin_message(self, bot, super_group_id):
message1 = bot.send_message(super_group_id, text="test_pin_message_1")
message2 = bot.send_message(super_group_id, text="test_pin_message_2")
@@ -1574,13 +1681,16 @@ class TestBot:
assert bot.pin_chat_message(
chat_id=super_group_id, message_id=message1.message_id, disable_notification=True
)
time.sleep(1)
bot.pin_chat_message(
chat_id=super_group_id, message_id=message2.message_id, disable_notification=True
)
time.sleep(1)
bot.pin_chat_message(
chat_id=super_group_id, message_id=message3.message_id, disable_notification=True
)
time.sleep(1)
chat = bot.get_chat(super_group_id)
assert chat.pinned_message == message3
@@ -1834,7 +1944,7 @@ class TestBot:
reply_to_message = default_bot.send_message(chat_id, 'test')
reply_to_message.delete()
if not default_bot.defaults.allow_sending_without_reply:
with pytest.raises(BadRequest, match='Reply message not found'):
with pytest.raises(BadRequest, match='not found'):
default_bot.copy_message(
chat_id,
from_chat_id=chat_id,
+61
View File
@@ -37,6 +37,7 @@ def chat(bot):
can_set_sticker_set=TestChat.can_set_sticker_set,
permissions=TestChat.permissions,
slow_mode_delay=TestChat.slow_mode_delay,
message_auto_delete_time=TestChat.message_auto_delete_time,
bio=TestChat.bio,
linked_chat_id=TestChat.linked_chat_id,
location=TestChat.location,
@@ -57,6 +58,7 @@ class TestChat:
can_invite_users=True,
)
slow_mode_delay = 30
message_auto_delete_time = 42
bio = "I'm a Barbie Girl in a Barbie World"
linked_chat_id = 11880
location = ChatLocation(Location(123, 456), 'Barbie World')
@@ -72,6 +74,7 @@ class TestChat:
'can_set_sticker_set': self.can_set_sticker_set,
'permissions': self.permissions.to_dict(),
'slow_mode_delay': self.slow_mode_delay,
'message_auto_delete_time': self.message_auto_delete_time,
'bio': self.bio,
'linked_chat_id': self.linked_chat_id,
'location': self.location.to_dict(),
@@ -87,6 +90,7 @@ class TestChat:
assert chat.can_set_sticker_set == self.can_set_sticker_set
assert chat.permissions == self.permissions
assert chat.slow_mode_delay == self.slow_mode_delay
assert chat.message_auto_delete_time == self.message_auto_delete_time
assert chat.bio == self.bio
assert chat.linked_chat_id == self.linked_chat_id
assert chat.location.location == self.location.location
@@ -103,6 +107,7 @@ class TestChat:
assert chat_dict['all_members_are_administrators'] == chat.all_members_are_administrators
assert chat_dict['permissions'] == chat.permissions.to_dict()
assert chat_dict['slow_mode_delay'] == chat.slow_mode_delay
assert chat_dict['message_auto_delete_time'] == chat.message_auto_delete_time
assert chat_dict['bio'] == chat.bio
assert chat_dict['linked_chat_id'] == chat.linked_chat_id
assert chat_dict['location'] == chat.location.to_dict()
@@ -556,6 +561,62 @@ class TestChat:
monkeypatch.setattr(chat.bot, 'copy_message', make_assertion)
assert chat.copy_message(chat_id='test_copy', message_id=42)
def test_export_invite_link(self, monkeypatch, chat):
def make_assertion(*_, **kwargs):
return kwargs['chat_id'] == chat.id
assert check_shortcut_signature(
Chat.export_invite_link, Bot.export_chat_invite_link, ['chat_id'], []
)
assert check_shortcut_call(chat.export_invite_link, chat.bot, 'export_chat_invite_link')
assert check_defaults_handling(chat.export_invite_link, chat.bot)
monkeypatch.setattr(chat.bot, 'export_chat_invite_link', make_assertion)
assert chat.export_invite_link()
def test_create_invite_link(self, monkeypatch, chat):
def make_assertion(*_, **kwargs):
return kwargs['chat_id'] == chat.id
assert check_shortcut_signature(
Chat.create_invite_link, Bot.create_chat_invite_link, ['chat_id'], []
)
assert check_shortcut_call(chat.create_invite_link, chat.bot, 'create_chat_invite_link')
assert check_defaults_handling(chat.create_invite_link, chat.bot)
monkeypatch.setattr(chat.bot, 'create_chat_invite_link', make_assertion)
assert chat.create_invite_link()
def test_edit_invite_link(self, monkeypatch, chat):
link = "ThisIsALink"
def make_assertion(*_, **kwargs):
return kwargs['chat_id'] == chat.id and kwargs['invite_link'] == link
assert check_shortcut_signature(
Chat.edit_invite_link, Bot.edit_chat_invite_link, ['chat_id'], []
)
assert check_shortcut_call(chat.edit_invite_link, chat.bot, 'edit_chat_invite_link')
assert check_defaults_handling(chat.edit_invite_link, chat.bot)
monkeypatch.setattr(chat.bot, 'edit_chat_invite_link', make_assertion)
assert chat.edit_invite_link(invite_link=link)
def test_revoke_invite_link(self, monkeypatch, chat):
link = "ThisIsALink"
def make_assertion(*_, **kwargs):
return kwargs['chat_id'] == chat.id and kwargs['invite_link'] == link
assert check_shortcut_signature(
Chat.revoke_invite_link, Bot.revoke_chat_invite_link, ['chat_id'], []
)
assert check_shortcut_call(chat.revoke_invite_link, chat.bot, 'revoke_chat_invite_link')
assert check_defaults_handling(chat.revoke_invite_link, chat.bot)
monkeypatch.setattr(chat.bot, 'revoke_chat_invite_link', make_assertion)
assert chat.revoke_invite_link(invite_link=link)
def test_equality(self):
a = Chat(self.id_, self.title, self.type_)
b = Chat(self.id_, self.title, self.type_)
+119
View File
@@ -0,0 +1,119 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2021
# 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
import pytest
from telegram import User, ChatInviteLink
from telegram.utils.helpers import to_timestamp
@pytest.fixture(scope='class')
def creator():
return User(1, 'First name', False)
@pytest.fixture(scope='class')
def invite_link(creator):
return ChatInviteLink(
TestChatInviteLink.link,
creator,
TestChatInviteLink.primary,
TestChatInviteLink.revoked,
expire_date=TestChatInviteLink.expire_date,
member_limit=TestChatInviteLink.member_limit,
)
class TestChatInviteLink:
link = "thisialink"
primary = True
revoked = False
expire_date = datetime.datetime.utcnow()
member_limit = 42
def test_de_json_required_args(self, bot, creator):
json_dict = {
'invite_link': self.link,
'creator': creator.to_dict(),
'is_primary': self.primary,
'is_revoked': self.revoked,
}
invite_link = ChatInviteLink.de_json(json_dict, bot)
assert invite_link.invite_link == self.link
assert invite_link.creator == creator
assert invite_link.is_primary == self.primary
assert invite_link.is_revoked == self.revoked
def test_de_json_all_args(self, bot, creator):
json_dict = {
'invite_link': self.link,
'creator': creator.to_dict(),
'is_primary': self.primary,
'is_revoked': self.revoked,
'expire_date': to_timestamp(self.expire_date),
'member_limit': self.member_limit,
}
invite_link = ChatInviteLink.de_json(json_dict, bot)
assert invite_link.invite_link == self.link
assert invite_link.creator == creator
assert invite_link.is_primary == self.primary
assert invite_link.is_revoked == self.revoked
assert pytest.approx(invite_link.expire_date == self.expire_date)
assert to_timestamp(invite_link.expire_date) == to_timestamp(self.expire_date)
assert invite_link.member_limit == self.member_limit
def test_to_dict(self, invite_link):
invite_link_dict = invite_link.to_dict()
assert isinstance(invite_link_dict, dict)
assert invite_link_dict['creator'] == invite_link.creator.to_dict()
assert invite_link_dict['invite_link'] == invite_link.invite_link
assert invite_link_dict['is_primary'] == self.primary
assert invite_link_dict['is_revoked'] == self.revoked
assert invite_link_dict['expire_date'] == to_timestamp(self.expire_date)
assert invite_link_dict['member_limit'] == self.member_limit
def test_equality(self):
a = ChatInviteLink("link", User(1, '', False), True, True)
b = ChatInviteLink("link", User(1, '', False), True, True)
d = ChatInviteLink("link", User(2, '', False), False, True)
d2 = ChatInviteLink("notalink", User(1, '', False), False, True)
d3 = ChatInviteLink("notalink", User(1, '', False), True, True)
e = User(1, '', False)
assert a == b
assert hash(a) == hash(b)
assert a is not b
assert a != d
assert hash(a) != hash(d)
assert a != d2
assert hash(a) != hash(d2)
assert d2 != d3
assert hash(d2) != hash(d3)
assert a != e
assert hash(a) != hash(e)
+4
View File
@@ -69,6 +69,8 @@ class TestChatMember:
'can_send_polls': False,
'can_send_other_messages': True,
'can_add_web_page_previews': False,
'can_manage_chat': True,
'can_manage_voice_chats': True,
}
chat_member = ChatMember.de_json(json_dict, bot)
@@ -91,6 +93,8 @@ class TestChatMember:
assert chat_member.can_send_polls is False
assert chat_member.can_send_other_messages is True
assert chat_member.can_add_web_page_previews is False
assert chat_member.can_manage_chat is True
assert chat_member.can_manage_voice_chats is True
def test_to_dict(self, chat_member):
chat_member_dict = chat_member.to_dict()
+221
View File
@@ -0,0 +1,221 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2021
# 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 time
from queue import Queue
import pytest
from telegram import (
Update,
Bot,
Message,
User,
Chat,
CallbackQuery,
ChosenInlineResult,
ShippingQuery,
PreCheckoutQuery,
ChatMemberUpdated,
ChatMember,
)
from telegram.ext import CallbackContext, JobQueue, ChatMemberHandler
from telegram.utils.helpers import from_timestamp
message = Message(1, None, Chat(1, ''), from_user=User(1, '', False), text='Text')
params = [
{'message': message},
{'edited_message': message},
{'callback_query': CallbackQuery(1, User(1, '', False), 'chat', message=message)},
{'channel_post': message},
{'edited_channel_post': message},
{'chosen_inline_result': ChosenInlineResult('id', User(1, '', False), '')},
{'shipping_query': ShippingQuery('id', User(1, '', False), '', None)},
{'pre_checkout_query': PreCheckoutQuery('id', User(1, '', False), '', 0, '')},
{'callback_query': CallbackQuery(1, User(1, '', False), 'chat')},
]
ids = (
'message',
'edited_message',
'callback_query',
'channel_post',
'edited_channel_post',
'chosen_inline_result',
'shipping_query',
'pre_checkout_query',
'callback_query_without_message',
)
@pytest.fixture(scope='class', params=params, ids=ids)
def false_update(request):
return Update(update_id=2, **request.param)
@pytest.fixture(scope='class')
def chat_member_updated():
return ChatMemberUpdated(
Chat(1, 'chat'),
User(1, '', False),
from_timestamp(int(time.time())),
ChatMember(User(1, '', False), ChatMember.CREATOR),
ChatMember(User(1, '', False), ChatMember.CREATOR),
)
@pytest.fixture(scope='function')
def chat_member(bot, chat_member_updated):
return Update(0, my_chat_member=chat_member_updated)
class TestChatMemberHandler:
test_flag = False
@pytest.fixture(autouse=True)
def reset(self):
self.test_flag = False
def callback_basic(self, bot, update):
test_bot = isinstance(bot, Bot)
test_update = isinstance(update, Update)
self.test_flag = test_bot and test_update
def callback_data_1(self, bot, update, user_data=None, chat_data=None):
self.test_flag = (user_data is not None) or (chat_data is not None)
def callback_data_2(self, bot, update, user_data=None, chat_data=None):
self.test_flag = (user_data is not None) and (chat_data is not None)
def callback_queue_1(self, bot, update, job_queue=None, update_queue=None):
self.test_flag = (job_queue is not None) or (update_queue is not None)
def callback_queue_2(self, bot, update, job_queue=None, update_queue=None):
self.test_flag = (job_queue is not None) and (update_queue is not None)
def callback_context(self, update, context):
self.test_flag = (
isinstance(context, CallbackContext)
and isinstance(context.bot, Bot)
and isinstance(update, Update)
and isinstance(context.update_queue, Queue)
and isinstance(context.job_queue, JobQueue)
and isinstance(context.user_data, dict)
and isinstance(context.chat_data, dict)
and isinstance(context.bot_data, dict)
and isinstance(update.chat_member or update.my_chat_member, ChatMemberUpdated)
)
def test_basic(self, dp, chat_member):
handler = ChatMemberHandler(self.callback_basic)
dp.add_handler(handler)
assert handler.check_update(chat_member)
dp.process_update(chat_member)
assert self.test_flag
@pytest.mark.parametrize(
argnames=['allowed_types', 'expected'],
argvalues=[
(ChatMemberHandler.MY_CHAT_MEMBER, (True, False)),
(ChatMemberHandler.CHAT_MEMBER, (False, True)),
(ChatMemberHandler.ANY_CHAT_MEMBER, (True, True)),
],
ids=['MY_CHAT_MEMBER', 'CHAT_MEMBER', 'ANY_CHAT_MEMBER'],
)
def test_chat_member_types(
self, dp, chat_member_updated, chat_member, expected, allowed_types
):
result_1, result_2 = expected
handler = ChatMemberHandler(self.callback_basic, chat_member_types=allowed_types)
dp.add_handler(handler)
assert handler.check_update(chat_member) == result_1
dp.process_update(chat_member)
assert self.test_flag == result_1
self.test_flag = False
chat_member.my_chat_member = None
chat_member.chat_member = chat_member_updated
assert handler.check_update(chat_member) == result_2
dp.process_update(chat_member)
assert self.test_flag == result_2
def test_pass_user_or_chat_data(self, dp, chat_member):
handler = ChatMemberHandler(self.callback_data_1, pass_user_data=True)
dp.add_handler(handler)
dp.process_update(chat_member)
assert self.test_flag
dp.remove_handler(handler)
handler = ChatMemberHandler(self.callback_data_1, pass_chat_data=True)
dp.add_handler(handler)
self.test_flag = False
dp.process_update(chat_member)
assert self.test_flag
dp.remove_handler(handler)
handler = ChatMemberHandler(self.callback_data_2, pass_chat_data=True, pass_user_data=True)
dp.add_handler(handler)
self.test_flag = False
dp.process_update(chat_member)
assert self.test_flag
def test_pass_job_or_update_queue(self, dp, chat_member):
handler = ChatMemberHandler(self.callback_queue_1, pass_job_queue=True)
dp.add_handler(handler)
dp.process_update(chat_member)
assert self.test_flag
dp.remove_handler(handler)
handler = ChatMemberHandler(self.callback_queue_1, pass_update_queue=True)
dp.add_handler(handler)
self.test_flag = False
dp.process_update(chat_member)
assert self.test_flag
dp.remove_handler(handler)
handler = ChatMemberHandler(
self.callback_queue_2, pass_job_queue=True, pass_update_queue=True
)
dp.add_handler(handler)
self.test_flag = False
dp.process_update(chat_member)
assert self.test_flag
def test_other_update_types(self, false_update):
handler = ChatMemberHandler(self.callback_basic)
assert not handler.check_update(false_update)
assert not handler.check_update(True)
def test_context(self, cdp, chat_member):
handler = ChatMemberHandler(self.callback_context)
cdp.add_handler(handler)
cdp.process_update(chat_member)
assert self.test_flag
+177
View File
@@ -0,0 +1,177 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2021
# 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
import pytest
import pytz
from telegram import User, ChatMember, Chat, ChatMemberUpdated, ChatInviteLink
from telegram.utils.helpers import to_timestamp
@pytest.fixture(scope='class')
def user():
return User(1, 'First name', False)
@pytest.fixture(scope='class')
def chat():
return Chat(1, Chat.SUPERGROUP, 'Chat')
@pytest.fixture(scope='class')
def old_chat_member(user):
return ChatMember(user, TestChatMemberUpdated.old_status)
@pytest.fixture(scope='class')
def new_chat_member(user):
return ChatMember(user, TestChatMemberUpdated.new_status)
@pytest.fixture(scope='class')
def time():
return datetime.datetime.now(tz=pytz.utc)
@pytest.fixture(scope='class')
def invite_link(user):
return ChatInviteLink('link', user, True, True)
@pytest.fixture(scope='class')
def chat_member_updated(user, chat, old_chat_member, new_chat_member, invite_link, time):
return ChatMemberUpdated(chat, user, time, old_chat_member, new_chat_member, invite_link)
class TestChatMemberUpdated:
old_status = ChatMember.MEMBER
new_status = ChatMember.ADMINISTRATOR
def test_de_json_required_args(self, bot, user, chat, old_chat_member, new_chat_member, time):
json_dict = {
'chat': chat.to_dict(),
'from': user.to_dict(),
'date': to_timestamp(time),
'old_chat_member': old_chat_member.to_dict(),
'new_chat_member': new_chat_member.to_dict(),
}
chat_member_updated = ChatMemberUpdated.de_json(json_dict, bot)
assert chat_member_updated.chat == chat
assert chat_member_updated.from_user == user
assert pytest.approx(chat_member_updated.date == time)
assert to_timestamp(chat_member_updated.date) == to_timestamp(time)
assert chat_member_updated.old_chat_member == old_chat_member
assert chat_member_updated.new_chat_member == new_chat_member
assert chat_member_updated.invite_link is None
def test_de_json_all_args(
self, bot, user, time, invite_link, chat, old_chat_member, new_chat_member
):
json_dict = {
'chat': chat.to_dict(),
'from': user.to_dict(),
'date': to_timestamp(time),
'old_chat_member': old_chat_member.to_dict(),
'new_chat_member': new_chat_member.to_dict(),
'invite_link': invite_link.to_dict(),
}
chat_member_updated = ChatMemberUpdated.de_json(json_dict, bot)
assert chat_member_updated.chat == chat
assert chat_member_updated.from_user == user
assert pytest.approx(chat_member_updated.date == time)
assert to_timestamp(chat_member_updated.date) == to_timestamp(time)
assert chat_member_updated.old_chat_member == old_chat_member
assert chat_member_updated.new_chat_member == new_chat_member
assert chat_member_updated.invite_link == invite_link
def test_to_dict(self, chat_member_updated):
chat_member_updated_dict = chat_member_updated.to_dict()
assert isinstance(chat_member_updated_dict, dict)
assert chat_member_updated_dict['chat'] == chat_member_updated.chat.to_dict()
assert chat_member_updated_dict['from'] == chat_member_updated.from_user.to_dict()
assert chat_member_updated_dict['date'] == to_timestamp(chat_member_updated.date)
assert (
chat_member_updated_dict['old_chat_member']
== chat_member_updated.old_chat_member.to_dict()
)
assert (
chat_member_updated_dict['new_chat_member']
== chat_member_updated.new_chat_member.to_dict()
)
assert chat_member_updated_dict['invite_link'] == chat_member_updated.invite_link.to_dict()
def test_equality(self, time, old_chat_member, new_chat_member, invite_link):
a = ChatMemberUpdated(
Chat(1, 'chat'),
User(1, '', False),
time,
old_chat_member,
new_chat_member,
invite_link,
)
b = ChatMemberUpdated(
Chat(1, 'chat'), User(1, '', False), time, old_chat_member, new_chat_member
)
# wrong date
c = ChatMemberUpdated(
Chat(1, 'chat'),
User(1, '', False),
time + datetime.timedelta(hours=1),
old_chat_member,
new_chat_member,
)
# wrong chat & form_user
d = ChatMemberUpdated(
Chat(42, 'wrong_chat'),
User(42, 'wrong_user', False),
time,
old_chat_member,
new_chat_member,
)
# wrong old_chat_member
e = ChatMemberUpdated(
Chat(1, 'chat'),
User(1, '', False),
time,
ChatMember(User(1, '', False), ChatMember.CREATOR),
new_chat_member,
)
# wrong new_chat_member
f = ChatMemberUpdated(
Chat(1, 'chat'),
User(1, '', False),
time,
old_chat_member,
ChatMember(User(1, '', False), ChatMember.CREATOR),
)
# wrong type
g = ChatMember(User(1, '', False), ChatMember.CREATOR)
assert a == b
assert hash(a) == hash(b)
assert a is not b
for other in [c, d, e, f, g]:
assert a != other
assert hash(a) != hash(other)
+28 -1
View File
@@ -44,7 +44,7 @@ def update():
@pytest.fixture(scope='function', params=MessageEntity.ALL_TYPES)
def message_entity(request):
return MessageEntity(request.param, 0, 0, url='', user='')
return MessageEntity(request.param, 0, 0, url='', user=User(1, 'first_name', False))
@pytest.fixture(
@@ -828,6 +828,11 @@ class TestFilters:
assert Filters.status_update.chat_created(update)
update.message.channel_chat_created = False
update.message.message_auto_delete_timer_changed = True
assert Filters.status_update(update)
assert Filters.status_update.message_auto_delete_timer_changed(update)
update.message.message_auto_delete_timer_changed = False
update.message.migrate_to_chat_id = 100
assert Filters.status_update(update)
assert Filters.status_update.migrate(update)
@@ -853,6 +858,21 @@ class TestFilters:
assert Filters.status_update.proximity_alert_triggered(update)
update.message.proximity_alert_triggered = None
update.message.voice_chat_started = 'hello'
assert Filters.status_update(update)
assert Filters.status_update.voice_chat_started(update)
update.message.voice_chat_started = None
update.message.voice_chat_ended = 'bye'
assert Filters.status_update(update)
assert Filters.status_update.voice_chat_ended(update)
update.message.voice_chat_ended = None
update.message.voice_chat_participants_invited = 'invited'
assert Filters.status_update(update)
assert Filters.status_update.voice_chat_participants_invited(update)
update.message.voice_chat_participants_invited = None
def test_filters_forwarded(self, update):
assert not Filters.forwarded(update)
update.message.forward_date = datetime.datetime.utcnow()
@@ -1453,6 +1473,13 @@ class TestFilters:
assert not Filters.dice.darts(update)
assert not Filters.dice.slot_machine([4])(update)
update.message.dice = Dice(5, '🎳')
assert Filters.dice.bowling(update)
assert Filters.dice.bowling([4, 5])(update)
assert not Filters.dice.dice(update)
assert not Filters.dice.darts(update)
assert not Filters.dice.bowling([4])(update)
def test_language_filter_single(self, update):
update.message.from_user.language_code = 'en_US'
assert (Filters.language('en_US'))(update)
+16
View File
@@ -48,6 +48,10 @@ from telegram import (
Dice,
Bot,
ChatAction,
VoiceChatStarted,
VoiceChatEnded,
VoiceChatParticipantsInvited,
MessageAutoDeleteTimerChanged,
)
from telegram.ext import Defaults
from tests.conftest import check_shortcut_signature, check_shortcut_call, check_defaults_handling
@@ -115,6 +119,7 @@ def message(bot):
{'group_chat_created': True},
{'supergroup_chat_created': True},
{'channel_chat_created': True},
{'message_auto_delete_timer_changed': MessageAutoDeleteTimerChanged(42)},
{'migrate_to_chat_id': -12345},
{'migrate_from_chat_id': -54321},
{'pinned_message': Message(7, None, None, None)},
@@ -166,6 +171,13 @@ def message(bot):
User(1, 'John', False), User(2, 'Doe', False), 42
)
},
{'voice_chat_started': VoiceChatStarted()},
{'voice_chat_ended': VoiceChatEnded(100)},
{
'voice_chat_participants_invited': VoiceChatParticipantsInvited(
[User(1, 'Rem', False), User(2, 'Emilia', False)]
)
},
{'sender_chat': Chat(-123, 'discussion_channel')},
],
ids=[
@@ -195,6 +207,7 @@ def message(bot):
'group_created',
'supergroup_created',
'channel_created',
'message_auto_delete_timer_changed',
'migrated_to',
'migrated_from',
'pinned',
@@ -211,6 +224,9 @@ def message(bot):
'dice',
'via_bot',
'proximity_alert_triggered',
'voice_chat_started',
'voice_chat_ended',
'voice_chat_participants_invited',
'sender_chat',
],
)
@@ -0,0 +1,51 @@
#!/usr/bin/env python
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2021
# 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 MessageAutoDeleteTimerChanged, VoiceChatEnded
class TestMessageAutoDeleteTimerChanged:
message_auto_delete_time = 100
def test_de_json(self):
json_dict = {'message_auto_delete_time': self.message_auto_delete_time}
madtc = MessageAutoDeleteTimerChanged.de_json(json_dict, None)
assert madtc.message_auto_delete_time == self.message_auto_delete_time
def test_to_dict(self):
madtc = MessageAutoDeleteTimerChanged(self.message_auto_delete_time)
madtc_dict = madtc.to_dict()
assert isinstance(madtc_dict, dict)
assert madtc_dict["message_auto_delete_time"] == self.message_auto_delete_time
def test_equality(self):
a = MessageAutoDeleteTimerChanged(100)
b = MessageAutoDeleteTimerChanged(100)
c = MessageAutoDeleteTimerChanged(50)
d = VoiceChatEnded(25)
assert a == b
assert hash(a) == hash(b)
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)
+7 -12
View File
@@ -63,7 +63,7 @@ class TestPhoto:
width = 800
height = 800
caption = '<b>PhotoTest</b> - *Caption*'
photo_file_url = 'https://python-telegram-bot.org/static/testfiles/telegram_new.jpg'
photo_file_url = 'https://python-telegram-bot.org/static/testfiles/telegram.jpg'
file_size = 29176
def test_creation(self, thumb, photo):
@@ -81,12 +81,12 @@ class TestPhoto:
assert thumb.file_unique_id != ''
def test_expected_values(self, photo, thumb):
# We used to test for file_size as well, but TG apparently at some point apparently changed
# the compression method and it's not really our job anyway ...
assert photo.width == self.width
assert photo.height == self.height
assert photo.file_size == self.file_size
assert thumb.width == 320
assert thumb.height == 320
assert thumb.file_size == 9331
@flaky(3, 1)
@pytest.mark.timeout(10)
@@ -191,9 +191,6 @@ class TestPhoto:
message = bot.send_photo(
chat_id, photo_file, caption=test_string, caption_entities=entities
)
# message = bot.send_photo(
# chat_id, photo_file, caption=test_string, caption_entities=entities
# )
assert message.caption == test_string
assert message.caption_entities == entities
@@ -305,18 +302,16 @@ class TestPhoto:
assert isinstance(message.photo[0].file_unique_id, str)
assert message.photo[0].file_id != ''
assert message.photo[0].file_unique_id != ''
assert message.photo[0].width == thumb.width
assert message.photo[0].height == thumb.height
assert message.photo[0].file_size == thumb.file_size
# We used to test for width, height and file_size, but TG apparently started to treat
# sending by URL and sending by upload differently and it's not really our job anyway ...
assert isinstance(message.photo[1], PhotoSize)
assert isinstance(message.photo[1].file_id, str)
assert isinstance(message.photo[1].file_unique_id, str)
assert message.photo[1].file_id != ''
assert message.photo[1].file_unique_id != ''
assert message.photo[1].width == photo.width
assert message.photo[1].height == photo.height
assert message.photo[1].file_size == photo.file_size
# We used to test for width, height and file_size, but TG apparently started to treat
# sending by URL and sending by upload differently and it's not really our job anyway ...
@flaky(3, 1)
@pytest.mark.timeout(10)
+17
View File
@@ -16,6 +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/].
import time
import pytest
@@ -31,10 +32,20 @@ from telegram import (
PreCheckoutQuery,
Poll,
PollOption,
ChatMemberUpdated,
ChatMember,
)
from telegram.poll import PollAnswer
from telegram.utils.helpers import from_timestamp
message = Message(1, None, Chat(1, ''), from_user=User(1, '', False), text='Text')
chat_member_updated = ChatMemberUpdated(
Chat(1, 'chat'),
User(1, '', False),
from_timestamp(int(time.time())),
ChatMember(User(1, '', False), ChatMember.CREATOR),
ChatMember(User(1, '', False), ChatMember.CREATOR),
)
params = [
{'message': message},
@@ -49,6 +60,8 @@ params = [
{'callback_query': CallbackQuery(1, User(1, '', False), 'chat')},
{'poll': Poll('id', '?', [PollOption('.', 1)], False, False, False, Poll.REGULAR, True)},
{'poll_answer': PollAnswer("id", User(1, '', False), [1])},
{'my_chat_member': chat_member_updated},
{'chat_member': chat_member_updated},
]
all_types = (
@@ -63,6 +76,8 @@ all_types = (
'pre_checkout_query',
'poll',
'poll_answer',
'my_chat_member',
'chat_member',
)
ids = all_types + ('callback_query_without_message',)
@@ -146,6 +161,8 @@ class TestUpdate:
or update.pre_checkout_query is not None
or update.poll is not None
or update.poll_answer is not None
or update.my_chat_member is not None
or update.chat_member is not None
):
assert eff_message.message_id == message.message_id
else:
+95 -35
View File
@@ -72,6 +72,7 @@ class TestUpdater:
err_handler_called = Event()
cb_handler_called = Event()
offset = 0
test_flag = False
@pytest.fixture(autouse=True)
def reset(self):
@@ -80,6 +81,7 @@ class TestUpdater:
self.attempts = 0
self.err_handler_called.clear()
self.cb_handler_called.clear()
self.test_flag = False
def error_handler(self, bot, update, error):
self.received = error.message
@@ -247,7 +249,7 @@ class TestUpdater:
cert=None,
key=None,
bootstrap_retries=0,
clean=False,
drop_pending_updates=False,
webhook_url=None,
allowed_updates=None,
)
@@ -274,7 +276,7 @@ class TestUpdater:
cert=None,
key=None,
bootstrap_retries=0,
clean=False,
drop_pending_updates=False,
webhook_url=None,
allowed_updates=None,
)
@@ -307,7 +309,7 @@ class TestUpdater:
cert=None,
key=None,
bootstrap_retries=0,
clean=False,
drop_pending_updates=False,
webhook_url=None,
allowed_updates=None,
force_event_loop=True,
@@ -328,7 +330,7 @@ class TestUpdater:
cert='./tests/test_updater.py',
key='./tests/test_updater.py',
bootstrap_retries=0,
clean=False,
drop_pending_updates=False,
webhook_url=None,
allowed_updates=None,
)
@@ -357,6 +359,42 @@ class TestUpdater:
assert q.get(False) == update
updater.stop()
def test_webhook_ssl_just_for_telegram(self, monkeypatch, updater):
q = Queue()
def set_webhook(**kwargs):
self.test_flag.append(bool(kwargs.get('certificate')))
return True
orig_wh_server_init = WebhookServer.__init__
def webhook_server_init(*args):
self.test_flag = [args[-1] is None]
orig_wh_server_init(*args)
monkeypatch.setattr(updater.bot, 'set_webhook', set_webhook)
monkeypatch.setattr(updater.bot, 'delete_webhook', lambda *args, **kwargs: True)
monkeypatch.setattr('telegram.ext.Dispatcher.process_update', lambda _, u: q.put(u))
monkeypatch.setattr(
'telegram.ext.utils.webhookhandler.WebhookServer.__init__', webhook_server_init
)
ip = '127.0.0.1'
port = randrange(1024, 49152) # Select random port
updater.start_webhook(ip, port, webhook_url=None, cert='./tests/test_updater.py')
sleep(0.2)
# Now, we send an update to the server via urlopen
update = Update(
1,
message=Message(1, None, Chat(1, ''), from_user=User(1, '', False), text='Webhook 2'),
)
self._send_webhook_msg(ip, port, update.to_json())
sleep(0.2)
assert q.get(False) == update
updater.stop()
assert self.test_flag == [True, True]
@pytest.mark.parametrize(('error',), argvalues=[(TelegramError(''),)], ids=('TelegramError',))
def test_bootstrap_retries_success(self, monkeypatch, updater, error):
retries = 2
@@ -391,41 +429,63 @@ class TestUpdater:
updater._bootstrap(retries, False, 'path', None, bootstrap_interval=0)
assert self.attempts == attempts
def test_bootstrap_clean_updates(self, monkeypatch, updater):
clean = True
expected_id = 4
self.offset = 0
@pytest.mark.parametrize('drop_pending_updates', (True, False))
def test_bootstrap_clean_updates(self, monkeypatch, updater, drop_pending_updates):
# As dropping pending updates is done by passing `drop_pending_updates` to
# set_webhook, we just check that we pass the correct value
self.test_flag = False
def get_updates(*args, **kwargs):
# we're hitting this func twice
# 1. no args, return list of updates
# 2. with 1 arg, int => if int == expected_id => test successful
def delete_webhook(**kwargs):
self.test_flag = kwargs.get('drop_pending_updates') == drop_pending_updates
# case 2
# 2nd call from bootstrap____clean
# we should be called with offset = 4
# save value passed in self.offset for assert down below
if len(args) > 0:
self.offset = int(args[0])
return []
class FakeUpdate:
def __init__(self, update_id):
self.update_id = update_id
# case 1
# return list of obj's
# build list of fake updates
# returns list of 4 objects with
# update_id's 0, 1, 2 and 3
return [FakeUpdate(i) for i in range(0, expected_id)]
monkeypatch.setattr(updater.bot, 'get_updates', get_updates)
monkeypatch.setattr(updater.bot, 'delete_webhook', delete_webhook)
updater.running = True
updater._bootstrap(1, clean, None, None, bootstrap_interval=0)
assert self.offset == expected_id
updater._bootstrap(
1,
drop_pending_updates=drop_pending_updates,
webhook_url=None,
allowed_updates=None,
bootstrap_interval=0,
)
assert self.test_flag is True
def test_clean_deprecation_warning_webhook(self, recwarn, updater, monkeypatch):
monkeypatch.setattr(updater.bot, 'set_webhook', lambda *args, **kwargs: True)
monkeypatch.setattr(updater.bot, 'delete_webhook', lambda *args, **kwargs: True)
# prevent api calls from @info decorator when updater.bot.id is used in thread names
monkeypatch.setattr(updater.bot, '_bot', User(id=123, first_name='bot', is_bot=True))
monkeypatch.setattr(updater.bot, '_commands', [])
ip = '127.0.0.1'
port = randrange(1024, 49152) # Select random port
updater.start_webhook(ip, port, clean=True)
updater.stop()
assert len(recwarn) == 2
assert str(recwarn[0].message).startswith('Old Handler API')
assert str(recwarn[1].message).startswith('The argument `clean` of')
def test_clean_deprecation_warning_polling(self, recwarn, updater, monkeypatch):
monkeypatch.setattr(updater.bot, 'set_webhook', lambda *args, **kwargs: True)
monkeypatch.setattr(updater.bot, 'delete_webhook', lambda *args, **kwargs: True)
# prevent api calls from @info decorator when updater.bot.id is used in thread names
monkeypatch.setattr(updater.bot, '_bot', User(id=123, first_name='bot', is_bot=True))
monkeypatch.setattr(updater.bot, '_commands', [])
updater.start_polling(clean=True)
updater.stop()
assert len(recwarn) == 2
for msg in recwarn:
print(msg)
assert str(recwarn[0].message).startswith('Old Handler API')
assert str(recwarn[1].message).startswith('The argument `clean` of')
def test_clean_drop_pending_mutually_exclusive(self, updater):
with pytest.raises(TypeError, match='`clean` and `drop_pending_updates` are mutually'):
updater.start_polling(clean=True, drop_pending_updates=False)
with pytest.raises(TypeError, match='`clean` and `drop_pending_updates` are mutually'):
updater.start_webhook(clean=True, drop_pending_updates=False)
@flaky(3, 1)
def test_webhook_invalid_posts(self, updater):
+114
View File
@@ -0,0 +1,114 @@
#!/usr/bin/env python
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2021
# 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 pytest
from telegram import VoiceChatStarted, VoiceChatEnded, VoiceChatParticipantsInvited, User
@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 TestVoiceChatStarted:
def test_de_json(self):
voice_chat_started = VoiceChatStarted.de_json({}, None)
assert isinstance(voice_chat_started, VoiceChatStarted)
def test_to_dict(self):
voice_chat_started = VoiceChatStarted()
voice_chat_dict = voice_chat_started.to_dict()
assert voice_chat_dict == {}
class TestVoiceChatEnded:
duration = 100
def test_de_json(self):
json_dict = {'duration': self.duration}
voice_chat_ended = VoiceChatEnded.de_json(json_dict, None)
assert voice_chat_ended.duration == self.duration
def test_to_dict(self):
voice_chat_ended = VoiceChatEnded(self.duration)
voice_chat_dict = voice_chat_ended.to_dict()
assert isinstance(voice_chat_dict, dict)
assert voice_chat_dict["duration"] == self.duration
def test_equality(self):
a = VoiceChatEnded(100)
b = VoiceChatEnded(100)
c = VoiceChatEnded(50)
d = VoiceChatStarted()
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 TestVoiceChatParticipantsInvited:
def test_de_json(self, user1, user2, bot):
json_data = {"users": [user1.to_dict(), user2.to_dict()]}
voice_chat_participants = VoiceChatParticipantsInvited.de_json(json_data, bot)
assert isinstance(voice_chat_participants.users, list)
assert voice_chat_participants.users[0] == user1
assert voice_chat_participants.users[1] == user2
assert voice_chat_participants.users[0].id == user1.id
assert voice_chat_participants.users[1].id == user2.id
def test_to_dict(self, user1, user2):
voice_chat_participants = VoiceChatParticipantsInvited([user1, user2])
voice_chat_dict = voice_chat_participants.to_dict()
assert isinstance(voice_chat_dict, dict)
assert voice_chat_dict["users"] == [user1.to_dict(), user2.to_dict()]
assert voice_chat_dict["users"][0]["id"] == user1.id
assert voice_chat_dict["users"][1]["id"] == user2.id
def test_equality(self, user1, user2):
a = VoiceChatParticipantsInvited([user1])
b = VoiceChatParticipantsInvited([user1])
c = VoiceChatParticipantsInvited([user1, user2])
d = VoiceChatParticipantsInvited([user2])
e = VoiceChatStarted()
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)