mirror of
https://github.com/python-telegram-bot/python-telegram-bot.git
synced 2026-06-21 08:35:28 +00:00
Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 73b0e29a30 | |||
| d27d1ea4d5 | |||
| ca04daf782 | |||
| ae9ce60b55 | |||
| 1cd3a0a156 | |||
| 58b9882021 | |||
| df6d5f0840 | |||
| 425716f966 | |||
| 8d9bb26cca | |||
| d1438a9b23 | |||
| 27b03edc59 | |||
| ac449deb5d | |||
| 3b9187ed5a | |||
| 9831458e22 | |||
| a0cd6e8fef | |||
| 8e7c0d6976 | |||
| 92b9370c23 | |||
| 237e73bfb4 | |||
| ff3fd34f08 | |||
| 83791d34e7 | |||
| 02cd7b642f | |||
| 165a24e13d | |||
| 88440079e3 | |||
| 9be4c7563b | |||
| b554f1a85d | |||
| 3b4559dd95 | |||
| 9ae48fecfe | |||
| 6af6648509 | |||
| 264b2c9c72 | |||
| 8efb05290a | |||
| 83a8874bb5 |
@@ -91,12 +91,14 @@ Here's how to make a one-off code change.
|
||||
|
||||
Once the process terminates, you can view the built documentation by opening ``docs/build/html/index.html`` with a browser.
|
||||
|
||||
- For consistency, please conform to `Google Python Style Guide`_ and `Google Python Style Docstrings`_. In addition, code should be formatted consistently with other code around it.
|
||||
- For consistency, please conform to `Google Python Style Guide`_ and `Google Python Style Docstrings`_.
|
||||
|
||||
- The following exceptions to the above (Google's) style guides applies:
|
||||
|
||||
- Documenting types of global variables and complex types of class members can be done using the Sphinx docstring convention.
|
||||
|
||||
- In addition, PTB uses the `Black`_ coder formatting. Plugins for Black exist for some `popular editors`_. You can use those instead of manually formatting everything.
|
||||
|
||||
- Please ensure that the code you write is well-tested.
|
||||
|
||||
- Don’t break backward compatibility.
|
||||
@@ -189,11 +191,6 @@ Here's how to make a one-off code change.
|
||||
Style commandments
|
||||
------------------
|
||||
|
||||
Specific commandments
|
||||
#####################
|
||||
|
||||
- Avoid using "double quotes" where you can reasonably use 'single quotes'.
|
||||
|
||||
Assert comparison order
|
||||
#######################
|
||||
|
||||
@@ -255,3 +252,5 @@ break the API classes. For example:
|
||||
.. _AUTHORS.rst: ../AUTHORS.rst
|
||||
.. _`MyPy`: https://mypy.readthedocs.io/en/stable/index.html
|
||||
.. _`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
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
runs-on: ${{matrix.os}}
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [3.6, 3.7, 3.8]
|
||||
python-version: [3.6, 3.7, 3.8, 3.9]
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
run:
|
||||
git submodule update --init --recursive
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v1
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install dependencies
|
||||
@@ -57,13 +57,11 @@ jobs:
|
||||
shell: bash --noprofile --norc {0}
|
||||
|
||||
- name: Submit coverage
|
||||
run: |
|
||||
if [ "$CODECOV_TOKEN" != "" ]; then
|
||||
codecov -F github -t $CODECOV_TOKEN --name "${{ matrix.os }}-${{ matrix.python-version }}"
|
||||
fi
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
shell: bash
|
||||
uses: codecov/codecov-action@v1.0.13
|
||||
with:
|
||||
env_vars: OS,PYTHON
|
||||
name: ${{ matrix.os }}-${{ matrix.python-version }}
|
||||
fail_ci_if_error: true
|
||||
test_official:
|
||||
name: test-official
|
||||
runs-on: ${{matrix.os}}
|
||||
|
||||
+14
-12
@@ -1,25 +1,27 @@
|
||||
# Make sure that
|
||||
# * the revs specified here match requirements-dev.txt
|
||||
# * the makefile checks the same files as pre-commit
|
||||
repos:
|
||||
- repo: git://github.com/python-telegram-bot/mirrors-yapf
|
||||
rev: 5769e088ef6e0a0d1eb63bd6d0c1fe9f3606d6c8
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 20.8b1
|
||||
hooks:
|
||||
- id: yapf
|
||||
files: ^(telegram|tests)/.*\.py$
|
||||
- id: black
|
||||
args:
|
||||
- --diff
|
||||
- --check
|
||||
- repo: https://gitlab.com/pycqa/flake8
|
||||
rev: 3.8.1
|
||||
rev: 3.8.4
|
||||
hooks:
|
||||
- id: flake8
|
||||
- repo: git://github.com/pre-commit/mirrors-pylint
|
||||
rev: v2.5.3
|
||||
- repo: https://github.com/PyCQA/pylint
|
||||
rev: pylint-2.6.0
|
||||
hooks:
|
||||
- id: pylint
|
||||
files: ^telegram/.*\.py$
|
||||
files: ^(telegram|examples)/.*\.py$
|
||||
args:
|
||||
- --errors-only
|
||||
- --disable=import-error
|
||||
- --rcfile=setup.cfg
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: 'v0.770'
|
||||
rev: v0.790
|
||||
hooks:
|
||||
- id: mypy
|
||||
files: ^telegram/.*\.py$
|
||||
files: ^(telegram|examples)/.*\.py$
|
||||
|
||||
+11
-2
@@ -2,9 +2,12 @@ Credits
|
||||
=======
|
||||
|
||||
``python-telegram-bot`` was originally created by
|
||||
`Leandro Toledo <https://github.com/leandrotoledo>`_ and is now maintained by
|
||||
`Leandro Toledo <https://github.com/leandrotoledo>`_ and is now maintained by `Hinrich Mahler <https://github.com/Bibo-Joshi>`_.
|
||||
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>`_, `Jasmin Bom <https://github.com/jsmnbom>`_ and `Hinrich Mahler <https://github.com/Bibo-Joshi>`_.
|
||||
`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.
|
||||
|
||||
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:
|
||||
@@ -36,9 +39,11 @@ The following wonderful people contributed directly or indirectly to this projec
|
||||
- `Eugene Lisitsky <https://github.com/lisitsky>`_
|
||||
- `Eugenio Panadero <https://github.com/azogue>`_
|
||||
- `Evan Haberecht <https://github.com/habereet>`_
|
||||
- `Evgeny Denisov <https://github.com/eIGato>`_
|
||||
- `evgfilim1 <https://github.com/evgfilim1>`_
|
||||
- `franciscod <https://github.com/franciscod>`_
|
||||
- `gamgi <https://github.com/gamgi>`_
|
||||
- `Gauthamram Ravichandran <https://github.com/GauthamramRavichandran>`_
|
||||
- `Harshil <https://github.com/harshil21>`_
|
||||
- `Hugo Damer <https://github.com/HakimusGIT>`_
|
||||
- `ihoru <https://github.com/ihoru>`_
|
||||
@@ -58,10 +63,12 @@ The following wonderful people contributed directly or indirectly to this projec
|
||||
- `Loo Zheng Yuan <https://github.com/loozhengyuan>`_
|
||||
- `LRezende <https://github.com/lrezende>`_
|
||||
- `macrojames <https://github.com/macrojames>`_
|
||||
- `Matheus Lemos <https://github.com/mlemosf>`_
|
||||
- `Michael Elovskikh <https://github.com/wronglink>`_
|
||||
- `Mischa Krüger <https://github.com/Makman2>`_
|
||||
- `naveenvhegde <https://github.com/naveenvhegde>`_
|
||||
- `neurrone <https://github.com/neurrone>`_
|
||||
- `NikitaPirate <https://github.com/NikitaPirate>`_
|
||||
- `njittam <https://github.com/njittam>`_
|
||||
- `Noam Meltzer <https://github.com/tsnoam>`_
|
||||
- `Oleg Shlyazhko <https://github.com/ollmer>`_
|
||||
@@ -72,6 +79,7 @@ The following wonderful people contributed directly or indirectly to this projec
|
||||
- `Paul Larsen <https://github.com/PaulSonOfLars>`_
|
||||
- `Pieter Schutz <https://github.com/eldinnie>`_
|
||||
- `Poolitzer <https://github.com/Poolitzer>`_
|
||||
- `Pranjalya Tiwari <https://github.com/Pranjalya>`_
|
||||
- `Rahiel Kasim <https://github.com/rahiel>`_
|
||||
- `Riko Naka <https://github.com/rikonaka>`_
|
||||
- `Rizlas <https://github.com/rizlas>`_
|
||||
@@ -82,6 +90,7 @@ The following wonderful people contributed directly or indirectly to this projec
|
||||
- `sooyhwang <https://github.com/sooyhwang>`_
|
||||
- `syntx <https://github.com/syntx>`_
|
||||
- `thodnev <https://github.com/thodnev>`_
|
||||
- `Timur Kushukov <https://github.com/timqsh>`_
|
||||
- `Trainer Jono <https://github.com/Tr-Jono>`_
|
||||
- `Valentijn <https://github.com/Faalentijn>`_
|
||||
- `voider1 <https://github.com/voider1>`_
|
||||
|
||||
+85
@@ -2,6 +2,91 @@
|
||||
Changelog
|
||||
=========
|
||||
|
||||
Version 13.1
|
||||
============
|
||||
*Released 2020-11-29*
|
||||
|
||||
**Major Changes:**
|
||||
|
||||
- Full support of Bot API 5.0 (`#2181`_, `#2186`_, `#2190`_, `#2189`_, `#2183`_, `#2184`_, `#2188`_, `#2185`_, `#2192`_, `#2196`_, `#2193`_, `#2223`_, `#2199`_, `#2187`_, `#2147`_, `#2205`_)
|
||||
|
||||
**New Features:**
|
||||
|
||||
- Add ``Defaults.run_async`` (`#2210`_)
|
||||
- Improve and Expand ``CallbackQuery`` Shortcuts (`#2172`_)
|
||||
- Add XOR Filters and make ``Filters.name`` a Property (`#2179`_)
|
||||
- Add ``Filters.document.file_extension`` (`#2169`_)
|
||||
- Add ``Filters.caption_regex`` (`#2163`_)
|
||||
- Add ``Filters.chat_type`` (`#2128`_)
|
||||
- Handle Non-Binary File Input (`#2202`_)
|
||||
|
||||
**Bug Fixes:**
|
||||
|
||||
- Improve Handling of Custom Objects in ``BasePersistence.insert``/``replace_bot`` (`#2151`_)
|
||||
- Fix bugs in ``replace/insert_bot`` (`#2218`_)
|
||||
|
||||
**Minor changes, CI improvements, doc fixes and type hinting:**
|
||||
|
||||
- Improve Type hinting (`#2204`_, `#2118`_, `#2167`_, `#2136`_)
|
||||
- Doc Fixes & Extensions (`#2201`_, `#2161`_)
|
||||
- Use F-Strings Where Possible (`#2222`_)
|
||||
- Rename kwargs to _kwargs where possible (`#2182`_)
|
||||
- Comply with PEP561 (`#2168`_)
|
||||
- Improve Code Quality (`#2131`_)
|
||||
- Switch Code Formatting to Black (`#2122`_, `#2159`_, `#2158`_)
|
||||
- Update Wheel Settings (`#2142`_)
|
||||
- Update ``timerbot.py`` to ``v13.0`` (`#2149`_)
|
||||
- Overhaul Constants (`#2137`_)
|
||||
- Add Python 3.9 to Test Matrix (`#2132`_)
|
||||
- Switch Codecov to ``GitHub`` Action (`#2127`_)
|
||||
- Specify Required pytz Version (`#2121`_)
|
||||
|
||||
|
||||
.. _`#2181`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2181
|
||||
.. _`#2186`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2186
|
||||
.. _`#2190`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2190
|
||||
.. _`#2189`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2189
|
||||
.. _`#2183`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2183
|
||||
.. _`#2184`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2184
|
||||
.. _`#2188`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2188
|
||||
.. _`#2185`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2185
|
||||
.. _`#2192`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2192
|
||||
.. _`#2196`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2196
|
||||
.. _`#2193`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2193
|
||||
.. _`#2223`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2223
|
||||
.. _`#2199`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2199
|
||||
.. _`#2187`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2187
|
||||
.. _`#2147`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2147
|
||||
.. _`#2205`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2205
|
||||
.. _`#2210`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2210
|
||||
.. _`#2172`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2172
|
||||
.. _`#2179`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2179
|
||||
.. _`#2169`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2169
|
||||
.. _`#2163`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2163
|
||||
.. _`#2128`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2128
|
||||
.. _`#2202`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2202
|
||||
.. _`#2151`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2151
|
||||
.. _`#2218`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2218
|
||||
.. _`#2204`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2204
|
||||
.. _`#2118`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2118
|
||||
.. _`#2167`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2167
|
||||
.. _`#2136`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2136
|
||||
.. _`#2201`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2201
|
||||
.. _`#2161`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2161
|
||||
.. _`#2222`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2222
|
||||
.. _`#2182`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2182
|
||||
.. _`#2168`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2168
|
||||
.. _`#2131`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2131
|
||||
.. _`#2122`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2122
|
||||
.. _`#2159`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2159
|
||||
.. _`#2158`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2158
|
||||
.. _`#2142`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2142
|
||||
.. _`#2149`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2149
|
||||
.. _`#2137`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2137
|
||||
.. _`#2132`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2132
|
||||
.. _`#2127`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2127
|
||||
.. _`#2121`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2121
|
||||
|
||||
Version 13.0
|
||||
============
|
||||
*Released 2020-10-07*
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
include LICENSE LICENSE.lesser Makefile requirements.txt
|
||||
include LICENSE LICENSE.lesser Makefile requirements.txt py.typed
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
.DEFAULT_GOAL := help
|
||||
.PHONY: clean pep257 pep8 yapf lint test install
|
||||
.PHONY: clean pep8 black lint test install
|
||||
|
||||
PYLINT := pylint
|
||||
PYTEST := pytest
|
||||
PEP257 := pep257
|
||||
PEP8 := flake8
|
||||
YAPF := yapf
|
||||
BLACK := black
|
||||
MYPY := mypy
|
||||
PIP := pip
|
||||
|
||||
@@ -15,22 +14,20 @@ clean:
|
||||
find . -name '*.pyc' -exec rm -f {} \;
|
||||
find . -name '*.pyo' -exec rm -f {} \;
|
||||
find . -name '*~' -exec rm -f {} \;
|
||||
find . -regex "./telegram.\(mp3\|mp4\|ogg\|png\|webp\)" -exec rm {} \;
|
||||
|
||||
pep257:
|
||||
$(PEP257) telegram
|
||||
find . -regex "./telegram[0-9]*.\(jpg\|mp3\|mp4\|ogg\|png\|webp\)" -exec rm {} \;
|
||||
|
||||
pep8:
|
||||
$(PEP8) telegram
|
||||
$(PEP8) telegram tests examples
|
||||
|
||||
yapf:
|
||||
$(YAPF) -r telegram
|
||||
black:
|
||||
$(BLACK) .
|
||||
|
||||
lint:
|
||||
$(PYLINT) -E telegram --disable=no-name-in-module,import-error
|
||||
$(PYLINT) --rcfile=setup.cfg telegram examples
|
||||
|
||||
mypy:
|
||||
$(MYPY) -p telegram
|
||||
$(MYPY) examples
|
||||
|
||||
test:
|
||||
$(PYTEST) -v
|
||||
@@ -41,18 +38,16 @@ install:
|
||||
help:
|
||||
@echo "Available targets:"
|
||||
@echo "- clean Clean up the source directory"
|
||||
@echo "- pep257 Check docstring style with pep257"
|
||||
@echo "- pep8 Check style with flake8"
|
||||
@echo "- lint Check style with pylint"
|
||||
@echo "- yapf Check style with yapf"
|
||||
@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 "- PEP257 default: $(PEP257)"
|
||||
@echo "- PEP8 default: $(PEP8)"
|
||||
@echo "- YAPF default: $(YAPF)"
|
||||
@echo "- BLACK default: $(BLACK)"
|
||||
@echo "- MYPY default: $(MYPY)"
|
||||
@echo "- PIP default: $(PIP)"
|
||||
|
||||
@@ -45,6 +45,9 @@ We have a vibrant community of developers helping each other in our `Telegram gr
|
||||
:target: https://www.codacy.com/app/python-telegram-bot/python-telegram-bot?utm_source=github.com&utm_medium=referral&utm_content=python-telegram-bot/python-telegram-bot&utm_campaign=Badge_Grade
|
||||
:alt: Code quality
|
||||
|
||||
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
|
||||
:target: https://github.com/psf/black
|
||||
|
||||
.. image:: https://img.shields.io/badge/Telegram-Group-blue.svg
|
||||
:target: https://telegram.me/pythontelegrambotgroup
|
||||
:alt: Telegram Group
|
||||
|
||||
+2
-2
@@ -58,9 +58,9 @@ author = u'Leandro Toledo'
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '13.0' # telegram.__version__[:3]
|
||||
version = '13.1' # telegram.__version__[:3]
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '13.0' # telegram.__version__
|
||||
release = '13.1' # telegram.__version__
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
telegram.ChatLocation
|
||||
=====================
|
||||
|
||||
.. autoclass:: telegram.ChatLocation
|
||||
:members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,6 @@
|
||||
telegram.MessageId
|
||||
==================
|
||||
|
||||
.. autoclass:: telegram.MessageId
|
||||
:members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,6 @@
|
||||
telegram.ProximityAlertTriggered
|
||||
================================
|
||||
|
||||
.. autoclass:: telegram.ProximityAlertTriggered
|
||||
:members:
|
||||
:show-inheritance:
|
||||
@@ -13,6 +13,7 @@ telegram package
|
||||
telegram.callbackquery
|
||||
telegram.chat
|
||||
telegram.chataction
|
||||
telegram.chatlocation
|
||||
telegram.chatmember
|
||||
telegram.chatpermissions
|
||||
telegram.chatphoto
|
||||
@@ -37,12 +38,14 @@ telegram package
|
||||
telegram.location
|
||||
telegram.loginurl
|
||||
telegram.message
|
||||
telegram.messageid
|
||||
telegram.messageentity
|
||||
telegram.parsemode
|
||||
telegram.photosize
|
||||
telegram.poll
|
||||
telegram.pollanswer
|
||||
telegram.polloption
|
||||
telegram.proximityalerttriggered
|
||||
telegram.replykeyboardremove
|
||||
telegram.replykeyboardmarkup
|
||||
telegram.replymarkup
|
||||
|
||||
+57
-43
@@ -1,5 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# pylint: disable=W0613, C0116
|
||||
# type: ignore[union-attr]
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
"""
|
||||
@@ -16,82 +18,97 @@ bot.
|
||||
|
||||
import logging
|
||||
|
||||
from telegram import (ReplyKeyboardMarkup, ReplyKeyboardRemove)
|
||||
from telegram.ext import (Updater, CommandHandler, MessageHandler, Filters,
|
||||
ConversationHandler)
|
||||
from telegram import ReplyKeyboardMarkup, ReplyKeyboardRemove, Update
|
||||
from telegram.ext import (
|
||||
Updater,
|
||||
CommandHandler,
|
||||
MessageHandler,
|
||||
Filters,
|
||||
ConversationHandler,
|
||||
CallbackContext,
|
||||
)
|
||||
|
||||
# Enable logging
|
||||
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
level=logging.INFO)
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
GENDER, PHOTO, LOCATION, BIO = range(4)
|
||||
|
||||
|
||||
def start(update, context):
|
||||
def start(update: Update, context: CallbackContext) -> int:
|
||||
reply_keyboard = [['Boy', 'Girl', 'Other']]
|
||||
|
||||
update.message.reply_text(
|
||||
'Hi! My name is Professor Bot. I will hold a conversation with you. '
|
||||
'Send /cancel to stop talking to me.\n\n'
|
||||
'Are you a boy or a girl?',
|
||||
reply_markup=ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True))
|
||||
reply_markup=ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True),
|
||||
)
|
||||
|
||||
return GENDER
|
||||
|
||||
|
||||
def gender(update, context):
|
||||
def gender(update: Update, context: CallbackContext) -> int:
|
||||
user = update.message.from_user
|
||||
logger.info("Gender of %s: %s", user.first_name, update.message.text)
|
||||
update.message.reply_text('I see! Please send me a photo of yourself, '
|
||||
'so I know what you look like, or send /skip if you don\'t want to.',
|
||||
reply_markup=ReplyKeyboardRemove())
|
||||
update.message.reply_text(
|
||||
'I see! Please send me a photo of yourself, '
|
||||
'so I know what you look like, or send /skip if you don\'t want to.',
|
||||
reply_markup=ReplyKeyboardRemove(),
|
||||
)
|
||||
|
||||
return PHOTO
|
||||
|
||||
|
||||
def photo(update, context):
|
||||
def photo(update: Update, context: 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.')
|
||||
update.message.reply_text(
|
||||
'Gorgeous! Now, send me your location please, ' 'or send /skip if you don\'t want to.'
|
||||
)
|
||||
|
||||
return LOCATION
|
||||
|
||||
|
||||
def skip_photo(update, context):
|
||||
def skip_photo(update: Update, context: 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.')
|
||||
update.message.reply_text(
|
||||
'I bet you look great! Now, send me your location please, ' 'or send /skip.'
|
||||
)
|
||||
|
||||
return LOCATION
|
||||
|
||||
|
||||
def location(update, context):
|
||||
def location(update: Update, context: 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.')
|
||||
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.'
|
||||
)
|
||||
|
||||
return BIO
|
||||
|
||||
|
||||
def skip_location(update, context):
|
||||
def skip_location(update: Update, context: 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.')
|
||||
update.message.reply_text(
|
||||
'You seem a bit paranoid! ' 'At last, tell me something about yourself.'
|
||||
)
|
||||
|
||||
return BIO
|
||||
|
||||
|
||||
def bio(update, context):
|
||||
def bio(update: Update, context: 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.')
|
||||
@@ -99,44 +116,41 @@ def bio(update, context):
|
||||
return ConversationHandler.END
|
||||
|
||||
|
||||
def cancel(update, context):
|
||||
def cancel(update: Update, context: CallbackContext) -> int:
|
||||
user = update.message.from_user
|
||||
logger.info("User %s canceled the conversation.", user.first_name)
|
||||
update.message.reply_text('Bye! I hope we can talk again some day.',
|
||||
reply_markup=ReplyKeyboardRemove())
|
||||
update.message.reply_text(
|
||||
'Bye! I hope we can talk again some day.', reply_markup=ReplyKeyboardRemove()
|
||||
)
|
||||
|
||||
return ConversationHandler.END
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
# Create the Updater and pass it your bot's token.
|
||||
# Make sure to set use_context=True to use the new context based callbacks
|
||||
# Post version 12 this will no longer be necessary
|
||||
updater = Updater("TOKEN", use_context=True)
|
||||
|
||||
# Get the dispatcher to register handlers
|
||||
dp = updater.dispatcher
|
||||
dispatcher = updater.dispatcher
|
||||
|
||||
# Add conversation handler with the states GENDER, PHOTO, LOCATION and BIO
|
||||
conv_handler = ConversationHandler(
|
||||
entry_points=[CommandHandler('start', start)],
|
||||
|
||||
states={
|
||||
GENDER: [MessageHandler(Filters.regex('^(Boy|Girl|Other)$'), gender)],
|
||||
|
||||
PHOTO: [MessageHandler(Filters.photo, photo),
|
||||
CommandHandler('skip', skip_photo)],
|
||||
|
||||
LOCATION: [MessageHandler(Filters.location, location),
|
||||
CommandHandler('skip', skip_location)],
|
||||
|
||||
BIO: [MessageHandler(Filters.text & ~Filters.command, bio)]
|
||||
PHOTO: [MessageHandler(Filters.photo, photo), CommandHandler('skip', skip_photo)],
|
||||
LOCATION: [
|
||||
MessageHandler(Filters.location, location),
|
||||
CommandHandler('skip', skip_location),
|
||||
],
|
||||
BIO: [MessageHandler(Filters.text & ~Filters.command, bio)],
|
||||
},
|
||||
|
||||
fallbacks=[CommandHandler('cancel', cancel)]
|
||||
fallbacks=[CommandHandler('cancel', cancel)],
|
||||
)
|
||||
|
||||
dp.add_handler(conv_handler)
|
||||
dispatcher.add_handler(conv_handler)
|
||||
|
||||
# Start the Bot
|
||||
updater.start_polling()
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# pylint: disable=W0613, C0116
|
||||
# type: ignore[union-attr]
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
"""
|
||||
@@ -15,120 +17,135 @@ bot.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Dict
|
||||
|
||||
from telegram import ReplyKeyboardMarkup
|
||||
from telegram.ext import (Updater, CommandHandler, MessageHandler, Filters,
|
||||
ConversationHandler)
|
||||
from telegram import ReplyKeyboardMarkup, Update
|
||||
from telegram.ext import (
|
||||
Updater,
|
||||
CommandHandler,
|
||||
MessageHandler,
|
||||
Filters,
|
||||
ConversationHandler,
|
||||
CallbackContext,
|
||||
)
|
||||
|
||||
# Enable logging
|
||||
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
level=logging.INFO)
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
CHOOSING, TYPING_REPLY, TYPING_CHOICE = range(3)
|
||||
|
||||
reply_keyboard = [['Age', 'Favourite colour'],
|
||||
['Number of siblings', 'Something else...'],
|
||||
['Done']]
|
||||
reply_keyboard = [
|
||||
['Age', 'Favourite colour'],
|
||||
['Number of siblings', 'Something else...'],
|
||||
['Done'],
|
||||
]
|
||||
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():
|
||||
facts.append('{} - {}'.format(key, value))
|
||||
facts.append(f'{key} - {value}')
|
||||
|
||||
return "\n".join(facts).join(['\n', '\n'])
|
||||
|
||||
|
||||
def start(update, context):
|
||||
def start(update: Update, context: 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?",
|
||||
reply_markup=markup)
|
||||
reply_markup=markup,
|
||||
)
|
||||
|
||||
return CHOOSING
|
||||
|
||||
|
||||
def regular_choice(update, context):
|
||||
def regular_choice(update: Update, context: CallbackContext) -> int:
|
||||
text = update.message.text
|
||||
context.user_data['choice'] = text
|
||||
update.message.reply_text(
|
||||
'Your {}? Yes, I would love to hear about that!'.format(text.lower()))
|
||||
update.message.reply_text(f'Your {text.lower()}? Yes, I would love to hear about that!')
|
||||
|
||||
return TYPING_REPLY
|
||||
|
||||
|
||||
def custom_choice(update, context):
|
||||
update.message.reply_text('Alright, please send me the category first, '
|
||||
'for example "Most impressive skill"')
|
||||
def custom_choice(update: Update, context: CallbackContext) -> int:
|
||||
update.message.reply_text(
|
||||
'Alright, please send me the category first, ' 'for example "Most impressive skill"'
|
||||
)
|
||||
|
||||
return TYPING_CHOICE
|
||||
|
||||
|
||||
def received_information(update, context):
|
||||
def received_information(update: Update, context: CallbackContext) -> int:
|
||||
user_data = context.user_data
|
||||
text = update.message.text
|
||||
category = user_data['choice']
|
||||
user_data[category] = text
|
||||
del user_data['choice']
|
||||
|
||||
update.message.reply_text("Neat! Just so you know, this is what you already told me:"
|
||||
"{} You can tell me more, or change your opinion"
|
||||
" on something.".format(facts_to_str(user_data)),
|
||||
reply_markup=markup)
|
||||
update.message.reply_text(
|
||||
"Neat! Just so you know, this is what you already told me:"
|
||||
f"{facts_to_str(user_data)} You can tell me more, or change your opinion"
|
||||
" on something.",
|
||||
reply_markup=markup,
|
||||
)
|
||||
|
||||
return CHOOSING
|
||||
|
||||
|
||||
def done(update, context):
|
||||
def done(update: Update, context: CallbackContext) -> int:
|
||||
user_data = context.user_data
|
||||
if 'choice' in user_data:
|
||||
del user_data['choice']
|
||||
|
||||
update.message.reply_text("I learned these facts about you:"
|
||||
"{}"
|
||||
"Until next time!".format(facts_to_str(user_data)))
|
||||
update.message.reply_text(
|
||||
f"I learned these facts about you: {facts_to_str(user_data)} Until next time!"
|
||||
)
|
||||
|
||||
user_data.clear()
|
||||
return ConversationHandler.END
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
# Create the Updater and pass it your bot's token.
|
||||
# Make sure to set use_context=True to use the new context based callbacks
|
||||
# Post version 12 this will no longer be necessary
|
||||
updater = Updater("TOKEN", use_context=True)
|
||||
|
||||
# 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(
|
||||
entry_points=[CommandHandler('start', start)],
|
||||
|
||||
states={
|
||||
CHOOSING: [MessageHandler(Filters.regex('^(Age|Favourite colour|Number of siblings)$'),
|
||||
regular_choice),
|
||||
MessageHandler(Filters.regex('^Something else...$'),
|
||||
custom_choice)
|
||||
],
|
||||
|
||||
CHOOSING: [
|
||||
MessageHandler(
|
||||
Filters.regex('^(Age|Favourite colour|Number of siblings)$'), regular_choice
|
||||
),
|
||||
MessageHandler(Filters.regex('^Something else...$'), custom_choice),
|
||||
],
|
||||
TYPING_CHOICE: [
|
||||
MessageHandler(Filters.text & ~(Filters.command | Filters.regex('^Done$')),
|
||||
regular_choice)],
|
||||
|
||||
MessageHandler(
|
||||
Filters.text & ~(Filters.command | Filters.regex('^Done$')), regular_choice
|
||||
)
|
||||
],
|
||||
TYPING_REPLY: [
|
||||
MessageHandler(Filters.text & ~(Filters.command | Filters.regex('^Done$')),
|
||||
received_information)],
|
||||
MessageHandler(
|
||||
Filters.text & ~(Filters.command | Filters.regex('^Done$')),
|
||||
received_information,
|
||||
)
|
||||
],
|
||||
},
|
||||
|
||||
fallbacks=[MessageHandler(Filters.regex('^Done$'), done)]
|
||||
fallbacks=[MessageHandler(Filters.regex('^Done$'), done)],
|
||||
)
|
||||
|
||||
dp.add_handler(conv_handler)
|
||||
dispatcher.add_handler(conv_handler)
|
||||
|
||||
# Start the Bot
|
||||
updater.start_polling()
|
||||
|
||||
+29
-22
@@ -1,5 +1,8 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# pylint: disable=W0613, C0116
|
||||
# type: ignore[union-attr]
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
"""Bot that explains Telegram's "Deep Linking Parameters" functionality.
|
||||
|
||||
@@ -19,14 +22,15 @@ bot.
|
||||
|
||||
import logging
|
||||
|
||||
from telegram import ParseMode, InlineKeyboardMarkup, InlineKeyboardButton
|
||||
from telegram.ext import Updater, CommandHandler, Filters
|
||||
from telegram import ParseMode, InlineKeyboardMarkup, InlineKeyboardButton, Update
|
||||
from telegram.ext import Updater, CommandHandler, Filters, CallbackContext
|
||||
|
||||
# Enable logging
|
||||
from telegram.utils import helpers
|
||||
|
||||
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
level=logging.INFO)
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -36,7 +40,7 @@ USING_ENTITIES = 'using-entities-here'
|
||||
SO_COOL = 'so-cool'
|
||||
|
||||
|
||||
def start(update, context):
|
||||
def start(update: Update, context: CallbackContext) -> None:
|
||||
"""Send a deep-linked URL when the command /start is issued."""
|
||||
bot = context.bot
|
||||
url = helpers.create_deep_linked_url(bot.get_me().username, CHECK_THIS_OUT, group=True)
|
||||
@@ -44,32 +48,34 @@ def start(update, context):
|
||||
update.message.reply_text(text)
|
||||
|
||||
|
||||
def deep_linked_level_1(update, context):
|
||||
def deep_linked_level_1(update: Update, context: CallbackContext) -> None:
|
||||
"""Reached through the CHECK_THIS_OUT payload"""
|
||||
bot = context.bot
|
||||
url = helpers.create_deep_linked_url(bot.get_me().username, SO_COOL)
|
||||
text = "Awesome, you just accessed hidden functionality! " \
|
||||
" Now let's get back to the private chat."
|
||||
text = (
|
||||
"Awesome, you just accessed hidden functionality! "
|
||||
" Now let's get back to the private chat."
|
||||
)
|
||||
keyboard = InlineKeyboardMarkup.from_button(
|
||||
InlineKeyboardButton(text='Continue here!', url=url)
|
||||
)
|
||||
update.message.reply_text(text, reply_markup=keyboard)
|
||||
|
||||
|
||||
def deep_linked_level_2(update, context):
|
||||
def deep_linked_level_2(update: Update, context: CallbackContext) -> None:
|
||||
"""Reached through the SO_COOL payload"""
|
||||
bot = context.bot
|
||||
url = helpers.create_deep_linked_url(bot.get_me().username, USING_ENTITIES)
|
||||
text = "You can also mask the deep-linked URLs as links: " \
|
||||
"[▶️ CLICK HERE]({}).".format(url)
|
||||
text = f"You can also mask the deep-linked URLs as links: [▶️ CLICK HERE]({url})."
|
||||
update.message.reply_text(text, parse_mode=ParseMode.MARKDOWN, disable_web_page_preview=True)
|
||||
|
||||
|
||||
def deep_linked_level_3(update, context):
|
||||
def deep_linked_level_3(update: Update, context: CallbackContext) -> None:
|
||||
"""Reached through the USING_ENTITIES payload"""
|
||||
payload = context.args
|
||||
update.message.reply_text("Congratulations! This is as deep as it gets 👏🏻\n\n"
|
||||
"The payload was: {}".format(payload))
|
||||
update.message.reply_text(
|
||||
f"Congratulations! This is as deep as it gets 👏🏻\n\nThe payload was: {payload}"
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
@@ -78,25 +84,26 @@ def main():
|
||||
updater = Updater("TOKEN", use_context=True)
|
||||
|
||||
# Get the dispatcher to register handlers
|
||||
dp = updater.dispatcher
|
||||
dispatcher = updater.dispatcher
|
||||
|
||||
# More info on what deep linking actually is (read this first if it's unclear to you):
|
||||
# https://core.telegram.org/bots#deep-linking
|
||||
|
||||
# Register a deep-linking handler
|
||||
dp.add_handler(CommandHandler("start", deep_linked_level_1, Filters.regex(CHECK_THIS_OUT)))
|
||||
dispatcher.add_handler(
|
||||
CommandHandler("start", deep_linked_level_1, Filters.regex(CHECK_THIS_OUT))
|
||||
)
|
||||
|
||||
# This one works with a textual link instead of an URL
|
||||
dp.add_handler(CommandHandler("start", deep_linked_level_2, Filters.regex(SO_COOL)))
|
||||
dispatcher.add_handler(CommandHandler("start", deep_linked_level_2, Filters.regex(SO_COOL)))
|
||||
|
||||
# We can also pass on the deep-linking payload
|
||||
dp.add_handler(CommandHandler("start",
|
||||
deep_linked_level_3,
|
||||
Filters.regex(USING_ENTITIES),
|
||||
pass_args=True))
|
||||
dispatcher.add_handler(
|
||||
CommandHandler("start", deep_linked_level_3, Filters.regex(USING_ENTITIES), pass_args=True)
|
||||
)
|
||||
|
||||
# Make sure the deep-linking handlers occur *before* the normal /start handler.
|
||||
dp.add_handler(CommandHandler("start", start))
|
||||
dispatcher.add_handler(CommandHandler("start", start))
|
||||
|
||||
# Start the Bot
|
||||
updater.start_polling()
|
||||
|
||||
+14
-10
@@ -1,5 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# pylint: disable=W0613, C0116
|
||||
# type: ignore[union-attr]
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
"""
|
||||
@@ -17,28 +19,30 @@ bot.
|
||||
|
||||
import logging
|
||||
|
||||
from telegram.ext import Updater, CommandHandler, MessageHandler, Filters
|
||||
from telegram import Update
|
||||
from telegram.ext import Updater, CommandHandler, MessageHandler, Filters, CallbackContext
|
||||
|
||||
# Enable logging
|
||||
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
level=logging.INFO)
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||
)
|
||||
|
||||
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, context):
|
||||
def start(update: Update, context: CallbackContext) -> None:
|
||||
"""Send a message when the command /start is issued."""
|
||||
update.message.reply_text('Hi!')
|
||||
|
||||
|
||||
def help_command(update, context):
|
||||
def help_command(update: Update, context: CallbackContext) -> None:
|
||||
"""Send a message when the command /help is issued."""
|
||||
update.message.reply_text('Help!')
|
||||
|
||||
|
||||
def echo(update, context):
|
||||
def echo(update: Update, context: CallbackContext) -> None:
|
||||
"""Echo the user message."""
|
||||
update.message.reply_text(update.message.text)
|
||||
|
||||
@@ -51,14 +55,14 @@ def main():
|
||||
updater = Updater("TOKEN", use_context=True)
|
||||
|
||||
# Get the dispatcher to register handlers
|
||||
dp = updater.dispatcher
|
||||
dispatcher = updater.dispatcher
|
||||
|
||||
# on different commands - answer in Telegram
|
||||
dp.add_handler(CommandHandler("start", start))
|
||||
dp.add_handler(CommandHandler("help", help_command))
|
||||
dispatcher.add_handler(CommandHandler("start", start))
|
||||
dispatcher.add_handler(CommandHandler("help", help_command))
|
||||
|
||||
# on noncommand i.e message - echo the message on Telegram
|
||||
dp.add_handler(MessageHandler(Filters.text & ~Filters.command, echo))
|
||||
dispatcher.add_handler(MessageHandler(Filters.text & ~Filters.command, echo))
|
||||
|
||||
# Start the Bot
|
||||
updater.start_polling()
|
||||
|
||||
+23
-23
@@ -1,5 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# pylint: disable=W0613, C0116
|
||||
# type: ignore[union-attr]
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
"""
|
||||
@@ -13,8 +15,9 @@ import traceback
|
||||
from telegram import Update, ParseMode
|
||||
from telegram.ext import Updater, CallbackContext, CommandHandler
|
||||
|
||||
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
level=logging.INFO)
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -26,7 +29,7 @@ BOT_TOKEN = "TOKEN"
|
||||
DEVELOPER_CHAT_ID = 123456789
|
||||
|
||||
|
||||
def error_handler(update: Update, context: CallbackContext):
|
||||
def error_handler(update: Update, 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)
|
||||
@@ -34,36 +37,33 @@ def error_handler(update: Update, context: CallbackContext):
|
||||
# traceback.format_exception returns the usual python message about an exception, but as a
|
||||
# list of strings rather than a single string, so we have to join them together.
|
||||
tb_list = traceback.format_exception(None, context.error, context.error.__traceback__)
|
||||
tb = ''.join(tb_list)
|
||||
tb_string = ''.join(tb_list)
|
||||
|
||||
# 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.
|
||||
message = (
|
||||
'An exception was raised while handling an update\n'
|
||||
'<pre>update = {}</pre>\n\n'
|
||||
'<pre>context.chat_data = {}</pre>\n\n'
|
||||
'<pre>context.user_data = {}</pre>\n\n'
|
||||
'<pre>{}</pre>'
|
||||
).format(
|
||||
html.escape(json.dumps(update.to_dict(), indent=2, ensure_ascii=False)),
|
||||
html.escape(str(context.chat_data)),
|
||||
html.escape(str(context.user_data)),
|
||||
html.escape(tb)
|
||||
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))}'
|
||||
'</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'
|
||||
f'<pre>{html.escape(tb_string)}</pre>'
|
||||
)
|
||||
|
||||
# Finally, send the message
|
||||
context.bot.send_message(chat_id=DEVELOPER_CHAT_ID, text=message, parse_mode=ParseMode.HTML)
|
||||
|
||||
|
||||
def bad_command(update: Update, context: CallbackContext):
|
||||
def bad_command(update: Update, context: CallbackContext) -> None:
|
||||
"""Raise an error to trigger the error handler."""
|
||||
context.bot.wrong_method_name()
|
||||
|
||||
|
||||
def start(update: Update, context: CallbackContext):
|
||||
update.effective_message.reply_html('Use /bad_command to cause an error.\n'
|
||||
'Your chat id is <code>{}</code>.'
|
||||
.format(update.effective_chat.id))
|
||||
def start(update: Update, context: 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():
|
||||
@@ -73,14 +73,14 @@ def main():
|
||||
updater = Updater(BOT_TOKEN, use_context=True)
|
||||
|
||||
# Get the dispatcher to register handlers
|
||||
dp = updater.dispatcher
|
||||
dispatcher = updater.dispatcher
|
||||
|
||||
# Register the commands...
|
||||
dp.add_handler(CommandHandler('start', start))
|
||||
dp.add_handler(CommandHandler('bad_command', bad_command))
|
||||
dispatcher.add_handler(CommandHandler('start', start))
|
||||
dispatcher.add_handler(CommandHandler('bad_command', bad_command))
|
||||
|
||||
# ...and the error handler
|
||||
dp.add_error_handler(error_handler)
|
||||
dispatcher.add_error_handler(error_handler)
|
||||
|
||||
# Start the Bot
|
||||
updater.start_polling()
|
||||
|
||||
+24
-21
@@ -1,5 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# pylint: disable=W0613, C0116
|
||||
# type: ignore[union-attr]
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
"""
|
||||
@@ -15,70 +17,71 @@ bot.
|
||||
import logging
|
||||
from uuid import uuid4
|
||||
|
||||
from telegram import InlineQueryResultArticle, ParseMode, \
|
||||
InputTextMessageContent
|
||||
from telegram.ext import Updater, InlineQueryHandler, CommandHandler
|
||||
from telegram import InlineQueryResultArticle, ParseMode, InputTextMessageContent, Update
|
||||
from telegram.ext import Updater, InlineQueryHandler, CommandHandler, CallbackContext
|
||||
from telegram.utils.helpers import escape_markdown
|
||||
|
||||
# Enable logging
|
||||
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
level=logging.INFO)
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||
)
|
||||
|
||||
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, context):
|
||||
def start(update: Update, context: CallbackContext) -> None:
|
||||
"""Send a message when the command /start is issued."""
|
||||
update.message.reply_text('Hi!')
|
||||
|
||||
|
||||
def help_command(update, context):
|
||||
def help_command(update: Update, context: CallbackContext) -> None:
|
||||
"""Send a message when the command /help is issued."""
|
||||
update.message.reply_text('Help!')
|
||||
|
||||
|
||||
def inlinequery(update, context):
|
||||
def inlinequery(update: Update, context: CallbackContext) -> None:
|
||||
"""Handle the inline query."""
|
||||
query = update.inline_query.query
|
||||
results = [
|
||||
InlineQueryResultArticle(
|
||||
id=uuid4(),
|
||||
title="Caps",
|
||||
input_message_content=InputTextMessageContent(
|
||||
query.upper())),
|
||||
id=uuid4(), title="Caps", input_message_content=InputTextMessageContent(query.upper())
|
||||
),
|
||||
InlineQueryResultArticle(
|
||||
id=uuid4(),
|
||||
title="Bold",
|
||||
input_message_content=InputTextMessageContent(
|
||||
"*{}*".format(escape_markdown(query)),
|
||||
parse_mode=ParseMode.MARKDOWN)),
|
||||
f"*{escape_markdown(query)}*", parse_mode=ParseMode.MARKDOWN
|
||||
),
|
||||
),
|
||||
InlineQueryResultArticle(
|
||||
id=uuid4(),
|
||||
title="Italic",
|
||||
input_message_content=InputTextMessageContent(
|
||||
"_{}_".format(escape_markdown(query)),
|
||||
parse_mode=ParseMode.MARKDOWN))]
|
||||
f"_{escape_markdown(query)}_", parse_mode=ParseMode.MARKDOWN
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
update.inline_query.answer(results)
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
# Create the Updater and pass it your bot's token.
|
||||
# Make sure to set use_context=True to use the new context based callbacks
|
||||
# Post version 12 this will no longer be necessary
|
||||
updater = Updater("TOKEN", use_context=True)
|
||||
|
||||
# Get the dispatcher to register handlers
|
||||
dp = updater.dispatcher
|
||||
dispatcher = updater.dispatcher
|
||||
|
||||
# on different commands - answer in Telegram
|
||||
dp.add_handler(CommandHandler("start", start))
|
||||
dp.add_handler(CommandHandler("help", help_command))
|
||||
dispatcher.add_handler(CommandHandler("start", start))
|
||||
dispatcher.add_handler(CommandHandler("help", help_command))
|
||||
|
||||
# on noncommand i.e message - echo the message on Telegram
|
||||
dp.add_handler(InlineQueryHandler(inlinequery))
|
||||
dispatcher.add_handler(InlineQueryHandler(inlinequery))
|
||||
|
||||
# Start the Bot
|
||||
updater.start_polling()
|
||||
|
||||
+18
-12
@@ -1,5 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# pylint: disable=W0613, C0116
|
||||
# type: ignore[union-attr]
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
"""
|
||||
@@ -7,36 +9,40 @@ Basic example for a bot that uses inline keyboards.
|
||||
"""
|
||||
import logging
|
||||
|
||||
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
|
||||
from telegram.ext import Updater, CommandHandler, CallbackQueryHandler
|
||||
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
|
||||
from telegram.ext import Updater, CommandHandler, CallbackQueryHandler, CallbackContext
|
||||
|
||||
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
level=logging.INFO)
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def start(update, context):
|
||||
keyboard = [[InlineKeyboardButton("Option 1", callback_data='1'),
|
||||
InlineKeyboardButton("Option 2", callback_data='2')],
|
||||
|
||||
[InlineKeyboardButton("Option 3", callback_data='3')]]
|
||||
def start(update: Update, context: CallbackContext) -> None:
|
||||
keyboard = [
|
||||
[
|
||||
InlineKeyboardButton("Option 1", callback_data='1'),
|
||||
InlineKeyboardButton("Option 2", callback_data='2'),
|
||||
],
|
||||
[InlineKeyboardButton("Option 3", callback_data='3')],
|
||||
]
|
||||
|
||||
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||
|
||||
update.message.reply_text('Please choose:', reply_markup=reply_markup)
|
||||
|
||||
|
||||
def button(update, context):
|
||||
def button(update: Update, context: CallbackContext) -> None:
|
||||
query = update.callback_query
|
||||
|
||||
# CallbackQueries need to be answered, even if no notification to the user is needed
|
||||
# Some clients may have trouble otherwise. See https://core.telegram.org/bots/api#callbackquery
|
||||
query.answer()
|
||||
|
||||
query.edit_message_text(text="Selected option: {}".format(query.data))
|
||||
query.edit_message_text(text=f"Selected option: {query.data}")
|
||||
|
||||
|
||||
def help_command(update, context):
|
||||
def help_command(update: Update, context: CallbackContext) -> None:
|
||||
update.message.reply_text("Use /start to test this bot.")
|
||||
|
||||
|
||||
|
||||
+66
-51
@@ -1,5 +1,9 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# pylint: disable=W0613, C0116
|
||||
# type: ignore[union-attr]
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
"""Simple inline keyboard bot with multiple CallbackQueryHandlers.
|
||||
|
||||
This Bot uses the Updater class to handle the bot.
|
||||
@@ -12,13 +16,20 @@ ConversationHandler.
|
||||
Send /start to initiate the conversation.
|
||||
Press Ctrl-C on the command line to stop the bot.
|
||||
"""
|
||||
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
|
||||
from telegram.ext import Updater, CommandHandler, CallbackQueryHandler, ConversationHandler
|
||||
import logging
|
||||
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
|
||||
from telegram.ext import (
|
||||
Updater,
|
||||
CommandHandler,
|
||||
CallbackQueryHandler,
|
||||
ConversationHandler,
|
||||
CallbackContext,
|
||||
)
|
||||
|
||||
# Enable logging
|
||||
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
level=logging.INFO)
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -28,7 +39,7 @@ FIRST, SECOND = range(2)
|
||||
ONE, TWO, THREE, FOUR = range(4)
|
||||
|
||||
|
||||
def start(update, context):
|
||||
def start(update: Update, context: CallbackContext) -> None:
|
||||
"""Send message on `/start`."""
|
||||
# Get user that sent /start and log his name
|
||||
user = update.message.from_user
|
||||
@@ -38,20 +49,19 @@ def start(update, context):
|
||||
# The keyboard is a list of button rows, where each row is in turn
|
||||
# a list (hence `[[...]]`).
|
||||
keyboard = [
|
||||
[InlineKeyboardButton("1", callback_data=str(ONE)),
|
||||
InlineKeyboardButton("2", callback_data=str(TWO))]
|
||||
[
|
||||
InlineKeyboardButton("1", callback_data=str(ONE)),
|
||||
InlineKeyboardButton("2", callback_data=str(TWO)),
|
||||
]
|
||||
]
|
||||
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||
# Send message with text and appended InlineKeyboard
|
||||
update.message.reply_text(
|
||||
"Start handler, Choose a route",
|
||||
reply_markup=reply_markup
|
||||
)
|
||||
update.message.reply_text("Start handler, Choose a route", reply_markup=reply_markup)
|
||||
# Tell ConversationHandler that we're in state `FIRST` now
|
||||
return FIRST
|
||||
|
||||
|
||||
def start_over(update, context):
|
||||
def start_over(update: Update, context: CallbackContext) -> None:
|
||||
"""Prompt same text & keyboard as `start` does but not as new message"""
|
||||
# Get CallbackQuery from Update
|
||||
query = update.callback_query
|
||||
@@ -59,93 +69,94 @@ def start_over(update, context):
|
||||
# Some clients may have trouble otherwise. See https://core.telegram.org/bots/api#callbackquery
|
||||
query.answer()
|
||||
keyboard = [
|
||||
[InlineKeyboardButton("1", callback_data=str(ONE)),
|
||||
InlineKeyboardButton("2", callback_data=str(TWO))]
|
||||
[
|
||||
InlineKeyboardButton("1", callback_data=str(ONE)),
|
||||
InlineKeyboardButton("2", callback_data=str(TWO)),
|
||||
]
|
||||
]
|
||||
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||
# Instead of sending a new message, edit the message that
|
||||
# originated the CallbackQuery. This gives the feeling of an
|
||||
# interactive menu.
|
||||
query.edit_message_text(
|
||||
text="Start handler, Choose a route",
|
||||
reply_markup=reply_markup
|
||||
)
|
||||
query.edit_message_text(text="Start handler, Choose a route", reply_markup=reply_markup)
|
||||
return FIRST
|
||||
|
||||
|
||||
def one(update, context):
|
||||
def one(update: Update, context: CallbackContext) -> None:
|
||||
"""Show new choice of buttons"""
|
||||
query = update.callback_query
|
||||
query.answer()
|
||||
keyboard = [
|
||||
[InlineKeyboardButton("3", callback_data=str(THREE)),
|
||||
InlineKeyboardButton("4", callback_data=str(FOUR))]
|
||||
[
|
||||
InlineKeyboardButton("3", callback_data=str(THREE)),
|
||||
InlineKeyboardButton("4", callback_data=str(FOUR)),
|
||||
]
|
||||
]
|
||||
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||
query.edit_message_text(
|
||||
text="First CallbackQueryHandler, Choose a route",
|
||||
reply_markup=reply_markup
|
||||
text="First CallbackQueryHandler, Choose a route", reply_markup=reply_markup
|
||||
)
|
||||
return FIRST
|
||||
|
||||
|
||||
def two(update, context):
|
||||
def two(update: Update, context: CallbackContext) -> None:
|
||||
"""Show new choice of buttons"""
|
||||
query = update.callback_query
|
||||
query.answer()
|
||||
keyboard = [
|
||||
[InlineKeyboardButton("1", callback_data=str(ONE)),
|
||||
InlineKeyboardButton("3", callback_data=str(THREE))]
|
||||
[
|
||||
InlineKeyboardButton("1", callback_data=str(ONE)),
|
||||
InlineKeyboardButton("3", callback_data=str(THREE)),
|
||||
]
|
||||
]
|
||||
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||
query.edit_message_text(
|
||||
text="Second CallbackQueryHandler, Choose a route",
|
||||
reply_markup=reply_markup
|
||||
text="Second CallbackQueryHandler, Choose a route", reply_markup=reply_markup
|
||||
)
|
||||
return FIRST
|
||||
|
||||
|
||||
def three(update, context):
|
||||
def three(update: Update, context: CallbackContext) -> None:
|
||||
"""Show new choice of buttons"""
|
||||
query = update.callback_query
|
||||
query.answer()
|
||||
keyboard = [
|
||||
[InlineKeyboardButton("Yes, let's do it again!", callback_data=str(ONE)),
|
||||
InlineKeyboardButton("Nah, I've had enough ...", callback_data=str(TWO))]
|
||||
[
|
||||
InlineKeyboardButton("Yes, let's do it again!", callback_data=str(ONE)),
|
||||
InlineKeyboardButton("Nah, I've had enough ...", callback_data=str(TWO)),
|
||||
]
|
||||
]
|
||||
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||
query.edit_message_text(
|
||||
text="Third CallbackQueryHandler. Do want to start over?",
|
||||
reply_markup=reply_markup
|
||||
text="Third CallbackQueryHandler. Do want to start over?", reply_markup=reply_markup
|
||||
)
|
||||
# Transfer to conversation state `SECOND`
|
||||
return SECOND
|
||||
|
||||
|
||||
def four(update, context):
|
||||
def four(update: Update, context: CallbackContext) -> None:
|
||||
"""Show new choice of buttons"""
|
||||
query = update.callback_query
|
||||
query.answer()
|
||||
keyboard = [
|
||||
[InlineKeyboardButton("2", callback_data=str(TWO)),
|
||||
InlineKeyboardButton("4", callback_data=str(FOUR))]
|
||||
[
|
||||
InlineKeyboardButton("2", callback_data=str(TWO)),
|
||||
InlineKeyboardButton("4", callback_data=str(FOUR)),
|
||||
]
|
||||
]
|
||||
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||
query.edit_message_text(
|
||||
text="Fourth CallbackQueryHandler, Choose a route",
|
||||
reply_markup=reply_markup
|
||||
text="Fourth CallbackQueryHandler, Choose a route", reply_markup=reply_markup
|
||||
)
|
||||
return FIRST
|
||||
|
||||
|
||||
def end(update, context):
|
||||
def end(update: Update, context: CallbackContext) -> None:
|
||||
"""Returns `ConversationHandler.END`, which tells the
|
||||
ConversationHandler that the conversation is over"""
|
||||
query = update.callback_query
|
||||
query.answer()
|
||||
query.edit_message_text(
|
||||
text="See you next time!"
|
||||
)
|
||||
query.edit_message_text(text="See you next time!")
|
||||
return ConversationHandler.END
|
||||
|
||||
|
||||
@@ -154,7 +165,7 @@ def main():
|
||||
updater = Updater("TOKEN", use_context=True)
|
||||
|
||||
# Get the dispatcher to register handlers
|
||||
dp = updater.dispatcher
|
||||
dispatcher = updater.dispatcher
|
||||
|
||||
# Setup conversation handler with the states FIRST and SECOND
|
||||
# Use the pattern parameter to pass CallbackQueries with specific
|
||||
@@ -165,19 +176,23 @@ def main():
|
||||
conv_handler = ConversationHandler(
|
||||
entry_points=[CommandHandler('start', start)],
|
||||
states={
|
||||
FIRST: [CallbackQueryHandler(one, pattern='^' + str(ONE) + '$'),
|
||||
CallbackQueryHandler(two, pattern='^' + str(TWO) + '$'),
|
||||
CallbackQueryHandler(three, pattern='^' + str(THREE) + '$'),
|
||||
CallbackQueryHandler(four, pattern='^' + str(FOUR) + '$')],
|
||||
SECOND: [CallbackQueryHandler(start_over, pattern='^' + str(ONE) + '$'),
|
||||
CallbackQueryHandler(end, pattern='^' + str(TWO) + '$')]
|
||||
FIRST: [
|
||||
CallbackQueryHandler(one, pattern='^' + str(ONE) + '$'),
|
||||
CallbackQueryHandler(two, pattern='^' + str(TWO) + '$'),
|
||||
CallbackQueryHandler(three, pattern='^' + str(THREE) + '$'),
|
||||
CallbackQueryHandler(four, pattern='^' + str(FOUR) + '$'),
|
||||
],
|
||||
SECOND: [
|
||||
CallbackQueryHandler(start_over, pattern='^' + str(ONE) + '$'),
|
||||
CallbackQueryHandler(end, pattern='^' + str(TWO) + '$'),
|
||||
],
|
||||
},
|
||||
fallbacks=[CommandHandler('start', start)]
|
||||
fallbacks=[CommandHandler('start', start)],
|
||||
)
|
||||
|
||||
# Add ConversationHandler to dispatcher that will be used for handling
|
||||
# updates
|
||||
dp.add_handler(conv_handler)
|
||||
dispatcher.add_handler(conv_handler)
|
||||
|
||||
# Start the Bot
|
||||
updater.start_polling()
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# pylint: disable=W0613, C0116
|
||||
# type: ignore[union-attr]
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
"""
|
||||
@@ -16,13 +18,21 @@ bot.
|
||||
|
||||
import logging
|
||||
|
||||
from telegram import (InlineKeyboardMarkup, InlineKeyboardButton)
|
||||
from telegram.ext import (Updater, CommandHandler, MessageHandler, Filters,
|
||||
ConversationHandler, CallbackQueryHandler)
|
||||
from telegram import InlineKeyboardMarkup, InlineKeyboardButton, Update
|
||||
from telegram.ext import (
|
||||
Updater,
|
||||
CommandHandler,
|
||||
MessageHandler,
|
||||
Filters,
|
||||
ConversationHandler,
|
||||
CallbackQueryHandler,
|
||||
CallbackContext,
|
||||
)
|
||||
|
||||
# Enable logging
|
||||
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
level=logging.INFO)
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -38,30 +48,46 @@ STOPPING, SHOWING = map(chr, range(8, 10))
|
||||
END = ConversationHandler.END
|
||||
|
||||
# Different constants for this example
|
||||
(PARENTS, CHILDREN, SELF, GENDER, MALE, FEMALE, AGE, NAME, START_OVER, FEATURES,
|
||||
CURRENT_FEATURE, CURRENT_LEVEL) = map(chr, range(10, 22))
|
||||
(
|
||||
PARENTS,
|
||||
CHILDREN,
|
||||
SELF,
|
||||
GENDER,
|
||||
MALE,
|
||||
FEMALE,
|
||||
AGE,
|
||||
NAME,
|
||||
START_OVER,
|
||||
FEATURES,
|
||||
CURRENT_FEATURE,
|
||||
CURRENT_LEVEL,
|
||||
) = map(chr, range(10, 22))
|
||||
|
||||
|
||||
# Helper
|
||||
def _name_switcher(level):
|
||||
if level == PARENTS:
|
||||
return ('Father', 'Mother')
|
||||
elif level == CHILDREN:
|
||||
return ('Brother', 'Sister')
|
||||
return 'Father', 'Mother'
|
||||
return 'Brother', 'Sister'
|
||||
|
||||
|
||||
# Top level conversation callbacks
|
||||
def start(update, context):
|
||||
def start(update: Update, context: CallbackContext) -> None:
|
||||
"""Select an action: Adding parent/child or show data."""
|
||||
text = 'You may add a familiy member, yourself show the gathered data or end the ' \
|
||||
'conversation. To abort, simply type /stop.'
|
||||
buttons = [[
|
||||
InlineKeyboardButton(text='Add family member', callback_data=str(ADDING_MEMBER)),
|
||||
InlineKeyboardButton(text='Add yourself', callback_data=str(ADDING_SELF))
|
||||
], [
|
||||
InlineKeyboardButton(text='Show data', callback_data=str(SHOWING)),
|
||||
InlineKeyboardButton(text='Done', callback_data=str(END))
|
||||
]]
|
||||
text = (
|
||||
'You may add a familiy member, yourself show the gathered data or end the '
|
||||
'conversation. To abort, simply type /stop.'
|
||||
)
|
||||
buttons = [
|
||||
[
|
||||
InlineKeyboardButton(text='Add family member', callback_data=str(ADDING_MEMBER)),
|
||||
InlineKeyboardButton(text='Add yourself', callback_data=str(ADDING_SELF)),
|
||||
],
|
||||
[
|
||||
InlineKeyboardButton(text='Show data', callback_data=str(SHOWING)),
|
||||
InlineKeyboardButton(text='Done', callback_data=str(END)),
|
||||
],
|
||||
]
|
||||
keyboard = InlineKeyboardMarkup(buttons)
|
||||
|
||||
# If we're starting over we don't need do send a new message
|
||||
@@ -69,15 +95,16 @@ def start(update, context):
|
||||
update.callback_query.answer()
|
||||
update.callback_query.edit_message_text(text=text, reply_markup=keyboard)
|
||||
else:
|
||||
update.message.reply_text('Hi, I\'m FamiliyBot and here to help you gather information'
|
||||
'about your family.')
|
||||
update.message.reply_text(
|
||||
'Hi, I\'m FamiliyBot and here to help you gather information' 'about your family.'
|
||||
)
|
||||
update.message.reply_text(text=text, reply_markup=keyboard)
|
||||
|
||||
context.user_data[START_OVER] = False
|
||||
return SELECTING_ACTION
|
||||
|
||||
|
||||
def adding_self(update, context):
|
||||
def adding_self(update: Update, context: CallbackContext) -> None:
|
||||
"""Add information about youself."""
|
||||
context.user_data[CURRENT_LEVEL] = SELF
|
||||
text = 'Okay, please tell me about yourself.'
|
||||
@@ -90,8 +117,9 @@ def adding_self(update, context):
|
||||
return DESCRIBING_SELF
|
||||
|
||||
|
||||
def show_data(update, context):
|
||||
def show_data(update: Update, context: CallbackContext) -> None:
|
||||
"""Pretty print gathered data."""
|
||||
|
||||
def prettyprint(user_data, level):
|
||||
people = user_data.get(level)
|
||||
if not people:
|
||||
@@ -100,41 +128,38 @@ def show_data(update, context):
|
||||
text = ''
|
||||
if level == SELF:
|
||||
for person in user_data[level]:
|
||||
text += '\nName: {}, Age: {}'.format(person.get(NAME, '-'), person.get(AGE, '-'))
|
||||
text += f"\nName: {person.get(NAME, '-')}, Age: {person.get(AGE, '-')}"
|
||||
else:
|
||||
male, female = _name_switcher(level)
|
||||
|
||||
for person in user_data[level]:
|
||||
gender = female if person[GENDER] == FEMALE else male
|
||||
text += '\n{}: Name: {}, Age: {}'.format(gender, person.get(NAME, '-'),
|
||||
person.get(AGE, '-'))
|
||||
text += f"\n{gender}: Name: {person.get(NAME, '-')}, Age: {person.get(AGE, '-')}"
|
||||
return text
|
||||
|
||||
ud = context.user_data
|
||||
text = 'Yourself:' + prettyprint(ud, SELF)
|
||||
text += '\n\nParents:' + prettyprint(ud, PARENTS)
|
||||
text += '\n\nChildren:' + prettyprint(ud, CHILDREN)
|
||||
user_data = context.user_data
|
||||
text = 'Yourself:' + prettyprint(user_data, SELF)
|
||||
text += '\n\nParents:' + prettyprint(user_data, PARENTS)
|
||||
text += '\n\nChildren:' + prettyprint(user_data, CHILDREN)
|
||||
|
||||
buttons = [[
|
||||
InlineKeyboardButton(text='Back', callback_data=str(END))
|
||||
]]
|
||||
buttons = [[InlineKeyboardButton(text='Back', callback_data=str(END))]]
|
||||
keyboard = InlineKeyboardMarkup(buttons)
|
||||
|
||||
update.callback_query.answer()
|
||||
update.callback_query.edit_message_text(text=text, reply_markup=keyboard)
|
||||
ud[START_OVER] = True
|
||||
user_data[START_OVER] = True
|
||||
|
||||
return SHOWING
|
||||
|
||||
|
||||
def stop(update, context):
|
||||
def stop(update: Update, context: CallbackContext) -> None:
|
||||
"""End Conversation by command."""
|
||||
update.message.reply_text('Okay, bye.')
|
||||
|
||||
return END
|
||||
|
||||
|
||||
def end(update, context):
|
||||
def end(update: Update, context: CallbackContext) -> None:
|
||||
"""End conversation from InlineKeyboardButton."""
|
||||
update.callback_query.answer()
|
||||
|
||||
@@ -145,16 +170,19 @@ def end(update, context):
|
||||
|
||||
|
||||
# Second level conversation callbacks
|
||||
def select_level(update, context):
|
||||
def select_level(update: Update, context: CallbackContext) -> None:
|
||||
"""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 = [[
|
||||
InlineKeyboardButton(text='Add parent', callback_data=str(PARENTS)),
|
||||
InlineKeyboardButton(text='Add child', callback_data=str(CHILDREN))
|
||||
], [
|
||||
InlineKeyboardButton(text='Show data', callback_data=str(SHOWING)),
|
||||
InlineKeyboardButton(text='Back', callback_data=str(END))
|
||||
]]
|
||||
buttons = [
|
||||
[
|
||||
InlineKeyboardButton(text='Add parent', callback_data=str(PARENTS)),
|
||||
InlineKeyboardButton(text='Add child', callback_data=str(CHILDREN)),
|
||||
],
|
||||
[
|
||||
InlineKeyboardButton(text='Show data', callback_data=str(SHOWING)),
|
||||
InlineKeyboardButton(text='Back', callback_data=str(END)),
|
||||
],
|
||||
]
|
||||
keyboard = InlineKeyboardMarkup(buttons)
|
||||
|
||||
update.callback_query.answer()
|
||||
@@ -163,7 +191,7 @@ def select_level(update, context):
|
||||
return SELECTING_LEVEL
|
||||
|
||||
|
||||
def select_gender(update, context):
|
||||
def select_gender(update: Update, context: CallbackContext) -> None:
|
||||
"""Choose to add mother or father."""
|
||||
level = update.callback_query.data
|
||||
context.user_data[CURRENT_LEVEL] = level
|
||||
@@ -172,13 +200,16 @@ def select_gender(update, context):
|
||||
|
||||
male, female = _name_switcher(level)
|
||||
|
||||
buttons = [[
|
||||
InlineKeyboardButton(text='Add ' + male, callback_data=str(MALE)),
|
||||
InlineKeyboardButton(text='Add ' + female, callback_data=str(FEMALE))
|
||||
], [
|
||||
InlineKeyboardButton(text='Show data', callback_data=str(SHOWING)),
|
||||
InlineKeyboardButton(text='Back', callback_data=str(END))
|
||||
]]
|
||||
buttons = [
|
||||
[
|
||||
InlineKeyboardButton(text='Add ' + male, callback_data=str(MALE)),
|
||||
InlineKeyboardButton(text='Add ' + female, callback_data=str(FEMALE)),
|
||||
],
|
||||
[
|
||||
InlineKeyboardButton(text='Show data', callback_data=str(SHOWING)),
|
||||
InlineKeyboardButton(text='Back', callback_data=str(END)),
|
||||
],
|
||||
]
|
||||
keyboard = InlineKeyboardMarkup(buttons)
|
||||
|
||||
update.callback_query.answer()
|
||||
@@ -187,7 +218,7 @@ def select_gender(update, context):
|
||||
return SELECTING_GENDER
|
||||
|
||||
|
||||
def end_second_level(update, context):
|
||||
def end_second_level(update: Update, context: CallbackContext) -> None:
|
||||
"""Return to top level conversation."""
|
||||
context.user_data[START_OVER] = True
|
||||
start(update, context)
|
||||
@@ -196,13 +227,15 @@ def end_second_level(update, context):
|
||||
|
||||
|
||||
# Third level callbacks
|
||||
def select_feature(update, context):
|
||||
def select_feature(update: Update, context: CallbackContext) -> None:
|
||||
"""Select a feature to update for the person."""
|
||||
buttons = [[
|
||||
InlineKeyboardButton(text='Name', callback_data=str(NAME)),
|
||||
InlineKeyboardButton(text='Age', callback_data=str(AGE)),
|
||||
InlineKeyboardButton(text='Done', callback_data=str(END)),
|
||||
]]
|
||||
buttons = [
|
||||
[
|
||||
InlineKeyboardButton(text='Name', callback_data=str(NAME)),
|
||||
InlineKeyboardButton(text='Age', callback_data=str(AGE)),
|
||||
InlineKeyboardButton(text='Done', callback_data=str(END)),
|
||||
]
|
||||
]
|
||||
keyboard = InlineKeyboardMarkup(buttons)
|
||||
|
||||
# If we collect features for a new person, clear the cache and save the gender
|
||||
@@ -221,7 +254,7 @@ def select_feature(update, context):
|
||||
return SELECTING_FEATURE
|
||||
|
||||
|
||||
def ask_for_input(update, context):
|
||||
def ask_for_input(update: Update, context: CallbackContext) -> None:
|
||||
"""Prompt user to input data for selected feature."""
|
||||
context.user_data[CURRENT_FEATURE] = update.callback_query.data
|
||||
text = 'Okay, tell me.'
|
||||
@@ -232,27 +265,27 @@ def ask_for_input(update, context):
|
||||
return TYPING
|
||||
|
||||
|
||||
def save_input(update, context):
|
||||
def save_input(update: Update, context: CallbackContext) -> None:
|
||||
"""Save input for feature and return to feature selection."""
|
||||
ud = context.user_data
|
||||
ud[FEATURES][ud[CURRENT_FEATURE]] = update.message.text
|
||||
user_data = context.user_data
|
||||
user_data[FEATURES][user_data[CURRENT_FEATURE]] = update.message.text
|
||||
|
||||
ud[START_OVER] = True
|
||||
user_data[START_OVER] = True
|
||||
|
||||
return select_feature(update, context)
|
||||
|
||||
|
||||
def end_describing(update, context):
|
||||
def end_describing(update: Update, context: CallbackContext) -> None:
|
||||
"""End gathering of features and return to parent conversation."""
|
||||
ud = context.user_data
|
||||
level = ud[CURRENT_LEVEL]
|
||||
if not ud.get(level):
|
||||
ud[level] = []
|
||||
ud[level].append(ud[FEATURES])
|
||||
user_data = context.user_data
|
||||
level = user_data[CURRENT_LEVEL]
|
||||
if not user_data.get(level):
|
||||
user_data[level] = []
|
||||
user_data[level].append(user_data[FEATURES])
|
||||
|
||||
# Print upper level menu
|
||||
if level == SELF:
|
||||
ud[START_OVER] = True
|
||||
user_data[START_OVER] = True
|
||||
start(update, context)
|
||||
else:
|
||||
select_level(update, context)
|
||||
@@ -260,7 +293,7 @@ def end_describing(update, context):
|
||||
return END
|
||||
|
||||
|
||||
def stop_nested(update, context):
|
||||
def stop_nested(update: Update, context: CallbackContext) -> None:
|
||||
"""Completely end conversation from within nested conversation."""
|
||||
update.message.reply_text('Okay, bye.')
|
||||
|
||||
@@ -274,50 +307,47 @@ def main():
|
||||
updater = Updater("TOKEN", use_context=True)
|
||||
|
||||
# Get the dispatcher to register handlers
|
||||
dp = updater.dispatcher
|
||||
dispatcher = updater.dispatcher
|
||||
|
||||
# Set up third level ConversationHandler (collecting features)
|
||||
description_conv = ConversationHandler(
|
||||
entry_points=[CallbackQueryHandler(select_feature,
|
||||
pattern='^' + str(MALE) + '$|^' + str(FEMALE) + '$')],
|
||||
|
||||
entry_points=[
|
||||
CallbackQueryHandler(
|
||||
select_feature, pattern='^' + str(MALE) + '$|^' + str(FEMALE) + '$'
|
||||
)
|
||||
],
|
||||
states={
|
||||
SELECTING_FEATURE: [CallbackQueryHandler(ask_for_input,
|
||||
pattern='^(?!' + str(END) + ').*$')],
|
||||
SELECTING_FEATURE: [
|
||||
CallbackQueryHandler(ask_for_input, pattern='^(?!' + str(END) + ').*$')
|
||||
],
|
||||
TYPING: [MessageHandler(Filters.text & ~Filters.command, save_input)],
|
||||
},
|
||||
|
||||
fallbacks=[
|
||||
CallbackQueryHandler(end_describing, pattern='^' + str(END) + '$'),
|
||||
CommandHandler('stop', stop_nested)
|
||||
CommandHandler('stop', stop_nested),
|
||||
],
|
||||
|
||||
map_to_parent={
|
||||
# Return to second level menu
|
||||
END: SELECTING_LEVEL,
|
||||
# End conversation alltogether
|
||||
STOPPING: STOPPING,
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
# Set up second level ConversationHandler (adding a person)
|
||||
add_member_conv = ConversationHandler(
|
||||
entry_points=[CallbackQueryHandler(select_level,
|
||||
pattern='^' + str(ADDING_MEMBER) + '$')],
|
||||
|
||||
entry_points=[CallbackQueryHandler(select_level, pattern='^' + str(ADDING_MEMBER) + '$')],
|
||||
states={
|
||||
SELECTING_LEVEL: [CallbackQueryHandler(select_gender,
|
||||
pattern='^{}$|^{}$'.format(str(PARENTS),
|
||||
str(CHILDREN)))],
|
||||
SELECTING_GENDER: [description_conv]
|
||||
SELECTING_LEVEL: [
|
||||
CallbackQueryHandler(select_gender, pattern=f'^{PARENTS}$|^{CHILDREN}$')
|
||||
],
|
||||
SELECTING_GENDER: [description_conv],
|
||||
},
|
||||
|
||||
fallbacks=[
|
||||
CallbackQueryHandler(show_data, pattern='^' + str(SHOWING) + '$'),
|
||||
CallbackQueryHandler(end_second_level, pattern='^' + str(END) + '$'),
|
||||
CommandHandler('stop', stop_nested)
|
||||
CommandHandler('stop', stop_nested),
|
||||
],
|
||||
|
||||
map_to_parent={
|
||||
# After showing data return to top level menu
|
||||
SHOWING: SHOWING,
|
||||
@@ -325,7 +355,7 @@ def main():
|
||||
END: SELECTING_ACTION,
|
||||
# End conversation alltogether
|
||||
STOPPING: END,
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
# Set up top level ConversationHandler (selecting action)
|
||||
@@ -339,7 +369,6 @@ def main():
|
||||
]
|
||||
conv_handler = ConversationHandler(
|
||||
entry_points=[CommandHandler('start', start)],
|
||||
|
||||
states={
|
||||
SHOWING: [CallbackQueryHandler(start, pattern='^' + str(END) + '$')],
|
||||
SELECTING_ACTION: selection_handlers,
|
||||
@@ -347,11 +376,10 @@ def main():
|
||||
DESCRIBING_SELF: [description_conv],
|
||||
STOPPING: [CommandHandler('start', start)],
|
||||
},
|
||||
|
||||
fallbacks=[CommandHandler('stop', stop)],
|
||||
)
|
||||
|
||||
dp.add_handler(conv_handler)
|
||||
dispatcher.add_handler(conv_handler)
|
||||
|
||||
# Start the Bot
|
||||
updater.start_polling()
|
||||
|
||||
+38
-18
@@ -1,5 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# pylint: disable=W0613, C0116
|
||||
# type: ignore[union-attr]
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
"""
|
||||
@@ -12,16 +14,18 @@ See https://git.io/fAvYd for how to use Telegram Passport properly with python-t
|
||||
"""
|
||||
import logging
|
||||
|
||||
from telegram.ext import Updater, MessageHandler, Filters
|
||||
from telegram import Update
|
||||
from telegram.ext import Updater, MessageHandler, Filters, CallbackContext
|
||||
|
||||
# Enable logging
|
||||
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
level=logging.DEBUG)
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.DEBUG
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def msg(update, context):
|
||||
def msg(update: Update, context: CallbackContext) -> None:
|
||||
# If we received any passport data
|
||||
passport_data = update.message.passport_data
|
||||
if passport_data:
|
||||
@@ -39,18 +43,28 @@ def msg(update, context):
|
||||
print('Phone: ', data.phone_number)
|
||||
elif data.type == 'email':
|
||||
print('Email: ', data.email)
|
||||
if data.type in ('personal_details', 'passport', 'driver_license', 'identity_card',
|
||||
'internal_passport', 'address'):
|
||||
if data.type in (
|
||||
'personal_details',
|
||||
'passport',
|
||||
'driver_license',
|
||||
'identity_card',
|
||||
'internal_passport',
|
||||
'address',
|
||||
):
|
||||
print(data.type, data.data)
|
||||
if data.type in ('utility_bill', 'bank_statement', 'rental_agreement',
|
||||
'passport_registration', 'temporary_registration'):
|
||||
if data.type in (
|
||||
'utility_bill',
|
||||
'bank_statement',
|
||||
'rental_agreement',
|
||||
'passport_registration',
|
||||
'temporary_registration',
|
||||
):
|
||||
print(data.type, len(data.files), 'files')
|
||||
for file in data.files:
|
||||
actual_file = file.get_file()
|
||||
print(actual_file)
|
||||
actual_file.download()
|
||||
if data.type in ('passport', 'driver_license', 'identity_card',
|
||||
'internal_passport'):
|
||||
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)
|
||||
@@ -60,16 +74,22 @@ def msg(update, context):
|
||||
file = data.reverse_side.get_file()
|
||||
print(data.type, file)
|
||||
file.download()
|
||||
if data.type in ('passport', 'driver_license', 'identity_card',
|
||||
'internal_passport'):
|
||||
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()
|
||||
if data.type in ('passport', 'driver_license', 'identity_card',
|
||||
'internal_passport', 'utility_bill', 'bank_statement',
|
||||
'rental_agreement', 'passport_registration',
|
||||
'temporary_registration'):
|
||||
if data.type in (
|
||||
'passport',
|
||||
'driver_license',
|
||||
'identity_card',
|
||||
'internal_passport',
|
||||
'utility_bill',
|
||||
'bank_statement',
|
||||
'rental_agreement',
|
||||
'passport_registration',
|
||||
'temporary_registration',
|
||||
):
|
||||
print(data.type, len(data.translation), 'translation')
|
||||
for file in data.translation:
|
||||
actual_file = file.get_file()
|
||||
@@ -83,10 +103,10 @@ def main():
|
||||
updater = Updater("TOKEN", private_key=open('private.key', 'rb').read())
|
||||
|
||||
# Get the dispatcher to register handlers
|
||||
dp = updater.dispatcher
|
||||
dispatcher = updater.dispatcher
|
||||
|
||||
# On messages that include passport data call msg
|
||||
dp.add_handler(MessageHandler(Filters.passport_data, msg))
|
||||
dispatcher.add_handler(MessageHandler(Filters.passport_data, msg))
|
||||
|
||||
# Start the Bot
|
||||
updater.start_polling()
|
||||
|
||||
+54
-32
@@ -1,5 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# pylint: disable=W0613, C0116
|
||||
# type: ignore[union-attr]
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
"""
|
||||
@@ -8,24 +10,32 @@ Basic example for a bot that can receive payment from user.
|
||||
|
||||
import logging
|
||||
|
||||
from telegram import (LabeledPrice, ShippingOption)
|
||||
from telegram.ext import (Updater, CommandHandler, MessageHandler,
|
||||
Filters, PreCheckoutQueryHandler, ShippingQueryHandler)
|
||||
from telegram import LabeledPrice, ShippingOption, Update
|
||||
from telegram.ext import (
|
||||
Updater,
|
||||
CommandHandler,
|
||||
MessageHandler,
|
||||
Filters,
|
||||
PreCheckoutQueryHandler,
|
||||
ShippingQueryHandler,
|
||||
CallbackContext,
|
||||
)
|
||||
|
||||
# Enable logging
|
||||
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
level=logging.INFO)
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def start_callback(update, context):
|
||||
def start_callback(update: Update, context: 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)
|
||||
|
||||
|
||||
def start_with_shipping_callback(update, context):
|
||||
def start_with_shipping_callback(update: Update, context: CallbackContext) -> None:
|
||||
chat_id = update.message.chat_id
|
||||
title = "Payment Example"
|
||||
description = "Payment Example using python-telegram-bot"
|
||||
@@ -43,13 +53,24 @@ def start_with_shipping_callback(update, context):
|
||||
|
||||
# optionally pass need_name=True, need_phone_number=True,
|
||||
# need_email=True, need_shipping_address=True, is_flexible=True
|
||||
context.bot.send_invoice(chat_id, title, description, payload,
|
||||
provider_token, start_parameter, currency, prices,
|
||||
need_name=True, need_phone_number=True,
|
||||
need_email=True, need_shipping_address=True, is_flexible=True)
|
||||
context.bot.send_invoice(
|
||||
chat_id,
|
||||
title,
|
||||
description,
|
||||
payload,
|
||||
provider_token,
|
||||
start_parameter,
|
||||
currency,
|
||||
prices,
|
||||
need_name=True,
|
||||
need_phone_number=True,
|
||||
need_email=True,
|
||||
need_shipping_address=True,
|
||||
is_flexible=True,
|
||||
)
|
||||
|
||||
|
||||
def start_without_shipping_callback(update, context):
|
||||
def start_without_shipping_callback(update: Update, context: CallbackContext) -> None:
|
||||
chat_id = update.message.chat_id
|
||||
title = "Payment Example"
|
||||
description = "Payment Example using python-telegram-bot"
|
||||
@@ -66,29 +87,30 @@ def start_without_shipping_callback(update, context):
|
||||
|
||||
# optionally pass need_name=True, need_phone_number=True,
|
||||
# need_email=True, need_shipping_address=True, is_flexible=True
|
||||
context.bot.send_invoice(chat_id, title, description, payload,
|
||||
provider_token, start_parameter, currency, prices)
|
||||
context.bot.send_invoice(
|
||||
chat_id, title, description, payload, provider_token, start_parameter, currency, prices
|
||||
)
|
||||
|
||||
|
||||
def shipping_callback(update, context):
|
||||
def shipping_callback(update: Update, context: CallbackContext) -> None:
|
||||
query = update.shipping_query
|
||||
# check the payload, is this from your bot?
|
||||
if query.invoice_payload != 'Custom-Payload':
|
||||
# answer False pre_checkout_query
|
||||
query.answer(ok=False, error_message="Something went wrong...")
|
||||
return
|
||||
else:
|
||||
options = list()
|
||||
# a single LabeledPrice
|
||||
options.append(ShippingOption('1', 'Shipping Option A', [LabeledPrice('A', 100)]))
|
||||
# an array of LabeledPrice objects
|
||||
price_list = [LabeledPrice('B1', 150), LabeledPrice('B2', 200)]
|
||||
options.append(ShippingOption('2', 'Shipping Option B', price_list))
|
||||
query.answer(ok=True, shipping_options=options)
|
||||
|
||||
options = list()
|
||||
# a single LabeledPrice
|
||||
options.append(ShippingOption('1', 'Shipping Option A', [LabeledPrice('A', 100)]))
|
||||
# an array of LabeledPrice objects
|
||||
price_list = [LabeledPrice('B1', 150), LabeledPrice('B2', 200)]
|
||||
options.append(ShippingOption('2', 'Shipping Option B', price_list))
|
||||
query.answer(ok=True, shipping_options=options)
|
||||
|
||||
|
||||
# after (optional) shipping, it's the pre-checkout
|
||||
def precheckout_callback(update, context):
|
||||
def precheckout_callback(update: Update, context: CallbackContext) -> None:
|
||||
query = update.pre_checkout_query
|
||||
# check the payload, is this from your bot?
|
||||
if query.invoice_payload != 'Custom-Payload':
|
||||
@@ -99,7 +121,7 @@ def precheckout_callback(update, context):
|
||||
|
||||
|
||||
# finally, after contacting the payment provider...
|
||||
def successful_payment_callback(update, context):
|
||||
def successful_payment_callback(update: Update, context: CallbackContext) -> None:
|
||||
# do something after successfully receiving payment?
|
||||
update.message.reply_text("Thank you for your payment!")
|
||||
|
||||
@@ -111,23 +133,23 @@ def main():
|
||||
updater = Updater("TOKEN", use_context=True)
|
||||
|
||||
# Get the dispatcher to register handlers
|
||||
dp = updater.dispatcher
|
||||
dispatcher = updater.dispatcher
|
||||
|
||||
# simple start function
|
||||
dp.add_handler(CommandHandler("start", start_callback))
|
||||
dispatcher.add_handler(CommandHandler("start", start_callback))
|
||||
|
||||
# Add command handler to start the payment invoice
|
||||
dp.add_handler(CommandHandler("shipping", start_with_shipping_callback))
|
||||
dp.add_handler(CommandHandler("noshipping", start_without_shipping_callback))
|
||||
dispatcher.add_handler(CommandHandler("shipping", start_with_shipping_callback))
|
||||
dispatcher.add_handler(CommandHandler("noshipping", start_without_shipping_callback))
|
||||
|
||||
# Optional handler if your product requires shipping
|
||||
dp.add_handler(ShippingQueryHandler(shipping_callback))
|
||||
dispatcher.add_handler(ShippingQueryHandler(shipping_callback))
|
||||
|
||||
# Pre-checkout handler to final check
|
||||
dp.add_handler(PreCheckoutQueryHandler(precheckout_callback))
|
||||
dispatcher.add_handler(PreCheckoutQueryHandler(precheckout_callback))
|
||||
|
||||
# Success! Notify your user!
|
||||
dp.add_handler(MessageHandler(Filters.successful_payment, successful_payment_callback))
|
||||
dispatcher.add_handler(MessageHandler(Filters.successful_payment, successful_payment_callback))
|
||||
|
||||
# Start the Bot
|
||||
updater.start_polling()
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# pylint: disable=W0613, C0116, C0103
|
||||
# type: ignore[union-attr]
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
"""
|
||||
@@ -14,23 +16,34 @@ Press Ctrl-C on the command line or send a signal to the process to stop the
|
||||
bot.
|
||||
"""
|
||||
|
||||
from telegram import ReplyKeyboardMarkup
|
||||
from telegram.ext import (Updater, CommandHandler, MessageHandler, Filters,
|
||||
ConversationHandler, PicklePersistence)
|
||||
|
||||
import logging
|
||||
|
||||
from telegram import ReplyKeyboardMarkup, Update
|
||||
from telegram.ext import (
|
||||
Updater,
|
||||
CommandHandler,
|
||||
MessageHandler,
|
||||
Filters,
|
||||
ConversationHandler,
|
||||
PicklePersistence,
|
||||
CallbackContext,
|
||||
)
|
||||
|
||||
|
||||
# Enable logging
|
||||
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
level=logging.INFO)
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
CHOOSING, TYPING_REPLY, TYPING_CHOICE = range(3)
|
||||
|
||||
reply_keyboard = [['Age', 'Favourite colour'],
|
||||
['Number of siblings', 'Something else...'],
|
||||
['Done']]
|
||||
reply_keyboard = [
|
||||
['Age', 'Favourite colour'],
|
||||
['Number of siblings', 'Something else...'],
|
||||
['Done'],
|
||||
]
|
||||
markup = ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True)
|
||||
|
||||
|
||||
@@ -38,72 +51,80 @@ def facts_to_str(user_data):
|
||||
facts = list()
|
||||
|
||||
for key, value in user_data.items():
|
||||
facts.append('{} - {}'.format(key, value))
|
||||
facts.append(f'{key} - {value}')
|
||||
|
||||
return "\n".join(facts).join(['\n', '\n'])
|
||||
|
||||
|
||||
def start(update, context):
|
||||
def start(update: Update, context: CallbackContext) -> None:
|
||||
reply_text = "Hi! My name is Doctor Botter."
|
||||
if context.user_data:
|
||||
reply_text += " You already told me your {}. Why don't you tell me something more " \
|
||||
"about yourself? Or change anything I " \
|
||||
"already know.".format(", ".join(context.user_data.keys()))
|
||||
reply_text += (
|
||||
f" You already told me your {', '.join(context.user_data.keys())}. Why don't you "
|
||||
f"tell me something more about yourself? Or change anything I already know."
|
||||
)
|
||||
else:
|
||||
reply_text += " I will hold a more complex conversation with you. Why don't you tell me " \
|
||||
"something about yourself?"
|
||||
reply_text += (
|
||||
" I will hold a more complex conversation with you. Why don't you tell me "
|
||||
"something about yourself?"
|
||||
)
|
||||
update.message.reply_text(reply_text, reply_markup=markup)
|
||||
|
||||
return CHOOSING
|
||||
|
||||
|
||||
def regular_choice(update, context):
|
||||
def regular_choice(update: Update, context: CallbackContext) -> None:
|
||||
text = update.message.text.lower()
|
||||
context.user_data['choice'] = text
|
||||
if context.user_data.get(text):
|
||||
reply_text = 'Your {}, I already know the following ' \
|
||||
'about that: {}'.format(text, context.user_data[text])
|
||||
reply_text = (
|
||||
f'Your {text}, I already know the following about that: {context.user_data[text]}'
|
||||
)
|
||||
else:
|
||||
reply_text = 'Your {}? Yes, I would love to hear about that!'.format(text)
|
||||
reply_text = f'Your {text}? Yes, I would love to hear about that!'
|
||||
update.message.reply_text(reply_text)
|
||||
|
||||
return TYPING_REPLY
|
||||
|
||||
|
||||
def custom_choice(update, context):
|
||||
update.message.reply_text('Alright, please send me the category first, '
|
||||
'for example "Most impressive skill"')
|
||||
def custom_choice(update: Update, context: CallbackContext) -> None:
|
||||
update.message.reply_text(
|
||||
'Alright, please send me the category first, ' 'for example "Most impressive skill"'
|
||||
)
|
||||
|
||||
return TYPING_CHOICE
|
||||
|
||||
|
||||
def received_information(update, context):
|
||||
def received_information(update: Update, context: CallbackContext) -> None:
|
||||
text = update.message.text
|
||||
category = context.user_data['choice']
|
||||
context.user_data[category] = text.lower()
|
||||
del context.user_data['choice']
|
||||
|
||||
update.message.reply_text("Neat! Just so you know, this is what you already told me:"
|
||||
"{}"
|
||||
"You can tell me more, or change your opinion on "
|
||||
"something.".format(facts_to_str(context.user_data)),
|
||||
reply_markup=markup)
|
||||
update.message.reply_text(
|
||||
"Neat! Just so you know, this is what you already told me:"
|
||||
f"{facts_to_str(context.user_data)}"
|
||||
"You can tell me more, or change your opinion on "
|
||||
"something.",
|
||||
reply_markup=markup,
|
||||
)
|
||||
|
||||
return CHOOSING
|
||||
|
||||
|
||||
def show_data(update, context):
|
||||
update.message.reply_text("This is what you already told me:"
|
||||
"{}".format(facts_to_str(context.user_data)))
|
||||
def show_data(update: Update, context: CallbackContext) -> None:
|
||||
update.message.reply_text(
|
||||
f"This is what you already told me: {facts_to_str(context.user_data)}"
|
||||
)
|
||||
|
||||
|
||||
def done(update, context):
|
||||
def done(update: Update, context: CallbackContext) -> None:
|
||||
if 'choice' in context.user_data:
|
||||
del context.user_data['choice']
|
||||
|
||||
update.message.reply_text("I learned these facts about you:"
|
||||
"{}"
|
||||
"Until next time!".format(facts_to_str(context.user_data)))
|
||||
update.message.reply_text(
|
||||
"I learned these facts about you:" f"{facts_to_str(context.user_data)}" "Until next time!"
|
||||
)
|
||||
return ConversationHandler.END
|
||||
|
||||
|
||||
@@ -118,26 +139,28 @@ def main():
|
||||
# Add conversation handler with the states CHOOSING, TYPING_CHOICE and TYPING_REPLY
|
||||
conv_handler = ConversationHandler(
|
||||
entry_points=[CommandHandler('start', start)],
|
||||
|
||||
states={
|
||||
CHOOSING: [MessageHandler(Filters.regex('^(Age|Favourite colour|Number of siblings)$'),
|
||||
regular_choice),
|
||||
MessageHandler(Filters.regex('^Something else...$'),
|
||||
custom_choice),
|
||||
],
|
||||
|
||||
CHOOSING: [
|
||||
MessageHandler(
|
||||
Filters.regex('^(Age|Favourite colour|Number of siblings)$'), regular_choice
|
||||
),
|
||||
MessageHandler(Filters.regex('^Something else...$'), custom_choice),
|
||||
],
|
||||
TYPING_CHOICE: [
|
||||
MessageHandler(Filters.text & ~(Filters.command | Filters.regex('^Done$')),
|
||||
regular_choice)],
|
||||
|
||||
MessageHandler(
|
||||
Filters.text & ~(Filters.command | Filters.regex('^Done$')), regular_choice
|
||||
)
|
||||
],
|
||||
TYPING_REPLY: [
|
||||
MessageHandler(Filters.text & ~(Filters.command | Filters.regex('^Done$')),
|
||||
received_information)],
|
||||
MessageHandler(
|
||||
Filters.text & ~(Filters.command | Filters.regex('^Done$')),
|
||||
received_information,
|
||||
)
|
||||
],
|
||||
},
|
||||
|
||||
fallbacks=[MessageHandler(Filters.regex('^Done$'), done)],
|
||||
name="my_conversation",
|
||||
persistent=True
|
||||
persistent=True,
|
||||
)
|
||||
|
||||
dp.add_handler(conv_handler)
|
||||
|
||||
+79
-46
@@ -1,5 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# pylint: disable=W0613, C0116
|
||||
# type: ignore[union-attr]
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
"""
|
||||
@@ -9,34 +11,62 @@ one the user sends the bot
|
||||
"""
|
||||
import logging
|
||||
|
||||
from telegram import (Poll, ParseMode, KeyboardButton, KeyboardButtonPollType,
|
||||
ReplyKeyboardMarkup, ReplyKeyboardRemove)
|
||||
from telegram.ext import (Updater, CommandHandler, PollAnswerHandler, PollHandler, MessageHandler,
|
||||
Filters)
|
||||
from telegram import (
|
||||
Poll,
|
||||
ParseMode,
|
||||
KeyboardButton,
|
||||
KeyboardButtonPollType,
|
||||
ReplyKeyboardMarkup,
|
||||
ReplyKeyboardRemove,
|
||||
Update,
|
||||
)
|
||||
from telegram.ext import (
|
||||
Updater,
|
||||
CommandHandler,
|
||||
PollAnswerHandler,
|
||||
PollHandler,
|
||||
MessageHandler,
|
||||
Filters,
|
||||
CallbackContext,
|
||||
)
|
||||
|
||||
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
level=logging.INFO)
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def start(update, context):
|
||||
def start(update: Update, context: 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'
|
||||
' to generate a preview for your poll')
|
||||
update.message.reply_text(
|
||||
'Please select /poll to get a Poll, /quiz to get a Quiz or /preview'
|
||||
' to generate a preview for your poll'
|
||||
)
|
||||
|
||||
|
||||
def poll(update, context):
|
||||
def poll(update: Update, context: CallbackContext) -> None:
|
||||
"""Sends a predefined poll"""
|
||||
questions = ["Good", "Really good", "Fantastic", "Great"]
|
||||
message = context.bot.send_poll(update.effective_chat.id, "How are you?", questions,
|
||||
is_anonymous=False, allows_multiple_answers=True)
|
||||
message = context.bot.send_poll(
|
||||
update.effective_chat.id,
|
||||
"How are you?",
|
||||
questions,
|
||||
is_anonymous=False,
|
||||
allows_multiple_answers=True,
|
||||
)
|
||||
# Save some info about the poll the bot_data for later use in receive_poll_answer
|
||||
payload = {message.poll.id: {"questions": questions, "message_id": message.message_id,
|
||||
"chat_id": update.effective_chat.id, "answers": 0}}
|
||||
payload = {
|
||||
message.poll.id: {
|
||||
"questions": questions,
|
||||
"message_id": message.message_id,
|
||||
"chat_id": update.effective_chat.id,
|
||||
"answers": 0,
|
||||
}
|
||||
}
|
||||
context.bot_data.update(payload)
|
||||
|
||||
|
||||
def receive_poll_answer(update, context):
|
||||
def receive_poll_answer(update: Update, context: CallbackContext) -> None:
|
||||
"""Summarize a users poll vote"""
|
||||
answer = update.poll_answer
|
||||
poll_id = answer.poll_id
|
||||
@@ -52,29 +82,33 @@ def receive_poll_answer(update, context):
|
||||
answer_string += questions[question_id] + " and "
|
||||
else:
|
||||
answer_string += questions[question_id]
|
||||
context.bot.send_message(context.bot_data[poll_id]["chat_id"],
|
||||
"{} feels {}!".format(update.effective_user.mention_html(),
|
||||
answer_string),
|
||||
parse_mode=ParseMode.HTML)
|
||||
context.bot.send_message(
|
||||
context.bot_data[poll_id]["chat_id"],
|
||||
f"{update.effective_user.mention_html()} feels {answer_string}!",
|
||||
parse_mode=ParseMode.HTML,
|
||||
)
|
||||
context.bot_data[poll_id]["answers"] += 1
|
||||
# Close poll after three participants voted
|
||||
if context.bot_data[poll_id]["answers"] == 3:
|
||||
context.bot.stop_poll(context.bot_data[poll_id]["chat_id"],
|
||||
context.bot_data[poll_id]["message_id"])
|
||||
context.bot.stop_poll(
|
||||
context.bot_data[poll_id]["chat_id"], context.bot_data[poll_id]["message_id"]
|
||||
)
|
||||
|
||||
|
||||
def quiz(update, context):
|
||||
def quiz(update: Update, context: CallbackContext) -> None:
|
||||
"""Send a predefined poll"""
|
||||
questions = ["1", "2", "4", "20"]
|
||||
message = update.effective_message.reply_poll("How many eggs do you need for a cake?",
|
||||
questions, type=Poll.QUIZ, correct_option_id=2)
|
||||
message = update.effective_message.reply_poll(
|
||||
"How many eggs do you need for a cake?", questions, type=Poll.QUIZ, correct_option_id=2
|
||||
)
|
||||
# Save some info about the poll the bot_data for later use in receive_quiz_answer
|
||||
payload = {message.poll.id: {"chat_id": update.effective_chat.id,
|
||||
"message_id": message.message_id}}
|
||||
payload = {
|
||||
message.poll.id: {"chat_id": update.effective_chat.id, "message_id": message.message_id}
|
||||
}
|
||||
context.bot_data.update(payload)
|
||||
|
||||
|
||||
def receive_quiz_answer(update, context):
|
||||
def receive_quiz_answer(update: Update, context: CallbackContext) -> None:
|
||||
"""Close quiz after three participants took it"""
|
||||
# the bot can receive closed poll updates we don't care about
|
||||
if update.poll.is_closed:
|
||||
@@ -88,18 +122,18 @@ def receive_quiz_answer(update, context):
|
||||
context.bot.stop_poll(quiz_data["chat_id"], quiz_data["message_id"])
|
||||
|
||||
|
||||
def preview(update, context):
|
||||
def preview(update: Update, context: 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())]]
|
||||
message = "Press the button to let the bot generate a preview for your poll"
|
||||
# using one_time_keyboard to hide the keyboard
|
||||
update.effective_message.reply_text(message,
|
||||
reply_markup=ReplyKeyboardMarkup(button,
|
||||
one_time_keyboard=True))
|
||||
update.effective_message.reply_text(
|
||||
message, reply_markup=ReplyKeyboardMarkup(button, one_time_keyboard=True)
|
||||
)
|
||||
|
||||
|
||||
def receive_poll(update, context):
|
||||
def receive_poll(update: Update, context: 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
|
||||
@@ -109,30 +143,29 @@ def receive_poll(update, context):
|
||||
options=[o.text for o in actual_poll.options],
|
||||
# with is_closed true, the poll/quiz is immediately closed
|
||||
is_closed=True,
|
||||
reply_markup=ReplyKeyboardRemove()
|
||||
reply_markup=ReplyKeyboardRemove(),
|
||||
)
|
||||
|
||||
|
||||
def help_handler(update, context):
|
||||
def help_handler(update: Update, context: CallbackContext) -> None:
|
||||
"""Display a help message"""
|
||||
update.message.reply_text("Use /quiz, /poll or /preview to test this "
|
||||
"bot.")
|
||||
update.message.reply_text("Use /quiz, /poll or /preview to test this " "bot.")
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
# Create the Updater and pass it your bot's token.
|
||||
# Make sure to set use_context=True to use the new context based callbacks
|
||||
# Post version 12 this will no longer be necessary
|
||||
updater = Updater("TOKEN", use_context=True)
|
||||
dp = updater.dispatcher
|
||||
dp.add_handler(CommandHandler('start', start))
|
||||
dp.add_handler(CommandHandler('poll', poll))
|
||||
dp.add_handler(PollAnswerHandler(receive_poll_answer))
|
||||
dp.add_handler(CommandHandler('quiz', quiz))
|
||||
dp.add_handler(PollHandler(receive_quiz_answer))
|
||||
dp.add_handler(CommandHandler('preview', preview))
|
||||
dp.add_handler(MessageHandler(Filters.poll, receive_poll))
|
||||
dp.add_handler(CommandHandler('help', help_handler))
|
||||
dispatcher = updater.dispatcher
|
||||
dispatcher.add_handler(CommandHandler('start', start))
|
||||
dispatcher.add_handler(CommandHandler('poll', poll))
|
||||
dispatcher.add_handler(PollAnswerHandler(receive_poll_answer))
|
||||
dispatcher.add_handler(CommandHandler('quiz', quiz))
|
||||
dispatcher.add_handler(PollHandler(receive_quiz_answer))
|
||||
dispatcher.add_handler(CommandHandler('preview', preview))
|
||||
dispatcher.add_handler(MessageHandler(Filters.poll, receive_poll))
|
||||
dispatcher.add_handler(CommandHandler('help', help_handler))
|
||||
|
||||
# Start the Bot
|
||||
updater.start_polling()
|
||||
|
||||
+16
-13
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# pylint: disable=W0603
|
||||
"""Simple Bot to reply to Telegram messages.
|
||||
|
||||
This is built on the API wrapper, see rawapibot.py to see the same example built
|
||||
@@ -7,26 +8,28 @@ on the telegram.ext bot framework.
|
||||
This program is dedicated to the public domain under the CC0 license.
|
||||
"""
|
||||
import logging
|
||||
import telegram
|
||||
from telegram.error import NetworkError, Unauthorized
|
||||
from typing import NoReturn
|
||||
from time import sleep
|
||||
|
||||
|
||||
update_id = None
|
||||
import telegram
|
||||
from telegram.error import NetworkError, Unauthorized
|
||||
|
||||
|
||||
def main():
|
||||
UPDATE_ID = None
|
||||
|
||||
|
||||
def main() -> NoReturn:
|
||||
"""Run the bot."""
|
||||
global update_id
|
||||
global UPDATE_ID
|
||||
# Telegram Bot Authorization Token
|
||||
bot = telegram.Bot('TOKEN')
|
||||
|
||||
# get the first pending update_id, this is so we can skip over it in case
|
||||
# we get an "Unauthorized" exception.
|
||||
try:
|
||||
update_id = bot.get_updates()[0].update_id
|
||||
UPDATE_ID = bot.get_updates()[0].update_id
|
||||
except IndexError:
|
||||
update_id = None
|
||||
UPDATE_ID = None
|
||||
|
||||
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
|
||||
@@ -37,15 +40,15 @@ def main():
|
||||
sleep(1)
|
||||
except Unauthorized:
|
||||
# The user has removed or blocked the bot.
|
||||
update_id += 1
|
||||
UPDATE_ID += 1 # type: ignore[operator]
|
||||
|
||||
|
||||
def echo(bot):
|
||||
def echo(bot: telegram.Bot) -> None:
|
||||
"""Echo the message the user sent."""
|
||||
global update_id
|
||||
global UPDATE_ID
|
||||
# Request updates after the last update_id
|
||||
for update in bot.get_updates(offset=update_id, timeout=10):
|
||||
update_id = update.update_id + 1
|
||||
for update in bot.get_updates(offset=UPDATE_ID, timeout=10):
|
||||
UPDATE_ID = update.update_id + 1
|
||||
|
||||
if update.message: # your bot can receive updates without messages
|
||||
# Reply to the message
|
||||
|
||||
+35
-30
@@ -1,5 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# pylint: disable=W0613, C0116
|
||||
# type: ignore[union-attr]
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
"""
|
||||
@@ -20,18 +22,20 @@ bot.
|
||||
|
||||
import logging
|
||||
|
||||
from telegram.ext import Updater, CommandHandler
|
||||
from telegram import Update
|
||||
from telegram.ext import Updater, CommandHandler, CallbackContext
|
||||
|
||||
# Enable logging
|
||||
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
level=logging.INFO)
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||
)
|
||||
|
||||
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, context):
|
||||
def start(update: Update, context: CallbackContext) -> None:
|
||||
update.message.reply_text('Hi! Use /set <seconds> to set a timer')
|
||||
|
||||
|
||||
@@ -41,7 +45,17 @@ def alarm(context):
|
||||
context.bot.send_message(job.context, text='Beep!')
|
||||
|
||||
|
||||
def set_timer(update, context):
|
||||
def remove_job_if_exists(name, context):
|
||||
"""Remove job with given name. Returns whether job was removed."""
|
||||
current_jobs = context.job_queue.get_jobs_by_name(name)
|
||||
if not current_jobs:
|
||||
return False
|
||||
for job in current_jobs:
|
||||
job.schedule_removal()
|
||||
return True
|
||||
|
||||
|
||||
def set_timer(update: Update, context: CallbackContext) -> None:
|
||||
"""Add a job to the queue."""
|
||||
chat_id = update.message.chat_id
|
||||
try:
|
||||
@@ -51,30 +65,24 @@ def set_timer(update, context):
|
||||
update.message.reply_text('Sorry we can not go back to future!')
|
||||
return
|
||||
|
||||
# Add job to queue and stop current one if there is a timer already
|
||||
if 'job' in context.chat_data:
|
||||
old_job = context.chat_data['job']
|
||||
old_job.schedule_removal()
|
||||
new_job = context.job_queue.run_once(alarm, due, context=chat_id)
|
||||
context.chat_data['job'] = new_job
|
||||
job_removed = remove_job_if_exists(str(chat_id), context)
|
||||
context.job_queue.run_once(alarm, due, context=chat_id, name=str(chat_id))
|
||||
|
||||
update.message.reply_text('Timer successfully set!')
|
||||
text = 'Timer successfully set!'
|
||||
if job_removed:
|
||||
text += ' Old one was removed.'
|
||||
update.message.reply_text(text)
|
||||
|
||||
except (IndexError, ValueError):
|
||||
update.message.reply_text('Usage: /set <seconds>')
|
||||
|
||||
|
||||
def unset(update, context):
|
||||
def unset(update: Update, context: CallbackContext) -> None:
|
||||
"""Remove the job if the user changed their mind."""
|
||||
if 'job' not in context.chat_data:
|
||||
update.message.reply_text('You have no active timer')
|
||||
return
|
||||
|
||||
job = context.chat_data['job']
|
||||
job.schedule_removal()
|
||||
del context.chat_data['job']
|
||||
|
||||
update.message.reply_text('Timer successfully unset!')
|
||||
chat_id = update.message.chat_id
|
||||
job_removed = remove_job_if_exists(str(chat_id), context)
|
||||
text = 'Timer successfully cancelled!' if job_removed else 'You have no active timer.'
|
||||
update.message.reply_text(text)
|
||||
|
||||
|
||||
def main():
|
||||
@@ -85,16 +93,13 @@ def main():
|
||||
updater = Updater("TOKEN", use_context=True)
|
||||
|
||||
# Get the dispatcher to register handlers
|
||||
dp = updater.dispatcher
|
||||
dispatcher = updater.dispatcher
|
||||
|
||||
# on different commands - answer in Telegram
|
||||
dp.add_handler(CommandHandler("start", start))
|
||||
dp.add_handler(CommandHandler("help", start))
|
||||
dp.add_handler(CommandHandler("set", set_timer,
|
||||
pass_args=True,
|
||||
pass_job_queue=True,
|
||||
pass_chat_data=True))
|
||||
dp.add_handler(CommandHandler("unset", unset, pass_chat_data=True))
|
||||
dispatcher.add_handler(CommandHandler("start", start))
|
||||
dispatcher.add_handler(CommandHandler("help", start))
|
||||
dispatcher.add_handler(CommandHandler("set", set_timer))
|
||||
dispatcher.add_handler(CommandHandler("unset", unset))
|
||||
|
||||
# Start the Bot
|
||||
updater.start_polling()
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
[tool.black]
|
||||
line-length = 99
|
||||
target-version = ['py36']
|
||||
skip-string-normalization = true
|
||||
|
||||
# We need to force-exclude the negated include pattern
|
||||
# so that pre-commit run --all-files does the correct thing
|
||||
# see https://github.com/psf/black/issues/1778
|
||||
force-exclude = '^(?!/(telegram|examples|tests)/).*\.py$'
|
||||
include = '(telegram|examples|tests)/.*\.py$'
|
||||
exclude = 'telegram/vendor'
|
||||
+11
-8
@@ -1,12 +1,15 @@
|
||||
flake8
|
||||
pep257
|
||||
pylint
|
||||
flaky
|
||||
yapf
|
||||
mypy==0.770
|
||||
pre-commit
|
||||
beautifulsoup4
|
||||
# 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.790
|
||||
|
||||
pytest==4.2.0
|
||||
# Need older attrs version for pytest 4.2.0
|
||||
attrs==19.1.0
|
||||
|
||||
flaky
|
||||
beautifulsoup4
|
||||
pytest-timeout
|
||||
wheel
|
||||
attrs==19.1.0
|
||||
|
||||
@@ -3,3 +3,4 @@ tornado>=5.1
|
||||
cryptography
|
||||
decorator>=4.4.0
|
||||
APScheduler==3.6.3
|
||||
pytz>=2018.6
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
[wheel]
|
||||
universal = 1
|
||||
|
||||
[metadata]
|
||||
license_file = LICENSE.dual
|
||||
|
||||
@@ -15,12 +12,14 @@ upload-dir = docs/build/html
|
||||
[flake8]
|
||||
max-line-length = 99
|
||||
ignore = W503, W605
|
||||
exclude = setup.py, docs/source/conf.py
|
||||
extend-ignore = E203
|
||||
exclude = setup.py, docs/source/conf.py, telegram/vendor
|
||||
|
||||
[yapf]
|
||||
based_on_style = google
|
||||
split_before_logical_operator = True
|
||||
column_limit = 99
|
||||
[pylint]
|
||||
ignore=vendor
|
||||
|
||||
[pylint.message-control]
|
||||
disable = C0330,R0801,R0913,R0904,R0903,R0902,W0511,C0116,C0115,W0703,R0914,R0914,C0302,R0912,R0915,R0401
|
||||
|
||||
[tool:pytest]
|
||||
testpaths = tests
|
||||
@@ -59,3 +58,9 @@ ignore_errors = True
|
||||
# We don't want to clutter the code with 'if self.bot is None: raise RuntimeError()'
|
||||
[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
|
||||
|
||||
[mypy-urllib3.*]
|
||||
ignore_missing_imports = True
|
||||
|
||||
[mypy-apscheduler.*]
|
||||
ignore_missing_imports = True
|
||||
|
||||
@@ -45,6 +45,7 @@ with codecs.open('README.rst', 'r', 'utf-8') as fd:
|
||||
description="We have made you a wrapper you can't refuse",
|
||||
long_description=fd.read(),
|
||||
packages=packages,
|
||||
package_data={'telegram': ['py.typed']},
|
||||
install_requires=requirements,
|
||||
extras_require={
|
||||
'json': 'ujson',
|
||||
@@ -64,4 +65,5 @@ with codecs.open('README.rst', 'r', 'utf-8') as fd:
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
],)
|
||||
|
||||
+166
-53
@@ -23,6 +23,7 @@ from .botcommand import BotCommand
|
||||
from .user import User
|
||||
from .files.chatphoto import ChatPhoto
|
||||
from .chat import Chat
|
||||
from .chatlocation import ChatLocation
|
||||
from .chatmember import ChatMember
|
||||
from .chatpermissions import ChatPermissions
|
||||
from .files.photosize import PhotoSize
|
||||
@@ -50,9 +51,11 @@ from .files.inputfile import InputFile
|
||||
from .files.file import File
|
||||
from .parsemode import ParseMode
|
||||
from .messageentity import MessageEntity
|
||||
from .messageid import MessageId
|
||||
from .games.game import Game
|
||||
from .poll import Poll, PollOption, PollAnswer
|
||||
from .loginurl import LoginUrl
|
||||
from .proximityalerttriggered import ProximityAlertTriggered
|
||||
from .games.callbackgame import CallbackGame
|
||||
from .payment.shippingaddress import ShippingAddress
|
||||
from .payment.orderinfo import OrderInfo
|
||||
@@ -102,63 +105,173 @@ from .payment.shippingquery import ShippingQuery
|
||||
from .webhookinfo import WebhookInfo
|
||||
from .games.gamehighscore import GameHighScore
|
||||
from .update import Update
|
||||
from .files.inputmedia import (InputMedia, InputMediaVideo, InputMediaPhoto, InputMediaAnimation,
|
||||
InputMediaAudio, InputMediaDocument)
|
||||
from .constants import (MAX_MESSAGE_LENGTH, MAX_CAPTION_LENGTH, SUPPORTED_WEBHOOK_PORTS,
|
||||
MAX_FILESIZE_DOWNLOAD, MAX_FILESIZE_UPLOAD,
|
||||
MAX_MESSAGES_PER_SECOND_PER_CHAT, MAX_MESSAGES_PER_SECOND,
|
||||
MAX_MESSAGES_PER_MINUTE_PER_GROUP)
|
||||
from .passport.passportelementerrors import (PassportElementError,
|
||||
PassportElementErrorDataField,
|
||||
PassportElementErrorFile,
|
||||
PassportElementErrorFiles,
|
||||
PassportElementErrorFrontSide,
|
||||
PassportElementErrorReverseSide,
|
||||
PassportElementErrorSelfie,
|
||||
PassportElementErrorTranslationFile,
|
||||
PassportElementErrorTranslationFiles,
|
||||
PassportElementErrorUnspecified)
|
||||
from .passport.credentials import (Credentials,
|
||||
DataCredentials,
|
||||
SecureData,
|
||||
FileCredentials,
|
||||
TelegramDecryptionError)
|
||||
from .files.inputmedia import (
|
||||
InputMedia,
|
||||
InputMediaVideo,
|
||||
InputMediaPhoto,
|
||||
InputMediaAnimation,
|
||||
InputMediaAudio,
|
||||
InputMediaDocument,
|
||||
)
|
||||
from .constants import (
|
||||
MAX_MESSAGE_LENGTH,
|
||||
MAX_CAPTION_LENGTH,
|
||||
SUPPORTED_WEBHOOK_PORTS,
|
||||
MAX_FILESIZE_DOWNLOAD,
|
||||
MAX_FILESIZE_UPLOAD,
|
||||
MAX_MESSAGES_PER_SECOND_PER_CHAT,
|
||||
MAX_MESSAGES_PER_SECOND,
|
||||
MAX_MESSAGES_PER_MINUTE_PER_GROUP,
|
||||
)
|
||||
from .passport.passportelementerrors import (
|
||||
PassportElementError,
|
||||
PassportElementErrorDataField,
|
||||
PassportElementErrorFile,
|
||||
PassportElementErrorFiles,
|
||||
PassportElementErrorFrontSide,
|
||||
PassportElementErrorReverseSide,
|
||||
PassportElementErrorSelfie,
|
||||
PassportElementErrorTranslationFile,
|
||||
PassportElementErrorTranslationFiles,
|
||||
PassportElementErrorUnspecified,
|
||||
)
|
||||
from .passport.credentials import (
|
||||
Credentials,
|
||||
DataCredentials,
|
||||
SecureData,
|
||||
FileCredentials,
|
||||
TelegramDecryptionError,
|
||||
)
|
||||
from .bot import Bot
|
||||
from .version import __version__ # noqa: F401
|
||||
|
||||
__author__ = 'devs@python-telegram-bot.org'
|
||||
|
||||
__all__ = [
|
||||
'Audio', 'Bot', 'Chat', 'ChatMember', 'ChatPermissions', 'ChatAction', 'ChosenInlineResult',
|
||||
'CallbackQuery', 'Contact', 'Document', 'File', 'ForceReply', 'InlineKeyboardButton',
|
||||
'InlineKeyboardMarkup', 'InlineQuery', 'InlineQueryResult', 'InlineQueryResult',
|
||||
'InlineQueryResultArticle', 'InlineQueryResultAudio', 'InlineQueryResultCachedAudio',
|
||||
'InlineQueryResultCachedDocument', 'InlineQueryResultCachedGif',
|
||||
'InlineQueryResultCachedMpeg4Gif', 'InlineQueryResultCachedPhoto',
|
||||
'InlineQueryResultCachedSticker', 'InlineQueryResultCachedVideo',
|
||||
'InlineQueryResultCachedVoice', 'InlineQueryResultContact', 'InlineQueryResultDocument',
|
||||
'InlineQueryResultGif', 'InlineQueryResultLocation', 'InlineQueryResultMpeg4Gif',
|
||||
'InlineQueryResultPhoto', 'InlineQueryResultVenue', 'InlineQueryResultVideo',
|
||||
'InlineQueryResultVoice', 'InlineQueryResultGame', 'InputContactMessageContent', 'InputFile',
|
||||
'InputLocationMessageContent', 'InputMessageContent', 'InputTextMessageContent',
|
||||
'InputVenueMessageContent', 'Location', 'EncryptedCredentials',
|
||||
'PassportFile', 'EncryptedPassportElement', 'PassportData', 'Message', 'MessageEntity',
|
||||
'ParseMode', 'PhotoSize', 'ReplyKeyboardRemove', 'ReplyKeyboardMarkup', 'ReplyMarkup',
|
||||
'Sticker', 'TelegramError', 'TelegramObject', 'Update', 'User', 'UserProfilePhotos', 'Venue',
|
||||
'Video', 'Voice', 'MAX_MESSAGE_LENGTH', 'MAX_CAPTION_LENGTH', 'SUPPORTED_WEBHOOK_PORTS',
|
||||
'MAX_FILESIZE_DOWNLOAD', 'MAX_FILESIZE_UPLOAD', 'MAX_MESSAGES_PER_SECOND_PER_CHAT',
|
||||
'MAX_MESSAGES_PER_SECOND', 'MAX_MESSAGES_PER_MINUTE_PER_GROUP', 'WebhookInfo', 'Animation',
|
||||
'Game', 'GameHighScore', 'VideoNote', 'LabeledPrice', 'SuccessfulPayment', 'ShippingOption',
|
||||
'ShippingAddress', 'PreCheckoutQuery', 'OrderInfo', 'Invoice', 'ShippingQuery', 'ChatPhoto',
|
||||
'StickerSet', 'MaskPosition', 'CallbackGame', 'InputMedia', 'InputMediaPhoto',
|
||||
'InputMediaVideo', 'PassportElementError', 'PassportElementErrorFile',
|
||||
'PassportElementErrorReverseSide', 'PassportElementErrorFrontSide',
|
||||
'PassportElementErrorFiles', 'PassportElementErrorDataField', 'PassportElementErrorFile',
|
||||
'Credentials', 'DataCredentials', 'SecureData', 'FileCredentials', 'IdDocumentData',
|
||||
'PersonalDetails', 'ResidentialAddress', 'InputMediaVideo', 'InputMediaAnimation',
|
||||
'InputMediaAudio', 'InputMediaDocument', 'TelegramDecryptionError',
|
||||
'PassportElementErrorSelfie', 'PassportElementErrorTranslationFile',
|
||||
'PassportElementErrorTranslationFiles', 'PassportElementErrorUnspecified', 'Poll',
|
||||
'PollOption', 'PollAnswer', 'LoginUrl', 'KeyboardButton', 'KeyboardButtonPollType', 'Dice',
|
||||
'BotCommand'
|
||||
'Audio',
|
||||
'Bot',
|
||||
'Chat',
|
||||
'ChatMember',
|
||||
'ChatPermissions',
|
||||
'ChatAction',
|
||||
'ChosenInlineResult',
|
||||
'CallbackQuery',
|
||||
'Contact',
|
||||
'Document',
|
||||
'File',
|
||||
'ForceReply',
|
||||
'InlineKeyboardButton',
|
||||
'InlineKeyboardMarkup',
|
||||
'InlineQuery',
|
||||
'InlineQueryResult',
|
||||
'InlineQueryResult',
|
||||
'InlineQueryResultArticle',
|
||||
'InlineQueryResultAudio',
|
||||
'InlineQueryResultCachedAudio',
|
||||
'InlineQueryResultCachedDocument',
|
||||
'InlineQueryResultCachedGif',
|
||||
'InlineQueryResultCachedMpeg4Gif',
|
||||
'InlineQueryResultCachedPhoto',
|
||||
'InlineQueryResultCachedSticker',
|
||||
'InlineQueryResultCachedVideo',
|
||||
'InlineQueryResultCachedVoice',
|
||||
'InlineQueryResultContact',
|
||||
'InlineQueryResultDocument',
|
||||
'InlineQueryResultGif',
|
||||
'InlineQueryResultLocation',
|
||||
'InlineQueryResultMpeg4Gif',
|
||||
'InlineQueryResultPhoto',
|
||||
'InlineQueryResultVenue',
|
||||
'InlineQueryResultVideo',
|
||||
'InlineQueryResultVoice',
|
||||
'InlineQueryResultGame',
|
||||
'InputContactMessageContent',
|
||||
'InputFile',
|
||||
'InputLocationMessageContent',
|
||||
'InputMessageContent',
|
||||
'InputTextMessageContent',
|
||||
'InputVenueMessageContent',
|
||||
'Location',
|
||||
'ChatLocation',
|
||||
'ProximityAlertTriggered',
|
||||
'EncryptedCredentials',
|
||||
'PassportFile',
|
||||
'EncryptedPassportElement',
|
||||
'PassportData',
|
||||
'Message',
|
||||
'MessageEntity',
|
||||
'ParseMode',
|
||||
'PhotoSize',
|
||||
'ReplyKeyboardRemove',
|
||||
'ReplyKeyboardMarkup',
|
||||
'ReplyMarkup',
|
||||
'Sticker',
|
||||
'TelegramError',
|
||||
'TelegramObject',
|
||||
'Update',
|
||||
'User',
|
||||
'UserProfilePhotos',
|
||||
'Venue',
|
||||
'Video',
|
||||
'Voice',
|
||||
'MAX_MESSAGE_LENGTH',
|
||||
'MAX_CAPTION_LENGTH',
|
||||
'SUPPORTED_WEBHOOK_PORTS',
|
||||
'MAX_FILESIZE_DOWNLOAD',
|
||||
'MAX_FILESIZE_UPLOAD',
|
||||
'MAX_MESSAGES_PER_SECOND_PER_CHAT',
|
||||
'MAX_MESSAGES_PER_SECOND',
|
||||
'MAX_MESSAGES_PER_MINUTE_PER_GROUP',
|
||||
'WebhookInfo',
|
||||
'Animation',
|
||||
'Game',
|
||||
'GameHighScore',
|
||||
'VideoNote',
|
||||
'LabeledPrice',
|
||||
'SuccessfulPayment',
|
||||
'ShippingOption',
|
||||
'ShippingAddress',
|
||||
'PreCheckoutQuery',
|
||||
'OrderInfo',
|
||||
'Invoice',
|
||||
'ShippingQuery',
|
||||
'ChatPhoto',
|
||||
'StickerSet',
|
||||
'MaskPosition',
|
||||
'CallbackGame',
|
||||
'InputMedia',
|
||||
'InputMediaPhoto',
|
||||
'InputMediaVideo',
|
||||
'PassportElementError',
|
||||
'PassportElementErrorFile',
|
||||
'PassportElementErrorReverseSide',
|
||||
'PassportElementErrorFrontSide',
|
||||
'PassportElementErrorFiles',
|
||||
'PassportElementErrorDataField',
|
||||
'PassportElementErrorFile',
|
||||
'Credentials',
|
||||
'DataCredentials',
|
||||
'SecureData',
|
||||
'FileCredentials',
|
||||
'IdDocumentData',
|
||||
'PersonalDetails',
|
||||
'ResidentialAddress',
|
||||
'InputMediaVideo',
|
||||
'InputMediaAnimation',
|
||||
'InputMediaAudio',
|
||||
'InputMediaDocument',
|
||||
'TelegramDecryptionError',
|
||||
'PassportElementErrorSelfie',
|
||||
'PassportElementErrorTranslationFile',
|
||||
'PassportElementErrorTranslationFiles',
|
||||
'PassportElementErrorUnspecified',
|
||||
'Poll',
|
||||
'PollOption',
|
||||
'PollAnswer',
|
||||
'LoginUrl',
|
||||
'KeyboardButton',
|
||||
'KeyboardButtonPollType',
|
||||
'Dice',
|
||||
'BotCommand',
|
||||
'MessageId',
|
||||
]
|
||||
|
||||
+10
-9
@@ -16,20 +16,21 @@
|
||||
#
|
||||
# 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 sys
|
||||
# pylint: disable=E0401, C0114
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import Optional
|
||||
|
||||
import certifi
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from . import __version__ as telegram_ver
|
||||
|
||||
|
||||
def _git_revision() -> Optional[str]:
|
||||
try:
|
||||
output = subprocess.check_output(["git", "describe", "--long", "--tags"],
|
||||
stderr=subprocess.STDOUT)
|
||||
output = subprocess.check_output(
|
||||
["git", "describe", "--long", "--tags"], stderr=subprocess.STDOUT
|
||||
)
|
||||
except (subprocess.SubprocessError, OSError):
|
||||
return None
|
||||
return output.decode().strip()
|
||||
@@ -37,10 +38,10 @@ def _git_revision() -> Optional[str]:
|
||||
|
||||
def print_ver_info() -> None:
|
||||
git_revision = _git_revision()
|
||||
print('python-telegram-bot {}'.format(telegram_ver) + (' ({})'.format(git_revision)
|
||||
if git_revision else ''))
|
||||
print('certifi {}'.format(certifi.__version__)) # type: ignore[attr-defined]
|
||||
print('Python {}'.format(sys.version.replace('\n', ' ')))
|
||||
print(f'python-telegram-bot {telegram_ver}' + (f' ({git_revision})' if git_revision else ''))
|
||||
print(f'certifi {certifi.__version__}') # type: ignore[attr-defined]
|
||||
sys_version = sys.version.replace('\n', ' ')
|
||||
print(f'Python {sys_version}')
|
||||
|
||||
|
||||
def main() -> None:
|
||||
|
||||
+12
-11
@@ -23,9 +23,9 @@ except ImportError:
|
||||
import json # type: ignore[no-redef]
|
||||
|
||||
import warnings
|
||||
from typing import TYPE_CHECKING, Any, List, Optional, Tuple, Type, TypeVar
|
||||
|
||||
from telegram.utils.types import JSONDict
|
||||
from typing import Tuple, Any, Optional, Type, TypeVar, TYPE_CHECKING, List
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Bot
|
||||
@@ -36,7 +36,7 @@ TO = TypeVar('TO', bound='TelegramObject', covariant=True)
|
||||
class TelegramObject:
|
||||
"""Base class for most telegram objects."""
|
||||
|
||||
# def __init__(self, *args: Any, **kwargs: Any):
|
||||
# def __init__(self, *args: Any, **_kwargs: Any):
|
||||
# pass
|
||||
|
||||
_id_attrs: Tuple[Any, ...] = ()
|
||||
@@ -62,13 +62,10 @@ class TelegramObject:
|
||||
|
||||
if cls == TelegramObject:
|
||||
return cls()
|
||||
else:
|
||||
return cls(bot=bot, **data) # type: ignore[call-arg]
|
||||
return cls(bot=bot, **data) # type: ignore[call-arg]
|
||||
|
||||
@classmethod
|
||||
def de_list(cls: Type[TO],
|
||||
data: Optional[List[JSONDict]],
|
||||
bot: 'Bot') -> List[Optional[TO]]:
|
||||
def de_list(cls: Type[TO], data: Optional[List[JSONDict]], bot: 'Bot') -> List[Optional[TO]]:
|
||||
if not data:
|
||||
return []
|
||||
|
||||
@@ -104,11 +101,15 @@ class TelegramObject:
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if isinstance(other, self.__class__):
|
||||
if self._id_attrs == ():
|
||||
warnings.warn("Objects of type {} can not be meaningfully tested for "
|
||||
"equivalence.".format(self.__class__.__name__))
|
||||
warnings.warn(
|
||||
f"Objects of type {self.__class__.__name__} can not be meaningfully tested for"
|
||||
" equivalence."
|
||||
)
|
||||
if other._id_attrs == ():
|
||||
warnings.warn("Objects of type {} can not be meaningfully tested for "
|
||||
"equivalence.".format(other.__class__.__name__))
|
||||
warnings.warn(
|
||||
f"Objects of type {other.__class__.__name__} can not be meaningfully tested"
|
||||
" for equivalence."
|
||||
)
|
||||
return self._id_attrs == other._id_attrs
|
||||
return super().__eq__(other) # pylint: disable=no-member
|
||||
|
||||
|
||||
+1526
-899
File diff suppressed because it is too large
Load Diff
@@ -18,9 +18,10 @@
|
||||
# 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 Bot Command."""
|
||||
from telegram import TelegramObject
|
||||
from typing import Any
|
||||
|
||||
from telegram import TelegramObject
|
||||
|
||||
|
||||
class BotCommand(TelegramObject):
|
||||
"""
|
||||
@@ -38,7 +39,8 @@ class BotCommand(TelegramObject):
|
||||
English letters, digits and underscores.
|
||||
description (:obj:`str`): Description of the command, 3-256 characters.
|
||||
"""
|
||||
def __init__(self, command: str, description: str, **kwargs: Any):
|
||||
|
||||
def __init__(self, command: str, description: str, **_kwargs: Any):
|
||||
self.command = command
|
||||
self.description = description
|
||||
|
||||
|
||||
+131
-107
@@ -16,14 +16,15 @@
|
||||
#
|
||||
# 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=W0622
|
||||
"""This module contains an object that represents a Telegram CallbackQuery"""
|
||||
from telegram import TelegramObject, Message, User
|
||||
from typing import TYPE_CHECKING, Any, List, Optional, Union
|
||||
|
||||
from telegram import Message, TelegramObject, User
|
||||
from telegram.utils.types import JSONDict
|
||||
from typing import Optional, Any, Union, TYPE_CHECKING, List
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Bot, InlineKeyboardMarkup, GameHighScore
|
||||
from telegram import Bot, GameHighScore, InlineKeyboardMarkup, MessageId
|
||||
|
||||
|
||||
class CallbackQuery(TelegramObject):
|
||||
@@ -78,18 +79,20 @@ class CallbackQuery(TelegramObject):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
id: str,
|
||||
from_user: User,
|
||||
chat_instance: str,
|
||||
message: Message = None,
|
||||
data: str = None,
|
||||
inline_message_id: str = None,
|
||||
game_short_name: str = None,
|
||||
bot: 'Bot' = None,
|
||||
**kwargs: Any):
|
||||
def __init__(
|
||||
self,
|
||||
id: str, # pylint: disable=W0622
|
||||
from_user: User,
|
||||
chat_instance: str,
|
||||
message: Message = None,
|
||||
data: str = None,
|
||||
inline_message_id: str = None,
|
||||
game_short_name: str = None,
|
||||
bot: 'Bot' = None,
|
||||
**_kwargs: Any,
|
||||
):
|
||||
# Required
|
||||
self.id = id
|
||||
self.id = id # pylint: disable=C0103
|
||||
self.from_user = from_user
|
||||
self.chat_instance = chat_instance
|
||||
# Optionals
|
||||
@@ -128,9 +131,7 @@ class CallbackQuery(TelegramObject):
|
||||
def edit_message_text(self, text: str, *args: Any, **kwargs: Any) -> Union[Message, bool]:
|
||||
"""Shortcut for either::
|
||||
|
||||
bot.edit_message_text(text, chat_id=update.callback_query.message.chat_id,
|
||||
message_id=update.callback_query.message.message_id,
|
||||
*args, **kwargs)
|
||||
update.callback_query.message.edit_text(text, *args, **kwargs)
|
||||
|
||||
or::
|
||||
|
||||
@@ -143,20 +144,17 @@ class CallbackQuery(TelegramObject):
|
||||
|
||||
"""
|
||||
if self.inline_message_id:
|
||||
return self.bot.edit_message_text(text, inline_message_id=self.inline_message_id,
|
||||
*args, **kwargs)
|
||||
else:
|
||||
return self.bot.edit_message_text(text, chat_id=self.message.chat_id,
|
||||
message_id=self.message.message_id, *args, **kwargs)
|
||||
return self.bot.edit_message_text(
|
||||
text, inline_message_id=self.inline_message_id, *args, **kwargs
|
||||
)
|
||||
return self.message.edit_text(text, *args, **kwargs)
|
||||
|
||||
def edit_message_caption(self, caption: str, *args: Any,
|
||||
**kwargs: Any) -> Union[Message, bool]:
|
||||
def edit_message_caption(
|
||||
self, caption: str, *args: Any, **kwargs: Any
|
||||
) -> Union[Message, bool]:
|
||||
"""Shortcut for either::
|
||||
|
||||
bot.edit_message_caption(caption=caption,
|
||||
chat_id=update.callback_query.message.chat_id,
|
||||
message_id=update.callback_query.message.message_id,
|
||||
*args, **kwargs)
|
||||
update.callback_query.message.edit_caption(caption, *args, **kwargs)
|
||||
|
||||
or::
|
||||
|
||||
@@ -170,28 +168,30 @@ class CallbackQuery(TelegramObject):
|
||||
|
||||
"""
|
||||
if self.inline_message_id:
|
||||
return self.bot.edit_message_caption(caption=caption,
|
||||
inline_message_id=self.inline_message_id,
|
||||
*args, **kwargs)
|
||||
else:
|
||||
return self.bot.edit_message_caption(caption=caption, chat_id=self.message.chat_id,
|
||||
message_id=self.message.message_id,
|
||||
*args, **kwargs)
|
||||
return self.bot.edit_message_caption(
|
||||
caption=caption, inline_message_id=self.inline_message_id, *args, **kwargs
|
||||
)
|
||||
return self.message.edit_caption(caption=caption, *args, **kwargs)
|
||||
|
||||
def edit_message_reply_markup(self, reply_markup: 'InlineKeyboardMarkup', *args: Any,
|
||||
**kwargs: Any) -> Union[Message, bool]:
|
||||
def edit_message_reply_markup(
|
||||
self, reply_markup: 'InlineKeyboardMarkup', *args: Any, **kwargs: Any
|
||||
) -> Union[Message, bool]:
|
||||
"""Shortcut for either::
|
||||
|
||||
bot.edit_message_reply_markup(chat_id=update.callback_query.message.chat_id,
|
||||
message_id=update.callback_query.message.message_id,
|
||||
reply_markup=reply_markup,
|
||||
*args, **kwargs)
|
||||
update.callback_query.message.edit_reply_markup(
|
||||
reply_markup=reply_markup,
|
||||
*args,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
or::
|
||||
|
||||
bot.edit_message_reply_markup(inline_message_id=update.callback_query.inline_message_id,
|
||||
reply_markup=reply_markup,
|
||||
*args, **kwargs)
|
||||
bot.edit_message_reply_markup
|
||||
inline_message_id=update.callback_query.inline_message_id,
|
||||
reply_markup=reply_markup,
|
||||
*args,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
Returns:
|
||||
:class:`telegram.Message`: On success, if edited message is sent by the bot, the
|
||||
@@ -199,27 +199,22 @@ class CallbackQuery(TelegramObject):
|
||||
|
||||
"""
|
||||
if self.inline_message_id:
|
||||
return self.bot.edit_message_reply_markup(reply_markup=reply_markup,
|
||||
inline_message_id=self.inline_message_id,
|
||||
*args, **kwargs)
|
||||
else:
|
||||
return self.bot.edit_message_reply_markup(reply_markup=reply_markup,
|
||||
chat_id=self.message.chat_id,
|
||||
message_id=self.message.message_id,
|
||||
*args, **kwargs)
|
||||
return self.bot.edit_message_reply_markup(
|
||||
reply_markup=reply_markup,
|
||||
inline_message_id=self.inline_message_id,
|
||||
*args,
|
||||
**kwargs,
|
||||
)
|
||||
return self.message.edit_reply_markup(reply_markup=reply_markup, *args, **kwargs)
|
||||
|
||||
def edit_message_media(self, *args: Any, **kwargs: Any) -> Union[Message, bool]:
|
||||
"""Shortcut for either::
|
||||
|
||||
bot.edit_message_media(chat_id=update.callback_query.message.chat_id,
|
||||
message_id=update.callback_query.message.message_id,
|
||||
media=media,
|
||||
*args, **kwargs)
|
||||
update.callback_query.message.edit_media(*args, **kwargs)
|
||||
|
||||
or::
|
||||
|
||||
bot.edit_message_media(inline_message_id=update.callback_query.inline_message_id,
|
||||
media=media,
|
||||
*args, **kwargs)
|
||||
|
||||
Returns:
|
||||
@@ -228,26 +223,20 @@ class CallbackQuery(TelegramObject):
|
||||
|
||||
"""
|
||||
if self.inline_message_id:
|
||||
return self.bot.edit_message_media(inline_message_id=self.inline_message_id,
|
||||
*args, **kwargs)
|
||||
else:
|
||||
return self.bot.edit_message_media(chat_id=self.message.chat_id,
|
||||
message_id=self.message.message_id,
|
||||
*args, **kwargs)
|
||||
return self.bot.edit_message_media(
|
||||
inline_message_id=self.inline_message_id, *args, **kwargs
|
||||
)
|
||||
return self.message.edit_media(*args, **kwargs)
|
||||
|
||||
def edit_message_live_location(self, *args: Any, **kwargs: Any) -> Union[Message, bool]:
|
||||
"""Shortcut for either::
|
||||
|
||||
bot.edit_message_live_location(chat_id=update.callback_query.message.chat_id,
|
||||
message_id=update.callback_query.message.message_id,
|
||||
reply_markup=reply_markup,
|
||||
*args, **kwargs)
|
||||
update.callback_query.message.edit_live_location(*args, **kwargs)
|
||||
|
||||
or::
|
||||
|
||||
bot.edit_message_live_location(
|
||||
inline_message_id=update.callback_query.inline_message_id,
|
||||
reply_markup=reply_markup,
|
||||
*args, **kwargs
|
||||
)
|
||||
|
||||
@@ -257,26 +246,20 @@ class CallbackQuery(TelegramObject):
|
||||
|
||||
"""
|
||||
if self.inline_message_id:
|
||||
return self.bot.edit_message_live_location(inline_message_id=self.inline_message_id,
|
||||
*args, **kwargs)
|
||||
else:
|
||||
return self.bot.edit_message_live_location(chat_id=self.message.chat_id,
|
||||
message_id=self.message.message_id,
|
||||
*args, **kwargs)
|
||||
return self.bot.edit_message_live_location(
|
||||
inline_message_id=self.inline_message_id, *args, **kwargs
|
||||
)
|
||||
return self.message.edit_live_location(*args, **kwargs)
|
||||
|
||||
def stop_message_live_location(self, *args: Any, **kwargs: Any) -> Union[Message, bool]:
|
||||
"""Shortcut for either::
|
||||
|
||||
bot.stop_message_live_location(chat_id=update.callback_query.message.chat_id,
|
||||
message_id=update.callback_query.message.message_id,
|
||||
reply_markup=reply_markup,
|
||||
*args, **kwargs)
|
||||
update.callback_query.message.stop_live_location(*args, **kwargs)
|
||||
|
||||
or::
|
||||
|
||||
bot.stop_message_live_location(
|
||||
inline_message_id=update.callback_query.inline_message_id,
|
||||
reply_markup=reply_markup,
|
||||
*args, **kwargs
|
||||
)
|
||||
|
||||
@@ -286,25 +269,19 @@ class CallbackQuery(TelegramObject):
|
||||
|
||||
"""
|
||||
if self.inline_message_id:
|
||||
return self.bot.stop_message_live_location(inline_message_id=self.inline_message_id,
|
||||
*args, **kwargs)
|
||||
else:
|
||||
return self.bot.stop_message_live_location(chat_id=self.message.chat_id,
|
||||
message_id=self.message.message_id,
|
||||
*args, **kwargs)
|
||||
return self.bot.stop_message_live_location(
|
||||
inline_message_id=self.inline_message_id, *args, **kwargs
|
||||
)
|
||||
return self.message.stop_live_location(*args, **kwargs)
|
||||
|
||||
def set_game_score(self, *args: Any, **kwargs: Any) -> Union[Message, bool]:
|
||||
"""Shortcut for either::
|
||||
|
||||
bot.set_game_score(chat_id=update.callback_query.message.chat_id,
|
||||
message_id=update.callback_query.message.message_id,
|
||||
reply_markup=reply_markup,
|
||||
*args, **kwargs)
|
||||
update.callback_query.message.set_game_score(*args, **kwargs)
|
||||
|
||||
or::
|
||||
|
||||
bot.set_game_score(inline_message_id=update.callback_query.inline_message_id,
|
||||
reply_markup=reply_markup,
|
||||
*args, **kwargs)
|
||||
|
||||
Returns:
|
||||
@@ -313,25 +290,19 @@ class CallbackQuery(TelegramObject):
|
||||
|
||||
"""
|
||||
if self.inline_message_id:
|
||||
return self.bot.set_game_score(inline_message_id=self.inline_message_id,
|
||||
*args, **kwargs)
|
||||
else:
|
||||
return self.bot.set_game_score(chat_id=self.message.chat_id,
|
||||
message_id=self.message.message_id,
|
||||
*args, **kwargs)
|
||||
return self.bot.set_game_score(
|
||||
inline_message_id=self.inline_message_id, *args, **kwargs
|
||||
)
|
||||
return self.message.set_game_score(*args, **kwargs)
|
||||
|
||||
def get_game_high_scores(self, *args: Any, **kwargs: Any) -> List['GameHighScore']:
|
||||
"""Shortcut for either::
|
||||
|
||||
bot.get_game_high_scores(chat_id=update.callback_query.message.chat_id,
|
||||
message_id=update.callback_query.message.message_id,
|
||||
reply_markup=reply_markup,
|
||||
*args, **kwargs)
|
||||
update.callback_query.message.get_game_high_score(*args, **kwargs)
|
||||
|
||||
or::
|
||||
|
||||
bot.get_game_high_scores(inline_message_id=update.callback_query.inline_message_id,
|
||||
reply_markup=reply_markup,
|
||||
*args, **kwargs)
|
||||
|
||||
Returns:
|
||||
@@ -339,9 +310,62 @@ class CallbackQuery(TelegramObject):
|
||||
|
||||
"""
|
||||
if self.inline_message_id:
|
||||
return self.bot.get_game_high_scores(inline_message_id=self.inline_message_id,
|
||||
*args, **kwargs)
|
||||
else:
|
||||
return self.bot.get_game_high_scores(chat_id=self.message.chat_id,
|
||||
message_id=self.message.message_id,
|
||||
*args, **kwargs)
|
||||
return self.bot.get_game_high_scores(
|
||||
inline_message_id=self.inline_message_id, *args, **kwargs
|
||||
)
|
||||
return self.message.get_game_high_scores(*args, **kwargs)
|
||||
|
||||
def delete_message(self, *args: Any, **kwargs: Any) -> Union[Message, bool]:
|
||||
"""Shortcut for::
|
||||
|
||||
update.callback_query.message.delete(*args, **kwargs)
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
|
||||
"""
|
||||
return self.message.delete(*args, **kwargs)
|
||||
|
||||
def pin_message(self, *args: Any, **kwargs: Any) -> bool:
|
||||
"""Shortcut for::
|
||||
|
||||
bot.pin_chat_message(chat_id=message.chat_id,
|
||||
message_id=message.message_id,
|
||||
*args,
|
||||
**kwargs)
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
|
||||
"""
|
||||
return self.message.pin(*args, **kwargs)
|
||||
|
||||
def unpin_message(self, *args: Any, **kwargs: Any) -> bool:
|
||||
"""Shortcut for::
|
||||
|
||||
bot.unpin_chat_message(chat_id=message.chat_id,
|
||||
message_id=message.message_id,
|
||||
*args,
|
||||
**kwargs)
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
|
||||
"""
|
||||
return self.message.unpin(*args, **kwargs)
|
||||
|
||||
def copy_message(self, chat_id: int, *args: Any, **kwargs: Any) -> 'MessageId':
|
||||
"""Shortcut for::
|
||||
|
||||
update.callback_query.message.copy(
|
||||
chat_id,
|
||||
from_chat_id=update.message.chat_id,
|
||||
message_id=update.message.message_id,
|
||||
*args,
|
||||
**kwargs)
|
||||
|
||||
Returns:
|
||||
:class:`telegram.MessageId`: On success, returns the MessageId of the sent message.
|
||||
|
||||
"""
|
||||
return self.message.copy(chat_id, *args, **kwargs)
|
||||
|
||||
+126
-38
@@ -19,13 +19,16 @@
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains an object that represents a Telegram Chat."""
|
||||
|
||||
from telegram import TelegramObject, ChatPhoto
|
||||
from .chatpermissions import ChatPermissions
|
||||
from typing import TYPE_CHECKING, Any, List, Optional, ClassVar
|
||||
|
||||
from telegram import ChatPhoto, TelegramObject, constants
|
||||
from telegram.utils.types import JSONDict
|
||||
from typing import Any, Optional, List, TYPE_CHECKING
|
||||
|
||||
from .chatpermissions import ChatPermissions
|
||||
from .chatlocation import ChatLocation
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Bot, Message, ChatMember
|
||||
from telegram import Bot, ChatMember, Message, MessageId
|
||||
|
||||
|
||||
class Chat(TelegramObject):
|
||||
@@ -42,10 +45,12 @@ class Chat(TelegramObject):
|
||||
first_name (:obj:`str`): Optional. First name of the other party in a private chat.
|
||||
last_name (:obj:`str`): Optional. Last name of the other party in a private chat.
|
||||
photo (:class:`telegram.ChatPhoto`): Optional. Chat photo.
|
||||
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.
|
||||
pinned_message (:class:`telegram.Message`): Optional. Pinned message, for supergroups.
|
||||
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,
|
||||
for groups and supergroups. Returned only in :meth:`telegram.Bot.get_chat`.
|
||||
slow_mode_delay (:obj:`int`): Optional. For supergroups, the minimum allowed delay between
|
||||
@@ -54,6 +59,11 @@ class Chat(TelegramObject):
|
||||
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.
|
||||
linked_chat_id (:obj:`int`): Optional. Unique identifier for the linked chat, i.e. the
|
||||
discussion group identifier for a channel and vice versa; for supergroups and channel
|
||||
chats. Returned only in :meth:`telegram.Bot.get_chat`.
|
||||
location (:class:`telegram.ChatLocation`): Optional. For supergroups, the location to which
|
||||
the supergroup is connected. Returned only in :meth:`telegram.Bot.get_chat`.
|
||||
|
||||
Args:
|
||||
id (:obj:`int`): Unique identifier for this chat. This number may be greater than 32 bits
|
||||
@@ -69,14 +79,16 @@ class Chat(TelegramObject):
|
||||
last_name(:obj:`str`, optional): Last name of the other party in a private chat.
|
||||
photo (:class:`telegram.ChatPhoto`, optional): Chat photo.
|
||||
Returned only in :meth:`telegram.Bot.get_chat`.
|
||||
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.
|
||||
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`.
|
||||
pinned_message (:class:`telegram.Message`, optional): Pinned message, for groups,
|
||||
supergroups and channels. 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,
|
||||
for groups and supergroups. Returned only in :meth:`telegram.Bot.get_chat`.
|
||||
slow_mode_delay (:obj:`int`, optional): For supergroups, the minimum allowed delay between
|
||||
@@ -87,36 +99,46 @@ class Chat(TelegramObject):
|
||||
Returned only in :meth:`telegram.Bot.get_chat`.
|
||||
can_set_sticker_set (:obj:`bool`, optional): :obj:`True`, if the bot can change group the
|
||||
sticker set. Returned only in :meth:`telegram.Bot.get_chat`.
|
||||
linked_chat_id (:obj:`int`, optional): Unique identifier for the linked chat, i.e. the
|
||||
discussion group identifier for a channel and vice versa; for supergroups and channel
|
||||
chats. Returned only in :meth:`telegram.Bot.get_chat`.
|
||||
location (:class:`telegram.ChatLocation`, optional): For supergroups, the location to which
|
||||
the supergroup is connected. Returned only in :meth:`telegram.Bot.get_chat`.
|
||||
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
||||
|
||||
"""
|
||||
|
||||
PRIVATE: str = 'private'
|
||||
""":obj:`str`: 'private'"""
|
||||
GROUP: str = 'group'
|
||||
""":obj:`str`: 'group'"""
|
||||
SUPERGROUP: str = 'supergroup'
|
||||
""":obj:`str`: 'supergroup'"""
|
||||
CHANNEL: str = 'channel'
|
||||
""":obj:`str`: 'channel'"""
|
||||
PRIVATE: ClassVar[str] = constants.CHAT_PRIVATE
|
||||
""":const:`telegram.constants.CHAT_PRIVATE`"""
|
||||
GROUP: ClassVar[str] = constants.CHAT_GROUP
|
||||
""":const:`telegram.constants.CHAT_GROUP`"""
|
||||
SUPERGROUP: ClassVar[str] = constants.CHAT_SUPERGROUP
|
||||
""":const:`telegram.constants.CHAT_SUPERGROUP`"""
|
||||
CHANNEL: ClassVar[str] = constants.CHAT_CHANNEL
|
||||
""":const:`telegram.constants.CHAT_CHANNEL`"""
|
||||
|
||||
def __init__(self,
|
||||
id: int,
|
||||
type: str,
|
||||
title: str = None,
|
||||
username: str = None,
|
||||
first_name: str = None,
|
||||
last_name: str = None,
|
||||
bot: 'Bot' = None,
|
||||
photo: ChatPhoto = None,
|
||||
description: str = None,
|
||||
invite_link: str = None,
|
||||
pinned_message: 'Message' = None,
|
||||
permissions: ChatPermissions = None,
|
||||
sticker_set_name: str = None,
|
||||
can_set_sticker_set: bool = None,
|
||||
slow_mode_delay: int = None,
|
||||
**kwargs: Any):
|
||||
def __init__(
|
||||
self,
|
||||
id: int,
|
||||
type: str,
|
||||
title: str = None,
|
||||
username: str = None,
|
||||
first_name: str = None,
|
||||
last_name: str = None,
|
||||
bot: 'Bot' = None,
|
||||
photo: ChatPhoto = None,
|
||||
description: str = None,
|
||||
invite_link: str = None,
|
||||
pinned_message: 'Message' = None,
|
||||
permissions: ChatPermissions = None,
|
||||
sticker_set_name: str = None,
|
||||
can_set_sticker_set: bool = None,
|
||||
slow_mode_delay: int = None,
|
||||
bio: str = None,
|
||||
linked_chat_id: int = None,
|
||||
location: ChatLocation = None,
|
||||
**_kwargs: Any,
|
||||
):
|
||||
# Required
|
||||
self.id = int(id)
|
||||
self.type = type
|
||||
@@ -126,8 +148,9 @@ class Chat(TelegramObject):
|
||||
self.first_name = first_name
|
||||
self.last_name = last_name
|
||||
# TODO: Remove (also from tests), when Telegram drops this completely
|
||||
self.all_members_are_administrators = kwargs.get('all_members_are_administrators')
|
||||
self.all_members_are_administrators = _kwargs.get('all_members_are_administrators')
|
||||
self.photo = photo
|
||||
self.bio = bio
|
||||
self.description = description
|
||||
self.invite_link = invite_link
|
||||
self.pinned_message = pinned_message
|
||||
@@ -135,6 +158,8 @@ class Chat(TelegramObject):
|
||||
self.slow_mode_delay = slow_mode_delay
|
||||
self.sticker_set_name = sticker_set_name
|
||||
self.can_set_sticker_set = can_set_sticker_set
|
||||
self.linked_chat_id = linked_chat_id
|
||||
self.location = location
|
||||
|
||||
self.bot = bot
|
||||
self._id_attrs = (self.id,)
|
||||
@@ -144,7 +169,7 @@ class Chat(TelegramObject):
|
||||
""":obj:`str`: Convenience property. If the chat has a :attr:`username`, returns a t.me
|
||||
link of the chat."""
|
||||
if self.username:
|
||||
return "https://t.me/{}".format(self.username)
|
||||
return f"https://t.me/{self.username}"
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
@@ -155,9 +180,11 @@ class Chat(TelegramObject):
|
||||
return None
|
||||
|
||||
data['photo'] = ChatPhoto.de_json(data.get('photo'), bot)
|
||||
from telegram import Message
|
||||
from telegram import Message # pylint: disable=C0415
|
||||
|
||||
data['pinned_message'] = Message.de_json(data.get('pinned_message'), bot)
|
||||
data['permissions'] = ChatPermissions.de_json(data.get('permissions'), bot)
|
||||
data['location'] = ChatLocation.de_json(data.get('location'), bot)
|
||||
|
||||
return cls(bot=bot, **data)
|
||||
|
||||
@@ -243,7 +270,7 @@ class Chat(TelegramObject):
|
||||
Returns:
|
||||
:obj:`bool`: If the action was sent successfully.
|
||||
|
||||
"""
|
||||
"""
|
||||
return self.bot.set_chat_permissions(self.id, *args, **kwargs)
|
||||
|
||||
def set_administrator_custom_title(self, *args: Any, **kwargs: Any) -> bool:
|
||||
@@ -254,9 +281,48 @@ class Chat(TelegramObject):
|
||||
Returns:
|
||||
:obj:`bool`: If the action was sent successfully.
|
||||
|
||||
"""
|
||||
"""
|
||||
return self.bot.set_chat_administrator_custom_title(self.id, *args, **kwargs)
|
||||
|
||||
def pin_message(self, *args: Any, **kwargs: Any) -> bool:
|
||||
"""Shortcut for::
|
||||
|
||||
bot.pin_chat_message(chat_id=update.effective_chat.id,
|
||||
*args,
|
||||
**kwargs)
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
|
||||
"""
|
||||
return self.bot.pin_chat_message(self.id, *args, **kwargs)
|
||||
|
||||
def unpin_message(self, *args: Any, **kwargs: Any) -> bool:
|
||||
"""Shortcut for::
|
||||
|
||||
bot.unpin_chat_message(chat_id=update.effective_chat.id,
|
||||
*args,
|
||||
**kwargs)
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
|
||||
"""
|
||||
return self.bot.unpin_chat_message(self.id, *args, **kwargs)
|
||||
|
||||
def unpin_all_messages(self, *args: Any, **kwargs: Any) -> bool:
|
||||
"""Shortcut for::
|
||||
|
||||
bot.unpin_all_chat_messages(chat_id=update.effective_chat.id,
|
||||
*args,
|
||||
**kwargs)
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
|
||||
"""
|
||||
return self.bot.unpin_all_chat_messages(chat_id=self.id, *args, **kwargs)
|
||||
|
||||
def send_message(self, *args: Any, **kwargs: Any) -> 'Message':
|
||||
"""Shortcut for::
|
||||
|
||||
@@ -457,3 +523,25 @@ class Chat(TelegramObject):
|
||||
|
||||
"""
|
||||
return self.bot.send_poll(self.id, *args, **kwargs)
|
||||
|
||||
def send_copy(self, *args: Any, **kwargs: Any) -> 'MessageId':
|
||||
"""Shortcut for::
|
||||
|
||||
bot.copy_message(chat_id=update.effective_chat.id, *args, **kwargs)
|
||||
|
||||
Returns:
|
||||
:class:`telegram.Message`: On success, instance representing the message posted.
|
||||
|
||||
"""
|
||||
return self.bot.copy_message(chat_id=self.id, *args, **kwargs)
|
||||
|
||||
def copy_message(self, *args: Any, **kwargs: Any) -> 'MessageId':
|
||||
"""Shortcut for::
|
||||
|
||||
bot.copy_message(from_chat_id=update.effective_chat.id, *args, **kwargs)
|
||||
|
||||
Returns:
|
||||
:class:`telegram.Message`: On success, instance representing the message posted.
|
||||
|
||||
"""
|
||||
return self.bot.copy_message(from_chat_id=self.id, *args, **kwargs)
|
||||
|
||||
+22
-20
@@ -18,28 +18,30 @@
|
||||
# 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 ChatAction."""
|
||||
from typing import ClassVar
|
||||
from telegram import constants
|
||||
|
||||
|
||||
class ChatAction:
|
||||
"""Helper class to provide constants for different chat actions."""
|
||||
|
||||
FIND_LOCATION: str = 'find_location'
|
||||
""":obj:`str`: 'find_location'"""
|
||||
RECORD_AUDIO: str = 'record_audio'
|
||||
""":obj:`str`: 'record_audio'"""
|
||||
RECORD_VIDEO: str = 'record_video'
|
||||
""":obj:`str`: 'record_video'"""
|
||||
RECORD_VIDEO_NOTE: str = 'record_video_note'
|
||||
""":obj:`str`: 'record_video_note'"""
|
||||
TYPING: str = 'typing'
|
||||
""":obj:`str`: 'typing'"""
|
||||
UPLOAD_AUDIO: str = 'upload_audio'
|
||||
""":obj:`str`: 'upload_audio'"""
|
||||
UPLOAD_DOCUMENT: str = 'upload_document'
|
||||
""":obj:`str`: 'upload_document'"""
|
||||
UPLOAD_PHOTO: str = 'upload_photo'
|
||||
""":obj:`str`: 'upload_photo'"""
|
||||
UPLOAD_VIDEO: str = 'upload_video'
|
||||
""":obj:`str`: 'upload_video'"""
|
||||
UPLOAD_VIDEO_NOTE: str = 'upload_video_note'
|
||||
""":obj:`str`: 'upload_video_note'"""
|
||||
FIND_LOCATION: ClassVar[str] = constants.CHATACTION_FIND_LOCATION
|
||||
""":const:`telegram.constants.CHATACTION_FIND_LOCATION`"""
|
||||
RECORD_AUDIO: ClassVar[str] = constants.CHATACTION_RECORD_AUDIO
|
||||
""":const:`telegram.constants.CHATACTION_RECORD_AUDIO`"""
|
||||
RECORD_VIDEO: ClassVar[str] = constants.CHATACTION_RECORD_VIDEO
|
||||
""":const:`telegram.constants.CHATACTION_RECORD_VIDEO`"""
|
||||
RECORD_VIDEO_NOTE: ClassVar[str] = constants.CHATACTION_RECORD_VIDEO_NOTE
|
||||
""":const:`telegram.constants.CHATACTION_RECORD_VIDEO_NOTE`"""
|
||||
TYPING: ClassVar[str] = constants.CHATACTION_TYPING
|
||||
""":const:`telegram.constants.CHATACTION_TYPING`"""
|
||||
UPLOAD_AUDIO: ClassVar[str] = constants.CHATACTION_UPLOAD_AUDIO
|
||||
""":const:`telegram.constants.CHATACTION_UPLOAD_AUDIO`"""
|
||||
UPLOAD_DOCUMENT: ClassVar[str] = constants.CHATACTION_UPLOAD_DOCUMENT
|
||||
""":const:`telegram.constants.CHATACTION_UPLOAD_DOCUMENT`"""
|
||||
UPLOAD_PHOTO: ClassVar[str] = constants.CHATACTION_UPLOAD_PHOTO
|
||||
""":const:`telegram.constants.CHATACTION_UPLOAD_PHOTO`"""
|
||||
UPLOAD_VIDEO: ClassVar[str] = constants.CHATACTION_UPLOAD_VIDEO
|
||||
""":const:`telegram.constants.CHATACTION_UPLOAD_VIDEO`"""
|
||||
UPLOAD_VIDEO_NOTE: ClassVar[str] = constants.CHATACTION_UPLOAD_VIDEO_NOTE
|
||||
""":const:`telegram.constants.CHATACTION_UPLOAD_VIDEO_NOTE`"""
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2020
|
||||
# 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 location to which a chat is connected."""
|
||||
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
|
||||
from telegram import TelegramObject
|
||||
from telegram.utils.types import JSONDict
|
||||
|
||||
from .files.location import Location
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Bot
|
||||
|
||||
|
||||
class ChatLocation(TelegramObject):
|
||||
"""This object represents a location to which a chat is connected.
|
||||
|
||||
Objects of this class are comparable in terms of equality. Two objects of this class are
|
||||
considered equal, if their :attr:`location` is equal.
|
||||
|
||||
Attributes:
|
||||
location (:class:`telegram.Location`): The location to which the supergroup is connected.
|
||||
address (:obj:`str`): Location address, as defined by the chat owner
|
||||
|
||||
Args:
|
||||
location (:class:`telegram.Location`): The location to which the supergroup is connected.
|
||||
Can't be a live location.
|
||||
address (:obj:`str`): Location address; 1-64 characters, as defined by the chat owner
|
||||
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
location: Location,
|
||||
address: str,
|
||||
**_kwargs: Any,
|
||||
):
|
||||
self.location = location
|
||||
self.address = address
|
||||
|
||||
self._id_attrs = (self.location,)
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['ChatLocation']:
|
||||
data = cls.parse_data(data)
|
||||
|
||||
if not data:
|
||||
return None
|
||||
|
||||
data['location'] = Location.de_json(data.get('location'), bot)
|
||||
|
||||
return cls(bot=bot, **data)
|
||||
+48
-37
@@ -18,12 +18,12 @@
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains an object that represents a Telegram ChatMember."""
|
||||
import datetime
|
||||
from typing import TYPE_CHECKING, Any, Optional, ClassVar
|
||||
|
||||
from telegram import User, TelegramObject
|
||||
from telegram.utils.helpers import to_timestamp, from_timestamp
|
||||
|
||||
from telegram import TelegramObject, User, constants
|
||||
from telegram.utils.helpers import from_timestamp, to_timestamp
|
||||
from telegram.utils.types import JSONDict
|
||||
from typing import Any, Optional, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Bot
|
||||
|
||||
@@ -38,6 +38,8 @@ class ChatMember(TelegramObject):
|
||||
user (:class:`telegram.User`): Information about the user.
|
||||
status (:obj:`str`): The member's status in the chat.
|
||||
custom_title (:obj:`str`): Optional. Custom title for owner and administrators.
|
||||
is_anonymous (:obj:`bool`): Optional. :obj:`True`, if the user's presence in the chat is
|
||||
hidden.
|
||||
until_date (:class:`datetime.datetime`): Optional. Date when restrictions will be lifted
|
||||
for this user.
|
||||
can_be_edited (:obj:`bool`): Optional. If the bot is allowed to edit administrator
|
||||
@@ -74,6 +76,8 @@ class ChatMember(TelegramObject):
|
||||
'member', 'restricted', 'left' or 'kicked'.
|
||||
custom_title (:obj:`str`, optional): Owner and administrators only.
|
||||
Custom title for this user.
|
||||
is_anonymous (:obj:`bool`, optional): Owner and administrators only. :obj:`True`, if the
|
||||
user's presence in the chat is hidden.
|
||||
until_date (:class:`datetime.datetime`, optional): Restricted and kicked only. Date when
|
||||
restrictions will be lifted for this user.
|
||||
can_be_edited (:obj:`bool`, optional): Administrators only. :obj:`True`, if the bot is
|
||||
@@ -110,44 +114,51 @@ class ChatMember(TelegramObject):
|
||||
may add web page previews to his messages.
|
||||
|
||||
"""
|
||||
ADMINISTRATOR: str = 'administrator'
|
||||
""":obj:`str`: 'administrator'"""
|
||||
CREATOR: str = 'creator'
|
||||
""":obj:`str`: 'creator'"""
|
||||
KICKED: str = 'kicked'
|
||||
""":obj:`str`: 'kicked'"""
|
||||
LEFT: str = 'left'
|
||||
""":obj:`str`: 'left'"""
|
||||
MEMBER: str = 'member'
|
||||
""":obj:`str`: 'member'"""
|
||||
RESTRICTED: str = 'restricted'
|
||||
""":obj:`str`: 'restricted'"""
|
||||
|
||||
def __init__(self,
|
||||
user: User,
|
||||
status: str,
|
||||
until_date: datetime.datetime = None,
|
||||
can_be_edited: bool = None,
|
||||
can_change_info: bool = None,
|
||||
can_post_messages: bool = None,
|
||||
can_edit_messages: bool = None,
|
||||
can_delete_messages: bool = None,
|
||||
can_invite_users: bool = None,
|
||||
can_restrict_members: bool = None,
|
||||
can_pin_messages: bool = None,
|
||||
can_promote_members: bool = None,
|
||||
can_send_messages: bool = None,
|
||||
can_send_media_messages: bool = None,
|
||||
can_send_polls: bool = None,
|
||||
can_send_other_messages: bool = None,
|
||||
can_add_web_page_previews: bool = None,
|
||||
is_member: bool = None,
|
||||
custom_title: str = None,
|
||||
**kwargs: Any):
|
||||
ADMINISTRATOR: ClassVar[str] = constants.CHATMEMBER_ADMINISTRATOR
|
||||
""":const:`telegram.constants.CHATMEMBER_ADMINISTRATOR`"""
|
||||
CREATOR: ClassVar[str] = constants.CHATMEMBER_CREATOR
|
||||
""":const:`telegram.constants.CHATMEMBER_CREATOR`"""
|
||||
KICKED: ClassVar[str] = constants.CHATMEMBER_KICKED
|
||||
""":const:`telegram.constants.CHATMEMBER_KICKED`"""
|
||||
LEFT: ClassVar[str] = constants.CHATMEMBER_LEFT
|
||||
""":const:`telegram.constants.CHATMEMBER_LEFT`"""
|
||||
MEMBER: ClassVar[str] = constants.CHATMEMBER_MEMBER
|
||||
""":const:`telegram.constants.CHATMEMBER_MEMBER`"""
|
||||
RESTRICTED: ClassVar[str] = constants.CHATMEMBER_RESTRICTED
|
||||
""":const:`telegram.constants.CHATMEMBER_RESTRICTED`"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
user: User,
|
||||
status: str,
|
||||
until_date: datetime.datetime = None,
|
||||
can_be_edited: bool = None,
|
||||
can_change_info: bool = None,
|
||||
can_post_messages: bool = None,
|
||||
can_edit_messages: bool = None,
|
||||
can_delete_messages: bool = None,
|
||||
can_invite_users: bool = None,
|
||||
can_restrict_members: bool = None,
|
||||
can_pin_messages: bool = None,
|
||||
can_promote_members: bool = None,
|
||||
can_send_messages: bool = None,
|
||||
can_send_media_messages: bool = None,
|
||||
can_send_polls: bool = None,
|
||||
can_send_other_messages: bool = None,
|
||||
can_add_web_page_previews: bool = None,
|
||||
is_member: bool = None,
|
||||
custom_title: str = None,
|
||||
is_anonymous: bool = None,
|
||||
**_kwargs: Any,
|
||||
):
|
||||
# Required
|
||||
self.user = user
|
||||
self.status = status
|
||||
|
||||
# Optionals
|
||||
self.custom_title = custom_title
|
||||
self.is_anonymous = is_anonymous
|
||||
self.until_date = until_date
|
||||
self.can_be_edited = can_be_edited
|
||||
self.can_change_info = can_change_info
|
||||
|
||||
+15
-12
@@ -18,9 +18,10 @@
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains an object that represents a Telegram ChatPermission."""
|
||||
|
||||
from telegram import TelegramObject
|
||||
from typing import Any
|
||||
|
||||
from telegram import TelegramObject
|
||||
|
||||
|
||||
class ChatPermissions(TelegramObject):
|
||||
"""Describes actions that a non-administrator user is allowed to take in a chat.
|
||||
@@ -77,16 +78,18 @@ class ChatPermissions(TelegramObject):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
can_send_messages: bool = None,
|
||||
can_send_media_messages: bool = None,
|
||||
can_send_polls: bool = None,
|
||||
can_send_other_messages: bool = None,
|
||||
can_add_web_page_previews: bool = None,
|
||||
can_change_info: bool = None,
|
||||
can_invite_users: bool = None,
|
||||
can_pin_messages: bool = None,
|
||||
**kwargs: Any):
|
||||
def __init__(
|
||||
self,
|
||||
can_send_messages: bool = None,
|
||||
can_send_media_messages: bool = None,
|
||||
can_send_polls: bool = None,
|
||||
can_send_other_messages: bool = None,
|
||||
can_add_web_page_previews: bool = None,
|
||||
can_change_info: bool = None,
|
||||
can_invite_users: bool = None,
|
||||
can_pin_messages: bool = None,
|
||||
**_kwargs: Any,
|
||||
):
|
||||
# Required
|
||||
self.can_send_messages = can_send_messages
|
||||
self.can_send_media_messages = can_send_media_messages
|
||||
@@ -105,5 +108,5 @@ class ChatPermissions(TelegramObject):
|
||||
self.can_add_web_page_previews,
|
||||
self.can_change_info,
|
||||
self.can_invite_users,
|
||||
self.can_pin_messages
|
||||
self.can_pin_messages,
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
# pylint: disable=R0902,R0912,R0913
|
||||
# pylint: disable=R0902,R0913
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2020
|
||||
@@ -19,9 +19,11 @@
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains an object that represents a Telegram ChosenInlineResult."""
|
||||
|
||||
from telegram import TelegramObject, User, Location
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
|
||||
from telegram import Location, TelegramObject, User
|
||||
from telegram.utils.types import JSONDict
|
||||
from typing import Any, Optional, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Bot
|
||||
|
||||
@@ -61,13 +63,15 @@ class ChosenInlineResult(TelegramObject):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
result_id: str,
|
||||
from_user: User,
|
||||
query: str,
|
||||
location: Location = None,
|
||||
inline_message_id: str = None,
|
||||
**kwargs: Any):
|
||||
def __init__(
|
||||
self,
|
||||
result_id: str,
|
||||
from_user: User,
|
||||
query: str,
|
||||
location: Location = None,
|
||||
inline_message_id: str = None,
|
||||
**_kwargs: Any,
|
||||
):
|
||||
# Required
|
||||
self.result_id = result_id
|
||||
self.from_user = from_user
|
||||
|
||||
+179
-3
@@ -38,21 +38,197 @@ The following constant have been found by experimentation:
|
||||
Attributes:
|
||||
MAX_MESSAGE_ENTITIES (:obj:`int`): 100 (Beyond this cap telegram will simply ignore further
|
||||
formatting styles)
|
||||
ANONYMOUS_ADMIN_ID (:obj:`int`): ``1087968824`` (User id in groups for anonymous admin)
|
||||
SERVICE_CHAT_ID (:obj:`int`): ``777000`` (Telegram service chat, that also acts as sender of
|
||||
channel posts forwarded to discussion groups)
|
||||
|
||||
The following constants are related to specific classes and are also available
|
||||
as attributes of those classes:
|
||||
|
||||
:class:`telegram.Chat`:
|
||||
|
||||
Attributes:
|
||||
CHAT_PRIVATE (:obj:`str`): 'private'
|
||||
CHAT_GROUP (:obj:`str`): 'group'
|
||||
CHAT_SUPERGROUP (:obj:`str`): 'supergroup'
|
||||
CHAT_CHANNEL (:obj:`str`): 'channel'
|
||||
|
||||
:class:`telegram.ChatAction`:
|
||||
|
||||
Attributes:
|
||||
CHATACTION_FIND_LOCATION (:obj:`str`): 'find_location'
|
||||
CHATACTION_RECORD_AUDIO (:obj:`str`): 'record_audio'
|
||||
CHATACTION_RECORD_VIDEO (:obj:`str`): 'record_video'
|
||||
CHATACTION_RECORD_VIDEO_NOTE (:obj:`str`): 'record_video_note'
|
||||
CHATACTION_TYPING (:obj:`str`): 'typing'
|
||||
CHATACTION_UPLOAD_AUDIO (:obj:`str`): 'upload_audio'
|
||||
CHATACTION_UPLOAD_DOCUMENT (:obj:`str`): 'upload_document'
|
||||
CHATACTION_UPLOAD_PHOTO (:obj:`str`): 'upload_photo'
|
||||
CHATACTION_UPLOAD_VIDEO (:obj:`str`): 'upload_video'
|
||||
CHATACTION_UPLOAD_VIDEO_NOTE (:obj:`str`): 'upload_video_note'
|
||||
|
||||
:class:`telegram.ChatMember`:
|
||||
|
||||
Attributes:
|
||||
CHATMEMBER_ADMINISTRATOR (:obj:`str`): 'administrator'
|
||||
CHATMEMBER_CREATOR (:obj:`str`): 'creator'
|
||||
CHATMEMBER_KICKED (:obj:`str`): 'kicked'
|
||||
CHATMEMBER_LEFT (:obj:`str`): 'left'
|
||||
CHATMEMBER_MEMBER (:obj:`str`): 'member'
|
||||
CHATMEMBER_RESTRICTED (:obj:`str`): 'restricted'
|
||||
|
||||
:class:`telegram.Dice`:
|
||||
|
||||
Attributes:
|
||||
DICE_DICE (:obj:`str`): '🎲'
|
||||
DICE_DARTS (:obj:`str`): '🎯'
|
||||
DICE_BASKETBALL (:obj:`str`): '🏀'
|
||||
DICE_FOOTBALL (:obj:`str`): '⚽'
|
||||
DICE_SLOT_MACHINE (:obj:`str`): '🎰'
|
||||
DICE_ALL_EMOJI (List[:obj:`str`]): List of all supported base emoji.
|
||||
|
||||
:class:`telegram.MessageEntity`:
|
||||
|
||||
Attributes:
|
||||
MESSAGEENTITY_MENTION (:obj:`str`): 'mention'
|
||||
MESSAGEENTITY_HASHTAG (:obj:`str`): 'hashtag'
|
||||
MESSAGEENTITY_CASHTAG (:obj:`str`): 'cashtag'
|
||||
MESSAGEENTITY_PHONE_NUMBER (:obj:`str`): 'phone_number'
|
||||
MESSAGEENTITY_BOT_COMMAND (:obj:`str`): 'bot_command'
|
||||
MESSAGEENTITY_URL (:obj:`str`): 'url'
|
||||
MESSAGEENTITY_EMAIL (:obj:`str`): 'email'
|
||||
MESSAGEENTITY_BOLD (:obj:`str`): 'bold'
|
||||
MESSAGEENTITY_ITALIC (:obj:`str`): 'italic'
|
||||
MESSAGEENTITY_CODE (:obj:`str`): 'code'
|
||||
MESSAGEENTITY_PRE (:obj:`str`): 'pre'
|
||||
MESSAGEENTITY_TEXT_LINK (:obj:`str`): 'text_link'
|
||||
MESSAGEENTITY_TEXT_MENTION (:obj:`str`): 'text_mention'
|
||||
MESSAGEENTITY_UNDERLINE (:obj:`str`): 'underline'
|
||||
MESSAGEENTITY_STRIKETHROUGH (:obj:`str`): 'strikethrough'
|
||||
MESSAGEENTITY_ALL_TYPES (List[:obj:`str`]): List of all the types of message entity.
|
||||
|
||||
:class:`telegram.ParseMode`:
|
||||
|
||||
Attributes:
|
||||
PARSEMODE_MARKDOWN (:obj:`str`): 'Markdown'
|
||||
PARSEMODE_MARKDOWN_V2 (:obj:`str`): 'MarkdownV2'
|
||||
PARSEMODE_HTML (:obj:`str`): 'HTML'
|
||||
|
||||
:class:`telegram.Poll`:
|
||||
|
||||
Attributes:
|
||||
POLL_REGULAR (:obj:`str`): 'regular'
|
||||
POLL_QUIZ (:obj:`str`): 'quiz'
|
||||
MAX_POLL_QUESTION_LENGTH (:obj:`int`): 300
|
||||
MAX_POLL_OPTION_LENGTH (:obj:`int`): 100
|
||||
|
||||
:class:`telegram.files.MaskPosition`:
|
||||
|
||||
Attributes:
|
||||
STICKER_FOREHEAD (:obj:`str`): 'forehead'
|
||||
STICKER_EYES (:obj:`str`): 'eyes'
|
||||
STICKER_MOUTH (:obj:`str`): 'mouth'
|
||||
STICKER_CHIN (:obj:`str`): 'chin'
|
||||
|
||||
"""
|
||||
from typing import List
|
||||
|
||||
MAX_MESSAGE_LENGTH: int = 4096
|
||||
MAX_CAPTION_LENGTH: int = 1024
|
||||
ANONYMOUS_ADMIN_ID: int = 1087968824
|
||||
SERVICE_CHAT_ID: int = 777000
|
||||
|
||||
# constants above this line are tested
|
||||
|
||||
SUPPORTED_WEBHOOK_PORTS: List[int] = [443, 80, 88, 8443]
|
||||
MAX_FILESIZE_DOWNLOAD: int = int(20E6) # (20MB)
|
||||
MAX_FILESIZE_UPLOAD: int = int(50E6) # (50MB)
|
||||
MAX_PHOTOSIZE_UPLOAD: int = int(10E6) # (10MB)
|
||||
MAX_FILESIZE_DOWNLOAD: int = int(20e6) # (20MB)
|
||||
MAX_FILESIZE_UPLOAD: int = int(50e6) # (50MB)
|
||||
MAX_PHOTOSIZE_UPLOAD: int = int(10e6) # (10MB)
|
||||
MAX_MESSAGES_PER_SECOND_PER_CHAT: int = 1
|
||||
MAX_MESSAGES_PER_SECOND: int = 30
|
||||
MAX_MESSAGES_PER_MINUTE_PER_GROUP: int = 20
|
||||
MAX_MESSAGE_ENTITIES: int = 100
|
||||
MAX_INLINE_QUERY_RESULTS: int = 50
|
||||
|
||||
CHAT_PRIVATE: str = 'private'
|
||||
CHAT_GROUP: str = 'group'
|
||||
CHAT_SUPERGROUP: str = 'supergroup'
|
||||
CHAT_CHANNEL: str = 'channel'
|
||||
|
||||
CHATACTION_FIND_LOCATION: str = 'find_location'
|
||||
CHATACTION_RECORD_AUDIO: str = 'record_audio'
|
||||
CHATACTION_RECORD_VIDEO: str = 'record_video'
|
||||
CHATACTION_RECORD_VIDEO_NOTE: str = 'record_video_note'
|
||||
CHATACTION_TYPING: str = 'typing'
|
||||
CHATACTION_UPLOAD_AUDIO: str = 'upload_audio'
|
||||
CHATACTION_UPLOAD_DOCUMENT: str = 'upload_document'
|
||||
CHATACTION_UPLOAD_PHOTO: str = 'upload_photo'
|
||||
CHATACTION_UPLOAD_VIDEO: str = 'upload_video'
|
||||
CHATACTION_UPLOAD_VIDEO_NOTE: str = 'upload_video_note'
|
||||
|
||||
CHATMEMBER_ADMINISTRATOR: str = 'administrator'
|
||||
CHATMEMBER_CREATOR: str = 'creator'
|
||||
CHATMEMBER_KICKED: str = 'kicked'
|
||||
CHATMEMBER_LEFT: str = 'left'
|
||||
CHATMEMBER_MEMBER: str = 'member'
|
||||
CHATMEMBER_RESTRICTED: str = 'restricted'
|
||||
|
||||
DICE_DICE: str = '🎲'
|
||||
DICE_DARTS: str = '🎯'
|
||||
DICE_BASKETBALL: str = '🏀'
|
||||
DICE_FOOTBALL: str = '⚽'
|
||||
DICE_SLOT_MACHINE: str = '🎰'
|
||||
DICE_ALL_EMOJI: List[str] = [
|
||||
DICE_DICE,
|
||||
DICE_DARTS,
|
||||
DICE_BASKETBALL,
|
||||
DICE_FOOTBALL,
|
||||
DICE_SLOT_MACHINE,
|
||||
]
|
||||
|
||||
MESSAGEENTITY_MENTION: str = 'mention'
|
||||
MESSAGEENTITY_HASHTAG: str = 'hashtag'
|
||||
MESSAGEENTITY_CASHTAG: str = 'cashtag'
|
||||
MESSAGEENTITY_PHONE_NUMBER: str = 'phone_number'
|
||||
MESSAGEENTITY_BOT_COMMAND: str = 'bot_command'
|
||||
MESSAGEENTITY_URL: str = 'url'
|
||||
MESSAGEENTITY_EMAIL: str = 'email'
|
||||
MESSAGEENTITY_BOLD: str = 'bold'
|
||||
MESSAGEENTITY_ITALIC: str = 'italic'
|
||||
MESSAGEENTITY_CODE: str = 'code'
|
||||
MESSAGEENTITY_PRE: str = 'pre'
|
||||
MESSAGEENTITY_TEXT_LINK: str = 'text_link'
|
||||
MESSAGEENTITY_TEXT_MENTION: str = 'text_mention'
|
||||
MESSAGEENTITY_UNDERLINE: str = 'underline'
|
||||
MESSAGEENTITY_STRIKETHROUGH: str = 'strikethrough'
|
||||
MESSAGEENTITY_ALL_TYPES: List[str] = [
|
||||
MESSAGEENTITY_MENTION,
|
||||
MESSAGEENTITY_HASHTAG,
|
||||
MESSAGEENTITY_CASHTAG,
|
||||
MESSAGEENTITY_PHONE_NUMBER,
|
||||
MESSAGEENTITY_BOT_COMMAND,
|
||||
MESSAGEENTITY_URL,
|
||||
MESSAGEENTITY_EMAIL,
|
||||
MESSAGEENTITY_BOLD,
|
||||
MESSAGEENTITY_ITALIC,
|
||||
MESSAGEENTITY_CODE,
|
||||
MESSAGEENTITY_PRE,
|
||||
MESSAGEENTITY_TEXT_LINK,
|
||||
MESSAGEENTITY_TEXT_MENTION,
|
||||
MESSAGEENTITY_UNDERLINE,
|
||||
MESSAGEENTITY_STRIKETHROUGH,
|
||||
]
|
||||
|
||||
PARSEMODE_MARKDOWN: str = 'Markdown'
|
||||
PARSEMODE_MARKDOWN_V2: str = 'MarkdownV2'
|
||||
PARSEMODE_HTML: str = 'HTML'
|
||||
|
||||
POLL_REGULAR: str = 'regular'
|
||||
POLL_QUIZ: str = 'quiz'
|
||||
MAX_POLL_QUESTION_LENGTH: int = 300
|
||||
MAX_POLL_OPTION_LENGTH: int = 100
|
||||
|
||||
STICKER_FOREHEAD: str = 'forehead'
|
||||
STICKER_EYES: str = 'eyes'
|
||||
STICKER_MOUTH: str = 'mouth'
|
||||
STICKER_CHIN: str = 'chin'
|
||||
|
||||
+27
-13
@@ -18,8 +18,9 @@
|
||||
# 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 Dice."""
|
||||
from telegram import TelegramObject
|
||||
from typing import Any, List
|
||||
from typing import Any, List, ClassVar
|
||||
|
||||
from telegram import TelegramObject, constants
|
||||
|
||||
|
||||
class Dice(TelegramObject):
|
||||
@@ -40,26 +41,39 @@ class Dice(TelegramObject):
|
||||
3 indicates that the basket was missed. However, this behaviour is undocumented and might
|
||||
be changed by Telegram.
|
||||
|
||||
If :attr:`emoji` is "⚽", a value of 3 to 5 currently scores a goal, while a value of 1 to
|
||||
3 indicates that the goal was 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.
|
||||
|
||||
Attributes:
|
||||
value (:obj:`int`): Value of the dice.
|
||||
emoji (:obj:`str`): Emoji on which the dice throw animation is based.
|
||||
|
||||
Args:
|
||||
value (:obj:`int`): Value of the dice. 1-6 for dice and darts, 1-5 for basketball.
|
||||
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.
|
||||
emoji (:obj:`str`): Emoji on which the dice throw animation is based.
|
||||
"""
|
||||
def __init__(self, value: int, emoji: str, **kwargs: Any):
|
||||
|
||||
def __init__(self, value: int, emoji: str, **_kwargs: Any):
|
||||
self.value = value
|
||||
self.emoji = emoji
|
||||
|
||||
self._id_attrs = (self.value, self.emoji)
|
||||
|
||||
DICE: str = '🎲'
|
||||
""":obj:`str`: '🎲'"""
|
||||
DARTS: str = '🎯'
|
||||
""":obj:`str`: '🎯'"""
|
||||
BASKETBALL = '🏀'
|
||||
""":obj:`str`: '🏀'"""
|
||||
ALL_EMOJI: List[str] = [DICE, DARTS, BASKETBALL]
|
||||
"""List[:obj:`str`]: List of all supported base emoji. Currently :attr:`DICE`,
|
||||
:attr:`DARTS` and :attr:`BASKETBALL`."""
|
||||
DICE: ClassVar[str] = constants.DICE_DICE
|
||||
""":const:`telegram.constants.DICE_DICE`"""
|
||||
DARTS: ClassVar[str] = constants.DICE_DARTS
|
||||
""":const:`telegram.constants.DICE_DARTS`"""
|
||||
BASKETBALL: ClassVar[str] = constants.DICE_BASKETBALL
|
||||
""":const:`telegram.constants.DICE_BASKETBALL`"""
|
||||
FOOTBALL: ClassVar[str] = constants.DICE_FOOTBALL
|
||||
""":const:`telegram.constants.DICE_FOOTBALL`"""
|
||||
SLOT_MACHINE: ClassVar[str] = constants.DICE_SLOT_MACHINE
|
||||
""":const:`telegram.constants.DICE_SLOT_MACHINE`"""
|
||||
ALL_EMOJI: ClassVar[List[str]] = constants.DICE_ALL_EMOJI
|
||||
""":const:`telegram.constants.DICE_ALL_EMOJI`"""
|
||||
|
||||
+7
-9
@@ -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/].
|
||||
# pylint: disable=C0115
|
||||
"""This module contains an object that represents Telegram errors."""
|
||||
from typing import Tuple
|
||||
|
||||
@@ -31,7 +32,7 @@ def _lstrip_str(in_s: str, lstr: str) -> str:
|
||||
|
||||
"""
|
||||
if in_s.startswith(lstr):
|
||||
res = in_s[len(lstr):]
|
||||
res = in_s[len(lstr) :]
|
||||
else:
|
||||
res = in_s
|
||||
return res
|
||||
@@ -92,7 +93,7 @@ class ChatMigrated(TelegramError):
|
||||
"""
|
||||
|
||||
def __init__(self, new_chat_id: int):
|
||||
super().__init__('Group migrated to supergroup. New chat id: {}'.format(new_chat_id))
|
||||
super().__init__(f'Group migrated to supergroup. New chat id: {new_chat_id}')
|
||||
self.new_chat_id = new_chat_id
|
||||
|
||||
def __reduce__(self) -> Tuple[type, Tuple[int]]: # type: ignore[override]
|
||||
@@ -107,7 +108,7 @@ class RetryAfter(TelegramError):
|
||||
"""
|
||||
|
||||
def __init__(self, retry_after: int):
|
||||
super().__init__('Flood control exceeded. Retry in {} seconds'.format(float(retry_after)))
|
||||
super().__init__(f'Flood control exceeded. Retry in {float(retry_after)} seconds')
|
||||
self.retry_after = float(retry_after)
|
||||
|
||||
def __reduce__(self) -> Tuple[type, Tuple[float]]: # type: ignore[override]
|
||||
@@ -116,15 +117,12 @@ class RetryAfter(TelegramError):
|
||||
|
||||
class Conflict(TelegramError):
|
||||
"""
|
||||
Raised when a long poll or webhook conflicts with another one.
|
||||
Raised when a long poll or webhook conflicts with another one.
|
||||
|
||||
Args:
|
||||
msg (:obj:`str`): The message from telegrams server.
|
||||
Args:
|
||||
msg (:obj:`str`): The message from telegrams server.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, msg: str):
|
||||
super().__init__(msg)
|
||||
|
||||
def __reduce__(self) -> Tuple[type, Tuple[str]]:
|
||||
return self.__class__, (self.message,)
|
||||
|
||||
@@ -45,11 +45,38 @@ from .pollanswerhandler import PollAnswerHandler
|
||||
from .pollhandler import PollHandler
|
||||
from .defaults import Defaults
|
||||
|
||||
__all__ = ('Dispatcher', 'JobQueue', 'Job', 'Updater', 'CallbackQueryHandler',
|
||||
'ChosenInlineResultHandler', 'CommandHandler', 'Handler', 'InlineQueryHandler',
|
||||
'MessageHandler', 'BaseFilter', 'MessageFilter', 'UpdateFilter', 'Filters',
|
||||
'RegexHandler', 'StringCommandHandler', 'StringRegexHandler', 'TypeHandler',
|
||||
'ConversationHandler', 'PreCheckoutQueryHandler', 'ShippingQueryHandler',
|
||||
'MessageQueue', 'DelayQueue', 'DispatcherHandlerStop', 'run_async', 'CallbackContext',
|
||||
'BasePersistence', 'PicklePersistence', 'DictPersistence', 'PrefixHandler',
|
||||
'PollAnswerHandler', 'PollHandler', 'Defaults')
|
||||
__all__ = (
|
||||
'Dispatcher',
|
||||
'JobQueue',
|
||||
'Job',
|
||||
'Updater',
|
||||
'CallbackQueryHandler',
|
||||
'ChosenInlineResultHandler',
|
||||
'CommandHandler',
|
||||
'Handler',
|
||||
'InlineQueryHandler',
|
||||
'MessageHandler',
|
||||
'BaseFilter',
|
||||
'MessageFilter',
|
||||
'UpdateFilter',
|
||||
'Filters',
|
||||
'RegexHandler',
|
||||
'StringCommandHandler',
|
||||
'StringRegexHandler',
|
||||
'TypeHandler',
|
||||
'ConversationHandler',
|
||||
'PreCheckoutQueryHandler',
|
||||
'ShippingQueryHandler',
|
||||
'MessageQueue',
|
||||
'DelayQueue',
|
||||
'DispatcherHandlerStop',
|
||||
'run_async',
|
||||
'CallbackContext',
|
||||
'BasePersistence',
|
||||
'PicklePersistence',
|
||||
'DictPersistence',
|
||||
'PrefixHandler',
|
||||
'PollAnswerHandler',
|
||||
'PollHandler',
|
||||
'Defaults',
|
||||
)
|
||||
|
||||
+125
-43
@@ -17,14 +17,13 @@
|
||||
# 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 BasePersistence class."""
|
||||
|
||||
import warnings
|
||||
from abc import ABC, abstractmethod
|
||||
from collections import defaultdict
|
||||
from copy import copy
|
||||
from typing import Any, DefaultDict, Dict, Optional, Tuple, cast, ClassVar
|
||||
|
||||
from telegram import Bot
|
||||
|
||||
from typing import DefaultDict, Dict, Any, Tuple, Optional, cast
|
||||
from telegram.utils.types import ConversationDict
|
||||
|
||||
|
||||
@@ -73,7 +72,7 @@ class BasePersistence(ABC):
|
||||
persistence class. Default is :obj:`True` .
|
||||
"""
|
||||
|
||||
def __new__(cls, *args: Any, **kwargs: Any) -> 'BasePersistence':
|
||||
def __new__(cls, *args: Any, **kwargs: Any) -> 'BasePersistence': # pylint: disable=W0613
|
||||
instance = super().__new__(cls)
|
||||
get_user_data = instance.get_user_data
|
||||
get_chat_data = instance.get_chat_data
|
||||
@@ -108,10 +107,12 @@ class BasePersistence(ABC):
|
||||
instance.update_bot_data = update_bot_data_replace_bot
|
||||
return instance
|
||||
|
||||
def __init__(self,
|
||||
store_user_data: bool = True,
|
||||
store_chat_data: bool = True,
|
||||
store_bot_data: bool = True):
|
||||
def __init__(
|
||||
self,
|
||||
store_user_data: bool = True,
|
||||
store_chat_data: bool = True,
|
||||
store_bot_data: bool = True,
|
||||
):
|
||||
self.store_user_data = store_user_data
|
||||
self.store_chat_data = store_chat_data
|
||||
self.store_bot_data = store_bot_data
|
||||
@@ -131,7 +132,7 @@ class BasePersistence(ABC):
|
||||
Replaces all instances of :class:`telegram.Bot` that occur within the passed object with
|
||||
:attr:`REPLACED_BOT`. Currently, this handles objects of type ``list``, ``tuple``, ``set``,
|
||||
``frozenset``, ``dict``, ``defaultdict`` and objects that have a ``__dict__`` or
|
||||
``__slot__`` attribute.
|
||||
``__slot__`` attribute, excluding objects that can't be copied with `copy.copy`.
|
||||
|
||||
Args:
|
||||
obj (:obj:`object`): The object
|
||||
@@ -139,26 +140,67 @@ class BasePersistence(ABC):
|
||||
Returns:
|
||||
:obj:`obj`: Copy of the object with Bot instances replaced.
|
||||
"""
|
||||
if isinstance(obj, Bot):
|
||||
return cls.REPLACED_BOT
|
||||
if isinstance(obj, (list, tuple, set, frozenset)):
|
||||
return obj.__class__(cls.replace_bot(item) for item in obj)
|
||||
return cls._replace_bot(obj, {})
|
||||
|
||||
new_obj = copy(obj)
|
||||
if isinstance(obj, (dict, defaultdict)):
|
||||
@classmethod
|
||||
def _replace_bot(cls, obj: object, memo: Dict[int, Any]) -> object: # pylint: disable=R0911
|
||||
obj_id = id(obj)
|
||||
if obj_id in memo:
|
||||
return memo[obj_id]
|
||||
|
||||
if isinstance(obj, Bot):
|
||||
memo[obj_id] = cls.REPLACED_BOT
|
||||
return cls.REPLACED_BOT
|
||||
if isinstance(obj, (list, set)):
|
||||
# We copy the iterable here for thread safety, i.e. make sure the object we iterate
|
||||
# over doesn't change its length during the iteration
|
||||
temp_iterable = obj.copy()
|
||||
new_iterable = obj.__class__(cls._replace_bot(item, memo) for item in temp_iterable)
|
||||
memo[obj_id] = new_iterable
|
||||
return new_iterable
|
||||
if isinstance(obj, (tuple, frozenset)):
|
||||
# tuples and frozensets are immutable so we don't need to worry about thread safety
|
||||
new_immutable = obj.__class__(cls._replace_bot(item, memo) for item in obj)
|
||||
memo[obj_id] = new_immutable
|
||||
return new_immutable
|
||||
|
||||
try:
|
||||
new_obj = copy(obj)
|
||||
memo[obj_id] = new_obj
|
||||
except Exception:
|
||||
warnings.warn(
|
||||
'BasePersistence.replace_bot does not handle objects that can not be copied. See '
|
||||
'the docs of BasePersistence.replace_bot for more information.',
|
||||
RuntimeWarning,
|
||||
)
|
||||
memo[obj_id] = obj
|
||||
return obj
|
||||
|
||||
if isinstance(obj, dict):
|
||||
# We handle dicts via copy(obj) so we don't have to make a
|
||||
# difference between dict and defaultdict
|
||||
new_obj = cast(dict, new_obj)
|
||||
# We can't iterate over obj.items() due to thread safety, i.e. the dicts length may
|
||||
# change during the iteration
|
||||
temp_dict = new_obj.copy()
|
||||
new_obj.clear()
|
||||
for k, v in obj.items():
|
||||
new_obj[cls.replace_bot(k)] = cls.replace_bot(v)
|
||||
for k, val in temp_dict.items():
|
||||
new_obj[cls._replace_bot(k, memo)] = cls._replace_bot(val, memo)
|
||||
memo[obj_id] = new_obj
|
||||
return new_obj
|
||||
if hasattr(obj, '__dict__'):
|
||||
for attr_name, attr in new_obj.__dict__.items():
|
||||
setattr(new_obj, attr_name, cls.replace_bot(attr))
|
||||
setattr(new_obj, attr_name, cls._replace_bot(attr, memo))
|
||||
memo[obj_id] = new_obj
|
||||
return new_obj
|
||||
if hasattr(obj, '__slots__'):
|
||||
for attr_name in new_obj.__slots__:
|
||||
setattr(new_obj, attr_name,
|
||||
cls.replace_bot(cls.replace_bot(getattr(new_obj, attr_name))))
|
||||
setattr(
|
||||
new_obj,
|
||||
attr_name,
|
||||
cls._replace_bot(cls._replace_bot(getattr(new_obj, attr_name), memo), memo),
|
||||
)
|
||||
memo[obj_id] = new_obj
|
||||
return new_obj
|
||||
|
||||
return obj
|
||||
@@ -168,7 +210,7 @@ class BasePersistence(ABC):
|
||||
Replaces all instances of :attr:`REPLACED_BOT` that occur within the passed object with
|
||||
:attr:`bot`. Currently, this handles objects of type ``list``, ``tuple``, ``set``,
|
||||
``frozenset``, ``dict``, ``defaultdict`` and objects that have a ``__dict__`` or
|
||||
``__slot__`` attribute.
|
||||
``__slot__`` attribute, excluding objects that can't be copied with `copy.copy`.
|
||||
|
||||
Args:
|
||||
obj (:obj:`object`): The object
|
||||
@@ -176,34 +218,75 @@ class BasePersistence(ABC):
|
||||
Returns:
|
||||
:obj:`obj`: Copy of the object with Bot instances inserted.
|
||||
"""
|
||||
if isinstance(obj, Bot):
|
||||
return self.bot
|
||||
if obj == self.REPLACED_BOT:
|
||||
return self.bot
|
||||
if isinstance(obj, (list, tuple, set, frozenset)):
|
||||
return obj.__class__(self.insert_bot(item) for item in obj)
|
||||
return self._insert_bot(obj, {})
|
||||
|
||||
new_obj = copy(obj)
|
||||
if isinstance(obj, (dict, defaultdict)):
|
||||
def _insert_bot(self, obj: object, memo: Dict[int, Any]) -> object: # pylint: disable=R0911
|
||||
obj_id = id(obj)
|
||||
if obj_id in memo:
|
||||
return memo[obj_id]
|
||||
|
||||
if isinstance(obj, Bot):
|
||||
memo[obj_id] = self.bot
|
||||
return self.bot
|
||||
if isinstance(obj, str) and obj == self.REPLACED_BOT:
|
||||
memo[obj_id] = self.bot
|
||||
return self.bot
|
||||
if isinstance(obj, (list, set)):
|
||||
# We copy the iterable here for thread safety, i.e. make sure the object we iterate
|
||||
# over doesn't change its length during the iteration
|
||||
temp_iterable = obj.copy()
|
||||
new_iterable = obj.__class__(self._insert_bot(item, memo) for item in temp_iterable)
|
||||
memo[obj_id] = new_iterable
|
||||
return new_iterable
|
||||
if isinstance(obj, (tuple, frozenset)):
|
||||
# tuples and frozensets are immutable so we don't need to worry about thread safety
|
||||
new_immutable = obj.__class__(self._insert_bot(item, memo) for item in obj)
|
||||
memo[obj_id] = new_immutable
|
||||
return new_immutable
|
||||
|
||||
try:
|
||||
new_obj = copy(obj)
|
||||
except Exception:
|
||||
warnings.warn(
|
||||
'BasePersistence.insert_bot does not handle objects that can not be copied. See '
|
||||
'the docs of BasePersistence.insert_bot for more information.',
|
||||
RuntimeWarning,
|
||||
)
|
||||
memo[obj_id] = obj
|
||||
return obj
|
||||
|
||||
if isinstance(obj, dict):
|
||||
# We handle dicts via copy(obj) so we don't have to make a
|
||||
# difference between dict and defaultdict
|
||||
new_obj = cast(dict, new_obj)
|
||||
# We can't iterate over obj.items() due to thread safety, i.e. the dicts length may
|
||||
# change during the iteration
|
||||
temp_dict = new_obj.copy()
|
||||
new_obj.clear()
|
||||
for k, v in obj.items():
|
||||
new_obj[self.insert_bot(k)] = self.insert_bot(v)
|
||||
for k, val in temp_dict.items():
|
||||
new_obj[self._insert_bot(k, memo)] = self._insert_bot(val, memo)
|
||||
memo[obj_id] = new_obj
|
||||
return new_obj
|
||||
if hasattr(obj, '__dict__'):
|
||||
for attr_name, attr in new_obj.__dict__.items():
|
||||
setattr(new_obj, attr_name, self.insert_bot(attr))
|
||||
setattr(new_obj, attr_name, self._insert_bot(attr, memo))
|
||||
memo[obj_id] = new_obj
|
||||
return new_obj
|
||||
if hasattr(obj, '__slots__'):
|
||||
for attr_name in obj.__slots__:
|
||||
setattr(new_obj, attr_name,
|
||||
self.insert_bot(self.insert_bot(getattr(new_obj, attr_name))))
|
||||
setattr(
|
||||
new_obj,
|
||||
attr_name,
|
||||
self._insert_bot(self._insert_bot(getattr(new_obj, attr_name), memo), memo),
|
||||
)
|
||||
memo[obj_id] = new_obj
|
||||
return new_obj
|
||||
|
||||
return obj
|
||||
|
||||
@abstractmethod
|
||||
def get_user_data(self) -> DefaultDict[int, Dict[Any, Any]]:
|
||||
""""Will be called by :class:`telegram.ext.Dispatcher` upon creation with a
|
||||
""" "Will be called by :class:`telegram.ext.Dispatcher` upon creation with a
|
||||
persistence object. It should return the user_data if stored, or an empty
|
||||
``defaultdict(dict)``.
|
||||
|
||||
@@ -213,7 +296,7 @@ class BasePersistence(ABC):
|
||||
|
||||
@abstractmethod
|
||||
def get_chat_data(self) -> DefaultDict[int, Dict[Any, Any]]:
|
||||
""""Will be called by :class:`telegram.ext.Dispatcher` upon creation with a
|
||||
""" "Will be called by :class:`telegram.ext.Dispatcher` upon creation with a
|
||||
persistence object. It should return the chat_data if stored, or an empty
|
||||
``defaultdict(dict)``.
|
||||
|
||||
@@ -223,7 +306,7 @@ class BasePersistence(ABC):
|
||||
|
||||
@abstractmethod
|
||||
def get_bot_data(self) -> Dict[Any, Any]:
|
||||
""""Will be called by :class:`telegram.ext.Dispatcher` upon creation with a
|
||||
""" "Will be called by :class:`telegram.ext.Dispatcher` upon creation with a
|
||||
persistence object. It should return the bot_data if stored, or an empty
|
||||
:obj:`dict`.
|
||||
|
||||
@@ -233,7 +316,7 @@ class BasePersistence(ABC):
|
||||
|
||||
@abstractmethod
|
||||
def get_conversations(self, name: str) -> ConversationDict:
|
||||
""""Will be called by :class:`telegram.ext.Dispatcher` when a
|
||||
""" "Will be called by :class:`telegram.ext.Dispatcher` when a
|
||||
:class:`telegram.ext.ConversationHandler` is added if
|
||||
:attr:`telegram.ext.ConversationHandler.persistent` is :obj:`True`.
|
||||
It should return the conversations for the handler with `name` or an empty :obj:`dict`
|
||||
@@ -246,9 +329,9 @@ class BasePersistence(ABC):
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def update_conversation(self,
|
||||
name: str, key: Tuple[int, ...],
|
||||
new_state: Optional[object]) -> None:
|
||||
def update_conversation(
|
||||
self, name: str, key: Tuple[int, ...], new_state: Optional[object]
|
||||
) -> None:
|
||||
"""Will be called when a :attr:`telegram.ext.ConversationHandler.update_state`
|
||||
is called. This allows the storage of the new state in the persistence.
|
||||
|
||||
@@ -292,7 +375,6 @@ class BasePersistence(ABC):
|
||||
persistence a chance to finish up saving or close a database connection gracefully. If this
|
||||
is not of any importance just pass will be sufficient.
|
||||
"""
|
||||
pass
|
||||
|
||||
REPLACED_BOT = 'bot_instance_replaced_by_ptb_persistence'
|
||||
REPLACED_BOT: ClassVar[str] = 'bot_instance_replaced_by_ptb_persistence'
|
||||
""":obj:`str`: Placeholder for :class:`telegram.Bot` instances replaced in saved data."""
|
||||
|
||||
@@ -16,11 +16,13 @@
|
||||
#
|
||||
# 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
|
||||
"""This module contains the CallbackContext class."""
|
||||
from queue import Queue
|
||||
from typing import Dict, Any, Tuple, TYPE_CHECKING, Optional, Match, List, NoReturn, Union
|
||||
from typing import TYPE_CHECKING, Any, Dict, List, Match, NoReturn, Optional, Tuple, Union
|
||||
|
||||
from telegram import Update
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Bot
|
||||
from telegram.ext import Dispatcher, Job, JobQueue
|
||||
@@ -91,8 +93,9 @@ class CallbackContext:
|
||||
dispatcher (:class:`telegram.ext.Dispatcher`):
|
||||
"""
|
||||
if not dispatcher.use_context:
|
||||
raise ValueError('CallbackContext should not be used with a non context aware '
|
||||
'dispatcher!')
|
||||
raise ValueError(
|
||||
'CallbackContext should not be used with a non context aware ' 'dispatcher!'
|
||||
)
|
||||
self._dispatcher = dispatcher
|
||||
self._bot_data = dispatcher.bot_data
|
||||
self._chat_data: Optional[Dict[Any, Any]] = None
|
||||
@@ -115,8 +118,9 @@ class CallbackContext:
|
||||
|
||||
@bot_data.setter
|
||||
def bot_data(self, value: Any) -> NoReturn:
|
||||
raise AttributeError("You can not assign a new value to bot_data, see "
|
||||
"https://git.io/fjxKe")
|
||||
raise AttributeError(
|
||||
"You can not assign a new value to bot_data, see " "https://git.io/fjxKe"
|
||||
)
|
||||
|
||||
@property
|
||||
def chat_data(self) -> Optional[Dict]:
|
||||
@@ -124,8 +128,9 @@ class CallbackContext:
|
||||
|
||||
@chat_data.setter
|
||||
def chat_data(self, value: Any) -> NoReturn:
|
||||
raise AttributeError("You can not assign a new value to chat_data, see "
|
||||
"https://git.io/fjxKe")
|
||||
raise AttributeError(
|
||||
"You can not assign a new value to chat_data, see " "https://git.io/fjxKe"
|
||||
)
|
||||
|
||||
@property
|
||||
def user_data(self) -> Optional[Dict]:
|
||||
@@ -133,16 +138,19 @@ class CallbackContext:
|
||||
|
||||
@user_data.setter
|
||||
def user_data(self, value: Any) -> NoReturn:
|
||||
raise AttributeError("You can not assign a new value to user_data, see "
|
||||
"https://git.io/fjxKe")
|
||||
raise AttributeError(
|
||||
"You can not assign a new value to user_data, see " "https://git.io/fjxKe"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_error(cls,
|
||||
update: object,
|
||||
error: Exception,
|
||||
dispatcher: 'Dispatcher',
|
||||
async_args: Union[List, Tuple] = None,
|
||||
async_kwargs: Dict[str, Any] = None) -> 'CallbackContext':
|
||||
def from_error(
|
||||
cls,
|
||||
update: object,
|
||||
error: Exception,
|
||||
dispatcher: 'Dispatcher',
|
||||
async_args: Union[List, Tuple] = None,
|
||||
async_kwargs: Dict[str, Any] = None,
|
||||
) -> 'CallbackContext':
|
||||
self = cls.from_update(update, dispatcher)
|
||||
self.error = error
|
||||
self.async_args = async_args
|
||||
@@ -158,9 +166,9 @@ class CallbackContext:
|
||||
user = update.effective_user
|
||||
|
||||
if chat:
|
||||
self._chat_data = dispatcher.chat_data[chat.id]
|
||||
self._chat_data = dispatcher.chat_data[chat.id] # pylint: disable=W0212
|
||||
if user:
|
||||
self._user_data = dispatcher.user_data[user.id]
|
||||
self._user_data = dispatcher.user_data[user.id] # pylint: disable=W0212
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -19,13 +19,24 @@
|
||||
"""This module contains the CallbackQueryHandler class."""
|
||||
|
||||
import re
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Callable,
|
||||
Dict,
|
||||
Match,
|
||||
Optional,
|
||||
Pattern,
|
||||
TypeVar,
|
||||
Union,
|
||||
cast,
|
||||
)
|
||||
|
||||
from telegram import Update
|
||||
from .handler import Handler
|
||||
|
||||
from telegram.utils.types import HandlerArg
|
||||
from typing import Callable, TYPE_CHECKING, Any, Optional, Union, TypeVar, Pattern, Match, Dict, \
|
||||
cast
|
||||
from telegram.utils.helpers import DefaultValue, DEFAULT_FALSE
|
||||
|
||||
from .handler import Handler
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram.ext import CallbackContext, Dispatcher
|
||||
@@ -110,23 +121,26 @@ class CallbackQueryHandler(Handler):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
callback: Callable[[HandlerArg, 'CallbackContext'], RT],
|
||||
pass_update_queue: bool = False,
|
||||
pass_job_queue: bool = False,
|
||||
pattern: Union[str, Pattern] = None,
|
||||
pass_groups: bool = False,
|
||||
pass_groupdict: bool = False,
|
||||
pass_user_data: bool = False,
|
||||
pass_chat_data: bool = False,
|
||||
run_async: bool = False):
|
||||
def __init__(
|
||||
self,
|
||||
callback: Callable[[HandlerArg, 'CallbackContext'], RT],
|
||||
pass_update_queue: bool = False,
|
||||
pass_job_queue: bool = False,
|
||||
pattern: Union[str, Pattern] = None,
|
||||
pass_groups: bool = False,
|
||||
pass_groupdict: 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)
|
||||
run_async=run_async,
|
||||
)
|
||||
|
||||
if isinstance(pattern, str):
|
||||
pattern = re.compile(pattern)
|
||||
@@ -155,10 +169,12 @@ class CallbackQueryHandler(Handler):
|
||||
return True
|
||||
return None
|
||||
|
||||
def collect_optional_args(self,
|
||||
dispatcher: 'Dispatcher',
|
||||
update: HandlerArg = None,
|
||||
check_result: Union[bool, Match] = None) -> Dict[str, Any]:
|
||||
def collect_optional_args(
|
||||
self,
|
||||
dispatcher: 'Dispatcher',
|
||||
update: HandlerArg = None,
|
||||
check_result: Union[bool, Match] = None,
|
||||
) -> Dict[str, Any]:
|
||||
optional_args = super().collect_optional_args(dispatcher, update, check_result)
|
||||
if self.pattern:
|
||||
check_result = cast(Match, check_result)
|
||||
@@ -168,11 +184,13 @@ class CallbackQueryHandler(Handler):
|
||||
optional_args['groupdict'] = check_result.groupdict()
|
||||
return optional_args
|
||||
|
||||
def collect_additional_context(self,
|
||||
context: 'CallbackContext',
|
||||
update: HandlerArg,
|
||||
dispatcher: 'Dispatcher',
|
||||
check_result: Union[bool, Match]) -> None:
|
||||
def collect_additional_context(
|
||||
self,
|
||||
context: 'CallbackContext',
|
||||
update: HandlerArg,
|
||||
dispatcher: 'Dispatcher',
|
||||
check_result: Union[bool, Match],
|
||||
) -> None:
|
||||
if self.pattern:
|
||||
check_result = cast(Match, check_result)
|
||||
context.matches = [check_result]
|
||||
|
||||
@@ -18,11 +18,13 @@
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains the ChosenInlineResultHandler class."""
|
||||
|
||||
from typing import Optional, TypeVar, Union
|
||||
|
||||
from telegram import Update
|
||||
from telegram.utils.types import HandlerArg
|
||||
|
||||
from .handler import Handler
|
||||
|
||||
from telegram.utils.types import HandlerArg
|
||||
from typing import Optional, Union, TypeVar
|
||||
RT = TypeVar('RT')
|
||||
|
||||
|
||||
|
||||
+103
-73
@@ -19,15 +19,16 @@
|
||||
"""This module contains the CommandHandler and PrefixHandler classes."""
|
||||
import re
|
||||
import warnings
|
||||
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple, TypeVar, Union
|
||||
|
||||
from telegram.ext import Filters, BaseFilter
|
||||
from telegram import MessageEntity, Update
|
||||
from telegram.ext import BaseFilter, Filters
|
||||
from telegram.utils.deprecate import TelegramDeprecationWarning
|
||||
from telegram.utils.types import HandlerArg, SLT
|
||||
from telegram.utils.helpers import DefaultValue, DEFAULT_FALSE
|
||||
|
||||
from telegram import Update, MessageEntity
|
||||
from .handler import Handler
|
||||
|
||||
from telegram.utils.types import HandlerArg
|
||||
from typing import Callable, TYPE_CHECKING, Any, Optional, Union, TypeVar, Dict, List, Tuple
|
||||
if TYPE_CHECKING:
|
||||
from telegram.ext import CallbackContext, Dispatcher
|
||||
|
||||
@@ -49,9 +50,9 @@ class CommandHandler(Handler):
|
||||
:class:`telegram.ext.CommandHandler` does *not* handle (edited) channel posts.
|
||||
|
||||
Attributes:
|
||||
command (:obj:`str` | List[:obj:`str`]): The command or list of commands this handler
|
||||
should listen for. Limitations are the same as described here
|
||||
https://core.telegram.org/bots#commands
|
||||
command (:class:`telegram.utils.types.SLT[str]`):
|
||||
The command or list of commands this handler should listen for.
|
||||
Limitations are the same as described here https://core.telegram.org/bots#commands
|
||||
callback (:obj:`callable`): The callback function for this handler.
|
||||
filters (:class:`telegram.ext.BaseFilter`): Optional. Only allow updates with these
|
||||
Filters.
|
||||
@@ -83,9 +84,9 @@ class CommandHandler(Handler):
|
||||
attributes to :class:`telegram.ext.CallbackContext`. See its docs for more info.
|
||||
|
||||
Args:
|
||||
command (:obj:`str` | List[:obj:`str`]): The command or list of commands this handler
|
||||
should listen for. Limitations are the same as described here
|
||||
https://core.telegram.org/bots#commands
|
||||
command (:class:`telegram.utils.types.SLT[str]`):
|
||||
The command or list of commands this handler should listen for.
|
||||
Limitations are the same as described here https://core.telegram.org/bots#commands
|
||||
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:
|
||||
@@ -130,24 +131,27 @@ class CommandHandler(Handler):
|
||||
ValueError - when command is too long or has illegal chars.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
command: Union[str, List[str]],
|
||||
callback: Callable[[HandlerArg, 'CallbackContext'], RT],
|
||||
filters: BaseFilter = None,
|
||||
allow_edited: bool = None,
|
||||
pass_args: bool = False,
|
||||
pass_update_queue: bool = False,
|
||||
pass_job_queue: bool = False,
|
||||
pass_user_data: bool = False,
|
||||
pass_chat_data: bool = False,
|
||||
run_async: bool = False):
|
||||
def __init__(
|
||||
self,
|
||||
command: SLT[str],
|
||||
callback: Callable[[HandlerArg, 'CallbackContext'], RT],
|
||||
filters: BaseFilter = None,
|
||||
allow_edited: bool = None,
|
||||
pass_args: bool = False,
|
||||
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)
|
||||
run_async=run_async,
|
||||
)
|
||||
|
||||
if isinstance(command, str):
|
||||
self.command = [command.lower()]
|
||||
@@ -163,17 +167,18 @@ class CommandHandler(Handler):
|
||||
self.filters = Filters.update.messages
|
||||
|
||||
if allow_edited is not None:
|
||||
warnings.warn('allow_edited is deprecated. See https://git.io/fxJuV for more info',
|
||||
TelegramDeprecationWarning,
|
||||
stacklevel=2)
|
||||
warnings.warn(
|
||||
'allow_edited is deprecated. See https://git.io/fxJuV for more info',
|
||||
TelegramDeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
if not allow_edited:
|
||||
self.filters &= ~Filters.update.edited_message
|
||||
self.pass_args = pass_args
|
||||
|
||||
def check_update(
|
||||
self,
|
||||
update: HandlerArg) -> Optional[Union[bool, Tuple[List[str],
|
||||
Optional[Union[bool, Dict]]]]]:
|
||||
self, update: HandlerArg
|
||||
) -> Optional[Union[bool, Tuple[List[str], Optional[Union[bool, Dict]]]]]:
|
||||
"""Determines whether an update should be passed to this handlers :attr:`callback`.
|
||||
|
||||
Args:
|
||||
@@ -186,41 +191,48 @@ class CommandHandler(Handler):
|
||||
if isinstance(update, Update) and update.effective_message:
|
||||
message = update.effective_message
|
||||
|
||||
if (message.entities and message.entities[0].type == MessageEntity.BOT_COMMAND
|
||||
and message.entities[0].offset == 0 and message.text and message.bot):
|
||||
command = message.text[1:message.entities[0].length]
|
||||
if (
|
||||
message.entities
|
||||
and message.entities[0].type == MessageEntity.BOT_COMMAND
|
||||
and message.entities[0].offset == 0
|
||||
and message.text
|
||||
and message.bot
|
||||
):
|
||||
command = message.text[1 : message.entities[0].length]
|
||||
args = message.text.split()[1:]
|
||||
command_parts = command.split('@')
|
||||
command_parts.append(message.bot.username)
|
||||
|
||||
if not (command_parts[0].lower() in self.command
|
||||
and command_parts[1].lower() == message.bot.username.lower()):
|
||||
if not (
|
||||
command_parts[0].lower() in self.command
|
||||
and command_parts[1].lower() == message.bot.username.lower()
|
||||
):
|
||||
return None
|
||||
|
||||
filter_result = self.filters(update)
|
||||
if filter_result:
|
||||
return args, filter_result
|
||||
else:
|
||||
return False
|
||||
return False
|
||||
return None
|
||||
|
||||
def collect_optional_args(
|
||||
self,
|
||||
dispatcher: 'Dispatcher',
|
||||
update: HandlerArg = None,
|
||||
check_result: Optional[Union[bool, Tuple[List[str],
|
||||
Optional[bool]]]] = None) -> Dict[str, Any]:
|
||||
self,
|
||||
dispatcher: 'Dispatcher',
|
||||
update: HandlerArg = None,
|
||||
check_result: Optional[Union[bool, Tuple[List[str], Optional[bool]]]] = None,
|
||||
) -> Dict[str, Any]:
|
||||
optional_args = super().collect_optional_args(dispatcher, update)
|
||||
if self.pass_args and isinstance(check_result, tuple):
|
||||
optional_args['args'] = check_result[0]
|
||||
return optional_args
|
||||
|
||||
def collect_additional_context(
|
||||
self,
|
||||
context: 'CallbackContext',
|
||||
update: HandlerArg,
|
||||
dispatcher: 'Dispatcher',
|
||||
check_result: Optional[Union[bool, Tuple[List[str], Optional[bool]]]]) -> None:
|
||||
self,
|
||||
context: 'CallbackContext',
|
||||
update: HandlerArg,
|
||||
dispatcher: 'Dispatcher',
|
||||
check_result: Optional[Union[bool, Tuple[List[str], Optional[bool]]]],
|
||||
) -> None:
|
||||
if isinstance(check_result, tuple):
|
||||
context.args = check_result[0]
|
||||
if isinstance(check_result[1], dict):
|
||||
@@ -257,9 +269,6 @@ class PrefixHandler(CommandHandler):
|
||||
use ~``Filters.update.edited_message``.
|
||||
|
||||
Attributes:
|
||||
prefix (:obj:`str` | List[:obj:`str`]): The prefix(es) that will precede :attr:`command`.
|
||||
command (:obj:`str` | List[:obj:`str`]): The command or list of commands this handler
|
||||
should listen for.
|
||||
callback (:obj:`callable`): The callback function for this handler.
|
||||
filters (:class:`telegram.ext.BaseFilter`): Optional. Only allow updates with these
|
||||
Filters.
|
||||
@@ -289,9 +298,10 @@ class PrefixHandler(CommandHandler):
|
||||
attributes to :class:`telegram.ext.CallbackContext`. See its docs for more info.
|
||||
|
||||
Args:
|
||||
prefix (:obj:`str` | List[:obj:`str`]): The prefix(es) that will precede :attr:`command`.
|
||||
command (:obj:`str` | List[:obj:`str`]): The command or list of commands this handler
|
||||
should listen for.
|
||||
prefix (:class:`telegram.utils.types.SLT[str]`):
|
||||
The prefix(es) that will precede :attr:`command`.
|
||||
command (:class:`telegram.utils.types.SLT[str]`):
|
||||
The command or list of commands this handler should listen for.
|
||||
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:
|
||||
@@ -330,29 +340,36 @@ class PrefixHandler(CommandHandler):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
prefix: Union[str, List[str]],
|
||||
command: Union[str, List[str]],
|
||||
callback: Callable[[HandlerArg, 'CallbackContext'], RT],
|
||||
filters: BaseFilter = None,
|
||||
pass_args: bool = False,
|
||||
pass_update_queue: bool = False,
|
||||
pass_job_queue: bool = False,
|
||||
pass_user_data: bool = False,
|
||||
pass_chat_data: bool = False,
|
||||
run_async: bool = False):
|
||||
def __init__(
|
||||
self,
|
||||
prefix: SLT[str],
|
||||
command: SLT[str],
|
||||
callback: Callable[[HandlerArg, 'CallbackContext'], RT],
|
||||
filters: BaseFilter = None,
|
||||
pass_args: bool = False,
|
||||
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,
|
||||
):
|
||||
|
||||
self._prefix: List[str] = list()
|
||||
self._command: List[str] = list()
|
||||
self._commands: List[str] = list()
|
||||
|
||||
super().__init__(
|
||||
'nocommand', callback, filters=filters, allow_edited=None, pass_args=pass_args,
|
||||
'nocommand',
|
||||
callback,
|
||||
filters=filters,
|
||||
allow_edited=None,
|
||||
pass_args=pass_args,
|
||||
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)
|
||||
run_async=run_async,
|
||||
)
|
||||
|
||||
self.prefix = prefix # type: ignore[assignment]
|
||||
self.command = command # type: ignore[assignment]
|
||||
@@ -360,6 +377,12 @@ class PrefixHandler(CommandHandler):
|
||||
|
||||
@property
|
||||
def prefix(self) -> List[str]:
|
||||
"""
|
||||
The prefixes that will precede :attr:`command`.
|
||||
|
||||
Returns:
|
||||
List[:obj:`str`]
|
||||
"""
|
||||
return self._prefix
|
||||
|
||||
@prefix.setter
|
||||
@@ -372,6 +395,12 @@ class PrefixHandler(CommandHandler):
|
||||
|
||||
@property # type: ignore[override]
|
||||
def command(self) -> List[str]: # type: ignore[override]
|
||||
"""
|
||||
The list of commands this handler should listen for.
|
||||
|
||||
Returns:
|
||||
List[:obj:`str`]
|
||||
"""
|
||||
return self._command
|
||||
|
||||
@command.setter
|
||||
@@ -385,8 +414,9 @@ class PrefixHandler(CommandHandler):
|
||||
def _build_commands(self) -> None:
|
||||
self._commands = [x.lower() + y.lower() for x in self.prefix for y in self.command]
|
||||
|
||||
def check_update(self, update: HandlerArg) -> Optional[Union[bool, Tuple[List[str],
|
||||
Optional[Union[bool, Dict]]]]]:
|
||||
def check_update(
|
||||
self, update: HandlerArg
|
||||
) -> Optional[Union[bool, Tuple[List[str], Optional[Union[bool, Dict]]]]]:
|
||||
"""Determines whether an update should be passed to this handlers :attr:`callback`.
|
||||
|
||||
Args:
|
||||
@@ -406,16 +436,16 @@ class PrefixHandler(CommandHandler):
|
||||
filter_result = self.filters(update)
|
||||
if filter_result:
|
||||
return text_list[1:], filter_result
|
||||
else:
|
||||
return False
|
||||
return False
|
||||
return None
|
||||
|
||||
def collect_additional_context(
|
||||
self,
|
||||
context: 'CallbackContext',
|
||||
update: HandlerArg,
|
||||
dispatcher: 'Dispatcher',
|
||||
check_result: Optional[Union[bool, Tuple[List[str], Optional[bool]]]]) -> None:
|
||||
self,
|
||||
context: 'CallbackContext',
|
||||
update: HandlerArg,
|
||||
dispatcher: 'Dispatcher',
|
||||
check_result: Optional[Union[bool, Tuple[List[str], Optional[bool]]]],
|
||||
) -> None:
|
||||
if isinstance(check_result, tuple):
|
||||
context.args = check_result[0]
|
||||
if isinstance(check_result[1], dict):
|
||||
|
||||
@@ -16,20 +16,26 @@
|
||||
#
|
||||
# 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
|
||||
"""This module contains the ConversationHandler."""
|
||||
|
||||
import logging
|
||||
import warnings
|
||||
from threading import Lock
|
||||
from typing import TYPE_CHECKING, Any, Dict, List, NoReturn, Optional, Tuple, cast, ClassVar
|
||||
|
||||
from telegram import Update
|
||||
from telegram.ext import (Handler, CallbackQueryHandler, InlineQueryHandler,
|
||||
ChosenInlineResultHandler, CallbackContext, BasePersistence,
|
||||
DispatcherHandlerStop)
|
||||
from telegram.ext import (
|
||||
BasePersistence,
|
||||
CallbackContext,
|
||||
CallbackQueryHandler,
|
||||
ChosenInlineResultHandler,
|
||||
DispatcherHandlerStop,
|
||||
Handler,
|
||||
InlineQueryHandler,
|
||||
)
|
||||
from telegram.utils.promise import Promise
|
||||
|
||||
from telegram.utils.types import ConversationDict, HandlerArg
|
||||
from typing import Dict, Any, List, Optional, Tuple, TYPE_CHECKING, cast, NoReturn
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram.ext import Dispatcher, Job
|
||||
@@ -37,11 +43,13 @@ CheckUpdateType = Optional[Tuple[Tuple[int, ...], Handler, object]]
|
||||
|
||||
|
||||
class _ConversationTimeoutContext:
|
||||
def __init__(self,
|
||||
conversation_key: Tuple[int, ...],
|
||||
update: Update,
|
||||
dispatcher: 'Dispatcher',
|
||||
callback_context: Optional[CallbackContext]):
|
||||
def __init__(
|
||||
self,
|
||||
conversation_key: Tuple[int, ...],
|
||||
update: Update,
|
||||
dispatcher: 'Dispatcher',
|
||||
callback_context: Optional[CallbackContext],
|
||||
):
|
||||
self.conversation_key = conversation_key
|
||||
self.update = update
|
||||
self.dispatcher = dispatcher
|
||||
@@ -76,6 +84,8 @@ class ConversationHandler(Handler):
|
||||
the conversation ends immediately after the execution of this callback function.
|
||||
To end the conversation, the callback function must return :attr:`END` or ``-1``. To
|
||||
handle the conversation timeout, use handler :attr:`TIMEOUT` or ``-2``.
|
||||
Finally, :class:`telegram.ext.DispatcherHandlerStop` can be used in conversations as described
|
||||
in the corresponding documentation.
|
||||
|
||||
Note:
|
||||
In each of the described collections of handlers, a handler may in turn be a
|
||||
@@ -160,26 +170,29 @@ class ConversationHandler(Handler):
|
||||
ValueError
|
||||
|
||||
"""
|
||||
END = -1
|
||||
|
||||
END: ClassVar[int] = -1
|
||||
""":obj:`int`: Used as a constant to return when a conversation is ended."""
|
||||
TIMEOUT = -2
|
||||
TIMEOUT: ClassVar[int] = -2
|
||||
""":obj:`int`: Used as a constant to handle state when a conversation is timed out."""
|
||||
WAITING = -3
|
||||
WAITING: ClassVar[int] = -3
|
||||
""":obj:`int`: Used as a constant to handle state when a conversation is still waiting on the
|
||||
previous ``@run_sync`` decorated running handler to finish."""
|
||||
|
||||
def __init__(self,
|
||||
entry_points: List[Handler],
|
||||
states: Dict[object, List[Handler]],
|
||||
fallbacks: List[Handler],
|
||||
allow_reentry: bool = False,
|
||||
per_chat: bool = True,
|
||||
per_user: bool = True,
|
||||
per_message: bool = False,
|
||||
conversation_timeout: int = None,
|
||||
name: str = None,
|
||||
persistent: bool = False,
|
||||
map_to_parent: Dict[object, object] = None):
|
||||
# pylint: disable=W0231
|
||||
def __init__(
|
||||
self,
|
||||
entry_points: List[Handler],
|
||||
states: Dict[object, List[Handler]],
|
||||
fallbacks: List[Handler],
|
||||
allow_reentry: bool = False,
|
||||
per_chat: bool = True,
|
||||
per_user: bool = True,
|
||||
per_message: bool = False,
|
||||
conversation_timeout: int = None,
|
||||
name: str = None,
|
||||
persistent: bool = False,
|
||||
map_to_parent: Dict[object, object] = None,
|
||||
):
|
||||
self.run_async = False
|
||||
|
||||
self._entry_points = entry_points
|
||||
@@ -211,8 +224,10 @@ class ConversationHandler(Handler):
|
||||
raise ValueError("'per_user', 'per_chat' and 'per_message' can't all be 'False'")
|
||||
|
||||
if self.per_message and not self.per_chat:
|
||||
warnings.warn("If 'per_message=True' is used, 'per_chat=True' should also be used, "
|
||||
"since message IDs are not globally unique.")
|
||||
warnings.warn(
|
||||
"If 'per_message=True' is used, 'per_chat=True' should also be used, "
|
||||
"since message IDs are not globally unique."
|
||||
)
|
||||
|
||||
all_handlers = list()
|
||||
all_handlers.extend(entry_points)
|
||||
@@ -224,22 +239,28 @@ class ConversationHandler(Handler):
|
||||
if self.per_message:
|
||||
for handler in all_handlers:
|
||||
if not isinstance(handler, CallbackQueryHandler):
|
||||
warnings.warn("If 'per_message=True', all entry points and state handlers"
|
||||
" must be 'CallbackQueryHandler', since no other handlers "
|
||||
"have a message context.")
|
||||
warnings.warn(
|
||||
"If 'per_message=True', all entry points and state handlers"
|
||||
" must be 'CallbackQueryHandler', since no other handlers "
|
||||
"have a message context."
|
||||
)
|
||||
break
|
||||
else:
|
||||
for handler in all_handlers:
|
||||
if isinstance(handler, CallbackQueryHandler):
|
||||
warnings.warn("If 'per_message=False', 'CallbackQueryHandler' will not be "
|
||||
"tracked for every message.")
|
||||
warnings.warn(
|
||||
"If 'per_message=False', 'CallbackQueryHandler' will not be "
|
||||
"tracked for every message."
|
||||
)
|
||||
break
|
||||
|
||||
if self.per_chat:
|
||||
for handler in all_handlers:
|
||||
if isinstance(handler, (InlineQueryHandler, ChosenInlineResultHandler)):
|
||||
warnings.warn("If 'per_chat=True', 'InlineQueryHandler' can not be used, "
|
||||
"since inline queries have no chat context.")
|
||||
warnings.warn(
|
||||
"If 'per_chat=True', 'InlineQueryHandler' can not be used, "
|
||||
"since inline queries have no chat context."
|
||||
)
|
||||
break
|
||||
|
||||
@property
|
||||
@@ -304,8 +325,9 @@ class ConversationHandler(Handler):
|
||||
|
||||
@conversation_timeout.setter
|
||||
def conversation_timeout(self, value: Any) -> NoReturn:
|
||||
raise ValueError('You can not assign a new value to conversation_timeout after '
|
||||
'initialization.')
|
||||
raise ValueError(
|
||||
'You can not assign a new value to conversation_timeout after ' 'initialization.'
|
||||
)
|
||||
|
||||
@property
|
||||
def name(self) -> Optional[str]:
|
||||
@@ -362,12 +384,14 @@ class ConversationHandler(Handler):
|
||||
key.append(user.id)
|
||||
|
||||
if self.per_message:
|
||||
key.append(update.callback_query.inline_message_id # type: ignore[union-attr]
|
||||
or update.callback_query.message.message_id) # type: ignore[union-attr]
|
||||
key.append(
|
||||
update.callback_query.inline_message_id # type: ignore[union-attr]
|
||||
or update.callback_query.message.message_id # type: ignore[union-attr]
|
||||
)
|
||||
|
||||
return tuple(key)
|
||||
|
||||
def check_update(self, update: HandlerArg) -> CheckUpdateType:
|
||||
def check_update(self, update: HandlerArg) -> CheckUpdateType: # pylint: disable=R0911
|
||||
"""
|
||||
Determines whether an update should be handled by this conversationhandler, and if so in
|
||||
which state the conversation currently is.
|
||||
@@ -379,12 +403,16 @@ class ConversationHandler(Handler):
|
||||
:obj:`bool`
|
||||
|
||||
"""
|
||||
if not isinstance(update, Update):
|
||||
return None
|
||||
# Ignore messages in channels
|
||||
if (not isinstance(update, Update)
|
||||
or update.channel_post
|
||||
or self.per_chat and not update.effective_chat
|
||||
or self.per_message and not update.callback_query
|
||||
or update.callback_query and self.per_chat and not update.callback_query.message):
|
||||
if update.channel_post:
|
||||
return None
|
||||
if self.per_chat and not update.effective_chat:
|
||||
return None
|
||||
if self.per_message and not update.callback_query:
|
||||
return None
|
||||
if update.callback_query and self.per_chat and not update.callback_query.message:
|
||||
return None
|
||||
|
||||
key = self._get_key(update)
|
||||
@@ -402,7 +430,7 @@ class ConversationHandler(Handler):
|
||||
res = res if res is not None else old_state
|
||||
except Exception as exc:
|
||||
self.logger.exception("Promise function raised exception")
|
||||
self.logger.exception("{}".format(exc))
|
||||
self.logger.exception("%s", exc)
|
||||
res = old_state
|
||||
finally:
|
||||
if res is None and old_state is None:
|
||||
@@ -418,7 +446,7 @@ class ConversationHandler(Handler):
|
||||
return key, hdlr, check
|
||||
return None
|
||||
|
||||
self.logger.debug('selecting conversation {} with state {}'.format(str(key), str(state)))
|
||||
self.logger.debug('selecting conversation %s with state %s', str(key), str(state))
|
||||
|
||||
handler = None
|
||||
|
||||
@@ -438,7 +466,7 @@ class ConversationHandler(Handler):
|
||||
if state is not None and not handler:
|
||||
handlers = self.states.get(state)
|
||||
|
||||
for candidate in (handlers or []):
|
||||
for candidate in handlers or []:
|
||||
check = candidate.check_update(update)
|
||||
if check is not None and check is not False:
|
||||
handler = candidate
|
||||
@@ -457,11 +485,13 @@ class ConversationHandler(Handler):
|
||||
|
||||
return key, handler, check # type: ignore[return-value]
|
||||
|
||||
def handle_update(self, # type: ignore[override]
|
||||
update: HandlerArg,
|
||||
dispatcher: 'Dispatcher',
|
||||
check_result: CheckUpdateType,
|
||||
context: CallbackContext = None) -> Optional[object]:
|
||||
def handle_update( # type: ignore[override]
|
||||
self,
|
||||
update: HandlerArg,
|
||||
dispatcher: 'Dispatcher',
|
||||
check_result: CheckUpdateType,
|
||||
context: CallbackContext = None,
|
||||
) -> Optional[object]:
|
||||
"""Send the update to the callback for the current state and Handler
|
||||
|
||||
Args:
|
||||
@@ -485,34 +515,34 @@ class ConversationHandler(Handler):
|
||||
timeout_job.schedule_removal()
|
||||
try:
|
||||
new_state = handler.handle_update(update, dispatcher, check_result, context)
|
||||
except DispatcherHandlerStop as e:
|
||||
new_state = e.state
|
||||
except DispatcherHandlerStop as exception:
|
||||
new_state = exception.state
|
||||
raise_dp_handler_stop = True
|
||||
with self._timeout_jobs_lock:
|
||||
if self.conversation_timeout and new_state != self.END and dispatcher.job_queue:
|
||||
# Add the new timeout job
|
||||
self.timeout_jobs[conversation_key] = dispatcher.job_queue.run_once(
|
||||
self._trigger_timeout, self.conversation_timeout, # type: ignore[arg-type]
|
||||
context=_ConversationTimeoutContext(conversation_key, update,
|
||||
dispatcher, context))
|
||||
self._trigger_timeout, # type: ignore[arg-type]
|
||||
self.conversation_timeout,
|
||||
context=_ConversationTimeoutContext(
|
||||
conversation_key, update, dispatcher, context
|
||||
),
|
||||
)
|
||||
|
||||
if isinstance(self.map_to_parent, dict) and new_state in self.map_to_parent:
|
||||
self.update_state(self.END, conversation_key)
|
||||
if raise_dp_handler_stop:
|
||||
raise DispatcherHandlerStop(self.map_to_parent.get(new_state))
|
||||
else:
|
||||
return self.map_to_parent.get(new_state)
|
||||
else:
|
||||
self.update_state(new_state, conversation_key)
|
||||
if raise_dp_handler_stop:
|
||||
# Don't pass the new state here. If we're in a nested conversation, the parent is
|
||||
# expecting None as return value.
|
||||
raise DispatcherHandlerStop()
|
||||
return self.map_to_parent.get(new_state)
|
||||
|
||||
self.update_state(new_state, conversation_key)
|
||||
if raise_dp_handler_stop:
|
||||
# Don't pass the new state here. If we're in a nested conversation, the parent is
|
||||
# expecting None as return value.
|
||||
raise DispatcherHandlerStop()
|
||||
return None
|
||||
|
||||
def update_state(self,
|
||||
new_state: object,
|
||||
key: Tuple[int, ...]) -> None:
|
||||
def update_state(self, new_state: object, key: Tuple[int, ...]) -> None:
|
||||
if new_state == self.END:
|
||||
with self._conversations_lock:
|
||||
if key in self.conversations:
|
||||
@@ -525,8 +555,9 @@ class ConversationHandler(Handler):
|
||||
with self._conversations_lock:
|
||||
self.conversations[key] = (self.conversations.get(key), new_state)
|
||||
if self.persistent and self.persistence and self.name:
|
||||
self.persistence.update_conversation(self.name, key,
|
||||
(self.conversations.get(key), new_state))
|
||||
self.persistence.update_conversation(
|
||||
self.name, key, (self.conversations.get(key), new_state)
|
||||
)
|
||||
|
||||
elif new_state is not None:
|
||||
with self._conversations_lock:
|
||||
@@ -534,9 +565,7 @@ class ConversationHandler(Handler):
|
||||
if self.persistent and self.persistence and self.name:
|
||||
self.persistence.update_conversation(self.name, key, new_state)
|
||||
|
||||
def _trigger_timeout(self,
|
||||
context: _ConversationTimeoutContext,
|
||||
job: 'Job' = None) -> None:
|
||||
def _trigger_timeout(self, context: _ConversationTimeoutContext, job: 'Job' = None) -> None:
|
||||
self.logger.debug('conversation timeout was triggered!')
|
||||
|
||||
# Backward compatibility with bots that do not use CallbackContext
|
||||
@@ -559,9 +588,12 @@ class ConversationHandler(Handler):
|
||||
check = handler.check_update(context.update)
|
||||
if check is not None and check is not False:
|
||||
try:
|
||||
handler.handle_update(context.update, context.dispatcher, check,
|
||||
callback_context)
|
||||
handler.handle_update(
|
||||
context.update, context.dispatcher, check, callback_context
|
||||
)
|
||||
except DispatcherHandlerStop:
|
||||
self.logger.warning('DispatcherHandlerStop in TIMEOUT state of '
|
||||
'ConversationHandler has no effect. Ignoring.')
|
||||
self.logger.warning(
|
||||
'DispatcherHandlerStop in TIMEOUT state of '
|
||||
'ConversationHandler has no effect. Ignoring.'
|
||||
)
|
||||
self.update_state(self.END, context.conversation_key)
|
||||
|
||||
+87
-28
@@ -16,9 +16,11 @@
|
||||
#
|
||||
# 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
|
||||
"""This module contains the class Defaults, which allows to pass default values to Updater."""
|
||||
from typing import Any, NoReturn, Optional, Union
|
||||
|
||||
import pytz
|
||||
from typing import Union, Optional, Any, NoReturn
|
||||
|
||||
from telegram.utils.helpers import DEFAULT_NONE, DefaultValue
|
||||
|
||||
@@ -33,6 +35,8 @@ class Defaults:
|
||||
receive a notification with no sound.
|
||||
disable_web_page_preview (:obj:`bool`): Optional. Disables link previews for links in this
|
||||
message.
|
||||
allow_sending_without_reply (:obj:`bool`): Optional. Pass :obj:`True`, if the message
|
||||
should be sent even if the specified replied-to message is not found.
|
||||
timeout (:obj:`int` | :obj:`float`): Optional. If this value is specified, use it as the
|
||||
read timeout from the server (instead of the one specified during creation of the
|
||||
connection pool).
|
||||
@@ -41,6 +45,9 @@ class Defaults:
|
||||
be ignored. Default: :obj:`True` in group chats and :obj:`False` in private chats.
|
||||
tzinfo (:obj:`tzinfo`): A timezone to be used for all date(time) objects appearing
|
||||
throughout PTB.
|
||||
run_async (:obj:`bool`): Optional. Default setting for the ``run_async`` parameter of
|
||||
handlers and error handlers registered through :meth:`Dispatcher.add_handler` and
|
||||
:meth:`Dispatcher.add_error_handler`.
|
||||
|
||||
Parameters:
|
||||
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show
|
||||
@@ -49,6 +56,8 @@ class Defaults:
|
||||
receive a notification with no sound.
|
||||
disable_web_page_preview (:obj:`bool`, optional): Disables link previews for links in this
|
||||
message.
|
||||
allow_sending_without_reply (:obj:`bool`, optional): Pass :obj:`True`, if the message
|
||||
should be sent even if the specified replied-to message is not found.
|
||||
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as the
|
||||
read timeout from the server (instead of the one specified during creation of the
|
||||
connection pool).
|
||||
@@ -59,22 +68,32 @@ class Defaults:
|
||||
appearing throughout PTB, i.e. if a timezone naive date(time) object is passed
|
||||
somewhere, it will be assumed to be in ``tzinfo``. Must be a timezone provided by the
|
||||
``pytz`` module. Defaults to UTC.
|
||||
run_async (:obj:`bool`, optional): Default setting for the ``run_async`` parameter of
|
||||
handlers and error handlers registered through :meth:`Dispatcher.add_handler` and
|
||||
:meth:`Dispatcher.add_error_handler`. Defaults to :obj:`False`.
|
||||
"""
|
||||
def __init__(self,
|
||||
parse_mode: str = None,
|
||||
disable_notification: bool = None,
|
||||
disable_web_page_preview: bool = None,
|
||||
# Timeout needs special treatment, since the bot methods have two different
|
||||
# default values for timeout (None and 20s)
|
||||
timeout: Union[float, DefaultValue] = DEFAULT_NONE,
|
||||
quote: bool = None,
|
||||
tzinfo: pytz.BaseTzInfo = pytz.utc):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
parse_mode: str = None,
|
||||
disable_notification: bool = None,
|
||||
disable_web_page_preview: bool = None,
|
||||
# Timeout needs special treatment, since the bot methods have two different
|
||||
# default values for timeout (None and 20s)
|
||||
timeout: Union[float, DefaultValue] = DEFAULT_NONE,
|
||||
quote: bool = None,
|
||||
tzinfo: pytz.BaseTzInfo = pytz.utc,
|
||||
run_async: bool = False,
|
||||
allow_sending_without_reply: bool = None,
|
||||
):
|
||||
self._parse_mode = parse_mode
|
||||
self._disable_notification = disable_notification
|
||||
self._disable_web_page_preview = disable_web_page_preview
|
||||
self._allow_sending_without_reply = allow_sending_without_reply
|
||||
self._timeout = timeout
|
||||
self._quote = quote
|
||||
self._tzinfo = tzinfo
|
||||
self._run_async = run_async
|
||||
|
||||
@property
|
||||
def parse_mode(self) -> Optional[str]:
|
||||
@@ -82,8 +101,10 @@ class Defaults:
|
||||
|
||||
@parse_mode.setter
|
||||
def parse_mode(self, value: Any) -> NoReturn:
|
||||
raise AttributeError("You can not assign a new value to defaults after because it would "
|
||||
"not have any effect.")
|
||||
raise AttributeError(
|
||||
"You can not assign a new value to defaults after because it would "
|
||||
"not have any effect."
|
||||
)
|
||||
|
||||
@property
|
||||
def disable_notification(self) -> Optional[bool]:
|
||||
@@ -91,8 +112,10 @@ class Defaults:
|
||||
|
||||
@disable_notification.setter
|
||||
def disable_notification(self, value: Any) -> NoReturn:
|
||||
raise AttributeError("You can not assign a new value to defaults after because it would "
|
||||
"not have any effect.")
|
||||
raise AttributeError(
|
||||
"You can not assign a new value to defaults after because it would "
|
||||
"not have any effect."
|
||||
)
|
||||
|
||||
@property
|
||||
def disable_web_page_preview(self) -> Optional[bool]:
|
||||
@@ -100,8 +123,21 @@ class Defaults:
|
||||
|
||||
@disable_web_page_preview.setter
|
||||
def disable_web_page_preview(self, value: Any) -> NoReturn:
|
||||
raise AttributeError("You can not assign a new value to defaults after because it would "
|
||||
"not have any effect.")
|
||||
raise AttributeError(
|
||||
"You can not assign a new value to defaults after because it would "
|
||||
"not have any effect."
|
||||
)
|
||||
|
||||
@property
|
||||
def allow_sending_without_reply(self) -> Optional[bool]:
|
||||
return self._allow_sending_without_reply
|
||||
|
||||
@allow_sending_without_reply.setter
|
||||
def allow_sending_without_reply(self, value: Any) -> NoReturn:
|
||||
raise AttributeError(
|
||||
"You can not assign a new value to defaults after because it would "
|
||||
"not have any effect."
|
||||
)
|
||||
|
||||
@property
|
||||
def timeout(self) -> Union[float, DefaultValue]:
|
||||
@@ -109,8 +145,10 @@ class Defaults:
|
||||
|
||||
@timeout.setter
|
||||
def timeout(self, value: Any) -> NoReturn:
|
||||
raise AttributeError("You can not assign a new value to defaults after because it would "
|
||||
"not have any effect.")
|
||||
raise AttributeError(
|
||||
"You can not assign a new value to defaults after because it would "
|
||||
"not have any effect."
|
||||
)
|
||||
|
||||
@property
|
||||
def quote(self) -> Optional[bool]:
|
||||
@@ -118,8 +156,10 @@ class Defaults:
|
||||
|
||||
@quote.setter
|
||||
def quote(self, value: Any) -> NoReturn:
|
||||
raise AttributeError("You can not assign a new value to defaults after because it would "
|
||||
"not have any effect.")
|
||||
raise AttributeError(
|
||||
"You can not assign a new value to defaults after because it would "
|
||||
"not have any effect."
|
||||
)
|
||||
|
||||
@property
|
||||
def tzinfo(self) -> pytz.BaseTzInfo:
|
||||
@@ -127,16 +167,35 @@ class Defaults:
|
||||
|
||||
@tzinfo.setter
|
||||
def tzinfo(self, value: Any) -> NoReturn:
|
||||
raise AttributeError("You can not assign a new value to defaults after because it would "
|
||||
"not have any effect.")
|
||||
raise AttributeError(
|
||||
"You can not assign a new value to defaults after because it would "
|
||||
"not have any effect."
|
||||
)
|
||||
|
||||
@property
|
||||
def run_async(self) -> Optional[bool]:
|
||||
return self._run_async
|
||||
|
||||
@run_async.setter
|
||||
def run_async(self, value: Any) -> NoReturn:
|
||||
raise AttributeError(
|
||||
"You can not assign a new value to defaults after because it would "
|
||||
"not have any effect."
|
||||
)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash((self._parse_mode,
|
||||
self._disable_notification,
|
||||
self._disable_web_page_preview,
|
||||
self._timeout,
|
||||
self._quote,
|
||||
self._tzinfo))
|
||||
return hash(
|
||||
(
|
||||
self._parse_mode,
|
||||
self._disable_notification,
|
||||
self._disable_web_page_preview,
|
||||
self._allow_sending_without_reply,
|
||||
self._timeout,
|
||||
self._quote,
|
||||
self._tzinfo,
|
||||
self._run_async,
|
||||
)
|
||||
)
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if isinstance(other, Defaults):
|
||||
|
||||
@@ -19,18 +19,21 @@
|
||||
"""This module contains the DictPersistence class."""
|
||||
from copy import deepcopy
|
||||
|
||||
from telegram.utils.helpers import decode_user_chat_data_from_json,\
|
||||
decode_conversations_from_json, encode_conversations_to_json
|
||||
from typing import Any, DefaultDict, Dict, Optional, Tuple
|
||||
from collections import defaultdict
|
||||
|
||||
from telegram.utils.helpers import (
|
||||
decode_conversations_from_json,
|
||||
decode_user_chat_data_from_json,
|
||||
encode_conversations_to_json,
|
||||
)
|
||||
from telegram.ext import BasePersistence
|
||||
from telegram.utils.types import ConversationDict
|
||||
|
||||
try:
|
||||
import ujson as json
|
||||
except ImportError:
|
||||
import json # type: ignore[no-redef]
|
||||
from collections import defaultdict
|
||||
from telegram.ext import BasePersistence
|
||||
|
||||
from typing import DefaultDict, Dict, Any, Tuple, Optional
|
||||
from telegram.utils.types import ConversationDict
|
||||
|
||||
|
||||
class DictPersistence(BasePersistence):
|
||||
@@ -76,17 +79,21 @@ class DictPersistence(BasePersistence):
|
||||
conversation on creating this persistence. Default is ``""``.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
store_user_data: bool = True,
|
||||
store_chat_data: bool = True,
|
||||
store_bot_data: bool = True,
|
||||
user_data_json: str = '',
|
||||
chat_data_json: str = '',
|
||||
bot_data_json: str = '',
|
||||
conversations_json: str = ''):
|
||||
super().__init__(store_user_data=store_user_data,
|
||||
store_chat_data=store_chat_data,
|
||||
store_bot_data=store_bot_data)
|
||||
def __init__(
|
||||
self,
|
||||
store_user_data: bool = True,
|
||||
store_chat_data: bool = True,
|
||||
store_bot_data: bool = True,
|
||||
user_data_json: str = '',
|
||||
chat_data_json: str = '',
|
||||
bot_data_json: str = '',
|
||||
conversations_json: str = '',
|
||||
):
|
||||
super().__init__(
|
||||
store_user_data=store_user_data,
|
||||
store_chat_data=store_chat_data,
|
||||
store_bot_data=store_bot_data,
|
||||
)
|
||||
self._user_data = None
|
||||
self._chat_data = None
|
||||
self._bot_data = None
|
||||
@@ -99,20 +106,20 @@ class DictPersistence(BasePersistence):
|
||||
try:
|
||||
self._user_data = decode_user_chat_data_from_json(user_data_json)
|
||||
self._user_data_json = user_data_json
|
||||
except (ValueError, AttributeError):
|
||||
raise TypeError("Unable to deserialize user_data_json. Not valid JSON")
|
||||
except (ValueError, AttributeError) as exc:
|
||||
raise TypeError("Unable to deserialize user_data_json. Not valid JSON") from exc
|
||||
if chat_data_json:
|
||||
try:
|
||||
self._chat_data = decode_user_chat_data_from_json(chat_data_json)
|
||||
self._chat_data_json = chat_data_json
|
||||
except (ValueError, AttributeError):
|
||||
raise TypeError("Unable to deserialize chat_data_json. Not valid JSON")
|
||||
except (ValueError, AttributeError) as exc:
|
||||
raise TypeError("Unable to deserialize chat_data_json. Not valid JSON") from exc
|
||||
if bot_data_json:
|
||||
try:
|
||||
self._bot_data = json.loads(bot_data_json)
|
||||
self._bot_data_json = bot_data_json
|
||||
except (ValueError, AttributeError):
|
||||
raise TypeError("Unable to deserialize bot_data_json. Not valid JSON")
|
||||
except (ValueError, AttributeError) as exc:
|
||||
raise TypeError("Unable to deserialize bot_data_json. Not valid JSON") from exc
|
||||
if not isinstance(self._bot_data, dict):
|
||||
raise TypeError("bot_data_json must be serialized dict")
|
||||
|
||||
@@ -120,8 +127,10 @@ class DictPersistence(BasePersistence):
|
||||
try:
|
||||
self._conversations = decode_conversations_from_json(conversations_json)
|
||||
self._conversations_json = conversations_json
|
||||
except (ValueError, AttributeError):
|
||||
raise TypeError("Unable to deserialize conversations_json. Not valid JSON")
|
||||
except (ValueError, AttributeError) as exc:
|
||||
raise TypeError(
|
||||
"Unable to deserialize conversations_json. Not valid JSON"
|
||||
) from exc
|
||||
|
||||
@property
|
||||
def user_data(self) -> Optional[DefaultDict[int, Dict]]:
|
||||
@@ -133,8 +142,7 @@ class DictPersistence(BasePersistence):
|
||||
""":obj:`str`: The user_data serialized as a JSON-string."""
|
||||
if self._user_data_json:
|
||||
return self._user_data_json
|
||||
else:
|
||||
return json.dumps(self.user_data)
|
||||
return json.dumps(self.user_data)
|
||||
|
||||
@property
|
||||
def chat_data(self) -> Optional[DefaultDict[int, Dict]]:
|
||||
@@ -146,8 +154,7 @@ class DictPersistence(BasePersistence):
|
||||
""":obj:`str`: The chat_data serialized as a JSON-string."""
|
||||
if self._chat_data_json:
|
||||
return self._chat_data_json
|
||||
else:
|
||||
return json.dumps(self.chat_data)
|
||||
return json.dumps(self.chat_data)
|
||||
|
||||
@property
|
||||
def bot_data(self) -> Optional[Dict]:
|
||||
@@ -159,8 +166,7 @@ class DictPersistence(BasePersistence):
|
||||
""":obj:`str`: The bot_data serialized as a JSON-string."""
|
||||
if self._bot_data_json:
|
||||
return self._bot_data_json
|
||||
else:
|
||||
return json.dumps(self.bot_data)
|
||||
return json.dumps(self.bot_data)
|
||||
|
||||
@property
|
||||
def conversations(self) -> Optional[Dict[str, Dict[Tuple, Any]]]:
|
||||
@@ -172,8 +178,7 @@ class DictPersistence(BasePersistence):
|
||||
""":obj:`str`: The conversations serialized as a JSON-string."""
|
||||
if self._conversations_json:
|
||||
return self._conversations_json
|
||||
else:
|
||||
return encode_conversations_to_json(self.conversations) # type: ignore[arg-type]
|
||||
return encode_conversations_to_json(self.conversations) # type: ignore[arg-type]
|
||||
|
||||
def get_user_data(self) -> DefaultDict[int, Dict[Any, Any]]:
|
||||
"""Returns the user_data created from the ``user_data_json`` or an empty
|
||||
@@ -226,9 +231,9 @@ class DictPersistence(BasePersistence):
|
||||
self._conversations = {}
|
||||
return self.conversations.get(name, {}).copy() # type: ignore[union-attr]
|
||||
|
||||
def update_conversation(self,
|
||||
name: str, key: Tuple[int, ...],
|
||||
new_state: Optional[object]) -> None:
|
||||
def update_conversation(
|
||||
self, name: str, key: Tuple[int, ...], new_state: Optional[object]
|
||||
) -> None:
|
||||
"""Will update the conversations for the given handler.
|
||||
|
||||
Args:
|
||||
|
||||
+109
-88
@@ -21,34 +21,33 @@
|
||||
import logging
|
||||
import warnings
|
||||
import weakref
|
||||
from functools import wraps
|
||||
from threading import Thread, Lock, Event, current_thread, BoundedSemaphore
|
||||
from time import sleep
|
||||
from uuid import uuid4
|
||||
from collections import defaultdict
|
||||
|
||||
from queue import Queue, Empty
|
||||
from functools import wraps
|
||||
from queue import Empty, Queue
|
||||
from threading import BoundedSemaphore, Event, Lock, Thread, current_thread
|
||||
from time import sleep
|
||||
from typing import TYPE_CHECKING, Any, Callable, DefaultDict, Dict, List, Optional, Set, Union
|
||||
from uuid import uuid4
|
||||
|
||||
from telegram import TelegramError, Update
|
||||
from telegram.ext.handler import Handler
|
||||
from telegram.ext import BasePersistence
|
||||
from telegram.ext.callbackcontext import CallbackContext
|
||||
from telegram.ext.handler import Handler
|
||||
from telegram.utils.deprecate import TelegramDeprecationWarning
|
||||
from telegram.utils.promise import Promise
|
||||
from telegram.ext import BasePersistence
|
||||
|
||||
from typing import Any, Callable, TYPE_CHECKING, Optional, Union, DefaultDict, Dict, List, Set
|
||||
|
||||
from telegram.utils.types import HandlerArg
|
||||
from telegram.utils.helpers import DefaultValue, DEFAULT_FALSE
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Bot
|
||||
from telegram.ext import JobQueue
|
||||
|
||||
DEFAULT_GROUP = 0
|
||||
DEFAULT_GROUP: int = 0
|
||||
|
||||
|
||||
def run_async(func: Callable[[Update, CallbackContext],
|
||||
Any]) -> Callable[[Update, CallbackContext], Any]:
|
||||
def run_async(
|
||||
func: Callable[[Update, CallbackContext], Any]
|
||||
) -> Callable[[Update, CallbackContext], Any]:
|
||||
"""
|
||||
Function decorator that will run the function in a new thread.
|
||||
|
||||
@@ -67,12 +66,15 @@ def run_async(func: Callable[[Update, CallbackContext],
|
||||
|
||||
@wraps(func)
|
||||
def async_func(*args: Any, **kwargs: Any) -> Any:
|
||||
warnings.warn('The @run_async decorator is deprecated. Use the `run_async` parameter of'
|
||||
'`Dispatcher.add_handler` or `Dispatcher.run_async` instead.',
|
||||
TelegramDeprecationWarning,
|
||||
stacklevel=2)
|
||||
return Dispatcher.get_instance()._run_async(func, *args, update=None, error_handling=False,
|
||||
**kwargs)
|
||||
warnings.warn(
|
||||
'The @run_async decorator is deprecated. Use the `run_async` parameter of '
|
||||
'your Handler or `Dispatcher.run_async` instead.',
|
||||
TelegramDeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return Dispatcher.get_instance()._run_async( # pylint: disable=W0212
|
||||
func, *args, update=None, error_handling=False, **kwargs
|
||||
)
|
||||
|
||||
return async_func
|
||||
|
||||
@@ -96,6 +98,7 @@ class DispatcherHandlerStop(Exception):
|
||||
Args:
|
||||
state (:obj:`object`, optional): The next state of the conversation.
|
||||
"""
|
||||
|
||||
def __init__(self, state: object = None) -> None:
|
||||
super().__init__()
|
||||
self.state = state
|
||||
@@ -137,14 +140,16 @@ class Dispatcher:
|
||||
__singleton = None
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def __init__(self,
|
||||
bot: 'Bot',
|
||||
update_queue: Queue,
|
||||
workers: int = 4,
|
||||
exception_event: Event = None,
|
||||
job_queue: 'JobQueue' = None,
|
||||
persistence: BasePersistence = None,
|
||||
use_context: bool = True):
|
||||
def __init__(
|
||||
self,
|
||||
bot: 'Bot',
|
||||
update_queue: Queue,
|
||||
workers: int = 4,
|
||||
exception_event: Event = None,
|
||||
job_queue: 'JobQueue' = None,
|
||||
persistence: BasePersistence = None,
|
||||
use_context: bool = True,
|
||||
):
|
||||
self.bot = bot
|
||||
self.update_queue = update_queue
|
||||
self.job_queue = job_queue
|
||||
@@ -152,8 +157,11 @@ class Dispatcher:
|
||||
self.use_context = use_context
|
||||
|
||||
if not use_context:
|
||||
warnings.warn('Old Handler API is deprecated - see https://git.io/fxJuV for details',
|
||||
TelegramDeprecationWarning, stacklevel=3)
|
||||
warnings.warn(
|
||||
'Old Handler API is deprecated - see https://git.io/fxJuV for details',
|
||||
TelegramDeprecationWarning,
|
||||
stacklevel=3,
|
||||
)
|
||||
|
||||
self.user_data: DefaultDict[int, Dict[Any, Any]] = defaultdict(dict)
|
||||
self.chat_data: DefaultDict[int, Dict[Any, Any]] = defaultdict(dict)
|
||||
@@ -184,7 +192,7 @@ class Dispatcher:
|
||||
"""Dict[:obj:`int`, List[:class:`telegram.ext.Handler`]]: Holds the handlers per group."""
|
||||
self.groups: List[int] = []
|
||||
"""List[:obj:`int`]: A list with all groups."""
|
||||
self.error_handlers: Dict[Callable, bool] = {}
|
||||
self.error_handlers: Dict[Callable, Union[bool, DefaultValue]] = {}
|
||||
"""Dict[:obj:`callable`, :obj:`bool`]: A dict, where the keys are error handlers and the
|
||||
values indicate whether they are to be run asynchronously."""
|
||||
|
||||
@@ -208,11 +216,10 @@ class Dispatcher:
|
||||
return self.__exception_event
|
||||
|
||||
def _init_async_threads(self, base_name: str, workers: int) -> None:
|
||||
base_name = '{}_'.format(base_name) if base_name else ''
|
||||
base_name = f'{base_name}_' if base_name else ''
|
||||
|
||||
for i in range(workers):
|
||||
thread = Thread(target=self._pooled, name='Bot:{}:worker:{}{}'.format(self.bot.id,
|
||||
base_name, i))
|
||||
thread = Thread(target=self._pooled, name=f'Bot:{self.bot.id}:worker:{base_name}{i}')
|
||||
self.__async_threads.add(thread)
|
||||
thread.start()
|
||||
|
||||
@@ -234,9 +241,7 @@ class Dispatcher:
|
||||
"""
|
||||
if cls.__singleton is not None:
|
||||
return cls.__singleton() # type: ignore[return-value] # pylint: disable=not-callable
|
||||
else:
|
||||
raise RuntimeError('{} not initialized or multiple instances exist'.format(
|
||||
cls.__name__))
|
||||
raise RuntimeError(f'{cls.__name__} not initialized or multiple instances exist')
|
||||
|
||||
def _pooled(self) -> None:
|
||||
thr_name = current_thread().getName()
|
||||
@@ -245,8 +250,9 @@ class Dispatcher:
|
||||
|
||||
# If unpacking fails, the thread pool is being closed from Updater._join_async_threads
|
||||
if not isinstance(promise, Promise):
|
||||
self.logger.debug("Closing run_async thread %s/%d", thr_name,
|
||||
len(self.__async_threads))
|
||||
self.logger.debug(
|
||||
"Closing run_async thread %s/%d", thr_name, len(self.__async_threads)
|
||||
)
|
||||
break
|
||||
|
||||
promise.run()
|
||||
@@ -258,7 +264,8 @@ class Dispatcher:
|
||||
if isinstance(promise.exception, DispatcherHandlerStop):
|
||||
self.logger.warning(
|
||||
'DispatcherHandlerStop is not supported with async functions; func: %s',
|
||||
promise.pooled_function.__name__)
|
||||
promise.pooled_function.__name__,
|
||||
)
|
||||
continue
|
||||
|
||||
# Avoid infinite recursion of error handlers.
|
||||
@@ -280,11 +287,9 @@ class Dispatcher:
|
||||
except Exception:
|
||||
self.logger.exception('An uncaught error was raised while handling the error.')
|
||||
|
||||
def run_async(self,
|
||||
func: Callable[..., Any],
|
||||
*args: Any,
|
||||
update: HandlerArg = None,
|
||||
**kwargs: Any) -> Promise:
|
||||
def run_async(
|
||||
self, func: Callable[..., Any], *args: Any, update: HandlerArg = None, **kwargs: Any
|
||||
) -> Promise:
|
||||
"""
|
||||
Queue a function (with given args/kwargs) to be run asynchronously. Exceptions raised
|
||||
by the function will be handled by the error handlers registered with
|
||||
@@ -310,12 +315,14 @@ class Dispatcher:
|
||||
"""
|
||||
return self._run_async(func, *args, update=update, error_handling=True, **kwargs)
|
||||
|
||||
def _run_async(self,
|
||||
func: Callable[..., Any],
|
||||
*args: Any,
|
||||
update: HandlerArg = None,
|
||||
error_handling: bool = True,
|
||||
**kwargs: Any) -> Promise:
|
||||
def _run_async(
|
||||
self,
|
||||
func: Callable[..., Any],
|
||||
*args: Any,
|
||||
update: HandlerArg = None,
|
||||
error_handling: bool = True,
|
||||
**kwargs: Any,
|
||||
) -> Promise:
|
||||
# TODO: Remove error_handling parameter once we drop the @run_async decorator
|
||||
promise = Promise(func, args, kwargs, update=update, error_handling=error_handling)
|
||||
self.__async_queue.put(promise)
|
||||
@@ -357,12 +364,12 @@ class Dispatcher:
|
||||
if self.__stop_event.is_set():
|
||||
self.logger.debug('orderly stopping')
|
||||
break
|
||||
elif self.__exception_event.is_set():
|
||||
if self.__exception_event.is_set():
|
||||
self.logger.critical('stopping due to exception in another thread')
|
||||
break
|
||||
continue
|
||||
|
||||
self.logger.debug('Processing Update: %s' % update)
|
||||
self.logger.debug('Processing Update: %s', update)
|
||||
self.process_update(update)
|
||||
self.update_queue.task_done()
|
||||
|
||||
@@ -387,10 +394,10 @@ class Dispatcher:
|
||||
self.__async_queue.put(None)
|
||||
|
||||
for i, thr in enumerate(threads):
|
||||
self.logger.debug('Waiting for async thread {}/{} to end'.format(i + 1, total))
|
||||
self.logger.debug('Waiting for async thread %s/%s to end', i + 1, total)
|
||||
thr.join()
|
||||
self.__async_threads.remove(thr)
|
||||
self.logger.debug('async thread {}/{} has ended'.format(i + 1, total))
|
||||
self.logger.debug('async thread %s/%s has ended', i + 1, total)
|
||||
|
||||
@property
|
||||
def has_running_threads(self) -> bool:
|
||||
@@ -436,9 +443,9 @@ class Dispatcher:
|
||||
break
|
||||
|
||||
# Dispatch any error.
|
||||
except Exception as e:
|
||||
except Exception as exc:
|
||||
try:
|
||||
self.dispatch_error(update, e)
|
||||
self.dispatch_error(update, exc)
|
||||
except DispatcherHandlerStop:
|
||||
self.logger.debug('Error handler stopped further handlers')
|
||||
break
|
||||
@@ -472,17 +479,18 @@ class Dispatcher:
|
||||
|
||||
"""
|
||||
# Unfortunately due to circular imports this has to be here
|
||||
from .conversationhandler import ConversationHandler
|
||||
from .conversationhandler import ConversationHandler # pylint: disable=C0415
|
||||
|
||||
if not isinstance(handler, Handler):
|
||||
raise TypeError('handler is not an instance of {}'.format(Handler.__name__))
|
||||
raise TypeError(f'handler is not an instance of {Handler.__name__}')
|
||||
if not isinstance(group, int):
|
||||
raise TypeError('group is not int')
|
||||
if isinstance(handler, ConversationHandler) and handler.persistent and handler.name:
|
||||
if not self.persistence:
|
||||
raise ValueError(
|
||||
"ConversationHandler {} can not be persistent if dispatcher has no "
|
||||
"persistence".format(handler.name))
|
||||
f"ConversationHandler {handler.name} can not be persistent if dispatcher has "
|
||||
f"no persistence"
|
||||
)
|
||||
handler.persistence = self.persistence
|
||||
handler.conversations = self.persistence.get_conversations(handler.name)
|
||||
|
||||
@@ -537,42 +545,50 @@ class Dispatcher:
|
||||
if self.persistence.store_bot_data:
|
||||
try:
|
||||
self.persistence.update_bot_data(self.bot_data)
|
||||
except Exception as e:
|
||||
except Exception as exc:
|
||||
try:
|
||||
self.dispatch_error(update, e)
|
||||
self.dispatch_error(update, exc)
|
||||
except Exception:
|
||||
message = 'Saving bot data raised an error and an ' \
|
||||
'uncaught error was raised while handling ' \
|
||||
'the error with an error_handler'
|
||||
message = (
|
||||
'Saving bot data raised an error and an '
|
||||
'uncaught error was raised while handling '
|
||||
'the error with an error_handler'
|
||||
)
|
||||
self.logger.exception(message)
|
||||
if self.persistence.store_chat_data:
|
||||
for chat_id in chat_ids:
|
||||
try:
|
||||
self.persistence.update_chat_data(chat_id, self.chat_data[chat_id])
|
||||
except Exception as e:
|
||||
except Exception as exc:
|
||||
try:
|
||||
self.dispatch_error(update, e)
|
||||
self.dispatch_error(update, exc)
|
||||
except Exception:
|
||||
message = 'Saving chat data raised an error and an ' \
|
||||
'uncaught error was raised while handling ' \
|
||||
'the error with an error_handler'
|
||||
message = (
|
||||
'Saving chat data raised an error and an '
|
||||
'uncaught error was raised while handling '
|
||||
'the error with an error_handler'
|
||||
)
|
||||
self.logger.exception(message)
|
||||
if self.persistence.store_user_data:
|
||||
for user_id in user_ids:
|
||||
try:
|
||||
self.persistence.update_user_data(user_id, self.user_data[user_id])
|
||||
except Exception as e:
|
||||
except Exception as exc:
|
||||
try:
|
||||
self.dispatch_error(update, e)
|
||||
self.dispatch_error(update, exc)
|
||||
except Exception:
|
||||
message = 'Saving user data raised an error and an ' \
|
||||
'uncaught error was raised while handling ' \
|
||||
'the error with an error_handler'
|
||||
message = (
|
||||
'Saving user data raised an error and an '
|
||||
'uncaught error was raised while handling '
|
||||
'the error with an error_handler'
|
||||
)
|
||||
self.logger.exception(message)
|
||||
|
||||
def add_error_handler(self,
|
||||
callback: Callable[[Any, CallbackContext], None],
|
||||
run_async: bool = False) -> None:
|
||||
def add_error_handler(
|
||||
self,
|
||||
callback: Callable[[Any, CallbackContext], None],
|
||||
run_async: Union[bool, DefaultValue] = DEFAULT_FALSE, # pylint: disable=W0621
|
||||
) -> None:
|
||||
"""Registers an error handler in the Dispatcher. This handler will receive every error
|
||||
which happens in your bot.
|
||||
|
||||
@@ -599,6 +615,11 @@ class Dispatcher:
|
||||
if callback in self.error_handlers:
|
||||
self.logger.debug('The callback is already registered as an error handler. Ignoring.')
|
||||
return
|
||||
|
||||
if run_async is DEFAULT_FALSE and self.bot.defaults:
|
||||
if self.bot.defaults.run_async:
|
||||
run_async = True
|
||||
|
||||
self.error_handlers[callback] = run_async
|
||||
|
||||
def remove_error_handler(self, callback: Callable[[Any, CallbackContext], None]) -> None:
|
||||
@@ -610,10 +631,9 @@ class Dispatcher:
|
||||
"""
|
||||
self.error_handlers.pop(callback, None)
|
||||
|
||||
def dispatch_error(self,
|
||||
update: Optional[HandlerArg],
|
||||
error: Exception,
|
||||
promise: Promise = None) -> None:
|
||||
def dispatch_error(
|
||||
self, update: Optional[HandlerArg], error: Exception, promise: Promise = None
|
||||
) -> None:
|
||||
"""Dispatches an error.
|
||||
|
||||
Args:
|
||||
@@ -627,11 +647,11 @@ class Dispatcher:
|
||||
async_kwargs = None if not promise else promise.kwargs
|
||||
|
||||
if self.error_handlers:
|
||||
for callback, run_async in self.error_handlers.items():
|
||||
for callback, run_async in self.error_handlers.items(): # pylint: disable=W0621
|
||||
if self.use_context:
|
||||
context = CallbackContext.from_error(update, error, self,
|
||||
async_args=async_args,
|
||||
async_kwargs=async_kwargs)
|
||||
context = CallbackContext.from_error(
|
||||
update, error, self, async_args=async_args, async_kwargs=async_kwargs
|
||||
)
|
||||
if run_async:
|
||||
self.run_async(callback, update, context, update=update)
|
||||
else:
|
||||
@@ -644,4 +664,5 @@ class Dispatcher:
|
||||
|
||||
else:
|
||||
self.logger.exception(
|
||||
'No error handlers are registered, logging exception.', exc_info=error)
|
||||
'No error handlers are registered, logging exception.', exc_info=error
|
||||
)
|
||||
|
||||
+763
-422
File diff suppressed because it is too large
Load Diff
+52
-36
@@ -19,11 +19,13 @@
|
||||
"""This module contains the base class for handlers as used by the Dispatcher."""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, TypeVar, Union
|
||||
|
||||
from telegram import Update
|
||||
from telegram.utils.promise import Promise
|
||||
from telegram.utils.types import HandlerArg
|
||||
from telegram import Update
|
||||
from typing import Callable, TYPE_CHECKING, Any, Optional, Union, TypeVar, Dict
|
||||
from telegram.utils.helpers import DefaultValue, DEFAULT_FALSE
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram.ext import CallbackContext, Dispatcher
|
||||
|
||||
@@ -87,13 +89,16 @@ class Handler(ABC):
|
||||
Defaults to :obj:`False`.
|
||||
|
||||
"""
|
||||
def __init__(self,
|
||||
callback: Callable[[HandlerArg, 'CallbackContext'], RT],
|
||||
pass_update_queue: bool = False,
|
||||
pass_job_queue: bool = False,
|
||||
pass_user_data: bool = False,
|
||||
pass_chat_data: bool = False,
|
||||
run_async: bool = False):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
callback: Callable[[HandlerArg, 'CallbackContext'], RT],
|
||||
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,
|
||||
):
|
||||
self.callback: Callable[[HandlerArg, 'CallbackContext'], RT] = callback
|
||||
self.pass_update_queue = pass_update_queue
|
||||
self.pass_job_queue = pass_job_queue
|
||||
@@ -117,11 +122,13 @@ class Handler(ABC):
|
||||
|
||||
"""
|
||||
|
||||
def handle_update(self,
|
||||
update: HandlerArg,
|
||||
dispatcher: 'Dispatcher',
|
||||
check_result: object,
|
||||
context: 'CallbackContext' = None) -> Union[RT, Promise]:
|
||||
def handle_update(
|
||||
self,
|
||||
update: HandlerArg,
|
||||
dispatcher: 'Dispatcher',
|
||||
check_result: object,
|
||||
context: 'CallbackContext' = None,
|
||||
) -> Union[RT, Promise]:
|
||||
"""
|
||||
This method is called if it was determined that an update should indeed
|
||||
be handled by this instance. Calls :attr:`callback` along with its respectful
|
||||
@@ -137,25 +144,31 @@ class Handler(ABC):
|
||||
the dispatcher.
|
||||
|
||||
"""
|
||||
run_async = self.run_async
|
||||
if self.run_async is DEFAULT_FALSE and dispatcher.bot.defaults:
|
||||
if dispatcher.bot.defaults.run_async:
|
||||
run_async = True
|
||||
|
||||
if context:
|
||||
self.collect_additional_context(context, update, dispatcher, check_result)
|
||||
if self.run_async:
|
||||
if run_async:
|
||||
return dispatcher.run_async(self.callback, update, context, update=update)
|
||||
else:
|
||||
return self.callback(update, context)
|
||||
else:
|
||||
optional_args = self.collect_optional_args(dispatcher, update, check_result)
|
||||
if self.run_async:
|
||||
return dispatcher.run_async(self.callback, dispatcher.bot, update, update=update,
|
||||
**optional_args)
|
||||
else:
|
||||
return self.callback(dispatcher.bot, update, **optional_args) # type: ignore
|
||||
return self.callback(update, context)
|
||||
|
||||
def collect_additional_context(self,
|
||||
context: 'CallbackContext',
|
||||
update: HandlerArg,
|
||||
dispatcher: 'Dispatcher',
|
||||
check_result: Any) -> None:
|
||||
optional_args = self.collect_optional_args(dispatcher, update, check_result)
|
||||
if run_async:
|
||||
return dispatcher.run_async(
|
||||
self.callback, dispatcher.bot, update, update=update, **optional_args
|
||||
)
|
||||
return self.callback(dispatcher.bot, update, **optional_args) # type: ignore
|
||||
|
||||
def collect_additional_context(
|
||||
self,
|
||||
context: 'CallbackContext',
|
||||
update: HandlerArg,
|
||||
dispatcher: 'Dispatcher',
|
||||
check_result: Any,
|
||||
) -> None:
|
||||
"""Prepares additional arguments for the context. Override if needed.
|
||||
|
||||
Args:
|
||||
@@ -165,12 +178,13 @@ class Handler(ABC):
|
||||
check_result: The result (return value) from :attr:`check_update`.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
def collect_optional_args(self,
|
||||
dispatcher: 'Dispatcher',
|
||||
update: HandlerArg = None,
|
||||
check_result: Any = None) -> Dict[str, Any]:
|
||||
def collect_optional_args(
|
||||
self,
|
||||
dispatcher: 'Dispatcher',
|
||||
update: HandlerArg = None,
|
||||
check_result: Any = None, # pylint: disable=W0613
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Prepares the optional arguments. If the handler has additional optional args,
|
||||
it should subclass this method, but remember to call this super method.
|
||||
@@ -193,10 +207,12 @@ class Handler(ABC):
|
||||
if self.pass_user_data and isinstance(update, Update):
|
||||
user = update.effective_user
|
||||
optional_args['user_data'] = dispatcher.user_data[
|
||||
user.id if user else None] # type: ignore[index]
|
||||
user.id if user else None # type: ignore[index]
|
||||
]
|
||||
if self.pass_chat_data and isinstance(update, Update):
|
||||
chat = update.effective_chat
|
||||
optional_args['chat_data'] = dispatcher.chat_data[
|
||||
chat.id if chat else None] # type: ignore[index]
|
||||
chat.id if chat else None # type: ignore[index]
|
||||
]
|
||||
|
||||
return optional_args
|
||||
|
||||
@@ -18,15 +18,25 @@
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
""" This module contains the InlineQueryHandler class """
|
||||
import re
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Callable,
|
||||
Dict,
|
||||
Match,
|
||||
Optional,
|
||||
Pattern,
|
||||
TypeVar,
|
||||
Union,
|
||||
cast,
|
||||
)
|
||||
|
||||
from telegram import Update
|
||||
from telegram.utils.types import HandlerArg
|
||||
from telegram.utils.helpers import DefaultValue, DEFAULT_FALSE
|
||||
|
||||
from .handler import Handler
|
||||
|
||||
from telegram.utils.types import HandlerArg
|
||||
from typing import Callable, TYPE_CHECKING, Any, Optional, Union, TypeVar, Dict, Pattern, Match, \
|
||||
cast
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram.ext import CallbackContext, Dispatcher
|
||||
|
||||
@@ -110,23 +120,26 @@ class InlineQueryHandler(Handler):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
callback: Callable[[HandlerArg, 'CallbackContext'], RT],
|
||||
pass_update_queue: bool = False,
|
||||
pass_job_queue: bool = False,
|
||||
pattern: Union[str, Pattern] = None,
|
||||
pass_groups: bool = False,
|
||||
pass_groupdict: bool = False,
|
||||
pass_user_data: bool = False,
|
||||
pass_chat_data: bool = False,
|
||||
run_async: bool = False):
|
||||
def __init__(
|
||||
self,
|
||||
callback: Callable[[HandlerArg, 'CallbackContext'], RT],
|
||||
pass_update_queue: bool = False,
|
||||
pass_job_queue: bool = False,
|
||||
pattern: Union[str, Pattern] = None,
|
||||
pass_groups: bool = False,
|
||||
pass_groupdict: 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)
|
||||
run_async=run_async,
|
||||
)
|
||||
|
||||
if isinstance(pattern, str):
|
||||
pattern = re.compile(pattern)
|
||||
@@ -157,10 +170,12 @@ class InlineQueryHandler(Handler):
|
||||
return True
|
||||
return None
|
||||
|
||||
def collect_optional_args(self,
|
||||
dispatcher: 'Dispatcher',
|
||||
update: HandlerArg = None,
|
||||
check_result: Optional[Union[bool, Match]] = None) -> Dict[str, Any]:
|
||||
def collect_optional_args(
|
||||
self,
|
||||
dispatcher: 'Dispatcher',
|
||||
update: HandlerArg = None,
|
||||
check_result: Optional[Union[bool, Match]] = None,
|
||||
) -> Dict[str, Any]:
|
||||
optional_args = super().collect_optional_args(dispatcher, update, check_result)
|
||||
if self.pattern:
|
||||
check_result = cast(Match, check_result)
|
||||
@@ -170,11 +185,13 @@ class InlineQueryHandler(Handler):
|
||||
optional_args['groupdict'] = check_result.groupdict()
|
||||
return optional_args
|
||||
|
||||
def collect_additional_context(self,
|
||||
context: 'CallbackContext',
|
||||
update: HandlerArg,
|
||||
dispatcher: 'Dispatcher',
|
||||
check_result: Optional[Union[bool, Match]]) -> None:
|
||||
def collect_additional_context(
|
||||
self,
|
||||
context: 'CallbackContext',
|
||||
update: HandlerArg,
|
||||
dispatcher: 'Dispatcher',
|
||||
check_result: Optional[Union[bool, Match]],
|
||||
) -> None:
|
||||
if self.pattern:
|
||||
check_result = cast(Match, check_result)
|
||||
context.matches = [check_result]
|
||||
|
||||
+166
-133
@@ -16,24 +16,25 @@
|
||||
#
|
||||
# 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
|
||||
import logging
|
||||
import pytz
|
||||
from typing import TYPE_CHECKING, Any, Callable, List, Optional, Tuple, Union, cast, overload
|
||||
|
||||
import pytz
|
||||
from apscheduler.events import EVENT_JOB_ERROR, EVENT_JOB_EXECUTED, JobEvent
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
from apscheduler.triggers.cron import CronTrigger
|
||||
from apscheduler.triggers.combining import OrTrigger
|
||||
from apscheduler.events import EVENT_JOB_EXECUTED, EVENT_JOB_ERROR, JobEvent
|
||||
from apscheduler.triggers.cron import CronTrigger
|
||||
|
||||
from telegram.ext.callbackcontext import CallbackContext
|
||||
|
||||
from typing import TYPE_CHECKING, Union, Callable, Tuple, Optional, List, Any, cast, overload
|
||||
from telegram.utils.types import JSONDict
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram.ext import Dispatcher
|
||||
from telegram import Bot
|
||||
from telegram.ext import Dispatcher
|
||||
|
||||
|
||||
class Days:
|
||||
@@ -56,8 +57,9 @@ class JobQueue:
|
||||
self._dispatcher: 'Dispatcher' = None # type: ignore[assignment]
|
||||
self.logger = logging.getLogger(self.__class__.__name__)
|
||||
self.scheduler = BackgroundScheduler(timezone=pytz.utc)
|
||||
self.scheduler.add_listener(self._update_persistence,
|
||||
mask=EVENT_JOB_EXECUTED | EVENT_JOB_ERROR)
|
||||
self.scheduler.add_listener(
|
||||
self._update_persistence, mask=EVENT_JOB_EXECUTED | EVENT_JOB_ERROR
|
||||
)
|
||||
|
||||
# Dispatch errors and don't log them in the APS logger
|
||||
def aps_log_filter(record): # type: ignore
|
||||
@@ -74,7 +76,7 @@ class JobQueue:
|
||||
def _tz_now(self) -> datetime.datetime:
|
||||
return datetime.datetime.now(self.scheduler.timezone)
|
||||
|
||||
def _update_persistence(self, event: JobEvent) -> None:
|
||||
def _update_persistence(self, event: JobEvent) -> None: # pylint: disable=W0613
|
||||
self._dispatcher.update_persistence()
|
||||
|
||||
def _dispatch_error(self, event: JobEvent) -> None:
|
||||
@@ -82,25 +84,29 @@ class JobQueue:
|
||||
self._dispatcher.dispatch_error(None, event.exception)
|
||||
# Errors should not stop the thread.
|
||||
except Exception:
|
||||
self.logger.exception('An error was raised while processing the job and an '
|
||||
'uncaught error was raised while handling the error '
|
||||
'with an error_handler.')
|
||||
self.logger.exception(
|
||||
'An error was raised while processing the job and an '
|
||||
'uncaught error was raised while handling the error '
|
||||
'with an error_handler.'
|
||||
)
|
||||
|
||||
@overload
|
||||
def _parse_time_input(self, time: None, shift_day: bool = False) -> None:
|
||||
...
|
||||
|
||||
@overload
|
||||
def _parse_time_input(self,
|
||||
time: Union[float, int, datetime.timedelta, datetime.datetime,
|
||||
datetime.time],
|
||||
shift_day: bool = False) -> datetime.datetime:
|
||||
def _parse_time_input(
|
||||
self,
|
||||
time: Union[float, int, datetime.timedelta, datetime.datetime, datetime.time],
|
||||
shift_day: bool = False,
|
||||
) -> datetime.datetime:
|
||||
...
|
||||
|
||||
def _parse_time_input(self,
|
||||
time: Union[float, int, datetime.timedelta, datetime.datetime,
|
||||
datetime.time, None],
|
||||
shift_day: bool = False) -> Optional[datetime.datetime]:
|
||||
def _parse_time_input(
|
||||
self,
|
||||
time: Union[float, int, datetime.timedelta, datetime.datetime, datetime.time, None],
|
||||
shift_day: bool = False,
|
||||
) -> Optional[datetime.datetime]:
|
||||
if time is None:
|
||||
return None
|
||||
if isinstance(time, (int, float)):
|
||||
@@ -108,13 +114,14 @@ class JobQueue:
|
||||
if isinstance(time, datetime.timedelta):
|
||||
return self._tz_now() + time
|
||||
if isinstance(time, datetime.time):
|
||||
dt = datetime.datetime.combine(
|
||||
datetime.datetime.now(tz=time.tzinfo or self.scheduler.timezone).date(), time)
|
||||
if dt.tzinfo is None:
|
||||
dt = self.scheduler.timezone.localize(dt)
|
||||
if shift_day and dt <= datetime.datetime.now(pytz.utc):
|
||||
dt += datetime.timedelta(days=1)
|
||||
return dt
|
||||
date_time = datetime.datetime.combine(
|
||||
datetime.datetime.now(tz=time.tzinfo or self.scheduler.timezone).date(), time
|
||||
)
|
||||
if date_time.tzinfo is None:
|
||||
date_time = self.scheduler.timezone.localize(date_time)
|
||||
if shift_day and date_time <= datetime.datetime.now(pytz.utc):
|
||||
date_time += datetime.timedelta(days=1)
|
||||
return date_time
|
||||
# isinstance(time, datetime.datetime):
|
||||
return time
|
||||
|
||||
@@ -131,12 +138,14 @@ class JobQueue:
|
||||
if dispatcher.bot.defaults:
|
||||
self.scheduler.configure(timezone=dispatcher.bot.defaults.tzinfo or pytz.utc)
|
||||
|
||||
def run_once(self,
|
||||
callback: Callable[['CallbackContext'], None],
|
||||
when: Union[float, datetime.timedelta, datetime.datetime, datetime.time],
|
||||
context: object = None,
|
||||
name: str = None,
|
||||
job_kwargs: JSONDict = None) -> 'Job':
|
||||
def run_once(
|
||||
self,
|
||||
callback: Callable[['CallbackContext'], None],
|
||||
when: Union[float, datetime.timedelta, datetime.datetime, datetime.time],
|
||||
context: object = None,
|
||||
name: str = None,
|
||||
job_kwargs: JSONDict = None,
|
||||
) -> 'Job':
|
||||
"""Creates a new ``Job`` that runs once and adds it to the queue.
|
||||
|
||||
Args:
|
||||
@@ -181,29 +190,31 @@ class JobQueue:
|
||||
|
||||
name = name or callback.__name__
|
||||
job = Job(callback, context, name, self)
|
||||
dt = self._parse_time_input(when, shift_day=True)
|
||||
date_time = self._parse_time_input(when, shift_day=True)
|
||||
|
||||
j = self.scheduler.add_job(callback,
|
||||
name=name,
|
||||
trigger='date',
|
||||
run_date=dt,
|
||||
args=self._build_args(job),
|
||||
timezone=dt.tzinfo or self.scheduler.timezone,
|
||||
**job_kwargs)
|
||||
j = self.scheduler.add_job(
|
||||
callback,
|
||||
name=name,
|
||||
trigger='date',
|
||||
run_date=date_time,
|
||||
args=self._build_args(job),
|
||||
timezone=date_time.tzinfo or self.scheduler.timezone,
|
||||
**job_kwargs,
|
||||
)
|
||||
|
||||
job.job = j
|
||||
return job
|
||||
|
||||
def run_repeating(self,
|
||||
callback: Callable[['CallbackContext'], None],
|
||||
interval: Union[float, datetime.timedelta],
|
||||
first: Union[float, datetime.timedelta, datetime.datetime,
|
||||
datetime.time] = None,
|
||||
last: Union[float, datetime.timedelta, datetime.datetime,
|
||||
datetime.time] = None,
|
||||
context: object = None,
|
||||
name: str = None,
|
||||
job_kwargs: JSONDict = None) -> 'Job':
|
||||
def run_repeating(
|
||||
self,
|
||||
callback: Callable[['CallbackContext'], None],
|
||||
interval: Union[float, datetime.timedelta],
|
||||
first: Union[float, datetime.timedelta, datetime.datetime, datetime.time] = None,
|
||||
last: Union[float, datetime.timedelta, datetime.datetime, datetime.time] = None,
|
||||
context: object = None,
|
||||
name: str = None,
|
||||
job_kwargs: JSONDict = None,
|
||||
) -> 'Job':
|
||||
"""Creates a new ``Job`` that runs at specified intervals and adds it to the queue.
|
||||
|
||||
Args:
|
||||
@@ -277,26 +288,30 @@ class JobQueue:
|
||||
if isinstance(interval, datetime.timedelta):
|
||||
interval = interval.total_seconds()
|
||||
|
||||
j = self.scheduler.add_job(callback,
|
||||
trigger='interval',
|
||||
args=self._build_args(job),
|
||||
start_date=dt_first,
|
||||
end_date=dt_last,
|
||||
seconds=interval,
|
||||
name=name,
|
||||
**job_kwargs)
|
||||
j = self.scheduler.add_job(
|
||||
callback,
|
||||
trigger='interval',
|
||||
args=self._build_args(job),
|
||||
start_date=dt_first,
|
||||
end_date=dt_last,
|
||||
seconds=interval,
|
||||
name=name,
|
||||
**job_kwargs,
|
||||
)
|
||||
|
||||
job.job = j
|
||||
return job
|
||||
|
||||
def run_monthly(self,
|
||||
callback: Callable[['CallbackContext'], None],
|
||||
when: datetime.time,
|
||||
day: int,
|
||||
context: object = None,
|
||||
name: str = None,
|
||||
day_is_strict: bool = True,
|
||||
job_kwargs: JSONDict = None) -> 'Job':
|
||||
def run_monthly(
|
||||
self,
|
||||
callback: Callable[['CallbackContext'], None],
|
||||
when: datetime.time,
|
||||
day: int,
|
||||
context: object = None,
|
||||
name: str = None,
|
||||
day_is_strict: bool = True,
|
||||
job_kwargs: JSONDict = None,
|
||||
) -> 'Job':
|
||||
"""Creates a new ``Job`` that runs on a monthly basis and adds it to the queue.
|
||||
|
||||
Args:
|
||||
@@ -332,45 +347,55 @@ class JobQueue:
|
||||
job = Job(callback, context, name, self)
|
||||
|
||||
if day_is_strict:
|
||||
j = self.scheduler.add_job(callback,
|
||||
trigger='cron',
|
||||
args=self._build_args(job),
|
||||
name=name,
|
||||
day=day,
|
||||
hour=when.hour,
|
||||
minute=when.minute,
|
||||
second=when.second,
|
||||
timezone=when.tzinfo or self.scheduler.timezone,
|
||||
**job_kwargs)
|
||||
j = self.scheduler.add_job(
|
||||
callback,
|
||||
trigger='cron',
|
||||
args=self._build_args(job),
|
||||
name=name,
|
||||
day=day,
|
||||
hour=when.hour,
|
||||
minute=when.minute,
|
||||
second=when.second,
|
||||
timezone=when.tzinfo or self.scheduler.timezone,
|
||||
**job_kwargs,
|
||||
)
|
||||
else:
|
||||
trigger = OrTrigger([CronTrigger(day=day,
|
||||
hour=when.hour,
|
||||
minute=when.minute,
|
||||
second=when.second,
|
||||
timezone=when.tzinfo,
|
||||
**job_kwargs),
|
||||
CronTrigger(day='last',
|
||||
hour=when.hour,
|
||||
minute=when.minute,
|
||||
second=when.second,
|
||||
timezone=when.tzinfo or self.scheduler.timezone,
|
||||
**job_kwargs)])
|
||||
j = self.scheduler.add_job(callback,
|
||||
trigger=trigger,
|
||||
args=self._build_args(job),
|
||||
name=name,
|
||||
**job_kwargs)
|
||||
trigger = OrTrigger(
|
||||
[
|
||||
CronTrigger(
|
||||
day=day,
|
||||
hour=when.hour,
|
||||
minute=when.minute,
|
||||
second=when.second,
|
||||
timezone=when.tzinfo,
|
||||
**job_kwargs,
|
||||
),
|
||||
CronTrigger(
|
||||
day='last',
|
||||
hour=when.hour,
|
||||
minute=when.minute,
|
||||
second=when.second,
|
||||
timezone=when.tzinfo or self.scheduler.timezone,
|
||||
**job_kwargs,
|
||||
),
|
||||
]
|
||||
)
|
||||
j = self.scheduler.add_job(
|
||||
callback, trigger=trigger, args=self._build_args(job), name=name, **job_kwargs
|
||||
)
|
||||
|
||||
job.job = j
|
||||
return job
|
||||
|
||||
def run_daily(self,
|
||||
callback: Callable[['CallbackContext'], None],
|
||||
time: datetime.time,
|
||||
days: Tuple[int, ...] = Days.EVERY_DAY,
|
||||
context: object = None,
|
||||
name: str = None,
|
||||
job_kwargs: JSONDict = None) -> 'Job':
|
||||
def run_daily(
|
||||
self,
|
||||
callback: Callable[['CallbackContext'], None],
|
||||
time: datetime.time,
|
||||
days: Tuple[int, ...] = Days.EVERY_DAY,
|
||||
context: object = None,
|
||||
name: str = None,
|
||||
job_kwargs: JSONDict = None,
|
||||
) -> 'Job':
|
||||
"""Creates a new ``Job`` that runs on a daily basis and adds it to the queue.
|
||||
|
||||
Args:
|
||||
@@ -409,25 +434,29 @@ class JobQueue:
|
||||
name = name or callback.__name__
|
||||
job = Job(callback, context, name, self)
|
||||
|
||||
j = self.scheduler.add_job(callback,
|
||||
name=name,
|
||||
args=self._build_args(job),
|
||||
trigger='cron',
|
||||
day_of_week=','.join([str(d) for d in days]),
|
||||
hour=time.hour,
|
||||
minute=time.minute,
|
||||
second=time.second,
|
||||
timezone=time.tzinfo or self.scheduler.timezone,
|
||||
**job_kwargs)
|
||||
j = self.scheduler.add_job(
|
||||
callback,
|
||||
name=name,
|
||||
args=self._build_args(job),
|
||||
trigger='cron',
|
||||
day_of_week=','.join([str(d) for d in days]),
|
||||
hour=time.hour,
|
||||
minute=time.minute,
|
||||
second=time.second,
|
||||
timezone=time.tzinfo or self.scheduler.timezone,
|
||||
**job_kwargs,
|
||||
)
|
||||
|
||||
job.job = j
|
||||
return job
|
||||
|
||||
def run_custom(self,
|
||||
callback: Callable[['CallbackContext'], None],
|
||||
job_kwargs: JSONDict,
|
||||
context: object = None,
|
||||
name: str = None) -> 'Job':
|
||||
def run_custom(
|
||||
self,
|
||||
callback: Callable[['CallbackContext'], None],
|
||||
job_kwargs: JSONDict,
|
||||
context: object = None,
|
||||
name: str = None,
|
||||
) -> 'Job':
|
||||
"""Creates a new customly defined ``Job``.
|
||||
|
||||
Args:
|
||||
@@ -453,10 +482,7 @@ class JobQueue:
|
||||
name = name or callback.__name__
|
||||
job = Job(callback, context, name, self)
|
||||
|
||||
j = self.scheduler.add_job(callback,
|
||||
args=self._build_args(job),
|
||||
name=name,
|
||||
**job_kwargs)
|
||||
j = self.scheduler.add_job(callback, args=self._build_args(job), name=name, **job_kwargs)
|
||||
|
||||
job.job = j
|
||||
return job
|
||||
@@ -472,11 +498,14 @@ class JobQueue:
|
||||
self.scheduler.shutdown()
|
||||
|
||||
def jobs(self) -> Tuple['Job', ...]:
|
||||
"""Returns a tuple of all jobs that are currently in the ``JobQueue``."""
|
||||
"""
|
||||
Returns a tuple of all *pending/scheduled* jobs that are currently in the ``JobQueue``.
|
||||
"""
|
||||
return tuple(Job.from_aps_job(job, self) for job in self.scheduler.get_jobs())
|
||||
|
||||
def get_jobs_by_name(self, name: str) -> Tuple['Job', ...]:
|
||||
"""Returns a tuple of jobs with the given name that are currently in the ``JobQueue``"""
|
||||
"""Returns a tuple of all *pending/scheduled* jobs with the given name that are currently
|
||||
in the ``JobQueue``"""
|
||||
return tuple(job for job in self.jobs() if job.name == name)
|
||||
|
||||
|
||||
@@ -516,12 +545,14 @@ class Job:
|
||||
job (:class:`apscheduler.job.Job`, optional): The APS Job this job is a wrapper for.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
callback: Callable[['CallbackContext'], None],
|
||||
context: object = None,
|
||||
name: str = None,
|
||||
job_queue: JobQueue = None,
|
||||
job: 'Job' = None):
|
||||
def __init__(
|
||||
self,
|
||||
callback: Callable[['CallbackContext'], None],
|
||||
context: object = None,
|
||||
name: str = None,
|
||||
job_queue: JobQueue = None,
|
||||
job: 'Job' = None,
|
||||
):
|
||||
|
||||
self.callback = callback
|
||||
self.context = context
|
||||
@@ -540,14 +571,16 @@ class Job:
|
||||
self.callback(CallbackContext.from_job(self, dispatcher))
|
||||
else:
|
||||
self.callback(dispatcher.bot, self) # type: ignore[arg-type,call-arg]
|
||||
except Exception as e:
|
||||
except Exception as exc:
|
||||
try:
|
||||
dispatcher.dispatch_error(None, e)
|
||||
dispatcher.dispatch_error(None, exc)
|
||||
# Errors should not stop the thread.
|
||||
except Exception:
|
||||
dispatcher.logger.exception('An error was raised while processing the job and an '
|
||||
'uncaught error was raised while handling the error '
|
||||
'with an error_handler.')
|
||||
dispatcher.logger.exception(
|
||||
'An error was raised while processing the job and an '
|
||||
'uncaught error was raised while handling the error '
|
||||
'with an error_handler.'
|
||||
)
|
||||
|
||||
def schedule_removal(self) -> None:
|
||||
"""
|
||||
|
||||
@@ -19,15 +19,16 @@
|
||||
# TODO: Remove allow_edited
|
||||
"""This module contains the MessageHandler class."""
|
||||
import warnings
|
||||
|
||||
from telegram.utils.deprecate import TelegramDeprecationWarning
|
||||
from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, TypeVar, Union
|
||||
|
||||
from telegram import Update
|
||||
from telegram.ext import Filters, BaseFilter
|
||||
from telegram.ext import BaseFilter, Filters
|
||||
from telegram.utils.deprecate import TelegramDeprecationWarning
|
||||
from telegram.utils.types import HandlerArg
|
||||
from telegram.utils.helpers import DefaultValue, DEFAULT_FALSE
|
||||
|
||||
from .handler import Handler
|
||||
|
||||
from telegram.utils.types import HandlerArg
|
||||
from typing import Callable, TYPE_CHECKING, Any, Optional, Union, TypeVar, Dict
|
||||
if TYPE_CHECKING:
|
||||
from telegram.ext import CallbackContext, Dispatcher
|
||||
|
||||
@@ -120,17 +121,19 @@ class MessageHandler(Handler):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
filters: BaseFilter,
|
||||
callback: Callable[[HandlerArg, 'CallbackContext'], RT],
|
||||
pass_update_queue: bool = False,
|
||||
pass_job_queue: bool = False,
|
||||
pass_user_data: bool = False,
|
||||
pass_chat_data: bool = False,
|
||||
message_updates: bool = None,
|
||||
channel_post_updates: bool = None,
|
||||
edited_updates: bool = None,
|
||||
run_async: bool = False):
|
||||
def __init__(
|
||||
self,
|
||||
filters: BaseFilter,
|
||||
callback: Callable[[HandlerArg, 'CallbackContext'], RT],
|
||||
pass_update_queue: bool = False,
|
||||
pass_job_queue: bool = False,
|
||||
pass_user_data: bool = False,
|
||||
pass_chat_data: bool = False,
|
||||
message_updates: bool = None,
|
||||
channel_post_updates: bool = None,
|
||||
edited_updates: bool = None,
|
||||
run_async: Union[bool, DefaultValue] = DEFAULT_FALSE,
|
||||
):
|
||||
|
||||
super().__init__(
|
||||
callback,
|
||||
@@ -138,36 +141,44 @@ class MessageHandler(Handler):
|
||||
pass_job_queue=pass_job_queue,
|
||||
pass_user_data=pass_user_data,
|
||||
pass_chat_data=pass_chat_data,
|
||||
run_async=run_async)
|
||||
run_async=run_async,
|
||||
)
|
||||
if message_updates is False and channel_post_updates is False and edited_updates is False:
|
||||
raise ValueError(
|
||||
'message_updates, channel_post_updates and edited_updates are all False')
|
||||
'message_updates, channel_post_updates and edited_updates are all False'
|
||||
)
|
||||
if filters is not None:
|
||||
self.filters = Filters.update & filters
|
||||
else:
|
||||
self.filters = Filters.update
|
||||
if message_updates is not None:
|
||||
warnings.warn('message_updates is deprecated. See https://git.io/fxJuV for more info',
|
||||
TelegramDeprecationWarning,
|
||||
stacklevel=2)
|
||||
warnings.warn(
|
||||
'message_updates is deprecated. See https://git.io/fxJuV for more info',
|
||||
TelegramDeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
if message_updates is False:
|
||||
self.filters &= ~Filters.update.message
|
||||
|
||||
if channel_post_updates is not None:
|
||||
warnings.warn('channel_post_updates is deprecated. See https://git.io/fxJuV '
|
||||
'for more info',
|
||||
TelegramDeprecationWarning,
|
||||
stacklevel=2)
|
||||
warnings.warn(
|
||||
'channel_post_updates is deprecated. See https://git.io/fxJuV ' 'for more info',
|
||||
TelegramDeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
if channel_post_updates is False:
|
||||
self.filters &= ~Filters.update.channel_post
|
||||
|
||||
if edited_updates is not None:
|
||||
warnings.warn('edited_updates is deprecated. See https://git.io/fxJuV for more info',
|
||||
TelegramDeprecationWarning,
|
||||
stacklevel=2)
|
||||
warnings.warn(
|
||||
'edited_updates is deprecated. See https://git.io/fxJuV for more info',
|
||||
TelegramDeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
if edited_updates is False:
|
||||
self.filters &= ~(Filters.update.edited_message
|
||||
| Filters.update.edited_channel_post)
|
||||
self.filters &= ~(
|
||||
Filters.update.edited_message | Filters.update.edited_channel_post
|
||||
)
|
||||
|
||||
def check_update(self, update: HandlerArg) -> Optional[Union[bool, Dict[str, Any]]]:
|
||||
"""Determines whether an update should be passed to this handlers :attr:`callback`.
|
||||
@@ -183,10 +194,12 @@ class MessageHandler(Handler):
|
||||
return self.filters(update)
|
||||
return None
|
||||
|
||||
def collect_additional_context(self,
|
||||
context: 'CallbackContext',
|
||||
update: HandlerArg,
|
||||
dispatcher: 'Dispatcher',
|
||||
check_result: Optional[Union[bool, Dict[str, Any]]]) -> None:
|
||||
def collect_additional_context(
|
||||
self,
|
||||
context: 'CallbackContext',
|
||||
update: HandlerArg,
|
||||
dispatcher: 'Dispatcher',
|
||||
check_result: Optional[Union[bool, Dict[str, Any]]],
|
||||
) -> None:
|
||||
if isinstance(check_result, dict):
|
||||
context.update(check_result)
|
||||
|
||||
@@ -20,14 +20,13 @@
|
||||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/]
|
||||
"""A throughput-limiting message processor for Telegram bots."""
|
||||
from telegram.utils import promise
|
||||
|
||||
import functools
|
||||
import time
|
||||
import threading
|
||||
import queue as q
|
||||
import threading
|
||||
import time
|
||||
from typing import TYPE_CHECKING, Any, Callable, List, NoReturn
|
||||
|
||||
from typing import Callable, Any, TYPE_CHECKING, List, NoReturn
|
||||
from telegram.utils.promise import Promise
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Bot
|
||||
@@ -38,7 +37,6 @@ curtime = time.perf_counter
|
||||
|
||||
class DelayQueueError(RuntimeError):
|
||||
"""Indicates processing errors."""
|
||||
pass
|
||||
|
||||
|
||||
class DelayQueue(threading.Thread):
|
||||
@@ -75,21 +73,23 @@ class DelayQueue(threading.Thread):
|
||||
|
||||
_instcnt = 0 # instance counter
|
||||
|
||||
def __init__(self,
|
||||
queue: q.Queue = None,
|
||||
burst_limit: int = 30,
|
||||
time_limit_ms: int = 1000,
|
||||
exc_route: Callable[[Exception], None] = None,
|
||||
autostart: bool = True,
|
||||
name: str = None):
|
||||
def __init__(
|
||||
self,
|
||||
queue: q.Queue = None,
|
||||
burst_limit: int = 30,
|
||||
time_limit_ms: int = 1000,
|
||||
exc_route: Callable[[Exception], None] = None,
|
||||
autostart: bool = True,
|
||||
name: str = None,
|
||||
):
|
||||
self._queue = queue if queue is not None else q.Queue()
|
||||
self.burst_limit = burst_limit
|
||||
self.time_limit = time_limit_ms / 1000
|
||||
self.exc_route = (exc_route if exc_route is not None else self._default_exception_handler)
|
||||
self.exc_route = exc_route if exc_route is not None else self._default_exception_handler
|
||||
self.__exit_req = False # flag to gently exit thread
|
||||
self.__class__._instcnt += 1
|
||||
if name is None:
|
||||
name = '{}-{}'.format(self.__class__.__name__, self.__class__._instcnt)
|
||||
name = f'{self.__class__.__name__}-{self.__class__._instcnt}'
|
||||
super().__init__(name=name)
|
||||
self.daemon = False
|
||||
if autostart: # immediately start processing
|
||||
@@ -201,24 +201,28 @@ class MessageQueue:
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
all_burst_limit: int = 30,
|
||||
all_time_limit_ms: int = 1000,
|
||||
group_burst_limit: int = 20,
|
||||
group_time_limit_ms: int = 60000,
|
||||
exc_route: Callable[[Exception], None] = None,
|
||||
autostart: bool = True):
|
||||
def __init__(
|
||||
self,
|
||||
all_burst_limit: int = 30,
|
||||
all_time_limit_ms: int = 1000,
|
||||
group_burst_limit: int = 20,
|
||||
group_time_limit_ms: int = 60000,
|
||||
exc_route: Callable[[Exception], None] = None,
|
||||
autostart: bool = True,
|
||||
):
|
||||
# create according delay queues, use composition
|
||||
self._all_delayq = DelayQueue(
|
||||
burst_limit=all_burst_limit,
|
||||
time_limit_ms=all_time_limit_ms,
|
||||
exc_route=exc_route,
|
||||
autostart=autostart)
|
||||
autostart=autostart,
|
||||
)
|
||||
self._group_delayq = DelayQueue(
|
||||
burst_limit=group_burst_limit,
|
||||
time_limit_ms=group_time_limit_ms,
|
||||
exc_route=exc_route,
|
||||
autostart=autostart)
|
||||
autostart=autostart,
|
||||
)
|
||||
|
||||
def start(self) -> None:
|
||||
"""Method is used to manually start the ``MessageQueue`` processing."""
|
||||
@@ -297,11 +301,13 @@ def queuedmessage(method: Callable) -> Callable:
|
||||
|
||||
@functools.wraps(method)
|
||||
def wrapped(self: 'Bot', *args: Any, **kwargs: Any) -> Any:
|
||||
queued = kwargs.pop('queued',
|
||||
self._is_messages_queued_default) # type: ignore[attr-defined]
|
||||
# pylint: disable=W0212
|
||||
queued = kwargs.pop(
|
||||
'queued', self._is_messages_queued_default # type: ignore[attr-defined]
|
||||
)
|
||||
isgroup = kwargs.pop('isgroup', False)
|
||||
if queued:
|
||||
prom = promise.Promise(method, (self, ) + args, kwargs)
|
||||
prom = Promise(method, (self,) + args, kwargs)
|
||||
return self._msg_queue(prom, isgroup) # type: ignore[attr-defined]
|
||||
return method(self, *args, **kwargs)
|
||||
|
||||
|
||||
@@ -20,10 +20,9 @@
|
||||
import pickle
|
||||
from collections import defaultdict
|
||||
from copy import deepcopy
|
||||
from typing import Any, DefaultDict, Dict, Optional, Tuple
|
||||
|
||||
from telegram.ext import BasePersistence
|
||||
|
||||
from typing import DefaultDict, Dict, Any, Tuple, Optional
|
||||
from telegram.utils.types import ConversationDict
|
||||
|
||||
|
||||
@@ -74,16 +73,20 @@ class PicklePersistence(BasePersistence):
|
||||
Default is :obj:`False`.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
filename: str,
|
||||
store_user_data: bool = True,
|
||||
store_chat_data: bool = True,
|
||||
store_bot_data: bool = True,
|
||||
single_file: bool = True,
|
||||
on_flush: bool = False):
|
||||
super().__init__(store_user_data=store_user_data,
|
||||
store_chat_data=store_chat_data,
|
||||
store_bot_data=store_bot_data)
|
||||
def __init__(
|
||||
self,
|
||||
filename: str,
|
||||
store_user_data: bool = True,
|
||||
store_chat_data: bool = True,
|
||||
store_bot_data: bool = True,
|
||||
single_file: bool = True,
|
||||
on_flush: bool = False,
|
||||
):
|
||||
super().__init__(
|
||||
store_user_data=store_user_data,
|
||||
store_chat_data=store_chat_data,
|
||||
store_bot_data=store_bot_data,
|
||||
)
|
||||
self.filename = filename
|
||||
self.single_file = single_file
|
||||
self.on_flush = on_flush
|
||||
@@ -95,8 +98,8 @@ class PicklePersistence(BasePersistence):
|
||||
def load_singlefile(self) -> None:
|
||||
try:
|
||||
filename = self.filename
|
||||
with open(self.filename, "rb") as f:
|
||||
data = pickle.load(f)
|
||||
with open(self.filename, "rb") as file:
|
||||
data = pickle.load(file)
|
||||
self.user_data = defaultdict(dict, data['user_data'])
|
||||
self.chat_data = defaultdict(dict, data['chat_data'])
|
||||
# For backwards compatibility with files not containing bot data
|
||||
@@ -107,31 +110,37 @@ class PicklePersistence(BasePersistence):
|
||||
self.user_data = defaultdict(dict)
|
||||
self.chat_data = defaultdict(dict)
|
||||
self.bot_data = {}
|
||||
except pickle.UnpicklingError:
|
||||
raise TypeError("File {} does not contain valid pickle data".format(filename))
|
||||
except Exception:
|
||||
raise TypeError("Something went wrong unpickling {}".format(filename))
|
||||
except pickle.UnpicklingError as exc:
|
||||
raise TypeError(f"File {filename} does not contain valid pickle data") from exc
|
||||
except Exception as exc:
|
||||
raise TypeError(f"Something went wrong unpickling {filename}") from exc
|
||||
|
||||
def load_file(self, filename: str) -> Any:
|
||||
@staticmethod
|
||||
def load_file(filename: str) -> Any:
|
||||
try:
|
||||
with open(filename, "rb") as f:
|
||||
return pickle.load(f)
|
||||
with open(filename, "rb") as file:
|
||||
return pickle.load(file)
|
||||
except IOError:
|
||||
return None
|
||||
except pickle.UnpicklingError:
|
||||
raise TypeError("File {} does not contain valid pickle data".format(filename))
|
||||
except Exception:
|
||||
raise TypeError("Something went wrong unpickling {}".format(filename))
|
||||
except pickle.UnpicklingError as exc:
|
||||
raise TypeError(f"File {filename} does not contain valid pickle data") from exc
|
||||
except Exception as exc:
|
||||
raise TypeError(f"Something went wrong unpickling {filename}") from exc
|
||||
|
||||
def dump_singlefile(self) -> None:
|
||||
with open(self.filename, "wb") as f:
|
||||
data = {'conversations': self.conversations, 'user_data': self.user_data,
|
||||
'chat_data': self.chat_data, 'bot_data': self.bot_data}
|
||||
pickle.dump(data, f)
|
||||
with open(self.filename, "wb") as file:
|
||||
data = {
|
||||
'conversations': self.conversations,
|
||||
'user_data': self.user_data,
|
||||
'chat_data': self.chat_data,
|
||||
'bot_data': self.bot_data,
|
||||
}
|
||||
pickle.dump(data, file)
|
||||
|
||||
def dump_file(self, filename: str, data: Any) -> None:
|
||||
with open(filename, "wb") as f:
|
||||
pickle.dump(data, f)
|
||||
@staticmethod
|
||||
def dump_file(filename: str, data: Any) -> None:
|
||||
with open(filename, "wb") as file:
|
||||
pickle.dump(data, file)
|
||||
|
||||
def get_user_data(self) -> DefaultDict[int, Dict[Any, Any]]:
|
||||
"""Returns the user_data from the pickle file if it exists or an empty :obj:`defaultdict`.
|
||||
@@ -142,7 +151,7 @@ class PicklePersistence(BasePersistence):
|
||||
if self.user_data:
|
||||
pass
|
||||
elif not self.single_file:
|
||||
filename = "{}_user_data".format(self.filename)
|
||||
filename = f"{self.filename}_user_data"
|
||||
data = self.load_file(filename)
|
||||
if not data:
|
||||
data = defaultdict(dict)
|
||||
@@ -162,7 +171,7 @@ class PicklePersistence(BasePersistence):
|
||||
if self.chat_data:
|
||||
pass
|
||||
elif not self.single_file:
|
||||
filename = "{}_chat_data".format(self.filename)
|
||||
filename = f"{self.filename}_chat_data"
|
||||
data = self.load_file(filename)
|
||||
if not data:
|
||||
data = defaultdict(dict)
|
||||
@@ -182,7 +191,7 @@ class PicklePersistence(BasePersistence):
|
||||
if self.bot_data:
|
||||
pass
|
||||
elif not self.single_file:
|
||||
filename = "{}_bot_data".format(self.filename)
|
||||
filename = f"{self.filename}_bot_data"
|
||||
data = self.load_file(filename)
|
||||
if not data:
|
||||
data = {}
|
||||
@@ -203,7 +212,7 @@ class PicklePersistence(BasePersistence):
|
||||
if self.conversations:
|
||||
pass
|
||||
elif not self.single_file:
|
||||
filename = "{}_conversations".format(self.filename)
|
||||
filename = f"{self.filename}_conversations"
|
||||
data = self.load_file(filename)
|
||||
if not data:
|
||||
data = {name: {}}
|
||||
@@ -212,9 +221,9 @@ class PicklePersistence(BasePersistence):
|
||||
self.load_singlefile()
|
||||
return self.conversations.get(name, {}).copy() # type: ignore[union-attr]
|
||||
|
||||
def update_conversation(self,
|
||||
name: str, key: Tuple[int, ...],
|
||||
new_state: Optional[object]) -> None:
|
||||
def update_conversation(
|
||||
self, name: str, key: Tuple[int, ...], new_state: Optional[object]
|
||||
) -> None:
|
||||
"""Will update the conversations for the given handler and depending on :attr:`on_flush`
|
||||
save the pickle file.
|
||||
|
||||
@@ -230,7 +239,7 @@ class PicklePersistence(BasePersistence):
|
||||
self.conversations[name][key] = new_state
|
||||
if not self.on_flush:
|
||||
if not self.single_file:
|
||||
filename = "{}_conversations".format(self.filename)
|
||||
filename = f"{self.filename}_conversations"
|
||||
self.dump_file(filename, self.conversations)
|
||||
else:
|
||||
self.dump_singlefile()
|
||||
@@ -249,7 +258,7 @@ class PicklePersistence(BasePersistence):
|
||||
self.user_data[user_id] = data
|
||||
if not self.on_flush:
|
||||
if not self.single_file:
|
||||
filename = "{}_user_data".format(self.filename)
|
||||
filename = f"{self.filename}_user_data"
|
||||
self.dump_file(filename, self.user_data)
|
||||
else:
|
||||
self.dump_singlefile()
|
||||
@@ -268,7 +277,7 @@ class PicklePersistence(BasePersistence):
|
||||
self.chat_data[chat_id] = data
|
||||
if not self.on_flush:
|
||||
if not self.single_file:
|
||||
filename = "{}_chat_data".format(self.filename)
|
||||
filename = f"{self.filename}_chat_data"
|
||||
self.dump_file(filename, self.chat_data)
|
||||
else:
|
||||
self.dump_singlefile()
|
||||
@@ -284,23 +293,22 @@ class PicklePersistence(BasePersistence):
|
||||
self.bot_data = data.copy()
|
||||
if not self.on_flush:
|
||||
if not self.single_file:
|
||||
filename = "{}_bot_data".format(self.filename)
|
||||
filename = f"{self.filename}_bot_data"
|
||||
self.dump_file(filename, self.bot_data)
|
||||
else:
|
||||
self.dump_singlefile()
|
||||
|
||||
def flush(self) -> None:
|
||||
""" Will save all data in memory to pickle file(s).
|
||||
"""
|
||||
"""Will save all data in memory to pickle file(s)."""
|
||||
if self.single_file:
|
||||
if self.user_data or self.chat_data or self.bot_data or self.conversations:
|
||||
self.dump_singlefile()
|
||||
else:
|
||||
if self.user_data:
|
||||
self.dump_file("{}_user_data".format(self.filename), self.user_data)
|
||||
self.dump_file(f"{self.filename}_user_data", self.user_data)
|
||||
if self.chat_data:
|
||||
self.dump_file("{}_chat_data".format(self.filename), self.chat_data)
|
||||
self.dump_file(f"{self.filename}_chat_data", self.chat_data)
|
||||
if self.bot_data:
|
||||
self.dump_file("{}_bot_data".format(self.filename), self.bot_data)
|
||||
self.dump_file(f"{self.filename}_bot_data", self.bot_data)
|
||||
if self.conversations:
|
||||
self.dump_file("{}_conversations".format(self.filename), self.conversations)
|
||||
self.dump_file(f"{self.filename}_conversations", self.conversations)
|
||||
|
||||
@@ -16,12 +16,13 @@
|
||||
#
|
||||
# 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 PollAnswerHandler class."""
|
||||
|
||||
from telegram import Update
|
||||
from .handler import Handler
|
||||
|
||||
from telegram.utils.types import HandlerArg
|
||||
|
||||
from .handler import Handler
|
||||
|
||||
|
||||
class PollAnswerHandler(Handler):
|
||||
"""Handler class to handle Telegram updates that contain a poll answer.
|
||||
|
||||
@@ -16,12 +16,13 @@
|
||||
#
|
||||
# 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 PollHandler classes."""
|
||||
|
||||
from telegram import Update
|
||||
from .handler import Handler
|
||||
|
||||
from telegram.utils.types import HandlerArg
|
||||
|
||||
from .handler import Handler
|
||||
|
||||
|
||||
class PollHandler(Handler):
|
||||
"""Handler class to handle Telegram updates that contain a poll.
|
||||
|
||||
@@ -19,10 +19,10 @@
|
||||
"""This module contains the PreCheckoutQueryHandler class."""
|
||||
|
||||
from telegram import Update
|
||||
from .handler import Handler
|
||||
|
||||
from telegram.utils.types import HandlerArg
|
||||
|
||||
from .handler import Handler
|
||||
|
||||
|
||||
class PreCheckoutQueryHandler(Handler):
|
||||
"""Handler class to handle Telegram PreCheckout callback queries.
|
||||
|
||||
@@ -20,13 +20,13 @@
|
||||
"""This module contains the RegexHandler class."""
|
||||
|
||||
import warnings
|
||||
from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Pattern, TypeVar, Union
|
||||
|
||||
from telegram.ext import Filters, MessageHandler
|
||||
from telegram.utils.deprecate import TelegramDeprecationWarning
|
||||
|
||||
from telegram.ext import MessageHandler, Filters
|
||||
|
||||
from telegram.utils.types import HandlerArg
|
||||
from typing import Callable, TYPE_CHECKING, Any, Optional, Union, TypeVar, Dict, Pattern
|
||||
from telegram.utils.helpers import DefaultValue, DEFAULT_FALSE
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram.ext import CallbackContext, Dispatcher
|
||||
|
||||
@@ -108,41 +108,48 @@ class RegexHandler(MessageHandler):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
pattern: Union[str, Pattern],
|
||||
callback: Callable[[HandlerArg, 'CallbackContext'], RT],
|
||||
pass_groups: bool = False,
|
||||
pass_groupdict: bool = False,
|
||||
pass_update_queue: bool = False,
|
||||
pass_job_queue: bool = False,
|
||||
pass_user_data: bool = False,
|
||||
pass_chat_data: bool = False,
|
||||
allow_edited: bool = False,
|
||||
message_updates: bool = True,
|
||||
channel_post_updates: bool = False,
|
||||
edited_updates: bool = False,
|
||||
run_async: bool = False):
|
||||
warnings.warn('RegexHandler is deprecated. See https://git.io/fxJuV for more info',
|
||||
TelegramDeprecationWarning,
|
||||
stacklevel=2)
|
||||
super().__init__(Filters.regex(pattern),
|
||||
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,
|
||||
message_updates=message_updates,
|
||||
channel_post_updates=channel_post_updates,
|
||||
edited_updates=edited_updates,
|
||||
run_async=run_async)
|
||||
def __init__(
|
||||
self,
|
||||
pattern: Union[str, Pattern],
|
||||
callback: Callable[[HandlerArg, 'CallbackContext'], RT],
|
||||
pass_groups: bool = False,
|
||||
pass_groupdict: bool = False,
|
||||
pass_update_queue: bool = False,
|
||||
pass_job_queue: bool = False,
|
||||
pass_user_data: bool = False,
|
||||
pass_chat_data: bool = False,
|
||||
allow_edited: bool = False, # pylint: disable=W0613
|
||||
message_updates: bool = True,
|
||||
channel_post_updates: bool = False,
|
||||
edited_updates: bool = False,
|
||||
run_async: Union[bool, DefaultValue] = DEFAULT_FALSE,
|
||||
):
|
||||
warnings.warn(
|
||||
'RegexHandler is deprecated. See https://git.io/fxJuV for more info',
|
||||
TelegramDeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
super().__init__(
|
||||
Filters.regex(pattern),
|
||||
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,
|
||||
message_updates=message_updates,
|
||||
channel_post_updates=channel_post_updates,
|
||||
edited_updates=edited_updates,
|
||||
run_async=run_async,
|
||||
)
|
||||
self.pass_groups = pass_groups
|
||||
self.pass_groupdict = pass_groupdict
|
||||
|
||||
def collect_optional_args(
|
||||
self,
|
||||
dispatcher: 'Dispatcher',
|
||||
update: HandlerArg = None,
|
||||
check_result: Optional[Union[bool, Dict[str, Any]]] = None) -> Dict[str, Any]:
|
||||
self,
|
||||
dispatcher: 'Dispatcher',
|
||||
update: HandlerArg = None,
|
||||
check_result: Optional[Union[bool, Dict[str, Any]]] = None,
|
||||
) -> Dict[str, Any]:
|
||||
optional_args = super().collect_optional_args(dispatcher, update, check_result)
|
||||
if isinstance(check_result, dict):
|
||||
if self.pass_groups:
|
||||
|
||||
@@ -19,10 +19,10 @@
|
||||
"""This module contains the ShippingQueryHandler class."""
|
||||
|
||||
from telegram import Update
|
||||
from .handler import Handler
|
||||
|
||||
from telegram.utils.types import HandlerArg
|
||||
|
||||
from .handler import Handler
|
||||
|
||||
|
||||
class ShippingQueryHandler(Handler):
|
||||
"""Handler class to handle Telegram shipping callback queries.
|
||||
|
||||
@@ -18,10 +18,13 @@
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains the StringCommandHandler class."""
|
||||
|
||||
from .handler import Handler
|
||||
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, TypeVar, Union
|
||||
|
||||
from telegram.utils.types import HandlerArg
|
||||
from typing import Callable, TYPE_CHECKING, Any, Optional, TypeVar, Dict, List
|
||||
from telegram.utils.helpers import DefaultValue, DEFAULT_FALSE
|
||||
|
||||
from .handler import Handler
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram.ext import CallbackContext, Dispatcher
|
||||
|
||||
@@ -80,18 +83,21 @@ class StringCommandHandler(Handler):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
command: str,
|
||||
callback: Callable[[HandlerArg, 'CallbackContext'], RT],
|
||||
pass_args: bool = False,
|
||||
pass_update_queue: bool = False,
|
||||
pass_job_queue: bool = False,
|
||||
run_async: bool = False):
|
||||
def __init__(
|
||||
self,
|
||||
command: str,
|
||||
callback: Callable[[HandlerArg, 'CallbackContext'], RT],
|
||||
pass_args: bool = False,
|
||||
pass_update_queue: bool = False,
|
||||
pass_job_queue: bool = False,
|
||||
run_async: Union[bool, DefaultValue] = DEFAULT_FALSE,
|
||||
):
|
||||
super().__init__(
|
||||
callback,
|
||||
pass_update_queue=pass_update_queue,
|
||||
pass_job_queue=pass_job_queue,
|
||||
run_async=run_async)
|
||||
run_async=run_async,
|
||||
)
|
||||
self.command = command
|
||||
self.pass_args = pass_args
|
||||
|
||||
@@ -111,18 +117,22 @@ class StringCommandHandler(Handler):
|
||||
return args[1:]
|
||||
return None
|
||||
|
||||
def collect_optional_args(self,
|
||||
dispatcher: 'Dispatcher',
|
||||
update: HandlerArg = None,
|
||||
check_result: Optional[List[str]] = None) -> Dict[str, Any]:
|
||||
def collect_optional_args(
|
||||
self,
|
||||
dispatcher: 'Dispatcher',
|
||||
update: HandlerArg = None,
|
||||
check_result: Optional[List[str]] = None,
|
||||
) -> Dict[str, Any]:
|
||||
optional_args = super().collect_optional_args(dispatcher, update, check_result)
|
||||
if self.pass_args:
|
||||
optional_args['args'] = check_result
|
||||
return optional_args
|
||||
|
||||
def collect_additional_context(self,
|
||||
context: 'CallbackContext',
|
||||
update: HandlerArg,
|
||||
dispatcher: 'Dispatcher',
|
||||
check_result: Optional[List[str]]) -> None:
|
||||
def collect_additional_context(
|
||||
self,
|
||||
context: 'CallbackContext',
|
||||
update: HandlerArg,
|
||||
dispatcher: 'Dispatcher',
|
||||
check_result: Optional[List[str]],
|
||||
) -> None:
|
||||
context.args = check_result
|
||||
|
||||
@@ -19,11 +19,13 @@
|
||||
"""This module contains the StringRegexHandler class."""
|
||||
|
||||
import re
|
||||
from typing import TYPE_CHECKING, Any, Callable, Dict, Match, Optional, Pattern, TypeVar, Union
|
||||
|
||||
from telegram.utils.types import HandlerArg
|
||||
from telegram.utils.helpers import DefaultValue, DEFAULT_FALSE
|
||||
|
||||
from .handler import Handler
|
||||
|
||||
from typing import Callable, TYPE_CHECKING, Optional, TypeVar, Match, Dict, Any, Union, Pattern
|
||||
from telegram.utils.types import HandlerArg
|
||||
if TYPE_CHECKING:
|
||||
from telegram.ext import CallbackContext, Dispatcher
|
||||
|
||||
@@ -90,19 +92,22 @@ class StringRegexHandler(Handler):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
pattern: Union[str, Pattern],
|
||||
callback: Callable[[HandlerArg, 'CallbackContext'], RT],
|
||||
pass_groups: bool = False,
|
||||
pass_groupdict: bool = False,
|
||||
pass_update_queue: bool = False,
|
||||
pass_job_queue: bool = False,
|
||||
run_async: bool = False):
|
||||
def __init__(
|
||||
self,
|
||||
pattern: Union[str, Pattern],
|
||||
callback: Callable[[HandlerArg, 'CallbackContext'], RT],
|
||||
pass_groups: bool = False,
|
||||
pass_groupdict: bool = False,
|
||||
pass_update_queue: bool = False,
|
||||
pass_job_queue: bool = False,
|
||||
run_async: Union[bool, DefaultValue] = DEFAULT_FALSE,
|
||||
):
|
||||
super().__init__(
|
||||
callback,
|
||||
pass_update_queue=pass_update_queue,
|
||||
pass_job_queue=pass_job_queue,
|
||||
run_async=run_async)
|
||||
run_async=run_async,
|
||||
)
|
||||
|
||||
if isinstance(pattern, str):
|
||||
pattern = re.compile(pattern)
|
||||
@@ -127,10 +132,12 @@ class StringRegexHandler(Handler):
|
||||
return match
|
||||
return None
|
||||
|
||||
def collect_optional_args(self,
|
||||
dispatcher: 'Dispatcher',
|
||||
update: HandlerArg = None,
|
||||
check_result: Optional[Match] = None) -> Dict[str, Any]:
|
||||
def collect_optional_args(
|
||||
self,
|
||||
dispatcher: 'Dispatcher',
|
||||
update: HandlerArg = None,
|
||||
check_result: Optional[Match] = None,
|
||||
) -> Dict[str, Any]:
|
||||
optional_args = super().collect_optional_args(dispatcher, update, check_result)
|
||||
if self.pattern:
|
||||
if self.pass_groups and check_result:
|
||||
@@ -139,10 +146,12 @@ class StringRegexHandler(Handler):
|
||||
optional_args['groupdict'] = check_result.groupdict()
|
||||
return optional_args
|
||||
|
||||
def collect_additional_context(self,
|
||||
context: 'CallbackContext',
|
||||
update: HandlerArg,
|
||||
dispatcher: 'Dispatcher',
|
||||
check_result: Optional[Match]) -> None:
|
||||
def collect_additional_context(
|
||||
self,
|
||||
context: 'CallbackContext',
|
||||
update: HandlerArg,
|
||||
dispatcher: 'Dispatcher',
|
||||
check_result: Optional[Match],
|
||||
) -> None:
|
||||
if self.pattern and check_result:
|
||||
context.matches = [check_result]
|
||||
|
||||
+15
-13
@@ -18,11 +18,11 @@
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains the TypeHandler class."""
|
||||
|
||||
from typing import TYPE_CHECKING, Any, Callable, Type, TypeVar, Union
|
||||
from telegram.utils.helpers import DefaultValue, DEFAULT_FALSE
|
||||
|
||||
from .handler import Handler
|
||||
|
||||
|
||||
from typing import Callable, TYPE_CHECKING, TypeVar, Type, Any
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram.ext import CallbackContext
|
||||
|
||||
@@ -74,18 +74,21 @@ class TypeHandler(Handler):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
type: Type,
|
||||
callback: Callable[[Any, 'CallbackContext'], RT],
|
||||
strict: bool = False,
|
||||
pass_update_queue: bool = False,
|
||||
pass_job_queue: bool = False,
|
||||
run_async: bool = False):
|
||||
def __init__(
|
||||
self,
|
||||
type: Type, # pylint: disable=W0622
|
||||
callback: Callable[[Any, 'CallbackContext'], RT],
|
||||
strict: bool = False,
|
||||
pass_update_queue: bool = False,
|
||||
pass_job_queue: bool = False,
|
||||
run_async: Union[bool, DefaultValue] = DEFAULT_FALSE,
|
||||
):
|
||||
super().__init__(
|
||||
callback,
|
||||
pass_update_queue=pass_update_queue,
|
||||
pass_job_queue=pass_job_queue,
|
||||
run_async=run_async)
|
||||
run_async=run_async,
|
||||
)
|
||||
self.type = type
|
||||
self.strict = strict
|
||||
|
||||
@@ -101,5 +104,4 @@ class TypeHandler(Handler):
|
||||
"""
|
||||
if not self.strict:
|
||||
return isinstance(update, self.type)
|
||||
else:
|
||||
return type(update) is self.type
|
||||
return type(update) is self.type # pylint: disable=C0123
|
||||
|
||||
+199
-123
@@ -21,20 +21,19 @@
|
||||
import logging
|
||||
import ssl
|
||||
import warnings
|
||||
from threading import Thread, Lock, current_thread, Event
|
||||
from time import sleep
|
||||
from signal import signal, SIGINT, SIGTERM, SIGABRT
|
||||
from queue import Queue
|
||||
from signal import SIGABRT, SIGINT, SIGTERM, signal
|
||||
from threading import Event, Lock, Thread, current_thread
|
||||
from time import sleep
|
||||
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple, Union, no_type_check
|
||||
|
||||
from telegram import Bot, TelegramError
|
||||
from telegram.error import InvalidToken, RetryAfter, TimedOut, Unauthorized
|
||||
from telegram.ext import Dispatcher, JobQueue
|
||||
from telegram.error import Unauthorized, InvalidToken, RetryAfter, TimedOut
|
||||
from telegram.utils.deprecate import TelegramDeprecationWarning
|
||||
from telegram.utils.helpers import get_signal_name
|
||||
from telegram.utils.request import Request
|
||||
from telegram.utils.webhookhandler import (WebhookServer, WebhookAppClass)
|
||||
|
||||
from typing import Callable, Dict, TYPE_CHECKING, Any, List, Union, Tuple, no_type_check, Optional
|
||||
from telegram.utils.webhookhandler import WebhookAppClass, WebhookServer
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram.ext import BasePersistence, Defaults
|
||||
@@ -108,26 +107,30 @@ class Updater:
|
||||
|
||||
_request = None
|
||||
|
||||
def __init__(self,
|
||||
token: str = None,
|
||||
base_url: str = None,
|
||||
workers: int = 4,
|
||||
bot: Bot = None,
|
||||
private_key: bytes = None,
|
||||
private_key_password: bytes = None,
|
||||
user_sig_handler: Callable = None,
|
||||
request_kwargs: Dict[str, Any] = None,
|
||||
persistence: 'BasePersistence' = None,
|
||||
defaults: 'Defaults' = None,
|
||||
use_context: bool = True,
|
||||
dispatcher: Dispatcher = None,
|
||||
base_file_url: str = None):
|
||||
def __init__(
|
||||
self,
|
||||
token: str = None,
|
||||
base_url: str = None,
|
||||
workers: int = 4,
|
||||
bot: Bot = None,
|
||||
private_key: bytes = None,
|
||||
private_key_password: bytes = None,
|
||||
user_sig_handler: Callable = None,
|
||||
request_kwargs: Dict[str, Any] = None,
|
||||
persistence: 'BasePersistence' = None,
|
||||
defaults: 'Defaults' = None,
|
||||
use_context: bool = True,
|
||||
dispatcher: Dispatcher = None,
|
||||
base_file_url: str = None,
|
||||
):
|
||||
|
||||
if defaults and bot:
|
||||
warnings.warn('Passing defaults to an Updater has no effect when a Bot is passed '
|
||||
'as well. Pass them to the Bot instead.',
|
||||
TelegramDeprecationWarning,
|
||||
stacklevel=2)
|
||||
warnings.warn(
|
||||
'Passing defaults to an Updater has no effect when a Bot is passed '
|
||||
'as well. Pass them to the Bot instead.',
|
||||
TelegramDeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
if dispatcher is None:
|
||||
if (token is None) and (bot is None):
|
||||
@@ -156,7 +159,8 @@ class Updater:
|
||||
if bot.request.con_pool_size < con_pool_size:
|
||||
self.logger.warning(
|
||||
'Connection pool of Request object is smaller than optimal value (%s)',
|
||||
con_pool_size)
|
||||
con_pool_size,
|
||||
)
|
||||
else:
|
||||
# we need a connection pool the size of:
|
||||
# * for each of the workers
|
||||
@@ -169,24 +173,28 @@ class Updater:
|
||||
if 'con_pool_size' not in request_kwargs:
|
||||
request_kwargs['con_pool_size'] = con_pool_size
|
||||
self._request = Request(**request_kwargs)
|
||||
self.bot = Bot(token, # type: ignore[arg-type]
|
||||
base_url,
|
||||
base_file_url=base_file_url,
|
||||
request=self._request,
|
||||
private_key=private_key,
|
||||
private_key_password=private_key_password,
|
||||
defaults=defaults)
|
||||
self.bot = Bot(
|
||||
token, # type: ignore[arg-type]
|
||||
base_url,
|
||||
base_file_url=base_file_url,
|
||||
request=self._request,
|
||||
private_key=private_key,
|
||||
private_key_password=private_key_password,
|
||||
defaults=defaults,
|
||||
)
|
||||
self.update_queue: Queue = Queue()
|
||||
self.job_queue = JobQueue()
|
||||
self.__exception_event = Event()
|
||||
self.persistence = persistence
|
||||
self.dispatcher = Dispatcher(self.bot,
|
||||
self.update_queue,
|
||||
job_queue=self.job_queue,
|
||||
workers=workers,
|
||||
exception_event=self.__exception_event,
|
||||
persistence=persistence,
|
||||
use_context=use_context)
|
||||
self.dispatcher = Dispatcher(
|
||||
self.bot,
|
||||
self.update_queue,
|
||||
job_queue=self.job_queue,
|
||||
workers=workers,
|
||||
exception_event=self.__exception_event,
|
||||
persistence=persistence,
|
||||
use_context=use_context,
|
||||
)
|
||||
self.job_queue.set_dispatcher(self.dispatcher)
|
||||
else:
|
||||
con_pool_size = dispatcher.workers + 4
|
||||
@@ -195,7 +203,8 @@ class Updater:
|
||||
if self.bot.request.con_pool_size < con_pool_size:
|
||||
self.logger.warning(
|
||||
'Connection pool of Request object is smaller than optimal value (%s)',
|
||||
con_pool_size)
|
||||
con_pool_size,
|
||||
)
|
||||
self.update_queue = dispatcher.update_queue
|
||||
self.__exception_event = dispatcher.exception_event
|
||||
self.persistence = dispatcher.persistence
|
||||
@@ -211,31 +220,35 @@ class Updater:
|
||||
self.__threads: List[Thread] = []
|
||||
|
||||
def _init_thread(self, target: Callable, name: str, *args: Any, **kwargs: Any) -> None:
|
||||
thr = Thread(target=self._thread_wrapper,
|
||||
name="Bot:{}:{}".format(self.bot.id, name),
|
||||
args=(target,) + args,
|
||||
kwargs=kwargs)
|
||||
thr = Thread(
|
||||
target=self._thread_wrapper,
|
||||
name=f"Bot:{self.bot.id}:{name}",
|
||||
args=(target,) + args,
|
||||
kwargs=kwargs,
|
||||
)
|
||||
thr.start()
|
||||
self.__threads.append(thr)
|
||||
|
||||
def _thread_wrapper(self, target: Callable, *args: Any, **kwargs: Any) -> None:
|
||||
thr_name = current_thread().name
|
||||
self.logger.debug('{} - started'.format(thr_name))
|
||||
self.logger.debug('%s - started', thr_name)
|
||||
try:
|
||||
target(*args, **kwargs)
|
||||
except Exception:
|
||||
self.__exception_event.set()
|
||||
self.logger.exception('unhandled exception in %s', thr_name)
|
||||
raise
|
||||
self.logger.debug('{} - ended'.format(thr_name))
|
||||
self.logger.debug('%s - ended', thr_name)
|
||||
|
||||
def start_polling(self,
|
||||
poll_interval: float = 0.0,
|
||||
timeout: float = 10,
|
||||
clean: bool = False,
|
||||
bootstrap_retries: int = -1,
|
||||
read_latency: float = 2.,
|
||||
allowed_updates: List[str] = None) -> Optional[Queue]:
|
||||
def start_polling(
|
||||
self,
|
||||
poll_interval: float = 0.0,
|
||||
timeout: float = 10,
|
||||
clean: bool = False,
|
||||
bootstrap_retries: int = -1,
|
||||
read_latency: float = 2.0,
|
||||
allowed_updates: List[str] = None,
|
||||
) -> Optional[Queue]:
|
||||
"""Starts polling updates from Telegram.
|
||||
|
||||
Args:
|
||||
@@ -270,9 +283,17 @@ class Updater:
|
||||
dispatcher_ready = Event()
|
||||
polling_ready = Event()
|
||||
self._init_thread(self.dispatcher.start, "dispatcher", ready=dispatcher_ready)
|
||||
self._init_thread(self._start_polling, "updater", poll_interval, timeout,
|
||||
read_latency, bootstrap_retries, clean, allowed_updates,
|
||||
ready=polling_ready)
|
||||
self._init_thread(
|
||||
self._start_polling,
|
||||
"updater",
|
||||
poll_interval,
|
||||
timeout,
|
||||
read_latency,
|
||||
bootstrap_retries,
|
||||
clean,
|
||||
allowed_updates,
|
||||
ready=polling_ready,
|
||||
)
|
||||
|
||||
self.logger.debug('Waiting for Dispatcher and polling to start')
|
||||
dispatcher_ready.wait()
|
||||
@@ -282,17 +303,19 @@ class Updater:
|
||||
return self.update_queue
|
||||
return None
|
||||
|
||||
def start_webhook(self,
|
||||
listen: str = '127.0.0.1',
|
||||
port: int = 80,
|
||||
url_path: str = '',
|
||||
cert: str = None,
|
||||
key: str = None,
|
||||
clean: bool = False,
|
||||
bootstrap_retries: int = 0,
|
||||
webhook_url: str = None,
|
||||
allowed_updates: List[str] = None,
|
||||
force_event_loop: bool = False) -> Optional[Queue]:
|
||||
def start_webhook(
|
||||
self,
|
||||
listen: str = '127.0.0.1',
|
||||
port: int = 80,
|
||||
url_path: str = '',
|
||||
cert: str = None,
|
||||
key: str = None,
|
||||
clean: bool = False,
|
||||
bootstrap_retries: int = 0,
|
||||
webhook_url: str = None,
|
||||
allowed_updates: List[str] = None,
|
||||
force_event_loop: bool = False,
|
||||
) -> 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
|
||||
@@ -344,9 +367,21 @@ class Updater:
|
||||
dispatcher_ready = Event()
|
||||
self.job_queue.start()
|
||||
self._init_thread(self.dispatcher.start, "dispatcher", dispatcher_ready)
|
||||
self._init_thread(self._start_webhook, "updater", listen, port, url_path, cert,
|
||||
key, bootstrap_retries, clean, webhook_url, allowed_updates,
|
||||
ready=webhook_ready, force_event_loop=force_event_loop)
|
||||
self._init_thread(
|
||||
self._start_webhook,
|
||||
"updater",
|
||||
listen,
|
||||
port,
|
||||
url_path,
|
||||
cert,
|
||||
key,
|
||||
bootstrap_retries,
|
||||
clean,
|
||||
webhook_url,
|
||||
allowed_updates,
|
||||
ready=webhook_ready,
|
||||
force_event_loop=force_event_loop,
|
||||
)
|
||||
|
||||
self.logger.debug('Waiting for Dispatcher and Webhook to start')
|
||||
webhook_ready.wait()
|
||||
@@ -357,8 +392,16 @@ class Updater:
|
||||
return None
|
||||
|
||||
@no_type_check
|
||||
def _start_polling(self, poll_interval, timeout, read_latency, bootstrap_retries, clean,
|
||||
allowed_updates, ready=None): # pragma: no cover
|
||||
def _start_polling(
|
||||
self,
|
||||
poll_interval,
|
||||
timeout,
|
||||
read_latency,
|
||||
bootstrap_retries,
|
||||
clean,
|
||||
allowed_updates,
|
||||
ready=None,
|
||||
): # pragma: no cover
|
||||
# Thread target of thread 'updater'. Runs in background, pulls
|
||||
# updates from Telegram and inserts them in the update queue of the
|
||||
# Dispatcher.
|
||||
@@ -370,10 +413,12 @@ class Updater:
|
||||
self.logger.debug('Bootstrap done')
|
||||
|
||||
def polling_action_cb():
|
||||
updates = self.bot.get_updates(self.last_update_id,
|
||||
timeout=timeout,
|
||||
read_latency=read_latency,
|
||||
allowed_updates=allowed_updates)
|
||||
updates = self.bot.get_updates(
|
||||
self.last_update_id,
|
||||
timeout=timeout,
|
||||
read_latency=read_latency,
|
||||
allowed_updates=allowed_updates,
|
||||
)
|
||||
|
||||
if updates:
|
||||
if not self.running:
|
||||
@@ -393,8 +438,9 @@ class Updater:
|
||||
if ready is not None:
|
||||
ready.set()
|
||||
|
||||
self._network_loop_retry(polling_action_cb, polling_onerr_cb, 'getting Updates',
|
||||
poll_interval)
|
||||
self._network_loop_retry(
|
||||
polling_action_cb, polling_onerr_cb, 'getting Updates', poll_interval
|
||||
)
|
||||
|
||||
@no_type_check
|
||||
def _network_loop_retry(self, action_cb, onerr_cb, description, interval):
|
||||
@@ -418,9 +464,9 @@ class Updater:
|
||||
try:
|
||||
if not action_cb():
|
||||
break
|
||||
except RetryAfter as e:
|
||||
self.logger.info('%s', e)
|
||||
cur_interval = 0.5 + e.retry_after
|
||||
except RetryAfter as exc:
|
||||
self.logger.info('%s', exc)
|
||||
cur_interval = 0.5 + exc.retry_after
|
||||
except TimedOut as toe:
|
||||
self.logger.debug('Timed out %s: %s', description, toe)
|
||||
# If failure is due to timeout, we should retry asap.
|
||||
@@ -428,9 +474,9 @@ class Updater:
|
||||
except InvalidToken as pex:
|
||||
self.logger.error('Invalid token; aborting')
|
||||
raise pex
|
||||
except TelegramError as te:
|
||||
self.logger.error('Error while %s: %s', description, te)
|
||||
onerr_cb(te)
|
||||
except TelegramError as telegram_exc:
|
||||
self.logger.error('Error while %s: %s', description, telegram_exc)
|
||||
onerr_cb(telegram_exc)
|
||||
cur_interval = self._increase_poll_interval(cur_interval)
|
||||
else:
|
||||
cur_interval = interval
|
||||
@@ -450,12 +496,24 @@ class Updater:
|
||||
return current_interval
|
||||
|
||||
@no_type_check
|
||||
def _start_webhook(self, listen, port, url_path, cert, key, bootstrap_retries, clean,
|
||||
webhook_url, allowed_updates, ready=None, force_event_loop=False):
|
||||
def _start_webhook(
|
||||
self,
|
||||
listen,
|
||||
port,
|
||||
url_path,
|
||||
cert,
|
||||
key,
|
||||
bootstrap_retries,
|
||||
clean,
|
||||
webhook_url,
|
||||
allowed_updates,
|
||||
ready=None,
|
||||
force_event_loop=False,
|
||||
):
|
||||
self.logger.debug('Updater thread started (webhook)')
|
||||
use_ssl = cert is not None and key is not None
|
||||
if not url_path.startswith('/'):
|
||||
url_path = '/{}'.format(url_path)
|
||||
url_path = f'/{url_path}'
|
||||
|
||||
# Create Tornado app instance
|
||||
app = WebhookAppClass(url_path, self.bot, self.update_queue)
|
||||
@@ -466,8 +524,8 @@ class Updater:
|
||||
try:
|
||||
ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
|
||||
ssl_ctx.load_cert_chain(cert, key)
|
||||
except ssl.SSLError:
|
||||
raise TelegramError('Invalid SSL Certificate')
|
||||
except ssl.SSLError as exc:
|
||||
raise TelegramError('Invalid SSL Certificate') from exc
|
||||
else:
|
||||
ssl_ctx = None
|
||||
|
||||
@@ -479,29 +537,29 @@ class Updater:
|
||||
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)
|
||||
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")
|
||||
self.logger.warning(
|
||||
"cleaning updates is not supported if "
|
||||
"SSL-termination happens elsewhere; skipping"
|
||||
)
|
||||
|
||||
self.httpd.serve_forever(force_event_loop=force_event_loop, ready=ready)
|
||||
|
||||
@staticmethod
|
||||
def _gen_webhook_url(listen: str, port: int, url_path: str) -> str:
|
||||
return 'https://{listen}:{port}{path}'.format(listen=listen, port=port, path=url_path)
|
||||
return f'https://{listen}:{port}{url_path}'
|
||||
|
||||
@no_type_check
|
||||
def _bootstrap(self,
|
||||
max_retries,
|
||||
clean,
|
||||
webhook_url,
|
||||
allowed_updates,
|
||||
cert=None,
|
||||
bootstrap_interval=5):
|
||||
def _bootstrap(
|
||||
self, max_retries, clean, webhook_url, allowed_updates, cert=None, bootstrap_interval=5
|
||||
):
|
||||
retries = [0]
|
||||
|
||||
def bootstrap_del_webhook():
|
||||
@@ -516,16 +574,17 @@ class Updater:
|
||||
return False
|
||||
|
||||
def bootstrap_set_webhook():
|
||||
self.bot.set_webhook(url=webhook_url,
|
||||
certificate=cert,
|
||||
allowed_updates=allowed_updates)
|
||||
self.bot.set_webhook(
|
||||
url=webhook_url, certificate=cert, allowed_updates=allowed_updates
|
||||
)
|
||||
return False
|
||||
|
||||
def bootstrap_onerr_cb(exc):
|
||||
if not isinstance(exc, Unauthorized) and (max_retries < 0 or retries[0] < max_retries):
|
||||
retries[0] += 1
|
||||
self.logger.warning('Failed bootstrap phase; try=%s max_retries=%s', retries[0],
|
||||
max_retries)
|
||||
self.logger.warning(
|
||||
'Failed bootstrap phase; try=%s max_retries=%s', retries[0], max_retries
|
||||
)
|
||||
else:
|
||||
self.logger.error('Failed bootstrap phase after %s retries (%s)', retries[0], exc)
|
||||
raise exc
|
||||
@@ -535,22 +594,34 @@ class Updater:
|
||||
# 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:
|
||||
self._network_loop_retry(bootstrap_del_webhook, bootstrap_onerr_cb,
|
||||
'bootstrap del webhook', bootstrap_interval)
|
||||
self._network_loop_retry(
|
||||
bootstrap_del_webhook,
|
||||
bootstrap_onerr_cb,
|
||||
'bootstrap del webhook',
|
||||
bootstrap_interval,
|
||||
)
|
||||
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)
|
||||
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:
|
||||
self._network_loop_retry(bootstrap_set_webhook, bootstrap_onerr_cb,
|
||||
'bootstrap set webhook', bootstrap_interval)
|
||||
self._network_loop_retry(
|
||||
bootstrap_set_webhook,
|
||||
bootstrap_onerr_cb,
|
||||
'bootstrap set webhook',
|
||||
bootstrap_interval,
|
||||
)
|
||||
|
||||
def stop(self) -> None:
|
||||
"""Stops the polling/webhook thread, the dispatcher and the job queue."""
|
||||
@@ -573,9 +644,11 @@ class Updater:
|
||||
@no_type_check
|
||||
def _stop_httpd(self) -> None:
|
||||
if self.httpd:
|
||||
self.logger.debug('Waiting for current webhook connection to be '
|
||||
'closed... Send a Telegram message to the bot to exit '
|
||||
'immediately.')
|
||||
self.logger.debug(
|
||||
'Waiting for current webhook connection to be '
|
||||
'closed... Send a Telegram message to the bot to exit '
|
||||
'immediately.'
|
||||
)
|
||||
self.httpd.shutdown()
|
||||
self.httpd = None
|
||||
|
||||
@@ -587,17 +660,18 @@ class Updater:
|
||||
@no_type_check
|
||||
def _join_threads(self) -> None:
|
||||
for thr in self.__threads:
|
||||
self.logger.debug('Waiting for {} thread to end'.format(thr.name))
|
||||
self.logger.debug('Waiting for %s thread to end', thr.name)
|
||||
thr.join()
|
||||
self.logger.debug('{} thread has ended'.format(thr.name))
|
||||
self.logger.debug('%s thread has ended', thr.name)
|
||||
self.__threads = []
|
||||
|
||||
@no_type_check
|
||||
def signal_handler(self, signum, frame) -> None:
|
||||
self.is_idle = False
|
||||
if self.running:
|
||||
self.logger.info('Received signal {} ({}), stopping...'.format(
|
||||
signum, get_signal_name(signum)))
|
||||
self.logger.info(
|
||||
'Received signal %s (%s), stopping...', signum, get_signal_name(signum)
|
||||
)
|
||||
if self.persistence:
|
||||
# Update user_data, chat_data and bot_data before flushing
|
||||
self.dispatcher.update_persistence()
|
||||
@@ -607,7 +681,9 @@ class Updater:
|
||||
self.user_sig_handler(signum, frame)
|
||||
else:
|
||||
self.logger.warning('Exiting immediately!')
|
||||
# pylint: disable=C0415,W0212
|
||||
import os
|
||||
|
||||
os._exit(1)
|
||||
|
||||
def idle(self, stop_signals: Union[List, Tuple] = (SIGINT, SIGTERM, SIGABRT)) -> None:
|
||||
|
||||
+17
-15
@@ -17,11 +17,11 @@
|
||||
# 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 Animation."""
|
||||
from telegram import PhotoSize
|
||||
from telegram import TelegramObject
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
|
||||
from telegram import PhotoSize, TelegramObject
|
||||
from telegram.utils.types import JSONDict
|
||||
from typing import Any, Optional, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Bot, File
|
||||
|
||||
@@ -64,18 +64,20 @@ class Animation(TelegramObject):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
file_id: str,
|
||||
file_unique_id: str,
|
||||
width: int,
|
||||
height: int,
|
||||
duration: int,
|
||||
thumb: PhotoSize = None,
|
||||
file_name: str = None,
|
||||
mime_type: str = None,
|
||||
file_size: int = None,
|
||||
bot: 'Bot' = None,
|
||||
**kwargs: Any):
|
||||
def __init__(
|
||||
self,
|
||||
file_id: str,
|
||||
file_unique_id: str,
|
||||
width: int,
|
||||
height: int,
|
||||
duration: int,
|
||||
thumb: PhotoSize = None,
|
||||
file_name: str = None,
|
||||
mime_type: str = None,
|
||||
file_size: int = None,
|
||||
bot: 'Bot' = None,
|
||||
**_kwargs: Any,
|
||||
):
|
||||
# Required
|
||||
self.file_id = str(file_id)
|
||||
self.file_unique_id = str(file_unique_id)
|
||||
|
||||
+20
-13
@@ -18,10 +18,11 @@
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains an object that represents a Telegram Audio."""
|
||||
|
||||
from telegram import TelegramObject, PhotoSize
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
|
||||
from telegram import PhotoSize, TelegramObject
|
||||
from telegram.utils.types import JSONDict
|
||||
from typing import Any, Optional, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Bot, File
|
||||
|
||||
@@ -41,6 +42,7 @@ class Audio(TelegramObject):
|
||||
performer (:obj:`str`): Optional. Performer of the audio as defined by sender or by audio
|
||||
tags.
|
||||
title (:obj:`str`): Optional. Title of the audio as defined by sender or by audio tags.
|
||||
file_name (:obj:`str`): Optional. Original filename as defined by sender.
|
||||
mime_type (:obj:`str`): Optional. MIME type of the file as defined by sender.
|
||||
file_size (:obj:`int`): Optional. File size.
|
||||
thumb (:class:`telegram.PhotoSize`): Optional. Thumbnail of the album cover to
|
||||
@@ -56,6 +58,7 @@ class Audio(TelegramObject):
|
||||
performer (:obj:`str`, optional): Performer of the audio as defined by sender or by audio
|
||||
tags.
|
||||
title (:obj:`str`, optional): Title of the audio as defined by sender or by audio tags.
|
||||
file_name (:obj:`str`, optional): Original filename as defined by sender.
|
||||
mime_type (:obj:`str`, optional): MIME type of the file as defined by sender.
|
||||
file_size (:obj:`int`, optional): File size.
|
||||
thumb (:class:`telegram.PhotoSize`, optional): Thumbnail of the album cover to
|
||||
@@ -65,17 +68,20 @@ class Audio(TelegramObject):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
file_id: str,
|
||||
file_unique_id: str,
|
||||
duration: int,
|
||||
performer: str = None,
|
||||
title: str = None,
|
||||
mime_type: str = None,
|
||||
file_size: int = None,
|
||||
thumb: PhotoSize = None,
|
||||
bot: 'Bot' = None,
|
||||
**kwargs: Any):
|
||||
def __init__(
|
||||
self,
|
||||
file_id: str,
|
||||
file_unique_id: str,
|
||||
duration: int,
|
||||
performer: str = None,
|
||||
title: str = None,
|
||||
mime_type: str = None,
|
||||
file_size: int = None,
|
||||
thumb: PhotoSize = None,
|
||||
bot: 'Bot' = None,
|
||||
file_name: str = None,
|
||||
**_kwargs: Any,
|
||||
):
|
||||
# Required
|
||||
self.file_id = str(file_id)
|
||||
self.file_unique_id = str(file_unique_id)
|
||||
@@ -83,6 +89,7 @@ class Audio(TelegramObject):
|
||||
# Optionals
|
||||
self.performer = performer
|
||||
self.title = title
|
||||
self.file_name = file_name
|
||||
self.mime_type = mime_type
|
||||
self.file_size = file_size
|
||||
self.thumb = thumb
|
||||
|
||||
@@ -17,9 +17,10 @@
|
||||
# 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 ChatPhoto."""
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from telegram import TelegramObject
|
||||
|
||||
from typing import Any, TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Bot, File
|
||||
|
||||
@@ -61,13 +62,15 @@ class ChatPhoto(TelegramObject):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
small_file_id: str,
|
||||
small_file_unique_id: str,
|
||||
big_file_id: str,
|
||||
big_file_unique_id: str,
|
||||
bot: 'Bot' = None,
|
||||
**kwargs: Any):
|
||||
def __init__(
|
||||
self,
|
||||
small_file_id: str,
|
||||
small_file_unique_id: str,
|
||||
big_file_id: str,
|
||||
big_file_unique_id: str,
|
||||
bot: 'Bot' = None,
|
||||
**_kwargs: Any,
|
||||
):
|
||||
self.small_file_id = small_file_id
|
||||
self.small_file_unique_id = small_file_unique_id
|
||||
self.big_file_id = big_file_id
|
||||
@@ -75,7 +78,10 @@ class ChatPhoto(TelegramObject):
|
||||
|
||||
self.bot = bot
|
||||
|
||||
self._id_attrs = (self.small_file_unique_id, self.big_file_unique_id,)
|
||||
self._id_attrs = (
|
||||
self.small_file_unique_id,
|
||||
self.big_file_unique_id,
|
||||
)
|
||||
|
||||
def get_small_file(self, timeout: int = None, **kwargs: Any) -> 'File':
|
||||
"""Convenience wrapper over :attr:`telegram.Bot.get_file` for getting the
|
||||
|
||||
@@ -18,9 +18,10 @@
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains an object that represents a Telegram Contact."""
|
||||
|
||||
from telegram import TelegramObject
|
||||
from typing import Any
|
||||
|
||||
from telegram import TelegramObject
|
||||
|
||||
|
||||
class Contact(TelegramObject):
|
||||
"""This object represents a phone contact.
|
||||
@@ -45,13 +46,15 @@ class Contact(TelegramObject):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
phone_number: str,
|
||||
first_name: str,
|
||||
last_name: str = None,
|
||||
user_id: int = None,
|
||||
vcard: str = None,
|
||||
**kwargs: Any):
|
||||
def __init__(
|
||||
self,
|
||||
phone_number: str,
|
||||
first_name: str,
|
||||
last_name: str = None,
|
||||
user_id: int = None,
|
||||
vcard: str = None,
|
||||
**_kwargs: Any,
|
||||
):
|
||||
# Required
|
||||
self.phone_number = str(phone_number)
|
||||
self.first_name = first_name
|
||||
|
||||
+15
-11
@@ -18,10 +18,11 @@
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains an object that represents a Telegram Document."""
|
||||
|
||||
from telegram import PhotoSize, TelegramObject
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
|
||||
from telegram import PhotoSize, TelegramObject
|
||||
from telegram.utils.types import JSONDict
|
||||
from typing import Any, Optional, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Bot, File
|
||||
|
||||
@@ -57,17 +58,20 @@ class Document(TelegramObject):
|
||||
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
||||
|
||||
"""
|
||||
|
||||
_id_keys = ('file_id',)
|
||||
|
||||
def __init__(self,
|
||||
file_id: str,
|
||||
file_unique_id: str,
|
||||
thumb: PhotoSize = None,
|
||||
file_name: str = None,
|
||||
mime_type: str = None,
|
||||
file_size: int = None,
|
||||
bot: 'Bot' = None,
|
||||
**kwargs: Any):
|
||||
def __init__(
|
||||
self,
|
||||
file_id: str,
|
||||
file_unique_id: str,
|
||||
thumb: PhotoSize = None,
|
||||
file_name: str = None,
|
||||
mime_type: str = None,
|
||||
file_size: int = None,
|
||||
bot: 'Bot' = None,
|
||||
**_kwargs: Any,
|
||||
):
|
||||
# Required
|
||||
self.file_id = str(file_id)
|
||||
self.file_unique_id = str(file_unique_id)
|
||||
|
||||
+69
-43
@@ -17,16 +17,17 @@
|
||||
# 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 File."""
|
||||
import os
|
||||
import shutil
|
||||
import urllib.parse as urllib_parse
|
||||
from base64 import b64decode
|
||||
from os.path import basename
|
||||
import os
|
||||
|
||||
import urllib.parse as urllib_parse
|
||||
from typing import IO, TYPE_CHECKING, Any, Optional, Union
|
||||
|
||||
from telegram import TelegramObject
|
||||
from telegram.passport.credentials import decrypt
|
||||
from telegram.utils.helpers import is_local_file
|
||||
|
||||
from typing import Any, Optional, IO, Union, TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Bot, FileCredentials
|
||||
|
||||
@@ -68,13 +69,15 @@ class File(TelegramObject):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
file_id: str,
|
||||
file_unique_id: str,
|
||||
bot: 'Bot' = None,
|
||||
file_size: int = None,
|
||||
file_path: str = None,
|
||||
**kwargs: Any):
|
||||
def __init__(
|
||||
self,
|
||||
file_id: str,
|
||||
file_unique_id: str,
|
||||
bot: 'Bot' = None,
|
||||
file_size: int = None,
|
||||
file_path: str = None,
|
||||
**_kwargs: Any,
|
||||
):
|
||||
# Required
|
||||
self.file_id = str(file_id)
|
||||
self.file_unique_id = str(file_unique_id)
|
||||
@@ -86,10 +89,9 @@ class File(TelegramObject):
|
||||
|
||||
self._id_attrs = (self.file_unique_id,)
|
||||
|
||||
def download(self,
|
||||
custom_path: str = None,
|
||||
out: IO = None,
|
||||
timeout: int = None) -> Union[str, IO]:
|
||||
def download(
|
||||
self, custom_path: str = None, out: IO = None, timeout: int = None
|
||||
) -> Union[str, IO]:
|
||||
"""
|
||||
Download this file. By default, the file is saved in the current working directory with its
|
||||
original filename as reported by Telegram. If the file has no filename, it the file ID will
|
||||
@@ -98,7 +100,10 @@ class File(TelegramObject):
|
||||
the ``out.write`` method.
|
||||
|
||||
Note:
|
||||
:attr:`custom_path` and :attr:`out` are mutually exclusive.
|
||||
* :attr:`custom_path` and :attr:`out` are mutually exclusive.
|
||||
* If neither :attr:`custom_path` nor :attr:`out` is provided and :attr:`file_path` is
|
||||
the path of a local file (which is the case when a Bot API Server is running in
|
||||
local mode), this method will just return the path.
|
||||
|
||||
Args:
|
||||
custom_path (:obj:`str`, optional): Custom path.
|
||||
@@ -110,7 +115,7 @@ class File(TelegramObject):
|
||||
|
||||
Returns:
|
||||
:obj:`str` | :obj:`io.BufferedWriter`: The same object as :attr:`out` if specified.
|
||||
Otherwise, returns the filename downloaded to.
|
||||
Otherwise, returns the filename downloaded to or the file path of the local file.
|
||||
|
||||
Raises:
|
||||
ValueError: If both :attr:`custom_path` and :attr:`out` are passed.
|
||||
@@ -119,39 +124,57 @@ class File(TelegramObject):
|
||||
if custom_path is not None and out is not None:
|
||||
raise ValueError('custom_path and out are mutually exclusive')
|
||||
|
||||
# Convert any UTF-8 char into a url encoded ASCII string.
|
||||
url = self._get_encoded_url()
|
||||
local_file = is_local_file(self.file_path)
|
||||
|
||||
if local_file:
|
||||
url = self.file_path
|
||||
else:
|
||||
# Convert any UTF-8 char into a url encoded ASCII string.
|
||||
url = self._get_encoded_url()
|
||||
|
||||
if out:
|
||||
buf = self.bot.request.retrieve(url)
|
||||
if self._credentials:
|
||||
buf = decrypt(b64decode(self._credentials.secret),
|
||||
b64decode(self._credentials.hash),
|
||||
buf)
|
||||
if local_file:
|
||||
with open(url, 'rb') as file:
|
||||
buf = file.read()
|
||||
else:
|
||||
buf = self.bot.request.retrieve(url)
|
||||
if self._credentials:
|
||||
buf = decrypt(
|
||||
b64decode(self._credentials.secret), b64decode(self._credentials.hash), buf
|
||||
)
|
||||
out.write(buf)
|
||||
return out
|
||||
else:
|
||||
if custom_path:
|
||||
filename = custom_path
|
||||
elif self.file_path:
|
||||
filename = basename(self.file_path)
|
||||
else:
|
||||
filename = os.path.join(os.getcwd(), self.file_id)
|
||||
|
||||
buf = self.bot.request.retrieve(url, timeout=timeout)
|
||||
if self._credentials:
|
||||
buf = decrypt(b64decode(self._credentials.secret),
|
||||
b64decode(self._credentials.hash),
|
||||
buf)
|
||||
with open(filename, 'wb') as fobj:
|
||||
fobj.write(buf)
|
||||
return filename
|
||||
if custom_path and local_file:
|
||||
shutil.copyfile(self.file_path, custom_path)
|
||||
return custom_path
|
||||
|
||||
if custom_path:
|
||||
filename = custom_path
|
||||
elif local_file:
|
||||
return self.file_path
|
||||
elif self.file_path:
|
||||
filename = basename(self.file_path)
|
||||
else:
|
||||
filename = os.path.join(os.getcwd(), self.file_id)
|
||||
|
||||
buf = self.bot.request.retrieve(url, timeout=timeout)
|
||||
if self._credentials:
|
||||
buf = decrypt(
|
||||
b64decode(self._credentials.secret), b64decode(self._credentials.hash), buf
|
||||
)
|
||||
with open(filename, 'wb') as fobj:
|
||||
fobj.write(buf)
|
||||
return filename
|
||||
|
||||
def _get_encoded_url(self) -> str:
|
||||
"""Convert any UTF-8 char in :obj:`File.file_path` into a url encoded ASCII string."""
|
||||
sres = urllib_parse.urlsplit(self.file_path)
|
||||
return urllib_parse.urlunsplit(urllib_parse.SplitResult(
|
||||
sres.scheme, sres.netloc, urllib_parse.quote(sres.path), sres.query, sres.fragment))
|
||||
return urllib_parse.urlunsplit(
|
||||
urllib_parse.SplitResult(
|
||||
sres.scheme, sres.netloc, urllib_parse.quote(sres.path), sres.query, sres.fragment
|
||||
)
|
||||
)
|
||||
|
||||
def download_as_bytearray(self, buf: bytearray = None) -> bytes:
|
||||
"""Download this file and return it as a bytearray.
|
||||
@@ -166,8 +189,11 @@ class File(TelegramObject):
|
||||
"""
|
||||
if buf is None:
|
||||
buf = bytearray()
|
||||
|
||||
buf.extend(self.bot.request.retrieve(self._get_encoded_url()))
|
||||
if is_local_file(self.file_path):
|
||||
with open(self.file_path, "rb") as file:
|
||||
buf.extend(file.read())
|
||||
else:
|
||||
buf.extend(self.bot.request.retrieve(self._get_encoded_url()))
|
||||
return buf
|
||||
|
||||
def set_credentials(self, credentials: 'FileCredentials') -> None:
|
||||
|
||||
+25
-20
@@ -20,15 +20,14 @@
|
||||
"""This module contains an object that represents a Telegram InputFile."""
|
||||
|
||||
import imghdr
|
||||
import logging
|
||||
import mimetypes
|
||||
import os
|
||||
from typing import IO, Optional, Tuple
|
||||
from uuid import uuid4
|
||||
|
||||
from telegram import TelegramError
|
||||
|
||||
from typing import IO, Tuple, Optional
|
||||
|
||||
DEFAULT_MIME_TYPE = 'application/octet-stream'
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class InputFile:
|
||||
@@ -57,17 +56,17 @@ class InputFile:
|
||||
|
||||
if filename:
|
||||
self.filename = filename
|
||||
elif (hasattr(obj, 'name') and not isinstance(obj.name, int)):
|
||||
elif hasattr(obj, 'name') and not isinstance(obj.name, int):
|
||||
self.filename = os.path.basename(obj.name)
|
||||
|
||||
try:
|
||||
self.mimetype = self.is_image(self.input_file_content)
|
||||
except TelegramError:
|
||||
if self.filename:
|
||||
self.mimetype = mimetypes.guess_type(
|
||||
self.filename)[0] or DEFAULT_MIME_TYPE
|
||||
else:
|
||||
self.mimetype = DEFAULT_MIME_TYPE
|
||||
image_mime_type = self.is_image(self.input_file_content)
|
||||
if image_mime_type:
|
||||
self.mimetype = image_mime_type
|
||||
elif self.filename:
|
||||
self.mimetype = mimetypes.guess_type(self.filename)[0] or DEFAULT_MIME_TYPE
|
||||
else:
|
||||
self.mimetype = DEFAULT_MIME_TYPE
|
||||
|
||||
if not self.filename:
|
||||
self.filename = self.mimetype.replace('/', '.')
|
||||
|
||||
@@ -76,21 +75,27 @@ class InputFile:
|
||||
return self.filename, self.input_file_content, self.mimetype
|
||||
|
||||
@staticmethod
|
||||
def is_image(stream: bytes) -> str:
|
||||
def is_image(stream: bytes) -> Optional[str]:
|
||||
"""Check if the content file is an image by analyzing its headers.
|
||||
|
||||
Args:
|
||||
stream (:obj:`bytes`): A byte stream representing the content of a file.
|
||||
|
||||
Returns:
|
||||
:obj:`str`: The str mime-type of an image.
|
||||
:obj:`str` | :obj:`None`: The mime-type of an image, if the input is an image, or
|
||||
:obj:`None` else.
|
||||
|
||||
"""
|
||||
image = imghdr.what(None, stream)
|
||||
if image:
|
||||
return 'image/%s' % image
|
||||
|
||||
raise TelegramError('Could not parse file content')
|
||||
try:
|
||||
image = imghdr.what(None, stream)
|
||||
if image:
|
||||
return f'image/{image}'
|
||||
return None
|
||||
except Exception:
|
||||
logger.debug(
|
||||
"Could not parse file content. Assuming that file is not an image.", exc_info=True
|
||||
)
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def is_file(obj: object) -> bool:
|
||||
|
||||
+134
-97
@@ -18,12 +18,20 @@
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""Base class for Telegram InputMedia Objects."""
|
||||
|
||||
from telegram import TelegramObject, InputFile, PhotoSize, Animation, Video, Audio, Document
|
||||
from telegram.utils.helpers import DEFAULT_NONE, DefaultValue
|
||||
from typing import Union, List, Tuple
|
||||
|
||||
from typing import Union, IO, cast
|
||||
|
||||
from telegram.utils.types import FileLike
|
||||
from telegram import (
|
||||
Animation,
|
||||
Audio,
|
||||
Document,
|
||||
InputFile,
|
||||
PhotoSize,
|
||||
TelegramObject,
|
||||
Video,
|
||||
MessageEntity,
|
||||
)
|
||||
from telegram.utils.helpers import DEFAULT_NONE, DefaultValue, parse_file_input
|
||||
from telegram.utils.types import FileInput, JSONDict
|
||||
|
||||
|
||||
class InputMedia(TelegramObject):
|
||||
@@ -34,7 +42,18 @@ class InputMedia(TelegramObject):
|
||||
:class:`telegram.InputMediaVideo` for detailed use.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
caption_entities: Union[List[MessageEntity], Tuple[MessageEntity, ...], None] = None
|
||||
|
||||
def to_dict(self) -> JSONDict:
|
||||
data = super().to_dict()
|
||||
|
||||
if self.caption_entities:
|
||||
data['caption_entities'] = [
|
||||
ce.to_dict() for ce in self.caption_entities # pylint: disable=E1133
|
||||
]
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class InputMediaAnimation(InputMedia):
|
||||
@@ -45,6 +64,8 @@ class InputMediaAnimation(InputMedia):
|
||||
media (:obj:`str` | :class:`telegram.InputFile`): Animation to send.
|
||||
caption (:obj:`str`): Optional. Caption of the document to be sent.
|
||||
parse_mode (:obj:`str`): Optional. The parse mode to use for text formatting.
|
||||
caption_entities (List[:class:`telegram.MessageEntity`]): Optional. List of special
|
||||
entities that appear in the caption.
|
||||
thumb (:class:`telegram.InputFile`): Optional. Thumbnail of the file to send.
|
||||
width (:obj:`int`): Optional. Animation width.
|
||||
height (:obj:`int`): Optional. Animation height.
|
||||
@@ -52,11 +73,13 @@ class InputMediaAnimation(InputMedia):
|
||||
|
||||
|
||||
Args:
|
||||
media (:obj:`str` | `filelike object` | :class:`telegram.Animation`): File to send. Pass a
|
||||
media (:obj:`str` | `filelike object` | :class:`pathlib.Path` | \
|
||||
:class:`telegram.Animation`): File to send. Pass a
|
||||
file_id to send a file that exists on the Telegram servers (recommended), pass an HTTP
|
||||
URL for Telegram to get a file from the Internet. Lastly you can pass an existing
|
||||
:class:`telegram.Animation` object to send.
|
||||
thumb (`filelike object`, optional): Thumbnail of the file sent; can be ignored if
|
||||
thumb (`filelike object` | :class:`pathlib.Path`, optional): Thumbnail of the file sent;
|
||||
can be ignored if
|
||||
thumbnail generation for the file is supported server-side. The thumbnail should be
|
||||
in JPEG format and less than 200 kB in size. A thumbnail's width and height should
|
||||
not exceed 320. Ignored if the file is not uploaded using multipart/form-data.
|
||||
@@ -66,6 +89,8 @@ class InputMediaAnimation(InputMedia):
|
||||
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show
|
||||
bold, italic, fixed-width text or inline URLs in the media caption. See the constants
|
||||
in :class:`telegram.ParseMode` for the available modes.
|
||||
caption_entities (List[:class:`telegram.MessageEntity`], optional): List of special
|
||||
entities that appear in the caption, which can be specified instead of parse_mode.
|
||||
width (:obj:`int`, optional): Animation width.
|
||||
height (:obj:`int`, optional): Animation height.
|
||||
duration (:obj:`int`, optional): Animation duration.
|
||||
@@ -76,14 +101,17 @@ class InputMediaAnimation(InputMedia):
|
||||
arguments.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
media: Union[str, FileLike, Animation],
|
||||
thumb: FileLike = None,
|
||||
caption: str = None,
|
||||
parse_mode: Union[str, DefaultValue] = DEFAULT_NONE,
|
||||
width: int = None,
|
||||
height: int = None,
|
||||
duration: int = None):
|
||||
def __init__(
|
||||
self,
|
||||
media: Union[FileInput, Animation],
|
||||
thumb: FileInput = None,
|
||||
caption: str = None,
|
||||
parse_mode: Union[str, DefaultValue] = DEFAULT_NONE,
|
||||
width: int = None,
|
||||
height: int = None,
|
||||
duration: int = None,
|
||||
caption_entities: Union[List[MessageEntity], Tuple[MessageEntity, ...]] = None,
|
||||
):
|
||||
self.type = 'animation'
|
||||
|
||||
if isinstance(media, Animation):
|
||||
@@ -91,22 +119,16 @@ class InputMediaAnimation(InputMedia):
|
||||
self.width = media.width
|
||||
self.height = media.height
|
||||
self.duration = media.duration
|
||||
elif InputFile.is_file(media):
|
||||
media = cast(IO, media)
|
||||
self.media = InputFile(media, attach=True)
|
||||
else:
|
||||
self.media = media # type: ignore[assignment]
|
||||
self.media = parse_file_input(media, attach=True)
|
||||
|
||||
if thumb:
|
||||
if InputFile.is_file(thumb):
|
||||
thumb = cast(IO, thumb)
|
||||
self.thumb = InputFile(thumb, attach=True)
|
||||
else:
|
||||
self.thumb = thumb # type: ignore[assignment]
|
||||
self.thumb = parse_file_input(thumb, attach=True)
|
||||
|
||||
if caption:
|
||||
self.caption = caption
|
||||
self.parse_mode = parse_mode
|
||||
self.caption_entities = caption_entities
|
||||
if width:
|
||||
self.width = width
|
||||
if height:
|
||||
@@ -123,9 +145,12 @@ class InputMediaPhoto(InputMedia):
|
||||
media (:obj:`str` | :class:`telegram.InputFile`): Photo to send.
|
||||
caption (:obj:`str`): Optional. Caption of the document to be sent.
|
||||
parse_mode (:obj:`str`): Optional. The parse mode to use for text formatting.
|
||||
caption_entities (List[:class:`telegram.MessageEntity`]): Optional. List of special
|
||||
entities that appear in the caption.
|
||||
|
||||
Args:
|
||||
media (:obj:`str` | `filelike object` | :class:`telegram.PhotoSize`): File to send. Pass a
|
||||
media (:obj:`str` | `filelike object` | :class:`pathlib.Path` | \
|
||||
:class:`telegram.PhotoSize`): File to send. Pass a
|
||||
file_id to send a file that exists on the Telegram servers (recommended), pass an HTTP
|
||||
URL for Telegram to get a file from the Internet. Lastly you can pass an existing
|
||||
:class:`telegram.PhotoSize` object to send.
|
||||
@@ -134,25 +159,24 @@ class InputMediaPhoto(InputMedia):
|
||||
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show
|
||||
bold, italic, fixed-width text or inline URLs in the media caption. See the constants
|
||||
in :class:`telegram.ParseMode` for the available modes.
|
||||
caption_entities (List[:class:`telegram.MessageEntity`], optional): List of special
|
||||
entities that appear in the caption, which can be specified instead of parse_mode.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
media: Union[str, FileLike, PhotoSize],
|
||||
caption: str = None,
|
||||
parse_mode: Union[str, DefaultValue] = DEFAULT_NONE):
|
||||
def __init__(
|
||||
self,
|
||||
media: Union[FileInput, PhotoSize],
|
||||
caption: str = None,
|
||||
parse_mode: Union[str, DefaultValue] = DEFAULT_NONE,
|
||||
caption_entities: Union[List[MessageEntity], Tuple[MessageEntity, ...]] = None,
|
||||
):
|
||||
self.type = 'photo'
|
||||
|
||||
if isinstance(media, PhotoSize):
|
||||
self.media: Union[str, InputFile] = media.file_id
|
||||
elif InputFile.is_file(media):
|
||||
media = cast(IO, media)
|
||||
self.media = InputFile(media, attach=True)
|
||||
else:
|
||||
self.media = media # type: ignore[assignment]
|
||||
self.media = parse_file_input(media, PhotoSize, attach=True)
|
||||
|
||||
if caption:
|
||||
self.caption = caption
|
||||
self.parse_mode = parse_mode
|
||||
self.caption_entities = caption_entities
|
||||
|
||||
|
||||
class InputMediaVideo(InputMedia):
|
||||
@@ -163,6 +187,8 @@ class InputMediaVideo(InputMedia):
|
||||
media (:obj:`str` | :class:`telegram.InputFile`): Video file to send.
|
||||
caption (:obj:`str`): Optional. Caption of the document to be sent.
|
||||
parse_mode (:obj:`str`): Optional. The parse mode to use for text formatting.
|
||||
caption_entities (List[:class:`telegram.MessageEntity`]): Optional. List of special
|
||||
entities that appear in the caption.
|
||||
width (:obj:`int`): Optional. Video width.
|
||||
height (:obj:`int`): Optional. Video height.
|
||||
duration (:obj:`int`): Optional. Video duration.
|
||||
@@ -171,7 +197,8 @@ class InputMediaVideo(InputMedia):
|
||||
thumb (:class:`telegram.InputFile`): Optional. Thumbnail of the file to send.
|
||||
|
||||
Args:
|
||||
media (:obj:`str` | `filelike object` | :class:`telegram.Video`): File to send. Pass a
|
||||
media (:obj:`str` | `filelike object` | :class:`pathlib.Path` | :class:`telegram.Video`):
|
||||
File to send. Pass a
|
||||
file_id to send a file that exists on the Telegram servers (recommended), pass an HTTP
|
||||
URL for Telegram to get a file from the Internet. Lastly you can pass an existing
|
||||
:class:`telegram.Video` object to send.
|
||||
@@ -180,12 +207,15 @@ class InputMediaVideo(InputMedia):
|
||||
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show
|
||||
bold, italic, fixed-width text or inline URLs in the media caption. See the constants
|
||||
in :class:`telegram.ParseMode` for the available modes.
|
||||
caption_entities (List[:class:`telegram.MessageEntity`], optional): List of special
|
||||
entities that appear in the caption, which can be specified instead of parse_mode.
|
||||
width (:obj:`int`, optional): Video width.
|
||||
height (:obj:`int`, optional): Video height.
|
||||
duration (:obj:`int`, optional): Video duration.
|
||||
supports_streaming (:obj:`bool`, optional): Pass :obj:`True`, if the uploaded video is
|
||||
suitable for streaming.
|
||||
thumb (`filelike object`, optional): Thumbnail of the file sent; can be ignored if
|
||||
thumb (`filelike object` | :class:`pathlib.Path`, optional): Thumbnail of the file sent;
|
||||
can be ignored if
|
||||
thumbnail generation for the file is supported server-side. The thumbnail should be
|
||||
in JPEG format and less than 200 kB in size. A thumbnail's width and height should
|
||||
not exceed 320. Ignored if the file is not uploaded using multipart/form-data.
|
||||
@@ -200,15 +230,18 @@ class InputMediaVideo(InputMedia):
|
||||
by Telegram.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
media: Union[str, FileLike, Video],
|
||||
caption: str = None,
|
||||
width: int = None,
|
||||
height: int = None,
|
||||
duration: int = None,
|
||||
supports_streaming: bool = None,
|
||||
parse_mode: Union[str, DefaultValue] = DEFAULT_NONE,
|
||||
thumb: FileLike = None):
|
||||
def __init__(
|
||||
self,
|
||||
media: Union[FileInput, Video],
|
||||
caption: str = None,
|
||||
width: int = None,
|
||||
height: int = None,
|
||||
duration: int = None,
|
||||
supports_streaming: bool = None,
|
||||
parse_mode: Union[str, DefaultValue] = DEFAULT_NONE,
|
||||
thumb: FileInput = None,
|
||||
caption_entities: Union[List[MessageEntity], Tuple[MessageEntity, ...]] = None,
|
||||
):
|
||||
self.type = 'video'
|
||||
|
||||
if isinstance(media, Video):
|
||||
@@ -216,22 +249,16 @@ class InputMediaVideo(InputMedia):
|
||||
self.width = media.width
|
||||
self.height = media.height
|
||||
self.duration = media.duration
|
||||
elif InputFile.is_file(media):
|
||||
media = cast(IO, media)
|
||||
self.media = InputFile(media, attach=True)
|
||||
else:
|
||||
self.media = media # type: ignore[assignment]
|
||||
self.media = parse_file_input(media, attach=True)
|
||||
|
||||
if thumb:
|
||||
if InputFile.is_file(thumb):
|
||||
thumb = cast(IO, thumb)
|
||||
self.thumb = InputFile(thumb, attach=True)
|
||||
else:
|
||||
self.thumb = thumb # type: ignore[assignment]
|
||||
self.thumb = parse_file_input(thumb, attach=True)
|
||||
|
||||
if caption:
|
||||
self.caption = caption
|
||||
self.parse_mode = parse_mode
|
||||
self.caption_entities = caption_entities
|
||||
if width:
|
||||
self.width = width
|
||||
if height:
|
||||
@@ -250,6 +277,8 @@ class InputMediaAudio(InputMedia):
|
||||
media (:obj:`str` | :class:`telegram.InputFile`): Audio file to send.
|
||||
caption (:obj:`str`): Optional. Caption of the document to be sent.
|
||||
parse_mode (:obj:`str`): Optional. The parse mode to use for text formatting.
|
||||
caption_entities (List[:class:`telegram.MessageEntity`]): Optional. List of special
|
||||
entities that appear in the caption.
|
||||
duration (:obj:`int`): Duration of the audio in seconds.
|
||||
performer (:obj:`str`): Optional. Performer of the audio as defined by sender or by audio
|
||||
tags.
|
||||
@@ -257,7 +286,8 @@ class InputMediaAudio(InputMedia):
|
||||
thumb (:class:`telegram.InputFile`): Optional. Thumbnail of the file to send.
|
||||
|
||||
Args:
|
||||
media (:obj:`str` | `filelike object` | :class:`telegram.Audio`): File to send. Pass a
|
||||
media (:obj:`str` | `filelike object` | :class:`pathlib.Path` | :class:`telegram.Audio`):
|
||||
File to send. Pass a
|
||||
file_id to send a file that exists on the Telegram servers (recommended), pass an HTTP
|
||||
URL for Telegram to get a file from the Internet. Lastly you can pass an existing
|
||||
:class:`telegram.Audio` object to send.
|
||||
@@ -266,11 +296,14 @@ class InputMediaAudio(InputMedia):
|
||||
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show
|
||||
bold, italic, fixed-width text or inline URLs in the media caption. See the constants
|
||||
in :class:`telegram.ParseMode` for the available modes.
|
||||
caption_entities (List[:class:`telegram.MessageEntity`], optional): List of special
|
||||
entities that appear in the caption, which can be specified instead of parse_mode.
|
||||
duration (:obj:`int`): Duration of the audio in seconds as defined by sender.
|
||||
performer (:obj:`str`, optional): Performer of the audio as defined by sender or by audio
|
||||
tags.
|
||||
title (:obj:`str`, optional): Title of the audio as defined by sender or by audio tags.
|
||||
thumb (`filelike object`, optional): Thumbnail of the file sent; can be ignored if
|
||||
thumb (`filelike object` | :class:`pathlib.Path`, optional): Thumbnail of the file sent;
|
||||
can be ignored if
|
||||
thumbnail generation for the file is supported server-side. The thumbnail should be
|
||||
in JPEG format and less than 200 kB in size. A thumbnail's width and height should
|
||||
not exceed 320. Ignored if the file is not uploaded using multipart/form-data.
|
||||
@@ -282,14 +315,17 @@ class InputMediaAudio(InputMedia):
|
||||
optional arguments.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
media: Union[str, FileLike, Audio],
|
||||
thumb: FileLike = None,
|
||||
caption: str = None,
|
||||
parse_mode: Union[str, DefaultValue] = DEFAULT_NONE,
|
||||
duration: int = None,
|
||||
performer: str = None,
|
||||
title: str = None):
|
||||
def __init__(
|
||||
self,
|
||||
media: Union[FileInput, Audio],
|
||||
thumb: FileInput = None,
|
||||
caption: str = None,
|
||||
parse_mode: Union[str, DefaultValue] = DEFAULT_NONE,
|
||||
duration: int = None,
|
||||
performer: str = None,
|
||||
title: str = None,
|
||||
caption_entities: Union[List[MessageEntity], Tuple[MessageEntity, ...]] = None,
|
||||
):
|
||||
self.type = 'audio'
|
||||
|
||||
if isinstance(media, Audio):
|
||||
@@ -297,22 +333,16 @@ class InputMediaAudio(InputMedia):
|
||||
self.duration = media.duration
|
||||
self.performer = media.performer
|
||||
self.title = media.title
|
||||
elif InputFile.is_file(media):
|
||||
media = cast(IO, media)
|
||||
self.media = InputFile(media, attach=True)
|
||||
else:
|
||||
self.media = media # type: ignore[assignment]
|
||||
self.media = parse_file_input(media, attach=True)
|
||||
|
||||
if thumb:
|
||||
if InputFile.is_file(thumb):
|
||||
thumb = cast(IO, thumb)
|
||||
self.thumb = InputFile(thumb, attach=True)
|
||||
else:
|
||||
self.thumb = thumb # type: ignore[assignment]
|
||||
self.thumb = parse_file_input(thumb, attach=True)
|
||||
|
||||
if caption:
|
||||
self.caption = caption
|
||||
self.parse_mode = parse_mode
|
||||
self.caption_entities = caption_entities
|
||||
if duration:
|
||||
self.duration = duration
|
||||
if performer:
|
||||
@@ -329,10 +359,16 @@ class InputMediaDocument(InputMedia):
|
||||
media (:obj:`str` | :class:`telegram.InputFile`): File to send.
|
||||
caption (:obj:`str`): Optional. Caption of the document to be sent.
|
||||
parse_mode (:obj:`str`): Optional. The parse mode to use for text formatting.
|
||||
caption_entities (List[:class:`telegram.MessageEntity`]): Optional. List of special
|
||||
entities that appear in the caption.
|
||||
thumb (:class:`telegram.InputFile`): Optional. Thumbnail of the file to send.
|
||||
disable_content_type_detection (:obj:`bool`): Optional. Disables automatic server-side
|
||||
content type detection for files uploaded using multipart/form-data. Always true, if
|
||||
the document is sent as part of an album.
|
||||
|
||||
Args:
|
||||
media (:obj:`str` | `filelike object` | :class:`telegram.Document`): File to send. Pass a
|
||||
media (:obj:`str` | `filelike object` | :class:`pathlib.Path` | \
|
||||
:class:`telegram.Document`): File to send. Pass a
|
||||
file_id to send a file that exists on the Telegram servers (recommended), pass an HTTP
|
||||
URL for Telegram to get a file from the Internet. Lastly you can pass an existing
|
||||
:class:`telegram.Document` object to send.
|
||||
@@ -341,35 +377,36 @@ class InputMediaDocument(InputMedia):
|
||||
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show
|
||||
bold, italic, fixed-width text or inline URLs in the media caption. See the constants
|
||||
in :class:`telegram.ParseMode` for the available modes.
|
||||
thumb (`filelike object`, optional): Thumbnail of the file sent; can be ignored if
|
||||
caption_entities (List[:class:`telegram.MessageEntity`], optional): List of special
|
||||
entities that appear in the caption, which can be specified instead of parse_mode.
|
||||
thumb (`filelike object` | :class:`pathlib.Path`, optional): Thumbnail of the file sent;
|
||||
can be ignored if
|
||||
thumbnail generation for the file is supported server-side. The thumbnail should be
|
||||
in JPEG format and less than 200 kB in size. A thumbnail's width and height should
|
||||
not exceed 320. Ignored if the file is not uploaded using multipart/form-data.
|
||||
Thumbnails can't be reused and can be only uploaded as a new file.
|
||||
disable_content_type_detection (:obj:`bool`, optional): Disables automatic server-side
|
||||
content type detection for files uploaded using multipart/form-data. Always true, if
|
||||
the document is sent as part of an album.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
media: Union[str, FileLike, Document],
|
||||
thumb: FileLike = None,
|
||||
caption: str = None,
|
||||
parse_mode: Union[str, DefaultValue] = DEFAULT_NONE):
|
||||
def __init__(
|
||||
self,
|
||||
media: Union[FileInput, Document],
|
||||
thumb: FileInput = None,
|
||||
caption: str = None,
|
||||
parse_mode: Union[str, DefaultValue] = DEFAULT_NONE,
|
||||
disable_content_type_detection: bool = None,
|
||||
caption_entities: Union[List[MessageEntity], Tuple[MessageEntity, ...]] = None,
|
||||
):
|
||||
self.type = 'document'
|
||||
|
||||
if isinstance(media, Document):
|
||||
self.media: Union[str, InputFile] = media.file_id
|
||||
elif InputFile.is_file(media):
|
||||
media = cast(IO, media)
|
||||
self.media = InputFile(media, attach=True)
|
||||
else:
|
||||
self.media = media # type: ignore[assignment]
|
||||
self.media = parse_file_input(media, Document, attach=True)
|
||||
|
||||
if thumb:
|
||||
if InputFile.is_file(thumb):
|
||||
thumb = cast(IO, thumb)
|
||||
self.thumb = InputFile(thumb, attach=True)
|
||||
else:
|
||||
self.thumb = thumb # type: ignore[assignment]
|
||||
self.thumb = parse_file_input(thumb, attach=True)
|
||||
|
||||
if caption:
|
||||
self.caption = caption
|
||||
self.parse_mode = parse_mode
|
||||
self.caption_entities = caption_entities
|
||||
self.disable_content_type_detection = disable_content_type_detection
|
||||
|
||||
@@ -18,9 +18,10 @@
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains an object that represents a Telegram Location."""
|
||||
|
||||
from telegram import TelegramObject
|
||||
from typing import Any
|
||||
|
||||
from telegram import TelegramObject
|
||||
|
||||
|
||||
class Location(TelegramObject):
|
||||
"""This object represents a point on the map.
|
||||
@@ -31,17 +32,50 @@ class Location(TelegramObject):
|
||||
Attributes:
|
||||
longitude (:obj:`float`): Longitude as defined by sender.
|
||||
latitude (:obj:`float`): Latitude as defined by sender.
|
||||
horizontal_accuracy (:obj:`float`): Optional. The radius of uncertainty for the location,
|
||||
measured in meters.
|
||||
live_period (:obj:`int`): Optional. Time relative to the message sending date, during which
|
||||
the location can be updated, in seconds. For active live locations only.
|
||||
heading (:obj:`int`): Optional. The direction in which user is moving, in degrees.
|
||||
For active live locations only.
|
||||
proximity_alert_radius (:obj:`int`): Optional. Maximum distance for proximity alerts about
|
||||
approaching another chat member, in meters. For sent live locations only.
|
||||
|
||||
Args:
|
||||
longitude (:obj:`float`): Longitude as defined by sender.
|
||||
latitude (:obj:`float`): Latitude as defined by sender.
|
||||
horizontal_accuracy (:obj:`float`, optional): The radius of uncertainty for the location,
|
||||
measured in meters; 0-1500.
|
||||
live_period (:obj:`int`, optional): Time relative to the message sending date, during which
|
||||
the location can be updated, in seconds. For active live locations only.
|
||||
heading (:obj:`int`, optional): The direction in which user is moving, in degrees; 1-360.
|
||||
For active live locations only.
|
||||
proximity_alert_radius (:obj:`int`, optional): Maximum distance for proximity alerts about
|
||||
approaching another chat member, in meters. For sent live locations only.
|
||||
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, longitude: float, latitude: float, **kwargs: Any):
|
||||
def __init__(
|
||||
self,
|
||||
longitude: float,
|
||||
latitude: float,
|
||||
horizontal_accuracy: float = None,
|
||||
live_period: int = None,
|
||||
heading: int = None,
|
||||
proximity_alert_radius: int = None,
|
||||
**_kwargs: Any,
|
||||
):
|
||||
# Required
|
||||
self.longitude = float(longitude)
|
||||
self.latitude = float(latitude)
|
||||
|
||||
# Optionals
|
||||
self.horizontal_accuracy = float(horizontal_accuracy) if horizontal_accuracy else None
|
||||
self.live_period = int(live_period) if live_period else None
|
||||
self.heading = int(heading) if heading else None
|
||||
self.proximity_alert_radius = (
|
||||
int(proximity_alert_radius) if proximity_alert_radius else None
|
||||
)
|
||||
|
||||
self._id_attrs = (self.longitude, self.latitude)
|
||||
|
||||
@@ -18,9 +18,11 @@
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains an object that represents a Telegram PhotoSize."""
|
||||
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from telegram import TelegramObject
|
||||
from telegram.utils.types import JSONDict
|
||||
from typing import Any, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Bot, File
|
||||
|
||||
@@ -55,14 +57,16 @@ class PhotoSize(TelegramObject):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
file_id: str,
|
||||
file_unique_id: str,
|
||||
width: int,
|
||||
height: int,
|
||||
file_size: int = None,
|
||||
bot: 'Bot' = None,
|
||||
**kwargs: Any):
|
||||
def __init__(
|
||||
self,
|
||||
file_id: str,
|
||||
file_unique_id: str,
|
||||
width: int,
|
||||
height: int,
|
||||
file_size: int = None,
|
||||
bot: 'Bot' = None,
|
||||
**_kwargs: Any,
|
||||
):
|
||||
# Required
|
||||
self.file_id = str(file_id)
|
||||
self.file_unique_id = str(file_unique_id)
|
||||
|
||||
+39
-33
@@ -18,9 +18,11 @@
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains objects that represents stickers."""
|
||||
|
||||
from telegram import PhotoSize, TelegramObject
|
||||
from typing import TYPE_CHECKING, Any, List, Optional, ClassVar
|
||||
|
||||
from telegram import PhotoSize, TelegramObject, constants
|
||||
from telegram.utils.types import JSONDict
|
||||
from typing import Any, Optional, List, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Bot, File
|
||||
|
||||
@@ -71,19 +73,21 @@ class Sticker(TelegramObject):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
file_id: str,
|
||||
file_unique_id: str,
|
||||
width: int,
|
||||
height: int,
|
||||
is_animated: bool,
|
||||
thumb: PhotoSize = None,
|
||||
emoji: str = None,
|
||||
file_size: int = None,
|
||||
set_name: str = None,
|
||||
mask_position: 'MaskPosition' = None,
|
||||
bot: 'Bot' = None,
|
||||
**kwargs: Any):
|
||||
def __init__(
|
||||
self,
|
||||
file_id: str,
|
||||
file_unique_id: str,
|
||||
width: int,
|
||||
height: int,
|
||||
is_animated: bool,
|
||||
thumb: PhotoSize = None,
|
||||
emoji: str = None,
|
||||
file_size: int = None,
|
||||
set_name: str = None,
|
||||
mask_position: 'MaskPosition' = None,
|
||||
bot: 'Bot' = None,
|
||||
**_kwargs: Any,
|
||||
):
|
||||
# Required
|
||||
self.file_id = str(file_id)
|
||||
self.file_unique_id = str(file_unique_id)
|
||||
@@ -158,15 +162,16 @@ class StickerSet(TelegramObject):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
name: str,
|
||||
title: str,
|
||||
is_animated: bool,
|
||||
contains_masks: bool,
|
||||
stickers: List[Sticker],
|
||||
bot: 'Bot' = None,
|
||||
thumb: PhotoSize = None,
|
||||
**kwargs: Any):
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
title: str,
|
||||
is_animated: bool,
|
||||
contains_masks: bool,
|
||||
stickers: List[Sticker],
|
||||
thumb: PhotoSize = None,
|
||||
**_kwargs: Any,
|
||||
):
|
||||
self.name = name
|
||||
self.title = title
|
||||
self.is_animated = is_animated
|
||||
@@ -227,16 +232,17 @@ class MaskPosition(TelegramObject):
|
||||
scale (:obj:`float`): Mask scaling coefficient. For example, 2.0 means double size.
|
||||
|
||||
"""
|
||||
FOREHEAD: str = 'forehead'
|
||||
""":obj:`str`: 'forehead'"""
|
||||
EYES: str = 'eyes'
|
||||
""":obj:`str`: 'eyes'"""
|
||||
MOUTH: str = 'mouth'
|
||||
""":obj:`str`: 'mouth'"""
|
||||
CHIN: str = 'chin'
|
||||
""":obj:`str`: 'chin'"""
|
||||
|
||||
def __init__(self, point: str, x_shift: float, y_shift: float, scale: float, **kwargs: Any):
|
||||
FOREHEAD: ClassVar[str] = constants.STICKER_FOREHEAD
|
||||
""":const:`telegram.constants.STICKER_FOREHEAD`"""
|
||||
EYES: ClassVar[str] = constants.STICKER_EYES
|
||||
""":const:`telegram.constants.STICKER_EYES`"""
|
||||
MOUTH: ClassVar[str] = constants.STICKER_MOUTH
|
||||
""":const:`telegram.constants.STICKER_MOUTH`"""
|
||||
CHIN: ClassVar[str] = constants.STICKER_CHIN
|
||||
""":const:`telegram.constants.STICKER_CHIN`"""
|
||||
|
||||
def __init__(self, point: str, x_shift: float, y_shift: float, scale: float, **_kwargs: Any):
|
||||
self.point = point
|
||||
self.x_shift = x_shift
|
||||
self.y_shift = y_shift
|
||||
|
||||
+27
-11
@@ -18,9 +18,11 @@
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains an object that represents a Telegram Venue."""
|
||||
|
||||
from telegram import TelegramObject, Location
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
|
||||
from telegram import Location, TelegramObject
|
||||
from telegram.utils.types import JSONDict
|
||||
from typing import Any, Optional, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Bot
|
||||
|
||||
@@ -31,13 +33,18 @@ class Venue(TelegramObject):
|
||||
Objects of this class are comparable in terms of equality. Two objects of this class are
|
||||
considered equal, if their :attr:`location` and :attr:`title` are equal.
|
||||
|
||||
Note:
|
||||
Foursquare details and Google Pace details are mutually exclusive. However, this
|
||||
behaviour is undocumented and might be changed by Telegram.
|
||||
|
||||
Attributes:
|
||||
location (:class:`telegram.Location`): Venue location.
|
||||
title (:obj:`str`): Name of the venue.
|
||||
address (:obj:`str`): Address of the venue.
|
||||
foursquare_id (:obj:`str`): Optional. Foursquare identifier of the venue.
|
||||
foursquare_type (:obj:`str`): Optional. Foursquare type of the venue. (For example,
|
||||
"arts_entertainment/default", "arts_entertainment/aquarium" or "food/icecream".)
|
||||
foursquare_type (:obj:`str`): Optional. Foursquare type of the venue.
|
||||
google_place_id (:obj:`str`): Optional. Google Places identifier of the venue.
|
||||
google_place_type (:obj:`str`): Optional. Google Places type of the venue.
|
||||
|
||||
Args:
|
||||
location (:class:`telegram.Location`): Venue location.
|
||||
@@ -46,17 +53,24 @@ class Venue(TelegramObject):
|
||||
foursquare_id (:obj:`str`, optional): Foursquare identifier of the venue.
|
||||
foursquare_type (:obj:`str`, optional): Foursquare type of the venue. (For example,
|
||||
"arts_entertainment/default", "arts_entertainment/aquarium" or "food/icecream".)
|
||||
google_place_id (:obj:`str`, optional): Google Places identifier of the venue.
|
||||
google_place_type (:obj:`str`, optional): Google Places type of the venue. (See
|
||||
`supported types <https://developers.google.com/places/web-service/supported_types>`_.)
|
||||
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
location: Location,
|
||||
title: str,
|
||||
address: str,
|
||||
foursquare_id: str = None,
|
||||
foursquare_type: str = None,
|
||||
**kwargs: Any):
|
||||
def __init__(
|
||||
self,
|
||||
location: Location,
|
||||
title: str,
|
||||
address: str,
|
||||
foursquare_id: str = None,
|
||||
foursquare_type: str = None,
|
||||
google_place_id: str = None,
|
||||
google_place_type: str = None,
|
||||
**_kwargs: Any,
|
||||
):
|
||||
# Required
|
||||
self.location = location
|
||||
self.title = title
|
||||
@@ -64,6 +78,8 @@ class Venue(TelegramObject):
|
||||
# Optionals
|
||||
self.foursquare_id = foursquare_id
|
||||
self.foursquare_type = foursquare_type
|
||||
self.google_place_id = google_place_id
|
||||
self.google_place_type = google_place_type
|
||||
|
||||
self._id_attrs = (self.location, self.title)
|
||||
|
||||
|
||||
+20
-12
@@ -18,9 +18,11 @@
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains an object that represents a Telegram Video."""
|
||||
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
|
||||
from telegram import PhotoSize, TelegramObject
|
||||
from telegram.utils.types import JSONDict
|
||||
from typing import Any, Optional, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Bot, File
|
||||
|
||||
@@ -40,6 +42,7 @@ class Video(TelegramObject):
|
||||
height (:obj:`int`): Video height as defined by sender.
|
||||
duration (:obj:`int`): Duration of the video in seconds as defined by sender.
|
||||
thumb (:class:`telegram.PhotoSize`): Optional. Video thumbnail.
|
||||
file_name (:obj:`str`): Optional. Original filename as defined by sender.
|
||||
mime_type (:obj:`str`): Optional. Mime type of a file as defined by sender.
|
||||
file_size (:obj:`int`): Optional. File size.
|
||||
bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods.
|
||||
@@ -54,6 +57,7 @@ class Video(TelegramObject):
|
||||
height (:obj:`int`): Video height as defined by sender.
|
||||
duration (:obj:`int`): Duration of the video in seconds as defined by sender.
|
||||
thumb (:class:`telegram.PhotoSize`, optional): Video thumbnail.
|
||||
file_name (:obj:`str`, optional): Original filename as defined by sender.
|
||||
mime_type (:obj:`str`, optional): Mime type of a file as defined by sender.
|
||||
file_size (:obj:`int`, optional): File size.
|
||||
bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods.
|
||||
@@ -61,17 +65,20 @@ class Video(TelegramObject):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
file_id: str,
|
||||
file_unique_id: str,
|
||||
width: int,
|
||||
height: int,
|
||||
duration: int,
|
||||
thumb: PhotoSize = None,
|
||||
mime_type: str = None,
|
||||
file_size: int = None,
|
||||
bot: 'Bot' = None,
|
||||
**kwargs: Any):
|
||||
def __init__(
|
||||
self,
|
||||
file_id: str,
|
||||
file_unique_id: str,
|
||||
width: int,
|
||||
height: int,
|
||||
duration: int,
|
||||
thumb: PhotoSize = None,
|
||||
mime_type: str = None,
|
||||
file_size: int = None,
|
||||
bot: 'Bot' = None,
|
||||
file_name: str = None,
|
||||
**_kwargs: Any,
|
||||
):
|
||||
# Required
|
||||
self.file_id = str(file_id)
|
||||
self.file_unique_id = str(file_unique_id)
|
||||
@@ -80,6 +87,7 @@ class Video(TelegramObject):
|
||||
self.duration = int(duration)
|
||||
# Optionals
|
||||
self.thumb = thumb
|
||||
self.file_name = file_name
|
||||
self.mime_type = mime_type
|
||||
self.file_size = file_size
|
||||
self.bot = bot
|
||||
|
||||
+14
-10
@@ -18,9 +18,11 @@
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains an object that represents a Telegram VideoNote."""
|
||||
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
|
||||
from telegram import PhotoSize, TelegramObject
|
||||
from telegram.utils.types import JSONDict
|
||||
from typing import Any, Optional, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Bot, File
|
||||
|
||||
@@ -58,15 +60,17 @@ class VideoNote(TelegramObject):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
file_id: str,
|
||||
file_unique_id: str,
|
||||
length: int,
|
||||
duration: int,
|
||||
thumb: PhotoSize = None,
|
||||
file_size: int = None,
|
||||
bot: 'Bot' = None,
|
||||
**kwargs: Any):
|
||||
def __init__(
|
||||
self,
|
||||
file_id: str,
|
||||
file_unique_id: str,
|
||||
length: int,
|
||||
duration: int,
|
||||
thumb: PhotoSize = None,
|
||||
file_size: int = None,
|
||||
bot: 'Bot' = None,
|
||||
**_kwargs: Any,
|
||||
):
|
||||
# Required
|
||||
self.file_id = str(file_id)
|
||||
self.file_unique_id = str(file_unique_id)
|
||||
|
||||
+13
-9
@@ -18,9 +18,11 @@
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains an object that represents a Telegram Voice."""
|
||||
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from telegram import TelegramObject
|
||||
from telegram.utils.types import JSONDict
|
||||
from typing import Any, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Bot, File
|
||||
|
||||
@@ -55,14 +57,16 @@ class Voice(TelegramObject):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
file_id: str,
|
||||
file_unique_id: str,
|
||||
duration: int,
|
||||
mime_type: str = None,
|
||||
file_size: int = None,
|
||||
bot: 'Bot' = None,
|
||||
**kwargs: Any):
|
||||
def __init__(
|
||||
self,
|
||||
file_id: str,
|
||||
file_unique_id: str,
|
||||
duration: int,
|
||||
mime_type: str = None,
|
||||
file_size: int = None,
|
||||
bot: 'Bot' = None,
|
||||
**_kwargs: Any,
|
||||
):
|
||||
# Required
|
||||
self.file_id = str(file_id)
|
||||
self.file_unique_id = str(file_unique_id)
|
||||
|
||||
@@ -18,9 +18,10 @@
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains an object that represents a Telegram ForceReply."""
|
||||
|
||||
from telegram import ReplyMarkup
|
||||
from typing import Any
|
||||
|
||||
from telegram import ReplyMarkup
|
||||
|
||||
|
||||
class ForceReply(ReplyMarkup):
|
||||
"""
|
||||
@@ -49,7 +50,7 @@ class ForceReply(ReplyMarkup):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, force_reply: bool = True, selective: bool = False, **kwargs: Any):
|
||||
def __init__(self, force_reply: bool = True, selective: bool = False, **_kwargs: Any):
|
||||
# Required
|
||||
self.force_reply = bool(force_reply)
|
||||
# Optionals
|
||||
|
||||
+19
-16
@@ -19,10 +19,11 @@
|
||||
"""This module contains an object that represents a Telegram Game."""
|
||||
|
||||
import sys
|
||||
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
||||
|
||||
from telegram import MessageEntity, TelegramObject, Animation, PhotoSize
|
||||
from telegram import Animation, MessageEntity, PhotoSize, TelegramObject
|
||||
from telegram.utils.types import JSONDict
|
||||
from typing import List, Any, Dict, Optional, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Bot
|
||||
|
||||
@@ -66,14 +67,16 @@ class Game(TelegramObject):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
title: str,
|
||||
description: str,
|
||||
photo: List[PhotoSize],
|
||||
text: str = None,
|
||||
text_entities: List[MessageEntity] = None,
|
||||
animation: Animation = None,
|
||||
**kwargs: Any):
|
||||
def __init__(
|
||||
self,
|
||||
title: str,
|
||||
description: str,
|
||||
photo: List[PhotoSize],
|
||||
text: str = None,
|
||||
text_entities: List[MessageEntity] = None,
|
||||
animation: Animation = None,
|
||||
**_kwargs: Any,
|
||||
):
|
||||
# Required
|
||||
self.title = title
|
||||
self.description = description
|
||||
@@ -130,11 +133,10 @@ class Game(TelegramObject):
|
||||
raise RuntimeError("This Game has no 'text'.")
|
||||
|
||||
# Is it a narrow build, if so we don't need to convert
|
||||
if sys.maxunicode == 0xffff:
|
||||
return self.text[entity.offset:entity.offset + entity.length]
|
||||
else:
|
||||
entity_text = self.text.encode('utf-16-le')
|
||||
entity_text = entity_text[entity.offset * 2:(entity.offset + entity.length) * 2]
|
||||
if sys.maxunicode == 0xFFFF:
|
||||
return self.text[entity.offset : entity.offset + entity.length]
|
||||
entity_text = self.text.encode('utf-16-le')
|
||||
entity_text = entity_text[entity.offset * 2 : (entity.offset + entity.length) * 2]
|
||||
|
||||
return entity_text.decode('utf-16-le')
|
||||
|
||||
@@ -164,7 +166,8 @@ class Game(TelegramObject):
|
||||
|
||||
return {
|
||||
entity: self.parse_text_entity(entity)
|
||||
for entity in (self.text_entities or []) if entity.type in types
|
||||
for entity in (self.text_entities or [])
|
||||
if entity.type in types
|
||||
}
|
||||
|
||||
def __hash__(self) -> int:
|
||||
|
||||
@@ -18,9 +18,11 @@
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains an object that represents a Telegram GameHighScore."""
|
||||
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from telegram import TelegramObject, User
|
||||
from telegram.utils.types import JSONDict
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Bot
|
||||
|
||||
|
||||
@@ -18,8 +18,10 @@
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains an object that represents a Telegram InlineKeyboardButton."""
|
||||
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from telegram import TelegramObject
|
||||
from typing import Any, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import CallbackGame, LoginUrl
|
||||
|
||||
@@ -81,16 +83,18 @@ class InlineKeyboardButton(TelegramObject):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
text: str,
|
||||
url: str = None,
|
||||
callback_data: str = None,
|
||||
switch_inline_query: str = None,
|
||||
switch_inline_query_current_chat: str = None,
|
||||
callback_game: 'CallbackGame' = None,
|
||||
pay: bool = None,
|
||||
login_url: 'LoginUrl' = None,
|
||||
**kwargs: Any):
|
||||
def __init__(
|
||||
self,
|
||||
text: str,
|
||||
url: str = None,
|
||||
callback_data: str = None,
|
||||
switch_inline_query: str = None,
|
||||
switch_inline_query_current_chat: str = None,
|
||||
callback_game: 'CallbackGame' = None,
|
||||
pay: bool = None,
|
||||
login_url: 'LoginUrl' = None,
|
||||
**_kwargs: Any,
|
||||
):
|
||||
# Required
|
||||
self.text = text
|
||||
|
||||
|
||||
@@ -18,9 +18,11 @@
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains an object that represents a Telegram InlineKeyboardMarkup."""
|
||||
|
||||
from telegram import ReplyMarkup, InlineKeyboardButton
|
||||
from typing import TYPE_CHECKING, Any, List, Optional
|
||||
|
||||
from telegram import InlineKeyboardButton, ReplyMarkup
|
||||
from telegram.utils.types import JSONDict
|
||||
from typing import Any, List, Optional, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Bot
|
||||
|
||||
@@ -43,7 +45,7 @@ class InlineKeyboardMarkup(ReplyMarkup):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, inline_keyboard: List[List[InlineKeyboardButton]], **kwargs: Any):
|
||||
def __init__(self, inline_keyboard: List[List[InlineKeyboardButton]], **_kwargs: Any):
|
||||
# Required
|
||||
self.inline_keyboard = inline_keyboard
|
||||
|
||||
@@ -57,8 +59,7 @@ class InlineKeyboardMarkup(ReplyMarkup):
|
||||
return data
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data: Optional[JSONDict],
|
||||
bot: 'Bot') -> Optional['InlineKeyboardMarkup']:
|
||||
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['InlineKeyboardMarkup']:
|
||||
data = cls.parse_data(data)
|
||||
|
||||
if not data:
|
||||
@@ -91,8 +92,9 @@ class InlineKeyboardMarkup(ReplyMarkup):
|
||||
return cls([[button]], **kwargs)
|
||||
|
||||
@classmethod
|
||||
def from_row(cls, button_row: List[InlineKeyboardButton],
|
||||
**kwargs: Any) -> 'InlineKeyboardMarkup':
|
||||
def from_row(
|
||||
cls, button_row: List[InlineKeyboardButton], **kwargs: Any
|
||||
) -> 'InlineKeyboardMarkup':
|
||||
"""Shortcut for::
|
||||
|
||||
InlineKeyboardMarkup([button_row], **kwargs)
|
||||
@@ -108,8 +110,9 @@ class InlineKeyboardMarkup(ReplyMarkup):
|
||||
return cls([button_row], **kwargs)
|
||||
|
||||
@classmethod
|
||||
def from_column(cls, button_column: List[InlineKeyboardButton],
|
||||
**kwargs: Any) -> 'InlineKeyboardMarkup':
|
||||
def from_column(
|
||||
cls, button_column: List[InlineKeyboardButton], **kwargs: Any
|
||||
) -> 'InlineKeyboardMarkup':
|
||||
"""Shortcut for::
|
||||
|
||||
InlineKeyboardMarkup([[button] for button in button_column], **kwargs)
|
||||
@@ -136,7 +139,7 @@ class InlineKeyboardMarkup(ReplyMarkup):
|
||||
if button != other.inline_keyboard[idx][jdx]:
|
||||
return False
|
||||
return True
|
||||
return super(InlineKeyboardMarkup, self).__eq__(other) # pylint: disable=no-member
|
||||
return super().__eq__(other)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(tuple(tuple(button for button in row) for row in self.inline_keyboard))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
# pylint: disable=R0902,R0912,R0913
|
||||
# pylint: disable=R0902,R0913
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2020
|
||||
@@ -19,9 +19,11 @@
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains an object that represents a Telegram InlineQuery."""
|
||||
|
||||
from telegram import TelegramObject, User, Location
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
|
||||
from telegram import Location, TelegramObject, User
|
||||
from telegram.utils.types import JSONDict
|
||||
from typing import Any, Optional, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Bot
|
||||
|
||||
@@ -57,16 +59,18 @@ class InlineQuery(TelegramObject):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
id: str,
|
||||
from_user: User,
|
||||
query: str,
|
||||
offset: str,
|
||||
location: Location = None,
|
||||
bot: 'Bot' = None,
|
||||
**kwargs: Any):
|
||||
def __init__(
|
||||
self,
|
||||
id: str, # pylint: disable=W0622
|
||||
from_user: User,
|
||||
query: str,
|
||||
offset: str,
|
||||
location: Location = None,
|
||||
bot: 'Bot' = None,
|
||||
**_kwargs: Any,
|
||||
):
|
||||
# Required
|
||||
self.id = id
|
||||
self.id = id # pylint: disable=C0103
|
||||
self.from_user = from_user
|
||||
self.query = query
|
||||
self.offset = offset
|
||||
@@ -125,8 +129,5 @@ class InlineQuery(TelegramObject):
|
||||
|
||||
"""
|
||||
return self.bot.answer_inline_query(
|
||||
self.id,
|
||||
*args,
|
||||
current_offset=self.offset if auto_pagination else None,
|
||||
**kwargs
|
||||
self.id, *args, current_offset=self.offset if auto_pagination else None, **kwargs
|
||||
)
|
||||
|
||||
@@ -16,11 +16,14 @@
|
||||
#
|
||||
# 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=W0622
|
||||
"""This module contains the classes that represent Telegram InlineQueryResult."""
|
||||
|
||||
from telegram import TelegramObject
|
||||
from typing import Any
|
||||
|
||||
from telegram import TelegramObject
|
||||
from telegram.utils.types import JSONDict
|
||||
|
||||
|
||||
class InlineQueryResult(TelegramObject):
|
||||
"""Baseclass for the InlineQueryResult* classes.
|
||||
@@ -39,10 +42,10 @@ class InlineQueryResult(TelegramObject):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, type: str, id: str, **kwargs: Any):
|
||||
def __init__(self, type: str, id: str, **_kwargs: Any):
|
||||
# Required
|
||||
self.type = str(type)
|
||||
self.id = str(id)
|
||||
self.id = str(id) # pylint: disable=C0103
|
||||
|
||||
self._id_attrs = (self.id,)
|
||||
|
||||
@@ -53,3 +56,17 @@ class InlineQueryResult(TelegramObject):
|
||||
@property
|
||||
def _has_input_message_content(self) -> bool:
|
||||
return hasattr(self, 'input_message_content')
|
||||
|
||||
def to_dict(self) -> JSONDict:
|
||||
data = super().to_dict()
|
||||
|
||||
# pylint: disable=E1101
|
||||
if (
|
||||
hasattr(self, 'caption_entities')
|
||||
and self.caption_entities # type: ignore[attr-defined]
|
||||
):
|
||||
data['caption_entities'] = [
|
||||
ce.to_dict() for ce in self.caption_entities # type: ignore[attr-defined]
|
||||
]
|
||||
|
||||
return data
|
||||
|
||||
@@ -18,8 +18,10 @@
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains the classes that represent Telegram InlineQueryResultArticle."""
|
||||
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from telegram import InlineQueryResult
|
||||
from typing import Any, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import InputMessageContent, ReplyMarkup
|
||||
|
||||
@@ -61,18 +63,20 @@ class InlineQueryResultArticle(InlineQueryResult):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
id: str,
|
||||
title: str,
|
||||
input_message_content: 'InputMessageContent',
|
||||
reply_markup: 'ReplyMarkup' = None,
|
||||
url: str = None,
|
||||
hide_url: bool = None,
|
||||
description: str = None,
|
||||
thumb_url: str = None,
|
||||
thumb_width: int = None,
|
||||
thumb_height: int = None,
|
||||
**kwargs: Any):
|
||||
def __init__(
|
||||
self,
|
||||
id: str, # pylint: disable=W0622
|
||||
title: str,
|
||||
input_message_content: 'InputMessageContent',
|
||||
reply_markup: 'ReplyMarkup' = None,
|
||||
url: str = None,
|
||||
hide_url: bool = None,
|
||||
description: str = None,
|
||||
thumb_url: str = None,
|
||||
thumb_width: int = None,
|
||||
thumb_height: int = None,
|
||||
**_kwargs: Any,
|
||||
):
|
||||
|
||||
# Required
|
||||
super().__init__('article', id)
|
||||
|
||||
@@ -18,9 +18,11 @@
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains the classes that represent Telegram InlineQueryResultAudio."""
|
||||
|
||||
from telegram import InlineQueryResult
|
||||
from typing import TYPE_CHECKING, Any, Union, Tuple, List
|
||||
|
||||
from telegram import InlineQueryResult, MessageEntity
|
||||
from telegram.utils.helpers import DEFAULT_NONE, DefaultValue
|
||||
from typing import Any, Union, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import InputMessageContent, ReplyMarkup
|
||||
|
||||
@@ -42,6 +44,9 @@ class InlineQueryResultAudio(InlineQueryResult):
|
||||
parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show
|
||||
bold, italic, fixed-width text or inline URLs in the media caption. See the constants
|
||||
in :class:`telegram.ParseMode` for the available modes.
|
||||
caption_entities (List[:class:`telegram.MessageEntity`]): Optional. List of special
|
||||
entities that appear in the caption, which can be specified instead of
|
||||
:attr:`parse_mode`.
|
||||
reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached
|
||||
to the message.
|
||||
input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the
|
||||
@@ -57,6 +62,9 @@ class InlineQueryResultAudio(InlineQueryResult):
|
||||
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show
|
||||
bold, italic, fixed-width text or inline URLs in the media caption. See the constants
|
||||
in :class:`telegram.ParseMode` for the available modes.
|
||||
caption_entities (List[:class:`telegram.MessageEntity`], optional): List of special
|
||||
entities that appear in the caption, which can be specified instead of
|
||||
:attr:`parse_mode`.
|
||||
reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached
|
||||
to the message.
|
||||
input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the
|
||||
@@ -65,17 +73,20 @@ class InlineQueryResultAudio(InlineQueryResult):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
id: str,
|
||||
audio_url: str,
|
||||
title: str,
|
||||
performer: str = None,
|
||||
audio_duration: int = None,
|
||||
caption: str = None,
|
||||
reply_markup: 'ReplyMarkup' = None,
|
||||
input_message_content: 'InputMessageContent' = None,
|
||||
parse_mode: Union[str, DefaultValue] = DEFAULT_NONE,
|
||||
**kwargs: Any):
|
||||
def __init__(
|
||||
self,
|
||||
id: str, # pylint: disable=W0622
|
||||
audio_url: str,
|
||||
title: str,
|
||||
performer: str = None,
|
||||
audio_duration: int = None,
|
||||
caption: str = None,
|
||||
reply_markup: 'ReplyMarkup' = None,
|
||||
input_message_content: 'InputMessageContent' = None,
|
||||
parse_mode: Union[str, DefaultValue] = DEFAULT_NONE,
|
||||
caption_entities: Union[Tuple[MessageEntity, ...], List[MessageEntity]] = None,
|
||||
**_kwargs: Any,
|
||||
):
|
||||
|
||||
# Required
|
||||
super().__init__('audio', id)
|
||||
@@ -87,5 +98,6 @@ class InlineQueryResultAudio(InlineQueryResult):
|
||||
self.audio_duration = audio_duration
|
||||
self.caption = caption
|
||||
self.parse_mode = parse_mode
|
||||
self.caption_entities = caption_entities
|
||||
self.reply_markup = reply_markup
|
||||
self.input_message_content = input_message_content
|
||||
|
||||
@@ -18,9 +18,11 @@
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains the classes that represent Telegram InlineQueryResultCachedAudio."""
|
||||
|
||||
from telegram import InlineQueryResult
|
||||
from typing import TYPE_CHECKING, Any, Union, Tuple, List
|
||||
|
||||
from telegram import InlineQueryResult, MessageEntity
|
||||
from telegram.utils.helpers import DEFAULT_NONE, DefaultValue
|
||||
from typing import Any, Union, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import InputMessageContent, ReplyMarkup
|
||||
|
||||
@@ -39,6 +41,9 @@ class InlineQueryResultCachedAudio(InlineQueryResult):
|
||||
parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show
|
||||
bold, italic, fixed-width text or inline URLs in the media caption. See the constants
|
||||
in :class:`telegram.ParseMode` for the available modes.
|
||||
caption_entities (List[:class:`telegram.MessageEntity`]): Optional. List of special
|
||||
entities that appear in the caption, which can be specified instead of
|
||||
:attr:`parse_mode`.
|
||||
reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached
|
||||
to the message.
|
||||
input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the
|
||||
@@ -51,6 +56,9 @@ class InlineQueryResultCachedAudio(InlineQueryResult):
|
||||
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show
|
||||
bold, italic, fixed-width text or inline URLs in the media caption. See the constants
|
||||
in :class:`telegram.ParseMode` for the available modes.
|
||||
caption_entities (List[:class:`telegram.MessageEntity`], optional): List of special
|
||||
entities that appear in the caption, which can be specified instead of
|
||||
:attr:`parse_mode`.
|
||||
reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached
|
||||
to the message.
|
||||
input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the
|
||||
@@ -59,14 +67,17 @@ class InlineQueryResultCachedAudio(InlineQueryResult):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
id: str,
|
||||
audio_file_id: str,
|
||||
caption: str = None,
|
||||
reply_markup: 'ReplyMarkup' = None,
|
||||
input_message_content: 'InputMessageContent' = None,
|
||||
parse_mode: Union[str, DefaultValue] = DEFAULT_NONE,
|
||||
**kwargs: Any):
|
||||
def __init__(
|
||||
self,
|
||||
id: str, # pylint: disable=W0622
|
||||
audio_file_id: str,
|
||||
caption: str = None,
|
||||
reply_markup: 'ReplyMarkup' = None,
|
||||
input_message_content: 'InputMessageContent' = None,
|
||||
parse_mode: Union[str, DefaultValue] = DEFAULT_NONE,
|
||||
caption_entities: Union[Tuple[MessageEntity, ...], List[MessageEntity]] = None,
|
||||
**_kwargs: Any,
|
||||
):
|
||||
# Required
|
||||
super().__init__('audio', id)
|
||||
self.audio_file_id = audio_file_id
|
||||
@@ -74,5 +85,6 @@ class InlineQueryResultCachedAudio(InlineQueryResult):
|
||||
# Optionals
|
||||
self.caption = caption
|
||||
self.parse_mode = parse_mode
|
||||
self.caption_entities = caption_entities
|
||||
self.reply_markup = reply_markup
|
||||
self.input_message_content = input_message_content
|
||||
|
||||
@@ -16,11 +16,14 @@
|
||||
#
|
||||
# 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=W0622
|
||||
"""This module contains the classes that represent Telegram InlineQueryResultCachedDocument."""
|
||||
|
||||
from telegram import InlineQueryResult
|
||||
from typing import TYPE_CHECKING, Any, Union, Tuple, List
|
||||
|
||||
from telegram import InlineQueryResult, MessageEntity
|
||||
from telegram.utils.helpers import DEFAULT_NONE, DefaultValue
|
||||
from typing import Any, Union, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import InputMessageContent, ReplyMarkup
|
||||
|
||||
@@ -42,6 +45,9 @@ class InlineQueryResultCachedDocument(InlineQueryResult):
|
||||
parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show
|
||||
bold, italic, fixed-width text or inline URLs in the media caption.. See the constants
|
||||
in :class:`telegram.ParseMode` for the available modes.
|
||||
caption_entities (List[:class:`telegram.MessageEntity`]): Optional. List of special
|
||||
entities that appear in the caption, which can be specified instead of
|
||||
:attr:`parse_mode`.
|
||||
reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached
|
||||
to the message.
|
||||
input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the
|
||||
@@ -57,6 +63,9 @@ class InlineQueryResultCachedDocument(InlineQueryResult):
|
||||
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show
|
||||
bold, italic, fixed-width text or inline URLs in the media caption.. See the constants
|
||||
in :class:`telegram.ParseMode` for the available modes.
|
||||
caption_entities (List[:class:`telegram.MessageEntity`], optional): List of special
|
||||
entities that appear in the caption, which can be specified instead of
|
||||
:attr:`parse_mode`.
|
||||
reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached
|
||||
to the message.
|
||||
input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the
|
||||
@@ -65,16 +74,19 @@ class InlineQueryResultCachedDocument(InlineQueryResult):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
id: str,
|
||||
title: str,
|
||||
document_file_id: str,
|
||||
description: str = None,
|
||||
caption: str = None,
|
||||
reply_markup: 'ReplyMarkup' = None,
|
||||
input_message_content: 'InputMessageContent' = None,
|
||||
parse_mode: Union[str, DefaultValue] = DEFAULT_NONE,
|
||||
**kwargs: Any):
|
||||
def __init__(
|
||||
self,
|
||||
id: str, # pylint: disable=W0622
|
||||
title: str,
|
||||
document_file_id: str,
|
||||
description: str = None,
|
||||
caption: str = None,
|
||||
reply_markup: 'ReplyMarkup' = None,
|
||||
input_message_content: 'InputMessageContent' = None,
|
||||
parse_mode: Union[str, DefaultValue] = DEFAULT_NONE,
|
||||
caption_entities: Union[Tuple[MessageEntity, ...], List[MessageEntity]] = None,
|
||||
**_kwargs: Any,
|
||||
):
|
||||
# Required
|
||||
super().__init__('document', id)
|
||||
self.title = title
|
||||
@@ -84,5 +96,6 @@ class InlineQueryResultCachedDocument(InlineQueryResult):
|
||||
self.description = description
|
||||
self.caption = caption
|
||||
self.parse_mode = parse_mode
|
||||
self.caption_entities = caption_entities
|
||||
self.reply_markup = reply_markup
|
||||
self.input_message_content = input_message_content
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user